该页面可以包含自动翻译的文本。

在 C# 和 VB.NET 中合并 PDF 文档

企业经常合并 PDF 文件以进行文档存档。虽然 PDF 合并听起来像是一项简单的任务,但这里有很多陷阱。您应该正确组合表单字段、书签、图层和其他 PDF 对象。您还应该避免重复的对象以获得紧凑的输出文件。

Docotic.Pdf 库 处理所有合并细节。它允许您仅用几行 C# 或 VB.NET 代码合并 PDF 文档。

合并 PDF 文档

Docotic.Pdf 附带 免费和付费许可证。您可以在 Docotic.Pdf 下载页面 下载库并获取评估许可证密钥。

Docotic.Pdf 库 9.4.17467-dev 回归测试 14,760 通过 NuGet 总下载量 4,415,970

PDF 合并基础

PdfDocument.Append 方法允许您从文件、流或字节数组中附加 PDF 文档。还有用于附加受保护文件和合并表单字段的选项。

合并两个 PDF 文件

此示例代码展示了如何在 C# 中合并 PDF 文件:

using var pdf = new PdfDocument("first.pdf");
pdf.Append("second.pdf");
pdf.Save("merged.pdf");

尝试 GitHub 中的 合并两个 PDF 文档 代码示例。

合并 PDF 流

很容易调整前面的示例以使用流而不是文件路径。以下是合并流的辅助方法:

void Merge(Stream first, Stream second, Stream result)
{
    using var pdf = new PdfDocument(first);
    pdf.Append(second);
    pdf.Save(result);
}

合并多个 PDF 文件

您可以重复调用 Append 方法来附加多个 PDF 文件:

string[] filesToMerge = ..;
using var pdf = new PdfDocument();
foreach (string file in filesToMerge)
    pdf.Append(file);

// 删除 PdfDocument() 调用添加的空白页
pdf.RemovePage(0);

pdf.Save(pathToFile);

合并加密 PDF 文件

有用于合并加密文档的 Append 重载:

using var pdf = new PdfDocument();
pdf.Append("encrypted.pdf", new PdfStandardDecryptionHandler("password"));
pdf.Save("merged.pdf");

您可以在 使用 C# 和 VB.NET 解密 PDF 文档 文章中找到更多信息。

合并 PDF 表单

PDF 文档中的每个表单字段都必须具有唯一的名称。当要合并的文档包含具有相同名称的字段时,这可能会导致问题。Docotic.Pdf 为冲突的表单控件提供了以下合并策略:

  • 当附加控件与现有控件发生冲突时重命名它们
  • 将附加控件合并到现有控件
  • 展平附加控件
  • 不附加任何控件
  • 按原样附加控件

默认情况下,库会在发生冲突时重命名附加的控件。您可以使用 PdfMergingOptions 类选择替代策略:

using var pdf = new PdfDocument("form.pdf");

var decryptionHandler = new PdfStandardDecryptionHandler(string.Empty);
var mergingOptions = new PdfMergingOptions()
{
    ControlMergingMode = PdfControlMergingMode.CopyAsKids
};
pdf.Append("form.pdf", decryptionHandler, mergingOptions);

pdf.Save("merged.pdf");

使用 CopyAsKids 模式,库会合并并同步冲突的控件。 即,当您更改一个控件时,第二个控件将具有相同的值。

减少合并的 PDF 文件

PDF 文档可能包含相同的对象,如字体或图像。合并此类文档时,生成的文档将包含相同对象的副本。使用 PdfDocument.ReplaceDuplicateObjects() 方法优化合并结果:

using var pdf = new PdfDocument("2024-05-28.pdf");
pdf.Append("2024-05-29.pdf");

pdf.ReplaceDuplicateObjects();

pdf.Save("merged.pdf");

您可以进一步减小输出文件的大小。例如,您可以删除未使用的字体字形或压缩图像。在 使用 C# 和 VB.NET 压缩 PDF 文档 文章中阅读有关支持的压缩选项的信息。

自定义 PDF 合并

Docotic.Pdf 提供了提取、重新排序或删除 PDF 页面的方法。您可以将它们与 Append 方法一起使用来实现自定义 PDF 合并任务。

附加特定 PDF 页面

Docotic.Pdf 还允许您合并 PDF 文档的一部分。有多种方法可以做到这一点。例如,您可以 拆分添加的 PDF 文档并附加提取的页面。以下 C# 帮助程序将选定的页面附加到 PdfDocument

private static void AppendPart(PdfDocument pdf, string filePath, params int[] pagesToAppend)
{
    using var streamToAppend = new MemoryStream();
    using var other = new PdfDocument(filePath);
    using var extracted = other.CopyPages(pagesToAppend);
    var options = new PdfSaveOptions
    {
        UseObjectStreams = false
    };
    extracted.Save(streamToAppend, options);

    pdf.Append(streamToAppend);
}

或者,您可以附加整个 PDF 文档并删除不必要的页面。以下代码示例附加了 second.pdf 的前两页:

using var pdf = new PdfDocument(@"first.pdf");

int pageCountBefore = pdf.PageCount;
pdf.Append(@"second.pdf");
pdf.RemovePages(pageCountBefore + 2);

pdf.Save(pathToFile);

另一个解决方案与 PDF 拼版有关。您可以在 相应部分 中阅读相关内容。

