Skip to content

Commit 11e1f13

Browse files
committed
Do not trim leading spaces in single line and multiline text fields
Previously, leading spaces were trimmed because this is the default layout behavior. However, Acrobat does not trim leading spaces and thus there was a difference between the appearance generated by iText and the one that Acrobat generates when you enter the field. We now follow Acrobat and do not trim leading spaces DEVSIX-3539
1 parent 2d56687 commit 11e1f13

File tree

7 files changed

+162
-8
lines changed

7 files changed

+162
-8
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2019 iText Group NV
4+
Authors: iText Software.
5+
6+
This program is offered under a commercial and under the AGPL license.
7+
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
8+
9+
AGPL licensing:
10+
This program is free software: you can redistribute it and/or modify
11+
it under the terms of the GNU Affero General Public License as published by
12+
the Free Software Foundation, either version 3 of the License, or
13+
(at your option) any later version.
14+
15+
This program is distributed in the hope that it will be useful,
16+
but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
GNU Affero General Public License for more details.
19+
20+
You should have received a copy of the GNU Affero General Public License
21+
along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
package com.itextpdf.forms.fields;
24+
25+
import com.itextpdf.layout.element.Text;
26+
import com.itextpdf.layout.layout.LayoutContext;
27+
import com.itextpdf.layout.layout.LayoutResult;
28+
import com.itextpdf.layout.layout.TextLayoutResult;
29+
import com.itextpdf.layout.renderer.IRenderer;
30+
import com.itextpdf.layout.renderer.TextRenderer;
31+
32+
/**
33+
* Custom implementation for rendering form field values. It makes sure that text value
34+
* trimming strategy matches Acrobat's behavior
35+
*/
36+
class FormFieldValueNonTrimmingTextRenderer extends TextRenderer {
37+
// Determines whether we want to trim leading space. In particular we don't want to trim
38+
// the very first leading spaces of the text value. When text overflows to the next lines,
39+
// whether we should trim the text depends on why the overflow happened
40+
private boolean callTrimFirst = false;
41+
42+
public FormFieldValueNonTrimmingTextRenderer(Text textElement) {
43+
super(textElement);
44+
}
45+
46+
@Override
47+
public IRenderer getNextRenderer() {
48+
return new FormFieldValueNonTrimmingTextRenderer((Text) getModelElement());
49+
}
50+
51+
@Override
52+
public LayoutResult layout(LayoutContext layoutContext) {
53+
LayoutResult baseLayoutResult = super.layout(layoutContext);
54+
if (baseLayoutResult instanceof TextLayoutResult &&
55+
baseLayoutResult.getOverflowRenderer() instanceof FormFieldValueNonTrimmingTextRenderer &&
56+
!((TextLayoutResult) baseLayoutResult).isSplitForcedByNewline()) {
57+
// In case the overflow to the next line happened naturally (without a forced line break),
58+
// we don't want to preserve the extra spaces at the beginning of the next line
59+
((FormFieldValueNonTrimmingTextRenderer) baseLayoutResult.getOverflowRenderer()).setCallTrimFirst(true);
60+
}
61+
return baseLayoutResult;
62+
}
63+
64+
@Override
65+
public void trimFirst() {
66+
if (callTrimFirst) {
67+
super.trimFirst();
68+
}
69+
}
70+
71+
private void setCallTrimFirst(boolean callTrimFirst) {
72+
this.callTrimFirst = callTrimFirst;
73+
}
74+
}

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ This file is part of the iText (R) project.
9090
import com.itextpdf.layout.Canvas;
9191
import com.itextpdf.layout.Style;
9292
import com.itextpdf.layout.element.Paragraph;
93+
import com.itextpdf.layout.element.Text;
9394
import com.itextpdf.layout.layout.LayoutArea;
9495
import com.itextpdf.layout.layout.LayoutContext;
9596
import com.itextpdf.layout.layout.LayoutResult;
@@ -101,8 +102,6 @@ This file is part of the iText (R) project.
101102
import com.itextpdf.layout.property.TransparentColor;
102103
import com.itextpdf.layout.property.VerticalAlignment;
103104
import com.itextpdf.layout.renderer.IRenderer;
104-
import org.slf4j.Logger;
105-
import org.slf4j.LoggerFactory;
106105

