In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

Sewing

/**
 * Copyright Saqoosha ( http://wonderfl.net/user/Saqoosha )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/o5bL
 */

package {
    
    import caurina.transitions.Equations;
    import caurina.transitions.Tweener;
    import caurina.transitions.properties.CurveModifiers;
    
    import flash.display.Graphics;
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageQuality;
    import flash.display.StageScaleMode;
    import flash.events.Event;
    import flash.geom.Point;
    import flash.net.URLLoader;
    import flash.net.URLLoaderDataFormat;
    import flash.net.URLRequest;
    import flash.utils.setTimeout;
    
    [SWF(width=465, height=465, backgroundColor=0xffffff, frameRate=60)]
    
    public class Sewing extends Sprite {

        private var _loader:URLLoader;
        private var _canvas:Sprite;
        private var _paths:Array;
    
        public function Sewing() {
            Wonderfl.capture_delay(8);
            
            this.stage.quality = StageQuality.BEST;
            this.stage.align = StageAlign.TOP_LEFT;
            this.stage.scaleMode = StageScaleMode.NO_SCALE;
            
            CurveModifiers.init();
            
            this._canvas = this.addChild(new Sprite()) as Sprite;
            
            this._loader = new URLLoader();
            this._loader.dataFormat = URLLoaderDataFormat.TEXT;
            this._loader.addEventListener(Event.COMPLETE, this._handleLoaded);
            this._loader.load(new URLRequest('http://saqoo.sh/a/labs/wonderfl/saqoosha.svg'));
        }
        
        private function _handleLoaded(e:Event):void {
            var svg:XML = new XML(this._loader.data);
            this._paths = [];
            var path:SVGPath;
            var delay:Number = 2.0;
            var cx:Number = parseInt(svg.@width) / 2;
            var cy:Number = parseInt(svg.@height) / 2;
            this._canvas.x = this.stage.stageWidth / 2 - cx;
            this._canvas.y = this.stage.stageHeight / 2 - cy;
            for each (var pathNode:XML in svg..*::path) {
                path = new SVGPath(pathNode);
                this._paths.push(path);
                var i:Number = 0;
                var di:Number = Math.PI * 2 / path.points.length;
                var px:Number = cx;
                var py:Number = this.stage.stageHeight - this._canvas.y;
                for each (var pt:Point in path.points) {
                    Tweener.addTween(pt, {
                        x: pt.x,
                        y: pt.y,
                        _bezier:[
                            { x:cx + Math.sin(delay * 1.3) * 300 + Math.sin(delay * 10.3) * 80, y:50 },
                            { x:100 - Math.sin(delay * 7.2) * 80, y:50 }
                        ],
                        time: 1.0,
                        delay: delay,
                        transition: Equations.easeNone
                    });
                    pt.x = px;
                    pt.y = py;
                    delay += 0.004;
                    i += di;
                }
            }
            this.addEventListener(Event.ENTER_FRAME, this._drawPaths);
            setTimeout(function ():void {
                removeEventListener(Event.ENTER_FRAME, _drawPaths);
            }, (delay + 1) * 1000);
        }
        
        private function _drawPaths(e:Event):void {
            this._canvas.graphics.clear();
            for each (var path:SVGPath in this._paths) {
                path.draw(this._canvas.graphics);
            }
        }
    }
}















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


/**
* @class PathToArray
* @author Helen Triolo (with contributions from many people)
* @version 1.01 
* @description  Takes as input an SVG Path node (eg, from Illustrator 10, SVG Factory, etc, but must
*            not contain any CRLF characters), and an empty array. 
*            Parses the path node to make an array of drawing commands, which include cubic bezier 
*            draw commands.  Converts the cubic beziers to an array of equivalent quad beziers, 
*            using Robert Penner's code to convert with accuracy within 1 pixel.  
*            Drawing commands are produced in the format originally devised by Peter Hall
*           in his ASVDrawing class.  These are the possible elements in array dCmds (and the 
*            corresponding Flash drawing API commands to apply them):
*                ['M',[x,y]]                    moveTo(x,y)
*                ['L',[x,y]]                    lineTo(x,y)
*                ['C',[cx,cy,ax,ay]]            moveTo(cx,cy,ax,ay)
*                ['S',[width,color,alpha]]    lineStyle(widtb,color,alpha)
*                ['F',[color,alpha]]            beginFill(color,alpha)
*                ['EF']                        endFill()
* History:
*    v1.00        2005/11/13    Original release
*    v1.01        2006/05/06    Stroke corrected to stroke, line 250 (thanks Gábor Szabó)
*
* @param svgnode (XMLNode) Path node from SVG file
* @param dCmds (Array) Empty array to write commands to
*/

class SVGPath {

    private var _commands:Array;
    public function get commands():Array { return this._commands }
    
    private var _points:Array;
    public function get points():Array { return this._points }
    private function addPoint(... points:Array):void {
        for each (var pt:Point in points) {
            this._addPoint(pt);
        }
    }
    private function _addPoint(pt:Point):void {
        if (pt.x < this._bounds.left) {
            this._bounds.left = pt.x;
        } else if (this._bounds.right < pt.x) {
            this._bounds.right = pt.x;
        }
        if (pt.y < this._bounds.top) {
            this._bounds.top = pt.y;
        } else if (this._bounds.bottom < pt.y) {
            this._bounds.bottom = pt.y;
        }
        this._points.push(pt);
    }
    
    private var _bounds:Rectangle;
    public function get boundsRect():Rectangle { return this._bounds }

    private var _hasFill:Boolean;
    public function get hasFill():Boolean { return this._hasFill }
    
    private var _hasStroke:Boolean;
    public function get hasStroke():Boolean { return this._hasStroke }
    
