Takumi

The paint tree

Read what the renderer paints with takumi-paint, for PPTX, Canvas, and native drawing exporters.

takumi-paint lays out a document and returns what painting it would draw, instead of an image. Use it when the output is not pixels: an editable PPTX, a Canvas replay, a native drawing API.

Draw the tree on a canvas
import { ,  } from "takumi-paint";

const  = await (
  < ={{ : 640, : 32, : "#F7F3EC", : "column", : 8 }}>
    < ={{ : 40, : 700, : "#B3261E" }}>Hello paint</>
    <>Every box and text run, with the values the renderer used.</>
  </>,
  { : 640 },
);

function (: ) {
  for (const  of ()) {
    if (.) .(....);
    else .(1, 0, 0, 1, ., .);
    if (.?.) {
      . = (..);
      .(0, 0, ., .);
    }
    for (const  of . ?? []) {
      .();
      if (.) .(....);
      . = `${..} ${.}px ${..}`;
      . = (.);
      .(., ., .);
      .();
    }
  }
  .();
}

const  = ([, , , ]: number[]) => `rgb(${} ${} ${} / ${ / 255})`;

The package runs the same layout as takumi-js and walks the same stacking-context scene the image, SVG, and PDF backends paint. It records used values: a 50% width is a number, currentColor is a color, and <b>world</b> is its own run with its own face.

Coordinates

Every length is a device pixel, and so are the width and height options. A 20px font measures 40 under a devicePixelRatio of 2.

A node's x and y place its border box on the canvas. transform appears only when the box is rotated, scaled, or skewed, and its translation equals x, y. Everything inside a node, such as clip, background, image, and textRuns, is relative to the node's border-box top-left.

Paint order

Nodes come in the order the renderer paints them, so zIndex does not appear. walk() yields them in that order. Within a node, draw shadows.outer, background, shadows.inset, and border, then image, inlineBackgrounds, and textRuns, then children, then outline.

Each node is a compositing group. Its opacity, clip, and blendMode apply to everything inside it, children included.

Fields

Prop

Type

Description

source?PaintSource

The input node this box came from: its child-index path, id, tag name, and class name

background?PaintBackground

Used background color, clip, and image layers

border?PaintBorder

Border widths, colors, styles, and radii

shadows?PaintBoxShadows

Box shadows split into inset and outer

outline?PaintOutline

The outline, painted after the children

image?PaintImage

The content box an image sits in and where it draws after object-fit and object-position

textRuns?PaintTextRun[]

Shaped text runs with their font, size, color, decorations, and glyph positions

inlineBackgrounds?PaintInlineBackground[]

One rounded rectangle per line for each inline span with a background

unresolvedEffects?PaintUnresolvedEffects

filter, backdrop-filter, mask-image, and clip-path as CSS text

Each run carries its font: the registered family, the face index, the weight and style after variations, and any synthetic bold or skew the shaper applied. textRuns() yields every run with the node that paints it, and find() looks a node up by its source id.

What stays CSS

Linear and radial gradients come resolved: stops in pixels along the axis, plus the tile geometry. A conic gradient is carried as CSS text. filter, backdrop-filter, mask-image, and clip-path are carried as CSS text under unresolvedEffects, so an exporter can decide what to do with them.

Last updated on

On this page