From fd438369203bcb5e47e3bf9110babf1d55d85bd6 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Thu, 1 Oct 2026 00:45:15 +0000 Subject: [PATCH] Poster Design: Styles draws the Bless wall panels from the shared panel geometry wall.bless-panels drew nothing. It now draws the ladder's 5 x 8 ft size (60x96: three 60 x 36 in sheets, 6 in joins) through panelSheets' planPanels and screenLayout, the same plan the Bless print uses: the sheets pulled apart, each join a red glue band with a dashed edge, a banner on the first sheet, a footer on the last, and rows of portrait cards inside each sheet's content box, never crossing a join. The cards are a layout (src/panelsSvg.js): the Bless wall draws its real cards in its own plugin, which the gallery cannot import. The card is not offered for printing here. --- .../poster-design/poster-styles.vue | 13 +++- plugin-designs/poster-design/src/panelsSvg.js | 69 +++++++++++++++++++ plugin-designs/poster-design/src/templates.js | 10 +-- 3 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 plugin-designs/poster-design/src/panelsSvg.js diff --git a/plugin-designs/poster-design/poster-styles.vue b/plugin-designs/poster-design/poster-styles.vue index d1552bb19..e27a14009 100644 --- a/plugin-designs/poster-design/poster-styles.vue +++ b/plugin-designs/poster-design/poster-styles.vue @@ -25,6 +25,8 @@ import countryData from '@map/data/country-posters.json' import { makeCountrySheet } from '@map/packet/countrySheet.js' import { loadPacketStrings } from '@map/packet/packetStrings.js' import { firstPageImage } from './src/pageThumb.js' +import { panelsSvgUrl } from './src/panelsSvg.js' +import { resolvePosterSize } from '@map/constants/posterSizes.js' import { listConfigurations, FAMILY_KEYS, SAMPLE_COUNTRY_ID } from './src/templates.js' import FullDetailsDialog from './src/FullDetailsDialog.vue' @@ -96,7 +98,12 @@ async function packetDocument(variant) { const doc = R.buildPacket(sample, rows, new Map(), mapSvg, null, null, variant, null) return { html: doc, page: '.sheet', width: R.W, height: R.H, firstPageOnly: true } } -const ctx = { countrySheet, cardDeck, packetDocument } +// A multi-sheet poster as its panels, from the shared ladder's sheets for that size. +function panels(sizeKey) { + const size = resolvePosterSize(sizeKey) + return size ? { image: panelsSvgUrl(size.sheets) } : null +} +const ctx = { countrySheet, cardDeck, packetDocument, panels } // The printed document's size, measured when it is drawn: what "file size" means in the details. // render() answers a poster's HTML (a map sheet) or a document { html, page, width, height } (a deck, a packet). @@ -105,6 +112,7 @@ async function renderDocument(name) { const c = byName.get(name) if (!c || !c.render) return null const out = await c.render(ctx) + if (out && out.image) return out const html = typeof out === 'string' ? out : out && out.html if (!html) return null if (!(out && out.firstPageOnly)) fileBytes.value = { ...fileBytes.value, [name]: new Blob([html]).size } @@ -117,6 +125,7 @@ async function renderDocument(name) { async function previewSrc(item) { const out = await renderDocument(item.id) if (!out) return null + if (out.image) return out.image if (typeof out === 'object') return firstPageImage(out.html, out) const { rasterPoster, looksRendered } = await import('@map/packet/poster-raster.js') const blob = await rasterPoster(out, { scale: 0.25 }) @@ -130,7 +139,7 @@ async function previewSrc(item) { // packet missing its later pages would misrepresent the template. async function sheet(item) { const out = await renderDocument(item.id) - if (!out || (typeof out === 'object' && out.firstPageOnly)) return '' + if (!out || out.image || (typeof out === 'object' && out.firstPageOnly)) return '' return typeof out === 'string' ? out : out.html } diff --git a/plugin-designs/poster-design/src/panelsSvg.js b/plugin-designs/poster-design/src/panelsSvg.js new file mode 100644 index 000000000..21723cdd7 --- /dev/null +++ b/plugin-designs/poster-design/src/panelsSvg.js @@ -0,0 +1,69 @@ +/** + * panelsSvg.js: a poster printed on several sheets, drawn as the sheets pulled apart, each join a red glue + * band, as one SVG image for a poster card's thumbnail. + * + * The geometry is the shared panel plan (library/components/poster/panelSheets.js planPanels + screenLayout): + * the same sheets, joins and content boxes the print uses. Inside each sheet's content box sit rows of cards, + * the way the Bless wall fills its 5 × 8 ft panels: a banner on the first sheet, a footer on the last, and + * card rows that never cross a join. The cards are a LAYOUT (blocks, no words): the Bless wall draws its real + * cards in its own plugin, which a library-only gallery cannot import. + */ +import { planPanels, screenLayout } from '@map/components/poster/panelSheets.js' + +const PAPER = '#f7f4ec' +const INK = '#3b463d' +const ACCENT = '#6B4A78' // the Bless wall's purple +const PHOTO = '#e9e3d4' +const LINE = '#c9c3b3' + +/** + * @param {{ sheetW, sheetH, count, direction, joinIn }} sheets a size's `sheets` from the shared ladder + * @param {{ borderIn?: number, gapIn?: number, cols?: number }} o + * @returns {string} an image URL (a data: URL of an SVG), drawn in inches + */ +export function panelsSvgUrl(sheets, { borderIn = sheets.joinIn || 2, gapIn = 0.75, cols = 14 } = {}) { + const plan = planPanels({ ...sheets, borderIn, joinFill: 'glue' }) + const L = screenLayout(plan, { gapIn, pxPerIn: 1 }) + const W = L.worldW, H = L.worldH + const s1 = Math.min(plan.sheetW, plan.sheetH) + const stroke = s1 / 200 + let out = '' + for (const sh of plan.sheets) { + const at = L.sheets[sh.s] + const ox = at.x, oy = at.y + out += `` + const c = sh.content + let top = oy + c.y0, bottom = oy + c.y1 + const cw0 = c.x1 - c.x0 + if (sh.first) { // the banner: a title band and the prayer-themes table + const bh = cw0 * 0.075, th = cw0 * 0.17 + out += `` + out += `` + top += bh + th + cw0 * 0.025 + } + if (sh.last) { // the footer: the blessing and the closing line + const fh = cw0 * 0.09 + out += `` + bottom -= fh + cw0 * 0.015 + } + // rows of portrait cards (0.8 aspect), centred in the band, never crossing a join + const cw = cw0 / cols, ch = cw / 0.8 + const rows = Math.max(0, Math.floor((bottom - top) / ch)) + const y0 = top + Math.max(0, (bottom - top) - rows * ch) / 2 + for (let r = 0; r < rows; r++) { + for (let k = 0; k < cols; k++) { + const x = ox + c.x0 + k * cw, y = y0 + r * ch + out += `` + out += `` + } + } + if (sh.band) { // the glue band: red, dashed at its edge, as PanelSheets draws it + const b = sh.band + const ex = plan.direction === 'vertical' ? `x1="${ox}" y1="${oy + b.h}" x2="${ox + b.w}" y2="${oy + b.h}"` : `x1="${ox + b.w}" y1="${oy}" x2="${ox + b.w}" y2="${oy + b.h}"` + out += `` + out += `` + } + } + const svg = `${out}` + return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}` +} diff --git a/plugin-designs/poster-design/src/templates.js b/plugin-designs/poster-design/src/templates.js index d29fd613d..c00cd5da1 100644 --- a/plugin-designs/poster-design/src/templates.js +++ b/plugin-designs/poster-design/src/templates.js @@ -10,7 +10,8 @@ * render(ctx) draws the template from the LIBRARY with the sample data, so a library change shows up in * the gallery the next time it opens. It returns either a poster's HTML (a map sheet: an the poster * rasteriser lifts) or a printable DOCUMENT as { html, page, width, height } (a deck, a packet: HTML pages, - * whose first page becomes the thumbnail, src/pageThumb.js). Entries the gallery cannot draw yet + * whose first page becomes the thumbnail, src/pageThumb.js) or a ready image as { image } (panels drawn from + * the shared panel geometry, src/panelsSvg.js). Entries the gallery cannot draw yet * say so (render: null) and the card shows the wall's "preview unavailable" placeholder; the details * dialog states which part is missing, rather than a picture of something else. * @@ -96,10 +97,11 @@ export function listConfigurations() { family: 'wall', labelKey: 'posterDesign.tpl.blessPanels', size: '5 × 8 ft, 3 panels', - renderer: 'plugin-designs/wall-of-people-groups/src/blessPoster.js (not in library/ yet)', - settings: {}, + renderer: 'library/components/poster/panelSheets.js planPanels (the sheets, joins and content boxes); the cards are drawn by plugin-designs/wall-of-people-groups/src/blessPoster.js', + settings: { size: '60x96', joinFill: 'glue' }, sample: null, - render: null, + // The panel geometry of the ladder's 5 × 8 ft size, with the card rows as a layout (src/panelsSvg.js). + render: (ctx) => ctx.panels('60x96'), missing: 'posterDesign.missing.bless', }, {