    public function swapFillAndStroke():void {
        var flg:Boolean = this._hasFill;
        this._hasFill = this._hasStroke;
        this._hasStroke = flg;
        
        var val:Number = this._fillColor;
        this._fillColor = this._strokeColor;
        this._strokeColor = val;
        
        val = this._fillAlpha;
        this._fillAlpha = this._strokeAlpha;
        this._strokeAlpha = val;
    }

    private var _fillColor:Number;
    public function get fillColor():Number { return this._fillColor }
    public function set fillColor(col:Number):void { this._fillColor = col }
    
    private var _fillAlpha:Number;
    public function get fillAlpha():Number { return this._fillAlpha }
    public function set fillAlpha(al:Number):void { this._fillAlpha = al }
    
    private var _strokeColor:Number;
    public function get strokeColor():Number { return this._strokeColor }
    public function set strokeColor(col:Number):void { this._strokeColor = col }
    
    private var _strokeAlpha:Number;
    public function get strokeAlpha():Number { return this._strokeAlpha }
    public function set strokeAlpha(al:Number):void { this._strokeAlpha = al }
    
    private var _strokeWidth:Number;
    public function get strokeWidth():Number { return this._strokeWidth }
    public function set strokeWidth(wd:Number):void { this._strokeWidth = wd }

    /**
     * 
     */
    public function SVGPath(svgNode:XML) {
        this._commands = [];
        this._points = [];
        this._bounds = new Rectangle();
        this._bounds.top = this._bounds.left = Number.MAX_VALUE;
        this._bounds.bottom = this._bounds.right = Number.MIN_VALUE;
        this._hasFill = false;
        this._hasStroke = false;
        
        this.makeDrawCmds(this.extractCmds(svgNode));
    }

    /**
    * @method extractCmds ()
    * @param node (XMLNode) SVG path node
    * @description Parse path node and convert to array of SVG drawing commands and data
    *            eg, M,250.8,33.8,c,-33.6,-9.7,-42,19.1,-48.2,22.6,s,-27.9,2.2,-33.3,5.8,
    *                c,-5.3,3.5,-17.3,23.5,-8.4,41.6
    * @returns (Array) array of drawing commands
    */    
    private function extractCmds(node:XML):Array {
        var i:Number;
        var startColor:Number;
        var thisColor:Number;

        //var hasFill:Boolean = false;
        var hasTransform:Boolean = false;
        //var hasStroke:Boolean = false;
        var hasStrokeWidth:Boolean = false;
        var hasRotate:Number = 0;
        var dstring:String = "";
        var rotation:Number;

        // is there a fill attribute, a transform attribute, a stroke attribute?
        for each (var a:XML in node.attributes()) {
            switch (a.name().toString()) {
                case 'fill': this._hasFill = true; break;
                case 'transform': hasTransform = true; break;
                case 'stroke': this._hasStroke = true; break;
                case 'stroke-width': hasStrokeWidth = true; break;
            }
        }
        if (this._hasFill) {
            // parse for fill color specification
            // if a hex number is specified, startColor will be > 0
            // if a color name is specified, startColor will be 0
            var fillStr:String = node.@fill;
            if (fillStr == 'none') {
                this._hasFill = false;
            } else {
                startColor = fillStr.indexOf("#") + 1;
                if (startColor == 0) {   // name specified instead of color number
                    thisColor = SVGColor.getByName(fillStr);
                    if (isNaN(thisColor)) {
                        this._hasFill = false;
                    } else {
                        this._fillColor = thisColor;
                        this._fillAlpha = 1.0;
                    }
                } else {
                    this._fillColor = parseInt(fillStr.substr(startColor, 6), 16);
                    this._fillAlpha = 1.0;
                }
            }
        }
        
        // stroke: color, width, alpha
        if (this._hasStroke) {
            // parse for stroke color specification
            var strokeStr:String = node.@stroke;
            if (strokeStr == 'none') {
                this._hasStroke = false;
            } else {
                startColor = strokeStr.indexOf("#")+1;        
                if (startColor == 0) {   // name specified instead of color number
                    thisColor = SVGColor.getByName(strokeStr);
                    if (isNaN(thisColor)) {
                        this._hasStroke = false;
                    } else {
                        this._strokeWidth = 0;
                        this._strokeColor = SVGColor.getByName(strokeStr);
                        this._strokeAlpha = 1.0;
                    }
                } else {
                    this._strokeWidth = 0;
                    this._strokeColor = parseInt(strokeStr.substr(startColor,6),16);
                    this._strokeAlpha = 1.0;
                }
            }
        }
        
        if (hasStrokeWidth) this._strokeWidth = Number(node.attribute("stroke-width"));
    
        // if stroke and fill are both undefined, set fill to black 
        if (!this._hasFill && !this._hasStroke) {
            this._hasFill = true;
            this._fillColor = 0;
            this._fillAlpha = 1.0;
        }
        
        if (hasTransform) {
            // parse for rotation specification
            var transformStr:String = node.@transform;
            hasRotate = transformStr.indexOf("rotate");
            if (hasRotate > -1) {
                var startRotate:Number = transformStr.indexOf("(");
                var endRotate:Number = transformStr.indexOf(")");
                rotation = parseInt(transformStr.substr(startRotate+1, endRotate-startRotate));
            } else {
                rotation = 0;
            }
    
        } else rotation = 0;
        
        // if commas included, is it Adobe Illustrator (no spaces) or SVG Factory/other?
        dstring = node.@d;
        if (dstring.indexOf(",") > -1) {  // has commas?
            if (dstring.indexOf(" ") > -1) {  // yes, has spaces?
                // change spaces to commas, then deal as for Illustrator
                dstring = String2.replace(dstring," ",",");
            }
        } else {  // no commas
            // get rid of extra spaces and change rest to commas
            dstring = String2.shrinkSequencesOf(dstring, " ");
            dstring = String2.replace(dstring, " ",",");
        }        
        dstring = String2.replace(dstring, "c",",c,");
        dstring = String2.replace(dstring, "C",",C,");
        dstring = String2.replace(dstring, "S",",S,");
        dstring = String2.replace(dstring, "s",",s,");
        // separate the z from the last element
        dstring = String2.replace(dstring, "z",",z");
        // change the following if M can be mid-path
        dstring = String2.replace(dstring, "M","M,");
        dstring = String2.replace(dstring, "L",",L,");
        dstring = String2.replace(dstring, "l",",l,");
        dstring = String2.replace(dstring, "H",",H,");
        dstring = String2.replace(dstring, "h",",h,");
        dstring = String2.replace(dstring, "V",",V,");
        dstring = String2.replace(dstring, "v",",v,");
        dstring = String2.replace(dstring, "Q",",Q,");
        dstring = String2.replace(dstring, "q",",q,");
        dstring = String2.replace(dstring, "T",",T,");
        dstring = String2.replace(dstring, "t",",t,");
        // Adobe includes no delimiter before negative numbers
        dstring = String2.replace(dstring, "-",",-");
        // get rid of any dup commas we might have introduced
        dstring = String2.replace(dstring, ",,",",");
        // get rid of spaces
        // (cr/lf's have to be removed before the xml object can be created,
        //  so that is done in xml.onData method)
        dstring = String2.replace(dstring, " ","");
        dstring = String2.replace(dstring, "\t","");

        return dstring.split(",");    
    }

