
pretext-text-measurement
Fast, accurate, DOM-free text measurement and layout library for JavaScript/TypeScript supporting multiline, rich-text, and variable-width layouts.
- Installs
- 695
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/reason-machines/trending-skills --skill pretext-text-measurement- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads pretext-text-measurement automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
Pretext Text Measurement & Layout
Skill by 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
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, notprepare().- Font string format — same as
CanvasRenderingContext2D.font, e.g.'16px Inter','700 18px "Helvetica Neue"'.
Use Case 1: Measure Paragraph Height (No DOM)
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)
const prepared = prepare(textareaValue, '16px Inter', { whiteSpace: 'pre-wrap' })
const { height } = layout(prepared, textareaWidth, 24)
With CJK keep-all
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
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
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)
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)
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 = layoutNextLineRange(prepared, cursor, width)
if (range === null) break
const line = materializeLineRange(prepared, range)
ctx.fillText(line.text, 0, y)
cursor = range.end
y += lineHeight
}
Iterator API (Fixed Width, With Text Strings)
import { prepareWithSegments, layoutNextLine, type LayoutCursor } from '@chenglou/pretext'
const prepared = prepareWithSegments(text, '16px Inter')
let cursor: LayoutCursor = { segmentIndex: 0, graphemeIndex: 0 }
let line = layoutNextLine(prepared, cursor, 400)
while (line !== null) {
console.log(line.text, line.width)
cursor = line.end
line = layoutNextLine(prepared, cursor, 400)
}
Use Case 3: Rich Inline Text Flow
For mixed fonts, chips, @mentions, and inline code spans:
import {
prepareRichInline,
walkRichInlineLineRanges,
materializeRichInlineLineRange
} from '@chenglou/pretext/rich-inline'
const prepared = prepareRichInline([
{ text: 'Ship ', font: '500 17px Inter' },
{ text: '@maya', font: '700 12px Inter', break: 'never', extraWidth: 22 },
{ text: "'s feature", font: '500 17px Inter' },
{ text: 'urgent', font: '600 12px Inter', break: 'never', extraWidth: 16 },
])
walkRichInlineLineRanges(prepared, 320, range => {
const line = materializeRichInlineLineRange(prepared, range)
line.fragments.forEach(frag => {
// frag: { itemIndex, text, gapBefore, occupiedWidth, start, end }
const item = items[frag.itemIndex]
ctx.font = item.font
ctx.fillText(frag.text, x + frag.gapBefore, y)
})
})
Rich Inline Stats
import { prepareRichInline, measureRichInlineStats } from '@chenglou/pretext/rich-inline'
const prepared = prepareRichInline(items)
const { lineCount, maxLineWidth } = measureRichInlineStats(prepared, containerWidth)
Common Patterns
Virtualized List Row Heights
import { prepare, layout } from '@chenglou/pretext'
// Pre-measure all items before virtualization
const rowHeights = items.map(item => {
const prepared = prepare(item.text, '14px Inter')
const { height } = layout(prepared, LIST_WIDTH, 20)
return height
})
Binary Search for Balanced Text Width
import { prepareWithSegments, measureLineStats } from '@chenglou/pretext'
function findBalancedWidth(text: string, font: string, maxWidth: number): number {
const prepared = prepareWithSegments(text, font)
const { lineCount: targetLines } = measureLineStats(prepared, maxWidth)
let lo = 1, hi = maxWidth
while (hi - lo > 1) {
const mid = (lo + hi) / 2
const { lineCount } = measureLineStats(prepared, mid)
if (lineCount <= targetLines) hi = mid
else lo = mid
}
return hi
}
Resize Handler Pattern
import { prepareWithSegments, layoutWithLines } from '@chenglou/pretext'
// Prepare ONCE per text/font change
let prepared = prepareWithSegments(text, '16px Inter')
function onResize(containerWidth: number) {
// layout() is cheap — safe to call on every resize event
const { lines } = layoutWithLines(prepared, containerWidth, 24)
renderLines(lines)
}
// Only re-prepare when text or font changes
function onTextChange(newText: string) {
prepared = prepareWithSegments(newText, '16px Inter')
onResize(currentWidth)
}
Prevent Layout Shift on Dynamic Content
import { prepare, layout } from '@chenglou/pretext'
async function loadAndRender(containerId: string, width: number) {
const container = document.getElementById(containerId)!
const text = await fetchText()
// Measure BEFORE inserting into DOM — no reflow needed
const prepared = prepare(text, '16px Inter')
const { height } = layout(prepared, width, 24)
// Reserve space first to prevent layout shift
container.style.height = `${height}px`
container.textContent = text
}
API Quick Reference
Use Case 1 (Height Only)
| Function | Description |
|---|---|
prepare(text, font, opts?) | One-time analysis, returns PreparedText |
layout(prepared, maxWidth, lineHeight) | Returns { height, lineCount } |
Use Case 2 (Manual Layout)
| Function | Description |
|---|---|
prepareWithSegments(text, font, opts?) | One-time analysis, returns PreparedTextWithSegments |
layoutWithLines(prepared, maxWidth, lineHeight) | Returns { height, lineCount, lines[] } |
walkLineRanges(prepared, maxWidth, onLine) | Calls onLine per line, no string allocs |
measureLineStats(prepared, maxWidth) | Returns { lineCount, maxLineWidth } only |
measureNaturalWidth(prepared) | Width if text never wraps |
layoutNextLineRange(prepared, cursor, maxWidth) | Iterator — one range at a time, variable width |
layoutNextLine(prepared, cursor, maxWidth) | Iterator — one line + text at a time |
materializeLineRange(prepared, range) | Range → LayoutLine with text string |
Options
{
whiteSpace?: 'normal' | 'pre-wrap' // default: 'normal'
wordBreak?: 'normal' | 'keep-all' // default: 'normal'
}
Troubleshooting
Text height is wrong / doesn't match browser rendering
- Ensure
fontstring exactly matches your CSSfontshorthand (weight, style, size, family all matter). - Ensure
lineHeightmatches your CSSline-heightin pixels. - Font must be loaded before calling
prepare()— usedocument.fonts.readyorFontFace.load().
prepare() is slow on every resize
- Only call
prepare()when text or font changes. For resizes, only calllayout()or equivalent.
Canvas not available (SSR / Node)
- Server-side support is listed as "coming soon". For now, this library requires a browser environment (canvas API).
CJK text not wrapping correctly
- Try
{ wordBreak: 'keep-all' }for Korean/Chinese text that should not break mid-word.
Rich inline items breaking when they should be atomic
- Add
break: 'never'to theRichInlineItemfor chips, mentions, badges.
Getting widest line for shrinkwrap containers
- Use
measureLineStats(prepared, maxWidth).maxLineWidthor walk withwalkLineRangesand track the maxline.width.
FAQ
What does the pretext-text-measurement skill do?
Fast, accurate, DOM-free text measurement and layout library for JavaScript/TypeScript supporting multiline, rich-text, and variable-width layouts.
How do I install pretext-text-measurement?
Run npx skills add https://github.com/reason-machines/trending-skills --skill pretext-text-measurement in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/pretext-text-measurement/ (personal) or .claude/skills/pretext-text-measurement/ (project).
Which AI agents can use pretext-text-measurement?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is pretext-text-measurement safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains pretext-text-measurement?
It is published by reason-machines in reason-machines/trending-skills. It has 695 recorded installs.
More from reason-machines/trending-skills
openclaw-control-center
reason-machines/trending-skills
Local-first, security-first control center for OpenClaw agents — visibility dashboard with readonly defaults, token attribution, collaboration tracing, and safe write operations.
inkos-multi-agent-novel-writing
reason-machines/trending-skills
Multi-agent CLI system for autonomous novel writing, auditing, and revision with human review gates
claude-hud-statusline
reason-machines/trending-skills
A Claude Code plugin that displays a real-time HUD showing context usage, active tools, running agents, and todo progress in your terminal statusline.
understand-anything-knowledge-graph
reason-machines/trending-skills
Turn any codebase into an interactive knowledge graph using Claude Code skills — explore, search, and ask questions about any project visually.
agency-agents-ai-specialists
reason-machines/trending-skills
A collection of specialized AI agent personalities for Claude Code, Cursor, Aider, Windsurf, and other AI coding tools — covering engineering, design, marketing, sales, and more.
agent-browser-automation
reason-machines/trending-skills
Headless browser automation CLI for AI agents using native Rust binary with Chrome DevTools Protocol
Popular frontend & design skills
setup-matt-pocock-skills
mattpocock/skills
Configure this repo for the engineering skills: set up its issue tracker, triage label vocabulary, and domain doc layout. Run once before first use of the other engineering skills.
frontend-design
anthropics/skills
Guidance for distinctive, intentional visual design when building new UI or reshaping an existing one. Helps with aesthetic direction, typography, and making choices that don't read as templated defaults.
hyperframes-animation
heygen-com/hyperframes
All animation knowledge for HyperFrames — atomic motion rules, multi-phase scene blueprints, scene transitions, broader motion-design techniques, AND the seven runtime adapters (GSAP default, plus Lottie, Three.js, Anime.js, CSS keyframes, Web Animations API, TypeGPU). Use for any motion or animation task: pick 2-4 rules and compose, or load a blueprint, or look up runtime-specific API (e.g. GSAP eases / Lottie player / Three.js mixer). Also covers auditing an existing composition's choreography (animation map) and 24 named text-animation effects. HyperFrames-native: single paused timeline, seek-safe, deterministic.
prototype
mattpocock/skills
Build a throwaway prototype to answer a design question. Use when the user wants to sanity-check whether a state model or logic feels right, or explore what a UI should look like.
hyperframes-core
heygen-com/hyperframes
The HyperFrames composition contract — build one renderable project. Use for composition structure, the data- timing attributes, class="clip", tracks, sub-compositions, variables, framework-owned media playback, deterministic-render rules, and validation. Read before writing composition HTML.
vercel-react-best-practices
vercel-labs/agent-skills
React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.