Reputation: 367
I'm using grails with pdfbox plugin. I'd like to print checkboxes in pdf some are checked and some are not. To print checkbox I did not a direct way(Even by using PDCheckbox class). So I've used the other way to print the checkbox with tick mark using the below code:
public static writeInputFieldToPDFPage( PDPage pdPage, PDDocument document, Float x, Float y, Boolean ticked) {
PDFont font = PDType1Font.HELVETICA
PDResources res = new PDResources()
String fontName = res.addFont(font)
String da = ticked?"/" + fontName + " 10 Tf 0 0.4 0 rg":""
COSDictionary acroFormDict = new COSDictionary()
acroFormDict.setBoolean(COSName.getPDFName("NeedAppearances"), true)
acroFormDict.setItem(COSName.FIELDS, new COSArray())
acroFormDict.setItem(COSName.DA, new COSString(da))
PDAcroForm acroForm = new PDAcroForm(document, acroFormDict)
acroForm.setDefaultResources(res)
document.getDocumentCatalog().setAcroForm(acroForm)
PDGamma colourBlack = new PDGamma()
PDAppearanceCharacteristicsDictionary fieldAppearance =
new PDAppearanceCharacteristicsDictionary(new COSDictionary())
fieldAppearance.setBorderColour(colourBlack)
if(ticked) {
COSArray arr = new COSArray()
arr.add(new COSFloat(0.89f))
arr.add(new COSFloat(0.937f))
arr.add(new COSFloat(1f))
fieldAppearance.setBackground(new PDGamma(arr))
}
COSDictionary cosDict = new COSDictionary()
COSArray rect = new COSArray()
rect.add(new COSFloat(x))
rect.add(new COSFloat(new Float(y-5)))
rect.add(new COSFloat(new Float(x+10)))
rect.add(new COSFloat(new Float(y+5)))
cosDict.setItem(COSName.RECT, rect)
cosDict.setItem(COSName.FT, COSName.getPDFName("Btn")) // Field Type
cosDict.setItem(COSName.TYPE, COSName.ANNOT)
cosDict.setItem(COSName.SUBTYPE, COSName.getPDFName("Widget"))
if(ticked) {
cosDict.setItem(COSName.TU, new COSString("Checkbox with PDFBox"))
}
cosDict.setItem(COSName.T, new COSString("Chk"))
//Tick mark color and size of the mark
cosDict.setItem(COSName.DA, new COSString(ticked?"/F0 10 Tf 0 0.4 0 rg":"/FF 1 Tf 0 0 g"))
cosDict.setInt(COSName.F, 4)
PDCheckbox checkbox = new PDCheckbox(acroForm, cosDict)
checkbox.setFieldFlags(PDCheckbox.FLAG_READ_ONLY)
checkbox.setValue("Yes")
checkbox.getWidget().setAppearanceCharacteristics(fieldAppearance)
pdPage.getAnnotations().add(checkbox.getWidget())
acroForm.getFields().add(checkbox)
}
This code is working fine in my application, this method is adding checkboxes with tick marks also. But I can see those rectangle checkboxes or tick marks in only pdf readers, not in all other readers(Like chrome default pdf viewer), and even when I try to print the pdf its not printing the checkboxes, rather its printing some random ASCII numbers.
Please let me know if there is any other way to do this or even if I have to refactor the code.
Upvotes: 0
Views: 2286
Reputation: 77
@mkl has a good answer, but I think it can be simplified a little bit in case you already have a Document
and just want to add a PDCheckbox
(scala):
def addCheckboxField(
doc: PDDocument,
form: PDAcroForm,
name: String,
pg: Int, // page number
x: Float,
y: Float,
width: Float,
height: Float
) = {
val normalAppearances = new COSDictionary()
normalAppearances.setItem(
"Yes", {
val appearanceStream = new PDAppearanceStream(doc)
appearanceStream.setResources(new PDResources())
appearanceStream
}
)
val appearanceDictionary = new PDAppearanceDictionary()
appearanceDictionary.setNormalAppearance(new PDAppearanceEntry(normalAppearances))
appearanceDictionary.setDownAppearance(new PDAppearanceEntry(normalAppearances))
val field = new PDCheckBox(form)
field.setPartialName(name)
val widget = field.getWidgets.get(0)
widget.setAppearance(appearanceDictionary)
form.getFields.add(field)
val page = doc.getPage(pg)
widget.setRectangle(new PDRectangle(x, y, width, height))
widget.setPage(page)
widget.setPrinted(true)
page.getAnnotations().add(widget)
// do what you want with it
field.unCheck()
}
It's likely there are other simplifications that can be made, but this is what worked for me.
PdfBox version: 2.0.21
Upvotes: 0
Reputation: 95918
Your AcroForm checkbox field construction is wrong: You treat it as a text field for which a PDF reader should create an appearance based on the default appearance (DA) value of the field in particular if NeedAppearances is true.
Checkboxes are different, though: you do have to supply an appearance stream at least for the on state, cf. the specification ISO 32000-1:
A check box field represents one or more check boxes that toggle between two states, on and off, when manipulated by the user with the mouse or keyboard. Its field type shall be Btn and its Pushbutton and Radio flags (see Table 226) shall both be clear. Each state can have a separate appearance, which shall be defined by an appearance stream in the appearance dictionary of the field’s widget annotation (see 12.5.5, “Appearance Streams”). The appearance for the off state is optional but, if present, shall be stored in the appearance dictionary under the name Off. Yes should be used as the name for the on state.
(ISO 32000-1 section 12.7.4.2.3 "Check Boxes")
Thus, instead of constructing a DA entry you have to construct an AP ("appearances") entry, itself a dictionary with at least a N ("normal appearances") entry, itself a dictionary with at least an entry for the on state appearance which is recommended to be called Yes.
The specification provides an example which shows a typical check box definition:
1 0 obj
<< /FT /Btn
/T (Urgent)
/V /Yes
/AS /Yes
/AP << /N << /Yes 2 0 R /Off 3 0 R>>
>>
endobj
2 0 obj
<< /Resources 20 0 R
/Length 104
>>
stream
q
0 0 1 rg
BT
/ZaDb 12 Tf
0 0 Td
(4) Tj
ET
Q
endstream
endobj
3 0 obj
<< /Resources 20 0 R
/Length 104
>>
stream
q
0 0 1 rg
BT
/ZaDb 12 Tf
0 0 Td
(8) Tj
ET
Q
endstream
endobj
(The resources in 20 0 obj
appear to include a font resource named ZaDb referencing ZapfDingbats.)
By the way, you mention that there is a PDF viewer which actually displays a tick for your document as is. You might want to inform their development that they are doing the wrong thing there.
In a comment you asked for sample code and indicated that it was ok if it were for a current 2.0.x version of PDFBox. So I tried it and came up with this code:
PDDocument document = new PDDocument();
PDPage page = new PDPage();
document.addPage(page);
PDAcroForm acroForm = new PDAcroForm(document);
document.getDocumentCatalog().setAcroForm(acroForm);
COSDictionary normalAppearances = new COSDictionary();
PDAppearanceDictionary pdAppearanceDictionary = new PDAppearanceDictionary();
pdAppearanceDictionary.setNormalAppearance(new PDAppearanceEntry(normalAppearances));
pdAppearanceDictionary.setDownAppearance(new PDAppearanceEntry(normalAppearances));
PDAppearanceStream pdAppearanceStream = new PDAppearanceStream(document);
pdAppearanceStream.setResources(new PDResources());
try (PDPageContentStream pdPageContentStream = new PDPageContentStream(document, pdAppearanceStream))
{
pdPageContentStream.setFont(PDType1Font.ZAPF_DINGBATS, 14.5f);
pdPageContentStream.beginText();
pdPageContentStream.newLineAtOffset(3, 4);
pdPageContentStream.showText("\u2714");
pdPageContentStream.endText();
}
pdAppearanceStream.setBBox(new PDRectangle(18, 18));
normalAppearances.setItem("Yes", pdAppearanceStream);
pdAppearanceStream = new PDAppearanceStream(document);
pdAppearanceStream.setResources(new PDResources());
try (PDPageContentStream pdPageContentStream = new PDPageContentStream(document, pdAppearanceStream))
{
pdPageContentStream.setFont(PDType1Font.ZAPF_DINGBATS, 14.5f);
pdPageContentStream.beginText();
pdPageContentStream.newLineAtOffset(3, 4);
pdPageContentStream.showText("\u2718");
pdPageContentStream.endText();
}
pdAppearanceStream.setBBox(new PDRectangle(18, 18));
normalAppearances.setItem("Off", pdAppearanceStream);
PDCheckBox checkBox = new PDCheckBox(acroForm);
acroForm.getFields().add(checkBox);
checkBox.setPartialName("CheckBoxField");
checkBox.setFieldFlags(4);
List<PDAnnotationWidget> widgets = checkBox.getWidgets();
for (PDAnnotationWidget pdAnnotationWidget : widgets)
{
pdAnnotationWidget.setRectangle(new PDRectangle(50, 750, 18, 18));
pdAnnotationWidget.setPage(page);
page.getAnnotations().add(pdAnnotationWidget);
pdAnnotationWidget.setAppearance(pdAppearanceDictionary);
}
// checkBox.setReadOnly(true);
checkBox.check();
// checkBox.unCheck();
document.save(new File(RESULT_FOLDER, "CheckBox.pdf"));
document.close();
(CreateCheckBox test testCheckboxForSureshGoud
)
Be sure to use either
checkBox.check();
or
checkBox.unCheck();
as otherwise the state of the box is undefined.
Upvotes: 3