追加模式要求文档没有错误,即使可以恢复也是如此

Append mode requires a document without errors, even if recovery is possible

我用append方式签名的PDF是从Office Word 2016导出的

这是我的文件:word.pdf

我收到了这条错误信息:

com.itextpdf.kernel.PdfException: Append mode requires a document without errors, even if recovery is possible.

我正在使用 iText7 7.0.4。

您尝试在追加模式下更改的文档已损坏。很可能,交叉引用中定义的字节偏移量 table 与 PDF 对象的实际字节位置不对应。

在你的例子中,我在文件末尾看到一些奇怪的东西:

xref
0 26
0000000010 65535 f
0000000017 00000 n
0000000166 00000 n
0000000222 00000 n
0000000492 00000 n
0000000755 00000 n
0000000932 00000 n
0000001180 00000 n
0000001233 00000 n
0000001286 00000 n
0000000011 65535 f
0000000012 65535 f
0000000013 65535 f
0000000014 65535 f
0000000015 65535 f
0000000016 65535 f
0000000017 65535 f
0000000018 65535 f
0000000019 65535 f
0000000020 65535 f
0000000000 65535 f
0000001961 00000 n
0000002154 00000 n
0000044863 00000 n
0000048000 00000 n
0000048045 00000 n
trailer
<</Size 26/Root 1 0 R/Info 9 0 R/ID[<8812105F6F93284DAEF240C8C1FC4C4E><8812105F6F93284DAEF240C8C1FC4C4E>] >>
startxref
48341
%%EOF
xref
0 0
trailer
<</Size 26/Root 1 0 R/Info 9 0 R/ID[<8812105F6F93284DAEF240C8C1FC4C4E><8812105F6F93284DAEF240C8C1FC4C4E>] /Prev 48341/XRefStm 48045>>
startxref
49017
%%EOF

您有一个包含两个预告片的 PDF。一个预告片声称交叉引用 table 存储在流中:

/XRefStm 48045

同时指示交叉引用的开始 table 在字节位置 49017:

startxref
49017

另一个预告片声称有一个未压缩的交叉引用 table 并且它从字节位置 48341 开始:

startxref
48341

确实:存在未压缩的交叉引用流:

xref
0 26
0000000010 65535 f
0000000017 00000 n

您了解文件中的不一致之处吗?

当您使用附加模式时,iText 不会更改原始文档的任何内容:一个字节都没有更改;在原始文件的最后一个 %%EOF 标记之后添加新字节。但是,当原始文件损坏时,iText 拒绝这样做。我希望您理解其中的原理:如果 iText 允许您这样做,您会使情况变得更糟。

要解决这个问题,您需要先修复损坏的文件。这可以通过 "manipulating" 文档完成而不更改任何内容,但是 在正常模式下执行此操作,而不是在追加模式下执行此操作。

您是否尝试过删除多余的预告片。我扔掉了:

xref
0 0
trailer
<</Size 26/Root 1 0 R/Info 9 0 R/ID[<8812105F6F93284DAEF240C8C1FC4C4E><8812105F6F93284DAEF240C8C1FC4C4E>] /Prev 48341/XRefStm 48045>>
startxref
49017
%%EOF

Adobe Reader 删除这些字节后没有抱怨。

这实际上是 iText 7 中的一个错误,它创建了对混合引用文件的增量更新。

错误情况

不幸的是,问题中的描述没有清楚地描述重现错误的方法。因此,可以像这样重现错误:

  1. 在附加模式中标记 OP 的 sample document(它不需要是签名用例)。

    此步骤不会但会产生有问题的错误。

  2. 在附加模式中再次标记步骤 1 的输出(同样不需要用于签名)。

    这一步例外

     com.itextpdf.kernel.PdfException: Append mode requires a document without errors, even if recovery is possible.
    

    出现。

有问题的 PDF

OP 的 PDF 很特别,因为它是一个混合参考文件。根据PDF规范(ISO 32000-1)这样的文件

