Search code examples
javapdfitext7acrobatpdf-form

PDF AcroForm field modified first in Acrobat then in iText7 becomes blank


I am generating some PDF documents with editable form fields using iText7. My code adds the fields to the PDF document with the following code:

    @Override
    public void draw(DrawContext drawContext) {
        super.draw(drawContext);
        
        PdfAcroForm form = PdfAcroForm.getAcroForm(drawContext.getDocument(), true);
        PdfFormField field = isMultiline
                ?   PdfFormField.createMultilineText(drawContext.getDocument(), getOccupiedAreaBBox(), fieldName, "")
                :   PdfFormField.createText(drawContext.getDocument(), getOccupiedAreaBBox(), fieldName, "");
        field.setFontSize(defaultFontSize - 1);
        form.addField(field);
    }

The above is the draw method of an iText custom renderer class.

The generated PDF document works as intended. I can open it in Adobe Acrobat Reader, fill in the editable fields, save it, reopen it, etc.

One of my requirements is to allow for programmatically flattening the form (obviously after a human user has filled it in) so that editing becomes disabled thereafter.

I do this with this code:

    PdfDocument pdf = ... // Read a PDF stream and get the document object
    PdfAcroForm form = PdfAcroForm.getAcroForm(pdf, false);
    form.flattenFields();
    pdf.close()

Now the problem is, when I open the flattened PDF with a PDF reader, the fields are blank.

I did some debugging, and it looks like this problem occurs only when I try to flatten fields that were edited (and saved) with Adobe Reader (version XI). If I edit the fields programmatically, e.g. with iText's PdfFormField#setValue(String) method, the flattened PDF renders the values just fine.

It seems that Adobe Reader is setting some attributes of the form fields that prevent them from being properly flattened... Is that actually the case? And is there any way around it?


Solution

  • Apparently the issue is caused by Adobe Reader: for some reason it saves fields definitions without the /XObject subtype, which iText7 checks inside the form.flattenFields() method.
    This is what the field strings look like in the Eclipse debugger:

    Generated by iText7: <</BBox [0 0 523 109 ] /Filter /FlateDecode /Length 95 /Matrix [1 0 0 1 0 0 ] /Resources <</Font <</F1 23 0 R >> >> /Subtype /Form /Type /XObject >>

    Saved by Acrobat Reader: <</BBox [0.0 0.0 523.0 109.0 ] /Filter /FlateDecode /Length 107 /Resources <</Font <</F1 21 0 R >> >> >>

    As you can see the /Subtype part is missing. Whenever you attempt to disable the form fields, e.g. PdfAcroForm#flattenFields() or PdfFormField#setReadOnly(boolean), iText tries to redraw its content on a PdfCanvas with this code:

      if(xObject != null && xObject.getPdfObject().get(PdfName.Subtype) != null) {
        [...]
        canvas.addXObject(xObject, ...);
      }
    

    Which is skipped, because the Reader field isn't an /XObject anymore.

    My workaround currently is to replace the field with a new one:

        Map<String,PdfFormField> fields = form.getFormFields();
        Set<String> keys = new HashSet<>(fields.keySet());      // avoids concurrent modifications
        
        for(String fieldName : keys) {
            PdfFormField field = fields.get(fieldName);
            PdfDictionary fieldObject = field.getPdfObject();
            
            PdfFormField newField = field.isMultiline() 
                    ? PdfFormField.createMultilineText(pdf, fieldObject.getAsRectangle(PdfName.Rect), fieldName, 
                            field.getValueAsString(), defaultFont, defaultFontSize)
                    : PdfFormField.createText(pdf, fieldObject.getAsRectangle(PdfName.Rect), fieldName, 
                            field.getValueAsString(), defaultFont, defaultFontSize);
            
            form.replaceField(fieldName, newField);
        }
        
        form.flattenFields();
    

    It sort of works, even though the font information are not dynamically set, but this is another story

    Edit:

    So at the end I opened the PDF with iText RUPS. It looks like Acrobat Reader does set the subtype but it is /Widget. The last workaround I found is to manually set the subtype to /XObject and then call form.flatten(). This also preserves font information on the field.

    Please note that manually changing the subtype might break other things. I'm not much of a PDF expert so I can't tell for sure. However right now I'm only changing the subtype and flattening right away, so it shouldn't be a big problem.

    The final version of this code looks like this:

    for(String fieldName : keys) {
        PdfFormField field = fields.get(fieldName);
        PdfDictionary fieldObject = field.getPdfObject();
        
        if(fieldObject.get(PdfName.Subtype) != PdfName.XObject) {
            fieldObject.put(PdfName.Subtype, PdfName.XObject);
        }
    }
    
    form.flattenFields();
    pdf.close();
    

    This way it looks like it's working as intended.