# 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 翻页展示 ```