注释内容未与 PDFBox 一起出现

时间:2021-01-27 20:55:42

标签: java pdfbox

更新:这适用于 adobe 阅读器,但不适用于 osx 默认的 pdf 阅读器。我们的许多用户使用默认的 osx 阅读器,所以理想情况下我可以让它在那里工作,我知道它支持注释)

我正在使用 Apache PDFBox 2.0.22 尝试以编程方式向 pdf 添加注释。我运行的代码,并生成一个带有注释的 pdf,但注释的内容文本是空的(见截图)。我做错了什么?

    1  2  3  4  5  6  7  
------------------------
1:  0  9  0  5  7  8  9  38  * #1
2:  0  1  1  8  3  0  3  16  
3:  8  8  3  4  3  1  5  32  
4:  9  0  1  8  5  6  0  29  
5:  7  4  5  7  1  5  7  36  * #2
6:  5  9  8  7  0  0  3  32  
7:  7  1  3  6  6  3  7  33  * #3
------------------------
   36 32 21 45 25 23 34 
    *        *        *  
   #2       #1       #3 

enter image description here

1 个答案:

答案 0 :(得分:0)

只需将其添加为答案即可获得更好的格式。

PDFBOX 不断变化,也许 .constructAppearances() 在某些时候效果更好 - 但与此同时,......我自己按照建议为注释创建了我自己的外观流。但它似乎仍然没有工作。我已经尝试了一切,似乎 - 在每种情况下,有时注释会出现在 Acrobat Reader DC 中,但它们不会在 Chrome 或 Firefox 默认 PDF 查看器中显示。

首先 - 如果您在注释对象本身上设置了内容(),并且如果您不提供自己的外观流 - 像 Acrobat Reader DC 这样的查看器有时会尝试构建他们自己的外观版本 - 这因观众 - 现在,如果您提供自己的,.. 一些观众仍会构建自己的.. 例如交互式元素,例如一个黄色小图标,表示我们在这里有一个注释,如果您将鼠标光标悬停在它上面,它会显示内容 - 这就是为什么,我猜,一些程序员会尝试不调用 setContent() (?)

此外,如果您像我一样选择将 PDAnnotationTextMarkup 与 SUB_TYPE_FREETEXT 子类型一起使用 - 只是在现有 PDF 上写一些东西,Acrobat Reader DC 特别会尝试创建自己的视觉内容 - 它似乎试图修改(更正?)文档的内容,生成并显示稍微更改的版本,这将导致现有数字签名更改状态 - 我们可以根据此 SO q&a 解决此问题。

但似乎有一种更好的方法可以在 PDF 上编写静态文本,而 Acrobat Reader DC 不会尝试修改它,那就是橡皮图章注释。您可以简单地用 PDAnnotationRubberStamp 替换 PDAnnotationTextMarkup(并且 PDFBOX 3.0.0 有另一个名称......)。其他一切都保持不变。

因此,我在所有注释代码中做错的地方是将注释外观的边界框放错了位置。 setBBox 函数将 PDRectangle 作为参数,而 PDAnnotation.getRectangle() 返回一个 PDRectangle - 但第一个不能按原样使用第二个!因为第二个 PDRectangle 坐标是相对于页面左下角的,而我们需要一个以 0, 0 为 X, Y 的矩形。

所以我想出的代码如下(我还没有开始使用 _font 和字体嵌入,这似乎是一个很大的话题..):

private void addAnnotation(String name, PDDocument doc, PDPage page, float x, float y, String text) throws IOException {
  
  List<PDAnnotation> annotations = page.getAnnotations();
  PDAnnotationRubberStamp t = new PDAnnotationRubberStamp();

  t.setAnnotationName(name); // might play important role
  t.setPrinted(true); // always visible
  t.setReadOnly(true); // does not interact with user
  t.setContents(text); 
    
  // calculate realWidth, realHeight according to font size (e.g. using _font.getStringWidth(text))         
  float realWidth = 100, realHeight = 100;  
  PDRectangle rect = new PDRectangle(x, y, realWidth, realHeight);
  t.setRectangle(rect);

  PDAppearanceDictionary ap = new PDAppearanceDictionary();
  ap.setNormalAppearance(createAppearanceStream(doc, t));
  t.setAppearance(ap);
  
  annotations.add(t);
  page.setAnnotations(annotations);

  // these must be set for incremental save to work properly (PDFBOX < 3.0.0 at least?)  
  ap.getCOSObject().setNeedToBeUpdated(true);
  t.getCOSObject().setNeedToBeUpdated(true);
  page.getResources().getCOSObject().setNeedToBeUpdated(true);
  page.getCOSObject().setNeedToBeUpdated(true);
  doc.getDocumentCatalog().getPages().getCOSObject().setNeedToBeUpdated(true);
  doc.getDocumentCatalog().getCOSObject().setNeedToBeUpdated(true);      
}

private void modifyAppearanceStream(PDAppearanceStream aps, PDAnnotation ann) throws IOException {
  PDAppearanceContentStream apsContent = null;
  
  try {
     PDRectangle rect = ann.getRectangle();
     rect = new PDRectangle(0, 0, rect.getWidth(), rect.getHeight()); // need to be relative - this is mega important because otherwise it appears as if nothing is printed
     aps.setBBox(rect); // set bounding box to the dimensions of the annotation itself
     
     // embed our unicode font (NB: yes, this needs to be done otherwise aps.getResources() == null which will cause NPE later during setFont)
     PDResources res = new PDResources();
     _fontName = res.add(_font).getName(); // okay I create _font elsewhere
     aps.setResources(res);
     
     // draw directly on the XObject's content stream
     apsContent = new PDAppearanceContentStream(aps);

     apsContent.beginText();
     apsContent.setFont(PDType1Font.HELVETICA_BOLD, _fontSize); // _font
     apsContent.setTextMatrix(Matrix.getTranslateInstance(0, 1));
     apsContent.showText(ann.getContents());
     apsContent.endText();
  }
  finally {
     if (apsContent != null) {
        try { apsContent.close(); } catch (Exception ex) { log.error(ex.getMessage(), ex); }
     }
  }      

  aps.getResources().getCOSObject().setNeedToBeUpdated(true);
  aps.getCOSObject().setNeedToBeUpdated(true);
}

private  PDAppearanceStream createAppearanceStream(final PDDocument document, PDAnnotation ann) throws IOException
{
  PDAppearanceStream aps = new PDAppearanceStream(document);
  modifyAppearanceStream(aps, ann);
  return aps;
}