/*
 *  FSShapeConstructor.java
 *  Transform Utilities
 *
 * 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.util;

import com.flagstone.transform.*;
import java.util.*;


/**
 * The FSShapeConstructor class is used to create shape definitions. Arbitrary paths can be 
 * created using a series of move, line or curve segments. Drawing operations using both
 * absolute coordinates and coordinates relative to the current point (updated after every 
 * operation) are supported. 
 * 
 * For curves both cubic and quadratic curves are supported. Flash only supports quadratic 
 * curves so cubic curves are approximated by a series of line segments using (converting cubic 
 * to quadratic curves is mathematically difficult). The smoothness of cubic curves is 
 * controlled by the flatness attribute which can be used to limit the number of line segments
 * that are drawn.
 * 
 * While drawing the start of the first segment to be drawn is recorded so the path can 
 * easily be closed by drawing a line from the current point to the start of the first 
 * segment drawn. To account for the rounded mitering supported by Flash the path is closed 
 * by re-adding the first segment drawn so the line ends overlap.
 * 
 * As a path is drawn the maximum and minimum x and y coordinates are recorded so that the 
 * bounding rectangle that completely encloses the shape can be defined. This is used when
 * creating shape definitions using the FSDefineShape, FSDefineShape2 or FSDefineShape3 classes.
 * 
 * The FSShapeConstructor class also supports a number of method to create closed paths that
 * represent different geometric shapes. Basic rectangles, ellipses and circles are supported.
 * More complex shapes can be drawn using the polygon() method which uses pairs of points to 
 * specified the vertices of an arbitrary shapes.
 * 
 * When drawing paths whether coordinates are specified in twips or pixels is controlled by 
 * the attributes COORDINATES_ARE_PIXELS. This is independent of the flag of the same name 
 * supported in the Transform SWF frameworks. When true coordinates are specified in pixels,
 * while coordinates are specified in twips when false. 1 pixel == 20 twips. Internally all
 * coordinates are converted to twips to perform the actual drawing.
 * 
 * The styles used to draw the path and fill the area enclosed is specified using the ordinal
 * position of the style object in the arrays of styles maintained by the shape definition
 * object (FSDefineShape, SDefineShape2 or FSDefineShape3) used to construct the final shape. 
 *  
 * <b>Examples</b>
 *
 * The following code samples illustrate how to use the FSShapeConstructor class create shapes.
 *
 * 1. Defining a rectangle using move and line drawing operations.\n
 * 
 *<pre>
 *    FSShapeConstructor path = new FSShapeConstructor();
 * 
 *    path.COORDINATES_ARE_PIXELS = true;
 * 
 *    int width = 200;
 *    int height = 100;
 *
 *    // Setting the line style before any drawing commands is important
 *    // so the thickness of the line can be taken into account when
 *    // calculating the bounding rectangle which must completely enclose
 *    // the shape so it will be drawn correctly.
 *
 *    path.add(new FSSolidLine(1, FSColorTable.black()));
 *    path.add(new FSSolidFill(FSColorTable.red()));
 *
 *    newPath();                   // start a new path
 *    selectStyle(1, 1);              // select the first line and fill style objects
 *    move(-width/2, -height/2);   // Move to the top left corner
 *    rline(width, 0);             // Draw the sides of the rectangle
 *    rline(0, height);
 *    rline(-width, 0);
 *    rline(0, -height);
 *    closePath();                 // close the path to correctly seal the line ends.
 * 
 *    // Now define the shape.
 * 
 *    FSDefineShape3 rect = path.defineShape(movie.newIdentifier());
 * 
 *</pre>
 *
 * 2. Defining a rectangle using the path creation method.\n
 * 
 *<pre>
 *    FSShapeConstructor path = new FSShapeConstructor();
 * 
 *    path.COORDINATES_ARE_PIXELS = true;
 *
 *    // Define the origin for the shape so the bottom left corner of the 
 *    // rectangle will appear at the coordinates the shape is place at 
 *    // using the FSPlaceObject or FSPlaceObject2 class.
 * 
 *    int xorigin = 100;
 *    int yorigin = -50;
 * 
 *    int width = 200;
 *    int height = 100;
 *    int cornerRadius = 10
 *
 *    path.add(new FSSolidLine(1, FSColorTable.black()));
 *    path.add(new FSSolidFill(FSColorTable.red()));
 *
 *    // Create a rectangle with rounded corners.
 * 
 *    path.rect(xorign, yorigin, width, height, cornerRadius);
 * 
 *    // Now define the shape.
 * 
 *    FSDefineShape3 rect = path.defineShape(movie.newIdentifier());
 *
 *</pre>
 *
 */
