
Pretext Text Measurement
Measure and lay out multiline canvas or custom UI text without DOM reflow when building responsive editors, charts, or design tools.
Overview
Pretext Text Measurement is an agent skill for the Build phase that teaches DOM-free multiline text measurement and layout with the @chenglou/pretext library.
Install
npx skills add https://github.com/aradotso/trending-skills --skill pretext-text-measurementWhat is this skill?
- DOM-free multiline measurement via prepare() cache plus cheap layout() on resize
- Rich-text segments with prepareWithSegments() and line-based layout helpers
- Canvas font-string compatible with CanvasRenderingContext2D.font
- Explicit split: one-time prepare per (text, font), hot-path layout without remeasuring
Adoption & trust: 592 installs on skills.sh; 31 GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your canvas or custom UI needs stable text heights and wrap positions but DOM measurement forces expensive reflow on every resize.
Who is it for?
Solo builders building canvas apps, design surfaces, or performance-critical typography outside standard DOM flow.
Skip if: Simple static sites where CSS line-clamp and normal DOM metrics are sufficient, or teams avoiding canvas-based text entirely.
When should I use this skill?
Triggers include measure text without DOM, multiline text measurement, wrap text in canvas, or pretext text library.
What do I get? / Deliverables
You cache prepared glyph widths once and recompute layouts cheaply on resize for predictable performance in TS/JS frontends.
- Prepared text measurement cache
- Layout line boxes and heights for target container width
Recommended Skills
Journey fit
Text measurement is a build-time frontend concern once you are implementing UI that cannot afford getBoundingClientRect on every frame. Pretext targets canvas and variable-width layout math—the core frontend shelf for performance-sensitive typography.
How it compares
Use instead of repeated getBoundingClientRect or hidden DOM probes when you need deterministic layout math on the hot path.
Common Questions / FAQ
Who is pretext-text-measurement for?
Frontend-focused solo builders and agents implementing custom editors, charts, or canvas UIs that need accurate wrap and height without DOM reflow.
When should I use pretext-text-measurement?
During Build frontend work when triggers like measure text without DOM, wrap text in canvas, multiline text measurement, or layout text around image appear in your spec.
Is pretext-text-measurement safe to install?
It documents an npm library pattern only; review the Security Audits panel on this page and audit @chenglou/pretext like any dependency before adding it to your app.
SKILL.md
READMESKILL.md - Pretext Text Measurement
# Pretext Text Measurement & Layout > Skill by [ara.so](https://ara.so) — Daily 2026 Skills collection. Pretext is a pure JavaScript/TypeScript library for fast, accurate, DOM-free multiline text measurement and layout. It avoids `getBoundingClientRect` and `offsetHeight` (which trigger expensive layout reflows) by implementing its own measurement logic using the browser's font engine as ground truth. ## Installation ```sh npm install @chenglou/pretext ``` ## Core Concepts - **`prepare()` / `prepareWithSegments()`** — one-time analysis: normalize whitespace, segment text, measure via canvas. Cache and reuse this result. - **`layout()` / `layoutWithLines()` etc.** — cheap hot path: pure arithmetic over cached widths. Call this on every resize, not `prepare()`. - **Font string format** — same as `CanvasRenderingContext2D.font`, e.g. `'16px Inter'`, `'700 18px "Helvetica Neue"'`. ## Use Case 1: Measure Paragraph Height (No DOM) ```ts import { prepare, layout } from '@chenglou/pretext' // One-time per unique (text, font) combination const prepared = prepare('AGI 春天到了. بدأت الرحلة 🚀', '16px Inter') // Cheap — call on every resize const { height, lineCount } = layout(prepared, containerWidth, 20) // height: total pixel height; lineCount: number of wrapped lines ``` ### With Pre-wrap (textarea-like) ```ts const prepared = prepare(textareaValue, '16px Inter', { whiteSpace: 'pre-wrap' }) const { height } = layout(prepared, textareaWidth, 24) ``` ### With CJK keep-all ```ts const prepared = prepare(cjkText, '16px NotoSansCJK', { wordBreak: 'keep-all' }) const { height, lineCount } = layout(prepared, 300, 22) ``` ## Use Case 2: Manual Line Layout ### Get All Lines at Fixed Width ```ts import { prepareWithSegments, layoutWithLines } from '@chenglou/pretext' const prepared = prepareWithSegments('Hello world, this is Pretext!', '18px "Helvetica Neue"') const { lines, height, lineCount } = layoutWithLines(prepared, 320, 26) // Render to canvas lines.forEach((line, i) => { ctx.fillText(line.text, 0, i * 26) }) // line shape: { text: string, width: number, start: LayoutCursor, end: LayoutCursor } ``` ### Line Stats Without Building Strings ```ts import { prepareWithSegments, measureLineStats, walkLineRanges } from '@chenglou/pretext' const prepared = prepareWithSegments(article, '16px Inter') // Just counts and widths — no string allocations const { lineCount, maxLineWidth } = measureLineStats(prepared, 320) // Walk line ranges for custom logic let widest = 0 walkLineRanges(prepared, 320, line => { if (line.width > widest) widest = line.width }) // widest is now the tightest container that still fits the text (shrinkwrap!) ``` ### Natural Width (No Wrap Constraint) ```ts import { prepareWithSegments, measureNaturalWidth } from '@chenglou/pretext' const prepared = prepareWithSegments('Short label', '14px Inter') const naturalWidth = measureNaturalWidth(prepared) // Width if text never wraps — useful for button sizing ``` ### Variable-Width Layout (Text Around Floated Image) ```ts import { prepareWithSegments, layoutNextLineRange, materializeLineRange, type LayoutCursor } from '@chenglou/pretext' const prepared = prepareWithSegments(article, '16px Inter') let cursor: LayoutCursor = { segmentIndex: 0, graphemeIndex: 0 } let y = 0 const lineHeight = 24 const image = { bottom: 200, width: 120 } const columnWidth = 600 while (true) { // Lines beside the image are narrower const width = y < image.bottom ? columnWidth - image.width : columnWidth const range = layoutNextLine