【问题标题】:How to Extract pages from a PDF using IText 7?如何使用 IText 7 从 PDF 中提取页面?
【发布时间】:2020-06-04 05:47:22
【问题描述】:

我尝试使用 IText 7 库从 PDF 文件中提取页面以创建新文件。

static void Splitter() {
            string file = @"C:\Users\Standard\Downloads\Merged\CK 2002989 $29,514.42 02.12.20.pdf";
            string range = "1, 4, 8";
            var pdfDocumentInvoiceNumber = new PdfDocument(new PdfReader(file));
            var split = new PdfSplitter(pdfDocumentInvoiceNumber);
            var result = split.ExtractPageRange(new PageRange(range));
            var numberOfPagesPdfDocumentInvoiceNumber = result.GetNumberOfPages();
            String toFile = @"C:\Users\Standard\Downloads\Result\Extracted.pdf";
            var pdfWriter = new PdfWriter(toFile);
            var pdfDocumentInvoiceMergeResult = new PdfDocument(pdfWriter);
           for (var i = 1; i <= numberOfPagesPdfDocumentInvoiceNumber; i++)
            { 
                var pdfPage = result.GetPage(i).CopyTo(pdfDocumentInvoiceMergeResult);
                pdfDocumentInvoiceMergeResult.AddPage(pdfPage);

                }

但是当我尝试使用 CopyTo 时出现错误

iText.Kernel.PdfException: 'Cannot copy indirect object from the document that is being written.'

【问题讨论】:

  • 问题从错误信息中很清楚,并解释了herehere无法从写入的文档中复制页面的限制是由于iText架构:当一个文档写入后,iText 会尝试尽快将此新内容推送到PdfWriter 输出流中,然后忘记它。这允许 iText 轻松生成大型结果 PDF,而无需大量内存。缺点是您面临的限制。
  • 另一个原因是文档中的某些结构在文档关闭之前没有最终确定,例如子集嵌入字体。
  • 那么,我如何才能在这种限制下正确使用方法 ExtractPageRange() 呢?

标签: c# pdf itext7


【解决方案1】:

这里的问题是PdfSplitter 方法返回的文档,特别是ExtractPageRange,是iText 7 文档写入,即这些PdfDocument 实例已被实例化为PdfWriter.

此类文档受到某些限制,特别是不能从中复制页面。有关这方面的详细信息,请阅读答案 herehere

要使这些结果文档(以及带有它们的整个PdfSplitter 类)具有任何价值,因此,您需要一种方法来定义这些文档的PdfWriter 对象写入的位置。还有一种方法,虽然不是很直观的方法:你必须覆盖 PdfSplitterGetNextPdfWriter 方法,它最初看起来像这样:

/// <summary>This method is called when another split document is to be created.</summary>
/// <remarks>
/// This method is called when another split document is to be created.
/// You can override this method and return your own
/// <see cref="iText.Kernel.Pdf.PdfWriter"/>
/// depending on your needs.
/// </remarks>
/// <param name="documentPageRange">the page range of the original document to be included in the document being created now.
///     </param>
/// <returns>the PdfWriter instance for the document which is being created.</returns>
protected internal virtual PdfWriter GetNextPdfWriter(PageRange documentPageRange) {
    return new PdfWriter(new ByteArrayOutputStream());
}

在像您这样的用例中,您只希望最终要写入文件的单个返回文档,您可以这样做:

class MySplitter : PdfSplitter
{
    public MySplitter(PdfDocument pdfDocument) : base(pdfDocument)
    {
    }

    protected override PdfWriter GetNextPdfWriter(PageRange documentPageRange)
    {
        String toFile = @"C:\Users\Standard\Downloads\Result\Extracted.pdf";
        return new PdfWriter(toFile);
    }
}

随着PdfWriter 实例化移动到自定义拆分器中,您的主代码将减少到

string file = @"C:\Users\Standard\Downloads\Merged\CK 2002989 $29,514.42 02.12.20.pdf";
string range = "1, 4, 8";
var pdfDocumentInvoiceNumber = new PdfDocument(new PdfReader(file));
var split = new MySplitter(pdfDocumentInvoiceNumber);
var result = split.ExtractPageRange(new PageRange(range));
result.Close();

在像您这样的用例中,这确实看起来很奇怪,必须从 PdfSplitter 派生一个自定义类,只是为了从源 PDF 中提取几页到结果 PDF。 ExtractPageRange 的附加 PdfWriter 参数不会让它变得更容易吗?

但请注意,PdfSplitter 类的主要目标是使用 ExtractPageRangesSplitBy... 方法将文档分成许多部分,在这种情况下,您需要提供更大的,可能不确切知道PdfWriters 的数量...一点也不容易!

当然,更好的解决方案可能是注入一些 lambda 表达式或一些其他回调机制。例如:

class ImprovedSplitter : PdfSplitter
{
    private Func<PageRange, PdfWriter> nextWriter;
    public ImprovedSplitter(PdfDocument pdfDocument, Func<PageRange, PdfWriter> nextWriter) : base(pdfDocument)
    {
        this.nextWriter = nextWriter;
    }

    protected override PdfWriter GetNextPdfWriter(PageRange documentPageRange)
    {
        return nextWriter.Invoke(documentPageRange);
    }
}

你可以这样使用

string file = @"C:\Users\Standard\Downloads\Merged\CK 2002989 $29,514.42 02.12.20.pdf";
string range = "1, 4, 8";
var pdfDocumentInvoiceNumber = new PdfDocument(new PdfReader(file));
var split = new ImprovedSplitter(pdfDocumentInvoiceNumber, pageRange => new PdfWriter(@"C:\Users\Standard\Downloads\Result\Extracted.pdf"));
var result = split.ExtractPageRange(new PageRange(range));
result.Close();

【讨论】:

  • 谢谢!我最终覆盖了这些方法。但是我还是想知道这个方法的默认返回会有什么样的用途。
  • 我也不确定。
  • 如果我想使用内存流,而不是将结果 pdf 提取到文件流中,该怎么办。实际上,我也有同样的要求。 bt 唯一关心的是我没有权限在我的生产服务器上创建文件。所以我必须将它们存储在内存流中。那么您对此有什么建议吗?
  • @KaranShah 在您的GetNextPdfWriter 覆盖中,您可以创建此类内存流并将它们存储在拆分器类的集合属性中。拆分后,您可以从拆分器中检索该集合。明显的集合选择是列表或基于 PageRange 作为对应键或值的映射。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-09-30
  • 1970-01-01
  • 2017-04-13
  • 1970-01-01
  • 2020-06-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多