Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
683 views
in Technique[技术] by (71.8m points)

java - PDFBox Form fill - saveIncremental does not work

I have a pdf file with some form field that I want to fill from java. Right now I'm trying to fill just one form which I am finding by its name. My code looks like this:

    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();

and this:

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");
    }
}

As system.out states, the value was set correctly, but in new the generated pdf file, new value is not showing up (original String is presented) so I guess the incremental saving does not work properly. What am I missing?

I use 2.0.2 version of pdfbox, and here is pdf file with which I working: pdf

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

In general

When saving changes to a PDF as an incremental update with PDFBox 2.0.x, you have to set the property NeedToBeUpdated to true for every PDF object changed. Furthermore, the object must be reachable from the PDF catalog via a chain of references, and each PDF object in this chain also has to have the property NeedToBeUpdated set to true.

This is due to the way PDFBox saves incrementally, starting from the catalog it inspects the NeedToBeUpdated property, and if it is set to true, PDFBox stores the object, and only in this case it recurses deeper into the objects referenced from this object in search for more objects to store.

In particular this implies that some objects unnecessarily have to be marked NeedToBeUpdated, e.g. the PDF catalog itself, and in some cases this even defeats the purpose of the incremental update at large, see below.

In case of the OP's document

Setting the NeedToBeUpdated properties

On one hand one has to extend the setField method to mark the chain of field dictionaries up to and including the changed field and also the appearance:

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 method setField)

On the other hand the main code has to be extended to mark a chain from the catalog to the fields array:

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)

Beware: for use with generic PDFs one obviously should introduce some null tests...


Opening the result file in Adobe Reader one will unfortunately see that the program complains about changes which disable extended features in the file.

This is due to the quirk in PDFBox' incremental saving that it requires some unnecessary objects in the update section. In particular the catalog is saved there which contains a usage rights signature (the technology granting extended features). The re-saved signature obviously is not at its original position in its original revision anymore. Thus, is invalidated.

Most likely the OP OP wanted to save the PDF incrementally to not break this signature but PDFBox does not permit this. Oh well...

Thus, the only thing one can do is prevent the warning by completely removing the signature.

Removing the usage rights signature

We already have retrieved the catalog object in the additions above, so removing the signature is easy:

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

(FillInFormSaveIncremental test testFillInSkierowanie3)


Opening the result file in Adobe Reader one will unfortunately see that the program complains about missing extended features in the file to save it.

This is due to the fact that Adobe Reader requires extended features to save changes to XFA forms, extended features we had to remove in this step.

But the document at hand is a hybrid AcroForm & XFA form document, and Adobe Reader requires no extended features to save AcroForm documents. Thus, all we have to do is remove the XFA form. As our code only sets the AcroForm value, this is a good idea anyways...

Removing the XFA form

We already have retrieved the acroform object in the additions above, so removing the XFA form referenced from there is easy:

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

(FillInFormSaveIncremental test testFillInSkierowanie3)


Opening the result file in Adobe Reader one will see that one now can without further ado edit the form and save the file.

Beware, a sufficiently new Adobe Reader version is required for this, earlier versions (up to at least version 9) did require extended features even for saving changes to an AcroForm form


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...