[Dojo-checkins] elazutkin - r11343 - dojox/trunk/gfx

dojo-checkins-admin at dojotoolkit.org dojo-checkins-admin at dojotoolkit.org
Fri Nov 2 19:49:23 UTC 2007


Author: elazutkin
Date: Fri Nov  2 19:49:22 2007
New Revision: 11343

Added:
   dojox/trunk/gfx/canvas.js   (contents, props changed)
Log:
gfx: the very first cut of the Canvas renderer courtesy of Chris Mitchell (IBM's CCLA on file).

Added: dojox/trunk/gfx/canvas.js
==============================================================================
--- (empty file)
+++ dojox/trunk/gfx/canvas.js	Fri Nov  2 19:49:22 2007
@@ -0,0 +1,1336 @@
+dojo.provide("dojox.gfx.canvas");
+
+dojo.require("dojox.gfx._base");
+dojo.require("dojox.gfx.shape");
+dojo.require("dojox.gfx.path");
+
+dojo.experimental("dojox.gfx.canvas");
+
+dojox.gfx.canvas.xmlns = {
+	xlink: "http://www.w3.org/1999/xlink",
+	svg:   "http://www.w3.org/2000/svg"
+};
+
+dojox.gfx.canvas.getRef = function(name){
+	// summary: returns a DOM Node specified by the name argument or null
+	// name: String: an SVG external reference
+//TODO: Determine if this is really necessary for html canvas implementation
+	if(!name || name == "none"){return null;}
+	if(name.match(/^url\(#.+\)$/)){
+		return dojo.byId(name.slice(5, -1));	// Node
+	}
+	// alternative representation of a reference
+	if(name.match(/^#dojoUnique\d+$/)){
+		// we assume here that a reference was generated by dojox.gfx
+		return dojo.byId(name.slice(1));	// Node
+	}
+	return null;	// Node
+};
+
+//Common constants
+dojox.gfx.canvas.pi2 = Math.PI / 2;
+dojox.gfx.canvas.pi4 = Math.PI / 4;
+dojox.gfx.canvas.pi8 = Math.PI / 8;
+dojox.gfx.canvas.two_pi = Math.PI * 2;
+dojox.gfx.canvas.kappa = 4 * ((Math.sqrt(2) -1) / 3);
+
+dojox.gfx.canvas.dasharray = {
+	shortdash:			"ShortDash",
+	shortdot:			"ShortDot",
+	shortdashdot:		"ShortDashDot",
+	shortdashdotdot:	"ShortDashDotDot",
+	dot:				"Dot",
+	dash:				"Dash",
+	longdash:			"LongDash",
+	dashdot:			"DashDot",
+	longdashdot:		"LongDashDot",
+	longdashdotdot:		"LongDashDotDot"
+};
+
+dojox.gfx.canvas.shapeExt = {
+	// summary: HTML Canvas-specific implementation of dojox.gfx.Shape methods
+	_getGC: function(){
+		return this.parent.ctx;
+	},
+	setFill: function(fill){
+		// summary: sets a fill into the current graphics context
+		// fill: Object: a fill object
+		//	(see dojox.gfx.defaultLinearGradient, 
+		//	dojox.gfx.defaultRadialGradient, 
+		//	dojox.gfx.defaultPattern, 
+		//	or dojo.Color)
+		if(!fill){
+			// fill=none
+			this.fillStyle = null;
+			return this;
+		}
+		if(typeof(fill) == "object" && "type" in fill){
+			if (fill.type=="linear"){
+				this.fillStyle = dojox.gfx.makeParameters(dojox.gfx.defaultLinearGradient, fill);
+			}
+			if (fill.type=="radial"){
+				this.fillStyle = dojox.gfx.makeParameters(dojox.gfx.defaultRadialGradient, fill);
+			}
+			if (fill.type=="pattern"){
+				this.fillStyle = dojox.gfx.makeParameters(dojox.gfx.defaultPattern, fill);
+				if(!this.fillStyle.src){return this;} 
+				dojox.gfx.canvas.imageLoader.loadImage(this.fillStyle.src);
+			}
+			return this;
+		}
+		// Use a color object as fill
+		var f = dojox.gfx.normalizeColor(fill);
+		this.fillStyle = f;
+		return this;	// self
+	},
+	_fill: function(){
+		var ctx = this._getGC();
+		if(!this.fillStyle){
+			// fill=none
+			this.parent.ctx.fillStyle = "rgba(0,0,0,0.0)";
+			return;
+		}
+		if(typeof(this.fillStyle) == "object" && "type" in this.fillStyle){
+				// gradient
+				// TODO: Fix special fill styles for canvas
+			var f = null;
+			with (this.fillStyle){
+				switch(this.fillStyle.type){
+					case "linear":
+						f = this._getGC().createLinearGradient(x1,y1,x2,y2);
+						for (var csi=0;csi<colors.length;csi++){
+							f.addColorStop(colors[csi].offset,this._normalizeColor(colors[csi].color).toString());
+						}
+						break;
+					case "radial":
+						f = this._getGC().createRadialGradient(cx,cy,0,cx,cy,r);
+						for (var csi=0;csi<colors.length;csi++){
+							f.addColorStop(colors[csi].offset,this._normalizeColor(colors[csi].color).toString());
+						}
+						break;
+					case "pattern":
+						if (this.fillStyle.src){
+							this.fillStyle.img=dojox.gfx.canvas.imageLoader.getImage(this.fillStyle.src);
+							if(!this.fillStyle.img){return this;} 
+							f=this._getGC().createPattern(this.fillStyle.img,"repeat");
+							// TODO: other repeat styles are possible (no-repeat,repeat-x,repeat-y). 
+						}
+				}
+			}
+			ctx.fillStyle=f;
+		} else {
+			// Set fill color using CSS RGBA func style
+			ctx.fillStyle=this.fillStyle.toString();
+		}
+	},
+	_normalizeColor: function(color){
+		var c = dojox.gfx.normalizeColor(color);
+		if (isNaN(c.r)){c.r = 0;}
+		if (isNaN(c.g)){c.g = 0;}
+		if (isNaN(c.b)){c.b = 0;}
+		if (isNaN(c.a)){c.a = 1;}
+		return c;
+	},
+	setStroke: function(stroke){
+		// summary: sets a stroke to use for the shape. 
+		// stroke: Object: a stroke object
+		//	(see dojox.gfx.defaultStroke) 
+		if(!stroke){
+			// don't stroke
+			this.strokeStyle = null;
+			return this;
+		}
+		// normalize the stroke
+		if(typeof stroke == "string"){
+			stroke = {color: stroke};
+		}
+		var s = this.strokeStyle;
+		s = dojox.gfx.makeParameters(dojox.gfx.defaultStroke, stroke);
+		s.color = dojox.gfx.normalizeColor(s.color);
+		// generate attributes
+		if(s){
+			this.strokeStyle = s;
+			var da = s.style.toLowerCase();
+			if(da in dojox.gfx.canvas.dasharray){
+				this.strokeStyle.daurl = dojo.moduleUrl("dojox","gfx/images/" + dojox.gfx.canvas.dasharray[da] + ".JPG");
+				dojox.gfx.canvas.imageLoader.loadImage(this.strokeStyle.daurl);
+			}
+		}
+		return this;	// self
+	},
+	_stroke: function(){
+		// summary: Applies the stroke into current graphics context 
+		// stroke: Object: a stroke object
+		//	(see dojox.gfx.defaultStroke) 
+		var ctx = this._getGC();
+		if(!this.strokeStyle){
+			// no stroke
+			ctx.strokeStyle="rgba(0,0,0,0.0)";
+			ctx.stroke();
+			return;
+		}
+		var s = this.strokeStyle;
+		if (!s.daurl){
+			ctx.strokeStyle=s.color.toString();
+		}else{
+			this.strokeStyle.img=dojox.gfx.canvas.imageLoader.getImage(this.strokeStyle.daurl);
+			if(!this.strokeStyle.img){return this;}
+			dash = ctx.createPattern(this.strokeStyle.img,"repeat");
+			ctx.strokeStyle=dash;
+		}
+		ctx.lineWidth = s.width;
+		ctx.lineCap = s.cap;
+		if(typeof s.join == "number"){
+			ctx.lineJoin = "miter";
+			ctx.miterLimit = s.join;
+		}else{
+			ctx.lineJoin = s.join;
+		}
+		ctx.stroke();
+	},
+	_getParentSurface: function(){
+		var surface = this.parent;
+		for(; surface && !(surface instanceof dojox.gfx.Surface); surface = surface.parent);
+		return surface;
+	},
+	setRawNode: function(rawNode){
+		/* Unnecessary for most canvas scene graph shapes (other than Surface and Group for placeholder divs)
+	},
+
+	setShape: function(newShape){
+		/* Unnecessary for canvas scene graph
+		// summary: sets a shape object (SVG)
+		// newShape: Object: a shape object
+		//	(see dojox.gfx.defaultPath,
+		//	dojox.gfx.defaultPolyline,
+		//	dojox.gfx.defaultRect,
+		//	dojox.gfx.defaultEllipse,
+		//	dojox.gfx.defaultCircle,
+		//	dojox.gfx.defaultLine,
+		//	or dojox.gfx.defaultImage)
+		this.shape = dojox.gfx.makeParameters(this.shape, newShape);
+		for(var i in this.shape){
+			if(i != "type"){ this.rawNode.setAttribute(i, this.shape[i]); }
+		}
+		return this;	// self
+	*/
+	},
+	// move family
+	_moveToFront: function(){
+		// summary: moves a shape to front of its parent (surface or group's) list of shapes
+		//this.parent.appendChild(this.rawNode);
+		return this;	// self
+	},
+	_moveToBack: function(){
+		// summary: moves a shape to back of its parent's list of shapes 
+		//this.parent.insertBefore(this.rawNode, this.rawNode.parentNode.firstChild);
+		return this;	// self
+	},
+	// transformations
+	_getRealMatrix: function(){
+		var m = this.matrix;
+		var p = this.parent;
+		while(p){
+			if(p.matrix){
+				m = dojox.gfx.matrix.multiply(p.matrix, m);
+			}
+			p = p.parent;
+		}
+		return m;
+	},
+	_applyTransform: function() {
+		// NOOP'd - render is separate stage for canvas.
+		// _transform is used instead.
+		return this;
+	},
+	
+	_transform: function() {
+		var tm = this._getRealMatrix();
+		var ctx = this._getGC();
+		if(tm){
+			ctx.translate(tm.dx,tm.dy); 
+			ctx.rotate(tm.yx);
+//			ctx.scale(tm.xx,tm.yy);
+//			console.debug("trans("+tm.dx+","+tm.dy+") rot("+tm.yx+") scale("+tm.xx+","+tm.yy+") ");
+		}else{
+			ctx.translate(0,0);
+			ctx.rotate(0);
+			ctx.scale(1);
+		}
+		// TODO: Scaling
+		// TODO: Skewing
+	}
+}
+dojo.extend(dojox.gfx.Shape, dojox.gfx.canvas.shapeExt);
+
+dojo.declare("dojox.gfx.Group", dojox.gfx.Shape, {
+	// summary: a group shape (a Composite), which can be used 
+	//	to logically group shapes (e.g, to propagate matricies)
+	constructor: function(){
+		dojox.gfx.canvas.Container._init.call(this);
+	},
+	// apply transformation
+	_applyTransform: function(){
+		// summary: applies a transformation matrix to a group
+		var matrix = this._getRealMatrix();
+		for(var i = 0; i < this.children.length; ++i){
+			this.children[i]._updateParentMatrix(matrix);
+		}
+		return this;	// self
+	},
+	setShape: function(newShape){
+		return this.setTransform(this.matrix);	// self
+		// NOOP--groups have no shape.
+	},
+	setRawNode: function(rawNode){
+		// summary:
+		//	assigns and clears the underlying node that will represent this
+		//	shape. Once set, transforms, gradients, etc, can be applied.
+		this.rawNode = rawNode;
+	},
+	_getGC: function(){
+		return this.ctx;
+	},
+	render: function(){
+		for (var i=0;i<this.children.length;i++){
+			this.children[i].render();
+		}
+	}
+});
+dojox.gfx.Group.nodeType = "group";
+
+dojo.declare("dojox.gfx.Rect", dojox.gfx.shape.Rect, {
+	// summary: a rectangle shape 
+	setShape: function(newShape){
+		// summary: sets a rectangle shape object (SVG)
+		// newShape: Object: a rectangle shape object
+		//this.shape = dojox.gfx.makeParameters(this.shape, newShape);
+		this.bbox = null;
+		this.shape = dojo.clone(dojox.gfx.defaultRect);
+		for(var i in newShape){
+			this.shape[i]=newShape[i];
+		}
+		return this.setTransform(this.matrix);	// self
+	},
+	render: function(){
+		var ctx = this._getGC();
+		if (!this.shape.r || this.shape.r==0){
+			//Draw the rect without rounded corners the hard way (to allow skew);
+			ctx.save(); 
+			this._transform();
+			ctx.beginPath();
+			this._fill();
+		 	ctx.translate(this.shape.x,this.shape.y);
+		 	ctx.moveTo(0,0);
+		 	ctx.lineTo(this.shape.width,0);
+		 	ctx.lineTo(this.shape.width,this.shape.height);
+		 	ctx.lineTo(0,this.shape.height);
+		 	ctx.lineTo(0,0);
+		 	ctx.lineTo(this.shape.width,0);
+			ctx.fill();
+			this._stroke();
+			ctx.closePath();
+			ctx.restore(); 
+		}else{
+			//Draw the rect with rounded corners the hard way.
+			ctx.save(); // save graphics context prior to rendering shape
+			this._transform();
+			ctx.beginPath();
+			this._fill();
+		 	ctx.translate(this.shape.x,this.shape.y);
+		 	ctx.moveTo(this.shape.r,0);
+		 	ctx.arc(this.shape.width-this.shape.r,this.shape.r,this.shape.r, -dojox.gfx.canvas.pi2, 0, false);
+		 	ctx.arc(this.shape.width-this.shape.r,this.shape.height-this.shape.r,this.shape.r, 0, dojox.gfx.canvas.pi2, false);
+		 	ctx.arc(this.shape.r,this.shape.height-this.shape.r,this.shape.r, dojox.gfx.canvas.pi2, Math.PI, false);
+		 	ctx.arc(this.shape.r,this.shape.r,this.shape.r, Math.PI, dojox.gfx.canvas.pi2, false);
+			ctx.fill();
+			this._stroke();
+			ctx.closePath();
+			ctx.restore(); // return graphics context after rendering shape
+		}
+	}
+});
+dojox.gfx.Rect.nodeType = "rect";
+
+dojo.declare("dojox.gfx.Ellipse", dojox.gfx.shape.Ellipse, {
+	setShape: function(newShape){
+		// summary: sets a ellipse shape object 
+		// newShape: Object: an ellipse shape object
+		this.shape = dojox.gfx.makeParameters(dojox.gfx.defaultEllipse, newShape);
+		return this.setTransform(this.matrix);	// self
+	},
+	render: function(){
+		console.debug("========= ellipse ===========");
+		console.debug("cx,cy=("+this.shape.cx+","+this.shape.cy+")");
+		console.debug("rx="+this.shape.rx+" ry="+this.shape.ry);
+		var ctx=this._getGC();
+		ctx.save(); 
+		this._transform();
+		ctx.beginPath();
+		this._fill();
+	 	ctx.beginPath();
+	 	var k = dojox.gfx.canvas.kappa;
+	 	with (this.shape){
+		 	ctx.moveTo(cx,cy-ry);
+		 	ctx.bezierCurveTo(cx+(k*rx),cy-ry,cx+rx,cy-(k*ry),cx+rx,cy);
+		 	ctx.bezierCurveTo(cx+rx,cy+(k*ry),cx+(k*rx),cy+ry,cx,cy+ry);
+		 	ctx.bezierCurveTo(cx-(k*rx),cy+ry,cx-rx,cy+(k*ry),cx-rx,cy);
+		 	ctx.bezierCurveTo(cx-rx,cy-(k*ry),cx-(k*rx),cy-ry,cx,cy-ry);
+	 	}
+		ctx.fill();
+		this._stroke();
+		ctx.closePath();
+		ctx.restore(); 
+	},
+});
+dojox.gfx.Ellipse.nodeType = "ellipse";
+
+dojo.declare("dojox.gfx.Circle", dojox.gfx.shape.Circle, {
+	setShape: function(newShape){
+		// summary: sets a circle shape object (SVG)
+		// newShape: Object: a circle shape object
+		this.shape = dojo.clone(newShape);
+		return this.setTransform(this.matrix);	// self
+	},
+	render: function(){
+		var ctx=this._getGC();
+		ctx.save(); // save graphics context prior to rendering shape
+		this._transform();
+		ctx.beginPath();
+	 	ctx.arc(this.shape.cx,this.shape.cy,this.shape.r,0,dojox.gfx.canvas.two_pi,true);
+		this._fill();
+		ctx.fill();
+		this._stroke();
+		ctx.closePath();
+		ctx.restore(); // return graphics context after rendering shape
+	}
+});
+dojox.gfx.Circle.nodeType = "circle";
+
+dojo.declare("dojox.gfx.Line", dojox.gfx.shape.Line, {
+	setShape: function(newShape){
+		// summary: sets a text shape object, given shape properties
+		// newShape: Object: a text shape object containing props to set
+		this.shape = dojo.clone(newShape);
+		return this.setTransform(this.matrix);	// self
+	},
+	render: function(){
+		var ctx=this._getGC();
+		ctx.save(); // save graphics context prior to rendering shape
+		this._transform();
+		ctx.beginPath();
+	 	ctx.moveTo(this.shape.x1,this.shape.y1);
+	 	ctx.lineTo(this.shape.x2,this.shape.y2);
+		this._fill();
+		ctx.fill();
+		this._stroke();
+		ctx.closePath();
+		ctx.restore(); // return graphics context after rendering shape
+	}
+});
+dojox.gfx.Line.nodeType = "line";
+
+dojo.declare("dojox.gfx.Polyline", dojox.gfx.shape.Polyline, {
+	// summary: a polyline/polygon shape 
+	setShape: function(points, closed){
+		// summary: sets a polyline/polygon shape object 
+		// points: Object: a polyline/polygon shape object
+		if(points && points instanceof Array){
+			// branch
+			// points: Array: an array of points
+			this.shape = dojox.gfx.makeParameters(this.shape, { points: points });
+			if(closed && this.shape.points.length){ 
+				this.shape.points.push(this.shape.points[0]);
+			}
+		}else{
+			this.shape = dojox.gfx.makeParameters(this.shape, points);
+		}
+		this.box = null;
+		var attr = [];
+		var p = this.shape.points;
+		for(var i = 0; i < p.length; ++i){
+			if(typeof p[i] == "number"){
+				attr.push(p[i].toFixed(8));
+			}else{
+				attr.push(p[i].x.toFixed(8));
+				attr.push(p[i].y.toFixed(8));
+			}
+		}
+//		this.rawNode.setAttribute("points", attr.join(" "));
+		return this.setTransform(this.matrix);	// self
+	},
+	render: function(){
+		var ctx=this._getGC();
+		if (this.shape.points.length==0){return;}
+		ctx.save(); // save graphics context prior to rendering shape
+		this._transform();
+		ctx.beginPath();
+		this._fill();
+		if (this.shape.points[0] instanceof Object){
+			// points is array of points [{x:x0,y:y0},...,{x:xN,y:yN}]
+			ctx.moveTo(this.shape.points[0].x,this.shape.points[0].y);
+			for (var i=1; i<this.shape.points.length; ++i){
+				ctx.lineTo(this.shape.points[i].x,this.shape.points[i].y);
+			}
+		} else {
+			// points is array of points [x0,y0,...,xN,yN]
+			ctx.moveTo(this.shape.points[0],this.shape.points[1]);
+			for (var i=2; i<this.shape.points.length-1; i=i+2){
+				ctx.lineTo(this.shape.points[i],this.shape.points[i+1]);
+			}
+
+		}
+		this._stroke();
+		ctx.fill();
+		ctx.closePath();
+		ctx.restore(); // return graphics context after rendering shape
+	}
+});
+dojox.gfx.Polyline.nodeType = "polyline";
+
+// Originally taken from vml.js--candidate for _base.js or path.js
+dojox.gfx.path._calcArc = function(alpha){
+	// return a start point, 1st and 2nd control points, and an end point
+	var cosa  = Math.cos(alpha), sina  = Math.sin(alpha),
+		p2 = {x: cosa + (4 / 3) * (1 - cosa), y: sina - (4 / 3) * cosa * (1 - cosa) / sina};
+	return {
+		s:  {x: cosa, y: -sina},
+		c1: {x: p2.x, y: -p2.y},
+		c2: p2,
+		e:  {x: cosa, y: sina}
+	};
+};
+
+dojo.declare("dojox.gfx.Path", dojox.gfx.path.Path, {
+	// summary: A shape defined using an SVG path string.
+	
+	render: function(){
+		// summary: Renders the path onto the HTML canvas, iterating through each path segment.
+		var ctx=this._getGC();
+		this.last={x:0,y:0};
+		this.first=null;
+		if (this.segments.length==0){return;}
+		ctx.save(); // save graphics context prior to rendering shape
+		this._transform();
+		ctx.beginPath();
+		this._fill();
+		//Iterate and render each path segments...
+		for (var i=0;i<this.segments.length;i++){
+			this._renderSegment(this.segments[i]);
+		}
+		this._stroke();
+		ctx.fill();
+		ctx.closePath(); // Ignore closePath, and implicitly close all paths
+		ctx.restore(); // return graphics context after rendering shape
+	},
+	_renderSegment: function(segment){
+		// summary: Render the specified segment on the surface of the canvas
+		// segment: Object: Segment to be rendered
+		var ctx=this._getGC();
+		var n = segment.args;
+		// update internal variables: bbox, absolute, last
+		switch(segment.action){
+			case "M": // moveTo (Absolute)
+				ctx.moveTo(n[0],n[1]);
+				if (!this.first){this.first={x:n[0],y:n[1]};}//If first position not at origin, track it
+				this.last = {x:n[0],y:n[1]};
+				if (!this.first){this.first=this.last;}
+				this.lastControl = {};
+				var n2=n;
+				while (n2.length>2){
+					n2=n2.slice(2,n.length);
+					this._renderSegment({action:"L",args:n2.slice(0,2)});
+				}
+				break;
+			case "L": // lineTo (Absolute)
+				ctx.lineTo(n[0],n[1]);
+				this.last={x:n[0],y:n[1]};
+				if (!this.first){this.first=this.last;}
+				this.lastControl = {};
+				var n2=n;
+				while (n2.length>2){
+					n2=n2.slice(2,n.length);
+					this._renderSegment({action:"L",args:n2.slice(0,2)});
+				}
+				break;
+			case "C": // Bezier curveTo (Absolute)
+				ctx.bezierCurveTo(n[0],n[1],n[2],n[3],n[4],n[5]);
+				this.last={x:n[4],y:n[5]};
+				this.lastControl={x:n[2],y:n[3],type:"C"};
+				var n2=n;
+				while (n2.length>6){
+					n2=n2.slice(6,n.length);
+					this._renderSegment({action:"C",args:n2.slice(0,6)});
+				}
+				break;
+			case "S": // Smooth Bezier curveTo (Absolute)
+				if(this.lastControl.type == "C"){// VERIFIED - BUT 2*last.x doesnt seem correct
+					ctx.bezierCurveTo(2*this.last.x-this.lastControl.x,
+									  2*this.last.y-this.lastControl.y,
+									  n[0],n[1],n[2],n[3]);
+				}else{
+					ctx.bezierCurveTo(this.last.x,this.last.y,n[0],n[1],n[2],n[3]);
+				}
+				this.last={x:n[2],y:n[3]};
+				this.lastControl={x:n[0],y:n[1],type:"C"};
+				var n2=n;
+				while (n2.length>4){
+					n2=n2.slice(4,n.length);
+					this._renderSegment({action:"C",args:n2.slice(0,4)}); // Curve to absolute for remaining args
+				}
+				break;
+			case "Q": // Quadratic curveTo (Absolute)
+				ctx.quadraticCurveTo(n[0],n[1],n[2],n[3]);
+				this.last={x:n[2],y:n[3]};
+				this.lastControl={x:n[0],y:n[1],type:"Q"};
+				var n2=n;
+				while (n2.length>4){
+					n2=n2.slice(4,n.length);
+					this._renderSegment({action:"Q",args:n2.slice(0,4)}); // Curve to absolute for remaining args
+				}
+				break;
+			case "T": // Smooth Quadratic curveTo (Absolute)
+				if(this.lastControl.type == "Q"){
+					this.lastControl.x=2*this.last.x-this.lastControl.x;
+					this.lastControl.y=2*this.last.y-this.lastControl.y;
+					ctx.quadraticCurveTo(this.lastControl.x,this.lastControl.y,n[0],n[1]);
+				}else{
+					ctx.quadraticCurveTo(this.last.x,this.last.y,n[0],n[1]);
+					this.lastControl.x=this.last.x;
+					this.lastControl.y=this.last.y;
+				}
+				this.last={x:n[0],y:n[1]};
+				this.lastControl.type="Q";
+				var n2=n;
+				while (n2.length>2){
+					n2=n2.slice(2,n.length);
+					this._renderSegment({action:"Q",args:n2.slice(0,2)}); // Curve to absolute for remaining args
+				}
+				break;
+			case "H": // Horizontal lineTo(y) (Absolute)
+				ctx.lineTo(n[0],this.last.y); 
+				this.last.x=n[0];
+				this.last.y=this.last.y;
+				if (!this.first){this.first=this.last;}
+				this.lastControl = {};
+				break;
+			case "V": // Vertical lineTo(x) (Absolute)
+				ctx.lineTo(this.last.x,n[0]);
+				this.last.x=this.last.x;
+				this.last.y=n[0];
+				this.lastControl = {};
+				if (!this.first){this.first=this.last;}
+				break;
+			case "m": // moveTo (Relative)
+				ctx.moveTo(this.last.x+n[0],this.last.y+n[1]);
+				this.last.x=this.last.x+n[0];
+				this.last.y=this.last.y+n[1];
+				if (!this.first){this.first=this.last;}
+				this.lastControl = {};
+				var n2=n;
+				while (n2.length>2){
+					n2=n2.slice(2,n.length);
+					this._renderSegment({action:"l",args:n2.slice(0,2)});
+				}
+				break;
+			case "l": // lineTo (Relative)
+				ctx.moveTo(this.last.x,this.last.y);
+				ctx.lineTo(this.last.x+n[0],this.last.y+n[1]);
+				this.last.x=this.last.x+n[0];
+				this.last.y=this.last.y+n[1];
+				if (!this.first){this.first=this.last;}
+				this.lastControl = {};
+				var n2=n;
+				while (n2.length>2){
+					n2=n2.slice(2,n.length);
+					this._renderSegment({action:"l",args:n2.slice(0,2)});
+				}
+				break;
+			case "q": // Quadratic curveTo (Relative)
+				ctx.quadraticCurveTo(this.last.x+n[0],this.last.y+n[1],
+								     this.last.x+n[2],this.last.y+n[3]);
+				this.lastControl={x:this.last.x+n[0],y:this.last.y+n[1],type:"Q"};
+				this.last.x=this.last.x+n[2];
+				this.last.y=this.last.y+n[3];
+				var n2=n;
+				while (n2.length>4){
+					n2=n2.slice(4,n.length);
+					this._renderSegment({action:"Q",args:n2.slice(0,4)});// Quadratic absolute remaining
+				}
+				break;
+			case "t": // Smooth Quadratic curveTo (Relative)
+				if(this.lastControl.type == "Q"){
+					this.lastControl.x=2*this.last.x-this.lastControl.x;
+					this.lastControl.y=2*this.last.y-this.lastControl.y;
+					ctx.quadraticCurveTo(this.lastControl.x,this.lastControl.y,
+							 this.last.x+n[0],this.last.y+n[1]);
+				}else{
+					this.lastControl.x=this.last.x;
+					this.lastControl.y=this.last.y;
+					ctx.quadraticCurveTo(this.lastControl.x,this.lastControl.y,
+										 this.last.x+n[0],this.last.y+n[1]);
+				}
+				this.last.x=this.last.x+n[0];
+				this.last.y=this.last.y+n[1];
+				this.lastControl.type = "Q";
+				var n2=n;
+				while (n2.length>2){
+					n2=n2.slice(2,n.length);
+					this._renderSegment({action:"Q",args:n2.slice(0,2)});// Quadratic absolute remaining
+				}
+				break;
+			case "h": // Horizontal lineTo (Relative)
+				ctx.moveTo(this.last.x,this.last.y);
+				ctx.lineTo(this.last.x+n[0],this.last.y);
+				this.last.x=this.last.x+n[0];
+				this.last.y=this.last.y;
+				if (!this.first){this.first=this.last;}
+				this.lastControl = {};
+				break;
+			case "v": // Vertical lineTo (Relative)
+				ctx.moveTo(this.last.x,this.last.y);
+				ctx.lineTo(this.last.x,this.last.y+n[0]);
+				this.last.x=this.last.x;
+				this.last.y=this.last.y+n[0];
+				if (!this.first){this.first=this.last;}
+				this.lastControl = {};
+				break;
+			case "c": // Bezier curveTo (Relative)
+				var len=n.length;
+				ctx.bezierCurveTo(this.last.x+n[0],this.last.y+n[1],
+								  this.last.x+n[2],this.last.y+n[3],
+								  this.last.x+n[4],this.last.y+n[5]);
+				this.last.x += n[4];
+				this.last.y += n[5];
+				this.lastControl={x:n[2],y:n[3],type:"C"};  
+				var n2=n;
+				while (n2.length>6){
+					n2=n2.slice(6,n.length);
+					this._renderSegment({action:"c",args:n2.slice(0,6)});// curve to relative
+				}
+				break;
+			case "s": // Smooth Bezier curveTo (Relative)
+				if(this.lastControl.type == "C"){
+					ctx.bezierCurveTo( 
+							this.last.x+(this.last.x-this.lastControl.x),this.last.y+(this.last.y-this.lastControl.y),
+							this.last.x+n[0],this.last.y+n[1],
+							this.last.x+n[2],this.last.y+n[3]);
+				}else{
+					ctx.bezierCurveTo(
+							0,0,
+							this.last.x+n[0],this.last.y+n[1],
+							this.last.x+n[2],this.last.y+n[3]);
+				}
+				this.lastControl={x:this.last.x+n[0],y:this.last.y+n[1],type:"C"};
+				this.last.x += n[2];
+				this.last.y += n[3];
+				var n2=n;
+				while (n2.length>4){
+					n2=n2.slice(4,n.length);
+					this._renderSegment({action:"c",args:n2.slice(0,4)});// curve to relative
+				}
+				break;
+			case "A":
+			case "a":
+				relative = segment.action == "a";
+				var x1 = n[5], y1 = n[6];
+				if(relative){
+					x1 += this.last.x;
+					y1 += this.last.y;
+				}
+				this._renderArcTo(this.last, n[0], n[1], n[2], 
+								  n[3] ? 1 : 0, n[4] ? 1 : 0,
+								  x1, y1
+				);
+				this.last = {x: x1, y: y1};
+				this.lastControl = {};
+				break;
+			case "Z":
+			case "z": // closePath (SVG closepath action)- draw a line back to first point in segment
+				if (this.first){
+					ctx.lineTo(this.first.x,this.first.y); // Return to first position
+				}else{
+					ctx.lineTo(0,0); // Return to origin
+				}
+				this.lastControl = {};
+				break;
+		}
+	},
+	_curvePI4: dojox.gfx.path._calcArc(dojox.gfx.canvas.pi8),
+	_renderArcTo: function(last, rx, ry, xRotg, large, sweep, x, y){
+		var m = dojox.gfx.matrix, ctx = this._getGC();
+		// calculate parameters
+		var xRot = m._degToRad(xRotg),
+			rx2 = rx * rx, ry2 = ry * ry,
+			pa = m.multiplyPoint(
+					m.rotate(-xRot), 
+					{x: (last.x - x) / 2, y: (last.y - y) / 2}
+			),
+			pax2 = pa.x * pa.x, pay2 = pa.y * pa.y,
+			c1 = Math.sqrt((rx2 * ry2 - rx2 * pay2 - ry2 * pax2) / (rx2 * pay2 + ry2 * pax2));
+		if(isNaN(c1)){ c1 = 0; }
+		var ca = {
+				x:  c1 * rx * pa.y / ry,
+				y: -c1 * ry * pa.x / rx
+			};
+		if(large == sweep){
+			ca = {x: -ca.x, y: -ca.y};
+		}
+		// our center
+		var c = m.multiplyPoint(
+			[
+				m.translate(
+					(last.x + x) / 2,
+					(last.y + y) / 2
+				),
+				m.rotate(xRot)
+			], 
+			ca
+		);
+		// calculate our elliptic transformation
+		var elliptic_transform = m.normalize([
+			m.translate(c.x, c.y),
+			m.rotate(xRot),
+			m.scale(rx, ry)
+		]);
+		// start, end, and size of our arc
+		var inversed = m.invert(elliptic_transform),
+			sp = m.multiplyPoint(inversed, last),
+			ep = m.multiplyPoint(inversed, x, y),
+			startAngle = Math.atan2(sp.y, sp.x),
+			endAngle   = Math.atan2(ep.y, ep.x),
+			// size of our arc in radians
+			theta = sweep ? endAngle - startAngle : startAngle - endAngle;
+		if(theta < 0){
+			theta += dojox.gfx.canvas.two_pi;
+		}else if(theta > dojox.gfx.canvas.two_pi){
+			theta = dojox.gfx.canvas.two_pi;
+		}
+		// draw curve chunks
+		var pi4 = Math.PI / 4, alpha = Math.PI / 8, curve = this._curvePI4, 
+			step  = sweep ? alpha : -alpha;
+		for(var angle = theta; angle > 0; angle -= pi4){
+			if(angle < pi4){
+				alpha = angle / 2;
+				curve = dojox.gfx.path._calcArc(alpha);
+				step  = sweep ? alpha : -alpha;
+			}
+			var c1, c2, e,
+				M = m.normalize([elliptic_transform, m.rotate(startAngle + step)]);
+			if(sweep){
+				c1 = m.multiplyPoint(M, curve.c1);
+				c2 = m.multiplyPoint(M, curve.c2);
+				e  = m.multiplyPoint(M, curve.e );
+			}else{
+				c1 = m.multiplyPoint(M, curve.c2);
+				c2 = m.multiplyPoint(M, curve.c1);
+				e  = m.multiplyPoint(M, curve.s );
+			}
+			// draw the curve
+			ctx.bezierCurveTo(c1.x, c1.y, c2.x, c2.y, e.x, e.y);
+			startAngle += 2 * step;
+		}
+	}
+	
+});
+dojo.extend(dojox.gfx.Path, dojox.gfx.canvas.shapeExt);
+dojo.extend(dojox.gfx.Path, {
+	// Note: This function is a copy of the function in dojo.gfx.path.Path it has to be mixed-in at this point 
+	// because the mixin of the Shape extension functions above overwrite the existing inherited setShape() 
+	// function from Path.
+	setShape: function(newShape){
+		// summary: forms a path using a shape
+		// newShape: Object: an SVG path string or a path object (see dojox.gfx.defaultPath)
+		this.shape = dojox.gfx.makeParameters(this.shape, typeof newShape == "string" ? {path: newShape} : newShape);
+		var path = this.shape.path;
+		// switch to non-updating version of path building
+		this.shape.path = [];
+		this._setPath(path);
+		// switch back to the string path
+		this.shape.path = this.shape.path.join("");
+		return this.setTransform(this.matrix);	// self
+	}
+});
+dojox.gfx.Path.nodeType = "path";
+
+dojo.declare("dojox.gfx.Image", dojox.gfx.shape.Image, {
+	// summary: an image 
+	setShape: function(newShape){
+		// summary: sets an image shape object
+		// newShape: Object: an image shape object
+		this.shape = dojo.clone(newShape);
+		if(!this.shape.x){this.shape.x=0;}
+		if(!this.shape.y){this.shape.y=0;}
+		if(!this.shape.width){this.useImgWidth=true;}
+		if(!this.shape.height){this.useImgHeight=true;}
+		dojox.gfx.canvas.imageLoader.loadImage(this.shape.src);
+		this.bbox = null;
+		return this.setTransform(this.matrix);	// self
+		// TODO: wait until image is loaded, or better yet,
+		// display loading image and then invalidate scene and re-render upon loaded.
+	},
+	setStroke: function(){
+		// summary: ignore setting a stroke style
+		return this;	// self
+	},
+	setFill: function(){
+		// summary: ignore setting a fill style
+		return this;	// self
+	},
+	render: function(){
+		this.img = dojox.gfx.canvas.imageLoader.getImage(this.shape.src);
+		if (!this.img){return;}
+		var ctx=this._getGC();
+		ctx.save(); // save graphics context prior to rendering shape
+		this._transform();
+		// Adjust width and height to the size of the loaded image that we have now.
+		if (this.useImgWidth){this.shape.width=this.img.width;}
+		if (this.useImgHeight){this.shape.height=this.img.height;}
+// TODO: FIXME: When width/height are specified rather than using the src image's w&h, drawImage doesnt seem to work
+//		ctx.drawImage(this.img,this.shape.x,this.shape.y,this.shape.width,this.shape.height);
+// TODO: FIXME: width and height of rotated images (using transform matrix) appears to clip at original image extent.
+		ctx.drawImage(this.img,this.shape.x,this.shape.y);
+		ctx.restore(); // return graphics context after rendering shape
+
+	}
+});
+dojox.gfx.Image.nodeType = "image";
+
+dojo.declare("dojox.gfx.Text", dojox.gfx.shape.Text, {
+	// summary: a text shape
+	setShape: function(newShape){
+		// summary: sets a text shape object (SVG)
+		// newShape: Object: a text shape object
+		this.shape = dojo.clone(newShape);
+		if (!this.shape.text){this.shape.text="";};
+		this.bbox = null;
+		if(!("fontStyle" in this)){
+			this.fontStyle = dojo.clone(dojox.gfx.defaultFont);
+		}else{
+			this.fontStyle = dojox.gfx.makeParameters(dojox.gfx.defaultFont,this.fontStyle);
+		}
+		//TODO: deal with other properties like css
+		return this.setTransform(this.matrix);	// self
+	},
+	setRawNode: function(rawNode){
+		// summary:
+		//	assigns and clears the underlying node that will represent this
+		//	shape. 
+		this.rawNode = rawNode;
+		this.rawNode.innerHtml="";
+		this.rawNode.appendChild(this.rawNode.ownerDocument.createTextNode(this.shape.text));
+		this.rawNode.style.position="absolute";
+		if (this.shape.align){
+			switch (this.shape.align){
+				case "middle":	this.rawNode.style.textAlign="center";
+					break;
+				case "end":	this.rawNode.style.textAlign="right";
+					break;
+				default:	this.rawNode.style.textAlign="left";
+			}
+		}
+		var fontOffset = {x:0,y:0}
+		if (this.fontStyle.size){
+			// Calculate offset based on estimated font height
+			var baseFontHeight = dojox.gfx._base._getCachedFontMeasurements()["12pt"];
+			// Use a ratio to estimate...
+			//  baseFontHeight        estFontHeight
+			// ---------------- = ---------------------------------
+			//  12					this.rawNode.style.fontHeight
+			var estFontHeight = (baseFontHeight/12) * dojox.gfx.normalizedLength(this.fontStyle.size);
+			fontOffset.y=-estFontHeight + 1;
+			// Fudge font width using 1/goldenratio 
+			//fontOffset.x=-(0.6180339882*estFontHeight/2) + 1
+		}
+		// TODO: Check to see if the node is nested within a table cell (parent tr).  
+		// use the offset for the cell (with insets)
+		var tableNode = this._nestedWithin(this.rawNode,"table"); 
+		if (tableNode){
+			var trNode = this._nestedWithin(this.rawNode,"tr");
+			var tdNode = this._nestedWithin(this.rawNode,"td");
+			this.rawNode.style.top=tableNode.offsetTop+trNode.offsetTop+this.shape.y+fontOffset.y+"px";
+			this.rawNode.style.left=tdNode.offsetLeft+this.shape.x+fontOffset.x+"px";
+		}else{
+			this.rawNode.style.left=this.rawNode.parentNode.offsetLeft+this.shape.x+fontOffset.x+"px";
+			this.rawNode.style.top=this.rawNode.parentNode.offsetTop+this.shape.y+fontOffset.y+"px";
+		}
+	},
+	_nestedWithin: function(node,nodeName){
+		while (node.parentNode){
+			if(node.parentNode.nodeName.toLowerCase()==nodeName){return node.parentNode;}
+			return this._nestedWithin(node.parentNode,nodeName);
+		}
+		return null;
+	},
+	getTextWidth: function(){ 
+		// summary: get the text width in pixels
+		// TODO: Use dojo text functions to compute width of text
+		return 50; 
+	},
+	render: function(){
+		// Noop - Text nodes are rendered by the browser using divs
+	}
+});
+dojox.gfx.Text.nodeType = "text";
+
+dojo.declare("dojox.gfx.TextPath", dojox.gfx.path.TextPath, {
+	// summary: a textpath shape (SVG)
+	_updateWithSegment: function(segment){
+		// summary: updates the bounding box of path with new segment
+		// segment: Object: a segment
+		dojox.gfx.Path.superclass._updateWithSegment.apply(this, arguments);
+		this._setTextPath();
+	},
+	setShape: function(newShape){
+		// summary: forms a path using a shape (SVG)
+		// newShape: Object: an SVG path string or a path object (see dojox.gfx.defaultPath)
+		dojox.gfx.Path.superclass.setShape.apply(this, arguments);
+		//this._setTextPath();
+		return this.setTransform(this.matrix);	// self
+	},
+/*	_setTextPath: function(){
+		if(typeof this.shape.path != "string"){ return; }
+		var r = this.rawNode;
+		if(!r.firstChild){
+			var tp = document.createElementNS(dojox.gfx.canvas.xmlns.svg, "textPath");
+			var tx = document.createTextNode("");
+			tp.appendChild(tx);
+			r.appendChild(tp);
+		}
+		var ref  = r.firstChild.getAttributeNS(dojox.gfx.canvas.xmlns.xlink, "href");
+		var path = ref && dojox.gfx.canvas.getRef(ref);
+		if(!path){
+			var surface = this._getParentSurface();
+			if(surface){
+				var defs = surface.defNode;
+				path = document.createElementNS(dojox.gfx.canvas.xmlns.svg, "path");
+				var id = dojox.gfx._base._getUniqueId();
+				path.setAttribute("id", id);
+				defs.appendChild(path);
+				r.firstChild.setAttributeNS(dojox.gfx.canvas.xmlns.xlink, "href", "#" + id);
+			}
+		}
+		if(path){
+			path.setAttribute("d", this.shape.path);
+		}
+	},
+*/	
+	_setText: function(){
+		var r = this.rawNode;
+		r = r.firstChild;
+/*
+		var t = this.text;
+		r.setAttribute("alignment-baseline", "middle");
+		switch(t.align){
+			case "middle":
+				r.setAttribute("text-anchor", "middle");
+				r.setAttribute("startOffset", "50%");
+				break;
+			case "end":
+				r.setAttribute("text-anchor", "end");
+				r.setAttribute("startOffset", "100%");
+				break;
+			default:
+				r.setAttribute("text-anchor", "start");
+				r.setAttribute("startOffset", "0%");
+				break;
+		}
+		//r.parentNode.setAttribute("alignment-baseline", "central");
+		//r.setAttribute("dominant-baseline", "central");
+		r.setAttribute("baseline-shift", "0.5ex");
+		r.setAttribute("text-decoration", t.decoration);
+		r.setAttribute("rotate", t.rotated ? 90 : 0);
+		r.setAttribute("kerning", t.kerning ? "auto" : 0);
+*/		
+		r.text = t.text;
+	},
+	render: function(){
+		// Noop - Text nodes are rendered by the browser using divs
+	}
+});
+dojox.gfx.TextPath.nodeType = "text";
+
+dojo.declare("dojox.gfx.Surface", dojox.gfx.shape.Surface, {
+	// summary: a surface object to be used for drawings (html canvas)
+	constructor: function(){
+		dojox.gfx.canvas.Container._init.call(this);
+	},
+	setDimensions: function(width, height){
+		// summary: sets the width and height of the rawNode
+		// width: String: width of surface, e.g., "100px"
+		// height: String: height of surface, e.g., "100px"
+		if(!this.rawNode){ return this; }
+		this.rawNode.setAttribute("width",  width);
+		this.rawNode.setAttribute("height", height);
+		return this;	// self
+	},
+	getDimensions: function(){
+		// summary: returns an object with properties "width" and "height"
+		return this.rawNode ? {width: this.rawNode.getAttribute("width"), height: this.rawNode.getAttribute("height")} : null; // Object
+	},
+	render: function(){
+		// wait for pending images to load.
+		console.debug("In surface render()");
+		if(!dojox.gfx.canvas.imageLoader.complete){
+			console.debug("Images not loaded yet.");
+			setTimeout(dojo.hitch(this, function(){this.render();}),500); // try rendering again in a bit...
+		}else{
+			console.debug("Images loaded.");
+			// clear the canvas;
+			this.ctx.clearRect(0,0,this.rawNode.width,this.rawNode.height);
+			// render shapes from back (first) to front (last)
+			for (var i=0;i<this.children.length;i++){
+				this.children[i].render();
+			}
+		}
+	}
+});
+
+dojox.gfx.createSurface = function(parentNode, width, height){
+	// summary: creates a surface (HTML Canvas)
+	// parentNode: Node: a parent node
+	// width: String: width of surface, e.g., "100px"
+	// height: String: height of surface, e.g., "100px"
+
+	var s = new dojox.gfx.Surface();
+	s.rawNode = dojo.byId(parentNode).ownerDocument.createElement("canvas");
+	s.ctx = s.rawNode.getContext("2d");
+
+	s.rawNode.width  = width  ? width  : "100%";
+	s.rawNode.height = height ? height : "100%";
+	// FIXME: Does HTML Canvas make use of CSS?
+	/*
+	s.rawNode.style.width  = width  ? width  : "100";
+	s.rawNode.style.height = height ? height : "100";
+	s.rawNode.style.position = "relative";
+	s.rawNode.coordsize = (width && height)
+		? (parseFloat(width) + " " + parseFloat(height))
+		: "100 100";
+	s.rawNode.coordorigin = "0 0";
+	*/
+	
+	dojo.byId(parentNode).appendChild(s.rawNode);
+	// FIXME: FOR DEBUG, so we can tell that we have a canvas
+	s.ctx.fillStyle = "rgb(255,255,255)";
+ 	s.ctx.fillRect (0, 0, width, height);
+	// END FOR DEBUG
+ 	return s;	// dojox.gfx.Surface
+};
+
+/*
+dojox.gfx._function draw() {
+	  // TODO: the image objects should be created and initialized once for all canvas surfaces
+	  // Surface needs to wait until the images have been loaded before constructing stroke patterns
+	  // which must be created with a per-surface graphics context during render phase of each shape 
+	  // Here's an example (broken)
+	  var img = new Image();
+	  img.src = 'images/wallpaper.png';
+	  img.onload = function(){
+	    // create pattern
+	    var ptrn = ctx.createPattern(img,'repeat');
+	    ctx.fillStyle = ptrn;
+	    ctx.fillRect(0,0,150,150);
+	  }
+};
+*/
+
+// Extenders
+
+dojox.gfx.canvas.Font = {
+	_setFont: function(){
+		// summary: sets a font object (SVG)
+		var f = this.fontStyle;
+		// next line doesn't work in Firefox 2 or Opera 9
+		//this.rawNode.setAttribute("font", dojox.gfx.makeFontString(this.fontStyle));
+		this.rawNode.style.fontStyle = f.style;
+		this.rawNode.style.fontVariant = f.variant;
+		this.rawNode.style.fontWeight = f.weight;
+//FIXME: Looks like there's a bug in dojox.gfx.splitFontString for size and family when using canvas
+// needs more work.
+		this.rawNode.style.fontSize = f.size;
+		this.rawNode.style.fontFamily = f.family;
+	},
+	setFill: function(fillColor){
+		this.rawNode.style.color=dojox.gfx.normalizeColor(fillColor).toHex();
+	}
+};
+
+dojox.gfx.canvas.Container = {
+	_init: function(){
+		dojox.gfx.shape.Container._init.call(this);
+	},
+	add: function(shape){
+		// summary: adds a shape to a group/surface
+		// shape: dojox.gfx.Shape: an VML shape object
+		if(this != shape.getParent()){
+			dojox.gfx.shape.Container.add.apply(this, arguments);
+		}
+		return this;	// self
+	},
+	remove: function(shape, silently){
+		// summary: remove a shape from a group/surface
+		// shape: dojox.gfx.Shape: an shape object
+		// silently: Boolean?: if true, regenerate a picture
+		if(this == shape.getParent()){
+			dojox.gfx.shape.Container.remove.apply(this, arguments);
+		}
+		return this;	// self
+	},
+	clear: function(){
+		// summary: removes all shapes from a group/surface
+		var r = this.rawNode;
+		while(r.lastChild){
+			r.removeChild(r.lastChild);
+		}
+		//return this.inherited(arguments);	// self
+		return dojox.gfx.shape.Container.clear.apply(this, arguments);
+	},
+	_moveChildToFront: dojox.gfx.shape.Container._moveChildToFront,
+	_moveChildToBack:  dojox.gfx.shape.Container._moveChildToBack
+};
+
+dojox.gfx.canvas.Creator = {
+	// summary: Shape creators
+	createPath: function(path){
+		// summary: creates an SVG path shape
+		// path: Object: a path object (see dojox.gfx.defaultPath)
+		return this.createObject(dojox.gfx.Path, path);	// dojox.gfx.Path
+	},
+	createRect: function(rect){
+		// summary: creates a rectangle shape
+		// rect: Object: a path object (see dojox.gfx.defaultRect)
+		return this.createObject(dojox.gfx.Rect, rect);	// dojox.gfx.Rect
+	},
+	createCircle: function(circle){
+		// summary: creates a circle shape
+		// circle: Object: a circle object (see dojox.gfx.defaultCircle)
+		return this.createObject(dojox.gfx.Circle, circle);	// dojox.gfx.Circle
+	},
+	createEllipse: function(ellipse){
+		// summary: creates an ellipse shape
+		// ellipse: Object: an ellipse object (see dojox.gfx.defaultEllipse)
+		return this.createObject(dojox.gfx.Ellipse, ellipse);	// dojox.gfx.Ellipse
+	},
+	createLine: function(line){
+		// summary: creates an SVG line shape
+		// line: Object: a line object (see dojox.gfx.defaultLine)
+		return this.createObject(dojox.gfx.Line, line);	// dojox.gfx.Line
+	},
+	createPolyline: function(points){
+		// summary: creates an SVG polyline/polygon shape
+		// points: Object: a points object (see dojox.gfx.defaultPolyline)
+		//	or an Array of points
+		return this.createObject(dojox.gfx.Polyline, points);	// dojox.gfx.Polyline
+	},
+	createImage: function(image){
+		// summary: creates an SVG image shape
+		// image: Object: an image object (see dojox.gfx.defaultImage)
+		return this.createObject(dojox.gfx.Image, image);	// dojox.gfx.Image
+	},
+	createText: function(textObj){
+		// summary: Creates an text shape. The text shape for html canvas uses div overlays (with CSS layout)
+		// to simulate text shapes.  Moz3 introduces proprietary canvas text functions that may be able to replace
+		// this on mozilla browsers in the future; however, there's no portable canvas text solution at this time.
+		// One other possible solution (though very compute intensive) would be to use custom path's and render our own fonts
+		// textObj: Object: a text object (see dojox.gfx.defaultText) which contains text property and other text attrs
+		var shape = new dojox.gfx.Text();
+		var node = this.rawNode.ownerDocument.createElement("div");
+		this.rawNode.parentNode.appendChild(node); // Make the div a peer of canvas (until we have group divs)
+		//shape.fontStyle = dojo.clone(dojox.gfx.defaultFont);
+		shape.setShape(textObj);
+		shape.setRawNode(node);
+		this.add(shape);
+		return shape;
+	},
+	createTextPath: function(text){
+		// summary: creates an text path shape (simulated with div overlays)
+		// text: Object: a textpath object (see dojox.gfx.defaultTextPath)
+		return this.createObject(dojox.gfx.TextPath, {}).setText(text);	// dojox.gfx.TextPath
+	},
+	createGroup: function(){
+		// summary: creates an SVG group shape
+		var g = this.createObject(dojox.gfx.Group);	// dojox.gfx.Group
+		g.ctx = this.ctx; // Pass the parent GC reference onto the child group
+		g.setRawNode(this.rawNode); // Each group shares the surface's rawNode
+		return g;
+	},
+	createObject: function(shapeType, rawShape){
+		// summary: creates an instance of the passed shapeType class
+		// shapeType: Function: a class constructor to create an instance of
+		// rawShape: Object: properties to be passed in to the classes "setShape" method
+		var shape = new shapeType();
+		shape.setShape(rawShape);
+		this.add(shape);
+		return shape;	// dojox.gfx.Shape
+	},
+	createShape: dojox.gfx._createShape
+};
+
+dojo.declare("dojox.gfx.canvas.ImageLoader", null, {
+	constructor: function(imageUrls,callback){
+	   this.callback = callback;
+	   this.numLoaded = 0;
+	   this.processed = 0;
+	   this.complete=false;
+	   this.images = [];
+	   this.urls = imageUrls?imageUrls:[];
+	   if(!imageUrls){
+		   this.complete=true;
+		   return;
+	   }
+	   for (var i=0;i<urls.length;i++){
+		   this.loadImage(urls[i]);
+	   }
+	},
+	getImage: function(src){
+		for(var i=0;i<this.urls.length;i++){
+			if (this.urls[i]==src){return this.images[i];}
+		}
+		return null;
+	},
+	loadImage: function(src){
+		var cached = this.getImage(src);
+	   if (cached){
+		   return cached;
+	   }
+	   var image = new Image();
+	   this.images.push(image);
+	   this.urls.push(src);
+	   image.loader = this;
+	   image.loaded = false;
+	   image.onload = dojo.hitch(this,function(){
+		   this.loaded=true;
+		   this.numLoaded++;
+		   this._imageLoaded();
+	   	});
+	   image.onerror = dojo.hitch(this,function(){
+		   this.error=true;
+		   this._imageLoaded();
+	   });
+	   image.onabort = dojo.hitch(this,function(){
+		   this.aborted=true;
+		   this._imageLoaded();
+	   });
+	   image.src = src;
+	},
+	_imageLoaded: function(){
+		this.processed++;
+		if(this.processed==this.images.length){
+			this.onComplete();
+		}
+	},
+	onComplete: function(){
+		this.complete=true;
+	}
+});
+dojox.gfx.canvas.imageLoader = new dojox.gfx.canvas.ImageLoader();
+
+dojo.extend(dojox.gfx.Text, dojox.gfx.canvas.Font);
+dojo.extend(dojox.gfx.TextPath, dojox.gfx.canvas.Font);
+
+dojo.extend(dojox.gfx.Group, dojox.gfx.canvas.Container);
+dojo.extend(dojox.gfx.Group, dojox.gfx.canvas.Creator);
+
+dojo.extend(dojox.gfx.Surface, dojox.gfx.canvas.Container);
+dojo.extend(dojox.gfx.Surface, dojox.gfx.canvas.Creator);
+dojo.extend(dojox.gfx.Surface, dojox.gfx.canvas.ImageLoader);


More information about the Dojo-checkins mailing list