设置值后 PDFBox 不一致的 PDTextField 自动调整大小行为

PDFBox Inconsistent PDTextField Autosize Behavior after setValue

我正在使用 Apache PDFBox 在 PDF 文档上配置 PDTextField,我使用以下方法将 Lato 加载到文档中:

font = PDType0Font.load(
    @j_pd_document,
    java.io.FileInputStream.new('/path/to/Lato-Regular.ttf')
) # => Lato-Regular

font_name = pd_default_resources.add(font).get_name # => F4

然后我将 font_name 传递给 PDTextFielddefault_appearance_string,如下所示:

j_text_field.set_default_appearance("/#{font_name} 0 Tf 0 g") # where font_name is
                                                              # passed in from above

当我在 PDTextField 上继续调用 setValue 时,现在出现了问题。因为我将 defaultAppearanceString 中的 font_size 设置为 0,根据库的 example,文本应自行缩放以适合文本框的给定区域。但是,此 'scale-to-fit' 的行为对于某些字段是不一致的:它并不总是选择最大的字体大小以适应 PDTextField。可能有任何进一步的配置可以允许这种情况发生吗?以下是我注意到出现此问题的 PDF。

未填充,已加载字体: http://www.filedropper.com/0postfontload

已填充,文本框文本大小不一致: http://www.filedropper.com/file_327

旁注:我通过 jruby 使用 PDFBox,它只是一个允许 Ruby 调用 Java 库的集成层。库的所有 java 方法都可用;像 thisExampleMethod 这样的 java 方法会一对一地转换成 ruby this_example_method.


更新

针对评论,第二个上传文件示例中不正确的外观是:

尤其是Resident Name 字段、Phone 字段和Care Providers Address 字段的外观显得很显眼。 OP只提到了前两者。

让我们检查一下这些字段;所有屏幕截图都是在 MS Windows:

上使用 Adob​​e Reader DC 制作的

居民姓名字段

填写的居民姓名字段如下所示

虽然高度合适,但字形比应有的要窄。其实这个效果在原PDF中已经可以看到了:

此水平压缩是由具有与分别匹配的正常外观流边界框不同的纵横比的字段小部件矩形引起的:

  • 小部件矩形:[ 45.72 601.44 118.924 615.24 ][ 119.282 601.127 192.486 614.927 ],即两种情况下均为 73.204*13.8。
  • 外观边界框:[ 0 0 147.24 13.8 ],即147.24*13.8。

因此它们具有相同的高度,但外观边界框的宽度大约是小部件矩形的两倍。因此,当外观显示在小部件矩形中时,通常在外观流中绘制的文本将被压缩到其宽度的一半。

不幸的是,当设置字段 PDFBox 的值时 re-uses 外观流照原样并且仅更新默认外观的详细信息,即字体名称、字体大小和颜色,以及实际的文本值,显然假设外观的其他属性是有原因的。因此,PDFBox 输出也显示了这种水平压缩

要使 PDFBox 创建正确的外观,需要在设置新值之前删除旧外观。

Phone 个字段

填写的 Phone 字段如下所示

原始文件中也有类似的显示

即使有足够的 space 用于整个单词,也只显示前两个字母,这是由于这些字段的配置:它们被配置为最大长度为 2 个字符的组合字段.

要在此处使用 PDFBox 设置的值完全显示而不是 spaced,您必须删除最大长度(或者至少必须使其不小于您的值的长度)并取消设置梳状标志。

护理提供者地址字段

填写的内容如下所示:

最初它们看起来很相似:

此垂直压缩再次由具有与分别匹配的正常外观流边界框不同的纵横比的字段小部件矩形引起:

  • 一个小部件矩形:[ 278.6 642.928 458.36 657.96 ],即 179.76*15.032。
  • 外观边界框:[ 0 0 179.76 58.56 ],即179.76*58.56。

就像上面的 Resident Name 字段一样,在设置新值以使 PDFBox 创建正确的外观之前,有必要删除旧的外观。

并发症

实际上在填写 Care Providers Address 字段时还有一个问题,删除旧外观后它们看起来像这样:

这是由于 PDFBox 的一个缺点:这些字段被配置为多行文本字段。虽然单行文本字段的 PDFBox 会根据内容正确计算字体大小,然后精细地确保文本在垂直方向上非常适合,但它对多行字段的处理非常粗暴,它选择硬编码字体大小 12 并且不微调垂直位置,参见 AppearanceGeneratorHelper 方法 calculateFontSize(PDFont, PDRectangle)insertGeneratedAppearance(PDAnnotationWidget, PDAppearanceStream, OutputStream).

的代码

因为在您的表单中,这些地址字段无论如何都只有一行高,一个明显的解决方案是使这些字段成为单行字段,即清除 Multiline 标志。

示例代码

使用 Java 可以像这样实现上面解释的解决方案:

final int FLAG_MULTILINE = 1 << 12;
final int FLAG_COMB = 1 << 24;

PDDocument doc = PDDocument.load(originalStream);
PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm();

PDType0Font font = PDType0Font.load(doc, fontStream, false);
String font_name = acroForm.getDefaultResources().add(font).getName();

for (PDField field : acroForm.getFieldTree()) {
    if (field instanceof PDTextField) {
        PDTextField textField = (PDTextField) field;
        textField.getCOSObject().removeItem(COSName.MAX_LEN);
        textField.getCOSObject().setFlag(COSName.FF, FLAG_COMB | FLAG_MULTILINE, false);;
        textField.setDefaultAppearance(String.format("/%s 0 Tf 0 g", font_name));
        textField.getWidgets().forEach(w -> w.getAppearance().setNormalAppearance((PDAppearanceEntry)null));
        textField.setValue("Test");
    }
}

(FillInForm 测试 testFill0DropOldAppearanceNoCombNoMaxNoMultiLine)

示例代码输出的屏幕截图

Resident Name 字段值现在不再垂直压缩:

Phone 和 Care Providers Address 字段现在看起来也合适: