wangdl 01a64320cc fix: DOC-FULL A1-A8 审查修复
A1: FFI 导出 V2 函数 (start/pause/resume/close/push/export/ack/mark)
A2: ActiveTimeTracker 集成到 session_v2
A3: push_event 调用 position.normalized()
A4: remove_session 检查 Closed 状态
A5: Buffer 溢出驱逐 if-let 链修正
A8: reload_stale_events_v2() 启动恢复

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 20:16:39 +08:00
2026-06-07 20:16:39 +08:00

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 可以打成静态库或 XCFrameworkAndroid 可以打成 .soWeb 后续可以考虑编译成 WASM。Cargo 支持 staticlibcdylib 等库产物类型,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 / QuickLookAndroid 后续适配
图片 内置查看 metadata、阅读事件 原生图片查看
.epub 后续支持 EPUB 结构解析 WebView / 原生阅读器渲染章节

Markdown 解析建议使用 comrak,它是 CommonMark + GitHub Flavored Markdown 兼容的 Rust parser / renderer支持表格、任务列表、删除线、自动链接等 GFM 扩展。

图片 metadata 可用 image crate。它提供 Rust 原生图像编码、解码和基础图像处理能力。


系统预览 / 外部打开

格式 支持策略
.docx / Word iOS/macOS 用 QuickLookAndroid 用外部 App / 系统能力
.xlsx / Excel iOS/macOS 用 QuickLookAndroid 用外部 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 内容。移动端电子书可以参考 ReadiumReadium 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

后续可能需要 thiserrorderive Error traittracing(日志诊断)、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 才不会重复造轮子。

Description
No description provided
Readme 132 MiB
Languages
Rust 46.7%
C 28.4%
Swift 23.6%
Shell 0.8%
Python 0.5%