diff --git a/make-pdf/test/image-policy.test.ts b/make-pdf/test/image-policy.test.ts index e46f8eb4e..b0c635519 100644 --- a/make-pdf/test/image-policy.test.ts +++ b/make-pdf/test/image-policy.test.ts @@ -17,7 +17,9 @@ import { const silent = { warn: () => {} }; // 6.5in content box → threshold = 6.5 × 96 × 2.5 = 1560 CSS px. -const OPTS = { contentWidthIn: 6.5, ...silent }; +// Letter landscape content box: 9in wide × 6.5in tall. +const LANDSCAPE = { contentWIn: 9, contentHIn: 6.5 }; +const OPTS = { contentWidthIn: 6.5, landscape: LANDSCAPE, ...silent }; function img(attrs: string): string { return `

`; @@ -131,20 +133,34 @@ describe("auto-landscape: negative cases (the load-bearing ones)", () => { }); describe("auto-landscape: positive cases", () => { - test("wide + alt hint + over threshold promotes and wraps", () => { + test("wide + alt hint + over threshold promotes, wraps, and vertically centers", () => { const warnings: string[] = []; const r = applyImagePolicy( img(`src="x" alt="architecture diagram" data-gstack-px-width="2400" data-gstack-px-height="1000"`), - { contentWidthIn: 6.5, warn: (m) => warnings.push(m) }, + { contentWidthIn: 6.5, landscape: LANDSCAPE, warn: (m) => warnings.push(m) }, + ); + expect(r.hasLandscape).toBe(true); + // placed height = 9in × (1000/2400) = 3.75in → margin-top = (6.5−3.75)/2 ≈ 1.38in + expect(r.html).toContain('
{ + // aspect 0.9 → placed height 9×0.9 = 8.1in > 6.5in box → margin clamps to 0 + const r = applyImagePolicy( + img(`src="x" data-gstack-page="landscape" data-gstack-px-width="1000" data-gstack-px-height="900"`), + OPTS, ); expect(r.hasLandscape).toBe(true); expect(r.html).toContain('
{ const r = applyImagePolicy(img(`src="x" data-gstack-page="landscape"`), OPTS); expect(r.hasLandscape).toBe(true); + // no intrinsic dims → no centering guess, top placement + expect(r.html).toContain('
{ const r = applyImagePolicy( @@ -159,10 +175,11 @@ describe("auto-landscape: diagram figures", () => { const fig = (svgAttrs: string, figAttrs = "") => ``; - test("wide diagram via viewBox promotes (provenance automatic, no alt needed)", () => { + test("wide diagram via viewBox promotes and centers (provenance automatic, no alt needed)", () => { const r = applyImagePolicy(fig(`width="100%" viewBox="0 0 2050 600"`), OPTS); expect(r.hasLandscape).toBe(true); - expect(r.html).toContain('
{ const r = applyImagePolicy(fig(`width="100%" viewBox="0 0 800 400"`), OPTS); diff --git a/make-pdf/test/render.test.ts b/make-pdf/test/render.test.ts index 413de1f98..4f5575b79 100644 --- a/make-pdf/test/render.test.ts +++ b/make-pdf/test/render.test.ts @@ -327,6 +327,33 @@ describe("printCss", () => { expect(css).toMatch(/@bottom-center\s*\{\s*content:\s*counter\(page\)/); }); + // Zero image truncation, ever: the cap must be a GLOBAL img rule. Markdown + // images render as

(no figure), so a figure-scoped cap alone lets + // wide screenshots run off the page edge — the exact regression this pins. + test("emits a global img max-width cap (zero truncation invariant)", () => { + const css = printCss(); + expect(css).toMatch(/(^|\n)img\s*\{\s*max-width:\s*100%;\s*height:\s*auto;\s*\}/); + }); + + test("typography floor: body 12pt, poster cover, readable TOC", () => { + const css = printCss({ cover: true, toc: true }); + expect(css).toContain("font-size: 12pt"); // body + expect(css).toMatch(/\.cover h1\.cover-title\s*\{[^}]*font-size:\s*56pt/); + expect(css).toMatch(/\.cover \.cover-meta\s*\{[^}]*font-size:\s*13pt/); + expect(css).toMatch(/\.toc li\s*\{[^}]*font-size:\s*12pt/); + }); + + test("page-wide carries the named page and NO height/flex centering", () => { + const css = printCss(); + expect(css).toMatch(/\.page-wide\s*\{[^}]*page:\s*wide/); + // Centering is computed by image-policy as an inline margin-top. CSS + // flex/min-height centering fragments into phantom empty landscape pages + // in Chromium — this pins the regression (landscape-gate: 5 pages for 3 + // promotions, bisected to min-height at any value). + expect(css).not.toMatch(/\.page-wide\s*\{[^}]*min-height/); + expect(css).not.toMatch(/\.page-wide\s*\{[^}]*flex/); + }); + test("font stacks include Liberation Sans adjacent to Helvetica", () => { const css = printCss({ confidential: true }); // Body stack