commit 785715c13a3b650c925feea5d32f877482768dcb Author: wangdl Date: Sat May 30 18:14:08 2026 +0800 Initial commit: README with technical boundary and architecture diff --git a/README.md b/README.md new file mode 100644 index 0000000..cdf169f --- /dev/null +++ b/README.md @@ -0,0 +1,772 @@ +# zhixi-document-runtime + +## 1. 仓库定位 + +`zhixi-document-runtime` 是知习的跨平台文档阅读与学习记录内核。 + +支撑平台: + +```text +iOS +Android +鸿蒙 +macOS +Windows +Web +``` + +统一处理资料阅读相关的底层能力。 + +它的目标不是做一个完整 App,也不是做 AI/RAG 解析,而是提供一个跨端复用的 Document Core。 + +本仓库服务于知习的资料阅读、阅读进度、笔记锚点、资料搜索和学习事件采集。知习主 App 继续负责账号、导航、知识库、AI 接口、上传、权限、UI 页面等业务能力。 + +--- + +## 2. 核心原则 + +```text +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. 技术路线 + +本仓库采用: + +```text +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 负责什么 + +```text +负责: +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 不负责什么 + +```text +不负责: +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 基础依赖 + +```toml +serde = { version = "1", features = ["derive"] } +serde_json = "1" +thiserror = "2" +anyhow = "1" +tracing = "0.1" +uuid = { version = "1", features = ["v4", "serde"] } +time = { version = "0.3", features = ["serde"] } +``` + +用途: + +| 包 | 用途 | +| ------------ | --------------------- | +| `serde` | 数据结构序列化 / 反序列化 | +| `serde_json` | JSON 协议、调试输出、事件序列化 | +| `thiserror` | 定义稳定错误类型 | +| `anyhow` | CLI / demo / 内部错误快速处理 | +| `tracing` | 日志与诊断 | +| `uuid` | 事件 ID、session ID | +| `time` | 时间戳、阅读事件时间 | + +Serde 是 Rust 生态常用的序列化 / 反序列化框架,适合把 Rust 数据结构转换成 JSON 传给 Swift/Kotlin。 + +--- + +### 7.2 文件类型识别 + +```toml +infer = "0.16" +mime_guess = "2" +``` + +用途: + +```text +infer:根据文件头判断类型 +mime_guess:根据扩展名推测 MIME +``` + +策略: + +```text +1. 优先读取 magic bytes +2. 其次使用 MIME +3. 最后使用扩展名 +``` + +--- + +### 7.3 Markdown + +```toml +comrak = "latest-compatible" +``` + +用途: + +```text +1. 解析 Markdown +2. 支持 GFM 表格、任务列表、删除线 +3. 输出 block tree +4. 可选输出 HTML,调试用 +``` + +不推荐只输出 HTML。更推荐输出知习自己的 block model: + +```rust +pub enum DocumentBlock { + Heading { id: String, level: u8, text: String }, + Paragraph { id: String, text: String }, + List { id: String, ordered: bool, items: Vec }, + CodeBlock { id: String, language: Option, code: String }, + Quote { id: String, text: String }, + Table { id: String, rows: Vec> }, + Image { id: String, src: String, alt: Option }, +} +``` + +--- + +### 7.4 图片 + +```toml +image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp", "gif"] } +``` + +用途: + +```text +1. 读取图片尺寸 +2. 判断图片格式 +3. 后续生成缩略图 +``` + +不做 OCR,不承诺选择图片文字。 + +--- + +### 7.5 EPUB + +```toml +zip = "latest-compatible" +roxmltree = "latest-compatible" +quick-xml = "latest-compatible" +``` + +用途: + +| 包 | 用途 | +| ----------- | ------------------------------ | +| `zip` | EPUB 本质是 zip 容器,用于读取 EPUB 内部文件 | +| `roxmltree` | 解析 OPF / nav XML 为只读树 | +| `quick-xml` | 后续处理大型 XML 或流式解析 | + +EPUB 目标不是完整渲染,而是: + +```text +1. 识别 EPUB +2. 读取 metadata +3. 解析 spine +4. 解析目录 +5. 输出章节列表 +6. 建立章节级阅读位置 +``` + +章节 HTML 的最终渲染可以交给宿主 App 的 WebView / 原生 HTML 渲染能力。 + +--- + +### 7.6 PDF + +第一版不强制引入 PDF 渲染库。 +后续可选: + +```toml +pdfium-render = { version = "latest-compatible", optional = true } +``` + +用途: + +```text +1. PDF metadata +2. PDF 页数 +3. PDF 文本提取 +4. PDF 页面 bitmap 渲染 POC +``` + +注意:PDF 在 iOS 可以先用 PDFKit / QuickLook。Rust Core 只统一 PDF 阅读位置模型: + +```rust +pub struct PdfReadingPosition { + pub page_number: u32, + pub page_progress: f32, + pub overall_progress: f32, +} +``` + +--- + +### 7.7 搜索 + +先自己实现轻量搜索: + +```text +Markdown / TXT: +- lowercase +- simple contains +- 返回 blockId + snippet + +PDF: +- 先交给平台 PDFKit +``` + +后续如果需要本地全文索引,可以评估: + +```toml +tantivy = { version = "latest-compatible", optional = true } +``` + +Tantivy 是 Rust 的全文搜索引擎库,类似 Lucene。 + +但第一版不建议直接上 Tantivy,避免移动端包体、索引存储和复杂度过早增加。 + +--- + +### 7.8 UniFFI + +```toml +uniffi = "latest-compatible" +``` + +用于生成 Swift / Kotlin bindings。UniFFI 支持为 Rust crate 生成外部语言绑定,适合本仓库作为跨端内核。 + +--- + +## 8. Cargo workspace 建议 + +```toml +[workspace] +resolver = "2" +members = [ + "crates/zx_document_core", + "crates/zx_document_ffi", + "crates/xtask" +] +``` + +### `crates/zx_document_core` + +核心 Rust 逻辑。 + +```text +负责: +- 文件类型识别 +- Markdown / TXT 解析 +- 图片 metadata +- EPUB 结构解析 +- 阅读位置 +- 阅读事件 +- 搜索 +- 锚点 +``` + +### `crates/zx_document_ffi` + +FFI 绑定层。 + +```text +负责: +- UniFFI 暴露 API +- Swift binding +- Kotlin binding +- 类型转换 +``` + +### `crates/xtask` + +开发辅助工具。 + +```text +负责: +- 构建 iOS XCFramework +- 生成 bindings +- 跑 fixtures 测试 +- 打包 release artifact +``` + +--- + +## 9. 推荐目录结构 + +```text +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 +│ ├── note-anchor-model.md +│ ├── ios-integration.md +│ ├── android-integration.md +│ └── roadmap.md +│ +└── scripts/ + ├── build-ios.sh + ├── build-android.sh + └── generate-bindings.sh +``` + +--- + +## 10. 核心数据模型 + +### 10.1 MaterialType + +```rust +pub enum MaterialType { + Markdown, + Text, + Pdf, + Image, + Epub, + Word, + Excel, + PowerPoint, + Unknown, +} +``` + +### 10.2 PreviewMode + +```rust +pub enum PreviewMode { + NativeReader, + PlatformPreview, + ExternalOpen, + Unsupported, +} +``` + +含义: + +```text +NativeReader: +由知习内置阅读器展示,例如 MD / TXT / 图片 / 后续 EPUB。 + +PlatformPreview: +由平台能力展示,例如 iOS QuickLook。 + +ExternalOpen: +交给外部 App 打开。 + +Unsupported: +暂不支持。 +``` + +--- + +### 10.3 DocumentInfo + +```rust +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, + pub word_count: Option, + pub created_at: Option, +} +``` + +--- + +### 10.4 ReadingPosition + +```rust +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 + +```rust +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, + timestamp_ms: i64, + }, + MarkedAsRead { + material_id: String, + timestamp_ms: i64, + }, +} +``` + +--- + +### 10.6 NoteAnchor + +```rust +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 + +```text +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 + +```text +DocumentInfo +DocumentBlock[] +SearchResult[] +ReadingEvent[] +NoteAnchor +DocumentError +``` + +### Rust 不直接做 + +```text +download_file() +upload_reading_events() +call_ai_chat() +save_note_to_backend() +share_file() +delete_material() +``` + +这些由宿主 App 调接口完成。 + +--- + +## 12. iOS 接入方式 + +iOS 侧目标: + +```text +SwiftUI 页面 +→ 调用 UniFFI Swift binding +→ Rust Core 返回 DocumentInfo / Blocks / Events +→ SwiftUI 渲染 +→ App 上传阅读事件 +``` + +支持: + +```text +1. Markdown block 渲染 +2. TXT 渲染 +3. 图片预览 +4. PDF 先走 PDFKit / QuickLook +5. Word / Excel 先走 QuickLook +6. 阅读位置上报 +7. 标记已读 +``` + +--- + +## 13. Android / 鸿蒙接入方式 + +Android 以后通过 Kotlin binding 接入: + +```text +Kotlin / Compose +→ UniFFI Kotlin binding +→ Rust Core +``` + +鸿蒙如果 UniFFI 没有直接支持,需要再评估: + +```text +1. C ABI +2. Rust 编译动态库 +3. 鸿蒙侧通过 FFI / NAPI / C bridge 调用 +``` + +这部分不进入第一版。 + +--- + +## 14. 第一版不允许做的事 + +为了防止 Agent 发散,明确禁止: + +```text +1. 不做 Rust UI 阅读器。 +2. 不做 PDFium 集成。 +3. 不做 Office 解析。 +4. 不做 PPT。 +5. 不做 OCR。 +6. 不做 AI/RAG。 +7. 不做富文本编辑器。 +8. 不做后端 API 请求。 +9. 不做文件上传下载。 +10. 不做知识库业务逻辑。 +``` + +--- + +## 15. 最小验收标准 + +第一版完成时,必须能做到: + +```text +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. 最终技术决策 + +```text +第一版不做万能阅读器。 +第一版只做跨平台 Document Core。 + +Markdown / TXT / 图片 metadata / 阅读事件是第一目标。 +PDF 先由平台阅读能力承接。 +Word / Excel 先由系统预览或外部应用承接。 +EPUB 放到后续版本。 +Office 高保真、PDF 标注、OCR、富文本编辑器全部后移。 +``` + +一句话: + +> `zhixi-document-runtime` 的第一版目标不是"支持所有文件预览",而是先建立跨平台文档内核、阅读位置模型、学习事件协议和 iOS 接入链路;只有这个底座稳定,后面 Android、鸿蒙、Mac、Windows、Web 才不会重复造轮子。