/* libsswf_edges.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::Edges 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;




/////////////////////////////////////////// Edges

/** \class sswf::Edges
 *
 * \brief List of coordinates to draw shapes.
 *
 * The Edges class represents an array of edges used to draw shapes.
 *
 * An edge is one point on a curve and eventually one control point.
 * All edges are specified in a two dimensional plane (i.e. x and y
 * coordinates).
 *
 * You can use the sswf::Edges::edge_t structure to setup a point and then add it to
 * the Edges class (C++ only) or use a specialized function which will
 * automatically create such a structure for you.
 *
 * There is no limit to the number of points in an Edges class. However,
 * every EDGE_BLOCK points, a new array is allocated to save more points.
 *
 * Note that there are no tests on the edges. You need to ensure that they
 * are valid for a Flash movie. It is strongly suggested that you do not
 * use too large or too small a value. The edges values are in TWIPS (i.e.
 * 1/20<sup>th</sup> of a pixel).
 *
 * The control points are set to LONG_MIN so as to be ignored. Such a large
 * value cannot be represented in Flash and it would be way too large anyway.
 *
 * \warning
 * Note that all the coordinates are specified as in Flash: the next coordinate
 * is ADDED to the previous coordinate. Thus if you have three points:
 *
 * \code
 * 	(10, 32)
 * 	(40, 14)
 * 	(3, 100)
 * \endcode
 *
 * You want to pass to the edges the following coordinates:
 *
 * \code
 * 	(10, 32)
 * 	(40 - 10, 14 - 32)
 * 	(3 - (40 - 10), 100 - (14 - 32))
 * \endcode
 *
 * \sa sswf::Edges::edge_t
 * \sa <a href="../SWFalexref.html#swf_shape_record">SWF Alexis' Reference&mdash;swf_shape_record</a>
 */


/** \class sswf::Edges::edge_t
 *
 * \brief The basic structure used to hold the coordinates of an edge.
 *
 * This helper structure is used to hold edges internally. You can also use
 * it to pass edges to an Edges object.
 *
 * The points are all specified as two dimension coordinates in TWIPS (1/20<sup>th</sup>
 * of a pixel.)
 *
 * Multiple points are specified as cumulative which means that the next
 * point coordinates are added to the previous points coordinates. Thus
 * a starting edge at (3, 7), followed by an edge at (9, 11) represents
 * one point at (3, 7) and another at (12, 18).
 *
 * Do not forget that this applies to control points as well. In an edge_t
 * structure you first move to the (x, y) coordinate, and then to the
 * (ctrl_x, ctrl_y) coordinates.
 *
 * \note
 * The fact that this structure is called Edges instead of Points is
 * because of this fact. Edges coordinates are cumulative and not
 * direct points. However, I most of the time will reference edges as
 * points and vice versa.
 *
 * \note
 * For those of you who have played with a turtle under Logo, you can view
 * this technique as if you were moving a turtle. It is very simiar (except
 * that you do not have a direction since you move with relative coordinates.)
 *
 * \code
 * ADVANCE [x1, y1]
 * ADVANCE [ctrl_x1, ctrl_y1]
 * ADVANCE [x2, y2]
 * ADVANCE [ctrl_x2, ctrl_y2]
 * ADVANCE [x3, y3]
 * ADVANCE [ctrl_x3, ctrl_y3]
 * ...
 * \endcode
 *
 * \warning
 * This sub-structure is not currently accessible to the C library because it
 * includes functions.
 *
 * \sa sswf::Edges
 */

/** \fn sswf::Edges::edge_t::f_x
 *
 * \brief The X coordinate
 *
 * This variable member represents the horizontal coordinate of this edge.
 *
 * This value is in TWIPS.
 */

/** \fn sswf::Edges::edge_t::f_y
 *
 * \brief The Y coordinate
 *
 * This variable member represents the vertical coordinate of this edge.
 *
 * This value is in TWIPS.
 */

/** \fn sswf::Edges::edge_t::f_ctrl_x
 *
 * \brief The control X coordinate or LONG_MIN
 *
 * This variable member represents the Control point horizontal coordinate.
 *
 * When set to LONG_MIN, it is ignored&mdash;no control point meaning that the
 * edge represents a line.
 *
 * This value is in TWIPS.
 */

/** \fn sswf::Edges::edge_t::f_ctrl_y
 *
 * \brief The control Y coordinate or LONG_MIN
 *
 * This variable member represents the Control point vertical coordinate.
 *
 * When set to LONG_MIN, it is ignored--no control point meaning that the
 * edge represents a line.
 */

/** \fn sswf::Edges::edge_t::edge_t(void)
 *
 * \brief Constructs a default edge structure.
 *
 * The default constructor creates a line edge at coordinate (0, 0).
 */

