/*
 * FSDefineFont2.java
 * Transform
 * 
 * Copyright (c) 2001-2008 Flagstone Software Ltd. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 *  * Neither the name of Flagstone Software Ltd. nor the names of its contributors 
 *    may be used to endorse or promote products derived from this software 
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.flagstone.transform;

import java.util.*;

/**
 * FSDefineFont2 defines the shapes and layout of the glyphs used in a font.
 * 
 * <p>
 * It extends the functionality provided by the FSDefineFont class by:
 * </p>
 * 
 * <ul>
 * <li>allowing more than 65535 glyphs in a particular font.</li>
 * <li>including the functionality provided by the FontInfo class.</li>
 * <li>specifying ascent, descent and leading layout information for the font.</li>
 * <li>specifying advances for each glyph.</li>
 * <li>specifying bounding rectangles for each glyph.</li>
 * <li>specifying kerning pairs defining the distance between pairs of glyphs.</li>
 * </ul>
 * 
 * <p>
 * The FSDefineFont2 class combines the attributes of the FSFontInfo and
 * FSDefineFont classes:
 * </p>
 * 
 * <table class="datasheet">
 * 
 * <tr>
 * <th align="left" colspan="2">Attributes</th>
 * </tr>
 * 
 * <tr>
 * <td>type</td>
 * <td>Identifies the data structure when it is encoded. The type attribute is
 * read-only and may be used when iterating through the objects in an FSMovie
 * object to identify the object class without using run-time type checking.</td>
 * </tr>
 * 
 * <tr>
 * <td>identifier</td>
 * <td>The unique identifier for this object.</td>
 * </tr>
 * 
 * <tr>
 * <td>encoding</td>
 * <td>Specifies whether the font codes are ANSI, SJIS or Unicode.</td>
 * </tr>
 * 
 * <tr>
 * <td>language</td>
 * <td>Identifies the spoken language for the font, either FSText.Japanese,
 * FSText.Korean, FSText.Latin, FSText.SimplifiedChinese,
 * FSText.TraditionalChinese. This attribute is used when displaying text fields
 * and assists in determining language-appropriate line breaks.</td>
 * </tr>
 * 
 * <tr>
 * <td>italic</td>
 * <td>Whether the font is italicised.</td>
 * </tr>
 * 
 * <tr>
 * <td>bold</td>
 * <td>Whether the font is bold.</td>
 * </tr>
 * 
 * <tr>
 * <td>small</td>
 * <td>Small point size fonts will be drawn aligned to pixel boundaries to
 * improve rendering.</td>
 * </tr>
 * 
 * <tr>
 * <td>name</td>
 * <td>The name of the font.</td>
 * </tr>
 * 
 * <tr>
 * <td>shapes</td>
 * <td>An array of FSShape objects that define the outline for each glyph.</td>
 * </tr>
 * 
 * <tr>
 * <td>codes</td>
 * <td>An array mapping a glyph index to a particular character code in the
 * font.</td>
 * </tr>
 * 
 * </table>
 * 
 * <p>
 * Additional layout information for the glyphs in the font can also be
 * specified through the following set of attributes:
 * </p>
 * 
 * <table class="datasheet">
 * 
 * <tr>
 * <td>ascent</td>
 * <td>The maximum ascent for the font in twips.</td>
 * </tr>
 * 
 * <tr>
 * <td>descent</td>
 * <td>The maximum descent for the font in twips.</td>
 * </tr>
 * 
 * <tr>
 * <td>leading</td>
 * <td>The leading for the font.</td>
 * </tr>
 * 
 * <tr>
 * <td>advances</td>
 * <td>An array of integers that define the advance for each glyph in the font.</td>
 * </tr>
 * 
 * <tr>
 * <td>bounds</td>
 * <td>An array of FSBounds object that define the bounding rectangle for each
 * glyph in the font.</td>
 * </tr>
 * 
 * <tr>
 * <td>kernings</td>
 * <td>An array of FSKerning objects that define adjustment to the advance for
 * particular pairs of glyphs in the font.</td>
 * </tr>
 * </table>
 * 
 * <p>
 * The default values for the ascent, descent and leading attributes is zero and
 * the arrays containing the advances, bounds and kernings are empty. If the
 * attributes all set to their default values then the layout information will
 * not be encoded. All the values must be set and the arrays must contain the
 * same number of entries as there are glyphs before the layout information will
 * be encoded.
 * </p>
 * 
 * <h1 class="datasheet">History</h1>
 * 
 * <p>
 * The FSDefineFont2 class represents the DefineFont2 structure from the
 * Macromedia Flash (SWF) File Format Specification. It was introduced in Flash
 * 2. Support for spoken languages was added in Flash 6. Support for small point
 * size fonts was added in Flash 7.
 * </p>
 */
