Skip to content

Text

<Text> draws type from quadratic Bezier outlines evaluated in the fragment shader. There is no glyph atlas and no texture. A font is a list of curves in a storage buffer, and each pixel decides whether it is inside a glyph by counting how many curves a horizontal ray crosses.

<Text position={[-2, -2, 0]} size="16px" color="#000">Hello world</Text>
<Text color="#fff">{['Master the ', { text: 'Flow', color: '#0f0' }]}</Text>

Text shares no code with the SDF engine — its own shader, its own pipeline. The engine, layout and shader load by dynamic import from <Text>, so a scene without text ships none of them.

loadFont fetches /fonts/{font}.glyphs, a compiled binary: a character map, per-glyph curve ranges, advances and bounds in em units, the curve table itself — three control points per quadratic — and kerning pairs. The curves go into one storage buffer per font, cached on the engine and shared by every <Text> using that font.

Fonts are subset at compile time to the characters a page uses. That is why the rig gizmo draws its digits as line segments instead of using <Text>: the shipped fonts carry no digits.

layoutText walks the string on the CPU: advance per glyph, kerning between consecutive pairs, \n for a line break, a fixed advance for a missing character. It emits one instance per renderable glyph — its curve range, its offset in em, its colour, and its quad bounds.

Every glyph is one instance of a shared unit quad. The vertex shader remaps the quad to that glyph’s own bounds plus a small antialiasing margin, so a period covers a handful of fragments rather than a full em square.

For each pixel the fragment shader walks the glyph’s curves. For each curve it solves the quadratic for where the curve crosses the pixel’s horizontal ray; every crossing to the right adds +1 or −1 by the curve’s vertical direction. A non-zero total means inside.

A per-curve range check skips any curve whose vertical span does not include the pixel’s row — most of them, for most rows — before the quadratic is solved.

aa is the sample count per pixel: 1 for hard edges, 4 (2×2, the default), 9, or 16. Above 4 the shader first samples the four pixel corners; if all agree, the pixel is interior or exterior and returns immediately. Only pixels straddling an edge run the full grid.

size is CSS-style and renders at exactly that many screen pixels regardless of camera, zoom or viewport: "16px", "1rem" and "1em" are 16 px, a bare number is px. It is resolved per frame from the live projection at the text’s Z, so it holds through camera moves. scale is world units and applies only when size is unset.

Text draws in pass 1, before the copy that glass reads, so it appears behind and through glass. It does not participate in the mesh and SDF depth sort.

children is a string, or an array of strings and { text, color } segments. A segment’s colour overrides the color prop for its run; plain strings inherit it. \n inside any segment starts a new line.