Skip to content

Commit e6de5b1

Browse files
committed
Form fields: fix bug with storing font and text (PushButton case). Using model for creating field appearances. Support complex scripts (Arabic/Indic) for field appearances. Minor bug fixes for model
DEVSIX-418
1 parent 4c155a0 commit e6de5b1

File tree

8 files changed

+142
-61
lines changed

8 files changed

+142
-61
lines changed

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

Lines changed: 104 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,12 @@
1010
import com.itextpdf.basics.io.PdfTokenizer;
1111
import com.itextpdf.basics.io.RandomAccessFileOrArray;
1212
import com.itextpdf.basics.io.RandomAccessSourceFactory;
13-
import com.itextpdf.core.font.PdfFontFactory;
14-
import com.itextpdf.core.pdf.canvas.PdfCanvas;
15-
import com.itextpdf.core.pdf.canvas.PdfCanvasConstants;
1613
import com.itextpdf.core.color.Color;
1714
import com.itextpdf.core.color.DeviceCmyk;
1815
import com.itextpdf.core.color.DeviceGray;
1916
import com.itextpdf.core.color.DeviceRgb;
2017
import com.itextpdf.core.font.PdfFont;
18+
import com.itextpdf.core.font.PdfFontFactory;
2119
import com.itextpdf.core.pdf.PdfArray;
2220
import com.itextpdf.core.pdf.PdfDictionary;
2321
import com.itextpdf.core.pdf.PdfDocument;
@@ -32,15 +30,19 @@
3230
import com.itextpdf.core.pdf.action.PdfAction;
3331
import com.itextpdf.core.pdf.annot.PdfAnnotation;
3432
import com.itextpdf.core.pdf.annot.PdfWidgetAnnotation;
33+
import com.itextpdf.core.pdf.canvas.PdfCanvas;
34+
import com.itextpdf.core.pdf.canvas.PdfCanvasConstants;
3535
import com.itextpdf.core.pdf.xobject.PdfFormXObject;
3636
import com.itextpdf.core.pdf.xobject.PdfImageXObject;
37+
import com.itextpdf.model.Canvas;
38+
import com.itextpdf.model.Property;
39+
import com.itextpdf.model.element.Paragraph;
3740

3841
import java.io.IOException;
3942
import java.util.ArrayList;
4043
import java.util.HashSet;
4144
import java.util.List;
4245
import java.util.Set;
43-
import java.util.StringTokenizer;
4446