public class FSDefineFont2 extends FSDefineObject
{
	private int encoding = FSText.Unicode;
	private boolean small = false;
	private boolean italic = false;
	private boolean bold = false;
	private int language = 0;
	private String name = "";
	private ArrayList shapes = new ArrayList();
	private ArrayList codes = new ArrayList();
	private int ascent = 0;
	private int descent = 0;
	private int leading = 0;
	private ArrayList advances = null;
	private ArrayList bounds = null;
	private ArrayList kernings = null;

	/**
	 * Construct an FSDefineFont2 object, initalizing it with values decoded
	 * from an encoded object.
	 * 
	 * @param coder
	 *            an FSCoder containing the binary data.
	 */
	public FSDefineFont2(FSCoder coder)
	{
		super(DefineFont2, 0);
		decode(coder);
	}

	/**
 	 * Constructs an FSDefineFont2 object specifying only the name of the font.
	 * 
	 * If none of the remaining attributes are set the Flash Player will load
	 * the font from the system on which it is running or substitute a suitable
	 * font if the specified font cannot be found. This is particularly useful
	 * when defining fonts that will be used to display text in
	 * FSDefineTextField objects.
	 * 
	 * The font will be defined to use Unicode encoding. The flags which define
	 * the font's face will be set to false. The arrays of glyphs which define
	 * the shapes and the code which map the character codes to a particular
	 * glyph will remain empty since the font is loaded from the system on which
	 * it is displayed.
	 * 
	 * @param anIdentifier
	 *            the unique identifier for this object.
	 * @param name
	 *            the name of the font.
	 */
	public FSDefineFont2(int anIdentifier, String name)
	{
		super(DefineFont2, anIdentifier);
		setName(name);
	}

	/**
	 * Constructs an FSDefineFont2 object by copying values from an existing
	 * object.
	 * 
	 * @param obj
	 *            an FSDefineFont2 object.
	 */
	public FSDefineFont2(FSDefineFont2 obj)
	{
		super(obj);
		encoding = obj.encoding;
		small = obj.small;
		italic = obj.italic;
		bold = obj.bold;
		language = obj.language;
		name = new String(obj.name);

		shapes = new ArrayList();

		for (Iterator i = obj.shapes.iterator(); i.hasNext();)
			shapes.add(((FSShape) i.next()).clone());

		codes = new ArrayList();

		for (Iterator i = obj.codes.iterator(); i.hasNext();)
			codes.add(new Integer(((Integer) i.next()).intValue()));

		ascent = obj.ascent;
		descent = obj.descent;
		leading = obj.leading;

		if (advances != null)
		{
			advances = new ArrayList();

			for (Iterator i = obj.advances.iterator(); i.hasNext();)
				advances.add(new Integer(((Integer) i.next()).intValue()));
		}

		if (bounds != null)
		{
			bounds = new ArrayList();

			for (Iterator i = obj.bounds.iterator(); i.hasNext();)
				bounds.add(((FSBounds) i.next()).clone());
		}

		if (kernings != null)
		{
			kernings = new ArrayList();

			for (Iterator i = obj.kernings.iterator(); i.hasNext();)
				kernings.add(((FSKerning) i.next()).clone());
		}
	}

	/**
	 * Add a shape to the array of shapes.
	 * 
	 * @param anObject
	 *            a shape.
	 */
	public void add(FSShape anObject)
	{
		shapes.add(anObject);
	}

