--- phase: 08-pagination-quality-cache-performance plan: 02 type: summary date: 2026-05-23 --- # Phase 08-02 Summary ## Objective Improve pagination quality by enforcing avoidPageBreakInside, adding orphan/widow line control, and implementing general image fit-to-page sizing. ## Tasks Completed ### Task 1: RDEPUBTextLayoutConfig struct and wiring - Defined `RDEPUBTextLayoutConfig` in `RDEPUBTextRenderer.swift` with 4 properties: `avoidOrphans`, `avoidWidows`, `avoidPageBreakInsideEnabled`, `imageMaxHeightRatio` (default 0.85) - Follows the exact `RDEPUBTextRenderStyle` struct pattern: public struct, public stored properties, public init with defaults, static `.default` instance - Added `config` parameter to `RDEPUBTextLayouter.init` with `.default` default value - Added `config` parameter to `rd_paginatedFrames` extension with `.default` default value - All existing call sites continue to work unchanged (backward compatible) ### Task 2: avoidPageBreakInside enforcement + orphan/widow control - Added `avoidPageBreakInsideBoundary(in:minimumEnd:)` method in `RDEPUBTextLayouter.swift` - Detects when page end falls INSIDE an avoidPageBreakInside block (complement to existing check that only handles block start as boundary) - Pushes entire block to next page when block start >= minimumEnd - Gated on `config.avoidPageBreakInsideEnabled` - Added `orphanWidowAdjustedRange(from:minimumEnd:)` method - **Orphan control**: When page starts at a paragraph beginning and only 1-2 lines fit, pulls back to include more content from previous page - **Widow control**: When page ends with only 1-2 lines of a paragraph remaining, pushes entire paragraph to next page - Both gated on `config.avoidOrphans` and `config.avoidWidows` respectively - Inserted both checks in `adjustedRange()` between `preferredSemanticBoundary` and `preferredAttachmentBoundary` - Diagnostic strings emitted on trigger: "avoidPageBreakInside enforcement", "orphan control: ...", "widow control: ..." ### Task 3: General image fit-to-page sizing and diagnostics - Added `return` at end of cover image block to prevent fall-through to general handling - Added general image sizing block for all non-footnote, non-cover images: - Scales images exceeding `maxImageHeight` (screen height * 0.85) or `maxWidth` proportionally - Sets `verticalAlignment = .center` for block-level images (bodyPic, qrbodyPic, figure, .block displayStyle) - Added diagnostic output: - `[EPUB][Attachment] general image ...` with original/display sizes and scaled flag - `[EPUB][Attachment] dark mode: image colors preserved ...` when in dark mode - `[EPUB][Attachment] background: ...` with hasBackground check for attachment blocks ## Files Modified - `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift` — added `RDEPUBTextLayoutConfig` struct - `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextLayouter.swift` — added config property, avoidPageBreakInside enforcement, orphan/widow control - `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextPaginationSupport.swift` — added config parameter to `rd_paginatedFrames` - `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift` — added general image sizing, cover return, diagnostic output ## Build Status All three tasks verified with `xcodebuild build -workspace ReadViewDemo/ReadViewDemo.xcworkspace -scheme ReadViewDemo -destination 'platform=iOS Simulator,name=iPhone 17 Pro'` — BUILD SUCCEEDED for each.