Files
endorphin-tabularium-docs/content/docs/pretext/04-chapter.md
T
codex-bot 2b3b3a1ae4
Verify and deploy Tabularium / verify-and-deploy (push) Successful in 5m47s
Expand document collections with Pretext tutorial and simplify library navigation
2026-10-28 13:42:51 +08:00

2.9 KiB

第四章:测量缓存如何减少浏览器工作

4.1 Canvas 提供的是字体测量能力

浏览器已经知道如何选择字体、塑造字形、计算宽度。Pretext 通过:

context.font = '16px Arial'
const width = context.measureText('hello').width

获得字体事实。它优先创建 OffscreenCanvas;没有该能力时,创建不挂载到文档的普通 Canvas。

OffscreenCanvas 这个名字不表示测量会自动进入另一个线程。代码在哪里调用,工作就在哪里执行;想在 worker 中使用,需要应用自己安排,还有第八章的准确性限制。

4.2 两层复用各解决什么问题

第一层是应用保留 prepared。第二层在库内部,以字体等测量条件组织缓存,再按分段文本查找:

字体 A 的缓存
  'hello' → 测量结果
  'world' → 测量结果

字体 B 的缓存
  'hello' → 另一份测量结果

在 measurement.ts 搜索 getSegmentMetrics(),会看到这一核心表达式:

measurement.metrics.get(seg) ?? addMetrics(...)

命中就读取结果;未命中才调用 Canvas 并存入缓存。相同词语出现在不同消息里,也有机会复用,不要求整段消息完全相同。

实际缓存不只有一个数字。SegmentMetrics 还会保留拆词适配、emoji 统计或其他修正所需的信息。语言变化和字距造成的 shaping 模式变化也受到专门处理;不能把它简单描述成全局 (text, font) 永久不变的表。

4.3 命中缓存后,连字体设置都尽量省

getFontMeasurement() 先找到字体对应的数据;真正测量时才通过 getContext() 设置 Canvas 的字体和测量模式。

为什么把两者拆开?富文本可能反复查询许多 item 的字体,但这些 item 的文字全都命中缓存。既然无需测量,也就不必为了查询而触碰 Canvas。

源码同时照顾了字体后加载等浏览器行为,因此不是无条件地“字体字符串相同就永不赋值”。读这一段时应一起看注释,而不要只把它当成普通 setter 缓存。

4.4 prepared 与全局测量缓存的寿命不同

全局缓存为了复用测量,prepared 为了一段具体文本的后续布局。二者不应混为一谈。

MAX_CACHED_SEGMENTS = 1 << 18,即 262,144。所有字体缓存的分段数超过边界后,下一次准备开始时会清理测量缓存。它不是每次插入都逐出一个条目的 LRU,因此单次准备期间可能超过阈值。

已经创建的 handle 保留自己的布局数据,不会因为这次缓存清理突然失效。不过,如果实际字体或语言变了,旧 handle 也不会自动更新,仍需要重新准备。

本章检查:清理全局缓存会自动把旧 prepared 换成新字体宽度吗?不会。缓存清理和重新创建 handle 是两步。