[00] DOMGL // Text.render

DOM on the left, GPU on the right. Drag the seam.

Every glyph,
solved per pixel.

The browser lays the text out and the GPU draws it from the font's own curves, at any size or angle, even while it animates. When nothing is moving, both sides of the line show the exact same letters.

[01] Layout

RANGE.READBACK

The browser still lays it out

Line breaking, kerning, tracking and wrapping are all still handled by the browser. Each character's position is read back from the page with a Range, and the markup stays as it is, so selection, find-in-page and screen readers keep working.

A character's Range rect is the font's content box, ascent plus descent tall regardless of line-height. Its top plus the ascent gives the baseline and its left edge gives the pen position, which is everything the GPU side needs.

Positions are stored relative to their element, so scrolling and moving elements is free. The page is only measured again when an element changes size or text, or when a font finishes loading and shifts every advance.

Text-transform happens at paint time. These lines are written in sentence case and set in capitals by CSS, so the measure maps each character to how the browser actually drew it before looking up its outline.

[02] Ink

QUAD.INSTANCED

Then the GPU draws it

There's no atlas, bitmap or signed distance field involved. A build step extracts every glyph's quadratic Bézier outline from the font into one flat buffer, around 270 KB per face. Each glyph on screen is an instanced quad, and the fragment shader calculates how much of each pixel the outline covers.

Since the shape is solved again every frame, a letter that's twisting or swapping stays as sharp as one at rest. Nothing gets stretched or re-rasterised when the type scales up.

Effects are written as two small functions. One places each glyph, setting its position, rotation and which character it shows. The other runs per pixel and shifts the point the outline is solved at, so a warp bends the actual letterform and the edge stays crisp.

The scrambling characters on this side are real outlines as well, swapped in from a small pool in the vertex stage. The CPU does nothing while they run and the page's glyph buffers aren't touched.

[--] Interlude

ALIGNMENT.CHECK

Pixel for pixel

[03] Coverage

COVERAGE.AREA

How much of each pixel is ink

Slug casts two rays from the pixel centre, one horizontal and one vertical, and averages how far each gets into the shape. Each ray only sees its own axis, so on a nearly flat edge, like the top of an o or a crossbar, one of them contributes almost nothing.

Exact area splits each curve into a few short segments and integrates the area they cover inside the pixel, in closed form. That gives a true box filter on both axes for about the same cost.

Each pixel collects curves from every band it overlaps, including bands its centre isn't in. Without that, a curve sitting just over a band edge gets skipped and a thin seam shows up through every round letter.

Add ?slug to the URL to use Slug's rays on the right side, and compare either one with the browser's own text on the left.

[04] Bands

BANDS.16

How it stays fast

This part comes straight from Slug. Every glyph is cut into horizontal bands, and each band lists only the curves that cross it, sorted by how far right they reach. A pixel checks a handful of curves instead of sixty and stops once the rest are behind it.

All the text on the page shares one draw call per font. Scrolling updates a few numbers per element and nothing per glyph, so the frame cost depends on how much text is on screen rather than how many elements it's split across.

Curve data
270 KB per face
Draw calls
1 per face
Uploads after load
0
Frame cost
~1.5 ms, a page of copy

[05] Material

HOVER A TILE

Effects on the letterforms themselves

[X1] X-ray

INK.EDGE

outline

The coverage drawn as an outline, showing the vectors underneath.

[X2] Warp

INK.EM

liquid

Bends the point the outline is solved at, so the letterform moves and the edge stays sharp.

[X3] Twist

INK.EM.Y / COS

ribbon

Each column of pixels squashed around the line, like a strip turning over.

[X4] Slice

INK.GLITCH

signal

Slices of the screen re-rolled eighteen times a second.

[X5] Blur

POSE.SOFT

focus

A wider footprint gives a real box blur in the same pass, without a second texture.

[X6] Lens

INK.EM.RADIAL

lens

A magnifier on the outline itself. It follows the cursor and never pixelates.

[06] Edit

CONTENTEDITABLE / LIVE

Type something here, or select a few words.

[07] Zoom / GL only

SCALE.UNBOUNDED

Keep scrolling. It is redrawn from the curves at every size, so it never goes soft.

glyph

[08] Volume

VOLUME.STRESS

What happens each frame

Scrolling moves the window inside the same animation frame the layer draws in, so the page and the canvas update together without waiting on the compositor.

Every element's rect is read once with getBoundingClientRect and written into its row along with its glyph count, alpha and a couple of values for effects. That's a few dozen floats per element, however long its text is.

Elements whose text, size or font changed since the last frame get measured again, with one Range per character and the baseline taken from the font's ascent. Elements that didn't change cost nothing.

Each font's glyphs are packed into one instance buffer at sixty bytes a glyph, holding pen position, size, ink box, band table, colour, element slot and reading order. It's only rewritten when a measurement changed something.

Each font is a single draw. The vertex stage looks up its element's row, asks the pose function where the glyph goes, builds a quad around the ink box with a little padding for antialiasing, and maps it straight to clip space without a camera or matrices.

The fragment stage passes the pixel to the ink function, takes derivatives of the point it gets back, and solves the outline there across every band and curve the pixel touches.

Glyphs in elements that are off screen collapse in the vertex stage, costing four vertices each and no fragments. You can put a book's worth of text on a page and the frame only pays for what's visible.

Outline data is shared by every element using the same font. A paragraph here and one forty screens down read the same curves from the same buffer, so more text means more instances and never more curves.

Colour comes from the DOM. Each element's computed colour is read when it's measured and stored on its glyphs, so a span can recolour a word and muted labels stay muted without any extra styling system.

You can still select text on both sides of the line because the text is still in the page. Find-in-page scrolls to it and screen readers read it as normal. The GPU only handles drawing it.

If a character isn't in the font's subset, its whole element falls back to normal DOM text instead of drawing with a gap, so at worst you get browser text in the right place.

The scramble characters come from a second, tiny table per font with about forty entries. Swapping a glyph just means using a different index in the vertex stage.

The glitch runs per pixel. The viewport is cut into slices an eighth of the type size tall, and each one re-rolls eighteen times a second. The ones that hit shift where the outline is solved, and the edge stays sharp because the footprint moves with it.

The cursor trail is a small fluid simulation running in compute, 256 cells across. Its dye goes to the text layer as a texture, and each glyph samples it at its centre to decide whether to scramble.

None of this column is an image. Zoom the page and it's solved again at the new size, and a rotated letter is still built from the same handful of curves at whatever angle it ends up.

Every effect here settles back to exactly where it started. The left side never moves, so if the right side drifted by even a pixel at rest you'd see it along the line.

[09] Skill

AGENT.SKILL / ZIP 34 KB

Get the skill

This whole page runs on one small reference implementation, packaged as an agent skill. It covers the build step that extracts outlines from a font, the DOM measuring, the coverage shaders, the layer, and the two hooks every effect here is written with. There's no package to install. Put it in your agent's skills folder and ask it for GPU text.

unzip dom-gpu-text-skill.zip -d ~/.claude/skills/