# pdfeditor **Repository Path**: araxit/pdfeditor ## Basic Information - **Project Name**: pdfeditor - **Description**: No description available - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-25 - **Last Updated**: 2026-10-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # PDF 编辑器(PdfEditor) Android 原生(Kotlin)PDF 查看与**对象级实时编辑**应用。 ## 核心架构:全量转换 —— SQLite 即文档本体 ### 功能总览(对标商业 PDF 编辑器) | 类别 | 功能 | 实现层 | |---|---|---| | 对象识别 | 文本块/图像 XObject/路径 对象级识别,坐标/字号/字体/颜色/像素尺寸等完整属性 | PDFBox + MiniPdf | | 对象编辑 | 文本真实替换(矢量可选中)、对象物理删除 | frags 片段改写 | | 对象内部 CRUD | **对象检查器**:任意 PDF 对象字典的键值增删改查(数字/名称/字符串/引用) | rawobjs + dictPut/dictRemove | | 页面操作 | 旋转(0/90/180/270)、删除页面、页面前移/后移(排序) | 页面树 Kids 数组改写 | | 文档信息 | 元数据编辑(Title/Author/Subject/Keywords/Creator) | trailer /Info 字典 | | 内容增强 | 文字水印(复用页面字体资源,斜置灰字插入内容流) | ctx 片段插入 | | **合并 PDF** | 追加合并另一文档:对象号偏移重编号(renumberRefs)、页面挂接页面树、/Parent 重指、Count 重建 | PdfObjectDb.mergeDocument | | **密码加密** | RC4 128-bit 标准安全处理器(V2/R3,算法 3.2/3.3/3.5),保存时全文档加密(字符串逐对象 RC4 + 流 RC4),兼容 poppler/qpdf/pikepdf | PdfSecurity + writeStore 加密重载 | | **批注** | 添加便签(Text)/文本框(FreeText)批注,/Annots 数组管理(间接/内联兼容),批注删除 | rawobjs + Annots 数组操作 | | **表单(AcroForm)** | 添加文本字段、赋值、删除;AcroForm /Fields 注册与目录挂接 | rawobjs + /AcroForm 字典 | | 保存 | 全量重建:库 → 全新 PDF,不读原文件;UUID 行序 → 紧凑新对象号统一重编码(body 引用/trailer 同步改写);支持加密保存 | MiniPdf.writeStore + savePlan | | 其他 | 复制对象文本、导出图像 XObject 为 PNG、系统"打开方式"进入、打开加密 PDF(密码框重试) | — | > **版本策略(设计决策)**:PDF 的增量更新(`/Prev` 链多版本)在**导入 SQLite 时即折叠为最新版本**——最新代胜出、free 对象不复活、不保留任何历史/日志/undo 表。SQLite 只保存文档当前状态,无历史回退能力;保存产物为单代干净文件。 以上页面级/文档级操作在库中即时生效,统一在"保存"时写入全新 PDF。 ``` 打开 PDF ──► MiniPdf 解析 ──► 整个文档转换入库(同名 .sqlite) │ ┌───────────────────────────┼───────────────────────────┐ ▼ ▼ ▼ pages(页面+内容流号) frags(内容流操作符片段) rawobjs(全部 PDF 对象序列化) │ │ │ └───── objects(对象展示模型,经 frag_seq 关联片段) ──────┘ │ 编辑 = 直接改 frags 行(替换片段字节 / 删除片段行)──► 三栏视图立即刷新 │ 保存 ──► MiniPdf.writeStore:从库中 rawobjs + frags 全新生成 PDF (不读原文件一个字节)──► 覆盖原文件 ──► 删除临时 .sqlite ``` 保存后原文件被一个**全新字节序列**的 PDF 替换:所有编辑(改文本、删片段)都真实 作用在新文件的内容流中,文本仍是矢量可选中可搜索。 ### 数据库表结构(`PdfObjectDb.kt`) - `pages(page_index PK, width, height)` - `objects(id PK, page_index, type, bbox_l/t/r/b, text, font_size, font_name, image_w, image_h, color, deleted, edited_text)` - `deleted`:删除标记 → 视图/导出时白矩形遮盖原对象 - `edited_text`:修改后文本 → 视图/导出时遮盖后按原字号重绘 ### 编辑语义 | 操作 | SQLite 写入 | 视图实时反馈 | 保存时 | |---|---|---|---| | 编辑文本 | `edited_text = 新文本` | 白底遮盖 + 按原字号绘制新文本 | 同视图效果写入新 PDF | | 删除对象 | `deleted = 1` | 白底遮盖 + 橙色虚线边框标记 | 对应区域留白 | | 未修改 | 不变 | 正常渲染 + 半透明青色选中高亮 | 原样保留 | ## 功能清单 - 三栏布局:左侧对象树 | 中间 PDF 视图(选中对象半透明高亮)| 右侧属性面板 - 对象级解析:文本块(内容/字号/字体/包围盒)、图像 XObject(CTM 定位+像素尺寸)、矩形路径(颜色) - 树/属性/页面双向联动,点击页面反向命中对象 - 实时编辑:编辑文本(对话框输入)、删除对象,改动即时写库并刷新全部视图 - 保存:依 SQLite 中对象的**当前状态**(非历史记录)由 MiniPdf 原生改写内容流,随后删除临时 `.sqlite` - 附加:复制对象文本到剪贴板、导出图像 XObject 为 PNG、系统"打开方式"进入 ## 构建与运行 用 Android Studio(JDK 17)打开本目录 → Gradle Sync → Run。 ``` minSdk 26 (Android 8.0) / targetSdk 34 / Kotlin 1.9 / AGP 8.5 依赖:androidx core/appcompat/recyclerview + com.tom-roush:pdfbox-android:2.0.27.0 ``` ## 工程结构 ``` app/src/main/java/com/example/pdfeditor/ ├── MainActivity.kt # 流程编排:打开→入库→编辑→保存→清理 ├── model/PdfObject.kt # 对象模型(含 deleted / editedText 编辑状态) ├── pdf/PdfObjectExtractor.kt # PDFBox 对象级解析(导入用) ├── pdf/PdfObjectDb.kt # 全文档 SQLite 库(pages/frags/rawobjs/objects) ├── pdf/PdfPageView.kt # PDF 视图:渲染 + 高亮 + 编辑遮盖/重绘 + 点击命中 ├── pdf/PdfExporter.kt # 栅格回退方案(保留备用) └── ui/TreeAdapter.kt / PropertyAdapter.kt ``` ## 定制类库 MiniPdf(完全自由的对象级编辑) `app/src/main/java/com/example/pdfeditor/minipdf/` 是本项目自研的 Java PDF 类库, 基于官方 ISO 32000-1:2008 规范实现,绕开第三方库的抽象限制: | 文件 | 内容 | |---|---| | `PdfValue.java` | 对象模型(Dict/Array/Str/Stream/Ref)+ 词法分析器 + 序列化 | | `PdfParser.java` | 文件结构解析:经典 xref / xref stream(含 PNG 预测器)/ ObjStm 对象流 / 多代 `/Prev` 链 / 全文容错搜索 | | `MiniPdf.java` | 编辑 API + 增量更新写入器 | 编辑能力(内容流操作符级,§7.8): - `replaceText`:定位 `Tj` / `'` / `TJ` 操作符,替换字符串字面量 —— **真实矢量文字替换**,可选中可搜索 - `removeText`:移除文本显示操作符(物理删除) - `removeImageDo`:移除 `/Name Do` 操作符(物理删除图像绘制调用) - 过滤器链:ASCII85Decode、FlateDecode 解码;写回规范化为未压缩流 保存采用 **增量更新(Incremental Update,§7.5.6)**:原始字节一字不动, 文件尾追加「修订对象 + 新 xref 节 + 带 `/Prev` 的 trailer」—— PDF 标准的原生编辑方式, 任意阅读器按最新代渲染。 **实测验证**(沙箱 JDK 20 编译 + poppler 验证,两条链路均通过): ``` 【全量转换】PDF → 片段化导入(8片段) → 库级改文本/删片段 → writeStore 全新重建 → pdftotext 输出 "EDITED FROM DB / Keep This",pdfinfo 结构合法,二次解析完整 【增量更新】解析 → replaceText/removeText → saveIncremental → 同样通过 ``` 当前 App 保存走「全量转换」链路;`saveIncremental` 保留作为未来"轻量快速保存"选项。 ## 说明与边界 - 底图渲染用系统 `PdfRenderer`,对象坐标用 PDFBox 提取,均为 PDF pt 坐标系,可精确对齐。 - 保存采用"底图 + 编辑覆盖"方式,可保留原 PDF 的复杂排版;如需完全由 SQLite 对象重建页面,可在 `PdfExporter` 中扩展为纯对象绘制模式。 - MiniPdf 当前边界:不支持加密 PDF(打开时即拒绝);文本编辑按 PDFDocEncoding/Latin-1 字节匹配(ASCII 完全可靠,CID 字体的中文片段改写会失败并提示);多内容流页面(Contents 数组)仅重建首个流。 - 大文档首次解析建议移入协程;数据库文件位于应用私有目录(`getDatabasePath`),随 App 卸载自动清理。