只支持浏览器 CSS 的子集,PDF 输出会拒绝 blur() 和 drop-shadow() 且不绘制 backdrop-filter;远程资源需自行预加载,不会像 Puppeteer 那样自动抓取;字体需通过 fonts 选项或 Renderer 显式注册,未覆盖的语言字形可能缺失。
takumi
kane50613Apache-2.0活跃维护用 Rust 写的轻量布局引擎,直接解析 JSX、HTML 和 CSS,把页面渲染成图片、SVG、PDF 或绘制树,不需要无头浏览器,适合生成 OG 图、发票和动态图像。
One layout engine for JSX, HTML, and CSS. Render images, SVG, PDFs, or a paint tree. No headless browser.
bun i takumi-js
bun i takumi-pdf
bun i takumi-paintREADME 未说明显存或 GPU 要求;Node.js 与 Bun 使用预编译原生绑定(macOS、Linux glibc/musl、Windows,x64 与 ARM64),Cloudflare Workers 和浏览器走 WebAssembly 构建。
用 Rust 写的布局引擎,把 JSX/HTML/CSS 直接渲染成图片、SVG 或 PDF,无需无头浏览器
用 Rust 写的轻量布局引擎,直接解析 JSX、HTML 和 CSS,把页面渲染成图片、SVG、PDF 或绘制树,不需要无头浏览器,适合生成 OG 图、发票和动态图像。
核心能力
- 同一份布局可输出 PNG/WebP/GIF、PDF 或 paint tree
- 文本与行内布局移植自 Chromium,支持 Flexbox、Grid、表格与浮动
- 支持 Tailwind v4 工具类、var()、calc() 与媒体查询
- PDF 支持分页、页眉页脚、可选中文字与书签大纲
- 提供 next/og 兼容的 ImageResponse 与 Rust crate
容易踩的坑
- [email protected]2026-11-08 · 2026-11-08 08:05 UTC
查看更新说明
Raster pages can take their sizes from
@pagerulesrender_pagestakes anything that converts into aPageSizer. ASizestill gives every page the same area, and each page's image is now that page's own size.PageRules::new(stylesheet, page_box, context)converts into aPageSizerthat gives each page the area the stylesheet's@pagerules give it, including:first,:left,:right, and page names.contextis thePageContextthe root element gives, fromPageContext::from_root.PageRules::page_boxreturns a page's size and margins, andViewport::print_pageis the viewport an@pagerule resolves against.Positions take offsets from a side, and an offset image keeps its pixels
object-position: bottom 1px right 2pxused to make its declaration invalid, so the image stayed centered. A position now takes the four-value form, where each side keyword takes an offset from that side, andbackground-positionandmask-positionalso take the three-value form, as in Chromium.An image drawn smaller than its file and reaching past its content box, such as one an offset moves, used to be squeezed into the part that shows. It now draws at its full size, and the box crops it.
- [email protected]2026-11-08 · 2026-11-08 08:05 UTC
查看更新说明
PaintTree.free()releases a tree's font files and glyph outlinesA paint tree keeps its font files and glyph outlines in WebAssembly memory until garbage collection releases them, which can take a while after many paints.
tree.free(), or ausingdeclaration, releases them right away. Calling it again does nothing, and the painter and other trees stay usable. Reading the files of a freed tree throwsThe paint tree was freed.A
unicodeRangesubset without a glyph falls back to the next family againA font in
fontswhoseunicodeRangeclaims a character it has no glyph for now hands that character on to the next family in the stack, as a browser does and as 0.18 did. Before, the render left the next family's subset unloaded. The PDF render then failed with "No registered font covers". That error now reads "No font the text can use draws", and suggests checking each font'sunicodeRangeand thefont-familystack, because a registered font can have the glyph and still be out of reach.A
usingblock frees a rendererRenderer,PdfRendererandPainterimplementSymbol.dispose, sousing renderer = new Renderer()frees the renderer's WebAssembly memory when the block ends, as callingfree()does.preparedownloads the fonts@font-facerules loadAn
@font-facerule incssor a<style>element now loads its font inprepare,render, andImageResponse. Asrcthat fails to download falls back to the next one. - [email protected]2026-11-08 · 2026-11-08 08:05 UTC
查看更新说明
The
pageproperty puts content on named pagesAn element with
page: coverstarts a page that@page coverrules size and margin, and the content after it that names no page goes back to the unnamed pages, as in Chromium.PageSizer::newnow takes the page name with the page index, andPageSizes::namereturns the name each page laid out as.A
unicodeRangesubset without a glyph falls back to the next family againA font in
fontswhoseunicodeRangeclaims a character it has no glyph for now hands that character on to the next family in the stack, as a browser does and as 0.18 did. Before, the render left the next family's subset unloaded. The PDF render then failed with "No registered font covers". That error now reads "No font the text can use draws", and suggests checking each font'sunicodeRangeand thefont-familystack, because a registered font can have the glyph and still be out of reach.Content moved past the last page gets pages of its own
A box that a transform or
position: relativemoved past the end of the content now adds the pages it reaches, as Chromium prints it. Before, the document ended on the last page of the content, and the moved box was cut off. A shadow, and content its box clips, add no page, as in Chromium.A
usingblock frees a rendererRenderer,PdfRendererandPainterimplementSymbol.dispose, sousing renderer = new Renderer()frees the renderer's WebAssembly memory when the block ends, as callingfree()does.PDF renders decode a repeated
data:URI image onceA PDF that repeats a
data:URI image, such as a table with the same signature in every row, used to decode and parse the image each time layout looked at it, so long documents rendered slowly.PdfOptionsandMeasureOptionsin thetakumi-pdfcrate take aresource_cacheas the raster renderer's options do, and a render without one keeps its own.PdfRendererpasses its cache, so its renders share decoded images.The
takumi-pdfcrate is on crates.ioRust programs can depend on
takumi-pdfto write PDFs with selectable text, as thetakumi-pdfnpm package does. Itsrender,measure, andneedstake the node tree, fonts, and stylesheet types fromtakumi-core.Paged PDFs keep the text a transform or a clip moves
In a paged PDF, text in a rotated or scaled box went missing from every page after the first. A line that an
overflow: hiddenbox or the page edge cut in half went missing too. Each now draws on the page that shows it, as Chromium prints it, and the PDF holds its text. A line a transform moves across a page edge draws its part on each page, as Chromium prints it. One page holds its text, and the other draws outlines, so the text is not repeated. A line a transform moves above the first page or below the last also draws, on that page.preparedownloads the fonts@font-facerules loadAn
@font-facerule incssor a<style>element now loads its font inprepare,render, andImageResponse. Asrcthat fails to download falls back to the next one.An
hrefon a table row or row group rendersAn
hrefon atr,thead,tbody, ortfootused to fail the whole render withinvalid tag tree: annotation identifier ... doesn't appear in the tag tree. The link now covers the row or group as it does on other elements, and itsLinkelement sits in the last cell the link spans.Content after a line taller than a page starts below it
When a line held something taller than a page, such as a tall
inline-block, the lines after it started at the top of the next page and drew over the part that ran on. They now start below it, as Chromium prints them.@pagerules size and margin the pages of a PDFA
@pagerule sets the pagesizeand margins and wins over thesize,landscape, andmarginoptions, as CSS does in Chromium withpreferCSSPageSize.:firststyles - [email protected]2026-11-08 · 2026-11-08 08:05 UTC
查看更新说明
Published [email protected].
- [email protected]
- @takumi-rs/[email protected]
- @takumi-rs/[email protected]
- @takumi-rs/[email protected]
- @takumi-rs/[email protected]
- [email protected]
registerFontregisters one buffer again under another name, weight, style orunicodeRangePassing the same
databuffer toregisterFonta second time with other descriptors used to return the first registration, so the new family or weight never existed. Each set of descriptors now registers its own face, as@font-facerules sharing onesrcdo. A lazy font's key includes itsunicodeRangetoo.A
unicodeRangesubset without a glyph falls back to the next family againA font in
fontswhoseunicodeRangeclaims a character it has no glyph for now hands that character on to the next family in the stack, as a browser does and as 0.18 did. Before, the render left the next family's subset unloaded. The PDF render then failed with "No registered font covers". That error now reads "No font the text can use draws", and suggests checking each font'sunicodeRangeand thefont-familystack, because a registered font can have the glyph and still be out of reach.A
usingblock frees a rendererRenderer,PdfRendererandPainterimplementSymbol.dispose, sousing renderer = new Renderer()frees the renderer's WebAssembly memory when the block ends, as callingfree()does.preparedownloads the fonts@font-facerules loadAn
@font-facerule incssor a<style>element now loads its font inprepare,render, andImageResponse. Asrcthat fails to download falls back to the next one.Load fonts from
@font-facerulesWith the
font-facefeature, on by default intakumi,@font-facerules add fonts to a render.needsreports the files the text uses inRenderNeeds::font_files. Pass each toFonts::load_font_file, or toFonts::fail_font_fileto fall back to the nextsrc.
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
One layout engine for JSX, HTML, and CSS. Render images, SVG, PDFs, or a paint tree. No headless browser.
Documentation · Playground · Showcase
Takumi is a layout engine written in Rust, with text and inline layout ported from Chromium. It lays out JSX, HTML, and CSS without launching a browser. One layout feeds several outputs, so the same component can become an Open Graph image, an animated GIF, an invoice PDF, or the paint tree behind editable slides.
Choose a Package
| Use | Package | Output |
|---|---|---|
| Images and animations | takumi-js | PNG, JPEG, WebP, ICO, raw RGBA, SVG. Animated WebP, APNG, and GIF. |
| PDF documents | takumi-pdf | Paged or single-page PDF with selectable text. |
| Your own renderer or layout data | takumi-paint | A paint tree of every box, text run, and image, positioned with CSS values resolved. |
| Embedding in Rust | takumi | Images, animations, and SVG from a node tree or HTML. |
Quick Start
Install the package for your output. The examples below use Bun to run JSX directly.
bun i takumi-js # PNG, JPEG, WebP, SVG, animations
bun i takumi-pdf # paged PDF
bun i takumi-paint # paint tree
Image
Save this as image.tsx and run bun image.tsx to write a 1200 × 630 PNG.
import { render } from "takumi-js";
import { writeFile } from "node:fs/promises";
const image = await render(
<div tw="w-full h-full flex items-center justify-center bg-linear-to-b from-blue-100 to-red-50">
<h1 tw="text-6xl font-bold">Hello from Takumi</h1>
</div>,
{ width: 1200, height: 630 },
);
await writeFile("./output.png", image);
Save this as document.tsx and run bun document.tsx to write an A4 PDF with a page-number footer.
import { render } from "takumi-pdf";
import { PageNumber, TotalPages } from "takumi-pdf/primitives";
import { writeFile } from "node:fs/promises";
const pdf = await render(
<main>
<h1>Invoice</h1>
<p>Total: $1,250.00</p>
</main>,
{
size: "a4",
footer: (
<div tw="flex w-full justify-center text-[10px] text-gray-500">
Page <PageNumber /> of <TotalPages />
</div>
),
},
);
await writeFile("invoice.pdf", pdf);
Paint
Save this as paint.tsx and run bun paint.tsx to print each text run with its font.
import { paint } from "takumi-paint";
const tree = await paint(
<div style={{ width: 240, padding: 16, background: "#F7F3EC" }}>
Hello <b>world</b>
</div>,
{ width: 320 },
);
for (const node of tree.nodes) {
if (node.type !== "text") {
continue;
}
for (const run of node.runs) {
console.log(run.text, run.font.family, run.fontSize);
}
}
tree.steps lists the draws in paint order. See Drawing to replay them on a canvas.
Features
Images
text-fitgrows or shrinks a headline to fill its line box, no measuring loop.text-wrap: balancebalances lines in a headline.prettyreduces short last lines.- Animations sample the tree across time.
@keyframesandanimate-spinbecome WebP, APNG, GIF, or video frames. - Stylesheets support complex selectors,
var(),calc(), and media queries. - Flexbox, CSS Grid, block, inline, table, and float layout are supported.
langpicks each language's own Han glyphs for the same code points.- Text on a path follows
offset-path.background-clip: textand conic gradients paint it. - Masks, clipping, filters, and blend modes control how image layers combine.
- SVG filters run through
filter: url(...),feTurbulenceandfeDisplacementMapincluded. corner-shapeswaps round corners forsquircle,bevel,scoop,notch, orsuperellipse(n).- Tailwind v4 utilities apply directly, arbitrary values included.
- Page breaks honor
break-before: page,break-after: page, andbreak-inside: avoid. - Widows and orphans default to 2, keeping lone lines away from page breaks.
- Headers and footers repeat on every page.
<PageNumber />and<TotalPages />count in CSS counter styles,trad-chinese-informalincluded. - Tables share column widths across pages and repeat their
<thead>and<tfoot>on every page. - Text stays selectable and searchable. Fonts embed as subsets.
- Links and metadata carry into the output.
outline: truebuilds bookmarks from headings. - Attachments embed files, including Factur-X e-invoice XML.
- Tagged PDF is on by default. See the PDF/A and PDF/UA guide for conformance options and validation.
- Multilingual text supports Arabic shaping, bidirectional layout, CJK, and emoji when the loaded fonts cover them.
Runtimes
- Node.js and Bun load a native binding, prebuilt for macOS, Linux (glibc and musl), and Windows on x64 and ARM64.
- Cloudflare Workers and browsers load the WebAssembly build.
takumi-pdfandtakumi-paintrun on WebAssembly everywhere. - Rust applications embed the
takumicrate.
Coming From Something Else
| You are using | What changes |
|---|---|
satori | Replace satori() with renderSvg(), or call render() for encoded image bytes. Compare the renderers. |
next/og | Swap the ImageResponse import. Keep explicit Flexbox styles and compare the rendered output. Read the migration guide. |
| Puppeteer or Playwright for PDFs | Replace page.pdf() with render(). You must preload remote assets, and Takumi supports less CSS than Chrome. Read the migration guide. |
@react-pdf/renderer | Replace Document, View, and Text with HTML elements and CSS. Browser viewers and some text controls have no equivalent. Read the migration guide. |
pdfkit | Use JSX or HTML instead of positioning each line. Keep pdfkit when you need low-level drawing control. |
Fonts and Output Formats
Fonts
import { render } from "takumi-js";
import { googleFonts } from "takumi-js/helpers";
const image = await render(
<div
tw="w-full h-full flex items-center justify-center"
style={{
fontSize: 72,
fontFamily: "Fraunces",
fontVariationSettings: "'opsz' 72, 'wght' 700",
}}
>
Hello from Takumi
</div>,
{
width: 1200,
height: 630,
fonts: googleFonts([{ name: "Fraunces", weight: "100..900", axes: { opsz: "9..144" } }]),
},
);
render() registers only the font subsets the content uses. To preload fonts outside the request path, register them on a Renderer and pass it as renderer. See Fonts.
API route (next/og-compatible)
import { ImageResponse } from "takumi-js/response";
export function GET() {
return new ImageResponse(
<div tw="w-full h-full flex items-center justify-center bg-linear-to-b from-blue-100 to-red-50">
<h1 tw="text-6xl font-bold">Hello from Takumi</h1>
</div>,
{ width: 1200, height: 630 },
);
}
Animated WebP
import { renderAnimation } from "takumi-js";
import { writeFile } from "node:fs/promises";
const animation = await renderAnimation({
width: 400,
height: 400,
fps: 30,
format: "webp",
scenes: [
{
durationMs: 1000,
node: (
<div tw="w-full h-full flex items-center justify-center">
<div tw="w-32 h-32 bg-blue-500 animate-spin rounded-lg" />
</div>
),
},
],
});
await writeFile("./output.webp", animation);
Vector SVG
import { renderSvg } from "takumi-js";
import { writeFile } from "node:fs/promises";
const svg = await renderSvg(
<div tw="w-full h-full flex items-center justify-center bg-linear-to-b from-blue-100 to-red-50">
<h1 tw="text-6xl font-bold">Hello from Takumi</h1>
</div>,
{ width: 1200, height: 630 },
);
await writeFile("./output.svg", svg);
Rust
cargo add takumi
Start from the Rust example or the crate docs.
Comparison
Compare Takumi and Satori for image formats, CSS support, runtime requirements, and migration examples. For PDFs, the renderer comparison records invoice benchmarks against @react-pdf/renderer and Puppeteer, with package versions and hardware.
Takumi supports a subset of browser CSS. PDF output keeps only color filter functions. It rejects blur() and drop-shadow(), and it does not draw backdrop-filter. Keep Puppeteer when your output depends on page scripts or browser layout. Test your templates before switching renderers.
Who's Using Takumi
- Dcard renders post share images
- TanStack renders OG images for its docs
- Fumadocs generates its docs OG images
- Nuxt OG Image ships Takumi as a built-in renderer
- Luma renders event share images
- shiki-image turns syntax-highlighted code into images
More projects in the showcase. Takumi is part of the Vercel OSS Program.
Core Architecture
Takumi converts JSX or HTML to a node tree and lays it out in takumi-core, whose block, inline, flexbox, grid, and table layout port Chromium's Blink. HarfRust shapes text, and Skrifa reads fonts.
The laid-out tree goes to one of four backends: raster (takumi-raster), SVG (takumi-svg), PDF (takumi-pdf), or the paint tree (takumi-paint). See the node reference for the input format.
Packages
| Package | Use |
|---|---|
takumi-js | JSX, HTML, and node trees to images, SVG, and animations. |
takumi-pdf | JSX, HTML, and node trees to PDF documents. |
takumi-paint | JSX, HTML, and node trees to a paint tree of used values. |
@takumi-rs/core | Native Node.js bindings for direct node-tree rendering. |
@takumi-rs/wasm | WebAssembly bindings for edge runtimes and browsers. |
@takumi-rs/helpers | Template conversion, fonts, images, and emoji. |
takumi | Rust crate for node trees and HTML to images, animations, and SVG. |
Showcase
| Takumi OG image (source) | Package OG card (source) |
|---|---|
![]() | ![]() |
| Prisma-style API card (source) | X-style social post (source) |
![]() | ![]() |
| Keyframe Animation (source) | shiki-image |
![]() |
See more examples for invoices, e-invoices, Next.js, Cloudflare Workers, TanStack Start, Svelte, Rust, and ffmpeg keyframe animation.
Contributing
Read CONTRIBUTING.md for local setup and the fixture workflow.
We welcome bug reports, feature requests, doc improvements, and new example integrations.
License
MIT or Apache-2.0
Bundled third-party code is listed in the third-party notices.




