/*
 * 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 com.softwareimaging.jpdf.core.pobjects.LiteralStringObject;
import com.softwareimaging.jpdf.core.pobjects.Name;
import com.softwareimaging.jpdf.core.pobjects.Stream;
import com.softwareimaging.jpdf.core.pobjects.StringObject;
import com.softwareimaging.jpdf.core.util.Library;
import com.softwareimaging.jpdf.core.util.Parser;
import com.softwareimaging.jpdf.util.Hashtable;

import java.io.EOFException;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.TreeMap;
import java.util.Vector;
import com.softwareimaging.logger.Logger;

/**
 * The purpose of the class is to parse a CMap file. A CMap specifies the
 * mapping from character codes to character selectors. A CMap file defines the
 * relationship between a character code and the character description
 * <p/>
 * Character selectors are always CIDs in a CIDFont. A CMap serves a function
 * analogous to the Encoding dictionary for a simple font. The CMap does not
 * refer directly to a specific CIDFont; instead, it is combined with it as part
 * of a CIDkeyed font, represented in PDF as a Type 0 font dictionary. Within
 * the CMap, the character mappings refer to the associated CIDFont by font
 * number, which in PDF is always 0.
 * 
 * @since 1.0
 */
public class CMap implements com.softwareimaging.jpdf.core.pobjects.fonts.CMap {
	/**
	 * The location of cmap resource
	 */
	public static final String cmapRoot = "/com/softwareimaging/jpdf/core/pobjects/fonts/cmaps/";
	/**
	 * cmap file extension
	 */
	public static final String cmapFileExtension = ".cmap";
	/**
	 * Supported Adobe CMaps
	 */
	public static final String[] cmapList = { "83pv-RKSJ-H", "90ms-RKSJ-H", "90ms-RKSJ-V",
			"90msp-RKSJ-H", "90msp-RKSJ-V", "90pv-RKSJ-H", "Add-RKSJ-H", "Add-RKSJ-V",
			"Adobe-CNS1-UCS2", "Adobe-GB1-UCS2", "Adobe-Japan1-UCS2", "Adobe-Korea1-UCS2",
			"B5pc-H", "B5pc-V", "CNS-EUC-H", "CNS-EUC-V", "ETen-B5-H", "ETen-B5-V", "ETenms-B5-H",
			"ETenms-B5-V", "EUC-H", "EUC-V", "Ext-RKSJ-H", "Ext-RKSJ-V", "GB-EUC-H", "GB-EUC-V",
			"GBK-EUC-H", "GBK-EUC-V", "GBK2K-H", "GBK2K-V", "GBKp-EUC-H", "GBKp-EUC-V",
			"GBpc-EUC-H", "GBpc-EUC-V", "H", "HKscs-B5-H", "HKscs-B5-V", "KSC-EUC-H", "KSC-EUC-V",
			"KSCms-UHC-H", "KSCms-UHC-HW-H", "KSCms-UHC-HW-V", "KSCms-UHC-V", "KSCpc-EUC-H",
			"UniCNS-UCS2-H", "UniCNS-UCS2-V", "UniCNS-UTF16-H", "UniCNS-UTF16-V", "UniGB-UCS2-H",
			"UniGB-UCS2-V", "UniGB-UTF16-H", "UniGB-UTF16-V", "UniJIS-UCS2-H", "UniJIS-UCS2-HW-H",
			"UniJIS-UCS2-HW-V", "UniJIS-UCS2-V", "UniJIS-UTF16-H", "UniJIS-UTF16-V",
			"UniKS-UCS2-H", "UniKS-UCS2-V", "UniKS-UTF16-H", "UniKS-UTF16-V", "V" };

	/**
	 * Dictionary containing entries that define the character collection for
	 * the CIDFont or CIDFonts associate with the CMap. Specifically the
	 * character collections registry, ordering and supplement is defined.
	 */
	public String registry;
	public String ordering;
	public byte supplement;

	/**
	 * PostScript name of the CMap.
	 */
	public String cMapName;

	/**
	 * defines changes to the internal organisation of CMap files or the
	 * semantics of CMap operators. The CMapType of CMaps described in this
	 * document. cMapType = 2 - indicates a ToUnicode cmap cMapType = 1 -
	 * indicates a CMap object cMapType = 0 - not sure yet, maybe CMap with
	 * external CMap reference
	 */
	private byte cMapType;

	/**
	 * The name of a predefined CMap that is to be used as the base for this
	 * CMap. This allows the CMap to be defined differentially, specifying only
	 * the character mappings that differ from the base CMap.
	 */
	private String useCMap;
	private CMap baseCMap;

	/**
	 * The WMode dictionary entry controls whether the CID-keyed font writes
	 * horizontally or vertically. It indicates which set of metrics will be
	 * used when a base font is shown. An entry of 0 defines horizontal writing
	 * from left to right; an entry of 1 defines vertical writing from top to
	 * bottom.
	 */
	public byte wMode;

