【问题标题】:iTextSharp v5 - How do you concatenate PDFs in memory?iTextSharp v5 - 如何在内存中连接 PDF?
【发布时间】:2020-03-06 15:59:02
【问题描述】:

我在合并内存中的 PDF 时遇到问题。我有 2 个内存流,一个 ma​​stercomponent 流,其想法是在构建每个组件 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);
}

【问题讨论】:

    标签: pdf itext itext7


    【解决方案1】:

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

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

    您可以合并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 类有许多构造函数,只有其中一些创建了这样的可扩展实例,而其他创建了 non-resizable 实例。详情请阅读here

    你的概念和代码中的问题

    连接 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))
    

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

    使用MemoryStream.GetBuffer

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

    updated = masterMemoryStream.GetBuffer();
    

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

    PS

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

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

    【讨论】:

    • 难以置信,您在回答中提供了如此详细的信息,谢谢。我对设置 PdfReader/Writers 所需的位置和顺序感到困惑,因此感谢您为我指明正确的方向。您有关 PDF 唯一 ID 和 .GetBuffer 的其他信息也很有帮助。对于您的代码,我发现我需要明确设置 Length (设置容量还不够),最后不需要设置 masterStream.Position = 0; 。除了这个细节,谢谢。
    • “我发现我需要明确设置长度(设置容量还不够)” - 这很有趣,因为我在发布之前测试了上面的代码,它确实有效。 “设置 masterStream.Position = 0; 没有必要” - 这很有可能,我非常慷慨地添加了位置设置线。
    • 它最初也对我有用,但后来我发现了一个特定的 PDF,它试图将容量设置为小于长度的值。
    • 啊,好的。我假设由于合并只会增加流,但不同的 PDF 生产者可能会以不同的方式压缩,从而导致问题......
    • 糟糕。已更正。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-11
    • 1970-01-01
    • 2016-05-20
    相关资源
    最近更新 更多