chenglou / pretext
git clone https://github.com/chenglou/pretext.git
v1.0.0 · 8d ago
Fast, accurate & comprehensive text measurement & layout
+8
stars, 24h · #4,344
+~58
stars, 7 days · #3,554
+~250
stars, 30 days · #3,240
51k
★ total · +0% this week
2.8k
forks · 65 open issues
Star history (as recorded here)
LanguageTypeScript
License—
Created2026-03-07 (7mo ago)
Last push7d ago
Watchers140
OwnerUser
Links
- GitHub: github.com/chenglou/pretext
- Homepage: http://chenglou.me/pretext/
- Releases · Issues · Hacker News mentions
README excerpt
# Pretext
Pure JavaScript/TypeScript library for multiline text measurement & layout. Fast, accurate & supports all the languages you didn't even know about. Allows rendering to DOM, Canvas and SVG.
Pretext side-steps the need for DOM measurements (e.g. `getBoundingClientRect`, `offsetHeight`), which trigger layout reflow, one of the most expensive operations in the browser. It implements its own text measurement logic, using the browsers' own font engine as ground truth (very AI-friendly iteration method).
## Installation
[code]
## Demos
The demos are exemplary API usage patterns we encourage you to read. They don't ship in the npm package, so clone the repo, run `bun install`, then `bun start`, and open in your browser. On Windows, use `bun run start:windows`.
Alternatively, see them live at chenglou.me/pretext. Some more at somnai-dreams.github.io/pretext-demos
Building a chat or another long list? pages/demos/markdown-chat.md walks through the Markdown chat demo's patterns and when you can skip each.
## API
Pretext serves 2 use cases:
### 1. Measure a paragraph's height _without ever touching DOM_
[code]
`prepare()` does the one-time work: normalize whitespace, segment the text at its break opportunities, measure the segments with canvas, and return an opaque handle. `layout()` is the cheap hot path after that: pure arithmetic over cached widths. Do not rerun `prepare()` for the same text, font and options; that'd defeat its precomputation. For example, on resize, only rerun `layout()`.
To paint the text, give one element that `font`, the `line-height` and content `width` you passed to `layout()`, and `overflow-wrap: break-word`: the browser wraps it to the same lines. Text in several fonts or with padded pieces goes through the rich inline text API below, and is painted a line at a time.
If you want textarea-like text where ordinary spaces, `\t` tabs, and `\n` hard breaks stay visible, pass `{ whiteSpace: 'pre-wrap' }` to `prepare()`:
[code]
Other `prepare()` options are `{ wordBreak: 'keep-all' }` for CSS-like `word-break: keep-all`, and `{ letterSpacing: n }` to match CSS `letter-spacing` (`n` is treated as a px value).
The returned height is the crucial last piece for unlocking web UIs:
- proper virtualization/occlusion without guesstimates & caching
- fancy userland layouts: masonry, JS-driven flexbox-like implementations, nudging a few layout values without CSS hacks (imagine that), etc.
- _development time_ verification (especially now with AI) that labels on e.g. buttons don't overflow to the next line, browser-free
- prevent layout shift when new text loads and you wanna re-anchor the scroll position
The same `prepare()` handle also works with the APIs below that don't return text (`measureLineStats()`, `measureNaturalWidth()`, `walkLineRanges()`, `layoutNextLineRange()`), e.g. to shrink-wrap a chat bubble to its widest line.
### 2. Lay out the paragraph lines manually yourself
Switch out `prepare` with `prepareWithSegments` (t