/*
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * "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 ICEpdf 3.0 open source software code, released
 * May 1st, 2009. The Initial Developer of the Original Code is ICEsoft
 * Technologies Canada, Corp. Portions created by ICEsoft are Copyright (C)
 * 2004-2009 ICEsoft Technologies Canada, Corp. All Rights Reserved.
 *
 * Contributor(s): _____________________.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"
 * License), in which case the provisions of the LGPL License 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 License. If you do
 * not delete the provisions above, a recipient may use your version of
 * this file under either the MPL or the LGPL License."
 *
 */
package com.softwareimaging.jpdf.core.pobjects.fonts.ofont;

import android.graphics.Typeface;
import android.graphics.PointF;

import com.softwareimaging.jpdf.core.pobjects.Name;
import com.softwareimaging.jpdf.core.pobjects.Reference;
import com.softwareimaging.jpdf.core.pobjects.Stream;
import com.softwareimaging.jpdf.core.pobjects.fonts.FontDescriptor;
import com.softwareimaging.jpdf.core.util.FontUtil;
import com.softwareimaging.jpdf.core.util.Library;
import com.softwareimaging.jpdf.core.util.ContentParser;
import com.softwareimaging.jpdf.core.pobjects.Resources;
import com.softwareimaging.jpdf.core.pobjects.graphics.Shapes;
import com.softwareimaging.jpdf.core.pobjects.PRectangle;
import com.softwareimaging.jpdf.core.pobjects.LiteralStringObject;
import com.softwareimaging.jpdf.util.HashMap;
import com.softwareimaging.jpdf.util.Hashtable;
import com.softwareimaging.logger.Logger;
import com.softwareimaging.android.graphics.AffineTransform;
import com.softwareimaging.data.ILogicalStreamReader;
import com.softwareimaging.fonts.IFIMetrics;
import com.softwareimaging.fonts.PostScript.PostScriptFontParser;

import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;
import java.io.IOException;
import java.io.InputStream;

/**
 *
 *
 */
public class Font extends com.softwareimaging.jpdf.core.pobjects.fonts.Font {

	// The font subset name (e.g. EOODIA+Poetica = the name of a subset of
	// Poetica)
	private String subsetfont;

	// A specification of the font's character encoding, if different from its
	// built-in encoding. The value of Encoding may be either the name of a
	// predefined
	// encoding (MacRomanEncoding, MacExpertEncoding, or WinAnsi- Encoding, as
	// described in Appendix D) or an encoding dictionary that specifies
	// differences from the font's built-in encoding or from a specified
	// predefined
	// encoding
	private Encoding encoding;
	// encoding name for debugging reasons;
	private String encodingName;

	// An array of (LastChar ? FirstChar + 1) widths, each element being the
	// glyph width for the character code that equals FirstChar plus the array
	// index.
	// For character codes outside the range FirstChar to LastChar, the value
	// of MissingWidth from the FontDescriptor entry for this font is used.
	private Vector widths;

	// widths for cid fonts.
	private Map<Integer, Float> cidWidths;
	private Float cidDefaultWidth;
	private static final Float missingCidDefaultWidth = 1.0f;

	// vertical metrics for cid fonts.
	private Map<Integer, CIDVerticalMetrics> cidVMetrics;
	private CIDVerticalMetrics cidDefaultVMetrics;
	private static final CIDVerticalMetrics missingCidDefaultVMetrics = new CIDVerticalMetrics();

	// Base character mapping of 256 chars
	private char[] cMap;

	// ToUnicode CMap object stores any mapping information
	private CMap toUnicodeCMap;

	// awt font style reference, ITALIC or BOLD|ITALIC
	protected int style;

	// Differences object redefines encoding
	Vector differences = null;

	// Predefined CMaps
	private static CMap toUnicodeCMap_AdobeCNS1;
	private static CMap toUnicodeCMap_AdobeGB1;
	private static CMap toUnicodeCMap_AdobeJapan1;
	private static CMap toUnicodeCMap_AdobeKorea1;

	// Predefined character collections
	private static final String charCollection_AdobeCNS1 = "Adobe-CNS1";
	private static final String charCollection_AdobeGB1 = "Adobe-GB1";
	private static final String charCollection_AdobeJapan1 = "Adobe-Japan1";
	private static final String charCollection_AdobeKorea1 = "Adobe-Korea1";
	private static final String charCollection_AdobeIdentity = "Adobe-Identity";

	// Predefined CMap suffix
	private static final String cMapSuffix_USCS = "-UCS2";

	// Predefined CMap names
	private static final String cMapName_AdobeCNS1 = charCollection_AdobeCNS1 + cMapSuffix_USCS;
	private static final String cMapName_AdobeGB1 = charCollection_AdobeGB1 + cMapSuffix_USCS;
	private static final String cMapName_AdobeJapan1 = charCollection_AdobeJapan1 + cMapSuffix_USCS;
	private static final String cMapName_AdobeKorea1 = charCollection_AdobeKorea1 + cMapSuffix_USCS;

	private byte[] CIDToGIDMap;

