/*
 * FSText.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.*;

/**
 * FSText is used to display a line of text.
 * 
 * <p>
 * It contains an array of FSCharacter objects which identify the glyphs that
 * will be displayed along with style information that sets the colour of the
 * text, the size of the font and the relative placement of the line within a
 * block of text.
 * </p>
 * 
 * <table class="datasheet">
 * 
 * <tr>
 * <th align="left" colspan="2">Attributes</th>
 * </tr>
 * 
 * <tr>
 * <td><a name="FSText_0">identifier</a></td>
 * <td>The identifier, in the range 1..65535, of the font object which contains
 * the glyphs representing each character displayed. Optional. Set to 0 if the
 * font is not being changed.</td>
 * </tr>
 * 
 * <tr>
 * <td><a name="FSText_1">color</a></td>
 * <td>An FSColor object describing colour of the text. Optional. Set to null
 * if the colour is not being changed.</td>
 * </tr>
 * 
 * <tr>
 * <td><a name="FSText_2">offsetX</a></td>
 * <td>The offset, in twips, relative to the left side bounding rectangle of
 * the text block where the text will be displayed. Optional. Set to zero if no
 * indent is being specified.</td>
 * </tr>
 * 
 * <tr>
 * <td><a name="FSText_3">offsetY</a></td>
 * <td>The offset, in twips, relative to the bottom of the bounding rectangle
 * of the text block where the text will be displayed. Optional. Set to the
 * height of the font if only a single line of text is being displayed so the
 * characters are placed correctly within the bounding rectangle defined for the
 * text object.</td>
 * </tr>
 * 
 * <tr>
 * <td><a name="FSText_4">height</a></td>
 * <td>The height of the font, in twips, used to render the characters.
 * Optional. Set to zero if the font size is not being changed.</td>
 * </tr>
 * 
 * <tr>
 * <td><a name="FSText_5">characters</a></td>
 * <td>An array of FSCharacter objects which identify the glyph that will be
 * displayed along with the spacing to the next character in the line of text.</td>
 * </tr>
 * 
 * </table>
 * 
 * <p>
 * Whether the alpha channel in the colour needs to be specified depends on the
 * class the FSText is added to. The FSDefineText2 class supports transparent
 * text while FSDefineText class does not.
 * </p>
 * 
 * <p>
 * The x and y offsets are used to control how several FSText objects are laid
 * out to create a block of text. The y offset is specified relative to the
 * bottom edge of the bounding rectangle, which is actually closer to the top of
 * the screen as the direction of the y-axis is from the top to the bottom of
 * the screen. In this respect Flash is counter-intuitive. Lines with higher
 * offset values are displayed below lines with lower offsets.
 * </p>
 * 
 * <p>
 * When several FSText objects are used to define a block of text using the
 * FSDefineText or FSDefineText2 classes the attributes such as the font
 * selected, size and colour must be specified in the first FSText object. For
 * the remaining FSText objects these may be set to null or zero. The attributes
 * will apply to all following FSText object until explicitly changed.
 * </p>
 * 
 * <h1 class="datasheet">Examples</h1>
 * 
 * <p>
 * The creation and layout of the glyphs to create the text is too onerous to
 * perform from scratch. It is easier and more convenient to use existing font
 * definitions either by parsing an existing Flash file or by converting a font
 * definition from a specification such as TrueType in order to obtain the
 * glyphs and layout information.
 * </p>
 * 
 * <p>
 * 1. Creating a simple string.<br/> This examples assumes a font definition
 * that contains glyphs for the following set of characters ' ', 'a', 'b', 'c'
 * .... 'x', 'y', 'z'. The font used fixed size for the spacing between glyphs
 * .e.g. Courier.
 * </p>
 * 
 * <pre>
 * ArrayList characters = new ArrayList();
 * 
 * characters.add(new FSCharacter(1, 120)); // 'a'
 * characters.add(new FSCharacter(0, 120)); // ' '
 * characters.add(new FSCharacter(19, 120)); // 's'
 * characters.add(new FSCharacter(10, 120)); // 't'
 * characters.add(new FSCharacter(18, 120)); // 'r'
 * characters.add(new FSCharacter(9, 120)); // 'i'
 * characters.add(new FSCharacter(14, 120)); // 'n'
 * characters.add(new FSCharacter(6, 120)); // 'g'
 * 
 * // Create the string setting the height to be 12-point (240 twips) and displaying 
 * // the characters in black. The text is not indented or offset.
 * 
 * int xOffset = 0;
 * int yOffset = 240;
 * int fontSize = 240;
 * 
 * FSText string = new FSText(font.getIdentifier(), FSColorTable.black(), xOffset,
 * 				yOffset, fontSize, characters);
 * </pre>
 * 
 * <p>
 * 2. Creating a block of text.<br/> Again this examples assumes a fixed size
 * font definition that contains glyphs for the set of characters.
 * </p>
 * 
 * <pre>
 * ArrayList line1 = new ArrayList();
 * 
 * line1.add(new FSCharacter(19, 120)); // 's'
 * line1.add(new FSCharacter(10, 120)); // 't'
 * line1.add(new FSCharacter(18, 120)); // 'r'
 * line1.add(new FSCharacter(9, 120)); // 'i'
 * line1.add(new FSCharacter(14, 120)); // 'n'
 * line1.add(new FSCharacter(6, 120)); // 'g'
 * line1.add(new FSCharacter(0, 120)); // ' '
 * line1.add(new FSCharacter(28, 120)); // '1'
 * 
 * // Create the string setting the height to be 12-point (240 twips) and 
 * // displaying the characters in black. The text is not indented or offset.
 * 
 * int fontSize = 240;
 * int yOffset = 240;
 * 
 * FSText string1 = new FSText(font.getIdentifier(), FSColorTable.black(), 0,
 * 				yOffset, fontSize, line1);
 * 
 * ArrayList line1 = new ArrayList();
 * 
 * line2.add(new FSCharacter(19, 120)); // 's'
 * line2.add(new FSCharacter(10, 120)); // 't'
 * line2.add(new FSCharacter(18, 120)); // 'r'
 * line2.add(new FSCharacter(9, 120)); // 'i'
 * line2.add(new FSCharacter(14, 120)); // 'n'
 * line2.add(new FSCharacter(6, 120)); // 'g'
 * line2.add(new FSCharacter(0, 120)); // ' '
 * line2.add(new FSCharacter(29, 120)); // '2'
 * 
 * // Display the second string 'one line' height (240 twips) below the first. 
 * // The font size and colour remain unchanged.
 * 
 * yOffset += fontSize;
 * 
 * FSText string2 = new FSText(0, yOffset, line2);
 * </pre>
 * 
 * <h1 class="datasheet">History</h1>
 * 
 * <p>
 * The FSText class represents both the TextRecordType0 and TextRecordType1
 * structures from the Macromedia Flash (SWF) File Format Specification. It was
 * introduced in Flash 1.
 * </p>
 */
