561 lines
23 KiB
Markdown
561 lines
23 KiB
Markdown
# 读书 EPUB 阅读器实现架构分析
|
||
|
||
> 分析日期: 2026-05-18
|
||
> 基于读书 v10.0.3
|
||
|
||
---
|
||
|
||
## 核心结论:双渲染引擎架构
|
||
|
||
读书使用了 **两套渲染引擎**,根据内容类型选择不同的渲染路径:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────┐
|
||
│ WRReaderViewController │
|
||
│ (阅读器主控制器, 管理翻页和状态) │
|
||
├─────────────────────────────────────────────────┤
|
||
│ WRPageViewController │
|
||
│ (基于 UIPageViewController) │
|
||
│ 支持 UIPageCurl(仿真翻页) + Scroll(滑动) │
|
||
├──────────────────────┬──────────────────────────┤
|
||
│ 路径A: 原生渲染 │ 路径B: WebView 渲染 │
|
||
│ (EPUB/书籍正文) │ (公众号/文集文章) │
|
||
│ │ │
|
||
│ WREpubTypesetter │ WKWebView + JS Bridge │
|
||
│ DTCoreText │ weread-highlighter.js │
|
||
│ CoreText 排版 │ rangy-*.js │
|
||
│ NSAttributedString │ MediaPlatform.js/css │
|
||
│ WRCoreTextLayouter │ Readability.js │
|
||
│ WRPageView (draw) │ WRMPPageView │
|
||
└──────────────────────┴──────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 路径A:EPUB 正文原生渲染 (核心路径)
|
||
|
||
这是 EPUB 阅读的**主要渲染方式**,完全用原生 CoreText 实现,不走 WebView。
|
||
|
||
### 1. EPUB 下载与解密流程
|
||
|
||
```
|
||
服务器 ZIP 包 (加密)
|
||
│
|
||
▼
|
||
WRBookNetwork.loadTarForEpubBookId:chapter:isPreload:
|
||
│
|
||
▼
|
||
WRBookNetwork.handleUnzipWithBookId:zipPath:encryptKey:plainBookDirectory:
|
||
│ - 使用 encryptKey 解密
|
||
│ - 解压到 plainBookDirectory
|
||
│ - 处理解压错误 handleUnzipErrorWithPath:
|
||
▼
|
||
WRBookNetwork.processEncryptedBookFileAtPath:encryptKey:book:chapterUid:isFromReview:
|
||
│
|
||
▼
|
||
WREncryptedFileManager.decryptContentsOfFile:forBookId:isFileLost:
|
||
│ - keyForBookId: 获取每本书的密钥
|
||
│ - 解密 EPUB 章节文件
|
||
▼
|
||
WREncryptedFileManager.encryptFileForBookId:originalEncryptKey:atPath:toPath:
|
||
│ - 本地二次加密存储 (DRM 保护)
|
||
▼
|
||
本地缓存: epubImage 目录 + 解密后的 XHTML 文件
|
||
```
|
||
|
||
**密钥管理**:
|
||
- `WRPreloadBookManager.saveEncryptKey:forPath:bookId:` - 预加载章节密钥
|
||
- `WRPreloadBookManager.encryptKeyForPath:bookId:` - 读取密钥
|
||
- `WREncryptedFileManager.keyForBookId:` - 每本书独立密钥
|
||
|
||
### 2. EPUB 解析与排版流程
|
||
|
||
```
|
||
XHTML 章节文件
|
||
│
|
||
▼
|
||
WRBookNetwork.fileContentWithChapter:book:shouldRemoveHtmlTags:filterTranslateContent:
|
||
│ - 读取 XHTML 内容
|
||
│ - 可选去除 HTML 标签
|
||
│ - 过滤翻译内容
|
||
▼
|
||
WREpubTypesetter.attributeStringWithFilePath:priority:insertArticleToolAttachment:
|
||
insertBookChapterToolAttachment:insertRecommendView:book:chapter:
|
||
pageFlippingStyle:renderErrorReason:isStyleFileNotFound:options:
|
||
│
|
||
│ ┌─────────────────────────────────────────┐
|
||
│ │ DTHTMLAttributedStringBuilder │
|
||
│ │ (HTML -> NSAttributedString 转换器) │
|
||
│ │ │
|
||
│ │ 1. 解析 XHTML DOM 树 │
|
||
│ │ 2. 读取 EPUB 内嵌 CSS (replace.css) │
|
||
│ │ 3. 合并默认样式 (default.css) │
|
||
│ │ 4. 应用用户主题样式 (dark.css) │
|
||
│ │ 5. 转换为 NSAttributedString │
|
||
│ │ - 保留字体、颜色、行高、对齐等属性 │
|
||
│ │ - 处理图片 (NSTextAttachment) │
|
||
│ │ - 处理超链接 │
|
||
│ └─────────────────────────────────────────┘
|
||
▼
|
||
NSAttributedString (富文本)
|
||
│
|
||
▼
|
||
WRCoreTextLayouter (CoreText 排版引擎)
|
||
│
|
||
│ ┌─────────────────────────────────────────┐
|
||
│ │ DTCoreText 框架 (自定义修改版) │
|
||
│ │ │
|
||
│ │ DTCoreTextLayouter │
|
||
│ │ └─ CTTypesetter │
|
||
│ │ └─ CTFramesetter │
|
||
│ │ └─ DTCoreTextLayoutFrame │
|
||
│ │ └─ CTFrame (每页) │
|
||
│ │ └─ DTCoreTextLayoutLine │
|
||
│ │ └─ CTLine (每行) │
|
||
│ │ └─ DTCoreTextGlyphRun │
|
||
│ │ └─ CTLine (字形) │
|
||
│ │ │
|
||
│ │ 特殊处理: │
|
||
│ │ - wr-vertical-center-style (图片居中) │
|
||
│ │ - weread-page-relate (分页控制) │
|
||
│ │ - avoidPageBreakInside (避免断页) │
|
||
│ │ - 繁简转换 (convertHansToHant) │
|
||
│ └─────────────────────────────────────────┘
|
||
▼
|
||
WRChapterData (章节数据模型)
|
||
│
|
||
│ - 包含排版后的 NSAttributedString
|
||
│ - 管理划线/高亮/书评等标注
|
||
│ - addHighlightInRange:key:itemId:color:
|
||
│ - addUnderLineToAttributedString:range:itemId:style:color:
|
||
│ - addReviewUnderlineInRange:itemId:type:
|
||
│ - generateOutlineContents (生成目录)
|
||
│ - freeTrialChapterCutOffStringLocaion (免费试读截断)
|
||
▼
|
||
分页计算: WRChapterPageCount
|
||
│
|
||
│ - rangeValueWithPageInfo: 计算每页的 NSRange
|
||
│ - rangeOfPage: 获取指定页的文本范围
|
||
▼
|
||
WRPageView (页面视图, UIView 子类)
|
||
│
|
||
│ - 继承 UIView
|
||
│ - drawRect: 中调用 CoreText 绘制
|
||
│ - WRCoreTextLayoutFrame.drawInContext:image:size:inRect:position:
|
||
│ - 直接用 CGContext 绘制文字和图片
|
||
│ - 不使用 UILabel/UITextView
|
||
▼
|
||
屏幕显示 (像素级精确渲染)
|
||
```
|
||
|
||
### 3. 翻页机制
|
||
|
||
```
|
||
WRPageViewController
|
||
│
|
||
│ 基于 UIPageViewController 封装
|
||
│
|
||
│ 初始化: initWithDelegate:withPageType:pageFlippingStyle:
|
||
│
|
||
│ pageFlippingStyle 支持:
|
||
│ ┌────────────────────────────────────┐
|
||
│ │ UIPageCurl - 仿真翻页 (纸张卷曲) │
|
||
│ │ Scroll - 左右滑动翻页 │
|
||
│ └────────────────────────────────────┘
|
||
│
|
||
│ 核心方法:
|
||
│ - pageViewController:viewControllerBeforeViewController: (上一页)
|
||
│ - pageViewController:viewControllerAfterViewController: (下一页)
|
||
│ - pageViewController:spineLocationForInterfaceOrientation: (书脊位置)
|
||
│ - weread_setViewControllers:withCurlOfType:fromLocation:direction:
|
||
│ animated:notifyDelegate:completion: (自定义设置方法)
|
||
│
|
||
│ 故障修复:
|
||
│ - patchNavigationDirectionFault (导航方向修复)
|
||
│ - patchNoViewControllerManagingPageViewFault (页面管理修复)
|
||
│ - patchUIPageCurlFault (翻页动画修复)
|
||
│ - detectNavigationDirectionCrashWithPageViewController: (崩溃检测)
|
||
│
|
||
▼
|
||
WRPageView (每个页面的渲染视图)
|
||
│
|
||
│ - 通过 WRChapterData 获取排版结果
|
||
│ - 通过 rangeOfPage: 获取当前页的文本范围
|
||
│ - 使用 CoreText 直接绘制到 CGContext
|
||
```
|
||
|
||
### 4. 文本选择与标注
|
||
|
||
```
|
||
用户触摸/长按
|
||
│
|
||
▼
|
||
WRPageView 手势识别
|
||
│
|
||
▼
|
||
文本位置计算 (CoreText hit test)
|
||
│ - CTLineGetStringIndexForPosition (坐标->字符索引)
|
||
│ - CTLineGetOffsetForStringIndex (字符索引->坐标)
|
||
▼
|
||
选择范围确定
|
||
│
|
||
▼
|
||
弹出操作菜单 (UIMenuController)
|
||
│ - 划线/高亮
|
||
│ - 写想法/书评
|
||
│ - 复制
|
||
│ - 查询/翻译
|
||
│ - 分享
|
||
▼
|
||
WRChapterData 添加标注
|
||
│ - addHighlightInRange:key:itemId:color:
|
||
│ - addUnderLineToAttributedString:range:itemId:style:color:
|
||
│ - addReviewUnderlineInRange:itemId:type:
|
||
▼
|
||
保存到服务器
|
||
│ - WRBookNetwork.addReview:shareToWechat:...
|
||
│ - 同步书签: loadBookmarkListWithBookId:syncKey:callback:
|
||
▼
|
||
重新排版当前页 (recomposeCurrentPageViewWithSource:)
|
||
```
|
||
|
||
---
|
||
|
||
## 路径B:WebView 渲染 (公众号/文集文章)
|
||
|
||
用于渲染**微信公众号文章、文集、书评**等富媒体内容。
|
||
|
||
```
|
||
HTML 内容 (来自服务器)
|
||
│
|
||
▼
|
||
WRMPReadingManager.composeMPReviewHTMLString:withReview:
|
||
│ - 组装 HTML 模板
|
||
│ - 注入 CSS (MediaPlatform.css, MPExtra.css)
|
||
│ - 注入 JS (MediaPlatform.js, mpForArticle.js)
|
||
▼
|
||
WKWebView 加载
|
||
│
|
||
│ 注入脚本:
|
||
│ <script src="WeReadApi.js"></script>
|
||
│ <script src="rich_display.js"></script>
|
||
▼
|
||
weread-highlighter.js 初始化
|
||
│
|
||
│ 1. rangy.init() - 初始化 Rangy 选择库
|
||
│ 2. 创建 Highlighter (TextRange 模式)
|
||
│ 3. 注册 ClassApplier:
|
||
│ - "highlight" (高亮)
|
||
│ - "review" / "friend-review" (书评)
|
||
│ - "reference" (引用)
|
||
│ - "tts" (语音朗读标记)
|
||
│ 4. 监听 selectionchange 事件
|
||
│ 5. 通过 wereadBridge.execMPReaderMethod 通知原生
|
||
▼
|
||
JS Bridge 双向通信
|
||
│
|
||
│ 原生 -> JS:
|
||
│ - evaluateJavaScript: 调用 JS 方法
|
||
│ - WKUserScript 注入脚本
|
||
│
|
||
│ JS -> 原生:
|
||
│ - window.webkit.messageHandlers.XXX.postMessage()
|
||
│ - wereadBridge.execMPReaderMethod('MPReader', data)
|
||
▼
|
||
WRMPReadingViewModel (ViewModel 层)
|
||
│
|
||
│ - addHighlightWithStart:withEnd:withContent:callback:
|
||
│ - addReviewWithRange:content:reference:secretMode:withCallback:
|
||
│ - genJSInfosWithHighlights:refrencedHighlight:
|
||
│ - genJSInfosWithReviews:refrencedReview:
|
||
│ - readReviewsWithLoadCount:maxObj:
|
||
│ - setupTTSAudioList
|
||
▼
|
||
WRMPPageView (WebView 包装视图)
|
||
```
|
||
|
||
---
|
||
|
||
## 关键源码路径 (从二进制中提取)
|
||
|
||
```
|
||
WeRead/Src/Modules/EpubParser/
|
||
├── WREpubTypesetter.m # EPUB 排版器
|
||
├── WREpubPositionConverter.m # 位置转换器
|
||
└── Utils/DTCoreTextFunctions.m # CoreText 工具函数
|
||
|
||
WeRead/Src/Modules/TypeSetter/
|
||
├── WRCoreTextLayouter.m # CoreText 排版器
|
||
├── WRCoreTextLayoutFrame.m # 排版帧 (管理页面)
|
||
└── DTCoreTextGlyphRun.m # 字形渲染
|
||
|
||
WeRead/Src/Modules/Reading/
|
||
├── Controller/
|
||
│ ├── WRReaderViewController.m # 阅读器主控制器
|
||
│ └── WRPageViewController.m # 翻页控制器
|
||
├── Model/
|
||
│ ├── WRChapterData.m # 章节数据模型
|
||
│ ├── WRChapterDownloadManger.m # 章节下载管理
|
||
│ ├── WRReaderViewModel.m # 阅读器 ViewModel
|
||
│ └── WRMPReadingManager.m # 公众号阅读管理
|
||
└── View/
|
||
└── WRPageView.m # 页面渲染视图
|
||
|
||
WeRead/Src/Modules/MediaPlatform/
|
||
├── Controller/
|
||
│ ├── WRMPListViewController.m
|
||
│ └── WRMPSubscribeViewController.m
|
||
└── Model/
|
||
├── WRMPCoverManager.m
|
||
├── WRMPCoverPainter.m
|
||
├── WRMPStore.m
|
||
└── WRMPViewModel.m
|
||
```
|
||
|
||
---
|
||
|
||
## EPUB CSS 样式系统
|
||
|
||
```
|
||
加载优先级 (从低到高):
|
||
1. default.css - 基础 HTML 标签样式 (Safari 默认)
|
||
2. replace.css - 读书默认替换样式
|
||
├── 标题样式 (h1-h6, 使用 Source Han Serif CN 字体)
|
||
├── 代码块 (pre, 使用 Menlo 字体)
|
||
├── 图片 (.bodyPic, wr-vertical-center-style: 2)
|
||
├── 引用 (.conQuot)
|
||
├── 翻译 (.wr-translation)
|
||
├── 章节工具 (.book-chapter-tool, .chapter-tool)
|
||
└── 分页控制 (.weread-page-relate)
|
||
3. dark.css - 暗黑主题样式
|
||
4. EPUB 内嵌 CSS - 书籍自带样式
|
||
5. 用户设置 - 字号、行高、主题覆盖
|
||
```
|
||
|
||
**自定义 CSS 属性** (读书私有):
|
||
- `wr-vertical-center-style: 1|2` - 图片垂直居中方式
|
||
- `weread-page-relate: true` - 控制分页时的内容关联
|
||
|
||
---
|
||
|
||
## 核心类职责表
|
||
|
||
| 类名 | 职责 | 渲染路径 |
|
||
|---|---|---|
|
||
| `WRReaderViewController` | 阅读器主控制器,管理阅读状态、进度保存、章节跳转 | 共用 |
|
||
| `WRPageViewController` | 翻页控制器,基于 UIPageViewController 封装 | 共用 |
|
||
| `WRPageView` | 页面渲染视图,CoreText 直接绘制 | 路径A |
|
||
| `WRMPPageView` | 公众号页面视图,WKWebView 包装 | 路径B |
|
||
| `WREpubTypesetter` | EPUB 排版器,HTML->NSAttributedString | 路径A |
|
||
| `WRCoreTextLayouter` | CoreText 排版引擎,管理 CTFrame/CTLine | 路径A |
|
||
| `WRCoreTextLayoutFrame` | 排版帧,管理单页的绘制 | 路径A |
|
||
| `WRChapterData` | 章节数据模型,存储排版结果和标注 | 路径A |
|
||
| `WRChapterPageCount` | 分页计算,管理每页的 NSRange | 路径A |
|
||
| `WREpubPositionConverter` | EPUB 位置转换器 (文件位置<->字符位置) | 路径A |
|
||
| `WRChapterDownloadManger` | 章节下载管理器 | 共用 |
|
||
| `WREncryptedFileManager` | 加密文件管理 (DRM) | 共用 |
|
||
| `WRBookNetwork` | 书籍网络请求 (下载/解密/解压) | 共用 |
|
||
| `WRMPReadingManager` | 公众号阅读管理器 | 路径B |
|
||
| `WRMPReadingViewModel` | 公众号阅读 ViewModel (JS Bridge 交互) | 路径B |
|
||
| `WRReaderViewModel` | 阅读器 ViewModel | 共用 |
|
||
| `DTCoreTextLayouter` | DTCoreText 排版器 (第三方库修改版) | 路径A |
|
||
| `DTHTMLAttributedStringBuilder` | HTML->NSAttributedString 构建器 | 路径A |
|
||
| `WRReaderPencilNoteManager` | Apple Pencil 手写笔记管理 | 共用 |
|
||
| `WRReaderTranslationManager` | 翻译管理 (繁简转换/中英翻译) | 共用 |
|
||
| `WRReaderCht2sManager` | 繁体转简体管理 | 共用 |
|
||
|
||
---
|
||
|
||
## JavaScript 文件职责
|
||
|
||
| 文件 | 职责 |
|
||
|---|---|
|
||
| `weread-highlighter.js` | 核心高亮引擎,初始化 Rangy,管理选择和高亮 |
|
||
| `rangy-core.js` | Rangy 核心库,跨浏览器 Range/Selection 封装 |
|
||
| `rangy-highlighter.js` | Rangy 高亮模块,管理高亮的创建/删除/序列化 |
|
||
| `rangy-classapplier.js` | Rangy ClassApplier 模块,CSS 类应用器 |
|
||
| `rangy-textrange.js` | Rangy TextRange 模块,文本范围操作 |
|
||
| `Readability.js` | Arc90 Readability 库,提取文章正文 |
|
||
| `MediaPlatform.js` | 公众号平台 JS,原生-JS 桥接 |
|
||
| `mpForArticle.js` | 文章相关 JS 逻辑 |
|
||
| `MPExtra.css` | 公众号额外样式 |
|
||
| `MediaPlatform.css` | 公众号基础样式 |
|
||
| `WeReadApi.js` | 读书 JS API (供 WebView 调用原生功能) |
|
||
| `rich_display.js` | 富文本显示逻辑 |
|
||
| `cssInjector.js` | CSS 注入器 |
|
||
| `highlight.min.js` | 代码语法高亮 (highlight.js) |
|
||
|
||
---
|
||
|
||
## DRM 与安全机制
|
||
|
||
```
|
||
┌─────────────────────────────────────────────┐
|
||
│ DRM 保护链 │
|
||
├─────────────────────────────────────────────┤
|
||
│ │
|
||
│ 1. 传输层: HTTPS + 加密 ZIP │
|
||
│ - 服务器下发加密的 .zip 文件 │
|
||
│ - 文件名格式: {bookId}_DECRYPT.zip │
|
||
│ │
|
||
│ 2. 解密层: 逐章解密 │
|
||
│ - WREncryptedFileManager │
|
||
│ - keyForBookId: (每本书独立密钥) │
|
||
│ - decryptContentsOfFile:forBookId: │
|
||
│ │
|
||
│ 3. 存储层: 本地二次加密 │
|
||
│ - encryptFileForBookId:atPath:toPath: │
|
||
│ - 解密后立即重新加密存储 │
|
||
│ - 防止直接拷贝文件读取 │
|
||
│ │
|
||
│ 4. 密钥管理: │
|
||
│ - WRPreloadBookManager 管理预加载密钥 │
|
||
│ - 密钥与设备绑定 │
|
||
│ - 通过 Keychain 安全存储 │
|
||
│ │
|
||
│ 5. 免费试读控制: │
|
||
│ - freeTrialChapterCutOffStringLocaion │
|
||
│ - 服务端控制试读范围 │
|
||
│ - 客户端截断显示 │
|
||
│ │
|
||
│ 6. 章节付费: │
|
||
│ - isChapterAvailableForBookId:chapterUid │
|
||
│ - getCouponBuyChapterWithBookId: │
|
||
│ - resetChapterPaidIfNeeded │
|
||
│ │
|
||
└─────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 字体系统
|
||
|
||
```
|
||
内嵌字体:
|
||
├── SourceHanSerifCN-Medium.ttf # 思源宋体 (正文默认)
|
||
├── FZJuZhenXinFang.ttf # 方正聚珍新仿
|
||
├── Lora-Regular.ttf / Italic.ttf # Lora 衬线体
|
||
├── PlayfairDisplay-Regular.ttf # Playfair Display
|
||
├── WeReadLS-Regular/Medium/Bold # 读书 LS 系列
|
||
├── WeReadRN-Regular.ttf # RN 专用字体
|
||
├── WeRead-Icon.ttf # 图标字体
|
||
├── WeRead-Rating-Icon.ttf # 评分图标
|
||
├── OpenDyslexic-Regular.otf # 阅读障碍友好字体
|
||
├── WeChatNumber.ttf # 微信数字字体
|
||
├── SharpGroteskTRIAL*.ttf # Sharp Grotesk 系列
|
||
└── icon_font.ttf # 通用图标字体
|
||
|
||
动态字体:
|
||
├── CDN 下载: cdn.weread.qq.com/app/assets/font/
|
||
│ ├── FZLTHProGBK_B/DB/SB.zip # 方正兰亭黑系列
|
||
│ ├── FZQingKBYSJF-M.zip # 方正清刻本悦宋
|
||
│ ├── FZSKBXKK.zip # 方正书宋
|
||
│ ├── FZYBKSK.zip # 方正中楷
|
||
│ └── SourceHanSansCN-Heavy.zip # 思源黑体
|
||
│
|
||
└── WRFontsManager 管理:
|
||
- unZipFileAndRegisterFont:completionHandler:filePath:lateOverWrite:
|
||
- 动态下载 + 解压 + 注册
|
||
```
|
||
|
||
---
|
||
|
||
## 预加载与缓存策略
|
||
|
||
```
|
||
预加载策略:
|
||
├── WRForecastUtils.shouldPreloadChapterUidsForBook:type:bookRank:archiveRank:
|
||
│ - 根据书籍排名和用户行为预测需要预加载的章节
|
||
│
|
||
├── WRChapterDownloadManger._preloadChapterContentWithBook:type:bookRank:
|
||
│ - 后台预下载相邻章节
|
||
│
|
||
├── WRPreloadBookManager
|
||
│ - saveEncryptKey:forPath:bookId: (缓存密钥)
|
||
│ - saveFileNameDict:bookId: (缓存文件名映射)
|
||
│ - clearKV (清理缓存)
|
||
│
|
||
└── SDWebImage 缓存:
|
||
- epubImage 目录缓存书籍图片
|
||
- com.hackemist.SDWebImageCache.epubImage
|
||
|
||
缓存目录结构:
|
||
├── Documents/
|
||
│ └── {bookId}/
|
||
│ ├── plainBookDirectory/ (解密后的 EPUB 文件)
|
||
│ ├── epubImage/ (书籍图片缓存)
|
||
│ └── _DECRYPT.zip (下载的加密 ZIP)
|
||
└── Library/
|
||
└── {cachePath}/
|
||
├── epubImage/ (SDWebImage 缓存)
|
||
└── com.hackemist.SDWebImageCache.epubImage
|
||
```
|
||
|
||
---
|
||
|
||
## 朗读 (TTS) 集成
|
||
|
||
```
|
||
朗读流程:
|
||
├── WRReadAloudAudio.parseCGIInfos: (解析音频信息)
|
||
├── TTS 引擎: wxtts (微信语音合成)
|
||
│ ├── 离线资源: wxtts_offline_resource5.zip
|
||
│ ├── 在线资源: getWxttsSeginfo / getWxttsVoice
|
||
│ └── VITS/VALL-E 模型 (高保真语音)
|
||
│
|
||
├── 文本分段:
|
||
│ - weread-highlighter.js 中的 "tts" ClassApplier
|
||
│ - 标记当前朗读位置
|
||
│
|
||
└── 进度同步:
|
||
- lastListenedChapterOffset / lastListenedChapterUid
|
||
- MPReading/lastListenedReviewId (公众号朗读进度)
|
||
```
|
||
|
||
---
|
||
|
||
## Apple Pencil 手写笔记
|
||
|
||
```
|
||
WRReaderPencilNoteManager:
|
||
├── 数据存储:
|
||
│ - writeDrawingDataToLocal:reviewItemId:reviewId:isDraft:
|
||
│ - drawingFilePathWithReviewItemId:reviewId:isDraft:
|
||
│ - 本地草稿 + 云端同步
|
||
│
|
||
├── 数据上传:
|
||
│ - uploadPencilDrawing:colorStyle:onlyUploadImage:canRetry:
|
||
│ - uploadPencilNoteData:suffix:
|
||
│ - 使用腾讯云 COS 存储
|
||
│ - authCosForPencilDataWithSuffix: (COS 认证)
|
||
│
|
||
├── 数据下载:
|
||
│ - downloadDrawingDataFromCosWithUrl:desPath:callback:
|
||
│ - downloadDrawingWithReviewItemId:reviewId:drawingUrl:dataBlock:
|
||
│
|
||
└── 图片导出:
|
||
- imageFilePathWithReviewItemId:
|
||
- 手绘笔记可导出为图片
|
||
```
|
||
|
||
---
|
||
|
||
## 总结
|
||
|
||
| 特性 | 实现方式 |
|
||
|---|---|
|
||
| **EPUB 解析** | 自研 EPUB Parser,解析 OPF/NCX/XHTML |
|
||
| **文字排版** | DTCoreText (CoreText 封装) + 自定义 WRCoreTextLayouter |
|
||
| **页面渲染** | CGContext 直接绘制,不用 UILabel/UITextView |
|
||
| **翻页效果** | UIPageViewController (UIPageCurl + Scroll) |
|
||
| **文本选择** | CoreText hit test + Rangy.js (WebView 场景) |
|
||
| **标注系统** | NSAttributedString 属性注入 + 服务器同步 |
|
||
| **图片处理** | NSTextAttachment + CDN 尺寸优化 + 白底透明化 |
|
||
| **DRM** | 逐章加密 + 逐书密钥 + 本地二次加密 |
|
||
| **公众号文章** | WKWebView + JS Bridge + Rangy 高亮 |
|
||
| **繁简转换** | CoreText 层面的 convertHansToHant |
|
||
| **字体** | 内嵌 Source Han Serif CN + 动态下载字体 |
|
||
| **预加载** | 后台预下载相邻章节 ZIP + 解密缓存 |
|
||
| **TTS 朗读** | wxtts 引擎 + VITS/VALL-E 高保真模型 |
|
||
| **手写笔记** | PencilKit + 腾讯云 COS 存储 |
|