/** \fn sswf::Edges::edge_t::edge_t(long x, long y)
 *
 * \brief Constructs a line edge structure.
 *
 * This constructor creates a line edge at coordinate (x, y).
 *
 * \param x The horizontal coordinate
 * \param y The vertical coordinate
 */

/** \fn sswf::Edges::edge_t::edge_t(long x, long y, long ctrl_x, long ctrl_y)
 *
 * \brief Constructs a curve edge structure.
 *
 * This constructor creates a curve edge at coordinate (x, y) and control
 * point (ctrl_x, ctrl_y).
 *
 * \param x The horizontal coordinate
 * \param y The vertical coordinate
 * \param ctrl_x The horizontal control coordinate
 * \param ctrl_y The vertical control coordinate
 */

/** \fn sswf::Edges::edge_t::edge_t(const edge_t& edge)
 *
 * \brief Copy an edge structure.
 *
 * The copy operator copies the input edge in 'this' edge.
 *
 * \note
 * It should not be necessary to make this copy operator explicit, but some
 * older compilers have a hard time without it.
 *
 * \param edge The edge to copy in 'this' edge_t
 */

/** \fn sswf::Edges::edge_t::operator= (const edge_t& edge)
 *
 * \brief Copy an edge structure.
 *
 * The copy operator copies the input edge in 'this' edge.
 *
 * \note
 * It should not be necessary to make this copy operator explicit, but some
 * older compilers have a hard time without it.
 *
 * \param edge The edge to copy in 'this'
 *
 * \return 'this' edge which is now a copy of the input edge
 */

/** \fn sswf::Edges::edge_t::IsLine(void) const
 *
 * \brief Check whether the edge represents a line (true) or a curve (false)
 *
 * This function returns true if one of the control point is set to LONG_MIN.
 * You should use that function and not try to test yourself whether an edge
 * represents a line or a curve.
 *
 * \return true if the edge represents a line, false when it represents a curve
 */

/** \brief Initialize the Edges object
 *
 * The constructor ensures that the list of points is empty.
 */
Edges::Edges(void)
{
	//f_edges -- auto-init
	f_pos = 0;
	//f_array -- f_pos = 0 means nothing in this array will be used
}


/** \brief Clean up a set of edges
 *
 * This function ensures that the edges are being deleted from memory.
 */
Edges::~Edges()
{
}


/** \brief Set an edge at the specified index
 *
 * Copy the specified edge at the specified index.
 *
 * If the index is set to -1, then the edge is appended to the existing
 * list.
 *
 * \param index The index where the edge is to be saved or -1 to append
 * \param edge A valid edge structure
 */
void Edges::Set(int index, const edge_t& edge)
{
	int		idx, max;
	array_edge_t	*e;

	if(index != -1) {
		// verify that this entry exists...
		max = f_edges.Count() * EDGE_BLOCK;
		if(index < max + f_pos) {
			// we have to replace the specified item
			if(index >= max) {
				// that's within the current array
				index -= max;
				f_array.f_edge[index] = edge;
			}
			else {
				// that's in one of the saved block of edges
				idx = index / EDGE_BLOCK;
				index -= idx * EDGE_BLOCK;
				e = dynamic_cast<array_edge_t *>(f_edges.Get(idx));
				e->f_edge[index] = edge;
			}
			return;
		}
		assert(index == max + f_pos, "invalid index of %d in Edges::Set()", index);
		// the index specified means "insert at the end (append)"
	}

	if(f_pos >= EDGE_BLOCK) {
		// not enough room in the current array, save it
		// in our list of vectors to arrays and reset the
		// current array!
		e = new array_edge_t;
		MemAttach(e, sizeof(f_array), "Edges::Set() -- large array");
		idx = EDGE_BLOCK;
		do {
			idx--;
			e->f_edge[idx] = f_array.f_edge[idx];
		} while(idx > 0);
		f_edges.Set(-1, e);
		f_pos = 0;
	}

	f_array.f_edge[f_pos] = edge;
	f_pos++;
}


/** \fn sswf::Edges::Set(long x, long y);
 *
 * \brief Append point (x, y)
 *
 * Add a straight line edge with coordinates (x, y)
 *
 * This point is appended at the end of the existing list.
 *
 * \param x The horizontal coordinate
 * \param y The vertical coordinate
 */

/** \fn sswf::Edges::Set(long x, long y, long ctrl_x, long ctrl_y);
 *
 * \brief Append point (x, y) with control point (ctrl_x, ctrl_y)
 *
 * Add a curve edge with coordinates (x, y) and control point (ctrl_x, ctrl_y).
 *
 * This point is appended at the end of the existing list.
 *
 * \param x The horizontal coordinate
 * \param y The vertical coordinate
 * \param ctrl_x The horizontal control point coordinate
 * \param ctrl_y The vertical control point coordinate
 */