public class FSText extends FSTransformObject
{
	/** Defines that the font character set is based on the Unicode standard. */
	public static final int Unicode = 0;

	/**
	 * Defines that the font character set is based on the SJIS standard for
	 * representing Kanji characters.
	 */
	public static final int SJIS = 2;

	/**
	 * Defines that the font character set is based on the ANSI UTF-8 standard
	 * which supports standard ASCII encoded strings.
	 */
	public static final int ANSI = 1;

	// Flash 6
	// / Code used to identify the spoken language for Latin fonts.
	public static final int Latin = 1;

	// / Code used to identify the spoken language for Japanese fonts.
	public static final int Japanese = 2;

	// / Code used to identify the spoken language for Japanese fonts.
	public static final int Korean = 3;

	// / Code used to identify the spoken language for simplified Chinese fonts.
	public static final int SimplifiedChinese = 4;

	// / Code used to identify the spoken language for traditional Chinese
	// fonts.
	public static final int TraditionalChinese = 5;

	// End Flash 6

	private int identifier = 0;

	private FSColor color = null;

	private int offsetX = Transform.VALUE_NOT_SET;

	private int offsetY = Transform.VALUE_NOT_SET;

	private int height = 0;

	private ArrayList characters = new ArrayList();

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

	/**
	 * Constructs an FSText object specifying the array of characters to be
	 * displayed and their relative placement within a block of text. The
	 * selected font, colour of the text and height will remain unchanged from
	 * previous settings.
	 * 
	 * @param xOffset
	 *            the location of the text relative to the left edge of the
	 *            bounding rectangle enclosing the block of text.
	 * @param yOffset
	 *            the location of the text relative to the bottom edge of the
	 *            bounding rectangle enclosing the block of text.
	 * @param anArray
	 *            an array of FSCharacter objects.
	 */
	public FSText(int xOffset, int yOffset, ArrayList anArray)
	{
		setOffsetX(xOffset);
		setOffsetY(yOffset);
		setCharacters(anArray);
	}

	/**
	 * Constructs an FSText object, specifying the colour and position of the
	 * following FSText.
	 * 
	 * @param anIdentifier
	 *            the identifier of the font that the text will be rendered in.
	 * @param aColor
	 *            the colour of the text.
	 * @param xOffset
	 *            the location of the text relative to the left edge of the
	 *            bounding rectangle enclosing the text.
	 * @param yOffset
	 *            the location of the text relative to the bottom edge of the
	 *            bounding rectangle enclosing the text.
	 * @param aHeight
	 *            the height of the text in the chosen font.
	 * @param anArray
	 *            an array of FSCharacter objects.
	 */
	public FSText(int anIdentifier, FSColor aColor, int xOffset, int yOffset,
					int aHeight, ArrayList anArray)
	{
		setIdentifier(anIdentifier);
		setColor(aColor);
		setOffsetX(xOffset);
		setOffsetY(yOffset);
		setHeight(aHeight);
		setCharacters(anArray);
	}

