ReadViewSDK/Doc/WXRead/decompiled-doc.md

335 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# WXRead/decompiled 逆向代码说明
## 概述
此目录包含从读书 (WeRead) iOS 客户端二进制逆向还原的头文件和实现代码。共 44 个文件22 对 .h/.m分为三大模块
1. **阅读器核心** — 阅读控制器、页面渲染、排版引擎、EPUB 解析
2. **标注系统** — 划线、高亮、书签、想法、Pencil 手写笔记
3. **DTCoreText 定制** — 基于开源 DTCoreText 库的读书私有定制
---
## 架构总览
```
WRReaderViewController (阅读器主控制器)
├── WRPageViewController (翻页控制器, 支持 curl/slide/fade/none)
│ └── WRPageView (页面渲染视图, CoreText 直接绘制)
│ └── WRCoreTextLayoutFrame (单页排版帧)
│ └── WRCoreTextLayoutLine (单行排版)
├── WREpubParser (EPUB 文件解析)
├── WREpubTypesetter (XHTML → NSAttributedString 排版)
│ └── DTHTMLAttributedStringBuilder (HTML DOM → 属性字符串)
│ └── DTHTMLElement (DOM 节点)
├── WRChapterData (章节数据模型)
├── WRChapterPageCount (分页计算器)
├── WRCoreTextLayouter (CoreText 排版器)
└── WRBookmark / WRPageHighlight / WRPageMark / WRPageUnderline (标注模型)
```
---
## 阅读器核心模块
### `WRReaderViewController`
阅读器的主入口控制器431 个方法,管理阅读状态的完整生命周期。
**核心职责:**
- 管理 `WRReadingProgress`(阅读进度:章节索引、页码、字符偏移、滚动位置、阅读百分比)
- 章节导航:`gotoChapterIdx:position:positionOfFile:`、`jumpReadingToNextChapter`、`jumpReadingToPreChapter`
- 页面渲染:`renderPageView:progressData:source:` — 获取/计算章节数据、分页、创建/更新 WRPageView
- 排版重排:`recomposeCurrentPageViewWithSource:` — 字号/行距/主题变更后触发
- 进度持久化:`_saveReadingProgressAndIsAsync:`
- 支持涂鸦模式 (doodle mode) 和自动阅读 (auto-read)
- 通过 `WRReaderViewControllerDelegate` 回调通知外部进度变化和退出
### `WRPageViewController`
自定义的翻页控制器,支持四种翻页动画:
| 枚举值 | 样式 | 说明 |
|--------|------|------|
| `WRPageFlippingStyleCurl` | 纸张翻页 | 基于 UIPageViewController 的 curl 效果 |
| `WRPageFlippingStyleSlide` | 水平滑动 | UIScrollView 驱动的滑动翻页 |
| `WRPageFlippingStyleFade` | 淡入淡出 | 交叉渐变 |
| `WRPageFlippingStyleNone` | 无动画 | 瞬间切换 |
**关键特性:**
- 内部封装 `UIPageViewController`,提供 `WRPageViewControllerDelegate` / `WRPageViewControllerDataSource` 协议
- 包含 3 个故障修补方法,处理 Apple `UIPageViewController` 的已知崩溃:
- `detectNavigationDirectionCrashWithPageViewController:` — 检测导航方向崩溃
- `patchNavigationDirectionFault` — 修复导航方向状态不一致
- `patchNoViewControllerManagingPageViewFault` — 修复子 VC 丢失
- `patchUIPageCurlFault` — 修复快速翻页时的动画故障
### `WRPageView`
单页渲染视图,继承 `UIView`,通过 CoreText 的 `drawRect:` 直接绘制文本,不使用 `UILabel``UITextView`
**核心绘制:**
- `drawInContext:withData:size:inRect:position:` — 将文本和行内图片直接绘制到 CGContext
- 通过 `CTLineGetStringIndexForPosition` 进行 CoreText 级别的坐标点击测试
**UI 覆盖层:**
- 章节标题头 (headerLabel)、背景图片 (backgroundImageView)
- 加载指示器 (activityIndicator)、加载进度条 (loadingProgressView)
- 状态提示 (statusLabel)、重试按钮 (retryButton)
- 分享按钮、书签按钮、字号调节按钮
- 好友想法按钮 (friendReviewsButton)
**文本选区:**
- `stringIndexForPoint:` / `lineRangeForStringIndex:` — CoreText 坐标与字符索引转换
- `isSelecting` / `selectedRange` — 选区状态
- `clearSelection` — 清除选区
---
## EPUB 解析与排版
### `WREpubParser`
EPUB 文件解析器,处理完整的 EPUB 结构:
**解析流程:** `container.xml``content.opf``spine` → 章节文件
**主要 API**
- `initWithFilePath:book:` — 初始化
- `parse:` — 执行解析,返回 YES/NO
- `parseContainerXML:` — 解析 container.xml 获取 OPF 路径
- `parseOPFAtRelativePath:error:` — 解析 content.opf填充章节列表和资源映射
- `parseNCX:` — 解析 toc.ncx 目录树
- `contentForChapterAtIndex:error:` — 读取单章 XHTML 内容
- `absolutePathForResource:` — 将相对资源路径解析为绝对路径
**错误码:**
| 错误码 | 含义 |
|--------|------|
| -1000 | 文件未找到 |
| -1001 | container.xml 解析失败 |
| -1002 | OPF 解析失败 |
| -1003 | NCX 解析失败 |
| -1004 | spine 为空 |
| -1005 | 章节加载失败 |
| -1006 | 解密失败 |
### `WREpubTypesetter`
EPUB 排版器XHTML → `NSAttributedString` 的转换核心。全部为类方法,无需实例化。
**主入口方法:**
```objc
+ (NSAttributedString *)attributeStringWithFilePath:priority:
insertArticleToolAttachment:insertBookChapterToolAttachment:
insertRecommendView:book:chapter:pageFlippingStyle:
renderErrorReason:isStyleFileNotFound:options:
```
**处理流程:**
1. 加载并级联合并 CSS用户设置 > EPUB 内嵌 > 默认样式)
2. 将 XHTML + 合并后的 CSS 喂入 `DTHTMLAttributedStringBuilder`
3. 遍历生成的元素树,修补图片、链接、自定义属性
4. 应用用户排版偏好(字体、行距、主题)
5. 可选截断用于免费试读预览
**自定义 CSS 属性:**
- `WREpubTypesetterVerticalCenterStyleAttribute` — 行内元素垂直居中(`wr-vertical-center-style`
- `WREpubTypesetterPageRelateAttribute` — 跨页关联标记(`weread-page-relate`
**翻译渲染错误上报:**
- `tryReportTranslationError:` — 双语(原文 + 翻译)渲染异常时上报分析
### `WREpubPositionConverter`
文件位置与字符位置的双向转换器。用于书签同步和阅读进度追踪。
**核心概念:**`(fileIndex, row, column)` 三元组映射为全局字符偏移量。
**主要 API**
- `initWithFilePaths:attributedStrings:offset:isContainIntroFlyleaf:` — 初始化
- `initIndices` — 构建内部索引表(调用前不能执行转换)
- `indicesInFile:forRowColumnPairs:stringIndices:` — 行/列 → 字符索引
- `stringRangeFromFileRange:` — 文件内范围 → 全局字符串范围
- `totalCharacterCount` — 全书总字符数
- `fileIndexForCharacterPosition:` — 全局位置 → 文件索引
- `localOffsetInFileAtIndex:forGlobalPosition:` — 全局位置 → 文件内偏移
---
## 分页与布局
### `WRChapterPageCount`
章节分页计算器。管理每页的 `NSRange`
**关键属性:**
- `pageRanges` — 每页的字符范围数组 (NSValue-wrapped NSRange)
- `totalPages` — 总页数
**关键方法:**
- `currentCacheKeyWithBookId:` — 生成缓存键(编码书 ID + 当前排版设置,设置变更则失效)
- `rangeValueWithPageInfo:` — 从服务端分页数据提取范围
- `rangeForPageAtIndex:` / `pageIndexForCharacterIndex:` — 页码 ↔ 字符位置
- `recalculatePageRangesForAttributedString:drawingSize:margins:` — 重算分页
### `WRCoreTextLayouter`
读书封装的 CoreText 排版器,创建 `CTTypesetter` / `CTFramesetter` 并产出 `WRCoreTextLayoutFrame` 页面对象。
**初始化:**
- `initWithAttributedString:` / `initWithAttributedString:config:`
- `WRCoreTextLayoutConfig` 配置frame 宽高、内边距、栏数、栏间距、孤行/寡行控制、连字符
**排版 API**
- `createTypesetter` / `createFramesetter` / `invalidateTypesetter`
- `layoutFrameWithRange:frame:` — 单帧排版
- `layoutFramesForPageSize:` — 全文分页排版
- `numberOfPagesForPageSize:` / `rangeForPageAtIndex:pageSize:` — 分页查询
- `pageBackgroundImageAtRange:themeBgColor:` — 页面背景图
- `resizedImageForImagePath:` — 图片缩放适配
### `WRCoreTextLayoutFrame`
单页排版帧,封装 `CTFrame`,管理 `WRCoreTextLayoutLine` 行数组。
**核心功能:**
- 文本绘制:`drawInContext:image:size:inRect:position:`(含 CTFrameDraw + 图片附件 + 装饰元素)
- 分栏支持:`numberOfColumns` / `columnGap`
- 跨页避让:`avoidPageBreakInsideByRemovingLastLinesIfNeeded` — CSS `avoid-page-break-inside` 实现
- 点击测试:`characterIndexAtPoint:` / `lineIndexAtPoint:` / `rectForCharacterAtIndex:`
- 选区操作:`selectedText` / `selectedRanges` / `selectTextInRange:` / `clearSelection`
- 搜索高亮:`highlightSearchResults:` / `clearSearchHighlights`
- 装饰元素:删除线范围、下划线范围、高亮范围、链接范围
- 响应式更新:通过 `RACSubject` (ReactiveObjC) 发出布局变更通知
---
## 标注系统
### `WRBookmark`
书签/标注的数据模型,支持 5 种类型:
| 类型 | 说明 |
|------|------|
| `WRBookmarkTypeHighlight` | 彩色高亮 |
| `WRBookmarkTypeUnderline` | 下划线 |
| `WRBookmarkTypeMark` | 页面书签(折角) |
| `WRBookmarkTypeNote` | 文字笔记 |
| `WRBookmarkTypePencil` | Apple Pencil 手绘 |
**关键属性:** `bookmarkId`、`bookId`、`chapterUid`、`chapterOffset`、`markText`、`startPos`/`endPos`、`colorStyle`、`anchorId`、`rangeKey`、`syncKey`
**持久化:** `toDictionary` / `fromDictionary`、`toJSONData` / `fromJSONData`
**工厂方法:**
- `bookmarkWithBookId:chapterUid:offset:text:type:`
- `highlightWithBookId:chapterUid:startPos:endPos:text:colorStyle:`
### `WRPageHighlight`
文本高亮标注模型。5 种预设颜色:黄、蓝、红、绿、紫。
**方法:**
- `initWithBookId:chapterUid:startPos:endPos:text:color:` — 创建高亮
- `toBookmark` — 转为 WRBookmark 进行持久化/同步
- `uiColor` — 返回渲染用 UIColor
### `WRPageUnderline`
下划线标注模型。支持 4 种下划线样式:实线、虚线、波浪线、点线。
**方法:**
- `toBookmark` / `fromBookmark:` — 与 WRBookmark 双向转换
- `underlinePathForRect:` — 返回渲染用 UIBezierPath
- `containsPosition:` — 范围查询
- `mergeWithUnderline:` — 合并相邻下划线
- `setNote:` — 附加笔记
### `WRPageMark`
页面书签折角模型。25 个方法,功能最丰富的标注类型。
**关键特性:**
- 支持 3 种展示模式:图标、列表、行内
- 关联标注:`linkedHighlights` / `linkedUnderlines` — 书签位置上附带的高亮和下划线
- 批量操作:`marksSortedByPosition:` / `marksSortedByDate:` / `marksInChapter:fromMarks:` / `marksInRange:fromMarks:`
- 序列化:`toDictionary` / `fromDictionary`、`toJSONData` / `fromJSONData`
### `WRReaderPencilNoteManager`
Apple Pencil 手写笔记管理器。11 个类方法,全部为类方法调用。
**支持 9 种画笔颜色/样式:** 黑、灰、红、蓝、黄、绿、铅笔、钢笔、荧光笔
**本地存储:**
- `drawingFileDirectory` / `drawingFilePathWithReviewItemId:` — 文件路径管理
- `writeDrawingDataToLocal:` / `writeDrawingToLocal:` — 本地写入(支持 draft/published 状态)
- `checkDrawingExistsWithReviewItemId:` — 存在性检查
- `deleteDrawingWithReviewItemId:` / `deleteAllReviewDrawings` — 删除
**云端同步:**
- `uploadPencilDrawing:colorStyle:onlyUploadImage:canRetry:` — 上传绘制图片和数据到腾讯云 COS
- `uploadPencilNoteData:suffix:` — 上传 PKDrawing 序列化数据
- `downloadDrawingDataFromCosWithUrl:desPath:callback:` — 从 COS 下载
---
## DTCoreText 定制模块
读书基于开源库 [DTCoreText](https://github.com/Cocoanetics/DTCoreText) 做了大量私有定制。
### `DTHTMLAttributedStringBuilder`
HTML DOM → `NSAttributedString` 的 SAX 风格解析构建器。`WREpubTypesetter` 的底层依赖。
**WeRead 自定义属性键:**
- `DTPageBackgroundColorAttribute` / `DTPageBackgroundImageAttribute` — 页面背景
- `DTPageBreakAfterAttribute` / `DTPageBreakBeforeAttribute` — 分页控制
- `DTPageBreakInsideAvoidAttribute` — 跨页避让
- `DTPageRelateAttribute` — 跨页关联
- `DTHTMLVerticalCenterAttribute` — 垂直居中
- `DTHTMLTranslateTagAttribute` — 翻译标签
### `DTHTMLElement`
HTML 元素 DOM 节点。构建解析时的 DOM 树,每个节点可通过 `attributedString` 方法生成对应的属性字符串。
**关键属性:** `tagName`、`parent`/`children`、`styleDictionary`、`isBlockElement`、`shouldAvoidPageBreakInside`
### `DTCoreTextLayouter` / `DTCoreTextLayoutFrame` / `DTCoreTextLayoutLine` / `DTCoreTextGlyphRun`
DTCoreText 的四层排版层次结构(与 `WR` 前缀版本功能对应,是底层实现):
```
DTCoreTextLayouter (排版器, CTTypesetter 包装)
└── DTCoreTextLayoutFrame (排版帧, CTFrame 包装)
└── DTCoreTextLayoutLine (排版行, CTLine 包装)
└── DTCoreTextGlyphRun (字形运行, CTRun 包装)
```
- **Layouter** — 创建 CTTypesetter产出 LayoutFrame支持帧缓存
- **LayoutFrame** — 管理排版行数组,支持多栏、分页避让、点击测试、选区、搜索
- **LayoutLine** — 封装 CTLine管理 GlyphRun 数组提供排版指标ascent/descent/leading/width和点击测试
- **GlyphRun** — 封装 CTRun管理字形glyph级别操作位置、路径绘制、附件图片、字体属性、绘制方向
---
## 数据流
```
EPUB 文件 (.epub)
↓ WREpubParser.parse
解析 container.xml → content.opf → toc.ncx
↓ WREpubTypesetter.attributeStringWithFilePath
XHTML + CSS → DTHTMLAttributedStringBuilder → NSAttributedString
↓ WRCoreTextLayouter / WRChapterPageCount
分页排版 → [NSRange per page]
↓ WRPageView.drawInContext
CoreText 绘制到 CGContext
↓ WRPageViewController
翻页展示
```