Radiobutton显示PDFBox的问题

我使用了来自这个问题的答案中的代码来创建我的radiobuttons: 如何使用PDFBox 2.0创建单选按钮组

创建我的PDF并尝试从中读取(以编程方式)选择的值后,此代码工作正常:

PDDocumentCatalog catalog = doc.getDocumentCatalog(); PDAcroForm form = catalog.getAcroForm(); List 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不再有实例,值始终为空字符串。 在此处输入图像描述

当我在Acrobat Touch中打开PDF时,它甚至无法正常显示。 在此处输入图像描述

(当我打开之前由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 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 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(); } } } 

您的代码显示字段树的顶部。 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 getFields() 

要获取所有字段(包括非终端字段),请执行以下操作:

 PDDocumentCatalog catalog = doc.getDocumentCatalog(); PDAcroForm form = catalog.getAcroForm(); Iterator 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 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 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中的主干源代码。