How to insert image programmatically in to AcroForm field using java PDFBox?
Asked Answered
R

2

10

I have created simple PDF document with 3 labels: First Name, Last Name and Photo. Then I added AcroForm layer with 2 'Text Fields' and one 'Image Field' using Adobe Acrobat PRO DC.

enter image description here

So if I want to fill up the form I can open this PDF file in regular Acrobat Reader and fill up by typing First Name, Last Name and in order to insert Photo I click on image placeholder and select photo in opened Dialog Window.

enter image description here

But how can I do same thing programmatically? Created simple Java Application that uses Apache PDFBox library (version 2.0.7) to find form fields and insert values.

I can easily populate Text Edit field, but can not figure out how can I insert image:

public class AcroFormPopulator {

    public static void main(String[] args) {

        AcroFormPopulator abd = new AcroFormPopulator();
        try {
            abd.populateAndCopy("test.pdf", "generated.pdf");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void populateAndCopy(String originalPdf, String targetPdf) throws IOException {
        File file = new File(originalPdf);

        PDDocument document = PDDocument.load(file);
        PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();

        Map<String, String> data = new HashMap<>();
        data.put("firstName", "Mike");
        data.put("lastName", "Taylor");
        data.put("photo_af_image", "photo.jpeg");

        for (Map.Entry<String, String> item : data.entrySet()) {
            PDField field = acroForm.getField(item.getKey());
            if (field != null) {

                if (field instanceof PDTextField) {
                    field.setValue(item.getValue());

                } else if (field instanceof PDPushButton) {
                    File imageFile = new File(item.getValue());

                    PDPushButton pdPushButton = (PDPushButton) field;
                    // do not see way to isert image

                } else {
                    System.err.println("No field found with name:" + item.getKey());
                }
            } else {
                System.err.println("No field found with name:" + item.getKey());
            }
        }

        document.save(targetPdf);
        document.close();
        System.out.println("Populated!");
    }
}

I have distinguished a weird thing - in Acrobat Pro DC it says that I add Image Field, but the only field I get by generated name: 'photo_af_image' is of type button - PDPushButton (that is why I check if (field instanceof PDPushButton)), but is nothing to do with Image.

How can I insert image to AcroForm 'photo_af_image' field, so that it will fit the size of a box created af Acrobat Pro DC?

Rianon answered 17/10, 2017 at 20:55 Comment(0)
R
10

I finally have found and built up nice solution. The goals of this solution is:

  1. to create form layer with text and image placeholders using simple tools, which can be done by non-programmer and does not require to manipulate low level PDF structure;
  2. make size of inserted image be driven by form creator using simple tools; size to be driven by height, but width will be adjusted by ratio;

The main idea of solution below for inserting images by acroForm placeholders is:

  1. you have to iterate acroForm layer and find button with corresponding placeholder name;
  2. if found field is of type PDPushButton get its first widget;
  3. create PDImageXObject from image file;
  4. create PDAppearanceStream using PDImageXObject and setting same x & y position and adjust the height and width to match the height of placeholder;
  5. set this PDAppearanceStream to a widget;
  6. you can optionally flatten the document to merge acroform lay to main one

Here is code:

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.imageio.ImageIO;

import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.interactive.action.PDAction;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionHide;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.pdmodel.interactive.form.PDPushButton;
import org.apache.pdfbox.pdmodel.interactive.form.PDTextField;

public class AcroFormPopulator {

    public static void main(String[] args) {
        AcroFormPopulator abd = new AcroFormPopulator();
        try {
            Map<String, String> data = new HashMap<>();
            data.put("firstName", "Mike");
            data.put("lastName", "Taylor");
            data.put("dateTime", (new Date()).toString());
            data.put("photo_af_image", "photo1.jpg");
            data.put("photo2_af_image", "photo2.jpg");
            data.put("photo3_af_image", "photo3.jpg");

            abd.populateAndCopy("test.pdf", "generated.pdf", data);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void populateAndCopy(String originalPdf, String targetPdf, Map<String, String> data) throws IOException {
        File file = new File(originalPdf);
        PDDocument document = PDDocument.load(file);
        PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();

        for (Map.Entry<String, String> item : data.entrySet()) {
            String key = item.getKey();
            PDField field = acroForm.getField(key);
            if (field != null) {
                System.out.print("Form field with placeholder name: '" + key + "' found");

                if (field instanceof PDTextField) {
                    System.out.println("(type: " + field.getClass().getSimpleName() + ")");
                    field.setValue(item.getValue());
                    System.out.println("value is set to: '" + item.getValue() + "'");

                } else if (field instanceof PDPushButton) {
                    System.out.println("(type: " + field.getClass().getSimpleName() + ")");
                    PDPushButton pdPushButton = (PDPushButton) field;

                    List<PDAnnotationWidget> widgets = pdPushButton.getWidgets();
                    if (widgets != null && widgets.size() > 0) {
                        PDAnnotationWidget annotationWidget = widgets.get(0); // just need one widget

                        String filePath = item.getValue();
                        File imageFile = new File(filePath);

                        if (imageFile.exists()) {
                            /*
                             * BufferedImage bufferedImage = ImageIO.read(imageFile); 
                             * PDImageXObject pdImageXObject = LosslessFactory.createFromImage(document, bufferedImage);
                             */
                            PDImageXObject pdImageXObject = PDImageXObject.createFromFile(filePath, document);
                            float imageScaleRatio = (float) pdImageXObject.getHeight() / (float) pdImageXObject.getWidth();

                            PDRectangle buttonPosition = getFieldArea(pdPushButton);
                            float height = buttonPosition.getHeight();
                            float width = height / imageScaleRatio;
                            float x = buttonPosition.getLowerLeftX();
                            float y = buttonPosition.getLowerLeftY();

                            PDAppearanceStream pdAppearanceStream = new PDAppearanceStream(document);
                            pdAppearanceStream.setResources(new PDResources());
                            try (PDPageContentStream pdPageContentStream = new PDPageContentStream(document, pdAppearanceStream)) {
                                pdPageContentStream.drawImage(pdImageXObject, x, y, width, height);
                            }
                            pdAppearanceStream.setBBox(new PDRectangle(x, y, width, height));

                            PDAppearanceDictionary pdAppearanceDictionary = annotationWidget.getAppearance();
                            if (pdAppearanceDictionary == null) {
                                pdAppearanceDictionary = new PDAppearanceDictionary();
                                annotationWidget.setAppearance(pdAppearanceDictionary);
                            }

                            pdAppearanceDictionary.setNormalAppearance(pdAppearanceStream);
                            System.out.println("Image '" + filePath + "' inserted");

                        } else {
                            System.err.println("File " + filePath + " not found");
                        }
                    } else {
                        System.err.println("Missconfiguration of placeholder: '" + key + "' - no widgets(actions) found");
                    }
                } else {
                    System.err.print("Unexpected form field type found with placeholder name: '" + key + "'");
                }
            } else {
                System.err.println("No field found with name:" + key);
            }
        }

        // you can optionally flatten the document to merge acroform lay to main one
        acroForm.flatten();

        document.save(targetPdf);
        document.close();
        System.out.println("Done");
    }

    private PDRectangle getFieldArea(PDField field) {
        COSDictionary fieldDict = field.getCOSObject();
        COSArray fieldAreaArray = (COSArray) fieldDict.getDictionaryObject(COSName.RECT);
        return new PDRectangle(fieldAreaArray);
    }
}

Please let me know if there is better solution or something this code you can improve.

Rianon answered 18/10, 2017 at 22:17 Comment(5)
For me, the code is working when I comment the line acroForm.flatten(); Can you please tell me when and why we are using the flatten method. Is it really required?Strident
@ANP Flatten method - remove the form-field but keep the text of the field in the resulting pdf meaning when you hover your mouse over them you will not see any highlighted boxes.Zugzwang
@ANP Your need to remove that line might be due to a bug in the PDFBox form flattening code, cf. the bottom part in this answer.Glandulous
I made my button field read-only and cleared its target URL, so after inserting the image it doesn't behave like a button. Nothing happens on clicking it. You can manipulate the text fields and print the form as usual.Minuteman
Appearapently those image fields also always end with _af_image, so that could be checked to prevent it from acting on normal buttons.Melicent
A
4

The answer by Renat Gatin was invaluable for getting me started on this. Thank you for that. However, I found I could accomplish the same result with less complexity. The original answer seems to be using the PDPushButton field primarily to determine the field's size and location. The field itself has little to do with inserting the image. The code is writing directly to the document stream and not really populating the field.

I created a text field in my form which covers the entire area where I want the image, in my case a QR code. Then using the discovered coordinates of the field, I can write the image in the document. This was done using PDFBox 2.0.11.

Disclaimers:

  • My field was made exactly square to fit QR codes which are always square
  • My images are black and white. I did not attempt to insert a color image
  • My template is known to have only one page, hence "document.getPage(0)"
  • I did not insert any text in the field used to position the image

Here is my partial code provided as an example, not a complete or generic solution:

public void setField(PDDocument document, String name, PDImageXObject image) 
    throws IOException {

  PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
  PDField field = acroForm.getField(name);
  if (field != null) {
    PDRectangle rectangle = getFieldArea(field);
    float size = rectangle.getHeight();
    float x = rectangle.getLowerLeftX();
    float y = rectangle.getLowerLeftY();

    try (PDPageContentStream contentStream = new PDPageContentStream(document, 
        document.getPage(0), PDPageContentStream.AppendMode.APPEND, true)) {
      contentStream.drawImage(image, x, y, size, size);
    }
  }
}

private PDRectangle getFieldArea(PDField field) {
  COSDictionary fieldDict = field.getCOSObject();
  COSArray fieldAreaArray = (COSArray) fieldDict.getDictionaryObject(COSName.RECT);
  return new PDRectangle(fieldAreaArray);
}
Arnulfo answered 27/4, 2020 at 22:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.