1 /*
2  * $Id: ColumnText.java 3904 2009-04-24 10:09:01Z blowagie $
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     /** Holds value of property filledWidth. */
255     private float filledWidth;
256
257     private boolean adjustFirstLine = true;
258     
259     /**
260      * Creates a <CODE>ColumnText</CODE>.
261      * 
262      * @param canvas the place where the text will be written to. Can
263      * be a template.
264      */

265     public ColumnText(PdfContentByte canvas) {
266         this.canvas = canvas;
267     }
268     
269     /**
270      * Creates an independent duplicated of the instance <CODE>org</CODE>.
271      * 
272      * @param org the original <CODE>ColumnText</CODE>
273      * @return the duplicated
274      */
    
275     public static ColumnText duplicate(ColumnText org) {
276         ColumnText ct = new ColumnText(null);
277         ct.setACopy(org);
278         return ct;
279     }
280     
281     /**
282      * Makes this instance an independent copy of <CODE>org</CODE>.
283      * 
284      * @param org the original <CODE>ColumnText</CODE>
285      * @return itself
286      */
    
287     public ColumnText setACopy(ColumnText org) {
288         setSimpleVars(org);
289         if (org.bidiLine != null)
290             bidiLine = new BidiLine(org.bidiLine);
291         return this;
292     }
293     
294     protected void setSimpleVars(ColumnText org) {
295         maxY = org.maxY;
296         minY = org.minY;
297         alignment = org.alignment;
298         leftWall = null;
299         if (org.leftWall != null)
300             leftWall = new ArrayList(org.leftWall);
301         rightWall = null;
302         if (org.rightWall != null)
303             rightWall = new ArrayList(org.rightWall);
304         yLine = org.yLine;
305         currentLeading = org.currentLeading;
306         fixedLeading = org.fixedLeading;
307         multipliedLeading = org.multipliedLeading;
308         canvas = org.canvas;
309         canvases = org.canvases;
310         lineStatus = org.lineStatus;
311         indent = org.indent;
312         followingIndent = org.followingIndent;
313         rightIndent = org.rightIndent;
314         extraParagraphSpace = org.extraParagraphSpace;
315         rectangularWidth = org.rectangularWidth;
316         rectangularMode = org.rectangularMode;
317         spaceCharRatio = org.spaceCharRatio;
318         lastWasNewline = org.lastWasNewline;
319         linesWritten = org.linesWritten;
320         arabicOptions = org.arabicOptions;
321         runDirection = org.runDirection;
322         descender = org.descender;
323         composite = org.composite;
324         splittedRow = org.splittedRow;
325         if (org.composite) {
326             compositeElements = new LinkedList(org.compositeElements);
327             if (splittedRow) {
328                 PdfPTable table = (PdfPTable)compositeElements.getFirst();
329                 compositeElements.set(0, new PdfPTable(table));
330             }
331             if (org.compositeColumn != null)
332                 compositeColumn = duplicate(org.compositeColumn);
333         }
334         listIdx = org.listIdx;
335         firstLineY = org.firstLineY;
336         leftX = org.leftX;
337         rightX = org.rightX;
338         firstLineYDone = org.firstLineYDone;
339         waitPhrase = org.waitPhrase;
340         useAscender = org.useAscender;
341         filledWidth = org.filledWidth;
342         adjustFirstLine = org.adjustFirstLine;
343     }
344     
345     private void addWaitingPhrase() {
346         if (bidiLine == null && waitPhrase != null) {
347             bidiLine = new BidiLine();
348             for (Iterator j = waitPhrase.getChunks().iterator(); j.hasNext();) {
349                 bidiLine.addChunk(new PdfChunk((Chunk)j.next(), null));
350             }
351             waitPhrase = null;
352         }
353     }
354     
355     /**
356      * Adds a <CODE>Phrase</CODE> to the current text array.
357      * Will not have any effect if addElement() was called before.
358      * 
359      * @param phrase the text
360      */

361     public void addText(Phrase phrase) {
362         if (phrase == null || composite)
363             return;
364         addWaitingPhrase();
365         if (bidiLine == null) {
366             waitPhrase = phrase;
367             return;
368         }
369         for (Iterator j = phrase.getChunks().iterator(); j.hasNext();) {
370             bidiLine.addChunk(new PdfChunk((Chunk)j.next(), null));
371         }
372     }
373     
374     /**
375      * Replaces the current text array with this <CODE>Phrase</CODE>.
376      * Anything added previously with addElement() is lost.
377      * 
378      * @param phrase the text
379      */

380     public void setText(Phrase phrase) {
381         bidiLine = null;
382         composite = false;
383         compositeColumn = null;
384         compositeElements = null;
385         listIdx = 0;
386         splittedRow = false;
387         waitPhrase = phrase;
388     }
389     
390     /**
391      * Adds a <CODE>Chunk</CODE> to the current text array.
392      * Will not have any effect if addElement() was called before.
393      * 
394      * @param chunk the text
395      */

396     public void addText(Chunk chunk) {
397         if (chunk == null || composite)
398             return;
399         addText(new Phrase(chunk));
400     }
401     
402     /**
403      * Adds an element. Elements supported are <CODE>Paragraph</CODE>,
404      * <CODE>List</CODE>, <CODE>PdfPTable</CODE>, <CODE>Image</CODE> and
405      * <CODE>Graphic</CODE>.
406      * <p>
407      * It removes all the text placed with <CODE>addText()</CODE>.
408      * 
409      * @param element the <CODE>Element</CODE>
410      */
    
