Skip to content

Commit 789caaa

Browse files
committed
Initial experimental rendering implementation checkbox
DEVSIX-7361
1 parent 4549b99 commit 789caaa

30 files changed

+1128
-69
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.itextpdf.commons.utils;
2+
3+
public final class ExperimentalFeatures {
4+
public static boolean ENABLE_EXPERIMENTAL_CHECKBOX_RENDERING = false;
5+
6+
private ExperimentalFeatures() {
7+
// utility class
8+
}
9+
10+
}

forms/src/main/java/com/itextpdf/forms/exceptions/FormsExceptionMessageConstant.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public final class FormsExceptionMessageConstant {
4444
public static final String APEARANCE_NAME_MUST_BE_PROVIDED = "Appearance name must be provided";
4545
public static final String WIDGET_RECTANGLE_MUST_BE_PROVIDED = "Widget rectangle must be provided";
4646
public static final String EMPTY_RADIO_GROUP_NAME = "Radio group name cannot be empty.";
47+
public static final String CHECKBOX_TYPE_NOT_SUPPORTED = "Unsupported checkbox type for PDF/A";
4748

4849
private FormsExceptionMessageConstant(){}
4950
}

forms/src/main/java/com/itextpdf/forms/fields/CheckBoxFormFieldBuilder.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ This file is part of the iText (R) project.
2222
*/
2323
package com.itextpdf.forms.fields;
2424