	/**
	 * Defines the source character code range. Source CMap references must be
	 * in this range.
	 */
	private CodeSpaceRange[] codeSpaceRange;

	/**
	 * Defines mappings from character codes to Unicode characters or CIDs in
	 * the associated font.
	 */
	private CMapHashMap<Character, Integer> defChars;

	/**
	 * Defines mappings from character codes to Unicode character ranges or CID
	 * ranges.
	 * 
	 */
	private ArrayList<CMapRange> defRange;

	/**
	 * Define notdef mappings
	 */
	private CMapHashMap<Character, Integer> notDefChars;

	/**
	 * Similar to notDefChars but defines ranges of input codes.
	 */
	private ArrayList<CMapRange> notDefRange;

	/**
	 * Stream containing the embedded/external CMap
	 */
	private transient Stream cMapStream;
	private transient InputStream cMapInputStream;

	public CMap() {
	}

	/**
	 * Create a new CMap instance.
	 * 
	 * @param cMapStream
	 *            stream containing CMap data.
	 */
	public CMap(Stream cMapStream) {
		this.cMapStream = cMapStream;
	}

	public CMap(InputStream cMapInputStream) {
		this.cMapInputStream = cMapInputStream;
	}

	/**
	 * Get a string of the character collection
	 * 
	 * @param o
	 *            LiteralStringObject or Name class
	 * @return a string of the character collection
	 */
	private String characterCollectionName(Object o) {
		String s = null;
		if (o instanceof LiteralStringObject) {
			s = ((LiteralStringObject) o).toString();
		} else if (o instanceof Name) {
			s = ((Name) o).toString();
		}
		return s;
	}