	/**
	 * Add a code to the array of codes.
	 * 
	 * @param aCode
	 *            a code for a glyph.
	 */
	public void addCode(int aCode)
	{
		codes.add(new Integer(aCode));
	}

	/**
	 * Add an advance to the array of advances.
	 * 
	 * @param anAdvance
	 *            an advance for a glyph.
	 */
	public void addAdvance(int anAdvance)
	{
		advances.add(new Integer(anAdvance));
	}

	/**
	 * Add a bounds object to the array of bounds for each glyph.
	 * 
	 * @param anObject
	 *            an FSBounds.
	 */
	public void add(FSBounds anObject)
	{
		bounds.add(anObject);
	}

	/**
	 * Add a kerning object to the array of kernings for pairs of glyphs.
	 * 
	 * @param anObject
	 *            an FSKerning.
	 */
	public void add(FSKerning anObject)
	{
		kernings.add(anObject);
	}

	/**
	 * Gets the encoding used for the font codes, either FSText.ASCII,
	 * FSText.SJIS or FSText.Unicode.
	 * 
	 * @return the encoding used to represent characters rendered in the font.
	 */
	public int getEncoding()
	{
		return encoding;
	}

	// Flash 7
	/**
	 * Does the font have a small point size. This is used only with a Unicode
	 * font encoding.
	 * 
	 * @return a boolean indicating whether the font will be aligned on pixel
	 *         boundaries.
	 */
	public boolean isSmall()
	{
		return small;
	}

	/**
	 * Sets the font is small. Used only with Unicode fonts.
	 * 
	 * @param aBool
	 *            a boolean flag indicating the font will be aligned on pixel
	 *            boundaries.
	 */
	public void setSmall(boolean aBool)
	{
		small = aBool;
	}

	// End Flash 7

	/**
	 * Is the font italicised.
	 * 
	 * @return a boolean indicating whether the font is rendered in italics.
	 */
	public boolean isItalic()
	{
		return italic;
	}

	/**
	 * Is the font bold.
	 * 
	 * @return a boolean indicating whether the font is rendered in a bold face.
	 */
	public boolean isBold()
	{
		return bold;
	}

	// Flash 6
	/**
	 * Gets the language code identifying the type of spoken language for the
	 * font either FSText.Japanese, FSText.Korean, FSText.Latin,
	 * FSText.SimplifiedChinese or FSText.TraditionalChinese.
	 * 
	 * @return the language code used to determine how line-breaks are inserted
	 *         into text rendered using the font. Returns 0 if the object was
	 *         decoded from a movie contains Flash 5 or less.
	 */
	public int getLanguage()
	{
		return language;
	}

	/**
	 * Sets the language code used to determine the position of line-breaks in
	 * text rendered using the font.
	 * 
	 * NOTE: The language attribute is ignored if the object is encoded in a
	 * Flash 5 movie.
	 * 
	 * @param code
	 *            the code identifying the spoken language either
	 *            FSText.Japanese, FSText.Korean, FSText.Latin,
	 *            FSText.SimplifiedChinese or FSText.TraditionalChinese.
	 */
	public void setLanguage(int code)
	{
		language = code;
	}

	// End Flash 6

	/**
	 * Gets the name of the font family.
	 * 
	 * @return the name of the font.
	 */
	public String getName()
	{
		return name;
	}

	/**
	 * Gets the array of shapes used to define the outlines of each font glyph.
	 * 
	 * @return an array of FSShape objects
	 */
	public ArrayList getShapes()
	{
		return shapes;
	}

	/**
	 * Gets the array of codes used to identify each glyph in the font. The
	 * ordinal position of each Integer representing a code identifies a
	 * particular glyph in the shapes array.
	 * 
	 * @return an array of Integer objects that contain the character codes for
	 *         each glyph in the font.
	 */
	public ArrayList getCodes()
	{
		return codes;
	}

	/**
	 * Gets the ascent for the font in twips.
	 * 
	 * @return the ascent for the font.
	 */
	public int getAscent()
	{
		return ascent;
	}

