// scripts/generate-content-alignment-test-pdf.mjs
//
// Generates `assets/content-alignment.pdf`, the document the content
// alignment visual regression test places its contents on. There is one page
// per content type, each printing a labelled grid of boxes which the contents
// of the test fixture (`packages/app-tests/constants/content-alignment-pdf.ts`)
// target, so misplacement is visible against the printed box.
//
// Keep the grid and page order in sync with `CONTENT_ALIGNMENT_GRID` and
// `CONTENT_ALIGNMENT_PAGES` in that fixture.
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { chromium } from 'playwright';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// --- Grid, as percentages of the page ---
const GRID = {
startX: 8,
startY: 12,
columnWidth: 28,
columnGap: 1,
rowHeight: 7,
// Holds each box's label, which sits just above the box.
rowGap: 2,
};
const cellLeft = (column) => GRID.startX + column * (GRID.columnWidth + GRID.columnGap);
const cellTop = (row) => GRID.startY + row * (GRID.rowHeight + GRID.rowGap);
function makeCell(row, column, label, { rows = 1, columns = 1 } = {}) {
const left = cellLeft(column);
const top = cellTop(row);
const width = columns * GRID.columnWidth + (columns - 1) * GRID.columnGap;
const height = rows * GRID.rowHeight + (rows - 1) * GRID.rowGap;
// The label is anchored to the top of its box and lifted by its own height
// (see `.label`), so it sits in the gap above the box rather than reaching
// into the row above.
return `
${label}
`;
}
function makeRowLabel(row, label) {
return `${label}
`;
}
function makeTitle(title) {
return `${title}
`;
}
// --- Page 1: Text ---
function makeTextPage() {
let content = makeTitle('Content Alignment - Text');
content += makeRowLabel(0, 'TEXT ALIGN');
content += makeCell(0, 0, 'TA_LEFT');
content += makeCell(0, 1, 'TA_CENTER');
content += makeCell(0, 2, 'TA_RIGHT');
content += makeRowLabel(1, 'VERT ALIGN');
content += makeCell(1, 0, 'VA_TOP', { rows: 2 });
content += makeCell(1, 1, 'VA_MIDDLE', { rows: 2 });
content += makeCell(1, 2, 'VA_BOTTOM', { rows: 2 });
content += makeRowLabel(3, 'FONT SIZE');
content += makeCell(3, 0, 'SIZE_8');
content += makeCell(3, 1, 'SIZE_14 RED');
content += makeCell(3, 2, 'SIZE_24 CROPPED');
content += makeRowLabel(4, 'SPACING');
content += makeCell(4, 0, 'LINE_HEIGHT_1.6');
content += makeCell(4, 1, 'LETTER_SPACING_3');
content += makeCell(4, 2, 'WRAPPED', { rows: 2 });
return content;
}
// --- Page 2: Lines ---
function makeLinePage() {
let content = makeTitle('Content Alignment - Lines');
content += makeRowLabel(0, 'STYLE');
content += makeCell(0, 0, 'SOLID_1');
content += makeCell(0, 1, 'DASHED_2');
content += makeCell(0, 2, 'DOTTED_3');
content += makeRowLabel(1, 'WIDTH');
content += makeCell(1, 0, 'WIDTH_0.5');
content += makeCell(1, 1, 'WIDTH_5');
content += makeCell(1, 2, 'WIDTH_10');
content += makeRowLabel(2, 'DIAGONAL');
content += makeCell(2, 0, 'TOP_LEFT TO BOTTOM_RIGHT', { columns: 3, rows: 2 });
content += makeRowLabel(4, 'DIAGONAL');
content += makeCell(4, 0, 'BOTTOM_LEFT TO TOP_RIGHT', { columns: 3, rows: 2 });
content += makeRowLabel(6, 'VERTICAL');
content += makeCell(6, 0, 'VERTICAL_CENTER', { rows: 2 });
content += makeCell(6, 1, 'VERTICAL_LEFT_EDGE', { rows: 2 });
content += makeCell(6, 2, 'COLORED_RED', { rows: 2 });
return content;
}
// --- Page 3: Shapes ---
function makeShapePage() {
let content = makeTitle('Content Alignment - Shapes');
content += makeRowLabel(0, 'STROKE');
content += makeCell(0, 0, 'STROKE_RED_2');
content += makeCell(0, 1, 'STROKE_DASHED_1');
content += makeCell(0, 2, 'STROKE_DOTTED_4');
content += makeRowLabel(1, 'FILL');
content += makeCell(1, 0, 'FILL_YELLOW');
content += makeCell(1, 1, 'FILL_GREEN_40%');
content += makeCell(1, 2, 'FILL_BLUE_NO_STROKE');
content += makeRowLabel(2, 'SIZE');
content += makeCell(2, 0, 'SQUARE', { rows: 2 });
content += makeCell(2, 1, 'FULL_WIDTH_BELOW', { columns: 2 });
content += makeRowLabel(4, 'ROTATION');
content += makeCell(4, 0, 'ROTATED_15', { rows: 2 });
content += makeCell(4, 1, 'ROTATED_45', { rows: 2 });
content += makeCell(4, 2, 'ROTATED_90', { rows: 2 });
// Two overlapping filled rectangles per cell. The one created first has the
// higher zIndex, so it must render on top despite creation order.
content += makeRowLabel(6, 'STACKING');
content += makeCell(6, 0, 'RED_ON_TOP (zIndex 2 over 1)', { rows: 2 });
content += makeCell(6, 1, 'BLUE_ON_TOP (zIndex 5 over 0)', { rows: 2 });
content += makeCell(6, 2, 'TIE (equal zIndex, id order)', { rows: 2 });
return content;
}
// --- Page 4: Highlights ---
function makeHighlightPage() {
let content = makeTitle('Content Alignment - Highlights');
content += makeRowLabel(0, 'COLOR');
content += makeCell(0, 0, 'YELLOW_40%');
content += makeCell(0, 1, 'GREEN_40%');
content += makeCell(0, 2, 'PINK_40%');
content += makeRowLabel(1, 'OPACITY');
content += makeCell(1, 0, 'OPACITY_10%');
content += makeCell(1, 1, 'OPACITY_60%');
content += makeCell(1, 2, 'OPACITY_100%');
content += makeRowLabel(2, 'OVER TEXT');
content += makeCell(2, 0, 'HIGHLIGHT OVER THIS PRINTED TEXT', { columns: 3 });
content += `The quick brown fox jumps over the lazy dog. A highlight is drawn over this line.
`;
return content;
}
// --- Page 5: Images ---
function makeImagePage() {
let content = makeTitle('Content Alignment - Images');
content += makeRowLabel(0, 'FIT');
content += makeCell(0, 0, 'WIDE_BOX (FILLS WIDTH)', { rows: 2 });
content += makeCell(0, 1, 'SQUARE_BOX (LETTERBOXED)', { rows: 2 });
content += makeCell(0, 2, 'TALL_BOX (CENTERED)', { rows: 3 });
content += makeRowLabel(3, 'SIZE');
content += makeCell(3, 0, 'FULL_WIDTH', { columns: 3, rows: 2 });
content += makeRowLabel(5, 'ROTATION');
content += makeCell(5, 0, 'ROTATED_15', { rows: 2 });
content += makeCell(5, 1, 'ROTATED_45', { rows: 2 });
content += makeCell(5, 2, 'ROTATED_90', { rows: 2 });
content += makeRowLabel(7, 'EDGE');
content += makeCell(7, 0, 'ROTATED_180', { rows: 2 });
content += makeCell(7, 1, 'ROTATED_270', { rows: 2 });
return content;
}
function buildPage(content) {
return `${content}
`;
}
const html = `
${buildPage(makeTextPage())}
${buildPage(makeLinePage())}
${buildPage(makeShapePage())}
${buildPage(makeHighlightPage())}
${buildPage(makeImagePage())}
`;
async function main() {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.setContent(html, { waitUntil: 'networkidle' });
const outputPath = path.join(__dirname, '../assets/content-alignment.pdf');
await page.pdf({
path: outputPath,
format: 'A4',
printBackground: true,
margin: { top: '0', right: '0', bottom: '0', left: '0' },
});
await browser.close();
console.log('PDF generated: assets/content-alignment.pdf');
}
main().catch(console.error);