[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. The GPU draws it from the font's own curves, at whatever size, angle or state it happens to be in. At rest the two halves are the same letters, split down the middle.

[01] Layout

RANGE.READBACK

The browser does the layout.

Line breaking, kerning, tracking, a word that wraps when the window narrows: none of it is reimplemented. Each character's position is read back from the page with a Range, which leaves the markup untouched, so selection, find-in-page and screen readers carry on as if nothing happened.

A text Range's rect is the font's content box, exactly ascent plus descent tall, whatever the line-height. The baseline is its top plus the ascent, and the pen sits at its left edge. That is the whole handshake between the two halves.

Positions are stored relative to their element, so scrolling and moving cost nothing. The page is measured again only when something can actually move a glyph: the element changes size, its text changes, or a font finishes loading and every advance shifts with it.

Text-transform is paint, not content. These lines are authored in sentence case and set in capitals by CSS, so the measure maps each character the way the browser drew it before it looks up an outline.

[02] Ink

QUAD.INSTANCED

The GPU does the ink.

No atlas, no bitmaps, no signed distance fields. A build step pulls every glyph's quadratic Bezier outline out of the font into one flat buffer, about 270 KB a face. Each glyph on screen is one instanced quad, and the fragment shader works out how much of each pixel the outline covers.

Because the shape is solved fresh every frame, a letter that is twisting, swapping or bending is exactly as sharp as one standing still. There is no texture to stretch and nothing to re-rasterise when the type grows.

Effects are two small functions. One poses each glyph: where it sits, how it turns, which character it shows. The other runs per pixel and moves the point the outline is solved at, so a warp bends the letterform itself and its edge stays exact.

The characters decoding on this side are real outlines too, swapped in from a pool in the vertex stage. Nothing on the CPU changes while they run, and the buffers holding the page's glyphs are never rewritten.

[--] Interlude

ALIGNMENT.CHECK

Same pixel. Both sides.

[03] Coverage

COVERAGE.AREA

Exact area, not rays.

Slug casts two rays from the pixel centre, one across and one down, and averages how far each gets inside the shape. Each ray only sees its own axis, so on a nearly flat edge (the top of an o, a crossbar) one of them has almost nothing to say.

Exact area flattens each curve into a few short segments and integrates the area they enclose inside the pixel, in closed form. It is a true box filter on both axes, at the same cost.

A pixel is assembled from every band it touches, not only the one its centre falls in. Otherwise a curve lying just across a band boundary is never visited, and a one-pixel seam runs straight through every round letter.

Add ?slug to the address to swap Slug's rays in on the right, and compare either against the browser's own rendering on the left.

[04] Bands

BANDS.16

Bands make it cheap.

The one idea taken whole from Slug: cut every glyph into horizontal bands and list only the curves that cross each, sorted by how far right they reach. A pixel visits a handful of curves instead of sixty, and stops at the first one entirely behind it.

Every element on the page shares one draw call per face. Scrolling rewrites a few numbers per element and nothing per glyph, and the cost of a frame follows the area of text on screen, not how many elements hold it.

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, OR WATCH IT BREATHE

Type as material, not a picture of it.

[X1] X-ray

INK.EDGE

outline

The coverage turned into its own rim: the vectors under the skin.

[X2] Warp

INK.EM

liquid

The point the outline is solved at, bent. The letterform moves, the edge stays exact.

[X3] Twist

INK.EM.Y / COS

ribbon

Each column of pixels squashed about the line: one strip, turning.

[X4] Slice

INK.GLITCH

signal

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

[X5] Blur

POSE.SOFT

focus

A wider footprint is a true box blur, in the same pass. No second texture.

[X6] Lens

INK.EM.RADIAL

lens

A magnifier on the outline itself. Follows the cursor; never pixelates.

[06] Edit

CONTENTEDITABLE / LIVE

Type here. The browser lays it out, the GPU draws it from the curves. Select some of it.

[07] Zoom / GL only

SCALE.UNBOUNDED

Scroll. Solved per pixel at every size, so there is no size it was made for.

glyph

[08] Volume

VOLUME.STRESS

A page of it, per frame.

Frame, step one. The scroll moves the window inside the same animation frame the layer draws in, so the page and the canvas leave the main thread together. Nothing here waits on the compositor.

Step two. Every element's rect is read once with getBoundingClientRect and written into its row: left, top, width, height, glyph count, alpha, and two vectors of the page's own numbers. A few dozen floats per element, whatever the length of its text.

Step three. Elements whose text, size or font changed since the last frame are measured again. One Range per character, one getClientRects per Range, the baseline found from the face's ascent. Untouched elements cost nothing.

Step four. Each face's glyphs are packed into one instance buffer, sixty bytes a glyph: pen origin, size, ink box, band table, colour, element slot, reading order. The buffer is rewritten only when a measure changed something.

Step five. One draw per face. The vertex stage finds its element's row, asks the pose function where the glyph should be, builds a quad around the ink box padded for antialiasing, and maps element pixels straight to clip space. No camera, no matrices.

Step six. The fragment stage hands the pixel to the ink function, takes the derivatives of whatever point comes back, and solves the outline there: every band the pixel touches, every curve in each band, closed-form area, signed by direction.

An element off screen collapses its quads in the vertex stage and costs four vertices a glyph and no fragments. A page can hold a book's worth of copy; the frame pays for what is visible.

The outline data is shared by every element in a face. A paragraph here and a paragraph forty screens down read the same curves from the same buffer; adding text adds instances, never curves.

Colour comes from the DOM. Each parent element's computed colour is read once per measure and carried on every glyph, so a span can recolour a word and a muted label stays muted, without a style system of its own.

Selection still works on the left of the seam and on the right, because the text is still there. Find-in-page scrolls to it. A screen reader reads it. The GPU only took over the paint.

A glyph outside the face's subset sends its whole element back to the DOM rather than drawing with a hole. The worst failure of the layer is browser text, in the right place.

The pool these decoding characters come from is a second, tiny table per face: forty-odd entries of ink box and band table. Swapping a glyph is a different index in the vertex stage, nothing else.

The glitch is per pixel. Slices of the viewport, an eighth of the type's size tall, roll a number eighteen times a second; the unlucky ones move where the outline is solved, and the edge stays exact because the footprint follows.

The pointer's ink is a small fluid simulation in compute, 256 cells across. Its dye is handed to the text layer as a texture, and each glyph reads it at its own centre to decide whether to scramble.

Nothing in this column is an image. Zoom the page and it re-solves at the new size; turn a letter over and it is still the letter, from the same eleven-odd curves, at whatever angle it lands.

All of it settles. Every effect here is written to return exactly to the identity, because the left half never moves, and a resting page that drifted by a pixel would show it, right down the seam.

[09] Skill

AGENT.SKILL / ZIP 32 KB

Take it with you.

Everything on this page runs on one small reference implementation, packaged as an agent skill: the build step that pulls outlines from a font, the DOM measure, the coverage shaders, the layer, and the pose and ink hooks every effect here is written in. No package to install. Drop it in your agent's skills folder and ask it for GPU text.

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

[ Download the skill ]