Image loading & emoji
Load remote, pre-fetched, and raw-pixel images, render emoji, and tune fetch and decode limits in Takumi.
Takumi fetches the remote images your markup references, accepts images you fetch or decode yourself, and turns emoji into images. Fetch limits, retries, and decode limits come after these, at the end of the page. Caching covers how Takumi keeps downloaded and decoded images between renders.
External images
render() and ImageResponse fetch every remote http:// or https:// image the tree draws once its styles apply:
- an
<img>src background-image,mask-image, andlist-style-image, whetherstyle,tw,css, a<style>element, or a::beforerule sets them- images an animation's
@keyframesswitch to
An image under display: none is not fetched. A relative URL such as /logo.png is fetched only through your own fetch, which can resolve it.
When user input can reach the markup, set allowUrl, as URLs from user input describes.
To reuse downloaded bytes across renders, pass a fetchCache.
Pre-fetched images
Give images a key so the renderer never fetches them. Use the key in any src or CSS url().
import { } from "takumi-js/response";
export function (: ) {
return new (< />, {
: [
{
: "my-logo",
: () => (new ("/logo.png", .)).(() => .()),
},
{
: "background",
: () => (new ("/background.png", .)).(() => .()),
},
],
});
}
function () {
return (
<
={{
: "url(background)",
}}
>
< ="my-logo" />
</>
);
}data is raw bytes or a function that returns them. Takumi skips fetching any remote URL that already has an entry.
Raw pixels
Pass raw RGBA pixels instead of encoding a PNG first, and Takumi skips decoding. The Bitmap helper renders a row-major buffer of width * height * 4 bytes as an <img>. This example fills a 64 × 64 image with opaque blue:
import { } from "takumi-js";
import { } from "takumi-js/helpers/jsx";
const = new (64 * 64 * 4);
for (let = 0; < .; += 4) {
.([59, 130, 246, 255], );
}
const = await (< ={64} ={64} ={} />, {
: 64,
: 64,
});Set premultiplied when the bytes already have premultiplied alpha. Takumi then uses them as they are. Outside JSX, an image node's src takes the same { width, height, data, premultiplied? } object.
Emoji
takumi-js turns emoji into images by default. It splits each emoji out of the text and fetches its SVG from a CDN, using Twemoji unless you pick another set.
Dynamic fetching
Set emoji to choose the set. render, renderSvg, renderAnimation, measure, and ImageResponse all take it, and the CDN values match satori's. Each emoji's SVG comes from jsDelivr, at the version the table lists:
| Value | Emoji set | Fetched from |
|---|---|---|
twemoji (default) | Twemoji | jdecked/twemoji 17.0.2 |
blobmoji | Blobmoji | @svgmoji/blob 2.0.0 |
noto | Noto Emoji | googlefonts/noto-emoji v2.051 |
openmoji | OpenMoji | @svgmoji/openmoji 2.0.0 |
fluent | Fluent Emoji, color | shuding/fluentui-emoji-unicode 7a40f1a |
fluentFlat | Fluent Emoji, flat | shuding/fluentui-emoji-unicode 7a40f1a |
from-font | Glyphs from a color font you load | No fetch 1 |
1 See COLR / bitmap fonts to load one.
import { } from "takumi-js/response";
export function () {
return new (< ="flex justify-center items-center text-3xl">Hello 👋😁</>, {
: "noto",
});
}Emoji detection follows the Unicode presentation rules. A pictograph that defaults to text presentation, such as ‼ or ▶, stays text and renders with your fonts. Add U+FE0F after it to force the emoji image. Add U+FE0E after an emoji to keep the text glyph.
Manual extraction
The extractEmojis helper does the splitting. It replaces each emoji with an image node that points at the CDN. On your own renderer, call it before prepare(), which then fetches the emoji with your other images.
import { } from "takumi-js/helpers/emoji";
import { } from "takumi-js/helpers/jsx";
import { } from "takumi-js/node";
let { } = await (< ="flex justify-center items-center text-3xl">Hello 👋😁</>);
= (, "twemoji");
const = new ();
const = await .(, { : 1200, : 630 });
const = await .(, { : "webp" });COLR / bitmap fonts
Takumi draws emoji from color fonts, either COLR fonts such as Twemoji-COLR or bitmap fonts such as Noto Color Emoji. A COLR file is much smaller than a bitmap font. The glyphs come from the loaded font, so no emoji request goes out.
To use a color font, do both of these:
- Register the font through
fonts. - Set
emoji: "from-font".
Variation selectors take priority over font order. A code point followed by U+FE0F renders as a color emoji, even when a text font comes first in the stack. A code point followed by U+FE0E renders as a monochrome glyph, even when a color font comes first. Bare code points follow stack order, as in browsers.
Prepare on your own renderer
A renderer you create yourself fetches nothing on its own. Its prepare() does what render() does before drawing. It styles the source, fetches the images it draws, and loads the font subsets its text uses. Pass the result to the renderer's render, renderSvg, or measure:
const = new ();
const = new <string, <>>();
const = await .(, {
: 1200,
: 630,
: { },
});
const = await .(, { : "webp" });prepare takes the layout options of render, such as the size, css, lang, and fonts, plus images with the same fetch limits. Rendering the result takes only output options such as format, so the layout stays the one you prepared.
- A prepared source renders on any renderer from the same package, which loads the fonts again if it no longer holds them.
prepareAnimationprepares every scene of an animation from the options ofrenderAnimation, which then takes its result.
PdfRenderer from takumi-pdf and Painter from takumi-paint have the same prepare. A PDF's header, footer, and page bands count as part of the document, and rendering a prepared PDF takes the file options such as metadata, outline, and pdfa.
Fetch limits
import { } from "takumi-js";
const = < ="https://example.com/image.png" />;
const = await (, {
: {
: 3000,
: 4 * 1024 * 1024,
: () => new (). === "example.com",
},
});images, googleFonts, and fontFromUrl take these options. A custom data loader makes its own requests, so they do not apply to it.
| Option | Default | Effect |
|---|---|---|
timeout | 30000 ms | Deadline for each request, including retry delays. 1 |
signal | none | Cancels a request and any pending retry. |
maxBytes | 32 MiB | Rejects larger bodies, by content-length and by streamed bytes. |
allowUrl | allow all | Rejects a URL before fetching it, redirect destinations included. |
fetch | globalThis.fetch | Replaces the fetch implementation. |
1 A timeout of 0 or less disables the deadline.
Retries
GET and HEAD requests make up to three attempts. A request retries when fetch throws a TypeError (such as a connection error) or a TimeoutError, and on HTTP 408, 429, 500, 502, 503, and 504. Takumi waits 100 ms before the first retry and 200 ms before the second. A Retry-After header can extend a wait to at most one second. If it asks for a longer wait, Takumi returns the error without retrying.
Retries also stop when any of these happens:
- the caller's
signalaborts - the deadline passes
- any other HTTP error
- a failure while reading the response body
A custom fetch must honor the abort signal it receives.
Failed fetches
By default, one image that fails to fetch rejects the whole render. Set throwOnError: false to leave that image out and draw the rest. An emoji with no Twemoji image counts as a failed fetch too.
import { } from "takumi-js";
const = < ="https://example.com/image.png" />;
const = await (, {
: {
: false,
},
});URLs from user input
Takumi fetches these URLs from your server. Set allowUrl whenever user input can reach the
markup.
allowUrlchecks each redirect destination, up to five hops. Each hop shares the same deadline and gets its own attempts.- A
fetchCachehit checks only the requested URL, because cached bytes keep no redirect history. Do not share onefetchCacheacross different URL policies. allowUrlsees URLs, not IP addresses. To filter by resolved address, pass a customfetch.
Decode limits
Takumi rejects oversized images when it decodes them. The limits protect servers from decode bombs, which are small files that declare huge dimensions to force huge allocations.
| Input | Limit |
|---|---|
| Image | 8192 × 8192 pixels |
| Animated GIF, APNG, or WebP | 4 × 8192 × 8192 pixels summed over all frames 1 |
1 Frames past 1024 are dropped.
Last updated on