public class FSShapeConstructor
{
    private double flattenLimit = 0.25;
   /** 
     * The COORDINATES_ARE_PIXELS flag controls whether the coordinates passed to methods when creating 
     * a path of predefined shape are expressed in pixels (true) or twips (false).
     *
     * Flash coordinates are specified in twips (1 twip equals 1/1440th of an inch or 1/20th of a point). 
     * Allowing coordinates to be specified in pixels simplifies the drawing process avoiding the conversion 
     * to twips by multiplying each value by 20.
     *
     * IMPORTANT: The value of this flag is independent of the COORDINATES_ARE_PIXELS flag specified in 
     * the Transform class of the Transform SWF framework.
     */
    public boolean COORDINATES_ARE_PIXELS = false;
    
    private double[] Px = new double[4];
    private double[] Py = new double[4];
    
    private boolean pathInProgress = false;
    
    private int initialX = 0;
    private int initialY = 0;
    
    private int currentX = 0;
    private int currentY = 0;
    
    private int controlX = 0;
    private int controlY = 0;

    private int minX = 0;
    private int minY = 0;
    private int maxX = 0;
    private int maxY = 0;
    
    private int lineWidth = 0;
    
    ArrayList objects = new ArrayList();
    ArrayList lineStyles = new ArrayList();
    ArrayList fillStyles = new ArrayList();
    
    /**
     * Creates an FSShapeConstructor object with no path defined.
     */
    public FSShapeConstructor()
    {
    }
    
    /** Sets the line style at the index in the array of line styles.

        @param index the position in the array of line styles.
        @param aLineStyle and FSSolidLine object.
        @throws ArrayIndexOutOfBounds if the index &lt; 0 or index &gt; array size.
        */
    public void set(int index, FSLineStyle aLineStyle)
    {
        lineStyles.set(index, aLineStyle);
    }

    /** Add a FSSolidLine object to the array of line styles.

        @param aLineStyle and FSSolidLine object.
        */
    public void add(FSLineStyle aLineStyle)
    {
        lineStyles.add(aLineStyle);
    }

    /** Sets the fill style at the index in the array of fill styles.

        @param index the position in the array of fill styles.
        @param aFillStyle an FSFillStyle object.
        @throws ArrayIndexOutOfBounds if the index &lt; 0 or index &gt; array size.
        */
    public void set(int index, FSFillStyle aFillStyle)
    {
        fillStyles.set(index, aFillStyle);
    }

    /** Add the fill style object to the array of fill styles.

        @param aFillStyle and FSFillStyle object.
        */
    public void add(FSFillStyle aFillStyle)
    {
        fillStyles.add(aFillStyle);
    }
    
    /** Gets the array line styles.

        @return the line styles used in the shape.
        */
    public ArrayList getLineStyles() 
    { 
        return lineStyles; 
    }

    /** Sets the line styles.

        @param anArray set the line styles for the shape.
        */
    public void setLineStyles(ArrayList anArray)
    {
        lineStyles = anArray;
    }

    /** Gets the array fill styles.

        @return the fill styles used in the shape.
        */
    public ArrayList getFillStyles()
    {
        return fillStyles;
    }

    /** Sets the fill styles.

        @param anArray set the fill styles for the shape.
        */
    public void setFillStyles(ArrayList anArray)
    {
        fillStyles = anArray;
    }

    /**
     * Selects the style used to draw the outline of the shape from the array of 
     * line styles.
     * 
     * @param index the position of the style used to draw the shape.
     */
    public void selectLineStyle(int index)
    {
        lineWidth = ((FSSolidLine)lineStyles.get(index)).getWidth();

        objects.add(new FSShapeStyle(index+1, Transform.VALUE_NOT_SET, Transform.VALUE_NOT_SET));
    }
    
    /**
     * Selects the style used to fill the contents of the shape from the array of 
     * fill styles.
     * 
     * @param index the position of the style used to fill the shape.
     */
    public void selectFillStyle(int index)
    {
        objects.add(new FSShapeStyle(Transform.VALUE_NOT_SET, index+1, Transform.VALUE_NOT_SET));
    }
    
