001/* 002 * $Id: PdfLine.java 4897 2011-06-07 15:09:25Z blowagie $ 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; 045 046import java.util.ArrayList; 047import java.util.Iterator; 048 049import com.itextpdf.text.Chunk; 050import com.itextpdf.text.Element; 051import com.itextpdf.text.Image; 052import com.itextpdf.text.ListItem; 053 054/** 055 * <CODE>PdfLine</CODE> defines an array with <CODE>PdfChunk</CODE>-objects 056 * that fit into 1 line. 057 */ 058 059public class PdfLine { 060 061 // membervariables 062 063 /** The arraylist containing the chunks. */ 064 protected ArrayList<PdfChunk> line; 065 066 /** The left indentation of the line. */ 067 protected float left; 068 069 /** The width of the line. */ 070 protected float width; 071 072 /** The alignment of the line. */ 073 protected int alignment; 074 075 /** The height of the line. */ 076 protected float height; 077 078 /** The listsymbol (if necessary). */ 079 protected Chunk listSymbol = null; 080 081 /** The listsymbol (if necessary). */ 082 protected float symbolIndent; 083 084 /** <CODE>true</CODE> if the chunk splitting was caused by a newline. */ 085 protected boolean newlineSplit = false; 086 087 /** The original width. */ 088 protected float originalWidth; 089 090 protected boolean isRTL = false; 091 092 // constructors 093 094 /** 095 * Constructs a new <CODE>PdfLine</CODE>-object. 096 * 097 * @param left the limit of the line at the left 098 * @param right the limit of the line at the right 099 * @param alignment the alignment of the line 100 * @param height the height of the line 101 */ 102 103 PdfLine(float left, float right, int alignment, float height) { 104 this.left = left; 105 this.width = right - left; 106 this.originalWidth = this.width; 107 this.alignment = alignment; 108 this.height = height; 109 this.line = new ArrayList<PdfChunk>(); 110 } 111 112 /** 113 * Creates a PdfLine object. 114 * @param left the left offset 115 * @param originalWidth the original width of the line 116 * @param remainingWidth bigger than 0 if the line isn't completely filled 117 * @param alignment the alignment of the line 118 * @param newlineSplit was the line splitted (or does the paragraph end with this line) 119 * @param line an array of PdfChunk objects 120 * @param isRTL do you have to read the line from Right to Left? 121 */ 122 PdfLine(float left, float originalWidth, float remainingWidth, int alignment, boolean newlineSplit, ArrayList<PdfChunk> line, boolean isRTL) { 123 this.left = left; 124 this.originalWidth = originalWidth; 125 this.width = remainingWidth; 126 this.alignment = alignment; 127 this.line = line; 128 this.newlineSplit = newlineSplit; 129 this.isRTL = isRTL; 130 } 131 132 // methods 133 134 /** 135 * Adds a <CODE>PdfChunk</CODE> to the <CODE>PdfLine</CODE>. 136 * 137 * @param chunk the <CODE>PdfChunk</CODE> to add 138 * @return <CODE>null</CODE> if the chunk could be added completely; if not 139 * a <CODE>PdfChunk</CODE> containing the part of the chunk that could 140 * not be added is returned 141 */ 142 143 PdfChunk add(PdfChunk chunk) { 144 // nothing happens if the chunk is null. 145 if (chunk == null || chunk.toString().equals("")) { 146 return null; 147 } 148 149 // we split the chunk to be added 150 PdfChunk overflow = chunk.split(width); 151 newlineSplit = chunk.isNewlineSplit() || overflow == null; 152 if (chunk.isTab()) { 153 Object[] tab = (Object[])chunk.getAttribute(Chunk.TAB); 154 float tabPosition = ((Float)tab[1]).floatValue(); 155 boolean newline = ((Boolean)tab[2]).booleanValue(); 156 if (newline && tabPosition < originalWidth - width) { 157 return chunk; 158 } 159 width = originalWidth - tabPosition; 160 chunk.adjustLeft(left); 161 addToLine(chunk); 162 } 163 // if the length of the chunk > 0 we add it to the line 164 else if (chunk.length() > 0 || chunk.isImage()) { 165 if (overflow != null) 166 chunk.trimLastSpace(); 167 width -= chunk.width(); 168 addToLine(chunk); 169 } 170 // if the length == 0 and there were no other chunks added to the line yet, 171 // we risk to end up in an endless loop trying endlessly to add the same chunk 172 else if (line.size() < 1) { 173 chunk = overflow; 174 overflow = chunk.truncate(width); 175 width -= chunk.width(); 176 if (chunk.length() > 0) { 177 addToLine(chunk); 178 return overflow; 179 } 180 // if the chunk couldn't even be truncated, we add everything, so be it 181 else { 182 if (overflow != null) 183 addToLine(overflow); 184 return null; 185 } 186 } 187 else { 188 width += (line.get(line.size() - 1)).trimLastSpace(); 189 } 190 return overflow; 191 } 192 193 private void addToLine(PdfChunk chunk) { 194 if (chunk.changeLeading && chunk.isImage()) { 195 Image img = chunk.getImage(); 196 float f = img.getScaledHeight() + chunk.getImageOffsetY() 197 + img.getBorderWidthTop() + img.getSpacingBefore(); 198 if (f > height) height = f; 199 } 200 line.add(chunk); 201 } 202 203 // methods to retrieve information 204 205 /** 206 * Returns the number of chunks in the line. 207 * 208 * @return a value 209 */ 210 211 public int size() { 212 return line.size(); 213 } 214 215 /** 216 * Returns an iterator of <CODE>PdfChunk</CODE>s. 217 * 218 * @return an <CODE>Iterator</CODE> 219 */ 220 221 public Iterator<PdfChunk> iterator() { 222 return line.iterator(); 223 } 224 225 /** 226 * Returns the height of the line. 227 * 228 * @return a value 229 */ 230 231 float height() { 232 return height; 233 } 234 235 /** 236 * Returns the left indentation of the line taking the alignment of the line into account. 237 * 238 * @return a value 239 */ 240 241 float indentLeft() { 242 if (isRTL) { 243 switch (alignment) { 244 case Element.ALIGN_LEFT: 245 return left + width; 246 case Element.ALIGN_CENTER: 247 return left + width / 2f; 248 default: 249 return left; 250 } 251 } 252 else if (this.getSeparatorCount() <= 0) { 253 switch (alignment) { 254 case Element.ALIGN_RIGHT: 255 return left + width; 256 case Element.ALIGN_CENTER: 257 return left + width / 2f; 258 } 259 } 260 return left; 261 } 262 263 /** 264 * Checks if this line has to be justified. 265 * 266 * @return <CODE>true</CODE> if the alignment equals <VAR>ALIGN_JUSTIFIED</VAR> and there is some width left. 267 */ 268 269 public boolean hasToBeJustified() { 270 return (alignment == Element.ALIGN_JUSTIFIED || alignment == Element.ALIGN_JUSTIFIED_ALL) && width != 0; 271 } 272 273 /** 274 * Resets the alignment of this line. 275 * <P> 276 * The alignment of the last line of for instance a <CODE>Paragraph</CODE> 277 * that has to be justified, has to be reset to <VAR>ALIGN_LEFT</VAR>. 278 */ 279 280 public void resetAlignment() { 281 if (alignment == Element.ALIGN_JUSTIFIED) { 282 alignment = Element.ALIGN_LEFT; 283 } 284 } 285 286 /** Adds extra indentation to the left (for Paragraph.setFirstLineIndent). */ 287 void setExtraIndent(float extra) { 288 left += extra; 289 width -= extra; 290 } 291 292 /** 293 * Returns the width that is left, after a maximum of characters is added to the line. 294 * 295 * @return a value 296 */ 297 298 float widthLeft() { 299 return width; 300 } 301 302 /** 303 * Returns the number of space-characters in this line. 304 * 305 * @return a value 306 */ 307 308 int numberOfSpaces() { 309 String string = toString(); 310 int length = string.length(); 311 int numberOfSpaces = 0; 312 for (int i = 0; i < length; i++) { 313 if (string.charAt(i) == ' ') { 314 numberOfSpaces++; 315 } 316 } 317 return numberOfSpaces; 318 } 319 320 /** 321 * Sets the listsymbol of this line. 322 * <P> 323 * This is only necessary for the first line of a <CODE>ListItem</CODE>. 324 * 325 * @param listItem the list symbol 326 */ 327 328 public void setListItem(ListItem listItem) { 329 this.listSymbol = listItem.getListSymbol(); 330 this.symbolIndent = listItem.getIndentationLeft(); 331 } 332 333 /** 334 * Returns the listsymbol of this line. 335 * 336 * @return a <CODE>PdfChunk</CODE> if the line has a listsymbol; <CODE>null</CODE> otherwise 337 */ 338 339 public Chunk listSymbol() { 340 return listSymbol; 341 } 342 343 /** 344 * Return the indentation needed to show the listsymbol. 345 * 346 * @return a value 347 */ 348 349 public float listIndent() { 350 return symbolIndent; 351 } 352 353 /** 354 * Get the string representation of what is in this line. 355 * 356 * @return a <CODE>String</CODE> 357 */ 358 359 @Override 360 public String toString() { 361 StringBuffer tmp = new StringBuffer(); 362 for (PdfChunk pdfChunk : line) { 363 tmp.append((pdfChunk).toString()); 364 } 365 return tmp.toString(); 366 } 367 368 /** 369 * Returns the length of a line in UTF32 characters 370 * @return the length in UTF32 characters 371 * @since 2.1.2; Get changed into get in 5.0.2 372 */ 373 public int getLineLengthUtf32() { 374 int total = 0; 375 for (Object element : line) { 376 total += ((PdfChunk)element).lengthUtf32(); 377 } 378 return total; 379 } 380 381 /** 382 * Checks if a newline caused the line split. 383 * @return <CODE>true</CODE> if a newline caused the line split 384 */ 385 public boolean isNewlineSplit() { 386 return newlineSplit && alignment != Element.ALIGN_JUSTIFIED_ALL; 387 } 388 389 /** 390 * Gets the index of the last <CODE>PdfChunk</CODE> with metric attributes 391 * @return the last <CODE>PdfChunk</CODE> with metric attributes 392 */ 393 public int getLastStrokeChunk() { 394 int lastIdx = line.size() - 1; 395 for (; lastIdx >= 0; --lastIdx) { 396 PdfChunk chunk = line.get(lastIdx); 397 if (chunk.isStroked()) 398 break; 399 } 400 return lastIdx; 401 } 402 403 /** 404 * Gets a <CODE>PdfChunk</CODE> by index. 405 * @param idx the index 406 * @return the <CODE>PdfChunk</CODE> or null if beyond the array 407 */ 408 public PdfChunk getChunk(int idx) { 409 if (idx < 0 || idx >= line.size()) 410 return null; 411 return line.get(idx); 412 } 413 414 /** 415 * Gets the original width of the line. 416 * @return the original width of the line 417 */ 418 public float getOriginalWidth() { 419 return originalWidth; 420 } 421 422 /* 423 * Gets the maximum size of all the fonts used in this line 424 * including images. 425 * @return maximum size of all the fonts used in this line 426 float getMaxSizeSimple() { 427 float maxSize = 0; 428 PdfChunk chunk; 429 for (int k = 0; k < line.size(); ++k) { 430 chunk = (PdfChunk)line.get(k); 431 if (!chunk.isImage()) { 432 maxSize = Math.max(chunk.font().size(), maxSize); 433 } 434 else { 435 maxSize = Math.max(chunk.getImage().getScaledHeight() + chunk.getImageOffsetY() , maxSize); 436 } 437 } 438 return maxSize; 439 }*/ 440 441 /** 442 * Gets the difference between the "normal" leading and the maximum 443 * size (for instance when there are images in the chunk and the leading 444 * has to be taken into account). 445 * @return an extra leading for images 446 * @since 2.1.5 447 */ 448 float[] getMaxSize() { 449 float normal_leading = 0; 450 float image_leading = -10000; 451 PdfChunk chunk; 452 for (int k = 0; k < line.size(); ++k) { 453 chunk = line.get(k); 454 if (!chunk.isImage()) { 455 normal_leading = Math.max(chunk.font().size(), normal_leading); 456 } 457 else { 458 Image img = chunk.getImage(); 459 if (chunk.changeLeading()) { 460 float height = img.getScaledHeight() + chunk.getImageOffsetY() + img.getSpacingBefore(); 461 image_leading = Math.max(height, image_leading); 462 } 463 } 464 } 465 return new float[]{normal_leading, image_leading}; 466 } 467 468 boolean isRTL() { 469 return isRTL; 470 } 471 472 /** 473 * Gets the number of separators in the line. 474 * Returns -1 if there's a tab in the line. 475 * @return the number of separators in the line 476 * @since 2.1.2 477 */ 478 int getSeparatorCount() { 479 int s = 0; 480 PdfChunk ck; 481 for (Object element : line) { 482 ck = (PdfChunk)element; 483 if (ck.isTab()) { 484 return -1; 485 } 486 if (ck.isHorizontalSeparator()) { 487 s++; 488 } 489 } 490 return s; 491 } 492 493 /** 494 * Gets a width corrected with a charSpacing and wordSpacing. 495 * @param charSpacing 496 * @param wordSpacing 497 * @return a corrected width 498 */ 499 public float getWidthCorrected(float charSpacing, float wordSpacing) { 500 float total = 0; 501 for (int k = 0; k < line.size(); ++k) { 502 PdfChunk ck = line.get(k); 503 total += ck.getWidthCorrected(charSpacing, wordSpacing); 504 } 505 return total; 506 } 507 508/** 509 * Gets the maximum size of the ascender for all the fonts used 510 * in this line. 511 * @return maximum size of all the ascenders used in this line 512 */ 513 public float getAscender() { 514 float ascender = 0; 515 for (int k = 0; k < line.size(); ++k) { 516 PdfChunk ck = line.get(k); 517 if (ck.isImage()) 518 ascender = Math.max(ascender, ck.getImage().getScaledHeight() + ck.getImageOffsetY()); 519 else { 520 PdfFont font = ck.font(); 521 ascender = Math.max(ascender, font.getFont().getFontDescriptor(BaseFont.ASCENT, font.size())); 522 } 523 } 524 return ascender; 525 } 526 527/** 528 * Gets the biggest descender for all the fonts used 529 * in this line. Note that this is a negative number. 530 * @return maximum size of all the descenders used in this line 531 */ 532 public float getDescender() { 533 float descender = 0; 534 for (int k = 0; k < line.size(); ++k) { 535 PdfChunk ck = line.get(k); 536 if (ck.isImage()) 537 descender = Math.min(descender, ck.getImageOffsetY()); 538 else { 539 PdfFont font = ck.font(); 540 descender = Math.min(descender, font.getFont().getFontDescriptor(BaseFont.DESCENT, font.size())); 541 } 542 } 543 return descender; 544 } 545}