Put a Button on PDF with PDFBox 2.x

2020-04-11 07:23发布

问题:

I hope somebody can help me with my Problem with Buttons and Textfields on a PDF created with PdfBox 2.x.

I tried to put a Button on my Page, which sets a Date in a Textfield with a Javascript function. That works fine.

I then tried to put the Textfield and the Button in a Document with more than one page, so that the Textfield and the Button appears on every Page, but in that way, that the Button on the page writes the Date only to the Textfield on the Page where the Button is, I clicked on.

From that on I receive the Problem, that the Button on Page one reacted to the Textfield on Page one, but Page one is the only Page where the Button reacts.

Then I saved 4 Documents with one Page Each, Each Document worked fine. But when at the end I merged the 4 Documents to one with 4 Pages, i received the same problem than before.

Can Somebody tell me, what the problem is here?

Thanks Thomas

Here is my Java-Code:

import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSFloat;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSString;
import org.apache.pdfbox.io.MemoryUsageSetting;
import org.apache.pdfbox.multipdf.PDFMergerUtility;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
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.annotation.PDAppearanceCharacteristicsDictionary;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDPushButton;
import org.apache.pdfbox.pdmodel.interactive.form.PDTextField;

public class ScriptButton {

public static void main(String[] args) throws IOException {

    List<PDDocument> aDocList = new ArrayList<PDDocument>();

    String destall = ".\\DS216J\\home\\01Privat\\Script_Button_all.pdf";
    DecimalFormat DFMM = new DecimalFormat("00");

    for (int i = 0; i < 4; i++) {

        PDDocument doc = new PDDocument();
        PDPage page = new PDPage();
        doc.addPage(page);

        COSDictionary acroFormDict = new COSDictionary();
        acroFormDict
                .setBoolean(COSName.getPDFName("NeedAppearances"), true);
        acroFormDict.setItem(COSName.FIELDS, new COSArray());

        PDAcroForm acroForm = new PDAcroForm(doc, acroFormDict);
        doc.getDocumentCatalog().setAcroForm(acroForm);

        PDAnnotationAdditionalActions buttonAction1 = null;
        PDActionJavaScript javascript = null;
        PDActionJavaScript tfJs = null;

        String iStr = DFMM.format(i);
        String dest = ".\\DS216J\\home\\01Privat\\Script_Button_" + iStr
                + ".pdf";

        PDFont font = PDType1Font.HELVETICA;
        PDResources resources = new PDResources();
        resources.put(COSName.getPDFName("Helvetica"), font);
        acroForm.setDefaultResources(resources);

        PDAppearanceStream pdAppearanceStream = new PDAppearanceStream(doc);
        pdAppearanceStream.setResources(resources);

        PDTextField textField = new PDTextField(acroForm);
        textField.setPartialName("SampleField-" + iStr);

        String defaultAppearance = "/Helv 24 Tf 0 0 0 rg";
        textField.setDefaultAppearance(defaultAppearance);

        textField.setMultiline(true);
        textField.setValue("Click to get Date");

        acroForm.getFields().add(textField);

        PDAnnotationWidget fieldwidget = textField.getWidgets().get(0);
        PDRectangle rect = new PDRectangle(50, 600, 300, 70);
        fieldwidget.setRectangle(rect);
        fieldwidget.setPage(page);

        PDAppearanceCharacteristicsDictionary fieldAppearance = new PDAppearanceCharacteristicsDictionary(
                new COSDictionary());
        fieldAppearance.setBorderColour(new PDColor(
                new float[] { 0, 0, 0 }, PDDeviceRGB.INSTANCE));
        fieldAppearance.setBackground(new PDColor(new float[] { 1, 1, 1 },
                PDDeviceRGB.INSTANCE));
        fieldwidget.setAppearanceCharacteristics(fieldAppearance);

        fieldwidget.setPrinted(true);

        page.getAnnotations().add(fieldwidget);

        COSDictionary cosDict1 = new COSDictionary();
        COSArray buttonRect1 = new COSArray();
        buttonRect1.add(new COSFloat(50));
        buttonRect1.add(new COSFloat(575));
        buttonRect1.add(new COSFloat(150));
        buttonRect1.add(new COSFloat(550));

        cosDict1.setItem(COSName.RECT, buttonRect1);
        cosDict1.setItem(COSName.FT, COSName.getPDFName("Btn")); // Field
                                                                    // Type
        cosDict1.setItem(COSName.TYPE, COSName.ANNOT);
        cosDict1.setItem(COSName.SUBTYPE, COSName.getPDFName("Widget"));
        cosDict1.setItem(COSName.T, new COSString("Datum anzeigen"));
        cosDict1.setItem(COSName.DA,
                new COSString("/F0 6 Tf 0 g 1 1 1 rg "));

        PDPushButton button1 = new PDPushButton(acroForm);
        javascript = new PDActionJavaScript("function date" + iStr
                + "() {var fld" + iStr + " = this.getField('SampleField-"
                + iStr + "'); fld" + iStr
                + ".value = util.printd('dd mmmm yyyy',new Date());}");

        doc.getDocumentCatalog().setOpenAction(javascript);

        tfJs = new PDActionJavaScript("date" + iStr + "();");
        buttonAction1 = new PDAnnotationAdditionalActions();

        buttonAction1.setU(tfJs);
        button1.getWidgets().get(0).setActions(buttonAction1);

        button1.getCOSObject().addAll(cosDict1);
        acroForm.getFields().add(button1);

        PDAnnotationWidget buttonWidget1 = button1.getWidgets().get(0);

        PDAppearanceCharacteristicsDictionary buttonFieldAppearance = new PDAppearanceCharacteristicsDictionary(
                new COSDictionary());
        COSArray borderColorArray = new COSArray();
        borderColorArray.add(new COSFloat((float) (141f / 255f)));
        borderColorArray.add(new COSFloat((float) (179f / 255f)));
        borderColorArray.add(new COSFloat((float) (226f / 255f)));
        PDColor blue = new PDColor(borderColorArray, PDDeviceRGB.INSTANCE);
        buttonFieldAppearance.setBorderColour(blue);
        buttonFieldAppearance.setBackground(blue);
        buttonFieldAppearance.setNormalCaption("Felder löschen");

        buttonWidget1.setAppearanceCharacteristics(buttonFieldAppearance);
        page.getAnnotations().add(buttonWidget1);

        File file = new File(dest);
        file.getParentFile().mkdirs();

        doc.save(dest);
        doc.close();

        aDocList.add(doc);
    }

    PDDocument aDocWithallPages = new PDDocument();
    PDFMergerUtility PDFmerger = new PDFMergerUtility();

    PDFmerger.setDestinationFileName(destall);

    int i = 0;
    for (Iterator<PDDocument> iterator = aDocList.iterator(); iterator
            .hasNext();) {
        iterator.next();

        String iStr = DFMM.format(i);
        File newFile = new File(".\\DS216J\\home\\01Privat\\Script_Button_"
                + iStr + ".pdf");
        PDFmerger.addSource(newFile);

        i = i + 1;
    }

    PDFmerger.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly());