	/**
	 * Start the parsing of the CMap file. Once completed, all necessary data
	 * should be captured from the CMap file. If the CMap is created from a
	 * named object the dictionary property will be populated with values for
	 * the keys Type, CMapName and CIDSystemInfo which are also repeated in the
	 * CMap file itself. If the CMap file was created from a Font object then
	 * they previously mentioned keys values must be parsed from the CMap file.
	 * 
	 * @param library
	 *            pointer to default library containing all document objects
	 * @param entries
	 *            hashtable containing all of the dictionary properties
	 *            associated with this object. The hashtable will be empty if
	 *            this object was created via a Font objects ToUnicode key.
	 */
	public void init(Library l, Hashtable h) {
		try {
			// get the byes and push them through the parser to get objects in
			// CMap
			if (cMapInputStream == null) {
				cMapInputStream = cMapStream.getInputStreamForDecodedStreamBytes();
			}

			Parser parser = new Parser(cMapInputStream);

			/**
			 * Start gathering the data from the CMap objects, the CMap file is
			 * fixed in format so this routine doesn't have to be to complicated
			 */
			Object previousToken = null;
			while (true) {
				Object token = parser.getStreamObject();
				// break out and the end of the stream
				if (token == null) {
					break;
				}
				// find cIdSystemInfo, not always a named attribute
				String nameString = token.toString();
				if (nameString.toLowerCase().indexOf("cidsysteminfo") >= 0) {
					// CIDSystemInfo only has one property which should be
					// always be hash by definition and our parser result
					token = parser.getStreamObject();
					if (token instanceof Hashtable) {
						// Typical format as follows is handled in parser:
						//
						// /CIDSystemInfo <<
						// /Registry (Adobe)
						// /Ordering (Adobe_Japan1_UCS2)
						// /Supplement 6
						// >> def
						Hashtable cIdSystemInfo = (Hashtable) token;
						registry = characterCollectionName(cIdSystemInfo.get("Registry"));
						ordering = characterCollectionName(cIdSystemInfo.get("Ordering"));
						supplement = ((Integer) cIdSystemInfo.get("Supplement")).byteValue();

					} else if (token instanceof Integer) {
						// The other format as follows is handled here
						//
						// /CIDSystemInfo 3 dict dup begin
						// /Registry (Adobe) def
						// /Ordering (Adobe_Japan1_UCS2) def
						// /Supplement 6 def
						// end def

						// As all three entries are required, the number of
						// dictionary must be three.
						if (((Integer) token).intValue() == 3) {
							// Skip dict, dup and begin tokens before Registry
							// token
							token = parser.getStreamObject();
							while (!token.equals("Registry")) {
								token = parser.getStreamObject();
							}
							registry = characterCollectionName(parser.getStreamObject());

							// Skip a def token before Ordering token
							while (!token.equals("Ordering")) {
								token = parser.getStreamObject();
							}
							ordering = characterCollectionName(parser.getStreamObject());

							// Skip a def token before Supplement token
							while (!token.equals("Supplement")) {
								token = parser.getStreamObject();
							}
							supplement = ((Integer) parser.getStreamObject()).byteValue();

							// Skip def and end tokens before /Supplement token
							while (!token.equals("end")) {
								token = parser.getStreamObject();
							}
						}
					}
					// always followed by a def token;
					token = parser.getStreamObject();
				}
				// find main CMap descriptors
				if (token instanceof Name) {
					nameString = token.toString();
					// find cMapName
					if (nameString.toLowerCase().indexOf("cmapname") >= 0) {
						// cMapName will always be a Name object
						token = parser.getStreamObject();
						cMapName = token.toString();
						// always followed by a def token;
						token = parser.getStreamObject();
					}
					// find cMapType
					if (nameString.toLowerCase().indexOf("cmaptype") >= 0) {
						// cMapType will always be a number
						token = parser.getStreamObject();
						cMapType = Byte.parseByte(token.toString());
						// always followed by a def token;
						token = parser.getStreamObject();
					}
					// find WMode (0: horizontal, 1: vertical writing)
					if (nameString.toLowerCase().indexOf("wmode") >= 0) {
						// WMode will always be a number
						token = parser.getStreamObject();
						wMode = Byte.parseByte(token.toString());
						// always followed by a def token;
						token = parser.getStreamObject();
					}
				}
				// record the actual CMap mappings
				if (token instanceof String) {
					String stringToken = (String) token;
					// find codeSpaceRange
					if (stringToken.equalsIgnoreCase("begincodespacerange")) {
						// before begincodespacerange, the number of ranges is
						// defined
						int numberOfRanges = (int) Float.parseFloat(previousToken.toString());
						codeSpaceRange = new CodeSpaceRange[numberOfRanges];
						// a range will always have two hex numbers
						for (int i = 0; i < numberOfRanges; i++) {
							// low end of range
							token = parser.getStreamObject();
							StringObject hexToken = (StringObject) token;
							int startRange = hexToken.getUnsignedInt(0, hexToken.getLength());

							// high end of range
							token = parser.getStreamObject();
							hexToken = (StringObject) token;
							int endRange = hexToken.getUnsignedInt(0, hexToken.getLength());

							// code length is calculated from the length of hex
							// token of high end.
							// Both low and high hex tokens in byte cannot be
							// different.
							byte codeLength = (byte) (hexToken.getLength() / 2);

							codeSpaceRange[i] = new CodeSpaceRange(startRange, endRange,
									(byte) codeLength);
						}
					}

					// Adobe Technical Note #5411 mentions that /CMapType is set
					// to 2 in "ToUnicode" mapping files.
					boolean toUnicodeMap = (cMapType == 2);

					// find each character mapping
					if (stringToken.equalsIgnoreCase("beginbfchar")
							|| stringToken.equalsIgnoreCase("begincidchar")) {
						// before beginbfchar/begincidchar, the number of
						// characters is defined
						int numberOfChars = (int) Float.parseFloat(previousToken.toString());
						// there can be multiple char maps so we don't want to
						// override previous values.
						if (defChars == null) {
							defChars = new CMapHashMap<Character, Integer>(numberOfChars);
						}
						token = parseCMapChars(defChars, parser, numberOfChars, toUnicodeMap);
					}

					// find characters mapping by range
					if (stringToken.equalsIgnoreCase("beginbfrange")
							|| stringToken.equalsIgnoreCase("begincidrange")) {
						int numberOfRanges = (int) Float.parseFloat(previousToken.toString());
						if (defRange == null) {
							defRange = new ArrayList<CMapRange>(numberOfRanges);
						}
						token = parseCMapRange(defRange, parser, numberOfRanges, toUnicodeMap);
					}

					// find UseCMap
					if (stringToken.equalsIgnoreCase("usecmap")) {
						useCMap = previousToken.toString();
					}

					// find notDefChars
					if (stringToken.equalsIgnoreCase("beginnotdefchar")) {
						// before beginnotdefchar, the number of characters is
						// defined
						int numberOfChars = (int) Float.parseFloat(previousToken.toString());
						if (notDefChars == null) {
							notDefChars = new CMapHashMap<Character, Integer>(numberOfChars);
						}
						token = parseCMapChars(notDefChars, parser, numberOfChars, toUnicodeMap);
					}

					// find notDefRange
					if (stringToken.equalsIgnoreCase("beginnotdefrange")) {
						int numberOfRanges = (int) Float.parseFloat(previousToken.toString());
						if (notDefRange == null) {
							notDefRange = new ArrayList<CMapRange>(numberOfRanges);
						}
						token = parseCMapRange(notDefRange, parser, numberOfRanges, toUnicodeMap);
					}
				}
				previousToken = token;
			}
		} catch (UnsupportedEncodingException e) {
			Logger.outputError("CMap parsing error", e);
		} catch (EOFException e) {
			// eat it, end of file stream
		} catch (IOException e) {
			Logger.outputStackTrace(e);
		} finally {
			if (cMapInputStream != null) {
				try {
					cMapInputStream.close();
				} catch (IOException e) {
					Logger.outputError("Error clossing cmap stream", e);
				}
			}
		}
	}

