Pretext Lab
02The two-phase dance
Arc 1 · Mental Model

The two-phase dance

In the previous lesson the browser handled all three things every frame: re-wrap the text (analyze), compute line breaks at the new width (fit), and paint the result (render). Pretext pulls the first two out of the browser and into JavaScript, where you control when they happen.

Same demo, same sliders, same workload. No DOM measurement. Drag the width; crank the workload to 2000. The FPS stays at the top of the dial.

workload · 200 items
— fps

The letter rests on the line. The line rests on the page. The page rests on the table. Nothing, so far, is busy.

Attention is the rarest and purest form of generosity. The mind turns toward what it values and learns by turning — the movement of attention is itself the learning.

Between the word you just read and the next one lies a small country where reading is not happening. Travelers there come back changed.

400
200

Mechanism

At startup we call prepare() on every item's text with a fixed font descriptor. Pretext segments the text, probes each segment's width using the browser's own canvas font engine, and stores the result in an opaque handle. Handles are reusable across any width we'll ever ask about.

Inside the width handler, we iterate the workload and call layout(handle, width, lineHeight) on each. Pretext walks the cached segments and applies its word-wrap rules entirely in arithmetic — no DOM reads. Two thousand of those calls fit in well under a frame on any modern machine.

The word-wrap rules Pretext reproduces are the browser's: Unicode line-break classes, CJK keep-all, bidirectional reordering, soft hyphens, emoji grapheme clusters. The canvas measurements are the ground truth, so the line breaks Pretext returns match what the browser would have produced at that width.

Application

Every effect the previous lesson listed as blocked becomes trivial:

The shape of the unlock is the same every time: you stop asking the DOM for measurements, and do the measurement yourself, once, with a tool that already knows what the DOM would have said.

"Our life is frittered away by detail. … Simplify, simplify."

Henry David Thoreau, Walden (1854)

Direct Claude

"pre-measure the messages" call prepare() on each string up front, cache the handle "reflow without measuring" call layout(handle, width, lineHeight) per width; no DOM reads "heights known at scroll time" virtualize using Pretext's returned height, not a DOM measurement "make it breathe on resize" bind layout() to the resize observer; animate the computed height
The rest of Arc 1 was here. The rest of the course shows what the two-phase pattern unlocks — beginning with the simplest question: given text and width, how tall?
prepare once, layout many
import { prepare, layout } from '@chenglou/pretext';

// Phase 1: analyze — run once per string.
const handles = items.map(text =>
  prepare(text, "400 20px 'EB Garamond', serif")
);

// Phase 2: fit — run on every input event. Pure arithmetic.
widthSlider.addEventListener('input', (e) => {
  const width = parseFloat(e.target.value);
  for (let i = 0; i < workload; i++) {
    const h = handles[i % handles.length];
    const { height } = layout(h, width, 20 * 1.55);
    // height is ready for anything downstream — no DOM read.
  }
  textRegion.style.width = width + 'px';
});