25+
import com.itextpdf.commons.utils.ExperimentalFeatures;
2526
import com.itextpdf.forms.fields.properties.CheckBoxType;
2627
import com.itextpdf.kernel.pdf.PdfDocument;
2728
import com.itextpdf.kernel.pdf.PdfName;
@@ -38,7 +39,7 @@ public class CheckBoxFormFieldBuilder extends TerminalFormFieldBuilder<CheckBoxF
3839
/**
3940
* Creates builder for {@link PdfButtonFormField} creation.
4041
*
41-
* @param document document to be used for form field creation
42+
* @param document document to be used for form field creation
4243
* @param formFieldName name of the form field
4344
*/
4445
public CheckBoxFormFieldBuilder(PdfDocument document, String formFieldName) {
@@ -58,6 +59,7 @@ public CheckBoxType getCheckType() {
5859
* Sets check type for checkbox form field. Default value is {@link CheckBoxType#CROSS}.
5960
*
6061
* @param checkType check type to be set for checkbox form field
62+
*
6163
* @return this builder
6264
*/
6365
public CheckBoxFormFieldBuilder setCheckType(CheckBoxType checkType) {
@@ -88,6 +90,14 @@ public PdfButtonFormField createCheckBox() {
8890
check.put(PdfName.V, new PdfName(PdfFormAnnotation.OFF_STATE_VALUE));
8991

9092
if (getWidgetRectangle() != null) {
93+
//TODO DEVSIX-7426 remove flag
94+
if (ExperimentalFeatures.ENABLE_EXPERIMENTAL_CHECKBOX_RENDERING) {
95+
check.getFirstFormAnnotation()
96+
.drawCheckBoxAndSaveAppearanceExperimental(PdfFormAnnotation.ON_STATE_VALUE);
97+
setPageToField(check);
98+
return check;
99+
}
100+
//TODO DEVSIX-7426 remove from here till end
91101
if (getConformanceLevel() == null) {
92102
check.getFirstFormAnnotation().drawCheckAppearance(getWidgetRectangle().getWidth(),
93103
getWidgetRectangle().getHeight(), PdfFormAnnotation.ON_STATE_VALUE);

forms/src/main/java/com/itextpdf/forms/fields/PdfFormAnnotation.java

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,17 @@ This file is part of the iText (R) project.
4343
*/
4444
package com.itextpdf.forms.fields;
4545

46+
import com.itextpdf.commons.utils.ExperimentalFeatures;
4647
import com.itextpdf.commons.utils.MessageFormatUtil;
4748
import com.itextpdf.forms.fields.borders.FormBorderFactory;
4849
import com.itextpdf.forms.fields.properties.CheckBoxType;
4950
import com.itextpdf.forms.form.FormProperty;
51+
import com.itextpdf.forms.form.element.CheckBox;
5052
import com.itextpdf.forms.form.element.Radio;
5153
import com.itextpdf.forms.logs.FormsLogMessageConstants;
5254
import com.itextpdf.forms.util.DrawingUtil;
53-
import com.itextpdf.io.logs.IoLogMessageConstant;
5455
import com.itextpdf.io.font.FontProgram;
56+
import com.itextpdf.io.logs.IoLogMessageConstant;
5557
import com.itextpdf.kernel.colors.Color;
5658
import com.itextpdf.kernel.colors.ColorConstants;
5759
import com.itextpdf.kernel.colors.DeviceCmyk;
@@ -102,7 +104,6 @@ This file is part of the iText (R) project.
102104
import java.util.LinkedHashSet;
103105
import java.util.List;
104106
import java.util.Set;
105-
106107
import org.slf4j.Logger;
107108
import org.slf4j.LoggerFactory;
108109

@@ -663,6 +664,7 @@ protected void drawBorder(PdfCanvas canvas, PdfFormXObject xObject, float width,
663664
* @param height the height of the checkbox to draw
664665
* @param onStateName the state of the form field that will be drawn
665666
*/
667+
//TODO DEVSIX-7426 remove method
666668
protected void drawCheckAppearance(float width, float height, String onStateName) {
667669
Rectangle rect = new Rectangle(0, 0, width, height);
668670

@@ -703,6 +705,7 @@ protected void drawCheckAppearance(float width, float height, String onStateName
703705
* @param onStateName name that corresponds to the "On" state of the checkbox
704706
* @param checkType the type that determines how the checkbox will look like. Instance of {@link CheckBoxType}
705707
*/
708+
//TODO DEVSIX-7426 remove method
706709
protected void drawPdfA2CheckAppearance(float width, float height, String onStateName, CheckBoxType checkType) {
707710
parent.checkType = checkType;
708711
Rectangle rect = new Rectangle(0, 0, width, height);
@@ -813,6 +816,7 @@ protected void drawButton(PdfCanvas canvas, float x, float y, float width, float
813816
* @param height the width of the button
814817
* @param fontSize the size of the font
815818
*/
819+
//TODO DEVSIX-7426 remove method
816820
protected void drawCheckBox(PdfCanvas canvas, float width, float height, float fontSize) {
817821
if (parent.checkType == CheckBoxType.CROSS) {
818822
DrawingUtil.drawCross(canvas, width, height, borderWidth);
@@ -834,6 +838,7 @@ protected void drawCheckBox(PdfCanvas canvas, float width, float height, float f
834838
endText();
835839
}
836840

841+
//TODO DEVSIX-7426 remove method
837842
protected void drawPdfACheckBox(PdfCanvas canvas, float width, float height, boolean on) {
838843
if (!on) {
839844
return;
@@ -1024,13 +1029,13 @@ void regeneratePushButtonField() {
10241029
}
10251030
}
10261031

1032+
//TODO DEVSIX-7426 remove method
10271033
void regenerateCheckboxField(CheckBoxType checkType) {
10281034
parent.setCheckType(checkType);
10291035
final String value = parent.getValueAsString();
10301036
Rectangle rect = getRect(getPdfObject());
10311037

10321038
PdfWidgetAnnotation widget = (PdfWidgetAnnotation) PdfAnnotation.makeAnnotation(getPdfObject());
1033-
10341039
if (getPdfAConformanceLevel() == null) {
10351040
drawCheckAppearance(rect.getWidth(), rect.getHeight(),
10361041
OFF_STATE_VALUE.equals(value) ? ON_STATE_VALUE : value);
@@ -1203,7 +1208,13 @@ boolean regenerateWidget() {
12031208
} else if (parent.getFieldFlag(PdfButtonFormField.FF_RADIO)) {
12041209
drawRadioButtonAndSaveAppearance(getRadioButtonValue());
12051210
} else {
1206-
regenerateCheckboxField(parent.checkType);
1211+
//TODO DEVSIX-7426 remove flag
1212+
if (ExperimentalFeatures.ENABLE_EXPERIMENTAL_CHECKBOX_RENDERING){
1213+
drawCheckBoxAndSaveAppearanceExperimental(parent.getValueAsString());
1214+
}else{
1215+
//TODO DEVSIX-7426 remove method
1216+
regenerateCheckboxField(parent.checkType);
1217+
}
12071218
}
12081219
return true;
12091220
}
@@ -1476,4 +1487,80 @@ private static Color appearancePropToColor(PdfDictionary appearanceCharacteristi
14761487
}
14771488
return null;
14781489
}
1490+
1491+
/**
1492+
* Experimental method to draw a checkbox and save its appearance.
1493+
*
1494+
* @param onStateName the name of the appearance state for the checked state
1495+
*/
1496+
//TODO DEVSIX-7426 rename experimental method
1497+
protected void drawCheckBoxAndSaveAppearanceExperimental(String onStateName) {
1498+
final Rectangle rect = getRect(this.getPdfObject());
1499+
if (rect == null) {
1500+
return;
1501+
}
1502+
1503+
final CheckBox formField = createCheckBox();
1504+
if (formField == null) {
1505+
return;
1506+
}
1507+
// First draw off appearance
1508+
if (getWidget().getNormalAppearanceObject() == null){
1509+
getWidget().setNormalAppearance(new PdfDictionary());
1510+
}
1511+
final PdfDictionary normalAppearance = getWidget().getNormalAppearanceObject();
1512+
// Draw on appearance
1513+
formField.setChecked(false);
1514+
final PdfFormXObject xObjectOff = new PdfFormXObject(new Rectangle(0, 0, rect.getWidth(), rect.getHeight()));
1515+
final Canvas canvasOff = new Canvas(xObjectOff, getDocument());
1516+
canvasOff.add(formField);
1517+
normalAppearance.put(new PdfName(OFF_STATE_VALUE), xObjectOff.getPdfObject());
1518+
if (onStateName != null && !onStateName.isEmpty() && !PdfFormAnnotation.OFF_STATE_VALUE.equals(onStateName)) {
1519+
formField.setChecked(true);
1520+
final PdfFormXObject xObject = new PdfFormXObject(new Rectangle(0, 0, rect.getWidth(), rect.getHeight()));
1521+
final Canvas canvas = new Canvas(xObject, this.getDocument());
1522+
canvas.add(formField);
1523+
normalAppearance.put(new PdfName(onStateName), xObject.getPdfObject());
1524+
}
1525+
1526+
final PdfDictionary mk = new PdfDictionary();
1527+
mk.put(PdfName.CA, new PdfString(parent.text));
1528+
getWidget().put(PdfName.MK, mk);
1529+
final PdfWidgetAnnotation widget = getWidget();
1530+
if (widget.getNormalAppearanceObject() != null &&
1531+
widget.getNormalAppearanceObject().containsKey(new PdfName(onStateName))) {
1532+
widget.setAppearanceState(new PdfName(onStateName));
1533+
} else {
1534+
widget.setAppearanceState(new PdfName(OFF_STATE_VALUE));
1535+
}
1536+
}
1537+
1538+
private CheckBox createCheckBox(){
1539+
final Rectangle rect = getRect(getPdfObject());
1540+
if (rect == null) {
1541+
return null;
1542+
}
1543+
1544+
// id doesn't matter here
1545+
CheckBox checkBox = new CheckBox("");
1546+
if (getBorderWidth() > 0 && borderColor != null ) {
1547+
Border border = new SolidBorder(Math.max(1, getBorderWidth()));
1548+
border.setColor(borderColor);
1549+
checkBox.setBorder(border);
1550+
}
1551+
1552+
if (backgroundColor != null) {
1553+
checkBox.setBackgroundColor(backgroundColor);
1554+
}
1555+
1556+
// Set fixed size
1557+
checkBox.setProperty(Property.WIDTH, UnitValue.createPointValue(rect.getWidth()));
1558+
checkBox.setProperty(Property.HEIGHT, UnitValue.createPointValue(rect.getHeight()));
1559+
// Always flatten
1560+
checkBox.setInteractive(false);
1561+
checkBox.setPdfAConformanceLevel(getPdfAConformanceLevel());
1562+
checkBox.setCheckBoxType(parent.checkType);
1563+
1564+
return checkBox;
1565+
}
14791566
}

forms/src/main/java/com/itextpdf/forms/fields/PdfFormField.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,6 @@ This file is part of the iText (R) project.
7878
import com.itextpdf.layout.properties.HorizontalAlignment;
7979
import com.itextpdf.layout.properties.TextAlignment;
8080

81-
import org.slf4j.Logger;
82-
import org.slf4j.LoggerFactory;
83-
8481
import java.io.ByteArrayOutputStream;
8582
import java.io.IOException;
8683
import java.nio.charset.StandardCharsets;
@@ -93,6 +90,8 @@ This file is part of the iText (R) project.
9390
import java.util.List;
9491
import java.util.Map;
9592
import java.util.Set;
93+
import org.slf4j.Logger;
94+
import org.slf4j.LoggerFactory;
9695

9796
/**
9897
* This class represents a single field or field group in an {@link com.itextpdf.forms.PdfAcroForm

forms/src/main/java/com/itextpdf/forms/form/FormProperty.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,14 @@ public final class FormProperty {
8989
/** The Constant FORM_FIELD_RADIO_BORDER_CIRCLE. */
9090
public static final int FORM_FIELD_RADIO_BORDER_CIRCLE = PROPERTY_START + 13;
9191

92+
/**
93+
* The Constant FORM_CHECKBOX_TYPE.
94+
*/
95+
public static final int FORM_CHECKBOX_TYPE = PROPERTY_START + 14;
96+
97+
/** The Constant FORM_CONFORMANCE_LEVEL. */
98+
public static final int FORM_CONFORMANCE_LEVEL = PROPERTY_START + 15;
99+
92100
private FormProperty() {
93101
// Empty constructor.
94102
}

forms/src/main/java/com/itextpdf/forms/form/element/CheckBox.java

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,27 @@ This file is part of the iText (R) project.
4242
*/
4343
package com.itextpdf.forms.form.element;
4444

45+
import com.itextpdf.commons.utils.MessageFormatUtil;
46+
import com.itextpdf.forms.fields.properties.CheckBoxType;
47+
import com.itextpdf.forms.form.FormProperty;
4548
import com.itextpdf.forms.form.renderer.CheckBoxRenderer;
49+
import com.itextpdf.forms.logs.FormsLogMessageConstants;
50+
import com.itextpdf.kernel.pdf.PdfAConformanceLevel;
51+
import com.itextpdf.layout.properties.Property;
52+
import com.itextpdf.layout.properties.RenderingMode;
53+
import com.itextpdf.layout.properties.UnitValue;
4654
import com.itextpdf.layout.renderer.IRenderer;
4755

56+
import org.slf4j.Logger;
57+
import org.slf4j.LoggerFactory;
58+
4859
/**
4960
* Extension of the {@link FormField} class representing a checkbox so that
5061
* a {@link CheckBoxRenderer} is used instead of the default renderer for fields.
5162
*/
52-
public class CheckBox extends FormField<TextArea> {
63+
public class CheckBox extends FormField<CheckBox> {
64+
65+
private static final Logger LOGGER = LoggerFactory.getLogger(CheckBox.class);
5366

5467
/**
5568
* Creates a new {@link CheckBox} instance.
@@ -60,11 +73,91 @@ public CheckBox(String id) {
6073
super(id);
6174
}
6275

76+
77+
/**
78+
* Sets the checked state of the checkbox.
79+
*
80+
* @param checked the checked state to set
81+
*
82+
* @return this checkbox instance
83+
*/
84+
public CheckBox setChecked(boolean checked) {
85+
setProperty(FormProperty.FORM_FIELD_CHECKED, checked);
86+
return this;
87+
}
88+
89+
/**
90+
* Sets the rendering mode for the checkbox.
91+
*
92+
* @param renderingMode the rendering mode to set
93+
*
94+
* @return this checkbox instance
95+
*/
96+
public CheckBox setRenderingMode(RenderingMode renderingMode) {
97+
if (renderingMode == null) {
98+
LOGGER.warn(MessageFormatUtil.format(
99+
FormsLogMessageConstants.INVALID_VALUE_FALLBACK_TO_DEFAULT, "renderingMode", null));
100+
return this;
101+
}
102+
setProperty(Property.RENDERING_MODE, renderingMode);
103+
return this;
104+
}
105+
106+
/**
107+
* Sets the PDF/A conformance level for the checkbox.
108+
*
109+
* @param conformanceLevel the PDF/A conformance level to set
110+
*
111+
* @return this checkbox instance
112+
*/
113+
public CheckBox setPdfAConformanceLevel(PdfAConformanceLevel conformanceLevel) {
114+
setProperty(FormProperty.FORM_CONFORMANCE_LEVEL, conformanceLevel);
115+
return this;
116+
}
117+
118+
119+
/**
120+
* Sets the icon of the checkbox.
121+
*
122+
* @param checkBoxType the type of the checkbox to set
123+
*
124+
* @return this checkbox instance
125+
*/
126+
public CheckBox setCheckBoxType(CheckBoxType checkBoxType) {
127+
if (checkBoxType == null) {
128+
LOGGER.warn(MessageFormatUtil.format(
129+
FormsLogMessageConstants.INVALID_VALUE_FALLBACK_TO_DEFAULT, "checkBoxType", null));
130+
return this;
131+
}
132+
setProperty(FormProperty.FORM_CHECKBOX_TYPE, checkBoxType);
133+
return this;
134+
}
135+
136+
/**
137+
* Sets the size of the checkbox.
138+
*
139+
* @param size the size of the checkbox to set, in points
140+
*
141+
* @return this checkbox instance
142+
*/
143+
public CheckBox setSize(float size) {
144+
if (size <= 0) {
145+
LOGGER.warn(MessageFormatUtil.format(
146+
FormsLogMessageConstants.INVALID_VALUE_FALLBACK_TO_DEFAULT, "size", size));
147+
return this;
148+
}
149+
setProperty(Property.WIDTH, UnitValue.createPointValue(size));
150+
setProperty(Property.HEIGHT, UnitValue.createPointValue(size));
151+
152+
return this;
153+
}
154+
63155
/* (non-Javadoc)
64156
* @see com.itextpdf.layout.element.AbstractElement#makeNewRenderer()
65157
*/
66158
@Override
67159
protected IRenderer makeNewRenderer() {
68160
return new CheckBoxRenderer(this);
69161
}
162+
70163
}

0 commit comments

Comments
 (0)