pretext
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesepretext — Fast Multiline Text Measurement & Layout
pretext — 快速多行文本测量与布局
No DOM reflow. Pure arithmetic on cached font metrics.
pretextgetBoundingClientRectoffsetHeight无需DOM重排。基于缓存字体指标的纯算术运算。
pretextgetBoundingClientRectoffsetHeightInstallation
安装方式
Plugin (Claude Code)
插件(Claude Code)
bash
claude plugin marketplace add chenglou/pretextbash
claude plugin marketplace add chenglou/pretextnpm
npm
bash
npm install @chenglou/pretextbash
npm install @chenglou/pretextSkill (any platform)
Skill(任意平台)
bash
npx skills add https://github.com/akillness/oh-my-skills --skill pretextbash
npx skills add https://github.com/akillness/oh-my-skills --skill pretextWhen 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 and Canvas 2D support
Intl.Segmenter
- 需要完整CSS文本引擎保真度 → 直接依赖DOM测量
- 需要OpenType特性支持(连字、字距调整表) → 使用专用的字体整形库
- 需要PDF文本布局 → 使用PDF生成库
- 目标环境缺少和Canvas 2D支持
Intl.Segmenter
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通过纯算术运算计算段落高度和行数——调用后无需操作DOM。
prepare()javascript
import { prepare, layout } from '@chenglou/pretext'
// 一次性分析 + 字体测量(已缓存)
const prepared = prepare('AGI 春天到了. بدأت الرحلة 🚀', '16px Inter')
// 纯算术运算——无DOM交互
const { height, lineCount } = layout(prepared, 320, 20)
// width: 320px容器, lineHeight: 20pxUse 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
配置选项参考
| Option | Type | Values | Description |
|---|---|---|---|
| string | | |
| string | | |
| number | pixels | CSS-equivalent letter-spacing |
javascript
const prepared = prepare(text, font, {
whiteSpace: 'pre-wrap',
wordBreak: 'keep-all',
letterSpacing: 1.5,
})| 选项 | 类型 | 可选值 | 描述 |
|---|---|---|---|
| string | | |
| string | | |
| number | 像素值 | 与CSS等效的字符间距 |
javascript
const prepared = prepare(text, font, {
whiteSpace: 'pre-wrap',
wordBreak: 'keep-all',
letterSpacing: 1.5,
})Requirements
环境要求
- API (locale-aware word/grapheme segmentation)
Intl.Segmenter - Canvas 2D text measurement ()
CanvasRenderingContext2D.measureText - Works in modern browsers and Node.js 16+ (with canvas package for Node)
- API(支持区域感知的单词/字形分割)
Intl.Segmenter - Canvas 2D文本测量()
CanvasRenderingContext2D.measureText - 适用于现代浏览器和Node.js 16+(Node环境需安装canvas包)
Output targets
输出目标
| Target | Notes |
|---|---|
| DOM | Use |
| Canvas | Use |
| SVG | Use |
| SSR | Heights computed server-side; hydrate positions client-side |
| 目标 | 说明 |
|---|---|
| DOM | 使用 |
| Canvas | 使用 |
| SVG | 使用 |
| SSR | 服务端计算高度;客户端渲染位置 |
Operating rules
使用规则
- Call /
prepare()once per unique (text, font) pair — it's the expensive stepprepareWithSegments() - Cache objects when the same text is measured at different widths
prepared - Call /
layout()freely — it's pure arithmetic with no side effectslayoutWithLines() - Always check availability before initializing in non-browser environments
Intl.Segmenter - Use to find minimum width before measuring at a fixed container width
shrinkWrap - Prefer over
prepareWithSegmentswhen you need per-line segment access for rich renderingprepare
- 每个唯一的(文本、字体)组合仅调用一次/
prepare()——这是开销较大的步骤prepareWithSegments() - 当同一文本需要在不同宽度下测量时,缓存对象
prepared - 可自由调用/
layout()——这是无副作用的纯算术运算layoutWithLines() - 在非浏览器环境初始化前,务必检查是否可用
Intl.Segmenter - 在固定容器宽度测量前,使用找到最小宽度
shrinkWrap - 当需要逐段访问行内容以实现富文本渲染时,优先使用而非
prepareWithSegmentsprepare
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许可证