Невозможно объединить 2 PDF файла с помощью MemoryStream

У меня есть класс С#, который берет HTML и конвертирует его в PDF с помощью wkhtmltopdf.
Как вы увидите ниже, я создаю 3 файла PDF - альбомную, книжную и комбинированную из двух.

Объект properties содержит HTML в виде строки и аргумент для ландшафта/портрета.

System.IO.MemoryStream PDF = new WkHtmlToPdfConverter().GetPdfStream(properties);
System.IO.FileStream file = new System.IO.FileStream("abc_landscape.pdf", System.IO.FileMode.Create);
PDF.Position = 0;

properties.IsHorizontalOrientation = false;
System.IO.MemoryStream PDF_portrait = new WkHtmlToPdfConverter().GetPdfStream(properties);
System.IO.FileStream file_portrait = new System.IO.FileStream("abc_portrait.pdf", System.IO.FileMode.Create);
PDF_portrait.Position = 0;

System.IO.MemoryStream finalStream = new System.IO.MemoryStream();
PDF.CopyTo(finalStream);
PDF_portrait.CopyTo(finalStream);
System.IO.FileStream file_combined = new System.IO.FileStream("abc_combined.pdf", System.IO.FileMode.Create);

try
{
    PDF.WriteTo(file);
    PDF.Flush();

    PDF_portrait.WriteTo(file_portrait);
    PDF_portrait.Flush();

    finalStream.WriteTo(file_combined);
    finalStream.Flush();
}
catch (Exception)
{
    throw;
}
finally
{
    PDF.Close();
    file.Close();

    PDF_portrait.Close();
    file_portrait.Close();

    finalStream.Close();
    file_combined.Close();
}

PDF файлы "abc_landscape.pdf" и "abc_portrait.pdf" генерируются правильно, как и ожидалось, но операция завершается неудачно, когда я пытаюсь объединить их в третий pdf (abc_combined.pdf).

Я использую MemoryStream для предварительного слияния, и во время отладки я вижу, что finalStream.length равен сумме двух предыдущих PDF файлов. Но когда я пытаюсь открыть PDF файл, я вижу содержимое только одного из двух PDF файлов.
То же самое можно увидеть ниже: PDF sizes

Кроме того, когда я пытаюсь закрыть файл "abc_combined.pdf", мне предлагается сохранить его, чего не происходит с двумя другими PDF файлами. Saving prompt

Ниже приведены некоторые вещи, которые я уже опробовал, но безрезультатно:

  1. Измените CopyTo() на WriteTo()
  2. Объедините один и тот же PDF файл (альбомный или книжный) с самим собой

    В случае, если это требуется, ниже приведена разработка метода GetPdfStream().
var htmlStream = new MemoryStream();
var writer = new StreamWriter(htmlStream);
writer.Write(htmlString);
writer.Flush();
htmlStream.Position = 0;
return htmlStream;

Process process = Process.Start(psi);
process.EnableRaisingEvents = true;
try
{
    process.Start();
    process.BeginErrorReadLine();

    var inputTask = Task.Run(() =>
    {
        htmlStream.CopyTo(process.StandardInput.BaseStream);
        process.StandardInput.Close();
    });

    // Copy the output to a memorystream
    MemoryStream pdf = new MemoryStream();
    var outputTask = Task.Run(() =>
    {
        process.StandardOutput.BaseStream.CopyTo(pdf);
    });

    Task.WaitAll(inputTask, outputTask);

    process.WaitForExit();

    // Reset memorystream read position
    pdf.Position = 0;

    return pdf;
}
catch (Exception ex)
{
    throw ex;
}
finally
{
    process.Dispose();
}

Ответы

Ответ 1

PDF файлы - это не просто текст и изображения. За кулисами существует строгий формат файла, который описывает такие вещи, как версия PDF, объекты, содержащиеся в файле и где их найти.

Чтобы объединить 2 PDF файла, вам нужно будет управлять потоками.

Сначала вам нужно сохранить заголовок только из одного файла. Это довольно просто, так как это только первая строка.

Затем вы можете написать тело первой страницы, а затем вторую.

