Pretext measures and lays out text by separating the process into two phases. First, it uses Canvas to measure text dimensions (prepare). Then, it computes the final positions using pure arithmetic (layout). Since the layout phase doesn't interact with the DOM, it avoids reflows entirely and typically takes about 0.0002ms to run.
You can use it to predict heights for virtualized lists, shrinkwrap chat bubbles, or route text around obstacles before rendering anything to the screen.
npm install @chenglou/pretextIf you only need to know the height or line count (e.g., for virtualized lists), use the basic prepare and layout functions.
import { prepare, layout } from '@chenglou/pretext';
// Measure once after fonts are ready
await document.fonts.ready;
const prepared = prepare("Hello World, Pretext is fast!", "16px Inter");
// Calculate layout on resize (no DOM interaction)
const { height, lineCount } = layout(prepared, 300, 24);
// width: 300px, lineHeight: 24px
console.log(`Height: ${height}px, Lines: ${lineCount}`);Depending on your target output, use different API paths:
| Scenario | API Path |
|---|---|
| Virtual scroll or height prediction | prepare() + layout() |
| Canvas or SVG rendering | prepareWithSegments() + layoutWithLines() |
| Finding the tightest width | prepareWithSegments() + walkLineRanges() |
| Flowing text across columns | prepareWithSegments() + layoutNextLine() |
You can flow text across multiple regions using cursor handoff.
import { prepareWithSegments, layoutNextLine } from '@chenglou/pretext';
const prepared = prepareWithSegments(longText, "18px Georgia");
let cursor = { segmentIndex: 0, graphemeIndex: 0 };
// First column
const column1 = layoutNextLine(prepared, cursor, 300);
cursor = column1.end;
// Second column
const column2 = layoutNextLine(prepared, cursor, 300);Pretext relies on a few constraints to work correctly:
prepare()must run afterdocument.fonts.ready. If fonts aren't loaded, the segment metrics will be inaccurate and the resulting heights will be wrong.- The
layout()function is strictly stateless. Do not read the DOM or call Canvas APIs inside your resize handler before callinglayout(). - Pass
lineHeightas a raw number (like24), not a CSS string. - Avoid using
system-ui. Provide explicit font names so sizes are consistent.
bun start # Dev server
bun run check # Typecheck & lint
bun test # Invariant tests
bun run accuracy-check # Browser accuracy sweepMIT © Chenglou