	/**
	 * Get Unicode value from hex token. This function takes care of surrogate
	 * pairs but does not handle ligatures.
	 * 
	 * @param hexToken
	 *            hex string
	 * @return Unicode value
	 * @throws IOException
	 */
	private int getUnicode(StringObject hexToken) {
		int value = 0;
		try {
			if (hexToken.getLength() >= 8) {
				value = -1;
				int high = hexToken.getUnsignedInt(0, 4);
				if (high >= 0x0000d800 && high <= 0x0000db7f) {
					// This is a high surrogate
					int low = hexToken.getUnsignedInt(4, 4);
					if (low >= 0x0000dc00 && low <= 0x0000dfff) {
						// This is a low surrogate so
						// make a pair.
						value = (high << 16) | low;
					}
				}
				if (value == -1) {
					// This must be a ligature, unicode array, which is not
					// supported yet.
					// Use first unicode only for now.
					value = high;
				}
			} else {
				value = hexToken.getUnsignedInt(0, hexToken.getLength());
			}
		} catch (NumberFormatException e) {
			Logger.outputError("CMAP: ", e);
		}

		return value;
	}

	/**
	 * Parse cmap stream and build a CMapHashMap.
	 * 
	 * @param chars
	 *            CMapHashMap for defChars or notDefChars
	 * @param parser
	 *            the PDF parser
	 * @param numberOfChars
	 *            the number of character definitions
	 * @param toUnicodeMap
	 *            true if this CMap is mapping to Unicode
	 * @return the last token processed
	 * @throws IOException
	 */
	private Object parseCMapChars(CMapHashMap<Character, Integer> chars, Parser parser,
			int numberOfChars, boolean toUnicodeMap) throws IOException {
		Object token = null;
		// a range will always have two hex numbers
		for (int i = 0; i < numberOfChars; i++) {
			// input value
			token = parser.getStreamObject();
			StringObject hexToken = (StringObject) token;
			char input = (char) hexToken.getUnsignedInt(0, hexToken.getLength());

			// mapping value
			int value = 0;
			token = parser.getStreamObject();
			if (token instanceof Integer) {
				value = ((Integer) token).intValue();
			} else {
				hexToken = (StringObject) token;
				try {
					if (hexToken.getLength() >= 8 && toUnicodeMap) {
						value = getUnicode(hexToken);
					} else {
						value = hexToken.getUnsignedInt(0, hexToken.getLength());
					}
				} catch (NumberFormatException e) {
					Logger.outputError("CMAP: ", e);
				}
			}
			chars.put(input, value);
		}
		return token;
	}

	/**
	 * Parse cmap stream and build an ArryList for CMapRange
	 * 
	 * @param chars
	 *            ArrayList for defRange or notDefRange
	 * @param parser
	 *            the PDF parser
	 * @param numberOfRanges
	 *            the number of ranges defined in this CMap
	 * @param toUnicodeMap
	 *            true if this CMap is mapping to Unicode
	 * @return the last token processed
	 * @throws IOException
	 */
	private Object parseCMapRange(ArrayList<CMapRange> range, Parser parser, int numberOfRanges,
			boolean toUnicodeMap) throws IOException {
		Object token = null;
		if (range == null) {
			range = new ArrayList<CMapRange>(numberOfRanges);
		}
		StringObject hexToken;
		char startRange;
		char endRange;
		// work through each range
		for (int i = 0; i < numberOfRanges; i++) {
			// look for start range.
			token = parser.getStreamObject();
			if (token instanceof StringObject) {
				hexToken = (StringObject) token;
				startRange = (char) hexToken.getUnsignedInt(0, hexToken.getLength());
			} else {
				// likely a malformed cmap
				break;
			}
			// end range
			token = parser.getStreamObject();
			if (token instanceof StringObject) {
				hexToken = (StringObject) token;
				endRange = (char) hexToken.getUnsignedInt(0, hexToken.getLength());
			} else {
				// likely a malformed cmap
				break;
			}

			// the next token will be vector or another Integer
			token = parser.getStreamObject();
			if (token instanceof Vector) {
				range.add(new CMapRange(startRange, endRange, (Vector) token));
				break;
			} else if (token instanceof Integer) {
				range.add(new CMapRange(startRange, endRange, (Integer) token));
			} else {
				hexToken = (StringObject) token;
				int offset;
				if (toUnicodeMap) {
					offset = getUnicode(hexToken);
				} else {
					offset = hexToken.getUnsignedInt(0, hexToken.getLength());
				}
				range.add(new CMapRange(startRange, endRange, offset));
			}
		}
		return token;
	}