	private CMap CIDToUnicodeCMap;
	private CMap toCIDMap;

	// Array of type1 font differences based on family names.
	static final String type1Diff[][] = {
			{ "Bookman-Demi", "URWBookmanL-DemiBold", "Arial" },
			{ "Bookman-DemiItalic", "URWBookmanL-DemiBoldItal", "Arial" },
			{ "Bookman-Light", "URWBookmanL-Ligh", "Arial" },
			{ "Bookman-LightItalic", "URWBookmanL-LighItal", "Arial" },
			{ "Courier", "Nimbus Mono L Regular", "Nimbus Mono L" },
			{ "Courier-Oblique", "Nimbus Mono L Regular Oblique", "Nimbus Mono L" },
			{ "Courier-Bold", "Nimbus Mono L Bold", "Nimbus Mono L" },
			{ "Courier-BoldOblique", "Nimbus Mono L Bold Oblique", "Nimbus Mono L" },
			{ "AvantGarde-Book", "URWGothicL-Book", "Arial" },
			{ "AvantGarde-BookOblique", "URWGothicL-BookObli", "Arial" },
			{ "AvantGarde-Demi", "URWGothicL-Demi", "Arial" },
			{ "AvantGarde-DemiOblique", "URWGothicL-DemiObli", "Arial" },
			{ "Helvetica", "Nimbus Sans L Regular", "Nimbus Sans L" },
			{ "Helvetica-Oblique", "Nimbus Sans L Regular Italic", "Nimbus Sans L" },
			{ "Helvetica-Bold", "Nimbus Sans L Bold", "Nimbus Sans L" },
			{ "Helvetica-BoldOblique", "Nimbus Sans L Bold Italic", "Nimbus Sans L" },
			{ "Helvetica-Narrow", "Nimbus Sans L Regular Condensed", "Nimbus Sans L" },
			{ "Helvetica-Narrow-Oblique", "Nimbus Sans L Regular Condensed Italic", "Nimbus Sans L" },
			{ "Helvetica-Narrow-Bold", "Nimbus Sans L Bold Condensed", "Nimbus Sans L" },
			{ "Helvetica-Narrow-BoldOblique", "Nimbus Sans L Bold Condensed Italic",
					"Nimbus Sans L" },
			{ "Helvetica-Condensed", "Nimbus Sans L Regular Condensed", "Nimbus Sans L" },
			{ "Helvetica-Condensed-Oblique", "Nimbus Sans L Regular Condensed Italic",
					"Nimbus Sans L" },
			{ "Helvetica-Condensed-Bold", "Nimbus Sans L Bold Condensed", "Nimbus Sans L" },
			{ "Helvetica-Condensed-BoldOblique", "Nimbus Sans L Bold Condensed Italic",
					"Nimbus Sans L" }, { "Palatino-Roman", "URWPalladioL-Roma", "Arial" },
			{ "Palatino-Italic", "URWPalladioL-Ital", "Arial" },
			{ "Palatino-Bold", "URWPalladioL-Bold", "Arial" },
			{ "Palatino-BoldItalic", "URWPalladioL-BoldItal", "Arial" },
			{ "NewCenturySchlbk-Roman", "CenturySchL-Roma", "Arial" },
			{ "NewCenturySchlbk-Italic", "CenturySchL-Ital", "Arial" },
			{ "NewCenturySchlbk-Bold", "CenturySchL-Bold", "Arial" },
			{ "NewCenturySchlbk-BoldItalic", "CenturySchL-BoldItal", "Arial" },
			{ "Times-Roman", "Nimbus Roman No9 L Regular", "Nimbus Roman No9 L" },
			{ "Times-Italic", "Nimbus Roman No9 L Regular Italic", "Nimbus Roman No9 L" },
			{ "Times-Bold", "Nimbus Roman No9 L Medium", "Nimbus Roman No9 L" },
			{ "Times-BoldItalic", "Nimbus Roman No9 L Medium Italic", "Nimbus Roman No9 L" },
			{ "Symbol", "Standard Symbols L", "Standard Symbols L" },
			{ "ZapfChancery-MediumItalic", "URWChanceryL-MediItal", "Arial" },
			{ "ZapfDingbats", "Dingbats", "Dingbats" } };

	// Weights to use when substituting fonts.
	static final int DEFAULT_WEIGHT = 400;
	static final int BOLD_WEIGHT = 700;

	public Font(Library library, Hashtable entries) {
		super(library, entries);

		// initialize cMap array with base characters
		cMap = new char[256];
		for (char i = 0; i < 256; i++) {
			cMap[i] = i;
		}

		// get font style value.
		style = FontUtil.guessFontStyle(basefont);

		// strip font name clean ready for processing
		subsetfont = basefont;
		basefont = cleanFontName(basefont);

		// Setup encoding for type 1 fonts
		if (subtype.equals("Type1")) {
			// symbol
			if (basefont.equals("Symbol")) {
				encoding = Encoding.getSymbol();
			}
			// ZapfDingbats
			else if (basefont.equalsIgnoreCase("ZapfDingbats") && subtype.equals("Type1")) {
				encoding = Encoding.getZapfDingBats();
			}
			// check type1Diff table against base font and assign encoding of
			// found
			else {
				for (String[] aType1Diff : type1Diff) {
					if (basefont.equals(aType1Diff[0])) {
						encodingName = "standard";
						encoding = Encoding.getStandard();
						break;
					}
				}
			}
		}
		// TrueType fonts with a Symbol name get Symbol encoding
		if (subtype.equals("TrueType")) {
			if (basefont.equals("Symbol")) {
				encodingName = "symbol";
				encoding = Encoding.getSymbol();
			}
		}

	}

