Skip to content

Commit 4f2f97a

Browse files
committed
Fix text clipping, rotation for input field drawing; switch some tests for new drawing mechanism
DEVSIX-7433
1 parent 8729ac9 commit 4f2f97a

File tree

66 files changed

+525
-252
lines changed

Some content is hidden

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

66 files changed

+525
-252
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public final class FormsExceptionMessageConstant {
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.";
4747
public static final String CHECKBOX_TYPE_NOT_SUPPORTED = "Unsupported checkbox type for PDF/A";
48+
public static final String INVALID_ROTATION_VALUE = "Invalid rotation. Rotation must be a multiple of 90 degrees.";
4849

4950
private FormsExceptionMessageConstant(){}
5051
}

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

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ 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;
3231
import com.itextpdf.forms.form.element.IFormField;
3332
import com.itextpdf.forms.form.element.InputField;
3433
import com.itextpdf.forms.form.element.Radio;
3534
import com.itextpdf.forms.form.element.TextArea;
3635
import com.itextpdf.forms.form.element.Button;
36+
import com.itextpdf.forms.form.renderer.FormFieldValueNonTrimmingTextRenderer;
3737
import com.itextpdf.forms.logs.FormsLogMessageConstants;
3838
import com.itextpdf.forms.util.DrawingUtil;
3939
import com.itextpdf.forms.util.FontSizeUtil;
@@ -679,7 +679,10 @@ protected void drawBorder(PdfCanvas canvas, PdfFormXObject xObject, float width,
679679
}
680680
}
681681

682-
applyRotation(xObject, height, width);
682+
PdfArray matrix = getRotationMatrix(getRotation() % 360, height, width);
683+
if (matrix != null) {
684+
xObject.put(PdfName.Matrix, matrix);
685+
}
683686
canvas.restoreState();
684687
}
685688

