java - PDFBox 表单填写 - saveIncremental 不起作用

标签 java pdf pdfbox

我有一个 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 2.0.x 将更改保存为增量更新时,您必须为每个更改的 PDF 对象将属性 NeedToBeUpdated 设置为 true。此外,该对象必须可以通过引用链从 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 表单也是如此

关于java - PDFBox 表单填写 - saveIncremental 不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42802996/

相关文章:

Java Long Compare 和 ValueOf 方法未定义

php - 寻找像 PDFBox for PHP 这样的 PDF 库

java - 将 BufferedImage 添加到 PDFBox 文档

java - PDFBox - 从图像生成 PDF 时出现问题

java - 从具有不同高度的表格行中提取 pdf 文本(使用 pdfbox 库的 java)

java - Tapestry ,内循环区域

java - 在子类中隐藏方法

java - 在 Intellij IDEA 中将 Java 8 流降级为 Java 7 循环

html - Rpres HTML5 演示文稿 "Save As PDF"(Google Chrome) 显示不正确

iphone - CGContext pdf页面宽高比适合