1

我用 Adob​​e 创建了一个 PDF,其中包含一个名为“logo”的图像字段

现在,如果我想使用 PDFBox 添加图片,它将不会显示在创建的 pdf 中。

但是,使用正确创建的 PDImageXObject 对象不会引发错误消息,并且调试看起来完全正常。

使用的代码主要改编自这个问题

 public static void setImageField(PDDocument pdfDocument, PDAcroForm acroForm, String fieldKey, byte[] imageData)
  {
    final PDField retrievedField = acroForm.getField(fieldKey);

    if (!(retrievedField instanceof PDPushButton)) {
      throw new RuntimeException("The field: " + fieldKey + " is not of the correct type");
    }

    LOGGER.info("Received field: " + retrievedField.getPartialName()); // correct field is being logged

    final PDPushButton imageField = (PDPushButton) retrievedField;
    final List<PDAnnotationWidget> fieldWidgets = imageField.getWidgets(); // it has exactly one widget, which would be the action to set the image

    if (fieldWidgets == null || fieldWidgets.size() <= 0) {
      throw new RuntimeException("Misconfiguration for field: " + fieldKey + ", it has no widgets(actions)");
    }

    final PDAnnotationWidget imageWidget = fieldWidgets.get(0);

    PDImageXObject imageForm;

    try {
      // This test data is resized to be smaller than the bounding box of the image element in the PDF. Just for testing purposes
      final String testImage = "iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAARUlEQVR42u3PMREAAAgEIO2fzkRvBlcPGtCVTD3QIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIXC7VGjKHva+IvAAAAAElFTkSuQmCC";
      final byte[] testData = Base64.getDecoder().decode(testImage);
      imageForm = PDImageXObject.createFromByteArray(pdfDocument, testData, "logo"); // imageForm is being populated with data and no error thrown
    }
    catch (IOException e) {
      throw new RuntimeException("Error creating Image from image data", e);
    }

    final float imageScaleRatio = (float) (imageForm.getHeight() / imageForm.getWidth());

    final PDRectangle imagePosition = getFieldArea(imageField);
    LOGGER.info("Received image position: " + imagePosition.toString());

    // Retrieve the height and width and position of the rectangle where the picture will be
    final float imageHeight = imagePosition.getHeight();
    LOGGER.info("Image height: " + imageHeight);

    final float imageWidth = imageHeight / imageScaleRatio;
    LOGGER.info("Image width: " + imageWidth);

    final float imageXPosition = imagePosition.getLowerLeftX();
    LOGGER.info("Image X position: " + imageXPosition);

    final float imageYPosition = imagePosition.getLowerLeftY();
    LOGGER.info("Image Y position: " + imageYPosition);

    final PDAppearanceStream documentAppearance = new PDAppearanceStream(pdfDocument);
    documentAppearance.setResources(new PDResources()); // not sure why this is done

    // Weird "bug" in pdfbox forces to create the contentStream always after the object to add
    try (PDPageContentStream documentContentStream = new PDPageContentStream(pdfDocument, documentAppearance)) {
      documentContentStream.drawImage(imageForm, imageXPosition, imageYPosition, imageWidth, imageHeight);
    }
    catch (IOException e) {
      throw new RuntimeException("Error drawing the picture in the document", e);
    }

    // Setting the boundary box is mandatory for the image field! Do not remove or the flatten call on the acroform will NPE
    documentAppearance.setBBox(new PDRectangle(imageXPosition, imageYPosition, imageWidth, imageHeight));

    // Apply the appearance settings of the document onto the image widget ( No border )
    setImageAppearance(imageWidget, documentAppearance);

    // This code is normally somewhere else but for SO copied in this method to show how the pdf is being created
    acroForm.flatten();
    AccessPermission ap = new AccessPermission();
    ap.setCanModify(false);
    ap.setCanExtractContent(false);
    ap.setCanFillInForm(false);
    ap.setCanModifyAnnotations(false);
    ap.setReadOnly();
    StandardProtectionPolicy spp = new StandardProtectionPolicy("", "", ap);
    spp.setEncryptionKeyLength(PdfBuildConstants.ENCRYPTION_KEY_LENTGH);

    pdfDocument.protect(spp);
    pdfDocument.save(pdfFile);
    pdfDocument.close();
  }

  /**
   * Applies the appearance settings of the document onto the widget to ensure a consistent look of the document.
   *
   * @param imageWidget        The {@link PDAnnotationWidget Widget} to apply the settings to
   * @param documentAppearance The {@link PDAppearanceStream Appearance settings} of the document
   */
  private static void setImageAppearance(final PDAnnotationWidget imageWidget,
                                         final PDAppearanceStream documentAppearance)
  {
    PDAppearanceDictionary widgetAppearance = imageWidget.getAppearance();

    if (widgetAppearance == null) {
      widgetAppearance = new PDAppearanceDictionary();
      imageWidget.setAppearance(widgetAppearance);
    }

    widgetAppearance.setNormalAppearance(documentAppearance);
  }

  /**
   * Retrieves the dimensions of the given {@link PDField Field} and creates an {@link PDRectangle Rectangle} with the
   * same dimensions.
   *
   * @param field The {@link PDField Field} to create the rectangle for
   * @return The created {@link PDRectangle Rectangle} with the dimensions of the field
   */
  private static PDRectangle getFieldArea(PDField field) {
    final COSDictionary fieldDictionary = field.getCOSObject();

    final COSBase fieldAreaValue = fieldDictionary.getDictionaryObject(COSName.RECT);

    if (!(fieldAreaValue instanceof COSArray)) {
      throw new RuntimeException("The field: " + field.getMappingName() + " has no position values");
    }

    final COSArray fieldAreaArray = (COSArray) fieldAreaValue;
    return new PDRectangle(fieldAreaArray);
  }

我还查看了其他问题,例如this,但我不能使用 PDJpeg,因为它在当前版本中不可用。此外,要使用的原始图像可以是从 jpeg、png 到 gif 的任何内容。

我验证了记录的位置和尺寸变量与 pdf 文件中的图像字段具有相同的值。(字段属性)

更新:这是一个示例 zip,其中包含模板 pdf 和生成的 pdf 填写表单字段:文件上传Dropbox

示例图片是一个 50x50 png,具有从在线 png 像素生成的纯绿色

4

0 回答 0