    /**
     * Selects the style used to fill overlapping areas of the shape from the 
     * array of fill styles.
     * 
     * @param index the position of the style used to fill overlapping areas.
     */
    public void selectAltStyle(int index)
    {
        objects.add(new FSShapeStyle(Transform.VALUE_NOT_SET, Transform.VALUE_NOT_SET, index+1));
    }
    
    /**
     * Selects the styles used to draw the outline and fill the contents of the 
     * shape from the array of line and fill styles.
     * 
     * @param lineIndex the position of the style used to draw the outline of the shape.
     * @param fillIndex the position of the style used to fill the shape.
     */
    public void selectStyle(int lineIndex, int fillIndex)
    {
        lineWidth = ((FSSolidLine)lineStyles.get(lineIndex)).getWidth();
        
        objects.add(new FSShapeStyle(lineIndex+1, fillIndex+1, Transform.VALUE_NOT_SET));
    }
    
    /**
     * Selects the styles used to draw the outline and fill the contents of the 
     * shape, including overlapping area, from the array of line and fill styles.
     * 
     * @param lineIndex the position of the style used to draw the outline of the shape.
     * @param fillIndex the position of the style used to fill the shape.
     * @param altIndex the position of the style used to fill overlapping areas of the shape.
     */
    public void selectStyle(int lineIndex, int fillIndex, int altIndex)
    {
        lineWidth = ((FSSolidLine)lineStyles.get(lineIndex)).getWidth();
 
        objects.add(new FSShapeStyle(lineIndex+1, fillIndex+1, altIndex+1));
    }
    
    /**
     * Generates the bounding box that encloses the current path.
     *
     * @return an FSBounds object representing the bounding box that encloses the path.
     */
    public FSBounds bounds()
    {
        return new FSBounds(minX, minY, maxX, maxY);
    }

    /**
     * Generates an FSShape object containing the objects used to draw the current path.
     *
     * @return an FSShape object contain the FSLine, FSCurve and FSShapeStyle objects used to 
     * construct the current path.
     */
    public FSShape shape()
    {
        return new FSShape((ArrayList)objects.clone());
    }

    /**
     * Generates a shape containing the current path and styles.
     *
     * The shape is constructed with copies of the style arrays and the shape representing the 
     * path drawn. This allows the number of styles to be changed without affecting previously
     * created shapes.
     *
     * @param identifier an unique identifier for the shape.
     */
     public FSDefineShape2 defineShape(int identifier)
     {
        return new FSDefineShape2(identifier, bounds(), (ArrayList)fillStyles.clone(), (ArrayList)lineStyles.clone(), new FSShape((ArrayList)objects.clone()));
     }
     
    /**
     * Generates a transparent shape containing the current path and styles.
     *
     * The shape is constructed with copies of the style arrays and the shape representing the 
     * path drawn. This allows the number of styles to be changed without affecting previously
     * created shapes.
     *
     * @param identifier an unique identifier for the shape.
     */
     public FSDefineShape3 defineTransparentShape(int identifier)
     {
        return new FSDefineShape3(identifier, bounds(), (ArrayList)fillStyles.clone(), (ArrayList)lineStyles.clone(), new FSShape((ArrayList)objects.clone()));
     }
     
    /**
     * Creates a new path, discarding any path elements drawn.
     */
    public void newPath()
    {
        pathInProgress = false;
        
        setInitial(0, 0);
        setCurrent(0, 0);
        setControl(0, 0);
        setBounds(0, 0, 0, 0);
        
        objects.clear();

        lineWidth = 0;
    }
    
    /**
     * Closes the current path by drawing a line from the current point to the starting point of the 
     * path. 
     */
    public void closePath()
    {
        int dx = initialX - currentX;
        int dy = initialY - currentY;
        
        if (dx != 0 || dy != 0)
            objects.add(new FSLine(dx, dy));
            
        setCurrent(initialX, initialY);
        pathInProgress = false;
    }
    
    /**
     * move to the point (x,y).
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * @param x the x-coordinate of the point to move to.
     * @param y the y-coordinate of the point to move to.
     */
    public void move(int x, int y)
    {
        x = COORDINATES_ARE_PIXELS ? x * 20 : x;
        y = COORDINATES_ARE_PIXELS ? y * 20 : y;
        
        objects.add(new FSShapeStyle(x, y));

        setControl((currentX+x)/2, (currentY+y)/2);
        setCurrent(x, y);
        setInitial(x, y);
    }
    
