Text
Draw shaped runs or collect values for a text exporter.
A paragraph's text is one TextNode, and node.runs holds its runs in visual order. A run is text shaped with one font and size, so a <b> starts a new run.
Read a run
Hover a run to see its fields. The dot marks run.x and run.y, where the baseline starts.
- text
- "Text arrives as "
- element
- none
- line
- 0
- x, y
- 24, 46
- width
- 135.44
- ascent
- 20.1
- descent
- 5.9
- fontSize
- 20
- lineHeight
- 26
- font
- Geist 400
import { } from "takumi-paint";
export const = await (
< ={{ : 260, : 24, : 0, : 20, : "30px" }}>
Text arrives as <>shaped runs</> with their baselines.
</>,
{ : 308 },
);
| Field | Value |
|---|---|
x, y | Baseline start, in the text node's local space |
width | Advance |
ascent, descent | Typographic distances from the baseline |
line | Index of the line, from 0 |
element | The inline element the run came from, such as <b> |
In tree.nodes, the text node comes before the inline boxes in its paragraph. Its runs can sit on both sides of those boxes.
run.transform is set when text-fit scales the line. Apply it before placing the run at x, y.
Draw glyphs
run.outlines() returns each glyph's outline as SVG path data, in the order of run.glyphs. The paths are relative to the baseline start, and run.outlines().join("") draws the whole run. Drawing them needs no font engine.
An outline is an empty string for a glyph with no contours, such as a space. It is null for a glyph the run's color does not fill. Color font layers arrive as their own drawables, and bitmap glyphs, such as some emoji, are left out.
| Drawable | Draw |
|---|---|
glyphs | Fill the outline with paint, blurred by blur |
glyphs with stroke | Stroke the outline, as -webkit-text-stroke does |
masked under background-clip: text | Draw the background layers, masked by the glyphs |
A native text API reads run.glyphs instead. Each glyph has an id in run.font, its x and y offset from the baseline start, and usually a cluster, which is where its cluster starts in run.text, in UTF-16 code units. run.font.data() returns the font file. Runs in the same face share one Font object.
Lay text out again
A slide writer can rebuild a text box from the paragraph box and the run fields.
import { } from "takumi-paint";
const = await (
< ={{ : 240, : 12, : "center", : "30px" }}>Title</>,
{ : 320 },
);
const = ..(() =>
. === "text"
? [
{
: .?.,
: .,
: .,
: ..(() => ({
: .,
: .,
: .,
: .,
: .,
: [., .],
})),
},
]
: [],
);
.();| Value | Field |
|---|---|
| Text box | node.parent.contentBox |
| Alignment | node.textAlign |
| Line | run.line |
| Text and face | run.text, run.font |
| Font size | run.fontSize |
| Line height | run.lineHeight |
| Letter spacing | run.letterSpacing |
| Baseline | run.x, run.y |
run.letterSpacing is already applied to the glyph positions, so do not add it again when you place glyphs. Under line-height: normal, a fallback face can make run.lineHeight taller.
Last updated on