Preview & PDF

How the PDF is produced, exactly which part of it is exact, and when to render it on your own server instead.

@storvexa/pdf-builder Chapter 12 of 14
Documentation

Preview

Preview opens a modal dialog containing a real PDF, rendered at that moment and shown in the browser’s own PDF viewer. It is not a chrome-free view of the canvas — it is the finished file.

Zoom, download and print in that dialog belong to the browser, not to the package. The dialog itself has only a close button. That also means what you see there is exactly what a user gets when they save the file.
<PdfBuilder onPreview={(doc) => analytics.track('previewed', doc.page.size)} />

How the PDF is made

There is one built-in path, and it does something unusual that is worth understanding, because it determines exactly what you can promise.

1. capture mode on, editor chrome hidden 2. wait for fonts and images to finish loading 3. walk the canvas's FINISHED DOM and record flat absolute geometry in points — boxes, text lines and images at the coordinates the browser measured 4. draw those nodes with @react-pdf/renderer, each absolutely positioned 5. return a Blob 6. capture mode off

It runs entirely in the browser. No server, no headless Chromium, nothing to install — so it works on localhost, in the cloud, and on shared hosting.

The PDF is captured from the canvas, not re-rendered from the document. That single decision is the reason the geometry is trustworthy — and the reason fonts are not.

What is exact

The geometry. Positions, page breaks, spacing, page furniture. Because they are measured rather than recomputed. There is no second layout engine to drift out of sync with the first — those numbers are the browser's own, taken from the page your user arranged. A block on page 2 in the editor is on page 2 in the PDF.

What is approximated

Everything else is a faithful reconstruction, and must never be described as exact.

ThingWhat happens
Fonts Mapped to the 14 standard PDF families — Helvetica, Times, Courier. A custom webfont's metrics are approximated, not embedded, so line lengths can differ slightly.
Gradients Collapse to their first colour. A PDF fill is one colour.
Anything outside box, text and image Not captured. Shadows, for instance, are not drawn.
Do not promise pixel fidelity, and do not quietly drop what the capture cannot express. If your product's value depends on exact brand typography, use a server renderer — the next section — rather than hoping nobody notices the substitution.

Rendering on your server

onRenderPdf replaces the built-in path entirely with an engine you already run.

<PdfBuilder onRenderPdf={async (document) => { const res = await fetch('/api/render-pdf', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(document), }) return res.blob() // a Blob, or a URL string }} />

The returned file is what the preview shows and what the user downloads. Reach for it when:

  • the output must be produced with no user present — a nightly batch, a webhook;
  • you need embedded custom fonts or effects the capture cannot express;
  • you need a real browser engine, or PDF/A, or digital signing.
The package makes no request itself. You own the endpoint, the engine, the authentication and the storage. It only calls your function and displays what comes back.

"Content did not fit"

If a block is genuinely wider or taller than the printable area, the capture reports it rather than silently cropping. A tolerance of one point separates real overflow from sub-point rounding, so the warning means something when you see it.

Usual causeFix
An image larger than the columnSet a width, or a fit mode, on the image block.
A table with too many columnsHide columns, or switch the page to landscape.
Margins too large for the contentReduce the page margin.
An unbreakable block taller than a pageTurn off keepTogether for it.

Getting a better PDF

Choose fonts that map well. A document set in a humanist sans will substitute to Helvetica convincingly. A display or script face will not. If brand type is non-negotiable, render server-side.
Return image dimensions from onUploadImage. The canvas then reserves the right space before the image loads, so nothing reflows mid-capture.
Use ctx.printing in custom blocks to drop editor hints — but keep the size. Removing the element instead changes the layout between the canvas and the PDF, and an unfilled slot should simply print blank.
Solid colours beat gradients in a document that will be printed. A gradient collapses to one colour in the PDF, so the exported file will not match the canvas — pick the solid deliberately rather than discovering the substitution later.