# 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 绘制 │ (文本 + 图片 + 装饰) ▼ 屏幕像素 ```