From 1bfe4d73532d2deae684d23cea5c7478db53f573 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Wed, 30 Sep 2026 23:49:05 +0000 Subject: [PATCH] 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. --- internal/check-poster-sizes.mjs | 10 +++++++++- library/constants/posterSizes.js | 12 ++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/internal/check-poster-sizes.mjs b/internal/check-poster-sizes.mjs index d2ccb42cf..e989aab4b 100644 --- a/internal/check-poster-sizes.mjs +++ b/internal/check-poster-sizes.mjs @@ -6,7 +6,8 @@ * 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. * - * 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 * 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. @@ -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 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 }) { const bad = [] 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 (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) 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) { @@ -60,6 +66,8 @@ if (process.argv.includes('--self-test')) { ['order drift', (t) => { t.order.reverse() }], ['a dangling alias', (t) => { t.aliases['4x6ft'] = '48x72' }], ['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 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++ } diff --git a/library/constants/posterSizes.js b/library/constants/posterSizes.js index 297d2cc61..b35222668 100644 --- a/library/constants/posterSizes.js +++ b/library/constants/posterSizes.js @@ -115,23 +115,27 @@ export function listByCategory(category) { export const POSTER_LADDER = { 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' }, + '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 }, 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 }, 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 }, sheets: { sheetW: 36, sheetH: 66, count: 2, direction: 'horizontal', joinIn: 6 }, photos: true, path: 'pdf' }, '60x96': { id: '60x96', inches: { w: 60, h: 96 }, sheets: { sheetW: 60, sheetH: 36, count: 3, direction: 'vertical', joinIn: 6 }, photos: true, path: 'pdf' }, } -/** The picker's order, smallest first. */ -export const POSTER_LADDER_ORDER = ['paper', '36x36', '36x60', '66x66', '60x96'] +/** 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', '18x24', '36x36', '36x48', '36x60', '36x66', '66x66', '60x96'] /** A size that still resolves (stored choices, today's defaults) but is NOT offered in the picker. */ export const POSTER_LADDER_LEGACY = { '24x36': { id: '24x36', inches: { w: 24, h: 36 }, 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). */ export const POSTER_LADDER_ALIASES = {