Pretext

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

Library
npm
v0.0.9
50,621 stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum. How we score it →
69 /100 Good
Development Activity 100
Maintenance 52
Community 56
Maturity 28
Momentum 40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation. How we score it →
89 /100 Excellent
Architecture 88
Code Quality 90
Innovation 92
Learning Curve 95

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.

Used by 4 apps in this directory

TypeScript
54%
MIT

Anarlog

AI Assistants · Note Taking · Productivity

9,404

Anarlog is an open-source, local-first AI meeting notetaker that records, transcribes, and summarizes meetings entirely on your device — no cloud lock-in, no mandatory account, and every note saved as a plain markdown file you own forever.

View details
86
Repo Health
77
Technical
68
Dependency
Built with
TypeScript 54%
Rust 34%
Updated 4 days ago
TypeScript
87%
Apache 2.0

HyperFrames

AI Design Tools · AI Development

53,695

Turn plain HTML and CSS into deterministic, pixel-perfect MP4 videos — authored by humans or AI agents, rendered by headless Chrome and FFmpeg.

View details
84
Repo Health
83
Technical
65
Dependency
Built with
TypeScript 87%
JavaScript 12%
Updated 4 days ago
TypeScript
98%
MIT

Kimi Code CLI

AI Agents · AI Code Assistants · Developer Tools

7,705

A single-binary, terminal-native coding agent that reads, edits, and runs code end to end, built by Moonshot AI for Kimi models but pluggable with Anthropic, OpenAI, and Google providers too.

View details
82
Repo Health
87
Technical
67
Dependency
Built with
TypeScript 98%
Updated 4 days ago
TypeScript
90%
MIT

open-pencil

AI Design Tools · Design Tools

8,642

An open-source design editor that reads native Figma files, ships a built-in AI assistant with 100+ design tools, and offers real-time serverless collaboration — all without giving up your files.

View details
81
Repo Health
78
Technical
69
Dependency
Built with
TypeScript 90%
Updated 6 days ago

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