如何将包含PDF文件数据的两个内存流合并为一个?

8
我将尝试将两个PDF文件读入两个内存流中,然后返回一个包含两个流数据的流。但我似乎不明白我的代码有什么问题。
示例代码:
string file1Path = "Sampl1.pdf";
string file2Path = "Sample2.pdf";
MemoryStream stream1 = new MemoryStream(File.ReadAllBytes(file1Path));
MemoryStream stream2 = new MemoryStream(File.ReadAllBytes(file2Path));
stream1.Position = 0;
stream1.Copyto(stream2);
return stream2;   /*supposed to be containing data of both stream1 and stream2 but contains data of stream1 only*/  

可能是如何合并两个内存流?的重复问题。 - Paul Zahra
可能是合并MemoryStream到一个iText文档的重复问题。 - Chris Haas
2个回答

11

看起来,在PDF文件的情况下,将内存流合并不同于txt文件。对于PDF,您需要使用一些.dll文件,例如我使用的iTextSharp.dll(可在AGPL许可下获得),然后使用该库的函数将它们组合起来,方法如下:

MemoryStream finalStream = new MemoryStream();
PdfCopyFields copy = new PdfCopyFields(finalStream);
string file1Path = "Sample1.pdf";
string file2Path = "Sample2.pdf";

var ms1 = new MemoryStream(File.ReadAllBytes(file1Path));
ms1.Position = 0;
copy.AddDocument(new PdfReader(ms1));
ms1.Dispose();

var ms2 = new MemoryStream(File.ReadAllBytes(file2Path));
ms2.Position = 0;
copy.AddDocument(new PdfReader(ms2));
ms2.Dispose();
copy.Close();

finalStream 包含了 ms1 和 ms2 合并后的 PDF。


2
当考虑合并文件时,这应该是默认选项 - 只有在它起作用时才是特例,而不是不起作用时。实际上,很少有文件格式可以通过简单地粘贴两个文件来合并。事实上,即使是文本文件也不完全适合 - 例如,如果您正在使用换行符来拆分数据(您需要在文件之间放置分隔符),或者如果它们使用带BOM的UTF-8编码(有效地给它们一个“标题”),甚至如果它们处于两种不同的编码中。 - Luaan
3
我已经更新了您的回答,将“在互联网上免费提供”更改为“在AGPL许可证下提供”。iTextSharp是受许可的软件,这意味着它只能在采用AGPL(而不是商业许可证)发布的项目中免费使用。一旦您在商业环境中使用iTextSharp,就必须购买其商业许可证来使用iTextSharp。 - Bruno Lowagie
@Luaan。你说得对。我是编程新手,这是我想到的解决方案。我认为将我的答案发布出来,对于寻找类似问题的人会有所帮助。 - ArslanIqbal
@Bruno Lowagie 谢谢。 :) - ArslanIqbal

5
注意:
整个问题都基于错误的前提,即您可以通过合并两个PDF文件的二进制文件来生成一个合并后的PDF文件。这对于纯文本文件(在某种程度上)可能有效,但绝对不适用于PDF文件。答案只回答了如何合并两个二进制数据流,而不是特别指出如何合并两个PDF文件。它回答了提问者提出的问题,但实际上并没有解决他的问题。
当您使用MemoryStreambyte[]构造函数时,内存流不会随着添加更多数据而扩展。因此,它将无法容纳stream1stream2的所有内容。此外,位置将从零开始,因此您正在用stream1中的数据覆盖stream2
修复方法相当简单:
var result = new MemoryStream();
using (var file1 = File.OpenRead(file1Path)) file1.CopyTo(result);
using (var file2 = File.OpenRead(file2Path)) file2.CopyTo(result);

另一个选择是创建自己的流类,该类将是两个单独流的组合 - 如果您对组合性感兴趣,则非常有趣,但对于这样简单的事情来说可能过于复杂 :) 只是为了好玩,它可能看起来像这样:
public class DualStream : Stream
{
    private readonly Stream _first;
    private readonly Stream _second;

    public DualStream(Stream first, Stream second)
    {
        _first = first;
        _second = second;
    }

    public override bool CanRead => true;
    public override bool CanSeek => true;
    public override bool CanWrite => false;
    public override long Length => _first.Length + _second.Length;

    public override long Position
    {
        get { return _first.Position + _second.Position; }
        set { Seek(value, SeekOrigin.Begin); }
    }

    public override void Flush() { throw new NotImplementedException(); }

    public override int Read(byte[] buffer, int offset, int count)
    {
        var bytesRead = _first.Read(buffer, offset, count);

        if (bytesRead == count) return bytesRead;

        return bytesRead + _second.Read(buffer, offset + bytesRead, count - bytesRead);
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        // To simplify, let's assume seek always works as if over one big MemoryStream
        long targetPosition;

        switch (origin)
        {
            case SeekOrigin.Begin: targetPosition = offset; break;
            case SeekOrigin.Current: targetPosition = Position + offset; break;
            case SeekOrigin.End: targetPosition = Length - offset; break;
            default: throw new NotSupportedException();
        }

        targetPosition = Math.Max(0, Math.Min(Length, targetPosition));

        var firstPosition = Math.Min(_first.Length, targetPosition);
        _first.Position = firstPosition;
        _second.Position = Math.Max(0, targetPosition - firstPosition);

        return Position;
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _first.Dispose();
            _second.Dispose();
        }

        base.Dispose(disposing);
    }

    public override void SetLength(long value) 
      { throw new NotImplementedException(); }
    public override void Write(byte[] buffer, int offset, int count) 
      { throw new NotImplementedException(); }
}

主要好处是您无需分配不必要的内存缓冲区即可拥有组合流——如果您敢于这样做,甚至可以直接使用文件流。而且它非常易于组合——您可以创建其他双重流的双重流,允许您将任意多个流链接在一起——与 IEnumerable.Concat 几乎相同。

那我该怎么办? - ArslanIqbal
不要使用byte构造函数,只需使用new MemoryStream()。 - Paul Zahra
@Arsal 是的,没问题 - 问题在于 stream1 也在位置零,所以当你开始复制时,它会覆盖 stream2 中现有的所有数据。你必须先使用 stream2.Seek(SeekOrigin.End, 0) - 然后你会收到关于无法扩展使用 byte[] 构造函数创建的内存流的错误。 - Luaan
是的,我确定。如果我注释掉file1行,它将复制file2。但是如果我取消注释,它只会复制file1的数据。文件大小是否有关系? - ArslanIqbal
1
@Arsal 你确定它被覆盖了吗?你检查过流的长度了吗?之后你是如何读取流的?这不像是你可以仅仅通过字节粘合两个PDF文件来得到一个合并的文件。尝试使用相同的代码与两个文本文件,这应该会使结果更加明显。 - Luaan
显示剩余13条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接