Takumi

Generate PDFs

Install takumi-pdf, render your first PDF from JSX or HTML, and find the guide for your document.

takumi-pdf is takumi's PDF output, for invoices, reports, and other paged documents. Pass JSX, an HTML string, or a node tree, and style it with CSS or Tailwind. The renderer runs in WebAssembly and does not launch a browser.

It shares one layout engine, a port of Chromium's layout, with image output and the paint tree. Each output applies the same layout rules to a template.

Install

npm i takumi-pdf @takumi-rs/helpers

Render a document

Save this as invoice.tsx and run bun invoice.tsx to write invoice.pdf. On Node.js, compile the JSX first.

invoice.tsx
import {  } from "takumi-pdf";
import { ,  } from "takumi-pdf/primitives";
import {  } from "@takumi-rs/helpers";
import {  } from "node:fs/promises";

const  = await (
  <>
    <>Invoice 1042</>
    <>Total: $1,290</>
  </>,
  {
    : "a4",
    : await (["Inter"]),
    : (
      < ="flex w-full justify-center text-[10px] text-gray-500">
        Page < /> of < />
      </>
    ),
  },
);

await ("invoice.pdf", );

render() returns a Uint8Array. By default, these hold:

  • Content flows across A4 pages with automatic margins.
  • Text stays selectable, and fonts are embedded as subsets.
  • Text inside an SVG image is drawn as outlines, so it cannot be selected.

An HTML string goes through the same parser as takumi-js. A <style> tag in the string applies only to that render:

Render HTML
import {  } from "takumi-pdf";

const  = await (`
  <style>.total { font-weight: 700 }</style>
  <div><span class="total">Total: $1,290</span></div>
`);

Headers, footers, and measure() take the same inputs.

Find your guide

TaskGuide
Control page breaks and repeated table headersPagination
Add page numbers, headers, and footersHeaders & footers
Load fonts, logos, and photosFonts & images
Add links, a table of contents, or bookmarksLinks, outline & metadata
Create a receipt, certificate, or labelSingle-page PDFs
Generate archival or accessible outputPDF/A & PDF/UA
Embed invoice XML or another fileAttachments
Build a complete documentInvoices, Reports & statements, Charts, Tickets & receipts
Move from Puppeteer or react-pdfFrom Puppeteer, From react-pdf
Compare takumi-pdf with other toolsComparison

Page setup

Set page geometry
import {  } from "takumi-pdf";

const  = await (, {
  : "letter", // a page keyword, or { width, height } in CSS px at 96 dpi
  : true,
  : { : 48, : 32, : 48, : 32 },
});
OptionTypeDefaultDescription
sizea page keyword or { width, height }"a4"Page size in CSS px at 96 dpi. Keywords ignore case.
landscapebooleanfalseSwaps page width and height, including explicit sizes.
marginnumber, "auto", or { top?, right?, bottom?, left? }"auto"Margin in CSS px. A side left out of the object is "auto".
backgroundColorCSS colorunsetFills the whole page, margins included, under everything. Viewers show unset as white.
CSS @page rules are not parsed.

Each keyword is a portrait paper size:

KeywordSizeKeywordSize
"a3"297 × 420 mm"jis-b4"257 × 364 mm
"a4"210 × 297 mm"jis-b5"182 × 257 mm
"a5"148 × 210 mm"ledger"11 × 17 in
"b4"250 × 353 mm"legal"8.5 × 14 in
"b5"176 × 250 mm"letter"8.5 × 11 in

Automatic margins

Side"auto" resolves to
Top, bottomThe header or footer height plus a 20px inset, and at least 37.8px (1cm)
Left, right37.8px

On a page one inch or smaller along an axis, automatic margins on that axis are 0. To set a numeric margin that fits a band, see Sizing the margin.

Reuse a renderer

render() and measure() share one renderer. Create a PdfRenderer to keep a separate font registry:

Reuse a font registry
import {  } from "takumi-pdf";

const  = new ();
await .("https://example.com/fonts/Inter-Regular.woff2");

const  = await .();

Replace the example URL with your font file. A font loads once, however many renders list it.

MethodEffect
render(node, options?)Renders to PDF bytes
measure(node, options?)Returns the laid-out size in CSS px
registerFont(font)Registers a font that stays until you remove it
unregisterFont(family)Removes every face of a family
clearFonts()Removes every registered font except the bundled fallback
free()Releases the renderer's WebAssembly memory

new PdfRenderer({ fontCacheMaxBytes }) caps the decoded fonts that renders' fonts lists keep. Past the cap, the least recently used families are released and load again when a render lists them. Fonts from registerFont() are never released.

Runtimes

Use the default import on Node.js, Bun, and Cloudflare Workers. For manual initialization, import takumi-pdf/no-init.

Next.js

Import takumi-pdf/next in a Next.js route handler. It works on the Node and Edge runtimes without a serverExternalPackages entry.

app/api/pdf/route.ts
import { render } from "takumi-pdf/next";

In the browser

In a browser bundle, initialize the renderer with takumi-pdf/no-init and takumi-pdf/wasm-url:

Initialize in the browser
import init, { render } from "takumi-pdf/no-init";
import wasmUrl from "takumi-pdf/wasm-url";

await init({ module_or_path: wasmUrl });

takumi-pdf/wasm-url resolves the binary with new URL(specifier, import.meta.url), and Vite, webpack, and Turbopack rewrite that call to the asset they emit. The module has no top-level await, so it also works in a worker bundled as an IIFE.

Keep the default entry in server code. A Vite SSR build leaves the URL pointing at the server chunk, where the asset is not emitted.

On Turbopack, remove any turbopack.rules entry that maps *.wasm to type: "wasm". That rule makes Turbopack instantiate the binary and look for wasm-bindgen glue the package does not ship.

Last updated on