////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2008 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

package spark.primitives
{

import flash.display.Graphics;
import flash.display.GraphicsPath;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;

import mx.core.mx_internal;
import mx.events.PropertyChangeEvent;
import mx.graphics.IStroke;
import mx.utils.MatrixUtil;

import spark.primitives.supportClasses.FilledElement;

use namespace mx_internal;

/**
 *  The Path class is a filled graphic element that draws a series of path segments.
 *  In vector graphics, a path is a series of points connected by straight or curved line segments. 
 *  Together the lines form an image. In Flex, you use the Path class to define a complex vector shape 
 *  constructed from a set of line segments. 
 * 
 *  <p>Typically, the first element of a path definition is a Move segment to specify the starting pen 
 *  position of the graphic. You then use the Line, CubicBezier and QuadraticBezier segments to  
 *  draw the lines of the graphic. When using these classes, you only specify the x and y coordinates 
 *  of the end point of the line; the x and y coordinate of the starting point is defined by the current 
 *  pen position.</p>
 *  
 *  <p>After drawing a line segment, the current pen position becomes the x and y coordinates of the end 
 *  point of the line. You can use multiple Move segments in the path definition to 
 *  reposition the pen.</p>
 *  
 *  <p>The syntax used by the Path class to define the shape is the same as the SVG path syntax, 
 *  which makes it easy to convert SVG paths to Flex paths.</p>
 *  
 *  @includeExample examples/ArrowExample.mxml
 *    
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
public class Path extends FilledElement
{
    include "../core/Version.as";
    
    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------
    
    /**
     *  Constructor. 
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function Path()
    {
        super();
    }
    
    //--------------------------------------------------------------------------
    //
    //  Variables
    //
    //--------------------------------------------------------------------------
    
    /**
     *  Dirty flag to indicate when path data has changed. 
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */ 
    private var graphicsPathChanged:Boolean = true;
    
    /**
     *  Private data structure to hold the parsed 
     *  path segment information  
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */ 
    private var segments:PathSegmentsCollection;
    
    /**
     *  A GraphicsPath object that contains the drawing 
     *  commands to draw this Path.  
     *  
     *  The data commands expressed in a Path's <code>data</code> 
     *  property are translated into drawing commands and 
     *  coordinate parameters for those commands, and then
     *  drawn to screen. 
     */ 
    mx_internal var graphicsPath:GraphicsPath = new GraphicsPath(new Vector.<int>(), new Vector.<Number>());
    
    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------
    
    //----------------------------------
    //  data
    //----------------------------------
    
    private var _data:String;
    
    [Inspectable(category="General")]
    
    /**
     *  A string containing a compact represention of the path segments. This is an alternate
     *  way of setting the segments property. Setting this property overrides any values
     *  stored in the segments array property.
     *
     *  <p>The value is a space-delimited string describing each path segment. Each
     *  segment entry has a single character which denotes the segment type and
     *  two or more segment parameters.</p>
     * 
     *  <p>If the segment command is upper-case, the parameters are absolute values.
     *  If the segment command is lower-case, the parameters are relative values.</p>
     *
     *  <p>The following table shows the syntax for the segments:
     *  
     *  
     *  <table class="innertable">
     *    <tr>
     *      <th>Segment Type</th>
     *      <th>Command</th>
     *      <th>Parameters</th>
     *      <th>Example</th>
     *    </tr>
     *    <tr>
     *      <td>Move</td>
     *      <td>M/m</td>
     *      <td>x y</td>
     *      <td><code>M 10 20</code> - Move line to 10, 20.</td>
     *    </tr>
     *    <tr>
     *      <td>Line</td>
     *      <td>L/l</td>
     *      <td>x y</td>
     *      <td><code>L 50 30</code> - Line to 50, 30.</td>
     *    </tr>
     *    <tr>
     *      <td>Horizontal line</td>
     *      <td>H/h</td>
     *      <td>x</td>
     *      <td><code>H 40</code> = Horizontal line to 40.</td>
     *    </tr>
     *    <tr>
     *      <td>Vertical line</td>
     *      <td>V/v</td>
     *      <td>y</td>
     *      <td><code>V 100</code> - Vertical line to 100.</td>
     *    </tr>
     *    <tr>
     *      <td>QuadraticBezier</td>
     *      <td>Q/q</td>
     *      <td>controlX controlY x y</td>
     *      <td><code>Q 110 45 90 30</code> - Curve to 90, 30 with the control point at 110, 45.</td>
     *    </tr>
     *    <tr>
     *      <td>CubicBezier</td>
     *      <td>C/c</td>
     *      <td>control1X control1Y control2X control2Y x y</td>
     *      <td><code>C 45 50 20 30 10 20</code> - Curve to 10, 20 with the first control point at 45, 50 and the second control point at 20, 30.</td>
     *    </tr>
     *    <tr>
     *      <td>Close path</td>
     *      <td>Z/z</td>
     *      <td>n/a</td>
     *      <td>Closes off the path.</td>
     *    </tr>
     *  </table>
     *  </p>
     *  
     *  @default null
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function set data(value:String):void
    {
        if (_data == value)
            return;
        
        segments = new PathSegmentsCollection(value);
        
        graphicsPathChanged = true;
        
        // Clear our cached measurement and data values
        clearCachedBoundingBoxWithStroke();
        invalidateSize();
        invalidateDisplayList();
        
        _data = value;
    }
    
    /** 
     *  @private
     */
    public function get data():String 
    {
        return _data;
    }
    
    //----------------------------------
    //  winding
    //----------------------------------
    
    private var _winding:String = "evenOdd";
    
    /**
     *  Fill rule for intersecting or overlapping path segments. 
     *  Possible values are <code>GraphicsPathWinding.EVEN_ODD</code> or <code>GraphicsPathWinding.NON_ZERO</code>.
     *
     *  @default evenOdd
     *  
     *  @see flash.display.GraphicsPathWinding 
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function set winding(value:String):void
    {
        if (_winding != value)
        {
            _winding = value;
            graphicsPathChanged = true;
            invalidateDisplayList();
        } 
    }
    
    /** 
     *  @private
     */
    public function get winding():String 
    {
        return _winding; 
    }
    
    //----------------------------------
    //  bounds
    //----------------------------------
    
    private function getBounds():Rectangle
    {
        return segments ? segments.getBounds() : new Rectangle();
    }
    
    //--------------------------------------------------------------------------
    //
    //  Overridden methods
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    override protected function measure():void
    {
        var bounds:Rectangle = getBounds();
        measuredWidth = bounds.width;
        measuredHeight = bounds.height;
        measuredX = bounds.left;
        measuredY = bounds.top;
    }
    
    /**
     *  @private
     *  Storage for the cached bounding box for particular transformation and size.
     *  We are also caching the bounding box original x & y, so that we can reuse it
     *  when bounds are requested with same size and transform that only differs by offsets. 
     */
    private var _boundingBoxCached:Rectangle;
    private var _boundingBoxMatrixCached:Matrix;
    private var _boundingBoxWidthParamCached:Number;
    private var _boundingBoxHeightParamCached:Number;
    private var _boundingBoxX:Number;
    private var _boundingBoxY:Number;
    
    /**
     *  @private
     *  Returns the bounding box for the path including stroke, if the path is resized
     *  to the specified size and transformed with "m". 
     *  Pass null for "m" to specify identity matrix.
     *
     *  Calling this method multiple times with the same parameters, or differences
     *  only in the matrix offset is fast, as it returns the cached bounding box.
     * 
     *  Don't modify directly the return value!
     */
    private function getBoundingBoxWithStroke(width:Number, height:Number, m:Matrix):Rectangle
    {
        if (_boundingBoxCached && 
            _boundingBoxWidthParamCached == width && 
            _boundingBoxHeightParamCached == height)
        {
            // Compare matrices:
            if (!m && !_boundingBoxMatrixCached)
            {
                _boundingBoxCached.x = _boundingBoxX;
                _boundingBoxCached.y = _boundingBoxY;
                return _boundingBoxCached;
            }
            else if (m && _boundingBoxMatrixCached &&
                m.a == _boundingBoxMatrixCached.a &&
                m.b == _boundingBoxMatrixCached.b &&
                m.c == _boundingBoxMatrixCached.c &&
                m.d == _boundingBoxMatrixCached.d)
            {
                _boundingBoxCached.x = _boundingBoxX + m.tx;
                _boundingBoxCached.y = _boundingBoxY + m.ty;
                return _boundingBoxCached;
            }
        }
        
        // Setup the matrix, ignore tx & ty, we'll account for it later
        if (m)
        {
            _boundingBoxMatrixCached = m.clone();
            _boundingBoxMatrixCached.tx = 0;
            _boundingBoxMatrixCached.ty = 0;
        }
        else
            _boundingBoxMatrixCached = null;
        
        // Remember width and height
        _boundingBoxWidthParamCached = width;
        _boundingBoxHeightParamCached = height;
        
        _boundingBoxCached = computeBoundsWithStroke(_boundingBoxWidthParamCached,
            _boundingBoxHeightParamCached,
            m);
        
        // Remember the original x & y:
        _boundingBoxX = _boundingBoxCached.x - (m ? m.tx : 0);
        _boundingBoxY = _boundingBoxCached.y - (m ? m.ty : 0);
        
        return _boundingBoxCached; // No need to return clone, as this is for internal use only
    }
    
    /**
     *  @private
     *  Static storage for intermediate calculations while calculating miter-limit bounds. 
     */
    static private var tangent:Point = new Point();
    
    /**
     *  @private
     *  Returns true when we have a valid tangent for curSegment. Pass prevSegment
     *  to know what the starting point of curSegment is.
     */
    private function tangentIsValid(prevSegment:PathSegment, curSegment:PathSegment,
                                    sx:Number, sy:Number, m:Matrix):Boolean
    {
        // TODO (egeorgie): optimize, we don't need to compute the tangent,
        // but just make sure the segment is not collapsed into a single point?
        
        // Check the start tangent only. If it's valid,
        // then there is a valid end tangent as well.
        curSegment.getTangent(prevSegment, true, sx, sy, m, tangent);
        return (tangent.x != 0 || tangent.y != 0);
    }
    
    /**
     *  @private
     *  @return Returns the axis aligned bounding box of the path when
     *  resized to (width, height) and then transformed by matrix m.
     */
    mx_internal function computeBoundsWithStroke(width:Number,
                                                 height:Number,
                                                 m:Matrix):Rectangle
    {
        var naturalBounds:Rectangle = getBounds();
        var sx:Number = naturalBounds.width == 0 ? 1 : width / naturalBounds.width;
        var sy:Number = naturalBounds.height == 0 ? 1 : height / naturalBounds.height; 
        
        // First, figure out the bounding box without stroke
        var pathBBox:Rectangle;
        // Special case, if there's no transformation or only offset,
        // then the non-stroked path bounds for the give size can be
        // scaled from the pre-transform natural bounds:
        if (!m || MatrixUtil.isDeltaIdentity(m) || !this.segments)
        {
            pathBBox = new Rectangle(naturalBounds.x * sx,
                naturalBounds.y * sy,
                naturalBounds.width * sx,
                naturalBounds.height * sy);
            if (m)
                pathBBox.offset(m.tx, m.ty);
        }
        else
        {
            pathBBox = this.segments.getBoundingBox(width, height, m);
        }
        
        // Do we have stroke? 
        var strokeSettings:IStroke = this.stroke;
        if (!strokeSettings || !this.segments)
            return pathBBox;
        
        // Always add half the stroke weight, even for miter limit paths,
        // as a point on a curve and not necessarily a joint tip could be
        // an extreme that pushes the bounds.
        var strokeExtents:Rectangle = getStrokeExtents();
        pathBBox.inflate(strokeExtents.right, strokeExtents.bottom);
        
        var seg:Vector.<PathSegment> = segments.data;
        
        if (strokeSettings.joints != "miter" || seg.length < 2)
        {
            // TODO (egeorgie): Will overshoot for "bevel"
            // by the assumed roundness of the joints.
            return pathBBox;
        }
        
        // Use strokeExtents to get the transformed stroke weight.
        var halfWeight:Number = strokeExtents.width / 2;
        
        // Miter limit is always at least 1
        var miterLimit:Number = Math.max(1, strokeSettings.miterLimit);
        var count:int = seg.length;
        var start:int = 0;
        var end:int;
        var lastMoveX:Number = 0;
        var lastMoveY:Number = 0;
        var lastOpenSegment:int = 0;
        
        while (true)
        {
            // Find a segment with a valid tangent or stop at a MoveSegment
            while (start < count && !(seg[start] is MoveSegment))
            {
                var prevSegment:PathSegment = start > 0 ? seg[start - 1] : null;
                if (tangentIsValid(prevSegment, seg[start], sx, sy, m))
                    break;
                start++;
            }
            
            if (start >= count)
                break; // No more segments with valid tangents
            
            var startSegment:PathSegment = seg[start];
            if (startSegment is MoveSegment)
            {
                // remember the last move segment 
                lastOpenSegment = start + 1;
                lastMoveX = startSegment.x;
                lastMoveY = startSegment.y;
                
                // move onto next segment:
                start++;
                continue;
            }
            
            // Does the current segment close to a previous segment and form a joint with it?
            // Note, even if the segment was originally a close segment, it may not form a joint
            // with the segment it closes to, unless it's followed by a MoveSegment or it's the last
            // segment in the sequence.
            if ((start == count - 1 || seg[start + 1] is MoveSegment) && 
                startSegment.x == lastMoveX &&
                startSegment.y == lastMoveY)
            {
                end = lastOpenSegment;
            }
            else
                end = start + 1;
            
            // Find a segment with a valid tangent or stop at a MoveSegment 
            while (end < count && !(seg[end] is MoveSegment))
            {
                if (tangentIsValid(startSegment, seg[end], sx, sy, m))
                    break;
                end++;
            }
            
            if (end >= count)
                break; // No more segments with valid tangents
            
            var endSegment:PathSegment = seg[end];
            
            if (!(endSegment is MoveSegment))
            {
                addMiterLimitStrokeToBounds(start > 0 ? seg[start - 1] : null, 
                    startSegment,
                    endSegment, 
                    miterLimit,
                    halfWeight,
                    sx,
                    sy,
                    m,
                    pathBBox);
            }
            
            // Move on to the next segment, but never go back. End could be less
            // than start, because of implicit/explicit CloseSegments.
            start = start > end ? start + 1 : end;
        }
        return pathBBox;
    }
    
    /**
     *  @private
     *  Returns the bounds of the element, including stroke in local coordinates.
     */  
    override protected function getStrokeBounds():Rectangle
    {
        return getBoundingBoxWithStroke(width, height, null);
    }
    
    /**
     *  @private 
     */
    override protected function get needsDisplayObject():Boolean
    {
        // Rendering with miter limit into the same DisplayObject will cause the
        // slow code-path Player execution for all graphics in that DisplayObject.
        // Make sure that we don't share the DisplayObject with other elements when 
        // we have stroke with miter joints.
        return super.needsDisplayObject || (stroke && stroke.joints == "miter");
    }
    
    /**
     *  @private
     *  Calculates the miter stroke contribution to the "result" bounding box.
     *  
     *  @param segment0 The segment before the first segment of the joint.
     *  @param segment1 The first segment of the joint.
     *  @param segment2 The second segment of the joint.
     * 
     *  @param miterLimit The miter limit. It must be at least 1. 
     * 
     *  @param weight The transformed stroke weight (the outside part only,
     *  if stroke is centered, this must be weight/2).
     * 
     *  @param sx The pre-transform horizontal scale factor for the segments.
     *  @param sy The pre-transform vertical scale factor for the segments.
     * 
     *  @param m The transformation for the segments.
     * 
     *  @param result The bounding box to be accumulating the bounds.
     */    
    private function addMiterLimitStrokeToBounds(segment0:PathSegment,
                                                 segment1:PathSegment,
                                                 segment2:PathSegment,
                                                 miterLimit:Number,
                                                 weight:Number,
                                                 sx:Number,
                                                 sy:Number,
                                                 m:Matrix,
                                                 result:Rectangle):void
    {
        // The tip of the joint
        var pt:Point;
        pt = MatrixUtil.transformPoint(segment1.x * sx, segment1.y * sy, m).clone();
        var jointX:Number = pt.x;
        var jointY:Number = pt.y;
        
        // End tangent for segment1:
        var t0:Point = new Point();
        segment1.getTangent(segment0, false /*start*/, sx, sy, m, t0);
        
        // Start tangent for segment2:
        var t1:Point = new Point();
        segment2.getTangent(segment1, true /*start*/, sx, sy, m, t1);
        
        // Valid tangents?
        if (t0.length == 0 || t1.length == 0)
            return;
        
        // The tip of the stroke lies on the bisector of the angle and lies at a distance
        // of weight / sin(A/2), where A is the angle between the tangents.
        
        // Quick and dirty way to calculate it, make the tangents unit length:
        t0.normalize(1);
        t0.x = -t0.x;
        t0.y = -t0.y;
        t1.normalize(1);
        
        // Find the vector from t0 to the midPoint from t0 to t1
        var halfT0T1:Point = new Point( (t1.x - t0.x) * 0.5, (t1.y - t0.y) * 0.5);
        
        // sin(A/2) == halfT0T1.length / t1.length()
        var sinHalfAlpha:Number = halfT0T1.length;
        if (Math.abs(sinHalfAlpha) < 1.0E-9)
        {
            // Don't count degenerate joints that are close to 0 degrees so
            // we avoid cases like this one L 0 0  0 50  100 0  30 0 50 0 Z
            return; 
        }
        
        // Find the vector of the bisect
        var bisect:Point = new Point( -0.5 * (t0.x + t1.x), -0.5 * (t0.y + t1.y) );
        if (bisect.length == 0)
        {
            // 180 degrees, nothing to contribute
            return;
        }
        
        // Is there miter limit at play?
        if (sinHalfAlpha == 0 || miterLimit < 1 / sinHalfAlpha)
        {
            // The miter limit is reached. Calculate two extra points that may
            // contribute to the bounds.
            // The points lie on the line perpendicular to the bisect and intersecting
            // it at offset of miterLimit * weight from the joint tip.
            // The points are equally offset from the bisect by a factor of X,
            // where X / sinAlpha == (weight / sinAlpha - miterLimit * weight) / bisect.lenght. 
            
            var bisectLength:Number = bisect.length;
            bisect.normalize(1);
            
            halfT0T1.normalize((weight - miterLimit * weight * sinHalfAlpha) / bisectLength);
            
            var pt0:Point = new Point(jointX + miterLimit * weight * bisect.x + halfT0T1.x,
                jointY + miterLimit * weight * bisect.y + halfT0T1.y);
            
            var pt1:Point = new Point(jointX + miterLimit * weight * bisect.x - halfT0T1.x,
                jointY + miterLimit * weight * bisect.y - halfT0T1.y);
            
            // Add it to the rectangle:
            MatrixUtil.rectUnion(pt0.x, pt0.y, pt0.x, pt0.y, result);
            MatrixUtil.rectUnion(pt1.x, pt1.y, pt1.x, pt1.y, result);
        }
        else
        {
            // miter limit is not reached, add the tip of the stroke
            bisect.normalize(1);
            var strokeTip:Point = new Point(jointX + bisect.x * weight / sinHalfAlpha,
                jointY + bisect.y * weight / sinHalfAlpha);
            
            // Add it to the rectangle:
            MatrixUtil.rectUnion(strokeTip.x, strokeTip.y, strokeTip.x, strokeTip.y, result);
        }
    }
    
    /**
     *  @private
     */
    override protected function transformWidthForLayout(width:Number,
                                                        height:Number,
                                                        postLayoutTransform:Boolean = true):Number
    {
        var m:Matrix = getComplexMatrix(postLayoutTransform);
        // Optimize for no-stroke, no transform
        if (!m && !stroke)
            return width;
        return getBoundingBoxWithStroke(width, height, m).width;
    }
    
    /**
     *  @private
     */
    override protected function transformHeightForLayout(width:Number,
                                                         height:Number,
                                                         postLayoutTransform:Boolean = true):Number
    {
        var m:Matrix = getComplexMatrix(postLayoutTransform);
        // Optimize for no-stroke, no transform
        if (!m && !stroke)
            return height;
        return getBoundingBoxWithStroke(width, height, m).height;
    }
    
    /**
     *  @private
     *  A helper function to implement the getBoundsXAtSize() and getBoundsYAtSize()
     *  mehtods. Calculates where the top-left corner of the bounds would end up 
     *  if the path is resized to the specified size.
     */
    private function getBoundsAtSize(width:Number, height:Number, m:Matrix):Rectangle
    {
        var strokeExtents:Rectangle = null;
        
        if (!isNaN(width))
        {
            strokeExtents = getStrokeExtents(true /*postLayoutTransform*/);
            width -= strokeExtents.width;
        }
        
        if (!isNaN(height))
        {
            if (!strokeExtents)
                strokeExtents = getStrokeExtents(true /*postLayoutTransform*/);
            height -= strokeExtents.height;
        }
        
        var newWidth:Number = preferredWidthPreTransform();
        var newHeight:Number = preferredHeightPreTransform();
        
        // Calculate the width and height pre-transform:
        if (m)
        {
            var newSize:Point = MatrixUtil.fitBounds(width,
                height,
                m,
                explicitWidth,
                explicitHeight,
                newWidth,
                newHeight,
                minWidth, minHeight,
                maxWidth, maxHeight);
            
            if (newSize)
            {
                newWidth = newSize.x;
                newHeight = newSize.y;
            }
            else
            {
                newWidth = minWidth;
                newHeight = minHeight;
            }
        }
        
        return getBoundingBoxWithStroke(newWidth, newHeight, m);
    }
    
    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    override public function getBoundsXAtSize(width:Number, height:Number, postLayoutTransform:Boolean = true):Number
    {
        var m:Matrix = getComplexMatrix(postLayoutTransform);
        return getBoundsAtSize(width, height, m).x + (m ? 0 : this.x);
    }
    
    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    override public function getBoundsYAtSize(width:Number, height:Number, postLayoutTransform:Boolean = true):Number
    {
        var m:Matrix = getComplexMatrix(postLayoutTransform);
        return getBoundsAtSize(width, height, m).y + (m ? 0 : this.y);
    }
    
    /**
     *  @private
     */
    override public function getLayoutBoundsX(postLayoutTransform:Boolean = true):Number
    {
        var m:Matrix = getComplexMatrix(postLayoutTransform);
        // Optimize for no-stroke, no transform
        if (!m && !stroke)
        {
            if (measuredX == 0)
                return this.x;
            var naturalBounds:Rectangle = getBounds();
            var sx:Number = (naturalBounds.width == 0 || _width == 0) ? 1 : _width / naturalBounds.width;
            return this.x + measuredX * sx;
        }
        return getBoundingBoxWithStroke(_width, _height, m).x + (m ? 0 : this.x);
    }
    
    /**
     *  @private
     */
    override public function getLayoutBoundsY(postLayoutTransform:Boolean = true):Number
    {
        var m:Matrix = getComplexMatrix(postLayoutTransform);
        // Optimize for no-stroke, no transform
        if (!m && !stroke)
        {
            if (measuredY == 0)
                return this.y;
            var naturalBounds:Rectangle = getBounds();
            var sy:Number = (naturalBounds.height == 0 || _height == 0) ? 1 : _height / naturalBounds.height;
            return this.y + measuredY * sy;
        }
        return getBoundingBoxWithStroke(_width, _height, m).y + (m ? 0 : this.y);
    }
    
    /**
     *  @private
     */    
    private function setLayoutBoundsTransformed(width:Number, height:Number, m:Matrix):void
    {
        var size:Point = fitLayoutBoundsIterative(width, height, m);
        
        // We couldn't find a solution, try to relax the constraints
        if (!size && !isNaN(width) && !isNaN(height))
        {
            // Try without width constraint
            var size1:Point = fitLayoutBoundsIterative(NaN, height, m);
            
            // Try without height constraint
            var size2:Point = fitLayoutBoundsIterative(width, NaN, m);
            
            // Ignore solutions that will exceeed the requested size
            if (size1 && getBoundingBoxWithStroke(size1.x, size1.y, m).width > width)
                size1 = null;
            if (size2 && getBoundingBoxWithStroke(size2.x, size2.y, m).height > height)
                size2 = null;
            
            // Which size was better?
            if (size1 && size2)
            {
                // Pick the one closest to the natural bounds ratio:
                //   ?  abs(n.x / n.y - size1.x / size1.y) < abs(n.x / n.y - size2.x / size2.y)
                // <=>  abs((n.x * size1.y - n.y * size1.x) / (n.y * size1.y)) < abs((n.x * size2.y - n.y * size2.x) / (n.y * size2.y))
                // <=>  abs((n.x * size1.y - n.y * size1.x)) / (n.y * size1.y) < abs((n.x * size2.y - n.y * size2.x)) / (n.y * size2.y) 
                // <=>  abs(n.x * size1.y - n.y * size1.x) / size1.y < abs(n.x * size2.y - n.y * size2.x) / size2.y
                // <=>  abs(n.x * size1.y - n.y * size1.x) * size2.y < abs(n.x * size2.y - n.y * size2.x) * size1.y

                var n:Point = getBounds().size;
                var pickSize1:Boolean = Math.abs( n.x * size1.y - n.y * size1.x ) * size2.y < 
                                        Math.abs( n.x * size2.y - n.y * size2.x ) * size1.y;

                if (pickSize1)
                    size = size1;
                else
                    size = size2;
            }
            else if (size1)
            {
                size = size1;
            }
            else
            {
                size = size2;
            }
        }
        
        if (size)
            setActualSize(size.x, size.y);
        else
            setActualSize(minWidth, minHeight);
    }
    
    /**
     *  Iteratively approach a solution. Returns 0 if no exact solution exists.
     *  NaN values for width/height mean "not constrained" in that dimesion. 
     * 
     *  @private
     */
    private function fitLayoutBoundsIterative(width:Number, height:Number, m:Matrix):Point
    {
        // Start from the natural bounds
        //var naturalBounds:Rectangle = getBounds();
        var newWidth:Number = this.preferredWidthPreTransform();
        var newHeight:Number = this.preferredHeightPreTransform();
        var fitWidth:Number = MatrixUtil.transformBounds(newWidth, newHeight, m).x;
        var fitHeight:Number = MatrixUtil.transformBounds(newWidth, newHeight, m).y;
        
        if (isNaN(width))
            fitWidth = NaN;
        if (isNaN(height))
            fitHeight = NaN;
        
        var i:int = 0;
        while (i++ < 150)
        {
            // Get current post-transform bounds
            var transformedBounds:Rectangle = getBoundingBoxWithStroke(newWidth, newHeight, m);
            
            var widthDistance:Number = isNaN(width) ? 0 : width - transformedBounds.width;
            var heightDistance:Number = isNaN(height) ? 0 : height - transformedBounds.height;
            if (Math.abs(widthDistance) < 0.1 && Math.abs(heightDistance) < 0.1)
            {
                return new Point(newWidth, newHeight);
            }
            
            // Try to fit bounds plus difference between
            fitWidth += widthDistance * 0.5;
            fitHeight += heightDistance * 0.5;
            
            var newSize:Point = MatrixUtil.fitBounds(fitWidth, 
                                                     fitHeight, 
                                                     m,
                                                     explicitWidth, 
                                                     explicitHeight,
                                                     preferredWidthPreTransform(),
                                                     preferredHeightPreTransform(),
                                                     minWidth, minHeight,
                                                     maxWidth, maxHeight);
            if (!newSize)
                break;

            newWidth = newSize.x;
            newHeight = newSize.y;
        }
        return null;
    }

    /**
     *  @private
     */
    override public function setLayoutBoundsSize(width:Number, height:Number, postLayoutTransform:Boolean=true):void
    {
        // We have special handling for miter-limit stroked non-transformed paths,
        // Otherwise we default to the generic GraphicElement sizing algorithm.
        if (isNaN(width) && isNaN(height))     // resetting to original size?
        {
            super.setLayoutBoundsSize(width, height, postLayoutTransform);
            return;
        }

        // Resize transformed path with the iterative solution
        var m:Matrix = getComplexMatrix(postLayoutTransform);
        if (m)
        {
            setLayoutBoundsTransformed(width, height, m);
            return;
        }

        if (!stroke || stroke.joints != "miter") // no stroke or no miter joints?
        {
            super.setLayoutBoundsSize(width, height, postLayoutTransform);
            return;
        }

        // Miter limit requires special handling since the stroke extents depend on
        // the path size as the size changes the angles of the joints.
        var newWidth:Number = preferredWidthPreTransform();
        var newHeight:Number = preferredHeightPreTransform();
        var bestWidth:Number;
        var bestHeight:Number;
        var bestScore:Number;
        
        // Special case if we are constrained in both dimensions
        if (!isNaN(width) && !isNaN(height))
        {
            var size:Point = fitLayoutBoundsIterative(width, height, new Matrix());
            if (size)
            {
                setActualSize(size.x, size.y);
                return;
            }

            // If we didn't find a solution, start from the natural bounds
            newWidth = getBounds().width;
            newHeight = getBounds().height;
            bestWidth = this.minWidth;
            bestHeight = this.minHeight;
            bestScore = (width - bestWidth) * (width - bestWidth) + (height - bestHeight) * (height - bestHeight);
        }

        var i:int = 0;
        while (i++ < 150)
        {
            var boundsWithStroke:Rectangle = getBoundingBoxWithStroke(newWidth, newHeight, null);
            
            var widthProximity:Number = 0;
            var heightProximity:Number = 0;
            if (!isNaN(width))
                widthProximity = Math.abs(width - boundsWithStroke.width);
            if (!isNaN(height))
                heightProximity = Math.abs(height - boundsWithStroke.height);
            
            if (!isNaN(width) && !isNaN(height))
            {
                var score:Number = (width - boundsWithStroke.width) * (width - boundsWithStroke.width) + 
                                   (height - boundsWithStroke.height) * (height - boundsWithStroke.height);
                
                if (!isNaN(score) && score < bestScore && boundsWithStroke.width <= width && boundsWithStroke.height <= height)
                {
                    bestScore = score;
                    bestWidth = newWidth;
                    bestHeight = newHeight;
                }
            }
            
            // Are we close enough? We want sub-pixel difference
            if (widthProximity < 1e-5 && heightProximity < 1e-5)
            {
                setActualSize(newWidth, newHeight);
                return;
            }
            
            var boundsWithoutStroke:Rectangle = segments.getBoundingBox(newWidth, newHeight, null);
            var strokeWidth:Number = boundsWithStroke.width - boundsWithoutStroke.width;
            var strokeHeight:Number = boundsWithStroke.height - boundsWithoutStroke.height;

            if (!isNaN(width))
                newWidth = width - strokeWidth;
            
            if (!isNaN(height))
                newHeight = height - strokeHeight;
        }

        setActualSize(bestWidth, bestHeight);
    }
    
    /**
     *  @private
     *  Use measuredX and measuredY instead of drawX and drawY
     */
    override protected function beginDraw(g:Graphics):void
    {
        // Don't call super.beginDraw() since it will also set up an 
        // invisible fill.
        
        // Adjust the position by the internal scale factor
        var naturalBounds:Rectangle = getBounds();
        var sx:Number = naturalBounds.width == 0 ? 1 : width / naturalBounds.width;
        var sy:Number = naturalBounds.height == 0 ? 1 : height / naturalBounds.height; 
        
        var origin:Point = new Point(drawX, drawY);
        var bounds:Rectangle = new Rectangle(
                                    drawX + measuredX * sx,
                                    drawY + measuredY * sy,
                                    width, 
                                    height);
        if (stroke)
        {
            var strokeBounds:Rectangle = getStrokeBounds();
            // Objects drawn in shared display objects are drawn at x,y rather
            // than 0,0 so need to move the strokeBounds if sharing.
            strokeBounds.offsetPoint(origin);
            stroke.apply(g, strokeBounds, origin);
        }
        else
        {
            g.lineStyle();
        }
        
        if (fill)
            fill.begin(g, bounds, origin);
    }
    
    private var _drawBounds:Rectangle = new Rectangle(); 
    
    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */    
    override protected function draw(g:Graphics):void
    {
        if (drawX !=  _drawBounds.x || drawY !=  _drawBounds.y ||
            width !=  _drawBounds.width || height !=  _drawBounds.height)
        {
            graphicsPathChanged = true;
            _drawBounds.x = drawX;
            _drawBounds.y = drawY;
            _drawBounds.width = width;
            _drawBounds.height = height;            
        }
        
        if (graphicsPathChanged)
        {
            var rcBounds:Rectangle = getBounds();
            var sx:Number = rcBounds.width == 0 ? 1 : width / rcBounds.width;
            var sy:Number = rcBounds.height == 0 ? 1 : height / rcBounds.height;
            if (segments)
                segments.generateGraphicsPath(graphicsPath, drawX, drawY, sx, sy);
            graphicsPathChanged = false;
        }
        
        g.drawPath(graphicsPath.commands, graphicsPath.data, winding);
    }
    
    /**
     * @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    override protected function endDraw(g:Graphics):void
    {
        // Set a transparent line style because filled, unclosed shapes will
        // automatically be closed by the Player. When they are, we want the line
        // to be invisible. 
        g.lineStyle();
        super.endDraw(g);
    } 
    
    
    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @inheritDoc
     * 
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    override protected function invalidateDisplayObjectSharing():void
    {
        graphicsPathChanged = true;
        super.invalidateDisplayObjectSharing();
    }
    
    /**
     *  @private
     */
    private function clearCachedBoundingBoxWithStroke():void
    {
        _boundingBoxCached = null;
        _boundingBoxMatrixCached = null;
    }
    
    /**
     *  @private
     *  Make sure we clear the cached boundingBoxWithStroke
     */
    override protected function stroke_propertyChangeHandler(event:PropertyChangeEvent):void
    {
        super.stroke_propertyChangeHandler(event);
        switch (event.property)
        {
            case "weight":
            case "scaleMode":
            case "joints":
            case "miterLimit":
                clearCachedBoundingBoxWithStroke();
                // Parent layout takes stroke weight into account
                invalidateParentSizeAndDisplayList();
                break;
        }
    }
    
    /**
     *  @private
     *  Make sure we clear the cached boundingBoxWithStroke
     */
    override public function set stroke(value:IStroke):void
    {
        super.stroke = value;
        clearCachedBoundingBoxWithStroke();
    }
}

}

