首页 > 解决方案 > iTextSharp v5 - 如何在内存中连接 PDF?

问题描述

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

我有下面的代码,但没有任何内容复制到我的masterStream. 我认为问题出在CopyPagesTo,但我不够熟悉,文档/示例也很难找到。

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

标签: pdfitextitext7

解决方案


您的代码中存在许多问题。我将首先为您提供一个工作版本,然后讨论您代码中的问题。

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

您可以组合实例中给出的两个 PDF MemoryStreammasterStreamcomponentStream在同一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有许多构造函数,其中一些创建这样的可扩展实例,而其他创建不可调整大小的实例。详情请阅读此处

您的概念和代码中的问题

连接 PDF 文件不会生成有效的合并 PDF

你这样描述你的概念

这个想法是,随着每个组件 PDF 的构建,组件 PDF 的字节被添加到主流中

但是,这不起作用,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))

在这里,您写入masterStreamto的内容masterMemoryStream,然后将masterMemoryStream位置设置为 start 并实例化PdfWriter开始写入的 a 。即masterStream内容的原始副本被覆盖,肯定不是你想要的。

使用MemoryStream.GetBuffer

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

updated = masterMemoryStream.GetBuffer();

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

附言

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

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

推荐阅读