Radiobutton显示PDFBox的问题

时间:2017-01-13 09:20:10

标签: java pdfbox

我使用了这个问题的答案中的代码来创建我的radiobuttons: How to Create a Radio Button Group with PDFBox 2.0

创建PDF并尝试从中读取(以编程方式)选择的值后,此代码运行良好:

    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
        }

    }

当我在Acrobat Reader DC中打开PDF时,进行更改并再次保存,代码不再起作用。 PDRadioButton不再有实例,值始终为空字符串。 enter image description here

当我在Acrobat Touch中打开PDF时,它甚至无法正常显示。 enter image description here

(当我打开以前由Acrobat Reader DC编辑的版本时,Acrobat Touch可以正确显示它)

有关代码可能出错的任何建议吗?

以下是行为相同的最小示例:

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

1 个答案:

答案 0 :(得分:3)

您的代码显示字段树的顶部。 getFields()的javadoc对此发出警告:

/**
 * 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);

它为“关闭”和每个按钮的开启选项添加空白外观。

更新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将生成按钮的外观流。涉及一些数学,请参阅here或PDCircleAppearanceHandler中的主干源代码。