    void moveForFont(int x, int y)
    {
        x = COORDINATES_ARE_PIXELS ? x * 20 : x;
        y = COORDINATES_ARE_PIXELS ? y * 20 : y;
        
        objects.add(new FSShapeStyle(1, Transform.VALUE_NOT_SET, 1, x, y));

        setControl((currentX+x)/2, (currentY+y)/2);
        setCurrent(x, y);
        setInitial(x, y);
    }
  
    /**
     * move relative to the current point.
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * @param x the distance along the x-axis.
     * @param y the distance along the y-axis.
     */
    public void rmove(int x, int y)
    {
        x = COORDINATES_ARE_PIXELS ? x * 20 : x;
        y = COORDINATES_ARE_PIXELS ? y * 20 : y;
        
        objects.add(new FSShapeStyle(x+currentX, y+currentY)); 

        setControl(currentX+x/2, currentY+y/2);
        setCurrent(currentX+x, currentY+y);
   }
    
    /**
     * draw a line from the current point to the point (x,y).
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * @param x the x-coordinate of the end of the line.
     * @param y the y-coordinate of the end of the line.
     */
    public void line(int x, int y)
    {
        x = (COORDINATES_ARE_PIXELS ? x * 20 : x) - currentX;
        y = (COORDINATES_ARE_PIXELS ? y * 20 : y) - currentY;

        objects.add(new FSLine(x, y));
        
        if (pathInProgress == false)
        {
            setInitial(currentX, currentY);
            pathInProgress = true;
        }
        setControl(currentX+x/2, currentY+y/2);
        setCurrent(currentX+x, currentY+y);
    }
    
    /**
     * draw a line relative to the current point.
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * @param x the distance along the x-axis to the end of the line.
     * @param y the distance along the y-axis to the end of the line.
     */
    public void rline(int x, int y)
    {
        x = COORDINATES_ARE_PIXELS ? x * 20 : x;
        y = COORDINATES_ARE_PIXELS ? y * 20 : y;

        objects.add(new FSLine(x, y));

        if (pathInProgress == false)
        {
            setInitial(currentX, currentY);
            pathInProgress = true;
        }
        setControl(currentX+x/2, currentY+y/2);
        setCurrent(currentX+x, currentY+y);
    }
    
    /**
     * draw a quadratic bezier curve from the current point to the point (x,y) with the control 
     * point (x1, y1).
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * @param x1 the x-coordinate of the control point.
     * @param y1 the y-coordinate of the control point.
     * @param x the x-coordinate of the end of the curve.
     * @param y the y-coordinate of the end of the curve.
     */
    public void curve(int x1, int y1, int x, int y)
    {
        x1 = (COORDINATES_ARE_PIXELS ? x1 * 20 : x1) - currentX;
        y1 = (COORDINATES_ARE_PIXELS ? y1 * 20 : y1) - currentY;
        x = (COORDINATES_ARE_PIXELS ? x * 20 : x) - currentX - x1;
        y = (COORDINATES_ARE_PIXELS ? y * 20 : y) - currentY - y1;

        objects.add(new FSCurve(x1, y1, x, y));

        if (pathInProgress == false)
        {
            setInitial(currentX, currentY);
            pathInProgress = true;
        }
        setControl(currentX+x1, currentY+y1);
        setCurrent(currentX+x1+x, currentY+y1+y);
    }
    
    /**
     * draw a quadratic bezier curve relative to the current point to the point.
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * @param x1 the distance along the x-axis from the current point to the control point.
     * @param y1 the distance along the y-axis from the current point to the control point.
     * @param x the distance along the x-axis from the current point to the end of the curve.
     * @param y the distance along the y-axis from the current point to the end of the curve.
     */
    public void rcurve(int x1, int y1, int x, int y)
    {
        x1 = COORDINATES_ARE_PIXELS ? x1 * 20 : x1;
        y1 = COORDINATES_ARE_PIXELS ? y1 * 20 : y1;
        x = COORDINATES_ARE_PIXELS ? x * 20 : x;
        y = COORDINATES_ARE_PIXELS ? y * 20 : y;

        objects.add(new FSCurve(x1, y1, x, y));

        if (pathInProgress == false)
        {
            setInitial(currentX, currentY);
            pathInProgress = true;
        }
 
        setControl(currentX+x1, currentY+y1);
        setCurrent(currentX+x1+x, currentY+y1+y);
    }
    
