Upgrade to v2
Walks through the breaking changes in v2 and how to upgrade from v1.
v2 makes fonts and images explicit per-render resources, tightens rendering to match the web platform, and splits the Rust crate into focused backends. This page covers the breaking changes; for everything else see the releases.
JavaScript
The Renderer constructor takes no arguments
new Renderer(...) no longer accepts fonts or a context. Construct it bare and register fonts afterwards. The embedded default fonts are decoded once and shared across every renderer.
const renderer = new Renderer({ fonts: [archivo] });
const renderer = new Renderer();
await renderer.registerFont(archivo); loadFont, loadFonts, and loadFontSync become registerFont
A single registerFont replaces the three loaders. It accepts either raw bytes or a { name, data, weight, style } descriptor, and resolves to the families it produced.
renderer.loadFonts([archivo, geist]);
await renderer.registerFont(archivo);
await renderer.registerFont(geist); Pick the fallback chain with fontFamilies
Each render now takes an ordered fontFamilies list: the family names tried in turn when a glyph is missing. It defaults to every registered family in registration order.
const image = await renderer.render(node, {
fontFamilies: ["Inter", "Noto Sans JP"],
});fetchedResources is now images
The pre-fetched image option was renamed.
const image = await renderer.render(node, {
fetchedResources: resources,
images: resources,
});The persistent image store and GlobalContext are gone
v1 kept a mutable image store and a GlobalContext on the renderer, so an image registered once stayed available for later renders. v2 removes both. Pass every image the render needs through images, keyed by src. See Load Images.
createImageResponse is removed
Construct ImageResponse directly and pass options inline. For shared defaults, wrap your own helper.
const ogImage = createImageResponse({ fonts: [inter] });
export function GET() {
return ogImage(<OgImage />);
return new ImageResponse(<OgImage />, { fonts: [inter] });
}Animations accept fonts
renderAnimation and encodeFrames now take fonts and fontFamilies, matching render.
CSS & rendering
currentColor in SVG images is no longer tinted by the host color
Through v1, an SVG supplied as an image (Node::image, background-image: url(...), or mask-image) had its currentColor rewritten to the host element's color before rendering. v2 removes this.
An SVG referenced as an image is an isolated document, so it does not inherit color from the host, the same rule browsers, satori, and @vercel/og follow. Its currentColor now resolves to the SVG's own default (black) instead of the surrounding text color. This also makes the raster and vector (SVG) backends agree, and lets the raster backend reuse its cached parse instead of reparsing the SVG on every render.
currentColor for Takumi's own properties (text color, border-color, box-shadow, outline, text-decoration-color) is unchanged. Only the contents of SVG images are affected.
If you relied on the old behavior to tint an icon, set the color in the SVG markup before passing it in:
const tinted = icon.replace(/currentColor/g, "#3b82f6"); position defaults to static
Through v1, position defaulted to relative, so every element established a containing block for absolutely-positioned descendants and honored top/right/bottom/left insets. v2 defaults to static to match the CSS specification, so an element only becomes a positioned containing block when you opt in.
If you relied on the default to anchor an absolutely-positioned child or to apply insets, set position: relative explicitly.
<div
style={{
display: "flex",
position: "relative",
}}
>
<div style={{ position: "absolute", top: 0, left: 0 }}>Badge</div>
</div>border-width and outline-width default to medium
Omitting the width in border / outline now resolves to the CSS initial value medium (3px) instead of 0, and the thin, medium, and thick keywords are accepted. border: solid red and outline: solid render a 3px line where they previously rendered nothing. The used width is still 0 when the line's style is none or hidden.
<div style={{ border: "solid red" }}>3px border in v2, invisible in v1</div>Negative scale factors reflect
scale: -1 and transform: scaleX(-1) / scaleY(-1) / scale(-1) now reflect the element instead of collapsing it to zero size.
line-clamp is now a shorthand
line-clamp is now a shorthand for the max-lines, block-ellipsis, and continue longhands (CSS Overflow 4), rather than a single collapsed property. Inheritance follows the spec: block-ellipsis inherits, while max-lines and continue do not, so a clamped ancestor no longer forces its line limit onto descendants. -webkit-line-clamp still works and expands to the same longhands.
Rust
takumi is split into focused crates
takumi is now a facade over takumi-core (layout, styling, resources), takumi-raster (the raster backend), and takumi-svg (the vector backend). The facade re-exports a curated stable surface, so most code keeps compiling, but the deep module paths it used to expose are gone.
Import the data structures from takumi::prelude and call the entry-point functions from the crate root:
use takumi::{
layout::{node::Node, Viewport, style::{Length::Px, Style, StyleDeclaration}},
resources::font::FontResource,
rendering::{render, RenderOptions},
GlobalContext,
};
use takumi::prelude::*;
use takumi::render; Backend internals are no longer re-exported. If you genuinely need them, enable the unstable feature and reach them through takumi::unstable; nothing under it is covered by semver.
GlobalContext becomes a Fonts context
With the image store gone, the render context holds only fonts. Build a Fonts, register resources on it, and pass it through RenderOptions::builder().fonts(&fonts).
let mut global = GlobalContext::default();
global.font_context.load_and_store(FontResource::new(font_bytes));
let mut fonts = Fonts::default();
fonts.register(FontResource::new(font_bytes)).unwrap();
let options = RenderOptions::builder()
.viewport(viewport)
.node(node)
.global(&global)
.fonts(&fonts)
.build();The raster feature is now raster-backend
The default raster backend feature was renamed to mirror svg-backend, and rayon no longer turns it on implicitly. Enable raster-backend (or keep the default features) to render rasters with rayon parallelism.
takumi = { version = "*", default-features = false, features = ["raster", "rayon"] }
takumi = { version = "*", default-features = false, features = ["raster-backend", "rayon"] } Image output quality is modeled per format
ImageOutputFormat::Jpeg and WebP now carry a Quality, lossless WebP moved to its own ImageOutputFormat::WebPLossless variant, and write_image no longer takes a separate quality argument.
write_image(&image, ImageOutputFormat::WebP, 80)?;
write_image(&image, ImageOutputFormat::WebP(Quality::new(80)))?; In the napi and wasm bindings, request lossless WebP with the lossless flag.
ImageSource::render_for_layout drops the current_color argument
With SVG currentColor no longer resolved against the host, render_for_layout no longer needs the color.
image.render_for_layout(width, height, image_rendering, time_ms, current_color)?;
image.render_for_layout(width, height, image_rendering, time_ms)?; More in Changelog!
For a more comprehensive list of changes, please refer to the changelog in releases.
Last updated on