	private class CMapHashMap<K, V> extends com.softwareimaging.jpdf.util.HashMap {
		public CMapHashMap() {
			super();
		}

		public CMapHashMap(int capacity) {
			super(capacity);
		}

		public Integer get(char k) {
			Object v;
			int value;
			v = super.get(Character.valueOf(k));
			if (v == null) {
				return null;
			}

			if (v instanceof Byte) {
				value = ((Byte) v).intValue() & 0xff;
			} else if (v instanceof Character) {
				value = ((Character) v).charValue();
			} else {
				value = ((Integer) v).intValue();
			}
			return Integer.valueOf(value);
		}

		/*
		 * Set value to the map using the smallest Number object for it
		 * 
		 * @param map map to use
		 * 
		 * @param k key
		 * 
		 * @param v value
		 */
		public void put(char k, int v) {
			Object value;
			if (v <= 255) {
				value = Byte.valueOf((byte) v);
			} else if (v <= 65535) {
				value = Character.valueOf((char) v);
			} else {
				value = Integer.valueOf(v);
			}
			super.put(Character.valueOf(k), value);
		}

		/**
		 * Write Byte values in ascending order.
		 * 
		 * @param out
		 *            output stream
		 * @throws IOException
		 */
		public void writeByteValues(CMapOutputStream out) throws IOException {
			TreeMap sortedMap = new TreeMap(this);
			int count = 0;
			Iterator itr = sortedMap.keySet().iterator();
			Object key, value;
			while (itr.hasNext()) {
				key = itr.next();
				value = sortedMap.get(key);
				if (value instanceof Byte) {
					count++;
				}
			}
			out.writeInt(count);

			itr = sortedMap.keySet().iterator();
			while (itr.hasNext()) {
				key = itr.next();
				value = sortedMap.get(key);
				if (value instanceof Byte) {
					out.writeChar(((Character) key).charValue());
					out.writeByte(((Byte) value).intValue());
				}
			}
			sortedMap.clear();
		}

		/**
		 * Write Character values in ascending order.
		 * 
		 * @param out
		 *            output stream
		 * @throws IOException
		 */
		public void writeCharacterValues(CMapOutputStream out) throws IOException {
			TreeMap sortedMap = new TreeMap(this);
			int count = 0;
			Iterator itr = sortedMap.keySet().iterator();
			Object key, value;
			while (itr.hasNext()) {
				key = itr.next();
				value = sortedMap.get(key);
				if (value instanceof Character) {
					count++;
				}
			}
			out.writeInt(count);

			itr = sortedMap.keySet().iterator();
			while (itr.hasNext()) {
				key = itr.next();
				value = sortedMap.get(key);
				if (value instanceof Character) {
					out.writeChar(((Character) key).charValue());
					out.writeChar(((Character) value).charValue());
				}
			}
			sortedMap.clear();
		}

		/**
		 * Write Integer values in ascending order.
		 * 
		 * @param out
		 *            output stream
		 * @throws IOException
		 */
		public void writeIntegerValues(CMapOutputStream out) throws IOException {
			TreeMap sortedMap = new TreeMap(this);
			int count = 0;
			Iterator itr = sortedMap.keySet().iterator();
			Object key, value;
			while (itr.hasNext()) {
				key = itr.next();
				value = sortedMap.get(key);
				if (value instanceof Integer) {
					count++;
				}
			}
			out.writeInt(count);

			itr = sortedMap.keySet().iterator();
			while (itr.hasNext()) {
				key = itr.next();
				value = sortedMap.get(key);
				if (value instanceof Integer) {
					out.writeChar(((Character) key).charValue());
					out.writeInt(((Integer) value).intValue());
				}
			}
			sortedMap.clear();
		}

		/**
		 * Read Byte values.
		 * 
		 * @param in
		 *            input stream
		 * @throws IOException
		 */
		public void readByteValues(CMapInputStream in) throws IOException {
			int length = in.readInt();
			char k;
			int v;
			for (int i = 0; i < length; i++) {
				k = in.readChar();
				v = in.readByte() & 0xff;
				put(k, v);
			}
		}

		/**
		 * Read Character values.
		 * 
		 * @param in
		 *            input stream
		 * @throws IOException
		 */
		public void readCharacterValues(CMapInputStream in) throws IOException {
			int length = in.readInt();
			char k;
			int v;
			for (int i = 0; i < length; i++) {
				k = in.readChar();
				v = in.readChar() & 0xffff;
				put(k, v);
			}
		}

		/**
		 * Read Integer values.
		 * 
		 * @param in
		 *            input stream
		 * @throws IOException
		 */
		public void readIntegerValues(CMapInputStream in) throws IOException {
			int length = in.readInt();
			char k;
			int v;
			for (int i = 0; i < length; i++) {
				k = in.readChar();
				v = in.readInt();
				put(k, v);
			}
		}

	}

