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

55 lines
3.2 KiB
Markdown

# 第三章:分析阶段如何把文字变成可布局的分段
### 3.1 三个容易混淆的单位
| 名称 | 含义 | 例子 |
| --- | --- | --- |
| UTF-16 码元 | JavaScript 字符串索引使用的存储单位 | 一些 emoji 的 `length` 大于 1 |
| 字素簇,简称字素 | 用户通常感知的一个文字单位 | 字母加组合音标、由多个码点组成的 emoji |
| 分段 segment | Pretext 为测量与换行建立的单位 | 一个词、一段文本、空格或特殊控制段 |
分段并不总等于单词。字素也不总等于一个 JavaScript 字符。理解这两个区别,后面的超长单词断行和 emoji 处理才容易读懂。
### 3.2 先找哪里允许断行
对 `hello world`,可以先想象成:
```text
['hello', ' ', 'world']
```
这个示意足以解释普通英文,但真实的中文标点、日文、泰文、软连字符和不可断空格需要更多规则。
[`analysis.ts` 的 `analyzeText()`](https://github.com/chenglou/pretext/blob/0d102a5cd933611e86a7e60bda7b4baa5995c6b0/src/analysis.ts)先处理空白与源文本,再按引擎选择扫描器,最后形成分段及标志。其中主要的引擎对应关系是:
| 浏览器引擎 | 主要实现入口 |
| --- | --- |
| Blink,例如 Chrome | `line-breaks.ts` 中的 `getBlinkLineBreaks()` |
| WebKit,例如 Safari | `line-breaks.ts` 中的 `getWebKitLineBreaks()` |
| Gecko,例如 Firefox | `gecko-line-breaks.ts` 中的 `getGeckoLineBreaks()` |
选择由 `measurement.ts` 的 `getEngineProfile()` 完成,并缓存 profile。未知引擎采用 Blink 的模型。
### 3.3 表驱动扫描是什么意思
可以把扫描器想象成一个不断读取字符的机器:
```text
当前状态 + 当前字符类别 → 新状态 + 是否出现边界
```
字符属于哪一类、某种状态遇到某一类应该怎么走,都放在表里。运行时主要是读字符、查类别、查转换表、更新状态。
规则数据来自对应引擎及其 Unicode/ICU 数据,保存在 [`src/generated/`](https://github.com/chenglou/pretext/blob/0d102a5cd933611e86a7e60bda7b4baa5995c6b0/src/generated)。数据经过压缩;页面按需解开自己的引擎所用的表。这样能把复杂规则变成较直接的扫描,但也增加了包体积与初始化成本。这是有代价的工程选择。
[`graphemes.ts` 的 `findGraphemeEnds()`](https://github.com/chenglou/pretext/blob/0d102a5cd933611e86a7e60bda7b4baa5995c6b0/src/graphemes.ts)也采用类似方式寻找字素边界。这个版本不是对所有文字都调用 `Intl.Segmenter`:词典分词主要留给泰语、老挝语、高棉语、缅甸语等特定文字的运行片段。
### 3.4 为什么空白处理要先做
`white-space: normal` 下,连续普通空格通常被折叠;`pre-wrap` 则保留空格、换行与制表符的相关行为。若测量的是原始字符串,绘制时浏览器却折叠了它,预测自然会错。
所以分析阶段必须建立与目标 CSS 相符的文本视图。这个版本对孤立 `\r` 有明确限制:`pre-wrap` 下绘制前也应把它规范成 `\n`,详见根目录 README。
本章检查:为什么不能直接 `text.split(' ')`?因为断行机会远不止空格,而且许多语言没有用空格分隔每一个词。