Java 合并 PDF 文件:完整合并、指定页面合并与流合并

整理项目资料或生成报告时,经常需要将多个 PDF 文件合并成一个。有时需要保留所有页面,有时只需要提取不同文件中的指定页面。如果 PDF 数据来自内存或网络,也可能需要直接通过流进行合并。

本文将介绍如何使用 Java 实现三种 PDF 合并方式:合并完整文档、合并多个 PDF 的指定页面,以及通过输入流合并 PDF。

一、添加 Maven 依赖

首先,在 Java 项目的 ​​pom.xml​​ 中添加以下 Maven 仓库和依赖:

xml 复制代码
<repositories>
    <repository>
        <id>com.e-iceblue</id>
        <name>e-iceblue</name>
        <url>https://repo.e-iceblue.cn/repository/maven-public/</url>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>e-iceblue</groupId>
        <artifactId>spire.pdf</artifactId>
        <version>12.9.0</version>
    </dependency>
</dependencies>

下面的示例使用三个 PDF 文件:

复制代码
pdfs/
├── 01-cover.pdf
├── 02-report.pdf
└── 03-appendix.pdf

它们分别表示封面、报告正文和附录。每个代码示例都可以独立运行,并生成不同的输出文件。

二、合并多个完整的 PDF 文件

如果需要将多个 PDF 文件的所有页面按顺序合并,可以使用 ​​PdfDocument.mergeFiles()​​ 方法。

该方法支持直接传入 PDF 文件路径数组,并将合并结果保存到指定位置。

完整代码如下:

typescript 复制代码
import com.spire.pdf.PdfDocument;

public class MergePDF {

    public static void main(String[] args) {

        // 指定需要合并的 PDF 文件
        String[] files = {
                "pdfs/01-cover.pdf",
                "pdfs/02-report.pdf",
                "pdfs/03-appendix.pdf"
        };

        // 指定输出文件路径
        String outputFile = "merged.pdf";

        // 合并 PDF 文件
        PdfDocument.mergeFiles(files, outputFile);

        System.out.println("PDF 合并完成:" + outputFile);
    }
}

这里的核心方法是:

​​PdfDocument.mergeFiles(files, outputFile)​​

它接收两个参数:

  • ​files​:包含多个 PDF 文件路径的字符串数组。
  • ​outputFile​:合并后 PDF 文件的保存路径。

程序会按照数组中的顺序合并文件。因此,生成的 ​​merged.pdf​​ 将依次包含封面、报告正文和附录的所有页面。

如果需要调整合并顺序,只需改变数组中文件路径的排列顺序,无须修改 PDF 文件本身。

这种方式适用于需要完整保留各个源文档的情况。

三、合并多个 PDF 文件中的指定页面

有时并不需要合并所有页面。例如,一份报告可能只需要封面、正文中的部分章节以及附录首页。

这种情况下,可以先加载多个 PDF 文件,再通过以下两个方法将指定页面插入到新文档中:

  • ​insertPage()​:从源 PDF 中插入指定页面。
  • ​insertPageRange()​:从源 PDF 中插入连续的页面范围。

假设现在需要合并以下内容:

源文件 需要合并的页面
01-cover.pdf 第 1 页
02-report.pdf 第 2~4 页
03-appendix.pdf 第 1 页

最终生成的 PDF 将包含 5 页,并按照上述顺序排列。

完整代码如下:

scss 复制代码
import com.spire.pdf.PdfDocument;

public class MergeSelectedPDFPages {

    public static void main(String[] args) {

        // 创建源 PDF 文档对象
        PdfDocument cover = new PdfDocument();
        PdfDocument report = new PdfDocument();
        PdfDocument appendix = new PdfDocument();

        // 创建用于保存合并结果的 PDF 文档
        PdfDocument mergedPdf = new PdfDocument();

        try {
            // 加载源 PDF 文件
            cover.loadFromFile("pdfs/01-cover.pdf");
            report.loadFromFile("pdfs/02-report.pdf");
            appendix.loadFromFile("pdfs/03-appendix.pdf");

            // 检查源 PDF 是否包含所需页面
            if (cover.getPages().getCount() < 1
                    || report.getPages().getCount() < 4
                    || appendix.getPages().getCount() < 1) {
                throw new IllegalArgumentException(
                        "源 PDF 的页数不足");
            }

            // 插入封面的第 1 页
            mergedPdf.insertPage(cover, 0);

            // 插入报告正文的第 2~4 页
            mergedPdf.insertPageRange(report, 1, 3);

            // 插入附录的第 1 页
            mergedPdf.insertPage(appendix, 0);

            // 保存合并结果
            mergedPdf.saveToFile("merged-selected.pdf");

            System.out.println("指定页面合并完成");

        } finally {
            // 释放 PDF 文档资源
            mergedPdf.close();
            cover.close();
            report.close();
            appendix.close();
        }
    }
}

这段代码需要注意两个地方。

1. PDF 页面的索引从 0 开始

在代码中,第 1 页对应索引 ​​0​​​,第 2 页对应索引 ​​1​​,依此类推。

例如:

​​mergedPdf.insertPage(cover, 0);​​

表示从 ​​cover​​ 文档中获取第 1 页,并将其添加到合并文档中。

2. insertPageRange() 用于插入连续页面

下面这行代码:

​​mergedPdf.insertPageRange(report, 1, 3);​​

表示从 ​​report​​ 文档中插入索引为 1、2、3 的页面,也就是实际的第 2~4 页。结束索引包含在范围内。

