http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/7d529524/frameworks/projects/Graphics/src/main/flex/org/apache/flex/graphics/utils/PathHelper.as ---------------------------------------------------------------------- diff --git a/frameworks/projects/Graphics/src/main/flex/org/apache/flex/graphics/utils/PathHelper.as b/frameworks/projects/Graphics/src/main/flex/org/apache/flex/graphics/utils/PathHelper.as new file mode 100644 index 0000000..0badb2e --- /dev/null +++ b/frameworks/projects/Graphics/src/main/flex/org/apache/flex/graphics/utils/PathHelper.as @@ -0,0 +1,1712 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.flex.graphics.utils +{ + import flash.display.GraphicsPath; + import flash.geom.Rectangle; + + public class PathHelper + { + private static var segments:PathSegmentsCollection; + private static var graphicsPath:GraphicsPath = new GraphicsPath(new Vector.<int>(), new Vector.<Number>()); + + public static function getSegments(data:String, x:Number=0, y:Number=0):GraphicsPath + { + segments = new PathSegmentsCollection(data); + segments.generateGraphicsPath(graphicsPath, x, y, 1, 1); + return graphicsPath; + } + + public static function getBounds(data:String):Rectangle + { + segments = new PathSegmentsCollection(data); + return segments.getBounds(); + } + } +} + + +//-------------------------------------------------------------------------- +// +// 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 'A' and 'V' + useRelative = false; + else if (c >= 0x61 && c <= 0x7A) // Between 'a' 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 0x61: //a + case 0x41: //A + var rx:Number = getNumber(useRelative, prevX, value); + var ry:Number = getNumber(useRelative, prevY, value); + var angle:Number = getNumber(useRelative, prevY, value); + var largeArcFlag:Boolean = getNumber(useRelative, prevY, value) == 1 ? true:false; + var sweepFlag:Boolean = getNumber(useRelative, prevY, value) == 1 ? true:false; + var endX:Number = getNumber(useRelative, prevX, value); + var endY:Number = getNumber(useRelative, prevY, value); + newSegments.push(new EllipticalArcSegment(rx,ry,angle,largeArcFlag,sweepFlag,endX,endY)); + prevX = endX; + prevY = endY; + prevIdentifier = 0x41; + 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; + + +/** + * 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; + + +/** + * 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 org.apache.flex.graphics.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 org.apache.flex.graphics.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); + } +} + + +/** + * The EllipticalArcSegment draws an arc from the current point to a specified point. + * The arc command begins with the x and y radius and ends with the ending point of the arc. + * Between these are three other values: x axis rotation, large arc flag and sweep flag. + * The x axis rotation is used to rotate the ellipse that the arc is created from. + * This rotation maintains the start and end points, whereas a rotation with the transform + * attribute (outside the path description) would cause the entire path, including the start + * and end points, to be rotated. The large arc flag and sweep flag control which part of + * the ellipse is used to cut the arc. These are needed because there's more than one way + * to place an ellipse so that it includes the start and end points. + * + * + * Derieved from the svgweb library + * Original found here https://code.google.com/p/svgweb/source/browse/trunk/src/org/svgweb/utils/EllipticalArc.as?r=1251 + * + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4 + */ +class EllipticalArcSegment extends PathSegment +{ + + private var _rx:Number; + private var _ry:Number; + private var _angle:Number; + private var _largeArcFlag:Boolean; + private var _sweepFlag:Boolean; + private var _endX:Number; + private var _endY:Number; + + /** + * Constructor. + * + * + * @param rx x radius + * + * @param ry y radius + * + * @param angle angle of rotation from the x-axis + * + * @param largeArcFlag true if arc is greater than 180 degrees + * + * @param sweepFlag determines if the arc proceeds in a postitive or negative radial direction + * + * @param x arc end x value + * + * @param y arc end y value + * + * @param LastPointX starting x value of arc + * + * @param LastPointY starting y value of arc + * + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4 + */ + public function EllipticalArcSegment(rx:Number, ry:Number, angle:Number, largeArcFlag:Boolean, sweepFlag:Boolean, endX:Number, endY:Number) + { + _rx = rx; + _ry = ry; + _angle = angle; + _largeArcFlag = largeArcFlag; + _sweepFlag = sweepFlag; + _endX = endX; + _endY = endY; + } + + override public function draw(graphicsPath:GraphicsPath, dx:Number, dy:Number, sx:Number, sy:Number, prev:PathSegment):void + { + var ellipticalArc:Object = computeSvgArc(_rx, _ry, _angle, _largeArcFlag, _sweepFlag, dx + _endX, dy + _endY, dx + prev.x , dy + prev.y); + drawEllipticalArc(ellipticalArc.cx, ellipticalArc.cy, ellipticalArc.startAngle, ellipticalArc.arc, ellipticalArc.radius, ellipticalArc.yRadius, ellipticalArc.xAxisRotation, graphicsPath); + } + + /** + * @private + * Create quadratic circle graphics commands from an elliptical arc + **/ + private static function drawEllipticalArc(x:Number, y:Number, startAngle:Number, arc:Number, + radius:Number, yRadius:Number, xAxisRotation:Number, path:GraphicsPath):void + { + // Circumvent drawing more than is needed + if (Math.abs(arc)>360) + { + arc = 360; + } + + // Draw in a maximum of 45 degree segments. First we calculate how many + // segments are needed for our arc. + var segs:Number = Math.ceil(Math.abs(arc)/45); + + // Now calculate the sweep of each segment + var segAngle:Number = arc/segs; + + var theta:Number = degreesToRadians(segAngle); + var angle:Number = degreesToRadians(startAngle); + + // Draw as 45 degree segments + if (segs>0) + { + var beta:Number = degreesToRadians(xAxisRotation); + var sinbeta:Number = Math.sin(beta); + var cosbeta:Number = Math.cos(beta); + + var cx:Number; + var cy:Number; + var x1:Number; + var y1:Number; + + // Loop for drawing arc segments + for (var i:int = 0; i<segs; i++) { + + angle += theta; + + var sinangle:Number = Math.sin(angle-(theta/2)); + var cosangle:Number = Math.cos(angle-(theta/2)); + + var div:Number = Math.cos(theta/2); + cx = x + (radius * cosangle * cosbeta - yRadius * sinangle * sinbeta)/div; //Why divide by Math.cos(theta/2)? - FIX THIS + cy = y + (radius * cosangle * sinbeta + yRadius * sinangle * cosbeta)/div; //Why divide by Math.cos(theta/2)? - FIX THIS + + sinangle = Math.sin(angle); + cosangle = Math.cos(angle); + + x1 = x + (radius * cosangle * cosbeta - yRadius * sinangle * sinbeta); + y1 = y + (radius * cosangle * sinbeta + yRadius * sinangle * cosbeta); + + path.curveTo(cx, cy, x1, y1); + } + } + } + + + private function computeSvgArc(rx:Number, ry:Number,angle:Number,largeArcFlag:Boolean,sweepFlag:Boolean, + x:Number,y:Number,LastPointX:Number, LastPointY:Number):Object { + //store before we do anything with it + var xAxisRotation:Number = angle; + + // Compute the half distance between the current and the final point + var dx2:Number = (LastPointX - x) / 2.0; + var dy2:Number = (LastPointY - y) / 2.0; + + // Convert angle from degrees to radians + angle = degreesToRadians(angle); + var cosAngle:Number = Math.cos(angle); + var sinAngle:Number = Math.sin(angle); + + + //Compute (x1, y1) + var x1:Number = (cosAngle * dx2 + sinAngle * dy2); + var y1:Number = (-sinAngle * dx2 + cosAngle * dy2); + + // Ensure radii are large enough + rx = Math.abs(rx); + ry = Math.abs(ry); + var Prx:Number = rx * rx; + var Pry:Number = ry * ry; + var Px1:Number = x1 * x1; + var Py1:Number = y1 * y1; + + // check that radii are large enough + var radiiCheck:Number = Px1/Prx + Py1/Pry; + if (radiiCheck > 1) { + rx = Math.sqrt(radiiCheck) * rx; + ry = Math.sqrt(radiiCheck) * ry; + Prx = rx * rx; + Pry = ry * ry; + } + + + //Compute (cx1, cy1) + var sign:Number = (largeArcFlag == sweepFlag) ? -1 : 1; + var sq:Number = ((Prx*Pry)-(Prx*Py1)-(Pry*Px1)) / ((Prx*Py1)+(Pry*Px1)); + sq = (sq < 0) ? 0 : sq; + var coef:Number = (sign * Math.sqrt(sq)); + var cx1:Number = coef * ((rx * y1) / ry); + var cy1:Number = coef * -((ry * x1) / rx); + + + //Compute (cx, cy) from (cx1, cy1) + var sx2:Number = (LastPointX + x) / 2.0; + var sy2:Number = (LastPointY + y) / 2.0; + var cx:Number = sx2 + (cosAngle * cx1 - sinAngle * cy1); + var cy:Number = sy2 + (sinAngle * cx1 + cosAngle * cy1); + + + //Compute the angleStart (angle1) and the angleExtent (dangle) + var ux:Number = (x1 - cx1) / rx; + var uy:Number = (y1 - cy1) / ry; + var vx:Number = (-x1 - cx1) / rx; + var vy:Number = (-y1 - cy1) / ry; + var p:Number + var n:Number + + //Compute the angle start + n = Math.sqrt((ux * ux) + (uy * uy)); + p = ux; + + sign = (uy < 0) ? -1.0 : 1.0; + + var angleStart:Number = radiansToDegrees(sign * Math.acos(p / n)); + + // Compute the angle extent + n = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy)); + p = ux * vx + uy * vy; + sign = (ux * vy - uy * vx < 0) ? -1.0 : 1.0; + var angleExtent:Number = radiansToDegrees(sign * Math.acos(p / n)); + + if(!sweepFlag && angleExtent > 0) + { + angleExtent -= 360; + } + else if (sweepFlag && angleExtent < 0) + { + angleExtent += 360; + } + + angleExtent %= 360; + angleStart %= 360; + + //return Object({x:LastPointX,y:LastPointY,startAngle:angleStart,arc:angleExtent,radius:rx,yRadius:ry,xAxisRotation:xAxisRotation}); + return Object({x:LastPointX,y:LastPointY,startAngle:angleStart,arc:angleExtent,radius:rx,yRadius:ry,xAxisRotation:xAxisRotation, cx:cx,cy:cy}); + } + + /** + * @private + * Convert degrees to radians + **/ + private static function degreesToRadians(angle:Number):Number{ + return angle*(Math.PI/180); + } + + /** + * @private + * Convert radiansToDegrees + **/ + private static function radiansToDegrees(angle:Number):Number{ + return angle*(180/Math.PI); + } + + +} +
http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/7d529524/frameworks/projects/Graphics/src/main/flex/org/apache/flex/graphics/utils/TransformOffsets.as ---------------------------------------------------------------------- diff --git a/frameworks/projects/Graphics/src/main/flex/org/apache/flex/graphics/utils/TransformOffsets.as b/frameworks/projects/Graphics/src/main/flex/org/apache/flex/graphics/utils/TransformOffsets.as new file mode 100644 index 0000000..864b18a --- /dev/null +++ b/frameworks/projects/Graphics/src/main/flex/org/apache/flex/graphics/utils/TransformOffsets.as @@ -0,0 +1,367 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.flex.graphics.utils +{ + import flash.events.Event; + import flash.events.EventDispatcher; + + import __AS3__.vec.Vector; + + + /** + * A CompoundTransform represents a 2D or 3D matrix transform. It can be used in the postLayoutTransformOffsets property on a UIComponent or GraphicElement. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public class TransformOffsets extends EventDispatcher + { + //-------------------------------------------------------------------------- + // + // Constructor + // + //-------------------------------------------------------------------------- + /** + * Constructor. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function TransformOffsets() + { + } + + + + /** + * @private + * storage for transform properties. These values are concatenated together with the layout properties to + * form the actual computed matrix used to render the object. + */ + private var _rotationX:Number = 0; + private var _rotationY:Number = 0; + private var _rotationZ:Number = 0; + private var _scaleX:Number = 1; + private var _scaleY:Number = 1; + private var _scaleZ:Number = 1; + private var _x:Number = 0; + private var _y:Number = 0; + private var _z:Number = 0; + + /** + * @private + * flags for tracking whether the transform is 3D. A transform is 3D if any of the 3D properties -- rotationX/Y, scaleZ, or z -- are set. + */ + private static const IS_3D:uint = 0x200; + private static const M3D_FLAGS_VALID:uint = 0x400; + + /** + * @private + * general storage for all of our flags. + */ + private var _flags:uint = 0; + + /** + * @private + */ + public var owner:AdvancedLayoutFeatures; + //---------------------------------------------------------------------------- + + /** + * the x value added to the transform + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function set x(value:Number):void + { + if (value == _x) + return; + _x = value; + invalidate(false); + } + /** + * @private + */ + public function get x():Number + { + return _x; + } + + /** + * the y value added to the transform + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function set y(value:Number):void + { + if (value == _y) + return; + _y = value; + invalidate(false); + } + + /** + * @private + */ + public function get y():Number + { + return _y; + } + + /** + * the z value added to the transform + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function set z(value:Number):void + { + if (value == _z) + return; + _z = value; + invalidate(true); + } + + /** + * @private + */ + public function get z():Number + { + return _z; + } + + //------------------------------------------------------------------------------ + + + /** + * the rotationX, in degrees, added to the transform + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function set rotationX(value:Number):void + { + if (value == _rotationX) + return; + _rotationX = value; + invalidate(true); + } + + /** + * @private + */ + public function get rotationX():Number + { + return _rotationX; + } + + /** + * the rotationY, in degrees, added to the transform + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function set rotationY(value:Number):void + { + if (value == _rotationY) + return; + _rotationY = value; + invalidate(true); + } + + /** + * @private + */ + public function get rotationY():Number + { + return _rotationY; + } + + /** + * the rotationZ, in degrees, added to the transform + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function set rotationZ(value:Number):void + { + if (value == _rotationZ) + return; + _rotationZ = value; + invalidate(false); + } + + /** + * @private + */ + public function get rotationZ():Number + { + return _rotationZ; + } + + //------------------------------------------------------------------------------ + + + /** + * the multiplier applied to the scaleX of the transform. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function set scaleX(value:Number):void + { + if (value == _scaleX) + return; + _scaleX = value; + invalidate(false); + } + + /** + * @private + */ + public function get scaleX():Number + { + return _scaleX; + } + + /** + * the multiplier applied to the scaleY of the transform. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function set scaleY(value:Number):void + { + if (value == _scaleY) + return; + _scaleY = value; + invalidate(false); + } + + /** + * @private + */ + public function get scaleY():Number + { + return _scaleY; + } + + + /** + * the multiplier applied to the scaleZ of the transform. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function set scaleZ(value:Number):void + { + if (value == _scaleZ) + return; + _scaleZ = value; + invalidate(true); + } + + /** + * @private + */ + public function get scaleZ():Number + { + return _scaleZ; + } + + + /** + * @private + * returns true if the transform has 3D values. + */ + public function get is3D():Boolean + { + if ((_flags & M3D_FLAGS_VALID) == 0) + update3DFlags(); + return ((_flags & IS_3D) != 0); + } + + + //------------------------------------------------------------------------------ + + /** + * @private + * invalidates our various cached values. Any change to the CompoundTransform object that affects + * the various transforms should call this function. + * @param reason - the code indicating what changes to cause the invalidation. + * @param affects3D - a flag indicating whether the change affects the 2D/3D nature of the various transforms. + * @param dispatchChangeEvent - if true, the CompoundTransform will dispatch a change indicating that its underlying transforms + * have been modified. + */ + private function invalidate(affects3D:Boolean,dispatchChangeEvent:Boolean = true):void + { + if (affects3D) + _flags &= ~M3D_FLAGS_VALID; + + if (dispatchChangeEvent) + dispatchEvent(new Event(Event.CHANGE)); + } + + private static const EPSILON:Number = .001; + /** + * @private + * updates the flags that indicate whether the layout, offset, and/or computed transforms are 3D in nature. + * Since the user can set either the individual transform properties or the matrices directly, we compute these + * flags based on what the current 'source of truth' is for each of these values. + */ + private function update3DFlags():void + { + if ((_flags & M3D_FLAGS_VALID) == 0) + { + var matrixIs3D:Boolean = ( // note that rotationZ is the same as rotation, and not a 3D affecting + (Math.abs(_scaleZ-1) > EPSILON) || // property. + ((Math.abs(_rotationX)+EPSILON)%360) > 2*EPSILON || + ((Math.abs(_rotationY)+EPSILON)%360) > 2*EPSILON || + Math.abs(_z) > EPSILON + ); + if (matrixIs3D) + _flags |= IS_3D; + else + _flags &= ~IS_3D; + _flags |= M3D_FLAGS_VALID; + } + } + + } +} http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/7d529524/frameworks/projects/Graphics/src/main/flex/org/apache/flex/svg/Circle.as ---------------------------------------------------------------------- diff --git a/frameworks/projects/Graphics/src/main/flex/org/apache/flex/svg/Circle.as b/frameworks/projects/Graphics/src/main/flex/org/apache/flex/svg/Circle.as new file mode 100644 index 0000000..5595207 --- /dev/null +++ b/frameworks/projects/Graphics/src/main/flex/org/apache/flex/svg/Circle.as @@ -0,0 +1,102 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.flex.svg +{ + COMPILE::SWF + { + import flash.geom.Point; + import flash.geom.Rectangle; + } + COMPILE::JS + { + import org.apache.flex.core.WrappedHTMLElement; + } + + public class Circle extends GraphicShape + { + private var _radius:Number; + + public function get radius():Number + { + return _radius; + } + + public function set radius(value:Number):void + { + _radius = value; + } + + COMPILE::JS + private var _circle:WrappedHTMLElement; + + /** + * Draw the circle. + * @param cx The x location of the center of the circle + * @param cy The y location of the center of the circle. + * @param radius The radius of the circle. + * + * @langversion 3.0 + * @playerversion Flash 10.2 + * @playerversion AIR 2.6 + * @productversion FlexJS 0.0 + * @flexjsignorecoercion org.apache.flex.core.WrappedHTMLElement + * @flexjsignorecoercion SVGCircleElement + */ + public function drawCircle(cx:Number, cy:Number, radius:Number):void + { + COMPILE::SWF + { + graphics.clear(); + applyStroke(); + beginFill(new Rectangle(cx,cy,radius*2, radius*2),new Point(cx-radius,cy-radius)); + graphics.drawCircle(cx,cy,radius); + endFill(); + } + COMPILE::JS + { + var style:String = getStyleStr(); + + if (_circle == null) { + _circle = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse') as WrappedHTMLElement; + _circle.flexjs_wrapper = this; + element.appendChild(_circle); + } + _circle.setAttribute('style', style); + if (stroke) + { + _circle.setAttribute('cx', String(radius + stroke.weight)); + _circle.setAttribute('cy', String(radius + stroke.weight)); + } + else + { + _circle.setAttribute('cx', String(radius)); + _circle.setAttribute('cy', String(radius)); + } + + _circle.setAttribute('rx', String(radius)); + _circle.setAttribute('ry', String(radius)); + + resize(x-radius, y-radius, (_circle as SVGCircleElement).getBBox()); + + } + } + + override protected function draw():void + { + drawCircle(0, 0, radius); + } + + } +} http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/7d529524/frameworks/projects/Graphics/src/main/flex/org/apache/flex/svg/Ellipse.as ---------------------------------------------------------------------- diff --git a/frameworks/projects/Graphics/src/main/flex/org/apache/flex/svg/Ellipse.as b/frameworks/projects/Graphics/src/main/flex/org/apache/flex/svg/Ellipse.as new file mode 100644 index 0000000..4104087 --- /dev/null +++ b/frameworks/projects/Graphics/src/main/flex/org/apache/flex/svg/Ellipse.as @@ -0,0 +1,94 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// +package org.apache.flex.svg +{ + COMPILE::SWF + { + import flash.geom.Point; + import flash.geom.Rectangle; + } + COMPILE::JS + { + import org.apache.flex.core.WrappedHTMLElement; + } + + public class Ellipse extends GraphicShape + { + + COMPILE::JS + private var _ellipse:WrappedHTMLElement; + + /** + * Draw the ellipse. + * @param xp The x position of the top-left corner of the bounding box of the ellipse. + * @param yp The y position of the top-left corner of the bounding box of the ellipse. + * @param width The width of the ellipse. + * @param height The height of the ellipse. + * + * @langversion 3.0 + * @playerversion Flash 10.2 + * @playerversion AIR 2.6 + * @productversion FlexJS 0.0 + * @flexjsignorecoercion org.apache.flex.core.WrappedHTMLElement + * @flexjsignorecoercion SVGEllipseElement + */ + public function drawEllipse(xp:Number, yp:Number, width:Number, height:Number):void + { + COMPILE::SWF + { + graphics.clear(); + applyStroke(); + beginFill(new Rectangle(xp, yp, width, height), new Point(xp,yp)); + graphics.drawEllipse(xp,yp,width,height); + endFill(); + } + COMPILE::JS + { + var style:String = getStyleStr(); + if (_ellipse == null) { + _ellipse = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse') as WrappedHTMLElement; + _ellipse.flexjs_wrapper = this; + element.appendChild(_ellipse); + } + _ellipse.setAttribute('style', style); + if (stroke) + { + _ellipse.setAttribute('cx', String(width / 2 + stroke.weight)); + _ellipse.setAttribute('cy', String(height / 2 + stroke.weight)); + } + else + { + _ellipse.setAttribute('cx', String(width / 2)); + _ellipse.setAttribute('cy', String(height / 2)); + } + _ellipse.setAttribute('rx', String(width / 2)); + _ellipse.setAttribute('ry', String(height / 2)); + + resize(x, y, (_ellipse as SVGEllipseElement).getBBox()); + + } + } + + override protected function draw():void + { + drawEllipse(0, 0, width, height); + } + + } +} http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/7d529524/frameworks/projects/Graphics/src/main/flex/org/apache/flex/svg/GraphicShape.as ---------------------------------------------------------------------- diff --git a/frameworks/projects/Graphics/src/main/flex/org/apache/flex/svg/GraphicShape.as b/frameworks/projects/Graphics/src/main/flex/org/apache/flex/svg/GraphicShape.as new file mode 100644 index 0000000..026ba93 --- /dev/null +++ b/frameworks/projects/Graphics/src/main/flex/org/apache/flex/svg/GraphicShape.as @@ -0,0 +1,213 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.flex.svg +{ + COMPILE::SWF + { + import flash.geom.Point; + import flash.geom.Rectangle; + } + COMPILE::JS + { + import org.apache.flex.core.WrappedHTMLElement; + } + + import org.apache.flex.core.UIBase; + + public class GraphicShape extends UIBase + { + private var _fill:IFill; + private var _stroke:IStroke; + + public function get stroke():IStroke + { + return _stroke; + } + + /** + * A solid color fill. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion FlexJS 0.0 + */ + public function set stroke(value:IStroke):void + { + _stroke = value; + } + + public function get fill():IFill + { + return _fill; + } + /** + * A solid color fill. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion FlexJS 0.0 + */ + public function set fill(value:IFill):void + { + _fill = value; + } + + /** + * Constructor + * + * @flexjsignorecoercion org.apache.flex.core.WrappedHTMLElement + */ + public function GraphicShape() + { + COMPILE::JS + { + element = document.createElementNS('http://www.w3.org/2000/svg', 'svg') as WrappedHTMLElement; + element.flexjs_wrapper = this; + element.style.left = 0; + element.style.top = 0; + //element.offsetParent = null; + positioner = element; + positioner.style.position = 'relative'; + } + } + + COMPILE::SWF + protected function applyStroke():void + { + if(stroke) + { + stroke.apply(this); + } + } + + COMPILE::SWF + protected function beginFill(targetBounds:Rectangle,targetOrigin:Point):void + { + if(fill) + { + fill.begin(this, targetBounds,targetOrigin); + } + } + + COMPILE::SWF + protected function endFill():void + { + if(fill) + { + fill.end(this); + } + } + + /** + * This is where the drawing methods get called from + */ + protected function draw():void + { + //Overwrite in subclass + } + + override public function addedToParent():void + { + COMPILE::SWF + { + super.addedToParent(); + } + draw(); + COMPILE::JS + { + element.style.overflow = 'visible'; + } + } + + /** + * @return {string} The style attribute. + */ + COMPILE::JS + public function getStyleStr():String + { + var fillStr:String; + if (fill) + { + fillStr = fill.addFillAttrib(this); + } + else + { + fillStr = 'fill:none'; + } + + var strokeStr:String; + if (stroke) + { + strokeStr = stroke.addStrokeAttrib(this); + } + else + { + strokeStr = 'stroke:none'; + } + + + return fillStr + ';' + strokeStr; + } + + + /** + * @param x X position. + * @param y Y position. + * @param bbox The bounding box of the svg element. + */ + COMPILE::JS + public function resize(x:Number, y:Number, bbox:SVGRect):void + { + var useWidth:Number = Math.max(this.width, bbox.width); + var useHeight:Number = Math.max(this.height, bbox.height); + + element.style.position = 'absolute'; + if (!isNaN(x)) element.style.top = String(x) + 'px'; + if (!isNaN(y)) element.style.left = String(y) + 'px'; + element.style.width = String(useWidth) + 'px'; + element.style.height = String(useHeight) + 'px'; + element.style.left = x; + element.style.top = y; + } + + COMPILE::JS + private var _x:Number; + COMPILE::JS + private var _y:Number; + COMPILE::JS + private var _xOffset:Number; + COMPILE::JS + private var _yOffset:Number; + + /** + * @param x X position. + * @param y Y position. + * @param xOffset offset from x position. + * @param yOffset offset from y position. + */ + COMPILE::JS + public function setPosition(x:Number, y:Number, xOffset:Number, yOffset:Number):void + { + _x = x; + _y = y; + _xOffset = xOffset; + _yOffset = yOffset; + element.style.left = xOffset; + element.style.top = yOffset; + } + } +} http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/7d529524/frameworks/projects/Graphics/src/main/flex/org/apache/flex/svg/GraphicsContainer.as ---------------------------------------------------------------------- diff --git a/frameworks/projects/Graphics/src/main/flex/org/apache/flex/svg/GraphicsContainer.as b/frameworks/projects/Graphics/src/main/flex/org/apache/flex/svg/GraphicsContainer.as new file mode 100644 index 0000000..90f33fb --- /dev/null +++ b/frameworks/projects/Graphics/src/main/flex/org/apache/flex/svg/GraphicsContainer.as @@ -0,0 +1,302 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.flex.svg +{ + COMPILE::SWF + { + import flash.display.GraphicsPath; + import flash.display.Shape; + import flash.display.Sprite; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.text.TextFieldType; + + import org.apache.flex.core.CSSTextField; + import org.apache.flex.graphics.utils.PathHelper; + } + COMPILE::JS + { + import org.apache.flex.core.WrappedHTMLElement; + } + + /** + * GraphicsContainer is a surface on which you can + * draw various graphic elements such as Rect, Circle, + * Ellipse, Path etc. + * Use this class if you want to draw multiple graphic + * shapes on a single container. + * + */ + public class GraphicsContainer extends GraphicShape + { + /** + * Removes all of the drawn elements of the container. + * + * @langversion 3.0 + * @playerversion Flash 10.2 + * @playerversion AIR 2.6 + * @productversion FlexJS 0.0.3 + */ + public function removeAllElements():void + { + COMPILE::SWF + { + graphics.clear(); + } + COMPILE::JS + { + var svg:HTMLElement = element; + while (svg.lastChild) { + svg.removeChild(svg.lastChild); + } + } + } + + /** + * Draw the rectangle. + * @param x The x position of the top-left corner of the rectangle. + * @param y The y position of the top-left corner. + * @param width The width of the rectangle. + * @param height The height of the rectangle. + * + * @langversion 3.0 + * @playerversion Flash 10.2 + * @playerversion AIR 2.6 + * @productversion FlexJS 0.0.3 + * @flexjsignorecoercion org.apache.flex.core.WrappedHTMLElement + */ + public function drawRect(x:Number, y:Number, width:Number, height:Number):void + { + COMPILE::SWF + { + applyStroke(); + beginFill(new Rectangle(x, y, width, height), new Point(x,y) ); + graphics.drawRect(x, y, width, height); + endFill(); + } + COMPILE::JS + { + var style:String = getStyleStr(); + var rect:WrappedHTMLElement = document.createElementNS('http://www.w3.org/2000/svg', 'rect') as WrappedHTMLElement; + rect.flexjs_wrapper = this; + rect.style.left = x; + rect.style.top = y; + rect.setAttribute('style', style); + rect.setAttribute('x', String(x) + 'px'); + rect.setAttribute('y', String(y) + 'px'); + rect.setAttribute('width', String(width) + 'px'); + rect.setAttribute('height', String(height) + 'px'); + element.appendChild(rect); + } + } + + COMPILE::SWF + public function createRect(x:Number, y:Number, width:Number, height:Number):void + { + var color:uint = (fill as SolidColor).color; + var alpha:uint = (fill as SolidColor).alpha; + + var shape:Sprite = new Sprite(); + shape.graphics.beginFill(color,alpha); + shape.graphics.drawRect(0, 0, width, height); + shape.graphics.endFill(); + shape.x = x; + shape.y = y; + addChild(shape); + } + + /** + * Draw the ellipse. + * @param x The x position of the top-left corner of the bounding box of the ellipse. + * @param y The y position of the top-left corner of the bounding box of the ellipse. + * @param width The width of the ellipse. + * @param height The height of the ellipse. + * + * @langversion 3.0 + * @playerversion Flash 10.2 + * @playerversion AIR 2.6 + * @productversion FlexJS 0.0.3 + * @flexjsignorecoercion org.apache.flex.core.WrappedHTMLElement + */ + public function drawEllipse(x:Number, y:Number, width:Number, height:Number):void + { + COMPILE::SWF + { + applyStroke(); + beginFill(new Rectangle(x,y,width,height), new Point(x,y)); + graphics.drawEllipse(x,y,width,height); + endFill(); + } + COMPILE::JS + { + var style:String = getStyleStr(); + var ellipse:WrappedHTMLElement = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse') as WrappedHTMLElement; + ellipse.flexjs_wrapper = this; + ellipse.style.left = x; + ellipse.style.top = y; + ellipse.setAttribute('style', style); + ellipse.setAttribute('cx', String(x + width / 2)); + ellipse.setAttribute('cy', String(y + height / 2)); + ellipse.setAttribute('rx', String(width / 2)); + ellipse.setAttribute('ry', String(height / 2)); + element.appendChild(ellipse); + } + } + + /** + * Draw the circle. + * @param x The x location of the center of the circle + * @param y The y location of the center of the circle. + * @param radius The radius of the circle. + * + * @langversion 3.0 + * @playerversion Flash 10.2 + * @playerversion AIR 2.6 + * @productversion FlexJS 0.0 + * @flexjsignorecoercion org.apache.flex.core.WrappedHTMLElement + */ + public function drawCircle(x:Number, y:Number, radius:Number):void + { + COMPILE::SWF + { + applyStroke(); + beginFill(new Rectangle(x,y,radius*2, radius*2),new Point(x-radius,y-radius)); + graphics.drawCircle(x,y,radius); + endFill(); + } + COMPILE::JS + { + var style:String = getStyleStr(); + var circle:WrappedHTMLElement = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse') as WrappedHTMLElement; + circle.flexjs_wrapper = this; + circle.style.left = x; + circle.style.top = y; + circle.setAttribute('style', style); + circle.setAttribute('cx', String(x)); + circle.setAttribute('cy', String(y)); + circle.setAttribute('rx', String(radius)); + circle.setAttribute('ry', String(radius)); + element.appendChild(circle); + + } + } + + /** + * Draw the path. + * @param data A string containing a compact represention of the path segments. + * 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. + * + * If the segment command is upper-case, the parameters are absolute values. + * If the segment command is lower-case, the parameters are relative values. + * + * @langversion 3.0 + * @playerversion Flash 10.2 + * @playerversion AIR 2.6 + * @productversion FlexJS 0.0 + * @flexjsignorecoercion org.apache.flex.core.WrappedHTMLElement + */ + public function drawPath(data:String):void + { + COMPILE::SWF + { + applyStroke(); + var bounds:Rectangle = PathHelper.getBounds(data); + beginFill(bounds,bounds.topLeft); + var graphicsPath:GraphicsPath = PathHelper.getSegments(data); + graphics.drawPath(graphicsPath.commands, graphicsPath.data); + endFill(); + } + COMPILE::JS + { + var style:String = getStyleStr(); + var path:WrappedHTMLElement = document.createElementNS('http://www.w3.org/2000/svg', 'path') as WrappedHTMLElement; + path.flexjs_wrapper = this; + path.style.left = 0; + path.style.top = 0; + path.setAttribute('style', style); + path.setAttribute('d', data); + element.appendChild(path); + } + } + + public function drawLine():void + { + + } + + public function drawPolygon():void + { + + } + + /** + * Draw a string of characters. + * @param value The string to draw. + * @param x The x location of the center of the circle + * @param y The y location of the center of the circle. + * + * @langversion 3.0 + * @playerversion Flash 10.2 + * @playerversion AIR 2.6 + * @productversion FlexJS 0.0 + * @flexjsignorecoercion org.apache.flex.core.WrappedHTMLElement + * @flexjsignorecoercion Text + * @flexjsignorecoercion Node + */ + public function drawText(value:String, x:Number, y:Number):Object + { + COMPILE::SWF + { + var textField:CSSTextField = new CSSTextField(); + addChild(textField); + + textField.selectable = false; + textField.type = TextFieldType.DYNAMIC; + textField.mouseEnabled = false; + textField.autoSize = "left"; + textField.text = value; + + var lineColor:SolidColorStroke = stroke as SolidColorStroke; + if (lineColor) { + textField.textColor = lineColor.color; + textField.alpha = lineColor.alpha; + } + + textField.x = x; + textField.y = y + textField.textHeight/4; + + return textField; + + } + COMPILE::JS + { + var style:String = getStyleStr(); + var text:WrappedHTMLElement = document.createElementNS('http://www.w3.org/2000/svg', 'text') as WrappedHTMLElement; + text.flexjs_wrapper = this; + text.style.left = x; + text.style.top = y; + text.setAttribute('style', style); + text.setAttribute('x', String(x) + 'px'); + text.setAttribute('y', String(y + 15) + 'px'); + var textNode:Text = document.createTextNode(value) as Text; + text.appendChild(textNode as Node); + element.appendChild(text); + return text; + } + } + } +}
