using System; using System.Collections; using System.util; using iTextSharp.text; /* * $Id: PdfChunk.cs,v 1.11 2008/05/22 22:11:10 psoares33 Exp $ * * * Copyright 1999, 2000, 2001, 2002 Bruno Lowagie * * The contents of this file are subject to the Mozilla Public License Version 1.1 * (the "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the License. * * The Original Code is 'iText, a free JAVA-PDF library'. * * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie. * All Rights Reserved. * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved. * * Contributor(s): all the names of the contributors are added in the source code * where applicable. * * Alternatively, the contents of this file may be used under the terms of the * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the * provisions of LGPL are applicable instead of those above. If you wish to * allow use of your version of this file only under the terms of the LGPL * License and not to allow others to use your version of this file under * the MPL, indicate your decision by deleting the provisions above and * replace them with the notice and other provisions required by the LGPL. * If you do not delete the provisions above, a recipient may use your version * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE. * * This library is free software; you can redistribute it and/or modify it * under the terms of the MPL as stated above or under the terms of the GNU * Library General Public License as published by the Free Software Foundation; * either version 2 of the License, or any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more * details. * * If you didn't download this code from the following link, you should check if * you aren't using an obsolete version: * http://www.lowagie.com/iText/ */ namespace iTextSharp.text.pdf { /** * A PdfChunk is the PDF translation of a Chunk. *

* A PdfChunk is a PdfString in a certain * PdfFont and Color. * * @see PdfString * @see PdfFont * @see iTextSharp.text.Chunk * @see iTextSharp.text.Font */ public class PdfChunk { private static char[] singleSpace = {' '}; private static PdfChunk[] thisChunk = new PdfChunk[1]; private const float ITALIC_ANGLE = 0.21256f; /** The allowed attributes in variable attributes. */ private static Hashtable keysAttributes = new Hashtable(); /** The allowed attributes in variable noStroke. */ private static Hashtable keysNoStroke = new Hashtable(); static PdfChunk() { keysAttributes.Add(Chunk.ACTION, null); keysAttributes.Add(Chunk.UNDERLINE, null); keysAttributes.Add(Chunk.REMOTEGOTO, null); keysAttributes.Add(Chunk.LOCALGOTO, null); keysAttributes.Add(Chunk.LOCALDESTINATION, null); keysAttributes.Add(Chunk.GENERICTAG, null); keysAttributes.Add(Chunk.NEWPAGE, null); keysAttributes.Add(Chunk.IMAGE, null); keysAttributes.Add(Chunk.BACKGROUND, null); keysAttributes.Add(Chunk.PDFANNOTATION, null); keysAttributes.Add(Chunk.SKEW, null); keysAttributes.Add(Chunk.HSCALE, null); keysAttributes.Add(Chunk.SEPARATOR, null); keysAttributes.Add(Chunk.TAB, null); keysNoStroke.Add(Chunk.SUBSUPSCRIPT, null); keysNoStroke.Add(Chunk.SPLITCHARACTER, null); keysNoStroke.Add(Chunk.HYPHENATION, null); keysNoStroke.Add(Chunk.TEXTRENDERMODE, null); } // membervariables /** The value of this object. */ protected string value = PdfObject.NOTHING; /** The encoding. */ protected string encoding = BaseFont.WINANSI; /** The font for this PdfChunk. */ protected PdfFont font; protected BaseFont baseFont; protected ISplitCharacter splitCharacter; /** * Metric attributes. *

* This attributes require the mesurement of characters widths when rendering * such as underline. */ protected Hashtable attributes = new Hashtable(); /** * Non metric attributes. *

* This attributes do not require the mesurement of characters widths when rendering * such as Color. */ protected Hashtable noStroke = new Hashtable(); /** true if the chunk split was cause by a newline. */ protected bool newlineSplit; /** The image in this PdfChunk, if it has one */ protected Image image; /** The offset in the x direction for the image */ protected float offsetX; /** The offset in the y direction for the image */ protected float offsetY; /** Indicates if the height and offset of the Image has to be taken into account */ protected bool changeLeading = false; // constructors /** * Constructs a PdfChunk-object. * * @param string the content of the PdfChunk-object * @param font the PdfFont * @param attributes the metrics attributes * @param noStroke the non metric attributes */ internal PdfChunk(string str, PdfChunk other) { thisChunk[0] = this; value = str; this.font = other.font; this.attributes = other.attributes; this.noStroke = other.noStroke; this.baseFont = other.baseFont; Object[] obj = (Object[])attributes[Chunk.IMAGE]; if (obj == null) image = null; else { image = (Image)obj[0]; offsetX = (float)obj[1]; offsetY = (float)obj[2]; changeLeading = (bool)obj[3]; } encoding = font.Font.Encoding; splitCharacter = (ISplitCharacter)noStroke[Chunk.SPLITCHARACTER]; if (splitCharacter == null) splitCharacter = DefaultSplitCharacter.DEFAULT; } /** * Constructs a PdfChunk-object. * * @param chunk the original Chunk-object * @param action the PdfAction if the Chunk comes from an Anchor */ internal PdfChunk(Chunk chunk, PdfAction action) { thisChunk[0] = this; value = chunk.Content; Font f = chunk.Font; float size = f.Size; if (size == iTextSharp.text.Font.UNDEFINED) size = 12; baseFont = f.BaseFont; BaseFont bf = f.BaseFont; int style = f.Style; if (style == iTextSharp.text.Font.UNDEFINED) { style = iTextSharp.text.Font.NORMAL; } if (baseFont == null) { // translation of the font-family to a PDF font-family baseFont = f.GetCalculatedBaseFont(false); } else{ // bold simulation if ((style & iTextSharp.text.Font.BOLD) != 0) attributes[Chunk.TEXTRENDERMODE] = new Object[]{PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE, size / 30f, null}; // italic simulation if ((style & iTextSharp.text.Font.ITALIC) != 0) attributes[Chunk.SKEW] = new float[]{0, ITALIC_ANGLE}; } font = new PdfFont(baseFont, size); // other style possibilities Hashtable attr = chunk.Attributes; if (attr != null) { foreach (DictionaryEntry entry in attr) { string name = (string)entry.Key; if (keysAttributes.ContainsKey(name)) { attributes[name] = entry.Value; } else if (keysNoStroke.ContainsKey(name)) { noStroke[name] = entry.Value; } } if ("".Equals(attr[Chunk.GENERICTAG])) { attributes[Chunk.GENERICTAG] = chunk.Content; } } if (f.IsUnderlined()) { Object[] obj = {null, new float[]{0, 1f / 15, 0, -1f / 3, 0}}; Object[][] unders = Utilities.AddToArray((Object[][])attributes[Chunk.UNDERLINE], obj); attributes[Chunk.UNDERLINE] = unders; } if (f.IsStrikethru()) { Object[] obj = {null, new float[]{0, 1f / 15, 0, 1f / 3, 0}}; Object[][] unders = Utilities.AddToArray((Object[][])attributes[Chunk.UNDERLINE], obj); attributes[Chunk.UNDERLINE] = unders; } if (action != null) attributes[Chunk.ACTION] = action; // the color can't be stored in a PdfFont noStroke[Chunk.COLOR] = f.Color; noStroke[Chunk.ENCODING] = font.Font.Encoding; Object[] obj2 = (Object[])attributes[Chunk.IMAGE]; if (obj2 == null) image = null; else { attributes.Remove(Chunk.HSCALE); // images are scaled in other ways image = (Image)obj2[0]; offsetX = ((float)obj2[1]); offsetY = ((float)obj2[2]); changeLeading = (bool)obj2[3]; } font.Image = image; object hs = attributes[Chunk.HSCALE]; if (hs != null) font.HorizontalScaling = (float)hs; encoding = font.Font.Encoding; splitCharacter = (ISplitCharacter)noStroke[Chunk.SPLITCHARACTER]; if (splitCharacter == null) splitCharacter = DefaultSplitCharacter.DEFAULT; } // methods /** Gets the Unicode equivalent to a CID. * The (inexistent) CID is translated as '\n'. * It has only meaning with CJK fonts with Identity encoding. * @param c the CID code * @return the Unicode equivalent */ public int GetUnicodeEquivalent(int c) { return baseFont.GetUnicodeEquivalent(c); } protected int GetWord(string text, int start) { int len = text.Length; while (start < len) { if (!char.IsLetter(text[start])) break; ++start; } return start; } /** * Splits this PdfChunk if it's too long for the given width. *

* Returns null if the PdfChunk wasn't truncated. * * @param width a given width * @return the PdfChunk that doesn't fit into the width. */ internal PdfChunk Split(float width) { newlineSplit = false; if (image != null) { if (image.ScaledWidth > width) { PdfChunk pc = new PdfChunk(Chunk.OBJECT_REPLACEMENT_CHARACTER, this); value = ""; attributes = new Hashtable(); image = null; font = PdfFont.DefaultFont; return pc; } else return null; } IHyphenationEvent hyphenationEvent = (IHyphenationEvent)noStroke[Chunk.HYPHENATION]; int currentPosition = 0; int splitPosition = -1; float currentWidth = 0; // loop over all the characters of a string // or until the totalWidth is reached int lastSpace = -1; float lastSpaceWidth = 0; int length = value.Length; char[] valueArray = value.ToCharArray(); char character = (char)0; BaseFont ft = font.Font; bool surrogate = false; if (ft.FontType == BaseFont.FONT_TYPE_CJK && ft.GetUnicodeEquivalent(' ') != ' ') { while (currentPosition < length) { // the width of every character is added to the currentWidth char cidChar = valueArray[currentPosition]; character = (char)ft.GetUnicodeEquivalent(cidChar); // if a newLine or carriageReturn is encountered if (character == '\n') { newlineSplit = true; string returnValue = value.Substring(currentPosition + 1); value = value.Substring(0, currentPosition); if (value.Length < 1) { value = "\u0001"; } PdfChunk pc = new PdfChunk(returnValue, this); return pc; } currentWidth += font.Width(cidChar); if (character == ' ') { lastSpace = currentPosition + 1; lastSpaceWidth = currentWidth; } if (currentWidth > width) break; // if a split-character is encountered, the splitPosition is altered if (splitCharacter.IsSplitCharacter(0, currentPosition, length, valueArray, thisChunk)) splitPosition = currentPosition + 1; currentPosition++; } } else { while (currentPosition < length) { // the width of every character is added to the currentWidth character = valueArray[currentPosition]; // if a newLine or carriageReturn is encountered if (character == '\r' || character == '\n') { newlineSplit = true; int inc = 1; if (character == '\r' && currentPosition + 1 < length && valueArray[currentPosition + 1] == '\n') inc = 2; string returnValue = value.Substring(currentPosition + inc); value = value.Substring(0, currentPosition); if (value.Length < 1) { value = " "; } PdfChunk pc = new PdfChunk(returnValue, this); return pc; } surrogate = Utilities.IsSurrogatePair(valueArray, currentPosition); if (surrogate) currentWidth += font.Width(Utilities.ConvertToUtf32(valueArray[currentPosition], valueArray[currentPosition + 1])); else currentWidth += font.Width(character); if (character == ' ') { lastSpace = currentPosition + 1; lastSpaceWidth = currentWidth; } if (surrogate) currentPosition++; if (currentWidth > width) break; // if a split-character is encountered, the splitPosition is altered if (splitCharacter.IsSplitCharacter(0, currentPosition, length, valueArray, null)) splitPosition = currentPosition + 1; currentPosition++; } } // if all the characters fit in the total width, null is returned (there is no overflow) if (currentPosition == length) { return null; } // otherwise, the string has to be truncated if (splitPosition < 0) { string returnValue = value; value = ""; PdfChunk pc = new PdfChunk(returnValue, this); return pc; } if (lastSpace > splitPosition && splitCharacter.IsSplitCharacter(0, 0, 1, singleSpace, null)) splitPosition = lastSpace; if (hyphenationEvent != null && lastSpace >= 0 && lastSpace < currentPosition) { int wordIdx = GetWord(value, lastSpace); if (wordIdx > lastSpace) { string pre = hyphenationEvent.GetHyphenatedWordPre(value.Substring(lastSpace, wordIdx - lastSpace), font.Font, font.Size, width - lastSpaceWidth); string post = hyphenationEvent.HyphenatedWordPost; if (pre.Length > 0) { string returnValue = post + value.Substring(wordIdx); value = Trim(value.Substring(0, lastSpace) + pre); PdfChunk pc = new PdfChunk(returnValue, this); return pc; } } } string retVal = value.Substring(splitPosition); value = Trim(value.Substring(0, splitPosition)); PdfChunk tmp = new PdfChunk(retVal, this); return tmp; } /** * Truncates this PdfChunk if it's too long for the given width. *

* Returns null if the PdfChunk wasn't truncated. * * @param width a given width * @return the PdfChunk that doesn't fit into the width. */ internal PdfChunk Truncate(float width) { if (image != null) { if (image.ScaledWidth > width) { PdfChunk pc = new PdfChunk("", this); value = ""; attributes.Remove(Chunk.IMAGE); image = null; font = PdfFont.DefaultFont; return pc; } else return null; } int currentPosition = 0; float currentWidth = 0; // it's no use trying to split if there isn't even enough place for a space if (width < font.Width()) { string returnValue = value.Substring(1); value = value.Substring(0, 1); PdfChunk pc = new PdfChunk(returnValue, this); return pc; } // loop over all the characters of a string // or until the totalWidth is reached int length = value.Length; bool surrogate = false; while (currentPosition < length) { // the width of every character is added to the currentWidth surrogate = Utilities.IsSurrogatePair(value, currentPosition); if (surrogate) currentWidth += font.Width(Utilities.ConvertToUtf32(value, currentPosition)); else currentWidth += font.Width(value[currentPosition]); if (currentWidth > width) break; if (surrogate) currentPosition++; currentPosition++; } // if all the characters fit in the total width, null is returned (there is no overflow) if (currentPosition == length) { return null; } // otherwise, the string has to be truncated //currentPosition -= 2; // we have to chop off minimum 1 character from the chunk if (currentPosition == 0) { currentPosition = 1; if (surrogate) ++currentPosition; } string retVal = value.Substring(currentPosition); value = value.Substring(0, currentPosition); PdfChunk tmp = new PdfChunk(retVal, this); return tmp; } // methods to retrieve the membervariables /** * Returns the font of this Chunk. * * @return a PdfFont */ internal PdfFont Font { get { return font; } } /** * Returns the color of this Chunk. * * @return a Color */ internal Color Color { get { return (Color)noStroke[Chunk.COLOR]; } } /** * Returns the width of this PdfChunk. * * @return a width */ internal float Width { get { return font.Width(this.value); } } /** * Checks if the PdfChunk split was caused by a newline. * @return true if the PdfChunk split was caused by a newline. */ public bool IsNewlineSplit() { return newlineSplit; } /** * Gets the width of the PdfChunk taking into account the * extra character and word spacing. * @param charSpacing the extra character spacing * @param wordSpacing the extra word spacing * @return the calculated width */ public float GetWidthCorrected(float charSpacing, float wordSpacing) { if (image != null) { return image.ScaledWidth + charSpacing; } int numberOfSpaces = 0; int idx = -1; while ((idx = value.IndexOf(' ', idx + 1)) >= 0) ++numberOfSpaces; return Width + (value.Length * charSpacing + numberOfSpaces * wordSpacing); } /** * Gets the text displacement relatiev to the baseline. * @return a displacement in points */ public float TextRise { get { object f = GetAttribute(Chunk.SUBSUPSCRIPT); if (f != null) { return (float)f; } return 0.0f; } } /** * Trims the last space. * @return the width of the space trimmed, otherwise 0 */ public float TrimLastSpace() { BaseFont ft = font.Font; if (ft.FontType == BaseFont.FONT_TYPE_CJK && ft.GetUnicodeEquivalent(' ') != ' ') { if (value.Length > 1 && value.EndsWith("\u0001")) { value = value.Substring(0, value.Length - 1); return font.Width('\u0001'); } } else { if (value.Length > 1 && value.EndsWith(" ")) { value = value.Substring(0, value.Length - 1); return font.Width(' '); } } return 0; } public float TrimFirstSpace() { BaseFont ft = font.Font; if (ft.FontType == BaseFont.FONT_TYPE_CJK && ft.GetUnicodeEquivalent(' ') != ' ') { if (value.Length > 1 && value.StartsWith("\u0001")) { value = value.Substring(1); return font.Width('\u0001'); } } else { if (value.Length > 1 && value.StartsWith(" ")) { value = value.Substring(1); return font.Width(' '); } } return 0; } /** * Gets an attribute. The search is made in attributes * and noStroke. * @param name the attribute key * @return the attribute value or null if not found */ internal Object GetAttribute(string name) { if (attributes.ContainsKey(name)) return attributes[name]; return noStroke[name]; } /** *Checks if the attribute exists. * @param name the attribute key * @return true if the attribute exists */ internal bool IsAttribute(string name) { if (attributes.ContainsKey(name)) return true; return noStroke.ContainsKey(name); } /** * Checks if this PdfChunk needs some special metrics handling. * @return true if this PdfChunk needs some special metrics handling. */ internal bool IsStroked() { return (attributes.Count > 0); } /** * Checks if this PdfChunk is a Separator Chunk. * @return true if this chunk is a separator. * @since 2.1.2 */ internal bool IsSeparator() { return IsAttribute(Chunk.SEPARATOR); } /** * Checks if this PdfChunk is a horizontal Separator Chunk. * @return true if this chunk is a horizontal separator. * @since 2.1.2 */ internal bool IsHorizontalSeparator() { if (IsAttribute(Chunk.SEPARATOR)) { Object[] o = (Object[])GetAttribute(Chunk.SEPARATOR); return !(bool)o[1]; } return false; } /** * Checks if this PdfChunk is a tab Chunk. * @return true if this chunk is a separator. * @since 2.1.2 */ internal bool IsTab() { return IsAttribute(Chunk.TAB); } /** * Correction for the tab position based on the left starting position. * @param newValue the new value for the left X. * @since 2.1.2 */ internal void AdjustLeft(float newValue) { Object[] o = (Object[])attributes[Chunk.TAB]; if (o != null) { attributes[Chunk.TAB] = new Object[]{o[0], o[1], o[2], newValue}; } } /** * Checks if there is an image in the PdfChunk. * @return true if an image is present */ internal bool IsImage() { return image != null; } /** * Gets the image in the PdfChunk. * @return the image or null */ internal Image Image { get { return image; } } /** * Gets the image offset in the x direction * @return the image offset in the x direction */ internal float ImageOffsetX { get { return offsetX; } set { this.offsetX = value; } } /** * Gets the image offset in the y direction * @return Gets the image offset in the y direction */ internal float ImageOffsetY { get { return offsetY; } set { this.offsetY = value; } } /** * sets the value. */ internal string Value { set { this.value = value; } } public override string ToString() { return value; } /** * Tells you if this string is in Chinese, Japanese, Korean or Identity-H. */ internal bool IsSpecialEncoding() { return encoding.Equals(CJKFont.CJK_ENCODING) || encoding.Equals(BaseFont.IDENTITY_H); } /** * Gets the encoding of this string. * * @return a string */ internal string Encoding { get { return encoding; } } internal int Length { get { return value.Length; } } internal int LengthUtf32 { get { if (!BaseFont.IDENTITY_H.Equals(encoding)) return value.Length; int total = 0; int len = value.Length; for (int k = 0; k < len; ++k) { if (Utilities.IsSurrogateHigh(value[k])) ++k; ++total; } return total; } } internal bool IsExtSplitCharacter(int start, int current, int end, char[] cc, PdfChunk[] ck) { return splitCharacter.IsSplitCharacter(start, current, end, cc, ck); } /** * Removes all the ' ' and '-'-characters on the right of a string. *

* @param string the string that has to be trimmed. * @return the trimmed string */ internal string Trim(string str) { BaseFont ft = font.Font; if (ft.FontType == BaseFont.FONT_TYPE_CJK && ft.GetUnicodeEquivalent(' ') != ' ') { while (str.EndsWith("\u0001")) { str = str.Substring(0, str.Length - 1); } } else { while (str.EndsWith(" ") || str.EndsWith("\t")) { str = str.Substring(0, str.Length - 1); } } return str; } public bool ChangeLeading { get { return changeLeading; } } internal float GetCharWidth(int c) { if (NoPrint(c)) return 0; return font.Width(c); } public static bool NoPrint(int c) { return ((c >= 0x200b && c <= 0x200f) || (c >= 0x202a && c <= 0x202e)); } } }