# Phase 4: 接回现有 reader 能力链路 - Research **Researched:** 2026-05-22 **Domain:** iOS EPUB reader reintegration / location mapping / highlight-search-state continuity **Confidence:** HIGH ## User Constraints No `CONTEXT.md` exists for this phase. Planning is based on ROADMAP requirements, current code, and completed artifacts from Phases 1-3 only. ## Architectural Responsibility Map | Capability | Primary Tier | Secondary Tier | Rationale | |------------|-------------|----------------|-----------| | Reflowable native pagination output | EPUBTextRendering | EPUBCore | Phase 3 already moved richer page semantics into `RDEPUBTextBook` / `RDEPUBTextPage`. | | Reader orchestration and restore logic | EPUBUI | EPUBCore | `RDEPUBReaderController` owns `paginatePublication`, `finishPagination`, restore, search navigation, selection, and configuration updates. | | Page lookup and navigator state | EPUBCore | EPUBUI | `RDEPUBReadingSession` remains the non-UI state owner for fixed/web flows and should keep that role rather than pushing state into `RDReaderView`. | | Highlight/search persistence semantics | EPUBCore | EPUBUI | `RDEPUBTextOffsetRangeInfo` and normalized locations are the durable compatibility contract. | | Page-turn UI and display modes | RDReaderView | EPUBUI | Explicitly out of scope to change; Phase 4 must adapt to existing `RDReaderView` APIs. | ## Summary Phase 3 strengthened the native reflowable engine but did not yet make that richer page model a first-class citizen across the whole reader capability chain. The main Phase 4 work is therefore not “more pagination”; it is reconnecting the existing reader shell to the upgraded `RDEPUBTextBook` path without changing `RDReaderView` or introducing a second native reader model. The current code already shows the exact reintegration seams: - `RDEPUBReaderController.paginatePublication` branches between native `RDEPUBTextBookBuilder`, fixed-layout snapshots, and `RDEPUBPaginator` WebKit measurement. - `applyTextBook`, `finishPagination`, `restoreReadingLocation`, and `currentVisibleLocation` already contain a parallel text-book path that bypasses `RDEPUBReadingSession.pageIndex(for:)`. - `pageContentView(readerView:pageNum:containerView:)` already swaps between `RDEPUBTextContentView` and `RDEPUBWebContentView`. - Search uses `RDEPUBTextSearchEngine` when `textBook` exists and `RDEPUBHTMLSearchEngine` otherwise. - Selection/highlight persistence for native text still depends on `RDEPUBTextOffsetRangeInfo` absolute offsets and chapter href normalization. That means Phase 4 should stabilize and tighten an already-existing split path rather than invent a new architecture. The right outcome is: 1. Reflowable native pages remain rendered through `RDEPUBTextBook`. 2. `RDEPUBReaderController` keeps one coherent restore/navigation/search/highlight API no matter whether the source page is native text or Web content. 3. Repagination triggered by font size, line height, viewport changes, or theme changes preserves reader state instead of resetting to page 1 or dropping search/highlight context. The riskiest compatibility areas are also already visible: - `RDEPUBReaderController.restoreReadingLocation` and `currentVisibleLocation` use separate logic for text-book vs snapshot pages. - `normalizedTextSelection` recomputes progression from absolute offset payloads; regressions here would silently break persisted highlights. - `refreshVisibleContentPreservingLocation` and `rebuildExternalTextBook` can preserve location only if the text-book lookup contract remains stable after repagination. - `flattenedTableOfContentsItems` and `pageNumber(for:)` need page-number continuity for TOC and search to stay believable. **Primary recommendation:** Treat Phase 4 as a reader-state consolidation phase. Strengthen `RDEPUBReaderController` and adjacent state surfaces so the Phase 3 native pagination model behaves like a first-class reader backend while preserving the existing `RDReaderView` page-turn container and the fixed / interactive WebKit paths. ## Code Evidence | Concern | Source | Why it matters | |--------|--------|----------------| | Native-vs-web pagination entry split | `RDEPUBReaderController.paginatePublication` | Defines Phase 4’s main re-integration entry point. | | Native restore and current-location logic | `RDEPUBReaderController.restoreReadingLocation`, `currentVisibleLocation` | Text-book navigation already bypasses `RDEPUBReadingSession`; this must stay coherent. | | Reader state owner for non-native flows | `RDEPUBReadingSession` | Existing navigation/session state should remain centralized instead of leaking into UI code. | | Native selection/highlight normalization | `RDEPUBReaderController.normalizedTextSelection`, `RDEPUBTextContentView`, `RDEPUBReadingModels` | Absolute offsets must survive reader interactions and persistence. | | Search backend switching | `RDEPUBReaderController.resolvedSearchMatches`, `RDEPUBTextSearchEngine` | Search behavior must remain uniform from the user’s perspective across render backends. | | Repagination triggers | `RDEPUBReaderController.configuration.didSet`, `handleViewportChangeIfNeeded`, `rebuildExternalTextBook`, `repaginatePreservingCurrentLocation` | These are the operational triggers most likely to break restore/state continuity. | ## Architecture Patterns ### Pattern 1: Consolidate reader behavior at the controller boundary **What:** Keep branching localized in `RDEPUBReaderController`, but make navigation, restore, search, and persistence semantics consistent across text-book and web paths. **Why:** The UI entry point already exists there; moving branching elsewhere would enlarge scope. ### Pattern 2: Keep offsets as the native compatibility key **What:** Native text selection, search, and highlight flows should continue to resolve through chapter href + absolute offset payloads. **Why:** Phase 3 deliberately preserved this invariant to make Phase 4 possible. ### Pattern 3: Repaginate by restoring location, not page number **What:** Font/theme/viewport changes should always preserve a semantic location and rebuild around it. **Why:** Page numbers are not stable under re-pagination; href + progression + offset-compatible selection are. ### Anti-Patterns to Avoid - Refactoring `RDReaderView` to understand native page metadata directly. - Creating a second navigation state machine just for the native text path. - Switching highlights/search to page-local identifiers that cannot survive repagination. ## Don't Hand-Roll | Problem | Don't Build | Use Instead | Why | |---------|-------------|-------------|-----| | Native reader shell | A new dedicated native-text controller | Existing `RDEPUBReaderController` split path | Scope stays contained and existing app entry points remain valid. | | Restore state | Page-number-only recovery | `RDEPUBLocation` + native page lookup + offset contract | Page numbers drift after repagination. | | Search compatibility | Separate search UI semantics for text pages | Existing `RDEPUBSearchState` and `RDEPUBSearchMatch` flow | User-facing search behavior should stay unified. | ## Common Pitfalls ### Pitfall 1: Letting native and web reader paths diverge in user-visible behavior If search, selection, or TOC navigation behaves differently based on backend, the reader becomes unpredictable. ### Pitfall 2: Preserving page numbers instead of semantic locations during repagination This causes subtle wrong-page restores after font, theme, or viewport changes. ### Pitfall 3: Refreshing visible content without reapplying current search/highlight state Native text pages are recreated from attributed substrings, so state must be re-derived every time. ## Open Questions - Should any of the new Phase 3 page metadata be surfaced to reader delegates in Phase 4, or remain internal until Phase 5 diagnostics? - Is `RDEPUBReadingSession` worth extending to cover native `RDEPUBTextBook` page lookup as well, or is the current controller-local split still the smallest safe change? - Which search/highlight and TOC navigation paths are most important to validate interactively in the demo during execution?