Files
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.6 KiB

第五章:换行算法为什么能变成数值循环

5.1 用一组虚构宽度手算

下面只是教学数据,不代表某种真实字体:

分段:hello   空格   world   空格   again
宽度:  40      4      45      4      42
可用宽度:90

从左到右累计:

操作 当前宽度 决定
放入 hello 40 留在第一行
放入空格 44 继续
放入 world 89 继续
尝试后面的空格 93 第一行结束,行尾空格按规则处理
放入 again 42 第二行

最终两行;若行高是 24px,高度就是 48px。

5.2 核心思路简单,实现处理边界

line-break.ts 的 countPreparedLines()为普通文本专门实现数值循环。以下是教学版,不是可替代源码的完整算法:

for (const segment of segments) {
  if (currentWidth + segment.width <= availableWidth) {
    currentWidth += segment.width
  } else {
    finishCurrentLine()
    startNextLineWith(segment)
  }
}

真实代码还处理行首空白、行尾修正、零宽空格、放不下一整个分段时的字素断行。关键是这些判断都基于提前准备的数据,不再重新问 Canvas 这个词有多宽。

5.3 为什么需要简单路径和完整路径

准备阶段设置两个相关标志:

  • simpleLineWalkFastPath:适合简单逐行遍历。
  • simpleLineCountFastPath:适合简单行数计算;某些边界仍可能回退处理。

普通文本、普通空格等可以走简单路径。保留空白、制表符和软连字符等内容需要更完整的规则,进入 walkPreparedComplexLines() 等路径。

源码在 countPreparedLines() 前的注释记录:完整 walker 每个分段的成本约为简单路径的 3~5 倍。这是项目记录的局部测量依据,不是所有设备上的固定倍数。 分开实现的目的,是让常见输入少支付复杂状态管理的成本。

5.4 高度计算为何不经过“每行对象”

仅求高度时,简单计数循环使用局部数值变量,直接累加行数,没有每行游标对象和逐行回调。layout() 最后仍会返回一个结果对象,所以准确说法是“避免每行分配”,而不是“零分配”。

这也是为什么一个看似更通用、更加抽象的 walker,未必适合最高频的行数路径。

本章检查:为什么文本支持更多规则,不一定让普通英文同样变慢?因为准备阶段能判断适用路径,复杂处理不必全部进入普通文本的热循环。