【问题标题】:PDFBox Form fill - saveIncremental does not workPDFBox 表单填写 - saveIncremental 不起作用
【发布时间】:2017-03-15 06:58:27
【问题描述】:

我有一个 pdf 文件,其中包含一些我想从 java 填写的表单字段。现在,我正在尝试填写一张我通过其名称找到的表格。我的代码如下所示:

    File file = new File("c:/Testy/luxmed/Skierowanie3.pdf");
    PDDocument document = PDDocument.load(file);
    PDDocumentCatalog doc = document.getDocumentCatalog();
    PDAcroForm Form = doc.getAcroForm();

    String formName = "topmostSubform[0].Page1[0].pana_pania[0]";
    PDField f = Form.getField(formName);
    setField(document, formName, "Artur");
    System.out.println("New value 2nd: " + f.getValueAsString());

    document.saveIncremental(new FileOutputStream("c:/Testy/luxmed/nowy_pd3.pdf"));
    document.close();

还有这个:

public static void setField(PDDocument pdfDocument, String name, String Value) throws IOException 
{
    PDDocumentCatalog docCatalog = pdfDocument.getDocumentCatalog();
    PDAcroForm acroForm = docCatalog.getAcroForm();
    PDField field = acroForm.getField(name);

    if (field instanceof PDCheckBox){
        field.setValue("Yes");
    }
    else if (field instanceof PDTextField){
        System.out.println("Original value: " + field.getValueAsString());
        field.setValue(Value);
        System.out.println("New value: " + field.getValueAsString());
    }
    else{
        System.out.println("Nie znaleziono pola");
    }
}

正如 system.out 所述,该值设置正确,但在新生成的 pdf 文件中,新值未显示(显示原始字符串),所以我猜增量保存无法正常工作。我错过了什么?

我使用 2.0.2 版本的 pdfbox,这是我使用的 pdf 文件:pdf

【问题讨论】:

  • 在以前的 PDFBox 版本中,必须为所有更改的对象设置 setNeedToBeUpdatedtrue,包括从指向它们的目录开始的每个对象链。我认为这个过程有待改进,但我不知道它是否已经改进了。
  • 还是这样。重要的是拥有从目录到您关心的更新对象的“链”,因此在这里:目录 - acroform - 字段列表 - 字段/注释。如果 PDF 已签名,则 saveIncremental 不起作用。另见问题 PDFBOX-45 及相关问题。可能是最古老的未解决问题。
  • 查看stackoverflow.com/questions/41467415/…了解如何使用setNeedToBeupdated

标签: java pdf pdfbox


【解决方案1】:

一般

使用 PDFBox 2.0.x 将更改保存为 PDF 时,您必须将属性 NeedToBeUpdated 设置为 true,以便更改每个 PDF 对象。此外,该对象必须可以通过引用链从 PDF 目录中访问,并且此链中的每个 PDF 对象还必须将属性 NeedToBeUpdated 设置为 true

这是由于 PDFBox 增量保存的方式,从目录开始它检查 NeedToBeUpdated 属性,如果设置为 true,PDFBox 存储对象,只有在这种情况下,它才会更深入地递归到从此对象引用的对象以搜索要存储的更多对象。

这意味着某些对象不必要地被标记为NeedToBeUpdated,例如PDF 目录本身,在某些情况下,这甚至违背了增量更新的目的,见下文。

如果是 OP 的文件

设置NeedToBeUpdated 属性

一方面必须扩展setField 方法来标记字段字典链,直到并包括更改的字段以及外观:

public static void setField(PDDocument pdfDocument, String name, String Value) throws IOException 
{
    PDDocumentCatalog docCatalog = pdfDocument.getDocumentCatalog();
    PDAcroForm acroForm = docCatalog.getAcroForm();
    PDField field = acroForm.getField(name);

    if (field instanceof PDCheckBox) {
        field.setValue("Yes");
    }
    else if (field instanceof PDTextField) {
        System.out.println("Original value: " + field.getValueAsString());
        field.setValue(Value);
        System.out.println("New value: " + field.getValueAsString());
    }
    else {
        System.out.println("Nie znaleziono pola");
    }

    // vvv--- new 
    COSDictionary fieldDictionary = field.getCOSObject();
    COSDictionary dictionary = (COSDictionary) fieldDictionary.getDictionaryObject(COSName.AP);
    dictionary.setNeedToBeUpdated(true);
    COSStream stream = (COSStream) dictionary.getDictionaryObject(COSName.N);
    stream.setNeedToBeUpdated(true);
    while (fieldDictionary != null)
    {
        fieldDictionary.setNeedToBeUpdated(true);
        fieldDictionary = (COSDictionary) fieldDictionary.getDictionaryObject(COSName.PARENT);
    }
    // ^^^--- new 
}

