1

I have created a PDF with Adobe, which contains an image field called "logo"

Now if i want to add a picture using PDFBox it won't be displayed in the created pdf.

However no error message is thrown and debugging looks completely fine with a correctly created PDImageXObject object.

The used code is mostly adapted from this question:

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

I also looked at other questions such as this, but I can't use PDJpeg since it is not available in the current version. Additionally the original image to use can be anything from jpeg,png to gif.

I validated that the position and dimension variables being logged have the same value as the image field in the pdf file. (properties of the field)

UPDATE: Here is an example zip containing the template pdf and the generated pdf which fills in the form fields: file upload or Dropbox

The example picture was a 50x50 png with plain green color generated from online png pixel

Nico
  • 1,727
  • 1
  • 24
  • 42
  • Could you share the result PDF? – Tilman Hausherr Sep 18 '19 at 11:30
  • Due to information's being displayed in the PDF I cannot upload a created PDF file. I can however add screenshots of the section where the logo is at without showing the rest of the pdf. Both from acrobar with the field visible and the blank space being created. – Nico Sep 18 '19 at 11:32
  • 2
    screenshots won't help. Try reproducing the effect with a non confidential file. Try also removing flatten(), and make sure you're using the latest version (2.0.16, and 2.0.17 release candidate at https://dist.apache.org/repos/dist/dev/pdfbox/2.0.17/ ) – Tilman Hausherr Sep 18 '19 at 11:35
  • @TilmanHausherr Updated from 2.0.15 to 2.0.16 and then removing the .flatten() call made the image appear. I guess there is some problem with the .flatten() call? – Nico Sep 18 '19 at 11:55
  • Yes there have been bugs with flatten, so I wonder if it is fixed in 2.0.17. – Tilman Hausherr Sep 18 '19 at 12:07
  • I would love to test that but that version is not available in maven central and trying to install the files to my local maven repository seems to have some problems at the moment. If I get it to work I will update the status with a new comment – Nico Sep 18 '19 at 12:36
  • I expect the release to be friday evening, so set yourself a notice for monday :-) – Tilman Hausherr Sep 18 '19 at 13:28
  • @TilmanHausherr updating to version 2.0.17 did not resolve this issue I'm experiencing. I will try to find free time for creating a non-confidential example file and will try to fill it. Once that is available I will update the question – Nico Sep 25 '19 at 07:47
  • @TilmanHausherr I added an example zip file containing the template pdf and the generated pdf using the above code. The used version is 2.0.17 and flatten is called. – Nico Sep 27 '19 at 11:53
  • On the download site your link leads to I find 2 download buttons, one tries to push an exe file to me which is diagnosed to contain a virus, the other tries to make me install some add-on. You probably should use a different file hoster. – mkl Sep 27 '19 at 12:08
  • Ok that is really weird. I just visited the site and have only one download button which presents me with my zip file. Which hoster would you recommend? @mkl – Nico Sep 27 '19 at 12:09
  • I've had good experiences with public shares on google drive or dropbox. – mkl Sep 27 '19 at 14:05
  • @mkl I added a link to dropbox for the zip file – Nico Sep 27 '19 at 14:35
  • dropbox worked well. – mkl Sep 27 '19 at 16:51
  • You appear to trigger a flattening bug in your PDFBox version by drawing the image in the appearance stream at `imageXPosition, imageYPosition` and putting the bounding box there. If you drew it at `0,0` and used `new PDRectangle(0, 0, imageWidth, imageHeight)` as bounding box, you most likely wouldn't trigger that bug. I'll look into that later, probably early next week. (Actually I think I remember there was a similar case not too long ago...) – mkl Sep 27 '19 at 17:31
  • Ah, yes. In a [comment to the answer](https://stackoverflow.com/questions/46799087/how-to-insert-image-programmatically-in-to-acroform-field-using-java-pdfbox/46820098#comment95015641_46820098) you based your code on I pointed to [another answer](https://stackoverflow.com/a/54091766/1729265). In the smaller printed bottom part of the answer I explain the problem. An issue has been opened for this, [PDFBOX-4430](https://issues.apache.org/jira/browse/PDFBOX-4430) but its not yet resolved. – mkl Sep 27 '19 at 17:41
  • @mkl Sorry for not answering sooner. I didn't receive notifications about the new comments. Thank you for the informations! – Nico Oct 04 '19 at 10:59

0 Answers0