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 Acr
I finally have found and built up nice solution. The goals of this solution is:
The main idea of solution below for inserting images by acroForm placeholders is:
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.
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:
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);
}