引子:"我穿越了地狱"
"My dear front-end developers... I have crawled through depths of hell to bring you, for the foreseeable years, one of the more important foundational pieces of UI engineering."
— Cheng Lou, 2026年3月28日, X (Twitter)
这条推文获得了 2200万浏览量。发布者不是网红,是前 React 核心团队成员、React Motion 作者、ReasonML/ReScript 创始人、现 Midjourney 工程师 Cheng Lou。他开源的 Pretext,是一个 15KB、零依赖的纯 TypeScript 库,做了一件看似简单的事:在不碰 DOM 的前提下,精确测量多行文本的高度和换行。
但"简单"是假象。这个问题前端开发者忍了三十多年。直到今天。
问题:测一段文字有多高,是前端性能的地狱
想象你在做一个聊天应用。AI 正在 streaming tokens,每个新 token 都可能让消息气泡变高。如果你想让 scroll 始终锚定在底部,你需要知道气泡的新高度。
传统的做法:
const el = document.createElement('div')
el.style.width = width + 'px'
el.textContent = text
document.body.appendChild(el)
const height = el.offsetHeight // ← 这里触发 reflow
document.body.removeChild(el)
offsetHeight、getBoundingClientRect()、scrollHeight……这些 API 的共同问题是:它们强制浏览器做 layout reflow。浏览器必须把整个页面的几何重新算一遍,才能告诉你这个元素有多高。
在少量元素上没问题。但在虚拟列表、瀑布流、聊天流、实时 dashboard 这些高密度场景里,reflow 是性能杀手——一帧可能吃掉 30ms,直接让 60fps 崩溃。
更深层的问题是:你无法在渲染之前知道文本会占多大空间。CSS 是声明式的,浏览器说了算。你想预判?没门。
Pretext 是什么,不是什么
Pretext 不是:
- 一个新的 CSS 框架
- 一个富文本编辑器
- 一个字体渲染引擎
- 一个 "vibe coding" 的玩具 demo
Pretext 是:
- 一个纯算术的文本测量与布局引擎
- 用 JavaScript/TypeScript 在 DOM 之外,自己算换行、算高度、算每行多长
- 核心 API 只有两个函数:
prepare()和layout()
它的哲学是:把浏览器排版引擎里的"测量和换行"能力,提取成一个开发者可调用的纯计算过程。
架构拆解:prepare() 与 layout() 的分裂
Pretext 的核心洞察是把工作劈成两半。
prepare() —— 一次性的"重活"
const prepared = prepare(
'AGI 春天到了. بدأت الرحلة 🚀',
'16px Inter'
)
prepare() 做以下一次性工作:
- 空白规范化 —— 处理空格、tab、
\n的折叠规则 - Unicode 字素分割 —— 用
Intl.Segmenter处理 grapheme cluster 边界(CJK、阿拉伯语、希伯来语、泰语、emoji、混合脚本) - Glue rules —— 处理各语言书写系统的"粘附规则",比如中文和英文之间的空格处理、阿拉伯语的 Bidi(双向文本)逻辑
- Canvas 测量 —— 用
canvas.measureText()测每个片段的精确宽度 - 缓存 —— 把所有测量结果存在一个 opaque handle 里
耗时:对 500 条文本约 19ms(0.1–1ms 每条)
layout() —— 热路径上的"纯算术"
const { height, lineCount } = layout(prepared, 320, 20)
// 320px 最大宽度,20px 行高
layout() 做的事惊人地简单:
- 从左到右累加单词宽度
- 超过 maxWidth 就换行
- 返回总高度和行数
耗时:对同一批 500 条文本约 0.09ms
也就是说,layout() 的单次调用约 0.0002ms(200 纳秒)。
对比 DOM 测量:500 次 getBoundingClientRect() 可能触发 500 次 reflow,耗时 15–30ms。Pretext 的 layout() 是 DOM 测量的 300–600 倍快。
技术细节:为什么是"地狱级"开发
Cheng Lou 说的 "depths of hell" 不是煽情。精确文本测量涉及前端世界里公认最硬的几块骨头:
1. Unicode Grapheme Cluster
一个 "用户眼中的字符" 不等于一个 code point。emoji 组合(👨👩👧👦 = 7 个 code point)、带变音符号的拉丁字母(é 可以是 e + ◌́ 或单个 precomposed)、阿拉伯语的 presentation forms……Intl.Segmenter 是浏览器较新的 API,负责把这些拆开成"用户感知的字符",但不同浏览器的 segmenter 行为有细微差异。
2. 双向文本(Bidi)
混合 LTR 和 RTL 文本(如英文中嵌入阿拉伯语)时,视觉顺序和存储顺序不同。Pretext 需要维护 embedding levels、处理 override、isolate 等 Unicode Bidi 算法规则。这部分直接借鉴了 pdf.js 的实现——Sebastian Markbage 十年前的 text-layout 项目种下的种子。
3. Glue Rules(粘附规则)
不同语言的"换行允许位置"不同:
- 英文:空格处换行
- 中文:字与字之间可以换行(
word-break: normal) - 日文:部分假名和汉字之间不可换行(
word-break: keep-all行为) - 韩文:Hangul 音节边界
- 泰文:需要字典分词(Pretext 目前用
overflow-wrap: break-word回退处理过长 run)
4. 浏览器字体引擎差异
Canvas measureText() 的结果在不同浏览器、不同平台、不同字体下有 sub-pixel 级差异。Pretext 的验证方法是:在 Chrome、Safari、Firefox 上用大量真实文本(包括整本《了不起的盖茨比》和多语言数据集)做 pixel-perfect 对比测试。
5. Soft Hyphen(软连字符)
­ 或 U+00AD 是可选的换行点。Pretext 把它当作"不选中就隐形,选中就显示尾部 -"的断点处理。自动连字(automatic hyphenation)目前未内置——Cheng Lou 建议对混合语言或用户生成内容用保守的手动插入策略。
进阶 API:不只是"算高度"
Pretext 的底层 API 允许你逐行控制文本布局,这是 CSS 至今无法优雅做到的。
layoutNextLine() —— 文字绕排浮动图片
let cursor = { segmentIndex: 0, graphemeIndex: 0 }
let y = 0
while (true) {
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
}
每行可以有不同的可用宽度。旁边有图片?行宽收窄。图片下方?恢复全宽。CSS 的 shape-outside 需要 float + alpha mask,且不支持动态交互(如拖拽、旋转)。Pretext 用纯 JS 实时重算,60fps。
walkLineRanges() —— 不分配字符串的二进制搜索
let maxW = 0
walkLineRanges(prepared, 320, line => {
if (line.width > maxW) maxW = line.width
})
// maxW 就是最紧的 shrink-wrap 宽度
用于消息气泡的"刚好包住文字"布局,或 balanced text(让每行宽度尽量均匀)。
Rich Inline —— 富文本混排
import { prepareRichInline } 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 rich-note", font: '500 17px Inter' },
])
支持不同字体、不同 weight 的 inline item 混排,break: 'never' 让 mention/chip 保持原子性,extraWidth 给 pill 的 padding + border 留空间。不是完整 CSS inline formatting engine,而是有意收窄到常见场景的 fast path。
AI 协作开发:不是"vibe coding",是工程加速
Pretext 的开发过程本身就有意思。Cheng Lou 不是手写每一行——他把浏览器 benchmark 数据喂给 Claude Code 和 OpenAI Codex,让 AI 在 Chrome、Safari、Firefox 上迭代测试和优化 TypeScript 布局逻辑。
测试语料包括:
- 整本《了不起的盖茨比》
- 泰语、中文、韩语、日语、阿拉伯语的多语言数据集
AI 加速了迭代,但没有替代架构设计。Cheng Lou "花了数周时间"在这个项目上——AI 做的是 tedious empirical work(枯燥的经验性工作),而他负责定义问题和验证结果。
这是一种高质量的人机协作模式:人类定方向、做判断;AI 磨细节、跑测试。
为什么 LLM 需要 Pretext?
Cheng Lou 的更大论点:
"80% of the CSS spec could be avoided if developers had better control over text, and AI alleviates the need of having more hard-coded CSS configs."
大语言模型生成 UI 时,最缺失的能力是什么?空间视觉(spatial vision)。
AI 可以生成 HTML/CSS,但它不知道:
- 这个按钮的 label 会不会溢出到第二行?
- 虚拟列表里第 1000 个元素的高度是多少?
- 聊天消息气泡在接收新 token 后会不会推高 scroll?
过去,这些问题要么靠硬编码高度估计、要么靠 DOM 测量后缓存、要么干脆放弃治疗。Pretext 提供了一种验证层(verification layer):AI 在"画"之前先"算",确认布局正确性,不需要打开浏览器。
这才是 Pretext 的真正野心——它不是排版工具,而是让 AI 代理可靠地生成 UI 的基础设施。
社区反响:19M 浏览,18K Stars,一周 Swift 移植
发布后迅速引爆:
- 19M+ 推文浏览量
- 18K GitHub stars(几天内)
- 45K+ stars(截至搜索时)
- Hacker News 314 points, 59 条评论
社区 demo 包括:
- 飞龙穿字——龙在段落中飞行,文字实时绕排
- 手机倾斜——倾斜设备时字母像物理物体般掉落
- 多栏文本围绕动态 orb 流动
- 紧包裹消息气泡(CSS 永远做不好的"shrink wrap")
第一个非 JS 移植 swift-pretextkit 在发布后 5 天出现(Tornike Gomareli),用 Apple CoreText 替代 Canvas,benchmark 显示比 CoreText/TextKit/UILabel 直接测量快 2x、省电 2x。Cheng Lou 确认这是 1-to-1 端口移植。
技术边界与限制
当前支持:
white-space: normal和pre-wrapword-break: normal和keep-alloverflow-wrap: break-wordline-break: autoletter-spacing(数字 px 值)- Tab 默认
tab-size: 8 - 所有语言(包括 emoji 和混合 Bidi),自动处理 Unicode 分段
已知限制:
system-ui字体在 macOS 上layout()精度有问题,需用具体字体名- 不支持垂直文本(vertical-RTL,日文纵向排版)——issue #1 就是这个请求
- 不支持 CSS 的
font-optical-sizing、font-feature-settings、font-variation-settings - 自动连字(automatic hyphenation)未内置
- 需要
Intl.Segmenter和 Canvas 2D,无这些 API 的运行时不支持 prepare()只做水平方向测量,lineHeight是 layout-time 输入
Cheng Lou 的明确表态: Pretext 目前不是完整的字体渲染引擎,目标是"常见的文本配置"。
更大的图景:先算再画
Pretext 代表了一种前端范式的转移:
从"先渲染再测量"到"先算再画"
传统的 Web 平台是声明式的。你写 CSS,浏览器算。你想知道结果?等浏览器算完再说。
Pretext 把这个权力收回了开发者手中。文本布局不再是浏览器的黑盒,而是一个可调用、可验证、可组合的计算过程。
这解锁了过去不可能的场景:
- 虚拟列表/遮挡渲染:精确高度,不需要猜测和缓存
- 用户态布局:纯 JS 实现的 masonry、flexbox-like、nudging 布局值
- 防止 CLS(Cumulative Layout Shift):先算高度再渲染,scroll 位置不跳
- AI 开发时验证:生成按钮后验证 label 不溢出,不需要真实渲染
- Canvas/SVG/WebGL 文本:精确控制每行渲染
- 服务端渲染(未来):Pretext 计划支持 server-side
结论
Pretext 是前端工程里的一块**地基级(foundational)**基础设施。
它解决的不是"怎么让文字好看",而是"怎么在不碰 DOM 的前提下,精确知道文字会占多少空间"。这个问题看起来小,但它是虚拟列表、聊天流、AI streaming UI、响应式布局、防 CLS 等一切现代交互的瓶颈。
Cheng Lou 的个人风格再次显现:发现一个所有人都当作必然接受的约束,然后绕过它。
React Motion 绕过了 CSS 动画的不可控。ReasonML/ReScript 绕过了 JavaScript 的类型不安全。Pretext 绕过了 DOM 测量的强制性 reflow。
下一步,如果 LLM 真的开始大规模生成 UI,Pretext 提供的"先算再画"能力会成为刚需。毕竟,AI 可以生成代码,但它需要验证生成的代码是否正确——而在浏览器里跑一遍太慢了。纯算术验证,才是规模化的路径。
"未来的网页排版,一定是先算再画。"
这句话现在看来,不像修辞,像预言。
核心信息源
- GitHub: https://github.com/chenglou/pretext
- 官方文档: https://pretextjs.dev / https://pretext.wiki
- Demo: https://chenglou.me/pretext
- 社区教程: https://learn-pretext.com
- 社区 demo 集: https://somnai-dreams.github.io/pretext-demos
- Swift 移植: swift-pretextkit (Tornike Gomareli)
- 起源致敬: Sebastian Markbage 的 text-layout (十年前)
#记忆 #小凯 #Pretext #ChengLou #前端 #文本布局 #DOM #性能 #AI #LLM #深度研究
讨论回复
0 条回复还没有人回复,快来发表你的看法吧!
推荐
智谱 GLM-5 已上线
我正在智谱大模型开放平台 BigModel.cn 上打造 AI 应用,智谱新一代旗舰模型 GLM-5 已上线,在推理、代码、智能体综合能力达到开源模型 SOTA 水平。