001/*
002 * $Id: ColumnText.java 4922 2011-07-05 09:15:06Z redlab_b $
003 *
004 * This file is part of the iText (R) project.
005 * Copyright (c) 1998-2011 1T3XT BVBA
006 * Authors: Bruno Lowagie, Paulo Soares, et al.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU Affero General Public License version 3
010 * as published by the Free Software Foundation with the addition of the
011 * following permission added to Section 15 as permitted in Section 7(a):
012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT,
013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
014 *
015 * This program is distributed in the hope that it will be useful, but
016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
017 * or FITNESS FOR A PARTICULAR PURPOSE.
018 * See the GNU Affero General Public License for more details.
019 * You should have received a copy of the GNU Affero General Public License
020 * along with this program; if not, see http://www.gnu.org/licenses or write to
021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
022 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
023 * http://itextpdf.com/terms-of-use/
024 *
025 * The interactive user interfaces in modified source and object code versions
026 * of this program must display Appropriate Legal Notices, as required under
027 * Section 5 of the GNU Affero General Public License.
028 *
029 * In accordance with Section 7(b) of the GNU Affero General Public License,
030 * a covered work must retain the producer line in every PDF that is created
031 * or manipulated using iText.
032 *
033 * You can be released from the requirements of the license by purchasing
034 * a commercial license. Buying such a license is mandatory as soon as you
035 * develop commercial activities involving the iText software without
036 * disclosing the source code of your own applications.
037 * These activities include: offering paid services to customers as an ASP,
038 * serving PDFs on the fly in a web application, shipping iText with a closed
039 * source product.
040 *
041 * For more information, please contact iText Software Corp. at this
042 * address: sales@itextpdf.com
043 */
044package com.itextpdf.text.pdf;
045import java.util.ArrayList;
046import java.util.LinkedList;
047import java.util.Stack;
048
049import com.itextpdf.text.Chunk;
050import com.itextpdf.text.DocumentException;
051import com.itextpdf.text.Element;
052import com.itextpdf.text.ExceptionConverter;
053import com.itextpdf.text.Image;
054import com.itextpdf.text.ListItem;
055import com.itextpdf.text.Paragraph;
056import com.itextpdf.text.Phrase;
057import com.itextpdf.text.error_messages.MessageLocalization;
058import com.itextpdf.text.pdf.draw.DrawInterface;
059
060/**
061 * Formats text in a columnwise form. The text is bound
062 * on the left and on the right by a sequence of lines. This allows the column
063 * to have any shape, not only rectangular.
064 * <P>
065 * Several parameters can be set like the first paragraph line indent and
066 * extra space between paragraphs.
067 * <P>
068 * A call to the method <CODE>go</CODE> will return one of the following
069 * situations: the column ended or the text ended.
070 * <P>
071 * If the column ended, a new column definition can be loaded with the method
072 * <CODE>setColumns</CODE> and the method <CODE>go</CODE> can be called again.
073 * <P>
074 * If the text ended, more text can be loaded with <CODE>addText</CODE>
075 * and the method <CODE>go</CODE> can be called again.<BR>
076 * The only limitation is that one or more complete paragraphs must be loaded
077 * each time.
078 * <P>
079 * Full bidirectional reordering is supported. If the run direction is
080 * <CODE>PdfWriter.RUN_DIRECTION_RTL</CODE> the meaning of the horizontal
081 * alignments and margins is mirrored.
082 * @author Paulo Soares
083 */
084
085public class ColumnText {
086    /** Eliminate the arabic vowels */
087    public static final int AR_NOVOWEL = ArabicLigaturizer.ar_novowel;
088    /** Compose the tashkeel in the ligatures. */
089    public static final int AR_COMPOSEDTASHKEEL = ArabicLigaturizer.ar_composedtashkeel;
090    /** Do some extra double ligatures. */
091    public static final int AR_LIG = ArabicLigaturizer.ar_lig;
092    /**
093     * Digit shaping option: Replace European digits (U+0030...U+0039) by Arabic-Indic digits.
094     */
095    public static final int DIGITS_EN2AN = ArabicLigaturizer.DIGITS_EN2AN;
096
097    /**
098     * Digit shaping option: Replace Arabic-Indic digits by European digits (U+0030...U+0039).
099     */
100    public static final int DIGITS_AN2EN = ArabicLigaturizer.DIGITS_AN2EN;
101
102    /**
103     * Digit shaping option:
104     * Replace European digits (U+0030...U+0039) by Arabic-Indic digits
105     * if the most recent strongly directional character
106     * is an Arabic letter (its Bidi direction value is RIGHT_TO_LEFT_ARABIC).
107     * The initial state at the start of the text is assumed to be not an Arabic,
108     * letter, so European digits at the start of the text will not change.
109     * Compare to DIGITS_ALEN2AN_INIT_AL.
110     */
111    public static final int DIGITS_EN2AN_INIT_LR = ArabicLigaturizer.DIGITS_EN2AN_INIT_LR;
112
113    /**
114     * Digit shaping option:
115     * Replace European digits (U+0030...U+0039) by Arabic-Indic digits
116     * if the most recent strongly directional character
117     * is an Arabic letter (its Bidi direction value is RIGHT_TO_LEFT_ARABIC).
118     * The initial state at the start of the text is assumed to be an Arabic,
119     * letter, so European digits at the start of the text will change.
120     * Compare to DIGITS_ALEN2AN_INT_LR.
121     */
122    public static final int DIGITS_EN2AN_INIT_AL = ArabicLigaturizer.DIGITS_EN2AN_INIT_AL;
123
124    /**
125     * Digit type option: Use Arabic-Indic digits (U+0660...U+0669).
126     */
127    public static final int DIGIT_TYPE_AN = ArabicLigaturizer.DIGIT_TYPE_AN;
128
129    /**
130     * Digit type option: Use Eastern (Extended) Arabic-Indic digits (U+06f0...U+06f9).
131     */
132    public static final int DIGIT_TYPE_AN_EXTENDED = ArabicLigaturizer.DIGIT_TYPE_AN_EXTENDED;
133
134    protected int runDirection = PdfWriter.RUN_DIRECTION_DEFAULT;
135
136    /** the space char ratio */
137    public static final float GLOBAL_SPACE_CHAR_RATIO = 0;
138
139    /** Initial value of the status. */
140    public static final int START_COLUMN = 0;
141
142    /** Signals that there is no more text available. */
143    public static final int NO_MORE_TEXT = 1;
144
145    /** Signals that there is no more column. */
146    public static final int NO_MORE_COLUMN = 2;
147
148    /** The column is valid. */
149    protected static final int LINE_STATUS_OK = 0;
150
151    /** The line is out the column limits. */
152    protected static final int LINE_STATUS_OFFLIMITS = 1;
153
154    /** The line cannot fit this column position. */
155    protected static final int LINE_STATUS_NOLINE = 2;
156
157    /** Upper bound of the column. */
158    protected float maxY;
159
160    /** Lower bound of the column. */
161    protected float minY;
162
163    protected float leftX;
164
165    protected float rightX;
166
167    /** The column alignment. Default is left alignment. */
168    protected int alignment = Element.ALIGN_LEFT;
169
170    /** The left column bound. */
171    protected ArrayList<float[]> leftWall;
172
173    /** The right column bound. */
174    protected ArrayList<float[]> rightWall;
175
176    /** The chunks that form the text. */
177//    protected ArrayList chunks = new ArrayList();
178    protected BidiLine bidiLine;
179
180    /** The current y line location. Text will be written at this line minus the leading. */
181    protected float yLine;
182
183    /**
184     * The X position after the last line that has been written.
185     * @since 5.0.3
186     */
187    protected float lastX;
188
189    /** The leading for the current line. */
190    protected float currentLeading = 16;
191
192    /** The fixed text leading. */
193    protected float fixedLeading = 16;
194
195    /** The text leading that is multiplied by the biggest font size in the line. */
196    protected float multipliedLeading = 0;
197
198    /** The <CODE>PdfContent</CODE> where the text will be written to. */
199    protected PdfContentByte canvas;
200
201    protected PdfContentByte[] canvases;
202
203    /** The line status when trying to fit a line to a column. */
204    protected int lineStatus;
205
206    /** The first paragraph line indent. */
207    protected float indent = 0;
208
209    /** The following paragraph lines indent. */
210    protected float followingIndent = 0;
211
212    /** The right paragraph lines indent. */
213    protected float rightIndent = 0;
214
215    /** The extra space between paragraphs. */
216    protected float extraParagraphSpace = 0;
217
218    /** The width of the line when the column is defined as a simple rectangle. */
219    protected float rectangularWidth = -1;
220
221    protected boolean rectangularMode = false;
222    /** Holds value of property spaceCharRatio. */
223    private float spaceCharRatio = GLOBAL_SPACE_CHAR_RATIO;
224
225    private boolean lastWasNewline = true;
226
227    /** Holds value of property linesWritten. */
228    private int linesWritten;
229
230    private float firstLineY;
231    private boolean firstLineYDone = false;
232
233    /** Holds value of property arabicOptions. */
234    private int arabicOptions = 0;
235
236    protected float descender;
237
238    protected boolean composite = false;
239
240    protected ColumnText compositeColumn;
241
242    protected LinkedList<Element> compositeElements;
243
244    protected int listIdx = 0;
245    /**
246     * Pointer for the row in a table that is being dealt with
247     * @since 5.1.0
248     */
249    protected int rowIdx = 0;
250
251    /**
252     * The index of the last row that needed to be splitted.
253     * @since 5.0.1 changed a boolean into an int
254     */
255    private int splittedRow = -1;
256
257    protected Phrase waitPhrase;
258
259    /** if true, first line height is adjusted so that the max ascender touches the top */
260    private boolean useAscender = false;
261
262    /** Holds value of property filledWidth. */
263    private float filledWidth;
264
265    private boolean adjustFirstLine = true;
266
267    /**
268     * Creates a <CODE>ColumnText</CODE>.
269     *
270     * @param canvas the place where the text will be written to. Can
271     * be a template.
272     */
273    public ColumnText(final PdfContentByte canvas) {
274        this.canvas = canvas;
275    }
276
277    /**
278     * Creates an independent duplicated of the instance <CODE>org</CODE>.
279     *
280     * @param org the original <CODE>ColumnText</CODE>
281     * @return the duplicated
282     */
283    public static ColumnText duplicate(final ColumnText org) {
284        ColumnText ct = new ColumnText(null);
285        ct.setACopy(org);
286        return ct;
287    }
288
289    /**
290     * Makes this instance an independent copy of <CODE>org</CODE>.
291     *
292     * @param org the original <CODE>ColumnText</CODE>
293     * @return itself
294     */
295    public ColumnText setACopy(final ColumnText org) {
296        setSimpleVars(org);
297        if (org.bidiLine != null)
298            bidiLine = new BidiLine(org.bidiLine);
299        return this;
300    }
301
302    protected void setSimpleVars(final ColumnText org) {
303        maxY = org.maxY;
304        minY = org.minY;
305        alignment = org.alignment;
306        leftWall = null;
307        if (org.leftWall != null)
308            leftWall = new ArrayList<float[]>(org.leftWall);
309        rightWall = null;
310        if (org.rightWall != null)
311            rightWall = new ArrayList<float[]>(org.rightWall);
312        yLine = org.yLine;
313        currentLeading = org.currentLeading;
314        fixedLeading = org.fixedLeading;
315        multipliedLeading = org.multipliedLeading;
316        canvas = org.canvas;
317        canvases = org.canvases;
318        lineStatus = org.lineStatus;
319        indent = org.indent;
320        followingIndent = org.followingIndent;
321        rightIndent = org.rightIndent;
322        extraParagraphSpace = org.extraParagraphSpace;
323        rectangularWidth = org.rectangularWidth;
324        rectangularMode = org.rectangularMode;
325        spaceCharRatio = org.spaceCharRatio;
326        lastWasNewline = org.lastWasNewline;
327        linesWritten = org.linesWritten;
328        arabicOptions = org.arabicOptions;
329        runDirection = org.runDirection;
330        descender = org.descender;
331        composite = org.composite;
332        splittedRow = org.splittedRow;
333        if (org.composite) {
334            compositeElements = new LinkedList<Element>(org.compositeElements);
335            if (splittedRow != -1) {
336                PdfPTable table = (PdfPTable)compositeElements.getFirst();
337                compositeElements.set(0, new PdfPTable(table));
338            }
339            if (org.compositeColumn != null)
340                compositeColumn = duplicate(org.compositeColumn);
341        }
342        listIdx = org.listIdx;
343        rowIdx = org.rowIdx;
344        firstLineY = org.firstLineY;
345        leftX = org.leftX;
346        rightX = org.rightX;
347        firstLineYDone = org.firstLineYDone;
348        waitPhrase = org.waitPhrase;
349        useAscender = org.useAscender;
350        filledWidth = org.filledWidth;
351        adjustFirstLine = org.adjustFirstLine;
352    }
353
354    private void addWaitingPhrase() {
355        if (bidiLine == null && waitPhrase != null) {
356            bidiLine = new BidiLine();
357            for (Chunk c: waitPhrase.getChunks()) {
358                bidiLine.addChunk(new PdfChunk(c, null));
359            }
360            waitPhrase = null;
361        }
362    }
363
364    /**
365     * Adds a <CODE>Phrase</CODE> to the current text array.
366     * Will not have any effect if addElement() was called before.
367     *
368     * @param phrase the text
369     */
370    public void addText(final Phrase phrase) {
371        if (phrase == null || composite)
372            return;
373        addWaitingPhrase();
374        if (bidiLine == null) {
375            waitPhrase = phrase;
376            return;
377        }
378        for (Object element : phrase.getChunks()) {
379            bidiLine.addChunk(new PdfChunk((Chunk)element, null));
380        }
381    }
382
383    /**
384     * Replaces the current text array with this <CODE>Phrase</CODE>.
385     * Anything added previously with addElement() is lost.
386     *
387     * @param phrase the text
388     */
389    public void setText(final Phrase phrase) {
390        bidiLine = null;
391        composite = false;
392        compositeColumn = null;
393        compositeElements = null;
394        listIdx = 0;
395        rowIdx = 0;
396        splittedRow = -1;
397        waitPhrase = phrase;
398    }
399
400    /**
401     * Adds a <CODE>Chunk</CODE> to the current text array.
402     * Will not have any effect if addElement() was called before.
403     *
404     * @param chunk the text
405     */
406    public void addText(final Chunk chunk) {
407        if (chunk == null || composite)
408            return;
409        addText(new Phrase(chunk));
410    }
411
412    /**
413     * Adds an element. Elements supported are <CODE>Paragraph</CODE>,
414     * <CODE>List</CODE>, <CODE>PdfPTable</CODE>, <CODE>Image</CODE> and
415     * <CODE>Graphic</CODE>.
416     * <p>
417     * It removes all the text placed with <CODE>addText()</CODE>.
418     *
419     * @param element the <CODE>Element</CODE>
420     */
421    public void addElement(Element element) {
422        if (element == null)
423            return;
424        if (element instanceof Image) {
425            Image img = (Image)element;
426            PdfPTable t = new PdfPTable(1);
427            float w = img.getWidthPercentage();
428            if (w == 0) {
429                t.setTotalWidth(img.getScaledWidth());
430                t.setLockedWidth(true);
431            }
432            else
433                t.setWidthPercentage(w);
434            t.setSpacingAfter(img.getSpacingAfter());
435            t.setSpacingBefore(img.getSpacingBefore());
436            switch (img.getAlignment()) {
437                case Image.LEFT:
438                    t.setHorizontalAlignment(Element.ALIGN_LEFT);
439                    break;
440                case Image.RIGHT:
441                    t.setHorizontalAlignment(Element.ALIGN_RIGHT);
442                    break;
443                default:
444                    t.setHorizontalAlignment(Element.ALIGN_CENTER);
445                    break;
446            }
447            PdfPCell c = new PdfPCell(img, true);
448            c.setPadding(0);
449            c.setBorder(img.getBorder());
450            c.setBorderColor(img.getBorderColor());
451            c.setBorderWidth(img.getBorderWidth());
452            c.setBackgroundColor(img.getBackgroundColor());
453            t.addCell(c);
454            element = t;
455        }
456        if (element.type() == Element.CHUNK) {
457                element = new Paragraph((Chunk)element);
458        }
459        else if (element.type() == Element.PHRASE) {
460                element = new Paragraph((Phrase)element);
461        }
462        if (element.type() != Element.PARAGRAPH && element.type() != Element.LIST && element.type() != Element.PTABLE && element.type() != Element.YMARK)
463            throw new IllegalArgumentException(MessageLocalization.getComposedMessage("element.not.allowed"));
464        if (!composite) {
465            composite = true;
466            compositeElements = new LinkedList<Element>();
467            bidiLine = null;
468            waitPhrase = null;
469        }
470        compositeElements.add(element);
471    }
472
473    /**
474     * Converts a sequence of lines representing one of the column bounds into
475     * an internal format.
476     * <p>
477     * Each array element will contain a <CODE>float[4]</CODE> representing
478     * the line x = ax + b.
479     *
480     * @param cLine the column array
481     * @return the converted array
482     */
483    protected ArrayList<float []> convertColumn(final float cLine[]) {
484        if (cLine.length < 4)
485            throw new RuntimeException(MessageLocalization.getComposedMessage("no.valid.column.line.found"));
486        ArrayList<float []> cc = new ArrayList<float []>();
487        for (int k = 0; k < cLine.length - 2; k += 2) {
488            float x1 = cLine[k];
489            float y1 = cLine[k + 1];
490            float x2 = cLine[k + 2];
491            float y2 = cLine[k + 3];
492            if (y1 == y2)
493                continue;
494            // x = ay + b
495            float a = (x1 - x2) / (y1 - y2);
496            float b = x1 - a * y1;
497            float r[] = new float[4];
498            r[0] = Math.min(y1, y2);
499            r[1] = Math.max(y1, y2);
500            r[2] = a;
501            r[3] = b;
502            cc.add(r);
503            maxY = Math.max(maxY, r[1]);
504            minY = Math.min(minY, r[0]);
505        }
506        if (cc.isEmpty())
507            throw new RuntimeException(MessageLocalization.getComposedMessage("no.valid.column.line.found"));
508        return cc;
509    }
510
511    /**
512     * Finds the intersection between the <CODE>yLine</CODE> and the column. It will
513     * set the <CODE>lineStatus</CODE> appropriately.
514     *
515     * @param wall the column to intersect
516     * @return the x coordinate of the intersection
517     */
518    protected float findLimitsPoint(final ArrayList<float []> wall) {
519        lineStatus = LINE_STATUS_OK;
520        if (yLine < minY || yLine > maxY) {
521            lineStatus = LINE_STATUS_OFFLIMITS;
522            return 0;
523        }
524        for (int k = 0; k < wall.size(); ++k) {
525            float r[] = wall.get(k);
526            if (yLine < r[0] || yLine > r[1])
527                continue;
528            return r[2] * yLine + r[3];
529        }
530        lineStatus = LINE_STATUS_NOLINE;
531        return 0;
532    }
533
534    /**
535     * Finds the intersection between the <CODE>yLine</CODE> and the two
536     * column bounds. It will set the <CODE>lineStatus</CODE> appropriately.
537     *
538     * @return a <CODE>float[2]</CODE>with the x coordinates of the intersection
539     */
540    protected float[] findLimitsOneLine() {
541        float x1 = findLimitsPoint(leftWall);
542        if (lineStatus == LINE_STATUS_OFFLIMITS || lineStatus == LINE_STATUS_NOLINE)
543            return null;
544        float x2 = findLimitsPoint(rightWall);
545        if (lineStatus == LINE_STATUS_NOLINE)
546            return null;
547        return new float[]{x1, x2};
548    }
549
550    /**
551     * Finds the intersection between the <CODE>yLine</CODE>,
552     * the <CODE>yLine-leading</CODE>and the two column bounds.
553     * It will set the <CODE>lineStatus</CODE> appropriately.
554     *
555     * @return a <CODE>float[4]</CODE>with the x coordinates of the intersection
556     */
557    protected float[] findLimitsTwoLines() {
558        boolean repeat = false;
559        for (;;) {
560            if (repeat && currentLeading == 0)
561                return null;
562            repeat = true;
563            float x1[] = findLimitsOneLine();
564            if (lineStatus == LINE_STATUS_OFFLIMITS)
565                return null;
566            yLine -= currentLeading;
567            if (lineStatus == LINE_STATUS_NOLINE) {
568                continue;
569            }
570            float x2[] = findLimitsOneLine();
571            if (lineStatus == LINE_STATUS_OFFLIMITS)
572                return null;
573            if (lineStatus == LINE_STATUS_NOLINE) {
574                yLine -= currentLeading;
575                continue;
576            }
577            if (x1[0] >= x2[1] || x2[0] >= x1[1])
578                continue;
579            return new float[]{x1[0], x1[1], x2[0], x2[1]};
580        }
581    }
582
583    /**
584     * Sets the columns bounds. Each column bound is described by a
585     * <CODE>float[]</CODE> with the line points [x1,y1,x2,y2,...].
586     * The array must have at least 4 elements.
587     *
588     * @param leftLine the left column bound
589     * @param rightLine the right column bound
590     */
591    public void setColumns(final float leftLine[], final float rightLine[]) {
592        maxY = -10e20f;
593        minY = 10e20f;
594        setYLine(Math.max(leftLine[1], leftLine[leftLine.length - 1]));
595        rightWall = convertColumn(rightLine);
596        leftWall = convertColumn(leftLine);
597        rectangularWidth = -1;
598        rectangularMode = false;
599    }
600
601    /**
602     * Simplified method for rectangular columns.
603     *
604     * @param phrase a <CODE>Phrase</CODE>
605     * @param llx the lower left x corner
606     * @param lly the lower left y corner
607     * @param urx the upper right x corner
608     * @param ury the upper right y corner
609     * @param leading the leading
610     * @param alignment the column alignment
611     */
612    public void setSimpleColumn(final Phrase phrase, final float llx, final float lly, final float urx, final float ury, final float leading, final int alignment) {
613        addText(phrase);
614        setSimpleColumn(llx, lly, urx, ury, leading, alignment);
615    }
616
617    /**
618     * Simplified method for rectangular columns.
619     *
620     * @param llx the lower left x corner
621     * @param lly the lower left y corner
622     * @param urx the upper right x corner
623     * @param ury the upper right y corner
624     * @param leading the leading
625     * @param alignment the column alignment
626     */
627    public void setSimpleColumn(final float llx, final float lly, final float urx, final float ury, final float leading, final int alignment) {
628        setLeading(leading);
629        this.alignment = alignment;
630        setSimpleColumn(llx, lly, urx, ury);
631    }
632
633    /**
634     * Simplified method for rectangular columns.
635     *
636     * @param llx
637     * @param lly
638     * @param urx
639     * @param ury
640     */
641    public void setSimpleColumn(final float llx, final float lly, final float urx, final float ury) {
642        leftX = Math.min(llx, urx);
643        maxY = Math.max(lly, ury);
644        minY = Math.min(lly, ury);
645        rightX = Math.max(llx, urx);
646        yLine = maxY;
647        rectangularWidth = rightX - leftX;
648        if (rectangularWidth < 0)
649            rectangularWidth = 0;
650        rectangularMode = true;
651    }
652
653    /**
654     * Sets the leading to fixed.
655     *
656     * @param leading the leading
657     */
658    public void setLeading(final float leading) {
659        fixedLeading = leading;
660        multipliedLeading = 0;
661    }
662
663    /**
664     * Sets the leading fixed and variable. The resultant leading will be
665     * fixedLeading+multipliedLeading*maxFontSize where maxFontSize is the
666     * size of the biggest font in the line.
667     *
668     * @param fixedLeading the fixed leading
669     * @param multipliedLeading the variable leading
670     */
671    public void setLeading(final float fixedLeading, final float multipliedLeading) {
672        this.fixedLeading = fixedLeading;
673        this.multipliedLeading = multipliedLeading;
674    }
675
676    /**
677     * Gets the fixed leading.
678     *
679     * @return the leading
680     */
681    public float getLeading() {
682        return fixedLeading;
683    }
684
685    /**
686     * Gets the variable leading.
687     *
688     * @return the leading
689     */
690    public float getMultipliedLeading() {
691        return multipliedLeading;
692    }
693
694    /**
695     * Sets the yLine. The line will be written to yLine-leading.
696     *
697     * @param yLine the yLine
698     */
699    public void setYLine(final float yLine) {
700        this.yLine = yLine;
701    }
702
703    /**
704     * Gets the yLine.
705     *
706     * @return the yLine
707     */
708    public float getYLine() {
709        return yLine;
710    }
711
712    /**
713     * Sets the alignment.
714     *
715     * @param alignment the alignment
716     */
717    public void setAlignment(final int alignment) {
718        this.alignment = alignment;
719    }
720
721    /**
722     * Gets the alignment.
723     *
724     * @return the alignment
725     */
726    public int getAlignment() {
727        return alignment;
728    }
729
730    /**
731     * Sets the first paragraph line indent.
732     *
733     * @param indent the indent
734     */
735    public void setIndent(final float indent) {
736        this.indent = indent;
737        lastWasNewline = true;
738    }
739
740    /**
741     * Gets the first paragraph line indent.
742     *
743     * @return the indent
744     */
745    public float getIndent() {
746        return indent;
747    }
748
749    /**
750     * Sets the following paragraph lines indent.
751     *
752     * @param indent the indent
753     */
754    public void setFollowingIndent(final float indent) {
755        this.followingIndent = indent;
756        lastWasNewline = true;
757    }
758
759    /**
760     * Gets the following paragraph lines indent.
761     *
762     * @return the indent
763     */
764    public float getFollowingIndent() {
765        return followingIndent;
766    }
767
768    /**
769     * Sets the right paragraph lines indent.
770     *
771     * @param indent the indent
772     */
773    public void setRightIndent(final float indent) {
774        this.rightIndent = indent;
775        lastWasNewline = true;
776    }
777
778    /**
779     * Gets the right paragraph lines indent.
780     *
781     * @return the indent
782     */
783    public float getRightIndent() {
784        return rightIndent;
785    }
786
787    /**
788     * Outputs the lines to the document. It is equivalent to <CODE>go(false)</CODE>.
789     *
790     * @return returns the result of the operation. It can be <CODE>NO_MORE_TEXT</CODE>
791     * and/or <CODE>NO_MORE_COLUMN</CODE>
792     * @throws DocumentException on error
793     */
794    public int go() throws DocumentException {
795        return go(false);
796    }
797
798    /**
799     * Outputs the lines to the document. The output can be simulated.
800     * @param simulate <CODE>true</CODE> to simulate the writing to the document
801     * @return returns the result of the operation. It can be <CODE>NO_MORE_TEXT</CODE>
802     * and/or <CODE>NO_MORE_COLUMN</CODE>
803     * @throws DocumentException on error
804     */
805    public int go(final boolean simulate) throws DocumentException {
806        if (composite)
807            return goComposite(simulate);
808        addWaitingPhrase();
809        if (bidiLine == null)
810            return NO_MORE_TEXT;
811        descender = 0;
812        linesWritten = 0;
813        lastX = 0;
814        boolean dirty = false;
815        float ratio = spaceCharRatio;
816        Object currentValues[] = new Object[2];
817        PdfFont currentFont = null;
818        Float lastBaseFactor = new Float(0);
819        currentValues[1] = lastBaseFactor;
820        PdfDocument pdf = null;
821        PdfContentByte graphics = null;
822        PdfContentByte text = null;
823        firstLineY = Float.NaN;
824        int localRunDirection = PdfWriter.RUN_DIRECTION_NO_BIDI;
825        if (runDirection != PdfWriter.RUN_DIRECTION_DEFAULT)
826            localRunDirection = runDirection;
827        if (canvas != null) {
828            graphics = canvas;
829            pdf = canvas.getPdfDocument();
830            text = canvas.getDuplicate();
831        }
832        else if (!simulate)
833            throw new NullPointerException(MessageLocalization.getComposedMessage("columntext.go.with.simulate.eq.eq.false.and.text.eq.eq.null"));
834        if (!simulate) {
835            if (ratio == GLOBAL_SPACE_CHAR_RATIO)
836                ratio = text.getPdfWriter().getSpaceCharRatio();
837            else if (ratio < 0.001f)
838                ratio = 0.001f;
839        }
840        if (!rectangularMode) {
841                float max = 0;
842                for (PdfChunk c : bidiLine.chunks) {
843                        max = Math.max(max, c.font.size());
844                }
845                currentLeading = fixedLeading + max * multipliedLeading;
846        }
847        float firstIndent = 0;
848        PdfLine line;
849        float x1;
850        int status = 0;
851        while(true) {
852                firstIndent = lastWasNewline ? indent : followingIndent; //
853                if (rectangularMode) {
854                        if (rectangularWidth <= firstIndent + rightIndent) {
855                                status = NO_MORE_COLUMN;
856                                if (bidiLine.isEmpty())
857                                        status |= NO_MORE_TEXT;
858                                break;
859                        }
860                        if (bidiLine.isEmpty()) {
861                                status = NO_MORE_TEXT;
862                                break;
863                        }
864                line = bidiLine.processLine(leftX, rectangularWidth - firstIndent - rightIndent, alignment, localRunDirection, arabicOptions);
865                if (line == null) {
866                        status = NO_MORE_TEXT;
867                        break;
868                }
869                float[] maxSize = line.getMaxSize();
870                if (isUseAscender() && Float.isNaN(firstLineY))
871                        currentLeading = line.getAscender();
872                else
873                        currentLeading = Math.max(fixedLeading + maxSize[0] * multipliedLeading, maxSize[1]);
874                if (yLine > maxY || yLine - currentLeading < minY ) {
875                        status = NO_MORE_COLUMN;
876                        bidiLine.restore();
877                        break;
878                }
879                yLine -= currentLeading;
880                if (!simulate && !dirty) {
881                        text.beginText();
882                        dirty = true;
883                }
884                if (Float.isNaN(firstLineY))
885                        firstLineY = yLine;
886                updateFilledWidth(rectangularWidth - line.widthLeft());
887                x1 = leftX;
888                }
889            else {
890                float yTemp = yLine - currentLeading;
891                float xx[] = findLimitsTwoLines();
892                if (xx == null) {
893                        status = NO_MORE_COLUMN;
894                        if (bidiLine.isEmpty())
895                                status |= NO_MORE_TEXT;
896                        yLine = yTemp;
897                        break;
898                }
899                if (bidiLine.isEmpty()) {
900                        status = NO_MORE_TEXT;
901                        yLine = yTemp;
902                        break;
903                }
904                x1 = Math.max(xx[0], xx[2]);
905                float x2 = Math.min(xx[1], xx[3]);
906                if (x2 - x1 <= firstIndent + rightIndent)
907                    continue;
908                if (!simulate && !dirty) {
909                    text.beginText();
910                    dirty = true;
911                }
912                line = bidiLine.processLine(x1, x2 - x1 - firstIndent - rightIndent, alignment, localRunDirection, arabicOptions);
913                if (line == null) {
914                    status = NO_MORE_TEXT;
915                    yLine = yTemp;
916                    break;
917                }
918            }
919            if (!simulate) {
920                currentValues[0] = currentFont;
921                text.setTextMatrix(x1 + (line.isRTL() ? rightIndent : firstIndent) + line.indentLeft(), yLine);
922                lastX = pdf.writeLineToContent(line, text, graphics, currentValues, ratio);
923                currentFont = (PdfFont)currentValues[0];
924            }
925            lastWasNewline = line.isNewlineSplit();
926            yLine -= line.isNewlineSplit() ? extraParagraphSpace : 0;
927            ++linesWritten;
928            descender = line.getDescender();
929        }
930        if (dirty) {
931            text.endText();
932            canvas.add(text);
933        }
934        return status;
935    }
936
937    /**
938     * Sets the extra space between paragraphs.
939     *
940     * @return the extra space between paragraphs
941     */
942    public float getExtraParagraphSpace() {
943        return extraParagraphSpace;
944    }
945
946    /**
947     * Sets the extra space between paragraphs.
948     *
949     * @param extraParagraphSpace the extra space between paragraphs
950     */
951    public void setExtraParagraphSpace(final float extraParagraphSpace) {
952        this.extraParagraphSpace = extraParagraphSpace;
953    }
954
955    /**
956     * Clears the chunk array.
957     * A call to <CODE>go()</CODE> will always return NO_MORE_TEXT.
958     */
959    public void clearChunks() {
960        if (bidiLine != null)
961            bidiLine.clearChunks();
962    }
963
964    /**
965     * Gets the space/character extra spacing ratio for fully justified text.
966     *
967     * @return the space/character extra spacing ratio
968     */
969    public float getSpaceCharRatio() {
970        return spaceCharRatio;
971    }
972
973    /**
974     * Sets the ratio between the extra word spacing and the extra character
975     * spacing when the text is fully justified.
976     * Extra word spacing will grow <CODE>spaceCharRatio</CODE> times more
977     * than extra character spacing.
978     * If the ratio is <CODE>PdfWriter.NO_SPACE_CHAR_RATIO</CODE> then the
979     * extra character spacing will be zero.
980     *
981     * @param spaceCharRatio the ratio between the extra word spacing and the extra character spacing
982     */
983    public void setSpaceCharRatio(final float spaceCharRatio) {
984        this.spaceCharRatio = spaceCharRatio;
985    }
986
987    /**
988     * Sets the run direction.
989     *
990     * @param runDirection the run direction
991     */
992    public void setRunDirection(final int runDirection) {
993        if (runDirection < PdfWriter.RUN_DIRECTION_DEFAULT || runDirection > PdfWriter.RUN_DIRECTION_RTL)
994            throw new RuntimeException(MessageLocalization.getComposedMessage("invalid.run.direction.1", runDirection));
995        this.runDirection = runDirection;
996    }
997
998    /**
999     * Gets the run direction.
1000     *
1001     * @return the run direction
1002     */
1003    public int getRunDirection() {
1004        return runDirection;
1005    }
1006
1007    /**
1008     * Gets the number of lines written.
1009     *
1010     * @return the number of lines written
1011     */
1012    public int getLinesWritten() {
1013        return this.linesWritten;
1014    }
1015
1016    /**
1017     * Gets the X position of the end of the last line that has been written
1018     * (will not work in simulation mode!).
1019     * @since 5.0.3
1020     */
1021    public float getLastX() {
1022        return lastX;
1023    }
1024
1025    /**
1026     * Gets the arabic shaping options.
1027     *
1028     * @return the arabic shaping options
1029     */
1030    public int getArabicOptions() {
1031        return this.arabicOptions;
1032    }
1033
1034    /**
1035     * Sets the arabic shaping options. The option can be AR_NOVOWEL,
1036     * AR_COMPOSEDTASHKEEL and AR_LIG.
1037     *
1038     * @param arabicOptions the arabic shaping options
1039     */
1040    public void setArabicOptions(final int arabicOptions) {
1041        this.arabicOptions = arabicOptions;
1042    }
1043
1044    /**
1045     * Gets the biggest descender value of the last line written.
1046     *
1047     * @return the biggest descender value of the last line written
1048     */
1049    public float getDescender() {
1050        return descender;
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     * @param runDirection the run direction
1059     * @param arabicOptions the options for the arabic shaping
1060     * @return the width of the line
1061     */
1062    public static float getWidth(final Phrase phrase, final int runDirection, final int arabicOptions) {
1063        ColumnText ct = new ColumnText(null);
1064        ct.addText(phrase);
1065        ct.addWaitingPhrase();
1066        PdfLine line = ct.bidiLine.processLine(0, 20000, Element.ALIGN_LEFT, runDirection, arabicOptions);
1067        if (line == null)
1068            return 0;
1069        else
1070            return 20000 - line.widthLeft();
1071    }
1072
1073    /**
1074     * Gets the width that the line will occupy after writing.
1075     * Only the width of the first line is returned.
1076     *
1077     * @param phrase the <CODE>Phrase</CODE> containing the line
1078     * @return the width of the line
1079     */
1080    public static float getWidth(final Phrase phrase) {
1081        return getWidth(phrase, PdfWriter.RUN_DIRECTION_NO_BIDI, 0);
1082    }
1083
1084    /**
1085     * Shows a line of text. Only the first line is written.
1086     *
1087     * @param canvas where the text is to be written to
1088     * @param alignment the alignment. It is not influenced by the run direction
1089     * @param phrase the <CODE>Phrase</CODE> with the text
1090     * @param x the x reference position
1091     * @param y the y reference position
1092     * @param rotation the rotation to be applied in degrees counterclockwise
1093     * @param runDirection the run direction
1094     * @param arabicOptions the options for the arabic shaping
1095     */
1096    public static void showTextAligned(final PdfContentByte canvas, int alignment, final Phrase phrase, final float x, final float y, final float rotation, final int runDirection, final int arabicOptions) {
1097        if (alignment != Element.ALIGN_LEFT && alignment != Element.ALIGN_CENTER
1098            && alignment != Element.ALIGN_RIGHT)
1099            alignment = Element.ALIGN_LEFT;
1100        canvas.saveState();
1101        ColumnText ct = new ColumnText(canvas);
1102        float lly = -1;
1103        float ury = 2;
1104        float llx;
1105        float urx;
1106        switch (alignment) {
1107                case Element.ALIGN_LEFT:
1108                        llx = 0;
1109                        urx = 20000;
1110                        break;
1111                case Element.ALIGN_RIGHT:
1112                        llx = -20000;
1113                        urx = 0;
1114                        break;
1115                default:
1116                        llx = -20000;
1117                        urx = 20000;
1118                        break;
1119        }
1120        if (rotation == 0) {
1121                llx += x;
1122                lly += y;
1123                urx += x;
1124                ury += y;
1125        }
1126        else {
1127            double alpha = rotation * Math.PI / 180.0;
1128            float cos = (float)Math.cos(alpha);
1129            float sin = (float)Math.sin(alpha);
1130            canvas.concatCTM(cos, sin, -sin, cos, x, y);
1131        }
1132        ct.setSimpleColumn(phrase, llx, lly, urx, ury, 2, alignment);
1133        if (runDirection == PdfWriter.RUN_DIRECTION_RTL) {
1134            if (alignment == Element.ALIGN_LEFT)
1135                alignment = Element.ALIGN_RIGHT;
1136            else if (alignment == Element.ALIGN_RIGHT)
1137                alignment = Element.ALIGN_LEFT;
1138        }
1139        ct.setAlignment(alignment);
1140        ct.setArabicOptions(arabicOptions);
1141        ct.setRunDirection(runDirection);
1142        try {
1143            ct.go();
1144        }
1145        catch (DocumentException e) {
1146            throw new ExceptionConverter(e);
1147        }
1148        canvas.restoreState();
1149    }
1150
1151    /**
1152     * Shows a line of text. Only the first line is written.
1153     *
1154     * @param canvas where the text is to be written to
1155     * @param alignment the alignment
1156     * @param phrase the <CODE>Phrase</CODE> with the text
1157     * @param x the x reference position
1158     * @param y the y reference position
1159     * @param rotation the rotation to be applied in degrees counterclockwise
1160     */
1161    public static void showTextAligned(final PdfContentByte canvas, final int alignment, final Phrase phrase, final float x, final float y, final float rotation) {
1162        showTextAligned(canvas, alignment, phrase, x, y, rotation, PdfWriter.RUN_DIRECTION_NO_BIDI, 0);
1163    }
1164
1165    protected int goComposite(final boolean simulate) throws DocumentException {
1166        if (!rectangularMode)
1167            throw new DocumentException(MessageLocalization.getComposedMessage("irregular.columns.are.not.supported.in.composite.mode"));
1168        linesWritten = 0;
1169        descender = 0;
1170        boolean firstPass = true;
1171        main_loop:
1172        while (true) {
1173            if (compositeElements.isEmpty())
1174                return NO_MORE_TEXT;
1175            Element element = compositeElements.getFirst();
1176            if (element.type() == Element.PARAGRAPH) {
1177                Paragraph para = (Paragraph)element;
1178                int status = 0;
1179                for (int keep = 0; keep < 2; ++keep) {
1180                    float lastY = yLine;
1181                    boolean createHere = false;
1182                    if (compositeColumn == null) {
1183                        compositeColumn = new ColumnText(canvas);
1184                        compositeColumn.setAlignment(para.getAlignment());
1185                        compositeColumn.setIndent(para.getIndentationLeft() + para.getFirstLineIndent());
1186                        compositeColumn.setExtraParagraphSpace(para.getExtraParagraphSpace());
1187                        compositeColumn.setFollowingIndent(para.getIndentationLeft());
1188                        compositeColumn.setRightIndent(para.getIndentationRight());
1189                        compositeColumn.setLeading(para.getLeading(), para.getMultipliedLeading());
1190                        compositeColumn.setRunDirection(runDirection);
1191                        compositeColumn.setArabicOptions(arabicOptions);
1192                        compositeColumn.setSpaceCharRatio(spaceCharRatio);
1193                        compositeColumn.addText(para);
1194                        if (!(firstPass && adjustFirstLine)) {
1195                            yLine -= para.getSpacingBefore();
1196                        }
1197                        createHere = true;
1198                    }
1199                    compositeColumn.setUseAscender(firstPass && adjustFirstLine ? useAscender : false);
1200                    compositeColumn.leftX = leftX;
1201                    compositeColumn.rightX = rightX;
1202                    compositeColumn.yLine = yLine;
1203                    compositeColumn.rectangularWidth = rectangularWidth;
1204                    compositeColumn.rectangularMode = rectangularMode;
1205                    compositeColumn.minY = minY;
1206                    compositeColumn.maxY = maxY;
1207                    boolean keepCandidate = para.getKeepTogether() && createHere && !(firstPass && adjustFirstLine);
1208                    status = compositeColumn.go(simulate || keepCandidate && keep == 0);
1209                    lastX = compositeColumn.getLastX();
1210                    updateFilledWidth(compositeColumn.filledWidth);
1211                    if ((status & NO_MORE_TEXT) == 0 && keepCandidate) {
1212                        compositeColumn = null;
1213                        yLine = lastY;
1214                        return NO_MORE_COLUMN;
1215                    }
1216                    if (simulate || !keepCandidate)
1217                        break;
1218                    if (keep == 0) {
1219                        compositeColumn = null;
1220                        yLine = lastY;
1221                    }
1222                }
1223                firstPass = false;
1224                yLine = compositeColumn.yLine;
1225                linesWritten += compositeColumn.linesWritten;
1226                descender = compositeColumn.descender;
1227                if ((status & NO_MORE_TEXT) != 0) {
1228                    compositeColumn = null;
1229                    compositeElements.removeFirst();
1230                    yLine -= para.getSpacingAfter();
1231                }
1232                if ((status & NO_MORE_COLUMN) != 0) {
1233                    return NO_MORE_COLUMN;
1234                }
1235            }
1236            else if (element.type() == Element.LIST) {
1237                com.itextpdf.text.List list = (com.itextpdf.text.List)element;
1238                ArrayList<Element> items = list.getItems();
1239                ListItem item = null;
1240                float listIndentation = list.getIndentationLeft();
1241                int count = 0;
1242                Stack<Object[]> stack = new Stack<Object[]>();
1243                for (int k = 0; k < items.size(); ++k) {
1244                    Object obj = items.get(k);
1245                    if (obj instanceof ListItem) {
1246                        if (count == listIdx) {
1247                            item = (ListItem)obj;
1248                            break;
1249                        }
1250                        else ++count;
1251                    }
1252                    else if (obj instanceof com.itextpdf.text.List) {
1253                        stack.push(new Object[]{list, Integer.valueOf(k), new Float(listIndentation)});
1254                        list = (com.itextpdf.text.List)obj;
1255                        items = list.getItems();
1256                        listIndentation += list.getIndentationLeft();
1257                        k = -1;
1258                        continue;
1259                    }
1260                    if (k == items.size() - 1) {
1261                        if (!stack.isEmpty()) {
1262                            Object objs[] = stack.pop();
1263                            list = (com.itextpdf.text.List)objs[0];
1264                            items = list.getItems();
1265                            k = ((Integer)objs[1]).intValue();
1266                            listIndentation = ((Float)objs[2]).floatValue();
1267                        }
1268                    }
1269                }
1270                int status = 0;
1271                for (int keep = 0; keep < 2; ++keep) {
1272                    float lastY = yLine;
1273                    boolean createHere = false;
1274                    if (compositeColumn == null) {
1275                        if (item == null) {
1276                            listIdx = 0;
1277                            compositeElements.removeFirst();
1278                            continue main_loop;
1279                        }
1280                        compositeColumn = new ColumnText(canvas);
1281                        compositeColumn.setUseAscender(firstPass && adjustFirstLine ? useAscender : false);
1282                        compositeColumn.setAlignment(item.getAlignment());
1283                        compositeColumn.setIndent(item.getIndentationLeft() + listIndentation + item.getFirstLineIndent());
1284                        compositeColumn.setExtraParagraphSpace(item.getExtraParagraphSpace());
1285                        compositeColumn.setFollowingIndent(compositeColumn.getIndent());
1286                        compositeColumn.setRightIndent(item.getIndentationRight() + list.getIndentationRight());
1287                        compositeColumn.setLeading(item.getLeading(), item.getMultipliedLeading());
1288                        compositeColumn.setRunDirection(runDirection);
1289                        compositeColumn.setArabicOptions(arabicOptions);
1290                        compositeColumn.setSpaceCharRatio(spaceCharRatio);
1291                        compositeColumn.addText(item);
1292                        if (!(firstPass && adjustFirstLine)) {
1293                            yLine -= item.getSpacingBefore();
1294                        }
1295                        createHere = true;
1296                    }
1297                    compositeColumn.leftX = leftX;
1298                    compositeColumn.rightX = rightX;
1299                    compositeColumn.yLine = yLine;
1300                    compositeColumn.rectangularWidth = rectangularWidth;
1301                    compositeColumn.rectangularMode = rectangularMode;
1302                    compositeColumn.minY = minY;
1303                    compositeColumn.maxY = maxY;
1304                    boolean keepCandidate = item.getKeepTogether() && createHere && !(firstPass && adjustFirstLine);
1305                    status = compositeColumn.go(simulate || keepCandidate && keep == 0);
1306                    lastX = compositeColumn.getLastX();
1307                    updateFilledWidth(compositeColumn.filledWidth);
1308                    if ((status & NO_MORE_TEXT) == 0 && keepCandidate) {
1309                        compositeColumn = null;
1310                        yLine = lastY;
1311                        return NO_MORE_COLUMN;
1312                    }
1313                    if (simulate || !keepCandidate)
1314                        break;
1315                    if (keep == 0) {
1316                        compositeColumn = null;
1317                        yLine = lastY;
1318                    }
1319                }
1320                firstPass = false;
1321                yLine = compositeColumn.yLine;
1322                linesWritten += compositeColumn.linesWritten;
1323                descender = compositeColumn.descender;
1324                if (!Float.isNaN(compositeColumn.firstLineY) && !compositeColumn.firstLineYDone) {
1325                    if (!simulate)
1326                        showTextAligned(canvas, Element.ALIGN_LEFT, new Phrase(item.getListSymbol()), compositeColumn.leftX + listIndentation, compositeColumn.firstLineY, 0);
1327                    compositeColumn.firstLineYDone = true;
1328                }
1329                if ((status & NO_MORE_TEXT) != 0) {
1330                    compositeColumn = null;
1331                    ++listIdx;
1332                    yLine -= item.getSpacingAfter();
1333                }
1334                if ((status & NO_MORE_COLUMN) != 0)
1335                    return NO_MORE_COLUMN;
1336            }
1337            else if (element.type() == Element.PTABLE) {
1338
1339                // INITIALISATIONS
1340
1341                // get the PdfPTable element
1342                PdfPTable table = (PdfPTable)element;
1343
1344                // tables without a body are dismissed
1345                if (table.size() <= table.getHeaderRows()) {
1346                    compositeElements.removeFirst();
1347                    continue;
1348                }
1349
1350                // Y-offset
1351                float yTemp = yLine;
1352                if (rowIdx == 0 && adjustFirstLine)
1353                    yTemp -= table.spacingBefore();
1354
1355                // if there's no space left, ask for new column
1356                if (yTemp < minY || yTemp > maxY)
1357                    return NO_MORE_COLUMN;
1358
1359                // mark start of table
1360                float yLineWrite = yTemp;
1361                float x1 = leftX;
1362                currentLeading = 0;
1363                // get the width of the table
1364                float tableWidth;
1365                if (table.isLockedWidth()) {
1366                    tableWidth = table.getTotalWidth();
1367                    updateFilledWidth(tableWidth);
1368                }
1369                else {
1370                    tableWidth = rectangularWidth * table.getWidthPercentage() / 100f;
1371                    table.setTotalWidth(tableWidth);
1372                }
1373
1374                // HEADERS / FOOTERS
1375
1376                // how many header rows are real header rows; how many are footer rows?
1377                table.normalizeHeadersFooters();
1378                int headerRows = table.getHeaderRows();
1379                int footerRows = table.getFooterRows();
1380                int realHeaderRows = headerRows - footerRows;
1381                float headerHeight = table.getHeaderHeight();
1382                float footerHeight = table.getFooterHeight();
1383
1384                // do we need to skip the header?
1385                boolean skipHeader = table.isSkipFirstHeader() && rowIdx <= realHeaderRows;
1386                // if not, we wan't to be able to add more than just a header and a footer
1387                if (!skipHeader) {
1388                    yTemp -= headerHeight;
1389                    if (yTemp < minY || yTemp > maxY) {
1390                        return NO_MORE_COLUMN;
1391                    }
1392                }
1393
1394                // MEASURE NECESSARY SPACE
1395
1396                // how many real rows (not header or footer rows) fit on a page?
1397                int k = 0;
1398                if (rowIdx < headerRows)
1399                    rowIdx = headerRows;
1400                // if the table isn't complete, we need to be able to add a footer
1401                if (!table.isComplete())
1402                        yTemp -= footerHeight;
1403                // k will be the first row that doesn't fit
1404                for (k = rowIdx; k < table.size(); ++k) {
1405                    float rowHeight = table.getRowHeight(k);
1406                    if (yTemp - rowHeight < minY)
1407                        break;
1408                    yTemp -= rowHeight;
1409                }
1410                // only for incomplete tables:
1411                if (!table.isComplete())
1412                        yTemp += footerHeight;
1413
1414                // IF ROWS MAY NOT BE SPLIT
1415                if (!table.isSplitRows()) {
1416                                splittedRow = -1;
1417                        if (k == rowIdx) {
1418                                // drop the whole table
1419                                if (k == table.size()) {
1420                                        compositeElements.removeFirst();
1421                                        continue;
1422                                }
1423                                // or drop the row
1424                                else {
1425                                        table.getRows().remove(k);
1426                                        return NO_MORE_COLUMN;
1427                                }
1428                        }
1429                }
1430                // IF ROWS SHOULD NOT BE SPLIT
1431                else if (table.isSplitLate() && !table.hasRowspan(k) && rowIdx < k) {
1432                        splittedRow = -1;
1433                }
1434                // SPLIT ROWS (IF WANTED AND NECESSARY)
1435                else if (k < table.size()) {
1436                        // if the row hasn't been split before, we duplicate (part of) the table
1437                        if (k != splittedRow) {
1438                                splittedRow = k + 1;
1439                                table = new PdfPTable(table);
1440                                compositeElements.set(0, table);
1441                                ArrayList<PdfPRow> rows = table.getRows();
1442                                for (int i = headerRows; i < rowIdx; ++i)
1443                                        rows.set(i, null);
1444                        }
1445                        // we calculate the remaining vertical space
1446                    float h = yTemp - minY;
1447                    // we create a new row with the remaining content
1448                    PdfPRow newRow = table.getRow(k).splitRow(table, k, h);
1449                    // if the row isn't null add it as an extra row
1450                    if (newRow == null) {
1451                        splittedRow = -1;
1452                        if (rowIdx == k)
1453                                return NO_MORE_COLUMN;
1454                    }
1455                    else {
1456                        yTemp = minY;
1457                        table.getRows().add(++k, newRow);
1458                    }
1459                }
1460                // We're no longer in the first pass
1461                firstPass = false;
1462
1463                // if not in simulation mode, draw the table
1464                if (!simulate) {
1465                        // set the alignment
1466                    switch (table.getHorizontalAlignment()) {
1467                        case Element.ALIGN_LEFT:
1468                            break;
1469                        case Element.ALIGN_RIGHT:
1470                            x1 += rectangularWidth - tableWidth;
1471                            break;
1472                        default:
1473                            x1 += (rectangularWidth - tableWidth) / 2f;
1474                    }
1475                    // copy the rows that fit on the page in a new table nt
1476                    PdfPTable nt = PdfPTable.shallowCopy(table);
1477                    ArrayList<PdfPRow> sub = nt.getRows();
1478                    // first we add the real header rows (if necessary)
1479                    if (!skipHeader && realHeaderRows > 0) {
1480                        sub.addAll(table.getRows(0, realHeaderRows));
1481                    }
1482                    else
1483                        nt.setHeaderRows(footerRows);
1484                    // then we add the real content
1485                    sub.addAll(table.getRows(rowIdx, k));
1486                    // do we need to show a footer?
1487                    boolean showFooter = !table.isSkipLastFooter();
1488                    boolean newPageFollows = false;
1489                    if (k < table.size()) {
1490                        nt.setComplete(true);
1491                        showFooter = true;
1492                        newPageFollows = true;
1493                    }
1494                    // we add the footer rows if necessary (not for incomplete tables)
1495                    if (footerRows > 0 && nt.isComplete() && showFooter) {
1496                        sub.addAll(table.getRows(realHeaderRows, realHeaderRows + footerRows));
1497                    }
1498                    else {
1499                        footerRows = 0;
1500                    }
1501
1502                    // we need a correction if the last row needs to be extended
1503                    float rowHeight = 0;
1504                    int lastIdx = sub.size() - 1 - footerRows;
1505                    PdfPRow last = sub.get(lastIdx);
1506                    if (table.isExtendLastRow(newPageFollows)) {
1507                        rowHeight = last.getMaxHeights();
1508                        last.setMaxHeights(yTemp - minY + rowHeight);
1509                        yTemp = minY;
1510                    }
1511
1512                    // newPageFollows indicates that this table is being split
1513                    if (newPageFollows) {
1514                        PdfPTableEvent tableEvent = table.getTableEvent();
1515                        if (tableEvent instanceof PdfPTableEventSplit) {
1516                            ((PdfPTableEventSplit)tableEvent).splitTable(table);
1517                        }
1518                    }
1519
1520                    // now we render the rows of the new table
1521                    if (canvases != null)
1522                        nt.writeSelectedRows(0, -1, 0, -1, x1, yLineWrite, canvases, false);
1523                    else
1524                        nt.writeSelectedRows(0, -1, 0, -1, x1, yLineWrite, canvas, false);
1525                    // if the row was split, we copy the content of the last row
1526                    // that was consumed into the first row shown on the next page
1527                    if (splittedRow == k && k < table.size()) {
1528                        PdfPRow splitted = table.getRows().get(k);
1529                        splitted.copyRowContent(nt, lastIdx);
1530                    }
1531                    // reset the row height of the last row
1532                    if (table.isExtendLastRow(newPageFollows)) {
1533                        last.setMaxHeights(rowHeight);
1534                    }
1535                }
1536                // in simulation mode, we need to take extendLastRow into account
1537                else if (table.isExtendLastRow() && minY > PdfPRow.BOTTOM_LIMIT) {
1538                    yTemp = minY;
1539                }
1540
1541                yLine = yTemp;
1542                if (!(skipHeader || table.isComplete()))
1543                        yLine += footerHeight;
1544                if (k >= table.size()) {
1545                        // Use up space no more than left
1546                        if(yLine - table.spacingAfter() < minY) {
1547                                yLine = minY;
1548                        }
1549                        else {
1550                                yLine -= table.spacingAfter();
1551                        }
1552                    compositeElements.removeFirst();
1553                    splittedRow = -1;
1554                    rowIdx = 0;
1555                }
1556                else {
1557                    if (splittedRow != -1) {
1558                        ArrayList<PdfPRow> rows = table.getRows();
1559                        for (int i = rowIdx; i < k; ++i)
1560                            rows.set(i, null);
1561                    }
1562                    rowIdx = k;
1563                    return NO_MORE_COLUMN;
1564                }
1565            }
1566            else if (element.type() == Element.YMARK) {
1567                if (!simulate) {
1568                    DrawInterface zh = (DrawInterface)element;
1569                    zh.draw(canvas, leftX, minY, rightX, maxY, yLine);
1570                }
1571                compositeElements.removeFirst();
1572            }
1573            else
1574                compositeElements.removeFirst();
1575        }
1576    }
1577
1578    /**
1579     * Gets the canvas.
1580     * If a set of four canvases exists, the TEXTCANVAS is returned.
1581     *
1582     * @return a PdfContentByte.
1583     */
1584    public PdfContentByte getCanvas() {
1585        return canvas;
1586    }
1587
1588    /**
1589     * Sets the canvas.
1590     * If before a set of four canvases was set, it is being unset.
1591     *
1592     * @param canvas
1593     */
1594    public void setCanvas(final PdfContentByte canvas) {
1595        this.canvas = canvas;
1596        this.canvases = null;
1597        if (compositeColumn != null)
1598            compositeColumn.setCanvas(canvas);
1599    }
1600
1601    /**
1602     * Sets the canvases.
1603     *
1604     * @param canvases
1605     */
1606    public void setCanvases(final PdfContentByte[] canvases) {
1607        this.canvases = canvases;
1608        this.canvas = canvases[PdfPTable.TEXTCANVAS];
1609        if (compositeColumn != null)
1610            compositeColumn.setCanvases(canvases);
1611    }
1612
1613    /**
1614     * Gets the canvases.
1615     *
1616     * @return an array of PdfContentByte
1617     */
1618    public PdfContentByte[] getCanvases() {
1619        return canvases;
1620    }
1621
1622    /**
1623     * Checks if the element has a height of 0.
1624     *
1625     * @return true or false
1626     * @since 2.1.2
1627     */
1628    public boolean zeroHeightElement() {
1629        return composite && !compositeElements.isEmpty() && compositeElements.getFirst().type() == Element.YMARK;
1630    }
1631
1632    /**
1633     * Checks if UseAscender is enabled/disabled.
1634     *
1635     * @return true is the adjustment of the first line height is based on max ascender.
1636     */
1637    public boolean isUseAscender() {
1638        return useAscender;
1639    }
1640
1641    /**
1642     * Enables/Disables adjustment of first line height based on max ascender.
1643     *
1644     * @param useAscender       enable adjustment if true
1645     */
1646    public void setUseAscender(final boolean useAscender) {
1647        this.useAscender = useAscender;
1648    }
1649
1650    /**
1651     * Checks the status variable and looks if there's still some text.
1652     */
1653    public static boolean hasMoreText(final int status) {
1654        return (status & ColumnText.NO_MORE_TEXT) == 0;
1655    }
1656
1657    /**
1658     * Gets the real width used by the largest line.
1659     *
1660     * @return the real width used by the largest line
1661     */
1662    public float getFilledWidth() {
1663        return filledWidth;
1664    }
1665
1666    /**
1667     * Sets the real width used by the largest line.
1668     * Only used to set it to zero to start another measurement.
1669     *
1670     * @param filledWidth the real width used by the largest line
1671     */
1672    public void setFilledWidth(final float filledWidth) {
1673        this.filledWidth = filledWidth;
1674    }
1675
1676    /**
1677     * Replaces the <CODE>filledWidth</CODE> if greater than the existing one.
1678     *
1679     * @param w the new <CODE>filledWidth</CODE> if greater than the existing one
1680     */
1681    public void updateFilledWidth(final float w) {
1682        if (w > filledWidth)
1683            filledWidth = w;
1684    }
1685
1686
1687    /**
1688     * Gets the first line adjustment property.
1689     *
1690     * @return the first line adjustment property.
1691     */
1692    public boolean isAdjustFirstLine() {
1693        return adjustFirstLine;
1694    }
1695
1696    /**
1697     * Sets the first line adjustment.
1698     * Some objects have properties, like spacing before, that behave
1699     * differently if the object is the first to be written after go() or not.
1700     * The first line adjustment is <CODE>true</CODE> by default but can be
1701     * changed if several objects are to be placed one after the other in the
1702     * same column calling go() several times.
1703     *
1704     * @param adjustFirstLine <CODE>true</CODE> to adjust the first line, <CODE>false</CODE> otherwise
1705     */
1706    public void setAdjustFirstLine(final boolean adjustFirstLine) {
1707        this.adjustFirstLine = adjustFirstLine;
1708    }
1709}