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[] = {nullnew 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[] = {nullnew 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