is readable by readers designed only to support versions of PDF before PDF 1.5. Such a file contains objects referenced by standard cross-reference tables in addition to objects in object streams that are referenced by cross-reference streams.

对于这些文件,startxref 偏移指向 pre-1.5 交叉引用 table 和尾部 XRefStm 1.5 交叉引用流的入口点。

PDF 规范还规定

the XRefStm entry shall not be used in the trailer dictionary of the main cross-reference section but only in an update cross-reference section.

因此文件中看起来很有趣的结构:

18 0 obj
<</Type/ObjStm/N 10/First 67/Filter/FlateDecode/Length 357>>
stream
[...object stream data...]
endstream
endobj
[...]
25 0 obj
<</Type/XRef/Size 25/W[ 1 4 2] /Root 1 0 R/Info 9 0 R/ID[<8812105F6F93284DAEF240C8C1FC4C4E><8812105F6F93284DAEF240C8C1FC4C4E>] /Filter/FlateDecode/Length 97>>
stream
[...cross reference stream data...]
endstream
endobj
xref
0 26
[...cross reference table with 25 entries, objects in object stream are marked free...]
trailer
<</Size 26/Root 1 0 R/Info 9 0 R/ID[<8812105F6F93284DAEF240C8C1FC4C4E><8812105F6F93284DAEF240C8C1FC4C4E>] >>
startxref
[points to the preceding cross reference table]
48341
%%EOF
xref
0 0
[...empty incremental update cross reference table...]
trailer
[XRefStm points to the cross reference stream in object 25]
<</Size 26/Root 1 0 R/Info 9 0 R/ID[<8812105F6F93284DAEF240C8C1FC4C4E><8812105F6F93284DAEF240C8C1FC4C4E>] /Prev 48341/XRefStm 48045>>
startxref
[points to the empty incremental update cross reference table]
49017
%%EOF

因此,虽然看起来很有趣,但这个结构是正确的。

出了什么问题

阅读原始文档时,iText 7 识别出该文档同时包含交叉引用 table 和交叉引用流,并选择交叉引用流。 (实际上PdfReader.readXrefSection先读取空的交叉引用table,然后在trailer中找到XRefStm入口,然后读取交叉引用流。)

创建增量更新时,iText 7 会记住源 PDF 已通过交叉引用流进行解析,因此使用完全压缩,即特别是它使用对象流和交叉引用流。

创建该交叉引用流时,它会将其 Prev 条目设置为原始 PDF 的最终 startxref 指向的内容,即空交叉引用 table,而不是它实际使用的交叉引用流。

但是不允许这样的混合结构(交叉引用流指向交叉引用 table 作为 Prev)。

因此,iText 在第一步中在其结果文档中创建了一个无效的交叉引用结构,因此,在第二步中发现了一个损坏的 PDF 来处理并投诉。

因此,由于此页面在错误的搜索结果中排名很高,但没有提供太多恢复步骤,所以我想我发布了如何解决此问题。

鉴于:

  • 从您不一定能控制的地方收到“损坏的”文件;和
  • 需要对这些文件做些什么

我发现 iTextSharp 中的某些操作会强制恢复。这并不完全“干净”,因为它修改了文件,但由于您收到此错误,您可能更关心完成某些事情而不是完全符合 PDF 规范。

解决方法基本上就是向 PDF 添加一段空文本。就我而言,这让我可以继续处理我收到的文件。

public byte[] InsertEmptyText(byte[] file)
{
    var customText = new CustomText();
    customText.FontSize = 1;
    customText.Align = TextAlign.Right;
    customText.Text = "";
    customText.StartingPointPosition = new System.Drawing.Point(50, 50);
    customText.PageNumber = 1;

    PdfInsertObject pi = new PdfInsertObject();
    pi.LoadPdfDocument(file);
    pi.AddText(customText);
    return pi.InsertObjects();
}

在创建 PdfReader 之前通过它传递数据(并捕获结果)让我继续。