/* libsswf_tag_shape.c++ -- written by Alexis WILKE for Made to Order Software Corp. (c) 2002-2008 */

/*

Copyright (c) 2002-2008 Made to Order Software Corp.

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and
associated documentation files (the "Software"), to
deal in the Software without restriction, including
without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial
portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

*/

/** \file
 *
 * \brief The implementation of the sswf::TagShape class
 *
 * This file declares the body of the functions which are not
 * inline. It is part of the SSWF library.
 */

#include	"sswf/libsswf.h"

using namespace sswf;




/////////////////////////////////////////// TagShape



/** \class sswf::TagShape
 *
 * \brief Defines an SWF shape.
 *
 * This object defines a shape or character.
 *
 * A shape is a set of lines and curves defining a picture.
 * It is composed of fill and line styles and edges. The
 * edges define the (x, y) coordinates of the images to
 * render. The fill styles define the colors, images and
 * gradients used to fill inside the polygones defined
 * by the edges. And the line styles define how to render
 * the segments defined by the edges.
 *
 * It is possible to not have a fill or line.
 *
 * Note that a glyph in a font is a special type of shape.
 * (a shape without all the bells and wissels)
 *
 * All the possibilities define a shape of many different
 * SWF versions.
 *
 * \sa sswf::Style
 * \sa sswf::TagFont
 * \sa <a href="../SWFalexref.html#tag_defineshape">SWF Alexis' Reference&mdash;Define Shape</a>
 * \sa <a href="../SWFalexref.html#tag_defineshape2">SWF Alexis' Reference&mdash;Define Shape2</a>
 * \sa <a href="../SWFalexref.html#tag_defineshape3">SWF Alexis' Reference&mdash;Define Shape3</a>
 * \sa <a href="../SWFalexref.html#tag_defineshape4">SWF Alexis' Reference&mdash;Define Shape4</a>
 * \sa <a href="../SWFalexref.html#tag_definemorphshape">SWF Alexis' Reference&mdash;Define Morph Shape</a>
 * \sa <a href="../SWFalexref.html#tag_definemorphshape2">SWF Alexis' Reference&mdash;Define Morph Shape2</a>
 * \sa <a href="../SWFalexref.html#swf_shape">SWF Alexis' Reference&mdash;swf_shape</a>
 * \sa <a href="../SWFalexref.html#swf_shape_record">SWF Alexis' Reference&mdash;swf_shape_record</a>
 * \sa <a href="../SWFalexref.html#swf_shape_with_style">SWF Alexis' Reference&mdash;swf_shape_with_style</a>
 * \sa <a href="../SWFalexref.html#swf_tag">SWF Alexis' Reference&mdash;swf_tag</a>
 */


/** \enum sswf::TagShape::morph_t
 *
 * \brief The mode used to add a style or edge.
 *
 * This enumeration helps you create only the first, only the
 * last or both shapes at once.
 */

/** \var sswf::TagShape::MORPH_MODE_SHAPE0
 *
 * \brief Added the information to the first shape.
 *
 * This is used to add information to the first shape of a morph.
 *
 * Use this if you are not creating a morph shape.
 *
 * \sa sswf::TagShape::MORPH_MODE_SHAPE1
 * \sa sswf::TagShape::MORPH_MODE_BOTH_SHAPES
 */

/** \var sswf::TagShape::MORPH_MODE_SHAPE1
 *
 * \brief Added the information to the last shape.
 *
 * This is used to add information to the last shape of a morph.
 *
 * Use this only if you intend to create a morph shape.
 *
 * Remember that a morph shape must have the exact same number of
 * entries in both shapes.
 *
 * \sa sswf::TagShape::MORPH_MODE_SHAPE0
 * \sa sswf::TagShape::MORPH_MODE_BOTH_SHAPES
 */

/** \var sswf::TagShape::MORPH_MODE_BOTH_SHAPES
 *
 * \brief Added the information to both shapes.
 *
 * This is used to add the same information the first and the
 * last shapes at the same time.
 *
 * It is equivalent to calling the corresponding function
 * once with TagShape::MORPH_MODE_SHAPE0 and once with
 * TagShape::MORPH_MODE_SHAPE1.
 *
 * \sa sswf::TagShape::MORPH_MODE_SHAPE0
 * \sa sswf::TagShape::MORPH_MODE_SHAPE1
 */



/** \enum sswf::TagShape::fill_t
 *
 * \brief Which fill is to be defined.
 *
 * This enumeration is used to determine the fill to
 * define. Whenever rendering a filled shape, two
 * fills are available: Fill0 and Fill1 (or even and
 * odd fills.)
 */

/** \var sswf::TagShape::SHAPE_FILL_EVEN
 *
 * \brief Use Fill0
 *
 * This enumeration defines Fill0 (or the even fill.)
 */

/** \var sswf::TagShape::SHAPE_FILL_ODD
 *
 * \brief Use Fill1
 *
 * This enumeration defines Fill1 (or the odd fill.)
 */




/** \brief Initializes the shape object.
 *
 * This function initializes the shape.
 *
 * By default the shape is empty.
 *
 * \param[in] parent The TagHeader in which this TagShape is inserted
 */
TagShape::TagShape(TagBase *parent)
	: TagBaseID("shape", parent)
{
	f_version = 1;
	f_morph = false;
	f_is_glyph = false;
	f_show_bounds = false;
	f_show_origin = false;
	f_has_non_scaling_strokes = true;	// smart default?
	f_has_scaling_strokes = true;		// smart default?
	// f_align_zone -- auto-init
	// f_bounds[0/1] -- auto-init
	// f_strokes_bounds[0/1] -- auto-init
	// f_shapes -- auto-init
	f_edges = 0;
	f_morph_edges = 0;
	f_setup = 0;
	// f_fill_styles -- auto-init
	// f_line_styles -- auto-init
	// f_record -- auto-init
	// f_morph_record -- auto-init
}


/** \brief Returns the type of a shape object.
 *
 * The shape object is a definition, it references other objects
 * and it has an identifier.
 *
 * \return SWF_TYPE_DEFINE, SWF_TYPE_REFERENCE and SWF_TYPE_HAS_ID
 */