411     public void addElement(Element element) {
412         if (element == null)
413             return;
414         if (element instanceof Image) {
415             Image img = (Image)element;
416             PdfPTable t = new PdfPTable(1);
417             float w = img.getWidthPercentage();
418             if (w == 0) {
419                 t.setTotalWidth(img.getScaledWidth());
420                 t.setLockedWidth(true);
421             }
422             else
423                 t.setWidthPercentage(w);
424             t.setSpacingAfter(img.getSpacingAfter());
425             t.setSpacingBefore(img.getSpacingBefore());
426             switch (img.getAlignment()) {
427                 case Image.LEFT:
428                     t.setHorizontalAlignment(Element.ALIGN_LEFT);
429                     break;
430                 case Image.RIGHT:
431                     t.setHorizontalAlignment(Element.ALIGN_RIGHT);
432                     break;
433                 default:
434                     t.setHorizontalAlignment(Element.ALIGN_CENTER);
435                     break;
436             }
437             PdfPCell c = new PdfPCell(img, true);
438             c.setPadding(0);
439             c.setBorder(img.getBorder());
440             c.setBorderColor(img.getBorderColor());
441             c.setBorderWidth(img.getBorderWidth());
442             c.setBackgroundColor(img.getBackgroundColor());
443             t.addCell(c);
444             element = t;
445         }
446         if (element.type() == Element.CHUNK) {
447             element = new Paragraph((Chunk)element);
448         }
449         else if (element.type() == Element.PHRASE) {
450             element = new Paragraph((Phrase)element);
451         }
452         if (element instanceof SimpleTable) {
453             try {
454                 element = ((SimpleTable)element).createPdfPTable();
455             } catch (DocumentException e) {
456                 throw new IllegalArgumentException("Element not allowed.");
457             }
458         }
459         else if (element.type() != Element.PARAGRAPH && element.type() != Element.LIST && element.type() != Element.PTABLE && element.type() != Element.YMARK)
460             throw new IllegalArgumentException("Element not allowed.");
461         if (!composite) {
462             composite = true;
463             compositeElements = new LinkedList();
464             bidiLine = null;
465             waitPhrase = null;
466         }
467         compositeElements.add(element);
468     }
469     
470     /**
471      * Converts a sequence of lines representing one of the column bounds into
472      * an internal format.
473      * <p>
474      * Each array element will contain a <CODE>float[4]</CODE> representing
475      * the line x = ax + b.
476      * 
477      * @param cLine the column array
478      * @return the converted array
479      */

480     protected ArrayList convertColumn(float cLine[]) {
481         if (cLine.length < 4)
482             throw new RuntimeException("No valid column line found.");
483         ArrayList cc = new ArrayList();
484         for (int k = 0; k < cLine.length - 2; k += 2) {
485             float x1 = cLine[k];
486             float y1 = cLine[k + 1];
487             float x2 = cLine[k + 2];
488             float y2 = cLine[k + 3];
489             if (y1 == y2)
490                 continue;
491             // x = ay + b
492             float a = (x1 - x2) / (y1 - y2);
493             float b = x1 - a * y1;
494             float r[] = new float[4];
495             r[0] = Math.min(y1, y2);
496             r[1] = Math.max(y1, y2);
497             r[2] = a;
498             r[3] = b;
499             cc.add(r);
500             maxY = Math.max(maxY, r[1]);
501             minY = Math.min(minY, r[0]);
502         }
503         if (cc.isEmpty())
504             throw new RuntimeException("No valid column line found.");
505         return cc;
506     }
507     
508     /**
509      * Finds the intersection between the <CODE>yLine</CODE> and the column. It will
510      * set the <CODE>lineStatus</CODE> appropriately.
511      * 
512      * @param wall the column to intersect
513      * @return the x coordinate of the intersection
514      */

515     protected float findLimitsPoint(ArrayList wall) {
516         lineStatus = LINE_STATUS_OK;
517         if (yLine < minY || yLine > maxY) {
518             lineStatus = LINE_STATUS_OFFLIMITS;
519             return 0;
520         }
521         for (int k = 0; k < wall.size(); ++k) {
522             float r[] = (float[])wall.get(k);
523             if (yLine < r[0] || yLine > r[1])
524                 continue;
525             return r[2] * yLine + r[3];
526         }
527         lineStatus = LINE_STATUS_NOLINE;
528         return 0;
529     }
530     
531     /**
532      * Finds the intersection between the <CODE>yLine</CODE> and the two
533      * column bounds. It will set the <CODE>lineStatus</CODE> appropriately.
534      * 
535      * @return a <CODE>float[2]</CODE>with the x coordinates of the intersection
536      */

537     protected float[] findLimitsOneLine() {
538         float x1 = findLimitsPoint(leftWall);
539         if (lineStatus == LINE_STATUS_OFFLIMITS || lineStatus == LINE_STATUS_NOLINE)
540             return null;
541         float x2 = findLimitsPoint(rightWall);
542         if (lineStatus == LINE_STATUS_NOLINE)
543             return null;
544         return new float[]{x1, x2};
545     }
546     
547     /**
548      * Finds the intersection between the <CODE>yLine</CODE>,
549      * the <CODE>yLine-leading</CODE>and the two column bounds.
550      * It will set the <CODE>lineStatus</CODE> appropriately.
551      * 
552      * @return a <CODE>float[4]</CODE>with the x coordinates of the intersection
553      */

554     protected float[] findLimitsTwoLines() {
555         boolean repeat = false;
556         for (;;) {
557             if (repeat && currentLeading == 0)
558                 return null;
559             repeat = true;
560             float x1[] = findLimitsOneLine();
561             if (lineStatus == LINE_STATUS_OFFLIMITS)
562                 return null;
563             yLine -= currentLeading;
564             if (lineStatus == LINE_STATUS_NOLINE) {
565                 continue;
566             }
567             float x2[] = findLimitsOneLine();
568             if (lineStatus == LINE_STATUS_OFFLIMITS)
569                 return null;
570             if (lineStatus == LINE_STATUS_NOLINE) {
571                 yLine -= currentLeading;
572                 continue;
573             }
574             if (x1[0] >= x2[1] || x2[0] >= x1[1])
575                 continue;
576             return new float[]{x1[0], x1[1], x2[0], x2[1]};
577         }
578     }
579     
580     /**
581      * Sets the columns bounds. Each column bound is described by a
582      * <CODE>float[]</CODE> with the line points [x1,y1,x2,y2,...].
583      * The array must have at least 4 elements.
584      * 
585      * @param leftLine the left column bound
586      * @param rightLine the right column bound
587      */

588     public void setColumns(float leftLine[], float rightLine[]) {
589         maxY = -10e20f;
590         minY = 10e20f;
591         setYLine(Math.max(leftLine[1], leftLine[leftLine.length - 1]));
592         rightWall = convertColumn(rightLine);
593         leftWall = convertColumn(leftLine);
594         rectangularWidth = -1;
595         rectangularMode = false;
596     }
597     
598     /**
599      * Simplified method for rectangular columns.
600      * 
601      * @param phrase a <CODE>Phrase</CODE>
602      * @param llx the lower left x corner
603      * @param lly the lower left y corner
604      * @param urx the upper right x corner
605      * @param ury the upper right y corner
606      * @param leading the leading
607      * @param alignment the column alignment
608      */

