DIY Accordion Notebook — print-ready PDFs from one geometry function
A generator for dot-grid concertina notebooks where the live SVG preview and the exported PDF are both dumb consumers of a single pure layout function. Print output is geometrically exact; the preview cannot drift from the export.
Deno Fresh 2 / Preact signals / pdf-lib / Tailwind v4 / Spec-first development
Context
I wanted a dot-grid accordion (concertina) notebook — the fold-out kind — and print shops need vector-accurate PDFs to cut from. Commercial generators do flat pages; accordion folds have panel geometry, front-and-back mirroring, and paper-thickness folds that none of them model.
Constraints
- Print is the contract. Output must be geometrically exact in real units — millimeters at the print shop, not pixels on my screen. A hairline of drift becomes a mis-cut notebook.
- Two renderers exist by necessity: an on-screen SVG preview and the exported PDF. If they compute geometry independently, they will disagree eventually.
- pdf-lib is a low-level PDF toolkit — it places lines and dots; it has no layout engine. Coordinate systems (mm → PDF points, y-axis flip) are on me.
Decisions
One pure function is the single source of truth. All geometry — panel
widths, fold allowances, dot-grid spacing, margins — lives in one pure
computeLayout() that takes configuration in millimeters and returns a complete
layout description. The SVG preview and the pdf-lib export are both dumb
consumers of that output. The unit conversion (mm → pt) and the y-axis flip
happen once, at the PDF edge, documented where they live.
This is the whole trick, and it's the same trick that keeps design systems honest: when there is exactly one place a number can come from, the preview cannot lie to you.
Spec and plan before code. The repo carries a written design spec and an implementation plan (docs/), because the interesting failure modes — fold allowance math, panel mirroring — are decided on paper cheaply and in code expensively.
Test the geometry, not the rendering. Unit tests pin computeLayout()
outputs for known configurations. Rendering is verified by looking at a PDF;
geometry is verified by not needing to.
deno task check gates everything. Format, lint, and typecheck must pass
before work is considered done — the same gate I apply to infrastructure at
work, applied to a notebook generator, because craft doesn't scale down.
What shipped
A working generator: configure panel count, size, and grid; watch the live preview; export a print-ready, vector-accurate PDF. Roughly 1,500 lines including tests and docs — small, and that's the point. It's the project I point people at when they ask whether I sweat details.
What I'd do differently
Panel-count presets for common paper sizes (A4, Letter) would remove the last bit of math users do in their heads. And publishing sample PDFs on each tag — so a print shop can sanity-check before a customer commits — would cost one CI job and save someone a wasted sheet.