	/**
	 * Initiate the Font. Retrieve any needed attributes, basically setup the
	 * font so it can be used by the content parser.
	 */

	public synchronized void init() {
		// flag for initiated fonts
		if (inited) {
			return;
		}

		// variables for temporary use
		Object o;
		Vector v;

		// re-initialize the char mapping array based on the encoding of the
		// font
		if (encoding != null) {
			for (char i = 0; i < 256; i++) {
				cMap[i] = encoding.get(i);
			}
		}

		// ToUnicode indicates that we now have CMap stream that need to be
		// parsed
		Object objectUnicode = library.getObject(entries, "ToUnicode");
		if (objectUnicode != null && objectUnicode instanceof Stream) {
			toUnicodeCMap = new CMap((Stream) objectUnicode);
			toUnicodeCMap.init(library, null);
		} else if (toUnicodeCMap == null) {
			String charCollectionName = getCharacterCollectionName();
			if (charCollectionName != null) {
				CIDToUnicodeCMap = getCMap(charCollectionName, cMapSuffix_USCS);
			}
		}

		// An array of (LastChar ? FirstChar + 1) widths, each element being the
		// glyph width for the character code that equals FirstChar plus the
		// array index.
		widths = (Vector) (library.getObject(entries, "Widths"));
		if (widths != null) {

			// Assigns the First character code defined in the font's Widths
			// array
			o = library.getObject(entries, "FirstChar");
			if (o != null) {
				firstchar = (int) (library.getFloat(entries, "FirstChar"));
			}
		}

		getCIDMetrics();

		// Assign the font descriptor
		Object of = library.getObject(entries, "FontDescriptor");
		if (of instanceof FontDescriptor) {
			fontDescriptor = (FontDescriptor) of;
			fontDescriptor.init();
		}

		HashMap glyphShapes = null;
		AffineTransform fontMatrix = null;
		PRectangle fontBBox = null;
		if (subtype.equals("Type3")) {
			try {
				o = library.getObject(entries, "FontMatrix");
				if (o instanceof Vector) {
					v = (Vector) o;
					if (v.size() == 6) {
						float[] values = new float[6];
						for (int i = 0; i < 6; i++) {
							values[i] = ((Number) v.elementAt(i)).floatValue();
						}
						fontMatrix = new AffineTransform(values);
						fontMatrix.scale(1, -1);
					}
				}
				o = library.getObject(entries, "FontBBox");
				if (o instanceof Vector) {
					v = (Vector) o;
					if (v.size() == 4) {
						fontBBox = new PRectangle(new PointF(
								((Number) v.elementAt(0)).floatValue(), ((Number) v.elementAt(1))
										.floatValue()), new PointF(((Number) v.elementAt(2))
								.floatValue(), ((Number) v.elementAt(3)).floatValue()));
					}
				}
				o = library.getObject(entries, "CharProcs");
				if (o instanceof Hashtable) {
					Hashtable hash = (Hashtable) o;
					Iterator iterator = hash.keySet().iterator();
					Object objKey;
					Object objVal;
					glyphShapes = new HashMap();
					while (iterator.hasNext()) {
						objKey = iterator.next();
						objVal = hash.get(objKey);
						if (objKey instanceof Name && objVal instanceof Reference) {
							String glyphName = ((Name) objKey).getName();
							// Reference r = ((Reference)objVal);
							Object objData = library.getObject(objVal);
							if (objData instanceof Stream) {
								InputStream in = (InputStream) ((Stream) objData)
										.getInputStreamForDecodedStreamBytes();
								Resources resources = library.getResources(entries, "Resources");
								ContentParser cp = new ContentParser(library, resources);
								Shapes shapes = null;
								try {
									shapes = cp.parse(in);
								} catch (Throwable e) {
									if (Logger.areErrorsSupported()) {
										Logger.outputError("Error processing CharProcs.");
									}
									Logger.outputStackTrace(e);
								} finally {
									in.close();
								}
								if (shapes != null) {
									glyphShapes.put(glyphName, shapes);
								}
							}
						}
					}
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				Logger.outputStackTrace(e);
			}
		}

		// Find any special encoding information, not used very often
		o = library.getObject(entries, "Encoding");
		if (o != null) {
			if (o instanceof Hashtable) {
				Hashtable encoding = (Hashtable) o;
				setBaseEncoding(library.getName(encoding, "BaseEncoding"));
				differences = (Vector) library.getObject(encoding, "Differences");
				if (differences != null) {
					int c = 0;
					for (Enumeration e = differences.elements(); e.hasMoreElements();) {
						Object oo = e.nextElement();
						if (oo instanceof Number) {
							c = ((Number) oo).intValue();
						} else if (oo instanceof Name) {
							String n = oo.toString();
							// First, try to identify character using standard
							// glyph name table
							int c1 = Encoding.getUV(n);

							// Second, try to identify character using
							// toUnicodeCMap.
							if (c1 == -1 && toUnicodeCMap != null) {
								c1 = toUnicodeCMap.toSelector((char) c);
							}

							// Third, check if the font is symbolic. If so,
							// mapping must be identity.
							// NOTE: When non-standard glyph names are used, the
							// symbolic flag must be set.
							if (c1 == -1
									&& fontDescriptor != null
									&& (fontDescriptor.getFlags() & FontDescriptor.FLAGS_SYMBOLIC) != 0) {
								c1 = c;
							}
							cMap[c] = (char) c1;
							c++;
						}
					}
				}
			} else if (o instanceof Name) {
				setBaseEncoding(((Name) o).getName());
			}
		}

		// assign font name for descriptor
		if (fontDescriptor != null) {
			String fontName = fontDescriptor.getFontName();
			if (fontName != null && fontName.length() > 0) {
				basefont = fontDescriptor.getFontName();
				subsetfont = basefont;
				basefont = cleanFontName(basefont);
			}
		}

		// checking flags for set bits.
		if (fontDescriptor != null
				&& (fontDescriptor.getFlags() & FontDescriptor.FLAGS_NONSYMBOLIC) != 0
				&& encoding == null) {
			encodingName = "standard";
			encoding = Encoding.getStandard();
		}

		// this is a test to basic CIDFont support. The current font class
		// is not setup to deal with this type of font, however we can still
		// located the descendant font described by the CIDFont's data and try
		// and cMap the properties over to the type1 font
		Object desendantFont = library.getObject(entries, "DescendantFonts");
		if (desendantFont != null) {
			Vector tmp = (Vector) desendantFont;
			if (tmp.elementAt(0) instanceof Reference) {
				// locate the font reference
				Object fontReference = library.getObject((Reference) tmp.elementAt(0));
				if (fontReference instanceof Font) {
					ProcessDescendant((Font) fontReference);
				}
			} else if (tmp.elementAt(0) instanceof Font) {
				ProcessDescendant((Font) tmp.elementAt(0));
			}
		}

		if (subTypeFormat == CID_FORMAT) {
			o = library.getObject(entries, "CIDToGIDMap");
			if (o instanceof Stream) {
				CIDToGIDMap = ((Stream) o).getBytes();
			}
		}

		// font substitution found flag
		isFontSubstitution = false;

		/*
		 * If a font is embedded, at most, only one of the FontFile, FontFile2
		 * and FontFile3 entries may be present. FontFile - optional for Type 1
		 * FontFile2 - optional for TrueType FontFile3 - optional for other
		 * fonts. See subtype for further info.
		 * 
		 * TS: we can handle TrueType in JPrintEngine.
		 */
		ILogicalStreamReader fontStream = null;
		ILogicalStreamReader fontStream2 = null;
		ILogicalStreamReader fontStream3 = null;
		if (fontDescriptor != null) {
			// if font stream exist, it must be one of 3 font files. (FontFile,
			// FontFile2 or FontFile3)
			fontStream = fontDescriptor.getFontFile();
			if (fontStream == null) {
				fontStream2 = fontDescriptor.getFontFile2();
			}
			if (fontStream == null && fontStream2 == null) {
				fontStream3 = fontDescriptor.getFontFile3();
			}
		}

		Reference objRef = getPObjectReference();
		// fontRef could be null if the font is a descendant font embedded in
		// the parent font. Descendant fonts are realised to be consumed by
		// parent fonts. They are used temporarily and will not be selected on
		// the device. So, using fontRef is not a must.
		String fontRef = null;
		if (objRef != null) {
			fontRef = "F" + Integer.toString(objRef.getObjectNumber()) + "_"
					+ Integer.toString(objRef.getGenerationNumber());
		}

		Vector<String> charNameString = null;
		String[] defaultCharNameStrings = null;

		if (subtype.equals("Type3")) {

			// This is a Type 3 embedded font.

			if (fontRef == null) {
				throw new NullPointerException(
						"Type3 font should be an indirect object and have object reference number");
			}

			if (encoding != null) {
				defaultCharNameStrings = encoding.getGlyphNames();
			}
			// Use a font reference name for Type 3 because there is no font
			// name given.
			font = new OFont(library.getPrinter(), fontRef, fontBBox, glyphShapes,
					createCharNameStrings(defaultCharNameStrings));
			if (fontMatrix != null) {
				font = font.deriveFont(fontMatrix);
			}

		} else if (subtype.equals("Type1") && fontStream != null) {

			// This is an embedded Type 1 font.

			int[] fontLengths = fontDescriptor.getFontLengths();
			if (encoding != null) {
				defaultCharNameStrings = encoding.getGlyphNames();
			} else {
				PostScriptFontParser psParser = PostScriptFontParser.getParser(fontStream,
						fontLengths);
				defaultCharNameStrings = psParser.getEncodingArray();
			}

			// An embedded Type 1 fonts has the font name in the font stream,
			// and cannot be composite fonts. So, 'subsetfont' must be unique in
			// the document and match the name in the font stream.
			// 'subsetfont' must be used without any modification.
			font = new OFont(library.getPrinter(), subsetfont, fontStream, fontLengths, 1, subtype,
					createCharNameStrings(defaultCharNameStrings));

		} else if ((subtype.equals("Type1") || (subTypeFormat == CID_FORMAT))
				&& fontStream3 != null) {

			// This is an embedded CFF font (simple CFF or CID-keyed CFF)

			if (subTypeFormat == SIMPLE_FORMAT) {
				if (encoding != null) {
					defaultCharNameStrings = encoding.getGlyphNames();
				} else {
					PostScriptFontParser psParser = PostScriptFontParser.getParser(fontStream3,
							null);
					defaultCharNameStrings = psParser.getEncodingArray();
				}
				charNameString = createCharNameStrings(defaultCharNameStrings);
			}
			font = new OFont(library.getPrinter(), (fontRef != null ? fontRef + "_" + subsetfont
					: subsetfont), fontStream3, null, 3, subtype, charNameString);

		} else if (fontStream2 != null) {

			// This is an embedded TrueType font (simple TrueType or CID-keyed
			// TrueType)

			if (subTypeFormat == SIMPLE_FORMAT) {
				if (encoding != null) {
					defaultCharNameStrings = encoding.getGlyphNames();
				} else {
					// Nothing to do here. defaultCharNameStrings is optional
					// for TrueType.
				}
				charNameString = createCharNameStrings(defaultCharNameStrings);
			}
			font = new OFont(library.getPrinter(), (fontRef != null ? fontRef + "_" + subsetfont
					: subsetfont), fontStream2, null, 2, subtype, charNameString);

		} else {

			// This is a non-embedded font. (Type 1, TrueType, unhandled
			// CID-keyed CFF, unhandled CID-keyed TrueType font or unknown fonts
			// such as OpenType version of CFF?)
			// The font will be substituted with a closest device font.

			int flags = 0;
			int weight = DEFAULT_WEIGHT;
			if (fontDescriptor != null) {
				flags = fontDescriptor.getFlags();
			}
			if ((flags & FontDescriptor.FLAGS_FORCEBOLD) != 0) {
				style |= Typeface.BOLD;
				weight = BOLD_WEIGHT;
			}
			if ((flags & FontDescriptor.FLAGS_ITALIC) != 0) {
				style |= Typeface.ITALIC;
			}

			if (fontDescriptor != null) {
				weight = (int) fontDescriptor.getFontWeight();
			}
			boolean fixedPitch = (flags & FontDescriptor.FLAGS_FIXEDPITCH) != 0;
			boolean script = (flags & FontDescriptor.FLAGS_SCRIPT) != 0;

			String familyName = null;
			if (fontDescriptor != null) {
				familyName = fontDescriptor.getFontFamily();
			}

			byte charSet = IFIMetrics.CS_DEFAULT;
			String cMapName = null;
			if (toUnicodeCMap != null) {
				cMapName = toUnicodeCMap.cMapName;
			} else if (CIDToUnicodeCMap != null) {
				cMapName = CIDToUnicodeCMap.cMapName;
			}

			if (cMapName != null) {
				if (cMapName.equals(cMapName_AdobeCNS1)) {
					charSet = IFIMetrics.CS_CHINESEBIG5;
				} else if (cMapName.equals(cMapName_AdobeGB1)) {
					charSet = IFIMetrics.CS_GB2312;
				} else if (cMapName.equals(cMapName_AdobeJapan1)) {
					charSet = IFIMetrics.CS_SHIFTJIS;
				} else if (cMapName.equals(cMapName_AdobeKorea1)) {
					charSet = IFIMetrics.CS_HANGEUL;
				}
			}

			if (basefont.equals("Symbol")) {
				// There is a symbolic flag in the FontDescriptor. Symbolic
				// fonts on the PDF spec is not the same as on JPrintEngine.
				// Symbolic fonts on PDF spec are just non Adobe standard fonts.
				// We use CS_SYMBOL explicitly
				charSet = IFIMetrics.CS_SYMBOL;
			}

			font = new OFont(library.getPrinter(), basefont, familyName, style, charSet, weight,
					fixedPitch, script);

			isFontSubstitution = true;
		}
		basefont = font.getName();

		// setup encoding and widths.
		setWidth();
		if (font != null) {
			if (encoding != null) {
				// If encoding exists, characters can be identified with it and
				// the font cannot be a CID font.
				// There is no reason to use toUnicodeCMap instead of encoding
				font = font.deriveFont(null, cMap, null, null, null);
			} else {
				// toUnicodeCMap is optional and cMap exists all the time.
				font = font
						.deriveFont(toUnicodeCMap, cMap, toCIDMap, CIDToUnicodeCMap, CIDToGIDMap);
			}
		}

		inited = true;
	}

	private void ProcessDescendant(Font descendant) {
		// create and initiate the font based on the stream data
		descendant.toUnicodeCMap = toUnicodeCMap;
		descendant.init();
		cidWidths = descendant.cidWidths;
		cidDefaultWidth = descendant.cidDefaultWidth;
		toUnicodeCMap = descendant.toUnicodeCMap;
		CIDToUnicodeCMap = descendant.CIDToUnicodeCMap;
		CIDToGIDMap = descendant.CIDToGIDMap;

		// FontDescriptor will be null for Type 0 and Type 3,
		// and could be null for the 14 standard Type 1 fonts.
		// However, this code is executed only for Type 0 because
		// the Type 0 font is only the font which have descendant
		// fonts.
		if (fontDescriptor == null && descendant.fontDescriptor != null) {
			fontDescriptor = descendant.fontDescriptor;
			basefont = fontDescriptor.getFontName();
			subsetfont = basefont;
			basefont = cleanFontName(basefont);
			// Ensure that subtype information is consistent with
			// fontDescriptor, which could have
			// embedded font stream. Otherwise the embedded font is
			// never used.
			subtype = descendant.subtype;
			subTypeFormat = descendant.subTypeFormat;
		}
	}

	/**
	 * Create character name strings from differences table, which includes
	 * user-define names and standard names.
	 * 
	 * @return The character name string used in the embedded font.
	 */
	private Vector<String> createCharNameStrings(String[] defaultCharNameStrings) {
		Vector<String> charNameStrings = new Vector<String>(256);
		int index = 0;

		// Fill table with the default char strings.
		if (defaultCharNameStrings != null) {
			for (index = 0; index < defaultCharNameStrings.length; index++) {
				if (index >= charNameStrings.size()) {
					charNameStrings.setSize(index + 1);
				}
				if (defaultCharNameStrings[index] != null) {
					charNameStrings.set(index, defaultCharNameStrings[index]);
				}
			}
			if (differences == null) {
				return charNameStrings;
			}
		}

		if (differences != null) {
			// The value of the Differences entry is an array of character codes
			// and character names organised as follows:
			//
			// code1 name name ...
			// code2 name name ...
			// ...
			// codeN name name ...
			//
			// Each code is the first index in a sequence of characters to be
			// changed. The first character name after the code becomes the name
			// corresponding to that code. Subsequent names replace consecutive
			// code indices until the next code appears in the array or the
			// array ends. These sequences may be specified in any order but
			// should not overlap.
			int chars = differences.size();
			Object o;
			for (int i = 0; i < chars; i++) {
				o = differences.elementAt(i);
				if (o instanceof Name) {
					Name glyph = ((Name) o);
					if (index >= charNameStrings.size()) {
						charNameStrings.setSize(index + 1);
					}
					charNameStrings.set(index, glyph.getName());
					index++;
				} else if (o instanceof Number) {
					Number number = ((Number) o);
					index = number.intValue();
				}
			}
		}

		if (charNameStrings.size() == 0 && subtype.equals("TrueType")) {
			// Glyph names are not important for TrueType because they are
			// additional information. So, we can generate the names if they are
			// not in PDF. Generated names allow JPrintEngine to enable all 256
			// characters.
			for (int i = 0; i < 256; i++) {
				charNameStrings.addElement("g" + Integer.toString(i));
			}
		}

		return charNameStrings;
	}

	/**
	 * Sets the encoding of the font
	 * 
	 * @param baseencoding
	 *            encoding name ususally MacRomanEncoding, MacExpertEncoding, or
	 *            WinAnsi- Encoding
	 */
	private void setBaseEncoding(String baseencoding) {

		if (subTypeFormat == SIMPLE_FORMAT) {
			if (baseencoding == null) {
				encodingName = "none";
				return;
			} else if (baseencoding.equals("StandardEncoding")) {
				encodingName = "StandardEncoding";
				encoding = Encoding.getStandard();
			} else if (baseencoding.equals("MacRomanEncoding")) {
				encodingName = "MacRomanEncoding";
				encoding = Encoding.getMacRoman();
			} else if (baseencoding.equals("WinAnsiEncoding")) {
				encodingName = "WinAnsiEncoding";
				encoding = Encoding.getWinAnsi();
			} else if (baseencoding.equals("PDFDocEncoding")) {
				encodingName = "PDFDocEncoding";
				encoding = Encoding.getPDFDoc();
			}
			// initiate encoding cMap.
			if (encoding != null) {
				for (char i = 0; i < 256; i++) {
					cMap[i] = encoding.get(i);
				}
			}
		} else {
			if (baseencoding == null || baseencoding.equals("Identity-H")) {
				return;
			}
			if (baseencoding.equals("Identity-V")) {
				isVerticalWriting = true;
				return;
			}

			toCIDMap = getCMap(baseencoding);

			if (toCIDMap != null && toCIDMap.wMode == 1) {
				isVerticalWriting = true;
			}
		}
	}

	/**
	 * String representation of the Font object.
	 * 
	 * @return string representing Font object attributes.
	 */
	public String toString() {
		return "FONT= " + encodingName + " " + entries.toString();
	}

	/**
	 * Utility method for setting the widths for a particular font given the
	 * specified encoding.
	 */
	private void setWidth() {
		float missingWidth = 0;
		float ascent = 0.0f;
		float descent = 0.0f;
		if (fontDescriptor != null) {
			if (fontDescriptor.getMissingWidth() > 0) {
				missingWidth = fontDescriptor.getMissingWidth() / 1000f;
			}
			ascent = fontDescriptor.getAscent() / 1000f;
			descent = fontDescriptor.getDescent() / 1000f;
		}
		if (widths != null) {
			float[] newWidth = new float[256 - firstchar];
			for (int i = 0, max = widths.size(); i < max; i++) {
				if (widths.elementAt(i) != null) {
					newWidth[i] = ((Number) widths.elementAt(i)).floatValue() / 1000f;
				}
			}
			if (font != null) {
				font = font.deriveFont(newWidth, firstchar, missingWidth, ascent, descent);
			}
		} else if (subtype.equals("CIDFontType2") || subtype.equals("CIDFontType0")) {
			// cidWidth are already scaled correct to .001
			if (font != null) {
				font = font.deriveFont(cidWidths, cidDefaultWidth, cidVMetrics, cidDefaultVMetrics,
						missingWidth, ascent, descent);
			}
		}
	}

	private String cleanFontName(String fontName) {

		// crystal report encoding specific, this will have to made more robust
		// when more examples are found.
		if (fontName.indexOf('+') >= 0) {
			int index = fontName.indexOf('+');
			String tmp = fontName.substring(index + 1);
			try {
				Integer.parseInt(tmp);
				fontName = fontName.substring(0, index);
			} catch (NumberFormatException e) {
				// Do nothing because this is not an error
			}
		}

		// clean up the font name, strip a subtyp reference from name
		while (fontName.indexOf('+') >= 0) {
			int index = fontName.indexOf('+');

			fontName = fontName.substring(index + 1, fontName.length());
		}

		// strip commas from basefont name and replace with dashes
		if (subtype.equals("Type0") || subtype.equals("Type1") || subtype.equals("MMType1")
				|| subtype.equals("TrueType")) {
			if (fontName != null) {
				// normalize so that java.awt.decode will work correctly
				fontName = fontName.replace(',', '-');
			}
		}
		return fontName;
	}

	private Map<Integer, Float> calculateCIDWidths() {
		HashMap<Integer, Float> cidWidths = null;
		// get width vector
		Object o = library.getObject(entries, "W");
		if (o instanceof Vector) {
			cidWidths = new HashMap<Integer, Float>(75);
			Vector cidWidth = (Vector) o;
			Object current;
			Object peek;
			Vector subWidth;
			int currentChar;
			Float width;
			for (int i = 0, max = cidWidth.size() - 1; i < max; i++) {
				current = cidWidth.get(i);
				peek = cidWidth.get(i + 1);
				// found format c[w1, w2 ... wn]
				if (current instanceof Integer && peek instanceof Vector) {
					currentChar = (Integer) current;
					subWidth = (Vector) peek;
					for (int j = 0, subMax = subWidth.size(); j < subMax; j++) {
						width = Float.valueOf(((Number) subWidth.get(j)).floatValue() / 1000f);
						cidWidths.put(currentChar + j, width);
					}
					i++;
				}
				if (current instanceof Integer && peek instanceof Integer) {
					width = Float.valueOf(((Number) cidWidth.get(i + 2)).floatValue() / 1000f);
					for (int j = (Integer) current; j <= (Integer) peek; j++) {
						currentChar = j;
						cidWidths.put(currentChar, width);
					}
					i += 2;
				}
			}
		}
		return cidWidths;
	}

	private void getCIDMetrics() {
		// get cid font widths
		if (library.getObject(entries, "W") != null) {
			// calculate CID widths
			cidWidths = calculateCIDWidths();
			// first char is likely 1.
			firstchar = 0;
		}

		// get vertical metrics of cid font
		cidVMetrics = calculateCIDVMetrics();

		// get the default width of the cid fonts.
		Object o = library.getObject(entries, "DW");
		if (o != null) {
			cidDefaultWidth = library.getFloat(entries, "DW") / 1000f;
		}
		if (cidDefaultWidth == null) {
			cidDefaultWidth = missingCidDefaultWidth;
		}

		o = library.getObject(entries, "DW2");
		if (o != null) {
			if (o instanceof Vector) {
				Vector component = (Vector) o;
				// DW2 is an array of two values
				if (component.size() == 2) {
					// The vertical component of the position vector v.
					float y = ((Number) component.elementAt(0)).floatValue() / 1000f;
					// The vertical component of the displacement vector w1.
					float w1 = ((Number) component.elementAt(1)).floatValue() / 1000f;
					cidDefaultVMetrics = new CIDVerticalMetrics(0, y, w1);
				}
			}
		}
		if (cidDefaultVMetrics == null) {
			cidDefaultVMetrics = missingCidDefaultVMetrics;
		}

	}

	private Map<Integer, CIDVerticalMetrics> calculateCIDVMetrics() {
		HashMap<Integer, CIDVerticalMetrics> cidVMetrics = null;
		// get width vector
		Object o = library.getObject(entries, "W2");
		if (o instanceof Vector) {
			cidVMetrics = new HashMap<Integer, CIDVerticalMetrics>();
			Vector vMetrics = (Vector) o;
			Object current;
			Object peek;
			Vector subVMetrics;
			int currentChar;
			float w1;
			float x;
			float y;
			for (int i = 0, max = vMetrics.size() - 1; i < max; i++) {
				current = vMetrics.get(i);
				peek = vMetrics.get(i + 1);
				// found format c[w1, x1, x1, w2, x2, y2 ... wn, xn, yn]
				if (current instanceof Integer && peek instanceof Vector) {
					currentChar = (Integer) current;
					subVMetrics = (Vector) peek;
					for (int j = 0, subMax = subVMetrics.size() / 3; j < subMax; j++) {
						w1 = ((Number) subVMetrics.get(j * 3)).floatValue() / 1000f;
						x = ((Number) subVMetrics.get(j * 3 + 1)).floatValue() / 1000f;
						y = ((Number) subVMetrics.get(j * 3 + 2)).floatValue() / 1000f;
						cidVMetrics.put(currentChar + j, new CIDVerticalMetrics(w1, x, y));
					}
					i++;
				}
				if (current instanceof Integer && peek instanceof Integer) {
					w1 = ((Number) vMetrics.get(i + 2)).floatValue() / 1000f;
					x = ((Number) vMetrics.get(i + 3)).floatValue() / 1000f;
					y = ((Number) vMetrics.get(i + 4)).floatValue() / 1000f;
					for (int j = (Integer) current; j <= (Integer) peek; j++) {
						currentChar = j;
						cidVMetrics.put(currentChar, new CIDVerticalMetrics(w1, x, y));
					}
					i += 4;
				}
			}
		}
		return cidVMetrics;
	}

	/**
	 * Generate character collection name using CIDSystemInfo
	 * 
	 * @return character collection name
	 */
	private String getCharacterCollectionName() {
		Object o = library.getObject(entries, "CIDSystemInfo");
		if (o instanceof Hashtable) {
			Hashtable cidSystemInfo = (Hashtable) o;
			String reg = ((LiteralStringObject) cidSystemInfo.get("Registry")).toString();
			String ord = ((LiteralStringObject) cidSystemInfo.get("Ordering")).toString();
			return reg + "-" + ord;
		}
		return null;
	}

	/**
	 * Create predefined CMap
	 * 
	 * @param charCollectionName
	 *            character collection name
	 * @param cMapSuffix
	 *            suffix of CMap. For example, -UCS2.
	 * @return predefined CMap
	 */
	private CMap getCMap(String charCollectionName, String cMapSuffix) {
		CMap cMap = null;
		if (charCollectionName.equals(charCollection_AdobeCNS1)) {
			cMap = toUnicodeCMap_AdobeCNS1;
		} else if (charCollectionName.equals(charCollection_AdobeGB1)) {
			cMap = toUnicodeCMap_AdobeGB1;
		} else if (charCollectionName.equals(charCollection_AdobeJapan1)) {
			cMap = toUnicodeCMap_AdobeJapan1;
		} else if (charCollectionName.equals(charCollection_AdobeKorea1)) {
			cMap = toUnicodeCMap_AdobeKorea1;
		} else if (charCollectionName.equals(charCollection_AdobeIdentity)) {
			// No cMap is required because of identity mapping
			return null;
		}

		if (cMap == null) {
			cMap = getCMap(charCollectionName + cMapSuffix);
			setCMap(charCollectionName, cMap);
		}
		return cMap;
	}

	/**
	 * Load CMap from resource
	 * 
	 * @param cmapName
	 *            resource name
	 * @return CMap
	 */
	private CMap getCMap(String cmapName) {
		CMap cMap = null;
		try {
			cMap = new CMap();
			cMap.read(cmapName, null);
		} catch (IOException e) {
			if (Logger.areErrorsSupported()) {
				Logger.outputError(cmapName + " is not supported");
			}
			return null; // use identity mapping
		}
		return cMap;
	}

	/**
	 * Save created CMap
	 * 
	 * @param charCollectionName
	 *            character collection name
	 * @param CMap
	 *            CMap to be saved
	 */
	private void setCMap(String charCollectionName, CMap cMap) {
		if (charCollectionName.equals(charCollection_AdobeCNS1)) {
			toUnicodeCMap_AdobeCNS1 = cMap;
		} else if (charCollectionName.equals(charCollection_AdobeGB1)) {
			toUnicodeCMap_AdobeGB1 = cMap;
		} else if (charCollectionName.equals(charCollection_AdobeJapan1)) {
			toUnicodeCMap_AdobeJapan1 = cMap;
		} else if (charCollectionName.equals(charCollection_AdobeKorea1)) {
			toUnicodeCMap_AdobeKorea1 = cMap;
		}
	}
}
