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 是两步。