整理项目资料或生成报告时,经常需要将多个 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) 加载文档,再根据需要合并其中的页面。