Long content that continues on the next page.
{title}
{description}
{/* [!code highlight] */} Pay online at example.com/pay/1042.
, ); ``` Inline anchors annotate each text run. A link that wraps across lines stays clickable on both lines. Block-level anchors annotate their entire box. `http`, `https`, `mailto` and `tel` links open outside the document. An `href` starting with `#` points inside it instead, to the element carrying that `id`: ```tsx twoslash import { render } from "takumi-pdf"; const pdf = await render(| Name | Qty |
|---|
Long content that continues on the next page.
| `, ` | `, and `
let x = 1; , {
fonts: [
{
name: "Geist Mono",
generic: "monospace",
data: () => fetch("https://example.com/GeistMono.woff2").then((res) => res.arrayBuffer()),
},
],
});
}
```
### From Google Fonts [#from-google-fonts]
`googleFonts` fetches families from Google Fonts in one request and returns `fonts` entries: one lazy loader per coverage subset, downloaded when the renderer first needs it. Reference each family by name in `font-family`. [Subsetting](#subsetting) trims them to the content, so unused weights and scripts download nothing.
```tsx twoslash
import { render } from "takumi-js";
import { googleFonts } from "takumi-js/helpers";
const image = await render(Hello 你好 こんにちは , {
width: 1200,
height: 630,
// [!code ++]
fonts: googleFonts(["Inter", "Noto Sans JP", "Noto Sans TC"]),
});
```
Pass an object instead of a name to set a family's weight, style, or variable axes:
```tsx twoslash
import { googleFonts } from "takumi-js/helpers";
await googleFonts([
{
name: "Inter",
weight: [400, 700],
style: "italic",
},
{
name: "Noto Sans JP",
weight: "600..700", // range loads the variable font
},
{
name: "Fraunces",
weight: "100..900",
axes: { opsz: "9..144" }, // vary the optical-size axis too
},
]);
```
Each family accepts:
| Field | Accepts | Effect |
| --------- | ------------------------------------------- | -------------------------------------------------------------------------- |
| `name` | a family name | The family to load. Known families autocomplete their weights and axes. |
| `weight` | a weight, an array, or a range `"100..900"` | A range loads the variable font; CSS `font-weight` drives it. |
| `style` | `"normal"`, `"italic"`, or both | Which styles to fetch. |
| `axes` | `{ tag: value \| "min..max" }` | Variable axes to load, e.g. `{ opsz: "9..144" }`. A range keeps them live. |
| `generic` | a CSS generic family keyword | Every loaded subset claims it, so e.g. `font-mono` resolves to the family. |
Pass an object instead of a list for request-level options:
| Option | Accepts | Effect |
| ---------- | ---------------------- | --------------------------------------------------------- |
| `families` | the list above | The families to load, in fallback priority order. |
| `display` | a `font-display` value | Maps to CSS `font-display`. |
| `cache` | a `Map` | Reuses the metadata CSS across renders. |
| `baseUrl` | a css2 endpoint | Point at a mirror such as `https://fonts.bunny.net/css2`. |
Each subset registers under its own internal name; `font-family: Inter` expands across all of them, so each script finds the file that covers it.
### Subsetting [#subsetting]
`render` registers only the subsets the content draws, so the same `fonts` work for any text and unused ranges never download. The pass runs automatically.
| Font kind | Behavior |
| ----------------------------------- | ------------------------------------------- |
| Ranged subsets (from `googleFonts`) | Trimmed to the codepoints the content uses. |
| Whole files (URL string, raw bytes) | No `unicode-range`, so they load in full. |
To trim before the render, call `subsetFonts({ fonts, source })`, where `source` is the string, node, or node array you render.
### Preloading with `registerFont` [#preloading-with-registerfont]
`registerFont` is the escape hatch for preloading. Register a font on a renderer up front, outside the request path, and reuse that renderer. It takes the same entries as `fonts` and returns the families it made.
```tsx
import { Renderer } from "@takumi-rs/core";
const renderer = new Renderer();
await renderer.registerFont({ name: "Inter", data: inter });
return new ImageResponse(
Variable Font Text
```
Register a variable font once, with no `weight`, and its axes stay live:
```tsx
// a `weight` here pins the file to that instance instead
await renderer.registerFont({ name: "Fraunces", data: variableWoff2 });
```
`googleFonts` does this for you: a weight range or an `axes` entry fetches the variable file and leaves its axes free.
No faux bold
```
Values are `weight`, `style`, or `none`. Pass `weight`, `style`, or both to allow each one; `none` disables them. Emoji never get a fake bold.
## Styling text [#styling-text]
### Color, stroke, and fill [#color-stroke-and-fill]
`color` accepts modern CSS color spaces:
* `rgb`
* `hsl`
* `oklch`
* `lab`
* `display-p3`
* and more
Outline glyphs with `-webkit-text-stroke`. Set a separate fill with `-webkit-text-fill-color`.
```tsx
Outlined
```
The stroke color defaults to `color` when you omit it.
### Decoration & transform [#decoration--transform]
`text-decoration` draws `underline`, `line-through`, and `overline`. Combine them, and set color and thickness. `text-transform` changes the case.
```tsx
Marked up
```
### Shadow [#shadow]
`text-shadow` takes offset, blur, and color. Stack layers with commas.
```tsx
Glow
```
### Spacing & alignment [#spacing--alignment]
`letter-spacing`, `word-spacing`, `line-height`, `text-indent`, and `text-align` (including `justify`) work as in the browser.
```tsx
Body copy
```
## Flowing text [#flowing-text]
### Wrapping [#wrapping]
Takumi supports `balance` and `pretty` wrapping, adapted from [satori](https://github.com/vercel/satori/blob/2a0878a7f329bdba3a17ad68f71186a47add0dde/src/text/index.ts#L365). `balance` evens out line lengths. `pretty` stops the last line from stranding one word.
```tsx
Super Long Text
```
`word-break` and `overflow-wrap` decide where a long token splits. `break-all`, `keep-all`, and `anywhere` cover CJK (Chinese/Japanese/Korean) and long URLs.
### Truncation [#truncation]
When `text-overflow` is `ellipsis`, Takumi truncates at the `line-clamp` count or the container's max height, whichever comes first. Multiline ellipsis works; no `white-space: nowrap` needed.
```tsx
Super Long Text
```
### Fit to container [#fit-to-container]
`text-fit` scales text to fit its line box instead of wrapping or overflowing. The mode comes first: `grow`, `shrink`, or `none`. An optional target and percentage cap follow.
```tsx
Headline that always fits
Per-line scaled, capped at 150%
```
The optional target controls which lines share a scale:
| Target | Effect |
| ---------------------- | --------------------------------- |
| `consistent` (default) | One scale for the whole block. |
| `per-line` | Scales each line except the last. |
| `per-line-all` | Scales every line. |
The percentage caps how far the scale can move.
### RTL & bidirectional text [#rtl--bidirectional-text]
Takumi handles Right-to-Left scripts like Arabic and Hebrew, including mixed runs. There's no manual override for text direction yet (see [issue #330](https://github.com/kane50613/takumi/issues/330)). The `direction` property controls layout, not the text run.
### Language-aware text [#language-aware-text]
The `lang` attribute sets the BCP-47 language for a node. Descendants inherit it, and a nested `lang` overrides. It drives locale-aware shaping: Han unification (one code point draws a different glyph for `zh-Hans`, `zh-Hant`, `ja`, or `ko`) and language-correct line breaking.
```tsx
日本語 繁體中文
{/* Your content here */}
```
## JavaScript / TypeScript [#javascript--typescript]
### Unified runtime detection and fallback [#unified-runtime-detection-and-fallback]
`takumi-js` now detects the right bindings for your environment (Node.js, Workers, and others). You no longer import NAPI (native Node addon) or WASM (WebAssembly) bindings directly.
```ts
import { ImageResponse } from "@takumi-rs/image-response"; // [!code --]
import { ImageResponse } from "takumi-js/response"; // [!code ++]
```
### `emoji` option for dynamic emoji loading [#emoji-option-for-dynamic-emoji-loading]
To match Next.js's `ImageResponse` API, `takumi-js` adds an `emoji` option for a custom emoji provider.
It defaults to `twemoji`. To source emoji glyphs from a font, set it to `from-font` and include an emoji font in `fonts`.
See [Images & emoji](/docs/load-images).
```tsx
import { ImageResponse } from "takumi-js/response";
import notoEmojiFont from "@fontsource/noto-color-emoji/files/noto-color-emoji-emoji-400-normal.woff2";
export function GET() {
return new ImageResponse(It works! 😀 , {
emoji: "from-font", // [!code ++]
fonts: [
{
name: "Noto Color Emoji",
data: notoEmojiFont,
},
],
});
}
```
### Lowercase image formats [#lowercase-image-formats]
Image format options in `@takumi-rs/core` now take lowercase strings only.
```ts
const image = await renderer.render(node, {
format: "WebP" // [!code --]
format: "webp" // [!code ++]
});
```
### `putPersistentImage()` takes `ImageSource` [#putpersistentimage-takes-imagesource]
`renderer.putPersistentImage()` no longer accepts a raw `Buffer` as the second argument. Pass an `ImageSource` object instead.
```ts
const data = await readFile("foo.png");
await renderer.putPersistentImage("foo.png", data); // [!code --]
await renderer.putPersistentImage({ src: "foo.png", data }); // [!code ++]
```
|
|---|