Reusable handle
One analyze, many fits. prepare() is the expensive step — it segments, measures, caches. layout() is cheap arithmetic. Once you hold a handle, you can ask for any width and get the exact height, as many times as you like, without repeating the measurement.
Below, four panes show the same paragraph at mobile, tablet, desktop, and wide breakpoints. One prepare() call at boot produced the handle. Four layout() calls filled the panes. Nudge the font size — all four update in lock-step.
Mechanism
A Pretext handle is an opaque cache of "everything the text and font say that doesn't depend on width." Segments. Glue rules. Canvas-measured widths. Bidi levels where relevant. None of that changes when you ask about a new width, so Pretext doesn't recompute it.
layout(handle, width, lineHeight) walks those cached segments and applies word-wrap rules in arithmetic. On the library's benchmark snapshot, prepare() averages about 19ms for a 500-text batch; layout() for that same batch averages about 0.09ms. The ratio is roughly 200:1. A handle pays for itself on the second call and compounds from there.
Practical consequence: the right unit of caching is the handle, not the rendered height. Store handles keyed by (text, font); never cache by width. Widths are cheap to answer about.
Application
"Cached across breakpoints" stops being aspirational and becomes the default:
- One content piece, every viewport — one handle, a
layout()call per column width on the page. - Responsive masonry — each tile prepares once on ingest; placement on resize is pure arithmetic over all tiles.
- A/B width tests — pick the width that yields the "nicest" line count without re-measuring the text.
- A print-preview panel that shows the same article at three widths simultaneously — three
layout()calls on one handle.
The heuristic: if two regions display the same text in the same font, they share one handle. The only thing that differs between them is the argument to layout().
"Do I contradict myself? Very well then I contradict myself, (I am large, I contain multitudes.)"
Walt Whitman, Song of Myself, from Leaves of Grass (1855)Direct Claude
prepare(), one layout() per column width you need
"cache across breakpoints"
→
keep the handle keyed by (text, font); never key by width
"three widths, side by side"
→
three layout() calls on the same handle — no extra measurement
"pick the nicest wrap"
→
sweep widths via layout(); take the lineCount you like
import { prepare, layout } from '@chenglou/pretext';
const FONT = "400 16px 'EB Garamond', serif";
const LINE_HEIGHT = 16 * 1.5;
const BREAKPOINTS = [320, 480, 720, 1040];
// Prepare once. Analyze cost paid.
const handle = prepare(TEXT, FONT);
// Lay out everywhere. Pure arithmetic per call.
for (const width of BREAKPOINTS) {
const { height, lineCount } = layout(handle, width, LINE_HEIGHT);
// ...place each pane using its own height.
}