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

/**
 * FSDefineText2 defines one or more lines of transparent text.
 * 
 * <p>
 * It extends the functionality provided by the FSDefineText class by supporting
 * transparent colours. The characters, style and layout information is defined
 * using FSText objects. The FSDefineText2 class acts as a container for the
 * text, defining the bounding rectangle that encloses the text along with a
 * coordinate transform that can be used to change the size and orientation of
 * the text when it is displayed.
 * </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. Read-only.</td>
 * </tr>
 * 
 * <tr>
 * <td>identifier</td>
 * <td>An unique identifier for this object in the range 1..65535.</td>
 * </tr>
 * 
 * <tr>
 * <td>bounds</td>
 * <td>A bounding rectangle for the entire block of text defined in the object.</td>
 * </tr>
 * 
 * <tr>
 * <td>transform</td>
 * <td>A coordinate transform describing the placement, size and rotation of
 * the text block.</td>
 * </tr>
 * 
 * <tr>
 * <td>objects</td>
 * <td>An array of FSText objects describing the characters to be displayed and
 * their attributes.</td>
 * </tr>
 * </table>
 * 
 * <p>
 * The bounding rectangle and transform controls how the text is laid out. Each
 * FSText object in the textRecords array specifies an offset from the left and
 * bottom edges of the bounding rectangle, allowing successive lines of text to
 * be arranged as a block or paragraph. The coordinate transform can be used to
 * control the size and orientation of the text when it is displayed.
 * </p>
 * 
 * <h1 class="datasheet">Examples</h1>
 * 
 * <p>
 * Creating a simple string. 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>
 * 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>
 * int fontSize = 240;
 * int leading = 80;
 * 
 * ArrayList characters = new ArrayList();
 * ArrayList lines = 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.
 * 
 * lines.add(new FSText(font.getIdentifier, FSColor.black()), 0, 0, fontSize,
 * 				characters);
 * 
 * // Calculate the bounding rectangle that encloses the text.
 * // The width of the text by summing all the advances.
 * 
 * int width = 0;
 * int height = fontSize + leading;
 * 
 * for (Iterator i = characters.iterator(); i.hasNext();)
 * 	width += ((FSCharacter) i.next()).getAdvance();
 * 
 * FSBounds bounds = new FSBounds(0, 0, width, height);
 * 
 * // Define the text with no coordinate transformation (use the default unity transform which leaves
 * // the text unchanged.
 * 
 * FSDefineText text = new FSDefineText(movie.newIdentifier(), bounds,
 * 				new FSCoordTransform(), lines);
 * 
 * </pre>
 * 
 * <h1 class="datasheet">History</h1>
 * 
 * <p>
 * The FSDefineText2 class represents the DefineText2 tag from the Macromedia
 * Flash (SWF) File Format Specification. It was introduced in Flash 3.
 * </p>
 */
public class FSDefineText2 extends FSDefineObject
{
	private FSBounds bounds = null;
	private FSCoordTransform transform = null;
	private ArrayList objects = null;

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

	/**
	 * Constructs an FSDefineText2 object with the specified bounding rectangle,
	 * coordinate transform and text records.
	 * 
	 * @param anIdentifier
	 *            the unique identifier for this object.
	 * @param aBounds
	 *            the bounding rectangle enclosing the text.
	 * @param aTransform
	 *            an FSCoordTransform to change the size and orientation of the
	 *            text.
	 * @param textRecordArray
	 *            an array of text record (FSTextStyle and FSText) object that
	 *            define the text to be displayed.
	 */
	public FSDefineText2(int anIdentifier, FSBounds aBounds,
							FSCoordTransform aTransform,
							ArrayList textRecordArray)
	{
		super(DefineText2, anIdentifier);
		setBounds(aBounds);
		setTransform(aTransform);
		setObjects(textRecordArray);
	}

	/**
	 * Constructs an FSDefineText object by copying values from an existing
	 * object.
	 * 
	 * @param obj
	 *            an FSDefineText object.
	 */
	public FSDefineText2(FSDefineText2 obj)
	{
		super(obj);
		bounds = new FSBounds(obj.bounds);
		transform = new FSCoordTransform(obj.transform);

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

		for (Iterator i = obj.objects.iterator(); i.hasNext();)
			objects.add(((FSText) i.next()).clone());
	}

	/**
	 * Add a FSText object to the array of text records.
	 * 
	 * @param anObject
	 *            an FSText object.
	 */
	public void add(FSText anObject)
	{
		objects.add(anObject);
	}

	/**
	 * Gets the bounding rectangle that completely encloses the text to be
	 * displayed.
	 * 
	 * @return the bounding rectangle of the text.
	 */
	public FSBounds getBounds()
	{
		return bounds;
	}

	/**
	 * Gets the coordinate transform that controls how the text is displayed.
	 * 
	 * @return the coordinate transform.
	 */
	public FSCoordTransform getTransform()
	{
		return transform;
	}

	/**
	 * Gets the array of text records that define the text to be displayed as
	 * its attributes.
	 * 
	 * @return the array of text records (FSTextStyle and FSText objects).
	 */
	public ArrayList getObjects()
	{
		return objects;
	}

	/**
	 * Sets the bounding rectangle that encloses the text being displayed.
	 * 
	 * @param aBounds
	 *            the bounding rectangle enclosing the text.
	 */
	public void setBounds(FSBounds aBounds)
	{
		bounds = aBounds;
	}

