6

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

在此处输入图像描述

因此,如果我想填写表格,我可以在常规 Acrobat Reader 中打开此 PDF 文件并通过键入名字、姓氏来填写,为了插入照片,我单击图像占位符并在打开的对话框窗口中选择照片。

在此处输入图像描述

但是我怎样才能以编程方式做同样的事情呢?创建了简单的 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 中,它说我添加了图像字段,但我通过生成的名称获得的唯一字段:'photo_af_image' 是按钮类型 - PDPushButton(这就是我检查 if (field instanceof PDPushButton) 的原因),但与 Image 无关。

如何将图像插入 AcroForm 'photo_af_image' 字段,使其适合 Acrobat Pro DC 创建的框的大小?

4

2 回答 2

7

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

  1. 使用简单的工具创建带有文本和图像占位符的表单层,这可以由非程序员完成,并且不需要操作低级 PDF 结构;
  2. 使用简单的工具使插入图像的大小由表单创建者驱动;大小由高度驱动,但宽度将按比例调整;

下面通过 acroForm 占位符插入图像的解决方案的主要思想是:

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

这是代码:

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

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

于 2017-10-18T22:17:42.760 回答
2

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);
}
于 2020-04-27T22:23:05.553 回答