TagBase::swf_type_t TagShape::TypeFlags(void) const
{
	return SWF_TYPE_DEFINE | SWF_TYPE_REFERENCE | SWF_TYPE_HAS_ID;
}


/** \brief Record the edges.
 *
 * This function is used to record the current edges in order to
 * start saving new style specifications.
 *
 * This function can safely be called multiple times.
 */
void TagShape::RecordEdges(void)
{
	if(f_edges != 0) {
		f_record.Set(-1, f_edges);
		f_edges = 0;
	}
}



/** \brief Record the setup.
 *
 * This function records the current setup. This allows one to
 * continue by adding new edges.
 *
 * This function can safely be called multiple times.
 */
void TagShape::RecordSetup(void)
{
	if(f_setup != 0) {
		f_record.Set(-1, f_setup);
		f_setup = 0;
	}
}



/** \brief Allocate a new edges structure.
 *
 * This function ensures that the current setup is recorded
 * and creates a new edges structure.
 */
void TagShape::NewEdges(void)
{
	RecordSetup();

	if(f_edges == 0) {
		f_edges = new shape_edges_t(SHAPE_EDGES);
		MemAttach(f_edges, sizeof(shape_edges_t), "TagShape::NewEdges() -- shape edges array");
		//f_edges->f_edges -- initialized by the new operator
	}
}


/** \brief Allocate a new setup structure.
 *
 * This function ensures that the current edges are recorded
 * and creates a new setup structure.
 */
void TagShape::NewSetup(void)
{
	RecordEdges();

	if(f_setup == 0) {
		f_setup = new shape_setup_t(SHAPE_SETUP);
		MemAttach(f_setup, sizeof(shape_setup_t), "TagShape::NewSetup() -- shape setup info");
	}
}



/** \brief Add an edge.
 *
 * Shapes are drawn using edges. An edge defines a curve or a line segment.
 *
 * To draw a morph shape, you can drawn in both shapes at the same time
 * or choose to draw only in one or the other.
 *
 * \param[in] morph_mode	One of the morph mode available
 * \param[in] edge		The edge to add
 */
ErrorManager::error_code_t TagShape::AddEdge(morph_t morph_mode, const Edges::edge_t& edge)
{
	switch(morph_mode) {
	case MORPH_MODE_SHAPE0:
	case MORPH_MODE_SHAPE1:
	case MORPH_MODE_BOTH_SHAPES:
		break;

	default:
		return OnError(ErrorManager::ERROR_CODE_INVALID_MORPH_INDEX, "the morph_mode parameter must be one of the MORPH_MODE_... enumeration item");

	}

	if(morph_mode == MORPH_MODE_SHAPE1 || morph_mode == MORPH_MODE_BOTH_SHAPES) {
		SetMorph();

		if(f_morph_edges == 0) {
			f_morph_edges = new shape_edges_t(SHAPE_EDGES);
			MemAttach(f_morph_edges, sizeof(shape_edges_t), "TagShape::AddEdge() -- shape morph edges array");
			//f_morph_edges->f_edges -- initialized by the new operator
		}

		f_morph_edges->f_edges.Set(edge);
	}

	if(morph_mode == MORPH_MODE_SHAPE0 || morph_mode == MORPH_MODE_BOTH_SHAPES) {
		// we need at least one style
		if(f_fill_styles.Count() == 0 && f_line_styles.Count() == 0) {
			return OnError(ErrorManager::ERROR_CODE_INVALID_SHAPE, "cannot insert an edge without any style");
		}

		NewEdges();
		f_edges->f_edges.Set(edge);
	}

	return ErrorManager::ERROR_CODE_NONE;
}


/** \fn sswf::TagShape::AddEdge(morph_t morph_mode, long x, long y, long ctrl_x, long ctrl_y)
 *
 * \brief Save a curve segment.
 *
 * Add the specified curve. This is a helper function which saves the parameters
 * in an edge_t structure then calls
 * sswf::TagShape::AddEdge(morph_t morph_mode, const Edges::edge_t& edge)
 *
 * \sa sswf::TagShape::AddEdge(morph_t morph_mode, const Edges::edge_t& edge)
 */

/** \fn sswf::TagShape::AddEdge(morph_t morph_mode, long x, long y)
 *
 * \brief Save a line segment.
 *
 * Add the specified line segment. This is a helper function which saves the parameters
 * in an edge_t structure then calls
 * sswf::TagShape::AddEdge(morph_t morph_mode, const Edges::edge_t& edge)
 *
 * \sa sswf::TagShape::AddEdge(morph_t morph_mode, const Edges::edge_t& edge)
 */




/** \brief Add a style to the shape.
 *
 * Since version 3 of SWF, it is possible to add new styles after adding
 * edges. Otherwise, all the styles should be defined before the edges.
 *
 * However, the SSWF library works in a slightly different way. It expects
 * the user to call AddStyle() with a full and a line style. Then to call
 * AddEdges(). For the next set of edges (next polygon), one is expected
 * to call AddStyle() again and AddEdges().
 *
 * The library will take care of compressing the result as much as possible
 * eventually making use of the new feature of defining extraneous styles
 * within the shape.
 *
 * \param[in] style	The style to add
 * \param[in] fill	The odd or even fill (i.e. SHAPE_FILL_EVEN or SHAPE_FILL_ODD)
 *
 * \return An Error code or ErrorManager::ERROR_CODE_NONE
 */