    /**
    * @method makeDrawCmds
    * @param svgCmds (Array) array of svg draw commands (as output from extractCmds)
    * @description Convert svg draw commands to array of ASVDrawing commands: _commands
    */    
    private function makeDrawCmds(svgCmds:Array):void {
        var j:Number = 0, ii:int;
        var qc:Array;
        var firstP:Point;
        var lastP:Point;
        var lastC:Point;
        var cmd:String;
        var cp:Point, pp:Point;
        
        do {
            cmd = svgCmds[j++];
            switch (cmd) {
            case "M" :
                // moveTo point
                firstP = lastP = new Point(Number(svgCmds[j]), Number(svgCmds[j+1]));
                if (this._hasFill) {
                    _commands.push(new BeginFillCommand(this._fillColor, this._fillAlpha));
                }
                if (this._hasStroke) {
                    _commands.push(new LineStyleCommand(this._strokeWidth, this._strokeColor, this._strokeAlpha));
                }
                _commands.push(new MoveToCommand(firstP));
                this.addPoint(firstP);
                j += 2;
                if (j < svgCmds.length && !isNaN(Number(svgCmds[j]))) {  
                    do {
                        // if multiple points listed, add the rest as lineTo points
                        lastP = new Point(Number(svgCmds[j]), Number(svgCmds[j+1]));
                        _commands.push(new LineToCommand(lastP));
                        this.addPoint(lastP);
                        firstP = lastP;
                        j += 2;
                    } while (j < svgCmds.length && !isNaN(Number(svgCmds[j])));
                }
                break;
                
            case "l" :
                do {
                    lastP = new Point(lastP.x+Number(svgCmds[j]), lastP.y+Number(svgCmds[j+1]));
                    _commands.push(new LineToCommand(lastP));
                    this.addPoint(lastP);
                    firstP = lastP;
                    j += 2;
                } while (j < svgCmds.length && !isNaN(Number(svgCmds[j])));
                break;
                
            case "L" :
                do {
                    lastP = new Point(Number(svgCmds[j]), Number(svgCmds[j+1]));
                    _commands.push(new LineToCommand(lastP));                
                    this.addPoint(lastP);
                    firstP = lastP;
                    j += 2;
                } while (j < svgCmds.length && !isNaN(Number(svgCmds[j])));
                break;
                
            case "h" :
                do {
                    lastP = new Point(lastP.x+Number(svgCmds[j]), lastP.y);
                    _commands.push(new LineToCommand(lastP));
                    this.addPoint(lastP);
                    firstP = lastP;
                    j += 1;
                } while (j < svgCmds.length && !isNaN(Number(svgCmds[j])));
                break;
                
            case "H" :
                do {
                    lastP = new Point(Number(svgCmds[j]), lastP.y);
                    _commands.push(new LineToCommand(lastP));
                    this.addPoint(lastP);
                    firstP = lastP;
                    j += 1;
                } while (j < svgCmds.length && !isNaN(Number(svgCmds[j])));
                break;
                
            case "v" :
                do {
                    lastP = new Point(lastP.x, lastP.y+Number(svgCmds[j]));
                    _commands.push(new LineToCommand(lastP));
                    this.addPoint(lastP);
                    firstP = lastP;
                    j += 1;
                } while (j < svgCmds.length && !isNaN(Number(svgCmds[j])));
                break;
                
            case "V" :
                do {
                    lastP = new Point(lastP.x, Number(svgCmds[j]));
                    _commands.push(new LineToCommand(lastP));
                    this.addPoint(lastP);
                    firstP = lastP;
                    j += 1;
                } while (j < svgCmds.length && !isNaN(Number(svgCmds[j])));
                break;
    
            case "q" :
                do {
                    // control is relative to lastP, not lastC
                    lastC = new Point(lastP.x+Number(svgCmds[j]), lastP.y+Number(svgCmds[j+1]));
                    lastP = new Point(lastP.x+Number(svgCmds[j+2]), lastP.y+Number(svgCmds[j+3]));
                    _commands.push(new CurveToCommand(lastC, lastP));
                    this.addPoint(lastC, lastP);
                    firstP = lastP;
                    j += 4;
                } while (j < svgCmds.length && !isNaN(Number(svgCmds[j])));
                break;
                
            case "Q" :
                do {
                    lastC = new Point(Number(svgCmds[j]), Number(svgCmds[j+1]));
                    lastP = new Point(Number(svgCmds[j+2]), Number(svgCmds[j+3]));
                    _commands.push(new CurveToCommand(lastC, lastP));
                    this.addPoint(lastC, lastP);
                    firstP = lastP;
                    j += 4;
                } while (j < svgCmds.length && !isNaN(Number(svgCmds[j])));
                break;
                
            case "c" :
                do {
                // don't save if c1.x=c1.y=c2.x=c2.y=0 
                    if (!Number(svgCmds[j]) && !Number(svgCmds[j+1]) && !Number(svgCmds[j+2]) && !Number(svgCmds[j+3])) {
                    } else {
                        qc = [];
                        Math2.getQuadBez_RP(
                            {x:lastP.x, y:lastP.y},   
                            {x:lastP.x+Number(svgCmds[j]), y:lastP.y+Number(svgCmds[j+1])},
                            {x:lastP.x+Number(svgCmds[j+2]), y:lastP.y+Number(svgCmds[j+3])},
                            {x:lastP.x+Number(svgCmds[j+4]), y:lastP.y+Number(svgCmds[j+5])},
                            1, qc);
                        for (ii=0; ii<qc.length; ii++) {
                            cp = new Point(qc[ii].cx, qc[ii].cy);
                            pp = new Point(qc[ii].p2x, qc[ii].p2y);
                            _commands.push(new CurveToCommand(cp, pp));
                            this.addPoint(cp, pp);
                        }
                        lastC = new Point(lastP.x+Number(svgCmds[j+2]), lastP.y+Number(svgCmds[j+3]));
                        lastP = new Point(lastP.x+Number(svgCmds[j+4]), lastP.y+Number(svgCmds[j+5]));
                        this.addPoint(lastC, lastP);
                        firstP = lastP;
                    }
                    j += 6;
                } while (j < svgCmds.length && !isNaN(Number(svgCmds[j])));
                break;
    
            case "C" :
                do {
                // don't save if c1.x=c1.y=c2.x=c2.y=0 
                    if (!Number(svgCmds[j]) && !Number(svgCmds[j+1]) && !Number(svgCmds[j+2]) && !Number(svgCmds[j+3])) {
                    } else {
                        qc = [];
                        Math2.getQuadBez_RP(
                            {x:firstP.x, y:firstP.y},   
                            {x:Number(svgCmds[j]), y:Number(svgCmds[j+1])},
                            {x:Number(svgCmds[j+2]), y:Number(svgCmds[j+3])},
                            {x:Number(svgCmds[j+4]), y:Number(svgCmds[j+5])},
                            1, qc);
                        for (ii=0; ii<qc.length; ii++) {
                            cp = new Point(qc[ii].cx, qc[ii].cy);
                            pp = new Point(qc[ii].p2x, qc[ii].p2y);
                            _commands.push(new CurveToCommand(cp, pp));
                            this.addPoint(cp, pp);
                        }
                        lastC = new Point(lastP.x+Number(svgCmds[j+2]), lastP.y+Number(svgCmds[j+3]));
                        lastP = new Point(Number(svgCmds[j+4]), Number(svgCmds[j+5]));
                        this.addPoint(lastC, lastP);
                        firstP = lastP;
                    }
                    j += 6;
                } while (j < svgCmds.length && !isNaN(Number(svgCmds[j])));
                break;
                
            case "s" :
                do {
                // don't save if c1.x=c1.y=c2.x=c2.y=0 
                    if (!Number(svgCmds[j]) && !Number(svgCmds[j+1]) && !Number(svgCmds[j+2]) && !Number(svgCmds[j+3])) {
                    } else {
                        qc = [];
                        Math2.getQuadBez_RP(
                            {x:firstP.x, y:firstP.y},   
                            {x:lastP.x + (lastP.x - lastC.x), y:lastP.y + (lastP.y - lastC.y)},
                            {x:lastP.x+Number(svgCmds[j]), y:lastP.y+Number(svgCmds[j+1])},
                            {x:lastP.x+Number(svgCmds[j+2]), y:lastP.y+Number(svgCmds[j+3])},
                            1, qc);
                        for (ii=0; ii<qc.length; ii++) {
                            cp = new Point(qc[ii].cx, qc[ii].cy);
                            pp = new Point(qc[ii].p2x, qc[ii].p2y);
                            _commands.push(new CurveToCommand(cp, pp));
                            this.addPoint(cp, pp);
                        }
                        lastC = new Point(lastP.x+Number(svgCmds[j]), lastP.y+Number(svgCmds[j+1]));
                        lastP = new Point(lastP.x+Number(svgCmds[j+2]), lastP.y+Number(svgCmds[j+3]));
                        this.addPoint(lastC, lastP);
                        firstP = lastP;
                    }
                    j += 4;
                } while (j < svgCmds.length && !isNaN(Number(svgCmds[j])));
                break;
                
            case "S" :
                do {
                // don't save if c1.x=c1.y=c2.x=c2.y=0 
                    if (!Number(svgCmds[j]) && !Number(svgCmds[j+1]) && !Number(svgCmds[j+2]) && !Number(svgCmds[j+3])) {
                    } else {
                        qc = [];
                        Math2.getQuadBez_RP(
                            {x:firstP.x, y:firstP.y},   
                            {x:lastP.x + (lastP.x - lastC.x), y:lastP.y + (lastP.y - lastC.y)},
                            {x:Number(svgCmds[j]), y:Number(svgCmds[j+1])},
                            {x:Number(svgCmds[j+2]), y:Number(svgCmds[j+3])}, 
                            1, qc);
                        for (ii=0; ii<qc.length; ii++) {
                            cp = new Point(qc[ii].cx, qc[ii].cy);
                            pp = new Point(qc[ii].p2x, qc[ii].p2y);
                            _commands.push(new CurveToCommand(cp, pp));
                            this.addPoint(cp, pp);
                        }
                        lastC = new Point(Number(svgCmds[j]), Number(svgCmds[j+1]));
                        lastP = new Point(Number(svgCmds[j+2]), Number(svgCmds[j+3]));
                        this.addPoint(lastC, lastP);
                        firstP = lastP;
                    }
                    j += 4;
                } while (j < svgCmds.length && !isNaN(Number(svgCmds[j])));
                break;
                
            case "z" :
            case "Z" :
                if (!firstP.equals(lastP)) {
                    _commands.push(new LineToCommand(firstP));
                    this.addPoint(firstP);
                }
                j++;
                break;        
                
            } // end switch
        }  while (j < svgCmds.length);
    }
    
