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.
The font file
Section titled “The font file”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.
Layout
Section titled “Layout”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.
One instanced draw per <Text>
Section titled “One instanced draw per <Text>”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.
Coverage by winding
Section titled “Coverage by winding”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.
Antialiasing
Section titled “Antialiasing”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.
It draws in the background pass
Section titled “It draws in the background pass”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.
Segments
Section titled “Segments”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.