    /**
     * draw a cubic bezier curve from the current point to the point (x,y) with the off-curve control 
     * points (x1, y1) and (x2, y2).
     * 
     * IMPORTANT: Converting cubic bezier curves to the quadratic bezier curves supported by Flash is 
     * mathematically difficult. The cubic curve is approximated by a series of straight line segments. 
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * @param x1 the x-coordinate of the first control point.
     * @param y1 the y-coordinate of the first control point.
     * @param x2 the x-coordinate of the second control point.
     * @param y2 the y-coordinate of the second control point.
     * @param x the x-coordinate of the end of the curve.
     * @param y the y-coordinate of the end of the curve.
     */
    public void curve(int x1, int y1, int x2, int y2, int x, int y)
    {
        Px[0] = currentX;
        Py[0] = currentY;
        Px[1] = COORDINATES_ARE_PIXELS ? x1 * 20 : x1;
        Py[1] = COORDINATES_ARE_PIXELS ? y1 * 20 : y1;
        Px[2] = COORDINATES_ARE_PIXELS ? x2 * 20 : x2;
        Py[2] = COORDINATES_ARE_PIXELS ? y2 * 20 : y2;
        Px[3] = COORDINATES_ARE_PIXELS ? x * 20 : x;
        Py[3] = COORDINATES_ARE_PIXELS ? y * 20 : y;
        
        flatten();
    }
    
    /**
     * draw a cubic bezier curve relative to the current point.
     * 
     * IMPORTANT: Converting cubic bezier curves to the quadratic bezier curves supported by Flash is 
     * mathematically difficult. The cubic curve is approximated by a series of straight line segments. 
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * @param x1 the distance along the x-axis from the current point to the first control point.
     * @param y1 the distance along the y-axis from the current point to the first control point.
     * @param x2 the distance along the x-axis from the current point to the second control point.
     * @param y2 the distance along the y-axis from the current point to the second control point.
     * @param x the distance along the x-axis from the current point to the end of the curve.
     * @param y the distance along the y-axis from the current point to the end of the curve.
     */
    public void rcurve(int x1, int y1, int x2, int y2, int x, int y)
    {
        Px[0] = currentX;
        Py[0] = currentY;
        Px[1] = currentX + (COORDINATES_ARE_PIXELS ? x1 * 20 : x1);
        Py[1] = currentY + (COORDINATES_ARE_PIXELS ? y1 * 20 : y1);
        Px[2] = currentX + (COORDINATES_ARE_PIXELS ? x2 * 20 : x2);
        Py[2] = currentY + (COORDINATES_ARE_PIXELS ? y2 * 20 : y2);
        Px[3] = currentX + (COORDINATES_ARE_PIXELS ? x * 20 : x);
        Py[3] = currentY + (COORDINATES_ARE_PIXELS ? y * 20 : y);
        
        flatten();
    }
    
    /**
     * draw a quadratic bezier curve from the current point to the point (x,y) using the control point
     * for the previously drawn curve.
     * 
     * If no curve has been drawn previously then a control point midway along the previous line or 
     * move is used.
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * @param x the x-coordinate of the end of the curve.
     * @param y the y-coordinate of the end of the curve.
     */
    public void reflect(int x, int y)
    {
        int x1 = currentX - controlX;
        int y1 = currentY - controlY;
        
        x = (COORDINATES_ARE_PIXELS ? x * 20 : x) - currentX;
        y = (COORDINATES_ARE_PIXELS ? y * 20 : y) - currentY;

        objects.add(new FSCurve(x1, y1, x, y));

        if (pathInProgress == false)
        {
            setInitial(currentX, currentY);
            pathInProgress = true;
        }
 
        setControl(x1+currentX, y1+currentY);
        setCurrent(x+currentX, y+currentY);
    }

    /**
     * draw a quadratic bezier curve relative to the current point to the point using the control point
     * for the previously drawn curve.
     * 
     * If no curve has been drawn previously then a control point midway along the previous line or 
     * move is used.
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * @param x the distance along the x-axis from the current point to the end of the curve.
     * @param y the distance along the y-axis from the current point to the end of the curve.
     */
    public void rreflect(int x, int y)
    {
        int x1 = currentX - controlX;
        int y1 = currentY - controlY;

        x = COORDINATES_ARE_PIXELS ? x * 20 : x;
        y = COORDINATES_ARE_PIXELS ? y * 20 : y;

        objects.add(new FSCurve(x1, y1, x, y));

        if (pathInProgress == false)
        {
            setInitial(currentX, currentY);
            pathInProgress = true;
        }
 
        setControl(x1+currentX, y1+currentY);
        setCurrent(x+currentX, y+currentY);
    }