609     public void setSimpleColumn(Phrase phrase, float llx, float lly, float urx, float ury, float leading, int alignment) {
610         addText(phrase);
611         setSimpleColumn(llx, lly, urx, ury, leading, alignment);
612     }
613     
614     /**
615      * Simplified method for rectangular columns.
616      * 
617      * @param llx the lower left x corner
618      * @param lly the lower left y corner
619      * @param urx the upper right x corner
620      * @param ury the upper right y corner
621      * @param leading the leading
622      * @param alignment the column alignment
623      */

624     public void setSimpleColumn(float llx, float lly, float urx, float ury, float leading, int alignment) {
625         setLeading(leading);
626         this.alignment = alignment;
627         setSimpleColumn(llx, lly, urx, ury);
628     }
629     
630     /**
631      * Simplified method for rectangular columns.
632      * 
633      * @param llx
634      * @param lly
635      * @param urx
636      * @param ury
637      */

638     public void setSimpleColumn(float llx, float lly, float urx, float ury) {
639         leftX = Math.min(llx, urx);
640         maxY = Math.max(lly, ury);
641         minY = Math.min(lly, ury);
642         rightX = Math.max(llx, urx);
643         yLine = maxY;
644         rectangularWidth = rightX - leftX;
645         if (rectangularWidth < 0)
646             rectangularWidth = 0;
647         rectangularMode = true;
648     }
649     
650     /**
651      * Sets the leading to fixed.
652      * 
653      * @param leading the leading
654      */

655     public void setLeading(float leading) {
656         fixedLeading = leading;
657         multipliedLeading = 0;
658     }
659     
660     /**
661      * Sets the leading fixed and variable. The resultant leading will be
662      * fixedLeading+multipliedLeading*maxFontSize where maxFontSize is the
663      * size of the biggest font in the line.
664      * 
665      * @param fixedLeading the fixed leading
666      * @param multipliedLeading the variable leading
667      */

668     public void setLeading(float fixedLeading, float multipliedLeading) {
669         this.fixedLeading = fixedLeading;
670         this.multipliedLeading = multipliedLeading;
671     }
672     
673     /**
674      * Gets the fixed leading.
675      * 
676      * @return the leading
677      */

678     public float getLeading() {
679         return fixedLeading;
680     }
681     
682     /**
683      * Gets the variable leading.
684      * 
685      * @return the leading
686      */

687     public float getMultipliedLeading() {
688         return multipliedLeading;
689     }
690     
691     /**
692      * Sets the yLine. The line will be written to yLine-leading.
693      * 
694      * @param yLine the yLine
695      */

696     public void setYLine(float yLine) {
697         this.yLine = yLine;
698     }
699     
700     /**
701      * Gets the yLine.
702      * 
703      * @return the yLine
704      */

705     public float getYLine() {
706         return yLine;
707     }
708     
709     /**
710      * Sets the alignment.
711      * 
712      * @param alignment the alignment
713      */

714     public void setAlignment(int alignment) {
715         this.alignment = alignment;
716     }
717     
718     /**
719      * Gets the alignment.
720      * 
721      * @return the alignment
722      */

723     public int getAlignment() {
724         return alignment;
725     }
726     
727     /**
728      * Sets the first paragraph line indent.
729      * 
730      * @param indent the indent
731      */

732     public void setIndent(float indent) {
733         this.indent = indent;
734         lastWasNewline = true;
735     }
736     
737     /**
738      * Gets the first paragraph line indent.
739      * 
740      * @return the indent
741      */

742     public float getIndent() {
743         return indent;
744     }
745     
746     /**
747      * Sets the following paragraph lines indent.
748      * 
749      * @param indent the indent
750      */

751     public void setFollowingIndent(float indent) {
752         this.followingIndent = indent;
753         lastWasNewline = true;
754     }
755     
756     /**
757      * Gets the following paragraph lines indent.
758      * 
759      * @return the indent
760      */

761     public float getFollowingIndent() {
762         return followingIndent;
763     }
764     
765     /**
766      * Sets the right paragraph lines indent.
767      * 
768      * @param indent the indent
769      */

770     public void setRightIndent(float indent) {
771         this.rightIndent = indent;
772         lastWasNewline = true;
773     }
774     
775     /**
776      * Gets the right paragraph lines indent.
777      * 
778      * @return the indent
779      */

780     public float getRightIndent() {
781         return rightIndent;
782     }
783     
784     /**
785      * Outputs the lines to the document. It is equivalent to <CODE>go(false)</CODE>.
786      * 
787      * @return returns the result of the operation. It can be <CODE>NO_MORE_TEXT</CODE>
788      * and/or <CODE>NO_MORE_COLUMN</CODE>
789      * @throws DocumentException on error
790      */

791     public int go() throws DocumentException {
792         return go(false);
793     }
794     
795     /**
796      * Outputs the lines to the document. The output can be simulated.
797      * @param simulate <CODE>true</CODE> to simulate the writing to the document
798      * @return returns the result of the operation. It can be <CODE>NO_MORE_TEXT</CODE>
799      * and/or <CODE>NO_MORE_COLUMN</CODE>
800      * @throws DocumentException on error
801      */

