Capabilities
htmlpdfx has two rendering tiers, and they are deliberately not equal. Knowing which one you're on is the difference between "my PDF matches my webpage" and "close enough".
Chromium
The browser's own print-to-PDF, via Puppeteer (bundled) or Playwright. The PDF is the webpage, rendered by the same engine — so page breaks, fonts, CSS, and layout are correct by construction. Runs on Node, Bun, and Deno, and is the default whenever a Chromium driver is available.
Use this for anything user-facing.
Canvas
A zero-dependency DOM rasterizer for the browser when Chromium isn't available. It produces a faithful screenshot of your DOM, sliced cleanly across pages — but it is a raster, not a reflow.
Use it as graceful degradation, not as a replacement for the real engine.
What each tier supports
Every row says why the two differ, because "partial" on its own isn't useful information.
Chromium
High fidelity
19 of 23 full
Canvas
Best effort
10 of 23 full
Text and vectors
Selectable, searchable text
The canvas tier rasterizes body text into the page image, so it can't be selected, searched, or copied.
Vector graphics, crisp at any zoom
Canvas embeds a raster image, so zooming in shows pixels.
Clickable links
<a href> becomes a real PDF link annotation in Chromium; it's flattened into the image in canvas.
Unicode and i18n
Any glyph the browser can render. PDF strings are escaped safely in both tiers.
RTL and bidirectional text
Chromium uses the real bidi engine. Canvas depends on what the host browser resolves before rasterizing.
Pagination
Native CSS page breaks
break-before, break-after, break-inside and @page are honoured by a real layout engine in Chromium. Canvas approximates them with a measured slicer.
Never splits an atomic block
Both tiers back up to the top of a table row, card, or figure rather than cutting through it.
Repeating table headers
<thead> repeats on every page a table spans — free in Chromium via table-header-group.
No blank pages
Page count is exactly ceil(content / page), with no trailing blank.
Headers and footers
{{page}}, {{pages}} and {{title}} tokens. Selectable text in Chromium; rasterized in canvas.
CSS and layout
Modern CSS — grid, flexbox, oklch()
Canvas depends on the host browser's foreignObject support for advanced CSS.
Web fonts
document.fonts.ready is awaited before rendering, and fonts are embedded.
Background colours and images
print-color-adjust: exact is forced, so backgrounds survive.
Page size and orientation
Never silently auto-flipped — an explicit custom size is preserved as given.
Document features
Outline and bookmarks
Generated from heading structure with chromium: { outline: true }.
Tagged, accessible output (PDF/UA)
On by default in the Chromium tier.
Interactive form fields
AcroForm widgets aren't implemented yet. Tracked on the roadmap.
Password and encryption
User and owner passwords and permission flags aren't implemented yet. Tracked on the roadmap.
Running it
Runs server-side, headless
The canvas tier needs a DOM, so it's browser-only.
Runs in the browser
Exactly the inverse — this is why auto picks per runtime.
Warm-browser pooling
createRenderer() keeps one browser open across renders.
Configurable DPI and scale
Canvas defaults to 2× device scale.
No install required
Puppeteer ships as an optional dependency, so Chromium arrives with it — but it is a download. The canvas tier needs nothing.
Check the tier at runtime
The same matrix is available programmatically, so you can branch, warn, or degrade a feature instead of shipping a surprise:
import { engineCapabilities, describeEngines } from "htmlpdfx";
engineCapabilities.chromium.selectableText; // true
engineCapabilities.canvas.tier; // "best-effort"
describeEngines(); // one-line summary for a log or CLIOr read it off a finished render:
const pdf = await toPdf(html);
if (pdf.engine === "canvas") {
console.warn("Rasterized fallback — text in this PDF is not selectable.");
}Not implemented yet
These are real gaps rather than partial support. They're additive, so they can land in a 1.x release:
Form fields
Interactive AcroForm widgets.
Encryption
User and owner passwords, and permission flags.
<PdfExport>
A browser component for client-side React and Vue export.
How this is verified
Every claim above is checked against a real produced PDF rather than asserted in prose. test/e2e.mjs renders through real Chromium in CI and asserts on the output bytes — page count, extractable text, repeating <thead>, link annotations, a navigable outline, and renderer pooling — while test/cross-browser.mjs drives the canvas tier through Chromium, Firefox, and WebKit.
npm test # unit + behaviour suite
npm run test:browser # Chromium + Firefox + WebKit rendering
npm run test:e2e # assert on a real Chromium-produced PDF