# Phase 8: 分页质量、缓存与性能采样 - Context **Gathered:** 2026-05-23 **Updated:** 2026-05-23 (research corrected: D-01/D-02/D-04/D-05 already implemented) **Status:** Ready for planning **Source:** ARCHITECTURE-CONTEXT.md + REQUIREMENTS.md (QUAL-01 ~ QUAL-04) + 08-RESEARCH.md --- ## Phase Boundary Phase 8 在 Phase 6(页面几何)和 Phase 7(自定义属性闭环)之上,解决三个问题: 1. **缓存**:相同视口 + 排版配置下,同一章节不重复全量排版 (QUAL-01) 2. **分页质量**:复杂图文章节减少粗暴截断、孤行/寡行、图片留白异常 (QUAL-02, QUAL-03) 3. **性能诊断**:输出稳定的采样数据,确保不出现明显退化 (QUAL-04) 本阶段**不**涉及 CoreText 直接绘制迁移(v1.2+),**不**涉及字体系统。 实现时可直接参考读书 (WXRead) 的对应实现模式。 ### 已完成的能力(研究发现) 以下能力在之前的 Phase 中已经实现,Phase 8 不需要重新实现: - **D-01 分页引擎集成**: `RDEPUBTextBookBuilder.build()` 已调用 `content.rd_paginatedFrames(size:)`(非旧的 `ss_pageRanges`) - **D-02 分页元数据暴露**: `RDEPUBTextPageMetadata` 已定义并包含 breakReason/blockKinds/semanticHints - **D-04 CSS `` 内联**: 已在渲染前处理 `` 标签 - **D-05 5 层 CSS 级联**: `RDEPUBTextStyleSheetPackage`/`RDEPUBTextStyleSheetLayer` 已在使用 ## Implementation Decisions ### D-01: 分页缓存 — RDEPUBTextBook 序列化 [P0, QUAL-01] 按 `bookID + fontSize + lineHeightMultiple + contentInsets` 生成缓存 key,将完整 `RDEPUBTextBook` 序列化到磁盘。 - **WXRead 参考:** `WRChapterPageCount.currentCacheKeyWithBookId:` 按排版设置缓存 - **关键文件:** `RDEPUBTextBookBuilder.swift`,新增 `RDEPUBTextBookCache.swift` - **技术决策:** - 缓存 key: `SHA256("\(bookID)_\(fontSize)_\(lineHeightMultiple)_\(contentInsets)")` + schema version - 存储格式: `NSKeyedArchiver`(`NSAttributedString` 支持 `NSCoding`,`NSRange` 同理) - 需运行时验证 DTCoreText 自定义属性在 `NSKeyedArchiver` 下的持久性 - 失效策略: 参数变化自动失效 + schema version bump 强制失效 - 存储位置: `Library/Caches` 子目录 ### D-02: 分页质量增强 — 避让规则执行 [P0, QUAL-02] `RDEPUBTextLayouter` 中 `avoidPageBreakInside` 的语义标记需要实际执行避让(当前仅有标记无行为),并增加孤行/寡行控制。 - **WXRead 参考:** `WRCoreTextLayoutFrame.avoidPageBreakInsideByRemovingLastLinesIfNeeded` — CSS `avoid-page-break-inside` 实现 - **关键文件:** `RDEPUBTextLayouter.swift`、`RDEPUBTextLayoutFrame.swift` - **技术决策:** - `avoidPageBreakInside`: 检测 block 最后 1-2 行被切到下一页时,将整个 block 移到下一页 - 孤行控制: 页首不允许出现段落最后一行(orphan),页尾不允许出现段落第一行(widow) - 增加 `RDEPUBTextLayoutConfig` 结构体封装分页配置(孤行/寡行阈值等),或先硬编码合理默认值 ### D-03: 图片/附件处理规则 [P1, QUAL-03] 图片尺寸策略、暗色模式适配、页面背景类信息有明确处理规则和诊断证据。 - **WXRead 参考:** `wr-vertical-center-style`(图片垂直居中)、`DTPageBreakInsideAvoid`(断页避让) - **关键文件:** `RDEPUBTextLayoutFrame.swift`、`RDEPUBTextRendererSupport.swift`、`RDEPUBDTCoreTextRenderer.swift` - **技术决策:** - 图片 fit: 超过页面高度的图片按比例缩放到页面内,不跨页 - 图片居中: 附件类 block 默认垂直居中处理 - 暗色模式: 图片在暗色模式下保留原始颜色(不反色),但为纯文本装饰元素提供适配 - 诊断: 每个 attachment block 输出尺寸、placement、是否触发缩放 ### D-04: 性能采样与诊断输出 [P1, QUAL-04] 分页深化后不显著恶化首屏时间、重分页耗时或交互流畅度;输出稳定采样数据。 - **关键文件:** `RDEPUBTextBookBuilder.swift` - **技术决策:** - 采样点: 每章渲染耗时、每章分页耗时、全书构建总耗时、缓存命中/未命中 - 输出方式: `RDEPUBTextBookBuilder` 回调中新增 `buildDiagnostics` 字段,包含耗时和缓存状态 - 不引入外部性能监控库,使用 `CFAbsoluteTimeGetCurrent()` 采样 ### Claude's Discretion - 缓存存储的线程安全策略(串行队列 vs 锁) - `RDEPUBTextLayoutConfig` 是新增结构体还是扩展已有类型 - 性能诊断的阈值告警(可选,初期仅输出数据不告警) ### 强制约束:编译验证 每个 task 代码编写完成后,**必须**通过 XcodeBuildMCP 工具执行编译验证,确认无编译错误后方可标记 task 完成。若有编译问题需立即修复并重新验证。 - 工具: XcodeBuildMCP (`build_sim` / `run_build_sim`) - 配置: workspace = `ReadViewDemo/ReadViewDemo.xcworkspace`, scheme = `ReadViewDemo`, simulator = `iPhone 17 Pro` - 流程: 代码修改 → XcodeBuildMCP build → 有错误则修复 → 重新 build → 通过后继续 ## Canonical References **Downstream agents MUST read these before planning or implementing.** ### Phase 8 核心文件 - `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift` — 书籍构建器,已调用 rd_paginatedFrames - `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextPaginationSupport.swift` — 分页支持 - `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextLayouter.swift` — CoreText 分页引擎,4 级语义断页 - `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextLayoutFrame.swift` — 帧模型 - `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift` — 渲染协议、样式表类型 - `Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift` — DTCoreText 渲染实现 - `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift` — 片段标记、属性标准化 ### WXRead 逆向参考(实现时可直接参考) - `Doc/WXRead/decompiled-doc.md` — 44 个逆向文件的职责说明 - `Doc/WXRead/resources-doc.md` — CSS/JS 资源文件清单与职责 - `Doc/WXRead/读书EPUB阅读器实现架构.md` — 双渲染引擎架构总览 ### 架构文档 - `.planning/ARCHITECTURE-CONTEXT.md` — 4 Area 决策总览 - `Doc/架构对比分析_WXRead_vs_ReadViewSDK.md` — 10 维度对比 - `08-RESEARCH.md` — 研究产出(本目录下) ### 前置 Phase 成果 - Phase 6 SUMMARY: 页面几何与交互命中层 - Phase 7 SUMMARY: 自定义属性闭环(avoidPageBreakInside/pageBreakBefore/pageBreakAfter/pageRelate 已注入 attributedString) ## Specific Ideas ### 缓存 key 设计 ``` cacheKey = SHA256("\(bookID)_\(fontSize)_\(lineHeightMultiple)_\(contentInsets.top)_\(contentInsets.left)_\(contentInsets.bottom)_\(contentInsets.right)_v\(schemaVersion)") ``` - schemaVersion = 1,代码变更导致缓存格式不兼容时 bump - 失效条件:参数变化自动失效 + schema version bump 强制失效 - 存储:Library/Caches/RDEPUBTextBookCache/ 目录 ### 性能采样点 - 每章渲染耗时(DTHTMLAttributedStringBuilder) - 每章分页耗时(CTFramesetter pagination) - 全书构建总耗时 - 缓存命中/未命中计数 ### avoidPageBreakInside 执行策略 ``` 当 CTFrame 可见范围尾部落在 avoidPageBreakInside block 内部时: 1. 向前扫描找到 block 起始位置 2. 将分页点移到 block 起始之前 3. 将整个 block 推到下一页 ``` ## Deferred Ideas - **CoreText 直接绘制迁移**: v1.2/v2.0,超出本阶段范围 - **字体系统**: 内嵌固定字体集,后续版本 - **字符级位置精度**: P2,与 CoreText 迁移关联 - **章节数据模型内聚**: P2,独立重构 - **TTS / DRM / Pencil / 多栏排版**: P3,独立功能模块 - **NSKeyedArchiver 对 DTCoreText 属性持久性验证**: 需运行时测试,若失败则降级为 decomposed JSON 策略 --- *Phase: 08-pagination-quality-cache-performance* *Context gathered: 2026-05-23* *Updated after research: D-01/D-02/D-04/D-05 confirmed already implemented*