]> git.pond.sub.org Git - eow/blob - static/dojo-release-1.1.1/dojox/gfx/canvas.js
add Dojo 1.1.1
[eow] / static / dojo-release-1.1.1 / dojox / gfx / canvas.js
1 if(!dojo._hasResource["dojox.gfx.canvas"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource["dojox.gfx.canvas"] = true;
3 dojo.provide("dojox.gfx.canvas");
4
5 dojo.require("dojox.gfx._base");
6 dojo.require("dojox.gfx.shape");
7 dojo.require("dojox.gfx.path");
8 dojo.require("dojox.gfx.arc");
9 dojo.require("dojox.gfx.decompose");
10
11 dojo.experimental("dojox.gfx.canvas");
12
13 (function(){
14         var g = dojox.gfx, gs = g.shape, ga = g.arc, 
15                 m = g.matrix, mp = m.multiplyPoint, pi = Math.PI, twoPI = 2 * pi, halfPI = pi /2;
16         
17         dojo.extend(g.Shape, {
18                 _render: function(/* Object */ ctx){
19                         // summary: render the shape
20                         ctx.save();
21                         this._renderTransform(ctx);
22                         this._renderShape(ctx);
23                         this._renderFill(ctx, true);
24                         this._renderStroke(ctx, true);
25                         ctx.restore();
26                 },
27                 _renderTransform: function(/* Object */ ctx){
28                         if("canvasTransform" in this){
29                                 var t = this.canvasTransform;
30                                 ctx.translate(t.dx, t.dy);
31                                 ctx.rotate(t.angle2);
32                                 ctx.scale(t.sx, t.sy);
33                                 ctx.rotate(t.angle1);
34                                 // The future implementation when vendors catch up with the spec:
35                                 // var t = this.matrix;
36                                 // ctx.transform(t.xx, t.yx, t.xy, t.yy, t.dx, t.dy);
37                         }
38                 },
39                 _renderShape: function(/* Object */ ctx){
40                         // nothing
41                 },
42                 _renderFill: function(/* Object */ ctx, /* Boolean */ apply){
43                         if("canvasFill" in this){
44                                 if("canvasFillImage" in this){
45                                         this.canvasFill = ctx.createPattern(this.canvasFillImage, "repeat");
46                                         delete this.canvasFillImage;
47                                 }
48                                 ctx.fillStyle = this.canvasFill;
49                                 if(apply){ ctx.fill(); }
50                         }else{
51                                 ctx.fillStyle = "rgba(0,0,0,0.0)";
52                         }
53                 },
54                 _renderStroke: function(/* Object */ ctx, /* Boolean */ apply){
55                         var s = this.strokeStyle;
56                         if(s){
57                                 ctx.strokeStyle = s.color.toString();
58                                 ctx.lineWidth = s.width;
59                                 ctx.lineCap = s.cap;
60                                 if(typeof s.join == "number"){
61                                         ctx.lineJoin = "miter";
62                                         ctx.miterLimit = s.join;
63                                 }else{
64                                         ctx.lineJoin = s.join;
65                                 }
66                                 if(apply){ ctx.stroke(); }
67                         }else if(!apply){
68                                 ctx.strokeStyle = "rgba(0,0,0,0.0)";
69                         }
70                 },
71                 
72                 // events are not implemented
73                 getEventSource: function(){ return null; },
74                 connect:                function(){},
75                 disconnect:             function(){}
76         });
77         
78         var modifyMethod = function(shape, method, extra){
79                         var old = shape.prototype[method];
80                         shape.prototype[method] = extra ?
81                                 function(){
82                                         this.surface.makeDirty();
83                                         old.apply(this, arguments);
84                                         extra.call(this);
85                                         return this;
86                                 } :
87                                 function(){
88                                         this.surface.makeDirty();
89                                         return old.apply(this, arguments);
90                                 };
91                 };
92
93         modifyMethod(g.Shape, "setTransform",           
94                 function(){
95                         // prepare Canvas-specific structures
96                         if(this.matrix){
97                                 this.canvasTransform = g.decompose(this.matrix);
98                         }else{
99                                 delete this.canvasTransform;
100                         }
101                 });
102
103         modifyMethod(g.Shape, "setFill",
104                 function(){
105                         // prepare Canvas-specific structures
106                         var fs = this.fillStyle, f;
107                         if(fs){
108                                 if(typeof(fs) == "object" && "type" in fs){
109                                         var ctx = this.surface.rawNode.getContext("2d");
110                                         switch(fs.type){
111                                                 case "linear":
112                                                 case "radial":
113                                                         f = fs.type == "linear" ? 
114                                                                 ctx.createLinearGradient(fs.x1, fs.y1, fs.x2, fs.y2) :
115                                                                 ctx.createRadialGradient(fs.cx, fs.cy, 0, fs.cx, fs.cy, fs.r);
116                                                         dojo.forEach(fs.colors, function(step){
117                                                                 f.addColorStop(step.offset, g.normalizeColor(step.color).toString());
118                                                         });
119                                                         break;
120                                                 case "pattern":
121                                                         var img = new Image(fs.width, fs.height);
122                                                         this.surface.downloadImage(img, fs.src);
123                                                         this.canvasFillImage = img;
124                                         }
125                                 }else{
126                                         // Set fill color using CSS RGBA func style
127                                         f = fs.toString();
128                                 }
129                                 this.canvasFill = f;
130                         }else{
131                                 delete this.canvasFill;
132                         }
133                 });
134         
135         modifyMethod(g.Shape, "setStroke");
136         modifyMethod(g.Shape, "setShape");
137                 
138         dojo.declare("dojox.gfx.Group", g.Shape, {
139                 // summary: a group shape (Canvas), which can be used 
140                 //      to logically group shapes (e.g, to propagate matricies)
141                 constructor: function(){
142                         gs.Container._init.call(this);
143                 },
144                 _render: function(/* Object */ ctx){
145                         // summary: render the group
146                         ctx.save();
147                         this._renderTransform(ctx);
148                         this._renderFill(ctx);
149                         this._renderStroke(ctx);
150                         for(var i = 0; i < this.children.length; ++i){
151                                 this.children[i]._render(ctx);
152                         }
153                         ctx.restore();
154                 }
155         });
156
157         dojo.declare("dojox.gfx.Rect", gs.Rect, {
158                 // summary: a rectangle shape (Canvas)
159                 _renderShape: function(/* Object */ ctx){
160                         var s = this.shape, r = Math.min(s.r, s.height / 2, s.width / 2),
161                                 xl = s.x, xr = xl + s.width, yt = s.y, yb = yt + s.height,
162                                 xl2 = xl + r, xr2 = xr - r, yt2 = yt + r, yb2 = yb - r;
163                         ctx.beginPath();
164                         ctx.moveTo(xl2, yt);
165                         if(r){
166                                 ctx.arc(xr2, yt2, r, -halfPI, 0, false);
167                                 ctx.arc(xr2, yb2, r, 0, halfPI, false);
168                                 ctx.arc(xl2, yb2, r, halfPI, pi, false);
169                                 ctx.arc(xl2, yt2, r, pi, halfPI, false);
170                         }else{
171                                 ctx.lineTo(xr2, yt);
172                                 ctx.lineTo(xr, yb2);
173                                 ctx.lineTo(xl2, yb);
174                                 ctx.lineTo(xl, yt2);
175                         }
176                         ctx.closePath();
177                 }
178         });
179         
180         var bezierCircle = [];
181         (function(){
182                 var u = ga.curvePI4;
183                 bezierCircle.push(u.s, u.c1, u.c2, u.e);
184                 for(var a = 45; a < 360; a += 45){
185                         var r = m.rotateg(a);
186                         bezierCircle.push(mp(r, u.c1), mp(r, u.c2), mp(r, u.e));
187                 }
188         })();
189         
190         dojo.declare("dojox.gfx.Ellipse", gs.Ellipse, {
191                 // summary: an ellipse shape (Canvas)
192                 setShape: function(){
193                         g.Ellipse.superclass.setShape.apply(this, arguments);
194                         // prepare Canvas-specific structures
195                         var s = this.shape, t, c1, c2, r = [],
196                                 M = m.normalize([m.translate(s.cx, s.cy), m.scale(s.rx, s.ry)]);
197                         t = mp(M, bezierCircle[0]);
198                         r.push([t.x, t.y]);
199                         for(var i = 1; i < bezierCircle.length; i += 3){
200                                 c1 = mp(M, bezierCircle[i]);
201                                 c2 = mp(M, bezierCircle[i + 1]);
202                                 t  = mp(M, bezierCircle[i + 2]);
203                                 r.push([c1.x, c1.y, c2.x, c2.y, t.x, t.y]);
204                         }
205                         this.canvasEllipse = r;
206                         return this;
207                 },
208                 _renderShape: function(/* Object */ ctx){
209                         var r = this.canvasEllipse;
210                         ctx.beginPath();
211                         ctx.moveTo.apply(ctx, r[0]);
212                         for(var i = 1; i < r.length; ++i){
213                                 ctx.bezierCurveTo.apply(ctx, r[i]);
214                         }
215                         ctx.closePath();
216                 }
217         });
218
219         dojo.declare("dojox.gfx.Circle", gs.Circle, {
220                 // summary: a circle shape (Canvas)
221                 _renderShape: function(/* Object */ ctx){
222                         var s = this.shape;
223                         ctx.beginPath();
224                         ctx.arc(s.cx, s.cy, s.r, 0, twoPI, 1);
225                 }
226         });
227
228         dojo.declare("dojox.gfx.Line", gs.Line, {
229                 // summary: a line shape (Canvas)
230                 _renderShape: function(/* Object */ ctx){
231                         var s = this.shape;
232                         ctx.beginPath();
233                         ctx.moveTo(s.x1, s.y1);
234                         ctx.lineTo(s.x2, s.y2);
235                 }
236         });
237
238         dojo.declare("dojox.gfx.Polyline", gs.Polyline, {
239                 // summary: a polyline/polygon shape (Canvas)
240                 setShape: function(){
241                         g.Polyline.superclass.setShape.apply(this, arguments);
242                         // dojo.inherited("setShape", arguments);
243                         // prepare Canvas-specific structures
244                         var p = this.shape.points, f = p[0], r = [], c, i;
245                         if(p.length){
246                                 if(typeof f == "number"){
247                                         r.push(f, p[1]);
248                                         i = 2;
249                                 }else{
250                                         r.push(f.x, f.y);
251                                         i = 1;
252                                 }
253                                 for(; i < p.length; ++i){
254                                         c = p[i];
255                                         if(typeof c == "number"){
256                                                 r.push(c, p[++i]);
257                                         }else{
258                                                 r.push(c.x, c.y);
259                                         }
260                                 }
261                         }
262                         this.canvasPolyline = r;
263                         return this;
264                 },
265                 _renderShape: function(/* Object */ ctx){
266                         // console.debug("Polyline::_renderShape");
267                         var p = this.canvasPolyline;
268                         if(p.length){
269                                 ctx.beginPath();
270                                 ctx.moveTo(p[0], p[1]);
271                                 for(var i = 2; i < p.length; i += 2){
272                                         ctx.lineTo(p[i], p[i + 1]);
273                                 }
274                         }
275                 }
276         });
277         
278         dojo.declare("dojox.gfx.Image", gs.Image, {
279                 // summary: an image shape (Canvas)
280                 setShape: function(){
281                         g.Image.superclass.setShape.apply(this, arguments);
282                         // prepare Canvas-specific structures
283                         var img = new Image();
284                         this.surface.downloadImage(img, this.shape.src);
285                         this.canvasImage = img;
286                         return this;
287                 },
288                 _renderShape: function(/* Object */ ctx){
289                         var s = this.shape;
290                         ctx.drawImage(this.canvasImage, s.x, s.y, s.width, s.height);
291                 }
292         });
293         
294         dojo.declare("dojox.gfx.Text", gs.Text, {
295                 // summary: a text shape (Canvas)
296                 _renderShape: function(/* Object */ ctx){
297                         var s = this.shape;
298                         // nothing for the moment
299                 }
300         });
301         modifyMethod(g.Text, "setFont");
302         
303         var pathRenderers = {
304                 M: "_moveToA", m: "_moveToR", 
305                 L: "_lineToA", l: "_lineToR", 
306                 H: "_hLineToA", h: "_hLineToR", 
307                 V: "_vLineToA", v: "_vLineToR", 
308                 C: "_curveToA", c: "_curveToR", 
309                 S: "_smoothCurveToA", s: "_smoothCurveToR", 
310                 Q: "_qCurveToA", q: "_qCurveToR", 
311                 T: "_qSmoothCurveToA", t: "_qSmoothCurveToR", 
312                 A: "_arcTo", a: "_arcTo", 
313                 Z: "_closePath", z: "_closePath"
314         };
315         
316         dojo.declare("dojox.gfx.Path", g.path.Path, {
317                 // summary: a path shape (Canvas)
318                 constructor: function(){
319                         this.last = {};
320                         this.lastControl = {};
321                 },
322                 setShape: function(){
323                         this.canvasPath = [];
324                         return g.Path.superclass.setShape.apply(this, arguments);
325                 },
326                 _updateWithSegment: function(segment){
327                         var last = dojo.clone(this.last);
328                         this[pathRenderers[segment.action]](this.canvasPath, segment.action, segment.args);
329                         this.last = last;
330                         g.Path.superclass._updateWithSegment.apply(this, arguments);
331                 },
332                 _renderShape: function(/* Object */ ctx){
333                         var r = this.canvasPath;
334                         ctx.beginPath();
335                         for(var i = 0; i < r.length; i += 2){
336                                 ctx[r[i]].apply(ctx, r[i + 1]);
337                         }
338                 },
339                 _moveToA: function(result, action, args){
340                         result.push("moveTo", [args[0], args[1]]);
341                         for(var i = 2; i < args.length; i += 2){
342                                 result.push("lineTo", [args[i], args[i + 1]]);
343                         }
344                         this.last.x = args[args.length - 2];
345                         this.last.y = args[args.length - 1];
346                         this.lastControl = {};
347                 },
348                 _moveToR: function(result, action, args){
349                         if("x" in this.last){
350                                 result.push("moveTo", [this.last.x += args[0], this.last.y += args[1]]);
351                         }else{
352                                 result.push("moveTo", [this.last.x = args[0], this.last.y = args[1]]);
353                         }
354                         for(var i = 2; i < args.length; i += 2){
355                                 result.push("lineTo", [this.last.x += args[i], this.last.y += args[i + 1]]);
356                         }
357                         this.lastControl = {};
358                 },
359                 _lineToA: function(result, action, args){
360                         for(var i = 0; i < args.length; i += 2){
361                                 result.push("lineTo", [args[i], args[i + 1]]);
362                         }
363                         this.last.x = args[args.length - 2];
364                         this.last.y = args[args.length - 1];
365                         this.lastControl = {};
366                 },
367                 _lineToR: function(result, action, args){
368                         for(var i = 0; i < args.length; i += 2){
369                                 result.push("lineTo", [this.last.x += args[i], this.last.y += args[i + 1]]);
370                         }
371                         this.lastControl = {};
372                 },
373                 _hLineToA: function(result, action, args){
374                         for(var i = 0; i < args.length; ++i){
375                                 result.push("lineTo", [args[i], this.last.y]);
376                         }
377                         this.last.x = args[args.length - 1];
378                         this.lastControl = {};
379                 },
380                 _hLineToR: function(result, action, args){
381                         for(var i = 0; i < args.length; ++i){
382                                 result.push("lineTo", [this.last.x += args[i], this.last.y]);
383                         }
384                         this.lastControl = {};
385                 },
386                 _vLineToA: function(result, action, args){
387                         for(var i = 0; i < args.length; ++i){
388                                 result.push("lineTo", [this.last.x, args[i]]);
389                         }
390                         this.last.y = args[args.length - 1];
391                         this.lastControl = {};
392                 },
393                 _vLineToR: function(result, action, args){
394                         for(var i = 0; i < args.length; ++i){
395                                 result.push("lineTo", [this.last.x, this.last.y += args[i]]);
396                         }
397                         this.lastControl = {};
398                 },
399                 _curveToA: function(result, action, args){
400                         for(var i = 0; i < args.length; i += 6){
401                                 result.push("bezierCurveTo", args.slice(i, i + 6));
402                         }
403                         this.last.x = args[args.length - 2];
404                         this.last.y = args[args.length - 1];
405                         this.lastControl.x = args[args.length - 4];
406                         this.lastControl.y = args[args.length - 3];
407                         this.lastControl.type = "C";
408                 },
409                 _curveToR: function(result, action, args){
410                         for(var i = 0; i < args.length; i += 6){
411                                 result.push("bezierCurveTo", [
412                                         this.last.x + args[i], 
413                                         this.last.y + args[i + 1], 
414                                         this.lastControl.x = this.last.x + args[i + 2], 
415                                         this.lastControl.y = this.last.y + args[i + 3], 
416                                         this.last.x + args[i + 4], 
417                                         this.last.y + args[i + 5]
418                                 ]);
419                                 this.last.x += args[i + 4];
420                                 this.last.y += args[i + 5];
421                         }
422                         this.lastControl.type = "C";
423                 },
424                 _smoothCurveToA: function(result, action, args){
425                         for(var i = 0; i < args.length; i += 4){
426                                 var valid = this.lastControl.type == "C";
427                                 result.push("bezierCurveTo", [
428                                         valid ? 2 * this.last.x - this.lastControl.x : this.last.x, 
429                                         valid ? 2 * this.last.y - this.lastControl.y : this.last.y, 
430                                         args[i], 
431                                         args[i + 1], 
432                                         args[i + 2], 
433                                         args[i + 3]
434                                 ]);
435                                 this.lastControl.x = args[i];
436                                 this.lastControl.y = args[i + 1];
437                                 this.lastControl.type = "C";
438                         }
439                         this.last.x = args[args.length - 2];
440                         this.last.y = args[args.length - 1];
441                 },
442                 _smoothCurveToR: function(result, action, args){
443                         for(var i = 0; i < args.length; i += 4){
444                                 var valid = this.lastControl.type == "C";
445                                 result.push("bezierCurveTo", [
446                                         valid ? 2 * this.last.x - this.lastControl.x : this.last.x, 
447                                         valid ? 2 * this.last.y - this.lastControl.y : this.last.y, 
448                                         this.last.x + args[i], 
449                                         this.last.y + args[i + 1], 
450                                         this.last.x + args[i + 2], 
451                                         this.last.y + args[i + 3]
452                                 ]);
453                                 this.lastControl.x = this.last.x + args[i];
454                                 this.lastControl.y = this.last.y + args[i + 1];
455                                 this.lastControl.type = "C";
456                                 this.last.x += args[i + 2];
457                                 this.last.y += args[i + 3];
458                         }
459                 },
460                 _qCurveToA: function(result, action, args){
461                         for(var i = 0; i < args.length; i += 4){
462                                 result.push("quadraticCurveTo", args.slice(i, i + 4));
463                         }
464                         this.last.x = args[args.length - 2];
465                         this.last.y = args[args.length - 1];
466                         this.lastControl.x = args[args.length - 4];
467                         this.lastControl.y = args[args.length - 3];
468                         this.lastControl.type = "Q";
469                 },
470                 _qCurveToR: function(result, action, args){
471                         for(var i = 0; i < args.length; i += 4){
472                                 result.push("quadraticCurveTo", [
473                                         this.lastControl.x = this.last.x + args[i], 
474                                         this.lastControl.y = this.last.y + args[i + 1], 
475                                         this.last.x + args[i + 2], 
476                                         this.last.y + args[i + 3]
477                                 ]);
478                                 this.last.x += args[i + 2];
479                                 this.last.y += args[i + 3];
480                         }
481                         this.lastControl.type = "Q";
482                 },
483                 _qSmoothCurveToA: function(result, action, args){
484                         for(var i = 0; i < args.length; i += 2){
485                                 var valid = this.lastControl.type == "Q";
486                                 result.push("quadraticCurveTo", [
487                                         this.lastControl.x = valid ? 2 * this.last.x - this.lastControl.x : this.last.x, 
488                                         this.lastControl.y = valid ? 2 * this.last.y - this.lastControl.y : this.last.y, 
489                                         args[i], 
490                                         args[i + 1]
491                                 ]);
492                                 this.lastControl.type = "Q";
493                         }
494                         this.last.x = args[args.length - 2];
495                         this.last.y = args[args.length - 1];
496                 },
497                 _qSmoothCurveToR: function(result, action, args){
498                         for(var i = 0; i < args.length; i += 2){
499                                 var valid = this.lastControl.type == "Q";
500                                 result.push("quadraticCurveTo", [
501                                         this.lastControl.x = valid ? 2 * this.last.x - this.lastControl.x : this.last.x, 
502                                         this.lastControl.y = valid ? 2 * this.last.y - this.lastControl.y : this.last.y, 
503                                         this.last.x + args[i], 
504                                         this.last.y + args[i + 1]
505                                 ]);
506                                 this.lastControl.type = "Q";
507                                 this.last.x += args[i];
508                                 this.last.y += args[i + 1];
509                         }
510                 },
511                 _arcTo: function(result, action, args){
512                         var relative = action == "a";
513                         for(var i = 0; i < args.length; i += 7){
514                                 var x1 = args[i + 5], y1 = args[i + 6];
515                                 if(relative){
516                                         x1 += this.last.x;
517                                         y1 += this.last.y;
518                                 }
519                                 var arcs = ga.arcAsBezier(
520                                         this.last, args[i], args[i + 1], args[i + 2], 
521                                         args[i + 3] ? 1 : 0, args[i + 4] ? 1 : 0,
522                                         x1, y1
523                                 );
524                                 dojo.forEach(arcs, function(p){
525                                         result.push("bezierCurveTo", p);
526                                 });
527                                 this.last.x = x1;
528                                 this.last.y = y1;
529                         }
530                         this.lastControl = {};
531                 },
532                 _closePath: function(result, action, args){
533                         result.push("closePath", []);
534                         this.lastControl = {};
535                 }
536         });
537         dojo.forEach(["moveTo", "lineTo", "hLineTo", "vLineTo", "curveTo", 
538                 "smoothCurveTo", "qCurveTo", "qSmoothCurveTo", "arcTo", "closePath"], 
539                 function(method){ modifyMethod(g.Path, method); }
540         );
541
542         dojo.declare("dojox.gfx.TextPath", g.path.TextPath, {
543                 // summary: a text shape (Canvas)
544                 _renderShape: function(/* Object */ ctx){
545                         var s = this.shape;
546                         // nothing for the moment
547                 }
548         });
549         
550         dojo.declare("dojox.gfx.Surface", gs.Surface, {
551                 // summary: a surface object to be used for drawings (Canvas)
552                 constructor: function(){
553                         gs.Container._init.call(this);
554                         this.pendingImageCount = 0;
555                         this.makeDirty();
556                 },
557                 setDimensions: function(width, height){
558                         // summary: sets the width and height of the rawNode
559                         // width: String: width of surface, e.g., "100px"
560                         // height: String: height of surface, e.g., "100px"
561                         this.width  = g.normalizedLength(width);        // in pixels
562                         this.height = g.normalizedLength(height);       // in pixels
563                         if(!this.rawNode) return this;
564                         this.rawNode.width = width;
565                         this.rawNode.height = height;
566                         this.makeDirty();
567                         return this;    // self
568                 },
569                 getDimensions: function(){
570                         // summary: returns an object with properties "width" and "height"
571                         return this.rawNode ? {width:  this.rawNode.width, height: this.rawNode.height} : null; // Object
572                 },
573                 _render: function(){
574                         // summary: render the all shapes
575                         if(this.pendingImageCount){ return; }
576                         var ctx = this.rawNode.getContext("2d");
577                         ctx.save();
578                         ctx.clearRect(0, 0, this.rawNode.width, this.rawNode.height);
579                         for(var i = 0; i < this.children.length; ++i){
580                                 this.children[i]._render(ctx);
581                         }
582                         ctx.restore();
583                         if("pendingRender" in this){
584                                 clearTimeout(this.pendingRender);
585                                 delete this.pendingRender;
586                         }
587                 },
588                 makeDirty: function(){
589                         // summary: internal method, which is called when we may need to redraw
590                         if(!this.pendingImagesCount && !("pendingRender" in this)){
591                                 this.pendingRender = setTimeout(dojo.hitch(this, this._render), 0);
592                         }
593                 },
594                 downloadImage: function(img, url){
595                         // summary: 
596                         //              internal method, which starts an image download and renders, when it is ready
597                         // img: Image:
598                         //              the image object
599                         // url: String:
600                         //              the url of the image
601                         var handler = dojo.hitch(this, this.onImageLoad);
602                         if(!this.pendingImageCount++ && "pendingRender" in this){
603                                 clearTimeout(this.pendingRender);
604                                 delete this.pendingRender;
605                         }
606                         img.onload  = handler;
607                         img.onerror = handler;
608                         img.onabort = handler;
609                         img.src = url;
610                 },
611                 onImageLoad: function(){
612                         if(!--this.pendingImageCount){ this._render(); }
613                 },
614
615                 // events are not implemented
616                 getEventSource: function(){ return null; },
617                 connect:                function(){},
618                 disconnect:             function(){}
619         });
620
621         g.createSurface = function(parentNode, width, height){
622                 // summary: creates a surface (Canvas)
623                 // parentNode: Node: a parent node
624                 // width: String: width of surface, e.g., "100px"
625                 // height: String: height of surface, e.g., "100px"
626
627                 if(!width){ width = "100%"; }
628                 if(!height){ height = "100%"; }
629                 var s = new g.Surface(),
630                         p = dojo.byId(parentNode),
631                         c = p.ownerDocument.createElement("canvas");
632                 c.width  = width;
633                 c.height = height;
634                 p.appendChild(c);
635                 s.rawNode = c;
636                 s.surface = s;
637                 return s;       // dojox.gfx.Surface
638         };
639         
640         // Extenders
641         
642         var C = gs.Container, Container = {
643                 add: function(shape){
644                         this.surface.makeDirty();
645                         return C.add.apply(this, arguments);
646                 },
647                 remove: function(shape, silently){
648                         this.surface.makeDirty();
649                         return C.remove.apply(this, arguments);
650                 },
651                 clear: function(){
652                         this.surface.makeDirty();
653                         return C.clear.apply(this, arguments);
654                 },
655                 _moveChildToFront: function(shape){
656                         this.surface.makeDirty();
657                         return C._moveChildToFront.apply(this, arguments);
658                 },
659                 _moveChildToBack: function(shape){
660                         this.surface.makeDirty();
661                         return C._moveChildToBack.apply(this, arguments);
662                 }
663         };
664
665         dojo.mixin(gs.Creator, {
666                 // summary: Canvas shape creators
667                 createObject: function(shapeType, rawShape) {
668                         // summary: creates an instance of the passed shapeType class
669                         // shapeType: Function: a class constructor to create an instance of
670                         // rawShape: Object: properties to be passed in to the classes "setShape" method
671                         // overrideSize: Boolean: set the size explicitly, if true
672                         var shape = new shapeType();
673                         shape.surface = this.surface;
674                         shape.setShape(rawShape);
675                         this.add(shape);
676                         return shape;   // dojox.gfx.Shape
677                 }
678         });
679
680         dojo.extend(g.Group, Container);
681         dojo.extend(g.Group, gs.Creator);
682
683         dojo.extend(g.Surface, Container);
684         dojo.extend(g.Surface, gs.Creator);
685 })();
686
687 }