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