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