Problems displaying Radiobutton using PDFBox

I used the code from the answer to this question to create my radio objects: How to create a group of radio buttons with PDFBox 2.0

After I created my PDF file and tried to read its (software) selected value, this code worked fine:

    PDDocumentCatalog catalog = doc.getDocumentCatalog();
    PDAcroForm form = catalog.getAcroForm();
    List<PDField> fields = form.getFields();

    for(PDField field: fields) {
        Object value = field.getValueAsString();
        String name = field.getFullyQualifiedName();
        if (field instanceof PDRadioButton) {
            // value is correct and field is instance of PDRadioButton works too
        }

    }

When I open a PDF file in Acrobat Reader DC, make changes and save it again, the code no longer works. There is no longer an instance of PDRadioButton, and the value is always an empty string. enter image description here

When I open a PDF file in Acrobat Touch, it does not even display correctly. enter image description here

(When I open the version previously edited by Acrobat Reader DC, Acrobat Touch may display it correctly)

Any suggestions what could be wrong with the code?

Here is a minimal example that behaves the same:

package test;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceCharacteristicsDictionary;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDRadioButton;


public class WriterTest {
    public static void main(String[] args) {
        try {
            PDDocument document = new PDDocument();
            PDPage page = new PDPage(PDRectangle.A4);

            document.addPage(page);

            PDAcroForm acroForm = new PDAcroForm(document);
            acroForm.setNeedAppearances(true);
            acroForm.setXFA(null);
            document.getDocumentCatalog().setAcroForm(acroForm);

            PDFont font = PDType1Font.HELVETICA;

            PDResources res = new PDResources();
            COSName fontName = res.add(font);
            acroForm.setDefaultResources(res);
            acroForm.setDefaultAppearance('/' + fontName.getName() + " 10 Tf 0 g");

            PDPageContentStream contents = new PDPageContentStream(document, page);

            List<String> options = Arrays.asList("a", "b", "c");
            PDRadioButton radioButton = new PDRadioButton(acroForm);
            radioButton.setPartialName("RadioButtonParent");
            radioButton.setExportValues(options);
            radioButton.getCOSObject().setName(COSName.DV, options.get(1));

            List<PDAnnotationWidget> widgets = new ArrayList<>();
            for (int i = 0; i < options.size(); i++) {
                PDRadioButton subRadioButtons = new PDRadioButton(acroForm);
                subRadioButtons.setPartialName("RadioButton");

                PDAppearanceCharacteristicsDictionary fieldAppearance = new PDAppearanceCharacteristicsDictionary(new COSDictionary());
                fieldAppearance.setBorderColour(new PDColor(new float[] { 0, 0, 0 }, PDDeviceRGB.INSTANCE));

                PDAnnotationWidget widget = subRadioButtons.getWidgets().get(0);
                widget.setRectangle(new PDRectangle(30, 811 - i * (21), 16, 16));
                widget.setAppearanceCharacteristics(fieldAppearance);

                widgets.add(widget);
                page.getAnnotations().add(widget);

                contents.beginText();
                contents.setFont(font, 10);
                contents.newLineAtOffset(56, 811 - i * (21) + 4);
                contents.showText(options.get(i));
                contents.endText();
            }
            radioButton.setWidgets(widgets);
            acroForm.getFields().add(radioButton);

            contents.close();
            try (FileOutputStream output = new FileOutputStream("test.pdf")) {
                document.save(output);
            }
            document.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
0
1

. Javadoc getFields() :

/**
 * This will return all of the documents root fields.
 * 
 * A field might have children that are fields (non-terminal field) or does not
 * have children which are fields (terminal fields).
 * 
 * The fields within an AcroForm are organized in a tree structure. The documents root fields 
 * might either be terminal fields, non-terminal fields or a mixture of both. Non-terminal fields
 * mark branches which contents can be retrieved using {@link PDNonTerminalField#getChildren()}.
 * 
 * @return A list of the documents root fields.
 * 
 */
public List<PDField> getFields()

( ), :

PDDocumentCatalog catalog = doc.getDocumentCatalog();
PDAcroForm form = catalog.getAcroForm();
Iterator<PDField> fieldIterator = form.getFieldIterator();
while (fieldIterator.hasNext())
{
    PDField field = fieldIterator.next();
    // ... do stuff ...
}

.

. "a", "b" "Choice1" "c".

, :

PDAppearanceDictionary appearance = new PDAppearanceDictionary();
COSDictionary dict = new COSDictionary();
dict.setItem(COSName.getPDFName("Off"), new COSDictionary());
dict.setItem(COSName.getPDFName(options.get(i)), new COSDictionary());
PDAppearanceEntry appearanceEntry = new PDAppearanceEntry(dict);
appearance.setNormalAppearance(appearanceEntry);
widget.setAppearance(appearance);

"." "on-Option" .

17.1.2017:

:

PDDocument document = new PDDocument();
PDPage page = new PDPage(PDRectangle.A4);

document.addPage(page);

PDAcroForm acroForm = new PDAcroForm(document);

// not needed, we have appearance streams
//acroForm.setNeedAppearances(true);

acroForm.setXFA(null);
document.getDocumentCatalog().setAcroForm(acroForm);

PDFont font = PDType1Font.HELVETICA;

PDResources res = new PDResources();
COSName fontName = res.add(font);
acroForm.setDefaultResources(res);
acroForm.setDefaultAppearance('/' + fontName.getName() + " 10 Tf 0 g");

PDPageContentStream contents = new PDPageContentStream(document, page);

List<String> options = Arrays.asList("a", "b", "c");
PDRadioButton radioButton = new PDRadioButton(acroForm);
radioButton.setPartialName("RadioButtonParent");
// removed per advice of Maruan Sahyoun, setValue didn't work anymore
//radioButton.setExportValues(options);
radioButton.getCOSObject().setName(COSName.DV, options.get(1));
radioButton.setFieldFlags(49152);
int on = 1;

List<PDAnnotationWidget> widgets = new ArrayList<>();
for (int i = 0; i < options.size(); i++)
{
    PDAppearanceCharacteristicsDictionary fieldAppearance = new PDAppearanceCharacteristicsDictionary(new COSDictionary());
    fieldAppearance.setBorderColour(new PDColor(new float[] { 0, 0, 0 }, PDDeviceRGB.INSTANCE));
    PDAnnotationWidget widget = new PDAnnotationWidget();
    widget.setRectangle(new PDRectangle(30, 811 - i * (21), 16, 16));
    widget.setAppearanceCharacteristics(fieldAppearance);
    widget.setAnnotationFlags(4);
    widget.setPage(page);
    widget.setParent(radioButton);

    String offNString = "0 G\n"
            + "q\n"
            + "  1 0 0 1 8 8 cm\n"
            + "  7.5 0 m\n"
            + "  7.5 4.1423 4.1423 7.5 0 7.5 c\n"
            + "  -4.1423 7.5 -7.5 4.1423 -7.5 0 c\n"
            + "  -7.5 -4.1423 -4.1423 -7.5 0 -7.5 c\n"
            + "  4.1423 -7.5 7.5 -4.1423 7.5 0 c\n"
            + "  s\n"
            + "Q";
    String onNString = "0 G\n"
            + "q\n"
            + "  1 0 0 1 8 8 cm\n"
            + "  7.5 0 m\n"
            + "  7.5 4.1423 4.1423 7.5 0 7.5 c\n"
            + "  -4.1423 7.5 -7.5 4.1423 -7.5 0 c\n"
            + "  -7.5 -4.1423 -4.1423 -7.5 0 -7.5 c\n"
            + "  4.1423 -7.5 7.5 -4.1423 7.5 0 c\n"
            + "  s\n"
            + "Q\n"
            + "q\n"
            + "  1 0 0 1 8 8 cm\n"
            + "  3.5 0 m\n"
            + "  3.5 1.9331 1.9331 3.5 0 3.5 c\n"
            + "  -1.9331 3.5 -3.5 1.9331 -3.5 0 c\n"
            + "  -3.5 -1.9331 -1.9331 -3.5 0 -3.5 c\n"
            + "  1.9331 -3.5 3.5 -1.9331 3.5 0 c\n"
            + "  f\n"
            + "Q";
    String offDString = "0.749023 g\n"
            + "q\n"
            + "  1 0 0 1 8 8 cm\n"
            + "  8 0 m\n"
            + "  8 4.4185 4.4185 8 0 8 c\n"
            + "  -4.4185 8 -8 4.4185 -8 0 c\n"
            + "  -8 -4.4185 -4.4185 -8 0 -8 c\n"
            + "  4.4185 -8 8 -4.4185 8 0 c\n"
            + "  f\n"
            + "Q\n"
            + "0 G\n"
            + "q\n"
            + "  1 0 0 1 8 8 cm\n"
            + "  7.5 0 m\n"
            + "  7.5 4.1423 4.1423 7.5 0 7.5 c\n"
            + "  -4.1423 7.5 -7.5 4.1423 -7.5 0 c\n"
            + "  -7.5 -4.1423 -4.1423 -7.5 0 -7.5 c\n"
            + "  4.1423 -7.5 7.5 -4.1423 7.5 0 c\n"
            + "  s\n"
            + "Q";
    String onDString = "0.749023 g\n"
            + "q\n"
            + "  1 0 0 1 8 8 cm\n"
            + "  8 0 m\n"
            + "  8 4.4185 4.4185 8 0 8 c\n"
            + "  -4.4185 8 -8 4.4185 -8 0 c\n"
            + "  -8 -4.4185 -4.4185 -8 0 -8 c\n"
            + "  4.4185 -8 8 -4.4185 8 0 c\n"
            + "  f\n"
            + "Q\n"
            + "0 G\n"
            + "q\n"
            + "  1 0 0 1 8 8 cm\n"
            + "  7.5 0 m\n"
            + "  7.5 4.1423 4.1423 7.5 0 7.5 c\n"
            + "  -4.1423 7.5 -7.5 4.1423 -7.5 0 c\n"
            + "  -7.5 -4.1423 -4.1423 -7.5 0 -7.5 c\n"
            + "  4.1423 -7.5 7.5 -4.1423 7.5 0 c\n"
            + "  s\n"
            + "Q\n"
            + "0 g\n"
            + "q\n"
            + "  1 0 0 1 8 8 cm\n"
            + "  3.5 0 m\n"
            + "  3.5 1.9331 1.9331 3.5 0 3.5 c\n"
            + "  -1.9331 3.5 -3.5 1.9331 -3.5 0 c\n"
            + "  -3.5 -1.9331 -1.9331 -3.5 0 -3.5 c\n"
            + "  1.9331 -3.5 3.5 -1.9331 3.5 0 c\n"
            + "  f\n"
            + "Q";
    COSDictionary apNDict = new COSDictionary();
    COSStream offNStream = new COSStream();
    offNStream.setItem(COSName.BBOX, new PDRectangle(16, 16));
    offNStream.setItem(COSName.FORMTYPE, COSInteger.ONE);
    offNStream.setItem(COSName.TYPE, COSName.XOBJECT);
    offNStream.setItem(COSName.SUBTYPE, COSName.FORM);
    OutputStream os = offNStream.createOutputStream(COSName.FLATE_DECODE);
    os.write(offNString.getBytes());
    os.close();
    apNDict.setItem(COSName.Off, offNStream);

    COSStream onNStream = new COSStream();
    onNStream.setItem(COSName.BBOX, new PDRectangle(16, 16));
    onNStream.setItem(COSName.FORMTYPE, COSInteger.ONE);
    onNStream.setItem(COSName.TYPE, COSName.XOBJECT);
    onNStream.setItem(COSName.SUBTYPE, COSName.FORM);
    os = onNStream.createOutputStream(COSName.FLATE_DECODE);
    os.write(onNString.getBytes());
    os.close();
    apNDict.setItem(options.get(i), onNStream);

    COSDictionary apDDict = new COSDictionary();
    COSStream offDStream = new COSStream();
    offDStream.setItem(COSName.BBOX, new PDRectangle(16, 16));
    offDStream.setItem(COSName.FORMTYPE, COSInteger.ONE);
    offDStream.setItem(COSName.TYPE, COSName.XOBJECT);
    offDStream.setItem(COSName.SUBTYPE, COSName.FORM);
    os = offDStream.createOutputStream(COSName.FLATE_DECODE);
    os.write(offDString.getBytes());
    os.close();
    apDDict.setItem(COSName.Off, offDStream);

    COSStream onDStream = new COSStream();
    onDStream.setItem(COSName.BBOX, new PDRectangle(16, 16));
    onDStream.setItem(COSName.FORMTYPE, COSInteger.ONE);
    onDStream.setItem(COSName.TYPE, COSName.XOBJECT);
    onDStream.setItem(COSName.SUBTYPE, COSName.FORM);
    os = onDStream.createOutputStream(COSName.FLATE_DECODE);
    os.write(onDString.getBytes());
    os.close();
    apDDict.setItem(options.get(i), onDStream);

    PDAppearanceDictionary appearance = new PDAppearanceDictionary();
    PDAppearanceEntry appearanceNEntry = new PDAppearanceEntry(apNDict);
    appearance.setNormalAppearance(appearanceNEntry);
    PDAppearanceEntry appearanceDEntry = new PDAppearanceEntry(apDDict);
    appearance.setDownAppearance(appearanceDEntry);

    widget.setAppearance(appearance);

    widget.setAppearanceState(i == on ? options.get(i) : "Off");

    widgets.add(widget);
    page.getAnnotations().add(widget);

    contents.beginText();
    contents.setFont(font, 10);
    contents.newLineAtOffset(56, 811 - i * (21) + 4);
    contents.showText(options.get(i));
    contents.endText();
}
radioButton.setWidgets(widgets);
acroForm.getFields().add(radioButton);

contents.close();
try (FileOutputStream output = new FileOutputStream("test.pdf"))
{
    document.save(output);
}
document.close();

, Adobe ( "" ), setNeedAppearances(true) widget.setAppearance(appearance);. Adobe , , . PDFDebugger, , AP .

, .

PDFBox . , . PDCircleAppearanceHandler.

+1

Source: https://habr.com/ru/post/1695836/


All Articles