【问题标题】:PDFBox 1.8.10: Fill and Sign Document, Filling again failsPDFBox 1.8.10:填写并签署文件,再次填写失败
【发布时间】:2016-01-05 20:24:15
【问题描述】:

在我之前的 SO 问题中PDFBox 1.8.10: Fill and Sign PDF produces invalid signatures 我解释了如何使用 PDFBox 1.8.10 填写并签署 PDF 文档。 在得到一些善意的帮助后,我现在继续研究同一主题。 从 doc_v2.pdf 开始(文件链接如下!),我填写并签名,结果是 doc_v2_fillsigned.pdf(一次性完成,逐步保存)。 我再次打开已编辑的文档(再次使用 PDFBox)并尝试填写另一个字段。

然后保存文档会导致以下堆栈跟踪:

    Exception in thread "main" java.lang.NullPointerException
        at org.apache.pdfbox.pdmodel.interactive.form.PDAppearance.calculateFontSize(PDAppearance.java:930)
        at org.apache.pdfbox.pdmodel.interactive.form.PDAppearance.setAppearanceValue(PDAppearance.java:359)
        at org.apache.pdfbox.pdmodel.interactive.form.PDVariableText.setValue(PDVariableText.java:131)
        at com.c10n.scalibur.ehealthdemo.examples.PdfEditor.fill(PdfEditor.java:100)
        at com.c10n.scalibur.ehealthdemo.examples.SignPdf_ProfileLayer.start(SignPdf_ProfileLayer.java:66)
        at com.c10n.scalibur.ehealthdemo.examples.SignPdf_ProfileLayer.main(SignPdf_ProfileLayer.java:28)

我在失败的填充运行中做了什么:

    File curentDocument new File("doc_v2_fillsigned.pdf);
    File newDocument = new File("doc_v2_fillsigned_filled.pdf);

    String fieldName ="New Emergency Contact";
    String value="test";
    PDDocument doc = null;

    try(FileOutputStream fos = new FileOutputStream(newDocument)){

        try(FileInputStream fis = new FileInputStream(currentDocument);){
            int c;
            while ((c = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, c);
            }
        }

        doc = PDDocument.load(currentDocument);
        PDDocumentCatalog catalog = doc.getDocumentCatalog();

        catalog.getCOSObject().setNeedToBeUpdate(true);
        catalog.getPages().getCOSObject().setNeedToBeUpdate(true);

        PDAcroForm form = catalog.getAcroForm();

        form.getCOSObject().setNeedToBeUpdate(true);
        form.getDefaultResources().getCOSObject().setNeedToBeUpdate(true);

        PDField field = form.getField(fieldName);
        field.setValue(value); // here the exception occurs.

        // What should happen afterwards:
        field.getCOSObject().setNeedToBeUpdate(true);
        field.getAcroForm().getCOSObject().setNeedToBeUpdate(true);

        ((COSDictionary) field.getDictionary().getDictionaryObject("AP")).getDictionaryObject("N").setNeedToBeUpdate(true);

        try(FileInputStream fis = new FileInputStream(newDocument)){
            doc.saveIncremental(fis, fos);
        }
    }finally{
        if(null != doc){
            doc.close();
            doc=null;
        }
    }

文件:

空文档:https://www.dropbox.com/s/xf5pb0ng8k9zd4i/doc_v2.pdf?dl=0

填写并签名的实例:https://www.dropbox.com/s/s8295tfyjpe1l4l/doc_v2_fillsigned.pdf?dl=0

再次,欢迎任何帮助解决这个问题!

更新:

mkl 在 cmets 中询问创建 fillsigned pdf 的代码。到目前为止,我了解到,只有签名就足够了,然后让上面的填充代码失败。 所以这里是我的签名代码中的一些例外:

@Override
public byte[] sign(InputStream data) throws SignatureException, IOException {
    CMSTypedDataInputStream input = new CMSTypedDataInputStream(data);
    try {

        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        InputStream in = new ByteArrayInputStream(certData);

        X509Certificate signCert = (X509Certificate)certFactory.generateCertificate(in);

        ContentSigner signer = new MyContentSigner(profile);

        SignerInfoGenerator i = new JcaSignerInfoGeneratorBuilder(
                new JcaDigestCalculatorProviderBuilder().setProvider("BC").build())

        .build(signer,signCert);

        Store<?> certStore = new JcaCertStore(Collections.singletonList(signCert));

        CMSSignedDataGenerator cmsGen = new CMSSignedDataGenerator();

        cmsGen.addCertificates(certStore);
        cmsGen.addSignerInfoGenerator(i);

        CMSSignedData signedData = cmsGen.generate(input);

        byte[] result =signedData.getEncoded(); 
        return result;
    } catch (Exception e) {
        e.printStackTrace();
        throw new SignatureException(e);
    }
}

这会创建签名,使用 BouncyCastle 1.52,代码是从

public void sign(SignatureInterface signer, String signatureFieldName, int pageNumber, String location, String reason, boolean lock) throws IOException, SignatureException{
    PDSignature signature = new PDSignature();
    signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); // default filter
    signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED); // for visible sigs!

    signature.setLocation(location);
    signature.setReason(reason);

    signature.setSignDate(Calendar.getInstance());

    SignatureOptions options = makeSignatureVisible(signature,signatureFieldName, pageNumber, lock );

    doc.addSignature(signature, signer, options);
}