ErrorManager::error_code_t TagShape::AddStyle(const Style& style, fill_t fill)
{
	// when a new style is given, we first search in the existing styles to see if
	// it is the first time it is inserted; if not, then we use the previous one
	// (we avoid duplicating the same style(s) many times!)
	int		idx;
	const Style	*s;
	Style		*keep;

	if(fill != SHAPE_FILL_EVEN && fill != SHAPE_FILL_ODD) {
		return OnError(ErrorManager::ERROR_CODE_INVALID_FILL_PARAMETER, "invalid fill parameter in TagShape::AddStyle()");
	}

	NewSetup();

	// This should certainly be done in the PreSave() function instead!
	if(style.HasAlpha() && f_version < 3) {
		f_version = 3;
	}
	if(style.HasHardEdges() && f_version < 7) {
		f_version = 7;
	}
	if(style.Gradients() > 8 && f_version < 8) {
		f_version = 8;
	}
	switch(style.Type()) {
	case Style::STYLE_TYPE_GRADIENT_FOCAL:
	case Style::STYLE_TYPE_ENHANCED_LINE:
		if(f_version < 8) {
			f_version = 8;
		}
		break;

	default:	// ignore others
		break;

	}
	if(style.HasMorph()) {
		SetMorph();
	}

	idx = 0;
	switch(style.Type()) {
	case Style::STYLE_TYPE_NO_LINE:
		f_setup->f_line_ref = 0;
		break;

	case Style::STYLE_TYPE_LINE:
	case Style::STYLE_TYPE_ENHANCED_LINE:
		idx = f_line_styles.Count();
		while(idx > 0) {
			idx--;
			s = dynamic_cast<const Style *>(f_line_styles.Get(idx));
			if(style == *s) {
				// use that already saved style
				goto add_line;
			}
		}
		// it doesn't exist, we need to insert it now
		idx = f_line_styles.Count();
		// TODO: if the Count() is 32768 then we need to reset the counters...
		//	 don't forget to copy the current fill styles
		//	 See the NewStyles() function
		// NOTE: if the maximum version is V1.0, then the limit should be
		//	 set to 255 instead
		if(idx >= 32766) {
			return OnError(ErrorManager::ERROR_CODE_TOO_MANY_STYLES, "too many line styles");
		}
		keep = new Style(style);		// copy the style
		MemAttach(keep, sizeof(Style), "TagShape::AddStyle() -- create a style (line)");
		f_line_styles.Set(-1, keep);
add_line:
		f_setup->f_line_ref = idx + 1;
		break;

	case Style::STYLE_TYPE_NO_FILL:
		f_setup->f_fill_ref[fill] = 0;
		break;

	default:	// most other cases...
		idx = f_fill_styles.Count();
		while(idx > 0) {
			idx--;
			s = dynamic_cast<const Style *>(f_fill_styles.Get(idx));
			if(style == *s) {
				// use that already saved style
				goto add_fill;
			}
		}
		// it doesn't exist, we need to insert it now
		idx = f_fill_styles.Count();
		// TODO: if the Count() is 32768 then we need to reset the counters...
		//	 don't forget to copy the other current fill & line styles
		//	 See the NewStyles() function
		// NOTE: if the maximum version is V1.0, then the limit should be
		//	 set to 255 instead
		if(idx >= 32766) {
			return OnError(ErrorManager::ERROR_CODE_TOO_MANY_STYLES, "too many fill styles");
		}
		keep = new Style(style);
		MemAttach(keep, sizeof(Style), "TagShape::AddStyle() -- create a style (fill)");
		//*keep = style;			// copy the style
		f_fill_styles.Set(-1, keep);
add_fill:
		f_setup->f_fill_ref[fill] = idx + 1;
		break;

	}

	// overflow for V1.0 of Flash
	if(idx > 255 && f_version < 2) {
		f_version = 2;
	}

	return ErrorManager::ERROR_CODE_NONE;
}


/** \brief Add a move to a specific location.
 *
 * This function is used to move the drawing pen to a specified location.
 * No drawing happens when moving the pen in this way.
 *
 * The function can be called multiple times in a raw, though only the
 * last position is kept.
 *
 * \param[in] morph_mode	One of the available morph modes
 * \param[in] x			The new horizontal location
 * \param[in] y			The new vertical location
 *
 * \return An error code or ErrorManager::ERROR_CODE_NONE
 */
ErrorManager::error_code_t TagShape::AddMove(morph_t morph_mode, long x, long y)
{
	switch(morph_mode) {
	case MORPH_MODE_SHAPE0:
	case MORPH_MODE_SHAPE1:
	case MORPH_MODE_BOTH_SHAPES:
		break;

	default:
		return OnError(ErrorManager::ERROR_CODE_INVALID_MORPH_INDEX, "the morph_mode parameter must be one of the MORPH_MODE_... enumeration item");

	}

	if(morph_mode == MORPH_MODE_SHAPE1 || morph_mode == MORPH_MODE_BOTH_SHAPES) {
		SetMorph();

		if(f_morph_edges != 0) {
			f_morph_record.Set(-1, f_morph_edges);
			f_morph_edges = 0;
		}

		shape_setup_t *setup = new shape_setup_t(SHAPE_SETUP);
		MemAttach(setup, sizeof(shape_setup_t), "TagShape::AddMove() -- shape morph setup info (i.e. move only)");
		setup->f_x = x;
		setup->f_y = y;
		f_morph_record.Set(-1, setup);
	}

	if(morph_mode == MORPH_MODE_SHAPE0 || morph_mode == MORPH_MODE_BOTH_SHAPES) {
		NewSetup();

		f_setup->f_x = x;
		f_setup->f_y = y;
	}

	return ErrorManager::ERROR_CODE_NONE;
}


/** \brief Mark the shape as a morphing shape
 *
 * This function marks the shape as a morphing shape.
 *
 * This means all the styles and edges must be defined
 * twice. Once for the "normal" shape and once for the
 * "morphed" shape.
 */
void TagShape::SetMorph(void)
{
	f_morph = true;
	if(f_version < 3) {
		f_version = 3;
	}
}


/** \brief Set the alignment zone rectangle of a glyph
 *
 * This function is used to define a alignment zone to properly place glyphs
 * on a pixel boundary.
 *
 * Calling this function implies calling the Glyph() function to mark
 * the shape as being a glyph.
 *
 * You must set an align zone on all the glyphs of a font for them to be
 * taken in account.
 *
 * \param[in] zone		The align zone rectangle
 */
void TagShape::SetAlignZone(const SRectangle& zone)
{
	f_align_zone = zone;
	Glyph();
}




