Skip to content
BACK TO DOSSIER

// Rendering system

jspdf-md-renderer

A Markdown-to-PDF engine that treats layout as a system instead of a collection of special cases.

Open source · v4.1 on npm2 min read

The problem

Markdown looks linear, but a PDF page is spatial. Inline formatting, images, code, and page boundaries all compete for the same finite geometry.

The version that preceded v4 handled this the way most renderers do at first: each component decided for itself how much room it needed and whether it fit. That works until two components disagree. A bold run inside a list item inside a table has three different opinions about line height, and the last one to write wins.

The approach

v4 moved inline layout into a unified engine. Content is measured before it is painted, and a centralized page-break system owns the decision of when a block moves to the next page.

code
measure → place → break → render

The ordering is the point. Measurement happens with no knowledge of pagination, so it is pure and testable. Placement consumes measurements. Breaking consumes placements and is the only stage permitted to introduce a page boundary. Rendering consumes the result and draws.

Because page breaks are decided in one place, a page boundary can change the available geometry for everything that follows without any component needing to know it happened.

Why centralizing breaks mattered

Page breaks are not an afterthought. A page boundary changes the available geometry for everything after it, so the decision belongs at the center of the pipeline rather than distributed across the components affected by it.

The practical payoff was testability. With breaking isolated, a page-break bug became reproducible from a layout fixture rather than from a full document render — which is the difference between a bug you can fix and a bug you keep re-encountering.

The threat model nobody expects

A Markdown renderer executes untrusted input, and Markdown has images. If it runs server-side, an attacker who can write Markdown can make your server issue HTTP requests to addresses of their choosing — including the cloud instance metadata endpoint.

v4 added an opt-in security layer for exactly that: protocol and domain allowlists, private/link-local/metadata IP blocking, image count and size caps, a render timeout, and a violationMode of skip, throw, or placeholder because a public generator, an internal pipeline, and a CMS preview each need a different correct behaviour. Two limits are unconditional — 2 MB of input and 300 levels of nesting — because those are availability, not policy.

Highlights

  • Unified inline layout across text styles and inline elements
  • Centralized page-break handling instead of per-component guesses
  • Headings, lists, task lists, tables, images with sizing, code, blockquotes
  • Configurable typography, headers, footers, and page numbers
  • Opt-in SSRF and resource-exhaustion controls for untrusted Markdown
  • A public API that stays useful for both simple documents and complex layouts

The full write-up — layout engine, pagination, and the SSRF problem — is in A Markdown renderer is a browser you didn't mean to write.

← ALL WORKCASE STUDY / JSPDF-MD-RENDERER