java - 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();
}
}
}
解决方案
只需将此作为答案添加即可获得更好的格式。
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;
}
推荐阅读
- typescript - 将泛型类型 T 限制为在 TypeScript 中未定义?
- user-interface - 如何使用 Google Apps 脚本检查侧边栏是否由“SpreadsheetApp.getUi().showSidebar(html);”打开 是开放还是不开放?
- kotlin - 检查通道中的元素数量
- r - 为什么在我的训练集中找不到我的随机森林回归预测值?(右)
- android - 带有html5视频的戏剧性屏幕闪烁
- html - div的高度不符合css中的声明
- hardware - 使用ISE模拟与Modelsim连接的程序时映射库失败
- objection.js - 为 objection.js 进行的所有查询打印完整的 SQL
- c - 将十六进制的char *转换为C中的实际十六进制的最简单方法?
- json - Xcode 与 IQAir Api 解析混淆