	/**
	 * Constructs an FSText object by copying values from an existing object.
	 * 
	 * @param obj
	 *            an FSText object.
	 */
	public FSText(FSText obj)
	{
		identifier = obj.identifier;
		color = new FSColor(obj.color);
		offsetX = obj.offsetX;
		offsetY = obj.offsetY;
		height = obj.height;

		characters = new ArrayList(obj.characters.size());

		for (Iterator i = obj.characters.iterator(); i.hasNext();)
			characters.add(((FSCharacter) i.next()).clone());
	}

	/**
	 * Gets the identifier of the font in which the text will be displayed.
	 * 
	 * @return the identifier of the font.
	 */
	public int getIdentifier()
	{
		return identifier;
	}

	/**
	 * Gets the colour of the font in which the text will be displayed.
	 * 
	 * @return the colour of the text.
	 */
	public FSColor getColor()
	{
		return color;
	}

	/**
	 * Gets the location of the start of the text relative to the left edge of
	 * the bounding rectangle in twips.
	 * 
	 * @return the indentation applied to the text.
	 */
	public int getOffsetX()
	{
		return offsetX;
	}

	/**
	 * Gets the location of the start of the text relative to the bottom edge of
	 * the bounding rectangle in twips.
	 * 
	 * @return the location of the text relative to the bottom of the bounding
	 *         rectangle.
	 */
	public int getOffsetY()
	{
		return offsetY;
	}

	/**
	 * Gets the height of the text.
	 * 
	 * @return the height of the font used to displayed the text.
	 */
	public int getHeight()
	{
		return height;
	}

	/**
	 * Sets the identifier of the font in which the text will be displayed.
	 * 
	 * @param anIdentifier
	 *            the identifier of the font that the text will be rendered in.
	 */
	public void setIdentifier(int anIdentifier)
	{
		identifier = anIdentifier;
	}

	/**
	 * Sets the colour of the font in which the text will be displayed.
	 * 
	 * @param aColor
	 *            the colour of the text.
	 */
	public void setColor(FSColor aColor)
	{
		color = aColor;
	}

	/**
	 * Sets the location of the start of the text relative to the left edge of
	 * the bounding rectangle in twips.
	 * 
	 * @param xOffset
	 *            the location of the text relative to the left edge of the
	 *            bounding rectangle enclosing the text.
	 */
	public void setOffsetX(int xOffset)
	{
		offsetX = xOffset;
	}

	/**
	 * Sets the location of the start of the text relative to the bottom edge of
	 * the bounding rectangle in twips.
	 * 
	 * @param yOffset
	 *            the location of the text relative to the bottom edge of the
	 *            bounding rectangle enclosing the text.
	 */
	public void setOffsetY(int yOffset)
	{
		offsetY = yOffset;
	}

	/**
	 * Sets the height of the text.
	 * 
	 * @param aHeight
	 *            the height of the text in the chosen font.
	 */
	public void setHeight(int aHeight)
	{
		height = aHeight;
	}

	/**
	 * Adds an FSCharacter object to the array of characters.
	 * 
	 * @param aCharacter
	 *            an FSCharacter object.
	 */
	public void add(FSCharacter aCharacter)
	{
		characters.add(aCharacter);
	}

	/**
	 * Gets the array of characters to be displayed.
	 * 
	 * @return the array of FSCharacter objects.
	 */
	public ArrayList getCharacters()
	{
		return characters;
	}

	/**
	 * Sets the array of characters to be displayed.
	 * 
	 * @param anArray
	 *            an array of FSCharacter objects.
	 */
	public void setCharacters(ArrayList anArray)
	{
		characters = anArray;
	}

	int glyphBits()
	{
		int numberOfBits = 0;

		for (Iterator charIter = characters.iterator(); charIter.hasNext();)
			numberOfBits = Math.max(numberOfBits, FSCoder.size(
							((FSCharacter) charIter.next()).getGlyphIndex(),
							false));

		return numberOfBits;
	}

	int advanceBits()
	{
		int numberOfBits = 0;

		for (Iterator charIter = characters.iterator(); charIter.hasNext();)
			numberOfBits = Math
							.max(
											numberOfBits,
											FSCoder
															.size(
																			((FSCharacter) charIter
																							.next())
																							.getAdvance(),
																			true));

		return numberOfBits;
	}

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