//--------------------------------------------------------------------------
//
//  Internal Helper Class - PathSegmentsCollection
//
//--------------------------------------------------------------------------

/**
 *  Helper class that takes in a string and stores and generates a vector of
 *  Path segments.
 *  Provides methods for generating GraphicsPath and calculating bounds. 
 */
class PathSegmentsCollection
{
//--------------------------------------------------------------------------
//
//  Constructor
//
//--------------------------------------------------------------------------

/**
 *  Constructor.
 * 
 *  @param value 
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
public function PathSegmentsCollection(value:String)
{
    if (!value)
    {
        _segments = new Vector.<PathSegment>();
        return;
    }
    
    var newSegments:Vector.<PathSegment> = new Vector.<PathSegment>();
    var charCount:int = value.length;
    var c:Number; // current char code, String.charCodeAt() returns Number.
    var useRelative:Boolean;
    var prevIdentifier:Number = 0;
    var prevX:Number = 0;
    var prevY:Number = 0;
    var lastMoveX:Number = 0;
    var lastMoveY:Number = 0;
    var x:Number;
    var y:Number;
    var controlX:Number;
    var controlY:Number;
    var control2X:Number;
    var control2Y:Number;
    var lastMoveSegmentIndex:int = -1;
    
    _dataLength = charCount;
    _charPos = 0;
    while (true)
    {
        // Skip any whitespace or commas first
        skipWhiteSpace(value);
        
        // Are we done parsing?
        if (_charPos >= charCount)
            break;
        
        // Get the next character
        c = value.charCodeAt(_charPos++);
        
        // Is this a start of a number? 
        // The RegExp for a float is /[+-]?\d*\.?\d+([Ee][+-]?\d+)?/
        if ((c >= 0x30 && c < 0x3A) ||   // A digit
            (c == 0x2B || c == 0x2D) ||  // '+' & '-'
            (c == 0x2E))                 // '.'
        {
            c = prevIdentifier;
            _charPos--;
        }
        else if (c >= 0x41 && c <= 0x56) // Between 'C' and 'V' 
            useRelative = false;
        else if (c >= 0x61 && c <= 0x7A) // Between 'c' and 'v'
            useRelative = true;
        
        switch(c)
        {
            case 0x63:  // c
            case 0x43:  // C
                controlX = getNumber(useRelative, prevX, value);
                controlY = getNumber(useRelative,  prevY, value);
                control2X = getNumber(useRelative, prevX, value);
                control2Y = getNumber(useRelative, prevY, value);
                x = getNumber(useRelative, prevX, value);
                y = getNumber(useRelative, prevY, value);
                newSegments.push(new CubicBezierSegment(controlX, controlY, 
                    control2X, control2Y,
                    x, y));
                prevX = x;
                prevY = y;
                prevIdentifier = 0x63;
                
                break;
            
            case 0x6D:  // m
            case 0x4D:  // M
                x = getNumber(useRelative, prevX, value);
                y = getNumber(useRelative, prevY, value);
                newSegments.push(new MoveSegment(x, y));
                prevX = x;
                prevY = y;
                // If a moveto is followed by multiple pairs of coordinates, 
                // the subsequent pairs are treated as implicit lineto commands.
                prevIdentifier = (c == 0x6D) ? 0x6C : 0x4C; // c == 'm' ? 'l' : 'L'
                
                // Fix for bug SDK-24457:
                // If the Quadratic segment is isolated, the Player
                // won't draw fill correctly. We need to generate
                // a dummy line segment.
                var curSegmentIndex:int = newSegments.length - 1;
                if (lastMoveSegmentIndex + 2 == curSegmentIndex && 
                    newSegments[lastMoveSegmentIndex + 1] is QuadraticBezierSegment)
                {
                    // Insert a dummy LineSegment
                    newSegments.splice(lastMoveSegmentIndex + 1, 0, new LineSegment(lastMoveX, lastMoveY));
                    curSegmentIndex++;
                }
                
                lastMoveSegmentIndex = curSegmentIndex;
                lastMoveX = x;
                lastMoveY = y;
                break;
            
            case 0x6C:  // l
            case 0x4C:  // L
                x = getNumber(useRelative, prevX, value);
                y = getNumber(useRelative, prevY, value);
                newSegments.push(new LineSegment(x, y));
                prevX = x;
                prevY = y;
                prevIdentifier = 0x6C;
                break;
            
            case 0x68:  // h
            case 0x48:  // H
                x = getNumber(useRelative, prevX, value);
                y = prevY;
                newSegments.push(new LineSegment(x, y));
                prevX = x;
                prevY = y;
                prevIdentifier = 0x68;
                break;
            
            case 0x76:  // v
            case 0x56:  // V
                x = prevX;
                y = getNumber(useRelative, prevY, value);
                newSegments.push(new LineSegment(x, y));
                prevX = x;
                prevY = y;
                prevIdentifier = 0x76;
                break;
            
            case 0x71:  // q
            case 0x51:  // Q
                controlX = getNumber(useRelative, prevX, value);
                controlY = getNumber(useRelative, prevY, value);
                x = getNumber(useRelative, prevX, value);
                y = getNumber(useRelative, prevY, value);
                newSegments.push(new QuadraticBezierSegment(controlX, controlY, x, y));
                prevX = x;
                prevY = y;
                prevIdentifier = 0x71;
                break;
            
            case 0x74:  // t
            case 0x54:  // T
                // control is a reflection of the previous control point
                if (prevIdentifier == 0x74 || prevIdentifier == 0x71) // 't' or 'q'
                {
                    controlX = prevX + (prevX - controlX);
                    controlY = prevY + (prevY - controlY);
                }
                else
                {
                    controlX = prevX;
                    controlY = prevY;
                }
                
                x = getNumber(useRelative, prevX, value);
                y = getNumber(useRelative, prevY, value);
                newSegments.push(new QuadraticBezierSegment(controlX, controlY, x, y));
                prevX = x;
                prevY = y;
                prevIdentifier = 0x74;
                
                break;
            
            case 0x73:  // s
            case 0x53:  // S
                if (prevIdentifier == 0x73 || prevIdentifier == 0x63) // s or c
                {
                    controlX = prevX + (prevX - control2X);
                    controlY = prevY + (prevY - control2Y);
                }
                else
                {
                    controlX = prevX;
                    controlY = prevY;
                }
                
                control2X = getNumber(useRelative, prevX, value);
                control2Y = getNumber(useRelative, prevY, value);
                x = getNumber(useRelative, prevX, value);
                y = getNumber(useRelative, prevY, value);
                newSegments.push(new CubicBezierSegment(controlX, controlY,
                    control2X, control2Y, x, y));
                prevX = x;
                prevY = y;
                prevIdentifier = 0x73;
                
                break;
            
            case 0x7A:  // z
            case 0x5A:  // Z
                x = lastMoveX;
                y = lastMoveY;
                newSegments.push(new LineSegment(x, y));
                prevX = x;
                prevY = y;
                prevIdentifier = 0x7A;
                
                break;
            
            default:
                // unknown identifier, throw error?
                _segments = new Vector.<PathSegment>();
                return;
        }
    }
    
    // Fix for bug SDK-24457:
    // If the Quadratic segment is isolated, the Player
    // won't draw fill correctly. We need to generate
    // a dummy line segment.
    curSegmentIndex = newSegments.length;
    if (lastMoveSegmentIndex + 2 == curSegmentIndex && 
        newSegments[lastMoveSegmentIndex + 1] is QuadraticBezierSegment)
    {
        // Insert a dummy LineSegment
        newSegments.splice(lastMoveSegmentIndex + 1, 0, new LineSegment(lastMoveX, lastMoveY));
        curSegmentIndex++;
    }
    
    _segments = newSegments;
}

//--------------------------------------------------------------------------
//
//  Properties
//
//--------------------------------------------------------------------------

//----------------------------------
//  data
//----------------------------------

private var _segments:Vector.<PathSegment>;

/**
 *  A Vector of the actual path segments. May be empty, but always non-null. 
 */
public function get data():Vector.<PathSegment>
{
    return _segments;
}

//----------------------------------
//  bounds
//----------------------------------

private var _bounds:Rectangle;

/**
 *  The bounds of the segments in local coordinates.  
 */
public function getBounds():Rectangle
{
    if (_bounds)
        return _bounds;
    
    // First, allocate temporary bounds, as getBoundingBox() requires
    // natual bounds to calculate a scaling factor
    _bounds = new Rectangle(0, 0, 1, 1);
    
    // Pass in the same size to getBoundingBox
    // so that the scaling factor is (1, 1).
    _bounds = getBoundingBox(1, 1, null /*Matrix*/);
    return _bounds;
}

//--------------------------------------------------------------------------
//
//  Methods
//
//--------------------------------------------------------------------------

/**
 *  @return Returns the axis aligned bounding box of the segments stretched to 
 *  width, height and then transformed with transformation matrix m.
 */
public function getBoundingBox(width:Number, height:Number, m:Matrix):Rectangle
{
    var naturalBounds:Rectangle = getBounds();
    var sx:Number = naturalBounds.width == 0 ? 1 : width / naturalBounds.width;
    var sy:Number = naturalBounds.height == 0 ? 1 : height / naturalBounds.height; 
    
    var prevSegment:PathSegment;
    var pathBBox:Rectangle;
    var count:int = _segments.length;
    
    for (var i:int = 0; i < count; i++)
    {
        var segment:PathSegment = _segments[i];
        pathBBox = segment.getBoundingBox(prevSegment, sx, sy, m, pathBBox);
        prevSegment = segment;
    }
    
    // If path is empty, it's untransformed bounding box is (0,0), so we return transformed point (0,0)
    if (!pathBBox)
    {
        var x:Number = m ? m.tx : 0;
        var y:Number = m ? m.ty : 0;
        pathBBox = new Rectangle(x, y);
    }
    return pathBBox;
}

/**
 *  Workhorse method that iterates through the <code>segments</code>
 *  array and draws each path egment based on its control points. 
 *  
 *  Segments are drawn from the x and y position of the path. 
 *  Additionally, segments are drawn by taking into account the scale  
 *  applied to the path. 
 * 
 *  @param tx A Number representing the x position of where this 
 *  path segment should be drawn
 *  
 *  @param ty A Number representing the y position of where this  
 *  path segment should be drawn
 * 
 *  @param sx A Number representing the scaleX at which to draw 
 *  this path segment 
 * 
 *  @param sy A Number representing the scaleY at which to draw this
 *  path segment
 */
public function generateGraphicsPath(graphicsPath:GraphicsPath,
                                     tx:Number, 
                                     ty:Number, 
                                     sx:Number, 
                                     sy:Number):void
{
    graphicsPath.commands = null;
    graphicsPath.data = null;
    
    // Always start by moving to drawX, drawY. Otherwise
    // the path will begin at the previous pen location
    // if it does not start with a MoveSegment.
    graphicsPath.moveTo(tx, ty);
    
    var curSegment:PathSegment;
    var prevSegment:PathSegment;
    var count:int = _segments.length;
    for (var i:int = 0; i < count; i++)
    {
        prevSegment = curSegment;
        curSegment = _segments[i];
        curSegment.draw(graphicsPath, tx, ty, sx, sy, prevSegment);
    }
}

//--------------------------------------------------------------------------
//
//  Private methods
//
//--------------------------------------------------------------------------

private var _charPos:int = 0;
private var _dataLength:int = 0;

private function skipWhiteSpace(data:String):void
{
    while (_charPos < _dataLength)
    {
        var c:Number = data.charCodeAt(_charPos);
        if (c != 0x20 && // Space
            c != 0x2C && // Comma
            c != 0xD  && // Carriage return
            c != 0x9  && // Tab
            c != 0xA)    // New line
        {
            break;
        }
        _charPos++;
    }
}

private function getNumber(useRelative:Boolean, offset:Number, value:String):Number
{
    // Parse the string and find the first occurrance of the following RexExp
    // numberRegExp:RegExp = /[+-]?\d*\.?\d+([Ee][+-]?\d+)?/g;
    
    skipWhiteSpace(value); // updates _charPos
    if (_charPos >= _dataLength)
        return NaN;
    
    // Remember the start of the number
    var numberStart:int = _charPos;
    var hasSignCharacter:Boolean = false;
    var hasDigits:Boolean = false;
    
    // The number could start with '+' or '-' (the "[+-]?" part of the RegExp)
    var c:Number = value.charCodeAt(_charPos);
    if (c == 0x2B || c == 0x2D) // '+' or '-'
    {
        hasSignCharacter = true;
        _charPos++;
    }
    
    // The index of the '.' if any
    var dotIndex:int = -1;
    
    // First sequence of digits and optional dot in between (the "\d*\.?\d+" part of the RegExp)
    while (_charPos < _dataLength)
    {
        c = value.charCodeAt(_charPos);
        
        if (c >= 0x30 && c < 0x3A) // A digit
        {
            hasDigits = true;
        }
        else if (c == 0x2E && dotIndex == -1) // '.'
        {
            dotIndex = _charPos;
        }
        else
            break;
        
        _charPos++;
    }
    
    // Now check whether we had at least one digit.
    if (!hasDigits)
    {
        // Go to the end of the data
        _charPos = _dataLength;
        return NaN;
    }
    
    // 1. Was the last character a '.'? If so, rewind one character back.
    if (c == 0x2E)
        _charPos--;
    
    // So far we have a valid number, remember its end character index
    var numberEnd:int = _charPos;
    
    // Check to see if we have scientific notation (the "([Ee][+-]?\d+)?" part of the RegExp)
    if (c == 0x45 || c == 0x65)
    {
        _charPos++;
        
        // Check for '+' or '-'
        if (_charPos < _dataLength)
        {            
            c = value.charCodeAt(_charPos);
            if (c == 0x2B || c == 0x2D)
                _charPos++;
        }
        
        // Find all the digits
        var digitStart:int = _charPos;
        while (_charPos < _dataLength)
        {
            c = value.charCodeAt(_charPos);
            
            // Not a digit?
            if (!(c >= 0x30 && c < 0x3A))
            {
                break;
            }
            
            _charPos++;
        }
        
        // Do we have at least one digit?
        if (digitStart < _charPos)
            numberEnd = _charPos; // Scientific notation, update the end index of the number.
        else
            _charPos = numberEnd; // No scientific notation, rewind back to the end index of the number.
    }
    
    // Use parseFloat to get the actual number.
    // TODO (egeorgie): we could build the number while matching the RegExp which will save the substr and parseFloat
    var subString:String = value.substr(numberStart, numberEnd - numberStart);
    var result:Number = parseFloat(subString);
    if (isNaN(result))
    {
        // Go to the end of the data
        _charPos = _dataLength;
        return NaN;
    }
    _charPos = numberEnd;
    return useRelative ? result + offset : result;
}
}