    /**
     * draw a cubic bezier curve from the current point to the point (x,y). The first control point
     * is the one defined for the previously drawn curve. The second control point is the 
     * coordinates (x2, y2).
     * 
     * If no curve has been drawn previously then a control point midway along the previous line or 
     * move is used.
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * @param x2 the x-coordinate of the control point.
     * @param y2 the y-coordinate of the control point.
     * @param x the x-coordinate of the end of the curve.
     * @param y the y-coordinate of the end of the curve.
     */
    public void reflect(int x2, int y2, int x, int y)
    {
        int x1 = currentX - controlX;
        int y1 = currentY - controlY;

        x2 = (COORDINATES_ARE_PIXELS ? x2 * 20 : x2) - currentX;
        y2 = (COORDINATES_ARE_PIXELS ? y2 * 20 : y2) - currentY;

        x = (COORDINATES_ARE_PIXELS ? x * 20 : x) - currentX;
        y = (COORDINATES_ARE_PIXELS ? y * 20 : y) - currentY;

        rcurve(x1, y1, x2, y2, x, y);
    }

    /**
     * draw a cubic bezier curve relative to the current point. The first control point
     * is the one defined for the previously drawn curve. The second control point is the 
     * relative point (x2, y2).
     * 
     * If no curve has been drawn previously then a control point midway along the previous line or 
     * move is used.
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * @param x2 the distance along the x-axis from the current point to the second control point.
     * @param y2 the distance along the y-axis from the current point to the second control point.
     * @param x the distance along the x-axis from the current point to the end of the curve.
     * @param y the distance along the y-axis from the current point to the end of the curve.
     */
    public void rreflect(int x2, int y2, int x, int y)
    {
        int x1 = currentX - controlX;
        int y1 = currentY - controlY;

        x2 = COORDINATES_ARE_PIXELS ? x2 * 20 : x2;
        y2 = COORDINATES_ARE_PIXELS ? y2 * 20 : y2;

        x = COORDINATES_ARE_PIXELS ? x * 20 : x;
        y = COORDINATES_ARE_PIXELS ? y * 20 : y;

        rcurve(x1, y1, x2, y2, x, y);
    }

    /**
     * Creates a closed path in the shape of a rectangle with the specified width and height.
     * The centre of the rectangle is located at the point (x,y).
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * The origin of the shape can be used to control the relative placement of the rectangle 
     * when it is placed on the Flash Player's display list using either the FSPlaceObject or 
     * FSPlaceObject2 class.
     * 
     * @param x the x-coordinate of the centre of the rectangle.
     * @param y the y-coordinate of the centre of the rectangle.
     * @param width the width of the rectangle.
     * @param height the height of the rectangle.
     */
    public void rect(int x, int y, int width, int height)
    {
        newPath();
        selectStyle(0, 0);
        move(x-width/2, y-height/2);
        rline(width, 0);
        rline(0, height);
        rline(-width, 0);
        rline(0, -height);
        closePath();
    }

    /**
     * Creates a closed path in the shape of a rectangle with the specified width and height.
     * The centre of the rectangle is located at the point (0,0).
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * @param width the width of the rectangle.
     * @param height the height of the rectangle.
     */
    public void rect(int width, int height)
    {
        rect(0, 0, width, height);
    }

    /**
     * Creates a closed path in the shape of a rectangle with rounded corners. The shape is drawn
     * with specified width and height and the radius argument specified the radius of the quarter 
     * circle used to draw the corners.
     * 
     * The centre of the rectangle is located at the point (x,y).
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * The origin of the shape can be used to control the relative placement of the rectangle 
     * when it is placed on the Flash Player's display list using either the FSPlaceObject or 
     * FSPlaceObject2 class.
     * 
     * @param x the x-coordinate of the centre of the rectangle.
     * @param y the y-coordinate of the centre of the rectangle.
     * @param width the width of the rectangle.
     * @param height the height of the rectangle.
     * @param radius the radius of the quarter circle used to draw the corners.
     */
    public void rect(int x, int y, int width, int height, int radius)
    {
        int shortestSide = (height < width) ? height : width;
    
        if (radius > shortestSide/2)
            radius = shortestSide/2;
    
        newPath();
        selectStyle(0, 0);
        move(x, y-height/2);
        rline(width/2-radius, 0);
        rcurve(radius, 0, 0, radius);
        rline(0, height - 2*radius);
        rcurve(0, radius, -radius, 0);
        rline(-(width-2*radius), 0);
        rcurve(-radius, 0, 0, -radius);
        rline(0, -(height-2*radius));
        rcurve(0, -radius, radius, 0);
        closePath();
    }

