ReadViewSDK/Doc/EPUBTextRendering_CODE_REFERENCE.md
shenlei d7fcda345d refactor: rename RDReaderView -> RDEpubReaderView, update pod config and docs
- Rename source module from RDReaderView to RDEpubReaderView
- Move all source files from Sources/RDReaderView/ to Sources/RDEpubReaderView/
- Update podspec: RDReaderView.podspec -> RDEpubReaderView.podspec
- Update Podfile, demo project, and CocoaPods config for new pod name
- Delete old RDReaderView pod support files from ReadViewDemo/Pods
- Add new RDEpubReaderView pod support files
- Update documentation (API ref, architecture, UML, conventions, etc.)
- Add FixedLayoutRotationTests
- Update .gitignore: exclude .DS_Store, manual unpack backups, _ssoft-output
2026-07-10 19:44:53 +09:00

522 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# EPUBTextRendering 模块代码级参考文档
> 最后更新2026-06-18
---
## 1. 模块概述
`EPUBTextRendering` 是 ReadViewSDK 的**文本渲染层**,负责将 EPUB HTML 内容转换为原生 `NSAttributedString`,执行排版分页,并构建全书文本模型。它位于 EPUBCore 之上、EPUBUI 之下,是 `textReflowable` 阅读配置文件的核心引擎。
**文件清单(~30 个 Swift 文件):**
| 子系统 | 核心文件 | 职责 |
|--------|----------|------|
| **排版管线** | `Typesetter/RDEPUBTypesettingPipeline.swift` | HTML → 标记化 HTML 的多阶段管线 |
| **文本渲染** | `RDEPUBTextRenderer.swift`, `RDEPUBDTCoreTextRenderer.swift` | HTML → NSAttributedString 转换 |
| **分页引擎** | `Pagination/*.swift` | CoreText 排版、页帧计算、分页策略 |
| **构建管线** | `BuildPipeline/*.swift` | 全书构建(渲染 → 分页 → 缓存) |
| **搜索** | `RDEPUBTextSearchEngine.swift` | 基于原生文本的全文搜索 |
| **索引** | `RDEPUBTextIndexTable.swift` | 全书字符偏移索引、CFI 映射 |
| **位置转换** | `RDEPUBTextPositionConverter.swift` | 字符偏移 ↔ 页码转换 |
| **章节数据** | `RDEPUBChapterData.swift` | 章节级数据访问封装 |
---
## 2. 排版管线Typesetter
### 2.1 RDEPUBTextTypesettingPipeline
**文件:** `Typesetter/RDEPUBTypesettingPipeline.swift`
多阶段 HTML 处理管线,将原始 HTML 转换为可渲染的标记化 HTML。
```swift
struct RDEPUBTextTypesetterPipeline {
func makeRequest(from input: RDEPUBTypesettingInput) -> RDEPUBTypesettingOutput
}
```
**管线阶段:**
```
原始 HTML
RDEPUBHTMLNormalizer → 清理 HTML移除脚本、修复标签
RDEPUBSemanticMarkerInjector → 注入语义标记(段落、列表、表格等)
RDEPUBCFIMarkerInjector → 注入 CFI 定位标记
RDEPUBStyleSheetComposer → 合并样式表(内联 CSS + 兼容性处理)
RDEPUBFontNormalizer → 注册嵌入字体、字体回退
RDEPUBFragmentMarkerInjector → 注入片段标记(用于锚点定位)
标记化 HTML + 诊断报告
```
### 2.2 管线输入/输出
```swift
struct RDEPUBTypesettingInput {
var href: String // 章节 href
var spineIndex: Int?
var title: String // 章节标题
var rawHTML: String // 原始 HTML
var baseURL: URL? // 资源基础 URL
var style: RDEPUBTextRenderStyle // 渲染样式
var resourceResolver: RDEPUBResourceResolver?
var contentLanguageCode: String?
var pageSize: CGSize?
var layoutConfig: RDEPUBTextLayoutConfig?
}
struct RDEPUBTypesettingOutput {
var request: RDEPUBTextChapterRenderRequest // 渲染请求
var diagnostics: [RDEPUBTextResourceReferenceDiagnostic] // 资源引用诊断
var styleCompatibilityReport: RDEPUBCSSCompatibilityReport // CSS 兼容性报告
}
```
### 2.3 各阶段处理器
| 处理器 | 文件 | 职责 |
|--------|------|------|
| `RDEPUBHTMLNormalizer` | `RDEPUBHTMLNormalizer.swift` | 清理 HTML移除 `<script>`、修复未闭合标签、规范化编码 |
| `RDEPUBSemanticMarkerInjector` | `RDEPUBSemanticMarkerInjector.swift` | 为块级元素注入 `data-rd-block-kind` 属性 |
| `RDEPUBCFIMarkerInjector` | `RDEPUBCFIMarkerInjector.swift` | 为文本节点注入 `data-rd-cfi` 属性 |
| `RDEPUBStyleSheetComposer` | `RDEPUBStyleSheetComposer.swift` | 合并 EPUB 原始 CSS + SDK 样式 + 兼容性补丁 |
| `RDEPUBFontNormalizer` | `RDEPUBFontNormalizer.swift` | 检测并注册 `@font-face` 嵌入字体 |
| `RDEPUBFragmentMarkerInjector` | `RDEPUBFragmentMarkerInjector.swift` | 为 `id` 属性注入片段偏移标记 |
| `RDEPUBAttachmentNormalizer` | `RDEPUBAttachmentNormalizer.swift` | 规范化 `<img>`、`<svg>` 等附件元素 |
| `RDEPUBRenderDiagnosticsCollector` | `RDEPUBRenderDiagnosticsCollector.swift` | 收集渲染诊断信息(缺失资源、不支持的特性) |
### 2.4 CSS 兼容性层
**文件:** `Typesetter/Compatibility/`
| 文件 | 职责 |
|------|------|
| `RDEPUBCSSCompatibilityLayer.swift` | CSS 兼容性补丁webkit 前缀、不支持属性替换) |
| `RDEPUBFontFallbackResolver.swift` | 字体回退链解析 |
| `RDEPUBStyleCompatibilityModels.swift` | 兼容性报告模型 |
---
## 3. 文本渲染器
### 3.1 RDEPUBTextRenderer协议
**文件:** `RDEPUBTextRenderer.swift`
```swift
public protocol RDEPUBTextRenderer {
/// 将 HTML 渲染为 NSAttributedString
func render(html: String, baseURL: URL?, style: RDEPUBTextRenderStyle) -> NSAttributedString
}
```
### 3.2 RDEPUBDTCoreTextRenderer
**文件:** `RDEPUBDTCoreTextRenderer.swift`
基于 DTCoreText 的渲染器实现,将 HTML 转换为 `NSAttributedString`
```swift
public final class RDEPUBDTCoreTextRenderer: RDEPUBTextRenderer {
public init()
public func render(html: String, baseURL: URL?, style: RDEPUBTextRenderStyle) -> NSAttributedString
}
```
### 3.3 渲染样式
```swift
public struct RDEPUBTextRenderStyle {
public var font: UIFont // 字体
public var lineSpacing: CGFloat // 行间距
public var textColor: UIColor? // 文字颜色
public var backgroundColor: UIColor? // 背景颜色
}
```
### 3.4 NSAttributedString 扩展键
```swift
extension NSAttributedString.Key {
static let rdPageBlockRange // 块级元素范围
static let rdPageBlockIndex // 块索引
static let rdPageFragmentID // 片段 ID
static let rdPageAttachmentKind // 附件类型
static let rdPageBlockKind // 块类型paragraph/list/table/code/blockquote/attachment/generic
static let rdPageSemanticHints // 语义提示avoidPageBreakInside/keepWithNext/pageBreakBefore/After
static let rdPageAttachmentPlacement // 附件放置方式inline/baseline/centered
}
```
---
## 4. 分页引擎
### 4.1 RDEPUBTextLayouter
**文件:** `Pagination/RDEPUBTextLayouter.swift`
分页的核心入口,组合 `RDEPUBCoreTextPageFrameFactory``RDEPUBChapterPageCounter`
```swift
struct RDEPUBTextLayouter {
init(attributedString: NSAttributedString, pageSize: CGSize, config: RDEPUBTextLayoutConfig = .default)
func layoutFrames(fragmentOffsets: [String: Int] = [:]) -> [RDEPUBTextLayoutFrame]
}
```
### 4.2 RDEPUBTextLayoutConfig
```swift
public struct RDEPUBTextLayoutConfig: Equatable {
public var frameWidth: CGFloat // 帧宽度
public var frameHeight: CGFloat // 帧高度
public var edgeInsets: UIEdgeInsets // 内边距
public var numberOfColumns: Int // 列数
public var columnGap: CGFloat // 列间距
public var avoidOrphans: Bool // 避免孤行
public var avoidWidows: Bool // 避免寡行
public var avoidPageBreakInsideEnabled: Bool // 避免块内分页
public var hyphenation: Bool // 连字符断词
public static let `default`: RDEPUBTextLayoutConfig
}
```
### 4.3 RDEPUBCoreTextPageFrameFactory
**文件:** `Pagination/RDEPUBCoreTextPageFrameFactory.swift`
使用 CoreText 排版引擎,将 `NSAttributedString` 排列到指定尺寸的帧中。
```swift
struct RDEPUBCoreTextPageFrameFactory {
init(attributedString: NSAttributedString, pageSize: CGSize, config: RDEPUBTextLayoutConfig)
func makeFrames(fragmentOffsets: [String: Int] = [:]) -> [RDEPUBTextLayoutFrame]
}
```
### 4.4 RDEPUBChapterPageCounter
**文件:** `Pagination/RDEPUBChapterPageCounter.swift`
基于 `RDEPUBCoreTextPageFrameFactory` 计算页范围。
```swift
struct RDEPUBChapterPageCounter {
init(factory: RDEPUBCoreTextPageFrameFactory)
func layoutFrames(fragmentOffsets: [String: Int] = [:]) -> [RDEPUBTextLayoutFrame]
}
```
### 4.5 RDEPUBTextLayoutFrame
**文件:** `Pagination/RDEPUBTextLayoutFrame.swift`
表示一个排版帧(一页的内容范围)。
```swift
public struct RDEPUBTextLayoutFrame {
public let range: NSRange // 字符范围
public let frameIndex: Int // 帧索引
public let diagnostics: [String] // 诊断信息
}
```
### 4.6 RDEPUBPageBreakPolicy
**文件:** `Pagination/RDEPUBPageBreakPolicy.swift`
分页策略,决定在何处断页。
```swift
struct RDEPUBPageBreakPolicy {
/// 检查指定位置是否允许分页
func canBreak(at offset: Int, in attributedString: NSAttributedString) -> Bool
}
```
---
## 5. 构建管线
### 5.1 RDEPUBTextBookBuilder
**文件:** `BuildPipeline/RDEPUBTextBookBuilder.swift`
全书构建的核心类,串联渲染、分页、缓存和诊断。
```swift
public final class RDEPUBTextBookBuilder {
public init(renderer: RDEPUBTextRenderer, cache: RDEPUBTextBookCache?, layoutConfig: RDEPUBTextLayoutConfig)
/// 构建全书文本模型
public func build(
parser: RDEPUBParser,
publication: RDEPUBPublication,
pageSize: CGSize,
style: RDEPUBTextRenderStyle
) throws -> RDEPUBTextBook
// 诊断信息
public private(set) var lastBuildResourceDiagnostics: [RDEPUBTextResourceReferenceDiagnostic]
public private(set) var lastBuildPaginationDiagnostics: [RDEPUBTextChapterPaginationDiagnostic]
public private(set) var lastBuildPerformanceSamples: [RDEPUBTextPerformanceSample]
public private(set) var lastBuildCacheStats: (hits: Int, misses: Int)
}
```
**构建流程:**
```
RDEPUBParser + RDEPUBPublication
▼ 遍历 spinelinear 章节)
├── 检查缓存RDEPUBPaginationCacheCoordinator
│ ├── 命中 → 直接使用缓存的分页结果
│ └── 未命中 → 执行渲染 + 分页
├── RDEPUBChapterRenderPipeline渲染单章
│ └── RDEPUBTextTypesetterPipeline → RDEPUBTextRenderer
├── RDEPUBChapterPaginationPipeline分页单章
│ └── RDEPUBTextLayouter → [RDEPUBTextLayoutFrame]
├── RDEPUBChapterTailNormalizer章节尾部规范化
└── 组装 RDEPUBTextChapter + RDEPUBTextPage
RDEPUBTextBook全书模型
```
### 5.2 辅助组件
| 文件 | 职责 |
|------|------|
| `RDEPUBChapterRenderPipeline.swift` | 单章渲染管线 |
| `RDEPUBChapterPaginationPipeline.swift` | 单章分页管线 |
| `RDEPUBChapterTailNormalizer.swift` | 章节尾部空白处理 |
| `RDEPUBPaginationCacheCoordinator.swift` | 分页缓存协调 |
| `RDEPUBTextBookCache.swift` | 分页缓存存储 |
| `RDEPUBTextPerformanceSampler.swift` | 性能采样 |
| `RDEPUBBuildDiagnosticsReporter.swift` | 构建诊断报告 |
| `RDEPUBTextBuildPipelineInterfaces.swift` | 管线接口定义 |
---
## 6. 全书模型
### 6.1 RDEPUBTextBook
**文件:** `BuildPipeline/RDEPUBTextBookModels.swift`
全书文本模型,包含所有章节和页面。
```swift
public struct RDEPUBTextBook {
public var chapters: [RDEPUBTextChapter] // 所有章节
public var pages: [RDEPUBTextPage] // 所有页面(扁平化)
public let indexTable: RDEPUBTextIndexTable // 索引表
public var positionConverter: RDEPUBTextPositionConverter
// 章节查询
public func chapterData(for href: String) -> RDEPUBChapterData?
public func chapterData(forSpineIndex spineIndex: Int) -> RDEPUBChapterData?
public func chapterData(atChapterIndex index: Int) -> RDEPUBChapterData?
public func chapterData(forPageNumber pageNumber: Int) -> RDEPUBChapterData?
// 页码查询
public func page(at pageNumber: Int) -> RDEPUBTextPage?
public func pageNumber(for location: RDEPUBLocation, resolver: RDEPUBResourceResolver, bookIdentifier: String?) -> Int?
public func location(forPageNumber pageNumber: Int, bookIdentifier: String?) -> RDEPUBLocation?
}
```
### 6.2 RDEPUBTextChapter
```swift
public struct RDEPUBTextChapter: Equatable {
public var chapterIndex: Int // 章节索引
public var spineIndex: Int // spine 索引
public var href: String // 章节 href
public var title: String // 章节标题
public var attributedContent: NSAttributedString // 章节完整富文本
public var fragmentOffsets: [String: Int] // 片段 ID → 字符偏移
public var cfiMap: RDEPUBCFIMap? // CFI 映射
public var pageBreakReasons: [RDEPUBTextPageBreakReason] // 分页原因
public var pages: [RDEPUBTextPage] // 章节内页面
}
```
### 6.3 RDEPUBTextPage
```swift
public struct RDEPUBTextPage: Equatable {
public var absolutePageIndex: Int // 全书绝对页码
public var chapterIndex: Int // 章节索引
public var spineIndex: Int // spine 索引
public var href: String // 章节 href
public var chapterTitle: String // 章节标题
public var pageIndexInChapter: Int // 章节内页码
public var totalPagesInChapter: Int // 章节总页数
public var chapterContent: NSAttributedString // 章节完整内容
public var content: NSAttributedString // 页面内容(子范围)
public var contentRange: NSRange // 在章节内容中的范围
public var pageStartOffset: Int // 起始偏移
public var pageEndOffset: Int // 结束偏移
public var metadata: RDEPUBTextPageMetadata // 页元数据
}
```
---
## 7. 索引与位置转换
### 7.1 RDEPUBTextIndexTable
**文件:** `RDEPUBTextIndexTable.swift`
全书字符偏移索引,支持快速查找。
```swift
public struct RDEPUBTextIndexTable {
public let chapterStartOffsets: [Int] // 各章节起始偏移
public let chapterLengths: [Int] // 各章节长度
public let hrefToChapterIndex: [String: Int] // href → 章节索引
public let hrefToFileIndex: [String: Int] // href → 文件索引
public let fragmentOffsetsByHref: [String: [String: Int]] // 片段偏移
public let fileRowColumnMap: [Int: [RDEPUBRowColumnIndex]] // 行列映射
public let fileCFIMap: [Int: RDEPUBCFIMap] // CFI 映射
public var totalCharacterCount: Int // 总字符数
public init(chapters: [RDEPUBTextChapter])
}
```
### 7.2 RDEPUBTextPositionConverter
**文件:** `RDEPUBTextPositionConverter.swift`
字符偏移 ↔ 页码双向转换。
```swift
public struct RDEPUBTextPositionConverter {
init(book: RDEPUBTextBook)
/// 字符偏移 → 页码
public func pageNumber(for offset: Int) -> Int?
/// 页码 → 字符偏移范围
public func offsetRange(forPageNumber pageNumber: Int) -> NSRange?
}
```
### 7.3 RDEPUBChapterData
**文件:** `RDEPUBChapterData.swift`
章节级数据访问封装。
```swift
public struct RDEPUBChapterData {
public let chapter: RDEPUBTextChapter
public let indexTable: RDEPUBTextIndexTable
/// 位置 → 页码
public func pageNumber(for location: RDEPUBLocation) -> Int?
/// 页面 → 位置
public func location(for page: RDEPUBTextPage, bookIdentifier: String?) -> RDEPUBLocation?
/// 字符范围 → 范围锚点
public func rangeAnchor(for range: NSRange) -> RDEPUBRangeAnchor
}
```
---
## 8. 搜索引擎
**文件:** `RDEPUBTextSearchEngine.swift`
基于原生 `NSAttributedString` 的搜索引擎,比 HTML 搜索更高效。
```swift
final class RDEPUBTextSearchEngine: RDEPUBSearchEngine {
init(textBook: RDEPUBTextBook, publication: RDEPUBPublication)
func search(keyword: String) -> [RDEPUBSearchMatch]
}
```
搜索结果包含href、progression、previewText、rangeAnchor、cfi 等定位信息。
---
## 9. 纯文本构建器
**文件:** `RDEpubPlainTextBookBuilder.swift`
从纯文本(.txt文件构建 `RDEPUBTextBook`,用于支持 TXT 格式阅读。
---
## 10. 设计模式总结
| 模式 | 应用 |
|------|------|
| **管线模式** | `RDEPUBTextTypesettingPipeline` 多阶段 HTML 处理 |
| **策略模式** | `RDEPUBTextRenderer` 协议,`RDEPUBDTCoreTextRenderer` 实现 |
| **Builder 模式** | `RDEPUBTextBookBuilder` 全书构建 |
| **缓存协调** | `RDEPUBPaginationCacheCoordinator` 分页结果缓存 |
| **索引表** | `RDEPUBTextIndexTable` 全书字符偏移快速查找 |
| **关注点分离** | 渲染/分页/缓存/诊断各司其职 |
---
## 11. 数据流图
```
EPUB HTML 文件
RDEPUBTypesettingPipeline
│ HTMLNormalizer → SemanticMarker → CFI → StyleSheet → Font → Fragment
标记化 HTML + CSS
RDEPUBDTCoreTextRenderer
│ DTCoreText HTML → NSAttributedString
NSAttributedString带语义属性
RDEPUBTextLayouter
│ CoreText 排版 → 帧计算 → 分页策略
[RDEPUBTextLayoutFrame](页面范围列表)
RDEPUBTextChapter + RDEPUBTextPage
RDEPUBTextBook全书模型 + 索引表)
```