Pretext API 记录
仓库地址: chenglou/pretext
许可证: MIT
描述: 高性能纯 JavaScript/TypeScript 库,用于多行文本测量和布局计算。通过绕过 DOM 测量(如
getBoundingClientRect)来避免昂贵的布局重排。
目录
核心 API 列表
基础测量 API
| API | 说明 | 返回值 |
|---|---|---|
prepare(text, font, options?) | 准备工作,标准化文本并测量,返回不透明句柄 | PreparedText |
layout(prepared, maxWidth, lineHeight) | 基于已准备文本计算高度(纯算术操作) | { height, lineCount } |
prepare 参数
| 参数 | 类型 | 说明 |
|---|---|---|
text | string | 要测量的文本 |
font | string | 字体字符串(与 Canvas Context font 属性格式一致,如 '16px Inter') |
options | object? | 可选配置 |
options.whiteSpace | 'normal' | 'pre-wrap' | 'normal'(默认)保留空格和换行;'pre-wrap' 保留空格、Tab 和换行符 |
layout 参数
| 参数 | 类型 | 说明 |
|---|---|---|
prepared | PreparedText | prepare() 返回的句柄 |
maxWidth | number | 最大宽度(像素) |
lineHeight | number | 行高(像素,需与 CSS line-height 同步) |
进阶手动布局 API
| API | 说明 | 返回值 |
|---|---|---|
prepareWithSegments(text, font, options?) | 类似 prepare,返回更丰富的分段结构 | PreparedTextWithSegments |
layoutWithLines(prepared, maxWidth, lineHeight) | 固定宽度的布局 | { height, lineCount, lines: LayoutLine[] } |
walkLineRanges(prepared, maxWidth, onLine) | 底层 API,遍历行范围(无字符串构建) | number |
layoutNextLine(prepared, start, maxWidth) | 迭代器风格,逐行布局(支持动态宽度) | LayoutLine | null |
辅助函数
| API | 说明 | 返回值 |
|---|---|---|
clearCache() | 清除内部共享缓存 | void |
setLocale(locale?) | 设置本地化环境(自动调用 clearCache) | void |
类型定义
// 单行文本信息
type LayoutLine = {
text: string // 该行的完整文本内容
width: number // 测量得到的宽度
start: LayoutCursor // 起始游标
end: LayoutCursor // 结束游标
}
// 行范围信息(不含 text 字符串)
type LayoutLineRange = {
width: number
start: LayoutCursor
end: LayoutCursor
}
// 游标位置
type LayoutCursor = {
segmentIndex: number // 片段索引
graphemeIndex: number // 片段内的字素索引
}
使用示例
示例 1:测量文本高度(不触碰 DOM)
适用于虚拟化列表、防止布局偏移等场景。
import { prepare, layout } from '@chenglou/pretext'
// 1. 准备文本(只需执行一次,除非文本或字体改变)
const prepared = prepare('AGI 春天到了. 시작했다 🚀', '16px Inter')
// 2. 计算高度(纯算术,可在 resize 事件中频繁调用)
const { height, lineCount } = layout(prepared, textWidth, 20)
// 结果即为文本在给定宽度和行高下的精确高度
示例 2:保留格式的文本(类似 Textarea)
适用于需要保留 \t、\n 和空格的场景。
const prepared = prepare(textareaValue, '16px Inter', {
whiteSpace: 'pre-wrap' // 保留 \t, \n 和空格
})
const { height } = layout(prepared, textareaWidth, 20)
示例 3:手动渲染到 Canvas
适用于需要精确控制每一行绘制的情况。
import { prepareWithSegments, layoutWithLines } from '@chenglou/pretext'
const prepared = prepareWithSegments('AGI 春天到了.开始了 🚀', '18px "Helvetica Neue"')
const { lines } = layoutWithLines(prepared, 320, 26) // 320px 宽, 26px 行高
// 遍历并绘制
for (let i = 0; i < lines.length; i++) {
ctx.fillText(lines[i].text, 0, i * 26)
}
示例 4:文本绕排(动态宽度)
适用于文本周围有浮动图片等复杂布局。
import { prepareWithSegments, layoutNextLine } from '@chenglou/pretext'
const prepared = prepareWithSegments(text, font, options)
let cursor = { segmentIndex: 0, graphemeIndex: 0 }
let y = 0
while (true) {
// 假设图片占据了一定高度和宽度,根据当前 y 决定行宽
const width = y < image.bottom ? columnWidth - image.width : columnWidth
const line = layoutNextLine(prepared, cursor, width)
if (line === null) break
ctx.fillText(line.text, 0, y)
cursor = line.end // 更新游标到下一行开头
y += 26
}
示例 5: speculative testing(二分查找最佳宽度)
使用 walkLineRanges 在不构建文本字符串的情况下遍历行宽和游标。
import { prepare, walkLineRanges } from '@chenglou/pretext'
const prepared = prepare(text, '16px Inter')
// 二分查找最佳宽度
let low = 100
let high = 500
while (high - low > 1) {
const mid = Math.floor((low + high) / 2)
walkLineRanges(prepared, mid, (lineRange) => {
// 处理每一行的范围信息
console.log(lineRange.width)
})
// 根据实际需求调整 low/high
// ...
}
注意事项
1. 性能基准
| 操作 | 耗时(500 个文本批次) |
|---|---|
prepare() | ~19ms |
layout() | ~0.09ms |
2. 多语言支持
- 支持几乎所有语言(包括 Emoji 和混合双向文本)
- 使用前可调用
setLocale()设置本地化环境
3. macOS 系统字体问题
在 macOS 上,system-ui 可能会导致 layout() 精度问题,建议使用命名字体(如 '16px Inter')。
4. 字体同步
lineHeight 参数需要与 CSS 的 line-height 保持同步,否则计算结果可能不准确。
安装
npm install @chenglou/pretext
或
yarn add @chenglou/pretext