ReadViewSDK/.planning/phases/01-current-engine-boundaries/01-RESEARCH.md

13 KiB
Raw Blame History

Phase 1: 对齐现状、边界与重构切入点 - Research

Researched: 2026-05-21 Domain: iOS EPUB reader architecture / DTCoreText-based native reflowable rendering Confidence: HIGH

<user_constraints>

User Constraints (from CONTEXT.md)

No user constraints - all decisions at the agent's discretion. </user_constraints>

<architectural_responsibility_map>

Architectural Responsibility Map

Capability Primary Tier Secondary Tier Rationale
EPUB URL entry and format routing Browser/Client RDURLReaderController decides epub vs txt and embeds the correct reader controller.
EPUB structure parsing and reading-profile split Browser/Client RDEPUBParser and RDEPUBPublication classify .textReflowable vs webFixedLayout / webInteractive.
Reflowable native chapter rendering Browser/Client RDEPUBDTCoreTextRenderer and RDEPUBTextBookBuilder own HTML to NSAttributedString and page slicing.
Fixed-layout and interactive content rendering Browser/Client RDEPUBWebView and RDEPUBPaginator keep WKWebView as the rendering and measurement path.
Page container and interaction shell Browser/Client RDReaderView is the stable presentation container that Phase 1 must not modify.

</architectural_responsibility_map>

<research_summary>

Summary

当前仓库已经存在一条独立的原生 reflowable 路径,而不是“所有 EPUB 都走 WebView”。RDEPUBReaderController.paginatePublication 会在 publication.readingProfile == .textReflowable 时改走 RDEPUBTextBookBuilder,使用 RDEPUBDTCoreTextRenderer 把章节 HTML 转成 NSAttributedString,再通过 ss_pageRanges(size:) 基于 CoreText 可见范围切页。该路径最终产出 RDEPUBTextBook / RDEPUBTextPage,并由 RDEPUBTextContentView 在现有 RDReaderView 容器内展示。

Fixed Layout 与交互式 EPUB 的边界已经在代码里清晰存在。RDEPUBParser.readingProfile()metadata.layout == .fixed 归类为 .webFixedLayout,并把包含脚本/多媒体/iframe/form 等交互内容的资源归类为 .webInteractive。这两类内容继续走 RDEPUBWebView / RDEPUBPaginator / WKWebView 体系,不应被纳入本次原生重构。

WXRead 参考资料说明,目标不是“再包一层轻量 renderer”而是把当前 DTCoreText 路径升级为完整的 typesetter + layouter + layout frame 体系:五层 CSS 级联、自定义 DTCoreText 属性键、块级避免断页、页面级元数据、图片与背景等页面语义。对本仓库来说,最稳妥的切入点是保留 RDURLReaderController -> RDEPUBReaderController -> RDReaderView 外层契约不变,把增强集中在 EPUBTextRendering 内部及其与 reader state 的对接点。

Primary recommendation: Phase 1 应先把当前 .textReflowable 调用链、WebView 边界、兼容性约束和可演进触点写成明确文档,再以这些文档作为 Phase 2/3 的实现基线。 </research_summary>

<standard_stack>

Standard Stack

Core

Library Version Purpose Why Standard
UIKit iOS 15+ SDK Reader container, controllers, text view presentation 现有 reader UI 与 RDReaderView 全部建立在 UIKit 上。
CoreText System Native pagination and text measurement 当前 ss_pageRanges(size:) 直接使用 CTFramesetterCreateFrame / CTFrameGetVisibleStringRange
DTCoreText 1.6.28 HTML/CSS to NSAttributedString 当前原生 reflowable 渲染核心已经依赖它,后续定制也必须围绕它展开。
WebKit System Fixed-layout / interactive EPUB rendering and pagination measurement 非 reflowable-native 路径仍依赖 WKWebView

Supporting

Library Version Purpose When to Use
DTFoundation 1.7.19 DTCoreText supporting parser/runtime pieces DTCoreText HTML 解析与附件支持所需。
ZIPFoundation 0.9.20 EPUB archive handling EPUB 解压与资源访问基础设施。
SnapKit 5.7.1 UI layout helper Reader UI 周边布局已有依赖,但不是本次内核切入点。

Alternatives Considered