	/**
	 * Gets the descent for the font in twips.
	 * 
	 * @return the descent for the font.
	 */
	public int getDescent()
	{
		return descent;
	}

	/**
	 * Gets the leading for the font in twips.
	 * 
	 * @return the leading for the font.
	 */
	public int getLeading()
	{
		return leading;
	}

	/**
	 * Gets the array of advances defined for each glyph in the font.
	 * 
	 * @return an array of Integer objects that contain the advance for each
	 *         glyph in the font.
	 */
	public ArrayList getAdvances()
	{
		return advances;
	}

	/**
	 * Gets the array of bounding rectangles defined for each glyph in the font.
	 * 
	 * @return an array of FSBounds objects.
	 */
	public ArrayList getBounds()
	{
		return bounds;
	}

	/**
	 * Gets the array of kerning records that define the spacing between glyph
	 * pairs.
	 * 
	 * @return an array of FSKerning objects that define the spacing adjustment
	 *         between pairs of glyphs.
	 */
	public ArrayList getKernings()
	{
		return kernings;
	}

	/**
	 * Sets the encoding for the font character codes.
	 * 
	 * @param aType
	 *            the encoding scheme used to denote characters, either
	 *            FSText.ASCII, FSText.SJIS or FSText.Unicode.
	 */
	public void setEncoding(int aType)
	{
		encoding = aType;
	}

	/**
	 * Set the font is italicised.
	 * 
	 * @param aBool
	 *            a boolean flag indicating whether the font will be rendered in
	 *            italics
	 */
	public void setItalic(boolean aBool)
	{
		italic = aBool;
	}

	/**
	 * Set the font is bold.
	 * 
	 * @param aBool
	 *            a boolean flag indicating whether the font will be rendered in
	 *            bold face.
	 */
	public void setBold(boolean aBool)
	{
		bold = aBool;
	}

	/**
	 * Set the name of the font.
	 * 
	 * @param aString
	 *            the name assigned to the font, identifying the font family.
	 */
	public void setName(String aString)
	{
		name = aString;
	}

	/**
	 * Set the array of shape records that define the outlines of the characters
	 * used from the font.
	 * 
	 * @param anArray
	 *            an array of FSShape objects that define the glyphs for the
	 *            font.
	 */
	public void setShapes(ArrayList anArray)
	{
		shapes = anArray;
	}

	/**
	 * Sets the codes used to identify each glyph in the font.
	 * 
	 * @param anArray
	 *            sets the code table that maps a particular glyph to a
	 *            character code.
	 */
	public void setCodes(ArrayList anArray)
	{
		codes = anArray;
	}

	/**
	 * Sets the ascent for the font in twips.
	 * 
	 * @param aNumber
	 *            the ascent for the font.
	 */
	public void setAscent(int aNumber)
	{
		ascent = aNumber;
	}

	/**
	 * Sets the descent for the font in twips.
	 * 
	 * @param aNumber
	 *            the descent for the font.
	 */
	public void setDescent(int aNumber)
	{
		descent = aNumber;
	}

	/**
	 * Sets the leading for the font in twips.
	 * 
	 * @param aNumber
	 *            the descent for the font.
	 */
	public void setLeading(int aNumber)
	{
		leading = aNumber;
	}

	/**
	 * Sets the array of advances for each glyph in the font.
	 * 
	 * @param anArray
	 *            of Integer objects that define the spacing between glyphs.
	 */
	public void setAdvances(ArrayList anArray)
	{
		advances = anArray;
	}

	/**
	 * Sets the array of bounding rectangles for each glyph in the font.
	 * 
	 * @param anArray
	 *            an array of FSBounds objects that define the bounding
	 *            rectangles that enclose each glyph in the font.
	 */
	public void setBounds(ArrayList anArray)
	{
		bounds = anArray;
	}

	/**
	 * Sets the array of kerning records for pairs of glyphs in the font.
	 * 
	 * @param anArray
	 *            an array of FSKerning objects that define an adjustment
	 *            applied to the spacing between pairs of glyphs.
	 */
	public void setKernings(ArrayList anArray)
	{
		kernings = anArray;
	}