/** \brief Check whether a zone align rectangle is defined.
 *
 * This function returns true if an align zone is defined.
 * This means that the rectangle used to define the alignment zone
 * is not empty.
 *
 * \return True if the alignment zone is defined in this glyph.
 */
bool TagShape::HasAlignZone(void) const
{
	return !f_align_zone.IsEmpty();
}



/** \brief Save alignment zone in a Data buffer
 *
 * This function saves the alignment zone in a Data buffer.
 *
 * This represents one entry in an swf_zone_array structure.
 *
 * \bug
 * At this time I assume that the rectangle has to be defined
 * in TWIPS. This means each coordinate is divided by 20 before
 * being saved. That seems to correspond to what the documentation
 * says, but the exact type of coordinate is not really defined.
 *
 * \param[in] data	The Data buffer where the zone is saved
 */
void TagShape::SaveAlignZone(Data& data) const
{
	data.PutShortFloat(f_align_zone.XMin() / 20.0f);
	data.PutShortFloat((f_align_zone.XMax() - f_align_zone.XMin()) / 20.0f);
	data.PutShortFloat(f_align_zone.YMin() / 20.0f);
	data.PutShortFloat((f_align_zone.YMax() - f_align_zone.YMin()) / 20.0f);
}





/** \brief Set the rectangle within which the shape is enclosed
 *
 * This function is used to mark the edges of the shape. This is the limit
 * where the points on a shape go. Watchout, whenever you draw a curve, the
 * points drawn can go outside of the limits defined by the control points.
 *
 * \bug
 * The show bounds feature does not work for a shape used as the hit test
 * of a button or in a glyph.
 *
 * \param[in] index		The morph image index (0 or 1)
 * \param[in] rect		The rectangle defining the bounds
 * \param[in] show_bounds	Whether the Save() function should generate lines
 *				to show the bounds in the resulting animation
 *
 * \return An error code or ErrorManager::ERROR_CODE_NONE
 */
ErrorManager::error_code_t TagShape::SetBounds(int index, const SRectangle& rect, bool show_bounds)
{
	if(index != 0 && index != 1) {
		return OnError(ErrorManager::ERROR_CODE_INVALID_MORPH_INDEX, "invalid index for TagShape::SetBounds()");
	}

	f_show_bounds = show_bounds;
	f_bounds[index] = rect;
	if(index == 1) {
		SetMorph();
	}

	return ErrorManager::ERROR_CODE_NONE;
}


/** \fn sswf::TagShape::Bounds(int index) const
 *
 * \brief Retrieve a copy of the bounds.
 *
 * This function retrieves a copy of the bounds rectangle
 * currently defined in the shape.
 *
 * The function returns a direct reference to the bounds
 * defined inside the shape. It will change whenever the
 * TagShape::SetBounds() function is called.
 *
 * \param[in] index		The morph image index (0 or 1)
 *
 * \return true if the bounds rectangle was defined
 *
 * \sa sswf::TagShape::SetBounds(int index, const SRectangle& rect, bool show_bounds)
 * \sa sswf::TagShape::HasBounds(void) const
 * \sa sswf::TagShape::HasShowBounds(void) const
 */

/** \fn sswf::TagShape::HasBounds(void) const
 *
 * \brief Check whether bounds were properly defined.
 *
 * This function defines whether the bounds are defined or
 * not.
 *
 * \return true if the bounds rectangle was defined
 *
 * \sa sswf::TagShape::SetBounds(int index, const SRectangle& rect, bool show_bounds)
 */

/** \fn sswf::TagShape::HasShowBounds(void) const
 *
 * \brief Check whether bounds should be shown.
 *
 * This function defines whether the Save() function will
 * add lines around the shape to show the location of the
 * bounds.
 *
 * \return true if the bounds rectangle was defined
 *
 * \sa sswf::TagShape::SetBounds(int index, const SRectangle& rect, bool show_bounds)
 */

/** \fn sswf::TagShape::ShowBounds(bool show = true)
 *
 * \brief Set whether the shape should show the bounds.
 *
 * This function defines the flag used to tell the shape
 * whether to show the bounds with lines.
 *
 * \bug
 * The show bounds feature does not work for a shape used as the hit test
 * of a button or in a glyph.
 *
 * \param[in] show Set to true if the bounds rectangle should be draw
 *
 * \sa sswf::TagShape::SetBounds(int index, const SRectangle& rect, bool show_bounds)
 */

/** \fn sswf::TagShape::ShowOrigin(bool show = true)
 *
 * \brief Set whether the shape should draw origin lines.
 *
 * This function defines the flag used to tell the shape
 * whether to draw lines to show the origin of the shape.
 *
 * This is particularly useful if the shape needs to be rotated.
 *
 * \bug
 * The show origin feature does not work for a shape used as the hit test
 * of a button or in a glyph.
 *
 * \param[in] show Set to true if the origin lines should be draw
 *
 * \sa sswf::TagShape::SetBounds(int index, const SRectangle& rect, bool show_bounds)
 */




/** \brief Set the rectangle where the shape including "leaking" strokes is to be rendered
 *
 * This function is used to mark the edges of the shape including the strokes.
 * This is the limit where the points of a shape go plus the width of the strokes
 * used by the lines on the edges. Watchout, whenever you draw a curve, the
 * points drawn can go outside of the limits defined by the control points.
 *
 * \param[in] index		The morph image index (0 or 1)
 * \param[in] rect		The rectangle defining the bounds
 *
 * \return An error code or ErrorManager::ERROR_CODE_NONE
 */
ErrorManager::error_code_t TagShape::SetStrokesBounds(int index, const SRectangle& rect)
{
	if(index != 0 && index != 1) {
		return OnError(ErrorManager::ERROR_CODE_INVALID_MORPH_INDEX, "invalid index for TagShape::SetStrokesBounds()");
	}

	f_strokes_bounds[index] = rect;
	if(index == 1) {
		SetMorph();
	}

	return ErrorManager::ERROR_CODE_NONE;
}