802     public int go(boolean simulate) throws DocumentException {
803         if (composite)
804             return goComposite(simulate);
805         addWaitingPhrase();
806         if (bidiLine == null)
807             return NO_MORE_TEXT;
808         descender = 0;
809         linesWritten = 0;
810         boolean dirty = false;
811         float ratio = spaceCharRatio;
812         Object currentValues[] = new Object[2];
813         PdfFont currentFont = null;
814         Float lastBaseFactor = new Float(0);
815         currentValues[1] = lastBaseFactor;
816         PdfDocument pdf = null;
817         PdfContentByte graphics = null;
818         PdfContentByte text = null;
819         firstLineY = Float.NaN;
820         int localRunDirection = PdfWriter.RUN_DIRECTION_NO_BIDI;
821         if (runDirection != PdfWriter.RUN_DIRECTION_DEFAULT)
822             localRunDirection = runDirection;
823         if (canvas != null) {
824             graphics = canvas;
825             pdf = canvas.getPdfDocument();
826             text = canvas.getDuplicate();
827         }
828         else if (!simulate)
829             throw new NullPointerException("ColumnText.go with simulate==false and text==null.");
830         if (!simulate) {
831             if (ratio == GLOBAL_SPACE_CHAR_RATIO)
832                 ratio = text.getPdfWriter().getSpaceCharRatio();
833             else if (ratio < 0.001f)
834                 ratio = 0.001f;
835         }
836         float firstIndent = 0;
837         PdfLine line;
838         float x1;
839         int status = 0;
840         while(true) {
841             firstIndent = (lastWasNewline ? indent : followingIndent); //
842             if (rectangularMode) {
843                 if (rectangularWidth <= firstIndent + rightIndent) {
844                     status = NO_MORE_COLUMN;
845                     if (bidiLine.isEmpty())
846                         status |= NO_MORE_TEXT;
847                     break;
848                 }
849                 if (bidiLine.isEmpty()) {
850                     status = NO_MORE_TEXT;
851                     break;
852                 }
853                 line = bidiLine.processLine(leftX, rectangularWidth - firstIndent - rightIndent, alignment, localRunDirection, arabicOptions);
854                 if (line == null) {
855                     status = NO_MORE_TEXT;
856                     break;
857                 }
858                 float[] maxSize = line.getMaxSize();
859                 if (isUseAscender() && Float.isNaN(firstLineY))
860                     currentLeading = line.getAscender();
861                 else
862                     currentLeading = Math.max(fixedLeading + maxSize[0] * multipliedLeading, maxSize[1]);
863                 if (yLine > maxY || yLine - currentLeading < minY ) {
864                     status = NO_MORE_COLUMN;
865                     bidiLine.restore();
866                     break;
867                 }
868                 yLine -= currentLeading;
869                 if (!simulate && !dirty) {
870                     text.beginText();
871                     dirty = true;
872                 }
873                 if (Float.isNaN(firstLineY))
874                     firstLineY = yLine;
875                 updateFilledWidth(rectangularWidth - line.widthLeft());
876                 x1 = leftX;
877             }
878             else {
879                    float yTemp = yLine;
880                    float xx[] = findLimitsTwoLines();
881                    if (xx == null) {
882                        status = NO_MORE_COLUMN;
883                        if (bidiLine.isEmpty())
884                            status |= NO_MORE_TEXT;
885                        yLine = yTemp;
886                        break;
887                    }
888                    if (bidiLine.isEmpty()) {
889                        status = NO_MORE_TEXT;
890                        yLine = yTemp;
891                        break;
892                    }
893                    x1 = Math.max(xx[0], xx[2]);
894                     float x2 = Math.min(xx[1], xx[3]);
895                     if (x2 - x1 <= firstIndent + rightIndent)
896                         continue;
897                     if (!simulate && !dirty) {
898                         text.beginText();
899                         dirty = true;
900                     }
901                     line = bidiLine.processLine(x1, x2 - x1 - firstIndent - rightIndent, alignment, localRunDirection, arabicOptions);
902                     if (line == null) {
903                         status = NO_MORE_TEXT;
904                         yLine = yTemp;
905                         break;
906                     }
907                 }
908                 if (!simulate) {
909                     currentValues[0] = currentFont;
910                     text.setTextMatrix(x1 + (line.isRTL() ? rightIndent : firstIndent) + line.indentLeft(), yLine);
911                     pdf.writeLineToContent(line, text, graphics, currentValues, ratio);
912                     currentFont = (PdfFont)currentValues[0];
913                 }
914                 lastWasNewline = line.isNewlineSplit();
915                 yLine -= line.isNewlineSplit() ? extraParagraphSpace : 0;
916                 ++linesWritten;
917                 descender = line.getDescender();
918             }
919         if (dirty) {
920             text.endText();
921             canvas.add(text);
922         }
923         return status;
924     }
925     
926     /**
927      * Sets the extra space between paragraphs.
928      * 
929      * @return the extra space between paragraphs
930      */

931     public float getExtraParagraphSpace() {
932         return extraParagraphSpace;
933     }
934     
935     /**
936      * Sets the extra space between paragraphs.
937      * 
938      * @param extraParagraphSpace the extra space between paragraphs
939      */

940     public void setExtraParagraphSpace(float extraParagraphSpace) {
941         this.extraParagraphSpace = extraParagraphSpace;
942     }
943     
944     /**
945      * Clears the chunk array.
946      * A call to <CODE>go()</CODE> will always return NO_MORE_TEXT.
947      */

948     public void clearChunks() {
949         if (bidiLine != null)
950             bidiLine.clearChunks();
951     }
952     
953     /**
954      * Gets the space/character extra spacing ratio for fully justified text.
955      *
956      * @return the space/character extra spacing ratio
957      */
    
958     public float getSpaceCharRatio() {
959         return spaceCharRatio;
960     }
961     
962     /**
963      * Sets the ratio between the extra word spacing and the extra character
964      * spacing when the text is fully justified.
965      * Extra word spacing will grow <CODE>spaceCharRatio</CODE> times more
966      * than extra character spacing.
967      * If the ratio is <CODE>PdfWriter.NO_SPACE_CHAR_RATIO</CODE> then the
968      * extra character spacing will be zero.
969      * 
970      * @param spaceCharRatio the ratio between the extra word spacing and the extra character spacing
971      */

972     public void setSpaceCharRatio(float spaceCharRatio) {
973         this.spaceCharRatio = spaceCharRatio;
974     }
975
976     /**
977      * Sets the run direction. 
978      * 
979      * @param runDirection the run direction
980      */
    
981     public void setRunDirection(int runDirection) {
982         if (runDirection < PdfWriter.RUN_DIRECTION_DEFAULT || runDirection > PdfWriter.RUN_DIRECTION_RTL)
983             throw new RuntimeException("Invalid run direction: " + runDirection);
984         this.runDirection = runDirection;
985     }
986     
987     /**
988      * Gets the run direction.
989      * 
990      * @return the run direction
991      */
    
992     public int getRunDirection() {
993         return runDirection;
994     }
995     
996     /**
997      * Gets the number of lines written.
998      * 
999      * @return the number of lines written
1000      */

1001     public int getLinesWritten() {
1002         return this.linesWritten;
1003     }
1004     
1005     /**
1006      * Gets the arabic shaping options.
1007      * 
1008      * @return the arabic shaping options
1009      */

