更新:这适用于 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
答案 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;
}