它使用以下方法,在某个签名字段中生成可见签名,在其中添加图片并正确放置:

SignatureOptions makeSignatureVisible( PDSignature signature, String fieldName, int pageNumber, boolean lock) throws IOException{
    PDDocumentCatalog catalog = doc.getDocumentCatalog();

    catalog.getCOSObject().setNeedToBeUpdate(true);
    catalog.getPages().getCOSObject().setNeedToBeUpdate(true);

    PDAcroForm form = catalog.getAcroForm();

    form.getCOSObject().setNeedToBeUpdate(true);
    form.getDefaultResources().getCOSObject().setNeedToBeUpdate(true);

    PDSignatureField field = (PDSignatureField) form.getField(fieldName);

    field.setSignature(signature);      
    field.setReadonly(lock);

    FileInputStream image = new FileInputStream("MUniverse_Signature.jpg");

    PDVisibleSignDesigner visibleSig =  new PDVisibleSignDesigner(newDocument.getName(), image, 1);

    PDRectangle area = getFieldArea(field);

    float max_width = area.getWidth();
    float max_height = area.getHeight();

    float scale = 1;

    if(max_height < visibleSig.getHeight()){
        scale = max_height / visibleSig.getHeight();
        System.out.println("scale: "+scale);
    }

    if(max_width < scale*visibleSig.getWidth()){
        scale = max_width / visibleSig.getWidth();
        System.out.println("scale: "+scale);
    }

    float zoom = ((scale-1)*100);

    visibleSig.zoom(zoom);

    PDPage page = (PDPage) doc.getDocumentCatalog().getAllPages().get(pageNumber);
    visibleSig.coordinates(area.getLowerLeftX(),page.getMediaBox().getHeight()-area.getUpperRightY());
    visibleSig.signatureFieldName(fieldName);

    PDVisibleSigProperties signatureProperties = new PDVisibleSigProperties();

    signatureProperties.signerName("name").signerLocation("location").signatureReason("Security")
    .visualSignEnabled(true).setPdVisibleSignature(visibleSig).buildSignature();

    SignatureOptions options = new SignatureOptions();
    options.setVisualSignature(signatureProperties);

    return options;
}

我怀疑这些片段不是必需的,并且在尝试用增量保存填充 pdf 时,应用 PDFBox 附带的签名示例会导致相同的冲突。

问候,

丹尼尔

【问题讨论】:

    标签: java pdf pdfbox sign pdf-form


    【解决方案1】:

    问题的原因是在原始填充和签名过程中,交互式表单字典的默认资源中的字体丢失了。

    PDFBox 在填写表单时尝试访问字体定义以创建外观流。它没有找到它,因此最终失败了。

    详细说明

    在原始文档doc_v2.pdf 中,交互式表单字典如下所示:

    您可以在默认资源DRFont字典中清楚地看到ZaDbHelv的条目字典。

    相比之下,填写并签名的文档doc_v2_fillsigned.pdf的交互式表单字典看起来像这样:

    如您所见,默认资源 DR 字典中的 Font 字典缺失。

    原因

    OP进一步观察:

    我又玩了一些,签名就足够了。之后,填充失败。我假设,我的签名创建或多或少与 PDFBox 中描述的示例相同,即如何添加可见签名。

    基于此,我只是将 PDFBox 示例 CreateVisibleSignature 应用于 OP 的原始 doc_v2.pdf 文件。实际上,这已经删除了 Font 默认资源字典。

    因此,这绝对看起来像一个 PDFBox 错误。

    PS:在 Apache Jira 上...

    环顾PDFBox Jira,其实已经可以发现这方面的问题了:

    因此,这是一个已知问题。我不确定 PDFBox 开发在确定优先级时是否会考虑问题投票,但如果您有兴趣解决这个问题,投票不会有什么坏处...... ;)

    【讨论】:

    • 再次感谢@mkl,我又玩了一些,签名就足够了。之后,填充失败。我假设,我的签名创建或多或少与 PDFBox 中描述的示例相同,即如何添加可见签名。另外,我在上面添加了我的 SignatureInterface:sign()(除了 BouncyCastle Signer,这里不相关)和保存增量的代码。
    • 顺便说一句,你用什么工具来浏览pdf的字典?
    • 以上截图来自RUPS
    • @DanielHeldt 你有什么新结果吗?也许来自 PDFBox 的人?
    • @mkl “我不确定 PDFBox 开发是否在确定优先级时是否考虑问题投票” - 我们大多不考虑,但在 PDFBOX-3114 的情况下,我看到了大量的观察者和投票在一个新问题上,尽管对签名知之甚少,但我还是决定自己对此进行调查。 (但我现在知道的更多)。
    猜你喜欢
    • 2015-12-29
    • 1970-01-01
    • 2022-08-02
    • 2011-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-14
    相关资源
    最近更新 更多