LabNotes

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.

Try it live: We built an interactive demo showing PreText in action. → Launch PreText Demo

The Problem: Layout Reflow Kills Performance

Here's a pattern every web developer knows:

  1. You need to know how tall a paragraph will be
  2. You call element.getBoundingClientRect() or check offsetHeight
  3. The browser recalculates the entire layout tree
  4. 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:

  1. Uses the Canvas API's measureText() to get character widths directly from the font engine
  2. Segments text by grapheme clusters (handles emojis, CJK, Arabic correctly)
  3. Applies word-wrapping rules in pure JavaScript
  4. 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:

  1. Calculate the new height with PreText
  2. Adjust scroll position before inserting the DOM
  3. 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 widths
  • walkLineRanges() — 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: normal or pre-wrap
  • word-break: normal
  • overflow-wrap: break-word
  • line-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:

  1. AI-generated UIs — More dynamic text, more need for programmatic layout calculation
  2. Performance expectations — 120Hz displays, instant interactions, zero tolerance for jank
  3. 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:

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.