Теперь сложная часть, и, вероятно, та, которая убедит вас использовать библиотеку, состоит в том, что вам нужно пересобрать таблицу внешних ссылок. Таблица внешних ссылок - это таблица перекрестных ссылок, которая описывает содержимое документа и, что более важно, где найти каждый элемент. Вам нужно будет вычислить смещение в байтах второй страницы, на столько сдвинуть все элементы в этой таблице внешних ссылок, а затем добавить эту таблицу внешних ссылок в первую. Вам также необходимо убедиться, что вы создаете объекты в таблице внешних ссылок для разрыва страницы.

После этого вам нужно пересобрать трейлер документа, который сообщает приложению, где находятся различные разделы документа.

Смотрите https://resources.infosecinstitute.com/pdf-file-format-basic-structure/

Это не тривиально, и в итоге вы переписываете много кода, который уже существует.

Ответ 2

Слияние pdf в С# или любом другом языке не является прямым без использования сторонней библиотеки.

Я предполагаю, что ваше требование не использовать библиотеку состоит в том, что большинство бесплатных библиотек, пакетов nuget имеют ограничения и/или стоят денег для коммерческого использования.

Я провел исследование и обнаружил, что у вас есть библиотека с открытым исходным кодом PdfClown с пакетом nuget, она также доступна для Java. Это бесплатно без ограничений (пожертвуйте, если хотите). Библиотека имеет множество функций. Один такой вы можете объединить 2 или более документов в один документ.

Я привожу свой пример, который берет папку с несколькими файлами PDF, объединяет ее и сохраняет в той же или другой папке. Также возможно использовать MemoryStream, но я не считаю это необходимым в этом случае.

Код самоочевиден, ключевым моментом здесь является использование SerializationModeEnum.Incremental:

public static void MergePdf(string srcPath, string destFile)
{
    var list = Directory.GetFiles(Path.GetFullPath(srcPath));
    if (string.IsNullOrWhiteSpace(srcPath) || string.IsNullOrWhiteSpace(destFile) || list.Length <= 1)
        return;
    var files = list.Select(File.ReadAllBytes).ToList();
    using (var dest = new org.pdfclown.files.File(new org.pdfclown.bytes.Buffer(files[0])))
    {
        var document = dest.Document;
        var builder = new org.pdfclown.tools.PageManager(document);
        foreach (var file in files.Skip(1))
        {
            using (var src = new org.pdfclown.files.File(new org.pdfclown.bytes.Buffer(file)))
            { builder.Add(src.Document); }
        }

        dest.Save(destFile, SerializationModeEnum.Incremental);
    }
}

Чтобы проверить это

var srcPath = @"C:\temp\pdf\input";
var destFile = @"c:\temp\pdf\output\merged.pdf";
MergePdf(srcPath, destFile);

Примеры ввода
PDF документ A и PDF документ B

PDF doc A and PDF doc B

Пример вывода

Merged

Ссылки на мои исследования:

Отказ от ответственности: часть этого ответа взята с моего личного веб-сайта https://itbackyard.com/merge-multiple-pdf-files-to-one-pdf-file-in-c/ с исходным кодом на github.

Ответ 3

Этот ответ от Qaru (Объединить два (или более) PDF & # 39; s) Эндрю Бернса работает для меня:

        using (PdfDocument one = PdfReader.Open("pdf 1.pdf", PdfDocumentOpenMode.Import))
        using (PdfDocument two = PdfReader.Open("pdf 2.pdf", PdfDocumentOpenMode.Import))
        using (PdfDocument outPdf = new PdfDocument())
        {
            CopyPages(one, outPdf);
            CopyPages(two, outPdf);

            outPdf.Save("file1and2.pdf");
        }

        void CopyPages(PdfDocument from, PdfDocument to)
        {
            for (int i = 0; i < from.PageCount; i++)
            {
                to.AddPage(from.Pages[i]);
            }
        }

Ответ 4

Это не совсем то, как работают PDF файлы. PDF файлы - это структурированные файлы в определенном формате. Вы не можете просто добавить байты одного к другому и ожидать, что результат будет действительным документом.

Вам понадобится использовать библиотеку, которая понимает формат и может выполнить операцию за вас, или разрабатывать собственное решение.