Skip to content

Commit 30a539f

Browse files
author
Eugene Bochilo
committed
Support basic implementation of text form fields new drawing logic
DEVSIX-7362
1 parent 784a772 commit 30a539f

File tree

50 files changed

+616
-72
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+616
-72
lines changed

commons/src/main/java/com/itextpdf/commons/utils/ExperimentalFeatures.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
11
package com.itextpdf.commons.utils;
22

3+
/**
4+
* Experimental features class which contains constants related to experimental form fields drawing.
5+
*/
6+
// TODO Shall be removed in the scope of DEVSIX-7385
37
public final class ExperimentalFeatures {
8+
/**
9+
* Determines, whether the old or the new checkbox form field drawing logic will be used.
10+
*/
411
public static boolean ENABLE_EXPERIMENTAL_CHECKBOX_RENDERING = false;
512

13+
/**
14+
* Determines, whether the old or the new text form field drawing logic will be used.
15+
*/
16+
public static boolean ENABLE_EXPERIMENTAL_TEXT_FORM_RENDERING = false;
17+
618
private ExperimentalFeatures() {
719
// utility class
820
}

forms/src/main/java/com/itextpdf/forms/PdfAcroForm.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ public PdfString getDefaultAppearance() {
560560
*
561561
* @param justification an integer representing a justification value
562562
* @return current AcroForm
563-
* @see PdfFormField#setJustification(com.itextpdf.layout.properties.HorizontalAlignment)
563+
* @see PdfFormField#setJustification(com.itextpdf.layout.properties.TextAlignment)
564564
*/
565565
public PdfAcroForm setDefaultJustification(int justification) {
566566
return put(PdfName.Q, new PdfNumber(justification));

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

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ This file is part of the iText (R) project.
2828
import com.itextpdf.forms.fields.properties.CheckBoxType;
2929
import com.itextpdf.forms.form.FormProperty;
3030
import com.itextpdf.forms.form.element.CheckBox;
31+
import com.itextpdf.forms.form.element.FormField;
32+
import com.itextpdf.forms.form.element.IFormField;
33+
import com.itextpdf.forms.form.element.InputField;
3134
import com.itextpdf.forms.form.element.Radio;
35+
import com.itextpdf.forms.form.element.TextArea;
3236
import com.itextpdf.forms.logs.FormsLogMessageConstants;
3337
import com.itextpdf.forms.util.DrawingUtil;
3438
import com.itextpdf.forms.util.FontSizeUtil;
@@ -66,6 +70,7 @@ This file is part of the iText (R) project.
6670
import com.itextpdf.layout.element.Div;
6771
import com.itextpdf.layout.element.Paragraph;
6872
import com.itextpdf.layout.element.Text;
73+
import com.itextpdf.layout.properties.Background;
6974
import com.itextpdf.layout.properties.BoxSizingPropertyValue;
7075
import com.itextpdf.layout.properties.Leading;
7176
import com.itextpdf.layout.properties.OverflowPropertyValue;
@@ -114,7 +119,6 @@ public class PdfFormAnnotation extends AbstractPdfFormField {
114119
protected float borderWidth = 1;
115120
protected Color backgroundColor;
116121
protected Color borderColor;
117-
protected int rotation = 0;
118122

119123
/**
120124
* Creates a form field annotation as a wrapper of a {@link PdfWidgetAnnotation}.
@@ -205,20 +209,22 @@ public PdfFormAnnotation setRotation(int degRotation) {
205209
if (degRotation < 0) {
206210
degRotation += 360;
207211
}
208-
209-
this.rotation = degRotation;
210212
}
211213
PdfDictionary mk = getWidget().getAppearanceCharacteristics();
212214
if (mk == null) {
213215
mk = new PdfDictionary();
214216
this.put(PdfName.MK, mk);
215217
}
216218
mk.put(PdfName.R, new PdfNumber(degRotation));
217-
218-
this.rotation = degRotation;
219+
219220
regenerateField();
220221
return this;
221222
}
223+
224+
public int getRotation() {
225+
PdfDictionary mk = getWidget().getAppearanceCharacteristics();
226+
return mk == null || mk.getAsInt(PdfName.R) == null ? 0 : (int) mk.getAsInt(PdfName.R);
227+
}
222228

223229
/**
224230
* Sets the action on {@link PdfWidgetAnnotation widget} of this annotation form field.
@@ -291,6 +297,21 @@ public float getBorderWidth() {
291297
return borderWidth;
292298
}
293299

300+
/**
301+
* Get border object specified in the widget annotation dictionary.
302+
*
303+
* @return {@link Border} specified in the widget annotation dictionary
304+
*/
305+
public Border getBorder() {
306+
float borderWidth = getBorderWidth();
307+
Border border = FormBorderFactory.getBorder(
308+
this.getWidget().getBorderStyle(), borderWidth, borderColor, backgroundColor);
309+
if (border == null && borderWidth > 0 && borderColor != null) {
310+
border = new SolidBorder(borderColor, Math.max(1, borderWidth));
311+
}
312+
return border;
313+
}
314+
294315
/**
295316
* Sets the border width for the field.
296317
*
@@ -299,13 +320,15 @@ public float getBorderWidth() {
299320
* @return The edited {@link PdfFormAnnotation}.
300321
*/
301322
public PdfFormAnnotation setBorderWidth(float borderWidth) {
323+
// Acrobat doesn't support float border width therefore we round it.
324+
int roundedBorderWidth = (int) Math.round(borderWidth);
302325
PdfDictionary bs = getWidget().getBorderStyle();
303326
if (bs == null) {
304327
bs = new PdfDictionary();
305328
put(PdfName.BS, bs);
306329
}
307-
bs.put(PdfName.W, new PdfNumber(borderWidth));
308-
this.borderWidth = borderWidth;
330+
bs.put(PdfName.W, new PdfNumber(roundedBorderWidth));
331+
this.borderWidth = roundedBorderWidth;
309332

310333
regenerateField();
311334
return this;
@@ -497,7 +520,8 @@ protected void drawTextAppearance(Rectangle rect, PdfFont font, float fontSize,
497520
saveState().
498521
endPath();
499522

500-
TextAlignment textAlignment = parent.convertJustificationToTextAlignment();
523+
TextAlignment textAlignment =
524+
parent.getJustification() == null ? TextAlignment.LEFT : parent.getJustification();
501525
float x = 0;
502526
if (textAlignment == TextAlignment.RIGHT) {
503527
x = rect.getWidth();
@@ -584,7 +608,7 @@ protected void drawMultiLineTextAppearance(Rectangle rect, PdfFont font, String
584608
paragraph.setFontSize(getFontSize());
585609
}
586610
paragraph.setProperty(Property.FORCED_PLACEMENT, Boolean.TRUE);
587-
paragraph.setTextAlignment(parent.convertJustificationToTextAlignment());
611+
paragraph.setTextAlignment(parent.getJustification());
588612

589613
if (getColor() != null) {
590614
paragraph.setFontColor(getColor());
@@ -891,6 +915,54 @@ protected void drawRadioButtonAndSaveAppearance(String value) {
891915
getWidget().setNormalAppearance(normalAppearance);
892916
}
893917

918+
/**
919+
* Draws the appearance of a text form field with and saves it into an appearance stream.
920+
*/
921+
protected void drawTextFormFieldAndSaveAppearance() {
922+
Rectangle rectangle = getRect(this.getPdfObject());
923+
if (rectangle == null) {
924+
return;
925+
}
926+
927+
IFormField textFormField;
928+
if (parent.isMultiline()) {
929+
textFormField = new TextArea(getParentField().getPartialFieldName().toUnicodeString());
930+
textFormField.setProperty(Property.FONT_SIZE, UnitValue.createPointValue(getFontSize()));
931+
} else {
932+
textFormField = new InputField(getParentField().getPartialFieldName().toUnicodeString());
933+
textFormField.setProperty(Property.FONT_SIZE,
934+
UnitValue.createPointValue(getFontSize(new PdfArray(rectangle), parent.getValueAsString())));
935+
}
936+
textFormField.setProperty(FormProperty.FORM_FIELD_VALUE, parent.getDisplayValue());
937+
textFormField.setProperty(Property.FONT, getFont());
938+
textFormField.setProperty(Property.TEXT_ALIGNMENT, parent.getJustification());
939+
textFormField.setProperty(FormProperty.FORM_FIELD_PASSWORD_FLAG, getParentField().isPassword());
940+
textFormField.setProperty(Property.ADD_MARKED_CONTENT_TEXT, true);
941+
if (getColor() != null) {
942+
textFormField.setProperty(Property.FONT_COLOR, new TransparentColor(getColor()));
943+
}
944+
945+
textFormField.setProperty(Property.BORDER, getBorder());
946+
947+
if (backgroundColor != null) {
948+
textFormField.setProperty(Property.BACKGROUND, new Background(backgroundColor, 1f, 0, 0, 0, 0));
949+
}
950+
951+
textFormField.setProperty(Property.WIDTH, UnitValue.createPointValue(rectangle.getWidth()));
952+
textFormField.setProperty(Property.HEIGHT, UnitValue.createPointValue(rectangle.getHeight()));
953+
// Always flatten
954+
textFormField.setProperty(FormProperty.FORM_FIELD_FLATTEN, true);
955+
956+
PdfFormXObject xObject = new PdfFormXObject(
957+
new Rectangle(0, 0, rectangle.getWidth(), rectangle.getHeight()));
958+
applyRotation(xObject, rectangle.getWidth(), rectangle.getHeight());
959+
Canvas canvas = new Canvas(xObject, this.getDocument());
960+
canvas.setProperty(Property.APPEARANCE_STREAM_LAYOUT, Boolean.TRUE);
961+
canvas.add(textFormField);
962+
963+
getWidget().setNormalAppearance(xObject.getPdfObject());
964+
}
965+
894966
@Override
895967
void retrieveStyles() {
896968
super.retrieveStyles();
@@ -951,7 +1023,7 @@ void drawChoiceAppearance(Rectangle rect, float fontSize, String value, PdfFormX
9511023
Paragraph paragraph = new Paragraph(strings.get(index)).setFont(getFont())
9521024
.setFontSize(fontSize).setMargins(0, 0, 0, 0).setMultipliedLeading(1);
9531025
paragraph.setProperty(Property.FORCED_PLACEMENT, Boolean.TRUE);
954-
paragraph.setTextAlignment(parent.convertJustificationToTextAlignment());
1026+
paragraph.setTextAlignment(parent.getJustification());
9551027

9561028
if (getColor() != null) {
9571029
paragraph.setFontColor(getColor());
@@ -1190,7 +1262,10 @@ boolean regenerateWidget() {
11901262
}
11911263
final PdfName type = parent.getFormType();
11921264

1193-
if (PdfName.Tx.equals(type) || PdfName.Ch.equals(type)) {
1265+
if (PdfName.Tx.equals(type) && ExperimentalFeatures.ENABLE_EXPERIMENTAL_TEXT_FORM_RENDERING) {
1266+
drawTextFormFieldAndSaveAppearance();
1267+
return true;
1268+
} else if (PdfName.Ch.equals(type) || PdfName.Tx.equals(type)) {
11941269
return regenerateTextAndChoiceField();
11951270
} else if (PdfName.Btn.equals(type)) {
11961271
if (parent.getFieldFlag(PdfButtonFormField.FF_PUSH_BUTTON)) {
@@ -1391,7 +1466,7 @@ private static String obfuscatePassword(String text) {
13911466
}
13921467

13931468
private void applyRotation(PdfFormXObject xObject, float height, float width) {
1394-
switch (rotation) {
1469+
switch (getRotation()) {
13951470
case 90:
13961471
xObject.put(PdfName.Matrix, new PdfArray(new float[] {0, 1, -1, 0, height, 0}));
13971472
break;

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

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -988,7 +988,7 @@ public void updateDefaultAppearance() {
988988
*
989989
* @return the current justification attribute.
990990
*/
991-
public HorizontalAlignment getJustification() {
991+
public TextAlignment getJustification() {
992992
Integer justification = getPdfObject().getAsInt(PdfName.Q);
993993
if (justification == null && getParent() != null) {
994994
justification = getParent().getAsInt(PdfName.Q);
@@ -1005,9 +1005,11 @@ public HorizontalAlignment getJustification() {
10051005
* @param justification the value to set the justification attribute to.
10061006
* @return the edited {@link PdfFormField}.
10071007
*/
1008-
public PdfFormField setJustification(HorizontalAlignment justification) {
1009-
put(PdfName.Q, new PdfNumber(justification.ordinal()));
1010-
regenerateField();
1008+
public PdfFormField setJustification(TextAlignment justification) {
1009+
if (justification != null) {
1010+
put(PdfName.Q, new PdfNumber(justification.ordinal()));
1011+
regenerateField();
1012+
}
10111013
return this;
10121014
}
10131015

@@ -1259,20 +1261,6 @@ static String optionsArrayToString(PdfArray options) {
12591261
return sb.toString();
12601262
}
12611263

1262-
TextAlignment convertJustificationToTextAlignment() {
1263-
HorizontalAlignment justification = getJustification();
1264-
1265-
TextAlignment textAlignment;
1266-
if (justification == HorizontalAlignment.RIGHT) {
1267-
textAlignment = TextAlignment.RIGHT;
1268-
} else if (justification == HorizontalAlignment.CENTER) {
1269-
textAlignment = TextAlignment.CENTER;
1270-
} else {
1271-
textAlignment = TextAlignment.LEFT;
1272-
}
1273-
return textAlignment;
1274-
}
1275-
12761264
/**
12771265
* Adds a field to the children of the current field.
12781266
*
@@ -1364,14 +1352,14 @@ private static PdfName getTypeFromParent(PdfDictionary field) {
13641352
return formType;
13651353
}
13661354

1367-
private static HorizontalAlignment numberToHorizontalAlignment(int alignment) {
1355+
private static TextAlignment numberToHorizontalAlignment(int alignment) {
13681356
switch (alignment) {
13691357
case 1:
1370-
return HorizontalAlignment.CENTER;
1358+
return TextAlignment.CENTER;
13711359
case 2:
1372-
return HorizontalAlignment.RIGHT;
1360+
return TextAlignment.RIGHT;
13731361
default:
1374-
return HorizontalAlignment.LEFT;
1362+
return TextAlignment.LEFT;
13751363
}
13761364
}
13771365

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

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ This file is part of the iText (R) project.
2424

2525
import com.itextpdf.forms.form.FormProperty;
2626
import com.itextpdf.layout.element.AbstractElement;
27+
import com.itextpdf.layout.properties.Property;
28+
import com.itextpdf.layout.properties.UnitValue;
2729

2830
/**
2931
* Implementation of the {@link AbstractElement} class for form fields.
@@ -70,15 +72,65 @@ public <T1> T1 getDefaultProperty(int property) {
7072
}
7173
}
7274

75+
/**
76+
* Sets the form field's width and height.
77+
*
78+
* @param size form field's width and height.
79+
*
80+
* @return this same {@link FormField} element.
81+
*/
82+
public T setSize(float size) {
83+
setProperty(Property.WIDTH, UnitValue.createPointValue(size));
84+
setProperty(Property.HEIGHT, UnitValue.createPointValue(size));
85+
86+
return (T) (Object) this;
87+
}
88+
89+
/**
90+
* Set the form field's width.
91+
*
92+
* @param width form field's width
93+
*
94+
* @return this {@link FormField} element.
95+
*/
96+
public T setWidth(float width) {
97+
setProperty(Property.WIDTH, UnitValue.createPointValue(width));
98+
return (T) (Object) this;
99+
}
100+
101+
/**
102+
* Set the form field's height.
103+
*
104+
* @param height form field's height
105+
*
106+
* @return this {@link FormField} element.
107+
*/
108+
public T setHeight(float height) {
109+
setProperty(Property.HEIGHT, UnitValue.createPointValue(height));
110+
return (T) (Object) this;
111+
}
112+
73113
/**
74114
* Set the form field to be interactive and added into Acroform instead of drawing it on a page.
75115
*
76116
* @param interactive {@code true} if the form field element shall be added into Acroform, {@code false} otherwise.
77117
* By default, the form field element is not interactive and drawn on a page.
78118
* @return this same {@link FormField} instance.
79119
*/
80-
public FormField<T> setInteractive(boolean interactive) {
120+
public T setInteractive(boolean interactive) {
81121
setProperty(FormProperty.FORM_FIELD_FLATTEN, !interactive);
82-
return this;
122+
return (T) (Object) this;
123+
}
124+
125+
/**
126+
* Set value to the form field. Meaning of this depends on the form field type.
127+
*
128+
* @param value string value to be set
129+
*
130+
* @return this {@link FormField} element.
131+
*/
132+
public T setValue(String value) {
133+
setProperty(FormProperty.FORM_FIELD_VALUE, value);
134+
return (T) (Object) this;
83135
}
84136
}

0 commit comments

Comments
 (0)