Pretext:用 Canvas 和 OpenType 数据替代 DOM 测量,解决多语言文本布局的确定性难题
Pretext 使用 Canvas、DOM 和 OpenType 数据来测量和布局 Web 应用程序中的多语言文本,以生成可预测的换行符和尺寸。
秒懂
- 它是什么?
- Pretext 是一个纯 TypeScript 库,通过 Canvas 和 OpenType 数据测量多语言文本,避免 DOM reflow。它提供两套 API,分别用于段落高度计算和手动逐行布局,适合虚拟滚动、动态排版和开发期验证。
- 适合谁用?
- Pretext 适合需要精确多语言文本测量、且愿意放弃 DOM 测量便利性的前端工程师,尤其是虚拟滚动、Canvas/SVG 渲染和 AI 辅助开发验证场景。不适合依赖 CSS 复杂排版(如浮动、多列、ruby 注音)或需要自动断字的项目,因为其 rich-inline 助手仅支持 white-space: normal,且自动断字未内置。
- 能商用吗?
- 可以。MIT 是宽松许可证:你可以使用、修改并销售基于它的软件,只需保留版权和许可证声明。
- 还在维护吗?
- 在维护。仓库在最近一天内有新的提交。
- 用什么语言写的?
- 主要是 TypeScript(依据 GitHub 的语言统计)。
以上回答依据项目的 GitHub 数据(最近同步于 2026年9月15日)和我们的分析,不构成法律意见。
开源项目深度解析
为什么需要绕开 DOM 测量
浏览器里测量文本高度和宽度,最常见的做法是 getBoundingClientRect 或 offsetHeight。这些 API 会触发布局 reflow,而 reflow 是浏览器最昂贵的操作之一。Pretext 的出发点很直接:用 Canvas 的字体引擎作为测量基准,自己实现分段、换行和尺寸计算,从而完全避免 DOM 布局。这个思路对虚拟滚动尤其有价值。虚拟列表需要知道每个元素的高度才能计算滚动条和渲染窗口,通常的做法是估算或缓存,Pretext 提供的是精确值。另一个场景是开发期验证,比如检查按钮文字是否换行,这可以在浏览器外完成,对 AI 辅助编码也有意义。Pretext 的目标用户是那些被 DOM 测量性能困扰,或者需要在 Canvas、SVG、WebGL 中渲染文本的开发者。
两阶段 API:prepare 与 layout 的分工
Pretext 把文本处理拆成两个阶段。prepare() 做一次性工作:归一化空白、分段、应用胶合规则、用 Canvas 测量每个分段,返回一个不透明句柄。layout() 是热路径,只做纯算术,基于缓存的宽度计算高度和行数。这个设计很关键,因为 resize 时只需要重新调用 layout(),不必重复 prepare()。文档明确警告不要对相同文本和配置重复运行 prepare(),否则就失去了预计算的意义。这种两阶段模型适合频繁调整宽度的场景,比如响应式布局或窗口缩放。但要注意,prepare() 的测量依赖 Canvas 上下文,而 Canvas 的字体加载状态会影响结果,如果字体未加载完成,测量可能不准确。
手动布局 API:逐行控制与动态宽度
第二套 API 提供更细粒度的控制。prepareWithSegments() 返回包含分段信息的结构,layoutWithLines() 给出所有行,measureLineStats() 和 walkLineRanges() 提供行数和宽度统计,layoutNextLineRange() 允许逐行路由文本,当宽度随位置变化时特别有用。README 里的浮动图片示例展示了这一点:图片旁边的行宽较窄,下方恢复全宽。materializeLineRange() 把范围转回完整字符串。这些 API 适合 Canvas 或 SVG 渲染,因为你可以直接拿到每行的文本和位置。一个有意思的功能是 walkLineRanges() 能找出最宽的行,从而得到文本的收缩包装宽度,文档称这是 Web 上一直缺失的多行 shrink-wrap 能力。
连字符与 rich-inline 助手的边界
Pretext 不内置自动断字。文档建议在 prepare() 之前手动插入软连字符,Pretext 会把它们当作可选断点,未选中的软连字符保持不可见,选中的则显示为尾部连字符。对于混合语言或用户生成内容,文档建议保守的、基于区域设置的插入,而不是激进的模式断字。此外,rich-inline 助手只支持 white-space: normal,且仅处理内联流,不支持嵌套标记树或通用 CSS 内联格式化。这意味着如果你需要处理 pre-wrap 或复杂的富文本结构,这个助手不适用。这些限制是刻意为之,但你需要评估是否匹配你的需求。
运行方式与配置项
安装很简单:npm install @chenglou/pretext。演示需要克隆仓库,用 bun install 安装依赖,然后 bun start,在浏览器打开 /demos/index。Windows 用户使用 bun run start:windows。prepare() 的选项包括 whiteSpace: 'pre-wrap'(模拟 textarea 中的空格、tab 和换行)、wordBreak: 'keep-all'(对应 CSS 的 word-break: keep-all)和 letterSpacing(像素值,需与 CSS 的 letter-spacing 同步)。font 参数使用 Canvas 字体格式,例如 '16px Inter'。注意,这些选项必须与 CSS 中的实际样式保持一致,否则测量结果会偏离实际渲染。
局限性与适用边界
Pretext 的测量基于 Canvas 字体引擎,这意味着它依赖浏览器自身的字体渲染。如果字体加载延迟或失败,测量结果会不准确。它也不支持自动断字,对于需要连字符的文本,你得自己实现插入逻辑。rich-inline 助手仅支持 white-space: normal,对于需要保留空白符的场景无能为力。此外,文档提到服务器端渲染是“soon”,目前尚未实现,所以纯服务端测量还不可用。对于复杂的 CSS 布局,如浮动、多列或 ruby 注音,Pretext 不提供支持,你需要自己处理这些情况。
替代方案:与 CSS 原生测量和 HarfBuzz 的比较
最直接的替代方案是继续使用 DOM 测量,比如 getBoundingClientRect。区别在于,DOM 测量依赖浏览器的完整布局引擎,能处理所有 CSS 特性,但代价是 reflow 性能。Pretext 绕开 DOM,但只覆盖它自己实现的文本布局子集。另一个替代是使用 HarfBuzz 等底层排版引擎,通过 WebAssembly 在浏览器中运行,提供更底层的 shaping 控制,但集成复杂度高,且与浏览器的字体渲染可能不一致。Pretext 的优势在于它使用 Canvas 作为 ground truth,与浏览器渲染结果更接近,但牺牲了 HarfBuzz 的跨平台一致性。选择取决于你的性能需求和对布局控制的精细程度。
编辑结论
Pretext 适合需要精确多语言文本测量、且愿意放弃 DOM 测量便利性的前端工程师,尤其是虚拟滚动、Canvas/SVG 渲染和 AI 辅助开发验证场景。不适合依赖 CSS 复杂排版(如浮动、多列、ruby 注音)或需要自动断字的项目,因为其 rich-inline 助手仅支持 white-space: normal,且自动断字未内置。采用前应验证其测量结果与目标浏览器字体引擎的一致性,特别是中文、阿拉伯文和 emoji 混合文本,并确认你的字体加载策略能保证 Canvas 测量时字体已就绪。MIT 许可证允许自由使用,但需自行处理字体子集和加载时序问题。
社区笔记