Epub阅读器0.0.1

This commit is contained in:
shen
2026-05-21 19:40:51 +08:00
commit daa36d8fe7
559 changed files with 106266 additions and 0 deletions
@@ -0,0 +1,398 @@
# 书签能力方案讨论
## 需求背景
`Doc/EPUB_MAINTENANCE.md` 已将“书签能力”列为 EPUB 阅读器下一阶段的高优先级能力之一。
当前阅读器已经具备:
1. EPUB 打开与解析
2. WebView / DTCoreText / fixed-layout 三条正文渲染路径
3. 目录跳转、阅读位置恢复、主题与字号调整
4. 高亮、划线、批注、搜索等阅读交互能力
但现阶段“位置记录”仍主要依赖:
1. 自动保存的上次阅读位置
2. 需要选中文本的高亮 / 批注能力
这会遗漏一种非常常见、也非常轻量的阅读诉求:
1. 只想标记当前位置
2. 稍后回来继续读
3. 不需要摘录正文
4. 不需要输入批注
因此,“书签能力”这项需求的目标,不是扩展现有标注功能,而是补上一条面向“位置收藏”的轻量链路,用来覆盖“只记录位置、不做摘录”的常见阅读需求。
## 代码事实
### 已有基础
当前仓库其实已经具备不少可直接复用的基础设施:
1. `RDEPUBLocation` 已能稳定表达 `href + progression + fragment`
2. `RDEPUBReaderController` 已具备“当前位置读取”和“跳转到位置”的主链路
3. `RDEPUBReaderPersistence` 已形成按 `bookIdentifier` 分书持久化的模式
4. 高亮管理页已经具备“列表展示 -> 点击跳转 -> 删除”的成熟交互范式
结论:
1. 书签不需要重新设计位置模型
2. 书签可以复用现有定位和跳转能力
3. 更需要新增的是独立模型、持久化存储位和 UI 入口
### 当前实现存在的问题
#### 问题 1:当前只有单一阅读位置,没有“多书签”能力
`RDEPUBReaderPersistence` 当前只提供:
1. `loadLocation / saveLocation`
2. `loadHighlights / saveHighlights`
3. `loadReaderSettings / saveReaderSettings`
这意味着:
1. 系统只能保存“上次读到哪”
2. 不能保存“用户主动收藏的多个位置”
而这两者的语义并不相同:
1. 阅读位置是自动覆盖的
2. 书签是主动创建、可长期保留多个的
#### 问题 2:当前标注模型不适合直接承载书签
虽然看起来可以用 `RDEPUBHighlight` 勉强模拟书签,但这会带来明显问题:
1. 书签不一定有选中文本
2. 书签不应依赖 `rangeInfo`
3. 书签列表应以“位置”为中心,而不是以“摘录内容”为中心
4. 书签与高亮、划线、批注的展示和管理语义不同
因此,不建议把书签硬塞进现有高亮模型。
#### 问题 3:当前 UI 没有书签入口与状态反馈
从 `RDEPUBReaderBottomToolView` 当前结构来看,只有:
1. 目录
2. 批注列表
3. 添加标注
4. 设置
当前并没有:
1. 当前页添加 / 取消书签入口
2. 书签列表入口
3. 当前阅读位置是否已加书签的状态反馈
这意味着即便补了数据层,用户仍然无法自然感知和使用这项能力。
## 需求拆解
建议把“书签能力”拆成四部分推进。
### 1. 独立书签模型
需要新增一个面向“位置记录”的独立模型,而不是复用高亮模型。
### 2. 独立书签持久化
需要新增书签读写接口,支持每本书存储多个书签。
### 3. 书签入口与状态反馈
需要让用户在当前页能轻量创建 / 取消书签,并能看到当前位置的书签状态。
### 4. 书签管理与跳转
需要补齐书签列表、点击跳转、删除等基本管理闭环。
## 技术路线对比
### 路线 A:把书签并入 `RDEPUBHighlight`
思路:
1. 给 `RDEPUBHighlight` 增加一个新的 style 或特殊标识
2. 将书签存入 highlights
3. 复用现有标注列表页
优点:
1. 表面上改动范围较小
2. 可以快速借用现有列表管理代码
风险:
1. 模型语义不清晰
2. 书签没有文本选区时会很别扭
3. 后续展示、筛选、持久化逻辑会越来越绕
4. 容易让“书签”和“标注”边界变得混乱
### 路线 B:完全独立一条书签链路
思路:
1. 新建 `RDEPUBBookmark`
2. 新增 `loadBookmarks / saveBookmarks`
3. 新增书签列表页
4. 独立接入添加、删除、跳转、状态更新
优点:
1. 模型语义清晰
2. 更符合产品能力边界
3. 后续扩展空间更好
风险:
1. 会新增一部分 UI 与管理代码
2. 与高亮列表在形态上会有一定重复
### 路线 C:模型独立,交互模式局部复用
思路:
1. 数据模型与持久化独立
2. 定位、跳转和列表交互模式尽量复用现有高亮链路
3. 第一阶段先做轻量闭环,再考虑更复杂管理能力
优点:
1. 语义清晰,同时控制改动范围
2. 能复用现有阅读位置链路和管理交互经验
3. 更适合当前仓库的演进方式
风险:
1. 仍需补一组新的数据与列表代码
2. 需要明确书签状态判定规则,避免重排后误判
### 推荐结论
建议采用:
**主路线:路线 C,模型独立,位置链路复用,UI 先做轻量闭环**
原因:
1. 书签和标注的语义不同,模型应保持独立
2. 当前仓库已有成熟的位置和跳转主链路,可以直接复用
3. 第一阶段先满足“记录位置”的核心需求,不需要把书签做得过重
## 推荐方案
### 方案总览
书签能力第一阶段建议形成如下结构:
1. 新增 `RDEPUBBookmark` 独立模型
2. 扩展 `RDEPUBReaderPersistence`,新增 bookmarks 读写
3. 在 `RDEPUBReaderController` 中接入书签状态、添加、删除、跳转能力
4. 增加轻量级书签入口和书签列表页
5. 支持退出重进后的书签持久化恢复
### 推荐数据模型
建议书签模型至少包含以下字段:
1. `id`
2. `bookIdentifier`
3. `location`
4. `chapterTitle`
5. `displayText` 或摘要信息(可选)
6. `note`(可选)
7. `createdAt`
其中第一阶段真正必要的最小集合是:
1. `id`
2. `bookIdentifier`
3. `location`
4. `createdAt`
建议同时补上 `chapterTitle`,这样书签列表在展示时会更友好,也更接近真实阅读器使用习惯。
### 推荐持久化结构
建议在 `RDEPUBReaderPersistence` 中新增:
1. `loadBookmarks(for:)`
2. `saveBookmarks(_:for:)`
`RDEPUBUserDefaultsPersistence` 中新增:
1. `bookmarksPrefix`
第一阶段继续沿用当前:
1. `UserDefaults`
2. `JSONEncoder / JSONDecoder`
3. `bookIdentifier` 作用域隔离
这样能在最小改动下形成可用闭环。
### 推荐 UI 入口
第一阶段建议至少补两类入口:
1. 当前页添加 / 取消书签入口
2. 书签列表入口
比较自然的放置方式是:
1. 顶部工具栏增加“当前页书签”切换按钮
2. 底部工具栏增加“书签列表”入口
这样可以把两种动作区分开:
1. “加书签”是当前上下文动作
2. “看书签列表”是全局管理动作
### 推荐交互闭环
第一阶段建议用户路径收敛成下面这条主链路:
1. 用户在当前阅读位置点击书签按钮
2. 如果当前语义位置未加书签,则创建书签
3. 如果当前语义位置已加书签,则取消该书签
4. 用户可从书签列表查看当前书籍的全部书签
5. 点击任意书签后跳转到对应位置
6. 支持从列表中删除书签
## 关键实现建议
### Task K1:新增书签模型
文件:
1. `Sources/RDReaderView/EPUBCore/RDEPUBReadingModels.swift`
改动:
1. 新增 `RDEPUBBookmark: Codable, Equatable`
2. 字段以 `location` 为核心,不依赖选区文本
完成定义:
1. 可以表达一本书中的一个可持久化书签位置
### Task K2:扩展书签持久化协议
文件:
1. `Sources/RDReaderView/EPUBUI/RDEPUBReaderPersistence.swift`
改动:
1. 协议增加 `loadBookmarks / saveBookmarks`
2. `RDEPUBUserDefaultsPersistence` 增加 `bookmarksPrefix`
完成定义:
1. 每本书可以稳定读取和保存多个书签
### Task K3:控制器接入书签状态与操作
文件:
1. `Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift`
改动:
1. 增加 `activeBookmarks`
2. 打开图书时加载 bookmarks
3. 增加 `addBookmark`
4. 增加 `removeBookmark`
5. 增加 `toggleBookmark`
6. 增加 `go(to: bookmark)`
7. 增加当前阅读位置是否已书签的状态计算
完成定义:
1. 书签与阅读主链路、跳转链路、持久化链路打通
### Task K4:补书签 UI 与列表管理
文件:
1. `Sources/RDReaderView/EPUBUI/RDEPUBReaderBottomToolView.swift`
2. 新增 `Sources/RDReaderView/EPUBUI/RDEPUBReaderBookmarksViewController.swift`
3. 视情况补充顶部工具栏按钮
改动:
1. 添加书签入口
2. 添加书签列表入口
3. 点击书签跳转
4. 支持删除
5. 提供当前位置书签状态反馈
完成定义:
1. 用户能看到、添加、取消、查看和管理书签
## 当前页书签判定建议
“当前位置是否已加书签”不建议简单按“完全相同的 `progression`”判断,因为:
1. 字号变化会触发重分页
2. 视口尺寸变化会导致 progression 细微漂移
3. WebView 和文本分页的进度精度并不完全一致
更合理的判定策略是:
1. 优先比较标准化后的 `href`
2. 如果存在 `fragment`,优先使用 `fragment` 作为更强锚点
3. reflowable 路径允许一定 progression 容差
4. fixed-layout 路径优先按页级位置判断
这样可以减少用户在重排、重进或轻微位置变化时的书签状态抖动。
## 验收矩阵建议
第一阶段至少要覆盖以下验证维度:
1. 当前页可添加书签
2. 同一位置可取消书签
3. 书签列表展示正常
4. 点击书签可以跳转
5. 删除书签后列表更新正确
6. 退出重进后书签仍存在
7. 字号变化后书签仍能回到同章节或邻近语义位置
8. `webInteractive` 路径正常
9. `textReflowable` 路径正常
10. `webFixedLayout` 路径至少能按页恢复
## 风险与注意点
### 风险 1:把书签与高亮强行合并
这样短期看似省事,长期会让模型、持久化和 UI 语义都变重。
### 风险 2:过度依赖精确 progression 匹配
如果书签命中规则太严格,重分页后会显得“不稳定”,用户体验会很差。
### 风险 3:第一阶段把书签做得过重
书签能力的首要目标是“轻量记录位置”,不是一开始就做成复杂的收藏管理系统。
## 推荐实施顺序
如果目标是“最小投入换最大需求闭环”,建议按下面顺序推进:
1. 先补独立 `RDEPUBBookmark` 模型
2. 先扩展 `RDEPUBReaderPersistence` 的书签读写能力
3. 再在 `RDEPUBReaderController` 接入书签主链路
4. 最后补 UI 入口和书签列表页
5. 待第一阶段稳定后,再考虑备注、排序、筛选等增强能力
## 推荐结论
“书签能力”这项需求,建议最终收敛为下面这句话:
**为 EPUB 阅读器补齐一条独立的轻量书签链路,以 `RDEPUBLocation` 为核心记录位置,支持添加、取消、列表管理、跳转与持久化恢复,用来覆盖“只记录位置、不做摘录”的常见阅读需求。**