1010     public int getArabicOptions() {
1011         return this.arabicOptions;
1012     }
1013     
1014     /**
1015      * Sets the arabic shaping options. The option can be AR_NOVOWEL,
1016      * AR_COMPOSEDTASHKEEL and AR_LIG.
1017      * 
1018      * @param arabicOptions the arabic shaping options
1019      */

1020     public void setArabicOptions(int arabicOptions) {
1021         this.arabicOptions = arabicOptions;
1022     }
1023     
1024     /**
1025      * Gets the biggest descender value of the last line written.
1026      * 
1027      * @return the biggest descender value of the last line written
1028      */
    
1029     public float getDescender() {
1030         return descender;
1031     }
1032     
1033     /**
1034      * Gets the width that the line will occupy after writing.
1035      * Only the width of the first line is returned.
1036      * 
1037      * @param phrase the <CODE>Phrase</CODE> containing the line
1038      * @param runDirection the run direction
1039      * @param arabicOptions the options for the arabic shaping
1040      * @return the width of the line
1041      */
    
1042     public static float getWidth(Phrase phrase, int runDirection, int arabicOptions) {
1043         ColumnText ct = new ColumnText(null);
1044         ct.addText(phrase);
1045         ct.addWaitingPhrase();
1046         PdfLine line = ct.bidiLine.processLine(0, 20000, Element.ALIGN_LEFT, runDirection, arabicOptions);
1047         if (line == null)
1048             return 0;
1049         else
1050             return 20000 - line.widthLeft();
1051     }
1052     
1053     /**
1054      * Gets the width that the line will occupy after writing.
1055      * Only the width of the first line is returned.
1056      * 
1057      * @param phrase the <CODE>Phrase</CODE> containing the line
1058      * @return the width of the line
1059      */
    
1060     public static float getWidth(Phrase phrase) {
1061         return getWidth(phrase, PdfWriter.RUN_DIRECTION_NO_BIDI, 0);
1062     }
1063     
1064     /**
1065      * Shows a line of text. Only the first line is written.
1066      * 
1067      * @param canvas where the text is to be written to
1068      * @param alignment the alignment. It is not influenced by the run direction
1069      * @param phrase the <CODE>Phrase</CODE> with the text
1070      * @param x the x reference position
1071      * @param y the y reference position
1072      * @param rotation the rotation to be applied in degrees counterclockwise
1073      * @param runDirection the run direction
1074      * @param arabicOptions the options for the arabic shaping
1075      */
    
1076     public static void showTextAligned(PdfContentByte canvas, int alignment, Phrase phrase, float x, float y, float rotation, int runDirection, int arabicOptions) {
1077         if (alignment != Element.ALIGN_LEFT && alignment != Element.ALIGN_CENTER
1078             && alignment != Element.ALIGN_RIGHT)
1079             alignment = Element.ALIGN_LEFT;
1080         canvas.saveState();
1081         ColumnText ct = new ColumnText(canvas);
1082         float lly = -1;
1083         float ury = 2;
1084         float llx;
1085         float urx;
1086         switch (alignment) {
1087             case Element.ALIGN_LEFT:
1088                 llx = 0;
1089                 urx = 20000;
1090                 break;
1091             case Element.ALIGN_RIGHT:
1092                 llx = -20000;
1093                 urx = 0;
1094                 break;
1095             default:
1096                 llx = -20000;
1097                 urx = 20000;
1098                 break;
1099         }
1100         if (rotation == 0) {
1101             llx += x;
1102             lly += y;
1103             urx += x;
1104             ury += y;
1105         }
1106         else {
1107             double alpha = rotation * Math.PI / 180.0;
1108             float cos = (float)Math.cos(alpha);
1109             float sin = (float)Math.sin(alpha);
1110             canvas.concatCTM(cos, sin, -sin, cos, x, y);
1111         }
1112         ct.setSimpleColumn(phrase, llx, lly, urx, ury, 2, alignment);
1113         if (runDirection == PdfWriter.RUN_DIRECTION_RTL) {
1114             if (alignment == Element.ALIGN_LEFT)
1115                 alignment = Element.ALIGN_RIGHT;
1116             else if (alignment == Element.ALIGN_RIGHT)
1117                 alignment = Element.ALIGN_LEFT;
1118         }
1119         ct.setAlignment(alignment);
1120         ct.setArabicOptions(arabicOptions);
1121         ct.setRunDirection(runDirection);
1122         try {
1123             ct.go();
1124         }
1125         catch (DocumentException e) {
1126             throw new ExceptionConverter(e);
1127         }
1128         canvas.restoreState();
1129     }
1130
1131     /**
1132      * Shows a line of text. Only the first line is written.
1133      * 
1134      * @param canvas where the text is to be written to
1135      * @param alignment the alignment
1136      * @param phrase the <CODE>Phrase</CODE> with the text
1137      * @param x the x reference position
1138      * @param y the y reference position
1139      * @param rotation the rotation to be applied in degrees counterclockwise
1140      */
    
