Apache PDFBox合并普通PDF与Adobe PDF Portfolio仅合并首文件的解决方法
解决方案:合并普通PDF与PDF Portfolio的所有嵌入式文件
问题原因
Apache PDFBox 3.0.1的命令行merge工具仅会读取PDF Portfolio的主文档(首个显示的文件),不会自动遍历并合并Portfolio中嵌入的所有PDF文件,这就是你只得到部分内容的原因。命令行工具没有内置解析Portfolio嵌入式资源的逻辑,要实现需求需要通过自定义Java代码来处理。
方案一:使用Apache PDFBox 3.0.1自定义代码实现
以下代码可以直接解析PDF Portfolio中的所有嵌入式PDF,无需提前提取,再与普通PDF合并:
import org.apache.pdfbox.Loader; import org.apache.pdfbox.multipdf.MergerUtility; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary; import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; import org.apache.pdfbox.pdmodel.common.PDNameTreeNode; import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; import java.io.File; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; public class PortfolioMerger { public static void main(String[] args) throws Exception { // 输入文件路径 String portfolioPath = "C:\\Users\\user\\Downloads\\pdf_portfolio.pdf"; String normalPdfPath = "C:\\Users\\user\\Downloads\\normal_pdf.pdf"; String outputPath = "C:\\Users\\user\\Downloads\\new.pdf"; MergerUtility merger = new MergerUtility(); List<PDDocument> docsToClose = new ArrayList<>(); try { // 添加普通PDF PDDocument normalDoc = Loader.loadPDF(new File(normalPdfPath)); merger.addDocument(normalDoc); docsToClose.add(normalDoc); // 解析PDF Portfolio,提取所有嵌入式PDF PDDocument portfolioDoc = Loader.loadPDF(new File(portfolioPath)); PDDocumentNameDictionary nameDict = portfolioDoc.getDocumentCatalog().getNames(); PDEmbeddedFilesNameTreeNode embeddedFiles = nameDict.getEmbeddedFiles(); if (embeddedFiles != null) { // 遍历所有嵌入式文件节点 for (PDNameTreeNode<PDComplexFileSpecification> node : embeddedFiles.getKids()) { processEmbeddedFiles(node, merger, docsToClose); } // 处理根节点的嵌入式文件 processEmbeddedFiles(embeddedFiles, merger, docsToClose); } // 执行合并并输出 merger.setDestinationFileName(outputPath); merger.mergeDocuments(null); } finally { // 关闭所有打开的文档 for (PDDocument doc : docsToClose) { if (doc != null) { doc.close(); } } } } private static void processEmbeddedFiles(PDNameTreeNode<PDComplexFileSpecification> node, MergerUtility merger, List<PDDocument> docsToClose) throws Exception { Map<String, PDComplexFileSpecification> files = node.getNames(); if (files != null) { for (PDComplexFileSpecification fileSpec : files.values()) { PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile(); // 判断是否为PDF文件 if (embeddedFile != null && "application/pdf".equals(embeddedFile.getSubtype())) { try (InputStream is = embeddedFile.createInputStream()) { PDDocument embeddedDoc = Loader.loadPDF(is); merger.addDocument(embeddedDoc); docsToClose.add(embeddedDoc); } } } } } }
注意事项
- 确保项目中引入PDFBox 3.0.1的依赖(Maven坐标:
org.apache.pdfbox:pdfbox:3.0.1) - 代码会过滤出所有MIME类型为
application/pdf的嵌入式文件,避免合并非PDF资源
方案二:使用iText 7实现
如果可以换用其他Java工具,iText 7同样支持解析PDF Portfolio并合并所有嵌入式PDF,示例代码如下:
import com.itextpdf.kernel.pdf.PdfDocument; import com.itextpdf.kernel.pdf.PdfReader; import com.itextpdf.kernel.pdf.PdfWriter; import com.itextpdf.kernel.pdf.filespec.PdfFileSpec; import com.itextpdf.kernel.pdf.names.PdfName; import com.itextpdf.merge.PdfMerger; import java.io.File; import java.util.Map; public class ITextPortfolioMerger { public static void main(String[] args) throws Exception { String portfolioPath = "C:\\Users\\user\\Downloads\\pdf_portfolio.pdf"; String normalPdfPath = "C:\\Users\\user\\Downloads\\normal_pdf.pdf"; String outputPath = "C:\\Users\\user\\Downloads\\new.pdf"; try (PdfWriter writer = new PdfWriter(outputPath); PdfDocument mergedDoc = new PdfDocument(writer); PdfDocument normalDoc = new PdfDocument(new PdfReader(normalPdfPath))) { PdfMerger merger = new PdfMerger(mergedDoc); // 合并普通PDF merger.merge(normalDoc, 1, normalDoc.getNumberOfPages()); // 解析PDF Portfolio try (PdfDocument portfolioDoc = new PdfDocument(new PdfReader(portfolioPath))) { // 获取嵌入式文件字典 Map<String, PdfFileSpec> embeddedFiles = portfolioDoc.getCatalog().getNameTree(PdfName.EmbeddedFiles).getNames(); if (embeddedFiles != null) { for (PdfFileSpec fileSpec : embeddedFiles.values()) { // 判断是否为PDF文件 if (fileSpec != null && "application/pdf".equals(fileSpec.getSubtype())) { try (PdfDocument embeddedDoc = new PdfDocument(new PdfReader(fileSpec.getEmbeddedFile().getBytes()))) { merger.merge(embeddedDoc, 1, embeddedDoc.getNumberOfPages()); } } } } } } } }
注意事项
- 引入iText 7的核心依赖(Maven坐标:
com.itextpdf:itext7-core:7.2.5,可使用最新稳定版) - 代码会自动处理嵌入式PDF的字节流,无需提前提取到本地
内容的提问来源于stack exchange,提问作者Jonas Baur
相关产品推荐
相关产品推荐

