1 /*
2 * $Id: PdfChunk.java 3407 2008-05-21 16:56:55Z blowagie $
3 *
4 * Copyright 1999, 2000, 2001, 2002 Bruno Lowagie
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * (the "License"); you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at http://www.mozilla.org/MPL/
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the License.
13 *
14 * The Original Code is 'iText, a free JAVA-PDF library'.
15 *
16 * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
17 * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
18 * All Rights Reserved.
19 * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
20 * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
21 *
22 * Contributor(s): all the names of the contributors are added in the source code
23 * where applicable.
24 *
25 * Alternatively, the contents of this file may be used under the terms of the
26 * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
27 * provisions of LGPL are applicable instead of those above. If you wish to
28 * allow use of your version of this file only under the terms of the LGPL
29 * License and not to allow others to use your version of this file under
30 * the MPL, indicate your decision by deleting the provisions above and
31 * replace them with the notice and other provisions required by the LGPL.
32 * If you do not delete the provisions above, a recipient may use your version
33 * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
34 *
35 * This library is free software; you can redistribute it and/or modify it
36 * under the terms of the MPL as stated above or under the terms of the GNU
37 * Library General Public License as published by the Free Software Foundation;
38 * either version 2 of the License, or any later version.
39 *
40 * This library is distributed in the hope that it will be useful, but WITHOUT
41 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
42 * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
43 * details.
44 *
45 * If you didn't download this code from the following link, you should check if
46 * you aren't using an obsolete version:
47 * http://www.lowagie.com/iText/
48 */
49
50 package com.lowagie.text.pdf;
51
52 import java.awt.Color;
53 import java.util.HashMap;
54 import java.util.Iterator;
55 import java.util.Map;
56
57 import com.lowagie.text.Chunk;
58 import com.lowagie.text.Font;
59 import com.lowagie.text.Image;
60 import com.lowagie.text.SplitCharacter;
61 import com.lowagie.text.Utilities;
62
63 /**
64 * A <CODE>PdfChunk</CODE> is the PDF translation of a <CODE>Chunk</CODE>.
65 * <P>
66 * A <CODE>PdfChunk</CODE> is a <CODE>PdfString</CODE> in a certain
67 * <CODE>PdfFont</CODE> and <CODE>Color</CODE>.
68 *
69 * @see PdfString
70 * @see com.lowagie.text.Chunk
71 * @see com.lowagie.text.Font
72 */
73
74 public class PdfChunk {
75
76 private static final char singleSpace[] = {' '};
77 private static final PdfChunk thisChunk[] = new PdfChunk[1];
78 private static final float ITALIC_ANGLE = 0.21256f;
79 /** The allowed attributes in variable <CODE>attributes</CODE>. */
80 private static final HashMap keysAttributes = new HashMap();
81
82 /** The allowed attributes in variable <CODE>noStroke</CODE>. */
83 private static final HashMap keysNoStroke = new HashMap();
84
85 static {
86 keysAttributes.put(Chunk.ACTION, null);
87 keysAttributes.put(Chunk.UNDERLINE, null);
88 keysAttributes.put(Chunk.REMOTEGOTO, null);
89 keysAttributes.put(Chunk.LOCALGOTO, null);
90 keysAttributes.put(Chunk.LOCALDESTINATION, null);
91 keysAttributes.put(Chunk.GENERICTAG, null);
92 keysAttributes.put(Chunk.NEWPAGE, null);
93 keysAttributes.put(Chunk.IMAGE, null);
94 keysAttributes.put(Chunk.BACKGROUND, null);
95 keysAttributes.put(Chunk.PDFANNOTATION, null);
96 keysAttributes.put(Chunk.SKEW, null);
97 keysAttributes.put(Chunk.HSCALE, null);
98 keysAttributes.put(Chunk.SEPARATOR, null);
99 keysAttributes.put(Chunk.TAB, null);
100 keysNoStroke.put(Chunk.SUBSUPSCRIPT, null);
101 keysNoStroke.put(Chunk.SPLITCHARACTER, null);
102 keysNoStroke.put(Chunk.HYPHENATION, null);
103 keysNoStroke.put(Chunk.TEXTRENDERMODE, null);
104 }
105
106 // membervariables
107
108 /** The value of this object. */
109 protected String value = PdfObject.NOTHING;
110
111 /** The encoding. */
112 protected String encoding = BaseFont.WINANSI;
113
114
115 /** The font for this <CODE>PdfChunk</CODE>. */
116 protected PdfFont font;
117
118 protected BaseFont baseFont;
119
120 protected SplitCharacter splitCharacter;
121 /**
122 * Metric attributes.
123 * <P>
124 * This attributes require the measurement of characters widths when rendering
125 * such as underline.
126 */
127 protected HashMap attributes = new HashMap();
128
129 /**
130 * Non metric attributes.
131 * <P>
132 * This attributes do not require the measurement of characters widths when rendering
133 * such as Color.
134 */
135 protected HashMap noStroke = new HashMap();
136
137 /** <CODE>true</CODE> if the chunk split was cause by a newline. */
138 protected boolean newlineSplit;
139
140 /** The image in this <CODE>PdfChunk</CODE>, if it has one */
141 protected Image image;
142
143 /** The offset in the x direction for the image */
144 protected float offsetX;
145
146 /** The offset in the y direction for the image */
147 protected float offsetY;
148
149 /** Indicates if the height and offset of the Image has to be taken into account */
150 protected boolean changeLeading = false;
151
152 // constructors
153
154 /**
155 * Constructs a <CODE>PdfChunk</CODE>-object.
156 *
157 * @param string the content of the <CODE>PdfChunk</CODE>-object
158 * @param other Chunk with the same style you want for the new Chunk
159 */
160
161 PdfChunk(String string, PdfChunk other) {
162 thisChunk[0] = this;
163 value = string;
164 this.font = other.font;
165 this.attributes = other.attributes;
166 this.noStroke = other.noStroke;
167 this.baseFont = other.baseFont;
168 Object obj[] = (Object[])attributes.get(Chunk.IMAGE);
169 if (obj == null)
170 image = null;
171 else {
172 image = (Image)obj[0];
173 offsetX = ((Float)obj[1]).floatValue();
174 offsetY = ((Float)obj[2]).floatValue();
175 changeLeading = ((Boolean)obj[3]).booleanValue();
176 }
177 encoding = font.getFont().getEncoding();
178 splitCharacter = (SplitCharacter)noStroke.get(Chunk.SPLITCHARACTER);
179 if (splitCharacter == null)
180 splitCharacter = DefaultSplitCharacter.DEFAULT;
181 }
182
183 /**
184 * Constructs a <CODE>PdfChunk</CODE>-object.
185 *
186 * @param chunk the original <CODE>Chunk</CODE>-object
187 * @param action the <CODE>PdfAction</CODE> if the <CODE>Chunk</CODE> comes from an <CODE>Anchor</CODE>
188 */
189
190 PdfChunk(Chunk chunk, PdfAction action) {
191 thisChunk[0] = this;
192 value = chunk.getContent();
193
194 Font f = chunk.getFont();
195 float size = f.getSize();
196 if (size == Font.UNDEFINED)
197 size = 12;
198 baseFont = f.getBaseFont();
199 int style = f.getStyle();
200 if (style == Font.UNDEFINED) {
201 style = Font.NORMAL;
202 }
203 if (baseFont == null) {
204 // translation of the font-family to a PDF font-family
205 baseFont = f.getCalculatedBaseFont(false);
206 }
207 else {
208 // bold simulation
209 if ((style & Font.BOLD) != 0)
210 attributes.put(Chunk.TEXTRENDERMODE, new Object[]{new Integer(PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE), new Float(size / 30f), null});
211 // italic simulation
212 if ((style & Font.ITALIC) != 0)
213 attributes.put(Chunk.SKEW, new float[]{0, ITALIC_ANGLE});
214 }
215 font = new PdfFont(baseFont, size);
216 // other style possibilities
217 HashMap attr = chunk.getAttributes();
218 if (attr != null) {
219 for (Iterator i = attr.entrySet().iterator(); i.hasNext();) {
220 Map.Entry entry = (Map.Entry) i.next();
221 Object name = entry.getKey();
222 if (keysAttributes.containsKey(name)) {
223 attributes.put(name, entry.getValue());
224 }
225 else if (keysNoStroke.containsKey(name)) {
226 noStroke.put(name, entry.getValue());
227 }
228 }
229 if ("".equals(attr.get(Chunk.GENERICTAG))) {
230 attributes.put(Chunk.GENERICTAG, chunk.getContent());
231 }
232 }
233 if (f.isUnderlined()) {
234 Object obj[] = {null, new float[]{0, 1f / 15, 0, -1f / 3, 0}};
235 Object unders[][] = Utilities.addToArray((Object[][])attributes.get(Chunk.UNDERLINE), obj);
236 attributes.put(Chunk.UNDERLINE, unders);
237 }
238 if (f.isStrikethru()) {
239 Object obj[] = {null, new float[]{0, 1f / 15, 0, 1f / 3, 0}};
240 Object unders[][] = Utilities.addToArray((Object[][])attributes.get(Chunk.UNDERLINE), obj);
241 attributes.put(Chunk.UNDERLINE, unders);
242 }
243 if (action != null)
244 attributes.put(Chunk.ACTION, action);
245 // the color can't be stored in a PdfFont
246 noStroke.put(Chunk.COLOR, f.getColor());
247 noStroke.put(Chunk.ENCODING, font.getFont().getEncoding());
248 Object obj[] = (Object[])attributes.get(Chunk.IMAGE);
249 if (obj == null) {
250 image = null;
251 }
252 else {
253 attributes.remove(Chunk.HSCALE); // images are scaled in other ways
254 image = (Image)obj[0];
255 offsetX = ((Float)obj[1]).floatValue();
256 offsetY = ((Float)obj[2]).floatValue();
257 changeLeading = ((Boolean)obj[3]).booleanValue();
258 }
259 font.setImage(image);
260 Float hs = (Float)attributes.get(Chunk.HSCALE);
261 if (hs != null)
262 font.setHorizontalScaling(hs.floatValue());
263 encoding = font.getFont().getEncoding();
264 splitCharacter = (SplitCharacter)noStroke.get(Chunk.SPLITCHARACTER);
265 if (splitCharacter == null)
266 splitCharacter = DefaultSplitCharacter.DEFAULT;
267 }
268
269 // methods
270
271 /** Gets the Unicode equivalent to a CID.
272 * The (inexistent) CID <FF00> is translated as '\n'.
273 * It has only meaning with CJK fonts with Identity encoding.
274 * @param c the CID code
275 * @return the Unicode equivalent
276 */
277 public int getUnicodeEquivalent(int c) {
278 return baseFont.getUnicodeEquivalent(c);
279 }
280
281 protected int getWord(String text, int start) {
282 int len = text.length();
283 while (start < len) {
284 if (!Character.isLetter(text.charAt(start)))
285 break;
286 ++start;
287 }
288 return start;
289 }
290
291 /**
292 * Splits this <CODE>PdfChunk</CODE> if it's too long for the given width.
293 * <P>
294 * Returns <VAR>null</VAR> if the <CODE>PdfChunk</CODE> wasn't truncated.
295 *
296 * @param width a given width
297 * @return the <CODE>PdfChunk</CODE> that doesn't fit into the width.
298 */
299
300 PdfChunk split(float width) {
301 newlineSplit = false;
302 if (image != null) {
303 if (image.getScaledWidth() > width) {
304 PdfChunk pc = new PdfChunk(Chunk.OBJECT_REPLACEMENT_CHARACTER, this);
305 value = "";
306 attributes = new HashMap();
307 image = null;
308 font = PdfFont.getDefaultFont();
309 return pc;
310 }
311 else
312 return null;
313 }
314 HyphenationEvent hyphenationEvent = (HyphenationEvent)noStroke.get(Chunk.HYPHENATION);
315 int currentPosition = 0;
316 int splitPosition = -1;
317 float currentWidth = 0;
318
319 // loop over all the characters of a string
320 // or until the totalWidth is reached
321 int lastSpace = -1;
322 float lastSpaceWidth = 0;
323 int length = value.length();
324 char valueArray[] = value.toCharArray();
325 char character = 0;
326 BaseFont ft = font.getFont();
327 boolean surrogate = false;
328 if (ft.getFontType() == BaseFont.FONT_TYPE_CJK && ft.getUnicodeEquivalent(' ') != ' ') {
329 while (currentPosition < length) {
330 // the width of every character is added to the currentWidth
331 char cidChar = valueArray[currentPosition];
332 character = (char)ft.getUnicodeEquivalent(cidChar);
333 // if a newLine or carriageReturn is encountered
334 if (character == '\n') {
335 newlineSplit = true;
336 String returnValue = value.substring(currentPosition + 1);
337 value = value.substring(0, currentPosition);
338 if (value.length() < 1) {
339 value = "\u0001";
340 }
341 PdfChunk pc = new PdfChunk(returnValue, this);
342 return pc;
343 }
344 currentWidth += font.width(cidChar);
345 if (character == ' ') {
346 lastSpace = currentPosition + 1;
347 lastSpaceWidth = currentWidth;
348 }
349 if (currentWidth > width)
350 break;
351 // if a split-character is encountered, the splitPosition is altered
352 if (splitCharacter.isSplitCharacter(0, currentPosition, length, valueArray, thisChunk))
353 splitPosition = currentPosition + 1;
354 currentPosition++;
355 }
356 }
357 else {
358 while (currentPosition < length) {
359 // the width of every character is added to the currentWidth
360 character = valueArray[currentPosition];
361 // if a newLine or carriageReturn is encountered
362 if (character == '\r' || character == '\n') {
363 newlineSplit = true;
364 int inc = 1;
365 if (character == '\r' && currentPosition + 1 < length && valueArray[currentPosition + 1] == '\n')
366 inc = 2;
367 String returnValue = value.substring(currentPosition + inc);
368 value = value.substring(0, currentPosition);
369 if (value.length() < 1) {
370 value = " ";
371 }
372 PdfChunk pc = new PdfChunk(returnValue, this);
373 return pc;
374 }
375 surrogate = Utilities.isSurrogatePair(valueArray, currentPosition);
376 if (surrogate)
377 currentWidth += font.width(Utilities.convertToUtf32(valueArray[currentPosition], valueArray[currentPosition + 1]));
378 else
379 currentWidth += font.width(character);
380 if (character == ' ') {
381 lastSpace = currentPosition + 1;
382 lastSpaceWidth = currentWidth;
383 }
384 if (surrogate)
385 currentPosition++;
386 if (currentWidth > width)
387 break;
388 // if a split-character is encountered, the splitPosition is altered
389 if (splitCharacter.isSplitCharacter(0, currentPosition, length, valueArray, null))
390 splitPosition = currentPosition + 1;
391 currentPosition++;
392 }
393 }
394
395 // if all the characters fit in the total width, null is returned (there is no overflow)
396 if (currentPosition == length) {
397 return null;
398 }
399 // otherwise, the string has to be truncated
400 if (splitPosition < 0) {
401 String returnValue = value;
402 value = "";
403 PdfChunk pc = new PdfChunk(returnValue, this);
404 return pc;
405 }
406 if (lastSpace > splitPosition && splitCharacter.isSplitCharacter(0, 0, 1, singleSpace, null))
407 splitPosition = lastSpace;
408 if (hyphenationEvent != null && lastSpace >= 0 && lastSpace < currentPosition) {
409 int wordIdx = getWord(value, lastSpace);
410 if (wordIdx > lastSpace) {
411 String pre = hyphenationEvent.getHyphenatedWordPre(value.substring(lastSpace, wordIdx), font.getFont(), font.size(), width - lastSpaceWidth);
412 String post = hyphenationEvent.getHyphenatedWordPost();
413 if (pre.length() > 0) {
414 String returnValue = post + value.substring(wordIdx);
415 value = trim(value.substring(0, lastSpace) + pre);
416 PdfChunk pc = new PdfChunk(returnValue, this);
417 return pc;
418 }
419 }
420 }
421 String returnValue = value.substring(splitPosition);
422 value = trim(value.substring(0, splitPosition));
423 PdfChunk pc = new PdfChunk(returnValue, this);
424 return pc;
425 }
426
427 /**
428 * Truncates this <CODE>PdfChunk</CODE> if it's too long for the given width.
429 * <P>
430 * Returns <VAR>null</VAR> if the <CODE>PdfChunk</CODE> wasn't truncated.
431 *
432 * @param width a given width
433 * @return the <CODE>PdfChunk</CODE> that doesn't fit into the width.
434 */
435
436 PdfChunk truncate(float width) {
437 if (image != null) {
438 if (image.getScaledWidth() > width) {
439 PdfChunk pc = new PdfChunk("", this);
440 value = "";
441 attributes.remove(Chunk.IMAGE);
442 image = null;
443 font = PdfFont.getDefaultFont();
444 return pc;
445 }
446 else
447 return null;
448 }
449
450 int currentPosition = 0;
451 float currentWidth = 0;
452
453 // it's no use trying to split if there isn't even enough place for a space
454 if (width < font.width()) {
455 String returnValue = value.substring(1);
456 value = value.substring(0, 1);
457 PdfChunk pc = new PdfChunk(returnValue, this);
458 return pc;
459 }
460
461 // loop over all the characters of a string
462 // or until the totalWidth is reached
463 int length = value.length();
464 boolean surrogate = false;
465 char character;
466 while (currentPosition < length) {
467 // the width of every character is added to the currentWidth
468 surrogate = Utilities.isSurrogatePair(value, currentPosition);
469 if (surrogate)
470 currentWidth += font.width(Utilities.convertToUtf32(value, currentPosition));
471 else
472 currentWidth += font.width(value.charAt(currentPosition));
473 if (currentWidth > width)
474 break;
475 if (surrogate)
476 currentPosition++;
477 currentPosition++;
478 }
479
480 // if all the characters fit in the total width, null is returned (there is no overflow)
481 if (currentPosition == length) {
482 return null;
483 }
484
485 // otherwise, the string has to be truncated
486 //currentPosition -= 2;
487 // we have to chop off minimum 1 character from the chunk
488 if (currentPosition == 0) {
489 currentPosition = 1;
490 if (surrogate)
491 ++currentPosition;
492 }
493 String returnValue = value.substring(currentPosition);
494 value = value.substring(0, currentPosition);
495 PdfChunk pc = new PdfChunk(returnValue, this);
496 return pc;
497 }
498
499 // methods to retrieve the membervariables
500
501 /**
502 * Returns the font of this <CODE>Chunk</CODE>.
503 *
504 * @return a <CODE>PdfFont</CODE>
505 */
506
507 PdfFont font() {
508 return font;
509 }
510
511 /**
512 * Returns the color of this <CODE>Chunk</CODE>.
513 *
514 * @return a <CODE>Color</CODE>
515 */
516
517 Color color() {
518 return (Color)noStroke.get(Chunk.COLOR);
519 }
520
521 /**
522 * Returns the width of this <CODE>PdfChunk</CODE>.
523 *
524 * @return a width
525 */
526
527 float width() {
528 return font.width(value);
529 }
530
531 /**
532 * Checks if the <CODE>PdfChunk</CODE> split was caused by a newline.
533 * @return <CODE>true</CODE> if the <CODE>PdfChunk</CODE> split was caused by a newline.
534 */
535
536 public boolean isNewlineSplit()
537 {
538 return newlineSplit;
539 }
540
541 /**
542 * Gets the width of the <CODE>PdfChunk</CODE> taking into account the
543 * extra character and word spacing.
544 * @param charSpacing the extra character spacing
545 * @param wordSpacing the extra word spacing
546 * @return the calculated width
547 */
548
549 public float getWidthCorrected(float charSpacing, float wordSpacing)
550 {
551 if (image != null) {
552 return image.getScaledWidth() + charSpacing;
553 }
554 int numberOfSpaces = 0;
555 int idx = -1;
556 while ((idx = value.indexOf(' ', idx + 1)) >= 0)
557 ++numberOfSpaces;
558 return width() + (value.length() * charSpacing + numberOfSpaces * wordSpacing);
559 }
560
561 /**
562 * Gets the text displacement relative to the baseline.
563 * @return a displacement in points
564 */
565 public float getTextRise() {
566 Float f = (Float) getAttribute(Chunk.SUBSUPSCRIPT);
567 if (f != null) {
568 return f.floatValue();
569 }
570 return 0.0f;
571 }
572
573 /**
574 * Trims the last space.
575 * @return the width of the space trimmed, otherwise 0
576 */
577
578 public float trimLastSpace()
579 {
580 BaseFont ft = font.getFont();
581 if (ft.getFontType() == BaseFont.FONT_TYPE_CJK && ft.getUnicodeEquivalent(' ') != ' ') {
582 if (value.length() > 1 && value.endsWith("\u0001")) {
583 value = value.substring(0, value.length() - 1);
584 return font.width('\u0001');
585 }
586 }
587 else {
588 if (value.length() > 1 && value.endsWith(" ")) {
589 value = value.substring(0, value.length() - 1);
590 return font.width(' ');
591 }
592 }
593 return 0;
594 }
595 public float trimFirstSpace()
596 {
597 BaseFont ft = font.getFont();
598 if (ft.getFontType() == BaseFont.FONT_TYPE_CJK && ft.getUnicodeEquivalent(' ') != ' ') {
599 if (value.length() > 1 && value.startsWith("\u0001")) {
600 value = value.substring(1);
601 return font.width('\u0001');
602 }
603 }
604 else {
605 if (value.length() > 1 && value.startsWith(" ")) {
606 value = value.substring(1);
607 return font.width(' ');
608 }
609 }
610 return 0;
611 }
612
613 /**
614 * Gets an attribute. The search is made in <CODE>attributes</CODE>
615 * and <CODE>noStroke</CODE>.
616 * @param name the attribute key
617 * @return the attribute value or null if not found
618 */
619
620 Object getAttribute(String name)
621 {
622 if (attributes.containsKey(name))
623 return attributes.get(name);
624 return noStroke.get(name);
625 }
626
627 /**
628 *Checks if the attribute exists.
629 * @param name the attribute key
630 * @return <CODE>true</CODE> if the attribute exists
631 */
632
633 boolean isAttribute(String name)
634 {
635 if (attributes.containsKey(name))
636 return true;
637 return noStroke.containsKey(name);
638 }
639
640 /**
641 * Checks if this <CODE>PdfChunk</CODE> needs some special metrics handling.
642 * @return <CODE>true</CODE> if this <CODE>PdfChunk</CODE> needs some special metrics handling.
643 */
644
645 boolean isStroked()
646 {
647 return (!attributes.isEmpty());
648 }
649
650 /**
651 * Checks if this <CODE>PdfChunk</CODE> is a Separator Chunk.
652 * @return true if this chunk is a separator.
653 * @since 2.1.2
654 */
655 boolean isSeparator() {
656 return isAttribute(Chunk.SEPARATOR);
657 }
658
659 /**
660 * Checks if this <CODE>PdfChunk</CODE> is a horizontal Separator Chunk.
661 * @return true if this chunk is a horizontal separator.
662 * @since 2.1.2
663 */
664 boolean isHorizontalSeparator() {
665 if (isAttribute(Chunk.SEPARATOR)) {
666 Object[] o = (Object[])getAttribute(Chunk.SEPARATOR);
667 return !((Boolean)o[1]).booleanValue();
668 }
669 return false;
670 }
671
672 /**
673 * Checks if this <CODE>PdfChunk</CODE> is a tab Chunk.
674 * @return true if this chunk is a separator.
675 * @since 2.1.2
676 */
677 boolean isTab() {
678 return isAttribute(Chunk.TAB);
679 }
680
681 /**
682 * Correction for the tab position based on the left starting position.
683 * @param newValue the new value for the left X.
684 * @since 2.1.2
685 */
686 void adjustLeft(float newValue) {
687 Object[] o = (Object[])attributes.get(Chunk.TAB);
688 if (o != null) {
689 attributes.put(Chunk.TAB, new Object[]{o[0], o[1], o[2], new Float(newValue)});
690 }
691 }
692
693 /**
694 * Checks if there is an image in the <CODE>PdfChunk</CODE>.
695 * @return <CODE>true</CODE> if an image is present
696 */
697
698 boolean isImage()
699 {
700 return image != null;
701 }
702
703 /**
704 * Gets the image in the <CODE>PdfChunk</CODE>.
705 * @return the image or <CODE>null</CODE>
706 */
707
708 Image getImage()
709 {
710 return image;
711 }
712
713 /**
714 * Sets the image offset in the x direction
715 * @param offsetX the image offset in the x direction
716 */
717
718 void setImageOffsetX(float offsetX)
719 {
720 this.offsetX = offsetX;
721 }
722
723 /**
724 * Gets the image offset in the x direction
725 * @return the image offset in the x direction
726 */
727
728 float getImageOffsetX()
729 {
730 return offsetX;
731 }
732
733 /**
734 * Sets the image offset in the y direction
735 * @param offsetY the image offset in the y direction
736 */
737
738 void setImageOffsetY(float offsetY)
739 {
740 this.offsetY = offsetY;
741 }
742
743 /**
744 * Gets the image offset in the y direction
745 * @return Gets the image offset in the y direction
746 */
747
748 float getImageOffsetY()
749 {
750 return offsetY;
751 }
752
753 /**
754 * sets the value.
755 * @param value content of the Chunk
756 */
757
758 void setValue(String value)
759 {
760 this.value = value;
761 }
762
763 /**
764 * @see java.lang.Object#toString()
765 */
766 public String toString() {
767 return value;
768 }
769
770 /**
771 * Tells you if this string is in Chinese, Japanese, Korean or Identity-H.
772 * @return true if the Chunk has a special encoding
773 */
774
775 boolean isSpecialEncoding() {
776 return encoding.equals(CJKFont.CJK_ENCODING) || encoding.equals(BaseFont.IDENTITY_H);
777 }
778
779 /**
780 * Gets the encoding of this string.
781 *
782 * @return a <CODE>String</CODE>
783 */
784
785 String getEncoding() {
786 return encoding;
787 }
788
789 int length() {
790 return value.length();
791 }
792
793 int lengthUtf32() {
794 if (!BaseFont.IDENTITY_H.equals(encoding))
795 return value.length();
796 int total = 0;
797 int len = value.length();
798 for (int k = 0; k < len; ++k) {
799 if (Utilities.isSurrogateHigh(value.charAt(k)))
800 ++k;
801 ++total;
802 }
803 return total;
804 }
805
806 boolean isExtSplitCharacter(int start, int current, int end, char[] cc, PdfChunk[] ck) {
807 return splitCharacter.isSplitCharacter(start, current, end, cc, ck);
808 }
809
810 /**
811 * Removes all the <VAR>' '</VAR> and <VAR>'-'</VAR>-characters on the right of a <CODE>String</CODE>.
812 * <P>
813 * @param string the <CODE>String<CODE> that has to be trimmed.
814 * @return the trimmed <CODE>String</CODE>
815 */
816 String trim(String string) {
817 BaseFont ft = font.getFont();
818 if (ft.getFontType() == BaseFont.FONT_TYPE_CJK && ft.getUnicodeEquivalent(' ') != ' ') {
819 while (string.endsWith("\u0001")) {
820 string = string.substring(0, string.length() - 1);
821 }
822 }
823 else {
824 while (string.endsWith(" ") || string.endsWith("\t")) {
825 string = string.substring(0, string.length() - 1);
826 }
827 }
828 return string;
829 }
830
831 public boolean changeLeading() {
832 return changeLeading;
833 }
834
835 float getCharWidth(int c) {
836 if (noPrint(c))
837 return 0;
838 return font.width(c);
839 }
840
841 public static boolean noPrint(int c) {
842 return ((c >= 0x200b && c <= 0x200f) || (c >= 0x202a && c <= 0x202e));
843 }
844
845 }
846