BufferedReadingEventV2{event, state: Pending|Exported|Failed, retry_count}
export_pending_events_v2(limit, ts) - marks as Exported
ack_events_v2(eventIds) - removes by eventId
mark_events_failed_v2(eventIds) - for retry
Overflow: Failed→Exported→oldest eviction
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
zhixi-document-runtime
1. 仓库定位
zhixi-document-runtime 是知习的跨平台文档阅读与学习记录内核。
支撑平台:
iOS
Android
鸿蒙
macOS
Windows
Web
统一处理资料阅读相关的底层能力。
它的目标不是做一个完整 App,也不是做 AI/RAG 解析,而是提供一个跨端复用的 Document Core。
本仓库服务于知习的资料阅读、阅读进度、笔记锚点、资料搜索和学习事件采集。知习主 App 继续负责账号、导航、知识库、AI 接口、上传、权限、UI 页面等业务能力。
2. 核心原则
1. 原文件预览和 AI 解析彻底分离。
2. Rust Core 不负责网络请求。
3. Rust Core 不直接访问后端 API。
4. Rust Core 不保存用户 Token。
5. Rust Core 不负责完整 App UI。
6. Rust Core 输出统一文档模型、阅读事件、搜索结果和锚点。
7. 各端 App 负责下载文件、缓存文件、展示 UI、调用后端接口。
8. 不追求全格式完美预览。
9. Word / Excel 第一版走系统预览或外部应用兜底。
10. PPT、OCR、复杂富文本、PDF 标注暂不进入第一版。
3. 技术路线
本仓库采用:
Rust Core
+ UniFFI 跨语言绑定
+ 各端原生 App 宿主
Rust 编译后会作为库打包进 App。iOS/macOS 可以打成静态库或 XCFramework;Android 可以打成 .so;Web 后续可以考虑编译成 WASM。Cargo 支持 staticlib、cdylib 等库产物类型,cdylib 用于生成可被其他语言加载的动态系统库。
跨语言绑定优先使用 UniFFI。UniFFI 可以为 Rust crate 生成 Swift、Kotlin 等语言绑定,适合把 Rust Core 暴露给 iOS 和 Android 使用。
4. Rust Core 负责什么
负责:
1. 文件类型识别
2. Markdown / TXT 解析
3. EPUB 结构解析
4. PDF 阅读位置模型
5. PDF 渲染 POC
6. 图片 metadata
7. 阅读位置模型
8. 阅读时长事件
9. 搜索模型
10. 笔记锚点模型
11. App ↔ Rust 事件协议
12. Swift / Kotlin binding
5. Rust Core 不负责什么
不负责:
1. 网络请求
2. COS 下载
3. 用户登录
4. Token 管理
5. 后端 API 调用
6. App 页面导航
7. 系统分享
8. 文件选择器
9. AI / RAG 解析
10. 向量化
11. Office 高保真预览
12. 完整 PDF 标注
13. OCR
14. 富文本编辑器
15. Rust 全 UI 阅读器
6. 支持格式分级
内置阅读 / 基础支持
| 格式 | 支持方式 | Rust 责任 | App 责任 |
|---|---|---|---|
.md |
内置阅读 | Markdown 解析为 block tree | 原生渲染 block |
.txt |
内置阅读 | 文本读取、搜索 | 原生文本渲染 |
.pdf |
平台阅读优先 | 阅读位置模型,后续 PDF metadata | iOS 用 PDFKit / QuickLook,Android 后续适配 |
| 图片 | 内置查看 | metadata、阅读事件 | 原生图片查看 |
.epub |
后续支持 | EPUB 结构解析 | WebView / 原生阅读器渲染章节 |
Markdown 解析建议使用 comrak,它是 CommonMark + GitHub Flavored Markdown 兼容的 Rust parser / renderer,支持表格、任务列表、删除线、自动链接等 GFM 扩展。
图片 metadata 可用 image crate。它提供 Rust 原生图像编码、解码和基础图像处理能力。
系统预览 / 外部打开
| 格式 | 支持策略 |
|---|---|
.docx / Word |
iOS/macOS 用 QuickLook;Android 用外部 App / 系统能力 |
.xlsx / Excel |
iOS/macOS 用 QuickLook;Android 用外部 App / 系统能力 |
.pptx / PPT |
暂不承诺内置预览,可外部打开 |
这些格式不进入 Rust Core 第一版。原因是 Office 高保真排版复杂,第一版不应该消耗精力在 Word / Excel / PPT 自研预览上。
后续增强
| 格式 / 能力 | 策略 |
|---|---|
| PDF Rust 渲染 | 评估 PDFium / MuPDF |
| PDF 文本搜索 | 优先读取 PDF 文本层 |
| EPUB 阅读 | 解析结构 + 原生/Web 渲染 |
| Word 转 PDF | 后端或本地转换后复用 PDF 阅读 |
| Excel 表格预览 | 后端转 HTML / CSV / PDF |
| PPT | 后续再评估 |
| OCR | 暂不做 |
| PDF 标注 | 后移 |
| 富文本笔记 | 后移 |
PDF 后续可评估 pdfium-render。它是 Pdfium 的 Rust 高层接口,Pdfium 可渲染 PDF 页面为 bitmap,也能提取文本和图片。
EPUB 不是 Word 那类复杂办公格式,它本质上更接近打包的 Web 内容。移动端电子书可以参考 Readium,Readium Swift / Kotlin Toolkit 面向 iOS、iPadOS、Android、ChromeOS 等平台,支持 EPUB、PDF、有声书、漫画等出版物格式;但 Readium 也说明它提供的是底层工具,UI 和数据层仍需应用自己实现。
7. 推荐 Rust 依赖包
7.1 Core 基础依赖(已使用)
serde = { version = "1", features = ["derive"] }
serde_json = "1"
uuid = { version = "1", features = ["v4"] }
用途:
| 包 | 用途 |
|---|---|
serde |
数据结构序列化 / 反序列化 |
serde_json |
JSON 协议、事件序列化 |
uuid |
block ID 生成(v4 随机 UUID) |
后续可能需要 thiserror(derive Error trait)、tracing(日志诊断)、time(时间戳序列化),按需引入即可。
7.2 文件类型识别
infer = "0.16"
mime_guess = "2"
用途:
infer:根据文件头判断类型
mime_guess:根据扩展名推测 MIME
策略:
1. 优先读取 magic bytes
2. 其次使用 MIME
3. 最后使用扩展名
7.3 Markdown
comrak = "latest-compatible"
用途:
1. 解析 Markdown
2. 支持 GFM 表格、任务列表、删除线
3. 输出 block tree
4. 可选输出 HTML,调试用
不推荐只输出 HTML。更推荐输出知习自己的 block model:
pub enum DocumentBlock {
Heading { id: String, level: u8, text: String },
Paragraph { id: String, text: String },
List { id: String, ordered: bool, items: Vec<String> },
CodeBlock { id: String, language: Option<String>, code: String },
Quote { id: String, text: String },
Table { id: String, headers: Vec<String>, rows: Vec<Vec<String>> },
Image { id: String, src: String, alt: Option<String> },
HorizontalRule { id: String },
}
7.4 图片
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp", "gif"] }
用途:
1. 读取图片尺寸
2. 判断图片格式
3. 后续生成缩略图
不做 OCR,不承诺选择图片文字。
7.5 EPUB
zip = "latest-compatible"
roxmltree = "latest-compatible"
quick-xml = "latest-compatible"
用途:
| 包 | 用途 |
|---|---|
zip |
EPUB 本质是 zip 容器,用于读取 EPUB 内部文件 |
roxmltree |
解析 OPF / nav XML 为只读树 |
quick-xml |
后续处理大型 XML 或流式解析 |
EPUB 目标不是完整渲染,而是:
1. 识别 EPUB
2. 读取 metadata
3. 解析 spine
4. 解析目录
5. 输出章节列表
6. 建立章节级阅读位置
章节 HTML 的最终渲染可以交给宿主 App 的 WebView / 原生 HTML 渲染能力。
7.6 PDF
第一版不强制引入 PDF 渲染库。 后续可选:
pdfium-render = { version = "latest-compatible", optional = true }
用途:
1. PDF metadata
2. PDF 页数
3. PDF 文本提取
4. PDF 页面 bitmap 渲染 POC
注意:PDF 在 iOS 可以先用 PDFKit / QuickLook。Rust Core 只统一 PDF 阅读位置模型:
pub struct PdfReadingPosition {
pub page_number: u32,
pub page_progress: f32,
pub overall_progress: f32,
}
7.7 搜索
先自己实现轻量搜索:
Markdown / TXT:
- lowercase
- simple contains
- 返回 blockId + snippet
PDF:
- 先交给平台 PDFKit
后续如果需要本地全文索引,可以评估:
tantivy = { version = "latest-compatible", optional = true }
Tantivy 是 Rust 的全文搜索引擎库,类似 Lucene。
但第一版不建议直接上 Tantivy,避免移动端包体、索引存储和复杂度过早增加。
7.8 UniFFI
uniffi = "0.28"
UniFFI 0.28 使用 proc-macro 模式 生成跨语言绑定:
- 类型导出:
#[derive(uniffi::Enum)]、#[derive(uniffi::Record)]、#[derive(uniffi::Error)]标注 Rust 类型 - 函数导出:
#[uniffi::export]标注公开函数 - Swift 绑定:
uniffi-bindgen library --swift-sources libzx_document_ffi.dylib从编译产物生成 - Kotlin 绑定:同理,
uniffi-bindgen library --kotlin-sources生成
注意:UniFFI 0.28 的纯 UDL 模式不再生成 extern "C" 分发函数。类型定义和函数导出均需 proc macro。
8. Cargo workspace 建议
[workspace]
resolver = "2"
members = [
"crates/zx_document_core",
"crates/zx_document_ffi",
"crates/xtask"
]
crates/zx_document_core
核心 Rust 逻辑。
负责:
- 文件类型识别
- Markdown / TXT 解析
- 图片 metadata
- EPUB 结构解析
- 阅读位置
- 阅读事件
- 搜索
- 锚点
crates/zx_document_ffi
FFI 绑定层。通过 proc-macro 暴露 API。
负责:
- #[uniffi::export] 标注导出函数
- #[derive(uniffi::*)] 标注导出类型
- build.rs 生成 scaffolding
- 类型转换(core ↔ FFI)
- Swift / Kotlin binding 生成
crates/xtask
开发辅助工具。
负责:
- 构建 iOS XCFramework
- 生成 bindings
- 跑 fixtures 测试
- 打包 release artifact
9. 推荐目录结构
zhixi-document-runtime/
├── README.md
├── Cargo.toml
├── crates/
│ ├── zx_document_core/
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── lib.rs
│ │ ├── error.rs
│ │ ├── material_type.rs
│ │ ├── document.rs
│ │ ├── blocks.rs
│ │ ├── markdown.rs
│ │ ├── text.rs
│ │ ├── image_meta.rs
│ │ ├── epub.rs
│ │ ├── pdf.rs
│ │ ├── search.rs
│ │ ├── progress.rs
│ │ ├── events.rs
│ │ └── anchors.rs
│ │
│ ├── zx_document_ffi/
│ │ ├── Cargo.toml
│ │ ├── build.rs
│ │ ├── src/
│ │ │ └── lib.rs
│ │ └── zx_document.udl
│ │
│ └── xtask/
│ ├── Cargo.toml
│ └── src/
│ └── main.rs
│
├── bindings/
│ ├── ios/
│ ├── android/
│ └── generated/
│
├── fixtures/
│ ├── markdown/
│ ├── text/
│ ├── pdf/
│ ├── images/
│ └── epub/
│
├── docs/
│ ├── architecture.md
│ ├── supported-formats.md
│ ├── event-protocol.md
│ ├── reading-position-model.md
│ ├── pdf-strategy.md
│ ├── ios-integration.md
│ └── app-rust-bridge.md
│
└── scripts/
├── build-ios.sh
├── build-android.sh
└── generate-bindings.sh
10. 核心数据模型
10.1 MaterialType
pub enum MaterialType {
Markdown,
Text,
Pdf,
Image,
Epub,
Word,
Excel,
PowerPoint,
Unknown,
}
10.2 PreviewMode
pub enum PreviewMode {
NativeReader,
PlatformPreview,
ExternalOpen,
Unsupported,
}
含义:
NativeReader:
由知习内置阅读器展示,例如 MD / TXT / 图片 / 后续 EPUB。
PlatformPreview:
由平台能力展示,例如 iOS QuickLook。
ExternalOpen:
交给外部 App 打开。
Unsupported:
暂不支持。
10.3 DocumentInfo
pub struct DocumentInfo {
pub material_id: String,
pub title: String,
pub material_type: MaterialType,
pub preview_mode: PreviewMode,
pub file_size: u64,
pub page_count: Option<u32>,
pub word_count: Option<u32>,
pub created_at: Option<String>,
}
10.4 ReadingPosition
pub enum ReadingPosition {
Markdown {
block_id: String,
scroll_progress: f32,
},
Text {
line_number: u32,
scroll_progress: f32,
},
Pdf {
page_number: u32,
page_progress: f32,
overall_progress: f32,
},
Image {
zoom_scale: f32,
offset_x: f32,
offset_y: f32,
},
Epub {
chapter_id: String,
chapter_progress: f32,
overall_progress: f32,
},
Unknown,
}
10.5 ReadingEvent
pub enum ReadingEvent {
MaterialOpened {
material_id: String,
timestamp_ms: i64,
},
MaterialClosed {
material_id: String,
timestamp_ms: i64,
active_seconds: u32,
},
PositionChanged {
material_id: String,
position: ReadingPosition,
timestamp_ms: i64,
},
Heartbeat {
material_id: String,
active_seconds: u32,
position: Option<ReadingPosition>,
timestamp_ms: i64,
},
MarkedAsRead {
material_id: String,
timestamp_ms: i64,
},
}
10.6 NoteAnchor
pub enum NoteAnchor {
Material {
material_id: String,
},
MarkdownBlock {
material_id: String,
block_id: String,
},
PdfPage {
material_id: String,
page_number: u32,
},
EpubChapter {
material_id: String,
chapter_id: String,
},
KnowledgeItem {
knowledge_item_id: String,
},
}
11. App ↔ Rust 调用边界
App 调用 Rust
detect_material_type(file_path)
open_document(file_path, material_id)
get_document_info(document_handle)
get_markdown_blocks(document_handle)
search_document(document_handle, query)
update_position(material_id, position)
export_pending_events()
create_note_anchor(material_id, position)
Rust 返回 App
DocumentInfo
DocumentBlock[]
SearchResult[]
ReadingEvent[]
NoteAnchor
DocumentError
Rust 不直接做
download_file()
upload_reading_events()
call_ai_chat()
save_note_to_backend()
share_file()
delete_material()
这些由宿主 App 调接口完成。
12. iOS 接入方式
iOS 侧目标:
SwiftUI 页面
→ 调用 UniFFI Swift binding
→ Rust Core 返回 DocumentInfo / Blocks / Events
→ SwiftUI 渲染
→ App 上传阅读事件
支持:
1. Markdown block 渲染
2. TXT 渲染
3. 图片预览
4. PDF 先走 PDFKit / QuickLook
5. Word / Excel 先走 QuickLook
6. 阅读位置上报
7. 标记已读
13. Android / 鸿蒙接入方式
Android 以后通过 Kotlin binding 接入:
Kotlin / Compose
→ UniFFI Kotlin binding
→ Rust Core
鸿蒙如果 UniFFI 没有直接支持,需要再评估:
1. C ABI
2. Rust 编译动态库
3. 鸿蒙侧通过 FFI / NAPI / C bridge 调用
这部分不进入第一版。
14. 第一版不允许做的事
为了防止 Agent 发散,明确禁止:
1. 不做 Rust UI 阅读器。
2. 不做 PDFium 集成。
3. 不做 Office 解析。
4. 不做 PPT。
5. 不做 OCR。
6. 不做 AI/RAG。
7. 不做富文本编辑器。
8. 不做后端 API 请求。
9. 不做文件上传下载。
10. 不做知识库业务逻辑。
15. 最小验收标准
第一版完成时,必须能做到:
1. Rust 能识别 md/txt/pdf/image/epub/docx/xlsx/pptx 类型。
2. Rust 能把 Markdown 解析成 DocumentBlock。
3. Rust 能读取 TXT 内容。
4. Rust 能读取图片尺寸。
5. Rust 能生成 ReadingPosition。
6. Rust 能生成 ReadingEvent。
7. Swift 能通过 UniFFI 调用 Rust。
8. iOS demo 能展示 Markdown blocks。
9. iOS demo 能收到阅读位置事件。
10. 所有核心模型都有单元测试。
16. 最终技术决策
第一版不做万能阅读器。
第一版只做跨平台 Document Core。
Markdown / TXT / 图片 metadata / 阅读事件是第一目标。
PDF 先由平台阅读能力承接。
Word / Excel 先由系统预览或外部应用承接。
EPUB 放到后续版本。
Office 高保真、PDF 标注、OCR、富文本编辑器全部后移。
一句话:
zhixi-document-runtime的第一版目标不是"支持所有文件预览",而是先建立跨平台文档内核、阅读位置模型、学习事件协议和 iOS 接入链路;只有这个底座稳定,后面 Android、鸿蒙、Mac、Windows、Web 才不会重复造轮子。