		anObject.color = (color != null) ? (FSColor) color.clone() : null;

		anObject.characters = new ArrayList();

		for (Iterator i = characters.iterator(); i.hasNext();)
			anObject.characters.add(((FSCharacter) i.next()).clone());

		return anObject;
	}

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

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

			result = identifier == typedObject.identifier;

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

			result = result && offsetX == typedObject.offsetX;
			result = result && offsetY == typedObject.offsetY;
			result = result && height == typedObject.height;

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

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

		if (depth > 0)
		{
			buffer.append(": { ");
			Transform.append(buffer, "identifier", identifier);
			Transform.append(buffer, "color", color, depth);
			Transform.append(buffer, "offsetX", offsetX);
			Transform.append(buffer, "offsetY", offsetY);
			Transform.append(buffer, "height", height);
			Transform.append(buffer, "characters", characters, depth);
			buffer.append("}");
		}
	}

	public int length(FSCoder coder)
	{
		boolean _containsFont = containsFont();
		boolean _containsColor = containsColor();
		boolean _containsOffsetX = containsOffsetX();
		boolean _containsOffsetY = containsOffsetY();

		int length = 1;

		if (containsStyle())
		{
			length += (_containsFont) ? 2 : 0;
			length += (_containsColor) ? color.length(coder) : 0;
			length += (_containsOffsetY) ? 2 : 0;
			length += (_containsOffsetX) ? 2 : 0;
			length += (_containsFont) ? 2 : 0;
		}

		length += 1;

		if (characters.size() > 0)
		{
			int numberOfBits = 0;
			int numberOfGlyphBits = coder.context[FSCoder.NumberOfGlyphBits];
			int numberOfAdvanceBits = coder.context[FSCoder.NumberOfAdvanceBits];

			numberOfBits = (numberOfGlyphBits + numberOfAdvanceBits)
							* characters.size();
			numberOfBits += (numberOfBits % 8 > 0) ? 8 - (numberOfBits % 8) : 0;

			length += numberOfBits >> 3;
		}
		return length;
	}

	public void encode(FSCoder coder)
	{
		boolean _containsFont = containsFont();
		boolean _containsColor = containsColor();
		boolean _containsOffsetY = containsOffsetY();
		boolean _containsOffsetX = containsOffsetX();

		coder.writeBits(1, 1);
		coder.writeBits(0, 3);

		coder.writeBits(_containsFont ? 1 : 0, 1);
		coder.writeBits(_containsColor ? 1 : 0, 1);
		coder.writeBits(_containsOffsetY ? 1 : 0, 1);
		coder.writeBits(_containsOffsetX ? 1 : 0, 1);

		if (_containsFont)
			coder.writeWord(identifier, 2);

		if (_containsColor)
			color.encode(coder);

		if (_containsOffsetX)
			coder.writeWord(offsetX, 2);

		if (_containsOffsetY)
			coder.writeWord(offsetY, 2);

		if (_containsFont)
			coder.writeWord(height, 2);

		coder.writeWord(characters.size(), 1);

		for (Iterator charIter = characters.iterator(); charIter.hasNext();)
			((FSTransformObject) charIter.next()).encode(coder);

		coder.alignToByte();
	}

	public void decode(FSCoder coder)
	{
		/* type */coder.readBits(1, false);
		/* reserved */coder.readBits(3, false);

		boolean _containsFont = coder.readBits(1, false) != 0 ? true : false;
		boolean _containsColor = coder.readBits(1, false) != 0 ? true : false;
		boolean _containsOffsetY = coder.readBits(1, false) != 0 ? true : false;
		boolean _containsOffsetX = coder.readBits(1, false) != 0 ? true : false;

		if (_containsFont)
			identifier = coder.readWord(2, false);

		if (_containsColor)
			color = new FSColor(coder);

		if (_containsOffsetX)
			offsetX = coder.readWord(2, true);

		if (_containsOffsetY)
			offsetY = coder.readWord(2, true);

		if (_containsFont)
			height = coder.readWord(2, false);

		int charCount = coder.readWord(1, false);

		characters = new ArrayList(charCount);

		for (int i = 0; i < charCount; i++)
			characters.add(new FSCharacter(coder));

		coder.alignToByte();
	}

	private boolean containsFont()
	{
		return identifier != 0 && height != 0;
	}

	private boolean containsColor()
	{
		return color != null;
	}

	private boolean containsOffsetX()
	{
		return offsetX != Transform.VALUE_NOT_SET;
	}

	private boolean containsOffsetY()
	{
		return offsetY != Transform.VALUE_NOT_SET;
	}

	boolean containsStyle()
	{
		return containsFont() || containsColor() || containsOffsetX()
						|| containsOffsetY();
	}

}