由于页面是按照方法调用的先后顺序添加的,因此可以通过调整 ​​insertPage()​​​ 和 ​​insertPageRange()​​ 的执行顺序来控制最终 PDF 的页面排列。

另外,插入的页面索引不能超过源 PDF 的实际页数。示例在执行插入操作前检查了页数,避免因页面不存在而产生异常。

如果只需要某几个不连续的页面,也可以多次调用 ​​insertPage()​​,分别指定对应的页面索引。

四、通过输入流合并 PDF 文件

除了直接通过文件路径合并 PDF,还可以使用输入流。

例如,在 Web 应用中,PDF 数据可能来自文件上传、网络请求或内存中的字节数组。此时不一定需要先将这些数据保存为临时 PDF 文件,再进行合并。

​​PdfDocument.mergeFiles()​​​ 提供了一个接收 ​​InputStream[]​​ 参数的重载方法,可以直接合并多个 PDF 输入流。

下面以 ​​FileInputStream​​ 为例,演示如何通过流合并三个 PDF 文件。

java 复制代码
import com.spire.pdf.FileFormat;
import com.spire.pdf.PdfDocument;
import com.spire.pdf.PdfDocumentBase;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class MergePDFByStream {

    public static void main(String[] args)
            throws IOException {

        // 创建三个 PDF 输入流
        try (InputStream stream1 = new FileInputStream(
                    "pdfs/01-cover.pdf");
             InputStream stream2 = new FileInputStream(
                    "pdfs/02-report.pdf");
             InputStream stream3 = new FileInputStream(
                    "pdfs/03-appendix.pdf")) {

            // 将输入流放入数组
            InputStream[] streams = {
                    stream1,
                    stream2,
                    stream3
            };

            // 合并 PDF 输入流
            PdfDocumentBase mergedPdf =
                    PdfDocument.mergeFiles(streams);

            try {
                // 保存合并后的 PDF
                mergedPdf.save(
                        "merged-stream.pdf",
                        FileFormat.PDF);

                System.out.println("PDF 流合并完成");

            } finally {
                // 释放合并后的文档资源
                mergedPdf.close();
            }
        }
    }
}

与前面的文件路径合并方式相比,这里的区别主要在于输入数据的形式。

首先,使用 ​​FileInputStream​​ 打开各个 PDF 文件,得到对应的输入流。

然后,将这些输入流放入 ​​InputStream[]​​ 数组,再调用:

​​PdfDocument.mergeFiles(streams);​​

该方法返回一个 ​​PdfDocumentBase​​ 对象,表示合并后的 PDF 文档。

最后,通过 ​​save()​​​ 方法将结果保存为 ​​merged-stream.pdf​​。

这里使用了 Java 的 try-with-resources 语法来管理输入流。无论合并成功还是发生异常,输入流都会在退出代码块时自动关闭。合并后的 PDF 对象也会在 ​​finally​​ 中释放资源。

需要注意,使用输入流合并并不意味着一定完全在内存中处理,也不代表不会产生输出文件。本例从本地文件创建输入流,并将结果保存到了磁盘。

如果输入 PDF 数据本身已经存储在内存中,也可以使用 ​​ByteArrayInputStream​​ 来创建输入流,无需先把原始数据写入临时文件。

五、合并 PDF 时需要注意的问题

1. 合并顺序由输入顺序决定

完整文件合并和流合并都按照输入数组中的顺序处理文档。

指定页面合并则按照页面插入的顺序生成结果。如果希望调整最终页面顺序,需要在合并时明确指定。

2. 不同页面尺寸可能同时存在

如果源 PDF 包含 A4、A3 或其他尺寸的页面,合并后的 PDF 也可能保留这些不同尺寸。

合并操作不会自动将所有页面转换成统一大小。如有统一页面尺寸的要求,需要额外进行处理。

3. 加密 PDF 需要正确的打开密码

如果源 PDF 设置了打开密码,直接合并可能失败。对于已知密码的 PDF,可以使用 ​​loadFromFile(filePath, password)​​ 加载文档,再根据需要合并其中的页面。

相关推荐
caoerzhong1 小时前
JeeWMS 开源 WMS 部署避坑指南:Java 仓库管理系统的环境基线、四类根因与可复现交付
java·开发语言·开源
for_ever_love__2 小时前
MySQL 全文索引实战:FULLTEXT、ngram 中文分词与 MATCH AGAINST 到底该怎么用
java·python·mysql·全文检索·分词·索引·ngram
java资料站2 小时前
五、Spring AI Alibaba · Memory · Saver(短期会话)
java·spring·microsoft
夜之眷属3 小时前
Core dump 崩溃排查:JVM 宕机后,那份 core 文件怎么用 gdb 还原现场
java·运维·服务器·jvm
波加曼大王3 小时前
# vLLM不要迷信PagedAttention神话,聊聊线上藏着的内部碎片陷阱
java·架构
SL_staff3 小时前
目标健康度自检清单:开发者视角下的目标-计划-任务链路断点诊断
java·开源·github
for_ever_love__3 小时前
MySQL 高频面试题 30 问:索引、事务、锁、日志、主从与分库分表(MySQL 8.0 版)
java·数据库·spring boot·mysql·spring cloud·面试·总结
神仙别闹3 小时前
基于C++实现的贪吃蛇游戏平台
java·c++·游戏
布吉岛的石头3 小时前
Java 程序员第 49 阶段11:Java 接 BERT:用 ONNX Runtime 做句向量推理
java·人工智能·python·深度学习·bert·transformer