Three plans for Phase 8 (分页质量、缓存与性能采样): - 08-01: Pagination cache key struct and manager stub (QUAL-01) - 08-02: Unified 1080x1920 max-size constraint for all images, footnote width-only sizing (QUAL-02, QUAL-03, QUAL-04) - 08-03: Pagination timing instrumentation with [EPUB][Perf] diagnostic output (QUAL-04) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
15 KiB
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 08-pagination-quality-cache-performance | 02 | execute | 1 |
|
false |
|
|
Purpose: Complex illustrated chapters currently have images that overflow pages, inconsistent footnote sizing, and no unified ceiling. This fixes QUAL-02 (pagination quality), QUAL-03 (verifiable image rules), and addresses QUAL-04 (lightweight operation, no perf impact).
Output: Updated RDEPUBTextRendererSupport.swift (main changes), RDEPUBDTCoreTextRenderer.swift (DTMaxImageSize), RDEPUBTextContentView.swift (remove footnote override).
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/08-pagination-quality-cache-performance/08-CONTEXT.md @.planning/phases/08-pagination-quality-cache-performance/08-RESEARCH.mdFrom DTCoreText (CocoaPod, DTTextAttachment):
attachment.originalSize: CGSize— the image's intrinsic pixel sizeattachment.displaySize: CGSize— the size used for layout (set by code)attachment.verticalAlignment: DTTextAttachmentVerticalAlignment— .center, .baseline, etc.element.displayStyle: DTHTMLElementDisplayStyle— .inline, .blockelement.fontDescriptor: UIFontDescriptor— font info from the elementelement.textAttachment: DTTextAttachment?— the attachment on the element
From RDEPUBTextRendererSupport.swift (current implementation, lines 217-262):
static func prepareHTMLElementForReaderRendering(
_ element: DTHTMLElement,
style: RDEPUBTextRenderStyle
) {
guard let attachment = element.textAttachment else { return }
// ... class/path detection, pointSize calculation ...
// footnote: height calc pointSize * 0.54, displaySize set
// cover: UIScreen.main.bounds.insetBy(dx: 20, dy: 28).size scaling
}
From RDEPUBDTCoreTextRenderer.swift (current implementation, line 100):
DTMaxImageSize: NSValue(cgSize: screenBounds.size) // ~353x757
From RDEPUBTextContentView.swift (current implementation, lines 263-282):
private func normalizeInlineAttachments(in content: NSMutableAttributedString, basePointSize: CGFloat) {
// footnote: targetHeight = pointSize * 0.14, displaySize set
}
From RDEPUBTextRendererSupport.swift (current implementation, lines 467-505):
private static func normalizeAttachmentDisplayIfNeeded(
in attributes: inout [NSAttributedString.Key: Any],
font: UIFont
) {
// footnote: targetHeight = font.pointSize * 0.14
// cover: maxWidth = max(UIScreen.main.bounds.width - 48, font.lineHeight * 8)
}
WXRead reference (Doc/WXRead/decompiled/WREpubTypesetter.m §672-701):
- Checks only
originalSize.width > maxSize.width(not height) - Max size is
CGSizeMake(1080, 1920) - Scale:
maxSize.width / originalSize.width - Sets
displaySizeto scaled size
File 1: RDEPUBDTCoreTextRenderer.swift — line 100
Change DTMaxImageSize value from NSValue(cgSize: screenBounds.size) to NSValue(cgSize: CGSize(width: 1080, height: 1920)). The screenBounds variable at line 92 can remain (used elsewhere or for future reference), but DTMaxImageSize must use the new constant. Per D-01.
File 2: RDEPUBTextRendererSupport.swift — 4 locations
Location A: prepareHTMLElementForReaderRendering (L217-262) — Add unified max-size block
Insert a new block immediately after guard let attachment = element.textAttachment else { return } (line 221), BEFORE the existing class/path detection (line 223). The block:
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)
)
}
Per D-01, D-02, D-03. This applies to ALL images before any type-specific handling.
Location B: prepareHTMLElementForReaderRendering footnote branch (L228-241) — Remove height calculation
Current code at lines 229-233:
let targetHeight = max(round(pointSize * 0.54), 1)
let originalSize = attachment.originalSize
let aspectRatio = originalSize.height > 0 ? originalSize.width / originalSize.height : 1
let targetWidth = max(round(targetHeight * max(aspectRatio, 0.1)), 1)
attachment.displaySize = CGSize(width: targetWidth, height: targetHeight)
Replace with: remove the height/width calculation entirely. Keep only attachment.verticalAlignment = .center and element.displayStyle = .inline. The return at the end stays. Per D-07, D-08. Width is already set via HTML preprocessing (width:1em in normalizeAttachmentHTMLMarkers). DTCoreText auto-calculates height from aspect ratio when only width is set.
Also remove the pointSize local variable usage from this branch — pointSize is still used by the logging line, so keep the variable declaration but it's no longer used for sizing.
Location C: normalizeAttachmentHTMLMarkers (L359-361) — Remove height:1em from footnote style fragments
Current styleFragments at line 360-361:
"width:1em",
"height:1em",
Change to:
"width:1em",
Remove "height:1em" line. Per D-10.
Location D: normalizeAttachmentDisplayIfNeeded (L477-482) — Remove footnote height override
Lines 477-483 currently compute footnote displaySize with font.pointSize * 0.14. Remove the entire if lowercasedClasses.contains("qqreader-footnote") || lowercasedPath == "note.png" block (lines 477-483). The footnote is already handled correctly by the unified max-size + width-only CSS approach in prepareHTMLElementForReaderRendering and normalizeAttachmentHTMLMarkers. Per D-08, D-09.
Keep the cover path (lines 484-494) — it acts as a fallback and does not conflict.
File 3: RDEPUBTextContentView.swift — normalizeInlineAttachments (L263-282)
Remove the footnote height override logic inside normalizeInlineAttachments. Lines 270-278:
guard classes.contains("qqreader-footnote") || source.contains("note.png") else { return }
let pointSize = max(basePointSize, 1)
let targetHeight = max(round(pointSize * 0.14), 1)
...
attachment.displaySize = CGSize(width: targetWidth, height: targetHeight)
Replace the entire DTTextAttachment branch body with just a guard + return (skip footnote attachments entirely — they're already sized correctly from the renderer pipeline). The function should still iterate but do nothing for footnotes. Per D-09.
Build verification: After all changes, run xcodebuild build to confirm compilation.
xcodebuild build -project ReadViewDemo/ReadViewDemo.xcodeproj -scheme ReadViewDemo -destination 'platform=iOS Simulator,name=iPhone 16' 2>&1 | tail -5
<acceptance_criteria>
- RDEPUBDTCoreTextRenderer.swift: DTMaxImageSize line contains CGSize(width: 1080, height: 1920) (not screenBounds.size)
- RDEPUBTextRendererSupport.swift: prepareHTMLElementForReaderRendering contains let maxImageSize = CGSize(width: 1080, height: 1920) BEFORE any class/path detection
- RDEPUBTextRendererSupport.swift: prepareHTMLElementForReaderRendering footnote branch does NOT contain pointSize * 0.54 or any targetHeight/targetWidth calculation
- RDEPUBTextRendererSupport.swift: prepareHTMLElementForReaderRendering footnote branch contains attachment.verticalAlignment = .center and element.displayStyle = .inline
- RDEPUBTextRendererSupport.swift: normalizeAttachmentHTMLMarkers footnote styleFragments contain "width:1em" but NOT "height:1em"
- RDEPUBTextRendererSupport.swift: normalizeAttachmentDisplayIfNeeded does NOT contain pointSize * 0.14 or font.pointSize * 0.14 for footnote path
- RDEPUBTextContentView.swift: normalizeInlineAttachments does NOT contain pointSize * 0.14 height override for footnotes
- xcodebuild build exits 0
</acceptance_criteria>
All three image sizing code paths are updated: unified 1080x1920 max-size in prepareHTMLElementForReaderRendering, DTMaxImageSize set to 1080x1920, footnote height overrides removed from all three locations, footnote CSS uses width-only (width:1em), build compiles
Changed files:
- RDEPUBDTCoreTextRenderer.swift (DTMaxImageSize)
- RDEPUBTextRendererSupport.swift (prepareHTMLElementForReaderRendering, normalizeAttachmentHTMLMarkers, normalizeAttachmentDisplayIfNeeded)
- RDEPUBTextContentView.swift (normalizeInlineAttachments)
- Build and run ReadViewDemo on simulator
- Open the demo book "宝山辽墓材料与释读"
- Navigate to the cover page — verify cover image displays correctly (not cropped, not stretched, centered)
- Navigate to Chapter 5 (has qrbodyPic images) — verify images display fully within page bounds, no height overflow
- Navigate to a chapter with footnote images — verify footnote images are inline with text, same height as text characters, not oversized
- Switch to dark mode — verify images still display correctly
- Check that page breaks around images are reasonable (no orphan images at page tops/bottoms) Type "approved" if all image types display correctly, or describe which image type has issues
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| EPUB HTML input -> DTCoreText parser | Untrusted HTML/CSS from EPUB files enters the rendering pipeline |
| DTCoreText attachment -> willFlushCallback | Image dimensions from EPUB metadata could be arbitrary |
STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|---|---|---|---|---|
| T-08-01 | Tampering | Image size from EPUB metadata | mitigate | Unified max-size constraint (1080x1920) caps any image regardless of declared dimensions |
| T-08-02 | Denial of Service | Maliciously large images | mitigate | DTMaxImageSize (first pass) + willFlushCallback max-size (second pass) ensure no image exceeds 1080x1920 display size |
| T-08-SC | Tampering | npm/pip/cargo installs | accept | No new packages installed in this phase |
| </threat_model> |
<success_criteria>
- All images constrained to 1080x1920 max (QUAL-03: verifiable processing rules)
- Complex illustrated chapters display without image overflow or unreasonable whitespace (QUAL-02)
- Image sizing is a lightweight operation in willFlushCallback with negligible perf impact (QUAL-04)
- Footnote images are inline, text-height, width-only (WXRead alignment) </success_criteria>