/** * Turn hand-built spreads into auto-layout templates. * * Reads the book from a journey and writes the spreads out as data: every * measurement divided by the page it was drawn on, so the same template lays * out on an A4 landscape book as it does on the 210mm square it was designed * for. Run from server/, output goes to the client. */ const Database = require('better-sqlite3'); const fs = require('fs'); const JOURNEY = Number(process.argv[2] || 7); const OUT = process.argv[3] || '../client/src/components/Studio/bookTemplates.data.ts'; const db = new Database('data/travel.db', { readonly: true }); const row = db.prepare('SELECT document FROM journey_books WHERE journey_id = ?').get(JOURNEY); db.close(); if (!row) throw new Error(`no book on journey ${JOURNEY}`); /* * Through the contract on the way in, so a template carries every field the * document has — including any the schema gained since the spread was drawn. * Without it a template written today is missing tomorrow's defaults and does * not typecheck as a BookElement. */ const { normalizeBookDocument } = require('@trek/shared'); const doc = normalizeBookDocument(JSON.parse(row.document)); const PW = doc.page.pageWidth; const PH = doc.page.pageHeight; const r4 = (n) => Math.round(n * 10000) / 10000; /** A frame as fractions of one page's width and of the page height. */ function relFrame(f) { return { x: r4(f.x / PW), y: r4(f.y / PH), w: r4(f.w / PW), h: r4(f.h / PH) }; } /** Type size as a fraction of the page height, so it scales with the sheet. */ const relSize = (n) => r4(n / PH); function element(e) { // The id travels with the template and is replaced when it is applied — // dropping it here would leave the element off the BookElement union. const out = { ...e, frame: relFrame(e.frame) }; if (typeof out.size === 'number') out.size = relSize(out.size); if (typeof out.radius === 'number') out.radius = r4(out.radius / PW); if (typeof out.strokeWidth === 'number') out.strokeWidth = r4(out.strokeWidth / PW); return out; } const spreads = doc.spreads .map((s, i) => ({ index: i, role: s.role, background: s.background, elements: s.elements.map(element) })) .filter((s) => s.role === 'inner'); const body = spreads .map( (s, i) => ` { id: 'ref-${i + 1}', background: ${JSON.stringify(s.background)}, elements: ${JSON.stringify(s.elements, null, 6).replace(/\n/g, '\n ')}, },`, ) .join('\n'); const file = `import type { BookElement } from '@trek/shared' /** * The spread templates, as data. * * ── Where these come from ──────────────────────────────────────────────── * * Not from this file's author. They were built by hand in Studio and read back * out of the document — which is the point: what a page should look like is a * matter of taste, and taste is not something a layout function arrives at by * reasoning. The editor is the design tool, and this is its output. * * To add one: build the spread in Studio, then run * * node scripts/extract-templates.cjs * * from server/ and commit what it writes here. * * ── Why the numbers are fractions ──────────────────────────────────────── * * Every frame is divided by the page it was drawn on — x and w by one page's * width, y and h by its height — and type sizes by the page height. A template * built on a 210mm square therefore lays out on an A4 landscape book without * being redrawn, which it would otherwise have to be for every trim size the * picker offers. * * ── What gets filled in ────────────────────────────────────────────────── * * Elements carrying a \`binding\` take their text from the entry; empty photo * frames take its photographs in order; the day, coordinate and country marks * take what the entry knows about its stop. Everything else — the panels, the * rules, the shapes that run off the page — is the design, and is kept as it * was drawn. */ export interface SpreadTemplate { id: string background: string | null /** Frames and sizes are fractions — see the note above. */ elements: BookElement[] } export const SPREAD_TEMPLATES: SpreadTemplate[] = [ ${body} ] `; fs.writeFileSync(OUT, file); console.log(`wrote ${spreads.length} templates to ${OUT}`);