Takumi

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, and list-style-image, whether style, tw, css, a <style> element, or a ::before rule sets them
  • images an animation's @keyframes switch 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().

Load named image sources
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:

Render raw RGBA pixels
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:

ValueEmoji setFetched from
twemoji (default)Twemojijdecked/twemoji 17.0.2
blobmojiBlobmoji@svgmoji/blob 2.0.0
notoNoto Emojigooglefonts/noto-emoji v2.051
openmojiOpenMoji@svgmoji/openmoji 2.0.0
fluentFluent Emoji, colorshuding/fluentui-emoji-unicode 7a40f1a
fluentFlatFluent Emoji, flatshuding/fluentui-emoji-unicode 7a40f1a
from-fontGlyphs from a color font you loadNo fetch 1

1 See COLR / bitmap fonts to load one.

Fetch emoji images
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.

Extract emoji before rendering
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:

Prepare, then render
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.
  • prepareAnimation prepares every scene of an animation from the options of renderAnimation, 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

Limit image fetches
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.

OptionDefaultEffect
timeout30000 msDeadline for each request, including retry delays. 1
signalnoneCancels a request and any pending retry.
maxBytes32 MiBRejects larger bodies, by content-length and by streamed bytes.
allowUrlallow allRejects a URL before fetching it, redirect destinations included.
fetchglobalThis.fetchReplaces 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 signal aborts
  • 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.

Render without the images that failed
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.

  • allowUrl checks each redirect destination, up to five hops. Each hop shares the same deadline and gets its own attempts.
  • A fetchCache hit checks only the requested URL, because cached bytes keep no redirect history. Do not share one fetchCache across different URL policies.
  • allowUrl sees URLs, not IP addresses. To filter by resolved address, pass a custom fetch.

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.

InputLimit
Image8192 × 8192 pixels
Animated GIF, APNG, or WebP4 × 8192 × 8192 pixels summed over all frames 1

1 Frames past 1024 are dropped.

Last updated on