docs(08): create phase plans for image display fix and pagination cache

3 plans covering QUAL-01 through QUAL-04:
- 08-01: pagination cache key + wiring into rendering pipeline
- 08-02: unified 1080x1920 max-size for all images, footnote width-only fix
- 08-03: pagination timing instrumentation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
shen 2026-05-23 18:43:41 +08:00
parent 86e6922f7b
commit 22bb86959c
6 changed files with 287 additions and 51 deletions

View File

@ -2,13 +2,13 @@
gsd_state_version: 1.0
milestone: v1.1
milestone_name: WXRead 深化对齐
status: ready_to_execute
last_updated: "2026-05-22T12:35:00.000Z"
last_activity: 2026-05-22 -- Phase 07 execution completed
status: executing
last_updated: "2026-05-23T10:43:34.404Z"
last_activity: 2026-05-23 -- Phase 8 planning complete
progress:
total_phases: 4
completed_phases: 2
total_plans: 6
total_plans: 9
completed_plans: 6
percent: 50
---
@ -62,5 +62,5 @@ progress:
Phase: 8
Plan: pending
Status: Ready for next phase
Last activity: 2026-05-22 -- Phase 07 execution completed with verification passed
Status: Ready to execute
Last activity: 2026-05-23 -- Phase 8 planning complete

View File

