# 阅读器功能开发计划 > 本文档整合了渲染质量三方对比(ReadViewSDK vs WXRead)与功能开发计划,作为阅读器能力演进的单一真值。 > 基于读书 v10.0.3 逆向分析,与当前 ReadViewSDK 代码核查结果整合。 ## 背景 渲染内核的架构对齐解决的是"代码可维护性"问题。本文档梳理从"能用的阅读器"到"能上架的商业阅读器"还需要补齐哪些能力,每项能力标注三方状态和具体实施计划。 ### 三方对比总览(渲染质量) | 缺失项 | 严重度 | ReadViewSDK | WXRead | 差距说明 | | --- | --- | --- | --- | --- | | **竖排文字** | 高 | ❌ 未实现 | ❌ 未实现 | 双方均无 `writing-mode` 支持 | | **Ruby 注音** | 高 | ❌ 未实现 | ❌ 未实现 | 双方均无 ``/`` 处理 | | **数学公式** | 中 | ❌ 未实现 | ⚠️ 仅字体回退 | 非原生渲染范畴,需 WebView 回退 | | **复杂图文分页质量** | 高 | ✅ 已实现 | ✅ 已实现 | 双方均已实现 | | **字体选择器** | 中 | ✅ 已实现 | ⚠️ 管线完备,UI 未见 | 已支持四档字体选择 | | **连字/断字** | 中 | ⚠️ 仅配置标记 | ⚠️ 仅属性声明 | 双方均未实际实现 | | **多语言排版回退** | 中 | ⚠️ 语言检测有,简繁转换无 | ✅ 已实现 | WXRead 额外有简繁转换 | ### ReadViewSDK 领先 WXRead 的项 | 项 | 说明 | | --- | --- | | **`keepWithNext`** | 已实现 `trimmedRangeForKeepWithNext`,WXRead 反编译代码中未找到对应实现 | ### 不需要对标 WXRead 的项 以下项 WXRead 自身也未实现,不属于必须补齐的能力:竖排文字、Ruby 注音、数学公式、Hyphenation 断字。 --- ## 一、渲染质量 ### 1. 字体选择器 **当前状态**:✅ 基础能力已完成。 已支持系统、宋体、圆体、等宽四档字体选择;设置项可持久化;切换字体会触发重新分页;分页缓存签名已包含字体信息,避免不同字体复用旧缓存。 **已落地文件清单**: | 文件 | 改动 | 状态 | | --- | --- | --- | | `RDEPUBReaderConfiguration.swift` | 新增 `RDEPUBReaderFontChoice`,配置增加 `fontChoice` | 已完成 | | `RDEPUBReaderSettings.swift` | 新增 `fontChoice` 持久化,更新 `applying(to:)` 和 `capture(configuration:brightness:)` | 已完成 | | `RDEPUBReaderContext.swift` | `currentTextRenderStyle()` 改用 `configuration.fontChoice.font(ofSize:)` | 已完成 | | `RDURLReaderController.swift` | URL 阅读器同步使用 `fontChoice` 生成文字样式 | 已完成 | | `RDEPUBReaderController+RuntimeBridge.swift` | `requiresRepagination(from:to:)` 增加 `fontChoice` 变更检查 | 已完成 | | `RDEPUBPaginationCacheCoordinator.swift` | 分页缓存签名增加字体名 | 已完成 | | `RDEPUBReaderSettingsViewController.swift` | 新增 `onFontChoiceChange` 回调、字体分段控件和 accessibility identifier | 已完成 | | `RDEPUBReaderChromeCoordinator.swift` | 设置面板接线字体变更回调 | 已完成 | | `SettingsPanelTests.swift` | UI 自动化覆盖字体选择 | 已完成 | **当前验收结果**: - ✅ 切换字体后触发重排。 - ✅ 字体选择可随阅读器设置持久化。 - ✅ 分页缓存按字体隔离。 - ✅ UI 自动化已覆盖设置面板字体选择。 **后续增强计划**: 1. 引入更多内置字体包(建议:思源宋体、思源黑体、方正书宋、方正兰亭黑、Lora、OpenDyslexic)。 2. 使用 `CTFontManagerRegisterFontsForURL` 注册 bundle 字体。 3. 字体列表从枚举升级为资源驱动模型,每项包含 `displayName`、`fontName`、`previewText`、可用性状态。 4. 字体选择 UI 从分段控件升级为横向预览卡片,展示真实字体效果。 5. 增加字体资源加载失败兜底,失败时回退系统字体并保留用户设置。 --- ### 2. 暗色模式图片处理 **当前状态**:✅ 基础能力已完成。 暗色主题下会对正文图片做显示副本调暗,降低白底图片在深色背景上的刺眼程度。处理只作用于展示层,不修改分页原始内容;封面和小图会跳过,避免误处理封面、图标和装饰图。 **已落地文件清单**: | 文件 | 改动 | 状态 | | --- | --- | --- | | `RDEPUBReaderConfiguration.swift` | 新增 `darkImageAdjustmentEnabled` 和 `darkImageBlendRatio` | 已完成 | | `RDEPUBReaderController+RuntimeBridge.swift` | 主题和暗色图片配置变更触发可见页刷新 | 已完成 | | `RDEPUBTextContentView.swift` | DTCoreText 展示内容增加暗色图片显示副本处理 | 已完成 | | `RDEPUBTextContentView.swift` | 增加暗色图片缓存,避免重复生成 | 已完成 | | `SettingsPanelTests.swift` | UI 自动化覆盖暗色主题切换 | 已完成 | **当前实现策略**: - 仅当背景亮度低于阈值、配置开启、混合比例大于 0 时启用。 - 仅处理正文 inline image attachment。 - 跳过封面和小于 80x80 的图片。 - 使用 `NSCache` 缓存处理后的图片。 - 使用主题背景色按比例覆盖原图,透明区域保持透明。 **当前验收结果**: - ✅ 暗色模式下图片不再完全以原始亮色块直出。 - ✅ 图片内容仍保持可辨认,不做反色。 - ✅ 处理结果有缓存。 - ✅ UI 自动化已覆盖暗色主题入口。 **后续增强计划**: 1. 增加真实 EPUB 图片样本的截图回归测试。 2. 按图片平均亮度自适应 `darkImageBlendRatio`。 3. 增加用户侧开关,允许关闭暗色图片处理。 4. 如果后续恢复 WebView 路径,再补充 CSS filter 策略。 --- ### 3. 简繁转换 **当前状态**:零实现。语言元数据已提取(`publication.metadata.language`),但仅用于拉丁/CJK CSS 分轨。 **WXRead 实现参考**: - `WREpubTypesetter` 检测 `book.language` 含 `Hant`/`TW`/`HK` 时调用 `_WRConvertHansToHantIfNeeded()` - 使用 `CFStringTransform` 两步转换:Hans → Latin → Hant **实施步骤**: **Step 1:转换工具** 1. 新增 `RDEPUBTextChineseConverter.swift`,提供: ```swift enum RDEPUBChineseScript { case hans, hant } func convert(_ text: String, to script: RDEPUBChineseScript) -> String ``` 2. 实现使用 `CFStringTransform`: ```swift let mutable = NSMutableString(string: text) as CFMutableString CFStringTransform(mutable, nil, kCFStringTransformToLatin, false) // Hans → Pinyin CFStringTransform(mutable, nil, kCFStringTransformStripDiacritics, false) // Pinyin → stripped // 然后通过字典映射到繁体 ``` 或直接使用 Apple 的 `kCFStringTransformHansToHant`(如果可用)。 **Step 2:判断是否需要转换** 3. 在 `RDEPUBTextBookBuilder.build()` 或 `RDEPUBTextRendererSupport.makeChapterRenderRequest()` 中: - 读取 `publication.metadata.language` - 如果语言为 `zh-Hant`/`zh-TW`/`zh-HK` 且用户设置为简体,或语言为 `zh-Hans`/`zh-CN` 且用户设置为繁体,触发转换 4. 新增 `RDEPUBReaderConfiguration.chineseScript: RDEPUBChineseScript?`(nil = 跟随书籍) **Step 3:应用转换** 5. 在 `normalizeReadingAttributes(in:style:)` 之后、返回 `RDEPUBTextChapterRenderRequest` 之前,对 attributed string 的文本内容执行转换 6. 需要保持 attributed string 的属性(字体、颜色、附件)不变,只替换字符 **验收标准**: - 简体 EPUB 在繁体模式下显示繁体 - 繁体 EPUB 在简体模式下显示简体 - 转换不影响分页结果(转换后字符数可能变化,需验证) - 高亮、搜索功能在转换后仍然正常 --- ### 4. 孤行/寡行控制 **当前状态**:`RDEPUBTextLayoutConfig` 声明了 `avoidOrphans`/`avoidWidows`(默认 true),但 `RDEPUBTextLayouter` 完全不读取这两个标记。 **实现原理**: - **寡行(widow)**:段落最后一行单独出现在页底 → 应将该行拉到下一页 - **孤行(orphan)**:段落第一行单独出现在页首 → 应将前一页最后一行拉过来 - 通常只处理寡行(保留至少 2 行在页底),孤行处理会引发连锁重排 **实施步骤**: **Step 1:CoreText 路径** 1. 在 `RDEPUBTextLayouter.layoutFramesUsingCoreText()` 的分页循环中(~行 63-137),在 `adjustedRange` 之后、追加 frame 之前,增加寡行检查: ```swift if config.avoidWidows { finalRange = trimmedRangeForAvoidWidows( from: ctFrame, proposed: finalRange, lineRanges: lineRanges ) } ``` 2. `trimmedRangeForAvoidWidows` 实现: - 从 proposed range 的最后一行向前检查 - 如果最后一行是一个段落的最后一行,且该段落在此页只有 1 行 → 回退到上一个段落边界 - 最多移除 2 行(避免过度收缩) **Step 2:DTCoreText 路径** 3. 在 `layoutFramesUsingDTCoreText()` 中增加相同逻辑 **Step 3:段落边界检测** 4. 利用已有的 `paragraphRange(containing:)` 方法(`NSString.paragraphRange`)获取段落范围 5. 对比当前页最后一行的 range 和段落的 range,判断是否为段落唯一一行 **验收标准**: - 分页页数可能轻微增加(1-3 页),但排版质量提升 - 已知的寡行问题页在修复后不再出现段落最后一行孤悬页底 - `avoidOrphans`/`avoidWidows = false` 时行为不变 --- ### 5. Hyphenation 断字 **当前状态**:`hyphenation: Bool = true` 配置标记已声明,但 `NSParagraphStyle.hyphenationFactor` 从未设置。CoreText 路径更不支持断字。 **技术约束**: - `NSParagraphStyle.hyphenationFactor` 对 `UITextView`(降级路径)有效 - CoreText 直绘路径需要设置 `kCTParagraphStyleSpecifierHyphenationFactor` - 中文场景断字影响较小(中文无空格分词),主要改善西文排版 **实施步骤**: **Step 1:UITextView 降级路径** 1. 在 `RDEPUBTextRendererSupport.paragraphStyle(lineSpacing:)`(行 929-934)中增加: ```swift style.hyphenationFactor = 1.0 ``` 2. 在 `normalizeReadingAttributes()`(行 137-143)中,对已有的 `NSMutableParagraphStyle` 增加: ```swift paragraphStyle.hyphenationFactor = 1.0 ``` **Step 2:CoreText 直绘路径** 3. 在 `RDEPUBTextLayouter` 的 frame 构造中,对 `CTParagraphStyle` 增加 hyphenation factor: ```swift var hyphenationFactor: Float = 1.0 let settings = [CTParagraphStyleSetting( spec: .hyphenationFactor, valueSize: MemoryLayout.size, value: &hyphenationFactor )] let paragraphStyle = CTParagraphStyleCreate(settings, settings.count) ``` 4. 将此 paragraph style 设置到 attributed string 的段落属性上 **Step 3:条件控制** 5. 读取 `config.hyphenation` 标记,为 false 时跳过上述设置 **验收标准**: - 西文长单词在行末正确断字(显示连字符) - 中文排版不受影响 - `hyphenation = false` 时回到当前行为 --- ## 二、工程成熟度 ### 6. 自动化测试 **当前状态**:✅ UI 自动化测试基础设施已完成,单元测试和分页回归基准待补。 已在 Demo 工程中接入 `ReadViewDemoUITests`,覆盖打开书籍、阅读器基础交互、顶部/底部工具栏、设置面板、字体选择和暗色主题切换。 **已落地内容**: | 项 | 状态 | | --- | --- | | UI Test target `ReadViewDemoUITests` | ✅ 已完成 | | Demo 自动打开测试 EPUB 的入口 | ✅ 已完成 | | Accessibility identifier 体系 | ✅ 已完成 | | 阅读器基础打开测试 | ✅ 已完成 | | 顶部/底部工具栏测试 | ✅ 已完成 | | 设置面板测试 | ✅ 已完成 | | 字体选择 UI 测试 | ✅ 已完成 | | 暗色主题切换 UI 测试 | ✅ 已完成 | **当前验证结果**: ```text xcodebuild build: BUILD SUCCEEDED SettingsPanelTests: TEST SUCCEEDED ReadViewDemoUITests 全量 8 个 UI 测试: TEST SUCCEEDED ``` **下一步实施步骤**: **Step 1:单元测试基础设施** 1. 在 `ReadViewDemo.xcodeproj` 中新增 Unit Test target `ReadViewSDKTests` 2. 创建 `Tests/` 目录,配置 `import RDReaderView` 或通过 Demo target 暴露内部测试入口 3. 新增 `.xctestplan` 配置,把 UI 测试和单元测试拆分为不同测试组 **Step 2:分页回归测试(最高优先级)** 4. 新增 `RDEPUBPaginationRegressionTests.swift`,固定 6 类样本章节: - 纯正文章节 - 图 + 图注 + 正文章节 - 脚注小图标章节 - 多段标题章节 - 大图跨页章节 - 长段落连续章节 5. 每个样本:构建 `RDEPUBTextBook`,断言总页数不变、已知页的 `contentRange` 不变 6. 使用 golden file 对比 `RDEPUBTextChapterPaginationDiagnostic` 输出 **Step 3:位置映射测试** 7. 新增 `RDEPUBLocationMappingTests.swift`: - `location → pageNumber` 正向映射 - `pageNumber → location` 逆向映射 - 往返一致性断言 **Step 4:渲染管线测试** 8. 新增 `RDEPUBTypesetterTests.swift`: - `normalizeHTML` 输入输出对比 - fragment marker 注入/提取一致性 - 语义标记注入后 `rdPage*` 属性计数不变 **Step 5:CI(可选)** 9. 配置 GitHub Actions 或 Jenkins,在 PR 时自动运行测试 **验收标准**: - UI 自动化全量测试稳定通过 - 6 个分页基准样本的页数和 contentRange 稳定 - 位置映射往返测试通过 - 新 PR 触发测试自动运行 --- ### 7. 性能基线 **当前状态**:`RDEPUBTextPerformanceSampler` 用 `CFAbsoluteTimeGetCurrent()` 采样,结果仅 `print()` 输出。无 Instruments 可视化、无内存监控、无阈值告警。 **实施步骤**: **Step 1:os_signpost 集成** 1. 在 `RDEPUBTextPerformanceSampler` 中引入 `os.signpost`: ```swift import os.signpost let log = OSLog(subsystem: "com.rdreader", category: .pointsOfInterest) ``` 2. 在 build/render/paginate 各阶段插入 `os_signpost(.begin, ...)` / `os_signpost(.end, ...)` 3. 这样 Instruments 的 Points of Interest 能直接可视化各阶段耗时 **Step 2:内存监控** 4. 新增 `RDEPUBMemoryMonitor`: ```swift func currentMemoryFootprint() -> UInt64 // task_info.resident_size ``` 5. 在 `RDEPUBTextBookBuilder.build()` 的每章循环中记录内存峰值 6. 在 `RDEPUBTextPerformanceSample` 中增加 `peakMemoryBytes: UInt64` **Step 3:阈值告警** 7. 定义基准值:首屏 < 2s、单章渲染 < 500ms、全书构建 < 30s(按书的大小可调) 8. 超出阈值时通过 `os_log(.error, ...)` 输出警告 9. 在 Demo 中展示性能摘要面板 **Step 4:持久化** 10. 性能样本可选持久化到文件,供回归对比 **验收标准**: - Instruments 能看到各阶段的 signpost 区间 - 内存峰值在大书(79MB 样本)构建过程中有记录 - 超出阈值时有日志输出 --- ### 8. 崩溃防护 **当前状态**:`RDReaderView` 有 pageCurl 崩溃检测和异步恢复,但无全局异常捕获。 **实施步骤**: **Step 1:NSException 捕获** 1. 新增 `RDEPUBCrashGuard` 工具类: ```swift static func performSafely(_ block: () throws -> Void) rethrows static func performWithObjCExceptionHandling(_ block: () -> Void) -> Bool ``` 2. 在关键入口包裹 `@try/@catch`: - `RDEPUBTextBookBuilder.build()` 的每章循环体 - `RDEPUBTextLayouter.layoutFrames()` 的分页循环 - `RDEPUBTextRendererSupport.makeChapterRenderRequest()` 的 HTML 处理 **Step 2:分页/渲染单章隔离** 3. 单章渲染/分页失败时,降级到空白页或上一次缓存结果,不中断全书构建 4. 在 `RDEPUBTextChapterPaginationDiagnostic` 中记录错误状态 **Step 3:全局信号处理(可选)** 5. 注册 `NSSetUncaughtExceptionHandler` 记录 ObjC 异常 6. 注册 signal handler(SIGABRT, SIGSEGV)记录崩溃现场 7. 下次启动时上报(如果后续有上报系统) **验收标准**: - 单章渲染异常不导致整个 build 中断 - pageCurl 崩溃场景已有保护,验证不退化 - 崩溃日志可追踪到具体章节和阶段 --- ### 9. 内存管理 **当前状态**:无显式内存预算。无 `didReceiveMemoryWarning` 处理。`RDEPUBTextBookCache` 磁盘缓存无大小限制。 **实施步骤**: **Step 1:内存警告响应** 1. 在 `RDEPUBReaderController` 中监听 `UIApplication.didReceiveMemoryWarningNotification` 2. 收到警告时: - 清空 `RDReaderPreloadController` 的预加载缓存 - 通知 `RDEPUBTextBookCache` 的内存缓存(如果有)清空 - 释放非当前可见章节的渲染结果 **Step 2:章节级内存释放** 3. 在 `RDEPUBTextBookBuilder` 构建完成后,不再持有已构建章节的 `NSAttributedString` 4. 当前只持有 `RDEPUBTextBook`(包含 `[RDEPUBTextPage]` 的 NSRange),内存占用已较低 5. 如果后续引入 attributed string 缓存,需增加 LRU 淘汰策略 **Step 3:图片内存控制** 6. `RDReaderPreloadController` 预加载的 page view 数量与内存挂钩 7. 当内存压力时减少 `preloadRadius`(从 1 降到 0) **验收标准**: - 在大书(79MB)构建过程中收到内存警告时,app 不被系统杀掉 - 预加载缓存在内存压力下自动收缩 - 当前页内容不丢失 --- ### 10. 增量构建 **当前状态**:全书一次性分页。`RDEPUBTextBookBuilder.build()` 遍历全部 spine item,输出完整 `RDEPUBTextBook`。 **实施步骤**: **Step 1:章节级构建接口** 1. 在 `RDEPUBTextBookBuilder` 上新增: ```swift func buildChapter( at spineIndex: Int, publication: RDEPUBPublication, parser: RDEPUBParser, pageSize: CGSize, style: RDEPUBTextRenderStyle ) -> RDEPUBTextChapter? ``` 2. 内部逻辑从 `build()` 的循环体中提取,单章可独立构建 **Step 2:按需加载** 3. `RDEPUBReaderLoadCoordinator` 在打开书时只构建当前章节和相邻 ±1 章 4. 用户翻到新章节时,后台异步构建该章节 5. 使用 `DispatchQueue` 串行队列保证线程安全 **Step 3:与缓存联动** 6. `RDEPUBPaginationCacheCoordinator` 支持单章缓存命中检查 7. 缓存命中时跳过构建,直接加载 **验收标准**: - 大书首次打开时间缩短(只构建 3 章而非全书) - 翻到未构建章节时无明显卡顿(后台预构建) - 全书构建 API 保持不变(兼容现有调用方) --- ### 11. 缓存管理 **当前状态**:`RDEPUBTextBookCache` 磁盘缓存无大小限制、无淘汰策略、无选择性失效。无内存缓存。 **实施步骤**: **Step 1:磁盘缓存容量控制** 1. 新增 `maxDiskCacheSize: UInt64`(默认 200MB) 2. 在 `save(_:key:)` 写入后检查总大小 3. 超限时按修改时间删除最旧的缓存文件,直到低于阈值 **Step 2:选择性失效** 4. 新增 `invalidate(key:)` 方法,删除指定缓存文件 5. 当用户修改 `fontChoice` 或 `fontSize` 时,只失效当前书的缓存(而非 `invalidateAll`) **Step 3:内存缓存(NSCache)** 6. 在 `RDEPUBTextBookCache` 上层增加 `NSCache` 7. `load(key:)` 先查内存缓存,miss 后查磁盘并回填 8. 内存警告时清空 NSCache **Step 4:缓存统计持久化** 9. `RDEPUBTextBookCache` 新增 `cacheStats` 属性,记录总命中/缺失次数 10. 可用于调试和性能分析 **验收标准**: - 磁盘缓存不超过设定上限 - 修改字号/字体后,旧缓存被正确失效 - 连续打开同一本书时,第二次命中内存缓存(< 1ms) --- ## 三、可访问性 ### 12. VoiceOver **当前状态**:9 个 `accessibilityIdentifier`(仅用于 UI 测试),无 `accessibilityLabel`、`accessibilityHint`、`accessibilityTraits`。阅读内容区域无任何无障碍支持。 **实施步骤**: **Step 1:工具栏无障碍(最低门槛)** 1. `RDEPUBReaderTopToolView`: - `backButton.accessibilityLabel = "返回"` - `bookmarkButton.accessibilityLabel = "书签"` + `accessibilityValue` 反映当前状态(已添加/未添加) - `titleLabel.accessibilityLabel` = 书籍标题 2. `RDEPUBReaderBottomToolView`: - 各按钮补充 `accessibilityLabel`("目录"、"书签列表"、"高亮列表"、"添加高亮"、"设置") - 进度 slider 补充 `accessibilityValue`("第 X 页,共 Y 页") **Step 2:设置面板** 3. `RDEPUBReaderSettingsViewController` 所有控件补充 label 和 traits **Step 3:阅读内容区域(中等难度)** 4. `RDEPUBTextContentView` 设置 `isAccessibilityElement = true` 5. `accessibilityLabel` = 当前页纯文本内容 6. 翻页时发出 `UIAccessibility.pageScrolledNotification` **Step 4:目录和高亮列表** 7. `RDEPUBReaderChapterListController` 列表项设置 `accessibilityLabel`(章节标题 + 页码) 8. `RDEPUBReaderHighlightsViewController` 列表项设置 `accessibilityLabel`(高亮文本 + 章节) **验收标准**: - VoiceOver 用户能完整导航工具栏 - 翻页时 VoiceOver 朗读新页内容 - 目录和高亮列表可被 VoiceOver 逐项朗读 --- ### 13. Dynamic Type **当前状态**:字号由 `RDEPUBReaderConfiguration.fontSize` 控制,不响应系统 `UIContentSizeCategory` 变化。 **实施步骤**: 1. 在 `RDEPUBReaderSettingsViewController` 中监听 `UIContentSizeCategory.didChangeNotification` 2. 当系统字体大小变化时,根据新的 `UIContentSizeCategory` 计算等效字号 3. 或者:在 `RDEPUBReaderConfiguration` 中增加 `useSystemFontSize: Bool`,启用时忽略 `fontSize`,使用系统推荐值 4. 字号映射表(参考 Apple 的 `preferredFont(forTextStyle:)` 返回值) **验收标准**: - 系统字体调大后,阅读器字号自动跟随 - 用户手动调节字号后,覆盖系统设置 --- ### 14. 高对比度 **当前状态**:6 个固定主题预设,不跟随系统 `UIAccessibility.isDarkerSystemColorsEnabled`。 **实施步骤**: 1. `RDEPUBReaderTheme` 增加 `highContrastVariant: RDEPUBReaderTheme?` 属性 2. 监听 `UIAccessibility.darkerSystemColorsStatusDidChangeNotification` 3. 高对比度启用时,自动切换到高对比度主题变体(更大色彩对比度) 4. 或提供独立的"高对比度"主题预设 **验收标准**: - 系统高对比度开启后,阅读器自动切换到高对比度主题 - 关闭后恢复原主题 --- ### 15. DRM **当前状态**:需求文档明确声明 DRM 不在 SDK 范围内。如果需要支持,建议通过以下方式: **建议方案**: 1. 不在 SDK 内实现 DRM,而是通过 `RDEPUBParser` 的输入端控制: - `RDEPUBParser` 接受 `Data` 或 `URL`,调用方可以先解密再传入 - 或新增 `RDEPUBDRMProvider` 协议: ```swift protocol RDEPUBDRMProvider { func decryptedData(for resourceURL: URL) -> Data? func isProtected(_ resourceURL: URL) -> Bool } ``` 2. SDK 内的 `ss-reader://` URL scheme handler 在读取资源时调用 `DRMProvider` 3. 商业 DRM(如 Adobe ADEPT、Readium LCP)由调用方集成,SDK 提供接入点 **验收标准**: - SDK 提供清晰的 DRM 接入协议 - 不引入 DRM 依赖,保持 SDK 轻量 - 调用方能通过协议接入自己的 DRM 方案 --- ## 四、功能完整度(暂未展开) 以下功能需求已识别但暂未进入详细开发计划: | 缺失项 | 严重度 | 说明 | | --- | --- | --- | | **书架/书库管理** | 高 | SDK 只能打开单本书,无书架 UI、阅读历史、分类管理 | | **批注导出/分享** | 高 | 高亮/笔记只有本地存储,无导出、分享、复制到剪贴板 | | **阅读统计** | 中 | 无阅读时长追踪、阅读速度、连续阅读天数 | | **TTS 朗读** | 中 | 微信读书核心功能之一,当前无任何语音相关代码 | | **全局搜索** | 中 | 当前搜索只在单本书内,无跨书搜索 | | **离线/云端同步** | 高 | 无 iCloud/自建同步,阅读进度和笔记只在本地 | | **夜间模式定时切换** | 低 | 有暗色主题但不能跟随系统或定时切换 | --- ## 五、按优先级排序的建议路线 ### P0 — 影响商业发布 1. **分页回归基准** — UI 自动化测试已启动,下一步需要把分页结果、首屏时间、截图差异纳入回归基准 2. **字体选择器增强** — 基础字体选择器已实现,后续需补:更多内置字体包、字体预览、字体资源加载失败兜底 3. **书架/书库管理** — 商业阅读器的入口 4. **暗色模式图片处理增强** — 基础处理已实现,后续可补:按图片亮度自适应混合比例 ### P1 — 影响用户留存 5. **批注导出/分享** — 深度阅读用户的核心需求 6. **阅读统计/时长追踪** — 用户粘性和产品数据的基础 7. **性能基线与大书优化** — 大书卡顿是用户流失的主要原因 8. **简繁转换** — 面向港澳台用户需要 ### P2 — 提升竞争力 9. **TTS 朗读** — 通勤场景、无障碍场景刚需 10. **云端同步** — 多设备用户的基本需求 11. **VoiceOver 完善** — 合规和品牌形象 12. **全局搜索** — 藏书量大时的效率工具 --- ## 六、总结 经过三方对比,渲染质量层面的真实差距比最初评估要小: - **图文分页质量**:双方基本对齐,我们甚至在 `keepWithNext` 上领先 - **真正的差距**:字体包数量、分页回归基准、简繁转换 - **WXRead 也没做的**:竖排、ruby、公式、hyphenation——这些不是必须对齐的 如果目标是"能上架的商业阅读器",当前已补齐字体选择器、暗色模式图片处理和基础 UI 自动化测试。下一步最值得投入的是分页回归基准、更多字体资源和书架/书库管理。