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.

[email protected]发布于 2026-11-08 08:05 UTC3,189star +8 / 7 天93fork同步于 2026-11-10 23:42 UTC

怎么上手

由 README 提炼,执行前请确认
bun i takumi-js
bun i takumi-pdf
bun i takumi-paint
硬件要求按 README 说明

README 未说明显存或 GPU 要求;Node.js 与 Bun 使用预编译原生绑定(macOS、Linux glibc/musl、Windows,x64 与 ARM64),Cloudflare Workers 和浏览器走 WebAssembly 构建。

下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

用 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

容易踩的坑

只支持浏览器 CSS 的子集,PDF 输出会拒绝 blur() 和 drop-shadow() 且不绘制 backdrop-filter;远程资源需自行预加载,不会像 Puppeteer 那样自动抓取;字体需通过 fonts 选项或 Renderer 显式注册,未覆盖的语言字形可能缺失。

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本[email protected]
  1. [email protected]2026-11-08 · 2026-11-08 08:05 UTC
    查看更新说明

    Raster pages can take their sizes from @page rules

    render_pages takes anything that converts into a PageSizer. A Size still 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 a PageSizer that gives each page the area the stylesheet's @page rules give it, including :first, :left, :right, and page names. context is the PageContext the root element gives, from PageContext::from_root. PageRules::page_box returns a page's size and margins, and Viewport::print_page is the viewport an @page rule resolves against.

    Positions take offsets from a side, and an offset image keeps its pixels

    object-position: bottom 1px right 2px used 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, and background-position and mask-position also 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.

  2. [email protected]2026-11-08 · 2026-11-08 08:05 UTC
    查看更新说明

    PaintTree.free() releases a tree's font files and glyph outlines

    A 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 a using declaration, releases them right away. Calling it again does nothing, and the painter and other trees stay usable. Reading the files of a freed tree throws The paint tree was freed.

    A unicodeRange subset without a glyph falls back to the next family again

    A font in fonts whose unicodeRange claims 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's unicodeRange and the font-family stack, because a registered font can have the glyph and still be out of reach.

    A using block frees a renderer

    Renderer, PdfRenderer and Painter implement Symbol.dispose, so using renderer = new Renderer() frees the renderer's WebAssembly memory when the block ends, as calling free() does.

    prepare downloads the fonts @font-face rules load

    An @font-face rule in css or a <style> element now loads its font in prepare, render, and ImageResponse. A src that fails to download falls back to the next one.

  3. [email protected]2026-11-08 · 2026-11-08 08:05 UTC
    查看更新说明

    The page property puts content on named pages

    An element with page: cover starts a page that @page cover rules size and margin, and the content after it that names no page goes back to the unnamed pages, as in Chromium. PageSizer::new now takes the page name with the page index, and PageSizes::name returns the name each page laid out as.

    A unicodeRange subset without a glyph falls back to the next family again

    A font in fonts whose unicodeRange claims 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's unicodeRange and the font-family stack, 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: relative moved 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 using block frees a renderer

    Renderer, PdfRenderer and Painter implement Symbol.dispose, so using renderer = new Renderer() frees the renderer's WebAssembly memory when the block ends, as calling free() does.

    PDF renders decode a repeated data: URI image once

    A 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. PdfOptions and MeasureOptions in the takumi-pdf crate take a resource_cache as the raster renderer's options do, and a render without one keeps its own. PdfRenderer passes its cache, so its renders share decoded images.

    The takumi-pdf crate is on crates.io

    Rust programs can depend on takumi-pdf to write PDFs with selectable text, as the takumi-pdf npm package does. Its render, measure, and needs take the node tree, fonts, and stylesheet types from takumi-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: hidden box 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.

    prepare downloads the fonts @font-face rules load

    An @font-face rule in css or a <style> element now loads its font in prepare, render, and ImageResponse. A src that fails to download falls back to the next one.

    An href on a table row or row group renders

    An href on a tr, thead, tbody, or tfoot used to fail the whole render with invalid 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 its Link element 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.

    @page rules size and margin the pages of a PDF

    A @page rule sets the page size and margins and wins over the size, landscape, and margin options, as CSS does in Chromium with preferCSSPageSize. :first styles

  4. [email protected]2026-11-08 · 2026-11-08 08:05 UTC
    查看更新说明
  5. [email protected]最新正式版2026-11-08 · 2026-11-08 08:05 UTC

    registerFont registers one buffer again under another name, weight, style or unicodeRange

    Passing the same data buffer to registerFont a 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-face rules sharing one src do. A lazy font's key includes its unicodeRange too.

    A unicodeRange subset without a glyph falls back to the next family again

    A font in fonts whose unicodeRange claims 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's unicodeRange and the font-family stack, because a registered font can have the glyph and still be out of reach.

    A using block frees a renderer

    Renderer, PdfRenderer and Painter implement Symbol.dispose, so using renderer = new Renderer() frees the renderer's WebAssembly memory when the block ends, as calling free() does.

    prepare downloads the fonts @font-face rules load

    An @font-face rule in css or a <style> element now loads its font in prepare, render, and ImageResponse. A src that fails to download falls back to the next one.

    Load fonts from @font-face rules

    With the font-face feature, on by default in takumi, @font-face rules add fonts to a render. needs reports the files the text uses in RenderNeeds::font_files. Pass each to Fonts::load_font_file, or to Fonts::fail_font_file to fall back to the next src.

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    One layout engine for JSX, HTML, and CSS. Render images, SVG, PDFs, or a paint tree. No headless browser.

    npm version npm version npm version crates.io npm downloads license

    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

    UsePackageOutput
    Images and animationstakumi-jsPNG, JPEG, WebP, ICO, raw RGBA, SVG. Animated WebP, APNG, and GIF.
    PDF documentstakumi-pdfPaged or single-page PDF with selectable text.
    Your own renderer or layout datatakumi-paintA paint tree of every box, text run, and image, positioned with CSS values resolved.
    Embedding in RusttakumiImages, 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);
    

    PDF

    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-fit grows or shrinks a headline to fill its line box, no measuring loop.
    • text-wrap: balance balances lines in a headline. pretty reduces short last lines.
    • Animations sample the tree across time. @keyframes and animate-spin become 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.
    • lang picks each language's own Han glyphs for the same code points.
    • Text on a path follows offset-path. background-clip: text and conic gradients paint it.
    • Masks, clipping, filters, and blend modes control how image layers combine.
    • SVG filters run through filter: url(...), feTurbulence and feDisplacementMap included.
    • corner-shape swaps round corners for squircle, bevel, scoop, notch, or superellipse(n).
    • Tailwind v4 utilities apply directly, arbitrary values included.

    PDF

    • Page breaks honor break-before: page, break-after: page, and break-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-informal included.
    • 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: true builds 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-pdf and takumi-paint run on WebAssembly everywhere.
    • Rust applications embed the takumi crate.

    Coming From Something Else

    You are usingWhat changes
    satoriReplace satori() with renderSvg(), or call render() for encoded image bytes. Compare the renderers.
    next/ogSwap the ImageResponse import. Keep explicit Flexbox styles and compare the rendered output. Read the migration guide.
    Puppeteer or Playwright for PDFsReplace page.pdf() with render(). You must preload remote assets, and Takumi supports less CSS than Chrome. Read the migration guide.
    @react-pdf/rendererReplace Document, View, and Text with HTML elements and CSS. Browser viewers and some text controls have no equivalent. Read the migration guide.
    pdfkitUse 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

    PackageUse
    takumi-jsJSX, HTML, and node trees to images, SVG, and animations.
    takumi-pdfJSX, HTML, and node trees to PDF documents.
    takumi-paintJSX, HTML, and node trees to a paint tree of used values.
    @takumi-rs/coreNative Node.js bindings for direct node-tree rendering.
    @takumi-rs/wasmWebAssembly bindings for edge runtimes and browsers.
    @takumi-rs/helpersTemplate conversion, fonts, images, and emoji.
    takumiRust crate for node trees and HTML to images, animations, and SVG.

    Showcase

    Takumi OG image (source)Package OG card (source)
    Takumi OG ImagePackage OG Image
    Prisma-style API card (source)X-style social post (source)
    Prisma OG ImageX-style Post Image
    Keyframe Animation (source)shiki-image
    Keyframe AnimationShiki Image Example

    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.