4547
/**
4648
* This class represents a single field or field group in an {@link com.itextpdf.forms.PdfAcroForm
@@ -255,8 +257,26 @@ public static PdfTextFormField createText(PdfDocument doc, Rectangle rect, Strin
255257
* @return a new {@link PdfTextFormField}
256258
*/
257259
public static PdfTextFormField createText(PdfDocument doc, Rectangle rect, String name, String value, PdfFont font, int fontSize) {
260+
return createText(doc, rect, name, value, font, fontSize, false);
261+
}
262+
263+
/**
264+
* Creates a named {@link PdfTextFormField text form field} with an initial
265+
* value, with a specified font and font size.
266+
*
267+
* @param doc the {@link PdfDocument} to create the text field in
268+
* @param rect the location on the page for the text field
269+
* @param name the name of the form field
270+
* @param value the initial value
271+
* @param font a {@link PdfFont}
272+
* @param fontSize a positive integer
273+
* @param multiline true for multiline text field
274+
* @return a new {@link PdfTextFormField}
275+
*/
276+
public static PdfTextFormField createText(PdfDocument doc, Rectangle rect, String name, String value, PdfFont font, int fontSize, boolean multiline) {
258277
PdfWidgetAnnotation annot = new PdfWidgetAnnotation(rect);
259278
PdfTextFormField field = new PdfTextFormField(annot, doc);
279+
field.setMultiline(multiline);
260280
field.font = font;
261281
field.fontSize = fontSize;
262282
field.setValue(value);
@@ -265,6 +285,41 @@ public static PdfTextFormField createText(PdfDocument doc, Rectangle rect, Strin
265285
return field;
266286
}
267287

288+
/**
289+
* Creates a named {@link PdfTextFormField multilined text form field} with an initial
290+
* value, with a specified font and font size.
291+
*
292+
* @param doc the {@link PdfDocument} to create the text field in
293+
* @param rect the location on the page for the text field
294+
* @param name the name of the form field
295+
* @param value the initial value
296+
* @param font a {@link PdfFont}
297+
* @param fontSize a positive integer
298+
* @return a new {@link PdfTextFormField}
299+
*/
300+
public static PdfTextFormField createMultilineText(PdfDocument doc, Rectangle rect, String name, String value, PdfFont font, int fontSize) {
301+
return createText(doc, rect, name, value, font, fontSize, true);
302+
}
303+
304+
/**
305+
* Creates a named {@link PdfTextFormField multiline text form field} with an initial
306+
* value, and the form's default font specified in
307+
* {@link com.itextpdf.forms.PdfAcroForm#getDefaultResources}.
308+
*
309+
* @param doc the {@link PdfDocument} to create the text field in
310+
* @param rect the location on the page for the text field
311+
* @param name the name of the form field
312+
* @param value the initial value
313+
* @return a new {@link PdfTextFormField}
314+
*/
315+
public static PdfTextFormField createMultilineText(PdfDocument doc, Rectangle rect, String name, String value) {
316+
try {
317+
return createText(doc, rect, name, value, PdfFontFactory.createFont(), DEFAULT_FONT_SIZE, true);
318+
} catch (IOException e) {
319+
throw new PdfException(e.getLocalizedMessage());
320+
}
321+
}
322+
268323
/**
269324
* Creates an empty {@link PdfChoiceFormField choice form field}.
270325
*
@@ -451,6 +506,7 @@ public static PdfButtonFormField createPushButton(PdfDocument doc, Rectangle rec
451506
PdfButtonFormField field = new PdfButtonFormField(annot, doc);
452507
field.setPushButton(true);
453508
field.setFieldName(name);
509+
field.text = caption;
454510
field.font = font;
455511
field.fontSize = fontSize;
456512

@@ -625,6 +681,8 @@ public <T extends PdfFormField> T setValue(String value) {
625681
kid = ((PdfIndirectReference) kid).getRefersTo();
626682
}
627683
PdfFormField field = new PdfFormField((PdfDictionary) kid);
684+
field.font = font;
685+
field.fontSize = fontSize;
628686
field.setValue(value);
629687
}
630688
}
@@ -1794,32 +1852,38 @@ protected PdfFormXObject drawTextAppearance(Rectangle rect, PdfFont font, int fo
17941852
canvas.
17951853
beginVariableText().
17961854
saveState().
1797-
newPath().
1798-
beginText().
1799-
setFontAndSize(font, fontSize);
1855+
newPath();
1856+
1857+
Paragraph paragraph = new Paragraph(value).setFont(font).setFontSize(fontSize).setMultipliedLeading(1).setPaddings(0, 2, 0, 2);
1858+
setParagraphProperties(paragraph, value);
18001859
if (color != null) {
1801-
canvas.setFillColor(color);
1802-
} else {
1803-
canvas.resetFillColorRgb();
1860+
paragraph.setFontColor(color);
18041861
}
18051862
Integer justification = getJustification();
18061863
if (justification == null) {
18071864
justification = 0;
18081865
}
1809-
drawTextAligned(canvas, justification, value, 2, height / 2 - fontSize * 0.3f, font, fontSize);
1866+
float x = 0;
1867+
Property.TextAlignment textAlignment = Property.TextAlignment.LEFT;
1868+
if (justification == ALIGN_RIGHT) {
1869+
textAlignment = Property.TextAlignment.RIGHT;
1870+
x = rect.getWidth();
1871+
} else if (justification == ALIGN_CENTER) {
1872+
textAlignment = Property.TextAlignment.CENTER;
1873+
x = rect.getWidth() / 2;
1874+
}
1875+
new Canvas(canvas, getDocument(), new Rectangle(0, -height, 0, 2 * height)).showTextAligned(paragraph, x, rect.getHeight() / 2, textAlignment, Property.VerticalAlignment.MIDDLE);
1876+
18101877
canvas.
1811-
endText().
18121878
restoreState().
18131879
endVariableText();
18141880

1815-
18161881
PdfFormXObject xObject = new PdfFormXObject(new Rectangle(0, 0, width, height));
18171882
xObject.getPdfObject().getOutputStream().writeBytes(stream.getBytes());
18181883

18191884
return xObject;
18201885
}
18211886

1822-
18231887
/**
18241888
* Draws the visual appearance of multiline text in a form field.
18251889
*
@@ -1838,39 +1902,26 @@ protected PdfFormXObject drawMultiLineTextAppearance(Rectangle rect, PdfFont fon
18381902
float width = rect.getWidth();
18391903
float height = rect.getHeight();
18401904

1841-
List<String> strings = font.splitString(value, fontSize, width - 6);
1842-
1843-
value = "";
1844-
for (String str : strings) {
1845-
value += str + '\n';
1846-
}
1847-
value = value.substring(0, value.length() - 1);
1848-
18491905
drawBorder(canvas, width, height);
18501906
canvas.
18511907
beginVariableText().
18521908
saveState().
18531909
rectangle(3, 3, width - 6, height - 6).
18541910
clip().
1855-
newPath().
1856-
beginText().
1857-
setFontAndSize(font, fontSize);
1911+
newPath();
1912+
1913+
Canvas modelCanvas = new Canvas(canvas, getDocument(), new Rectangle(3, 0, width - 6, height - 2));
1914+
Paragraph paragraph = new Paragraph(value).setFont(font).setFontSize(fontSize).setMargins(0, 0, 0, 0).setMultipliedLeading(1);
1915+
setParagraphProperties(paragraph, value);
1916+
if (value != null && value.length() > 0) {
1917+
paragraph.setFontScript(Character.UnicodeScript.of(value.charAt(0)));
1918+
}
18581919
if (color != null) {
1859-
canvas.setFillColor(color);
1860-
} else {
1861-
canvas.resetFillColorRgb();
1920+
paragraph.setFontColor(color);
18621921
}
1922+
modelCanvas.add(paragraph);
18631923

1864-
canvas.setTextMatrix(4, 5);
1865-
StringTokenizer tokenizer = new StringTokenizer(value, "\n");
1866-
while (tokenizer.hasMoreTokens()) {
1867-
height -= fontSize * 1.2;
1868-
canvas.
1869-
setTextMatrix(3, height).
1870-
showText(tokenizer.nextToken());
1871-
}
18721924
canvas.
1873-
endText().
18741925
restoreState().
18751926
endVariableText();
18761927

@@ -2067,13 +2118,14 @@ protected void drawButton(PdfCanvas canvas, float x, float y, float width, float
20672118
lineTo(x + width - 1, y + 1).
20682119
lineTo(x + width - 1, y + height - 1).
20692120
stroke().
2070-
resetFillColorRgb().
2071-
beginText().
2072-
setFontAndSize(font, fontSize).
2073-
setTextMatrix(0, y + (height - fontSize) / 2).
2074-
showText(text).
2075-
endText().
2076-
restoreState();
2121+
resetFillColorRgb();
2122+
2123+
Paragraph paragraph = new Paragraph(text).setFont(font).setFontSize(fontSize).setMargin(0).setMultipliedLeading(1).
2124+
setVerticalAlignment(Property.VerticalAlignment.MIDDLE);
2125+
setParagraphProperties(paragraph, text);
2126+
new Canvas(canvas, getDocument(), new Rectangle(0, -height, width, 2 * height)).showTextAligned(paragraph, width / 2, height / 2, Property.TextAlignment.CENTER, Property.VerticalAlignment.MIDDLE);
2127+
2128+
canvas.restoreState();
20772129
}
20782130

20792131
/**
@@ -2119,17 +2171,15 @@ private String obfuscatePassword(String text) {
21192171
return new String(pchar);
21202172
}
21212173

2122-
private void drawTextAligned(PdfCanvas canvas, int alignment, String text, float x, float y, PdfFont font, int fontSize) {
2123-
switch (alignment) {
2124-
case ALIGN_CENTER:
2125-
x = (getRect(getPdfObject()).getWidth() - font.getWidth(text, fontSize)) / 2;
2126-
break;
2127-
case ALIGN_RIGHT:
2128-
x = (getRect(getPdfObject()).getWidth() - font.getWidth(text, fontSize));
2129-
break;
2174+
private void setParagraphProperties(Paragraph paragraph, String value) {
2175+
// TODO this is temporary and will be replaced by script autodetection logic on model level
2176+
if (value != null && value.length() > 0) {
2177+
Character.UnicodeScript script = Character.UnicodeScript.of(value.charAt(0));
2178+
paragraph.setFontScript(script);
2179+
if (script == Character.UnicodeScript.ARABIC || script == Character.UnicodeScript.HEBREW) {
2180+
paragraph.setBaseDirection(Property.BaseDirection.RIGHT_TO_LEFT);
2181+
}
21302182
}
2131-
canvas.setTextMatrix(x, y);
2132-
canvas.showText(text);
21332183
}
21342184

21352185
}
Binary file not shown.
Binary file not shown.

model/src/main/java/com/itextpdf/model/RootElement.java

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,12 +206,41 @@ public <T extends RootElement> T showTextAlignedKerned(String text, float x, flo
206206
* Set width to write multiline text.
207207
* @param x the point about which the text will be aligned and rotated
208208
* @param y the point about which the text will be aligned and rotated
209-
* @param pageNumber the page number to write the text
209+
* @param textAlign horizontal alignment about the specified point
210+
* @return this object
211+
*/
212+
public <T extends RootElement> T showTextAligned(Paragraph p, float x, float y, Property.TextAlignment textAlign) {
213+
return showTextAligned(p, x, y, pdfDocument.getNumberOfPages(), textAlign, Property.VerticalAlignment.BOTTOM, 0);
214+
}
215+
216+
/**
217+
* Convenience method to write a text aligned about the specified point
218+
* @param <T> the return type
219+
* @param p paragraph of text to be placed to the page. By default it has no leading and is written in single line.
220+
* Set width to write multiline text.
221+
* @param x the point about which the text will be aligned and rotated
222+
* @param y the point about which the text will be aligned and rotated
210223
* @param textAlign horizontal alignment about the specified point
211224
* @param vertAlign vertical alignment about the specified point
212-
* @param angle the angle of rotation applied to the text, in radians
213225
* @return this object
214226
*/
227+
public <T extends RootElement> T showTextAligned(Paragraph p, float x, float y, Property.TextAlignment textAlign, Property.VerticalAlignment vertAlign) {
228+
return showTextAligned(p, x, y, pdfDocument.getNumberOfPages(), textAlign, vertAlign, 0);
229+
}
230+
231+
/**
232+
* Convenience method to write a text aligned about the specified point
233+
* @param <T> the return type
234+
* @param p paragraph of text to be placed to the page. By default it has no leading and is written in single line.
235+
* Set width to write multiline text.
236+
* @param x the point about which the text will be aligned and rotated
237+
* @param y the point about which the text will be aligned and rotated
238+
* @param pageNumber the page number to write the text
239+
* @param textAlign horizontal alignment about the specified point
240+
* @param vertAlign vertical alignment about the specified point
241+
* @param angle the angle of rotation applied to the text, in radians
242+
* @return this object
243+
*/
215244
public <T extends RootElement> T showTextAligned(Paragraph p, float x, float y, int pageNumber, Property.TextAlignment textAlign, Property.VerticalAlignment vertAlign, float angle) {
216245
Div div = new Div();
217246
div.setTextAlignment(textAlign).setVerticalAlignment(vertAlign);

model/src/main/java/com/itextpdf/model/renderer/RootRenderer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public void addChild(IRenderer renderer) {
5757
if (currentArea.isEmptyArea() && !(renderer instanceof AreaBreakRenderer)) {
5858
if (Boolean.valueOf(true).equals(result.getOverflowRenderer().getModelElement().getProperty(Property.KEEP_TOGETHER))) {
5959
result.getOverflowRenderer().getModelElement().setProperty(Property.KEEP_TOGETHER, false);
60-
Logger logger = LoggerFactory.getLogger(DocumentRenderer.class);
60+
Logger logger = LoggerFactory.getLogger(RootRenderer.class);
6161
logger.warn(MessageFormat.format(LogMessageConstant.ELEMENT_DOES_NOT_FIT_AREA, "KeepTogether property will be ignored."));
6262
if (storedArea != null) {
6363
nextStoredArea = currentArea;
@@ -67,7 +67,7 @@ public void addChild(IRenderer renderer) {
6767
storedArea = currentArea;
6868
} else {
6969
result.getOverflowRenderer().setProperty(Property.FORCED_PLACEMENT, true);
70-
Logger logger = LoggerFactory.getLogger(DocumentRenderer.class);
70+
Logger logger = LoggerFactory.getLogger(RootRenderer.class);
7171
logger.warn(MessageFormat.format(LogMessageConstant.ELEMENT_DOES_NOT_FIT_AREA, ""));
7272
}
7373
renderer = result.getOverflowRenderer();

model/src/main/java/com/itextpdf/model/renderer/TextRenderer.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -564,9 +564,11 @@ public <T> T getDefaultProperty(Property property) {
564564
public void trimFirst() {
565565
convertWaitingStringToGlyphLine();
566566

567-
Glyph glyph;
568-
while (text.start < text.end && (glyph = text.glyphs.get(text.start)).getUnicode() != null && Character.isWhitespace(glyph.getUnicode())) {
569-
text.start++;
567+
if (text != null) {
568+
Glyph glyph;
569+
while (text.start < text.end && (glyph = text.glyphs.get(text.start)).getUnicode() != null && Character.isWhitespace(glyph.getUnicode())) {
570+
text.start++;
571+
}
570572
}
571573
}
572574

0 commit comments

Comments
 (0)