    /**
     * 
     */
    public function draw(graphics:Graphics):void {
        for each (var command:SVGPathCommand in this._commands) {
            command.execute(graphics);
        }
    }
    
}



class String2 {
    
    /**
    * @class String2
    * @author Helen Triolo, with inclusions from Tim Groleau
    * @description String functions not included in String needed for path->array conversion
    */

    /**
    * @method replace ()
    * @description Replaces sFind in s with sReplace
    * @param s (String) original string
    * @param sFind (String) part to be replaced
    * @param sReplace (String) string to replace it with
    * @returns (String) string with replacement
    */
    public static function replace(s:String, sFind:String, sReplace:String):String {
      return s.split(sFind).join(sReplace);
    }
    
    /**
    * @method shrinkSequencesOf (Groleau)
    * @description Shrinks all sequences of a given character in a string to one
    * @param s (String) original string
    * @param ch (String) character to be found
    * @returns (String) string with sequences shrunk
    */
    public static function shrinkSequencesOf(s:String, ch:String):String {
        var len:Number = s.length;
        var idx:Number = 0;
        var idx2:Number = 0;
        var rs:String = "";
        
        while ((idx2 = s.indexOf(ch, idx) + 1) != 0) {
            // include string up to first character in sequence
            rs += s.substring(idx, idx2);
            idx = idx2;
            
            // remove all subsequent characters in sequence
            while ((s.charAt(idx) == ch) && (idx < len)) idx++;
        }
        return rs + s.substring(idx, len);    
    }
}


