In the previous SO question of PDFBox 1.8.10: Fill and Sign PDF creates invalid signatures. I explained how I could not fill out and subsequently sign the PDF using PDFBox 1.8.10. After it turned out with some help, I now continue to work on the same topic. Starting with doc_v2.pdf (links to the file below!), I fill out and sign it, resulting in doc_v2_fillsigned.pdf (do it at a time, saving it step by step). Again I open the edited document (using the PDFBox again) and try to fill in another field.
Then saving the document leads to the following stack trace:
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)
What am I doing in a bad fill:
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; } }
Files:
empty document: https://www.dropbox.com/s/xf5pb0ng8k9zd4i/doc_v2.pdf?dl=0
completed and signed copy: https://www.dropbox.com/s/s8295tfyjpe1l4l/doc_v2_fillsigned.pdf?dl=0
Again, any help to resolve this is appreciated!
Update:
mkl asked in the code comments to create a fillsigned pdf. So far, I have learned that only signatures are enough to reset the fill code above after that. So, here are some exceptions to my code for signing:
@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); } }
this creates a signature using BouncyCastle 1.52, the code is called from
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);
which uses the following method to create a visible signature in some signature field, adding an image there and placing it correctly:
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; }
I suspect that this snippet is optional, and applying the signature examples that come with the PDFBox results in the same conflict when you try to fill out a PDF and save it.
Yours faithfully,
Daniel