pdf - 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);
}
解决方案
您的代码中存在许多问题。我将首先为您提供一个工作版本,然后讨论您代码中的问题。
一个工作版本(有一个重要的限制)
您可以组合实例中给出的两个 PDF MemoryStream
,masterStream
并componentStream
在同一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))
在这里,您写入masterStream
to的内容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;
...
推荐阅读
- laravel - 如何使用 Laravel Eloquent 获取相关城市的国家名称
- java - 如何使用 jdbc 将我的移动应用程序连接到域中的 mysql 数据库?
- django - 如何在实例化期间获取 CharField() 的值?
- jquery - 具有动态时隙的日期时间选择器
- javascript - 从本地(Angular)的响应标头中删除 Content-Security-Policy
- php - PHP `glob()` 多个前缀
- python - 无法读取图像特征 - Python 路径错误
- c - 如何在我自己的操作系统中从裸 C(无库)读取/写入软盘驱动器或 ATA 驱动器?
- c++ - 在 C++ 中重新定义 C 函数
- python - pd.to_datetime 产生“仅对唯一值索引对象有效的重新索引”