class Math2 {
    
    /**
    * @class Math2
    * @author Helen Triolo, with inclusions from Robert Penner, Tim Groleau
    * @description Math functions not included in Math needed for path->array conversion
    */
    
    /**
    * @method ratioTo (Groleau)
    * @description Returns the point on segment [p1,p2] which is ratio times the total distance 
    *                between p1 and p2 away from p1
    * @param p1 (Object) x and y values of point p1
    * @param p2 (Object) x and y values of point p2
    * @param ratio (Number) real
    * @returns Object
    */
    public static function ratioTo(p1:Object, p2:Object, ratio:Number):Object {
        return {x:p1.x + (p2.x - p1.x) * ratio, y:p1.y + (p2.y - p1.y) * ratio };
    }

    /**
    * @method intersect2Lines (Penner)
    * @description Returns the point of intersection between two lines
    * @param p1, p2 (Objects) points on line 1
    * @param p3, p4 (Objects) points on line 2
    * @returns Object (point of intersection)
    */
    public static function intersect2Lines (p1:Object, p2:Object, p3:Object, p4:Object):Object {
        var x1:Number = p1.x; var y1:Number = p1.y;
        var x4:Number = p4.x; var y4:Number = p4.y;
        var dx1:Number = p2.x - x1;
        var dx2:Number = p3.x - x4;

       if (!(dx1 || dx2)) return NaN;
       var m1:Number = (p2.y - y1) / dx1;
       var m2:Number = (p3.y - y4) / dx2;
       if (!dx1) {
          return { x:x1, y:m2 * (x1 - x4) + y4 };
       } else if (!dx2) {
          return { x:x4, y:m1 * (x4 - x1) + y1 };
       }
       var xInt:Number = (-m2 * x4 + y4 + m1 * x1 - y1) / (m1 - m2);
       var yInt:Number = m1 * (xInt - x1) + y1;
       return { x:xInt,y:yInt };
    }

    /**
    * @method rotation
    * @description Returns the angle in degrees from the horizontal to a point dy up and dx over
    * @param dy (Number) pixels
    * @param dx (Number) pixels
    * @returns Number (angle, degrees)
    */
    public static function rotation(dy:Number, dx:Number):Number {
        return Math.atan2(dy, dx) * 180/Math.PI;
    }

    /**
    * @method midPt
    * @description Returns the midpoint (x/y) of a line segment from p1x/p1y to p2x/p2y
    * @param p1x (Number) pixels
    * @param p1y (Number) pixels
    * @param p2x (Number) pixels
    * @param p2y (Number) pixels
    * @returns Object (midpoint)
    */
    public static function midPt(p1x:Number, p1y:Number, p2x:Number, p2y:Number):Object {
        return {x:(p1x + p2x)/2, y:(p1y + p2y)/2};
    }

    /**
    * @method getQuadBez_RP (Penner)
    * @description  Approximates a cubic bezier with as many quadratic bezier segments (n) as required 
    *            to achieve a specified tolerance
    * @param p1 (Object) endpoint
    * @param c1 (Object) 1st control point
    * @param c2 (Object) 2nd control point
    * @param p2 (Object) endpoint
    * @param k: tolerance (low number = most accurate result)
    * @param qcurves (Array) will contain array of quadratic bezier curves, each element containing
    *        p1x, p1y, cx, cy, p2x, p2y
    */
     public static function getQuadBez_RP(p1:Object, c1:Object, c2:Object, p2:Object, k:Number, qcurves:Array):void {
        // find intersection between bezier arms
        var s:Object = Math2.intersect2Lines (p1, c1, c2, p2);
        // find distance between the midpoints
        var dx:Number = (p1.x + p2.x + s.x * 4 - (c1.x + c2.x) * 3) * .125;
        var dy:Number = (p1.y + p2.y + s.y * 4 - (c1.y + c2.y) * 3) * .125;
        // split curve if the quadratic isn't close enough
        if (dx*dx + dy*dy > k) {
            var halves:Object = Math2.bezierSplit (p1.x, p1.y, c1.x, c1.y, c2.x, c2.y, p2.x, p2.y);
            var b0:Object = halves.b0; var b1:Object = halves.b1;
            // recursive call to subdivide curve
            getQuadBez_RP (p1,     b0.c1, b0.c2, b0.p2, k, qcurves);
            getQuadBez_RP (b1.p1,  b1.c1, b1.c2, p2,    k, qcurves);
        } else {
            // end recursion by saving points
            qcurves.push({p1x:p1.x, p1y:p1.y, cx:s.x, cy:s.y, p2x:p2.x, p2y:p2.y});
        }
    }
        