    aDocWithallPages.close();

}

}

回答1:

The second solution (merging) won't work because PDFBox can't change the JS code. The first solution (which you don't show) I tried to recreate, one problem IMHO is that there is only 1 date function which is in OpenAction. You need each function in the JavaScript name tree (you might even work without by having all in the field but I didn't test that):

public static void main(String[] args) throws IOException
{
    String dest = "SO52807807.pdf";

    Map<String, PDActionJavaScript> map = new HashMap<>();
    DecimalFormat DFMM = new DecimalFormat("00");

    try (PDDocument doc = new PDDocument())
    {
        PDDocumentNameDictionary documentNameDictionary = new PDDocumentNameDictionary(doc.getDocumentCatalog());
        PDJavascriptNameTreeNode javascriptNameTreeNode = new PDJavascriptNameTreeNode();
        documentNameDictionary.setJavascript(javascriptNameTreeNode);

        COSDictionary acroFormDict = new COSDictionary();
        acroFormDict
                .setBoolean(COSName.getPDFName("NeedAppearances"), true);
        acroFormDict.setItem(COSName.FIELDS, new COSArray());

        PDAcroForm acroForm = new PDAcroForm(doc, acroFormDict);
        doc.getDocumentCatalog().setAcroForm(acroForm);

        for (int i = 0; i < 4; i++)
        {
            PDPage page = new PDPage();
            doc.addPage(page);

            PDAnnotationAdditionalActions buttonAction1 = null;
            PDActionJavaScript javascript = null;
            PDActionJavaScript tfJs = null;

            String iStr = DFMM.format(i);

            PDFont font = PDType1Font.HELVETICA;
            PDResources resources = new PDResources();
            resources.put(COSName.getPDFName("Helv"), font);
            acroForm.setDefaultResources(resources);

            PDAppearanceStream pdAppearanceStream = new PDAppearanceStream(doc);
            pdAppearanceStream.setResources(resources);

            PDTextField textField = new PDTextField(acroForm);
            textField.setPartialName("SampleField-" + iStr);

            String defaultAppearance = "/Helv 24 Tf 0 0 0 rg";
            textField.setDefaultAppearance(defaultAppearance);

            textField.setMultiline(true);

            acroForm.getFields().add(textField);

            PDAnnotationWidget fieldwidget = textField.getWidgets().get(0);
            PDRectangle rect = new PDRectangle(50, 600, 300, 70);
            fieldwidget.setRectangle(rect);
            fieldwidget.setPage(page);

            textField.setValue("Click to get Date");

            PDAppearanceCharacteristicsDictionary fieldAppearance = new PDAppearanceCharacteristicsDictionary(
                    new COSDictionary());
            fieldAppearance.setBorderColour(new PDColor(
                    new float[]
                    {
                        0, 0, 0
                    }, PDDeviceRGB.INSTANCE));
            fieldAppearance.setBackground(new PDColor(new float[]
            {
                1, 1, 1
            },
                    PDDeviceRGB.INSTANCE));
            fieldwidget.setAppearanceCharacteristics(fieldAppearance);

            fieldwidget.setPrinted(true);

            page.getAnnotations().add(fieldwidget);

            COSDictionary cosDict1 = new COSDictionary();
            COSArray buttonRect1 = new COSArray();
            buttonRect1.add(new COSFloat(50));
            buttonRect1.add(new COSFloat(575));
            buttonRect1.add(new COSFloat(150));
            buttonRect1.add(new COSFloat(550));

            cosDict1.setItem(COSName.RECT, buttonRect1);
            cosDict1.setItem(COSName.FT, COSName.getPDFName("Btn")); // Field
            // Type
            cosDict1.setItem(COSName.TYPE, COSName.ANNOT);
            cosDict1.setItem(COSName.SUBTYPE, COSName.getPDFName("Widget"));
            cosDict1.setItem(COSName.T, new COSString("Datum anzeigen"));
            cosDict1.setItem(COSName.DA,
                    new COSString("/F0 6 Tf 0 g 1 1 1 rg "));

            PDPushButton button1 = new PDPushButton(acroForm);
            javascript = new PDActionJavaScript("function date" + iStr
                    + "() {var fld" + iStr + " = this.getField('SampleField-"
                    + iStr + "'); fld" + iStr
                    + ".value = util.printd('dd mmmm yyyy',new Date());}");

            //doc.getDocumentCatalog().setOpenAction(javascript);
            map.put("date" + iStr, javascript);

            tfJs = new PDActionJavaScript("date" + iStr + "();");
            buttonAction1 = new PDAnnotationAdditionalActions();

            buttonAction1.setU(tfJs);
            button1.getWidgets().get(0).setActions(buttonAction1);

            button1.getCOSObject().addAll(cosDict1);
            acroForm.getFields().add(button1);

            PDAnnotationWidget buttonWidget1 = button1.getWidgets().get(0);

            PDAppearanceCharacteristicsDictionary buttonFieldAppearance = new PDAppearanceCharacteristicsDictionary(
                    new COSDictionary());
            COSArray borderColorArray = new COSArray();
            borderColorArray.add(new COSFloat((float) (141f / 255f)));
            borderColorArray.add(new COSFloat((float) (179f / 255f)));
            borderColorArray.add(new COSFloat((float) (226f / 255f)));
            PDColor blue = new PDColor(borderColorArray, PDDeviceRGB.INSTANCE);
            buttonFieldAppearance.setBorderColour(blue);
            buttonFieldAppearance.setBackground(blue);
            buttonFieldAppearance.setNormalCaption("Felder löschen");

            buttonWidget1.setAppearanceCharacteristics(buttonFieldAppearance);
            page.getAnnotations().add(buttonWidget1);

        }
        javascriptNameTreeNode.setNames(map);
        doc.getDocumentCatalog().setNames(documentNameDictionary);
        File file = new File(dest);
        file.getParentFile().mkdirs();
        doc.save(dest);
    }
}

Update 20.10.2018: I made two more changes in the code: 1) the font name in the defaultAppearance must be the same font name as in the default resources ("Helv"). 2) the value of the field must be set AFTER the widget is assigned, not before. (makes sense if you think about it - the widget is about the visual).. Not doing that could mean trouble when displaying with non Adobe viewers.

Update: 25.5.2019: IMHO the code is a bit clunky because most dictionary elements don't have to be set. A better version to create a button can be found in this answer.