//--------------------------------------------------------------------------
//
//  Internal Helper Class - PathSegment 
//
//--------------------------------------------------------------------------
import flash.display.GraphicsPath;
import flash.geom.Matrix;
import flash.geom.Rectangle;

import mx.events.PropertyChangeEvent;

/**
 *  The PathSegment class is the base class for a segment of a path.
 *  This class is not created directly. It is the base class for 
 *  MoveSegment, LineSegment, CubicBezierSegment and QuadraticBezierSegment.
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
class PathSegment extends Object
{

//--------------------------------------------------------------------------
//
//  Constructor
//
//--------------------------------------------------------------------------

/**
 *  Constructor.
 * 
 *  @param _x The x position of the pen in the current coordinate system.
 *  
 *  @param _y The y position of the pen in the current coordinate system.
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
public function PathSegment(_x:Number = 0, _y:Number = 0)
{
    super();
    x = _x;  
    y = _y; 
}   

//--------------------------------------------------------------------------
//
//  Properties
//
//--------------------------------------------------------------------------

//----------------------------------
//  x
//----------------------------------

/**
 *  The ending x position for this segment.
 *
 *  @default 0
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
public var x:Number = 0;

//----------------------------------
//  y
//----------------------------------

/**
 *  The ending y position for this segment.
 *
 *  @default 0
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
public var y:Number = 0;

//--------------------------------------------------------------------------
//
//  Methods
//
//--------------------------------------------------------------------------

/**
 *  Draws this path segment. You can determine the current pen position by 
 *  reading the x and y values of the previous segment. 
 *
 *  @param g The graphics context to draw into.
 *  @param prev The previous segment drawn, or null if this is the first segment.
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
public function draw(graphicsPath:GraphicsPath, dx:Number,dy:Number,sx:Number,sy:Number,prev:PathSegment):void
{
    // Override to draw your segment
}

/**
 *  @param prev The previous segment drawn, or null if this is the first segment.
 *  @param sx Pre-transform scale factor for x coordinates.
 *  @param sy Pre-transform scale factor for y coordinates.
 *  @param m Transformation matrix.
 *  @param rect If non-null, rect is expanded to include the bounding box of the segment.
 *  @return Returns the union of rect and the axis aligned bounding box of the post-transformed
 *  path segment.
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */    
public function getBoundingBox(prev:PathSegment, sx:Number, sy:Number, m:Matrix, rect:Rectangle):Rectangle
{
    // Override to calculate your segment's bounding box.
    return rect;
}