    /**
    * @method bezierSplit (Penner)
    * @description    Divides a cubic bezier curve into two halves (each also cubic beziers)
    * @param p1x (Number) pixels, endpoint 1
    * @param p1y (Number) pixels
    * @param c1x (Number) pixels, control point 1
    * @param c1y (Number) pixels
    * @param c2x (Number) pixels, control point 2
    * @param c2y (Number) pixels
    * @param p2x (Number) pixels, endpoint 2
    * @param p2y (Number) pixels
    * @returns Object (object with two cubic bezier definitions, b0 and b1)
    */
    public static function bezierSplit(p1x:Number, p1y:Number, c1x:Number, c1y:Number, c2x:Number, c2y:Number, p2x:Number, p2y:Number):Object {
        var m:Function = Math2.midPt;
        var p1:Object = {x:p1x, y:p1y};
        var p2:Object = {x:p2x, y:p2y};
        var p01:Object = m (p1x, p1y, c1x, c1y);
        var p12:Object = m (c1x, c1y, c2x, c2y);
        var p23:Object = m (c2x, c2y, p2x, p2y);
        var p02:Object = m (p01.x, p01.y, p12.x, p12.y);
        var p13:Object = m (p12.x, p12.y, p23.x, p23.y);
        var p03:Object = m (p02.x, p02.y, p13.x, p13.y);

        /*
        b0:{a:p0,  b:p01, c:p02, d:p03},
        b1:{a:p03, b:p13, c:p23, d:p3 }  
        */

        return { b0:{p1:p1, c1:p01, c2:p02, p2:p03}, b1:{p1:p03, c1:p13, c2:p23, p2:p2} };
    }

    /**
    * @method pointOnCurve (Penner)
    * @description Returns a point on a quadratic bezier curve with Robert Penner's optimization 
    *                of the standard equation:
    *                    {x:p1x * (1-t) * (1-t) + 2 * cx * t * (1-t) + p2x * t * t,
    *                     y:p1y * (1-t) * (1-t) + 2 * cy * t * (1-t) + p2y * t * t }
    * @param p1x (Number) pixels, endpoint 1
    * @param p1y (Number) pixels
    * @param cx (Number) pixels, control point 
    * @param cy (Number) pixels
    * @param p2x (Number) pixels, endpoint 2
    * @param p2y (Number) pixels
    * @param t (Number) if time is 0-1 along curve, value of t to find point at
    * @returns Object (point at time t)
    */
    public static function pointOnCurve(p1x:Number, p1y:Number, cx:Number, cy:Number, p2x:Number, p2y:Number, t:Number):Object {
        var o:Object = new Object();
        o.x = p1x + t*(2*(1-t)*(cx-p1x) + t*(p2x - p1x));
        o.y = p1y + t*(2*(1-t)*(cy-p1y) + t*(p2y - p1y));
        return o;
    }

    /**
    * @method pointsOnCurve (Penner)
    * @description Returns an array of objects defining points on a quadratic bezier curve, each of which
    *                includes:
    *                    x,y = location of point defining start of segment
    *                    r = rotation of segment (last entry has none because it's just a point)
    *                    n = number of subdivisions into which curve will be divided (pts = n+1)
    * @param p1x (Number) pixels, endpoint 1
    * @param p1y (Number) pixels
    * @param cx (Number) pixels, control point 
    * @param cy (Number) pixels
    * @param p2x (Number) pixels, endpoint 2
    * @param p2y (Number) pixels
    * @param n (Number) number of points to return
    * @returns Array
    */
    public static function pointsOnCurve(p1x:Number, p1y:Number, cx:Number, cy:Number, p2x:Number, p2y:Number, n:Number):Array {
        var pts:Array = [];
        for (var i:Number=0; i <= n; i++) {
           pts.push(Math2.pointOnCurve(p1x, p1y, cx, cy, p2x, p2y, i/n));
           if (i > 0) {
               pts[i].r = Math2.rotation(pts[i].y-pts[(i-1)].y, pts[i].x-pts[(i-1)].x);
           }
        }
        pts.splice(0,1);  // remove 1st element to return 1/n, 2/n,... n
        return pts;
    }

    /**
    * @method pointsOnLine (Penner)
    * @description Returns an array of point positions and rotations for n evenly spaced points 
    *                    along a line segment
    *                    x,y = location of point defining start of segment
    *                    r = rotation of segment (last entry has none because it's just a point)
    *                    n = number of subdivisions into which curve will be divided (pts = n+1)
    * @param p1x (Number) pixels, endpoint 1
    * @param p1y (Number) pixels
    * @param p2x (Number) pixels, endpoint 2
    * @param p2y (Number) pixels
    * @param n (Number) number of points to return
    * @returns Array
    *//* ....................................................................
       Returns an array of point positions and rotation for n evenly spaced points along a line segment
    */
    public static function pointsOnLine(p1x:Number, p1y:Number, p2x:Number, p2y:Number, n:Number):Array {
        var pts:Array = [];
        if (p2x != p1x) {
            var m:Number = (p2y-p1y)/(p2x-p1x);
            var b:Number = p1y - p1x * m;
            for (var i:Number=0; i <= n; i++) {
                var x:Number = p1x + ((p2x-p1x)/n) * i;
                pts.push({x:x, y:m*x+b});
                if (i > 0) {
                    pts[i].r = Math2.rotation(pts[i].y-pts[(i-1)].y, pts[i].x-pts[(i-1)].x);
                }
            }
        // vertical segment
        } else {
            for (i=0; i<=n; i++) {
                pts.push({x:p1x, y:p1y + ((p2y-p1y)/n) * i, r:90});
            }
        }            
        pts.splice(0,1);  // remove 1st element to return 1/n, 2/n,... n
        return pts;
    }

