itextsharp - AcroForm 字段值在签署 PDF 文档后消失

itextsharp - AcroForm field value disappears after signing PDF document

给出以下情况:第1方签署pdf,第2方填写AcroForm字段,然后也签署pdf。

问题是,在第二次签名后,表格字段值仅在我单击该字段时出现。表单填写过程和签名过程都发生在追加模式。

签名pdf代码:

using (PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '[=10=]', tempFolder, true))
{
    // Creating the signature
    SigPadSignature sig = new SigPadSignature(sigInfo, DigestAlgorithms.SHA256);

    // Creating the appearance
    PdfSignatureAppearance appearance = stamper.SignatureAppearance;
    appearance.Reason = sigInfo.Reason;
    appearance.Location = sigInfo.Location;
    appearance.Contact = sigInfo.ContactInfo;
    appearance.Certificate = chain[0];
    appearance.SignatureCreator = sigInfo.SignatureCreator;
    appearance.SetVisibleSignature(area.CreateRectangle(), area.PageNumber, fieldName);
    appearance.Layer2Text = string.Format("\n{0}\n{1}, {2:g}", sigInfo.SignerName, sigInfo.Location, DateTime.Now);
    appearance.Layer2Font = new Font(Font.FontFamily.COURIER, 7.0f);
    appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.GRAPHIC_AND_DESCRIPTION;
    appearance.SignatureGraphic = Image.GetInstance(sigInfo.SignatureImage, System.Drawing.Imaging.ImageFormat.Bmp);
    appearance.SignatureEvent = sig;

    MakeSignature.SignDetached(appearance, sig, chain, /*crlList*/null, /*ocspClient*/null, /*tsaClient*/null, estimatedSize, CryptoStandard.CMS);
}


填写表单字段代码:

using (PdfStamper stamper = new PdfStamper(reader, outStrm, '[=11=]', true))
{
    stamper.AcroFields.SetField("fieldname", "test1234");
}

这里有一个例子PDF。如果您在 Adob​​eReader 中打开它,您可以看到所描述的场景。表单字段值仅在单击该字段时出现。

我做错了什么?有什么建议吗?

问候

简而言之

您实际上在 iTextSharp v5.x 中发现了一个错误(在 iText v5.x 和 OpenPdf 中也是如此)。在此库中以追加模式工作时,必须将原始文件中的每个间接对象标记为 'used' 但 iText(Sharp) 在更改字段外观的上下文中忘记为一个对象这样做。

这个对象不需要是间接的,实际上它更经常是直接的或不存在的而不是间接的;因此,这个错误通常不会产生明显的影响。

您可以通过在设置字段值之前删除其先前形式的对象来解决此问题:

using (PdfStamper stamper = new PdfStamper(reader, outStrm, '[=10=]', true))
{
    stamper.AcroFields.GetFieldItem("AcroFormField_0").GetWidget(0).Remove(PdfName.AP);
    stamper.AcroFields.SetField("AcroFormField_0", "test1234");
}

(实际上一些 null 检查在这里也不会受到伤害...)

详细

设置字段值时,最终会调用AcroFields.SetField(String name, String value, String display, bool saveAppearance)。如果是文本字段,该字段的新外观将通过以下代码在该方法中更新:

PdfAppearance app = GetAppearance(merged, display, name);
...

PdfDictionary appDic = widget.GetAsDict(PdfName.AP);
if (appDic == null) {
    appDic = new PdfDictionary();
    widget.Put(PdfName.AP, appDic);
    merged.Put(PdfName.AP, appDic);
}

appDic.Put(PdfName.N, app.IndirectReference);
writer.ReleaseTemplate(app);

如果之前没有外观字典,则创建一个新的作为直接对象,一切正常。如果有外观字典作为直接对象,它与 widget 标记为进一步使用相同。但是,如果有一个外观字典 appDict 作为间接对象,则该字典未标记为已使用,因此最终不会保存更改 (appDic.Put(PdfName.N, app.IndirectReference)),即新外观与该字段无关小部件。

解决方法是将以前存在的外观字典标记为已使用,例如像这样:

if (appDic == null) {
    ...
} else {
    MarkUsed(appDic);
}

通过按照上面的建议先将其删除,iText 会创建一个新的外观字典作为直接对象,因此可以绕过该错误。

那么签名...?

看起来 Adob​​e Reader 识别出之前的处理器忘记更新外观,因此在 PDF 的最后一个操作中设置了字段值的情况下,它本身也在后台进行更新。

不过,在向该 PDF 添加另一个签名后,Adobe Reader 显然不想再在后台执行此操作。这毕竟会改变签名者在签名时所看到的内容,并且 Adob​​e 试图防止其软件可能因更改先前签名的数据而受到指责的情况...