首页 / 资讯中心 / 文章详情

MooTool next-compose F24 PDF 验收记录:基于 Apache PDFBox 3.0.4 的 PDF 拆分/合并对齐实现

MooTool next-compose F24 PDF 验收记录:基于 Apache PDFBox 3.0.4 的 PDF 拆分/合并对齐实现 ★ FEATURED ARTICLE
桌面应用开发工具【免费下载链接】MooToolHandy tool set for developers项目地址https://gitcode.com/gh_mirrors/mo/MooTool点击查看免费下载本篇基于 MooTool 仓库next-compose产品线Jetpack Compose Multiplatform 桌面版F24 PDF 功能的本轮验收记录 results.md 展开说明在 JDK 21 环境下执行./gradlew :composeApp:desktopTest全量 87/87 通过的验收口径、拆分/合并的页码语义与_split.pdf输出规则并结合同仓库 Electron 参考实现pdf-lib与 Compose 侧PdfEngine源码讲清 PDFBoximportPage的页级复制原理、加密 PDF 的失败语义、取消时的半成品清理机制以及本轮仍未覆盖的测试边界窗口截图、加密样本走查、安装镜像等。一、验收记录原文口径F24 是什么、本轮做了什么验收记录位于 next-compose/docs/evidence/2026-09-09-f24/results.md完整要素如下记录项内容阶段/条目F24 PDFP4 媒体/加密组本产品工作树仅next-compose/目录Compose 产品线独立开发不改动 Electron 或 Java 产品线依赖Apache PDFBox3.0.4版本固化在 next-compose/gradle/libs.versions.tomlpdfbox 3.0.4参考基线Electron 版 pdfService.ts、pageRanges.ts、PdfTool.tsx已执行JAVA_HOME指向 Zulu 21./gradlew :composeApp:desktopTest87/87通过语义覆盖拆分/合并 Tab、最多 20 项、奇偶/自定义页码、源顺序去重、_split.pdf后缀、合并保存对话框、历史与分离窗口、取消删除半成品关键差异DIFF-012PDFBoximportPage对齐pdf-libcopyPages见 diff/012-pdfbox-import-page.md未测项窗口截图、加密样本手工走查、表单/书签/签名安装镜像内、安装镜像下一轮P4 剩余 F23 图片助手在 next-compose/docs/feature-parity.md 的功能清单中F24 登记为pdf工具pdf/PdfTool.tsx的 Compose 对齐项其验收状态与差异引用DIFF-012、DIFF-032、DIFF-048均指向本记录所在的 PDF 拆分/合并能力。二、参考基线Electron 版用 pdf-lib 做了什么Compose 产品线的目标不是重写功能而是与 Electron 产品线做行为对齐parity。Electron 侧的实现是两条短链页码解析在 next/src/shared/utils/pageRanges.tsexport function parsePageSelection(expression: string, maxPage: number): number[] { if (!Number.isSafeInteger(maxPage) || maxPage 1) throw new Error(PDF has no pages) const value expression.trim().replace(/[,]/g, ;).replace(/\s/g, ) if (!value || !/^\d(?:-\d)?(?:;\d(?:-\d)?)*$/.test(value)) throw new Error(Use page ranges such as 1-5;8;10-12) const pages new Setnumber() for (const token of value.split(;)) { const [startText, endText startText] token.split(-) const start Number(startText) const end Number(endText) if (start 1 || end start || end maxPage) throw new Error(Page range must stay between 1 and ${maxPage}) for (let page start; page end; page 1) pages.add(page) } return [...pages] }要点逗号含中文逗号统一转分号、剔除空白后用^\d(?:-\d)?(?:;\d(?:-\d)?)*$校验用Set按 token 出现顺序去重返回 1 基页码列表。selectSplitPages再在其上叠加odd/even/custom自定义规则与候选页求交集且保留候选顺序。页面操作在 next/electron/main/pdfService.tsmergePdfs要求至少 2 个源splitPdfs对每个任务读取字节 →PDFDocument.load→selectSplitPages选页 →output.copyPages(source, pages.map((p) p - 1))→addPage输出路径固定为源目录下的${parsed.name.toLowerCase()}_split.pdf小写主名 _split.pdf已存在则覆盖。加密 PDF 未支持。三、Compose 侧实现PdfEngine 的页级复制与错误语义Compose 产品线把上述逻辑落在 next-compose/composeApp/src/desktopMain/kotlin/com/rememberber/mootool/next/compose/domain/PdfEngine.kt对象PdfEngine依赖org.apache.pdfbox:pdfbox:3.0.4在 composeApp/build.gradle.kts 的 desktopMain 源集中以implementation(libs.pdfbox)引入。3.1 页码解析与奇偶/自定义规则对齐 pageRanges.tsparsePageSelection(expression, maxPage)与 Electron 版逐条对齐maxPage 1抛no-pagestrim后把[,]替换为;、剔除空白同一套正则校验非法抛invalid-range错误信息同为Use page ranges such as 1-5;8;10-12用LinkedHashSet展开区间并保持源顺序去重越界start 1 || end start || end maxPage抛invalid-range。selectSplitPages(pageRange, rule, customRule, maxPage)对PdfSplitRule.Odd/Even做奇偶过滤Custom则把自定义表达式解析成集合后取交集——与 pageRanges.ts 中selectSplitPages的语义完全一致。3.2 拆分_split.pdf命名与 20 项上限split(tasks, cancelled)的约束与行为空任务列表抛empty-selectiontasks.size MAX_TASKS常量MAX_TASKS 20抛too-many错误信息At most 20 PDF tasks每个任务内先open(sourcePath)内部ensurePdf校验存在性与.pdf扩展名再Loader.loadPDF按selectSplitPages选页空选择抛empty-pages输出路径由splitOutputPath(source)生成source.resolveSibling(${source.nameWithoutExtension.lowercase()}_split.pdf)——即源目录、小写主名 _split.pdf与 ElectronpdfService.ts第 44 行的join(dirname(task.path), ${parsed.name.toLowerCase()}_split.pdf)一致源文件叫Quarterly.PDF时输出为quarterly_split.pdf每页写入前与写完后都会检查cancelled()命中即抛cancelled。页级复制的核心是writePages新建空文档subset.importPage(source.getPage(page - 1))逐页导入PDFMergerUtility().appendDocument(destination, subset)合并后保存。importPage是 PDFBox 的页面对象复制 API直接搬移 PDF 内部的页面对象内容流、资源引用而不是把页面渲染成位图再重新造一个 PDF——这正是 DIFF-012 中「保留页面内容流满足真 PDF 页面操作」的实现依据也对应 Electron 侧pdf-libcopyPages的等价语义。3.3 合并至少 2 个源、按源顺序追加merge(sources, outputPath, cancelled)少于 2 个源抛need-two超过 20 个抛too-many对每个源解析各自页码表达式、importPage到临时子集文档、appendDocument追加到目标文档最终pageCount 0抛empty-pages保存前Files.createDirectories补齐父目录destination.save落盘。合并入口由 UI 弹出保存对话框指定输出验收记录中的「合并保存对话框」Electron 侧默认桌面merge.pdf。3.4 三个值得注意的纵深细节加密 PDF 的稳定错误码open()捕获 PDFBox 的InvalidPasswordException并转成PdfException(encrypted, Encrypted PDF files are not supported)其他读取失败统一归一为invalid。这与 DIFF-012 声明的「加密 PDF 以InvalidPasswordException明确失败」一致且错误码稳定便于 UI 以 toast 形式提示而非栈溢出信息。取消删除半成品split的 catch 块中若异常是code cancelled会Files.deleteIfExists删除本批已经写出的全部输出文件再向外抛出——即验收记录中的「取消删除半成品」测试侧用AtomicBoolean(true)模拟取消断言目标文件不存在见 4.1 节第 6 条。页级表单修复repairImportedInteractiveFormsimportPage复制的页面携带 AcroForm widget 标注后PDFBox 需要重新挂接孤儿 widget 与默认值。源码对每个子集文档与最终文档执行AcroFormOrphanWidgetsProcessor(document).process()AcroFormDefaultFixup(document).apply()保证拆分/合并后表单字段、签名字段计数可被inspect正确统计。同时ELECTRON_PARITY_OUTLINES_NOT_COPIED true显式声明文档大纲/书签不被复制与 Electronpdf-libcopyPages行为一致——这是刻意为之的 parity 取舍而非能力缺失。inspect(path)还会返回结构统计页数、表单字段数acroForm.fields、书签数递归遍历documentOutline、签名字段数PDSignatureField计数hasSpecialObjects供 UI 在含表单/书签/签名时给出提示。四、测试证据PdfEngineTest 如何印证验收语义验收记录的「已执行./gradlew :composeApp:desktopTest87/87」中PDF 引擎的验证集中在 next-compose/composeApp/src/desktopTest/kotlin/com/rememberber/mootool/next/compose/domain/PdfEngineTest.kt。DIFF-012 记录的证据条目与测试一一对应1-3;2;7;9-10→[1,2,3,7,9,10]跨 token 去重保序1-2, 46验证逗号/中文逗号/空白归一3-1、1;;2、1-6(maxPage5) 断言invalid-range失败。奇偶/自定义2-8Odd→[3,5,7]Even→[2,4,6,8]Custom 1;3-4;8-10→[3,4,8]交集且保持候选顺序。真实文件合并4 页First.pdf取2-3 3 页Second.pdf取1,3合并 4 页extractText验证第 1 页含Page 2、第 4 页含Page 3——用PDFTextStripper提取文本证明输出是结构完整、可再解析的真 PDF。拆分命名Quarterly.PDF6 页按Odd拆 3 页输出恰为quarterly_split.pdf且第 1/3 页文本为Page 1/Page 5。上限21 个拆分任务与 21 个合并源均断言PdfException.code too-many。边界与取消非 PDF 文件断言not-pdf0 个合并源断言need-twocancelled { true }的合并不产出cancelled.pdf取消无输出文件。结构保留构造含表单 签名字段 书签的 fixture合并/拆分后inspect的表单与签名字段数保持不变而书签数为 0断言注释明确标注原因即「Page-only merge does not copy document outlines (same as Electron pdf-lib copyPages)」。加密样本StandardProtectionPolicy保护后的 PDFinspect断言code encrypted。测试 fixturewriteNumberedPdf/writeStructuredPdf/writeEncryptedPdf全部用 PDFBox 在临时目录现造无外部资产依赖这也是 87/87 可以在无头 CI 环境稳定跑通的原因。五、如何在本仓库复跑该验收环境与命令以 next-compose/README.md 与 next-compose/scripts/check-core.sh 为准export JAVA_HOME$(/usr/libexec/java_home -v 21) # Zulu 21README 环境表JDK 21 / Zulu 21.0.12.1 ./gradlew :composeApp:desktopTest # 或 ./scripts/check-core.sh 封装执行适用前提与限制本记录对应的验收轮只跑next-compose/工作树:composeApp:desktopTest87/87next-compose/docs/acceptance.md中后续轮次的数字如 1342/1344属于后续 DIFF 累积后的全量结果不代表 2026-09-09 本轮口径PDFBox 版本以gradle/libs.versions.toml的pdfbox 3.0.4为准未覆盖项如实登记窗口截图、加密样本的手工走查、表单/书签/签名在安装镜像内的表现、安装镜像内的 PDFBox native 字体替代路径均未测——即 DIFF-012 的「受影响范围」条目阅读代码结论时应以此为边界。六、与 Electron 版的差异与边界DIFF-012 摘要diff/012-pdfbox-import-page.md 对两版实现做了明确归档可归纳为三点维度Electronpdf-libnext-composePDFBox 3.0.4页面复制PDFDocument.load字节后copyPagesaddPageLoader.loadPDFPDDocument.importPagePDFMergerUtility页面对象复制不光栅化拆分输出源目录{小写主名}_split.pdf相同splitOutputPath同样覆盖已存在的*_split.pdf加密 PDF未支持PDFDocument.load失败以InvalidPasswordException明确失败稳定错误码encrypted差异文档同时声明边界表单/书签/签名的保留范围未做专项实测不能宣称与pdf-lib像素级一致文档书签在拆分/合并不复制ELECTRON_PARITY_OUTLINES_NOT_COPIED。选择 PDFBox 而非 iText 的理由是架构指定避免引入额外 PDF 库。七、结论这条验收记录留下的可验证链路F24 本轮验收的价值在于形成了一条完整可复现的证据链页码语义由 pageRanges.ts 定义、PdfEngine.kt 逐条复刻含too-many/encrypted/cancelled等稳定错误码与半成品清理PdfEngineTest.kt 用真实生成的 PDF 验证拆分/合并/去重/取消/结构保留./gradlew :composeApp:desktopTest87/87 收口差异与未测项由 DIFF-012 与验收记录本身登记在案。下一轮按记录推进至 P4 剩余的 F23 图片助手。赞分享桌面应用开发工具【免费下载链接】MooToolHandy tool set for developers项目地址https://gitcode.com/gh_mirrors/mo/MooTool点击查看免费下载相关推荐MooTool next-compose 的 PDF 拆分/合并引擎基于 PDFBox importPage 的真页面操作实现MooTool next compose 的 PDF 拆分/合并引擎基于 PDFBox importPage 的真页面操作实现 导读 MooTool 的 ne桌面应用开发工具Symfony Process 组件 CHANGELOG 深度解读从 Process 到 PhpSubprocess 与 Messenger 的完整演进Symfony Process 组件 CHANGELOG 深度解读从 Process 到 PhpSubprocess 与 Messenger 的完整演进 本篇桌面应用开发工具Wand-Enhancer 完整指南免费解除 WeMod 免费版 2 小时限制、启用 Pro 功能Wand Enhancer 完整指南免费解除 WeMod 免费版 2 小时限制、启用 Pro 功能 Boss 打到一半突然被掐断多半是免费版 WeMod 用桌面应用开发工具上一篇PandasAI农业农村农业农村水利区块链应用区块链应用数据分析与应用技能下一篇Rome 规则详解noInvalidConstructorSuper 如何校验 class 继承中的 super() 调用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站