diff --git a/.planning/phases/08-pagination-quality-cache-performance/08-RESEARCH.md b/.planning/phases/08-pagination-quality-cache-performance/08-RESEARCH.md
new file mode 100644
index 0000000..328ece9
--- /dev/null
+++ b/.planning/phases/08-pagination-quality-cache-performance/08-RESEARCH.md
@@ -0,0 +1,474 @@
+# Phase 8: 图片显示修复 - Research
+
+**Researched:** 2026-05-23
+**Domain:** DTCoreText image attachment sizing, WXRead alignment
+**Confidence:** HIGH
+
+## Summary
+
+Phase 8 plan 08-02 focuses on fixing image display in the native text rendering path (DTCoreText). Three image types have sizing issues: (1) `qrbodyPic` images can overflow page height because no max size constraint is applied, (2) cover images use screen-size baselines but lack a unified ceiling, (3) footnote images have inconsistent height calculations across three separate code paths.
+
+The core fix is to add a unified maximum size constraint (`CGSize(width: 1080, height: 1920)`) to ALL image attachments in `prepareHTMLElementForReaderRendering`, applied BEFORE any type-specific handling. This directly mirrors WXRead's `_WRPostProcessElementTree` approach. The fix is surgical: one new code block at the top of the existing function, plus removal of redundant height logic in two other locations.
+
+**Primary recommendation:** Add a unified max-size block at the top of `prepareHTMLElementForReaderRendering` that constrains all images to 1080x1920 via aspect-ratio-preserving scaling. Then simplify footnote handling to width-only (no explicit height), matching WXRead's `replace.css`.
+
+
+## User Constraints (from CONTEXT.md)
+
+### Locked Decisions
+- **D-01:** Unified max image size `CGSizeMake(1080, 1920)` for all image attachments
+- **D-02:** Scale proportionally when exceeding max width (matching WXRead `_WRPostProcessElementTree`)
+- **D-03:** Implementation location: `prepareHTMLElementForReaderRendering`, BEFORE existing footnote/cover special handling
+- **D-04:** Cover images keep screen-size base (`UIScreen.main.bounds.insetBy(dx: 20, dy: 28)`), constrained by unified max
+- **D-05:** Keep `configureCoverIfNeeded` UIImageView path unchanged
+- **D-06:** Cover images still processed via cover branch in `prepareHTMLElementForReaderRendering`, but must not exceed unified max
+- **D-07:** Footnote images: only set `width:1em`, no height (align with WXRead `replace.css`)
+- **D-08:** Remove footnote height calculation in `prepareHTMLElementForReaderRendering` (`pointSize * 0.54`)
+- **D-09:** Remove height override in `normalizeInlineAttachments` (`pointSize * 0.14`)
+- **D-10:** Keep CSS `width:1em` in `normalizeAttachmentHTMLMarkers`, remove `height:1em`
+- **D-11:** No special handling for `qrbodyPic` — unified max size implicitly fixes overflow
+- **D-12:** Keep existing CSS rules for `qrbodyPic` unchanged
+
+### Claude's Discretion
+- Whether to auto-add `bodyPic` class to all images (like WXRead does)
+- Whether `wr-vertical-center-style` semantic marker is needed (Phase 7 already handles this)
+- Specific implementation details of the unified max-size block
+
+### Deferred Ideas (OUT OF SCOPE)
+- Image caching mechanism (belongs to plan 08-01)
+- `wr-vertical-center-style` full implementation (Phase 7 already completed)
+- Image tap-to-zoom feature (new capability, not in scope)
+
+
+
+## Phase Requirements
+
+| ID | Description | Research Support |
+|----|-------------|------------------|
+| QUAL-01 | Cache layout frame / pagination results | Not in scope for 08-02 (belongs to 08-01) |
+| QUAL-02 | Improve pagination quality for complex illustrated chapters — reduce bad page breaks, orphan/widow lines, abnormal whitespace around images | Image sizing fix directly addresses image-related pagination quality |
+| QUAL-03 | Image/attachment sizing strategy, dark mode adaptation, page background — must have verifiable processing rules, not rely on DTCoreText defaults | Unified max size + explicit CSS rules provide verifiable rules |
+| QUAL-04 | Pagination improvements must not significantly degrade first-screen time, re-pagination time, or interaction smoothness | Image sizing is a lightweight operation in willFlushCallback, negligible performance impact |
+
+
+## Architectural Responsibility Map
+
+| Capability | Primary Tier | Secondary Tier | Rationale |
+|------------|-------------|----------------|-----------|
+| Image max-size constraint | Swift `willFlushCallback` (renderer) | CSS `replaceCSS` | Swift handles absolute pixel ceiling; CSS handles responsive width |
+| Footnote image sizing | CSS (`width:1em` in HTML preprocessing) | Swift (vertical alignment) | CSS controls width; Swift only sets alignment |
+| Cover image sizing | Swift (`prepareHTMLElementForReaderRendering`) | `configureCoverIfNeeded` (view layer) | Swift constrains in attributed string; view layer only extracts for UIImageView display |
+| Page-break control around images | Layouter (pagination) | CSS (`page-break-inside: avoid`) | Layouter decides actual breaks; CSS provides hints |
+
+## Standard Stack
+
+### Core
+| Library | Version | Purpose | Why Standard |
+|---------|---------|---------|--------------|
+| DTCoreText | (CocoaPod, project-local) | HTML/CSS to NSAttributedString | Existing project dependency; provides `DTTextAttachment`, `willFlushCallback` |
+| UIKit | iOS SDK | `UIScreen.main.bounds` for cover sizing | Platform standard |
+
+### Supporting
+| Library | Version | Purpose | When to Use |
+|---------|---------|---------|-------------|
+| Foundation | iOS SDK | `NSRegularExpression` for HTML preprocessing | Already used throughout `RDEPUBTextRendererSupport` |
+
+### Alternatives Considered
+| Instead of | Could Use | Tradeoff |
+|------------|-----------|----------|
+| Swift-side max size in `willFlushCallback` | CSS `max-height` in `replaceCSS` | CSS approach is simpler but DTCoreText CSS parser may not support `max-height` reliably for attachments; Swift is authoritative |
+| Removing `DTMaxImageSize` from builder options | Keeping both layers | Keeping both is safer — DTMaxImageSize is the first line of defense at attachment creation time; willFlushCallback is the second pass |
+
+## Package Legitimacy Audit
+
+No new packages are installed in this phase. All changes are to existing Swift source files.
+
+| Package | Registry | Age | Downloads | Source Repo | slopcheck | Disposition |
+|---------|----------|-----|-----------|-------------|-----------|-------------|
+| (none) | — | — | — | — | — | No new packages |
+
+## Architecture Patterns
+
+### Current Image Processing Pipeline
+
+```
+HTML input
+ |
+ v
+normalizeAttachmentHTMLMarkers() <-- HTML regex: adds inline styles to
tags
+ | (footnote: width:1em;height:1em)
+ | (cover: adds rd-front-cover-image class)
+ v
+DTHTMLAttributedStringBuilder <-- Creates DTTextAttachment with DTMaxImageSize
+ | (currently screenBounds.size, ~353x757)
+ | DTCoreText sets displaySize = min(original, max)
+ v
+willFlushCallback <-- prepareHTMLElementForReaderRendering()
+ | Currently: footnote → height calc, cover → screen scale
+ | MISSING: unified max-size for ALL images
+ v
+normalizeReadingAttributes() <-- normalizeAttachmentDisplayIfNeeded()
+ | Currently: footnote → height override (0.14*pt)
+ | cover → maxWidth override
+ v
+configureCoverIfNeeded() <-- View layer: extracts cover image for UIImageView
+ |
+ v
+normalizeInlineAttachments() <-- View layer: footnote → height override (0.14*pt)
+```
+
+### Recommended Pipeline After Fix
+
+```
+HTML input
+ |
+ v
+normalizeAttachmentHTMLMarkers() <-- CHANGED: footnote
only gets width:1em (no height)
+ v
+DTHTMLAttributedStringBuilder <-- CHANGED: DTMaxImageSize → CGSizeMake(1080, 1920)
+ v
+willFlushCallback <-- prepareHTMLElementForReaderRendering()
+ | NEW: unified max-size block for ALL images first
+ | THEN: footnote → only verticalAlignment + inline
+ | THEN: cover → screen-size scale (capped by max)
+ v
+normalizeReadingAttributes() <-- normalizeAttachmentDisplayIfNeeded()
+ | CHANGED: footnote path removed (no height override)
+ | cover path can remain as fallback
+ v
+configureCoverIfNeeded() <-- UNCHANGED
+ v
+normalizeInlineAttachments() <-- CHANGED: footnote height override removed
+```
+
+### Pattern 1: Unified Max-Size Block in `prepareHTMLElementForReaderRendering`
+
+**What:** A new code block at the top of the function, before any type-specific handling, that constrains ALL image attachments to 1080x1920.
+
+**When to use:** Every image attachment that enters `willFlushCallback`.
+
+**Example:**
+```swift
+// Source: WXRead WREpubTypesetter.m §672-701 (_WRPostProcessElementTree)
+// Placed BEFORE existing footnote/cover handling in prepareHTMLElementForReaderRendering
+
+static func prepareHTMLElementForReaderRendering(
+ _ element: DTHTMLElement,
+ style: RDEPUBTextRenderStyle
+) {
+ guard let attachment = element.textAttachment else { return }
+
+ // --- NEW: Unified max image size (aligns with WXRead _WRPostProcessElementTree) ---
+ let maxImageSize = CGSize(width: 1080, height: 1920)
+ let originalSize = attachment.originalSize
+ if originalSize.width > maxImageSize.width || originalSize.height > maxImageSize.height {
+ let scale = min(maxImageSize.width / max(originalSize.width, 1),
+ maxImageSize.height / max(originalSize.height, 1))
+ attachment.displaySize = CGSize(
+ width: round(originalSize.width * scale),
+ height: round(originalSize.height * scale)
+ )
+ }
+
+ // ... existing footnote/cover handling follows ...
+}
+```
+
+### Pattern 2: Footnote Width-Only Sizing
+
+**What:** Footnote images get `width:1em` in HTML preprocessing, with no explicit height. DTCoreText calculates height from aspect ratio.
+
+**When to use:** In `normalizeAttachmentHTMLMarkers` for `qqreader-footnote` images.
+
+**Example:**
+```swift
+// Source: WXRead replace.css §100-103 (.qqreader-footnote { width: 1em; })
+// Changed: removed "height:1em" from styleFragments
+
+normalized = replaceMatches(
+ using: footnoteRegex,
+ in: normalized
+) { tag in
+ mergeHTMLAttributes(
+ into: tag,
+ requiredClass: nil,
+ styleFragments: [
+ "width:1em",
+ // "height:1em" <-- REMOVED per D-10
+ "vertical-align:middle",
+ "display:inline-block"
+ ]
+ )
+}
+```
+
+### Anti-Patterns to Avoid
+
+- **Separate max-size blocks for each image type:** WXRead applies one unified block to ALL images. Do not duplicate the scaling logic for qrbodyPic, cover, and footnote separately.
+- **Removing `DTMaxImageSize` from builder options:** The DTCoreText-level max size is the first line of defense (applied during attachment creation). The `willFlushCallback` max size is the second pass. Keep both.
+- **Setting explicit height on footnote images:** WXRead only sets `width:1em` on `.qqreader-footnote`. DTCoreText's `setDisplaySize:withMaxDisplaySize:` handles width-only cases by calculating height from aspect ratio (line 231-236 of DTTextAttachment.m).
+
+## Don't Hand-Roll
+
+| Problem | Don't Build | Use Instead | Why |
+|---------|-------------|-------------|-----|
+| Aspect ratio calculation | Custom scaling math | DTCoreText's `DTCGSizeThatFitsKeepingAspectRatio` or the existing `min(w/maxW, h/maxH)` pattern | Consistent with DTCoreText internals |
+| Image type detection | New classification system | Existing CSS class + filename detection (`lowercasedClasses`, `lowercasedPath`) | Already works, battle-tested in Phase 7 |
+
+## Common Pitfalls
+
+### Pitfall 1: DTMaxImageSize vs willFlushCallback Ordering
+**What goes wrong:** If the max-size constraint is only in `willFlushCallback`, DTCoreText's initial `setDisplaySize:withMaxDisplaySize:` uses the old (screen-size) max. The `willFlushCallback` then overrides it. This works but the intermediate state could confuse debugging.
+**Why it happens:** DTMaxImageSize is consumed at attachment creation time; willFlushCallback runs after.
+**How to avoid:** Update BOTH: change `DTMaxImageSize` in `dtOptions()` to `CGSize(width: 1080, height: 1920)` AND add the max-size block in `prepareHTMLElementForReaderRendering`. The willFlushCallback block is the authoritative constraint; DTMaxImageSize is the first-pass optimization.
+**Warning signs:** If only one is updated, images may briefly appear at wrong size before being corrected.
+
+### Pitfall 2: Footnote Aspect Ratio Without Explicit Height
+**What goes wrong:** After removing `height:1em` from HTML preprocessing AND removing the height calculation in `prepareHTMLElementForReaderRendering`, footnote images might render with incorrect aspect ratio if `originalSize` is not available.
+**Why it happens:** Some images in EPUBs may not have `width`/`height` attributes, so `originalSize` could be `CGSizeZero`.
+**How to avoid:** DTCoreText's `setDisplaySize:withMaxDisplaySize:` (line 218-237 of DTTextAttachment.m) handles the case where `originalSize` has width but not height (and vice versa) by calculating from aspect ratio. If `originalSize` is completely zero, the image falls back to the `DTMaxImageSize` constraint. This should be safe, but verify with the demo sample.
+**Warning signs:** Footnote images appearing at 0x0 or unreasonably large sizes.
+
+### Pitfall 3: Cover Image Double-Scaling
+**What goes wrong:** The unified max-size block scales the image, then the cover-specific block scales it again, resulting in an image that's too small.
+**Why it happens:** Two sequential scaling operations compound.
+**How to avoid:** The cover-specific block in `prepareHTMLElementForReaderRendering` already uses `UIScreen.main.bounds.insetBy(dx: 20, dy: 28)` as its max, which is approximately 353x757 points. This is SMALLER than 1080x1920. So the unified max-size block will be a no-op for cover images (they're already within bounds). The cover block then applies its own tighter constraint. This is correct behavior — no double-scaling occurs.
+**Warning signs:** Cover images appearing smaller than expected.
+
+### Pitfall 4: normalizeAttachmentDisplayIfNeeded Still Overrides
+**What goes wrong:** After fixing `prepareHTMLElementForReaderRendering`, the `normalizeAttachmentDisplayIfNeeded` function (called from `normalizeReadingAttributes`) still overrides footnote and cover sizes.
+**Why it happens:** This function runs AFTER `willFlushCallback` in the pipeline.
+**How to avoid:** Per D-08/D-09, remove the footnote height override in this function too. The cover path in this function can remain as a fallback (it uses `maxWidth = max(UIScreen.main.bounds.width - 48, font.lineHeight * 8)` which is similar to the cover block in `prepareHTMLElementForReaderRendering`).
+**Warning signs:** If footnote images still appear at wrong size after the fix, this function is the likely culprit.
+
+## Code Examples
+
+### WXRead Reference: _WRPostProcessElementTree (Objective-C)
+
+```objc
+// Source: Doc/WXRead/decompiled/WREpubTypesetter.m §672-701
+if (element.textAttachment) {
+ DTTextAttachment *attachment = element.textAttachment;
+
+ CGSize maxSize = CGSizeMake(1080, 1920);
+ NSNumber *maxW = options[@"maxImageWidth"];
+ if (maxW) maxSize.width = maxW.floatValue;
+
+ if (attachment.originalSize.width > maxSize.width) {
+ CGFloat scale = maxSize.width / attachment.originalSize.width;
+ attachment.displaySize = CGSizeMake(
+ attachment.originalSize.width * scale,
+ attachment.originalSize.height * scale
+ );
+ }
+
+ element.textAttachment.attributes = @{
+ WREpubTypesetterVerticalCenterStyleAttribute: @(2)
+ };
+ [element addClass:@"bodyPic"];
+}
+```
+
+**Key observations:**
+1. Max size is applied to ALL images, unconditionally (no class check)
+2. Only width is checked against max (`originalSize.width > maxSize.width`), not height
+3. `bodyPic` class is auto-added to every image
+4. `wr-vertical-center-style: 2` is set on every image
+
+### DTCoreText's setDisplaySize:withMaxDisplaySize: (Objective-C)
+
+```objc
+// Source: ReadViewDemo/Pods/DTCoreText/Core/Source/DTTextAttachment.m §216-245
+- (void)setDisplaySize:(CGSize)displaySize withMaxDisplaySize:(CGSize)maxDisplaySize {
+ if (_originalSize.width!=0 && _originalSize.height!=0) {
+ if (displaySize.width==0 && displaySize.height==0) {
+ displaySize = _originalSize;
+ } else if (displaySize.width==0 && displaySize.height!=0) {
+ CGFloat factor = _originalSize.height / displaySize.height;
+ displaySize.width = round(_originalSize.width / factor);
+ } else if (displaySize.width!=0 && displaySize.height==0) {
+ CGFloat factor = _originalSize.width / displaySize.width;
+ displaySize.height = round(_originalSize.height / factor);
+ }
+ }
+ if (maxDisplaySize.width>0 && maxDisplaySize.height>0) {
+ if (maxDisplaySize.width < displaySize.width || maxDisplaySize.height < displaySize.height) {
+ displaySize = DTCGSizeThatFitsKeepingAspectRatio(displaySize, maxDisplaySize);
+ }
+ }
+}
+```
+
+**Key observation for footnote width-only approach:** When `displaySize.width != 0` and `displaySize.height == 0`, DTCoreText calculates height from the aspect ratio. This means setting only `width:1em` in CSS (which translates to a display width) will automatically produce correct height. This confirms D-07 is safe.
+
+### Recommended: Updated prepareHTMLElementForReaderRendering
+
+```swift
+// Source: Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift §217-262
+static func prepareHTMLElementForReaderRendering(
+ _ element: DTHTMLElement,
+ style: RDEPUBTextRenderStyle
+) {
+ guard let attachment = element.textAttachment else { return }
+
+ // --- NEW: Unified max image size (WXRead alignment) ---
+ let maxImageSize = CGSize(width: 1080, height: 1920)
+ let originalSize = attachment.originalSize
+ if originalSize.width > 0, originalSize.height > 0,
+ (originalSize.width > maxImageSize.width || originalSize.height > maxImageSize.height) {
+ let scale = min(maxImageSize.width / originalSize.width,
+ maxImageSize.height / originalSize.height)
+ attachment.displaySize = CGSize(
+ width: round(originalSize.width * scale),
+ height: round(originalSize.height * scale)
+ )
+ }
+
+ let lowercasedClasses = (((attachment.attributes["class"] as? String)
+ ?? (element.attributes["class"] as? String) ?? "")).lowercased()
+ let lowercasedPath = attachment.contentURL?.lastPathComponent.lowercased()
+ ?? ((attachment.attributes["src"] as? String)
+ ?? (element.attributes["src"] as? String) ?? "").lowercased()
+ let pointSize = max(element.fontDescriptor.pointSize, style.font.pointSize)
+
+ // Footnote: width-only sizing, no explicit height (WXRead alignment)
+ if lowercasedClasses.contains("qqreader-footnote") || lowercasedPath == "note.png" {
+ attachment.verticalAlignment = .center
+ element.displayStyle = .inline
+ // Note: NO height calculation here. Width is set via HTML preprocessing
+ // (normalizeAttachmentHTMLMarkers adds width:1em). DTCoreText calculates
+ // height from aspect ratio automatically.
+ if !didLogFootnoteAttachment {
+ didLogFootnoteAttachment = true
+ print("[EPUB][Attachment] footnote classes=\(lowercasedClasses) path=\(lowercasedPath) original=\(string(from: originalSize)) display=\(string(from: attachment.displaySize)) font=\(pointSize)")
+ }
+ return
+ }
+
+ // Cover: screen-size base, already constrained by unified max above
+ if lowercasedClasses.contains("rd-front-cover-image") || lowercasedPath == "cover.jpg" {
+ let maxSize = UIScreen.main.bounds.insetBy(dx: 20, dy: 28).size
+ let currentOriginalSize = attachment.originalSize
+ if currentOriginalSize.width > 0, currentOriginalSize.height > 0 {
+ let scale = min(maxSize.width / currentOriginalSize.width, maxSize.height / currentOriginalSize.height)
+ if scale < 1.0 { // Only re-scale if larger than screen
+ attachment.displaySize = CGSize(
+ width: round(currentOriginalSize.width * scale),
+ height: round(currentOriginalSize.height * scale)
+ )
+ }
+ } else {
+ attachment.displaySize = CGSize(width: round(maxSize.width), height: round(maxSize.height))
+ }
+ attachment.verticalAlignment = .baseline
+ element.displayStyle = .block
+ if !didLogCoverAttachment {
+ didLogCoverAttachment = true
+ print("[EPUB][Attachment] cover classes=\(lowercasedClasses) path=\(lowercasedPath) original=\(string(from: currentOriginalSize)) display=\(string(from: attachment.displaySize))")
+ }
+ }
+}
+```
+
+## State of the Art
+
+| Old Approach | Current Approach | When Changed | Impact |
+|--------------|------------------|--------------|--------|
+| DTMaxImageSize = screen bounds | DTMaxImageSize = 1080x1920 | This phase | All images get correct ceiling at creation time |
+| Footnote: explicit height calc (0.54*pt in renderer, 0.14*pt in view) | Footnote: width-only from CSS, height auto-calculated | This phase | Consistent with WXRead, simpler, no conflicting overrides |
+| Cover: screen-size only, no unified max | Cover: screen-size + unified max ceiling | This phase | Rare edge case: very large cover images get proper constraint |
+| qrbodyPic: no max constraint | qrbodyPic: constrained by unified max | This phase | Fixes height overflow for image-heavy chapters |
+
+**Deprecated/outdated:**
+- `pointSize * 0.54` footnote height calculation in `prepareHTMLElementForReaderRendering` — replaced by width-only approach
+- `pointSize * 0.14` footnote height override in `normalizeInlineAttachments` — removed entirely
+- `pointSize * 0.14` footnote height override in `normalizeAttachmentDisplayIfNeeded` — removed entirely
+
+## Assumptions Log
+
+| # | Claim | Section | Risk if Wrong |
+|---|-------|---------|---------------|
+| A1 | DTCoreText correctly calculates height from aspect ratio when only width is set on an attachment (verified via DTTextAttachment.m source code reading) | Pattern 2: Footnote Width-Only | LOW — code at line 231-236 explicitly handles this case |
+| 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
+
+1. **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.
+
+2. **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.
+
+3. **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.
+
+## Environment Availability
+
+> No external dependencies required — all changes are to existing Swift source files.
+
+| Dependency | Required By | Available | Version | Fallback |
+|------------|------------|-----------|---------|----------|
+| Xcode / Swift compiler | Build & run | ✓ | (project toolchain) | — |
+| DTCoreText (CocoaPod) | Image attachment API | ✓ | (project-local) | — |
+| Demo EPUB sample | Visual verification | ✓ | 宝山辽墓材料与释读 | — |
+
+**Missing dependencies with no fallback:** None
+**Missing dependencies with fallback:** None
+
+## Validation Architecture
+
+### Test Framework
+| Property | Value |
+|----------|-------|
+| Framework | None (no unit test target for EPUBTextRendering) |
+| Config file | none |
+| Quick run command | `xcodebuild build -project ReadViewDemo/ReadViewDemo.xcodeproj -scheme ReadViewDemo -destination 'platform=iOS Simulator,name=iPhone 16'` |
+| Full suite command | Same as quick run (build-only verification) |
+
+### Phase Requirements → Test Map
+| Req ID | Behavior | Test Type | Automated Command | File Exists? |
+|--------|----------|-----------|-------------------|-------------|
+| QUAL-02 | Images don't overflow page | manual visual | Build + run demo + navigate to 图文章节 | N/A |
+| QUAL-03 | Image sizing rules are verifiable | code review | Verify max-size constant is 1080x1920 | N/A |
+| QUAL-04 | No performance degradation | manual timing | Compare pagination time before/after | N/A |
+
+### Sampling Rate
+- **Per task commit:** `xcodebuild build` (compilation check)
+- **Per wave merge:** Build + run demo + visual inspection of 图文章节
+- **Phase gate:** Visual verification that all three image types display correctly
+
+### Wave 0 Gaps
+- [ ] No automated image size assertion tests — manual visual verification required
+- [ ] No snapshot/regression test infrastructure for image rendering
+
+## Sources
+
+### Primary (HIGH confidence)
+- `Doc/WXRead/decompiled/WREpubTypesetter.m` §672-701 — `_WRPostProcessElementTree` max size logic
+- `Doc/WXRead/decompiled/WRCoreTextLayoutFrame.m` §615-635 — cover image drawing
+- `Doc/WXRead/resources/css/replace.css` §91-103 — `.bodyPic`, `.qrbodyPic`, `.qqreader-footnote` CSS
+- `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift` §217-262, §302-343, §345-404, §467-505 — current implementation
+- `ReadViewDemo/Pods/DTCoreText/Core/Source/DTTextAttachment.m` §216-245 — `setDisplaySize:withMaxDisplaySize:` implementation
+- `ReadViewDemo/Pods/DTCoreText/Core/Source/DTTextAttachment.h` — `originalSize`, `displaySize`, `verticalAlignment` API
+
+### Secondary (MEDIUM confidence)
+- `ReadViewDemo/ReadViewDemo/book/宝山辽墓材料与释读_副本/OEBPS/Text/Chapter_5.xhtml` — `qrbodyPic` HTML structure
+- `ReadViewDemo/ReadViewDemo/book/宝山辽墓材料与释读_副本/OEBPS/Text/cover.xhtml` — cover HTML structure
+- `ReadViewDemo/ReadViewDemo/book/宝山辽墓材料与释读_副本/OEBPS/Styles/stylesheets.css` — EPUB's own CSS rules
+
+## Metadata
+
+**Confidence breakdown:**
+- Standard stack: HIGH — DTCoreText is the existing project dependency, API is well-understood
+- Architecture: HIGH — pipeline is clearly documented, code paths are traced
+- Pitfalls: HIGH — all pitfalls identified from direct source code reading
+
+**Research date:** 2026-05-23
+**Valid until:** 2026-06-23 (stable — DTCoreText and project code are not changing rapidly)