1141     public static void showTextAligned(PdfContentByte canvas, int alignment, Phrase phrase, float x, float y, float rotation) {
1142         showTextAligned(canvas, alignment, phrase, x, y, rotation, PdfWriter.RUN_DIRECTION_NO_BIDI, 0);
1143     }
1144
1145     protected int goComposite(boolean simulate) throws DocumentException {
1146         if (!rectangularMode)
1147             throw new DocumentException("Irregular columns are not supported in composite mode.");
1148         linesWritten = 0;
1149         descender = 0;
1150         boolean firstPass = adjustFirstLine;
1151         
1152         main_loop:
1153         while (true) {
1154             if (compositeElements.isEmpty())
1155                 return NO_MORE_TEXT;
1156             Element element = (Element)compositeElements.getFirst();
1157             if (element.type() == Element.PARAGRAPH) {
1158                 Paragraph para = (Paragraph)element;
1159                 int status = 0;
1160                 for (int keep = 0; keep < 2; ++keep) {
1161                     float lastY = yLine;
1162                     boolean createHere = false;
1163                     if (compositeColumn == null) {
1164                         compositeColumn = new ColumnText(canvas);
1165                         compositeColumn.setUseAscender(firstPass ? useAscender : false);
1166                         compositeColumn.setAlignment(para.getAlignment());
1167                         compositeColumn.setIndent(para.getIndentationLeft() + para.getFirstLineIndent());
1168                         compositeColumn.setExtraParagraphSpace(para.getExtraParagraphSpace());
1169                         compositeColumn.setFollowingIndent(para.getIndentationLeft());
1170                         compositeColumn.setRightIndent(para.getIndentationRight());
1171                         compositeColumn.setLeading(para.getLeading(), para.getMultipliedLeading());
1172                         compositeColumn.setRunDirection(runDirection);
1173                         compositeColumn.setArabicOptions(arabicOptions);
1174                         compositeColumn.setSpaceCharRatio(spaceCharRatio);
1175                         compositeColumn.addText(para);
1176                         if (!firstPass) {
1177                             yLine -= para.getSpacingBefore();
1178                         }
1179                         createHere = true;
1180                     }
1181                     compositeColumn.leftX = leftX;
1182                     compositeColumn.rightX = rightX;
1183                     compositeColumn.yLine = yLine;
1184                     compositeColumn.rectangularWidth = rectangularWidth;
1185                     compositeColumn.rectangularMode = rectangularMode;
1186                     compositeColumn.minY = minY;
1187                     compositeColumn.maxY = maxY;
1188                     boolean keepCandidate = (para.getKeepTogether() && createHere && !firstPass);
1189                     status = compositeColumn.go(simulate || (keepCandidate && keep == 0));
1190                     updateFilledWidth(compositeColumn.filledWidth);
1191                     if ((status & NO_MORE_TEXT) == 0 && keepCandidate) {
1192                         compositeColumn = null;
1193                         yLine = lastY;
1194                         return NO_MORE_COLUMN;
1195                     }
1196                     if (simulate || !keepCandidate)
1197                         break;
1198                     if (keep == 0) {
1199                         compositeColumn = null;
1200                         yLine = lastY;
1201                     }
1202                 }
1203                 firstPass = false;
1204                 yLine = compositeColumn.yLine;
1205                 linesWritten += compositeColumn.linesWritten;
1206                 descender = compositeColumn.descender;
1207                 if ((status & NO_MORE_TEXT) != 0) {
1208                     compositeColumn = null;
1209                     compositeElements.removeFirst();
1210                     yLine -= para.getSpacingAfter();
1211                 }
1212                 if ((status & NO_MORE_COLUMN) != 0) {
1213                     return NO_MORE_COLUMN;
1214                 }
1215             }
1216             else if (element.type() == Element.LIST) {
1217                 com.lowagie.text.List list = (com.lowagie.text.List)element;
1218                 ArrayList items = list.getItems();
1219                 ListItem item = null;
1220                 float listIndentation = list.getIndentationLeft();
1221                 int count = 0;
1222                 Stack stack = new Stack();
1223                 for (int k = 0; k < items.size(); ++k) {
1224                     Object obj = items.get(k);
1225                     if (obj instanceof ListItem) {
1226                         if (count == listIdx) {
1227                             item = (ListItem)obj;
1228                             break;
1229                         }
1230                         else ++count;
1231                     }
1232                     else if (obj instanceof com.lowagie.text.List) {
1233                         stack.push(new Object[]{list, new Integer(k), new Float(listIndentation)});
1234                         list = (com.lowagie.text.List)obj;
1235                         items = list.getItems();
1236                         listIndentation += list.getIndentationLeft();
1237                         k = -1;
1238                         continue;
1239                     }
1240                     if (k == items.size() - 1) {
1241                         if (!stack.isEmpty()) {
1242                             Object objs[] = (Object[])stack.pop();
1243                             list = (com.lowagie.text.List)objs[0];
1244                             items = list.getItems();
1245                             k = ((Integer)objs[1]).intValue();
1246                             listIndentation = ((Float)objs[2]).floatValue();
1247                         }
1248                     }
1249                 }
1250                 int status = 0;
1251                 for (int keep = 0; keep < 2; ++keep) {
1252                     float lastY = yLine;
1253                     boolean createHere = false;
1254                     if (compositeColumn == null) {
1255                         if (item == null) {
1256                             listIdx = 0;
1257                             compositeElements.removeFirst();
1258                             continue main_loop;
1259                         }
1260                         compositeColumn = new ColumnText(canvas);
1261                         compositeColumn.setUseAscender(firstPass ? useAscender : false);
1262                         compositeColumn.setAlignment(item.getAlignment());
1263                         compositeColumn.setIndent(item.getIndentationLeft() + listIndentation + item.getFirstLineIndent());
1264                         compositeColumn.setExtraParagraphSpace(item.getExtraParagraphSpace());
1265                         compositeColumn.setFollowingIndent(compositeColumn.getIndent());
1266                         compositeColumn.setRightIndent(item.getIndentationRight() + list.getIndentationRight());
1267                         compositeColumn.setLeading(item.getLeading(), item.getMultipliedLeading());
1268                         compositeColumn.setRunDirection(runDirection);
1269                         compositeColumn.setArabicOptions(arabicOptions);
1270                         compositeColumn.setSpaceCharRatio(spaceCharRatio);
1271                         compositeColumn.addText(item);
1272                         if (!firstPass) {
1273                             yLine -= item.getSpacingBefore();
1274                         }
1275                         createHere = true;
1276                     }
1277                     compositeColumn.leftX = leftX;
1278                     compositeColumn.rightX = rightX;
1279                     compositeColumn.yLine = yLine;
1280                     compositeColumn.rectangularWidth = rectangularWidth;
1281                     compositeColumn.rectangularMode = rectangularMode;
1282                     compositeColumn.minY = minY;
1283                     compositeColumn.maxY = maxY;
1284                     boolean keepCandidate = (item.getKeepTogether() && createHere && !firstPass);
1285                     status = compositeColumn.go(simulate || (keepCandidate && keep == 0));
1286                     updateFilledWidth(compositeColumn.filledWidth);
1287                     if ((status & NO_MORE_TEXT) == 0 && keepCandidate) {
1288                         compositeColumn = null;
1289                         yLine = lastY;
1290                         return NO_MORE_COLUMN;
1291                     }
1292                     if (simulate || !keepCandidate)
1293                         break;
1294                     if (keep == 0) {
1295                         compositeColumn = null;
1296                         yLine = lastY;
1297                     }
1298                 }
1299                 firstPass = false;
1300                 yLine = compositeColumn.yLine;
1301                 linesWritten += compositeColumn.linesWritten;
1302                 descender = compositeColumn.descender;
1303                 if (!Float.isNaN(compositeColumn.firstLineY) && !compositeColumn.firstLineYDone) {
1304                     if (!simulate)
1305                         showTextAligned(canvas, Element.ALIGN_LEFT, new Phrase(item.getListSymbol()), compositeColumn.leftX + listIndentation, compositeColumn.firstLineY, 0);
1306                     compositeColumn.firstLineYDone = true;
1307                 }
1308                 if ((status & NO_MORE_TEXT) != 0) {
1309                     compositeColumn = null;
1310                     ++listIdx;
1311                     yLine -= item.getSpacingAfter();
1312                 }
1313                 if ((status & NO_MORE_COLUMN) != 0)
1314                     return NO_MORE_COLUMN;
1315             }
1316             else if (element.type() == Element.PTABLE) {
1317                 // don't write anything in the current column if there's no more space available
1318                 if (yLine < minY || yLine > maxY)
1319                     return NO_MORE_COLUMN;
1320                 
1321                 // get the PdfPTable element
1322                 PdfPTable table = (PdfPTable)element;
1323                 // we ignore tables without a body
1324                 if (table.size() <= table.getHeaderRows()) {
1325                     compositeElements.removeFirst();
1326                     continue;
1327                 }
1328                 
1329                 // offsets
1330                 float yTemp = yLine;
1331                 if (!firstPass && listIdx == 0)
1332                     yTemp -= table.spacingBefore();
1333                 float yLineWrite = yTemp;
1334                 
1335                 // don't write anything in the current column if there's no more space available
1336                 if (yTemp < minY || yTemp > maxY)
1337                     return NO_MORE_COLUMN;
1338                 
1339                 // coordinates
1340                 currentLeading = 0;
1341                 float x1 = leftX;
1342                 float tableWidth;
1343                 if (table.isLockedWidth()) {
1344                     tableWidth = table.getTotalWidth();
1345                     updateFilledWidth(tableWidth);
1346                 }
1347                 else {
1348                     tableWidth = rectangularWidth * table.getWidthPercentage() / 100f;
1349                     table.setTotalWidth(tableWidth);
1350                 }
1351                 
1352                 // how many header rows are real header rows; how many are footer rows?
1353                 int headerRows = table.getHeaderRows();
1354                 int footerRows = table.getFooterRows();
1355                 if (footerRows > headerRows)
1356                     footerRows = headerRows;
1357                 int realHeaderRows = headerRows - footerRows;
1358                 float headerHeight = table.getHeaderHeight();
1359                 float footerHeight = table.getFooterHeight();
1360
1361                 // make sure the header and footer fit on the page
1362                 boolean skipHeader = (!firstPass && table.isSkipFirstHeader() && listIdx <= headerRows);
1363                 if (!skipHeader) {
1364                     yTemp -= headerHeight;
1365                     if (yTemp < minY || yTemp > maxY) {
1366                         if (firstPass) {
1367                             compositeElements.removeFirst();
1368                             continue;
1369                         }
1370                         return NO_MORE_COLUMN;
1371                     }
1372                 }
1373                 
1374                 // how many real rows (not header or footer rows) fit on a page?
1375                 int k;
1376                 if (listIdx < headerRows)
1377                     listIdx = headerRows;
1378                 if (!table.isComplete())
1379                     yTemp -= footerHeight;
1380                 for (k = listIdx; k < table.size(); ++k) {
1381                     float rowHeight = table.getRowHeight(k);
1382                     if (yTemp - rowHeight < minY)
1383                         break;
1384                     yTemp -= rowHeight;
1385                 }
1386                 if (!table.isComplete())
1387                     yTemp += footerHeight;
1388                 // either k is the first row that doesn't fit on the page (break);
1389                 if (k < table.size()) {
1390                     if (table.isSplitRows() && (!table.isSplitLate() || (k == listIdx && firstPass))) {
1391                         if (!splittedRow) {
1392                             splittedRow = true;
1393                             table = new PdfPTable(table);
1394                             compositeElements.set(0, table);
1395                             ArrayList rows = table.getRows();
1396                             for (int i = headerRows; i < listIdx; ++i)
1397                                 rows.set(i, null);
1398                         }
1399                         float h = yTemp - minY;
1400                         PdfPRow newRow = table.getRow(k).splitRow(table, k, h);
1401                         if (newRow == null) {
1402                             if (k == listIdx)
1403                                 return NO_MORE_COLUMN;
1404                         }
1405                         else {
1406                             yTemp = minY;
1407                             table.getRows().add(++k, newRow);                                    
1408                         }
1409                     }
1410                     else if (!table.isSplitRows() && k == listIdx && firstPass) {
1411                         compositeElements.removeFirst();
1412                         splittedRow = false;
1413                         continue;
1414                     }
1415                     else if (k == listIdx && !firstPass && (!table.isSplitRows() || table.isSplitLate()) && (table.getFooterRows() == 0 || table.isComplete()))
1416                         return NO_MORE_COLUMN;
1417                 }
1418                 // or k is the number of rows in the table (for loop was done).
1419                 firstPass = false;
1420                 // we draw the table (for real now)
1421                 if (!simulate) {
1422                     // set the alignment
1423                     switch (table.getHorizontalAlignment()) {
1424                         case Element.ALIGN_LEFT:
1425                             break;
1426                         case Element.ALIGN_RIGHT:
1427                             x1 += rectangularWidth - tableWidth;
1428                             break;
1429                         default:
1430                             x1 += (rectangularWidth - tableWidth) / 2f;
1431                     }
1432                     // copy the rows that fit on the page in a new table nt
1433                     PdfPTable nt = PdfPTable.shallowCopy(table);
1434                     ArrayList sub = nt.getRows();
1435                     
1436                     // first we add the real header rows (if necessary)
1437                     if (!skipHeader) {
1438                         for (int j = 0; j < realHeaderRows; ++j) {
1439                             PdfPRow headerRow = table.getRow(j);
1440                             sub.add(headerRow);
1441                         }
1442                     }
1443                     else
1444                         nt.setHeaderRows(footerRows);
1445                     // then we add the real content
1446                     sub.addAll(table.getRows(listIdx, k));
1447                     // if k < table.size(), we must indicate that the new table is complete;
1448                     // otherwise no footers will be added (because iText thinks the table continues on the same page)
1449                     boolean showFooter = !table.isSkipLastFooter();
1450                     if (k < table.size()) {
1451                         nt.setComplete(true);
1452                         showFooter = true;
1453                     }
1454                     // we add the footer rows if necessary (not for incomplete tables)
1455                     for (int j = 0; j < footerRows && nt.isComplete() && showFooter; ++j)
1456                         sub.add(table.getRow(j + realHeaderRows));
1457
1458                     // we need a correction if the last row needs to be extended
1459                     float rowHeight = 0;
1460                     PdfPRow last = (PdfPRow)sub.get(sub.size() - 1 - footerRows);
1461                     if (table.isExtendLastRow()) {
1462                         rowHeight = last.getMaxHeights();
1463                         last.setMaxHeights(yTemp - minY + rowHeight);
1464                         yTemp = minY;
1465                     }
1466                     
1467                     // now we render the rows of the new table
1468                     if (canvases != null)
1469                         nt.writeSelectedRows(0, -1, x1, yLineWrite, canvases);
1470                     else
1471                         nt.writeSelectedRows(0, -1, x1, yLineWrite, canvas);
1472                     if (table.isExtendLastRow()) {
1473                         last.setMaxHeights(rowHeight);
1474                     }
1475                 }
1476                 else if (table.isExtendLastRow() && minY > PdfPRow.BOTTOM_LIMIT)
1477                     yTemp = minY;
1478                 yLine = yTemp;
1479                 if (!(skipHeader || table.isComplete()))
1480                     yLine += footerHeight;
1481                 if (k >= table.size()) {
1482                     yLine -= table.spacingAfter();
1483                     compositeElements.removeFirst();
1484                     splittedRow = false;
1485                     listIdx = 0;
1486                 }
1487                 else {
1488                     if (splittedRow) {
1489                         ArrayList rows = table.getRows();
1490                         for (int i = listIdx; i < k; ++i)
1491                             rows.set(i, null);
1492                     }
1493                     listIdx = k;
1494                     return NO_MORE_COLUMN;
1495                 }
1496             }
1497             else if (element.type() == Element.YMARK) {
1498                 if (!simulate) {
1499                     DrawInterface zh = (DrawInterface)element;
1500                     zh.draw(canvas, leftX, minY, rightX, maxY, yLine);
1501                 }
1502                 compositeElements.removeFirst();
1503             }
1504             else
1505                 compositeElements.removeFirst();
1506         }
1507     }
1508     
1509     /**
1510      * Gets the canvas.
1511      * If a set of four canvases exists, the TEXTCANVAS is returned.
1512      * 
1513      * @return a PdfContentByte.
1514      */

