001/* 002 * $Id: PdfChunk.java 4784 2011-03-15 08:33:00Z 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.HashMap; 047import java.util.HashSet; 048import java.util.Map; 049 050import com.itextpdf.text.BaseColor; 051import com.itextpdf.text.Chunk; 052import com.itextpdf.text.Font; 053import com.itextpdf.text.Image; 054import com.itextpdf.text.SplitCharacter; 055import com.itextpdf.text.Utilities; 056 057/** 058 * A <CODE>PdfChunk</CODE> is the PDF translation of a <CODE>Chunk</CODE>. 059 * <P> 060 * A <CODE>PdfChunk</CODE> is a <CODE>PdfString</CODE> in a certain 061 * <CODE>PdfFont</CODE> and <CODE>BaseColor</CODE>. 062 * 063 * @see PdfString 064 * @see com.itextpdf.text.Chunk 065 * @see com.itextpdf.text.Font 066 */ 067 068public class PdfChunk { 069 070 private static final char singleSpace[] = {' '}; 071 private static final PdfChunk thisChunk[] = new PdfChunk[1]; 072 private static final float ITALIC_ANGLE = 0.21256f; 073/** The allowed attributes in variable <CODE>attributes</CODE>. */ 074 private static final HashSet<String> keysAttributes = new HashSet<String>(); 075 076/** The allowed attributes in variable <CODE>noStroke</CODE>. */ 077 private static final HashSet<String> keysNoStroke = new HashSet<String>(); 078 079 static { 080 keysAttributes.add(Chunk.ACTION); 081 keysAttributes.add(Chunk.UNDERLINE); 082 keysAttributes.add(Chunk.REMOTEGOTO); 083 keysAttributes.add(Chunk.LOCALGOTO); 084 keysAttributes.add(Chunk.LOCALDESTINATION); 085 keysAttributes.add(Chunk.GENERICTAG); 086 keysAttributes.add(Chunk.NEWPAGE); 087 keysAttributes.add(Chunk.IMAGE); 088 keysAttributes.add(Chunk.BACKGROUND); 089 keysAttributes.add(Chunk.PDFANNOTATION); 090 keysAttributes.add(Chunk.SKEW); 091 keysAttributes.add(Chunk.HSCALE); 092 keysAttributes.add(Chunk.SEPARATOR); 093 keysAttributes.add(Chunk.TAB); 094 keysAttributes.add(Chunk.CHAR_SPACING); 095 keysNoStroke.add(Chunk.SUBSUPSCRIPT); 096 keysNoStroke.add(Chunk.SPLITCHARACTER); 097 keysNoStroke.add(Chunk.HYPHENATION); 098 keysNoStroke.add(Chunk.TEXTRENDERMODE); 099 } 100 101 // membervariables 102 103 /** The value of this object. */ 104 protected String value = PdfObject.NOTHING; 105 106 /** The encoding. */ 107 protected String encoding = BaseFont.WINANSI; 108 109 110/** The font for this <CODE>PdfChunk</CODE>. */ 111 protected PdfFont font; 112 113 protected BaseFont baseFont; 114 115 protected SplitCharacter splitCharacter; 116/** 117 * Metric attributes. 118 * <P> 119 * This attributes require the measurement of characters widths when rendering 120 * such as underline. 121 */ 122 protected HashMap<String, Object> attributes = new HashMap<String, Object>(); 123 124/** 125 * Non metric attributes. 126 * <P> 127 * This attributes do not require the measurement of characters widths when rendering 128 * such as BaseColor. 129 */ 130 protected HashMap<String, Object> noStroke = new HashMap<String, Object>(); 131 132/** <CODE>true</CODE> if the chunk split was cause by a newline. */ 133 protected boolean newlineSplit; 134 135/** The image in this <CODE>PdfChunk</CODE>, if it has one */ 136 protected Image image; 137 138/** The offset in the x direction for the image */ 139 protected float offsetX; 140 141/** The offset in the y direction for the image */ 142 protected float offsetY; 143 144/** Indicates if the height and offset of the Image has to be taken into account */ 145 protected boolean changeLeading = false; 146 147 // constructors 148 149/** 150 * Constructs a <CODE>PdfChunk</CODE>-object. 151 * 152 * @param string the content of the <CODE>PdfChunk</CODE>-object 153 * @param other Chunk with the same style you want for the new Chunk 154 */ 155 156 PdfChunk(String string, PdfChunk other) { 157 thisChunk[0] = this; 158 value = string; 159 this.font = other.font; 160 this.attributes = other.attributes; 161 this.noStroke = other.noStroke; 162 this.baseFont = other.baseFont; 163 Object obj[] = (Object[])attributes.get(Chunk.IMAGE); 164 if (obj == null) 165 image = null; 166 else { 167 image = (Image)obj[0]; 168 offsetX = ((Float)obj[1]).floatValue(); 169 offsetY = ((Float)obj[2]).floatValue(); 170 changeLeading = ((Boolean)obj[3]).booleanValue(); 171 } 172 encoding = font.getFont().getEncoding(); 173 splitCharacter = (SplitCharacter)noStroke.get(Chunk.SPLITCHARACTER); 174 if (splitCharacter == null) 175 splitCharacter = DefaultSplitCharacter.DEFAULT; 176 } 177 178/** 179 * Constructs a <CODE>PdfChunk</CODE>-object. 180 * 181 * @param chunk the original <CODE>Chunk</CODE>-object 182 * @param action the <CODE>PdfAction</CODE> if the <CODE>Chunk</CODE> comes from an <CODE>Anchor</CODE> 183 */ 184 185 PdfChunk(Chunk chunk, PdfAction action) { 186 thisChunk[0] = this; 187 value = chunk.getContent(); 188 189 Font f = chunk.getFont(); 190 float size = f.getSize(); 191 if (size == Font.UNDEFINED) 192 size = 12; 193 baseFont = f.getBaseFont(); 194 int style = f.getStyle(); 195 if (style == Font.UNDEFINED) { 196 style = Font.NORMAL; 197 } 198 if (baseFont == null) { 199 // translation of the font-family to a PDF font-family 200 baseFont = f.getCalculatedBaseFont(false); 201 } 202 else { 203 // bold simulation 204 if ((style & Font.BOLD) != 0) 205 attributes.put(Chunk.TEXTRENDERMODE, new Object[]{Integer.valueOf(PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE), new Float(size / 30f), null}); 206 // italic simulation 207 if ((style & Font.ITALIC) != 0) 208 attributes.put(Chunk.SKEW, new float[]{0, ITALIC_ANGLE}); 209 } 210 font = new PdfFont(baseFont, size); 211 // other style possibilities 212 HashMap<String, Object> attr = chunk.getAttributes(); 213 if (attr != null) { 214 for (Map.Entry<String, Object>entry: attr.entrySet()) { 215 String name = entry.getKey(); 216 if (keysAttributes.contains(name)) { 217 attributes.put(name, entry.getValue()); 218 } 219 else if (keysNoStroke.contains(name)) { 220 noStroke.put(name, entry.getValue()); 221 } 222 } 223 if ("".equals(attr.get(Chunk.GENERICTAG))) { 224 attributes.put(Chunk.GENERICTAG, chunk.getContent()); 225 } 226 } 227 if (f.isUnderlined()) { 228 Object obj[] = {null, new float[]{0, 1f / 15, 0, -1f / 3, 0}}; 229 Object unders[][] = Utilities.addToArray((Object[][])attributes.get(Chunk.UNDERLINE), obj); 230 attributes.put(Chunk.UNDERLINE, unders); 231 } 232 if (f.isStrikethru()) { 233 Object obj[] = {null, new float[]{0, 1f / 15, 0, 1f / 3, 0}}; 234 Object unders[][] = Utilities.addToArray((Object[][])attributes.get(Chunk.UNDERLINE), obj); 235 attributes.put(Chunk.UNDERLINE, unders); 236 } 237 if (action != null) 238 attributes.put(Chunk.ACTION, action); 239 // the color can't be stored in a PdfFont 240 noStroke.put(Chunk.COLOR, f.getColor()); 241 noStroke.put(Chunk.ENCODING, font.getFont().getEncoding()); 242 Object obj[] = (Object[])attributes.get(Chunk.IMAGE); 243 if (obj == null) { 244 image = null; 245 } 246 else { 247 attributes.remove(Chunk.HSCALE); // images are scaled in other ways 248 image = (Image)obj[0]; 249 offsetX = ((Float)obj[1]).floatValue(); 250 offsetY = ((Float)obj[2]).floatValue(); 251 changeLeading = ((Boolean)obj[3]).booleanValue(); 252 } 253 font.setImage(image); 254 Float hs = (Float)attributes.get(Chunk.HSCALE); 255 if (hs != null) 256 font.setHorizontalScaling(hs.floatValue()); 257 encoding = font.getFont().getEncoding(); 258 splitCharacter = (SplitCharacter)noStroke.get(Chunk.SPLITCHARACTER); 259 if (splitCharacter == null) 260 splitCharacter = DefaultSplitCharacter.DEFAULT; 261 } 262 263 // methods 264 265 /** Gets the Unicode equivalent to a CID. 266 * The (inexistent) CID <FF00> is translated as '\n'. 267 * It has only meaning with CJK fonts with Identity encoding. 268 * @param c the CID code 269 * @return the Unicode equivalent 270 */ 271 public int getUnicodeEquivalent(int c) { 272 return baseFont.getUnicodeEquivalent(c); 273 } 274 275 protected int getWord(String text, int start) { 276 int len = text.length(); 277 while (start < len) { 278 if (!Character.isLetter(text.charAt(start))) 279 break; 280 ++start; 281 } 282 return start; 283 } 284 285/** 286 * Splits this <CODE>PdfChunk</CODE> if it's too long for the given width. 287 * <P> 288 * Returns <VAR>null</VAR> if the <CODE>PdfChunk</CODE> wasn't truncated. 289 * 290 * @param width a given width 291 * @return the <CODE>PdfChunk</CODE> that doesn't fit into the width. 292 */ 293 294 PdfChunk split(float width) { 295 newlineSplit = false; 296 if (image != null) { 297 if (image.getScaledWidth() > width) { 298 PdfChunk pc = new PdfChunk(Chunk.OBJECT_REPLACEMENT_CHARACTER, this); 299 value = ""; 300 attributes = new HashMap<String, Object>(); 301 image = null; 302 font = PdfFont.getDefaultFont(); 303 return pc; 304 } 305 else 306 return null; 307 } 308 HyphenationEvent hyphenationEvent = (HyphenationEvent)noStroke.get(Chunk.HYPHENATION); 309 int currentPosition = 0; 310 int splitPosition = -1; 311 float currentWidth = 0; 312 313 // loop over all the characters of a string 314 // or until the totalWidth is reached 315 int lastSpace = -1; 316 float lastSpaceWidth = 0; 317 int length = value.length(); 318 char valueArray[] = value.toCharArray(); 319 char character = 0; 320 BaseFont ft = font.getFont(); 321 boolean surrogate = false; 322 if (ft.getFontType() == BaseFont.FONT_TYPE_CJK && ft.getUnicodeEquivalent(' ') != ' ') { 323 while (currentPosition < length) { 324 // the width of every character is added to the currentWidth 325 char cidChar = valueArray[currentPosition]; 326 character = (char)ft.getUnicodeEquivalent(cidChar); 327 // if a newLine or carriageReturn is encountered 328 if (character == '\n') { 329 newlineSplit = true; 330 String returnValue = value.substring(currentPosition + 1); 331 value = value.substring(0, currentPosition); 332 if (value.length() < 1) { 333 value = "\u0001"; 334 } 335 PdfChunk pc = new PdfChunk(returnValue, this); 336 return pc; 337 } 338 currentWidth += getCharWidth(cidChar); 339 if (character == ' ') { 340 lastSpace = currentPosition + 1; 341 lastSpaceWidth = currentWidth; 342 } 343 if (currentWidth > width) 344 break; 345 // if a split-character is encountered, the splitPosition is altered 346 if (splitCharacter.isSplitCharacter(0, currentPosition, length, valueArray, thisChunk)) 347 splitPosition = currentPosition + 1; 348 currentPosition++; 349 } 350 } 351 else { 352 while (currentPosition < length) { 353 // the width of every character is added to the currentWidth 354 character = valueArray[currentPosition]; 355 // if a newLine or carriageReturn is encountered 356 if (character == '\r' || character == '\n') { 357 newlineSplit = true; 358 int inc = 1; 359 if (character == '\r' && currentPosition + 1 < length && valueArray[currentPosition + 1] == '\n') 360 inc = 2; 361 String returnValue = value.substring(currentPosition + inc); 362 value = value.substring(0, currentPosition); 363 if (value.length() < 1) { 364 value = " "; 365 } 366 PdfChunk pc = new PdfChunk(returnValue, this); 367 return pc; 368 } 369 surrogate = Utilities.isSurrogatePair(valueArray, currentPosition); 370 if (surrogate) 371 currentWidth += getCharWidth(Utilities.convertToUtf32(valueArray[currentPosition], valueArray[currentPosition + 1])); 372 else 373 currentWidth += getCharWidth(character); 374 if (character == ' ') { 375 lastSpace = currentPosition + 1; 376 lastSpaceWidth = currentWidth; 377 } 378 if (surrogate) 379 currentPosition++; 380 if (currentWidth > width) 381 break; 382 // if a split-character is encountered, the splitPosition is altered 383 if (splitCharacter.isSplitCharacter(0, currentPosition, length, valueArray, null)) 384 splitPosition = currentPosition + 1; 385 currentPosition++; 386 } 387 } 388 389 // if all the characters fit in the total width, null is returned (there is no overflow) 390 if (currentPosition == length) { 391 return null; 392 } 393 // otherwise, the string has to be truncated 394 if (splitPosition < 0) { 395 String returnValue = value; 396 value = ""; 397 PdfChunk pc = new PdfChunk(returnValue, this); 398 return pc; 399 } 400 if (lastSpace > splitPosition && splitCharacter.isSplitCharacter(0, 0, 1, singleSpace, null)) 401 splitPosition = lastSpace; 402 if (hyphenationEvent != null && lastSpace >= 0 && lastSpace < currentPosition) { 403 int wordIdx = getWord(value, lastSpace); 404 if (wordIdx > lastSpace) { 405 String pre = hyphenationEvent.getHyphenatedWordPre(value.substring(lastSpace, wordIdx), font.getFont(), font.size(), width - lastSpaceWidth); 406 String post = hyphenationEvent.getHyphenatedWordPost(); 407 if (pre.length() > 0) { 408 String returnValue = post + value.substring(wordIdx); 409 value = trim(value.substring(0, lastSpace) + pre); 410 PdfChunk pc = new PdfChunk(returnValue, this); 411 return pc; 412 } 413 } 414 } 415 String returnValue = value.substring(splitPosition); 416 value = trim(value.substring(0, splitPosition)); 417 PdfChunk pc = new PdfChunk(returnValue, this); 418 return pc; 419 } 420 421 /** 422 * Truncates this <CODE>PdfChunk</CODE> if it's too long for the given width. 423 * <P> 424 * Returns <VAR>null</VAR> if the <CODE>PdfChunk</CODE> wasn't truncated. 425 * 426 * @param width a given width 427 * @return the <CODE>PdfChunk</CODE> that doesn't fit into the width. 428 */ 429 PdfChunk truncate(float width) { 430 if (image != null) { 431 if (image.getScaledWidth() > width) { 432 // Image does not fit the line, resize if requested 433 if (image.isScaleToFitLineWhenOverflow()) { 434 float scalePercent = width / image.getWidth() * 100; 435 image.scalePercent(scalePercent); 436 return null; 437 } 438 PdfChunk pc = new PdfChunk("", this); 439 value = ""; 440 attributes.remove(Chunk.IMAGE); 441 image = null; 442 font = PdfFont.getDefaultFont(); 443 return pc; 444 } 445 else 446 return null; 447 } 448 449 int currentPosition = 0; 450 float currentWidth = 0; 451 452 // it's no use trying to split if there isn't even enough place for a space 453 if (width < font.width()) { 454 String returnValue = value.substring(1); 455 value = value.substring(0, 1); 456 PdfChunk pc = new PdfChunk(returnValue, this); 457 return pc; 458 } 459 460 // loop over all the characters of a string 461 // or until the totalWidth is reached 462 int length = value.length(); 463 boolean surrogate = false; 464 while (currentPosition < length) { 465 // the width of every character is added to the currentWidth 466 surrogate = Utilities.isSurrogatePair(value, currentPosition); 467 if (surrogate) 468 currentWidth += getCharWidth(Utilities.convertToUtf32(value, currentPosition)); 469 else 470 currentWidth += getCharWidth(value.charAt(currentPosition)); 471 if (currentWidth > width) 472 break; 473 if (surrogate) 474 currentPosition++; 475 currentPosition++; 476 } 477 478 // if all the characters fit in the total width, null is returned (there is no overflow) 479 if (currentPosition == length) { 480 return null; 481 } 482 483 // otherwise, the string has to be truncated 484 //currentPosition -= 2; 485 // we have to chop off minimum 1 character from the chunk 486 if (currentPosition == 0) { 487 currentPosition = 1; 488 if (surrogate) 489 ++currentPosition; 490 } 491 String returnValue = value.substring(currentPosition); 492 value = value.substring(0, currentPosition); 493 PdfChunk pc = new PdfChunk(returnValue, this); 494 return pc; 495 } 496 497 // methods to retrieve the membervariables 498 499/** 500 * Returns the font of this <CODE>Chunk</CODE>. 501 * 502 * @return a <CODE>PdfFont</CODE> 503 */ 504 505 PdfFont font() { 506 return font; 507 } 508 509/** 510 * Returns the color of this <CODE>Chunk</CODE>. 511 * 512 * @return a <CODE>BaseColor</CODE> 513 */ 514 515 BaseColor color() { 516 return (BaseColor)noStroke.get(Chunk.COLOR); 517 } 518 519/** 520 * Returns the width of this <CODE>PdfChunk</CODE>. 521 * 522 * @return a width 523 */ 524 525 float width() { 526 if (isAttribute(Chunk.CHAR_SPACING)) { 527 Float cs = (Float) getAttribute(Chunk.CHAR_SPACING); 528 return font.width(value) + value.length() * cs.floatValue(); 529 } 530 if (isAttribute(Chunk.SEPARATOR)) { 531 return 0; 532 } 533 return font.width(value); 534 } 535 536/** 537 * Checks if the <CODE>PdfChunk</CODE> split was caused by a newline. 538 * @return <CODE>true</CODE> if the <CODE>PdfChunk</CODE> split was caused by a newline. 539 */ 540 541 public boolean isNewlineSplit() 542 { 543 return newlineSplit; 544 } 545 546/** 547 * Gets the width of the <CODE>PdfChunk</CODE> taking into account the 548 * extra character and word spacing. 549 * @param charSpacing the extra character spacing 550 * @param wordSpacing the extra word spacing 551 * @return the calculated width 552 */ 553 554 public float getWidthCorrected(float charSpacing, float wordSpacing) 555 { 556 if (image != null) { 557 return image.getScaledWidth() + charSpacing; 558 } 559 int numberOfSpaces = 0; 560 int idx = -1; 561 while ((idx = value.indexOf(' ', idx + 1)) >= 0) 562 ++numberOfSpaces; 563 return width() + value.length() * charSpacing + numberOfSpaces * wordSpacing; 564 } 565 566 /** 567 * Gets the text displacement relative to the baseline. 568 * @return a displacement in points 569 */ 570 public float getTextRise() { 571 Float f = (Float) getAttribute(Chunk.SUBSUPSCRIPT); 572 if (f != null) { 573 return f.floatValue(); 574 } 575 return 0.0f; 576 } 577 578/** 579 * Trims the last space. 580 * @return the width of the space trimmed, otherwise 0 581 */ 582 583 public float trimLastSpace() 584 { 585 BaseFont ft = font.getFont(); 586 if (ft.getFontType() == BaseFont.FONT_TYPE_CJK && ft.getUnicodeEquivalent(' ') != ' ') { 587 if (value.length() > 1 && value.endsWith("\u0001")) { 588 value = value.substring(0, value.length() - 1); 589 return font.width('\u0001'); 590 } 591 } 592 else { 593 if (value.length() > 1 && value.endsWith(" ")) { 594 value = value.substring(0, value.length() - 1); 595 return font.width(' '); 596 } 597 } 598 return 0; 599 } 600 public float trimFirstSpace() 601 { 602 BaseFont ft = font.getFont(); 603 if (ft.getFontType() == BaseFont.FONT_TYPE_CJK && ft.getUnicodeEquivalent(' ') != ' ') { 604 if (value.length() > 1 && value.startsWith("\u0001")) { 605 value = value.substring(1); 606 return font.width('\u0001'); 607 } 608 } 609 else { 610 if (value.length() > 1 && value.startsWith(" ")) { 611 value = value.substring(1); 612 return font.width(' '); 613 } 614 } 615 return 0; 616 } 617 618/** 619 * Gets an attribute. The search is made in <CODE>attributes</CODE> 620 * and <CODE>noStroke</CODE>. 621 * @param name the attribute key 622 * @return the attribute value or null if not found 623 */ 624 625 Object getAttribute(String name) 626 { 627 if (attributes.containsKey(name)) 628 return attributes.get(name); 629 return noStroke.get(name); 630 } 631 632/** 633 *Checks if the attribute exists. 634 * @param name the attribute key 635 * @return <CODE>true</CODE> if the attribute exists 636 */ 637 638 boolean isAttribute(String name) 639 { 640 if (attributes.containsKey(name)) 641 return true; 642 return noStroke.containsKey(name); 643 } 644 645/** 646 * Checks if this <CODE>PdfChunk</CODE> needs some special metrics handling. 647 * @return <CODE>true</CODE> if this <CODE>PdfChunk</CODE> needs some special metrics handling. 648 */ 649 650 boolean isStroked() 651 { 652 return !attributes.isEmpty(); 653 } 654 655 /** 656 * Checks if this <CODE>PdfChunk</CODE> is a Separator Chunk. 657 * @return true if this chunk is a separator. 658 * @since 2.1.2 659 */ 660 boolean isSeparator() { 661 return isAttribute(Chunk.SEPARATOR); 662 } 663 664 /** 665 * Checks if this <CODE>PdfChunk</CODE> is a horizontal Separator Chunk. 666 * @return true if this chunk is a horizontal separator. 667 * @since 2.1.2 668 */ 669 boolean isHorizontalSeparator() { 670 if (isAttribute(Chunk.SEPARATOR)) { 671 Object[] o = (Object[])getAttribute(Chunk.SEPARATOR); 672 return !((Boolean)o[1]).booleanValue(); 673 } 674 return false; 675 } 676 677 /** 678 * Checks if this <CODE>PdfChunk</CODE> is a tab Chunk. 679 * @return true if this chunk is a separator. 680 * @since 2.1.2 681 */ 682 boolean isTab() { 683 return isAttribute(Chunk.TAB); 684 } 685 686 /** 687 * Correction for the tab position based on the left starting position. 688 * @param newValue the new value for the left X. 689 * @since 2.1.2 690 */ 691 void adjustLeft(float newValue) { 692 Object[] o = (Object[])attributes.get(Chunk.TAB); 693 if (o != null) { 694 attributes.put(Chunk.TAB, new Object[]{o[0], o[1], o[2], new Float(newValue)}); 695 } 696 } 697 698/** 699 * Checks if there is an image in the <CODE>PdfChunk</CODE>. 700 * @return <CODE>true</CODE> if an image is present 701 */ 702 703 boolean isImage() 704 { 705 return image != null; 706 } 707 708/** 709 * Gets the image in the <CODE>PdfChunk</CODE>. 710 * @return the image or <CODE>null</CODE> 711 */ 712 713 Image getImage() 714 { 715 return image; 716 } 717 718/** 719 * Sets the image offset in the x direction 720 * @param offsetX the image offset in the x direction 721 */ 722 723 void setImageOffsetX(float offsetX) 724 { 725 this.offsetX = offsetX; 726 } 727 728/** 729 * Gets the image offset in the x direction 730 * @return the image offset in the x direction 731 */ 732 733 float getImageOffsetX() 734 { 735 return offsetX; 736 } 737 738/** 739 * Sets the image offset in the y direction 740 * @param offsetY the image offset in the y direction 741 */ 742 743 void setImageOffsetY(float offsetY) 744 { 745 this.offsetY = offsetY; 746 } 747 748/** 749 * Gets the image offset in the y direction 750 * @return Gets the image offset in the y direction 751 */ 752 753 float getImageOffsetY() 754 { 755 return offsetY; 756 } 757 758/** 759 * sets the value. 760 * @param value content of the Chunk 761 */ 762 763 void setValue(String value) 764 { 765 this.value = value; 766 } 767 768 /** 769 * @see java.lang.Object#toString() 770 */ 771 @Override 772 public String toString() { 773 return value; 774 } 775 776 /** 777 * Tells you if this string is in Chinese, Japanese, Korean or Identity-H. 778 * @return true if the Chunk has a special encoding 779 */ 780 781 boolean isSpecialEncoding() { 782 return encoding.equals(CJKFont.CJK_ENCODING) || encoding.equals(BaseFont.IDENTITY_H); 783 } 784 785 /** 786 * Gets the encoding of this string. 787 * 788 * @return a <CODE>String</CODE> 789 */ 790 791 String getEncoding() { 792 return encoding; 793 } 794 795 int length() { 796 return value.length(); 797 } 798 799 int lengthUtf32() { 800 if (!BaseFont.IDENTITY_H.equals(encoding)) 801 return value.length(); 802 int total = 0; 803 int len = value.length(); 804 for (int k = 0; k < len; ++k) { 805 if (Utilities.isSurrogateHigh(value.charAt(k))) 806 ++k; 807 ++total; 808 } 809 return total; 810 } 811 812 boolean isExtSplitCharacter(int start, int current, int end, char[] cc, PdfChunk[] ck) { 813 return splitCharacter.isSplitCharacter(start, current, end, cc, ck); 814 } 815 816/** 817 * Removes all the <VAR>' '</VAR> and <VAR>'-'</VAR>-characters on the right of a <CODE>String</CODE>. 818 * <P> 819 * @param string the <CODE>String<CODE> that has to be trimmed. 820 * @return the trimmed <CODE>String</CODE> 821 */ 822 String trim(String string) { 823 BaseFont ft = font.getFont(); 824 if (ft.getFontType() == BaseFont.FONT_TYPE_CJK && ft.getUnicodeEquivalent(' ') != ' ') { 825 while (string.endsWith("\u0001")) { 826 string = string.substring(0, string.length() - 1); 827 } 828 } 829 else { 830 while (string.endsWith(" ") || string.endsWith("\t")) { 831 string = string.substring(0, string.length() - 1); 832 } 833 } 834 return string; 835 } 836 837 public boolean changeLeading() { 838 return changeLeading; 839 } 840 841 float getCharWidth(int c) { 842 if (noPrint(c)) 843 return 0; 844 if (isAttribute(Chunk.CHAR_SPACING)) { 845 Float cs = (Float) getAttribute(Chunk.CHAR_SPACING); 846 return font.width(c) + cs.floatValue() * font.getHorizontalScaling(); 847 } 848 return font.width(c); 849 } 850 851 public static boolean noPrint(int c) { 852 return c >= 0x200b && c <= 0x200f || c >= 0x202a && c <= 0x202e; 853 } 854 855}