附加 PDF

Append 方法始终将页面附加到当前文档的末尾。如何以不同 的顺序合并 PDF 文件?有时您可以更改 Append 调用的顺序。即使用

pdf.Append("first.pdf");
pdf.Append("second.pdf");

而不是

pdf.Append("second.pdf");
pdf.Append("first.pdf");

或者,您可以在合并后重新排序页面。此 C# 代码将附加的 PDF 文档移至开头:

using var pdf = new PdfDocument(@"second.pdf");

int pageCountBefore = pdf.PageCount;
pdf.Append(@"first.pdf");
pdf.MovePages(pageCountBefore, pdf.PageCount - pageCountBefore, 0);

pdf.Save(pathToFile);

重新排序 PDF 页面的相关代码示例:

合并 PDF

Docotic.Pdf 允许您将多个 PDF 页面合并到单个页面上。使用 PdfDocument.CreateXObject(PdfPage)方 法基于现有页面创建 PdfXObject 对象。然后,以所需的缩放比例绘 制此对象。示例代码:

using var src = new PdfDocument(@"src.pdf");
using var dest = new PdfDocument();
PdfXObject firstXObject = dest.CreateXObject(src.Pages[0]);
PdfXObject secondXObject = dest.CreateXObject(src.Pages[1]);

PdfPage page = dest.Pages[0];
page.Orientation = PdfPaperOrientation.Landscape;
double halfOfPage = page.Width / 2;
page.Canvas.DrawXObject(firstXObject, 0, 0, halfOfPage, page.Height, 0);
page.Canvas.DrawXObject(secondXObject, halfOfPage, 0, halfOfPage, page.Height, 0);

dest.Save("result.pdf");

测试来自 GitHub 的相关 从页面创建 XObject 示例项目。

合并为附件

有时,您可能需要将 PDF 文件作为附件嵌入到另一个 PDF 文件中。这也是可能的。您还可以在 PDF 页面上添加嵌入文件的链接:

using var pdf = new PdfDocument();

PdfFileSpecification first = pdf.CreateFileAttachment("first.pdf");
pdf.SharedAttachments.Add(first);

var bounds = new PdfRectangle(20, 70, 100, 100);
PdfFileSpecification fs = pdf.CreateFileAttachment("second.pdf");
pdf.Pages[0].AddFileAnnotation(bounds, fs);

pdf.Save("attachments.pdf");

您可以在 GitHub 上的 PDF 附件 组中找到相关代码示例。

在并行线程中合并

合并多个 PDF 文件时,可以并行化代码。PdfDocument 类不是线程 安全的。因此,我们需要在不同的线程中使用单独的 PdfDocument 对象。查看 在并行线程中合并 PDF 文档 代码示例以了解更多详细信息。

此代码显示了如何并行合并 PDF 流:

Stream[] documentsToMerge = ..;

int rangeSize = 50;
while (documentsToMerge.Length > rangeSize)
{
    int partitionCount = (int)Math.Ceiling(documentsToMerge.Length / (double)rangeSize);
    var result = new Stream[partitionCount];

    var partitioner = Partitioner.Create(0, documentsToMerge.Length, rangeSize);
    Parallel.ForEach(partitioner, range =>
    {
        int startIndex = range.Item1;
        int count = range.Item2 - range.Item1;
        result[startIndex / rangeSize] = MergeToStream(documentsToMerge, startIndex, count);
    });
    documentsToMerge = result;
}

using PdfDocument final = GetMergedDocument(documentsToMerge, 0, documentsToMerge.Length);
final.Save("merged.pdf");


private static Stream MergeToStream(Stream[] streams, int startIndex, int count)
{
    using PdfDocument pdf = GetMergedDocument(streams, startIndex, count);

    var result = new MemoryStream();

    var options = new PdfSaveOptions
    {
        UseObjectStreams = false // 加快中间文档的写入速度
    };
    pdf.Save(result, options);
    return result;
}

private static PdfDocument GetMergedDocument(Stream[] streams, int startIndex, int count)
{
    var pdf = new PdfDocument();
    try
    {
        for (int i = 0; i < count; ++i)
        {
            var s = streams[startIndex + i];
            pdf.Append(s);
            s.Dispose();
        }

        pdf.RemovePage(0);

        pdf.ReplaceDuplicateObjects();

        return pdf;
    }
    catch
    {
        pdf.Dispose();
        throw;
    }
}

上述代码将输入文档拆分为 rangeSize 大小的组。然后,代码将每个组并行合并为中间文档。该过程持续进行,直到输入文档的数量足够小,可以进行简单合并。

并行解决方案不一定比单线程版本更快。结果可能因输入文档的数量及其大小而异。在示例代码中,rangeSize 参数的最佳值可能更大或更小。您应该对您的应用程序进行基准测试以找到最有效的实现。

结论

您可以使用 Docotic.Pdf 库 在 C# 和 VB.NET 中合并 PDF 文档。它允许您合并文件、流或字节数组。您可以合并加密文件、PDF 表单、特定 PDF 页面。Docotic.Pdf 还可以帮助您压缩生成的文件并节省磁盘空间。

尝试使用 GitHub 上 Docotic.Pdf 示例存储库 中的代码示例。您可以在 Docotic.Pdf 下载页面 获取评估许可证密钥并下载库。