/** \fn sswf::Edges::Set(int index, long x, long y, long ctrl_x, long ctrl_y);
 *
 * \brief Append point (x, y) with control point (ctrl_x, ctrl_y) at position index
 *
 * Set a curve edge at the specified 'index' with coordinates (x, y)
 * and control point (ctrl_x, ctrl_y).
 *
 * This point overwrites the existing point at coordinate 'index'.
 *
 * If index is set to -1, then the new point is added at the end (appended).
 *
 * \param index The position in the array of edges where the edge is saved
 * \param x The horizontal coordinate
 * \param y The vertical coordinate
 * \param ctrl_x The horizontal control point coordinate
 * \param ctrl_y The vertical control point coordinate
 */

/** \fn sswf::Edges::Set(const edge_t& edge);
 *
 * \brief Append the specified edge
 *
 * This function adds the specified edge at the end of the existing edges.
 *
 * \param edge The edge to append
 */





/** \brief Save the Edges in the Data buffer
 *
 * This function saves the array of edges in the specified Data buffer.
 *
 * The x and y parameters are in/out coordinates used to know where these
 * edges end. If you enter the function with x and y set to 0, then you
 * will know exactly where the edges end as if the shape was starting
 * at the origin. This is used to move to the next set of edges and close
 * shapes.
 *
 * \note
 * The x and y coordinates have NO effect on what is being saved
 * by this function.
 *
 * \param data The data buffer where the data is to be saved
 * \param x The starting (in) and ending (out) horizontal coordinate
 * \param y The starting (in) and ending (out) vertical coordinate
 */
void Edges::Save(Data& data, long& x, long& y)
{
	int		index, idx, max;
	array_edge_t	*e;

	max = f_edges.Count();
	for(idx = 0; idx < max; idx++) {
		e = dynamic_cast<array_edge_t *>(f_edges.Get(idx));
		for(index = 0; index < EDGE_BLOCK; index++) {
			SaveEdge(data, e->f_edge[index], x, y);
		}
	}

	for(index = 0; index < f_pos; index++) {
		SaveEdge(data, f_array.f_edge[index], x, y);
	}
}



/* private function */
void Edges::SaveEdge(Data& data, const edge_t& edge, long& x, long& y)
{
	int		bits, sz;

	x += edge.f_x;
	y += edge.f_y;

	data.WriteBits(1, 1);		// Edge record
	if(edge.IsLine()) {
		// straight line
		data.WriteBits(1, 1);
		if(edge.f_y == 0.0) {
			// don't need y but we MUST save at least one coordinate so save x here
			// even if it is zero (note that it shouldn't be otherwise these coordinates
			// are really useless!)
			sz = TagBase::SIBitSize(edge.f_x);
			if(sz < 2) {
				sz = 2;
			}
			data.WriteBits(sz - 2, 4);
			data.WriteBits(0, 2);				// only X inserted
			data.WriteBits(edge.f_x, sz);
		}
		else if(edge.f_x == 0.0) {
			// don't need x -- we already know y is necessary
			sz = TagBase::SIBitSize(edge.f_y);
			if(sz < 2) {
				sz = 2;
			}
			data.WriteBits(sz - 2, 4);
			data.WriteBits(1, 2);				// only Y inserted
			data.WriteBits(edge.f_y, sz);
		}
		else {
			// both coordinates necessary
			sz = TagBase::SIBitSize(edge.f_x);
			bits = TagBase::SIBitSize(edge.f_y);
			if(bits > sz) {
				sz = bits;
			}
			if(sz < 2) {
				sz = 2;
			}
			data.WriteBits(sz - 2, 4);
			data.WriteBits(1, 1);
			data.WriteBits(edge.f_x, sz);
			data.WriteBits(edge.f_y, sz);
		}
	}
	else {
		// curve
		x += edge.f_ctrl_x;
		y += edge.f_ctrl_y;

		data.WriteBits(0, 1);
		sz = TagBase::SIBitSize(edge.f_x);
		bits = TagBase::SIBitSize(edge.f_y);
		if(bits > sz) {
			sz = bits;
		}
		bits = TagBase::SIBitSize(edge.f_ctrl_x);
		if(bits > sz) {
			sz = bits;
		}
		bits = TagBase::SIBitSize(edge.f_ctrl_y);
		if(bits > sz) {
			sz = bits;
		}
		if(sz < 2) {
			sz = 2;
		}
		data.WriteBits(sz - 2, 4);
		data.WriteBits(edge.f_ctrl_x, sz);
		data.WriteBits(edge.f_ctrl_y, sz);
		data.WriteBits(edge.f_x, sz);
		data.WriteBits(edge.f_y, sz);
	}
}



/* 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
 */