/**
 *  Returns the tangent for the segment. 
 *  @param prev The previous segment drawn, or null if this is the first segment.
 *  @param start If true, returns the tangent to the start point, otherwise the tangend to the end point.
 *  @param sx Pre-transform scale factor for x coordinates.
 *  @param sy Pre-transform scale factor for y coordinates.
 *  @param m Transformation matrix.
 *  @param result The tangent is returned as vector (x, y) in result.
 */
public function getTangent(prev:PathSegment, start:Boolean, sx:Number, sy:Number, m:Matrix, result:Point):void
{
    result.x = 0;
    result.y = 0;
}
}


//--------------------------------------------------------------------------
//
//  Internal Helper Class - LineSegment 
//
//--------------------------------------------------------------------------

import flash.display.GraphicsPath;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;

import mx.utils.MatrixUtil;

/**
 *  The LineSegment draws a line from the current pen position to the coordinate located at x, y.
 *  
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
class LineSegment extends PathSegment
{

//--------------------------------------------------------------------------
//
//  Constructor
//
//--------------------------------------------------------------------------

/**
 *  Constructor.
 *  
 *  @param x The current location of the pen along the x axis. The <code>draw()</code> method uses 
 *  this value to determine where to draw to.
 * 
 *  @param y The current location of the pen along the y axis. The <code>draw()</code> method uses 
 *  this value to determine where to draw to.
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
public function LineSegment(x:Number = 0, y:Number = 0)
{
    super(x, y);
}   

//--------------------------------------------------------------------------
//
//  Methods
//
//--------------------------------------------------------------------------

/**
 *  @inheritDoc
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
override public function draw(graphicsPath:GraphicsPath, dx:Number,dy:Number,sx:Number,sy:Number,prev:PathSegment):void
{
    graphicsPath.lineTo(dx + x*sx, dy + y*sy);
}

/**
 *  @inheritDoc
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
override public function getBoundingBox(prev:PathSegment, sx:Number, sy:Number, m:Matrix, rect:Rectangle):Rectangle
{
    pt = MatrixUtil.transformPoint(x * sx, y * sy, m);
    var x1:Number = pt.x;
    var y1:Number = pt.y;
    
    // If the previous segment actually draws, then only add the end point to the rectangle,
    // as the start point would have been added by the previous segment:
    if (prev != null && !(prev is MoveSegment))
        return MatrixUtil.rectUnion(x1, y1, x1, y1, rect); 
    
    var pt:Point = MatrixUtil.transformPoint(prev ? prev.x * sx : 0, prev ? prev.y * sy : 0, m);
    var x2:Number = pt.x;
    var y2:Number = pt.y;
    
    return MatrixUtil.rectUnion(Math.min(x1, x2), Math.min(y1, y2),
        Math.max(x1, x2), Math.max(y1, y2), rect); 
}

/**
 *  Returns the tangent for the segment. 
 *  @param prev The previous segment drawn, or null if this is the first segment.
 *  @param start If true, returns the tangent to the start point, otherwise the tangend to the end point.
 *  @param sx Pre-transform scale factor for x coordinates.
 *  @param sy Pre-transform scale factor for y coordinates.
 *  @param m Transformation matrix.
 *  @param result The tangent is returned as vector (x, y) in result.
 */
