Epub阅读器0.0.1

This commit is contained in:
shen
2026-05-21 19:40:51 +08:00
commit daa36d8fe7
559 changed files with 106266 additions and 0 deletions
@@ -0,0 +1,369 @@
# DTCoreText 自定义修改分析
微信读书 (WeRead) 基于开源 DTCoreText 库进行了深度定制,将其从一个通用的 HTML-to-NSAttributedString 转换库改造为一套完整的电子书分页渲染引擎。本文档详细记录所有自定义修改。
---
## 1. 架构概览
开源 DTCoreText 提供三个核心能力:
- HTML/CSS 解析 (DTHTMLAttributedStringBuilder)
- DOM 元素模型 (DTHTMLElement)
- CoreText 排版封装 (DTCoreTextLayouter / LayoutFrame / LayoutLine / GlyphRun)
WeRead 在此基础上增加了:
- 一套完整的分页渲染引擎 (WRCoreTextLayouter / WRCoreTextLayoutFrame)
- 自定义 CSS 属性体系(控制分页、背景、垂直居中等)
- 自定义 NSAttributedString 属性键(传递页面布局元数据)
- 翻译/双语支持
- 免费试读截断机制
- 主题/字体动态切换
---
## 2. 自定义 CSS 属性
### 2.1 wr-vertical-center-style
**用途**: 控制内联元素(主要是图片)在行内的垂直居中方式。
**取值**:
- `1` — 基线对齐(默认)
- `2` — 垂直居中(用于 .bodyPic 类图片)
**处理流程**:
1. DTHTMLAttributedStringBuilder 解析 HTML 时读取该属性
2. 存储到 DTHTMLElement.verticalCenterStyle
3. 转换为 NSAttributedString 属性 `DTHTMLVerticalCenterAttribute`
4. WRCoreTextLayoutLine 排版时根据该属性调整 baseline offset
**关联 CSS 规则** (replace.css):
```css
img.bodyPic {
wr-vertical-center-style: 2;
max-width: 100%;
}
```
### 2.2 weread-page-relate
**用途**: 标记元素与页面级布局相关。
**取值**: `true` / `false`
**处理流程**:
1. 解析时存储到 DTHTMLElement.pageRelate
2. 转换为 `DTPageRelateAttribute`
3. 分页引擎使用该属性决定元素是否参与页面级定位
### 2.3 avoidPageBreakInside
**用途**: 防止元素内部被分页截断(类似 CSS `break-inside: avoid`)。
**适用场景**: 代码块、表格、列表、引用块。
**处理流程**:
1. 解析时设置 DTHTMLElement.shouldAvoidPageBreakInside = YES
2. 转换为 `DTPageBreakInsideAvoidAttribute`
3. WRCoreTextLayoutFrame.avoidPageBreakInsideByRemovingLastLinesIfNeeded 检查该属性
4. 如果元素会被截断,移除末尾几行(最多 3 行)以避免断页
**关联类**: `WRBlockType` 属性标识块类型(table / code / list / blockquote)
### 2.4 DTPageBreakAfter / DTPageBreakBefore
**用途**: 强制在元素前/后插入分页符。
**处理流程**:
1. 解析时设置 DTHTMLElement.pageBreakAfter / pageBreakBefore
2. 转换为 `DTPageBreakAfterAttribute` / `DTPageBreakBeforeAttribute`
3. attributedString 生成时插入 LINE SEPARATOR (U+2028) 作为分页标记
---
## 3. 自定义 NSAttributedString 属性键
WeRead 在 DTCoreText 标准属性之外定义了 12 个自定义属性键:
| 属性键 | 类型 | 用途 |
|--------|------|------|
| `DTPageBackgroundColorAttribute` | UIColor | 页面背景色 |
| `DTPageBackgroundImageAttribute` | NSString | 页面背景图片路径 |
| `DTPageBackgroundImagePathAttribute` | NSString | 背景图片绝对路径 |
| `DTPageBreakAfterAttribute` | NSNumber(BOOL) | 元素后强制分页 |
| `DTPageBreakBeforeAttribute` | NSNumber(BOOL) | 元素前强制分页 |
| `DTPageBreakInsideAvoidAttribute` | NSNumber(BOOL) | 避免元素内分页 |
| `DTPageRelateAttribute` | NSString | 页面关联标记 |
| `DTPageSize` | NSValue(CGSize) | 页面尺寸 |
| `DTPageFlippingStyle` | NSNumber(NSInteger) | 翻页动画样式 |
| `DTHTMLVerticalCenterAttribute` | NSString | 垂直居中样式 |
| `DTHTMLTranslateTagAttribute` | NSString | 翻译标签标记 |
| `DTHTMLTranslateNoStyleAttribute` | NSString | 无翻译样式标记 |
这些属性在 DTHTMLElement.attributedString 生成阶段写入,在 WRCoreTextLayoutFrame 排版和渲染阶段被读取。
---
## 4. DTHTMLElement 修改
### 4.1 新增属性
原版 DTHTMLElement 的属性主要关注字体、颜色、段落样式。WeRead 新增:
```objc
// 分页控制
@property BOOL shouldAvoidPageBreakInside;
@property BOOL pageBreakAfter;
@property BOOL pageBreakBefore;
// 页面布局
@property NSString *verticalCenterStyle; // wr-vertical-center-style
@property NSString *pageRelate; // weread-page-relate
@property UIColor *pageBackgroundColor; // DTPageBackgroundColor
@property NSString *pageBackgroundImage; // DTPageBackgroundImage
```
### 4.2 applyStyleDictionary 扩展
原版处理标准 CSS 属性(font-family, color, margin 等)。WeRead 新增 `_applyWeReadProperties:` 方法处理自定义 CSS 属性。
### 4.3 attributedString 生成扩展
原版递归生成 NSAttributedString。WeRead 在生成流程中增加了:
- 分页标记插入(pageBreakBefore / pageBreakAfter)
- WeRead 页面属性应用(_applyWeReadPageAttributesToString:)
---
## 5. DTHTMLAttributedStringBuilder 修改
### 5.1 新增 Book/Chapter 上下文
```objc
@property WRBook *book;
@property WRChapter *chapter;
```
原版 builder 无业务上下文。WeRead 注入书籍和章节信息,用于:
- 判断是否拉丁语书籍(影响字体回退)
- 获取 EPUB 嵌入 CSS
- 解析相对资源路径
### 5.2 自定义属性处理
新增 `_applyWeReadCustomAttributes:fromAttributes:` 方法,在 SAX 解析阶段识别并存储自定义 CSS 属性。
### 5.3 DTHTMLParserDelegate 扩展
内部 delegate 新增:
- `_book` / `_chapter` 上下文引用
- `_currentImageView` / `_imageContainerView` 等图片视图引用
- `_rootView` / `_parentView` 视图层级追踪
---
## 6. DTCoreTextLayouter 修改
### 6.1 布局帧缓存
新增 `NSCache *layoutFrameCache`(最多 20 个帧),避免重复排版。
### 6.2 分页辅助方法
新增:
- `stringIndexFittingLengthForWidth:startIndex:` — 模拟分页计算适配长度
- `suggestLineBreakStartIndex:width:` — 行断点建议
### 6.3 线程安全
新增 `NSLock *_lock` 保护 typesetter 和 layout frames 的并发访问。
---
## 7. DTCoreTextLayoutFrame 修改
### 7.1 完整的绘图管线
原版提供基本的 drawInContext:。WeRead 扩展为:
- `drawInContext:options:` — 支持 drawImages / drawLinks / drawSelection 选项
- `drawImagesInContext:options:` — 绘制图片附件
- `drawLinksInContext:options:` — 绘制链接下划线
- `drawSelectionInContext:options:` — 绘制选区高亮
### 7.2 文本几何查询
新增丰富的几何查询方法:
- `rectsForRange:` — 字符串范围对应的矩形数组
- `frameForStringRange:` — 范围的包围矩形
- `stringRangeForRect:` — 矩形内的字符串范围
- `baselineOriginForStringIndex:` — 基线原点
- `cursorRectForIndex:` — 光标位置
### 7.3 截断检测
新增 `isTruncated` / `truncatedStringRange` 用于检测内容是否超出可见区域。
---
## 8. DTCoreTextLayoutLine / GlyphRun 修改
### 8.1 LayoutLine
- 新增 `lineIndex` 属性标识行序号
- 新增 `isLastLineInParagraph` / `isLineBreak` 标记
- 新增 `paragraphStyle` / `metrics` 扩展属性
- 完整的 hit testing 支持
### 8.2 GlyphRun
- 新增 `glyphImages` 数组支持自定义字形渲染
- 新增 `attachments` 数组支持附件
- 新增 `isAttachment` / `isWhitespace` / `isNewline` 标记
- 新增 `writingDirection` / `isRTL` 支持从右到左文字
- 完整的 CGPath 生成(newPathWithGlyphs / newPathForGlyphAtIndex:)
- 字形级别的 hit testing
---
## 9. WRCoreTextLayouter(WeRead 新增类)
这是 WeRead 自己的排版器,包装 DTCoreTextLayouter 并增加业务逻辑。
### 9.1 核心功能
- **分页**: `layoutFramesForPageSize:` 将整章内容分割为页面
- **页数计算**: `numberOfPagesForPageSize:` 快速计算总页数
- **范围查询**: `rangeForPageAtIndex:pageSize:` 获取指定页的文本范围
- **页面背景**: `pageBackgroundImageAtRange:themeBgColor:` 生成页面背景图
- **图片缩放**: `resizedImageForImagePath:` 支持 full/half/third/quarter 尺寸模式
- **暗色模式**: `darkModeAdjusted` 图片混合处理
### 9.2 布局配置
```objc
@interface WRCoreTextLayoutConfig : NSObject
@property CGFloat frameWidth;
@property CGFloat frameHeight;
@property UIEdgeInsets edgeInsets;
@property NSUInteger numberOfColumns;
@property CGFloat columnGap;
@property BOOL avoidOrphans; // 避免孤行
@property BOOL avoidWidows; // 避免寡行
@property BOOL hyphenation; // 连字符断字
@end
```
### 9.3 与 DTCoreTextLayouter 的关系
WRCoreTextLayouter 内部持有 DTCoreTextLayouter 的 CTTypesetter,但使用自己的 WRCoreTextLayoutFrame 进行页面级布局。
---
## 10. WRCoreTextLayoutFrame(WeRead 新增类)
这是 WeRead 自己的布局帧,包装 CTFrame 并增加页面级功能。
### 10.1 核心功能
- **avoidPageBreakInside**: 实现 CSS break-inside:avoid 语义
- **渲染高度计算**: `getRenderHeight` 返回实际内容高度
- **附件绘制**: `drawAttachmentsInContext:` 绘制图片
- **装饰元素**: 删除线、下划线、高亮背景
- **文本选择**: 完整的选择/搜索/高亮支持
- **RAC 响应式**: 通过 RACSubject 发送布局变化通知
### 10.2 avoidPageBreakInside 实现算法
```
1. 从末尾向前遍历所有行
2. 检查每行是否属于 avoidPageBreakInside 元素
3. 如果是,标记需要移除
4. 最多移除 3 行 (kMaxLinesToRemove)
5. 调用 rebuildFrameWithoutLastLines: 重建帧
```
---
## 11. WREpubTypesetter(EPUB 排版入口)
这是 EPUB 渲染的最高层入口,协调整个排版流程。
### 11.1 CSS 级联顺序(优先级从低到高)
1. **default.css** — 基础 HTML 标签样式
2. **replace.css** — WeRead 默认替换(思源宋体标题、Menlo 代码、bodyPic 图片)
3. **dark.css** — 暗色主题覆盖(仅暗色模式加载)
4. **EPUB 嵌入 CSS** — 书籍自带样式
5. **用户设置 CSS** — 运行时用户偏好(字号、行高、主题色)
### 11.2 繁简转换
支持将简体中文内容转换为繁体(zh-Hant / zh-TW / zh-HK),使用 CFStringTransform 实现。
### 11.3 免费试读截断
非 VIP 用户每章有字符数限制,截断点为限制前最后一个段落边界,追加 "..." 指示器。
### 11.4 附件注入
章节末尾可选注入:
- articleTool — 文章工具栏(高亮、笔记、分享)
- bookChapterTool — 章节工具
- recommendView — 推荐视图
---
## 12. 字体系统
### 12.1 默认字体
- 正文: PingFang SC(苹方简体)
- 标题: Source Han Serif CN(思源宋体)
- 代码: Menlo(等宽)
### 12.2 动态字体加载
支持从 CDN 动态下载字体,通过 DTCoreTextFontDescriptor 管理字体描述符和匹配。
### 12.3 字体回退
拉丁语书籍使用不同的字体回退链,通过 `isLatinLanguageBook` 标记区分。
---
## 13. 主题系统
### 13.1 主题类型
- 默认(白底黑字)
- 护眼(米色 #f5f0e8)
- 暗色(黑底灰字 #1a1a1a / #cccccc)
- 自定义背景色
### 13.2 用户可配置项
通过 options 字典传入:
- `fontFamily` — 字体族
- `fontSize` — 字号 (默认 18px)
- `lineHeight` — 行高倍数
- `letterSpacing` — 字间距
- `backgroundColor` / `textColor` — 主题色
- `paragraphSpacing` — 段间距
- `firstLineIndent` — 首行缩进
---
## 14. 与原版 DTCoreText 的差异总结
| 维度 | 原版 DTCoreText | WeRead 定制版 |
|------|----------------|---------------|
| 定位 | 通用 HTML 渲染库 | 电子书分页渲染引擎 |
| CSS 支持 | 标准 CSS 2.1 | 标准 + 6 个自定义属性 |
| 分页 | 无 | 完整分页 + avoidPageBreakInside |
| 主题 | 无 | 暗色/护眼/自定义主题 |
| 翻译 | 无 | 繁简转换 + 双语支持 |
| 图片 | 基本附件 | 垂直居中 + 暗色适配 + CDN 加载 |
| 选择 | 无 | 完整文本选择 + 搜索高亮 |
| 响应式 | 无 | RACSubject 通知 |
| 线程安全 | 部分 | 全面 NSLock 保护 |
| 缓存 | 无 | 布局帧缓存 + 图片缓存 |