/** \fn sswf::TagShape::SetNonScalingStrokes(bool has_non_scaling_strokes)
 *
 * \brief Set whether the shape has any non-scaling strokes.
 *
 * This function can be used to mark whether some of the LINE2 are
 * marked as non-scaling.
 *
 * \bug
 * This needs to be done dynamically whenever we save the shapes.
 *
 * \param[in] has_non_scaling_strokes Set whether that flag should be saved as true or false
 */

/** \fn sswf::TagShape::SetScalingStrokes(bool has_scaling_strokes)
 *
 * \brief Set whether the shape has any scaling strokes.
 *
 * This function can be used to mark whether some of the LINE2 are
 * marked as scaling.
 *
 * \bug
 * This needs to be done dynamically whenever we save the shapes.
 *
 * \param[in] has_scaling_strokes Set whether that flag should be saved as true or false
 */




/** \brief Force a new set of styles.
 *
 * This function can be called to request the TagShape object to create
 * a new set of styles.
 *
 * \note
 * This will enable one to have smaller fill & line style
 * references (less bits) -- however that is most probably
 * useless (my point of view.)
 */
void TagShape::NewStyles(void)
{
	shape_record_t	*sr;

	// valid only in V2.0+
	if(f_version < 2) {
		f_version = 2;
	}

	sr = new shape_record_t;
	MemAttach(sr, sizeof(shape_record_t), "TagShape::NewStyles() -- shape record used to have new styles");

	sr->f_fill_styles = new Vectors(f_fill_styles);
	MemAttach(sr->f_fill_styles, sizeof(Vectors), "TagShape::NewStyles() -- fill styles record copy");
	f_fill_styles.Empty();

	sr->f_line_styles = new Vectors(f_line_styles);
	MemAttach(sr->f_line_styles, sizeof(Vectors), "TagShape::NewStyles() -- line styles record copy");
	f_line_styles.Empty();

	sr->f_record = new Vectors(f_record);
	MemAttach(sr->f_record, sizeof(Vectors), "TagShape::NewStyles() -- shape record copy");
	f_record.Empty();

	f_shapes.Set(-1, sr);
}



/** \brief Mark the shape as being a glyph
 *
 * This function marks the shape as being a glyph. It has
 * two effects: make sure the shape can be used in a font
 * as a glyph and it removes the identification used for
 * this shape.
 */
void TagShape::Glyph(void)
{
	f_is_glyph = true;
	NoIdentification();
}


/** \brief Check whehter this shape is a glyph.
 *
 * This function checks whether the Glyph() function was called.
 *
 * \return true if the shape is a glyph
 */
bool TagShape::IsGlyph(void) const
{
	return f_is_glyph;
}


/** \brief Check whether there is anything in this shape.
 *
 * This function checks to see whether the shape is still
 * empty.
 *
 * \return true if the shape was not yet defined.
 */
bool TagShape::IsEmpty(void) const
{
	return f_record.Count() == 0 && f_edges == 0;
}


/** \brief Ensure the minimum version is acceptable.
 *
 * This function ensures that the minimum version necessary to
 * save that TagShape is acceptable.
 *
 * \return An error code or ErrorManager::ERROR_CODE_NONE.
 */
ErrorManager::error_code_t TagShape::PreSave(void)
{
	MinimumVersion(f_version);

	return ErrorManager::ERROR_CODE_NONE;
}


/** \brief This function saves the TagShape in a Data buffer
 *
 * This function determines the type of a shape this shape is
 * (DefineShape, DefineShape2, DefineShape3, DefineShape4,
 * DefineMorphShape or DefineMorphShape2) and then saves it
 * in the specified Data buffer.
 *
 * The PreSave() function must have been called once before.
 *
 * \note
 * Glyphs aren't saved via this function. Instead the
 * Font saves the edges as required.
 *
 * \param[in] data	The Data buffer where the edges are to be saved
 *
 * \return An error code or ErrorManager::ERROR_CODE_NONE.
 */
