pretext: the height comes back without a reflow, as long as you never call prepare() twice
Pretext measures and lays out multilingual text in web applications, using Canvas, DOM, and OpenType data to produce predictable line breaks and dimensions.
At a glance
- What is it?
- A pre-1.0 TypeScript library that measures and breaks multiline text with canvas and OpenType data instead of touching the DOM. Its speed depends entirely on splitting one expensive prepare() from a cheap layout(), and its own docs warn that the rich inline path is not a CSS inline formatting engine.
- Who is it for?
- pretext fits a developer who needs a height before the text is on screen, whether for virtualizing a long list, sizing a bubble to its widest line, or checking in development that a button label will not wrap. It does not fit someone who wants the browser's own inline layout rules, since the rich inline path is explicitly not a CSS inline formatting engine, and it does not hyphenate for you.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The manifest says 0.0.9 and the repository has no releases at all
Pretext is published to npm under a scoped name, and the install is one line:
npm install @chenglou/pretextThe manifest version reads 0.0.9, and the repository carries no GitHub releases, so the tag landscape gives you nothing to pin against. The last push to main is dated 2026-09-29, which tells you the code is moving even if the version number is not telling you much. Two details in the manifest are worth reading before you install. The files array ships the built dist directory and the src directory, excluding only the layout test and the test data file, so the source travels with the package and you can read the implementation from node_modules. The export map exposes two entry points, the root layout module and a separate rich-inline module, plus a pass through for package.json. The consequence for a consumer is that a pre-1.0 version is a movable surface, so wrap it behind your own module if the exports matter to you.
prepare() is the expensive half, and rerunning it throws the precomputation away
The whole speed argument rests on a split between two calls. prepare() does the one-time work: it normalizes whitespace, segments the text at its break opportunities, measures those segments with canvas, and returns an opaque handle. layout() is the hot path after that, described as pure arithmetic over cached widths, and the result is the height and line count you actually wanted:
const { height, lineCount } = layout(prepared, 320, 20)The instruction that follows is the sharpest edge in the whole API: do not rerun prepare() for the same text, font and options, because that defeats its precomputation, and on resize rerun only layout(). The same idea shows up in the editable text case, where each paragraph is prepared apart so an edit only forces one re-prepare. The consequence is that a library designed to be cheap can still be made expensive by a single misplaced call in a render or resize handler, and the symptom looks like the library being slow rather than the call site being wrong. Measure where you measure, and cache the handle where the text does not change.
The demos are not in the package, so the canonical examples need a clone
The examples are treated as the documentation, and they are not shipped. The page says the demos do not ship in the npm package, so the route to them is to clone the repository and run the development server:
bun install
bun startThen you open http://localhost:3000/demos. Windows has its own instruction, to use a separate start script, which exists because the start script clears anything already listening on port 3000 using a POSIX port lookup that does not behave the same way on Windows. The live demos are also published at chenglou.me/pretext, with a further set at somnai-dreams.github.io/pretext-demos, and there is a written walkthrough of the Markdown chat demo in pages/demos/markdown-chat.md for anyone building a chat or another long list. The consequence is that you cannot learn the idiomatic usage from the installed package, and the page positions the demos as patterns worth reading rather than as illustrations, which makes the extra step part of the intended workflow.
Break data is generated and checked, and the project keeps its own list of engine bugs
The accuracy claim rests on using the browser's own font engine as ground truth, and the repository is organised around keeping that honest. Two scripts generate data, one for engine break data and one for WebKit generic families, and each has a check mode that runs as part of the verification command alongside the TypeScript compiler, a type aware lint pass over src and harness, and a dead code check. A harness is exposed as its own command, and there is a directory of corpora for test material. Then there are the two files that tell you what the author knows is still unresolved: PLATFORM_BUGS.md and ENGINE_FOLLOWUPS.md. The consequence is worth stating plainly. Pretext computes a layout and the browser still paints the text, so wherever the two disagree, the browser wins and your careful arithmetic is what has to bend. A project maintaining a list of platform bugs is telling you the gap is real and tracked, not absent.
The rich inline path is not a CSS inline formatting engine, and the page says so
For text that mixes fonts, code spans, mentions, or chips, there is a second entry point, @chenglou/pretext/rich-inline, taking a flat list of items where each one carries its own text, font, a break policy, and an extra width, and the fragments that come back retain their source index, their text slice, and their cursors. Then the documentation draws the line explicitly. In pre-wrap, every item except an atomic one keeps its spaces, tabs, and newlines, spaces at the end of a line hang past it whichever items hold them, tab stops count from the start of the line, and a newline ends its line. You are told to paint each line with white-space: pre, because a line painted alone in pre-wrap counts as the paragraph's last line, and the closing line says this is not a general CSS inline formatting engine. The consequence is that if you expected CSS semantics, you will find documented disagreements instead, in the handling of trailing spaces and padded items, and you have to accept the library's rules rather than the browser's.
Hyphenation is left to you, and the guidance is deliberately conservative
The library does not break words for you. Hyphenation is handled by inserting soft hyphens before you call prepare() or prepareWithSegments(), and those characters stay invisible unless the line actually breaks there, in which case the line ends with a hyphen. The advice that follows is about restraint: for mixed language or user generated application text, prefer conservative, locale-aware insertion over aggressive pattern hyphenation. The consequence is symmetric and easy to get wrong in both directions. Insert nothing and a long unbroken token, a URL, or a chemical name simply overflows the column you measured, because measurement will faithfully report the width of a line that has nowhere to break. Insert too eagerly and you ship visible hyphens in languages where breaking mid-word is wrong, which is worse than overflow because the text reads as incorrect rather than as cramped. The measured height is only as good as your break opportunities.
Math.ceil is load bearing, because the exact fractional width makes the browser wrap
The last mechanical detail explains a class of bug that looks like a library fault. When you want the tightest container that still fits, you walk the line ranges, keep the widest width, and size the element to it. The page is explicit that you must round up, and it says why: at the exact fractional width the browser can wrap the widest line, which is what the bubbles demo does with Math.ceil. The payoffs the project lists for having a height without touching the DOM are practical ones: proper virtualization and occlusion without guesstimates and caching, userland layouts such as masonry or a JavaScript driven flexbox, nudging a few layout values without CSS hacks, and development time verification that a label on a button will not overflow to the next line, done browser-free. The consequence is that this is a measurement tool for your own layout code, not a replacement for how the browser wraps. Trust it for arithmetic, round up at the edges, and keep a real browser in the loop for the final check.
Editorial conclusion
pretext fits a developer who needs a height before the text is on screen, whether for virtualizing a long list, sizing a bubble to its widest line, or checking in development that a button label will not wrap. It does not fit someone who wants the browser's own inline layout rules, since the rich inline path is explicitly not a CSS inline formatting engine, and it does not hyphenate for you. Before depending on it, read the prepare and layout split carefully, keep the demos repository to hand, and treat a 0.0.9 surface as one that can move.
Frequently asked questions
how to install pretext
From npm the install is `npm install @chenglou/pretext`, a scoped package published with public access and its manifest version at 0.0.9. The demos are not part of that package, so to read them you clone the repository, run `bun install`, then `bun start`, and open http://localhost:3000/demos, with a separate start script for Windows.
how to use pretext js
It serves two use cases. Call prepare() once for a piece of text and a font to get an opaque handle, then call layout() with a max width and line height for the height and line count as pure arithmetic with no DOM reflow. For manual line placement, swap in prepareWithSegments and use layoutWithLines(), measureLineStats(), walkLineRanges(), or layoutNextLineRange() with a cursor.
how to use pretext library
Measure a paragraph's height without touching the DOM, then use that number for virtualization, userland layouts, or checking labels in development. For text mixing fonts, code spans, mentions, or chips, use the separate rich-inline entry point, which takes a flat list of text items and is explicitly not a general CSS inline formatting engine.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/chenglou-pretext)