    /**
    * @method curveApproxLen
    * @description Returns the approximate length of a curved segment, found by dividing it 
    *                into two segments at t=0.5
    * @param p1x (Number) pixels, endpoint 1
    * @param p1y (Number) pixels
    * @param cx (Number) pixels, control point 
    * @param cy (Number) pixels
    * @param p2x (Number) pixels, endpoint 2
    * @param p2y (Number) pixels
    * @returns Number
    */
    public static function curveApproxLen(p1x:Number, p1y:Number, cx:Number, cy:Number, p2x:Number, p2y:Number):Number {
        var mp:Object = Math2.pointOnCurve(p1x, p1y, cx, cy, p2x, p2y, 0.5);
        var len1:Number = Math.sqrt((mp.x - p1x) * (mp.x - p1x) + (mp.y - p1y) * (mp.y - p1y));
        var len2:Number = Math.sqrt((mp.x - p2x) * (mp.x - p2x) + (mp.y - p2y) * (mp.y - p2y));
        return len1+len2;
    }

    /**
    * @method lineLen
    * @description Returns the length of a line segment
    * @param p1x (Number) pixels, endpoint 1
    * @param p1y (Number) pixels
    * @param p2x (Number) pixels, endpoint 2
    * @param p2y (Number) pixels
    * @returns Number
    */
    public static function lineLen(p1x:Number, p1y:Number, p2x:Number, p2y:Number):Number {
        return Math.sqrt((p2x - p1x) * (p2x - p1x) + (p2y - p1y) * (p2y - p1y));
    }

    /**
    * @method roundTo
    * @description Returns a number rounded to specified number of decimals 
    * @param n (Number) 
    * @param ndec (Number) number of decimals to round to
    * @returns Number
    */
    public static function roundTo(n:Number, ndec:Number):Number {
        var multiplier:Number = Math.pow(10, ndec);
        return Math.round(n*multiplier)/multiplier;
    }
}



class SVGColor {
    
    public static function getByName(name:String):Number {
        var col:Number = SVGColor._colorTable[name];
        return isNaN(col) ? 0 : col;
    }
    
    private static const _colorTable:Object = {
        blue:0x0000ff,
        green:0x008000,
        red:0xff0000,
        aliceblue:0xf0f8ff,
        antiquewhite:0xfaebd7,
        aqua:0x00ffff,
        aquamarine:0x7fffd4,
        azure:0xf0ffff,
        beige:0xf5f5dc,
        bisque:0xffe4c4,
        black:0x000000,
        blanchedalmond:0xffebcd,
        blueviolet:0x8a2be2,
        brown:0xa52a2a,
        burlywood:0xdeb887,
        cadetblue:0x5f9ea0,
        chartreuse:0x7fff00,
        chocolate:0xd2691e,
        coral:0xff7f50,
        cornflowerblue:0x6495ed,
        cornsilk:0xfff8dc,
        crimson:0xdc143c,
        cyan:0x00ffff,
        darkblue:0x00008b,
        darkcyan:0x008b8b,
        darkgoldenrod:0xb8860b,
        darkgray:0xa9a9a9,
        darkgreen:0x006400,
        darkgrey:0xa9a9a9,
        darkkhaki:0xbdb76b,
        darkmagenta:0x8b008b,
        darkolivegreen:0x556b2f,
        darkorange:0xff8c00,
        darkorchid:0x9932cc,
        darkred:0x8b0000,
        darksalmon:0xe9967a,
        darkseagreen:0x8fbc8f,
        darkslateblue:0x483d8b,
        darkslategray:0x2f4f4f,
        darkslategrey:0x2f4f4f,
        darkturquoise:0x00ced1,
        darkviolet:0x9400d3,
        deeppink:0xff1493,
        deepskyblue:0x00bfff,
        dimgray:0x696969,
        dimgrey:0x696969,
        dodgerblue:0x1e90ff,
        firebrick:0xb22222,
        floralwhite:0xfffaf0,
        forestgreen:0x228b22,
        fuchsia:0xff00ff,
        gainsboro:0xdcdcdc,
        ghostwhite:0xf8f8ff,
        gold:0xffd700,
        goldenrod:0xdaa520,
        gray:0x808080,
        grey:0x808080,
        greenyellow:0xadff2f,
        honeydew:0xf0fff0,
        hotpink:0xff69b4,
        indianred:0xcd5c5c,
        indigo:0x4b0082,
        ivory:0xfffff0,
        khaki:0xf0e68c,
        lavender:0xe6e6fa,
        lavenderblush:0xfff0f5,
        lawngreen:0x7cfc00,
        lemonchiffon:0xfffacd,
        lightblue:0xadd8e6,
        lightcoral:0xf08080,
        lightcyan:0xe0ffff,
        lightgoldenrodyellow:0xfafad2,
        lightgray:0xd3d3d3,
        lightgreen:0x90ee90,
        lightgrey:0xd3d3d3,
        lightpink:0xffb6c1,
        lightsalmon:0xffa07a,
        lightseagreen:0x20b2aa,
        lightskyblue:0x87cefa,
        lightslategray:0x778899,
        lightslategrey:0x778899,
        lightsteelblue:0xb0c4de,
        lightyellow:0xffffe0,
        lime:0x00ff00,
        limegreen:0x32cd32,
        linen:0xfaf0e6,
        magenta:0xff00ff,
        maroon:0x800000,
        mediumaquamarine:0x66cdaa,
        mediumblue:0x0000cd,
        mediumorchid:0xba55d3,
        mediumpurple:0x9370db,
        mediumseagreen:0x3cb371,
        mediumslateblue:0x7b68ee,
        mediumspringgreen:0x00fa9a,
        mediumturquoise:0x48d1cc,
        mediumvioletred:0xc71585,
        midnightblue:0x191970,
        mintcream:0xf5fffa,
        mistyrose:0xffe4e1,
        moccasin:0xffe4b5,
        navajowhite:0xffdead,
        navy:0x000080,
        oldlace:0xfdf5e6,
        olive:0x808000,
        olivedrab:0x6b8e23,
        orange:0xffa500,
        orangered:0xff4500,
        orchid:0xda70d6,
        palegoldenrod:0xeee8aa,
        palegreen:0x98fb98,
        paleturquoise:0xafeeee,
        palevioletred:0xdb7093,
        papayawhip:0xffefd5,
        peachpuff:0xffdab9,
        peru:0xcd853f,
        pink:0xffc0cb,
        plum:0xdda0dd,
        powderblue:0xb0e0e6,
        purple:0x800080,
        rosybrown:0xbc8f8f,
        royalblue:0x4169e1,
        saddlebrown:0x8b4513,
        salmon:0xfa8072,
        sandybrown:0xf4a460,
        seagreen:0x2e8b57,
        seashell:0xfff5ee,
        sienna:0xa0522d,
        silver:0xc0c0c0,
        skyblue:0x87ceeb,
        slateblue:0x6a5acd,
        slategray:0x708090,
        slategrey:0x708090,
        snow:0xfffafa,
        springgreen:0x00ff7f,
        steelblue:0x4682b4,
        tan:0xd2b48c,
        teal:0x008080,
        thistle:0xd8bfd8,
        tomato:0xff6347,
        turquoise:0x40e0d0,
        violet:0xee82ee,
        wheat:0xf5deb3,
        white:0xffffff,
        whitesmoke:0xf5f5f5,    
        yellow:0xffff00,
        yellowgreen:0x9acd32
    }
    
}