override public function getTangent(prev:PathSegment, start:Boolean, sx:Number, sy:Number, m:Matrix, result:Point):void
{
    var pt0:Point = MatrixUtil.transformPoint(prev ? prev.x * sx : 0, prev ? prev.y * sy : 0, m).clone();
    var pt1:Point = MatrixUtil.transformPoint(x * sx, y * sy, m);
    
    result.x = pt1.x - pt0.x;
    result.y = pt1.y - pt0.y;
}
}

//--------------------------------------------------------------------------
//
//  Internal Helper Class - MoveSegment 
//
//--------------------------------------------------------------------------
import flash.display.GraphicsPath;

/**
 *  The MoveSegment moves the pen to the x,y position. This class calls the <code>Graphics.moveTo()</code> method 
 *  from the <code>draw()</code> method.
 * 
 *  
 *  @see flash.display.Graphics
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
class MoveSegment extends PathSegment
{

//--------------------------------------------------------------------------
//
//  Constructor
//
//--------------------------------------------------------------------------

/**
 *  Constructor.
 *  
 *  @param x The target x-axis location in 2-d coordinate space.
 *  
 *  @param y The target y-axis location in 2-d coordinate space.
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
public function MoveSegment(x:Number = 0, y:Number = 0)
{
    super(x, y);
}   

//--------------------------------------------------------------------------
//
//  Methods
//
//--------------------------------------------------------------------------

/**
 *  @inheritDoc
 * 
 *  The MoveSegment class moves the pen to the position specified by the
 *  x and y properties.
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
override public function draw(graphicsPath:GraphicsPath, dx:Number,dy:Number,sx:Number,sy:Number,prev:PathSegment):void
{
    graphicsPath.moveTo(dx+x*sx, dy+y*sy);
}
}

//--------------------------------------------------------------------------
//
//  Internal Helper Class - CubicBezierSegment 
//
//--------------------------------------------------------------------------

import flash.display.GraphicsPath;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;

import mx.utils.MatrixUtil;

/**
 *  The CubicBezierSegment draws a cubic bezier curve from the current pen position 
 *  to x, y. The control1X and control1Y properties specify the first control point; 
 *  the control2X and control2Y properties specify the second control point.
 *
 *  <p>Cubic bezier curves are not natively supported in Flash Player. This class does
 *  an approximation based on the fixed midpoint algorithm and uses 4 quadratic curves
 *  to simulate a cubic curve.</p>
 *
 *  <p>For details on the fixed midpoint algorithm, see:<br/>
 *  http://timotheegroleau.com/Flash/articles/cubic_bezier_in_flash.htm</p>
 *  
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
class CubicBezierSegment extends PathSegment
{
    
    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------
    
    /**
     *  Constructor.
     *  
     *  <p>For a CubicBezierSegment, there are two control points, each with x and y coordinates. Control points 
     *  are points that define the direction and amount of curves of a Bezier curve. 
     *  The curved line never reaches the control points; however, the line curves as though being drawn 
     *  toward the control point.</p>
     *  
     *  @param _control1X The x-axis location in 2-d coordinate space of the first control point.
     *  
     *  @param _control1Y The y-axis location of the first control point.
     *  
     *  @param _control2X The x-axis location of the second control point.
     *  
     *  @param _control2Y The y-axis location of the second control point.
     *  
     *  @param x The x-axis location of the starting point of the curve.
     *  
     *  @param y The y-axis location of the starting point of the curve.
     *  
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function CubicBezierSegment(
        _control1X:Number = 0, _control1Y:Number = 0,
        _control2X:Number = 0, _control2Y:Number = 0,
        x:Number = 0, y:Number = 0)
    {
        super(x, y);
        
        control1X = _control1X;
        control1Y = _control1Y;
        control2X = _control2X;
        control2Y = _control2Y;
    }   
    
    
    //--------------------------------------------------------------------------
    //
    //  Variables
    //
    //--------------------------------------------------------------------------
    
    private var _qPts:QuadraticPoints;
    
    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------
    
    //----------------------------------
    //  control1X
    //----------------------------------
    
    /**
     *  The first control point x position.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public var control1X:Number = 0;
    
    //----------------------------------
    //  control1Y
    //----------------------------------
    
    /**
     *  The first control point y position.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public var control1Y:Number = 0;
    
    //----------------------------------
    //  control2X
    //----------------------------------
    
    /**
     *  The second control point x position.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public var control2X:Number = 0;
    
    //----------------------------------
    //  control2Y
    //----------------------------------
    
    /**
     *  The second control point y position.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public var control2Y:Number = 0;
    
    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------
    
    /**
     *  Draws the segment.
     *
     *  @param g The graphics context where the segment is drawn.
     *  
     *  @param prev The previous location of the pen.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    override public function draw(graphicsPath:GraphicsPath, dx:Number, dy:Number, sx:Number, sy:Number, prev:PathSegment):void
    {
        var qPts:QuadraticPoints = getQuadraticPoints(prev);
        
        graphicsPath.curveTo(dx + qPts.control1.x*sx, dy+qPts.control1.y*sy, dx+qPts.anchor1.x*sx, dy+qPts.anchor1.y*sy);
        graphicsPath.curveTo(dx + qPts.control2.x*sx, dy+qPts.control2.y*sy, dx+qPts.anchor2.x*sx, dy+qPts.anchor2.y*sy);
        graphicsPath.curveTo(dx + qPts.control3.x*sx, dy+qPts.control3.y*sy, dx+qPts.anchor3.x*sx, dy+qPts.anchor3.y*sy);
        graphicsPath.curveTo(dx + qPts.control4.x*sx, dy+qPts.control4.y*sy, dx+qPts.anchor4.x*sx, dy+qPts.anchor4.y*sy);
    }
    
    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    override public function getBoundingBox(prev:PathSegment, sx:Number, sy:Number,
                                            m:Matrix, rect:Rectangle):Rectangle
    {
        var qPts:QuadraticPoints = getQuadraticPoints(prev);
        
        rect = MatrixUtil.getQBezierSegmentBBox(prev ? prev.x : 0, prev ? prev.y : 0,
            qPts.control1.x, qPts.control1.y,
            qPts.anchor1.x, qPts.anchor1.y,
            sx, sy, m, rect); 
        
        rect = MatrixUtil.getQBezierSegmentBBox(qPts.anchor1.x, qPts.anchor1.y,
            qPts.control2.x, qPts.control2.y,
            qPts.anchor2.x, qPts.anchor2.y,
            sx, sy, m, rect); 
        
        rect = MatrixUtil.getQBezierSegmentBBox(qPts.anchor2.x, qPts.anchor2.y,
            qPts.control3.x, qPts.control3.y,
            qPts.anchor3.x, qPts.anchor3.y,
            sx, sy, m, rect); 
        
        rect = MatrixUtil.getQBezierSegmentBBox(qPts.anchor3.x, qPts.anchor3.y,
            qPts.control4.x, qPts.control4.y,
            qPts.anchor4.x, qPts.anchor4.y,
            sx, sy, m, rect); 
        return rect;
    }
    
    /**
     *  Returns the tangent for the segment. 
     *  @param prev The previous segment drawn, or null if this is the first segment.
     *  @param start If true, returns the tangent to the start point, otherwise the tangend to the end point.
     *  @param sx Pre-transform scale factor for x coordinates.
     *  @param sy Pre-transform scale factor for y coordinates.
     *  @param m Transformation matrix.
     *  @param result The tangent is returned as vector (x, y) in result.
     */
    override public function getTangent(prev:PathSegment, start:Boolean, sx:Number, sy:Number, m:Matrix, result:Point):void
    {
        // Get the approximation (we want the tangents to be the same as the ones we use to draw
        var qPts:QuadraticPoints = getQuadraticPoints(prev);
        
        var pt0:Point = MatrixUtil.transformPoint(prev ? prev.x * sx : 0, prev ? prev.y * sy : 0, m).clone();
        var pt1:Point = MatrixUtil.transformPoint(qPts.control1.x * sx, qPts.control1.y * sy, m).clone();
        var pt2:Point = MatrixUtil.transformPoint(qPts.anchor1.x * sx, qPts.anchor1.y * sy, m).clone();
        var pt3:Point = MatrixUtil.transformPoint(qPts.control2.x * sx, qPts.control2.y * sy, m).clone();
        var pt4:Point = MatrixUtil.transformPoint(qPts.anchor2.x * sx, qPts.anchor2.y * sy, m).clone();
        var pt5:Point = MatrixUtil.transformPoint(qPts.control3.x * sx, qPts.control3.y * sy, m).clone();
        var pt6:Point = MatrixUtil.transformPoint(qPts.anchor3.x * sx, qPts.anchor3.y * sy, m).clone();
        var pt7:Point = MatrixUtil.transformPoint(qPts.control4.x * sx, qPts.control4.y * sy, m).clone();
        var pt8:Point = MatrixUtil.transformPoint(qPts.anchor4.x * sx, qPts.anchor4.y * sy, m).clone();
        
        if (start)
        {
            QuadraticBezierSegment.getQTangent(pt0.x, pt0.y, pt1.x, pt1.y, pt2.x, pt2.y, start, result);
            // If there is no tangent
            if (result.x == 0 && result.y == 0)
            {
                // Try 3 & 4
                QuadraticBezierSegment.getQTangent(pt0.x, pt0.y, pt3.x, pt3.y, pt4.x, pt4.y, start, result);
                
                // If there is no tangent
                if (result.x == 0 && result.y == 0)
                {
                    // Try 5 & 6
                    QuadraticBezierSegment.getQTangent(pt0.x, pt0.y, pt5.x, pt5.y, pt6.x, pt6.y, start, result);
                    
                    // If there is no tangent
                    if (result.x == 0 && result.y == 0)
                        // Try 7 & 8
                        QuadraticBezierSegment.getQTangent(pt0.x, pt0.y, pt7.x, pt7.y, pt8.x, pt8.y, start, result);
                }
            }
        }
        else
        {
            QuadraticBezierSegment.getQTangent(pt6.x, pt6.y, pt7.x, pt7.y, pt8.x, pt8.y, start, result);
            // If there is no tangent
            if (result.x == 0 && result.y == 0)
            {
                // Try 4 & 5
                QuadraticBezierSegment.getQTangent(pt4.x, pt4.y, pt5.x, pt5.y, pt8.x, pt8.y, start, result);
                
                // If there is no tangent
                if (result.x == 0 && result.y == 0)
                {
                    // Try 2 & 3
                    QuadraticBezierSegment.getQTangent(pt2.x, pt2.y, pt3.x, pt3.y, pt8.x, pt8.y, start, result);
                    
                    // If there is no tangent
                    if (result.x == 0 && result.y == 0)
                        // Try 0 & 1
                        QuadraticBezierSegment.getQTangent(pt0.x, pt0.y, pt1.x, pt1.y, pt8.x, pt8.y, start, result);
                }
            }
        }
    }    
    
    /** 
     *  @private
     *  Tim Groleau's method to approximate a cubic bezier with 4 quadratic beziers, 
     *  with endpoint and control point of each saved. 
     */
    private function getQuadraticPoints(prev:PathSegment):QuadraticPoints
    {
        if (_qPts)
            return _qPts;
        
        var p1:Point = new Point(prev ? prev.x : 0, prev ? prev.y : 0);
        var p2:Point = new Point(x, y);
        var c1:Point = new Point(control1X, control1Y);     
        var c2:Point = new Point(control2X, control2Y);
        
        // calculates the useful base points
        var PA:Point = Point.interpolate(c1, p1, 3/4);
        var PB:Point = Point.interpolate(c2, p2, 3/4);
        
        // get 1/16 of the [p2, p1] segment
        var dx:Number = (p2.x - p1.x) / 16;
        var dy:Number = (p2.y - p1.y) / 16;
        
        _qPts = new QuadraticPoints;
        
        // calculates control point 1
        _qPts.control1 = Point.interpolate(c1, p1, 3/8);
        
        // calculates control point 2
        _qPts.control2 = Point.interpolate(PB, PA, 3/8);
        _qPts.control2.x -= dx;
        _qPts.control2.y -= dy;
        
        // calculates control point 3
        _qPts.control3 = Point.interpolate(PA, PB, 3/8);
        _qPts.control3.x += dx;
        _qPts.control3.y += dy;
        
        // calculates control point 4
        _qPts.control4 = Point.interpolate(c2, p2, 3/8);
        
        // calculates the 3 anchor points
        _qPts.anchor1 = Point.interpolate(_qPts.control1, _qPts.control2, 0.5); 
        _qPts.anchor2 = Point.interpolate(PA, PB, 0.5); 
        _qPts.anchor3 = Point.interpolate(_qPts.control3, _qPts.control4, 0.5); 
        
        // the 4th anchor point is p2
        _qPts.anchor4 = p2;
        
        return _qPts;      
    }
}

