You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 06:17:32