From Puppeteer
Replace headless Chrome with a 1.5 MB wasm module.
takumi-pdf was designed against Puppeteer's page.pdf(). Most options have a direct counterpart, header and footer bands use the same class hooks, and pagination follows Chromium's fragmentation rules. Names and input types differ in places, so read the map below.
What changes is the deploy. No browser process, no Chrome install, no page to navigate. The renderer takes a tree and returns bytes, so it runs on Cloudflare Workers.
Before and after
// Puppeteer
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(html, { waitUntil: "networkidle0" });
const pdf = await page.pdf({
format: "A4",
margin: { top: "48px", bottom: "64px", left: "48px", right: "48px" },
displayHeaderFooter: true,
footerTemplate: `<div style="font-size:10px;width:100%;text-align:center">
Page <span class="pageNumber"></span> of <span class="totalPages"></span>
</div>`,
printBackground: true,
});
await browser.close();// takumi-pdf
import { } from "@takumi-rs/helpers";
import { } from "@takumi-rs/helpers/html";
import { } from "takumi-pdf";
const { , } = ();
const = await (, {
: "a4",
: { : 48, : 64, : 48, : 48 },
,
: (
< ="flex w-full justify-center text-[10px]">
Page < ="pageNumber" /> of < ="totalPages" />
</>
),
: await (["Inter"]),
});fromHtml converts the markup already being fed to setContent. JSX and node trees skip that step. Bands take the same input, so a header can be a component instead of a template string.
Option map
page.pdf() | takumi-pdf |
|---|---|
format: "A4" | size: "a4" |
width, height | size: { width, height } |
landscape | landscape |
margin | margin, in CSS px |
displayHeaderFooter + headerTemplate | header |
footerTemplate | footer |
pageNumber, totalPages classes | the same classes |
date, title, url classes | interpolate the value yourself |
tagged (default on) | tagged (default on) |
outline | outline |
printBackground | always on |
path | write the returned Uint8Array |
timeout, waitForFonts | not needed: no page to load |
scale | not supported: scale the CSS instead |
pageRanges | not supported |
preferCSSPageSize, @page | not supported: set size and margin |
omitBackground | not supported |
Margins take numbers in CSS px. Chromium's "0.5in" strings have no equivalent; 48 is that half inch.
Fetching moves to your code
Nothing is fetched implicitly. A browser resolved image URLs and web fonts while it loaded the page. Now the fetch is a call you make:
import { } from "@takumi-rs/helpers";
import { } from "@takumi-rs/helpers/html";
import { } from "takumi-pdf";
const { , } = ();
const = await ({ });
const = await (, { , });prepareImages walks the tree, fetches every remote <img>, background-image, and mask-image URL, and returns the images entries. It takes a fetchCache to reuse bytes, an allowUrl predicate, and a timeout.
Fonts come from the fonts option rather than a CSS @font-face rule. See Fonts & images.
A render never reaches for a URL on its own. Timeouts and allow-lists sit in the fetch, where they can be enforced.
What Chrome still does better
Chrome is a browser. Takumi is a layout engine with a document-shaped CSS subset.
- No scripts. Charts and diagrams have to arrive as markup, SVG, or images. A client-side chart library will not run.
- Narrower CSS. No
filter: blur(),drop-shadow(), orbackdrop-filterin PDF output. A blurredbox-shadowapproximates with bands. Grid and flex, transforms, gradients, masks, and clip paths all work. - No
@page. Page geometry lives in the options.
Keep Puppeteer to reproduce a live web page as it appears in a browser. Move to takumi-pdf for documents written for print, and see Comparison for the measured numbers.
Last updated on