Rust Core: - events: global Mutex-buffered event queue (push/export/clear/update_position) - image_meta: use ImageReader::format() for real decoded format instead of extension - search: change match_start/match_end usize→u64 for FFI compatibility - epub/pdf: module-level placeholder comments FFI (UDL exposed, 12 total functions): - Added: search_markdown_blocks, search_text_content, create_note_anchor - Added: push_reading_event, update_reading_position, export_pending_events, clear_exported_events Docs: - README: synced DocumentBlock model (Table.headers + HorizontalRule), simplified dependency recommendations, removed nonexistent doc references - docs/pdf-strategy.md: PDFKit vs PDFium vs MuPDF evaluation, 5 decisions, platform strategy matrix, roadmap Build: - .cargo/config.toml: cargo xtask alias - xtask: CLI implementation (test/build-ios/fixtures commands) - fixtures: markdown/sample.md (9 block types), text/sample.txt - XCFramework rebuilt, Swift bindings regenerated (1977 lines) 58 tests pass, zero warnings. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
185 lines
5.7 KiB
Markdown
185 lines
5.7 KiB
Markdown
# PDF 阅读方案评估
|
||
|
||
## 概述
|
||
|
||
PDF 是知习支持的核心文件格式之一。本文档明确 PDF 在各平台的处理策略、技术选型边界和后续路线图。
|
||
|
||
## 核心结论
|
||
|
||
1. **iOS 第一版继续使用 PDFKit / QuickLook** — 系统内置,无需额外依赖
|
||
2. **Rust 暂不接入 PDFium** — 增加包体但不创造足够价值
|
||
3. **文本选择由平台能力承担** — PDFKit(iOS)、PdfRenderer(Android)
|
||
4. **PDF 搜索后置** — 待文本提取方案确定后再做
|
||
5. **扫描 PDF / OCR 暂缓** — 明确不在第一版范围
|
||
|
||
---
|
||
|
||
## 方案对比
|
||
|
||
### 候选方案
|
||
|
||
| 方案 | 二进制大小 | 平台支持 | 文本提取 | 搜索 | 标注 | 成熟度 |
|
||
|------|----------|---------|---------|------|------|--------|
|
||
| **QuickLook (iOS/macOS)** | 0(系统内置) | Apple only | 否 | 否 | 否 | 高 |
|
||
| **PDFKit (iOS/macOS)** | 0(系统内置) | Apple only | 是 | 是 | 是 | 高 |
|
||
| **Android PdfRenderer** | 0(系统内置) | Android only | 部分 | 否 | 否 | 中 |
|
||
| **pdfium-render (Rust)** | ~15MB/平台 | 全平台 | 是 | 可自建 | 否 | 中 |
|
||
| **MuPDF (C)** | ~8MB/平台 | 全平台 | 是 | 可自建 | 部分 | 高 |
|
||
|
||
### 评估维度
|
||
|
||
| 维度 | QuickLook (iOS) | PDFKit (iOS) | pdfium-render (Rust) |
|
||
|------|----------------|-------------|---------------------|
|
||
| 集成成本 | 极低(QLPreviewController) | 低(原生 API) | 高(交叉编译、binding) |
|
||
| 包体影响 | 0 | 0 | ~15MB × 平台数 |
|
||
| 页面渲染 | ✅ | ✅ | ✅(需要 bitmap pipeline) |
|
||
| 文本提取 | ❌ | ✅ | ✅ |
|
||
| 文本选择 | ❌(只能看不选) | ✅ | 需自建 UI |
|
||
| 搜索 | ❌ | ✅ | 可自建 |
|
||
| 阅读位置 | App 侧维护 | App 侧 + delegate | Rust 侧统一 |
|
||
| 统一数据模型 | ❌ | ❌ | ✅ |
|
||
| 跨平台复用 | ❌ | ❌ | ✅ |
|
||
|
||
---
|
||
|
||
## 各平台策略
|
||
|
||
### iOS / macOS
|
||
|
||
**当前(M3-M4):PDFKit**
|
||
|
||
```
|
||
MaterialReaderView
|
||
→ PreviewMode.platformPreview
|
||
→ QuickLook sheet (QLPreviewController)
|
||
→ App 侧监听页码变化
|
||
→ 生成 ReadingPosition::Pdf { pageNumber, pageProgress, overallProgress }
|
||
→ pushReadingEvent
|
||
```
|
||
|
||
优势:
|
||
- 零依赖,系统自带
|
||
- 渲染质量高
|
||
- 支持系统级文本选择、搜索
|
||
- 用户熟悉的交互
|
||
|
||
局限:
|
||
- QuickLook 不暴露页码变化回调(需要 PDFKit 的 PDFView 才能精确跟踪)
|
||
- 无法提取文本传给 Rust 做统一搜索
|
||
- App 侧需用 PDFView delegate 替代 QLPreviewController 以获得页码回调
|
||
|
||
**后续增强**:如果需要文本提取/搜索,把 QuickLook sheet 替换为 PDFView + PDFDocument,通过 `PDFDocument.string` 提取全文传给 Rust `search_text`。
|
||
|
||
### Android
|
||
|
||
**当前策略:系统预览 / 外部 App**
|
||
|
||
Android 有 `PdfRenderer`(API 21+),可渲染页面为 Bitmap。但第一版不做内置 PDF 阅读器。
|
||
|
||
```
|
||
MaterialReaderView (Android)
|
||
→ PreviewMode.platformPreview
|
||
→ Intent.ACTION_VIEW + content:// URI
|
||
→ 系统 PDF 阅读器 / Chrome
|
||
→ 回到 App 后手动记录阅读时长
|
||
```
|
||
|
||
后续可选:`PdfRenderer` + `RecyclerView` 自建阅读器,用 Rust `ReadingPosition::Pdf` 统一位置模型。
|
||
|
||
### 鸿蒙 / Windows / Web
|
||
|
||
均优先走系统预览或浏览器内置 PDF 阅读器。Rust 只统一 `ReadingPosition::Pdf` 模型。
|
||
|
||
---
|
||
|
||
## 阅读位置模型
|
||
|
||
Rust 已定义统一的 `ReadingPosition::Pdf`:
|
||
|
||
```rust
|
||
ReadingPosition::Pdf {
|
||
page_number: u32, // 1-based 页码
|
||
page_progress: f32, // 0.0 ~ 1.0,该页内滚动比例
|
||
overall_progress: f32, // 0.0 ~ 1.0,全书进度
|
||
}
|
||
```
|
||
|
||
App 侧职责:
|
||
- iOS:PDFView.pageChange delegate → 更新位置
|
||
- Android:监听页面变化 → 更新位置
|
||
- 所有平台:用同一套 `ReadingPosition::Pdf` 模型,不重复造轮子
|
||
|
||
---
|
||
|
||
## Rust 侧职责边界
|
||
|
||
### 当前(M3-M4)
|
||
|
||
- `MaterialType::Pdf` — 文件类型识别 ✅
|
||
- `ReadingPosition::Pdf` — 统一位置模型 ✅
|
||
- `PreviewMode::PlatformPreview` — 预览模式映射 ✅
|
||
- `pdf.rs` — 模块占位(注释说明走平台预览)✅
|
||
|
||
### 不做的
|
||
|
||
- 不集成 PDFium
|
||
- 不做 PDF 渲染(bitmap 生成)
|
||
- 不做 PDF 文本提取
|
||
- 不做 PDF 标注
|
||
- 不做 OCR
|
||
|
||
### 后续评估(M5+)
|
||
|
||
如果以下条件满足 **3 项以上**,重新评估 PDFium 集成:
|
||
|
||
1. Android 需要内置 PDF 阅读器
|
||
2. 搜索需要在 PDF 中定位
|
||
3. 需要跨平台统一的文本提取
|
||
4. 用户量大到平台差异成为维护负担
|
||
5. PDFium 交叉编译经验积累充分
|
||
|
||
---
|
||
|
||
## 搜索策略
|
||
|
||
| 阶段 | 方案 | 能力 |
|
||
|------|------|------|
|
||
| M3-M4 | 不搜索 PDF | 用户在平台预览器中手动使用系统搜索 |
|
||
| M5+ | 平台提取 + Rust 搜索 | PDFKit.string / PdfRenderer → 传给 Rust `search_text` |
|
||
| 远期 | PDFium 提取 + Tantivy | 全文索引,支持 PDF 内定位 |
|
||
|
||
---
|
||
|
||
## 扫描 PDF / OCR
|
||
|
||
**明确不进入知习范围**。理由:
|
||
|
||
- OCR 是独立技术领域,与"阅读内核"定位不符
|
||
- 高精度 OCR 需要专门模型(Tesseract / Apple Vision / ML Kit)
|
||
- 绝大多数学习资料是原生电子文档,非扫描件
|
||
- 扫描件场景可由用户自行 OCR 后导入
|
||
|
||
---
|
||
|
||
## 后续路线图
|
||
|
||
```
|
||
M3 ✅ — QuickLook sheet + ReadingPosition::Pdf
|
||
M4 ● — pdf-strategy.md(本文档)
|
||
M5 ○ — iOS 迁移至 PDFView(获得文本提取和页码回调)
|
||
— Rust 接收 PDF 全文做 search_text
|
||
M6+ ○ — 评估 PDFium(Android 自建阅读器需求驱动)
|
||
— 如集成 PDFium:文本提取 + bitmap 渲染 + 搜索定位
|
||
```
|
||
|
||
---
|
||
|
||
## 验收确认
|
||
|
||
- [x] iOS 第一版继续使用 PDFKit / QuickLook
|
||
- [x] Rust 暂不接 PDFium
|
||
- [x] PDF 文本选择由平台能力承担
|
||
- [x] PDF 搜索后置
|
||
- [x] 扫描 PDF / OCR 暂缓
|
||
- [x] 文档存在,方案决策明确,有后续路线图
|