Skip to content

feat(layouts): offer six more page fonts, previewed in their own type - #66

Open
patriksimms wants to merge 1 commit into
mainfrom
feat/more-fonts
Open

feat(layouts): offer six more page fonts, previewed in their own type#66
patriksimms wants to merge 1 commit into
mainfrom
feat/more-fonts

Conversation

@patriksimms

Copy link
Copy Markdown
Owner

Problem

Layouts could only use Inter or Source Serif 4, so every book looked the same. The picker also listed both names in the app's own UI font, so you had to apply a font and look at the page to see what it actually was.

Solution

Six more families are available for every text element, grouped by category, and each name is rendered in the font it names. Everything is bundled with the app, so no font is ever fetched from Google at runtime.

Before After
before after

Picking a family carries through the editor canvas, the book preview and the exported PDF:

Caveat on the layout canvas

How it works

  • src/domain/fonts.ts is now the single registry behind the picker, the browser preview, the layout metrics and the PDF cuts, so a family cannot render one way on screen and another in print.
  • scripts/build-print-fonts.py (via bun run fonts:build) derives the static print TTFs and the web woff2 cuts from the pinned OFL variable sources on github.com/google/fonts. Only the derived files are served.
  • Exports now embed just the cuts a book uses instead of all of them, so a typical export got smaller rather than larger despite four times as many fonts.
  • Caveat has no italic cut upstream, so italic is disabled for it instead of silently rendering upright text.

Notable side effect

Routing families through the registry quotes the CSS family name. font-family: Source Serif 4 Variable unquoted is an invalid identifier and was silently dropped, so Source Serif 4 text previewed in Inter while the PDF printed it correctly. Preview and print now agree.

Trade-off

Advance-width metrics for 30 cuts grow the organizer-only layout editor chunk from about 159 kB to 221 kB gzip. Keeping them exact is what makes preview and PDF line breaking identical; trimming to a codepoint subset is possible later if that chunk becomes a problem.

Validation

bun run verify locally: format, lint, typecheck, 233 unit tests, 20 e2e tests, build. New coverage: registry-to-asset-to-metrics integrity, the Caveat italic fallback, and that a PDF embeds only the cuts in use. A rasterised export was inspected for each new family, umlauts and eszett included.

Closes #65


Changes authored by Claude Opus 5 in Claude Code.

Summary:
- Add Montserrat, Nunito, Lora, EB Garamond, Playfair Display and
  Caveat to the font picker, grouped by Sans, Serif and Handwriting,
  with every name rendered in the font it names.
- Introduce src/domain/fonts.ts as the single registry behind the
  picker, the browser preview, the layout metrics and the embedded
  PDF cuts, so a family cannot render one way on screen and another
  in print.
- Derive the print TTF and web woff2 cuts locally from the pinned OFL
  variable sources (scripts/build-print-fonts.py) and self-host them;
  nothing is fetched from Google at runtime.
- Embed only the font cuts a book uses instead of all of them, which
  also shrinks existing exports.
- Caveat ships no italic upstream: italic is disabled for it and
  resolves to the upright cuts rather than silently faking a slant.

Rationale:
- Quoting the family name through the registry also fixes Source
  Serif 4 text previewing in Inter, because an unquoted family name
  containing a digit is an invalid CSS identifier and was dropped.
- Full advance-width metrics for 30 cuts grow the organizer-only
  layout chunk by about 62 kB gzip; exactness keeps preview and PDF
  line breaking identical, which is the point of the metrics.

Tests:
- bun run format:check, lint, typecheck: pass
- bun run test: 233 pass (new src/domain/fonts.test.ts and a PDF
  embedding-scope test)
- bun run test:e2e: 20 pass, 2 production smoke skipped
- bun run build: pass
- Verified new families render in the editor canvas and in a
  rasterised PDF export, umlauts and eszett included

Closes #65

AI-Assisted-By: Codex
AI-Assisted: true
AI-Agent: claude-code
AI-Model: anthropic/claude-opus-5
@patriksimms
patriksimms marked this pull request as ready for review August 19, 2026 08:43
@patriksimms

Copy link
Copy Markdown
Owner Author

CI note: verify passed in full for 349c04a via the pull_request trigger (run 32230301297) — every step green, including the Linux e2e run and the layout parity snapshots.

The duplicate push-triggered run for the same commit (32230298722) is wedged on bunx playwright install --with-deps chromium, a step that took seconds in the run that passed. It does not respond to cancel, so it is a stuck runner rather than anything in this branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Offer more page fonts and preview each font name in its own typeface

1 participant