# EPUBCore 模块代码级参考文档 > 最后更新:2026-06-18 --- ## 1. 模块概述 `EPUBCore` 是 ReadViewSDK 的**基础层**,负责 EPUB 文件解析、资源管理、WebView 渲染、JavaScript 桥接、CFI 定位、搜索和分页计算。它是上层 EPUBTextRendering 和 EPUBUI 的数据提供者。 **文件清单(~50 个 Swift 文件):** | 子系统 | 核心文件 | 职责 | |--------|----------|------| | **解析器** | `RDEPUBParser*.swift` | EPUB 解压、OPF 解析、TOC 解析、资源提取 | | **数据模型** | `RDEPUBModels.swift` | Metadata、ManifestItem、SpineItem、TOC 等基础模型 | | **Publication** | `RDEPUBPublication.swift` | 出版物抽象,聚合 parser 和 resourceResolver | | **阅读会话** | `RDEPUBReadingSession.swift` | 管理分页快照、阅读位置、待导航状态 | | **WebView** | `RDEPUBWebView*.swift` | WKWebView 封装,支持 Reflowable 和 Fixed Layout | | **JS 桥接** | `RDEPUBJavaScriptBridge.swift` | JavaScript 消息协议和脚本注入 | | **资源解析** | `RDEPUBResourceResolver.swift` | href 归一化、文件路径解析 | | **URL 方案** | `RDEPUBResourceURLSchemeHandler.swift` | 自定义 `ss-reader://` 协议处理 | | **分页器** | `RDEPUBPaginator.swift` | WebView 分页计算(按章测量页数) | | **CFI** | `CFI/RDEPUBCFI*.swift` | EPUB CFI 解析、生成、序列化、恢复 | | **搜索** | `RDEPUBSearchEngine.swift` | HTML 全文搜索 | | **样式** | `RDEPUBStyleSheetBuilder.swift` | CSS 注入与分页样式生成 | | **偏好** | `RDEPUBPreferences.swift` | 阅读偏好设置模型 | | **资源文件** | `RDEPUBAssetRepository.swift` | JS/CSS/HTML 资源文件加载 | | **笔记** | `Notes/RDEPUBNote*.swift` | 脚注/尾注检测与解析 | --- ## 2. 解析器子系统 ### 2.1 RDEPUBParser **文件:** `RDEPUBParser.swift` + 扩展文件 EPUB 文件解析的核心类,负责从 .epub 文件中提取结构化数据。 ```swift public final class RDEPUBParser { public internal(set) var metadata: RDEPUBMetadata // 书籍元数据 public internal(set) var manifest: [String: RDEPUBManifestItem] // 资源清单 public internal(set) var spine: [RDEPUBSpineItem] // 阅读顺序 public internal(set) var tableOfContents: [EPUBTableOfContentsItem] // 目录 public internal(set) var extractionRootURL: URL? // 解压根目录 public internal(set) var opfURL: URL? // OPF 文件路径 public var opfDirectoryURL: URL? // OPF 所在目录 /// 解析 EPUB 文件(解压 + 解析 container.xml + 解析 OPF) public func parse(epubURL: URL) throws /// 直接解析 OPF 文件 public func parseOPF(at opfURL: URL) throws /// 解析 Navigation Document 或 NCX public func parseTOC() -> [EPUBTableOfContentsItem] /// 从 parser 创建 Publication public func makePublication() -> RDEPUBPublication } ``` **扩展文件职责:** | 文件 | 职责 | |------|------| | `RDEPUBParser+Archive.swift` | ZIP 解压(ZIPFoundation),提取到 Caches 目录 | | `RDEPUBParser+Package.swift` | OPF XML SAX 解析(metadata/manifest/spine) | | `RDEPUBParser+TOC.swift` | Navigation Document 和 NCX 解析 | | `RDEPUBParser+Resources.swift` | 资源文件读取(HTML 字符串、文件 URL) | | `RDEPUBParser+ReadingProfile.swift` | 阅读配置文件检测 | ### 2.2 RDEPUBParserError ```swift public enum RDEPUBParserError: LocalizedError { case archiveOpenFailed(URL) case missingContainerXML case missingRootFile case invalidRootFilePath(String) case invalidXML(URL) case missingManifestItem(idref: String) case emptySpine case invalidArchiveEntryPath(String) } ``` --- ## 3. 数据模型 ### 3.1 RDEPUBMetadata **文件:** `RDEPUBModels.swift` ```swift public struct RDEPUBMetadata: Codable, Equatable { public var identifier: String? // ISBN/UUID public var title: String // 书名 public var author: String? // 作者 public var language: String? // 语言 public var version: String? // EPUB 版本 public var layout: RDEPUBLayout // reflowable / fixed public var spread: String? // none / auto public var readingProgression: RDEPUBReadingProgression // ltr / rtl / auto } ``` ### 3.2 RDEPUBManifestItem ```swift public struct RDEPUBManifestItem: Codable, Equatable { public var id: String // 资源 ID public var href: String // 相对路径 public var mediaType: String // MIME 类型 public var properties: [String] // 属性(nav, mathml, svg 等) public var fallback: String? // 回退资源 ID public var mediaOverlay: String? // 媒体叠加 ID public var title: String? // 标题 public var isNavigationDocument: Bool // 是否为 Navigation Document public var isNCX: Bool // 是否为 NCX 文件 } ``` ### 3.3 RDEPUBSpineItem ```swift public struct RDEPUBSpineItem: Codable, Equatable { public var idref: String // 引用 manifest ID public var href: String // 解析后的相对路径 public var mediaType: String // MIME 类型 public var title: String // 章节标题 public var linear: Bool // 是否为线性内容 public var properties: [String] // 属性(page-spread-left/right) public var pageSpread: RDEPUBPageSpread? // 页面位置 public var layout: RDEPUBLayout? // 章节级布局覆盖 } ``` ### 3.4 EPUBTableOfContentsItem ```swift public struct EPUBTableOfContentsItem: Codable, Equatable { public var title: String // 目录标题 public var href: String // 链接地址 public var children: [EPUBTableOfContentsItem] // 子目录(支持多级) } ``` ### 3.5 枚举类型 ```swift public enum RDEPUBLayout: String, Codable { case reflowable // 文本重排 case fixed // 固定布局 } public enum RDEPUBReadingProfile: String, Codable { case webInteractive // WebView 交互模式(原 EPUB 渲染) case webFixedLayout // WebView 固定布局 case textReflowable // 原生文本渲染(DTCoreText) } public enum RDEPUBReadingProgression: String, Codable { case ltr, rtl, auto } public enum RDEPUBPageSpread: String, Codable { case left, right, center } ``` --- ## 4. Publication **文件:** `RDEPUBPublication.swift` 聚合 `RDEPUBParser` 和 `RDEPUBResourceResolver`,提供统一的出版物访问接口。 ```swift public final class RDEPUBPublication { public let parser: RDEPUBParser public let resourceResolver: RDEPUBResourceResolver public var metadata: RDEPUBMetadata public var manifest: [String: RDEPUBManifestItem] public var spine: [RDEPUBSpineItem] public var tableOfContents: [EPUBTableOfContentsItem] public var layout: RDEPUBLayout public var readingProfile: RDEPUBReadingProfile public var readingProgression: RDEPUBReadingProgression public var bookIdentifier: String? /// 判断 Fixed Layout 是否启用双页展开 public func fixedLayoutSpreadEnabled(for preferences: RDEPUBPreferences, viewportSize: CGSize) -> Bool /// 生成 Fixed Layout 的 Spread 列表 public func makeFixedSpreads(preferences: RDEPUBPreferences, viewportSize: CGSize) -> [EPUBFixedSpread] } ``` --- ## 5. 阅读会话 **文件:** `RDEPUBReadingSession.swift` 管理阅读过程中的分页快照、阅读位置和待导航状态。 ```swift public final class RDEPUBReadingSession { public typealias PaginationSnapshot = (pages: [EPUBPage], chapters: [EPUBChapterInfo]) public let publication: RDEPUBPublication public private(set) var navigatorState: RDEPUBNavigatorState public private(set) var activePages: [EPUBPage] // 当前活跃页列表 public private(set) var activeChapters: [EPUBChapterInfo] // 当前活跃章节列表 public private(set) var stagedPages: [EPUBPage]? // 暂存页列表(待应用) public private(set) var stagedChapters: [EPUBChapterInfo]? public private(set) var currentViewport: RDEPUBViewport? public private(set) var currentReadingContext: RDEPUBReadingContext? // 状态管理 public func transition(to state: RDEPUBNavigatorState) public func resetRuntimeState() // 快照管理 public func setActiveSnapshot(_ snapshot: PaginationSnapshot) public func stageSnapshot(_ snapshot: PaginationSnapshot, restoreLocation: RDEPUBLocation?) public func consumeStagedSnapshotIfAllowed() -> (snapshot: PaginationSnapshot, restoreLocation: RDEPUBLocation?)? // 导航 public func queueNavigation(to location: RDEPUBLocation, ...) -> Int? public func updateReadingContext(pageNumber:location:spineIndex:chapterIndex:bookIdentifier:) public func currentReadingLocation(bookIdentifier: String?) -> RDEPUBLocation? // 分页快照生成 public func makePaginationSnapshot(pageCounts:preferences:layoutContext:) -> PaginationSnapshot } ``` ### 5.1 RDEPUBNavigatorState ```swift public enum RDEPUBNavigatorState: String, Codable { case initializing // 初始化中 case loading // 加载中 case idle // 空闲(可应用快照) case jumping // 跳转中 case moving // 翻页中 case repaginating // 重新分页中 public var isStableForSnapshotApplication: Bool // 仅 idle 状态为 true } ``` --- ## 6. WebView 子系统 ### 6.1 RDEPUBWebView **文件:** `RDEPUBWebView.swift` + 扩展文件 `UIView` 子类,内部封装 `WKWebView`,支持 Reflowable 和 Fixed Layout 两种渲染模式。 ```swift public final class RDEPUBWebView: UIView { public weak var delegate: RDEPUBWebViewDelegate? public var onRendered: (() -> Void)? var publication: RDEPUBPublication? var currentRenderRequest: RDEPUBRenderRequest? var webView: WKWebView? var schemeHandler: RDEPUBResourceURLSchemeHandler? var currentSpineIndex: Int var currentHref: String var currentPageIndex: Int var currentTotalPagesInChapter: Int var viewportSize: CGSize var currentFontSize: CGFloat var currentLineHeightMultiple: CGFloat var targetLocation: RDEPUBLocation? var pendingHighlights: [RDEPUBHighlight] } ``` **扩展文件:** | 文件 | 职责 | |------|------| | `RDEPUBWebView+Configuration.swift` | WKWebView 配置(scheme handler、user script) | | `RDEPUBWebView+Reflowable.swift` | Reflowable 模式渲染逻辑 | | `RDEPUBWebView+FixedLayout.swift` | Fixed Layout 模式渲染逻辑 | | `RDEPUBWebView+JavaScriptBridge.swift` | JS 消息接收与处理 | | `RDEPUBWebView+Search.swift` | 搜索高亮渲染 | ### 6.2 RDEPUBWebViewDelegate ```swift public protocol RDEPUBWebViewDelegate: AnyObject { func epubWebView(_ webView: RDEPUBWebView, didUpdateLocation location: RDEPUBLocation, spineIndex: Int) func epubWebView(_ webView: RDEPUBWebView, didChangeSelection selection: RDEPUBSelection?, spineIndex: Int) func epubWebView(_ webView: RDEPUBWebView, didRequestSelectionAction action: RDEPUBAnnotationMenuAction) func epubWebView(_ webView: RDEPUBWebView, didActivateInternalLink location: RDEPUBLocation, fromSpineIndex: Int) func epubWebView(_ webView: RDEPUBWebView, didActivateExternalLink url: URL) func epubWebView(_ webView: RDEPUBWebView, didLogJavaScriptError message: String) func epubWebViewDidFinishRendering(_ webView: RDEPUBWebView) } ``` --- ## 7. JavaScript 桥接 **文件:** `RDEPUBJavaScriptBridge.swift` 定义 Native ↔ WebView 的消息协议和脚本注入。 ### 7.1 消息类型 ```swift enum RDEPUBJavaScriptBridgeMessage: String, CaseIterable { case progressionChanged = "ssReaderProgressionChanged" // 阅读进度变化 case selectionChanged = "ssReaderSelectionChanged" // 文本选择变化 case internalLink = "ssReaderInternalLink" // 内部链接点击 case externalLink = "ssReaderExternalLink" // 外部链接点击 case javaScriptError = "ssReaderJSError" // JS 错误 case fixedLayoutReady = "ssReaderFixedLayoutReady" // Fixed Layout 就绪 } ``` ### 7.2 注入的 JS 资源 - `rangy-core.js` — 范围与选择 API - `rangy-serializer.js` — 选择序列化 - `cssInjector.js` — CSS 注入 - `WeReadApi.js` — 阅读器 API(分页、跳转、高亮) - `epub-bridge.js` — 消息桥接 ### 7.3 关键方法 ```swift enum RDEPUBJavaScriptBridge { static var messageNames: [String] // 所有消息名称 static var userScript: String // 注入的 User Script /// 生成 Reflowable 渲染脚本 static func applyPresentationScript(for request: RDEPUBReflowableRenderRequest) -> String /// 生成 Fixed Layout 渲染脚本 static func applyFixedPresentationScript(for request: RDEPUBFixedRenderRequest) -> String } ``` --- ## 8. 资源解析 **文件:** `RDEPUBResourceResolver.swift` 负责 href 归一化、文件路径解析和 spine 索引查找。 ```swift public final class RDEPUBResourceResolver { public var opfDirectoryURL: URL? /// 将相对路径转为文件 URL public func fileURL(forRelativePath relativePath: String) -> URL? /// 将相对路径转为 ss-reader:// 资源 URL public func resourceURL(forRelativePath relativePath: String) -> URL? /// href 归一化(去除 fragment,解析相对路径) public func normalizedHref(_ href: String, relativeToSpineIndex spineIndex: Int? = nil) -> String? public func normalizedHref(_ href: String, relativeToHref baseHref: String) -> String? /// 位置归一化(合并 href + fragment + progression) public func normalizedLocation(_ location: RDEPUBLocation, relativeToSpineIndex spineIndex: Int?, bookIdentifier: String?) -> RDEPUBLocation? /// 根据 href 查找 spine 索引 public func spineIndex(forNormalizedHref normalizedHref: String) -> Int? public func spineIndex(for location: RDEPUBLocation) -> Int? /// 获取 spine 项的 href 和 title public func href(forSpineIndex spineIndex: Int) -> String? public func title(forSpineIndex spineIndex: Int) -> String? } ``` --- ## 9. URL 方案处理器 **文件:** `RDEPUBResourceURLSchemeHandler.swift` 实现 `WKURLSchemeHandler`,拦截 `ss-reader://book/` 请求,从 EPUB 解压目录流式读取资源文件。 ```swift public final class RDEPUBResourceURLSchemeHandler: NSObject, WKURLSchemeHandler { public static let scheme = "ss-reader" public static let host = "book" public init(parser: RDEPUBParser) // WKURLSchemeHandler public func webView(_ webView: WKWebView, start urlSchemeTask: any WKURLSchemeTask) public func webView(_ webView: WKWebView, stop urlSchemeTask: any WKURLSchemeTask) } ``` --- ## 10. 分页器 **文件:** `RDEPUBPaginator.swift` 使用隐藏的 WKWebView 逐章加载 HTML,通过 JS 测量每章的页数。 ```swift public final class RDEPUBPaginator: NSObject { /// 计算所有章节的页数 public func calculate( parser: RDEPUBParser, hostingView: UIView, presentation: RDEPUBPresentationStyle, completion: @escaping ([Int]) -> Void ) /// 计算单个章节的页数 public func calculateSingleChapter( parser: RDEPUBParser, spineIndex: Int, hostingView: UIView, presentation: RDEPUBPresentationStyle, completion: @escaping (Int) -> Void ) } ``` --- ## 11. 搜索引擎 **文件:** `RDEPUBSearchEngine.swift` ```swift protocol RDEPUBSearchEngine { func search(keyword: String) -> [RDEPUBSearchMatch] } ``` ### RDEPUBHTMLSearchEngine 基于 HTML 的搜索引擎,逐章加载 HTML → 转为纯文本 → 关键词匹配。 ```swift final class RDEPUBHTMLSearchEngine: RDEPUBSearchEngine { init(parser: RDEPUBParser, publication: RDEPUBPublication) func search(keyword: String) -> [RDEPUBSearchMatch] } ``` ### RDEPUBTextSearchEngine(EPUBTextRendering 模块) 基于原生文本的搜索引擎,在 `NSAttributedString` 上直接搜索,性能更好。 ```swift final class RDEPUBTextSearchEngine: RDEPUBSearchEngine { init(textBook: RDEPUBTextBook, publication: RDEPUBPublication) func search(keyword: String) -> [RDEPUBSearchMatch] } ``` --- ## 12. CFI 子系统 **文件:** `CFI/RDEPUBCFI*.swift`(13 个文件) 实现 EPUB CFI(Canonical Fragment Identifier)标准,用于精确定位 EPUB 内容。 | 文件 | 职责 | |------|------| | `RDEPUBCFI.swift` | CFI 值类型,核心解析入口 | | `RDEPUBCFIParser.swift` | CFI 字符串解析器 | | `RDEPUBCFISerializer.swift` | CFI 序列化为字符串 | | `RDEPUBCFIPath.swift` | CFI 路径节点 | | `RDEPUBCFIRange.swift` | CFI 范围(起止点) | | `RDEPUBCFIResolver.swift` | CFI → DOM 位置解析 | | `RDEPUBCFIGenerator.swift` | DOM 位置 → CFI 生成 | | `RDEPUBCFIMap.swift` | 章节级 CFI 映射表 | | `RDEPUBCFIDOMPathBuilder.swift` | DOM 路径构建 | | `RDEPUBCFITextAssertion.swift` | 文本断言(偏移校验) | | `RDEPUBCFIRecoveryEngine.swift` | CFI 恢复引擎(节点变化后重新定位) | | `RDEPUBCFICompatibility.swift` | CFI 兼容性处理 | | `RDEPUBCFIError.swift` | 错误类型 | --- ## 13. 样式构建 **文件:** `RDEPUBStyleSheetBuilder.swift` ```swift public enum RDEPUBStyleSheetBuilder { /// 向 HTML 注入分页 CSS(用于 Paginator 测量) public static func injectPaginationCSS(into html: String, presentation: RDEPUBPresentationStyle) -> String /// 生成渲染用 CSS(viewport 尺寸、字体、行高、主题色) public static func renderCSS(for presentation: RDEPUBPresentationStyle) -> String } ``` --- ## 14. 偏好设置 **文件:** `RDEPUBPreferences.swift` ```swift public struct RDEPUBPreferences: Equatable { public var fontSize: CGFloat // 字体大小 public var lineHeightMultiple: CGFloat // 行高倍数 public var reflowableContentInsets: UIEdgeInsets // Reflowable 内容边距 public var fixedContentInset: UIEdgeInsets // Fixed Layout 内容边距 public var themeBackgroundColor: String? // 主题背景色(CSS) public var themeTextColor: String? // 主题文字色(CSS) public var fixedBackgroundColor: String? // Fixed 背景色 public var fixedLayoutFit: RDEPUBFixedLayoutFit // Fixed 适配模式 public var fixedLayoutSpreadMode: RDEPUBFixedLayoutSpreadMode // 双页模式 public var numberOfColumns: Int // 列数 public var columnGap: CGFloat // 列间距 /// 生成 PresentationStyle public func presentationStyle(viewportSize: CGSize) -> RDEPUBPresentationStyle /// 生成渲染请求 public func renderRequest(for page: EPUBPage, publication: RDEPUBPublication, viewportSize: CGSize, ...) -> RDEPUBRenderRequest? } ``` --- ## 15. 渲染请求模型 **文件:** `RDEPUBRenderRequest.swift` ```swift public enum RDEPUBRenderRequest: Equatable { case reflowable(RDEPUBReflowableRenderRequest) case fixed(RDEPUBFixedRenderRequest) public var isFixedLayout: Bool public var primarySpineIndex: Int public var primaryHref: String } public struct RDEPUBReflowableRenderRequest: Equatable { public var spineIndex: Int public var href: String public var pageIndex: Int public var totalPagesInChapter: Int public var presentation: RDEPUBPresentationStyle public var targetLocation: RDEPUBLocation? public var highlights: [RDEPUBHighlight] public var searchPresentation: RDEPUBSearchPresentation? } public struct RDEPUBFixedRenderRequest: Equatable { public var spread: EPUBFixedSpread public var viewportSize: CGSize public var contentInset: UIEdgeInsets public var fit: RDEPUBFixedLayoutFit } ``` ### RDEPUBPresentationStyle ```swift public struct RDEPUBPresentationStyle: Equatable { public var viewportSize: CGSize public var contentInsets: UIEdgeInsets public var fontSize: CGFloat public var lineHeightMultiple: CGFloat public var numberOfColumns: Int public var columnGap: CGFloat public var themeBackgroundColor: String? public var themeTextColor: String? } ``` --- ## 16. 资源仓库 **文件:** `RDEPUBAssetRepository.swift` 管理 SDK 内置的 JS/CSS/HTML 资源文件加载。 ```swift enum RDEPUBAsset: String { case rangyCoreScript, rangySerializerScript, cssInjectorScript case weReadAPIScript, bridgeScript, fixedLayoutTemplate case wxReadDefaultCSS, wxReadReplaceCSS, wxReadDarkCSS, wxReadLatinReplaceCSS } enum RDEPUBAssetRepository { /// 加载资源文件内容,支持模板替换 static func string(for asset: RDEPUBAsset, replacements: [String: String] = [:]) -> String } ``` --- ## 17. 笔记子系统 **文件:** `Notes/RDEPUBNote*.swift` | 文件 | 职责 | |------|------| | `RDEPUBNoteModels.swift` | 脚注/尾注数据模型 | | `RDEPUBNoteDetector.swift` | 从 HTML 中检测脚注标记 | | `RDEPUBNoteResolver.swift` | 解析脚注内容 | --- ## 18. 设计模式总结 | 模式 | 应用 | |------|------| | **Builder 模式** | `RDEPUBParser` → `RDEPUBPublication` 构建链 | | **Strategy 模式** | `RDEPUBSearchEngine` 协议,HTML 和 Text 两种实现 | | **URL Scheme 拦截** | `RDEPUBResourceURLSchemeHandler` 自定义协议 | | **消息桥接** | `RDEPUBJavaScriptBridge` Native ↔ JS 双向通信 | | **快照管理** | `RDEPUBReadingSession` staged/active 双快照 | | **状态机** | `RDEPUBNavigatorState` 管理导航状态 |