1 /*
2 * $Id: ColumnText.java 3373 2008-05-12 16:21:24Z xlv $
3 *
4 * Copyright 2001, 2002 by Paulo Soares.
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * (the "License"); you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at http://www.mozilla.org/MPL/
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the License.
13 *
14 * The Original Code is 'iText, a free JAVA-PDF library'.
15 *
16 * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
17 * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
18 * All Rights Reserved.
19 * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
20 * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
21 *
22 * Contributor(s): all the names of the contributors are added in the source code
23 * where applicable.
24 *
25 * Alternatively, the contents of this file may be used under the terms of the
26 * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
27 * provisions of LGPL are applicable instead of those above. If you wish to
28 * allow use of your version of this file only under the terms of the LGPL
29 * License and not to allow others to use your version of this file under
30 * the MPL, indicate your decision by deleting the provisions above and
31 * replace them with the notice and other provisions required by the LGPL.
32 * If you do not delete the provisions above, a recipient may use your version
33 * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
34 *
35 * This library is free software; you can redistribute it and/or modify it
36 * under the terms of the MPL as stated above or under the terms of the GNU
37 * Library General Public License as published by the Free Software Foundation;
38 * either version 2 of the License, or any later version.
39 *
40 * This library is distributed in the hope that it will be useful, but WITHOUT
41 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
42 * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
43 * details.
44 *
45 * If you didn't download this code from the following link, you should check if
46 * you aren't using an obsolete version:
47 * http://www.lowagie.com/iText/
48 */
49
50 package com.lowagie.text.pdf;
51 import java.util.ArrayList;
52 import java.util.Iterator;
53 import java.util.LinkedList;
54 import java.util.Stack;
55
56 import com.lowagie.text.Chunk;
57 import com.lowagie.text.DocumentException;
58 import com.lowagie.text.Element;
59 import com.lowagie.text.ExceptionConverter;
60 import com.lowagie.text.Image;
61 import com.lowagie.text.ListItem;
62 import com.lowagie.text.Paragraph;
63 import com.lowagie.text.Phrase;
64 import com.lowagie.text.SimpleTable;
65 import com.lowagie.text.pdf.draw.DrawInterface;
66
67 /**
68 * Formats text in a columnwise form. The text is bound
69 * on the left and on the right by a sequence of lines. This allows the column
70 * to have any shape, not only rectangular.
71 * <P>
72 * Several parameters can be set like the first paragraph line indent and
73 * extra space between paragraphs.
74 * <P>
75 * A call to the method <CODE>go</CODE> will return one of the following
76 * situations: the column ended or the text ended.
77 * <P>
78 * I the column ended, a new column definition can be loaded with the method
79 * <CODE>setColumns</CODE> and the method <CODE>go</CODE> can be called again.
80 * <P>
81 * If the text ended, more text can be loaded with <CODE>addText</CODE>
82 * and the method <CODE>go</CODE> can be called again.<BR>
83 * The only limitation is that one or more complete paragraphs must be loaded
84 * each time.
85 * <P>
86 * Full bidirectional reordering is supported. If the run direction is
87 * <CODE>PdfWriter.RUN_DIRECTION_RTL</CODE> the meaning of the horizontal
88 * alignments and margins is mirrored.
89 * @author Paulo Soares (psoares@consiste.pt)
90 */
91
92 public class ColumnText {
93 /** Eliminate the arabic vowels */
94 public static final int AR_NOVOWEL = ArabicLigaturizer.ar_novowel;
95 /** Compose the tashkeel in the ligatures. */
96 public static final int AR_COMPOSEDTASHKEEL = ArabicLigaturizer.ar_composedtashkeel;
97 /** Do some extra double ligatures. */
98 public static final int AR_LIG = ArabicLigaturizer.ar_lig;
99 /**
100 * Digit shaping option: Replace European digits (U+0030...U+0039) by Arabic-Indic digits.
101 */
102 public static final int DIGITS_EN2AN = ArabicLigaturizer.DIGITS_EN2AN;
103
104 /**
105 * Digit shaping option: Replace Arabic-Indic digits by European digits (U+0030...U+0039).
106 */
107 public static final int DIGITS_AN2EN = ArabicLigaturizer.DIGITS_AN2EN;
108
109 /**
110 * Digit shaping option:
111 * Replace European digits (U+0030...U+0039) by Arabic-Indic digits
112 * if the most recent strongly directional character
113 * is an Arabic letter (its Bidi direction value is RIGHT_TO_LEFT_ARABIC).
114 * The initial state at the start of the text is assumed to be not an Arabic,
115 * letter, so European digits at the start of the text will not change.
116 * Compare to DIGITS_ALEN2AN_INIT_AL.
117 */
118 public static final int DIGITS_EN2AN_INIT_LR = ArabicLigaturizer.DIGITS_EN2AN_INIT_LR;
119
120 /**
121 * Digit shaping option:
122 * Replace European digits (U+0030...U+0039) by Arabic-Indic digits
123 * if the most recent strongly directional character
124 * is an Arabic letter (its Bidi direction value is RIGHT_TO_LEFT_ARABIC).
125 * The initial state at the start of the text is assumed to be an Arabic,
126 * letter, so European digits at the start of the text will change.
127 * Compare to DIGITS_ALEN2AN_INT_LR.
128 */
129 public static final int DIGITS_EN2AN_INIT_AL = ArabicLigaturizer.DIGITS_EN2AN_INIT_AL;
130
131 /**
132 * Digit type option: Use Arabic-Indic digits (U+0660...U+0669).
133 */
134 public static final int DIGIT_TYPE_AN = ArabicLigaturizer.DIGIT_TYPE_AN;
135
136 /**
137 * Digit type option: Use Eastern (Extended) Arabic-Indic digits (U+06f0...U+06f9).
138 */
139 public static final int DIGIT_TYPE_AN_EXTENDED = ArabicLigaturizer.DIGIT_TYPE_AN_EXTENDED;
140
141 protected int runDirection = PdfWriter.RUN_DIRECTION_DEFAULT;
142
143 /** the space char ratio */
144 public static final float GLOBAL_SPACE_CHAR_RATIO = 0;
145
146 /** Initial value of the status. */
147 public static final int START_COLUMN = 0;
148
149 /** Signals that there is no more text available. */
150 public static final int NO_MORE_TEXT = 1;
151
152 /** Signals that there is no more column. */
153 public static final int NO_MORE_COLUMN = 2;
154
155 /** The column is valid. */
156 protected static final int LINE_STATUS_OK = 0;
157
158 /** The line is out the column limits. */
159 protected static final int LINE_STATUS_OFFLIMITS = 1;
160
161 /** The line cannot fit this column position. */
162 protected static final int LINE_STATUS_NOLINE = 2;
163
164 /** Upper bound of the column. */
165 protected float maxY;
166
167 /** Lower bound of the column. */
168 protected float minY;
169
170 protected float leftX;
171
172 protected float rightX;
173
174 /** The column alignment. Default is left alignment. */
175 protected int alignment = Element.ALIGN_LEFT;
176
177 /** The left column bound. */
178 protected ArrayList leftWall;
179
180 /** The right column bound. */
181 protected ArrayList rightWall;
182
183 /** The chunks that form the text. */
184 // protected ArrayList chunks = new ArrayList();
185 protected BidiLine bidiLine;
186
187 /** The current y line location. Text will be written at this line minus the leading. */
188 protected float yLine;
189
190 /** The leading for the current line. */
191 protected float currentLeading = 16;
192
193 /** The fixed text leading. */
194 protected float fixedLeading = 16;
195
196 /** The text leading that is multiplied by the biggest font size in the line. */
197 protected float multipliedLeading = 0;
198
199 /** The <CODE>PdfContent</CODE> where the text will be written to. */
200 protected PdfContentByte canvas;
201
202 protected PdfContentByte[] canvases;
203
204 /** The line status when trying to fit a line to a column. */
205 protected int lineStatus;
206
207 /** The first paragraph line indent. */
208 protected float indent = 0;
209
210 /** The following paragraph lines indent. */
211 protected float followingIndent = 0;
212
213 /** The right paragraph lines indent. */
214 protected float rightIndent = 0;
215
216 /** The extra space between paragraphs. */
217 protected float extraParagraphSpace = 0;
218
219 /** The width of the line when the column is defined as a simple rectangle. */
220 protected float rectangularWidth = -1;
221
222 protected boolean rectangularMode = false;
223 /** Holds value of property spaceCharRatio. */
224 private float spaceCharRatio = GLOBAL_SPACE_CHAR_RATIO;
225
226 private boolean lastWasNewline = true;
227
228 /** Holds value of property linesWritten. */
229 private int linesWritten;
230
231 private float firstLineY;
232 private boolean firstLineYDone = false;
233
234 /** Holds value of property arabicOptions. */
235 private int arabicOptions = 0;
236
237 protected float descender;
238
239 protected boolean composite = false;
240
241 protected ColumnText compositeColumn;
242
243 protected LinkedList compositeElements;
244
245 protected int listIdx = 0;
246
247 private boolean splittedRow;
248
249 protected Phrase waitPhrase;
250
251 /** if true, first line height is adjusted so that the max ascender touches the top */
252 private boolean useAscender = false;
253
254 /**
255 * Creates a <CODE>ColumnText</CODE>.
256 * @param canvas the place where the text will be written to. Can
257 * be a template.
258 */
259 public ColumnText(PdfContentByte canvas) {
260 this.canvas = canvas;
261 }
262
263 /** Creates an independent duplicated of the instance <CODE>org</CODE>.
264 * @param org the original <CODE>ColumnText</CODE>
265 * @return the duplicated
266 */
267 public static ColumnText duplicate(ColumnText org) {
268 ColumnText ct = new ColumnText(null);
269 ct.setACopy(org);
270 return ct;
271 }
272
273 /** Makes this instance an independent copy of <CODE>org</CODE>.
274 * @param org the original <CODE>ColumnText</CODE>
275 * @return itself
276 */
277 public ColumnText setACopy(ColumnText org) {
278 setSimpleVars(org);
279 if (org.bidiLine != null)
280 bidiLine = new BidiLine(org.bidiLine);
281 return this;
282 }
283
284 protected void setSimpleVars(ColumnText org) {
285 maxY = org.maxY;
286 minY = org.minY;
287 alignment = org.alignment;
288 leftWall = null;
289 if (org.leftWall != null)
290 leftWall = new ArrayList(org.leftWall);
291 rightWall = null;
292 if (org.rightWall != null)
293 rightWall = new ArrayList(org.rightWall);
294 yLine = org.yLine;
295 currentLeading = org.currentLeading;
296 fixedLeading = org.fixedLeading;
297 multipliedLeading = org.multipliedLeading;
298 canvas = org.canvas;
299 canvases = org.canvases;
300 lineStatus = org.lineStatus;
301 indent = org.indent;
302 followingIndent = org.followingIndent;
303 rightIndent = org.rightIndent;
304 extraParagraphSpace = org.extraParagraphSpace;
305 rectangularWidth = org.rectangularWidth;
306 rectangularMode = org.rectangularMode;
307 spaceCharRatio = org.spaceCharRatio;
308 lastWasNewline = org.lastWasNewline;
309 linesWritten = org.linesWritten;
310 arabicOptions = org.arabicOptions;
311 runDirection = org.runDirection;
312 descender = org.descender;
313 composite = org.composite;
314 splittedRow = org.splittedRow;
315 if (org.composite) {
316 compositeElements = new LinkedList(org.compositeElements);
317 if (splittedRow) {
318 PdfPTable table = (PdfPTable)compositeElements.getFirst();
319 compositeElements.set(0, new PdfPTable(table));
320 }
321 if (org.compositeColumn != null)
322 compositeColumn = duplicate(org.compositeColumn);
323 }
324 listIdx = org.listIdx;
325 firstLineY = org.firstLineY;
326 leftX = org.leftX;
327 rightX = org.rightX;
328 firstLineYDone = org.firstLineYDone;
329 waitPhrase = org.waitPhrase;
330 useAscender = org.useAscender;
331 filledWidth = org.filledWidth;
332 adjustFirstLine = org.adjustFirstLine;
333 }
334
335 private void addWaitingPhrase() {
336 if (bidiLine == null && waitPhrase != null) {
337 bidiLine = new BidiLine();
338 for (Iterator j = waitPhrase.getChunks().iterator(); j.hasNext();) {
339 bidiLine.addChunk(new PdfChunk((Chunk)j.next(), null));
340 }
341 waitPhrase = null;
342 }
343 }
344
345 /**
346 * Adds a <CODE>Phrase</CODE> to the current text array.
347 * Will not have any effect if addElement() was called before.
348 * @param phrase the text
349 */
350 public void addText(Phrase phrase) {
351 if (phrase == null || composite)
352 return;
353 addWaitingPhrase();
354 if (bidiLine == null) {
355 waitPhrase = phrase;
356 return;
357 }
358 for (Iterator j = phrase.getChunks().iterator(); j.hasNext();) {
359 bidiLine.addChunk(new PdfChunk((Chunk)j.next(), null));
360 }
361 }
362
363 /**
364 * Replaces the current text array with this <CODE>Phrase</CODE>.
365 * Anything added previously with addElement() is lost.
366 * @param phrase the text
367 */
368 public void setText(Phrase phrase) {
369 bidiLine = null;
370 composite = false;
371 compositeColumn = null;
372 compositeElements = null;
373 listIdx = 0;
374 splittedRow = false;
375 waitPhrase = phrase;
376 }
377
378 /**
379 * Adds a <CODE>Chunk</CODE> to the current text array.
380 * Will not have any effect if addElement() was called before.
381 * @param chunk the text
382 */
383 public void addText(Chunk chunk) {
384 if (chunk == null || composite)
385 return;
386 addText(new Phrase(chunk));
387 }
388
389 /**
390 * Adds an element. Elements supported are <CODE>Paragraph</CODE>,
391 * <CODE>List</CODE>, <CODE>PdfPTable</CODE>, <CODE>Image</CODE> and
392 * <CODE>Graphic</CODE>.
393 * <p>
394 * It removes all the text placed with <CODE>addText()</CODE>.
395 * @param element the <CODE>Element</CODE>
396 */
397 public void addElement(Element element) {
398 if (element == null)
399 return;
400 if (element instanceof Image) {
401 Image img = (Image)element;
402 PdfPTable t = new PdfPTable(1);
403 float w = img.getWidthPercentage();
404 if (w == 0) {
405 t.setTotalWidth(img.getScaledWidth());
406 t.setLockedWidth(true);
407 }
408 else
409 t.setWidthPercentage(w);
410 t.setSpacingAfter(img.getSpacingAfter());
411 t.setSpacingBefore(img.getSpacingBefore());
412 switch (img.getAlignment()) {
413 case Image.LEFT:
414 t.setHorizontalAlignment(Element.ALIGN_LEFT);
415 break;
416 case Image.RIGHT:
417 t.setHorizontalAlignment(Element.ALIGN_RIGHT);
418 break;
419 default:
420 t.setHorizontalAlignment(Element.ALIGN_CENTER);
421 break;
422 }
423 PdfPCell c = new PdfPCell(img, true);
424 c.setPadding(0);
425 c.setBorder(img.getBorder());
426 c.setBorderColor(img.getBorderColor());
427 c.setBorderWidth(img.getBorderWidth());
428 c.setBackgroundColor(img.getBackgroundColor());
429 t.addCell(c);
430 element = t;
431 }
432 if (element.type() == Element.CHUNK) {
433 element = new Paragraph((Chunk)element);
434 }
435 else if (element.type() == Element.PHRASE) {
436 element = new Paragraph((Phrase)element);
437 }
438 if (element instanceof SimpleTable) {
439 try {
440 element = ((SimpleTable)element).createPdfPTable();
441 } catch (DocumentException e) {
442 throw new IllegalArgumentException("Element not allowed.");
443 }
444 }
445 else if (element.type() != Element.PARAGRAPH && element.type() != Element.LIST && element.type() != Element.PTABLE && element.type() != Element.YMARK)
446 throw new IllegalArgumentException("Element not allowed.");
447 if (!composite) {
448 composite = true;
449 compositeElements = new LinkedList();
450 bidiLine = null;
451 waitPhrase = null;
452 }
453 compositeElements.add(element);
454 }
455
456 /**
457 * Converts a sequence of lines representing one of the column bounds into
458 * an internal format.
459 * <p>
460 * Each array element will contain a <CODE>float[4]</CODE> representing
461 * the line x = ax + b.
462 * @param cLine the column array
463 * @return the converted array
464 */
465 protected ArrayList convertColumn(float cLine[]) {
466 if (cLine.length < 4)
467 throw new RuntimeException("No valid column line found.");
468 ArrayList cc = new ArrayList();
469 for (int k = 0; k < cLine.length - 2; k += 2) {
470 float x1 = cLine[k];
471 float y1 = cLine[k + 1];
472 float x2 = cLine[k + 2];
473 float y2 = cLine[k + 3];
474 if (y1 == y2)
475 continue;
476 // x = ay + b
477 float a = (x1 - x2) / (y1 - y2);
478 float b = x1 - a * y1;
479 float r[] = new float[4];
480 r[0] = Math.min(y1, y2);
481 r[1] = Math.max(y1, y2);
482 r[2] = a;
483 r[3] = b;
484 cc.add(r);
485 maxY = Math.max(maxY, r[1]);
486 minY = Math.min(minY, r[0]);
487 }
488 if (cc.isEmpty())
489 throw new RuntimeException("No valid column line found.");
490 return cc;
491 }
492
493 /**
494 * Finds the intersection between the <CODE>yLine</CODE> and the column. It will
495 * set the <CODE>lineStatus</CODE> appropriately.
496 * @param wall the column to intersect
497 * @return the x coordinate of the intersection
498 */
499 protected float findLimitsPoint(ArrayList wall) {
500 lineStatus = LINE_STATUS_OK;
501 if (yLine < minY || yLine > maxY) {
502 lineStatus = LINE_STATUS_OFFLIMITS;
503 return 0;
504 }
505 for (int k = 0; k < wall.size(); ++k) {
506 float r[] = (float[])wall.get(k);
507 if (yLine < r[0] || yLine > r[1])
508 continue;
509 return r[2] * yLine + r[3];
510 }
511 lineStatus = LINE_STATUS_NOLINE;
512 return 0;
513 }
514
515 /**
516 * Finds the intersection between the <CODE>yLine</CODE> and the two
517 * column bounds. It will set the <CODE>lineStatus</CODE> appropriately.
518 * @return a <CODE>float[2]</CODE>with the x coordinates of the intersection
519 */
520 protected float[] findLimitsOneLine() {
521 float x1 = findLimitsPoint(leftWall);
522 if (lineStatus == LINE_STATUS_OFFLIMITS || lineStatus == LINE_STATUS_NOLINE)
523 return null;
524 float x2 = findLimitsPoint(rightWall);
525 if (lineStatus == LINE_STATUS_NOLINE)
526 return null;
527 return new float[]{x1, x2};
528 }
529
530 /**
531 * Finds the intersection between the <CODE>yLine</CODE>,
532 * the <CODE>yLine-leading</CODE>and the two
533 * column bounds. It will set the <CODE>lineStatus</CODE> appropriately.
534 * @return a <CODE>float[4]</CODE>with the x coordinates of the intersection
535 */
536 protected float[] findLimitsTwoLines() {
537 boolean repeat = false;
538 for (;;) {
539 if (repeat && currentLeading == 0)
540 return null;
541 repeat = true;
542 float x1[] = findLimitsOneLine();
543 if (lineStatus == LINE_STATUS_OFFLIMITS)
544 return null;
545 yLine -= currentLeading;
546 if (lineStatus == LINE_STATUS_NOLINE) {
547 continue;
548 }
549 float x2[] = findLimitsOneLine();
550 if (lineStatus == LINE_STATUS_OFFLIMITS)
551 return null;
552 if (lineStatus == LINE_STATUS_NOLINE) {
553 yLine -= currentLeading;
554 continue;
555 }
556 if (x1[0] >= x2[1] || x2[0] >= x1[1])
557 continue;
558 return new float[]{x1[0], x1[1], x2[0], x2[1]};
559 }
560 }
561
562 /**
563 * Sets the columns bounds. Each column bound is described by a
564 * <CODE>float[]</CODE> with the line points [x1,y1,x2,y2,...].
565 * The array must have at least 4 elements.
566 * @param leftLine the left column bound
567 * @param rightLine the right column bound
568 */
569 public void setColumns(float leftLine[], float rightLine[]) {
570 maxY = -10e20f;
571 minY = 10e20f;
572 rightWall = convertColumn(rightLine);
573 leftWall = convertColumn(leftLine);
574 rectangularWidth = -1;
575 rectangularMode = false;
576 }
577
578 /**
579 * Simplified method for rectangular columns.
580 * @param phrase a <CODE>Phrase</CODE>
581 * @param llx the lower left x corner
582 * @param lly the lower left y corner
583 * @param urx the upper right x corner
584 * @param ury the upper right y corner
585 * @param leading the leading
586 * @param alignment the column alignment
587 */
588 public void setSimpleColumn(Phrase phrase, float llx, float lly, float urx, float ury, float leading, int alignment) {
589 addText(phrase);
590 setSimpleColumn(llx, lly, urx, ury, leading, alignment);
591 }
592
593 /**
594 * Simplified method for rectangular columns.
595 * @param llx the lower left x corner
596 * @param lly the lower left y corner
597 * @param urx the upper right x corner
598 * @param ury the upper right y corner
599 * @param leading the leading
600 * @param alignment the column alignment
601 */
602 public void setSimpleColumn(float llx, float lly, float urx, float ury, float leading, int alignment) {
603 setLeading(leading);
604 this.alignment = alignment;
605 setSimpleColumn(llx, lly, urx, ury);
606 }
607
608 /**
609 * Simplified method for rectangular columns.
610 * @param llx
611 * @param lly
612 * @param urx
613 * @param ury
614 */
615 public void setSimpleColumn(float llx, float lly, float urx, float ury) {
616 leftX = Math.min(llx, urx);
617 maxY = Math.max(lly, ury);
618 minY = Math.min(lly, ury);
619 rightX = Math.max(llx, urx);
620 yLine = maxY;
621 rectangularWidth = rightX - leftX;
622 if (rectangularWidth < 0)
623 rectangularWidth = 0;
624 rectangularMode = true;
625 }
626 /**
627 * Sets the leading to fixed
628 * @param leading the leading
629 */
630 public void setLeading(float leading) {
631 fixedLeading = leading;
632 multipliedLeading = 0;
633 }
634
635 /**
636 * Sets the leading fixed and variable. The resultant leading will be
637 * fixedLeading+multipliedLeading*maxFontSize where maxFontSize is the
638 * size of the biggest font in the line.
639 * @param fixedLeading the fixed leading
640 * @param multipliedLeading the variable leading
641 */
642 public void setLeading(float fixedLeading, float multipliedLeading) {
643 this.fixedLeading = fixedLeading;
644 this.multipliedLeading = multipliedLeading;
645 }
646
647 /**
648 * Gets the fixed leading
649 * @return the leading
650 */
651 public float getLeading() {
652 return fixedLeading;
653 }
654
655 /**
656 * Gets the variable leading
657 * @return the leading
658 */
659 public float getMultipliedLeading() {
660 return multipliedLeading;
661 }
662
663 /**
664 * Sets the yLine. The line will be written to yLine-leading.
665 * @param yLine the yLine
666 */
667 public void setYLine(float yLine) {
668 this.yLine = yLine;
669 }
670
671 /**
672 * Gets the yLine.
673 * @return the yLine
674 */
675 public float getYLine() {
676 return yLine;
677 }
678
679 /**
680 * Sets the alignment.
681 * @param alignment the alignment
682 */
683 public void setAlignment(int alignment) {
684 this.alignment = alignment;
685 }
686
687 /**
688 * Gets the alignment.
689 * @return the alignment
690 */
691 public int getAlignment() {
692 return alignment;
693 }
694
695 /**
696 * Sets the first paragraph line indent.
697 * @param indent the indent
698 */
699 public void setIndent(float indent) {
700 this.indent = indent;
701 lastWasNewline = true;
702 }
703
704 /**
705 * Gets the first paragraph line indent.
706 * @return the indent
707 */
708 public float getIndent() {
709 return indent;
710 }
711
712 /**
713 * Sets the following paragraph lines indent.
714 * @param indent the indent
715 */
716 public void setFollowingIndent(float indent) {
717 this.followingIndent = indent;
718 lastWasNewline = true;
719 }
720
721 /**
722 * Gets the following paragraph lines indent.
723 * @return the indent
724 */
725 public float getFollowingIndent() {
726 return followingIndent;
727 }
728
729 /**
730 * Sets the right paragraph lines indent.
731 * @param indent the indent
732 */
733 public void setRightIndent(float indent) {
734 this.rightIndent = indent;
735 lastWasNewline = true;
736 }
737
738 /**
739 * Gets the right paragraph lines indent.
740 * @return the indent
741 */
742 public float getRightIndent() {
743 return rightIndent;
744 }
745
746 /**
747 * Outputs the lines to the document. It is equivalent to <CODE>go(false)</CODE>.
748 * @return returns the result of the operation. It can be <CODE>NO_MORE_TEXT</CODE>
749 * and/or <CODE>NO_MORE_COLUMN</CODE>
750 * @throws DocumentException on error
751 */
752 public int go() throws DocumentException {
753 return go(false);
754 }
755
756 /**
757 * Outputs the lines to the document. The output can be simulated.
758 * @param simulate <CODE>true</CODE> to simulate the writing to the document
759 * @return returns the result of the operation. It can be <CODE>NO_MORE_TEXT</CODE>
760 * and/or <CODE>NO_MORE_COLUMN</CODE>
761 * @throws DocumentException on error
762 */
763 public int go(boolean simulate) throws DocumentException {
764 if (composite)
765 return goComposite(simulate);
766 addWaitingPhrase();
767 if (bidiLine == null)
768 return NO_MORE_TEXT;
769 descender = 0;
770 linesWritten = 0;
771 boolean dirty = false;
772 float ratio = spaceCharRatio;
773 Object currentValues[] = new Object[2];
774 PdfFont currentFont = null;
775 Float lastBaseFactor = new Float(0);
776 currentValues[1] = lastBaseFactor;
777 PdfDocument pdf = null;
778 PdfContentByte graphics = null;
779 PdfContentByte text = null;
780 firstLineY = Float.NaN;
781 int localRunDirection = PdfWriter.RUN_DIRECTION_NO_BIDI;
782 if (runDirection != PdfWriter.RUN_DIRECTION_DEFAULT)
783 localRunDirection = runDirection;
784 if (canvas != null) {
785 graphics = canvas;
786 pdf = canvas.getPdfDocument();
787 text = canvas.getDuplicate();
788 }
789 else if (!simulate)
790 throw new NullPointerException("ColumnText.go with simulate==false and text==null.");
791 if (!simulate) {
792 if (ratio == GLOBAL_SPACE_CHAR_RATIO)
793 ratio = text.getPdfWriter().getSpaceCharRatio();
794 else if (ratio < 0.001f)
795 ratio = 0.001f;
796 }
797 float firstIndent = 0;
798
799 int status = 0;
800 if (rectangularMode) {
801 for (;;) {
802 firstIndent = (lastWasNewline ? indent : followingIndent);
803 if (rectangularWidth <= firstIndent + rightIndent) {
804 status = NO_MORE_COLUMN;
805 if (bidiLine.isEmpty())
806 status |= NO_MORE_TEXT;
807 break;
808 }
809 if (bidiLine.isEmpty()) {
810 status = NO_MORE_TEXT;
811 break;
812 }
813 PdfLine line = bidiLine.processLine(leftX, rectangularWidth - firstIndent - rightIndent, alignment, localRunDirection, arabicOptions);
814 if (line == null) {
815 status = NO_MORE_TEXT;
816 break;
817 }
818 float maxSize = line.getMaxSizeSimple();
819 if (isUseAscender() && Float.isNaN(firstLineY)) {
820 currentLeading = line.getAscender();
821 }
822 else {
823 currentLeading = fixedLeading + maxSize * multipliedLeading;
824 }
825 if (yLine > maxY || yLine - currentLeading < minY ) {
826 status = NO_MORE_COLUMN;
827 bidiLine.restore();
828 break;
829 }
830 yLine -= currentLeading;
831 if (!simulate && !dirty) {
832 text.beginText();
833 dirty = true;
834 }
835 if (Float.isNaN(firstLineY)) {
836 firstLineY = yLine;
837 }
838 updateFilledWidth(rectangularWidth - line.widthLeft());
839 if (!simulate) {
840 currentValues[0] = currentFont;
841 text.setTextMatrix(leftX + (line.isRTL() ? rightIndent : firstIndent) + line.indentLeft(), yLine);
842 pdf.writeLineToContent(line, text, graphics, currentValues, ratio);
843 currentFont = (PdfFont)currentValues[0];
844 }
845 lastWasNewline = line.isNewlineSplit();
846 yLine -= line.isNewlineSplit() ? extraParagraphSpace : 0;
847 ++linesWritten;
848 descender = line.getDescender();
849 }
850 }
851 else {
852 currentLeading = fixedLeading;
853 for (;;) {
854 firstIndent = (lastWasNewline ? indent : followingIndent);
855 float yTemp = yLine;
856 float xx[] = findLimitsTwoLines();
857 if (xx == null) {
858 status = NO_MORE_COLUMN;
859 if (bidiLine.isEmpty())
860 status |= NO_MORE_TEXT;
861 yLine = yTemp;
862 break;
863 }
864 if (bidiLine.isEmpty()) {
865 status = NO_MORE_TEXT;
866 yLine = yTemp;
867 break;
868 }
869 float x1 = Math.max(xx[0], xx[2]);
870 float x2 = Math.min(xx[1], xx[3]);
871 if (x2 - x1 <= firstIndent + rightIndent)
872 continue;
873 if (!simulate && !dirty) {
874 text.beginText();
875 dirty = true;
876 }
877 PdfLine line = bidiLine.processLine(x1, x2 - x1 - firstIndent - rightIndent, alignment, localRunDirection, arabicOptions);
878 if (line == null) {
879 status = NO_MORE_TEXT;
880 yLine = yTemp;
881 break;
882 }
883 if (!simulate) {
884 currentValues[0] = currentFont;
885 text.setTextMatrix(x1 + (line.isRTL() ? rightIndent : firstIndent) + line.indentLeft(), yLine);
886 pdf.writeLineToContent(line, text, graphics, currentValues, ratio);
887 currentFont = (PdfFont)currentValues[0];
888 }
889 lastWasNewline = line.isNewlineSplit();
890 yLine -= line.isNewlineSplit() ? extraParagraphSpace : 0;
891 ++linesWritten;
892 descender = line.getDescender();
893 }
894 }
895 if (dirty) {
896 text.endText();
897 canvas.add(text);
898 }
899 return status;
900 }
901
902 /**
903 * Sets the extra space between paragraphs.
904 * @return the extra space between paragraphs
905 */
906 public float getExtraParagraphSpace() {
907 return extraParagraphSpace;
908 }
909
910 /**
911 * Sets the extra space between paragraphs.
912 * @param extraParagraphSpace the extra space between paragraphs
913 */
914 public void setExtraParagraphSpace(float extraParagraphSpace) {
915 this.extraParagraphSpace = extraParagraphSpace;
916 }
917
918 /**
919 * Clears the chunk array. A call to <CODE>go()</CODE> will always return
920 * NO_MORE_TEXT.
921 */
922 public void clearChunks() {
923 if (bidiLine != null)
924 bidiLine.clearChunks();
925 }
926
927 /** Gets the space/character extra spacing ratio for
928 * fully justified text.
929 * @return the space/character extra spacing ratio
930 */
931 public float getSpaceCharRatio() {
932 return spaceCharRatio;
933 }
934
935 /** Sets the ratio between the extra word spacing and the extra character spacing
936 * when the text is fully justified.
937 * Extra word spacing will grow <CODE>spaceCharRatio</CODE> times more than extra character spacing.
938 * If the ratio is <CODE>PdfWriter.NO_SPACE_CHAR_RATIO</CODE> then the extra character spacing
939 * will be zero.
940 * @param spaceCharRatio the ratio between the extra word spacing and the extra character spacing
941 */
942 public void setSpaceCharRatio(float spaceCharRatio) {
943 this.spaceCharRatio = spaceCharRatio;
944 }
945
946 /** Sets the run direction.
947 * @param runDirection the run direction
948 */
949 public void setRunDirection(int runDirection) {
950 if (runDirection < PdfWriter.RUN_DIRECTION_DEFAULT || runDirection > PdfWriter.RUN_DIRECTION_RTL)
951 throw new RuntimeException("Invalid run direction: " + runDirection);
952 this.runDirection = runDirection;
953 }
954
955 /** Gets the run direction.
956 * @return the run direction
957 */
958 public int getRunDirection() {
959 return runDirection;
960 }
961
962 /** Gets the number of lines written.
963 * @return the number of lines written
964 */
965 public int getLinesWritten() {
966 return this.linesWritten;
967 }
968
969 /** Gets the arabic shaping options.
970 * @return the arabic shaping options
971 */
972 public int getArabicOptions() {
973 return this.arabicOptions;
974 }
975
976 /** Sets the arabic shaping options. The option can be AR_NOVOWEL,
977 * AR_COMPOSEDTASHKEEL and AR_LIG.
978 * @param arabicOptions the arabic shaping options
979 */
980 public void setArabicOptions(int arabicOptions) {
981 this.arabicOptions = arabicOptions;
982 }
983
984 /** Gets the biggest descender value of the last line written.
985 * @return the biggest descender value of the last line written
986 */
987 public float getDescender() {
988 return descender;
989 }
990
991 /** Gets the width that the line will occupy after writing.
992 * Only the width of the first line is returned.
993 * @param phrase the <CODE>Phrase</CODE> containing the line
994 * @param runDirection the run direction
995 * @param arabicOptions the options for the arabic shaping
996 * @return the width of the line
997 */
998 public static float getWidth(Phrase phrase, int runDirection, int arabicOptions) {
999 ColumnText ct = new ColumnText(null);
1000 ct.addText(phrase);
1001 ct.addWaitingPhrase();
1002 PdfLine line = ct.bidiLine.processLine(0, 20000, Element.ALIGN_LEFT, runDirection, arabicOptions);
1003 if (line == null)
1004 return 0;
1005 else
1006 return 20000 - line.widthLeft();
1007 }
1008
1009 /** Gets the width that the line will occupy after writing.
1010 * Only the width of the first line is returned.
1011 * @param phrase the <CODE>Phrase</CODE> containing the line
1012 * @return the width of the line
1013 */
1014 public static float getWidth(Phrase phrase) {
1015 return getWidth(phrase, PdfWriter.RUN_DIRECTION_NO_BIDI, 0);
1016 }
1017
1018 /** Shows a line of text. Only the first line is written.
1019 * @param canvas where the text is to be written to
1020 * @param alignment the alignment. It is not influenced by the run direction
1021 * @param phrase the <CODE>Phrase</CODE> with the text
1022 * @param x the x reference position
1023 * @param y the y reference position
1024 * @param rotation the rotation to be applied in degrees counterclockwise
1025 * @param runDirection the run direction
1026 * @param arabicOptions the options for the arabic shaping
1027 */
1028 public static void showTextAligned(PdfContentByte canvas, int alignment, Phrase phrase, float x, float y, float rotation, int runDirection, int arabicOptions) {
1029 if (alignment != Element.ALIGN_LEFT && alignment != Element.ALIGN_CENTER
1030 && alignment != Element.ALIGN_RIGHT)
1031 alignment = Element.ALIGN_LEFT;
1032 canvas.saveState();
1033 ColumnText ct = new ColumnText(canvas);
1034 if (rotation == 0) {
1035 if (alignment == Element.ALIGN_LEFT)
1036 ct.setSimpleColumn(phrase, x, y - 1, 20000 + x, y + 2, 2, alignment);
1037 else if (alignment == Element.ALIGN_RIGHT)
1038 ct.setSimpleColumn(phrase, x-20000, y-1, x, y+2, 2, alignment);
1039 else
1040 ct.setSimpleColumn(phrase, x-20000, y-1, x+20000, y+2, 2, alignment);
1041 }
1042 else {
1043 double alpha = rotation * Math.PI / 180.0;
1044 float cos = (float)Math.cos(alpha);
1045 float sin = (float)Math.sin(alpha);
1046 canvas.concatCTM(cos, sin, -sin, cos, x, y);
1047 if (alignment == Element.ALIGN_LEFT)
1048 ct.setSimpleColumn(phrase, 0, -1, 20000, 2, 2, alignment);
1049 else if (alignment == Element.ALIGN_RIGHT)
1050 ct.setSimpleColumn(phrase, -20000, -1, 0, 2, 2, alignment);
1051 else
1052 ct.setSimpleColumn(phrase, -20000, -1, 20000, 2, 2, alignment);
1053 }
1054 if (runDirection == PdfWriter.RUN_DIRECTION_RTL) {
1055 if (alignment == Element.ALIGN_LEFT)
1056 alignment = Element.ALIGN_RIGHT;
1057 else if (alignment == Element.ALIGN_RIGHT)
1058 alignment = Element.ALIGN_LEFT;
1059 }
1060 ct.setAlignment(alignment);
1061 ct.setArabicOptions(arabicOptions);
1062 ct.setRunDirection(runDirection);
1063 try {
1064 ct.go();
1065 }
1066 catch (DocumentException e) {
1067 throw new ExceptionConverter(e);
1068 }
1069 canvas.restoreState();
1070 }
1071
1072 /** Shows a line of text. Only the first line is written.
1073 * @param canvas where the text is to be written to
1074 * @param alignment the alignment
1075 * @param phrase the <CODE>Phrase</CODE> with the text
1076 * @param x the x reference position
1077 * @param y the y reference position
1078 * @param rotation the rotation to be applied in degrees counterclockwise
1079 */
1080 public static void showTextAligned(PdfContentByte canvas, int alignment, Phrase phrase, float x, float y, float rotation) {
1081 showTextAligned(canvas, alignment, phrase, x, y, rotation, PdfWriter.RUN_DIRECTION_NO_BIDI, 0);
1082 }
1083
1084 protected int goComposite(boolean simulate) throws DocumentException {
1085 if (!rectangularMode)
1086 throw new DocumentException("Irregular columns are not supported in composite mode.");
1087 linesWritten = 0;
1088 descender = 0;
1089 boolean firstPass = adjustFirstLine;
1090 main_loop:
1091 while (true) {
1092 if (compositeElements.isEmpty())
1093 return NO_MORE_TEXT;
1094 Element element = (Element)compositeElements.getFirst();
1095 if (element.type() == Element.PARAGRAPH) {
1096 Paragraph para = (Paragraph)element;
1097 int status = 0;
1098 for (int keep = 0; keep < 2; ++keep) {
1099 float lastY = yLine;
1100 boolean createHere = false;
1101 if (compositeColumn == null) {
1102 compositeColumn = new ColumnText(canvas);
1103 compositeColumn.setUseAscender(firstPass ? useAscender : false);
1104 compositeColumn.setAlignment(para.getAlignment());
1105 compositeColumn.setIndent(para.getIndentationLeft() + para.getFirstLineIndent());
1106 compositeColumn.setExtraParagraphSpace(para.getExtraParagraphSpace());
1107 compositeColumn.setFollowingIndent(para.getIndentationLeft());
1108 compositeColumn.setRightIndent(para.getIndentationRight());
1109 compositeColumn.setLeading(para.getLeading(), para.getMultipliedLeading());
1110 compositeColumn.setRunDirection(runDirection);
1111 compositeColumn.setArabicOptions(arabicOptions);
1112 compositeColumn.setSpaceCharRatio(spaceCharRatio);
1113 compositeColumn.addText(para);
1114 if (!firstPass) {
1115 yLine -= para.spacingBefore();
1116 }
1117 createHere = true;
1118 }
1119 compositeColumn.leftX = leftX;
1120 compositeColumn.rightX = rightX;
1121 compositeColumn.yLine = yLine;
1122 compositeColumn.rectangularWidth = rectangularWidth;
1123 compositeColumn.rectangularMode = rectangularMode;
1124 compositeColumn.minY = minY;
1125 compositeColumn.maxY = maxY;
1126 boolean keepCandidate = (para.getKeepTogether() && createHere && !firstPass);
1127 status = compositeColumn.go(simulate || (keepCandidate && keep == 0));
1128 updateFilledWidth(compositeColumn.filledWidth);
1129 if ((status & NO_MORE_TEXT) == 0 && keepCandidate) {
1130 compositeColumn = null;
1131 yLine = lastY;
1132 return NO_MORE_COLUMN;
1133 }
1134 if (simulate || !keepCandidate)
1135 break;
1136 if (keep == 0) {
1137 compositeColumn = null;
1138 yLine = lastY;
1139 }
1140 }
1141 firstPass = false;
1142 yLine = compositeColumn.yLine;
1143 linesWritten += compositeColumn.linesWritten;
1144 descender = compositeColumn.descender;
1145 if ((status & NO_MORE_TEXT) != 0) {
1146 compositeColumn = null;
1147 compositeElements.removeFirst();
1148 yLine -= para.spacingAfter();
1149 }
1150 if ((status & NO_MORE_COLUMN) != 0) {
1151 return NO_MORE_COLUMN;
1152 }
1153 }
1154 else if (element.type() == Element.LIST) {
1155 com.lowagie.text.List list = (com.lowagie.text.List)element;
1156 ArrayList items = list.getItems();
1157 ListItem item = null;
1158 float listIndentation = list.getIndentationLeft();
1159 int count = 0;
1160 Stack stack = new Stack();
1161 for (int k = 0; k < items.size(); ++k) {
1162 Object obj = items.get(k);
1163 if (obj instanceof ListItem) {
1164 if (count == listIdx) {
1165 item = (ListItem)obj;
1166 break;
1167 }
1168 else ++count;
1169 }
1170 else if (obj instanceof com.lowagie.text.List) {
1171 stack.push(new Object[]{list, new Integer(k), new Float(listIndentation)});
1172 list = (com.lowagie.text.List)obj;
1173 items = list.getItems();
1174 listIndentation += list.getIndentationLeft();
1175 k = -1;
1176 continue;
1177 }
1178 if (k == items.size() - 1) {
1179 if (!stack.isEmpty()) {
1180 Object objs[] = (Object[])stack.pop();
1181 list = (com.lowagie.text.List)objs[0];
1182 items = list.getItems();
1183 k = ((Integer)objs[1]).intValue();
1184 listIndentation = ((Float)objs[2]).floatValue();
1185 }
1186 }
1187 }
1188 int status = 0;
1189 for (int keep = 0; keep < 2; ++keep) {
1190 float lastY = yLine;
1191 boolean createHere = false;
1192 if (compositeColumn == null) {
1193 if (item == null) {
1194 listIdx = 0;
1195 compositeElements.removeFirst();
1196 continue main_loop;
1197 }
1198 compositeColumn = new ColumnText(canvas);
1199 compositeColumn.setUseAscender(firstPass ? useAscender : false);
1200 compositeColumn.setAlignment(item.getAlignment());
1201 compositeColumn.setIndent(item.getIndentationLeft() + listIndentation + item.getFirstLineIndent());
1202 compositeColumn.setExtraParagraphSpace(item.getExtraParagraphSpace());
1203 compositeColumn.setFollowingIndent(compositeColumn.getIndent());
1204 compositeColumn.setRightIndent(item.getIndentationRight() + list.getIndentationRight());
1205 compositeColumn.setLeading(item.getLeading(), item.getMultipliedLeading());
1206 compositeColumn.setRunDirection(runDirection);
1207 compositeColumn.setArabicOptions(arabicOptions);
1208 compositeColumn.setSpaceCharRatio(spaceCharRatio);
1209 compositeColumn.addText(item);
1210 if (!firstPass) {
1211 yLine -= item.spacingBefore();
1212 }
1213 createHere = true;
1214 }
1215 compositeColumn.leftX = leftX;
1216 compositeColumn.rightX = rightX;
1217 compositeColumn.yLine = yLine;
1218 compositeColumn.rectangularWidth = rectangularWidth;
1219 compositeColumn.rectangularMode = rectangularMode;
1220 compositeColumn.minY = minY;
1221 compositeColumn.maxY = maxY;
1222 boolean keepCandidate = (item.getKeepTogether() && createHere && !firstPass);
1223 status = compositeColumn.go(simulate || (keepCandidate && keep == 0));
1224 updateFilledWidth(compositeColumn.filledWidth);
1225 if ((status & NO_MORE_TEXT) == 0 && keepCandidate) {
1226 compositeColumn = null;
1227 yLine = lastY;
1228 return NO_MORE_COLUMN;
1229 }
1230 if (simulate || !keepCandidate)
1231 break;
1232 if (keep == 0) {
1233 compositeColumn = null;
1234 yLine = lastY;
1235 }
1236 }
1237 firstPass = false;
1238 yLine = compositeColumn.yLine;
1239 linesWritten += compositeColumn.linesWritten;
1240 descender = compositeColumn.descender;
1241 if (!Float.isNaN(compositeColumn.firstLineY) && !compositeColumn.firstLineYDone) {
1242 if (!simulate)
1243 showTextAligned(canvas, Element.ALIGN_LEFT, new Phrase(item.getListSymbol()), compositeColumn.leftX + listIndentation, compositeColumn.firstLineY, 0);
1244 compositeColumn.firstLineYDone = true;
1245 }
1246 if ((status & NO_MORE_TEXT) != 0) {
1247 compositeColumn = null;
1248 ++listIdx;
1249 yLine -= item.spacingAfter();
1250 }
1251 if ((status & NO_MORE_COLUMN) != 0) {
1252 return NO_MORE_COLUMN;
1253 }
1254 }
1255 else if (element.type() == Element.PTABLE) {
1256 // don't write anything in the current column if there's no more space available
1257 if (yLine < minY || yLine > maxY)
1258 return NO_MORE_COLUMN;
1259
1260 // get the PdfPTable element
1261 PdfPTable table = (PdfPTable)element;
1262
1263 // we ignore tables without a body
1264 if (table.size() <= table.getHeaderRows()) {
1265 compositeElements.removeFirst();
1266 continue;
1267 }
1268
1269 // offsets
1270 float yTemp = yLine;
1271 if (!firstPass && listIdx == 0) {
1272 yTemp -= table.spacingBefore();
1273 }
1274 float yLineWrite = yTemp;
1275
1276 // don't write anything in the current column if there's no more space available
1277 if (yTemp < minY || yTemp > maxY) {
1278 return NO_MORE_COLUMN;
1279 }
1280
1281 // coordinates
1282 currentLeading = 0;
1283 float x1 = leftX;
1284 float tableWidth;
1285 if (table.isLockedWidth()) {
1286 tableWidth = table.getTotalWidth();
1287 updateFilledWidth(tableWidth);
1288 }
1289 else {
1290 tableWidth = rectangularWidth * table.getWidthPercentage() / 100f;
1291 table.setTotalWidth(tableWidth);
1292 }
1293
1294 // how many header rows are real header rows; how many are footer rows?
1295 int headerRows = table.getHeaderRows();
1296 int footerRows = table.getFooterRows();
1297 if (footerRows > headerRows)
1298 footerRows = headerRows;
1299 int realHeaderRows = headerRows - footerRows;
1300 float headerHeight = table.getHeaderHeight();
1301 float footerHeight = table.getFooterHeight();
1302
1303 // make sure the header and footer fit on the page
1304 boolean skipHeader = (!firstPass && table.isSkipFirstHeader() && listIdx <= headerRows);
1305 if (!skipHeader) {
1306 yTemp -= headerHeight;
1307 if (yTemp < minY || yTemp > maxY) {
1308 if (firstPass) {
1309 compositeElements.removeFirst();
1310 continue;
1311 }
1312 return NO_MORE_COLUMN;
1313 }
1314 }
1315
1316 // how many real rows (not header or footer rows) fit on a page?
1317 int k;
1318 if (listIdx < headerRows) {
1319 listIdx = headerRows;
1320 }
1321 if (!table.isComplete()) {
1322 yTemp -= footerHeight;
1323 }
1324 for (k = listIdx; k < table.size(); ++k) {
1325 float rowHeight = table.getRowHeight(k);
1326 if (yTemp - rowHeight < minY)
1327 break;
1328 yTemp -= rowHeight;
1329 }
1330 if (!table.isComplete()) {
1331 yTemp += footerHeight;
1332 }
1333 // either k is the first row that doesn't fit on the page (break);
1334 if (k < table.size()) {
1335 if (table.isSplitRows() && (!table.isSplitLate() || (k == listIdx && firstPass))) {
1336 if (!splittedRow) {
1337 splittedRow = true;
1338 table = new PdfPTable(table);
1339 compositeElements.set(0, table);
1340 ArrayList rows = table.getRows();
1341 for (int i = headerRows; i < listIdx; ++i)
1342 rows.set(i, null);
1343 }
1344 float h = yTemp - minY;
1345 PdfPRow newRow = table.getRow(k).splitRow(h);
1346 if (newRow == null) {
1347 if (k == listIdx) {
1348 return NO_MORE_COLUMN;
1349 }
1350 }
1351 else {
1352 yTemp = minY;
1353 table.getRows().add(++k, newRow);
1354 }
1355 }
1356 else if (!table.isSplitRows() && k == listIdx && firstPass) {
1357 compositeElements.removeFirst();
1358 splittedRow = false;
1359 continue;
1360 }
1361 else if (k == listIdx && !firstPass && (!table.isSplitRows() || table.isSplitLate()) && (table.getFooterRows() == 0 || table.isComplete())) {
1362 return NO_MORE_COLUMN;
1363 }
1364 }
1365 // or k is the number of rows in the table (for loop was done).
1366 firstPass = false;
1367 // we draw the table (for real now)
1368 if (!simulate) {
1369 // set the alignment
1370 switch (table.getHorizontalAlignment()) {
1371 case Element.ALIGN_LEFT:
1372 break;
1373 case Element.ALIGN_RIGHT:
1374 x1 += rectangularWidth - tableWidth;
1375 break;
1376 default:
1377 x1 += (rectangularWidth - tableWidth) / 2f;
1378 }
1379 // copy the rows that fit on the page in a new table nt
1380 PdfPTable nt = PdfPTable.shallowCopy(table);
1381 ArrayList rows = table.getRows();
1382 ArrayList sub = nt.getRows();
1383
1384 // first we add the real header rows (if necessary)
1385 if (!skipHeader) {
1386 for (int j = 0; j < realHeaderRows; ++j) {
1387 PdfPRow headerRow = (PdfPRow)rows.get(j);
1388 sub.add(headerRow);
1389 }
1390 }
1391 else {
1392 nt.setHeaderRows(footerRows);
1393 }
1394 // then we add the real content
1395 for (int j = listIdx; j < k; ++j) {
1396 sub.add(rows.get(j));
1397 }
1398 // if k < table.size(), we must indicate that the new table is complete;
1399 // otherwise no footers will be added (because iText thinks the table continues on the same page)
1400 if (k < table.size()) {
1401 nt.setComplete(true);
1402 }
1403 // we add the footer rows if necessary (not for incomplete tables)
1404 for (int j = 0; j < footerRows && nt.isComplete(); ++j) {
1405 sub.add(rows.get(j + realHeaderRows));
1406 }
1407
1408 // we need a correction if the last row needs to be extended
1409 float rowHeight = 0;
1410 if (table.isExtendLastRow()) {
1411 PdfPRow last = (PdfPRow)sub.get(sub.size() - 1 - footerRows);
1412 rowHeight = last.getMaxHeights();
1413 last.setMaxHeights(yTemp - minY + rowHeight);
1414 yTemp = minY;
1415 }
1416
1417 // now we render the rows of the new table
1418 if (canvases != null)
1419 nt.writeSelectedRows(0, -1, x1, yLineWrite, canvases);
1420 else
1421 nt.writeSelectedRows(0, -1, x1, yLineWrite, canvas);
1422 if (table.isExtendLastRow()) {
1423 PdfPRow last = (PdfPRow)sub.get(sub.size() - 1 - footerRows);
1424 last.setMaxHeights(rowHeight);
1425 }
1426 }
1427 else if (table.isExtendLastRow() && minY > PdfPRow.BOTTOM_LIMIT) {
1428 yTemp = minY;
1429 }
1430 yLine = yTemp;
1431 if (!(skipHeader || table.isComplete())) {
1432 yLine += footerHeight;
1433 }
1434 if (k >= table.size()) {
1435 yLine -= table.spacingAfter();
1436 compositeElements.removeFirst();
1437 splittedRow = false;
1438 listIdx = 0;
1439 }
1440 else {
1441 if (splittedRow) {
1442 ArrayList rows = table.getRows();
1443 for (int i = listIdx; i < k; ++i)
1444 rows.set(i, null);
1445 }
1446 listIdx = k;
1447 return NO_MORE_COLUMN;
1448 }
1449 }
1450 else if (element.type() == Element.YMARK) {
1451 if (!simulate) {
1452 DrawInterface zh = (DrawInterface)element;
1453 zh.draw(canvas, leftX, minY, rightX, maxY, yLine);
1454 }
1455 compositeElements.removeFirst();
1456 }
1457 else
1458 compositeElements.removeFirst();
1459 }
1460 }
1461
1462 /**
1463 * Gets the canvas.
1464 * @return a PdfContentByte.
1465 */
1466 public PdfContentByte getCanvas() {
1467 return canvas;
1468 }
1469
1470 /**
1471 * Sets the canvas.
1472 * @param canvas
1473 */
1474 public void setCanvas(PdfContentByte canvas) {
1475 this.canvas = canvas;
1476 this.canvases = null;
1477 if (compositeColumn != null)
1478 compositeColumn.setCanvas(canvas);
1479 }
1480
1481 /**
1482 * Sets the canvases.
1483 * @param canvases
1484 */
1485 public void setCanvases(PdfContentByte[] canvases) {
1486 this.canvases = canvases;
1487 this.canvas = canvases[PdfPTable.TEXTCANVAS];
1488 if (compositeColumn != null)
1489 compositeColumn.setCanvases(canvases);
1490 }
1491
1492 /**
1493 * Gets the canvases.
1494 * @return an array of PdfContentByte
1495 */
1496 public PdfContentByte[] getCanvases() {
1497 return canvases;
1498 }
1499
1500 /**
1501 * Checks if the element has a height of 0.
1502 * @return true or false
1503 * @since 2.1.2
1504 */
1505 public boolean zeroHeightElement() {
1506 return composite && !compositeElements.isEmpty() && ((Element)compositeElements.getFirst()).type() == Element.YMARK;
1507 }
1508
1509 /**
1510 * Checks if UseAscender is enabled/disabled.
1511 * @return true is the adjustment of the first line height is based on max ascender.
1512 */
1513 public boolean isUseAscender() {
1514 return useAscender;
1515 }
1516
1517 /**
1518 * Enables/Disables adjustment of first line height based on max ascender.
1519 * @param use enable adjustment if true
1520 */
1521 public void setUseAscender(boolean use) {
1522 useAscender = use;
1523 }
1524
1525 /**
1526 * Checks the status variable and looks if there's still some text.
1527 */
1528 public static boolean hasMoreText(int status) {
1529 return (status & ColumnText.NO_MORE_TEXT) == 0;
1530 }
1531
1532 /**
1533 * Holds value of property filledWidth.
1534 */
1535 private float filledWidth;
1536
1537 /**
1538 * Gets the real width used by the largest line.
1539 * @return the real width used by the largest line
1540 */
1541 public float getFilledWidth() {
1542
1543 return this.filledWidth;
1544 }
1545
1546 /**
1547 * Sets the real width used by the largest line. Only used to set it
1548 * to zero to start another measurement.
1549 * @param filledWidth the real width used by the largest line
1550 */
1551 public void setFilledWidth(float filledWidth) {
1552
1553 this.filledWidth = filledWidth;
1554 }
1555
1556 /**
1557 * Replaces the <CODE>filledWidth</CODE> if greater than the existing one.
1558 * @param w the new <CODE>filledWidth</CODE> if greater than the existing one
1559 */
1560 public void updateFilledWidth(float w) {
1561 if (w > filledWidth)
1562 filledWidth = w;
1563 }
1564
1565 private boolean adjustFirstLine = true;
1566
1567 /**
1568 * Gets the first line adjustment property.
1569 * @return the first line adjustment property.
1570 */
1571 public boolean isAdjustFirstLine() {
1572 return this.adjustFirstLine;
1573 }
1574
1575 /**
1576 * Sets the first line adjustment. Some objects have properties, like spacing before, that
1577 * behave differently if the object is the first to be written after go() or not. The first line adjustment is
1578 * <CODE>true</CODE> by default but can be changed if several objects are to be placed one
1579 * after the other in the same column calling go() several times.
1580 * @param adjustFirstLine <CODE>true</CODE> to adjust the first line, <CODE>false</CODE> otherwise
1581 */
1582 public void setAdjustFirstLine(boolean adjustFirstLine) {
1583 this.adjustFirstLine = adjustFirstLine;
1584 }
1585 }