PreText: The Library That Makes Text Layout 1000x Faster
Cheng Lou's new library eliminates DOM reflow entirely by using canvas-based text measurement. The result: 19ms instead of 19,000ms for 500 text measurements.
The Problem: Layout Reflow Kills Performance
Here's a pattern every web developer knows:
- You need to know how tall a paragraph will be
- You call
element.getBoundingClientRect()or checkoffsetHeight - The browser recalculates the entire layout tree
- Your animation stutters. Your virtual list breaks. Your users notice.
This is layout reflow — one of the most expensive operations browsers perform. When you measure something in the DOM, the browser has to reconcile styles, recalculate layouts, and paint. Do this 500 times in a loop and you've triggered 500 reflows.
The Solution: Canvas-Based Measurement
PreText sidesteps the DOM entirely. Instead of asking the browser to layout text and then measure it, PreText:
- Uses the Canvas API's
measureText()to get character widths directly from the font engine - Segments text by grapheme clusters (handles emojis, CJK, Arabic correctly)
- Applies word-wrapping rules in pure JavaScript
- Returns line counts and heights through pure arithmetic
The browser's font engine becomes the ground truth. No DOM elements. No reflow. Just measurements.
The Numbers Are Shocking
| Method | Time (500 texts) | Speedup |
|---|---|---|
| DOM Layout (getBoundingClientRect) | ~19,000ms | 1× (baseline) |
| PreText prepare() | ~19ms | 1000× |
| PreText layout() | ~0.09ms | 211,000× |
PreText splits the work into two phases:
- prepare() — One-time pass that segments text, normalizes whitespace, and measures segments. ~19ms for 500 texts.
- layout() — Pure arithmetic hot path. ~0.09ms for the same batch.
The prepare() call is your upfront cost. After that, resizing, recalculating, and experimenting with different widths becomes virtually free.
It Actually Works Everywhere
Text layout is surprisingly hard. PreText handles:
- CJK scripts — Chinese, Japanese, Korean with no spaces between words
- BiDi text — Mixed left-to-right and right-to-left (Arabic + English)
- Emoji — Complex multi-codepoint emoji as single grapheme clusters
- Browser quirks — Safari vs Chrome differences, system font edge cases
PreText's test suite includes strings like "AGI 春天到了. بدأت الرحلة 🚀" — mixing English, Chinese, Arabic, and emoji — and measures them correctly across all browsers.
Real Use Cases
1. List Virtualization Without Guessing
Virtual lists need to know item heights before rendering. Most implementations either:
- Guess (and get it wrong, causing jank)
- Measure after render (causing double work)
- Fix heights (inflexible)
PreText lets you calculate exact heights from text content alone. No DOM required. Your virtual list knows the right scroll position immediately.
2. Canvas, SVG, and WebGL Rendering
When you're rendering to non-DOM targets (game UIs, data visualizations, generative art), you need to know where line breaks go. PreText's layoutWithLines() returns:
{
lines: [
{ text: "First line", width: 145.5 },
{ text: "Second line", width: 156.2 }
],
height: 56,
lineCount: 2
}
Render to canvas with ctx.fillText(). Position SVG <text> elements. WebGL texture atlases. All from the same layout logic.
3. AI-Friendly Development
Cheng Lou explicitly mentions this: "development time verification (especially now with AI) that labels on e.g. buttons don't overflow to the next line, browser-free."
AI coding assistants can generate UI code and use PreText to verify layout constraints without running a browser. Static analysis of text fits becomes possible.
4. Preventing Layout Shift
When new text loads (chat messages, async content, translations), you want to:
- Calculate the new height with PreText
- Adjust scroll position before inserting the DOM
- Render the content
No jump. No shift. Smooth experience.
5. Text Around Floating Elements
layoutNextLine() lets you change width as you go. Flow text around an image:
// Lines beside the image are narrower
while (true) {
const width = y < image.bottom ? columnWidth - image.width : columnWidth
const line = layoutNextLine(prepared, cursor, width)
if (line === null) break
ctx.fillText(line.text, 0, y)
cursor = line.end
y += 26
}
The API Is Minimal
PreText exposes exactly what you need and nothing more:
import { prepare, layout } from '@chenglou/pretext'
// One-time preparation
const prepared = prepare('Your text here', '16px Inter')
// Instant layout calculation
const { height, lineCount } = layout(prepared, 320, 24)
// maxWidth, lineHeight
For manual layout control, swap in prepareWithSegments and get:
layoutWithLines()— get individual line strings and widthswalkLineRanges()— iterate lines without building strings (for width calculations)layoutNextLine()— line-by-line iterator with varying widths
Tradeoffs and Limitations
PreText is explicitly not a full font rendering engine. It targets:
white-space: normalorpre-wrapword-break: normaloverflow-wrap: break-wordline-break: auto
If you need complex text shaping (Harfbuzz-level features), PreText isn't there yet. For 95% of web text layout, it's more than sufficient.
One gotcha: avoid system-ui font on macOS — it has measurement inconsistencies. Use a named font stack instead.
Why This Matters Now
Three trends make PreText increasingly relevant:
- AI-generated UIs — More dynamic text, more need for programmatic layout calculation
- Performance expectations — 120Hz displays, instant interactions, zero tolerance for jank
- Non-DOM rendering — Canvas-based tools (Figma, Excalidraw), WebGL games, generative art
The DOM was never designed for high-frequency text measurement. PreText fills that gap without requiring a headless browser or complex caching strategies.
Getting Started
npm install @chenglou/pretext
Then check out:
- GitHub repo — Source and benchmarks
- Our demo — Interactive playground
- Official demos — From Cheng Lou
The Bigger Picture
PreText is part of a shift toward "AI-friendly" libraries — tools that expose deterministic, programmatic APIs rather than DOM-dependent side effects. When AI writes code, it prefers functions that return values over operations that mutate global state.
Text measurement has been a DOM operation for 30 years. PreText proves it doesn't have to be.
About the author: Andy Stable builds prototypes at PromptEngines Lab. Follow Lab Notes for more experiments in frontend engineering, AI tooling, and system design.