	/**
	 * Sets the coordinate transform that changes the orientation and size of
	 * the text displayed.
	 * 
	 * @param aTransform
	 *            an FSCoordTransform to change the size and orientation of the
	 *            text.
	 */
	public void setTransform(FSCoordTransform aTransform)
	{
		transform = aTransform;
	}

	/**
	 * Sets the array of text records that define the text to be displayed.
	 * 
	 * @param anArray
	 *            an array of FSText objects that define the text to be
	 *            displayed.
	 */
	public void setObjects(ArrayList anArray)
	{
		objects = anArray;
	}

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

		anObject.bounds = (bounds != null) ? (FSBounds) bounds.clone() : null;
		anObject.transform = (transform != null) ? (FSCoordTransform) transform
						.clone() : null;

		anObject.objects = new ArrayList(objects.size());

		for (Iterator i = objects.iterator(); i.hasNext();)
			anObject.objects.add(((FSText) i.next()).clone());

		return anObject;
	}

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

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

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

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

			if (objects != null)
				result = result && objects.equals(typedObject.objects);
			else
				result = result && objects == typedObject.objects;
		}
		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, "bounds", bounds, depth);
			Transform.append(buffer, "transform", transform, depth);
			Transform.append(buffer, "objects", objects, depth);
			buffer.append("}");
		}
	}

	public int length(FSCoder coder)
	{
		super.length(coder);

		coder.context[FSCoder.TransparentColors] = 1;
		coder.context[FSCoder.NumberOfGlyphBits] = glyphBits();
		coder.context[FSCoder.NumberOfAdvanceBits] = advanceBits();

		length += bounds.length(coder);
		length += transform.length(coder);
		length += 2;

		for (Iterator recordIterator = objects.iterator(); recordIterator
						.hasNext();)
			length += ((FSTransformObject) recordIterator.next()).length(coder);

		length += 1;

		coder.context[FSCoder.TransparentColors] = 0;
		coder.context[FSCoder.NumberOfGlyphBits] = 0;
		coder.context[FSCoder.NumberOfAdvanceBits] = 0;

		return length;
	}

	public void encode(FSCoder coder)
	{
		super.encode(coder);

		coder.context[FSCoder.TransparentColors] = 1;
		coder.context[FSCoder.NumberOfGlyphBits] = glyphBits();
		coder.context[FSCoder.NumberOfAdvanceBits] = advanceBits();

		bounds.encode(coder);
		transform.encode(coder);

		coder.writeWord(coder.context[FSCoder.NumberOfGlyphBits], 1);
		coder.writeWord(coder.context[FSCoder.NumberOfAdvanceBits], 1);

		for (Iterator recordIterator = objects.iterator(); recordIterator
						.hasNext();)
			((FSTransformObject) recordIterator.next()).encode(coder);

		coder.writeWord(0, 1);

		coder.context[FSCoder.TransparentColors] = 0;
		coder.context[FSCoder.NumberOfGlyphBits] = 0;
		coder.context[FSCoder.NumberOfAdvanceBits] = 0;
		coder.endObject(name());
	}

	public void decode(FSCoder coder)
	{
		int glyphBits = 0;
		int advanceBits = 0;

		super.decode(coder);

		// Mark the start of the tag body

		/* location */coder.getPointer();

		bounds = new FSBounds(coder);

		// This code is used to get round a bug in Flash - sometimes 16,
		// 8-bit zeroes are written out before the transform. The root
		// cause in Flash is unknown but seems to be related to the
		// bounds not being set - all values are zero.

		boolean bytesAreZero = true;
		int start = coder.getPointer();

		for (int i = 0; i < 16; i++)
		{
			if (coder.scanWord(1, false) != 0)
				bytesAreZero = false;

			coder.adjustPointer(8);
		}

		coder.setPointer(start);

		if (bytesAreZero)
		{
			for (int i = 0; i < 16; i++)
			{
				coder.scanWord(1, false);
				coder.adjustPointer(8);
			}
		}

		// Back to reading the rest of the tag

		transform = new FSCoordTransform(coder);

		glyphBits = coder.readWord(1, false);
		advanceBits = coder.readWord(1, false);

		coder.context[FSCoder.TransparentColors] = 1;
		coder.context[FSCoder.NumberOfGlyphBits] = glyphBits;
		coder.context[FSCoder.NumberOfAdvanceBits] = advanceBits;

		/*
		 * Set the instance variable directly since Flash files can contain
		 * FSDefineText objects with no text records - this appears to be a bug
		 * in the Flash file.
		 */
		objects = new ArrayList();

		while (coder.scanBits(8, false) != 0)
			objects.add(new FSText(coder));

		coder.readBits(8, false);

		coder.context[FSCoder.TransparentColors] = 0;
		coder.context[FSCoder.NumberOfGlyphBits] = 0;
		coder.context[FSCoder.NumberOfAdvanceBits] = 0;
		coder.endObject(name());
	}

	private int glyphBits()
	{
		int numberOfBits = 0;

		for (Iterator recordIterator = objects.iterator(); recordIterator
						.hasNext();)
			numberOfBits = Math.max(numberOfBits, ((FSText) recordIterator
							.next()).glyphBits());

		return numberOfBits;
	}

	private int advanceBits()
	{
		int numberOfBits = 0;

		for (Iterator recordIterator = objects.iterator(); recordIterator
						.hasNext();)
			numberOfBits = Math.max(numberOfBits, ((FSText) recordIterator
							.next()).advanceBits());

		return numberOfBits;
	}
}