	/**
	 * The method is called when input character code is converted to CID or
	 * Unicode.
	 * 
	 * @param charMap
	 *            value to map against the CMap
	 * @return mapped character value.
	 */
	public int toSelector(char charMap) {
		// simple check for init call
		if (registry != null) {
			// check notDefChars
			if (notDefChars != null) {
				Integer tmp = notDefChars.get(charMap);
				if (tmp != null) {
					return tmp.intValue();
				}
			}

			// check defChars
			if (defChars != null) {
				Integer tmp = defChars.get(charMap);
				if (tmp != null) {
					return tmp.intValue();
				}
			}

			// check notDefRange, which is usually for control characters and
			// mapped to space.
			if (notDefRange != null) {
				for (CMapRange range : notDefRange) {
					if (range.inRange(charMap)) {
						// notDefRange is always mapped to a CID unlike defRange
						return range.offsetValue;
					}
				}
			}

			// check defRange for matches, there may be many ranges to check
			if (defRange != null) {
				for (CMapRange range : defRange) {
					if (range.inRange(charMap)) {
						return range.getCMapValue(charMap);
					}
				}
			}

			if (baseCMap != null) {
				return baseCMap.toSelector(charMap);
			}
		}
		return charMap & 0xffff;
	}

	/*
	 * Check if a character exists in the code range
	 * 
	 * @param ch character to be checked
	 * 
	 * @param length length of character in byte
	 * 
	 * @return true if the character exists
	 */
	public boolean inCodeSpaceRange(char ch, byte length) {
		if (codeSpaceRange != null) {
			for (int range = 0; range < codeSpaceRange.length; range++) {
				if (codeSpaceRange[range].length == length && ch >= codeSpaceRange[range].start
						&& ch <= codeSpaceRange[range].end) {
					return true;
				}
			}
		}
		if (baseCMap != null) {
			return baseCMap.inCodeSpaceRange(ch, length);
		}
		return false;
	}

	private class CMapOutputStream {
		private OutputStream out;

		/*
		 * Constructor
		 * 
		 * @param out output stream
		 */
		public CMapOutputStream(OutputStream out) {
			this.out = out;
		}

		/*
		 * Write an int value
		 * 
		 * @param i int value to write
		 * 
		 * @throws IOException
		 */
		public void writeInt(int i) throws IOException {
			int b;
			b = (i >> 24) & 0xff;
			out.write(b);
			b = (i >> 16) & 0xff;
			out.write(b);
			b = (i >> 8) & 0xff;
			out.write(b);
			b = i & 0xff;
			out.write(b);
		}

		/*
		 * Write a byte value
		 * 
		 * @param b byte value to write
		 * 
		 * @throws IOException
		 */
		public void writeByte(int b) throws IOException {
			out.write(b);
		}

		/*
		 * Write a char value
		 * 
		 * @param c char value to write
		 * 
		 * @throws IOException
		 */
		public void writeChar(char c) throws IOException {
			int b;
			b = (c >> 8) & 0xff;
			out.write(b);
			b = c & 0xff;
			out.write(b);
		}

		/*
		 * Write a String value
		 * 
		 * @param s String to write
		 * 
		 * @throws IOException
		 */
		public void writeString(String s) throws IOException {
			if (s != null) {
				int length = s.length();
				writeByte(length);

				for (int i = 0; i < length; i++) {
					writeChar(s.charAt(i));
				}
			} else {
				writeByte(0);
			}
		}
	}

	private class CMapInputStream {
		private InputStream in;
		private int dataIdx;
		private int bytesRead;
		private byte[] data = new byte[512];

		public CMapInputStream(InputStream in) {
			this.in = in;
		}

		/*
		 * Read an int value
		 * 
		 * @return int value
		 * 
		 * @throws IOException
		 */
		public int readInt() throws IOException {
			int i = 0;
			i |= read() & 0xff;
			i <<= 8;
			i |= read() & 0xff;
			i <<= 8;
			i |= read() & 0xff;
			i <<= 8;
			i |= read() & 0xff;
			return i;
		}

		/*
		 * Read a byte value
		 * 
		 * @return byte value
		 * 
		 * @throws IOException
		 */
		public byte readByte() throws IOException {
			return (byte) read();
		}

		private int read() throws IOException {
			if (dataIdx >= bytesRead) {
				bytesRead = in.read(data);
				dataIdx = 0;
			}
			if (dataIdx < bytesRead) {
				return data[dataIdx++] & 0xFF;
			}
			return -1;
		}

		/*
		 * Read a char value
		 * 
		 * @return char value
		 * 
		 * @throws IOException
		 */
		public char readChar() throws IOException {
			char c = 0;
			c |= (char) (read() & 0xff);
			c <<= 8;
			c |= (char) (read() & 0xff);
			return c;
		}