    /**
     * Creates a closed path in the shape of a rectangle with rounded corners. The shape is drawn
     * with specified width and height and the radius argument specified the radius of the quarter 
     * circle used to draw the corners. The centre of the rectangle is located at the point (0,0).
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * @param width the width of the rectangle.
     * @param height the height of the rectangle.
     * @param radius the radius of the quarter circle used to draw the corners.
     */
    public void rect(int width, int height, int radius)
    {
        rect(0, 0, width, height, radius);
    }

    /**
     * Creates a closed path in the shape of an ellipse. The arguments rx and ry specify the radius 
     * of the ellipse in the x and y directions respectively.
     * 
     * The centre of the ellipse is located at the point (x,y).
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * The origin of the shape can be used to control the relative placement of the ellipse 
     * when it is placed on the Flash Player's display list using either the FSPlaceObject or 
     * FSPlaceObject2 class.
     * 
     * @param x the x-coordinate of the centre of the ellipse.
     * @param y the y-coordinate of the centre of the ellipse.
     * @param rx the radius of the ellipse in the x direction.
     * @param ry the radius of the ellipse in the y direction.
     */
    public void ellipse(int x, int y, int rx, int ry)
    {
        boolean wasInPixels = false;

        if (COORDINATES_ARE_PIXELS)
        {
            COORDINATES_ARE_PIXELS = false;
            wasInPixels = true;
            
            x *= 20;
            y *= 20;
            rx *= 20;
            ry *= 20;
        }

        int startX = (int) (0.707 * rx) + x;
        int startY = (int) (0.707 * ry) + y;

        int ax = (int) (0.293 * rx);
        int ay = (int) (0.293 * ry);
        int cx = (int) (0.414 * rx);
        int cy = (int) (0.414 * ry);
            
        newPath();
        selectStyle(0, 0);
        move(startX, startY);
        rcurve(-ax, ay, -cx, 0);
        rcurve(-cx, 0, -ax, -ay);
        rcurve(-ax, -ay, 0, -cy);
        rcurve(0, -cy,  ax, -ay);
        rcurve(ax, -ay, cx, 0);
        rcurve(cx, 0,  ax, ay);
        rcurve(ax, ay, 0, cy);
        rcurve(0, cy, -ax, ay);
        closePath();
        
        if (wasInPixels)
        {
            COORDINATES_ARE_PIXELS = true;
        }
    }

    /**
     * Creates a closed path in the shape of an ellipse. The arguments rx and ry specify the radius 
     * of the ellipse in the x and y directions respectively.
     * 
     * The centre of the ellipse is located at the point (0,0).
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * @param rx the radius of the ellipse in the x direction.
     * @param ry the radius of the ellipse in the y direction.
     */
    public void ellipse(int rx, int ry)
    {
        ellipse(0, 0, rx, ry);
    }

    /**
     * Creates a closed path in the shape of a circle. The centre of the circle is located at 
     * the point (x,y) with radius r.
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * The origin of the shape can be used to control the relative placement of the circle 
     * when it is placed on the Flash Player's display list using either the FSPlaceObject or 
     * FSPlaceObject2 class.
     * 
     * @param x the x-coordinate of the centre of the circle.
     * @param y the y-coordinate of the centre of the circle.
     * @param r the radius of the circle.
     */
    public void circle(int x, int y, int r)
    {
        ellipse(x, y, r, r);
    }

    /**
     * Creates a closed path in the shape of a circle. The centre of the circle is located at 
     * the point (0,0) with radius r.
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * @param r the radius of the circle.
     */
    public void circle(int r)
    {
        ellipse(0, 0, r, r);
    }

