You're reading the v2 beta docs. For the stable release, switch to v1 →
TakumiTakumi

Typography & Fonts

font management and advanced text rendering.

Takumi does not use system fonts. All fonts must be explicitly loaded to be available for rendering.

For @takumi-rs/core (napi-rs version), full axis Geist and Geist Mono are embedded by default.

For @takumi-rs/wasm, a single variable Latin font, Manrope, is embedded by default.

If you need a monospace family in WASM, provide your own font.

Adding fonts

Pass the fonts a render needs through the fonts option. Each entry is raw bytes or a { name, data, weight, style } descriptor, and data may be a loader that returns the bytes. The renderer registers and deduplicates them, so a reused renderer pays the decode cost once.

import {  } from "takumi-js/response";

export function () {
  return new (< />, {
    : [
      {
        : "Inter",
        : () => ("/path-to-inter.woff2").(() => .()),
      },
    ],
  });
}

The same fonts option works on render, renderAnimation, and encodeFrames.

From Google Fonts

googleFont fetches a family's CSS from Google Fonts and returns ready-to-use fonts entries — one lazy loader per woff2 file, downloaded only when the renderer first needs it. Reference the family by its name in font-family.

import { render } from "takumi-js";
import { googleFont } from "takumi-js/helpers";

const image = await render(<div style={{ fontFamily: "Inter" }}>Hello</div>, {
  width: 1200,
  height: 630,
  fonts: await googleFont("Inter", { weight: [400, 700] }),
});

weight takes one weight, an array, or a range like "100..900" that loads the variable font and leaves CSS font-weight in control. style is "normal", "italic", or both. text limits the download to the glyphs that string uses — worth it for OG images, where you know the text up front.

fonts: await googleFont("Inter", { weight: 700, style: "italic", text: title }),

Content-driven subsets

For multilingual content where you can't predict which scripts appear, googleFontSubsets loads only the subsets the content actually renders. Give it your content and the families to draw from; it scans the codepoints, fetches every family's metadata in one request, and keeps just the subsets whose unicode-range intersects.

import { render } from "takumi-js";
import { fromJsx } from "takumi-js/helpers/jsx";
import { googleFontSubsets } from "takumi-js/helpers";

const element = <div style={{ fontFamily: "Inter" }}>Hello 你好 こんにちは</div>;

const { node } = await fromJsx(element);
const fonts = await googleFontSubsets(node, ["Inter", "Noto Sans JP", "Noto Sans TC"]);

const image = await render(node, { width: 1200, height: 630, fonts });

Each subset registers under a unique internal name, while font-family: Inter expands across every subset, so each script routes to the file that covers it. Pass a Map as cache to reuse the parsed metadata across renders — useful when a playground re-renders on every edit.

Preloading with registerFont

registerFont is the escape hatch for preloading: register a font on a renderer up front, outside the request path, then reuse that renderer. It accepts the same entry shape as fonts and resolves to the families it produced.

const renderer = new Renderer();
await renderer.registerFont({ name: "Inter", data: inter });

return new ImageResponse(<OgImage />, { renderer });

Font fallback chain

fontFamilies is the ordered list of families tried in turn when a glyph is missing. It defaults to the families you passed, in order, so the chain follows the order you listed your fonts. Set it to pin which family wins and what backs it up.

const image = await renderer.render(node, {
  fontFamilies: ["Inter", "Noto Sans JP"],
});

Variations & Features

Thanks to underlying engine support, you can control font axes using the font-variation-settings CSS property, or font-feature-settings for OpenType features.

For variable fonts, font-weight has the same effect as font-variation-settings: "wght" <weight>.

<div
  style={{
    fontFamily: "Manrope",
    fontVariationSettings: "'wght' 700, 'wdth' 150",
    fontFeatureSettings: "ss01",
  }}
>
  Variable Font Text
</div>

Render Emojis

Dynamic fetching

If you are using ImageResponse API, there's a satori compatible emoji option.

import {  } from "takumi-js/response";

export function () {
  return new (< ="flex justify-center items-center text-3xl">Hello 👋😁</>, {
    : "twemoji", 
  });
}

Under the hood it calls extractEmojis helper function, which separates the emoji segments from the text and modifies the text node.

import {  } from "takumi-js/helpers/emoji";
import {  } from "takumi-js/helpers/jsx";
import { ,  } from "takumi-js/helpers";
import {  } from "takumi-js/node";

let {  } = await (< ="flex justify-center items-center text-3xl">Hello 👋😁</>);
 = (, "twemoji");

const  = ();
const  = await ();

const  = new ();

const  = await .(, {
  ,
});

COLR/Bitmap Font File

Takumi supports the COLR font format, which is commonly used for emojis like Twemoji-COLR.

The file size is much smaller than rasterized emoji fonts like Noto Color Emoji.

Typography

Overflow Ellipsis

When text-overflow is set to ellipsis, Takumi tries to match the expected line-clamp constraint or maximum container height.

Setting white-space: nowrap is not required, which enables multiline ellipsis handling.

<div
  style={{
    textOverflow: "ellipsis",
    lineClamp: 3,
  }}
>
  Super Long Text
</div>

RTL & Bidirectional Text

Support for Right-to-Left (RTL) languages like Arabic or Hebrew is handled automatically by the underlying Parley engine.

But currently there's no manual control over the direction of the text (see issue #330).

Wrapping

Takumi supports both balance and pretty text wrapping. The algorithm is modified from satori's implementation.

<div style={{ textWrap: "balance" }}>Super Long Text</div>
Edit on GitHub

Last updated on

On this page