受众:开源贡献者
最后更新:2026-06-02
版本:0.1.0
PdfToolbox 是一个纯本地离线的 PDF 桌面工具箱,基于 Rust + Tauri 2.x 构建。
- 本地优先:所有 PDF 处理在本机完成,无需联网,文件不会离开用户电脑
- 即开即用:无需注册登录,无文件大小限制(受系统内存限制)
- 免费开源:100% 开源,无广告,无收费功能
- 极简体验:清爽的 UI 设计,聚焦核心功能,零学习成本
- 普通办公人员:需要快速合并、拆分、压缩 PDF
- 文档处理用户:需要格式互转(Word/PPT/Excel ↔ PDF)
- 隐私敏感用户:不愿意将文档上传到在线 PDF 工具
- 开发者/贡献者:对 Rust + Tauri 桌面应用开发感兴趣的开源贡献者
| 维度 | 状态 |
|---|---|
| Rust 后端命令 | ~48 个已实现 |
| 前端 UI | 全部功能已有对应界面 |
| 测试覆盖率 | 基础功能有单元测试,覆盖率约 40% |
| CI/CD | 未配置 |
| 国际化 | 仅中文 |
| 发布渠道 | 未发布 |
npm run dev # 启动 Vite 前端开发服务器 (port 3000)
npm run tauri dev # 启动 Tauri 完整开发模式(前端 + Rust 热重载)
npm run tauri build # 生产构建,生成安装包 (.exe/.msi/.dmg)
npm run build # 仅构建前端 (Vite)
npm run lint # TypeScript 类型检查 (tsc --noEmit)
npm run clean # 清理 dist 目录
npm run tauri -- [args] # 透传参数给 Tauri CLI# 运行所有 Rust 单元测试(在 src-tauri 目录下)
cd src-tauri && cargo test
# 运行特定测试
cd src-tauri && cargo test test_merge_two_pdfs
# 运行测试并显示输出
cd src-tauri && cargo test -- --nocapture
# 检查编译
cd src-tauri && cargo checknpm run lint # TypeScript 类型检查
cd src-tauri && cargo clippy # Rust 代码风格检查(需安装 clippy)
cd src-tauri && cargo fmt # Rust 代码格式化- Tauri 构建需要:Rust 工具链 (>=1.85)、Node.js (>=20)、WebView2 (Windows 内置)
- 额外系统依赖:
libheif— HEIC 转 JPG(可选,运行时检测)LibreOffice— 文档格式互转(可选,运行时检测,推荐内置便携版)poppler/cairo— PDF 渲染为图片(暂未实现,可选)
pdftoolbox/
├── src/ # React 前端
│ ├── App.tsx # 主页:搜索 + 分类导航 + 工具卡片网格
│ ├── ToolDetailView.tsx # 工具详情页:文件选择 + 设置 + 处理 + 结果预览
│ ├── PdfPreview.tsx # PDF 预览(基于 pdf.js)
│ ├── types.ts # TypeScript 接口定义 (Tool/Category/ToolboxConfig)
│ ├── toolbox-config.json # 配置驱动:工具定义清单(JSON 格式)
│ ├── main.tsx # React 入口
│ └── index.css # Tailwind CSS v4 样式
│
├── src-tauri/ # Rust 后端
│ ├── src/
│ │ ├── main.rs # Windows 入口(隐藏控制台窗口)
│ │ ├── lib.rs # Tauri Builder + 命令注册 + 插件初始化
│ │ ├── commands.rs # Tauri 命令层(输入校验 + 调用 pdf_ops)
│ │ └── pdf_ops.rs # PDF 处理核心(merge/split/compress/rotate 等)
│ ├── Cargo.toml # Rust 依赖管理
│ ├── tauri.conf.json # Tauri 窗口/构建/安全配置
│ └── capabilities/ # Tauri v2 权限配置 (dialog/shell)
│
├── vite.config.ts # Vite 构建配置
├── package.json # npm 依赖 + 脚本
├── tsconfig.json # TypeScript 配置
├── CLAUDE.md # AI 辅助说明文件
├── SPEC.md # 本文件 - 项目规范
└── README.md # 项目 README(待补充)
┌─────────────────────────────────────────┐
│ 前端 (React 19 + TypeScript + Tailwind) │
│ App.tsx → ToolDetailView.tsx │
│ ↓ invoke('command', args) │
├─────────────────────────────────────────┤
│ 命令层 (commands.rs) │
│ 输入校验 + 参数默认值 → 调用 pdf_ops │
├─────────────────────────────────────────┤
│ 核心处理层 (pdf_ops.rs) │
│ lopdf / image / pdf-extract / qrcode │
├─────────────────────────────────────────┤
│ LibreOffice (可选) │
│ 文档格式互转 (Word/PPT/Excel/EPUB/PDF) │
└─────────────────────────────────────────┘
用户点击"选择文件" → Tauri 原生对话框 → 文件路径传给前端
→ 用户设置参数 → 点击"处理"
→ invoke Tauri 命令 (camelCase 参数名)
→ Rust 后端处理 → 输出到 %TEMP%/pdftoolbox/ 临时目录
→ 返回路径给前端
→ 前端显示完成状态 → 用户"打开文件"/"打开所在文件夹"
工具定义在 src/toolbox-config.json,前端根据 tool.id 查找 CMD_MAP(ToolDetailView.tsx 中定义)映射到对应的 Tauri 命令。
// toolbox-config.json 中的一条工具定义
{ "id": "merge", "name": "PDF合并", "category": "BASICS", "icon": "Merge" }// CMD_MAP 中的命令映射
merge: { cmd: 'merge_pdfs', multiple: true, getArgs: (files) => ({ files }) }- 注释语言:所有
///文档注释使用中文 - 属性位置:
///注释位于#[tauri::command]之前 - 错误消息:使用中文
- 命名风格:Rust 标准 snake_case
- 错误处理:函数返回
Result<T, String>,用map_err转换错误为中文描述
/// 合并多个 PDF 文件为一个:加载所有 PDF,重映射对象 ID 避免冲突,合并页面树到同一文档
pub fn merge_pdfs(input_paths: &[String], output_path: &str) -> Result<(), String> {
// ...
}- 命令拆分:避免一个命令包含多个
Option参数区分不同模式,应拆分为独立命令 - 参数必填:参数尽量为必填(非
Option),减少反序列化歧义 - 参数命名:前端 invoke 传参使用 camelCase,Tauri v2 自动映射到 Rust snake_case
// ✅ 正确
fn split_pdf_pages(file: String, pages_per_group: u32)
// ❌ 避免
fn split_pdf(file: String, ranges: Option<String>, pages_per_group: Option<u32>)- 类型定义:使用 TypeScript 接口(
interface),不使用type - 组件风格:函数组件 + React Hooks
- 图标:使用
lucide-react,通过ICON_MAP按名称查找 - 样式:Tailwind CSS v4 + utility classes
- 动画:使用
motion(Framer Motion)
- 所有 UI 文本目前为简体中文
- 错误消息前端显示中文(来自 Rust 的
Err字符串) - 暂未使用 i18n 框架,贡献者需保持中文一致性
位置:pdf_ops.rs 末尾的 #[cfg(test)] mod tests { ... }
已有测试覆盖:
| 功能模块 | 测试用例 | 覆盖情况 |
|---|---|---|
| Merge | test_merge_two_pdfs |
✅ |
test_merge_empty_list (边界) |
✅ | |
test_merge_single_pdf (边界) |
✅ | |
test_merge_invalid_path (错误路径) |
✅ | |
| Split | test_split_all_pages / test_split_ranges |
✅ |
test_split_by_pages / test_split_by_pages_zero |
✅ | |
test_split_invalid_range (错误路径) |
✅ | |
| Compress | test_compress_pdf / test_compress_levels |
✅ |
| Rotate | test_rotate_all_pages / test_rotate_cumulative |
✅ |
| Delete | test_delete_pages / test_delete_range |
✅ |
| Extract | test_extract_pages / test_extract_range |
✅ |
| Reorder | test_reorder_pages / test_reorder_empty |
✅ |
| Watermark | test_add_watermark |
✅ |
| Page Numbers | test_add_page_numbers |
✅ |
| Info | test_get_pdf_info / test_set_pdf_info |
✅ |
| Crop/Resize | test_crop_pdf / test_resize_pdf |
✅ |
| Flatten | test_flatten_pdf |
✅ |
| Protect/Unlock | test_protect_roundtrip / test_protect_empty_password |
✅ |
test_protect_wrong_password_fails (错误路径) |
✅ | |
| Replace Text | test_replace_text_simple / test_replace_text_not_found |
✅ |
test_replace_text_empty_search / test_replace_text_multiple |
✅ | |
| Images to PDF | test_images_to_pdf / test_images_to_pdf_empty |
✅ |
| WebP to JPG | test_webp_to_jpg |
✅ |
| QR Code | test_generate_qr / test_generate_qr_empty |
✅ |
| Repair | test_repair_valid_pdf / test_repair_nonexistent |
✅ |
test_repair_empty_file |
✅ | |
| Read File | test_read_file_base64 / test_read_file_base64_invalid |
✅ |
| HEIC | test_heic_to_jpg_not_found |
✅ |
| Extract Images | test_extract_images |
✅ |
| Fill Form | test_fill_pdf_form_empty |
✅ |
| PDF to Text | test_pdf_to_text |
✅ |
create_test_pdf(page_count)— 创建用于测试的空白 PDFcreate_text_pdf(text)— 创建含文本内容的 PDFtemp_path(ext)— 生成临时文件路径
新增功能或修改既有功能时,必须:
- 为核心处理函数(
pdf_ops.rs)添加对应单元测试 - 至少包含:1 个正常路径、1 个边界条件、1 个错误路径
- 测试使用
create_test_pdf/create_text_pdf等辅助函数 - 测试应独立、可重复、不依赖外部文件
前端目前没有测试基础设施。贡献者可选择引入:
vitest+@testing-library/react用于组件测试- 优先测试
ToolDetailView.tsx中的handleProcess逻辑
| 规则 | 说明 |
|---|---|
| 纯本地处理 | 所有文件操作在本地完成,不发送网络请求 |
| Rust 核心 | 所有 PDF 处理逻辑在 Rust 端实现,前端仅做 UI 和调用 |
| 中文优先 | UI 文本、注释、错误消息全部使用中文 |
| 配置文件驱动 | 新增工具时先注册到 toolbox-config.json |
| camelCase 传参 | 前端 invoke 使用 camelCase 参数名映射到 Rust snake_case |
| 临时文件清理 | 输出文件放在 %TEMP%/pdftoolbox/,系统自动清理 |
| 中文注释 | Rust /// 文档注释使用中文说明功能和参数 |
| 错误友好 | Rust 端返回中文错误消息,前端直接显示 |
| 函数注释 | 所有 Rust 函数必须有中文注释描述用途、参数、返回值 |
| 测试覆盖 | 新增核心功能必须有对应的单元测试 |
| 场景 | 说明 |
|---|---|
| 新增外部依赖 | 添加 Rust crate 或 npm 包前先讨论必要性 |
| 引入系统依赖 | 需要用户安装额外系统库(如 poppler/tesseract)的功能需慎重讨论 |
| 改变架构 | 修改数据流、命令注册方式、配置格式等需先讨论 |
| 添加付费/云端功能 | 引入云存储、付费墙等违反本地优先理念的功能 |
| 影响性能的变更 | 大文件处理、内存占用优化等需讨论方案 |
| 跨平台问题 | 涉及 macOS/Linux 特有 API 或路径处理时需讨论 |
| UI 大改 | 修改整体布局、色彩方案、交互模式时需讨论 |
| 数据库引入 | 项目目前无数据库依赖,引入需充分论证 |
| CI/CD 配置 | 添加 GitHub Actions 自动构建等需讨论平台和策略 |
| 版本号升级 | 涉及 SemVer 主版本变更或破坏性变更前需讨论 |
| 红线 | 说明 |
|---|---|
| 不上传文件到云端 | 任何功能都不应将用户文件发送到远程服务器 |
| 不收集用户数据 | 不埋点、不统计、不发送任何分析数据 |
| 不引入广告 | 应用内不展示任何商业广告 |
| 不要求注册/登录 | 用户无需创建账号即可使用全部功能 |
| 不开后门/远程代码执行 | 不做任何远程控制、自动更新下载、执行远程脚本 |
| 不引入 AGPL 等传染性许可证 | 依赖项需兼容当前许可证(MIT) |
| 不修改用户文件 | 所有操作生成新文件,不覆盖原始文件(需另存为) |
已实现 (48 个 Tauri 命令):
- PDF 基础:合并、拆分(两种模式)、压缩(三级)、水印、页码、文字替换、叠加、优化、修复、扁平化、密文遮盖
- 页面操作:旋转、删除、提取、重排、N-up、裁剪、缩放、裁半
- 格式转换:图片→PDF、Markdown→PDF、HEIC→JPG、WebP→JPG
- LibreOffice 互转:Word/PPT/Excel ↔ PDF、PDF→EPUB/HTML/PDF/A
- 安全:加密、解密
- 表单:填写 PDF 表单
- 元数据:读取/设置 PDF 信息
- 工具:生成二维码、生成密码、PDF 预览、比较差异、添加注释、添加书签、提取图片
待实现 (status: "pending" / "paused"):
| 功能 | 状态 | 所需技术 | 优先级 |
|---|---|---|---|
| PDF 编辑 (edit) | pending | 需要 PDF 内容流解析/重写 | 低 |
| PDF 签署 (sign) | pending | 数字签名库 | 低 |
| OCR 文本识别 (ocr) | paused | tesseract 系统库 | 低 |
| 创建可填写表单 (form-creator) | paused | 复杂表单字典构建 | 低 |
| 网页转 PDF (web-to-pdf) | pending | headless 浏览器 | 低 |
| 创建账单 (invoice-creator) | paused | PDF 排版引擎 | 低 |
| 用摄像头创建 PDF (cam-to-pdf) | paused | WebRTC 摄像头 | 低 |
| PDF 求职申请书 (app-creator) | paused | 模板引擎 | 低 |
| 技术 | 版本 |
|---|---|
| Rust | edition 2021 |
| Tauri | 2.x |
| React | 19.x |
| TypeScript | 5.8.x |
| Vite | 6.x |
| Tailwind CSS | 4.x |
| lopdf | 0.35 |
| image | 0.25 |
| pdf-extract | 0.7 |
| pdfjs-dist | 5.x |
| motion (Framer Motion) | 12.x |
MIT License — 详情见项目 LICENSE 文件。