	public Object clone()
	{
		FSDefineFont2 anObject = (FSDefineFont2) super.clone();

		anObject.shapes = new ArrayList();

		for (Iterator i = shapes.iterator(); i.hasNext();)
			anObject.shapes.add(((FSShape) i.next()).clone());

		anObject.codes = new ArrayList();

		for (Iterator i = codes.iterator(); i.hasNext();)
			anObject.codes.add(new Integer(((Integer) i.next()).intValue()));

		if (advances != null)
		{
			anObject.advances = new ArrayList();

			for (Iterator i = advances.iterator(); i.hasNext();)
				anObject.advances.add(new Integer(((Integer) i.next())
								.intValue()));
		}

		if (bounds != null)
		{
			anObject.bounds = new ArrayList();

			for (Iterator i = bounds.iterator(); i.hasNext();)
				anObject.bounds.add(((FSBounds) i.next()).clone());
		}

		if (kernings != null)
		{
			anObject.kernings = new ArrayList();

			for (Iterator i = kernings.iterator(); i.hasNext();)
				anObject.kernings.add(((FSKerning) i.next()).clone());
		}

		return anObject;
	}

	public boolean equals(Object anObject)
	{
		boolean result = false;

		if (super.equals(anObject))
		{
			FSDefineFont2 typedObject = (FSDefineFont2) anObject;

			result = encoding == typedObject.encoding;
			// Flash 7
			result = result && small == typedObject.small;
			// End Flash 7
			result = result && italic == typedObject.italic;
			result = result && bold == typedObject.bold;
			// Flash 6
			result = result && language == typedObject.language;
			// End Flash 6

			if (name != null)
				result = result && name.equals(typedObject.name);
			else
				result = result && name == typedObject.name;

			if (shapes != null)
				result = result && shapes.equals(typedObject.shapes);
			else
				result = result && shapes == typedObject.shapes;

			if (codes != null)
				result = result && codes.equals(typedObject.codes);
			else
				result = result && codes == typedObject.codes;

			if (containsLayoutInfo())
			{
				result = result && ascent == typedObject.ascent;
				result = result && descent == typedObject.descent;
				result = result && leading == typedObject.leading;

				if (advances != null)
					result = result && advances.equals(typedObject.advances);
				else
					result = result && advances == typedObject.advances;

				if (bounds != null)
					result = result && bounds.equals(typedObject.bounds);
				else
					result = result && bounds == typedObject.bounds;

				if (kernings != null)
					result = result && kernings.equals(typedObject.kernings);
				else
					result = result && kernings == typedObject.kernings;
			}
		}
		return result;
	}

	public void appendDescription(StringBuffer buffer, int depth)
	{
		buffer.append(name());

		if (depth > 0)
		{
			buffer.append(": { ");
			Transform.append(buffer, "encoding", encoding);
			// Flash 7
			Transform.append(buffer, "small", small);
			// End Flash 7
			Transform.append(buffer, "italic", italic);
			Transform.append(buffer, "bold", bold);
			// Flash 6
			Transform.append(buffer, "language", language);
			// End Flash 6
			Transform.append(buffer, "name", name);
			Transform.append(buffer, "shapes", shapes, depth);
			Transform.append(buffer, "codes", codes, depth);
			Transform.append(buffer, "ascent", ascent);
			Transform.append(buffer, "descent", descent);
			Transform.append(buffer, "leading", leading);
			Transform.append(buffer, "advances", advances, depth);
			Transform.append(buffer, "bounds", bounds, depth);
			Transform.append(buffer, "kernings", kernings, depth);
			buffer.append("}");
		}
	}

