From c95d80e257ec62f94c98e9cf928a1f9c165dbe89 Mon Sep 17 00:00:00 2001 From: shen <> Date: Thu, 21 May 2026 20:44:30 +0800 Subject: [PATCH] docs: add WXRead reflowable renderer design --- .../ReflowableEPUB_WXReadRenderer_Design.md | 251 ++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 Doc/FeatureSolution/ReflowableEPUB_WXReadRenderer_Design.md diff --git a/Doc/FeatureSolution/ReflowableEPUB_WXReadRenderer_Design.md b/Doc/FeatureSolution/ReflowableEPUB_WXReadRenderer_Design.md new file mode 100644 index 0000000..1a11bb2 --- /dev/null +++ b/Doc/FeatureSolution/ReflowableEPUB_WXReadRenderer_Design.md @@ -0,0 +1,251 @@ +# Reflowable EPUB 使用 WXRead 风格原生渲染:详细设计 + +> 文档目的:讨论并固化“将 ReadViewSDK 的 reflowable EPUB 渲染/排版/分页改为参考 Doc/WXRead 的微信读书(WXRead)原生渲染方式”的可落地设计,供后续开发与回归使用。 +> 版本:v0(设计草案) +> 日期:2026-05-21 + +## 0. 背景与结论(先说人话) + +ReadViewSDK 当前对 EPUB 有三类渲染路径: + +- `RDEPUBReadingProfile.webFixedLayout`:Fixed Layout EPUB → `WKWebView`(保持不变) +- `RDEPUBReadingProfile.webInteractive`:交互式 EPUB(JS/音视频/表单/iframe/外链/bridge)→ `WKWebView`(保持不变) +- `RDEPUBReadingProfile.textReflowable`:普通 reflowable EPUB → **当前走 DTCoreText → NSAttributedString → CoreText 分页 → 原生文本视图**(这是我们要“升级成 WXRead 风格”的主战场) + +本次改造的最小可落地方向是:**保留三分流策略不变**,只增强 `.textReflowable` 分支,使其在“CSS 分层、样式一致性、资源解析、分页稳定性”等方面更接近 `Doc/WXRead/analysis/EPUB渲染管线详解.md` 所描述的 WXRead 管线,而不是引入新的 WebView 渲染。 + +## 1. 目标 / 非目标 + +### 1.1 目标(In Scope) + +- G1:reflowable EPUB 的正文渲染/排版/分页改为“WXRead 风格原生渲染管线”: + - XHTML/HTML →(CSS 分层 + 解析 + 后处理)→ `NSAttributedString` + - `NSAttributedString` →(CoreText 分页)→ 单页内容 + - 单页内容 → 原生绘制/展示 +- G2:保持 Fixed Layout 与交互式 EPUB 的 `WKWebView` 路径不回归。 +- G3:不破坏 `RDURLReaderController` 打开 `.epub` / `.txt` 的主流程。 +- G4:Demo 可用于验证:至少 2-3 本典型 reflowable EPUB 在段落/标题/图片/链接等常见内容下可稳定阅读。 + +### 1.2 非目标(Out of Scope) + +- N1:Fixed Layout EPUB 切换为原生渲染(明确不做)。 +- N2:交互式 EPUB 切换为原生渲染(明确不做)。 +- N3:一次性复刻 WXRead 对 DTCoreText 的所有深度魔改(例如自定义 CSS 属性体系、复杂后处理、分页避断规则等)——本次先按“问题驱动”逐步对齐。 + +## 2. 关键事实核验(当前代码真实路径) + +### 2.1 渲染路径分流(已存在) + +- 判定在 `Sources/RDReaderView/EPUBCore/RDEPUBParser+ReadingProfile.swift`: + - `metadata.layout == .fixed` → `.webFixedLayout` + - `hasInteractiveContent() == true` → `.webInteractive` + - 否则 → `.textReflowable` + +### 2.2 `.textReflowable` 当前实现(已存在,且是正确切入点) + +`Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift`: + +- `publication.readingProfile == .textReflowable` 时: + - 使用 `RDEPUBTextBookBuilder(renderer: resolvedTextRenderer())` + - 默认 renderer 是 `RDEPUBDTCoreTextRenderer`(`#if canImport(DTCoreText)`) + - 分页使用 `NSAttributedString.ss_pageRanges(size:)`(`CTFramesetterCreateFrame` + `CTFrameGetVisibleStringRange`) + - UI 展示使用 `RDEPUBTextContentView` + +结论:我们不需要“新起一个阅读器”,只要把 `.textReflowable` 的 **渲染(typesetter)层**与部分 **分页策略**升级即可。 + +## 3. WXRead 参考模型(我们要对齐的最小子集) + +来自 `Doc/WXRead/analysis/EPUB渲染管线详解.md` 的管线: + +1) `WREpubParser`:解析 EPUB 结构(spine、manifest、resourceMap) +2) `WREpubTypesetter`:XHTML → `NSAttributedString`(CSS 级联 + HTML 解析 + 后处理) +3) `WRCoreTextLayouter`:`NSAttributedString` → 分页布局(`CTTypesetter` + 分页算法) +4) `WRCoreTextLayoutFrame`:单页 layout frame +5) `WRPageView`:绘制到屏幕 + +本次设计对齐重点(最小集合): + +- A:**CSS 分层与合成**(default/replace/dark/epub/user)并注入到渲染输入 +- B:**资源解析**(图片/CSS 的相对路径 baseURL)与稳定性保障 +- C:在现有分页基础上逐步迭代(先可用,后对齐“避免断页”等高级策略) + +## 4. 是否能直接使用 Doc/WXRead 中的 JS/CSS? + +结论:**不建议、也不应该直接把“来自微信读书 App bundle 的私有 JS/CSS”拷贝进 SDK 作为产品代码**;但可以按以下原则“选择性使用”: + +### 4.1 可以使用的情况(需满足其一) + +- 文件本身带有明确开源许可证声明,且我们按许可证要求引入(保留 license、署名、NOTICE 等),并建议从官方 upstream 获取: + - 例如 `Doc/WXRead/resources/js/rangy-core.js` 明确标注 MIT + - 例如 `Doc/WXRead/resources/js/Readability.js` 明确标注 Apache-2.0 + +> 建议:即便文件里有 license 头,也优先从其原始开源仓库拉取对应版本,而不是从逆向提取的副本直接入库,以降低合规风险。 + +### 4.2 不建议/不能直接使用的情况 + +- 无明确许可证头、看起来是微信读书私有逻辑/样式(例如 `weread-highlighter.js`、`MediaPlatform.js`、`replace.css` 等):默认视为私有作品,不应直接拷贝使用。 +- 即便是“Safari 默认样式”类文件(例如 `default.css` 的注释提到 Safari),也不建议直接照搬;我们可以根据需求写一份“SDK 自己的 default.css / replace.css”,只实现必要规则。 + +### 4.3 对本次需求的实际影响 + +本次 reflowable EPUB 走原生渲染,不依赖 WebView,因此 **JS 不是本次必需**。 +CSS 方面我们需要的是“分层策略”和一小部分通用排版规则,可在 SDK 内重写为“WXRead 风格的默认样式集合”。 + +## 5. 详细设计(核心) + +### 5.1 总体架构:在现有 `.textReflowable` 上增量替换 renderer + +新增一个 renderer(实现 `RDEPUBTextRenderer`): + +- `RDEPUBWXReadTextRenderer`(新) + - 输入:`html: String`, `baseURL: URL?`, `style: RDEPUBTextRenderStyle` + - 输出:`RDEPUBRenderedChapterContent`(`NSAttributedString` + `fragmentOffsets`) + - 内部职责: + 1) 读取/生成 CSS 各层(default/replace/dark/user) + 2) 与 EPUB 自带 CSS 共同作用(通过 HTML 注入 + baseURL) + 3) 调用 DTCoreText builder 生成 attributedString + 4) 做最小后处理(段落间距/字体/颜色标准化、fragment marker 提取等) + +切换点: + +- 在 `RDEPUBReaderController.resolvedTextRenderer()`(或其配置位置)增加策略:当开关启用时选择 `RDEPUBWXReadTextRenderer()`,否则沿用 `RDEPUBDTCoreTextRenderer()`。 + - 建议默认先提供“实验开关”(仅 Demo / debug 可见),降低回归风险。 + +### 5.2 CSS 分层策略(WXRead 风格) + +我们在 SDK 内实现与 `Doc/WXRead/analysis/EPUB渲染管线详解.md` 一致的分层概念,但不直接照搬其私有样式文件: + +- Layer 1:`default.css`(SDK 自己维护的基础排版规则) +- Layer 2:`replace.css`(SDK 自己维护的替换/增强规则:标题、代码块、图片最大宽度等) +- Layer 3:`dark.css`(暗色主题覆盖,仅在暗色主题启用) +- Layer 4:EPUB 嵌入 CSS(书籍自带,DTCoreText 解析 HTML 时自然生效;相对路径靠 baseURL) +- Layer 5:用户设置 CSS(由 `RDEPUBTextRenderStyle` 动态生成:字体、字号、行高、背景色、文字色等) + +实现方式(建议): + +1) 新增 `RDEPUBWXReadStyleSheetBuilder`: + - `func makeDefaultCSS() -> String` + - `func makeReplaceCSS() -> String` + - `func makeDarkCSS(theme: RDEPUBTheme) -> String?` + - `func makeUserCSS(style: RDEPUBTextRenderStyle, theme: RDEPUBTheme) -> String` + - `func composeCSS(...) -> String`(按层拼接,后层覆盖前层) + +2) 在 renderer 中将合成后的 CSS 注入到 HTML: + - 若存在 ``:插入 `