ErrorManager::error_code_t TagShape::Save(Data& data)
{
	// In order to save a shape properly we need to (1) know its
	// version (f_version) and whether it is a morphing shape
	// (f_morph).
	save_info_t			info, sub_info, *info_ptr;
	swf_tag_t			tag;
	int				idx, max;
	ErrorManager::error_code_t	ec;
	shape_setup_t			last_setup(SHAPE_SETUP, true);
	SRectangle			rect;

	RecordEdges();
	// RecordSetup(); -- don't record the last setup!!! that's totally useless!!!

	if(f_is_glyph) {
		// save nothing for a glyph since these are saved within a font instead
		return ErrorManager::ERROR_CODE_NONE;
	}

	// default for V1.0
	tag = SWF_TAG_DEFINE_SHAPE;
	info.f_save_alpha = false;
	info.f_ext_size = false;
	info.f_shape4 = false;

	switch(f_version) {
	case 2:
		//info.f_save_alpha = false;
		info.f_ext_size = true;
		tag = SWF_TAG_DEFINE_SHAPE2;
		break;

	case 3:
	case 7:
		info.f_save_alpha = true;
		info.f_ext_size = true;
		if(f_morph) {
			tag = SWF_TAG_DEFINE_MORPH_SHAPE;
		}
		else {
			tag = SWF_TAG_DEFINE_SHAPE3;
		}
		break;

	case 8:
		info.f_save_alpha = true;
		info.f_ext_size = true;
		info.f_shape4 = true;
		if(f_morph) {
			tag = SWF_TAG_DEFINE_MORPH_SHAPE2;
		}
		else {
			tag = SWF_TAG_DEFINE_SHAPE4;
		}
		break;

#if DEBUG
	case 1:
		break;

	default:
		assert(0, "invalid f_version in a TagShape object to be saved");
		return OnError(ErrorManager::ERROR_CODE_INTERNAL_ERROR, "invalid f_version in a TagShape object to be saved");
#endif

	}
	info.f_first = true;
	info.f_save_styles = true;

	if(f_morph && f_bounds[1].IsEmpty()) {
		f_bounds[1] = f_bounds[0];
	}

	SaveID(info.f_data);
	max = f_morph ? 2 : 1;
	for(idx = 0; idx < max; idx++) {
		info.f_data.Align();
		if(f_show_bounds) {
			// first we have to make sure that it is properly ordered
			rect.SetReorder(f_bounds[idx].XMin(), f_bounds[idx].XMax(), f_bounds[idx].YMin(), f_bounds[idx].YMax());
			// then we can enlarge it as required to include the bounding line
			rect.Set(rect.XMin() - 2, rect.XMax() + 2, rect.YMin() - 2, rect.YMax() + 2);
			rect.Save(info.f_data);
		}
		else {
			f_bounds[idx].Save(info.f_data);
		}
	}

	if(info.f_shape4) {
		for(idx = 0; idx < max; idx++) {
			info.f_data.Align();
			if(f_strokes_bounds[idx].IsEmpty()) {
				f_strokes_bounds[idx] = f_bounds[idx];
			}
			f_strokes_bounds[idx].Save(info.f_data);
		}

		info.f_data.WriteBits(0, 6);
		info.f_data.WriteBits(f_has_non_scaling_strokes, 1);
		info.f_data.WriteBits(f_has_scaling_strokes, 1);
	}

	if(f_morph) {
		// in case of a morph we need to save all the styles
		// and the first shape to know the size to save in
		// the offset;
		sub_info.f_save_alpha = info.f_save_alpha;
		sub_info.f_ext_size = info.f_ext_size;
		sub_info.f_shape4 = info.f_shape4;
		sub_info.f_first = true;
		sub_info.f_save_styles = true;

		info_ptr = &sub_info;
	}
	else {
		info_ptr = &info;
	}

	// save the extras
	max = f_shapes.Count();
	for(idx = 0; idx < max; idx++) {
		info_ptr->f_sr = *dynamic_cast<shape_record_t *>(f_shapes.Get(idx));
		ec = SaveShape(*info_ptr, last_setup);
		if(ec != ErrorManager::ERROR_CODE_NONE) {
			return ec;
		}
	}

	// now save the current
	info_ptr->f_sr.f_fill_styles = &f_fill_styles;
	info_ptr->f_sr.f_line_styles = &f_line_styles;
	info_ptr->f_sr.f_record = &f_record;
	ec = SaveShape(*info_ptr, last_setup);
	if(ec != ErrorManager::ERROR_CODE_NONE) {
		return ec;
	}


	if(f_morph) {
		// the last setup for the morphed version has
		// to start from scratch
		shape_setup_t	last_setup(SHAPE_SETUP, true);

		// save an end of record now (between both shapes)
		sub_info.f_data.WriteBits(0, 6);

		// save the offset to the 2nd set of edges
		info.f_data.PutLong(sub_info.f_data.ByteSize());

		// now we can add the style & 1st set of edges to the main buffer
		info.f_data.Append(sub_info.f_data);

		// we need to save a count for the styles; since they are
		// not referenced at all, we always use 0x11
		info.f_data.PutByte(0x11);

		// we need to record the last edges now
		if(f_morph_edges != 0) {
			f_morph_record.Set(-1, f_morph_edges);
			f_morph_edges = 0;
		}

		// now save the morphed edges
		info.f_sr.f_fill_styles = &f_fill_styles;
		info.f_sr.f_line_styles = &f_line_styles;
		info.f_sr.f_record = &f_morph_record;
		info.f_save_styles = false;
		ec = SaveShape(info, last_setup);
		if(ec != ErrorManager::ERROR_CODE_NONE) {
			return ec;
		}
	}
	else {
		// TODO: the bounds and/or origin do not work properly in
		// different types of shapes (i.e. hit area for a button)
		// we should somehow test that... (it could be that a hit
		// area needs to be a fill only?)
		if(f_show_bounds || f_show_origin) {
			Vectors		fill_styles;		// empty!
			Vectors		line_styles;		// one style: a red line
			Vectors		record;			// setup + rect.
			Style		red_line(*Header());
			Color		red;
			shape_setup_t	bounds_setup(SHAPE_SETUP);
			shape_setup_t	horizontal_setup(SHAPE_SETUP);
			shape_setup_t	vertical_setup(SHAPE_SETUP);
			shape_edges_t	bounds_edges(SHAPE_EDGES);
			shape_edges_t	horizontal_edges(SHAPE_EDGES);
			shape_edges_t	vertical_edges(SHAPE_EDGES);

			// create red line style
			red.Set(255, 0, 0);
			red_line.SetLine(0, 2, red);
			line_styles.Set(-1, &red_line);

			// insert line style
			if(f_show_bounds) {
				bounds_setup.f_fill_ref[0] = 0;
				bounds_setup.f_fill_ref[1] = 0;
				bounds_setup.f_line_ref = 1;
				bounds_setup.f_x = f_bounds[0].XMin();
				bounds_setup.f_y = f_bounds[0].YMin();
				record.Set(-1, &bounds_setup);

				// draw rectangle
				bounds_edges.f_edges.Set(f_bounds[0].XMax() - f_bounds[0].XMin(), 0);
				bounds_edges.f_edges.Set(0, f_bounds[0].YMax() - f_bounds[0].YMin());
				bounds_edges.f_edges.Set(f_bounds[0].XMin() - f_bounds[0].XMax(), 0);
				bounds_edges.f_edges.Set(0, f_bounds[0].YMin() - f_bounds[0].YMax());

				// insert rect.
				record.Set(-1, &bounds_edges);
			}

			if(f_show_origin) {
				// insert X axis
				horizontal_setup.f_fill_ref[0] = 0;
				horizontal_setup.f_fill_ref[1] = 0;
				horizontal_setup.f_line_ref = 1;
				horizontal_setup.f_x = f_bounds[0].XMin();
				horizontal_setup.f_y = 0;
				record.Set(-1, &horizontal_setup);
				horizontal_edges.f_edges.Set(f_bounds[0].XMax() - f_bounds[0].XMin(), 0);
				record.Set(-1, &horizontal_edges);

				// insert Y axis
				vertical_setup.f_fill_ref[0] = 0;
				vertical_setup.f_fill_ref[1] = 0;
				vertical_setup.f_line_ref = 1;
				vertical_setup.f_x = 0;
				vertical_setup.f_y = f_bounds[0].YMin();
				record.Set(-1, &vertical_setup);
				vertical_edges.f_edges.Set(0, f_bounds[0].YMax() - f_bounds[0].YMin());
				record.Set(-1, &vertical_edges);
			}

			// insert all of that in the shape now
			info.f_sr.f_fill_styles = &fill_styles;
			info.f_sr.f_line_styles = &line_styles;
			info.f_sr.f_record = &record;
			ec = SaveShape(info, last_setup);
			if(ec != ErrorManager::ERROR_CODE_NONE) {
				return ec;
			}
		}
	}

	// save an end of record now
	info.f_data.WriteBits(0, 6);

	SaveTag(data, tag, info.f_data.ByteSize());
	data.Append(info.f_data);

	return ErrorManager::ERROR_CODE_NONE;
}



