# EPUB 渲染管线详解
微信读书 (WeRead) 的 EPUB 渲染管线将原始 XHTML 文件转换为可分页、可交互的阅读视图。本文档完整描述从 EPUB 文件到屏幕像素的每一步。
---
## 1. 管线总览
```
EPUB 文件 (.epub)
|
v
[WREpubParser] 解析 EPUB 结构
|
v
[WREpubTypesetter] XHTML → NSAttributedString
| (CSS 级联 + HTML 解析 + 后处理)
v
[WRCoreTextLayouter] NSAttributedString → 分页布局
| (CTTypesetter + 分页算法)
v
[WRCoreTextLayoutFrame] 单页布局帧
| (CTFrame + 行提取 + 避免断页)
v
[WRPageView] 渲染到屏幕
| (CGContext 绘制 + 图片 + 装饰)
v
屏幕像素
```
---
## 2. 阶段一: EPUB 解析 (WREpubParser)
### 2.1 输入
EPUB 文件路径 + WRBook 模型对象
### 2.2 解析步骤
```
1. parseContainerXML
- 读取 META-INF/container.xml
- 提取 OPF 文件路径 (rootfile full-path)
2. parseOPFAtRelativePath
- 解析 content.opf
- 提取 manifest (id → href 映射)
- 提取 spine (阅读顺序 idref 列表)
- 提取 metadata (书名、标识符等)
3. parseNCX
- 解析 toc.ncx (目录)
- 提取 navPoint 树 (id, label, src, playOrder)
4. _buildChapterList
- 将 spine idref 映射到 manifest href
- 生成 chapters 数组 [{id, href, mediaType, fullPath}]
5. _buildResourceMap
- 构建资源路径映射 (href → 绝对路径)
- 同时索引文件名用于快速查找
```
### 2.3 输出
- chapters 数组(有序章节列表)
- resourceMap 字典(资源路径映射)
- WRBook 元数据更新
---
## 3. 阶段二: CSS 级联 (WREpubTypesetter)
### 3.1 CSS 加载顺序
```
Layer 1: default.css (App Bundle)
↓ 覆盖
Layer 2: replace.css (App Bundle)
↓ 覆盖
Layer 3: dark.css (App Bundle, 仅暗色模式)
↓ 覆盖
Layer 4: EPUB 嵌入 CSS (书籍自带)
↓ 覆盖
Layer 5: 用户设置 CSS (运行时生成)
```
### 3.2 各层内容
**Layer 1 - default.css**:
```css
body { font-family: "PingFang SC", sans-serif; margin: 0; padding: 10px 15px; }
p { margin-top: 0.5em; margin-bottom: 0.5em; }
h1 { font-size: 1.8em; font-weight: bold; }
h2 { font-size: 1.5em; font-weight: bold; }
h3 { font-size: 1.3em; font-weight: bold; }
ul, ol { padding-left: 1.5em; }
blockquote { margin-left: 1em; font-style: italic; }
```
**Layer 2 - replace.css**:
```css
h1, h2, h3 { font-family: "Source Han Serif CN", serif; }
pre, code { font-family: "Menlo", monospace; }
img.bodyPic { wr-vertical-center-style: 2; max-width: 100%; }
.conQuot { /* 引用块样式 */ }
```
**Layer 3 - dark.css**:
```css
body { background-color: #1a1a1a; color: #cccccc; }
a { color: #6eaad7; }
img { filter: brightness(0.85); }
```
**Layer 5 - 用户设置 CSS** (动态生成):
```css
body { font-size: 18px; line-height: 1.8; font-family: "PingFang SC", sans-serif; }
body { background-color: #f5f0e8; } /* 护眼模式 */
p { text-indent: 2em; } /* 首行缩进 */
```
### 3.3 级联合并
使用 DTCSSStylesheet.mergeStylesheet: 方法按顺序合并,后加载的覆盖先前的同名规则。
---
## 4. 阶段三: HTML 解析 (DTHTMLAttributedStringBuilder)
### 4.1 SAX 解析流程
```
XHTML 数据
|
v
DTHTMLParser (SAX)
|
├── didStartElement: → 创建 DTHTMLElement 节点
| 应用 CSS 样式
| 处理自定义属性
|
├── foundCharacters: → 累积文本到当前元素
|
├── foundCDATA: → 处理 CDATA 内容
|
└── didEndElement: → 弹出元素栈
调用 interpretAttributes
插入分页标记
```
### 4.2 元素处理
每个 HTML 标签被转换为 DTHTMLElement 节点:
```
DTHTMLElement
├── tagName: "p" / "div" / "img" / ...
├── classNames: ["bodyPic", "conQuot"]
├── fontDescriptor: 字体描述符
├── paragraphStyle: 段落样式
├── textColor / backgroundColor
├── children: [DTHTMLElement]
├── textAttachment (图片)
├── linkURL (链接)
├── [WeRead 扩展]
│ ├── verticalCenterStyle
│ ├── pageRelate
│ ├── shouldAvoidPageBreakInside
│ ├── pageBreakAfter / pageBreakBefore
│ ├── pageBackgroundColor
│ └── pageBackgroundImage
```
### 4.3 样式应用顺序
```
1. CSS 样式表规则 (class, id, tag 选择器)
2. 内联 style="" 属性
3. WeRead 自定义 CSS 属性
4. 元素默认样式 (基于标签名)
```
### 4.4 后处理 (_WRPostProcessElementTree)
在元素树转为 NSAttributedString 之前,执行 WeRead 特有的后处理:
**图片处理**:
- 设置最大显示尺寸 (1080x1920)
- 超限图片按比例缩放
- 添加 .bodyPic CSS 类
- 设置 wr-vertical-center-style: 2
**链接处理**:
- 添加下划线样式
- 存储链接 URL 到自定义属性
**自定义属性处理**:
- wr-vertical-center-style → DTHTMLVerticalCenterAttribute
- weread-page-relate → DTPageRelateAttribute
---
## 5. 阶段四: NSAttributedString 生成
### 5.1 递归转换
```
DTHTMLElement.attributedString
|
├── 处理 void 元素 (br → "\n", img → attachment, hr → "\n")
|
├── 插入 pageBreakBefore 标记 (如果需要)
|
├── 转换文本内容
| ├── 应用 CTFont (从 fontDescriptor)
| ├── 应用前景色 (kCTForegroundColorAttributeName)
| ├── 应用背景色 (DTBackgroundColor)
| └── 应用链接 (DTLink)
|
├── 递归处理子元素
|
├── 应用段落样式 (kCTParagraphStyleAttributeName)
|
├── 插入 pageBreakAfter 标记 (如果需要)
|
└── 应用 WeRead 页面属性
├── DTHTMLVerticalCenterAttribute
├── DTPageRelateAttribute
├── DTPageBreakInsideAvoidAttribute
├── DTPageBackgroundColorAttribute
└── DTPageBackgroundImageAttribute
```
### 5.2 输出
一个 NSAttributedString,包含:
- 标准 CoreText 属性 (字体、颜色、段落样式)
- DTCoreText 标准属性 (链接、附件、列表)
- WeRead 自定义属性 (分页、居中、背景)
---
## 6. 阶段五: 分页布局 (WRCoreTextLayouter)
### 6.1 Typesetter 创建
```
NSAttributedString
|
v
CTTypesetterCreateWithAttributedString
|
v
CTTypesetter (内部缓存字形分析结果)
```
### 6.2 分页算法
```
输入: NSAttributedString + pageSize
1. 计算可用宽度 = pageSize.width - edgeInsets.left - edgeInsets.right
2. 初始化 currentIndex = 0
3. 循环:
a. lineBreakIndex = CTTypesetterSuggestLineBreak(typesetter, currentIndex, usableWidth)
b. 如果 lineBreakIndex <= 0, 退出循环
c. 创建 WRCoreTextLayoutFrame(range: currentIndex.. trialCharacterLimit:
从限制位置向前搜索段落边界 (\n 或 U+2029)
截取子串
追加 "\n\n...\n\n" 指示器
```
### 10.3 附件注入
```
如果 insertArticleToolAttachment:
追加 NSTextAttachment {type: "articleTool"}
如果 insertBookChapterToolAttachment:
追加 NSTextAttachment {type: "bookChapterTool"}
如果 insertRecommendView:
追加 NSTextAttachment {type: "recommendView"}
```
### 10.4 页面背景生成
```
pageBackgroundImageAtRange:themeBgColor:
检查是否为章节开头页
创建 UIGraphicsImageContext
填充主题背景色
如果是章节开头: 绘制装饰边框
否则: 绘制边距参考线
缓存结果
```
### 10.5 图片缩放
```
resizedImageForImagePath:rect:position:sizePattern:darkMode:themeBgColor:
根据 sizePattern 计算目标尺寸:
"full" → 全宽
"half" → 半宽
"third" → 1/3 宽
"quarter" → 1/4 宽
如果 darkMode: 混合背景色
高质量插值缩放
缓存结果
```
---
## 11. 性能优化
### 11.1 缓存策略
- **布局帧缓存**: NSCache, 最多 20 个 (DTCoreTextLayouter)
- **图片缓存**: NSCache, 最多 50 个 (WRCoreTextLayouter)
- **页面背景缓存**: NSCache, 按 range + 颜色键 (WRCoreTextLayouter)
- **内容高度缓存**: 布局帧级别 (DTCoreTextLayoutFrame)
### 11.2 懒加载
- CTTypesetter 按需创建 (createTypesetter)
- CTFramesetter 按需创建 (createFramesetter)
- 行数据按需提取 (extractLines)
- 字形数据按需提取 (extractGlyphs)
### 11.3 线程安全
所有核心类使用 NSLock 保护:
- DTCoreTextLayouter._lock
- DTCoreTextLayoutFrame._lock
- DTCoreTextLayoutLine._lock
- DTCoreTextGlyphRun._lock
- WRCoreTextLayouter._layoutLock
- WRCoreTextLayoutFrame._frameLock
### 11.4 增量更新
- attributedString 变化时标记 typesetterDirty
- 仅在下次访问时重建 typesetter
- 布局帧缓存自动失效
---
## 12. 数据流总结
```
EPUB 文件
│
├─ container.xml → OPF 路径
├─ content.opf → manifest + spine + metadata
├─ toc.ncx → 目录树
└─ *.xhtml → 章节内容
│
▼
XHTML 字符串
│ (繁简转换)
▼
CSS 级联合并
│ (5 层合并)
▼
DTHTMLAttributedStringBuilder
│ (SAX 解析 + DOM 构建 + 样式应用)
▼
DTHTMLElement 树
│ (后处理: 图片/链接/自定义属性)
▼
NSAttributedString
│ (包含标准 + 自定义属性)
▼
CTTypesetter
│ (字形分析 + 行断点计算)
▼
WRCoreTextLayoutFrame[]
│ (每帧 = 一页, 含 avoidPageBreakInside)
▼
CGContext 绘制
│ (文本 + 图片 + 装饰)
▼
屏幕像素
```