Pretext

Fast, DOM-free multiline text measurement and layout for JavaScript and TypeScript

Library
npm
v0.0.8
49,933stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
52/100Fair
Development Activity52
Maintenance36
Community56
Maturity24
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
89/100Excellent
Architecture88
Code Quality90
Innovation92
Learning Curve95

Pretext is a pure JavaScript/TypeScript library that measures and lays out multiline text without ever touching the DOM. Instead of relying on layout-triggering calls like getBoundingClientRect or offsetHeight, it uses the browser’s own canvas font engine as ground truth, then does the actual line-wrapping and height math with pure arithmetic over cached widths.

It splits work into a one-time prepare() pass (segmentation, glue rules, canvas measurement, emoji correction) and a cheap repeatable layout() pass, so resizing or virtualizing large lists of text stays fast. Full Unicode support (via Intl.Segmenter) covers CJK, Thai, Arabic, and other complex scripts, and a lower-level API exposes line ranges and cursors for custom rendering to canvas, SVG, or WebGL.

What You Get

  • A two-phase prepare()/layout() API: prepare once per text+font+config, then run cheap pure-arithmetic layout on every resize
  • Accurate multiline height and line-count calculation without DOM reads, avoiding the reflow cost of getBoundingClientRect/offsetHeight
  • Full Unicode-aware segmentation via Intl.Segmenter, covering CJK per-character breaking, Thai, Arabic, and emoji grapheme clusters
  • Low-level manual-layout APIs (walkLineRanges, layoutNextLineRange, materializeLineRange) for custom rendering to canvas, SVG, or WebGL, including variable-width flow around floats
  • A dedicated rich-inline entry point for inline rich text spans (mentions, chips, code spans) with caller-owned chrome width and atomic break control
  • Options matching common CSS behaviors: white-space: pre-wrap, word-break: keep-all, letter-spacing, and soft-hyphen-driven hyphenation

Common Use Cases

  • Virtualizing long lists of text blocks (chat messages, feed items) without measuring each one against the live DOM
  • Preventing scroll-position jumps by pre-computing new text height before it renders
  • Development-time verification that button/label text won’t overflow to a second line, without a browser round-trip
  • Building custom canvas/SVG/WebGL text renderers that need line-by-line layout data instead of a browser’s native text flow
  • Implementing masonry or flexbox-like custom layouts where a paragraph’s shrink-wrapped width or height is a required input

Under The Hood

Architecture: Pretext is organized as a small set of focused modules stitched together in layout.ts. analysis.ts normalizes and segments raw text into typed pieces (word, space, tab, soft-hyphen, hard-break, etc.) and applies CJK/kinsoku/punctuation glue rules. measurement.ts owns the canvas measurement context (OffscreenCanvas when available, falling back to a DOM canvas), per-font metric caches, and browser-specific correction logic (emoji width inflation, Safari’s prefix-fit measurement quirks capped at 96 graphemes to avoid superlinear blowups). line-break.ts walks the prepared segments purely arithmetically to produce line ranges, and line-text.ts materializes a range back into an actual substring only when the caller needs the text itself. bidi.ts, forked from pdf.js’s bidi implementation, computes embedding levels for the richer prepareWithSegments() path via a generated Latin-1/non-Latin-1 range table, but the core line-breaking engine never reads bidi levels itself. A separate rich-inline.ts module implements a narrower, inline-only rich-text flow (fragments with fonts, atomic chips, caller-owned extra width) as its own public entry point (@chenglou/pretext/rich-inline), keeping it decoupled from the main paragraph API.

Tech Stack: Written entirely in TypeScript (91% of the codebase) targeting ES modules, with zero runtime dependencies — the only real inputs are the browser’s Canvas 2D API and Intl.Segmenter. The package ships as type: module with typed dual exports for the main entry and the rich-inline sub-path. Development tooling runs on Bun (install, test runner, dev server for the demo pages) with a strict tsc build (tsconfig.build.json) producing the published dist/ output, plus oxlint (with oxlint-tsgolint for type-aware rules) and knip for dead-code/unused-export detection.

Code Quality: The core src/ sits around 6,600 lines split across ~8 focused files, backed by a 1,687-line layout.test.ts covering the public and internal APIs, alongside separate accuracy/, corpora/, and benchmarks/ directories that check real-browser pixel-accuracy (Chrome/Firefox/Safari) and measure performance regressions — a testing posture well beyond typical npm libraries of this size. Internal invariants are documented inline (e.g. the 96-grapheme cap rationale in measurement.ts), and lint suppressions are scoped and explained rather than blanket-disabled. No obvious gaps in error handling were found for the documented public surface; unsupported environments (no OffscreenCanvas/DOM) throw an explicit, descriptive error rather than failing silently.

API Design: The public API is deliberately two-tiered: a minimal prepare()/layout() pair covers the common “give me a height” case with almost no boilerplate, while prepareWithSegments() plus layoutWithLines()/walkLineRanges()/layoutNextLineRange() expose progressively lower-level control for custom renderers without requiring callers to learn the internals up front. Naming is consistent (prepare*/layout*/materialize* prefixes signal cost and side effects), option objects mirror familiar CSS properties (whiteSpace, wordBreak, letterSpacing) to minimize new concepts, and the README includes a full inline API glossary with types and inline usage examples for every exported function.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search