    /**
     * Create a closed shape with vertices defines by pairs of coordinates from the array argument.
     * The first pair of points in the array specifies a move. Line segments a drawn relative to 
     * the current point which is updated after each segment is drawn.
     * 
     * If the number of points is an odd number then the last point will be ignored.
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * @param points and array of coordinate pairs. The first pair of points defines the coordinates
     * of a move operation, successive pairs define the coordinates for relative lines.
     */
    public void rpolygon(int[] points)
    {
        int i;
        int length = points.length;
        
        if (length % 2 == 1)
            length -= 1;
        
        rmove(points[0], points[1]);

        for (i=2; i<length; i+=2)
            rline(points[i], points[i+1]);
            
        closePath();
    }
    /**
     * Create a closed shape with vertices defines by pairs of coordinates from the array argument.
     * The first pair of points in the array specifies a move. Line segments a drawn using 
     * abolute coordinates. The current point which is updated after each segment is drawn.
     * 
     * If the number of points is an odd number then the last point will be ignored.
     * 
     * If the COORDINATES_ARE_PIXELS attribute is true then the coordinates are specified in pixels,
     * otherwise the coordinates are specified in twips.
     * 
     * @param points and array of coordinate pairs. The first pair of points defines the coordinates
     * of a move operation, successive pairs define the coordinates of the lines.
     */
    public void polygon(int[] points)
    {
        int i;
        int length = points.length;
        
        if (length % 2 == 1)
            length -= 1;
        
        move(points[0], points[1]);

        for (i=2; i<length; i+=2)
            line(points[i], points[i+1]);
            
        closePath();
    }

    private void setInitial(int x, int y)
    {
        initialX = x;
        initialY = y;
    }
    
    private void setCurrent(int x, int y)
    {
        currentX = x;
        currentY = y;
        
        if ((x - lineWidth/2) < minX) minX = x - lineWidth/2;
        if ((y - lineWidth/2) < minY) minY = y - lineWidth/2;
        if ((x + lineWidth/2) > maxX) maxX = x + lineWidth/2;
        if ((y + lineWidth/2) > maxY) maxY = y + lineWidth/2;
    }
    
    private void setControl(int x, int y)
    {
        controlX = x;
        controlY = y;

        if ((x - lineWidth/2) < minX) minX = x - lineWidth/2;
        if ((y - lineWidth/2) < minY) minY = y - lineWidth/2;
        if ((x + lineWidth/2) > maxX) maxX = x + lineWidth/2;
        if ((y + lineWidth/2) > maxY) maxY = y + lineWidth/2;
    }
    
    private void setBounds(int xl, int yl, int xu, int yu)
    {
        minX = xl;
        minY = yl;
        maxX = xu;
        maxY = yu;
    }
    
    private void flatten()
    {
        double[] Qx = new double[] {0.0, 0.0, 0.0, 0.0};
        double[] Qy = new double[] {0.0, 0.0, 0.0, 0.0};

        double u;
        double Ax, Ay, Bx, By;

        for (;;) 
        {
            Ax = 2.0 * Px[0] + Px[3] - 3.0 * Px[1];  Ax *= Ax;
            Bx = 2.0 * Px[3] + Px[0] - 3.0 * Px[2];  Bx *= Bx;
        
            if (Ax < Bx) Ax = Bx;

            Ay = 2.0 * Py[0] + Py[3] - 3.0 * Py[1];  Ay *= Ay;
            By = 2.0 * Py[3] + Py[0] - 3.0 * Py[2];  By*= By;
        
            if (Ay < By) Ay = By;

            if ((Ax + Ay) < flattenLimit) 
            {
                objects.add(new FSLine((int)(Px[3])-currentX, (int)(Py[3])-currentY));
                setControl((int)(Px[1]), (int)(Py[1]));
                setControl((int)(Px[2]), (int)(Py[2]));
                setCurrent((int)(Px[3]), (int)(Py[3]));
                break;
            }
            else 
            {
                Qx[3] = Px[3];
                u = (Px[1] + Px[2]) / 2;
                Px[1] = (Px[0] + Px[1]) / 2;
                Qx[2] = (Px[2] + Px[3]) / 2;
                Px[2] = (Px[1] + u) / 2;
                Qx[1] = (u + Qx[2]) / 2;
                Px[3] = Qx[0] = (Px[2] + Qx[1]) / 2;

                Qy[3] = Py[3];
                u = (Py[1] + Py[2]) / 2;
                Py[1] = (Py[0] + Py[1]) / 2;
                Qy[2] = (Py[2] + Py[3]) / 2;
                Py[2] = (Py[1] + u) / 2;
                Qy[1] = (u + Qy[2]) / 2;
                Py[3] = Qy[0] = (Py[2] + Qy[1]) / 2;

                flatten();
                  
                Px[0] = Qx[0];
                Py[0] = Qy[0];
                Px[1] = Qx[1];
                Py[1] = Qy[1];
                Px[2] = Qx[2];
                Py[2] = Qy[2];
                Px[3] = Qx[3];
                Py[3] = Qy[3];
                  
                continue;
            }
        }
    }
}

