pretext

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

pretext — Fast Multiline Text Measurement & Layout

pretext — 快速多行文本测量与布局

No DOM reflow. Pure arithmetic on cached font metrics.
pretext
is a pure JavaScript/TypeScript library for fast, accurate, and comprehensive multiline text measurement and layout — without triggering
getBoundingClientRect
,
offsetHeight
, or any other DOM reflow operations.
无需DOM重排。基于缓存字体指标的纯算术运算。
pretext
是一款纯JavaScript/TypeScript库,用于快速、精准且全面的多行文本测量与布局——无需触发
getBoundingClientRect
offsetHeight
或任何其他DOM重排操作。

Installation

安装方式

Plugin (Claude Code)

插件(Claude Code)

bash
claude plugin marketplace add chenglou/pretext
bash
claude plugin marketplace add chenglou/pretext

npm

npm

bash
npm install @chenglou/pretext
bash
npm install @chenglou/pretext

Skill (any platform)

Skill(任意平台)

bash
npx skills add https://github.com/akillness/oh-my-skills --skill pretext
bash
npx skills add https://github.com/akillness/oh-my-skills --skill pretext

When to use

适用场景

  • Calculate paragraph heights and line counts without measuring in the DOM
  • Build manual line layouts for text flowing around floated images or variable-width containers
  • Handle rich text with emoji, CJK characters, RTL text, and mixed-language content
  • Render measured text to DOM, Canvas, or SVG with accurate metrics
  • Implement soft hyphenation, letter-spacing, or locale-aware word breaking
  • Avoid layout reflow in performance-critical rendering loops
  • Server-side text measurement (SSR-friendly)
  • 无需在DOM中测量即可计算段落高度和行数
  • 构建手动行布局,实现文本环绕浮动图像或可变宽度容器的效果
  • 处理包含表情符号、中日韩文字、RTL文本和混合语言内容的富文本
  • 将测量后的文本精准渲染到DOM、Canvas或SVG
  • 实现软连字符、字符间距或区域感知的断词功能
  • 在性能关键的渲染循环中避免布局重排
  • 支持服务端文本测量(SSR友好)

Do not use when

不适用场景

  • You need full CSS text engine fidelity → rely on DOM measurements directly
  • You need OpenType feature support (ligatures, kerning tables) → use a dedicated font shaping library
  • You need PDF text layout → route to a PDF generation library
  • Your target environment lacks
    Intl.Segmenter
    and Canvas 2D support
  • 需要完整CSS文本引擎保真度 → 直接依赖DOM测量
  • 需要OpenType特性支持(连字、字距调整表) → 使用专用的字体整形库
  • 需要PDF文本布局 → 使用PDF生成库
  • 目标环境缺少
    Intl.Segmenter
    和Canvas 2D支持

Core API

核心API

Use Case 1: Height Measurement

场景1:高度测量

Calculate paragraph height and line count with pure arithmetic — no DOM touch after
prepare()
.
javascript
import { prepare, layout } from '@chenglou/pretext'

// One-time analysis + font measurement (cached)
const prepared = prepare('AGI 春天到了. بدأت الرحلة 🚀‎', '16px Inter')

// Pure arithmetic — no DOM interaction
const { height, lineCount } = layout(prepared, 320, 20)
// width: 320px container, lineHeight: 20px
通过纯算术运算计算段落高度和行数——调用
prepare()
后无需操作DOM。
javascript
import { prepare, layout } from '@chenglou/pretext'

// 一次性分析 + 字体测量(已缓存)
const prepared = prepare('AGI 春天到了. بدأت الرحلة 🚀‎', '16px Inter')

// 纯算术运算——无DOM交互
const { height, lineCount } = layout(prepared, 320, 20)
// width: 320px容器, lineHeight: 20px

Use Case 2: Manual Line Layout

场景2:手动行布局

Access individual lines for custom rendering (e.g., text around floated images).
javascript
import { prepareWithSegments, layoutWithLines } from '@chenglou/pretext'

const prepared = prepareWithSegments('Text here', '18px "Helvetica Neue"')
const { lines } = layoutWithLines(prepared, 320, 26)

for (const line of lines) {
  // line.text, line.y, line.width, line.segments
}
访问单独的行以实现自定义渲染(例如:文本环绕浮动图像)。
javascript
import { prepareWithSegments, layoutWithLines } from '@chenglou/pretext'

const prepared = prepareWithSegments('Text here', '18px "Helvetica Neue"')
const { lines } = layoutWithLines(prepared, 320, 26)

for (const line of lines) {
  // line.text, line.y, line.width, line.segments
}

Use Case 3: Shrink-wrap width

场景3:自适应宽度

Find the minimum container width that fits the text without wrapping.
javascript
import { prepare, shrinkWrap } from '@chenglou/pretext'