		/*
		 * Read a String
		 * 
		 * @return String value
		 * 
		 * @throws IOException
		 */
		public String readString() throws IOException {
			int length = read();
			if (length == 0) {
				return null;
			}
			StringBuffer sb = new StringBuffer();
			for (int i = 0; i < length; i++) {
				sb.append(readChar());
			}
			return sb.toString();
		}
	}

	private class CodeSpaceRange {

		public int start;
		public int end;
		public byte length;

		/*
		 * Constructor
		 */
		public CodeSpaceRange() {
		}

		/*
		 * Constructor
		 * 
		 * @param start start value of the range
		 * 
		 * @param end end value of the range
		 * 
		 * @param length range size in byte
		 */
		public CodeSpaceRange(int start, int end, byte length) {
			this.start = start;
			this.end = end;
			this.length = length;
		}

		/*
		 * Write this to output stream
		 * 
		 * @param out output stream
		 * 
		 * @throws IOException
		 */
		public void write(CMapOutputStream out) throws IOException {
			out.writeInt(start);
			out.writeInt(end);
			out.writeByte(length);
		}

		/*
		 * Read this from input stream
		 * 
		 * @param in input stream
		 * 
		 * @throws IOException
		 */
		public void read(CMapInputStream in) throws IOException {
			start = in.readInt();
			end = in.readInt();
			length = in.readByte();
		}
	}

	private class CMapRange {

		// start value for a range
		char startRange = 0;
		// end value for a range
		char endRange = 0;
		// offset mapping
		int offsetValue = 0;
		// offset vector
		Vector offsetVector = null;

		public CMapRange() {
		}

		/**
		 * Create a new instance of a CMapRange, when it is a simple range
		 * mapping with an offset value.
		 * 
		 * @param startRange
		 *            start range of mapping
		 * @param endRange
		 *            end range of mapping
		 * @param offsetValue
		 *            value to offset a mapping by
		 */
		public CMapRange(char startRange, char endRange, int offsetValue) {
			this.startRange = startRange;
			this.endRange = endRange;
			this.offsetValue = offsetValue;
		}

		/**
		 * Create new instance of a CMapRange, when it is a more vector range
		 * mapping. Each valid number in the range maps the a corresponding
		 * value in the vector based on the numbers offset from the start range.
		 * 
		 * @param startRange
		 *            start range of mapping
		 * @param endRange
		 *            end range of the mapping
		 * @param offsetVecor
		 *            offset mapped vector
		 */
		public CMapRange(char startRange, char endRange, Vector offsetVecor) {
			this.startRange = startRange;
			this.endRange = endRange;
			this.offsetVector = offsetVecor;
		}

		/**
		 * Checks if a <code>value</code> is in the CMap bfrange.
		 * 
		 * @param value
		 *            value to check for containment
		 * @return true if the cmap falls inside one of the bfranges, false
		 *         otherwise.
		 */
		public boolean inRange(char value) {
			return (value >= startRange && value <= endRange);
		}

		/**
		 * Get the mapped value of <code>value</code>. It is assumed that
		 * inRange is called before this method is called. If the
		 * <code>value</code> is not in the range then a value of -1 is returned
		 * 
		 * @param value
		 *            value to find corresponding CMap for
		 * @return the mapped CMap value for <code>value</code>, -1 if the
		 *         <code>value</code> can not be mapped.
		 */
		public int getCMapValue(char value) {
			int tmp;
			// case of float offset
			if (offsetVector == null) {
				tmp = offsetValue + (value - startRange);// value + offsetValue;
			} else {// case of vector offset
				// value - startRange will give the index in the vector of the
				// desired
				// mapping value
				StringObject hexToken = (StringObject) offsetVector.elementAt(value - startRange);
				return hexToken.getUnsignedInt(0, hexToken.getLength());
			}
			return tmp;
		}

		/**
		 * Write this to output stream
		 * 
		 * @param out
		 *            output stream
		 * @throws IOException
		 */
		public void write(CMapOutputStream out) throws IOException {
			out.writeChar(startRange);
			out.writeChar(endRange);
			out.writeInt(offsetValue);
		}

		/**
		 * Read this from input stream
		 * 
		 * @param in
		 *            input stream
		 * @throws IOException
		 */
		public void read(CMapInputStream in) throws IOException {
			startRange = in.readChar();
			endRange = in.readChar();
			offsetValue = in.readInt();
		}
	}

