2

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!

1
  • Also have a look at the examples UpdateFieldOnDocumentOpen.java and AddJavascript.java . getScriptingHandler() returns an interface, so if you didn't create a handler you'll get nothing. Commented Sep 27, 2021 at 8:05

2 Answers 2

2

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...

Sign up to request clarification or add additional context in comments.

3 Comments

I got it to work, but now I'm struggling with some unintended behavior...I updated my question.
Please provide an example PDF or show a PDF code snippet (like I did in the upper part of my post)
I just realized I made a mistake in my JavaScript, I updated my post, the calculation works now as intended.
0

Here's a complete example using Javascript to sum the fields:

package com.danielcentore.formmaker;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
import org.apache.pdfbox.pdmodel.interactive.action.PDAnnotationAdditionalActions;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDTextField;

public class Main {

    public static void main(String[] args) {
        try (PDDocument document = new PDDocument()) {
            PDPage page = new PDPage(PDRectangle.A4);
            document.addPage(page);

            PDAcroForm acroForm = new PDAcroForm(document);
            document.getDocumentCatalog().setAcroForm(acroForm);

            PDTextField textField1 = createTextField(acroForm, page, "textField1", 50, 750);
            PDTextField textField2 = createTextField(acroForm, page, "textField2", 50, 720);
            PDTextField sumField = createTextField(acroForm, page, "sumField", 50, 690);
            sumField.setReadOnly(true);

            String js = "var val1 = Number(this.getField('textField1').value);"
                    + "var val2 = Number(this.getField('textField2').value);"
                    + "this.getField('sumField').value = val1 + val2;";
            
            PDActionJavaScript actionJavaScript = new PDActionJavaScript(js);
            
            PDAnnotationAdditionalActions actions = new PDAnnotationAdditionalActions();
            actions.setBl(actionJavaScript);
            
            textField1.getWidgets().get(0).setActions(actions);
            textField2.getWidgets().get(0).setActions(actions);

            document.save("DynamicFormWithSumField.pdf");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static PDTextField createTextField(PDAcroForm acroForm, PDPage page, String fieldName, float x, float y)
            throws Exception {
        PDTextField textField = new PDTextField(acroForm);
        textField.setPartialName(fieldName);
        acroForm.getFields().add(textField);
        PDAnnotationWidget widget = textField.getWidgets().get(0);
        widget.setRectangle(new PDRectangle(x, y, 200, 20));
        widget.setPage(page);
        page.getAnnotations().add(widget);
        return textField;
    }
}

What the resulting PDF looks like:

What the pdf looks like

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.