Эта страница может содержать автоматически переведенный текст.

Объединение PDF-документов в C# и VB.NET

Бизнес часто объединяет PDF-файлы для архивирования документов. Хотя объединение PDF может показаться простой задачей, здесь есть много подводных камней. Нужно правильно объединять поля форм, закладки, слои и другие объекты PDF. Также следует избегать дублирования объектов, чтобы получить компактный выходной файл.

Библиотека Docotic.Pdf обрабатывает все нюансы объединения. Она позволяет объединять PDF-документы всего в несколько строк кода C# или VB.NET.

Объединение PDF-документов

Docotic.Pdf поставляется с бесплатными и платными лицензиями. Получите библиотеку и бесплатный временный ключ лицензии на странице Скачать C# .NET библиотеку PDF.

Основы объединения PDF

Методы PdfDocument.Append позволяют добавлять PDF-документы из файлов, потоков или массивов байтов. Также есть параметры для добавления защищенных файлов и объединения полей форм.

Объединение двух PDF-файлов

Этот пример кода показывает, как объединить PDF-файлы в C#:

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

Попробуйте пример кода Объединить два PDF документа на GitHub.

Объединение потоков 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");

Дополнительную информацию можно найти в статье Расшифровка PDF-документов в C# и VB.NET.

Объединение 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");

Можно еще сильнее уменьшить размер выходного файла. Например, можно удалить неиспользуемые глифы шрифтов или сжать изображения. О поддерживаемых параметрах сжатия читайте в статье Сжатие PDF-документов в C# и VB.NET.

Настройка объединения 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");

Проверьте связанный пример проекта Создать XObject со страницы на GitHub.

Объединение в качестве вложения

Иногда может понадобиться встроить один 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");

Связанные примеры кода можно найти в группе Вложения PDF на GitHub.

Объединение в параллельных потоках

При объединении многих 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 для объединения PDF-документов в C# и VB.NET. Она позволяет объединять файлы, потоки или массивы байтов. Можно объединять зашифрованные файлы, PDF-формы и отдельные страницы PDF. Docotic.Pdf также помогает сжимать итоговые файлы и экономить место на диске.

Попробуйте примеры кода из репозитория примеров Docotic.Pdf на GitHub. Можно получить ключ оценочной лицензии и скачать библиотеку на странице загрузки Docotic.Pdf.