	/**
	 * Write this to a file
	 * 
	 * @param out
	 *            output stream to write
	 * @throws IOException
	 */
	public void write(OutputStream out) throws IOException {
		CMapOutputStream cmapOut = new CMapOutputStream(out);

		cmapOut.writeString(registry);
		cmapOut.writeString(ordering);
		cmapOut.writeByte(supplement);
		cmapOut.writeString(cMapName);
		cmapOut.writeByte(cMapType);
		cmapOut.writeString(useCMap);
		cmapOut.writeByte(wMode);

		// Write codeSpaceRange
		if (codeSpaceRange != null) {
			cmapOut.writeInt(codeSpaceRange.length);
			for (int i = 0; i < codeSpaceRange.length; i++) {
				codeSpaceRange[i].write(cmapOut);
			}
		} else {
			cmapOut.writeInt(0);
		}

		// Write notDefChars
		if (notDefChars != null) {
			cmapOut.writeInt(notDefChars.size()); // Total size
			notDefChars.writeByteValues(cmapOut);
			notDefChars.writeCharacterValues(cmapOut);
			notDefChars.writeIntegerValues(cmapOut);
		} else {
			cmapOut.writeInt(0);
		}

		// Write defChars
		if (defChars != null) {
			cmapOut.writeInt(defChars.size()); // Total size
			defChars.writeByteValues(cmapOut);
			defChars.writeCharacterValues(cmapOut);
			defChars.writeIntegerValues(cmapOut);
		} else {
			cmapOut.writeInt(0);
		}

		// Write defRange
		if (defRange != null) {
			cmapOut.writeInt(defRange.size());
			for (int i = 0; i < defRange.size(); i++) {
				Object o = defRange.get(i);
				((CMapRange) o).write(cmapOut);
			}
		} else {
			cmapOut.writeInt(0);
		}

		// Write notDefRange
		if (notDefRange != null) {
			cmapOut.writeInt(notDefRange.size());
			for (int i = 0; i < notDefRange.size(); i++) {
				Object o = notDefRange.get(i);
				((CMapRange) o).write(cmapOut);
			}
		} else {
			cmapOut.writeInt(0);
		}
	}

	/**
	 * Read this from a file
	 * 
	 * @param in
	 *            input stream to read from
	 * @param fileRoot
	 *            the root of the cmap files. null if cmap files are in resource
	 * @throws IOException
	 */
	public void read(String cmapName, String fileRoot) throws IOException {
		InputStream in;
		if (fileRoot == null) {
			String cmapPath = CMap.cmapPath(cmapName);
			in = getClass().getResourceAsStream(cmapPath);
		} else {
			in = new FileInputStream(fileRoot + cmapName + cmapFileExtension);
		}

		if (in == null) {
			throw new IOException();
		}

		CMapInputStream cmapIn = new CMapInputStream(in);

		registry = cmapIn.readString();
		ordering = cmapIn.readString();
		supplement = cmapIn.readByte();
		cMapName = cmapIn.readString();
		cMapType = cmapIn.readByte();
		useCMap = cmapIn.readString();
		// Read base CMap if exists.
		if (useCMap != null) {
			InputStream base;
			baseCMap = new CMap();
			baseCMap.read(useCMap, fileRoot);
		}
		wMode = cmapIn.readByte();

		// Read codeSpaceRange
		int length = cmapIn.readInt();
		if (length != 0) {
			codeSpaceRange = new CodeSpaceRange[length];
			for (int i = 0; i < length; i++) {
				codeSpaceRange[i] = new CodeSpaceRange();
				codeSpaceRange[i].read(cmapIn);
			}
		} else {
			codeSpaceRange = null;
		}

		// Read notDefChars
		length = cmapIn.readInt();
		if (length != 0) {
			notDefChars = new CMapHashMap<Character, Integer>(length);
			notDefChars.readByteValues(cmapIn);
			notDefChars.readCharacterValues(cmapIn);
			notDefChars.readIntegerValues(cmapIn);
		} else {
			notDefChars = null;
		}

		// Read defChars
		length = cmapIn.readInt();
		if (length != 0) {
			defChars = new CMapHashMap<Character, Integer>(length);
			defChars.readByteValues(cmapIn);
			defChars.readCharacterValues(cmapIn);
			defChars.readIntegerValues(cmapIn);
		} else {
			defChars = null;
		}

		// Read defRange
		length = cmapIn.readInt();
		if (length != 0) {
			defRange = new ArrayList<CMapRange>(length);
			for (int i = 0; i < length; i++) {
				CMapRange range = new CMapRange();
				range.read(cmapIn);
				defRange.add(range);
			}
		} else {
			defRange = null;
		}

		// Read notDefRange
		length = cmapIn.readInt();
		if (length != 0) {
			notDefRange = new ArrayList<CMapRange>(length);
			for (int i = 0; i < length; i++) {
				CMapRange range = new CMapRange();
				range.read(cmapIn);
				notDefRange.add(range);
			}
		} else {
			notDefRange = null;
		}
	}

	/**
	 * Get cmap resource path
	 * 
	 * @param cmapName
	 *            resourc name
	 * @return cmap resource path
	 */
	public static String cmapPath(String cmapName) {
		return cmapRoot + cmapName + cmapFileExtension;
	}

}