const prepared = prepare('Hello world', '14px system-ui')
const { width } = shrinkWrap(prepared, 26)
找到能容纳文本且不换行的最小容器宽度。
javascript
import { prepare, shrinkWrap } from '@chenglou/pretext'

const prepared = prepare('Hello world', '14px system-ui')
const { width } = shrinkWrap(prepared, 26)

Options reference

配置选项参考

OptionTypeValuesDescription
whiteSpace
string
'normal'
|
'pre-wrap'
'pre-wrap'
preserves whitespace (textarea-style)
wordBreak
string
'normal'
|
'keep-all'
'keep-all'
prevents mid-word breaks in CJK text
letterSpacing
numberpixelsCSS-equivalent letter-spacing
javascript
const prepared = prepare(text, font, {
  whiteSpace: 'pre-wrap',
  wordBreak: 'keep-all',
  letterSpacing: 1.5,
})
选项类型可选值描述
whiteSpace
string
'normal'
|
'pre-wrap'
'pre-wrap'
保留空白符(类似textarea样式)
wordBreak
string
'normal'
|
'keep-all'
'keep-all'
避免中日韩文本在单词中间断行
letterSpacing
number像素值与CSS等效的字符间距
javascript
const prepared = prepare(text, font, {
  whiteSpace: 'pre-wrap',
  wordBreak: 'keep-all',
  letterSpacing: 1.5,
})

Requirements

环境要求

  • Intl.Segmenter
    API (locale-aware word/grapheme segmentation)
  • Canvas 2D text measurement (
    CanvasRenderingContext2D.measureText
    )
  • Works in modern browsers and Node.js 16+ (with canvas package for Node)
  • Intl.Segmenter
    API(支持区域感知的单词/字形分割)
  • Canvas 2D文本测量(
    CanvasRenderingContext2D.measureText
  • 适用于现代浏览器和Node.js 16+(Node环境需安装canvas包)

Output targets

输出目标

TargetNotes
DOMUse
lines
+ absolute positioning or
transform: translateY
CanvasUse
lines[i].y
+
ctx.fillText
SVGUse
lines[i].y
+
<text y=...>
elements
SSRHeights computed server-side; hydrate positions client-side
目标说明
DOM使用
lines
+ 绝对定位或
transform: translateY
Canvas使用
lines[i].y
+
ctx.fillText
SVG使用
lines[i].y
+
<text y=...>
元素
SSR服务端计算高度;客户端渲染位置

Operating rules

使用规则

  1. Call
    prepare()
    /
    prepareWithSegments()
    once per unique (text, font) pair — it's the expensive step
  2. Cache
    prepared
    objects when the same text is measured at different widths
  3. Call
    layout()
    /
    layoutWithLines()
    freely — it's pure arithmetic with no side effects
  4. Always check
    Intl.Segmenter
    availability before initializing in non-browser environments
  5. Use
    shrinkWrap
    to find minimum width before measuring at a fixed container width
  6. Prefer
    prepareWithSegments
    over
    prepare
    when you need per-line segment access for rich rendering
  1. 每个唯一的(文本、字体)组合仅调用一次
    prepare()
    /
    prepareWithSegments()
    ——这是开销较大的步骤
  2. 当同一文本需要在不同宽度下测量时,缓存
    prepared
    对象
  3. 可自由调用
    layout()
    /
    layoutWithLines()
    ——这是无副作用的纯算术运算
  4. 在非浏览器环境初始化前,务必检查
    Intl.Segmenter
    是否可用
  5. 在固定容器宽度测量前,使用
    shrinkWrap
    找到最小宽度
  6. 当需要逐段访问行内容以实现富文本渲染时,优先使用
    prepareWithSegments
    而非
    prepare

Examples

示例

javascript
import { prepare, layout, prepareWithSegments, layoutWithLines } from '@chenglou/pretext'

// Basic height check
const p = prepare('Hello, 世界! 🌏', '16px sans-serif')
console.log(layout(p, 200, 20))  // { height: 20, lineCount: 1 }

// Variable-width layout (text around floated image)
const ps = prepareWithSegments('Long article text...', '16px Georgia')
const { lines } = layoutWithLines(ps, (lineIndex) => {
  return lineIndex < 5 ? 200 : 320  // narrower for first 5 lines
}, 24)
Source: chenglou/pretext — MIT License
javascript
import { prepare, layout, prepareWithSegments, layoutWithLines } from '@chenglou/pretext'

// 基础高度检测
const p = prepare('Hello, 世界! 🌏', '16px sans-serif')
console.log(layout(p, 200, 20))  // { height: 20, lineCount: 1 }

// 可变宽度布局(文本环绕浮动图像)
const ps = prepareWithSegments('Long article text...', '16px Georgia')
const { lines } = layoutWithLines(ps, (lineIndex) => {
  return lineIndex < 5 ? 200 : 320  // 前5行使用较窄宽度
}, 24)
来源:chenglou/pretext — MIT许可证