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

时间:2019-09-04 09:18:44

标签: pdf itext digital-signature

给出以下方案:第1方签名pdf,第2方填写AcroForm字段,然后也对pdf签名。

问题是,在第二次签名后,仅出现表单字段值如果我单击该字段。表单填写过程和签名过程都以追加方式进行。

签名pdf代码:

using (PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0', 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, '\0', true))
{
    stamper.AcroFields.SetField("fieldname", "test1234");
}

这里是一个示例PDF。如果在AdobeReader中打开它,则可以看到描述的szenario。表单字段值仅在单击字段时出现。

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

问候

1 个答案:

答案 0 :(得分:0)

简而言之

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

此对象不必是间接的,实际上它比间接的更常见是直接的或不存在的;因此,此错误不会经常产生可见的影响。

您可以通过以下方法解决此问题:在设置设置字段值之前,先删除该对象的先前形式:

using (PdfStamper stamper = new PdfStamper(reader, outStrm, '\0', 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显然不想再这样做了。毕竟,这将改变签名者在签名时所看到的内容,而Adobe试图防止这种情况,因为这种情况可能被指责其软件更改了先前签名的数据...