diff --git a/Degrafa/com/degrafa/GeometryComposition.as b/Degrafa/com/degrafa/GeometryComposition.as new file mode 100644 index 0000000..0201467 --- /dev/null +++ b/Degrafa/com/degrafa/GeometryComposition.as @@ -0,0 +1,226 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa{ + + import com.degrafa.core.collections.FillCollection; + import com.degrafa.core.collections.StrokeCollection; + import com.degrafa.geometry.Geometry; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + import mx.events.PropertyChangeEvent; + + [DefaultProperty("geometry")] + + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("GeometryComposition.png")] + + /** + * GeometryComposition allows composing only objects that + * extend Geometry. This is not Sprite based like GeometryGroup and as such + * can be used in situations where a sprite is not allowed, for example skins. + * + * If you just want to draw to a arbitrary graphics context like Canvas, this + * object is recommended for the opening tag of your composition. + **/ + public class GeometryComposition extends Geometry implements IGeometry{ + + public function GeometryComposition(){ + super(); + } + + private var _fills:FillCollection; + [Inspectable(category="General", arrayType="com.degrafa.core.IGraphicsFill")] + [ArrayElementType("com.degrafa.core.IGraphicsFill")] + /** + * A array of IGraphicsFill objects. + **/ + public function get fills():Array{ + initFillsCollection(); + return _fills.items; + } + public function set fills(value:Array):void{ + initFillsCollection(); + _fills.items = value; + } + + /** + * Access to the Degrafa fill collection object for this graphic object. + **/ + public function get fillCollection():FillCollection{ + initFillsCollection(); + return _fills; + } + + /** + * Initialize the collection by creating it and adding an event listener. + **/ + private function initFillsCollection():void{ + if(!_fills){ + _fills = new FillCollection(); + + //add a listener to the collection + if(enableEvents){ + _fills.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + private var _strokes:StrokeCollection; + [Inspectable(category="General", arrayType="com.degrafa.core.IGraphicsStroke")] + [ArrayElementType("com.degrafa.core.IGraphicsStroke")] + /** + * A array of IStroke objects. + **/ + public function get strokes():Array{ + initSrokesCollection(); + return _strokes.items; + } + public function set strokes(value:Array):void{ + initSrokesCollection(); + _strokes.items = value; + + } + + /** + * Access to the Degrafa stroke collection object for this graphic object. + **/ + public function get strokeCollection():StrokeCollection{ + initSrokesCollection(); + return _strokes; + } + + /** + * Initialize the collection by creating it and adding an event listener. + **/ + private function initSrokesCollection():void{ + if(!_strokes){ + _strokes = new StrokeCollection(); + + //add a listener to the collection + if(enableEvents){ + _strokes.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + /** + * Returns the tight bounds for this objects children + * not including this object. + **/ + public function get childBounds():Rectangle{ + var tempRect:Rectangle; + for each (var item:Geometry in geometry){ + if(!tempRect || tempRect.isEmpty()){ + tempRect = item.bounds; + } + else{ + tempRect=tempRect.union(item.bounds); + } + } + + return tempRect; + } + + + + private var _bounds:Rectangle; + /** + * The tight bounds of this element as represented by a Rectangle object. + **/ + override public function get bounds():Rectangle{ + return _bounds; + } + + /** + * Calculates the bounds for this element. + **/ + public function calcBounds():void{ + + if(_layoutConstraint){ + super.calculateLayout(); + _bounds = layoutRectangle; + } + else if(parent && parent is Geometry){ + _bounds=Geometry(parent).bounds; + } + else if (_currentGraphicsTarget){ + + _bounds= _currentGraphicsTarget.getRect(_currentGraphicsTarget) + + } + else{ + _bounds= new Rectangle(); + } + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + //in the case of geom comp the predraw only sets up the bounds + calcBounds(); + } + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + + super.calculateLayout(tempLayoutRect); + _layoutRectangle = _layoutConstraint.layoutRectangle; + } + } + } + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + + //init the layout in this case done before predraw. + calculateLayout(); + + //re init if required + preDraw(); + + super.draw(graphics, (rc)? rc:_bounds); + + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/GeometryComposition.png b/Degrafa/com/degrafa/GeometryComposition.png new file mode 100644 index 0000000..75a8be7 Binary files /dev/null and b/Degrafa/com/degrafa/GeometryComposition.png differ diff --git a/Degrafa/com/degrafa/GeometryGroup.as b/Degrafa/com/degrafa/GeometryGroup.as new file mode 100644 index 0000000..e211e68 --- /dev/null +++ b/Degrafa/com/degrafa/GeometryGroup.as @@ -0,0 +1,155 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa{ + import com.degrafa.core.collections.GeometryCollection; + import com.degrafa.geometry.Geometry; + + import flash.display.DisplayObject; + import flash.display.Graphics; + import flash.geom.Rectangle; + + import mx.events.PropertyChangeEvent; + + [DefaultProperty("geometry")] + [Bindable(event="propertyChange")] + + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("GeometryGroup.png")] + + /** + * GeometryGroup is where composition is achieved for + * all Degrafa objects that render to a graphics context. Nested GeometryGroups + * can be added each of which may contain IGraphic or IGeometry type objects. + * GeometryGroup is the principle building block for compositing. + **/ + public class GeometryGroup extends Graphic implements IGraphic, IGeometry{ + + public function GeometryGroup(){ + super(); + } + + private var _geometry:GeometryCollection; + [Inspectable(category="General", arrayType="com.degrafa.IGeometry")] + [ArrayElementType("com.degrafa.IGeometry")] + /** + * A array of IGeometry objects. Objects of type GraphicText, GraphicImage + * and GeometryGroup are added to the target display list. + **/ + public function get geometry():Array{ + initGeometryCollection(); + return _geometry.items; + } + public function set geometry(value:Array):void + { + + initGeometryCollection(); + + _geometry.items = value; + + //add the children is required + for each (var item:IGeometry in _geometry.items){ + if(item is IGraphic){ + addChild(DisplayObject(item)); + } + + //set the root geometry IGraphicParent + if (item is Geometry){ + Geometry(item).IGraphicParent = this; + } + } + + } + + /** + * Access to the Degrafa geometry collection object for this graphic object. + **/ + public function get geometryCollection():GeometryCollection{ + initGeometryCollection(); + return _geometry; + } + + /** + * Initialize the geometry collection by creating it and adding an event listener. + **/ + private function initGeometryCollection():void{ + if(!_geometry){ + _geometry = new GeometryCollection(); + + //add a listener to the collection + if(enableEvents){ + _geometry.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + /** + * Principle event handler for any property changes to a + * graphic object or it's child objects. + **/ + private function propertyChangeHandler(event:PropertyChangeEvent):void{ + draw(null,null); + } + + /** + * Begins the draw phase for graphic objects. All graphic objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + if(!parent){return;} + + super.draw(null,null); + + if (_geometry){ + for each (var geometryItem:IGeometry in _geometry.items){ + + if(geometryItem is IGraphic){ + //a IGraphic is a sprite and does not draw to + //this graphics object + geometryItem.draw(null,null); + } + else{ + geometryItem.draw(this.graphics,null); + } + } + } + + super.endDraw(null); + + } + + /** + * Data is required for the IGeometry interface and has no effect here. + * @private + **/ + public function get data():Object{return null;} + public function set data(value:Object):void{} + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/GeometryGroup.png b/Degrafa/com/degrafa/GeometryGroup.png new file mode 100644 index 0000000..79a0460 Binary files /dev/null and b/Degrafa/com/degrafa/GeometryGroup.png differ diff --git a/Degrafa/com/degrafa/Graphic.as b/Degrafa/com/degrafa/Graphic.as new file mode 100644 index 0000000..47dcddb --- /dev/null +++ b/Degrafa/com/degrafa/Graphic.as @@ -0,0 +1,489 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +package com.degrafa{ + + import com.degrafa.core.IGraphicsFill; + import com.degrafa.core.IGraphicsStroke; + import com.degrafa.core.collections.FillCollection; + + import com.degrafa.core.collections.StrokeCollection; + + import flash.display.DisplayObject; + import flash.display.DisplayObjectContainer; + import flash.display.Graphics; + import flash.display.Sprite; + import flash.events.Event; + import flash.geom.Rectangle; + + import mx.events.FlexEvent; + import mx.utils.NameUtil; + + + import mx.core.IMXMLObject; + import mx.events.PropertyChangeEvent; + import mx.events.PropertyChangeEventKind; + + [Event(name="initialize", type="mx.events.FlexEvent")] + [Event(name="propertyChange", type="mx.events.PropertyChangeEvent")] + [Bindable(event="propertyChange",type="mx.events.PropertyChangeEvent")] + + /** + * Graphic is the base class for Degrafa objects that allow complete composition + * GeometryGroup for example. + * + * @see flash.display.Sprite + **/ + public class Graphic extends Sprite implements IMXMLObject{ + + /** + * Number that specifies the vertical position, in pixels, within the target. + **/ + override public function get y():Number{ + return super.y; + } + override public function set y(value:Number):void{ + super.y = value; + } + + /** + * Number that specifies the horizontal position, in pixels, within the target. + **/ + override public function get x():Number{ + return super.x; + } + override public function set x(value:Number):void{ + super.x = value; + } + + private var _width:Number=0; + [PercentProxy("percentWidth")] + /** + * Number that specifies the width, in pixels, in the target's coordinates. + **/ + override public function get width():Number{ + return _width; + } + override public function set width(value:Number):void{ + _width = value; + draw(null,null); + dispatchEvent(new Event("change")); + } + + + + private var _height:Number=0; + [PercentProxy("percentHeight")] + /** + * Number that specifies the height, in pixels, in the target's coordinates. + **/ + override public function get height():Number{ + return _height; + } + override public function set height(value:Number):void{ + _height = value; + draw(null,null); + dispatchEvent(new Event("change")); + } + + /** + * The default height, in pixels. + **/ + public function get measuredHeight():Number{ + return _height; + } + + /** + * The default width, in pixels. + **/ + public function get measuredWidth():Number{ + return _width; + } + + private var _percentWidth:Number; + [Inspectable(environment="none")] + /** + * Number that specifies the width as a percentage of the target. + **/ + public function get percentWidth():Number{ + return _percentWidth; + } + public function set percentWidth(value:Number):void{ + if (_percentWidth == value){return}; + _percentWidth = value; + + } + + + private var _percentHeight:Number; + [Inspectable(environment="none")] + /** + * Number that specifies the height as a percentage of the target. + **/ + public function get percentHeight():Number{ + return _percentHeight; + } + public function set percentHeight(value:Number):void{ + if (_percentHeight == value){return;} + _percentHeight = value; + + } + + + private var _target:DisplayObjectContainer; + /** + * A target DisplayObjectContainer that this graphic object should be added or drawn to. + **/ + public function get target():DisplayObjectContainer{ + return _target; + } + public function set target(value:DisplayObjectContainer):void{ + + if (!value){return;} + + //reparent if nessesary + if (_target != value && _target!=null) + { + //remove this obejct from previous parent + _target.removeChild(this); + } + + _target = value; + _target.addChild(this); + + //draw the obejct + draw(null,null); + endDraw(null); + + + } + + + private var _stroke:IGraphicsStroke; + /** + * Defines the stroke object that will be used for + * rendering this graphic object. + **/ + public function get stroke():IGraphicsStroke{ + return _stroke; + } + public function set stroke(value:IGraphicsStroke):void{ + _stroke = value; + } + + + private var _fill:IGraphicsFill; + /** + * Defines the fill object that will be used for + * rendering this graphic object. + **/ + public function get fill():IGraphicsFill{ + return _fill; + } + public function set fill(value:IGraphicsFill):void{ + _fill=value; + } + + + private var _fills:FillCollection; + [Inspectable(category="General", arrayType="com.degrafa.core.IGraphicsFill")] + [ArrayElementType("com.degrafa.core.IGraphicsFill")] + /** + * A array of IGraphicsFill objects. + **/ + public function get fills():Array{ + initFillsCollection(); + return _fills.items; + } + public function set fills(value:Array):void{ + initFillsCollection(); + _fills.items = value; + } + + /** + * Access to the Degrafa fill collection object for this graphic object. + **/ + public function get fillCollection():FillCollection{ + initFillsCollection(); + return _fills; + } + + /** + * Initialize the fills collection by creating it and adding an event listener. + **/ + private function initFillsCollection():void{ + if(!_fills){ + _fills = new FillCollection(); + + //add a listener to the collection + if(enableEvents){ + _fills.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + private var _strokes:StrokeCollection; + [Inspectable(category="General", arrayType="com.degrafa.core.IGraphicsStroke")] + [ArrayElementType("com.degrafa.core.IGraphicsStroke")] + /** + * A array of IStroke objects. + **/ + public function get strokes():Array{ + initSrokesCollection(); + return _strokes.items; + } + public function set strokes(value:Array):void{ + initSrokesCollection(); + _strokes.items = value; + + } + + /** + * Access to the Degrafa stroke collection object for this graphic object. + **/ + public function get strokeCollection():StrokeCollection{ + initSrokesCollection(); + return _strokes; + } + + /** + * Initialize the strokes collection by creating it and adding an event listener. + **/ + private function initSrokesCollection():void{ + if(!_strokes){ + _strokes = new StrokeCollection(); + + //add a listener to the collection + if(enableEvents){ + _strokes.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + /** + * Principle event handler for any property changes to a + * graphic object or it's child objects. + **/ + private function propertyChangeHandler(event:PropertyChangeEvent):void{ + draw(null,null); + } + + /** + * Ends the draw phase for geometry objects. + * + * @param graphics The current Graphics context being drawn to. + **/ + public function endDraw(graphics:Graphics):void{ + + if (fill){ + fill.end(this.graphics); + } + } + + + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + public function draw(graphics:Graphics,rc:Rectangle):void{ + + if (!parent){return;} + + if(percentWidth || percentHeight) + { + //calculate based on the parent + _width = (parent.width/100)*_percentHeight; + _height = (parent.height/100)*_percentHeight; + } + + + this.graphics.clear(); + + if (stroke) + { + if(!rc){ + stroke.apply(this.graphics,null); + } + else{ + stroke.apply(this.graphics,rc); + } + + } + else + { + this.graphics.lineStyle(0, 0xFFFFFF, 0); + } + + + if (fill){ + + if(!rc){ + var rect:Rectangle = new Rectangle(0,0,width,height); + fill.begin(this.graphics, rect); + } + else{ + fill.begin(this.graphics, rc); + } + + } + + + } + + + private var _enableEvents:Boolean=true; + /** + * Enable events for this object. + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get enableEvents():Boolean{ + return _enableEvents; + } + public function set enableEvents(value:Boolean):void{ + _enableEvents=value; + } + + private var _suppressEventProcessing:Boolean=false; + /** + * Temporarily suppress event processing for this object. + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get suppressEventProcessing():Boolean{ + return _suppressEventProcessing; + } + public function set suppressEventProcessing(value:Boolean):void{ + + if(_suppressEventProcessing==true && value==false){ + _suppressEventProcessing=value; + initChange("suppressEventProcessing",false,true,this); + } + else{ + _suppressEventProcessing=value; + } + } + + /** + * Dispatches an event into the event flow. + * + * @see EventDispatcher + **/ + override public function dispatchEvent(event:Event):Boolean{ + if(_suppressEventProcessing){return false;} + + return(super.dispatchEvent(event)); + + } + + /** + * Dispatches an property change event into the event flow. + **/ + public function dispatchPropertyChange(bubbles:Boolean = false, + property:Object = null, oldValue:Object = null, + newValue:Object = null, source:Object = null):Boolean{ + return dispatchEvent(new PropertyChangeEvent("propertyChange",bubbles,false,PropertyChangeEventKind.UPDATE,property,oldValue,newValue,source)); + } + + /** + * Helper function for dispatching property changes + **/ + public function initChange(property:String,oldValue:Object,newValue:Object,source:Object):void{ + if(hasEventManager){ + dispatchPropertyChange(false,property,oldValue,newValue,source); + } + } + + /** + * Tests to see if a EventDispatcher instance has been created for this object. + **/ + public function get hasEventManager():Boolean{ + return true; + } + + //specific identity code + + private var _id:String; + /** + * The identifier used by document to refer to this object. + **/ + public function get id():String{ + + if(_id){ + return _id; + } + else{ + _id =NameUtil.createUniqueName(this); + name=_id; + return _id; + } + } + public function set id(value:String):void{ + _id = value; + name=_id; + } + + + private var _document:Object; + /** + * The MXML document that created this object. + **/ + public function get document():Object{ + return _document; + } + + /** + * Called after the implementing object has been created and all component properties specified on the MXML tag have been initialized. + * + * @param document The MXML document that created this object. + * @param id The identifier used by document to refer to this object. + **/ + public function initialized(document:Object, id:String):void { + + //if the id has not been set (through as perhaps) + if(!_id){ + if(id){ + _id = id; + } + else{ + //if no id specified create one + _id = NameUtil.createUniqueName(this); + } + } + + //sprit has a name property and it is set + //to the instance value. Make sure it is the + //same as the id + name=_id; + + _document=document; + + if(enableEvents && !suppressEventProcessing){ + dispatchEvent(new FlexEvent(FlexEvent.INITIALIZE)); + } + + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/GraphicImage.as b/Degrafa/com/degrafa/GraphicImage.as new file mode 100644 index 0000000..a3f8527 --- /dev/null +++ b/Degrafa/com/degrafa/GraphicImage.as @@ -0,0 +1,113 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +package com.degrafa{ + + import flash.display.DisplayObject; + import flash.display.Graphics; + import flash.display.Loader; + import flash.events.Event; + import flash.geom.Rectangle; + import flash.net.URLRequest; + + + [Bindable(event="propertyChange")] + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("GraphicImage.png")] + + /** + * GraphicImage Enables support for images to be added to compositions. + **/ + public class GraphicImage extends Graphic implements IGraphic, IGeometry{ + + public function GraphicImage(){ + super(); + } + + /** + * Data is required for the IGeometry interface and has no effect here. + * @private + **/ + public function get data():Object{return null;} + public function set data(value:Object):void{} + + private var loader:Loader; + private var _source:Object; + + /** + * The URL, class or string name of a class to load as the content + **/ + public function get source():Object{ + return _source; + } + public function set source(value:Object):void{ + _source = value; + + var newClass:Class; + + if (_source is Class) + { + newClass = Class(_source); + } + else if (_source is String) + { + //treat as a url so need a loader + loader = new Loader(); + var urlRequest:URLRequest = new URLRequest(String(_source)); + loader.load(urlRequest); + loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaded); + } + + if(newClass){ + var child:DisplayObject; + child = new newClass(); + addChild(child); + } + } + + /** + * Called when the image has been successfully loaded. + **/ + protected function onLoaded(event:Event):void{ + addChild(event.target.content); + loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, onLoaded); + } + + /** + * draw is required for the IGeometry interface and has no effect here. + * @private + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{} + + /** + * endDraw is required for the IGeometry interface and has no effect here. + * @private + **/ + override public function endDraw(graphics:Graphics):void{} + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/GraphicImage.png b/Degrafa/com/degrafa/GraphicImage.png new file mode 100644 index 0000000..910af62 Binary files /dev/null and b/Degrafa/com/degrafa/GraphicImage.png differ diff --git a/Degrafa/com/degrafa/GraphicPoint.as b/Degrafa/com/degrafa/GraphicPoint.as new file mode 100644 index 0000000..10304b0 --- /dev/null +++ b/Degrafa/com/degrafa/GraphicPoint.as @@ -0,0 +1,39 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa{ + import flash.geom.Point; + + + [Bindable] + /** + * Simple point definition. + * + * @see flash.geom.Point + **/ + public class GraphicPoint extends Point implements IGraphicPoint{ + + public function GraphicPoint(x:Number=0, y:Number=0){ + super(x, y); + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/GraphicPointEX.as b/Degrafa/com/degrafa/GraphicPointEX.as new file mode 100644 index 0000000..50f91fc --- /dev/null +++ b/Degrafa/com/degrafa/GraphicPointEX.as @@ -0,0 +1,259 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa{ + + /** + * servers as a proxy wrapper so we can get the event manager in here. + * This should/can not be used in a matrix where flex/flash expect a + * point. But rather use/pass/set the point property when required. It should + * never evenr be used on a complex graphic. It is intended to be wrapped for + * a current object if manipulation is required. + **/ + import com.degrafa.core.DegrafaObject; + + import flash.geom.Point; + + + + [Bindable(event="propertyChange")] + /** + * Extended Degrafa event enabled point definition. + * + * @see flash.geom.Point + **/ + public class GraphicPointEX extends DegrafaObject implements IGraphicPoint{ + + /** + * Constructor. + * + *

The graphic extended point constructor accepts 2 optional arguments that define it's + * x, and y coordinate values.

+ * + * @param x A number indicating the x-axis coordinate. + * @param y A number indicating the y-axis coordinate. + * + */ + public function GraphicPointEX(x:Number=0, y:Number=0){ + this.x=x + this.y=y + this.point.x=x + this.point.y=y + } + + private var _data:String; + /** + * GraphicPointEX short hand data value. + * + *

The extended graphic point data property expects exactly 2 values x and + * y separated by spaces.

+ **/ + public function get data():String{ + return _data; + } + public function set data(value:String):void + { + if(_data != value){ + var oldValue:String=_data; + + _data = value; + + var tempArray:Array = value.split(" "); + + if (tempArray.length == 2) + { + _x=tempArray[0]; + _y=tempArray[1]; + } + + _point.x=_x + _point.y=_y + + //call local helper to dispatch event + if(enableEvents){ + initChange("data",oldValue,value,this); + } + } + + } + + private var _point:Point=new Point(); + /** + * The internal point object. + **/ + public function get point():Point{ + return _point; + } + public function set point(value:Point):void{ + if(!_point.equals(value)){ + + var oldValue:Point=_point; + _point = value; + + _x=_point.x; + _y=_point.y; + + //call local helper to dispatch event + if(enableEvents){ + initChange("data",oldValue,value,this); + } + } + + } + + + private var _x:Number=0; + /** + * The x-coordinate of the point. If not specified + * a default value of 0 is used. + **/ + public function get x():Number{ + return _x; + } + public function set x(value:Number):void{ + if(_x != value){ + var oldValue:Number=_x; + + _x = value; + _point.y=_x; + + //call local helper to dispatch event + if(enableEvents){ + initChange("x",oldValue,_x,this); + } + } + } + + + private var _y:Number=0; + /** + * The y-coordinate of the point. If not specified + * a default value of 0 is used. + **/ + public function get y():Number{ + return _y; + } + public function set y(value:Number):void{ + if(_y != value){ + var oldValue:Number=_y; + + _y = value; + _point.y=_y; + + //call local helper to dispatch event + if(enableEvents){ + initChange("y",oldValue,_y,this); + } + } + } + + /** + * Adds the coordinates of another point to the coordinates of this point to create a new point. + * + * @see flash.geom.Point + **/ + public function add(v:Point):Point{ + return _point.add(v); + } + + /** + * Creates a copy of this Point object. + * + * @see flash.geom.Point + **/ + public function clone():Point { + return _point.clone(); + } + + /** + * Returns the distance between pt1 and pt2. + * + * @see flash.geom.Point + **/ + public static function distance(pt1:Point, pt2:Point):Number{ + return Point.distance(pt1,pt2); + } + + /** + * Determines whether two points are equal. + * + * @see flash.geom.Point + **/ + public function equals(toCompare:Point):Boolean { + return _point.equals(toCompare); + } + + /** + * Determines a point between two specified points. + * + * @see flash.geom.Point + **/ + public static function interpolate(pt1:Point, pt2:Point, f:Number):Point{ + return Point.interpolate(pt1,pt2,f); + } + + /** + * Scales the line segment between (0,0) and the current point to a set length. + * + * @see flash.geom.Point + **/ + public function normalize(thickness:Number):void { + _point.normalize(thickness); + } + + /** + * Offsets the Point object by the specified amount. + * + * @see flash.geom.Point + **/ + public function offset(dx:Number, dy:Number):void { + _point.offset(dx,dy); + } + + /** + * Converts a pair of polar coordinates to a Cartesian point coordinate. + * + * @see flash.geom.Point + **/ + public static function polar(len:Number, angle:Number):Point{ + return Point.polar(len,angle); + } + + /** + * Subtracts the coordinates of another point from the coordinates of this point to create a new point. + * + * @see flash.geom.Point + **/ + public function subtract(v:Point):Point{ + return _point.subtract(v); + } + + /** + * The length from (0,0) to this point. + * + * @see flash.geom.Point + **/ + public function get length():Number{ + return _point.length; + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/GraphicText.as b/Degrafa/com/degrafa/GraphicText.as new file mode 100644 index 0000000..69472b6 --- /dev/null +++ b/Degrafa/com/degrafa/GraphicText.as @@ -0,0 +1,554 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +package com.degrafa +{ + import com.degrafa.core.IGraphicsFill; + import com.degrafa.core.IGraphicsStroke; + import com.degrafa.core.collections.FillCollection; + import com.degrafa.core.collections.StrokeCollection; + import com.degrafa.paint.SolidFill; + + import flash.display.DisplayObjectContainer; + import flash.display.Graphics; + import flash.events.Event; + import flash.geom.Rectangle; + + import flash.text.TextField; + import flash.text.TextFormat; + + import mx.core.IMXMLObject; + import mx.events.FlexEvent; + import mx.events.PropertyChangeEvent; + import mx.events.PropertyChangeEventKind; + import mx.utils.NameUtil; + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("GraphicText.png")] + + /** + * some of these will be added back at a later date + **/ + + [Exclude(name="percentHeight", kind="property")] + [Exclude(name="percentWidth", kind="property")] + [Exclude(name="measuredWidth", kind="property")] + [Exclude(name="measuredHeight", kind="property")] + [Exclude(name="target", kind="property")] + [Exclude(name="stroke", kind="property")] + [Exclude(name="fills", kind="property")] + [Exclude(name="strokes", kind="property")] + + [Bindable(event="propertyChange")] + + /** + * GraphicText extends TextField and enables support for text fields + * to be added to compositions. + * + * @see flash.text.TextField + **/ + public class GraphicText extends TextField implements IGraphic, IGeometry, IMXMLObject + { + public function GraphicText() + { + super(); + defaultTextFormat = _textFormat; + } + + /** + * Data is required for the IGeometry interface and has no effect here. + * @private + **/ + public function get data():Object{return null;} + public function set data(value:Object):void{} + + + /** + * Text format. + * + * @see flash.text.TextFormat + **/ + private var _textFormat:TextFormat=new TextFormat(); + public function get textFormat():TextFormat{ + return _textFormat; + } + public function set textFormat(value:TextFormat):void{ + _textFormat = value; + defaultTextFormat = _textFormat; + } + + + /** + * Controls automatic sizing and alignment of text fields. + * + * @see flash.text.TextField + **/ + private var _autoSize:String="none"; + [Inspectable(category="General", enumeration="none,left,center", defaultValue="none")] + override public function get autoSize():String{ + return _autoSize; + } + override public function set autoSize(value:String):void{ + _autoSize = value; + super.autoSize =_autoSize; + } + + + /** + * Autosize the text field to text size. When set to true the + * GraphicText object will size to fit the height and width of + * the text. + **/ + private var _autoSizeField:Boolean=true; + [Inspectable(category="General", enumeration="true,false")] + public function get autoSizeField():Boolean{ + return _autoSizeField; + } + public function set autoSizeField(value:Boolean):void{ + _autoSizeField = value; + + //NOTE: added the 4px offset as the left and bottom was + //being cut off requires investigation. Was changed + //from 5px to 4px to address the "walking text" issue + + if(text != ""){ + width = textWidth + 4; + height = textHeight + 4; + } + + } + + /** + * A string that is the current text in the text field. + **/ + override public function set text(value:String):void{ + + super.text = value; + + //NOTE: added the 4px offset as the left and bottom was + //being cut off requires investigation. Was changed + //from 5px to 4px to address the "walking text" issue + + if(_autoSizeField){ + width = textWidth + 4; + height = textHeight + 4; + } + + } + + + /** + * Indicates the color of the text. + * + * @see flash.text.TextFormat + **/ + private var _color:uint; + public function set color(value:uint):void{ + _color = value; + _textFormat.color = _color; + defaultTextFormat = _textFormat; + } + public function get color():uint{ + return _color; + } + + /** + * The name of the font for text in this text format, as a string. + * + * @see flash.text.TextFormat + **/ + private var _fontFamily:String; + public function set fontFamily(value:String):void{ + _fontFamily = value; + _textFormat.font = _fontFamily; + defaultTextFormat = _textFormat; + } + public function get fontFamily():String{ + return _fontFamily; + } + + /** + * The point size of text in this text format. + * + * @see flash.text.TextFormat + **/ + private var _fontSize:Number; + public function set fontSize(value:Number):void{ + _fontSize = value; + _textFormat.size = _fontSize; + defaultTextFormat = _textFormat; + } + public function get fontSize():Number{ + return _fontSize; + } + + /** + * Specifies whether the text is normal or boldface. + * + * @see flash.text.TextFormat + **/ + private var _fontWeight:String="normal"; + [Inspectable(category="General", enumeration="normal,bold", defaultValue="normal")] + public function set fontWeight(value:String):void{ + _fontWeight = value; + _textFormat.bold = (_fontWeight == "bold") ? true: false; + defaultTextFormat = _textFormat; + } + public function get fontWeight():String{ + return _fontWeight; + } + + /** + * A number representing the amount of space that is uniformly distributed between all characters. + * + * @see flash.text.TextFormat + **/ + private var _letterSpacing:int; + public function set letterSpacing(value:int):void{ + _letterSpacing = value; + _textFormat.letterSpacing = _letterSpacing; + defaultTextFormat = _textFormat; + } + public function get letterSpacing():int{ + return _letterSpacing; + } + + private var _stroke:IGraphicsStroke; + /** + * Defines the stroke object that will be used for + * rendering this geometry object. Coming soon has no effect here. + * + * @private + **/ + public function get stroke():IGraphicsStroke{ + return _stroke; + } + public function set stroke(value:IGraphicsStroke):void{ + if(_stroke != value){ + + if(_stroke){ + if(_stroke.hasEventManager){ + _stroke.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + + _stroke = value; + + if(enableEvents){ + _stroke.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler,false,0,true); + } + } + } + + /** + * Defines the fill object that will be used for + * rendering this geometry object. Coming soon has no effect here. + * + * @private + **/ + private var _fill:IGraphicsFill; + public function get fill():IGraphicsFill{ + return _fill; + } + public function set fill(value:IGraphicsFill):void{ + _fill=value; + + if (_fill is SolidFill){ + color = uint(SolidFill(_fill).color); + } + else{ + //gradient fill need to do runtime mask + + } + + } + + private var _fills:FillCollection; + [Inspectable(category="General", arrayType="com.degrafa.core.IGraphicsFill")] + [ArrayElementType("com.degrafa.core.IGraphicsFill")] + /** + * A array of IGraphicsFill objects. + **/ + public function get fills():Array{ + initFillsCollection(); + return _fills.items; + } + public function set fills(value:Array):void{ + initFillsCollection(); + _fills.items = value; + } + + /** + * Access to the Degrafa fill collection object for this graphic object. + **/ + public function get fillCollection():FillCollection{ + initFillsCollection(); + return _fills; + } + + /** + * Initialize the fills collection by creating it and adding an event listener. + **/ + private function initFillsCollection():void{ + if(!_fills){ + _fills = new FillCollection(); + + //add a listener to the collection + if(enableEvents){ + _fills.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + private var _strokes:StrokeCollection; + [Inspectable(category="General", arrayType="com.degrafa.core.IGraphicsStroke")] + [ArrayElementType("com.degrafa.core.IGraphicsStroke")] + /** + * A array of IStroke objects. + **/ + public function get strokes():Array{ + initSrokesCollection(); + return _strokes.items; + } + public function set strokes(value:Array):void{ + initSrokesCollection(); + _strokes.items = value; + + } + + /** + * Access to the Degrafa stroke collection object for this graphic object. + **/ + public function get strokeCollection():StrokeCollection{ + initSrokesCollection(); + return _strokes; + } + + /** + * Initialize the strokes collection by creating it and adding an event listener. + **/ + private function initSrokesCollection():void{ + if(!_strokes){ + _strokes = new StrokeCollection(); + + //add a listener to the collection + if(enableEvents){ + _strokes.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + /** + * Principle event handler for any property changes to a + * graphic object or it's child objects. + * + * @private + **/ + private function propertyChangeHandler(event:PropertyChangeEvent):void{ + draw(null,null); + } + + /** + * draw is required for the IGeometry interface and has no effect here. + * @private + **/ + + public function draw(graphics:Graphics,rc:Rectangle):void{} + + /** + * endDraw is required for the IGeometry interface and has no effect here. + * @private + **/ + public function endDraw(graphics:Graphics):void{} + + + + /** + * the below are all excluded for now + **/ + + /** + * @private + **/ + public function get percentHeight():Number{return 0;} + public function set percentHeight(value:Number):void{} + + /** + * @private + **/ + public function get percentWidth():Number{return 0;} + public function set percentWidth(value:Number):void{} + + /** + * @private + **/ + public function get measuredWidth():Number{return 0;} + + /** + * @private + **/ + public function get measuredHeight():Number{return 0;} + + /** + * @private + **/ + public function get target():DisplayObjectContainer{return null;} + public function set target(value:DisplayObjectContainer):void{} + + + + //event related stuff + + private var _enableEvents:Boolean=true; + /** + * Enable events for this object. + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get enableEvents():Boolean{ + return _enableEvents; + } + public function set enableEvents(value:Boolean):void{ + _enableEvents=value; + } + + private var _suppressEventProcessing:Boolean=false; + /** + * Temporarily suppress event processing for this object. + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get suppressEventProcessing():Boolean{ + return _suppressEventProcessing; + } + public function set suppressEventProcessing(value:Boolean):void{ + + if(_suppressEventProcessing==true && value==false){ + _suppressEventProcessing=value; + initChange("suppressEventProcessing",false,true,this); + } + else{ + _suppressEventProcessing=value; + } + } + + /** + * Dispatches an event into the event flow. + * + * @see EventDispatcher + **/ + override public function dispatchEvent(event:Event):Boolean{ + if(_suppressEventProcessing){return false;} + + return(super.dispatchEvent(event)); + + } + + /** + * Dispatches an property change event into the event flow. + **/ + public function dispatchPropertyChange(bubbles:Boolean = false, + property:Object = null, oldValue:Object = null, + newValue:Object = null, source:Object = null):Boolean{ + return dispatchEvent(new PropertyChangeEvent("propertyChange",bubbles,false,PropertyChangeEventKind.UPDATE,property,oldValue,newValue,source)); + } + + /** + * Helper function for dispatching property changes + **/ + public function initChange(property:String,oldValue:Object,newValue:Object,source:Object):void{ + if(hasEventManager){ + dispatchPropertyChange(false,property,oldValue,newValue,source); + } + } + + /** + * Tests to see if a EventDispatcher instance has been created for this object. + **/ + public function get hasEventManager():Boolean{ + return true; + } + + //specific identity code + + private var _id:String; + /** + * The identifier used by document to refer to this object. + **/ + public function get id():String{ + + if(_id){ + return _id; + } + else{ + _id =NameUtil.createUniqueName(this); + name=_id; + return _id; + } + } + public function set id(value:String):void{ + _id = value; + name=_id; + } + + private var _document:Object; + /** + * The MXML document that created this object. + **/ + public function get document():Object{ + return _document; + } + + /** + * Called after the implementing object has been created and all component properties specified on the MXML tag have been initialized. + * + * @param document The MXML document that created this object. + * @param id The identifier used by document to refer to this object. + **/ + public function initialized(document:Object, id:String):void { + + //if the id has not been set (through as perhaps) + if(!_id){ + if(id){ + _id = id; + } + else{ + //if no id specified create one + _id = NameUtil.createUniqueName(this); + } + } + + //sprit has a name property and it is set + //to the instance value. Make sure it is the + //same as the id + name=_id; + + _document=document; + + if(enableEvents && !suppressEventProcessing){ + dispatchEvent(new FlexEvent(FlexEvent.INITIALIZE)); + } + + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/GraphicText.png b/Degrafa/com/degrafa/GraphicText.png new file mode 100644 index 0000000..e4298af Binary files /dev/null and b/Degrafa/com/degrafa/GraphicText.png differ diff --git a/Degrafa/com/degrafa/GraphicTextPlus.as b/Degrafa/com/degrafa/GraphicTextPlus.as new file mode 100644 index 0000000..669ce60 --- /dev/null +++ b/Degrafa/com/degrafa/GraphicTextPlus.as @@ -0,0 +1,159 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +package com.degrafa +{ + import flash.display.DisplayObjectContainer; + import flash.display.Graphics; + import flash.events.Event; + import flash.geom.Rectangle; + + import flash.text.TextField; + import flash.text.TextFormat; + + /** + * GraphicText extends TextField and enables support for text fields + * to be added to compositions. + * + * @see flash.text.TextField + **/ + public class GraphicTextPlus extends GraphicText + { + public function GraphicTextPlus() + { + super(); + addEventListener(Event.RENDER, renderHandler); + addEventListener(Event.INIT, initHandler); + } + + /** + * The render handler is used to enable runtime resizing/alignment + * @private + **/ + private function renderHandler(event:Event):void { + realign(); + } + + /** + * The init handler is used to enable runtime resizing/alignment + * @private + **/ + private function initHandler(event:Event):void { + realign(); + } + + /** + * Indicates the horizontal alignment of the anchored text object. Valid values are center, left, and right. + * + * @see flash.text.TextFormat + **/ + private var _halign:String="left"; + [Inspectable(category="General", enumeration="left,center,right", defaultValue="left")] + public function set halign(value:String):void{ + _halign = value; + realign(); + } + public function get halign():String{ + return _halign; + } + + /** + * Indicates the vertical alignment of the anchored text object. Valid values are center, left, and right. + * + * @see flash.text.TextFormat + **/ + private var _valign:String="top"; + [Inspectable(category="General", enumeration="top,middle,bottom", defaultValue="top")] + public function set valign(value:String):void{ + _valign = value; + realign(); + } + public function get valign():String{ + return _valign; + } + + /** + * An integer representing the horizontal anchor point + * + * @see flash.text.TextFormat + **/ + private var _anchorx:Number; + public function set anchorx(value:Number):void{ + _anchorx = value; + realign(); + } + public function get anchorx():Number{ + return _anchorx; + } + + /** + * An integer representing the vertical anchor point + * + * @see flash.text.TextFormat + **/ + private var _anchory:Number; + public function set anchory(value:Number):void{ + _anchory = value; + realign(); + } + public function get anchory():Number{ + return _anchory; + } + + /** + * draw is required for the IGeometry interface and has no effect here. + * @private + **/ + + public override function draw(graphics:Graphics,rc:Rectangle):void{ + realign(); + super.draw(graphics,rc); + } + + public function realign() : void { + if(isNaN(anchorx)) + this.anchorx = x; + + if(isNaN(anchory)) + this.anchory = y; + + if(halign == "left") { + this.x = this.anchorx; + } + if(halign == "center") { + this.x = this.anchorx - (this.width / 2); + } + if(halign == "right") { + this.x = this.anchorx - this.width; + } + if(valign == "top") { + this.y = this.anchory; + } + if(valign == "middle") { + this.y = this.anchory - (this.height/ 2); + } + if(valign == "bottom") { + this.y = this.anchory - this.height; + } + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/IGeometry.as b/Degrafa/com/degrafa/IGeometry.as new file mode 100644 index 0000000..6abfbda --- /dev/null +++ b/Degrafa/com/degrafa/IGeometry.as @@ -0,0 +1,43 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa{ + + import com.degrafa.core.IGraphicsFill; + import com.degrafa.core.IGraphicsStroke; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + /** + * Base interface for all Degrafa geometry objects. + **/ + public interface IGeometry{ + function draw(graphics:Graphics,rc:Rectangle):void + function endDraw(graphics:Graphics):void + function get stroke():IGraphicsStroke + function set stroke(value:IGraphicsStroke):void + function get fill():IGraphicsFill + function set fill(value:IGraphicsFill):void + function get data():Object + function set data(value:Object):void + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/IGeometryComposition.as b/Degrafa/com/degrafa/IGeometryComposition.as new file mode 100644 index 0000000..533a941 --- /dev/null +++ b/Degrafa/com/degrafa/IGeometryComposition.as @@ -0,0 +1,40 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa{ + + import com.degrafa.geometry.command.CommandStack; + + import flash.display.Graphics; + import flash.geom.Rectangle; + /** + * Base interface for all composable Degrafa objects. + **/ + public interface IGeometryComposition{ + + function draw(graphics:Graphics,rc:Rectangle):void + function endDraw(graphics:Graphics):void + function get bounds():Rectangle + function preDraw():void + function get commandStack():CommandStack; + function set commandStack(value:CommandStack):void; + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/IGraphic.as b/Degrafa/com/degrafa/IGraphic.as new file mode 100644 index 0000000..7301c6c --- /dev/null +++ b/Degrafa/com/degrafa/IGraphic.as @@ -0,0 +1,85 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa{ + + import com.degrafa.core.IGraphicsFill; + import com.degrafa.core.IGraphicsStroke; + import flash.display.DisplayObject; + import flash.geom.Rectangle; + + import flash.display.DisplayObjectContainer; + import flash.display.Graphics; + + /** + * Base interface for Degrafa Graphic objects. + **/ + public interface IGraphic{ + + function get width():Number + function set width(value:Number):void + + function get height():Number + function set height(value:Number):void + + function get x():Number + function set x(value:Number):void + + function get y():Number + function set y(value:Number):void + + function get name():String + function set name(value:String):void + + function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void + function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void + + function get fills():Array + function set fills(value:Array):void + + function get strokes():Array + function set strokes(value:Array):void + + function set percentHeight(value:Number):void + function get percentHeight():Number + + function set percentWidth(value:Number):void + function get percentWidth():Number + + function get measuredWidth():Number + function get measuredHeight():Number + + function get parent():DisplayObjectContainer + + function draw(graphics:Graphics,rc:Rectangle):void + function endDraw(graphics:Graphics):void + + function set target(value:DisplayObjectContainer):void + function get target():DisplayObjectContainer + + function get stroke():IGraphicsStroke + function set stroke(value:IGraphicsStroke):void + + function get fill():IGraphicsFill + function set fill(value:IGraphicsFill):void + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/IGraphicPoint.as b/Degrafa/com/degrafa/IGraphicPoint.as new file mode 100644 index 0000000..ae83511 --- /dev/null +++ b/Degrafa/com/degrafa/IGraphicPoint.as @@ -0,0 +1,33 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa{ + + //NOTE: though this has no details it is required to limit + //the types of points. + + /** + * Base interface for Degrafa point objects. + **/ + public interface IGraphicPoint{ + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/Surface.as b/Degrafa/com/degrafa/Surface.as new file mode 100644 index 0000000..56f148f --- /dev/null +++ b/Degrafa/com/degrafa/Surface.as @@ -0,0 +1,273 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa{ + + import com.degrafa.core.collections.FillCollection; + import com.degrafa.core.collections.GraphicsCollection; + import com.degrafa.core.collections.StrokeCollection; + + import flash.display.DisplayObject; + import flash.events.Event; + + + import mx.core.UIComponent; + import mx.events.PropertyChangeEvent; + import mx.events.PropertyChangeEventKind; + + [DefaultProperty("graphicsData")] + [Bindable(event="propertyChange")] + + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("Surface.png")] + + /** + * Surface is a simple UIComponent extension that allows Degrafa objects + * to be added to it's display list. Fills and strokes set here have no + * effect and only serve organizational purposes. + **/ + public class Surface extends UIComponent{ + + public function Surface(){ + super(); + } + + + private var _graphicsData:GraphicsCollection; + [Inspectable(category="General", arrayType="com.degrafa.IGraphic")] + [ArrayElementType("com.degrafa.IGraphic")] + /** + * A array of IGraphic objects. Objects of type GraphicText, GraphicImage + * and GeometryGroup are accepted and to this objects display list. + **/ + public function get graphicsData():Array{ + initGraphicsCollection(); + return _graphicsData.items; + } + public function set graphicsData(value:Array):void{ + + initGraphicsCollection(); + + _graphicsData.items = value; + + for each(var item:IGraphic in _graphicsData.items){ + + addChild(DisplayObject(item)); + + if (item.target==null){ + item.target=this; + } + } + + } + + /** + * Access to the Degrafa graphics collection object for this graphic object. + **/ + public function get graphicsCollection():GraphicsCollection{ + initGraphicsCollection(); + return _graphicsData; + } + + /** + * Initialize the strokes collection by creating it and adding an event listener. + **/ + private function initGraphicsCollection():void{ + if(!_graphicsData){ + _graphicsData = new GraphicsCollection(); + + //add a listener to the collection + if(enableEvents){ + _graphicsData.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + /** + * Principle event handler for any property changes to a + * graphic object or it's child objects. + **/ + private function propertyChangeHandler(event:PropertyChangeEvent):void{ + dispatchEvent(event); + // trace("propertyChange: " + event.property + " " + event.source); + } + + + private var _fills:FillCollection; + [Inspectable(category="General", arrayType="com.degrafa.core.IGraphicsFill")] + [ArrayElementType("com.degrafa.core.IGraphicsFill")] + /** + * A array of IGraphicsFill objects. + **/ + public function get fills():Array{ + initFillsCollection(); + return _fills.items; + } + public function set fills(value:Array):void{ + initFillsCollection(); + _fills.items = value; + } + + /** + * Access to the Degrafa fill collection object for this graphic object. + **/ + public function get fillCollection():FillCollection{ + initFillsCollection(); + return _fills; + } + + /** + * Initialize the fills collection by creating it and adding an event listener. + **/ + private function initFillsCollection():void{ + if(!_fills){ + _fills = new FillCollection(); + + //add a listener to the collection + if(enableEvents){ + _fills.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + private var _strokes:StrokeCollection; + [Inspectable(category="General", arrayType="com.degrafa.core.IGraphicsStroke")] + [ArrayElementType("com.degrafa.core.IGraphicsStroke")] + /** + * A array of IStroke objects. + **/ + public function get strokes():Array{ + initSrokesCollection(); + return _strokes.items; + } + public function set strokes(value:Array):void{ + initSrokesCollection(); + _strokes.items = value; + + } + + /** + * Access to the Degrafa stroke collection object for this graphic object. + **/ + public function get strokeCollection():StrokeCollection{ + initSrokesCollection(); + return _strokes; + } + + /** + * Initialize the strokes collection by creating it and adding an event listener. + **/ + private function initSrokesCollection():void{ + if(!_strokes){ + _strokes = new StrokeCollection(); + + //add a listener to the collection + if(enableEvents){ + _strokes.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + /** + * Draws the object and/or sizes and positions its children. + **/ + protected override function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void{ + super.updateDisplayList(unscaledWidth, unscaledHeight); + + setActualSize(getExplicitOrMeasuredWidth(), getExplicitOrMeasuredHeight()); + } + + //event related stuff + private var _enableEvents:Boolean=true; + /** + * Enable events for this object. + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get enableEvents():Boolean{ + return _enableEvents; + } + public function set enableEvents(value:Boolean):void{ + _enableEvents=value; + } + + private var _suppressEventProcessing:Boolean=false; + /** + * Temporarily suppress event processing for this object. + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get suppressEventProcessing():Boolean{ + return _suppressEventProcessing; + } + public function set suppressEventProcessing(value:Boolean):void{ + + if(_suppressEventProcessing==true && value==false){ + _suppressEventProcessing=value; + initChange("suppressEventProcessing",false,true,this); + } + else{ + _suppressEventProcessing=value; + } + } + + /** + * Dispatches an event into the event flow. + * + * @see EventDispatcher + **/ + override public function dispatchEvent(event:Event):Boolean{ + if(_suppressEventProcessing){return false;} + + return(super.dispatchEvent(event)); + + } + + /** + * Dispatches an property change event into the event flow. + **/ + public function dispatchPropertyChange(bubbles:Boolean = false, + property:Object = null, oldValue:Object = null, + newValue:Object = null, source:Object = null):Boolean{ + return dispatchEvent(new PropertyChangeEvent("propertyChange",bubbles,false,PropertyChangeEventKind.UPDATE,property,oldValue,newValue,source)); + } + + /** + * Helper function for dispatching property changes + **/ + public function initChange(property:String,oldValue:Object,newValue:Object,source:Object):void{ + if(hasEventManager){ + dispatchPropertyChange(false,property,oldValue,newValue,source); + } + } + + /** + * Tests to see if a EventDispatcher instance has been created for this object. + **/ + public function get hasEventManager():Boolean{ + return true; + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/Surface.png b/Degrafa/com/degrafa/Surface.png new file mode 100644 index 0000000..0b5d5c7 Binary files /dev/null and b/Degrafa/com/degrafa/Surface.png differ diff --git a/Degrafa/com/degrafa/core/DegrafaObject.as b/Degrafa/com/degrafa/core/DegrafaObject.as new file mode 100644 index 0000000..c11e541 --- /dev/null +++ b/Degrafa/com/degrafa/core/DegrafaObject.as @@ -0,0 +1,259 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core{ + + import flash.events.Event; + import flash.events.EventDispatcher; + + import mx.core.IMXMLObject; + import mx.events.FlexEvent; + import mx.events.PropertyChangeEvent; + import mx.events.PropertyChangeEventKind; + import mx.utils.NameUtil; + import com.degrafa.core.degrafa_internal; + + [Event(name="initialize", type="mx.events.FlexEvent")] + [Event(name="propertyChange", type="mx.events.PropertyChangeEvent")] + + /** + * Base class for all event enabled Degrafa objects. + **/ + public class DegrafaObject implements IDegrafaObject, IMXMLObject{ + + //if false the internal listeners will not be + //set for the objects at creation time in other words + //if you don't want runtime events set this to false + //if you do then set it to true + + private var _enableEvents:Boolean=true; + /** + * Enable events for this object. + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get enableEvents():Boolean{ + return _enableEvents; + } + public function set enableEvents(value:Boolean):void{ + _enableEvents=value; + } + + //if true all event processing will stop being dispatched + //used when you need to update many properties when set back + //to true the event that gets dispatched will cause the display + //update (draw) + private var _suppressEventProcessing:Boolean=false; + /** + * Temporarily suppress event processing for this object. + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get suppressEventProcessing():Boolean{ + return _suppressEventProcessing; + } + public function set suppressEventProcessing(value:Boolean):void{ + if(_suppressEventProcessing==true && value==false){ + _suppressEventProcessing=value; + initChange("suppressEventProcessing",false,true,this); + } + else{ + _suppressEventProcessing=value; + } + + } + + /** + * Tests to see if a EventDispatcher instance has been created for this object. + **/ + public function get hasEventManager():Boolean{ + return (_eventDispatcher) ? true:false; + } + + /** + * Registers an event listener object with an EventDispatcher object so that the listener receives notification of an event. + * + * @see EventDispatcher + **/ + public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = true):void{ + eventDispatcher.addEventListener(type, listener, useCapture, priority,useWeakReference); + } + + /** + * Dispatches an event into the event flow. + * + * @see EventDispatcher + **/ + public function dispatchEvent(evt:Event):Boolean{ + if(_suppressEventProcessing){ + evt.stopImmediatePropagation(); + return false; + } + + return eventDispatcher.dispatchEvent(evt); + + } + + /** + * Checks whether the EventDispatcher object has any listeners registered for a specific type of event. + * + * @see EventDispatcher + **/ + public function hasEventListener(type:String):Boolean{ + return eventDispatcher.hasEventListener(type); + } + + /** + * Removes a listener from the EventDispatcher object. + * + * @see EventDispatcher + **/ + public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void{ + eventDispatcher.removeEventListener(type, listener, useCapture); + } + + /** + * Checks whether an event listener is registered with this EventDispatcher object or any of its ancestors for the specified event type. + * + * @see EventDispatcher + **/ + public function willTrigger(type:String):Boolean { + return eventDispatcher.willTrigger(type); + } + + /** + * Dispatches an property change event into the event flow. + **/ + public function dispatchPropertyChange(bubbles:Boolean = false, + property:Object = null, oldValue:Object = null, + newValue:Object = null, source:Object = null):Boolean{ + return dispatchEvent(new PropertyChangeEvent("propertyChange",bubbles,false,PropertyChangeEventKind.UPDATE,property,oldValue,newValue,source)); + } + + /** + * Helper function for dispatching property changes + **/ + public function initChange(property:String,oldValue:Object,newValue:Object,source:Object):void{ + if(hasEventManager && !suppressEventProcessing){ + dispatchPropertyChange(false,property,oldValue,newValue,source); + } + } + + + private var _eventDispatcher:EventDispatcher; + /** + * EventDispatcher instance for this object. + **/ + protected function get eventDispatcher():EventDispatcher{ + if(!_eventDispatcher){ + _eventDispatcher=new EventDispatcher(this) + } + + return _eventDispatcher; + } + protected function set eventDispatcher(value:EventDispatcher):void{ + _eventDispatcher = value; + } + + private var _id:String; + /** + * The identifier used by document to refer to this object. + **/ + public function get id():String{ + + if(_id){ + return _id; + } + else{ + _id =NameUtil.createUniqueName(this); + return _id; + } + } + public function set id(value:String):void{ + _id = value; + } + + /** + * The name that refers to this object. + **/ + public function get name():String{ + return id; + } + + private var _document:Object; + /** + * The MXML document that created this object. + **/ + public function get document():Object{ + return _document; + } + + /** + * Called after the implementing object has been created and all component properties specified on the MXML tag have been initialized. + * + * @param document The MXML document that created this object. + * @param id The identifier used by document to refer to this object. + **/ + public function initialized(document:Object, id:String):void{ + + //if the id has not been set (through as perhaps) + if(!_id){ + if(id){ + _id = id; + } + else{ + //if no id specified create one + _id = NameUtil.createUniqueName(this); + } + } + _document=document; + + + _isInitialized = true; + + if(enableEvents && ! _suppressEventProcessing){ + dispatchEvent(new FlexEvent(FlexEvent.INITIALIZE)); + } + } + + /** + * A boolean value indicating that this object has been initialized + **/ + private var _isInitialized:Boolean; + public function get isInitialized():Boolean{ + return _isInitialized; + } + + degrafa_internal var _parent:IDegrafaObject; + /** + * The current degrafa object parent. + * At this time only used for geometry. + **/ + public function get parent():IDegrafaObject{ + return degrafa_internal::_parent; + } + public function set parent(value:IDegrafaObject):void{ + degrafa_internal::_parent=value; + } + + //an array of current bindings for this object + public var objectBindings:Array; + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/IBlend.as b/Degrafa/com/degrafa/core/IBlend.as new file mode 100644 index 0000000..1c8ce51 --- /dev/null +++ b/Degrafa/com/degrafa/core/IBlend.as @@ -0,0 +1,31 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core{ + + /** + * Base interface for Degrafa objects that require a blend mode. + **/ + public interface IBlend{ + function get blendMode():String; + function set blendMode(value:String):void; + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/IDegrafaObject.as b/Degrafa/com/degrafa/core/IDegrafaObject.as new file mode 100644 index 0000000..32d6e67 --- /dev/null +++ b/Degrafa/com/degrafa/core/IDegrafaObject.as @@ -0,0 +1,38 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core{ + + import flash.events.IEventDispatcher; + /** + * Base interface for all event enabled Degrafa objects. + **/ + public interface IDegrafaObject extends IEventDispatcher{ + function dispatchPropertyChange(bubbles:Boolean = false, property:Object = null, oldValue:Object = null,newValue:Object = null, source:Object = null):Boolean + function get enableEvents():Boolean + function set enableEvents(value:Boolean):void + function get hasEventManager():Boolean + function get suppressEventProcessing():Boolean + function set suppressEventProcessing(value:Boolean):void + function get parent():IDegrafaObject + function set parent(value:IDegrafaObject):void + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/IGraphicSkin.as b/Degrafa/com/degrafa/core/IGraphicSkin.as new file mode 100644 index 0000000..c5dcd4d --- /dev/null +++ b/Degrafa/com/degrafa/core/IGraphicSkin.as @@ -0,0 +1,37 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core{ + + import com.degrafa.IGraphic; + import com.degrafa.core.collections.GeometryCollection; + [Exclude(name="graphicsData", kind="property")] + + /** + * Base interface for all Degrafa skin objects. + **/ + public interface IGraphicSkin extends IGraphic{ + function set geometry(value:Array):void + function get geometry():Array + function get geometryCollection():GeometryCollection + function invalidateDisplayList():void + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/IGraphicsFill.as b/Degrafa/com/degrafa/core/IGraphicsFill.as new file mode 100644 index 0000000..e46099b --- /dev/null +++ b/Degrafa/com/degrafa/core/IGraphicsFill.as @@ -0,0 +1,44 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core{ + import com.degrafa.core.IDegrafaObject; + import com.degrafa.IGeometryComposition; + import flash.geom.Rectangle; + import flash.display.Graphics; + import mx.graphics.IFill; + + /** + * Base interface for all Degrafa fill objects. + **/ + public interface IGraphicsFill extends IDegrafaObject, IFill { + //no difference as of yet basically used to limit the list + // we have to be careful as we would like the ability to use + //IGraphicsFill objects for charts etc.. + + function set requester(value:IGeometryComposition ):void; + + function get restartFunction():Function; + function get lastArgs():Array; + function get lastRectangle():Rectangle; + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/IGraphicsStroke.as b/Degrafa/com/degrafa/core/IGraphicsStroke.as new file mode 100644 index 0000000..2a584ef --- /dev/null +++ b/Degrafa/com/degrafa/core/IGraphicsStroke.as @@ -0,0 +1,56 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core{ + import flash.display.Graphics; + import flash.geom.Rectangle; + + /** + * Base interface for all Degrafa stroke objects. + **/ + public interface IGraphicsStroke extends IDegrafaObject{ + + function get weight():Number; + function set weight(value:Number):void; + + function get scaleMode():String; + function set scaleMode(value:String):void; + + function get pixelHinting():Boolean; + function set pixelHinting(value:Boolean):void; + + function get miterLimit():Number; + function set miterLimit(value:Number):void; + + function get joints():String; + function set joints(value:String):void; + + function get caps():String; + function set caps(value:String):void; + + function apply(graphics:Graphics, rc:Rectangle):void + + function get reApplyFunction():Function; + function get lastArgs():Array; + function get lastRectangle():Rectangle; + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/ITransformablePaint.as b/Degrafa/com/degrafa/core/ITransformablePaint.as new file mode 100644 index 0000000..639b307 --- /dev/null +++ b/Degrafa/com/degrafa/core/ITransformablePaint.as @@ -0,0 +1,34 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core{ + import com.degrafa.transform.ITransform; + + /** + * Base interface for all Degrafa transformable fill and stroke objects. + **/ + public interface ITransformablePaint extends IDegrafaObject, IGraphicsFill{ + + function get transform():ITransform; + function set transform(value:ITransform):void + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/Measure.as b/Degrafa/com/degrafa/core/Measure.as new file mode 100644 index 0000000..574ddcb --- /dev/null +++ b/Degrafa/com/degrafa/core/Measure.as @@ -0,0 +1,135 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core{ + + import flash.system.Capabilities; + + /** + * Constant and conversion class for css skin values. + */ + public class Measure{ + + // relative length units + public static const EM:String = "em"; + public static const EX:String = "ex"; + public static const PIXELS:String = "pixels"; + //public static const GRID:String = "gd"; + //public static const REM:String = "rem"; + //public static const VIEWPORT_WIDTH:String = "vw"; + //public static const VIEWPORT_HEIGHT:String = "vh"; + //public static const VIEWPORT_MEASURE:String = "vm"; + public static const CH:String = "ch"; + public static const PERCENT:String = "percent"; // 0 to 100 + public static const RATIO:String = "ratio"; // 0 to 1 + + // absolute length units + public static const INCHES:String = "inches"; + public static const CENTIMETERS:String = "centimeters"; + public static const MILLIMETERS:String = "millimeters"; + public static const POINTS:String = "points"; + public static const PICAS:String = "picas"; + + // alternative + public static const DEGREES:String = "degrees"; + + // todo: implement real font measurement? + public var emSize:Number = -1; + public var exSize:Number = -1; + public var chSize:Number = -1; + + public var unit:String; + public var value:Number; + + public function Measure(value:Number = 0, unit:String = Measure.PIXELS):void { + this.value = value; + this.unit = unit; + } + + public function relativeTo(length:Number, object:Object = null):Number { + switch(unit) { + case Measure.PIXELS: + return value; + break; + case Measure.PERCENT: + return length*(value/100); + break; + case Measure.RATIO: + return length*value; + break; + case Measure.POINTS: + return (value/72)*Capabilities.screenDPI; + break; + case Measure.INCHES: + return value*Capabilities.screenDPI; + break; + case Measure.CENTIMETERS: + return (value/2.54)*Capabilities.screenDPI; + break; + case Measure.MILLIMETERS: + return (value/25.4)*Capabilities.screenDPI; + break; + case Measure.PICAS: + return (value/6)*Capabilities.screenDPI; + break; + case Measure.EM: + updateFontSizes(); + return value*emSize; + break; + case Measure.EX: + updateFontSizes(); + return value*exSize; + break; + case Measure.CH: + updateFontSizes(); + return value*chSize; + break; + default: + return value; + break; + } + } + + private function updateFontSizes():void { + if(emSize < 0) { + emSize = (12/72)*Capabilities.screenDPI; // 12pt? + } + if(exSize < 0) { + exSize = (6/72)*Capabilities.screenDPI; // half line of 12pt? + } + if(chSize < 0) { + chSize = (12/72)*Capabilities.screenDPI; // 12pt? + } + } + + + //Object Cohersion Methods + + public function toString():String { + return value.toString() + unit; + } + + public function valueOf():Number { + return relativeTo(100); + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/collections/DecoratorCollection.as b/Degrafa/com/degrafa/core/collections/DecoratorCollection.as new file mode 100644 index 0000000..bb5bd52 --- /dev/null +++ b/Degrafa/com/degrafa/core/collections/DecoratorCollection.as @@ -0,0 +1,134 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core.collections{ + + import com.degrafa.decorators.IDecorator; + + + /** + * The DecoratorCollection stores a collection of IDecorator objects + **/ + public class DecoratorCollection extends DegrafaCollection{ + + /** + * Constructor. + * + *

The DecoratorCollection collection constructor accepts 2 optional arguments + * that specify the IDecorator objects to be added and a event operation flag.

+ * + * @param array An array of IDecorator objects. + * @param suppressEvents A boolean value indicating if events should not be + * dispatched for this collection. + */ + public function DecoratorCollection(array:Array=null,suppressEvents:Boolean=false){ + super(IDecorator,array,suppressEvents); + } + + /** + * Adds a IDecorator item to the collection. + * + * @param value The IDecorator object to be added. + * @return The IDecorator object that was added. + **/ + public function addItem(value:IDecorator):IDecorator{ + return super._addItem(value); + } + + /** + * Removes an IDecorator item from the collection. + * + * @param value The IDecorator object to be removed. + * @return The IDecorator object that was removed. + **/ + public function removeItem(value:IDecorator):IDecorator{ + return super._removeItem(value); + } + + /** + * Retrieve a IDecorator item from the collection based on the index value + * requested. + * + * @param index The collection index of the IDecorator object to retrieve. + * @return The IDecorator object that was requested if it exists. + **/ + public function getItemAt(index:Number):IDecorator{ + return super._getItemAt(index); + } + + /** + * Retrieve a IDecorator item from the collection based on the object value. + * + * @param value The IDecorator object for which the index is to be retrieved. + * @return The IDecorator index value that was requested if it exists. + **/ + public function getItemIndex(value:IDecorator):int{ + return super._getItemIndex(value); + } + + /** + * Adds a IDecorator item to this collection at the specified index. + * + * @param value The IDecorator object that is to be added. + * @param index The position in the collection at which to add the IDecorator object. + * + * @return The IDecorator object that was added. + **/ + public function addItemAt(value:IDecorator,index:Number):IDecorator{ + return super._addItemAt(value,index); + } + + /** + * Removes a IDecorator object from this collection at the specified index. + * + * @param index The index of the IDecorator object to remove. + * @return The IDecorator object that was removed. + **/ + public function removeItemAt(index:Number):IDecorator{ + return super._removeItemAt(index); + } + + /** + * Change the index of the IDecorator object within this collection. + * + * @param value The IDecorator object that is to be repositioned. + * @param newIndex The position at which to place the IDecorator object within the collection. + * @return True if the operation is successful False if unsuccessful. + **/ + public function setItemIndex(value:IDecorator,newIndex:Number):Boolean{ + return super._setItemIndex(value,newIndex); + } + + /** + * Adds a series of IDecorator objects to this collection. + * + * @param value The collection to be added to this IDecorator collection. + * @return The resulting DecoratorCollection after the objects are added. + **/ + public function addItems(value:DecoratorCollection):DecoratorCollection{ + //todo + super.concat(value.items) + return this; + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/collections/DegrafaCollection.as b/Degrafa/com/degrafa/core/collections/DegrafaCollection.as new file mode 100644 index 0000000..6f19a82 --- /dev/null +++ b/Degrafa/com/degrafa/core/collections/DegrafaCollection.as @@ -0,0 +1,597 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core.collections{ + + import com.degrafa.core.DegrafaObject; + import com.degrafa.core.IDegrafaObject; + + + import flash.utils.getQualifiedClassName; + + import mx.events.PropertyChangeEvent; + + //base degrafa collection that proxies the array type + [DefaultProperty("items")] + /** + * The Degrafa collection stores a collection of objects + * of a specific type and is the base class for all collection + * objects. + **/ + public class DegrafaCollection extends DegrafaObject + { + /** + * The type of objects being stored in this colelction. + **/ + private var type:Class; + + /** + * Constructor. + * + *

The Degrafa collection constructor accepts 4 optional arguments + * that specify it's type, an array of values of the expected type to be added + * and 2 event operation flags.

+ * + * @param type An class value specifing the types of objects being stored here. + * @param array An array of objects that are of the specified type. + * @param suppressEvents A boolean value indicating if events should not be + * dispatched for this collection. + * @param enableTypeChecking A boolean value indicating if type checking should be performed. + */ + public function DegrafaCollection(type:Class,array:Array=null,suppressEvents:Boolean=false,enableTypeChecking:Boolean=true){ + + this.type = type; + _enableTypeChecking = enableTypeChecking; + + suppressEventProcessing = suppressEvents; + + if(array){ + items =array; + } + + } + + private var _enableTypeChecking:Boolean=true; + /** + * Allows internal type checking to be turned off. + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get enableTypeChecking():Boolean{ + return _enableTypeChecking; + } + public function set enableTypeChecking(value:Boolean):void{ + _enableTypeChecking=value; + } + + + /** + * Verifies each item in the passed array for a valid + * type and throws a runtime error if an object with + * out a valid type is found. + * + * @param value An array of objects to test on. + **/ + private function checkValidTypes(value:Array):void{ + //type check and throw exception if invalid type found + if(_enableTypeChecking){ + for each (var item:Object in value){ + if(!item is type){ + throw new TypeError(flash.utils.getQualifiedClassName(item) + + " is not a valid " + + flash.utils.getQualifiedClassName(type)); + + return; + } + } + } + } + + + private var _items:Array=[]; + /** + * An array of items being stored in this collection. + **/ + public function get items():Array{ + return _items; + } + public function set items(value:Array):void{ + //support null assignments + if (value==null) value=[]; + //type check items + checkValidTypes(value); + //compare and update + if(value !=_items){ + + //clear any event listeners + if(_items && _items.length && enableEvents && hasEventManager){ + removeListeners(); + } + + var oldValue:Array = _items; + _items=value; + + if(enableEvents && hasEventManager){ + //call local helper to dispatch event (but only if either the old or new array had at least one element) + if (value.length||oldValue.length) initChange("items",oldValue,_items,this); + } + } + + //add event listeners + if (value && value.length) addListeners(); + + } + + + /** + * Adds a new item to the collection. + * + * @param value The item to add. + * @return The item added. + **/ + protected function _addItem(value:*):*{ + addListener(value); + concat(value); + return value; + } + + /** + * Removes a item from the collection. + * + * @param value The item to remove. + * @return The item removed. + **/ + protected function _removeItem(value:*):*{ + + //get the index + var index:int = indexOf(value,0); + _removeItemAt(index); + + return null; + } + + /** + * Return a item at the given index. + * + * @param value The item index to return. + * @return The item requested. + **/ + protected function _getItemAt(index:Number):*{ + return items[index]; + } + + /** + * Return the index for the item in the collection. + * + * @param value The item to find. + * @return The index location of the item. + **/ + protected function _getItemIndex(value:*):int{ + return indexOf(value,0); + } + + + /** + * Add a item at the given index. + * + * @param value The item to add to the collection. + * @param index The index at which to add the item. + * @return The item added. + **/ + protected function _addItemAt(value:Object,index:Number):*{ + addListener(value); + splice(index,0,value); + return value; + } + + /** + * Removes a item from the collection. + * + * @param value The item index to remove. + * @return The removed item. + **/ + protected function _removeItemAt(index:Number):*{ + //clean up + removeListener(items[index]); + return splice(index,1)[0]; + } + + /** + * Change the position of an item in the collection + * + * @param index The items new index. + * @param value The item to be repositioned. + * @return True if the item was repositioned. + **/ + protected function _setItemIndex(value:*,newIndex:Number):Boolean{ + var spliced:Array = items.splice(items.indexOf(value),1); + items.splice(newIndex, 0, spliced[0]); + return true; + } + + //to be overidden in subclasse if nessesary + + /** + * Addes a property change event listener and a parent reference to each item in the collection. + **/ + public function addListeners():void{ + if(enableEvents){ + for each (var item:Object in items){ + if(item is IDegrafaObject){ + //update parent reff + IDegrafaObject(item).parent = this.parent; + + if(IDegrafaObject(item).enableEvents){ + IDegrafaObject(item).addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + + } + } + } + + /** + * Removes the property change event listener and parent reference from each item in the collection. + **/ + public function removeListeners():void{ + for each (var item:Object in items){ + if(item is IDegrafaObject){ + //update parent reff + IDegrafaObject(item).parent = null; + + IDegrafaObject(item).removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + /** + * Addes a property change event listener and a parent reference to the passed object. + **/ + public function addListener(value:*):void{ + if(value is IDegrafaObject){ + //update parent reff + IDegrafaObject(value).parent = this.parent; + + if(enableEvents){ + if(IDegrafaObject(value).enableEvents){ + IDegrafaObject(value).addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + } + + /** + * Removes the property change event listener and parent reference from the passed object. + **/ + public function removeListener(value:*):void{ + if(value is IDegrafaObject){ + //update parent reff + IDegrafaObject(value).parent = null; + + IDegrafaObject(value).removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + + } + + + /** + * Property change event handler for this collection. + **/ + public function propertyChangeHandler(event:PropertyChangeEvent):void{ + if(!suppressEventProcessing){ + dispatchEvent(event); + } + } + + + //proxy for array calls in some cases the subclasses may override these + //to provide additional function or safety + + /** + * Concatenates the elements specified in the parameters with the elements in an array and creates a new array. + * + * @see Array + **/ + public function concat(... args):Array{ + + var oldValue:Array = _items; + + //type check item(s) + checkValidTypes(args); + + var i:int = 0 + var length:int = args.length + + for (; i 1){ + returnArray=_items.splice(startIndex,deleteCount,values); + } + else{ + returnArray=_items.splice(startIndex,deleteCount); + } + + if(returnArray){ + len=returnArray.length; + for (i= 0; i BEFORE_FIRST_INDEX) + return source[currentIndex]; + else + return null; + } + + /** + * Moves the cursor up one item in the currentIndex unless it is at the end. + * + * @return Boolean value of whether or not the cursor is at the end of the array. + * + */ + public function moveNext():Boolean + { + //the afterLast getter checks validity and also checks length > 0 + if (afterLast) + { + return false; + } + // we can't set the index until we know that we can move there first. + var tempIndex:int = beforeFirst ? 0 : currentIndex + 1; + if (tempIndex >= source.length) + { + tempIndex = AFTER_LAST_INDEX; + } + currentIndex = tempIndex; + return !afterLast; + } + + /** + * Moves the cursor down one item in the currentIndex unless it is at the beginning. + * + * @return Boolean value of whether or not the cursor is at the beginning of the array. + * + */ + public function movePrevious():Boolean + { + //the afterLast getter checks validity and also checks length > 0 + if (beforeFirst) + { + return false; + } + // we can't set the index until we know that we can move there first + var tempIndex:int = afterLast ? source.length - 1 : currentIndex - 1; + + currentIndex = tempIndex; + return !beforeFirst; + } + + /** + * Moves cursor to the front. + */ + public function moveFirst():void + { + currentIndex = BEFORE_FIRST_INDEX; + } + + /** + * Moves cursor to the end. + */ + public function moveLast():void + { + currentIndex = source.length; + } + + /** + * Inserts a Object into the array at the currentIndex. + * + * @param value The Object to be inserted into the array. + * + */ + public function insert(value:*):void + { + var insertIndex:int; + if (afterLast || beforeFirst) + { + source.push(value); + } + else + { + source.splice(currentIndex, 0, value); + } + } + + /** + * Removes a Object from the array at the currentIndex. + * + * @return The Object removed from the array. + */ + public function remove():* + { + var value:Object = source[currentIndex]; + + source = source.splice(currentIndex, 1); + + return value; + } + + /** + * Moves the currentIndex using the bookmark and offset. + * + * @param bookmark CursorBookmark used to assist the seek. The enumeration values are FIRST, CURRENT, LAST. + * @param offset Number of places away from the bookmark the currentIndex should be moved. + */ + public function seek(bookmark:CursorBookmark, offset:int = 0):void + { + if (source.length == 0) + { + currentIndex = AFTER_LAST_INDEX; + return; + } + + var newIndex:int = currentIndex; + if (bookmark == CursorBookmark.FIRST) + { + newIndex = 0; + } + else if (bookmark == CursorBookmark.LAST) + { + newIndex = source.length - 1; + } + + newIndex += offset; + + if (newIndex >= source.length) + { + currentIndex = AFTER_LAST_INDEX; + } + else if (newIndex < 0) + { + currentIndex = BEFORE_FIRST_INDEX; + } + else + { + currentIndex = newIndex; + } + } + + /** + * Checks whether or not the cursor is before the first item. + */ + public function get beforeFirst():Boolean + { + return currentIndex == BEFORE_FIRST_INDEX || source.length == 0; + } + + /** + * Checks whether or not the cursor is after the last item. + */ + public function get afterLast():Boolean + { + return currentIndex == AFTER_LAST_INDEX || source.length == 0; + } + + /** + * Gets the Object before the currentIndex + */ + public function get previousObject():* + { + if (beforeFirst) + return null; + + var tempIndex:int = afterLast ? source.length - 1 : currentIndex - 1; + + if (tempIndex == BEFORE_FIRST_INDEX) + return null; + + return source[tempIndex]; + } + /** + * Gets the Object after the currentIndex + */ + public function get nextObject():* + { + if(afterLast) + return null; + + var tempIndex:int = beforeFirst ? 0 : currentIndex + 1; + + if (tempIndex >= source.length) + return null; + + return source[tempIndex]; + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/collections/DisplayObjectCollection.as b/Degrafa/com/degrafa/core/collections/DisplayObjectCollection.as new file mode 100644 index 0000000..8e24856 --- /dev/null +++ b/Degrafa/com/degrafa/core/collections/DisplayObjectCollection.as @@ -0,0 +1,137 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core.collections{ + + import flash.display.DisplayObject; + + + /** + * The DisplayObjectCollection stores a collection of flash.display.DisplayObject + * objects. + **/ + public class DisplayObjectCollection extends DegrafaCollection{ + + /** + * Constructor. + * + *

The display object collection constructor accepts 2 optional arguments + * that specify the display objects to be added and a event operation flag.

+ * + * @param array An array of DisplayObject objects. + * @param suppressEvents A boolean value indicating if events should not be + * dispatched for this collection. + */ + public function DisplayObjectCollection(array:Array=null,suppressEvents:Boolean=false){ + super(DisplayObject,array,suppressEvents); + + } + + + /** + * Adds a DisplayObject item to the collection. + * + * @param value The DisplayObject object to be added. + * @return The DisplayObject object that was added. + **/ + public function addItem(value:DisplayObject):DisplayObject{ + return super._addItem(value); + } + + /** + * Removes an DisplayObject item from the collection. + * + * @param value The DisplayObject object to be removed. + * @return The DisplayObject object that was removed. + **/ + public function removeItem(value:DisplayObject):DisplayObject{ + return super._removeItem(value); + } + + /** + * Retrieve a DisplayObject item from the collection based on the index value + * requested. + * + * @param index The collection index of the DisplayObject object to retrieve. + * @return The DisplayObject object that was requested if it exists. + **/ + public function getItemAt(index:Number):DisplayObject{ + return super._getItemAt(index); + } + + /** + * Retrieve a DisplayObject item from the collection based on the object value. + * + * @param value The DisplayObject object for which the index is to be retrieved. + * @return The DisplayObject index value that was requested if it exists. + **/ + public function getItemIndex(value:DisplayObject):int{ + return super._getItemIndex(value); + } + + /** + * Adds a DisplayObject item to this collection at the specified index. + * + * @param value The DisplayObject object that is to be added. + * @param index The position in the collection at which to add the DisplayObject object. + * + * @return The DisplayObject object that was added. + **/ + public function addItemAt(value:DisplayObject,index:Number):DisplayObject{ + return super._addItemAt(value,index); + } + + /** + * Removes a DisplayObject object from this collection at the specified index. + * + * @param index The index of the DisplayObject object to remove. + * @return The DisplayObject object that was removed. + **/ + public function removeItemAt(index:Number):DisplayObject{ + return super._removeItemAt(index); + } + + /** + * Change the index of the DisplayObject object within this collection. + * + * @param value The DisplayObject object that is to be repositioned. + * @param newIndex The position at which to place the DisplayObject object within the collection. + * @return True if the operation is successful False if unsuccessful. + **/ + public function setItemIndex(value:DisplayObject,newIndex:Number):Boolean{ + return super._setItemIndex(value,newIndex); + } + + /** + * Adds a series of DisplayObject objects to this collection. + * + * @param value The collection to be added to this DisplayObject collection. + * @return The resulting DisplayObjectCollection after the objects are added. + **/ + public function addItems(value:DisplayObjectCollection):DisplayObjectCollection{ + //todo + super.concat(value.items) + return this; + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/collections/FillCollection.as b/Degrafa/com/degrafa/core/collections/FillCollection.as new file mode 100644 index 0000000..23df95b --- /dev/null +++ b/Degrafa/com/degrafa/core/collections/FillCollection.as @@ -0,0 +1,135 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core.collections{ + + import com.degrafa.core.IGraphicsFill; + + + /** + * The FillCollection stores a collection of IGraphicsFill objects + **/ + public class FillCollection extends DegrafaCollection{ + + /** + * Constructor. + * + *

The fill collection constructor accepts 2 optional arguments + * that specify the fills to be added and a event operation flag.

+ * + * @param array An array of IGraphicsFill objects. + * @param suppressEvents A boolean value indicating if events should not be + * dispatched for this collection. + */ + public function FillCollection(array:Array=null,suppressEvents:Boolean=false){ + super(IGraphicsFill,array,suppressEvents); + + } + + /** + * Adds a IGraphicsFill item to the collection. + * + * @param value The IGraphicsFill object to be added. + * @return The IGraphicsFill object that was added. + **/ + public function addItem(value:IGraphicsFill):IGraphicsFill{ + return super._addItem(value); + } + + /** + * Removes an IGraphicsFill item from the collection. + * + * @param value The IGraphicsFill object to be removed. + * @return The IGraphicsFill object that was removed. + **/ + public function removeItem(value:IGraphicsFill):IGraphicsFill{ + return super._removeItem(value); + } + + /** + * Retrieve a IGraphicsFill item from the collection based on the index value + * requested. + * + * @param index The collection index of the IGraphicsFill object to retrieve. + * @return The IGraphicsFill object that was requested if it exists. + **/ + public function getItemAt(index:Number):IGraphicsFill{ + return super._getItemAt(index); + } + + /** + * Retrieve a IGraphicsFill item from the collection based on the object value. + * + * @param value The IGraphicsFill object for which the index is to be retrieved. + * @return The IGraphicsFill index value that was requested if it exists. + **/ + public function getItemIndex(value:IGraphicsFill):int{ + return super._getItemIndex(value); + } + + /** + * Adds a IGraphicsFill item to this collection at the specified index. + * + * @param value The IGraphicsFill object that is to be added. + * @param index The position in the collection at which to add the IGraphicsFill object. + * + * @return The IGraphicsFill object that was added. + **/ + public function addItemAt(value:IGraphicsFill,index:Number):IGraphicsFill{ + return super._addItemAt(value,index); + } + + /** + * Removes a IGraphicsFill object from this collection at the specified index. + * + * @param index The index of the IGraphicsFill object to remove. + * @return The IGraphicsFill object that was removed. + **/ + public function removeItemAt(index:Number):IGraphicsFill{ + return super._removeItemAt(index); + } + + /** + * Change the index of the IGraphicsFill object within this collection. + * + * @param value The IGraphicsFill object that is to be repositioned. + * @param newIndex The position at which to place the IGraphicsFill object within the collection. + * @return True if the operation is successful False if unsuccessful. + **/ + public function setItemIndex(value:IGraphicsFill,newIndex:Number):Boolean{ + return super._setItemIndex(value,newIndex); + } + + /** + * Adds a series of IGraphicsFill objects to this collection. + * + * @param value The collection to be added to this IGraphicsFill collection. + * @return The resulting FillCollection after the objects are added. + **/ + public function addItems(value:FillCollection):FillCollection{ + //todo + super.concat(value.items) + return this; + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/collections/FilterCollection.as b/Degrafa/com/degrafa/core/collections/FilterCollection.as new file mode 100644 index 0000000..6f2989c --- /dev/null +++ b/Degrafa/com/degrafa/core/collections/FilterCollection.as @@ -0,0 +1,194 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core.collections{ + + import com.degrafa.filters.DegrafaFilter; + + import flash.filters.BitmapFilter; + + import mx.events.PropertyChangeEvent; + + + + /** + * The FilterCollection stores a collection of BitmapFilter objects + **/ + public class FilterCollection extends DegrafaCollection{ + + /** + * Constructor. + * + *

The filter collection constructor accepts 2 optional arguments + * that specify the filters to be added and a event operation flag.

+ * + * @param array An array of BitmapFilter objects. + * @param suppressEvents A boolean value indicating if events should not be + * dispatched for this collection. + */ + public function FilterCollection(array:Array=null,suppressEvents:Boolean=false){ + super(BitmapFilter,array,suppressEvents); + + } + + //Internal list of watched degrafa filter proxies + private var degrafaFilters:Array=[]; + + override public function set items(value:Array):void + { + if (!value) + value=[]; + + removeAllWatchers(); + + degrafaFilters=[]; + var actualFilters:Array=[]; + + var i:uint; + + for (i=0; i < value.length; i++) + { + var item:*=value[i]; + var df:DegrafaFilter=item as DegrafaFilter; + + if (df) + { + actualFilters.push(df.bitmapFilter); + degrafaFilters.push(df); + addWatcher(df); + } + else + { + actualFilters.push(item); + } + } + + super.items=actualFilters; + } + + private function removeAllWatchers():void + { + for each (var df:DegrafaFilter in degrafaFilters) + { + df.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, filterPropertyChangeHandler); + } + } + + private function addWatcher(df:DegrafaFilter):void + { + df.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, filterPropertyChangeHandler, false, 0, true); + } + + private function filterPropertyChangeHandler(event:PropertyChangeEvent):void + { + var actualFilter:BitmapFilter=DegrafaFilter(event.target).bitmapFilter; + dispatchPropertyChange(false, event.property, event.oldValue, event.newValue, actualFilter); + } + + /** + * Adds a BitmapFilter item to the collection. + * + * @param value The BitmapFilter object to be added. + * @return The BitmapFilter object that was added. + **/ + public function addItem(value:BitmapFilter):BitmapFilter{ + return super._addItem(value); + } + + /** + * Removes an BitmapFilter item from the collection. + * + * @param value The BitmapFilter object to be removed. + * @return The BitmapFilter object that was removed. + **/ + public function removeItem(value:BitmapFilter):BitmapFilter{ + return super._removeItem(value); + } + + /** + * Retrieve a BitmapFilter item from the collection based on the index value + * requested. + * + * @param index The collection index of the BitmapFilter object to retrieve. + * @return The BitmapFilter object that was requested if it exists. + **/ + public function getItemAt(index:Number):BitmapFilter{ + return super._getItemAt(index); + } + + /** + * Retrieve a BitmapFilter item from the collection based on the object value. + * + * @param value The BitmapFilter object for which the index is to be retrieved. + * @return The BitmapFilter index value that was requested if it exists. + **/ + public function getItemIndex(value:BitmapFilter):int{ + return super._getItemIndex(value); + } + + /** + * Adds a BitmapFilter item to this collection at the specified index. + * + * @param value The BitmapFilter object that is to be added. + * @param index The position in the collection at which to add the BitmapFilter object. + * + * @return The BitmapFilter object that was added. + **/ + public function addItemAt(value:BitmapFilter,index:Number):BitmapFilter{ + return super._addItemAt(value,index); + } + + /** + * Removes a BitmapFilter object from this collection at the specified index. + * + * @param index The index of the BitmapFilter object to remove. + * @return The BitmapFilter object that was removed. + **/ + public function removeItemAt(index:Number):BitmapFilter{ + return super._removeItemAt(index); + } + + /** + * Change the index of the BitmapFilter object within this collection. + * + * @param value The BitmapFilter object that is to be repositioned. + * @param newIndex The position at which to place the BitmapFilter object within the collection. + * @return True if the operation is successful False if unsuccessful. + **/ + public function setItemIndex(value:BitmapFilter,newIndex:Number):Boolean{ + return super._setItemIndex(value,newIndex); + } + + /** + * Adds a series of BitmapFilter objects to this collection. + * + * @param value The collection to be added to this BitmapFilter collection. + * @return The resulting FillCollection after the objects are added. + **/ + public function addItems(value:FilterCollection):FilterCollection{ + //todo + super.concat(value.items) + return this; + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/collections/GeometryCollection.as b/Degrafa/com/degrafa/core/collections/GeometryCollection.as new file mode 100644 index 0000000..4596d83 --- /dev/null +++ b/Degrafa/com/degrafa/core/collections/GeometryCollection.as @@ -0,0 +1,134 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core.collections{ + + import com.degrafa.IGeometry; + + + /** + * The GeometryCollection stores a collection of IGeometry objects + **/ + public class GeometryCollection extends DegrafaCollection{ + + /** + * Constructor. + * + *

The geometry collection constructor accepts 2 optional arguments + * that specify the geometry objects to be added and a event operation flag.

+ * + * @param array An array of IGeometry objects. + * @param suppressEvents A boolean value indicating if events should not be + * dispatched for this collection. + */ + public function GeometryCollection(array:Array=null,suppressEvents:Boolean=false){ + super(IGeometry,array,suppressEvents); + + } + + /** + * Adds a IGeometry item to the collection. + * + * @param value The IGeometry object to be added. + * @return The IGeometry object that was added. + **/ + public function addItem(value:IGeometry):IGeometry{ + return super._addItem(value); + } + + /** + * Removes an IGeometry item from the collection. + * + * @param value The IGeometry object to be removed. + * @return The IGeometry object that was removed. + **/ + public function removeItem(value:IGeometry):IGeometry{ + return super._removeItem(value); + } + + /** + * Retrieve a IGeometry item from the collection based on the index value + * requested. + * + * @param index The collection index of the IGeometry object to retrieve. + * @return The IGeometry object that was requested if it exists. + **/ + public function getItemAt(index:Number):IGeometry{ + return super._getItemAt(index); + } + + /** + * Retrieve a IGeometry item from the collection based on the object value. + * + * @param value The IGeometry object for which the index is to be retrieved. + * @return The IGeometry index value that was requested if it exists. + **/ + public function getItemIndex(value:IGeometry):int{ + return super._getItemIndex(value); + } + + /** + * Adds a IGeometry item to this collection at the specified index. + * + * @param value The IGeometry object that is to be added. + * @param index The position in the collection at which to add the IGeometry object. + * + * @return The IGeometry object that was added. + **/ + public function addItemAt(value:IGeometry,index:Number):IGeometry{ + return super._addItemAt(value,index); + } + + /** + * Removes a IGeometry object from this collection at the specified index. + * + * @param index The index of the IGeometry object to remove. + * @return The IGeometry object that was removed. + **/ + public function removeItemAt(index:Number):IGeometry{ + return super._removeItemAt(index); + } + + /** + * Change the index of the IGeometry object within this collection. + * + * @param value The IGeometry object that is to be repositioned. + * @param newIndex The position at which to place the IGeometry object within the collection. + * @return True if the operation is successful False if unsuccessful. + **/ + public function setItemIndex(value:IGeometry,newIndex:Number):Boolean{ + return super._setItemIndex(value,newIndex); + } + + /** + * Adds a series of IGeometry objects to this collection. + * + * @param value The collection to be added to this IGeometry collection. + * @return The resulting GeometryCollection after the objects are added. + **/ + public function addItems(value:GeometryCollection):GeometryCollection{ + //todo + super.concat(value.items) + return this; + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/collections/GradientStopsCollection.as b/Degrafa/com/degrafa/core/collections/GradientStopsCollection.as new file mode 100644 index 0000000..b84cced --- /dev/null +++ b/Degrafa/com/degrafa/core/collections/GradientStopsCollection.as @@ -0,0 +1,132 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core.collections{ + import com.degrafa.paint.GradientStop; + + + /** + * The GradientStopsCollection stores a collection of GradientStop objects + **/ + public class GradientStopsCollection extends DegrafaCollection{ + + /** + * Constructor. + * + *

The gradient stops collection constructor accepts 2 optional arguments + * that specify the gradient stops to be added and a event operation flag.

+ * + * @param array An array of GradientStop objects. + * @param suppressEvents A boolean value indicating if events should not be + * dispatched for this collection. + */ + public function GradientStopsCollection(array:Array=null,suppressEvents:Boolean=false){ + super(GradientStop,array,suppressEvents); + + } + + /** + * Adds a GradientStop item to the collection. + * + * @param value The GradientStop object to be added. + * @return The GradientStop object that was added. + **/ + public function addItem(value:GradientStop):GradientStop{ + return super._addItem(value); + } + + /** + * Removes an GradientStop item from the collection. + * + * @param value The GradientStop object to be removed. + * @return The GradientStop object that was removed. + **/ + public function removeItem(value:GradientStop):GradientStop{ + return super._removeItem(value); + } + + /** + * Retrieve a GradientStop item from the collection based on the index value + * requested. + * + * @param index The collection index of the GradientStop object to retrieve. + * @return The GradientStop object that was requested if it exists. + **/ + public function getItemAt(index:Number):GradientStop{ + return super._getItemAt(index); + } + + /** + * Retrieve a GradientStop item from the collection based on the object value. + * + * @param value The GradientStop object for which the index is to be retrieved. + * @return The GradientStop index value that was requested if it exists. + **/ + public function getItemIndex(value:GradientStop):int{ + return super._getItemIndex(value); + } + + /** + * Adds a GradientStop item to this collection at the specified index. + * + * @param value The GradientStop object that is to be added. + * @param index The position in the collection at which to add the GradientStop object. + * + * @return The GradientStop object that was added. + **/ + public function addItemAt(value:GradientStop,index:Number):GradientStop{ + return super._addItemAt(value,index); + } + + /** + * Removes a GradientStop object from this collection at the specified index. + * + * @param index The index of the GradientStop object to remove. + * @return The GradientStop object that was removed. + **/ + public function removeItemAt(index:Number):GradientStop{ + return super._removeItemAt(index); + } + + /** + * Change the index of the GradientStop object within this collection. + * + * @param value The GradientStop object that is to be repositioned. + * @param newIndex The position at which to place the GradientStop object within the collection. + * @return True if the operation is successful False if unsuccessful. + **/ + public function setItemIndex(value:GradientStop,newIndex:Number):Boolean{ + return super._setItemIndex(value,newIndex); + } + + /** + * Adds a series of GradientStop objects to this collection. + * + * @param value The collection to be added to this GradientStop collection. + * @return The resulting GradientStopsCollection after the objects are added. + **/ + public function addItems(value:GradientStopsCollection):GradientStopsCollection{ + //todo + super.concat(value.items) + return this; + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/collections/GraphicPointCollection.as b/Degrafa/com/degrafa/core/collections/GraphicPointCollection.as new file mode 100644 index 0000000..e4f220a --- /dev/null +++ b/Degrafa/com/degrafa/core/collections/GraphicPointCollection.as @@ -0,0 +1,134 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core.collections{ + import com.degrafa.IGraphicPoint; + + + + /** + * The GraphicPointCollection stores a collection of IGraphicPoint objects + **/ + public class GraphicPointCollection extends DegrafaCollection{ + /** + * Constructor. + * + *

The graphic point collection constructor accepts 2 optional arguments + * that specify the graphic points to be added and a event operation flag.

+ * + * @param array An array of IGraphicPoint objects. + * @param suppressEvents A boolean value indicating if events should not be + * dispatched for this collection. + */ + public function GraphicPointCollection(array:Array=null,suppressEvents:Boolean=false){ + super(IGraphicPoint,array,suppressEvents); + + } + + /** + * Adds a IGraphicPoint item to the collection. + * + * @param value The IGraphicPoint object to be added. + * @return The IGraphicPoint object that was added. + **/ + public function addItem(value:IGraphicPoint):IGraphicPoint{ + return super._addItem(value); + } + + /** + * Removes an IGraphicPoint item from the collection. + * + * @param value The IGraphicPoint object to be removed. + * @return The IGraphicPoint object that was removed. + **/ + public function removeItem(value:IGraphicPoint):IGraphicPoint{ + return super._removeItem(value); + } + + /** + * Retrieve a IGraphicPoint item from the collection based on the index value + * requested. + * + * @param index The collection index of the IGraphicPoint object to retrieve. + * @return The IGraphicPoint object that was requested if it exists. + **/ + public function getItemAt(index:Number):IGraphicPoint{ + return super._getItemAt(index); + } + + /** + * Retrieve a IGraphicPoint item from the collection based on the object value. + * + * @param value The IGraphicPoint object for which the index is to be retrieved. + * @return The IGraphicPoint index value that was requested if it exists. + **/ + public function getItemIndex(value:IGraphicPoint):int{ + return super._getItemIndex(value); + } + + /** + * Adds a IGraphicPoint item to this collection at the specified index. + * + * @param value The IGraphicPoint object that is to be added. + * @param index The position in the collection at which to add the IGraphicPoint object. + * + * @return The IGraphicPoint object that was added. + **/ + public function addItemAt(value:IGraphicPoint,index:Number):IGraphicPoint{ + return super._addItemAt(value,index); + } + + /** + * Removes a IGraphicPoint object from this collection at the specified index. + * + * @param index The index of the IGraphicPoint object to remove. + * @return The IGraphicPoint object that was removed. + **/ + public function removeItemAt(index:Number):IGraphicPoint{ + return super._removeItemAt(index); + } + + /** + * Change the index of the IGraphicPoint object within this collection. + * + * @param value The IGraphicPoint object that is to be repositioned. + * @param newIndex The position at which to place the IGraphicPoint object within the collection. + * @return True if the operation is successful False if unsuccessful. + **/ + public function setItemIndex(value:IGraphicPoint,newIndex:Number):Boolean{ + return super._setItemIndex(value,newIndex); + } + + /** + * Adds a series of IGraphicPoint objects to this collection. + * + * @param value The collection to be added to this IGraphicPoint collection. + * @return The resulting GraphicPointCollection after the objects are added. + **/ + public function addItems(value:GraphicPointCollection):GraphicPointCollection{ + //todo + super.concat(value.items) + return this; + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/collections/GraphicPointEXCollection.as b/Degrafa/com/degrafa/core/collections/GraphicPointEXCollection.as new file mode 100644 index 0000000..ba9908e --- /dev/null +++ b/Degrafa/com/degrafa/core/collections/GraphicPointEXCollection.as @@ -0,0 +1,134 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core.collections{ + import com.degrafa.GraphicPointEX; + + + /** + * The GraphicPointEXCollection stores a collection of GraphicPointEX objects + **/ + public class GraphicPointEXCollection extends DegrafaCollection{ + /** + * Constructor. + * + *

The extended graphic point collection constructor accepts 2 optional arguments + * that specify the graphic points to be added and a event operation flag.

+ * + * @param array An array of GraphicPointEX objects. + * @param suppressEvents A boolean value indicating if events should not be + * dispatched for this collection. + */ + public function GraphicPointEXCollection(array:Array=null,suppressEvents:Boolean=false){ + super(GraphicPointEX,array,suppressEvents); + + + } + + /** + * Adds a GraphicPointEX item to the collection. + * + * @param value The GraphicPointEX object to be added. + * @return The GraphicPointEX object that was added. + **/ + public function addItem(value:GraphicPointEX):GraphicPointEX{ + return super._addItem(value); + } + + /** + * Removes an GraphicPointEX item from the collection. + * + * @param value The GraphicPointEX object to be removed. + * @return The GraphicPointEX object that was removed. + **/ + public function removeItem(value:GraphicPointEX):GraphicPointEX{ + return super._removeItem(value); + } + + /** + * Retrieve a GraphicPointEX item from the collection based on the index value + * requested. + * + * @param index The collection index of the GraphicPointEX object to retrieve. + * @return The GraphicPointEX object that was requested if it exists. + **/ + public function getItemAt(index:Number):GraphicPointEX{ + return super._getItemAt(index); + } + + /** + * Retrieve a GraphicPointEX item from the collection based on the object value. + * + * @param value The GraphicPointEX object for which the index is to be retrieved. + * @return The GraphicPointEX index value that was requested if it exists. + **/ + public function getItemIndex(value:GraphicPointEX):int{ + return super._getItemIndex(value); + } + + /** + * Adds a GraphicPointEX item to this collection at the specified index. + * + * @param value The GraphicPointEX object that is to be added. + * @param index The position in the collection at which to add the GraphicPointEX object. + * + * @return The GraphicPointEX object that was added. + **/ + public function addItemAt(value:GraphicPointEX,index:Number):GraphicPointEX{ + return super._addItemAt(value,index); + } + + /** + * Removes a GraphicPointEX object from this collection at the specified index. + * + * @param index The index of the GraphicPointEX object to remove. + * @return The GraphicPointEX object that was removed. + **/ + public function removeItemAt(index:Number):GraphicPointEX{ + return super._removeItemAt(index); + } + + /** + * Change the index of the GraphicPointEX object within this collection. + * + * @param value The GraphicPointEX object that is to be repositioned. + * @param newIndex The position at which to place the GraphicPointEX object within the collection. + * @return True if the operation is successful False if unsuccessful. + **/ + public function setItemIndex(value:GraphicPointEX,newIndex:Number):Boolean{ + return super._setItemIndex(value,newIndex); + } + + /** + * Adds a series of GraphicPointEX objects to this collection. + * + * @param value The collection to be added to this GraphicPointEX collection. + * @return The resulting GraphicPointEXCollection after the objects are added. + **/ + public function addItems(value:GraphicPointEXCollection):GraphicPointEXCollection{ + //todo + super.concat(value.items) + return this; + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/collections/GraphicSkinCollection.as b/Degrafa/com/degrafa/core/collections/GraphicSkinCollection.as new file mode 100644 index 0000000..59e9c84 --- /dev/null +++ b/Degrafa/com/degrafa/core/collections/GraphicSkinCollection.as @@ -0,0 +1,138 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core.collections{ + + //these subclasses are to make api access easier and to ensure + //the the correct types are returned. + import com.degrafa.core.IGraphicSkin; + + + + /** + * The GraphicSkinCollection stores a collection of IGraphicSkin objects + **/ + public class GraphicSkinCollection extends DegrafaCollection{ + /** + * Constructor. + * + *

The graphic skin collection constructor accepts 2 optional arguments + * that specify the graphic skins to be added and a event operation flag.

+ * + * @param array An array of IGraphicSkin objects. + * @param suppressEvents A boolean value indicating if events should not be + * dispatched for this collection. + */ + public function GraphicSkinCollection(array:Array=null,suppressEvents:Boolean=false){ + super(IGraphicSkin,array,suppressEvents); + + } + + /** + * Adds a IGraphicSkin item to the collection. + * + * @param value The IGraphicSkin object to be added. + * @return The IGraphicSkin object that was added. + **/ + public function addItem(value:IGraphicSkin):IGraphicSkin{ + return super._addItem(value); + } + + /** + * Removes an IGraphicSkin item from the collection. + * + * @param value The IGraphicSkin object to be removed. + * @return The IGraphicSkin object that was removed. + **/ + public function removeItem(value:IGraphicSkin):IGraphicSkin{ + return super._removeItem(value); + } + + /** + * Retrieve a IGraphicSkin item from the collection based on the index value + * requested. + * + * @param index The collection index of the IGraphicSkin object to retrieve. + * @return The IGraphicSkin object that was requested if it exists. + **/ + public function getItemAt(index:Number):IGraphicSkin{ + return super._getItemAt(index); + } + + /** + * Retrieve a IGraphicSkin item from the collection based on the object value. + * + * @param value The IGraphicSkin object for which the index is to be retrieved. + * @return The IGraphicSkin index value that was requested if it exists. + **/ + public function getItemIndex(value:IGraphicSkin):int{ + return super._getItemIndex(value); + } + + /** + * Adds a IGraphicSkin item to this collection at the specified index. + * + * @param value The IGraphicSkin object that is to be added. + * @param index The position in the collection at which to add the IGraphicSkin object. + * + * @return The IGraphicSkin object that was added. + **/ + public function addItemAt(value:IGraphicSkin,index:Number):IGraphicSkin{ + return super._addItemAt(value,index); + } + /** + * Removes a IGraphicSkin object from this collection at the specified index. + * + * @param index The index of the IGraphicSkin object to remove. + * @return The IGraphicSkin object that was removed. + **/ + public function removeItemAt(index:Number):IGraphicSkin{ + return super._removeItemAt(index); + } + /** + * Change the index of the IGraphicSkin object within this collection. + * + * @param value The IGraphicSkin object that is to be repositioned. + * @param newIndex The position at which to place the IGraphicSkin object within the collection. + * @return True if the operation is successful False if unsuccessful. + **/ + public function setItemIndex(value:IGraphicSkin,newIndex:Number):Boolean{ + return super._setItemIndex(value,newIndex); + } + + /** + * Adds a series of IGraphicSkin objects to this collection. + * + * @param value The collection to be added to this IGraphicSkin collection. + * @return The resulting GraphicSkinCollection after the objects are added. + **/ + public function addItems(value:GraphicSkinCollection):GraphicSkinCollection{ + //todo + super.concat(value.items) + return this; + } + + + + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/collections/GraphicsCollection.as b/Degrafa/com/degrafa/core/collections/GraphicsCollection.as new file mode 100644 index 0000000..a7dc2b4 --- /dev/null +++ b/Degrafa/com/degrafa/core/collections/GraphicsCollection.as @@ -0,0 +1,132 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core.collections{ + import com.degrafa.IGraphic; + + + /** + * The GraphicsCollection stores a collection of IGraphic objects + **/ + public class GraphicsCollection extends DegrafaCollection{ + + /** + * Constructor. + * + *

The graphics collection constructor accepts 2 optional arguments + * that specify the graphics to be added and a event operation flag.

+ * + * @param array An array of IGraphic objects. + * @param suppressEvents A boolean value indicating if events should not be + * dispatched for this collection. + */ + public function GraphicsCollection(array:Array=null,suppressEvents:Boolean=false){ + super(IGraphic,array,suppressEvents); + + } + + /** + * Adds a IGraphic item to the collection. + * + * @param value The IGraphic object to be added. + * @return The IGraphic object that was added. + **/ + public function addItem(value:IGraphic):IGraphic{ + return super._addItem(value); + } + + /** + * Removes an IGraphic item from the collection. + * + * @param value The IGraphic object to be removed. + * @return The IGraphic object that was removed. + **/ + public function removeItem(value:IGraphic):IGraphic{ + return super._removeItem(value); + } + + /** + * Retrieve a IGraphic item from the collection based on the index value + * requested. + * + * @param index The collection index of the IGraphic object to retrieve. + * @return The IGraphic object that was requested if it exists. + **/ + public function getItemAt(index:Number):IGraphic{ + return super._getItemAt(index); + } + + /** + * Retrieve a IGraphic item from the collection based on the object value. + * + * @param value The IGraphic object for which the index is to be retrieved. + * @return The IGraphic index value that was requested if it exists. + **/ + public function getItemIndex(value:IGraphic):int{ + return super._getItemIndex(value); + } + + /** + * Adds a IGraphic item to this collection at the specified index. + * + * @param value The IGraphic object that is to be added. + * @param index The position in the collection at which to add the IGraphic object. + * + * @return The IGraphic object that was added. + **/ + public function addItemAt(value:IGraphic,index:Number):IGraphic{ + return super._addItemAt(value,index); + } + + /** + * Removes a IGraphic object from this collection at the specified index. + * + * @param index The index of the IGraphic object to remove. + * @return The IGraphic object that was removed. + **/ + public function removeItemAt(index:Number):IGraphic{ + return super._removeItemAt(index); + } + + /** + * Change the index of the IGraphic object within this collection. + * + * @param value The IGraphic object that is to be repositioned. + * @param newIndex The position at which to place the IGraphic object within the collection. + * @return True if the operation is successful False if unsuccessful. + **/ + public function setItemIndex(value:IGraphic,newIndex:Number):Boolean{ + return super._setItemIndex(value,newIndex); + } + + /** + * Adds a series of IGraphic objects to this collection. + * + * @param value The collection to be added to this IGraphic collection. + * @return The resulting GraphicsCollection after the objects are added. + **/ + public function addItems(value:GraphicsCollection):GraphicsCollection{ + //todo + super.concat(value.items) + return this; + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/collections/IDegrafaCollection.as b/Degrafa/com/degrafa/core/collections/IDegrafaCollection.as new file mode 100644 index 0000000..0ff9517 --- /dev/null +++ b/Degrafa/com/degrafa/core/collections/IDegrafaCollection.as @@ -0,0 +1,34 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core.collections{ + + import com.degrafa.core.IDegrafaObject; + /** + * Base interface for all Degrafa collection classes. + **/ + public interface IDegrafaCollection extends IDegrafaObject{ + + function get items():Array + function set items(value:Array):void + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/collections/RepeaterModifierCollection.as b/Degrafa/com/degrafa/core/collections/RepeaterModifierCollection.as new file mode 100644 index 0000000..94e5ace --- /dev/null +++ b/Degrafa/com/degrafa/core/collections/RepeaterModifierCollection.as @@ -0,0 +1,133 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core.collections{ + import com.degrafa.repeaters.IRepeaterModifier; + + + /** + * The RepeaterModifierCollection stores a collection of RepeaterModifier objects + **/ + public class RepeaterModifierCollection extends DegrafaCollection{ + + /** + * Constructor. + * + *

The repeater modifier collection constructor accepts 2 optional arguments + * that specify the repeater modifiers to be added and a event operation flag.

+ * + * @param array An array of RepeaterModifier objects. + * @param suppressEvents A boolean value indicating if events should not be + * dispatched for this collection. + */ + public function RepeaterModifierCollection(array:Array=null,suppressEvents:Boolean=false){ + super(IRepeaterModifier,array,suppressEvents); + + + } + + /** + * Adds a RepeaterModifier item to the collection. + * + * @param value The RepeaterModifier object to be added. + * @return The RepeaterModifier object that was added. + **/ + public function addItem(value:IRepeaterModifier):IRepeaterModifier{ + return super._addItem(value); + } + + /** + * Removes an RepeaterModifier item from the collection. + * + * @param value The RepeaterModifier object to be removed. + * @return The RepeaterModifier object that was removed. + **/ + public function removeItem(value:IRepeaterModifier):IRepeaterModifier{ + return super._removeItem(value); + } + + /** + * Retrieve a RepeaterModifier item from the collection based on the index value + * requested. + * + * @param index The collection index of the RepeaterModifier object to retrieve. + * @return The RepeaterModifier object that was requested if it exists. + **/ + public function getItemAt(index:Number):IRepeaterModifier{ + return super._getItemAt(index); + } + + /** + * Retrieve a RepeaterModifier item from the collection based on the object value. + * + * @param value The RepeaterModifier object for which the index is to be retrieved. + * @return The RepeaterModifier index value that was requested if it exists. + **/ + public function getItemIndex(value:IRepeaterModifier):int{ + return super._getItemIndex(value); + } + + /** + * Adds a RepeaterModifier item to this collection at the specified index. + * + * @param value The RepeaterModifier object that is to be added. + * @param index The position in the collection at which to add the RepeaterModifier object. + * + * @return The RepeaterModifier object that was added. + **/ + public function addItemAt(value:IRepeaterModifier,index:Number):IRepeaterModifier{ + return super._addItemAt(value,index); + } + + /** + * Removes a RepeaterModifier object from this collection at the specified index. + * + * @param index The index of the RepeaterModifier object to remove. + * @return The RepeaterModifier object that was removed. + **/ + public function removeItemAt(index:Number):IRepeaterModifier{ + return super._removeItemAt(index); + } + + /** + * Change the index of the RepeaterModifier object within this collection. + * + * @param value The RepeaterModifier object that is to be repositioned. + * @param newIndex The position at which to place the RepeaterModifier object within the collection. + * @return True if the operation is successful False if unsuccessful. + **/ + public function setItemIndex(value:IRepeaterModifier,newIndex:Number):Boolean{ + return super._setItemIndex(value,newIndex); + } + + /** + * Adds a series of RepeaterModifier objects to this collection. + * + * @param value The collection to be added to this RepeaterModifier collection. + * @return The resulting RepeaterModifiersCollection after the objects are added. + **/ + public function addItems(value:RepeaterModifierCollection):RepeaterModifierCollection{ + //todo + super.concat(value.items) + return this; + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/collections/SegmentsCollection.as b/Degrafa/com/degrafa/core/collections/SegmentsCollection.as new file mode 100644 index 0000000..c83f6d7 --- /dev/null +++ b/Degrafa/com/degrafa/core/collections/SegmentsCollection.as @@ -0,0 +1,134 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core.collections{ + import com.degrafa.geometry.segment.ISegment; + + + /** + * The SegmentsCollection stores a collection of ISegment objects + **/ + public class SegmentsCollection extends DegrafaCollection{ + + /** + * Constructor. + * + *

The segments collection constructor accepts 2 optional arguments + * that specify the segments to be added and a event operation flag.

+ * + * @param array An array of ISegment objects. + * @param suppressEvents A boolean value indicating if events should be + * dispatched for this collection. + */ + public function SegmentsCollection(array:Array=null,suppressEvents:Boolean=false){ + super(ISegment,array,suppressEvents); + + } + + /** + * Adds a ISegment item to the collection. + * + * @param value The ISegment object to be added. + * @return The ISegment object that was added. + **/ + public function addItem(value:ISegment):ISegment{ + return super._addItem(value); + } + + /** + * Removes an ISegment item from the collection. + * + * @param value The ISegment object to be removed. + * @return The ISegment object that was removed. + **/ + public function removeItem(value:ISegment):ISegment{ + return super._removeItem(value); + } + + /** + * Retrieve a ISegment item from the collection based on the index value + * requested. + * + * @param index The collection index of the ISegment object to retrieve. + * @return The ISegment object that was requested if it exists. + **/ + public function getItemAt(index:Number):ISegment{ + return super._getItemAt(index); + } + + /** + * Retrieve a ISegment item from the collection based on the object value. + * + * @param value The ISegment object for which the index is to be retrieved. + * @return The ISegment index value that was requested if it exists. + **/ + public function getItemIndex(value:ISegment):int{ + return super._getItemIndex(value); + } + + /** + * Adds a ISegment item to this collection at the specified index. + * + * @param value The ISegment object that is to be added. + * @param index The position in the collection at which to add the ISegment object. + * + * @return The ISegment object that was added. + **/ + public function addItemAt(value:ISegment,index:Number):ISegment{ + return super._addItemAt(value,index); + } + + /** + * Removes a ISegment object from this collection at the specified index. + * + * @param index The index of the ISegment object to remove. + * @return The ISegment object that was removed. + **/ + public function removeItemAt(index:Number):ISegment{ + return super._removeItemAt(index); + } + + /** + * Change the index of the ISegment object within this collection. + * + * @param value The ISegment object that is to be repositioned. + * @param newIndex The position at which to place the ISegment object within the collection. + * @return True if the operation is successful False if unsuccessful. + **/ + public function setItemIndex(value:ISegment,newIndex:Number):Boolean{ + return super._setItemIndex(value,newIndex); + } + + /** + * Adds a series of ISegment objects to this collection. + * + * @param value The collection to be added to this ISegment collection. + * @return The resulting SegmentsCollection after the objects are added. + **/ + public function addItems(value:SegmentsCollection):SegmentsCollection{ + //todo + super.concat(value.items) + return this; + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/collections/StrokeCollection.as b/Degrafa/com/degrafa/core/collections/StrokeCollection.as new file mode 100644 index 0000000..f45f6ad --- /dev/null +++ b/Degrafa/com/degrafa/core/collections/StrokeCollection.as @@ -0,0 +1,132 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core.collections{ + import com.degrafa.core.IGraphicsStroke; + + + /** + * The StrokeCollection stores a collection of IGraphicsStroke objects + **/ + public class StrokeCollection extends DegrafaCollection{ + + /** + * Constructor. + * + *

The stroke collection constructor accepts 2 optional arguments + * that specify the strokes to be added and a event operation flag.

+ * + * @param array An array of IGraphicsStroke objects. + * @param suppressEvents A boolean value indicating if events should be + * dispatched for this collection. + */ + public function StrokeCollection(array:Array=null,suppressEvents:Boolean=false){ + super(IGraphicsStroke,array,suppressEvents); + + } + + /** + * Adds a IGraphicsStroke item to the collection. + * + * @param value The IGraphicsStroke object to be added. + * @return The IGraphicsStroke object that was added. + **/ + public function addItem(value:IGraphicsStroke):IGraphicsStroke{ + return super._addItem(value); + } + + /** + * Removes an IGraphicsStroke item from the collection. + * + * @param value The IGraphicsStroke object to be removed. + * @return The IGraphicsStroke object that was removed. + **/ + public function removeItem(value:IGraphicsStroke):IGraphicsStroke{ + return super._removeItem(value); + } + + /** + * Retrieve a IGraphicsStroke item from the collection based on the index value + * requested. + * + * @param index The collection index of the IGraphicsStroke object to retrieve. + * @return The IGraphicsStroke object that was requested if it exists. + **/ + public function getItemAt(index:Number):IGraphicsStroke{ + return super._getItemAt(index); + } + + /** + * Retrieve a IGraphicsStroke item from the collection based on the object value. + * + * @param value The IGraphicsStroke object for which the index is to be retrieved. + * @return The IGraphicsStroke index value that was requested if it exists. + **/ + public function getItemIndex(value:IGraphicsStroke):int{ + return super._getItemIndex(value); + } + + /** + * Adds a IGraphicsStroke item to this collection at the specified index. + * + * @param value The IGraphicsStroke object that is to be added. + * @param index The position in the collection at which to add the IGraphicsStroke object. + * + * @return The IGraphicsStroke object that was added. + **/ + public function addItemAt(value:IGraphicsStroke,index:Number):IGraphicsStroke{ + return super._addItemAt(value,index); + } + + /** + * Removes a IGraphicsStroke object from this collection at the specified index. + * + * @param index The index of the IGraphicsStroke object to remove. + * @return The IGraphicsStroke object that was removed. + **/ + public function removeItemAt(index:Number):IGraphicsStroke{ + return super._removeItemAt(index); + } + + /** + * Change the index of the IGraphicsStroke object within this collection. + * + * @param value The IGraphicsStroke object that is to be repositioned. + * @param newIndex The position at which to place the IGraphicsStroke object within the collection. + * @return True if the operation is successful False if unsuccessful. + **/ + public function setItemIndex(value:IGraphicsStroke,newIndex:Number):Boolean{ + return super._setItemIndex(value,newIndex); + } + + /** + * Adds a series of IGraphicsStroke objects to this collection. + * + * @param value The collection to be added to this IGraphicsStroke collection. + * @return The resulting StrokeCollection after the objects are added. + **/ + public function addItems(value:StrokeCollection):StrokeCollection{ + //todo + super.concat(value.items) + return this; + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/collections/TransformCollection.as b/Degrafa/com/degrafa/core/collections/TransformCollection.as new file mode 100644 index 0000000..5b1a178 --- /dev/null +++ b/Degrafa/com/degrafa/core/collections/TransformCollection.as @@ -0,0 +1,136 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core.collections{ + import com.degrafa.transform.ITransform; + + + /** + * The TransformCollection stores a collection of ITransform objects + **/ + public class TransformCollection extends DegrafaCollection{ + + /** + * Constructor. + * + *

The transform collection constructor accepts 2 optional arguments + * that specify the transforms to be added and a event operation flag.

+ * + * @param array An array of ITransform objects. + * @param suppressEvents A boolean value indicating if events should be + * dispatched for this collection. + */ + public function TransformCollection(array:Array=null,suppressEvents:Boolean=false){ + super(ITransform,array,suppressEvents); + + } + + /** + * Adds a ITransform item to the collection. + * + * @param value The ITransform object to be added. + * @return The ITransform object that was added. + **/ + public function addItem(value:ITransform):ITransform{ + return super._addItem(value); + } + + /** + * Removes an ITransform item from the collection. + * + * @param value The ITransform object to be removed. + * @return The ITransform object that was removed. + **/ + public function removeItem(value:ITransform):ITransform{ + return super._removeItem(value); + } + + /** + * Retrieve a ITransform item from the collection based on the index value + * requested. + * + * @param index The collection index of the ITransform object to retrieve. + * @return The ITransform object that was requested if it exists. + **/ + public function getItemAt(index:Number):ITransform{ + return super._getItemAt(index); + } + + /** + * Adds a ITransform item to this collection at the specified index. + * + * @param value The ITransform object that is to be added. + * @param index The position in the collection at which to add the ITransform object. + * + * @return The ITransform object that was added. + **/ + public function getItemIndex(value:ITransform):int{ + return super._getItemIndex(value); + } + + /** + * Adds a ITransform item to this collection at the specified index. + * + * @param value The ITransform object that is to be added. + * @param index The position in the collection at which to add the ITransform object. + * + * @return The ITransform object that was added. + **/ + public function addItemAt(value:ITransform,index:Number):ITransform{ + return super._addItemAt(value,index); + } + + /** + * Removes a ITransform object from this collection at the specified index. + * + * @param index The index of the ITransform object to remove. + * @return The ITransform object that was removed. + **/ + public function removeItemAt(index:Number):ITransform{ + return super._removeItemAt(index); + } + + /** + * Change the index of the ITransform object within this collection. + * + * @param value The ITransform object that is to be repositioned. + * @param newIndex The position at which to place the ITransform object within the collection. + * @return True if the operation is successful False if unsuccessful. + **/ + public function setItemIndex(value:ITransform,newIndex:Number):Boolean{ + return super._setItemIndex(value,newIndex); + } + + /** + * Adds a series of ITransform objects to this collection. + * + * @param value The collection to be added to this ITransform collection. + * @return The resulting TransformCollection after the objects are added. + **/ + public function addItems(value:TransformCollection):TransformCollection{ + //todo + super.concat(value.items) + return this; + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/degrafa_internal.as b/Degrafa/com/degrafa/core/degrafa_internal.as new file mode 100644 index 0000000..b86563a --- /dev/null +++ b/Degrafa/com/degrafa/core/degrafa_internal.as @@ -0,0 +1,11 @@ +package com.degrafa.core +{ + +/** + * This namespace is used for undocumented APIs -- usually implementation + * details -- which can't be private because they need to visible + * to other classes. + */ +public namespace degrafa_internal = "http://www.degrafa.com/2009/internal"; + +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/manipulators/DegrafaSkinManipulator.as b/Degrafa/com/degrafa/core/manipulators/DegrafaSkinManipulator.as new file mode 100644 index 0000000..bd05088 --- /dev/null +++ b/Degrafa/com/degrafa/core/manipulators/DegrafaSkinManipulator.as @@ -0,0 +1,194 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.core.manipulators{ + + import com.degrafa.core.DegrafaObject; + import com.degrafa.core.IGraphicSkin; + import com.degrafa.core.collections.GraphicSkinCollection; + + import flash.display.DisplayObject; + import flash.display.DisplayObjectContainer; + import flash.events.Event; + + import mx.events.FlexEvent; + import mx.events.PropertyChangeEvent; + + + [Bindable] + /** + * The DegrafaSkinManipulator allows one to tie in and manipulate the specified + * IGraphicSkin for an object. Degrafa Geometry, fills and strokes can then be + * manipulated and bound to like you would do with any other Degrafa graphic + * or geometry object. ** Experimental. + **/ + public class DegrafaSkinManipulator extends DegrafaObject{ + + private var _target:DisplayObjectContainer; + /** + * Target UI Object for this manipulator + **/ + public function get target():DisplayObjectContainer + { + return _target; + } + public function set target(value:DisplayObjectContainer):void{ + if (!value){return;} + _target = value; + + _target.addEventListener("added",onChildAdded); + + //add the creation complete handler + _target.addEventListener(FlexEvent.CREATION_COMPLETE,onTargetCreationComplete); + + } + + /** + * Return the requested skin if available by name. + **/ + public function getSkinByName(value:String):IGraphicSkin{ + for each (var item:IGraphicSkin in skinsCollection.items){ + if (item.name == value){ + return item; + } + } + return null; + } + + /** + * Processes skins as they are instantiated. + **/ + private function onChildAdded(event:Event):void{ + //add item to the skins collection if not already present + if(event.target is IGraphicSkin){ + var exists:Boolean; + for each (var item:IGraphicSkin in skinsCollection.items){ + if (item == IGraphicSkin(event.target)){ + exists = true; + } + } + if(!exists){ + skinsCollection.addItem(IGraphicSkin(event.target)); + } + } + } + + /** + * Sets up this manipulator. + **/ + private function onTargetCreationComplete(event:FlexEvent):void{ + + var i:int; + + //loop through the children and get the first IGraphicSkin that matches + //the targetSkinClass. + if(event.currentTarget is DisplayObject){ + if(event.currentTarget.hasOwnProperty("rawChildren")){ + if(targetSkinClass){ + for (i=0;i>16; + var greenBlue:Number = hex-(red<<16); + var green:Number = greenBlue>>8; + var blue:Number = greenBlue-(green<<8); + return({red:red, green:green, blue:blue}) + } + + /** + * Converts a decimal color to a hex value. + **/ + public static function decColorToHex(color:uint,prefix:String="0x"):String{ + + var hexVal:String = ("00000" + color.toString(16).toUpperCase()).substr( -6); + return prefix + hexVal; + + } + + /** + * Take a short color notation and convert it to a full color. + * Repeats each value once so that #FB0 expands to #FFBB00 + **/ + public static function parseColorNotation(color:String):uint + { + //dev note:extra check here for #, although it's already removed when requested from inside resolveColorFromString method above + color = color.replace("#", ""); + color = '0x'+color.charAt(0) + color.charAt(0) + color.charAt(1) + color.charAt(1) + color.charAt(2) + color.charAt(2); + return uint(color); + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/core/utils/StyleUtil.as b/Degrafa/com/degrafa/core/utils/StyleUtil.as new file mode 100644 index 0000000..4e5cbde --- /dev/null +++ b/Degrafa/com/degrafa/core/utils/StyleUtil.as @@ -0,0 +1,434 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +package com.degrafa.core.utils{ + + import com.degrafa.core.Measure; + import com.degrafa.paint.BitmapFill; + import com.degrafa.paint.ComplexFill; + import com.degrafa.paint.GradientStop; + import com.degrafa.paint.LinearGradientFill; + import com.degrafa.paint.SolidFill; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.DisplayObject; + import flash.filters.DropShadowFilter; + import flash.geom.Point; + import flash.utils.Dictionary; + + import mx.graphics.IFill; + import mx.graphics.SolidColor; + + /** + * A helper utility class for style processing. + */ + public class StyleUtil{ + + public static var LEGACY:String = "BETA"; + private static var functions:Dictionary; + + + //******************************************** + // Public Methods + //******************************************** + + /** + * Return an IFill object based on the CSS input given. + * @param value May be a uint, String, asset, or Array of uints, Strings, or assets. + */ + public static function resolveFill(value:Object, none:IFill = null):IFill { + if(value is uint) { + return new SolidFill(value as uint, 1); + } else if(value is Class || value is BitmapData || value is Bitmap || value is DisplayObject) { // causes duplicate type checking + return new BitmapFill(value); + } else if(value is String) { + return resolveFillFromString(value as String); + } else if(value is Array) { + return resolveFillFromArray(value as Array); + } else if(value is IFill) { + return value as IFill; + } else { + return none; + } + } + + public static function resolveRepeat(value:Object, none:Array = null):Array { + if(value is String) { + return [resolveRepeatFromString(value as String)] + } else if(value is Array) { + return resolveRepeatFromArray(value as Array); + } + return none; + } + + public static function resolvePosition(value:Object, none:Array = null):Array { + if(value is String) { + return [resolvePositionFromString(value as String)]; + } else if(value is Array) { + return resolvePositionFromArray(value as Array); + } else { + return none; + } + } + + /** + * Returns an array of DropShadowFilter objects based on the CSS input given. + * @parameter value may be a Number, String or Array of Numbers or Strings + */ + public static function resolveShadow(value:Object, none:Array = null ):Array { + if(value is Array) { + return resolveShadowFromArray(value as Array); + } else if(value is String || value is Number || value is int || value is uint) { + return [resolveShadowFromString(value.toString())]; + } else { + return none; + } + return result; + } + + public static function resolveRadius(value:Object, none:Point = null):Object { + if(value is Number) { + return {x:new Measure(Number(value)), y:new Measure(Number(value))}; + } else { + // should be none + return {x:new Measure(0), y:new Measure(0)}; + } + } + + public static function resolveMeasure(value:Object, none:Measure = null):Measure { + if(value is Number || value is int || value is uint) { + return new Measure(value as Number); + } else if(value is String) { + return resolveMeasureFromString(value as String); + } else { + return none; + } + } + + public static function registerFunction(name:String, f:Function):void { + if(functions == null) { + functions = new Dictionary(); + } + functions[name] = f; + } + + + //******************************* + // Parsing Functions + //******************************* + + private static function resolveFillFromArray(value:Array):IFill { + if(LEGACY == "ALPHA") { return resolveFillFromArrayOld(value); } + var complex:ComplexFill = new ComplexFill(); + var fills:Array = complex.fills = new Array(); + var i:int = value.length; + while(i-->0) { + var object:Object = value[i]; + var fill:IFill = resolveFill(object); + fills.push(fill); + } + return complex; + } + + + + // this whole thing needs refactoring + private static function resolveFillFromString(value:String):IFill { + if(value == null) { return null; } + if(value.toLowerCase() == "none") { return null; } + if(value.indexOf(" ") > 0) { // gradient definitions must have at least one space + return resolveGradientFromString(value); + } else { + return new SolidFill(ColorUtil.resolveColor(value)); + } + } + + private static function resolveGradientFromString(value:String):IFill { + var fill:LinearGradientFill = new LinearGradientFill(); + fill.gradientStops = new Array(); + var split:Array = splitString(value); + var length:int = split.length; + if(length > 0) { + var i:int = 0; + var angle:Measure = resolveMeasure(split[0]); + if(angle != null && angle.unit == Measure.DEGREES) { + fill.angle = angle.value; + i = 1; + } + while(i < length) { + var entry:GradientStop = new GradientStop(); + + var measure:Measure = resolveMeasureFromString(split[i]); + if(measure != null) { + entry.ratio = measure.value; + entry.ratioUnit = measure.unit; + i++ + } + + var alpha:Measure = resolveMeasureFromString(split[i]); + if(alpha != null) { + entry.alpha = Number(alpha)/100; + i++; + } + + // this should be optimized better + var color:IFill = new SolidColor(ColorUtil.resolveColor(split[i])); + if(color != null && color is SolidColor) { + entry.color = (color as SolidColor).color; + i++; + } + fill.gradientStops.push(entry); + //fill.entries = [new MeasuredGradientEntry(0xFF0000, new Measure(0, "px")), new MeasuredGradientEntry(0x0000FF, new Measure(100, "px"))]; + } + } + return fill; + } + + + private static function resolveRepeatFromString(value:String):Object { + switch(value) { + case "repeat": + return {x:"repeat", y:"repeat"}; + break; + case "repeat-x": + return {x:"repeat", y:"none"}; + break; + case "repeat-y": + return {x:"none", y:"repeat"}; + break; + case "no-repeat": + return {x:"none", y:"none"}; + break; + case "space": + return {x:"space", y:"space"}; + break; + case "stretch": + return {x:"stretch", y:"stretch"}; + break; + default: + return {x:"repeat", y:"repeat"}; + break; + } + } + + private static function resolveRepeatFromArray(value:Array):Array { + if(LEGACY == "ALPHA") { return resolveRepeatFromArrayOld(value); } + var result:Array = new Array(); + var i:int = value.length; + while(i-->0) { + var item:Object = value[i]; + if(item is String) { + result.push(resolveRepeatFromString(item as String)); + } else { + result.push(item); + } + } + return result; + } + + private static function resolvePositionFromString(value:String):Object { + var properties:Array = expandProperty(value, ["top", -1]); + return {x:resolveMeasureFromString(properties[1] as String), y:resolveMeasureFromString(properties[0] as String)}; + } + + private static function resolvePositionFromArray(value:Array):Array { + if(LEGACY == "ALPHA") { return resolvePositionFromArrayOld(value); } + var result:Array = new Array(); + var i:int = value.length; + while(i-->0) { + var item:Object = value[i]; + if(item is String) { + result.push(resolvePositionFromString(item as String)); + } else { + result.push(item); + } + } + return result; + } + + private static function resolveMeasureFromString(value:String):Measure { + if(value == null) { return null; } + switch(value) { + case "bottom": + case "right": + return new Measure(100, Measure.PERCENT); + break; + case "top": + case "left": + return new Measure(0, Measure.PIXELS); + break; + case "center": + return new Measure(50, Measure.PERCENT); + break; + } + value = value.replace(" ", ""); + var length:int = value.length; + for(var i:int = 0; i < length; i++) { + var ch:String = value.charAt(i); + + switch(ch) { + case "0": + case "1": + case "2": + case "3": + case "4": + case "5": + case "6": + case "7": + case "8": + case "9": + case "-": + break; + default: + var v:Number = Number(value.substring(0, i)); + var u:String = value.substring(i); + switch(u) { + case "px": + return new Measure(v, Measure.PIXELS); + break; + case "pt": + return new Measure(v, Measure.POINTS); + break; + case "%": + return new Measure(v, Measure.PERCENT); + break; + case "deg": + return new Measure(v, Measure.DEGREES); + break; + case "in": + return new Measure(v, Measure.INCHES); + break; + default: + return null; + break; + } + } + } + return new Measure(Number(value)); + } + + private static function resolveShadowFromArray(value:Array):Array { + var filters:Array = new Array(); + var i:int = value.length; + while(i-->0) { + filters.push( resolveShadow(value[i]) ); + } + return filters; + } + + private static function resolveShadowFromString(value:String):DropShadowFilter { + var result:DropShadowFilter = new DropShadowFilter(0, 0, 0, 1, 0, 0); + var array:Array = expandProperty(value, [0, -1, 0, 0] ); + var point:Point = new Point(resolveLength(array[0]), resolveLength(array[1])); + result.distance = Math.sqrt(Math.pow(point.x, 2) + Math.pow(point.y, 2)); + result.angle = Math.atan2(point.y, point.x)*(180/Math.PI); + result.blurX = result.blurY = resolveLength(array[2]); + result.color = (resolveFill(array[3]) as SolidColor).color; + return result; + } + + private static function resolveLength( object:Object ):Number { + return Number( object ); + } + + // legacy code + private static function resolveFillFromArrayOld(value:Array):IFill { + var complex:ComplexFill = new ComplexFill(); + var fills:Array = complex.fills = new Array(); + for each(var object:Object in value as Array) { + var fill:IFill = resolveFill(object); + fills.push(fill); + } + return complex; + } + + private static function resolveRepeatFromArrayOld(value:Array):Array { + var result:Array = new Array(); + for each(var item:Object in value) { + if(item is String) { + result.push(resolveRepeatFromString(item as String)); + } else { + result.push(item); + } + } + return result; + } + + private static function resolvePositionFromArrayOld(value:Array):Array { + var result:Array = new Array(); + for each(var item:Object in value) { + if(item is String) { + result.push(resolvePositionFromString(item as String)); + } else { + result.push(item); + } + } + return result; + } + + //******************************* + // generic utility functions + //******************************* + + /** + * Expands shorthand properties into an Array of values. + * This is used to evaluate shorthand CSS properties where ommited values may + * have a default value or be inherited from other values defined by the property. + * @argument property A shorthand property declaration (usually a String). + * @argument relationships An array of values used to determine the value of ommited values in the property. + *
    + *
  • A negative integer defines that the default value should be inherited from a value to the left.
  • + *
  • All other values define an explicit default.
  • + *
+ */ + public static function expandProperty(property:Object, relationships:Array):Array { + var split:Array = splitString(property.toString()); + var length:int = relationships.length; + for(var i:int = split.length; i < length; i++) { + var v:Object = relationships[i]; + if(v is int && v < 0) { + split.push(split[i+v]); + } else { + split.push(v); + } + } + return split; + } + + /** + * Converts space seperated values into an Array. + */ + public static function splitString( value:String ):Array { + var split:Array = value.split(" "); + for(var i:uint = 0; i < split.length; i++) { + if(split[i]==""){ + split.splice(i,1); + i--; + } + } + return split; + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/decorators/DecoratorBase.as b/Degrafa/com/degrafa/decorators/DecoratorBase.as new file mode 100644 index 0000000..232b79b --- /dev/null +++ b/Degrafa/com/degrafa/decorators/DecoratorBase.as @@ -0,0 +1,67 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.decorators{ + + import com.degrafa.core.DegrafaObject; + import com.degrafa.geometry.command.CommandStack; + + + /** + * DecoratorBase is intended to be extended for basic decoration creation. + * Decorations that use a delegate type manipulation extend from this base + * class. + **/ + public class DecoratorBase extends DegrafaObject implements IDecorator{ + public function DecoratorBase():void{ + super(); + } + + //overridden in sub classes + /** + * Called during render setup and provides the opportunity to add + * item delegates or perform other initialization tasks. + * Decorators should use this to reset any tracking variables back to their + * original state, as the same decorator may have been used previously + * and there is no 'end' call at the completion of the rendering phase. + * To be overridden in sub classes. + **/ + public function initialize(stack:CommandStack):void{ + + //walk the stack to add the delegates + + } + + //overridden in sub classes + /** + * Called at the end of the render phase for the current object. + * Provides opportunity to perform any post-decoration tasks, including any cleanup/reset activities before next request. + * To be overridden in sub classes. + **/ + /**/ + public function end(stack:CommandStack):void{ + + //perform any post-decoration tasks, including any reset activities before next request + + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/decorators/IDecorator.as b/Degrafa/com/degrafa/decorators/IDecorator.as new file mode 100644 index 0000000..a0e8b95 --- /dev/null +++ b/Degrafa/com/degrafa/decorators/IDecorator.as @@ -0,0 +1,35 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.decorators{ + + import com.degrafa.geometry.command.CommandStack; + + import flash.display.Graphics; + + /** + * IDecorator is the base interface for basic decorations. See DecoratorBase. + **/ + public interface IDecorator{ + function initialize(stack:CommandStack):void; + function end(stack:CommandStack):void; + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/decorators/IRenderDecorator.as b/Degrafa/com/degrafa/decorators/IRenderDecorator.as new file mode 100644 index 0000000..fe2d1f7 --- /dev/null +++ b/Degrafa/com/degrafa/decorators/IRenderDecorator.as @@ -0,0 +1,35 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.decorators{ + + import flash.display.Graphics; + + /** + * IRenderDecorator is the base interface for complex render time decorations. See RenderDecoratorBase. + **/ + public interface IRenderDecorator extends IDecorator { + function get isValid():Boolean; + function moveTo(x:Number,y:Number,graphics:Graphics):void + function lineTo(x:Number,y:Number,graphics:Graphics):void + function curveTo(cx:Number, cy:Number, x:Number, y:Number,graphics:Graphics):void + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/decorators/RenderDecoratorBase.as b/Degrafa/com/degrafa/decorators/RenderDecoratorBase.as new file mode 100644 index 0000000..ad9855c --- /dev/null +++ b/Degrafa/com/degrafa/decorators/RenderDecoratorBase.as @@ -0,0 +1,69 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.decorators{ + + import flash.display.Graphics; + + /** + * RenderDecoratorBase is intended to be extended for complex decorations. + * Decorations that use a render time type manipulation extend from this base + * class. Extensions of this class are expected to render the actual data to the + * graphics context passed and as such provides the nessesary proxy methods which + * can be overridden. + **/ + public class RenderDecoratorBase extends DecoratorBase implements IRenderDecorator{ + public function RenderDecoratorBase(){ + super(); + } + + + //override in sub classes. + /** + * A test, which is used to skip a particular decorator if it determines its current state is not valid or would + * not have an effect based on its current settings. A decorator is not executed if this returns false at the time it would + * normally be executed. + **/ + public function get isValid():Boolean { + return true; + } + + //override in sub classes. + /** + * moveTo proxy method. The graphics property is the current context being rendered to. + * This method is expected to be overridden by subclasses. + **/ + public function moveTo(x:Number,y:Number,graphics:Graphics):void {} + + /** + * lineTo proxy method. The graphics property is the current context being rendered to. + * This method is expected to be overridden by subclasses. + **/ + public function lineTo(x:Number,y:Number,graphics:Graphics):void {} + + /** + * curveTo proxy method. The graphics property is the current context being rendered to. + * This method is expected to be overridden by subclasses. + **/ + public function curveTo(cx:Number, cy:Number, x:Number, y:Number,graphics:Graphics):void {} + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/decorators/standard/SVGDashLine.as b/Degrafa/com/degrafa/decorators/standard/SVGDashLine.as new file mode 100644 index 0000000..b3b48a7 --- /dev/null +++ b/Degrafa/com/degrafa/decorators/standard/SVGDashLine.as @@ -0,0 +1,572 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// +// Some algorithms based on code from Trevor McCauley, www.senocular.com +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.decorators.standard{ + + import com.degrafa.core.IGraphicsStroke; + import com.degrafa.decorators.RenderDecoratorBase; + import com.degrafa.geometry.command.CommandStack; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + import mx.events.PropertyChangeEvent; + + public class SVGDashLine extends RenderDecoratorBase{ + /** + * A value representing the accuracy used in determining the length + * of curveTo curves. + */ + public var _curveaccuracy:Number =6; + + private var isLine:Boolean = true; + private var overflow:Number = 0; + private var _penx:Number = 0; + private var _peny:Number = 0; + + private var _dashArray:Array; //same as SVG's dasharray + private var _dashIndex:uint = 0; //where are we in the _dashArray currently + private var _dashVal:Number = 0; //offset from beginning of current dash + private var _dashoffset:Number = 0; //dash offset + private var _alternateStroke:IGraphicsStroke; //an optional alternate stroke to use instead of unstroked gaps + private static var DEFAULT_DASH_PATTERN:Array = [10, 10]; + + + public function SVGDashLine(){ + super(); + } + + + /** + * Allows a short hand property setting that is + * similar to the stroke-dasharray setting in SVG. Populates dashArray from a comma-delimited list of values. + * @see dashArray + **/ + public function get data():String{ + return dashArray.join(","); + } + public function set data(value:String):void{ + var temp:Array = value.split(","); + if (temp[temp.length-1]=="") temp.pop(); + //avoid unnecesary updates: + if ((temp.join(",")+(temp.length&1?","+temp.join(","):""))!=dashArray.join(",")){ + dashArray = temp; + } + } + //is this Decorator valid + //with a default dash pattern this decorator is now initialized as valid: + private var _isValid:Boolean=true; + override public function get isValid():Boolean { + return _isValid; + } + + /** + * Sets new lengths for dash sizes. Follows SVG rules for dasharray stroke style. + * The contents specify a list of on and off dash lengths similar to SVG's dasharray setting. + * If the array assigned contains an odd number of elements, then it is duplicated to + * create an even number of elements. + * Unlike SVG, only pixel units are supported here. Any non-numeric or negative numeric values are in error. + * @see http://www.w3.org/TR/SVG/painting.html#StrokeProperties + */ + public function set dashArray(value:Array):void { + if (value == _dashArray ) return; + //check for errors + for (var i:uint = 0; i < value.length;i++) { + if (isNaN(value[i] = Number(value[i])) || value[i]<0) return; //error + } + //if its an odd length, make it even by doubling it + if (value.length &1) { + value = value.concat(value); + } + _totalLength = 0; + for each(var v:Number in value) _totalLength += v; + if (_totalLength) { + _isValid = true; + } else _isValid = false; + initChange("dashArray", _dashArray, _dashArray = value, this); + } + /** + * Gets the current lengths for dash sizes + * @return Array containing the onLength and offLength values + * respectively in that order + */ + public function get dashArray():Array { + if (_dashArray) return _dashArray; + var defaultArr:Array = DEFAULT_DASH_PATTERN.concat(); + if (isNaN(_totalLength)) { + _totalLength = 0; + for each(var v:Number in defaultArr) _totalLength += v; + _isValid = (_totalLength > 0); + } + return defaultArr; + } + + /** + * Gets the total length of the dash sequence for dash on/dash-off combinations + * @return length of the total dash sequence + */ + public function get totalLength():Number { + if (isNaN(_totalLength)) { + var defaultArr:Array = DEFAULT_DASH_PATTERN.concat(); + _totalLength = 0; + for each(var v:Number in defaultArr) _totalLength += v; + _isValid = (_totalLength > 0); + } + return _totalLength; + } + + + /** + * Specifies the distance into the stroke to start the dash pattern + * similar to the stroke-dashoffset setting in SVG. Negative values are permitted. + * @see http://www.w3.org/TR/SVG/painting.html#StrokeProperties + **/ + public function get dashOffset():Number{ + return _dashoffset; + } + public function set dashOffset(value:Number):void { + if (value != _dashoffset) { + initChange("dashOffset", _dashoffset, _dashoffset = value, this); + } + } + + + /** + * alternateStroke permits an alternate stroke to be used instead of unstroked gaps (this is a degrafa extension, not part of SVG 1.1) + * + **/ + public function get alternateStroke():IGraphicsStroke{ + return _alternateStroke; + } + public function set alternateStroke(value:IGraphicsStroke):void { + if (value != _alternateStroke) { + if (_alternateStroke) _alternateStroke.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler); + var oldVal:IGraphicsStroke = _alternateStroke; + _alternateStroke = value; + if (_alternateStroke) _alternateStroke.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler,false,0,true); + initChange("alternateStroke", oldVal, _alternateStroke, this); + } + } + + + private var _matchCommonStrokeSettings:Boolean = true; + /** + * when an alternateStroke is in use, this setting, if true (the default), matches weight, pixelHinting, caps, joints, scaleMode and miterLimit + * with those of the primary stroke on the original geometry being decorated, otherwise the alternateStroke's own settings for these properties will be used + **/ + public function get matchCommonStrokeSettings():Boolean{ + return _matchCommonStrokeSettings; + } + public function set matchCommonStrokeSettings(value:Boolean):void { + if (value != _matchCommonStrokeSettings) { + _matchCommonStrokeSettings = value; + initChange("matchCommonStrokeSettings", !_matchCommonStrokeSettings, _matchCommonStrokeSettings, this); + } + } + + private var _disableAlternateStroke:Boolean; + /** + * when an alternateStroke is in use, this setting, if true (it defaults to false), will disable the alternate stroke, rendering the dash pattern + * with unstroked gaps as per SVG's standard dash strokes. + **/ + public function get disableAlternateStroke():Boolean{ + return _disableAlternateStroke; + } + public function set disableAlternateStroke(value:Boolean):void { + if (value != _disableAlternateStroke) { + _disableAlternateStroke = value; + initChange("disableAlternateStroke", !_disableAlternateStroke, _disableAlternateStroke, this); + } + } + + + + /** + * @private local handler for referenced external objects + **/ + protected function propertyChangeHandler(event:PropertyChangeEvent):void { + //ignore changes from the alternate stroke if it's disabled + if (_disableAlternateStroke && _alternateStroke && event.source == _alternateStroke ) return; + //for now: + dispatchEvent(event); + //so far just alternateStroke: + //initChange("alternateStroke." + event.property, event.oldValue, event.newValue, this); + } + + + + private var _totalLength:Number; + private var _currentStrokeArgs:Array; + private var _currentRectangle:Rectangle; + private var _altStrokeArgs:Array; + + /** + * initialize override, to set up local reStroking support and adjust for dashoffset, nothing else is required at this point. + * If no stroke was originally set on the decorated geometry, no original stroke will be drawn. + * @param stack + */ + override public function initialize(stack:CommandStack):void { + var i:uint; + _reStrokeActive = true; + if (CommandStack.currentStroke) { + _currentRectangle = CommandStack.currentStroke.lastRectangle; + _currentStrokeArgs = CommandStack.currentStroke.lastArgs; + var restroke:Function = CommandStack.currentStroke.reApplyFunction; + _reStroke = function(graphics:Graphics):void { + restroke(graphics,_currentStrokeArgs); + _reStrokeActive = true; + } + if (_alternateStroke && !_disableAlternateStroke) { + _alternateStroke.apply(null, _currentRectangle); + _altStrokeArgs = _alternateStroke.lastArgs.concat(); + if (_matchCommonStrokeSettings) { + var targ:Array = (_altStrokeArgs[0] is Array)?_altStrokeArgs[0]:_altStrokeArgs; + var copyFrom:Array= (_currentStrokeArgs[0] is Array)? _currentStrokeArgs[0]:_currentStrokeArgs; + targ[0] = copyFrom[0];//weight + targ[3] = copyFrom[3];//pixelhinting + targ[4] = copyFrom[4];//scaling + targ[5] = copyFrom[5];//caps + targ[6] = copyFrom[6];//joints + targ[7] = copyFrom[7];//miterlimit + } + var destroke:Function = _alternateStroke.reApplyFunction; + _deStroke = function(graphics:Graphics):void { + destroke(graphics,_altStrokeArgs); + + } + } else if (_deStroke!=null) _deStroke = null; + } else { + _isValid = false; + return; + } + isLine = true; + overflow = 0; + var dashcalc:Number = Math.abs(_dashoffset) % _totalLength; + + if (dashcalc) { + var dir:int = (_dashoffset < 0)? -1:1; + isLine=(dir==1) + for (i = (dir == -1)? _dashArray.length - 1:0; i > -1 && i < _dashArray.length; i += dir) { + if (dashcalc < _dashArray[i]) { + _dashIndex = i; + _dashVal = (dir == -1)? _dashArray[i] - dashcalc:dashcalc; + dashcalc = 0; + break; + } else { + dashcalc -= _dashArray[i]; + isLine = !isLine; + } + } + } else { + _dashIndex = 0; + _dashVal = 0; + } + if (_dashArray) { + var len:uint = _dashArray.length; + } else _dashArray = DEFAULT_DASH_PATTERN.concat(); + + } + + + /** + * Moves the current drawing position in graphics to (x, y). + */ + override public function moveTo(x:Number, y:Number,graphics:Graphics):void { + graphics.moveTo(x, y); + _penx = x;_peny=y + } + + /** + * Draws a dashed line in graphics from the current drawing position + * to (x, y). + */ + override public function lineTo(x:Number, y:Number, graphics:Graphics):void { + var dx:Number = x-_penx + var dy:Number = y-_peny; + var a:Number = Math.atan2(dy, dx); + var ca:Number + var sa:Number ; + var segLength:Number = lineLength(dx, dy); + + if (overflow) { + if (overflow > segLength) { + //then we won't advance to the next index in dashArray with this lineTo + if (isLine) doLineTo(x,y,graphics); + else doAltLineTo(x,y,graphics); + overflow -= segLength; + _dashVal += segLength; + return; + } + //otherwise we're dealing with a switch inside this lineto following an overflow: + ca = Math.cos(a); + sa = Math.sin(a); + if (isLine) doLineTo(_penx + ca*overflow, _peny + sa*overflow,graphics); + else doAltLineTo(_penx + ca*overflow, _peny + sa*overflow,graphics); + segLength -= overflow; + overflow = 0; + _dashVal = 0; + _dashIndex++ + if (_dashIndex == _dashArray.length)_dashIndex = 0; + isLine = !isLine; + if (!segLength) return; + } else { + ca = Math.cos(a); + sa = Math.sin(a); + } + while ((_dashVal + segLength) > _dashArray[_dashIndex]) { + var remaining:Number = _dashArray[_dashIndex] - _dashVal; + if (segLength > remaining) { + if (isLine) doLineTo(_penx + ca * (remaining), _peny + sa * (remaining), graphics); + else doAltLineTo(_penx + ca * (remaining), _peny + sa * (remaining), graphics); + _dashVal = 0; + //reduce the length of this segment + segLength -= remaining; + //advance to next dash value + _dashIndex++ + if (_dashIndex == _dashArray.length)_dashIndex = 0; + //flip dash state + isLine = !isLine; + }else { + if (isLine) doLineTo(x, y, graphics); + else doAltLineTo(x, y, graphics); + if (segLength == remaining){ + overflow = 0; + _dashVal = 0; + segLength-=remaining; + _dashIndex++ + if (_dashIndex == _dashArray.length)_dashIndex = 0; + isLine = !isLine; + }else{ + overflow = remaining - segLength; + _dashVal += segLength; + if (isLine) doLineTo(x, y,graphics); + else doAltLineTo(x, y,graphics); + } + } + } + + if (_dashVal+segLength <= _dashArray[_dashIndex]) { + + _dashVal += segLength; + overflow = _dashArray[_dashIndex] - _dashVal; + if (isLine) { + doLineTo(_penx+ca*(_dashArray[_dashIndex]-overflow), _peny+sa*(_dashArray[_dashIndex]-overflow),graphics); + + } else { + doAltLineTo(_penx+ca*(_dashArray[_dashIndex]-overflow), _peny+sa*(_dashArray[_dashIndex]-overflow),graphics); + } + } + } + + /** + * Draws a dashed curve in graphics using the current from the current drawing position to + * (x, y) using the control point specified by (cx, cy). + */ + override public function curveTo(cx:Number, cy:Number, x:Number, y:Number, graphics:Graphics):void { + var sx:Number = _penx; + var sy:Number = _peny; + var segLength:Number = curveLength(sx, sy, cx, cy, x, y,_curveaccuracy); + var t:Number = 0; + var t2:Number = 0; + var c:Array; + var d:Array; + if (overflow) { + if (overflow > segLength){ + if (isLine) doCurveTo(cx, cy, x, y,graphics); + else doAltCurveTo(cx, cy, x, y, graphics); + overflow -= segLength; + _dashVal += segLength; + return; + } + t = overflow/segLength; + c = curveSliceUpTo(sx, sy, cx, cy, x, y, t); + d = curveSliceFrom(sx, sy, cx, cy, x, y, t); + if (isLine) doCurveTo(c[2], c[3], c[4], c[5],graphics); + else doAltCurveTo(c[2], c[3], c[4], c[5], graphics); + segLength -= overflow; + overflow = 0; + _dashVal = 0; + _dashIndex++ + if (_dashIndex == _dashArray.length)_dashIndex = 0; + isLine = !isLine; + if (!segLength) return; + sx = d[0]; sy = d[1]; + cx = d[2]; cy = d[3]; + } + + while ((_dashVal + segLength) > _dashArray[_dashIndex]) { + + var remaining:Number = _dashArray[_dashIndex] - _dashVal; + if (segLength > remaining) { + t = remaining / segLength; + c = curveSliceUpTo(sx, sy, cx, cy, x, y, t); + d = curveSliceFrom(sx, sy, cx, cy, x, y, t); + + if (isLine) doCurveTo(c[2], c[3], c[4], c[5], graphics); + else doAltCurveTo(c[2], c[3], c[4], c[5], graphics); + _dashVal = 0; + //reduce the length of this segment + segLength -= remaining; + //advance to next dash value + _dashIndex++ + if (_dashIndex == _dashArray.length)_dashIndex = 0; + //flip dash state + isLine = !isLine; + sx = d[0]; sy = d[1]; + cx = d[2]; cy = d[3]; + }else { + if (isLine) doCurveTo(cx, cy, x, y, graphics); + else doAltCurveTo(cx, cy, x, y, graphics); + if (segLength == remaining) { + overflow = 0; + _dashVal = 0; + segLength-=remaining; + _dashIndex++ + if (_dashIndex == _dashArray.length)_dashIndex = 0; + isLine = !isLine; + }else{ + overflow = remaining - segLength; + _dashVal += segLength; + if (isLine) doCurveTo(cx, cy, x, y, graphics); + else doAltCurveTo(cx, cy, x, y, graphics); + return; + } + } + } + if (_dashVal+segLength <= _dashArray[_dashIndex]) { + _dashVal += segLength; + overflow = _dashArray[_dashIndex] - _dashVal; + if (isLine) { + doCurveTo(cx, cy, x, y, graphics); + } else { + doAltCurveTo(cx, cy, x, y, graphics); + } + } + } + + private var _reStrokeActive:Boolean; + private var _restrokeArgs:Array; + private var _reStroke:Function; + private var _deStroke:Function; + private var _destrokeArgs:Array; + + private var deStroke:Function = function (graphics:Graphics):void { + //use the alternate stroke if one is specified + if (_deStroke!=null) _deStroke(graphics) + else graphics.lineStyle(); + _reStrokeActive = false; + } + + private function doAltLineTo(x:Number, y:Number, graphics:Graphics):void { + if (x == _penx && y == _peny) return; + _penx = x; _peny = y; + if (_reStrokeActive) deStroke(graphics); + graphics.lineTo(x, y); + } + + private function doLineTo(x:Number, y:Number, graphics:Graphics):void { + if (x == _penx && y == _peny) return; + _penx = x; _peny = y; + if (!_reStrokeActive)_reStroke(graphics); + graphics.lineTo(x, y); + } + private function doCurveTo(cx:Number, cy:Number, x:Number, y:Number, graphics:Graphics):void { + if (cx == x && cy == y && x == _penx && y == _peny) return; + _penx = x; _peny = y; + if (!_reStrokeActive) _reStroke(graphics); + graphics.curveTo(cx, cy, x, y); + } + + private function doAltCurveTo(cx:Number, cy:Number, x:Number, y:Number, graphics:Graphics):void { + if (cx == x && cy == y && x == _penx && y == _peny) return; + _penx = x; _peny = y; + if (_reStrokeActive) deStroke(graphics); + graphics.curveTo(cx, cy, x, y); + } + //the below to be moved into a shared class. + + // private methods + private function lineLength(sx:Number, sy:Number, ex:Number=0, ey:Number=0):Number { + if (arguments.length == 2) return Math.sqrt(sx*sx + sy*sy); + var dx:Number = ex - sx; + var dy:Number = ey - sy; + return Math.sqrt(dx*dx + dy*dy); + } + + private function curveLength(sx:Number, sy:Number, cx:Number, cy:Number, ex:Number, ey:Number, accuracy:Number):Number { + var total:Number = 0; + var tx:Number = sx; + var ty:Number = sy; + var px:Number, py:Number, t:Number, it:Number, a:Number, b:Number, c:Number; + var n:Number = (accuracy) ? accuracy : _curveaccuracy; + for (var i:Number = 1; i<=n; i++){ + t = i/n; + it = 1-t; + a = it*it; b = 2*t*it; c = t*t; + px = a*sx + b*cx + c*ex; + py = a*sy + b*cy + c*ey; + total += lineLength(tx, ty, px, py); + tx = px; + ty = py; + } + return total; + } + + private function curveSlice(sx:Number, sy:Number, cx:Number, cy:Number, ex:Number, ey:Number, t1:Number, t2:Number):Array { + if (t1 == 0) return curveSliceUpTo(sx, sy, cx, cy, ex, ey, t2); + else if (t2 == 1) return curveSliceFrom(sx, sy, cx, cy, ex, ey, t1); + var c:Array = curveSliceUpTo(sx, sy, cx, cy, ex, ey, t2); + c.push(t1/t2); + return curveSliceFrom.apply(this, c); + } + + private function curveSliceUpTo(sx:Number, sy:Number, cx:Number, cy:Number, ex:Number, ey:Number, t:Number):Array { + if (isNaN(t)) t = 1; + if (t != 1) { + var midx:Number = cx + (ex-cx)*t; + var midy:Number = cy + (ey-cy)*t; + cx = sx + (cx-sx)*t; + cy = sy + (cy-sy)*t; + ex = cx + (midx-cx)*t; + ey = cy + (midy-cy)*t; + } + return [sx, sy, cx, cy, ex, ey]; + } + + private function curveSliceFrom(sx:Number, sy:Number, cx:Number, cy:Number, ex:Number, ey:Number, t:Number):Array { + if (isNaN(t)) t = 1; + if (t != 1) { + var midx:Number = sx + (cx-sx)*t; + var midy:Number = sy + (cy-sy)*t; + cx = cx + (ex-cx)*t; + cy = cy + (ey-cy)*t; + sx = midx + (cx-midx)*t; + sy = midy + (cy-midy)*t; + } + return [sx, sy, cx, cy, ex, ey]; + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/decorators/standard/ShapeStrokeDecorator.as b/Degrafa/com/degrafa/decorators/standard/ShapeStrokeDecorator.as new file mode 100644 index 0000000..243cb06 --- /dev/null +++ b/Degrafa/com/degrafa/decorators/standard/ShapeStrokeDecorator.as @@ -0,0 +1,243 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.decorators.standard{ + + import com.degrafa.GeometryComposition; + import com.degrafa.core.collections.GeometryCollection; + import com.degrafa.geometry.Geometry; + import com.degrafa.transform.RotateTransform; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + import mx.events.PropertyChangeEvent; + /** + * ShapeStrokeDecorator is intended as an example wrapper type decoration. + * Given a source object the ShapeStrokeDecorator will repeat that object + * along the wrapped geometry. ShapeStrokeDecorator is based on GeometryComposition, + * so all contained geometry will be decorated. + **/ + public class ShapeStrokeDecorator extends GeometryComposition{ + + private var coords:Array=[]; //stores coordinates and angles along the path + + public function ShapeStrokeDecorator(){ + super(); + invalidated = true; + } + + private var _sourceGeometry:GeometryCollection; + [Inspectable(category="General", arrayType="com.degrafa.IGeometryComposition")] + [ArrayElementType("com.degrafa.IGeometryComposition")] + /** + * A array of IGeometryComposition objects. For ShapeStrokeDecorator + * at this time only one is allowed. + **/ + public function get sourceGeometry():Array{ + initSourceGeometryCollection(); + return _sourceGeometry.items; + } + public function set sourceGeometry(value:Array):void{ + initSourceGeometryCollection(); + _sourceGeometry.items = value; + } + + /** + * Access to the Degrafa geometry collection object for this geometry object. + **/ + public function get sourceGeometryCollection():GeometryCollection{ + initSourceGeometryCollection(); + return _sourceGeometry; + } + + /** + * Initialize the geometry collection by creating it and adding an event listener. + **/ + private function initSourceGeometryCollection():void{ + if(!_sourceGeometry){ + _sourceGeometry = new GeometryCollection(); + + //add the parent so it can be managed by the collection + _sourceGeometry.parent = this; + + //add a listener to the collection + if(enableEvents){ + _sourceGeometry.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + private var _explicitRepeatCount:int=-1; + /** + * Use an explicit count. If not specified the optimal will be calculated. + **/ + public function get explicitRepeatCount():int{ + return _explicitRepeatCount; + } + public function set explicitRepeatCount(value:int):void{ + _explicitRepeatCount=value; + invalidated =true; + } + + private var _gap:Number=0; + /** + * The gap of empty space between repeated items. Only applicable + * when explicitRepeatCount is not set. + **/ + public function get gap():Number{ + return _gap; + } + public function set gap(value:Number):void{ + _gap=value; + invalidated =true; + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + + if(invalidated){ + + for each (var geom:Geometry in geometry){ + geom.preDraw(); + geom.calculateLayout(); + geom.commandStack.lengthInvalidated =true; + } + invalidated =false; + } + } + + /** + * Ends the draw phase for geometry objects. + * + * @param graphics The current Graphics context being drawn to. + **/ + override public function endDraw(graphics:Graphics):void { + super.endDraw(graphics); + + //we have drawn our object that we will be repeating around, predrawn the source + //object to repeat and stored the angles and points on our geometry that are required. + executeStroke(graphics); + + } + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics, rc:Rectangle):void{ + + //init the layout in this case done before predraw. + calculateLayout(); + + //re init if required + preDraw(); + + super.draw(graphics, (rc)? rc:bounds); + } + + /** + * Calculates the values required for distribution. + **/ + private function calcValues(geom:Geometry):void{ + + var repeateCount:int; + if(_explicitRepeatCount==-1){ + //calculate optimal based on geometric length + var optimalDivisor:Number = Math.min(Geometry(sourceGeometry[0]).bounds.height,Geometry(sourceGeometry[0]).bounds.width); + repeateCount = (geom.commandStack.transformedPathLength / (optimalDivisor+_gap)); + } + else{ + //use explicit count setting + repeateCount=_explicitRepeatCount; + } + + for (var i:int=0;i<(repeateCount+1);i++){ + coords.push({point:geom.pointAt(i/repeateCount),angle:geom.angleAt(i/repeateCount)*(180/Math.PI)}) + } + + //add item at t:1 + coords.push({point:geom.pointAt(1),angle:geom.angleAt(1)*(180/Math.PI)}) + + } + + /** + * Executes the distribution. + **/ + private function executeStroke(graphics:Graphics):void{ + + if(sourceGeometry.length==0){return;} + + coords.length=0; + + var trans:RotateTransform = new RotateTransform(); + trans.angle = 0; + trans.registrationPoint = "center"; + + trans.enableEvents = false; + + var geom:Geometry; + for each (geom in sourceGeometry){ + geom.suppressEventProcessing = true; + geom.transform = trans; + geom.preDraw(); + } + + //get the angles and points + for each (geom in geometry){ + calcValues(geom); + } + + var xOffset:Number=0; + var yOffset:Number=0; + var sourceRect:Rectangle; + + var sourcelength:int=sourceGeometry.length; + var sourceIndex:int=0 + + for (var i:int=0;iThe advanced cubic Bézier constructor accepts 8 optional arguments that define it's + * start, end and controls points.

+ * + * @param x0 A number indicating the starting x-axis coordinate. + * @param y0 A number indicating the starting y-axis coordinate. + * @param cx A number indicating the first control x-axis coordinate. + * @param cy A number indicating the first control y-axis coordinate. + * @param cx1 A number indicating the second control x-axis coordinate. + * @param cy1 A number indicating the second control y-axis coordinate. + * @param x1 A number indicating the ending x-axis coordinate. + * @param y1 A number indicating the ending y-axis coordinate. + */ + public function AdvancedCubicBezier(x0:Number=NaN,y0:Number=NaN,cx:Number=NaN,cy:Number=NaN,cx1:Number=NaN,cy1:Number=NaN,x1:Number=NaN,y1:Number=NaN) + { + super(); + + this.x0 = x0; + this.y0 = y0; + this.cx = cx; + this.cy = cy; + this.x1 = x1; + this.y1 = y1; + + _bisectLimit = 0.05; + _left = 0; + _right = 1; + _t1X = 0; + _t1Y = 0; + _t2X = 0; + _t2Y = 0; + + + // Jack Crenshaw's TWBRF and 2x2 solver, both instantiated on demand + _twbrf = null; + _solver = null; + } + + /** + * @inheritDoc + **/ + override public function preDraw():void + { + if( invalidated ) + { + + commandStack.length=0; + + // add a MoveTo at the start of the commandStack rendering chain + commandStack.addMoveTo(x0,y0); + + commandStack.addCubicBezierTo(x0,y0,cx,cy,cx1,cy1,x1,y1,1); + + if( close ) + { + commandStack.addLineTo(x0,y0); + } + + getBezierCoef(); + invalidated = false; + } + } + + override public function pointAt(_t:Number):Point + { + var t:Number = _t < 0 ? 0 : _t; + t = t > 1 ? 1 : t; + + return new Point( _c0X + t*(_c1X + t*(_c2X + t*_c3X)), _c0Y + t*(_c1Y + t*(_c2Y + t*_c3Y)) ); + } + +/** +* interpolate +* +*

Compute control points so that quadratic Bezier passes through three points at the specified parameter value. +* +* @param _points:Array - array of three Point references, representing the coordinates of the interpolation points. +* +* @return Array the parameter values in [0,1] at which the Bezier curve passes through the second and third interpolation points (determined by a chord-length parameterization). +* A negative value is returned if less than three interpolation points are provided. +* +*/ + public function interpolate(points:Array):Array + { + // compute t-value using chord-length parameterization + if( points.length < 4 ) + { + return [-1]; + } + + // no error-checking ... you break it, you buy it. + var p0:Point = points[0]; + var p1:Point = points[1]; + var p2:Point = points[2]; + var p3:Point = points[3]; + + x0 = p0.x; + y0 = p0.y; + x1 = p3.x; + y1 = p3.y; + + // currently, this method auto-parameterizes the curve using chord-length parameterization. A future version might allow inputting the two t-values, but this is more + // user-friendly (what an over-used term :) + var deltaX:Number = p1.x - p0.x; + var deltaY:Number = p1.y - p0.y; + var d1:Number = Math.sqrt(deltaX*deltaX + deltaY*deltaY); + + deltaX = p2.x - p1.x; + deltaY = p2.y - p1.y; + var d2:Number = Math.sqrt(deltaX*deltaX + deltaY*deltaY); + + deltaX = p3.x - p2.x; + deltaY = p3.y - p2.y; + var d3:Number = Math.sqrt(deltaX*deltaX + deltaY*deltaY); + + var d:Number = d1 + d2 + d3; + var t1:Number = d1/d; + var t2:Number = (d1+d2)/d; + + // there are four unknowns (x- and y-coords for P1 and P2), which are solved as two separate sets of two equations in two unknowns + var t12:Number = t1*t1; + var t13:Number = t1*t12; + + var t22:Number = t2*t2; + var t23:Number = t2*t22; + + // x-coordinates of P1 and P2 (t = t1 and t2) - exercise: eliminate redudant computations in these equations + var a11:Number = 3*t13 - 6*t12 + 3*t1; + var a12:Number = -3*t13 + 3*t12; + var a21:Number = 3*t23 - 6*t22 + 3*t2; + var a22:Number = -3*t23 + 3*t22; + + var b1:Number = -t13*x1 + x0*(t13 - 3*t12 + 3*t1 - 1) + p1.x; + var b2:Number = -t23*x1 + x0*(t23 - 3*t22 + 3*t2 - 1) + p2.x; + + if( _solver == null ) + { + _solver = new Solve2x2(); + } + + // beware nearly or exactly coincident interior interpolation points + var p:Point = _solver.solve(a11, a12, a21, a22, b1, b2); + if( _solver.determinant < 0.000001 ) + { + // degenerates to a parabolic interpolation + var t1m1:Number = 1.0-t1; + var tSq:Number = t1*t1; + var denom:Number = 2.0*t1*t1m1; + + // to do - handle case where this degenerates into all overlapping points (i.e. denom is numerically zero) + cx = (p1.x - t1m1*t1m1*x0 - tSq*p2.x)/denom; + cy = (p1.y - t1m1*t1m1*y0 - tSq*p2.y)/denom; + + cx1 = cx; + cy1 = cy; + + getBezierCoef(); + + return [t1, t1]; + } + else + { + cx = p.x + cx1 = p.y; + } + + // y-coordinates of P1 and P2 (t = t1 and t2) + b1 = -t13*y1 + y0*(t13 - 3*t12 + 3*t1 - 1) + p1.y; + b2 = -t23*y1 + y0*(t23 - 3*t22 + 3*t2 - 1) + p2.y; + + // resolving with same coefficients, but new RHS + p = _solver.solve(a11, a12, a21, a22, b1, b2, 0.00001, true); + cy = p.x + cy1 = p.y; + + getBezierCoef(); + + return [t1, t2]; + } + +/** +* tAtMinX +* +*

Find t-parameter at which the x-coordinate is a minimum.

+* +* @return Number Parameter value in [0,1] at which the cubic Bezier curve's x-coordinate is a minimum +* +* @since 1.0 +* +*/ + public function tAtMinX():Number + { + getStationaryPoints(); + + var t:Number = 0; + var minX:Number = x0; + + if( x1 < minX ) + { + t = 1; + minX = x1; + } + + if( _t1X > 0 && _t1X < 1 ) + { + var myX:Number = pointAt(_t1X).x; + if( myX < minX ) + { + t = _t1X; + minX = myX; + } + } + + if( _t2X > 0 && _t2X < 1 ) + { + if( pointAt(_t2X).x < minX ) + { + t = _t2X; + } + } + + return t; + } + +/** +* tAtMaxX +* +*

Find t-parameter at which the x-coordinate is a maximum.

+* +* @return Number Parameter value in [0,1] at which the cubic Bezier curve's x-coordinate is a maximum. +* +*/ + public function tAtMaxX():Number + { + getStationaryPoints(); + + var t:Number = 0; + var maxX:Number = x0; + + if( x1 > maxX ) + { + t = 1; + maxX = x1; + } + + if( _t1X > 0 && _t1X < 1 ) + { + var myX:Number = pointAt(_t1X).x; + if( myX > maxX ) + { + t = _t1X; + maxX = myX; + } + } + + if( _t2X > 0 && _t2X < 1 ) + { + if( pointAt(_t2X).x > maxX ) + { + t = _t2X; + } + } + + return t; + } + +/** +* tAtMinY +* +*

Find t-parameter at which the y-coordinate is a minimum. +* +* @return Number - Parameter value in [0,1] at which the cubic Bezier curve's y-coordinate is a minimum. +* +*/ + public function tAtMinY():Number + { + getStationaryPoints(false); + + var t:Number = 0; + var minY:Number = y0; + + if( y1 < minY ) + { + t = 1; + minY = y1; + } + + if( _t1Y > 0 && _t1Y < 1 ) + { + var myY:Number = pointAt(_t1Y).y; + if( myY < minY ) + { + t = _t1Y; + minY = myY; + } + } + + if( _t2Y > 0 && _t2Y < 1 ) + { + if( pointAt(_t2Y).y < minY ) + { + t = _t2Y; + } + } + + return t; + } + +/** +* tAtMaxY +* +*

Find t-parameter at which the y-coordinate is a maximum.

+* +* @return Number Parameter value in [0,1] at which the cubic Bezier curve's y-coordinate is a maximum. +* +*/ + public function tAtMaxY():Number + { + getStationaryPoints(false); + + var t:Number = 0; + var maxY:Number = y0; + + if( y1 > maxY ) + { + t = 1; + maxY = y1; + } + + if( _t1Y > 0 && _t1Y < 1 ) + { + var myY:Number = pointAt(_t1Y).y; + if( myY > maxY ) + { + t = _t1Y; + maxY = myY; + } + } + + if( _t2Y > 0 && _t2Y < 1 ) + { + if( pointAt(_t2Y).y > maxY ) + { + t = _t2Y; + } + } + + return t; + } + +/** + * yAtX + * + *

Return the set of y-coordinates corresponding to the input x-coordinate.

+ * + * @param _x:Number x-coordinate at which the desired y-coordinates are desired + * + * @return Array set of (t,y)-coordinates at the input x-coordinate provided that the x-coordinate is inside the range + * covered by the quadratic Bezier in [0,1]; that is there must exist t in [0,1] such that Bx(t) = _x. If the input + * x-coordinate is not inside the range covered by the Bezier curve, the returned array is empty. Otherwise, the + * array contains either one, two, or three y-coordinates. There are issues with curves that are exactly or nearly (for + * numerical purposes) vertical in which there could theoretically be an infinite number of y-coordinates for a single + * x-coordinate. This method does not work in such cases, although compensation might be added in the future. + * + *

Each array element is a reference to an Object whose 't' parameter represents the Bezier t parameter. The + * Object 'y' property is the corresponding y-value. The returned (t,y) coordinates may be used by the caller + * to determine which of the (up to three) returned y-coordinates might be preferred over the others.

+ * + */ + public function yAtX(_x:Number):Array + { + if( isNaN(_x) ) + { + return []; + } + + // check bounds + var xMax:Number = pointAt(tAtMaxX()).x; + var xMin:Number = pointAt(tAtMinX()).x; + + if( _x < xMin || _x > xMax ) + { + return []; + } + + // the necessary y-coordinates are the intersection of the curve with the line x = _x. The curve is generated in the + // form c0 + c1*t + c2*t^2 + c3*t^3, so the intersection satisfies the equation + // Bx(t) = _x or Bx(t) - _x = 0, or c0x-_x + c1x*t + c2x*t^2 + c3x*t^3 = 0. + + getBezierCoef(); + + // Find one root - any root - then factor out (t-r) to get a quadratic poly. for the remaining roots + var f:Function = function(_t:Number):Number { return _t*(_c1X + _t*(_c2X + _t*(_c3X))) + _c0X-_x; } + + if( _twbrf == null ) + _twbrf = new SimpleRoot(); + + // some curves that loop around on themselves may require bisection + _left = 0; + _right = 1; + __bisect(f, 0, 1); + + // experiment with tolerance - but not too tight :) + var t0:Number = _twbrf.findRoot(_left, _right, f, 50, 0.000001); + var eval:Number = Math.abs(f(t0)); + if( eval > 0.00001 ) + return []; // compensate in case method quits due to error (no event listener here) + + var result:Array = new Array(); + if( t0 <= 1 ) + result.push({t:t0, y:pointAt(t0).y}); + + // Factor theorem: t-r is a factor of the cubic polynomial if r is a root. Use this to reduce to a quadratic poly. + // using synthetic division + var a:Number = _c3X; + var b:Number = t0*a+_c2X; + var c:Number = t0*b+_c1X; + + // process the quadratic for the remaining two possible roots + var d:Number = b*b - 4*a*c; + if( d < 0 ) + { + return result; + } + + d = Math.sqrt(d); + a = 1/(a + a); + var t1:Number = (d-b)*a; + var t2:Number = (-b-d)*a; + + if( t1 >= 0 && t1 <=1 ) + result.push( {t:t1, y:pointAt(t1).y} ); + + if( t2 >= 0 && t2 <=1 ) + result.push( {t:t2, y:pointAt(t2).y} ); + + return result; + } + +/** + * xAtY + * + *

Return the set of x-coordinates corresponding to the input y-coordinate.

+ * + * @param _y:Number y-coordinate at which the desired x-coordinates are desired + * + * @return Array set of (t,x)-coordinates at the input y-coordinate provided that the y-coordinate is inside the range + * covered by the quadratic Bezier in [0,1]; that is there must exist t in [0,1] such that By(t) = _y. If the input + * y-coordinate is not inside the range covered by the Bezier curve, the returned array is empty. Otherwise, the + * array contains either one, two, or three x-coordinates. There are issues with curves that are exactly or nearly (for + * numerical purposes) horizontal in which there could theoretically be an infinite number of x-coordinates for a single + * y-coordinate. This method does not work in such cases, although compensation might be added in the future. + * + *

Each array element is a reference to an Object whose 't' parameter represents the Bezier t parameter. The + * Object 'x' property is the corresponding x-coordinate. The returned (t,x) coordinates may be used by the caller + * to determine which of the (up to three) returned x-coordinates might be preferred over the others.

+ * + */ + public function xAtY(_y:Number):Array + { + if( isNaN(_y) ) + { + return []; + } + + // check bounds + var yMax:Number = pointAt(tAtMaxY()).y; + var yMin:Number = pointAt(tAtMinY()).y; + + if( _y < yMin || _y > yMax ) + { + return []; + } + + // the necessary y-coordinates are the intersection of the curve with the line y = _y. The curve is generated in the + // form c0 + c1*t + c2*t^2 + c3*t^3, so the intersection satisfies the equation + // By(t) = _y or By(t) - _y = 0, or c0y-_y + c1y*t + c2y*t^2 + c3y*t^3 = 0. + + getBezierCoef(); + + // Find one root - any root - then factor out (t-r) to get a quadratic poly. for the remaining roots + var f:Function = function(_t:Number):Number { return _t*(_c1Y + _t*(_c2Y + _t*(_c3Y))) + _c0Y-_y; } + + if( _twbrf == null ) + _twbrf = new SimpleRoot(); + + // some curves that loop around on themselves may require bisection + _left = 0; + _right = 1; + __bisect(f, 0, 1); + + // experiment with tolerance - but not too tight :) + var t0:Number = _twbrf.findRoot(_left, _right, f, 50, 0.000001); + var eval:Number = Math.abs(f(t0)); + if( eval > 0.00001 ) + return []; // compensate in case method quits due to error (no event listener here) + + var result:Array = new Array(); + if( t0 <= 1 ) + result.push({t:t0, x:pointAt(t0).x}); + + // Factor theorem: t-r is a factor of the cubic polynomial if r is a root. Use this to reduce to a quadratic poly. using synthetic division + var a:Number = _c3Y; + var b:Number = t0*a+_c2Y; + var c:Number = t0*b+_c1Y; + + // process the quadratic for the remaining two possible roots + var d:Number = b*b - 4*a*c; + if( d < 0 ) + { + return result; + } + + d = Math.sqrt(d); + a = 1/(a + a); + var t1:Number = (d-b)*a; + var t2:Number = (-b-d)*a; + + if( t1 >= 0 && t1 <=1 ) + result.push( {t:t1, x:pointAt(t1).x} ); + + if( t2 >= 0 && t2 <=1 ) + result.push( {t:t2, x:pointAt(t2).x} ); + + return result; + } + + // recompute polynomial coefficients + private function getBezierCoef():void + { + _c0X = x0; + _c0Y = y0; + + var dX:Number = 3.0*(cx-x0); + var dY:Number = 3.0*(cy-y0); + _c1X = dX; + _c1Y = dY; + + var bX:Number = 3.0*(cx1-cx) - dX; + var bY:Number = 3.0*(cy1-cy) - dY; + _c2X = bX; + _c2Y = bY; + + _c3X = x1 - x0 - dX - bX; + _c3Y = y1 - y0 - dY - bY; + } + + // bisect the specified range to isolate an interval with a root. + private function __bisect(_f:Function, _l:Number, _r:Number):void + { + if( Math.abs(_r-_l) <= _bisectLimit ) + { + return; + } + + var left:Number = _l; + var right:Number = _r; + var middle:Number = 0.5*(left+right); + if( _f(left)*_f(right) <= 0 ) + { + _left = left; + _right = right; + return; + } + else + { + __bisect(_f, left, middle); + __bisect(_f, middle, right); + } + } + + // get the statonary points of x(t) and y(t) + private function getStationaryPoints(pX:Boolean=true):void + { + // in a future release, this will be made more efficient - don't want to mess with the invalidated flag just yet :) + getBezierCoef(); + + // given polynomial coefficients, the bezier curve equation is of the form c0 + c1*t + c2*t^2 + c3*t^3, so the derivative is of + // the form c1 + 2*c2*t + 3*c3*t^2, which has two roots + var d:Number = -1; + var t1:Number = -1; + var t2:Number = -1; + + if( pX ) + { + d = 4*_c2X*_c2X - 12*_c1X*_c3X; + if( d >= 0 ) + { + d = Math.sqrt(d); + var a:Number = 6*_c3X; + var b:Number = 2*_c2X; + t1 = (-b + d)/a; + t2 = (-b - d)/a; + } + + _t1X = t1 >= 0 && t1 <= 1 ? t1 : -1; + _t2X = t2 >= 0 && t2 <= 1 ? t2 : -1; + } + else + { + d = 4*_c2Y*_c2Y - 12*_c1Y*_c3Y; + if( d >= 0 ) + { + d = Math.sqrt(d); + a = 6*_c3Y; + b = 2*_c2Y; + t1 = (-b + d)/a; + t2 = (-b - d)/a; + } + + _t1Y = t1 >= 0 && t1 <= 1 ? t1 : -1; + _t2Y = t2 >= 0 && t2 <= 1 ? t2 : -1; + } + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/AdvancedQuadraticBezier.as b/Degrafa/com/degrafa/geometry/AdvancedQuadraticBezier.as new file mode 100644 index 0000000..f5eaf1f --- /dev/null +++ b/Degrafa/com/degrafa/geometry/AdvancedQuadraticBezier.as @@ -0,0 +1,497 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Programmed by: Jim Armstrong +// +// This software is derived from source containing the following copyright notice +// +// copyright (c) 2006-2007, Jim Armstrong. All Rights Reserved. +// +// This software program is supplied 'as is' without any warranty, express, implied, +// or otherwise, including without limitation all warranties of merchantability or fitness +// for a particular purpose. Jim Armstrong shall not be liable for any special incidental, or +// consequential damages, including, without limitation, lost revenues, lost profits, or +// loss of prospective economic advantage, resulting from the use or misuse of this software +// program. +// +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry +{ + import com.degrafa.IGeometry; + import com.degrafa.geometry.utilities.GeometryUtils; + + import flash.display.Graphics; + import flash.geom.Rectangle; + import flash.geom.Point; + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("QuadraticBezier.png")] + + [Bindable] + /** + * The AdvancedQuadraticBezier element draws a quadratic Bézier using the specified + * start point, end point and control point and contains several additional methods + * that are useful in advanced applications. + * + * + **/ + public class AdvancedQuadraticBezier extends QuadraticBezier + { + // bezier polynomial coefficients + private var _c0X:Number; + private var _c0Y:Number; + private var _c1X:Number; + private var _c1Y:Number; + private var _c2X:Number; + private var _c2Y:Number; + + /** + * Constructor. + * + *

The advanced quadratic Bézier constructor accepts 6 optional arguments that define it's + * start, end and controls points.

+ * + * @param x0 A number indicating the starting x-axis coordinate. + * @param y0 A number indicating the starting y-axis coordinate. + * @param cx A number indicating the control x-axis coordinate. + * @param cy A number indicating the control y-axis coordinate. + * @param x1 A number indicating the ending x-axis coordinate. + * @param y1 A number indicating the ending y-axis coordinate. + */ + public function AdvancedQuadraticBezier(x0:Number=NaN,y0:Number=NaN,cx:Number=NaN,cy:Number=NaN,x1:Number=NaN,y1:Number=NaN) + { + super(); + + this.x0 = x0; + this.y0 = y0; + this.cx = cx; + this.cy = cy; + this.x1 = x1; + this.y1 = y1; + } + + /** + * @inheritDoc + **/ + override public function preDraw():void + { + // i should just call super.preDraw() then add the new stuff, but this reminds me what is going on under the hood :) + if( invalidated ) + { + commandStack.length=0; + + commandStack.resetBounds(); + + commandStack.addMoveTo(x0,y0); + commandStack.addCurveTo(cx,cy,x1,y1); + + if( close ) + commandStack.addLineTo(x0,y0); + + getBezierCoef(); + invalidated = false; + } + } + +/** +* interpolate +* +*

Compute control points so that quadratic Bezier passes through three points at the specified parameter value. +* +* @param _points:Array - array of three Point references, representing the coordinates of the interpolation points. +* +* @return Number the parameter value in [0,1] at which the Bezier curve passes through the second control point (determined by a chord-length parameterization). +* A negative value is returned if less than three interpolation points are provided. +* +*/ + public function interpolate(points:Array):Number + { + // compute t-value using chord-length parameterization + if( points.length < 3 ) + { + return -1; + } + + var p0:Point = points[0]; + var p1:Point = points[1]; + var p2:Point = points[2]; + var dX:Number = p1.x - p0.x; + var dY:Number = p1.y - p0.y; + var d1:Number = Math.sqrt(dX*dX + dY*dY); + var d:Number = d1; + + dX = p2.x - p1.x; + dY = p2.y - p1.y; + d += Math.sqrt(dX*dX + dY*dY); + + var t:Number = d1/d; + + var t1:Number = 1.0-t; + var tSq:Number = t*t; + var denom:Number = 2.0*t*t1; + + x0 = p0.x; + y0 = p0.y; + + cx = (p1.x - t1*t1*p0.x - tSq*p2.x)/denom; + cy = (p1.y - t1*t1*p0.y - tSq*p2.y)/denom; + + x1 = p2.x; + y1 = p2.y; + + getBezierCoef(); + return t; + } + +/** +* tAtMinX +* +*

Find t-parameter at which the x-coordinate is a minimum.

+* +* @return Number Parameter value in [0,1] at which the qudratic Bezier curve's x-coordinate is a minimum +* +* @since 1.0 +* +*/ + public function tAtMinX():Number + { + var denom:Number = (x0 - 2*cx + x1); + var tStar:Number = 0; + if( Math.abs(denom) > 0.0000001 ) + tStar = (x0 - cx)/denom; + + var t:Number = 0; + var minX:Number = x0; + + if( x1 < minX ) + { + t = 1; + minX = x1; + } + + if( tStar > 0 && tStar < 1 ) + { + if( pointAt(tStar).x < minX ) + { + t = tStar; + } + } + + return t; + } + +/** +* tAtMaxX +* +*

Find t-parameter at which the x-coordinate is a maximum.

+* +* @return Number Parameter value in [0,1] at which the quadratic Bezier curve's x-coordinate is a maximum. +* +*/ + public function tAtMaxX():Number + { + var denom:Number = (x0 - 2*cx + x1); + var tStar:Number = 0; + if( Math.abs(denom) > 0.0000001 ) + tStar = (x0 - cx)/denom; + + var t:Number = 0; + var maxX:Number = x0; + + if( x1 > maxX ) + { + t = 1; + maxX = x1; + } + + if( tStar > 0 && tStar < 1 ) + { + if( pointAt(tStar).x > maxX ) + { + t = tStar; + } + } + + return t; + } + +/** +* tAtMinY +* +*

Find t-parameter at which the y-coordinate is a minimum. +* +* @return Number - Parameter value in [0,1] at which the quadratic Bezier curve's y-coordinate is a minimum. +* +*/ + public function tAtMinY():Number + { + var denom:Number = (y0 - 2*cy + y1); + var tStar:Number = 0; + if( Math.abs(denom) > 0.0000001 ) + tStar = (y0 - cy)/denom; + + var t:Number = 0; + var minY:Number = y0; + + if( y1 < minY ) + { + t = 1; + minY = y1; + } + + if( tStar > 0 && tStar < 1 ) + { + if( pointAt(tStar).y < minY ) + { + t = tStar; + } + } + + return t; + } + +/** +* tAtMaxY +* +*

Find t-parameter at which the y-coordinate is a maximum.

+* +* @return Number Parameter value in [0,1] at which the quadratic Bezier curve's y-coordinate is a maximum. +* +*/ + public function tAtMaxY():Number + { + var denom:Number = (y0 - 2*cy + y1); + var tStar:Number = 0; + if( Math.abs(denom) > 0.0000001 ) + tStar = (y0 - cy)/denom; + + var t:Number = 0; + var maxY:Number = y0; + + if( y1 > maxY ) + { + t = 1; + maxY = y1; + } + + if( tStar > 0 && tStar < 1 ) + { + if( pointAt(tStar).y > maxY ) + { + t = tStar; + } + } + + return t; + } + +/** + * yAtX + * + *

Return the set of y-coordinates corresponding to the input x-coordinate.

+ * + * @param _x:Number x-coordinate at which the desired y-coordinates are desired + * + * @return Array set of (t,y)-coordinates at the input x-coordinate provided that the x-coordinate is inside the range + * covered by the quadratic Bezier in [0,1]; that is there must exist t in [0,1] such that Bx(t) = _x. If the input + * x-coordinate is not inside the range covered by the Bezier curve, the returned array is empty. Otherwise, the + * array contains either one or two y-coordinates. There are issues with curves that are exactly or nearly (for + * numerical purposes) vertical in which there could theoretically be an infinite number of y-coordinates for a single + * x-coordinate. This method does not work in such cases, although compensation might be added in the future. + * + *

Each array element is a reference to an Object whose 't' parameter represents the Bezier t parameter. The + * Object 'y' property is the corresponding y-value. The returned (t,y) coordinates may be used by the caller + * to determine which of two returned y-coordinates might be preferred over the other.

+ * + */ + public function yAtX(_x:Number):Array + { + if( isNaN(_x) ) + { + return []; + } + + // check bounds + var xMax:Number = pointAt(tAtMaxX()).x; + var xMin:Number = pointAt(tAtMinX()).x; + + if( _x < xMin || _x > xMax ) + { + return []; + } + + // the necessary y-coordinates are the intersection of the curve with the line x = _x. The curve is generated in the + // form c0 + c1*t + c2*t^2, so the intersection satisfies the equation Bx(t) = _x or Bx(t) - _x = 0, or c0x-_x + c1x*t + c2x*t^2 = 0, + // which is quadratic in t. I wonder what formula can be used to solve that ???? + getBezierCoef(); + + // this is written out in individual steps for clarity + var c:Number = _c0X - _x; + var b:Number = _c1X; + var a:Number = _c2X; + + var d:Number = b*b - 4*a*c; + if( d < 0 ) + { + return []; + } + + d = Math.sqrt(d); + a = 1/(a + a); + var t0:Number = (d-b)*a; + var t1:Number = (-b-d)*a; + + var result:Array = new Array(); + if( t0 <= 1 ) + result.push( {t:t0, y:pointAt(t0).y} ); + + if( t1 >= 0 && t1 <=1 ) + result.push( {t:t1, y:pointAt(t1).y} ); + + return result; + } + +/** + * xAtY + * + *

Return the set of x-coordinates corresponding to the input y-coordinate.

+ * + * @param _y:Number y-coordinate at which the desired x-coordinates are desired + * + * @return Array set of (t,x)-coordinates at the input y-coordinate provided that the y-coordinate is inside the range + * covered by the quadratic Bezier in [0,1]; that is there must exist t in [0,1] such that By(t) = _y. If the input + * y-coordinate is not inside the range covered by the Bezier curve, the returned array is empty. Otherwise, the + * array contains either one or two x-coordinates. There are issues with curves that are exactly or nearly (for + * numerical purposes) horizontal in which there could theoretically be an infinite number of x-coordinates for a single + * y-coordinate. This method does not work in such cases, although compensation might be added in the future. + * + *

Each array element is a reference to an Object whose 't' parameter represents the Bezier t parameter. The + * Object 'x' property is the corresponding x-coordinate. The returned (t,x) coordinates may be used by the caller + * to determine which of two returned x-coordinates might be preferred over the other.

+ * + */ + public function xAtY(_y:Number):Array + { + if( isNaN(_y) ) + { + return []; + } + + // check bounds + var yMax:Number = pointAt(tAtMaxY()).y; + var yMin:Number = pointAt(tAtMinY()).y; + + if( _y < yMin || _y > yMax ) + { + return []; + } + + // the necessary x-coordinates are the intersection of the curve with the horizontal line y = _y. The curve is generated in the + // form c0 + c1*t + c2*t^2, so the intersection satisfies the equation By(t) = _y or By(t) - _y = 0, or c0y-_y + c1y*t + c2y*t^2 = 0, + // which is quadratic in t. I wonder what formula can be used to solve that ???? + getBezierCoef(); + + // this is written out in individual steps for clarity + var c:Number = _c0Y - _y; + var b:Number = _c1Y; + var a:Number = _c2Y; + + var d:Number = b*b - 4*a*c; + if( d < 0 ) + { + return []; + } + + d = Math.sqrt(d); + a = 1/(a + a); + var t0:Number = (d-b)*a; + var t1:Number = (-b-d)*a; + + var result:Array = new Array(); + if( t0 <= 1 ) + result.push( {t:t0, x:pointAt(t0).x} ); + + if( t1 >= 0 && t1 <=1 ) + result.push( {t:t1, x:pointAt(t1).x} ); + + return result; + } + + +/** +* join +* +*

Given the current AdvancedQuadraticBezier and an arbitrary point, return a new AdvancedQuadraticBezier instance so that the new quadratic +* Bezier interpolates the input point and matches tangent with the current quadratic Bezier at its origin. In other words, given the current (x0,y0), (cx,cy), and (x1,y1), +* return a new AdvancedQuadraticBezier with parameters (w0,u0), (zx,zy), and (w1,u1) so that w0 = x1, u0 = y1, and the segment from (cx,cy) to (x1,y1) and +* (w0,u0) to (zx,zy) have the same slope. This is one prelude to a more general quadratic spline.

+* +* @param _x:Number x-coordinate of final interpolation point of output quadratic Bezier. +* @param _y:Number y-coordinate of final interpolation point of output quadratic Bezier. +* @param _tension:Number - tension parameter between 0 and 1. 0 produces a near linear curve while 1 produces a curve whose control point is the projection of (_x,_y), +* onto the vector from (cx,cy) to (x1,y1), unless that point is less than one third the distance from (cx,cy) to (x1,y1). This may be broken into two parameters in +* the future for more control. Values outside the interval [0,1] are clipped to either 0 or 1. +* +* @return AdvancedQuadraticBezier reference to quadratic Bezier that can be considered an 'add on' curve to the current quadratic bezier with matching +* tangents at the join (x1,y1). +* +*/ + public function join(_x:Number, _y:Number, _tension:Number=1):AdvancedQuadraticBezier + { + // tension parameter not yet implemented + var deltaX:Number = x1 - cx; + var deltaY:Number = y1 - cy; + var m1:Number = 0; + var m2:Number = 0; + var pX:Number = 0; + var pY:Number = 0; + var tension:Number = Math.max(_tension,0); + tension = Math.min(tension,1); + + // get the t-parameter corresponding to projecting the vector from (_x,_y) to (px,py) onto the vector from (cx,cy) to (x1,y1). This determines the 'direction' + // of the projection point, which we want to test vs. the tension parameter to avoid kinking. + var t:Number = ((_x-cx)*(x1-cx) + (_y-cy)*(y1-cy)) / (deltaX*deltaX + deltaY*deltaY); + var target:Number = 1 + tension/3; + t = t < target ? target : 1 + tension*(t-1); + + pX = cx + t*deltaX; + pY = cy + t*deltaY; + + return new AdvancedQuadraticBezier(x1,y1,pX,pY,_x,_y); + } + + // recompute polynomial coefficients + private function getBezierCoef():void + { + _c0X = x0; + _c0Y = y0; + + _c1X = 2.0*(cx-x0); + _c1Y = 2.0*(cy-y0); + + _c2X = x0-2.0*cx+x1; + _c2Y = y0-2.0*cy+y1; + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/AdvancedRectangle.as b/Degrafa/com/degrafa/geometry/AdvancedRectangle.as new file mode 100644 index 0000000..39cbb45 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/AdvancedRectangle.as @@ -0,0 +1,342 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry{ + + import com.degrafa.IGeometry; + import com.degrafa.geometry.layout.LayoutConstraint; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + import mx.graphics.IFill; + import mx.graphics.IStroke; + import mx.graphics.SolidColor; + import mx.graphics.Stroke; + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("AdvancedRectangle.png")] + + /** + * Used by the CSSSkin for graphics rendering. + */ + public class AdvancedRectangle extends Geometry implements IGeometry{ + + public var backgroundFill:IFill; + + public var leftWidth:Number; + public var topWidth:Number; + public var rightWidth:Number; + public var bottomWidth:Number; + + public var leftFill:IFill; + public var topFill:IFill; + public var rightFill:IFill; + public var bottomFill:IFill; + + public var topLeftRadiusX:Number; + public var topLeftRadiusY:Number; + public var topRightRadiusX:Number; + public var topRightRadiusY:Number; + public var bottomLeftRadiusX:Number; + public var bottomLeftRadiusY:Number; + public var bottomRightRadiusX:Number; + public var bottomRightRadiusY:Number; + + public var topLeftFill:IFill; + public var topRightFill:IFill; + public var bottomLeftFill:IFill; + public var bottomRightFill:IFill; + + public function AdvancedRectangle(){ + super(); + } + + //Layout is Not yet implemented here so override to avoid mass havoc + override public function get layoutConstraint():LayoutConstraint{return null} + override public function set layoutConstraint(value:LayoutConstraint):void{} + override public function get layoutRectangle():Rectangle{return null} + + override public function set width(value:Number):void{} + override public function set percentWidth(value:Number):void{} + override public function set maxWidth(value:Number):void{} + override public function set minWidth(value:Number):void{} + override public function set height(value:Number):void{} + override public function set percentHeight(value:Number):void{} + override public function set maxHeight(value:Number):void{} + override public function set minHeight(value:Number):void{} + override public function set x(value:Number):void{} + override public function set maxX(value:Number):void{} + override public function set minX(value:Number):void{} + override public function set y(value:Number):void{} + override public function set maxY(value:Number):void{} + override public function set minY(value:Number):void{} + override public function set horizontalCenter(value:Number):void{} + override public function set verticalCenter(value:Number):void{} + override public function set top(value:Number):void{} + override public function set bottom(value:Number):void{} + override public function set left(value:Number):void{} + override public function set right(value:Number):void{} + override public function set maintainAspectRatio(value:Boolean):void{} + + override public function draw(graphics:Graphics, rc:Rectangle):void { + + // test for simpler drawing methods + var isRoundRectComplex:Boolean = topWidth == rightWidth == bottomWidth == leftWidth && topLeftRadiusX == topLeftRadiusY && topRightRadiusX == topRightRadiusY && bottomLeftRadiusX == bottomLeftRadiusY && bottomRightRadiusX == bottomRightRadiusY && isEquivalentSolidFill([topFill,rightFill,bottomFill,leftFill]); + var isRoundRect:Boolean = isRoundRectComplex && topLeftRadiusX == topRightRadiusX == bottomLeftRadiusX == bottomRightRadiusX; + var isRect:Boolean = isRoundRect && topLeftRadiusX == 0; + + if(isRect || isRoundRect || isRoundRectComplex) { + var stroke:IStroke = convertSolidColorToStroke(topFill as SolidColor, topWidth); + stroke.apply(graphics); + backgroundFill.begin(graphics, rc); + if(isRect) { + graphics.drawRect(rc.x, rc.y, rc.width, rc.height); + } else if(isRoundRect) { + graphics.drawRoundRect(rc.x, rc.y, rc.width, rc.height, topLeftRadiusX, topLeftRadiusY); + } else if(isRoundRectComplex) { + graphics.drawRoundRectComplex(rc.x, rc.y, rc.width, rc.height, topLeftRadiusX, topRightRadiusX, bottomLeftRadiusX, bottomRightRadiusX); + } + backgroundFill.end(graphics); + } else { + drawLeftBorder(graphics, rc); + drawTopLeftRadius(graphics, rc); + drawTopBorder(graphics, rc); + drawTopRightRadius(graphics, rc); + drawRightBorder(graphics, rc); + drawBottomLeftRadius(graphics, rc); + drawBottomBorder(graphics, rc); + drawBottomRightRadius(graphics, rc); + drawBackground(graphics, rc); + } + } + + //************************************************************************** + // Drawing Functions + //************************************************************************** + + private function drawLeftBorder(graphics:Graphics, rectangle:Rectangle):void { + if(leftFill != null) { + var rc:Rectangle = new Rectangle(0, topLeftRadiusY, leftWidth, rectangle.height - topLeftRadiusY - bottomLeftRadiusY); + graphics.lineStyle(0, 0, 0); + leftFill.begin(graphics, rc); + graphics.moveTo(0, topLeftRadiusY); // top outside + graphics.lineTo(leftWidth, Math.max(topLeftRadiusY, topWidth)); // top inside + graphics.lineTo(leftWidth, rectangle.height - Math.max(bottomLeftRadiusY, bottomWidth)); // bottom inside + graphics.lineTo(0, rectangle.height - bottomLeftRadiusY); // bottom outside + graphics.lineTo(0, topLeftRadiusY); // top outside + leftFill.end(graphics); + } + } + + private function drawTopBorder(graphics:Graphics, rectangle:Rectangle):void { + if(topFill != null) { + var rc:Rectangle = new Rectangle(topLeftRadiusX, 0, rectangle.width - topLeftRadiusX - topRightRadiusX, topWidth); + graphics.lineStyle(0, 0, 0); + topFill.begin(graphics, rc); + graphics.moveTo(topLeftRadiusX, 0); + graphics.lineTo(rectangle.width - topRightRadiusX, 0); + graphics.lineTo(rectangle.width - Math.max(topRightRadiusX, rightWidth), topWidth); + graphics.lineTo(Math.max(topLeftRadiusX, leftWidth), topWidth); + graphics.lineTo(topLeftRadiusX, 0); + topFill.end(graphics); + } + } + + private function drawRightBorder(graphics:Graphics, rectangle:Rectangle):void { + if(rightFill != null) { + var rc:Rectangle = new Rectangle(0, topRightRadiusY, rightWidth, rectangle.height - topRightRadiusY - bottomRightRadiusY); + graphics.lineStyle(0, 0, 0); + rightFill.begin(graphics, rc); + graphics.moveTo(rectangle.width, Math.max(topRightRadiusY, topWidth)); // top outside + graphics.lineTo(rectangle.width, rectangle.height - bottomRightRadiusY); // bottom outside + graphics.lineTo(rectangle.width - rightWidth, rectangle.height - Math.max(bottomRightRadiusY, bottomWidth)); + graphics.lineTo(rectangle.width - rightWidth, Math.max(topRightRadiusY, topWidth)); + graphics.lineTo(rectangle.width, topRightRadiusY); // top outside + rightFill.end(graphics); + } + } + + private function drawBottomBorder(graphics:Graphics, rectangle:Rectangle):void { + if(bottomFill != null) { + var rc:Rectangle = new Rectangle(bottomLeftRadiusX, 0, rectangle.width - bottomLeftRadiusX - bottomRightRadiusX, bottomWidth); + graphics.lineStyle(0, 0, 0); + bottomFill.begin(graphics, rc); + graphics.moveTo(Math.max(bottomLeftRadiusX, leftWidth), rectangle.height - bottomWidth); // left inside + graphics.lineTo(rectangle.width - Math.max(bottomRightRadiusX, rightWidth), rectangle.height - bottomWidth); // right inside + graphics.lineTo(rectangle.width - bottomRightRadiusX, rectangle.height); // right outside + graphics.lineTo(bottomLeftRadiusX, rectangle.height); // left outside + graphics.lineTo(Math.max(bottomLeftRadiusX, leftWidth), rectangle.height - bottomWidth); // left inside + graphics.endFill(); + bottomFill.end(graphics); + } + } + + private function drawTopLeftRadius(graphics:Graphics, rc:Rectangle):void { + // draw top left curve + if(topLeftRadiusX > 0){ + topLeftFill.begin(graphics, rc); + graphics.moveTo(0, topLeftRadiusY); + graphics.curveTo(0, 0, topLeftRadiusX, 0); + graphics.lineTo(Math.max(topLeftRadiusX, leftWidth), topWidth); + graphics.curveTo(leftWidth, topWidth, leftWidth, Math.max(topLeftRadiusY, topWidth)); + graphics.lineTo(0, topLeftRadiusY); + topLeftFill.end(graphics); + + } + } + + private function drawTopRightRadius(graphics:Graphics, rc:Rectangle):void { + // draw top right curve + if(topRightRadiusX > 0){ + var trc:Rectangle = new Rectangle(rc.width - Math.max(rightWidth, topRightRadiusX), 0, Math.max(topRightRadiusX, rightWidth), Math.max(topRightRadiusY, topWidth)); + topRightFill.begin(graphics, trc); + graphics.moveTo(rc.width - topRightRadiusX, 0); + graphics.curveTo(rc.width, 0, rc.width, topRightRadiusY); + graphics.lineTo(rc.width - rightWidth, Math.max(topRightRadiusY, topWidth)); + graphics.curveTo(rc.width - rightWidth, topWidth, rc.width - Math.max(topRightRadiusX, rightWidth), topWidth); + graphics.lineTo(rc.width - topRightRadiusX, 0); + topRightFill.end(graphics); + } + } + + private function drawBottomLeftRadius(graphics:Graphics, rc:Rectangle):void { + // draw bottom left curve + if(bottomLeftRadiusX > 0){ + var brc:Rectangle = new Rectangle(0, rc.height - Math.max(bottomWidth, bottomLeftRadiusY), Math.max(leftWidth, bottomLeftRadiusX), Math.max(bottomWidth, bottomLeftRadiusY)); + bottomLeftFill.begin(graphics, brc); + graphics.moveTo(bottomLeftRadiusX, rc.height); + graphics.curveTo(0, rc.height, 0, rc.height - bottomLeftRadiusY); + graphics.lineTo(leftWidth, Math.min(rc.height - bottomLeftRadiusY, rc.height - bottomWidth)); + graphics.curveTo(leftWidth, rc.height - bottomWidth, Math.max(bottomLeftRadiusX, leftWidth), rc.height - bottomWidth); + graphics.lineTo(bottomLeftRadiusX, rc.height); + bottomLeftFill.end(graphics); + } + } + + private function drawBottomRightRadius(graphics:Graphics, rc:Rectangle):void { + // draw bottom right curve + if(bottomRightRadiusX > 0){ + bottomRightFill.begin(graphics, rc); + graphics.moveTo(rc.width - bottomRightRadiusX , rc.height); + graphics.curveTo(rc.width, rc.height, rc.width, rc.height - bottomRightRadiusY); + graphics.lineTo(rc.width - rightWidth, Math.min(rc.height - bottomRightRadiusY, rc.height - bottomWidth)); + graphics.curveTo(rc.width - rightWidth, rc.height - bottomWidth, Math.min(rc.width - bottomRightRadiusX, rc.width - rightWidth), rc.height - bottomWidth); + graphics.moveTo(rc.width - bottomRightRadiusX , rc.height); + bottomRightFill.end(graphics); + } + } + + private function drawBackground(graphics : Graphics, rc : Rectangle) : void + { + var brc : Rectangle = new Rectangle(rc.x + leftWidth, + rc.y + topWidth, + rc.width - (leftWidth + rightWidth), + rc.height - (topWidth + bottomWidth)); + + // draw background + backgroundFill.begin(graphics, brc); + // todo: lots more optimizing =D + + //Calculate inner radii + var tl_x : Number = Math.max(topLeftRadiusX - leftWidth, 0); + var tl_y : Number = Math.max(topLeftRadiusY - topWidth, 0); + + var tr_x : Number = Math.max(topRightRadiusX - rightWidth, 0); + var tr_y : Number = Math.max(topRightRadiusY - topWidth, 0); + + var br_x : Number = Math.max(bottomRightRadiusX - rightWidth, 0); + var br_y : Number = Math.max(bottomRightRadiusY - bottomWidth, 0); + + var bl_x : Number = Math.max(bottomLeftRadiusX - leftWidth, 0); + var bl_y : Number = Math.max(bottomLeftRadiusY - bottomWidth, 0); + + //Calculate inner pixel positions + var innerTop : Number = topWidth; + var innerRight : Number = rc.width - rightWidth; + var innerBottom : Number = rc.height - bottomWidth; + var innerLeft : Number = leftWidth; + + graphics.moveTo(innerLeft + tl_x, innerTop); + + //Top + graphics.lineTo(innerRight - tr_x, innerTop); + + //Top Right + graphics.curveTo(innerRight, innerTop, innerRight, innerTop + tr_y); + + //Right + graphics.lineTo(innerRight, innerBottom - br_y); + + //Bottom right + graphics.curveTo(innerRight, innerBottom, innerRight - br_x, innerBottom); + + //Bottom + graphics.lineTo(innerLeft + bl_x, innerBottom); + + //Bottom left + graphics.curveTo(innerLeft, innerBottom, innerLeft, innerBottom - bl_y); + + //Left + graphics.lineTo(innerLeft, innerTop + tl_y); + + //Top Left + graphics.curveTo(innerLeft, innerTop, innerLeft + tl_x, innerTop); + + backgroundFill.end(graphics); + } + + + //************************************************************ + // Utility Functions + //************************************************************ + + private function convertSolidColorToStroke(fill:SolidColor, weight:Number):IStroke { + if(fill != null) { + return new Stroke(fill.color, weight, fill.alpha); + } else { + return new Stroke(0, 0, 0); + } + } + + private function isEquivalentSolidFill(fills:Array):Boolean { + var temp:SolidColor; + for each(var fill:IFill in fills) { + if(fill is SolidColor || fill == null) { + if(temp != null) { + var solid:SolidColor = fill as SolidColor; + if(solid.color != temp.color || solid.alpha == temp.alpha) { + return false; + } + } + temp = fill as SolidColor; + } else { + return false; + } + } + return true; + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/AdvancedRectangle.png b/Degrafa/com/degrafa/geometry/AdvancedRectangle.png new file mode 100644 index 0000000..e912443 Binary files /dev/null and b/Degrafa/com/degrafa/geometry/AdvancedRectangle.png differ diff --git a/Degrafa/com/degrafa/geometry/Circle.as b/Degrafa/com/degrafa/geometry/Circle.as new file mode 100644 index 0000000..9603439 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/Circle.as @@ -0,0 +1,284 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry{ + + import com.degrafa.IGeometry; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("Circle.png")] + + [Bindable] + /** + * The Circle element draws a circle using the specified center point + * and radius. + * + * @see http://samples.degrafa.com/Circle/Circle.html + * + **/ + public class Circle extends Geometry implements IGeometry{ + + /** + * Constructor. + * + *

The circle constructor accepts 3 optional arguments that define it's + * center point and radius.

+ * + * @param centerX A number indicating the center x-axis coordinate. + * @param centerY A number indicating the center y-axis coordinate. + * @param radius A number indicating the radius of the circle. + */ + public function Circle(centerX:Number=NaN,centerY:Number=NaN,radius:Number=NaN){ + super(); + + if (centerX) this.centerX=centerX; + if (centerY) this.centerY=centerY; + if (radius) this.radius=radius; + + } + + /** + * Circle short hand data value. + * + *

The circle data property expects exactly 3 values centerX, + * centerY and radius separated by spaces.

+ * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + + //parse the string + var tempArray:Array = value.split(" "); + + if (tempArray.length == 3) + { + super.data = value; + _centerX= tempArray[0]; + _centerY= tempArray[1]; + _radius = tempArray[2]; + invalidated = true; + } + } + } + + + private var _centerX:Number; + /** + * The x-axis coordinate of the center of the circle. If not specified + * a default value of 0 is used. + **/ + public function get centerX():Number{ + if(!_centerX){return (hasLayout)? 0.5:0;} + return _centerX; + } + public function set centerX(value:Number):void{ + if (_centerX != value) { + _centerX = value; + invalidated = true; + } + } + + private var _centerY:Number; + /** + * The y-axis coordinate of the center of the circle. If not specified + * a default value of 0 is used. + **/ + public function get centerY():Number{ + if(!_centerY){return (hasLayout)? 0.5:0;} + return _centerY; + } + public function set centerY(value:Number):void{ + if(_centerY != value){ + _centerY = value; + invalidated = true; + } + + } + + private var _radius:Number; + /** + * The radius of the circle. If not specified a default value of 0 + * is used. + **/ + public function get radius():Number{ + if(!_radius){return (hasLayout)? .5:0;} + return _radius; + } + public function set radius(value:Number):void{ + if(_radius != value){ + _radius = value; + if (hasLayout)super.width= super.height = value*2; + invalidated = true; + } + } + + private var _accuracy:Number; + /** + * The accuracy of the circle. If not specified a default value of 8 + * is used. + **/ + public function get accuracy():Number{ + if(!_accuracy){return 8;} + return _accuracy; + } + public function set accuracy(value:Number):void{ + if(_accuracy != value){ + _accuracy = value; + invalidated = true; + } + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + if(invalidated){ + + commandStack.source.length = 0; + + var span:Number = Math.PI/accuracy; + var controlRadius:Number = radius/Math.cos(span); + var anchorAngle:Number=0 + var controlAngle:Number=0; + + //add the move to the command stack + commandStack.addMoveTo( + centerX+Math.cos(anchorAngle)*radius, + centerY+Math.sin(anchorAngle)*radius); + + var i:int=0; + + //loop through and add the curve commands + for (i; iThe cubic Bézier constructor accepts 8 optional arguments that define it's + * start, end and controls points.

+ * + * @param x0 A number indicating the starting x-axis coordinate. + * @param y0 A number indicating the starting y-axis coordinate. + * @param cx A number indicating the first control x-axis coordinate. + * @param cy A number indicating the first control y-axis coordinate. + * @param cx1 A number indicating the second control x-axis coordinate. + * @param cy1 A number indicating the second control y-axis coordinate. + * @param x1 A number indicating the ending x-axis coordinate. + * @param y1 A number indicating the ending y-axis coordinate. + */ + public function CubicBezier(x0:Number=NaN,y0:Number=NaN,cx:Number=NaN,cy:Number=NaN,cx1:Number=NaN,cy1:Number=NaN,x1:Number=NaN,y1:Number=NaN){ + super(); + + this.x0=x0; + this.y0=y0; + this.cx=cx; + this.cy=cy; + this.cx1=cx1; + this.cy1=cy1; + this.x1=x1; + this.y1=y1; + + + } + + /** + * CubicBezier short hand data value. + * + *

The cubic Bézier data property expects exactly 8 values x0, + * y0, cx, cy, cx1, cy1, x1 and y1 separated by spaces.

+ * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + super.data = value; + + //parse the string on the space + var tempArray:Array = value.split(" "); + + if (tempArray.length == 8){ + _x0=tempArray[0]; + _y0=tempArray[1]; + _cx=tempArray[2]; + _cy=tempArray[3]; + _cx1=tempArray[4]; + _cy1=tempArray[5]; + _x1=tempArray[6]; + _y1=tempArray[7]; + } + + invalidated = true; + } + } + + + private var _x0:Number; + /** + * The x0-coordinate of the start point of the curve. If not specified + * a default value of 0 is used. + **/ + public function get x0():Number{ + if(isNaN(_x0)){return 0;} + + return _x0; + } + public function set x0(value:Number):void{ + if(_x0 != value){ + _x0 = value; + invalidated = true; + } + } + + + private var _y0:Number; + /** + * The y0-coordinate of the start point of the curve. If not specified + * a default value of 0 is used. + **/ + public function get y0():Number{ + if(isNaN(_y0)){return (hasLayout)? 2:0;} + return _y0; + } + public function set y0(value:Number):void{ + if(_y0 != value){ + _y0 = value; + invalidated = true; + } + } + + + private var _x1:Number; + /** + * The x-coordinate of the end point of the curve. If not specified + * a default value of 0 is used. + **/ + public function get x1():Number{ + if(isNaN(_x1)){return (hasLayout)? 2:0;} + return _x1; + } + public function set x1(value:Number):void{ + if(_x1 != value){ + _x1 = value; + invalidated = true; + } + + } + + + private var _y1:Number; + /** + * The y-coordinate of the end point of the curve. If not specified + * a default value of 0 is used. + **/ + public function get y1():Number{ + if(isNaN(_y1)){return (hasLayout)? 2:0;} + return _y1; + } + public function set y1(value:Number):void{ + if(_y1 != value){ + _y1 = value; + invalidated = true; + } + } + + + private var _cx:Number; + /** + * The x-coordinate of the first control point of the curve. If not specified + * a default value of 0 or cx1 if specified is used. + **/ + public function get cx():Number{ + if(isNaN(_cx)){return (hasLayout)? 1:0;} + return _cx; + } + public function set cx(value:Number):void{ + if(_cx != value){ + _cx = value; + invalidated = true; + } + } + + + private var _cy:Number; + /** + * The y-coordinate of the first control point of the curve. If not specified + * a default value of 0 or cy1 if specified is used. + **/ + public function get cy():Number{ + if(isNaN(_cy)){return (hasLayout)? .66:0;} + return _cy; + } + public function set cy(value:Number):void{ + if(_cy != value){ + _cy = value; + invalidated = true; + } + } + + + private var _cx1:Number; + /** + * The x-coordinate of the second control point of the curve. If not specified + * a default value of 0 or cx if specified is used. + **/ + public function get cx1():Number{ + if(isNaN(_cx1)){return (hasLayout)? 1:0;} + return _cx1; + } + public function set cx1(value:Number):void{ + if(_cx1 != value){ + _cx1 = value; + invalidated = true; + } + } + + + private var _cy1:Number; + /** + * The y-coordinate of the second control point of the curve. If not specified + * a default value of 0 or cy if specified is used. + **/ + public function get cy1():Number{ + if(isNaN(_cy1)){return (hasLayout)? .66:0;} + return _cy1; + } + public function set cy1(value:Number):void{ + if(_cy1 != value){ + _cy1 = value; + invalidated = true; + } + } + + private var _close:Boolean=false; + /** + * If true draws a line from the end point to the start point to + * close this curve. + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get close():Boolean{ + return _close; + } + public function set close(value:Boolean):void{ + if(_close != value){ + _close = value; + invalidated = true; + } + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + if(invalidated){ + + commandStack.length=0; + + //add a MoveTo at the start of the commandStack + //rendering chain + commandStack.addMoveTo(x0,y0); + + commandStack.addCubicBezierTo(x0,y0,cx,cy,cx1,cy1,x1,y1,1); + + if(close){ + commandStack.addLineTo(x0,y0); + } + + invalidated = false; + } + + } + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + + if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + if(isNaN(_layoutConstraint.width)){ + tempLayoutRect.width = bounds.width; + } + + if(isNaN(_layoutConstraint.height)){ + tempLayoutRect.height = bounds.height; + } + + if(isNaN(_layoutConstraint.x)){ + tempLayoutRect.x = bounds.x; + } + + if(isNaN(_layoutConstraint.y)){ + tempLayoutRect.y = bounds.y; + } + trace(tempLayoutRect) + super.calculateLayout(tempLayoutRect); + + _layoutRectangle = _layoutConstraint.layoutRectangle; + + } + } + } + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + + //re init if required + if (invalidated) preDraw(); + + //init the layout in this case done after predraw. + if (_layoutConstraint) calculateLayout(); + + super.draw(graphics, (rc)? rc:bounds); + + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:CubicBezier):void{ + + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke} + if (!_x0){_x0 = value.x0;} + if (!_y0){_y0 = value.y0;} + if (!_x1){_x1 = value.x1;} + if (!_y1){_y1 = value.y1;} + if (!_cx){_cx = value.cx;} + if (!_cy){_cy = value.cy;} + if (!_cx1){_cx1 = value.cx1;} + if (!_cy1){_cy1 = value.cy1;} + + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/CubicBezier.png b/Degrafa/com/degrafa/geometry/CubicBezier.png new file mode 100644 index 0000000..da24508 Binary files /dev/null and b/Degrafa/com/degrafa/geometry/CubicBezier.png differ diff --git a/Degrafa/com/degrafa/geometry/Ellipse.as b/Degrafa/com/degrafa/geometry/Ellipse.as new file mode 100644 index 0000000..4b790dc --- /dev/null +++ b/Degrafa/com/degrafa/geometry/Ellipse.as @@ -0,0 +1,291 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry{ + + import com.degrafa.IGeometry; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("Ellipse.png")] + + [Bindable] + /** + * The Ellipse element draws a ellipse using the specified x,y, + * width and height. + * + * @see http://samples.degrafa.com/Ellipse/Ellipse.html + * + **/ + public class Ellipse extends Geometry implements IGeometry{ + + /** + * Constructor. + * + *

The ellipse constructor accepts 4 optional arguments that define it's + * x, y, width and height.

+ * + * @param x A number indicating the upper left x-axis coordinate. + * @param y A number indicating the upper left y-axis coordinate. + * @param width A number indicating the width. + * @param height A number indicating the height. + */ + public function Ellipse(x:Number=NaN,y:Number=NaN,width:Number=NaN,height:Number=NaN){ + super(); + if (x) this.x=x; + if (y) this.y=y; + if (width) this.width=width; + if (height) this.height=height; + } + + + /** + * Ellipse short hand data value. + * + *

The ellipse data property expects exactly 4 values x, + * y, width and height separated by spaces.

+ * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + super.data = value; + //parse the string on the space + var tempArray:Array = value.split(" "); + + if (tempArray.length == 4){ + _x=tempArray[0]; + _y=tempArray[1]; + _width=tempArray[2]; + _height=tempArray[3]; + } + + invalidated = true; + } + } + + private var _x:Number; + /** + * The x-axis coordinate of the upper left point of the ellipse. If not specified + * a default value of 0 is used. + **/ + override public function get x():Number{ + if(isNaN(_x)){return 0;} + return _x; + } + override public function set x(value:Number):void{ + if(_x != value){ + _x = value; + if (hasLayout) super.x=value + invalidated = true; + } + } + + + private var _y:Number; + /** + * The y-axis coordinate of the upper left point of the ellipse. If not specified + * a default value of 0 is used. + **/ + override public function get y():Number{ + if(isNaN(_y)){return 0;} + return _y; + } + override public function set y(value:Number):void{ + if(_y != value){ + _y = value; + if (hasLayout) super.y=value + invalidated = true; + } + } + + + private var _width:Number; + /** + * The width of the ellipse. + **/ + [PercentProxy("percentWidth")] + override public function get width():Number{ + if(isNaN(_width)){return (hasLayout)? 1:0;} + return _width; + } + override public function set width(value:Number):void{ + if(_width != value){ + _width = value; + if (hasLayout) super.width=value + invalidated = true; + } + + } + + + private var _height:Number; + /** + * The height of the ellipse. + **/ + [PercentProxy("percentHeight")] + override public function get height():Number{ + if(isNaN(_height)){return (hasLayout)? 1:0;} + return _height; + } + override public function set height(value:Number):void{ + if(_height != value){ + _height = value; + if (hasLayout) super.height=value + invalidated = true; + } + } + + private var _accuracy:Number; + /** + * The accuracy of the ellipse. If not specified a default value of 8 + * is used. + **/ + public function get accuracy():Number{ + if(!_accuracy){return 8;} + return _accuracy; + } + public function set accuracy(value:Number):void{ + if(_accuracy != value){ + _accuracy = value; + invalidated = true; + } + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + if(invalidated){ + + commandStack.length=0; + + var angleDelta:Number = Math.PI / (accuracy/2); + + var rx: Number = width/2; + var ry: Number = height/2; + var dx:Number = rx/Math.cos(angleDelta/2); + var dy:Number = ry/Math.cos(angleDelta/2); + + commandStack.addMoveTo((x+rx) + rx,(y+ry)); + + var i:Number = 0 + var angle:Number=0; + + for (i= 0; i < accuracy; i++) { + angle += angleDelta; + + commandStack.addCurveTo( + (x+rx) + Math.cos(angle-(angleDelta/2))*(dx), + (y+ry) + Math.sin(angle-(angleDelta/2))*(dy), + (x+rx) + Math.cos(angle)*rx, + (y+ry) + Math.sin(angle)*ry); + } + invalidated = false; + } + + } + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + + if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + + if(_width){ + tempLayoutRect.width = _width; + } + + if(_height){ + tempLayoutRect.height = _height; + } + + if(_x){ + tempLayoutRect.x = _x; + } + + if(_y){ + tempLayoutRect.y = _y; + } + + super.calculateLayout(tempLayoutRect); + + _layoutRectangle = _layoutConstraint.layoutRectangle; + + if (isNaN(_width) || isNaN(_height)) { + //layout defined initial state: + _width = isNaN(_width)? layoutRectangle.width:_width; + _height = isNaN(_height) ? layoutRectangle.height: _height; + invalidated = true; + } + + } + } + } + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + + //init the layout in this case done before predraw. + if (_layoutConstraint) calculateLayout(); + + //re init if required + if (invalidated) preDraw(); + + super.draw(graphics,(rc)? rc:bounds); + } + + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:Ellipse):void{ + + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke;} + if (!_x){_x = value.x;} + if (!_y){_y = value.y;} + if (!_width){_width = value.width;} + if (!_height){_height = value.height;} + if (!_accuracy){_accuracy = value.accuracy;} + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/Ellipse.png b/Degrafa/com/degrafa/geometry/Ellipse.png new file mode 100644 index 0000000..c734f3c Binary files /dev/null and b/Degrafa/com/degrafa/geometry/Ellipse.png differ diff --git a/Degrafa/com/degrafa/geometry/EllipticalArc.as b/Degrafa/com/degrafa/geometry/EllipticalArc.as new file mode 100644 index 0000000..384a49c --- /dev/null +++ b/Degrafa/com/degrafa/geometry/EllipticalArc.as @@ -0,0 +1,364 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry{ + + import com.degrafa.geometry.utilities.GeometryUtils; + import com.degrafa.IGeometry; + import com.degrafa.geometry.command.CommandStackItem; + import com.degrafa.geometry.utilities.ArcUtils; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("EllipticalArc.png")] + + [Bindable] + /** + * The EllipticalArc element draws an elliptical arc using the specified + * x, y, width, height, start angle, arc and closure type. + * + * @see http://degrafa.com/samples/EllipticalArc_Element.html + * + **/ + public class EllipticalArc extends Geometry implements IGeometry{ + + /** + * Constructor. + * + *

The elliptical arc constructor accepts 7 optional arguments that define it's + * x, y, width, height, start angle, arc and closure type.

+ * + * @param x A number indicating the upper left x-axis coordinate. + * @param y A number indicating the upper left y-axis coordinate. + * @param width A number indicating the width. + * @param height A number indicating the height. + * @param startAngle A number indicating the beginning angle of the arc. + * @param arc A number indicating the the angular extent of the arc, relative to the start angle. + * @param closureType A string indicating the method used to close the arc. + */ + public function EllipticalArc(x:Number=NaN,y:Number=NaN,width:Number=NaN, + height:Number=NaN,startAngle:Number=NaN,arc:Number=NaN,closureType:String=null){ + + super(); + if (!isNaN(x)) this.x=x; + if (!isNaN(y)) this.y=y; + if (!isNaN(width)) this.width=width; + if (!isNaN(height)) this.height=height; + if (!isNaN(startAngle)) this.startAngle=startAngle; + if (!isNaN(arc)) this.arc=arc; + if (closureType) this.closureType=closureType; + + } + + /** + * EllipticalArc short hand data value. + * + *

The elliptical arc data property expects exactly 6 values x, + * y, width, height, startAngle and arc separated by spaces.

+ * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + super.data = value; + + //parse the string on the space + var tempArray:Array = value.split(" "); + + if (tempArray.length == 6){ + _x=tempArray[0]; + _y=tempArray[1]; + _width=tempArray[2]; //radius + _height=tempArray[3]; //yRadius + _startAngle=tempArray[4]; //angle + _arc=tempArray[5]; //extent + invalidated = true; + } + + } + + } + + private var _x:Number; + /** + * The x-axis coordinate of the upper left point of the arcs enclosure. If not specified + * a default value of 0 is used. + **/ + override public function get x():Number{ + if(!_x){return 0;} + return _x; + } + override public function set x(value:Number):void{ + if(_x != value){ + _x = value; + if (hasLayout) super.x=value + invalidated = true; + } + } + + + private var _y:Number; + /** + * The y-axis coordinate of the upper left point of the arcs enclosure. If not specified + * a default value of 0 is used. + **/ + override public function get y():Number{ + if(!_y){return 0;} + return _y; + } + override public function set y(value:Number):void{ + if(_y != value){ + _y = value; + if (hasLayout) super.y=value + invalidated = true; + } + } + + + private var _startAngle:Number; + /** + * The beginning angle of the arc. If not specified + * a default value of 0 is used. + **/ + public function get startAngle():Number{ + if(!_startAngle){return 0;} + return _startAngle; + } + public function set startAngle(value:Number):void{ + if(_startAngle != value){ + _startAngle = value; + invalidated = true; + } + } + + + private var _arc:Number; + /** + * The angular extent of the arc. If not specified + * a default value of 0 is used. + **/ + public function get arc():Number{ + if(!_arc){return 0;} + return _arc; + } + public function set arc(value:Number):void{ + if(_arc != value){ + _arc = value; + invalidated = true; + } + } + + + private var _width:Number; + /** + * The width of the arc. + **/ + [PercentProxy("percentWidth")] + override public function get width():Number{ + if(isNaN(_width)){return (hasLayout)? 1:0;} + return _width; + } + override public function set width(value:Number):void{ + if(_width != value){ + _width = value; + if (hasLayout) super.width=value + invalidated = true; + } + } + + + private var _height:Number; + /** + * The height of the arc. + **/ + [PercentProxy("percentHeight")] + override public function get height():Number{ + if(isNaN(_height)){return (hasLayout)? 1:0;} + return _height; + } + override public function set height(value:Number):void{ + if(_height != value){ + _height = value; + if (hasLayout) super.height=value + invalidated = true; + } + } + + + private var _closureType:String; + [Inspectable(category="General", enumeration="open,chord,pie", defaultValue="open")] + /** + * The method in which to close the arc. + *

+ *

  • open will apply no closure.
  • + *
  • chord will close the arc with a strait line to the start.
  • + *
  • pie will draw a line from center to start and end to center forming a pie shape.
  • + *

    + **/ + public function get closureType():String{ + if(!_closureType){return "open";} + return _closureType; + } + public function set closureType(value:String):void{ + if(_closureType != value){ + _closureType = value; + invalidated = true; + } + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + if(invalidated){ + + //calculate based on startangle, radius, width, and height to get the drawing + //x and y so that our arc is always in the bounds of the rectangle. may want + //to store this local sometime + var newX:Number = (width/2) + (width/2) * + Math.cos(startAngle * (Math.PI / 180))+x; + + var newY:Number = (height/2) - (height/2) * + Math.sin(startAngle * (Math.PI / 180))+y; + + + commandStack.length=0; + + //Calculate the center point. We only needed is we have a pie type + //closeur. May want to store this local sometime + var ax:Number=newX-Math.cos(-(startAngle/180)*Math.PI)*width/2; + var ay:Number=newY-Math.sin(-(startAngle/180)*Math.PI)*height/2; + + //draw the start line in the case of a pie type + if (closureType =="pie"){ + if(Math.abs(arc)<360){ + commandStack.addMoveTo(ax,ay); + commandStack.addLineTo(newX,newY); + } + } else commandStack.addMoveTo(newX,newY); + + + + //fill the quad array with curve to segments + //which we'll use to draw and calc the bounds + ArcUtils.drawEllipticalArc(newX,newY,startAngle,arc,width/2,height/2,commandStack); + + //close the arc if required + if(Math.abs(arc)<360){ + if (closureType == "pie"){ + commandStack.addLineTo(ax,ay); + } + if(closureType == "chord"){ + commandStack.addLineTo(newX,newY); + } + } + invalidated = false; + } + } + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + + if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + + if(_width){ + tempLayoutRect.width = _width; + } + + if(_height){ + tempLayoutRect.height = _height; + } + + if(_x){ + tempLayoutRect.x = _x; + } + + if(_y){ + tempLayoutRect.y = _y; + } + + super.calculateLayout(tempLayoutRect); + + _layoutRectangle = _layoutConstraint.layoutRectangle; + if (isNaN(_width) || isNaN(_height)) { + //layout defined initial state: + _width = isNaN(_width)? layoutRectangle.width:_width; + _height = isNaN(_height) ? layoutRectangle.height: _height; + invalidated = true; + } + + } + } + } + + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + + //init the layout in this case done before predraw. + if (_layoutConstraint) calculateLayout(); + + //re init if required + if (invalidated) preDraw(); + + super.draw(graphics,(rc)? rc:bounds); + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:EllipticalArc):void{ + + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke} + if (!_x){_x = value.x;} + if (!_y){_y = value.y;} + if (!_width){_width = value.width;} + if (!_height){_height = value.height;} + if (!_startAngle){_startAngle = value.startAngle;} + if (!_arc){_arc = value.arc;} + if (!_closureType){_closureType = value.closureType;} + + + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/EllipticalArc.png b/Degrafa/com/degrafa/geometry/EllipticalArc.png new file mode 100644 index 0000000..e9ec6e9 Binary files /dev/null and b/Degrafa/com/degrafa/geometry/EllipticalArc.png differ diff --git a/Degrafa/com/degrafa/geometry/Geometry.as b/Degrafa/com/degrafa/geometry/Geometry.as new file mode 100644 index 0000000..a384fe3 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/Geometry.as @@ -0,0 +1,1787 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry{ + + import com.degrafa.IGeometryComposition; + import com.degrafa.IGraphic; + import com.degrafa.core.DegrafaObject; + import com.degrafa.core.IDegrafaObject; + import com.degrafa.core.IGraphicSkin; + import com.degrafa.core.IGraphicsFill; + import com.degrafa.core.IGraphicsStroke; + import com.degrafa.core.ITransformablePaint; + import com.degrafa.core.collections.DecoratorCollection; + import com.degrafa.core.collections.DisplayObjectCollection; + import com.degrafa.core.collections.FilterCollection; + import com.degrafa.core.collections.GeometryCollection; + import com.degrafa.events.DegrafaEvent; + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.geometry.layout.LayoutConstraint; + import com.degrafa.states.IDegrafaStateClient; + import com.degrafa.states.State; + import com.degrafa.states.StateManager; + import com.degrafa.transform.ITransform; + import com.degrafa.triggers.ITrigger; + + import flash.display.DisplayObject; + import flash.display.Graphics; + import flash.display.Sprite; + import flash.display.Stage; + import flash.events.Event; + import flash.filters.BitmapFilter; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.utils.Dictionary; + + import mx.binding.utils.ChangeWatcher; + import mx.core.IUIComponent; + + import mx.events.FlexEvent; + import mx.events.PropertyChangeEvent; + import mx.styles.ISimpleStyleClient; + + [DefaultProperty("geometry")] + [Bindable(event="propertyChange")] + + /** + * A geometry object is a type of Degrafa object that enables + * rendering to a graphics context. Degrafa provides a number of + * ready-to-use geometry objects. All geometry objects inherit + * from the Geometry class. All geometry objects have a default data + * property that can be used for short hand property setting. + **/ + public class Geometry extends DegrafaObject implements IDegrafaObject, + IGeometryComposition, IDegrafaStateClient, ISimpleStyleClient { + + private var _invalidated:Boolean; + /** + * Specifies whether this object is to be re calculated + * on the next cycle. Only property updates which affect the + * computation of this object set this property + **/ + public function get invalidated():Boolean{ + return _invalidated; + } + public function set invalidated(value:Boolean):void{ + if(_invalidated != value){ + _invalidated = value; + + if(_invalidated && _isRootGeometry){ + drawToTargets(); + } + } + } + + /** + * Returns true if this Geometry object is invalidated + **/ + public function get isInvalidated():Boolean{ + return _invalidated; + } + + private var _data:Object; + /** + * Allows a short hand property setting that is + * specific to and parsed by each geometry object. + * Look at the various geometry objects to learn what + * this setting requires. + **/ + public function get data():Object{ + return _data; + } + public function set data(value:Object):void{ + _data=value; + } + + private var _visible:Boolean=true; + /** + * Controls the visibility of this geometry object. If true, the geometry is visible. + * + * When set to false this geometry object will be pre computed nor drawn. + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get visible():Boolean{ + return _visible; + } + public function set visible(value:Boolean):void{ + if(_visible != value){ + + var oldValue:Boolean=_visible; + _visible=value; + + invalidated = true; + + //call local helper to dispatch event + initChange("visible",oldValue,_visible,this); + } + + } + + //Dev Note :: Needed to add this speacial case as the parent in + //DegrafaObject is of type IDegrafaObject for type safty. + /** + * Provides access to the IGraphic object parent in a nested situation. + * Set when this object is at the root of a Degrafa + * IGraphic object such as GeometryGroup. + **/ + private var _IGraphicParent:IGraphic; + public function get IGraphicParent():IGraphic{ + return _IGraphicParent; + } + public function set IGraphicParent(value:IGraphic):void{ + if (parent==null){ + if(_IGraphicParent != value){ + _IGraphicParent=value; + } + } + } + + + + + private var _inheritStroke:Boolean=true; + /** + * If set to true and no stroke is defined and there is a parent object + * then this object will walk up through the parents to retrive a stroke + * object. + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get inheritStroke():Boolean{ + return _inheritStroke; + } + public function set inheritStroke(value:Boolean):void{ + _inheritStroke=value; + } + + private var _inheritFill:Boolean=true; + /** + * If set to true and no fill is defined and there is a parent object + * then this object will walk up through the parents to retrive a fill + * object. + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get inheritFill():Boolean{ + return _inheritFill; + } + public function set inheritFill(value:Boolean):void{ + _inheritFill=value; + } + + + private var _scaleOnLayout:Boolean=true; + /** + * When using layout this flag will determine if you want + * Scale to be applied to fit layout rules + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get scaleOnLayout():Boolean{ + return _scaleOnLayout; + } + public function set scaleOnLayout(value:Boolean):void{ + _scaleOnLayout=value; + } + + private var _autoClearGraphicsTarget:Boolean=true; + /** + * When using a graphicsTarget and if this property is set to true + * the draw phase will clear the graphics context before drawing. + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get autoClearGraphicsTarget():Boolean{ + return _autoClearGraphicsTarget; + } + public function set autoClearGraphicsTarget(value:Boolean):void{ + _autoClearGraphicsTarget=value; + } + + private var _graphicsTarget:DisplayObjectCollection; + [Inspectable(category="General", arrayType="flash.display.DisplayObject")] + [ArrayElementType("flash.display.DisplayObject")] + /** + * One or more display object's that this Geometry is to be drawn to. + * During the drawing phase this is tested first. If items have been defined + * the drawing of the geometry is done on each item(s) graphics context. + **/ + public function get graphicsTarget():Array{ + initGraphicsTargetCollection(); + return _graphicsTarget.items; + } + public function set graphicsTarget(value:Array):void{ + + if(!value){return;} + + var item:Object; + for each (item in value){ + if (!item){return;} + } + + //make sure we don't set anything until all target creation is + //complete otherwise we will be getting null items since flex + //has not finished creation of the target items. + initGraphicsTargetCollection(); + _graphicsTarget.items = value; + + var displayObject:DisplayObject; + for each (displayObject in _graphicsTarget.items ){ + //for now this process does not include skins + //to be investigated post b3. + if(!(displayObject is IGraphicSkin)){ + //only need to call on first render of each target + //dev note :: does not support runtime addition of targets. To Investigate. + displayObject.addEventListener(Event.RENDER,onTargetRender); + + //required for stand alone player. + displayObject.addEventListener(Event.ADDED_TO_STAGE,onTargetRender); + + if(displayObject is IUIComponent){ + displayObject.addEventListener(FlexEvent.UPDATE_COMPLETE,onTargetRender); + } + } + } + _isRootGeometry = true; + + + } + + //Method Queue Work + + /** + * NOTE :: All this code can be moved into the DisplayObjectCollection post b3. + * This way only targets that have changed are drawn to. + **/ + private function onTargetRender(event:Event):void{ + + //update local stage property + if(!_stage){ + _stage = event.currentTarget.stage; + } + + if(_stage){ + + //remove the event listeners no longer needed + event.currentTarget.removeEventListener(Event.RENDER,onTargetRender); + event.currentTarget.removeEventListener(Event.ADDED_TO_STAGE,onTargetRender); + + //we may want to do this only for displayobject containers + if(event.currentTarget is IUIComponent){ + event.currentTarget.removeEventListener(FlexEvent.UPDATE_COMPLETE,onTargetRender); + } + + } + else{ + return; + } + + //and setup the layoutChange watcher for the target + + //Only do this for IContainer. Requires extensive testing. + if(event.currentTarget is IUIComponent){ + initLayoutChangeWatcher(event.currentTarget as IUIComponent); + } + + //init the draw que + initDrawQueue(); + + //make sure we have not missed one draw cycle chance. + + //add a draw to the que. + queueDraw(event.currentTarget,event.currentTarget.graphics,null) + + } + + //target stage refference + private var _stage:Stage; + + private var methodQueue:Array=[]; + private function initDrawQueue():void{ + //add listener to frame change. + _stage.addEventListener(Event.ENTER_FRAME, processMethodQueue); + } + + //adds a draw to the queue + private function queueDraw(...args):void{ + + //make sure we are not already queued up to draw + //to that target otherwise add it + + //DEV note: could improve perf here + for each(var item:Object in methodQueue){ + //we only want to add it one time + if(item.args[0] == args[0]){ + return; + } + } + + methodQueue.push({method:drawToTarget, args:args}); + + if(_stage){ + _stage.addEventListener(Event.ENTER_FRAME, processMethodQueue,false,0,true); + } + else{ + //could have been added runtime + if (graphicsTarget.length) { + for each(var dispObj:DisplayObject in graphicsTarget){ + if(dispObj.stage){ + _stage = dispObj.stage; + _stage.addEventListener(Event.ENTER_FRAME, processMethodQueue, false, 0, true); + break; + } + } + } + } + } + + + private function processMethodQueue(event:Event):void{ + + if(methodQueue.length == 0){return;} + + //trace("Queue LENGTH :::" + methodQue.length) + + // make local copy so that new calls get through + var queue:Array = methodQueue; + //methodQueue = []; + + var len:int = queue.length; + for (var i:int=0;i 0) { + cmd.type=CommandStackItem.LINE_TO; + commandStack.addItem(cmd); + } + i++; + } + + } + this.commandStack.invalidated=true; + var t:Rectangle=this.commandStack.bounds; //Force calc on bounds + + } + + + //may need to do stuff here for the fill side + override public function initFill(graphics:Graphics,rc:Rectangle):void{ + if (_fill) + { + //we can't pass a reference to the requesting Geometry in the method signature with IFill - its required for transform inheritance by some fills + if (_fill is ITransformablePaint) (_fill as ITransformablePaint).requester = this; + _fill.begin(graphics, (rc) ? rc:null); + CommandStack.currentFill = _fill; + } else + CommandStack.currentFill = null; + } + + //override for now + override public function draw(graphics:Graphics, rc:Rectangle):void{ + //re init if required + calculateLayout(); + preDraw(); + super.draw(graphics, (rc)? rc:bounds); + + + } + + + override public function endDraw(graphics:Graphics):void { + if (fill) { + fill.end(graphics); + } + //append a null moveTo following a stroke without a fill + //forces a break in continuity with moveTo before the next + //path - if we have the last point coords we could use them + //instead of null, null or perhaps any value + if (stroke && !fill) graphics.moveTo.call(graphics, null, null); + + + dispatchEvent(new DegrafaEvent(DegrafaEvent.RENDER)); + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/HorizontalLine.as b/Degrafa/com/degrafa/geometry/HorizontalLine.as new file mode 100644 index 0000000..7b14328 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/HorizontalLine.as @@ -0,0 +1,242 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry{ + + import com.degrafa.IGeometry; + import com.degrafa.core.IGraphicsFill; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + //excluded here + [Exclude(name="fill", kind="property")] + [Exclude(name="height", kind="property")] + [Exclude(name="percentHeight", kind="property")] + [Exclude(name="maxHeight", kind="property")] + [Exclude(name="minHeight", kind="property")] + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("HorizontalLine.png")] + + [Bindable] + /** + * The HorizontalLine element draws a horizontal line using the specified x, y, + * and x1 coordinate values. + * + * @see http://samples.degrafa.com/HorizontalLine/HorizontalLine.html + * + **/ + public class HorizontalLine extends Geometry implements IGeometry{ + + /** + * Constructor. + * + *

    The horizontal line constructor accepts 3 optional arguments that define it's + * center point and radius.

    + * + * @param x A number indicating the starting x-axis coordinate. + * @param y A number indicating the starting y-axis coordinate. + * @param x1 A number indicating the ending x-axis coordinate. + */ + public function HorizontalLine(x:Number=NaN,y:Number=NaN,x1:Number=NaN){ + super(); + + if (x) this.x=x; + if (y) this.y=y; + if (x1) this.x1=x1; + + + } + + //excluded here + override public function set fill(value:IGraphicsFill):void{} + override public function set height(value:Number):void{} + override public function set percentHeight(value:Number):void{} + override public function set maxHeight(value:Number):void{} + override public function set minHeight(value:Number):void{} + + /** + * HorizontalLine short hand data value. + * + *

    The horizontal line data property expects exactly 3 values x, + * y and x1 separated by spaces.

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + super.data = value; + + //parse the string on the space + var tempArray:Array = value.split(" "); + + if (tempArray.length == 3){ + _x=tempArray[0]; + _y=tempArray[1]; + _x1 = tempArray[2]; + invalidated = true; + } + + + } + + } + + + private var _x:Number; + /** + * The x-coordinate of the start point of the line. If not specified + * a default value of 0 is used. + **/ + override public function get x():Number{ + if(!_x){return 0;} + return _x; + } + override public function set x(value:Number):void{ + if(_x != value){ + _x = value; + invalidated = true; + } + } + + + private var _y:Number; + /** + * The y-coordinate of the start point of the line. If not specified + * a default value of 0 is used. + **/ + override public function get y():Number{ + if(!_y){return 0;} + return _y; + } + override public function set y(value:Number):void{ + if(_y != value){ + _y = value; + invalidated = true; + } + } + + + private var _x1:Number; + /** + * The x-coordinate of the end point of the line. If not specified + * a default value of 0 is used. + **/ + public function get x1():Number{ + if(isNaN(_x1)){return (hasLayout)? 0.0001:0;} + return _x1; + } + public function set x1(value:Number):void{ + if(_x1 != value){ + _x1 = value; + invalidated = true; + } + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + if(invalidated){ + commandStack.length=0; + commandStack.addMoveTo(x,y); + commandStack.addLineTo(x1,y); + invalidated = false; + } + } + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + + if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + + var tempLayoutRect:Rectangle = new Rectangle(0,0,0.0001,0.0001); + + if(!isNaN(_x1) || !isNaN(_x)){ + if (_x1?_x1:0 - _x?_x:0) tempLayoutRect.width = Math.abs(_x1?_x1:0 - _x?_x:0); + tempLayoutRect.x = Math.min(_x?_x:0,_x1?_x1:0); + } + + if(_y){ + tempLayoutRect.y = _y; + } + + super.calculateLayout(tempLayoutRect); + _layoutRectangle = _layoutConstraint.layoutRectangle; + //force the layout to minimum height in this case + _layoutRectangle.height = 0.0001; + if (isNaN(_x1)) { + //layout defined initial settings + if (isNaN(_x)) _x = _layoutRectangle.x; + if (isNaN(_y)) _y = _layoutRectangle.y; + _x1 = _layoutRectangle.right; + invalidated = true; + } + + + } + } + } + + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + + //init the layout in this case done before predraw. + if (_layoutConstraint) calculateLayout(); + + //re init if required + if (invalidated) preDraw(); + + super.draw(graphics,(rc)? rc:bounds); + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:HorizontalLine):void{ + + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke;} + if (!_x){_x = value.x;} + if (!_y){_y = value.y;} + if (!_x1){_x1 = value.x1;} + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/HorizontalLine.png b/Degrafa/com/degrafa/geometry/HorizontalLine.png new file mode 100644 index 0000000..39e487a Binary files /dev/null and b/Degrafa/com/degrafa/geometry/HorizontalLine.png differ diff --git a/Degrafa/com/degrafa/geometry/Line.as b/Degrafa/com/degrafa/geometry/Line.as new file mode 100644 index 0000000..0af2a23 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/Line.as @@ -0,0 +1,302 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry{ + + import com.degrafa.IGeometry; + import com.degrafa.core.IGraphicsFill; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + //excluded here + [Exclude(name="fill", kind="property")] + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("Line.png")] + + [Bindable] + /** + * The Line element draws a line using the specified x, y, + * x1 and y1 coordinate values. + * + * @see http://samples.degrafa.com/Line/Line.html + * + **/ + public class Line extends Geometry implements IGeometry{ + + /** + * Constructor. + * + *

    The circle constructor accepts 4 optional arguments that define it's + * center point and radius.

    + * + * @param x A number indicating the starting x-axis coordinate. + * @param y A number indicating the starting y-axis coordinate. + * @param x1 A number indicating the ending x-axis coordinate. + * @param y1 A number indicating the ending y-axis coordinate. + * + */ + public function Line(x:Number=NaN,y:Number=NaN,x1:Number=NaN,y1:Number=NaN){ + super(); + + this.x=x; + this.y=y; + this.x1=x1; + this.y1=y1; + + + } + + //excluded here + override public function set fill(value:IGraphicsFill):void{} + + /** + * Line short hand data value. + * + *

    The line data property expects exactly 3 values x, + * y, x1 and y1 separated by spaces.

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + + //parse the string on the space + var tempArray:Array = value.split(" "); + + if (tempArray.length == 4){ + _x=tempArray[0]; + _y=tempArray[1]; + _x1=tempArray[2]; + _y1=tempArray[3]; + } + + invalidated = true; + } + + } + + + private var _x:Number; + /** + * The x-coordinate of the start point of the line. If not specified + * a default value of 0 is used. + **/ + override public function get x():Number{ + if(!_x){return 0;} + return _x; + } + override public function set x(value:Number):void{ + if(_x != value){ + _x = value; + invalidated = true; + } + } + + + private var _y:Number; + /** + * The y-coordinate of the start point of the line. If not specified + * a default value of 0 is used. + **/ + override public function get y():Number{ + if(!_y){return 0;} + return _y; + } + override public function set y(value:Number):void{ + if(_y != value){ + _y = value; + invalidated = true; + } + } + + + + private var _x1:Number; + /** + * The x-coordinate of the end point of the line. If not specified + * a default value of 0 is used. + **/ + public function get x1():Number{ + if(!_x1){return (hasLayout)? 1:0;} + return _x1; + } + public function set x1(value:Number):void{ + if(_x1 != value){ + _x1 = value; + invalidated = true; + } + } + + + private var _y1:Number; + /** + * The y-coordinate of the end point of the line. If not specified + * a default value of 0 is used. + **/ + public function get y1():Number{ + if(!_y1){return (hasLayout)? 1:0;} + return _y1; + } + public function set y1(value:Number):void{ + if(_y1 != value){ + _y1 = value; + invalidated = true; + } + + } + + private var _length:Number; + /** + * The length of the line in pixels. Setting this value will modify + * the end point of the line to the appropriate length. If both x1 + * and y1 are not set then setting the length will impact x1 only, + * set y1 to y and effectively create a horizontal line. + **/ + public function get length():Number{ + var dx:Number = x1 - x; + var dy:Number = y1 - y; + return Math.sqrt(dx*dx + dy*dy); + } + + public function set length(value:Number):void{ + if(_length != value){ + _length = value; + + //if both x1 and y1 are NaN then set x1 to length + if(!_x1 && !_y1 ){ + _x1 = _length; + } + + //this needs to be done before setting the values + var newX1:Number = Math.abs(_length * Math.cos(angle)); + var newY1:Number = Math.abs(_length * Math.sin(angle)); + + _x1=newX1; + _y1=newY1; + + invalidated = true; + } + } + + /** + * The angle of the line in radians. + **/ + public function get angle():Number{ + return Math.atan((y1 - y)/(x1 - x)); + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + if(invalidated){ + commandStack.length=0; + commandStack.addMoveTo(x,y); + commandStack.addLineTo(x1,y1); + invalidated = false; + } + + } + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + + if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + var tempLayoutRect:Rectangle = new Rectangle(0,0,0.0001,0.0001); + + if(!isNaN(_x1) || !isNaN(_x)){ + if (_x1?_x1:0-_x?_x:0) tempLayoutRect.width = Math.abs(_x1?_x1:0-_x?_x:0); + } + + if(!isNaN(_y1) || !isNaN(_y)){ + if (_y1?_y1:0-_y?_y:0) tempLayoutRect.height = Math.abs(_y1?_y1:0-_y?_y:0); + } + + if(!isNaN(_x) || !isNaN(_x1)){ + tempLayoutRect.x = Math.min(_x?_x:0,_x1?_x1:0); + } + + if(!isNaN(_y) || !isNaN(_y1)){ + tempLayoutRect.y = Math.min(_y?_y:0,_y1?_y1:0); + } + + super.calculateLayout(tempLayoutRect); + + _layoutRectangle = _layoutConstraint.layoutRectangle; + + if (isNaN(_x1) && isNaN(_y1) && !_x && !_y) { + //layout defined initial state + _x = _layoutRectangle.x; + _y = _layoutRectangle.y; + _x1 = _layoutRectangle.right; + _y1 = _layoutRectangle.bottom; + invalidated = true; + } + } + } + } + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + + //init the layout in this case done before predraw. + if (_layoutConstraint) calculateLayout(); + + //re init if required + if (invalidated) preDraw(); + + super.draw(graphics,(rc)? rc:bounds); + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:Line):void{ + + if (!stroke){stroke = value.stroke;} + if (!_x){_x = value.x;} + if (!_y){_y = value.y;} + if (!_x1){_x1 = value.x1;} + if (!_y1){_y1 = value.y1;} + if (!_length){_length = value.length;} + + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/Line.png b/Degrafa/com/degrafa/geometry/Line.png new file mode 100644 index 0000000..81e9963 Binary files /dev/null and b/Degrafa/com/degrafa/geometry/Line.png differ diff --git a/Degrafa/com/degrafa/geometry/Move.as b/Degrafa/com/degrafa/geometry/Move.as new file mode 100644 index 0000000..628f546 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/Move.as @@ -0,0 +1,145 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry{ + + import com.degrafa.IGeometry; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + + [Exclude(name="fill", kind="property")] + [Exclude(name="stroke", kind="property")] + + [Bindable] + /** + * The Move element moves the drawing context current point to a specified x and y + * coordinate value. + * + * @see http://degrafa.com/samples/Move_Element.html + * + **/ + public class Move extends Geometry implements IGeometry{ + + /** + * Constructor. + * + *

    The move constructor accepts 2 optional arguments that define it's + * x, and y coordinate values.

    + * + * @param x A number indicating the x-axis coordinate to move to. + * @param y A number indicating the y-axis coordinate to move to. + * + */ + public function Move(x:Number=0,y:Number=0){ + super(); + + this.x=x; + this.y=y; + + } + + /** + * Move short hand data value. + * + *

    The move data property expects exactly 2 values x and + * y separated by spaces.

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + super.data = value; + + //parse the string on the space + var tempArray:Array = value.split(" "); + + if (tempArray.length == 2){ + _x=tempArray[0]; + _y=tempArray[1]; + } + + invalidated = true; + } + } + + + private var _x:Number=0; + /** + * The x-coordinate to move to. If not specified + * a default value of 0 is used. + **/ + override public function get x():Number{ + return _x; + } + override public function set x(value:Number):void{ + if(_x != value){ + _x = value; + invalidated = true; + } + } + + + private var _y:Number=0; + /** + * The y-coordinate to move to. If not specified + * a default value of 0 is used. + **/ + override public function get y():Number{ + return _y; + } + override public function set y(value:Number):void{ + if(_y != value){ + _y = value; + invalidated = true; + } + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + if(invalidated){ + commandStack.length=0; + commandStack.addMoveTo(x,y); + invalidated = false; + } + } + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + //re init if required + if (invalidated) preDraw(); + + super.draw(graphics,(rc)? rc:bounds); + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/Path.as b/Degrafa/com/degrafa/geometry/Path.as new file mode 100644 index 0000000..33be3bd --- /dev/null +++ b/Degrafa/com/degrafa/geometry/Path.as @@ -0,0 +1,600 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry{ + +// import com.degrafa.core.utils.CloneUtil; + import com.degrafa.IGeometry; + import com.degrafa.core.collections.SegmentsCollection; + import com.degrafa.geometry.segment.*; + import com.degrafa.geometry.utilities.*; + + import flash.display.Graphics; + import flash.geom.Point; + import flash.geom.Rectangle; + + import mx.events.PropertyChangeEvent; + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("Path.png")] + + [DefaultProperty("segments")] + [Bindable] + + /** + * The Path element draws a path comprised of several + * available path commands using the specified path data + * value. + * + *

    Paths represent the geometry of the outline of an object, + * defined in terms of moveto (set a new current point), lineto + * (draw a straight line), curveto (draw a curve using a cubic Bézier), + * arc (elliptical or circular arc) and closepath (close the current shape + * by drawing a line to the last moveto) elements.

    + * + * + * @see http://www.w3.org/TR/SVG/paths.html + * @see http://samples.degrafa.com/Path/Path.html + * + **/ + public class Path extends Geometry implements IGeometry{ + + + /** + * Constructor. + * + *

    The path constructor accepts 1 optional argument that + * defines it's segments.

    + * + * @param data A string representing 1 or more segment commands + * with which to draw the path. + */ + public function Path(data:String=null){ + super(); + + if (data) this.data=data; + + + } + + /** + * Path short hand data value. + * + *

    The line data property expects exactly 1 value that + * defines the path. See below link to SVG path specification + * of which we follow most of.

    + * + * @see Geometry#data + * @see http://www.w3.org/TR/SVG/paths.html + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + + super.data = value; + + //clear the data + commandStack.length = 0; + + /** + * Parse the path data using svg commands: + * ClosePath = z + * Moveto = M,m + * LineTo = L,l + * EllipticalArcTo = A,a + * HorizontalLineTo = H,h + * VerticalLineTo = V,v + * Quadratic Bezier = Q,q,T,t + * Cubic Bezier = C,c,S,s + **/ + + var pathDataArray:Array = PathDataToArray(String(value)) + + //store the array as we add items and set the segments after + var segmentStack:Array=[]; + + var length:int = pathDataArray.length; + var i:int = 0; + var seg:int=-1; + + for (;iThe polygon constructor accepts 1 optional argument that describes it's points.

    + * + * @param points An array of points describing the polygon. + */ + public function Polygon(points:Array=null){ + super(); + + if(points){ + this.points=points; + } + + + } + + /** + * Polygon short hand data value. + * + *

    The polygon data property expects a list of space seperated points. For example + * "10,20 30,35".

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + + if(super.data != value){ + + super.data = value; + + //parse the string on the space + var pointsArray:Array = value.split(" "); + + //create a temporary point array + var pointArray:Array=[]; + var pointItem:Array; + + //and then create a point struct for each resulting pair + //eventually throw excemption is not matching properly + var i:int = 0; + var length:int = pointsArray.length; + for (; i< length;i++){ + pointItem = String(pointsArray[i]).split(","); + + //skip past blank items as there may have been bad + //formatting in the value string, so make sure it is + //a length of 2 min + if(pointItem.length==2){ + pointArray.push(new GraphicPoint(pointItem[0],pointItem[1])); + } + + } + + //set the points property + points=pointArray; + + invalidated = true; + + } + + + } + + private var _points:GraphicPointCollection; + [Inspectable(category="General", arrayType="com.degrafa.IGraphicPoint")] + [ArrayElementType("com.degrafa.IGraphicPoint")] + /** + * A array of points that describe this polygon. + **/ + public function get points():Array{ + initPointsCollection(); + return _points.items; + } + public function set points(value:Array):void{ + initPointsCollection(); + _points.items = value; + + invalidated = true; + + } + + /** + * Access to the Degrafa point collection object for this polyline. + **/ + public function get pointCollection():GraphicPointCollection{ + initPointsCollection(); + return _points; + } + + /** + * Initialize the point collection by creating it and adding the event listener. + **/ + private function initPointsCollection():void{ + if(!_points){ + _points = new GraphicPointCollection(); + + //add a listener to the collection + if(enableEvents){ + _points.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + /** + * Principle event handler for any property changes to a + * geometry object or it's child objects. + **/ + override protected function propertyChangeHandler(event:PropertyChangeEvent):void{ + invalidated = true; + super.propertyChangeHandler(event); + } + + + private var _x:Number; + /** + * The x-coordinate of the upper left point to begin drawing from. If not specified + * a default value of 0 is used. + **/ + override public function get x():Number{ + if(!_x){return 0;} + return _x; + } + override public function set x(value:Number):void{ + if(_x != value){ + _x = value; + invalidated = true; + } + } + + + private var _y:Number; + /** + * The y-coordinate of the upper left point to begin drawing from. If not specified + * a default value of 0 is used. + **/ + override public function get y():Number{ + if(!_y){return 0;} + return _y; + } + override public function set y(value:Number):void{ + if(_y != value){ + _y = value; + invalidated = true; + } + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + if(invalidated){ + + if(!_points || !_points.items || _points.items.length<1){return;} + + commandStack.length=0; + + commandStack.addMoveTo(_points.items[0].x+x,_points.items[0].y+y); + + var i:int = 0; + var length:int = _points.items.length; + for (;i < length; i++){ + commandStack.addLineTo(_points.items[i].x+x,_points.items[i].y+y); + } + + //close if not done already + if (_points.items[_points.items.length-1].x+x !=_points.items[0].x+x || _points.items[_points.items.length-1].y+y !=_points.items[0].y+y){ + commandStack.addLineTo(_points.items[0].x+x,_points.items[0].y+y); + } + invalidated = false; + } + } + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + + //default to bounds if no width or height is set + //and we have layout + if(isNaN(_layoutConstraint.width)){ + tempLayoutRect.width = bounds.width; + } + + if(isNaN(_layoutConstraint.height)){ + tempLayoutRect.height = bounds.height; + } + + tempLayoutRect.x = (_x)? _x:0; + tempLayoutRect.y = (_y)? _y:0; + + super.calculateLayout(tempLayoutRect); + + _layoutRectangle = _layoutConstraint.layoutRectangle; + + } + } + } + + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + + //re init if required + if (invalidated) preDraw(); + + //init the layout in this case done after predraw. + if (_layoutConstraint) calculateLayout(); + + super.draw(graphics,(rc)? rc:bounds); + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:Polygon):void{ + + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke} + if (!_x){_x = value.x}; + if (!_y){_y = value.y}; + + if (!_points && value.points.length!=0){points = value.points}; + + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/Polygon.png b/Degrafa/com/degrafa/geometry/Polygon.png new file mode 100644 index 0000000..8186fa8 Binary files /dev/null and b/Degrafa/com/degrafa/geometry/Polygon.png differ diff --git a/Degrafa/com/degrafa/geometry/Polyline.as b/Degrafa/com/degrafa/geometry/Polyline.as new file mode 100644 index 0000000..6cb77b0 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/Polyline.as @@ -0,0 +1,301 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry{ + + import com.degrafa.GraphicPoint; + import com.degrafa.IGeometry; + import com.degrafa.core.collections.GraphicPointCollection; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + import mx.events.PropertyChangeEvent; + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("Polyline.png")] + + [DefaultProperty("points")] + + [Bindable] + /** + * The Polyline element draws a polyline using the specified points. + * + * @see http://degrafa.com/samples/Polyline_Element.html + * + **/ + public class Polyline extends Geometry implements IGeometry{ + + /** + * Constructor. + * + *

    The polyline constructor accepts 1 optional argument that describes it's points.

    + * + * @param points An array of points describing this polyline. + */ + public function Polyline(points:Array=null){ + super(); + if(points){ + this.points=points; + } + } + + /** + * Polyline short hand data value. + * + *

    The polyline data property expects a list of space seperated points. For example + * "10,20 30,35".

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + super.data = value; + + //parse the string on the space + var pointsArray:Array = value.split(" "); + + //create a temporary point array + var pointArray:Array=[]; + var pointItem:Array; + + //and then create a point struct for each resulting pair + //eventually throw excemption is not matching properly + var i:int = 0; + var length:int = pointsArray.length; + for (; i< length;i++){ + pointItem = String(pointsArray[i]).split(","); + + //skip past blank items as there may have been bad + //formatting in the value string, so make sure it is + //a length of 2 min + if(pointItem.length==2){ + pointArray.push(new GraphicPoint(pointItem[0],pointItem[1])); + } + } + + //set the points property + points=pointArray; + + invalidated = true; + + } + } + + + private var _points:GraphicPointCollection; + [Inspectable(category="General", arrayType="com.degrafa.IGraphicPoint")] + [ArrayElementType("com.degrafa.IGraphicPoint")] + /** + * A array of points that describe this polyline. + **/ + public function get points():Array{ + initPointsCollection(); + return _points.items; + } + public function set points(value:Array):void{ + initPointsCollection(); + _points.items = value; + + invalidated = true; + + } + + /** + * Access to the Degrafa point collection object for this polyline. + **/ + public function get pointCollection():GraphicPointCollection{ + initPointsCollection(); + return _points; + } + + /** + * Initialize the point collection by creating it and adding the event listener. + **/ + private function initPointsCollection():void{ + if(!_points){ + _points = new GraphicPointCollection(); + + //add a listener to the collection + if(enableEvents){ + _points.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + /** + * Principle event handler for any property changes to a + * geometry object or it's child objects. + **/ + override protected function propertyChangeHandler(event:PropertyChangeEvent):void{ + invalidated = true; + super.propertyChangeHandler(event); + } + + private var _x:Number; + /** + * The x-coordinate of the upper left point to begin drawing from. If not specified + * a default value of 0 is used. + **/ + override public function get x():Number{ + if(!_x){return 0;} + return _x; + } + override public function set x(value:Number):void{ + if(_x != value){ + _x = value; + invalidated = true; + } + } + + + private var _y:Number; + /** + * The y-coordinate of the upper left point to begin drawing from. If not specified + * a default value of 0 is used. + **/ + override public function get y():Number{ + if(!_y){return 0;} + return _y; + } + override public function set y(value:Number):void{ + if(_y != value){ + _y = value; + invalidated = true; + } + } + + + private var _autoClose:Boolean; + /** + * Specifies if this polyline is to be automatically closed. + * If true a line is drawn to the first point. + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get autoClose():Boolean{ + return _autoClose; + } + public function set autoClose(value:Boolean):void{ + if(_autoClose != value){ + _autoClose = value; + invalidated = true; + } + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + if(invalidated){ + + if(!_points || !_points.items || _points.items.length<1){return;} + + commandStack.length=0; + + commandStack.addMoveTo(_points.items[0].x+x,_points.items[0].y+y); + + var i:int = 0; + var length:int = _points.items.length; + for (;i < length; i++){ + commandStack.addLineTo(_points.items[i].x+x,_points.items[i].y+y); + } + + //close if required + if(_autoClose){ + commandStack.addLineTo(_points.items[0].x+x,_points.items[0].y+y); + } + + invalidated = false; + } + + } + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + + if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + + //default to bounds if no width or height is set + //and we have layout + if(isNaN(_layoutConstraint.width)){ + tempLayoutRect.width = bounds.width; + } + + if(isNaN(_layoutConstraint.height)){ + tempLayoutRect.height = bounds.height; + } + + tempLayoutRect.x = (_x)? _x:0; + tempLayoutRect.y = (_y)? _y:0; + + super.calculateLayout(tempLayoutRect); + + _layoutRectangle = _layoutConstraint.layoutRectangle; + + } + } + } + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + //re init if required + if (invalidated) preDraw(); + + //init the layout in this case done after predraw. + if (_layoutConstraint) calculateLayout(); + + super.draw(graphics,(rc)? rc:bounds); + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:Polyline):void{ + + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke} + if (!_x){_x = value.x}; + if (!_y){_y = value.y}; + + if (!_points && value.points.length!=0){points = value.points}; + + + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/Polyline.png b/Degrafa/com/degrafa/geometry/Polyline.png new file mode 100644 index 0000000..ed1631c Binary files /dev/null and b/Degrafa/com/degrafa/geometry/Polyline.png differ diff --git a/Degrafa/com/degrafa/geometry/QuadraticBezier.as b/Degrafa/com/degrafa/geometry/QuadraticBezier.as new file mode 100644 index 0000000..015c5be --- /dev/null +++ b/Degrafa/com/degrafa/geometry/QuadraticBezier.as @@ -0,0 +1,314 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry{ + + import com.degrafa.IGeometry; + import com.degrafa.geometry.utilities.GeometryUtils; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("QuadraticBezier.png")] + + [Bindable] + /** + * The QuadraticBezier element draws a quadratic Bézier using the specified + * start point, end point and control point. + * + * @see http://degrafa.com/samples/QuadraticBezier_Element.html + * + **/ + public class QuadraticBezier extends Geometry implements IGeometry{ + + /** + * Constructor. + * + *

    The quadratic Bézier constructor accepts 6 optional arguments that define it's + * start, end and controls points.

    + * + * @param x0 A number indicating the starting x-axis coordinate. + * @param y0 A number indicating the starting y-axis coordinate. + * @param cx A number indicating the control x-axis coordinate. + * @param cy A number indicating the control y-axis coordinate. + * @param x1 A number indicating the ending x-axis coordinate. + * @param y1 A number indicating the ending y-axis coordinate. + */ + public function QuadraticBezier(x0:Number=NaN,y0:Number=NaN,cx:Number=NaN,cy:Number=NaN,x1:Number=NaN,y1:Number=NaN){ + super(); + + this.x0=x0; + this.y0=y0; + this.cx=cx; + this.cy=cy; + this.x1=x1; + this.y1=y1; + + + } + + /** + * QuadraticBezier short hand data value. + * + *

    The quadratic Bézier data property expects exactly 6 values x0, + * y0, cx, cy, x1 and y1 separated by spaces.

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + + if(super.data != value){ + super.data = value; + + //parse the string on the space + var tempArray:Array = value.split(" "); + + if (tempArray.length == 6){ + _x0=tempArray[0]; + _y0=tempArray[1]; + _cx=tempArray[2]; + _cy=tempArray[3]; + _x1=tempArray[4]; + _y1=tempArray[5]; + } + + invalidated = true; + + } + } + + private var _x0:Number; + /** + * The x0-coordinate of the start point of the curve. If not specified + * a default value of 0 is used. + **/ + public function get x0():Number{ + if(!_x0){return 0;} + return _x0; + } + public function set x0(value:Number):void{ + if(_x0 != value){ + _x0 = value; + invalidated = true; + } + } + + + private var _y0:Number; + /** + * The y0-coordinate of the start point of the curve. If not specified + * a default value of 0 is used. + **/ + public function get y0():Number{ + if(!_y0){return (hasLayout)? 2:0;} + return _y0; + } + public function set y0(value:Number):void{ + if(_y0 != value){ + _y0 = value; + invalidated = true; + } + } + + + private var _x1:Number; + /** + * The x-coordinate of the end point of the curve. If not specified + * a default value of 0 is used. + **/ + public function get x1():Number{ + if(!_x1){return (hasLayout)? 2:0;} + return _x1; + } + public function set x1(value:Number):void{ + if(_x1 != value){ + _x1 = value; + invalidated = true; + } + } + + + private var _y1:Number; + /** + * The y-coordinate of the end point of the curve. If not specified + * a default value of 0 is used. + **/ + public function get y1():Number{ + if(!_y1){return (hasLayout)? 2:0;} + return _y1; + } + public function set y1(value:Number):void{ + if(_y1 != value){ + _y1 = value; + invalidated = true; + } + } + + + private var _cx:Number; + /** + * The x-coordinate of the control point of the curve. If not specified + * a default value of 0 is used. + **/ + public function get cx():Number{ + if(!_cx){return (hasLayout)? 1:0;} + return _cx; + } + public function set cx(value:Number):void{ + if(_cx != value){ + _cx = value; + invalidated = true; + } + } + + + private var _cy:Number; + /** + * The y-coordinate of the control point of the curve. If not specified + * a default value of 0 is used. + **/ + public function get cy():Number{ + if(!_cy){return 0;} + return _cy; + } + public function set cy(value:Number):void{ + if(_cy != value){ + _cy = value; + invalidated = true; + } + } + + private var _close:Boolean=false; + /** + * If true draws a line from the end point to the start point to + * close this curve. + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get close():Boolean{ + return _close; + } + public function set close(value:Boolean):void{ + if(_close != value){ + _close = value; + invalidated = true; + } + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + if(invalidated){ + + commandStack.length=0; + + commandStack.resetBounds(); + + commandStack.addMoveTo(x0,y0); + commandStack.addCurveTo(cx,cy,x1,y1); + + if(close){ + commandStack.addLineTo(x0,y0); + } + + invalidated = false; + } + + } + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + + if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + var tempLayoutRect:Rectangle = new Rectangle(0, 0, 1, 1); + + if(isNaN(_layoutConstraint.width)){ + tempLayoutRect.width = bounds.width; + } + + if(isNaN(_layoutConstraint.height)){ + tempLayoutRect.height = bounds.height; + } + + if(isNaN(_layoutConstraint.x)){ + tempLayoutRect.x = bounds.x; + } + + if(isNaN(_layoutConstraint.y)){ + tempLayoutRect.y = bounds.y; + } + + super.calculateLayout(tempLayoutRect); + _layoutRectangle = _layoutConstraint.layoutRectangle; + } + } + } + + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + + //re init if required + if (invalidated) preDraw(); + + //init the layout in this case done after predraw. + if (_layoutConstraint) calculateLayout(); + + super.draw(graphics, (rc)? rc:bounds); + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:QuadraticBezier):void{ + + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke} + if (!_x0){_x0 = value.x0;} + if (!_y0){_y0 = value.y0;} + if (!_cx){_cx = value.cx;} + if (!_cy){_cy = value.cy;} + if (!_x1){_x1 = value.x1;} + if (!_y1){_y1 = value.y1;} + + + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/QuadraticBezier.png b/Degrafa/com/degrafa/geometry/QuadraticBezier.png new file mode 100644 index 0000000..a2ebecf Binary files /dev/null and b/Degrafa/com/degrafa/geometry/QuadraticBezier.png differ diff --git a/Degrafa/com/degrafa/geometry/RasterImage.as b/Degrafa/com/degrafa/geometry/RasterImage.as new file mode 100644 index 0000000..2c64ba7 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/RasterImage.as @@ -0,0 +1,468 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry{ + + import com.degrafa.geometry.display.IDisplayObjectProxy; + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.geom.Matrix; + import com.degrafa.core.IGraphicsFill; + import flash.events.Event; + import flash.display.DisplayObject; + import flash.display.Graphics; + import flash.display.Sprite; + import flash.geom.Rectangle; + import flash.utils.getDefinitionByName; + import mx.events.PropertyChangeEvent; + import mx.events.PropertyChangeEventKind; + import com.degrafa.utilities.external.ExternalBitmapData; + import com.degrafa.utilities.external.ExternalDataAsset; + import com.degrafa.utilities.external.LoadingLocation; + import com.degrafa.utilities.external.ExternalDataPropertyChangeEvent; + import flash.utils.setTimeout; + import mx.events.PropertyChangeEvent; + + + [Exclude(name="data", kind="property")] + [Exclude(name="fill", kind="property")] + [Bindable(event = "propertyChange")] + + /** + * The RasterImage element draws a Bitmap Rectangle to the + * drawing target. + * + *

    RasterImage represents a bitmap image that can be part of a geometry composition + * with behavior similar to a regular geometry object. The source of the image mirrors the + * flexibility provided by the paint BitmapFill object, including the possibility of externally + * loaded iamges.

    + * + * + **/ + public class RasterImage extends Geometry implements IDisplayObjectProxy{ + + + + public var sprite:Sprite = new Sprite(); + private var _externalBitmapData:ExternalBitmapData; + private var _loadingLocation:LoadingLocation; + private var _contentWidth:Number; + private var _contentHeight:Number; + + + /** + * Constructor. + * + *

    The RasterImage constructor has no arguments . RasterImage does not inherit stroke by default unlike other Geometry items.

    + * + */ + public function RasterImage(){ + super(); + //default to false for images on stroke inheritance + inheritStroke = false; + inheritFill = false; + } + + /** + * Excluded Items + **/ + /** + * Data is required for the IGeometry interface and has no effect here. + * @private + **/ + override public function get data():Object{return null;} + override public function set data(value:Object):void{} + + /** + * This item currently has no regular fill. We may add this to behave as a background fill (visible through transparent pixels in the image) in the future. + */ + override public function get fill():IGraphicsFill { return null }; + override public function set fill(value:IGraphicsFill):void { }; + + + private var _x:Number; + /** + * The x-axis coordinate of the upper left point of the regular rectangle. If not specified + * a default value of 0 is used. + **/ + override public function get x():Number{ + if(!_x){return 0;} + return _x; + } + override public function set x(value:Number):void{ + if(_x != value){ + _x = value; + invalidated = true; + } + } + + + private var _y:Number; + /** + * The y-axis coordinate of the upper left point of the regular rectangle. If not specified + * a default value of 0 is used. + **/ + override public function get y():Number{ + if(!_y){return 0;} + return _y; + } + override public function set y(value:Number):void{ + if(_y != value){ + _y = value; + invalidated = true; + } + } + + private var _width:Number; + /** + * The width of the regular rectangle. + **/ + [PercentProxy("percentWidth")] + override public function get width():Number{ + if(!_width){return (hasLayout)? 1:0;} + return _width; + } + override public function set width(value:Number):void{ + if(_width != value){ + _width = value; + invalidated = true; + } + } + + + private var _height:Number; + /** + * The height of the regular rectangle. + **/ + [PercentProxy("percentHeight")] + override public function get height():Number{ + if(!_height){return (hasLayout)? 1:0;} + return _height; + } + override public function set height(value:Number):void{ + if(_height != value){ + _height = value; + invalidated = true; + } + } + + + /** + * Returns this objects displayObject. + **/ + public function get displayObject():DisplayObject{ + if (sprite.numChildren == 0) return null; + //provide the container object + return sprite; + } + + + + /** + * A support property for binding to in the event of an external loading wait. + * permits a simple binding to indicate that the wait is over + */ + private var _waiting:Boolean; + [Bindable("externalDataPropertyChange")] + public function get waiting():Boolean + { + return (_waiting==true); + } + public function set waiting(val:Boolean):void + { + if (val != _waiting ) + { + _waiting = val; + //support binding, but don't use propertyChange to avoid Degrafa redraws for no good reason + dispatchEvent(new ExternalDataPropertyChangeEvent(ExternalDataPropertyChangeEvent.EXTERNAL_DATA_PROPERTY_CHANGE, false, false, PropertyChangeEventKind.UPDATE , "waiting", !_waiting, _waiting, this)) + } + } + + + private var _internalDO:DisplayObject; + /** + * handles the ready state for an ExternalBitmapData as the source of a RasterImage + * @param evt an ExternalDataAsset.STATUS_READY event + */ + private function externalBitmapHandler(evt:Event):void { + switch(evt.type) + { + case ExternalDataAsset.STATUS_READY: + var oldValue:Object = (sprite.numChildren) ? sprite.removeChildAt(0) : null; + var bitmapData:BitmapData = evt.target.content; + swapInContent(new Bitmap(bitmapData, "auto", true)); + invalidated = true; + initChange("source", oldValue, _internalDO, this); + waiting = false; + break; + } + } + + private function swapInContent(dobj :DisplayObject):void { + if (sprite.numChildren) sprite.removeChildAt(0); + _internalDO = dobj; + sprite.addChild(dobj); + //cache original values: + _contentWidth = sprite.width; + _contentHeight = sprite.height; + //if there were no explicit assigned width and height, use the content's width and height + if (isNaN(_width)) _width = _contentWidth; + if (isNaN(_height)) _height = _contentHeight; + invalidated = true; + } + + /** + * Optional loadingLocation reference. Only relevant when a subsequent source assignment is made as + * a url string. Using a LoadingLocation simplifies management of loading from external domains + * and is required if a crossdomain policy file is not in the default location (web root) and with the default name (crossdomain.xml) + * In actionscript, a loadingLocation assignment MUST precede a change in the url assigned to the source property + * If a LoadingLocation is being used, the url assigned to the source property MUST be relative to the base path + * defined in the LoadingLocation, otherwise loading will fail. + * If a LoadingLocation is NOT used and the source property assignment is an external domain url, then the crossdomain permissions + * must exist in the default location and with the default name crossdomain.xml, otherwise loading will fail. + */ + public function get loadingLocation():LoadingLocation { return _loadingLocation; } + + public function set loadingLocation(value:LoadingLocation):void + { + if (value) _loadingLocation = value; + } + + private var target:DisplayObject; + //used in actionscript when a remote load is done + private var instantiationTimer:uint=5; + public function set source(value:Object):void { + var oldValue:Object = _internalDO; + + target = null; + + if (_externalBitmapData) { + _externalBitmapData.removeEventListener(ExternalDataAsset.STATUS_READY, externalBitmapHandler); + _externalBitmapData = null; + } + + if (!value) { + _internalDO = null; + if (sprite.numChildren) sprite.removeChildAt(0); + if (oldValue!=null) initChange("source", oldValue, null, this); + return; + } + + if (value is ExternalBitmapData) { + _externalBitmapData = value as ExternalBitmapData; + if (value.content) { + value = value.content; + } else { + value.addEventListener(ExternalDataAsset.STATUS_READY, externalBitmapHandler) + waiting = true; + return; + } + } + + if (value is BitmapData) + { + swapInContent( new Bitmap(value as BitmapData,"auto",true)); + initChange("source", oldValue, _internalDO, this); + return; + } + if (value is Class) + { + target= new value() as DisplayObject; + + } + else if (value is Bitmap) + { + + target = value as Bitmap; + } + else if (value is DisplayObject) + { + target = value as DisplayObject; + } + else if (value is String) + { + //is it a class name or an external url? + try { + var cls:Class = Class(getDefinitionByName(value as String)); + } catch (e:Error) + { + //if its not a class name, assume url string for an ExternalBitmapData + //and wait for isInitialized to check/access loadingLocation mxml assignment + //if not isInitialized after 5 ms, assume actionscript instantiation and not mxml + if (!isInitialized && instantiationTimer) { + instantiationTimer--; + setTimeout( + function():void + {source = value }, 1); + } else { + source = ExternalBitmapData.getUniqueInstance(value as String, _loadingLocation); + } + return; + } + target = new cls(); + } + else + { + _internalDO = null; + if (sprite.numChildren) sprite.removeChildAt(0); + invalidated = true; + if (oldValue!=null) initChange("source", oldValue, null, this); + return; + } + + if( target != null) + { + swapInContent(target as DisplayObject) + invalidated = true; + initChange("source", oldValue, _internalDO, this); + } + + + } + + private var _bounds:Rectangle; + /** + * The tight bounds of this element as represented by a Rectangle object. + **/ + override public function get bounds():Rectangle { + return commandStack.bounds; + } + + + + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used or the most appropriate size is calculated. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + + if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + if (isNaN(_width)) _width = _contentWidth; + if (isNaN(_height)) _height = _contentHeight; + + + tempLayoutRect.width = _width + tempLayoutRect.height = _height + tempLayoutRect.x =_x?_x: _x=0; + tempLayoutRect.y =_y?_y: _y=0;; + + + super.calculateLayout(tempLayoutRect); + + _layoutRectangle = _layoutConstraint.layoutRectangle.isEmpty()? tempLayoutRect: _layoutConstraint.layoutRectangle; + _layoutMode = "scale"; + + invalidated = true; + + } + } else { + //size into regular settings + _transformBeforeRender = false; + if (isNaN(_width)) _width = _contentWidth; + if (isNaN(_height)) _height = _contentHeight; + _internalDO.x = x; + _internalDO.y = y + + if (_width !=_contentWidth) _internalDO.scaleX = _width / _contentWidth; + if (_height != _contentHeight) _internalDO.scaleY = _height / _contentHeight; + + invalidated = true; + } + + } + + + /** + * @inheritDoc + **/ + override public function preDraw():void { + + if(invalidated){ + + commandStack.length=0; + //frame it in a rectangle to permit transforms + //(whether this is used or not will depend on the + //transformBeforeRender setting + commandStack.addMoveTo(x, y); + commandStack.addLineTo(x+_width, y); + commandStack.addLineTo(x+_width, y+_height); + commandStack.addLineTo(x, y + _height); + commandStack.addLineTo(x, y); + invalidated = false; + } + + + + } + + + private var _layoutMode:String = "scale"; + + /** + * The layout mode associated with this IDisplayObjectProxy. For an image this is always scale only. + */ + public function get layoutMode():String { + return _layoutMode; + } + + + private var _transformBeforeRender:Boolean; + /** + * A setting to determine at what point transforms are performed when capturing the bitmap representation of this object internally + * before final rendering. Unless otherwise needed, the default setting of false uses less memory. + */ + [Inspectable(category="General", enumeration="true,false", defaultValue="false")] + public function get transformBeforeRender():Boolean { + return Boolean(_transformBeforeRender); + } + public function set transformBeforeRender(value:Boolean):void { + if (_transformBeforeRender != value) { + _transformBeforeRender = value; + } + } + + /** + * Begins the draw phase for geometry/IDisplayObjectProxy objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics, rc:Rectangle):void { + if (!_internalDO || _internalDO.getBounds(_internalDO).isEmpty()) return; + //init the layout in this case done after predraw. + calculateLayout(); + + if (invalidated) preDraw(); + + super.draw(graphics, rc?rc:bounds); + + } + + + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/RasterText.as b/Degrafa/com/degrafa/geometry/RasterText.as new file mode 100644 index 0000000..811fd26 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/RasterText.as @@ -0,0 +1,1156 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry{ + + import com.degrafa.core.IGraphicsFill; + import com.degrafa.core.utils.ColorUtil; + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.geometry.display.IDisplayObjectProxy; + import com.degrafa.geometry.text.DegrafaTextFormat; + import com.degrafa.paint.SolidFill; + import com.degrafa.paint.SolidStroke; + + import flash.accessibility.AccessibilityProperties; + import flash.display.DisplayObject; + import flash.display.Graphics; + import flash.display.Shape; + import flash.display.Sprite; + import flash.geom.Matrix; + import flash.geom.Rectangle; + import flash.text.AntiAliasType; + import flash.text.Font; + import flash.text.GridFitType; + import flash.text.StyleSheet; + import flash.text.TextField; + import flash.text.TextFormat; + + import mx.events.PropertyChangeEvent; + + [Exclude(name = "data", kind = "property")] + + + [Bindable(event = "propertyChange")] + + /** + * The RasterText element permits rendering Text as part of a composition or drawing to an arbitrary + * graphics target. + * + *

    RasterText represents a bitmap copy of text content that can be part of a geometry composition + * with behavior similar to a regular geometry object. It provides a simple way to include Text based + * content within compositions rendered to an arbitrary graphics context.

    + **/ + public class RasterText extends Geometry implements IDisplayObjectProxy{ + + //Store the textField internally so that properties are proxied + protected var textField:TextField = new TextField(); + + protected var sprite:Sprite = new Sprite(); + protected var internalMaskee:Shape; + protected var internalbackground:Shape=new Shape(); + + static public const ADJUST:String = "adjust"; + static public const SCALE:String = "scale"; + + protected var _embedded:Boolean; + /** + *

    The RasterText constructor has no arguments . RasterText does not inherit stroke or fill by default unlike other Geometry items.

    + */ + public function RasterText(){ + super(); + //by default do not inherit stroke. + inheritStroke = false; + inheritFill = false; + //this is a dynamic only type text Field non + //editable and no mouse events as it is just + //rendered only and not added to the dispaly list. + + textField.selectable = false; + textField.mouseEnabled = false; + //though heavy handed this is required to get around + //a bug when copying the bitmapdata. + sprite.addChild(internalbackground); + sprite.addChild(textField); + } + + /** + * Data is required for the IGeometry interface and has no effect here. + * @private + **/ + override public function get data():Object{return null;} + override public function set data(value:Object):void{} + + + /** + * The Fill of this RasterText is represented by a SolidFill if the textColor property is used. As a convenience + * the regular textfield textColor property is channeled through this object, so it can be used in place of assigning a fill - it also permits color expression in colorkey and alternate formats (e.g. "red") + * If you wish to use Gradient or other Fill types, they should be assigned directly to the fill property as usual, and the textColor setting not used. + */ + override public function set fill(value:IGraphicsFill):void { + super.fill = value; + _fill = super.fill; + }; + + override public function get fill():IGraphicsFill { + if (!super.fill) super.fill = new SolidFill(0, 1); + return super.fill; + }; + + + /** + * Internal function to update the textfield based on settings + * @private + */ + private function updateTextField():void { + + if (fill is SolidFill) textField.textColor = uint(SolidFill(fill).color); + else {textField.textColor = 0;} + //simple for now: re-apply any formatting changes to the whole text content + textField.text = textField.text; + if(autoSizeField){ + textField.width = textField.textWidth +4; + textField.height = textField.textHeight +4; + _width = textField.width; + _height = textField.height; + } + } + + + private var _autoSizeField:Boolean=false; + /** + * Autosize the text field to text size. Default value is false. When set to true the + * TextField object will size to fit the height and width of the text. If layout is active on this object + * and its layoutMode is set to "adjust", then this setting will be overridden by the layout constraints. + * If layoutMode is set to "scale" then layout scaling will be applied after + * the textfield has been autosized to its contents (not yet available, planned for future release) + **/ + + [Inspectable(category="General", enumeration="true,false")] + public function get autoSizeField():Boolean{ + return _autoSizeField; + } + public function set autoSizeField(value:Boolean):void { + if (value!=_autoSizeField){ + _autoSizeField = value; + invalidated = true; + initChange('autoSizeField', !_autoSizeField, _autoSizeField, this); + } + } + private static var _fontList:Array; + /** + * Utility function for checking fonts + */ + public static function get availableEmbeddedFonts():Array { + if (!_fontList) _fontList = []; + _fontList = Font.enumerateFonts(); + for (var i:uint = 0; i < _fontList.length; i++) _fontList[i] = _fontList[i].fontName; + _fontList.sort(); + return _fontList; + } + + private var _x:Number; + /** + * The x-axis coordinate of the upper left point of the text element. If not specified + * a default value of 0 is used. + **/ + override public function get x():Number{ + if(!_x){return 0;} + return _x; + } + override public function set x(value:Number):void{ + if(_x != value){ + var temp:Number=_x; + _x = value; + invalidated = true; + this.dispatchPropertyChange(true,"x",temp,value,this); + } + } + + + private var _y:Number; + + /** + * The y-axis coordinate of the upper left point of the text element. If not specified + * a default value of 0 is used. + **/ + override public function get y():Number{ + if(!_y){return 0;} + return _y; + } + override public function set y(value:Number):void{ + if(_y != value){ + var temp:Number=_y; + _y = value; + invalidated = true; + this.dispatchPropertyChange(true,"y",temp,value,this); + } + } + + private var _width:Number; + /** + * The width of the text element. This may change depending on autoSizeField settings (true) and/or layoutMode settings (adjust). + **/ + [PercentProxy("percentWidth")] + override public function get width():Number{ + if (isNaN(_width)) { + if (hasLayout) return 1; + updateTextField(); + } + return _width; + } + override public function set width(value:Number):void{ + if(_width != value){ + _width = value; + invalidated = true; + } + } + + + private var _height:Number; + /** + * The height of the text element. This may change depending on autoSizeField settings (true) and/or layoutMode settings (adjust). + **/ + [PercentProxy("percentHeight")] + override public function get height():Number{ + if (isNaN(_height)) { + if (hasLayout) return 1; + updateTextField(); + } + return _height; + } + override public function set height(value:Number):void{ + if(_height != value){ + _height = value; + invalidated = true; + } + } + + /** + * Initialise the stroke/border for this RasterText object. Typically only called by draw. + * RasterText only has a visible border stroke if the border property is set to true. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function initStroke(graphics:Graphics, rc:Rectangle):void { + + if (border) { + super.initStroke(graphics, rc); + } + else{ + graphics.lineStyle(); + } + } + + /** + * Initialise the fill for RasterText. This overrides Geometry to implement a specific combination + * for RasterText. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function initFill(graphics:Graphics, rc:Rectangle):void { + if (background && _backgroundFill ) { + internalbackground.graphics.clear(); + _backgroundFill.begin(internalbackground.graphics, rc); + internalbackground.graphics.drawRect(rc.x, rc.y, rc.width, rc.height); + } else { + internalbackground.graphics.clear(); + } + if (!(_fill is SolidFill)) { + if (!internalMaskee) { + internalMaskee = new Shape(); + sprite.addChild(internalMaskee) + } + internalMaskee.graphics.clear(); + super.initFill(internalMaskee.graphics, rc); + var cacheLayout:Matrix = CommandStack.currentLayoutMatrix.clone(); + var cacheTrans:Matrix = CommandStack.currentTransformMatrix.clone(); + commandStack.simpleRender(internalMaskee.graphics, rc); + + if (CommandStack.currentTransformMatrix) { + cacheTrans.invert() + internalMaskee.transform.matrix = cacheTrans; + } + else internalMaskee.transform.matrix.identity();// = new Matrix(); + internalMaskee.cacheAsBitmap = true; + //dev note: consider applying a slight blur to device fonts for 'Antialiasing' + // if (availableEmbeddedFonts.indexOf(_fontFamily) == -1) { + /// textField.filters = [new BlurFilter(2,2,3)]; + // } + textField.cacheAsBitmap = true; + internalMaskee.mask = textField; + } else { + if (internalMaskee) { + + internalMaskee.graphics.clear(); + textField.cacheAsBitmap = false; + internalMaskee.cacheAsBitmap = false; + internalMaskee.mask = null; + } + + } + + } + + /** + * Returns this objects bitmapdata. + * @private + **/ + public function get displayObject():DisplayObject{ + + if (!textField.textWidth || !textField.textHeight){ + return null; + } + + //for now just return the textField + return sprite; + + } + + + private var _bounds:Rectangle; + /** + * The tight bounds of this element as represented by a Rectangle object. + **/ + override public function get bounds():Rectangle { + return commandStack.bounds; + } + + private var _layoutinited:Boolean; + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used or the most appropriate size is calculated. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + + if(_layoutConstraint){ + if (_layoutConstraint.invalidated) { + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + + if(_width){ + tempLayoutRect.width = _width; + } + else{ + tempLayoutRect.width = textField.width?textField.width:1; + } + + if(_height){ + tempLayoutRect.height = _height; + } + else{ + tempLayoutRect.height = textField.height?textField.height:1; + } + + if(_x){ + tempLayoutRect.x = _x; + } + + if(_y){ + tempLayoutRect.y = _y; + } + + super.calculateLayout(tempLayoutRect); + _layoutRectangle = _layoutConstraint.layoutRectangle; + + + if (isNaN(_width) || isNaN(_height) || layoutMode == "adjust") { + + _x=textField.x=layoutRectangle.x ; + _y=textField.y=layoutRectangle.y ; + _width=textField.width=layoutRectangle.width; + _height=textField.height=layoutRectangle.height ; + + if (!_transformBeforeRender) { + //make commandstack outline at layoutrectangle pixelbounds + _width = _layoutRectangle.width=Math.ceil(_layoutRectangle.width+(_layoutRectangle.x-(_x = _layoutRectangle.x=Math.floor(_layoutRectangle.x)))); + _height = _layoutRectangle.height=Math.ceil(_layoutRectangle.height+(_layoutRectangle.y-(_y = _layoutRectangle.y=Math.floor(_layoutRectangle.y)))); + } + }else { + if (layoutMode == "scale" ) { + _width = _layoutRectangle.width=Math.ceil(_layoutRectangle.width+(_layoutRectangle.x-(_x = _layoutRectangle.x=Math.floor(_layoutRectangle.x)))); + _height = _layoutRectangle.height=Math.ceil(_layoutRectangle.height+(_layoutRectangle.y-(_y = _layoutRectangle.y=Math.floor(_layoutRectangle.y)))); + + //dev note: under development + } + } + invalidated = true; + + } + } else { + //size into regular settings + _transformBeforeRender = false; + if (isNaN(_width)) { + updateTextField(); + _width = textField.width } + else { + //fixed width setting + if (!_autoSizeField && !_layoutinited ) { + textField.width = width; + } + + } + if (isNaN(_height)) { + updateTextField(); + _height = textField.height; + } else { + if (!_autoSizeField && !_layoutinited) textField.height = height; + } + textField.x = x; + textField.y = y + + invalidated = true; + _layoutinited = true; + } + } + + /** + * @inheritDoc + **/ + override public function preDraw():void { + if(invalidated){ + updateTextField(); + commandStack.length=0; + //frame it in a rectangle to permit transforms via + //commandStack (whether this is used or not will + //depend on the transformBeforeRender setting + commandStack.addMoveTo(x, y); + commandStack.addLineTo(x+width, y); + commandStack.addLineTo(x+width, y+height); + commandStack.addLineTo(x, y + height); + commandStack.addLineTo(x, y); + invalidated = false; + } + } + + private var _layoutMode:String = "adjust"; + + /** + * The layout mode associated with this RasterText. Currently fixed at 'adjust' which means + * that layout adjusts the size of the text field instead of scaling it. A 'scale' option will be available + * in a future release. + */ + [Inspectable(category="General", enumeration="adjust,scale")] + public function get layoutMode():String { + return _layoutMode; + } + /* public function set layoutMode(value:String):void { + if (_layoutMode != value && (['adjust','scale']).indexOf(value)!=-1) { + initChange('layoutMode',_layoutMode,_layoutMode=value,this) + } + }*/ + + private var _transformBeforeRender:Boolean; + + /** + * A setting to determine at what point transforms are performed when capturing the bitmap representation of this object internally + * before final rendering. This is currently fixed as false for RasterText. A true option may be made available in a future release. + */ + public function get transformBeforeRender():Boolean { + return Boolean(_transformBeforeRender); + } + + /** + * Begins the draw phase for geometry/IDisplayObjectProxy objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics, rc:Rectangle):void { + + if (invalidated) updateTextField(); + calculateLayout(); + preDraw() + super.draw(graphics, (rc)? rc:bounds); + } + + + /** NOTE :: Need to add the complete list or format properties. + * Below are the TextField text Format proxied properties. Any changes here + * will update the public textFormat and set the textfield defaultTextFormat + * to the textformat. + */ + + /** + * Text format. + * + * @see flash.text.TextFormat + **/ + private var _textFormat:DegrafaTextFormat; + public function get textFormat():DegrafaTextFormat { + if(!_textFormat){ + textFormat = new DegrafaTextFormat(); + } + return _textFormat; + } + + public function set textFormat(value:DegrafaTextFormat):void { + if (_textFormat != value) { + + if(_textFormat){ + if(_textFormat.hasEventManager){ + _textFormat.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + if(enableEvents){ + value.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler, false, 0, true); + } + textField.defaultTextFormat = value.textFormat; + invalidated = true; + initChange('textFormat',_textFormat,_textFormat = value,this) + } + } + + + override protected function propertyChangeHandler(event:PropertyChangeEvent):void{ + + if (_textFormat) textField.defaultTextFormat = _textFormat.textFormat; + + invalidated = true; + //carry on to the super. + super.propertyChangeHandler(event); + + } + + /** + * The name of the font for text in this text format, as a string. If the font is a registered embedded font, then embedFonts is + * automatically set to true, otherwise to false. + * + * @see flash.text.TextFormat + **/ + private var _fontFamily:String="_sans"; + public function set fontFamily(value:String):void { + if (textFormat.font!=value){ + if (availableEmbeddedFonts.indexOf(value)!=-1) { + textField.embedFonts = true; + } + else { + textField.embedFonts = false; + } + _embedded = textField.embedFonts; + _fontFamily = value; + textFormat.font = _fontFamily; + textField.defaultTextFormat = _textFormat.textFormat; + // invalidated = true; + } + + } + + public function get fontFamily():String{ + return _fontFamily; + } + + + + + private var _fontSize:Number; + /** + * The point size of text in this text format. + * + * @see flash.text.TextFormat + **/ + public function set fontSize(value:Number):void{ + _fontSize = value; + textFormat.size = _fontSize; + //Adobe recommendations in livedocs: + textField.antiAliasType = (_fontSize > 48)? AntiAliasType.NORMAL:AntiAliasType.ADVANCED; + + if (textField.antiAliasType == AntiAliasType.ADVANCED) textField.gridFitType = GridFitType.PIXEL; + + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get fontSize():Number{ + return _fontSize; + } + + /** + * Specifies whether the text is normal or boldface. + * + * @see flash.text.TextFormat + **/ + private var _fontWeight:String="normal"; + [Inspectable(category="General", enumeration="normal,bold", defaultValue="normal")] + public function set fontWeight(value:String):void{ + _fontWeight = value; + textFormat.bold = _bold = (_fontWeight == "bold") ? true: false; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get fontWeight():String{ + return _fontWeight; + } + + + /** + * Indicates the alignment of the paragraph. Valid values are TextFormatAlign constants. + * + * @see flash.text.TextFormat + **/ + private var _align:String="left"; + [Inspectable(category="General", enumeration="center,justify,left,right", defaultValue="left")] + public function set align(value:String):void{ + _align = value; + textFormat.align = _align; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get align():String{ + return _align; + } + + /** + * Indicates the block indentation in pixels. + * + * @see flash.text.TextFormat + **/ + private var _blockIndent:Object; + public function set blockIndent(value:Object):void{ + _blockIndent = value; + textFormat.blockIndent = _blockIndent; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get blockIndent():Object{ + return _blockIndent; + } + + /** + * Specifies whether the text is boldface. + * + * @see flash.text.TextFormat + **/ + private var _bold:Boolean; + [Inspectable(category="General", enumeration="true,false")] + public function set bold(value:Boolean):void{ + _bold = value; + textFormat.bold = _bold; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get bold():Boolean{ + return _bold; + } + + /** + * Indicates that the text is part of a bulleted list. + * + * @see flash.text.TextFormat + **/ + private var _bullet:Object; + public function set bullet(value:Object):void{ + _bullet = value; + textFormat.bullet = _bullet; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get bullet():Object{ + return _bullet; + } + + /** + * Indicates the indentation from the left margin to the first character in the paragraph. + * + * @see flash.text.TextFormat + **/ + private var _indent:Object; + public function set indent(value:Object):void { + _indent = value; + textFormat.indent = _indent; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get indent():Object{ + return _indent; + } + + /** + * Indicates whether text in this text format is italicized. + * + * @see flash.text.TextFormat + **/ + private var _italic:Boolean; + [Inspectable(category="General", enumeration="true,false")] + public function set italic(value:Boolean):void{ + _italic = value; + textFormat.italic = _italic; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get italic():Boolean{ + return _italic; + } + + /** + * A Boolean value that indicates whether kerning is enabled (true) or disabled (false). + * + * @see flash.text.TextFormat + **/ + private var _kerning:Boolean; + [Inspectable(category="General", enumeration="true,false")] + public function set kerning(value:Boolean):void{ + _kerning = value; + textFormat.kerning = _kerning; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get kerning():Boolean{ + return _kerning; + } + + /** + * An integer representing the amount of vertical space (called leading) between lines. + * + * @see flash.text.TextFormat + **/ + private var _leading:int; + public function set leading(value:int):void{ + _leading = value; + textFormat.leading = _leading; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get leading():int{ + return _leading; + } + + /** + * The left margin of the paragraph, in pixels. + * + * @see flash.text.TextFormat + **/ + private var _leftMargin:Number; + public function set leftMargin(value:Number):void{ + _leftMargin = value; + textFormat.leftMargin = _leftMargin; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get leftMargin():Number{ + return _leftMargin; + } + + /** + * A number representing the amount of space that is uniformly distributed between all characters. + * + * @see flash.text.TextFormat + **/ + private var _letterSpacing:Number; + public function set letterSpacing(value:Number):void{ + _letterSpacing = value; + textFormat.letterSpacing = _letterSpacing; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get letterSpacing():Number{ + return _letterSpacing; + } + + /** + * The right margin of the paragraph, in pixels. + * + * @see flash.text.TextFormat + **/ + private var _rightMargin:Number; + public function set rightMargin(value:Number):void{ + _rightMargin = value; + textFormat.rightMargin = _rightMargin; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get rightMargin():Number{ + return _rightMargin; + } + + + /** + * The point size of text in this text format. + * + * @see flash.text.TextFormat + **/ + private var _size:Number; + public function set size(value:Number):void { + if (textFormat.size!=value){ + textFormat.size = _fontSize = value; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + _size = value; + } + } + public function get size():Number{ + return _size; + } + + + /** + * Specifies custom tab stops as an array of non-negative integers. + * + * @see flash.text.TextFormat + **/ + private var _tabStops:Array; + public function set tabStops(value:Array):void{ + _tabStops = value; + textFormat.tabStops = _tabStops; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get tabStops():Array{ + return _tabStops; + } + + /** + * Indicates whether the text that uses this text format is underlined (true) or not (false). + * + * @see flash.text.TextFormat + **/ + private var _underline:Boolean; + [Inspectable(category="General", enumeration="true,false")] + public function set underline(value:Boolean):void{ + _underline = value; + textFormat.underline = _underline; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get underline():Boolean{ + return _underline; + } + + + /** + * accessibilityProperties property for the textField. + * + * @see flash.text.TextField + **/ + public function get accessibilityProperties():AccessibilityProperties { + return textField.accessibilityProperties; + } + public function set accessibilityProperties(value:AccessibilityProperties):void { + if (value!=textField.accessibilityProperties) + initChange("accessibilityProperties", textField.accessibilityProperties, textField.accessibilityProperties = value, this); + } + /** + * alpha property for the textField. + * + * @see flash.text.TextField + **/ + override public function get alpha():Number{ + return textField.alpha; + } + override public function set alpha(value:Number):void { + if (value!=textField.alpha) + initChange("alpha", textField.alpha, textField.alpha = value, this); + } + /** + * antiAliasType property for the textField. + * + * @see flash.text.TextField + **/ + [Inspectable(category="General", enumeration="normal,advanced", defaultValue="normal")] + public function get antiAliasType():String { + return textField.antiAliasType; + } + public function set antiAliasType(value:String):void { + if (value!=textField.antiAliasType) + initChange("antiAliasType", textField.antiAliasType, textField.antiAliasType = value, this); + } + /** + * autoSize property for the textField. + * + * @see flash.text.TextField + **/ + [Inspectable(category="General", enumeration="none,left,right,center", defaultValue="none")] + public function get autoSize():String{ + return textField.autoSize; + } + public function set autoSize(value:String):void { + if (value!=textField.autoSize) + initChange("autoSize", textField.autoSize, textField.autoSize = value, this); + } + + + + private var _backgroundFill:IGraphicsFill; + /** + * A Degrafa Fill used in the background of this RasterText area. + * + **/ + public function get backgroundFill():IGraphicsFill{ + return _backgroundFill; + } + public function set backgroundFill(value:IGraphicsFill):void { + if (value != _backgroundFill) { + if (_backgroundFill) _backgroundFill.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler); + value.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler); + initChange("backgroundFill", _backgroundFill, _backgroundFill = value, this); + } + } + + private var _background:Boolean; + /** + * background property for the textField. Activates the backgroundFill. + * + * @see flash.text.TextField + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get background():Boolean{ + return _background; + } + public function set background(value:Boolean):void { + if (value!=_background) + initChange("background", _background, _background = value, this); + } + + /** + * Similar to backgroundColor property for the textField. A shortcut setting for backgroundFill's color property. If used, and a non-SolidFill (e.g gradientFill) is currently being + * used as a backgroundFill, then this will force creation of a new SolidFill in its place. + * + * @see flash.text.TextField + **/ + public function get backgroundColor():Object{ + return (_backgroundFill is SolidFill)? (_backgroundFill as SolidFill).color:null; + } + public function set backgroundColor(value:Object):void { + if (_backgroundFill is SolidFill) (_backgroundFill as SolidFill).color = value; + else { backgroundFill = new SolidFill(value, 1) }; + + } + + + private var _border:Boolean; + /** + * border property for the textField. + * + * @see flash.text.TextField + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get border():Boolean{ + return _border; + } + public function set border(value:Boolean):void { + if (value!=_border) + initChange("border", _border, _border = value, this); + } + /** + * borderColor property for the textField. This property has additional support for extended Degrafa color specifications, for example "red" as a color key. + * + * @see flash.text.TextField + **/ + public function get borderColor():Object{ + return textField.borderColor; + } + public function set borderColor(value:Object):void { + + var newval:uint = ColorUtil.resolveColor(value); + if (stroke is SolidStroke) SolidStroke(stroke).color = value; + else stroke = new SolidStroke(value, 1, 0); + } + + /** + * condenseWhite property for the textField. + * + * @see flash.text.TextField + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get condenseWhite():Boolean{ + return textField.condenseWhite; + } + public function set condenseWhite(value:Boolean):void { + if (value!=textField.condenseWhite) + initChange("condenseWhite", textField.condenseWhite, textField.condenseWhite = value, this); + } + + //either made private and the formazt options moved to this class(align etc.. or + //create a textFormat class that is bindable. + private function get defaultTextFormat():TextFormat{ + return textField.defaultTextFormat + } + private function set defaultTextFormat(value:TextFormat):void { + if (value!=textField.defaultTextFormat) + initChange("defaultTextFormat", textField.defaultTextFormat, textField.defaultTextFormat = value, this); + } + /** + * embedFonts property for the textField. setting the fontFamily can also change this setting in RasterText. + * + * @see flash.text.TextField + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get embedFonts():Boolean{ + return textField.embedFonts; + } + public function set embedFonts(value:Boolean):void { + if (value!=textField.embedFonts) + initChange("embedFonts", textField.embedFonts, textField.embedFonts = value, this); + } + /** + * gridFitType property for the textField. + * + * @see flash.text.TextField + **/ + [Inspectable(category="General", enumeration="none,pixel,subpixel", defaultValue="none")] + public function get gridFitType():String{ + return textField.gridFitType; + } + public function set gridFitType(value:String):void { + if (value!=textField.gridFitType) + initChange("gridFitType", textField.gridFitType, textField.gridFitType = value, this); + } + /** + * htmlText property for the textField. + * + * @see flash.text.TextField + **/ + public function get htmlText():String{ + return textField.htmlText; + } + public function set htmlText(value:String):void { + + if (value != textField.htmlText) { + if (value == null) + value=""; + invalidated = true; + initChange("htmlText",textField.htmlText, textField.htmlText=value, this); + } + } + /** + * length property for the textField. + * + * @see flash.text.TextField + **/ + public function get length():int{ + return textField.length; + } + /** + * multiline property for the textField. + * + * @see flash.text.TextField + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get multiline():Boolean{ + return textField.multiline; + } + public function set multiline(value:Boolean):void { + if (value!=textField.multiline) + initChange("multiline", textField.multiline, textField.multiline = value, this); + } + /** + * numLines property for the textField. + * + * @see flash.text.TextField + **/ + public function get numLines():int{ + return textField.numLines; + } + /** + * sharpness property for the textField. + * + * @see flash.text.TextField + **/ + public function get sharpness():Number{ + return textField.sharpness; + } + public function set sharpness(value:Number):void { + if (value!=textField.sharpness) + initChange("sharpness", textField.sharpness, textField.sharpness = value, this); + } + /** + * styleSheet property for the textField. + * + * @see flash.text.TextField + **/ + public function get styleSheet():StyleSheet{ + return textField.styleSheet; + } + public function set styleSheet(value:StyleSheet):void { + if (value != textField.styleSheet) { + var oldSS:StyleSheet = textField.styleSheet; + textField.styleSheet = value; + invalidated = true; + initChange("styleSheet", oldSS, textField.styleSheet, this); + } + } + /** + * text property for the textField. + * + * @see flash.text.TextField + **/ + public function get text():String{ + return textField.text; + } + public function set text(value:String):void { + if (value != textField.text) { + if (value == null) value=""; + invalidated = true; + initChange("text",textField.text, textField.text=value, this); + } + } + + /** + * textColor property for the textField. + * + * @see flash.text.TextField + **/ + public function get textColor():uint{ + return (_fill is SolidFill) ? uint((_fill as SolidFill).color):null; + } + public function set textColor(value:uint):void{ + if (!(_fill is SolidFill) || value != uint((_fill as SolidFill).color)) { + if (_fill is SolidFill) (_fill as SolidFill).color = value; + else fill = new SolidFill(value, 1); + } + } + /** + * textHeight property for the textField. + * + * @see flash.text.TextField + **/ + public function get textWidth():Number{ + return textField.textWidth; + } + /** + * thickness property for the textField. + * + * @see flash.text.TextField + **/ + public function get thickness():Number{ + return textField.thickness; + } + public function set thickness(value:Number):void { + if (value!=textField.thickness && value>=-200 && value<=200) + initChange("thickness", textField.thickness, textField.thickness = value, this); + } + /** + * wordWrap property for the textField. + * + * @see flash.text.TextField + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get wordWrap():Boolean{ + return textField.wordWrap; + } + public function set wordWrap(value:Boolean):void { + if (value!=textField.wordWrap) + initChange("wordWrap", textField.wordWrap, textField.wordWrap = value, this); + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/RasterTextPlus.as b/Degrafa/com/degrafa/geometry/RasterTextPlus.as new file mode 100644 index 0000000..9619c3c --- /dev/null +++ b/Degrafa/com/degrafa/geometry/RasterTextPlus.as @@ -0,0 +1,1216 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry{ + + import com.degrafa.core.IGraphicsFill; + import com.degrafa.core.utils.ColorUtil; + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.geometry.display.IDisplayObjectProxy; + import com.degrafa.geometry.text.DegrafaTextFormat; + import com.degrafa.paint.SolidFill; + import com.degrafa.paint.SolidStroke; + import flash.display.Shape; + import flash.filters.BlurFilter; + import flash.geom.Matrix; + + import flash.accessibility.AccessibilityProperties; + import flash.display.DisplayObject; + import flash.display.Graphics; + import flash.display.Sprite; + import flash.geom.Rectangle; + import flash.text.AntiAliasType; + import flash.text.Font; + import flash.text.GridFitType; + import flash.text.StyleSheet; + import flash.text.TextField; + import flash.text.TextFormat; + + import mx.events.PropertyChangeEvent; + + [Exclude(name = "data", kind = "property")] + + + [Bindable(event = "propertyChange")] + + /** + * The RasterText element permits rendering Text as part of a composition or drawing to an arbitrary + * graphics target. + * + *

    RasterText represents a bitmap copy of text content that can be part of a geometry composition + * with behavior similar to a regular geometry object. It provides a simple way to include Text based + * content within compositions rendered to an arbitrary graphics context.

    + **/ + public class RasterTextPlus extends Geometry implements IDisplayObjectProxy{ + + //Store the textField internally so that properties are proxied + protected var textField:TextField = new TextField(); + + protected var sprite:Sprite = new Sprite(); + protected var internalMaskee:Shape; + protected var internalbackground:Shape=new Shape(); + + static public const ADJUST:String = "adjust"; + static public const SCALE:String = "scale"; + + protected var _embedded:Boolean; + /** + *

    The RasterText constructor has no arguments . RasterText does not inherit stroke by default unlike other Geometry items.

    + */ + public function RasterTextPlus(){ + super(); + //by default do not inherit stroke. + inheritStroke = false; + inheritFill = false; + //this is a dynamic only type text Field non + //editable and no mouse events as it is just + //rendered only and not added to the dispaly list. + + textField.selectable = false; + textField.mouseEnabled = false; + //though heavy handed this is required to get around + //a bug when copying the bitmapdata. + sprite.addChild(internalbackground); + sprite.addChild(textField); + } + + /** + * Data is required for the IGeometry interface and has no effect here. + * @private + **/ + override public function get data():Object{return null;} + override public function set data(value:Object):void{} + + /** + * Internal function to update the textfield based on settings + * @private + */ + private function updateTextField():void { + + if (fill is SolidFill) { + textField.textColor = uint(SolidFill(fill).color); + } + else + { + textField.textColor = 0; + } + + //simple for now: re-apply any formatting changes to the whole text content + textField.text = textField.text; + + if(autoSizeField){ + textField.width = textField.textWidth + 4; + textField.height = textField.textHeight + 4; + _width = textField.width; + _height = textField.height; + realign(); + } + } + + + private var _autoSizeField:Boolean=true; + /** + * Autosize the text field to text size. When set to true the + * TextField object will size to fit the height and width of + * the text. If layout is active on this object and its layoutMode is set to "adjust", then this + * setting will be overridden by the layout constraints. If layoutMode is set to "scale" then layout scaling will be applied after + * the textfield has been autosized to its contents. + **/ + + [Inspectable(category="General", enumeration="true,false")] + public function get autoSizeField():Boolean{ + return _autoSizeField; + } + public function set autoSizeField(value:Boolean):void { + if (value!=_autoSizeField){ + _autoSizeField = value; + invalidated = true; + initChange('autoSizeField', !_autoSizeField, _autoSizeField, this); + } + } + private static var _fontList:Array; + /** + * Utility function for checking fonts + */ + public static function get availableEmbeddedFonts():Array { + if (!_fontList) _fontList = []; + _fontList = Font.enumerateFonts(); + for (var i:uint = 0; i < _fontList.length; i++) _fontList[i] = _fontList[i].fontName; + _fontList.sort(); + return _fontList; + } + + private var _x:Number; + /** + * The x-axis coordinate of the upper left point of the text element. If not specified + * a default value of 0 is used. + **/ + override public function get x():Number{ + if(!_x){return 0;} + return _x; + } + override public function set x(value:Number):void{ + if(_x != value){ + _x = value; + invalidated = true; + } + } + + + private var _y:Number; + + /** + * The y-axis coordinate of the upper left point of the text element. If not specified + * a default value of 0 is used. + **/ + override public function get y():Number{ + if(!_y){return 0;} + return _y; + } + override public function set y(value:Number):void{ + if(_y != value){ + _y = value; + invalidated = true; + } + } + + private var _width:Number; + /** + * The width of the text element. This may change depending on autoSizeField settings (true) and/or layoutMode settings (adjust). + **/ + [PercentProxy("percentWidth")] + override public function get width():Number{ + if(!_width){return (hasLayout)? 1:0;} + return _width; + } + override public function set width(value:Number):void{ + if(_width != value){ + _width = value; + invalidated = true; + } + } + + + private var _height:Number; + /** + * The height of the text element. This may change depending on autoSizeField settings (true) and/or layoutMode settings (adjust). + **/ + [PercentProxy("percentHeight")] + override public function get height():Number{ + if(!_height){return (hasLayout)? 1:0;} + return _height; + } + override public function set height(value:Number):void{ + if(_height != value){ + _height = value; + invalidated = true; + } + } + + /** + * Initialise the stroke/border for this RasterText object. Typically only called by draw + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function initStroke(graphics:Graphics, rc:Rectangle):void { + + if (border) { + super.initStroke(graphics, rc); + } + else{ + graphics.lineStyle(); + } + } + + /** + * Initialise the fill for RasterText. This overrides Geometry to implement a specific combination + * for RasterText. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function initFill(graphics:Graphics, rc:Rectangle):void { + if (background && _backgroundFill ) { + internalbackground.graphics.clear(); + _backgroundFill.begin(internalbackground.graphics, rc); + internalbackground.graphics.drawRect(rc.x, rc.y, rc.width, rc.height); + } + if (!(_fill is SolidFill)) { + if (!internalMaskee) { + internalMaskee = new Shape(); + sprite.addChild(internalMaskee) + } + internalMaskee.graphics.clear(); + super.initFill(internalMaskee.graphics, rc); + var cacheLayout:Matrix = CommandStack.currentLayoutMatrix.clone(); + var cacheTrans:Matrix = CommandStack.currentTransformMatrix.clone(); + commandStack.simpleRender(internalMaskee.graphics, rc); + + if (CommandStack.currentTransformMatrix) { + cacheTrans.invert() + internalMaskee.transform.matrix = cacheTrans; + } + else internalMaskee.transform.matrix.identity();// = new Matrix(); + internalMaskee.cacheAsBitmap = true; + //dev note: consider applying a slight blur to device fonts for 'Antialiasing' + // if (availableEmbeddedFonts.indexOf(_fontFamily) == -1) { + /// textField.filters = [new BlurFilter(2,2,3)]; + // } + textField.cacheAsBitmap = true; + internalMaskee.mask = textField; + } else { + if (internalMaskee) { + internalMaskee.graphics.clear(); + textField.cacheAsBitmap = false; + internalMaskee.cacheAsBitmap = false; + internalMaskee.mask = null; + } + + } + + } + + /** + * Returns this objects bitmapdata. + **/ + public function get displayObject():DisplayObject{ + + if (!textField.textWidth || !textField.textHeight){ + return null; + } + + //for now just return the textField + return sprite; + + } + + private var _bounds:Rectangle; + /** + * The tight bounds of this element as represented by a Rectangle object. + **/ + override public function get bounds():Rectangle { + return commandStack.bounds; + } + + private var _layoutinited:Boolean; + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used or the most appropriate size is calculated. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + + if(_layoutConstraint){ + if (_layoutConstraint.invalidated) { + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + + if(_width){ + tempLayoutRect.width = _width; + } + else{ + tempLayoutRect.width = textField.width?textField.width:1; + } + + if(_height){ + tempLayoutRect.height = _height; + } + else{ + tempLayoutRect.height = textField.height?textField.height:1; + } + + if(_x){ + tempLayoutRect.x = _x; + } + + if(_y){ + tempLayoutRect.y = _y; + } + + super.calculateLayout(tempLayoutRect); + _layoutRectangle = _layoutConstraint.layoutRectangle; + + + if (isNaN(_width) || isNaN(_height) || layoutMode == "adjust") { + + _x=textField.x=layoutRectangle.x ; + _y=textField.y=layoutRectangle.y ; + _width=textField.width=layoutRectangle.width; + _height=textField.height=layoutRectangle.height ; + + if (!_transformBeforeRender) { + //make commandstack outline at layoutrectangle pixelbounds + _width = _layoutRectangle.width=Math.ceil(_layoutRectangle.width+(_layoutRectangle.x-(_x = _layoutRectangle.x=Math.floor(_layoutRectangle.x)))); + _height = _layoutRectangle.height=Math.ceil(_layoutRectangle.height+(_layoutRectangle.y-(_y = _layoutRectangle.y=Math.floor(_layoutRectangle.y)))); + } + }else { + if (layoutMode == "scale" ) { + _width = _layoutRectangle.width=Math.ceil(_layoutRectangle.width+(_layoutRectangle.x-(_x = _layoutRectangle.x=Math.floor(_layoutRectangle.x)))); + _height = _layoutRectangle.height=Math.ceil(_layoutRectangle.height+(_layoutRectangle.y-(_y = _layoutRectangle.y=Math.floor(_layoutRectangle.y)))); + + //dev note: under development + } + } + invalidated = true; + + } + } else { + //size into regular settings + _transformBeforeRender = false; + if (isNaN(_width)) { + updateTextField(); + _width = textField.width } + else { + //fixed width setting + if (!_autoSizeField && !_layoutinited ) { + textField.width = width; + } + + } + if (isNaN(_height)) { + updateTextField(); + _height = textField.height; + } else { + if (!_autoSizeField && !_layoutinited) textField.height = height; + } + textField.x = x; + textField.y = y + + invalidated = true; + _layoutinited = true; + } + } + + /** + * @inheritDoc + **/ + override public function preDraw():void { + if(invalidated){ + + commandStack.length=0; + //frame it in a rectangle to permit transforms via + //commandStack (whether this is used or not will + //depend on the transformBeforeRender setting + commandStack.addMoveTo(x, y); + commandStack.addLineTo(x+width, y); + commandStack.addLineTo(x+width, y+height); + commandStack.addLineTo(x, y + height); + commandStack.addLineTo(x, y); + invalidated = false; + } + } + + private var _layoutMode:String = "adjust"; + + /** + * The layout mode associated with this RasterText. Currently fixed at 'adjust' which means + * that layout adjusts the size of the text field instead of scaling it. A 'scale' option will be available + * in a future release. + */ + [Inspectable(category="General", enumeration="adjust,scale")] + public function get layoutMode():String { + return _layoutMode; + } + public function set layoutMode(value:String):void { + if (_layoutMode != value && (['adjust','scale']).indexOf(value)!=-1) { + initChange('layoutMode',_layoutMode,_layoutMode=value,this) + } + } + + private var _transformBeforeRender:Boolean; + + /** + * A setting to determine at what point transforms are performed when capturing the bitmap representation of this object internally + * before final rendering. This is currently fixed as false for RasterText. A true option may be made available in a future release. + */ + public function get transformBeforeRender():Boolean { + return Boolean(_transformBeforeRender); + } + + /** + * Begins the draw phase for geometry/IDisplayObjectProxy objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics, rc:Rectangle):void { + + if (invalidated) updateTextField(); + calculateLayout(); + preDraw() + super.draw(graphics, (rc)? rc:bounds); + } + + + /** NOTE :: Need to add the complete list or format properties. + * Below are the TextField text Format proxied properties. Any changes here + * will update the public textFormat and set the textfield defaultTextFormat + * to the textformat. + */ + + /** + * Text format. + * + * @see flash.text.TextFormat + **/ + private var _textFormat:DegrafaTextFormat; + public function get textFormat():DegrafaTextFormat { + if(!_textFormat){ + textFormat = new DegrafaTextFormat(); + } + return _textFormat; + } + + public function set textFormat(value:DegrafaTextFormat):void { + if (_textFormat != value) { + + if(_textFormat){ + if(_textFormat.hasEventManager){ + _textFormat.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + if(enableEvents){ + value.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler, false, 0, true); + } + textField.defaultTextFormat = value.textFormat; + invalidated = true; + initChange('textFormat',_textFormat,_textFormat = value,this) + } + } + + + override protected function propertyChangeHandler(event:PropertyChangeEvent):void{ + + if (_textFormat) textField.defaultTextFormat = _textFormat.textFormat; + + invalidated = true; + //carry on to the super. + super.propertyChangeHandler(event); + + } + + /** + * The name of the font for text in this text format, as a string. If the font is a registered embedded font, then embedFonts is + * automatically set to true, otherwise to false. + * + * @see flash.text.TextFormat + **/ + private var _fontFamily:String="_sans"; + public function set fontFamily(value:String):void { + if (textFormat.font!=_fontFamily){ + + if (availableEmbeddedFonts.indexOf(value)!=-1) { + textField.embedFonts = true; + } + else { + textField.embedFonts = false; + } + _embedded = textField.embedFonts; + _fontFamily = value; + textFormat.font = _fontFamily; + textField.defaultTextFormat = _textFormat.textFormat; + // invalidated = true; + } + + } + + public function get fontFamily():String{ + return _fontFamily; + } + + + + + private var _fontSize:Number; + /** + * The point size of text in this text format. + * + * @see flash.text.TextFormat + **/ + public function set fontSize(value:Number):void{ + _fontSize = value; + textFormat.size = _fontSize; + //Adobe recommendations in livedocs: + textField.antiAliasType = (_fontSize > 48)? AntiAliasType.NORMAL:AntiAliasType.ADVANCED; + + if (textField.antiAliasType == AntiAliasType.ADVANCED) textField.gridFitType = GridFitType.PIXEL; + + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get fontSize():Number{ + return _fontSize; + } + + /** + * Specifies whether the text is normal or boldface. + * + * @see flash.text.TextFormat + **/ + private var _fontWeight:String="normal"; + [Inspectable(category="General", enumeration="normal,bold", defaultValue="normal")] + public function set fontWeight(value:String):void{ + _fontWeight = value; + textFormat.bold = _bold = (_fontWeight == "bold") ? true: false; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get fontWeight():String{ + return _fontWeight; + } + + + /** + * Indicates the alignment of the paragraph. Valid values are TextFormatAlign constants. + * + * @see flash.text.TextFormat + **/ + private var _align:String="left"; + [Inspectable(category="General", enumeration="center,justify,left,right", defaultValue="left")] + public function set align(value:String):void{ + _align = value; + textFormat.align = _align; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get align():String{ + return _align; + } + + /** + * Indicates the block indentation in pixels. + * + * @see flash.text.TextFormat + **/ + private var _blockIndent:Object; + public function set blockIndent(value:Object):void{ + _blockIndent = value; + textFormat.blockIndent = _blockIndent; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get blockIndent():Object{ + return _blockIndent; + } + + /** + * Specifies whether the text is boldface. + * + * @see flash.text.TextFormat + **/ + private var _bold:Boolean; + [Inspectable(category="General", enumeration="true,false")] + public function set bold(value:Boolean):void{ + _bold = value; + textFormat.bold = _bold; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get bold():Boolean{ + return _bold; + } + + /** + * Indicates that the text is part of a bulleted list. + * + * @see flash.text.TextFormat + **/ + private var _bullet:Object; + public function set bullet(value:Object):void{ + _bullet = value; + textFormat.bullet = _bullet; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get bullet():Object{ + return _bullet; + } + + /** + * Indicates the indentation from the left margin to the first character in the paragraph. + * + * @see flash.text.TextFormat + **/ + private var _indent:Object; + public function set indent(value:Object):void { + _indent = value; + textFormat.indent = _indent; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get indent():Object{ + return _indent; + } + + /** + * Indicates whether text in this text format is italicized. + * + * @see flash.text.TextFormat + **/ + private var _italic:Boolean; + [Inspectable(category="General", enumeration="true,false")] + public function set italic(value:Boolean):void{ + _italic = value; + textFormat.italic = _italic; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get italic():Boolean{ + return _italic; + } + + /** + * A Boolean value that indicates whether kerning is enabled (true) or disabled (false). + * + * @see flash.text.TextFormat + **/ + private var _kerning:Boolean; + [Inspectable(category="General", enumeration="true,false")] + public function set kerning(value:Boolean):void{ + _kerning = value; + textFormat.kerning = _indent; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get kerning():Boolean{ + return _kerning; + } + + /** + * An integer representing the amount of vertical space (called leading) between lines. + * + * @see flash.text.TextFormat + **/ + private var _leading:int; + public function set leading(value:int):void{ + _leading = value; + textFormat.leading = _leading; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get leading():int{ + return _leading; + } + + /** + * The left margin of the paragraph, in pixels. + * + * @see flash.text.TextFormat + **/ + private var _leftMargin:Number; + public function set leftMargin(value:Number):void{ + _leftMargin = value; + textFormat.leftMargin = _leftMargin; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get leftMargin():Number{ + return _leftMargin; + } + + /** + * A number representing the amount of space that is uniformly distributed between all characters. + * + * @see flash.text.TextFormat + **/ + private var _letterSpacing:Number; + public function set letterSpacing(value:Number):void{ + _letterSpacing = value; + textFormat.letterSpacing = _letterSpacing; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get letterSpacing():Number{ + return _letterSpacing; + } + + /** + * The right margin of the paragraph, in pixels. + * + * @see flash.text.TextFormat + **/ + private var _rightMargin:Number; + public function set rightMargin(value:Number):void{ + _rightMargin = value; + textFormat.rightMargin = _rightMargin; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get rightMargin():Number{ + return _rightMargin; + } + + + /** + * The point size of text in this text format. + * + * @see flash.text.TextFormat + **/ + private var _size:Number; + public function set size(value:Number):void { + if (textFormat.size!=value){ + textFormat.size = _fontSize = value; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + _size = value; + } + } + public function get size():Number{ + return _size; + } + + + /** + * Specifies custom tab stops as an array of non-negative integers. + * + * @see flash.text.TextFormat + **/ + private var _tabStops:Array; + public function set tabStops(value:Array):void{ + _tabStops = value; + textFormat.tabStops = _tabStops; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get tabStops():Array{ + return _tabStops; + } + + /** + * Indicates whether the text that uses this text format is underlined (true) or not (false). + * + * @see flash.text.TextFormat + **/ + private var _underline:Boolean; + [Inspectable(category="General", enumeration="true,false")] + public function set underline(value:Boolean):void{ + _underline = value; + textFormat.underline = _underline; + textField.defaultTextFormat = _textFormat.textFormat; + invalidated = true; + } + public function get underline():Boolean{ + return _underline; + } + + + /** + * accessibilityProperties property for the textField. + * + * @see flash.text.TextField + **/ + public function get accessibilityProperties():AccessibilityProperties { + return textField.accessibilityProperties; + } + public function set accessibilityProperties(value:AccessibilityProperties):void { + if (value!=textField.accessibilityProperties) + initChange("accessibilityProperties", textField.accessibilityProperties, textField.accessibilityProperties = value, this); + } + /** + * alpha property for the textField. + * + * @see flash.text.TextField + **/ + override public function get alpha():Number{ + return textField.alpha; + } + override public function set alpha(value:Number):void { + if (value!=textField.alpha) + initChange("alpha", textField.alpha, textField.alpha = value, this); + } + /** + * antiAliasType property for the textField. + * + * @see flash.text.TextField + **/ + [Inspectable(category="General", enumeration="normal,advanced", defaultValue="normal")] + public function get antiAliasType():String { + return textField.antiAliasType; + } + public function set antiAliasType(value:String):void { + if (value!=textField.antiAliasType) + initChange("antiAliasType", textField.antiAliasType, textField.antiAliasType = value, this); + } + /** + * autoSize property for the textField. + * + * @see flash.text.TextField + **/ + [Inspectable(category="General", enumeration="none,left,right,center", defaultValue="none")] + public function get autoSize():String{ + return textField.autoSize; + } + public function set autoSize(value:String):void { + if (value!=textField.autoSize) + initChange("autoSize", textField.autoSize, textField.autoSize = value, this); + } + + + + private var _backgroundFill:IGraphicsFill; + /** + * A Degrafa Fill used in the background of this RasterText area. + * + **/ + public function get backgroundFill():IGraphicsFill{ + return _backgroundFill; + } + public function set backgroundFill(value:IGraphicsFill):void { + if (value != _backgroundFill) { + if (_backgroundFill) _backgroundFill.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler); + value.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler); + initChange("backgroundFill", _backgroundFill, _backgroundFill = value, this); + } + } + + private var _background:Boolean; + /** + * background property for the textField. Activates the backgroundFill. + * + * @see flash.text.TextField + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get background():Boolean{ + return _background; + } + public function set background(value:Boolean):void { + if (value!=_background) + initChange("background", _background, _background = value, this); + } + + /** + * Similar to backgroundColor property for the textField. A shortcut setting for backgroundFill's color property. If used, and a non-SolidFill (e.g gradientFill) is currently being + * used as a backgroundFill, then this will force creation of a new SolidFill in its place. + * + * @see flash.text.TextField + **/ + public function get backgroundColor():Object{ + return (_backgroundFill is SolidFill)? (_backgroundFill as SolidFill).color:null; + } + public function set backgroundColor(value:Object):void { + if (_backgroundFill is SolidFill) (_backgroundFill as SolidFill).color = value; + else { backgroundFill = new SolidFill(value, 1) }; + + } + + + private var _border:Boolean; + /** + * border property for the textField. + * + * @see flash.text.TextField + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get border():Boolean{ + return _border; + } + public function set border(value:Boolean):void { + if (value!=_border) + initChange("border", _border, _border = value, this); + } + /** + * borderColor property for the textField. This property has additional support for extended Degrafa color specifications, for example "red" as a color key. + * + * @see flash.text.TextField + **/ + public function get borderColor():Object{ + return textField.borderColor; + } + public function set borderColor(value:Object):void { + + var newval:uint = ColorUtil.resolveColor(value); + if (stroke is SolidStroke) SolidStroke(stroke).color = value; + else stroke = new SolidStroke(value, 1, 0); + + // if (newval!=textField.borderColor) + // initChange("borderColor", textField.borderColor, textField.borderColor = newval, this); + } + /** + * condenseWhite property for the textField. + * + * @see flash.text.TextField + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get condenseWhite():Boolean{ + return textField.condenseWhite; + } + public function set condenseWhite(value:Boolean):void { + if (value!=textField.condenseWhite) + initChange("condenseWhite", textField.condenseWhite, textField.condenseWhite = value, this); + } + + //either made private and the formazt options moved to this class(align etc.. or + //create a textFormat class that is bindable. + private function get defaultTextFormat():TextFormat{ + return textField.defaultTextFormat + } + private function set defaultTextFormat(value:TextFormat):void { + if (value!=textField.defaultTextFormat) + initChange("defaultTextFormat", textField.defaultTextFormat, textField.defaultTextFormat = value, this); + } + /** + * embedFonts property for the textField. setting the fontFamily can also change this setting in RasterText. + * + * @see flash.text.TextField + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get embedFonts():Boolean{ + return textField.embedFonts; + } + public function set embedFonts(value:Boolean):void { + if (value!=textField.embedFonts) + initChange("embedFonts", textField.embedFonts, textField.embedFonts = value, this); + } + /** + * gridFitType property for the textField. + * + * @see flash.text.TextField + **/ + [Inspectable(category="General", enumeration="none,pixel,subpixel", defaultValue="none")] + public function get gridFitType():String{ + return textField.gridFitType; + } + public function set gridFitType(value:String):void { + if (value!=textField.gridFitType) + initChange("gridFitType", textField.gridFitType, textField.gridFitType = value, this); + } + /** + * htmlText property for the textField. + * + * @see flash.text.TextField + **/ + public function get htmlText():String{ + return textField.htmlText; + } + public function set htmlText(value:String):void { + if (value!=textField.htmlText) + initChange("htmlText", textField.htmlText, textField.htmlText = value, this); + } + /** + * length property for the textField. + * + * @see flash.text.TextField + **/ + public function get length():int{ + return textField.length; + } + /** + * multiline property for the textField. + * + * @see flash.text.TextField + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get multiline():Boolean{ + return textField.multiline; + } + public function set multiline(value:Boolean):void { + if (value!=textField.multiline) + initChange("multiline", textField.multiline, textField.multiline = value, this); + } + /** + * numLines property for the textField. + * + * @see flash.text.TextField + **/ + public function get numLines():int{ + return textField.numLines; + } + /** + * sharpness property for the textField. + * + * @see flash.text.TextField + **/ + public function get sharpness():Number{ + return textField.sharpness; + } + public function set sharpness(value:Number):void { + if (value!=textField.sharpness) + initChange("sharpness", textField.sharpness, textField.sharpness = value, this); + } + /** + * styleSheet property for the textField. + * + * @see flash.text.TextField + **/ + public function get styleSheet():StyleSheet{ + return textField.styleSheet; + } + public function set styleSheet(value:StyleSheet):void { + if (value != textField.styleSheet) { + var oldSS:StyleSheet = textField.styleSheet; + textField.styleSheet = value; + invalidated = true; + initChange("styleSheet", oldSS, textField.styleSheet, this); + } + } + /** + * text property for the textField. + * + * @see flash.text.TextField + **/ + public function get text():String{ + return textField.text; + } + public function set text(value:String):void { + if (value != textField.text) { + var oldVal:String = textField.text; + textField.text = value; + invalidated = true; + initChange("text",oldVal, textField.text, this); + } + } + + /** + * textColor property for the textField. + * + * @see flash.text.TextField + **/ + public function get textColor():uint{ + return (_fill is SolidFill) ? uint((_fill as SolidFill).color):null;// textField.textColor; + } + public function set textColor(value:uint):void{ + if (!(_fill is SolidFill) || value != uint((_fill as SolidFill).color)) { + if (_fill is SolidFill) (_fill as SolidFill).color = value; + else fill = new SolidFill(value, 1); + //initChange("textColor", textField.textColor, textField.textColor = value, this); + } + } + /** + * textHeight property for the textField. + * + * @see flash.text.TextField + **/ + public function get textWidth():Number{ + return textField.textWidth; + } + /** + * thickness property for the textField. + * + * @see flash.text.TextField + **/ + public function get thickness():Number{ + return textField.thickness; + } + public function set thickness(value:Number):void { + if (value!=textField.thickness && value>=-200 && value<=200) + initChange("thickness", textField.thickness, textField.thickness = value, this); + } + /** + * wordWrap property for the textField. + * + * @see flash.text.TextField + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get wordWrap():Boolean{ + return textField.wordWrap; + } + public function set wordWrap(value:Boolean):void { + if (value!=textField.wordWrap) + initChange("wordWrap", textField.wordWrap, textField.wordWrap = value, this); + } + + /** + * Indicates the horizontal alignment of the anchored text object. Valid values are center, left, and right. + * + * @see flash.text.TextFormat + **/ + private var _halign:String="left"; + [Inspectable(category="General", enumeration="left,center,right", defaultValue="left")] + public function set halign(value:String):void{ + _halign = value; + realign(); + } + public function get halign():String{ + return _halign; + } + + /** + * Indicates the vertical alignment of the anchored text object. Valid values are center, left, and right. + * + * @see flash.text.TextFormat + **/ + private var _valign:String="top"; + [Inspectable(category="General", enumeration="top,middle,bottom", defaultValue="top")] + public function set valign(value:String):void{ + _valign = value; + realign(); + } + public function get valign():String{ + return _valign; + } + + /** + * An integer representing the horizontal anchor point + * + * @see flash.text.TextFormat + **/ + private var _anchorx:Number; + public function set anchorx(value:Number):void{ + _anchorx = value; + realign(); + } + public function get anchorx():Number{ + return _anchorx; + } + + /** + * An integer representing the vertical anchor point + * + * @see flash.text.TextFormat + **/ + private var _anchory:Number; + public function set anchory(value:Number):void{ + _anchory = value; + realign(); + } + public function get anchory():Number{ + return _anchory; + } + + public function realign() : void { + if(isNaN(anchorx)) + this.anchorx = x; + + if(isNaN(anchory)) + this.anchory = y; + + if(halign == "left") { + this.x = this.anchorx; + } + if(halign == "center") { + this.x = this.anchorx - (this.width / 2); + } + if(halign == "right") { + this.x = this.anchorx - this.width; + } + if(valign == "top") { + this.y = this.anchory; + } + if(valign == "middle") { + this.y = this.anchory - (this.height/ 2); + } + if(valign == "bottom") { + this.y = this.anchory - this.height; + } + } + } +} diff --git a/Degrafa/com/degrafa/geometry/RegularRectangle.as b/Degrafa/com/degrafa/geometry/RegularRectangle.as new file mode 100644 index 0000000..580bf0b --- /dev/null +++ b/Degrafa/com/degrafa/geometry/RegularRectangle.as @@ -0,0 +1,262 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry{ + + import com.degrafa.IGeometry; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("RegularRectangle.png")] + + [Bindable] + /** + * The RegularRectangle element draws a regular rectangle using the specified x,y, + * width and height. + * + * @see http://samples.degrafa.com/RegularRectangle/RegularRectangle.html + * + **/ + public class RegularRectangle extends Geometry implements IGeometry{ + + /** + * Constructor. + * + *

    The regular rectangle constructor accepts 4 optional arguments that define it's + * x, y, width and height.

    + * + * @param x A number indicating the upper left x-axis coordinate. + * @param y A number indicating the upper left y-axis coordinate. + * @param width A number indicating the width. + * @param height A number indicating the height. + */ + public function RegularRectangle(x:Number=NaN,y:Number=NaN,width:Number=NaN,height:Number=NaN){ + super(); + + if(x) this.x=x; + if(y) this.y=y; + if(width) this.width=width; + if(height) this.height=height; + + + } + + /** + * RegularRectangle short hand data value. + * + *

    The regular rectangle data property expects exactly 4 values x, + * y, width and height separated by spaces.

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + + if(super.data != value){ + super.data = value; + + //parse the string on the space + var tempArray:Array = value.split(" "); + + if (tempArray.length == 4){ + _x=tempArray[0]; + _y=tempArray[1]; + _width=tempArray[2]; + _height=tempArray[3]; + invalidated = true; + } + } + } + + private var _x:Number; + /** + * The x-axis coordinate of the upper left point of the regular rectangle. If not specified + * a default value of 0 is used. + **/ + override public function get x():Number{ + if(isNaN(_x)){return 0;} + return _x; + } + override public function set x(value:Number):void{ + if(_x != value){ + _x = value; + if (hasLayout) super.x=value + invalidated = true; + } + } + + + private var _y:Number; + /** + * The y-axis coordinate of the upper left point of the regular rectangle. If not specified + * a default value of 0 is used. + **/ + override public function get y():Number{ + if(isNaN(_y)){return 0;} + return _y; + } + override public function set y(value:Number):void{ + if(_y != value){ + _y = value; + if (hasLayout) super.y=value + invalidated = true; + } + } + + private var _width:Number; + /** + * The width of the regular rectangle. + **/ + [PercentProxy("percentWidth")] + override public function get width():Number{ + if(isNaN(_width)){return (hasLayout)? 1:0;} + return _width; + } + override public function set width(value:Number):void{ + if(_width != value){ + _width = value; + if (hasLayout) super.width=value + invalidated = true; + } + } + + + private var _height:Number; + /** + * The height of the regular rectangle. + **/ + [PercentProxy("percentHeight")] + override public function get height():Number{ + if(isNaN(_height)){return (hasLayout)? 1:0;} + return _height; + } + override public function set height(value:Number):void{ + if(_height != value){ + _height = value; + if (hasLayout) super.height=value + invalidated = true; + } + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + if(invalidated){ + + commandStack.length = 0; + + commandStack.addMoveTo(x,y); + commandStack.addLineTo(x+width,y); + commandStack.addLineTo(x+width,y+height) + commandStack.addLineTo(x,y+height); + commandStack.addLineTo(x,y); + + invalidated = false; + } + + } + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + + if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + + if(_width){ + tempLayoutRect.width = _width; + } + + if(_height){ + tempLayoutRect.height = _height; + } + + if(_x){ + tempLayoutRect.x = _x; + } + + if(_y){ + tempLayoutRect.y = _y; + } + + super.calculateLayout(tempLayoutRect); + _layoutRectangle = _layoutConstraint.layoutRectangle; + + + if (isNaN(_width) || isNaN(_height)) { + + //layout defined initial state: + _width = isNaN(_width)? layoutRectangle.width:_width; + _height = isNaN(_height) ? layoutRectangle.height: _height; + invalidated = true; + } + + + } + } + } + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + + //init the layout in this case done before predraw. + if (_layoutConstraint) calculateLayout(); + + //re init if required + if (invalidated) preDraw(); + + super.draw(graphics,(rc)? rc:bounds); + + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:RegularRectangle):void{ + + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke;} + if (!_x){_x = value.x;} + if (!_y){_y = value.y;} + if (!_width){_width = value.width;} + if (!_height){_height = value.height;} + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/RegularRectangle.png b/Degrafa/com/degrafa/geometry/RegularRectangle.png new file mode 100644 index 0000000..818f0c8 Binary files /dev/null and b/Degrafa/com/degrafa/geometry/RegularRectangle.png differ diff --git a/Degrafa/com/degrafa/geometry/RoundedRectangle.as b/Degrafa/com/degrafa/geometry/RoundedRectangle.as new file mode 100644 index 0000000..e48de43 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/RoundedRectangle.as @@ -0,0 +1,584 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry{ + + import com.degrafa.IGeometry; + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.geometry.command.CommandStackItem; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("RoundedRectangle.png")] + + [Bindable] + /** + * The RoundedRectangle element draws a rounded rectangle using the specified x,y, + * width, height and corner radius. + * + * @see http://degrafa.org/source/RoundedRectangle/RoundedRectangle.html + * + **/ + public class RoundedRectangle extends Geometry implements IGeometry { + /** + * private constant used to avoid unnecessary trignometry calculations + */ + private static const TRIG:Number = 0.4142135623730950488016887242097; + + /** + * Constructor. + * + *

    The rounded rectangle constructor accepts 5 optional arguments that define it's + * x, y, width, height and corner radius.

    + * + * @param x A number indicating the upper left x-axis coordinate. + * @param y A number indicating the upper left y-axis coordinate. + * @param width A number indicating the width. + * @param height A number indicating the height. + * @param cornerRadius A number indicating the radius of each corner. + */ + public function RoundedRectangle(x:Number=NaN,y:Number=NaN,width:Number=NaN,height:Number=NaN,cornerRadius:Number=NaN){ + + super(); + + if (x) this.x=x; + if (y) this.y=y; + if (width) this.width=width; + if (height) this.height=height; + if (cornerRadius)this.cornerRadius=cornerRadius; + + } + + /** + * RoundedRectangle short hand data value. + * + *

    The rounded rectangle data property expects exactly 5 values x, + * y, width, height and corner radius separated by spaces.

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + super.data = value; + + //parse the string on the space + var tempArray:Array = value.split(" "); + + if (tempArray.length == 5){ + _x=tempArray[0]; + _y=tempArray[1]; + _width=tempArray[2]; + _height=tempArray[3]; + _cornerRadius = tempArray[4]; + invalidated = true; + } + } + } + + private var _x:Number; + /** + * The x-axis coordinate of the upper left point of the rounded rectangle. If not specified + * a default value of 0 is used. + **/ + override public function get x():Number{ + if(!_x){return 0;} + return _x; + } + override public function set x(value:Number):void{ + if(_x != value){ + _x = value; + if (hasLayout) super.y=value + invalidated = true; + } + } + + + private var _y:Number; + /** + * The y-axis coordinate of the upper left point of the rounded rectangle. If not specified + * a default value of 0 is used. + **/ + override public function get y():Number{ + if(!_y){return 0;} + return _y; + } + override public function set y(value:Number):void{ + if(_y != value){ + _y = value; + if (hasLayout) super.y=value + invalidated = true; + } + } + + + private var _width:Number; + /** + * The width of the rounded rectangle. + **/ + [PercentProxy("percentWidth")] + override public function get width():Number{ + if(!_width){return (hasLayout)? 1:0;} + return _width; + } + override public function set width(value:Number):void{ + if(_width != value){ + _width = value; + if (hasLayout) super.width=value + invalidated = true; + } + } + + + private var _height:Number; + /** + * The height of the rounded rectangle. + **/ + [PercentProxy("percentHeight")] + override public function get height():Number{ + if(!_height){return (hasLayout)? 1:0;} + return _height; + } + override public function set height(value:Number):void{ + if(_height != value){ + _height = value; + if (hasLayout) super.height=value + invalidated = true; + } + } + + + private var _cornerRadius:Number; + /** + * The radius to be used for each corner of the rounded rectangle. + **/ + public function get cornerRadius():Number{ + if(!_cornerRadius){return 0;} + return _cornerRadius; + } + public function set cornerRadius(value:Number):void{ + if (_cornerRadius != value) { + var oldval:Number = _cornerRadius; + _cornerRadius = value; + invalidated = true; + } + } + + private var _permitCornerInversion:uint; + [Inspectable(category="General", enumeration="true,false")] + /** + * If any of the corner radii are negative, the corners with negative values will cut inwards if permitCornerInversion is true. + * Defaults to false, in which case negative corner radius values represent a zero corner radius. + */ + public function get permitCornerInversion():Boolean { + return _permitCornerInversion? true:false; + } + public function set permitCornerInversion(value:Boolean):void { + if (value=!_permitCornerInversion) { + _permitCornerInversion = value?1:0; + invalidated = true; + } + } + + /** + * The tight bounds of this element as represented by a Rectangle object. + **/ + override public function get bounds():Rectangle { + //exception here for now, not using commandStack bounds: + return new Rectangle(x, y, width, height); + } + + + /** + * private internal function to update the values in the commandStack for rendering. + * This approach is taken to enforce the cornerRadius rules under layout. This method handles the corner calculations and variants with cornerInversion settings + * called from the render pipeline in CommandStack and also in preDraw for when layout is not active. + * @param cStack + * @param item + * @param graphics + * @param currentIndex + * @return + */ + private function updateCommandStack(cStack:CommandStack=null, item:CommandStackItem=null, graphics:Graphics=null,currentIndex:int=0):CommandStackItem { + + var _cornerRadius:Number = cornerRadius; + + //use local vars instead of the main getters + var x:Number; + var y:Number; + var width:Number ; + var height:Number + if (hasLayout && cStack) { //handle layout variant call at render time + CommandStack.transMatrix = CommandStack.currentTransformMatrix; + + x = layoutRectangle.x; + y = layoutRectangle.y; + width = layoutRectangle.width; + height = layoutRectangle.height; + + } else { + x = this.x; + y = this.y; + width = this.width; + height = this.height; + + } + + if (!_permitCornerInversion) { + if (_cornerRadius<0) _cornerRadius=0; + } + + //set to skip + topRightCorner1.skip = topRightCorner2.skip = + bottomRightCorner1.skip = bottomRightCorner2.skip= + bottomLeftCorner1.skip = bottomLeftCorner2.skip = + topLeftCorner1.skip=topLeftCorner2.skip = (_cornerRadius)? false:true; + + if(_cornerRadius){ + + // make sure that width + h are larger than 2*cornerRadius + if (Math.abs(_cornerRadius)>Math.min(width, height)/2) { + _cornerRadius = Math.min(width, height) / 2 * (_cornerRadius < 0? -1:1); + } + + //round to nearest + // _cornerRadius = Math.round(_cornerRadius); + + } + + var adjx:Number = 0; + var adjy:Number = 0; + //apply fix for player rendering bug + if ( stroke && stroke.weight < 4 &&!stroke.pixelHinting ) { + //player rendering bug workaround: make sure the coords are offset from integer pixel values by at least 3 twips + //this seems to solve an anti-aliasing error with small stroke weights that is very obvious for RoundedRectangles + var adjbase:Number = 0.15; + var under:Boolean; + var diff:Number; + if ((stroke.weight != 2 && (diff = Math.abs(x -Math.round(x ))) < adjbase) ) { + under== x < Math.round(x); + adjx = (adjbase-diff)* (under?-1:1); + x += adjx; + } else { + if (stroke.weight == 2) { //variation - artefact seems to be centered around midpixel values with stroke.weight==2 + under = x < Math.round(x * 2 ) / 2; + if ((diff = Math.abs(x -Math.round(x * 2 ) / 2)) < adjbase) { + adjx = (adjbase-diff)* (under?-1:1); + x += adjx; + } + } + } + + under = y < Math.round(y); + if (stroke.weight!=2 && (diff = Math.abs(y -Math.round(y ))) < adjbase) { + adjy = (adjbase-diff)* (under?-1:1); + y += adjy; + } else { + if (stroke.weight == 2) { //variation - artefact seems to be centered around midpixel values with stroke.weight==2 + if ((diff = Math.abs(y -Math.round(y * 2 ) / 2)) < adjbase) { + under = y < Math.round(y * 2 ) / 2; + adjy = (adjbase-diff)* (under?-1:1); + y += adjx; + } + } + } + + } + + //dev note:through initial testing this seems fine, but may also need to test for being on a pixel boundaries as well + var bottom:Number = y + height-adjy; + var right:Number = x + width-adjx; + var innerRight:Number = right - Math.abs(_cornerRadius); + var innerLeft:Number = x + Math.abs(_cornerRadius); + var innerTop:Number = y + Math.abs(_cornerRadius); + var innerBottom:Number = bottom - Math.abs(_cornerRadius); + + // manipulate the commandStack + //basic rectangle: + startPoint.x = innerLeft; + startPoint.y = y; + topLine.x = innerRight; + topLine.y = y; + rightLine.x = right; + rightLine.y = innerBottom; + bottomLine.x = innerLeft; + bottomLine.y = bottom; + leftLine.x = x; + leftLine.y = innerTop; + + + //corners if necessary + if (_cornerRadius) { + + var cornersplitoffset:Number; + var controlPointOffset:Number; + var innerRightcx:Number; + var innerRightx:Number ; + var innerBottomcy:Number ; + var innerBottomy:Number ; + var innerLeftcx:Number ; + var innerLeftx:Number ; + var innerTopcy:Number ; + var innerTopy:Number ; + cornersplitoffset = Math.SQRT1_2 * _cornerRadius; + controlPointOffset = TRIG * _cornerRadius; + + if (_cornerRadius>0){ + innerRightcx = innerRight + controlPointOffset; + innerRightx = innerRight + cornersplitoffset; + innerBottomcy = innerBottom + controlPointOffset; + innerBottomy = innerBottom + cornersplitoffset; + innerLeftcx = innerLeft - controlPointOffset; + innerLeftx = innerLeft - cornersplitoffset; + innerTopcy = innerTop - controlPointOffset; + innerTopy = innerTop - cornersplitoffset; + topRightCorner1.cx = innerRightcx; + topRightCorner1.cy = y; + topRightCorner1.x1 = innerRightx; + topRightCorner1.y1 = innerTopy; + topRightCorner2.cx = right; + topRightCorner2.cy = innerTopcy; + topRightCorner2.x1 = right; + topRightCorner2.y1 = innerTop; + bottomRightCorner1.cx = right; + bottomRightCorner1.cy = innerBottomcy; + bottomRightCorner1.x1 = innerRightx; + bottomRightCorner1.y1 = innerBottomy; + bottomRightCorner2.cx = innerRightcx; + bottomRightCorner2.cy = bottom; + bottomRightCorner2.x1 = innerRight; + bottomRightCorner2.y1 = bottom; + bottomLeftCorner1.cx = innerLeftcx; + bottomLeftCorner1.cy = bottom; + bottomLeftCorner1.x1 = innerLeftx; + bottomLeftCorner1.y1 = innerBottomy; + bottomLeftCorner2.cx = x; + bottomLeftCorner2.cy = innerBottomcy; + bottomLeftCorner2.x1 = x; + bottomLeftCorner2.y1 = innerBottom; + topLeftCorner1.cx = x; + topLeftCorner1.cy = innerTopcy; + topLeftCorner1.x1 = innerLeftx; + topLeftCorner1.y1 = innerTopy; + topLeftCorner2.cx = innerLeftcx; + topLeftCorner2.cy = y; + topLeftCorner2.x1 = innerLeft; + topLeftCorner2.y1 = y;/**/ + } else { + innerRightcx = right+ controlPointOffset; + innerRightx = right + cornersplitoffset; + innerBottomcy = bottom+ controlPointOffset; + innerBottomy = bottom + cornersplitoffset; + innerLeftcx = x - controlPointOffset; + innerLeftx = x - cornersplitoffset; + innerTopcy = y - controlPointOffset; + innerTopy = y - cornersplitoffset; + topRightCorner1.cx = innerRight; + topRightCorner1.cy = innerTopcy; + topRightCorner1.x1 = innerRightx; + topRightCorner1.y1 = innerTopy; + topRightCorner2.cx = innerRightcx; + topRightCorner2.cy = innerTop; + topRightCorner2.x1 = right; + topRightCorner2.y1 = innerTop; + bottomRightCorner1.cx = innerRightcx; + bottomRightCorner1.cy = innerBottom; + bottomRightCorner1.x1 = innerRightx; + bottomRightCorner1.y1 = innerBottomy; + bottomRightCorner2.cx = innerRight; + bottomRightCorner2.cy = innerBottomcy; + bottomRightCorner2.x1 = innerRight; + bottomRightCorner2.y1 = bottom; + bottomLeftCorner1.cx = innerLeft; + bottomLeftCorner1.cy = innerBottomcy; + bottomLeftCorner1.x1 = innerLeftx; + bottomLeftCorner1.y1 = innerBottomy; + bottomLeftCorner2.cx = innerLeftcx; + bottomLeftCorner2.cy = innerBottom; + bottomLeftCorner2.x1 = x; + bottomLeftCorner2.y1 = innerBottom; + topLeftCorner1.cx = innerLeftcx; + topLeftCorner1.cy = innerTop + topLeftCorner1.x1 = innerLeftx; + topLeftCorner1.y1 = innerTopy; + topLeftCorner2.cx = innerLeft; + topLeftCorner2.cy = innerTopcy; + topLeftCorner2.x1 = innerLeft; + topLeftCorner2.y1 = y; + } + + } + + return commandStack.source[0]; + + } + + private var startPoint:CommandStackItem; + private var topLine:CommandStackItem; + + private var topRightCorner1:CommandStackItem; + private var topRightCorner2:CommandStackItem; + + private var rightLine:CommandStackItem; + + private var bottomRightCorner1:CommandStackItem; + private var bottomRightCorner2:CommandStackItem; + + private var bottomLine:CommandStackItem; + + private var bottomLeftCorner1:CommandStackItem + private var bottomLeftCorner2:CommandStackItem + + private var leftLine:CommandStackItem; + + private var topLeftCorner1:CommandStackItem; + private var topLeftCorner2:CommandStackItem; + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + if(invalidated){ + + if (!commandStack.length) { + //one top level item permits a single renderDelegate call + //var commandStackItem:CommandStackItem = commandStack.addItem(new CommandStackItem(CommandStackItem.COMMAND_STACK,NaN,NaN,NaN,NaN,NaN,NaN,new CommandStack())) ; + + var commandStackItem:CommandStackItem = commandStack.addItem(new CommandStackItem(CommandStackItem.DELEGATE_TO)); + commandStackItem.delegate = updateCommandStack; + + //set up quick references to manipulate items directly + startPoint=commandStack.addItem(new CommandStackItem(CommandStackItem.MOVE_TO)); + topLine = commandStack.addItem(new CommandStackItem(CommandStackItem.LINE_TO)); + + topRightCorner1=commandStack.addItem(new CommandStackItem(CommandStackItem.CURVE_TO)); + topRightCorner2=commandStack.addItem(new CommandStackItem(CommandStackItem.CURVE_TO)); + + rightLine=commandStack.addItem(new CommandStackItem(CommandStackItem.LINE_TO)); + + bottomRightCorner1=commandStack.addItem(new CommandStackItem(CommandStackItem.CURVE_TO)); + bottomRightCorner2=commandStack.addItem(new CommandStackItem(CommandStackItem.CURVE_TO)); + + bottomLine=commandStack.addItem(new CommandStackItem(CommandStackItem.LINE_TO)); + + bottomLeftCorner1=commandStack.addItem(new CommandStackItem(CommandStackItem.CURVE_TO)); + bottomLeftCorner2=commandStack.addItem(new CommandStackItem(CommandStackItem.CURVE_TO)); + + leftLine=commandStack.addItem(new CommandStackItem(CommandStackItem.LINE_TO)); + + topLeftCorner1=commandStack.addItem(new CommandStackItem(CommandStackItem.CURVE_TO)); + topLeftCorner2=commandStack.addItem(new CommandStackItem(CommandStackItem.CURVE_TO)); + + } + + updateCommandStack(); + + invalidated = false; + } + + } + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + + if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + + if(_width){ + tempLayoutRect.width = _width; + } + + if(_height){ + tempLayoutRect.height = _height; + } + + if(_x){ + tempLayoutRect.x = _x; + } + + if(_y){ + tempLayoutRect.y = _y; + } + + super.calculateLayout(tempLayoutRect); + _layoutRectangle = _layoutConstraint.layoutRectangle; + + + if (isNaN(_width) || isNaN(_height)) { + //layout defined initial state + _width = layoutRectangle.width; + _height = layoutRectangle.height; + _x = layoutRectangle.x; + _y = layoutRectangle.y; + invalidated = true; + } + + } + } + } + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + + //init the layout in this case done before predraw. + if(_layoutConstraint) calculateLayout(); + + //re init if required + if (invalidated) preDraw(); + + super.draw(graphics,(rc)? rc:bounds); + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:RoundedRectangle):void{ + + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke;} + if (!_x){_x = value.x;} + if (!_y){_y = value.y;} + if (!_width){_width = value.width;} + if (!_height){_height = value.height;} + if (!_cornerRadius) { _cornerRadius = value.cornerRadius; } + if (isNaN(_permitCornerInversion)) { _permitCornerInversion = value.permitCornerInversion?1:0; } + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/RoundedRectangle.png b/Degrafa/com/degrafa/geometry/RoundedRectangle.png new file mode 100644 index 0000000..8374c8e Binary files /dev/null and b/Degrafa/com/degrafa/geometry/RoundedRectangle.png differ diff --git a/Degrafa/com/degrafa/geometry/RoundedRectangleComplex.as b/Degrafa/com/degrafa/geometry/RoundedRectangleComplex.as new file mode 100644 index 0000000..4f551cf --- /dev/null +++ b/Degrafa/com/degrafa/geometry/RoundedRectangleComplex.as @@ -0,0 +1,687 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry{ + + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.geometry.command.CommandStackItem; + import com.degrafa.IGeometry; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("RoundedRectangleComplex.png")] + + [Bindable] + /** + * The RoundedRectangleComplex element draws a complex rounded rectangle using the specified x,y, + * width, height and top left radius, top right radius, bottom left radius and bottom right + * radius. + * + * @see http://degrafa.org/source/RoundedRectangleComplex/RoundedRectangleComplex.html + * + **/ + public class RoundedRectangleComplex extends Geometry implements IGeometry{ + /** + * private constant used to avoid unnecessary trignometry calculations + */ + private static const TRIG:Number = 0.4142135623730950488016887242097; + + + /** + * Constructor. + * + *

    The complex rounded rectangle constructor accepts 8 optional arguments that define it's + * x, y, width, height, top left radius, top right radius, bottom left radius + * and bottom right radius.

    + * + * @param x A number indicating the upper left x-axis coordinate. + * @param y A number indicating the upper left y-axis coordinate. + * @param width A number indicating the width. + * @param height A number indicating the height. + * @param topLeftRadius A number indicating the top left corner radius. + * @param topRightRadius A number indicating the top right corner radius. + * @param bottomLeftRadius A number indicating the bottom left corner radius. + * @param bottomRightRadius A number indicating the bottom right corner radius. + */ + public function RoundedRectangleComplex(x:Number=NaN,y:Number=NaN,width:Number=NaN, + height:Number=NaN,topLeftRadius:Number=NaN,topRightRadius:Number=NaN, + bottomLeftRadius:Number=NaN,bottomRightRadius:Number=NaN){ + + super(); + + if (x) this.x=x; + if (y) this.y=y; + if (width) this.width=width; + if (height) this.height=height; + if (topLeftRadius) this.topLeftRadius=topLeftRadius; + if (topRightRadius) this.topRightRadius=topRightRadius; + if (bottomLeftRadius) this.bottomLeftRadius=bottomLeftRadius; + if (bottomRightRadius) this.bottomRightRadius=bottomRightRadius; + } + + /** + * RoundedRectangleComplex short hand data value. + * + *

    The complex rounded rectangle data property expects exactly 8 values x, + * y, width, height, top left radius, top right radius, bottom left radius + * and bottom right radius separated by spaces.

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + super.data = value; + + //parse the string on the space + var tempArray:Array = value.split(" "); + + if (tempArray.length == 8){ + _x=tempArray[0]; + _y=tempArray[1]; + _width=tempArray[2]; + _height=tempArray[3]; + _topLeftRadius=tempArray[4]; + _topRightRadius=tempArray[5]; + _bottomLeftRadius=tempArray[6]; + _bottomRightRadius = tempArray[7]; + invalidated = true; + } + } + } + + private var _x:Number; + /** + * The x-axis coordinate of the upper left point of the complex rounded rectangle. If not specified + * a default value of 0 is used. + **/ + override public function get x():Number{ + if(!_x){return 0;} + return _x; + } + override public function set x(value:Number):void{ + if(_x != value){ + _x = value; + if (hasLayout) super.x=value + invalidated = true; + } + } + + + private var _y:Number; + /** + * The y-axis coordinate of the upper left point of the complex rounded rectangle. If not specified + * a default value of 0 is used. + **/ + override public function get y():Number{ + if(!_y){return 0;} + return _y; + } + override public function set y(value:Number):void{ + if(_y != value){ + _y = value; + if (hasLayout) super.y=value + invalidated = true; + } + } + + + private var _width:Number; + /** + * The width of the complex rounded rectangle. + **/ + [PercentProxy("percentWidth")] + override public function get width():Number{ + if(!_width){return (hasLayout)? 1:0;} + return _width; + } + override public function set width(value:Number):void{ + if(_width != value){ + _width = value; + if (hasLayout) super.width=value + invalidated = true; + } + } + + + private var _height:Number; + /** + * The height of the complex rounded rectangle. + **/ + [PercentProxy("percentHeight")] + override public function get height():Number{ + if(!_height){return (hasLayout)? 1:0;} + return _height; + } + override public function set height(value:Number):void{ + if(_height != value){ + _height = value; + if (hasLayout) super.height=value + invalidated = true; + } + } + + + private var _topLeftRadius:Number; + /** + * The radius for the top left corner of the complex rounded rectangle. + **/ + public function get topLeftRadius():Number{ + if(!_topLeftRadius){return 0;} + return _topLeftRadius; + } + public function set topLeftRadius(value:Number):void{ + if(_topLeftRadius != value){ + _topLeftRadius = value; + invalidated = true; + } + + } + + + private var _topRightRadius:Number; + /** + * The radius for the top right corner of the complex rounded rectangle. + **/ + public function get topRightRadius():Number{ + if(!_topRightRadius){return 0;} + return _topRightRadius; + } + public function set topRightRadius(value:Number):void{ + if(_topRightRadius != value){ + _topRightRadius = value; + invalidated = true; + } + + } + + + private var _bottomLeftRadius:Number; + /** + * The radius for the bottom left corner of the complex rounded rectangle. + **/ + public function get bottomLeftRadius():Number{ + if(!_bottomLeftRadius){return 0;} + return _bottomLeftRadius; + } + public function set bottomLeftRadius(value:Number):void { + if(_bottomLeftRadius != value){ + _bottomLeftRadius = value; + invalidated = true; + } + } + + + private var _bottomRightRadius:Number; + /** + * The radius for the bottom right corner of the complex rounded rectangle. + **/ + public function get bottomRightRadius():Number{ + if(!_bottomRightRadius){return 0;} + return _bottomRightRadius; + } + public function set bottomRightRadius(value:Number):void{ + if(_bottomRightRadius != value){ + _bottomRightRadius = value; + invalidated = true; + } + + } + + /** + * The tight bounds of this element as represented by a Rectangle object. + **/ + override public function get bounds():Rectangle{ + return new Rectangle(x, y, width, height); + } + + private var _permitCornerInversion:uint; + [Inspectable(category="General", enumeration="true,false")] + /** + * If any of the corner radii are negative, the corners with negative values will cut inwards if permitCornerInversion is true. + * Defaults to false, in which case negative corner radius values represent a zero corner radius. + */ + public function get permitCornerInversion():Boolean { + return _permitCornerInversion? true:false; + } + public function set permitCornerInversion(value:Boolean):void { + if (value=!_permitCornerInversion) { + _permitCornerInversion = value?1:0; + invalidated = true; + } + } + + + /** + * private internal function to update the values in the commandStack for rendering. + * This approach is taken to enforce the cornerRadius rules under layout. This method handles the corner calculations and variants with cornerInversion settings + * called from the render pipeline in CommandStack and also in preDraw for when layout is not active. + * @param cStack + * @param item + * @param graphics + * @param currentIndex + * @return + */ + private function updateCommandStack(cStack:CommandStack=null, item:CommandStackItem=null, graphics:Graphics=null,currentIndex:int=0):CommandStackItem { + + //use local vars instead of the main getters + var x:Number; + var y:Number; + var width:Number ; + var height:Number + if (hasLayout && cStack) { //handle layout variant call at render time + CommandStack.transMatrix = CommandStack.currentTransformMatrix; + x = layoutRectangle.x; + y = layoutRectangle.y; + width = layoutRectangle.width; + height = layoutRectangle.height; + + } else { + x = this.x; + y = this.y; + width = this.width; + height = this.height; + } + + + // make sure that cornerRadii fit within the bounds of the rectangle + var minSize:Number = Math.min(width, height)*.5; + var topLeftRadius:Number = Math.abs(this.topLeftRadius) < minSize ? this.topLeftRadius : minSize* (this.topLeftRadius<0?-1:1) ; + var topRightRadius:Number = Math.abs(this.topRightRadius) < minSize ? this.topRightRadius :minSize* (this.topRightRadius<0?-1:1); + var bottomLeftRadius:Number = Math.abs(this.bottomLeftRadius) < minSize ? this.bottomLeftRadius : minSize* (this.bottomLeftRadius<0?-1:1); + var bottomRightRadius:Number = Math.abs(this.bottomRightRadius) < minSize ? this.bottomRightRadius : minSize* (this.bottomRightRadius<0?-1:1); + + //don't permit negative values from the corner radii unless permitCornerInversion is true + if (!_permitCornerInversion) { + if (topLeftRadius < 0) topLeftRadius = 0; + if (topRightRadius < 0) topRightRadius = 0; + if (bottomLeftRadius < 0) bottomLeftRadius = 0; + if (bottomRightRadius < 0) bottomRightRadius = 0; + } + + var adjx:Number = 0; + var adjy:Number = 0; + //apply fix for player rendering bug + if ( stroke && stroke.weight < 4 ) { + //player rendering bug workaround: make sure the coords are offset from integer pixel values by at least 3 twips + //this seems to solve an anti-aliasing error with small stroke weights that is very obvious for RoundedRectangles + var adjbase:Number = 0.15; + var under:Boolean; + var diff:Number; + if ((stroke.weight != 2 && (diff = Math.abs(x -Math.round(x ))) < adjbase) ) { + under== x < Math.round(x); + adjx = (adjbase-diff)* (under?-1:1); + x += adjx; + } else { + if (stroke.weight == 2) { //variation - artefact seems to be centered around midpixel values with stroke.weight==2 + under = x < Math.round(x * 2 ) / 2; + if ((diff = Math.abs(x -Math.round(x * 2 ) / 2)) < adjbase) { + adjx = (adjbase-diff)* (under?-1:1); + x += adjx; + } + } + } + + under = y < Math.round(y); + if (stroke.weight!=2 && (diff = Math.abs(y -Math.round(y ))) < adjbase) { + adjy = (adjbase-diff)* (under?-1:1); + y += adjy; + } else { + if (stroke.weight == 2) { //variation - artefact seems to be centered around midpixel values with stroke.weight==2 + if ((diff = Math.abs(y -Math.round(y * 2 ) / 2)) < adjbase) { + under = y < Math.round(y * 2 ) / 2; + adjy = (adjbase-diff)* (under?-1:1); + y += adjx; + } + } + } + } + //dev note:through initial testing this seems fine, but may also need to test for being on a pixel boundaries as well + var bottom:Number = y + height -adjx; + var right:Number = x + width -adjy; + var innerRightTop:Number = right - Math.abs(topRightRadius); + var innerRightBottom:Number = right - Math.abs(bottomRightRadius); + var innerLeftTop:Number = x + Math.abs(topLeftRadius); + var innerLeftBottom:Number = x + Math.abs(bottomLeftRadius); + var innerTopLeft:Number = y + Math.abs(topLeftRadius); + var innerTopRight:Number = y + Math.abs(topRightRadius); + var innerBottomLeft:Number = bottom - Math.abs(bottomLeftRadius); + var innerBottomRight:Number = bottom - Math.abs(bottomRightRadius); + // manipulate the commandStack but do not invalidate its bounds + //basic rectangle: + startPoint.x = innerLeftTop; + startPoint.y = y; + topLine.x = innerRightTop; + topLine.y = y; + rightLine.x = right; + rightLine.y = innerBottomRight; + bottomLine.x = innerLeftBottom; + bottomLine.y = bottom; + leftLine.x = x; + leftLine.y = innerTopLeft; + + //set to skip + topRightCorner1.skip = topRightCorner2.skip = topRightRadius? false:true; + bottomRightCorner1.skip = bottomRightCorner2.skip = bottomRightRadius? false:true; + bottomLeftCorner1.skip = bottomLeftCorner2.skip = bottomLeftRadius? false:true; + topLeftCorner1.skip=topLeftCorner2.skip = topLeftRadius? false:true; + //corners as necessary + + var cornersplitoffset:Number; + var controlPointOffset:Number + var c1x:Number; + var c1y:Number; + var c2x:Number; + var c2y:Number; + var x1:Number; + var y1:Number; + //var manipulate:CommandStackItem; + //topRightCorner + if (topRightRadius) { + cornersplitoffset= Math.SQRT1_2 * topRightRadius; + controlPointOffset = TRIG * topRightRadius; + if (topRightRadius < 0) { + //inversion + c1x = innerRightTop; + c1y = y- controlPointOffset; + x1 = right + cornersplitoffset; + y1 = y - cornersplitoffset; + c2x = right +controlPointOffset; + c2y = innerTopRight; + } else { + //normal + c1x = innerRightTop + controlPointOffset; + c1y = y; + c2x = right; + c2y = innerTopRight - controlPointOffset; + x1 = innerRightTop + cornersplitoffset; + y1 = innerTopRight - cornersplitoffset; + } + topRightCorner1.cx = c1x; + topRightCorner1.cy = c1y; + topRightCorner1.x1 = x1; + topRightCorner1.y1 = y1; + topRightCorner2.cx = c2x; + topRightCorner2.cy = c2y; + topRightCorner2.x1 = right; + topRightCorner2.y1 = innerTopRight; + } + + //bottomRightCorner + if (bottomRightRadius) { + cornersplitoffset= Math.SQRT1_2 * bottomRightRadius; + controlPointOffset = TRIG * bottomRightRadius; + if (bottomRightRadius < 0) { + //inversion + c1x = right+ controlPointOffset; + c1y = innerBottomRight; + x1 = right + cornersplitoffset; + y1 = bottom + cornersplitoffset; + c2x = innerRightBottom; + c2y = bottom+controlPointOffset; + } else { + //normal + c1x = right; + c1y = innerBottomRight + controlPointOffset; + c2x = innerRightBottom + controlPointOffset; + c2y = bottom; + x1 = innerRightBottom + cornersplitoffset; + y1 = innerBottomRight + cornersplitoffset; + } + //manipulate: + bottomRightCorner1.cx = c1x; + bottomRightCorner1.cy = c1y; + bottomRightCorner1.x1 = x1; + bottomRightCorner1.y1 = y1; + bottomRightCorner2.cx = c2x; + bottomRightCorner2.cy = c2y; + bottomRightCorner2.x1 = innerRightBottom; + bottomRightCorner2.y1 = bottom; + } + + //bottomLeftCorner + if (bottomLeftRadius) { + cornersplitoffset= Math.SQRT1_2 * bottomLeftRadius; + controlPointOffset = TRIG * bottomLeftRadius; + if (bottomLeftRadius < 0) { + //inversion + c1x = innerLeftBottom; + c1y = bottom+controlPointOffset; + x1 = x - cornersplitoffset; + y1 = bottom + cornersplitoffset; + c2x = x-controlPointOffset; + c2y = innerBottomLeft; + } else { + //normal + c1x = innerLeftBottom - controlPointOffset; + c1y = bottom; + c2x = x; + c2y = innerBottomLeft + controlPointOffset; + x1 = innerLeftBottom - cornersplitoffset; + y1 = innerBottomLeft + cornersplitoffset; + } + //manipulate: + bottomLeftCorner1.cx = c1x; + bottomLeftCorner1.cy = c1y; + bottomLeftCorner1.x1 = x1; + bottomLeftCorner1.y1 = y1; + bottomLeftCorner2.cx = c2x; + bottomLeftCorner2.cy = c2y; + bottomLeftCorner2.x1 = x; + bottomLeftCorner2.y1 = innerBottomLeft; + } + + + //topLeftCorner + if (topLeftRadius) { + cornersplitoffset= Math.SQRT1_2 * topLeftRadius; + controlPointOffset = TRIG * topLeftRadius; + if (topLeftRadius < 0) { + //inversion + c1x = x-controlPointOffset; + c1y = innerTopLeft; + x1 = x - cornersplitoffset; + y1 = y - cornersplitoffset; + c2x = innerLeftTop; + c2y = y-controlPointOffset; + } else { + //normal + c1x = x; + c1y = innerTopLeft - controlPointOffset; + c2x = innerLeftTop - controlPointOffset; + c2y = y; + x1 = innerLeftTop - cornersplitoffset; + y1 = innerTopLeft - cornersplitoffset; + } + //manipulate: + topLeftCorner1.cx = c1x; + topLeftCorner1.cy = c1y; + topLeftCorner1.x1 = x1; + topLeftCorner1.y1 = y1; + topLeftCorner2.cx = c2x; + topLeftCorner2.cy = c2y; + topLeftCorner2.x1 = innerLeftTop; + topLeftCorner2.y1 = y; + } + + return commandStack.source[0]; + + } + + /** Exposed as protected (from private) + * so subclasses can have access to these as reference points + * regardless of what order they get added to the + * command stack. 6/10/2009 twgonzalez + */ + + protected var startPoint:CommandStackItem; + protected var topLine:CommandStackItem; + + protected var topRightCorner1:CommandStackItem; + protected var topRightCorner2:CommandStackItem; + + protected var rightLine:CommandStackItem; + + protected var bottomRightCorner1:CommandStackItem; + protected var bottomRightCorner2:CommandStackItem; + + protected var bottomLine:CommandStackItem; + + protected var bottomLeftCorner1:CommandStackItem + protected var bottomLeftCorner2:CommandStackItem + + protected var leftLine:CommandStackItem; + + protected var topLeftCorner1:CommandStackItem; + protected var topLeftCorner2:CommandStackItem; + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + if(invalidated){ + + if (!commandStack.length) { + //one top level item permits a single renderDelegate call + //var commandStackItem:CommandStackItem = commandStack.addItem(new CommandStackItem(CommandStackItem.COMMAND_STACK,NaN,NaN,NaN,NaN,NaN,NaN,new CommandStack())) ; + + var commandStackItem:CommandStackItem = commandStack.addItem(new CommandStackItem(CommandStackItem.DELEGATE_TO)); + commandStackItem.delegate = updateCommandStack; + + //set up quick references to manipulate items directly + startPoint=commandStack.addItem(new CommandStackItem(CommandStackItem.MOVE_TO)); + topLine = commandStack.addItem(new CommandStackItem(CommandStackItem.LINE_TO)); + + topRightCorner1=commandStack.addItem(new CommandStackItem(CommandStackItem.CURVE_TO)); + topRightCorner2=commandStack.addItem(new CommandStackItem(CommandStackItem.CURVE_TO)); + + rightLine=commandStack.addItem(new CommandStackItem(CommandStackItem.LINE_TO)); + + bottomRightCorner1=commandStack.addItem(new CommandStackItem(CommandStackItem.CURVE_TO)); + bottomRightCorner2=commandStack.addItem(new CommandStackItem(CommandStackItem.CURVE_TO)); + + bottomLine=commandStack.addItem(new CommandStackItem(CommandStackItem.LINE_TO)); + + bottomLeftCorner1=commandStack.addItem(new CommandStackItem(CommandStackItem.CURVE_TO)); + bottomLeftCorner2=commandStack.addItem(new CommandStackItem(CommandStackItem.CURVE_TO)); + + leftLine=commandStack.addItem(new CommandStackItem(CommandStackItem.LINE_TO)); + + topLeftCorner1=commandStack.addItem(new CommandStackItem(CommandStackItem.CURVE_TO)); + topLeftCorner2=commandStack.addItem(new CommandStackItem(CommandStackItem.CURVE_TO)); + + } + updateCommandStack(); + + invalidated = false; + } + + } + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + + if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + + if(_width){ + tempLayoutRect.width = _width; + } + + if(_height){ + tempLayoutRect.height = _height; + } + + if(_x){ + tempLayoutRect.x = _x; + } + + if(_y){ + tempLayoutRect.y = _y; + } + + super.calculateLayout(tempLayoutRect); + _layoutRectangle = _layoutConstraint.layoutRectangle; + + + if (isNaN(_width) || isNaN(_height)) { + //layout defined initial state + _width = _layoutRectangle.width; + _height = _layoutRectangle.height; + _x = isNaN(_x)? _layoutRectangle.x:_x; + _y = isNaN(_y)?_layoutRectangle.y:_y; + invalidated = true; + } + + } + } + } + + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + + //init the layout in this case done before predraw. + if(_layoutConstraint) calculateLayout(); + + //re init if required + if (invalidated) preDraw(); + + super.draw(graphics, (rc)? rc:bounds); + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:RoundedRectangleComplex):void{ + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke;} + if (!_x){_x = value.x;} + if (!_y){_y = value.y;} + if (!_width){_width = value.width;} + if (!_height){_height = value.height;} + if (!_bottomLeftRadius){_bottomLeftRadius = value.bottomLeftRadius;} + if (!_bottomRightRadius){_bottomRightRadius = value.bottomRightRadius;} + if (!_topLeftRadius){_topLeftRadius = value.topLeftRadius;} + if (!_topRightRadius) { _topRightRadius = value.topRightRadius; } + if (isNaN(_permitCornerInversion)) { _permitCornerInversion = value.permitCornerInversion?1:0; } + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/RoundedRectangleComplex.png b/Degrafa/com/degrafa/geometry/RoundedRectangleComplex.png new file mode 100644 index 0000000..e912443 Binary files /dev/null and b/Degrafa/com/degrafa/geometry/RoundedRectangleComplex.png differ diff --git a/Degrafa/com/degrafa/geometry/SuperShape2D.as b/Degrafa/com/degrafa/geometry/SuperShape2D.as new file mode 100644 index 0000000..fbf6314 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/SuperShape2D.as @@ -0,0 +1,282 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Credit to Mr. Bourke see link for details. +// http://local.wasp.uwa.edu.au/~pbourke/geometry/supershape/ +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry{ + + import flash.display.Graphics; + import flash.geom.Rectangle; + + /** + * The SuperShape2D element draws a shape using the specified parameters. + * + * Credit to Mr. Bourke with a stellar algorithm. + * @see http://local.wasp.uwa.edu.au/~pbourke/geometry/supershape/ + * + * Also Jim Has elaborated on the algorithm with some helpful links. + * @see http://algorithmist.wordpress.com/2009/06/10/supershapes-in-degrafa/ + * + **/ + public class SuperShape2D extends Geometry{ + + /** + * Constructor. + * + *

    The SuperShape2D constructor accepts 6 optional arguments that define it's + * properties.

    + * + * @param n1 A number indicating the n1 value. + * @param n2 A number indicating the n2 value. + * @param n3 A number indicating the n3 value. + * @param m A number indicating the m value. + * @param detail A number indicating the level of detail. + * @param range A number indicating the range. + */ + public function SuperShape2D(n1:Number=NaN,n2:Number=NaN,n3:Number=NaN + ,m:Number=NaN,detail:int=-1,range:int=-1){ + super(); + + if (n1) this.n1=n1; + if (n2) this.n2=n2; + if (n3) this.n3=n3; + if (m) this.m=m; + if (detail!=-1) this.detail=detail; + if (range!=-1) this.range=range; + + } + + /** + * SuperShape2D short hand data value. + * + *

    The SuperShape2D data property expects exactly 6 values n1, + * n2,n3,m,detail and range separated by spaces.

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + + //parse the string + var tempArray:Array = value.split(" "); + + if (tempArray.length == 6) + { + super.data = value; + n1= tempArray[0]; + n2= tempArray[1]; + n3= tempArray[2]; + m= tempArray[3]; + detail= tempArray[4]; + range = tempArray[5]; + invalidated = true; + } + } + } + + private var _n1:Number=1; + /** + * The n1 paramater of the super shape. + **/ + public function get n1():Number{ + return _n1; + } + public function set n1(value:Number):void{ + if(_n1 != value){ + _n1 = value; + invalidated = true; + } + } + + private var _n2:Number=1; + /** + * The n2 paramater of the super shape. + **/ + public function get n2():Number{ + return _n2; + } + public function set n2(value:Number):void{ + if(_n2 != value){ + _n2 = value; + invalidated = true; + } + } + + private var _n3:Number=1; + /** + * The n3 paramater of the super shape. + **/ + public function get n3():Number{ + return _n3; + } + public function set n3(value:Number):void{ + if(_n3 != value){ + _n3 = value; + invalidated = true; + } + } + + private var _m:Number=4; + /** + * The m paramater of the super shape. + **/ + public function get m():Number{ + return _m; + } + public function set m(value:Number):void{ + if(_m != value){ + _m = value; + invalidated = true; + } + } + + private var _detail:int=4; + /** + * The detail of the super shape. The number of points to be used. + **/ + public function get detail():int{ + return _detail; + } + public function set detail(value:int):void{ + if(_detail != value){ + _detail = value; + invalidated = true; + } + } + + private var _range:int=2; + /** + * The range of the super shape. + **/ + public function get range():int{ + return _range; + } + public function set range(value:int):void{ + if(_range != value){ + _range = value; + invalidated = true; + } + } + + /** + * The core of the formula. Full credits to Mr. bourke. + * + * @see http://local.wasp.uwa.edu.au/~pbourke/geometry/supershape/ + **/ + private function eval(phi:Number,isLine:Boolean=true):void{ + + var a:Number = 1; + var b:Number = 1; + + var t1:Number = Math.cos(_m * phi / 4) / a; + t1 = Math.abs(t1); + t1 = Math.pow(t1, _n2); + + var t2:Number = Math.sin(_m * phi / 4) / b; + t2 = Math.abs(t2); + t2 = Math.pow(t2, _n3); + + var r:Number = Math.pow(t1 + t2, 1 / _n1); + + if (Math.abs(r) != 0) { + r = 1 / r; + + if(isLine){ + commandStack.addLineTo(r * Math.cos(phi),r * Math.sin(phi)) + } + else{//move to + commandStack.addMoveTo(r * Math.cos(phi),r * Math.sin(phi)) + } + } + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + if(invalidated){ + + commandStack.source.length = 0; + + eval(0,false); + + var i:int = 0; + + while (++i <= _detail) { + eval(range * Math.PI * (i / _detail),true); + } + + invalidated = false; + } + } + + + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + + //default to bounds if no width or height is set + //and we have layout + if(isNaN(_layoutConstraint.width)){ + tempLayoutRect.width = bounds.width; + } + + if(isNaN(_layoutConstraint.height)){ + tempLayoutRect.height = bounds.height; + } + + super.calculateLayout(tempLayoutRect); + + _layoutRectangle = _layoutConstraint.layoutRectangle; + + } + } + } + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + + //re init if required + if (invalidated) preDraw(); + + //init the layout in this case done after predraw. + if (_layoutConstraint) calculateLayout(); + + super.draw(graphics,(rc)? rc:bounds); + } + } +} diff --git a/Degrafa/com/degrafa/geometry/VerticalLine.as b/Degrafa/com/degrafa/geometry/VerticalLine.as new file mode 100644 index 0000000..67ba551 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/VerticalLine.as @@ -0,0 +1,248 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry{ + + import com.degrafa.IGeometry; + import com.degrafa.core.IGraphicsFill; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + //excluded here + [Exclude(name="fill", kind="property")] + [Exclude(name="width", kind="property")] + [Exclude(name="percentWidth", kind="property")] + [Exclude(name="maxWidth", kind="property")] + [Exclude(name="minWidth", kind="property")] + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("VerticalLine.png")] + + [Bindable] + /** + * The VerticalLine element draws a vertical line using the specified x, y, + * and y1 coordinate values. + * + * @see http://samples.degrafa.com/VerticalLine/VerticalLine.html + * + **/ + public class VerticalLine extends Geometry implements IGeometry{ + + /** + * Constructor. + * + *

    The vertical line constructor accepts 3 optional arguments that define it's + * center point and radius.

    + * + * @param x A number indicating the starting x-axis coordinate. + * @param y A number indicating the starting y-axis coordinate. + * @param y1 A number indicating the ending y-axis coordinate. + */ + public function VerticalLine(x:Number=NaN,y:Number=NaN,y1:Number=NaN){ + super(); + + if (x) this.x=x; + if (y) this.y=y; + if (y1) this.y1=y1; + + } + + //excluded here + override public function set fill(value:IGraphicsFill):void{} + override public function set width(value:Number):void{} + override public function set percentWidth(value:Number):void{} + override public function set maxWidth(value:Number):void{} + override public function set minWidth(value:Number):void{} + + + /** + * VerticalLine short hand data value. + * + *

    The vertical line data property expects exactly 3 values x, + * y and y1 separated by spaces.

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + super.data = value; + + //parse the string on the space + var tempArray:Array = value.split(" "); + + if (tempArray.length == 3){ + _x=tempArray[0]; + _y=tempArray[1]; + _y1 = tempArray[2]; + invalidated = true; + } + + + } + + } + + + private var _x:Number; + /** + * The x-coordinate of the start point of the line. If not specified + * a default value of 0 is used. + **/ + override public function get x():Number{ + if(!_x){return 0;} + return _x; + } + override public function set x(value:Number):void{ + if(_x != value){ + _x = value; + invalidated = true; + } + } + + + private var _y:Number; + /** + * The y-coordinate of the start point of the line. If not specified + * a default value of 0 is used. + **/ + override public function get y():Number{ + if(!_y){return 0;} + return _y; + } + override public function set y(value:Number):void{ + if(_y != value){ + _y = value; + invalidated = true; + } + } + + + private var _y1:Number; + /** + * The y-coordinate of the end point of the line. If not specified + * a default value of 0 is used. + **/ + public function get y1():Number{ + if(isNaN(_y1)){return (hasLayout)? 0.0001:0;} + return _y1; + } + public function set y1(value:Number):void{ + if(_y1 != value){ + _y1 = value; + invalidated = true; + } + + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + if(invalidated){ + + commandStack.length=0; + + commandStack.addMoveTo(x,y); + commandStack.addLineTo(x,y1); + + invalidated = false; + } + + } + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + + if (_layoutConstraint) { + + if (_layoutConstraint.invalidated){ + + var tempLayoutRect:Rectangle = new Rectangle(0,0,0.0001,0.0001); + + if(!isNaN(_y1) || !isNaN(_y)){ + if (_y1?_y1:0 - _y?_y:0) tempLayoutRect.height = Math.abs(_y1?_y1:0 - _y?_y:0); + tempLayoutRect.y = Math.min(_y?_y:0,_y1?_y1:0); + } + + if(_x){ + tempLayoutRect.x = _x; + } + + + super.calculateLayout(tempLayoutRect); + //update the local layoutRectangle + _layoutRectangle = _layoutConstraint.layoutRectangle; + //force the layout to minimum width in this case + _layoutRectangle.width = 0.0001; + + if (isNaN(_y1)) { + //layout defined initial settings + if (isNaN(_x)) _x = _layoutRectangle.x; + if (isNaN(_y)) _y = _layoutRectangle.y; + _y1 = _layoutRectangle.bottom; + invalidated = true; + } + } + } + } + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + + //init the layout in this case done before predraw. + if (_layoutConstraint) calculateLayout(); + + //re init if required + if (invalidated) preDraw(); + + super.draw(graphics,(rc)? rc:bounds); + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:VerticalLine):void{ + + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke;} + if (!_x){_x = value.x;} + if (!_y){_y = value.y;} + if (!_y1){_y1 = value.y1;} + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/VerticalLine.png b/Degrafa/com/degrafa/geometry/VerticalLine.png new file mode 100644 index 0000000..a27a618 Binary files /dev/null and b/Degrafa/com/degrafa/geometry/VerticalLine.png differ diff --git a/Degrafa/com/degrafa/geometry/autoshapes/ArrowAutoShape.as b/Degrafa/com/degrafa/geometry/autoshapes/ArrowAutoShape.as new file mode 100644 index 0000000..aa830cb --- /dev/null +++ b/Degrafa/com/degrafa/geometry/autoshapes/ArrowAutoShape.as @@ -0,0 +1,157 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.autoshapes{ + + //only need 2 offsets here so need to exclude + [Exclude(name="offset3", kind="property")] + [Exclude(name="offset3Percent", kind="property")] + + [Exclude(name="offset4", kind="property")] + [Exclude(name="offset4Percent", kind="property")] + + /** + * The ArrowAutoShape element draws a basic arrow. + **/ + public class ArrowAutoShape extends AutoShapeTypeOffsets{ + + /** + * Constructor. + * + *

    The ArrowAutoShape constructor accepts 2 optional + * argument that defines it's properties.

    + * + * @param offset1 A number indicating the offset1. + * @param offset2 A number indicating the offset2. + */ + public function ArrowAutoShape(offset1:Number=NaN,offset2:Number=NaN){ + super(); + if (offset1) this.offset1=offset1; + if (offset2) this.offset2=offset2; + + } + + /** + * ArrowAutoShape short hand data value. + * + *

    The ArrowAutoShape data property expects exactly 2 values for offsets

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + + //parse the string + var tempArray:Array = value.split(" "); + + if (tempArray.length == 1) + { + super.data = value; + _offset1= tempArray[0]; + _offset2= tempArray[1]; + + invalidated = true; + } + } + } + + /** + * Draw the ArrowAutoShape part(s) based on the parameters. + */ + override protected function preDrawPart():void{ + + //store local to calculate + var _Offset1:Number=_offset1; + var _Offset2:Number=_offset2; + + //calc desired final offset 1 + if (isNaN(_Offset1) && hasLayout && isNaN(_offset1Percent)){ + if(_layoutRectangle.width){ + _Offset1 = _layoutRectangle.height/4; + } + else{ + _Offset1 = 0; + } + } + else if (!isNaN(_offset1Percent) && hasLayout){ + if(_offset1Percent >= 1){ + _Offset1 = (_offset1Percent/100)*_layoutRectangle.width; + } + else{ + _Offset1 = _offset1Percent*_layoutRectangle.width; + } + } + else{ + if(isNaN(_Offset1)){ + _Offset1 = 0; + } + } + + //calc desired final offset 2 + if (isNaN(_Offset2) && hasLayout && isNaN(_offset2Percent)){ + if(_layoutRectangle.width){ + _Offset2 = _layoutRectangle.height/4; + } + else{ + _Offset2 = 0; + } + } + else if (!isNaN(_offset2Percent) && hasLayout){ + if(_offset2Percent >= 1){ + _Offset2 = (_offset2Percent/100)*_layoutRectangle.width; + } + else{ + _Offset2 = _offset2Percent*_layoutRectangle.width; + } + } + else{ + if(isNaN(_Offset2)){ + _Offset2 = 0; + } + } + + //Arrow with point begin drawing + commandStack.addMoveTo(0, _Offset1); + commandStack.addLineTo(_Offset2, _Offset1); + commandStack.addLineTo(_Offset2, 0); + commandStack.addLineTo(_layoutRectangle.width-_Offset2, 0); + commandStack.addLineTo(_layoutRectangle.width-_Offset2, _Offset1); + commandStack.addLineTo(_layoutRectangle.width, _Offset1); + commandStack.addLineTo(_layoutRectangle.width/2, _layoutRectangle.height); + commandStack.addLineTo(0, _Offset1); + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:ArrowAutoShape):void{ + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke} + if (!_offset1){_offset1 = value.offset1} + if (!_offset2){_offset2 = value.offset2} + if (!_offset1Percent){_offset1Percent = value.offset1Percent} + if (!_offset2Percent){_offset2Percent = value.offset2Percent} + + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/autoshapes/AutoShape.as b/Degrafa/com/degrafa/geometry/autoshapes/AutoShape.as new file mode 100644 index 0000000..a959c77 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/autoshapes/AutoShape.as @@ -0,0 +1,44 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.autoshapes{ + + import com.degrafa.geometry.Geometry; + + /** + * Dev Note:: this base class will be used to provide a structure + * for ideal control points, connection points etc.. among other + * common items. + **/ + + /** + * Base class for AutoShape type geometry objects. Provides the base common + * functionality including a structure for ideal control point location and + * available connection point locations (both not yet implemented). + **/ + public class AutoShape extends Geometry{ + + public function AutoShape():void{ + super(); + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/autoshapes/AutoShapeTypeOffsets.as b/Degrafa/com/degrafa/geometry/autoshapes/AutoShapeTypeOffsets.as new file mode 100644 index 0000000..a70efd7 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/autoshapes/AutoShapeTypeOffsets.as @@ -0,0 +1,271 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.autoshapes{ + + import flash.display.Graphics; + import flash.geom.Rectangle; + + [Bindable] + + /** + * Provides a base class that has common logic for offset type AutoShapes. + * 4 Offset values are provided and AutoShapes that extend this base class + * should use the exclude tag for any undesired properties. i.e. If the + * subclass requires only 1 offset then the other offset related properties + * should be excluded. + **/ + public class AutoShapeTypeOffsets extends AutoShape{ + + /** + * Constructor. + **/ + public function AutoShapeTypeOffsets(){ + super(); + } + + protected var _offset1:Number; + /** + * The offset1 value. A percent value is also accepted here (50%). + * If no value is specified then 0 is used. Some subclasses will use a + * value other then 0 during their calculation. + **/ + [PercentProxy("offset1Percent")] + public function get offset1():Number{ + if(!_offset1){return (hasLayout)? 0:0;} + return _offset1; + } + public function set offset1(value:Number):void{ + + if (_offset1 != value) { + _offset1 = value; + _offset1Percent = NaN; + invalidated = true; + } + } + + protected var _offset1Percent:Number; + /** + * The offset1 percent value. Acceptable values include .5 + * or 50 both equating to 50%. How this rule is applied to the + * rendered output is specific to subclasses. + **/ + public function get offset1Percent():Number{ + if(!_offset1Percent){return NaN;} + return _offset1Percent; + } + public function set offset1Percent(value:Number):void{ + if (_offset1Percent != value) { + _offset1Percent = value; + invalidated = true; + } + } + + protected var _offset2:Number; + /** + * The offset2 value. A percent value is also accepted here (50%). + * If no value is specified then 0 is used. Some subclasses will use a + * value other then 0 during their calculation. + **/ + [PercentProxy("offset2Percent")] + public function get offset2():Number{ + if(!_offset2){return (hasLayout)? 0:0;} + return _offset2; + } + public function set offset2(value:Number):void{ + + if (_offset2 != value) { + _offset2 = value; + _offset2Percent = NaN; + invalidated = true; + } + } + + protected var _offset2Percent:Number; + /** + * The offset2 percent value. Acceptable values include .5 + * or 50 both equating to 50%. How this rule is applied to the + * rendered output is specific to subclasses. + **/ + public function get offset2Percent():Number{ + if(!_offset2Percent){return NaN;} + return _offset2Percent; + } + public function set offset2Percent(value:Number):void{ + if (_offset2Percent != value) { + _offset2Percent = value; + invalidated = true; + } + } + + protected var _offset3:Number; + /** + * The offset3 value. A percent value is also accepted here (50%). + * If no value is specified then 0 is used. Some subclasses will use a + * value other then 0 during their calculation. + **/ + [PercentProxy("offset3Percent")] + public function get offset3():Number{ + if(!_offset3){return (hasLayout)? 0:0;} + return _offset3; + } + public function set offset3(value:Number):void{ + + if (_offset3 != value) { + _offset3 = value; + _offset3Percent = NaN; + invalidated = true; + } + } + + protected var _offset3Percent:Number; + /** + * The offset3 percent value. Acceptable values include .5 + * or 50 both equating to 50%. How this rule is applied to the + * rendered output is specific to subclasses. + **/ + public function get offset3Percent():Number{ + if(!_offset3Percent){return NaN;} + return _offset3Percent; + } + public function set offset3Percent(value:Number):void{ + if (_offset3Percent != value) { + _offset3Percent = value; + invalidated = true; + } + } + + protected var _offset4:Number; + /** + * The offset4 value. A percent value is also accepted here (50%). + * If no value is specified then 0 is used. Some subclasses will use a + * value other then 0 during their calculation. + **/ + [PercentProxy("offset4Percent")] + public function get offset4():Number{ + if(!_offset4){return (hasLayout)? 0:0;} + return _offset4; + } + public function set offset4(value:Number):void{ + + if (_offset4 != value) { + _offset4 = value; + _offset4Percent = NaN; + invalidated = true; + } + } + + protected var _offset4Percent:Number; + /** + * The offset4 percent value. Acceptable values include .5 + * or 50 both equating to 50%. How this rule is applied to the + * rendered output is specific to subclasses. + **/ + public function get offset4Percent():Number{ + if(!_offset4Percent){return NaN;} + return _offset4Percent; + } + public function set offset4Percent(value:Number):void{ + if (_offset4Percent != value) { + _offset4Percent = value; + invalidated = true; + } + } + + /** + * Draw the objects part(s) based on passed parameters. + * Intended to be overridden by subclasses to draw the base geometry + * and calculations that make up it's AutoShape. + */ + protected function preDrawPart():void{ + //overridden in sub classes. + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + if(invalidated){ + commandStack.source.length = 0; + preDrawPart(); + invalidated = false; + } + } + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used or the most appropriate size is calculated. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + + //default to bounds if no width or height is set + //and we have layout + if(isNaN(_layoutConstraint.width)){ + tempLayoutRect.width = bounds.width; + } + + if(isNaN(_layoutConstraint.height)){ + tempLayoutRect.height = bounds.height; + } + + if(isNaN(_layoutConstraint.x)){ + tempLayoutRect.x = bounds.x; + } + + if(isNaN(_layoutConstraint.y)){ + tempLayoutRect.y = bounds.y; + } + + super.calculateLayout(tempLayoutRect); + _layoutRectangle = _layoutConstraint.layoutRectangle; + } + } + } + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + + //init the layout in this case done before predraw. + if (hasLayout) calculateLayout(); + + //invalidate as perhaps no offset settings + if(commandStack.length==0){invalidated=true;} + + //re init if required + if (invalidated) preDraw(); + + super.draw(graphics, (rc)? rc:bounds); + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/autoshapes/BoxArrowBasicAutoShape.as b/Degrafa/com/degrafa/geometry/autoshapes/BoxArrowBasicAutoShape.as new file mode 100644 index 0000000..1ed15a5 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/autoshapes/BoxArrowBasicAutoShape.as @@ -0,0 +1,131 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.autoshapes{ + + //only need one offset here so need to exclude + [Exclude(name="offset2", kind="property")] + [Exclude(name="offset2Percent", kind="property")] + + [Exclude(name="offset3", kind="property")] + [Exclude(name="offset3Percent", kind="property")] + + [Exclude(name="offset4", kind="property")] + [Exclude(name="offset4Percent", kind="property")] + + /** + * The BoxArrowBasicAutoShape element draws a box with a pointy type call out. + **/ + public class BoxArrowBasicAutoShape extends AutoShapeTypeOffsets{ + + /** + * Constructor. + * + *

    The BoxArrowBasicAutoShape constructor accepts 1 optional + * argument that defines it's properties.

    + * + * @param offset A number indicating the offset. + */ + public function BoxArrowBasicAutoShape(offset1:Number=NaN){ + super(); + if (offset1) this.offset1=offset1; + } + + /** + * BoxArrowBasicAutoShape short hand data value. + * + *

    The BoxArrowBasicAutoShape data property expects exactly 1 value an offset1

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + + //parse the string + var tempArray:Array = value.split(" "); + + if (tempArray.length == 1) + { + super.data = value; + _offset1= tempArray[0]; + invalidated = true; + } + } + } + + /** + * Draw the BoxArrowBasicAutoShape part(s) based on the parameters. + */ + override protected function preDrawPart():void{ + + //store local to calculate + var _Offset1:Number=_offset1; + + //calc desired final offset 1 + if (isNaN(_Offset1) && hasLayout && isNaN(_offset1Percent)){ + if(_layoutRectangle.width){ + _Offset1 = _layoutRectangle.width/4; + } + else{ + _Offset1 = 0; + } + } + else if (!isNaN(_offset1Percent) && hasLayout){ + if(_offset1Percent >= 1){ + _Offset1 = (_offset1Percent/100)*_layoutRectangle.width; + } + else{ + _Offset1 = _offset1Percent*_layoutRectangle.width; + } + } + else{ + if(isNaN(_Offset1)){ + _Offset1 = 0; + } + } + + + //begin drawing + commandStack.addMoveTo(0,0); + commandStack.addLineTo(_layoutRectangle.width-_Offset1,0); + commandStack.addLineTo(_layoutRectangle.width-_Offset1,_layoutRectangle.height/2-_Offset1); + commandStack.addLineTo(_layoutRectangle.width,_layoutRectangle.height/2); + commandStack.addLineTo(_layoutRectangle.width-_Offset1,_layoutRectangle.height/2+_Offset1); + commandStack.addLineTo(_layoutRectangle.width-_Offset1,_layoutRectangle.height); + commandStack.addLineTo(0,_layoutRectangle.height); + commandStack.addLineTo(0,0); + + + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:BoxArrowBasicAutoShape):void{ + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke} + if (!_offset1){_offset1 = value.offset1} + if (!_offset1Percent){_offset1Percent = value.offset1Percent} + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/autoshapes/BurstAutoShape.as b/Degrafa/com/degrafa/geometry/autoshapes/BurstAutoShape.as new file mode 100644 index 0000000..bc25be6 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/autoshapes/BurstAutoShape.as @@ -0,0 +1,403 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.autoshapes{ + + import flash.display.Graphics; + import flash.geom.Rectangle; + + [Bindable] + + /** + * The BurstAutoShape element draws a burst + * based on the number of points at the specified angle with a specified innerRadius. + * If not specified the default x,y starting point is 0,0 on center. + * + * User Tip: See reverseBurst property. + **/ + public class BurstAutoShape extends AutoShape{ + + /** + * Constructor. + * + *

    The BurstAutoShape constructor accepts 6 optional + * arguments that define it's properties.

    + * + * @param centerX A number indicating the center x-axis coordinate. + * @param centerY A number indicating the center y-axis coordinate. + * @param points A number indicating the count of points to include. + * @param angle A number indicating the start angleof the obejct value between 0° and 360°. + * @param radius A number indicating the radius. + * @param innerRadius A number indicating the inner radius. + * + */ + public function BurstAutoShape(centerX:Number=NaN,centerY:Number=NaN,points:Number=NaN, + angle:Number=NaN,radius:Number=NaN,innerRadius:Number=NaN){ + super(); + if (centerX) this.centerX=centerX; + if (centerY) this.centerY=centerY; + if (points) this.points=points; + if (angle) this.angle=angle; + if (radius) this.radius=radius; + if (innerRadius) this.innerRadius=innerRadius; + + } + + /** + * BurstAutoShape short hand data value. + * + *

    The BurstAutoShape data property expects exactly 5 values a + * centerX, centerY, point count, angle, radius and an innerRadius + * separated by spaces.

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + + //parse the string + var tempArray:Array = value.split(" "); + + if (tempArray.length == 5) + { + super.data = value; + _centerX= tempArray[0]; + _centerY= tempArray[1]; + _points= tempArray[2]; + _angle= tempArray[3]; + _radius = tempArray[4]; + _innerRadius = tempArray[5]; + invalidated = true; + } + } + } + + private var _centerX:Number; + /** + * The x-axis coordinate of the center of the BurstAutoShape. If not specified + * a default value of 0 is used. + **/ + public function get centerX():Number{ + if(!_centerX){return (hasLayout)? 0.5:0;} + return _centerX; + } + public function set centerX(value:Number):void{ + if (_centerX != value) { + _centerX = value; + invalidated = true; + } + } + + private var _centerY:Number; + /** + * The y-axis coordinate of the center of the BurstAutoShape. If not specified + * a default value of 0 is used. + **/ + public function get centerY():Number{ + if(!_centerY){return (hasLayout)? 0.5:0;} + return _centerY; + } + public function set centerY(value:Number):void{ + if(_centerY != value){ + _centerY = value; + invalidated = true; + } + + } + + private var _points:Number; + /** + * The number of points to include in this BurstAutoShape construction. + * The minimum number of points is 3 the default is 5. + **/ + public function get points():Number{ + if(!_points){return 5;} + return _points; + } + public function set points(value:Number):void{ + if (_points != value) { + if(value ==3 || value > 3){ + _points = value; + } + else{ + _points = 3; + } + + invalidated = true; + } + } + + + private var _angle:Number; + /** + * The start angle of rotation for this BurstAutoShape a value + * between 0° and 360°. + * + * User Tip :: Transforms can also be used to adjust the angle. + **/ + public function get angle():Number{ + if(!_angle){return 0;} + return _angle; + } + public function set angle(value:Number):void{ + if (_angle != value) { + _angle = value; + invalidated = true; + } + } + + private var _radius:Number; + /** + * The radius of the BurstAutoShape. If not specified a default value of 0 + * is used. + **/ + public function get radius():Number{ + if(!_radius){return (hasLayout)? .5:0;} + return _radius; + } + public function set radius(value:Number):void{ + if(_radius != value){ + _radius = value; + invalidated = true; + } + } + + private var _innerRadius:Number; + /** + * The inner radius of the BurstAutoShape. If not specified a default value of 0 + * is used. Percent values (50%) are accepted. + **/ + [PercentProxy("innerRadiusPercent")] + public function get innerRadius():Number{ + if(!_innerRadius){return (hasLayout)? 0:0;} + return _innerRadius; + } + + public function set innerRadius(value:Number):void{ + if(_innerRadius != value){ + _innerRadius = value; + _innerRadiusPercent = NaN; + invalidated = true; + } + } + + private var _innerRadiusPercent:Number; + /** + * The percent inner radius of the BurstAutoShape. If not specified a default value of 0 + * is used. Expects a value between 0 and 100. + * Note: Percent values between 0 and 1 are not yet supported. + **/ + public function get innerRadiusPercent():Number{ + if(!_innerRadiusPercent){return NaN;} + return _innerRadiusPercent; + } + + public function set innerRadiusPercent(value:Number):void{ + if(_innerRadiusPercent != value){ + _innerRadiusPercent = value; + invalidated = true; + } + } + + private var _reverseBurst:Boolean=false; + /** + * If false draws a regular burst object if true will reverse the burst so that + * the rounded edges are on the outside. + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get reverseBurst():Boolean{ + return _reverseBurst; + } + + public function set reverseBurst(value:Boolean):void{ + if(_reverseBurst != value){ + _reverseBurst = value; + invalidated = true; + } + } + + /** + * Draw the objects part(s) based on passed parameters. + */ + private function preDrawPart(x:Number, y:Number, points:Number, innerRadius:Number, outerRadius:Number,angle:Number=0):void{ + + var count:int = Math.abs(points); + + if (count>=2){ + + // calculate length of sides + var step:Number = (Math.PI*2)/points; + var halfStep:Number = step/2; + var qtrStep:Number = step/4; + + // calculate starting angle in radians + var start:Number = (angle/180)*Math.PI; + + commandStack.addMoveTo(x+(Math.cos(start)*outerRadius), y-(Math.sin(start)*outerRadius)); + + // draw curves + for (var i:int=1; i<=count; i++){ + commandStack.addCurveTo(x+Math.cos(start+(step*i)-(qtrStep*3))*(innerRadius/Math.cos(qtrStep)), + y-Math.sin(start+(step*i)-(qtrStep*3))*(innerRadius/Math.cos(qtrStep)), + x+Math.cos(start+(step*i)-halfStep)*innerRadius, + y-Math.sin(start+(step*i)-halfStep)*innerRadius); + + commandStack.addCurveTo(x+Math.cos(start+(step*i)-qtrStep)*(innerRadius/Math.cos(qtrStep)), + y-Math.sin(start+(step*i)-qtrStep)*(innerRadius/Math.cos(qtrStep)), + x+Math.cos(start+(step*i))*outerRadius, + y-Math.sin(start+(step*i))*outerRadius); + } + } + + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + if(invalidated){ + + commandStack.source.length = 0; + + var tempInnerRadius:Number=0; + + if(!isNaN(_innerRadiusPercent)){ + tempInnerRadius = (innerRadiusPercent/100)*radius; + } + else if(isNaN(_innerRadius) && isNaN(_innerRadiusPercent)){ + tempInnerRadius = radius/2; + } + else{ + tempInnerRadius = innerRadius; + } + + if(!reverseBurst){ + preDrawPart(0,0,points,tempInnerRadius,radius,angle); + } + else{ + preDrawPart(0,0,points,radius,tempInnerRadius,angle); + } + + + invalidated = false; + } + + } + + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used or the most appropriate size is calculated. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + + //default to bounds if no width or height is set + //and we have layout + if(isNaN(_layoutConstraint.width)){ + tempLayoutRect.width = bounds.width; + } + + if(isNaN(_layoutConstraint.height)){ + tempLayoutRect.height = bounds.height; + } + + if(isNaN(_layoutConstraint.x)){ + tempLayoutRect.x = bounds.x; + } + + if(isNaN(_layoutConstraint.y)){ + tempLayoutRect.y = bounds.y; + } + + super.calculateLayout(tempLayoutRect); + _layoutRectangle = _layoutConstraint.layoutRectangle; + + //Dev Note: layout needs testing and verification. + //Seems we are getting a bunch of duplicated code for + //this may want to seperate this out. + + if (isNaN(_radius)) { + //handle layout defined startup values: + _radius = _layoutRectangle.width / 2; + + if (isNaN(_centerX)){ + _centerX = layoutRectangle.width / 2 + layoutRectangle.x; + } + else{ + _layoutRectangle.x -= _radius; + } + + if (isNaN(_centerY)){ + _centerY = layoutRectangle.height / 2 + layoutRectangle.y; + } + else{ + _layoutRectangle.y -= _radius; + } + + invalidated = true; + } + } + } + } + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + + //init the layout in this case done before predraw. + if (hasLayout) calculateLayout(); + + //re init if required + if (invalidated) preDraw(); + + super.draw(graphics, (rc)? rc:bounds); + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:BurstAutoShape):void{ + + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke} + if (!_points){_points = value.points} + if (!_angle){_angle = value.angle} + if (!_radius){_radius = value.radius} + if (!_centerX){_centerX = value.centerX;} + if (!_centerY){_centerY = value.centerY;} + if (!_innerRadius){_innerRadius = value.innerRadius;} + + + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/autoshapes/CrossAutoShape.as b/Degrafa/com/degrafa/geometry/autoshapes/CrossAutoShape.as new file mode 100644 index 0000000..e83374b --- /dev/null +++ b/Degrafa/com/degrafa/geometry/autoshapes/CrossAutoShape.as @@ -0,0 +1,136 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.autoshapes{ + + + //only need one offset here so need to exclude + [Exclude(name="offset2", kind="property")] + [Exclude(name="offset2Percent", kind="property")] + + [Exclude(name="offset3", kind="property")] + [Exclude(name="offset3Percent", kind="property")] + + [Exclude(name="offset4", kind="property")] + [Exclude(name="offset4Percent", kind="property")] + + /** + * The CrossAutoShape element draws a cross. + **/ + public class CrossAutoShape extends AutoShapeTypeOffsets{ + + /** + * Constructor. + * + *

    The CrossAutoShape constructor accepts 1 optional + * argument that defines it's properties.

    + * + * @param offset1 A number indicating the offset1. + */ + public function CrossAutoShape(offset1:Number=NaN){ + super(); + if (offset1) this.offset1=offset1; + } + + /** + * CrossAutoShape short hand data value. + * + *

    The CrossAutoShape data property expects exactly 1 value an offset1

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + + //parse the string + var tempArray:Array = value.split(" "); + + if (tempArray.length == 1) + { + super.data = value; + _offset1= tempArray[0]; + invalidated = true; + } + } + } + + + /** + * Draw the CrossAutoShape part(s) based on the parameters. + */ + override protected function preDrawPart():void{ + + //store local to calculate + var _Offset1:Number=_offset1; + + //calc desired final offset 1 + if (isNaN(_Offset1) && hasLayout && isNaN(_offset1Percent)){ + if(_layoutRectangle.width){ + _Offset1 = _layoutRectangle.width/4; + } + else{ + _Offset1 = 0; + } + } + else if (!isNaN(_offset1Percent) && hasLayout){ + if(_offset1Percent >= 1){ + _Offset1 = (_offset1Percent/100)*_layoutRectangle.width; + } + else{ + _Offset1 = _offset1Percent*_layoutRectangle.width; + } + } + else{ + if(isNaN(_Offset1)){ + _Offset1 = 0; + } + } + + //begin drawing + commandStack.addMoveTo(_Offset1,0); + + commandStack.addLineTo(_layoutRectangle.width-_Offset1,0); + commandStack.addLineTo(_layoutRectangle.width-_Offset1,_Offset1); + commandStack.addLineTo(_layoutRectangle.width,_Offset1); + commandStack.addLineTo(_layoutRectangle.width,_layoutRectangle.height-_Offset1); + commandStack.addLineTo(_layoutRectangle.width-_Offset1,_layoutRectangle.height-_Offset1); + commandStack.addLineTo(_layoutRectangle.width-_Offset1,_layoutRectangle.height); + commandStack.addLineTo(_Offset1,_layoutRectangle.height); + commandStack.addLineTo(_Offset1,_layoutRectangle.height-_Offset1); + commandStack.addLineTo(0,_layoutRectangle.height-_Offset1); + commandStack.addLineTo(0,_Offset1); + commandStack.addLineTo(_Offset1,_Offset1); + commandStack.addLineTo(_Offset1,0); + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:CrossAutoShape):void{ + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke} + if (!_offset1){_offset1 = value.offset1} + if (!_offset1Percent){_offset1Percent = value.offset1Percent} + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/autoshapes/DonutAutoShape.as b/Degrafa/com/degrafa/geometry/autoshapes/DonutAutoShape.as new file mode 100644 index 0000000..9857e56 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/autoshapes/DonutAutoShape.as @@ -0,0 +1,349 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.autoshapes{ + + import com.degrafa.IGeometry; + import com.degrafa.geometry.command.CommandStack; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + [Bindable] + + /** + * The DonutAutoShape element draws a donut using the specified centre point + * and radius with a cut out hole defined by the innerRadius. + * + * User Tip: Adjust accuracy for other results. + **/ + public class DonutAutoShape extends AutoShape implements IGeometry{ + + /** + * Constructor. + * + *

    The DonutAutoShape constructor accepts 4 optional arguments that define it's + * center point and radius.

    + * + * @param centerX A number indicating the center x-axis coordinate. + * @param centerY A number indicating the center y-axis coordinate. + * @param radius A number indicating the radius of the circle. + * @param innerRadius A number indicating the radius of the inner cut out circle. + */ + public function DonutAutoShape(centerX:Number=NaN,centerY:Number=NaN,radius:Number=NaN,innerRadius:Number=NaN){ + super(); + + if (centerX) this.centerX=centerX; + if (centerY) this.centerY=centerY; + if (radius) this.radius=radius; + if (innerRadius) this.innerRadius=innerRadius; + } + + /** + * DonutAutoShape short hand data value. + * + *

    The DonutAutoShape data property expects exactly 4 values centerX, + * centerY, radius and innerRadius separated by spaces.

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + + //parse the string + var tempArray:Array = value.split(" "); + + if (tempArray.length == 4) + { + super.data = value; + _centerX= tempArray[0]; + _centerY= tempArray[1]; + _radius = tempArray[2]; + _innerRadius = tempArray[4]; + invalidated = true; + } + } + } + + private var _centerX:Number; + /** + * The x-axis coordinate of the center of the DonutAutoShape. If not specified + * a default value of 0 is used. + **/ + public function get centerX():Number{ + if(!_centerX){return (hasLayout)? 0.5:0;} + return _centerX; + } + public function set centerX(value:Number):void{ + if (_centerX != value) { + _centerX = value; + invalidated = true; + } + } + + private var _centerY:Number; + /** + * The y-axis coordinate of the center of the DonutAutoShape. If not specified + * a default value of 0 is used. + **/ + public function get centerY():Number{ + if(!_centerY){return (hasLayout)? 0.5:0;} + return _centerY; + } + public function set centerY(value:Number):void{ + if(_centerY != value){ + _centerY = value; + invalidated = true; + } + + } + + private var _radius:Number; + /** + * The radius of the DonutAutoShape. If not specified a default value of 0 + * is used. + **/ + public function get radius():Number{ + if(!_radius){return (hasLayout)? .5:0;} + return _radius; + } + public function set radius(value:Number):void{ + if(_radius != value){ + _radius = value; + invalidated = true; + } + } + + private var _innerRadius:Number; + /** + * The inner radius of the DonutAutoShape. If not specified a default value of 0 + * is used. Percent values (50%) are accepted. + **/ + [PercentProxy("innerRadiusPercent")] + public function get innerRadius():Number{ + if(!_innerRadius){return (hasLayout)? 0:0;} + return _innerRadius; + } + + public function set innerRadius(value:Number):void{ + if(_innerRadius != value){ + _innerRadius = value; + _innerRadiusPercent = NaN; + invalidated = true; + } + } + + private var _innerRadiusPercent:Number; + /** + * The percent inner radius of the DonutAutoShape. If not specified a default value of 0 + * is used. Expects a value between 0 and 100. + * Note: Percent values between 0 and 1 are not yet supported. + **/ + public function get innerRadiusPercent():Number{ + if(!_innerRadiusPercent){return NaN;} + return _innerRadiusPercent; + } + + public function set innerRadiusPercent(value:Number):void{ + if(_innerRadiusPercent != value){ + _innerRadiusPercent = value; + invalidated = true; + } + } + + private var _accuracy:Number; + /** + * The accuracy of the circles that make up the DonutAutoShape. + * If not specified a default value of 8 is used. + **/ + public function get accuracy():Number{ + if(!_accuracy){return 8;} + return _accuracy; + } + public function set accuracy(value:Number):void{ + if(_accuracy != value){ + _accuracy = value; + invalidated = true; + } + } + + /** + * Draw the objects part(s) based on passed parameters. + */ + private function preDrawPart(item:int,commandStackForItem:CommandStack):void{ + + var itemRadius:Number; + + if(item==0){ + itemRadius = radius; + } + else{ + if(_innerRadiusPercent){ + itemRadius = (innerRadiusPercent/100)*radius; + } + } + + + //item 0 = outer, 1 = inner + var span:Number = Math.PI/accuracy; + var controlRadius:Number = itemRadius/Math.cos(span); + var anchorAngle:Number=0 + var controlAngle:Number=0; + + //add the move to the command stack + commandStackForItem.addMoveTo( + centerX+Math.cos(anchorAngle)*itemRadius, + centerY+Math.sin(anchorAngle)*itemRadius); + + var i:int=0; + + //loop through and add the curve commands + for (i; iThe FlowArrowAutoShape constructor accepts 1 optional + * argument that defines it's properties.

    + * + * @param offset1 A number indicating the offset1. + */ + public function FlowArrowAutoShape(offset1:Number=NaN){ + super(); + if (offset1) this.offset1=offset1; + } + + /** + * FlowArrowAutoShape short hand data value. + * + *

    The FlowArrowAutoShape data property expects exactly 1 value an offset1

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + + //parse the string + var tempArray:Array = value.split(" "); + + if (tempArray.length == 1) + { + super.data = value; + _offset1= tempArray[0]; + invalidated = true; + } + } + } + + /** + * Draw the FlowArrowAutoShape part(s) based on the parameters. + */ + override protected function preDrawPart():void{ + + //store local to calculate + var _Offset1:Number=_offset1; + + //calc desired final offset 1 + if (isNaN(_Offset1) && hasLayout && isNaN(_offset1Percent)){ + if(_layoutRectangle.width){ + _Offset1 = _layoutRectangle.width/4; + } + else{ + _Offset1 = 0; + } + } + else if (!isNaN(_offset1Percent) && hasLayout){ + if(_offset1Percent >= 1){ + _Offset1 = (_offset1Percent/100)*_layoutRectangle.width; + } + else{ + _Offset1 = _offset1Percent*_layoutRectangle.width; + } + } + else{ + if(isNaN(_Offset1)){ + _Offset1 = 0; + } + } + + //begin drawing + commandStack.addMoveTo(0,0); + commandStack.addLineTo(_layoutRectangle.width-_Offset1,0); + commandStack.addLineTo(_layoutRectangle.width,_layoutRectangle.height/2); + commandStack.addLineTo(_layoutRectangle.width-_Offset1,_layoutRectangle.height); + commandStack.addLineTo(0,_layoutRectangle.height); + commandStack.addLineTo(_Offset1,_layoutRectangle.height/2); + commandStack.addLineTo(0,0); + + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:FlowArrowAutoShape):void{ + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke} + if (!_offset1){_offset1 = value.offset1} + if (!_offset1Percent){_offset1Percent = value.offset1Percent} + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/autoshapes/GearAutoShape.as b/Degrafa/com/degrafa/geometry/autoshapes/GearAutoShape.as new file mode 100644 index 0000000..636aca0 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/autoshapes/GearAutoShape.as @@ -0,0 +1,471 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.autoshapes{ + + import flash.display.Graphics; + import flash.geom.Rectangle; + + [Bindable] + + /** + * The GearAutoShape element draws a gear + * based on the number of points at the specified angle with a specified innerRadius. + * If not specified the default x,y starting point is 0,0 on center. + **/ + public class GearAutoShape extends AutoShape{ + + /** + * Constructor. + * + *

    The GearAutoShape constructor accepts 7 optional + * arguments that define it's properties.

    + * + * @param centerX A number indicating the center x-axis coordinate. + * @param centerY A number indicating the center y-axis coordinate. + * @param points A number indicating the count of points to include. + * @param angle A number indicating the start angleof the obejct value between 0° and 360°. + * @param radius A number indicating the radius. + * @param innerRadius A number indicating the inner radius. + * @param holeRadius A number indicating the inner cut out radius. + * @param holePoints A number indicating count of points to use for the inner cut out. + * + */ + public function GearAutoShape(centerX:Number=NaN,centerY:Number=NaN,points:Number=NaN, + angle:Number=NaN,radius:Number=NaN,innerRadius:Number=NaN,holeRadius:Number=NaN, + holePoints:Number=NaN){ + super(); + if (centerX) this.centerX=centerX; + if (centerY) this.centerY=centerY; + if (points) this.points=points; + if (angle) this.angle=angle; + if (radius) this.radius=radius; + if (innerRadius) this.innerRadius=innerRadius; + if (holeRadius) this.holeRadius=holeRadius; + if (holePoints) this.holePoints=holePoints; + + } + + /** + * GearAutoShape short hand data value. + * + *

    The GearAutoShape data property expects exactly 7 values a + * centerX, centerY, point count, angle, radius, innerRadius, holeRadius + * and holePoints separated by spaces.

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + + //parse the string + var tempArray:Array = value.split(" "); + + if (tempArray.length == 5) + { + super.data = value; + _centerX= tempArray[0]; + _centerY= tempArray[1]; + _points= tempArray[2]; + _angle= tempArray[3]; + _radius = tempArray[4]; + _innerRadius = tempArray[5]; + _holeRadius = tempArray[6]; + _holePoints = tempArray[7]; + + invalidated = true; + } + } + } + + private var _centerX:Number; + /** + * The x-axis coordinate of the center of the GearAutoShape. If not specified + * a default value of 0 is used. + **/ + public function get centerX():Number{ + if(!_centerX){return (hasLayout)? 0.5:0;} + return _centerX; + } + public function set centerX(value:Number):void{ + if (_centerX != value) { + _centerX = value; + invalidated = true; + } + } + + private var _centerY:Number; + /** + * The y-axis coordinate of the center of the GearAutoShape. If not specified + * a default value of 0 is used. + **/ + public function get centerY():Number{ + if(!_centerY){return (hasLayout)? 0.5:0;} + return _centerY; + } + public function set centerY(value:Number):void{ + if(_centerY != value){ + _centerY = value; + invalidated = true; + } + + } + + private var _points:Number; + /** + * The number of points to include in this GearAutoShape construction. + * The minimum number of points is 3 the default is 5. + **/ + public function get points():Number{ + if(!_points){return 5;} + return _points; + } + public function set points(value:Number):void{ + if (_points != value) { + if(value ==3 || value > 3){ + _points = value; + } + else{ + _points = 3; + } + + invalidated = true; + } + } + + + private var _angle:Number; + /** + * The start angle of rotation for this GearAutoShape a value + * between 0° and 360°. + * + * User Tip :: Transforms can also be used to adjust the angle. + **/ + public function get angle():Number{ + if(!_angle){return 0;} + return _angle; + } + public function set angle(value:Number):void{ + if (_angle != value) { + _angle = value; + invalidated = true; + } + } + + private var _radius:Number; + /** + * The radius of the GearAutoShape. If not specified a default value of 0 + * is used. + **/ + public function get radius():Number{ + if(!_radius){return (hasLayout)? .5:0;} + return _radius; + } + public function set radius(value:Number):void{ + if(_radius != value){ + _radius = value; + invalidated = true; + } + } + + private var _innerRadius:Number; + /** + * The inner radius of the GearAutoShape. If not specified a default value of 0 + * is used. Percent values (50%) are accepted. + **/ + [PercentProxy("innerRadiusPercent")] + public function get innerRadius():Number{ + if(!_innerRadius){return (hasLayout)? 0:0;} + return _innerRadius; + } + + public function set innerRadius(value:Number):void{ + if(_innerRadius != value){ + _innerRadius = value; + _innerRadiusPercent = NaN; + invalidated = true; + } + } + + private var _innerRadiusPercent:Number; + /** + * The percent inner radius of the GearAutoShape. If not specified a default value of 0 + * is used. Expects a value between 0 and 100. + * Note: Percent values between 0 and 1 are not yet supported. + **/ + public function get innerRadiusPercent():Number{ + if(!_innerRadiusPercent){return NaN;} + return _innerRadiusPercent; + } + + public function set innerRadiusPercent(value:Number):void{ + if(_innerRadiusPercent != value){ + _innerRadiusPercent = value; + invalidated = true; + } + } + + private var _holeRadius:Number; + /** + * The radius for the inside cut out of the gear. + **/ + [PercentProxy("holeRadiusPercent")] + public function get holeRadius():Number{ + if(!_holeRadius){return (hasLayout)? 0:0;} + return _holeRadius; + } + public function set holeRadius(value:Number):void{ + if(_holeRadius != value){ + _holeRadius = value; + _holeRadiusPercent = NaN; + invalidated = true; + } + } + + private var _holeRadiusPercent:Number; + /** + * The percent radius of the GearAutoShapes cut out hole. If not specified a default + * value of 0 is used. Expects a value between 0 and 100. + * Note: Percent values between 0 and 1 are not yet supported. + **/ + public function get holeRadiusPercent():Number{ + if(!_holeRadiusPercent){return NaN;} + return _holeRadiusPercent; + } + + public function set holeRadiusPercent(value:Number):void{ + if(_holeRadiusPercent != value){ + _holeRadiusPercent = value; + invalidated = true; + } + } + + private var _holePoints:Number; + /** + * The number of points for the inside cut out of the gear. + **/ + public function get holePoints():Number{ + if(!_holePoints){return 5;} + return _holePoints; + } + public function set holePoints(value:Number):void{ + if (_holePoints != value) { + if(value ==3 || value > 3){ + _holePoints = value; + } + else{ + _holePoints = 3; + } + + invalidated = true; + } + } + + /** + * Draw the objects part(s) based on passed parameters. + */ + private function preDrawPart( x:Number, y:Number, points:Number, innerRadius:Number, outerRadius:Number,angle:Number=0, holeSides:Number=0, holeRadius:Number=0):void{ + + if (points>=2){ + + // calculate length of sides + var step:Number = (Math.PI*2)/points; + var qtrStep:Number = step/4; + + // calculate starting angle in radians + var start:Number = (angle/180)*Math.PI; + commandStack.addMoveTo(x+(Math.cos(start)*outerRadius), y-(Math.sin(start)*outerRadius)); + + // draw lines + for (var i:int=1; i<=points; i++){ + commandStack.addLineTo(x+Math.cos(start+(step*i)-(qtrStep*3))*innerRadius, + y-Math.sin(start+(step*i)-(qtrStep*3))*innerRadius); + + commandStack.addLineTo(x+Math.cos(start+(step*i)-(qtrStep*2))*innerRadius, + y-Math.sin(start+(step*i)-(qtrStep*2))*innerRadius); + + commandStack.addLineTo(x+Math.cos(start+(step*i)-qtrStep)*outerRadius, + y-Math.sin(start+(step*i)-qtrStep)*outerRadius); + + commandStack.addLineTo(x+Math.cos(start+(step*i))*outerRadius, + y-Math.sin(start+(step*i))*outerRadius); + + } + + if (holeSides>=2){ + + if(holeRadius == 0){ + holeRadius = innerRadius/3; + } + + step = (Math.PI*2)/holeSides; + commandStack.addMoveTo(x+(Math.cos(start)*holeRadius), y-(Math.sin(start)*holeRadius)); + + for (var j:int=1; j<=holeSides; j++){ + commandStack.addLineTo(x+Math.cos(start+(step*j))*holeRadius, + y-Math.sin(start+(step*j))*holeRadius); + } + + } + + } + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + if(invalidated){ + + commandStack.source.length = 0; + + var tempInnerRadius:Number=0; + var tempHoleRadius:Number=0; + + //convert percent for inner radius + if(!isNaN(_innerRadiusPercent)){ + tempInnerRadius = (innerRadiusPercent/100)*radius; + } + else if(isNaN(_innerRadius) && isNaN(_innerRadiusPercent)){ + tempInnerRadius = radius/2; + } + else{ + tempInnerRadius = innerRadius; + } + + //convert percent for hole radius + if(!isNaN(_holeRadiusPercent)){ + tempHoleRadius = (holeRadiusPercent/100)*radius; + } + else if(isNaN(_holeRadius) && isNaN(_holeRadiusPercent)){ + tempHoleRadius = radius/2; + } + else{ + tempHoleRadius = holeRadius; + } + + preDrawPart(0,0,points,tempInnerRadius,radius,angle,holePoints,tempHoleRadius); + + invalidated = false; + } + + } + + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used or the most appropriate size is calculated. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + + //default to bounds if no width or height is set + //and we have layout + if(isNaN(_layoutConstraint.width)){ + tempLayoutRect.width = bounds.width; + } + + if(isNaN(_layoutConstraint.height)){ + tempLayoutRect.height = bounds.height; + } + + if(isNaN(_layoutConstraint.x)){ + tempLayoutRect.x = bounds.x; + } + + if(isNaN(_layoutConstraint.y)){ + tempLayoutRect.y = bounds.y; + } + + super.calculateLayout(tempLayoutRect); + _layoutRectangle = _layoutConstraint.layoutRectangle; + + //Dev Note: layout needs testing and verification. + //Seems we are getting a bunch of duplicated code for + //this may want to seperate this out. + + if (isNaN(_radius)) { + //handle layout defined startup values: + _radius = _layoutRectangle.width / 2; + + if (isNaN(_centerX)){ + _centerX = layoutRectangle.width / 2 + layoutRectangle.x; + } + else{ + _layoutRectangle.x -= _radius; + } + + if (isNaN(_centerY)){ + _centerY = layoutRectangle.height / 2 + layoutRectangle.y; + } + else{ + _layoutRectangle.y -= _radius; + } + + invalidated = true; + } + } + } + } + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + + //init the layout in this case done before predraw. + if (hasLayout) calculateLayout(); + + //re init if required + if (invalidated) preDraw(); + + super.draw(graphics, (rc)? rc:bounds); + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:GearAutoShape):void{ + + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke} + if (!_points){_points = value.points} + if (!_angle){_angle = value.angle} + if (!_radius){_radius = value.radius} + if (!_centerX){_centerX = value.centerX;} + if (!_centerY){_centerY = value.centerY;} + if (!_innerRadius){_innerRadius = value.innerRadius;} + if (!_holeRadius){_holeRadius = value.holeRadius;} + if (!_holePoints){_holePoints = value.holePoints;} + + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/autoshapes/HexagonAutoShape.as b/Degrafa/com/degrafa/geometry/autoshapes/HexagonAutoShape.as new file mode 100644 index 0000000..35106a3 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/autoshapes/HexagonAutoShape.as @@ -0,0 +1,128 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.autoshapes{ + + //only need one offset here so need to exclude + [Exclude(name="offset2", kind="property")] + [Exclude(name="offset2Percent", kind="property")] + + [Exclude(name="offset3", kind="property")] + [Exclude(name="offset3Percent", kind="property")] + + [Exclude(name="offset4", kind="property")] + [Exclude(name="offset4Percent", kind="property")] + + /** + * The HexagonAutoShape element draws a hexagon + * including an offset1 passed. + **/ + public class HexagonAutoShape extends AutoShapeTypeOffsets{ + + /** + * Constructor. + * + *

    The HexagonAutoShape constructor accepts 1 optional + * argument that defines it's properties.

    + * + * @param offset1 A number indicating the offset1. + */ + public function HexagonAutoShape(offset1:Number=NaN){ + super(); + if (offset1) this.offset1=offset1; + } + + /** + * HexagonAutoShape short hand data value. + * + *

    The HexagonAutoShape data property expects exactly 1 value an offset1

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + + //parse the string + var tempArray:Array = value.split(" "); + + if (tempArray.length == 1) + { + super.data = value; + _offset1= tempArray[0]; + invalidated = true; + } + } + } + + /** + * Draw the HexagonAutoShape part(s) based on the parameters. + */ + override protected function preDrawPart():void{ + + //store local to calculate + var _Offset1:Number=_offset1; + + //calc desired final offset 1 + if (isNaN(_Offset1) && hasLayout && isNaN(_offset1Percent)){ + if(_layoutRectangle.width){ + _Offset1 = _layoutRectangle.width/4; + } + else{ + _Offset1 = 0; + } + } + else if (!isNaN(_offset1Percent) && hasLayout){ + if(_offset1Percent >= 1){ + _Offset1 = (_offset1Percent/100)*_layoutRectangle.width; + } + else{ + _Offset1 = _offset1Percent*_layoutRectangle.width; + } + } + else{ + if(isNaN(_Offset1)){ + _Offset1 = 0; + } + } + + commandStack.addMoveTo(_Offset1,0); + commandStack.addLineTo(_layoutRectangle.width-_Offset1,0); + commandStack.addLineTo(_layoutRectangle.width,_layoutRectangle.height/2); + commandStack.addLineTo(_layoutRectangle.width-_Offset1,_layoutRectangle.height); + commandStack.addLineTo(_Offset1,_layoutRectangle.height); + commandStack.addLineTo(0,_layoutRectangle.height/2); + commandStack.addLineTo(_Offset1,0); + + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:HexagonAutoShape):void{ + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke} + if (!_offset1){_offset1 = value.offset1} + if (!_offset1Percent){_offset1Percent = value.offset1Percent} + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/autoshapes/IsocelesTriangleAutoShape.as b/Degrafa/com/degrafa/geometry/autoshapes/IsocelesTriangleAutoShape.as new file mode 100644 index 0000000..3bf88ef --- /dev/null +++ b/Degrafa/com/degrafa/geometry/autoshapes/IsocelesTriangleAutoShape.as @@ -0,0 +1,122 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.autoshapes{ + + //only need one offset here so need to exclude + [Exclude(name="offset2", kind="property")] + [Exclude(name="offset2Percent", kind="property")] + + [Exclude(name="offset3", kind="property")] + [Exclude(name="offset3Percent", kind="property")] + + [Exclude(name="offset4", kind="property")] + [Exclude(name="offset4Percent", kind="property")] + + /** + * The IsocelesTriangleAutoShape element draws a isoceles triangle. + **/ + public class IsocelesTriangleAutoShape extends AutoShapeTypeOffsets{ + + /** + * Constructor. + *

    The IsocelesTriangleAutoShape constructor.

    + */ + public function IsocelesTriangleAutoShape(offset1:Number=NaN){ + super(); + if (offset1) this.offset1=offset1; + } + + /** + * IsocelesTriangleAutoShape short hand data value. + * + *

    The IsocelesTriangleAutoShape data property expects exactly 1 + * value an offset1

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + + //parse the string + var tempArray:Array = value.split(" "); + + if (tempArray.length == 1) + { + super.data = value; + _offset1= tempArray[0]; + invalidated = true; + } + } + } + + /** + * Draw the IsocelesTriangleAutoShape part(s) based on the parameters. + */ + override protected function preDrawPart():void{ + + //store local to calculate + var _Offset1:Number=_offset1; + + //calc desired final offset 1 + if (isNaN(_Offset1) && hasLayout && isNaN(_offset1Percent)){ + if(_layoutRectangle.width){ + _Offset1 = _layoutRectangle.width/2; + } + else{ + _Offset1 = 0; + } + } + else if (!isNaN(_offset1Percent) && hasLayout){ + if(_offset1Percent >= 1){ + _Offset1 = (_offset1Percent/100)*_layoutRectangle.width; + } + else{ + _Offset1 = _offset1Percent*_layoutRectangle.width; + } + } + else{ + if(isNaN(_Offset1)){ + _Offset1 = 0; + } + } + + + commandStack.addMoveTo(_Offset1,0); + commandStack.addLineTo(_layoutRectangle.width,_layoutRectangle.height); + commandStack.addLineTo(0,_layoutRectangle.height); + commandStack.addLineTo(_Offset1,0); + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:IsocelesTriangleAutoShape):void{ + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke} + if (!_offset1){_offset1 = value.offset1} + if (!_offset1Percent){_offset1Percent = value.offset1Percent} + + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/autoshapes/LeftRightArrowAutoShape.as b/Degrafa/com/degrafa/geometry/autoshapes/LeftRightArrowAutoShape.as new file mode 100644 index 0000000..3e650f2 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/autoshapes/LeftRightArrowAutoShape.as @@ -0,0 +1,186 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.autoshapes{ + + import com.degrafa.geometry.command.CommandStack; + + [Exclude(name="offset4", kind="property")] + [Exclude(name="offset4Percent", kind="property")] + + /** + * The LeftRightArrowAutoShape element draws an arrow with a left and right head.. + **/ + public class LeftRightArrowAutoShape extends AutoShapeTypeOffsets{ + + /** + * Constructor. + * + *

    The LeftRightArrowAutoShape constructor accepts 3 optional + * argument that defines it's properties.

    + * + * @param offset1 A number indicating the offset1. + * @param offset2 A number indicating the offset2. + * @param offset3 A number indicating the offset3. + */ + public function LeftRightArrowAutoShape(offset1:Number=NaN,offset2:Number=NaN,offset3:Number=NaN){ + super(); + if (offset1) this.offset1=offset1; + if (offset2) this.offset2=offset2; + if (offset3) this.offset3=offset3; + } + + /** + * LeftRightArrowAutoShape short hand data value. + * + *

    The LeftRightArrowAutoShape data property expects exactly 3 values for offsets

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + + //parse the string + var tempArray:Array = value.split(" "); + + if (tempArray.length == 1) + { + super.data = value; + _offset1= tempArray[0]; + _offset2= tempArray[1]; + _offset3= tempArray[2]; + invalidated = true; + } + } + } + + /** + * Draw the LeftRightArrowAutoShape part(s) based on the parameters. + */ + override protected function preDrawPart():void{ + + //store local to calculate + var _Offset1:Number=_offset1; + var _Offset2:Number=_offset2; + var _Offset3:Number=_offset3; + + //calc desired offset 1 + if (isNaN(_Offset1) && hasLayout && isNaN(_offset1Percent)){ + if(_layoutRectangle.width){ + _Offset1 = _layoutRectangle.height/2; + } + else{ + _Offset1 = 0; + } + } + else if (!isNaN(_offset1Percent) && hasLayout){ + if(_offset1Percent >= 1){ + _Offset1 = (_offset1Percent/100)*_layoutRectangle.width; + } + else{ + _Offset1 = _offset1Percent*_layoutRectangle.width; + } + } + else{ + if(isNaN(_Offset1)){ + _Offset1 = 0; + } + } + + //calc desired offset 2 + if (isNaN(_Offset2) && hasLayout && isNaN(_offset2Percent)){ + if(_layoutRectangle.width){ + _Offset2 = _layoutRectangle.width/2; + } + else{ + _Offset2 = 0; + } + } + else if (!isNaN(_offset2Percent) && hasLayout){ + if(_offset2Percent >= 1){ + _Offset2 = (_offset2Percent/100)*_layoutRectangle.width; + } + else{ + _Offset2 = _offset2Percent*_layoutRectangle.width; + } + } + else{ + if(isNaN(_Offset2)){ + _Offset2 = 0; + } + } + + //calc desired offset 3 + if (isNaN(_Offset3) && hasLayout && isNaN(_offset3Percent)){ + if(_layoutRectangle.width){ + _Offset3 = _layoutRectangle.width/2; + } + else{ + _Offset3 = 0; + } + } + else if (!isNaN(_offset3Percent) && hasLayout){ + if(_offset3Percent >= 1){ + _Offset3 = (_offset3Percent/100)*_layoutRectangle.width; + } + else{ + _Offset3 = _offset3Percent*_layoutRectangle.width; + } + } + else{ + if(isNaN(_Offset3)){ + _Offset3 = 0; + } + } + + //begin drawing + commandStack.addMoveTo(0,_layoutRectangle.height/2); + commandStack.addLineTo(_Offset2+_Offset3,0); + commandStack.addLineTo(_Offset2,_Offset1); + commandStack.addLineTo(_layoutRectangle.width-_Offset2,_Offset1); + commandStack.addLineTo(_layoutRectangle.width-(_Offset2+_Offset3),0); + commandStack.addLineTo(_layoutRectangle.width,_layoutRectangle.height/2); + commandStack.addLineTo(_layoutRectangle.width-(_Offset2+_Offset3),_layoutRectangle.height); + commandStack.addLineTo(_layoutRectangle.width-_Offset2,_layoutRectangle.height-_Offset1); + commandStack.addLineTo(_layoutRectangle.width-_Offset2,_layoutRectangle.height-_Offset1); + commandStack.addLineTo(_Offset2,_layoutRectangle.height-_Offset1); + commandStack.addLineTo(_Offset2+_Offset3,_layoutRectangle.height); + commandStack.addLineTo(0,_layoutRectangle.height/2); + + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:LeftRightArrowAutoShape):void{ + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke} + if (!_offset1){_offset1 = value.offset1} + if (!_offset2){_offset2 = value.offset2} + if (!_offset3){_offset3 = value.offset3} + if (!_offset1Percent){_offset1Percent = value.offset1Percent} + if (!_offset2Percent){_offset2Percent = value.offset2Percent} + if (!_offset3Percent){_offset3Percent = value.offset3Percent} + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/autoshapes/ObtuseTriangleAutoShape.as b/Degrafa/com/degrafa/geometry/autoshapes/ObtuseTriangleAutoShape.as new file mode 100644 index 0000000..1758751 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/autoshapes/ObtuseTriangleAutoShape.as @@ -0,0 +1,126 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.autoshapes{ + + //only need one offset here so need to exclude + [Exclude(name="offset2", kind="property")] + [Exclude(name="offset2Percent", kind="property")] + + [Exclude(name="offset3", kind="property")] + [Exclude(name="offset3Percent", kind="property")] + + [Exclude(name="offset4", kind="property")] + [Exclude(name="offset4Percent", kind="property")] + + /** + * The ObtuseTriangleAutoShape element draws a obtuse triangle. + **/ + public class ObtuseTriangleAutoShape extends AutoShapeTypeOffsets{ + + /** + * Constructor. + * + *

    The ObtuseTriangleAutoShape constructor accepts 1 optional + * argument that defines it's properties.

    + * + * @param offset1 A number indicating the offset1. + */ + public function ObtuseTriangleAutoShape(offset1:Number=NaN){ + super(); + if (offset1) this.offset1=offset1; + } + + /** + * ObtuseTriangleAutoShape short hand data value. + * + *

    The ObtuseTriangleAutoShape data property expects exactly 1 value an offset1

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + + //parse the string + var tempArray:Array = value.split(" "); + + if (tempArray.length == 1) + { + super.data = value; + _offset1= tempArray[0]; + invalidated = true; + } + } + } + + /** + * Draw the ObtuseTriangleAutoShape part(s) based on the parameters. + */ + override protected function preDrawPart():void{ + + //store local to calculate + var _Offset1:Number=_offset1; + + //calc desired final offset 1 + if (isNaN(_Offset1) && hasLayout && isNaN(_offset1Percent)){ + if(_layoutRectangle.width){ + _Offset1 = _layoutRectangle.width/2; + } + else{ + _Offset1 = 0; + } + } + else if (!isNaN(_offset1Percent) && hasLayout){ + if(_offset1Percent >= 1){ + _Offset1 = (_offset1Percent/100)*_layoutRectangle.width; + } + else{ + _Offset1 = _offset1Percent*_layoutRectangle.width; + } + } + else{ + if(isNaN(_Offset1)){ + _Offset1 = 0; + } + } + + commandStack.addMoveTo(0,0); + commandStack.addLineTo(_layoutRectangle.width,_layoutRectangle.height); + commandStack.addLineTo(_Offset1,_layoutRectangle.height); + commandStack.addLineTo(0,0); + + + } + + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:ObtuseTriangleAutoShape):void{ + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke} + if (!_offset1){_offset1 = value.offset1} + if (!_offset1Percent){_offset1Percent = value.offset1Percent} + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/autoshapes/ParallelogramAutoShape.as b/Degrafa/com/degrafa/geometry/autoshapes/ParallelogramAutoShape.as new file mode 100644 index 0000000..184ca79 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/autoshapes/ParallelogramAutoShape.as @@ -0,0 +1,125 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.autoshapes{ + + //only need one offset here so need to exclude + [Exclude(name="offset2", kind="property")] + [Exclude(name="offset2Percent", kind="property")] + + [Exclude(name="offset3", kind="property")] + [Exclude(name="offset3Percent", kind="property")] + + [Exclude(name="offset4", kind="property")] + [Exclude(name="offset4Percent", kind="property")] + + /** + * The ParallelogramAutoShape element draws a parallelogram. + **/ + public class ParallelogramAutoShape extends AutoShapeTypeOffsets{ + + /** + * Constructor. + * + *

    The ParallelogramAutoShape constructor accepts 1 optional + * argument that defines it's properties.

    + * + * @param offset1 A number indicating the offset1. + */ + public function ParallelogramAutoShape(offset1:Number=NaN){ + super(); + if (offset1) this.offset1=offset1; + } + + /** + * ParallelogramAutoShape short hand data value. + * + *

    The ParallelogramAutoShape data property expects exactly 1 value an offset1

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + + //parse the string + var tempArray:Array = value.split(" "); + + if (tempArray.length == 1) + { + super.data = value; + _offset1= tempArray[0]; + invalidated = true; + } + } + } + + /** + * Draw the ParallelogramAutoShape part(s) based on the parameters. + */ + override protected function preDrawPart():void{ + + //store local to calculate + var _Offset1:Number=_offset1; + + //calc desired final offset 1 + if (isNaN(_Offset1) && hasLayout && isNaN(_offset1Percent)){ + if(_layoutRectangle.width){ + _Offset1 = _layoutRectangle.width/3; + } + else{ + _Offset1 = 0; + } + } + else if (!isNaN(_offset1Percent) && hasLayout){ + if(_offset1Percent >= 1){ + _Offset1 = (_offset1Percent/100)*_layoutRectangle.width; + } + else{ + _Offset1 = _offset1Percent*_layoutRectangle.width; + } + } + else{ + if(isNaN(_Offset1)){ + _Offset1 = 0; + } + } + + commandStack.addMoveTo(_Offset1,0); + commandStack.addLineTo(_layoutRectangle.width, 0); + commandStack.addLineTo(_layoutRectangle.width - _Offset1, _layoutRectangle.height); + commandStack.addLineTo(0,_layoutRectangle.height); + commandStack.addLineTo(_Offset1,0); + + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:ParallelogramAutoShape):void{ + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke} + if (!_offset1){_offset1 = value.offset1} + if (!_offset1Percent){_offset1Percent = value.offset1Percent} + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/autoshapes/RegularPolygonAutoShape.as b/Degrafa/com/degrafa/geometry/autoshapes/RegularPolygonAutoShape.as new file mode 100644 index 0000000..1f3486f --- /dev/null +++ b/Degrafa/com/degrafa/geometry/autoshapes/RegularPolygonAutoShape.as @@ -0,0 +1,315 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.autoshapes{ + + import flash.display.Graphics; + import flash.geom.Rectangle; + + [Bindable] + + /** + * The RegularPolygonAutoShape element draws a regular polygon + * based on the number of points at the specified angle. If not + * specified the default x,y starting point is 0,0 on center. + **/ + public class RegularPolygonAutoShape extends AutoShape{ + + /** + * Constructor. + * + *

    The RegularPolygonAutoShape constructor accepts 5 optional + * arguments that define it's point count and angle.

    + * + * @param centerX A number indicating the center x-axis coordinate. + * @param centerY A number indicating the center y-axis coordinate. + * @param points A number indicating the count of points to include. + * @param angle A number indicating the start angleof the obejct value between 0° and 360°. + * @param radius A number indicating the radius. + * + */ + public function RegularPolygonAutoShape(centerX:Number=NaN,centerY:Number=NaN,points:Number=NaN,angle:Number=NaN,radius:Number=NaN){ + super(); + if (centerX) this.centerX=centerX; + if (centerY) this.centerY=centerY; + if (points) this.points=points; + if (angle) this.angle=angle; + if (radius) this.radius=radius; + + } + + /** + * RegularPolygonAutoShape short hand data value. + * + *

    The RegularPolygonAutoShape data property expects exactly 5 values a + * centerX, centerY, point count, angle, and a radius separated by spaces.

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + + //parse the string + var tempArray:Array = value.split(" "); + + if (tempArray.length == 5) + { + super.data = value; + _centerX= tempArray[0]; + _centerY= tempArray[1]; + _points= tempArray[2]; + _angle= tempArray[3]; + _radius = tempArray[4]; + invalidated = true; + } + } + } + + private var _centerX:Number; + /** + * The x-axis coordinate of the center of the RegularPolygonAutoShapes. If not specified + * a default value of 0 is used. + **/ + public function get centerX():Number{ + if(!_centerX){return (hasLayout)? 0.5:0;} + return _centerX; + } + public function set centerX(value:Number):void{ + if (_centerX != value) { + _centerX = value; + invalidated = true; + } + } + + private var _centerY:Number; + /** + * The y-axis coordinate of the center of the RegularPolygonAutoShapes. If not specified + * a default value of 0 is used. + **/ + public function get centerY():Number{ + if(!_centerY){return (hasLayout)? 0.5:0;} + return _centerY; + } + public function set centerY(value:Number):void{ + if(_centerY != value){ + _centerY = value; + invalidated = true; + } + + } + + private var _points:Number; + /** + * The number of points to include in this RegularPolygonAutoShapes construction. + * The minimum number of points is 3 the default is 5. + **/ + public function get points():Number{ + if(!_points){return 5;} + return _points; + } + public function set points(value:Number):void{ + if (_points != value) { + if(value ==3 || value > 3){ + _points = value; + } + else{ + _points = 3; + } + + invalidated = true; + } + } + + + private var _angle:Number; + /** + * The start angle of rotation for this RegularPolygonAutoShape a value + * between 0° and 360°. + * + * User Tip :: Transforms can also be used to adjust the angle. + **/ + public function get angle():Number{ + if(!_angle){return 0;} + return _angle; + } + public function set angle(value:Number):void{ + if (_angle != value) { + _angle = value; + invalidated = true; + } + } + + private var _radius:Number; + /** + * The radius of the RegularPolygonAutoShape. If not specified a default value of 0 + * is used. + **/ + public function get radius():Number{ + if(!_radius){return (hasLayout)? .5:0;} + return _radius; + } + public function set radius(value:Number):void{ + if(_radius != value){ + _radius = value; + invalidated = true; + } + } + + /** + * Draw the objects part(s) based on passed parameters. + */ + private function preDrawPart(x:Number, y:Number, points:Number, radius:Number, angle:Number=0):void{ + + // convert sides to positive value + var count:int = Math.abs(points); + + if (count>=2){ + + // calculate span of sides + var step:Number = (Math.PI*2)/points; + + // calculate starting angle in radians + var start:Number = (angle/180)*Math.PI; + + commandStack.addMoveTo(x+(Math.cos(start)*radius), y-(Math.sin(start)*radius)); + + // draw the polygon + for (var i:int=1; i<=count; i++){ + commandStack.addLineTo(x+Math.cos(start+(step*i))*radius, + y-Math.sin(start+(step*i))*radius); + } + + } + + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + if(invalidated){ + + commandStack.source.length = 0; + + preDrawPart(0,0,points,radius,angle); + + invalidated = false; + } + + } + + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used or the most appropriate size is calculated. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + + //default to bounds if no width or height is set + //and we have layout + if(isNaN(_layoutConstraint.width)){ + tempLayoutRect.width = bounds.width; + } + + if(isNaN(_layoutConstraint.height)){ + tempLayoutRect.height = bounds.height; + } + + if(isNaN(_layoutConstraint.x)){ + tempLayoutRect.x = bounds.x; + } + + if(isNaN(_layoutConstraint.y)){ + tempLayoutRect.y = bounds.y; + } + + super.calculateLayout(tempLayoutRect); + _layoutRectangle = _layoutConstraint.layoutRectangle; + + //Dev Note: layout needs testing and verification. + //Seems we are getting a bunch of duplicated code for + //this may want to seperate this out. + + if (isNaN(_radius)) { + //handle layout defined startup values: + _radius = _layoutRectangle.width / 2; + + if (isNaN(_centerX)){ + _centerX = layoutRectangle.width / 2 + layoutRectangle.x; + } + else{ + _layoutRectangle.x -= _radius; + } + + if (isNaN(_centerY)){ + _centerY = layoutRectangle.height / 2 + layoutRectangle.y; + } + else{ + _layoutRectangle.y -= _radius; + } + + invalidated = true; + } + } + } + } + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + + //init the layout in this case done before predraw. + if (hasLayout) calculateLayout(); + + //re init if required + if (invalidated) preDraw(); + + super.draw(graphics, (rc)? rc:bounds); + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:RegularPolygonAutoShape):void{ + + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke} + if (!_points){_points = value.points} + if (!_angle){_angle = value.angle} + if (!_radius){_radius = value.radius} + if (!_centerX){_centerX = value.centerX;} + if (!_centerY){_centerY = value.centerY;} + + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/autoshapes/StarAutoShape.as b/Degrafa/com/degrafa/geometry/autoshapes/StarAutoShape.as new file mode 100644 index 0000000..4934d7e --- /dev/null +++ b/Degrafa/com/degrafa/geometry/autoshapes/StarAutoShape.as @@ -0,0 +1,373 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.autoshapes{ + + import flash.display.Graphics; + import flash.geom.Rectangle; + + [Bindable] + + /** + * The StarAutoShape element draws a star + * based on the number of points at the specified angle with a specified innerRadius. + * If not specified the default x,y starting point is 0,0 on center. + **/ + public class StarAutoShape extends AutoShape{ + + /** + * Constructor. + * + *

    The StarAutoShape constructor accepts 6 optional + * arguments that define it's properties.

    + * + * @param centerX A number indicating the center x-axis coordinate. + * @param centerY A number indicating the center y-axis coordinate. + * @param points A number indicating the count of points to include. + * @param angle A number indicating the start angleof the obejct value between 0° and 360°. + * @param radius A number indicating the radius. + * @param innerRadius A number indicating the inner radius. + * + */ + public function StarAutoShape(centerX:Number=NaN,centerY:Number=NaN,points:Number=NaN, + angle:Number=NaN,radius:Number=NaN,innerRadius:Number=NaN){ + super(); + if (centerX) this.centerX=centerX; + if (centerY) this.centerY=centerY; + if (points) this.points=points; + if (angle) this.angle=angle; + if (radius) this.radius=radius; + if (innerRadius) this.innerRadius=innerRadius; + + } + + /** + * StarAutoShape short hand data value. + * + *

    The StarAutoShape data property expects exactly 5 values a + * centerX, centerY, point count, angle, radius and an innerRadius + * separated by spaces.

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + + //parse the string + var tempArray:Array = value.split(" "); + + if (tempArray.length == 5) + { + super.data = value; + _centerX= tempArray[0]; + _centerY= tempArray[1]; + _points= tempArray[2]; + _angle= tempArray[3]; + _radius = tempArray[4]; + _innerRadius = tempArray[5]; + invalidated = true; + } + } + } + + private var _centerX:Number; + /** + * The x-axis coordinate of the center of the StarAutoShape. If not specified + * a default value of 0 is used. + **/ + public function get centerX():Number{ + if(!_centerX){return (hasLayout)? 0.5:0;} + return _centerX; + } + public function set centerX(value:Number):void{ + if (_centerX != value) { + _centerX = value; + invalidated = true; + } + } + + private var _centerY:Number; + /** + * The y-axis coordinate of the center of the StarAutoShape. If not specified + * a default value of 0 is used. + **/ + public function get centerY():Number{ + if(!_centerY){return (hasLayout)? 0.5:0;} + return _centerY; + } + public function set centerY(value:Number):void{ + if(_centerY != value){ + _centerY = value; + invalidated = true; + } + + } + + private var _points:Number; + /** + * The number of points to include in this StarAutoShape construction. + * The minimum number of points is 3 the default is 5. + **/ + public function get points():Number{ + if(!_points){return 5;} + return _points; + } + public function set points(value:Number):void{ + if (_points != value) { + if(value ==3 || value > 3){ + _points = value; + } + else{ + _points = 3; + } + + invalidated = true; + } + } + + + private var _angle:Number; + /** + * The start angle of rotation for this StarAutoShape a value + * between 0° and 360°. + * + * User Tip :: Transforms can also be used to adjust the angle. + **/ + public function get angle():Number{ + if(!_angle){return 0;} + return _angle; + } + public function set angle(value:Number):void{ + if (_angle != value) { + _angle = value; + invalidated = true; + } + } + + private var _radius:Number; + /** + * The radius of the StarAutoShape. If not specified a default value of 0 + * is used. + **/ + public function get radius():Number{ + if(!_radius){return (hasLayout)? .5:0;} + return _radius; + } + public function set radius(value:Number):void{ + if(_radius != value){ + _radius = value; + invalidated = true; + } + } + + private var _innerRadius:Number; + /** + * The inner radius of the StarAutoShape. If not specified a default value of 0 + * is used. Percent values (50%) are accepted. + **/ + [PercentProxy("innerRadiusPercent")] + public function get innerRadius():Number{ + if(!_innerRadius){return (hasLayout)? 0:0;} + return _innerRadius; + } + + public function set innerRadius(value:Number):void{ + if(_innerRadius != value){ + _innerRadius = value; + _innerRadiusPercent = NaN; + invalidated = true; + } + } + + private var _innerRadiusPercent:Number; + /** + * The percent inner radius of the StarAutoShape. If not specified a default value of 0 + * is used. Expects a value between 0 and 100. + * Note: Percent values between 0 and 1 are not yet supported. + **/ + public function get innerRadiusPercent():Number{ + if(!_innerRadiusPercent){return NaN;} + return _innerRadiusPercent; + } + + public function set innerRadiusPercent(value:Number):void{ + if(_innerRadiusPercent != value){ + _innerRadiusPercent = value; + invalidated = true; + } + } + + /** + * Draw the objects part(s) based on passed parameters. + */ + private function preDrawPart(x:Number, y:Number, points:Number, innerRadius:Number, outerRadius:Number,angle:Number=0):void{ + + var count:int = Math.abs(points); + + if (count>=2){ + + // calculate distance between points + var step:Number = (Math.PI*2)/points; + var halfStep:Number = step/2; + + // calculate starting angle in radians + var start:Number = (angle/180)*Math.PI; + commandStack.addMoveTo(x+(Math.cos(start)*outerRadius), + y-(Math.sin(start)*outerRadius)); + + // draw lines + for (var i:int=1; i<=count; i++){ + commandStack.addLineTo(x+Math.cos(start+(step*i)-halfStep)*innerRadius, + y-Math.sin(start+(step*i)-halfStep)*innerRadius); + + commandStack.addLineTo(x+Math.cos(start+(step*i))*outerRadius, + y-Math.sin(start+(step*i))*outerRadius); + } + } + } + + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + if(invalidated){ + + commandStack.source.length = 0; + + var tempInnerRadius:Number=0; + + if(!isNaN(_innerRadiusPercent)){ + tempInnerRadius = (innerRadiusPercent/100)*radius; + } + else if(isNaN(_innerRadius) && isNaN(_innerRadiusPercent)){ + tempInnerRadius = radius/2; + } + else{ + tempInnerRadius = innerRadius; + } + + preDrawPart(0,0,points,tempInnerRadius,radius,angle); + + invalidated = false; + } + + } + + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used or the most appropriate size is calculated. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + + //default to bounds if no width or height is set + //and we have layout + if(isNaN(_layoutConstraint.width)){ + tempLayoutRect.width = bounds.width; + } + + if(isNaN(_layoutConstraint.height)){ + tempLayoutRect.height = bounds.height; + } + + if(isNaN(_layoutConstraint.x)){ + tempLayoutRect.x = bounds.x; + } + + if(isNaN(_layoutConstraint.y)){ + tempLayoutRect.y = bounds.y; + } + + super.calculateLayout(tempLayoutRect); + _layoutRectangle = _layoutConstraint.layoutRectangle; + + //Dev Note: layout needs testing and verification. + //Seems we are getting a bunch of duplicated code for + //this may want to seperate this out. + + if (isNaN(_radius)) { + //handle layout defined startup values: + _radius = _layoutRectangle.width / 2; + + if (isNaN(_centerX)){ + _centerX = layoutRectangle.width / 2 + layoutRectangle.x; + } + else{ + _layoutRectangle.x -= _radius; + } + + if (isNaN(_centerY)){ + _centerY = layoutRectangle.height / 2 + layoutRectangle.y; + } + else{ + _layoutRectangle.y -= _radius; + } + + invalidated = true; + } + } + } + } + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + + //init the layout in this case done before predraw. + if (hasLayout) calculateLayout(); + + //re init if required + if (invalidated) preDraw(); + + super.draw(graphics, (rc)? rc:bounds); + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:StarAutoShape):void{ + + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke} + if (!_points){_points = value.points} + if (!_angle){_angle = value.angle} + if (!_radius){_radius = value.radius} + if (!_centerX){_centerX = value.centerX;} + if (!_centerY){_centerY = value.centerY;} + if (!_innerRadius){_innerRadius = value.innerRadius;} + + + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/autoshapes/TrapezoidAutoShape.as b/Degrafa/com/degrafa/geometry/autoshapes/TrapezoidAutoShape.as new file mode 100644 index 0000000..c2b4956 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/autoshapes/TrapezoidAutoShape.as @@ -0,0 +1,126 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.autoshapes{ + + //only need one offset here so need to exclude + [Exclude(name="offset2", kind="property")] + [Exclude(name="offset2Percent", kind="property")] + + [Exclude(name="offset3", kind="property")] + [Exclude(name="offset3Percent", kind="property")] + + [Exclude(name="offset4", kind="property")] + [Exclude(name="offset4Percent", kind="property")] + + /** + * The TrapezoidAutoShape element draws a trapezoid. + **/ + public class TrapezoidAutoShape extends AutoShapeTypeOffsets{ + + /** + * Constructor. + * + *

    The TrapezoidAutoShape constructor accepts 1 optional + * argument that defines it's properties.

    + * + * @param offset1 A number indicating the offset1. + */ + public function TrapezoidAutoShape(offset1:Number=NaN){ + super(); + if (offset1) this.offset1=offset1; + } + + /** + * TrapezoidAutoShape short hand data value. + * + *

    The TrapezoidAutoShape data property expects exactly 1 value an offset1

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + + //parse the string + var tempArray:Array = value.split(" "); + + if (tempArray.length == 1) + { + super.data = value; + _offset1= tempArray[0]; + invalidated = true; + } + } + } + + /** + * Draw the TrapezoidAutoShape part(s) based on the parameters. + */ + override protected function preDrawPart():void{ + + //store local to calculate + var _Offset1:Number=_offset1; + + //calc desired final offset 1 + if (isNaN(_Offset1) && hasLayout && isNaN(_offset1Percent)){ + if(_layoutRectangle.width){ + _Offset1 = _layoutRectangle.width/3; + } + else{ + _Offset1 = 0; + } + } + else if (!isNaN(_offset1Percent) && hasLayout){ + if(_offset1Percent >= 1){ + _Offset1 = (_offset1Percent/100)*_layoutRectangle.width; + } + else{ + _Offset1 = _offset1Percent*_layoutRectangle.width; + } + } + else{ + if(isNaN(_Offset1)){ + _Offset1 = 0; + } + } + + + commandStack.addMoveTo(_Offset1,0); + commandStack.addLineTo(_layoutRectangle.width - _Offset1, 0); + commandStack.addLineTo(_layoutRectangle.width,_layoutRectangle.height); + commandStack.addLineTo(0,_layoutRectangle.height); + commandStack.addLineTo(_Offset1,0); + + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:TrapezoidAutoShape):void{ + if (!fill){fill=value.fill;} + if (!stroke){stroke = value.stroke} + if (!_offset1){_offset1 = value.offset1} + if (!_offset1Percent){_offset1Percent = value.offset1Percent} + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/command/CommandCollection.as b/Degrafa/com/degrafa/geometry/command/CommandCollection.as new file mode 100644 index 0000000..68bb17d --- /dev/null +++ b/Degrafa/com/degrafa/geometry/command/CommandCollection.as @@ -0,0 +1,34 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.command +{ + import com.degrafa.core.collections.DegrafaCollection; + + public class CommandCollection extends DegrafaCollection + { + public function CommandCollection(array:Array=null) + { + super(CommandStackItem, array, true, false); + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/command/CommandCursor.as b/Degrafa/com/degrafa/geometry/command/CommandCursor.as new file mode 100644 index 0000000..f92cb47 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/command/CommandCursor.as @@ -0,0 +1,89 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.command +{ + import com.degrafa.core.collections.DegrafaCursor; + + public class CommandCursor extends DegrafaCursor + { + public function CommandCursor(source:Array) + { + super(source); + } + + public function nextCommand(type:int):CommandStackItem + { + var tempIndex:int = currentIndex; + var found:Object; + + while(moveNext()) + { + if(current.type == type) + { + found = current; + break; + } + } + + currentIndex = tempIndex; + return CommandStackItem(found); + } + + public function previousCommand(type:String):CommandStackItem + { + var tempIndex:int = currentIndex; + var found:Object; + + while(movePrevious()) + { + if(current.type == type) + { + found = current; + break; + } + } + + currentIndex = tempIndex; + return CommandStackItem(found); + } + + public function moveNextCommand(type:int):Boolean + { + while(moveNext()) + { + if(current.type == type) + return true; + } + return false; + } + + public function movePreviousCommand(type:int):Boolean + { + while(movePrevious()) + { + if(current.type == type) + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/command/CommandStack.as b/Degrafa/com/degrafa/geometry/command/CommandStack.as new file mode 100644 index 0000000..08740b6 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/command/CommandStack.as @@ -0,0 +1,1284 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// +// Some algorithms based on code from Trevor McCauley, www.senocular.com +//////////////////////////////////////////////////////////////////////////////// + +package com.degrafa.geometry.command{ + + import com.degrafa.core.collections.DegrafaCursor; + import com.degrafa.core.IGraphicsFill; + import com.degrafa.core.IGraphicsStroke; + import com.degrafa.decorators.IDecorator; + import com.degrafa.decorators.IRenderDecorator; + import com.degrafa.geometry.Geometry; + import com.degrafa.geometry.display.IDisplayObjectProxy; + import com.degrafa.geometry.utilities.GeometryUtils; + import com.degrafa.GeometryComposition; + import com.degrafa.transform.TransformBase; +// import flash.display.Bitmap; + import flash.filters.ColorMatrixFilter; + + import flash.display.BitmapData; + import flash.display.DisplayObject; + import flash.display.Graphics; + import flash.display.Shape; + import flash.display.Sprite; + import flash.filters.BitmapFilter; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.net.registerClassAlias; + + /** + * The CommandStack manages and stores the render process. All geometry goes + * through this process at draw time. The command stack provides convenient access + * to all commands that make up the drawing of the Geometry and helper methods. + **/ + final public class CommandStack{ + + static public const IS_REGISTERED:Boolean = !registerClassAlias("com.degrafa.geometry.command.CommandStack", CommandStack); + + static public var transMatrix:Matrix=new Matrix(); + static public var currentLayoutMatrix:Matrix=new Matrix(); + static public var currentTransformMatrix:Matrix = new Matrix(); + + static public var currentStroke:IGraphicsStroke; + static public var currentFill:IGraphicsFill; + static public var currentContext:Graphics; + + //single references to point objects used for internal calculations: + static private var transXY:Point=new Point(); + static private var transCP:Point = new Point(); + + //paint related stacks + static private var svgClipMode:Array; + static private var alphaStack:Array = []; + + //TODO this has to be made private eventually otherwise we can lose + //previous and next references + public var source:Array = []; + + public var owner:Geometry; + public var parent:CommandStackItem; + + //rasterized rendering support:temporary displayobjects: + private var _fxShape:Shape; + private var _maskRender:Shape; + private var _container:Sprite; + private var _original:Sprite; + private var _mask:Sprite; + static private const _maskFilt:Array = [new ColorMatrixFilter([ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2125,0.7154,0.0721,0,0])]; + + /** + * The current contextual alpha value that represents nested alpha values during render. This is used during requests from + * paint objects. + */ + static public function get currentAlpha():Number { + if (!alphaStack.length) return 1; + return alphaStack[alphaStack.length-1]; + } + /** + * called internally by Geometry during a draw loop to create a nested alpha 'context' + * dev: this and related methods may change in the future. + * @param alpha + */ + static public function stackAlpha(alpha:Number):void { + if (!alphaStack.length) alphaStack.push( alpha ) + else alphaStack.push(alpha*alphaStack[alphaStack.length-1]) + } + /** + * called internally during endDraw from Geometry to remove the local alpha 'context' after any children have rendered + * dev: this and related methods may change in the future. + */ + static public function unstackAlpha():void { + if (!alphaStack.length) { + trace('error: unmatched unstackAlpha calls') + return; + } + alphaStack.length--; + } + + static private var _cacheable:Array = ['alphaStack', 'currentContext', 'currentFill', 'currentLayoutMatrix', 'currentStroke', 'currentTransformMatrix', 'transMatrix']; + /** + * Helper function to permit caching values before nested calls inside a draw/endDraw phase + * dev: this and related methods may change in the future. + * @return an object holding cached settings to be re-applied following a nested call + */ + static public function getSettingsCache():Object { + var ret:Object = { }; + for each(var item:String in _cacheable) { + var iObj:Object = CommandStack[item]; + if (iObj is Array) { + ret[item] = (iObj as Array).concat(); + continue; + } + if (iObj is Matrix) { + ret[item] = (iObj as Matrix).clone(); + continue; + } + ret[item] = CommandStack[item]; + } + return ret; + } + /** + * Helper function to reapply a set of cached settings to reset the context + * dev: this and related methods may change in the future. + * @param values + */ + static public function resetCacheValues(values:Object):void { + for (var item:String in values) { + CommandStack[item]=values[item]; + } + } + /** + * Helper function to reset a the rendering context to its default state + * dev: this and related methods may change in the future. + * @param values + */ + static public function removeContextualSettings():void { + for each(var item:String in _cacheable) { + var iObj:Object = CommandStack[item]; + if (iObj is Array) { + (iObj as Array).length=0; + } + if (iObj is Matrix) { + (iObj as Matrix).identity(); + } + } + } + + public function CommandStack(geometry:Geometry = null){ + super(); + this.owner = geometry; + } + + + + /** + * Setups the layout and transforms + **/ + private function predraw():void{ + + var requester:Geometry = owner; + //establish a transform context if there are ancestral transforms + while (requester.parent){ + //assign a transformContext based on the closest ancestral transform + requester = (requester.parent as Geometry); + if (requester.transform) { + owner.transformContext = requester.transform.getTransformFor(requester); + break; + } + } + + var layout:Boolean = owner.hasLayout; + transMatrix=null; + currentLayoutMatrix.identity(); + + //setup a layout transform + if (layout){ + //give DisplayObjectProxies the ability to define their own bounds + var tempRect:Rectangle = (owner is IDisplayObjectProxy)?owner.bounds:bounds; + var layoutRect:Rectangle = owner.layoutRectangle; + if (!tempRect.equals(layoutRect) ) { + if (layoutRect.width!=tempRect.width || layoutRect.height!=tempRect.width){ + currentLayoutMatrix.translate( -tempRect.x, -tempRect.y) + + //If the developer does not want to force scale on layout (GeometryUnion) he can set this flag to stop it + //Tom Gonzalez 1/16/2009 + if (owner.scaleOnLayout) + currentLayoutMatrix.scale(layoutRect.width / tempRect.width, layoutRect.height / tempRect.height); + + currentLayoutMatrix.translate(layoutRect.x, layoutRect.y); + } else currentLayoutMatrix.translate(layoutRect.x-tempRect.x, layoutRect.y-tempRect.y); + owner._layoutMatrix = currentLayoutMatrix.clone(); + transMatrix = currentLayoutMatrix.clone(); + } else { + layout = false; + owner._layoutMatrix = null; + currentLayoutMatrix.identity(); + } + } + else { + if (owner._layoutMatrix){ + owner._layoutMatrix = null; + } + } + + + var trans:Boolean = (owner.transformContext || (owner.transform && !owner.transform.isIdentity)); + + //combine the layout and transform into one matrix + if (trans) { + currentTransformMatrix = (owner.transform)? owner.transform.getTransformFor(owner): owner.transformContext; + if (!layout){ + transMatrix = (owner.transform)? owner.transform.getTransformFor(owner): owner.transformContext; + } + else{ + transMatrix.concat((owner.transform)? owner.transform.getTransformFor(owner): owner.transformContext) + } + } + else { + currentTransformMatrix.identity(); + if (!layout) transMatrix = null; + } + } + + + private function initDecorators():void { + hasRenderDecoration = false; + for each (var item:IDecorator in owner.decorators){ + item.initialize(this); + if(item is IRenderDecorator){ + hasRenderDecoration = true; + } + } + } + + private function endDecorators():void { + hasRenderDecoration = false; + for each (var item:IDecorator in owner.decorators){ + item.end(this); + if(item is IRenderDecorator){ + hasRenderDecoration = true; + } + } + } + + + private var hasmask:Boolean; + private var hasfilters:Boolean; + private var isComp:Boolean; + + /** + * Initiates the render phase. + * @return true if the final phase of rendering in Geometry (endDraw) should be skipped + **/ + public function draw(graphics:Graphics, rc:Rectangle):Boolean { + + //exit if no command stack on this item, unless owner is a 'Group' style implementation, e.g. GeometryComposition with filters or masking + //dev note: should set this up for an interface test rather than specifically for GeometryComposition...consider GeometryRepeater etc + if (!(isComp=owner is GeometryComposition && (owner.hasFilters || owner.mask))) if(source.length==0 && !(owner is IDisplayObjectProxy)){return false;} + + currentContext = graphics; + //setup requirements before the render + predraw() + + if((owner is IDisplayObjectProxy)){ + if(!IDisplayObjectProxy(owner).displayObject){ + return false; + } + var displayObject:DisplayObject = IDisplayObjectProxy(owner).displayObject; + //apply the filters + if(owner.hasFilters){ + displayObject.filters = owner.filters; + } else if (displayObject.filters.length) displayObject.filters = []; + + if (transMatrix && (IDisplayObjectProxy(owner).transformBeforeRender || (owner._layoutMatrix && IDisplayObjectProxy(owner).layoutMode == 'scale'))) { + var transObject:DisplayObject; + //always expect a single child of this displayobject + if(Sprite(displayObject).numChildren!=0){ + transObject = Sprite(displayObject).getChildAt(0); + if (!IDisplayObjectProxy(owner).transformBeforeRender) { + //scale layoutmode only, without a pretransformed capture: scale before capture to bitmapData: + transObject.transform.matrix = CommandStack.currentLayoutMatrix; + } else { + if (IDisplayObjectProxy(owner).layoutMode == 'scale') { + transObject.transform.matrix = CommandStack.transMatrix; + } + else { + if (owner._layoutMatrix) { + var tempMat:Matrix = owner._layoutMatrix.clone(); + tempMat.a = 1; tempMat.d = 1; + tempMat.concat(CommandStack.currentTransformMatrix); + transObject.transform.matrix = tempMat; + } else transObject.transform.matrix = CommandStack.currentTransformMatrix; + } + } + } + } + + // maybe there are paint settings on some owners at this point: + //setup the fill + if (!svgClipMode) owner.initFill(graphics, rc); + //setup the stroke + if (!svgClipMode) owner.initStroke(graphics, rc); + + //if (owner.hasDecorators) initDecorators(); + renderBitmapDatatoContext(IDisplayObjectProxy(owner).displayObject, graphics, !IDisplayObjectProxy(owner).transformBeforeRender, rc); + return false; + + } + else{ + + //setup a cursor for the path data interation + _cursor = new DegrafaCursor(source); + + //setup the temporary shape to draw to in place + //of the passsed graphics context + hasmask = (owner.mask != null); + hasfilters = (owner.hasFilters); + if(hasfilters || hasmask){ + if (!_fxShape){ + _fxShape = new Shape(); + _container = new Sprite(); + _original = new Sprite(); + _original.addChild(_fxShape); + _container.addChild(_original); + } + else{ + _fxShape.graphics.clear(); + } + + if (hasmask) { + //dev note: need to change this so mask is only redrawn when necessary + if (!_maskRender) { + _mask = new Sprite(); + _maskRender = new Shape(); + _mask.addChild(_maskRender); + _container.addChild(_mask); + } + _maskRender.graphics.clear(); + _mask.graphics.clear(); + //set the maskSpace implementation: + if (owner.maskSpace == "local" && transMatrix) _mask.transform.matrix = transMatrix; + else _mask.transform.matrix =new Matrix(); + + //cache the current settings as rendering the mask will alter them + // var cacheLayout:Matrix = currentLayoutMatrix? currentLayoutMatrix.clone():null; + // var cacheTransform:Matrix = currentTransformMatrix? currentTransformMatrix.clone():null; + // var cacheCombo:Matrix = transMatrix? transMatrix.clone():null; + var temp:Object = getSettingsCache(); + //svg clipping for evenodd clip-rule is achieved this way: + if (owner.maskMode == "svgClip" || owner.maskMode == "clip") { + if (owner.maskMode == "svgClip" ) { + //match svg clipping behaviour - ensure there is a fill, linestyle is irrelevant: + _maskRender.graphics.lineStyle(); + _maskRender.graphics.beginFill(0, 1); + } + _original.cacheAsBitmap = _mask.cacheAsBitmap = false; + if (_maskRender.filters.length) { + _maskRender.filters = []; + _maskRender.blendMode = "normal"; + } + //dev note: check whether clipping in svg ignores filters, that probably should be done as it will create rectangular clipping regions instead of the original shape + + removeContextualSettings(); + svgClipMode = svgClipMode? svgClipMode.concat(true):((owner.maskMode=="svgClip")?[true]:null); + owner.mask.draw(_maskRender.graphics, owner.mask.bounds); + if (svgClipMode) { + svgClipMode.length--; + if (!svgClipMode.length) svgClipMode = null; + } + resetCacheValues(temp); + } else { + + removeContextualSettings(); + owner.mask.draw(_maskRender.graphics, owner.mask.bounds); + + if (owner.maskMode == "svgMask") { + //implementation of a maskMode that is compatible with SVG masking + //omitted: owner.mask.draw(_mask.graphics, owner.mask.bounds); + //faster version: + //dev note: fp10 version use graphicsCopy with this same approach or a dedicated PB filter + var rect:Rectangle = _maskRender.getBounds(_maskRender); + var bmd:BitmapData = new BitmapData(rect.width, rect.height, true, 0); + var bmdmat:Matrix = new Matrix(1, 0, 0, 1, -rect.x, -rect.y); + if (_maskRender.filters.length) _maskRender.filters = []; + bmd.draw(_maskRender, bmdmat, null, null, null, true); + bmdmat.invert(); + _mask.graphics.beginBitmapFill(bmd, bmdmat, false, true); + _mask.graphics.drawRect(rect.x, rect.y, rect.width, rect.height); + _mask.graphics.endFill(); + _maskRender.filters = _maskFilt; + _maskRender.blendMode = "alpha"; + + } else { + //_maskRender.blendMode = "normal"; + + if (_maskRender.filters.length) { + _maskRender.filters = []; + _maskRender.blendMode = "normal"; + + } + } + _original.cacheAsBitmap = _mask.cacheAsBitmap = true; + } + resetCacheValues(temp); + + //restore cached transform settings + // currentLayoutMatrix = cacheLayout; + // currentTransformMatrix = cacheTransform; + // transMatrix = cacheCombo; + + if (hasfilters) { + hasfilters = false; + if (!svgClipMode) _fxShape.filters = owner.filters; + else _fxShape.filters = []; + } else if (_fxShape.filters.length) _fxShape.filters = []; + if (owner.maskMode != "unMask") _original.mask = _mask; + else _original.mask = null; + } else { + if (_maskRender) { + _mask.cacheAsBitmap = false; + _maskRender.graphics.clear(); + } + _original.cacheAsBitmap = false; + if (_fxShape.mask) _fxShape.mask = null; + if (_fxShape.filters.length)_fxShape.filters = []; + } + + //setup the fill + if (!svgClipMode) owner.initFill(_fxShape.graphics, rc); + else _fxShape.graphics.beginFill(0, 1); + //setup the stroke + if (!svgClipMode) owner.initStroke(_fxShape.graphics, rc); + + //init the decorations if required + + if (owner.hasDecorators) initDecorators() else hasRenderDecoration = false; + lineTo = _fxShape.graphics.lineTo; + curveTo = _fxShape.graphics.curveTo; + moveTo = _fxShape.graphics.moveTo; + if (!isComp) renderCommandStack(_fxShape.graphics, rc, _cursor); + else { + //for a GeometryComposition, draw the children + owner.endDraw(_fxShape.graphics); + } + + if (owner.hasDecorators) endDecorators(); + renderBitmapDatatoContext(_container, graphics); + return isComp; + + } + else { + + //setup the stroke + if (!svgClipMode) owner.initStroke(graphics, rc); + //setup the fill + if (!svgClipMode) owner.initFill(graphics, rc); + else graphics.beginFill(0,1) + + //init the decorations if required + if (owner.hasDecorators) initDecorators()else hasRenderDecoration = false; + lineTo = graphics.lineTo; + curveTo = graphics.curveTo; + moveTo = graphics.moveTo; + renderCommandStack(graphics, rc, _cursor); + if (owner.hasDecorators) endDecorators(); + return false; + } + } + } + + /** + * + * @private + */ + private function renderBitmapDatatoContext(source:DisplayObject,context:Graphics, viaCommandStack:Boolean=false, rc:Rectangle=null):void{ + + if(!source){return;} + + var sourceRect:Rectangle=source.getBounds(source); + + //if (owner.mask) sourceRect = sourceRect.intersection(_maskRender.getBounds(_maskRender)); + + if(sourceRect.isEmpty()){return;} + var filteredRect:Rectangle = sourceRect.clone(); + + + if (hasfilters) { + source.filters = owner.filters; + filteredRect.x = filteredRect.y = 0; + filteredRect.width = Math.ceil(filteredRect.width); + filteredRect.height = Math.ceil(filteredRect.height); + if (!filteredRect.width || !filteredRect.height) return; //nothing to draw + filteredRect = updateToFilterRectangle(filteredRect,source); + filteredRect.offset(sourceRect.x, sourceRect.y); + } + + var bitmapData:BitmapData; + + var clipTo:Rectangle = (owner.clippingRectangle)? owner.clippingRectangle:null; + + if(filteredRect.width<1 || filteredRect.height<1){ + return; + + } else { + //adjust to pixelbounds: + // filteredRect.y = Math.floor(filteredRect.y ); + filteredRect.width = Math.ceil(filteredRect.width +(filteredRect.x -(filteredRect.x = Math.floor(filteredRect.x )))); + filteredRect.height = Math.ceil(filteredRect.height +(filteredRect.y-(filteredRect.y = Math.floor(filteredRect.y )))); + if (filteredRect.width > 2880 || filteredRect.height > 2880) { + //trace('DEBUG:oversize bitmap : '+owner.id) + return; + } + } + + + var mat:Matrix + if (owner is IDisplayObjectProxy){ + //padding with transparent pixel border + bitmapData = new BitmapData(filteredRect.width+4 , filteredRect.height+4,true,0); + mat = new Matrix(1, 0, 0, 1, 2 - filteredRect.x, 2 - filteredRect.y) + // bitmapData = new BitmapData(filteredRect.width , filteredRect.height,true,0); + // mat = new Matrix(1, 0, 0, 1, - filteredRect.x, - filteredRect.y) + + } else { + bitmapData = new BitmapData(filteredRect.width , filteredRect.height,true,0); + mat = new Matrix(1, 0, 0, 1, - filteredRect.x, - filteredRect.y) + } + bitmapData.draw(source, mat, null, null, clipTo, true); + mat.invert(); + + if (!viaCommandStack) { + var tempMat:Matrix + if (owner.hasFilters &&!sourceRect.equals(filteredRect) && owner is IDisplayObjectProxy ) { + //adjust for scale- downscale to fit filters in the same bounds: + + mat = new Matrix(sourceRect.width / filteredRect.width, 0, 0, sourceRect.height / filteredRect.height, mat.tx, mat.ty); + context.lineStyle(); + context.beginBitmapFill(bitmapData, mat,false,true); + context.drawRect(Math.floor(sourceRect.x),Math.floor(sourceRect.y), Math.ceil(sourceRect.width), Math.ceil(sourceRect.height)); + context.endFill(); + } else { + //draw at filtered size + context.lineStyle(); + context.beginBitmapFill(bitmapData, mat,false,true); + context.drawRect(filteredRect.x, filteredRect.y, filteredRect.width, filteredRect.height); + context.endFill(); + } + } else { + if (transMatrix) { + var temp:Matrix + if (owner is IDisplayObjectProxy ) { + if (owner._layoutMatrix && IDisplayObjectProxy(owner).layoutMode=="scale") { + mat.concat(CommandStack.currentTransformMatrix) + } else { + mat.concat( currentTransformMatrix); + transMatrix = currentTransformMatrix; + } + } else mat.concat(transMatrix); + } + context.beginBitmapFill(bitmapData, mat, false, true); + lineTo = context.lineTo; + curveTo = context.curveTo; + moveTo = context.moveTo; + renderCommandStack(context, rc, new DegrafaCursor(this.source)) + } + } + + private function updateToFilterRectangle(filterRect:Rectangle,source:DisplayObject):Rectangle{ + + //iterate the filters to calculte the desired rect + try{ + var bitmapData:BitmapData = new BitmapData(filterRect.width, filterRect.height, true, 0); + } catch (e:Error) { + trace(e + ":" + filterRect) + return filterRect; + } + + //compute the combined filter rectangle + for each (var filter:BitmapFilter in owner.filters){ + filterRect = filterRect.union(bitmapData.generateFilterRect(filterRect,filter)); + } + return filterRect; + + } + + private var hasRenderDecoration:Boolean; + //called from render loop if the geometry has an IRenderDecorator + private function delegateGraphicsCall(methodName:String,graphics:Graphics,x:Number=0,y:Number=0,cx:Number=0,cy:Number=0,x1:Number=0,y1:Number=0):*{ + //permit each decoration to do work on the current segment + for each (var item:IRenderDecorator in owner.decorators) { + if (item.isValid){ + switch(methodName){ + case "moveTo": + return item.moveTo(x,y,graphics); + break; + case "lineTo": + return item.lineTo(x,y,graphics); + break; + case "curveTo": + return item.curveTo(cx,cy,x1,y1,graphics); + break; + } + } + } + } + + //calls each delegate in order + private function processDelegateArray(delegates:Array,item:CommandStackItem,graphics:Graphics,currentIndex:int):CommandStackItem{ + + for each (var delegate:Function in delegates){ + item = delegate(this,item,graphics,currentIndex); + } + return item; + } + + /** + * Array of delegate functions to be called during the render loop when + * each item is about to be rendered. Individual item + * delegates take precedence if both are set + */ + private var _globalRenderDelegateStart:Array; + public function get globalRenderDelegateStart():Array{ + return _globalRenderDelegateStart?_globalRenderDelegateStart:null;; + } + public function set globalRenderDelegateStart(value:Array):void{ + if(_globalRenderDelegateStart != value){ + _globalRenderDelegateStart = value; + invalidated = true; + } + } + + /** + * Function to be called during the render loop when + * each item has just been rendered. Individual item + * delegates take precedence if both are set + */ + private var _globalRenderDelegateEnd:Array; + public function get globalRenderDelegateEnd():Array{ + return _globalRenderDelegateEnd?_globalRenderDelegateEnd:null; + } + public function set globalRenderDelegateEnd(value:Array):void{ + if(_globalRenderDelegateEnd != value){ + _globalRenderDelegateEnd = value; + invalidated = true; + } + } + + private var lineTo:Function; + private var moveTo:Function; + private var curveTo:Function; + + public function simpleRender(graphics:Graphics, rc:Rectangle):void { + lineTo = graphics.lineTo; + curveTo = graphics.curveTo; + moveTo = graphics.moveTo; + renderCommandStack(graphics, rc, new DegrafaCursor(this.source)); + } + /** + * Principle render loop. Use delgates to override specific items + * while the render loop is processing. + **/ + private function renderCommandStack(graphics:Graphics,rc:Rectangle,cursor:DegrafaCursor=null):void{ + + var item:CommandStackItem; + while(cursor.moveNext()){ + + item = cursor.current; + + //defer to the start delegate if one found + if (item.renderDelegateStart){ + item=processDelegateArray(item.renderDelegateStart,item,graphics,cursor.currentIndex); + } + + //process any global type items + if (_globalRenderDelegateStart){ + item=processDelegateArray(_globalRenderDelegateStart,item,graphics,cursor.currentIndex); + } + + if(item.skip){continue;} + + with(item){ + switch(type){ + case CommandStackItem.MOVE_TO: + if (transMatrix){ + transXY.x = x; + transXY.y = y; + transXY = transMatrix.transformPoint(transXY); + if(hasRenderDecoration){ + delegateGraphicsCall("moveTo",graphics,transXY.x, transXY.y); + } + else{ + moveTo(transXY.x, transXY.y); + } + } + else{ + if(hasRenderDecoration){ + delegateGraphicsCall("moveTo",graphics,x, y); + } + else{ + moveTo(x,y); + } + } + break; + case CommandStackItem.LINE_TO: + if (transMatrix){ + transXY.x = x; + transXY.y = y; + transXY = transMatrix.transformPoint(transXY); + if(hasRenderDecoration){ + delegateGraphicsCall("lineTo",graphics,transXY.x, transXY.y); + } + else{ + lineTo(transXY.x, transXY.y); + } + } + else{ + if(hasRenderDecoration){ + delegateGraphicsCall("lineTo",graphics,x, y); + } + else{ + lineTo(x,y); + } + } + break; + case CommandStackItem.CURVE_TO: + if (transMatrix){ + transXY.x = x1; + transXY.y = y1; + transCP.x = cx; + transCP.y = cy; + transXY = transMatrix.transformPoint(transXY); + transCP = transMatrix.transformPoint(transCP); + if(hasRenderDecoration){ + delegateGraphicsCall("curveTo",graphics,0,0,transCP.x,transCP.y,transXY.x,transXY.y); + } + else{ + curveTo(transCP.x,transCP.y,transXY.x,transXY.y); + } + } + else{ + if(hasRenderDecoration){ + delegateGraphicsCall("curveTo",graphics,0,0,cx,cy,x1,y1); + } + else{ + curveTo(cx,cy,x1,y1); + } + } + break; + case CommandStackItem.DELEGATE_TO: + item.delegate(this,item,graphics,cursor.currentIndex); + break; + + //recurse if required + case CommandStackItem.COMMAND_STACK: + renderCommandStack(graphics,rc,new DegrafaCursor(commandStack.source)) + break; + + } + } + + //defer to the end delegate if one found + if (item.renderDelegateEnd){ + item=processDelegateArray(item.renderDelegateEnd,item,graphics,cursor.currentIndex); + } + + //process any global type items + if (_globalRenderDelegateEnd){ + item=processDelegateArray(_globalRenderDelegateEnd,item,graphics,cursor.currentIndex); + } + + } + } + + /** + * Updates the item with the correct previous and next reference + **/ + private function updateItemRelations(item:CommandStackItem,index:int):void{ + item.previous = (index>0)? source[index-1]:null; + if(item.previous){ + if(item.previous.type == CommandStackItem.COMMAND_STACK){ + item.previous = item.previous.commandStack.lastNonCommandStackItem; + } + item.previous.next = (item.type == CommandStackItem.COMMAND_STACK)? item.commandStack.firstNonCommandStackItem:item; + } + } + + /** + * get the last none commandstack type (CommandStackItem.COMMAND_STACK) + * item in this command stack. + **/ + public function get lastNonCommandStackItem():CommandStackItem { + var i:int = source.length-1; + while (i > 0) { + if(source[i].type != CommandStackItem.COMMAND_STACK){ + return source[i]; + } + else{ + return CommandStackItem(source[i]).commandStack.lastNonCommandStackItem; + } + i-- + } + return source[0]; + } + + /** + * Get the first none commandstack type (CommandStackItem.COMMAND_STACK) + * item in this command stack. + **/ + public function get firstNonCommandStackItem():CommandStackItem{ + + var i:int = source.length-1; + while(i0){ + if(source[i].type == 1 || source[i].type == 2){ + return source[i]; + } + + if(source[i].type == 4){ + //recurse todo + return source[i].commandStack.lastSegmentWithLength; + } + i--; + } + + return source[length-1]; + } + + + /** + * Returns the point at t(0-1) on the path. + **/ + public function pathPointAt(t:Number):Point { + + if(!source.length){return new Point(0,0);} + + t = cleant(t); + + var curLength:Number = 0; + + if (t == 0){ + var firstSegment:CommandStackItem =firstSegmentWithLength; + curLength = firstSegment.segmentLength; + return adjustPointToLayoutAndTransform(firstSegment.segmentPointAt(t)); + } + else if (t == 1){ + return adjustPointToLayoutAndTransform(lastSegmentWithLength.segmentPointAt(t)); + } + + var tLength:Number = t*pathLength; + var lastLength:Number = 0; + var item:CommandStackItem; + var n:Number = source.length; + + for each (item in source){ + + with(item){ + if (type != 0){ + curLength += segmentLength; + } + else{ + continue; + } + if (tLength <= curLength){ + return adjustPointToLayoutAndTransform(segmentPointAt((tLength - lastLength)/segmentLength)); + } + } + + lastLength = curLength; + } + + return new Point(0, 0); + + } + + /** + * Returns the angle of a point t(0-1) on the path. + **/ + public function pathAngleAt(t:Number):Number { + + if(!source.length){return 0;} + + t = cleant(t); + + var curLength:Number = 0; + + if (t == 0){ + var firstSegment:CommandStackItem =firstSegmentWithLength; + curLength = firstSegment.segmentLength; + return firstSegment.segmentAngleAt(t); + } + else if (t == 1){ + return lastSegmentWithLength.segmentAngleAt(t); + } + + var tLength:Number = t*pathLength; + var lastLength:Number = 0; + var item:CommandStackItem; + var n:Number = source.length; + + for each (item in source){ + with(item){ + if (type != 0){ + curLength += segmentLength; + } + else{ + continue; + } + + if (tLength <= curLength){ + return segmentAngleAt((tLength - lastLength)/segmentLength); + } + } + + lastLength = curLength; + } + return 0; + } + + private function cleant(t:Number, base:Number=NaN):Number { + if (isNaN(t)) t = base; + else if (t < 0 || t > 1){ + t %= 1; + if (t == 0) t = base; + else if (t < 0) t += 1; + } + return t; + } + } + +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/command/CommandStackItem.as b/Degrafa/com/degrafa/geometry/command/CommandStackItem.as new file mode 100644 index 0000000..06e4901 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/command/CommandStackItem.as @@ -0,0 +1,619 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.command{ + + import com.degrafa.geometry.utilities.GeometryUtils; + import flash.geom.Matrix; + + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.net.registerClassAlias; + + /** + * The CommandStackItem manages and stores one of type (MOVE_TO,LINE_TO,CURVE_TO,DELEGATE_TO,COMMAND_STACK). + * It also serves as an injection point for decoration. Like the CommandStack it provides management and + * helper methods. + **/ + final public class CommandStackItem{ + + static public const IS_REGISTERED:Boolean = !( registerClassAlias("com.degrafa.geometry.command.CommandStackItem", CommandStackItem) || + registerClassAlias("flash.geom.Point", Point)|| registerClassAlias("flash.geom.Rectangle", Rectangle)); + + + public static const MOVE_TO:int=0; + public static const LINE_TO:int=1; + public static const CURVE_TO:int=2; + public static const DELEGATE_TO:int=3; + public static const COMMAND_STACK:int=4; + + public var type:int; + public var id:String; + public var reference:String; + + //skip flag for rendering + public var skip:Boolean; + internal var indexInParent:uint; + internal var parent:CommandStack; + + public function CommandStackItem(type:int=0,x:Number=NaN,y:Number=NaN,x1:Number=NaN,y1:Number=NaN,cx:Number=NaN,cy:Number=NaN,commandStack:CommandStack=null){ + + invalidated = true; + + this.type = type; + _x=x; + _y=y; + _x1=x1; + _y1=y1; + _cx=cx; + _cy=cy; + + if(commandStack){ + this.commandStack = commandStack; + } + + } + + /** + * A reference to the previouse item in the parent command stack. + */ + private var _previous:CommandStackItem; + public function get previous():CommandStackItem{ + return _previous; + } + public function set previous(value:CommandStackItem):void{ + if(_previous != value){ + _previous = value; + if (value.type == CommandStackItem.COMMAND_STACK) { + _previous = value.commandStack.lastNonCommandStackItem; + } + //on set if this is a command stack then forward to the first item + if(type==CommandStackItem.COMMAND_STACK && commandStack.length){ + CommandStackItem(commandStack.source[0]).previous = _previous; + } + } + } + + /** + * A reference to the next item in the parent command stack. + */ + private var _next:CommandStackItem; + public function get next():CommandStackItem{ + return _next; + } + public function set next(value:CommandStackItem):void{ + if(_next != value){ + _next = value; + + //on set if this is a command stack then forward to the last item + if (type == CommandStackItem.COMMAND_STACK) { + commandStack.lastNonCommandStackItem.next = value; + } + } + } + + private var _invalidated:Boolean = true; + /** + * Specifies whether bounds for this object is to be re calculated. + * Only property updates which affect the computation of this + * object set this property. It will only get recalculated + * when bounds is requested. + **/ + public function get invalidated():Boolean{ + return _invalidated; + } + public function set invalidated(value:Boolean):void{ + _invalidated = value; + if(_invalidated !=value){ + _invalidated = value; + + if(_invalidated){ + parent.invalidated = _invalidated; + lengthInvalidated = _invalidated; + } + } + } + + private var _lengthInvalidated:Boolean = true; + /** + * Specifies whether length this object is to be re calculated. + * It will only get recalculated when segmentLength is requested. + **/ + public function get lengthInvalidated():Boolean{ + return _lengthInvalidated; + } + public function set lengthInvalidated(value:Boolean):void{ + _lengthInvalidated = value; + if(_lengthInvalidated !=value){ + _lengthInvalidated = value; + + if(_lengthInvalidated){ + parent.lengthInvalidated = value; + } + } + } + + + + private var _bounds:Rectangle; + /** + * The calculated bounds for this object. + */ + + public function get bounds():Rectangle { + if (invalidated) calcBounds(); + return _bounds; + } + + /** + * Calculates the bounds for this item. + **/ + public function calcBounds():void{ + + if(!invalidated){return;} + var start:Point + + //using the previous item calculate the bounds for this object + switch(type){ + + case CommandStackItem.MOVE_TO: + if (isNaN(_x) || isNaN(_y)) { + //no bounds: + _bounds = new Rectangle(); + skip = true; + break; + } + _bounds = new Rectangle(x, y, 0, 0); + break; + + case CommandStackItem.LINE_TO: + if (isNaN(_x) || isNaN(_y)) { + //no bounds: + _bounds = new Rectangle(); + skip = true; + break; + } + start = this.start; + + _bounds = new Rectangle(Math.min(x,start.x),Math.min(y,start.y), + Math.abs(x - start.x), Math.abs(y - start.y)); + if (!_bounds.width) _bounds.width = 0.0001; + if (!_bounds.height) _bounds.height = 0.0001; + break; + + case CommandStackItem.CURVE_TO: + + if (isNaN(_cx) || isNaN(_cy) || isNaN(_x1)|| isNaN(_y1)) { + //no bounds: + _bounds = new Rectangle(); + skip = true; + break; + } + start = this.start; + _bounds = GeometryUtils.bezierBounds(start.x, start.y, cx, cy, x1, y1).clone(); + break; + + case CommandStackItem.COMMAND_STACK: + _bounds = commandStack.bounds; + break; + } + invalidated = false; + + } + + + /** + * Return the start point as a point object. This is considered to be + * the previous segments end point. You can only get the start point + * you can not set it. + **/ + public function get start():Point{ + if (_previous) { + if (_previous.skip) return (_previous.start); + return _previous.end; + } + else { + return new Point(0,0); + } + + //TODO add case if the last item is a command stack type + //unless decided to add the proper item in the case of + //command stack. + + + } + + /** + * Return the control point as a point object + **/ + public function get control():Point{ + return new Point(cx,cy); + } + /** + * Set the control point to the point value passed. + **/ + public function set control(value:Point):void{ + cx = value.x; + cy = value.y; + } + + /** + * Return the end point as a point object + **/ + public function get end():Point{ + return new Point((type==1 || type==0)? _x:_x1,(type==1 || type==0)? _y:_y1); + } + + /** + * Set the end point to the point value passed. + **/ + public function set end(value:Point):void{ + if (type==1 || type==0){ + x=value.x; + y=value.y; + } + else{ + x1=value.x; + y1=value.y; + } + } + + /** + * Function to be called during the render loop when + * this item is encountered for use with delegate type. + */ + private var _delegate:Function; + public function get delegate():Function{ + return _delegate; + } + public function set delegate(value:Function):void{ + if(_delegate != value){ + _delegate = value; + invalidated = true; + } + } + + private var _renderDelegateStart:Array; + /** + * Functions to be called during the render loop when + * this item is about to be rendered. + */ + + public function get renderDelegateStart():Array{ + return _renderDelegateStart; + } + public function set renderDelegateStart(value:Array):void{ + if(_renderDelegateStart != value){ + _renderDelegateStart = value; + invalidated = true; + } + } + + private var _renderDelegateEnd:Array; + /** + * Functions to be called during the render loop when + * this item has just been rendered. + */ + + public function get renderDelegateEnd():Array{ + return _renderDelegateEnd; + } + public function set renderDelegateEnd(value:Array):void{ + if(_renderDelegateEnd != value){ + _renderDelegateEnd = value; + invalidated = true; + } + } + + /** + * A nested command stack in the case of a command stack type + **/ + private var _commandStack:CommandStack; + public function get commandStack():CommandStack{ + return _commandStack; + } + public function set commandStack(value:CommandStack):void{ + if(_commandStack != value){ + _commandStack = value; + value.parent = this; + invalidated = true; + } + } + + /** + * x coordinate for a LINE_TO or MOVE_TO + */ + private var _x:Number; + public function get x():Number{ + return _x; + } + public function set x(value:Number):void{ + if(_x != value){ + _x = value; + invalidated = true; + } + } + /** + * y coordinate for a LINE_TO or MOVE_TO + */ + private var _y:Number; + public function get y():Number{ + return _y; + } + public function set y(value:Number):void{ + if(_y != value){ + _y = value; + invalidated = true; + } + } + /** + * x1 anchor point for a CURVE_TO + */ + private var _x1:Number; + public function get x1():Number{ + return _x1; + } + public function set x1(value:Number):void{ + if(_x1 != value){ + _x1 = value; + invalidated = true; + } + } + /** + * y1 anchor point for CURVE_TO + */ + private var _y1:Number; + public function get y1():Number{ + return _y1; + } + public function set y1(value:Number):void{ + if(_y1 != value){ + _y1 = value; + invalidated = true; + } + } + /** + * cx control point for a CURVE_TO + */ + private var _cx:Number; + public function get cx():Number{ + return _cx; + } + public function set cx(value:Number):void{ + if(_cx != value){ + _cx = value; + invalidated = true; + } + } + /** + * cy control point for a CURVE_TO + */ + private var _cy:Number; + public function get cy():Number{ + return _cy; + } + public function set cy(value:Number):void{ + if(_cy != value){ + _cy = value; + invalidated = true; + } + } + + /** + * Returns the length of the this segment + **/ + private var _segmentLength:Number=0; + public function get segmentLength():Number{ + if(!_segmentLength || lengthInvalidated){ + switch(type){ + case CommandStackItem.MOVE_TO: + _segmentLength =0; + break; + case CommandStackItem.LINE_TO: + _segmentLength =lineLength(start,end); + break; + case CommandStackItem.CURVE_TO: + _segmentLength =curveLength(); + break; + case CommandStackItem.COMMAND_STACK: + _segmentLength =commandStack.pathLength; + break; + default: + _segmentLength =0; + break; + } + } + return _segmentLength; + } + + public function get transformedLength():Number { + if (!CommandStack.transMatrix) return segmentLength; + var t:Matrix = CommandStack.transMatrix; + if(!_segmentLength || lengthInvalidated){ + switch(type){ + case CommandStackItem.MOVE_TO: + return 0; + break; + case CommandStackItem.LINE_TO: + return lineLength(t.transformPoint(start),t.transformPoint(end)); + break; + case CommandStackItem.CURVE_TO: + return curveLength(5,t.transformPoint(start),t.transformPoint(control),t.transformPoint(end)); + break; + case CommandStackItem.COMMAND_STACK: + return commandStack.transformedPathLength; + break; + default: + //_segmentLength =0; + break; + } + } + return 0; + } + + + /** + * Returns the point on this segment at t (0-1) + **/ + public function segmentPointAt(t:Number):Point{ + + switch(type){ + case CommandStackItem.MOVE_TO: + return start.clone(); + case CommandStackItem.LINE_TO: + return linePointAt(t,start,end); + case CommandStackItem.CURVE_TO: + return curvePointAt(t); + case CommandStackItem.COMMAND_STACK: + return commandStack.pathPointAt(t); + default: + return null; + } + } + + /** + * Returns the angle of a point on this segment at t (0-1) + **/ + public function segmentAngleAt(t:Number):Number{ + + switch(type){ + case CommandStackItem.MOVE_TO: + return 0; + case CommandStackItem.LINE_TO: + return lineAngleAt(t); + case CommandStackItem.CURVE_TO: + return curveAngleAt(t); + case CommandStackItem.COMMAND_STACK: + return commandStack.pathAngleAt(t); + default: + return 0; + } + + } + + //Based on code from Trevor McCauley, www.senocular.com + /** + * Returns the point on the line at t (0-1) of a line. + **/ + private function linePointAt(t:Number, startPt:Point = null, endPt:Point = null):Point { + if (!startPt) startPt = start; + if (!endPt) endPt = end; + var dx:Number = endPt.x - startPt.x; + var dy:Number = endPt.y - startPt.y; + return new Point(startPt.x + dx*t, startPt.y + dy*t); + } + + /** + * Returns the angle between start and end point. + **/ + private function lineAngleAt(t:Number, startPt:Point = null, endPt:Point = null):Number { + if (!startPt) startPt = start; + if (!endPt) endPt = end; + return Math.atan2(endPt.y - startPt.y, endPt.x - startPt.x); + } + + /** + * Returns the length of a line. + **/ + private function lineLength(startPt:Point = null, endPt:Point = null):Number { + if (!startPt) startPt = start; + if (!endPt) endPt = end; + var dx:Number = endPt.x - startPt.x; + var dy:Number = endPt.y - startPt.y; + return Math.sqrt(dx*dx + dy*dy); + } + + + /** + * Returns the length of a quadratic curve + **/ + private function curveLength(curveAccuracy:int=5,startPt:Point = null, controlPt:Point = null, endPt:Point = null):Number { + + if (!startPt) startPt = start; + if (!controlPt) controlPt = control; + if (!endPt) endPt = end; + + var dx:Number = endPt.x - startPt.x; + var dy:Number = endPt.y - startPt.y; + var cx:Number = (dx == 0) ? 0 : (controlPt.x - startPt.x)/dx; + var cy:Number = (dy == 0) ? 0 : (controlPt.y - startPt.y)/dy; + var f1:Number; + var f2:Number; + var t:Number; + var d:Number = 0; + var p:Point = startPt; + var np:Point; + var i:int; + + for (i=1; i1)? (_percentWidth/100)*container.width:_percentWidth *container.width; + _layoutRectangle.x = isNaN(_x)? 0:_x + container.left; + }else{ + // centered + _layoutRectangle.width = isNaN(_percentWidth) ? _width : (_percentWidth>1)? (_percentWidth/100)*container.width:_percentWidth *container.width; + _layoutRectangle.x = _horizontalCenter - _layoutRectangle.width/2 + container.left + container.width/2; + } + + }else if (!alignedRight) { + // left + _layoutRectangle.width = isNaN(_percentWidth) ? _width : (_percentWidth>1)? (_percentWidth/100)*container.width:_percentWidth *container.width; + _layoutRectangle.x = container.left + _left; + }else if (!alignedLeft) { + // right + _layoutRectangle.width = isNaN(_percentWidth) ? _width : (_percentWidth>1)? (_percentWidth/100)*container.width:_percentWidth *container.width; + _layoutRectangle.x = container.right - _right - _layoutRectangle.width; + }else{ + // right and left (boxed) + _layoutRectangle.right = container.right - _right; + _layoutRectangle.left = container.left + _left; + } + } + + // apply limits + if (!isNaN(_minX)){ + currValue = container.x + _minX; + if (currValue > _layoutRectangle.x) _layoutRectangle.x = currValue; + } + if (!isNaN(_maxX)){ + currValue = container.x + _maxX; + if (currValue < _layoutRectangle.x) _layoutRectangle.x = currValue; + } + + currValue = 0; + if (!isNaN(_minWidth) && _minWidth > _layoutRectangle.width){ + currValue = _layoutRectangle.width - _minWidth; + }else if (!isNaN(_maxWidth) && _maxWidth < _layoutRectangle.width){ + currValue = _layoutRectangle.width - _maxWidth; + } + + if (currValue){ // if change in width, adjust position + if (!alignedLeft) { + if (alignedRight) { // right + _layoutRectangle.x += currValue; + }else if (!noHorizontalCenter) { // centered + _layoutRectangle.x += currValue/2; + } + }else if (alignedLeft && alignedRight) { // boxed + _layoutRectangle.x += currValue/2; + } + // fit width + _layoutRectangle.width -= currValue; + } + + // vertical placement + var noTop:Boolean = isNaN(_top); + var noBottom:Boolean = isNaN(_bottom); + var noVerticalCenter:Boolean = isNaN(_verticalCenter); + var alignedTop:Boolean = !Boolean(noTop); + var alignedBottom:Boolean = !Boolean(noBottom); + + if (container){ + if (!alignedTop && !alignedBottom) { + + if (noVerticalCenter) { + // normal + _layoutRectangle.height = isNaN(_percentHeight) ? _height : (_percentHeight>1)? (_percentHeight/100)*container.height:_percentHeight *container.height; + _layoutRectangle.y = isNaN(_y)? 0:_y + container.top; + + }else{ + // centered + _layoutRectangle.height = isNaN(_percentHeight) ? _height : (_percentHeight>1)? (_percentHeight/100)*container.height:_percentHeight *container.height; + _layoutRectangle.y = _verticalCenter - _layoutRectangle.height/2 + container.top + container.height/2; + } + + }else if (!alignedBottom) { + // top + _layoutRectangle.height = isNaN(_percentHeight) ? _height : (_percentHeight>1)? (_percentHeight/100)*container.height:_percentHeight *container.height; + _layoutRectangle.y = container.top + _top; + + }else if (!alignedTop) { + // bottom + _layoutRectangle.height = isNaN(_percentHeight) ? _height : (_percentHeight>1)? (_percentHeight/100)*container.height:_percentHeight *container.height; + _layoutRectangle.y = container.bottom - _bottom - _layoutRectangle.height; + + }else{ + // top and bottom (boxed) + _layoutRectangle.bottom = container.bottom - _bottom; + _layoutRectangle.top = container.top + _top; + } + } + + // apply limits + if (!isNaN(_minY)){ + currValue = container.y + _minY; + if (currValue > _layoutRectangle.y) _layoutRectangle.y = currValue; + } + if (!isNaN(_maxY)){ + currValue = container.y + _maxY; + if (currValue < _layoutRectangle.y) _layoutRectangle.y = currValue; + } + + currValue = 0; + if (!isNaN(_minHeight) && _minHeight > _layoutRectangle.height){ + currValue = _layoutRectangle.height - _minHeight; + }else if (!isNaN(_maxHeight) && _maxHeight < _layoutRectangle.height){ + currValue = _layoutRectangle.height - _maxHeight; + } + + if (currValue){ // if change in height, adjust position + if (!alignedTop) { + if (alignedBottom) { // bottom + _layoutRectangle.y += currValue; + }else if (!noVerticalCenter) { // centered + _layoutRectangle.y += currValue/2; + } + }else if (alignedTop && alignedBottom) { // boxed + _layoutRectangle.y += currValue/2; + } + // fit height + _layoutRectangle.height -= currValue; + } + + + + // maintaining aspect if applicable; use width and height for aspect + // only apply if one dimension is static and the other dynamic + // maintaining aspect has highest priority so it is evaluated last + if (_maintainAspectRatio && _height && _width) { + + var sizeRatio:Number = _height/_width; + var rectRatio:Number = _layoutRectangle.height/_layoutRectangle.width; + + if (sizeRatio > rectRatio) { + // width + currValue = _layoutRectangle.height/sizeRatio; + + if (!alignedLeft) { + if (alignedRight) { + // right + _layoutRectangle.x += _layoutRectangle.width - currValue; + }else if (!(noHorizontalCenter)) { + // centered + _layoutRectangle.x += (_layoutRectangle.width - currValue)/2; + } + }else if (alignedLeft && alignedRight) { + // boxed + _layoutRectangle.x += (_layoutRectangle.width - currValue)/2; + } + _layoutRectangle.width = currValue; + + }else if (sizeRatio < rectRatio) { + // height + currValue = _layoutRectangle.width * sizeRatio; + + if (!alignedTop) { + if (alignedBottom) { + // bottom + _layoutRectangle.y += _layoutRectangle.height - currValue; + }else if (!(noVerticalCenter)) { + // centered + _layoutRectangle.y += (_layoutRectangle.height - currValue)/2; + } + }else if (alignedTop && alignedBottom) { + // boxed + _layoutRectangle.y += (_layoutRectangle.height - currValue)/2; + } + _layoutRectangle.height = currValue; + } + } + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/layout/LayoutConstraint.png b/Degrafa/com/degrafa/geometry/layout/LayoutConstraint.png new file mode 100644 index 0000000..c081253 Binary files /dev/null and b/Degrafa/com/degrafa/geometry/layout/LayoutConstraint.png differ diff --git a/Degrafa/com/degrafa/geometry/layout/LayoutUtils.as b/Degrafa/com/degrafa/geometry/layout/LayoutUtils.as new file mode 100644 index 0000000..c52bcec --- /dev/null +++ b/Degrafa/com/degrafa/geometry/layout/LayoutUtils.as @@ -0,0 +1,195 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.layout{ + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.geometry.command.CommandStackItem; + import com.degrafa.geometry.utilities.GeometryUtils; + + import flash.geom.Point; + import flash.geom.Rectangle; + + /** + * LayoutUtils is an all staic helper class for layout related tasks. + **/ + public class LayoutUtils{ + + public function LayoutUtils(){} + + private static var width:Number; + private static var height:Number; + private static var x:Number; + private static var y:Number; + + /** + * Proportionally sizes each point in the command array to the given width and height + * taking into account any additional x or y offset that the command data may have. + * This ensures that rendering is always started at point(0,0) and that the maximum + * allotted spaced is used for both width and height. + **/ + public static function calculateRatios(commandStack:CommandStack,destinationRectangle:Rectangle):void{ + + + x = destinationRectangle.x; + y = destinationRectangle.y; + width = destinationRectangle.width; + height = destinationRectangle.height; + + + var minPoint:Point = new Point(Number.POSITIVE_INFINITY,Number.POSITIVE_INFINITY); + var maxPoint:Point = new Point(0,0); + + var lastX:Number=0; + var lastY:Number=0; + + getCommandStackMinMax(commandStack,maxPoint,minPoint,lastX,lastY); + + //apply the offset + applyOffsetToCommandStack(commandStack, + width/(maxPoint.x-minPoint.x), + height/(maxPoint.y-minPoint.y), + minPoint); + + } + + /** + * Traverses the given command stack and calculates the min and max points. + **/ + public static function getCommandStackMinMax(commandStack:CommandStack,maxPoint:Point,minPoint:Point,lastX:Number,lastY:Number):void{ + + var bezierRect:Rectangle; + + var item:CommandStackItem; + + for each (item in commandStack.source){ + switch(item.type){ + case CommandStackItem.MOVE_TO: + case CommandStackItem.LINE_TO: + maxPoint.x =Math.max(maxPoint.x,item.x); + maxPoint.y =Math.max(maxPoint.y,item.y); + + minPoint.x =Math.min(minPoint.x,item.x); + minPoint.y =Math.min(minPoint.y,item.y); + + //store for next iteration + lastX=item.x; + lastY=item.y; + break; + case CommandStackItem.CURVE_TO: + + bezierRect = GeometryUtils.bezierBounds(lastX,lastY, + item.cx,item.cy,item.x1,item.y1); + + //now take our bounds into account + maxPoint.x =Math.max(maxPoint.x,bezierRect.x); + maxPoint.y =Math.max(maxPoint.y,bezierRect.y); + + maxPoint.x =Math.max(maxPoint.x,bezierRect.x+bezierRect.width); + maxPoint.y =Math.max(maxPoint.y,bezierRect.y+bezierRect.height); + + minPoint.x =Math.min(minPoint.x,bezierRect.x); + minPoint.y =Math.min(minPoint.y,bezierRect.y); + + minPoint.x =Math.min(minPoint.x,bezierRect.x+bezierRect.width); + minPoint.y =Math.min(minPoint.y,bezierRect.y+bezierRect.height); + + //store for next iteration + lastX=item.x1; + lastY=item.y1; + break; + + case CommandStackItem.COMMAND_STACK: + //recurse + getCommandStackMinMax(item.commandStack,maxPoint,minPoint,lastX,lastY); + break; + } + } + + } + + + /** + * Traverses through the given command stack applying the offset. + **/ + public static function applyOffsetToCommandStack(commandStack:CommandStack,xMultiplier:Number,yMultiplier:Number,minPoint:Point,lastPoint:Point=null):void{ + + var item:CommandStackItem; + + //keep last point for recursion and setting the origin + if(!lastPoint){ + lastPoint=minPoint.clone(); + } + + //multiply the axis by the difference + for each (item in commandStack.source){ + switch(item.type){ + case CommandStackItem.MOVE_TO: + case CommandStackItem.LINE_TO: + if(item.x!=0){ + item.x = (item.x-minPoint.x) * xMultiplier; + } + if(item.y!=0){ + item.y = (item.y-minPoint.y) * yMultiplier; + } + + //offset according to x and y + item.x += x; + item.y += y; + + lastPoint.x=item.x; + lastPoint.y=item.y; + + break; + case CommandStackItem.CURVE_TO: + if(item.cx!=0){ + item.cx = (item.cx-minPoint.x) * xMultiplier; + } + if(item.cy!=0){ + item.cy = (item.cy-minPoint.y) * yMultiplier; + } + if(item.x1!=0){ + item.x1 = (item.x1-minPoint.x) * xMultiplier; + } + + if(item.y1!=0){ + item.y1 = (item.y1-minPoint.y) * yMultiplier; + } + + //offset according to x and y + item.cx += x; + item.cy += y; + item.x1 += x; + item.y1 += y; + + lastPoint.x=item.x1; + lastPoint.y=item.y1; + + break; + case CommandStackItem.COMMAND_STACK: + //recurse + applyOffsetToCommandStack(item.commandStack,xMultiplier,yMultiplier,minPoint,lastPoint); + break; + } + + } + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/segment/ClosePath.as b/Degrafa/com/degrafa/geometry/segment/ClosePath.as new file mode 100644 index 0000000..338aed7 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/segment/ClosePath.as @@ -0,0 +1,133 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.segment{ + + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.geometry.command.CommandStackItem; + + import flash.geom.Point; + import flash.geom.Rectangle; + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("ClosePath.png")] + + //close path to the last used move + //(Z or z) path data command + [Exclude(name="data", kind="property")] + [Exclude(name="coordinateType", kind="property")] + [Exclude(name="isShortSequence", kind="property")] + /** + * The "closepath" (Z or z) ends the current subpath + * by drawing a straight line from the current point to + * current subpath's initial point. + * + * @see http://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand + * + **/ + public class ClosePath extends Segment implements ISegment{ + public function ClosePath(){ + + super(); + invalidated = true; + } + + /** + * Setting data on ClosePath has no effect + **/ + override public function set data(value:String):void{} + override public function get data():String{return null;} + + /** + * Setting coordinateType on ClosePath has no effect + **/ + override public function set coordinateType(value:String):void{} + override public function get coordinateType():String{return null;} + + /** + * Setting isShortSequence on ClosePath has no effect + **/ + override public function get isShortSequence():Boolean{return false;}; + override public function set isShortSequence(value:Boolean):void{}; + + /** + * Return the segment type + **/ + override public function get segmentType():String{ + return "ClosePath"; + } + + private var _bounds:Rectangle; + /** + * The tight bounds of this segment as represented by a Rectangle object. + **/ + public function get bounds():Rectangle{ + return commandStackItem.bounds; + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + invalidated = false; + } + + private var lastPoint:Point=new Point(NaN,NaN); + private var firstPoint:Point=new Point(NaN,NaN); + + /** + * Compute the segment adding instructions to the command stack. + **/ + public function computeSegment(firstPoint:Point,lastPoint:Point,lastControlPoint:Point,commandStack:CommandStack):void{ + + if (!invalidated ) + { + invalidated= (!lastPoint.equals(this.lastPoint) || !firstPoint.equals(this.firstPoint) ) + } + + if (invalidated){ + if(!_commandStackItem){ + _commandStackItem = new CommandStackItem(CommandStackItem.LINE_TO,firstPoint.x,firstPoint.y); + commandStack.addItem(_commandStackItem); + } + else{ + _commandStackItem.x = firstPoint.x; + _commandStackItem.y = firstPoint.y; + } + //update this segment's point references + this.lastPoint.x = lastPoint.x; + this.lastPoint.y = lastPoint.y; + this.firstPoint.x = firstPoint.x; + this.firstPoint.y = firstPoint.y; + } + //update the buildFlashCommandStack Point tracking reference + lastPoint.x = _commandStackItem.x; + lastPoint.y = _commandStackItem.y; + + //pre calculate the bounds for this segment + preDraw(); + + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/segment/ClosePath.png b/Degrafa/com/degrafa/geometry/segment/ClosePath.png new file mode 100644 index 0000000..5108e1c Binary files /dev/null and b/Degrafa/com/degrafa/geometry/segment/ClosePath.png differ diff --git a/Degrafa/com/degrafa/geometry/segment/CubicBezierTo.as b/Degrafa/com/degrafa/geometry/segment/CubicBezierTo.as new file mode 100644 index 0000000..7010a88 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/segment/CubicBezierTo.as @@ -0,0 +1,312 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.segment{ + + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.geometry.command.CommandStackItem; + import com.degrafa.geometry.utilities.GeometryUtils; + + import flash.geom.Point; + import flash.geom.Rectangle; + + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("CubicBezierTo.png")] + + //(C,c,S,s) path data commands + [Bindable] + /** + * A cubic Bézier (C,c S,s) segment is defined by a end point and two control points. + * + * @see http://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands + * + **/ + public class CubicBezierTo extends Segment implements ISegment{ + /** + * Constructor. + * + *

    The CubicBezierTo constructor accepts 9 optional arguments that define it's + * data, properties, coordinate type and a flag that specifies a short sequence.

    + * + * @param cx A number indicating the x-coordinate of the first control point of the curve. + * @param cy A number indicating the y-coordinate of the first control point of the curve. + * @param cx1 A number indicating the x-coordinate of the second control point of the curve. + * @param cy1 A number indicating the y-coordinate of the second control point of the curve. + * @param x A number indicating the x-coordinate of the end point of the curve. + * @param y A number indicating the y-coordinate of the end point of the curve. + * @param data A string indicating the data to be used for this segment. + * @param coordinateType A string indicating the coordinate type (absolute or relative) to be used for this segment. + * @param isShortSequence A boolean indicating the if this segment is a short segment definition. + **/ + public function CubicBezierTo(cx:Number=0,cy:Number=0,cx1:Number=0,cy1:Number=0,x:Number=0,y:Number=0, + data:String=null,coordinateType:String="absolute",isShortSequence:Boolean=false){ + + _cx = cx; + _cy = cy; + _cx1 = cx1; + _cy1 = cy1; + _x = x; + _y = y; + if (data) this.data = data; + _absCoordType= (coordinateType == "absolute"); + if (isShortSequence) _isShortSequence = true; + invalidated = true; + + } + + /** + * Return the segment type + **/ + override public function get segmentType():String{ + return "CubicBezierTo"; + } + + + /** + * CubicBezierTo short hand data value. + * + *

    The cubic Bézier data property expects exactly 6 values + * cx, cy, cx1, cy1, x and y separated by spaces.

    + * + * @see Segment#data + * + **/ + override public function set data(value:String):void{ + if(super.data != value){ + super.data = value; + + //parse the string on the space + var tempArray:Array = value.split(" "); + + if (tempArray.length == 6){ + _cx=tempArray[0]; + _cy=tempArray[1]; + _cx1=tempArray[2]; + _cy1=tempArray[3]; + _x=tempArray[4]; + _y=tempArray[5]; + } + invalidated = true; + } + } + + + private var _x:Number=0; + /** + * The x-coordinate of the end point of the curve. If not specified + * a default value of 0 is used. + **/ + public function get x():Number{ + return _x; + } + public function set x(value:Number):void{ + if(_x != value){ + _x = value; + invalidated = true; + } + } + + + private var _y:Number=0; + /** + * The y-coordinate of the end point of the curve. If not specified + * a default value of 0 is used. + **/ + public function get y():Number{ + return _y; + } + public function set y(value:Number):void{ + if(_y != value){ + _y = value; + invalidated = true; + } + } + + + + private var _cx:Number=0; + /** + * The x-coordinate of the first control point of the curve. If not specified + * a default value of 0 is used. + **/ + public function get cx():Number{ + return _cx; + } + public function set cx(value:Number):void{ + if(_cx != value){ + _cx = value; + invalidated = true; + } + } + + + private var _cy:Number=0; + /** + * The y-coordinate of the first control point of the curve. If not specified + * a default value of 0 is used. + **/ + public function get cy():Number{ + return _cy; + } + public function set cy(value:Number):void{ + if(_cy != value){ + _cy = value; + invalidated = true; + } + } + + + private var _cx1:Number=0; + /** + * The x-coordinate of the second control point of the curve. If not specified + * a default value of 0 is used. + **/ + public function get cx1():Number{ + return _cx1; + } + public function set cx1(value:Number):void{ + if(_cx1 != value){ + _cx1 = value; + invalidated = true; + } + } + + + private var _cy1:Number=0; + /** + * The y-coordinate of the second control point of the curve. If not specified + * a default value of 0 is used. + **/ + public function get cy1():Number{ + return _cy1; + } + public function set cy1(value:Number):void{ + if(_cy1 != value){ + _cy1 = value; + invalidated = true; + } + } + + private var _bounds:Rectangle; + /** + * The tight bounds of this segment as represented by a Rectangle object. + **/ + public function get bounds():Rectangle{ + return commandStackItem.bounds; + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + invalidated = false; + } + + private var lastPoint:Point=new Point(NaN,NaN); + private var lastControlPoint:Point=new Point(NaN,NaN); + + /** + * Compute the segment adding instructions to the command stack. + **/ + public function computeSegment(firstPoint:Point,lastPoint:Point,lastControlPoint:Point,commandStack:CommandStack):void{ + + if (!invalidated ) + { + invalidated= (!lastPoint.equals(this.lastPoint) || !lastControlPoint.equals(this.lastControlPoint)) + } + + //some early references to the updated last tracking point coords in case we exit early + var nlcpx:Number; + var nlcpy:Number; + var nlpx:Number; + var nlpy:Number; + if (_absCoordType) + { + nlcpx = _cx1; nlcpy = _cy1; nlpx = _x; nlpy = _y; + + } else { + nlcpx = lastPoint.x + _cx1; + nlcpy = lastPoint.y + _cy1; + nlpx = lastPoint.x + _x; + nlpy = lastPoint.y + _y; + } + + //test if anything has changed and only recalculate if something has + if(invalidated){ + + var isNewItem:Boolean; + + //add for the first run + if(!_commandStackItem){ + _commandStackItem = new CommandStackItem(CommandStackItem.COMMAND_STACK, + NaN,NaN,NaN,NaN,NaN,NaN,new CommandStack()); + + isNewItem =true; + } + + //clear the array in this case as it's a complex item + _commandStackItem.commandStack.length=0; + + if(_isShortSequence){ + _commandStackItem.commandStack.addCubicBezierTo(lastPoint.x,lastPoint.y, + lastPoint.x+(lastPoint.x-lastControlPoint.x),lastPoint.y+(lastPoint.y-lastControlPoint.y), + nlcpx,nlcpy,nlpx,nlpy,1); + } + else{ + _commandStackItem.commandStack.addCubicBezierTo(lastPoint.x,lastPoint.y, + _absCoordType? _cx:lastPoint.x+_cx,_absCoordType ? _cy : lastPoint.y+_cy, + nlcpx,nlcpy,nlpx,nlpy,1); + } + //not sure about this but it seems the best way temporarily + _commandStackItem.end.x = nlpx; + _commandStackItem.end.y = nlpy; + + //update the stack being built + if(isNewItem){ + commandStack.addItem(_commandStackItem); + } + + //update this segment's point references + this.lastPoint.x = lastPoint.x; + this.lastPoint.y = lastPoint.y; + this.lastControlPoint.x = lastControlPoint.x; + this.lastControlPoint.y = lastControlPoint.y; + } + + //update the buildFlashCommandStack Point tracking reference + lastPoint.x = nlpx; + lastPoint.y = nlpy; + lastControlPoint.x = nlcpx; + lastControlPoint.y = nlcpy; + + //pre calculate the bounds for this segment + //preDraw(); + invalidated = false; + + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/segment/CubicBezierTo.png b/Degrafa/com/degrafa/geometry/segment/CubicBezierTo.png new file mode 100644 index 0000000..671f6ce Binary files /dev/null and b/Degrafa/com/degrafa/geometry/segment/CubicBezierTo.png differ diff --git a/Degrafa/com/degrafa/geometry/segment/EllipticalArcTo.as b/Degrafa/com/degrafa/geometry/segment/EllipticalArcTo.as new file mode 100644 index 0000000..ac21b08 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/segment/EllipticalArcTo.as @@ -0,0 +1,341 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.segment{ + + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.geometry.command.CommandStackItem; + import com.degrafa.geometry.utilities.ArcUtils; + import com.degrafa.geometry.utilities.GeometryUtils; + + import flash.geom.Point; + import flash.geom.Rectangle; + + + [Exclude(name="isShortSequence", kind="property")] + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("EllipticalArcTo.png")] + + //(A or a) path data command + [Bindable] + + /** + * Defines an elliptical arc (A,a) segment from the current point. + * + * @see http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands + **/ + public class EllipticalArcTo extends Segment implements ISegment{ + + /** + * Constructor. + * + *

    The EllipticalArcTo constructor accepts 9 optional arguments that define it's + * data, properties and a coordinate type.

    + + * @param rx A number indicating the x-coordinate radius of the arc. + * @param ry A number indicating the y-coordinate radius of the arc.. + * @param xAxisRotation A number indicating the x axis rotation of the arc. + * @param largeArcFlag A number indicating if the arc is to use a large arc. + * @param sweepFlag A number indicating if the arc is to use a sweep. + * @param x A number indicating the x-coordinate of the end point of the arc. + * @param y A number indicating the y-coordinate of the end point of the arc. + * @param data A string indicating the data to be used for this segment. + * @param coordinateType A string indicating the coordinate type to be used for this segment. + **/ + public function EllipticalArcTo(rx:Number=0,ry:Number=0,xAxisRotation:Number=0, + largeArcFlag:uint=0,sweepFlag:uint=0,x:Number=0,y:Number=0,data:String=null, + coordinateType:String="absolute"){ + + _rx = rx; + _ry = ry; + _xAxisRotation = xAxisRotation; + _largeArcFlag = Boolean(largeArcFlag); + _sweepFlag = Boolean(sweepFlag); + _x = x; + _y = y; + if (data) this.data = data; + _absCoordType = (coordinateType == "absolute"); + invalidated = true; + } + + /** + * The isShortSequence property is ingnored on the EllipticalArcTo segment and + * setting it will have no effect. + **/ + override public function get isShortSequence():Boolean{return false;} + override public function set isShortSequence(value:Boolean):void{} + + /** + * Return the segment type + **/ + override public function get segmentType():String{ + return "EllipticalArcTo"; + } + + /** + * EllipticalArcTo short hand data value. + * + *

    The elliptical arc to data property expects exactly 7 values + * rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x and y separated + * by spaces.

    + * + * @see Segment#data + * + **/ + override public function set data(value:String):void{ + if(super.data != value){ + super.data = value; + + //parse the string on the space + var tempArray:Array = value.split(" "); + + if (tempArray.length == 7) + { + _rx=tempArray[0]; //ry + _ry=tempArray[1]; //rx + _xAxisRotation=tempArray[2]; //x-axis-rotation + _largeArcFlag=tempArray[3]; //largeArcFlag + _sweepFlag=tempArray[4]; //sweepFlag + _x=tempArray[5]; //x end point + _y=tempArray[6]; //y end point + + } + invalidated = true; + } + } + + private var _rx:Number=0; + /** + * The x-coordinate radius of the arc. If not specified + * a default value of 0 is used. + **/ + public function get rx():Number{ + return _rx; + } + public function set rx(value:Number):void{ + if(_rx != value){ + _rx = value; + invalidated = true; + } + } + + + private var _ry:Number=0; + /** + * The y-coordinate radius of the arc. If not specified + * a default value of 0 is used. + **/ + public function get ry():Number{ + return _ry; + } + public function set ry(value:Number):void{ + if(_ry != value){ + _ry = value; + invalidated = true; + } + + } + + + private var _xAxisRotation:Number=0; + /** + * The x axis rotation of the arc. If not specified + * a default value of 0 is used. + **/ + public function get xAxisRotation():Number{ + return _xAxisRotation; + } + public function set xAxisRotation(value:Number):void{ + if(_xAxisRotation != value){ + _xAxisRotation = value; + invalidated = true; + } + + } + + + + private var _largeArcFlag:Boolean=true; + /** + * A value indicating if the arc is to use a large arc. + * A value of 0 = true and a value of 1 = false + * + * see@ http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands + **/ + public function get largeArcFlag():uint{ + return uint(_largeArcFlag); + } + public function set largeArcFlag(value:uint):void { + if(_largeArcFlag != Boolean(value)){ + _largeArcFlag = Boolean(value); + invalidated = true; + } + } + + + private var _sweepFlag:Boolean=true; + /** + * A value indicating if the arc is to use a sweep. + * A value of 0 = true and a value of 1 = false + * + * see@ http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands + **/ + public function get sweepFlag():uint{ + return uint(_sweepFlag); + } + public function set sweepFlag(value:uint):void { + if(_sweepFlag != Boolean(value)){ + _sweepFlag = Boolean(value); + invalidated = true; + } + } + + private var _x:Number=0; + /** + * The x-coordinate of the end point of the arc. If not specified + * a default value of 0 is used. + **/ + public function get x():Number{ + return _x; + } + public function set x(value:Number):void{ + if(_x != value){ + _x = value; + invalidated = true; + } + } + + + private var _y:Number=0; + /** + * The y-coordinate of the end point of the arc. If not specified + * a default value of 0 is used. + **/ + public function get y():Number{ + return _y; + } + public function set y(value:Number):void{ + if(_y != value){ + _y = value; + invalidated = true; + } + } + + + private var _bounds:Rectangle; + /** + * The tight bounds of this segment as represented by a Rectangle object. + **/ + public function get bounds():Rectangle{ + return commandStackItem.bounds; + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + invalidated = false; + } + + private var lastPoint:Point=new Point(NaN,NaN); + + /** + * Compute the segment adding instructions to the command stack. + **/ + public function computeSegment(firstPoint:Point,lastPoint:Point,lastControlPoint:Point,commandStack:CommandStack):void{ + + + if (!invalidated ) + { + invalidated= (!lastPoint.equals(this.lastPoint) ) + } + //precalc new last point tracking values + var nlpx:Number = _absCoordType? x :lastPoint.x + _x; + var nlpy:Number = _absCoordType? y : lastPoint.y + _y ; + + //edge case: if lastpoint and endpoint are the same, then ignore this arc segment (SVG implementation notes: F.6.2 Out-of-range parameters) + if (nlpx == lastPoint.x && nlpy == lastPoint.y) + { + _bounds = new Rectangle(nlpx, nlpy, 0, 0); + //or maybe: + //_bounds = new Rectangle(nlpx, nlpy, 0.0001, 0.0001); + invalidated = false; + return; + } + + //test if anything has changed and only recalculate if something has + if(invalidated){ + + var isNewItem:Boolean; + + //edge case: if either of the radii are zero, it's a straight line (SVG implementation notes) + if (_rx == 0 || _ry == 0) { + if (!_commandStackItem || _commandStackItem.type != CommandStackItem.LINE_TO) + { + _commandStackItem = new CommandStackItem(CommandStackItem.LINE_TO, nlpx, nlpy); + } else { + _commandStackItem.x = nlpx; + _commandStackItem.y = nlpy; + } + } else { + + if(!_commandStackItem){ + _commandStackItem = new CommandStackItem(CommandStackItem.COMMAND_STACK, + NaN,NaN,NaN,NaN,NaN,NaN,new CommandStack()); + + isNewItem = true; + } + + var computedArc:Object = ArcUtils.computeSvgArc(_rx,_ry,xAxisRotation,Boolean(_largeArcFlag), + Boolean(_sweepFlag), nlpx, nlpy , lastPoint.x, lastPoint.y); + + ArcUtils.drawArc(computedArc.x,computedArc.y,computedArc.startAngle, + computedArc.arc,computedArc.radius,computedArc.yRadius, + computedArc.xAxisRotation,_commandStackItem.commandStack); + } + //add the move to at the start of this stack + //_commandStackItem.commandStack.source.unshift(new CommandStackItem(CommandStackItem.MOVE_TO,computedArc.x,computedArc.y)); + + //update the stack being built + if(isNewItem){ + commandStack.addItem(_commandStackItem); + } + + //update this segment's point references + this.lastPoint.x = lastPoint.x; + this.lastPoint.y = lastPoint.y; + } + + //update the buildFlashCommandStack Point tracking reference + lastPoint.x = nlpx; + lastPoint.y = nlpy; + + //pre calculate the bounds for this segment + preDraw(); + + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/segment/EllipticalArcTo.png b/Degrafa/com/degrafa/geometry/segment/EllipticalArcTo.png new file mode 100644 index 0000000..ea465ce Binary files /dev/null and b/Degrafa/com/degrafa/geometry/segment/EllipticalArcTo.png differ diff --git a/Degrafa/com/degrafa/geometry/segment/HorizontalLineTo.as b/Degrafa/com/degrafa/geometry/segment/HorizontalLineTo.as new file mode 100644 index 0000000..6b01967 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/segment/HorizontalLineTo.as @@ -0,0 +1,164 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.segment{ + + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.geometry.command.CommandStackItem; + + import flash.geom.Point; + import flash.geom.Rectangle; + + + [Exclude(name="isShortSequence", kind="property")] + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("HorizontalLineTo.png")] + + //(H or h) path data command + [Bindable] + /** + * A horizontal line (H,h) segment is defined by a ending x-axis point. + * + * @see http://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands + * + **/ + public class HorizontalLineTo extends Segment implements ISegment{ + + /** + * Constructor. + * + *

    The HorizontalLineTo constructor accepts 3 optional arguments that define it's + * data, properties and a coordinate type.

    + * + * @param x A number indicating the x-coordinate of the end point of the line. + * @param data A string indicating the data to be used for this segment. + * @param coordinateType A string indicating the coordinate type to be used for this segment. + **/ + public function HorizontalLineTo(x:Number=0,data:String=null,coordinateType:String="absolute"){ + _x = x; + if (data) this.data = data; + if (coordinateType != "absolute") _absCoordType = false; + invalidated = true; + } + + /** + * The isShortSequence property is ingnored on the HorizontalLineTo segment and + * setting it will have no effect. + **/ + override public function get isShortSequence():Boolean{return false;} + override public function set isShortSequence(value:Boolean):void{} + + /** + * Return the segment type + **/ + override public function get segmentType():String{ + return "HorizontalLineTo"; + } + + /** + * HorizontalLineTo short hand data value. + * + *

    The horizontal line to data property expects exactly 1 value + * x.

    + * + * @see Segment#data + * + **/ + override public function set data(value:String):void{ + if(super.data != value){ + super.data = value; + _x=Number(value); + invalidated = true; + } + } + + private var _x:Number=0; + /** + * The x-coordinate of the end point of the line. If not specified + * a default value of 0 is used. + **/ + public function get x():Number{ + return _x; + } + public function set x(value:Number):void{ + if(_x != value){ + _x = value; + invalidated = true; + } + } + + private var _bounds:Rectangle; + /** + * The tight bounds of this segment as represented by a Rectangle object. + **/ + public function get bounds():Rectangle{ + return commandStackItem.bounds; + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + invalidated = false; + } + + private var lastPoint:Point=new Point(NaN,NaN); + + /** + * Compute the segment adding instructions to the command stack. + **/ + public function computeSegment(firstPoint:Point,lastPoint:Point,lastControlPoint:Point,commandStack:CommandStack):void{ + + if(!invalidated) { + invalidated= (lastPoint.y!=this.lastPoint.y || (!this._absCoordType && lastPoint.x!=this.lastPoint.x) ) + } + + if(invalidated){ + if(!_commandStackItem){ + _commandStackItem = new CommandStackItem(CommandStackItem.LINE_TO,_absCoordType? _x:lastPoint.x + _x,lastPoint.y); + commandStack.addItem(_commandStackItem); + } + else{ + _commandStackItem.x = _absCoordType? _x:lastPoint.x + _x; + _commandStackItem.y = lastPoint.y; + } + //update this segment's point tracking reference + this.lastPoint.x = lastPoint.x; + this.lastPoint.y = lastPoint.y; + + } + + //update the buildFlashCommandStack Point tracking reference + lastPoint.x = _commandStackItem.x; + + + //pre calculate the bounds for this segment + preDraw(); + + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/segment/HorizontalLineTo.png b/Degrafa/com/degrafa/geometry/segment/HorizontalLineTo.png new file mode 100644 index 0000000..e7e6ced Binary files /dev/null and b/Degrafa/com/degrafa/geometry/segment/HorizontalLineTo.png differ diff --git a/Degrafa/com/degrafa/geometry/segment/ISegment.as b/Degrafa/com/degrafa/geometry/segment/ISegment.as new file mode 100644 index 0000000..67a03ce --- /dev/null +++ b/Degrafa/com/degrafa/geometry/segment/ISegment.as @@ -0,0 +1,51 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.segment{ + + import com.degrafa.core.IDegrafaObject; + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.geometry.command.CommandStackItem; + import flash.geom.Point; + + /** + * Base interface for all Degrafa segment objects. + **/ + public interface ISegment extends IDegrafaObject{ + + function get data():String; + function set data(value:String):void; + + function get segmentType():String; + + function get isShortSequence():Boolean; + function set isShortSequence(value:Boolean):void; + + function get coordinateType():String; + function set coordinateType(value:String):void; + + function get commandStackItem():CommandStackItem + function set commandStackItem(value:CommandStackItem):void + + function computeSegment(firstPoint:Point,lastPoint:Point,lastControlPoint:Point,commandStack:CommandStack):void + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/segment/LineTo.as b/Degrafa/com/degrafa/geometry/segment/LineTo.as new file mode 100644 index 0000000..2a5a488 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/segment/LineTo.as @@ -0,0 +1,201 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.segment{ + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.geometry.command.CommandStackItem; + + import flash.geom.Point; + import flash.geom.Rectangle; + + + [Exclude(name="isShortSequence", kind="property")] + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("LineTo.png")] + + //(L or l) path data command + [Bindable] + /** + * A line (L,l) segment is defined by a ending x-axis and y-axis point. + * + * @see http://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands + * + **/ + public class LineTo extends Segment implements ISegment{ + + /** + * Constructor. + * + *

    The LineTo constructor accepts 4 optional arguments that define it's + * data, properties and a coordinate type.

    + + * @param x A number indicating the x-coordinate of the end point of the line. + * @param y A number indicating the y-coordinate of the end point of the line. + * @param data A string indicating the data to be used for this segment. + * @param coordinateType A string indicating the coordinate type to be used for this segment. + **/ + public function LineTo(x:Number=0,y:Number=0,data:String=null,coordinateType:String="absolute"){ + + _x = x; + _y = y; + if (data) this.data = data; + _absCoordType= (coordinateType == "absolute"); + invalidated = true; + + } + + /** + * The isShortSequence property is ingnored on the LineTo segment and + * setting it will have no effect. + **/ + override public function get isShortSequence():Boolean{return false;} + override public function set isShortSequence(value:Boolean):void{} + + /** + * Return the segment type + **/ + override public function get segmentType():String{ + return "LineTo"; + } + + /** + * LineTo short hand data value. + * + *

    The line to data property expects exactly 2 values + * x and y.

    + * + * @see Segment#data + * + **/ + override public function set data(value:String):void{ + + if(super.data != value){ + super.data = value; + + //parse the string on the space + var tempArray:Array = value.split(" "); + + if (tempArray.length == 2) + { + _x=tempArray[0]; + _y=tempArray[1]; + } + + invalidated = true; + } + } + + private var _x:Number=0; + /** + * The x-coordinate of the end point of the line. If not specified + * a default value of 0 is used. + **/ + public function get x():Number{ + return _x; + } + public function set x(value:Number):void{ + if(_x != value){ + _x = value; + invalidated = true; + } + + } + + + private var _y:Number=0; + /** + * The y-coordinate of the end point of the line. If not specified + * a default value of 0 is used. + **/ + public function get y():Number{ + return _y; + } + public function set y(value:Number):void{ + if(_y != value){ + _y = value; + invalidated = true; + } + } + + private var _bounds:Rectangle; + /** + * The tight bounds of this segment as represented by a Rectangle object. + **/ + public function get bounds():Rectangle{ + return commandStackItem.bounds; + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + invalidated = false; + } + + public var lastPoint:Point=new Point(NaN,NaN); + + /** + * Compute the segment adding instructions to the command stack. + **/ + public function computeSegment(firstPoint:Point,lastPoint:Point,lastControlPoint:Point,commandStack:CommandStack):void{ + + if (!invalidated ) + { + invalidated= (!lastPoint.equals(this.lastPoint) ) + } + + if(invalidated){ + + if(!_commandStackItem){ + _commandStackItem = new CommandStackItem(CommandStackItem.LINE_TO,_absCoordType? _x: lastPoint.x+_x,_absCoordType? _y: lastPoint.y+_y); + commandStack.addItem(_commandStackItem); + } + else + { + if (_absCoordType) + { + _commandStackItem.x = _x; + _commandStackItem.y = _y + } else { + _commandStackItem.x = lastPoint.x + _x; + _commandStackItem.y = lastPoint.y + _y; + } + } + + //update this segment's point references + this.lastPoint.x = lastPoint.x; + this.lastPoint.y = lastPoint.y; + } + + //update the buildFlashCommandStack Point tracking reference + lastPoint.x = _commandStackItem.x; + lastPoint.y = _commandStackItem.y; + + //pre calculate the bounds for this segment + preDraw(); + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/segment/LineTo.png b/Degrafa/com/degrafa/geometry/segment/LineTo.png new file mode 100644 index 0000000..e3909be Binary files /dev/null and b/Degrafa/com/degrafa/geometry/segment/LineTo.png differ diff --git a/Degrafa/com/degrafa/geometry/segment/MoveTo.as b/Degrafa/com/degrafa/geometry/segment/MoveTo.as new file mode 100644 index 0000000..607efff --- /dev/null +++ b/Degrafa/com/degrafa/geometry/segment/MoveTo.as @@ -0,0 +1,201 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.segment{ + + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.geometry.command.CommandStackItem; + + import flash.geom.Point; + import flash.geom.Rectangle; + + + [Exclude(name="isShortSequence", kind="property")] + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("MoveTo.png")] + + //(M or m) path data command + [Bindable] + /** + * A line (M,m) segment is defined by a target x-axis and y-axis point. + * + * @see http://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands + * + **/ + public class MoveTo extends Segment implements ISegment{ + + /** + * Constructor. + * + *

    The MoveTo constructor accepts 4 optional arguments that define it's + * data, properties and a coordinate type.

    + * + * @param x A number indicating the x-coordinate to move to. + * @param y A number indicating the y-coordinate to move to. + * @param data A string indicating the data to be used for this segment. + * @param coordinateType A string indicating the coordinate type to be used for this segment. + **/ + public function MoveTo(x:Number=0,y:Number=0,data:String=null,coordinateType:String="absolute"){ + + _x = x; + _y = y; + if (data) this.data = data; + _absCoordType= (coordinateType == "absolute"); + invalidated = true; + + } + + /** + * The isShortSequence property is ingnored on the MoveTo segment and + * setting it will have no effect. + **/ + override public function get isShortSequence():Boolean{return false;} + override public function set isShortSequence(value:Boolean):void{} + + + /** + * Return the segment type + **/ + override public function get segmentType():String{ + return "MoveTo"; + } + + /** + * MoveTo short hand data value. + * + *

    The move to data property expects exactly 2 values + * x and y.

    + * + * @see Segment#data + * + **/ + override public function set data(value:String):void{ + if(super.data != value){ + super.data = value; + + //parse the string on the space + var tempArray:Array = value.split(" "); + + if (tempArray.length == 2) + { + _x=tempArray[0]; + _y=tempArray[1]; + } + invalidated = true; + } + } + + private var _x:Number=0; + /** + * The x-coordinate to move to. If not specified + * a default value of 0 is used. + **/ + public function get x():Number{ + return _x; + } + public function set x(value:Number):void{ + if(_x != value){ + _x = value; + invalidated = true; + } + + } + + + private var _y:Number=0; + /** + * The y-coordinate to move to. If not specified + * a default value of 0 is used. + **/ + public function get y():Number{ + return _y; + } + public function set y(value:Number):void{ + if(_y != value){ + _y = value; + invalidated = true; + } + } + + private var _bounds:Rectangle; + /** + * The tight bounds of this segment as represented by a Rectangle object. + **/ + public function get bounds():Rectangle{ + return commandStackItem.bounds; + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + invalidated = false; + } + + private var lastPoint:Point=new Point(NaN,NaN); + + /** + * Compute the segment adding instructions to the command stack. + **/ + public function computeSegment(firstPoint:Point,lastPoint:Point,lastControlPoint:Point,commandStack:CommandStack):void{ + + if (!invalidated ) + { + invalidated= (!lastPoint.equals(this.lastPoint) ) + } + + if(invalidated){ + + if(!commandStackItem){ + _commandStackItem = new CommandStackItem(CommandStackItem.MOVE_TO, + _absCoordType? _x: lastPoint.x+_x,_absCoordType? _y: lastPoint.y+_y); + commandStack.addItem(_commandStackItem); + } + else{ + if (_absCoordType) + { + _commandStackItem.x = _x; + _commandStackItem.y = _y + } else { + _commandStackItem.x = lastPoint.x + _x; + _commandStackItem.y = lastPoint.y + _y; + } + } + //update this segment's point tracking reference + this.lastPoint.x = lastPoint.x; + this.lastPoint.y = lastPoint.y; + } + + //update the buildFlashCommandStack Point tracking reference + firstPoint.x=lastPoint.x = _commandStackItem.x; + firstPoint.y=lastPoint.y = _commandStackItem.y; + + //pre calculate the bounds for this segment + preDraw(); + + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/segment/MoveTo.png b/Degrafa/com/degrafa/geometry/segment/MoveTo.png new file mode 100644 index 0000000..202bfd3 Binary files /dev/null and b/Degrafa/com/degrafa/geometry/segment/MoveTo.png differ diff --git a/Degrafa/com/degrafa/geometry/segment/QuadraticBezierTo.as b/Degrafa/com/degrafa/geometry/segment/QuadraticBezierTo.as new file mode 100644 index 0000000..25a6806 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/segment/QuadraticBezierTo.as @@ -0,0 +1,278 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.segment{ + + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.geometry.command.CommandStackItem; + import com.degrafa.geometry.utilities.GeometryUtils; + + import flash.geom.Point; + import flash.geom.Rectangle; + + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("QuadraticBezierTo.png")] + + //(Q,q,T,t) path data commands + [Bindable] + /** + * Defines a quadratic Bézier curve from the current point to + * (x,y) using (cx,cy) as the control point. + * + * @see http://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands + * + **/ + public class QuadraticBezierTo extends Segment implements ISegment{ + + /** + * Constructor. + * + *

    The QuadraticBezierTo constructor accepts 7 optional arguments that define it's + * data, properties, coordinate type and a flag that specifies a short sequence.

    + + * @param cx A number indicating the x-coordinate of the control point of the curve. + * @param cy A number indicating the y-coordinate of the control point of the curve. + * @param x A number indicating the x-coordinate of the end point of the curve. + * @param y A number indicating the y-coordinate of the end point of the curve. + * @param data A string indicating the data to be used for this segment. + * @param coordinateType A string indicating the coordinate type to be used for this segment. + * @param isShortSequence A boolean indicating the if this segment is a short segment definition. + **/ + public function QuadraticBezierTo(cx:Number=0,cy:Number=0,x:Number=0,y:Number=0,data:String=null,coordinateType:String="absolute",isShortSequence:Boolean=false){ + + _cx = cx; + _cy = cy; + _x = x; + _y = y; + if (data) this.data = data; + _absCoordType= (coordinateType == "absolute"); + if (isShortSequence) _isShortSequence = true; + invalidated = true; + + } + + /** + * Return the segment type + **/ + override public function get segmentType():String{ + return "QuadraticBezierTo"; + } + + /** + * QuadraticBezierTo short hand data value. + * + *

    The quadratic Bézier data property expects exactly 4 values + * cx, cy, x and y separated by spaces.

    + * + * @see Segment#data + * + **/ + override public function set data(value:String):void{ + + if(super.data != value){ + super.data = value; + + //parse the string on the space + var tempArray:Array = value.split(" "); + + if (tempArray.length == 4) + { + _cx=tempArray[0]; + _cy=tempArray[1]; + _x=tempArray[2]; + _y=tempArray[3]; + } + invalidated = true; + } + } + + private var _x:Number=0; + /** + * The x-coordinate of the end point of the curve. If not specified + * a default value of 0 is used. + **/ + public function get x():Number{ + return _x; + } + public function set x(value:Number):void{ + if(_x != value){ + _x = value; + invalidated = true; + } + + } + + + private var _y:Number=0; + /** + * The y-coordinate of the end point of the curve. If not specified + * a default value of 0 is used. + **/ + public function get y():Number{ + return _y; + } + public function set y(value:Number):void{ + if(_y != value){ + _y = value; + invalidated = true; + } + } + + + private var _cx:Number=0; + /** + * The x-coordinate of the control point of the curve. If not specified + * a default value of 0 is used. + **/ + public function get cx():Number{ + return _cx; + } + public function set cx(value:Number):void{ + if(_cx != value){ + _cx = value; + invalidated = true; + } + } + + + private var _cy:Number=0; + /** + * The y-coordinate of the control point of the curve. If not specified + * a default value of 0 is used. + **/ + public function get cy():Number{ + return _cy; + } + public function set cy(value:Number):void{ + if(_cy != value){ + _cy = value; + invalidated = true; + } + } + + private var _bounds:Rectangle; + /** + * The tight bounds of this segment as represented by a Rectangle object. + **/ + public function get bounds():Rectangle{ + return commandStackItem.bounds; + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + invalidated = false; + } + + private var lastPoint:Point=new Point(NaN,NaN); + private var lastControlPoint:Point=new Point(NaN,NaN); + + /** + * Compute the segment adding instructions to the command stack. + **/ + public function computeSegment(firstPoint:Point,lastPoint:Point,lastControlPoint:Point,commandStack:CommandStack):void{ + + if (!invalidated ) + { + invalidated= (!lastPoint.equals(this.lastPoint) || !lastControlPoint.equals(this.lastControlPoint)) + } + + + + if (invalidated){ + //not yet created need to build it + //otherwise just reset the values. + if(!commandStackItem){ + if(_isShortSequence){ + _commandStackItem = new CommandStackItem(CommandStackItem.CURVE_TO, + NaN, + NaN, + _absCoordType? x:lastPoint.x+_x, + _absCoordType? y:lastPoint.y+_y, + lastPoint.x+(lastPoint.x-lastControlPoint.x), + lastPoint.y+(lastPoint.y-lastControlPoint.y) + ); + + commandStack.addItem(_commandStackItem); + } + else{ + _commandStackItem = new CommandStackItem(CommandStackItem.CURVE_TO, + NaN, + NaN, + _absCoordType? _x:lastPoint.x+_x, + _absCoordType? _y:lastPoint.y+_y, + _absCoordType? _cx:lastPoint.x+_cx, + _absCoordType? _cy:lastPoint.y+_cy + ); + + commandStack.addItem(_commandStackItem); + + } + } + else{ + if(isShortSequence){ + _commandStackItem.cx = lastPoint.x+(lastPoint.x-lastControlPoint.x); + _commandStackItem.cy = lastPoint.y+(lastPoint.y-lastControlPoint.y), + _commandStackItem.x1 = _absCoordType? _x:lastPoint.x+_x, + _commandStackItem.y1 = _absCoordType? _y:lastPoint.y+_y; + } + else + { + if (_absCoordType) + { + _commandStackItem.cx = _cx; + _commandStackItem.cy = _cy; + _commandStackItem.x1 = _x; + _commandStackItem.y1 = _y; + } else { + _commandStackItem.cx = lastPoint.x + _cx; + _commandStackItem.cy = lastPoint.y + _cy; + _commandStackItem.x1 = lastPoint.x + _x; + _commandStackItem.y1 = lastPoint.y + _y; + } + + } + } + //update this segment's point references + this.lastPoint.x = lastPoint.x; + this.lastPoint.y = lastPoint.y; + this.lastControlPoint.x = lastControlPoint.x; + this.lastControlPoint.y = lastControlPoint.y; + } + + //update the buildFlashCommandStack Point tracking reference + lastPoint.x = _commandStackItem.x1; + lastPoint.y = _commandStackItem.y1; + lastControlPoint.x = _commandStackItem.cx; + lastControlPoint.y = _commandStackItem.cy; + + + //pre calculate the bounds for this segment + preDraw(); + + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/segment/QuadraticBezierTo.png b/Degrafa/com/degrafa/geometry/segment/QuadraticBezierTo.png new file mode 100644 index 0000000..f7579ba Binary files /dev/null and b/Degrafa/com/degrafa/geometry/segment/QuadraticBezierTo.png differ diff --git a/Degrafa/com/degrafa/geometry/segment/Segment.as b/Degrafa/com/degrafa/geometry/segment/Segment.as new file mode 100644 index 0000000..7f26da3 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/segment/Segment.as @@ -0,0 +1,127 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.segment{ + + import com.degrafa.core.DegrafaObject; + import com.degrafa.core.IDegrafaObject; + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.geometry.command.CommandStackItem; + + [DefaultProperty("data")] + [Bindable(event="propertyChange")] + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("Segment.png")] + + /** + * Base class for segment elements that make up path geometry. + **/ + public class Segment extends DegrafaObject implements IDegrafaObject{ + + /** + * Specifies whether this object is to be re calculated + * on the next cycle. + **/ + public var invalidated:Boolean; + + /** + * Performs any pre calculation that is required to successfully render + * this element. Including bounds calculations and lower level drawing + * command storage. Each geometry object overrides this + * and is responsible for it's own pre calculation cycle. + **/ + public function preDraw():void{ + //overridden + } + + private var _data:String; + /** + * Allows a short hand property setting that is + * specific to and parsed by each geometry object. + * Look at the various geometry objects to learn what + * this setting requires. + **/ + public function get data():String{ + return _data; + } + public function set data(value:String):void{ + _data=value; + } + + /** + * Used for short sequence svg support specifically in a quad or + * cubic instance where the the mirror of the last control point + * is to be used as the new see svg specification S,s,T,t + **/ + internal var _isShortSequence:Boolean; + public function get isShortSequence():Boolean{ + return _isShortSequence; + } + public function set isShortSequence(value:Boolean):void{ + if(_isShortSequence != value){ + _isShortSequence = value; + } + } + + + /** + * Coordinate type to be used for segment. + **/ + internal var _absCoordType:Boolean = true; + + [Inspectable(category="General", enumeration="absolute,relative", defaultValue="absolute")] + public function set coordinateType(value:String):void + { + if ((value == "absolute") != _absCoordType) + { + _absCoordType = (value == "absolute"); + } + } + public function get coordinateType():String{ + return _absCoordType? "absolute":"relative"; + } + + //strickly overriden for each segment type + /** + * Returns this segment type. + **/ + public function get segmentType():String + { + return "none"; + } + + /** + * An Array of flash rendering commands that make up this element. + **/ + internal var _commandStackItem:CommandStackItem; + public function get commandStackItem():CommandStackItem{ + return _commandStackItem; + } + public function set commandStackItem(value:CommandStackItem):void{ + _commandStackItem=value; + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/segment/Segment.png b/Degrafa/com/degrafa/geometry/segment/Segment.png new file mode 100644 index 0000000..c00f01c Binary files /dev/null and b/Degrafa/com/degrafa/geometry/segment/Segment.png differ diff --git a/Degrafa/com/degrafa/geometry/segment/VerticalLineTo.as b/Degrafa/com/degrafa/geometry/segment/VerticalLineTo.as new file mode 100644 index 0000000..323318a --- /dev/null +++ b/Degrafa/com/degrafa/geometry/segment/VerticalLineTo.as @@ -0,0 +1,171 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.segment{ + + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.geometry.command.CommandStackItem; + + import flash.geom.Point; + import flash.geom.Rectangle; + + + [Exclude(name="isShortSequence", kind="property")] + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("VerticalLineTo.png")] + + //(V or v) path data command + [Bindable] + /** + * A vertical line (V,v) segment is defined by a ending y-axis point. + * + * @see http://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands + * + **/ + public class VerticalLineTo extends Segment implements ISegment{ + + /** + * Constructor. + * + *

    The VerticalLineTo constructor accepts 3 optional arguments that define it's + * data, properties and a coordinate type.

    + * + * @param y A number indicating the y-coordinate of the end point of the line. + * @param data A string indicating the data to be used for this segment. + * @param coordinateType A string indicating the coordinate type to be used for this segment. + **/ + public function VerticalLineTo(y:Number=0,data:String=null,coordinateType:String="absolute"){ + + _y = y; + if (data) this.data = data; + _absCoordType= (coordinateType == "absolute"); + invalidated = true; + + } + + /** + * The isShortSequence property is ingnored on the VerticalLineTo segment and + * setting it will have no effect. + **/ + override public function get isShortSequence():Boolean{return false;} + override public function set isShortSequence(value:Boolean):void{} + + /** + * Return the segment type + **/ + override public function get segmentType():String{ + return "VerticalLineTo"; + } + + /** + * VerticalLineTo short hand data value. + * + *

    The vertical line to data property expects exactly 1 value + * y.

    + * + * @see Segment#data + * + **/ + override public function set data(value:String):void{ + if(super.data != value){ + super.data = value; + _y=Number(value); + invalidated = true; + } + + } + + private var _y:Number=0; + /** + * The y-coordinate of the end point of the line. If not specified + * a default value of 0 is used. + **/ + public function get y():Number{ + return _y; + } + public function set y(value:Number):void{ + if(_y != value){ + _y = value; + invalidated = true; + } + } + + private var _bounds:Rectangle; + /** + * The tight bounds of this segment as represented by a Rectangle object. + **/ + public function get bounds():Rectangle{ + return commandStackItem.bounds; + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + invalidated = false; + } + + private var lastPoint:Point=new Point(NaN,NaN); + + /** + * Compute the segment adding instructions to the command stack. + **/ + public function computeSegment(firstPoint:Point,lastPoint:Point,lastControlPoint:Point,commandStack:CommandStack):void{ + + if(!invalidated) { + invalidated= (lastPoint.x!=this.lastPoint.x || (!this._absCoordType && lastPoint.y!=this.lastPoint.y) ) + } + + if(invalidated){ + + if(!commandStackItem){ + _commandStackItem = new CommandStackItem(CommandStackItem.LINE_TO, + lastPoint.x, + _absCoordType? _y:lastPoint.y + _y); + + commandStack.addItem(_commandStackItem); + } + else{ + _commandStackItem.x = lastPoint.x; + _commandStackItem.y = _absCoordType? _y:lastPoint.y + _y; + } + //update this segment's Point tracking reference + this.lastPoint.x = lastPoint.x; + this.lastPoint.y = lastPoint.y; + } + + + //update the buildFlashCommandStack Point tracking reference + lastPoint.y = _commandStackItem.y; + + + //pre calculate the bounds for this segment + preDraw(); + + + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/segment/VerticalLineTo.png b/Degrafa/com/degrafa/geometry/segment/VerticalLineTo.png new file mode 100644 index 0000000..7af3bb7 Binary files /dev/null and b/Degrafa/com/degrafa/geometry/segment/VerticalLineTo.png differ diff --git a/Degrafa/com/degrafa/geometry/splines/BasicSpline.as b/Degrafa/com/degrafa/geometry/splines/BasicSpline.as new file mode 100644 index 0000000..6249f8a --- /dev/null +++ b/Degrafa/com/degrafa/geometry/splines/BasicSpline.as @@ -0,0 +1,479 @@ +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// Programmed by: Jim Armstrong, (http://algorithmist.wordpress.com) +// +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.splines +{ + import com.degrafa.GraphicPoint; + import com.degrafa.IGeometry; + import com.degrafa.IGraphicPoint; + import com.degrafa.core.collections.GraphicPointCollection; + import com.degrafa.geometry.CubicBezier; + import com.degrafa.geometry.Geometry; + import com.degrafa.geometry.utilities.BezierUtils; + import com.degrafa.utilities.math.SplineToBezier; + + import flash.display.Graphics; + import flash.geom.Point; + import flash.geom.Rectangle; + + import mx.events.PropertyChangeEvent; + + [DefaultProperty("points")] + + [Bindable] + /** + * The Basic Spline is a pseudo-abstract base class from which a wide variety of splines + * may be constructed and easily integrated into the Degrafa geometry pipeline. This class + * is currently designed for purely interpolative splines. + * + **/ + public class BasicSpline extends Geometry implements IGeometry, ISpline + { + // count number of points added + protected var _count:uint=0; + + // reference to QuadData instances for the quad. beziers that approximate the spline + protected var _quads:Array; + + // reference to plottable spline that provides the computational 'base' for this spline. this is developed externally. + protected var _spline:IPlottableSpline; + + // approximate cartesian or parametric spline with quad. Beziers + protected var _toBezier:SplineToBezier; + + /** + * @description Method: BasicSpline() - Construct a new BasicSpline instance + * + * @return Nothing + * + * @since 1.0 + * + */ + public function BasicSpline( _myPoints:Array=null ) + { + super(); + + _count = 0; + + if( _myPoints ) + { + points = _myPoints; + } + + _spline = null; + _quads = new Array(); + _toBezier = new SplineToBezier(); + } + + private var _points:GraphicPointCollection; + + // it is more natural to talk about knots for spline developers, although 'points' are more frequently used for other Degrafa geometry objects + [Inspectable(category="General", arrayType="com.degrafa.IGraphicPoint")] + [ArrayElementType("com.degrafa.IGraphicPoint")] + /** + * Access the array of points that describe the knot set. + **/ + public function get points():Array + { + initPointsCollection(); + return _points.items; + } + + public function set points(value:Array):void + { + initPointsCollection(); + _points.items = value; + _count = value.length; + + // tbd - assign the knots to the spline + invalidated = true; + } + + /** + * Access the direct sequence of quadratic Bezier data that approximates the spline, including index into starting quad at each knot. + * First array is sequence of QuadData instances. Second array is index of QuadData instance of each knot. + **/ + public function get quadApproximation():Array { return _quads.slice(); } + + /** + * Access to the Degrafa point collection object for this spline. + **/ + public function get pointCollection():GraphicPointCollection + { + initPointsCollection(); + return _points; + } + + /** + * [set] spline Assign the reference to the IPlottableSpline providing the computational basis for this Degrafa spline. + **/ + public function set spline(splineRef:IPlottableSpline):void + { + if( splineRef != null ) + { + _spline = splineRef; + } + } + + /** + * return an array of quad Bezier approximations to the spline over the specified interval (cartesian or parameteric) - returns null if the + * values are outside the knot range for a cartesian spline or outside [0,1] for a parametric spline. Also returns null if the quad. Bezier + * approximation is not yet available, which is the case until Degrafa indicates the spline is completely rendered. + **/ + public function approximateInterval(val1:Number, val2:Number):Array + { + if( _spline.type == SplineTypeEnum.CARTESIAN ) + { + return approximateCartesianInterval(val1, val2); + } + + return []; + } + + protected function approximateCartesianInterval(val1:Number, val2:Number):Array + { + var quads:Array = quadApproximation; + if( quads == null ) + { + return quads; + } + + if( val2 <= val1 ) + { + return null; + } + + var knots:Array = points; + if( val1 < knots[0].x || val2 > knots[knots.length-1].x ) + { + return null; + } + + var q:Array = quads[0]; + var index:Array = quads[1]; + + // find bezier interval for the first and last values + var i1:int = 0; + var i2:int = 0; + for( var i:int=0; i= 0 ) + { + // subdivide at the parameter and take the second Bezier as the first quad in sequence - only need the middle control point, the other two points are already computed + var t1:Number = 1.0 - t; + + var cx:Number = t*qb.x1 + t1*qb.cx; + var cy:Number = t*qb.y1 + t1*qb.cy; + + approx.push( new QuadData(val1, eval(val1), cx, cy, qb.x1, qb.y1) ); + } + else + { + // should not happen, but put in a safety valve + approx.push(qb); + } + } + + // fill out in-between quads + if( i2 > i1 ) + { + for( i=i1+1; i= 0 ) + { + // subdivide at the parameter and take the first Bezier as the last quad in sequence - only need the middle control point, the other two points are already computed + t1 = 1.0 - t; + + cx = t*qb.cx + t1*qb.x0; + cy = t*qb.cy + t1*qb.y0; + + approx.push( new QuadData(qb.x0, qb.y0, cx, cy, val2, eval(val2)) ); + } + else + { + // should not happen, but put in a safety valve + approx.push(qb); + } + } + + return approx; + } + + /** + * Assign the knot collection using a shorthand data value, similar to the Geometry data setter. + * + *

    The spline data property expects a list of space seperated points. For example + * "10,20 30,35".

    + * + * @see Geometry#data + * + **/ + public function set knots(value:Object):void + { + // borrowed from BezierSpline + if(super.data != value && _spline != null) + { + super.data = value; + + // parse the string on the space + var pointsArray:Array = value.split(" "); + + // create a temporary point array + var pointArray:Array=[]; + var pointItem:Array; + + // and then create a point struct for each resulting pair eventually throw excemption is not matching properly + var i:int = 0; + var length:int = pointsArray.length; + for (; i< length;i++) + { + pointItem = String(pointsArray[i]).split(","); + + // skip past blank items as there may have been bad formatting in the value string, so make sure it is a length of 2 min + if( pointItem.length == 2 ) + { + pointArray.push(new GraphicPoint(pointItem[0],pointItem[1])); + + _spline.addControlPoint( pointItem[0], pointItem[1] ); // immediately add control point to the internal cubic spline + } + } + + // set the points property + points = pointArray; + } + } + + + public function addItem(_x:Number, _y:Number):void + { + _points.addItem(new GraphicPoint(_x,_y)); + } + + /** + * Initialize the point collection by creating it and adding the event listener. + **/ + protected function initPointsCollection():void + { + if( !_points ) + { + _points = new GraphicPointCollection(); + + // add a listener to the collection + if( enableEvents ) + { + _points.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + /** + * Principle event handler for any property changes to a geometry object or it's child objects. + **/ + override protected function propertyChangeHandler(event:PropertyChangeEvent):void + { + invalidated = true; + super.propertyChangeHandler(event); + } + + /** + * Access the knot count + **/ + public function get knotCount():int + { + return int(points.length); + } + + /** + * Adds a new knot to the spline. + **/ + public function addControlPoint(x:Number,y:Number):void + { + if( !isNaN(x) && !isNaN(y) && _spline != null ) + { + initPointsCollection(); + + addItem(x,y); + _spline.addControlPoint(x,y); + + _count++; + + invalidated = true; + } + } + + // following accessors should be overriden and implemented based on the type of spline, which should be a simple call since each spline implements IPlottableSpline + + // evaluate a cartesian spline at the specified x-coordinate + public function eval(_x:Number):Number { return 0; } + + // evaluate the first derivative of a cartesian spline at the specified x-coordinate + public function derivative(_x:Number):Number { return 0; } + + // evaluate the x- and y-coordinates of a parameteric spline at the specified parameter + public function getX(_t:Number):Number { return 0; } + public function getY(_t:Number):Number { return 0; } + + // evaluate x'(t) and y'(t) of a parameteric spline at the specified parameter + public function getXPrime(_t:Number):Number { return 0; } + public function getYPrime(_t:Number):Number { return 0; } + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void + { + // tbd + /*if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + + //default to bounds if no width or height is set + //and we have layout + if(isNaN(_layoutConstraint.width)){ + tempLayoutRect.width = bounds.width; + } + + if(isNaN(_layoutConstraint.height)){ + tempLayoutRect.height = bounds.height; + } + + if(isNaN(_layoutConstraint.x)){ + tempLayoutRect.x = bounds.x; + } + + if(isNaN(_layoutConstraint.y)){ + tempLayoutRect.y = bounds.y; + } + + super.calculateLayout(tempLayoutRect); + + _layoutRectangle = _layoutConstraint.layoutRectangle; + + } + }*/ + } + + // approximate spline with quad. Beziers + protected function initPoints():void + { + if( !points.length ) + { + return; + } + + if( _count > 2 ) + { + _quads = _toBezier.convert(_spline); + invalidated = false; + } + } + + /** + * @inheritDoc + **/ + override public function preDraw():void + { + if( invalidated ) + { + if( _count < 3 ) + { + return; + } + + //init the points + initPoints(); + + commandStack.length=0; + + // add a MoveTo at the start of the commandStack rendering chain + commandStack.addMoveTo(points[0].x,points[0].y); + + var quads:Array = _quads[0]; + for( var i:uint=0; iThe spline data property expects a list of space seperated points. For example + * "10,20 30,35".

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void{ + if(super.data != value){ + super.data = value; + + //parse the string on the space + var pointsArray:Array = value.split(" "); + + //create a temporary point array + var pointArray:Array=[]; + var pointItem:Array; + + //and then create a point struct for each resulting pair + //eventually throw excemption is not matching properly + var i:int = 0; + var length:int = pointsArray.length; + for (; i< length;i++){ + pointItem = String(pointsArray[i]).split(","); + + //skip past blank items as there may have been bad + //formatting in the value string, so make sure it is + //a length of 2 min + if(pointItem.length==2){ + pointArray.push(new GraphicPoint(pointItem[0],pointItem[1])); + } + } + + //set the points property + points=pointArray; + } + } + + private var _points:GraphicPointCollection; + [Inspectable(category="General", arrayType="com.degrafa.IGraphicPoint")] + [ArrayElementType("com.degrafa.IGraphicPoint")] + /** + * A array of points that describe this polyline. + **/ + public function get points():Array{ + initPointsCollection(); + return _points.items; + } + public function set points(value:Array):void{ + initPointsCollection(); + _points.items = value; + + invalidated = true; + } + + /** + * Access to the Degrafa point collection object for this polyline. + **/ + public function get pointCollection():GraphicPointCollection{ + initPointsCollection(); + return _points; + } + + /** + * Initialize the point collection by creating it and adding the event listener. + **/ + private function initPointsCollection():void{ + if(!_points){ + _points = new GraphicPointCollection(); + + //add a listener to the collection + if(enableEvents){ + _points.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + private var _autoClose:Boolean; + /** + * Specifies if this polyline is to be automatically closed. + * If true a line is drawn to the first point. + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get autoClose():Boolean{ + return _autoClose; + } + public function set autoClose(value:Boolean):void + { + if( _autoClose != value ) + { + _autoClose = value; + _controlCage.CLOSED = value; // BezierSplineControl needs to know whether or not the control cages are constructed with automatic closure + invalidated = true; + } + } + + /** + * Principle event handler for any property changes to a + * geometry object or it's child objects. + **/ + override protected function propertyChangeHandler(event:PropertyChangeEvent):void{ + invalidated = true; + super.propertyChangeHandler(event); + } + + public function get length():Number{ + return points.length; + } + + private var _tension:Number=1; + /** + * spline 'tension' - lower tension increases probability of ripples + **/ + public function get tension():uint { + return _tension; + } + public function set tension(value:uint):void{ + var t:Number = Math.max(0,value); + t = Math.min(5,t); + _controlCage.tension = t; + + if(_tension != t){ + _tension = t; + invalidated = true; + } + + } + + + private var _quality:uint=2; + /** + * Controls number of subdivision sweeps in cubic bezier segments + **/ + public function get quality():uint { + return _quality; + } + public function set quality(value:uint):void{ + var t:Number = Math.max(0,value); + + t = Math.min(3,t); + + if(_quality != t){ + _quality = t; + invalidated = true; + } + + } + + private var _parameterization:String="UNIFORM"; + /** + * Spline parameterization, arc-length or uniform. + **/ + [Inspectable(category="General", enumeration="AUTO,ARC_LENGTH,UNIFORM", defaultValue="UNIFORM")] + public function get parameterization():String{ + return _parameterization; + } + public function set parameterization(value:String):void{ + + if(_parameterization != value){ + _parameterization = value + + //map the const + value= Consts[value]; + + if( value == Consts.ARC_LENGTH || value == Consts.UNIFORM ){ + _param = value; + } + + invalidated = true; + } + + } + + //todo not yet implemented + public function _integrand(_t:Number):Number{ + var x:Number = _bezier[_index].getXPrime(_t); + var y:Number = _bezier[_index].getYPrime(_t); + return Math.sqrt( x*x + y*y ); + } + + /** + * Adds a new point to the bezier curve. + **/ + public function addControlPoint(x:Number,y:Number):void{ + if( !isNaN(x) && !isNaN(y) ){ + _points.addItem(new GraphicPoint(x,y)); + + _controlCage.knots = points; + + _index = points.length-1; + + if( _index > 0 ){ + var b:CubicBezier = new CubicBezier(); + _bezier.push(b); + } + + invalidated =true; + } + } + + /** + * Resets the spline to it's initial state + **/ + public function reset():void{ + points.splice(0); + for( var i:uint=0; i<_bezier.length; ++i ) + _bezier[i].reset(); + + _bezier.splice(0); + _interpS.splice(0); + _interpT.splice(0); + points.splice(0); + _arcLengthAtSegments.splice(0); + + _count = 0; + _arcLength = -1; + invalidated = true; + } + + public function getX(_t:Number):Number{ + if(invalidated) + _assignControlPoints(); + + _interval(_t); + + return _bezier[_index].getX(_t); + } + + public function getY(_t:Number):Number{ + if(invalidated) + _assignControlPoints(); + + _interval(_t); + + return _bezier[_index].getY(_t); + } + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + + if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + + //default to bounds if no width or height is set + //and we have layout + if(isNaN(_layoutConstraint.width)){ + tempLayoutRect.width = bounds.width; + } + + if(isNaN(_layoutConstraint.height)){ + tempLayoutRect.height = bounds.height; + } + + if(isNaN(_layoutConstraint.x)){ + tempLayoutRect.x = bounds.x; + } + + if(isNaN(_layoutConstraint.y)){ + tempLayoutRect.y = bounds.y; + } + + super.calculateLayout(tempLayoutRect); + + _layoutRectangle = _layoutConstraint.layoutRectangle; + + } + } + } + + //reset the knots and reset the beziers + private function initPoints():void{ + + if(!points.length){return;} + + // Set the knot reference so that BezierSplineControl knows how + //to compute control points for each Bezier curve to ensure + //C-1 continuity at the knots + _controlCage.knots = points; + + _bezier.length=0; + + //add the cubic bezier curves for each segment + for each (var point:IGraphicPoint in points){ + _bezier.push(new CubicBezier()); + } + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + if( invalidated ){ + + if(!points.length){return;} + + //exit if point count is to low. + if((points.length-1)<2){return;} + + //init the points + initPoints() + + _assignControlPoints(); + + //add a move to for the first item. + commandStack.length=0; + + //add a MoveTo at the start of the commandStack rendering chain + commandStack.addMoveTo(points[0].x,points[0].y); + + var cubic:CubicBezier; + + //todo not sure were this extra one is coming from yet. + //re:: - 1 on the count + for( var i:uint=0; i<_bezier.length-1; ++i ){ + + cubic = _bezier[i]; + + commandStack.addCubicBezierTo(cubic.x0,cubic.y0,cubic.cx, + cubic.cy,cubic.cx1,cubic.cy1,cubic.x1,cubic.y1,1); + + } + + invalidated = false; + } + } + + //_t:Number=1.0 + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + + //re init if required + if (invalidated) preDraw(); + + //init the layout in this case done after predraw. + if (_layoutConstraint) calculateLayout(); + + super.draw(graphics,(rc)? rc:bounds); + + } + + + public function arcLength():Number{ + if ( _arcLength != -1 ){return _arcLength;} + + if( invalidated ){ + _assignControlPoints(); + } + + if( _arcLengthAtSegments.length == 0 ){ + _getArcLengthAtSegments(); + } + + _arcLength = _arcLengthAtSegments[points.length-2]; + + return _arcLength; + } + + public function arcLengthAt(_t:Number):Number{ + + // compute the length of each segment and sum + var len:Number = 0; + var k:uint = points.length; + + if( k < 2 || _t == 0 ){ + return len; + } + + if( invalidated ){ + _assignControlPoints(); + } + + if( _arcLengthAtSegments.length == 0 ){ + _getArcLengthAtSegments(); + } + + if( _t == 1 ){return _arcLength;} + + var t:Number = (_t<0) ? 0 : _t; + t = (t>1) ? 1 : t; + + // determine which segment corresponds to the input value and the local + //parameter for that segment + var N1:Number = k-1; + var N1t:Number = N1*t; + var f:Number = Math.floor(N1t); + var maxSeg:Number = Math.min(f+1, N1)-1; + var param:Number = N1t - f; + + // full curve length up to, but not including final segment + if( maxSeg > 0 ){ + len = _arcLengthAtSegments[maxSeg-1]; + } + + // add partial curve segment length, unless we're at a knot + if( param != 0 ){ + _index = maxSeg; + len += _integral.eval( _integrand, 0, param, 5 ); + } + + return len; + } + + private function _getArcLengthAtSegments():void{ + _index = 0; + _arcLengthAtSegments[0] = _integral.eval( _integrand, 0, 1, 5 ); + + _minSegLength = _arcLengthAtSegments[0]; + + for( var i:uint=1; i1) ? 1 : t; + + // if arc-length parameterization, approximate L^-1(s) + if( _param == Consts.ARC_LENGTH ){ + if( t != _s ){ + //_t = _spline.eval(t); + _t = _interpolate(t); + _s = t; + _segment(); + } + } + else{ + if( t != _t ){ + _t = t; + _segment(); + } + } + } + + // compute current segment and local parameter value + private function _segment():void{ + // the trivial case -- one segment + var k:Number = points.length; + if( k == 2 ){ + _index = 0; + } + else { + if( _t == 0 ){ + _index = 0; + } + else if( _t == 1.0 ){ + _index = k-2; + } + else{ + var N1:Number = k-1; + var N1t:Number = N1*_t; + var f:Number = Math.floor(N1t); + _index = Math.min(f+1, N1)-1; + _t = N1t - f; + } + } + } + + // assign control points for each cubic segment + private function _assignControlPoints():void{ + // if the spline is closed, a new control point is added so that the + //start and end points of the spline match + + var l1:uint = points.length-1; + if(autoClose && ((points[0].x != points[l1].x) || (points[0].y != points[l1].y))){ + //todo jason + addControlPoint(points[0].x, points[0].y); + } + + _controlCage.construct(); + + for( var i:uint=0; i<_bezier.length-1; ++i ){ + var c:CubicCage = _controlCage.getCage(i); + var b:CubicBezier = _bezier[i]; + b.x0 =c.P0X; + b.y0 =c.P0Y; + b.cx=c.P1X; + b.cy=c.P1Y; + b.cx1=c.P2X; + b.cy1=c.P2Y; + b.x1=c.P3X; + b.y1=c.P3Y; + + } + + invalidated = false; + _parameterize(); + } + + // parameterize composite curve - this function may vary based on the type of curve. + private function _parameterize():void{ + if( _param == Consts.ARC_LENGTH ){ + + if( _arcLength == -1 ) + var len:Number = arcLength(); + + _interpS.splice(0); + _interpT.splice(0); + points.splice(0); + + // number of interpolation points per segment - min segment length gets four, everything else is proportional (up to a limit) + for( var i:uint=0; i<_arcLengthAtSegments.length; ++i ){ + var ratio:Number = Math.floor(_arcLengthAtSegments[i]/_minSegLength); + points[i] = Math.min( 12, 4*ratio); + } + + var normalize:Number = 1.0/_arcLength; + + // x-coordinate of spline knot is normalized arc-length, y-coordinate + //is t-value for uniform parameterization + _interpS[0] = 0; + _interpT[0] = 0; + var prevT:Number = 0; + //var k:uint = _knots.length; + var k:uint = points.length; + var knotsInv:Number = 1.0/Number(k-1); + var indx:uint = 1; + + for( i=1; i= s ){ + start = k; + break; + } + } + + start = start == 0 ? 1 : points[start-1]; + + // determine specific lookup interval in the segment + for( var i:uint=start; i<_interpS.length; ++i ){ + var knot:Number = _interpS[i]; + if( _s <= knot ){ + var knot1:Number = _interpS[i-1]; + var r:Number = (_s-knot1)/(knot-knot1); + return _interpT[i-1] + r*(_interpT[i] - _interpT[i-1]); + } + } + + return 0; + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/splines/BezierSplineControl.as b/Degrafa/com/degrafa/geometry/splines/BezierSplineControl.as new file mode 100644 index 0000000..df7f07d --- /dev/null +++ b/Degrafa/com/degrafa/geometry/splines/BezierSplineControl.as @@ -0,0 +1,489 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Programmed by: Jim Armstrong, Singularity (www.algorithmist.net) and +// ported by the Degrafa team. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.splines{ + + import com.degrafa.utilities.math.*; + import flash.display.Shape; + + /** + * Helper class used by various spline Geometry. + **/ + public class BezierSplineControl + { + // properties + public var CLOSED:Boolean; // true if closed spline + + // core + private var _bXNR:Number; // bisector 'right' normal, x-coordinate + private var _bYNR:Number; // bisector 'right' normal, y-coordinate + private var _bXNL:Number; // bisector 'left' normal, x-coordinate + private var _bYNL:Number; // bisector 'left' normal, y-coordinate + private var _pX:Number; // reflected point, x-coordinate + private var _pY:Number; // reflected point, y-coordinate + + private var _dX1:Number; // delta-x, first segment + private var _dY1:Number; // delta-y, first segment + private var _dX2:Number; // delta-x, second segment + private var _dY2:Number; // delta-y, second segment + private var _d1:Number; // first segment length + private var _d2:Number; // second segment length + private var _tension:Number; // tension parameter (1-5) + private var _uX:Number; // unit vector, direction of bisector, x-coordinate + private var _uY:Number; // unit vector, direction of bisector, y-coordinate + private var _dist:Number; // distance measure from segment intersection, along direction of bisector + + private var _cage:Array; // CubicCage instances for each segment + private var _points:Array; // knots + private var _numPoints:uint; // number of knots + private var _tensionMap:Array // map user-specified tension value (1-5) into fraction of segment distance (0.1-0.4) + + public function BezierSplineControl() + { + CLOSED = false; + + _points = new Array(); + _cage = new Array(); + _tensionMap = new Array(); + + _bXNR = 0; + _bYNR = 0; + _bXNL = 0; + _bYNL = 0; + _pX = 0; + _pY = 0; + + _dX1 = 0; + _dY1 = 0; + _dX2 = 0; + _dY2 = 0; + _d1 = 0; + _d2 = 0; + + _uX = 0; + _uY = 0; + _dist = 0; + + _tension = 1; + _numPoints = 0; + + // These are arbitrary, so experiment and have fun. Don't go over 0.5 or very bad things will + // happen. Do you know why? Less than 0.1 is kind of useless as you might as well just connect + // the knots with lines and be done with it :) 0.3 is pretty good 'middle' ground. Low values + // have low tension and the tension increases as the index increases. Lower tension increases + // the probability of 'ripples'. + _tensionMap[0] = 0.4; + _tensionMap[1] = 0.3; + _tensionMap[2] = 0.25; + _tensionMap[3] = 0.175; + _tensionMap[4] = 0.1; + } + + public function get tension():Number + { + return _tension; + } + + public function set knots(_a:Array):void + { + // Sets reference only; does not copy knots. Only needs to be set once, + //then construct() can be called after changes to the original knot set + _points = _a; + } + + public function set tension(_t:Number):void + { + var n:Number = Math.round(_t); + if( n > 0 && n < 6 ) + _tension = _t-1; + } + + /** + * @description Method: getCage(_i:Number) - draw the control cages + * + * @param _i:Number - Control-cage index (i-th segment, not i-th knot), zero based + * @return CubicCage - reference to control cage for cubic bezier curve at the specified segment. + * + * @since 1.0 + * + * Note: No out-of-range checking, caveat receptor! + */ + public function getCage(_i:Number):CubicCage + { + return _cage[_i]; + } + + /** + * @description Method: construct() - construct all control cages based on current knot set + * + * @return Nothing + * + * @since 1.0 + * + */ + public function construct():void + { + var count:uint = _points.length-1; + _numPoints = _points.length; + + if( count < 2 ) + return; // safety valve + + for( var i:uint=0; i Consts.ZERO_TOL ) + { + if( _isClockWise(_points, _i) ) + _CW(_i, _t); + else + _CCW(_i, _t); + } + else + { + _bXNR = _points[_i].x + _t*(_points[_i].x - _points[_i+1].x); + _bYNR = _points[_i].y + _t*(_points[_i].y - _points[_i+1].y); + + _bXNL = _points[_i].x + _t*(_points[_i+2].x - _points[_i+1].x); + _bYNL = _points[_i].y + _t*(_points[_i+2].y - _points[_i+1].y); + } + + coef.P2X = _bXNR; + coef.P2Y = _bYNR; + coef.P3X = _points[_i+1].x; + coef.P3Y = _points[_i+1].y; + } + + private function _getNormals(_i:uint):void + { + _dX1 = _points[_i].x - _points[_i+1].x; + _dY1 = _points[_i].y - _points[_i+1].y; + _d1 = Math.sqrt(_dX1*_dX1 + _dY1*_dY1); + _dX1 /= _d1; + _dY1 /= _d1; + + _dX2 = _points[_i+2].x - _points[_i+1].x; + _dY2 = _points[_i+2].y - _points[_i+1].y; + _d2 = Math.sqrt(_dX2*_dX2 + _dY2*_dY2); + _dX2 /= _d2; + _dY2 /= _d2; + + _uX = _dX1 + _dX2; + _uY = _dY1 + _dY2; + _dist = Math.sqrt(_uX*_uX + _uY*_uY); + _uX /= _dist; + _uY /= _dist; + } + + // 'leftmost' control cage, open spline + private function _left(_t:Number):void + { + _getNormals(0); + + if( _dist > Consts.ZERO_TOL ) + { + if( _isClockWise(_points, 0) ) + _CW(0, _t); + else + _CCW(0, _t); + + var mX:Number = 0.5*(_points[0].x + _points[1].x); + var mY:Number = 0.5*(_points[0].y + _points[1].y); + var pX:Number = _points[0].x - mX; + var pY:Number = _points[0].y - mY; + + // normal at midpoint + var n:Number = 2.0/_d1; + var nX:Number = -n*pY; + var nY:Number = n*pX; + + // upper triangle of symmetric transform matrix + var a11:Number = nX*nX - nY*nY + var a12:Number = 2*nX*nY; + var a22:Number = nY*nY - nX*nX; + + var dX:Number = _bXNR - mX; + var dY:Number = _bYNR - mY; + + // coordinates of reflected vector + _pX = mX + a11*dX + a12*dY; + _pY = mY + a12*dX + a22*dY; + } + else + { + _bXNR = _points[1].x + _t*(_points[0].x - _points[1].x); + _bYNR = _points[1].y + _t*(_points[0].y - _points[1].y); + + _bXNL = _points[1].x + _t*(_points[2].x - _points[1].x); + _bYNL = _points[1].y + _t*(_points[2].y - _points[1].y); + + _pX = _points[0].x + _t*(_points[1].x - _points[0].x); + _pY = _points[0].y + _t*(_points[1].y - _points[0].y); + } + + var coef:CubicCage = _cage[0]; + + coef.P0X = _points[0].x; + coef.P0Y = _points[0].y; + coef.P1X = _pX; + coef.P1Y = _pY; + coef.P2X = _bXNR; + coef.P2Y = _bYNR; + coef.P3X = _points[1].x; + coef.P3Y = _points[1].y; + } + + // 'leftmost' control cage, closed spline + private function _leftClosed(_t:Number):void + { + // point order is n-2, 0, 1 (as 0 and n-1 are the same knot in a closed spline). Use 'right normal' to set first two control cage points + var n2:uint = _numPoints-2; + + // Exercise - modify the argument list for _getNormals() to work with the following computations + _dX1 = _points[n2].x - _points[0].x; + _dY1 = _points[n2].y - _points[0].y; + _d1 = Math.sqrt(_dX1*_dX1 + _dY1*_dY1); + _dX1 /= _d1; + _dY1 /= _d1; + + _dX2 = _points[1].x - _points[0].x; + _dY2 = _points[1].y - _points[0].y; + _d2 = Math.sqrt(_dX2*_dX2 + _dY2*_dY2); + _dX2 /= _d2; + _dY2 /= _d2; + + _uX = _dX1 + _dX2; + _uY = _dY1 + _dY2; + _dist = Math.sqrt(_uX*_uX + _uY*_uY); + _uX /= _dist; + _uY /= _dist; + + if( _dist > Consts.ZERO_TOL ) + { + if( ((_points[1].y-_points[n2].y)*(_points[0].x-_points[n2].x) > (_points[0].y-_points[n2].y)*(_points[1].x-_points[n2].x))) + { + var dt:Number = _t*_d2; + _bXNL = _points[0].x + dt*_uY; + _bYNL = _points[0].y - dt*_uX; + } + else + { + dt = _t*_d2; + _bXNL = _points[0].x - dt*_uY; + _bYNL = _points[0].y + dt*_uX; + } + } + else + { + _bXNL = _points[0].x + _t*_dX1; + _bYNL = _points[0].y + _t*_dY1; + } + + var coef:CubicCage = _cage[0]; + coef.P0X = _points[0].x; + coef.P0Y = _points[0].y; + coef.P1X = _bXNL; + coef.P1Y = _bYNL; + + // now, continue as before using the point order 0, 1, 2 + _getNormals(0); + + if( _dist > Consts.ZERO_TOL ) + { + if( _isClockWise(_points, 0)) + _CW(0, _t); + else + _CCW(0, _t); + } + else + { + _bXNR = _points[1].x + _t*_dX1; + _bYNR = _points[1].y + _t*_dY1; + + _bXNL = _points[1].x + _t*_dX2; + _bYNL = _points[1].y + _t*_dY2; + } + + coef.P2X = _bXNR; + coef.P2Y = _bYNR; + coef.P3X = _points[1].x; + coef.P3Y = _points[1].y; + } + + // 'rightmost' control cage, open spline + private function _right(_t:Number):void + { + var count:Number = _points.length-1; + if( _dist > Consts.ZERO_TOL ) + { + var mX:Number = 0.5*(_points[count-1].x + _points[count].x); + var mY:Number = 0.5*(_points[count-1].y + _points[count].y); + var pX:Number = _points[count].x - mX; + var pY:Number = _points[count].y - mY; + + // normal at midpoint + var n:Number = 2.0/_d2; + var nX:Number = -n*pY; + var nY:Number = n*pX; + + // upper triangle of symmetric transform matrix + var a11:Number = nX*nX - nY*nY + var a12:Number = 2*nX*nY; + var a22:Number = nY*nY - nX*nX; + + var dX:Number = _bXNL - mX; + var dY:Number = _bYNL - mY; + + // coordinates of reflected vector + _pX = mX + a11*dX + a12*dY; + _pY = mY + a12*dX + a22*dY; + } + else + { + _bXNL = _points[count-1].x + _t*(_points[count].x - _points[count-1].x); + _bYNL = _points[count-1].y + _t*(_points[count].y - _points[count-1].y); + + _pX = _points[count].x + _t*(_points[count-1].x - _points[count].x); + _pY = _points[count].y + _t*(_points[count-1].y - _points[count].y); + } + + var coef:CubicCage = _cage[count-1]; + + coef.P0X = _points[count-1].x; + coef.P0Y = _points[count-1].y; + coef.P1X = _bXNL; + coef.P1Y = _bYNL; + coef.P2X = _pX; + coef.P2Y = _pY; + coef.P3X = _points[count].x; + coef.P3Y = _points[count].y; + } + + // 'rightmost' control cage, closed spline + private function _rightClosed(_t:Number):void + { + // no additional computations are required as the P2X, P2Y point is a reflection of the P1X, P1Y point from the very first control cage + var count:Number = _numPoints-1; + + var c0:CubicCage = _cage[0]; + var coef:CubicCage = _cage[count-1]; + + coef.P0X = _points[count-1].x; + coef.P0Y = _points[count-1].y; + coef.P1X = _bXNL; + coef.P1Y = _bYNL; + coef.P2X = 2.0*_points[0].x - c0.P1X; + coef.P2Y = 2.0*_points[0].y - c0.P1Y; + coef.P3X = _points[count].x; // knot number 'count' and knot number 0 should be the same for a closed spline + coef.P3Y = _points[count].y; + } + + + // bisector normal computations, clockwise knot order + private function _CW(_i:int, _t:Number):void + { + var dt:Number = _t*_d1; + + _bXNR = _points[_i+1].x - dt*_uY; + _bYNR = _points[_i+1].y + dt*_uX; + + dt = _t*_d2; + _bXNL = _points[_i+1].x + dt*_uY; + _bYNL = _points[_i+1].y - dt*_uX; + } + + // bisector normal computations, counter-clockwise knot order + private function _CCW(_i:int, _t:Number):void + { + var dt:Number = _t*_d2; + + _bXNL = _points[_i+1].x - dt*_uY; + _bYNL = _points[_i+1].y + dt*_uX; + + dt = _t*_d1; + _bXNR = _points[_i+1].x + dt*_uY; + _bYNR = _points[_i+1].y - dt*_uX; + } + + // clockwise order for three-knot sequence? + private function _isClockWise(_pts:Array, _i:Number):Boolean + { + return ((_pts[_i+2].y-_pts[_i].y)*(_pts[_i+1].x-_pts[_i].x) > (_pts[_i+1].y-_pts[_i].y)*(_pts[_i+2].x-_pts[_i].x)); + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/splines/CardinalSpline.as b/Degrafa/com/degrafa/geometry/splines/CardinalSpline.as new file mode 100644 index 0000000..93d1868 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/splines/CardinalSpline.as @@ -0,0 +1,138 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// Programmed by: Jim Armstrong, (http://algorithmist.wordpress.com) +// Reference: http://www.algorithmist.net/media/catmullrom.pdf +// +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.splines +{ + import com.degrafa.GraphicPoint; + import com.degrafa.IGeometry; + import com.degrafa.IGraphicPoint; + import com.degrafa.core.collections.GraphicPointCollection; + import com.degrafa.geometry.Geometry; + import com.degrafa.utilities.math.CardSpline; + + import flash.display.Graphics; + import flash.geom.Point; + import flash.geom.Rectangle; + + import mx.events.PropertyChangeEvent; + + [DefaultProperty("points")] + + [Bindable] +/** + * The CardinalSpline is a version of the Cardinal Spline utility that is optimized + * for drawing in the Degrafa command stack. This spline is normally used to interpolate a set + * of x-y data points with variable tensionn. The default tension reproduces the Catmull-Rom + * spline. The CardinalSpline has adjustable 'auxiliary' points that affect the spline shape + * at beginning and end knots just as the Catmull-Rom. + * + * There should be at least three knots specified before drawing the spline. + * + * @see com.degrafa.geometry.splines.CatmullRom CatmullRom + */ + public class CardinalSpline extends BasicSpline + { + // reference to base card. spline utility + private var _mySpline:CardSpline; + + // closed spline? + private var _isClosed:Boolean; + +/** + * CardinalSpline Construct a new CardinalSpline instance + * + * @return Nothing + * + * @since 1.0 + * + */ + public function CardinalSpline( _myPoints:Array=null ) + { + super(_myPoints); + + _mySpline = new CardSpline(); + super.spline = _mySpline; + + _isClosed = false; + } + +/** + * [set] tension Set the spline's tension + * + * @param _t:Number tension value, normally in [0,1] range + * + * @return Nothing Tension affects how 'tight' the spline fits the knots. A zero-tension spline provides a relatively loose fit, and in fact defaults + * to the Catmull-Rom spline. A negative tension loosens the spline even further. Tensions as low as -1 are allowed. As tension approaches 1, the + * spline approaches a line-to-line interpolation of the knots. A tension value above 1 causes the spline to loop around itself moving through knots. + * Tensions as high as +3 are supported. + * + * @since 1.0 + * + */ + public function set tension(_t:Number):void + { + var t:Number = Math.min(_t, 3.0); + t = Math.max(t, -1.0); + + _mySpline.tension = _t; + } + +/** + * [set] closed Create a closed-loop spline from the current knot set + * + * @param _closed:Boolean true if the spline is to be automatically closed + * + * @return Nothing Knots should already be defined in a manner that tends towards a naturally closed loop. There is no need to duplicate the first knot in + * sequence. If the knot sequence does not tends towards a closed shape, results are unpredicatable. DO NOT attempt to add knots to a closed spline. + * Setting closure to true after false currently has no effect, but allowing unclosure is reserved for possible inclusion in a future version. + * This setter is intended for use via MXML. + * + * @since 1.0 + * + */ + public function set closed(_closed:Boolean):void + { + if( !_isClosed && _closed ) + { + _mySpline.closed = true; + + super.invalidated = true; + super.drawToTargets(); + } + } + +/** + * x-coordinate of the Catmull-Rom spline at the specified t-parameter + */ + override public function getX(_t:Number):Number { return _mySpline.getX(_t); } + +/** + * y-coordinate of the Catmull-Rom spline at the specified t-parameter + */ + override public function getY(_t:Number):Number { return _mySpline.getY(_t); } + +/** + * dx/dt of Catmull-Rom spline + */ + override public function getXPrime(_t:Number):Number { return _mySpline.getXPrime(_t); } + +/** + * dx/dt of Catmull-Rom spline + */ + override public function getYPrime(_t:Number):Number { return _mySpline.getYPrime(_t); } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/splines/CatmullRomSpline.as b/Degrafa/com/degrafa/geometry/splines/CatmullRomSpline.as new file mode 100644 index 0000000..ac77e0c --- /dev/null +++ b/Degrafa/com/degrafa/geometry/splines/CatmullRomSpline.as @@ -0,0 +1,118 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// Programmed by: Jim Armstrong, (http://algorithmist.wordpress.com) +// Reference: http://www.algorithmist.net/media/catmullrom.pdf +// +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.splines +{ + import com.degrafa.GraphicPoint; + import com.degrafa.IGeometry; + import com.degrafa.IGraphicPoint; + import com.degrafa.core.collections.GraphicPointCollection; + import com.degrafa.geometry.Geometry; + import com.degrafa.utilities.math.CatmullRom; + + import flash.display.Graphics; + import flash.geom.Point; + import flash.geom.Rectangle; + + import mx.events.PropertyChangeEvent; + + [DefaultProperty("points")] + + [Bindable] + /** + * The CatmullRomSpline is a version of the Catmull-Rom spline utility that is optimized + * for drawing in the Degrafa command stack. This spline is normally used to interpolate a set + * of x-y data points and for path animation. The spline may be closed and there is a facility + * to adjust the shape of the spline out of the first knot and into the last knot by adjusting + * 'auxiliary' or 'artificial' knots before the first and after the last. Options are to + * have the code automatically chose the artificial knots (default), duplicate the first and last + * knots, or specify them manually. + * + * There should be at least three knots specified before drawing the spline. + * + **/ + public class CatmullRomSpline extends BasicSpline + { + // reference to CR spline utility + private var _mySpline:CatmullRom; + + // closed spline? + private var _isClosed:Boolean; + + /** + * CatmullRomSpline Construct a new CatmullRomSpline instance + * + * @return Nothing + * + * @since 1.0 + * + */ + public function CatmullRomSpline( _myPoints:Array=null ) + { + super(_myPoints); + + _mySpline = new CatmullRom(); + super.spline = _mySpline; + + _isClosed = false; + } + +/** + * [set] closed Create a closed-loop spline from the current knot set + * + * @param _closed:Boolean true if the spline is to be automatically closed + * + * @return Nothing Knots should already be defined in a manner that tends towards a naturally closed loop. There is no need to duplicate the first knot in + * sequence. If the knot sequence does not tends towards a closed shape, results are unpredicatable. DO NOT attempt to add knots to a closed spline. + * Setting closure to true after false currently has no effect, but allowing unclosure is reserved for possible inclusion in a future version. + * This setter is intended for use via MXML. + * + * @since 1.0 + * + */ + public function set closed(_closed:Boolean):void + { + if( !_isClosed && _closed ) + { + _mySpline.closed = true; + + super.invalidated = true; + super.drawToTargets(); + } + } + + /** + * x-coordinate of the Catmull-Rom spline at the specified t-parameter + **/ + override public function getX(_t:Number):Number { return _mySpline.getX(_t); } + + /** + * y-coordinate of the Catmull-Rom spline at the specified t-parameter + **/ + override public function getY(_t:Number):Number { return _mySpline.getY(_t); } + + /** + * dx/dt of Catmull-Rom spline + **/ + override public function getXPrime(_t:Number):Number { return _mySpline.getXPrime(_t); } + + /** + * dx/dt of Catmull-Rom spline + **/ + override public function getYPrime(_t:Number):Number { return _mySpline.getYPrime(_t); } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/splines/CubicCage.as b/Degrafa/com/degrafa/geometry/splines/CubicCage.as new file mode 100644 index 0000000..f18cb13 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/splines/CubicCage.as @@ -0,0 +1,89 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Programmed by: Jim Armstrong, Singularity (www.algorithmist.net) and +// ported by the Degrafa team. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.splines{ + + import flash.display.Graphics; + import flash.display.Shape; + + /** + * Helper class used by various spline Geometry. + **/ + public class CubicCage + { + // properties + public var P0X:Number; + public var P1X:Number; + public var P2X:Number; + public var P3X:Number; + public var P0Y:Number; + public var P1Y:Number; + public var P2Y:Number; + public var P3Y:Number; + + public function CubicCage() + { + init(); + } + + public function init():void + { + P0X = 0; + P1X = 0; + P2X = 0; + P3X = 0; + P0Y = 0; + P1Y = 0; + P2Y = 0; + P3Y = 0; + } + + public function toString():String + { + var myStr:String = ""; + myStr += formatPoint(P0X, P0Y); + myStr += formatPoint(P1X, P1Y); + myStr += formatPoint(P2X, P2Y); + myStr += formatPoint(P3X, P3Y); + + return myStr; + } + + // used primarily for visual debugging, allowing each control cage to be drawn to check in- and out-tangents + public function draw(_s:Shape, _c:Number):void + { + var g:Graphics = _s.graphics; + g.lineStyle(0, _c, 100); + g.moveTo(P0X, P0Y); + g.lineTo(P1X, P1Y); + g.lineTo(P2X, P2Y); + g.lineTo(P3X, P3Y); + } + + private function formatPoint(_pX:Number, _pY:Number):String + { + return " ("+ _pX.toString() + "," + _pY.toString() + ") "; + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/splines/IPlottableSpline.as b/Degrafa/com/degrafa/geometry/splines/IPlottableSpline.as new file mode 100644 index 0000000..0b1766b --- /dev/null +++ b/Degrafa/com/degrafa/geometry/splines/IPlottableSpline.as @@ -0,0 +1,34 @@ +// This interface documents the methods that must be implemented by any plottable spline that is not naturally +// subdivided into constituents that fit into the Degrafa command stack, such as natural cubic spline, Catmull-Rom +// spline, parameteric spline, etc. This is used by the spline->Bezier conversion utility. +package com.degrafa.geometry.splines +{ + public interface IPlottableSpline + { + // return type of spline - cartesian (y as a function of x) or parameteric (x and y as functions of t in [0,1]) + function get type():String; + + // return the knot collection as a simple collection of Objects with 'X' and 'Y' properties representing the point coordinates + function get knots():Array; + + // add a set of control or interpolation points to the spline + function addControlPoint(_x:Number, _y:Number):void; + + // evaluate a cartesian spline at the specified x-coordinate + function eval(_x:Number):Number; + + // evaluate the first derivative of a cartesian spline at the specified x-coordinate + function derivative(_x:Number):Number; + + // evaluate the x- and y-coordinates of a parameteric spline at the specified parameter + function getX(_t:Number):Number; + function getY(_t:Number):Number; + + // evaluate x'(t) and y'(t) of a parameteric spline at the specified parameter + function getXPrime(_t:Number):Number; + function getYPrime(_t:Number):Number; + + // for a parametric spline, return the cubic polynomial coefficients for a specified segment in an Object + function getCoef(_segment:uint):Object; + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/splines/ISpline.as b/Degrafa/com/degrafa/geometry/splines/ISpline.as new file mode 100644 index 0000000..698660a --- /dev/null +++ b/Degrafa/com/degrafa/geometry/splines/ISpline.as @@ -0,0 +1,35 @@ +package com.degrafa.geometry.splines +{ + import com.degrafa.core.collections.GraphicPointCollection; + + public interface ISpline + { + function get quadApproximation():Array; + function get points():Array; + function get knotCount():int; + function get pointCollection():GraphicPointCollection; + + function set points(value:Array):void; + function set knots(value:Object):void; + + // return a quad Bezier approximation to the spline over the specified interval (cartesian or parameteric) + function approximateInterval(val1:Number, val2:Number):Array; + + // add a single control point (knot) to the spline + function addControlPoint(x:Number,y:Number):void; + + // evaluate a cartesian spline at the specified x-coordinate + function eval(_x:Number):Number; + + // evaluate the first derivative of a cartesian spline at the specified x-coordinate + function derivative(_x:Number):Number; + + // evaluate the x- and y-coordinates of a parameteric spline at the specified parameter + function getX(_t:Number):Number; + function getY(_t:Number):Number; + + // evaluate x'(t) and y'(t) of a parameteric spline at the specified parameter + function getXPrime(_t:Number):Number; + function getYPrime(_t:Number):Number; + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/splines/NaturalCubicSpline.as b/Degrafa/com/degrafa/geometry/splines/NaturalCubicSpline.as new file mode 100644 index 0000000..eb06d6d --- /dev/null +++ b/Degrafa/com/degrafa/geometry/splines/NaturalCubicSpline.as @@ -0,0 +1,77 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// Programmed by: Jim Armstrong, (http://algorithmist.wordpress.com) +// Reference: http://www.algorithmist.net/spline.html +// +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.splines +{ + import com.degrafa.GraphicPoint; + import com.degrafa.IGeometry; + import com.degrafa.IGraphicPoint; + import com.degrafa.core.collections.GraphicPointCollection; + import com.degrafa.geometry.CubicBezier; + import com.degrafa.geometry.Geometry; + + import flash.display.Graphics; + import flash.geom.Point; + import flash.geom.Rectangle; + + import mx.events.PropertyChangeEvent; + + [DefaultProperty("points")] + + [Bindable] + /** + * The Natural Cubic Spline is a version of the natural cubic spline utility that is optimized + * for drawing in the Degrafa command stack. This spline is normally used to interpolate a set + * of x-y data points. y must be a strict function of x; that is, x-coordinates are monotonically + * increasing. The spline is designed to be open. It can be manually closed, but there is no facility + * to adjust tangents to make the closure smooth. + * + * There should be at least three knots specified before drawing the spline. + * + **/ + public class NaturalCubicSpline extends BasicSpline + { + // reference to plottable cubic spline + private var _cubicSpline:PlottableCubicSpline; + + /** + * @description Method: NaturalCubicSpline() - Construct a new NaturalCubicSpline instance + * + * @return Nothing + * + * @since 1.0 + * + */ + public function NaturalCubicSpline( _myPoints:Array=null ) + { + super(_myPoints); + + _cubicSpline = new PlottableCubicSpline(); + super.spline = _cubicSpline; + } + + /** + * evaluate a cartesian spline at the specified x-coordinate + **/ + override public function eval(_x:Number):Number { return _cubicSpline.eval(_x); } + + /** + * evaluate a cartesian spline's first derivative at the specified x-coordinate + **/ + override public function derivative(_x:Number):Number { return _cubicSpline.derivative(_x); } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/splines/PlottableCubicSpline.as b/Degrafa/com/degrafa/geometry/splines/PlottableCubicSpline.as new file mode 100644 index 0000000..ad6621a --- /dev/null +++ b/Degrafa/com/degrafa/geometry/splines/PlottableCubicSpline.as @@ -0,0 +1,114 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Programmed by: Jim Armstrong +// +// This software is derived from source containing the following copyright notice +// +// copyright (c) 2006-2007, Jim Armstrong. All Rights Reserved. +// +// This software program is supplied 'as is' without any warranty, express, implied, +// or otherwise, including without limitation all warranties of merchantability or fitness +// for a particular purpose. Jim Armstrong shall not be liable for any special incidental, or +// consequential damages, including, without limitation, lost revenues, lost profits, or +// loss of prospective economic advantage, resulting from the use or misuse of this software +// program. +// +// Reference: http://www.algorithmist.net/spline.html +// + +package com.degrafa.geometry.splines +{ + import com.degrafa.utilities.math.CubicSpline; + + public class PlottableCubicSpline extends CubicSpline implements IPlottableSpline + { +/** +* PlottableCubicSpline() Construct a new Plottable Cubic Spline instance. +* +* @return nothing. +* +* @since 1.0 +* +*/ + public function PlottableCubicSpline() + { + super(); + } + + // return type of spline - cartesian (y as a function of x) or parameteric (x and y as functions of t in [0,1]) + public function get type():String { return SplineTypeEnum.CARTESIAN; } + + // evaluate the first derivative of a cartesian spline at the specified x-coordinate + public function derivative(_x:Number):Number + { + if( __knots == 0 ) + return NaN; + else if( __knots == 1 ) + return __y[0]; + + if( __invalidate ) + __computeZ(); + + // determine interval + var i:uint = 0; + __delta = _x - __t[0]; + var delta2:Number = __t[1] - _x; + for( var j:uint=__knots-2; j>=0; j-- ) + { + if( _x >= __t[j] ) + { + __delta = _x - __t[j]; + delta2 = __t[j+1] - _x; + i = j; + break; + } + } + + // this can be made more efficient - doing so is left as an exercise - see eq. [3] in the above reference + var h:Number = __h[i]; + var h2:Number = 1/(2.0*h); + var h6:Number = h/6; + + var a:Number = __delta*__delta; + var b:Number = delta2*delta2; + var c:Number = __z[i+1]*h2*a; + c -= __z[i]*h2*b; + c += __hInv[i]*__y[i+1]; + c -= __z[i+1]*h6; + c -= __y[i]*__hInv[i]; + c += h6*__z[i]; + + return c; + } + + // these functions are not required for a cartesian spline and are provided to fully implement the interface + public function getX(_t:Number):Number { return 0; } + public function getY(_t:Number):Number { return 0; } + + // evaluate x'(t) and y'(t) of a parameteric spline at the specified parameter + public function getXPrime(_t:Number):Number { return 0; } + public function getYPrime(_t:Number):Number { return 0; } + + // for a parametric spline, return the cubic polynomial coefficients for a specified segment in an Object + public function getCoef(_segment:uint):Object { return null; } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/splines/QuadData.as b/Degrafa/com/degrafa/geometry/splines/QuadData.as new file mode 100644 index 0000000..501bdfc --- /dev/null +++ b/Degrafa/com/degrafa/geometry/splines/QuadData.as @@ -0,0 +1,50 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// Programmed by: Jim Armstrong, (www.algorithmist.net) +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.splines +{ + /** + * QuadData is a holder class for the minimial number of parameters to define a quadratic Bezier curve. + * It serves as a cache for the creation parameters. + **/ + public class QuadData + { + // properties + public var x0:Number; + public var y0:Number; + public var cx:Number; + public var cy:Number; + public var x1:Number; + public var y1:Number; + + /** + * @description Method: QuadData() - Construct a new QuadData instance + * + * @return Nothing + * + * @since 1.0 + * + */ + public function QuadData(_x0:Number=0, _y0:Number=0, _cx:Number=0, _cy:Number=0, _x1:Number=0, _y1:Number=0) + { + x0 = _x0; + y0 = _y0; + cx = _cx; + cy = _cy; + x1 = _x1; + y1 = _y1; + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/splines/QuadraticHermiteSpline.as b/Degrafa/com/degrafa/geometry/splines/QuadraticHermiteSpline.as new file mode 100644 index 0000000..6dac211 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/splines/QuadraticHermiteSpline.as @@ -0,0 +1,119 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// Programmed by: Jim Armstrong, (http://algorithmist.wordpress.com) +// +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.splines +{ + import com.degrafa.GraphicPoint; + import com.degrafa.IGeometry; + import com.degrafa.IGraphicPoint; + import com.degrafa.core.collections.GraphicPointCollection; + import com.degrafa.geometry.Geometry; + import com.degrafa.utilities.math.QuadHermiteSpline; + + import flash.display.Graphics; + import flash.geom.Point; + import flash.geom.Rectangle; + + import mx.events.PropertyChangeEvent; + + [DefaultProperty("points")] + + [Bindable] + /** + * The QuadraticHermitSpline interpolates a set of knots with quadratic Hermit seggments. The shape of the spline + * is determined by both the interpolation points and a start tangent from the first point. The start tangent may + * be automatically chosen (default) or user selected. The spline has no facllity for automatic closure. It can be manually + * closed, but without continuity. + * + * There should be at least three knots specified before drawing the spline. + * + **/ + public class QuadraticHermiteSpline extends BasicSpline + { + // reference to base quad hermite utility + private var _mySpline:QuadHermiteSpline; + + /** + * CatmullRomSpline Construct a new CatmullRomSpline instance + * + * @return Nothing + * + * @since 1.0 + * + */ + public function QuadraticHermiteSpline( _myPoints:Array=null ) + { + super(_myPoints); + + _mySpline = new QuadHermiteSpline(); + super.spline = _mySpline; + } + +/** + * [set] startTangent Assign the start tangent + * + * @param _tangent:String Comma-delimited string of x- and y-coordinates for the start tangent. Use the actual coordinates, not offsets from the initial knot to + * define the tangent. Use the string "auto" to have the spline automatically select a start tangent. + * + * @return Nothing. + * + * @since 1.0 + * + */ + public function set startTangent(_tangent:String):void + { + if( _tangent.toLowerCase() != "auto" ) + { + var pts:Array = _tangent.split(","); + if( pts.length == 2 ) + { + if( !isNaN(pts[0]) && !isNaN(pts[1]) ) + { + _mySpline.startTangent(pts[0], pts[1]); + } + } + } + } + +/** + * getX return x-coordinate of the quadratic Hermite spline at the specified t-parameter + * + * @return Number x-coordinate of spline at specified parameter + **/ + override public function getX(_t:Number):Number { return _mySpline.getX(_t); } + +/** + * getY y-coordinate of the quadratic Hermite spline at the specified t-parameter + * + * @return Number y-coordinate of spline at specified parameter +**/ + override public function getY(_t:Number):Number { return _mySpline.getY(_t); } + +/** + * getXPrime return dx/dt of quadratic Hermite spline + * + * @return Number x-coordinate of spline first derivative at specified parameter +**/ + override public function getXPrime(_t:Number):Number { return _mySpline.getXPrime(_t); } + +/** + * getYPrime return dy/dt of quadratic Hermite spline + * + * @return Number y-coordinate of spline first derivative at specified parameter +**/ + override public function getYPrime(_t:Number):Number { return _mySpline.getYPrime(_t); } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/splines/QuadraticSpline.as b/Degrafa/com/degrafa/geometry/splines/QuadraticSpline.as new file mode 100644 index 0000000..59d82c4 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/splines/QuadraticSpline.as @@ -0,0 +1,409 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// Programmed by: Jim Armstrong, (www.algorithmist.net) +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.splines +{ + import com.degrafa.GraphicPoint; + import com.degrafa.IGeometry; + import com.degrafa.IGraphicPoint; + import com.degrafa.core.collections.GraphicPointCollection; + import com.degrafa.geometry.CubicBezier; + import com.degrafa.geometry.Geometry; + import com.degrafa.geometry.AdvancedQuadraticBezier; + + import flash.display.Graphics; + import flash.geom.Point; + import flash.geom.Rectangle; + + import mx.events.PropertyChangeEvent; + + [DefaultProperty("points")] + + [Bindable] + /** + * The Quadratic can be used for drawing of a smooth curve that passes through + * the first and last points. Interior points influence the shape of the curve. + * Some shape control is provided with a tension parameter and the spline is + * intended for fast drawing of approximate shapes. Use the cubic Bezier spline + * to fit a smooth curve through an arbitrary number of points. + **/ + public class QuadraticSpline extends Geometry implements IGeometry + { + // count number of points added + private var _count:uint=0; + + // index into a specific quadratic segment or AdvancedQuadraticBezier instance + private var _index:uint; + + // reference to QuadData instance representing each segment + private var _quadratics:Array; + + // min/max t-parameters for mapping non-interpolative tension + private var _tMin:Number; + private var _tMax:Number; + + /** + * @description Method: QuadraticSpline() - Construct a new QuadraticSpline instance + * + * @return Nothing + * + * @since 1.0 + * + */ + public function QuadraticSpline( _myPoints:Array=null ) + { + super(); + + if( _myPoints ) + { + points = _myPoints; + } + + _quadratics = new Array(); + _tension = 0; + _index = 0; + _count = 0; + _autoClose = false; + + _tMin = 0; + _tMax = 1; + } + + /** + * Spline short hand data value. + * + *

    The spline data property expects a list of space seperated points. For example + * "10,20 30,35".

    + * + * @see Geometry#data + * + **/ + override public function set data(value:Object):void + { + // borrowed from BezierSpline + if(super.data != value) + { + super.data = value; + + // parse the string on the space + var pointsArray:Array = value.split(" "); + + // create a temporary point array + var pointArray:Array=[]; + var pointItem:Array; + + // and then create a point struct for each resulting pair eventually throw excemption is not matching properly + var i:int = 0; + var length:int = pointsArray.length; + for (; i< length;i++) + { + pointItem = String(pointsArray[i]).split(","); + + // skip past blank items as there may have been bad formatting in the value string, so make sure it is a length of 2 min + if( pointItem.length == 2 ) + { + pointArray.push(new GraphicPoint(pointItem[0],pointItem[1])); + } + } + + // set the points property + points=pointArray; + } + } + + private var _autoClose:Boolean; + /** + * Specifies if this polyline is to be automatically closed. + **/ + [Inspectable(category="General", enumeration="true,false")] + public function get autoClose():Boolean + { + return _autoClose; + } + public function set autoClose(value:Boolean):void + { + if( _autoClose != value ) + { + _autoClose = value; + invalidated = true; + } + } + + private var _points:GraphicPointCollection; + + [Inspectable(category="General", arrayType="com.degrafa.IGraphicPoint")] + [ArrayElementType("com.degrafa.IGraphicPoint")] + /** + * A array of points that describe this polyline. + **/ + public function get points():Array + { + initPointsCollection(); + return _points.items; + } + public function set points(value:Array):void + { + initPointsCollection(); + _points.items = value; + _count = value.length; + + invalidated = true; + } + + /** + * Access to the Degrafa point collection object for this spline. + **/ + public function get pointCollection():GraphicPointCollection + { + initPointsCollection(); + return _points; + } + + /** + * Initialize the point collection by creating it and adding the event listener. + **/ + private function initPointsCollection():void + { + if( !_points ) + { + _points = new GraphicPointCollection(); + + // add a listener to the collection + if( enableEvents ) + { + _points.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + /** + * tension value controls the general tightness of the spline between points and ranges from zero to one. + **/ + private var _tension:Number; + [Inspectable(category="General", type="Number")] + public function get tension():Number + { + return _tension; + } + + public function set tension(value:Number):void + { + _tension = isNaN(value) ? 1 : value; + _tension = Math.max(0, _tension); + _tension = Math.min(1, _tension); + } + + /** + * Principle event handler for any property changes to a geometry object or it's child objects. + **/ + override protected function propertyChangeHandler(event:PropertyChangeEvent):void + { + invalidated = true; + super.propertyChangeHandler(event); + } + + public function get length():Number + { + return points.length; + } + + /** + * Adds a new point to the quadratic spline. + **/ + public function addControlPoint(x:Number,y:Number):void + { + if( !isNaN(x) && !isNaN(y) ) + { + initPointsCollection(); + + _points.addItem(new GraphicPoint(x,y)); + _count++; + + invalidated = true; + } + } + + /** + * Resets the spline to its original state, that is, no control points and default parameter values + **/ + public function reset():void + { + points.splice(0); + _quadratics.splice(0); + + _tension = 0; + _index = 0; + _count = 0; + _autoClose = false; + invalidated = true; + } + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void + { + // tbd + /*if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + + //default to bounds if no width or height is set + //and we have layout + if(isNaN(_layoutConstraint.width)){ + tempLayoutRect.width = bounds.width; + } + + if(isNaN(_layoutConstraint.height)){ + tempLayoutRect.height = bounds.height; + } + + if(isNaN(_layoutConstraint.x)){ + tempLayoutRect.x = bounds.x; + } + + if(isNaN(_layoutConstraint.y)){ + tempLayoutRect.y = bounds.y; + } + + super.calculateLayout(tempLayoutRect); + + _layoutRectangle = _layoutConstraint.layoutRectangle; + + } + }*/ + } + + // assign AdvancedQuadraticBezier instances for each segment + private function initPoints():void + { + if( !points.length ) + { + return; + } + + if( _count > 2 ) + { + _createQuadControlPoints(); + } + } + + /** + * @inheritDoc + **/ + override public function preDraw():void + { + if( invalidated ) + { + if( _count < 3 ) + { + return; + } + + //init the points + initPoints(); + + _createQuadControlPoints(); + + commandStack.length=0; + + // add a MoveTo at the start of the commandStack rendering chain + commandStack.addMoveTo(points[0].x,points[0].y); + + // The AdvancedQuadraticBezier class will probably not be used in the future. It's useful in the event we need to perform + // advanced operations on individual quadratic segments of the spline. If it is only used for fast drawing, we can store + // the quad. coefficients in an Object and be done with it :) + var q:QuadData = _quadratics[0]; + commandStack.addLineTo(q.x0, q.y0); + + for( var i:uint=0; i<_quadratics.length; ++i ) + { + q = _quadratics[i]; + commandStack.addCurveTo(q.cx, q.cy, q.x1, q.y1); + } + + commandStack.addLineTo(points[_count-1].x, points[_count-1].y); + + invalidated = false; + } + } + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics, rc:Rectangle):void + { + //re init if required + if( invalidated ) + preDraw(); + + // init the layout in this case done after predraw. + if( _layoutConstraint ) + calculateLayout(); + + super.draw( graphics,(rc)? rc:bounds ); + } + + // assign control points for each quadratic segment + private function _createQuadControlPoints():void + { + // first implementation is to verify functionality; will be performance-optimized in a future release + + var l1:uint = points.length-1; + if( autoClose && ((points[0].x != points[l1].x) || (points[0].y != points[l1].y)) ) + { + addControlPoint( points[0].x, points[0].y ); + } + + // always start from a clean set + _quadratics.splice(0); + + var t:Number = _tMin + _tension*(_tMax-_tMin); + var t1:Number = 1.0-t; + var pX:Number = (1-t)*points[0].x + t*points[1].x; + var pY:Number = (1-t)*points[0].y + t*points[1].y; + var qX:Number = (1-t1)*points[1].x + t1*points[2].x; + var qY:Number = (1-t1)*points[1].y + t1*points[2].y; + + var q:QuadData = new QuadData( pX, pY, points[1].x, points[1].y, qX, qY ); + _quadratics[0] = q; + + + if( _count > 3 ) + { + var s:Number = t1; + for( var i:uint=2; i<_count-1; ++i ) + { + pX = qX; + pY = qY; + s = s == t ? t1 : t; + qX = (1-s)*points[i].x + s*points[i+1].x; + qY = (1-s)*points[i].y + s*points[i+1].y; + + q = new QuadData( pX, pY, points[i].x, points[i].y, qX, qY ); + _quadratics[i-1] = q; + } + } + + invalidated = false; + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/splines/SplineTypeEnum.as b/Degrafa/com/degrafa/geometry/splines/SplineTypeEnum.as new file mode 100644 index 0000000..06857c9 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/splines/SplineTypeEnum.as @@ -0,0 +1,40 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Programmed by: Jim Armstrong +// +// SplineTypeEnum is a placeholder for symbolic constants representing the various types of plottable splines +// + +package com.degrafa.geometry.splines +{ + public class SplineTypeEnum + { + public static const CARTESIAN:String = "car"; + public static const PARAMETRIC:String = "par"; + + public function SplineTypeEnum() + { + // empty + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/stencil/Stencil.as b/Degrafa/com/degrafa/geometry/stencil/Stencil.as new file mode 100644 index 0000000..306730d --- /dev/null +++ b/Degrafa/com/degrafa/geometry/stencil/Stencil.as @@ -0,0 +1,232 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.stencil{ + + import com.degrafa.IGeometry; + import com.degrafa.geometry.Geometry; + import com.degrafa.geometry.Path; + import com.degrafa.geometry.Polygon; + import com.degrafa.geometry.command.CommandStack; + + import flash.display.Graphics; + import flash.geom.Rectangle; + import flash.utils.Dictionary; + + [Exclude(name="data", kind="property")] + + [Bindable] + /** + * Base class for Stencil type geometry objects. + **/ + public class Stencil extends Geometry implements IGeometry{ + + public static const POLYGON:int=0; + public static const PATH:int=1; + + public var itemDataDictionary:Dictionary = new Dictionary(); + + public function Stencil(){ + super(); + } + + /** + * adds a new item to the library + **/ + public function addItem(key:String,type:int,data:String):void{ + + _shapeList.push(key); + itemDataDictionary[key] = {id:_shapeList.length,type:type,data:data,originalCommandStack:null,originalBounds:null}; + } + + private var _selectedItem:String; + /** + * the currently loaded item + **/ + public function get selectedItem():String{ + return _selectedItem + } + + private var _selectedIndex:String; + /** + * the currently loaded item + **/ + public function get selectedIndex():String{ + return _selectedIndex + } + + protected var _shapeList:Array = []; + /** + * Stores a string array of item keys. + **/ + public function get shapeList():Array{ + return _shapeList; + } + + private var _type:String; + /** + * Sets the type of object to be rendered. + **/ + public function get type():String{ + return _type; + } + public function set type(value:String):void{ + if(_type != value){ + _type = value; + + loadLibraryItem(); + + invalidated = true; + } + } + + private function loadLibraryItem():void{ + + //set the data + data = itemDataDictionary[type].data; + + //process if command data not already loaded and in the dictionary + if(!itemDataDictionary[type].originalCommandStack){ + //use a switch for later types + switch(itemDataDictionary[type].type){ + case Stencil.POLYGON: + //in the case of poygon we need to split the points into an array + //so explicitly set the data after creation + var tempPolyGon:Polygon = new Polygon(); + tempPolyGon.data = data; + + tempPolyGon.commandStack = new CommandStack(this); + + //Calculate + tempPolyGon.preDraw(); + + //store the processed result so we only have to do it one time + itemDataDictionary[type].originalCommandStack = tempPolyGon.commandStack; + itemDataDictionary[type].originalBounds = tempPolyGon.bounds; + + //clean up + tempPolyGon.points = null; + tempPolyGon = null; + break; + + case Stencil.PATH: + //create new path to aid us in calculation + var tempPath:Path = new Path(data as String); + + tempPath.commandStack = new CommandStack(this); + + //Calculate + tempPath.preDraw(); + //store the processed result so we only have to do it one time + itemDataDictionary[type].originalCommandStack = tempPath.commandStack; + itemDataDictionary[type].originalBounds = tempPath.bounds; + + //clean up + tempPath.segments = null; + tempPath = null; + break; + } + } + } + + /** + * The tight bounds of this element as represented by a Rectangle object. + **/ + override public function get bounds():Rectangle{ + return itemDataDictionary[type].originalBounds; + } + + /** + * @inheritDoc + **/ + override public function preDraw():void{ + + if(!data){return} + + if(invalidated){ + + //set the right command stack + commandStack = itemDataDictionary[type].originalCommandStack; + commandStack.owner = this; + + invalidated = false; + } + + } + + /** + * Performs the specific layout work required by this Geometry. + * @param childBounds the bounds to be layed out. If not specified a rectangle + * of (0,0,1,1) is used. + **/ + override public function calculateLayout(childBounds:Rectangle=null):void{ + if(_layoutConstraint){ + if (_layoutConstraint.invalidated){ + var tempLayoutRect:Rectangle = new Rectangle(0,0,1,1); + + //default to bounds if no width or height is set + //and we have layout + if(isNaN(_layoutConstraint.width)){ + tempLayoutRect.width = bounds.width; + } + + if(isNaN(_layoutConstraint.height)){ + tempLayoutRect.height = bounds.height; + } + + if(isNaN(_layoutConstraint.x)){ + tempLayoutRect.x = bounds.x; + } + + if(isNaN(_layoutConstraint.y)){ + tempLayoutRect.y = bounds.y; + } + + super.calculateLayout(tempLayoutRect); + + _layoutRectangle = _layoutConstraint.layoutRectangle; + + } + } + } + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics,rc:Rectangle):void{ + + //re init if required + if (invalidated) preDraw(); + + //init the layout in this case done after predraw. + if (_layoutConstraint) calculateLayout(); + + super.draw(graphics,(rc)? rc:bounds); + } + + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/text/DegrafaTextFormat.as b/Degrafa/com/degrafa/geometry/text/DegrafaTextFormat.as new file mode 100644 index 0000000..80e2fad --- /dev/null +++ b/Degrafa/com/degrafa/geometry/text/DegrafaTextFormat.as @@ -0,0 +1,247 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.text{ + + import com.degrafa.core.DegrafaObject; + + import flash.text.TextFormat; + + [Bindable] + /** + * DegrafaTextFormat is a convenience object for defining reusable textformat data + * for use with RasterText. It can be defined in mxml and bound to. + **/ + public class DegrafaTextFormat extends DegrafaObject{ + + public var textFormat:TextFormat; + + public function DegrafaTextFormat(){ + textFormat = new TextFormat(); + } + + /** + * The name of the font for text in this text format, as a string. + * + * @see flash.text.TextFormat + **/ + public function set font(value:String):void { + if (textFormat.font != value) { + textFormat.font=value; + } + } + public function get font():String{ + return textFormat.font; + } + + + /** + * Indicates the color of the text. If a fill is assigned, then it will override this setting. + * + * @see flash.text.TextFormat + **/ + public function set color(value:uint):void { + if (textFormat.color != value) { + textFormat.color = value; + } + } + public function get color():uint{ + return textFormat.color as uint; + } + + /** + * Indicates the alignment of the paragraph. Valid values are TextFormatAlign constants. + * + * @see flash.text.TextFormat + **/ + [Inspectable(category="General", enumeration="center,justify,left,right", defaultValue="left")] + public function set align(value:String):void{ + textFormat.align = value; + } + public function get align():String{ + return textFormat.align; + } + + /** + * Indicates the block indentation in pixels. + * + * @see flash.text.TextFormat + **/ + public function set blockIndent(value:Object):void{ + textFormat.blockIndent = value; + } + public function get blockIndent():Object{ + return textFormat.blockIndent; + } + + /** + * Specifies whether the text is boldface. + * + * @see flash.text.TextFormat + **/ + private var _bold:Boolean; + [Inspectable(category="General", enumeration="true,false")] + public function set bold(value:Boolean):void{ + textFormat.bold = value; + } + public function get bold():Boolean{ + return textFormat.bold; + } + + /** + * Indicates that the text is part of a bulleted list. + * + * @see flash.text.TextFormat + **/ + public function set bullet(value:Object):void{ + textFormat.bullet = value; + } + public function get bullet():Object{ + return textFormat.bullet; + } + + /** + * Indicates the indentation from the left margin to the first character in the paragraph. + * + * @see flash.text.TextFormat + **/ + public function set indent(value:Object):void{ + textFormat.indent = value; + } + public function get indent():Object{ + return textFormat.indent; + } + + /** + * Indicates whether text in this text format is italicized. + * + * @see flash.text.TextFormat + **/ + [Inspectable(category="General", enumeration="true,false")] + public function set italic(value:Boolean):void{ + textFormat.italic = value; + } + public function get italic():Boolean{ + return textFormat.italic; + } + + /** + * A Boolean value that indicates whether kerning is enabled (true) or disabled (false). + * + * @see flash.text.TextFormat + **/ + [Inspectable(category="General", enumeration="true,false")] + public function set kerning(value:Boolean):void{ + textFormat.kerning = value; + } + public function get kerning():Boolean{ + return textFormat.kerning; + } + + /** + * An integer representing the amount of vertical space (called leading) between lines. + * + * @see flash.text.TextFormat + **/ + public function set leading(value:Object):void{ + textFormat.leading = value; + } + public function get leading():Object{ + return textFormat.leading; + } + + /** + * The left margin of the paragraph, in pixels. + * + * @see flash.text.TextFormat + **/ + public function set leftMargin(value:Object):void{ + textFormat.leftMargin = value; + } + public function get leftMargin():Object{ + return textFormat.leftMargin; + } + + /** + * A number representing the amount of space that is uniformly distributed between all characters. + * + * @see flash.text.TextFormat + **/ + public function set letterSpacing(value:Object):void{ + textFormat.letterSpacing = value; + } + public function get letterSpacing():Object{ + return textFormat.letterSpacing; + } + + /** + * The right margin of the paragraph, in pixels. + * + * @see flash.text.TextFormat + **/ + public function set rightMargin(value:Object):void{ + textFormat.rightMargin = value; + } + public function get rightMargin():Object{ + return textFormat.rightMargin; + } + + + /** + * The point size of text in this text format. + * + * @see flash.text.TextFormat + **/ + public function set size(value:Object):void{ + textFormat.size = value; + } + public function get size():Object{ + return textFormat.size; + } + + + /** + * Specifies custom tab stops as an array of non-negative integers. + * + * @see flash.text.TextFormat + **/ + public function set tabStops(value:Array):void{ + textFormat.tabStops = value; + } + public function get tabStops():Array{ + return textFormat.tabStops; + } + + /** + * Indicates whether the text that uses this text format is underlined (true) or not (false). + * + * @see flash.text.TextFormat + **/ + [Inspectable(category="General", enumeration="true,false")] + public function set underline(value:Boolean):void{ + textFormat.underline = value; + } + public function get underline():Boolean{ + return textFormat.underline; + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/utilities/ArcUtils.as b/Degrafa/com/degrafa/geometry/utilities/ArcUtils.as new file mode 100644 index 0000000..59952d4 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/utilities/ArcUtils.as @@ -0,0 +1,269 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.utilities{ + + import flash.geom.Point; + import com.degrafa.geometry.command.CommandStack; + + /** + * A helper utility class for drawing arcs. + **/ + public class ArcUtils { + + /** + * Converts a svg arc specification to a Degrafa arc. + **/ + public static 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 = GeometryUtils.degressToRadius(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 = GeometryUtils.radiusToDegress(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 = GeometryUtils.radiusToDegress(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 null; + + } + + /** + * Draws an arc of type "open" only. Accepts an optional x axis rotation value + **/ + public static function drawArc(x:Number,y:Number,startAngle:Number, arc:Number, radius:Number,yRadius:Number,xAxisRotation:Number,commandStack:CommandStack):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; + + // The math requires radians rather than degrees. To convert from degrees + // use the formula (degrees/180)*Math.PI to get radians. + var theta:Number = (segAngle/180)*Math.PI; + + // convert angle startAngle to radians + var angle:Number = (startAngle/180)*Math.PI; + + // find our starting points (ax,ay) relative to the secified x,y + //i.e. the center point + var ax:Number =x-Math.cos(angle)*(radius); + var ay:Number =y-Math.sin(angle)*(yRadius); + + //prepar to rotate each control point and anchor around the svg x-axis + var pivotPoint:Point = new Point(x,y); + var anchorPoint:Point = new Point(0,0); + var controlPoint:Point = new Point(0,0); + + // Draw as 45 degree segments + if (segs>0) + { + var i:int = 0 + // Loop for drawing arc segments + for (; i360) + { + 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; + + // The math requires radians rather than degrees. To convert from degrees + // use the formula (degrees/180)*Math.PI to get radians. + var theta:Number = -(segAngle/180)*Math.PI; + + // convert angle startAngle to radians + var angle:Number = -(startAngle/180)*Math.PI; + + // find our starting points (ax,ay) relative to the secified x,y + ax = x-Math.cos(angle)*radius; + ay = y-Math.sin(angle)*yRadius; + + // Draw as 45 degree segments + if (segs>0) + { + var oldX:Number = x; + var oldY:Number = y; + var cx:Number; + var cy:Number; + var x1:Number; + var y1:Number; + + // Loop for drawing arc segments + for (var i:int = 0; iclosestPointToBezier(). +* +* @since 1.0 +* +*/ + public function get minDistance():Number { return __dMinimum; } + +/** + * Given control and anchor points for a quad Bezier and an x-coordinate between the initial and terminal control points, return the t-parameter(s) at the input x-coordinate + * or -1 if no such parameter exists. +**/ + + public static function tAtX(x0:Number, y0:Number, cx:Number, cy:Number, x1:Number, y1:Number, x:Number):Object + { + // quad. bezier coefficients + var c0X:Number = x0; + var c1X:Number = 2.0*(cx-x0); + var c2X:Number = x0-2.0*cx+x1; + + var c:Number = c0X - x; + var b:Number = c1X; + var a:Number = c2X; + + var d:Number = b*b - 4*a*c; + if( d < 0 ) + { + return {t1:-1, t2:-1}; + } + + if( Math.abs(a) < 0.00000001 ) + { + if( Math.abs(b) < 0.00000001 ) + { + return {t1:-1, t2:-1}; + } + else + { + return{t1:-c/b, t1:-1}; + } + } + + d = Math.sqrt(d); + a = 1/(a + a); + var t0:Number = (d-b)*a; + var t1:Number = (-b-d)*a; + + var result:Object = {t1:-1, t2:-1}; + if( t0 >= 0 && t0 <= 1 ) + result["t1"] = t0; + + if( t1 >= 0 && t1 <=1 ) + { + if( t0 <= 0 && t0 <= 1 ) + result["t2"] = t1; + else + result["t1"] = t1; + } + + return result; + } + +/** +* closestPointToBezier Find the closest point on a quadratic or cubic Bezier curve to an arbitrary point +* +* @param _curve:Geometry reference that must be a quadratic or cubic Bezier3 +* @param _p:Point reference to Point to which the closest point on the Bezier curve is desired +* +* @return Number t-parameter of the closest point on the parametric curve. Returns 0 if inputs are null or not a valid reference to a Bezier curve. +* +* This code is derived from the Graphic Gem, "Solving the Nearest-Point-On-Curve Problem", by P.J. Schneider, published in 'Graphic Gems', +* A.S. Glassner, ed., Academic Press, Boston, 1990, pp. 607-611. +* +* @since 1.0 +* +*/ + public function closestPointToBezier( _curve:Geometry, _p:Point ):Number + { + // Note - until issue is resolved with pointAt() for cubic Beziers, you should always used AdvancedCubicBezier for closest point to a cubic + // Bezier when you need to visually identify the point in an application. + if( _curve == null || _p == null ) + { + return 0; + } + + // tbd - dispatch a warning event in this instance + if( !(_curve is QuadraticBezier) && !(_curve is CubicBezier) ) + { + return 0; + } + + // record distances from point to endpoints + var p:Point = _curve.pointAt(0); + var deltaX:Number = p.x-_p.x; + var deltaY:Number = p.y-_p.y; + var d0:Number = Math.sqrt(deltaX*deltaX + deltaY*deltaY); + + p = _curve.pointAt(1); + deltaX = p.x-_p.x; + deltaY = p.y-_p.y; + var d1:Number = Math.sqrt(deltaX*deltaX + deltaY*deltaY); + + var n:uint = (_curve is QuadraticBezier) ? 2 : 3; // degree of input Bezier curve + + // array of control points + var v:Array = new Array(); + if( n == 2 ) + { + var quad:QuadraticBezier = _curve as QuadraticBezier; + v[0] = new Point(quad.x0, quad.y0); + v[1] = new Point(quad.cx, quad.cy); + v[2] = new Point(quad.x1, quad.y1); + } + else + { + var cubic:CubicBezier = _curve as CubicBezier; + v[0] = new Point(cubic.x0 , cubic.y0 ); + v[1] = new Point(cubic.cx , cubic.cy ); + v[2] = new Point(cubic.cx1, cubic.cy1); + v[3] = new Point(cubic.x1 , cubic.y1 ); + } + + // instaead of power form, convert the function whose zeros are required to Bezier form + var w:Array = toBezierForm(_p, v); + + // Find roots of the Bezier curve with control points stored in 'w' (algorithm is recursive, this is root depth of 0) + var roots:Array = findRoots(w, 2*n-1, 0); + + // compare the candidate distances to the endpoints and declare a winner :) + if( d0 < d1 ) + { + var tMinimum:Number = 0; + __dMinimum = d0; + } + else + { + tMinimum = 1; + __dMinimum = d1; + } + + // tbd - compare 2-norm squared + for( var i:uint=0; i= 0 && t <= 1 ) + { + p = _curve.pointAt(t); + deltaX = p.x - _p.x; + deltaY = p.y - _p.y; + var d:Number = Math.sqrt(deltaX*deltaX + deltaY*deltaY); + + if( d < __dMinimum ) + { + tMinimum = t; + __dMinimum = d; + } + } + } + + // tbd - alternate optima. + return tMinimum; + } + + // compute control points of the polynomial resulting from the inner product of B(t)-P and B'(t), constructing the result as a Bezier + // curve of order 2n-1, where n is the degree of B(t). + private function toBezierForm(_p:Point, _v:Array):Array + { + var row:uint = 0; // row index + var column:uint = 0; // column index + + var c:Array = new Array(); // V(i) - P + var d:Array = new Array(); // V(i+1) - V(i) + var w:Array = new Array(); // control-points for Bezier curve whose zeros represent candidates for closest point to the input parametric curve + + var n:uint = _v.length-1; // degree of B(t) + var degree:uint = 2*n-1; // degree of B(t) . P + + var pX:Number = _p.x; + var pY:Number = _p.y; + + for( var i:uint=0; i<=n; ++i ) + { + var v:Point = _v[i]; + c[i] = new Point(v.x - pX, v.y - pY); + } + + var s:Number = Number(n); + for( i=0; i<=n-1; ++i ) + { + v = _v[i]; + var v1:Point = _v[i+1]; + d[i] = new Point( s*(v1.x-v.x), s*(v1.y-v.y) ); + } + + var cd:Array = new Array(); + + // inner product table + for( row=0; row<=n-1; ++row ) + { + var di:Point = d[row]; + var dX:Number = di.x; + var dY:Number = di.y; + + for( var col:uint=0; col<=n; ++col ) + { + var k:uint = getLinearIndex(n+1, row, col); + cd[k] = dX*c[col].x + dY*c[col].y; + k++; + } + } + + // Bezier is uniform parameterized + var dInv:Number = 1.0/Number(degree); + for( i=0; i<=degree; ++i ) + { + w[i] = new Point(Number(i)*dInv, 0); + } + + // reference to appropriate pre-computed coefficients + var z:Array = n == 3 ? Z_CUBIC : Z_QUAD; + + // accumulate y-coords of the control points along the skew diagonal of the (n-1) x n matrix of c.d and z values + var m:uint = n-1; + for( k=0; k<=n+m; ++k ) + { + var lb:uint = Math.max(0, k-m); + var ub:uint = Math.min(k, n); + for( i=lb; i<=ub; ++i) + { + var j:uint = k - i; + var p:Point = w[i+j]; + var index:uint = getLinearIndex(n+1, j, i); + p.y += cd[index]*z[index]; + w[i+j] = p; + } + } + + return w; + } + + // convert 2D array indices in a k x n matrix to a linear index (this is an interim step ahead of a future implementation optimized for 1D array indexing) + private function getLinearIndex(_n:uint, _row:uint, _col:uint):uint + { + // no range-checking; you break it ... you buy it! + return _row*_n + _col; + } + + // how many times does the Bezier curve cross the horizontal axis - the number of roots is less than or equal to this count + private function crossingCount(_v:Array, _degree:uint):uint + { + var nCrossings:uint = 0; + var sign:int = _v[0].y < 0 ? -1 : 1; + var oldSign:int = sign; + for( var i:int=1; i<=_degree; ++i) + { + sign = _v[i].y < 0 ? -1 : 1; + if( sign != oldSign ) + nCrossings++; + + oldSign = sign; + } + + return nCrossings; + } + + // is the control polygon for a Bezier curve suitably linear for subdivision to terminate? + private function isControlPolygonLinear(_v:Array, _degree:uint):Boolean + { + // Given array of control points, _v, find the distance from each interior control point to line connecting v[0] and v[degree] + + // implicit equation for line connecting first and last control points + var a:Number = _v[0].y - _v[_degree].y; + var b:Number = _v[_degree].x - _v[0].x; + var c:Number = _v[0].x * _v[_degree].y - _v[_degree].x * _v[0].y; + + var abSquared:Number = a*a + b*b; + var distance:Array = new Array(); // Distances from control points to line + + for( var i:uint=1; i<_degree; ++i) + { + // Compute distance from each of the points to that line + distance[i] = a * _v[i].x + b * _v[i].y + c; + if( distance[i] > 0.0 ) + { + distance[i] = (distance[i] * distance[i]) / abSquared; + } + if( distance[i] < 0.0 ) + { + distance[i] = -((distance[i] * distance[i]) / abSquared); + } + } + + // Find the largest distance + var maxDistanceAbove:Number = 0.0; + var maxDistanceBelow:Number = 0.0; + for( i=1; i<_degree; ++i) + { + if( distance[i] < 0.0 ) + { + maxDistanceBelow = Math.min(maxDistanceBelow, distance[i]); + } + if( distance[i] > 0.0 ) + { + maxDistanceAbove = Math.max(maxDistanceAbove, distance[i]); + } + } + + // Implicit equation for zero line + var a1:Number = 0.0; + var b1:Number = 1.0; + var c1:Number = 0.0; + + // Implicit equation for "above" line + var a2:Number = a; + var b2:Number = b; + var c2:Number = c + maxDistanceAbove; + + var det:Number = a1*b2 - a2*b1; + var dInv:Number = 1.0/det; + + var intercept1:Number = (b1*c2 - b2*c1)*dInv; + + // Implicit equation for "below" line + a2 = a; + b2 = b; + c2 = c + maxDistanceBelow; + + var intercept2:Number = (b1*c2 - b2*c1)*dInv; + + // Compute intercepts of bounding box + var leftIntercept:Number = Math.min(intercept1, intercept2); + var rightIntercept:Number = Math.max(intercept1, intercept2); + + var error:Number = 0.5*(rightIntercept-leftIntercept); + + return error < EPSILON; + } + + // compute intersection of line segnet from first to last control point with horizontal axis + private function computeXIntercept(_v:Array, _degree:uint):Number + { + var XNM:Number = _v[_degree].x - _v[0].x; + var YNM:Number = _v[_degree].y - _v[0].y; + var XMK:Number = _v[0].x; + var YMK:Number = _v[0].y; + + var detInv:Number = - 1.0/YNM; + + return (XNM*YMK - YNM*XMK) * detInv; + } + + // return roots in [0,1] of a polynomial in Bernstein-Bezier form + private function findRoots(_w:Array, _degree:uint, _depth:uint):Array + { + var t:Array = new Array(); // t-values of roots + var m:uint = 2*_degree-1; + + switch( crossingCount(_w, _degree) ) + { + case 0: + return []; + break; + + case 1: + // Unique solution - stop recursion when the tree is deep enough (return 1 solution at midpoint) + if( _depth >= MAX_DEPTH ) + { + t[0] = 0.5*(_w[0].x + _w[m].x); + return t; + } + + if( isControlPolygonLinear(_w, _degree) ) + { + t[0] = computeXIntercept(_w, _degree); + return t; + } + break; + } + + // Otherwise, solve recursively after subdividing control polygon + var left:Array = new Array(); + var right:Array = new Array(); + + // child solutions + + subdivide(_w, 0.5, left, right); + var leftT:Array = findRoots(left, _degree, _depth+1); + var rightT:Array = findRoots(right, _degree, _depth+1); + + // Gather solutions together + for( var i:uint= 0; iArray of Point references, of the left control cage after subdivision are stored +* @param _right:Array reference to an array in which the control points, Array of Point references, of the right control cage after subdivision are stored +* @return nothing +* +* @since 1.0 +* +*/ + public function subdivide( _c:Array, _t:Number, _left:Array, _right:Array ):void + { + var degree:uint = _c.length-1; + var n:uint = degree+1; + var p:Array = _c.slice(); + var t1:Number = 1.0 - _t; + + for( var i:uint=1; i<=degree; ++i ) + { + for( var j:uint=0; j<=degree-i; ++j ) + { + var vertex:Point = new Point(); + var ij:uint = getLinearIndex(n, i, j); + var im1j:uint = getLinearIndex(n, i-1, j); + var im1jp1:uint = getLinearIndex(n, i-1, j+1); + + vertex.x = t1*p[im1j].x + _t*p[im1jp1].x; + vertex.y = t1*p[im1j].y + _t*p[im1jp1].y; + p[ij] = vertex; + } + } + + for( j=0; j<=degree; ++j ) + { + var index:uint = getLinearIndex(n, j, 0); + _left[j] = p[index]; + } + + for( j=0; j<=degree; ++j) + { + index = getLinearIndex(n, degree-j, j); + _right[j] = p[index]; + } + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/geometry/utilities/GeometryUtils.as b/Degrafa/com/degrafa/geometry/utilities/GeometryUtils.as new file mode 100644 index 0000000..97ce732 --- /dev/null +++ b/Degrafa/com/degrafa/geometry/utilities/GeometryUtils.as @@ -0,0 +1,552 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.geometry.utilities{ + + + import com.degrafa.geometry.command.CommandStack; + + import flash.geom.Point; + import flash.geom.Rectangle; + + /** + * A helper utility class for various geometric calculations. + **/ + public class GeometryUtils + { + //dividing by 2 is frequently required + private static var half:Number = 0.5; + /** + * Calculates the barycenter of a quadratic bezier curve. + * + * @param a A number indicating the start axis coordinate. + * @param a A number indicating the control axis coordinate. + * @param a A number indicating the end axis coordinate. + * @param t A number indicating the accuracy. + * + * @return The barycenter of the given points. + **/ + public static function barycenter(a:Number, b:Number, c:Number, t:Number):Number{ + return (1-t)*(1-t)*a + 2*(1-t)*t*b + t*t*c; + } + + /** + * Calculates the perimeter of a quadratic bezier curve + * + * @param x A number indicating the starting x-axis coordinate. + * @param y A number indicating the starting y-axis coordinate. + * @param cx A number indicating the control x-axis coordinate. + * @param cy A number indicating the control y-axis coordinate. + * @param x1 A number indicating the ending x-axis coordinate. + * @param y1 A number indicating the ending y-axis coordinate. + * + * @return The perimeter distance for the bezier curve. + **/ + public static function perimeter(x:Number,y:Number,cx:Number,cy:Number,x1:Number,y1:Number):Number{ + + var oldX:Number = x; + var oldY:Number = y; + var distance:Number = 0; + var posx:Number; + var posy:Number; + var dx:Number; + var dy:Number; + var dist:Number; + + for(var i:Number=0;i<=1;i+=0.001){ + posx = barycenter(x,cx,x1,i); + posy = barycenter(y,cy,y1,i); + dx = Math.abs(posx - oldX); + dy = Math.abs(posy - oldY); + dist = Math.sqrt((dx*dx)+(dy*dy)); + distance+=dist; + oldX = posx; + oldY = posy; + } + + return distance; + } + + /** + * Returns a point on a quadratic bezier curve. + **/ + public static function pointOnQuadraticCurve(t:Number, x:Number,y:Number,cx:Number,cy:Number,x1:Number,y1:Number):Object{ + var v:Number = t / 100; + + return {x:x + v*(2*(1-v)*(cx-x) + v*(x1 - x)),y:y + v*(2*(1-v)*(cy-y) + v*(y1 - y))}; + } + + //optimization:reuse a single Rectangle instance rather than creating new Rectangle instances for the bezierbounds calc + private static var bezBoundsRect:Rectangle = new Rectangle(); + + /** + * Return the tight bounding rectangle for a bezier curve. + * + * @param x A number indicating the starting x-axis coordinate. + * @param y A number indicating the starting y-axis coordinate. + * @param cx A number indicating the control x-axis coordinate. + * @param cy A number indicating the control y-axis coordinate. + * @param x1 A number indicating the ending x-axis coordinate. + * @param y1 A number indicating the ending y-axis coordinate. + * + * @return The bounds rectangle for the bezier curve. + **/ + public static function bezierBounds(x:Number,y:Number,cx:Number,cy:Number,x1:Number,y1:Number):Rectangle{ + + + var t: Number; + + if (x == cx && cx == x1) + { + //vertical line + bezBoundsRect.x = x; + bezBoundsRect.y = Math.min(y, y1); + bezBoundsRect.width = 0.0001; + bezBoundsRect.height = Math.abs(y1 - y); + return bezBoundsRect; + } + if (y == cy && cy == y1) + { + //horizontal line + bezBoundsRect.x = Math.min(x,x1); + bezBoundsRect.y = y; + bezBoundsRect.width = Math.abs(x1 - x); + bezBoundsRect.height = 0.0001; + return bezBoundsRect; + } + //-- yMin + if( y > y1 ){ + if( cy > y1 ){ + bezBoundsRect.y = y1; + } + else{ + t = -( cy - y ) / ( y1 - 2 * cy + y ); + bezBoundsRect.y=( 1 - t ) * ( 1 - t ) * y + 2 * t * ( 1 - t ) * cy + t * t * y1; + } + } + else{ + if( cy > y ){ + bezBoundsRect.y = y; + } + else{ + t = -( cy - y ) / ( y1 - 2 * cy + y ); + bezBoundsRect.y=( 1 - t ) * ( 1 - t ) * y + 2 * t * ( 1 - t ) * cy + t * t * y1; + } + } + + //-- yMax + if( y > y1 ){ + if( cy < y ){ + bezBoundsRect.bottom=y + } + else{ + t = -( cy - y ) / ( y1 - 2 * cy + y ); + bezBoundsRect.bottom=( 1 - t ) * ( 1 - t ) * y + 2 * t * ( 1 - t ) * cy + t * t * y1; + } + } + else{ + if( y1 > cy ){ + bezBoundsRect.bottom = y1; + } + else{ + t = -( cy - y ) / ( y1 - 2 * cy + y ); + bezBoundsRect.bottom =( 1 - t ) * ( 1 - t ) * y + 2 * t * ( 1 - t ) * cy + t * t * y1; + } + } + + //-- xMin + if( x > x1 ){ + if( cx > x1 ){ + bezBoundsRect.x = x1; + } + else{ + t = -( cx - x ) / ( x1 - 2 * cx + x ); + bezBoundsRect.x = ( 1 - t ) * ( 1 - t ) * x + 2 * t * ( 1 - t ) * cx + t * t * x1; + } + } + else{ + if( cx > x ){ + bezBoundsRect.x = x; + } + else{ + t = -( cx - x ) / ( x1 - 2 * cx + x ); + bezBoundsRect.x = ( 1 - t ) * ( 1 - t ) * x + 2 * t * ( 1 - t ) * cx + t * t * x1; + } + } + + //-- xMax + if( x > x1 ){ + if( cx < x ){ + bezBoundsRect.right = x; + } + else{ + t = -( cx - x ) / ( x1 - 2 * cx + x ); + bezBoundsRect.right =( 1 - t ) * ( 1 - t ) * x + 2 * t * ( 1 - t ) * cx + t * t * x1; + } + } + else{ + if( cx < x1 ){ + bezBoundsRect.right = x1; + } + else{ + t = -( cx - x ) / ( x1 - 2 * cx + x ); + bezBoundsRect.right = ( 1 - t ) * ( 1 - t ) * x + 2 * t * ( 1 - t ) * cx + t * t * x1; + } + } + return bezBoundsRect; + + } + + /** + * LineIntersects + * Returns the point of intersection between two lines + * @param p1, p2 (Point) line 1 point struct + * @param p3, p4 (Point) line 2 point struct + * @return Point (Point object of intersection) + */ + + public static function lineIntersects (p1:Point, p2:Point, p3:Point, p4:Point):Point { + 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; + + var intersectPoint:Point = new Point() + + if (!(dx1 || dx2)){ + + intersectPoint.x=0; + intersectPoint.y=0; + + //return NaN; + } + + var m1:Number = (p2.y - y1) / dx1; + var m2:Number = (p3.y - y4) / dx2; + + if (!dx1){ + intersectPoint.x=x1; + intersectPoint.y=m2 * (x1 - x4) + y4; + return intersectPoint; + } + else if (!dx2){ + intersectPoint.x=x4; + intersectPoint.y=m1 * (x4 - x1) + y1; + return intersectPoint; + } + + var xInt:Number = (-m2 * x4 + y4 + m1 * x1 - y1) / (m1 - m2); + var yInt:Number = m1 * (xInt - x1) + y1; + + intersectPoint.x=xInt; + intersectPoint.y=yInt; + + return intersectPoint; + + } + + /** + * MidPoint + * Returns the midpoint Point of 2 Point structures + * @param p1 Point Struc 1 + * @param p2 Point Struc 2 + * @return Point (the midpoint of the 2 points) + */ + public static function midPoint(p1:Point, p2:Point):Point{ + return new Point((p1.x + p2.x)*half,(p1.y + p2.y)*half); + } + + + + /** + * SplitBezier + * Divides a cubic bezier curve into two cubic bezier curve definitions + * + * @param p1 (Point) endpoint 1 + * @param c1 (Point) control point 1 + * @param c2 (Point)control point 2 + * @param p2 (Point) endpoint 2 + * @return Object (object with two cubic bezier definitions, b0 and b1) + */ + public static function splitBezier(p1:Point, c1:Point, c2:Point, p2:Point):Object{ + + var p01:Point = midPoint(p1, c1); + var p12:Point = midPoint(c1, c2); + var p23:Point = midPoint(c2, p2); + var p02:Point = midPoint(p01, p12); + var p13:Point = midPoint(p12, p23); + var p03:Point = midPoint(p02, p13); + + return { b0:{p1:p1, c1:p01, c2:p02, p2:p03}, b1:{p1:p03, c1:p13, c2:p23, p2:p2} }; + + } + + /** + * Round a number a specified number of decimal places. + * + * @param input The number to round. + * @param input The number of deciaml points to round to. + * + * @return The resulting rounded number. + **/ + public static function roundTo(input:Number, digits:Number):Number{ + return Math.round(input*Math.pow(10, digits))/Math.pow(10, digits); + } + + + /** + * Convert Degress to radius. + * + * @param angle A angle value to convert. + * + * @return The resulting number converted to a radius. + **/ + public static function degressToRadius(angle:Number):Number{ + return angle*(Math.PI/180); + } + + /** + * Convert radius to degrees. + * + * @param angle A angle radius to convert. + * + * @return The resulting number converted to degress. + **/ + public static function radiusToDegress(angle:Number):Number{ + return angle*(180/Math.PI); + } + + /** + * Rotate a point by a degrees. + * + * @param point The point to rotate. + * @param degrees A radius to rotate. + * + * @return The transformed Point point object. + **/ + public static function rotatePoint(value:Point,angle:Number):Point{ + var radius:Number = Math.sqrt(Math.pow(value.x, 2)+Math.pow(value.y, 2)); + var angle:Number = Math.atan2(value.y, value.x)+degressToRadius(angle); + return new Point(roundTo(radius*Math.cos(angle), 3), roundTo(radius*Math.sin(angle), 3)); + } + + /** + * Rotate a point around a given center point by degrees. + * + * @param point The point to rotate. + * @param centerPoint the center point that point should be roatated around. + * @param degrees A radius to rotate. + * + * @return The transformed Point point object. + **/ + public static function rotatePointOnCenterPoint(point:Point,centerPoint:Point,degrees:Number):Point{ + var tempReturnPoint:Point = new Point(); + var radians:Number = (degrees/180)*Math.PI; + + tempReturnPoint.x = centerPoint.x + ( Math.cos(radians) * + (point.x - centerPoint.x) - Math.sin(radians) * + (point.y - centerPoint.y)); + + tempReturnPoint.y = centerPoint.y + ( Math.sin(radians) * + (point.x - centerPoint.x) + Math.cos(radians) * + (point.y - centerPoint.y) ) + + return tempReturnPoint; + + } + + //performance/optimization (assumption only:avoid local variable creation in recursive function calls) + //the following external variable references are used to avoid local variable/Number object creation on recursive calls + //they are not required as local variables/new instances by recursion in the cubicToQuadratic function + private static var sx:Number; + private static var sy:Number; + private static var m1:Number; + private static var m2:Number; + private static var m3:Number; + private static var dx:Number; + private static var dy:Number; + private static var dx1:Number; + private static var dx2:Number; + private static var returnResult:Array; + /** + * CubicToQuadratic + *

    Approximates a cubic bezier with as many quadratic bezier segments (n) as required + * to achieve a specified tolerance.

    + * + * @param p1x first endpoint x coord + * @param p1y first endpoint y coord + * @param c1x 1st control point x coord + * @param c1y 1st control point y coord + * @param c2x 2nd control point x coord + * @param c2y 2nd control point y coord + * @param p2x last endpoint x coord + * @param p2y last endpoint y coord + * @param k tolerance (low number = most accurate result) + * @param commandStack will contain the path of quadratic bezier curves that closely approximates the original cubic bezier curve + */ + public static function cubicToQuadratic(p1x:Number,p1y:Number, c1x:Number,c1y:Number, c2x:Number,c2y:Number, p2x:Number,p2y:Number, k:Number, commandStack:CommandStack):void{ + + // find intersection between bezier arms (intersection point calculated as coords sx,xy) + dx1= c1x - p1x; + dx2 = c2x - p2x; + + if ((dx1 < 0? -dx1:dx1) < 1e-5) dx1 = 0; + if ((dx2 < 0? -dx2:dx2) < 1e-5) dx2 = 0; + + if (p1y == p2y && c1y == p1y && c2y == p2y) + { + //horizontal line: store it as a lineTo in commandStack + //todo: consider the possible case where the control points extend beyond the anchor points... (not yet accounted for - need to check SVG standard) + commandStack.addLineTo(p2x, p2y); + return ; + } + if (!dx1 && !dx2 ) + { + var hDiff:Number = p1x - p2x; + if (hDiff < 0) hDiff = -hDiff; + if (!hDiff || hDiff < 1e-5) { + //vertical line: store it as a lineTo in commandStack + //todo: consider the possible case where the control points extend beyond the anchor points... (not yet accounted for - need to check SVG standard) + commandStack.addLineTo(p2x, p2y); + return ; + } else { + dy = p1y - c1y; + if (dy < 0) dy = -dy; + if (!dy || dy < 1e-5) { + //c1 == p1 + dy = p2y - c2y; + if (dy < 0) dy = -dy; + if (!dy || dy < 1e-5) { + //c2==p2,c1==p1 + commandStack.addLineTo(p2x, p2y); + return ; + } else { + //c1==p1, c2!=p2 + sx = c2x; + sy = c2y; + } + }else { + //c1x==p1x , c2x==p2x + dy = p2y - c2y; + if (dy < 0) dy = -dy; + if (!dy || dy < 1e-5) { + //c2==p2, c1!=p1 + sx = c1x; + sy = c1y; + } else { + if ((c1y < p1y && c2y < p2y) || (c1y > p1y && c2y > p2y)) { + sx = (c1x + c2x) / 2; + sy = (c1y + c2y) / 2; + } else { + //the bezier arms are parallel and on opposite sides of the line between the anchors + //let's just force a split as the bezier curve crosses through between the anchors + dx = k; + dy = k; + } + } + } + if (!dx) { + dx = (p1x + p2x + sx * 4 - (c1x + c2x) * 3) * .125; + dy = (p1y + p2y + sy * 4 - (c1y + c2y) * 3) * .125; + } + + } + } + else if (!dx1) { + + sx=p1x; + sy = ((c2y - p2y) / dx2) * (p1x - p2x) + p2y; + dx = (p1x + p2x + sx * 4 - (c1x + c2x) * 3) * .125; + dy = (p1y + p2y + sy * 4 - (c1y + c2y) * 3) * .125; + } + else if (!dx2){ + sx=p2x; + sy=((c1y - p1y) / dx1) * (p2x - p1x) + p1y; + dx = (p1x + p2x + sx * 4 - (c1x + c2x) * 3) * .125; + dy = (p1y + p2y + sy * 4 - (c1y + c2y) * 3) * .125; + } else{ + m1 = (c1y - p1y) / dx1; + m2 = (c2y - p2y) / dx2; + + if ((m1<0?-m1:m1)==(m2<0?-m2:m2)) + { + //edge case: + //bezier arms are parallel, so: are they colinear with anchors? + m3 = ((p2y - p1y) / (p2x - p1x)); + if (m1 == m2 && m3 == m1) { + //if they are colinear with the line between the anchors...its just a line + //todo: consider the possible case where the control points extend beyond the anchor points... (not yet accounted for - need to check SVG standard) + commandStack.addLineTo(p2x, p2y); + return ; + } + //are they on the same side or opposite sides of the line between the anchors? + if ((m1 > m3 && m2 < m3) || (m1 < m3 && m2 > m3)){ + //we can split the curve because the beziers arms are on the same side of the line between the anchors + //and it is therefore reasonable to aim for a quadratic approximation. Use the average of the control points. + sx = (c1x + c2x) / 2; + sy = (c1y + c2y) / 2; + dx = (p1x + p2x + sx * 4 - (c1x + c2x) * 3) * .125; + dy = (p1y + p2y + sy * 4 - (c1y + c2y) * 3) * .125; + } else { + //the bezier arms are parallel and on opposite sides of the line between the anchors + //let's just force a split as the bezier curve crosses through between the anchors + dx = k; + dy = k; + } + } else { + //normal handling + sx = (-m2 * p2x + p2y + m1 * p1x - p1y) / (m1 - m2); + sy = m1 * (sx - p1x) + p1y; + dx = (p1x + p2x + sx * 4 - (c1x + c2x) * 3) * .125; + dy = (p1y + p2y + sy * 4 - (c1y + c2y) * 3) * .125; + } + } + + // split curve if the quadratic isn't close enough + if (dx * dx + dy * dy > k) + { + //dev note:these cannot be static external variables for performance gain, as they are required to maintain previous values on return from recusive execution + var p01x:Number = (p1x + c1x) * .5; + var p01y:Number = (p1y + c1y) * .5; + var p12x:Number= (c1x + c2x) * .5; + var p12y:Number = (c1y + c2y) * .5; + var p23x:Number = (c2x + p2x) * .5; + var p23y:Number = (c2y + p2y) * .5; + var p02x:Number = (p01x + p12x) * .5; + var p02y:Number= (p01y + p12y) * .5; + var p13x:Number= (p12x + p23x ) * .5; + var p13y:Number = (p12y + p23y ) * .5; + var p03x:Number = (p02x + p13x) * .5; + var p03y:Number = (p02y + p13y) * .5; + // recursive call to subdivide curve + cubicToQuadratic (p1x,p1y,p01x,p01y,p02x,p02y, p03x,p03y,k, commandStack); + cubicToQuadratic (p03x,p03y,p13x,p13y,p23x,p23y, p2x,p2y,k, commandStack); + } + else{ + // end recursion by saving points + commandStack.addCurveTo(sx,sy,p2x,p2y); + } + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/paint/BitmapFill.as b/Degrafa/com/degrafa/paint/BitmapFill.as new file mode 100644 index 0000000..72db180 --- /dev/null +++ b/Degrafa/com/degrafa/paint/BitmapFill.as @@ -0,0 +1,790 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.paint{ + + import com.degrafa.core.DegrafaObject; + import com.degrafa.core.IBlend; + import com.degrafa.core.IGraphicsFill; + import com.degrafa.core.ITransformablePaint; + import com.degrafa.core.Measure; + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.geometry.Geometry; + import com.degrafa.IGeometryComposition; + import com.degrafa.transform.ITransform; + + import mx.events.PropertyChangeEventKind; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.DisplayObject; + import flash.display.Graphics; + import flash.display.Shape; + import flash.display.Sprite; + import flash.events.Event; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + + import flash.utils.getDefinitionByName; + import mx.events.PropertyChangeEvent; + import com.degrafa.utilities.external.ExternalBitmapData; + import com.degrafa.utilities.external.ExternalDataAsset; + import com.degrafa.utilities.external.LoadingLocation; + import com.degrafa.utilities.external.ExternalDataPropertyChangeEvent; + import flash.utils.setTimeout; + + [DefaultProperty("source")] + + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("BitmapFill.png")] + + /** + * Used to fill an area on screen with a bitmap or other DisplayObject. + */ + public class BitmapFill extends DegrafaObject implements IGraphicsFill, IBlend,ITransformablePaint{ + + // static constants + public static const NONE:String = "none"; + public static const REPEAT:String = "repeat"; + public static const SPACE:String = "space"; + public static const STRETCH:String = "stretch"; + + // private variables + private var sprite:Sprite; + private var target:DisplayObject; + private var bitmapData:BitmapData; + private var _externalBitmapData:ExternalBitmapData; + private var _loadingLocation:LoadingLocation; + //used in actionscript when a remote load is done + private var instantiationTimer:uint=5; + + public function BitmapFill(source:Object = null,loc:LoadingLocation=null){ + this._loadingLocation = loc; + this.source = source; + + } + + private var _blendMode:String="normal"; + [Inspectable(category="General", enumeration="normal,layer,multiply,screen,lighten,darken,difference,add,subtract,invert,alpha,erase,overlay,hardlight", defaultValue="normal")] + [Bindable(event="propertyChange")] + public function get blendMode():String { + return _blendMode; + } + + public function set blendMode(value:String):void { + if(_blendMode != value){ + + var oldValue:String=_blendMode; + + _blendMode = value; + + //call local helper to dispatch event + initChange("blendMode",oldValue,_blendMode,this); + + } + + } + + /** + * The horizontal origin for the bitmap fill. + * The bitmap fill is offset so that this point appears at the origin. + * Scaling and rotation of the bitmap are performed around this point. + * @default 0 + */ + private var _originX:Number = 0; + [Bindable(event="propertyChange")] + public function get originX():Number { + return _originX; + } + + public function set originX(value:Number):void { + + if(_originX != value){ + + var oldValue:Number=_originX; + + _originX = value; + + //call local helper to dispatch event + initChange("originX",oldValue,_originX,this); + + } + + } + + + /** + * The vertical origin for the bitmap fill. + * The bitmap fill is offset so that this point appears at the origin. + * Scaling and rotation of the bitmap are performed around this point. + * @default 0 + */ + private var _originY:Number = 0; + [Bindable(event="propertyChange")] + public function get originY():Number { + return _originY; + } + public function set originY(value:Number):void { + + if(_originY != value){ + + var oldValue:Number=_originY; + + _originY = value; + + //call local helper to dispatch event + initChange("originY",oldValue,_originY,this); + } + } + + + /** + * How far the bitmap is horizontally offset from the origin. + * This adjustment is performed after rotation and scaling. + * @default 0 + */ + private var _offsetX:Measure = new Measure(); + [Bindable(event="propertyChange")] + public function get offsetX():Number { + return _offsetX.value; + } + + public function set offsetX(value:Number):void { + + if(_offsetX.value != value){ + + var oldValue:Number=value; + + _offsetX.value = value; + + //call local helper to dispatch event + initChange("offsetX",oldValue,_offsetX,this); + + } + + } + + /** + * The unit of measure corresponding to offsetX. + */ + [Bindable(event="propertyChange")] + public function get offsetXUnit():String { return _offsetX.unit; } + public function set offsetXUnit(value:String):void { + if(_offsetX.unit != value) { + initChange("offsetXUnit", _offsetX.unit, _offsetX.unit = value, this); + } + } + + + /** + * How far the bitmap is vertically offset from the origin. + * This adjustment is performed after rotation and scaling. + * @default 0 + */ + private var _offsetY:Measure = new Measure(); + [Bindable(event="propertyChange")] + public function get offsetY():Number { + return _offsetY.value; + } + + public function set offsetY(value:Number):void { + + if(_offsetY.value != value){ + + var oldValue:Number=value; + + _offsetY.value = value; + + //call local helper to dispatch event + initChange("offsetY",oldValue,_offsetY,this); + + } + + } + + /** + * The unit of measure corresponding to offsetY. + */ + public function get offsetYUnit():String { return _offsetY.unit; } + [Bindable(event="propertyChange")] + public function set offsetYUnit(value:String):void { + if(_offsetY.unit != value) { + initChange("offsetYUnit", _offsetY.unit, _offsetY.unit = value, this); + } + } + + private var _repeatX:String = "repeat"; + /** + * How the bitmap repeats horizontally. + * Valid values are "none", "repeat", "space", and "stretch". + * @default "repeat" + */ + [Inspectable(category="General", enumeration="none,repeat,space,stretch")] + [Bindable(event="propertyChange")] + public function get repeatX():String{ + return _repeatX; + } + + public function set repeatX(value:String):void { + if(_repeatX != value){ + + var oldValue:String=value; + + _repeatX = value; + + //call local helper to dispatch event + initChange("repeatX",oldValue,_repeatX,this); + + } + + } + + private var _repeatY:String = "repeat"; + /** + * How the bitmap repeats vertically. + * Valid values are "none", "repeat", "space", and "stretch". + * @default "repeat" + */ + [Inspectable(category = "General", enumeration = "none,repeat,space,stretch")] + [Bindable(event="propertyChange")] + public function get repeatY():String{ + return _repeatY; + } + + public function set repeatY(value:String):void { + if(_repeatY != value){ + + var oldValue:String=value; + + _repeatY = value; + + //call local helper to dispatch event + initChange("repeatY",oldValue,_repeatY,this); + + } + + } + + private var _rotation:Number = 0; + /** + * The number of degrees to rotate the bitmap. + * Valid values range from 0.0 to 360.0. + * @default 0 + */ + [Bindable(event="propertyChange")] + public function get rotation():Number { + return _rotation; + } + + public function set rotation(value:Number):void { + + if(_rotation != value){ + + var oldValue:Number=value; + + _rotation = value; + + //call local helper to dispatch event + initChange("rotation",oldValue,_rotation,this); + + } + + } + + private var _scaleX:Number = 1; + /** + * The percent to horizontally scale the bitmap when filling, from 0.0 to 1.0. + * If 1.0, the bitmap is filled at its natural size. + * @default 1.0 + */ + [Bindable(event="propertyChange")] + public function get scaleX():Number { + return _scaleX; + } + + public function set scaleX(value:Number):void { + + if(_scaleX != value){ + + var oldValue:Number=value; + + _scaleX = value; + + //call local helper to dispatch event + initChange("scaleX",oldValue,_scaleX,this); + + } + + } + + private var _scaleY:Number = 1; + /** + * The percent to vertically scale the bitmap when filling, from 0.0 to 1.0. + * If 1.0, the bitmap is filled at its natural size. + * @default 1.0 + */ + [Bindable(event="propertyChange")] + public function get scaleY():Number { + return _scaleY; + } + + public function set scaleY(value:Number):void { + + if(_scaleY != value){ + + var oldValue:Number=value; + + _scaleY = value; + + //call local helper to dispatch event + initChange("scaleY",oldValue,_scaleY,this); + + } + + } + + private var _smooth:Boolean = false; + /** + * A flag indicating whether to smooth the bitmap data when filling with it. + * @default false + */ + [Inspectable(category = "General", enumeration = "true,false")] + [Bindable(event="propertyChange")] + public function get smooth():Boolean{ + return _smooth; + } + + public function set smooth(value:Boolean):void { + + if(_smooth != value){ + + var oldValue:Boolean=value; + + _smooth = value; + + //call local helper to dispatch event + initChange("smooth",oldValue,_smooth,this); + + } + + } + + //EXTERNAL BITMAP SUPPORT + private var _waiting:Boolean; + /** + * A support property for binding to in the event of an external loading wait. + * permits a simple binding to indicate that the wait is over + */ + [Bindable("externalDataPropertyChange")] + public function get waiting():Boolean + { + return (_waiting==true); + } + public function set waiting(val:Boolean):void + { + if (val != _waiting ) + { + _waiting = val; + //support binding, but don't use propertyChange to avoid Degrafa redraws for no good reason + dispatchEvent(new ExternalDataPropertyChangeEvent(ExternalDataPropertyChangeEvent.EXTERNAL_DATA_PROPERTY_CHANGE, false, false, PropertyChangeEventKind.UPDATE , "waiting", !_waiting, _waiting, this)) + } + } + + /** + * handles the ready state for an ExternalBitmapData as the source of a BitmapFill + * @param evt an ExternalDataAsset.STATUS_READY event + */ + private function externalBitmapHandler(evt:Event):void { + //TODO: consider passing all ExternalBitmapData events through here and redispatching from BitmapFill + switch(evt.type) + { + case ExternalDataAsset.STATUS_READY: + var oldValue:Object = bitmapData; + bitmapData = evt.target.content; + initChange("source", oldValue, bitmapData, this); + waiting = false; + break; + } + } + + /** + * Optional loadingLocation reference. Only relevant when a subsequent source assignment is made as + * a url string. Using a LoadingLocation simplifies management of loading from external domains + * and is required if a crossdomain policy file is not in the default location (web root) and with the default name (crossdomain.xml) + * In actionscript, a loadingLocation assignment MUST precede a change in the url assigned to the source property + * If a LoadingLocation is being used, the url assigned to the source property MUST be relative to the base path + * defined in the LoadingLocation, otherwise loading will fail. + * If a LoadingLocation is NOT used and the source property assignment is an external domain url, then the crossdomain permissions + * must exist in the default location and with the default name crossdomain.xml, otherwise loading will fail. + */ + public function get loadingLocation():LoadingLocation { return _loadingLocation; } + + public function set loadingLocation(value:LoadingLocation):void + { + if (value) _loadingLocation = value; + } + + + /** + * The source used for the bitmap fill. + * The fill can render from various graphical sources, including the following: + * A Bitmap or BitmapData instance. + * A class representing a subclass of DisplayObject. The BitmapFill instantiates the class and creates a bitmap rendering of it. + * An instance of a DisplayObject. The BitmapFill copies it into a Bitmap for filling. + * The name of a subclass of DisplayObject. The BitmapFill loads the class, instantiates it, and creates a bitmap rendering of it. + * An instance of an ExternalBitmapData to be loaded at runtime. + * A url string to either as a relative url (local domain or with a LoadingLocation) or absolute with no LoadingLocation (see loadingLocation property) + **/ + [Bindable(event="propertyChange")] + public function get source():Object { return bitmapData; } + public function set source(value:Object):void { + + var oldValue:Object = bitmapData; + + target = null; + + if (_externalBitmapData) { + _externalBitmapData.removeEventListener(ExternalDataAsset.STATUS_READY, externalBitmapHandler); + _externalBitmapData = null; + } + + if (!value) { + //set to null ? + //todo: evaluate bitmapdata GC handling...*tricky* if fill is used outside defrafa geometry targets + // if (bitmapData) bitmapData.dispose(); + bitmapData = null; + if (oldValue!=null) initChange("source", oldValue, null, this); + return; + } + + if (value is ExternalBitmapData) { + _externalBitmapData = value as ExternalBitmapData; + if (value.content) { + value = value.content; + } else { + value.addEventListener(ExternalDataAsset.STATUS_READY, externalBitmapHandler) + waiting = true; + return; + } + } + + if (value is BitmapData) + { + bitmapData = value as BitmapData; + initChange("source", oldValue, bitmapData, this); + return; + } + //var sprite:DisplayObject; + if (value is Class) + { + //var cls:Class = value as Class; + target = new value(); + //if(target is Bitmap) { + sprite = new Sprite(); + sprite.addChild(target); + //} + } + else if (value is Bitmap) + { + bitmapData = value.bitmapData; + target = value as Bitmap; + } + else if (value is DisplayObject) + { + target = value as DisplayObject; + } + else if (value is String) + { + //is it a class name or an external url? + try { + var cls:Class = Class(getDefinitionByName(value as String)); + } catch (e:Error) + { + //if its not a class name, assume url string for an ExternalBitmapData + //and wait for isInitialized to check/access loadingLocation mxml assignment + //if not isInitialized after 5 ms, assume actionscript instantiation and not mxml (5 ms is arbitrary) + if (!isInitialized && instantiationTimer) { + instantiationTimer--; + setTimeout( + function():void + {source = value }, 1); + + } else { + source = ExternalBitmapData.getUniqueInstance(value as String, _loadingLocation); + } + return; + } + target = new cls(); + } + else + { + //option: + //source = null; + //or: + bitmapData = null; + if (oldValue!=null) initChange("source", oldValue, null, this); + return; + } + //original: if (bitmapData == null && target != null) + if( target != null) + { + //handle displayObjects with zero width and height + if (!target.width || !target.height) + { + //check the bounds and if they're not empty use them. + var tempRect:Rectangle = target.getBounds(target); + + if (!tempRect.isEmpty()) + { + bitmapData = new BitmapData(Math.ceil(tempRect.width), Math.ceil(tempRect.height), true, 0); + bitmapData.draw(target, new Matrix(1, 0, 0, 1, -tempRect.x, -tempRect.y)); + } else bitmapData = null; + } else { + + bitmapData = new BitmapData(target.width, target.height, true, 0); + bitmapData.draw(target); + } + } + + initChange("source", oldValue, bitmapData, this); + } + + + private var _requester:IGeometryComposition; + /** + * reference to the requesting geometry + **/ + public function set requester(value:IGeometryComposition):void + { + _requester = value; + } + + private var _lastRect:Rectangle; + /** + * Provides access to the last rectangle that was relevant for this fill. + */ + public function get lastRectangle():Rectangle { + return (_lastRect)?_lastRect.clone():null; + } + + private var _lastContext:Graphics; + private var _lastArgs:Array = []; + + /** + * Provide access to the lastArgs array + */ + public function get lastArgs():Array { + return _lastArgs; + } + + /** + * Provides quick access to a cached function for restarting the last used fill either in the last used context, or, if a context is provided as an argument, + * then to an alternate context. If no last used context is available then this will do nothing; + */ + public function get restartFunction():Function { + var copy:Array = _lastArgs.concat(); + var last:Graphics = _lastContext; + return function(alternate:Graphics = null):void { + if (alternate) alternate.beginBitmapFill.apply(alternate, copy); + else if (last) last.beginBitmapFill.apply(last,copy); + } + } + + /** + * Begins the bitmap fill. + **/ + public function begin(graphics:Graphics, rc:Rectangle):void { + + if(!bitmapData) { + return; + } + // todo: optimize all this with cacheing + var template:BitmapData = bitmapData; + + var repeat:Boolean = true; + var positionX:Number = 0; + var positionY:Number = 0; + + var matrix:Matrix = new Matrix(); + + + matrix.translate(rc.x, rc.y); + // deal with stretching + if(repeatX == BitmapFill.STRETCH || repeatY == BitmapFill.STRETCH) { + var stretchX:Number = repeatX == STRETCH ? rc.width : template.width; + var stretchY:Number = repeatY == STRETCH ? rc.height : template.height; + if(target) { + target.width = stretchX; + target.height = stretchY; + template = new BitmapData(stretchX, stretchY, true, 0); + // use sprite to render 9-slice Bitmap + if(sprite) { + template.draw(sprite); + } else { + template.draw(target); + } + } else { + matrix.scale(stretchX/template.width, stretchY/template.height); + } + } + + // deal with spacing + if(repeatX == BitmapFill.SPACE || repeatY == BitmapFill.SPACE) { + // todo: account for rounding issues here + var spaceX:Number = repeatX == BitmapFill.SPACE ? Math.round((rc.width % template.width) / int(rc.width/template.width)) : 0; + var spaceY:Number = repeatY == BitmapFill.SPACE ? Math.round((rc.height % template.height) / int(rc.height/template.height)) : 0; + var pattern:BitmapData = new BitmapData(Math.round(spaceX+template.width), Math.round(spaceY+template.height), true, 0); + pattern.copyPixels(template, template.rect, new Point(Math.round(spaceX/2), Math.round(spaceY/2))); + template = pattern; + } + + if(repeatX == BitmapFill.NONE || repeatX == BitmapFill.REPEAT) { + positionX = _offsetX.relativeTo(rc.width-template.width) + } + + if(repeatY == BitmapFill.NONE || repeatY == BitmapFill.REPEAT) { + positionY = _offsetY.relativeTo(rc.height-template.height) + } + + // deal with repeating (or no-repeating rather) + if(repeatX == BitmapFill.NONE || repeatY == BitmapFill.NONE) { + var area:Rectangle = new Rectangle(1, 1, rc.width, rc.height); + var areaMatrix:Matrix = new Matrix(); + + if(repeatX == BitmapFill.NONE) { + area.width = template.width + } else { + areaMatrix.translate(positionX, 0) + positionX = 0; + } + + if(repeatY == BitmapFill.NONE) { + area.height = template.height + } else { + areaMatrix.translate(0, positionY); + positionY = 0; + } + + // repeat onto a shape as needed + var shape:Shape = new Shape(); // todo: cache for performance + shape.graphics.beginBitmapFill(template, areaMatrix); + shape.graphics.drawRect(0, 0, area.width, area.height); + shape.graphics.endFill(); + + // use the shape to create a new template (with transparent edges) + template = new BitmapData(area.width+2, area.height+2, true, 0); + template.draw(shape, new Matrix(1, 0, 0, 1, 1, 1), null, null, area); + + repeat = false; + } + + matrix.translate( -_originX, -_originY); + + matrix.scale(_scaleX, _scaleY); + matrix.rotate(_rotation*(Math.PI/180)); + matrix.translate(positionX, positionY); + + var regPoint:Point; + var transformRequest:ITransform; + var tempmat:Matrix; + //handle layout transforms - only renderLayouts so far + if (_requester && (_requester as Geometry).hasLayout) { + var geom:Geometry = _requester as Geometry; + if (geom._layoutMatrix) matrix.concat( geom._layoutMatrix); + } + if (_transform && ! _transform.isIdentity) { + + tempmat= new Matrix(); + regPoint = _transform.getRegPointForRectangle(rc); + tempmat.translate(-regPoint.x,-regPoint.y); + tempmat.concat(_transform.transformMatrix); + tempmat.translate( regPoint.x,regPoint.y); + matrix.concat(tempmat); + } + if (_requester && ((transformRequest = (_requester as Geometry).transform) || (_requester as Geometry).transformContext)) { + + if (transformRequest) matrix.concat(transformRequest.getTransformFor(_requester)); + else matrix.concat((_requester as Geometry).transformContext); + //remove the requester reference + _requester = null; + } + + _lastArgs.length = 0; + _lastArgs[0] = template; + _lastArgs[1] = matrix; + _lastArgs[2] = repeat; + _lastArgs[3] = smooth; + _lastContext = graphics; + _lastRect = rc; + if (graphics) graphics.beginBitmapFill(template, matrix, repeat, smooth); + } + + /** + * Ends the bitmap fill. + **/ + public function end(graphics:Graphics):void { + graphics.endFill(); + } + + private var _transform:ITransform; + /** + * Defines the transform object that will be used for + * altering this bitmapfill object. + **/ + [Bindable(event="propertyChange")] + public function get transform():ITransform{ + return _transform; + } + public function set transform(value:ITransform):void{ + + if(_transform != value){ + + var oldValue:Object=_transform; + + if(_transform){ + if(_transform.hasEventManager){ + _transform.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + + _transform = value; + + if(enableEvents){ + _transform.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler,false,0,true); + } + //call local helper to dispatch event + initChange("transform", oldValue, _transform, this); + } + + } + + private function propertyChangeHandler(event:PropertyChangeEvent):void + { + dispatchEvent(event); + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/paint/BitmapFill.png b/Degrafa/com/degrafa/paint/BitmapFill.png new file mode 100644 index 0000000..b10ea19 Binary files /dev/null and b/Degrafa/com/degrafa/paint/BitmapFill.png differ diff --git a/Degrafa/com/degrafa/paint/BlendFill.as b/Degrafa/com/degrafa/paint/BlendFill.as new file mode 100644 index 0000000..4889789 --- /dev/null +++ b/Degrafa/com/degrafa/paint/BlendFill.as @@ -0,0 +1,146 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.paint{ + + import com.degrafa.core.DegrafaObject; + import com.degrafa.core.IBlend; + import com.degrafa.core.IGraphicsFill; + import com.degrafa.core.ITransformablePaint; + import com.degrafa.IGeometryComposition; + import flash.display.Graphics; + import flash.geom.Rectangle; + + + import mx.events.PropertyChangeEvent; + import mx.graphics.IFill; + + [DefaultProperty("fill")] + [Bindable(event="propertyChange")] + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("BlendFill.png")] + + /** + * Used to wrap standard IFill objects for use in a ComplexFill. + * The blendMode is only recognized in the context of a ComplexFill. + */ + public class BlendFill extends DegrafaObject implements IGraphicsFill, IBlend{ + + //***************************************** + // Constructor + //***************************************** + + public function BlendFill(fill:IFill = null, blendMode:String = "normal"){ + this.fill = fill; + this.blendMode = blendMode; + + } + + + //************************************** + // Public Properties + //************************************** + private var _blendMode:String="normal"; + /** + * The blendMode used to render this layer in a ComplexFill. + * You may use any constant provided in the flash.display.BlendMode class. + */ + [Inspectable(category="General", enumeration="normal,layer,multiply,screen,lighten,darken,difference,add,subtract,invert,alpha,erase,overlay,hardlight", defaultValue="normal")] + public function get blendMode():String { return _blendMode; } + public function set blendMode(value:String):void { + if(_blendMode != value) { + initChange("blendMode", _blendMode, _blendMode = value, this); + } + } + + private var _fill:IFill; + /** + * The IFill which this BlendFill wraps. + */ + public function get fill():IFill { return _fill; } + public function set fill(value:IFill):void { + if(_fill != value) { + if(_fill is IGraphicsFill) { + (_fill as IGraphicsFill).removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler, false); + } + if(value is IGraphicsFill) { + (value as IGraphicsFill).addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler, false, 0, true); + } + initChange("fill", _fill, _fill = value, this); + } + } + + //interface requirements + public function get restartFunction():Function { return null }; + public function get lastArgs():Array { return null }; + public function get lastRectangle():Rectangle { return null };; + + // + + + private var _requester:IGeometryComposition; + /** + * Reference to the requesting geometry. + **/ + public function set requester(value:IGeometryComposition):void + { + _requester = value; + } + + + //***************************************** + // Public Methods + //***************************************** + /** + * Begins the blend fill. + **/ + public function begin(graphics:Graphics, rectangle:Rectangle):void { + if (fill != null) { + if (fill is ITransformablePaint) (fill as ITransformablePaint).requester = _requester; + fill.begin(graphics, rectangle); + _requester = null; + } + } + + /** + * Ends the blend fill. + **/ + public function end(graphics:Graphics):void { + if (fill != null) { + fill.end(graphics); + } + } + + + //************************************* + // Event Handlers + //************************************* + + private function propertyChangeHandler(event:PropertyChangeEvent):void{ + dispatchEvent(event); + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/paint/BlendFill.png b/Degrafa/com/degrafa/paint/BlendFill.png new file mode 100644 index 0000000..413a7ea Binary files /dev/null and b/Degrafa/com/degrafa/paint/BlendFill.png differ diff --git a/Degrafa/com/degrafa/paint/ComplexFill.as b/Degrafa/com/degrafa/paint/ComplexFill.as new file mode 100644 index 0000000..0801c2e --- /dev/null +++ b/Degrafa/com/degrafa/paint/ComplexFill.as @@ -0,0 +1,332 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.paint{ + + import com.degrafa.core.DegrafaObject; + import com.degrafa.core.IBlend; + import com.degrafa.core.IGraphicsFill; + import com.degrafa.core.ITransformablePaint; + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.geometry.Geometry; + import com.degrafa.IGeometryComposition; + import com.degrafa.transform.ITransform; + import flash.geom.Point; + + import flash.display.BitmapData; + import flash.display.Graphics; + import flash.display.Shape; + import flash.geom.Matrix; + import flash.geom.Rectangle; + + + import mx.events.PropertyChangeEvent; + import mx.graphics.IFill; + + [DefaultProperty("fills")] + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("ComplexFill.png")] + + /** + * Used to render multiple, layered IGraphicsFill objects as a single fill. + * This allows complex background graphics to be rendered with a single drawing pass. + */ + public class ComplexFill extends DegrafaObject implements IGraphicsFill, IBlend, ITransformablePaint{ + + //********************************************* + // Constructor + //********************************************* + + public function ComplexFill(fills:Array = null){ + shape = new Shape(); + this.fills = fills; + + } + + + //************************************ + // Static Methods + //************************************ + + /** + * Combines an IFill object with the target ComplexFill, merging ComplexFills if necessary. + */ + public static function add(value:IFill, target:ComplexFill):void { + // todo: update this to account for events + var complex:ComplexFill = target; + if(complex == null) { + complex = new ComplexFill(); + } + if(complex.fills == null) { + complex.fills = new Array(); + } + if(value is ComplexFill) { + for each(var fill:IFill in (value as ComplexFill).fills) { + complex.fills.push(fill); + complex.refresh(); + } + } else if(value != null) { + complex.fills.push(value); + complex.refresh(); + } + } + + + private var shape:Shape; + private var bitmapData:BitmapData; + + + private var _fills:Array; // property backing var + private var fillsChanged:Boolean; // dirty flag + + + //************************************** + // Public Properties + //************************************** + + private var _blendMode:String="normal"; + /** + * Blend mode effect to use for this fill. + * You may use any constant provided in the flash.display.BlendMode class. + **/ + [Inspectable(category="General", enumeration="normal,layer,multiply,screen,lighten,darken,difference,add,subtract,invert,alpha,erase,overlay,hardlight", defaultValue="normal")] + public function get blendMode():String { return _blendMode; } + public function set blendMode(value:String):void { + if(_blendMode != value) { + initChange("blendMode", _blendMode, _blendMode = value, this); + } + } + + /** + * Array of IGraphicsFill Objects to be rendered + */ + [Inspectable(category="General", arrayType="com.degrafa.core.IGraphicsFill")] + [ArrayElementType("com.degrafa.core.IGraphicsFill")] + public function get fills():Array { return _fills; } + public function set fills(value:Array):void { + if(_fills != value) { + removeFillListeners(_fills); + addFillListeners(_fills = value); + fillsChanged = true; + } + } + + + + private var _requester:IGeometryComposition; + /** + * Reference to the requesting geometry. + **/ + public function set requester(value:IGeometryComposition):void + { + _requester = value; + } + + private var _lastRect:Rectangle; + /** + * Provides access to the last rectangle that was relevant for this fill. + */ + public function get lastRectangle():Rectangle { + return (_lastRect)?_lastRect.clone():null; + } + private var _lastContext:Graphics; + private var _lastArgs:Array = []; + /** + * Provide access to the lastArgs array + */ + public function get lastArgs():Array { + return _lastArgs; + } + /** + * Provides quick access to a cached function for restarting the last used fill either in the last used context, or, if a context is provided as an argument, + * then to an alternate context. If no last used context is available then this will do nothing; + */ + public function get restartFunction():Function { + var copy:Array = _lastArgs.concat(); + var last:Graphics = _lastContext; + return function(alternate:Graphics = null):void { + if (alternate) alternate.beginBitmapFill.apply(alternate, copy); + else if (last) last.beginBitmapFill.apply(last,copy); + } + } + + + //********************************************* + // Public Methods + //********************************************* + /** + * Begins the complex fill. + **/ + public function begin(graphics:Graphics, rc:Rectangle):void { + // todo: optimize with more cacheing + if(rc.width > 0 && rc.height > 0 && _fills != null && _fills.length > 0) { + if (_fills.length == 1) { // short cut + if (_fills[0] is ITransformablePaint) (_fills[0] as ITransformablePaint).requester = _requester; + (_fills[0] as IFill ).begin(graphics, rc); + } else { + var matrix:Matrix = new Matrix(1, 0, 0, 1, rc.x*-1, rc.y*-1); + if(fillsChanged || bitmapData == null || Math.ceil(rc.width) != bitmapData.width || Math.ceil(rc.height) != bitmapData.height) { // cacheing + bitmapData = new BitmapData(Math.ceil(rc.width), Math.ceil(rc.height), true, 0); + var g:Graphics = shape.graphics; + g.clear(); + var lastType:String; + for each(var fill:IFill in _fills) { + if(fill is IBlend) { + if(lastType == "fill") { + bitmapData.draw(shape, matrix,null,null,null,true); + } + g.clear(); + fill.begin(g, rc); + g.drawRect(rc.x, rc.y, rc.width, rc.height); + fill.end(g); + bitmapData.draw(shape, matrix, null, (fill as IBlend).blendMode,null,true); + lastType = "blend"; + } else { + fill.begin(g, rc); + g.drawRect(rc.x, rc.y, rc.width, rc.height); + fill.end(g); + lastType = "fill"; + } + } + + if(lastType == "fill") { + bitmapData.draw(shape, matrix); + } + fillsChanged = false; + } + matrix.invert(); + //handle layout transforms - only renderLayouts so far + if (_requester && (_requester as Geometry).hasLayout) { + var geom:Geometry = _requester as Geometry; + if (geom._layoutMatrix) matrix.concat( geom._layoutMatrix); + } + + if (_transform && ! _transform.isIdentity) { + var regPoint:Point ; + var tempmat:Matrix = new Matrix(); + regPoint = _transform.getRegPointForRectangle(rc); + tempmat.translate(-regPoint.x,-regPoint.y); + tempmat.concat(_transform.transformMatrix); + tempmat.translate( regPoint.x,regPoint.y); + matrix.concat(tempmat); + } + + var transformRequest:ITransform; + if (_requester && ((transformRequest = (_requester as Geometry).transform) || (_requester as Geometry).transformContext)) { + if (transformRequest) matrix.concat(transformRequest.getTransformFor(_requester)); + else matrix.concat((_requester as Geometry).transformContext); + //remove the requester reference + _requester = null; + } + + _lastArgs.length = 0; + _lastArgs[0] = bitmapData; + _lastArgs[1] = matrix; + _lastRect = rc; + if (graphics) graphics.beginBitmapFill(bitmapData, matrix); + } + } + } + + /** + * Ends the complex fill. + **/ + public function end(graphics:Graphics):void { + graphics.endFill(); + } + + /** + * Refreshs the complex fill. + **/ + public function refresh():void { + fillsChanged = true; + } + + + protected var _transform:ITransform; + /** + * Defines the transform object that will be used for + * altering this gradientfill object. + **/ + public function get transform():ITransform{ + return _transform; + } + public function set transform(value:ITransform):void{ + + if(_transform != value){ + + var oldValue:Object=_transform; + + if(_transform){ + if(_transform.hasEventManager){ + _transform.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + + _transform = value; + + if(enableEvents){ + _transform.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler,false,0,true); + } + //call local helper to dispatch event + initChange("transform",oldValue,_transform,this); + } + + } + + //******************************************** + // Private Methods + //******************************************** + + private function addFillListeners(fills:Array):void { + var fill:IGraphicsFill; + for each(fill in fills) { + if(fill is IGraphicsFill) { + (fill as IGraphicsFill).addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler, false, 0, true); + } + } + } + + private function removeFillListeners(fills:Array):void { + var fill:IGraphicsFill; + for each(fill in fills) { + if(fill is IGraphicsFill) { + (fill as IGraphicsFill).removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler, false); + } + } + } + + + //****************************************** + // Event Handlers + //****************************************** + + private function propertyChangeHandler(event:PropertyChangeEvent):void{ + refresh(); + dispatchEvent(event); + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/paint/ComplexFill.png b/Degrafa/com/degrafa/paint/ComplexFill.png new file mode 100644 index 0000000..a0ce795 Binary files /dev/null and b/Degrafa/com/degrafa/paint/ComplexFill.png differ diff --git a/Degrafa/com/degrafa/paint/EmptyFill.as b/Degrafa/com/degrafa/paint/EmptyFill.as new file mode 100644 index 0000000..2646058 --- /dev/null +++ b/Degrafa/com/degrafa/paint/EmptyFill.as @@ -0,0 +1,123 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.paint{ + + + import com.degrafa.IGeometryComposition; + import com.degrafa.core.DegrafaObject; + import com.degrafa.core.IGraphicsFill; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("EmptyFill.png")] + + /** + * The EmptyFill class provides a class for explicitly setting an empty Fill. In situations, e.g. where 'derive' is used and you require + * that the local Geometry class does not derive the fill from the derive target, then you must set an EmptyFill explicitly. + * + **/ + public class EmptyFill extends DegrafaObject implements IGraphicsFill{ + + /** + * Constructor. + * + *

    The EmptyFill constructor accepts no arguments

    + * + * @param color A unit or String value indicating the stroke color. + * @param alpha A number indicating the alpha to be used for the fill. + */ + public function EmptyFill(){ + + } + + + + //reference to the requesting geometry + private var _requester:IGeometryComposition; + public function set requester(value:IGeometryComposition):void{ + _requester = value; + } + + + private var _lastRect:Rectangle; + /** + * Provides access to the last rectangle that was relevant for this fill. + */ + public function get lastRectangle():Rectangle { + return _lastRect.clone(); + } + private var _lastContext:Graphics; + private var _lastArgs:Array = []; + + /** + * Provide access to the lastArgs array + */ + public function get lastArgs():Array { + return _lastArgs; + } + + /** + * Provides access to a cached function for restarting the last used fill either it the same context, or , if context is provided as an argument, + * then to an alternate context. + */ + public function get restartFunction():Function { + //var copy:Array = _lastArgs.concat(); + var last:Graphics = _lastContext; + return function(alternate:Graphics = null):void { + if (alternate) alternate.beginFill(0xffffffff); + else if (last) last.beginFill(0xffffffff); + } + } + /** + * Begins the fill for the graphics context. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + public function begin(graphics:Graphics, rc:Rectangle):void{ + + //basically do nothing: force the fill to be empty + _lastContext = graphics; + _lastRect = rc; + if (graphics) graphics.beginFill(0xffffffff); + + } + + /** + * Ends the fill for the graphics context. + * + * @param graphics The current context being drawn to. + **/ + public function end(graphics:Graphics):void{ + //basically do nothing + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/paint/EmptyFill.png b/Degrafa/com/degrafa/paint/EmptyFill.png new file mode 100644 index 0000000..ac99eeb Binary files /dev/null and b/Degrafa/com/degrafa/paint/EmptyFill.png differ diff --git a/Degrafa/com/degrafa/paint/EmptyStroke.as b/Degrafa/com/degrafa/paint/EmptyStroke.as new file mode 100644 index 0000000..f335a8f --- /dev/null +++ b/Degrafa/com/degrafa/paint/EmptyStroke.as @@ -0,0 +1,169 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.paint{ + + import com.degrafa.core.DegrafaObject; + import com.degrafa.core.IGraphicsStroke; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + import mx.events.PropertyChangeEvent; + + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("EmptyStroke.png")] + + [Bindable(event="propertyChange")] + + /** + * The EmptyStroke class provides a class for explicitly setting an empty stroke. In situations, e.g. where 'derive' is used and you require + * that the local Geometry class does not derive the stroke from the derive target, then you must set an EmptyStroke explicitly. + * + **/ + public class EmptyStroke extends DegrafaObject implements IGraphicsStroke { + + /** + * Constructor. + * + *

    The Emptry stroke constructor takes no arguments.

    + * + */ + public function EmptyStroke(){ + + } + + + private var _lastRect:Rectangle; + /** + * Provides access to the last rectangle that was relevant for this fill. + */ + public function get lastRectangle():Rectangle { + return _lastRect.clone(); + } + private var _lastContext:Graphics; + private var _lastArgs:Array = []; + + /** + * Provide access to the lastArgs array + */ + public function get lastArgs():Array { + return _lastArgs; + } + + /** + * Provides access to a cached function for restarting the last used fill either it the same context, or , if context is provided as an argument, + * then to an alternate context. If no + */ + public function get reApplyFunction():Function { + var copy:Array = _lastArgs.concat(); + var last:Graphics = _lastContext; + return function(alternate:Graphics = null, altArgs:Array = null):void { + if (alternate) alternate.lineStyle.apply(alternate, null); + else if (last) last.lineStyle.apply(last, null); + } + } + /** + * In this case, does nothing because there is no stroke. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for stroke bounds. + **/ + public function apply(graphics:Graphics,rc:Rectangle):void{ + + _lastContext = graphics; + _lastRect = rc; + if (graphics) graphics.lineStyle.apply(graphics,null); + + + } + + /* INTERFACE com.degrafa.core.IGraphicsStroke */ + + public function get weight():Number + { + return NaN; + } + + public function set weight(value:Number):void + { + + } + + public function get scaleMode():String + { + return null; + } + + public function set scaleMode(value:String):void + { + + } + + public function get pixelHinting():Boolean + { + return false; + } + + public function set pixelHinting(value:Boolean):void + { + + } + + public function get miterLimit():Number + { + return NaN; + } + + public function set miterLimit(value:Number):void + { + + } + + public function get joints():String + { + return null; + } + + public function set joints(value:String):void + { + + } + + public function get caps():String + { + return null; + } + + public function set caps(value:String):void + { + + } + + + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/paint/EmptyStroke.png b/Degrafa/com/degrafa/paint/EmptyStroke.png new file mode 100644 index 0000000..641bb31 Binary files /dev/null and b/Degrafa/com/degrafa/paint/EmptyStroke.png differ diff --git a/Degrafa/com/degrafa/paint/GradientFillBase.as b/Degrafa/com/degrafa/paint/GradientFillBase.as new file mode 100644 index 0000000..f455f6d --- /dev/null +++ b/Degrafa/com/degrafa/paint/GradientFillBase.as @@ -0,0 +1,489 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.paint{ + + import com.degrafa.core.DegrafaObject; + import com.degrafa.core.collections.GradientStopsCollection; + import com.degrafa.core.ITransformablePaint; + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.geometry.Geometry; + import com.degrafa.IGeometryComposition; + import com.degrafa.transform.ITransform; + + import flash.display.Graphics; + import flash.geom.Matrix; + import flash.geom.Rectangle; + import flash.geom.Point; + import mx.events.PropertyChangeEvent; + + [DefaultProperty("gradientStops")] + + [Bindable(event="propertyChange")] + /** + * The gradient fill class lets you specify a gradient fill. + * This is the base class for other gradient fill types. + * + * @see http://degrafa.com/samples/LinearGradientFill.html + **/ + public class GradientFillBase extends DegrafaObject implements ITransformablePaint{ + + //these are setup in processEntries + protected var _colors:Array = []; + protected var _ratios:Array = []; + protected var _alphas:Array = []; + + //************************************** + // Public Properties + //************************************** + + protected var _gradientStops:GradientStopsCollection; + [Inspectable(category="General", arrayType="com.degrafa.paint.GradientStop")] + [ArrayElementType("com.degrafa.paint.GradientStop")] + /** + * A array of gradient stops that describe this gradient. + **/ + public function get gradientStops():Array{ + initGradientStopsCollection(); + return _gradientStops.items; + } + public function set gradientStops(value:Array):void{ + initGradientStopsCollection(); + _gradientStops.items = value; + } + + /** + * Access to the Degrafa gradient stop collection object for this gradient. + **/ + public function get gradientStopsCollection():GradientStopsCollection{ + initGradientStopsCollection(); + return _gradientStops; + } + + /** + * Initialize the gradient stop collection by creating it and adding an event listener. + **/ + private function initGradientStopsCollection():void{ + if(!_gradientStops){ + _gradientStops = new GradientStopsCollection(); + + //add a listener to the collection + if(enableEvents){ + _gradientStops.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + /** + * Principle event handler for any property changes to a + * gradient stop or it's child objects. + **/ + private function propertyChangeHandler(event:PropertyChangeEvent):void{ + dispatchEvent(event); + } + + protected var _angle:Number; + [Inspectable(category="General")] + /** + * The angle that defines a transition across the context. + * + * @see mx.graphics.LinearGradient + **/ + public function get angle():Number{ + if(!_angle){return 0;} + return _angle; + } + public function set angle(value:Number):void{ + if(_angle != value){ + var oldValue:Number=_angle; + _angle = value; + //call local helper to dispatch event + initChange("angle",oldValue,_angle,this); + } + + } + + protected var _interpolationMethod:String; + [Inspectable(category="General", enumeration="rgb,linearRGB", defaultValue="rgb")] + /** + * A value from the InterpolationMethod class that specifies which interpolation method to use. + * + * @see mx.graphics.LinearGradient + **/ + public function get interpolationMethod():String{ + if(!_interpolationMethod){return "rgb";} + return _interpolationMethod; + } + public function set interpolationMethod(value:String):void{ + if(_interpolationMethod != value){ + var oldValue:String=_interpolationMethod; + _interpolationMethod = value; + //call local helper to dispatch event + initChange("interpolationMethod",oldValue,_interpolationMethod,this); + } + } + + protected var _spreadMethod:String; + [Inspectable(category="General", enumeration="pad,reflect,repeat", defaultValue="pad")] + /** + * A value from the SpreadMethod class that specifies which spread method to use. + * + * @see mx.graphics.LinearGradient + **/ + public function get spreadMethod():String { + if(!_spreadMethod){return "pad";} + return _spreadMethod; + } + public function set spreadMethod(value:String):void{ + if(_spreadMethod != value){ + var oldValue:String=_spreadMethod; + _spreadMethod = value; + //call local helper to dispatch event + initChange("spreadMethod",oldValue,_spreadMethod,this); + } + } + + //************************************** + // IBlend Implementation + //************************************** + protected var _blendMode:String; + public function get blendMode():String { + if(!_blendMode){return null;} + return _blendMode; + } + public function set blendMode(value:String):void { + if(_blendMode != value) { + initChange("blendMode", _blendMode, _blendMode = value, this); + } + } + + protected var _gradientType:String; + [Inspectable(category="General", enumeration="linear,radial", defaultValue="linear")] + /** + * Sets the type of gradient to be applied. + **/ + public function get gradientType():String{ + if(!_gradientType){return "linear";} + return _gradientType; + } + public function set gradientType(value:String):void{ + if(_gradientType != value){ + var oldValue:String=_gradientType; + + _gradientType = value; + + //call local helper to dispatch event + initChange("gradientType",oldValue,_gradientType,this); + } + + } + + protected var _focalPointRatio:Number; + [Inspectable(category="General")] + /** + * Sets the location of the start of a radial fill. + * + * @see mx.graphics.RadialGradient + **/ + public function get focalPointRatio():Number{ + if(!_focalPointRatio){return 0;} + return _focalPointRatio; + } + public function set focalPointRatio(value:Number):void{ + if(_focalPointRatio != value){ + var oldValue:Number=_focalPointRatio; + + _focalPointRatio = value; + + //call local helper to dispatch event + initChange("focalPointRatio",oldValue,_focalPointRatio,this); + } + + } + + protected var _coordType:String = "absolute"; + /** + * Coordinate type to be used for fill bounds, either absolute, or relative to target bounds, or as a ratio to target bounds. + **/ + [Inspectable(category="General", enumeration="absolute,relative,ratio", defaultValue="absolute")] + public function set coordinateType(value:String):void + { + if (value!=_coordType) + { + //call local helper to dispatch event + initChange("coordinateType",_coordType,_coordType = value,this); + } + } + public function get coordinateType():String{ + return _coordType; + } + + + //reference to the requesting geometry + protected var _requester:IGeometryComposition; + public function set requester(value:IGeometryComposition):void + { + _requester = value; + } + + private var _lastRect:Rectangle; + /** + * Provides access to the last rectangle that was relevant for this fill. + */ + public function get lastRectangle():Rectangle { + return _lastRect.clone(); + } + private var _lastContext:Graphics; + private var _lastArgs:Array = []; + /** + * Provide access to the lastArgs array + */ + public function get lastArgs():Array { + return _lastArgs; + } + + /** + * Provides quick access to a cached function for restarting the last used fill either in the last used context, or, if a context is provided as an argument, + * then to an alternate context. If no last used context is available then this will do nothing; + */ + public function get restartFunction():Function { + var copy:Array = _lastArgs.concat(); + var last:Graphics = _lastContext; + return function(alternate:Graphics = null):void { + if (alternate) alternate.beginGradientFill.apply(alternate, copy); + else if (last) last.beginGradientFill.apply(last,copy); + } + } + /** + * Begins the fill for the graphics context. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + public function begin(graphics:Graphics, rc:Rectangle):void{ + var matrix:Matrix; + + //ensure that all defaults are in fact set these are temp until fully tested + if(!_angle){_angle=0;} + if(!_focalPointRatio){_focalPointRatio=0;} + if(!_spreadMethod){_spreadMethod="pad";} + if(!_interpolationMethod){_interpolationMethod="rgb";} + matrix=new Matrix(); + if (rc) + { + + matrix.createGradientBox(rc.width, rc.height, + (_angle/180)*Math.PI, rc.x, rc.y); + var xp:Number = (angle % 90)/90; + var yp:Number = 1 - xp; + processEntries(rc.width * xp + rc.height * yp); + + } + //handle layout transforms + if (_requester && (_requester as Geometry).hasLayout) { + var geom:Geometry = _requester as Geometry; + if (geom._layoutMatrix) matrix.concat( geom._layoutMatrix); + } + + if (_transform && ! _transform.isIdentity) { + var regPoint:Point; + var tempmat:Matrix = new Matrix(); + regPoint = _transform.getRegPointForRectangle(rc); + tempmat.translate(-regPoint.x,-regPoint.y); + tempmat.concat(_transform.transformMatrix); + tempmat.translate( regPoint.x,regPoint.y); + matrix.concat(tempmat); + } + + var transformRequest:ITransform; + if (_requester && ((transformRequest = (_requester as Geometry).transform) || (_requester as Geometry).transformContext)) { + if (transformRequest) matrix.concat(transformRequest.getTransformFor(_requester)); + else matrix.concat((_requester as Geometry).transformContext); + + } + + //handle alpha modification + var csAlpha:Number = CommandStack.currentAlpha; + var _alphas:Array = this._alphas; + if (csAlpha != 1) { + _alphas = _alphas.concat(); + //modify the alphas for the gradient stops: + _alphas.forEach(function(el:*, index:uint, arr:Array):void { arr[index] *= csAlpha }, null); + } + + _lastArgs.length = 0; + _lastArgs[0] = gradientType; + _lastArgs[1] = _colors; + _lastArgs[2] = _alphas; + _lastArgs[3] = _ratios; + _lastArgs[4] = matrix; + _lastArgs[5] = spreadMethod; + _lastArgs[6] = interpolationMethod; + _lastArgs[7] = focalPointRatio; + _lastContext = graphics; + _lastRect = rc; + if (graphics) { + if (_colors.length > 16 && _requester) { + //handle larger stop collections + var len:uint = _colors.length; + var loops:uint = (len -15)/ 12 +1; + //first: + var __colors:Array = _colors.slice(0, 15); + var __alphas:Array = _alphas.slice(0, 15); + var __ratios:Array = _ratios.slice(0, 15); + __colors.push(0); + __ratios.push(_ratios[14]); + __alphas.push(0); + graphics.beginGradientFill(gradientType,__colors,__alphas,__ratios,matrix,spreadMethod,interpolationMethod,focalPointRatio); + Geometry(_requester).commandStack.simpleRender(graphics, rc); + graphics.endFill() + var start:uint = 15; + while (loops--) { + __colors = _colors.slice(start-1, start + 13); + __alphas = _alphas.slice(start-1, start + 13); + __ratios = _ratios.slice(start-1, start + 13); + __colors.unshift(0); + __alphas.unshift(0); + __ratios.unshift(__ratios[0]); + + if (loops) { + __colors.push(0); + __alphas.push(0); + __ratios.push(__ratios[15]) + graphics.beginGradientFill(gradientType,__colors,__alphas,__ratios,matrix,spreadMethod,interpolationMethod,focalPointRatio); + Geometry(_requester).commandStack.simpleRender(graphics, rc); + graphics.endFill(); + } + start += 13; + } + //back to regular draw cycle, with the final arguments + graphics.beginGradientFill(gradientType,__colors,__alphas,__ratios,matrix,spreadMethod,interpolationMethod,focalPointRatio); + + } else { + graphics.beginGradientFill(gradientType, _colors, _alphas, _ratios, matrix, spreadMethod, interpolationMethod, focalPointRatio); + } + } + //remove the requester reference + _requester = null; + } + + /** + * Ends the fill for the graphics context. + * + * @param graphics The current context being drawn to. + **/ + public function end(graphics:Graphics):void{ + graphics.endFill(); + } + + protected var _transform:ITransform; + /** + * Defines the transform object that will be used for + * altering this gradientfill object. + **/ + public function get transform():ITransform{ + return _transform; + } + public function set transform(value:ITransform):void{ + + if(_transform != value){ + + var oldValue:Object=_transform; + + if(_transform){ + if(_transform.hasEventManager){ + _transform.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + + _transform = value; + + if(enableEvents){ + _transform.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler,false,0,true); + } + //call local helper to dispatch event + initChange("transform",oldValue,_transform,this); + } + + } + + + + /** + * Process the gradient stops + **/ + protected function processEntries(length:Number):void{ + _colors = []; + _ratios = []; + _alphas = []; + + if (!_gradientStops || _gradientStops.items.length == 0){return;} + + var ratioConvert:Number = 255; + + var i:int; + + var n:int = _gradientStops.items.length; + for (i = 0; i < n; i++) { + var e:GradientStop = _gradientStops.items[i]; + _colors.push(e.color); + _alphas.push(e.alpha); + if(e.measure != null && e.measure.value >= 0) { + var ratio:Number = e.measure.relativeTo(length)/length; + _ratios.push(Math.min(ratio, 1) * ratioConvert); + } else { + _ratios.push(NaN); + } + } + + if (isNaN(_ratios[0])) + _ratios[0] = 0; + + if (isNaN(_ratios[n - 1])) + _ratios[n - 1] = 255; + + i = 1; + + while (true) { + while (i < n && !isNaN(_ratios[i])) { + i++; + } + + if (i == n) + break; + + var start:int = i - 1; + + while (i < n && isNaN(_ratios[i])) { + i++; + } + + var br:Number = _ratios[start]; + var tr:Number = _ratios[i]; + + for (var j:int = 1; j < i - start; j++) { + _ratios[j] = br + j * (tr - br) / (i - start); + } + } + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/paint/GradientStop.as b/Degrafa/com/degrafa/paint/GradientStop.as new file mode 100644 index 0000000..a302ba9 --- /dev/null +++ b/Degrafa/com/degrafa/paint/GradientStop.as @@ -0,0 +1,230 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.paint{ + + import com.degrafa.core.DegrafaObject; + import com.degrafa.core.Measure; + import com.degrafa.core.utils.ColorUtil; + import com.degrafa.paint.palette.PaletteEntry; + + import mx.events.PropertyChangeEvent; + + + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("GradientStop.png")] + + [Bindable(event="propertyChange")] + /** + * The gradient stop class defines the objects that control + * a transition as part of a gradient fill. + * + * @see mx.graphics.GradientEntry + **/ + public class GradientStop extends DegrafaObject { + + /** + * Constructor. + * + *

    The solid stroke constructor accepts 3 optional arguments that define + * it's rendering color, alpha and weight.

    + * + * @param color A unit value indicating the stop color. + * @param alpha A number indicating the alpha to be used for the stop. + * @param ratio A number indicating where in the graphical element, as a percentage from 0.0 to 1.0, Flex starts the transition to the associated color. + * @param ratioUnit A string indicating the unit of the ratio for the stop. + */ + public function GradientStop(color:Object=0x000000,alpha:Number=1,ratio:Number=-1,ratioUnit:String="ratio"){ + this.color = color; + _alpha = alpha; + _ratio.value = ratio; + _ratio.unit = ratioUnit; + + } + + private var _alpha:Number = 1; + [Inspectable(category="General", defaultValue="1")] + /** + * The transparency of a gradient fill. + * + * @see mx.graphics.GradientEntry + **/ + public function get alpha():Number{ + return _alpha; + } + public function set alpha(value:Number):void{ + if(_alpha != value){ + var oldValue:Number=_alpha; + + _alpha = value; + + //call local helper to dispatch event + initChange("alpha",oldValue,_alpha,this); + } + + } + + private var _color:Object; + [Inspectable(category="General", format="Color",defaultValue="0x000000")] + /** + * The color value for a gradient stop. + * + * @see mx.graphics.GradientEntry + **/ + public function get color():Object{ + if(colorFunction !=null){ + return ColorUtil.resolveColor(colorFunction()); + } + else if(!_color){ + return 0x000000; + } + return _color; + } + public function set color(value:Object):void{ + + //setup for a palette entry if one is passed + if(value is PaletteEntry){ + paletteEntry = value as PaletteEntry; + } + else{ + paletteEntry=null; + } + + value = ColorUtil.resolveColor(value); + if(_color != value){ + var oldValue:uint =_color as uint; + _color= value as uint; + //call local helper to dispatch event + initChange("color",oldValue,_color,this); + } + } + + private var _paletteEntry:PaletteEntry; + private function set paletteEntry(value:PaletteEntry):void{ + if(value){ + if(_paletteEntry !== value){ + //remove old listener is required + if(_paletteEntry){ + if(_paletteEntry.hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE)){ + _paletteEntry.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,onPaletteEntryChange); + } + } + //listen for changes + _paletteEntry = value + if(_paletteEntry.enableEvents){ + _paletteEntry.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,onPaletteEntryChange); + } + } + } + else{ + //clean up + if(_paletteEntry){ + if(_paletteEntry.hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE)){ + _paletteEntry.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,onPaletteEntryChange); + } + _paletteEntry=null; + } + } + } + + //handle the change to the palette entry + private function onPaletteEntryChange(event:PropertyChangeEvent):void{ + if(event.property=="value" && event.kind=="update"){ + color = event.source; + } + } + + protected var _colorFunction:Function; + [Inspectable(category="General")] + /** + * Function that sets the color of the fill. It is executed on + * every draw. + **/ + public function get colorFunction():Function{ + return _colorFunction; + } + public function set colorFunction(value:Function):void{ + if(_colorFunction != value){ // value gets resolved first + var oldValue:Function =_colorFunction as Function; + _colorFunction= value as Function; + //call local helper to dispatch event + initChange("colorFunction",oldValue,_colorFunction,this); + } + } + + + private var _ratio:Measure = new Measure(-1, Measure.RATIO); + [Inspectable(category="General")] + /** + * Where in the graphical element, as a percentage from 0.0 to 1.0, + * Flex starts the transition to the associated color. + * + * @see mx.graphics.GradientEntry + **/ + public function get ratio():Number{ + return _ratio.value; + } + public function set ratio(value:Number):void + { + if(_ratio.value != value){ + var oldValue:Number=_ratio.value; + + _ratio.value = value; + + //call local helper to dispatch event + initChange("ratio",oldValue,_ratio.value,this); + } + + } + + [Inspectable(category="General")] + /** + * Unit of measure for the ratio of the stop. + **/ + public function get ratioUnit():String{ + return _ratio.unit; + } + public function set ratioUnit(value:String):void{ + if(_ratio.unit != value){ + var oldValue:String=_ratio.unit; + + _ratio.unit = value; + + //call local helper to dispatch event + initChange("ratio",oldValue,_ratio.unit,this); + } + + } + + /** + * Returns the current ratio unit value. + **/ + public function get measure():Measure{ + return _ratio; + } + + } + +} diff --git a/Degrafa/com/degrafa/paint/GradientStop.png b/Degrafa/com/degrafa/paint/GradientStop.png new file mode 100644 index 0000000..05c0e7a Binary files /dev/null and b/Degrafa/com/degrafa/paint/GradientStop.png differ diff --git a/Degrafa/com/degrafa/paint/GradientStrokeBase.as b/Degrafa/com/degrafa/paint/GradientStrokeBase.as new file mode 100644 index 0000000..02a6077 --- /dev/null +++ b/Degrafa/com/degrafa/paint/GradientStrokeBase.as @@ -0,0 +1,355 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.paint { + + import com.degrafa.core.IGraphicsStroke; + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.geometry.Geometry; + import com.degrafa.transform.ITransform; + import flash.geom.Point; + + import flash.display.Graphics; + import flash.geom.Matrix; + import flash.geom.Rectangle; + + + [DefaultProperty("gradientStops")] + [Exclude(name="color", kind="property")] + [Exclude(name="alpha", kind="property")] + + [Bindable(event="propertyChange")] + + + /** + * The gradient stroke class lets you specify a gradient filled stroke. + * This is the base class for other gradient stroke types. + * + * @see http://degrafa.com/samples/LinearGradientStroke.html + **/ + public class GradientStrokeBase extends GradientFillBase implements IGraphicsStroke { + + public function GradientStrokeBase(){ + super(); + } + + + protected var _weight:Number; + [Inspectable(category="General", defaultValue=1)] + /** + * The line weight, in pixels. + * + * @see mx.graphics.Stroke + **/ + public function get weight():Number{ + if(!_weight){return 1;} + return _weight; + } + public function set weight(value:Number):void{ + if(_weight != value){ + var oldValue:Number=_weight; + + _weight = value; + + //call local helper to dispatch event + initChange("weight",oldValue,_weight,this); + } + + } + + protected var _degrafaScaling:int; + [Inspectable(category="General", enumeration="true,false", defaultValue="false")] + /** + * Determines whether Degrafa transforms will perform additional stroke weight transforms in accordance with the scaleMode setting on this stroke (or not) to scale a stroke. + * Default is false in which case degrafa transforms do not affect this stroke's weight. + * + * @see scaleMode + **/ + public function get degrafaScaling():Boolean { + return (_degrafaScaling<1) + } + public function set degrafaScaling(value:Boolean):void{ + if((_degrafaScaling>0) != value){ + _degrafaScaling = value?1: -1; + //call local helper to dispatch event + initChange("degrafaScaling",!value,value,this); + } + } + + protected var _scaleMode:String; + [Inspectable(category="General", enumeration="normal,vertical,horizontal,none", defaultValue="normal")] + /** + * Specifies how to scale a stroke. Strokes inside filtered or masked Degrafa geometry objects do not respect this setting for scaling in the native flash renderer + * as they are rasterized before being rendered by Degrafa. + * + * @see mx.graphics.Stroke + **/ + public function get scaleMode():String{ + if(!_scaleMode){return "normal";} + return _scaleMode; + } + public function set scaleMode(value:String):void{ + if(_scaleMode != value){ + var oldValue:String=_scaleMode; + + _scaleMode = value; + + //call local helper to dispatch event + initChange("scaleMode",oldValue,_scaleMode,this); + } + } + + + protected var _pixelHinting:Boolean = false; + [Inspectable(category="General", enumeration="true,false")] + /** + * Specifies whether to hint strokes to full pixels. + * + * @see mx.graphics.Stroke + **/ + public function get pixelHinting():Boolean { + return _pixelHinting; + } + public function set pixelHinting(value:Boolean):void{ + if(_pixelHinting != value){ + var oldValue:Boolean=_pixelHinting; + + _pixelHinting = value; + + //call local helper to dispatch event + initChange("pixelHinting",oldValue,_pixelHinting,this); + } + } + + protected var _miterLimit:Number; + [Inspectable(category="General")] + /** + * Indicates the limit at which a miter is cut off. + * + * @see mx.graphics.Stroke + **/ + public function get miterLimit():Number{ + if(!_miterLimit){return 3;} + return _miterLimit; + } + public function set miterLimit(value:Number):void{ + if(_miterLimit != value){ + var oldValue:Number=_miterLimit; + + _miterLimit = value; + + //call local helper to dispatch event + initChange("miterLimit",oldValue,_miterLimit,this); + } + + } + + protected var _joints:String; + [Inspectable(category="General", enumeration="round,bevel,miter", defaultValue="round")] + /** + * Specifies the type of joint appearance used at angles. + * + * @see mx.graphics.Stroke + **/ + public function get joints():String{ + if(!_joints){return "round";} + return _joints; + } + public function set joints(value:String):void{ + + if(_joints != value){ + var oldValue:String=_joints; + + _joints = value; + + //call local helper to dispatch event + initChange("joints",oldValue,_joints,this); + } + + } + + protected var _caps:String; + [Inspectable(category="General", enumeration="round,square,none", defaultValue="round")] + /** + * Specifies the type of caps at the end of lines. + * + * @see mx.graphics.Stroke + **/ + public function get caps():String{ + if(!_caps){return "round";} + return _caps; + } + public function set caps(value:String):void{ + if(_caps != value){ + var oldValue:String=_caps; + + _caps = value; + + //call local helper to dispatch event + initChange("caps",oldValue,_caps,this); + } + + } + + private var _lastRect:Rectangle; + /** + * Provides access to the last rectangle that was relevant for this fill. + */ + override public function get lastRectangle():Rectangle { + return _lastRect.clone(); + } + private var _lastContext:Graphics; + private var _lastArgs:Array = []; + + /** + * Provide access to the lastArgs array + */ + override public function get lastArgs():Array { + return _lastArgs; + } + + /** + * Provides access to a cached function for reapplying the last used stroke either in the same context, or , if context is provided as an argument, + * to an alternate context. + */ + public function get reApplyFunction():Function { + var copy:Array = [_lastArgs[0].concat(),_lastArgs[1].concat()]; + var last:Graphics = _lastContext; + return function(alternate:Graphics = null,altArgs:Array=null):void { + var local:Array = altArgs?altArgs:copy; + if (alternate) { + alternate.lineStyle.apply(alternate, local[0]); + alternate.lineGradientStyle.apply(alternate, local[1]); + } + else if (last) { + last.lineStyle.apply(last, local[0]); + last.lineGradientStyle.apply(last, local[1]); + } + } + } + + + /** + * Applies the properties to the specified Graphics object. + * + * @see mx.graphics.LinearGradientStroke + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for stroke bounds. + **/ + public function apply(graphics:Graphics,rc:Rectangle):void{ + + //ensure that all defaults are in fact set these are temp until fully tested + if(!_caps){_caps="round";} + if(!_joints){_joints="round";} + if(!_miterLimit){_miterLimit=3;} + if(!_scaleMode){_scaleMode="normal";} + if(!_weight){_weight=1;} + + var matrix:Matrix; + if (rc) { + matrix=new Matrix(); + matrix.createGradientBox(rc.width, rc.height, + (angle/180)*Math.PI, rc.x, rc.y); + var xp:Number = (angle % 90)/90; + var yp:Number = 1 - xp; + processEntries((rc.width)*xp + (rc.height)*yp); + } else { + matrix = null; + } + + + //handle layout transforms - only renderLayouts so far + if (_requester && (_requester as Geometry).hasLayout) { + var geom:Geometry = _requester as Geometry; + if (geom._layoutMatrix) matrix.concat( geom._layoutMatrix); + } + //handle transforms on the gradient stroke + if (_transform && ! _transform.isIdentity) { + var regPoint:Point; + var tempmat:Matrix = new Matrix(); + regPoint = _transform.getRegPointForRectangle(rc); + tempmat.translate(-regPoint.x,-regPoint.y); + tempmat.concat(_transform.transformMatrix); + tempmat.translate( regPoint.x,regPoint.y); + matrix.concat(tempmat); + } + + var transformRequest:ITransform; + var weight:Number = this.weight;; + if (_requester && ((transformRequest = (_requester as Geometry).transform) || (_requester as Geometry).transformContext)) { + if (transformRequest) matrix.concat(transformRequest.getTransformFor(_requester)); + else matrix.concat((_requester as Geometry).transformContext); + //handle degrafa stroke scaling if applicable + if (_degrafaScaling > 0 && scaleMode != "none") { + var m:Matrix = CommandStack.transMatrix; + var s:Number + switch(_scaleMode) { + case "normal": + //discriminant seems to make sense here + s = Math.sqrt(Math.abs(m.a * m.d - m.b * m.c)); + break; + case "vertical": + //this seems to provide the same behaviour as the flash native vertical stroke scaling. Not sure if it makes sense with rotation, but best to keep consistent + s = m.b + m.d; + break; + case "horizontal": + //this seems to provide the same behaviour as the flash native horizontal stroke scaling. Not sure if it makes sense with rotation, but best to keep consistent + s = m.a + m.c; + break; + } + weight *= s; + } + //remove the requester reference + _requester = null; + } + + //handle alpha modification + var csAlpha:Number = CommandStack.currentAlpha; + var _alphas:Array = this._alphas; + if (csAlpha != 1) { + _alphas = _alphas.concat(); + //modify the alphas for the gradient stops: + _alphas.forEach(function(el:*, index:uint, arr:Array):void { arr[index] *= csAlpha }, null); + + } + + //performance gain by not setting the last 3 arguments if + //they are already the default flash values + if(caps=="round" && joints=="round" && miterLimit==3){ + if (graphics) graphics.lineStyle(weight, 0, 1, pixelHinting, scaleMode); + _lastArgs[0] = [weight, 0, 1, pixelHinting, scaleMode]; + _lastArgs[1] = [gradientType, _colors, _alphas, _ratios, matrix, spreadMethod, interpolationMethod, focalPointRatio]; + } + else{ + if (graphics) graphics.lineStyle(weight, 0, 1, pixelHinting, scaleMode, caps, joints, miterLimit); + _lastArgs[0] = [weight, 0, 1, pixelHinting, scaleMode, caps, joints, miterLimit]; + _lastArgs[1] = [gradientType, _colors, _alphas, _ratios, matrix, spreadMethod, interpolationMethod, focalPointRatio]; + } + _lastContext = graphics; + _lastRect = rc; + if (graphics) graphics.lineGradientStyle(gradientType, _colors, _alphas, _ratios, matrix, spreadMethod, interpolationMethod,focalPointRatio); + + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/paint/LinearGradientFill.as b/Degrafa/com/degrafa/paint/LinearGradientFill.as new file mode 100644 index 0000000..25aada0 --- /dev/null +++ b/Degrafa/com/degrafa/paint/LinearGradientFill.as @@ -0,0 +1,207 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.paint{ + + import com.degrafa.core.IGraphicsFill; + import com.degrafa.IGeometryComposition; + import flash.geom.Matrix; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + + [Exclude(name="focalPointRatio", kind="property")] + [Bindable(event="propertyChange")] + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("LinearGradientFill.png")] + + /** + * The linear gradient fill class lets you specify a gradient fill. + * + * @see mx.graphics.LinearGradient + * @see http://samples.degrafa.com/LinearGradientFill/LinearGradientFill.html + **/ + public class LinearGradientFill extends GradientFillBase implements IGraphicsFill { + + public function LinearGradientFill(){ + super(); + super.gradientType = "linear"; + + } + + /** + * The focalPointRatio property is not valide for a LinearGradientFill and + * will be ignored. + **/ + override public function get focalPointRatio():Number{return 0;} + override public function set focalPointRatio(value:Number):void{} + + private var _x:Number; + /** + * The x-axis coordinate of the upper left point of the gradient rectangle. If not specified + * a default value of 0 is used. + **/ + public function get x():Number{ + if(!_x){return 0;} + return _x; + } + public function set x(value:Number):void{ + if(_x != value){ + + var oldValue:Number=_x; + + _x = value; + + //call local helper to dispatch event + initChange("x",oldValue,_x,this); + + } + } + + + private var _y:Number; + /** + * The y-axis coordinate of the upper left point of the gradient rectangle. If not specified + * a default value of 0 is used. + **/ + public function get y():Number{ + if(!_y){return 0;} + return _y; + } + public function set y(value:Number):void{ + if(_y != value){ + + var oldValue:Number=_y; + + _y = value; + + //call local helper to dispatch event + initChange("y",oldValue,_y,this); + + } + } + + + private var _width:Number; + /** + * The width to be used for the gradient rectangle. + **/ + public function get width():Number{ + if(!_width){return 0;} + return _width; + } + public function set width(value:Number):void{ + if(_width != value){ + + var oldValue:Number=_width; + + _width = value; + + //call local helper to dispatch event + initChange("width",oldValue,_width,this); + } + } + + + private var _height:Number; + /** + * The height to be used for the gradient rectangle. + **/ + public function get height():Number{ + if(!_height){return 0;} + return _height; + } + public function set height(value:Number):void{ + if(_height != value){ + + var oldValue:Number=_height; + + _height = value; + + //call local helper to dispatch event + initChange("height",oldValue,_height,this); + + } + } + + + + /** + * Ends the fill for the graphics context. + * + * @param graphics The current context being drawn to. + **/ + override public function end(graphics:Graphics):void + { + super.end(graphics); + } + + + + /** + * Begins the fill for the graphics context. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function begin(graphics:Graphics, rc:Rectangle):void { + if (_x && _y && _width && _height) { + if (_coordType == "relative") super.begin(graphics, new Rectangle(rc.x + x, rc.y + y, width, height)); + else if (_coordType == "ratio") super.begin(graphics, new Rectangle(rc.x + x * rc.width, rc.y + y * rc.height, width * rc.width, height * rc.height)); + else super.begin(graphics, new Rectangle(x, y, width, height)); + } + else if (_width && _height) { + if (_coordType == "relative") super.begin(graphics, new Rectangle(rc.x , rc.y , width, height)); + else if (_coordType == "ratio") super.begin(graphics, new Rectangle(rc.x, rc.y , width * rc.width, height * rc.height)); + else super.begin(graphics, new Rectangle(0, 0, width, height)); + } + else{ + super.begin(graphics,rc); + } + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:LinearGradientFill):void{ + + if (!_x){_x = value.x;} + if (!_y){_y = value.y;} + if (!_width){_width = value.width;} + if (!_height){_height = value.height;} + if (!_spreadMethod){_spreadMethod = value.spreadMethod;} + if (!_angle){_angle = value.angle;} + if (!_blendMode){_blendMode = value.blendMode;} + if (!_interpolationMethod){_interpolationMethod = value.interpolationMethod;} + if (!_spreadMethod){_spreadMethod = value.spreadMethod;} + + if (!_gradientStops && value.gradientStops.length!=0){gradientStops = value.gradientStops}; + + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/paint/LinearGradientFill.png b/Degrafa/com/degrafa/paint/LinearGradientFill.png new file mode 100644 index 0000000..2e3eec6 Binary files /dev/null and b/Degrafa/com/degrafa/paint/LinearGradientFill.png differ diff --git a/Degrafa/com/degrafa/paint/LinearGradientStroke.as b/Degrafa/com/degrafa/paint/LinearGradientStroke.as new file mode 100644 index 0000000..8c6ba69 --- /dev/null +++ b/Degrafa/com/degrafa/paint/LinearGradientStroke.as @@ -0,0 +1,188 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.paint{ + + import flash.display.Graphics; + import flash.geom.Rectangle; + + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("LinearGradientStroke.png")] + + [Exclude(name="focalPointRatio", kind="property")] + [Bindable(event="propertyChange")] + + /** + * The linear gradient stroke class lets you specify a gradient filled stroke. + * + * @see mx.graphics.LinearGradientStroke + * @see http://samples.degrafa.com/LinearGradientStroke/LinearGradientStroke.html + **/ + public class LinearGradientStroke extends GradientStrokeBase { + + public function LinearGradientStroke(){ + super(); + super.gradientType = "linear"; + } + + /** + * The focalPointRatio property is not valide for a LinearGradientStroke + * and will be ignored. + **/ + override public function get focalPointRatio():Number{return 0;} + override public function set focalPointRatio(value:Number):void{} + + private var _x:Number; + /** + * The x-axis coordinate of the upper left point of the grtadient rectangle. If not specified + * a default value of 0 is used. + **/ + public function get x():Number{ + if(!_x){return 0;} + return _x; + } + public function set x(value:Number):void{ + if(_x != value){ + + var oldValue:Number=_x; + + _x = value; + + //call local helper to dispatch event + initChange("x",oldValue,_x,this); + + } + } + + + private var _y:Number; + /** + * The y-axis coordinate of the upper left point of the grtadient rectangle. If not specified + * a default value of 0 is used. + **/ + public function get y():Number{ + if(!_y){return 0;} + return _y; + } + public function set y(value:Number):void{ + if(_y != value){ + var oldValue:Number=_y; + + _y = value; + + //call local helper to dispatch event + initChange("y",oldValue,_y,this); + } + } + + + private var _width:Number; + /** + * The width to be used for the grtadient rectangle. + **/ + public function get width():Number{ + if(!_width){return 0;} + return _width; + } + public function set width(value:Number):void{ + if(_width != value){ + var oldValue:Number=_width; + + _width = value; + + //call local helper to dispatch event + initChange("width",oldValue,_width,this); + } + } + + + private var _height:Number; + /** + * The height to be used for the grtadient rectangle. + **/ + public function get height():Number{ + if(!_height){return 0;} + return _height; + } + public function set height(value:Number):void{ + if(_height != value){ + var oldValue:Number=_height; + _height = value; + //call local helper to dispatch event + initChange("height",oldValue,_height,this); + } + } + + + /** + * Applies the properties to the specified Graphics object. + * + * @see mx.graphics.LinearGradientStroke + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for stroke bounds. + **/ + override public function apply(graphics:Graphics,rc:Rectangle):void{ + if (_x && _y && _width && _height) { + if (_coordType == "relative") super.apply(graphics, new Rectangle(rc.x + x, rc.y + y, width, height)); + else if (_coordType == "ratio") super.apply(graphics, new Rectangle(rc.x + x * rc.width, rc.y + y * rc.height, width * rc.width, height * rc.height)); + else super.apply(graphics, new Rectangle(x, y, width, height)); + } + else if (_width && _height) { + if (_coordType == "relative") super.apply(graphics, new Rectangle(rc.x , rc.y , width, height)); + else if (_coordType == "ratio") super.apply(graphics, new Rectangle(rc.x, rc.y , width * rc.width, height * rc.height)); + else super.apply(graphics, new Rectangle(0, 0, width, height)); + } + else{ + super.apply(graphics,rc); + } + + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:LinearGradientStroke):void{ + + if (!_x){_x = value.x;} + if (!_y){_y = value.y;} + if (!_width){_width = value.width;} + if (!_height){_height = value.height;} + if (!_caps){_caps = value.caps;} + if (!_joints){_joints = value.joints;} + if (!_miterLimit){_miterLimit = value.miterLimit;} + if (!_pixelHinting){_pixelHinting = value.pixelHinting;} + if (!_scaleMode){_scaleMode = value.scaleMode;} + if (!_weight) {_weight = value.weight;} + if (!_angle){_angle = value.angle;} + if (!_interpolationMethod){_interpolationMethod = value.interpolationMethod;} + if (!_gradientStops && value.gradientStops.length != 0) { gradientStops = value.gradientStops }; + if (!_degrafaScaling) {_degrafaScaling = value.degrafaScaling?1:-1} + + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/paint/LinearGradientStroke.png b/Degrafa/com/degrafa/paint/LinearGradientStroke.png new file mode 100644 index 0000000..b7b5ece Binary files /dev/null and b/Degrafa/com/degrafa/paint/LinearGradientStroke.png differ diff --git a/Degrafa/com/degrafa/paint/RadialGradientFill.as b/Degrafa/com/degrafa/paint/RadialGradientFill.as new file mode 100644 index 0000000..95b356d --- /dev/null +++ b/Degrafa/com/degrafa/paint/RadialGradientFill.as @@ -0,0 +1,216 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.paint{ + + import com.degrafa.core.IGraphicsFill; + import com.degrafa.IGeometryComposition; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + + [Bindable(event="propertyChange")] + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("RadialGradientFill.png")] + + /** + * The radial gradient fill class lets you specify a gradient fill that + * radiates out from the center of a graphical element. + * + * @see mx.graphics.RadialGradient + * @see http://samples.degrafa.com/RadialGradientFill/RadialGradientFill.html + **/ + public class RadialGradientFill extends GradientFillBase implements IGraphicsFill { + + public function RadialGradientFill(){ + super(); + super.gradientType = "radial"; + } + + private var _cx:Number; + /** + * The x-axis coordinate of the center of the gradient rectangle. If not specified + * a default value of 0 is used. + **/ + public function get cx():Number{ + if(!_cx){return 0;} + return _cx; + } + public function set cx(value:Number):void{ + if(_cx != value){ + + var oldValue:Number=_cx; + + _cx = value; + + //call local helper to dispatch event + initChange("cx",oldValue,_cx,this); + + } + } + + + private var _cy:Number; + /** + * The y-axis coordinate of the center of the gradient rectangle. If not specified + * a default value of 0 is used. + **/ + public function get cy():Number{ + if(!_cy){return 0;} + return _cy; + } + public function set cy(value:Number):void{ + if(_cy != value){ + var oldValue:Number=_cy; + + _cy = value; + + //call local helper to dispatch event + initChange("cy",oldValue,_cy,this); + } + } + + private var _radiusy:Number; + private var _radius:Number; + private var _ellipse:Boolean; + /** + * The radius of the gradient fill, for a circular radial gradient, otherwise it represents the x radius of an elliptical radial gradient. If not specified a default value of 0 + * is used. + **/ + public function get radius():Number{ + if(!_radius){return 0;} + return _radius; + } + public function set radius(value:Number):void{ + if(_radius != value){ + var oldValue:Number=_radius; + + _radiusy = _radius = value; + _ellipse = false; + //call local helper to dispatch event + initChange("radius",oldValue,_radius,this); + } + } + /** + * The x radius of the gradient fill, before any rotation is applied, for an elliptical radial gradient. If not specified a default value of 0 + * is used. + **/ + public function get radiusX():Number{ + if(!_radius){return 0;} + return _radius; + } + public function set radiusX(value:Number):void + { + if(_radius != value){ + var oldValue:Number=_radius; + + _radius = value; + _ellipse = (_radius!=_radiusy); + //call local helper to dispatch event + initChange("radiusX",oldValue,_radius,this); + } + } + /** + * The y radius of the gradient fill, before any rotation is applied, for an elliptical radial gradient. If not specified a default value of 0 + * is used. + **/ + public function get radiusY():Number{ + if(!_radiusy){return 0;} + return _radiusy; + } + public function set radiusY(value:Number):void + { + if(_radiusy != value){ + var oldValue:Number=_radiusy; + + _radiusy = value; + _ellipse = (_radius!=_radiusy); + //call local helper to dispatch event + initChange("radiusY",oldValue,_radiusy,this); + } + } + + /** + * Ends the fill for the graphics context. + * + * @param graphics The current context being drawn to. + **/ + override public function end(graphics:Graphics):void{ + super.end(graphics); + } + + /** + * Begins the fill for the graphics context. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function begin(graphics:Graphics, rc:Rectangle):void{ + var forceCircle:Number; + if (_cx && _cy && _radius) { + if (_coordType == "relative") super.begin(graphics, new Rectangle(rc.x + cx-radiusX, rc.y + cy-radiusY, radiusX*2, radiusY*2)); + else if (_coordType == "ratio") { + forceCircle = _ellipse? NaN:Math.sqrt(rc.width * rc.width + rc.height * rc.height) / Math.SQRT2; + super.begin(graphics, new Rectangle(rc.x + (cx * rc.width)-radiusX*(_ellipse?rc.width:forceCircle), rc.y + (cy * rc.height)-radiusY*(_ellipse?rc.height:forceCircle), radiusX *2* ((_ellipse? rc.width:forceCircle)), radiusY*2 * (_ellipse? rc.height:(_ellipse? rc.width:forceCircle)))); + } + else super.begin(graphics,new Rectangle(cx-radiusX,cy-radiusY,radiusX*2,radiusY*2)); + } + else if (_radius) { + if (_coordType == "relative") super.begin(graphics, new Rectangle(rc.x -radiusX, rc.y -radiusY, radiusX*2, radiusY*2)); + else if (_coordType == "ratio") { + forceCircle = _ellipse? NaN:Math.sqrt(rc.width * rc.width + rc.height * rc.height) / Math.SQRT2; + super.begin(graphics, new Rectangle(rc.x -radiusX * (_ellipse? rc.width:forceCircle), rc.y -radiusY * (_ellipse? rc.height:forceCircle), radiusX * 2 * (_ellipse? rc.width:forceCircle), radiusY*2 * (_ellipse? rc.height:forceCircle ))); + } + else super.begin(graphics,new Rectangle(0,0,radiusX*2,radiusY*2)); + } + else { + super.begin(graphics,rc); + } + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:RadialGradientFill):void{ + + if (!_cx){_cx = value.cx;} + if (!_cy){_cy = value.cy;} + if (!_radius){_radius = value.radius; } + if (!_radiusy) { _radiusy = value.radiusY; _ellipse = (_radiusy != _radius); } + if (!_spreadMethod){_spreadMethod = value.spreadMethod;} + if (!_angle){_angle = value.angle;} + if (!_blendMode){_blendMode = value.blendMode;} + if (!_interpolationMethod){_interpolationMethod = value.interpolationMethod;} + if (!_spreadMethod){_spreadMethod = value.spreadMethod;} + if (!_focalPointRatio){_focalPointRatio = value.focalPointRatio} + + if (!_gradientStops && value.gradientStops.length!=0){gradientStops = value.gradientStops}; + + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/paint/RadialGradientFill.png b/Degrafa/com/degrafa/paint/RadialGradientFill.png new file mode 100644 index 0000000..d15444c Binary files /dev/null and b/Degrafa/com/degrafa/paint/RadialGradientFill.png differ diff --git a/Degrafa/com/degrafa/paint/RadialGradientStroke.as b/Degrafa/com/degrafa/paint/RadialGradientStroke.as new file mode 100644 index 0000000..ae075ba --- /dev/null +++ b/Degrafa/com/degrafa/paint/RadialGradientStroke.as @@ -0,0 +1,217 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.paint{ + + import com.degrafa.core.ITransformablePaint; + import flash.display.Graphics; + import flash.geom.Rectangle; + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("RadialGradientStroke.png")] + + [Bindable(event="propertyChange")] + + /** + * The radial gradient stroke class lets you specify a gradient stroke that + * radiates out from the center of a graphical element. + * + * @see mx.graphics.RadialGradient + * @see http://samples.degrafa.com/RadialGradientStroke/RadialGradientStroke.html + **/ + public class RadialGradientStroke extends GradientStrokeBase { + + public function RadialGradientStroke(){ + super(); + super.gradientType = "radial"; + + } + + private var _cx:Number; + /** + * The x-axis coordinate of the center of the gradient rectangle. If not specified + * a default value of 0 is used. + **/ + public function get cx():Number{ + if(!_cx){return 0;} + return _cx; + } + public function set cx(value:Number):void{ + if(_cx != value){ + + var oldValue:Number=_cx; + + _cx = value; + + //call local helper to dispatch event + initChange("cx",oldValue,_cx,this); + + } + } + + + private var _cy:Number; + /** + * The y-axis coordinate of the center of the gradient rectangle. If not specified + * a default value of 0 is used. + **/ + public function get cy():Number{ + if(!_cy){return 0;} + return _cy; + } + public function set cy(value:Number):void{ + if(_cy != value){ + + var oldValue:Number=_cy; + + _cy = value; + + //call local helper to dispatch event + initChange("cy",oldValue,_cy,this); + + } + } + + + private var _radiusy:Number; + private var _radius:Number; + private var _ellipse:Boolean; + /** + * The radius of the gradient stroke, for a circular radial gradient, otherwise it represents the x radius of an elliptical radial gradient. If not specified a default value of 0 + * is used. + **/ + public function get radius():Number{ + if(!_radius){return 0;} + return _radius; + } + public function set radius(value:Number):void{ + if(_radius != value){ + var oldValue:Number=_radius; + + _radiusy = _radius = value; + _ellipse = false; + //call local helper to dispatch event + initChange("radius",oldValue,_radius,this); + } + } + /** + * The x radius of the gradient stroke, before any rotation is applied, for an elliptical radial gradient. If not specified a default value of 0 + * is used. + **/ + public function get radiusX():Number{ + if(!_radius){return 0;} + return _radius; + } + public function set radiusX(value:Number):void + { + if(_radius != value){ + var oldValue:Number=_radius; + + _radius = value; + _ellipse = (_radius!=_radiusy); + //call local helper to dispatch event + initChange("radiusX",oldValue,_radius,this); + } + } + /** + * The y radius of the gradient stroke, before any rotation is applied, for an elliptical radial gradient. If not specified a default value of 0 + * is used. + **/ + public function get radiusY():Number{ + if(!_radiusy){return 0;} + return _radiusy; + } + public function set radiusY(value:Number):void + { + if(_radiusy != value){ + var oldValue:Number=_radiusy; + + _radiusy = value; + _ellipse = (_radius!=_radiusy); + //call local helper to dispatch event + initChange("radiusY",oldValue,_radiusy,this); + } + } + + + /** + * Applies the properties to the specified Graphics object. + * + * @see mx.graphics.LinearGradientStroke + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for stroke bounds. + **/ + override public function apply(graphics:Graphics, rc:Rectangle):void { + var forceCircle:Number; + if (_cx && _cy && _radius) { + if (_coordType == "relative") super.apply(graphics, new Rectangle(rc.x + cx-radiusX, rc.y + cy-radiusY, radiusX*2, radiusY*2)); + else if (_coordType == "ratio") { + forceCircle = _ellipse? NaN:Math.sqrt(rc.width * rc.width + rc.height * rc.height) / Math.SQRT2; + super.apply(graphics, new Rectangle(rc.x + (cx * rc.width)-radiusX*(_ellipse?rc.width:forceCircle), rc.y + (cy * rc.height)-radiusY*(_ellipse?rc.height:forceCircle), radiusX *2* ((_ellipse? rc.width:forceCircle)), radiusY*2 * (_ellipse? rc.height:(_ellipse? rc.width:forceCircle)))); + } + else super.apply(graphics,new Rectangle(cx-radiusX,cy-radiusY,radiusX*2,radiusY*2)); + } + else if (_radius) { + if (_coordType == "relative") super.apply(graphics, new Rectangle(rc.x -radiusX, rc.y -radiusY, radiusX*2, radiusY*2)); + else if (_coordType == "ratio") { + forceCircle = _ellipse? NaN:Math.sqrt(rc.width * rc.width + rc.height * rc.height) / Math.SQRT2; + super.apply(graphics, new Rectangle(rc.x -radiusX * (_ellipse? rc.width:forceCircle), rc.y -radiusY * (_ellipse? rc.height:forceCircle), radiusX * 2 * (_ellipse? rc.width:forceCircle), radiusY*2 * (_ellipse? rc.height:forceCircle ))); + } + else super.apply(graphics,new Rectangle(0,0,radiusX*2,radiusY*2)); + } + else { + super.apply(graphics,rc); + } + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:RadialGradientStroke):void{ + + if (!_cx){_cx = value.cx;} + if (!_cy){_cy = value.cy;} + if (!_radius && !value.radiusY){_radius = value.radius; } + if (!_radiusy) { _radiusy = value.radiusY; _ellipse = (_radiusy != _radius); } + if (!_caps){_caps = value.caps;} + if (!_joints){_joints = value.joints;} + if (!_miterLimit){_miterLimit = value.miterLimit;} + if (!_pixelHinting){_pixelHinting = value.pixelHinting;} + if (!_scaleMode){_scaleMode = value.scaleMode;} + if (!_weight) {_weight = value.weight;} + + if (!_angle){_angle = value.angle;} + if (!_interpolationMethod){_interpolationMethod = value.interpolationMethod;} + if (!_focalPointRatio){_focalPointRatio = value.focalPointRatio} + + if (!_gradientStops && value.gradientStops.length != 0) { gradientStops = value.gradientStops }; + if (!_degrafaScaling) {_degrafaScaling = value.degrafaScaling?1:-1} + + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/paint/RadialGradientStroke.png b/Degrafa/com/degrafa/paint/RadialGradientStroke.png new file mode 100644 index 0000000..c1317e7 Binary files /dev/null and b/Degrafa/com/degrafa/paint/RadialGradientStroke.png differ diff --git a/Degrafa/com/degrafa/paint/SolidFill.as b/Degrafa/com/degrafa/paint/SolidFill.as new file mode 100644 index 0000000..bcc6ef0 --- /dev/null +++ b/Degrafa/com/degrafa/paint/SolidFill.as @@ -0,0 +1,258 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.paint{ + + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.IGeometryComposition; + import com.degrafa.core.DegrafaObject; + import com.degrafa.core.IGraphicsFill; + import com.degrafa.core.utils.ColorUtil; + import com.degrafa.paint.palette.PaletteEntry; + + import flash.display.Graphics; + import flash.geom.Rectangle; + + import mx.events.PropertyChangeEvent; + + + [Bindable(event="propertyChange")] + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("SolidFill.png")] + + /** + * Solid fill defines a fill color to be applied to a graphics contex. + * @see http://samples.degrafa.com/SolidFill/SolidFill.html + **/ + public class SolidFill extends DegrafaObject implements IGraphicsFill{ + + /** + * Constructor. + * + *

    The solid fill constructor accepts 2 optional arguments that define + * it's rendering color and alpha.

    + * + * @param color A unit or String value indicating the stroke color. + * @param alpha A number indicating the alpha to be used for the fill. + */ + public function SolidFill(color:Object=null, alpha:Number=NaN){ + this.alpha = alpha; + this.color = color; + } + + protected var _alpha:Number; + [Inspectable(category="General")] + /** + * The transparency of a fill. + * + * @see mx.graphics.Stroke + **/ + public function get alpha():Number{ + if(isNaN(_alpha)){return 1;} + return _alpha; + } + public function set alpha(value:Number):void{ + if(_alpha != value){ + var oldValue:Number=_alpha; + + _alpha = value; + + //call local helper to dispatch event + initChange("alpha",oldValue,_alpha,this); + } + } + + protected var _color:Object; + [Inspectable(category="General", format="Color",defaultValue="0x000000")] + /** + * The fill color. + * This property accepts uint, hexadecimal (including shorthand), + * and color keys as well as comma seperated rgb or cmyk values. + * + **/ + public function get color():Object { + if(colorFunction!=null){ + return ColorUtil.resolveColor(colorFunction()); + } + else if(!_color){ + return 0x000000; + } + return _color; + } + public function set color(value:Object):void{ + + //setup for a palette entry if one is passed + if(value is PaletteEntry){ + paletteEntry = value as PaletteEntry; + } + else{ + paletteEntry=null; + } + + value = ColorUtil.resolveColor(value); + + if(_color != value){ // value gets resolved first + var oldValue:uint =_color as uint; + _color= value as uint; + //call local helper to dispatch event + initChange("color",oldValue,_color,this); + } + } + + private var _paletteEntry:PaletteEntry; + private function set paletteEntry(value:PaletteEntry):void{ + if(value){ + if(_paletteEntry !== value){ + //remove old listener is required + if(_paletteEntry){ + if(_paletteEntry.hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE)){ + _paletteEntry.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,onPaletteEntryChange); + } + } + //listen for changes + _paletteEntry = value + if(_paletteEntry.enableEvents){ + _paletteEntry.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,onPaletteEntryChange); + } + } + } + else{ + //clean up + if(_paletteEntry){ + if(_paletteEntry.hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE)){ + _paletteEntry.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,onPaletteEntryChange); + } + _paletteEntry=null; + } + } + } + + //handle the change to the palette entry + private function onPaletteEntryChange(event:PropertyChangeEvent):void{ + if(event.property=="value" && event.kind=="update"){ + color = event.source; + } + } + + protected var _colorFunction:Function; + [Inspectable(category="General")] + /** + * Function that sets the color of the fill. It is executed on + * every draw. + **/ + public function get colorFunction():Function{ + return _colorFunction; + } + public function set colorFunction(value:Function):void{ + if(_colorFunction != value){ // value gets resolved first + var oldValue:Function =_colorFunction as Function; + _colorFunction= value as Function; + //call local helper to dispatch event + initChange("colorFunction",oldValue,_colorFunction,this); + } + } + + + //reference to the requesting geometry + private var _requester:IGeometryComposition; + public function set requester(value:IGeometryComposition):void{ + _requester = value; + } + + + private var _lastRect:Rectangle; + /** + * Provides access to the last rectangle that was relevant for this fill. + */ + public function get lastRectangle():Rectangle { + return _lastRect.clone(); + } + private var _lastContext:Graphics; + private var _lastArgs:Array = []; + + /** + * Provide access to the lastArgs array + */ + public function get lastArgs():Array { + return _lastArgs; + } + + /** + * Provides access to a cached function for restarting the last used fill either it the same context, or , if context is provided as an argument, + * then to an alternate context. If no + */ + public function get restartFunction():Function { + var copy:Array = _lastArgs.concat(); + var last:Graphics = _lastContext; + return function(alternate:Graphics = null):void { + if (alternate) alternate.beginFill(copy[0], copy[1]); + else if (last) last.beginFill(copy[0], copy[1]); + } + } + /** + * Begins the fill for the graphics context. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + public function begin(graphics:Graphics, rc:Rectangle):void{ + + //ensure that all defaults are in fact set these are temp until fully tested + if (isNaN(_alpha)) { _alpha = 1; } + + //handle alpha modification + var csAlpha:Number = CommandStack.currentAlpha; + var alpha:Number = this.alpha; + if (csAlpha != 1) { alpha *= csAlpha; } + + + _lastArgs.length = 0; + _lastArgs[0] = color as uint; + _lastArgs[1] = alpha; + _lastContext = graphics; + _lastRect = rc; + graphics.beginFill(color as uint,alpha); + } + + /** + * Ends the fill for the graphics context. + * + * @param graphics The current context being drawn to. + **/ + public function end(graphics:Graphics):void{ + graphics.endFill(); + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:SolidFill):void{ + + if (!_color){_color = uint(value.color);} + if (isNaN(_alpha)){_alpha = value.alpha;} + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/paint/SolidFill.png b/Degrafa/com/degrafa/paint/SolidFill.png new file mode 100644 index 0000000..2aa0552 Binary files /dev/null and b/Degrafa/com/degrafa/paint/SolidFill.png differ diff --git a/Degrafa/com/degrafa/paint/SolidStroke.as b/Degrafa/com/degrafa/paint/SolidStroke.as new file mode 100644 index 0000000..220c0a6 --- /dev/null +++ b/Degrafa/com/degrafa/paint/SolidStroke.as @@ -0,0 +1,467 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.paint{ + + import com.degrafa.core.DegrafaObject; + import com.degrafa.core.IGraphicsStroke; + import com.degrafa.core.utils.ColorUtil; + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.paint.palette.PaletteEntry; + import flash.geom.Point; + + import flash.display.Graphics; + import flash.geom.Rectangle; + import flash.geom.Matrix; + import mx.events.PropertyChangeEvent; + + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("SolidStroke.png")] + + [Bindable(event="propertyChange")] + + /** + * The Stroke class defines the properties for a line. You can define a + * Stroke object in MXML, but you must attach that Stroke to another object + * for it to appear in your application. + * + * @see mx.graphics.Stroke + * @see http://samples.degrafa.com/SolidStroke/SolidStroke.html + **/ + public class SolidStroke extends DegrafaObject implements IGraphicsStroke { + + /** + * Constructor. + * + *

    The solid stroke constructor accepts 3 optional arguments that define + * it's rendering color, alpha and weight.

    + * + * @param color A unit value indicating the stroke color. + * @param alpha A number indicating the alpha to be used for the stoke. + * @param weight A number indicating the weight of the line for the stroke. + */ + public function SolidStroke(color:Object=null, alpha:Number=NaN,weight:Number=NaN){ + this.color = color; + this.alpha = alpha; + this.weight = weight; + + + } + + protected var _alpha:Number; + [Inspectable(category="General")] + /** + * The transparency of a fill. + * + * @see mx.graphics.Stroke + **/ + public function get alpha():Number{ + if(isNaN(_alpha)){return 1;} + return _alpha; + } + public function set alpha(value:Number):void{ + if(_alpha != value){ + var oldValue:Number=_alpha; + + _alpha = value; + + //call local helper to dispatch event + initChange("alpha",oldValue,_alpha,this); + } + } + + protected var _color:Object; + [Inspectable(category="General", format="Color",defaultValue="0x000000")] + /** + * The fill color. + * This property accepts uint, hexadecimal (including shorthand), + * and color keys as well as comma seperated rgb or cmyk values. + * + **/ + public function get color():Object { + if(colorFunction!=null){ + return ColorUtil.resolveColor(colorFunction()); + } + else if(!_color){ + return 0x000000; + } + return _color; + } + public function set color(value:Object):void{ + + //setup for a palette entry if one is passed + if(value is PaletteEntry){ + paletteEntry = value as PaletteEntry; + } + else{ + paletteEntry=null; + } + + value = ColorUtil.resolveColor(value); + + if(_color != value){ // value gets resolved first + var oldValue:uint=_color as uint; + _color= value as uint; + //call local helper to dispatch event + initChange("color",oldValue,_color,this); + } + } + + private var _paletteEntry:PaletteEntry; + private function set paletteEntry(value:PaletteEntry):void{ + if(value){ + if(_paletteEntry !== value){ + //remove old listener is required + if(_paletteEntry){ + if(_paletteEntry.hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE)){ + _paletteEntry.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,onPaletteEntryChange); + } + } + //listen for changes + _paletteEntry = value + if(_paletteEntry.enableEvents){ + _paletteEntry.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,onPaletteEntryChange); + } + } + } + else{ + //clean up + if(_paletteEntry){ + if(_paletteEntry.hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE)){ + _paletteEntry.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,onPaletteEntryChange); + } + _paletteEntry=null; + } + } + } + + //handle the change to the palette entry + private function onPaletteEntryChange(event:PropertyChangeEvent):void{ + if(event.property=="value" && event.kind=="update"){ + color = event.source; + } + } + + protected var _colorFunction:Function; + [Inspectable(category="General")] + /** + * Function that sets the color of the fill. It is executed on + * every draw. + **/ + public function get colorFunction():Function{ + return _colorFunction; + } + public function set colorFunction(value:Function):void{ + if(_colorFunction != value){ // value gets resolved first + var oldValue:Function =_colorFunction as Function; + _colorFunction= value as Function; + //call local helper to dispatch event + initChange("colorFunction",oldValue,_colorFunction,this); + } + } + + private var _weight:Number; + [Inspectable(category="General", defaultValue=1)] + /** + * The line weight, in pixels. + * + * @see mx.graphics.Stroke + **/ + public function get weight():Number{ + if(!_weight){return 1;} + return _weight; + } + public function set weight(value:Number):void{ + if(_weight != value){ + var oldValue:Number=_weight; + + _weight = value; + + //call local helper to dispatch event + initChange("weight",oldValue,_weight,this); + } + + } + + protected var _degrafaScaling:int; + [Inspectable(category="General", enumeration="true,false", defaultValue="false")] + /** + * Determines whether Degrafa transforms will perform additional stroke weight transforms in accordance with the scaleMode setting on this stroke (or not) to scale a stroke. + * Default is false in which case degrafa transforms do not affect this stroke's weight. + * + * @see scaleMode + **/ + public function get degrafaScaling():Boolean { + return (_degrafaScaling<1) + } + public function set degrafaScaling(value:Boolean):void{ + if((_degrafaScaling>0) != value){ + _degrafaScaling = value?1: -1; + //call local helper to dispatch event + initChange("degrafaScaling",!value,value,this); + } + } + + protected var _scaleMode:String; + [Inspectable(category="General", enumeration="normal,vertical,horizontal,none", defaultValue="normal")] + /** + * Specifies how to scale a stroke. Strokes inside filtered or masked Degrafa geometry objects do not respect this setting for scaling in the native flash renderer + * as they are rasterized before being rendered by Degrafa. + * + * @see mx.graphics.Stroke + **/ + public function get scaleMode():String{ + if(!_scaleMode){return "normal";} + return _scaleMode; + } + public function set scaleMode(value:String):void{ + if(_scaleMode != value){ + var oldValue:String=_scaleMode; + + _scaleMode = value; + + //call local helper to dispatch event + initChange("scaleMode",oldValue,_scaleMode,this); + } + } + + + private var _pixelHinting:Boolean = false; + [Inspectable(category="General", enumeration="true,false")] + /** + * Specifies whether to hint strokes to full pixels. + * + * @see mx.graphics.Stroke + **/ + public function get pixelHinting():Boolean{ + return _pixelHinting; + } + public function set pixelHinting(value:Boolean):void{ + if(_pixelHinting != value){ + var oldValue:Boolean=_pixelHinting; + + _pixelHinting = value; + + //call local helper to dispatch event + initChange("pixelHinting",oldValue,_pixelHinting,this); + } + } + + private var _miterLimit:Number; + [Inspectable(category="General")] + /** + * Indicates the limit at which a miter is cut off. + * + * @see mx.graphics.Stroke + **/ + public function get miterLimit():Number{ + if(!_miterLimit){return 3;} + return _miterLimit; + } + public function set miterLimit(value:Number):void{ + if(_miterLimit != value){ + var oldValue:Number=_miterLimit; + + _miterLimit = value; + + //call local helper to dispatch event + initChange("miterLimit",oldValue,_miterLimit,this); + } + + } + + private var _joints:String; + [Inspectable(category="General", enumeration="round,bevel,miter", defaultValue="round")] + /** + * Specifies the type of joint appearance used at angles. + * + * @see mx.graphics.Stroke + **/ + public function get joints():String{ + if(!_joints){return "round";} + return _joints; + } + public function set joints(value:String):void{ + + if(_joints != value){ + var oldValue:String=_joints; + + _joints = value; + + //call local helper to dispatch event + initChange("joints",oldValue,_joints,this); + } + + } + + private var _caps:String; + [Inspectable(category="General", enumeration="round,square,none", defaultValue="round")] + /** + * Specifies the type of caps at the end of lines. + * + * @see mx.graphics.Stroke + **/ + public function get caps():String{ + if(!_caps){return "round";} + return _caps; + } + public function set caps(value:String):void{ + if(_caps != value){ + var oldValue:String=_caps; + + _caps = value; + + //call local helper to dispatch event + initChange("caps",oldValue,_caps,this); + } + } + + private var _lastRect:Rectangle; + /** + * Provides access to the last rectangle that was relevant for this fill. + */ + public function get lastRectangle():Rectangle { + return _lastRect.clone(); + } + private var _lastContext:Graphics; + private var _lastArgs:Array = []; + + /** + * Provide access to the lastArgs array + */ + public function get lastArgs():Array { + return _lastArgs; + } + + /** + * Provides access to a cached function for reapplying the last used stroke either in the same context, or , if context is provided as an argument, + * to an alternate context. + */ + public function get reApplyFunction():Function { + var copy:Array = _lastArgs.concat(); + var last:Graphics = _lastContext; + return function(alternate:Graphics = null, altArgs:Array = null):void { + var local:Array = altArgs?altArgs:copy; + if (alternate) alternate.lineStyle.apply(alternate, local); + else if (last) last.lineStyle.apply(last, local); + } + } + + /** + * Applies the properties to the specified Graphics object. + * + * @see mx.graphics.Stroke + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for stroke bounds. + **/ + public function apply(graphics:Graphics,rc:Rectangle):void{ + + //ensure that all defaults are in fact set these are temp until fully tested + if(isNaN(_alpha)){_alpha=1;} + if(!_caps){_caps="round";} + if(!_joints){_joints="round";} + if(!_miterLimit){_miterLimit=3;} + if(!_scaleMode){_scaleMode="normal";} + if(isNaN(_weight)){_weight=1;} + var weight:Number = this.weight; + if (_degrafaScaling > 0 && scaleMode != "none") { + var m:Matrix = CommandStack.transMatrix; + if (m) { + var s:Number + switch(_scaleMode) { + case "normal": + //discriminant seems to make sense here + s = Math.sqrt(Math.abs(m.a * m.d - m.b * m.c)); + break; + case "vertical": + //this seems to provide the same behaviour as the flash native vertical stroke scaling. Not sure if it makes sense with rotation, but best to keep consistent + s = m.b + m.d; + break; + case "horizontal": + //this seems to provide the same behaviour as the flash native horizontal stroke scaling. Not sure if it makes sense with rotation, but best to keep consistent + s = m.a + m.c; + break; + } + weight *= s; + } + } + + //handle alpha modification + var csAlpha:Number = CommandStack.currentAlpha; + var alpha:Number = this.alpha; + if (csAlpha != 1) { alpha *= csAlpha; } + + //performance gain by not setting the last 3 arguments if + //they are already the default flash values + if(caps=="round" && joints=="round" && miterLimit==3){ + if (graphics) graphics.lineStyle(weight, uint(color), alpha, pixelHinting, scaleMode); + _lastArgs.length = 0; + _lastArgs[0] = weight; + _lastArgs[1] = color as uint; + _lastArgs[2] = alpha; + _lastArgs[3] = pixelHinting; + _lastArgs[4] = scaleMode; + _lastContext = graphics; + _lastRect = rc; + } + else{ + if (graphics) graphics.lineStyle(weight,uint(color),alpha,pixelHinting, + scaleMode, caps, joints, miterLimit); + _lastArgs.length = 0; + _lastArgs[0] = weight; + _lastArgs[1] = color as uint; + _lastArgs[2] = alpha; + _lastArgs[3] = pixelHinting; + _lastArgs[4] = scaleMode; + _lastArgs[5] = caps; + _lastArgs[6] = joints; + _lastArgs[7] = miterLimit; + _lastContext = graphics; + _lastRect = rc; + } + } + + /** + * An object to derive this objects properties from. When specified this + * object will derive it's unspecified properties from the passed object. + **/ + public function set derive(value:SolidStroke):void{ + + if (isNaN(_alpha)) { _alpha = value.alpha } + if (!_caps){_caps = value.caps;} + if (!_color){_color = uint(value.color);} + if (!_joints){_joints = value.joints;} + if (!_miterLimit){_miterLimit = value.miterLimit;} + if (!_pixelHinting){_pixelHinting = value.pixelHinting} + if (!_scaleMode){_scaleMode = value.scaleMode} + if (!_weight) { _weight = value.weight } + if (!_degrafaScaling) {_degrafaScaling = value.degrafaScaling?1:-1} + + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/paint/SolidStroke.png b/Degrafa/com/degrafa/paint/SolidStroke.png new file mode 100644 index 0000000..f006141 Binary files /dev/null and b/Degrafa/com/degrafa/paint/SolidStroke.png differ diff --git a/Degrafa/com/degrafa/paint/VectorFill.as b/Degrafa/com/degrafa/paint/VectorFill.as new file mode 100644 index 0000000..030b67e --- /dev/null +++ b/Degrafa/com/degrafa/paint/VectorFill.as @@ -0,0 +1,1280 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.paint{ + + import com.degrafa.core.collections.FilterCollection; + import com.degrafa.core.ITransformablePaint; + import com.degrafa.events.DegrafaEvent; + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.GeometryComposition; + import com.degrafa.GeometryGroup; + import com.degrafa.transform.TransformBase; + + import com.degrafa.geometry.RegularRectangle; + import com.degrafa.IGeometryComposition; + import com.degrafa.core.DegrafaObject; + import com.degrafa.core.IBlend; + import com.degrafa.core.IGraphicsFill; + import com.degrafa.core.Measure; + import com.degrafa.geometry.Geometry; + import com.degrafa.transform.ITransform; + import flash.display.BlendMode; + import flash.display.Sprite; + import flash.filters.BitmapFilter; + + import flash.display.BitmapData; + import flash.display.Graphics; + import flash.display.Shape; + import flash.events.Event; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + + import mx.events.PropertyChangeEvent; + + [DefaultProperty("source")] + [Bindable(event="propertyChange")] + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("VectorFill.png")] + + + /** + * Used to fill one Geometry Object with other Degrafa-defined Geometry Objects or compositions + */ + public class VectorFill extends DegrafaObject implements IGraphicsFill, IBlend, ITransformablePaint{ + + // static constants + public static const NONE:String = "none"; + public static const REPEAT:String = "repeat"; + public static const SPACE:String = "space"; + public static const STRETCH:String = "stretch"; + //targetSettings + //scale to target bounds, without maintaining aspect ratio. + public static const MATCH_BOUNDS:String = "matchTargetBounds"; + //scale the VectorFill to the target bounds, whilst maintaining aspect ratio. center horizontally and vertically + public static const MATCH_BOUNDS_MAINTAIN_AR:String = "matchTargetBoundsMaintainAspectRatio"; + //draw without any scaling transforms to match the target bounds, but center on the fill bounds to the target's center of bounds + public static const CENTER_TO_TARGET:String = "centerToTarget"; + private static var _targetSettings:Array = [NONE, MATCH_BOUNDS,MATCH_BOUNDS_MAINTAIN_AR,CENTER_TO_TARGET ]; + + /** + * targetSetting options, avalailable as a convenience. + */ + public static function get targetSettingOptions():Array + { + return _targetSettings.concat(); + } + + // private variables + private var shape:Shape; + private var _source:IGeometryComposition; + private var bitmapData:BitmapData; + + //flags + private var _requiresPreRender:Boolean = true; + + /** + * Constructor. Accepts an optional reference to the source Geometry composition to be used to define what this VectorFill renders. + * @param source + */ + public function VectorFill(source:IGeometryComposition = null){ + if (source) this.source = source; + shape = new Shape(); + } + + private var _blendMode:String="normal"; + [Inspectable(category="General", enumeration="normal,layer,multiply,screen,lighten,darken,difference,add,subtract,invert,alpha,erase,overlay,hardlight", defaultValue="normal")] + public function get blendMode():String { + return _blendMode; + } + + public function set blendMode(value:String):void { + if(_blendMode != value){ + + var oldValue:String=_blendMode; + + _blendMode = value; + + //call local helper to dispatch event + initChange("blendMode",oldValue,_blendMode,this); + + } + + } + + + private var _originX:Number = 0; + /** + * The horizontal origin for the VectorFill. + * The VectorFill is offset so that this point appears at the origin. + * Scaling and rotation of the VectorFill are performed around this point. + * @default 0 + */ + public function get originX():Number { + return _originX; + } + + public function set originX(value:Number):void { + + if(_originX != value){ + + var oldValue:Number=_originX; + + _originX = value; + + //call local helper to dispatch event + initChange("originX",oldValue,_originX,this); + + } + + } + + + private var _originY:Number = 0; + /** + * The vertical origin for the VectorFill. + * The VectorFill is offset so that this point appears at the origin. + * Scaling and rotation of the rendered Geometry are performed around this point. + * @default 0 + */ + public function get originY():Number { + return _originY; + } + public function set originY(value:Number):void { + + if(_originY != value){ + + var oldValue:Number=_originY; + + _originY = value; + + //call local helper to dispatch event + initChange("originY",oldValue,_originY,this); + + } + + } + + private var _offsetX:Measure = new Measure(); + /** + * How far the VectorFill is horizontally offset from the origin. + * This adjustment is performed after rotation and scaling. + * @default 0 + */ + public function get offsetX():Number { + return _offsetX.value; + } + + public function set offsetX(value:Number):void { + + if(_offsetX.value != value){ + + var oldValue:Number=value; + + _offsetX.value = value; + + //call local helper to dispatch event + initChange("offsetX",oldValue,_offsetX,this); + + } + + } + + /** + * The unit of measure corresponding to offsetX. + */ + public function get offsetXUnit():String { return _offsetX.unit; } + public function set offsetXUnit(value:String):void { + if(_offsetX.unit != value) { + initChange("offsetXUnit", _offsetX.unit, _offsetX.unit = value, this); + } + } + + + private var _offsetY:Measure = new Measure(); + /** + * How far the Geometry is vertically offset from the origin. + * This adjustment is performed after rotation and scaling. + * @default 0 + */ + + public function get offsetY():Number { + return _offsetY.value; + } + + public function set offsetY(value:Number):void { + + if(_offsetY.value != value){ + + var oldValue:Number=value; + + _offsetY.value = value; + + //call local helper to dispatch event + initChange("offsetY",oldValue,_offsetY,this); + + } + + } + + /** + * The unit of measure corresponding to offsetY. + */ + public function get offsetYUnit():String { return _offsetY.unit; } + public function set offsetYUnit(value:String):void { + if(_offsetY.unit != value) { + initChange("offsetYUnit", _offsetY.unit, _offsetY.unit = value, this); + } + } + + private var _repeatX:String = "repeat"; + /** + * How the Geometry repeats horizontally. + * Valid values are "none", "repeat", "space", and "stretch". + * @default "repeat" + */ + + [Inspectable(category="General", enumeration="none,repeat,space,stretch")] + public function get repeatX():String{ + return _repeatX; + } + + public function set repeatX(value:String):void { + if(_repeatX != value){ + + var oldValue:String=value; + //this setting will cause the underlying BitmapData to be recreated + _requiresPreRender = true; + //call local helper to dispatch event + initChange("repeatX",oldValue,_repeatX= value,this); + + } + + } + + private var _repeatY:String = "repeat"; + /** + * How the Geometry repeats vertically. + * Valid values are "none", "repeat", "space", and "stretch". + * @default "repeat" + */ + + [Inspectable(category="General", enumeration="none,repeat,space,stretch")] + public function get repeatY():String{ + return _repeatY; + } + + public function set repeatY(value:String):void { + if(_repeatY != value){ + + var oldValue:String=_repeatY; + //this setting will cause the underlying BitmapData to be recreated + _requiresPreRender = true; + //call local helper to dispatch event + initChange("repeatY",oldValue,_repeatY= value,this); + + } + + } + + private var _rotation:Number = 0; + /** + * The number of degrees to rotate the Geometry. + * Valid values range from 0.0 to 360.0. + * @default 0 + */ + + public function get rotation():Number { + return _rotation; + } + + public function set rotation(value:Number):void { + + if(_rotation != value){ + + var oldValue:Number=_rotation; + + + //call local helper to dispatch event + initChange("rotation",oldValue,_rotation= value,this); + + } + + } + + private var _scaleX:Number = 1; + /** + * The percent to horizontally scale the Geometry when filling, from 0.0 to 1.0. + * If 1.0, the Geometry is filled at its natural size. + * @default 1.0 + */ + + public function get scaleX():Number { + return _scaleX; + } + + public function set scaleX(value:Number):void { + + if(_scaleX != value){ + + var oldValue:Number=value; + _scaleX = value; + //call local helper to dispatch event + initChange("scaleX",oldValue,_scaleX,this); + } + + } + + private var _scaleY:Number = 1; + /** + * The percent to vertically scale the Geometry when filling, from 0.0 to 1.0. + * If 1.0, the Geometry is filled at its natural size. + * @default 1.0 + */ + + public function get scaleY():Number { + return _scaleY; + } + + public function set scaleY(value:Number):void { + + if(_scaleY != value){ + + var oldValue:Number=value; + + _scaleY = value; + + //call local helper to dispatch event + initChange("scaleY",oldValue,_scaleY,this); + + } + + } + + private var _smooth:Boolean = true; + /** + * A flag indicating whether to smooth the bitmap data when filling with it. + * @default true + */ + + [Inspectable(category="General", enumeration="true,false")] + public function get smooth():Boolean{ + return _smooth; + } + + public function set smooth(value:Boolean):void { + + if(_smooth != value){ + + var oldValue:Boolean=value; + + _smooth = value; + //this setting will cause the underlying BitmapData to be recreated + _requiresPreRender = true; + //call local helper to dispatch event + initChange("smooth",oldValue,_smooth,this); + + } + + } + + private var _filters:FilterCollection; + /** + * A collection of filters to apply to the geometry source being used for the fill. + */ + [Inspectable(category="General", arrayType="flash.filters.BitmapFilter")] + [ArrayElementType("flash.filters.BitmapFilter")] + public function get filters():Array{ + initFilterCollection(); + //TODO: investigate bindable filters for Degrafa. + return _filters.items; + } + + public function set filters(value:Array):void { + initFilterCollection(); + if(_filters.items != value){ + + var oldValue:Array=_filters.items; + + _filters.items = value; + if (_enableFilters) shape.filters = _filters.items; + else shape.filters = []; + //call local helper to dispatch event + if (_enableFilters) initChange("filters",oldValue,_filters.items,this); + } + } + + /** + * Initialize the filter collection by creating it and adding an event listener. + **/ + private function initFilterCollection():void{ + if(!_filters){ + _filters = new FilterCollection(); + //add the parent so it can be managed by the collection + _filters.parent = this; + + //add a listener to the collection + if(enableEvents){ + _filters.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + private var _enableFilters:Boolean; + /** + * Specifies whether to use enable any filters assigned to this Fill + */ + [Inspectable(category="General", enumeration="true,false")] + public function get enableFilters():Boolean + { + return (_enableFilters == true); + } + public function set enableFilters(value:Boolean):void + { + + if (value!=_enableFilters){ + _enableFilters = value; + + if (_filters) { + _requiresRedraw = true; + _requiresPreRender = true; + if (_enableFilters) shape.filters = _filters.items; + else shape.filters = []; + initChange('enableFilters', !_enableFilters, _enableFilters, this); + } + } + } + + private var _enableBackground:Boolean; + /** + * Specifies whether to use the solidFillBackGround SolidFill (if set) when rendering + */ + + [Inspectable(category="General", enumeration="true,false")] + public function get enableBackground():Boolean + { + return (_enableBackground == true); + } + public function set enableBackground(value:Boolean):void + { + if (value!=_enableBackground){ + _enableBackground = value; + + //only trigger an update at this point if there is a solidFillBackground defined + if (_solidFillBackground) { + _requiresPreRender = true; + initChange('enableBackground', !_enableBackground, _enableBackground, this); + } + } + } + + + private var _solidFillBackground:SolidFill; + /** + * A SolidFill instance to use when rendering + */ + public function get solidFillBackground():SolidFill + { + if (_solidFillBackground) return _solidFillBackground; + else { + // create a default a default instance on first request, but do not trigger a redraw until it has been manipulated + _solidFillBackground = new SolidFill(); + _solidFillBackground.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE , BGListener,false,0,true); + return _solidFillBackground; + } + } + public function set solidFillBackground(value:SolidFill):void + { + if (value != _solidFillBackground) { + var oldVal:SolidFill = _solidFillBackground; + if (oldVal) oldVal.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE ,BGListener) + _solidFillBackground = value; + _solidFillBackground.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE , BGListener,false,0,true); + //mark for recreation of the fill bitmapdata in begin method requests + if (_enableBackground){ + _requiresPreRender = true; + initChange("solidFillBackground", oldVal, _solidFillBackground , this); + } + } + } + + private function BGListener(event:PropertyChangeEvent):void + { + if (_enableBackground){ + _requiresPreRender = true; + initChange('solidFillBackground.'+event.property ,event.oldValue,event.newValue,this) + } + } + + private var _insetFromStroke:Boolean; + /** + * whether the fillrendering bounds are determined by insetting from half the stroke width of the target or not. + * this setting only has effect when used to fill degrafa target geometry otherwise it is ignored. + */ + [Inspectable(category="General", enumeration="true,false")] + public function get insetFromStroke():Boolean + { + return _insetFromStroke? _insetFromStroke:false; + } + public function set insetFromStroke(value:Boolean):void + { + if (value != _insetFromStroke) { + _insetFromStroke = value; + //_requiresRedraw = true; + _requiresPreRender = true; + initChange("insetFromStroke", !_insetFromStroke, _insetFromStroke, this); + } + } + + + private var _enableSourceClipping:Boolean; + /** + * Specifies whether to use a the bounds of the clipSource Geometry object to clip the bounds of the fill + * from the rendered version of the source Geometry. If set to true and no clipSource has been assigned to this + * fill, then this setting is of no effect. + */ + [Inspectable(category="General", enumeration="true,false")] + public function get enableSourceClipping():Boolean + { + return (_enableSourceClipping == true); + } + public function set enableSourceClipping(value:Boolean):void + { + if (value!=_enableSourceClipping){ + _enableSourceClipping = value; + + if (_clipSource) { + if (_enableSourceClipping) _clipSourceRect = (_clipSource is GeometryComposition)? (_clipSource as GeometryComposition).childBounds:_clipSource.bounds; + _requiresPreRender = true; + initChange('enableSourceClipping', !_enableSourceClipping, _enableSourceClipping, this); + } + } + } + + + private var _clipSource:Object; + private var _clipSourceRect:Rectangle; + /** + * Specifies a Geometry object to use as a clipping area for the fill's source geometry when determining the + * region to be used for the fill. Requires the enableSourceClipping setting to be enabled to have effect. + * Accepts a GeometryGroup (DisplayObject) as an alternate source for bounds detection. + */ + public function get clipSource():Object + { + if (!_clipSourceRect) { + //default to an new RegularRectangle - might be useful from actionscript for easy clipSource settings + _clipSource = new RegularRectangle(); + _clipSource.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE , clipSourceChange,false, 0, true); + } + return _clipSource; + } + public function set clipSource(value:Object):void + { + //the only permitted clipSource items are GeometryGroup or Geometry + + if (value != _clipSource && (value is Geometry || value is GeometryGroup)) + { + if (_clipSource ) + { + if (_clipSource is Geometry) (_clipSource as Geometry).removeEventListener(DegrafaEvent.RENDER , clipSourceChange); + else (_clipSource as GeometryGroup).geometry.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE , clipSourceChange); + } + var oldVal:Object; + if (value is Geometry) { + + value.addEventListener(DegrafaEvent.RENDER , clipSourceChange); + //force a boundsCalc if the clipSource Geometry is invalidated: + value.preDraw(); + _clipSourceRect =value.bounds; + _clipSourceRect = TransformBase.getRenderedBounds(value as IGeometryComposition); + oldVal = _clipSource; + } else { + //dev note: this is preliminary... an option to use a full GeometryGroup as the clipSource. Untested at this point. + value.geometry.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE , clipSourceChange,false, 0,true); + _clipSourceRect = value.getBounds(value); + oldVal = _clipSource; + } + + //assign it, but only dispatch a property change if enableSourceClipping is true + _clipSource = value; + if (enableSourceClipping) { + _requiresPreRender = true; + initChange('clipSource', oldVal, _clipSource = value, this); + } + } + } + + /** + * respond to changes in the assigned source clipping object + * @param event + */ + private function clipSourceChange(event:Event):void + { + //ignore any changes if enableSourceClipping is false + if (!_enableSourceClipping) return; + //track the bounds + //ignore any changes here that don't affect the bounds + var checkBounds:Rectangle + if (event.target is Geometry){ + //get the transformed (render) bounds for the clipSource object: + //dev note: for GeometryComposition this needs more attention (e.g. if a transform is applied at the composition level), but is a quick fix to get it working: + if (event.target is GeometryComposition) checkBounds = (event.target as GeometryComposition).childBounds; + else checkBounds = TransformBase.getRenderedBounds(event.target as IGeometryComposition ); + + } else { //a displayObject is being used + //dev note: this is preliminary... an option to use a full GeometryGroup as the clipSource. Untested at this point. + checkBounds = event.target.getBounds(event.target); + } + if (!checkBounds.equals(_clipSourceRect)) + { + _requiresPreRender=true + initChange('clipSource', _clipSourceRect , _clipSourceRect=checkBounds, this); + } + } + + + + + private var _targetSetting:uint = 0; + [Inspectable(category = "General", enumeration = "none,matchTargetBounds,matchTargetBoundsMaintainAspectRatio,centerToTarget")] + /** + * A 'smart'/quick setting for matching fill rendering between source and target. Using this setting overrides - or more precisely, ignores - + * most of the manual settings applied to the fill. Using 'none' enables all the regular manual settings + */ + public function get targetSetting():String{ + return _targetSettings[_targetSetting]; + } + + public function set targetSetting(value:String):void { + var valIndex:int = _targetSettings.indexOf(value); + if (valIndex == -1) { + valIndex = 0; + } + if (_targetSetting != valIndex) + { + var oldValue:uint = _targetSetting; + _requiresPreRender = true; + //call local helper to dispatch event + initChange("targetSetting",oldValue,_targetSetting=valIndex,this); + } + } + + + /** + * listener to handle the property changes from the source geometry + * @param event + */ + protected function geomListener(event:PropertyChangeEvent):void + { + _requiresRedraw = true; + _requiresPreRender = true; + initChange("source." + event.property , event.oldValue, event.newValue, this); + } + + + private var sourceBounds:Rectangle; + private function redraw():void + { + //dev note: [optimization] at the moment, switching filters on/off also triggers a redraw below...this is not necessary if it is the only propertyChange + // as a redraw is not required, just application or removal of the filters and adjustment of sourceBounds + shape.graphics.clear(); + var settings:Object = CommandStack.getSettingsCache(); + _source.draw(shape.graphics, _source.bounds); + CommandStack.resetCacheValues(settings); + sourceBounds = shape.getBounds(shape); + + //filters, if used, are not included in sourceBounds from the Shape. So we need to add the filter effects to the sourceBounds + + //dev note: this filter calculation is preliminary and (hopefully) may be able to be done in a more lightweight way: + if (_enableFilters && _filters) + { + var calcHelper:BitmapData; + for each(var filter:BitmapFilter in _filters.items) + { + calcHelper = new BitmapData(int(sourceBounds.width ), int(sourceBounds.height )); + var check:Rectangle = calcHelper.generateFilterRect(calcHelper.rect, filter) + check.offset(sourceBounds.x,sourceBounds.y) + sourceBounds = sourceBounds.union(check); + calcHelper.dispose(); + } + var yround:Number; + var xround:Number + xround= sourceBounds.x-Math.floor(sourceBounds.x); + yround= sourceBounds.y-Math.floor(sourceBounds.y); + sourceBounds.x=Math.floor(sourceBounds.x); + sourceBounds.y=Math.floor(sourceBounds.y); + sourceBounds.width = Math.ceil(sourceBounds.width); + sourceBounds.height = Math.ceil(sourceBounds.height ); + } + + _requiresRedraw = false; + } + + + private var _disposalQueue:Array = []; + private var _disposalLimit:uint = 20; + /** + * A simple means by which to limit the amount of bitmapData this Fill generates. + * Any quantities of bitmapData older than this limit will be disposed. + * We may replace this at some point with a Degrafa bitmapData cache/manager + * or some other centralized way to manage the amount of/GC of bitmapData + */ + public function set disposalLimit(val:uint):void + { + _disposalLimit = val >= 0 ? val:0; + } + public function get disposalLimit():uint + { + return _disposalLimit; + } + + /** + * disposes of older bitmapData generated by this fill, depending on the limit set by disposalLimit + */ + protected function disposeBitmapData():void + { + while (_disposalQueue.length > _disposalLimit) + { + _disposalQueue.shift().dispose(); + } + } + + /** + * generate a prerendered bitmapData with padding by xoffset and yoffset, with a number of options + * @param xoffset the number of pixels to pad left and right + * @param yoffset the number of pixels to pad top and bottom + * @param redrawSource force a refresh of the source (re-render the vectors) + * @param targRect the target Rectangle for resizing into + * @param clipSource an optional rectangle for the source space to clip to. + * @param repX repeat count horizontally + * @param repY repeat count vertically + */ + private function preRender(xoffset:uint=0,yoffset:uint=0,redrawSource:Boolean=true,targRect:Rectangle=null,clipperRect:Rectangle=null,repX:uint=1,repY:uint=1):void + { + if(_source ) + { + //should only actually redraw if the source geometry has changed + if (redrawSource) + { + redraw(); + } + var workingBitmapData:BitmapData; + var workingRect:Rectangle ; + if (_enableSourceClipping && _clipSourceRect ) { + workingRect = _clipSourceRect.clone(); + } else { + workingRect = sourceBounds.clone(); + } + if (!workingRect.isEmpty()) { + var newWidth:uint ; + var newHeight:uint; + + //move to the 'origin' of the fill capture + var transMat:Matrix = new Matrix(1, 0, 0, 1, -(workingRect.x), -(workingRect.y)) + if (targRect) + { + if (targRect.isEmpty()) { + //we have essentially scaled to zero + if (bitmapData) _disposalQueue.push(bitmapData); + bitmapData = null; + return; + } + //handle scaling bitmapdata + var xscaler:Number = targRect.width / workingRect.width; + var yscaler:Number = targRect.height / workingRect.height; + + if (_targetSetting == 2) { + //maintain aspect ratio + var scale:Number = Math.min(xscaler, yscaler); + transMat.scale(scale, scale); + + newWidth = Math.ceil(scale * workingRect.width) + xoffset * 2; + newHeight = Math.ceil(scale * workingRect.height) + yoffset * 2; + if (newWidth && newHeight) { + workingBitmapData = new BitmapData(newWidth*repX, newHeight*repY, true, 0); + } + else { + if (bitmapData) _disposalQueue.push(bitmapData); + bitmapData = null; + return; + } + + } else { + + transMat.scale(xscaler, yscaler); + newWidth = targRect.width+xoffset*2; + newHeight = targRect.height + yoffset * 2; + + var xpadExtra:uint = (repX!=1 && repY==1)?1:0; + var ypadExtra:uint = (repY!=1 && repX==1)?1:0; + if (newWidth>0 && newHeight>0) { + workingBitmapData = new BitmapData(newWidth*repX+xpadExtra*2, newHeight*repY+ypadExtra*2, true, 0); + } + else { + if (bitmapData) _disposalQueue.push(bitmapData); + bitmapData = null; + return; + } + + } + + } else { + + workingBitmapData = new BitmapData((workingRect.width+xoffset*2)*repX, (workingRect.height+yoffset*2)*repY, true, 0); + + + if (bitmapData) _disposalQueue.push(bitmapData); + } + bitmapData = workingBitmapData; + + //dev note: move this background fill operation into the BitmapData instantiation + if (_enableBackground && _solidFillBackground) { + bitmapData.fillRect(bitmapData.rect, uint(_solidFillBackground.alpha * 255 * 0x1000000)+uint(_solidFillBackground.color)); + } + + var tempBmp:BitmapData; + if (repX == 1 && repY == 1) + { + + if (!_enableSourceClipping) { + //simple + + tempBmp = new BitmapData(workingRect.width*transMat.a + .5, workingRect.height*transMat.d + .5,true,0); + tempBmp.draw(shape, transMat, null, null, null, true ); + bitmapData.copyPixels(tempBmp,tempBmp.rect,new Point(xoffset,yoffset),null,null,true) + + } else { + transMat.tx = -clipperRect.x * transMat.a; + transMat.ty = -clipperRect.y * transMat.d; + + tempBmp = new BitmapData(clipperRect.width*transMat.a + .5, clipperRect.height*transMat.d + .5,true,0); + tempBmp.draw(shape, transMat, null, null, null, true ); + + bitmapData.copyPixels(tempBmp,tempBmp.rect,new Point(xoffset,yoffset),null,null,true) + } + } + else { + //repeat in one direction + var i:uint; + var j:uint; + + if (_enableSourceClipping) { + + transMat.tx = -clipperRect.x*transMat.a; + transMat.ty = -clipperRect.y * transMat.d; + + tempBmp = new BitmapData(clipperRect.width*transMat.a + .5, clipperRect.height*transMat.d + .5,true,0); + tempBmp.draw(shape, transMat, null, null, null, true ); + + } + + transMat.translate(xoffset, yoffset); + var orgty:Number = transMat.ty; + var orgtx:Number = transMat.tx; + + for (i = 0; i < repX; i++) + { + if (repX!=1) { + transMat.tx = i * (workingRect.width + xoffset * 2) + xoffset - workingRect.x; + } else transMat.ty = orgtx; + if (xpadExtra) { + transMat.tx += xpadExtra; + } + + for (j = 0; j < repY; j++) + { + if (repY!=1) { + transMat.ty = j * (workingRect.height + yoffset * 2 ) + yoffset - workingRect.y; + } else transMat.ty = orgty; + if (ypadExtra) { + transMat.ty += ypadExtra; + + } + + if (!tempBmp) { + bitmapData.draw(shape, transMat, null, _blendMode, null, true ) + } else { + var tpoint:Point = new Point(xoffset+i*(workingRect.width+xoffset*2)+((xpadExtra)?xpadExtra:0), yoffset+j * (workingRect.height + yoffset*2 )+((ypadExtra)?ypadExtra:0)); + bitmapData.copyPixels(tempBmp, tempBmp.rect, tpoint,null,null,true); + } + + } + + } + + } + if (tempBmp) tempBmp.dispose(); + + } else { + //trace('empty working Rect') + } + } + } + + + /** + * The source used for the VectorFill. + * An IGeometryComposition object + **/ + public function get source():IGeometryComposition { return _source; } + public function set source(value:IGeometryComposition):void { + //no change + if (_source== value) return; + //no value + if (!value) { + return; + } + var oldValue:Object = bitmapData; + //remove old listener + if (_source) { + (_source as DegrafaObject).removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, geomListener); + } + _source = value; + //add new listener + (_source as DegrafaObject).addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, geomListener,false,0,true); + _requiresRedraw = true; + _requiresPreRender = true; + initChange("source", oldValue, bitmapData, this); + + } + + private var _requester:IGeometryComposition; + /** + * Used to set a temporary reference to the requesting geometry. Mainly for internal use, for transform and layout 'inheritance' by this fill. + */ + public function set requester(value:IGeometryComposition):void + { + _requester = value; + } + + + private var _lastRect:Rectangle; + /** + * Provides access to the last rectangle that was relevant for this fill. + */ + public function get lastRectangle():Rectangle { + return (_lastRect)?_lastRect.clone():null; + } + private var _lastContext:Graphics; + private var _lastArgs:Array = []; + + /** + * Provide access to the lastArgs array + */ + public function get lastArgs():Array { + return _lastArgs; + } + /** + * Provides quick access to a cached function for restarting the last used fill either in the last used context, or, if a context is provided as an argument, + * then to an alternate context. If no last used context is available then this will do nothing; + */ + public function get restartFunction():Function { + var copy:Array = _lastArgs.concat(); + var last:Graphics = _lastContext; + return function(alternate:Graphics = null):void { + if (alternate) alternate.beginBitmapFill.apply(alternate, copy); + else if (last) last.beginBitmapFill.apply(last,copy); + } + } + + + + private var matrix:Matrix = new Matrix(); + //flag to force a redraw of the source geometry + private var _requiresRedraw:Boolean; + //temporary working matrix + private var tempmat:Matrix; + //track difference in target bounds on subsequent requests + private var _rectBuffer:Rectangle=new Rectangle(); + + /** + * begins the VectorFill + */ + public function begin(graphics:Graphics, rc:Rectangle):void { + + if (rc && (rc.isEmpty() || rc.width*rc.height<1)) return; //no fill + if (_enableSourceClipping && !_clipSourceRect) return ; // empty fill....as clipSource has not yet been assigned. + + if (_requiresRedraw) redraw(); + + var template:BitmapData = bitmapData; + var repeat:Boolean; + var positionX:Number = 0; + var positionY:Number = 0; + var regPoint:Point; + matrix.identity(); + matrix.translate(rc.x, rc.y); + + + if (_insetFromStroke && _requester && (_requester as Geometry).stroke){ + var strokeoffset:uint; + strokeoffset = Math.ceil((_requester as Geometry).stroke.weight / 2); + // for a zero weight stroke, give it a 1 pixel offset + if (!strokeoffset) strokeoffset = 1; + rc = rc.clone(); + rc.inflate( -strokeoffset, -strokeoffset); //inset by strokeoffset - used for scaling if needed + matrix.translate(strokeoffset, strokeoffset); //ditto for rendering + _requiresPreRender = true; + } + if (rc && !_rectBuffer.equals(rc)) { + //we have a different target to draw to. This may force a preRender if we need to. + //todo: review targetSetting related conditions and add here if needed + + if (!(_repeatX==VectorFill.NONE || _repeatX==VectorFill.REPEAT) || !(_repeatY==VectorFill.NONE || _repeatY==VectorFill.REPEAT) ) _requiresPreRender = true; + _rectBuffer.topLeft = rc.topLeft; + _rectBuffer.bottomRight = rc.bottomRight; + + } + + repeat = false; + if (_targetSetting) + { + if (_requiresPreRender) + if (_targetSetting!=3){ + + + { + + //first let's get some integer constraints on the fill target bounds. + var targRect:Rectangle = rc.clone(); + targRect.width=Math.ceil(targRect.width+(targRect.x- (targRect.x=Math.floor(targRect.x)))); + targRect.height=Math.ceil(targRect.height+(targRect.y-(targRect.y=Math.floor(targRect.y)))); + + preRender(2, 2, false, targRect, _enableSourceClipping? _clipSourceRect:null); + //if the bitmapdata was scaled to zero, then exit the fill + if (bitmapData==null) return; + template = bitmapData; + + + } + + } + else + { //centre to target + + if (_enableSourceClipping) preRender(2, 2, _requiresRedraw,null,_clipSourceRect); + else preRender(2, 2, _requiresRedraw, null); + //if the bitmapdata was scaled to zero in prendering, then exit the fill + if (!bitmapData) return; + template = bitmapData; + + + } + switch(_targetSetting) + { + case 2: + //if match targetboundsmaintainaspectratio, then centre it to the target bounds + + matrix.translate(rc.width/2-bitmapData.width/2,rc.height/2-bitmapData.height/2) + + break; + case 3 : + matrix.translate(-2,-2) + if (enableSourceClipping) matrix.translate(rc.width/2-_clipSourceRect.width/2, rc.height/2-_clipSourceRect.height/2); + else matrix.translate(rc.width/2-sourceBounds.width/2, rc.height/2-sourceBounds.height/2); + //allow repeating in both directions on this setting otherwise ignore. + if (_repeatX == VectorFill.REPEAT && _repeatY == VectorFill.REPEAT) repeat = true; + + break; + default: + matrix.translate(-2,-2) + + break; + + + } + + } + + else { //there is no 'smart' setting for the target... use the regular BitmapFill approach + + + var targetRect:Rectangle= _enableSourceClipping ? _clipSourceRect.clone(): sourceBounds.clone(); + + var padX:uint = 2; + var padY:uint = 2; + var repX:uint = 1; + var repY:uint = 1; + var renderingPadX:uint = 0; + var renderingPadY:uint = 0; + switch (_repeatX) + { + case VectorFill.STRETCH: + targetRect.width = rc.width; + break; + case VectorFill.SPACE: + renderingPadX = Math.round(Math.round((rc.width % targetRect.width) / int(rc.width/targetRect.width)) / 2); + padX = 0; + repX = int(rc.width / targetRect.width); + repX = repX == 0?1:repX; + break; + case VectorFill.REPEAT : + padX = 0; + repX = int(rc.width / targetRect.width) + 1; + break; + case VectorFill.NONE: + + break; + } + switch (_repeatY) + { + case VectorFill.STRETCH: + targetRect.height = rc.height; + break; + case VectorFill.SPACE: + renderingPadY = Math.round(Math.round((rc.height % targetRect.height) / int(rc.height/targetRect.height)) / 2); + padY = 0; + repY = int(rc.height / targetRect.height); + repY = repY == 0?1:repY; + + break; + case VectorFill.REPEAT : + padY = 0; + repY = int(rc.height / targetRect.height) + 1 + break; + case VectorFill.NONE: + if (_repeatX==VectorFill.NONE) repeat = false; + break; + } + + if ((repX > 1 && repY > 1) ) { + //we have both x and y repeats, so just use the flash native bitmapfill's x&y repeat + repX = 1; + repY = 1; + padX = 0; + padY = 0; + repeat = true; + } + + if (_requiresPreRender){ + preRender(padX+renderingPadX,padY+renderingPadY,_requiresRedraw,targetRect,_enableSourceClipping?_clipSourceRect:null,repX,repY) + if (bitmapData==null) return; + template = bitmapData; + + } + repeat = (repeat || !(repeatX == VectorFill.NONE || repeatY == VectorFill.NONE)); + + if(repeatX == VectorFill.NONE || repeatX == VectorFill.REPEAT) { + positionX = _offsetX.relativeTo(rc.width-template.width) + } + + if(repeatY == VectorFill.NONE || repeatY == VectorFill.REPEAT) { + positionY = _offsetY.relativeTo(rc.height-template.height) + } + + matrix.translate(-padX, -padY); + + } + matrix.translate( -_originX, -_originY); + + matrix.scale(_scaleX, _scaleY); + matrix.rotate(_rotation*(Math.PI/180)); + matrix.translate(positionX, positionY); + var transformRequest:ITransform; + var tempmat:Matrix; + //handle layout transforms + if (_requester && (_requester as Geometry).hasLayout) { + var geom:Geometry = _requester as Geometry; + if (geom._layoutMatrix) matrix.concat( geom._layoutMatrix); + } + if (_transform && ! _transform.isIdentity) { + tempmat= new Matrix(); + regPoint = _transform.getRegPointForRectangle(rc); + tempmat.translate(-regPoint.x,-regPoint.y); + tempmat.concat(_transform.transformMatrix); + tempmat.translate( regPoint.x,regPoint.y); + matrix.concat(tempmat); + } + if (_requester && ((transformRequest = (_requester as Geometry).transform) || (_requester as Geometry).transformContext)) { + + if (transformRequest) matrix.concat(transformRequest.getTransformFor(_requester)); + else matrix.concat((_requester as Geometry).transformContext); + //remove the requester reference + _requester = null; + } + + if (graphics) graphics.beginBitmapFill(template, matrix, repeat, _smooth ); + _lastArgs.length = 0; + _lastArgs[0] = template; + _lastArgs[1] = matrix; + _lastArgs[2] = repeat; + _lastArgs[3] = smooth; + _lastContext = graphics; + _lastRect = rc; + + //reset the forcePrerender flag + _requiresPreRender = false; + } + + /** + * Ends the Vectorfill for the graphics context. + **/ + public function end(graphics:Graphics):void { + graphics.endFill(); + disposeBitmapData(); + + if (_requester) _requester = null; + } + + private var _transform:ITransform; + /** + * Defines the transform object that will be used for + * altering this VectorFill object. + **/ + public function get transform():ITransform{ + return _transform; + } + public function set transform(value:ITransform):void{ + + if(_transform != value){ + + var oldValue:Object=_transform; + + if(_transform){ + if(_transform.hasEventManager){ + _transform.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + + _transform = value; + + if(enableEvents){ + _transform.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler,false,0,true); + } + //call local helper to dispatch event + initChange("transform", oldValue, _transform, this); + } + + } + + private function propertyChangeHandler(event:PropertyChangeEvent):void + { + + if (event.source == _filters) _requiresPreRender = true; + dispatchEvent(event); + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/paint/VectorFill.png b/Degrafa/com/degrafa/paint/VectorFill.png new file mode 100644 index 0000000..0fef06c Binary files /dev/null and b/Degrafa/com/degrafa/paint/VectorFill.png differ diff --git a/Degrafa/com/degrafa/paint/VideoFill.as b/Degrafa/com/degrafa/paint/VideoFill.as new file mode 100644 index 0000000..ffa2634 --- /dev/null +++ b/Degrafa/com/degrafa/paint/VideoFill.as @@ -0,0 +1,987 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2009 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Original author of this code: Greg Dove http://greg-dove.com +// Contributed to Degrafa for beta 3.2, November 2009 +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.paint{ + + import com.degrafa.IGeometryComposition; + import com.degrafa.core.DegrafaObject; + import com.degrafa.core.IBlend; + import com.degrafa.core.IGraphicsFill; + import com.degrafa.core.ITransformablePaint; + import com.degrafa.core.Measure; + import com.degrafa.geometry.Geometry; + import com.degrafa.geometry.command.CommandStack; + import com.degrafa.transform.ITransform; + import com.degrafa.utilities.external.ExternalDataAsset; + import com.degrafa.utilities.external.ExternalDataPropertyChangeEvent; + import com.degrafa.utilities.external.LoadingLocation; + import com.degrafa.utilities.external.VideoStream; + + import flash.display.BitmapData; + import flash.display.Graphics; + import flash.events.Event; + import flash.geom.ColorTransform; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.utils.Dictionary; + import flash.utils.setTimeout; + + import mx.events.PropertyChangeEvent; + import mx.events.PropertyChangeEventKind; + + [DefaultProperty("source")] + + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("VideoFill.png")] + + /** + * VideoFill is an advanced paint class used to fill an area with playing Video content. + */ + public class VideoFill extends DegrafaObject implements IGraphicsFill, IBlend,ITransformablePaint{ + + // static constants + public static const NONE:String = "none"; + public static const REPEAT:String = "repeat"; + public static const STRETCH:String = "stretch"; + //targetSettings + //scale to target bounds, without maintaining aspect ratio. + public static const MATCH_BOUNDS:String = "matchTargetBounds"; + //scale the VectorFill to the target bounds, whilst maintaining aspect ratio. center horizontally and vertically + public static const MATCH_BOUNDS_MAINTAIN_AR:String = "matchTargetBoundsMaintainAspectRatio"; + //draw without any scaling transforms to match the target bounds, but center on the fill bounds to the target's center of bounds + public static const CENTER_TO_TARGET:String = "centerToTarget"; + private static var _targetSettings:Array = [NONE, MATCH_BOUNDS,MATCH_BOUNDS_MAINTAIN_AR,CENTER_TO_TARGET ]; + + private var _alphaRequests:Dictionary; + + /** + * targetSetting options, avalailable as a convenience. + */ + public static function get targetSettingOptions():Array + { + return _targetSettings.concat(); + } + + + private var bitmapData:BitmapData; + private var _videoSource:VideoStream; + private var _loadingLocation:LoadingLocation; + + private var instantiationTimer:uint=5; + + public function VideoFill(source:Object = null,loc:LoadingLocation=null){ + this._loadingLocation = loc; + this.source = source; + + } + + //TODO: Consider not implementing IBlend + private var _blendMode:String="normal"; + [Inspectable(category="General", enumeration="normal,layer,multiply,screen,lighten,darken,difference,add,subtract,invert,alpha,erase,overlay,hardlight", defaultValue="normal")] + [Bindable(event="propertyChange")] + public function get blendMode():String { + return _blendMode; + } + + public function set blendMode(value:String):void { + if(_blendMode != value){ + + var oldValue:String=_blendMode; + + _blendMode = value; + + //call local helper to dispatch event + initChange("blendMode",oldValue,_blendMode,this); + + } + + } + + private var _x:Number; + /** + * The x-axis coordinate of the upper left point of the video content rectangle. If not specified + * a default value of 0 is used. + **/ + public function get x():Number{ + if(!_x){return 0;} + return _x; + } + public function set x(value:Number):void{ + if(_x != value){ + + var oldValue:Number=_x; + + _x = value; + + //call local helper to dispatch event + initChange("x",oldValue,_x,this); + + } + } + + + private var _y:Number; + /** + * The y-axis coordinate of the upper left point of the video content rectangle. If not specified + * a default value of 0 is used. + **/ + public function get y():Number{ + if(!_y){return 0;} + return _y; + } + public function set y(value:Number):void{ + if(_y != value){ + + var oldValue:Number=_y; + + _y = value; + + //call local helper to dispatch event + initChange("y",oldValue,_y,this); + + } + } + + + private var _width:Number; + /** + * The width to be used for scaling the video content rectangle (excluding any pixelMargin setting on the VideoStream source). + **/ + public function get width():Number{ + if(_width*0!=0){ + if (_coordType == "ratio") return 1; + if (bitmapData) { + return bitmapData.width-(_correctionMatrix?_correctionMatrix.tx*2:0); + } else return 0; + } + return _width; + } + public function set width(value:Number):void{ + if(_width != value){ + + var oldValue:Number=_width; + + _width = value; + + //call local helper to dispatch event + initChange("width",oldValue,_width,this); + } + } + + + private var _height:Number; + /** + * The height to be used for scaling the video content rectangle (excluding any pixelMargin setting on the VideoStream source). + **/ + public function get height():Number{ + if(_height*0!=0){ + if (_coordType == "ratio") return 1; + if (bitmapData) { + return bitmapData.height-(_correctionMatrix?_correctionMatrix.ty*2:0); + } else return 0; + } + return _height; + } + public function set height(value:Number):void{ + if(_height != value){ + if (value<0){ + if (!_height) return; + value=0; + } + if (!value) value=NaN; + var oldValue:Number=_height; + _height = value; + //call local helper to dispatch event + initChange("height",oldValue,_height,this); + } + } + + private var _repeatX:String = "none"; + [Inspectable(category="General", enumeration="none,repeat,stretch", defaultValue="none")] + [Bindable(event="propertyChange")] + /** + * How the bitmap repeats horizontally. + * Valid values are "none", "repeat", and "stretch". + * Setting this value to "repeat" if repeatY is "none" will automatically set repeatY to "repeat" also. + * If targetSetting is set to a value other than "none" then both repeatX and repeatY must be set to "repeat" to enable repeating + * If targetSetting is set to a value other than "none" then values of "stretch" are ignored for repeatX or repeatY + * @default "none" + */ + + public function get repeatX():String{ + return _repeatX; + } + + public function set repeatX(value:String):void { + if(_repeatX != value){ + + var oldValue:String=value; + + _repeatX = value; + + //call local helper to dispatch event + initChange("repeatX",oldValue,_repeatX,this); + if (_repeatX=="repeat" && _repeatY=="none") repeatY="repeat"; + + } + + } + + private var _repeatY:String = "none"; + [Inspectable(category = "General", enumeration = "none,repeat,stretch", defaultValue="none")] + [Bindable(event="propertyChange")] + /** + * How the bitmap repeats vertically. + * Valid values are "none", "repeat", and "stretch". + * Setting this value to "repeat" if repeatX is "none" will automatically set repeatX to "repeat" also. + * If targetSetting is set to a value other than "none" then both repeatX and repeatY must be set to "repeat" to enable repeating + * If targetSetting is set to a value other than "none" then values of "stretch" are ignored for repeatX or repeatY + * @default "none" + */ + + public function get repeatY():String{ + return _repeatY; + } + + public function set repeatY(value:String):void { + if(_repeatY != value){ + + var oldValue:String=value; + + _repeatY = value; + + + //call local helper to dispatch event + initChange("repeatY",oldValue,_repeatY,this); + if (_repeatY=="repeat" && _repeatX=="none") repeatX="repeat"; + + } + + } + + private var _rotation:Number = 0; + [Bindable(event="propertyChange")] + /** + * The number of degrees to rotate the bitmap. + * Valid values range from 0.0 to 360.0. + * @default 0 + */ + + public function get rotation():Number { + return _rotation; + } + + public function set rotation(value:Number):void { + + if(_rotation != value){ + + var oldValue:Number=value; + + _rotation = value; + + //call local helper to dispatch event + initChange("rotation",oldValue,_rotation,this); + + } + + } + + private var _scaleX:Number = 1; + /** + * The percent to horizontally scale the video when filling, from 0.0 to 1.0. + * If 1.0, the video is filled at its natural size. + * @default 1.0 + */ + [Bindable(event="propertyChange")] + public function get scaleX():Number { + return _scaleX; + } + + public function set scaleX(value:Number):void { + + if(_scaleX != value){ + + var oldValue:Number=value; + + _scaleX = value; + + //call local helper to dispatch event + initChange("scaleX",oldValue,_scaleX,this); + + } + + } + + private var _scaleY:Number = 1; + [Bindable(event="propertyChange")] + /** + * The percent to vertically scale the video when filling, from 0.0 to 1.0. + * If 1.0, the video is filled at its natural size. + * @default 1.0 + */ + + public function get scaleY():Number { + return _scaleY; + } + + public function set scaleY(value:Number):void { + + if(_scaleY != value){ + + var oldValue:Number=value; + + _scaleY = value; + + //call local helper to dispatch event + initChange("scaleY",oldValue,_scaleY,this); + + } + + } + + + protected var _coordType:String = "relative"; + [Inspectable(category="General", enumeration="absolute,relative,ratio", defaultValue="relative")] + /** + * The coordinateType property specifies the type of coordinates to be used for fill bounds, either absolute, or relative to top,left of target bounds, or as a ratio to target bounds. + * For VideoFill this property defaults to 'relative'.
    + **/ + + public function set coordinateType(value:String):void + { + if (("absolute,relative,ratio,").indexOf(value+",")==-1) value="relative"; + if (value!=_coordType) + { + //call local helper to dispatch event + initChange("coordinateType",_coordType,_coordType = value,this); + } + } + public function get coordinateType():String{ + return _coordType; + } + + + + /** + * @private + * + * */ + private var _smooth:Boolean = true; + [Inspectable(category = "General", enumeration = "true,false", defaultValue="true")] + [Bindable(event="propertyChange")] + /** + * A flag indicating whether to smooth the video image when filling with it if scaling is applied. + * @default true + */ + + public function get smooth():Boolean{ + return _smooth; + } + + public function set smooth(value:Boolean):void { + + if(_smooth != value){ + + var oldValue:Boolean=value; + + _smooth = value; + + //call local helper to dispatch event + initChange("smooth",oldValue,_smooth,this); + + } + + } + + /** + * @private + * + * */ + private var _targetSetting:uint = 0; + [Inspectable(category = "General", enumeration = "none,matchTargetBounds,matchTargetBoundsMaintainAspectRatio,centerToTarget", defaultValue="none")] + /** + * A 'smart'/quick setting for matching fill rendering between source and target. Using this setting overrides - or more precisely, ignores - + * most of the manual settings applied to the fill. Using 'none' enables all the regular manual settings + */ + public function get targetSetting():String{ + return _targetSettings[_targetSetting]; + } + + public function set targetSetting(value:String):void { + var valIndex:int = _targetSettings.indexOf(value); + if (valIndex == -1) { + valIndex = 0; + } + if (_targetSetting != valIndex) + { + var oldValue:uint = _targetSetting; + _targetSetting=valIndex; + //_requiresPreRender = true; + //call local helper to dispatch event + initChange("targetSetting",_targetSettings[oldValue],_targetSettings[_targetSetting],this); + } + } + /** + * @private + * + * */ + private var _insetFromStroke:Boolean; + [Inspectable(category="General", enumeration="true,false", defaultValue="false")] + /** + * whether the fillrendering bounds are determined by insetting from half the stroke width of the target or not. + * this setting only has effect when used to fill degrafa target geometry otherwise it is ignored. + */ + public function get insetFromStroke():Boolean + { + return _insetFromStroke? _insetFromStroke:false; + } + public function set insetFromStroke(value:Boolean):void + { + if (value != _insetFromStroke) { + _insetFromStroke = value; + initChange("insetFromStroke", !_insetFromStroke, _insetFromStroke, this); + } + } + + //EXTERNAL DATA SUPPORT + /** + * @private + * + * */ + private var _waiting:Boolean; + [Inspectable(category="General", enumeration="true,false", defaultValue="false")] + [Bindable("externalDataPropertyChange")] + /** + * A support property for binding to in the event of an external loading wait. + * permits a simple binding to indicate that the wait is over + */ + + public function get waiting():Boolean + { + return (_waiting==true); + } + public function set waiting(val:Boolean):void + { + if (val != _waiting ) + { + _waiting = val; + //support binding, but don't use propertyChange to avoid Degrafa redraws for no good reason + dispatchEvent(new ExternalDataPropertyChangeEvent(ExternalDataPropertyChangeEvent.EXTERNAL_DATA_PROPERTY_CHANGE, false, false, PropertyChangeEventKind.UPDATE , "waiting", !_waiting, _waiting, this)) + } + } + /** + * @private + * + * */ + private var _correctionMatrix:Matrix; + /** + * @private + * + * */ + private var _vidStream:VideoStream; + /** + * @private + * handles the ready state for a VideoStream as the source of a VideoFill + * @param evt an ExternalDataAsset.STATUS_READY event + */ + private function bitmapChangeHandler(evt:Event):void { + //TODO: consider redispatching from VideoFill + + switch(evt.type) + { + case ExternalDataAsset.STATUS_READY: + var oldValue:Object = bitmapData; + if (_vidStream) { + _vidStream.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,streamUpdater); + } + + if (_alphaRequests) { + for (var _requester:Object in _alphaRequests){ + _vidStream.deregisterCopyTarget(AlphaRequest(_alphaRequests[_requester]).alphaBitmap,true); + + _alphaRequests[_requester]=null; + delete _alphaRequests[_requester]; + } + } + + + _vidStream=VideoStream(evt.target); + bitmapData = _vidStream.content; + + + _vidStream.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,streamUpdater,false,0,true); + //correction matrix is to allow for pixelmargins on the video bitmap (useful to avoid color bleeds on rotation and scaling + _correctionMatrix=_vidStream.reverseOffset; + + initChange("source", oldValue, bitmapData, this); + waiting = false; + break; + } + } + + /** + * @private + * streamUpdater + * */ + private function streamUpdater(e:PropertyChangeEvent):void{ + var property:String=e.property.toString(); + if (property=="content" || property=="pixelMargin") { + var oldBMP:BitmapData=bitmapData; + + bitmapData = _vidStream.content; + if (_alphaRequests) { + for (var _requester:Object in _alphaRequests){ + _vidStream.deregisterCopyTarget(AlphaRequest(_alphaRequests[_requester]).alphaBitmap,true); + + _alphaRequests[_requester]=null; + delete _alphaRequests[_requester]; + } + } + if (property=="pixelMargin") _correctionMatrix=_vidStream.reverseOffset; + //trigger a redraw + initChange("source",oldBMP,bitmapData,this); + //if (oldBMP) _vidStream.deregisterCopyTarget(oldBMP,true); + } + } + + /** + * Optional loadingLocation reference. Only relevant when a subsequent source assignment is made as + * a url string. Using a LoadingLocation simplifies management of loading from external domains + * and is required if a crossdomain policy file is not in the default location (web root) and with the default name (crossdomain.xml) + * In actionscript, a loadingLocation assignment MUST precede a change in the url assigned to the source property + * If a LoadingLocation is being used, the url assigned to the source property MUST be relative to the base path + * defined in the LoadingLocation, otherwise loading will fail. + * If a LoadingLocation is NOT used and the source property assignment is an external domain url, then the crossdomain permissions + * must exist in the default location and with the default name crossdomain.xml, otherwise loading will fail. + */ + public function get loadingLocation():LoadingLocation { return _loadingLocation; } + + public function set loadingLocation(value:LoadingLocation):void + { + if (value) _loadingLocation = value; + } + + [Bindable(event="propertyChange")] + /** + * The source used for the Video fill. + * The source can either be a VideoStream instance or a url.
    + * NOT YET FUNCTIONING (BUT COMING):A url string, if used, can be either a relative url (relative within the local domain or relative to a LoadingLocation + * specified in the loadingLocation property) or absolute with no LoadingLocation (see loadingLocation property) + **/ + public function get source():Object { return bitmapData; } + public function set source(value:Object):void { + + var oldValue:Object = bitmapData; + var v:VideoStream; + if (!value) { + bitmapData = null; + if (_vidStream) _vidStream.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,streamUpdater); + if (oldValue!=null) initChange("source", oldValue, null, this); + return; + } + + if (value is VideoStream) { + v=VideoStream(value); + if (v.content) { + bitmapData = value.content as BitmapData; + if (_vidStream) { + _vidStream.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,streamUpdater); + _vidStream.removeEventListener(VideoStream.STATUS_READY, bitmapChangeHandler); + } + _vidStream=v; + _vidStream.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,streamUpdater,false,0,true); + initChange("source", oldValue, bitmapData, this); + return; + } else { + //trace('waiting for READY:'+value.id) + VideoStream(value).addEventListener(VideoStream.STATUS_READY, bitmapChangeHandler,false,0,true) + waiting = true; + return; + } + } + else if (value is String) + { + //assume url string for a VideoStream + //and wait for isInitialized to check/access loadingLocation mxml assignment + //if not isInitialized after 5 ms, assume actionscript instantiation and not mxml (5 ms is arbitrary) + if (!isInitialized && instantiationTimer) { + instantiationTimer--; + setTimeout( + function():void {source = value }, 1); + } else { + v = new VideoStream(); + //ExternalBitmapData.getUniqueInstance(value as String, _loadingLocation); + v.url = value as String; + source = v; + } + return; + + } + else + { + //option: + //source = null; + //or: + bitmapData = null; + if (oldValue!=null) initChange("source", oldValue, null, this); + return; + } + } + + /** + * @private + * reference to the requesting geometry + **/ + private var _requester:IGeometryComposition; + /** + * @private + * reference to the requesting geometry + **/ + public function set requester(value:IGeometryComposition):void + { + _requester = value; + } + /** + * @private + * _lastRect + * */ + private var _lastRect:Rectangle; + /** + * Provides access to the last rectangle that was relevant for this fill. + */ + public function get lastRectangle():Rectangle { + return (_lastRect)?_lastRect.clone():null; + } + /** + * @private + * _lastContext + * */ + private var _lastContext:Graphics; + /** + * @private + * _lastArgs + * */ + private var _lastArgs:Array = []; + + /** + * Provide access to the lastArgs array + */ + public function get lastArgs():Array { + return _lastArgs; + } + + /** + * Provides quick access to a cached function for restarting the last used fill either in the last used context, or, if a context is provided as an argument, + * then to an alternate context. If no last used context is available then this will do nothing; + */ + public function get restartFunction():Function { + var copy:Array = _lastArgs.concat(); + var last:Graphics = _lastContext; + return function(alternate:Graphics = null):void { + if (alternate) alternate.beginBitmapFill.apply(alternate, copy); + else if (last) last.beginBitmapFill.apply(last,copy); + } + + } + + /** + * Begins the Videofill. + **/ + public function begin(graphics:Graphics, rc:Rectangle):void { + + if(!bitmapData) { + return; + } + if (_coordType == "absolute") rc= new Rectangle(x, y, width, height); + else if (_coordType == "ratio") rc= new Rectangle(rc.x + x * rc.width, rc.y + y * rc.height, width * rc.width, height * rc.height); + var template:BitmapData = bitmapData; + var repeat:Boolean = true; + var positionX:Number = 0; + var positionY:Number = 0; + var deLetterBoxing:Boolean; + + var matrix:Matrix = _correctionMatrix? _correctionMatrix.clone():new Matrix(); + + if (_vidStream && _vidStream.detectLetterBox && _vidStream.isLetterBoxed){ + matrix.translate(-_vidStream.letterBoxContent.x,-_vidStream.letterBoxContent.y); + deLetterBoxing=true; + } + + if (_insetFromStroke && _requester && (_requester as Geometry).stroke){ + var strokeoffset:uint; + strokeoffset = int((_requester as Geometry).stroke.weight *0.5); + + // for a zero weight stroke, give it a 1 pixel offset + if (!strokeoffset) strokeoffset = 1; + rc = rc.clone(); + rc.inflate( -strokeoffset, -strokeoffset); //inset by strokeoffset - used for scaling if needed + + } + var sx:Number; + var sy:Number; + var tx:Number; + var ty:Number; + var twidth:Number; + var theight:Number; + if (_targetSetting) + { + //allow repeating in both directions only. + repeat = (_repeatX == VideoFill.REPEAT && _repeatY == VideoFill.REPEAT); + switch(_targetSetting) + { + case 1: + //targetbounds + twidth= deLetterBoxing? _vidStream.letterBoxContent.width :(template.width+matrix.tx*2); + theight= deLetterBoxing? _vidStream.letterBoxContent.height :(template.height+matrix.ty*2); + sx = (rc.width)/twidth; + sy = (rc.height)/theight; + + matrix.scale(sx,sy); + + matrix.translate(rc.x, rc.y); + break; + case 2: + //if match targetboundsmaintainaspectratio, then centre it to the target bounds + twidth= deLetterBoxing? _vidStream.letterBoxContent.width :(template.width+matrix.tx*2); + theight= deLetterBoxing? _vidStream.letterBoxContent.height :(template.height+matrix.ty*2); + + sx = (rc.width)/twidth; + sy = (rc.height)/theight; + tx=(sx>sy)? (sx-sy)*twidth/2 :0; + ty=(sy>sx)? (sy-sx)*theight/2 :0; + + if (sx>sy) sx=sy; + if (sy>sx) sy=sx; + + matrix.scale(sx,sy); + + matrix.translate(rc.x+tx, rc.y+ty); + + break; + case 3 : + //center target + matrix.identity(); //don't need to compensate for the offset here + //no scaling, just positioning: + //deLetterBoxing has no effect here as it is assumed that a letterbox is centered (this may not be true all the time perhaps) + matrix.translate(rc.x+(rc.width-bitmapData.width)/2,rc.y+(rc.height-bitmapData.height)/2) + + break; + default: + + + break; + } + + } + + else { + repeat = (((repeatX == VideoFill.REPEAT) && (repeatY == VideoFill.REPEAT)) || ((repeatX == VideoFill.STRETCH) && (repeatY == VideoFill.REPEAT)) || ((repeatX == VideoFill.REPEAT) && (repeatY == VideoFill.STRETCH))); + // deal with stretching + if(repeatX == VideoFill.STRETCH || repeatY == VideoFill.STRETCH) { + twidth= deLetterBoxing? _vidStream.letterBoxContent.width :(template.width+matrix.tx*2); + theight= deLetterBoxing? _vidStream.letterBoxContent.height :(template.height+matrix.ty*2); + sx = repeatX == STRETCH ?(rc.width)/twidth : 1; + sy = repeatY == STRETCH ? (rc.height)/theight :1; + if (sx!=1 || sy!=1) matrix.scale(sx, sy); + } + matrix.translate(rc.x, rc.y); + + } + + + if (_scaleX!=1 || _scaleY!=1) matrix.scale(_scaleX, _scaleY); + if (_rotation) matrix.rotate(_rotation*(Math.PI/180)); + + + var regPoint:Point; + var transformRequest:ITransform; + var tempmat:Matrix; + //handle layout transforms - only renderLayouts so far + if (_requester && (_requester as Geometry).hasLayout) { + var geom:Geometry = _requester as Geometry; + if (geom._layoutMatrix) matrix.concat( geom._layoutMatrix); + } + if (_transform && ! _transform.isIdentity) { + tempmat= new Matrix(); + regPoint = _transform.getRegPointForRectangle(rc); + tempmat.translate(-regPoint.x,-regPoint.y); + tempmat.concat(_transform.transformMatrix); + tempmat.translate( regPoint.x,regPoint.y); + matrix.concat(tempmat); + } + if (_requester && ((transformRequest = (_requester as Geometry).transform) || (_requester as Geometry).transformContext)) { + if (transformRequest) matrix.concat(transformRequest.getTransformFor(_requester)); + else matrix.concat((_requester as Geometry).transformContext); + //remove the requester reference + } + + var csAlpha:Number = CommandStack.currentAlpha; + var alpha:Number = this.alpha; + if (csAlpha != 1) { alpha *= csAlpha; } + + //TODO: consider an approach keyed by alpha key as well so that if an alpha key version exists and it has another requester it is reused rather than reinstanced per requester + if (alpha<0.997 && _requester){ + var key:int = alpha*255; + var _alphaBitmapData:BitmapData; + if (!_alphaRequests)_alphaRequests=new Dictionary(true); + + //this will not work as expected with repeaters that use alpha modifiers....but such use should be avoided in any case for videofill for performance reasons + if (!_alphaRequests[_requester]){ + //make a quick copy: + _alphaBitmapData = new BitmapData(template.width,template.height,true,key<<24); + _alphaBitmapData.copyPixels(template,template.rect,new Point(0,0),_alphaBitmapData,new Point(0,0)); + //set up the recurring alpha request for the VideoStream + _alphaRequests[_requester]=new AlphaRequest(_alphaBitmapData,new ColorTransform(1,1,1,key/255)); + _vidStream.registerCopyTarget(_alphaBitmapData,AlphaRequest(_alphaRequests[_requester]).colorTransform); + } else { + //update the colortransform on the existing alpharequest + AlphaRequest(_alphaRequests[_requester]).colorTransform.alphaMultiplier=key/255; + _alphaBitmapData=AlphaRequest(_alphaRequests[_requester]).alphaBitmap; + } + if (_vidStream.isPaused) _vidStream.requestPausedBitmapdataUpdate(); + template=_alphaBitmapData; + + } else if (_alphaRequests && _alphaRequests[_requester]) { + //deregister and delete, because we're not using it + _vidStream.deregisterCopyTarget(AlphaRequest(_alphaRequests[_requester]).alphaBitmap,true); + _alphaRequests[_requester]=null; + delete _alphaRequests[_requester]; + } + + _lastArgs.length = 0; + _lastArgs[0] = template; + _lastArgs[1] = matrix; + _lastArgs[2] = repeat; + _lastArgs[3] = smooth; + _lastContext = graphics; + _lastRect = rc; + + if (graphics) graphics.beginBitmapFill(template, matrix, repeat, smooth); + _requester = null; + } + + /** + * Ends the Video fill. + **/ + public function end(graphics:Graphics):void { + graphics.endFill(); + } + + /** + * @private + * _transform + * */ + private var _transform:ITransform; + + [Bindable(event="propertyChange")] + /** + * Defines the transform object that will be used for + * altering this VideoFill object. + **/ + public function get transform():ITransform{ + return _transform; + } + public function set transform(value:ITransform):void{ + + if(_transform != value){ + var oldValue:Object=_transform; + if(_transform){ + if(_transform.hasEventManager){ + _transform.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + _transform = value; + if(enableEvents){ + _transform.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler,false,0,true); + } + //call local helper to dispatch event + initChange("transform", oldValue, _transform, this); + } + } + + /** + * @private + * */ + private var _alpha:Number=1; + + [Bindable(event="propertyChange")] + /** + * an alpha property that will be applied to this fill. + **/ + public function get alpha():Number{ + return _alpha; + } + public function set alpha(value:Number):void{ + //clamp to valid range + if (value<0) value=0; + if (value>1) value=1; + if(_alpha != value){ + var oldValue:Number=_alpha; + _alpha = value; + //call local helper to dispatch event + initChange("alpha", oldValue, _alpha, this); + } + } + + /** + * @private + * propertyChangeHandler + * */ + private function propertyChangeHandler(event:PropertyChangeEvent):void + { + dispatchEvent(event); + } + + } +} + +//---------------------------------------- +import flash.display.BitmapData; +import flash.geom.ColorTransform; + + +//local alpharequest objects +class AlphaRequest{ + /** + * @private + * */ + public var colorTransform:ColorTransform; + /** + * @private + * */ + public var alphaBitmap:BitmapData; + /** + * @private + * */ + public function AlphaRequest(b:BitmapData=null,c:ColorTransform=null):void{ + colorTransform=c; + alphaBitmap=b; + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/paint/VideoFill.png b/Degrafa/com/degrafa/paint/VideoFill.png new file mode 100644 index 0000000..0fef06c Binary files /dev/null and b/Degrafa/com/degrafa/paint/VideoFill.png differ diff --git a/Degrafa/com/degrafa/paint/palette/AutoPalette.as b/Degrafa/com/degrafa/paint/palette/AutoPalette.as new file mode 100644 index 0000000..b293caa --- /dev/null +++ b/Degrafa/com/degrafa/paint/palette/AutoPalette.as @@ -0,0 +1,113 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.paint.palette{ + import flash.utils.Dictionary; + + import mx.events.PropertyChangeEvent; + + [Bindable] + /** + * Base class for auto palettes not intended to be instanced directly. + **/ + public dynamic class AutoPalette extends Palette + { + public function AutoPalette(){ + super(); + addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,onChange); + } + + /** + * Triggeres the re generation of entry items when internal properties change. + **/ + public function onChange(event:PropertyChangeEvent):void{ + generatePalette(); + } + + private var _entryPrefix:String; + /** + * The prefix to use for item names so that they can be properly refferenced. + * If not specified a default value of "pal_" is used. The items can then be + * retrived via myPalette.pal_1 + **/ + public function get entryPrefix():String{ + if(!_entryPrefix){return "pal_";} + return _entryPrefix; + } + public function set entryPrefix(value:String):void{ + if(_entryPrefix != value){ + _entryPrefix = value; + } + } + + private var _requestedSize:int; + /** + * The number of entries to be generated. Only used with AutoPalettes. + * If not specified the default value of 12 is used. + **/ + public function get requestedSize():int{ + if(!_requestedSize){return 12;} + return _requestedSize; + } + public function set requestedSize(value:int):void{ + if(_requestedSize != value){ + _requestedSize = value; + // Jason, I put this line in below because it appears the paletteEntries does not get cleared out, and subsequent changes to requestedSize were simply appending entries. + // Tom G. 8/24/09 + paletteEntries=new Dictionary(); + } + } + + protected var currentLength:int=0; + + /** + * Overriden in subclasses. Does the work required to populate the palette. + **/ + protected function generatePalette():void{} + + /** + * Appends the passed array of values to the palette entries array and dictionary. + **/ + protected function appendItems(value:Array):void{ + + var offset:int =currentLength + + for(var i:int=0;i>16) & 0xFF; + } + + /** + * Get the green component of the given color. + */ + public static function green(color:int):int { + return (color>>8) & 0xFF; + } + + /** + * Get the blue component of the given color. + **/ + public static function blue(color:int):int { + return color & 0xFF; + } + + /** + * Get the alpha component of the given color. + **/ + public static function alpha(color:int):int { + return (color>>24) & 0xFF; + } + + /** + * Set the alpha component of the given color. + */ + public static function setAlpha(color:uint, alpha:Number):uint { + return rgba(red(color), green(color), blue(color), alpha); + } + + /** + * Set the saturation of an input color. + */ + public static function saturate(color:uint, saturation:Number):uint{ + var hsb:Object = RGBtoHSB(red(color), green(color), blue(color)); + return hsb(hsb.h, saturation, hsb.b); + } + + + /** + * Get a darker shade of an input color. + */ + public static function darker(color:uint):uint { + return rgba(Math.max(0, (baseScale*red(color))), + Math.max(0, (baseScale*green(color))), + Math.max(0, (baseScale*blue(color))), + alpha(color)); + } + + /** + * Get a brighter shade of an input color. + */ + public static function brighter(color:uint):uint { + var r:int = red(color); + var g:int = green(color); + var b:int = blue(color); + var i:int = (1.0/(1.0-baseScale)); + + if ( r == 0 && g == 0 && b == 0) { + return rgba(i, i, i, alpha(color)); + } + if ( r > 0 && r < i ) r = i; + if ( g > 0 && g < i ) g = i; + if ( b > 0 && b < i ) b = i; + + return rgba(Math.min(255, (r/baseScale)), + Math.min(255, (g/baseScale)), + Math.min(255, (b/baseScale)), + alpha(color)); + } + + /** + * Get a desaturated shade of an input color. + */ + public static function desaturate(color:uint):uint { + var a:int = color & 0xff000000; + var r:Number = ((color & 0xff0000) >> 16); + var g:Number = ((color & 0x00ff00) >> 8); + var b:Number = (color & 0x0000ff); + + r *= 0.2125; // red band weight + g *= 0.7154; // green band weight + b *= 0.0721; // blue band weight + + var gray:uint = Math.min(((r+g+b)),0xff) & 0xff; + return a | (gray << 16) | (gray << 8) | gray; + } + + /** + * Adjust the color brightness by a given value. From Adobe Flex. + */ + public static function adjustBrightness(color:uint, value:Number):uint{ + var r:Number = Math.max(Math.min(((color >> 16) & 0xFF) + value, 255), 0); + var g:Number = Math.max(Math.min(((color >> 8) & 0xFF) + value, 255), 0); + var b:Number = Math.max(Math.min((color & 0xFF) + value, 255), 0); + return (r << 16) | (g << 8) | b; + } + + /** + * Adjust the color brightness by a given value. From Adobe Flex. + */ + public static function adjustBrightness2(color:uint, value:Number):uint{ + var r:Number; + var g:Number; + var b:Number; + + if (value == 0) + return color; + + if (value < 0) + { + value = (100 + value) / 100; + r = ((color >> 16) & 0xFF) * value; + g = ((color >> 8) & 0xFF) * value; + b = (color & 0xFF) * value; + } + else // bright > 0 + { + value /= 100; + r = ((color >> 16) & 0xFF); + g = ((color >> 8) & 0xFF); + b = (color & 0xFF); + + r += ((0xFF - r) * value); + g += ((0xFF - g) * value); + b += ((0xFF - b) * value); + + r = Math.min(r, 255); + g = Math.min(g, 255); + b = Math.min(b, 255); + } + + return (r << 16) | (g << 8) | b; + } + + /** + * Convert hsb color value to rgb. + **/ + public static function HSBtoRGB(h:Number,s:Number,v:Number):uint{ + var r:int; + var g:int; + var b:int; + + var h:Number = Math.round(h); + var s:Number = Math.round(s*255/100); + var v:Number = Math.round(v*255/100); + + if(s == 0) { + r = g = b = v; + } else { + var t1:Number = v; + var t2:Number = (255-s)*v/255; + var t3:Number = (t1-t2)*(h%60)/60; + if(h==360) h = 0; + if(h<60) {r=t1; b=t2; g=t2+t3} + else if(h<120) {g=t1; b=t2; r=t1-t3} + else if(h<180) {g=t1; r=t2; b=t2+t3} + else if(h<240) {b=t1; r=t2; g=t1-t3} + else if(h<300) {b=t1; g=t2; r=t2+t3} + else if(h<360) {r=t1; g=t2; b=t1-t3} + else {r=0; g=0; b=0} + } + return rgb(r, g, b); + } + + /** + * Convert rbg color value to hsb. + **/ + public static function RGBtoHSB(r:int,g:int,b:int):Object { + var hsb:Object = new Object(); + hsb.b = Math.max(Math.max(r,g),b); + var min:int = Math.min(Math.min(r,g),b); + + hsb.s = (hsb.b <= 0) ? 0 : Math.round(100*(hsb.b - min)/hsb.b); + hsb.b = Math.round((hsb.b /255)*100); + hsb.h = 0; + + if((r==g) && (g==b)) hsb.h = 0; + else if(r>=g && g>=b) hsb.h = 60*(g-b)/(r-b); + else if(g>=r && r>=b) hsb.h = 60 + 60*(g-r)/(g-b); + else if(g>=b && b>=r) hsb.h = 120 + 60*(b-r)/(g-r); + else if(b>=g && g>=r) hsb.h = 180 + 60*(b-g)/(b-r); + else if(b>=r && r>=g) hsb.h = 240 + 60*(r-g)/(b-g); + else if(r>=b && b>=g) hsb.h = 300 + 60*(r-b)/(r-g); + else hsb.h = 0; + hsb.h = Math.round(hsb.h); + return hsb; + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/paint/palette/PresetColorPalette.as b/Degrafa/com/degrafa/paint/palette/PresetColorPalette.as new file mode 100644 index 0000000..5727e97 --- /dev/null +++ b/Degrafa/com/degrafa/paint/palette/PresetColorPalette.as @@ -0,0 +1,73 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.paint.palette{ + + [Bindable] + /** + * A color palette that auto generates one of 3 presets cool, hot or grey. + **/ + public dynamic class PresetColorPalette extends AutoPalette{ + + public function PresetColorPalette(){ + super(); + } + + private var _type:String="cool"; + /** + * The type of color space to use one of cool, hot or grey. + **/ + [Inspectable(category="General", enumeration="cool,hot,grey", defaultValue="cool")] + public function get type():String{ + if(!_type){return "cool";} + return _type; + } + public function set type(value:String):void{ + if(_type != value){ + _type = value; + } + } + + /** + * Generates the entries for this palette. + **/ + override protected function generatePalette():void{ + + //clear the existing palette entries + clear(); + + switch(type){ + case "cool": + appendItems(PaletteUtils.getCoolPalette(this.requestedSize)); + break; + case "hot": + appendItems(PaletteUtils.getHotPalette(this.requestedSize)); + break; + case "grey": + appendItems(PaletteUtils.getGreyScalePalette(this.requestedSize)); + break; + + } + + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/repeaters/GeometryRepeater.as b/Degrafa/com/degrafa/repeaters/GeometryRepeater.as new file mode 100644 index 0000000..1474054 --- /dev/null +++ b/Degrafa/com/degrafa/repeaters/GeometryRepeater.as @@ -0,0 +1,330 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.repeaters +{ + import com.degrafa.IGeometry; + import com.degrafa.core.DegrafaObject; + import com.degrafa.core.collections.RepeaterModifierCollection; + import com.degrafa.geometry.Geometry; + + import flash.display.Graphics; + import flash.events.Event; + import flash.geom.Rectangle; + import flash.utils.getTimer; + + import mx.events.PropertyChangeEvent; + + [IconFile("GeometryRepeater.png")] + + /** + * The GeometryRepeater repeates geometry objects. For each item + * repeated values can be modified through an array of PropertyModifiers. + **/ + [Event(name="iterationChanged", type="flash.events.Event")] + public class GeometryRepeater extends Geometry implements IGeometry { + + private var _bounds:Rectangle; + private var _isDrawing:Boolean=false; + + [Inspectable] + public var renderOnFinalIteration:Boolean=false; + + /** + * GeometryRepeater constructor takes no arguments. + **/ + public function GeometryRepeater(){ + super(); + } + + + private var _count:int=1; + /** + * Denotes how many times object will be repeated. + **/ + public function set count(value:int):void { + var oldValue:int=_count; + _count=value; + invalidated=true; + initChange("count",oldValue,_count,this); + } + public function get count():int { return _count; } + + + /** + * Returns current iteration for a draw cycle + * -1 if not currently drawing + */ + [Bindable(event="iterationChanged")] + public function get iteration():int { + return _curIteration; + } + + private var _curIteration:int=-1; + + private var _modifiers:RepeaterModifierCollection; + /** + * Contains a collection of RepeaterModifiers that will be used to + * repeat instances of the repeaterObject. + **/ + [Inspectable(category="General", arrayType="com.degrafa.repeaters.IRepeaterModifier")] + [ArrayElementType("com.degrafa.repeaters.IRepeaterModifier")] + public function get modifiers():Array{ + initModifiersCollection(); + return _modifiers.items; + } + public function set modifiers(value:Array):void{ + initModifiersCollection(); + _modifiers.items = value; + } + + /** + * Access to the Degrafa fill collection object for this graphic object. + **/ + public function get modifierCollection():RepeaterModifierCollection{ + initModifiersCollection(); + return _modifiers; + } + + /** + * Initialize the collection by creating it and adding an event listener. + **/ + private function initModifiersCollection():void{ + if(!_modifiers){ + _modifiers = new RepeaterModifierCollection(); + + //add a listener to the collection + if(enableEvents){ + _modifiers.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + /** + * Principle event handler for any property changes to a + * geometry object or it's child objects. + **/ + override protected function propertyChangeHandler(event:PropertyChangeEvent):void{ + if(_isDrawing || this.suppressEventProcessing==true) { + this.invalidated=true; + return; + } + super.propertyChangeHandler(event); + } + + //TODO + //DEV: How should we be calculating bounds + //(by the x/y width/height or dynamically based on the repeaters ??) + /** + * The tight bounds of this element as represented by a Rectangle. + * The value does not include children. + **/ + override public function get bounds():Rectangle { + return _bounds + } + + override public function preDraw():void { + if (invalidated) commandStack.length=0; + super.preDraw(); + } + + + /** + * Begins the draw phase for geometry objects. All geometry objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + override public function draw(graphics:Graphics, rc:Rectangle):void { + + preDraw(); + + if(!this.isInitialized){return;} + _isDrawing=true; + + var t:Number=getTimer(); + + //We need to do this before we reset our objects states + calcBounds(); + + var isSuppressed:Boolean=suppressEventProcessing; + + suppressEventProcessing=true; + + //Create a loop that iterates through our modifiers at each stage and applies the modifications to the object + for (var i:int=0; i<_count; i++) { + + _curIteration=i; + + dispatchEvent(new Event("iterationChanged")); + + //Apply our modifiers + if (_modifiers) { + for each (var modifier:IRepeaterModifier in _modifiers.items) { + DegrafaObject(modifier).parent=this; + DegrafaObject(modifier).suppressEventProcessing=true; + if (i==0) modifier.beginModify(geometryCollection); + modifier.apply(); + } + } + //Draw out our changed object + if ((renderOnFinalIteration==true && (i==_count-1)) || !renderOnFinalIteration) { + + if(graphics){ + //t=getTimer(); + super.draw(graphics,rc); + //trace("GeometeryRepeater.inner Draw = " + (getTimer()-t) + "ms"); + } + else{ + if(graphicsTarget){ + for each (var targetItem:Object in graphicsTarget){ + if(targetItem){ + + if(autoClearGraphicsTarget){ + targetItem.graphics.clear(); + } + super.draw(targetItem.graphics,rc); + } + } + } + } + } + } + + //End modifications (which returns the object to its original state + if (_modifiers) { + for each (modifier in _modifiers.items) { + modifier.end(); + DegrafaObject(modifier).suppressEventProcessing=false; + } + } + + suppressEventProcessing=isSuppressed; + + _isDrawing=false; + + _curIteration=-1; + + + this.invalidated=false; + + //super.endDraw(graphics); + //trace("GeometeryRepeater.draw() = " + (getTimer()-t) + "ms"); + } + + /** + * We need to override DegrafaObject here, because we don't want to trigger another change event + * as it would put us in an endless loop with the draw function + */ + override public function dispatchEvent(evt:Event):Boolean{ + + if (evt.type=="iterationChanged") return eventDispatcher.dispatchEvent(evt); + + if(suppressEventProcessing || _isDrawing) { + evt.stopImmediatePropagation(); + this.invalidated=true; + return false; + } + + return eventDispatcher.dispatchEvent(evt); + + } + + + private function calcBounds():void { + _bounds=new Rectangle(); + _bounds.x=x; + _bounds.y=y; + _bounds.width=width; + _bounds.height=height; + } + + //******* + //temporary until bounds for this is figured out + private var _x:Number; + + /** + * The x-axis coordinate of the upper left point of the regular rectangle. If not specified + * a default value of 0 is used. + **/ + override public function get x():Number{ + if(!_x){return 0;} + return _x; + } + override public function set x(value:Number):void{ + if(_x != value){ + _x = value; + invalidated = true; + } + } + + + private var _y:Number; + /** + * The y-axis coordinate of the upper left point of the regular rectangle. If not specified + * a default value of 0 is used. + **/ + override public function get y():Number{ + if(!_y){return 0;} + return _y; + } + override public function set y(value:Number):void{ + if(_y != value){ + _y = value; + invalidated = true; + } + } + + private var _width:Number; + /** + * The width of the regular rectangle. + **/ + [PercentProxy("percentWidth")] + override public function get width():Number{ + if(!_width){return 0;} + return _width; + } + override public function set width(value:Number):void{ + if(_width != value){ + _width = value; + invalidated = true; + } + } + + + private var _height:Number; + /** + * The height of the regular rectangle. + **/ + [PercentProxy("percentHeight")] + override public function get height():Number{ + if(!_height){return 0;} + return _height; + } + override public function set height(value:Number):void{ + if(_height != value){ + _height = value; + invalidated = true; + } + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/repeaters/GeometryRepeater.png b/Degrafa/com/degrafa/repeaters/GeometryRepeater.png new file mode 100644 index 0000000..ea126dc Binary files /dev/null and b/Degrafa/com/degrafa/repeaters/GeometryRepeater.png differ diff --git a/Degrafa/com/degrafa/repeaters/IRepeaterModifier.as b/Degrafa/com/degrafa/repeaters/IRepeaterModifier.as new file mode 100644 index 0000000..b7748aa --- /dev/null +++ b/Degrafa/com/degrafa/repeaters/IRepeaterModifier.as @@ -0,0 +1,35 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.repeaters{ + import flash.geom.Rectangle; + + /** + * IRepeaterModifier is the base interface for objects + * qualifying as property modifiers. + **/ + public interface IRepeaterModifier{ + function beginModify(sourceObject:Object):void + function apply():void + function end():void + function get iteration():Number; + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/repeaters/PropertyModifier.as b/Degrafa/com/degrafa/repeaters/PropertyModifier.as new file mode 100644 index 0000000..aa673d1 --- /dev/null +++ b/Degrafa/com/degrafa/repeaters/PropertyModifier.as @@ -0,0 +1,264 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.repeaters{ + + import com.degrafa.core.DegrafaObject; + import com.degrafa.core.collections.GeometryCollection; + import com.degrafa.geometry.Geometry; + + import flash.geom.Rectangle; + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("PropertyModifier.png")] + + /** + * PropertyModifier is used to specify changes that should + * occur on the geometry or subsequent objects being repeated. + **/ + public class PropertyModifier extends DegrafaObject implements IRepeaterModifier{ + + /** + * The source object we will apply mods to. + */ + private var _sourceObject:Object; + + /** + * the actual objects that gets modified (could be a child of the source object) + */ + private var _targetObjects:Array; + + /** + * the related properties of the targetObjects that gets its value changed + */ + private var _targetProperties:Array; + + /** + * the original values of the target objects property + */ + private var _originalValues:Array; + + private var _iteration:Number=0; + private var _modifyInProgress:Boolean=false; + + /** + * The PropertyModifier constructor. + */ + public function PropertyModifier(){ + super(); + } + + /** + * The current iteration of an active modification. + */ + public function get iteration():Number { return _iteration; } + + private var _targets:Array + /** + * An array of targets. + */ + public function set targets(value:Array):void { + _targets=value; + } + public function get targets():Array { return _targets } + + + private var _property:String; + /** + * This is the property we intend to modify when we iterate. + */ + public function set property(value:String):void { + _property=value; + } + public function get property():String { return _property } + + private var _modifier:Object; + /** + * This represents how we will be modifierting the property. + */ + public function set modifier(value:Object):void { + var oldValue:Object=_modifier; + _modifier=value; + initChange("modifier",oldValue,_modifier,this); + } + public function get modifier():Object { return _modifier }; + + public static var MODIFIER_ADD:String="add"; + public static var MODIFIER_NONE:String="none"; + public static var MODIFIER_SUBTRACT:String="subtract"; + public static var MODIFIER_MULTIPLY:String="multiply"; + public static var MODIFIER_DIVIDE:String="divide"; + + private var _modifierOperator:String="add"; + /** + * How to apply the modifier for each iteration. + */ + [Inspectable(category="General", enumeration="add,subtract,multiply,divide,none" )] + public function set modifierOperator(value:String):void { + _modifierOperator=value; + } + public function get modifierOperator():String { return _modifierOperator; } + + /** + * This tells the modifier that it will be doing iterations and + * modifying the source object. + */ + public function beginModify(sourceObject:Object):void { + //Expects a geometry array + + if (_modifyInProgress) return; + _sourceObject=sourceObject; + setTargetProperty(_sourceObject); + _iteration=0; + _modifyInProgress=true; + this.suppressEventProcessing=true; + } + + /** + * This ends the modification loop and we need to set + * back our modified property to its original state. + */ + public function end():void { + if (!_modifyInProgress) return; + var i:uint; + for (i=0;i<_targetObjects.length;i++) { + _targetObjects[i][_targetProperties[i]]=_originalValues[i]; + } + + for (i=0;i0) + _targetObjects[i][_targetProperties[i]]+=Number(tempModifier); + else if (_modifierOperator==PropertyModifier.MODIFIER_SUBTRACT && _iteration>0) + _targetObjects[i][_targetProperties[i]]-=Number(tempModifier); + else if (_modifierOperator==PropertyModifier.MODIFIER_MULTIPLY && _iteration>0) + _targetObjects[i][_targetProperties[i]]*=Number(tempModifier); + else if (_modifierOperator==PropertyModifier.MODIFIER_DIVIDE && _iteration>0) + _targetObjects[i][_targetProperties[i]]/=Number(tempModifier); + else if (_modifierOperator != PropertyModifier.MODIFIER_ADD && _modifierOperator != PropertyModifier.MODIFIER_SUBTRACT && _modifierOperator != PropertyModifier.MODIFIER_MULTIPLY && _modifierOperator != PropertyModifier.MODIFIER_DIVIDE) + _targetObjects[i][_targetProperties[i]]=tempModifier; + } + } + + _iteration++; + + + } + + /** + * We want to find the property we are modifying and cache the property. + * If the sourceObject has changed then we need to find the property again + * We store the object in _targetObject + * And the property name in _targetProperty + * This is an easy way to keep a reference versus trying to cast one. + */ + private function setTargetProperty(sourceObject:Object):void { + + //Clear our targets + _targetObjects=new Array(); + _targetProperties=new Array(); + _originalValues=new Array(); + + if (_targets==null) _targets=new Array(); + + //Set our initial source object + if (sourceObject is GeometryCollection) { + if (_targets.length==0) + _targets.push(Geometry(GeometryCollection(sourceObject).items[0])); + } + else { + _targets.push(sourceObject); + } + + //Go through our source objects and find the correct objects. + for (var i:int=0;i<_targets.length;i++) { + + var targetObject:Object=_targets[i]; + var targetProperty:String; + + if (targetObject is Geometry) { + //We want to make sure we are part of the source object so we dont update properties of other objects + if (Geometry(targetObject).parent && Geometry(targetObject).parent!=this.parent) continue; + Geometry(targetObject).suppressEventProcessing=true; + } + else if (targetObject==null) continue; + + if (_property.indexOf(".") < 0) { + if (_property.indexOf("[")<0) + targetProperty = _property; + else { + targetProperty = _property.substring(_property.indexOf("[") + 1, _property.indexOf("]") ); + targetObject = targetObject[_property.substr(0, _property.indexOf("[") )]; + } + } + else { + //We must have a property chain lets use it + var propChain:Array=_property.split(/[\.\[]/); + + for (var y:int = 0; y < propChain.length - 1; y++) { + if (propChain[y].indexOf("]") != -1) propChain[y] = propChain[y].substr(0, propChain[y].indexOf("]") ); + targetObject = targetObject[propChain[y]]; + } + if (propChain[y].indexOf("]") != -1) propChain[y] = propChain[y].substr(0, propChain[y].indexOf("]") ); + targetProperty=propChain[y]; + } + _targetObjects.push(targetObject); + _targetProperties.push(targetProperty); + _originalValues.push(targetObject[targetProperty]); + } + + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/repeaters/PropertyModifier.png b/Degrafa/com/degrafa/repeaters/PropertyModifier.png new file mode 100644 index 0000000..4483ea8 Binary files /dev/null and b/Degrafa/com/degrafa/repeaters/PropertyModifier.png differ diff --git a/Degrafa/com/degrafa/skins/CSSSkin.as b/Degrafa/com/degrafa/skins/CSSSkin.as new file mode 100644 index 0000000..9fd2d1f --- /dev/null +++ b/Degrafa/com/degrafa/skins/CSSSkin.as @@ -0,0 +1,926 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 Jason Hawryluk, Juan Sanchez, Andy McIntosh, Ben Stucki +// and Pavan Podila. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.skins +{ + + import com.degrafa.core.Measure; + import com.degrafa.core.utils.StyleUtil; + import com.degrafa.geometry.AdvancedRectangle; + import com.degrafa.paint.BitmapFill; + import com.degrafa.paint.BlendFill; + import com.degrafa.paint.ComplexFill; + import com.degrafa.paint.SolidFill; + + import flash.geom.Rectangle; + + import mx.core.EdgeMetrics; + import mx.graphics.GradientEntry; + import mx.graphics.IFill; + import mx.graphics.LinearGradient; + import mx.graphics.SolidColor; + import mx.skins.Border; + + + //************************************************* + // Style MetaData + //************************************************* + + // CSS3 Based Stles + // http://www.w3.org/TR/2005/WD-css3-background-20050216/ + + /** + * Sets the background color of an element. + */ + [Style(name="backgroundColor")] + + + // woops + // Images are drawn with the first specified one on top (closest to the user) and each subsequent image behind the previous one. + + /** + * Sets the background image(s) of an element, or 'none' for no image.. + * The 'background-color' is painted below all the images. + */ + [Style(name="backgroundImage", enumeration="none")] // need to implement none + + /** + * Values have the following meanings: + *
    + *
    repeat-x
    + *
    Equivalent to 'repeat no-repeat'.
    + *
    repeat-y
    + *
    Equivalent to 'no-repeat repeat'
    + *
    + * Otherwise, if there is one value, it sets both the horizontal and vertical repeat. If there are two, the first one is for the horizontal direction, the second for the vertical one. As follows: + *
    + *
    repeat
    + *
    The image is repeated in this direction as often as needed to cover the area. The image is clipped at the border or padding edge (depending on 'background-clip').
    + *
    no-repeat
    + *
    The image is not repeated in this direction. If it is too large, the image is clipped at the border or padding edge (depending on 'background-clip').
    + *
    space
    + *
    The image is repeated as often as it will fit without being clipped and then the images are spaced out to fill the area. + * The value of 'background-position' for this direction is ignored.
    + *
    + * @default "repeat" + */ + [Style(name="backgroundRepeat", enumeration="repeat,repeat-x,repeat-y,no-repeat,space")] + + // [Style(name="backgroundAttachment", enumeration="scroll,fixed,local")] // requires consideration + + /** + * If a background image has been specified, this property specifies its initial position. + */ + [Style(name="backgroundPosition", enumeration="top,bottom,left,right,center")] + + // [Style(name="backgroundClip", enumeration="border,padding")] // not yet implemented + // [Style(name="backgroundOrigin", enumeration="border,padding,content")] // not yet implemented + // [Style(name="backgroundSize", enumeration="auto,round")] // not yet implemented (? conflict with repeat: stretch ?) + // [Style(name="backgroundBreak", enumeration="bounding-box,each-box,continuous")] // technical limitation? + + + // [Style(name="border")] // not yet implemented + // [Style(name="borderTop")] // not yet implemented + // [Style(name="borderRight")] // not yet implemented + // [Style(name="borderBottom")] // not yet implemented + // [Style(name="borderLeft")] // not yet implemented + + /** + * Shorthand that sets the four 'border-*-width' properties. + * If it has four values, they set top, right, bottom and left in that order. If left is missing, it is the same as right; if bottom is missing, it is the same as top; if right is missing, it is the same as top. + */ + [Style(name="borderWidth")] + + [Style(name="borderTopWidth")] + [Style(name="borderRightWidth")] + [Style(name="borderBottomWidth")] + [Style(name="borderLeftWidth")] + + // [Style(name="borderRadius")] // todo: check standard + + [Style(name="borderTopRightRadius")] + [Style(name="borderBottomRightRadius")] + [Style(name="borderBottomLeftRadius")] + [Style(name="borderTopLeftRadius")] + + /** + * Shorthand for the four 'border-*-color' properties. + * The four values set the top, right, bottom and left border, respectively. + * A missing left is the same as right, a missing bottom is the same as top, and a missing right is also the same as top. + * Note: space delimited shorthand properties must be surrounded by quotation marks in Flex CSS. + */ + [Style(name="borderColor")] + [Style(name="borderTopColor")] + [Style(name="borderRightColor")] + [Style(name="borderBottomColor")] + [Style(name="borderLeftColor")] + + // [Style(name="boxShadow")] // not yet implemented + + + // Custom (non-w3c based) Styles + + /** + * Sets the background alpha of an element. + */ + //[Style(name="backgroundAlpha")] // hmm, there is no background-alpha in the css3 spec ??? + + + /** + * Sets the blend mode used to blend a given image layer with the layers behind it. + * @defaul "normal" + */ + [Style(name="backgroundBlend", enumeration="add,alpha,darken,difference,erase,hardlight,invert,layer,lighten,multiply,normal,overlay,screen,subtract")] + + + /** + * A Flex skin for applying advanced styling through W3C based CSS. + */ + public class CSSSkin extends Border + { + + + //***************************************************** + // Private Constants (magic strings) + //***************************************************** + + private static const ASSET_CLASS:String = "assetClass"; + //private static const BACKGROUND_ALPHA:String = "backgroundAlpha"; + private static const BACKGROUND_COLOR:String = "backgroundColor"; + private static const BACKGROUND_IMAGE:String = "backgroundImage"; + private static const BACKGROUND_REPEAT:String = "backgroundRepeat"; + private static const BACKGROUND_POSITION:String = "backgroundPosition"; + private static const BOX_SHADOW:String = "boxShadow"; + private static const BACKGROUND_BLEND:String = "backgroundBlend"; + private static const BORDER_ALPHA:String = "borderAlpha"; + private static const BORDER_TOP_WIDTH:String = "borderTopWidth"; + private static const BORDER_RIGHT_WIDTH:String = "borderRightWidth"; + private static const BORDER_BOTTOM_WIDTH:String = "borderBottomWidth"; + private static const BORDER_LEFT_WIDTH:String = "borderLeftWidth"; + private static const BORDER_WIDTH:String = "borderWidth"; + private static const BORDER_COLOR:String = "borderColor"; + private static const BORDER_TOP_COLOR:String = "borderTopColor"; + private static const BORDER_RIGHT_COLOR:String = "borderRightColor"; + private static const BORDER_BOTTOM_COLOR:String = "borderBottomColor"; + private static const BORDER_LEFT_COLOR:String = "borderLeftColor"; + private static const BORDER_TOP_RIGHT_RADIUS:String = "borderTopRightRadius"; + private static const BORDER_BOTTOM_RIGHT_RADIUS:String = "borderBottomRightRadius"; + private static const BORDER_BOTTOM_LEFT_RADIUS:String = "borderBottomLeftRadius"; + private static const BORDER_TOP_LEFT_RADIUS:String = "borderTopLeftRadius"; + + + //********************************************************** + // Private Variables + //********************************************************** + + private var assetClass:Class; + private var box:AdvancedRectangle = new AdvancedRectangle(); + + private var background:ComplexFill; // all background layers + private var backgroundColor:IFill; // complex or simple + private var backgroundImage:IFill; // complex or simple + private var backgroundPosition:Array = []; // of {x:Measure, y:Measure} + private var backgroundRepeat:Array; // of {x:String, y:String} + private var backgroundBlend:Array; // of strings + private var boxShadow:Array; // of filters + + private var borderTopWidth:Measure; + private var borderRightWidth:Measure; + private var borderBottomWidth:Measure; + private var borderLeftWidth:Measure; + + private var borderTopFill:IFill; + private var borderRightFill:IFill; + private var borderBottomFill:IFill; + private var borderLeftFill:IFill; + + private var borderTopRightRadius:Object; // {x:Measure, y:Measure, curve:Measure} + private var borderBottomRightRadius:Object; // {x:Measure, y:Measure, curve:Measure} + private var borderBottomLeftRadius:Object; // {x:Measure, y:Measure, curve:Measure} + private var borderTopLeftRadius:Object; // {x:Measure, y:Measure, curve:Measure} + + private var assetClassChanged:Boolean; + private var backgroundChanged:Boolean; + private var backgroundColorChanged:Boolean; + private var backgroundImageChanged:Boolean; + private var backgroundPositionChanged:Boolean; + private var backgroundRepeatChanged:Boolean; + private var backgroundBlendChanged:Boolean; + private var boxShadowChanged:Boolean; + private var borderTopWidthChanged:Boolean; + private var borderRightWidthChanged:Boolean; + private var borderBottomWidthChanged:Boolean; + private var borderLeftWidthChanged:Boolean; + private var borderWidthChanged:Boolean; + private var borderTopRightRadiusChanged:Boolean; + private var borderBottomRightRadiusChanged:Boolean; + private var borderBottomLeftRadiusChanged:Boolean; + private var borderTopLeftRadiusChanged:Boolean; + private var borderMetricsChanged:Boolean; + private var borderColorChanged:Boolean; + private var borderTopColorChanged:Boolean; + private var borderRightColorChanged:Boolean; + private var borderBottomColorChanged:Boolean; + private var borderLeftColorChanged:Boolean; + + + //******************************************************** + // Public Properties + //******************************************************** + + private var _borderMetrics:EdgeMetrics; + override public function get borderMetrics():EdgeMetrics { + if(borderMetricsChanged) { + //updateBorderMetrics(); + borderMetricsChanged = false; + } + return _borderMetrics; + } + + + //********************************************************* + // Constructor + //********************************************************* + + public function CSSSkin(){ + super(); + _borderMetrics = EdgeMetrics.EMPTY; + background = new ComplexFill(); + refreshStyles(); + } + + + //************************************************************ + // Invalidation Framework + //************************************************************ + + override public function styleChanged(styleProp:String):void { + super.styleChanged(styleProp); + switch(styleProp) { + case ASSET_CLASS: + assetClassChanged = true; + break; + case BACKGROUND_COLOR: + backgroundColorChanged = true; + invalidateDisplayList(); + break; + case BACKGROUND_IMAGE: + backgroundImageChanged = true; + invalidateDisplayList(); + break; + case BACKGROUND_REPEAT: + backgroundRepeatChanged = true; + invalidateDisplayList(); + break; + case BACKGROUND_POSITION: + backgroundPositionChanged = true; + invalidateDisplayList(); + break; + case BACKGROUND_BLEND: + backgroundBlendChanged = true; + invalidateDisplayList(); + break; + case BOX_SHADOW: + boxShadowChanged = true; + invalidateDisplayList(); + break; + case BORDER_ALPHA: + //var style:Object = getStyle(BORDER_ALPHA); + invalidateDisplayList(); + break; + case BORDER_TOP_WIDTH: + borderTopWidthChanged = true; + borderMetricsChanged = true; + invalidateDisplayList(); + break; + case BORDER_RIGHT_WIDTH: + borderRightWidthChanged = true; + borderMetricsChanged = true; + invalidateDisplayList(); + break; + case BORDER_BOTTOM_WIDTH: + borderBottomWidthChanged = true; + borderMetricsChanged = true; + invalidateDisplayList(); + break; + case BORDER_LEFT_WIDTH: + borderLeftWidthChanged = true; + borderMetricsChanged = true; + invalidateDisplayList(); + break; + case BORDER_WIDTH: + borderWidthChanged = true; + borderMetricsChanged = true; + invalidateDisplayList(); + break; + case BORDER_TOP_RIGHT_RADIUS: + borderTopRightRadiusChanged = true; + invalidateDisplayList(); + break; + case BORDER_BOTTOM_RIGHT_RADIUS: + borderBottomRightRadiusChanged = true; + invalidateDisplayList(); + break; + case BORDER_BOTTOM_LEFT_RADIUS: + borderBottomLeftRadiusChanged = true; + invalidateDisplayList(); + break; + case BORDER_TOP_LEFT_RADIUS: + borderTopLeftRadiusChanged = true; + invalidateDisplayList(); + break; + case BORDER_COLOR: + borderColorChanged = true; + invalidateDisplayList(); + break; + case BORDER_TOP_COLOR: + borderTopColorChanged = true; + invalidateDisplayList(); + break; + case BORDER_RIGHT_COLOR: + borderRightColorChanged = true; + invalidateDisplayList(); + break; + case BORDER_BOTTOM_COLOR: + borderBottomColorChanged = true; + invalidateDisplayList(); + break; + case BORDER_LEFT_COLOR: + borderLeftColorChanged = true; + invalidateDisplayList(); + break; + } + + } + + override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void { + + //super.updateDisplayList(unscaledWidth, unscaledHeight); + commitStyles(); + graphics.clear(); + + // AdvancedBox will do the actual drawing. + // It will also optimize for performance where it can. + if(borderLeftWidth != null) { + box.leftWidth = borderLeftWidth.relativeTo(unscaledWidth); + } + if(borderTopWidth != null) { + box.topWidth = borderTopWidth.relativeTo(unscaledHeight); + } + if(borderTopWidth != null) { + box.rightWidth = borderRightWidth.relativeTo(unscaledWidth); + } + if(borderTopWidth != null) { + box.bottomWidth = borderBottomWidth.relativeTo(unscaledHeight); + } + + box.leftFill = borderLeftFill; + box.topFill = borderTopFill; + box.rightFill = borderRightFill; + box.bottomFill = borderBottomFill; + + // needs calculation for fill somewhere + /* + var tr:LinearGradient = new LinearGradient(); + tr.entries = [new GradientEntry((box.topFill as SolidColor).color, 0, (box.topFill as SolidColor).alpha), new GradientEntry((box.rightFill as SolidColor).color, 1, (box.rightFill as SolidColor).alpha)]; + tr.angle = -15; + + var bl:LinearGradient = new LinearGradient(); + bl.entries = [new GradientEntry((box.leftFill as SolidColor).color, 0, (box.leftFill as SolidColor).alpha), new GradientEntry((box.bottomFill as SolidColor).color, 1, (box.bottomFill as SolidColor).alpha)]; + bl.angle = 45; + */ + + /* + box.topLeftFill = createTransition(box.leftFill, box.topFill, -45) + box.topRightFill = createTransition(box.topFill, box.rightFill, 45); // box.rightFill; + box.bottomLeftFill = createTransition(box.leftFill, box.bottomFill, 45); // box.leftFill; + box.bottomRightFill = createTransition(box.bottomFill, box.rightFill, -45); + */ + // Corner fill colours matched to the correct border colours. Noticable when diffent border colours are used (Patrick Groves). + box.topLeftFill = createTransition(box.topFill, box.leftFill, -45); + box.topRightFill = createTransition(box.topFill, box.rightFill, 45); + box.bottomLeftFill = createTransition(box.bottomFill, box.leftFill, 45); + box.bottomRightFill = createTransition(box.bottomFill, box.rightFill, -45); + + box.topLeftRadiusX = (borderTopLeftRadius.x as Measure).relativeTo(unscaledWidth); + box.topLeftRadiusY = (borderTopLeftRadius.y as Measure).relativeTo(unscaledHeight); + box.topRightRadiusX = (borderTopRightRadius.x as Measure).relativeTo(unscaledWidth); + box.topRightRadiusY = (borderTopRightRadius.y as Measure).relativeTo(unscaledHeight); + box.bottomLeftRadiusX = (borderBottomLeftRadius.x as Measure).relativeTo(unscaledWidth); + box.bottomLeftRadiusY = (borderBottomLeftRadius.y as Measure).relativeTo(unscaledHeight); + box.bottomRightRadiusX = (borderBottomRightRadius.x as Measure).relativeTo(unscaledWidth); + box.bottomRightRadiusY = (borderBottomRightRadius.y as Measure).relativeTo(unscaledHeight); + box.draw(graphics, new Rectangle(0, 0, unscaledWidth, unscaledHeight)); + /* + var vm:EdgeMetrics = new EdgeMetrics(); + if(borderTopWidth != null) { + vm.top = borderTopWidth.relativeTo(unscaledHeight); + } + if(borderRightWidth != null) { + vm.right = borderRightWidth.relativeTo(unscaledWidth); + } + if(borderBottomWidth != null) { + vm.bottom = borderBottomWidth.relativeTo(unscaledHeight); + } + if(borderLeftWidth != null) { + vm.left = borderLeftWidth.relativeTo(unscaledWidth); + } + _borderMetrics = vm;*/ + } + + // will act as commitProperties for our styles + protected function commitStyles():void { + if(assetClassChanged) { + updateAssetClass(); + assetClassChanged = true; + } + var backgroundChanged:Boolean = false; + if(backgroundColorChanged) { + updateBackgroundColor(); + backgroundColorChanged = false; + backgroundChanged = true; + } + if(backgroundImageChanged) { + updateBackgroundImage(); + backgroundImageChanged = false; + backgroundChanged = true; + } + if(backgroundRepeatChanged) { + updateBackgroundRepeat(); + backgroundRepeatChanged = false; + } + if(backgroundPositionChanged) { + updateBackgroundPosition(); + backgroundPositionChanged = false; + } + if(backgroundBlendChanged) { + updateBackgroundBlend(); + backgroundBlendChanged = false; + backgroundChanged = true; + } + if(boxShadowChanged) { + updateBoxShadow(); + boxShadowChanged = false; + } + if(borderWidthChanged) { + updateBorderWidth(); + borderWidthChanged = false; + } + if(borderTopWidthChanged) { + updateBorderSide(BORDER_TOP_WIDTH); + borderTopWidthChanged = false; + } + if(borderRightWidthChanged) { + updateBorderSide(BORDER_RIGHT_WIDTH); + borderRightWidthChanged = false; + } + if(borderBottomWidthChanged) { + updateBorderSide(BORDER_BOTTOM_WIDTH); + borderBottomWidthChanged = false; + } + if(borderLeftWidthChanged) { + updateBorderSide(BORDER_LEFT_WIDTH); + borderLeftWidthChanged = false; + } + if(borderTopRightRadiusChanged) { + updateBorderRadius(BORDER_TOP_RIGHT_RADIUS); + borderTopRightRadiusChanged = false; + } + if(borderBottomRightRadiusChanged) { + updateBorderRadius(BORDER_BOTTOM_RIGHT_RADIUS); + borderBottomRightRadiusChanged = false; + } + if(borderBottomLeftRadiusChanged) { + updateBorderRadius(BORDER_BOTTOM_LEFT_RADIUS); + borderBottomLeftRadiusChanged = false; + } + if(borderTopLeftRadiusChanged) { + updateBorderRadius(BORDER_TOP_LEFT_RADIUS); + borderTopLeftRadiusChanged = false; + } + applyBackgroundImageSettings(); + if(backgroundChanged) { + background.fills = new Array(); + ComplexFill.add(backgroundColor, background); + ComplexFill.add(backgroundImage, background); + } + box.backgroundFill = background; + if(borderColorChanged) { + updateBorderColor(); + borderColorChanged = false; + } + if(borderTopColorChanged) { + updateBorderFill(BORDER_TOP_COLOR); + borderTopColorChanged = false; + } + if(borderRightColorChanged) { + updateBorderFill(BORDER_RIGHT_COLOR); + borderRightColorChanged = false; + } + if(borderBottomColorChanged) { + updateBorderFill(BORDER_BOTTOM_COLOR); + borderBottomColorChanged = false; + } + if(borderLeftColorChanged) { + updateBorderFill(BORDER_LEFT_COLOR); + borderLeftColorChanged = false; + } + } + + + //******************************************************************* + // Utility Functions + //******************************************************************* + + private function updateAssetClass():void { + var style:Object = getStyle(ASSET_CLASS); + if(style is Class) { + assetClass = style as Class; + } else { + assetClass = null; + } + } + + private function updateBorderColor():void { + if(StyleUtil.LEGACY == "ALPHA") { return updateBorderColorOld(); } + var style:Object = getStyle(BORDER_COLOR); + var styles:Array = StyleUtil.expandProperty(style, [0, -1, -2, -2]); + for(var i:int = 0; i < 4; i++) { + var fill:IFill = StyleUtil.resolveFill(styles[i]); + (fill as SolidFill).alpha = getStyle(BORDER_ALPHA); + switch(i) { + case 0: + borderTopFill = fill; + break; + case 1: + borderRightFill = fill; + break; + case 2: + borderBottomFill = fill; + break; + case 3: + borderLeftFill = fill; + break; + } + } + } + + private function updateBackgroundColor():void { + var style:Object = getStyle(BACKGROUND_COLOR); + backgroundColor = StyleUtil.resolveFill(style); + } + + private function updateBackgroundImage():void { + var style:Object = resolveAssets(getStyle(BACKGROUND_IMAGE)); + backgroundImage = StyleUtil.resolveFill(style); + } + + private function updateBackgroundRepeat():void { + var style:Object = getStyle(BACKGROUND_REPEAT); + backgroundRepeat = StyleUtil.resolveRepeat(style, []); + } + + private function updateBackgroundPosition():void { + var style:Object = getStyle(BACKGROUND_POSITION); + backgroundPosition = StyleUtil.resolvePosition(style, []); + } + + private function updateBackgroundBlend():void { + var style:Object = getStyle(BACKGROUND_BLEND); + if(style is String) { + backgroundBlend = [style] + } else if(style is Array) { + backgroundBlend = (style as Array).concat(); + if(StyleUtil.LEGACY != "ALPHA") { + backgroundBlend.reverse(); + } + } else { + backgroundBlend = []; + } + } + + private function updateBoxShadow():void { + cleanFilters(boxShadow); + var style:Object = getStyle(BOX_SHADOW); + boxShadow = StyleUtil.resolveShadow(style); + applyFilters(boxShadow); + } + + // border updates + + private function updateBorderWidth():void { + if(StyleUtil.LEGACY == "ALPHA") { return updateBorderWidthOld(); } + var style:Object = getStyle(BORDER_WIDTH); + if(style != null) { + var properties:Array = StyleUtil.expandProperty(style, [0, -1, -2, -2]); + for(var i:int = 0; i < 4; i++) { + var measure:Measure = StyleUtil.resolveMeasure(properties[i]); + switch(i) { + case 0: + borderTopWidth = measure; + break; + case 1: + borderRightWidth = measure; + break; + case 2: + borderBottomWidth = measure; + break; + case 3: + borderLeftWidth = measure; + break; + } + } + } + } + + private function updateBorderSide(token:String):void { + var style:Object = getStyle(token); + var measure:Measure = StyleUtil.resolveMeasure(style); + /*if(measure == null) { + measure = new Measure(0); + }*/ + //if(measure != null) { + switch(token) { + case BORDER_LEFT_WIDTH: + borderLeftWidth = measure; + break; + case BORDER_TOP_WIDTH: + borderTopWidth = measure; + break; + case BORDER_RIGHT_WIDTH: + borderRightWidth = measure; + break; + case BORDER_BOTTOM_WIDTH: + borderBottomWidth = measure; + break; + } + //} + } + + private function updateBorderRadius(token:String):void { + var style:Object = getStyle(token); + var radius:Object = StyleUtil.resolveRadius(style); + switch(token) { + case BORDER_TOP_LEFT_RADIUS: + borderTopLeftRadius = radius; + break; + case BORDER_TOP_RIGHT_RADIUS: + borderTopRightRadius = radius; + break; + case BORDER_BOTTOM_LEFT_RADIUS: + borderBottomLeftRadius = radius; + break; + case BORDER_BOTTOM_RIGHT_RADIUS: + borderBottomRightRadius = radius; + break; + } + } + + private function updateBorderFill(token:String):void { + var style:Object = getStyle(token); + //var fill:IFill = StyleUtil.resolveFill(style, new SolidColor(0xE2E2E2, 0.4)); + var fill:IFill = StyleUtil.resolveFill(style, new SolidColor(0xFFFFFF, 1)); + (fill as SolidColor).alpha = getStyle(BORDER_ALPHA); + switch(token) { + case BORDER_TOP_COLOR: + //borderTopFill = fill; + break; + case BORDER_RIGHT_COLOR: + //borderRightFill = fill; + break; + case BORDER_BOTTOM_COLOR: + //borderBottomFill = fill; + break; + case BORDER_LEFT_COLOR: + //borderLeftFill = fill; + break; + } + } + + private function applyBackgroundImageSettings():void { + if(backgroundImage is ComplexFill) { + var complex:ComplexFill = backgroundImage as ComplexFill; + var length:int = complex.fills.length; + for(var i:int = 0; i < length; i++) { + var fill:IFill = complex.fills[i] as IFill; + if(fill is BitmapFill) { + applyRepeat(fill as BitmapFill, backgroundRepeat[i]); + applyPosition(fill as BitmapFill, backgroundPosition[i]); + } + if(backgroundBlend[i] is String) { + fill = new BlendFill(fill, backgroundBlend[i] as String); + } + complex.fills[i] = fill; + } + } else if(backgroundImage is BitmapFill) { + if(backgroundRepeat != null) { + applyRepeat(backgroundImage as BitmapFill, backgroundRepeat[0]); + } + if(backgroundPosition != null) { + applyPosition(backgroundImage as BitmapFill, backgroundPosition[0]); + } + if(backgroundBlend != null && backgroundBlend[0] is String) { + backgroundImage = new BlendFill(backgroundImage, backgroundBlend[0] as String); + } + } else if(backgroundImage != null) { + if(backgroundBlend[0] is String) { + backgroundImage = new BlendFill(backgroundImage, backgroundBlend[0] as String); + } + } + } + + private function applyRepeat(fill:BitmapFill, repeat:Object):void { + if(repeat != null) { + fill.repeatX = repeat.x as String; + fill.repeatY = repeat.y as String; + } else { + fill.repeatX = BitmapFill.REPEAT; + fill.repeatY = BitmapFill.REPEAT; + } + } + + private function applyPosition(fill:BitmapFill, position:Object):void { + var positionX:Measure; + var positionY:Measure; + if(position != null) { + positionX = position.x as Measure; + positionY = position.y as Measure; + } else { + positionX = new Measure(); + positionY = new Measure(); + } + fill.offsetX = positionX.value; + fill.offsetXUnit = positionX.unit; + fill.offsetY = positionY.value; + fill.offsetYUnit = positionY.unit; + } + + private function applyBlend(fill:IFill, blendMode:String):void { + fill = new BlendFill(fill, blendMode); + } + + private function resolveAssets(value:Object):Object { + if(value is Array) { + return resolveAssetsFromArray(value as Array); + } else if(value is String) { + return resolveAssetsFromString(value as String); + } else { + return value; + } + } + + private function resolveAssetsFromArray(value:Array):Array { + var assets:Array = new Array(); + for each(var item:Object in value) { + if(item is String) { + assets.push(resolveAssetsFromString(item as String)); + } else { + assets.push(item); + } + } + return assets; + } + + private function resolveAssetsFromString(value:String):Object { + var asset:Object; + if(assetClass) { + asset = assetClass[value]; + } + if(asset != null) { + return asset; + } else { + return value; + } + } + + private function cleanFilters(filters:Array):void { + if(parent) { + var temp:Array = parent.filters; + for each(var item:Object in filters) { + var index:int = temp.indexOf(item); + if(index >= 0) { + temp.splice(index, 1); + } + } + //parent.filters = temp; + } + } + + private function createTransition(fill1:IFill, fill2:IFill, angle:Number):IFill { + if(fill1 is SolidColor && fill2 is SolidColor) { + var fill:LinearGradient = new LinearGradient(); + fill.entries = [new GradientEntry((fill1 as SolidColor).color, 0, (fill1 as SolidColor).alpha), new GradientEntry((fill2 as SolidColor).color, 1, (fill2 as SolidColor).alpha)]; + fill.angle = angle; + return fill; + } else { + return fill1; + } + } + + private function applyFilters(filters:Array):void { + if(parent) { + var temp:Array = parent.filters + //parent.filters = temp.concat(filters); + } + } + + private function refreshStyles():void { + styleChanged(ASSET_CLASS); + styleChanged(BACKGROUND_COLOR); + styleChanged(BACKGROUND_IMAGE); + styleChanged(BACKGROUND_REPEAT); + styleChanged(BACKGROUND_POSITION); + styleChanged(BACKGROUND_BLEND); + styleChanged(BOX_SHADOW); + borderTopWidth = new Measure(); + borderRightWidth = new Measure(); + borderBottomWidth = new Measure(); + borderLeftWidth = new Measure(); + //styleChanged(BORDER_TOP_WIDTH); + //styleChanged(BORDER_RIGHT_WIDTH); + //styleChanged(BORDER_BOTTOM_WIDTH); + //styleChanged(BORDER_LEFT_WIDTH); + styleChanged(BORDER_ALPHA); + styleChanged(BORDER_WIDTH); + styleChanged(BORDER_TOP_RIGHT_RADIUS); + styleChanged(BORDER_BOTTOM_RIGHT_RADIUS); + styleChanged(BORDER_BOTTOM_LEFT_RADIUS); + styleChanged(BORDER_TOP_LEFT_RADIUS); + styleChanged(BORDER_COLOR); + styleChanged(BORDER_TOP_COLOR); + styleChanged(BORDER_RIGHT_COLOR); + styleChanged(BORDER_BOTTOM_COLOR); + styleChanged(BORDER_LEFT_COLOR); + } + + // legacy code + + private function updateBorderWidthOld():void { + var style:Object = getStyle(BORDER_WIDTH); + if(style != null) { + var properties:Array = StyleUtil.expandProperty(style, [0, -1, -2, -2]); + for(var i:int = 0; i < 4; i++) { + var measure:Measure = StyleUtil.resolveMeasure(properties[i]); + switch(i) { + case 0: + borderLeftWidth = measure; + break; + case 1: + borderTopWidth = measure; + break; + case 2: + borderRightWidth = measure; + break; + case 3: + borderBottomWidth = measure; + break; + } + } + } + } + + private function updateBorderColorOld():void { + var style:Object = getStyle(BORDER_COLOR); + var styles:Array = StyleUtil.expandProperty(style, [0, -1, -2, -2]); + for(var i:int = 0; i < 4; i++) { + var fill:IFill = StyleUtil.resolveFill(styles[i]); + (fill as SolidFill).alpha = getStyle(BORDER_ALPHA); + switch(i) { + case 0: + borderLeftFill = fill; + break; + case 1: + borderTopFill = fill; + break; + case 2: + borderRightFill = fill; + break; + case 3: + borderBottomFill = fill; + break; + } + } + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/skins/DegrafaSkinMixin.as.inc b/Degrafa/com/degrafa/skins/DegrafaSkinMixin.as.inc new file mode 100644 index 0000000..ac47533 --- /dev/null +++ b/Degrafa/com/degrafa/skins/DegrafaSkinMixin.as.inc @@ -0,0 +1,471 @@ + + private var _data:Object; + /** + * Allows a short hand property setting that is + * specific to and parsed by each geometry object. + * Look at the various geometry objects to learn what + * this setting requires. + **/ + public function get data():Object{ + return _data; + } + public function set data(value:Object):void{ + _data=value; + } + + private var _stroke:IGraphicsStroke; + /** + * Defines the stroke object that will be used for + * rendering this geometry object. + **/ + public function get stroke():IGraphicsStroke{ + return _stroke; + } + public function set stroke(value:IGraphicsStroke):void{ + _stroke = value; + + } + + private var _fill:IGraphicsFill; + /** + * Defines the fill object that will be used for + * rendering this geometry object. + **/ + public function get fill():IGraphicsFill{ + return _fill; + } + public function set fill(value:IGraphicsFill):void{ + _fill=value; + + } + + private var _fills:FillCollection; + [Inspectable(category="General", arrayType="com.degrafa.core.IGraphicsFill")] + [ArrayElementType("com.degrafa.core.IGraphicsFill")] + /** + * A array of IGraphicsFill objects. + **/ + public function get fills():Array{ + initFillsCollection(); + return _fills.items; + } + public function set fills(value:Array):void{ + initFillsCollection(); + _fills.items = value; + } + + /** + * Access to the Degrafa fill collection object for this graphic object. + **/ + public function get fillCollection():FillCollection{ + initFillsCollection(); + return _fills; + } + + /** + * Initialize the collection by creating it and adding an event listener. + **/ + private function initFillsCollection():void{ + if(!_fills){ + _fills = new FillCollection(); + + //add a listener to the collection + if(enableEvents){ + _fills.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + private var _strokes:StrokeCollection; + [Inspectable(category="General", arrayType="com.degrafa.core.IGraphicsStroke")] + [ArrayElementType("com.degrafa.core.IGraphicsStroke")] + /** + * A array of IStroke objects. + **/ + public function get strokes():Array{ + initSrokesCollection(); + return _strokes.items; + } + public function set strokes(value:Array):void{ + initSrokesCollection(); + _strokes.items = value; + + } + + /** + * Access to the Degrafa stroke collection object for this graphic object. + **/ + public function get strokeCollection():StrokeCollection{ + initSrokesCollection(); + return _strokes; + } + + /** + * Initialize the collection by creating it and adding an event listener. + **/ + private function initSrokesCollection():void{ + if(!_strokes){ + _strokes = new StrokeCollection(); + + //add a listener to the collection + if(enableEvents){ + _strokes.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + private var _geometry:GeometryCollection; + [Inspectable(category="General", arrayType="com.degrafa.IGeometryComposition")] + [ArrayElementType("com.degrafa.IGeometryComposition")] + /** + * A array of IGeometryComposition objects. + **/ + public function get geometry():Array{ + initGeometryCollection(); + return _geometry.items; + } + public function set geometry(value:Array):void{ + + initGeometryCollection(); + _geometry.items = value; + + for each (var item:Geometry in _geometry.items){ + item.autoClearGraphicsTarget = false; + item.graphicsTarget = [this]; + } + + } + + /** + * Access to the Degrafa geometry collection object for this geometry object. + **/ + public function get geometryCollection():GeometryCollection{ + initGeometryCollection(); + return _geometry; + } + + /** + * Initialize the geometry collection by creating it and adding an event listener. + **/ + private function initGeometryCollection():void{ + if(!_geometry){ + _geometry = new GeometryCollection(); + + //add a listener to the collection + if(enableEvents){ + _geometry.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + + /** + * Principle event handler for any property changes to a + * graphic object or it's child objects. + **/ + private function propertyChangeHandler(event:PropertyChangeEvent):void{ + dispatchEvent(event); + invalidateDisplayList(); + } + + //not required here but need for interface + /** + * @private + **/ + public function get percentWidth():Number{return 0;} + public function set percentWidth(value:Number):void{} + + /** + * @private + **/ + public function get percentHeight():Number{return 0;} + public function set percentHeight(value:Number):void{} + + /** + * @private + **/ + public function get target():DisplayObjectContainer{return null;} + public function set target(value:DisplayObjectContainer):void{} + + /** + * @private + **/ + public function set graphicsData(value:Array):void{} + public function get graphicsData():Array{return null;} + + + /** + * Ends the draw phase for geometry objects. + * + * @param graphics The current Graphics context being drawn to. + **/ + public function endDraw(graphics:Graphics):void{ + if (fill){ + fill.end(this.graphics); + } + } + + /** + * Begins the draw phase for graphic objects. All graphic objects + * override this to do their specific rendering. + * + * @param graphics The current context to draw to. + * @param rc A Rectangle object used for fill bounds. + **/ + public function draw(graphics:Graphics,rc:Rectangle):void{ + + if(!parent){return;} + + this.graphics.clear(); + + if (geometry){ + for each (var geometryItem:Geometry in _geometry.items){ + + if(geometryItem.state =="" || geometryItem.state ==null){ + if(states.length !=0){ + prepareState(); + } + geometryItem.draw(this.graphics,null); + } + else { + var possibleStates:Array=geometryItem.state.split(" "); + if (possibleStates.length>0) { + for (var i:int=0;i p.y) tempBounds.top = p.y; + if (tempBounds.left > p.x) tempBounds.left = p.x; + if (tempBounds.bottom < p.y) tempBounds.bottom = p.y; + } + return tempBounds; + } + + /** + * Specifies whether this object's matrix is to be re calculated + * on the next request. + **/ + public var invalidated:Boolean; + + private var _data:String; + public function get data():String{ + return _data; + } + public function set data(value:String):void{ + _data=value; + } + + //matrix transform properties that can be selectively exposed via setters in sub-classes + protected var _scaleX:Number = 1; + protected var _scaleY:Number = 1; + protected var _skewY:Number = 0; + protected var _skewX:Number = 0; + protected var _angle:Number = 0; + protected var _tx:Number = 0; + protected var _ty:Number = 0; + + protected var _transformMatrix:Matrix=new Matrix(); + /** + * The internal matrix calculated from the exposed transform properties. + **/ + public function get transformMatrix():Matrix + { + if (!invalidated) return _transformMatrix; + else { + //recreate the matrix based on the transform properties + //the sequence used here will result in a matrix that appears to mimic the behavior exhibited by the Flash IDE + //re-use the matrix instead of creating a new Matrix instance to begin from an identity matrix + _transformMatrix.identity(); + + if (_scaleX!=1 || _scaleY!=1) _transformMatrix.scale(_scaleX, _scaleY); + if (_skewX || _skewY) + { + var skewMat:Matrix = new Matrix(); + skewMat.a = Math.cos(_skewY*Math.PI/180); + skewMat.b = Math.sin(_skewY * Math.PI / 180); + skewMat.c = - Math.sin(_skewX * Math.PI / 180); + skewMat.d = Math.cos(_skewX * Math.PI / 180); + _transformMatrix.concat(skewMat); + } + if (_angle) _transformMatrix.rotate(_angle * Math.PI / 180); + + if (_tx ||_ty) _transformMatrix.translate(_tx, _ty); + } + invalidated = false; + return _transformMatrix; + } + public function set transformMatrix(value:Matrix):void{ + _transformMatrix=value; + } + + protected var _centerX:Number=NaN; + /** + * The center point of the transform along the x-axis. This property is ignored if the registrationPoint property has been set. + **/ + public function get centerX():Number{ + return isNaN(_centerX)? 0:_centerX; + } + public function set centerX(value:Number):void{ + + if(_centerX != value){ + _centerX = value; + invalidated = true; + } + + } + + protected var _centerY:Number=NaN; + /** + * The center point of the transform along the y-axis. This property is ignored if the registrationPoint property has been set. + **/ + public function get centerY():Number { + return isNaN(_centerY)? 0:_centerY; + } + public function set centerY(value:Number):void{ + if(_centerY != value){ + _centerY = value; + invalidated = true; + } + } + + protected var _registrationPoint:String; + [Inspectable(category="General", enumeration="topLeft,centerLeft,bottomLeft,centerTop,center,centerBottom,topRight,centerRight,bottomRight")] + /** + * A value defining one of 9 possible registration points. Takes priority over centerX,centerY. + **/ + public function get registrationPoint():String{ + return _registrationPoint; + } + public function set registrationPoint(value:String):void{ + if(_registrationPoint != value){ + var oldValue:String=_registrationPoint; + _registrationPoint = value; + } + } + + /** + * A check to determine if the center of transfomation has been explicitly set. Required for TransformGroup + * @return boolean true if this Transform object has an explicit setting for it's registration point + */ + public function hasExplicitSetting():Boolean { + return (_registrationPoint || !(isNaN(_centerX) || isNaN(_centerY))); + } + + /** + * Detetermines the transformation registration point based on this transform's settings for an arbitrary Rectangle. Exposed primarily for + * use in Fills. This method returns a point where 0,0 as centerX,centerY (default for a Transform) is the center of the rectangle, because Fill transforms default to the center + * of the Fill target's bounds for their registration Point. + * @param rectangle + * @return a Point representing the registration point settings for this transform, expressed in the context of the rectangle argument + */ + public function getRegPointForRectangle(rectangle:Rectangle):Point + { + var fillRegPoint:Point; + if (_registrationPoint) fillRegPoint= getRegistrationPoint(null, rectangle); + else + { + //use the centerpoint of the rectangle as the 'origin' in this case (best for fills) + fillRegPoint = new Point(rectangle.x+rectangle.width/2+centerX ,rectangle.y+rectangle.height/2+ centerY); + } + return fillRegPoint; + } + + + /** + * Calculates the translation offset based on the set registration point. + **/ + protected function getRegistrationPoint(value:IGeometryComposition,rectangle:Rectangle=null):Point{ + + var regPoint:Point; + + if (value) rectangle = (value as Geometry).hasLayout? Geometry(value).layoutRectangle: Geometry(value).bounds; + + switch(_registrationPoint){ + + case "topLeft": + regPoint = rectangle.topLeft; + break; + case "centerLeft": + regPoint = new Point(rectangle.left,rectangle.y+rectangle.height/2); + break; + case "bottomLeft": + regPoint = new Point(rectangle.left,rectangle.bottom); + break; + case "centerTop": + regPoint = new Point(rectangle.x+rectangle.width/2,rectangle.y); + break; + case "center": + regPoint = new Point(rectangle.x+rectangle.width/2,rectangle.y+rectangle.height/2); + break; + case "centerBottom": + regPoint = new Point(rectangle.x+rectangle.width/2,rectangle.bottom); + break; + case "topRight": + regPoint = new Point(rectangle.right,rectangle.top); + break; + case "centerRight": + regPoint = new Point(rectangle.right,rectangle.y+rectangle.height/2); + break; + case "bottomRight": + regPoint = rectangle.bottomRight; + break; + } + return regPoint; + + } + + /** + * Utility function used internally for accessing the bounds of a transformed object + * @param value - the Geometry object for which the transformed bounds are required + * @return a Rectangle representing the transformed bounds of the Geometry + */ + public function getTransformedBoundsFor(value:IGeometryComposition):Rectangle + { + var requester:Geometry = (value as Geometry); + var trans:Matrix = getTransformFor(value); + return transformBounds(requester.bounds, trans); + + } + + /** + * Retrieves the adjusted matrix for the registration offset based on the Geometry target bounds, + * if this transform is based on a registrationPoint, otherwise based on the centerX and centerY settings + * @param value - the Geometry context for the transform + * @return a flash Matrix representing the transform for the Geometry argument + */ + public function getTransformFor(value:IGeometryComposition):Matrix + { + var offset:Point = (_registrationPoint)? getRegistrationPoint(value):new Point(centerX, centerY); + //first check for a transform history... + //this relies on the nested update sequence from parent to children to work in + //a stable way. + + var geomContext:Geometry = (value as Geometry); + var context:Matrix = geomContext.transformContext; + + if (!context) + { + //check the parent hierachy for the closest ancestor with a transform + while (geomContext.parent) + { + geomContext = (geomContext.parent as Geometry); + if (geomContext.transform) { + context = geomContext.transform.getTransformFor(geomContext); + break; + } + } + } + + var transMat:Matrix = new Matrix(); + transMat.translate( -offset.x, -offset.y); + transMat.concat(transformMatrix); + transMat.translate(offset.x, offset.y) + + if (context) { + transMat.concat(context); + } + return transMat; + + } + /** + * Retrieves the registration offset for the Geometry target. If the registrationPoint property is set, + * this will return the registrationPoint calculated from the geometry target bounds, otherwise it will + * return the centerX and centerY settings for this Transform. + * @param value + * @return + */ + public function getRegPoint(value:IGeometryComposition):Point + { + if (_registrationPoint) return getRegistrationPoint(value); + else return new Point(centerX, centerY); + } + /** + * Boolean value indicating whether this transform will have no effect on the coordinate space of a target + */ + public function get isIdentity():Boolean + { + // make sure we're checking a validated transform + if (invalidated) var forceupdate:Matrix = transformMatrix; + //dev note: this may be useful to avoid the transform conditional branch in commandstack rendering + //need to check fastest way to compare to identity matrix, ie. like this or via toString() + return (_transformMatrix.a == 1 && !_transformMatrix.b && !_transformMatrix.c && _transformMatrix.d == 1 && !_transformMatrix.tx && !_transformMatrix.ty); + } + + //getters are always available from the base class + public function get y():Number + { + return _ty; + } + + public function get x():Number + { + return _tx; + } + + public function get angle():Number + { + return _angle; + } + + public function get scaleX():Number + { + return _scaleX; + } + + public function get scaleY():Number + { + return _scaleY; + } + + public function get skewX():Number + { + return _skewX; + } + + public function get skewY():Number + { + return _skewY; + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/transform/TransformGroup.as b/Degrafa/com/degrafa/transform/TransformGroup.as new file mode 100644 index 0000000..22a237c --- /dev/null +++ b/Degrafa/com/degrafa/transform/TransformGroup.as @@ -0,0 +1,163 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +package com.degrafa.transform{ + import com.degrafa.geometry.Geometry; + import com.degrafa.IGeometryComposition; + import com.degrafa.core.collections.TransformCollection; + import com.degrafa.transform.TransformBase + import flash.geom.Point; + + import flash.geom.Matrix; + + + import mx.events.PropertyChangeEvent; + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("TransformGroup.png")] + + [DefaultProperty("transforms")] + /** + * TransformGroup is a collection of Degrafa Transforms that are processed together + * to generate a composite transform on the requesting geometry and/or fill object. + * The same collection of transforms will generate a different result depending on their sequence. + * + */ + public class TransformGroup extends TransformBase implements ITransform + { + + public function TransformGroup(){ + super(); + } + + private var _transforms:TransformCollection; + [Inspectable(category="General", arrayType="com.degrafa.transform.ITransform")] + [ArrayElementType("com.degrafa.transform.ITransform")] + /** + * A array of ITransform objects. + **/ + public function get transforms():Array{ + initTransformsCollection(); + return _transforms.items; + } + public function set transforms(value:Array):void{ + + initTransformsCollection(); + _transforms.items = value; + } + + /** + * Access to the Degrafa transforms collection object for this geometry object. + **/ + public function get transformCollection():TransformCollection{ + initTransformsCollection(); + return _transforms; + } + + /** + * Initialize the transforms collection by creating it and adding an event listener. + **/ + private function initTransformsCollection():void{ + if(!_transforms){ + _transforms = new TransformCollection(); + + //add a listener to the collection + if(enableEvents){ + _transforms.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,propertyChangeHandler); + } + } + } + + /** + * Principle event handler for any property changes to a + * transforms object or it's child objects. + **/ + private function propertyChangeHandler(event:PropertyChangeEvent):void{ + dispatchEvent(event) + } + + override public function get isIdentity():Boolean + { + //for now override and return false. Need to address this + return false; + } + + override public function getTransformFor(value:IGeometryComposition):Matrix + { + //dev note: this doesn't yet have an invalidation check.. + var groupOffset:Point = (registrationPoint)? getRegistrationPoint(value):new Point(centerX, centerY); + var retMatrix:Matrix = new Matrix(); + var currentOffset:Point=new Point(); + var geomContext:Geometry = (value as Geometry); + var context:Matrix = geomContext.transformContext; + + if (!context) + { + //check the parent hierachy for the closest ancestor with a transform + while (geomContext.parent) + { + geomContext = (geomContext.parent as Geometry); + if (geomContext.transform) { + context = geomContext.transform.getTransformFor(geomContext); + break; + } + } + } + + for each(var matrix:ITransform in transforms) + { + if (matrix.hasExplicitSetting()) currentOffset = matrix.getRegPoint(value) + else currentOffset = groupOffset.clone(); + + currentOffset = retMatrix.transformPoint(currentOffset) + + retMatrix.translate(-currentOffset.x, -currentOffset.y) + retMatrix.concat(matrix.transformMatrix); + retMatrix.translate(currentOffset.x, currentOffset.y) + + + } + + if (context) retMatrix.concat(context); + return retMatrix; + } + + + + + + + //some fills can be directly requesting this for compound transforms, so need to implement it locally in TransformGroup + override public function get transformMatrix():Matrix + { + var retMatrix:Matrix = new Matrix(); + for each(var matrix:ITransform in transforms) + { + retMatrix.concat(matrix.transformMatrix); + } + return retMatrix; + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/transform/TransformGroup.png b/Degrafa/com/degrafa/transform/TransformGroup.png new file mode 100644 index 0000000..2d1be6a Binary files /dev/null and b/Degrafa/com/degrafa/transform/TransformGroup.png differ diff --git a/Degrafa/com/degrafa/transform/TranslateTransform.as b/Degrafa/com/degrafa/transform/TranslateTransform.as new file mode 100644 index 0000000..1ae2575 --- /dev/null +++ b/Degrafa/com/degrafa/transform/TranslateTransform.as @@ -0,0 +1,84 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// + +package com.degrafa.transform{ + + import com.degrafa.transform.TransformBase; + import com.degrafa.transform.ITransform; + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("TranslateTransform.png")] + + [Exclude(name="centerX", kind="property")] + [Exclude(name="centerY", kind="property")] + [Exclude(name="registrationPoint", kind="property")] + + + [Bindable] + /** + * TranslateTransform translates an object in the two-dimensional space. The amount in + * pixels for translating the object is specified through the x and + * y properties. + **/ + public class TranslateTransform extends TransformBase implements ITransform{ + + + public function TranslateTransform(){ + super(); + + } + + //setting these has no effect on TranslateTransform + + override public function set centerX(value:Number):void{} + override public function set centerY(value:Number):void{} + override public function set registrationPoint(value:String):void{} + + + /** + * The value of the translation rendering for this Geometry along the x axis. + */ + public function set x(value:Number):void + { + if (value != _tx) + { + _tx = value; + invalidated = true; + } + } + /** + * The value of the translation rendering for this Geometry along the y axis. + */ + public function set y(value:Number):void + { + if (value != _ty) + { + _ty = value; + invalidated = true; + } + } + } + +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/transform/TranslateTransform.png b/Degrafa/com/degrafa/transform/TranslateTransform.png new file mode 100644 index 0000000..1536b5e Binary files /dev/null and b/Degrafa/com/degrafa/transform/TranslateTransform.png differ diff --git a/Degrafa/com/degrafa/triggers/EventTrigger.as b/Degrafa/com/degrafa/triggers/EventTrigger.as new file mode 100644 index 0000000..949cf23 --- /dev/null +++ b/Degrafa/com/degrafa/triggers/EventTrigger.as @@ -0,0 +1,103 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.triggers{ + + import flash.events.Event; + import flash.events.IEventDispatcher; + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("EventTrigger.png")] + /** + * EventTrigger will listen to the specified event on the source + * and set the specified state when that event occurs. + **/ + public class EventTrigger extends Trigger implements ITrigger{ + + /** + * Constructor. + **/ + public function EventTrigger(source:IEventDispatcher=null,event:String="",ruleFunction:Function=null){ + super(); + + super.source = source; + _event= event; + super.ruleFunction= ruleFunction; + + } + + private var _event:String; + /** + * The event on the source we are listening for + **/ + public function get event():String{ + return _event; + } + public function set event(value:String):void{ + _event = value; + + initTrigger(); + } + + /** + * Sets the listener on the event specified. + **/ + override protected function initTrigger():void{ + if(!source || !event){return;} + source.addEventListener(event,onEventTriggered,false,0,true); + } + + /** + * Clears the listener setup by this trigger. + **/ + override protected function clearTrigger():void{ + if(source){ + source.removeEventListener(event,onEventTriggered); + } + } + + /** + * Executes the rule. + **/ + private function onEventTriggered(event:Event):void{ + + var result:Boolean=true; + + //if there are rules then they must all evaluate + //to true before this state change will take place + if(ruleFunction != null){ + if(!ruleFunction.call(this,event,this)){ + return; + } + } + + if(result){ + if(setState){ + triggerParent.currentState =setState; + } + } + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/triggers/EventTrigger.png b/Degrafa/com/degrafa/triggers/EventTrigger.png new file mode 100644 index 0000000..53cfd30 Binary files /dev/null and b/Degrafa/com/degrafa/triggers/EventTrigger.png differ diff --git a/Degrafa/com/degrafa/triggers/ITrigger.as b/Degrafa/com/degrafa/triggers/ITrigger.as new file mode 100644 index 0000000..0ed2dda --- /dev/null +++ b/Degrafa/com/degrafa/triggers/ITrigger.as @@ -0,0 +1,39 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.triggers{ + + import com.degrafa.states.IDegrafaStateClient; + + import flash.events.IEventDispatcher; + + /** + * ITrigger is the interface objects must implement in order to participate in triggers. + **/ + public interface ITrigger{ + function get triggerParent():IDegrafaStateClient + function set triggerParent(value:IDegrafaStateClient):void + function get id():String + function set id(value:String):void + function get source():IEventDispatcher + function set source(value:IEventDispatcher):void + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/triggers/PropertyTrigger.as b/Degrafa/com/degrafa/triggers/PropertyTrigger.as new file mode 100644 index 0000000..3a784d5 --- /dev/null +++ b/Degrafa/com/degrafa/triggers/PropertyTrigger.as @@ -0,0 +1,151 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.triggers{ + + import flash.events.Event; + + import mx.binding.utils.ChangeWatcher; + + //-------------------------------------- + // Other metadata + //-------------------------------------- + + [IconFile("PropertyTrigger.png")] + /** + * Used for notification of a property change on a source object. + **/ + public class PropertyTrigger extends Trigger implements ITrigger{ + + /** + * Constructor. + **/ + public function PropertyTrigger(){ + super(); + } + + private var _property:String; + /** + * The property on the source to be watched for changes. + **/ + public function get property():String{ + return _property; + } + public function set property(value:String):void{ + _property = value; + + initTrigger(); + } + + private var _autoRestoreState:Boolean=true; + /** + * If true will set the state to the old state + * when rule test is false. + **/ + public function get autoRestoreState():Boolean{ + return _autoRestoreState; + } + public function set autoRestoreState(value:Boolean):void{ + _autoRestoreState = value; + } + + private var _propertyValue:String; + /** + * Property value is used as an initial rule and is optional + * when set the value being set on the target property must be equal + * to this value before the trigger will occure. If not set this + * test is ignored. + **/ + public function get propertyValue():String{ + return _propertyValue; + } + public function set propertyValue(value:String):void{ + _propertyValue = value; + + initTrigger(); + } + + //internal change watcher + private var changeWatcher:ChangeWatcher; + + /** + * Sets up a ChangeWatcher for the property on the source specified. + **/ + override protected function initTrigger():void{ + if(!source || !property){return;} + + if(Object(source).hasOwnProperty(property)){ + + //setup the watcher is we can + if(ChangeWatcher.canWatch(source,property)){ + changeWatcher = ChangeWatcher.watch(source,property,executeTrigger); + } + + } + + } + + /** + * Clears the ChangeWatcher for the property on the source specified. + **/ + override protected function clearTrigger():void{ + if(changeWatcher){ + changeWatcher.unwatch(); + } + } + + //store the old state for a false value + private var oldState:String=""; + + //executes the trigger when a change takes place. + private function executeTrigger(event:Event):void{ + + var result:Boolean=true; + + if(propertyValue){ + if(Object(source)[property]!=propertyValue){ + if(autoRestoreState){triggerParent.currentState =oldState;} + return; + } + } + + //if there are rules then they must all evaluate + //to true before this state change will take place + if(ruleFunction != null){ + if(!ruleFunction.call(this,property,this)){ + if(autoRestoreState){triggerParent.currentState =oldState;} + return; + } + } + + if(result){ + if(setState){ + //store the old state + oldState = triggerParent.currentState; + //set the new state + triggerParent.currentState =setState; + } + } + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/triggers/PropertyTrigger.png b/Degrafa/com/degrafa/triggers/PropertyTrigger.png new file mode 100644 index 0000000..c1577e9 Binary files /dev/null and b/Degrafa/com/degrafa/triggers/PropertyTrigger.png differ diff --git a/Degrafa/com/degrafa/triggers/Trigger.as b/Degrafa/com/degrafa/triggers/Trigger.as new file mode 100644 index 0000000..db0a92a --- /dev/null +++ b/Degrafa/com/degrafa/triggers/Trigger.as @@ -0,0 +1,118 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.triggers{ + + import com.degrafa.core.DegrafaObject; + import com.degrafa.states.IDegrafaStateClient; + + import flash.events.IEventDispatcher; + /** + * Trigger is the base class that other triggers extend from. + **/ + public class Trigger extends DegrafaObject{ + + /** + * Constructor. + **/ + public function Trigger(){} + + private var _triggerParent:IDegrafaStateClient; + /** + * The parent for this trigger. A valide IDegrafaStateClient. + **/ + public function get triggerParent():IDegrafaStateClient{ + return _triggerParent; + } + public function set triggerParent(value:IDegrafaStateClient):void{ + _triggerParent = value; + + if(!value){ + clearTrigger(); + } + + } + + private var _source:IEventDispatcher; + /** + * The source of the property or event we want to be notified about. + * Any valid IEventDispatcher. + **/ + public function get source():IEventDispatcher{ + return _source; + } + public function set source(value:IEventDispatcher):void{ + _source = value; + + if(value){ + initTrigger(); + } + else{ + clearTrigger(); + } + } + + protected var _ruleFunction:Function; + /** + * Function that gets evaluated on the event trigger and + * if true the state change will take place. The default + * for any evaluation if no rules exist is true + * The arguments passed to the function call are: + * 1 : the event the trigger received. + * 2 : the trigger. + * + **/ + public function get ruleFunction():Function{ + return _ruleFunction; + } + public function set ruleFunction(value:Function):void{ + if(_ruleFunction != value){ + _ruleFunction= value as Function; + } + } + + private var _setState:String; + /** + * The state should the rule result be true (default) that will be set on the + * triggerParent. + **/ + public function get setState():String{ + return _setState; + } + public function set setState(value:String):void{ + _setState = value; + } + + /** + * Inits the trigger overrideen by subclasses. + **/ + protected function initTrigger():void{ + //overridden + } + + /** + * Clears the trigger overrideen by subclasses. + **/ + protected function clearTrigger():void{ + //overridden + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/utilities/Aliases.as b/Degrafa/com/degrafa/utilities/Aliases.as new file mode 100644 index 0000000..a4d92b9 --- /dev/null +++ b/Degrafa/com/degrafa/utilities/Aliases.as @@ -0,0 +1,233 @@ +package com.degrafa.utilities{ + + + import flash.utils.Dictionary; + import flash.net.registerClassAlias; + + //import classes for registering + + import flash.geom.* + import com.degrafa.* + import com.degrafa.geometry.* + import com.degrafa.geometry.segment.* + import com.degrafa.geometry.command.* + import com.degrafa.core.* + import com.degrafa.core.collections.* + import com.degrafa.paint.* + import com.degrafa.repeaters.* + import com.degrafa.skins.* + import com.degrafa.transform.* + import com.degrafa.utilities.external.* + + /** + * The Aliases helper is for registering a Degrafa manifest of class aliases. + * This class is supportive for potential ByteArray cacheing, if used, or for + * future runtime loading of externally created amf-encoded degrafa objects. + **/ + public class Aliases{ + + //reference object package/manifest for classes + //requires manual update if/when changes are made to the structure of the + //alias mapping + private static var manifest:Object = { + flash: { + geom: { + Point:Point, + Matrix:Matrix, + Rectangle:Rectangle + } + }, + com: { + degrafa: + { + GeometryComposition:GeometryComposition, + GeometryGroup:GeometryGroup, + GraphicPoint:GraphicPoint, + GraphicPointEX:GraphicPointEX, + GraphicText:GraphicText, + Surface:Surface, + + core: { + Measure:Measure, + + collections: { + DegrafaCollection:DegrafaCollection, + DisplayObjectCollection:DisplayObjectCollection, + FillCollection:FillCollection, + GeometryCollection:GeometryCollection, + GradientStopsCollection:GradientStopsCollection, + GraphicPointCollection:GraphicPointCollection, + GraphicPointEXCollection:GraphicPointEXCollection, + GraphicsCollection:GraphicsCollection, + GraphicSkinColletion:GraphicSkinCollection, + RepeaterModifierCollection:RepeaterModifierCollection, + SegmentsCollection:SegmentsCollection, + StrokeCollection:StrokeCollection, + TransformCollection:TransformCollection + } + }, + geometry: { + AdvancedRectangle:AdvancedRectangle, + Circle:Circle, + CubicBezier:CubicBezier, + Ellipse:Ellipse, + EllipticalArc:EllipticalArc, + HorizontalLine:HorizontalLine, + Line:Line, + Move:Move, + Path:Path, + Polygon:Polygon, + Polyline:Polyline, + QuadraticBezier:QuadraticBezier, + RegularRectangle:RegularRectangle, + RoundedRectangleComplex:RoundedRectangleComplex, + VerticalLine:VerticalLine, + + segment: { + ClosePath:ClosePath, + CubicBezierTo:CubicBezierTo, + EllipticalArcTo:EllipticalArcTo, + HorizontalLineTo:HorizontalLineTo, + LineTo:LineTo, + MoveTo:MoveTo, + QuadraticBezierTo:QuadraticBezierTo, + VerticalLineTo:VerticalLineTo + }, + command: { + CommandStack:CommandStack, + CommandStackItem:CommandStackItem, + CommandCollection:CommandCollection + } + + }, + paint: { + BitmapFill:BitmapFill, + BlendFill:BlendFill, + ComplexFill:ComplexFill, + GradientStop:GradientStop, + LinearGradientFill:LinearGradientFill, + LinearGradientStroke:LinearGradientStroke, + RadialGradientStroke:RadialGradientStroke, + SolidFill:SolidFill, + SolidStroke:SolidStroke + }, + repeaters: { + GeometryRepeater:GeometryRepeater, + PropertyModifier:PropertyModifier + }, + skins: { + CSSSkin:CSSSkin, + GraphicBorderSkin:GraphicBorderSkin, + GraphicProgrammaticSkin:GraphicProgrammaticSkin, + GraphicRectangularBorderSkin:GraphicRectangularBorderSkin + }, + transform: { + MatrixTransform:MatrixTransform, + RotateTransform:RotateTransform, + ScaleTransform:ScaleTransform, + SkewTransform:SkewTransform, + TransformGroup:TransformGroup, + TranslateTransform:TranslateTransform + }, + utilities: { + external:{ + ExternalBitmapData:ExternalBitmapData, + LoadingLocation:LoadingLocation + } + } + } + } + + } + + private static var registeredAliases:Dictionary = new Dictionary(); + private static var registeredClasses:Dictionary = new Dictionary(); + + /** + * + * @param classOrAlias either a Class reference or an alias to check + * @return true if the Class or alias is already registered. + */ + public static function isRegistered(classOrAlias:*):Boolean + { + if (classOrAlias is String) + { + if (registeredAliases[classOrAlias]) return true; + return false; + } else if (classOrAlias is Class) + { + if (registeredClasses[classOrAlias]) return true; + return false; + } + throw new Error('invalid argument'); + } + /** + * Provides a method of Class retrieval that is faster than + * flash.utils.getDefinitionByName for specific class references + * stored here. + * @param alias, a string that represents the alias for the class being requested + * @return a reference to the Class from the alias + */ + public static function getClassReference(alias:String):Class + { + if (registeredAliases[alias]) + { + return registeredAliases[alias]; + } + return ArgumentError; + } + /** + * Registers class aliases in the manifest object from the package level specified + * @param including , the package path or fully qualified class path to register + */ + + private static function registerFrom(including:String = "com"):void + { + if (_all) return; + var pckg:Array = including.split('.'); + var i:uint = 0; + var base:Object=manifest; + for (; i < pckg.length; i++) base = base[pckg[i]]; + if (base is Class) { + if (!registeredClasses[base]) { + registerClassAlias(including, base as Class ); + registeredClasses[base] = including; + registeredAliases[including] = base; + } + return; + } + if (base is Object) + { + for (var prop:String in base) + { + var newInclude:String = including + "." + prop; + registerFrom(newInclude); + } + } + + + } + + private static var _all:Boolean = false; + + /** + * Registers all classes stored in the Aliases class static manifest object + */ + public static function registerAll():void + { + //dev note: registerAll incurs around 10 ms in testing + // likely to be faster if the manifest is a simple array, + // so that's an option if a structured representation offers no utility + //also consider releasing the manifest object for garbage collection if _all is true + if (!_all) + { + for (var s:String in manifest) + { + registerFrom(s); + } + _all = true; + } + + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/utilities/SWFReader.as b/Degrafa/com/degrafa/utilities/SWFReader.as new file mode 100644 index 0000000..5d9466e --- /dev/null +++ b/Degrafa/com/degrafa/utilities/SWFReader.as @@ -0,0 +1,211 @@ +package com.degrafa.utilities +{ + import com.degrafa.utilities.swf.SWFFontReader; + import com.degrafa.utilities.swf.SWFTag; + + import flash.geom.Rectangle; + import flash.utils.ByteArray; + import flash.utils.Endian; + + public class SWFReader + { + private var _bytes:ByteArray; + private var _data:ByteArray; + private var _tags:Array; + private var _fileSize:uint; + private var _uncompressedFileSize:uint; + private var _compressed:Boolean; + private var _version:uint; + private var _size:Rectangle; + private var _frameRate:uint; + private var _frameCount:uint; + + private static var _bitPos:int; + private static var _bitBuf:int; + + [Bindable] + public var fonts:Array; + + public function get tags():Array { return _tags; } + + public function SWFReader() + { + super(); + } + + public function loadBytes(bytes:ByteArray):void + { + _bytes = bytes; + decode(); + } + + private function decode():void + { + + _bytes.endian = Endian.LITTLE_ENDIAN; + _bytes.position = 0; + + var version:uint = _bytes.readUnsignedInt(); + var csize:int; + + _fileSize = _bytes.length; + + switch (version) { + case 67|87<<8|83<<16|9<<24: // CWS9 + case 67|87<<8|83<<16|8<<24: // CWS8 + case 67|87<<8|83<<16|7<<24: // CWS7 + case 67|87<<8|83<<16|6<<24: // CWS6 + _compressed = true; + _data = new ByteArray(); + _data.endian = Endian.LITTLE_ENDIAN; + _bytes.position = 8; + _bytes.readBytes(_data, 0, _bytes.length - _bytes.position); + csize = _data.length; + _data.uncompress(); + trace("{SWFReader.decode} Decompressed SWF " + csize + " -> " + _data.length + " bytes..."); + _uncompressedFileSize = _data.length + 8; + _data.position = 0 + break; + case 70|87<<8|83<<16|9<<24: // FWS9 + case 70|87<<8|83<<16|8<<24: // FWS8 + case 70|87<<8|83<<16|7<<24: // FWS7 + case 70|87<<8|83<<16|6<<24: // FWS6 + case 70|87<<8|83<<16|5<<24: // FWS5 + case 70|87<<8|83<<16|4<<24: // FWS4 + _uncompressedFileSize = _bytes.length; + _compressed = false; + _bytes.position = 8 // skip header and length + _data = _bytes; + trace("{SWFReader.decode} Parsed SWF " + _bytes.length + " bytes..."); + break + default: + trace('{SWFReader.decode} Unknown format ' + version); + break; + } + + _version = version >> 24; + + _size = decodeRect(_data); + _frameRate = ( _data.readUnsignedByte() << 8 | _data.readUnsignedByte() ); + _frameCount = _data.readUnsignedShort(); + + decodeTags(); + + var fr:SWFFontReader = new SWFFontReader(); + for (var i:int = 0; i < _tags.length; i++) { + if (fr.isFontTag(_tags[i])) { + fr.decodeTag(_tags[i]); + } + } + fonts = fr.fonts; + + } + + private function decodeTags():void + { + var dataLength:int = _data.length; + var type:int, h:int, length:int; + var offset:int; + var tag:SWFTag; + + _tags = []; + + while (_data.position < dataLength) + { + type = (h = _data.readUnsignedShort()) >> 6; + + if (((length = h & 0x3F) == 0x3F)) + length = _data.readInt(); + + tag = new SWFTag(); + tag.type = type; + tag.start = _data.position; + tag.length = length; + tag.bytes = _data; + + _tags.push(tag); + + _data.position += length; + } + } + + public static function decodeRect(data:ByteArray):Rectangle + { + syncBits(); + + var rect:Rectangle = new Rectangle(); + + var nBits:int = readUBits(5, data) + rect.x = readSBits(nBits, data); + rect.width = readSBits(nBits, data); + rect.y = readSBits(nBits, data); + rect.height = readSBits(nBits, data); + + return rect; + } + + public static function syncBits():void + { + _bitPos = 0; + } + + public static function unreadBits(n:int):void { + _bitPos = Math.max(0, _bitPos - n); + } + + public static function readSBits(numBits:int, data:ByteArray):int + { + if (numBits > 32) + throw new Error("Number of bits > 32"); + + var num:int = readUBits(numBits, data); + var shift:int = 32 - numBits; + + // sign extension + num = (num << shift) >> shift; + + return num; + } + + public static function readUBits(numBits:int, data:ByteArray):uint + { + if (numBits == 0) + return 0; + + var bitsLeft:int = numBits; + var result:int = 0; + + if (_bitPos == 0) //no value in the buffer - read a byte + { + _bitBuf = data.readUnsignedByte(); + _bitPos = 8; + } + + while (true) + { + var shift:int = bitsLeft - _bitPos; + if (shift > 0) + { + // Consume the entire buffer + result |= _bitBuf << shift; + bitsLeft -= _bitPos; + + // Get the next byte from the input stream + _bitBuf = data.readUnsignedByte(); + _bitPos = 8; + } + else + { + // Consume a portion of the buffer + result |= _bitBuf >> -shift; + _bitPos -= bitsLeft; + _bitBuf &= 0xff >> (8 - _bitPos); // mask off the consumed bits + return result; + } + } + + return 0; + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/utilities/external/ExternalBitmapData.as b/Degrafa/com/degrafa/utilities/external/ExternalBitmapData.as new file mode 100644 index 0000000..fc7539a --- /dev/null +++ b/Degrafa/com/degrafa/utilities/external/ExternalBitmapData.as @@ -0,0 +1,88 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.utilities.external { + import com.degrafa.utilities.external.IExternalData + import com.degrafa.utilities.external.ExternalDataAsset + import flash.display.Loader; + import flash.display.BitmapData; + + + /** + * The ExternalDataAsset class defines the properties for an external data source used at runtime by Degrafa. + * You can use an ExternalDataAsset object in actionscript - it may be useful to + * set up preloading, for example, but in mxml use of an ExternalDataAsset is already encapsulated into + * the Degrafa class that uses the content (when it is ready) by virtue of its source property assignment. + * The data content provided by an ExternalDataAsset will only be available once the asset from the external url has + * loaded. + */ + public class ExternalBitmapData extends ExternalDataAsset implements IExternalData { + + + private var tempBitmapdata:BitmapData; + + //local override for getUniqueInstance mxml helper + public static function getUniqueInstance(url:String = null, loc:LoadingLocation = null):ExternalBitmapData + { + return ExternalDataAsset.getUniqueInstance(url, loc, ExternalBitmapData) as ExternalBitmapData; + } + /** + * Constructor for ExternalBitmapData + * @param url absolute or relative url to external asset (must be relative if a LoadingLocation is used) + * @param totalBytes [optional] total bytes, if known before loading commences + * @param loc a LoadingLocation for security handling + */ + public function ExternalBitmapData(url:String = null, totalBytes:Number = NaN,loc:LoadingLocation=null) + { + dataType = BitmapData; + super(url, totalBytes,Loader); + } + + override protected function processLoad(loader:Object):String + { + tempBitmapdata = _data as BitmapData; + var err:Boolean = false; + try { + _data = new BitmapData((loader as Loader).content.width, (loader as Loader).content.height, true, 0x00000000); + _data.draw((loader as Loader).content); + } catch (e:Error) + { + //the image has loaded but a crossdomain permission was not granted + //so the bitmapData cannot be accessed. + //Only recourse is to check for another location if we're using a LoadingGroup + //consider dispatching a specific permission failure event here. + err = true; + } + //release the loaded DisplayObject + (loader as Loader).unload(); + if (!err) { + return ExternalDataAsset.STATUS_READY; + } else return ExternalDataAsset.STATUS_SECURITY_ERROR; + } + + override protected function cleanUp():void + { + if (tempBitmapdata) tempBitmapdata.dispose(); + tempBitmapdata = null; + } + } +} + diff --git a/Degrafa/com/degrafa/utilities/external/ExternalDataAsset.as b/Degrafa/com/degrafa/utilities/external/ExternalDataAsset.as new file mode 100644 index 0000000..991128e --- /dev/null +++ b/Degrafa/com/degrafa/utilities/external/ExternalDataAsset.as @@ -0,0 +1,414 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.utilities.external { + import com.degrafa.core.DegrafaObject; + import com.degrafa.core.IDegrafaObject; + import flash.events.EventDispatcher + import flash.display.BitmapData; + import flash.display.Loader; + import flash.events.Event; + import flash.events.SecurityErrorEvent; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.net.URLLoader; + import flash.net.URLRequest; + import flash.system.LoaderContext; + import com.degrafa.utilities.external.LoadingLocation; + import flash.utils.ByteArray; + import flash.utils.Dictionary; + + + + + /** + * The ExternalDataAsset class defines the properties for an external data source used at runtime by Degrafa. + * You can use an ExternalDataAsset object in actionscript - it may be useful to + * set up preloading, for example, but in mxml use of an ExternalDataAsset is already encapsulated into + * the Degrafa class that uses the content (when it is ready) by virtue of its source property assignment. + * The data content provided by an ExternalDataAsset will only be available once the asset from the external url has + * loaded. + */ + public class ExternalDataAsset extends DegrafaObject implements IDegrafaObject{ + protected var _url:String; + //type as identified by mime type + private var _mimeType:String ; + //status before and during loading + private var _status:String ; + //either a URLLoader or a Loader intance + private var _loader:Object; + //external bytesize, if known before loading (provided for actionscript use/preloading support only). + private var _externalSize:Boolean = false; + private var _bytesTotalExternal:Number=NaN; //assigned a value at instantiation if available. Possible use via actionscript for preloading activity. + + protected var dataType:Class; + protected var _data:Object; + //optional LoadingLocation for external security file/policy file requests + private var _loadingLocation:LoadingLocation; + + //static status constants/events + public static const STATUS_WAITING:String = 'itemWaiting'; + public static const STATUS_REQUESTED:String = 'itemRequested'; + public static const STATUS_STARTED:String = 'itemLoadStarted'; + public static const STATUS_PROGRESS:String = 'itemLoadProgress'; + public static const STATUS_INITIALIZING:String = 'itemInitializing'; + public static const STATUS_READY:String = 'itemReady'; + public static const STATUS_IDENTIFIED:String = 'itemIdentified'; + public static const STATUS_LOAD_ERROR:String = 'itemLoadError'; + public static const STATUS_SECURITY_ERROR:String = 'itemSecurityError'; + public static const STATUS_DATA_ERROR:String = 'itemDataError'; + + //static type constants for identified mime type of loaded content + public static const TYPE_UNKNOWN:String = 'unknown'; + public static const TYPE_SWF:String = 'application/x-shockwave-flash'; + public static const TYPE_IMAGE_JPEG:String = 'image/jpeg'; + public static const TYPE_IMAGE_PNG:String = 'image/png'; + public static const TYPE_IMAGE_GIF:String = 'image/gif'; + + //for loading from external domains using a Loader,always assume bitmapData is wanted see note in load method + public static var requestBitmapDataAccess:LoaderContext = new LoaderContext(true); + + //store a dictionary of references to instances by class, based on urls. This is ignored by the constructor + //and is only for use by the static getUniqueInstance class function, to faciliatate url assignments to source properties for Degrafa mxml + private static var _uniqueInstances:Dictionary=new Dictionary(true); + + /** + * static method to prevent multiple instances referring to the same external asset + * this avoids creation of multiple instances of the same loaded data (multiple instances remain possible if required, by simply by using the constructor) + * @param url the url to the external asset (must be relative if a LoadingLocation is used) + * @param loc an optional LoadingLocation + */ + protected static function getUniqueInstance(url:String = null, loc:LoadingLocation = null,clazz:Class=null):ExternalDataAsset + { + + if (!_uniqueInstances[clazz]) _uniqueInstances[clazz] = new Dictionary(); + if (url && _uniqueInstances[clazz][url] && _uniqueInstances[clazz][url].loadingLocation === loc) + { + // trace('found existing instance:'+url) + return _uniqueInstances[clazz][url] + } else { + // trace('creating new instance:'+url) + _uniqueInstances[clazz][url] = new clazz(url); + if (loc) _uniqueInstances[clazz][url].loadingLocation = loc; + return _uniqueInstances[clazz][url]; + } + + } + + + /** + * Constructor + * + *

    The ExternalDataAsset constructor has one optional argument for url(s) and a second optional argument + * to specifiy filesize for an external bitmap (useful when considered as part of a collection to preload if the data is available). + * The url argument can be either a string for a single url or an array of url strings for backup.

    + * + * @param url a single url as a string. If a loadingGroup association is made in the loadingGroup property the url should be relative to the LoadingGroup basePath : use a LoadingGroup for fallback urls to provide redundancy under error conditions + * @param totalBytes an [optional] specification for the total bytes to be loaded for this item, only available through the constructor (actionscript use) + */ + public function ExternalDataAsset(url:String = null, totalBytes:Number = NaN,loader:Class=null) { + if (url != null) + { + _url = url; + } + //if (!_loader) _loader = Loader; + _loader = new loader(); + _mimeType = ExternalDataAsset.TYPE_UNKNOWN; + _status = ExternalDataAsset.STATUS_WAITING; + if (!isNaN(totalBytes)) { + _bytesTotalExternal = Math.floor(totalBytes); + _externalSize = true; + } + } + + + /** + * load this item, using LoadingGroup settings if this ExternalDataAsset is associated with a LoadingGroup instance + */ + public function load():void { + if (_url.length){ + addListeners(); + + var loadFrom:String; + if (_loadingLocation) + { + if (!_loadingLocation.requestedPolicyFile) _loadingLocation.requestPolicyFile(); + loadFrom = _loadingLocation.basePath + _url; + } else { + loadFrom = _url; + } + + //for loading from external domains, set default loading behaviour to check policy file permissions and attempt loading of default policyfile location/name if not yet granted. + if (_loader is Loader) (_loader as Loader).load(new URLRequest(loadFrom),requestBitmapDataAccess ); + else + { + (_loader as URLLoader).load(new URLRequest(loadFrom)); + // trace('requested') + } + _status = ExternalDataAsset.STATUS_REQUESTED; + dispatchEvent(new Event(ExternalDataAsset.STATUS_REQUESTED)); + } + } + + /** + * add internal listeners for loading support + */ + private function addListeners():void + { + var target:Object + if (_loader is URLLoader) + { + target = _loader as URLLoader; + } else if (_loader is Loader) + { + target = (_loader as Loader).contentLoaderInfo; + } + if (!target.hasEventListener(Event.OPEN)) { + with (target) { + + addEventListener(Event.OPEN, onLoadStart); + addEventListener(ProgressEvent.PROGRESS, onLoadProgress); + addEventListener(Event.COMPLETE, onLoadComplete); + addEventListener(Event.INIT, onLoadInit); + addEventListener(IOErrorEvent.IO_ERROR, onLoadError); + + } + if (target is URLLoader) (target as URLLoader).addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError); + } + } + + /** + * remove internal listeners for loading support + */ + private function removeListeners():void { + var target:Object + if (_loader is URLLoader) + { + target = _loader as URLLoader; + } else if (_loader is Loader) + { + target = (_loader as Loader).contentLoaderInfo; + } + if (hasEventListener(Event.OPEN)) { + with (target) { + removeEventListener(Event.OPEN, onLoadStart); + removeEventListener(ProgressEvent.PROGRESS, onLoadProgress); + removeEventListener(Event.COMPLETE, onLoadComplete); + removeEventListener(Event.INIT, onLoadInit); + removeEventListener(IOErrorEvent.IO_ERROR, onLoadError); + } + if (target is URLLoader) (target as URLLoader).removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError); + } + } + + /** + * Check mime type of loaded content. Incorporated in loading event processing, but not yet used. May be used to restrict loading to data assets only + * to prevent swf loading. E.g. The ExternalBitmapData subclass is intended for bitmap loading only. + */ + private function checkContentType():void { + if (_loader is URLLoader) return; + if (_mimeType == ExternalDataAsset.TYPE_UNKNOWN && _loader.contentLoaderInfo.contentType != null) { + _mimeType = _loader.contentLoaderInfo.contentType; + //this contentType property did not seem to be available until after the last progress event in testing + dispatchEvent(new Event(ExternalDataAsset.STATUS_IDENTIFIED)); + } + + } + + /** + * event handler for explicit SecurityError from a URLLoader based load + * @param evt event received from eventDispatcher + */ + private function onSecurityError(evt:Event):void { + _status = ExternalDataAsset.STATUS_SECURITY_ERROR; + removeListeners(); + dispatchEvent(new Event(ExternalDataAsset.STATUS_SECURITY_ERROR)); + //this ExternalDataAsset does nothing now...as it cannot. + } + + + /** + * event handler for start of loading + * @param evt event received from eventDispatcher + */ + private function onLoadStart(evt:Event):void { + _status = ExternalDataAsset.STATUS_STARTED; + checkContentType(); + dispatchEvent(new Event(ExternalDataAsset.STATUS_STARTED)); + } + + //overridden in subclasses, processes the loaded content, + //returns the status as one of the ExternalDataAsset.STATUS constants. + protected function processLoad(_loader:Object):String + { + return ExternalDataAsset.STATUS_READY; + } + //overridden in subclasses + protected function cleanUp():void + { + + } + /** + * event handler for completion of loading + * @param evt event received from eventDispatcher + */ + private function onLoadComplete(evt:Event):void { + checkContentType(); + + //let the sub-class process the loader's content and return the status + _status = processLoad(_loader); + + switch (_status) + { + case ExternalDataAsset.STATUS_READY: + dispatchEvent(new Event(ExternalDataAsset.STATUS_READY)); + cleanUp(); + removeListeners(); + break; + case ExternalDataAsset.STATUS_SECURITY_ERROR: + //the data is inaccessible after load (e.g. bitmapdata loaded with a Loader) + removeListeners(); + dispatchEvent(new Event(ExternalDataAsset.STATUS_SECURITY_ERROR)); + break; + } + } + + /** + * event handler for progress of loading + * @param evt ProgressEvent event received from eventDispatcher + */ + private function onLoadProgress(evt:ProgressEvent):void { + checkContentType(); + _status = ExternalDataAsset.STATUS_PROGRESS; + dispatchEvent(new ProgressEvent(ExternalDataAsset.STATUS_PROGRESS, false, false, evt.bytesLoaded, evt.bytesTotal)); + } + + /** + * event handler for initialization of loaded content + * @param evt event received from eventDispatcher + */ + private function onLoadInit(evt:Event):void { + checkContentType(); + _status = ExternalDataAsset.STATUS_INITIALIZING; + checkContentType(); + dispatchEvent(new Event(ExternalDataAsset.STATUS_INITIALIZING)); + } + + /** + * event handler for error in loading + * @param evt IOErrorEvent event received from eventDispatcher + */ + private function onLoadError(evt:IOErrorEvent):void { + // trace('LOAD ERROR:error '+evt) + _status = ExternalDataAsset.STATUS_LOAD_ERROR; + dispatchEvent(new Event(ExternalDataAsset.STATUS_LOAD_ERROR)); + //we cannot provide the bitmapdata for use in the BitmapFill + //just dispatch a STATUS_LOAD_ERROR event, the BitmapFill will not be rendered + removeListeners(); + //Consider implementing an automatic retry on load error + + } + + /** + * The loaded content (a BitmapData instance) if it is available + * or false (Boolean) if not available (triggers a loading request if not already requested). + */ + public function get content():Object { + // trace(_status+',content requested for '+_url) + if (_status == ExternalDataAsset.STATUS_READY) return (_data as dataType); + else { + //initiate load if it has not already commenced and return false (could also be null if preferred). + if (_status == ExternalDataAsset.STATUS_WAITING) load(); + return false; + } + } + + /** + * the current bytes loaded for this ExternalDataAsset + */ + public function get bytesLoaded():Number { + if (!(_status == ExternalDataAsset.STATUS_WAITING || _status == ExternalDataAsset.STATUS_REQUESTED)) return _loader.contentLoaderInfo.bytesLoaded; + else return 0; //this is always accurate! + } + + /** + * the bytesTotal for this ExternalDataAsset if known (i.e. verified during an actual load, or the value provided if pre-assigned through the constructor when instantiated) + * the value returned is NaN for unassigned, unverified (from actual file data) values + */ + public function get bytesTotal():Number { + if (!(_status == ExternalDataAsset.STATUS_WAITING || _status == ExternalDataAsset.STATUS_REQUESTED)) return _loader.contentLoaderInfo.bytesTotal; + else return _bytesTotalExternal; //returns NaN if unassigned a value from the constructor at this point + } + + /** + * resets the status to waiting prior to load, stopping any current load in progress. + * @return Boolean value of true if status was anything other than waiting when called, otherwise false + */ + protected function reset():Boolean { + if (!(_status == ExternalDataAsset.STATUS_WAITING || _status == ExternalDataAsset.STATUS_READY)) { + //cancel any load in progress: + removeListeners(); + _loader.close(); + } + var reload:Boolean = (_status!=ExternalDataAsset.STATUS_WAITING) + _status = ExternalDataAsset.STATUS_WAITING; + _bytesTotalExternal = NaN; + _mimeType = ExternalDataAsset.TYPE_UNKNOWN; + return reload; + } + + + /** + * the url of the external asset + * assignable as either a string representing a url relative to an associated LoadingGroup basePath or as a regular url + * For alternate domain loading or for redundancy support (multple locations) loading on error, use an associated LoadingGroup + * assigned via the loadingGroup property and make this url relative to the basePath defined in the LoadingGroup + */ + public function get url():String { return _url; } + + public function set url(value:String):void { + if (_url != value) { + _url = value; + //reset and reload automatically if this instance has already been requested and the url has been changed + if (reset()) load(); + }// else ignore the assigned value because it hasn't changed + } + + + + /** + *optional loadingLocation reference. Using a LoadingLocation simplifies management of groups of bitmap assets from other domains + *by permitting different locations (alternate domains used for loading) to be specified once in code + *if a loadingLocation is specified the url property in the ExternalDataAsset must be relative to the basepath specified in the LoadingLocation + *if an ExternalDataAsset's domain has a non-default policy file, a LoadingLocation must be used to specify the explicit location and + *name of the cross-domain file that grants access. An ExternalDataAsset without a LoadingLocation will only check for permission + *in the default location and name (web document root, crossdomain.xml) for permission to access the remote file's BitmapData. + */ + public function get loadingLocation():LoadingLocation { return _loadingLocation; } + + public function set loadingLocation(value:LoadingLocation):void + { + if (value) _loadingLocation = value; + } + + } +} + diff --git a/Degrafa/com/degrafa/utilities/external/ExternalDataPropertyChangeEvent.as b/Degrafa/com/degrafa/utilities/external/ExternalDataPropertyChangeEvent.as new file mode 100644 index 0000000..82b7f25 --- /dev/null +++ b/Degrafa/com/degrafa/utilities/external/ExternalDataPropertyChangeEvent.as @@ -0,0 +1,21 @@ +package com.degrafa.utilities.external +{ + import mx.events.PropertyChangeEvent; + import mx.events.PropertyChangeEventKind; + + //Simply an Event class for dispatching events from DegrafaObjects that do not trigger redraws in Degrafa + //intended for use in transient states while data loading is not yet complete (e.g. ExternalBitmapData on a BitmapFill) + /** + * ... + * @author Greg Dove + */ + public class ExternalDataPropertyChangeEvent extends PropertyChangeEvent + { + public static const EXTERNAL_DATA_PROPERTY_CHANGE:String = "externalDataPropertyChange"; + public function ExternalDataPropertyChangeEvent(type:String=ExternalDataPropertyChangeEvent.EXTERNAL_DATA_PROPERTY_CHANGE ,bubbles:Boolean=false,cancelable:Boolean=false,kind:String=PropertyChangeEventKind.UPDATE ,property:Object=null,newValue:Object=null,oldValue:Object=null,source:Object=null):void + { + super(type,bubbles,cancelable,kind,property,newValue,oldValue,source); + } + } + +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/utilities/external/IExternalData.as b/Degrafa/com/degrafa/utilities/external/IExternalData.as new file mode 100644 index 0000000..893e014 --- /dev/null +++ b/Degrafa/com/degrafa/utilities/external/IExternalData.as @@ -0,0 +1,49 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.utilities.external { + + + + + + /** + * The ExternalDataAsset class defines the properties for an external data source used at runtime by Degrafa. + * You can use an ExternalDataAsset object in actionscript - it may be useful to + * set up preloading, for example, but in mxml use of an ExternalDataAsset is already encapsulated into + * the Degrafa class that uses the content (when it is ready) by virtue of its source property assignment. + * The data content provided by an ExternalDataAsset will only be available once the asset from the external url has + * loaded. + */ + public interface IExternalData { + + + + + //process the URLLoader or Loader object to extract the data from + //after a successful load has finished, returning a Status value + //function processLoad(_loader:Object):String + //perform cleanup activities after successful load + //function cleanUp():void + + } +} + diff --git a/Degrafa/com/degrafa/utilities/external/LoadingLocation.as b/Degrafa/com/degrafa/utilities/external/LoadingLocation.as new file mode 100644 index 0000000..313dd32 --- /dev/null +++ b/Degrafa/com/degrafa/utilities/external/LoadingLocation.as @@ -0,0 +1,191 @@ +package com.degrafa.utilities.external +{ + + import flash.system.ApplicationDomain; + import flash.system.Security; + + import mx.utils.NameUtil; + + + /** + * A representation of a loading location specified in terms of a base path and + * crossdomain policy file to be accessed. To be associated with externally loaded content. + * */ + public class LoadingLocation + { + private static var requestedPolicyFiles:Object={}; + private var _basepath:String=null; + private var _policyFile:String=null; + private var _requestedPolicyFile:Boolean = false; + private static var _flexApplication:Class = (ApplicationDomain.currentDomain.hasDefinition("mx.core.Application")? Class(ApplicationDomain.currentDomain.getDefinition("mx.core.Application")):null);// try { return getDefinitionByName("mx.core.Application") } catch (e:Error) { return null } } ()); + + + /** + * static utility function to extract location elements from a url or the location of the flex application + * @param url + * @return an object with protocol, domain, and basepath properties + */ + public static function extractLocation(url:String=null):Object + { + // trace(url) + //TODO: consider converting this to regex. (but beware lack of accented characters not matching in \w) + //if the url argument is not passed or the url appears to be a relative url then use the location of the flex application + if (url == null || url.indexOf("//") == -1) { + if (_flexApplication != null) url = _flexApplication.application.url; + else throw new Error("unable to detect a default url with a domain to extract full Location details from"); + //consider using ExternalInterface as a backup and detecting the settings on the browser's location object and on the embedding tag for the swf - but this will only fault if the person has js switched off + //and is dependent on sandboxType + } + var retObj:Object = { }; + var arr:Array; + + if (url.indexOf("///")!=-1){ //required for file: protocol (works on pc, others?) + arr=url.split("///"); + arr[1]="/"+arr[1]; + } else arr = url.split("//"); + retObj.rawProtocol = String(arr.shift()).toLowerCase().substr(0, -1); + retObj.protocol = (retObj.rawProtocol + "://"); + + arr=arr[0].split('/') + + retObj.domain = arr.shift(); + retObj.hasPortSpecified = (retObj.domain.indexOf(":") != -1); + + if (arr[arr.length - 1] != "") arr[arr.length - 1] = "" + + + retObj.basepath = "/"+arr.join("/") + return retObj; + } + + /** + * a simple test to see if the url string is absolute or relative + * @return boolean value indicating whether this url is absolute or not + */ + public static function isAbsoluteURL(val:String):Boolean + { + return (val.indexOf("//") != -1); + } + + + /** + * a test to see if the local application requires loading of policy files to permit access to external data + * @return boolean value indicating whether this application type requires loading of policy files prior to accessing data + */ + private static function requiresPolicyFile():Boolean + { + var rpf:Boolean; + switch (Security.sandboxType) + { + case "application": //TODO: does AIR need them? don't think so, uncertain, TO VERIFY + rpf= false; + break; + case Security.REMOTE: + case Security.LOCAL_TRUSTED: + case Security.LOCAL_WITH_NETWORK: + rpf= true; + break; + case Security.LOCAL_WITH_FILE: + trace('this swf cannot communicate with the internet'); + rpf= false; + break; + } + return rpf; + } + + + public function LoadingLocation(basepath:String = null, policyFile:String = null):void + { + if (basepath != null) { + _basepath = basepath; + _policyFile = policyFile; + _requestedPolicyFile=requestedPolicyFiles[_policyFile]; + } + } + + /** + * request the policyFile associated with this location, if it has not already been requested + */ + public function requestPolicyFile():void + { + var tmpLoc:Object; + //if a policyfile is not specified then explicitly load a default instead of letting flash choose + if (!_requestedPolicyFile && LoadingLocation.requiresPolicyFile()) { + if (_basepath){ + if (_policyFile == null) + { + tmpLoc = LoadingLocation.extractLocation(_basepath); + _policyFile = tmpLoc.protocol+ tmpLoc.domain + "/crossdomain.xml"; + } + //may need to consider excluding the file:// protocol here, where a policy file would not be required although its not unsually specified. + + if (!requestedPolicyFiles[_policyFile]) Security.loadPolicyFile(_policyFile); + requestedPolicyFiles[_policyFile]=true; + _requestedPolicyFile = true; + + } else { + //basepath is not defined...so try getting a default location - no policyfile needed + try{ + tmpLoc = LoadingLocation.extractLocation(); + _basepath = tmpLoc.protocol + tmpLoc.domain + tmpLoc.basepath; + } catch (e:Error) { + _basepath = ""; + } + //assume we don't need a policyfile for this location, just flag as requested + _requestedPolicyFile = true; + + } + } + } + + private var _id:String; + /** + * The identifier used by document to refer to this object. + **/ + public function get id():String{ + + if(_id){ + return _id; + } + else{ + _id =NameUtil.createUniqueName(this); + return _id; + } + } + public function set id(value:String):void{ + _id = value; + } + + /** + * The name that refers to this object. + **/ + public function get name():String{ + return id; + } + + public function toString():String + { + return "[LoadingLocation " + _basepath + "]"; + } + + + //getters/setters + public function get basePath():String { return _basepath; } + + public function set basePath(value:String):void + { + _basepath = value; + } + + public function get policyFile():String { return _policyFile; } + + public function set policyFile(value:String):void + { + _policyFile = value; + } + + public function get requestedPolicyFile():Boolean { return _requestedPolicyFile; } + + } +} + diff --git a/Degrafa/com/degrafa/utilities/external/VideoStream.as b/Degrafa/com/degrafa/utilities/external/VideoStream.as new file mode 100644 index 0000000..365f337 --- /dev/null +++ b/Degrafa/com/degrafa/utilities/external/VideoStream.as @@ -0,0 +1,2814 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2009 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Original author of this code: Greg Dove http://greg-dove.com +// Contributed to Degrafa for beta 3.2, November 2009 +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.utilities.external +{ + import com.degrafa.core.DegrafaObject; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.Loader; + import flash.events.Event; + import flash.events.IOErrorEvent; + import flash.events.NetStatusEvent; + import flash.events.TimerEvent; + import flash.geom.ColorTransform; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.media.SoundTransform; + import flash.media.Video; + import flash.net.NetConnection; + import flash.net.NetStream; + import flash.text.TextField; + import flash.text.TextFieldAutoSize; + import flash.text.TextFormat; + import flash.utils.Dictionary; + import flash.utils.Timer; + import flash.utils.setTimeout; + + + /** + * ... + * @author Greg Dove + * + * TODO: + * A) Implement snap-to-keyframe seek targeting: use keyframe metadata where it is available for seek targeting the timestamp of closest keyframe. + * B) more streaming testing. + * - check specs: It appears that video content encoding is important. Poorly encoded content fails on streaming seek in both wowza and FMS. This happens in other players as well. + * - testing has been done with wowza1.7.2 and FMS3.5. Need to test with Red 5, Weborb streaming servers (others?) + * C) Add automatic streaming connection attempts for rtmp connections through firewalls dropping down to http (similar to other players like FLVPlayback etc) until successful connection attempt where + * rmtp connections fail on first attempt. + * D) Add Camera/Webcam support and support for live streams in/out. + * E) Add binding support for metaData, embedded cuepoints, images, text etc + */ + public class VideoStream extends DegrafaObject + { + //ExternalData static status constants/events + public static const STATUS_WAITING:String='itemWaiting'; + public static const STATUS_REQUESTED:String='itemRequested'; + public static const STATUS_STARTED:String='itemLoadStarted'; + public static const STATUS_PROGRESS:String='itemLoadProgress'; + public static const STATUS_INITIALIZING:String='itemInitializing'; + public static const STATUS_READY:String='itemReady'; + public static const STATUS_IDENTIFIED:String='itemIdentified'; + public static const STATUS_LOAD_ERROR:String='itemLoadError'; + public static const STATUS_SECURITY_ERROR:String='itemSecurityError'; + public static const STATUS_DATA_ERROR:String='itemDataError'; + + //Error statuses + public static const ERROR_STATUS_NONE:String="errorNone"; + public static const ERROR_STATUS_DATA_ACCESS:String="errorDataAccess"; + public static const ERROR_STATUS_CONTENT_ACCESS:String="errorDataAccess"; + public static const ERROR_STATUS_CONNECTION_ACCESS:String="errorConnectionAccess"; + + public static const ERROR_STATUS_UPDATE:String="errorUpdate"; + + //Reference/Lookup Objects + //DEV NOTE: some work in progress going on here. + protected static const _fileTypePrefixes:Object={type_mp3: "mp3", type_mp4: "mp4", type_m4v: "mp4", type_f4v: "mp4", type_3gpp: "mp4", type_mov: "mp4"}; + protected static const _streamingProtocols:Array=["rtmp", "rtmpt", "rtmps", "rtmpe", "rtmpte"]; + protected static var instances:Dictionary=new Dictionary(true); + protected static var __connections:Object={}; + + //VideoStream statuses + protected static const STREAM_STATUS_NORMAL:int=0; + protected static const STREAM_STATUS_LOOPING:int=1; + protected static const STREAM_STATUS_REWINDING:int=2; + + + //Audio status + public static const AUDIO_STATUS_UPDATE:String="audioStatusUpdate"; //muted or unmuted changes + + //Buffer statuses + public static const BUFFER_EMPTY:String="bufferEmpty"; + public static const BUFFER_FULL:String="bufferFull"; + public static const BUFFER_DIMINISHED:String="bufferDiminished"; + public static const BUFFER_BUFFERING:String="bufferBuffering"; + public static const BUFFER_FLUSHING:String="bufferFlushing"; + + public static const BUFFER_STATUS_UPDATE:String="bufferStatusUpdate"; + public static const BUFFER_STATUS_CHANGE:String="bufferStatusChange"; + public static const BUFFER_TIME_CHANGED:String="bufferTimeChanged"; + + //http loading statuses + public static const LOAD_START:String="loadStart"; + public static const LOAD_PROGRESS:String="loadProgress"; + public static const LOAD_COMPLETE:String="loadComplete"; + public static const LOAD_UDPATE:String="loadUpdate"; + + //Seek intent statuses + public static const SEEK_NONE:String="seekNone"; + public static const SEEK_SEEKING:String="seekSeeking"; + //Seek result statuses + public static const SEEK_STATUS_END:String="seekStatusEnd"; + + //Play activity intent statuses + public static const PLAY_STOPPED:String="playStopped"; //stopped at start of stream + public static const PLAY_PAUSED:String="playPaused"; //paused somewhere in stream + public static const PLAY_PLAYING:String="playPlaying"; //playing intent or actual + //Play activity statuses/events + public static const PLAY_STATUS_RESET:String="playStatusReset"; + public static const PLAY_STATUS_WAITING:String="playStatusWaiting"; + public static const PLAY_STATUS_COMPLETE:String="playStatusComplete"; //end of stream + public static const PLAY_STATUS_UPDATE:String="playStatusUpdate"; //update pulse during play + public static const PLAY_STATUS_CHANGED:String="playStatusChanged"; //change event for playStatus + public static const PLAY_STATUS_FPS_UPDATE:String="playStatusFPSupdate"; //change in frames per second + + //Connection statuses + public static const CONNECT_FAILED:String="NetConnection.Connect.Failed"; + public static const CONNECT_SUCCESS:String="NetConnection.Connect.Success"; + public static const CONNECT_REJECTED:String="NetConnection.Connect.Rejected"; + public static const CONNECT_APPSHUTDOWN:String="NetConnection.Connect.AppShutDown"; + public static const CONNECT_CLOSED:String="NetConnection.Connect.Closed"; + + + //letterbox detection + public static const LETTERBOX_UPDATE:String="letterBoxUpdate"; + + //content related constants + + public static const HIGH:String="high"; + public static const MEDIUM:String="medium"; + public static const LOW:String="low"; + public static const NON_STREAMING:String="non-streaming"; + public static const STREAMING:String="streaming"; + public static const NONE:String="none"; + + + //fundamental video support + protected var _ns:NetStream; + protected var _nc:NetConnection; + protected var _st:SoundTransform; + protected var _vid:Video=new Video(1920, 1080); + + //bitmapData related + protected var _pixelMargin:uint=1; + protected var _bitmapData:BitmapData; + protected var _latestImage:BitmapData; + private static var _testBitmapData:BitmapData=new BitmapData(1,1,false,0); + + //video source related + //local working url + protected var _workingUrl:String; + //original setter value + protected var _setUrl:String; + //other + protected var _metaData:Object; + protected var _textData:String; + protected var _protocol:String; + protected var _port:String; + protected var _basePath:String; + protected var _relativeURL:Boolean; + protected var _loadingLocation:LoadingLocation; + protected var _implicitLocation:LoadingLocation; + + //video characteristics + protected var _width:uint; + protected var _height:uint; + protected var _position:Number=0; + protected var _bufferTime:Number=2; + protected var _bufferLength:Number=0; + protected var _duration:Number; + protected var _audioDelay:Number=0; + protected var _currentFPS:Number=0; + + //http loading + protected var _bytesTotal:Number; //stored as Number type to handle extremely large files + protected var _bytesLoaded:Number; //stored as Number type to handle extremely large files + protected var _httpCached:Boolean; //have we determined that this is a cached video? + + + + //initial buffer state + protected var _bufferStatus:String=BUFFER_EMPTY; //buffer status: either empty, buffering, full or flushing + + //initial seek and play intents + protected var _seekStatus:String=SEEK_NONE; // seek intent: either seeking, end of seeking, or none + protected var _playStatus:String=PLAY_STOPPED; // play intent: either playing, paused or stopped (at start of stream) + + //quick check flags - actual stream state + protected var _rtmp:Boolean; //is this an rtmp stream (or http) + protected var _isPlaying:Boolean; //is the stream currently in a playing state + protected var _isPaused:Boolean=true; //is the stream paused; + protected var _isSeeking:Boolean; //is the stream currently in a seeking state + //general flags - stream state + protected var _isConnected:Boolean; //netconnection status is good + protected var _isReady:Boolean; //has the stream started (once) + protected var _wasBitmapAccessible:Boolean; //stream level flag for whether this rtmp stream has had a valid bitmapdata access history + protected var _wasStreamingSeek:Boolean; //flag to check if the last seek was in a rtmp stream + protected var _requiresMetaData:Boolean;// rtmp stream permissions appear to arrive after a seek via metadata. + protected var _rtmpSeekCycle:Boolean;//flag for seeking during stream + + protected var _bitmapAccessible:Boolean; //the bitmapData is currently accessible + protected var _bitmapAccessTested:Boolean; //the test for rtmp access to bitmapData has been performed. + protected var _rtmpWait:Boolean; //if an bitmap write fails after having been successful, keep waiting until metaData + protected var _rtmpMonitor:Boolean; //for an rtmp stream, monitor access after Play.Stop + protected var _updateSeekPos:Boolean; //misc flags + protected var _reset:Boolean; //reset flag to capture initial frame for rtmp content paused at start; + protected var _bufferTimeChange:Boolean; //misc flags + protected var _waitOneFrame:Boolean; //misc flags + + protected var _seekQueue:Array=[]; //queue of seek requests within this stream + protected var _maxCurrentSeekablePosition:Number=0; //for http: maximum observed seekable keyframe if not full loaded or explicit metadata for fully loaded content + + + //VideoStream characteristics + protected var _forceUpdates:Boolean; //misc flags + protected var _playheadUpdateInterval:uint=250; //how often to dispatch playheadUpdates + protected var _seekUpdateTimeFrame:uint=30; //combine all seek operations that occur within this many milliseconds timeframe into a single seek operation + protected var _updateTimer:Timer=new Timer(_playheadUpdateInterval, 0); //timers related to the above settings + protected var _seekTimer:Timer=new Timer(_seekUpdateTimeFrame,1);//timers related to the above settings + protected var _seekTarget:Number; //last requested seek value during the seek update timeframe + + //audio + protected var _volume:Number=0.5; + + //content related + protected var _invalidated:Boolean; + protected var _copyTargets:Dictionary; + protected var _copyTargetCount:uint; + protected static const copyPoint:Point=new Point(); + protected static const DISPOSAL_DEFERRAL:uint=35; + + /** + * @private + * TODO... not yet complete. Intended for use in String url assignments to the source property in a VideoFill as a shortcut approach to referencing content + * similar to BitmapFill + */ + public function getUniqueInstance(url:String, loc:LoadingLocation=null):VideoStream + { + //if the url is absolute, then ignore any loading location parameter (should this be an error instead?) + if (LoadingLocation.isAbsoluteURL(url)) + { + if (loc) trace('WARNING: had a request for ' + url + ' with a loading location specified. Ignoring the loading location because the url is an absolute url') + var decoded:Object=LoadingLocation.extractLocation(url); + } + if (!loc) + { + loc=new LoadingLocation(); + if (LoadingLocation.isAbsoluteURL(url)) + { + decoded=LoadingLocation.extractLocation(url); + } + } + return new VideoStream(); + } + + /** + * Constructor + * VideoStream encapsulates the functionality required to support provision of video content in BitmapData format. + * It provides support for both http progressive video and rtmp streamed video, subject to access permissions to the underlying bitmapdata. + * */ + public function VideoStream() + { + _updateTimer.addEventListener(TimerEvent.TIMER, onUpdate,false,0,true); + _seekTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onSeekReady,false,0,true); + _vid.smoothing=true; + } + + /** + * @private + * onSeekReady is used to handle timer based consolidation of seek requests + * to prevent excessive seeking via binding to a thumb/track type control with liveDragging + * */ + protected function onSeekReady(e:TimerEvent):void{ + if(_ns){ + _seekQueue.push(_seekTarget); + _seekTimer.reset(); + if (!_isSeeking) { + performSeek(_seekTarget); + _isSeeking=true; + _seekTarget=NaN; + } else _seekTimer.start(); + } + } + /** + * @private + * destroyStream + * */ + protected function destroyStream():void + { + ConnectionItem.decommissionNetStream(_ns); + _ns.removeEventListener(NetStatusEvent.NET_STATUS, statusMonitor); + _ns.removeEventListener(IOErrorEvent.IO_ERROR, ioErrhandler); + _ns.close(); + _ns=null; + } + + /** + * @private + * onBWDone callback + * */ + protected function onBWDone(...args):void{ + //TODO + trace('onBWDone'); + if (args.length && args[0]) { + trace('received bandwidth data:'+args.length+" element(s):") + trace(args.join(" :: ")) + } + } + + /** + * @private + * onFCUnsubscribe callback + * */ + protected function onFCUnsubscribe(info:Object):void{ + //TODO + trace('onFCUnsubscribe'); + } + + /** + * @private + * onFCSubscribe callback + * */ + protected function onFCSubscribe(info:Object):void{ + //TODO + trace('onFCSubscribe'); + } + + /** + * @private + * connection status listener + * */ + protected function onConnectionStatus(e:NetStatusEvent):void + { + var oldErr:String=_errorDetail; + switch (e.info.code) + { + case CONNECT_SUCCESS: + ConnectionItem(__connections[_nc.uri]).status=ConnectionItem.IN_USE; //=new ConnectionItem(_nc); + if (_ns) + { + _vid.attachNetStream(null); + _ns.close(); + } + _ns=new NetStream(_nc); + _ns.bufferTime=_bufferTime; + + _ns.client= clientObj; //using client object to access private methods inside this VideoStream instance + _ns.addEventListener(NetStatusEvent.NET_STATUS, statusMonitor, false, 0, true); + _ns.addEventListener(IOErrorEvent.IO_ERROR, ioErrhandler, false, 0, true); + _ns.soundTransform=_st; + _isConnected=true; + + + updateErrorStatus(ERROR_STATUS_NONE,ERROR_STATUS_NONE,_errorStatus!=ERROR_STATUS_NONE); + play(); + break; + case CONNECT_FAILED: + ConnectionItem(__connections[_nc.uri]).status=ConnectionItem.ERROR; + updateErrorStatus(ERROR_STATUS_CONNECTION_ACCESS,"The attempt to connect to the server at\n" + loadingLocation.basePath + "\nhas failed"); + if (_debug) showDebug(_errorDetail, 400, 400); + _isConnected=false; + break; + case CONNECT_CLOSED: + + if (__connections[_nc.uri]){ + updateErrorStatus(ERROR_STATUS_CONNECTION_ACCESS,"The connection to the server at\n" + loadingLocation.basePath + "\nhas been closed"); + ConnectionItem(__connections[_nc.uri]).status=ConnectionItem.ERROR; + if (_debug) showDebug(_errorDetail, 400, 400); + } + _isConnected=false; + break; + case CONNECT_APPSHUTDOWN: + _errorDetail="The server at\n" + loadingLocation.basePath + "\nhas shut down the application on the server"; + _errorStatus=ERROR_STATUS_CONNECTION_ACCESS; + ConnectionItem(__connections[_nc.uri]).status=ConnectionItem.ERROR; + if (_debug ) showDebug(_errorDetail, 400, 400); + _isConnected=false; + break; + case CONNECT_REJECTED: + _errorDetail="The server at\n" + loadingLocation.basePath + "\nhas rejected the connection attempt"; + _errorStatus=ERROR_STATUS_CONNECTION_ACCESS; + ConnectionItem(__connections[_nc.uri]).status=ConnectionItem.ERROR; + if (_debug ) showDebug(_errorDetail, 400, 400); + _isConnected=false; + break; + default: + trace('unhandled netconnection status') + break; + } + if (oldErr!=_errorDetail) dispatchUpdateEvent(ERROR_STATUS_UPDATE); + } + + + /** + * @private + * set up a http progressive video connection + * */ + protected function prepHttpCon():void + { + //connect only once + if (!_httpConnected) + { + //'null' connection + _httpNC.connect(null); + //static connection flag for http + _httpConnected=true; + } + //this instance is connected: + _isConnected=true; + //use the single connection for all http netstreams + _ns=new NetStream(_httpNC); + + //catchall in addition to any LoadingLocation: + _ns.checkPolicyFile=true; + _ns.bufferTime=_bufferTime; + _httpCached=false; //cacheing needs to be rechecked + _maxCurrentSeekablePosition=0; //tracking for http max seek positions + + _ns.client=clientObj; + _ns.addEventListener(NetStatusEvent.NET_STATUS, statusMonitor, false, 0, true); + _ns.addEventListener(IOErrorEvent.IO_ERROR, ioErrhandler,false,0,true); + _ns.bufferTime=_bufferTime; + _ns.soundTransform=_st; + + } + + protected static var _httpNC:NetConnection=new NetConnection(); + protected static var _httpConnected:Boolean; + //clientObj is required for netstream access to private methods inside this instance + protected var clientObj:Object={onBWDone:onBWDone,onFCSubscribe:onFCSubscribe, onCuePoint: onCuePoint, onMetaData: onMetaData, onTextData: onTextData, onImageData: onImageData, onPlayStatus: onPlayStatus, onXMPData: onXMPData}; + + + + protected function resetInternalState():void{ + + _wasBitmapAccessible=false; + _bitmapAccessible=false; + _bitmapAccessTested=false; + //reset any letterbox detections + _letterBoxContent=null; + _letterBoxDetected=false; + _letterBoxChecked=false; + //mark the content as changed by zeroing the _width and _height + _width=_height=0; + _currentFPS=0; + //clear the display if we have switched urls + if (_bitmapData) _bitmapData.fillRect(_bitmapData.rect,0); + if(_copyTargetCount) { + var copyRect:Rectangle = _bitmapData.rect + for (var copyBitmapData:Object in _copyTargets){ + var copyData:BitmapData = BitmapData(copyBitmapData); + //make a copy + copyData.copyPixels(_bitmapData,copyRect,copyPoint); + //transform it + var ctrans:ColorTransform = _copyTargets[copyBitmapData] as ColorTransform; + if (ctrans) { + copyData.colorTransform(copyRect,ctrans); + } + } + } + if (_position){ + _position=0; + dispatchStatusEvent(PLAY_STATUS_UPDATE); + } + + if (_bytesLoaded||_bytesTotal ) { + _bytesLoaded=0; + _bytesTotal=0; + dispatchUpdateEvent(LOAD_UDPATE) + } + _metaData=null; + _bufferLength=0; + } + + + /** + * @private + * instantiate supporting classes for video connection and reset all flags and status variables + * */ + protected function initVideoSupport(streamingApplicationURL:String=null):void + { + if (!_st) { + _st=new SoundTransform(); + } + _st.volume=_volume; + resetInternalState(); + + if (!streamingApplicationURL) + { + prepHttpCon(); + + } + else + { + if (_rtmp) + { + + if (__connections[streamingApplicationURL]) + { + var connItem:ConnectionItem = ConnectionItem(__connections[streamingApplicationURL]); + if (connItem.status != ConnectionItem.CLOSED) { + //re-use the existing connection + _nc=ConnectionItem(__connections[streamingApplicationURL]).increment(onConnectionStatus,ioErrhandler); + //call connect handler: + var info:Object={code:CONNECT_SUCCESS} + _nc.dispatchEvent(new NetStatusEvent(NetStatusEvent.NET_STATUS,false,false,info)) + } else { + //reconnect + _nc=ConnectionItem(__connections[streamingApplicationURL]).increment(onConnectionStatus,ioErrhandler); + } + } + else + { + _nc=new NetConnection(); + __connections[streamingApplicationURL]=new ConnectionItem(_nc,clientObj,onConnectionStatus,ioErrhandler,streamingApplicationURL,true); + + } + } + else + { + prepHttpCon(); + } + } + dispatchStatusEvent(STATUS_INITIALIZING); + } + + + + /** + * @private + * */ + private static var _testRect:Rectangle=new Rectangle(0,0,1,1); + /** + * @private + * data access check + * */ + private function hasAccess():Boolean{ + try { + _testBitmapData.draw(_vid,null,null,null,_testRect); + return true; + } catch (e:Error){ } + return false; + } + + + + /** + * @private + * */ + protected var _streamStatus:uint; + + /** + * @private + * stream status event listener/monitor + * */ + protected function statusMonitor(e:NetStatusEvent):void + { + var info:Object=e.info; + switch (info.level) + { + case "error": + + switch (info.code) + { + case "NetStream.Play.StreamNotFound": + _errorStatus=ERROR_STATUS_CONTENT_ACCESS; + _errorDetail="No video stream was found \n" + loadingLocation.basePath + "\n" + _workingUrl; + + if (_debug) + showDebug(_errorDetail,400,400); + + break; + case "NetStream.Play.FileStructureInvalid": + _errorStatus=ERROR_STATUS_CONTENT_ACCESS; + _errorDetail="The following video stream was invalid \n" + loadingLocation.basePath + "\n" + _workingUrl; + + if (_debug) + showDebug(_errorDetail,400,400); + + break; + case "NetStream.Play.Failed": + _errorStatus=ERROR_STATUS_CONTENT_ACCESS; + _errorDetail="The following video stream was unable to play \n" + loadingLocation.basePath + "\n" + _workingUrl + "\n" + info.description; + + if (_debug) + showDebug(_errorDetail,400,400); + + + break; + case "NetStream.Seek.Failed": + + if (!_rtmp) + { + //seek to the maximum possible + + _isSeeking=false; + _seekQueue.length=0; + //let's assume that this is not a cached copy otherwise the seek should have worked + _httpCached=false; + //force an update for position listeners + dispatchStatusEvent(PLAY_STATUS_UPDATE); + } + else + { + var tmp:Number=Number(_seekQueue.shift()); + _isSeeking=Boolean(_seekQueue.length); + + dispatchStatusEvent(PLAY_STATUS_UPDATE); + } + + break; + case "NetStream.Seek.InvalidTime": + + + if (!_rtmp) + { + + _isSeeking=false; + _seekQueue.length=0; + //we can be sure that this is not a cached copy otherwise the seek should have worked + _httpCached=false; + //force an update for position listeners + dispatchStatusEvent(PLAY_STATUS_UPDATE); + } + else + { + tmp=Number(_seekQueue.shift()); + _isSeeking=Boolean(_seekQueue.length); + + dispatchStatusEvent(PLAY_STATUS_UPDATE); + } + + break; + + default: + + break; + + } + break; + case "status": + + switch (info.code) + { + case "NetStream.Play.Start": + if (!_isReady) + { + _isReady=true; + if (_autoPlay){ + //playing intent + playStatus=PLAY_PLAYING; + } + else + { + //stopped intent + if (!_rtmp) _ns.pause(); + else { + _requiresMetaData=true; + _reset=true; + } + playStatus=PLAY_STOPPED; + } + + return; + } + + if (_rtmp ) { + //reset for updated permissions + + if (!hasAccess()){ + _wasStreamingSeek=true; + if (_bitmapAccessible) _wasBitmapAccessible=true; + _bitmapAccessible=false; + _bitmapAccessTested=false; + if (_isPaused) { + _requiresMetaData=true; + } + if (playStatus==PLAY_PAUSED && !_isPaused) { + _ns.pause(); + } + } + + } + + break; + + case "NetStream.Play.Reset": + + playStatus=PLAY_STATUS_RESET + + break; + case "NetStream.Play.Stop": + + if (_ns.time >= _duration || (Math.abs(_ns.time - _duration) < 0.1)) + { + if (_autoLoop && (_streamStatus != STREAM_STATUS_LOOPING)) + { + _streamStatus=STREAM_STATUS_LOOPING; + _seekQueue.length=0; + _seekQueue.push(0); + _isSeeking=true; + _isPlaying=false; + performSeek(0); + } + } else { + if (_rtmp){ + _rtmpMonitor=true; + } else _isPlaying=false; + } + + break; + case "NetStream.Play.Complete": + //only happens for rtmp streams, not progressive + if (_duration){ + if (_ns.time >= _duration || (Math.abs(_ns.time - _duration) < 0.1)){ + _isPlaying=false; + if (_autoLoop && (_streamStatus != STREAM_STATUS_LOOPING)) + { + _streamStatus=STREAM_STATUS_LOOPING; + _seekQueue.length=0; + _seekQueue.push(0); + _isSeeking=true; + performSeek(0); + } + } + playStatus=PLAY_STATUS_COMPLETE + + } + + break; + case "NetStream.Buffer.Full": + + if (!_isReady) + { + //first time playing + if (_autoPlay) + { + playStatus=PLAY_PLAYING; + _isPlaying=true; + _isPaused=false; + if (!_rtmp && !_vid.hasEventListener(Event.ENTER_FRAME)) + _vid.addEventListener(Event.ENTER_FRAME, videoFrameUpdate, false, 0, true); + } + else + { + playStatus=PLAY_STOPPED; + _isPlaying=false; + _isPaused=true; + _ns.pause() + } + if (!_rtmp) { + _isReady=true; + } + } + + + if (_bufferStatus!=BUFFER_FULL){ + _bufferLength=_ns.bufferLength; + _bufferStatus=BUFFER_FULL; + dispatchStatusEvent(BUFFER_STATUS_CHANGE); + } + break; + case "NetStream.Buffer.Flush": + + + if (_bufferStatus!=BUFFER_FLUSHING){ + _bufferLength=_ns.bufferLength; + _bufferStatus=BUFFER_FLUSHING; + dispatchStatusEvent(BUFFER_STATUS_CHANGE); + } + break; + + case "NetStream.Buffer.Empty": + + _bufferTime=_ns.bufferTime + if (_ns.time >= _duration || (Math.abs(_ns.time - _duration) < 0.1)) + { + if (_autoLoop && (_streamStatus != STREAM_STATUS_LOOPING)) + { + _streamStatus=STREAM_STATUS_LOOPING; + _seekQueue.length=0; + _seekQueue.push(0); + _isSeeking=true; + performSeek(0); + } + } + _bufferStatus=BUFFER_EMPTY + + break; + case "NetStream.Seek.Notify": + + if (_reset){ + playStatus=PLAY_STOPPED; + _ns.pause(); + _isSeeking=false; + _isPlaying=false; + _reset=false; + break; + } + if (_seekQueue.length) _position=Number(_seekQueue.shift()); //approximate + _wasStreamingSeek=_rtmp ; + _isSeeking=Boolean(_seekQueue.length); + //is this an autoLoop or a rewind? + if (playStatus == PLAY_STOPPED && !_isSeeking && _position != 0) + { + //we have seeked from the stopped state at the start...so switch to PAUSE intent + playStatus=PLAY_PAUSED; + } + + //if we have seeked back to the start as an autoLoop setting, then reset the looping state + if (_streamStatus == STREAM_STATUS_LOOPING && _position == 0) + { + _streamStatus=STREAM_STATUS_NORMAL; + //autoset the play intent: looping happens when the playhead hits the end of the video + playStatus=PLAY_PLAYING; + } + //if we have seeked back to the start as an rewind action , then reset the rewinding state + if (_streamStatus == STREAM_STATUS_REWINDING && _position == 0) + { + _streamStatus=STREAM_STATUS_NORMAL; + //autoset the play intent: a rewind action results in a 'stopped at start' behaviour + playStatus=PLAY_STOPPED; + } + + if (_wasStreamingSeek) + { + if (_bitmapAccessible) _wasBitmapAccessible=true; + _bitmapAccessible=false; + _bitmapAccessTested=false; + } + else { + while (_seekQueue.length) + { + _position=Number(_seekQueue.shift()); + _isSeeking=false; + _updateSeekPos=true; + + } + if (!_vid.hasEventListener(Event.ENTER_FRAME)) _vid.addEventListener(Event.ENTER_FRAME, videoFrameUpdate,false,0,true); + } + + if (!_isSeeking) + { + _seekStatus=SEEK_STATUS_END + dispatchStatusEvent(_seekStatus); + _seekStatus=SEEK_NONE; + //if there was a pause request during seeking: + if (!_isPaused && (playStatus == PLAY_PAUSED || playStatus == PLAY_STOPPED)) + { + if (!_rtmp) _isPaused=true; + _ns.pause(); + } + + //http seek ? + if (!_rtmp) + { + dispatchStatusEvent(PLAY_STATUS_UPDATE); + } + } + else{ + _seekStatus=SEEK_SEEKING; + performSeek(Number(_seekQueue.shift())) + dispatchStatusEvent(_seekStatus); + } + break; + case "NetStream.Unpause.Notify": + if (_requiresMetaData && playStatus==PLAY_PAUSED){ + + break; + + } + _isPaused=false; + _isPlaying=true; + + if (!_vid.hasEventListener(Event.ENTER_FRAME)) _vid.addEventListener(Event.ENTER_FRAME, videoFrameUpdate, false, 0, true); + playStatus=PLAY_PLAYING; + dispatchStatusEvent(PLAY_STATUS_UPDATE); + break; + case "NetStream.Pause.Notify": + _isPaused=true; + _isPlaying=false; + //autoPlay is set to false? then handle the pause at the start + if (!_isReady && !_autoPlay) + { + _isReady=true; + dispatchStatusEvent(PLAY_STATUS_UPDATE); + } + + _vid.removeEventListener(Event.ENTER_FRAME, videoFrameUpdate); + if (_streamStatus == STREAM_STATUS_REWINDING) + { + _seekQueue.length=0; + _seekQueue.push(0); + _isSeeking=true; + performSeek(0); + } + break; + default: + trace("UNHANDLED STATUS EVENT:" + info.code); + break; + } + break; + } + } + + /** + * @private + * */ + protected function onCuePoint(infoObject:Object):void + { + //TODO make this available via binding + trace("onCuePoint"); + } + + /** + * @private + * */ + protected function onXMPData(infoObject:Object):void + { + //TODO make this available via binding + trace('received XMP data:' ); + /* for (var thing:String in infoObject){ + trace(thing+":"+XML(infoObject[thing]).toXMLString()); + }*/ + } + + + /** + * @private + * Important handler: required information is included in metadata and it signals bitmapdata access for rtmp streams. + * rtmp streams must be configured to sendDuplicateMetaData (e.g. FMS options) + * */ + + protected function onMetaData(infoObject:Object):void + { + _requiresMetaData=false; + + _metaData=infoObject; + if (_metaData.duration != _duration) + { + initChange("duration", _duration, _duration=_metaData.duration, this); + } + //earliest point that we have valid width and height data for the video itself + if (_metaData.width && (_metaData.width != _width || _metaData.height != _height)) + { + + _width=_metaData.width; + _height=_metaData.height + if (_vid) + { + _vid.attachNetStream(null) + + _vid.clear(); + _vid.width=_width; + _vid.height=_height; + _offset.a=_vid.scaleX; + _offset.d=_vid.scaleY; + } + + if (!_rtmp) + { + if (_ns.bytesTotal && (_ns.bytesLoaded == _ns.bytesTotal)) + { + //assume cached + _httpCached=true; + } + } + + if (_wasStreamingSeek) + { + if (!_seekQueue.length) + { + _wasStreamingSeek=false; + _seekStatus=SEEK_NONE; + _waitOneFrame=true; + } + return; + } + + if (_streamStatus == STREAM_STATUS_LOOPING || _streamStatus == STREAM_STATUS_REWINDING) + { + return; + } + if (_bitmapData) + { + var oldBD:BitmapData=_bitmapData; + + } + if (_width && _height) + { + _bitmapData=new BitmapData(_width + _pixelMargin * 2, _height + _pixelMargin * 2, false, 0); + + if (oldBD){ + //get rid of the old bitmapdata and dispatch a change + initChange("content", null, _bitmapData, this); + oldBD.dispose(); + } + } + else + trace('need to deal with situation with width and height are not yet known') + + _vid.attachNetStream(_ns); + + if (!_vid.hasEventListener(Event.ENTER_FRAME)) _vid.addEventListener(Event.ENTER_FRAME, videoFrameUpdate, false, 0, true); + + dispatchStatusEvent(STATUS_READY); + + } + else + { + if (_wasStreamingSeek) + { + if (!_seekQueue.length) + { + _wasStreamingSeek=false; + _seekStatus=SEEK_NONE; + _waitOneFrame=true; + } + } + if (_wasBitmapAccessible ){ + //assume it is again + _bitmapAccessible=_bitmapAccessTested=true; + if (!_vid.hasEventListener(Event.ENTER_FRAME)) _vid.addEventListener(Event.ENTER_FRAME, videoFrameUpdate,false,0,true); + _waitOneFrame=true; + if (!_isSeeking) _seekStatus=SEEK_NONE; + return; + } + + } + + } + + + /** + * @private + * */ + private function onTextData(textData:Object):void + { + trace("client onTextData"); + trace("--- textData properties ----"); + var key:String; + + for (key in textData) + { + trace(key + ": " + textData[key]); + } + } + + /** + * @private + * */ + private function onImageData(imageData:Object):void + { + trace("client onImageData"); + //TODO: make this accessible via binding + var imageloader:Loader=new Loader(); + imageloader.loadBytes(imageData.data); + _latestImage=(imageloader.content as Bitmap).bitmapData.clone(); + } + + /** + * @private + * */ + private function onPlayStatus(infoObject:Object):void + { + //fire it through the statusMonitor handler for consistency + _ns.dispatchEvent(new NetStatusEvent(NetStatusEvent.NET_STATUS, false, false, infoObject)) + } + + /** + * @private + * */ + private function ioErrhandler(e:IOErrorEvent):void + { + //TODO: finalise error handling + var target:Object=e.target; + if (target == _ns) + { + _errorStatus=ERROR_STATUS_DATA_ACCESS; + _errorDetail="A stream error has occured for "+_setUrl; + } + else if (target == _nc) + { + _errorStatus=ERROR_STATUS_CONNECTION_ACCESS; + _errorDetail="A connection error has occured for "+_nc.uri; + } + dispatchUpdateEvent(ERROR_STATUS_UPDATE); + } + + /** + * @private + * utility support to show a visual cue when debugging via the bitmapData content. + * This is usually triggered by a lack of bitmapData access permission, but could be for other reasons. + * + * */ + private function showDebug(msg:String, width:uint=0, height:uint=0):void + { + var force:Boolean; + var widthtarg:uint=(width ? width : _bitmapData ? _bitmapData.width : 400); + var t:TextField=new TextField(); + t.width=widthtarg; + t.wordWrap=true; + t.autoSize=TextFieldAutoSize.LEFT; + t.defaultTextFormat=new TextFormat("_sans", 12, 0x000000, false, false, false, null, null, "center"); + t.text=msg; + if (width && height) + { + if (!_bitmapData || (_bitmapData && (_bitmapData.width != width || _bitmapData.height != height))) + { + _bitmapData=new BitmapData(width, height, false, 0xffffff); + force=true; + } + } + if (!_bitmapData) + { + _bitmapData=new BitmapData(t.width, t.height, false, 0xffffff); + force=true; + } + _bitmapData.fillRect(_bitmapData.rect, 0xffffff) + _bitmapData.draw(t, new Matrix(1, 0, 0, 1, 0, (_bitmapData.height - t.height) / 2), null, null, null, true); + if (force) + dispatchStatusEvent(STATUS_READY); + + } + + /** + * @private + * */ + private var _lastDecFrame:uint; + /** + * @private + * */ + private var _lastcheckFrame:uint; + /** + * @private + * */ + private var _offset:Matrix=new Matrix(1, 0, 0, 1, _pixelMargin, _pixelMargin); + + + /** + * @private + * */ + private function videoFrameUpdate(e:Event,forceRedraw:Boolean=false):void + { + var triggerChange:Boolean=_forceUpdates; + //detect new frame + if (_isSeeking) + { + + return; + } + if (_requiresMetaData ) { + + if (_reset) _st.volume=0; + if (!hasAccess()) { + if (_wasStreamingSeek){ + + } + return; + } + else { + _requiresMetaData=false; + _bitmapAccessible=true; + _bitmapAccessTested=true; + _wasStreamingSeek=false; + } + } + + if (!_bitmapAccessible && _bitmapAccessTested) + { + _vid.removeEventListener(Event.ENTER_FRAME, videoFrameUpdate); + return; + } + if (_wasStreamingSeek) + { + //retest + _bitmapAccessible=false; + _bitmapAccessTested=false; + return; + } + + if (_waitOneFrame) { + _waitOneFrame=false; + return; + } + + _lastcheckFrame=_ns.decodedFrames; + if (_lastcheckFrame != _lastDecFrame || forceRedraw) + { + _lastDecFrame=_lastcheckFrame; + + if (!_rtmp && (_maxCurrentSeekablePosition < _ns.time)) + { + if (_updateSeekPos) + _updateSeekPos=false; + //haven't figured out a way to detect keyframes yet during play + _maxCurrentSeekablePosition=_ns.time; + } + + var copyBitmapData:Object; + var cbd:BitmapData; + var copyRect:Rectangle = _bitmapData.rect + var ctrans:ColorTransform; + if (_bitmapAccessible) + { + if (_rtmpMonitor ){ + if ( !hasAccess()) { + return; + } else _rtmpMonitor=false; + } + _bitmapData.draw(_vid, _offset, null, null, null,_quality==HIGH); + + + if(_copyTargetCount) { + for (copyBitmapData in _copyTargets){ + cbd = BitmapData(copyBitmapData); + // copyData.lock(); + ctrans = _copyTargets[copyBitmapData] as ColorTransform; + + if (ctrans && ctrans.alphaMultiplier>0) { + //make a copy + cbd.copyPixels(_bitmapData,copyRect,copyPoint); + + //colortransform it + cbd.colorTransform(copyRect,ctrans); + + } else { + cbd.fillRect(copyRect,0); + } + + } + } + + + //check for letterboxing within the first 100 frames + //TODO: make this smarter + if (_detectLetterBox && !_letterBoxChecked){ + + if ( _checkLBLimit) { + var checkRect:Rectangle + + if (!_letterBoxContent) { + _checkLetterBoxData=new BitmapData(_width,_height,false,0xffffff); + _letterBoxHelper=new Rectangle(_pixelMargin,_pixelMargin,_width,_height); + _checkLetterBoxData.threshold(_bitmapData,_letterBoxHelper,copyPoint,">=",0x080808,0xffffff,0xffffff,false); + checkRect=_checkLetterBoxData.getColorBoundsRect(0x0,0x0,false); + if(!checkRect.equals(_checkLetterBoxData.rect)&&!checkRect.isEmpty()){ + _letterBoxContent=checkRect; + _letterBoxDetected=true; + dispatchUpdateEvent(LETTERBOX_UPDATE); + triggerChange=true + } + } else { + + _checkLetterBoxData.threshold(_bitmapData,_letterBoxHelper,copyPoint,">=",0x080808,0xffffff,0xffffff,false); + checkRect=_checkLetterBoxData.getColorBoundsRect(0x0,0x0,false); + if (!checkRect.equals(_checkLetterBoxData.rect) && !_letterBoxContent.equals(checkRect)&&!checkRect.isEmpty()){ + _letterBoxContent=checkRect; + _letterBoxDetected=true; + + dispatchUpdateEvent(LETTERBOX_UPDATE); + triggerChange=true + } + } + _checkLBLimit--; + + } else { + //cleanup + if (_checkLetterBoxData) { + deregisterCopyTarget(_checkLetterBoxData,true); + _checkLetterBoxData=null; + } + _checkLBLimit=100; + _letterBoxChecked=true; + + } + } + + if (triggerChange) + initChange("content", _bitmapData, _bitmapData, this); + } + else + { + + try + { + _bitmapAccessTested=true; + _bitmapData.draw(_vid, _offset, null, null, null, true); + + if(_copyTargetCount) { + + for ( copyBitmapData in _copyTargets){ + + cbd = BitmapData(copyBitmapData); + ctrans = _copyTargets[copyBitmapData] as ColorTransform; + + if (ctrans && ctrans.alphaMultiplier>0) { + //make a copy + cbd.copyPixels(_bitmapData,copyRect,copyPoint); + //colortransform it + cbd.colorTransform(copyRect,ctrans); + + } else { //zero alpha optimization + cbd.fillRect(copyRect,0); + } + } + } + + if (triggerChange) + initChange("content", _bitmapData, _bitmapData, this); + _bitmapAccessible=true + + if (_reset){ + playStatus=PLAY_STOPPED; + performSeek(0); + } + } + catch (err:Error) + { + _bitmapAccessible=false; + _errorStatus=ERROR_STATUS_DATA_ACCESS; + _errorDetail="There is a problem with " + (_rtmp ? " streaming VideoSampleAccess " : " crossdomain ") + " permissions to permit access\nto VideoStream " + this.id + "'s bitmapdata"; + + + if (_debug) + showDebug(_errorDetail,400,400); + } + } + } else { + if (!_rtmp && isPaused){ + if (_vid.hasEventListener(Event.ENTER_FRAME)) _vid.removeEventListener(Event.ENTER_FRAME, videoFrameUpdate); + + } + + } + } + + /** + * @private + * */ + private var _autoPlay:Boolean=true; + + public function set autoPlay(value:Boolean):void + { + if (value != _autoPlay) + { + _autoPlay=value; + initChange("autoPlay", !_autoPlay, _autoPlay, this); + } + } + + public function get autoPlay():Boolean + { + return _autoPlay; + } + + /** + *An optional loadingLocation reference. Using a LoadingLocation simplifies management of groups of video assets from other domains + *by permitting different locations (alternate domains used for loading) to be specified once in code.
    + *If a loadingLocation is specified the url property in the VideoStream must be relative to the basePath specified in the LoadingLocation. + *For http progressive download video, permission rules are subject to flash standard crossdomain permissions. + *If an VideoStream's domain has a non-default policy file, a LoadingLocation must be used to specify the explicit location and + *name of the cross-domain file that grants access. An ExternalDataAsset without a LoadingLocation will only check for permission + *in the default location and name (web document root, crossdomain.xml) for permission to access the remote file's BitmapData. + * For rtmp streamed video content, a loadingLocation basePath provides an easily maintained reference to a video application, and the possibility + * to specifiy the video assets as being relative to that location. + */ + public function set loadingLocation(value:LoadingLocation):void + { + if (value != _loadingLocation) + _invalidated=true; + _loadingLocation=value; + } + + public function get loadingLocation():LoadingLocation + { + if (_implicitLocation) return _implicitLocation; + if (!_loadingLocation) + _loadingLocation=new LoadingLocation(); + return _loadingLocation; + } + + /** + * the url of the external asset + * assignable as either a string representing a url relative to an associated LoadingLocation basePath or as a regular url + * For alternate domain loading use an associated LoadingLocation + * assigned via the loadingLocation property and make this url relative to the basePath defined in the LoadingLocation + */ + public function get url():String + { + return _setUrl; + + } + private var _delayedSetter:Boolean; + + private function delayedURLsetter():void + { + _delayedSetter=true; + var toSet:String=_setUrl; + _setUrl=""; + url=toSet; + } + + public function set url(value:String):void + { + if (_setUrl != value) + { + playStatus=PLAY_STATUS_WAITING; + + if (_vid.hasEventListener(Event.ENTER_FRAME)) _vid.removeEventListener(Event.ENTER_FRAME,videoFrameUpdate); + _isReady=false; + _updateTimer.stop(); + if (_ns) destroyStream(); + + _setUrl=value; + + + if (_nc && _nc.uri.length){ + //remove listeners and request autoclose for the old connection + ConnectionItem(__connections[_nc.uri]).decrementAndAutoClose(onConnectionStatus,ioErrhandler); + _nc=null; + //update position if its not zero + if (_position) { + _position=0; + dispatchStatusEvent(PLAY_STATUS_UPDATE); + } + } + //was it null or empty String? + if (!value || value=="") { + _contentType=NONE; + if (_bitmapData){ + //erase to black + _bitmapData.fillRect(_bitmapData.rect,0); + } + resetInternalState(); + + return; + } + + + + //if the url is relative, then assume it is http unless a LoadingLocation is set + if (!_loadingLocation && _setUrl.indexOf("//") == -1) + { + if (!_delayedSetter) + { + //wait in case the loadingLocation has been set via mxml and is not yet instantiated + //don't much like this, can't think of anything better atm for mxml assignment/instantiation delay + setTimeout(delayedURLsetter, 1) + return; + } + _relativeURL=true; + _workingUrl=_setUrl; + //if there's no loadinging location and its relative, then assume its always http + _rtmp=false; + _protocol="http"; + + } + else + { + //the url is either full or it is relative and there is a loadingLocation to which it is relative + if (_setUrl.indexOf("//") != -1) + { + //it's not relative + _relativeURL=false; + var decoded:Object=LoadingLocation.extractLocation(_setUrl); + var rawProtocol:String=decoded.protocol.split(":")[0]; + _protocol=rawProtocol; + if (_streamingProtocols.indexOf(rawProtocol) != -1) + { + _rtmp=true; + _workingUrl=_setUrl.split(decoded.basepath)[1]; + + //wowza permits this, it seems FMS 3.5 does not, so make take it down to common ground + if (_workingUrl.indexOf("flv:")!=-1) _workingUrl=_workingUrl.split("flv:").join(""); + //ditto for flv extension + if (_workingUrl.lastIndexOf(".flv")==(_workingUrl.length-4))_workingUrl=_workingUrl.substring(0,_workingUrl.length-4); + + } + else + { + _rtmp=false; + _workingUrl=_setUrl; + + } + //if we already had a loadingLocation in the past and have just decoded a full url with a different basePath, then create a new LoadingLocation instance + + //this is an implicit loadingLocation + if (!_implicitLocation) _implicitLocation=new LoadingLocation(); + _implicitLocation.basePath=decoded.protocol + decoded.domain + decoded.basepath; + + _implicitLocation.requestPolicyFile(); + } + else + { + //it's relative (to an explicit LoadingLocation) + _implicitLocation=null; + decoded=LoadingLocation.extractLocation(_loadingLocation.basePath) + rawProtocol=decoded.protocol.split(":")[0]; + _protocol=rawProtocol; + if (_streamingProtocols.indexOf(rawProtocol) != -1) + { + _rtmp=true; + _relativeURL=false; + _workingUrl=_setUrl; + + //no point trying to request a policy file here as its a streaming video + + //wowza permits this, it seems FMS 3.5 does not, so make take it down to common ground + if (_workingUrl.indexOf("flv:")!=-1) _workingUrl=_workingUrl.split("flv:").join(""); + //ditto + if (_workingUrl.lastIndexOf(".flv")==(_workingUrl.length-4))_workingUrl=_workingUrl.substring(0,_workingUrl.length-4); + + } + else + { + _rtmp=false; + + //is it really relative to the application or just to a loading location? + //check to see if its relative to the application's location (true relative), otherwise prepend the LoadingLocation's basepath + var appObject:Object = LoadingLocation.extractLocation(); + if (_loadingLocation.basePath.indexOf(appObject.protocol+appObject.domain+appObject.basepath)!=0) { + _relativeURL=false; + _workingUrl=_loadingLocation.basePath+_setUrl; + } else { + _workingUrl=_setUrl; + _relativeURL=true; + } + + _loadingLocation.requestPolicyFile(); + } + } + } + + _invalidated=true; + if (_rtmp) _contentType=STREAMING; + else _contentType=NON_STREAMING; + + + initVideoSupport(loadingLocation.basePath); + + if (!_rtmp) + play(); + } // else ignore the assigned value because it hasn't changed + + } + + /** + * The content property provides access to the BitmapData that this VideoStream generates.
    + * This should not usually be further manipulated directly but used as it is supplied. If this value is null, then the BitmapData is not + * currently available from the VideoStream. This property is intended to be used in situations where BitmapData is used as an input to + * drawing API functions. + * @see flash.display.BitmapData + * */ + public function get content():BitmapData + { + return _bitmapData; + } + +////--------------------AUDIO RELATED------------------------ + + /** + * The volume property lets you set or access the sound volume of the audio component of a VideoStream.
    + * The range of values is 0.0 (no sound) through to 1.0 (full volume).
    + * Defaults to 0.5 .
    + * If the muted property is true then adjusting this value will not automatically unmute the sound, unless the autoUnMute property is set to true. + * @see autoUnMute + * @see muted + * */ + public function get volume():Number + { + return _volume; + } + + public function set volume(value:Number):void + { + _volume=value; + if (!_muted || (_muted && _autoUnMute)){ + if (_st ) { + _st.volume=value; + if (_ns &&_st) _ns.soundTransform=_st; + } + if (_muted && _volume){ + //its autoUnMuted: + _muted=false; + dispatchUpdateEvent(AUDIO_STATUS_UPDATE) + } + } + } + + private var _autoUnMute:Boolean; + /** + * The autoUnMute property lets you determine whether unMuting will occur automatically if an adjustmet to the volume property is made.
    + * Defaults to false. + * When set to true, if the volume property is changed to a value other than zero while muted is true, then muted will be set to false. + * This permits easy configuration for the situation where manipulating the volume setting via a component is intended to switch off the + * muted state of the VideoStream. + * @see volume + * @see muted + * */ + public function get autoUnMute():Boolean{ + return _autoUnMute; + } + + public function set autoUnMute(val:Boolean):void{ + _autoUnMute=val; + } + + + + /** + * @private + * */ + private var _muted:Boolean; + [Bindable(event="audioStatusUpdate")] + /** + * The muted property lets you set or access the muted state of the audio component of a VideoStream.
    + * Defaults to false. + * A muted VideoStream has no audible sound, adjusting the volume. + * @see volume + * @see autoUnMute + * */ + public function get muted():Boolean{ + return _muted; + } + + public function set muted(val:Boolean):void{ + if (_muted!=val){ + _muted=val; + if (!_muted){ + if (_st) _st.volume=_volume; + } else { + if (_st) _st.volume=0; + } + if (_ns &&_st) _ns.soundTransform=_st; + dispatchUpdateEvent(AUDIO_STATUS_UPDATE) + + } + } + +///--------------------END AUDIO RELATED------------------------ + + + private var _autoLoop:Boolean=true; + + /** + * Determines whether this video stream automatically loops back to the start at position zero. Defaults to true. + */ + public function get autoLoop():Boolean + { + return _autoLoop; + } + + public function set autoLoop(value:Boolean):void + { + _autoLoop=value; + } + + //proxy getters for metadata: + [Bindable(event="propertyChange")] + public function get duration():Number + { + return _duration; + } + + + + public function get audioCodec():String + { + if (_metaData && _metaData.audiocodecid) + { + return String(_metaData.audiocodecid); + } + else + return "unknown"; + } + + /** + * Specifies an optional pixel padding around the bitmap that this VideoSource generates. This is useful to prevent color bleeding under some circumstances when the repeat + * setting on the VideoFill is not set to repeat and the fill is rotated or scaled. + */ + public function get pixelMargin():uint + { + return _pixelMargin; + } + + public function set pixelMargin(val:uint):void + { + if (_pixelMargin!=val) { + var oldVal:int=_pixelMargin; + _pixelMargin=val; + _offset.tx=_offset.ty=_pixelMargin; + if (_bitmapData) { + if (_width && _height) { + var oldBmp:BitmapData=_bitmapData; + _bitmapData=new BitmapData(_width+_pixelMargin*2,_height+_pixelMargin*2,false,0); + var tempRec:Rectangle= oldBmp.rect; + tempRec.inflate(-oldVal,-oldVal); + _bitmapData.copyPixels(oldBmp,tempRec,new Point(_pixelMargin,_pixelMargin)) + if (oldBmp) deregisterCopyTarget(oldBmp,true); + + initChange("pixelMargin",oldVal,_pixelMargin,this); + } + } + } + } + /** + * The reverseOffset property provides access to a read-only correction Matrix for the content BitmapData + * generated by this VideoStream. + * This matrix is the offset to the actual video content within the BitmapData. + */ + public function get reverseOffset():Matrix + { + return new Matrix(1, 0, 0, 1, -_pixelMargin, -pixelMargin); + } + + /** + * @private + */ + protected function dispatchStatusEvent(status:String):void + { + + dispatchEvent(new Event(status)); + + } + + /** + * @private + */ + protected function dispatchUpdateEvent(update:String):void + { + + dispatchEvent(new Event(update)); + } + + /** + * The playheadUpdateInterval property specifies whether to dispatch propertyChange events for every frame update of the video content.
    + * Defaults to false.
    + * Setting this to true can be useful in some situations with VideoFill, but it is usually best (i.e. less cpu intensive) to structure the Degrafa composition + * in such a way that it is not necessary to have this set to true.
    + * Situations to avoid are having filters or masking on the geometry that is being filled with video or in any of its parent hierarchy, including target displayobjects. + */ + protected function get forceUpdates():Boolean + { + return _forceUpdates; + } + + public function set forceUpdates(val:Boolean):void + { + _forceUpdates=val; + } + + /** + * The playheadUpdateInterval property specifies the precision of the update events for playheadTime in milliseconds.
    + * Defaults to 250 ms.
    + * Setting this to a lower value will dispatch playheadTime updates more frequently (and therefore with more precision) + * but is more CPU intensive. This property has a minimum value of 5, values lower than 5 will be set to 5. + * @default 250 + */ + public function set playheadUpdateInterval(val:uint):void + { + if (val < 5) + val=5; //minimum of 5 ms for this value + _playheadUpdateInterval=val; + _updateTimer.delay=_playheadUpdateInterval; + } + + public function get playheadUpdateInterval():uint + { + return _playheadUpdateInterval; + } + + /** + * @private + */ + protected var _lastUpdate:Number=0; + + /** + * @private + * listener to handle dispatching events for updated metrics related to the stream + * */ + protected function onUpdate(e:TimerEvent):void + { + + _position=_ns.time; + var curLen:Number=_ns.bufferLength; + var _updateBuffer:Boolean; + var _updatePlayhead:Boolean; + if (_bufferLength != curLen || _bufferTimeChange) + { + //logic: if bufferStatus was full or empty and it is now observed to not be full, then we are in a diminished buffer state + if (_bufferStatus == BUFFER_FULL && _bufferTime > curLen) + { + _bufferStatus=BUFFER_DIMINISHED; + dispatchStatusEvent(BUFFER_STATUS_CHANGE); + } else if (_bufferStatus == BUFFER_EMPTY && curLen > 0) + { + _bufferStatus=BUFFER_BUFFERING; + dispatchStatusEvent(BUFFER_STATUS_CHANGE); + } else if (_bufferStatus == BUFFER_DIMINISHED && _bufferTime <= curLen) + { + _bufferStatus=BUFFER_FULL; + dispatchStatusEvent(BUFFER_STATUS_CHANGE); + } + //this seemed to happen in rtmp (Wowza) - to verify + if (_bufferStatus==BUFFER_FLUSHING && curLen>_bufferTime) _bufferStatus=BUFFER_FULL; + + _updateBuffer=true; //flag to dispatch event + if (_bufferTimeChange) _bufferTimeChange=false; // transient flag has been dealt with + + } + + if (!_rtmp) + { + //dispatch loading and buffer update events + //observed: sometimes bytesTotal is -1 int value (or 4294967295), if it is ignore + if (!_bytesTotal && (_ns.bytesTotal>0 && _ns.bytesTotal!=4294967295)) + { + + _bytesTotal=_ns.bytesTotal; + dispatchUpdateEvent(LOAD_START) + } + if (_bytesLoaded != (_bytesLoaded=_ns.bytesLoaded)) + dispatchUpdateEvent(LOAD_PROGRESS) + //if we detect that the full video has downloaded: + if ( !_httpCached && _bytesTotal) { + if (_bytesLoaded==_bytesTotal) { + _httpCached=true; + dispatchUpdateEvent(LOAD_COMPLETE) + } + } + + } + + if (!_isSeeking) + { + if (_lastUpdate != _position) + { + + _lastUpdate=_position; + + _updatePlayhead=true; + } + } + + if (_updateBuffer) + dispatchUpdateEvent(BUFFER_STATUS_UPDATE); + if (_updatePlayhead) + dispatchUpdateEvent(PLAY_STATUS_UPDATE); + //frames per second support + var fps:Number=_ns.currentFPS; + if (fps!=_currentFPS){ + _currentFPS=fps; + if (!_isPaused) dispatchUpdateEvent(PLAY_STATUS_FPS_UPDATE); + } + + if (!_isConnected) _updateTimer.stop(); + } + + + [Bindable(event="loadUpdate")] + [Bindable(event="loadStart")] + /** + * The bytesTotal property lets you access the total amount of bytes to be loaded if this VideoStream + * is playing a progressive download (http) video file. + */ + public function get bytesTotal():uint + { + return _bytesTotal; + } + [Bindable(event="loadUpdate")] + [Bindable(event="loadProgress")] + /** + * The bytesLoaded property lets you access the total amount of bytes already loaded if this VideoStream + * is playing a progressive download (http) video file. + */ + public function get bytesLoaded():uint + { + return _bytesLoaded; + } + + /** + * @private + */ + protected var _debug:Boolean; + /** + * The debugMode property provides a visual cue to some errors during development with VideoStream + * by making the errors visible in the bitmapData that this VideoStream creates. + */ + public function set debugMode(val:Boolean):void + { + _debug=val; + } + + public function get debugMode():Boolean + { + return _debug; + } + + //CONTROL + [Bindable(event="playStatusUpdate")] + /** + * The playheadTime property represents the current time code in seconds for the playhead in this VideoStream + */ + public function get playheadTime():Number + { + if (_ns) + { + var tmp:Number=_position - (_audioDelay ? _audioDelay : 0) + if (tmp*0!=0) //isNaN + { + tmp=0; + } + if (tmp) tmp = uint(tmp*100)*0.01; + + return (tmp < 0 ? 0 : tmp); + } + else + { + return 0; + } + } + + public function set playheadTime(val:Number):void + { + if (val*0!=0 || val < 0) //isNan or negative + { + val=0; + } + if (_ns) + { + var limit:Number=seekableTo; + if (limitseekableTo property represents the calculated (known) or estimated current maximum seekable point within the stream. + * This differs between http progressive videos and streaming videos. + * for streaming video it covers the duration of the video, for progressive download video + * it includes the full video if the video has completely downloaded otherwise it is within + * the proportion of the video that has downloaded thus far (which may be an estimate in terms + * of the actual playing time this represents) + * + * */ + public function get seekableTo():Number + { + if (!_isReady) + { + return _maxCurrentSeekablePosition=0; + } + //if its a stream return the _duration value extracted from metadata + if (_rtmp) + { + if (_metaData) return _maxCurrentSeekablePosition=_duration; + //what to do here? + return _ns.time; + } + //for http progressive download its 'less certain' + if (!_rtmp) + { + if (_httpCached) + { + //if we believe its fully loaded/cached, then its easy, so long as we have information about the duration + if (_metaData.lastkeyframetimestamp) + return _maxCurrentSeekablePosition=_metaData.lastkeyframetimestamp; + //a couple of common metadata encodings of seek points: + if (_metaData.keyframes && _metaData.keyframes.times) + { + if (_metaData.keyframes.times is Array) + { + var keyframeTimes:Array=_metaData.keyframes.times as Array; + return _maxCurrentSeekablePosition=Number(keyframeTimes[keyframeTimes.length - 1]); + } + } + if (_metaData.seekpoints && _metaData.seekpoints is Array) + { + _maxCurrentSeekablePosition=Number(_metaData.seekpoints[ _metaData.seekpoints.length-1].time); + + if (_maxCurrentSeekablePosition*0==0) + { + return _maxCurrentSeekablePosition; + } //otherwise its NaN + } + if (_metaData.canSeekToEnd && _duration) + return _maxCurrentSeekablePosition=_duration; + + + if (_duration) return _maxCurrentSeekablePosition=_duration; + //what if there was no helpful metadata? + //catchall: seek no further that current playhead as we don't know the duration + _maxCurrentSeekablePosition=_ns.time; + + //TODO: check if/how often this occurs. + } + else + { + var temp:Number=_maxCurrentSeekablePosition; + if (_bufferStatus == BUFFER_FULL || _bufferStatus == BUFFER_FLUSHING) + { + //allow for current position plus buffer or below as a certainty + temp= _maxCurrentSeekablePosition + Math.min(_ns.bufferLength, _ns.bufferTime); + //isNaN ? + if (temp*0!=0) temp= _maxCurrentSeekablePosition; + } + + //allow for a proportion of the preloaded content as well : guesstimates + if (_bytesTotal>0){ + var approxPos:Number; + //if we have the metadata, use it to help the calculation + if (_metaData.framerate && _metaData.audiodatarate && _metaData.videodatarate && _bytesLoaded>0){ + var rate:Number=(Number(_metaData.audiodatarate)+Number(_metaData.videodatarate))*Number(_metaData.framerate); + //drop back bytesLoaded to cover an arbitrary or informed allocation for the size of metadata + var nonmedia:Number = (_metaData.audiosize && _metaData.datasize && _metaData.videosize)? Number(_metaData.datasize-(_metaData.audiosize+_metaData.videosize)):10000; + + approxPos = (_bytesLoaded-nonmedia)/rate; + //drop back by half a sec to be a little more conservative (the encoding data rate can be variable so this approx could be out by quite a bit) + approxPos-=0.5; + if (tempisPlaying property returns a Boolean true if the VideoStream is currently playing. + * */ + public function get isPlaying():Boolean + { + //based on intent + return (_playStatus == PLAY_PLAYING || _playStatus == PLAY_STATUS_COMPLETE); + } + + [Bindable(event="playStatusChanged")] + /** + * The isPaused property returns a Boolean true if the VideoStream is currently paused or stopped. + * */ + public function get isPaused():Boolean + { + //based on intent + return (_playStatus == PLAY_PAUSED || _playStatus == PLAY_STOPPED); + } + + [Bindable(event="bufferStatusChange")] + /** + * The bufferStatus property returns a string value indicating the current buffer status in the VideoStream. + * possible values are bufferBuffering,bufferFull,bufferDiminished,bufferEmpty, and bufferFlushing + * */ + public function get bufferStatus():String + { + return _bufferStatus; + } + + + [Bindable(event="bufferStatusUpdate")] + public function get bufferIndicator():String + { + //asume green + var ret:String="green"; + var ratio:Number; + switch (_bufferStatus) + { + case BUFFER_FULL: + case BUFFER_FLUSHING: + //ret= "green" already + break; + case BUFFER_EMPTY: + ret="red"; + break; + case BUFFER_BUFFERING: + ratio=_bufferLength / _bufferTime; + //the buffer is not 'healthy' until its full + if (ratio < 1) + ret="orange"; + if (ratio < .2) + ret="red"; + break; + case BUFFER_DIMINISHED: + //buffering or falling + if (!_bufferTime) + { + if (_bufferLength) + return 'green'; + return 'red'; + } + ret="green"; + ratio=_bufferLength / _bufferTime; + //'health' is considered higher if we have already had a full buffer + if (ratio < .75) + ret="orange"; + if (ratio < .2) + ret="red"; + break; + default: + trace('err: unhandled buffer state') + break; + } + return ret; + + } + + + + [Bindable(event="bufferTimeChanged")] + public function get bufferTime():Number + { + return Number(_bufferTime.toFixed(2)); + } + + + public function set bufferTime(val:Number):void + { + if (val >= 0 && _bufferTime != val) + { + _bufferTime=val; + if (_ns) + { + _ns.bufferTime=val; + _bufferTimeChange=true; + } + //TODO: (check) consider maybe should only dispatch this if the ns exists? + dispatchUpdateEvent(BUFFER_TIME_CHANGED); + } + } + + [Bindable(event="bufferStatusUpdate")] + public function get bufferLength():Number + { + if(!_ns) return 0; + _bufferLength=_ns.bufferLength + return _bufferLength; + } + + [Bindable(event="bufferStatusUpdate")] + public function get bufferRatio():Number{ + if (!_bufferTime) return 1; + return _bufferLength/_bufferTime; + } + + // ************ ALPHA SUPPORT FROM EXTERNAL REQUESTERS********************* + + protected static function processDisposalQueue(e:TimerEvent):void{ + if (_disposalQueue && _disposalQueue.length){ + BitmapData(_disposalQueue.shift()).dispose(); + } else { + _disposalTimer.stop(); + _disposalTimer.removeEventListener(TimerEvent.TIMER,processDisposalQueue); + _disposalTimer=null; + } + } + + protected static var _disposalQueue:Array; + protected static var _disposalTimer:Timer; + + /** + * utility support functions for updating additional bitmapdata copies. Intended for use with alpha target copies. + * This VideoStream generates bitmapData without alpha channel by default for performance reasons + * so on2vp6 alpha channel video is currently not supported - will review this based on demand and/or provide a subclass for on2vp6 content with alpha. + * The original bitmapData can be used as a source for multiple alpha versions, but the bitmapdata frameupdating will become very cpu intensive for + * multiple simultaneous alpha versions of higher resolution video content or multiple VideoStreams with alpha requests etc. Use with a modicum of common sense. + * */ + public function registerCopyTarget(target:BitmapData,colorTransform:ColorTransform=null):void{ + if (!_copyTargets) _copyTargets=new Dictionary(true); + if (_bitmapData && target.rect.equals(_bitmapData.rect)){ + _copyTargets[target]=colorTransform; + _copyTargetCount++ + } //otherwise do nothing, consider throwing an error here + } + + protected function clearAllCopyTargets():void{ + if (!_copyTargets) return; + for (var target:Object in _copyTargets){ + deregisterCopyTarget(BitmapData(target),true); + } + } + + public function deregisterCopyTarget(target:BitmapData,destroyTarget:Boolean=false):void{ + + if (_copyTargets && _copyTargets[target]!==undefined){ + if (_copyTargets[target] is ColorTransform) { + if (!_disposalQueue) _disposalQueue=[]; + if (destroyTarget) _disposalQueue.push(target) + } else { + if (destroyTarget){ + if (!_disposalQueue) _disposalQueue=[]; + _disposalQueue.push(target); + } + } + + _copyTargetCount--; + _copyTargets[target]=null; + delete _copyTargets[target]; + } else{//destroy target if requested, simple utility only + if (destroyTarget){ + if (!_disposalQueue) _disposalQueue=[]; + _disposalQueue.push(target); + } + } + if (!_disposalTimer && _disposalQueue.length) { + _disposalTimer =new Timer(DISPOSAL_DEFERRAL,0); + _disposalTimer.addEventListener(TimerEvent.TIMER,processDisposalQueue,false,0,true); + _disposalTimer.start(); + } + } + /** + * The letterBoxContent property, if not null, provides a Rectangle instance that defines the VideoStream's letterboxed content. + * This property is read-only. + * + * @see flash.geom.Rectangle + */ + public function requestPausedBitmapdataUpdate():void{ + if (_isPaused && _bitmapAccessible){ + videoFrameUpdate(new Event("explicitRequest",false,false),true); + } + } + +//LETTERBOX DETECTION SUPPORT + protected var _letterBoxContent:Rectangle; + protected var _letterBoxHelper:Rectangle; + protected var _letterBoxDetected:Boolean; + protected var _letterBoxChecked:Boolean; + protected var _checkLetterBoxData:BitmapData; + protected var _checkLBLimit:uint =100; + + //letterbox detection + [Bindable(event="letterBoxUpdate")] + /** + * The letterBoxContent property, if not null, provides a Rectangle instance that defines the VideoStream's letterboxed content. + * This property is read-only. + * + * @see flash.geom.Rectangle + */ + public function get letterBoxContent():Rectangle + { + return _letterBoxContent; + } + /** + * @private + * */ + protected var _detectLetterBox:Boolean; + /** + * The detectLetterBox property lets you specify whether this VideoStream has letterbox detection enabled.
    + * Defaults to false.
    + * This is an experimental feature that is still under development.
    + * When set to true, in the current (beta) implementation, the stream will perform a very simple analysis during the first + * 100 rendered frames after it has been enabled. The detection algorithm is likely to change in the future and is likely to include sporadic + * monitoring throughout the stream rather than just a simple test during the first 100 frames after being enabled.
    + * The current (beta) algorithm only looks for black letterboxing of content within a small tolerance range. + * Results may be inaccurate or undesirable for some content. + */ + public function get detectLetterBox():Boolean{ + return _detectLetterBox; + } + + + [Inspectable(category="General", enumeration="true,false", defaultValue="false")] + public function set detectLetterBox(val:Boolean):void{ + var notify:Boolean; + if (val!=_detectLetterBox){ + if (_letterBoxDetected) notify=true; + _letterBoxDetected=false; + _letterBoxContent=null; + _letterBoxHelper=null; + _letterBoxChecked=false; + _checkLBLimit=100; + if (_detectLetterBox) { + //setting to false + if (_checkLetterBoxData) deregisterCopyTarget(_checkLetterBoxData,true); + } + _checkLetterBoxData=null; + _detectLetterBox=val; + if (notify) { + dispatchUpdateEvent(LETTERBOX_UPDATE); + initChange('content',_bitmapData,_bitmapData,this); + } + } + } + + + + [Bindable(event="letterBoxUpdate")] + /** + * The isLetterBoxed property lets you access the current frames per second decoding from this VideoStream. + * This property is read-only. + */ + public function get isLetterBoxed():Boolean{ + return _letterBoxDetected; + } + + [Bindable(event="playStatusFPSupdate")] + /** + * The currentFPS property lets you access the current frames per second decoding from this VideoStream. + * This is independent of the swf frame rate. + */ + public function get currentFPS():Number{ + return _currentFPS; + } + + /** + * @private + * */ + private var _quality:String="high"; + + /** + * The quality property lets you alter the rendering quality for this VideoStream at the basic content level.
    + * Defaults to high.
    + * This affects the quality of the rendered bitmapData, and may be reduced to lower + * CPU load slightly if rendering quality is not the highest priority. + * @default high + * */ + public function get quality():String{ + return _quality; + } + [Inspectable(category="General", enumeration="high,med,low", defaultValue="high")] + public function set quality(val:String):void{ + val=val.toLowerCase(); + if (_quality!=val){ + if (val=="low" || val=="high"||val=="med"){ + _quality=val; + _vid.smoothing=(val!="low"); + } + } + } + + /** + * @private + * */ + private var _contentType:String; + [Bindable(event="itemInitializing")] + /** + * The contentType property lets you access a string value that represents the type of the content. + * Valid values are none,non-streaming,streaming (VideoFill.NONE,VideoFill.STREAMING,VideoFill.NON_STREAMING) + * This property is read-only. + * */ + public function get contentType():String{ + if (!_contentType || !_contentType.length) return VideoStream.NONE; + return _contentType; + } + + //-----ERROR SUPPORT + private var _errorStatus:String=ERROR_STATUS_NONE; + private var _errorDetail:String; + [Bindable(event="errorUpdate")] + /** + * The errorStatus property lets you access a string value that represents the general type of error that has occured, if one exists. + * Valid values are errorNone,errorConnectionAccess,errorDataAccess,errorConnectionAccess (VideoFill.ERROR_STATUS_NONE,VideoFill.ERROR_STATUS_DATA_ACCESS,VideoFill.ERROR_STATUS_CONNECTION_ACCESS) + * This property is read-only. + * */ + public function get errorStatus():String{ + return _errorStatus; + } + [Bindable(event="errorUpdate")] + /** + * The errorStatus property lets you access a string value that represents a more descriptive explanation of an error that has occured, if one exists. + * If no error has occurred, the value remains as "none". + * This property is read-only. + * */ + public function get errorDetail():String{ + return _errorDetail; + } + + + protected function updateErrorStatus(status:String,detail:String,sendEvent:Boolean=true):void{ + _errorStatus=status; + _errorDetail=detail; + if (sendEvent) dispatchStatusEvent(ERROR_STATUS_UPDATE); + } + + } +} + + +//////////////////////////////////////////////////////////////////////////////////////////// +//Local helper class for NetConnection management + +import flash.events.IOErrorEvent; +import flash.events.NetStatusEvent; +import flash.events.TimerEvent; +import flash.net.NetConnection; +import flash.net.NetStream; +import flash.utils.Timer; +import flash.utils.getTimer; +import com.degrafa.utilities.external.LoadingLocation; + +final class ConnectionItem{ + + private static var closureQueue:Array=[]; + private static const idleClosureDelay:int=30000; //30 secs + private static const closeTimer:Timer = new Timer(idleClosureDelay+1,0) + private static function nullFunc(...args):*{} + private static const nullClient:Object={onBWDone:nullFunc,onFCSubscribe:nullFunc, onCuePoint: nullFunc, onMetaData: nullFunc, onTextData: nullFunc, onImageData: nullFunc, onPlayStatus: nullFunc, onXMPData: nullFunc}; + + public static const CONNECTING:String="connecting"; + public static const IN_USE:String="in use"; + public static const NEW_ITEM:String="new item"; + public static const REDUNDANT:String="redundant"; + public static const CLOSED:String="closed"; + public static const ERROR:String="error"; + + private var _connectionCount:int=1; + private var _nc:NetConnection; + private var _connStatus:String = NEW_ITEM; + + private var deathTime:int; + + //local copies of LoadingLocation props + + private var uri:String; + private var policyFile:String; + private var loadingLocation:LoadingLocation; + + /** + * @private + * utility method to ignore any client callbacks on a NetStream object + */ + public static function decommissionNetStream(ns:NetStream):void{ + ns.client=nullClient; + } + + + /** + * @private + * Utility method for closing NetConnections for un-used ConnectionItems after a defined period. + * */ + private static function processQueue(e:TimerEvent):void{ + var t:int=getTimer(); + for each(var connItem:ConnectionItem in closureQueue){ + if (connItem.deathTime __knots-1 ) + return -1; + + var t:Number = 0; + + if( _k == 0 ) + t = 0; + else if( _k == (__knots-1) ) + t = 1; + else + t = Number(_k)/Number((__knots-1)); + + return t; + } + + // compute polynomical coefficients + protected function __computeCoef():void + { + // fill out endpoints based on user selection + if( __tangent == AUTO ) + __computeEndpoints(); + else + { + __x[__knots+1] = __xHold; + __y[__knots+1] = __yHold; + } + + // loop over segments + for( var i:uint=1; i<__knots; ++i ) + { + var c:Cubic = __coef[i]; + if( c == null ) + c = new Cubic(); + else + c.reset(); + + c.addCoef( 2.0*__x[i], 2.0*__y[i] ); + + c.addCoef( __x[i+1] - __x[i-1], __y[i+1] - __y[i-1] ); + + c.addCoef( 2.0*__x[i-1] - 5.0*__x[i] + 4.0*__x[i+1] - __x[i+2], 2.0*__y[i-1] - 5.0*__y[i] + 4.0*__y[i+1] - __y[i+2] ); + + c.addCoef( -__x[i-1] + 3.0*__x[i] - 3.0*__x[i+1] + __x[i+2], -__y[i-1] + 3.0*__y[i] - 3.0*__y[i+1] + __y[i+2] ); + + __coef[i] = c; + } + + __invalidate = false; + __parameterize(); + } + + // parameterize spline - this should be overriden by any extending class to support arc-length, chord-length, or other specific parameterization + protected function __parameterize():void + { + + } + + // base class only supports uniform parameterization + protected function __setParam(_t:Number):void + { + var t:Number = (_t<0) ? 0 : _t; + t = (t>1) ? 1 : t; + + if( t != __t ) + { + __t = t; + __segment(); + } + } + + // compute current segment and local parameter value + protected function __segment():void + { + // the trivial case -- one segment + if( __knots == 2 ) + { + __index = 1; + __localParam = __t; + } + else + { + if( __t == 0 ) + { + __index = 1; + __localParam = 0; + } + else if( __t == 1.0 ) + { + __index = __knots-1; + __localParam = 1.0; + } + else + { + var N1:Number = __knots-1; + var N1t:Number = N1*__t; + var f:Number = Math.floor(N1t); + __index = Math.min(f+1, N1); + __localParam = N1t - f; + } + } + } + + // compute endpoints at extremes of knot sequence - simple reflection about endpoints (compensating for closed spline) + protected function __computeEndpoints():void + { + if( !__isClosed ) + { + // simple reflection + __x[0] = 2.0*__x[1] - __x[2]; + __y[0] = 2.0*__y[1] - __y[2]; + + __x[__knots+1] = 2.0*__x[__knots] - __x[__knots-1]; + __y[__knots+1] = 2.0*__y[__knots] - __y[__knots-1]; + } + } + + protected function __closedSplineEndpoints():void + { + var x1:Number = __x[1]; + var y1:Number = __y[1]; + var dX1:Number = __x[2] - x1; + var dY1:Number = __y[2] - y1; + var dX2:Number = __x[__knots-1] - x1; + var dY2:Number = __y[__knots-1] - y1; + var d1:Number = Math.sqrt(dX1*dX1 + dY1*dY1); + var d2:Number = Math.sqrt(dX2*dX2 + dY2*dY2); + dX1 /= d1; + dY1 /= d1; + dX2 /= d2; + dY2 /= d2; + + __x[0] = x1 + d1*dX2; + __y[0] = y1 + d1*dY2; + __x[__knots+1] = x1 + d2*dX1; + __y[__knots+1] = y1 + d2*dY1; + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/utilities/math/CatmullRomUtility.as b/Degrafa/com/degrafa/utilities/math/CatmullRomUtility.as new file mode 100644 index 0000000..76277ac --- /dev/null +++ b/Degrafa/com/degrafa/utilities/math/CatmullRomUtility.as @@ -0,0 +1,253 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2009 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Programmed by: Jim Armstrong +// +// This software is derived from source containing the following copyright notice +// +// copyright (c) 2006-2007, Jim Armstrong. All Rights Reserved. +// +// This software program is supplied 'as is' without any warranty, express, implied, +// or otherwise, including without limitation all warranties of merchantability or fitness +// for a particular purpose. Jim Armstrong shall not be liable for any special incidental, or +// consequential damages, including, without limitation, lost revenues, lost profits, or +// loss of prospective economic advantage, resulting from the use or misuse of this software +// program. +// +// Ported to Degrafa with permission of author +// +// CatmullRomUtility.as - Generate arc-length parameterized Catmull-Rom spline that interpolates a set of data points, suitable for use +// as a utility in keyframing, interpolation, and path animation, but not intended for general drawing. +// +// Reference: www.algorithmist.net/arclen1.html +// +// Programmed by Jim Armstrong +// +// Note: Class defaults to auto-tangent, uniform parameterization. This class is meant to be used as a +// math utility for interpolation or path animation, not for drawing. This class is structured more for +// clarity than performance. +// + +package com.degrafa.utilities.math +{ + public class CatmullRomUtility extends CatmullRom + { + private static const ONE_THIRD:Number = 1.0/3.0; + private static const TWO_THIRDS:Number = 2.0/3.0; + + // Arc-length computation and parameterization + protected var __param:String; // parameterization method + protected var __integral:Gauss; // Gauss-Legendre integration class + protected var __arcLength:Number; // current arc length + protected var __spline:CubicSpline; // interpolate arc-length vs. t + +/** +* CatmullRomUtility() - Construct a new Catmull-Rom spline +* +* @return Nothing +* +* @since 1.0 +* +*/ + public function CatmullRomUtility() + { + super(); + + __arcLength = -1; + __param = ARC_LENGTH; + + __spline = new CubicSpline(); + __integral = new Gauss(); + } + + protected function __integrand(_t:Number):Number + { + var x:Number = __coef[__index].getXPrime(_t); + var y:Number = __coef[__index].getYPrime(_t); + + return Math.sqrt( x*x + y*y ); + } + + override public function reset():void + { + super.reset(); + __arcLength = -1; + } + +/** +* arcLength Estimate arc-length of the entire curve by numerical integration +* +* @return Number: Estimate of total arc length of the spline over [0,1] +* +* @since 1.0 +* +*/ + public function arcLength():Number + { + if ( __arcLength != -1 ) + return __arcLength; + + // compute the length of each segment and sum + var len:Number = 0; + if( __knots < 2 ) + return len; + + if( __invalidate ) + __computeCoef(); + + for( var i:uint=1; i<__knots; ++i ) + { + __index = i; + len += 0.5*__integral.eval( __integrand, 0, 1, 5 ); + } + + __arcLength = len; + return len; + } + +/** +* arcLengthAt Return arc-length of curve segment on [0,_t]. +* +* @param _t:Number - parameter value to describe partial curve whose arc-length is desired +* +* @return Number: Estimate of arc length of curve segment from t=0 to t=_t. +* +* @since 1.0 +* +*/ + public function arcLengthAt(_t:Number):Number + { + // compute the length of each segment and sum + var len:Number = 0; + if( __knots < 2 || _t == 0 ) + return len; + + if( __invalidate ) + __computeCoef(); + + var t:Number = (_t<0) ? 0 : _t; + t = (t>1) ? 1 : t; + + // determine which segment corresponds to the input value and the local parameter for that segment + var N1:Number = __knots-1; + var N1t:Number = N1*t; + var f:Number = Math.floor(N1t); + var maxSeg:Number = Math.min(f+1, N1); + var param:Number = N1t - f; + + // compute full curve length up to, but not including final segment + for( var i:uint=1; i 0 ) + __spline.deleteAllKnots(); + + // x-coordinate of spline knot is normalized arc-length, y-coordinate is t-value for uniform parameterization + __spline.addControlPoint(0.0, 0.0); + var prevT:Number = 0; + var knotsInv:Number = 1.0/Number(__knots-1); + + for( var i:uint=1; i<__knots-1; i++ ) + { + // get t-value at this knot for uniform parameterization + var t:Number = Number(i)*knotsInv; + var t1:Number = prevT + ONE_THIRD*(t-prevT); + var l:Number = arcLengthAt(t1)*normalize; + __spline.addControlPoint(l,t1); + + var t2:Number = prevT + TWO_THIRDS*(t-prevT); + l = arcLengthAt(t2)*normalize; + __spline.addControlPoint(l,t2); + + l = arcLengthAt(t)*normalize; + __spline.addControlPoint(l,t); + + prevT = t; + } + + t1 = prevT + ONE_THIRD*(1.0-prevT); + l = arcLengthAt(t1)*normalize; + __spline.addControlPoint(l,t1); + + t2 = prevT + TWO_THIRDS*(1.0-prevT); + l = arcLengthAt(t2)*normalize; + __spline.addControlPoint(l,t2); + + // last knot, t=1, normalized arc-length = 1 + __spline.addControlPoint(1.0, 1.0); + } + } + + // support optional arc-length parameterization + override protected function __setParam(_t:Number):void + { + var t:Number = (_t<0) ? 0 : _t; + t = (t>1) ? 1 : t; + + // if arc-length parameterization, approximate L^-1(s) + if( __param == ARC_LENGTH ) + { + if( t != __s ) + { + __t = __spline.eval(t); + __s = t; + __segment(); + } + } + else + { + if( t != __t ) + { + __t = t; + __segment(); + } + } + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/utilities/math/Consts.as b/Degrafa/com/degrafa/utilities/math/Consts.as new file mode 100644 index 0000000..3a4171f --- /dev/null +++ b/Degrafa/com/degrafa/utilities/math/Consts.as @@ -0,0 +1,75 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Programmed by: Jim Armstrong, Singularity (www.algorithmist.net) and +// ported by the Degrafa team. +//////////////////////////////////////////////////////////////////////////////// +package com.degrafa.utilities.math{ + + /** + * Helper class for advanced math used in various spline Geometry. + **/ + public class Consts{ + public static const ZERO_TOL:Number = 0.0001; // zero tolerance + + public static const PI_2:Number = 0.5*Math.PI; + public static const PI_4:Number = 0.25*Math.PI; + public static const PI_8:Number = 0.125*Math.PI; + public static const PI_16:Number = 0.0625*Math.PI; + public static const TWO_PI:Number = 2.0*Math.PI; + public static const THREE_PI_2:Number = 1.5*Math.PI; + public static const ONE_THIRD:Number = 1.0/3.0; + public static const TWO_THIRDS:Number = ONE_THIRD + ONE_THIRD; + public static const ONE_SIXTH:Number = 1.0/6.0; + public static const DEG_TO_RAD:Number = Math.PI/180; + public static const RAD_TO_DEG:Number = 180/Math.PI; + + public static const CIRCLE_ALPHA:Number = 4*(Math.sqrt(2)-1)/3.0; + + public static const ON:Boolean = true; + public static const OFF:Boolean = false; + + public static const AUTO:String = "A"; + public static const DUPLICATE:String = "D"; + public static const EXPLICIT:String = "E"; + public static const CHORD_LENGTH:String = "C"; + public static const ARC_LENGTH:String = "AL"; + public static const UNIFORM:String = "U"; + public static const FIRST:String = "F"; + public static const LAST:String = "L"; + public static const POLAR:String = "P"; + + // Machine-dependent + private var _epsilon:Number; + + public function Consts(){ + // Machine epsilon ala Eispack + var _fourThirds:Number = 4.0/3.0; + var _third:Number = _fourThirds - 1.0; + var _one:Number = _third + _third + _third; + _epsilon = Math.abs(1.0 - _one); + } + + public function get EPSILON():Number { + return _epsilon; + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/utilities/math/Cubic.as b/Degrafa/com/degrafa/utilities/math/Cubic.as new file mode 100644 index 0000000..25e9a4e --- /dev/null +++ b/Degrafa/com/degrafa/utilities/math/Cubic.as @@ -0,0 +1,142 @@ +// +// Cubic.as - Simple holder class for cubic polynomial coefficients. +// +// Programmed by Jim Armstrong +// +// + +package com.degrafa.utilities.math +{ + public class Cubic + { + // properties + private var __c0X:Number; + private var __c1X:Number; + private var __c2X:Number; + private var __c3X:Number; + private var __c0Y:Number; + private var __c1Y:Number; + private var __c2Y:Number; + private var __c3Y:Number; + private var __count:uint; + + public function Cubic() + { + reset(); + } + + public function reset():void + { + __c0X = 0; + __c1X = 0; + __c2X = 0; + __c3X = 0; + __c0Y = 0; + __c1Y = 0; + __c2Y = 0; + __c3Y = 0; + + __count = 0; + } + + public function addCoef( _cX:Number, _cY:Number ):void + { + if( __count < 4 && !isNaN(_cX) && !isNaN(_cY) ) + { + switch(__count) + { + case 0: + __c0X = _cX; + __c0Y = _cY; + break; + + case 1: + __c1X = _cX; + __c1Y = _cY; + break; + + case 2: + __c2X = _cX; + __c2Y = _cY; + break; + + case 3: + __c3X = _cX; + __c3Y = _cY; + break; + } + __count++; + } + } + + public function getCoef( _indx:uint ):Object + { + if( _indx > -1 && _indx < 4 ) + { + var coef:Object = new Object(); + switch(_indx) + { + case 0: + coef.X = __c0X; + coef.Y = __c0Y; + break; + + case 1: + coef.X = __c1X; + coef.Y = __c1Y; + break; + + case 2: + coef.X = __c2X; + coef.Y = __c2Y; + break; + + case 3: + coef.X = __c3X; + coef.Y = __c3Y; + break; + } + } + return coef; + } + + + public function getX(_t:Number):Number + { + return (__c0X + _t*(__c1X + _t*(__c2X + _t*(__c3X)))); + } + + public function getY(_t:Number):Number + { + return (__c0Y + _t*(__c1Y + _t*(__c2Y + _t*(__c3Y)))); + } + + public function getXPrime(_t:Number):Number + { + return (__c1X + _t*(2.0*__c2X + _t*(3.0*__c3X))); + } + + public function getYPrime(_t:Number):Number + { + return (__c1Y + _t*(2.0*__c2Y + _t*(3.0*__c3Y))); + } + + public function getDeriv(_t:Number):Number + { + // use chain rule - tbd compensate for numerical issues + var dy:Number = getYPrime(_t); + var dx:Number = getXPrime(_t); + return dy/dx; + } + + public function toString():String + { + var myStr:String = "coef[0] " + __c0X + "," + __c0Y; + myStr += " coef[1] " + __c1X + "," + __c1Y; + myStr += " coef[2] " + __c2X + "," + __c2Y; + myStr += " coef[3] " + __c3X + "," + __c3Y; + + return myStr; + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/utilities/math/CubicSpline.as b/Degrafa/com/degrafa/utilities/math/CubicSpline.as new file mode 100644 index 0000000..6604919 --- /dev/null +++ b/Degrafa/com/degrafa/utilities/math/CubicSpline.as @@ -0,0 +1,358 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008 The Degrafa Team : http://www.Degrafa.com/team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Programmed by: Jim Armstrong +// +// This software is derived from source containing the following copyright notice +// +// copyright (c) 2006-2007, Jim Armstrong. All Rights Reserved. +// +// This software program is supplied 'as is' without any warranty, express, implied, +// or otherwise, including without limitation all warranties of merchantability or fitness +// for a particular purpose. Jim Armstrong shall not be liable for any special incidental, or +// consequential damages, including, without limitation, lost revenues, lost profits, or +// loss of prospective economic advantage, resulting from the use or misuse of this software +// program. +// +// + +package com.degrafa.utilities.math +{ + public class CubicSpline + { + protected static const ONE_SIXTH:Number = 1.0/6.0; + + // read associated white paper for details on these variables + protected var __t:Array; + protected var __y:Array; + protected var __u:Array; + protected var __v:Array; + protected var __h:Array; + protected var __b:Array; + protected var __z:Array; + + protected var __hInv:Array; // precomputed h^-1 values + protected var __delta:Number; // current x-t(i) + protected var __knots:Number; // current knot count + + protected var __invalidate:Boolean; // true if current coefficients are invalid + +/** +* CubicSpline() Construct a new Cubic Spline instance. +* +* @return nothing. Note: Although some attempt has been made to optimize operation count and complexity, this code is written more for clarity than Actionscript performance. +*

    Important notes:

    1) Intervals must be non-overlapping. Insertion preserves this constraint.
    2) Knot insertion/deletion causes a complete regeneration of +* coefficients. A future (faster) version will do this adaptively.

    +* Reference: http://www.algorithmist.net/spline.html. +* +* @since 1.0 +* +*/ + public function CubicSpline() + { + __t = new Array(); + __y = new Array(); + __u = new Array(); + __v = new Array(); + __h = new Array(); + __b = new Array(); + __z = new Array(); + __hInv = new Array(); + + __invalidate = true; + __delta = 0.0; + __knots = 0; + } + + // return knot count + public function get knotCount():Number { return __knots; } + + // return array of Objects with X and Y properties containing knot coordinates + public function get knots():Array + { + var knotArr:Array = new Array(); + for( var i:uint=0; i<__knots; ++i ) + knotArr.push({X:__t[i], Y:__y[i]}); + + return knotArr; + } + +/** +* addControlPoint Add/Insert a knot in a manner that maintains non-overlapping intervals. This method rearranges knot order, if necessary, to +* maintain non-overlapping intervals. +* +* @param _xKNot:Number - x-coordinate of knot to add +* @param _yKnot:Number - y-coordinate of knot to add +* +* @return Nothing +* +* @since 1.0 +* +*/ + public function addControlPoint(_xKnot:Number, _yKnot:Number):void + { + if( !isNaN(_xKnot) && !isNaN(_yKnot) ) + { + __invalidate = true; + + if( __t.length == 0 ) + { + __t.push(_xKnot); + __y.push(_yKnot); + __knots++; + } + else + { + if ( _xKnot > __t[__knots-1] ) + { + __t.push(_xKnot); + __y.push(_yKnot); + __knots++; + } + else if( _xKnot < __t[0] ) + __insert(_xKnot, _yKnot, 0); + else + { + if( __knots > 1 ) + { + for( var i:uint=0; i<__knots-1; ++i ) + { + if( _xKnot > __t[i] && _xKnot < __t[i+1] ) + __insert(_xKnot, _yKnot, i+1 ); + } + } + } + } + } + } + + // insert knot at index + protected function __insert(_xKnot:Number, _yKnot:Number, _indx:Number):void + { + for( var i:uint=__knots-1; i>=_indx; i-- ) + { + __t[i+1] = __t[i]; + __y[i+1] = __y[i]; + } + + __t[_indx] = _xKnot; + __y[_indx] = _yKnot; + __knots++; + } + + // remove knot at index + protected function __remove(_indx:Number):void + { + for( var i:uint=_indx; i<__knots; ++i) + { + __t[i] = __t[i+1]; + __y[i] = __y[i+1]; + } + + __t.pop(); + __y.pop(); + __knots--; + } + +/** +* removePointAt Delete knot at the specified index +* +* @param _indx:uint - index of knot to delete +* +* @return Nothing +* +* @since 1.0 +* +*/ + public function removePointAt(_indx:uint):void + { + if( _indx < 0 || _indx >= __knots ) + { + return; + } + + __remove(_indx); + __invalidate = true; + } + +/** +* moveControlPoint Move knot at the specified index within its interval +* +* @param _indx:uint - index of knot to replace +* @param _xKnot:Number - new x-coordinate +* @param _yKnot:Number - new y-coordinate +* +* @return Nothing - There is no testing to see if the move causes any intervals to overlap +* +* @since 1.0 +* +*/ + public function moveControlPoint(_indx:uint, _xKnot:Number, _yKnot:Number):void + { + if( _indx < 0 || _indx >= __knots ) + { + return; + } + + if( isNaN(_xKnot) || isNaN(_yKnot) ) + { + return; + } + + __t[_indx] = _xKnot; + __y[_indx] = _yKnot; + __invalidate = true; + } + +/** +* deleteAllKnots Delete all knots +* +* @return Nothing +* +* @since 1.0 +* +*/ + public function deleteAllKnots():void + { + __t.splice(0); + __y.splice(0); + + __knots = 0; + __invalidate = true; + } + +/** +* removeKnotAtX Delete knot at a given x-coordinate +* +* @param _xKnot:Number - x-coordinate of knot to delete +* +* @return Nothing +* +* @since 1.0 +* +*/ + public function removeKnotAtX(_xKnot:Number):void + { + if( isNaN(_xKnot) ) + { + return; + } + + var i:int = -1; + for( var j:int=0; j<__knots; ++j ) + { + if( __t[j] == _xKnot ) + { + i = j; + break; + } + } + + if( i == -1 ) + { + return; + } + else + { + __remove(i); + __invalidate = true; + } + } + +/** +* eval Evaluate spline at a given x-coordinate +* +* @param _xKnot:Number - x-coordinate to evaluate spline +* +* @return Number: - NaN if there are no knots +* - y[0] if there is only one knot +* - Spline value at the input x-coordinate, if there are two or more knots +* +* @since 1.0 +* +*/ + public function eval(_xKnot:Number):Number + { + if( __knots == 0 ) + return NaN; + else if( __knots == 1 ) + return __y[0]; + else + { + if( __invalidate ) + __computeZ(); + + // determine interval + var i:uint = 0; + __delta = _xKnot - __t[0]; + for( var j:uint=__knots-2; j>=0; j-- ) + { + if( _xKnot >= __t[j] ) + { + __delta = _xKnot - __t[j]; + i = j; + break; + } + } + + var b:Number = (__y[i+1] - __y[i])*__hInv[i] - __h[i]*(__z[i+1] + 2.0*__z[i])*ONE_SIXTH; + var q:Number = 0.5*__z[i] + __delta*(__z[i+1]-__z[i])*ONE_SIXTH*__hInv[i]; + var r:Number = b + __delta*q; + var s:Number = __y[i] + __delta*r; + + return s; + } + } + + // compute z[i] based on current knots + protected function __computeZ():void + { + // reference the white paper for details on this code + + // pre-generate h^-1 since the same quantity could be repeatedly calculated in eval() + for( var i:uint=0; i<__knots-1; ++i ) + { + __h[i] = __t[i+1] - __t[i]; + __hInv[i] = 1.0/__h[i]; + __b[i] = (__y[i+1] - __y[i])*__hInv[i]; + } + + // recurrence relations for u(i) and v(i) -- tridiagonal solver + __u[1] = 2.0*(__h[0]+__h[1]); + __v[1] = 6.0*(__b[1]-__b[0]); + + for( i=2; i<__knots-1; ++i ) + { + __u[i] = 2.0*(__h[i]+__h[i-1]) - (__h[i-1]*__h[i-1])/__u[i-1]; + __v[i] = 6.0*(__b[i]-__b[i-1]) - (__h[i-1]*__v[i-1])/__u[i-1]; + } + + // compute z(i) + __z[__knots-1] = 0.0; + for( i=__knots-2; i>=1; i-- ) + __z[i] = (__v[i]-__h[i]*__z[i+1])/__u[i]; + + __z[0] = 0.0; + + __invalidate = false; + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/utilities/math/Gauss.as b/Degrafa/com/degrafa/utilities/math/Gauss.as new file mode 100644 index 0000000..1ea6ca0 --- /dev/null +++ b/Degrafa/com/degrafa/utilities/math/Gauss.as @@ -0,0 +1,199 @@ +// +// Gauss.as - Gauss-Legendre Numerical Integration. +// +// This code is derived from source bearing the following copyright notice, +// +// copyright (c) 2006-2007, Jim Armstrong. All Rights Reserved. +// +// This software program is supplied 'as is' without any warranty, express, implied, +// or otherwise, including without limitation all warranties of merchantability or fitness +// for a particular purpose. Jim Armstrong shall not be liable for any special +// incidental, or consequential damages, including, without limitation, lost +// revenues, lost profits, or loss of prospective economic advantage, resulting +// from the use or misuse of this software program. +// +// Programmed by: Jim Armstrong, (http://algorithmist.wordpress.com) +// Ported to Degrafa with full consent of the author +// +/** + * @version 1.0 + */ +package com.degrafa.utilities.math +{ + import flash.events.EventDispatcher; + import flash.events.Event; + + public class Gauss extends EventDispatcher + { + public static const MAX_POINTS:Number = 8; + public static const INVALID_PARAMS:String = "invalid_parameters"; + + // core + private var __abscissa:Array; // abscissa table + private var __weight:Array; // weight table + + public function Gauss() + { + __abscissa = new Array(); + __weight = new Array(); + + // N=2 + __abscissa.push(-0.5773502692); + __abscissa.push( 0.5773502692); + + __weight.push(1); + __weight.push(1); + + // N=3 + __abscissa.push(-0.7745966692); + __abscissa.push( 0.7745966692); + __abscissa.push(0); + + __weight.push(0.5555555556); + __weight.push(0.5555555556); + __weight.push(0.8888888888); + + // N=4 + __abscissa.push(-0.8611363116); + __abscissa.push( 0.8611363116); + __abscissa.push(-0.3399810436); + __abscissa.push( 0.3399810436); + + __weight.push(0.3478548451); + __weight.push(0.3478548451); + __weight.push(0.6521451549); + __weight.push(0.6521451549); + + // N=5 + __abscissa.push(-0.9061798459); + __abscissa.push( 0.9061798459); + __abscissa.push(-0.5384693101); + __abscissa.push( 0.5384693101); + __abscissa.push( 0.0000000000); + + __weight.push(0.2369268851); + __weight.push(0.2369268851); + __weight.push(0.4786286705); + __weight.push(0.4786286705); + __weight.push(0.5688888888); + + // N=6 + __abscissa.push(-0.9324695142); + __abscissa.push( 0.9324695142); + __abscissa.push(-0.6612093865); + __abscissa.push( 0.6612093865); + __abscissa.push(-0.2386191861); + __abscissa.push( 0.2386191861); + + __weight.push(0.1713244924); + __weight.push(0.1713244924); + __weight.push(0.3607615730); + __weight.push(0.3607615730); + __weight.push(0.4679139346); + __weight.push(0.4679139346); + + // N=7 + __abscissa.push(-0.9491079123); + __abscissa.push( 0.9491079123); + __abscissa.push(-0.7415311856); + __abscissa.push( 0.7415311856); + __abscissa.push(-0.4058451514); + __abscissa.push( 0.4058451514); + __abscissa.push( 0.0000000000); + + __weight.push(0.1294849662); + __weight.push(0.1294849662); + __weight.push(0.2797053915); + __weight.push(0.2797053915); + __weight.push(0.3818300505); + __weight.push(0.3818300505); + __weight.push(0.4179591837); + + // N=8 + __abscissa.push(-0.9602898565); + __abscissa.push( 0.9602898565); + __abscissa.push(-0.7966664774); + __abscissa.push( 0.7966664774); + __abscissa.push(-0.5255324099); + __abscissa.push( 0.5255324099); + __abscissa.push(-0.1834346425); + __abscissa.push( 0.1834346425); + + __weight.push(0.1012285363); + __weight.push(0.1012285363); + __weight.push(0.2223810345); + __weight.push(0.2223810345); + __weight.push(0.3137066459); + __weight.push(0.3137066459); + __weight.push(0.3626837834); + __weight.push(0.3626837834); + } + +/** +* @param _f:Function - Reference to function to be integrated - must accept a numerical argument and return +* the function value at that argument. +* +* @param _a:Number - Left-hand value of interval. +* @param _b:Number - Right-hand value of inteval. +* @param _n:Number - Number of points -- must be between 2 and 8 +* +* @return Number - approximate integral value over [_a, _b] or 0 if an error condition occured. Assign a handler for the +* 'invalid_parameters' event. The three possible error conditions are 1) non-numeric values for the interval, 2) an invalid +* interval, 3) an invalid function reference, and 4) an invalid number of samples (must be between two and 8 +* +* @since 1.0 +* +*/ + public function eval(_f:Function, _a:Number, _b:Number, _n:uint=5):Number + { + // evaluate the integral over the specified interval + if( isNaN(_a) || isNaN(_b) ) + { + dispatchEvent( new Event(INVALID_PARAMS) ); + return 0; + } + + if( _a >= _b ) + { + dispatchEvent( new Event(INVALID_PARAMS) ); + return -0; + } + + if( !(_f is Function) ) + { + dispatchEvent( new Event(INVALID_PARAMS) ); + return 0; + } + + if( isNaN(_n) || _n < 2 ) + { + dispatchEvent( new Event(INVALID_PARAMS) ); + return 0; + } + + var n:uint = Math.max(_n,2); + n = Math.min(n,MAX_POINTS); + + var l:uint = (n==2) ? 0 : n*(n-1)/2 - 1; + var sum:Number = 0; + + if( _a == -1 && _b == 1 ) + { + for( var i:uint=0; i 0 ) + __tolerance = _tol; + } + + // set the iteration limit (should be greater than zero) + public function set iterLimit(_limit:uint):void { __iterLimit = _limit > 0 ? _limit : 1; } + +/** +* @param _start:Number desired starting point for iteration +* @param _function:Function reference to Function to evalute f(x) +* @param _deriv:Function reference to Function to evaluate f'(x) +* @param _secondDeriv:Function reference to Function to evaluate f''(x) +* +* @since 1.0 +* +* @return Number: Approximation of desired root or iterate value at which iteration limit was met (the method always performs at +* least one iteration) +* +*/ + public function findRoot( _start:Number, _function:Function, _deriv:Function, _secondDeriv:Function ):Number + { + __iter = 0; + __previous = _start; + + if( _function == null || _deriv == null ) + return __previous; + + // tbd - modify stopping criteria to use relative error + __iter = 1; + var f:Number = _function(__previous); + var deriv:Number = _deriv(__previous); + var x:Number = __previous - (2*f*deriv) / (2*deriv*deriv - f*_secondDeriv(__previous)); + var finished:Boolean = Math.abs(x - __previous) < __tolerance; + + while( __iter < __iterLimit && !finished ) + { + __previous = x; + + f = _function(__previous); + deriv = _deriv(__previous); + x = __previous - (2*f*deriv) / (2*deriv*deriv - f*_secondDeriv(__previous)); + finished = Math.abs(x - __previous) < __tolerance; + + __iter++; + } + + return x; + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/utilities/math/Newton.as b/Degrafa/com/degrafa/utilities/math/Newton.as new file mode 100644 index 0000000..0589929 --- /dev/null +++ b/Degrafa/com/degrafa/utilities/math/Newton.as @@ -0,0 +1,99 @@ +// +// Newton.as - Compute simple roots of the equation f(x) = 0 given a starting point and convergence criteria, using +// Newton's method. To use, set the iteration limit and tolerance, then call the findRoot method. +// +// This code is derived from source bearing the following copyright notice, +// +// Copyright (c) 2008, Jim Armstrong. All rights reserved. +// +// This software program is supplied 'as is' without any warranty, express, +// implied, or otherwise, including without limitation all warranties of +// merchantability or fitness for a particular purpose. Jim Armstrong shall not +// be liable for any special incidental, or consequential damages, including, +// witout limitation, lost revenues, lost profits, or loss of prospective +// economic advantage, resulting from the use or misuse of this software program. +// +// Programmed by Jim Armstrong, (http://algorithmist.wordpress.com) +// Ported to Degrafa with full permission of author +/** +* @version 1.0 +*/ +package com.degrafa.utilities.math +{ + public class Newton + { + // these values are somewhat arbitrary and may be fine-tuned in the future + private static const TOLERANCE:Number = 0.000001; + private static const ZERO_TOL:Number = 0.0000000000000001; + private static const ITER_LIMIT:uint = 100; + + private var __iter:uint; // number of iterations + private var __iterLimit:uint; // maximum number of allowed iterations + private var __tolerance:Number; // tolerance for convergence (absolute error between iterates) + private var __previous:Number; // value of previous iteration + + public function Newton() + { + __iter = 0; + __iterLimit = ITER_LIMIT; + __tolerance = TOLERANCE; + __previous = 0; + } + + // access iteration count + public function get iterations():uint { return __iter; } + + // allow caller to access previous iteration value to determine 'closeness' of iterates at stopping criteria + public function get previousIterate():Number { return __previous; } + + // set the convergence tolerance + public function set tolerance(_tol:Number):void + { + if( _tol > 0 ) + __tolerance = _tol; + } + + // set the iteration limit (should be greater than zero) + public function set iterLimit(_limit:uint):void { __iterLimit = _limit > 0 ? _limit : 1; } + +/** +* @param _start:Number desired starting point for iteration +* @param _function:Function reference to Function to evalute f(x) +* @param _deriv:Function reference to Function to evaluate f'(x) +* +* @since 1.0 +* +* @return Number: Approximation of desired root or iterate value at which iteration limit was met (the method always performs at +* least one iteration) +* +*/ + public function findRoot( _start:Number, _function:Function, _deriv:Function ):Number + { + __iter = 0; + __previous = _start; + + if( _function == null || _deriv == null ) + return __previous; + + + // Exercise - modify stopping criteria to use relative error + __iter = 1; + var deriv:Number = _deriv(__previous); + var x:Number = Math.abs(deriv)QuadHermiteSpline() Construct a new QuadHermiteSpline instance. + * + * @return Nothing. A Quadratic Hermite spline is interpolates a sequence of knots but requires an initial start tangent from the first knot. This + * tangent influences the overall shape of the spline. Choices are 1) Natural Quadratic Spline - zero tangent, 2) User-selected tangent, 3) Auto-selected tangent. + * The user-selected method should be chosen when the knot sequence is statically defined and known in advance. Automatic selection is useful when the knot + * sequence is determined at runtime. Default is automatic. Call the startTangent method to assign tangent coordinates. + * + * @since 1.0 + * + */ + public function QuadHermiteSpline() + { + __quads = []; + __x = []; + __y = []; + + __invalidate = true; + __tX = NaN; + __tY = NaN; + __knots = 0; + __t = -1; + __index = 0; + __localParam = 0; + } + + public function get type():String { return SplineTypeEnum.PARAMETRIC; } + public function eval(_x:Number):Number { return 0; } + public function derivative(_x:Number):Number { return 0; } + public function getCoef(_segment:uint):Object { return __quads[_segment].getCoef(); } + +/** + * [get] knotCount Access number of knots. + * + * @param _t:Number - x-coordinate of knot to add + * @param _y:Number - y-coordinate of knot to add + * + * @return int Current knot count + * + * @since 1.0 + * + */ + public function get knotCount():int { return __knots; } + +/** + * [get] knots Access knots collection. + * + * @return Array Knot collection; i-th Array element is an Object with x-coordinate of the i-th knot in the 'X' property and y-coordinate in the 'Y' property + * + * @since 1.0 + * + */ + public function get knots():Array + { + var knotArr:Array = new Array(); + for( var i:uint=0; i<__knots; ++i ) + knotArr.push({X:__x[i], Y:__y[i]}); + + return knotArr; + } + +/** + * startTangent Assign start tangent coordinates. + * + * @param _x:Number x-coordinate of start tangent in same coordinate space as control points + * @param _y:Number y-coordinate of start tangent in same coordinate space as control points + * + * @return Nothing Call this method to override the default automatic start tangent and assign a specific tangent. + * + * @since 1.0 + * + */ + public function startTangent(_x:Number, _y:Number):void + { + if( !isNaN(_x) && !isNaN(_y) ) + { + __tX = _x; + __tY = _y; + + __invalidate = true; + } + } + +/** + * addControlPoint Add a knot or control point. + * + * @param _t:Number - x-coordinate of knot + * @param _y:Number - y-coordinate of knot + * + * @return Nothing + * + * @since 1.0 + * + */ + public function addControlPoint(_xKnot:Number, _yKnot:Number):void + { + + if( !isNaN(_xKnot) && !isNaN(_yKnot) ) + { + __invalidate = true; + + __x[__knots] = _xKnot; + __y[__knots++] = _yKnot; + } + } + +/** + * moveControlPoint Move knot at the specified index within its interval + * + * @param _indx:uint - index of knot to replace + * @param _xKnot:Number - new x-coordinate + * @param _yKnot:Number - new y-coordinate + * + * @return Nothing - There is no testing to see if the move causes any intervals to overlap + * + * @since 1.0 + * + */ + public function moveControlPoint(_indx:uint, _xKnot:Number, _yKnot:Number):void + { + if( _indx < 0 || _indx >= __knots ) + { + return; + } + + if( isNaN(_xKnot) || isNaN(_yKnot) ) + { + return; + } + + __x[_indx] = _xKnot; + __y[_indx] = _yKnot; + __invalidate = true; + } + +/** + * reset - Remove all control points and initialize spline for new control point entry (tangents are set to automatic, so call startTangent() to override and + * manually specify a start tangent) + * + * @return Nothing + * + * @since 1.0 + * + */ + public function reset():void + { + __x.splice(0); + __y.splice(0); + __quads.splice(0); + + __knots = 0; + __tX = NaN; + __tY = NaN; + __t = -1; + __invalidate = true; + } + +/** + * getX - Return x-coordinate for a given t + * + * @param _t:Number - parameter value in [0,1] + * + * @return Number Value of Catmull-Rom spline, provided input is in [0,1], C(0) or C(1). If knot count is below 2, return 0. + * + * @since 1.0 + * + */ + public function getX(_t:Number):Number + { + if( __knots < 2 ) + return ( (__knots==1) ? __x[1] : 0 ); + + if( __invalidate ) + __computeCoef(); + + // assign the t-parameter for this evaluation + __setParam(_t); + + return __quads[__index].getX(__localParam); + } + +/** + * getXPrime - Return dx/dt for a given t + * + * @param _t:Number - parameter value in [0,1] + * + * @return Number: Value of dx/dt, provided input is in [0,1]. + * + * @since 1.0 + * + */ + public function getXPrime(_t:Number):Number + { + if( __knots < 2 ) + return 0; + + if( __invalidate ) + __computeCoef(); + + // assign the t-parameter for this evaluation + __setParam(_t); + + return __quads[__index].getXPrime(__localParam); + } + +/** + * getY - Return y-coordinate for a given t + * + * @param _t:Number - parameter value in [0,1] + * + * @return Number: Value of Catmull-Rom spline, provided input is in [0,1], C(0) or C(1). + * + * @since 1.0 + * + */ + public function getY(_t:Number):Number + { + if( __knots < 2 ) + return ( (__knots==1) ? __y[1] : 0 ); + + if( __invalidate ) + __computeCoef(); + + // assign the t-parameter for this evaluation + __setParam(_t); + + return __quads[__index].getY(__localParam); + } + +/** + * getYPrime - Return dy/dt for a given t + * + * @param _t:Number - parameter value in [0,1] + * + * @return Number Value of dy/dt, provided input is in [0,1]. + * + * @since 1.0 + * + */ + public function getYPrime(_t:Number):Number + { + if( __knots < 2 ) + return 0; + + if( __invalidate ) + __computeCoef(); + + // assign the t-parameter for this evaluation + __setParam(_t); + + return __quads[__index].getYPrime(__localParam); + } + + + + protected function __setParam(_t:Number):void + { + var t:Number = (_t<0) ? 0 : _t; + t = (t>1) ? 1 : t; + + if( t != __t ) + { + __t = t; + __segment(); + } + } + + protected function __computeCoef():void + { + if( isNaN(__tX) && isNaN(__tY) ) + __computeEndpoints(); + + // currently, all segments are recomputed from scratch any time something changes; may make more efficient in the future if this is used + // for games or other application requiring frequent redraws with modified data + __quads.length = 0; + + // loop over segments + var tx:Number = __tX; + var ty:Number = __tY; + for( var i:uint=0; i<__knots-1; ++i ) + { + var p0X:Number = __x[i]; + var p0Y:Number = __y[i]; + var p1X:Number = __x[i+1]; + var p1Y:Number = __y[i+1]; + + var q:QuadraticHermiteCurve = new QuadraticHermiteCurve(p0X, p0Y, p1X, p1Y, tx, ty); + + // add to the collective and compute start tangent for next segment + __quads[i] = q; + + var dx:Number = tx - p0X; + var dy:Number = ty - p0Y; + var e1X:Number = 2*p1X - p0X; + var e1Y:Number = 2*p1Y - p0Y; + var endX:Number = e1X - dx; + var endY:Number = e1Y - dy; + + dx = endX - p0X; + dy = endY - p0Y; + tx = p1X + dx; + ty = p1Y + dy; + } + + __invalidate = false; + } + + protected function __segment():void + { + // the trivial case -- one segment + if( __knots == 2 ) + { + __index = 0; + } + else + { + if( __t == 0 ) + { + __index = 0; + __localParam = 0; + } + else if( __t == 1.0 ) + { + __index = __knots-2; + __localParam = 1.0; + } + else + { + var N1:Number = __knots-1; + var N1t:Number = N1*__t; + var f:Number = Math.floor(N1t); + __index = Math.min(f+1, N1)-1; + __localParam = N1t - f; + } + } + } + + protected function __computeEndpoints():void + { + var p0X:Number = __x[0]; + var p0Y:Number = __y[0]; + var p1X:Number = __x[1]; + var p1Y:Number = __y[1]; + var p2X:Number = __x[2]; + var p2Y:Number = __y[2]; + var nx:Number = p1X - p0X; + var ny:Number = p1Y - p0Y; + var d:Number = Math.sqrt(nx*nx + ny*ny); + nx /= d; + ny /= d; + + var px:Number = p2X - p0X; + var py:Number = p2Y - p0Y; + var w:Number = nx*px + ny*py; + + __tX = 2*p0X - p2X + 2*w*nx; + __tY = 2*p0Y - p2Y + 2*w*ny; + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/utilities/math/QuadraticHermiteCurve.as b/Degrafa/com/degrafa/utilities/math/QuadraticHermiteCurve.as new file mode 100644 index 0000000..088b4e5 --- /dev/null +++ b/Degrafa/com/degrafa/utilities/math/QuadraticHermiteCurve.as @@ -0,0 +1,269 @@ +/** +/* QuadHermiteCurve.as - Construct a quadratic Hermite curve given two interpolation points and a start tangent. +* +* This code is derived from source bearing the following copyright notice, +* +* Copyright (c) 2009, Jim Armstrong. All rights reserved. +* +* This software program is supplied 'as is' without any warranty, express, +* implied, or otherwise, including without limitation all warranties of +* merchantability or fitness for a particular purpose. Jim Armstrong shall not +* be liable for any special incidental, or consequential damages, including, +* witout limitation, lost revenues, lost profits, or loss of prospective +* economic advantage, resulting from the use or misuse of this software program. +* +* Programmed by Jim Armstrong, (http://algorithmist.wordpress.com) +* Ported to Degrafa with full permission of author +**/ + +package com.degrafa.utilities.math +{ + public class QuadraticHermiteCurve + { + // first and last points + private var __p0X:Number; + private var __p0Y:Number; + private var __p1X:Number; + private var __p1Y:Number; + + // tangent + private var __tX:Number; + private var __tY:Number; + + // your friendly neighborhood quad. coefficients + private var __c0X:Number; + private var __c0Y:Number; + private var __c1X:Number; + private var __c1Y:Number; + private var __c2X:Number; + private var __c2Y:Number; + + private var __invalidated:Boolean; + +/** + * QuadraticHermitCurve Construct a new QuadraticHeriteCurve instance. + * + * @param _x0:Number x-coordinate of first interpolation point + * @default 0 + * + * @param _y0:Number y-coordinate of first interpolation point + * @default 0 + * + * @param _x1:Number x-coordinate of second inpterolation point + * @default 0 + * + * @param _y1:Number y-coordinate of second interpolation point + * @default 0 + * + * @param _tx:Number x-coordinate of start tangent (tangent to first interpolation point) + * @default 0 + * + * @param _ty:Number y-coordinate of start tangent (tangent to first interpolation point) + * @since 1.0 + * + * @return Nothing. Make sure to enter the endpoints of the tangent vector in the parent coordinate system. + */ + public function QuadraticHermiteCurve(_x0:Number=0, _y0:Number=0, _x1:Number=0, _y1:Number=0, _tx:Number=0, _ty:Number=0) + { + __p0X = _x0; + __p0Y = _y0; + __p1X = _x1; + __p1Y = _y1; + __tX = _tx; + __tY = _ty; + __c0X = 0; + __c0Y = 0; + __c1X = 0; + __c1Y = 0; + __c2X = 0; + __c2Y = 0; + + __invalidated = true; + } + + public function getCoef():Object + { + return {c0X:__c0X, c0Y:__c0Y, c1X:__c1X, c1Y:__c1Y, c2X:__c2X, c2Y:__c2Y}; + } +/** +* [set] x0 Assign x-coordinate of first interpolation point +* +* @return Nothing. +* +* @since 1.0 +* +*/ + public function set x0(_n:Number):void + { + __p0X = _n; + __invalidated = true; + } + +/** +* [set] y0 Assign y-coordinate of first interpolation point +* +* @return Nothing. +* +* @since 1.0 +* +*/ + public function set y0(_n:Number):void + { + __p0Y = _n; + __invalidated = true; + } + +/** +* [set] x1 Assign x-coordinate of second interpolation point +* +* @return Nothing. +* +* @since 1.0 +* +*/ + public function set x1(_n:Number):void + { + __p1X = _n; + __invalidated = true; + } + +/** +* [set] y1 Assign y-coordinate of second interpolation point +* +* @return Nothing. +* +* @since 1.0 +* +*/ + public function set y1(_n:Number):void + { + __p1Y = _n; + __invalidated = true; + } + +/** +* [set] tx Assign x-coordinate of start tangent +* +* @return Nothing. +* +* @since 1.0 +* +*/ + public function set tx(_n:Number):void + { + __tX = _n; + __invalidated = true; + } + +/** +* [set] y0 Assign y-coordinate of start tangent +* +* @return Nothing. +* +* @since 1.0 +* +*/ + public function set ty(_n:Number):void + { + __tY = _n; + __invalidated = true; + } + +/** +* getX Access the x-coordinate of the curve at the specified t-parameter +* +* @param _t:Number t-paramter in [0,1]. +* +* @return Number x-coordinate of quadratic Hermite curve at the specified t-parameter. Extrapolating outside [0.1] is allowed, but not recommended. Assign +* interpolation points and start tangent before calling this method. +* +* @since 1.0 +* +*/ + public function getX(_t:Number):Number + { + if( __invalidated ) + { + __computeCoef(); + } + + return __c0X + _t*(__c1X + _t*__c2X); + } + +/** +* getY Access the y-coordinate of the curve at the specified t-parameter +* +* @param _t:Number t-paramter in [0,1]. +* +* @return Number y-coordinate of quadratic Hermite curve at the specified t-parameter. Extrapolating outside [0.1] is allowed, but not recommended. Assign +* interpolation points and start tangent before calling this method. +* +* @since 1.0 +* +*/ + public function getY(_t:Number):Number + { + if( __invalidated ) + { + __computeCoef(); + } + + return __c0Y + _t*(__c1Y + _t*__c2Y); + } + +/** +* getXPrime Access the x-coordinate of dx/dt at the specified t-parameter +* +* @param _t:Number t-paramter in [0,1]. +* +* @return Number x-coordinate of dx/dt at the specified t-parameter. Extrapolating outside [0.1] is allowed, but not recommended. Assign +* interpolation points and start tangent before calling this method. +* +* @since 1.0 +* +*/ + public function getXPrime(_t:Number):Number + { + if( __invalidated ) + { + __computeCoef(); + } + + return __c1X + 2.0*__c2X*_t; + } + +/** +* getYPrime Access the x-coordinate of dy/dt at the specified t-parameter +* +* @param _t:Number t-paramter in [0,1]. +* +* @return Number y-coordinate of dy/dt at the specified t-parameter. Extrapolating outside [0.1] is allowed, but not recommended. Assign +* interpolation points and start tangent before calling this method. +* +* @since 1.0 +* +*/ + public function getYPrime(_t:Number):Number + { + if( __invalidated ) + { + __computeCoef(); + } + + return __c1Y + 2.0*__c2Y*_t; + } + + private function __computeCoef():void + { + // as an exercise, fold these coefficients directly into the x-y point evaluation and optimize + __c0X = __p0X; + __c0Y = __p0Y; + __c1X = __tX - __p0X; + __c1Y = __tY - __p0Y; + __c2X = __p1X - __p0X - (__tX-__p0X); + __c2Y = __p1Y - __p0Y - (__tY-__p0Y); + + __invalidated = false; + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/utilities/math/SimpleRoot.as b/Degrafa/com/degrafa/utilities/math/SimpleRoot.as new file mode 100644 index 0000000..faac1dc --- /dev/null +++ b/Degrafa/com/degrafa/utilities/math/SimpleRoot.as @@ -0,0 +1,165 @@ +// +// SimpleRoot.as - A straight port of Jack Crenshaw's TWBRF method for simple roots in an interval. To use, identify an interval in which +// the function whose zero is desired has a sign change (via bisection, for example). Call the findRoot method. +// +// This program is derived from source bearing the following copyright notice, +// +// Copyright (c) 2008, Jim Armstrong. All rights reserved. +// +// This software program is supplied 'as is' without any warranty, express, +// implied, or otherwise, including without limitation all warranties of +// merchantability or fitness for a particular purpose. Jim Armstrong shall not +// be liable for any special incidental, or consequential damages, including, +// witout limitation, lost revenues, lost profits, or loss of prospective +// economic advantage, resulting from the use or misuse of this software program. +// +// Programmed by Jim Armstrong, (http://algorithmist.wordpress.com) +// Ported to Degrafa with full permission of author +/** +* @version 1.0 +*/ +package com.degrafa.utilities.math +{ + import flash.events.Event; + import flash.events.EventDispatcher; + + public class SimpleRoot extends EventDispatcher + { + public static const ERROR:String = "error"; + public static const INVALID_INTERVAL:String = "invalid interval"; + public static const NO_CONVERGENCE:String = "no convergence"; + + private static const TOL:Number = 0.000001; + private static const MAX_ITER:uint = 100; + + private var __iter:uint; + private var __message:String; + + public function SimpleRoot() + { + super(); + + __iter = 0; + __message = INVALID_INTERVAL; + } + + public function get iterations():uint { return __iter; } + public function get message():String { return __message; } + +/** +* +* @param _x0:Number root isolated in interval [_x0, _x2] +* @param _x2:Number root isolated in interval [_x0, _x2] +* @param _f:Function reference to function whose root in the interval is desired. Function accepts a single Number argument. +* @param _imax:uint maximum number of iterations +* @default MAX_ITER +* @param _eps:Number tolerance value for root +* @default TOL +* +* @since 1.0 +* +* @return Number: Approximation of desired root within specified tolerance and iteration limit. In addition to too small +* an iteration limit or too tight a tolerance, some patholotical numerical conditions exist under which the method may +* incorrectly report a root. +* +*/ + public function findRoot(_x0:Number, _x2:Number, _f:Function, _imax:uint=MAX_ITER, _eps:Number=TOL):Number + { + var x0:Number; + var x1:Number; + var x2:Number; + var y0:Number; + var y1:Number; + var y2:Number; + var b:Number; + var c:Number; + var y10:Number; + var y20:Number; + var y21:Number; + var xm:Number; + var ym:Number; + var temp:Number; + + var xmlast:Number = _x0; + y0 = _f(_x0); + + if( y0 == 0.0 ) + return _x0; + + y2 = _f(_x2); + if( y2 == 0.0 ) + return _x2; + + if( y2*y0 > 0.0 ) + { + dispatchEvent( new Event(ERROR) ); + return _x0; + } + + __iter = 0; + x0 = _x0; + x2 = _x2; + for( var i:uint=0; i<_imax; ++i) + { + __iter++; + + x1 = 0.5 * (x2 + x0); + y1 = _f(x1); + if( y1 == 0.0 ) + return x1; + + if( Math.abs(x1 - x0) < _eps) + return x1; + + if( y1*y0 > 0.0 ) + { + temp = x0; + x0 = x2; + x2 = temp; + temp = y0; + y0 = y2; + y2 = temp; + } + + y10 = y1 - y0; + y21 = y2 - y1; + y20 = y2 - y0; + if( y2*y20 < 2.0*y1*y10 ) + { + x2 = x1; + y2 = y1; + } + else + { + b = (x1 - x0 ) / y10; + c = (y10 - y21) / (y21 * y20); + xm = x0 - b*y0*(1.0 - c*y1); + ym = _f(xm); + if( ym == 0.0 ) + return xm; + + if( Math.abs(xm - xmlast) < _eps ) + return xm; + + xmlast = xm; + if( ym*y0 < 0.0 ) + { + x2 = xm; + y2 = ym; + } + else + { + x0 = xm; + y0 = ym; + x2 = x1; + y2 = y1; + } + } + } + + __message = NO_CONVERGENCE; + dispatchEvent( new Event(ERROR) ); + return x1; + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/utilities/math/Solve2x2.as b/Degrafa/com/degrafa/utilities/math/Solve2x2.as new file mode 100644 index 0000000..0f81e90 --- /dev/null +++ b/Degrafa/com/degrafa/utilities/math/Solve2x2.as @@ -0,0 +1,73 @@ +// +// Solve2x2.as - A simple solver for two equations and two unknowns using Cramer's rule. If the determinant is close to zero +// a zero vector is returned as the solution. +// +// This program is derived from source bearing the following copyright notice, +// +// copyright (c) 2006-2008, Jim Armstrong. All Rights Reserved. +// +// This software program is supplied 'as is' without any warranty, express, implied, +// or otherwise, including without limitation all warranties of merchantability or fitness +// for a particular purpose. Jim Armstrong shall not be liable for any special incidental, or +// consequential damages, including, without limitation, lost revenues, lost profits, or +// loss of prospective economic advantage, resulting from the use or misuse of this software +// program. +// +// Programmed by Jim Armstrong, Singularity (http://algorithmist.wordperss.com) +// Ported to Degrafa will full consent of author +// +/** +* Version 1.0 +*/ +package com.degrafa.utilities.math +{ + import flash.geom.Point; + + public class Solve2x2 + { + private var __determinant:Number; // value of determinant + + public function Solve2x2() + { + __determinant = 0; + } + + public function get determinant():Number { return __determinant; } + +/** +* @param _a11:Number coefficient of x in first equation +* @param _a12:Number coefficient of y in first equation +* @param _a21:Number coefficient of x in second equation +* @param _a22:Number coefficient of y in second equation +* @param _b1:Number right-hand side value in first equation +* @param _b2:Number right-hand side value in second equation +* @param _zeroTol:Number optional zero-tolerance for determinant +* @default 0.00001 +* @param _resolve:Boolean true if resolving a new system of equations with same coefficients, but different RHS +* @default false +* +* @return Point contains solution values or zero-vector if determinant is less than or equal to zero tolerance +* +* @since 1.0 +* +*/ + public function solve( _a11:Number, _a12:Number, _a21:Number, _a22:Number, _b1:Number, _b2:Number, _zeroTol:Number=0.00001, _resolve:Boolean=false ):Point + { + if( !_resolve ) + { + __determinant = _a11*_a22 - _a12*_a21; + } + + // tbd - dispatch an event if the determinant is near zero? + if( Math.abs(__determinant) > _zeroTol ) + { + var x:Number = (_a22*_b1 - _a12*_b2)/__determinant; + var y:Number = (_a11*_b2 - _a21*_b1)/__determinant; + + return new Point(x,y); + } + + return new Point(0,0); + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/utilities/math/SplineToBezier.as b/Degrafa/com/degrafa/utilities/math/SplineToBezier.as new file mode 100644 index 0000000..2a48977 --- /dev/null +++ b/Degrafa/com/degrafa/utilities/math/SplineToBezier.as @@ -0,0 +1,602 @@ +// +// SplineToBezier.as - This utility approximates a spline curve with a sequence of quadratic Beziers that can be inserted into the +// Degrafa command stack. Instead of trying to minimize the total number of quads, the code produces an integral number of quads +// between knots. This provides some utility for charting applications, allowing the construction of vertical 'strips' of a chart +// with a modest number of lines/curves. +// +// This code is derived from source bearing the following copyright notice +// +// copyright (c) 2005, Jim Armstrong. All Rights Reserved. +// +// This software program is supplied 'as is' without any warranty, express, implied, +// or otherwise, including without limitation all warranties of merchantability or fitness +// for a particular purpose. Jim Armstrong shall not be liable for any special incidental, or +// consequential damages, including, without limitation, lost revenues, lost profits, or +// loss of prospective economic advantage, resulting from the use or misuse of this software +// program. +// +// Programmed by Jim Armstrong, (http://algorithmist.wordpress.com) +// Ported to Degrafa with full consent of author +// +/** + * @version 1.1 + */ + +package com.degrafa.utilities.math +{ + import com.degrafa.geometry.splines.IPlottableSpline; + import com.degrafa.geometry.splines.SplineTypeEnum; + import com.degrafa.geometry.splines.QuadData; + import com.degrafa.utilities.math.Gauss; + + public class SplineToBezier + { + private static const ONE_THIRD:Number = 1/3; + private static const ZERO_TOL:Number = 0.00000001; + private static const LARGE:Number = 1/ZERO_TOL; + + private var __count:uint; // counts the number of knots in the spline + private var __knots:Array; // knot collection for the spline + private var __mySpline:IPlottableSpline; // reference to current plottable spline for numerical integration + private var __integral:Gauss; // numerical integration by gaussian quadrature + + public function SplineToBezier() + { + __count = 0; + + __knots = new Array(); + + __mySpline = null; + + __integral = new Gauss(); + } + +/** +* convert Convert a plottable spline (one that implements the IPlottableSpline interface) to a sequence of quadratic Bezier curves, +* returning the raw information needed to construct the quadratic curves. +* +* @param _spline:IPlottableSpline Plottable Spline +* @param _tol:Number tolerance value for relative error (five percent is used for default, which is pretty tight) +* @default 0.05 +* +* @return Array a collection of two Arrays, the first off which is the QuadData instances describing the sequence of quadratic +* Bezier curves that approximate the plottable spline. The spline is approximated over the entire span of knots. The second array contains a list of +* indices into the first array corresponding to the quadratic curves beginning at each segment. For example, the second spline segment (index 1) is +* from knot 1 to knot 2. Suppose the first array is named quads. The second array is named segments. The sequence of quad. +* Beziers approximating that segment of the spline range from quads[segments[1]] to quads[segments[2]-1]. This allows the specific set of quad. Beziers +* spanning a specific knot set to be identified and used for highlighting those sections or creating new shapes based on that data. +* +* @since 1.0 +* +*/ + public function convert(_spline:IPlottableSpline, _tol:Number=0.05):Array + { + if( _spline == null ) + { + return []; + } + + // access the knot collection + __knots = _spline.knots; + if( __knots.length == 0 ) + { + return []; + } + + if( _spline.type == SplineTypeEnum.CARTESIAN ) + { + return __cartesianToBezier(_spline, _tol); + } + else + { + return __parametricToBezier(_spline, _tol); + } + } + + private function __cartesianToBezier(_spline:IPlottableSpline, _tol:Number):Array + { + if( _spline == null ) + { + return []; + } + + __mySpline = _spline; + var tol:Number = Math.max(0.001,Math.abs(_tol)); + var quads:Array = new Array(); + var segments:Array = new Array(); + __count = __knots.length; + + if( __count == 1 ) + { + var o:Object = __knots[0]; + return [ [new QuadData(o.X,o.Y,o.X,o.Y,o.X,o.Y)], [0] ] ; + } + + if( __count == 2 ) + { + o = __knots[0]; + var x1:Number = o.X; + var y1:Number = o.Y; + + o = __knots[1]; + var x2:Number = o.X; + var y2:Number = o.Y; + + return [ [new QuadData(x1, y1, 0.5*(x1+x2), 0.5*(y1+y2), x2, y2)], [0] ]; + } + + var q:Array = new Array(); + var indx:Array = [0]; + + // process each segment, producing an integral number of quads between each knot. + for( var i:uint=0; i<__count-1; ++i ) + { + var qSegment:Array = __subdivideCartesian(_spline, i, tol); + indx[i+1] = indx[i] + qSegment.length; + + q = q.concat(qSegment); + } + + return [q, indx]; + } + + private function __parametricToBezier(_spline:IPlottableSpline, _tol:Number):Array + { + if( _spline == null ) + { + return []; + } + + // may merge paths for the special cases in the future + __mySpline = _spline; + var tol:Number = Math.max(0.001,Math.abs(_tol)); + var quads:Array = new Array(); + var segments:Array = new Array(); + __count = __knots.length; + + if( __count == 1 ) + { + var o:Object = __knots[0]; + return [ [new QuadData(o.X,o.Y,o.X,o.Y,o.X,o.Y)], [0] ] ; + } + + if( __count == 2 ) + { + o = __knots[0]; + var x1:Number = o.X; + var y1:Number = o.Y; + + o = __knots[1]; + var x2:Number = o.X; + var y2:Number = o.Y; + + return [ [new QuadData(x1, y1, 0.5*(x1+x2), 0.5*(y1+y2), x2, y2)], [0] ]; + } + + var q:Array = new Array(); + var indx:Array = [0]; + + // process each segment, producing an integral number of quads between each knot. + for( var i:uint=0; i<__count-1; ++i ) + { + var qSegment:Array = __subdivideParametric(_spline, i, tol); + indx[i+1] = indx[i] + qSegment.length; + + q = q.concat(qSegment); + } + + return [q, indx]; + } + + private function __subdivideCartesian(_spline:IPlottableSpline, _segment:uint, _tol:Number):Array + { + // first pass, check for an inflection point to subdivide - as we're dealing predominantly with cubic polynomials in between knots, there + // will be at most two. Return the one farthest from an endpoint as the parameter to subdivide the curve. May modify to include stationary + // points in the future, if easy to compute. + var x1:Number = __inflect(_spline, _segment); + if ( x1 == -1 ) + { + x1 = 0.5*(__knots[_segment].X + __knots[_segment+1].X); + } + + var q:Array = new Array(); + var complete:Array = new Array(); + var limit:uint = 4; + var finished:Boolean = false; + + // always begin with one subdivision - two quads; this often provides a tight enough fit without any further recursion + var o:Object = __knots[_segment]; + var x0:Number = o.X; + var y0:Number = o.Y; + + var y1:Number = _spline.eval(x1); + + // slope at each endpoint + var m1:Number = _spline.derivative(x0); + var m2:Number = _spline.derivative(x1); + o = __intersect(x0, y0, m1, x1, y1, m2); + + var quad:QuadData = new QuadData(x0, y0, o.px, o.py, x1, y1); + q[0] = quad; + complete[0] = false; + + o = __knots[_segment+1]; + var x2:Number = o.X; + var y2:Number = o.Y; + m1 = m2; + m2 = _spline.derivative(x2); + o = __intersect(x1, y1, m1, x2, y2, m2); + + quad = new QuadData(x1, y1, o.px, o.py, x2, y2); + q[1] = quad; + complete[1] = false; + + // this approach could be implemented recursively, but I think it's more difficult to understand and recursive calls are usually computationally inefficient + while( !finished ) + { + // check each quad segment vs. closeness metric unless it's already completed + for( var i:uint=0; i _tol ) + { + // subdivide + var newX:Number = 0.5*(quad.x0 + quad.x1); + var newY:Number = _spline.eval(newX); + + // slope at each new endpoint + m1 = _spline.derivative(quad.x0); + m2 = _spline.derivative(newX); + o = __intersect(quad.x0, quad.y0, m1, newX, newY, m2); + + var q1:QuadData = new QuadData(x0, y0, o.px, o.py, newX, newY); + + // replace existing quad + q[i] = q1; + complete[i] = false; + + m1 = m2; + m2 = _spline.derivative(quad.x1); + o = __intersect(newX, newY, m1, quad.x1, quad.y1, m2); + + var q2:QuadData = new QuadData(newX, newY, o.px, o.py, quad.x1, quad.y1); + + // add to the collective + q.splice(i+1, 0, q2); + complete.splice(i+1, 0, false); + + if( q.length >= limit ) + { + return q; + } + } + else + { + complete[i] = true; // finished with this one + } + } + } + + // are we finished - this is the simple and straightforward way to do it + finished = true; + for( var j:uint=0; j _tol ) + { + // subdivide + t0 = t[i]; + t2 = t[i+1]; + t1 = 0.5*(t0 + t2); + + var newX:Number = _spline.getX(t1); + var newY:Number = _spline.getY(t1); + + // slope at each new endpoint + m1 = __getParametricDerivative(_spline, t0); + m2 = __getParametricDerivative(_spline, t1); + o = __intersect(quad.x0, quad.y0, m1, newX, newY, m2); + + var q1:QuadData = new QuadData(x0, y0, o.px, o.py, newX, newY); + + // replace existing quad + q[i] = q1; + complete[i] = false; + + t.splice(i+1, 0, t1); + + m1 = m2; + m2 = __getParametricDerivative(_spline, t2); + o = __intersect(newX, newY, m1, quad.x1, quad.y1, m2); + + var q2:QuadData = new QuadData(newX, newY, o.px, o.py, quad.x1, quad.y1); + + // add to the collective + q.splice(i+1, 0, q2); + complete.splice(i+1, 0, false); + + if( q.length >= limit ) + { + return q; + } + } + else + { + complete[i] = true; // finished with this one + } + } + } + + // are we finished - this is the simple and straightforward way to do it + finished = true; + for( var j:uint=0; j= LARGE ) + { + px = _p0X; + py = (Math.abs(_m2) >= LARGE) ? (_p0Y + 3*(_p0Y-_p0X)) : (_m2*(_p0X-_p2X)+_p2Y); + } + else if( Math.abs(_m2) >= LARGE ) + { + px = _p2X; + py = (Math.abs(_m1) >= LARGE) ? (_p2Y + 3*(_p2Y-_p0X)) : (_m1*(_p2X-_p0X)+_p0Y); + } + else + { + if( Math.abs(_m1-_m2) <= 0.05 ) + { + // lines nearly parallel, meaning no intersection or the difference in slope is sufficiently small that the intersection will be well outside + // where we would like a control point to be positioned. This is subject to some future tweaking :) + px = 0.5*(_p0X+_p2X); + py = 0.5*(_p0Y+_p2Y); + } + else + { + var b1:Number = _p0Y - _m1*_p0X; + var b2:Number = _p2Y - _m2*_p2X; + px = (b2-b1)/(_m1-_m2); + py = _m1*px + b1; + + if( __mySpline.type == SplineTypeEnum.CARTESIAN && (px >= _p2X || px <= _p0X) ) + { + px = 0.5*(_p0X+_p2X); + py = 0.5*(_p0Y+_p2Y); + } + + // quad must have unique control points + if( (Math.abs(px-_p0X) < 0.0000001 && Math.abs(py-_p0Y) < 0.0000001) || + (Math.abs(px-_p2X) < 0.0000001 && Math.abs(py-_p2Y) < 0.0000001) ) + { + px += 1; + py += 1; + } + } + } + + return {px:px, py:py} + } + + // arc-length integrand for spline in cartesian form + private function __cartesianIntegrand(_x:Number):Number + { + var d:Number = __mySpline.derivative(_x); + + return Math.sqrt( 1 + d*d ); + } + + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/utilities/swf/SWFFontReader.as b/Degrafa/com/degrafa/utilities/swf/SWFFontReader.as new file mode 100644 index 0000000..9e1fb32 --- /dev/null +++ b/Degrafa/com/degrafa/utilities/swf/SWFFontReader.as @@ -0,0 +1,160 @@ +package com.degrafa.utilities.swf +{ + import com.degrafa.utilities.swf.fonts.FontSet; + + import flash.utils.ByteArray; + + public class SWFFontReader + { + + private static const DEFINE_FONT:int = 10; + private static const DEFINE_FONT_2:int = 62; + private static const DEFINE_FONT_3:int = 75; + private static const DEFINE_FONT_INFO:int = 13; + private static const DEFINE_FONT_INFO_2:int = 62; + private static const DEFINE_FONT_ALIGN_ZONES:int = 73; + private static const DEFINE_FONT_NAME:int = 88; + + public var fonts:Array; + + public function SWFFontReader() { + fonts = []; + } + + public function isFontTag(tag:SWFTag):Boolean { + switch(tag.type) { + case DEFINE_FONT: + case DEFINE_FONT_2: + case DEFINE_FONT_3: + case DEFINE_FONT_INFO: + case DEFINE_FONT_INFO_2: + case DEFINE_FONT_ALIGN_ZONES: + case DEFINE_FONT_NAME: + return true; + default: + return false; + } + } + + public function decodeTag(tag:SWFTag):void { + switch(tag.type) { + case DEFINE_FONT: + parseFont(tag); + break; + case DEFINE_FONT_2: + parseFont2(tag); + break; + case DEFINE_FONT_3: + parseFont3(tag); + break; + case DEFINE_FONT_INFO: + parseFontInfo(tag); + break; + case DEFINE_FONT_INFO_2: + parseFontInfo2(tag); + break; + case DEFINE_FONT_ALIGN_ZONES: + parseFontAlignZones(tag); + break; + case DEFINE_FONT_NAME: + parseFontName(tag); + break; + default: + // TODO: Warn developer here. Not a recognized font tag. + break; + } + + } + + private function parseFont(tag:SWFTag):void { + //trace('{FontReader.parseFont} Not implemented.'); + + } + + private function parseFont2(tag:SWFTag):void { + //trace('{FontReader.parseFont2} Not implemented.'); + } + + private function parseFont3(tag:SWFTag):void { + + //trace('{FontReader.parseFont3}'); + + var fs:FontSet = new FontSet(); + var bytes:ByteArray = tag.bytes; + var flags:uint, nameLen:uint, i:int, len:int; + + bytes.position = tag.start; + + fs.tag = tag; + + fs.fontID = bytes.readUnsignedShort(); + + flags = bytes.readUnsignedByte(); + fs.hasLayout = Boolean(flags & 128); + fs.shiftJIS = Boolean(flags & 64); + fs.smallText = Boolean(flags & 32); + fs.ansi = Boolean(flags & 16); + fs.wideOffsets = Boolean(flags & 8); + fs.wideCodes = Boolean(flags & 4); + fs.italic = Boolean(flags & 2); + fs.bold = Boolean(flags & 1); + + fs.languageCode = bytes.readUnsignedByte(); + + nameLen = bytes.readUnsignedByte(); + fs.fontName = bytes.readUTFBytes(nameLen); + + fs.numGlyphs = bytes.readUnsignedShort(); + + var offsetTableStartPtr:uint = fs.offsetTableOffset = bytes.position; + + fs.offsetTable = []; + len = fs.numGlyphs; + for (i = 0; i < len; i++) { + fs.offsetTable.push(fs.wideOffsets ? bytes.readUnsignedInt() : bytes.readUnsignedShort()); + } + + fs.codeTableOffset = fs.wideOffsets ? bytes.readUnsignedInt() : bytes.readUnsignedShort(); + + len = fs.offsetTable.length; + + /* + + if (fs.hasLayout) { + fs.fontAscent = bytes.readShort(); + fs.fontDescent = bytes.readShort(); + fs.fontLeading = bytes.readShort(); + } + + */ + + fonts.push(fs); + } + + private function parseFontInfo(tag:SWFTag):void { + //trace('{FontReader.parseFontInfo} Not implemented.'); + } + + private function parseFontInfo2(tag:SWFTag):void { + //trace('{FontReader.parseFontInfo2} Not implemented.'); + } + + private function parseFontAlignZones(tag:SWFTag):void { + //trace('{FontReader.parseFontAlignZones} Not implemented.'); + } + + private function parseFontName(tag:SWFTag):void { + //trace('{FontReader.parseFontName} Not implemented.'); + + /* + var fn:FontName = new FontName(); + var bytes:ByteArray = tag.bytes; + bytes.position = tag.start; + fn.fontID = bytes.readUnsignedShort(); + fn.fontName = tag.readString(); + fn.fontCopyright = tag.readString(); + */ + } + + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/utilities/swf/SWFShapeReader.as b/Degrafa/com/degrafa/utilities/swf/SWFShapeReader.as new file mode 100644 index 0000000..58ab6d7 --- /dev/null +++ b/Degrafa/com/degrafa/utilities/swf/SWFShapeReader.as @@ -0,0 +1,383 @@ +package com.degrafa.utilities.swf +{ + import com.degrafa.utilities.SWFReader; + + import flash.geom.Point; + import flash.utils.ByteArray; + import flash.utils.Endian; + + public class SWFShapeReader + { + + private var shapeType:int; + + private var numFillBits:uint; + private var numLineBits:uint; + + private var fillStyles:Array; + private var lineStyles:Array; + + public var path:String; + + public var drawCommands:Array=[]; + + private var lastMovePoint:Point= new Point(0,0); + private var hasMove:Boolean; + + private var x:Number; + private var y:Number; + + private var xx:Number; + private var yy:Number; + + public static var RETURN_TYPE_PATH:int =0; + public static var RETURN_TYPE_DRAW_COMMAND_ARRAY:int =1; + + public function readShape(start:uint, end:uint, bytes:ByteArray,returnType:int=0):void { + + //trace('{ShapeReader.readShape} start = ' + start + '; end = ' + end); + + var bits:uint; + + bytes.position = start; + bytes.endian = Endian.LITTLE_ENDIAN; + + shapeType = 1; + fillStyles = []; + lineStyles = []; + + + // Read the number of fill/line bits + SWFReader.syncBits(); + + numFillBits = SWFReader.readUBits(4, bytes); + numLineBits = SWFReader.readUBits(4, bytes); + +// numFillBits = 0; +// numLineBits = 1; + + //trace('fill bits: ' + numFillBits + ', line bits: ' + numLineBits); + + if(returnType==RETURN_TYPE_PATH){ + path = ""; + + readShapeRecords(bytes); + + path += "Z"; + } + else if (returnType==RETURN_TYPE_DRAW_COMMAND_ARRAY){ + drawCommands.length=0; + readShapeRecords(bytes,returnType); + + //close the path + drawCommands.push({type:1,x:lastMovePoint.x,y:lastMovePoint.y}); + + } + } + + public function readShapeRecords(bytes:ByteArray,returnType:int=0):Array { + + var records:Array = []; + var record:Object; + + xx = yy = x = y = Number.NaN; + + do { + record = readOneShapeRecord(bytes,returnType); + if (record) { + records.push(record); + } + } + while (record); + + return records; + } + + private function readOneShapeRecord(bytes:ByteArray,returnType:int=0):Object { + + var record:Object = new Object(); + + var typeFlag:uint; + + // Style change record flags + var newStylesFlag:uint; + var lineStyleFlag:uint; + var fillStyle1Flag:uint; + var fillStyle0Flag:uint; + var moveToFlag:uint; + + // Edge record flags + var straightFlag:uint; + var generalLineFlag:int; + var vertLineFlag:int; + + var numBits:uint; + + var moveDeltaX:int; + var moveDeltaY:int; + var controlDeltaX:int; + var controlDeltaY:int; + var anchorDeltaX:int; + var anchorDeltaY:int; + var fillStyle0:int; + var fillStyle1:int; + var lineStyle:int; + + var fontBaseline:int = Number.NaN; + + typeFlag = SWFReader.readUBits(1, bytes); + + if (typeFlag == 0) { // Type of zero means it's a non-edge record + + newStylesFlag = SWFReader.readUBits(1, bytes); + lineStyleFlag = SWFReader.readUBits(1, bytes); + fillStyle1Flag = SWFReader.readUBits(1, bytes); + fillStyle0Flag = SWFReader.readUBits(1, bytes); + moveToFlag = SWFReader.readUBits(1, bytes); + + + if (newStylesFlag | lineStyleFlag | fillStyle1Flag | fillStyle0Flag | moveToFlag) { + + + // trace('[StyleChangeRecord] ' + newStylesFlag + ' ' + lineStyleFlag + ' ' + fillStyle1Flag + ' ' + fillStyle0Flag + ' ' + moveToFlag); + // If any of these flags are set, it's a StyleChangeRecord + + if (moveToFlag) { + numBits = SWFReader.readUBits(5, bytes); + + moveDeltaX = SWFReader.readSBits(numBits, bytes); + + moveDeltaY = SWFReader.readSBits(numBits, bytes); + + + if (isNaN(x) && isNaN(y)) { + + fontBaseline = 0; // 1024 - (moveDeltaY / 20); + + // trace('Baseline: ' + fontBaseline); + + x = moveDeltaX / 20; + y = moveDeltaY / 20 + fontBaseline; + + xx = moveDeltaX; + yy = moveDeltaY; + } + else { + x = moveDeltaX / 20; + y = moveDeltaY / 20; // + fontBaseline; + xx = moveDeltaX; + yy = moveDeltaY; + } + + if(returnType==RETURN_TYPE_PATH){ + path += "M" + (Math.abs(x) < 0.0001 ? 0 : x) + "," + (Math.abs(y) < 0.0001 ? 0 : y); + } + else{ + + //set the current point to close to + lastMovePoint.x = (Math.abs(x) < 0.0001 ? 0 : x); + lastMovePoint.y = (Math.abs(y) < 0.0001 ? 0 : y); + + //finally add the move + drawCommands.push({type:0, x:(Math.abs(x) < 0.0001 ? 0 : x),y:(Math.abs(y) < 0.0001 ? 0 : y)}); + } + + //trace('SSCR' + numBits + '\t' + moveDeltaX + '\t' + moveDeltaY + '\t\t\t\t\t\t' + xx + '\t' + yy); + + } + + + + if (fillStyle0Flag) { + fillStyle0 = SWFReader.readUBits(numFillBits, bytes); + } + + if (fillStyle1Flag) { + fillStyle1 = SWFReader.readUBits(numFillBits, bytes); + } + + if (lineStyleFlag) { + lineStyle = SWFReader.readUBits(numLineBits, bytes); + } + + + if (newStylesFlag && (shapeType == 2 || shapeType == 3)) { + fillStyles = readFillStyles(bytes); + lineStyles = readLineStyles(bytes); + SWFReader.syncBits(); + numFillBits = SWFReader.readUBits(4, bytes); + numLineBits = SWFReader.readUBits(4, bytes); + } + + + } + else { + + // trace('[EndShapeRecord]'); + + // It's an EndShapeRecord, return null. + return null; + } + + } + else { // Handle the edge records now. + + straightFlag = SWFReader.readUBits(1, bytes); + + numBits = SWFReader.readUBits(4, bytes) + 2; + + if (straightFlag) { // It's a straight edge + + generalLineFlag = SWFReader.readUBits(1, bytes); + + if (generalLineFlag == 0) { + vertLineFlag = SWFReader.readUBits(1, bytes); + } + + moveDeltaX = (generalLineFlag == 1 || vertLineFlag == 0) ? SWFReader.readSBits(numBits, bytes) : 0; + moveDeltaY = (generalLineFlag == 1 || vertLineFlag == 1) ? SWFReader.readSBits(numBits, bytes) : 0; + + xx += moveDeltaX; + yy += moveDeltaY; + + //trace('SER\t' + moveDeltaX + '\t' + moveDeltaY + '\t\t\t\t\t\t' + xx + '\t' + yy); + + x += moveDeltaX / 20; + y += moveDeltaY / 20 + fontBaseline; + + if(returnType==RETURN_TYPE_PATH){ + path += "L" + (Math.abs(x) < 0.0001 ? 0 : x) + "," + (Math.abs(y) < 0.0001 ? 0 : y); + } + else{ + drawCommands.push({type:1, x:(Math.abs(x) < 0.0001 ? 0 : x),y:(Math.abs(y) < 0.0001 ? 0 : y)}); + } + + + } + else { // It's a curved edge + + controlDeltaX = SWFReader.readSBits(numBits, bytes); + controlDeltaY = SWFReader.readSBits(numBits, bytes); + anchorDeltaX = SWFReader.readSBits(numBits, bytes); + anchorDeltaY = SWFReader.readSBits(numBits, bytes); + + var cx:Number = x + controlDeltaX / 20; + var cy:Number = y + controlDeltaY / 20 + fontBaseline; + var ax:Number = cx + anchorDeltaX / 20; + var ay:Number = cy + anchorDeltaY / 20 + fontBaseline; + + x = ax; + y = ay; + + xx += controlDeltaX + anchorDeltaX; + yy += controlDeltaY + anchorDeltaY; + + if(returnType==RETURN_TYPE_PATH){ + path += "Q" + (Math.abs(cx) < 0.0001 ? 0 : cx) + "," + (Math.abs(cy) < 0.0001 ? 0 : cy) + "," + (Math.abs(ax) < 0.0001 ? 0 : ax) + "," + (Math.abs(ay) < 0.0001 ? 0 : ay); + } + else{ + drawCommands.push({type:2, cx:(Math.abs(cx) < 0.0001 ? 0 : cx), + cy:(Math.abs(cy) < 0.0001 ? 0 : cy), + x1:(Math.abs(ax) < 0.0001 ? 0 : ax), + y1:(Math.abs(ay) < 0.0001 ? 0 : ay)}); + } + //trace('CER\t' + controlDeltaX + '\t' + controlDeltaY +'\t' + anchorDeltaX + '\t' + anchorDeltaY + '\t\t\t\t' + xx + '\t' + yy); + + + } + + + } + + return record; + } + + private function readFillStyles(bytes:ByteArray):Array { + var results:Array = []; + var count:uint = bytes.readUnsignedByte(); + + if ((shapeType == 2 || shapeType == 3) && count == 0xFF) { + count = bytes.readUnsignedShort(); + } + + while (count--) { + results.push(readOneFillStyle(bytes)); + } + + return results; + } + + private function readLineStyles(bytes:ByteArray):Array { + var results:Array = []; + var count:uint = bytes.readUnsignedByte(); + + if (count == 0xFF) { + count = bytes.readUnsignedShort(); + } + + while (count--) { + if (shapeType < 4) { + results.push(readOneLineStyle(bytes)); + } + else { + throw new Error('ouch panic'); +// results.push(readOneLineStyle2(bytes)); + } + } + + return results; + } + + private function readOneFillStyle(bytes:ByteArray):void { + + var type:uint = bytes.readUnsignedByte(); + var red:uint, green:uint, blue:uint, alpha:uint; + + switch(type) { + case 0x00: // Solid Fill + case 0x10: // Linear Gradient Fill + case 0x12: // Radial Gradient Fill + case 0x13: // Focal Gradient Fill + case 0x40: // Repeating Bitmap Fill + case 0x41: // Clipped Bitmap Fill + case 0x42: // Non-Smoothed Repeating Bitmap + case 0x43: // Non-Smoothed Clipped Bitmap + break; + default: + //trace('Invalid Fill Style...'); + break; + } + + if (type == 0x00) { + + red = bytes.readUnsignedByte(); + green = bytes.readUnsignedByte(); + blue = bytes.readUnsignedByte(); + + if (shapeType == 3) { + alpha = bytes.readUnsignedByte(); + } + + } + + if (type > 0) { + throw new Error('ouch panic'); + } + + } + + private function readOneLineStyle(bytes:ByteArray):void { + + var width:uint = bytes.readUnsignedShort(); + var red:uint = bytes.readUnsignedByte(); + var green:uint = bytes.readUnsignedByte(); + var blue:uint = bytes.readUnsignedByte(); + + if (shapeType == 3) { + var alpha:uint = bytes.readUnsignedByte(); + } + + } + + } +} diff --git a/Degrafa/com/degrafa/utilities/swf/SWFTag.as b/Degrafa/com/degrafa/utilities/swf/SWFTag.as new file mode 100644 index 0000000..6e27daa --- /dev/null +++ b/Degrafa/com/degrafa/utilities/swf/SWFTag.as @@ -0,0 +1,198 @@ +package com.degrafa.utilities.swf +{ + import flash.utils.ByteArray; + + public class SWFTag + { + + public static const END:String = "End"; + public static const SHOW_FRAME:String = "ShowFrame"; + public static const DEFINE_SHAPE:String = "DefineShape"; + public static const PLACE_OBJECT:String = "PlaceObject"; + public static const REMOVE_OBJECT:String = "RemoveObject"; + public static const DEFINE_BITS:String = "DefineBits"; + public static const DEFINE_BUTTON:String = "DefineButton"; + public static const JPEG_TABLES:String = "JPEGTables"; + public static const SET_BACKGROUND_COLOR:String = "SetBackgroundColor"; + public static const DEFINE_FONT:String = "DefineFont"; + public static const DEFINE_TEXT:String = "DefineText"; + public static const DO_ACTION:String = "DoAction"; + public static const DEFINE_FONT_INFO:String = "DefineFontInfo"; + public static const DEFINE_SOUND:String = "DefineSound"; + public static const START_SOUND:String = "StartSound"; + public static const DEFINE_BUTTON_SOUND:String = "DefineButtonSound"; + public static const SOUND_STREAM_HEAD:String = "SoundStreamHead"; + public static const SOUND_STREAM_BLOCK:String = "SoundStreamBlock"; + public static const DEFINE_BITS_LOSSLESS:String = "DefineBitsLossless"; + public static const DEFINE_BITS_JPEG_2:String = "DefineBitsJPEG2"; + public static const DEFINE_SHAPE_2:String = "DefineShape2"; + public static const DEFINE_BUTTON_CX_FORM:String = "DefineButtonCxForm"; + public static const PROTECT:String = "Protect"; + public static const PLACE_OBJECT_2:String = "PlaceObject2"; + public static const REMOVE_OBJECT_2:String = "RemoveObject2"; + public static const DEFINE_SHAPE_3:String = "DefineShape3"; + public static const DEFINE_TEXT_2:String = "DefineText2"; + public static const DEFINE_BUTTON_2:String = "DefineButton2"; + public static const DEFINE_BITS_JPEG_3:String = "DefineBitsJPEG3"; + public static const DEFINE_BITS_LOSSLESS_2:String = "DefineBitsLossless2"; + public static const DEFINE_EDIT_TEXT:String = "DefineEditText"; + public static const DEFINE_SPRITE:String = "DefineSprite"; + public static const FRAME_LABEL:String = "FrameLabel"; + public static const SOUND_STREAM_HEAD_2:String = "SoundStreamHead2"; + public static const DEFINE_MORPH_SHAPE:String = "DefineMorphShape"; + public static const DEFINE_FONT_2:String = "DefineFont2"; + public static const EXPORT_ASSETS:String = "ExportAssets"; + public static const IMPORT_ASSETS:String = "ImportAssets"; + public static const ENABLE_DEBUGGER:String = "EnableDebugger"; + public static const DO_INIT_ACTION:String = "DoInitAction"; + public static const DEFINE_VIDEO_STREAM:String = "DefineVideoStream"; + public static const VIDEO_FRAME:String = "VideoFrame"; + public static const DEFINE_FONT_INFO_2:String = "DefineFontInfo2"; + public static const ENABLE_DEBUGGER_2:String = "EnableDebugger2"; + public static const SCRIPT_LIMITS:String = "ScriptLimits"; + public static const SET_TAB_INDEX:String = "SetTabIndex"; + public static const FILE_ATTRIBUTES:String = "FileAttributes"; + public static const PLACE_OBJECT_3:String = "PlaceObject3"; + public static const IMPORT_ASSETS_2:String = "ImportAssets2"; + public static const DEFINE_FONT_ALIGN_ZONES:String = "DefineFontAlignZones"; + public static const CSM_TEXT_SETTINGS:String = "CSMTextSettings"; + public static const DEFINE_FONT_3:String = "DefineFont3"; + public static const SYMBOL_CLASS:String = "SymbolClass"; + public static const METADATA:String = "Metadata"; + public static const DEFINE_SCALING_GRID:String = "DefineScalingGrid"; + public static const DO_ABC:String = "DoABC"; + public static const DEFINE_SHAPE_4:String = "DefineShape4"; + public static const DEFINE_MORPH_SHAPE_2:String = "DefineMorphShape2"; + public static const DEFINE_SCENE_AND_FRAME_LABEL_DATA:String = "DefineSceneAndFrameLabelData"; + public static const DEFINE_BINARY_DATA:String = "DefineBinaryData"; + public static const DEFINE_FONT_NAME:String = "DefineFontName"; + public static const START_SOUND_2:String = "StartSound2"; + + public static const UNKNOWN:String = 'Unknown'; + + public static const TAG_NAMES_BY_VALUE:Array = [ + + /* 0 */ END, + /* 1 */ SHOW_FRAME, + /* 2 */ DEFINE_SHAPE, + /* 3 */ UNKNOWN, + /* 4 */ PLACE_OBJECT, + /* 5 */ REMOVE_OBJECT, + /* 6 */ DEFINE_BITS, + /* 7 */ DEFINE_BUTTON, + /* 8 */ JPEG_TABLES, + /* 9 */ SET_BACKGROUND_COLOR, + /* 10 */ DEFINE_FONT, + + /* 11 */ DEFINE_TEXT, + /* 12 */ DO_ACTION, + /* 13 */ DEFINE_FONT_INFO, + /* 14 */ DEFINE_SOUND, + /* 15 */ START_SOUND, + /* 16 */ UNKNOWN, + /* 17 */ DEFINE_BUTTON_SOUND, + /* 18 */ SOUND_STREAM_HEAD, + /* 19 */ SOUND_STREAM_BLOCK, + /* 20 */ DEFINE_BITS_LOSSLESS, + + /* 21 */ DEFINE_BITS_JPEG_2, + /* 22 */ DEFINE_SHAPE_2, + /* 23 */ DEFINE_BUTTON_CX_FORM, + /* 24 */ PROTECT, + /* 25 */ UNKNOWN, + /* 26 */ PLACE_OBJECT_2, + /* 27 */ UNKNOWN, + /* 28 */ REMOVE_OBJECT_2, + /* 29 */ UNKNOWN, + /* 30 */ UNKNOWN, + + /* 31 */ UNKNOWN, + /* 32 */ DEFINE_SHAPE_3, + /* 33 */ DEFINE_TEXT_2, + /* 34 */ DEFINE_BUTTON_2, + /* 35 */ DEFINE_BITS_JPEG_3, + /* 36 */ DEFINE_BITS_LOSSLESS_2, + /* 37 */ DEFINE_EDIT_TEXT, + /* 38 */ UNKNOWN, + /* 39 */ DEFINE_SPRITE, + /* 40 */ UNKNOWN, + + /* 41 */ UNKNOWN, + /* 42 */ UNKNOWN, + /* 43 */ FRAME_LABEL, + /* 44 */ UNKNOWN, + /* 45 */ SOUND_STREAM_HEAD_2, + /* 46 */ DEFINE_MORPH_SHAPE, + /* 47 */ UNKNOWN, + /* 48 */ DEFINE_FONT_2, + /* 49 */ UNKNOWN, + /* 50 */ UNKNOWN, + + /* 51 */ UNKNOWN, + /* 52 */ UNKNOWN, + /* 53 */ UNKNOWN, + /* 54 */ UNKNOWN, + /* 55 */ UNKNOWN, + /* 56 */ EXPORT_ASSETS, + /* 57 */ IMPORT_ASSETS, + /* 58 */ ENABLE_DEBUGGER, + /* 59 */ DO_INIT_ACTION, + /* 60 */ DEFINE_VIDEO_STREAM, + + /* 61 */ VIDEO_FRAME, + /* 62 */ DEFINE_FONT_INFO_2, + /* 63 */ UNKNOWN, + /* 64 */ ENABLE_DEBUGGER_2, + /* 65 */ SCRIPT_LIMITS, + /* 66 */ SET_TAB_INDEX, + /* 67 */ UNKNOWN, + /* 68 */ UNKNOWN, + /* 69 */ FILE_ATTRIBUTES, + /* 70 */ PLACE_OBJECT_3, + + /* 71 */ IMPORT_ASSETS_2, + /* 72 */ UNKNOWN, + /* 73 */ DEFINE_FONT_ALIGN_ZONES, + /* 74 */ CSM_TEXT_SETTINGS, + /* 75 */ DEFINE_FONT_3, + /* 76 */ SYMBOL_CLASS, + /* 77 */ METADATA, + /* 78 */ DEFINE_SCALING_GRID, + /* 79 */ UNKNOWN, + /* 80 */ UNKNOWN, + /* 81 */ UNKNOWN, + /* 82 */ DO_ABC, + /* 83 */ DEFINE_SHAPE_4, + /* 84 */ DEFINE_MORPH_SHAPE_2, + /* 85 */ UNKNOWN, + /* 86 */ DEFINE_SCENE_AND_FRAME_LABEL_DATA, + /* 87 */ DEFINE_BINARY_DATA, + /* 88 */ DEFINE_FONT_NAME, + /* 89 */ START_SOUND_2 + + ]; + + public var type: int; + public var start: int; + public var length: int; + public var bytes:ByteArray; + + public function toString():String { + return '[SWFTag ' + TAG_NAMES_BY_VALUE[type] + '(' + type + ') ' + length + ' bytes]'; + } + + public function readString():String { + var pointer:int = bytes.position; + while (pointer < bytes.length) { + if (bytes[pointer] == 0) { + break; + } + pointer++; + } + if (pointer >= bytes.length) { + throw new Error('{SWFTag.readString} Out of Bounds'); + } + return bytes.readUTFBytes(pointer - bytes.position); + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/utilities/swf/fonts/FontGlyph.as b/Degrafa/com/degrafa/utilities/swf/fonts/FontGlyph.as new file mode 100644 index 0000000..653b153 --- /dev/null +++ b/Degrafa/com/degrafa/utilities/swf/fonts/FontGlyph.as @@ -0,0 +1,12 @@ +package com.degrafa.utilities.swf.fonts +{ + public class FontGlyph + { + + public var codePoint:uint; + public var shapeOffset:uint; + public var shapeOffsetEnd:uint; + public var fontSet:FontSet; + public var index:uint; + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/utilities/swf/fonts/FontName.as b/Degrafa/com/degrafa/utilities/swf/fonts/FontName.as new file mode 100644 index 0000000..3cf6866 --- /dev/null +++ b/Degrafa/com/degrafa/utilities/swf/fonts/FontName.as @@ -0,0 +1,13 @@ +package com.degrafa.utilities.swf.fonts +{ + public class FontName + { + public var fontID:uint; + public var fontName:String; + public var fontCopyright:String; + + public function toString():String { + return '[FontName {id: ' + fontID + ', name: "' + fontName + '", copyright: "' + fontCopyright + '"}]'; + } + } +} \ No newline at end of file diff --git a/Degrafa/com/degrafa/utilities/swf/fonts/FontSet.as b/Degrafa/com/degrafa/utilities/swf/fonts/FontSet.as new file mode 100644 index 0000000..090f418 --- /dev/null +++ b/Degrafa/com/degrafa/utilities/swf/fonts/FontSet.as @@ -0,0 +1,186 @@ +package com.degrafa.utilities.swf.fonts +{ + import com.degrafa.utilities.swf.SWFTag; + + import flash.utils.ByteArray; + import flash.utils.Dictionary; + + public class FontSet + { + + public static var excludeEmptyGlyphs:Boolean = true; + + public var tag:SWFTag; + + public var fontID:uint; + + public var hasLayout:Boolean; + public var shiftJIS:Boolean; + public var smallText:Boolean; + public var ansi:Boolean; + public var wideOffsets:Boolean; + public var wideCodes:Boolean; + public var italic:Boolean; + public var bold:Boolean; + + public var languageCode:uint; + + public var fontName:String; + + public var numGlyphs:uint; + + public var offsetTableOffset:uint; + + public var offsetTable:Array; + + public var codeTableOffset:uint; + + public var glyphShapeTable:Array; + + public var fontAscent:int; + public var fontDescent:int; + public var fontLeading:int; + public var kerningCount:uint; + + private var _children:Array; + public function get children():Array { + if (!_children) { + generateGlyphMap(); + } + + + return _children; + } + + private var _glyphs:Dictionary; + public function get glyphs():Dictionary { + + if (!_glyphs) { + generateGlyphMap(); + } + + return _glyphs; + } + + private function generateGlyphMap():void { + + var bytes:ByteArray = tag.bytes; + + _children = []; + _glyphs = new Dictionary(); + + for (var i:int = 0; i < numGlyphs; i++) { + var glyph:FontGlyph = new FontGlyph(); + bytes.position = offsetTableOffset + codeTableOffset + i * 2; + glyph.codePoint = bytes.readUnsignedShort(); + glyph.shapeOffset = offsetTableOffset + offsetTable[i]; + + if (i < (numGlyphs - 1)) { + glyph.shapeOffsetEnd = offsetTableOffset + offsetTable[i + 1]; + } + else { + glyph.shapeOffsetEnd = codeTableOffset - 1; + } + + glyph.fontSet = this; + glyph.index = i; + + if (excludeEmptyGlyphs && ((glyph.shapeOffset + 2) == glyph.shapeOffsetEnd)) { + //trace('empty glyph skipped...'); + } + else { + _glyphs[glyph.codePoint] = glyph; + _children.push(glyph); + } + } + } + +/* + public function getShapeFromCharCode(charCode:uint):void { + + var sr:ShapeReader = new ShapeReader(); + + var offset:int = getShapeOffsetFromCharCode(charCode); + + if (offset != -1) { + sr.readShape(offset, tag.bytes); + } + else { + // TODO: No shape, warn developer. + } + + } + + private function getShapeOffsetFromCharCode(charCode:uint):int { + + var bytes:ByteArray = tag.bytes; + var oldPos:uint = bytes.position; + var oldEndian:String = bytes.endian; + var i:int, len:int = numGlyphs; + var notFound:Boolean = true; + var offset:uint; + + + for (i = 0; i < numGlyphs; i++) { + if (charCode == bytes.readUnsignedShort()) { + notFound = false; + break; + } + } + + if (notFound) { return -1; } + + bytes.position = offsetTableOffset + (i * (wideOffsets ? 4 : 2)); + offset = wideOffsets ? bytes.readUnsignedInt() : bytes.readUnsignedShort(); + + bytes.position = oldPos; + bytes.endian = oldEndian; + + trace('wideOffsets: ' + wideOffsets); + trace('inner offset: ' + offset); + trace('tag start: ' + tag.start); + trace('table offset: ' + offsetTableOffset); + + return offsetTableOffset + offset; + } + + + +*/ +/* + public function dumpCodeTable():void { + + var bytes:ByteArray = tag.bytes; + + var oldPos:uint = bytes.position; + var oldEndian:String = bytes.endian; + + var i:int, len:int = numGlyphs; + var codePoint:uint; + var codes:Array = []; + var chars:Array = []; + + bytes.position = offsetTableOffset + codeTableOffset; + bytes.endian = Endian.LITTLE_ENDIAN; + + for (i = 0; i < numGlyphs; i++) { + codePoint = bytes.readUnsignedShort(); + codes.push('U+' + codePoint.toString(16).toUpperCase()); + if (codePoint == 0 || codePoint == 10 || codePoint == 13) { + chars.push('?'); + } + else { + chars.push(String.fromCharCode(codePoint)); + } + } + trace('Glyph Count: ' + len); + trace(' Code Count: ' + codes.length); + trace('Codes: ' + codes.join(', ')); + trace('Chars: ' + chars.join(', ')); + + bytes.position = oldPos; + bytes.endian = oldEndian; + } + */ + } +} diff --git a/Degrafa/manifest.xml b/Degrafa/manifest.xml new file mode 100644 index 0000000..0ddca30 --- /dev/null +++ b/Degrafa/manifest.xml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file