/** \brief Save the edges only.
 *
 * This function is used by the sswf::TagFont object to save the
 * edges without any style.
 *
 * Note that this function is expected to be used to save glyphs
 * only and thus shapes that are not morphing.
 *
 * \param[in] data	The Data buffer where the edges are to be saved
 *
 * \return An error code or ErrorManager::ERROR_CODE_NONE.
 */
ErrorManager::error_code_t TagShape::SaveWithoutStyles(Data& data)
{
	save_info_t			info;
	int				idx, max;
	shape_setup_t			last_setup(SHAPE_SETUP, true);
	ErrorManager::error_code_t	ec;

	RecordEdges();
	// don't record the last setup!!! that's totally useless!!!

	if(f_morph) {
		return OnError(ErrorManager::ERROR_CODE_INCOMPATIBLE_MORPH, "cannot save a morphing glyph");
	}

	// assume V1.0 for fonts (that's what this is for!)
	info.f_save_alpha = false;
	info.f_ext_size = false;
	info.f_first = false;		// no style is to be saved... (only style changes?)
	info.f_save_styles = false;

	// this is not automatically defined in this case
	info.f_fill_bits_count = 1;
	info.f_line_bits_count = 1;

	max = f_shapes.Count();
	for(idx = 0; idx < max; idx++) {
		info.f_sr = *dynamic_cast<shape_record_t *>(f_shapes.Get(idx));
		ec = SaveShape(info, last_setup);
		if(ec != ErrorManager::ERROR_CODE_NONE) {
			return ec;
		}
	}

	info.f_sr.f_fill_styles = &f_fill_styles;
	info.f_sr.f_line_styles = &f_line_styles;
	info.f_sr.f_record = &f_record;
	ec = SaveShape(info, last_setup);
	if(ec != ErrorManager::ERROR_CODE_NONE) {
		return ec;
	}

	// save an end of record now
	info.f_data.WriteBits(0, 6);

	data.PutByte(0x11);		// TODO: should that be 0x11 or dynamically determined?
	data.Append(info.f_data);

	return ErrorManager::ERROR_CODE_NONE;
}


/** \brief Save a shape.
 *
 * This function saves a shape depending on the info definitions.
 *
 * \param[in] info		Definition of what needs to be saved.
 * \param[in] last_setup	Current shape setup (move, fill & line styles)
 *
 * \return An error code or ErrorManager::ERROR_CODE_NONE.
 */
ErrorManager::error_code_t TagShape::SaveShape(save_info_t& info, shape_setup_t& last_setup)
{
	ErrorManager::error_code_t	ec;
	int				idx, max;
	shape_what_t			*rec;
	shape_edges_t			*edges;
	shape_setup_t			*setup;

	idx = 0;

	if(info.f_save_styles) {
		if(!info.f_first) {
			// we need a "special" setup entry with the new fill & line styles
			// NOTE: I'm not sure whether it could be merged with the following
			//	 setup record, it is possible to use idx = 1 for that purpose;
			//	 however it would mean that the number of bits used to
			//	 define the new fill & line styles isn't known before you
			//	 read the new styles which happen AFTER the rest!?!? strange
			//	 (stupid?) idea from Macromedia I think
			info.f_data.WriteBits(0x10, 6);		// setup with new styles byte indicator
		}
		info.f_first = false;
		ec = SaveStyles(info);
		if(ec != ErrorManager::ERROR_CODE_NONE) {
			return ec;
		}
	}

	max = info.f_sr.f_record->Count();
//fprintf(stderr, "Count = %d\n", max);
	while(idx < max) {
		rec = dynamic_cast<shape_what_t *>(info.f_sr.f_record->Get(idx));
		if(rec->f_what == SHAPE_SETUP) {
			// change setup here
			setup = dynamic_cast<shape_setup_t *>(rec);

//fprintf(stderr, "  setup = %p\n", setup);

			ec = SaveSetup(info, *setup, last_setup);
			if(ec != ErrorManager::ERROR_CODE_NONE) {
				return ec;
			}
		}
		else {
			// we've got a set of edges
			edges = dynamic_cast<shape_edges_t *>(rec);
			edges->f_edges.Save(info.f_data, last_setup.f_x, last_setup.f_y);
		}
		idx++;
	}

	return ErrorManager::ERROR_CODE_NONE;
}


/** \brief Save the specified shape setup.
 *
 * This function saves a setup (move, fill and lines).
 *
 * \param[in] info	Definition of what needs to be saved
 * \param[in] setup	Setup to save
 * \param[in] last	Previous setup saved (to avoid repeats)
 *
 * \return An error code or ErrorManager::ERROR_CODE_NONE.
 */