	public int length(FSCoder coder)
	{
		boolean wideOffsets = wideOffsetsForIndexedArray(shapes, coder);
		int wideCodes = 0;

		if (coder.context[FSCoder.Version] > 5)
			wideCodes = 1;
		else
			wideCodes = (encoding != FSText.ANSI) ? 1 : 0;

		super.length(coder);

		coder.context[FSCoder.NumberOfFillBits] = 1;
		coder.context[FSCoder.NumberOfLineBits] = 0;
		coder.context[FSCoder.WideCodes] = wideCodes;
		length += 3;
		length += coder.strlen(name, false);
		length += 2;
		length += shapes.size() * (wideOffsets ? 4 : 2);
		length += wideOffsets ? 4 : 2;

		for (Iterator shapeIterator = shapes.iterator(); shapeIterator
						.hasNext();)
			length += ((FSTransformObject) shapeIterator.next()).length(coder);

		length += shapes.size() * ((wideCodes == 1) ? 2 : 1);

		if (containsLayoutInfo())
		{
			length += 6;
			length += shapes.size() * 2;

			for (Iterator boundsIterator = bounds.iterator(); boundsIterator
							.hasNext();)
				length += ((FSTransformObject) boundsIterator.next())
								.length(coder);

			length += 2;
			length += kernings.size() * ((wideCodes == 1) ? 6 : 4);
		}

		coder.context[FSCoder.NumberOfFillBits] = 0;
		coder.context[FSCoder.NumberOfLineBits] = 0;
		coder.context[FSCoder.WideCodes] = 0;

		return length;
	}

	public void encode(FSCoder coder)
	{
		boolean wideOffsets = wideOffsetsForIndexedArray(shapes, coder);
		int wideCodes = 0;
		int format = 0;

		if (coder.context[FSCoder.Version] > 5)
			wideCodes = 1;
		else
			wideCodes = (encoding != FSText.ANSI) ? 1 : 0;

		if (encoding == FSText.ANSI)
			format = 1;
		// Flash 7
		else if (small)
			format = 2;
		// End Flash 7
		else if (encoding == FSText.SJIS)
			format = 4;

		super.encode(coder);

		coder.context[FSCoder.NumberOfFillBits] = 1;
		coder.context[FSCoder.NumberOfLineBits] = 0;
		coder.context[FSCoder.WideCodes] = wideCodes;

		coder.writeBits(containsLayoutInfo() ? 1 : 0, 1);
		coder.writeBits(format, 3);
		coder.writeBits(wideOffsets ? 1 : 0, 1);
		coder.writeBits(wideCodes, 1);
		coder.writeBits(italic ? 1 : 0, 1);
		coder.writeBits(bold ? 1 : 0, 1);
		coder.writeWord(coder.context[FSCoder.Version] > 5 ? language : 0, 1);
		coder.writeWord(coder.strlen(name, false), 1);

		coder.writeString(name);
		coder.writeWord(shapes.size(), 2);

		int currentLocation;
		int offset;

		int tableStart = coder.getPointer();
		int tableEntry = tableStart;
		int entrySize = wideOffsets ? 4 : 2;

		for (int i = 0; i <= shapes.size(); i++)
			coder.writeWord(0, entrySize);

		for (Iterator i = shapes.iterator(); i.hasNext(); tableEntry += entrySize << 3)
		{
			currentLocation = coder.getPointer();
			offset = (coder.getPointer() - tableStart) >> 3;

			coder.setPointer(tableEntry);
			coder.writeWord(offset, entrySize);
			coder.setPointer(currentLocation);

			((FSTransformObject) i.next()).encode(coder);
		}

		currentLocation = coder.getPointer();
		offset = (coder.getPointer() - tableStart) >> 3;

		coder.setPointer(tableEntry);
		coder.writeWord(offset, entrySize);
		coder.setPointer(currentLocation);

		for (Iterator codesIterator = codes.iterator(); codesIterator.hasNext();)
			coder.writeWord(((Integer) codesIterator.next()).intValue(),
							(wideCodes == 1) ? 2 : 1);

		if (containsLayoutInfo())
		{
			coder.writeWord(ascent, 2);
			coder.writeWord(descent, 2);
			coder.writeWord(leading, 2);

			for (Iterator advancesIterator = advances.iterator(); advancesIterator
							.hasNext();)
				coder.writeWord(((Integer) advancesIterator.next()).intValue(),
								2);

			for (Iterator boundsIterator = bounds.iterator(); boundsIterator
							.hasNext();)
				((FSTransformObject) boundsIterator.next()).encode(coder);

			coder.writeWord(kernings.size(), 2);

			for (Iterator kerningIterator = kernings.iterator(); kerningIterator
							.hasNext();)
				((FSTransformObject) kerningIterator.next()).encode(coder);
		}

		coder.context[FSCoder.NumberOfFillBits] = 0;
		coder.context[FSCoder.NumberOfLineBits] = 0;
		coder.context[FSCoder.WideCodes] = 0;

		coder.endObject(name());
	}