//--------------------------------------------------------------------------
//
//  Internal Helper Class - QuadraticPoints  
//
//--------------------------------------------------------------------------
import flash.geom.Point;

/**
 *  Utility class to store the computed quadratic points.
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
class QuadraticPoints
{
    public var control1:Point;
    public var anchor1:Point;
    public var control2:Point;
    public var anchor2:Point;
    public var control3:Point;
    public var anchor3:Point;
    public var control4:Point;
    public var anchor4:Point;
    
    /**
     * Constructor.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function QuadraticPoints()
    {
        super();
    }
}

//--------------------------------------------------------------------------
//
//  Internal Helper Class - QuadraticBezierSegment 
//
//--------------------------------------------------------------------------
import flash.display.GraphicsPath;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;

import mx.utils.MatrixUtil;

/**
 *  The QuadraticBezierSegment draws a quadratic curve from the current pen position 
 *  to x, y. 
 *
 *  Quadratic bezier is the native curve type
 *  in Flash Player.
 *  
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
class QuadraticBezierSegment extends PathSegment
{
    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------
    
    /**
     *  Constructor.
     *  
     *  <p>For a QuadraticBezierSegment, there is one control point. A control point
     *  is a point that defines the direction and amount of a Bezier curve. 
     *  The curved line never reaches the control point; however, the line curves as though being drawn 
     *  toward the control point.</p>
     * 
     *  @param _control1X The x-axis location in 2-d coordinate space of the control point.
     *  
     *  @param _control1Y The y-axis location in 2-d coordinate space of the control point.
     *  
     *  @param x The x-axis location of the starting point of the curve.
     *  
     *  @param y The y-axis location of the starting point of the curve.
     * 
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function QuadraticBezierSegment(
        _control1X:Number = 0, _control1Y:Number = 0, 
        x:Number = 0, y:Number = 0)
    {
        super(x, y);
        
        control1X = _control1X;
        control1Y = _control1Y;
    }   
    
    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------
    
    //----------------------------------
    //  control1X
    //----------------------------------
    
    /**
     *  The control point's x position.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public var control1X:Number = 0;
    
    //----------------------------------
    //  control1Y
    //----------------------------------
    
    /**
     *  The control point's y position.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public var control1Y:Number = 0;
    
    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------
    
    /**
     *  Draws the segment using the control point location and the x and y coordinates. 
     *  This method calls the <code>Graphics.curveTo()</code> method.
     *  
     *  @see flash.display.Graphics
     *
     *  @param g The graphics context where the segment is drawn.
     *  
     *  @param prev The previous location of the pen.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    override public function draw(graphicsPath:GraphicsPath, dx:Number,dy:Number,sx:Number,sy:Number,prev:PathSegment):void
    {
        graphicsPath.curveTo(dx+control1X*sx, dy+control1Y*sy, dx+x*sx, dy+y*sy);
    }
    
    static public function getQTangent(x0:Number, y0:Number,
                                       x1:Number, y1:Number,
                                       x2:Number, y2:Number,
                                       start:Boolean,
                                       result:Point):void
    {
        if (start)
        {
            if (x0 == x1 && y0 == y1)
            {
                result.x = x2 - x0;
                result.y = y2 - y0;
            }
            else
            {
                result.x = x1 - x0;
                result.y = y1 - y0;
            }
        }
        else
        {
            if (x2 == x1 && y2 == y1)
            {
                result.x = x2 - x0;
                result.y = y2 - y0;
            }
            else
            {
                result.x = x2 - x1;
                result.y = y2 - y1;
            }
        }
    }
    
    /**
     *  Returns the tangent for the segment. 
     *  @param prev The previous segment drawn, or null if this is the first segment.
     *  @param start If true, returns the tangent to the start point, otherwise the tangend to the end point.
     *  @param sx Pre-transform scale factor for x coordinates.
     *  @param sy Pre-transform scale factor for y coordinates.
     *  @param m Transformation matrix.
     *  @param result The tangent is returned as vector (x, y) in result.
     */
    override public function getTangent(prev:PathSegment, start:Boolean, sx:Number, sy:Number, m:Matrix, result:Point):void
    {
        var pt0:Point = MatrixUtil.transformPoint(prev ? prev.x * sx : 0, prev ? prev.y * sy : 0, m).clone();
        var pt1:Point = MatrixUtil.transformPoint(control1X * sx, control1Y * sy, m).clone();;
        var pt2:Point = MatrixUtil.transformPoint(x * sx, y * sy, m).clone();
        
        getQTangent(pt0.x, pt0.y, pt1.x, pt1.y, pt2.x, pt2.y, start, result);
    }
    
    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    override public function getBoundingBox(prev:PathSegment, sx:Number, sy:Number,
                                            m:Matrix, rect:Rectangle):Rectangle
    {
        return MatrixUtil.getQBezierSegmentBBox(prev ? prev.x : 0, prev ? prev.y : 0,
            control1X, control1Y, x, y, sx, sy, m, rect);
    }
}
