Fast DOM-Free Text Height Measurement – Pretext
The library enables you to handle auto-growing UI, virtualization, custom Canvas rendering, and shape-aware text flow with far less layout churn than repeated DOM reads. Ideal for layout patterns that still feel awkward or incomplete in CSS alone, such as shrink-wrapped chat bubbles, streaming rendering in AI Assistants, dynamic magazine spreads, obstacle-aware text flow, or text blocks routed through Canvas and SVG.
1. Install Pretext with NPM.
npm install @chenglou/pretext
2. Measure paragraph height. Call prepare() once per unique text-and-font combination, then call layout() on every resize.
import { prepare, layout } from '@chenglou/pretext'
// One-time pass: segments text, applies line-break rules, measures glyph widths via canvas
const prepared = prepare('Your product description goes here. It wraps at the container edge.', '16px Roboto')
// Pure arithmetic: sums cached widths and counts lines at 300px wide with a 22px line height
const { height, lineCount } = layout(prepared, 300, 22)
console.log(`Paragraph height: ${height}px — ${lineCount} lines`)
// On a resize event, only call layout() again — prepare() is already done
window.addEventListener('resize', () => {
const containerWidth = document.getElementById('card')!.clientWidth
const { height: newHeight } = layout(prepared, containerWidth, 22)
console.log(`Updated height: ${newHeight}px`)
}) 3. Pre-wrap mode for textarea content. Pass { whiteSpace: 'pre-wrap' } when tabs and hard newlines must be preserved, such as in a live Markdown editor or a comment input.
import { prepare, layout } from '@chenglou/pretext'
const rawInput = 'Line onenLine twothas a tab stopnLine three'
// pre-wrap mode: spaces, tabs (t), and newlines (n) are preserved, not collapsed
const prepared = prepare(rawInput, '14px "Courier New"', { whiteSpace: 'pre-wrap' })
const { height } = layout(prepared, 480, 20)
console.log(`Textarea height: ${height}px`) 4. Render to canvas with layoutWithLines. Switch to prepareWithSegments when you need the actual text string and measured width of each line. This path suits canvas, SVG, and WebGL rendering.
import { prepareWithSegments, layoutWithLines } from '@chenglou/pretext'
const paragraph = 'Multilingual text: 春天到了, بدأت الرحلة, and a rocket 🚀'
// prepareWithSegments returns a richer data structure needed for line-level APIs
const prepared = prepareWithSegments(paragraph, '18px Arial')
// layoutWithLines gives you height, lineCount, and each line's text string and width
const { lines, lineCount } = layoutWithLines(prepared, 320, 26)
const canvas = document.getElementById('output') as HTMLCanvasElement
const ctx = canvas.getContext('2d')!
ctx.font = '18px Arial'
for (let i = 0; i < lines.length; i++) {
// lines[i].text is the full string for that line; lines[i].width is its measured pixel width
ctx.fillText(lines[i].text, 10, (i + 1) * 26)
} 5. Shrink-wrap a container to its text with walkLineRanges. It fires a callback per line with width and cursor data, but never builds the line text strings.
import { prepareWithSegments, walkLineRanges, layoutWithLines } from '@chenglou/pretext'
const prepared = prepareWithSegments('A short chat bubble that should shrink to its content.', '15px Georgia')
let maxLineWidth = 0
// Speculative pass at 600px: collects the widest measured line width
walkLineRanges(prepared, 600, line => {
if (line.width > maxLineWidth) maxLineWidth = line.width
})
// maxLineWidth is now the tightest container width that still fits every line
console.log(`Shrink-wrap width: ${maxLineWidth}px`)
// Final pass at the confirmed width to retrieve line strings
const { lines } = layoutWithLines(prepared, maxLineWidth, 22) 6. Float-aware text flow with layoutNextLine. Pass a different maxWidth on each call. This is great for text that flows around a floated image or any column where the available width changes by row.
import { prepareWithSegments, layoutNextLine } from '@chenglou/pretext'
const prepared = prepareWithSegments(
'This article body wraps around a thumbnail image on the left side of the column.',
'16px "Open Sans"'
)
const columnWidth = 500
const floatWidth = 140 // image occupies 140px of horizontal space
const floatBottom = 100 // image extends 100px down from the top
// Initialize the cursor at the very start of the prepared text
let cursor = { segmentIndex: 0, graphemeIndex: 0 }
let y = 0
const canvas = document.getElementById('article') as HTMLCanvasElement
const ctx = canvas.getContext('2d')!
ctx.font = '16px "Open Sans"'
while (true) {
// Use a narrower width while rendering beside the floated image
const lineWidth = y < floatBottom ? columnWidth - floatWidth : columnWidth
const xOffset = y < floatBottom ? floatWidth : 0
const line = layoutNextLine(prepared, cursor, lineWidth)
if (line === null) break // paragraph is fully exhausted
ctx.fillText(line.text, xOffset, y + 16)
cursor = line.end // advance to the next line's start position
y += 22
} 7. API methods.
// Analyze text once and return an opaque prepared handle for fast height layout prepare(text, font, options) // Calculate total height and line count from a prepared value layout(prepared, maxWidth, lineHeight) // Analyze text and keep richer segment data for manual line layout prepareWithSegments(text, font, options) // Calculate height, line count, and full wrapped lines for a fixed width layoutWithLines(prepared, maxWidth, lineHeight) // Walk each line range for a fixed width without building text strings walkLineRanges(prepared, maxWidth, onLine) // Return the next wrapped line from a cursor position, or null at the end layoutNextLine(prepared, start, maxWidth) // Clear Pretext's shared internal caches clearCache() // Set the locale for future prepare calls and clear the shared cache setLocale(locale)
8. Return types:
type LayoutLine = {
text: string
width: number
start: LayoutCursor
end: LayoutCursor
}
type LayoutLineRange = {
width: number
start: LayoutCursor
end: LayoutCursor
}
type LayoutCursor = {
segmentIndex: number
graphemeIndex: number
} Q: Should I call prepare() on every resize?
A: No. Call prepare() once for the same text and config. Re-run layout() on resize. That is where the performance model pays off.
Q: Can I use Pretext for Canvas and SVG rendering?
A: Yes. layoutWithLines() works well for fixed-width blocks, and layoutNextLine() works well for variable-width flows.
Q: Why do my measured results drift from the DOM?
A: Check your font shorthand first. Then check your lineHeight. Both values need to match your real rendered text. On macOS, avoid system-ui if you want stable results.
Q: When should I call prepare() again vs. calling only layout() again?
A: Call prepare() once per unique combination of text content and font string. On viewport resize, call only layout() with the updated maxWidth. Calling prepare() again on unchanged text repeats the canvas measurement work and throws away the cached glyph data.
Q: My app cycles through many fonts and memory keeps climbing. How do I fix it?
A: Call clearCache() periodically. Pretext accumulates glyph width measurements keyed by font, and a long-running app that cycles through many font variants will grow that cache over time. clearCache() resets it fully.
Q: How does Pretext handle right-to-left and mixed-direction text?
A: Bidirectional text runs through the library’s internal bidi algorithm and Unicode segmentation. You pass the text as a plain string. Direction, script detection, and correct line widths are all handled internally.
The post Fast DOM-Free Text Height Measurement – Pretext appeared first on CSS Script.
The post NAB Show: Cartoni To Introduce Control Unite For Liftto HP Elevation System appeared…
Proton Camera Innovations will feature its miniature camera ecosystem at the NAB Show in Las…
PSSI and Appear are upgrading PSSI’s mobile fleet and the PSSI International Teleport with Appear’s…
Telos Alliance, a provider of broadcast audio solutions, is introducing its Axia Pulsar AoIP console…
Imagine Communications, a provider of video network and advertising technology solutions, is integrating AI-assisted scheduling…
Nevion, a Sony group company, is launching Moxela, a software-based media processing platform designed to…
This website uses cookies.