All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.lowagie.text.pdf.PdfChunk Maven / Gradle / Ivy

/*
 * $Id: PdfChunk.java 4092 2009-11-11 17:58:16Z psoares33 $
 *
 * 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/
 */

package com.lowagie.text.pdf;

import java.awt.Color;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import com.lowagie.text.Chunk;
import com.lowagie.text.Font;
import com.lowagie.text.Image;
import com.lowagie.text.SplitCharacter;
import com.lowagie.text.Utilities;

/**
 * A PdfChunk is the PDF translation of a Chunk.
 * 

* A PdfChunk is a PdfString in a certain * PdfFont and Color. * * @see PdfString * @see com.lowagie.text.Chunk * @see com.lowagie.text.Font */ public class PdfChunk { private static final char singleSpace[] = {' '}; private static final PdfChunk thisChunk[] = new PdfChunk[1]; private static final float ITALIC_ANGLE = 0.21256f; /** The allowed attributes in variable attributes. */ private static final HashMap keysAttributes = new HashMap(); /** The allowed attributes in variable noStroke. */ private static final HashMap keysNoStroke = new HashMap(); static { keysAttributes.put(Chunk.ACTION, null); keysAttributes.put(Chunk.UNDERLINE, null); keysAttributes.put(Chunk.REMOTEGOTO, null); keysAttributes.put(Chunk.LOCALGOTO, null); keysAttributes.put(Chunk.LOCALDESTINATION, null); keysAttributes.put(Chunk.GENERICTAG, null); keysAttributes.put(Chunk.NEWPAGE, null); keysAttributes.put(Chunk.IMAGE, null); keysAttributes.put(Chunk.BACKGROUND, null); keysAttributes.put(Chunk.PDFANNOTATION, null); keysAttributes.put(Chunk.SKEW, null); keysAttributes.put(Chunk.HSCALE, null); keysAttributes.put(Chunk.SEPARATOR, null); keysAttributes.put(Chunk.TAB, null); keysAttributes.put(Chunk.CHAR_SPACING, null); keysNoStroke.put(Chunk.SUBSUPSCRIPT, null); keysNoStroke.put(Chunk.SPLITCHARACTER, null); keysNoStroke.put(Chunk.HYPHENATION, null); keysNoStroke.put(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 SplitCharacter splitCharacter; /** * Metric attributes. *

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

* This attributes do not require the measurement of characters widths when rendering * such as Color. */ protected HashMap noStroke = new HashMap(); /** true if the chunk split was cause by a newline. */ protected boolean 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 boolean changeLeading = false; // constructors /** * Constructs a PdfChunk-object. * * @param string the content of the PdfChunk-object * @param other Chunk with the same style you want for the new Chunk */ PdfChunk(String string, PdfChunk other) { thisChunk[0] = this; value = string; this.font = other.font; this.attributes = other.attributes; this.noStroke = other.noStroke; this.baseFont = other.baseFont; Object obj[] = (Object[])attributes.get(Chunk.IMAGE); if (obj == null) image = null; else { image = (Image)obj[0]; offsetX = ((Float)obj[1]).floatValue(); offsetY = ((Float)obj[2]).floatValue(); changeLeading = ((Boolean)obj[3]).booleanValue(); } encoding = font.getFont().getEncoding(); splitCharacter = (SplitCharacter)noStroke.get(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 */ PdfChunk(Chunk chunk, PdfAction action) { thisChunk[0] = this; value = chunk.getContent(); Font f = chunk.getFont(); float size = f.getSize(); if (size == Font.UNDEFINED) size = 12; baseFont = f.getBaseFont(); int style = f.getStyle(); if (style == Font.UNDEFINED) { style = Font.NORMAL; } if (baseFont == null) { // translation of the font-family to a PDF font-family baseFont = f.getCalculatedBaseFont(false); } else { // bold simulation if ((style & Font.BOLD) != 0) attributes.put(Chunk.TEXTRENDERMODE, new Object[]{new Integer(PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE), new Float(size / 30f), null}); // italic simulation if ((style & Font.ITALIC) != 0) attributes.put(Chunk.SKEW, new float[]{0, ITALIC_ANGLE}); } font = new PdfFont(baseFont, size); // other style possibilities HashMap attr = chunk.getAttributes(); if (attr != null) { for (Iterator i = attr.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); Object name = entry.getKey(); if (keysAttributes.containsKey(name)) { attributes.put(name, entry.getValue()); } else if (keysNoStroke.containsKey(name)) { noStroke.put(name, entry.getValue()); } } if ("".equals(attr.get(Chunk.GENERICTAG))) { attributes.put(Chunk.GENERICTAG, chunk.getContent()); } } if (f.isUnderlined()) { Object obj[] = {null, new float[]{0, 1f / 15, 0, -1f / 3, 0}}; Object unders[][] = Utilities.addToArray((Object[][])attributes.get(Chunk.UNDERLINE), obj); attributes.put(Chunk.UNDERLINE, unders); } if (f.isStrikethru()) { Object obj[] = {null, new float[]{0, 1f / 15, 0, 1f / 3, 0}}; Object unders[][] = Utilities.addToArray((Object[][])attributes.get(Chunk.UNDERLINE), obj); attributes.put(Chunk.UNDERLINE, unders); } if (action != null) attributes.put(Chunk.ACTION, action); // the color can't be stored in a PdfFont noStroke.put(Chunk.COLOR, f.getColor()); noStroke.put(Chunk.ENCODING, font.getFont().getEncoding()); Object obj[] = (Object[])attributes.get(Chunk.IMAGE); if (obj == null) { image = null; } else { attributes.remove(Chunk.HSCALE); // images are scaled in other ways image = (Image)obj[0]; offsetX = ((Float)obj[1]).floatValue(); offsetY = ((Float)obj[2]).floatValue(); changeLeading = ((Boolean)obj[3]).booleanValue(); } font.setImage(image); Float hs = (Float)attributes.get(Chunk.HSCALE); if (hs != null) font.setHorizontalScaling(hs.floatValue()); encoding = font.getFont().getEncoding(); splitCharacter = (SplitCharacter)noStroke.get(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 (!Character.isLetter(text.charAt(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. */ PdfChunk split(float width) { newlineSplit = false; if (image != null) { if (image.getScaledWidth() > width) { PdfChunk pc = new PdfChunk(Chunk.OBJECT_REPLACEMENT_CHARACTER, this); value = ""; attributes = new HashMap(); image = null; font = PdfFont.getDefaultFont(); return pc; } else return null; } HyphenationEvent hyphenationEvent = (HyphenationEvent)noStroke.get(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 = 0; BaseFont ft = font.getFont(); boolean surrogate = false; if (ft.getFontType() == 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 += getCharWidth(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 += getCharWidth(Utilities.convertToUtf32(valueArray[currentPosition], valueArray[currentPosition + 1])); else currentWidth += getCharWidth(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), font.getFont(), font.size(), width - lastSpaceWidth); String post = hyphenationEvent.getHyphenatedWordPost(); 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 returnValue = value.substring(splitPosition); value = trim(value.substring(0, splitPosition)); PdfChunk pc = new PdfChunk(returnValue, this); return pc; } /** * 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. */ PdfChunk truncate(float width) { if (image != null) { if (image.getScaledWidth() > width) { PdfChunk pc = new PdfChunk("", this); value = ""; attributes.remove(Chunk.IMAGE); image = null; font = PdfFont.getDefaultFont(); 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(); boolean surrogate = false; char character; while (currentPosition < length) { // the width of every character is added to the currentWidth surrogate = Utilities.isSurrogatePair(value, currentPosition); if (surrogate) currentWidth += getCharWidth(Utilities.convertToUtf32(value, currentPosition)); else currentWidth += getCharWidth(value.charAt(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 returnValue = value.substring(currentPosition); value = value.substring(0, currentPosition); PdfChunk pc = new PdfChunk(returnValue, this); return pc; } // methods to retrieve the membervariables /** * Returns the font of this Chunk. * * @return a PdfFont */ PdfFont font() { return font; } /** * Returns the color of this Chunk. * * @return a Color */ Color color() { return (Color)noStroke.get(Chunk.COLOR); } /** * Returns the width of this PdfChunk. * * @return a width */ float width() { if (isAttribute(Chunk.CHAR_SPACING)) { Float cs = (Float) getAttribute(Chunk.CHAR_SPACING); return font.width(value) + value.length() * cs.floatValue(); } return font.width(value); } /** * Checks if the PdfChunk split was caused by a newline. * @return true if the PdfChunk split was caused by a newline. */ public boolean 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.getScaledWidth() + 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 relative to the baseline. * @return a displacement in points */ public float getTextRise() { Float f = (Float) getAttribute(Chunk.SUBSUPSCRIPT); if (f != null) { return f.floatValue(); } return 0.0f; } /** * Trims the last space. * @return the width of the space trimmed, otherwise 0 */ public float trimLastSpace() { BaseFont ft = font.getFont(); if (ft.getFontType() == 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.getFont(); if (ft.getFontType() == 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 */ Object getAttribute(String name) { if (attributes.containsKey(name)) return attributes.get(name); return noStroke.get(name); } /** *Checks if the attribute exists. * @param name the attribute key * @return true if the attribute exists */ boolean 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. */ boolean isStroked() { return (!attributes.isEmpty()); } /** * Checks if this PdfChunk is a Separator Chunk. * @return true if this chunk is a separator. * @since 2.1.2 */ boolean 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 */ boolean isHorizontalSeparator() { if (isAttribute(Chunk.SEPARATOR)) { Object[] o = (Object[])getAttribute(Chunk.SEPARATOR); return !((Boolean)o[1]).booleanValue(); } return false; } /** * Checks if this PdfChunk is a tab Chunk. * @return true if this chunk is a separator. * @since 2.1.2 */ boolean 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 */ void adjustLeft(float newValue) { Object[] o = (Object[])attributes.get(Chunk.TAB); if (o != null) { attributes.put(Chunk.TAB, new Object[]{o[0], o[1], o[2], new Float(newValue)}); } } /** * Checks if there is an image in the PdfChunk. * @return true if an image is present */ boolean isImage() { return image != null; } /** * Gets the image in the PdfChunk. * @return the image or null */ Image getImage() { return image; } /** * Sets the image offset in the x direction * @param offsetX the image offset in the x direction */ void setImageOffsetX(float offsetX) { this.offsetX = offsetX; } /** * Gets the image offset in the x direction * @return the image offset in the x direction */ float getImageOffsetX() { return offsetX; } /** * Sets the image offset in the y direction * @param offsetY the image offset in the y direction */ void setImageOffsetY(float offsetY) { this.offsetY = offsetY; } /** * Gets the image offset in the y direction * @return Gets the image offset in the y direction */ float getImageOffsetY() { return offsetY; } /** * sets the value. * @param value content of the Chunk */ void setValue(String value) { this.value = value; } /** * @see java.lang.Object#toString() */ public String toString() { return value; } /** * Tells you if this string is in Chinese, Japanese, Korean or Identity-H. * @return true if the Chunk has a special encoding */ boolean isSpecialEncoding() { return encoding.equals(CJKFont.CJK_ENCODING) || encoding.equals(BaseFont.IDENTITY_H); } /** * Gets the encoding of this string. * * @return a String */ String getEncoding() { return encoding; } int length() { return value.length(); } int lengthUtf32() { 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.charAt(k))) ++k; ++total; } return total; } boolean 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 */ String trim(String string) { BaseFont ft = font.getFont(); if (ft.getFontType() == BaseFont.FONT_TYPE_CJK && ft.getUnicodeEquivalent(' ') != ' ') { while (string.endsWith("\u0001")) { string = string.substring(0, string.length() - 1); } } else { while (string.endsWith(" ") || string.endsWith("\t")) { string = string.substring(0, string.length() - 1); } } return string; } public boolean changeLeading() { return changeLeading; } float getCharWidth(int c) { if (noPrint(c)) return 0; if (isAttribute(Chunk.CHAR_SPACING)) { Float cs = (Float) getAttribute(Chunk.CHAR_SPACING); return font.width(c) + cs.floatValue(); } return font.width(c); } public static boolean noPrint(int c) { return ((c >= 0x200b && c <= 0x200f) || (c >= 0x202a && c <= 0x202e)); } }