Skip to content

Generate filled PDF character sheets using MPMB templates#78

Merged
igor47 merged 9 commits into
mainfrom
print-export
May 26, 2026
Merged

Generate filled PDF character sheets using MPMB templates#78
igor47 merged 9 commits into
mainfrom
print-export

Conversation

@igor47

@igor47 igor47 commented May 26, 2026

Copy link
Copy Markdown
Owner

Summary

Replaces the HTML print export (#77, by @lionel-lints) with a server-side PDF generation approach using MPMB's D&D Character Record Sheet as a form-fillable template.

  • GET /characters/:id/pdf — fills the MPMB main sheet (page 1) with the character's data
  • GET /campaigns/:id/pdf (DM-only) — concatenates per-character pages into one combined party PDF
  • Print button on the character info card relocated to the upper-right as an icon; Campaign "Print All" button updated to point at the new route

What gets filled in

  • Identity: PC Name, Player Name (from user.name ?? user.email), Class & Levels, Background, Race/Species, Alignment
  • Abilities: 6 ability scores + modifiers + saves + proficient checkboxes
  • Skills: all 18 skill modifiers + proficient/expert flags
  • Combat: AC, Initiative, Speed, HP Max/Current, Proficiency Bonus, Passive Perception
  • Hit dice: per-die-size rows (HD1/2/3) with Level + Used, grouped for multi-class
  • Spellcasting: spell save DC + spellcasting ability + spell-slot usage tracking per spell level

Selects the 2014 (srd51.pdf) or 2024 (srd52.pdf) MPMB template based on the character's ruleset field.

MPMB templates and licensing

The 2014 and 2024 MPMB templates plus per-class spell sheets live at assets/mpmb/ with their own LICENSE.md clarifying that those files are GPLv3 (MPMB's license) while the rest of the repo remains FSL-1.1-MIT. Files were obtained via flapkan.com's pay-what-you-want and are redistributed under GPLv3 §6.

A few MPMB-specific concessions in the renderer:

  • The d20warning "CRITICAL FAIL!" overlay is removed before save (MPMB's JS would normally hide it in Adobe Acrobat; we don't run AcroForm JS).
  • form.flatten() crashes on MPMB's rich-text-field, so we save with updateFieldAppearances: false and set /NeedAppearances=true on the AcroForm — PDF viewers and print engines regenerate field appearances from /V.
  • Only page 1 of the main sheet is emitted right now; pages 2-7 are removed via removePage. Spell list (the dedicated MPMB spell sheets) and equipment are future work.

Why PDF instead of HTML

The HTML approach in #77 hit hard layout problems in print mode (forced page breaks with whitespace, two-column grid not balancing). Generating from MPMB's official template produces output identical to what D&D players already use, with zero design effort. The HTML/CSS/JS print code created a parallel design system to maintain.

Supersedes #77

This replaces #77 entirely. Lionel's original commit is preserved as the first commit on this branch for credit — none of the HTML print code lands in the final diff (cleaned up in chore: remove HTML print routes and components).

Test plan

  • Open a character page, click the print icon → downloads filled MPMB sheet
  • Open a campaign page as DM, click "Print All" → downloads multi-page party PDF
  • No "CRITICAL FAIL" overlay in the output
  • Open the downloaded PDF in Chrome → print preview → all identity/spell/skill fields render
  • `mise run test src/services/characterPdf.test.ts` — 17 tests pass
  • `mise run test` — full suite passes

lionel-lints and others added 7 commits May 25, 2026 22:52
Adds browser-native print-to-PDF export:
- GET /characters/:id/print — standalone multi-column character sheet (owner + DM)
- GET /campaigns/:id/print — DM-only full party export with cover page

The print layout uses a two-column grid (abilities/skills left, combat/HP/weapons/items right) with a second page for traits and spells. Zero new runtime dependencies — uses the browser's built-in print dialog.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the standalone Print button row with a small printer icon in
the upper-right of the character info card, next to the character name.
Loads the MPMB form-fillable template for the character's ruleset, populates
name, class/level, race/background/alignment, ability scores + modifiers +
saves, removes the d20warning JS overlay (which would otherwise be visible
since pdf-lib doesn't execute AcroForm scripts), trims to the front page of
the main sheet, and returns bytes as application/pdf.

Skips form.flatten() — MPMB ships rich text fields and buttons with missing
appearance streams that crash pdf-lib's flattener; the form-bearing PDF still
renders correctly in Chrome, Firefox, Preview, and Evince.
Skills: all 18 skill modifiers + proficient/expert checkboxes (expertise
implies proficient).

Combat block: AC, Initiative, Speed, HP Max, HP Current, Proficiency Bonus,
Passive Perception.

Hit dice: HD1/2/3 Die / Level / Used, grouped by die size for multi-class
characters.

Player Name: looked up via character.user_id (user.name ?? user.email);
threaded through both /characters/:id/pdf and /campaigns/:id/pdf.

Two number-format helpers: fmt() for the big ability-mod boxes (signed),
unsignedFmt() for fields where MPMB has a '+' pre-rendered in the sheet
layout (save mods, skill mods, initiative, prof bonus) — supplying a sign
there yields '++2' on render.

Set /NeedAppearances=true on the AcroForm so PDF viewers and print engines
regenerate appearance streams from /V on render. Without this, in-browser
viewers auto-render values fine on screen but their print pipelines fall
back to the stored /AP — which is stale because we save with
updateFieldAppearances:false — and identity/dropdown fields come up blank
in print preview.
igor47 added 2 commits May 26, 2026 01:18
Per spellcasting class on page 1 (MPMB caps at 2):
- Spell save DC {1,2} ← computed DC value
- Spell DC {1,2} Mod ← title-cased spellcasting ability

For slot tracking we use MPMB's 'Limited Feature' rows on page 1, since the
actual spell-slot checkbox grid widgets in srd52.pdf's AcroForm have orphan
/P references — they belong to the dedicated spell-sheet PDFs, not the main
sheet. The Limited Feature block has 8 visible rows on page 1, each with
Name / Max / Recovery / Used columns — a natural fit for any tracked
resource.

One row per spell level the character has slots for (recovery: Long Rest),
plus one row for warlock pact magic per pact-slot level (recovery: Short
Rest). Non-casters skip the block entirely.
Each attack-resolution cantrip (resolution.kind === 'attack' — Fire Bolt,
Eldritch Blast, Toll the Dead with attack subtype, etc.) prepared on a
spellcasting class becomes an entry in one of MPMB's 5 Attack rows on page
1, with name, ability mod, range, to-hit, damage dice, damage type, and
brief description filled in.

Implementation notes:

- The visible 'Attack Name' is the Weapon Selection dropdown, not the
  underlying Weapon text field — they're stacked at the same coordinates.
  Cantrip names that aren't in MPMB's 93-option predefined list get added
  via dropdown.addOptions.
- Damage scaling: cantrips with damageScaling.mode === 'characterLevel' get
  their dice resolved at the character's current total level (highest
  threshold ≤ level).
- The 'Mod' dropdown uses the caster's ability abbreviation (Int/Wis/Cha);
  MPMB has no generic 'Spell' option in this template version.
- Without MPMB-JS, selecting a weapon/cantrip in the dropdown doesn't auto-
  fill the other columns, so we fill them all manually.
@igor47 igor47 merged commit 0237bbf into main May 26, 2026
2 checks passed
@igor47 igor47 deleted the print-export branch May 26, 2026 08:34
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.

2 participants