首页 > 解决方案 > PDFBox 不显示注释内容

问题描述

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

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

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationTextMarkup;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;

public class Sample{



  public static void main(String[] args)  throws FileNotFoundException, IOException{
    PDDocument doc = PDDocument.load(new File("test.pdf"));

    try {
      //insert new page
      PDPage page = (PDPage) doc.getDocumentCatalog().getPages().get(0);

      List<PDAnnotation> annotations = page.getAnnotations();

      //generate instanse for annotation
      PDAnnotationTextMarkup txtMark = new PDAnnotationTextMarkup(PDAnnotationTextMarkup.SUB_TYPE_HIGHLIGHT);

      //set the rectangle
      PDRectangle position = new PDRectangle();
      position.setLowerLeftX(170);
      position.setLowerLeftY(125);
      position.setUpperRightX(195);
      position.setUpperRightY(140);
      txtMark.setRectangle(position);

      //set the quadpoint
      float[] quads = new float[8];
      //x1,y1
      quads[0] = position.getLowerLeftX();
      quads[1] = position.getUpperRightY() - 2;
      //x2,y2
      quads[2] = position.getUpperRightX();
      quads[3] = quads[1];
      //x3,y3
      quads[4] = quads[0];
      quads[5] = position.getLowerLeftY() - 2;
      //x4,y4
      quads[6] = quads[2];
      quads[7] = quads[5];
      txtMark.setQuadPoints(quads);
      txtMark.setAnnotationName("My annotation");
      txtMark.setTitlePopup("title popup");
      txtMark.setContents("Highlighted since it's important");
      txtMark.setRichContents("Here is some rich content");
      PDColor blue = new PDColor(new float[] { 0, 0, 1 }, PDDeviceRGB.INSTANCE);
      txtMark.setColor(blue);


      annotations.add(txtMark);

      page.setAnnotations(annotations);

      doc.save("test-out.pdf");

    }finally
    {
      doc.close();
    }
  }
}

在此处输入图像描述

标签: javapdfbox

解决方案


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

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

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

此外,如果您像我一样选择使用带有 SUB_TYPE_FREETEXT 子类型的 PDAnnotationTextMarkup - 只是在现有 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;
}   

推荐阅读