Instead of Could Use Tradeoff
Directly evolving EPUBTextRendering A second native engine alongside the existing one 会增加长期双轨维护成本,并破坏 roadmap 已锁定的“旧引擎直接演进”原则。
DTCoreText-based native rendering Full WKWebView for all EPUB modes 无法满足 WXRead-style page metadata / page-frame semantics / native selection control goals。
Keeping RDReaderView stable Reworking page container and gesture stack together 改动半径过大Phase 1 明确要求把翻页容器排除出本次重构。

Installation:

pod install

</standard_stack>

<architecture_patterns>

Architecture Patterns

System Architecture Diagram

RDURLReaderController
  -> RDEPUBReaderController
    -> RDEPUBParser.parse()
      -> RDEPUBPublication / readingProfile()
        -> textReflowable
          -> RDEPUBTextBookBuilder
            -> RDEPUBDTCoreTextRenderer
              -> DTHTMLAttributedStringBuilder
              -> NSAttributedString
            -> RDEPUBTextPaginationSupport.ss_pageRanges()
            -> RDEPUBTextBook / RDEPUBTextPage
          -> RDEPUBTextContentView
          -> RDReaderView
        -> webFixedLayout
          -> RDEPUBWebView.loadFixedSpread()
          -> RDReaderView
        -> webInteractive
          -> RDEPUBPaginator + RDEPUBWebView.loadPage()
          -> RDReaderView
Sources/RDReaderView/
├── EPUBCore/             # Parser, publication, web pagination, render requests
├── EPUBTextRendering/    # Native reflowable rendering and pagination
├── EPUBUI/               # Reader controller, content views, settings
└── RDReaderView.swift    # Stable page container and gestures

Pattern 1: Preserve the outer reader contract

What: Keep RDURLReaderController, RDEPUBReaderController, persistence, location mapping, and RDReaderView as the stable shell while replacing internals inside EPUBTextRendering. When to use: Any phase that upgrades the reflowable engine without expanding scope into the page container. Example: RDEPUBReaderController.paginatePublication already branches into text vs web rendering without changing the reader container contract.

Pattern 2: Keep rendering and presentation separate

What: Produce rich RDEPUBTextBook / RDEPUBTextPage models first, then let RDEPUBTextContentView render those models. When to use: When introducing page-level metadata, selection offsets, image semantics, or new pagination logic. Example: RDEPUBTextBookBuilder owns chapter rendering and page slicing, while RDEPUBTextContentView only paints attributed substrings and overlays highlights/search state.

Pattern 3: Branch by publication profile early

What: Decide native reflowable vs fixed vs interactive before rendering work starts. When to use: Any future entry-point or repagination logic. Example: RDEPUBParser.readingProfile() uses layout == .fixed and interactive-content detection to keep WKWebView modes out of the native path.

Anti-Patterns to Avoid

  • Touching RDReaderView during engine migration: Phase 1 explicitly excludes page curl / scroll / gesture container changes.
  • Building a parallel renderer shell: The roadmap already rejects a “new engine beside old engine” approach.
  • Mixing WebView-only semantics into the native path: textReflowable should evolve around NSAttributedString and CoreText, not around hidden WKWebView dependencies. </architecture_patterns>

<dont_hand_roll>

Don't Hand-Roll

Problem Don't Build Use Instead Why
HTML parsing A brand-new EPUB HTML parser DTCoreText builder extension points 仓库已经围绕 DTCoreText 建立基础能力,重写 parser 成本高且风险大。
EPUB mode classification Ad-hoc view-layer checks RDEPUBParser.readingProfile() 当前 profile 分流已经覆盖 fixed / interactive / text reflowable。
Reader shell replacement A new page container Existing RDReaderView contract 现有 container 已承担翻页模式、双页与方向逻辑,超出本次范围。
Location/highlight transport A second mapping format Existing RDEPUBLocation + RDEPUBTextOffsetRangeInfo 这些类型已被 highlights / search / restore location 链路依赖。

Key insight: 应该复用现有阅读器契约和数据模型,把新增复杂度集中在旧引擎内部,而不是在外围重造一层基础设施。 </dont_hand_roll>

<common_pitfalls>

Common Pitfalls

Pitfall 1: Confusing native reflowable with the WebView paginator