107106
import java.io.ByteArrayOutputStream;
108107
import java.io.IOException;
@@ -113,6 +112,8 @@ This file is part of the iText (R) project.
113112
import java.util.List;
114113
import java.util.Map;
115114
import java.util.Set;
115+
import org.slf4j.Logger;
116+
import org.slf4j.LoggerFactory;
116117

117118
/**
118119
* This class represents a single field or field group in an {@link com.itextpdf.forms.PdfAcroForm
@@ -2668,7 +2669,7 @@ protected void drawTextAppearance(Rectangle rect, PdfFont font, float fontSize,
26682669
Logger logger = LoggerFactory.getLogger(PdfFormField.class);
26692670
logger.error(MessageFormatUtil.format(LogMessageConstant.COMB_FLAG_MAY_BE_SET_ONLY_IF_MAXLEN_IS_PRESENT));
26702671
}
2671-
modelCanvas.showTextAligned(new Paragraph(value).addStyle(paragraphStyle).setPaddings(0, X_OFFSET, 0, X_OFFSET),
2672+
modelCanvas.showTextAligned(createParagraphForTextFieldValue(value).addStyle(paragraphStyle).setPaddings(0, X_OFFSET, 0, X_OFFSET),
26722673
x, rect.getHeight() / 2, textAlignment, VerticalAlignment.MIDDLE);
26732674
}
26742675
canvas.
@@ -2708,7 +2709,7 @@ protected void drawMultiLineTextAppearance(Rectangle rect, PdfFont font, String
27082709
Canvas modelCanvas = new Canvas(canvas, getDocument(), areaRect);
27092710
modelCanvas.setProperty(Property.APPEARANCE_STREAM_LAYOUT, true);
27102711

2711-
Paragraph paragraph = new Paragraph(value).setFont(font)
2712+
Paragraph paragraph = createParagraphForTextFieldValue(value).setFont(font)
27122713
.setMargin(0)
27132714
.setPadding(3)
27142715
.setMultipliedLeading(1);
@@ -3687,4 +3688,10 @@ private Color appearancePropToColor(PdfDictionary appearanceCharacteristics, Pdf
36873688
}
36883689
return null;
36893690
}
3691+
3692+
private static Paragraph createParagraphForTextFieldValue(String value) {
3693+
Text text = new Text(value);
3694+
text.setNextRenderer(new FormFieldValueNonTrimmingTextRenderer(text));
3695+
return new Paragraph(text);
3696+
}
36903697
}

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

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,20 @@ This file is part of the iText (R) project.
3030
import com.itextpdf.kernel.font.PdfFontFactory;
3131
import com.itextpdf.kernel.geom.Rectangle;
3232
import com.itextpdf.kernel.pdf.PdfDocument;
33+
import com.itextpdf.kernel.pdf.PdfPage;
3334
import com.itextpdf.kernel.pdf.PdfReader;
3435
import com.itextpdf.kernel.pdf.PdfWriter;
3536
import com.itextpdf.kernel.utils.CompareTool;
36-
import com.itextpdf.layout.Document;
3737
import com.itextpdf.test.ExtendedITextTest;
3838
import com.itextpdf.test.annotations.type.IntegrationTest;
3939

40+
import java.io.IOException;
41+
import java.util.Map;
4042
import org.junit.Assert;
4143
import org.junit.BeforeClass;
4244
import org.junit.Test;
4345
import org.junit.experimental.categories.Category;
4446

45-
import java.io.IOException;
46-
import java.util.Map;
47-
4847
@Category(IntegrationTest.class)
4948
public class PdfFormFieldMultilineTextTest extends ExtendedITextTest {
5049

@@ -243,4 +242,54 @@ public void formFieldFilledWithStringTest() throws IOException, InterruptedExcep
243242
Assert.assertNull(new CompareTool().compareByContent(destinationFolder + "formFieldWithStringTest.pdf",
244243
sourceFolder + "cmp_formFieldWithStringTest.pdf", destinationFolder, "diff_"));
245244
}
245+
246+
@Test
247+
public void multilineTextFieldLeadingSpacesAreNotTrimmedTest() throws IOException, InterruptedException {
248+
String filename = destinationFolder + "multilineTextFieldLeadingSpacesAreNotTrimmed.pdf";
249+
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(filename));
250+
pdfDoc.addNewPage();
251+
252+
PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true);
253+
254+
PdfPage page = pdfDoc.getFirstPage();
255+
Rectangle rect = new Rectangle(210, 490, 300, 200);
256+
257+
PdfTextFormField field = PdfFormField.createMultilineText(pdfDoc, rect,
258+
"TestField", " value\n with\n leading\n space");
259+
260+
form.addField(field, page);
261+
262+
pdfDoc.close();
263+
264+
CompareTool compareTool = new CompareTool();
265+
String errorMessage = compareTool.compareByContent(filename, sourceFolder + "cmp_multilineTextFieldLeadingSpacesAreNotTrimmed.pdf", destinationFolder, "diff_");
266+
if (errorMessage != null) {
267+
Assert.fail(errorMessage);
268+
}
269+
}
270+
271+
@Test
272+
public void multilineTextFieldRedundantSpacesAreTrimmedTest() throws IOException, InterruptedException {
273+
String filename = destinationFolder + "multilineTextFieldRedundantSpacesAreTrimmedTest.pdf";
274+
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(filename));
275+
pdfDoc.addNewPage();
276+
277+
PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true);
278+
279+
PdfPage page = pdfDoc.getFirstPage();
280+
Rectangle rect = new Rectangle(210, 490, 90, 200);
281+
282+
PdfTextFormField field = PdfFormField.createMultilineText(pdfDoc, rect,
283+
"TestField", "before spaces after spaces");
284+
285+
form.addField(field, page);
286+
287+
pdfDoc.close();
288+
289+
CompareTool compareTool = new CompareTool();
290+
String errorMessage = compareTool.compareByContent(filename, sourceFolder + "cmp_multilineTextFieldRedundantSpacesAreTrimmedTest.pdf", destinationFolder, "diff_");
291+
if (errorMessage != null) {
292+
Assert.fail(errorMessage);
293+
}
294+
}
246295
}

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,30 @@ public void formFieldTest04() throws IOException, InterruptedException {
175175
}
176176
}
177177

178+
@Test
179+
public void textFieldLeadingSpacesAreNotTrimmedTest() throws IOException, InterruptedException {
180+
String filename = destinationFolder + "textFieldLeadingSpacesAreNotTrimmed.pdf";
181+
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(filename));
182+
pdfDoc.addNewPage();
183+
184+
PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true);
185+
186+
PdfPage page = pdfDoc.getFirstPage();
187+
Rectangle rect = new Rectangle(210, 490, 300, 22);
188+
189+
PdfTextFormField field = PdfFormField.createText(pdfDoc, rect, "TestField", " value with leading space");
190+
191+
form.addField(field, page);
192+
193+
pdfDoc.close();
194+
195+
CompareTool compareTool = new CompareTool();
196+
String errorMessage = compareTool.compareByContent(filename, sourceFolder + "cmp_textFieldLeadingSpacesAreNotTrimmed.pdf", destinationFolder, "diff_");
197+
if (errorMessage != null) {
198+
Assert.fail(errorMessage);
199+
}
200+
}
201+
178202
@Test
179203
public void unicodeFormFieldTest() throws IOException {
180204
String filename = sourceFolder + "unicodeFormFieldFile.pdf";

0 commit comments

Comments
 (0)