如何使用java PDFBox以编程方式将图像插入AcroForm字段?

时间:2017-10-17 20:55:36

标签: java pdfbox acrofields

我创建了带有3个标签的简单PDF文档:名字,姓氏和照片。然后我添加了AcroForm图层和2'文本字段'和一个图像场'使用Adobe Acrobat PRO DC。

enter image description here

因此,如果我想填写表格,我可以在常规Acrobat Reader中打开此PDF文件,并输入名字,姓氏填写并插入照片我点击图像占位符并在打开的对话窗口中选择照片

enter image description here

但是我怎么能以编程方式做同样的事情呢? 创建了简单的Java应用程序,它使用Apache PDFBox库(版本2.0.7)来查找表单字段和插入值。

我可以轻松填充文本编辑字段,但无法弄清楚如何插入图像:

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!");
    }
}

我区分了一个奇怪的东西 - 在Acrobat Pro DC中,我说我添加了Image Field,但是我生成的名字只能得到唯一的字段:&#39; photo_af_image&#39;是类型按钮 - PDPushButton (这就是我检查是否(字段instanceof PDPushButton)),但与Image无关。

如何将图像插入AcroForm&#39; photo_af_image&#39;字段,以便它适合Acrobat Pro DC创建的框的大小?

2 个答案:

答案 0 :(得分:5)

我终于找到并建立了很好的解决方案。该解决方案的目标是:

  1. 使用简单的方法创建包含文本和图像占位符的表单层 工具,可以由非程序员完成,不需要 操纵低级PDF结构;
  2. 使用简单的工具使表单创建者驱动插入图像的大小;尺寸由高度驱动,但宽度将按比例调整;
  3. 以下解决方案的主要思想是通过acroForm占位符插入图像:

    1. 你必须迭代acroForm层并找到按钮 相应的占位符名称;
    2. 如果找到的字段是PDPushButton类型的第一个小部件;
    3. 从图像文件创建PDImageXObject;
    4. 使用PDImageXObject创建PDAppearanceStream并设置相同的x&amp; y位置并调整高度和宽度以匹配高度 占位符;
    5. 将此PDAppearanceStream设置为小部件;
    6. 您可以选择展平文档以合并acroform 主要的
    7. 这是代码:

      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);
          }
      }
      

      如果有更好的解决方案,或者您可以改进此代码,请告诉我。

答案 1 :(得分:1)

Renat Gatin的回答对于让我着手这一点非常宝贵。谢谢你但是,我发现我可以用更少的复杂度完成相同的结果。最初的答案似乎主要是使用PDPushButton字段来确定该字段的大小和位置。字段本身与插入图像无关。该代码直接写入文档流,而没有真正填充该字段。

我在表单中创建了一个文本字段,该文本字段覆盖了我想要图像的整个区域,在本例中为QR码。然后,使用发现的字段坐标,可以将图像写入文档中。这是使用PDFBox 2.0.11完成的。

免责声明:

  • 将我的字段完全设为正方形以适合始终为正方形的QR码
  • 我的图像是黑白的。我没有尝试插入彩色图像
  • 已知我的模板只有一页,因此是“ document.getPage(0)”
  • 我没有在用于放置图片的字段中插入任​​何文本

以下是我提供的部分代码作为示例,而不是完整或通用的解决方案:

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);
}