@ -6,31 +6,39 @@ wave: 1
depends_on: []
files_modified:
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererCache.swift
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift
autonomous: true
requirements:
- QUAL-01
must_haves:
truths:
- "Cache key struct exists that captures viewport + typography configuration"
- "Cache lookup and store methods exist on the cache type"
- "Build succeeds with new cache file in project"
- "Repeated pagination of the same chapter with unchanged viewport and typography returns cached layout frames without re-typesetting"
- "Changing font size or viewport dimensions invalidates the cache and triggers fresh pagination"
- "Cache lookup is transparent — downstream consumers receive the same RDEPUBTextLayoutFrame array whether cached or freshly computed"
artifacts:
- path: "Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererCache.swift"
provides: "Cache key struct and cache manager stub"
provides: "Cache key struct and cache manager for pagination layout frames"
exports: ["RDEPUBTextRendererCache", "RDEPUBPaginationCacheKey"]
- path: "Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift"
provides: "Cache wiring in pagination pipeline"
contains: "RDEPUBTextRendererCache.shared"
key_links:
- from: "Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift"
to: "Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererCache.swift"
via: "cache lookup before rd_paginatedFrames, store after"
pattern: "RDEPUBTextRendererCache\\.shared"
- from: "Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererCache.swift"
to: "RDEPUBDTCoreTextRenderer"
via: "import (future wiring)"
pattern: "RDEPUBTextRendererCache"
to: "RDEPUBTextLayoutFrame"
via: "cached value type"
pattern: "\\[RDEPUBTextLayoutFrame\\]"
---
<objective>
Create the pagination cache infrastructure: a cache key struct that captures viewport dimensions and typography configuration, plus a cache manager stub with lookup/store/invalidate methods. This is the structural foundation that plan 08-02 and future work will wire into the actual rendering pipeline.
Create the pagination cache infrastructure and wire it into the rendering pipeline so that repeated pagination of the same chapter with unchanged viewport and typography configuration returns cached layout frames without re-typesetting.
Purpose: Prevents the same chapter from being fully re-typeset when viewport and typography config haven't changed (QUAL-01).
Purpose: Prevents the same chapter from being fully re-typeset when viewport and typography config haven't changed (QUAL-01). The cache wraps the `rd_paginatedFrames` call in `RDEPUBTextBookBuilder`, which is the actual pagination bottleneck.
Output: `RDEPUBTextRendererCache.swift` with cache key struct and manager stub.
Output: `RDEPUBTextRendererCache.swift` with cache key struct and manager, plus updated `RDEPUBTextBookBuilder.swift` with cache wiring.
</objective>
<execution_context>
@ -48,47 +56,56 @@ Output: `RDEPUBTextRendererCache.swift` with cache key struct and manager stub.
<interfaces>
<!-- Existing types the executor needs to know about -->
From Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift:
- `RDEPUBTextChapterRenderRequest` — contains `style: RDEPUBTextRenderStyle`, `context` with baseURL
From Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift:
- `RDEPUBTextChapterRenderRequest` — contains `context: RDEPUBTextChapterContext`, `style: RDEPUBTextRenderStyle`
- `RDEPUBTextChapterContext` — has `href: String`, `title: String`, `html: String`, `baseURL: URL?`
- `RDEPUBTextRenderStyle` — has `font: UIFont`, `lineSpacing: CGFloat`, `textColor: UIColor?`, `backgroundColor: UIColor?`
- `RDEPUBRenderedChapterContent` — has `attributedString: NSAttributedString`, `fragmentOffsets: [String: Int]`
From Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift:
- `RDEPUBTextRenderStyle` properties used in rendering: `font.pointSize`, `font.familyName`, `font.fontName`, `lineSpacing`
From Sources/RDReaderView/EPUBTextRendering/RDEPUBTextLayoutFrame.swift:
- `struct RDEPUBTextLayoutFrame: Equatable` — value type with contentRange, breakReason, attachmentRanges, diagnostics, etc.
From Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift (line 135, 196):
- `buildBook(... pageSize: CGSize, ...)` — the function that iterates spine items
- `content.rd_paginatedFrames(size: pageSize, fragmentOffsets: rendered.fragmentOffsets)` — the pagination call to cache around
From Sources/RDReaderView/EPUBTextRendering/RDEPUBTextPaginationSupport.swift:
- `rd_paginatedFrames(size: CGSize, fragmentOffsets: [String: Int]) -> [RDEPUBTextLayoutFrame]` — extension on NSAttributedString
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Create cache key struct and cache manager stub</name>
<name>Task 1: Create cache key struct and cache manager</name>
<files>Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererCache.swift</files>
<read_first>
- Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift (understand render request and style types)
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift (understand what configuration affects pagination output)
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift (understand RDEPUBTextRenderStyle, RDEPUBTextChapterRenderRequest, RDEPUBTextChapterContext)
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextLayoutFrame.swift (understand the value type to cache)
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift (lines 130-200 — understand pageSize and rd_paginatedFrames usage)
</read_first>
<action>
Create `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererCache.swift` with:
1. `RDEPUBPaginationCacheKey` struct — captures the inputs that determine pagination output:
- `chapterID: String` (unique chapter identifier, e.g. href or file path)
- `viewportSize: CGSize` (the available page dimensions)
- `fontDescriptor: String` (font family + name + pointSize, concatenated)
- `chapterID: String` (the chapter href from `RDEPUBTextChapterContext.href`)
- `viewportSize: CGSize` (the `pageSize` passed to `buildBook`)
- `fontDescriptor: String` (font family + name + pointSize, concatenated, e.g. `"\(font.familyName)-\(font.fontName)-\(font.pointSize)"`)
- `lineSpacing: CGFloat`
- `textColorHash: Int?` (hash of textColor, if present)
- Implement `Equatable` conformance (auto-synthesize is fine)
- `textColorHash: Int` (hash of textColor, or 0 if nil)
- Implement `Equatable` conformance (auto-synthesize is fine since all stored properties are Equatable)
2. `RDEPUBTextRendererCache` class:
- `static let shared = RDEPUBTextRendererCache()`
- `private var cache: [RDEPUBPaginationCacheKey: [NSAttributedString]]` — maps key to array of page attributed strings
- `func cachedPages(for key: RDEPUBPaginationCacheKey) -> [NSAttributedString]?` — returns cached pages or nil
- `func store(pages: [NSAttributedString], for key: RDEPUBPaginationCacheKey)` — stores pages
- `func invalidate(for key: RDEPUBPaginationCacheKey)` — removes a specific entry
- `func invalidateAll()` — clears entire cache
- `var entryCount: Int` — read-only count of cached entries (for diagnostics)
- `private var cache: [RDEPUBPaginationCacheKey: [RDEPUBTextLayoutFrame]]` — maps key to array of layout frames
- `private let lock = NSLock()` — thread safety
- `func cachedFrames(for key: RDEPUBPaginationCacheKey) -> [RDEPUBTextLayoutFrame]?` — lock, lookup, unlock, return
- `func store(frames: [RDEPUBTextLayoutFrame], for key: RDEPUBPaginationCacheKey)` — lock, store, unlock
- `func invalidate(for key: RDEPUBPaginationCacheKey)` — lock, remove, unlock
- `func invalidateAll()` — lock, removeAll, unlock
- `var entryCount: Int` — lock, read count, unlock (for diagnostics)
Make the class `final` and `Sendable`-safe (use a lock or `@MainActor` annotation since rendering is main-thread). Use `NSLock` for thread safety.
Do NOT wire this cache into the rendering pipeline yet — that is future work. This plan only creates the infrastructure.
Make the class `final`. Use `NSLock` for thread safety since the cache may be accessed from different queues.
</action>
<verify>
<automated>xcodebuild build -project ReadViewDemo/ReadViewDemo.xcodeproj -scheme ReadViewDemo -destination 'platform=iOS Simulator,name=iPhone 16' 2>&1 | tail -5</automated>
@ -97,24 +114,91 @@ Do NOT wire this cache into the rendering pipeline yet — that is future work.
- `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererCache.swift` exists
- File contains `struct RDEPUBPaginationCacheKey: Equatable` with properties: chapterID, viewportSize, fontDescriptor, lineSpacing, textColorHash
- File contains `final class RDEPUBTextRendererCache` with static `shared` instance
- Class has methods: `cachedPages(for:)`, `store(pages:for:)`, `invalidate(for:)`, `invalidateAll()`
- Cache value type is `[RDEPUBTextLayoutFrame]` (not `[NSAttributedString]`)
- Class has methods: `cachedFrames(for:)`, `store(frames:for:)`, `invalidate(for:)`, `invalidateAll()`
- Class has computed property `entryCount: Int`
- Build succeeds (xcodebuild exits 0)
</acceptance_criteria>
<done>Cache key struct and cache manager stub exist, build compiles successfully</done>
<done>Cache key struct and cache manager exist with correct value types, build compiles successfully</done>
</task>
<task type="auto">
<name>Task 2: Wire cache into pagination pipeline in RDEPUBTextBookBuilder</name>
<files>Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift</files>
<read_first>
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift (full file — understand buildBook function, pageSize parameter, rd_paginatedFrames call at line 196)
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererCache.swift (created in Task 1 — understand API surface)
</read_first>
<action>
Wire the cache into `RDEPUBTextBookBuilder.swift` around the `rd_paginatedFrames` call at line 196.
**Location:** Inside the `buildBook` function, around the existing pagination block (lines 194-198):
```swift
layoutFrames = content.length > 0
? content.rd_paginatedFrames(size: pageSize, fragmentOffsets: rendered.fragmentOffsets)
: []
```
**Replace with cache-aware logic:**
1. Before the pagination call, build a cache key:
```swift
let cacheKey = RDEPUBPaginationCacheKey(
chapterID: item.href,
viewportSize: pageSize,
fontDescriptor: "\(style.font.familyName)-\(style.font.fontName)-\(style.font.pointSize)",
lineSpacing: style.lineSpacing,
textColorHash: style.textColor?.hashValue ?? 0
)
```
2. Check cache first:
```swift
if let cached = RDEPUBTextRendererCache.shared.cachedFrames(for: cacheKey) {
layoutFrames = cached
} else {
layoutFrames = content.length > 0
? content.rd_paginatedFrames(size: pageSize, fragmentOffsets: rendered.fragmentOffsets)
: []
if !layoutFrames.isEmpty {
RDEPUBTextRendererCache.shared.store(frames: layoutFrames, for: cacheKey)
}
}
```
3. Keep the cover chapter special-case path (lines 175-193) unchanged — it doesn't go through `rd_paginatedFrames`.
The `style` variable is available in scope (it's the `style: RDEPUBTextRenderStyle` parameter of `buildBook`). The `item.href` is the chapter identifier from the spine iteration.
</action>
<verify>
<automated>xcodebuild build -project ReadViewDemo/ReadViewDemo.xcodeproj -scheme ReadViewDemo -destination 'platform=iOS Simulator,name=iPhone 16' 2>&1 | tail -5</automated>
</verify>
<acceptance_criteria>
- RDEPUBTextBookBuilder.swift imports or references `RDEPUBTextRendererCache`
- Cache key is constructed from `item.href`, `pageSize`, `style.font`, `style.lineSpacing`, `style.textColor`
- `RDEPUBTextRendererCache.shared.cachedFrames(for:)` is called before `rd_paginatedFrames`
- `RDEPUBTextRendererCache.shared.store(frames:for:)` is called after successful pagination
- Cover chapter special-case path is NOT modified
- Build succeeds (xcodebuild exits 0)
</acceptance_criteria>
<done>Cache is wired into the pagination pipeline — repeated pagination of the same chapter with identical config returns cached frames, build compiles</done>
</task>
</tasks>
<verification>
- Build succeeds with new file
- Cache types are properly defined and accessible from other modules in the same target
- Build succeeds with both new and modified files
- Cache types are properly defined and accessible from RDEPUBTextBookBuilder
- Cache key captures all inputs that affect pagination output (chapterID, viewportSize, font, lineSpacing, textColor)
- Cache value is [RDEPUBTextLayoutFrame] matching the rd_paginatedFrames return type
</verification>
<success_criteria>
- RDEPUBPaginationCacheKey captures all inputs that affect pagination output
- RDEPUBTextRendererCache provides a thread-safe, singleton cache surface ready for wiring
- No existing code is modified — this is purely additive
- RDEPUBTextRendererCache provides a thread-safe, singleton cache surface
- Cache is wired into the actual pagination path in RDEPUBTextBookBuilder
- Same chapter + same config = cache hit (no re-typesetting)
- Different viewport or typography = cache miss (fresh pagination)
</success_criteria>
<output>

View File

@ -12,7 +12,6 @@ requirements:
must_haves:
truths:
- "Pagination timing is logged with chapter ID, page count, and elapsed milliseconds"
- "Diagnostic summary includes per-chapter pagination stats"
artifacts:
- path: "Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift"
provides: "Pagination timing instrumentation in existing rendering path"

View File

@ -0,0 +1,114 @@
# Phase 8: 图片显示修复 - Context
**Gathered:** 2026-05-23
**Status:** Ready for planning
<domain>
## Phase Boundary
修复 native text 渲染路径DTCoreText中三类图片的尺寸适配问题
1. `qrbodyPic` 容器中的独立图片 — 当前无特殊处理,高度可能溢出页面
2. 封面图片 (`frontCover`) — 当前用屏幕尺寸基准,需统一到最大尺寸逻辑
3. 脚注图片 (`qqreader-footnote`) — 当前三处不一致的尺寸处理
核心目标:对齐 WXRead 的 `_WRPostProcessElementTree` 逻辑,对所有图片附件统一执行最大尺寸限制。
</domain>
<decisions>
## Implementation Decisions
### 图片最大尺寸
- **D-01:** 对所有图片附件统一执行最大尺寸限制 `CGSizeMake(1080, 1920)`
- **D-02:** 超过最大宽度时等比缩放(与 WXRead `_WRPostProcessElementTree` 一致)
- **D-03:** 实现位置:`prepareHTMLElementForReaderRendering` 中,在现有 footnote/cover 特殊处理之前,添加统一的最大尺寸限制逻辑
### 封面图片
- **D-04:** 保持屏幕尺寸基准(`UIScreen.main.bounds.insetBy(dx: 20, dy: 28)`),但受统一最大尺寸 1080x1920 约束
- **D-05:** 保持 `configureCoverIfNeeded` 的 UIImageView 专用路径不变
- **D-06:** 封面图片仍通过 `prepareHTMLElementForReaderRendering` 中的 cover 分支处理,但需确保不超过统一最大尺寸
### 脚注图片
- **D-07:** 对齐 WXRead只设 `width:1em`,不设高度,让渲染器按原始宽高比自动计算
- **D-08:** 移除 `prepareHTMLElementForReaderRendering` 中脚注的高度计算逻辑(`pointSize * 0.54`
- **D-09:** 移除 `normalizeInlineAttachments` 中的高度覆盖逻辑(`pointSize * 0.14`
- **D-10:** 保留 `normalizeAttachmentHTMLMarkers` 中的 CSS `width:1em`,移除 `height:1em`
### qrbodyPic 图片
- **D-11:** 不单独添加特殊处理逻辑,统一最大尺寸限制会隐式修复高度溢出问题
- **D-12:** 保持现有 CSS 规则不变(`max-width:100%; height:auto; display:block; margin:auto`
### Claude's Discretion
- 具体实现细节(如是否需要自动添加 `bodyPic` 类、是否需要 `wr-vertical-center-style` 语义标记)由 planner 和 executor 决定
- 图片缓存策略不在本次讨论范围,属于 Phase 8 的 08-01 计划
</decisions>
<canonical_refs>
## Canonical References
**Downstream agents MUST read these before planning or implementing.**
### WXRead 参考实现
- `Doc/WXRead/decompiled/WREpubTypesetter.m` §672-701 — `_WRPostProcessElementTree()`统一图片最大尺寸逻辑1080x1920、自动添加 bodyPic 类、设置 wr-vertical-center-style
- `Doc/WXRead/decompiled/WRCoreTextLayoutFrame.m` §615-635 — `drawCoverImage:inContext:rect:`:封面图片等比缩放适配 frame
- `Doc/WXRead/decompiled/WRCoreTextLayouter.m` §639-667 — `targetSizeForPattern:rect:imageSize:`sizePattern 缩放逻辑
- `Doc/WXRead/resources/css/replace.css` §91-103 — `.bodyPic`、`.qrbodyPic`、`.qqreader-footnote` 的 CSS 规则
### ReadViewSDK 当前实现
- `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift` §220-262 — `prepareHTMLElementForReaderRendering`:当前图片处理逻辑
- `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift` §302-343 — `replaceCSS()`:当前图片 CSS 规则
- `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift` §345-404 — `normalizeAttachmentHTMLMarkers`HTML 预处理
- `Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift` §217-282 — `configureCoverIfNeeded``normalizeInlineAttachments`
- `Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift` §90-112 — `dtOptions`DTMaxImageSize 配置
### Demo 样本
- `ReadViewDemo/ReadViewDemo/book/宝山辽墓材料与释读_副本/OEBPS/Styles/stylesheets.css` — 实际 EPUB 书籍的图片 CSS
- `ReadViewDemo/ReadViewDemo/book/宝山辽墓材料与释读_副本/OEBPS/Text/cover.xhtml` — 封面 HTML 结构
- `ReadViewDemo/ReadViewDemo/book/宝山辽墓材料与释读_副本/OEBPS/Text/Chapter_5.xhtml` — qrbodyPic 图片 HTML 结构
</canonical_refs>
<code_context>
## Existing Code Insights
### Reusable Assets
- `prepareHTMLElementForReaderRendering`:已有 footnote 和 cover 的分类处理框架,可在此基础上添加统一最大尺寸逻辑
- `normalizeAttachmentHTMLMarkers`:已有 HTML 正则替换基础设施,可复用于 qrbodyPic 的 HTML 预处理
- `replaceCSS()`:已有图片 CSS 规则,可直接修改
### Established Patterns
- 图片分类检测:通过 CSS class`qqreader-footnote`、`rd-front-cover-image`)和文件名(`note.png`、`cover.jpg`)识别图片类型
- DTTextAttachment API`originalSize`(原始尺寸)、`displaySize`(显示尺寸)、`verticalAlignment`(垂直对齐)
- HTML 正则替换:`mergeHTMLAttributes` + `replaceMatches` 模式用于 HTML 预处理
### Integration Points
- `RDEPUBDTCoreTextRenderer.swift``willFlushCallback``prepareHTMLElementForReaderRendering`DTCoreText 渲染时的图片处理入口
- `RDEPUBTextContentView.swift``configureCoverIfNeeded`:封面图片显示入口
- `RDEPUBTextContentView.swift``normalizeInlineAttachments`:显示时的脚注图片处理入口
</code_context>
<specifics>
## Specific Ideas
- 用户明确要求"等比缩放,让图片都能显示全"——图片不能被裁剪,必须完整显示
- 用户明确要求脚注图片"显示的和文本的高度一样,高度不能超过文本的高度"
- WXRead 的实现是参考标准,但不需要 100% 复制——只对齐图片尺寸逻辑
</specifics>
<deferred>
## Deferred Ideas
- 图片缓存机制(属于 08-01 计划)
- `wr-vertical-center-style` 垂直居中语义的完整实现(属于 Phase 7 已完成的工作)
- 图片点击放大/查看大图功能(新能力,不属于当前范围)
- 代码块/表格被粗暴截断QUAL-02 子需求)— 本次聚焦图片尺寸修复,代码块/表格截断问题留待后续分页质量迭代处理
</deferred>
---
*Phase: 8-图片显示修复*
*Context gathered: 2026-05-23*

View File

@ -392,22 +392,22 @@ static func prepareHTMLElementForReaderRendering(
| A2 | WXRead's `_WRPostProcessElementTree` checks only width, not height, against maxSize (verified via decompiled source) | WXRead Reference | LOW — source code at line 680 confirms `originalSize.width > maxSize.width` |
| A3 | The `normalizeAttachmentDisplayIfNeeded` function at line 467-505 of RDEPUBTextRendererSupport.swift needs its footnote height override removed (per D-08/D-09) | Pitfall 4 | MEDIUM — if not removed, it will override the willFlushCallback changes |
## Open Questions
## Open Questions (RESOLVED)
1. **Should we also update `DTMaxImageSize` in `dtOptions()` from screen bounds to 1080x1920?**
1. **(RESOLVED) Should we also update `DTMaxImageSize` in `dtOptions()` from screen bounds to 1080x1920?**
- What we know: `dtOptions()` currently uses `UIScreen.main.bounds.insetBy(dx: 20, dy: 28).size` (~353x757)
- What's unclear: Whether changing this affects non-image attachments or has side effects
- Recommendation: Yes, update to `CGSize(width: 1080, height: 1920)`. This is the first-pass constraint at attachment creation time. The willFlushCallback block is the authoritative second pass. Aligning both to the same max size eliminates confusion.
- Resolution: Yes, update to `CGSize(width: 1080, height: 1920)`. This is the first-pass constraint at attachment creation time. The willFlushCallback block is the authoritative second pass. Aligning both to the same max size eliminates confusion. Plan 08-02 Task 1 includes this change.
2. **Should we auto-add `bodyPic` class to all images like WXRead does?**
2. **(RESOLVED) Should we auto-add `bodyPic` class to all images like WXRead does?**
- What we know: WXRead adds `bodyPic` class to every image in `_WRPostProcessElementTree`
- What's unclear: Whether ReadViewSDK's CSS rules depend on `bodyPic` being present for proper centering
- Recommendation: Not required for the immediate fix. The `replaceCSS` already handles `img, svg, video, canvas` with `max-width: 100%; height: auto`, and specific selectors like `.bodyPic img` for centering. Adding the class would be a nice-to-have for full WXRead alignment but is not blocking.
- Resolution: Not required for the immediate fix. The `replaceCSS` already handles `img, svg, video, canvas` with `max-width: 100%; height: auto`, and specific selectors like `.bodyPic img` for centering. Adding the class would be a nice-to-have for full WXRead alignment but is not blocking. Deferred to future WXRead alignment work.
3. **Should `normalizeAttachmentDisplayIfNeeded` (line 467-505) be updated or removed entirely?**
3. **(RESOLVED) Should `normalizeAttachmentDisplayIfNeeded` (line 467-505) be updated or removed entirely?**
- What we know: This function runs in `normalizeReadingAttributes` (post-willFlushCallback) and overrides footnote/cover sizes
- What's unclear: Whether removing the footnote path is sufficient, or whether the cover path should also be simplified
- Recommendation: Remove the footnote height override path (D-08). The cover path can remain as a fallback since it produces similar results to the `prepareHTMLElementForReaderRendering` cover block.
- Resolution: Remove the footnote height override path (D-08). The cover path can remain as a fallback since it produces similar results to the `prepareHTMLElementForReaderRendering` cover block. Plan 08-02 Task 1 includes the footnote path removal.
## Environment Availability

View File

@ -0,0 +1,39 @@
# Phase 8: Validation Plan
**Phase:** 08-pagination-quality-cache-performance
**Created:** 2026-05-23
## Verification Strategy
This phase has no unit test target for EPUBTextRendering. Verification is build-only plus manual visual inspection.
### Automated Verification
| Check | Command | Frequency |
|-------|---------|-----------|
| Build succeeds | `xcodebuild build -project ReadViewDemo/ReadViewDemo.xcodeproj -scheme ReadViewDemo -destination 'platform=iOS Simulator,name=iPhone 16'` | Per task commit |
### Manual Verification (Phase Gate)
| Requirement | What to Check | How |
|-------------|---------------|-----|
| QUAL-01 | Same chapter does not re-typeset on repeat navigation | Navigate to a chapter, back, then forward again — second visit should be noticeably faster (check `[EPUB][Perf]` log for elapsed time) |
| QUAL-02 | Images don't overflow pages | Open 宝山辽墓材料与释读, navigate to Chapter 5 (qrbodyPic images), verify no height overflow |
| QUAL-03 | Image sizing rules are verifiable | Code review: `DTMaxImageSize` = 1080x1920, unified max-size block in `prepareHTMLElementForReaderRendering` |
| QUAL-04 | No perf degradation | Check `[EPUB][Perf]` log output — pagination times should be comparable to pre-change baseline |
### Image Display Checklist
- [ ] Cover image: displays correctly, not cropped, not stretched
- [ ] qrbodyPic images: display within page bounds, no height overflow
- [ ] Footnote images: inline with text, same height as text characters, width-only sizing
- [ ] Dark mode: all image types still display correctly
- [ ] Page breaks around images: no orphan images at page tops/bottoms
## Rationale
Build-only verification is the appropriate approach for this phase because:
1. No unit test target exists for EPUBTextRendering code
2. Image sizing correctness is inherently visual — pixel-level assertions would be brittle
3. The changes are surgical (one new code block, removals of redundant logic) and can be verified by code review + visual inspection
4. Creating a test infrastructure is out of scope (belongs to Phase 9: 自动化回归与证据标准化)