1515     public PdfContentByte getCanvas() {
1516         return canvas;
1517     }
1518     
1519     /**
1520      * Sets the canvas.
1521      * If before a set of four canvases was set, it is being unset.
1522      * 
1523      * @param canvas
1524      */

1525     public void setCanvas(PdfContentByte canvas) {
1526         this.canvas = canvas;
1527         this.canvases = null;
1528         if (compositeColumn != null)
1529             compositeColumn.setCanvas(canvas);
1530     }
1531     
1532     /**
1533      * Sets the canvases.
1534      * 
1535      * @param canvases
1536      */

1537     public void setCanvases(PdfContentByte[] canvases) {
1538         this.canvases = canvases;
1539         this.canvas = canvases[PdfPTable.TEXTCANVAS];
1540         if (compositeColumn != null)
1541             compositeColumn.setCanvases(canvases);
1542     }
1543     
1544     /**
1545      * Gets the canvases.
1546      * 
1547      * @return an array of PdfContentByte
1548      */

1549     public PdfContentByte[] getCanvases() {
1550         return canvases;
1551     }
1552     
1553     /**
1554      * Checks if the element has a height of 0.
1555      * 
1556      * @return true or false
1557      * @since 2.1.2
1558      */

1559     public boolean zeroHeightElement() {
1560         return composite && !compositeElements.isEmpty() && ((Element)compositeElements.getFirst()).type() == Element.YMARK;
1561     }
1562     
1563     /**
1564      * Checks if UseAscender is enabled/disabled.
1565      * 
1566      * @return true is the adjustment of the first line height is based on max ascender.
1567      */

