WorldForge Studio

08 — Print System

A vector PDF atlas engine for A4, A3 and Letter. Multiple print modes (GM, Player, Battle, Poster, Atlas). Embedded fonts, bleed, crop marks. Optional print-shop marketplace integration for nearby print shops with QR pickup codes.


0. Where we actually are (refreshed 2026-09-06)

The sections below describe the intended print design. This block records what is actually on main, verified against packages/print*, apps/api/src/routes/print*.ts, apps/web/src/app/print-shops and git log (PRs #73–#113). Ground truth for milestones is superpowers/STATUS.md.

Design elementStatusWhere
Layout engine + vector output (§2, §3)✅ SHIPPED (tags spec-4-print-complete, spec-4-print-slice-2-complete, spec-4-themes-borders-and-print-complete)packages/print-engine/src/{layout,renderer,svg-emitter,png}.ts (v0.2.0); packages/print/src/index.ts (card/section page breaking, breakMode per-card or per-section); POST /print/compile in apps/api/src/routes/print.ts.
Five named print modes (§1, §4 — GM / Player / Battle / Poster / Atlas)❌ Not built as named modesNo PrintMode enum or per-mode content filtering exists in packages/print* or the compile route. Atlas-style multi-map output is covered by the Atlas feature (tag spec-6-complete, /atlas/[worldId]), not by a print mode.
PNG / image export (§5)✅ SHIPPEDpackages/print-engine/src/png.ts; editor <ExportMenu>.
Print-shop directory (§6)⚠️ Stub with real distance mathGET /print-shops?near=lat,lng&radius_km= (PR #73 stub, 2026-08-23) now filters by haversine distance on a hard-coded in-memory shop list (PR #111, 2026-08-25). No real shop registry / onboarding.
Print orders + QR pickup (§6)✅ MVPPOST /print-orders, GET /print-orders, GET /print-orders/:id, GET /print-orders/:id/qr (apps/api/src/routes/print-shops.ts); DB-persisted in print_orders (PR #76, migration 0024); audit row on create (PR #79).
/print-shops UI✅ MVPapps/web/src/app/print-shops/page.tsx (PR #74) with "Your recent orders" (PR #77) and browser geolocation feeding the distance filter via hooks/use-geolocation.ts (PR #112). E2E: e2e/print-shop-order.spec.ts (PR #92) + geo-status branches (PR #113). Audit flow print-shop-order enrolled in the testid gate (PR #110).
Payments, shop notifications, pickup email/push (§6)❌ Not startedNo Stripe / payment provider code in the print path; no notification transport.
Print preview (§7), print profiles (§8)❌ Not built—

1. Print modes

Every world can be exported as a PDF atlas in five modes:

ModeUse caseFeatures
GMGame master referencefull grid, hidden notes, secret doors, NPC names
PlayerHandout to playersclean version, no grid, no secret info
BattleTactical combatgrid visible, scale ruler, no labels
PosterWall artno grid, no labels, decorative border
AtlasMulti-page bookindex page, legend, coordinates, scale

Each mode produces a different PDF with different visible content.


2. Layout engine

The layout engine slices a world into printable pages.

2.1 Paper sizes

SizeWidth × Height (pt)Width × Height (mm)
A4595 × 842210 × 297
A3842 × 1191297 × 420
Letter612 × 792216 × 279

2.2 Inputs

interface PrintLayoutOptions {
  paperSize: 'A4' | 'A3' | 'Letter';
  orientation: 'portrait' | 'landscape';
  scale: number;                  // e.g. 1 hex = 1 inch
  marginsMm: number;              // default 5
  bleedMm: number;                // default 3
  overlapMm: number;              // overlap between adjacent pages, default 10
  includeGrid: boolean;
  includeCoords: boolean;
  includeLegend: boolean;
  includeCover: boolean;
  includeIndex: boolean;
  mode: 'gm' | 'player' | 'battle' | 'poster' | 'atlas';
  theme?: string;                 // optional theme override
  title?: string;
  author?: string;
}

2.3 Output

The engine produces a multi-page PDF:

Page 1:   Cover (title + hero shot)
Page 2:   Index (list of regions + page numbers)
Page 3..N: Atlas pages
Last page: Legend + scale + credits

Each atlas page:

  • Contains the tile window for its grid bounds.
  • Has crop marks at the corners.
  • Has a 10 mm overlap band on the right + bottom edges with assembly instructions.
  • Has a small footer with page number, world title, and a "Page X of N" indicator.
  • Includes a 3 mm bleed on all edges.

2.4 Calculation

function layoutPages(world: World, opts: PrintLayoutOptions): Page[] {
  const paper = PAPER_SIZES[opts.paperSize];
  const printableMm = {
    width:  paper.mm.width  - 2 * (opts.marginsMm + opts.bleedMm),
    height: paper.mm.height - 2 * (opts.marginsMm + opts.bleedMm),
  };

  // world extents in mm at the given scale
  const worldMm = {
    width:  world.grid.size.cols * world.grid.hexSize * 2 * opts.scale,
    height: world.grid.size.rows * world.grid.hexSize * Math.sqrt(3) * opts.scale,
  };

  const colsPerPage = Math.ceil(worldMm.width  / printableMm.width);
  const rowsPerPage = Math.ceil(worldMm.height / printableMm.height);

  // ...build a Page[] with overlap and crop marks
}

The user sees a print preview with red lines showing where the cuts and overlaps fall, and can adjust scale / paper size until satisfied.


3. Vector output

For maximum print quality, every element is vector:

  • Tile SVG → vector path.
  • Object SVG → vector path.
  • Text → embedded font glyphs (subset).
  • Grid lines → stroked vector lines.
  • Borders → vector rectangles.

The PDF contains no rasters unless the user enables "Include rasters" (e.g. for a photo-realistic theme pack that ships PNG textures).

3.1 Embedded fonts

Theme packs ship font files; the print engine embeds the relevant subset. No font substitution at print time.

3.2 Crop marks

┌─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┐
                             
                             
        Page content         
                             
                             
└─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┘

Crop marks are 5 mm long, 0.25 pt wide, offset 3 mm from the page edge. They sit on the bleed layer and never appear in print.

3.3 Bleed

A 3 mm bleed extends artwork past the trim line. The print engine knows the bleed for each theme pack and renders correctly.


4. The five modes in detail

4.1 GM mode

  • Full grid visible (subtle).
  • All labels visible.
  • All NPC names + roles shown.
  • Hidden regions: a "GM only" overlay with notes pinned by the GM.
  • A small "GM secret" stamp at the corner.

4.2 Player mode

  • Grid: hidden by default (toggleable).
  • Labels: visible if marked public.
  • Hidden regions: never rendered.
  • A "Players' version" stamp at the corner.

4.3 Battle mode

  • Grid: bold, high contrast.
  • Labels: hidden.
  • Scale ruler at the corner.
  • "Battle map" stamp.
  • A faint hex / square coordinate grid in the margin.

4.4 Poster mode

  • Grid: hidden.
  • Labels: hidden.
  • Decorative border from the theme pack.
  • "Poster" stamp hidden (this is wall art).

4.5 Atlas mode

  • Multiple pages.
  • Index page with hyperlinked page numbers.
  • Legend page.
  • Per-page header with region name.
  • Coordinates on every page.
  • Scale ruler on the cover.

5. PNG / image export

In addition to PDF, the engine supports PNG export:

  • Single PNG: the full world at a chosen DPI (default 300).
  • Tiled PNGs: like the PDF atlas but as separate PNG files for digital use.
  • WebP: smaller, supports transparency.

The PNG export runs the same layout engine; each page renders at the chosen DPI.


6. Print-shop marketplace

The killer feature: find a print shop, upload, pick up.

6.1 User flow

  1. User clicks Print this map in the editor.
  2. A modal asks:
    • Paper size (A3 default).
    • Mode (Atlas default).
    • Quantity (1 default).
    • Optional: matte / gloss / lamination.
  3. The engine:
    • Renders the PDF in-browser.
    • Calls a print-shop API to find nearby shops.
    • Lists shops with price + ETA + rating.
  4. User selects a shop.
  5. Engine uploads the PDF (signed URL).
  6. Shop confirms the order.
  7. User gets:
    • Order ID.
    • QR code (PNG) for pickup.
    • Email receipt.

6.2 Print-shop API

A simple REST API:

GET  /v1/shops?lat=..&lon=..&radius=..&paper=A3
→ [{ id, name, address, distanceKm, priceCents, etaHours, rating }]

POST /v1/orders
   { shopId, pdfUrl, options }
→ { orderId, qrUrl, statusUrl }

The integration is best-effort: if no API exists, the user gets an email template with the PDF attached for manual sending.

6.3 Caching

Shop listings are cached locally per geohash for 24 hours.


7. Print preview

A live preview lets the user see exactly what will print:

  • Page-by-page navigation.
  • Toggle grid, labels, legend, crop marks.
  • Zoom + pan.
  • "What you see is what prints" — no hidden surprises.

The preview is rendered in the browser using the same vector pipeline; no server round-trip needed.


8. Print profiles

Save a print profile for reuse:

interface PrintProfile {
  id: string;
  name: string;                  // "GM A3 Atlas"
  options: PrintLayoutOptions;
}

Profiles are GAS assets (gas://print-profile/...) and can be shared.


9. Multi-map atlases

A user can bundle multiple worlds into one PDF atlas (e.g. "Northern Kingdom + Southern Wastes + Underdark"). Each map gets its own section with cover, index, legend, and atlas pages.


10. Performance

  • PDF generation runs in a Web Worker (the browser stays responsive).
  • For very large worlds (> 1000 pages), the engine streams pages to the user.
  • Tile vectorisation is cached per pack.

11. Determinism

Same World.json + same PrintLayoutOptions → byte-identical PDF. Required for marketplace previews and reproducible tests.