feat: EPUB阅读器搜索、注释、CFI模块及大书远距跳转优化

- 实现EPUB阅读器搜索功能及选中注释功能
- 优化CFI模块,修复代码审查发现的11个问题
- 实现大书远距目录跳转与后台补全优化方案
- 优化设置面板与章节运行时联动
- 重构及大量改进优化
This commit is contained in:
shenlei
2026-06-22 20:26:34 +08:00
parent f50495ad91
commit c65c190b71
178 changed files with 11380 additions and 6728 deletions
+521
View File
@@ -0,0 +1,521 @@
# 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. 纯文本构建器
**文件:** `RDPlainTextBookBuilder.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(全书模型 + 索引表)
```