14 KiB
WXRead/decompiled 逆向代码说明
概述
此目录包含从读书 (WeRead) iOS 客户端二进制逆向还原的头文件和实现代码。共 44 个文件(22 对 .h/.m),分为三大模块:
- 阅读器核心 — 阅读控制器、页面渲染、排版引擎、EPUB 解析
- 标注系统 — 划线、高亮、书签、想法、Pencil 手写笔记
- 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/NOparseContainerXML:— 解析 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 的转换核心。全部为类方法,无需实例化。
主入口方法:
+ (NSAttributedString *)attributeStringWithFilePath:priority:
insertArticleToolAttachment:insertBookChapterToolAttachment:
insertRecommendView:book:chapter:pageFlippingStyle:
renderErrorReason:isStyleFileNotFound:options:
处理流程:
- 加载并级联合并 CSS(用户设置 > EPUB 内嵌 > 默认样式)
- 将 XHTML + 合并后的 CSS 喂入
DTHTMLAttributedStringBuilder - 遍历生成的元素树,修补图片、链接、自定义属性
- 应用用户排版偏好(字体、行距、主题)
- 可选截断用于免费试读预览
自定义 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/invalidateTypesetterlayoutFrameWithRange:frame:— 单帧排版layoutFramesForPageSize:— 全文分页排版numberOfPagesForPageSize:/rangeForPageAtIndex:pageSize:— 分页查询pageBackgroundImageAtRange:themeBgColor:— 页面背景图resizedImageForImagePath:— 图片缩放适配
WRCoreTextLayoutFrame
单页排版帧,封装 CTFrame,管理 WRCoreTextLayoutLine 行数组。
核心功能:
- 文本绘制:
drawInContext:image:size:inRect:position:(含 CTFrameDraw + 图片附件 + 装饰元素) - 分栏支持:
numberOfColumns/columnGap - 跨页避让:
avoidPageBreakInsideByRemovingLastLinesIfNeeded— CSSavoid-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:— 返回渲染用 UIBezierPathcontainsPosition:— 范围查询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:— 上传绘制图片和数据到腾讯云 COSuploadPencilNoteData:suffix:— 上传 PKDrawing 序列化数据downloadDrawingDataFromCosWithUrl:desPath:callback:— 从 COS 下载
DTCoreText 定制模块
读书基于开源库 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
翻页展示