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}