test(print): each print path must produce its OWN page geometry
Some checks are pending
build-gate / build (push) Waiting to run
Some checks are pending
build-gate / build (push) Waiting to run
Driver: "make sure poster generation works and the aspect ratio of the print
dialog can be custom based on your print point, so print dialog works for
poster, for vertical packet, and for horizontal cards... all working in the
jamstack maps."
One gate over all four paths, because they share one failure: silently falling
back to letter. Chromium does that for ANY @page size it does not recognise
('A6 portrait' -> 612x792pt, no error, no warning), so every assertion here is
on the MEASURED MediaBox from page.pdf(), never on the stylesheet. A gate that
grepped the CSS would pass a deck that prints as paper.
Measured, all from the shared renderers the UPG-100 page itself imports:
packet (vertical) 612 x 792 pt letter portrait
cards (poker) 180 x 252 pt 2.5 x 3.5in
cards (landscape) 252 x 180 pt 3.5 x 2.5in
poster 1500 x 2000 px rasterised, painted
Orientation is asserted separately from size, so a transposed stock cannot pass
by accident, and the poster is checked for ink rather than dimensions because it
rasterises rather than paginating.
This commit is contained in:
parent
507cf68e12
commit
347823a92b
2 changed files with 96 additions and 0 deletions
95
internal/check-print-geometry.mjs
Normal file
95
internal/check-print-geometry.mjs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
// check-print-geometry — the THREE print paths must each produce their OWN page
|
||||
// geometry, from the UPG-100 page, in JavaScript.
|
||||
//
|
||||
// Driver: "make sure poster generation works and the aspect ratio of the print
|
||||
// dialog can be custom based on your print point, so print dialog works for
|
||||
// poster, for vertical packet, and for horizontal cards... not using python but
|
||||
// all working in the jamstack maps."
|
||||
//
|
||||
// One gate over all three because the failure they share is the same: a print
|
||||
// path silently falling back to letter. Chromium does that SILENTLY for any
|
||||
// @page size it does not recognise ('A6 portrait' -> 612x792pt, no error), so
|
||||
// every assertion here is on the MEASURED MediaBox, never on the stylesheet.
|
||||
//
|
||||
// Usage: node internal/check-print-geometry.mjs
|
||||
// PACKET_ORIGIN (default http://127.0.0.1:4898) — a static server over the repo root
|
||||
import { chromium } from 'playwright'
|
||||
|
||||
const ORIGIN = process.env.PACKET_ORIGIN || 'http://127.0.0.1:4898'
|
||||
const API = process.env.PACKET_API || 'https://pray.doxa.life'
|
||||
const ISO3 = process.env.PACKET_ISO3 || 'LAO'
|
||||
|
||||
// Each path, its expected page in POINTS (72pt = 1in), and its orientation.
|
||||
const PATHS = [
|
||||
{ id: 'packet (vertical)', w: 612, h: 792, orient: 'portrait' },
|
||||
{ id: 'cards (poker)', w: 180, h: 252, orient: 'portrait', stock: 'poker' },
|
||||
{ id: 'cards (landscape)', w: 252, h: 180, orient: 'landscape', stock: 'pokerLandscape' },
|
||||
{ id: 'poster', w: null, h: null, orient: 'portrait' }, // raster, not paginated
|
||||
]
|
||||
|
||||
const b = await chromium.launch()
|
||||
const pg = await b.newPage({ viewport: { width: 1200, height: 900 } })
|
||||
const errs = []; pg.on('pageerror', (e) => errs.push(String(e).slice(0, 120)))
|
||||
await pg.goto(ORIGIN + '/')
|
||||
|
||||
const fails = []
|
||||
const line = (id, got, want) => console.log(` ${id.padEnd(20)} ${got}${want ? ` (want ${want})` : ''}`)
|
||||
|
||||
// ── 1 + 2: the paginated paths, measured through page.pdf ────────────────────
|
||||
for (const p of PATHS.filter((x) => x.w)) {
|
||||
const html = await pg.evaluate(async ({ ORIGIN, API, ISO3, stock }) => {
|
||||
const norm = await import(`${ORIGIN}/plugin-designs/my-upg-100-list/data/normalize.js`)
|
||||
const strings = await fetch(`${ORIGIN}/library/scripts/posters/packet-strings.json`).then((r) => r.json())
|
||||
const recs = norm.normalizeRecords(norm.extractRecords(await (await fetch(
|
||||
`${API}/api/people-groups/list?fields=slug,name,country_code,population,religion,image_url,primary_language&lang=en`)).json()))
|
||||
const groups = recs.filter((r) => String(r.country_code) === ISO3).slice(0, 6)
|
||||
if (stock) {
|
||||
const { createCardRenderer } = await import(`${ORIGIN}/library/packet/card-render.js`)
|
||||
return createCardRenderer({ strings, lang: 'en', stock }).buildDeck({ id: 'x', label: 'X' }, groups)
|
||||
}
|
||||
const { createPacketRenderer } = await import(`${ORIGIN}/library/packet/packet-render.js`)
|
||||
return createPacketRenderer({ strings, lang: 'en' }).buildPacket({ id: 'x', label: 'X' }, groups, new Map(), '')
|
||||
}, { ORIGIN, API, ISO3, stock: p.stock })
|
||||
|
||||
await pg.setContent(html, { waitUntil: 'load' })
|
||||
await pg.waitForTimeout(1200)
|
||||
const pdf = await pg.pdf({ preferCSSPageSize: true })
|
||||
const raw = pdf.toString('latin1')
|
||||
const box = [...raw.matchAll(/MediaBox\s*\[\s*0\s+0\s+([\d.]+)\s+([\d.]+)\s*\]/g)].map((m) => [+m[1], +m[2]])[0] || [0, 0]
|
||||
const [w, h] = box.map((v) => Math.round(v))
|
||||
line(p.id, `${w} x ${h} pt`, `${p.w} x ${p.h}`)
|
||||
if (Math.abs(w - p.w) > 2 || Math.abs(h - p.h) > 2) {
|
||||
fails.push(`${p.id}: page is ${w}x${h}pt, expected ${p.w}x${p.h}pt`
|
||||
+ (Math.abs(w - 612) < 2 && Math.abs(h - 792) < 2 && p.w !== 612
|
||||
? ' — this is LETTER: the @page size silently fell back' : ''))
|
||||
}
|
||||
const landscape = w > h
|
||||
if ((p.orient === 'landscape') !== landscape) {
|
||||
fails.push(`${p.id}: orientation is ${landscape ? 'landscape' : 'portrait'}, expected ${p.orient}`)
|
||||
}
|
||||
}
|
||||
|
||||
// ── 3: the poster, which rasterises rather than paginating ───────────────────
|
||||
const poster = await pg.evaluate(async ({ ORIGIN }) => {
|
||||
const { rasterPoster, looksRendered } = await import(`${ORIGIN}/library/packet/poster-raster.js`)
|
||||
const res = await fetch(`${ORIGIN}/library/scripts/posters/out/posters/laos.html`)
|
||||
if (!res.ok) return { error: `poster HTML ${res.status}` }
|
||||
const blob = await rasterPoster(await res.text(), { scale: 1 })
|
||||
const bmp = await createImageBitmap(blob)
|
||||
return { w: bmp.width, h: bmp.height, ok: await looksRendered(blob), bytes: blob.size }
|
||||
}, { ORIGIN })
|
||||
if (poster.error) fails.push(`poster: ${poster.error}`)
|
||||
else {
|
||||
line('poster', `${poster.w} x ${poster.h} px`, 'portrait, painted')
|
||||
if (poster.w >= poster.h) fails.push(`poster rasterised ${poster.w}x${poster.h} — expected portrait`)
|
||||
if (!poster.ok) fails.push('poster rasterised blank/black')
|
||||
}
|
||||
|
||||
await b.close()
|
||||
if (errs.length) fails.push(`${errs.length} page error(s): ${errs[0]}`)
|
||||
if (fails.length) {
|
||||
console.error(`\nPRINT-GEOMETRY: FAIL — ${fails.length} problem(s)`)
|
||||
for (const f of fails) console.error(' - ' + f)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('\nPRINT-GEOMETRY: PASS — every print path produces its own page geometry.')
|
||||
|
|
@ -57,6 +57,7 @@
|
|||
"build:unlocked": "bun run gates:pre && bun run library-index && bun run check:library-index && bun run clean && bun run token && bun run build:bundles && bun run tree && bun run gates:post && bun run gallery",
|
||||
"check:card-deck": "node internal/check-card-deck.mjs",
|
||||
"check:poster-raster": "node internal/check-poster-raster.mjs",
|
||||
"check:print-geometry": "node internal/check-print-geometry.mjs",
|
||||
"check:packet-browser": "node internal/check-packet-browser.mjs",
|
||||
"check:packet-render": "node internal/check-packet-render.mjs",
|
||||
"check:template-literal": "node internal/template-literal-check.mjs",
|
||||
|
|
|
|||
Loading…
Reference in a new issue