java - 用于注释的pdfbox嵌入子集字体

标签 java pdf pdfbox

我正在尝试使用 Apache PDFBOX v2.0.21 来修改现有的 PDF 文档,添加签名和注释。这意味着我正在积极使用增量保存模式。我还嵌入了 LiberationSans 字体以容纳一些 Unicode 字符。对我来说使用 PDF 嵌入字体的子集功能是有意义的,因为完全嵌入 LiberationSans 使 PDF 文件的边长约 200+ KB。
经过多次试验和错误,我终于设法使一些工作 - 除了字体子集之外。我这样做的方法是使用一次初始化 PDFont 对象

  try (InputStream fs = PDFService.class.getResourceAsStream("/static/fonts/LiberationSans-Regular.ttf")) {
     _font = PDType0Font.load(pddoc, fs, true);
  }
然后使用自定义外观流来显示文本。
   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); 
      
      PDRectangle rect = ....;
      t.setRectangle(rect);

      PDAppearanceDictionary ap = new PDAppearanceDictionary();
      ap.setNormalAppearance(createAppearanceStream(doc, t));
      ap.getCOSObject().setNeedToBeUpdated(true);
      t.setAppearance(ap);
      
      annotations.add(t);
      page.setAnnotations(annotations);
      
      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 PDAppearanceStream createAppearanceStream(final PDDocument document, PDAnnotation ann) throws IOException
   {
      PDAppearanceStream aps = new PDAppearanceStream(document);
      PDRectangle rect = ann.getRectangle();
      rect = new PDRectangle(0, 0, rect.getWidth(), rect.getHeight());
      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();
      aps.setResources(res);

      PDAppearanceContentStream apsContent = null;
      
      try {
         // draw directly on the XObject's content stream
         apsContent = new PDAppearanceContentStream(aps);

         apsContent.beginText();
         apsContent.setFont(_font, _fontSize);         
         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);
      return aps;
   }     
这段代码运行,但创建了一个带有点而不是实际字符的 PDF,我猜这意味着字体子集尚未嵌入。此外,我收到以下警告:

2021-04-17 12:33:31.326 WARN 20820 --- [ main] o.a.p.pdmodel.PDAbstractContentStream : attempting to use subset font LiberationSans without proper context


在查看源代码后,我得到并且我想我在创建外观流时搞砸了一些东西 - 不知何故它没有与 PDDocument 连接,并且子集不能正常继续。请注意,当字体完全嵌入时,上面的代码运行良好(即,如果我调用 PDType0Font.load 并将最后一个参数设置为 false)
谁能想到一些提示给我?谢谢!

最佳答案

我不知道——我幸运吗?编程中的幸运常常指向完全错误或误导的事情。不管怎样,如果还有人能指点一下,我的耳朵就大开了……
同样,在查看代码后,我在 PDDocument.save() 中看到了以下内容:

// subset designated fonts
for (PDFont font : fontsToSubset)
{
    font.subset();
}
这在我使用的 PDDocument.saveIncremental() 中没有发生。只是为了弄乱代码,我在对我的文档调用 saveIncremental() 之前执行了以下操作:
 _font.subset(); // you can see in the beginning of the question how _font is created
 _font.getCOSObject().setNeedToBeUpdated(true);
 pddoc.saveIncremental(baos);
信不信由你,但文档已正确保存 - 至少它在 Acrobat Reader DC 和 Chrome & Firefox PDF 查看器中显示正确。请注意,在外观内容流上的 showText() 期间,Unicode 代码点被添加到字体的子集中。
2021 年 4 月 18 日更新 :正如我在评论中提到的,我收到用户的报告,当他们打开修改后的 PDF 文件时,他们开始看到诸如“无法从...中提取嵌入字体 XXXXXX+LiberationSans-Regular”之类的消息。奇怪的是,我在测试期间没有看到这些消息。事实证明,我的 Acrobat Reader DC 副本比他们的新,特别是在连续发布版本 2021.001.20149 中没有显示错误,而在连续发布版本 2020.012.20043 中显示了上述消息。
经过调查,事实证明问题出在我嵌入字体的方式上。我不知道是否存在任何其他方式,而且我对 PDF 规范不太熟悉,不知道其他方式。从上面的代码可以看出,我所做的是为文档加载一次字体,然后在每个注释的外观流的资源字典中自由使用它。因此,注释内容流的所有资源字典都引用了使用 SAME/BaseFont 名称定义的 F1 字体。 PDF 引用,第 3 版。在 p.323 上特别指出:

"... the PostScript name of the font - ... - begins with a tag followed by a plus sign (+). The tag consists of exactly six uppercase letters; the choice of letters is arbitrary, but different subsets in the same PDF file must have different tags..."


一旦我开始为我的每个注释调用 PDType0Font.load 并在为每个注释创建外观流后调用 subset()(当然还有 setNeedToBeUpdated),我看到 BaseName 属性开始看起来确实不同 - 事实上,更旧的2020 版 Acrobat Reader DC 停止提示。
请注意,除了使用 iText RUPS 检查 PDF 内容外,还可以使用 Foxit PDF 查看器至少确保子集字体名称不同。 Acrobat Reader DC 和 PDF-xChange 在 Properties -> Fonts 中只显示初始字体名称,如 LiberationSans,而不显示 6 个字母的唯一前缀。
2021 年 4 月 19 日更新 我仍在解决这个问题 - 因为我仍然收到有关臭名昭著的“无法提取嵌入字体”消息的报告。该消息的原始原因很可能不是(或不仅)不同子集具有相同 BaseFont 名称的事实。我观察到的一件事是,在某些计算机上,我使用的图章注释会导致 Acrobat Reader DC 自动打开所谓的“评论 Pane ” - 有选项可以关闭此自动功能(首选项 -> 评论 ->打开带有注释的 PDF 时显示注释 Pane )。当此 Pane 手动或自动打开时,会出现错误消息(我很想知道为什么相同版本的 Acrobat Reader DC 对不同机器的行为不同)。我认为 Acrobat Reader 尝试提取字体的完整版本并失败了,因为它只是一个子集。但是,我想,这与文档的语义内容无关 - 文档仍然通过“qpdf --check”。我目前正在尝试寻找是否可以限制图章以不允许评论 - 即某种方法来禁用 Acrobat Reader DC 中的评论 Pane ,尽管我希望渺茫。
2021 年 4 月 20 日更新 开了一个新问题 here

关于java - 用于注释的pdfbox嵌入子集字体,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67137356/

相关文章:

c# - 如何从 PDF 中读取日语字符?

java - Apache PDFBox : Get alignment and font from a PDAnnotationWidget or PDTextField

java - 如何使用pdf框中的书签选择pdf页面?

java - 需要一种可中断的方式来监听工作线程中的 UDP 数据包

java - org.json.JSONException : Value Array of type java. lang.String 无法转换为 JSONObject

java - 如何从JideSoft TreeTable中删除树线

java - 有人有处理 Nuance 的 OmniPage SDK 的 XML 格式的经验吗?

java - 该输入流是否已关闭?

javascript - 如何通过jspdf将单个图像放置在多个页面上?

java - 如何设置PDFBox的字符编码