ErrorManager::error_code_t TagShape::SaveSetup(save_info_t& info, const shape_setup_t& setup, shape_setup_t& last)
{
	bool		has_line_style, has_fill_style[2], has_move;
	int		sz, bits;
	long		x, y;

	info.f_data.WriteBits(0, 1);		// a setup entry

	// V2.0+ have a NewStyles flag
#if 0
	if(f_version != 1) {
		info.f_data.WriteBits(0, 1);		// has no new styles
	}
#endif
	info.f_data.WriteBits(0, 1);		// has no new styles

	// is the line style setup changing?
	has_line_style = /*!info.f_save_styles ||*/ setup.f_line_ref != -1 && setup.f_line_ref != last.f_line_ref;
	info.f_data.WriteBits(has_line_style, 1);

	// is one of the fill style changing?
	has_fill_style[0] = setup.f_fill_ref[0] != -1 && setup.f_fill_ref[0] != last.f_fill_ref[0];
	has_fill_style[1] = setup.f_fill_ref[1] != -1 && setup.f_fill_ref[1] != last.f_fill_ref[1];
	info.f_data.WriteBits(has_fill_style[1] * 2 + has_fill_style[0], 2);

	// is it moving?
	x = setup.f_x;
	y = setup.f_y;
	if(x == LONG_MIN) {
		x = last.f_x;
	}
	if(y == LONG_MIN) {
		y = last.f_y;
	}
	has_move = x != last.f_x || y != last.f_y;
	info.f_data.WriteBits(has_move, 1);

#if 0
printf("has line: %d has fill: %d/%d, has move: %d (%ld, %ld) vs (%ld, %ld)\n",
		has_line_style, has_fill_style[0], has_fill_style[1], has_move,
		x, y, last.f_x, last.f_y);
#endif

	// save the move if there is one
	if(has_move) {
		sz = SIBitSize(x);
		bits = SIBitSize(y);
		if(bits > sz) {
			sz = bits;
		}
		info.f_data.WriteBits(sz, 5);
		info.f_data.WriteBits(x, sz);
		info.f_data.WriteBits(y, sz);
		last.f_x = x;
		last.f_y = y;
	}

	// save fills if they changed; the # of bits was defined while
	// saving the styles last
	// TODO: if we have to save new fill/line styles we will need
	//	 the new fill/line bits counts BEFORE to save the
	//	 following... (is that logical?!?)
	if(has_fill_style[0]) {
		info.f_data.WriteBits(setup.f_fill_ref[0], info.f_fill_bits_count);
		last.f_fill_ref[0] = setup.f_fill_ref[0];
	}
	if(has_fill_style[1]) {
		info.f_data.WriteBits(setup.f_fill_ref[1], info.f_fill_bits_count);
		last.f_fill_ref[1] = setup.f_fill_ref[1];
	}

	// save the line style if it changed
	if(has_line_style) {
		info.f_data.WriteBits(setup.f_line_ref, info.f_line_bits_count);
		last.f_line_ref = setup.f_line_ref;

		/* tried that -- doesn't seem to be necessary!
		if(info.f_save_styles) {
		}
		else {
			info.f_data.WriteBits(0, info.f_line_bits_count);
printf("Save line style of 0!\n");
		}
		*/
	}

	// we never have new fill & styles here

	return ErrorManager::ERROR_CODE_NONE;
}


/** \brief Save the specified shape setup.
 *
 * This function saves a setup (move, fill and lines).
 *
 * \param[in] info	Definition of what needs to be saved
 * \param[in] count	Save this count depending on the version
 *
 * \return An error code or ErrorManager::ERROR_CODE_NONE.
 */
ErrorManager::error_code_t TagShape::SaveStylesCount(save_info_t& info, long count)
{
	if(f_version == 1) {
		if(count >= 256) {
			return OnError(ErrorManager::ERROR_CODE_TOO_MANY_STYLES, "invalid style count of %ld for a V1.0 flash", count);
		}
		info.f_data.PutByte((char) count);
	}
	else if(count > 254) {
		info.f_data.PutByte((char) 255);
		info.f_data.PutShort((short) count);
	}
	else {
		info.f_data.PutByte((char) count);
	}

	return ErrorManager::ERROR_CODE_NONE;
}


/** \brief Save the specified shape styles.
 *
 * This function saves a set of styles.
 *
 * \param[in] info	Definition of what needs to be saved
 *
 * \return An error code or ErrorManager::ERROR_CODE_NONE.
 */
ErrorManager::error_code_t TagShape::SaveStyles(save_info_t& info)
{
	ErrorManager::error_code_t	ec;
	int				idx, count;
	Style				*style;

// save the FILL STYLES
	count = info.f_sr.f_fill_styles->Count();
	ec = SaveStylesCount(info, count);
	if(ec != ErrorManager::ERROR_CODE_NONE) {
		return ec;
	}

	for(idx = 0; idx < count; idx++) {
		style = dynamic_cast<Style *>(info.f_sr.f_fill_styles->Get(idx));
		ec = style->Save(info.f_data, info.f_save_alpha, f_morph);
		if(ec != ErrorManager::ERROR_CODE_NONE) {
			return ec;
		}
	}

	info.f_fill_bits_count = UIBitSize(count);

// save the LINE STYLES
	count = info.f_sr.f_line_styles->Count();
	ec = SaveStylesCount(info, count);
	if(ec != ErrorManager::ERROR_CODE_NONE) {
		return ec;
	}

	for(idx = 0; idx < count; idx++) {
		style = dynamic_cast<Style *>(info.f_sr.f_line_styles->Get(idx));
		if(info.f_shape4) {
			// make sure we have enhanced lines with a shape4
			style->SetType(Style::STYLE_TYPE_ENHANCED_LINE);
		}
		else {
			// make sure we do not have enhanced lines with shape1,2,3
			style->SetType(Style::STYLE_TYPE_LINE);
		}
		ec = style->Save(info.f_data, info.f_save_alpha, f_morph);
		if(ec != ErrorManager::ERROR_CODE_NONE) {
			return ec;
		}
	}

	info.f_line_bits_count = UIBitSize(count);

// save the number of bits we will use to save fill & line references from now on
	info.f_data.PutByte((info.f_fill_bits_count << 4) + info.f_line_bits_count);

	return ErrorManager::ERROR_CODE_NONE;
}





/* The following options fold the documentation; use 'zi' to turn on and off
 *
 * vim: foldexpr=getline(v\:lnum)!~'^/\\*\\*'&&getline(v\:lnum)!~'^\ \\*'?0\:1 foldcolumn=2 foldmethod=expr
 */