What goes wrong:RDEPUBPaginator 当成所有 reflowable 的基础,导致 Phase 2/3 方案偏向继续强化 WebView。 Why it happens: 当前工程同时存在 textReflowable 和基于 WKWebView 的分页/展示路径。 How to avoid: 在所有 Phase 1 输出里明确:RDEPUBPaginator 服务于 fixed 或 interactive/web paths原生重构主战场是 EPUBTextRenderingWarning signs: 计划任务把 RDEPUBPaginator 作为 CSS 五层或页面元数据的主要落点。

Pitfall 2: Breaking RDReaderView assumptions while changing pagination

What goes wrong: 新分页结果不再满足现有 page index / container reuse / orientation repagination 约定。 Why it happens: 容易把引擎升级和容器升级绑在一起处理。 How to avoid:RDEPUBTextBook.pages / RDEPUBTextPage 视为 Phase 2/3 的兼容面,并在策略中显式约束 RDReaderView 不改。 Warning signs: 方案需要修改 RDReaderView.DataSource、翻页方向、双页逻辑或 content view reuse 协议。

Pitfall 3: Introducing richer layout semantics without preserving location mapping

What goes wrong: 新分页器能排版复杂元素,但阅读位置、高亮、搜索结果定位失效。 Why it happens: 只关注排版帧,不关注 pageStartOffset/pageEndOffsetRDEPUBLocation 的传递。 How to avoid: 把 offset continuity、fragment offset、selection range transport 作为 Phase 2/3 的第一类兼容目标。 Warning signs: 设计里没有说明新页面元数据如何回写 RDEPUBTextBookRDEPUBSelectionRDEPUBHighlight。 </common_pitfalls>

<code_examples>

Code Examples

Current reflowable split

if publication.readingProfile == .textReflowable {
    let renderer = resolvedTextRenderer()
    let builder = RDEPUBTextBookBuilder(renderer: renderer)
    let textBook = try builder.build(
        parser: parser,
        publication: publication,
        pageSize: pageSize,
        style: renderStyle
    )
}

Current fixed vs interactive boundary

public func readingProfile() -> RDEPUBReadingProfile {
    if metadata.layout == .fixed {
        return .webFixedLayout
    }
    return hasInteractiveContent() ? .webInteractive : .textReflowable
}

Current native pagination primitive

while visibleRange.location + visibleRange.length < length {
    let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(location, 0), path, nil)
    visibleRange = CTFrameGetVisibleStringRange(frame)
    ranges.append(NSRange(location: location, length: visibleRange.length))
    location += visibleRange.length
}

</code_examples>

<sota_updates>

State of the Art (2024-2025)

Old Approach Current Approach When Changed Impact
Generic HTML to attributed string only HTML to attributed string plus page-level layout semantics 已反映在 WXRead 参考资料中 需要把 CSS/HTML 处理与分页语义一起设计,而不是只换 builder options。
Simple visible-range page slicing Block-aware layout frames and custom pagination metadata 已反映在 WXRead 参考资料中 当前 CTFrameGetVisibleStringRange 模式会成为主要替换对象。
Styling as a small option bag Multi-layer stylesheet cascade 已反映在 WXRead 参考资料中 dtOptions 需要被更明确的 stylesheet pipeline 取代或扩展。

New tools/patterns to consider:

  • DTCoreText attribute extension points: 适合承接 WXRead 风格自定义属性键。
  • Page-level layout model: 需要在 RDEPUBTextChapter / RDEPUBTextPage 附近引入更丰富的元数据,而不是只保存范围。

Deprecated/outdated:

  • “Simple DTCoreText renderer + pageRanges” 作为长期方案:对复杂块元素、页面语义和 CSS 分层都不够。
  • “为原生重构同时替换翻页容器”:与当前范围控制相冲突。 </sota_updates>

<open_questions>

Open Questions

  • LegacyRDReaderController/RDEPUBTextPaging.swift 与新 EPUBTextRendering 之间哪些实现差异仍值得迁移,哪些应直接废弃,需要在 Phase 1 执行阶段做一次并列审计。
  • Doc/FeatureSolution/ReflowableEPUB_WXReadRenderer_Design.md 与当前源码是否已经出现偏差,需要在 Plan 01-02 中显式核对。
  • 未来自定义 DTCoreText 属性应优先挂在 NSAttributedString、页模型,还是单独的 layout frame 对象上,需要在策略文档中做出建议但不提前定版实现。 </open_questions>