` 标签
+- 在其前面注入 `${id=xxx}` 标记
+
+**阶段 8:图片诊断收集** (`RDEPUBRenderDiagnosticsCollector`)
+- 扫描 `
` 标签
+- 解析引用路径,检查文件是否存在
+- 收集诊断信息用于调试
+
+### 2.2 HTML→NSAttributedString
+
+`RDEPUBDTCoreTextRenderer.renderChapter(request:)`:
+
+1. 将 HTML 编码为 `Data`
+2. 使用 `DTHTMLAttributedStringBuilder` 构建 `NSAttributedString`
+3. 在 `willFlushCallback` 中对每个 DOM 元素调用 `RDEPUBAttachmentNormalizer.prepareHTMLElementForReaderRendering()`
+4. 后处理:
+ - `applyPaginationSemantics()` - 将 `${rd-sem-start/end}` 标记转为 NSAttributedString 属性
+ - `extractFragmentOffsets()` - 提取 fragment ID→offset 映射,删除标记文本
+ - `normalizeReadingAttributes()` - 规范化字体、行距、颜色、附件
+
+### 2.3 分页计算
+
+`RDEPUBChapterPageCounter.layoutFrames()` 使用迭代循环:
+
+```
+location = 0
+while location < totalLength:
+ 1. 创建 CTFrame(通过 CTFramesetter)
+ 2. 获取可见范围 (CTFrameGetVisibleStringRange)
+ 3. 应用 avoidPageBreakInside 规则(最多移除 3 行尾部)
+ 4. 应用 keepWithNext 规则(最多移除 3 行尾部)
+ 5. 应用 widow/orphan 控制
+ 6. 应用 pageBreakPolicy 调整
+ 7. 记录页面范围
+ 8. location = 调整后的范围末尾
+```
+
+**分页规则优先级:**
+
+| 规则 | 说明 | 最大调整行数 |
+|------|------|-------------|
+| avoidPageBreakInside | 块内不分页(标题、图片等) | 3 行 |
+| keepWithNext | 标题与正文不分离 | 3 行 |
+| widow control | 段落最后一行不留到下一页 | 1 行 |
+| orphan control | 段落第一行不单独在上一页 | 1 行 |
+| pageRelate | 微信读书跨页关联 | 1 行 |
+| attachment boundary | 块级图片前后分页 | 0(精确切分) |
+
+### 2.4 尾页规范化
+
+`RDEPUBChapterTailNormalizer.normalize()` 三遍处理:
+
+1. **删除空白中间帧:** 无可见字符且无附件的帧
+2. **删除空白尾部帧:** 从末尾开始删除同类空白帧
+3. **合并短尾帧:** 如果最后一帧 ≤2 个可见字符且前一帧 ≥8 倍长,合并
+
+---
+
+## 3. 后台元数据解析(大书优化)
+
+### 3.1 整体流程
+
+对于《凡人修仙传》这样的大书(~1000 章),后台解析是核心性能路径。
+
+```
+打开书籍
+ │
+ ├─ 尝试磁盘缓存恢复(restoreBookPageMapIfPossible)
+ │ └─ 成功 → 直接显示完整页码
+ │
+ ├─ 快速打开(3-5 章窗口)
+ │ ├─ 同步加载首个可渲染章节
+ │ ├─ 加载窗口内相邻章节
+ │ └─ 应用局部 BookPageMap → 用户可立即阅读
+ │
+ └─ 后台解析(paginateMetadataOnly)
+ ├─ 预计算所有章节 contentHash(串行,~1-3s)
+ ├─ 恢复已有磁盘缓存
+ ├─ 等待用户操作冷却 0.8s
+ ├─ 并发渲染未缓存章节(OperationQueue,N=CPU核心数)
+ ├─ 每 32 章增量刷新 UI
+ └─ 最终完整刷新
+```
+
+### 3.2 预计算 contentHash 优化
+
+**问题:** 每个章节在渲染后需要构建缓存键,原始实现会重新读取 HTML 并计算 SHA-256。
+
+**优化:** 在后台解析开始时,串行预计算所有章节的 contentHash:
+
+```swift
+var contentHashBySpineIndex: [Int: String] = [:]
+for spineIndex in allBuildableIndices {
+ let html = parser.htmlString(forRelativePath: href)
+ contentHashBySpineIndex[spineIndex] = html?.sha256Hex ?? ""
+}
+```
+
+后续所有 `chapterCacheKey` 调用都使用预计算值,避免重复 I/O 和 SHA-256 计算。
+
+### 3.3 冻结 renderSignature
+
+**问题:** 如果用户在后台解析进行中更改字号/行距,`context.currentRenderSignature()` 会返回新值,导致部分章节用旧签名、部分用新签名写入磁盘缓存,造成缓存不一致。
+
+**解决:** 在 `paginateMetadataOnly` 开始时冻结签名:
+
+```swift
+let renderSignature = context.currentRenderSignature()
+// 后续所有 chapterCacheKey 调用使用此固定值
+```
+
+token 机制确保设置变更会触发新的解析任务(新 token),旧任务自动废弃。
+
+### 3.4 锁区瘦身
+
+**原始实现:** `resultLock` 内调用 `buildPageMap()`,该方法遍历全量 catalog 和 summaries。
+
+**优化:** 锁内只做写入和计数,快照数据后锁外构建 pageMap:
+
+```swift
+var snapshot: [Int: RDEPUBChapterSummary]?
+resultLock.lock()
+summariesBySpineIndex[spineIndex] = renderResult
+totalResolvedCount += 1
+if shouldRefresh {
+ snapshot = summariesBySpineIndex // 快照
+}
+resultLock.unlock()
+
+if let snapshot {
+ let partialMap = buildPageMap(from: catalog, summaries: snapshot) // 锁外
+}
+```
+
+### 3.5 可配置刷新间隔
+
+```swift
+static var pageMapRefreshInterval: Int = 32 // 默认 32 章
+```
+
+可实测调优:32 / 48 / 64。值越大,UI 刷新频率越低,后台解析吞吐越高。
+
+---
+
+## 4. 章节按需加载
+
+### 4.1 加载优先级
+
+`RDEPUBChapterLoader.loadChapter(spineIndex:store:)` 按以下优先级:
+
+1. **Tier 1 内存缓存** - `store.chapterData(for: spineIndex)` → 直接返回
+2. **Tier 1 页数缓存** - `store.pageCount(for: cacheKey)` → 跳过分页计算
+3. **Tier 2 磁盘缓存** - `summaryDiskCache.read(for: cacheKey)` → 恢复页范围
+4. **全量构建** - 渲染 + 分页 + 写缓存
+
+### 4.2 窗口驱逐策略
+
+`RDEPUBChapterRuntimeStore.setCurrentChapter(spineIndex:totalSpineCount:windowRadius:)`:
+
+- 保留范围:`[spineIndex - windowRadius, spineIndex + windowRadius]`
+- 窗口半径由 `configuration.chapterWindowRadius` 控制
+- 超出窗口的章节从内存缓存中驱逐
+- 内存警告时驱逐除当前章节外的所有缓存
+
+### 4.3 BookPageMap 增量刷新
+
+`RDEPUBBookPageMap` 是轻量页码映射(~100KB/1000 章),不持有 `NSAttributedString`。
+
+Builder 模式支持增量构建:
+
+```swift
+var builder = RDEPUBBookPageMap.Builder()
+for item in catalog {
+ if let summary = summaries[item.spineIndex] {
+ builder.add(spineIndex:href:title:pageCount:fragmentOffsets:)
+ }
+}
+return builder.build()
+```
+
+---
+
+## 5. 翻页容器逻辑
+
+### 5.1 三种翻页模式
+
+| 模式 | 底层实现 | 特点 |
+|------|----------|------|
+| pageCurl | UIPageViewController | 翻页动画,系统手势 |
+| horizontalScroll | UICollectionView + 自定义 Layout | 水平滑动,分页锁定 |
+| verticalScroll | UICollectionView + 自定义 Layout | 垂直滚动,连续滚动 |
+
+### 5.2 双页展开(Landscape Dual Page)
+
+**触发条件:** `landscapeDualPageEnabled && isLandscape && !verticalScroll`
+
+**页面配对逻辑(RDReaderSpreadResolver):**
+
+- 有封面页:封面独占一屏,后续页面两两配对
+ - 配对规则:`(coverIndex, nil)`, `(1, 2)`, `(3, 4)`, ...
+- 无封面页:标准偶奇配对
+ - 配对规则:`(0, 1)`, `(2, 3)`, `(4, 5)`, ...
+
+**封面感知布局(RDReaderFlowLayout):**
+
+```
+封面页: [====全屏宽度====]
+配对页: [==半宽==][==半宽==]
+```
+
+### 5.3 页面预加载
+
+`RDReaderPreloadController` 管理两个缓存:
+- `preloadedPageViews` - 预渲染的页面视图
+- `pageCurlCachedViews` - 当前显示的页面视图
+
+**预加载策略:**
+1. 每次页面变化后调用 `prime(around:preferredForward:)`
+2. 计算预测目标:前后各 `radius` 个展开页 + 预测方向额外 1 个展开页
+3. 将目标页面预渲染到隐藏的 `preloadHostView`
+4. 缓存签名(displayType + isLandscape + pagesPerScreen + boundsSize)变化时清除缓存
+
+### 5.4 点击区域处理
+
+屏幕三等分:
+
+```
+[ 左 1/3 ][ 中 1/3 ][ 右 1/3 ]
+ 上一页 切换工具栏 下一页
+```
+
+RTL 模式下左右互换。工具栏显示时,左右区域变为 `.center`(隐藏工具栏)。
+
+---
+
+## 6. 标注系统
+
+### 6.1 数据模型
+
+```swift
+// 统一标注类型
+struct RDEPUBAnnotation {
+ let id: String
+ let kind: RDEPUBAnnotationKind // .bookmark, .highlight, .underline
+ let location: RDEPUBLocation
+ let text: String?
+ let color: String?
+ let note: String?
+}
+
+// 书签
+struct RDEPUBBookmark {
+ let id: String
+ let location: RDEPUBLocation
+ let chapterTitle: String?
+ let note: String?
+}
+
+// 高亮
+struct RDEPUBHighlight {
+ let id: String
+ let location: RDEPUBLocation
+ let text: String
+ let style: RDEPUBHighlightStyle // .highlight, .underline
+ let color: String
+ let note: String?
+}
+```
+
+### 6.2 选区处理流程
+
+```
+用户长按 → WKWebView 选区变化
+ │
+ ▼
+JS Bridge: ssReaderSelectionChanged
+ │
+ ▼
+RDEPUBReaderAnnotationCoordinator
+ ├─ 解析选区位置 (RDEPUBLocation)
+ ├─ 提取选中文本
+ ├─ 创建 RDEPUBSelection
+ └─ 显示操作菜单(拷贝/高亮/批注)
+```
+
+### 6.3 高亮渲染
+
+文本模式下,高亮通过 NSAttributedString 属性注入:
+
+```swift
+// 注入高亮属性
+attributedString.addAttribute(
+ kRDEPUBHighlightAttributeName, // "com.rdreader.highlight"
+ value: highlight,
+ range: nsRange
+)
+
+// 注入下划线属性
+attributedString.addAttribute(
+ kRDEPUBUnderlineAttributeName, // "com.rdreader.underline"
+ value: highlight,
+ range: nsRange
+)
+```
+
+CoreText 渲染时识别这些自定义属性并绘制高亮背景/下划线。
+
+---
+
+## 7. 搜索系统
+
+### 7.1 全文搜索
+
+`RDEPUBTextSearchEngine.search(keyword:)`:
+
+1. 遍历所有 linear spine 条目(html/xhtml 类型)
+2. 读取 HTML,转为纯文本:
+ - 优先:`NSAttributedString(data:options:documentAttributes:)` with `.documentType: .html`
+ - 回退:正则去除 HTML 标签
+3. 执行大小写不敏感的 `NSString.range(of:options:)` 搜索
+4. 为每个匹配生成:
+ - `progression`:0.0-1.0 的阅读进度
+ - `previewText`:匹配位置前后各 12 字符
+ - `rangeAnchor`:精确的文本锚点
+
+### 7.2 搜索结果导航
+
+搜索结果通过 `RDEPUBSearchState` 管理:
+
+```swift
+struct RDEPUBSearchState {
+ let keyword: String
+ let matches: [RDEPUBSearchMatch]
+ var currentMatchIndex: Int?
+}
+```
+
+WebView 通过 JS Bridge 接收搜索高亮数据,在 DOM 中绘制高亮矩形。
+
+---
+
+## 8. 设置与主题
+
+### 8.1 配置变更流程
+
+```
+用户修改设置
+ │
+ ▼
+RDEPUBReaderConfiguration 更新
+ │
+ ├─ 字号/字体/行距变更 → 触发重新分页
+ │ └─ renderSignature 变化 → 磁盘缓存失效 → 后台重新解析
+ │
+ ├─ 主题变更 → 刷新可见内容
+ │ └─ CSS 层重建(dark/user 层)
+ │
+ ├─ 显示模式变更 → 切换翻页容器
+ │ └─ switchReaderDisplayType()
+ │
+ └─ 列数变更 → 触发重新分页
+ └─ layoutConfig.cacheSignature 变化
+```
+
+### 8.2 主题系统
+
+6 种内置主题,通过 `RDEPUBReaderThemeSelector` 选择:
+
+| 主题 | 背景色 | 文字色 |
+|------|--------|--------|
+| 默认白 | #FFFFFF | #333333 |
+| 护眼绿 | #CCE8CF | #333333 |
+| 牛皮纸 | #E8D8B8 | #333333 |
+| 深灰 | #333333 | #CCCCCC |
+| 纯黑 | #000000 | #B4B4B6 |
+| 暗蓝 | #1A2332 | #8A9BB5 |
+
+暗色主题(深灰/纯黑/暗蓝)注入 `wxread-dark.css` 覆盖层。
+
+### 8.3 持久化策略
+
+设置通过 `RDEPUBReaderPersistence` 协议持久化:
+
+```swift
+protocol RDEPUBReaderPersistence {
+ func saveLocation(_ location: RDEPUBLocation, for bookIdentifier: String)
+ func loadLocation(for bookIdentifier: String) -> RDEPUBLocation?
+ func saveBookmarks(_ bookmarks: [RDEPUBBookmark], for bookIdentifier: String)
+ func loadBookmarks(for bookIdentifier: String) -> [RDEPUBBookmark]
+ func saveHighlights(_ highlights: [RDEPUBHighlight], for bookIdentifier: String)
+ func loadHighlights(for bookIdentifier: String) -> [RDEPUBHighlight]
+}
+```
+
+默认实现使用 `UserDefaults`,键格式:
+- 位置:`ssreader.epub.location.{bookID}`
+- 书签:`ssreader.epub.bookmarks.{bookID}`
+- 高亮:`ssreader.epub.highlights.{bookID}`
+- 设置:`ssreader.epub.settings`
+
+---
+
+## 9. 纯文本 (.txt) 支持
+
+`RDPlainTextBookBuilder` 复用 EPUB 渲染管线处理 .txt 文件:
+
+1. **解码:** 依次尝试 UTF-8 → GB18030 → GBK
+2. **分章:** 正则匹配 `^(第[零一二三四五六七八九十百千万\d]+[章节回卷].*)$`
+3. **包装 HTML:** 每行包裹 `` 标签
+4. **渲染+分页:** 复用 `RDEPUBTextRenderer` 和 `RDEPUBCoreTextPageFrameFactory`
+
+---
+
+## 10. 性能关键路径
+
+### 10.1 首次打开(冷启动)
+
+| 阶段 | 耗时占比 | 优化方向 |
+|------|----------|----------|
+| ZIP 解压 | ~5% | 缓存解压目录 |
+| OPF 解析 | ~1% | SAX 流式解析 |
+| 首章渲染 | ~15% | 快速打开路径 |
+| 后台全量解析 | ~70% | 并发、缓存、预计算 |
+| 磁盘 I/O | ~9% | 异步写入、批量读取 |
+
+### 10.2 二次打开(热缓存)
+
+| 阶段 | 耗时占比 | 说明 |
+|------|----------|------|
+| 磁盘摘要恢复 | ~30% | readAll 批量读取 |
+| BookPageMap 构建 | ~5% | Builder 模式 |
+| UI 应用 | ~65% | 刷新 collection view |
+
+### 10.3 关键优化项
+
+| 优化 | 收益 | 文件 |
+|------|------|------|
+| 预计算 contentHash | 消除每章重复 HTML 读取 + SHA-256 | RDEPUBReaderPaginationCoordinator |
+| 冻结 renderSignature | 避免参数漂移导致缓存不一致 | RDEPUBReaderContext |
+| 锁区瘦身 | 降低并发锁竞争 | RDEPUBReaderPaginationCoordinator |
+| 可配置并发数 | 适配不同设备 | RDEPUBReaderConfiguration |
+| 可配置刷新间隔 | 平衡 UI 响应和吞吐 | RDEPUBReaderPaginationCoordinator |
diff --git a/Doc/CODING_STYLE.md b/Doc/CODING_STYLE.md
deleted file mode 100644
index fdc05c8..0000000
--- a/Doc/CODING_STYLE.md
+++ /dev/null
@@ -1,295 +0,0 @@
-# ReadViewSDK 代码规范
-
-## 适用范围
-
-本文档适用于 `ReadViewSDK` 新增代码与重构代码。
-
-- 规范覆盖 `Sources/` 与 `RDReaderDemo/` 中的 Swift 代码。
-- 命名、分层与职责边界以 SDK 可维护性和可扩展性为优先。
-- 本文档分为两类内容:
- - `已观察到的约定`:当前工程常见写法。
- - `建议统一的规范`:后续统一执行的规则。
-
-## 新架构目录与职责
-
-### 已观察到的约定
-
-- `Sources/RDReaderView`:阅读核心容器、翻页能力与基础视图。
-- `EPUBCore`:EPUB 解析、资源定位、导航状态、分页与会话协调。
-- `EPUBTextRendering`:文本渲染引擎与分页支持。
-- `EPUBUI`:可开箱即用的 Reader UI 层。
-- `RDReaderDemo`:示例应用与调试入口。
-
-### 建议统一的规范
-
-- 新增业务能力优先归入 `Sources/RDReaderView` 下的对应模块目录。
-- `Core` 层只承载解析、会话、状态机与通用能力,不写页面级交互。
-- `UI` 层只承载展示、事件分发和轻量状态同步,不直接处理底层解析逻辑。
-- 若模块持续膨胀,优先在当前模块下继续拆分子文件,不跨目录散落实现。
-
-## 命名规范
-
-### 已观察到的约定
-
-- 当前工程历史命名以 `SS`、`RDEPUB` 开头。
-- 控制器常用 `...Controller`,视图常用 `...View`,会话对象常用 `...Session`。
-- 扩展文件采用 `类型名+功能域.swift`,如 `Parser+Archive.swift`。
-- 事件方法常使用 `Action` 结尾,例如 `pageTapAction()`、`themeChangeAction()`。
-- 绑定数据的方法常使用 `bind`、`update`、`refresh`、`configure` 等动词。
-
-### 建议统一的规范
-
-- **所有新增类型必须以 `RD` 开头。**
-- EPUB 相关类型统一以 `RDEPUB` 开头。
-- 方法名、变量名沿用 Swift 小驼峰,不增加额外前缀。
-- 类型名应反映职责,不使用过宽泛的后缀;只有真正承担协调逻辑时才使用 `Manager`、`Handler`。
-- 事件处理方法统一使用"对象/意图 + Action"命名,例如 `pageTapAction`、`themeChangeAction`。
-- 数据绑定方法优先使用以下语义:
- - `bind...`:将模型绑定到视图或模块。
- - `update...`:增量刷新已有界面或状态。
- - `configure...`:一次性配置样式或依赖。
- - `refresh...`:重新拉取或重建数据状态。
-- 避免新增拼写不一致的方法名;若发现历史命名拼写错误,新增代码必须使用正确拼写,旧接口修复时应配合调用点一起调整。
-
-命名示例:
-
-- `RDReaderView`
-- `RDEPUBParser`
-- `RDEPUBPublication`
-- `RDEPUBReadingSession`
-- `RDEPUBReaderController`
-- `RDEPUBReaderTheme`
-
-扩展文件命名示例:
-
-```text
-RDEPUBParser.swift
-RDEPUBParser+Archive.swift
-RDEPUBParser+Package.swift
-RDEPUBParser+TOC.swift
-RDEPUBParser+Resources.swift
-RDEPUBWebView.swift
-RDEPUBWebView+Configuration.swift
-RDEPUBWebView+Reflowable.swift
-```
-
-## 分层与职责边界
-
-### 已观察到的约定
-
-- 阅读入口负责容器装配、翻页模式切换和事件分发。
-- EPUB 核心层负责解析、导航、分页、资源读取与定位。
-- 渲染层负责 HTML/富文本渲染与分页支持。
-- UI 层负责主题、工具栏、目录、设置等交互能力。
-
-### 建议统一的规范
-
-- `RD...Controller`:负责页面级编排与流程调度,不承载复杂渲染细节。
-- `RD...View`:负责展示与局部交互,不承载完整业务流程。
-- `RDEPUB...Core`:负责解析、会话状态与数据模型,不依赖具体页面。
-- 配置、主题、定位、进度模型统一下沉为 `struct`。
-- 跨层通信优先通过会话层或协议,不做跨层直接写状态。
-
-## UI 与布局规范
-
-### 已观察到的约定
-
-- 视图多采用 `lazy var` 初始化,并在闭包内完成默认配置。
-- 自定义 View 通常在 `init(frame:)` 或业务绑定后调用 `initView()` 完成视图树搭建。
-- 复杂页面使用分区 extension 组织代理与事件实现。
-- 页面或组件内部按职责拆分样式方法,例如 `topBarStyle()`、`contentStyle()`。
-- 页面经常通过回调闭包把交互抛给外层,例如 `pageChangeCallback`、`selectionCallback`。
-- 模块内存在多处布局方式混用情况。
-
-### 建议统一的规范
-
-- SDK 层新增 UI 使用 Auto Layout 原生约束,Demo 层可使用 SnapKit;单文件内不混用多套布局体系。
-- 视图层初始化顺序保持一致:
- - 定义属性与子视图
- - 在 `initView()` 中组装视图树
- - 在独立方法中拆分样式和状态刷新逻辑
-- 当约束会被多次切换时:
- - 首次创建使用 `makeConstraints`(SnapKit)或 `NSLayoutConstraint`
- - 重建结构使用 `remakeConstraints`(SnapKit)或先移除再添加
- - 仅修改常量时使用 `updateConstraints`(SnapKit)或修改 `constant` 属性
-- 布局分支明显时,优先拆成语义化私有方法,不要把所有状态分支堆在一个超长方法里。
-- 对外暴露的 UI 刷新入口建议以 `bind` 或 `update` 开头,避免把布局细节暴露给调用方。
-- 交互事件通过闭包或 delegate 抛出,避免子视图持有上层业务依赖。
-
-## 交互与状态处理规范
-
-### 已观察到的约定
-
-- 事件处理使用 `@objc` + selector。
-- 异步回调中广泛使用 `[weak self]`。
-- 状态判断常通过 `guard` 提前返回。
-
-### 建议统一的规范
-
-- 按钮、通知、系统回调放入独立 extension 分组。
-- 异步闭包默认先使用 `[weak self]`,仅在必要时改强引用。
-- 多前置条件入口统一先 `guard` 校验,减少嵌套。
-- 导航与进度恢复统一通过 `RDEPUBReadingSession` 协调。
-
-## 代码风格细则
-
-### 已观察到的约定
-
-- extension 分区较常见。
-- 解析与渲染模型多数采用 `struct`。
-- 存在少量历史命名不统一与可选值处理不一致情况。
-
-### 建议统一的规范
-
-- 默认遵循”最小可见性”:`private` > `fileprivate` > `internal` > `public`。
-- 纯数据模型使用 `struct` + `Codable` + `Equatable`。
-- 服务对象使用 `final class`,避免无意义继承。
-- 错误类型统一使用 `enum + LocalizedError`。
-- 新增代码避免强制解包;若当前上下文无法避免,至少先在上层收敛边界。
-- 统一优先使用 `guard` 做前置失败处理,减少深层嵌套。
-- extension 的拆分原则以”单一职责”优先:
- - 事件处理一组
- - 代理 / DataSource 实现一组
- - 工具方法一组
- - 通知适配一组
-- 调试代码继续使用 `#if DEBUG` 包裹,不把调试边框、日志、测试分支直接带入正式逻辑。
-
-## 文件组织规范
-
-### 已观察到的约定
-
-- 目录按阅读容器、EPUB Core、渲染、UI 分层组织。
-- 大类通过 `+Extension` 文件拆分职责。
-
-### 建议统一的规范
-
-- 目录保持以下分层,不跨层放置实现:
- - `Sources/RDReaderView/EPUBCore`
- - `Sources/RDReaderView/EPUBTextRendering`
- - `Sources/RDReaderView/EPUBUI`
-- 单文件建议不超过 `600` 行;超出后按职责拆分 extension 文件。
-- extension 文件命名统一 `RD类型名+功能域.swift`。
-
-## 注释规范
-
-- 注释、错误提示、日志统一使用中文。
-- 关键流程方法保留”为什么这样做”的注释,不写重复代码字面行为的注释。
-- 调试输出统一放在 `#if DEBUG` 下。
-- 新代码保留有信息量的注释,避免重复描述显而易见的代码行为。
-
-## 已观察到的项目模式
-
-### 模式 1:统一入口 + 扩展拆分
-
-- `RDEPUBParser` 负责 EPUB 解析主入口,不同能力拆到 `+Archive`、`+Package`、`+TOC`、`+Resources` 等扩展文件。
-- `RDEPUBReadingSession` 负责阅读会话主入口,状态管理、分页、定位等能力拆到扩展文件。
-- 该模式适合继续用于解析器、会话管理、控制器工具方法等横向能力。
-
-### 模式 2:页面编排在 Controller,局部交互下沉到 View
-
-- `RDEPUBReaderController` 负责阅读页整体编排:翻页容器装配、工具栏切换、阅读位置恢复。
-- `RDReaderView` 负责分页容器布局与翻页交互,并通过 DataSource / Delegate 把数据需求交回外层。
-- 该模式保持 Controller 管流程、View 管展示的职责分离。
-
-### 模式 3:列表与容器逻辑通过扩展拆开
-
-- 复杂容器将 `UICollectionViewDataSource`、`UICollectionViewDelegate`、`UICollectionViewDelegateFlowLayout` 分别拆分到 extension。
-- 该模式降低单文件中主逻辑与代理逻辑的耦合,适合继续用于任何包含列表或容器的组件。
-
-### 模式 4:渲染路径抽象
-
-- `RDEPUBReadingProfile` 根据 EPUB 特征自动选择渲染路径(`webFixedLayout` / `webInteractive` / `textReflowable`)。
-- 新增渲染相关功能时,必须评估对三种路径的覆盖情况。
-
-## 待统一项
-
-- 当前访问控制级别存在混用:同一类里 `public`、默认 `internal`、`private` 并存,建议后续新增代码默认从最小可见范围开始声明。
-- 当前存在少量强制解包,建议新增代码优先通过前置校验收敛风险。
-- 当前存在拼写不一致问题,建议后续新增代码统一使用标准英文单词,旧接口如需修复应配合调用点一起调整。
-- 当前部分注释偏”过程说明”或遗留调试注释,建议新代码保留有信息量的注释。
-- 当前个别 View 在数据绑定阶段再次调用 `initView()` 重建界面,这种方式在复杂组件中容易引入重复添加子视图或状态不一致。建议新增组件优先区分”初始化视图结构”和”刷新数据状态”两个阶段。
-
-## 禁忌事项
-
-| 禁忌 | 替代做法 |
-|------|----------|
-| 新增类型不加 `RD` 前缀 | 所有新增类型统一 `RD` / `RDEPUB` 前缀 |
-| UI 层直接拼装解析状态 | 通过 `RDEPUBReadingSession` 获取状态 |
-| 控制器直接操作底层解析细节 | 通过 `RDEPUBPublication`、`RDEPUBParser` 暴露接口 |
-| 强制解包可选值 | `guard let` / `if let` |
-| 用页号单独恢复阅读进度 | 统一使用 `href + progression` |
-
-## 使用建议
-
-- 新增功能前先确定目录归属和职责边界。
-- 命名先定前缀再落代码:类型一律 `RD` 开头。
-- 若需迁移历史 `SS` 前缀,按模块渐进替换,优先替换新增与重构触达文件。
-
-## 旧 SS 命名迁移到 RD 的执行状态
-
-**迁移已完成**:源码中已无 `SS` 前缀类型定义,全部使用 `RD`/`RDEPUB` 前缀。新增代码必须继续遵守此规则。
-
-### 阶段 0:冻结新增 SS 命名(已完成)
-
-- 目标:从当前时点开始,不再引入新的 `SS`/`RDEPUB` 类型名。
-- 动作:
- - 新增类型统一使用 `RD`/`RDEPUB` 前缀。
- - Code Review 增加命名检查项:发现新增 `SS` 命名必须驳回。
- - 在 PR 模板中加入”本次是否新增旧前缀命名”勾选项。
-- 验收:
- - 新提交代码中,新增类型 `SS` 前缀数量为 `0`。
-
-### 阶段 1:建立迁移映射表(第 1 周)
-
-- 目标:明确“旧名 -> 新名”一一映射,避免多人并行改名冲突。
-- 动作:
- - 统计核心公开类型、内部核心类型、测试类型三类清单。
- - 建立命名映射表,例如:
- - `RDReaderView` -> `RDReaderView`
- - `RDEPUBParser` -> `RDEPUBParser`
- - `RDEPUBReadingSession` -> `RDEPUBReadingSession`
- - 对外 API 单独标记“需兼容过渡”的类型。
-- 验收:
- - 映射表覆盖全部高频核心类型,且团队评审通过。
-
-### 阶段 2:先迁移内部类型(第 2-3 周)
-
-- 目标:优先改内部实现,降低外部兼容压力。
-- 动作:
- - 按模块分批迁移:`EPUBCore` -> `EPUBTextRendering` -> `EPUBUI`。
- - 每批次只改一个子模块,避免超大 PR。
- - 同步修复调用点、扩展文件名与注释中的旧命名。
-- 验收:
- - 目标模块内类型命名全部满足 `RD` 规则。
- - 编译通过,Demo 阅读主流程可用。
-
-### 阶段 3:迁移公开 API 并保留兼容层(第 3-4 周)
-
-- 目标:完成对外接口改名,同时给接入方提供平滑升级窗口。
-- 动作:
- - 对外公开类型切换为 `RD` 命名。
- - 旧公开类型保留兼容别名,并标注废弃说明(`deprecated`)。
- - 在 Release Note 提供“旧名/新名对照表”和迁移示例。
-- 验收:
- - 新接入示例仅使用 `RD` 命名。
- - 旧接入代码在兼容期内无需立即改动即可编译。
-
-### 阶段 4:清理兼容层与收口(下一主版本)
-
-- 目标:在约定主版本移除旧前缀,完成命名收口。
-- 动作:
- - 删除 `SS` 兼容别名与过渡代码。
- - 清理文档、注释、示例工程中的旧前缀残留。
- - 对外发布最终迁移公告与升级说明。
-- 验收:
- - 工程内无 `SS`/`RDEPUB` 类型定义残留。
- - 文档与示例代码全部为 `RD`/`RDEPUB` 命名。
-
-### 迁移过程约束
-
-- 每次迁移 PR 必须包含:
- - 命名改动清单
- - 影响范围说明
- - 回归验证结果(编译、Demo 主流程、关键阅读路径)
-- 禁止在同一 PR 中同时做“大规模命名迁移 + 业务逻辑重构”。
-- 若改名会影响外部接入,必须先补迁移文档再合并代码。
diff --git a/Doc/EPUBCore_功能实现逻辑.md b/Doc/EPUBCore_功能实现逻辑.md
deleted file mode 100644
index 120b368..0000000
--- a/Doc/EPUBCore_功能实现逻辑.md
+++ /dev/null
@@ -1,402 +0,0 @@
-# EPUBCore 功能实现逻辑
-
-## 1. 范围与目标
-
-- 代码范围:`Sources/RDReaderView/EPUBCore/`(31 个 Swift 文件 + 2 个资源文件)
-- 目标:说明 EPUBCore 如何完成 EPUB 解析、资源定位、阅读会话管理、离屏分页、JS 桥接渲染和全文搜索。
-- 主链路关键词:`epubURL -> RDEPUBParser.parse -> container.xml -> OPF -> spine/TOC -> RDEPUBPublication -> RDEPUBReadingSession -> RDEPUBWebView/Paginator -> 分页/渲染`。
-
-## 2. 关键对象职责
-
-### 2.1 解析器 `RDEPUBParser`
-
-- 文件:`EPUBCore/RDEPUBParser.swift` + 5 个扩展文件
-- 入口方法:`parse(epubURL:)`
-- 职责:
- - ZIP 解压到缓存目录(`RDEPUBParser+Archive.swift`)
- - 解析 `META-INF/container.xml` 定位 OPF 根文件
- - 解析 OPF 文档提取 metadata / manifest / spine(`RDEPUBParser+Package.swift`)
- - 解析目录支持 NCX(EPUB 2)和 Nav Document(EPUB 3)(`RDEPUBParser+TOC.swift`)
- - 判断阅读配置文件类型(`RDEPUBParser+ReadingProfile.swift`)
- - 资源路径转换:相对路径 ↔ 文件 URL ↔ `ss-reader://` 自定义 scheme URL(`RDEPUBParser+Resources.swift`)
-
-### 2.2 Publication 门面 `RDEPUBPublication`
-
-- 文件:`EPUBCore/RDEPUBPublication.swift`
-- 职责:
- - 包装 `RDEPUBParser` + `RDEPUBResourceResolver`,对外暴露只读接口
- - 提供 metadata、manifest、spine、TOC、layout、readingProfile、readingProgression
- - 计算 fixed layout 的 spread 组合:`makeFixedSpreads(preferences:viewportSize:)`
-
-### 2.3 阅读会话 `RDEPUBReadingSession`
-
-- 文件:`EPUBCore/RDEPUBReadingSession.swift` + `RDEPUBNavigatorState.swift`
-- 职责:
- - 管理导航状态机:`initializing -> loading -> idle <-> jumping/moving/repaginating`
- - 维护活跃/暂存分页快照(`activePages` / `activeChapters` / `stagedPages` / `stagedChapters`)
- - 管理待定导航目标(`pendingNavigationLocation` / `pendingNavigationPageNum`)
- - 更新阅读上下文(`currentReadingContext`:location + viewport + pageNumber + chapterIndex)
- - 构建分页快照:`makePaginationSnapshot(pageCounts:preferences:layoutContext:)`
-
-### 2.4 WebView 渲染层 `RDEPUBWebView`
-
-- 文件:`EPUBCore/RDEPUBWebView.swift` + 5 个扩展文件(+Configuration / +Reflowable / +FixedLayout / +JavaScriptBridge / +Search)
-- 职责:
- - 内部持有 WKWebView,配置自定义 scheme handler 和 JS 桥接
- - 可重排内容加载:`loadPage(parser:spineIndex:pageIndex:...)`(`+Reflowable.swift`)
- - 固定版式内容加载:`loadFixedSpread(parser:spread:...)`(`+FixedLayout.swift`)
- - 处理 JS 桥接消息和链接导航拦截(`+JavaScriptBridge.swift`)
- - 搜索高亮装饰(`+Search.swift`)
- - 定义 `RDEPUBWebViewDelegate` 协议(6 个回调方法)
-
-### 2.5 离屏分页器 `RDEPUBPaginator`
-
-- 文件:`EPUBCore/RDEPUBPaginator.swift`
-- 职责:
- - 创建隐藏的 WKWebView 加载每个 spine 项的 HTML
- - 注入分页 CSS,运行 JS 测量 `scrollWidth`
- - 计算 `ceil(scrollWidth / viewportWidth)` 作为页数
- - 多次测量取最大值(延迟 0ms / 80ms / 180ms)确保布局稳定
-
-### 2.6 资源解析 `RDEPUBResourceResolver` + `RDEPUBResourceURLSchemeHandler`
-
-- 文件:`EPUBCore/RDEPUBResourceResolver.swift`、`EPUBCore/RDEPUBResourceURLSchemeHandler.swift`
-- 职责:
- - `RDEPUBResourceResolver`:对外门面,提供 href 标准化、spine 索引查找、Location 构建
- - `RDEPUBResourceURLSchemeHandler`:实现 `WKURLSchemeHandler`,将 `ss-reader://book/` 映射到解压后的文件系统路径,缺失的可选资源(字体/图片/CSS/JS)返回空 200
-
-### 2.7 JS 桥接
-
-- 文件:`EPUBCore/RDEPUBJavaScriptBridge.swift`、`Resources/epub-bridge.js`
-- 6 种消息类型(JS → Swift):
- - `ssReaderProgressionChanged`:阅读进度变化
- - `ssReaderSelectionChanged`:文本选择变化
- - `ssReaderInternalLink`:内部链接点击
- - `ssReaderExternalLink`:外部链接点击
- - `ssReaderJSError`:JS 错误
- - `ssReaderFixedLayoutReady`:固定版式渲染完成
-- JS 端 `window.RDReaderBridge` 暴露:`applyPagination`、`setPageMetrics`、`scrollToPage`、`scrollToLocation`、`setHighlights`、`clearHighlights`、`reportProgression`、`selectionPayload`
-
-### 2.8 搜索引擎 `RDEPUBSearchEngine` / `RDEPUBHTMLSearchEngine`
-
-- 文件:`EPUBCore/RDEPUBSearchEngine.swift`、`EPUBCore/RDEPUBSearchModels.swift`
-- 职责:
- - `RDEPUBSearchEngine`:搜索协议定义
- - `RDEPUBHTMLSearchEngine`:WebView 路径实现,将 HTML 转为纯文本(NSAttributedString 或正则兜底),执行大小写不敏感的全文搜索
- - 搜索模型:`RDEPUBSearchMatch`、`RDEPUBSearchResult`、`RDEPUBSearchState`、`RDEPUBSearchPresentation`
-
-### 2.9 配置与样式
-
-- `RDEPUBPreferences`(`EPUBCore/RDEPUBPreferences.swift`):用户阅读偏好(字号、行高、内容边距、主题色、固定版式适配模式),构建 `RDEPUBPresentationStyle` 和 `RDEPUBRenderRequest`
-- `RDEPUBNavigatorLayoutContext`(`EPUBCore/RDEPUBNavigatorLayoutContext.swift`):容器布局上下文(容器尺寸、每屏页数、安全区域、设备类型)
-- `RDEPUBStyleSheetBuilder`(`EPUBCore/RDEPUBStyleSheetBuilder.swift`):生成分页 CSS、渲染 CSS 和测量 JS 脚本
-- `RDEPUBFixedLayoutTemplate`(`EPUBCore/RDEPUBFixedLayoutTemplate.swift`):生成固定版式 HTML 模板
-
-### 2.10 文本锚点 `RDEPUBTextAnchor` / `RDEPUBTextRangeAnchor`
-
-- 文件:`EPUBCore/RDEPUBTextAnchor.swift`
-- 职责:
- - `RDEPUBTextAnchor`:精确定位到 EPUB 中的某个字符位置,包含 fileIndex(spine 索引)、row(行号)、column(列号)、chapterOffset(章节内字符偏移)、fragmentID(最近的 fragment)
- - `RDEPUBTextRangeAnchor`:由起止锚点组成的文本区间,可转换为 NSRange
- - 支持 Codable 序列化,兼容 `fileIndex` 和 `spineIndex` 两种 key
- - 用于文本 EPUB 的高亮精确锚定和跨会话恢复
-
-### 2.11 渲染请求模型 `RDEPUBRenderRequest`
-
-- 文件:`EPUBCore/RDEPUBRenderRequest.swift`
-- 职责:
- - `RDEPUBFixedLayoutFit`:固定版式适配模式枚举(`.auto` / `.page` / `.width`)
- - `RDEPUBFixedLayoutSpreadMode`:Spread 显示模式枚举(`.automatic` / `.always` / `.never`)
- - `RDEPUBPresentationStyle`:WebView 和 Paginator 共用的视觉参数(viewportSize、contentInsets、fontSize、lineHeightMultiple、主题色)
- - `RDEPUBReflowableRenderRequest`:可重排内容渲染请求(spineIndex、href、pageIndex、presentation、highlights、searchPresentation)
- - `RDEPUBFixedRenderRequest`:固定版式渲染请求(spread、viewportSize、fit、searchPresentation)
- - `RDEPUBRenderRequest`:统一枚举(`.reflowable` / `.fixed`),WebView 根据此类型选择渲染路径
-
-### 2.12 调试工具 `RDEPUBWebViewDebug`
-
-- 文件:`EPUBCore/RDEPUBWebViewDebug.swift`
-- 职责:
- - WebView 调试日志工具集,DEBUG 模式默认开启
- - 支持导航事件、JS 执行、消息接收、URL Scheme 任务的日志记录
- - 可通过 UserDefaults `"RDEPUBWebViewDebugEnabled"` 覆盖开关
-
-### 2.13 资源加载器 `RDEPUBAssetRepository`
-
-- 文件:`EPUBCore/RDEPUBAssetRepository.swift`
-- 职责:
- - 从资源包加载 JS 桥接脚本(`epub-bridge.js`)和固定版式 HTML 模板(`epub-fixed-layout.html`)
- - 支持 `{{token}}` 模板变量替换
- - 自动定位 `RDReaderViewAssets.bundle`(优先已解析子 bundle,兜底宿主 bundle)
-
-## 3. 主流程(代码级)
-
-### 3.1 EPUB 解析全流程
-
-1. 入口:`RDEPUBParser.parse(epubURL:)`。
-2. 调用 `extractArchiveIfNeeded(epubURL:)`:
- - 缓存路径:`~/Library/Caches/ssreaderview-epub/--/`
- - 若目录已存在则跳过解压
- - 否则用 ZIPFoundation 解压 ZIP 到缓存目录
-3. 解析 `META-INF/container.xml`:
- - `ContainerXMLParserDelegate`(SAX)找到第一个 `` 的 `full-path` 属性
-4. 解析 OPF 文档:
- - `OPFPackageParserDelegate`(SAX)分段解析 metadata / manifest / spine
- - 提取 `` 版本和 unique-identifier
- - 提取 `` / `` / `` / ``
- - 提取 `` 判断 fixed/reflowable
- - 提取 manifest 每个 `- ` 为 `RDEPUBManifestItem`
- - 提取 spine 每个 `` 为 `OPFSpineReference`
-5. 构建 spine:`buildSpine(from:opfURL:)`
- - 将 spine reference 匹配到 manifest item
- - 标准化 href(相对于 OPF 目录)
- - 确定每项的有效 layout(显式属性覆盖出版级设置)
- - 返回 `[RDEPUBSpineItem]`,空 spine 抛 `emptySpine` 错误
-6. 解析目录:`parseTOC(from:opfURL:)`
- - 优先 NCX(`NCXParserDelegate`),其次 Nav Document(`NavDocumentParserDelegate`)
- - 兜底使用 spine 标题
- - href 标准化保留 fragment 标识符
-7. 创建 Publication:`makePublication()` → `RDEPUBPublication(parser: self)`
-
-### 3.2 阅读配置文件判断
-
-- 入口:`RDEPUBParser+ReadingProfile.swift`
-- 判断逻辑:
- - 扫描 manifest 检查是否有 `scripted` 属性的项
- - 扫描 HTML body 检查是否有 `script`、`iframe`、`video` 等交互元素
- - 结果:
- - `.webFixedLayout`:固定版式 EPUB(漫画、绘本)— WKWebView 渲染
- - `.webInteractive`:可重排 + 交互脚本 EPUB — WKWebView 渲染
- - `.textReflowable`:纯文本可重排 EPUB — DTCoreText 渲染
-
-### 3.3 离屏分页测量
-
-1. 入口:`RDEPUBPaginator.calculate(parser:hostingView:presentation:completion:)`
-2. 初始化所有 spine 项页数为 `[1, 1, ..., 1]`
-3. 固定版式直接返回(每 spread = 1 页)
-4. 顺序遍历可渲染的 HTML/XHTML spine 项
-5. 对每项:
- - `webView.loadFileURL(...)` 加载文件
- - `didFinish` 后启动多次测量(`scheduleMeasurementPass`)
- - 3 次测量延迟 [0ms, 80ms, 180ms]
- - 每次执行 `measurementScript`:注入分页 CSS → 测量 `scrollWidth` → 返回 `Math.max(1, Math.ceil(totalWidth / viewportWidth))`
- - 取多次测量的最大值
-6. 全部完成后回调 `completion([Int])` 页数数组
-7. `activeSessionID`(UUID)防止过期回调污染结果
-
-### 3.4 WebView 可重排内容加载
-
-1. 入口:`RDEPUBWebView.loadPage(parser:spineIndex:pageIndex:...)`
-2. 计算 `loadSignature`(包含 publication key、spine index、href、viewport、字号、行高、主题色、目标位置、高亮等)
-3. 若签名与 `currentLoadSignature` 相同且已渲染/正在加载,跳过重复请求
-4. 若签名匹配但文档 URL 已加载,仅调用 `applyPresentation()` 重新应用样式
-5. 否则 `handleReflowableLoad(...)` 执行完整加载:
- - 读取 HTML 内容
- - 调用 `applyPresentationScript(for:)` 生成 JS
- - JS 执行:`RDReaderBridge.applyPagination(css)` → `setPageMetrics` → 清除并重新应用高亮 → 滚动到目标位置 → 报告进度 → 应用搜索装饰
-
-### 3.5 固定版式内容加载
-
-1. 入口:`RDEPUBWebView.loadFixedSpread(parser:spread:...)`
-2. `RDEPUBFixedLayoutTemplate.html(for:publication:)` 从模板生成 HTML,包含 spread 中每个资源的 `