1568     public boolean isUseAscender() {
1569         return useAscender;
1570     }
1571
1572     /**
1573      * Enables/Disables adjustment of first line height based on max ascender.
1574      * 
1575      * @param useAscender    enable adjustment if true
1576      */

1577     public void setUseAscender(boolean useAscender) {
1578         this.useAscender = useAscender;
1579     }
1580     
1581     /**
1582      * Checks the status variable and looks if there's still some text.
1583      */

1584     public static boolean hasMoreText(int status) {
1585         return (status & ColumnText.NO_MORE_TEXT) == 0;
1586     }
1587
1588     /**
1589      * Gets the real width used by the largest line.
1590      * 
1591      * @return the real width used by the largest line
1592      */

1593     public float getFilledWidth() {
1594         return filledWidth;
1595     }
1596
1597     /**
1598      * Sets the real width used by the largest line.
1599      * Only used to set it to zero to start another measurement.
1600      *
1601      * @param filledWidth the real width used by the largest line
1602      */

1603     public void setFilledWidth(float filledWidth) {
1604         this.filledWidth = filledWidth;
1605     }
1606     
1607     /**
1608      * Replaces the <CODE>filledWidth</CODE> if greater than the existing one.
1609      *
1610      * @param w the new <CODE>filledWidth</CODE> if greater than the existing one
1611      */

1612     public void updateFilledWidth(float w) {
1613         if (w > filledWidth)
1614             filledWidth = w;
1615     }
1616
1617
1618     /**
1619      * Gets the first line adjustment property.
1620      * 
1621      * @return the first line adjustment property.
1622      */

1623     public boolean isAdjustFirstLine() {
1624         return adjustFirstLine;
1625     }
1626
1627     /**
1628      * Sets the first line adjustment.
1629      * Some objects have properties, like spacing before, that behave
1630      * differently if the object is the first to be written after go() or not.
1631      * The first line adjustment is <CODE>true</CODE> by default but can be
1632      * changed if several objects are to be placed one after the other in the
1633      * same column calling go() several times.
1634      * 
1635      * @param adjustFirstLine <CODE>true</CODE> to adjust the first line, <CODE>false</CODE> otherwise
1636      */

1637     public void setAdjustFirstLine(boolean adjustFirstLine) {
1638         this.adjustFirstLine = adjustFirstLine;
1639     }
1640 }
1641