Drawing
Place nodes with their transform, then replay the steps.
A renderer reads two things from the tree: where each node sits, and the draw order.
Place a node
Each node has a local space from (0, 0) to (width, height). Its drawables use that space. node.transform maps the space onto the canvas.
Pick a rotation below. The drawables stay the same. Only the transform changes.
- width
- 160
- height
- 72
- transform
- [0.87, 0.5, -0.5, 0.87, 76.72, 12.82]
- bounds
- 40.72, 12.82, 174.56 × 142.35
node.bounds is the node's rectangle on the canvas. It grows when the node rotates.
Canvas takes the matrix in the same order: ctx.setTransform(...node.transform).
| Node | Local space |
|---|---|
box | Its border box |
text | The border box of the block its lines fill |
image | Its content box |
Replay the steps
tree.steps lists every step, bottom to top. The demo draws the card from Paint JSX one step at a time.
The order already handles z-index and negative stacking. Outlines that paint after later siblings are in place too.
| Step | Do |
|---|---|
draw | Draw drawables under node.transform |
begin-group / end-group | Draw the steps between them into a layer, then blend |
begin-clip / end-clip | Clip the steps between them to clip, in node space |
This loop replays the steps onto a canvas:
import type { , PaintTree } from "takumi-paint";
declare function (: , : ): void;
declare function (): ;
function (: , : PaintTree) {
const : [] = [];
let = ;
for (const of .) {
switch (.) {
case "draw":
.(.....);
for (const of .) (, );
break;
case "begin-clip":
.();
.(.....);
// Clip to step.clip.
break;
case "end-clip":
.();
break;
case "begin-group":
.();
= ();
break;
case "end-group": {
const = .() ?? ;
// Filter, clip, and mask the layer per step.node.effects, then blend it into parent.
.();
. = ..?. ?? 1;
.(., 0, 0);
. = 1;
= ;
break;
}
}
}
}A group needs its own layer. Setting globalAlpha per draw darkens the spots where draws overlap.
Draw a drawable
| Type | Draw |
|---|---|
fill | Fill shape with paint, clipped to every shape in clips |
stroke | Stroke shape with stroke and paint |
shadow | Blur a copy of shape, filled or stroked with stroke, moved by offset, visible only outside or inside its box |
glyphs | Fill the run's outline with paint, blurred by blur, or stroke it when stroke is set |
image | Draw image into rect, clipped to clip |
masked | Draw content into a layer, keep it only where mask covers, then composite it |
group | Draw drawables into a layer, then composite it at opacity |
A shadow or glyphs blur is a Gaussian standard deviation. It is half the CSS blur radius.
Every drawable has a role, such as background, border, or text. Exporters use it to rebuild editable objects. For example, a slide writer can merge a box's background, border, and shadow into one shape.
Read paints
Gradient stops interpolate in sRGB. Takumi adds stops where the CSS color space would differ. Oklab is the CSS default.
A repeating gradient has spread: "repeat". Its stops cover one period, like SVG's spreadMethod. Canvas gradients only pad, so repeat the stops yourself.
Background and mask layers arrive as pattern paints. A pattern draws one tile at every x and y, clipped to area.
Apply effects
A box with effects composites as a group. Apply them in this order:
- Render the group's steps into a layer.
- Run
filtersover it. - Clip it to
clip, and multiply it by the alpha ofmask. - Blend it with
opacityandblendMode.
backdropFilters apply to what is already drawn behind the node. They run inside backdropClip, before the node draws.
Last updated on