[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