ITextSharp中的PdfCopyForms导致堆栈溢出错误

本文关键字:堆栈 栈溢出 错误 中的 PdfCopyForms ITextSharp | 更新日期: 2023-09-27 18:05:36

在这种方法中,我试图从一个PDF文档中获取输入字段,将它们粘贴到另一个文档中,并将结果打印为PDF文件。结果将是一个新的PDF文件,其中包含第一个PDF的输入字段和第二个PDF的静态内容。

我写了一些我认为会执行这个任务的代码,但是每次执行"copy .close()"时,我都会遇到StackOverflow错误。这是它抛出的错误:

An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.dll

这是代码:

public static void AddFormFieldsFromSource(string sourcePath, string secondSourcePath, string targetPath) {
  lock (syncLock) {
    PdfReader.unethicalreading = true;
    PdfReader readerMain = new PdfReader(sourcePath);
    FileStream stream = new FileStream(targetPath, FileMode.Create, FileAccess.Write);
    PdfCopyForms copier = new PdfCopyForms(stream);
    PdfReader secondSourceReader = new PdfReader(secondSourcePath);
    copier.AddDocument(secondSourceReader);
    copier.CopyDocumentFields(readerMain);

    copier.Close();
    secondSourceReader.Close();
  }
}

源路径是我获取输入字段的地方,第二个源路径是我获取静态内容的地方。

我用于SourcePath变量的PDF位于这里:https://www.dropbox.com/s/qcc6ug8oohqvmca/primarytwopages2.pdf

我用于secondSourcePath变量的PDF位于这里:https://www.dropbox.com/s/kx2rlhmizh46hl7/secondarytwopages.pdf

另外,我使用的是ITextSharp 5.5.0版本。

知道为什么它抛出StackOverflow错误吗?我在代码中没有做任何递归调用。我的第一个猜测是,我试图做这个任务是错误的。另一种可能是ITextSharp有bug。

更新:我下载了ITextSharp最新版本(5.5.1)的源代码,构建了一个dll以便我可以调试,然后在我的代码中引用该dll。这个方法中的PdfIndirectReference类中出现了堆栈溢出错误:

public class PdfIndirectReference : PdfObject {
....
        internal PdfIndirectReference(int type, int number, int generation) : base(0, new StringBuilder().Append(number).Append(' ').Append(generation).Append(" R").ToString()) {
        this.number = number;
        this.generation = generation;
    }
在dll代码的调用堆栈中,我发现它递归地反复调用 中的方法。

itextsharp.text.pdf.PdfCopyFieldsImp.Propagate()。

这一定是发生堆栈溢出的原因。

所以,它不会发生在我的代码中,而是发生在dll中。你知道怎么解决这个问题吗?

ITextSharp中的PdfCopyForms导致堆栈溢出错误

我用ittext和Java重现了这个问题;这里也出现了同样的问题,所以很可能原因是相同的。

PdfCopyForms内部使用由PdfCopyFieldsImp衍生而来的PdfCopyFormsImp。后一个类提供了执行字段和表单复制的繁重工作的基本方法,其中propagate是OP在堆栈溢出发生时多次在调用堆栈中发现的。

与观察到的堆栈溢出留下的印象相反,PdfCopyFieldsImp 确实具有通过标记已经访问的对象来防止无限循环的机制:

/**
 * Sets a reference to "visited" in the copy process.
 * @param   ref the reference that needs to be set to "visited"
 * @return  true if the reference was set to visited
 */
protected boolean setVisited(PRIndirectReference ref) {
    IntHashtable refs = visited.get(ref.getReader());
    if (refs != null)
        return refs.put(ref.getNumber(), 1) != 0;
    else
        return false;
}

此方法同时将来自某个PdfReader的对象引用标记为已访问,并返回该对象之前是否被访问过。

至少对于所有在visited映射中有一个表项的PdfReader实例的引用是这样做的,没有这个表项的PdfReader实例的引用总是被声明尚未访问(return false)。因此,在多次访问的情况下,这些后一种读者的引用不被认为是访问过的!

PdfReader实例只能在一个代码位置获得visited映射中的条目:只有使用addDocument添加到副本的读取器才能获得该条目。

使用PdfCopyForms将表单字段从一个文档添加到另一个PDF,显然使用addDocument来复制表单,而是使用copyDocumentFields。因此,循环预防在这里不起作用。

通过在visited映射中为复制表单的阅读器添加一个条目,可以防止Stack Overflow。我在PdfCopyFormsImp.copyDocumentFields

public void copyDocumentFields(PdfReader reader) throws DocumentException {
    if (!reader.isOpenedWithFullPermissions())
        throw new IllegalArgumentException(MessageLocalization.getComposedMessage("pdfreader.not.opened.with.owner.password"));
    if (readers2intrefs.containsKey(reader)) {
        reader = new PdfReader(reader);
    }
    else {
        if (reader.isTampered())
            throw new DocumentException(MessageLocalization.getComposedMessage("the.document.was.reused"));
        reader.consolidateNamedDestinations();
        reader.setTampered(true);
    }
    reader.shuffleSubsetNames();
    readers2intrefs.put(reader, new IntHashtable());
    visited.put(reader, new IntHashtable()); //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    fields.add(reader.getAcroFields());
    updateCalculationOrder(reader);
}

在iTextSharp中,类似的变化将在PdfCopyFormsImp.CopyDocumentFields:

    virtual public void CopyDocumentFields(PdfReader reader) {
        if (!reader.IsOpenedWithFullPermissions)
            throw new BadPasswordException(MessageLocalization.GetComposedMessage("pdfreader.not.opened.with.owner.password"));
        if (readers2intrefs.ContainsKey(reader)) {
            reader = new PdfReader(reader);
        }
        else {
            if (reader.Tampered)
                throw new DocumentException(MessageLocalization.GetComposedMessage("the.document.was.reused"));
            reader.ConsolidateNamedDestinations();
            reader.Tampered = true;
        }
        reader.ShuffleSubsetNames();
        readers2intrefs[reader] = new IntHashtable();
        visited[reader] =  new IntHashtable();  //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
        fields.Add(reader.AcroFields);
        UpdateCalculationOrder(reader);
    }

免责声明:我没有检查PdfCopyForms是否完全按照要求进行此更改。我只是在Java中测试了它,只观察到不再发生堆栈溢出,并且OP用例中的PDF结果看起来很好。