Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
aradotso avatar

Pretext Text Measurement

  • 640 installs
  • 66 repo stars
  • Updated July 9, 2026
  • aradotso/trending-skills

pretext-text-measurement is a frontend skill that guides developers to use the Pretext JavaScript/TypeScript library for fast, DOM-free multiline text measurement and layout without triggering reflows.

About

pretext-text-measurement is a skill for developers building canvas UIs, rich-text editors, and custom layout engines in JavaScript or TypeScript. Pretext measures multiline, variable-width, and rich text without calling getBoundingClientRect or offsetWidth, avoiding expensive DOM reflows during layout passes. The skill triggers on tasks like calculating paragraph height, shrink-wrapping text width, wrapping text on canvas, or laying text around images. It documents how to use Pretext for accurate sizing when the browser DOM is too slow or unavailable, such as in WebGL overlays, PDF-like renderers, or performance-sensitive dashboards. Developers reach for this skill when standard DOM measurement APIs cause jank or when layout must run entirely off-DOM while still matching real font metrics.

  • DOM-free text measurement using canvas as ground truth
  • prepare() for one-time analysis and layout() for cheap hot-path resizes
  • Supports multiline, rich-text, variable-width fonts, and text wrapping around images
  • Cache-and-reuse architecture eliminates layout thrashing on every resize
  • Pure JavaScript/TypeScript with zero dependencies

Pretext Text Measurement by the numbers

  • 640 all-time installs (skills.sh)
  • +12 installs in the week ending Jul 5, 2026 (Skillselion tracking)
  • Ranked #529 of 2,277 Frontend Development skills by installs in the Skillselion catalog
  • Security screen: LOW risk (skills.sh audit)
  • Data as of Jul 19, 2026 (Skillselion catalog sync)
npx skills add https://github.com/aradotso/trending-skills --skill pretext-text-measurement

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs640
repo stars66
Security audit3 / 3 scanners passed
Last updatedJuly 9, 2026
Repositoryaradotso/trending-skills

How do you measure multiline text without DOM reflow?

Perform fast, accurate multiline text measurement and layout calculations in the browser without triggering DOM reflows.

Who is it for?

Frontend developers building canvas renderers, rich-text UIs, or performance-critical layouts that cannot rely on DOM measurement APIs.

Skip if: Simple static HTML pages where a single DOM element's offsetWidth is sufficient and reflow cost is negligible.

When should I use this skill?

The user needs paragraph height, shrinkwrap width, canvas text wrap, or layout-around-image without DOM reflow.

What you get

DOM-free text measurement code, accurate multiline layout calculations, and canvas-ready wrap and shrinkwrap sizing.

  • Pretext measurement snippets
  • multiline layout calculations

Files

SKILL.mdMarkdownGitHub ↗

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, 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)

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)

FunctionDescription
prepare(text, font, opts?)One-time analysis, returns PreparedText
layout(prepared, maxWidth, lineHeight)Returns { height, lineCount }

Use Case 2 (Manual Layout)

FunctionDescription
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 font string exactly matches your CSS font shorthand (weight, style, size, family all matter).
  • Ensure lineHeight matches your CSS line-height in pixels.
  • Font must be loaded before calling prepare() — use document.fonts.ready or FontFace.load().

`prepare()` is slow on every resize

  • Only call prepare() when text or font changes. For resizes, only call layout() 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 the RichInlineItem for chips, mentions, badges.

Getting widest line for shrinkwrap containers

  • Use measureLineStats(prepared, maxWidth).maxLineWidth or walk with walkLineRanges and track the max line.width.

Related skills

How it compares

Choose pretext-text-measurement over generic canvas skills when the blocker is accurate off-DOM text metrics, not scene graph or WebGL setup.

FAQ

What does pretext-text-measurement solve?

pretext-text-measurement guides use of the Pretext library for fast, accurate multiline text measurement without DOM APIs. Pretext avoids getBoundingClientRect and similar calls that trigger reflow during layout.

When should I use Pretext instead of DOM measurement?

Use Pretext when building canvas UIs, custom renderers, or rich-text layouts where DOM measurement causes jank or is unavailable. The skill covers shrinkwrap width, paragraph height, and wrap-around-image cases.

Is Pretext Text Measurement safe to install?

skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.

Frontend Developmentfrontendintegrations

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.