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

17 KiB
Raw Blame History

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。

struct RDEPUBTextTypesetterPipeline {
    func makeRequest(from input: RDEPUBTypesettingInput) -> RDEPUBTypesettingOutput
}

管线阶段:

原始 HTML
    │
    ▼
RDEPUBHTMLNormalizer          → 清理 HTML移除脚本、修复标签
    │
    ▼
RDEPUBSemanticMarkerInjector  → 注入语义标记(段落、列表、表格等)
    │
    ▼
RDEPUBCFIMarkerInjector       → 注入 CFI 定位标记
    │
    ▼
RDEPUBStyleSheetComposer      → 合并样式表(内联 CSS + 兼容性处理)
    │
    ▼
RDEPUBFontNormalizer          → 注册嵌入字体、字体回退
    │
    ▼
RDEPUBFragmentMarkerInjector  → 注入片段标记(用于锚点定位)
    │
    ▼
标记化 HTML + 诊断报告

2.2 管线输入/输出

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

public protocol RDEPUBTextRenderer {
    /// 将 HTML 渲染为 NSAttributedString
    func render(html: String, baseURL: URL?, style: RDEPUBTextRenderStyle) -> NSAttributedString
}

3.2 RDEPUBDTCoreTextRenderer

文件: RDEPUBDTCoreTextRenderer.swift

基于 DTCoreText 的渲染器实现,将 HTML 转换为 NSAttributedString

public final class RDEPUBDTCoreTextRenderer: RDEPUBTextRenderer {
    public init()
    public func render(html: String, baseURL: URL?, style: RDEPUBTextRenderStyle) -> NSAttributedString
}

3.3 渲染样式

public struct RDEPUBTextRenderStyle {
    public var font: UIFont              // 字体
    public var lineSpacing: CGFloat      // 行间距
    public var textColor: UIColor?       // 文字颜色
    public var backgroundColor: UIColor? // 背景颜色
}

3.4 NSAttributedString 扩展键

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

分页的核心入口,组合 RDEPUBCoreTextPageFrameFactoryRDEPUBChapterPageCounter

struct RDEPUBTextLayouter {
    init(attributedString: NSAttributedString, pageSize: CGSize, config: RDEPUBTextLayoutConfig = .default)
    func layoutFrames(fragmentOffsets: [String: Int] = [:]) -> [RDEPUBTextLayoutFrame]
}

4.2 RDEPUBTextLayoutConfig

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 排列到指定尺寸的帧中。

struct RDEPUBCoreTextPageFrameFactory {
    init(attributedString: NSAttributedString, pageSize: CGSize, config: RDEPUBTextLayoutConfig)
    func makeFrames(fragmentOffsets: [String: Int] = [:]) -> [RDEPUBTextLayoutFrame]
}

4.4 RDEPUBChapterPageCounter

文件: Pagination/RDEPUBChapterPageCounter.swift

基于 RDEPUBCoreTextPageFrameFactory 计算页范围。

struct RDEPUBChapterPageCounter {
    init(factory: RDEPUBCoreTextPageFrameFactory)
    func layoutFrames(fragmentOffsets: [String: Int] = [:]) -> [RDEPUBTextLayoutFrame]
}

4.5 RDEPUBTextLayoutFrame

文件: Pagination/RDEPUBTextLayoutFrame.swift

表示一个排版帧(一页的内容范围)。

public struct RDEPUBTextLayoutFrame {
    public let range: NSRange           // 字符范围
    public let frameIndex: Int          // 帧索引
    public let diagnostics: [String]    // 诊断信息
}

4.6 RDEPUBPageBreakPolicy

文件: Pagination/RDEPUBPageBreakPolicy.swift

分页策略,决定在何处断页。

struct RDEPUBPageBreakPolicy {
    /// 检查指定位置是否允许分页
    func canBreak(at offset: Int, in attributedString: NSAttributedString) -> Bool
}

5. 构建管线

5.1 RDEPUBTextBookBuilder

文件: BuildPipeline/RDEPUBTextBookBuilder.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

全书文本模型,包含所有章节和页面。

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

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

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

全书字符偏移索引,支持快速查找。

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

字符偏移 ↔ 页码双向转换。

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

章节级数据访问封装。

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 搜索更高效。

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全书模型 + 索引表)