PDFBox表单填写 - saveIncremental不起作用

时间:2017-03-15 06:58:27

标签: 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文件中,新值未显示(显示原始字符串),因此我猜增量保存无法正常工作。我错过了什么?

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

1 个答案:

答案 0 :(得分:5)

一般

将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

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

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 test testFillInSkierowanie3

注意:与通用PDF一起使用时,显然应该引入一些null测试......

在Adobe Reader中打开结果文件遗憾的是,程序会抱怨有关禁用文件中扩展功能的更改。

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

OP OP很可能希望将PDF逐步保存到而不是打破此签名,但PDFBox不允许这样做。哦,好吧......

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

删除使用权签名

我们已经在上面的附加内容中检索了目录对象,因此删除签名很简单:

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

FillInFormSaveIncremental test testFillInSkierowanie3

不幸的是,在Adobe Reader中打开结果文件会发现该程序抱怨文件中缺少扩展功能以保存它。

这是因为Adobe 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 test testFillInSkierowanie3

在Adobe Reader中打开结果文件,可以看到现在可以不用再编辑表格并保存文件了。

请注意,这需要一个足够新的Adobe Reader版本,早期版本(至少版本9)确实需要扩展功能,即使保存对AcroForm表单的更改