iTextSharp v5 - 如何在内存中连接 PDF?

iTextSharp v5 - How do you concatenate PDFs in memory?

我在内存中合并 PDF 时遇到问题。我有 2 个内存流,一个 mastercomponent 流,想法是随着每个组件 PDF 的构建,添加组件 PDF 的字节到主流。在所有组件的最后,我们有一个 PDF 格式的字节数组。

我有下面的代码,但没有任何内容复制到我的 masterStream 中。我认为问题出在 CopyPagesTo,但我不够熟悉,documentation/examples 很难找到。

byte[] updated;
using (MemoryStream masterMemoryStream = new MemoryStream())
{
    masterStream.WriteTo(masterMemoryStream);

    // Read from master stream (ie. all existing components)
    masterMemoryStream.Position = 0;
    using (iText.Kernel.Pdf.PdfWriter masterPdfWriter = new iText.Kernel.Pdf.PdfWriter(masterMemoryStream))
    using (iText.Kernel.Pdf.PdfDocument masterPdfDocument = new iText.Kernel.Pdf.PdfDocument(masterPdfWriter))
    {


        using (MemoryStream componentMemoryStream = new MemoryStream())
        {
            componentStream.WriteTo(componentMemoryStream);

            // Read from new component
            componentMemoryStream.Position = 0;
            using (iText.Kernel.Pdf.PdfReader componentPdfReader = new iText.Kernel.Pdf.PdfReader(componentMemoryStream))
            using (iText.Kernel.Pdf.PdfDocument componentPdfDocument = new iText.Kernel.Pdf.PdfDocument(componentPdfReader))
            {
                // Copy pages from component into master
                componentPdfDocument.CopyPagesTo(1, componentPdfDocument.GetNumberOfPages(), masterPdfDocument);
            }
        }
    }

    updated = masterMemoryStream.GetBuffer();
}

// Write updates to master stream?
masterStream.SetLength(0);
using (MemoryStream temp = new MemoryStream(updated))
    temp.WriteTo(masterStream);

回答

这是 mkl 的回答,我做了一些更正:

using (MemoryStream temporaryStream = new MemoryStream())
{
    masterStream.Position = 0;
    componentStream.Position = 0;
    using (PdfDocument combinedDocument = new PdfDocument(new PdfReader(masterStream), new PdfWriter(temporaryStream)))
    using (PdfDocument componentDocument = new PdfDocument(new PdfReader(componentStream)))
    {
        componentDocument.CopyPagesTo(1, componentDocument.GetNumberOfPages(), combinedDocument);
    }
    byte[] temporaryBytes = temporaryStream.ToArray();
    masterStream.Position = 0;
    masterStream.SetLength(temporaryBytes.Length);
    masterStream.Capacity = temporaryBytes.Length;
    masterStream.Write(temporaryBytes, 0, temporaryBytes.Length);
}

您的代码中存在许多问题。我会先给你一个工作版本,然后再进入你的代码中的问题。

一个工作版本(有一个重要的限制)

您可以组合 MemoryStream 实例 masterStreamcomponentStream 中给出的两个 PDF,并在相同的 MemoryStream 实例 masterStream 中获得结果,如下所示:

using (MemoryStream temporaryStream = new MemoryStream())
{
    masterStream.Position = 0;
    componentStream.Position = 0;
    using (PdfDocument combinedDocument = new PdfDocument(new PdfReader(masterStream), new PdfWriter(temporaryStream)))
    using (PdfDocument componentDocument = new PdfDocument(new PdfReader(componentStream)))
    {
        componentDocument.CopyPagesTo(1, componentDocument.GetNumberOfPages(), combinedDocument);
    }
    byte[] temporaryBytes = temporaryStream.ToArray();
    masterStream.Position = 0;
    masterStream.Capacity = temporaryBytes.Length;
    masterStream.Write(temporaryBytes, 0, temporaryBytes.Length);
    masterStream.Position = 0;
}

限制是您必须实例化masterStream具有可扩展容量; MemoryStream class 有许多构造函数,其中只有一些构造函数创建这样的可扩展实例,而其他构造函数创建 不可调整大小的 实例。有关详细信息,请阅读 here

你的概念和代码有问题

串联 PDF 文件不会生成有效的合并 PDF

你这样描述你的概念

the idea is that as each component PDF is built up, the component PDF's bytes are added to the master stream

这不起作用,但是 PDF 格式不允许通过简单地连接它们来合并 PDF。特别是 PDF 中的(活动)对象具有标识符号,该标识符号在 PDF 中必须是唯一的,连接将导致文件具有非唯一的对象标识符; PDF 包含交叉引用结构,该结构将每个对象标识符映射到其从文件开始的偏移量,对于添加的 PDF,连接会使所有这些偏移量出错;此外,PDF 必须有一个根对象,从中直接或间接引用其他对象,连接将导致多个根对象。

写入并立即覆盖

在你的代码中你有

masterStream.WriteTo(masterMemoryStream);

// Read from master stream (ie. all existing components)
masterMemoryStream.Position = 0;
using (iText.Kernel.Pdf.PdfWriter masterPdfWriter = new iText.Kernel.Pdf.PdfWriter(masterMemoryStream))

此处将masterStream的内容写入masterMemoryStream,然后将masterMemoryStream位置设置为开始,实例化一个PdfWriter开始写入。 IE。 masterStream 内容的原始副本被覆盖,这肯定不是您想要的。

使用MemoryStream.GetBuffer

MemoryStream.GetBuffer 不只是 return 设计写入 MemoryStream 的数据,而是整个缓冲区;即,您在此处检索到的实际 PDF 之后可能有很多垃圾字节

updated = masterMemoryStream.GetBuffer();

这可能会导致 PDF 处理器尝试处理您的结果 PDF 无法打开文件:PDF 的末尾有一个指向最后交叉引用的指针,因此如果您的 PDF 的实际末尾后面有垃圾字节, PDF 处理器可能找不到该指针。

PS

如评论中所述,上面的代码在流长度不断增加的情况下工作正常(这通常会在手头的用例中发生)但通常需要在编写新的之前限制流的大小内容,例如像这样:

...
masterStream.Position = 0;
masterStream.SetLength(temporaryBytes.Length); // <<<<
masterStream.Capacity = temporaryBytes.Length;
...