	public void decode(FSCoder coder)
	{
		boolean containsWideOffsets = false;
		boolean containsWideCodes = false;
		boolean containsLayout = false;

		int glyphCount = 0;
		int kerningCount = 0;
		int nameLength = 0;

		super.decode(coder);

		containsLayout = coder.readBits(1, false) != 0 ? true : false;
		int format = coder.readBits(3, false);

		encoding = FSText.Unicode;

		if (format == 1)
			encoding = FSText.ANSI;
		// Flash 7
		else if (format == 2)
			small = true;
		// End Flash 7
		else if (format == 4)
			encoding = FSText.SJIS;

		containsWideOffsets = coder.readBits(1, false) != 0 ? true : false;
		containsWideCodes = coder.readBits(1, false) != 0 ? true : false;

		coder.context[FSCoder.WideCodes] = containsWideCodes ? 1 : 0;

		italic = coder.readBits(1, false) != 0 ? true : false;
		bold = coder.readBits(1, false) != 0 ? true : false;
		language = coder.readBits(8, false);
		nameLength = coder.readWord(1, false);
		name = coder.readString(nameLength);

		if (name.length() > 0)
		{
			while (name.charAt(name.length() - 1) == 0)
			{
				name = name.substring(0, name.length() - 1);
			}
		}

		glyphCount = coder.readWord(2, false);

		int offsetStart = coder.getPointer();
		int[] offset = new int[glyphCount + 1];

		for (int i = 0; i < glyphCount; i++)
			offset[i] = coder.readWord((containsWideOffsets) ? 4 : 2, false);

		offset[glyphCount] = coder.readWord((containsWideOffsets) ? 4 : 2,
						false);

		for (int i = 0; i < glyphCount; i++)
		{
			coder.setPointer(offsetStart + (offset[i] << 3));

			if (coder.context[FSCoder.DecodeGlyphs] == 1)
				shapes.add(new FSShape(coder));
			else
				shapes.add(new FSShape(coder, offset[i + 1] - offset[i]));

		}

		for (int i = 0; i < glyphCount; i++)
			codes.add(new Integer(coder.readWord((containsWideCodes) ? 2 : 1,
							false)));

		if (containsLayout)
		{
			ascent = coder.readWord(2, true);
			descent = coder.readWord(2, true);
			leading = coder.readWord(2, true);

			advances = new ArrayList();
			bounds = new ArrayList();
			kernings = new ArrayList();

			for (int i = 0; i < glyphCount; i++)
				advances.add(new Integer(coder.readWord(2, true)));

			for (int i = 0; i < glyphCount; i++)
				bounds.add(new FSBounds(coder));

			kerningCount = coder.readWord(2, false);

			for (int i = 0; i < kerningCount; i++)
				kernings.add(new FSKerning(coder));
		}

		coder.context[FSCoder.WideCodes] = 0;

		coder.endObject(name());
	}

	private boolean containsLayoutInfo()
	{
		boolean layout = false;

		layout = layout || ascent != 0;
		layout = layout || descent != 0;
		layout = layout || leading != 0;
		layout = layout || ((advances != null) && advances.size() > 0);
		layout = layout || ((bounds != null) && bounds.size() > 0);
		layout = layout || ((kernings != null) && kernings.size() > 0);

		return layout;
	}

	private boolean wideOffsetsForIndexedArray(ArrayList anArray, FSCoder coder)
	{
		boolean wideOffsets = false;

		int glyphLength = 0;

		for (Iterator i = anArray.iterator(); i.hasNext();)
			glyphLength += ((FSTransformObject) i.next()).length(coder);

		if ((anArray.size() * 2 + glyphLength) > 65535)
			wideOffsets = true;

		return wideOffsets;
	}
}
