Poster sizes: the ladder offers eight sizes (paper, 18x24, 36x36, 36x48, 36x60, 36x66, 66x66, 60x96)

Every size up to 36x66 prints on one sheet; 66x66 is two 36x66 sheets glued side by side over 6 in, and 60x96
is three 36x60 sheets stacked over 6 in overlaps. 36x48 moves from legacy into the ladder (3x4ft still
resolves to it); 24x36 stays legacy. check:poster-sizes now also refuses a size that is both offered and
legacy, and a glued size other than 66x66 and 60x96; its self-test refuses 10 broken tables.
This commit is contained in:
Doxa Maps 2026-09-30 23:49:05 +00:00
parent e1c79fb19f
commit 1bfe4d7353
2 changed files with 17 additions and 5 deletions

View file

@ -6,7 +6,8 @@
* geometry. So the table itself must be right: a wrong sheet count or overlap prints a poster that does not * geometry. So the table itself must be right: a wrong sheet count or overlap prints a poster that does not
* glue up to its size, and a landscape size breaks the portrait-only rule. * glue up to its size, and a landscape size breaks the portrait-only rule.
* *
* ASSERTS: the picker order lists exactly the ladder; every key is dot-free (i18n paths, URLs); every size is * ASSERTS: the picker order lists exactly the ladder; no size is both offered and legacy; only 66x66 and 60x96
* are glued from several sheets; every key is dot-free (i18n paths, URLs); every size is
* PORTRAIT (h ≥ w); the sheets glue up to the finished size (count × sheet − overlaps, in `direction`); a * PORTRAIT (h ≥ w); the sheets glue up to the finished size (count × sheet − overlaps, in `direction`); a
* photo slot is allowed on exactly the large sizes (66x66, 60x96); paper prints through the browser and the * photo slot is allowed on exactly the large sizes (66x66, 60x96); paper prints through the browser and the
* rest through a print-shop PDF; every alias resolves; paperFor() picks Letter or A4 by region. * rest through a print-shop PDF; every alias resolves; paperFor() picks Letter or A4 by region.
@ -18,6 +19,8 @@ const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
const S = await import(pathToFileURL(join(ROOT, 'library/constants/posterSizes.js')).href) const S = await import(pathToFileURL(join(ROOT, 'library/constants/posterSizes.js')).href)
const PHOTO_SIZES = ['66x66', '60x96'] const PHOTO_SIZES = ['66x66', '60x96']
// Every size up to 36x66 prints on ONE sheet; only these two are glued from several.
const MULTI_SHEET = ['66x66', '60x96']
function audit({ ladder, order, legacy, aliases }) { function audit({ ladder, order, legacy, aliases }) {
const bad = [] const bad = []
const keys = Object.keys(ladder) const keys = Object.keys(ladder)
@ -33,6 +36,9 @@ function audit({ ladder, order, legacy, aliases }) {
if (W !== w || H !== h) bad.push(`${k}: ${s.count} sheet(s) of ${s.sheetW}×${s.sheetH} (${s.direction}, ${s.joinIn} in overlap) glue up to ${W}×${H}, not ${w}×${h}`) if (W !== w || H !== h) bad.push(`${k}: ${s.count} sheet(s) of ${s.sheetW}×${s.sheetH} (${s.direction}, ${s.joinIn} in overlap) glue up to ${W}×${H}, not ${w}×${h}`)
if (s.count === 1 && s.joinIn !== 0) bad.push(`${k}: one sheet cannot have a ${s.joinIn} in overlap`) if (s.count === 1 && s.joinIn !== 0) bad.push(`${k}: one sheet cannot have a ${s.joinIn} in overlap`)
} }
for (const k of Object.keys(legacy)) if (ladder[k]) bad.push(`${k} is both offered and legacy`)
const multi = keys.filter((k) => ladder[k].sheets.count > 1)
if (multi.join() !== MULTI_SHEET.join()) bad.push(`glued sizes are [${multi}], expected [${MULTI_SHEET}]; every other size is one sheet`)
const photos = keys.filter((k) => ladder[k].photos) const photos = keys.filter((k) => ladder[k].photos)
if (photos.join() !== PHOTO_SIZES.join()) bad.push(`photo slots are allowed on [${photos}], expected [${PHOTO_SIZES}] (the large sizes)`) if (photos.join() !== PHOTO_SIZES.join()) bad.push(`photo slots are allowed on [${photos}], expected [${PHOTO_SIZES}] (the large sizes)`)
for (const k of keys) { for (const k of keys) {
@ -60,6 +66,8 @@ if (process.argv.includes('--self-test')) {
['order drift', (t) => { t.order.reverse() }], ['order drift', (t) => { t.order.reverse() }],
['a dangling alias', (t) => { t.aliases['4x6ft'] = '48x72' }], ['a dangling alias', (t) => { t.aliases['4x6ft'] = '48x72' }],
['paper through a PDF', (t) => { t.ladder.paper.path = 'pdf' }], ['paper through a PDF', (t) => { t.ladder.paper.path = 'pdf' }],
['offered and legacy at once', (t) => { t.legacy['36x48'] = t.ladder['36x48'] }],
['a single-sheet size split in two', (t) => { t.ladder['36x66'].sheets = { sheetW: 36, sheetH: 36, count: 2, direction: 'vertical', joinIn: 6 } }],
] ]
let missed = 0 let missed = 0
for (const [name, poison] of poisons) { const t = clone(); poison(t); const caught = audit(t).length > 0; console.log(` ${name}: ${caught ? 'caught' : 'NOT CAUGHT'}`); if (!caught) missed++ } for (const [name, poison] of poisons) { const t = clone(); poison(t); const caught = audit(t).length > 0; console.log(` ${name}: ${caught ? 'caught' : 'NOT CAUGHT'}`); if (!caught) missed++ }

View file

@ -115,23 +115,27 @@ export function listByCategory(category) {
export const POSTER_LADDER = { export const POSTER_LADDER = {
paper: { id: 'paper', inches: { w: 8.5, h: 11 }, paper: true, paper: { id: 'paper', inches: { w: 8.5, h: 11 }, paper: true,
sheets: { sheetW: 8.5, sheetH: 11, count: 1, direction: 'vertical', joinIn: 0 }, photos: false, path: 'print' }, sheets: { sheetW: 8.5, sheetH: 11, count: 1, direction: 'vertical', joinIn: 0 }, photos: false, path: 'print' },
'18x24': { id: '18x24', inches: { w: 18, h: 24 },
sheets: { sheetW: 18, sheetH: 24, count: 1, direction: 'vertical', joinIn: 0 }, photos: false, path: 'pdf' },
'36x36': { id: '36x36', inches: { w: 36, h: 36 }, '36x36': { id: '36x36', inches: { w: 36, h: 36 },
sheets: { sheetW: 36, sheetH: 36, count: 1, direction: 'vertical', joinIn: 0 }, photos: false, path: 'pdf' }, sheets: { sheetW: 36, sheetH: 36, count: 1, direction: 'vertical', joinIn: 0 }, photos: false, path: 'pdf' },
'36x48': { id: '36x48', inches: { w: 36, h: 48 },
sheets: { sheetW: 36, sheetH: 48, count: 1, direction: 'vertical', joinIn: 0 }, photos: false, path: 'pdf' },
'36x60': { id: '36x60', inches: { w: 36, h: 60 }, '36x60': { id: '36x60', inches: { w: 36, h: 60 },
sheets: { sheetW: 36, sheetH: 60, count: 1, direction: 'vertical', joinIn: 0 }, photos: false, path: 'pdf' }, sheets: { sheetW: 36, sheetH: 60, count: 1, direction: 'vertical', joinIn: 0 }, photos: false, path: 'pdf' },
'36x66': { id: '36x66', inches: { w: 36, h: 66 },
sheets: { sheetW: 36, sheetH: 66, count: 1, direction: 'vertical', joinIn: 0 }, photos: false, path: 'pdf' },
'66x66': { id: '66x66', inches: { w: 66, h: 66 }, '66x66': { id: '66x66', inches: { w: 66, h: 66 },
sheets: { sheetW: 36, sheetH: 66, count: 2, direction: 'horizontal', joinIn: 6 }, photos: true, path: 'pdf' }, sheets: { sheetW: 36, sheetH: 66, count: 2, direction: 'horizontal', joinIn: 6 }, photos: true, path: 'pdf' },
'60x96': { id: '60x96', inches: { w: 60, h: 96 }, '60x96': { id: '60x96', inches: { w: 60, h: 96 },
sheets: { sheetW: 60, sheetH: 36, count: 3, direction: 'vertical', joinIn: 6 }, photos: true, path: 'pdf' }, sheets: { sheetW: 60, sheetH: 36, count: 3, direction: 'vertical', joinIn: 6 }, photos: true, path: 'pdf' },
} }
/** The picker's order, smallest first. */ /** The picker's order, smallest first. Every size up to 36x66 is one sheet; 66x66 and 60x96 are glued. */
export const POSTER_LADDER_ORDER = ['paper', '36x36', '36x60', '66x66', '60x96'] export const POSTER_LADDER_ORDER = ['paper', '18x24', '36x36', '36x48', '36x60', '36x66', '66x66', '60x96']
/** A size that still resolves (stored choices, today's defaults) but is NOT offered in the picker. */ /** A size that still resolves (stored choices, today's defaults) but is NOT offered in the picker. */
export const POSTER_LADDER_LEGACY = { export const POSTER_LADDER_LEGACY = {
'24x36': { id: '24x36', inches: { w: 24, h: 36 }, '24x36': { id: '24x36', inches: { w: 24, h: 36 },
sheets: { sheetW: 24, sheetH: 36, count: 1, direction: 'vertical', joinIn: 0 }, photos: false, path: 'pdf' }, sheets: { sheetW: 24, sheetH: 36, count: 1, direction: 'vertical', joinIn: 0 }, photos: false, path: 'pdf' },
'36x48': { id: '36x48', inches: { w: 36, h: 48 },
sheets: { sheetW: 36, sheetH: 48, count: 1, direction: 'vertical', joinIn: 0 }, photos: false, path: 'pdf' },
} }
/** Older names for the same sizes (poster-render's FORMATS, Bless's PRESETS, the map exporter). */ /** Older names for the same sizes (poster-render's FORMATS, Bless's PRESETS, the map exporter). */
export const POSTER_LADDER_ALIASES = { export const POSTER_LADDER_ALIASES = {