Search code examples
javascriptjavapdfbox

Add calculation script to pdf form fields with PDFBox


Maybe someone can help me with this. I have a java application, which generates a PDF file from an HTML template using flying-saucer-pdf. Since there is no support for JavaScript, I'm planning to add my JavaScript after creating the PDF using PDFBox.

I want to add JavaScript to read a value from field A, calculate a new sum based on that value and write it to another field B. This calculation should take place every time the value in the input field A changes.

My problem is though, that I have no clue on how to add my JavaScript to the existing PDF file. I found this example online on how to add JavaScript, but it only shows how to register an Action to the opening event of the PDF. This doesn't help since I want to add calculations as described in the Acrobat JS Developer Guide under Calculation script.

I guess I have to modify the Documents PDAcroForm somehow. There is even a getScriptingHandler()-Method, which seems promising, it's null on my document. My code to add the JavaScript looks like this sofar:

try (var document = PDDocument.load(pdfFile)) {
  var documentDocumentCatalog = document.getDocumentCatalog();
  var documentAcroForm = documentDocumentCatalog.getAcroForm();
  var javascriptString = FileUtils.readFileToString(
                           javascriptFile, 
                           StandardCharsets.UTF_8,
                         );
  var javascript = new PDActionJavaScript(javascriptString);
  documentAcroForm.getScriptingHandler().calculate(javascript, "99");
} catch (IOException e) {
  e.printStackTrace();
}

>>Update<<

Thanks to your input I have managed to achieve my goal of calculating new values. My Java code now looks like this:

try (var document = PDDocument.load(pdfFile)) {
  var catalog = document.getDocumentCatalog();
  var acroForm = catalog.getAcroForm();

  var fieldActions = new PDFormFieldAdditionalActions();
  var javascriptString = FileUtils.readFileToString(javascriptFile, StandardCharsets.UTF_8);
  var actionJavaScript = new PDActionJavaScript(javascriptString);
  actionJavaScript.setAction(javascriptString);
  fieldActions.setC(actionJavaScript);

  var field = (PDTextField) acroForm.getField("fieldA");
  field.setActions(fieldActions);
  var coArray = new COSArray();
  coArray.add(field);
  acroForm.getCOSObject().setItem(COSName.CO, coArray);

  document.save(pdfFile);
} catch (IOException e) {
  e.printStackTrace();
}

And my JavaScript code looks like this:

calculateValue("fieldA", "fieldB");

function calculateValue(sourceFieldName, targetFieldName) {
    this.getField(sourceFieldName).value = Math.floor(this.getField(targetFieldName).value / 2 - 5)
}

This works, although I stumbled on some weird behaviour, maybe I'm just misunderstanding the change event. With this code, even though I only added "fieldA" to the recalculation order, it seems that any change in any field triggers the event. What I actually wanted to achieve was, that only the change in a specific field triggers a recalculation of another specific field. Is this how the recalculation is supposed to work?

>>Update2<<

Ok never mind, I just made mistake and updated both fields on every recalculation event...after adjusting my JavaScript, everything works now as intended.

Just one more question, how do I add a JavaScript function globally to the document to then call it on field events?

Thanks for the help!


Solution

  • On the PDF level it would look like this:

    //The text field
    10 0 obj
    <</AA<</C 12 0 R>>/AP<</N 100 0 R>>/F 4/FT/Tx/Ff 2/MK<</BC[0.0 0.0 0.0]>>/P 23 0 R/Rect[339.288 570.374 443.231 589.985]/Subtype/Widget/T(Textfield10)/Type/Annot/V(7)>>
    endobj
    
    //The JS function which writes the result of 'Textfield1'+'Textfield3' into Textfield10
    12 0 obj
    <</JS(AFSimple_Calculate\("SUM", new Array \("Textfield1", "Textfield3"\)\);)/S/JavaScript>>
    endobj
    
    14 0 obj
    <</CO[10 0 R]/DA(/Helv 0 Tf 0 g )/Fields[10 0 R 16 0 R 18 0 R] ...>>
    

    So as you can see the additional actions (AA) attribute is the key to use. There are several answers in the web or here on SO on how to do that. It would look something like this:

    PDFormFieldAdditionalActions pdFormFieldAdditionalActions = new PDFormFieldAdditionalActions();
    PDActionJavaScript jsSumAction = new PDActionJavaScript();
    jsSumAction .setAction("AFSimple_Calculate\("SUM", new Array \("Textfield1", "Textfield3"\)\);");
    pdFormFieldAdditionalActions.setC((PDAction) jsSumAction);
    

    Note that the JS syntax used here is *dobe JS syntax - so it probably doesn't work on other viewers. But that is the thing with JS script in PDFs anyhow and one of the reasons it is forbidden in PDF/A...