Skip to content
Nap Joseph Calub

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.