# 第二章:先看懂 prepare 与 layout 的分工 ### 2.1 同一份输入,有不同的变化频率 聊天消息的文字和字体可能很久不变,但容器宽度会在拖动窗口时连续变化。把所有工作绑在一个函数里,每次 resize 都会重复做许多相同的事情。 Pretext 将它拆成两步: ```js import { prepare, layout } from '@chenglou/pretext' const prepared = prepare('Hello 世界,今天一起读源码。', '16px Arial') const narrow = layout(prepared, 200, 24) const wide = layout(prepared, 400, 24) ``` 这里 `200`、`400` 是内容宽度,`24` 是调用者指定的每行高度,单位都是 CSS 像素。 | 阶段 | 输入 | 主要工作 | 什么时候重做 | | --- | --- | --- | --- | | `prepare()` | 文字、字体、选项及语言环境 | 分析断行机会、测量、记录修正数据 | 相关输入变化 | | `layout()` | prepared、宽度、行高 | 根据已知宽度决定换行并计数 | 容器宽度或行高变化 | prepared 可以理解为“已经整理好的排版材料”。它与容器宽度无关,但并不包含任意宽度下现成的布局答案。 ### 2.2 沿源码走一遍 打开 [`src/layout.ts`](https://github.com/chenglou/pretext/blob/0d102a5cd933611e86a7e60bda7b4baa5995c6b0/src/layout.ts),定位 `prepareInternal()`: ```text 读取引擎规则与语言 → analyzeText(...) → measureAnalysis(...) → prepared handle ``` 再定位 `layout()`,其核心逻辑是: ```js const lineCount = countPreparedLines(internal, normalizedWidth) return { lineCount, height: lineCount * lineHeight } ``` 这是省略类型与内部转换的源码摘义。它没有逐行调用 Canvas,也没有先创建 DOM 再读取高度。 ### 2.3 一段文字被布局多次,省下多少工作 用 `P` 表示准备成本,用 `L` 表示一次布局成本,一共布局 `k` 次: ```text 复用 prepared:P + k × L 每次重新 prepare:k × (P + L) ``` 两种写法相差 `(k - 1) × P`。这解释了为什么使用方式会直接决定收益:在 resize 回调中重新 prepare,会让原本可以省掉的分析工作再次发生。 另外,重复 prepare 可能命中测量缓存,但仍要分析文本、组装新的 handle。它依然比直接复用已有 handle 做得更多。 本章检查:`layout()` 是 O(1) 吗?不是。它通常还要扫描分段。快来自减少每一步要做的工作,而不是完全不看文本。