(FillInFormSaveIncremental 方法setField)

另一方面,必须扩展主代码以标记从目录到字段数组的链:

PDDocument document = PDDocument.load(...);
PDDocumentCatalog doc = document.getDocumentCatalog();
PDAcroForm Form = doc.getAcroForm();

String formName = "topmostSubform[0].Page1[0].pana_pania[0]";
PDField f = Form.getField(formName);
setField(document, formName, "Artur");
System.out.println("New value 2nd: " + f.getValueAsString());

// vvv--- new 
COSDictionary dictionary = document.getDocumentCatalog().getCOSObject();
dictionary.setNeedToBeUpdated(true);
dictionary = (COSDictionary) dictionary.getDictionaryObject(COSName.ACRO_FORM);
dictionary.setNeedToBeUpdated(true);
COSArray array = (COSArray) dictionary.getDictionaryObject(COSName.FIELDS);
array.setNeedToBeUpdated(true);
// ^^^--- new 

document.saveIncremental(new FileOutputStream(...));
document.close();

(FillInFormSaveIncremental 测试testFillInSkierowanie3)

注意:对于通用 PDF,显然应该引入一些 null 测试...


不幸的是,在 Adob​​e Reader 中打开结果文件会发现程序抱怨更改会禁用文件中的扩展功能。

这是由于 PDFBox 增量保存的怪癖,它需要更新部分中的一些不必要的对象。特别是目录保存在那里,其中包含使用权限签名(授予扩展功能的技术)。重新保存的签名显然不再在其原始修订中的原始位置。因此,无效。

OP OP 很可能希望逐步保存 PDF 以 破坏此签名,但 PDFBox 不允许这样做。哦,好吧……

因此,唯一能做的就是通过完全删除签名来防止警告。

删除使用权签名

我们已经在上面的添加中检索到目录对象,因此删除签名很容易:

COSDictionary dictionary = document.getDocumentCatalog().getCOSObject();
// vvv--- new 
dictionary.removeItem(COSName.PERMS);
// ^^^--- new 
dictionary.setNeedToBeUpdated(true);

(FillInFormSaveIncremental 测试testFillInSkierowanie3)


不幸的是,在 Adob​​e Reader 中打开结果文件会发现程序抱怨文件中缺少用于保存它的扩展功能。

这是因为 Adob​​e Reader 需要扩展功能来保存对 XFA 表单的更改,我们必须在此步骤中删除扩展功能。

但手头的文档是混合 AcroForm 和 XFA 表单文档,Adobe Reader 不需要扩展功能来保存 AcroForm 文档。因此,我们所要做的就是删除 XFA 表单。由于我们的代码只设置 AcroForm 值,所以无论如何这都是个好主意...

删除 XFA 表单

我们已经在上面添加的内容中检索到了 acroform 对象,因此从那里删除引用的 XFA 表单很容易:

dictionary = (COSDictionary) dictionary.getDictionaryObject(COSName.ACRO_FORM);
// vvv--- new 
dictionary.removeItem(COSName.XFA);
// ^^^--- new 
dictionary.setNeedToBeUpdated(true);

(FillInFormSaveIncremental 测试testFillInSkierowanie3)


在 Adob​​e Reader 中打开结果文件会发现,现在可以毫不费力地编辑表单并保存文件。

请注意,此操作需要足够新的 Adob​​e Reader 版本,早期版本(至少到版本 9)确实需要扩展功能,即使将更改保存到 AcroForm 表单

【讨论】:

  • 您好,如何处理签名字段等小部件?我想添加一些签名字段并保存增量,但没有显示,只有当我正常保存时
  • 您是否确定,从目录开始,您的更改有一系列对象链,都标记为NeedToBeUpdated
  • 感谢您的宝贵时间!我在这里stackoverflow.com/questions/62601879/… 的另一个问题中详细阐述了我的评论。你能看看吗?谢谢!
猜你喜欢
  • 2012-11-07
  • 2022-10-04
  • 1970-01-01
  • 1970-01-01
  • 2011-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多