@@ -778,7 +781,10 @@ protected void drawPushButtonFieldAndSaveAppearance() {
778781
createInputButton();
779782

780783
PdfFormXObject xObject = new PdfFormXObject(new Rectangle(0, 0, width, height));
781-
applyRotation(xObject, height, width);
784+
PdfArray matrix = getRotationMatrix(getRotation() % 360, height, width);
785+
if (matrix != null) {
786+
xObject.put(PdfName.Matrix, matrix);
787+
}
782788
Canvas canvas = new Canvas(xObject, this.getDocument());
783789
setMetaInfoToCanvas(canvas);
784790

@@ -957,14 +963,28 @@ protected void drawTextFormFieldAndSaveAppearance() {
957963
textFormField.setProperty(Property.BACKGROUND, new Background(backgroundColor, 1f, 0, 0, 0, 0));
958964
}
959965

960-
textFormField.setProperty(Property.WIDTH, UnitValue.createPointValue(rectangle.getWidth()));
961-
textFormField.setProperty(Property.HEIGHT, UnitValue.createPointValue(rectangle.getHeight()));
962966
// Always flatten
963967
textFormField.setProperty(FormProperty.FORM_FIELD_FLATTEN, true);
964968

969+
// Rotation
970+
final int fieldRotation = getRotation() % 360;
971+
PdfArray matrix = getRotationMatrix(fieldRotation, rectangle.getHeight(), rectangle.getWidth());
972+
if (fieldRotation == 90 || fieldRotation == 270) {
973+
Rectangle invertedRectangle = rectangle.clone();
974+
invertedRectangle.setWidth(rectangle.getHeight());
975+
invertedRectangle.setHeight(rectangle.getWidth());
976+
rectangle = invertedRectangle;
977+
}
978+
979+
// Set fixed size
980+
textFormField.setProperty(Property.WIDTH, UnitValue.createPointValue(rectangle.getWidth()));
981+
textFormField.setProperty(Property.HEIGHT, UnitValue.createPointValue(rectangle.getHeight()));
982+
965983
PdfFormXObject xObject = new PdfFormXObject(
966984
new Rectangle(0, 0, rectangle.getWidth(), rectangle.getHeight()));
967-
applyRotation(xObject, rectangle.getWidth(), rectangle.getHeight());
985+
if (matrix != null) {
986+
xObject.put(PdfName.Matrix, matrix);
987+
}
968988
Canvas canvas = new Canvas(xObject, this.getDocument());
969989
canvas.setProperty(Property.APPEARANCE_STREAM_LAYOUT, Boolean.TRUE);
970990
canvas.add(textFormField);
@@ -1143,7 +1163,7 @@ boolean regenerateTextAndChoiceField() {
11431163
} else {
11441164
//Avoid NPE when handling corrupt pdfs
11451165
Logger logger = LoggerFactory.getLogger(PdfFormAnnotation.class);
1146-
logger.error(FormsLogMessageConstants.INCORRECT_PAGEROTATION);
1166+
logger.error(FormsLogMessageConstants.INCORRECT_PAGE_ROTATION);
11471167
matrix = new PdfArray(new double[] {1, 0, 0, 1, 0, 0});
11481168
}
11491169
//Apply field rotation
@@ -1479,20 +1499,20 @@ private static String obfuscatePassword(String text) {
14791499
return new String(pchar);
14801500
}
14811501

1482-
private void applyRotation(PdfFormXObject xObject, float height, float width) {
1483-
switch (getRotation()) {
1502+
private static PdfArray getRotationMatrix(int rotation, float height, float width) {
1503+
switch (rotation) {
1504+
case 0:
1505+
return null;
14841506
case 90:
1485-
xObject.put(PdfName.Matrix, new PdfArray(new float[] {0, 1, -1, 0, height, 0}));
1486-
break;
1507+
return new PdfArray(new float[] {0, 1, -1, 0, height, 0});
14871508
case 180:
1488-
xObject.put(PdfName.Matrix, new PdfArray(new float[] {-1, 0, 0, -1, width, height}));
1489-
break;
1509+
return new PdfArray(new float[] {-1, 0, 0, -1, width, height});
14901510
case 270:
1491-
xObject.put(PdfName.Matrix, new PdfArray(new float[] {0, -1, 1, 0, 0, width}));
1492-
break;
1511+
return new PdfArray(new float[] {0, -1, 1, 0, 0, width});
14931512
default:
1494-
// Rotation 0 - do nothing
1495-
break;
1513+
Logger logger = LoggerFactory.getLogger(PdfFormAnnotation.class);
1514+
logger.error(FormsLogMessageConstants.INCORRECT_WIDGET_ROTATION);
1515+
return null;
14961516
}
14971517
}
14981518

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

Lines changed: 31 additions & 0 deletions
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.form.element;
2424

25+
import com.itextpdf.forms.exceptions.FormsExceptionMessageConstant;
2526
import com.itextpdf.forms.form.renderer.InputFieldRenderer;
2627
import com.itextpdf.forms.form.FormProperty;
2728
import com.itextpdf.layout.element.Paragraph;
@@ -47,6 +48,11 @@ public class InputField extends FormField<InputField> implements IPlaceholderabl
4748
*/
4849
private Paragraph placeholder;
4950

51+
/**
52+
* Field rotation, counterclockwise. Must be a multiple of 90 degrees.
53+
*/
54+
private int rotation = 0;
55+
5056
/**
5157
* Creates a new input field.
5258
*
@@ -106,6 +112,31 @@ public <T1> T1 getDefaultProperty(int property) {
106112
}
107113
}
108114

115+
/**
116+
* Set rotation of the input field.
117+
*
118+
* @param rotation new rotation value, counterclockwise. Must be a multiple of 90 degrees.
119+
* It has sense only in interactive mode, see {@link FormField#setInteractive}.
120+
* @return the edited {@link InputField}.
121+
*/
122+
public InputField setRotation(int rotation) {
123+
if (rotation % 90 != 0) {
124+
throw new IllegalArgumentException(FormsExceptionMessageConstant.INVALID_ROTATION_VALUE);
125+
}
126+
127+
this.rotation = rotation;
128+
return this;
129+
}
130+
131+
/**
132+
* Get rotation.
133+
*
134+
* @return rotation value.
135+
*/
136+
public int getRotation() {
137+
return this.rotation;
138+
}
139+
109140
/* (non-Javadoc)
110141
* @see com.itextpdf.layout.element.AbstractElement#makeNewRenderer()
111142
*/

forms/src/main/java/com/itextpdf/forms/fields/FormFieldValueNonTrimmingTextRenderer.java renamed to forms/src/main/java/com/itextpdf/forms/form/renderer/FormFieldValueNonTrimmingTextRenderer.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,31 @@ This file is part of the iText (R) project.
2020
You should have received a copy of the GNU Affero General Public License
2121
along with this program. If not, see <https://www.gnu.org/licenses/>.
2222
*/
23-
package com.itextpdf.forms.fields;
23+
package com.itextpdf.forms.form.renderer;
2424

25+
import com.itextpdf.commons.utils.MessageFormatUtil;
26+
import com.itextpdf.io.font.otf.GlyphLine;
27+
import com.itextpdf.io.logs.IoLogMessageConstant;
28+
import com.itextpdf.kernel.font.PdfFont;
2529
import com.itextpdf.layout.element.Text;
2630
import com.itextpdf.layout.layout.LayoutContext;
2731
import com.itextpdf.layout.layout.LayoutResult;
2832
import com.itextpdf.layout.layout.TextLayoutResult;
2933
import com.itextpdf.layout.renderer.IRenderer;
3034
import com.itextpdf.layout.renderer.TextRenderer;
3135

36+
import org.slf4j.Logger;
37+
import org.slf4j.LoggerFactory;
38+
3239
/**
3340
* Custom implementation for rendering form field values. It makes sure that text value
3441
* trimming strategy matches Acrobat's behavior
3542
*/
36-
class FormFieldValueNonTrimmingTextRenderer extends TextRenderer {
43+
44+
// Temporarily public, make it package private on cleanup of PdfFormAnnotation
45+
// TODO DEVSIX-7423 (or put another devsix if the usage of this class is not removed from PdfFormAnnotation
46+
// as part of DEVSIX-7423)
47+
public class FormFieldValueNonTrimmingTextRenderer extends TextRenderer {
3748
// Determines whether we want to trim leading space. In particular we don't want to trim
3849
// the very first leading spaces of the text value. When text overflows to the next lines,
3950
// whether we should trim the text depends on why the overflow happened
@@ -68,6 +79,20 @@ public void trimFirst() {
6879
}
6980
}
7081

82+
/**
83+
* {@inheritDoc}
84+
*/
85+
@Override
86+
protected TextRenderer createCopy(GlyphLine gl, PdfFont font) {
87+
if (FormFieldValueNonTrimmingTextRenderer.class != this.getClass()) {
88+
Logger logger = LoggerFactory.getLogger(FormFieldValueNonTrimmingTextRenderer.class);
89+
logger.error(MessageFormatUtil.format(IoLogMessageConstant.CREATE_COPY_SHOULD_BE_OVERRIDDEN));
90+
}
91+
FormFieldValueNonTrimmingTextRenderer copy = new FormFieldValueNonTrimmingTextRenderer((Text)this.modelElement);
92+
copy.setProcessedGlyphLineAndFont(gl, font);
93+
return copy;
94+
}
95+
7196
private void setCallTrimFirst(boolean callTrimFirst) {
7297
this.callTrimFirst = callTrimFirst;
7398
}

forms/src/main/java/com/itextpdf/forms/form/renderer/InputFieldRenderer.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ This file is part of the iText (R) project.
3434
import com.itextpdf.kernel.pdf.PdfDocument;
3535
import com.itextpdf.kernel.pdf.PdfPage;
3636
import com.itextpdf.kernel.pdf.PdfString;
37+
import com.itextpdf.layout.element.Paragraph;
38+
import com.itextpdf.layout.element.Text;
3739
import com.itextpdf.layout.layout.LayoutContext;
3840
import com.itextpdf.layout.minmaxwidth.MinMaxWidth;
3941
import com.itextpdf.layout.properties.Property;
@@ -96,7 +98,17 @@ IRenderer createParagraphRenderer(String defaultValue) {
9698
&& !((InputField) modelElement).getPlaceholder().isEmpty()) {
9799
return ((InputField) modelElement).getPlaceholder().createRendererSubTree();
98100
}
99-
return super.createParagraphRenderer(defaultValue);
101+
if (defaultValue.isEmpty()) {
102+
defaultValue = "\u00A0";
103+
}
104+
105+
Text text = new Text(defaultValue);
106+
FormFieldValueNonTrimmingTextRenderer nextRenderer = new FormFieldValueNonTrimmingTextRenderer(text);
107+
text.setNextRenderer(nextRenderer);
108+
109+
IRenderer flatRenderer = new Paragraph(text).setMargin(0).createRendererSubTree();
110+
flatRenderer.setProperty(Property.NO_SOFT_WRAP_INLINE, true);
111+
return flatRenderer;
100112
}
101113

102114
/* (non-Javadoc)
@@ -131,6 +143,7 @@ protected IRenderer createFlatRenderer() {
131143
if (flatten && password) {
132144
defaultValue = obfuscatePassword(defaultValue);
133145
}
146+
134147
return createParagraphRenderer(defaultValue);
135148
}
136149

@@ -162,6 +175,10 @@ protected void applyAcroField(DrawContext drawContext) {
162175
} else {
163176
inputField.setDefaultValue(new PdfString(value));
164177
}
178+
final int rotation = ((InputField)modelElement).getRotation();
179+
if (rotation != 0) {
180+
inputField.getFirstFormAnnotation().setRotation(rotation);
181+
}
165182
applyDefaultFieldProperties(inputField);
166183
PdfAcroForm.getAcroForm(doc, true).addField(inputField, page);
167184

forms/src/main/java/com/itextpdf/forms/logs/FormsLogMessageConstants.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,12 @@ public final class FormsLogMessageConstants {
4848
public static final String FORM_FIELD_WAS_FLUSHED =
4949
"A form field was flushed. There's no way to create this field in the AcroForm dictionary.";
5050

51-
public static final String INCORRECT_PAGEROTATION =
52-
"Encounterd a page rotation that was not a multiple of 90°/ (Pi/2) when generating default appearances "
51+
public static final String INCORRECT_PAGE_ROTATION =
52+
"Encountered a page rotation that was not a multiple of 90°/ (Pi/2) when generating default appearances "
53+
+ "for form fields";
54+
55+
public static final String INCORRECT_WIDGET_ROTATION =
56+
"Encountered a widget rotation that was not a multiple of 90°/ (Pi/2) when generating default appearances "
5357
+ "for form fields";
5458

5559
public static final String INPUT_FIELD_DOES_NOT_FIT = "Input field doesn't fit in outer object. It will be clipped";

forms/src/test/java/com/itextpdf/forms/FlatteningRotatedTest.java

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,57 +22,85 @@ This file is part of the iText (R) project.
2222
*/
2323
package com.itextpdf.forms;
2424

25+
import com.itextpdf.commons.utils.ExperimentalFeatures;
2526
import com.itextpdf.forms.fields.PdfFormField;
26-
import com.itextpdf.commons.utils.MessageFormatUtil;
27+
import com.itextpdf.kernel.colors.ColorConstants;
2728
import com.itextpdf.kernel.pdf.PdfDocument;
2829
import com.itextpdf.kernel.pdf.PdfReader;
2930
import com.itextpdf.kernel.pdf.PdfWriter;
3031
import com.itextpdf.kernel.utils.CompareTool;
3132
import com.itextpdf.test.ExtendedITextTest;
3233
import com.itextpdf.test.annotations.type.IntegrationTest;
3334
import java.io.IOException;
35+
import java.util.ArrayList;
36+
import java.util.Collection;
37+
import java.util.List;
38+
import org.junit.AfterClass;
3439
import org.junit.Assert;
3540
import org.junit.BeforeClass;
3641
import org.junit.Test;
3742
import org.junit.experimental.categories.Category;
43+
import org.junit.runner.RunWith;
44+
import org.junit.runners.Parameterized;
3845

46+
@RunWith(Parameterized.class)
3947
@Category(IntegrationTest.class)
4048
public class FlatteningRotatedTest extends ExtendedITextTest {
4149

4250
public static final String sourceFolder = "./src/test/resources/com/itextpdf/forms/FlatteningRotatedTest/";
4351
public static final String destinationFolder = "./target/test/com/itextpdf/forms/FlatteningRotatedTest/";
4452

53+
private final String inputPdfFileName;
54+
private static boolean experimentalRenderingPreviousValue;
55+
56+
@Parameterized.Parameters(name = "{0}")
57+
public static Collection<Object[]> inputFileNames() {
58+
List<Object[]> inputFileNames = new ArrayList<Object[]>();
59+
for (int pageRot = 0; pageRot < 360; pageRot += 90) {
60+
for (int fieldRot = 0; fieldRot < 360; fieldRot += 90) {
61+
inputFileNames.add(new Object[] {"FormFlatteningDefaultAppearance_" + pageRot + "_" + fieldRot});
62+
}
63+
}
64+
return inputFileNames;
65+
}
66+
67+
public FlatteningRotatedTest(Object inputPdfFileName) {
68+
this.inputPdfFileName = (String) inputPdfFileName;
69+
}
70+
4571
@BeforeClass
4672
public static void beforeClass() {
4773
createOrClearDestinationFolder(destinationFolder);
74+
experimentalRenderingPreviousValue = ExperimentalFeatures.ENABLE_EXPERIMENTAL_TEXT_FORM_RENDERING;
75+
ExperimentalFeatures.ENABLE_EXPERIMENTAL_TEXT_FORM_RENDERING = true;
76+
}
77+
78+
@AfterClass
79+
public static void afterClass() {
80+
ExperimentalFeatures.ENABLE_EXPERIMENTAL_TEXT_FORM_RENDERING = experimentalRenderingPreviousValue;
4881
}
4982

5083
@Test
5184
public void formFlatteningTest_DefaultAppearanceGeneration_Rot() throws IOException, InterruptedException {
52-
String srcFilePatternPattern = "FormFlatteningDefaultAppearance_{0}_";
53-
String destPatternPattern = "FormFlatteningDefaultAppearance_{0}_";
54-
55-
String[] rotAngle = new String[] {"0", "90", "180", "270"};
56-
57-
for (String angle : rotAngle) {
58-
String srcFilePattern = MessageFormatUtil.format(srcFilePatternPattern, angle);
59-
String destPattern = MessageFormatUtil.format(destPatternPattern, angle);
60-
for (int i = 0; i < 360; i += 90) {
61-
String src = sourceFolder + srcFilePattern + i + ".pdf";
62-
String dest = destinationFolder + destPattern + i + "_flattened.pdf";
63-
String cmp = sourceFolder + "cmp_" + srcFilePattern + i + ".pdf";
64-
PdfDocument doc = new PdfDocument(new PdfReader(src), new PdfWriter(dest));
65-
66-
PdfAcroForm form = PdfAcroForm.getAcroForm(doc, true);
67-
for (PdfFormField field : form.getAllFormFields().values()) {
68-
field.setValue("Test");
69-
}
70-
form.flattenFields();
85+
String src = sourceFolder + inputPdfFileName + ".pdf";
86+
String dest = destinationFolder + inputPdfFileName + ".pdf";
87+
String dest_flattened = destinationFolder + inputPdfFileName + "_flattened.pdf";
88+
String cmp = sourceFolder + "cmp_" + inputPdfFileName + ".pdf";
89+
String cmp_flattened = sourceFolder + "cmp_" + inputPdfFileName + "_flattened.pdf";
7190

72-
doc.close();
73-
74-
Assert.assertNull(new CompareTool().compareByContent(dest, cmp, destinationFolder, "diff_"));
91+
try (PdfDocument doc = new PdfDocument(new PdfReader(src), new PdfWriter(dest))) {
92+
PdfAcroForm form = PdfAcroForm.getAcroForm(doc, true);
93+
for (PdfFormField field : form.getAllFormFields().values()) {
94+
field.setValue("Long Long Text");
95+
field.getFirstFormAnnotation().setBorderWidth(1);
96+
field.getFirstFormAnnotation().setBorderColor(ColorConstants.BLUE);
7597
}
7698
}
99+
Assert.assertNull(new CompareTool().compareByContent(dest, cmp, destinationFolder, "diff_"));
100+
101+
try (PdfDocument doc = new PdfDocument(new PdfReader(dest), new PdfWriter(dest_flattened))) {
102+
PdfAcroForm.getAcroForm(doc, true).flattenFields();
103+
}
104+
Assert.assertNull(new CompareTool().compareByContent(dest_flattened, cmp_flattened, destinationFolder, "diff_"));
77105
}
78106
}

0 commit comments

Comments
 (0)