class SVGPathCommandType {

    public static const BEGIN_FILL_COMMAND:Number = 1;
    public static const LINE_STYLE_COMMAND:Number = 2;
    public static const MOVE_TO_COMMAND:Number = 3;
    public static const LINE_TO_COMMAND:Number = 4;
    public static const CURVE_TO_COMMAND:Number = 5;
    
}


interface ISVGPathCommand {
        
    function execute(graphics:Graphics):void;
    
}


class SVGPathCommand implements ISVGPathCommand {
    
    private var _type:Number;
    public function get type():Number { return this._type; }
    
    public function SVGPathCommand(type:Number = 0) {
        this._type = type;
    }
    
    public function execute(graphics:Graphics):void {
        throw new Error('Subclass must be implement execute method.');
    }
    
}


class BeginFillCommand extends SVGPathCommand {
    
    private var _color:Number;
    public function get color():Number { return this._color; }
    
    private var _alpha:Number;
    public function get alpha():Number { return this._alpha; }
    
    public function BeginFillCommand(color:Number, alpha:Number) {
        super(SVGPathCommandType.BEGIN_FILL_COMMAND);
        this._color = color;
        this._alpha = alpha;
    }
    
    public override function execute(graphics:Graphics):void {
        graphics.beginFill(this._color, this._alpha);
    }
    
}


class CurveToCommand extends SVGPathCommand implements ISVGPathCommand {
    
    private var _pt1:Point;
    public function get x1():Number { return this._pt1.x; }
    public function get y1():Number { return this._pt1.y; }
    
    private var _pt2:Point;
    public function get x2():Number { return this._pt2.x; }
    public function get y2():Number { return this._pt2.y; }
    
    public function CurveToCommand(pt1:Point, pt2:Point) {
        super(SVGPathCommandType.CURVE_TO_COMMAND);
        this._pt1 = pt1;
        this._pt2 = pt2;
    }
    
    public override function execute(graphics:Graphics):void {
        graphics.curveTo(this._pt1.x, this._pt1.y, this._pt2.x, this._pt2.y);
    }
    
}


class LineStyleCommand extends SVGPathCommand implements ISVGPathCommand {
    
    private var _width:Number;
    public function get width():Number { return this._width; }
    
    private var _color:Number;
    public function get color():Number { return this._color; }
    
    private var _alpha:Number;
    public function get alpha():Number { return this._alpha; }
    
    public function LineStyleCommand(width:Number, color:Number, alpha:Number) {
        super(SVGPathCommandType.LINE_STYLE_COMMAND);
        this._width = width;
        this._color = color;
        this._alpha = alpha;
    }
    
    public override function execute(graphics:Graphics):void {
        graphics.lineStyle(this._width, this._color, this._alpha);
    }
}


class LineToCommand extends SVGPathCommand implements ISVGPathCommand {
    
    private var _pt:Point;
    public function get x():Number { return this._pt.x; }
    public function get y():Number { return this._pt.y; }
    
    public function LineToCommand(pt:Point) {
        super(SVGPathCommandType.LINE_TO_COMMAND);
        this._pt = pt;
    }
    
    public override function execute(graphics:Graphics):void {
        graphics.lineTo(this._pt.x, this._pt.y);
    }
    
}


class MoveToCommand extends SVGPathCommand implements ISVGPathCommand {
    
    private var _pt:Point;
    public function get x():Number { return this._pt.x; }
    public function get y():Number { return this._pt.y; }
    
    public function MoveToCommand(pt:Point) {
        super(SVGPathCommandType.MOVE_TO_COMMAND);
        this._pt = pt;
    }
    
    public override function execute(graphics:Graphics):void {
        graphics.moveTo(this._pt.x, this._pt.y);
    }
    
}