无法使用 MemoryStream 合并 2 个 PDF

Unable to merge 2 PDFs using MemoryStream

我有一个 c# class,它接受 HTML 并使用 wkhtmltopdf 将其转换为 PDF。
正如您将在下面看到的,我正在生成 3 个 PDF——横向、纵向以及两者的组合。

properties 对象包含作为字符串的 html,以及 landscape/portrait.

的参数
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 中的一个的内容。
同样可以在下面看到:

此外,当我尝试关闭 "abc_combined.pdf" 时,系统提示我保存它,而其他 2 个 PDF 则不会出现这种情况。

以下是我已经尝试过但无济于事的一些方法:

  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();
}

这个来自 Stack Overflow 的答案(Combine two (or more) PDF's) by Andrew Burns 对我有用:

        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]);
            }
        }

PDF 并不是这样工作的。 PDF 是特定格式的结构化文件。 您不能只是将一个的字节附加到另一个并期望结果是一个有效的文档。

您将拥有 to use a library 理解格式并可以为您执行操作,或开发您自己的解决方案。

在不使用第 3 方库的情况下,在 C# 或任何其他语言中合并 pdf 并不是直接的。

我假设您不使用库的要求是大多数免费库、nuget 包都有限制 or/and 商业用途需要花钱。

我进行了研究并为您找到了一个名为 PdfClown with nuget package, it is also available for Java. It is Free with out limitation (donate if you like). The library has a lot of features 的开源库。一个这样你可以将 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

输出示例

我的研究链接:

Disclaimer: A part of this answer is taken from my my personal web site https://itbackyard.com/merge-multiple-pdf-files-to-one-pdf-file-in-c/ with source code to github.

PDF 文件不仅仅是文本和图像。在幕后有一个严格的文件格式,它描述了 PDF 版本、文件中包含的 objects 和 在哪里可以找到它们

为了合并 2 个 PDF,您需要操作流。

首先,您需要仅保存其中一个文件的 header。这很简单,因为它只是第一行。

然后可以写第一页的body,然后是第二页

现在最困难的部分,也可能是说服您使用库的部分,是您必须 re-build 外部参照 table。外部参照 table 是一个交叉引用 table,它描述了文档的内容,更重要的是 在哪里可以找到每个元素 。您必须计算第二页的字节偏移量,将其外部参照 table 中的所有元素移动那么多,然后将其外部参照 table 添加到第一页。您还需要确保在外部参照 table 中为分页符创建 objects。

完成后,您需要 re-build 文档尾部,它告诉应用程序文档的各个部分在哪里。

https://resources.infosecinstitute.com/pdf-file-format-basic-structure/

这不是微不足道的,你最终会得到 re-writing 很多已经存在的代码。