source: Dev/trunk/src/client/dojox/gfx/canvas.js @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 39.6 KB
Line 
1define(["./_base", "dojo/_base/lang", "dojo/_base/array", "dojo/_base/declare", "dojo/_base/window", "dojo/dom-geometry",
2                "dojo/dom", "./shape", "./path", "./arc", "./matrix", "./decompose", "./bezierutils"],
3function(g, lang, arr, declare, win, domGeom, dom, gs, pathLib, ga, m, decompose, bezierUtils ){
4        var canvas = g.canvas = {
5                // summary:
6                //              This the graphics rendering bridge for W3C Canvas compliant browsers.
7                //              Since Canvas is an immediate mode graphics api, with no object graph or
8                //              eventing capabilities, use of this module alone will only add in drawing support.
9                //              The additional module, canvasWithEvents extends this module with additional support
10                //              for handling events on Canvas.  By default, the support for events is now included
11                //              however, if only drawing capabilities are needed, canvas event module can be disabled
12                //              using the dojoConfig option, canvasEvents:true|false.
13                //              The id of the Canvas renderer is 'canvas'.  This id can be used when switch Dojo's
14                //              graphics context between renderer implementations.  See dojox/gfx/_base.switchRenderer
15                //              API.
16        };
17        var pattrnbuffer = null,
18                mp = m.multiplyPoint,
19                pi = Math.PI,
20                twoPI = 2 * pi,
21                halfPI = pi /2,
22                extend = lang.extend;
23
24        if(win.global.CanvasRenderingContext2D){
25                var ctx2d = win.doc.createElement("canvas").getContext("2d");
26                var hasNativeDash = typeof ctx2d.setLineDash == "function";
27                var hasFillText = typeof ctx2d.fillText == "function";
28        }
29
30        var dasharray = {
31                solid:                          "none",
32                shortdash:                      [4, 1],
33                shortdot:                       [1, 1],
34                shortdashdot:           [4, 1, 1, 1],
35                shortdashdotdot:        [4, 1, 1, 1, 1, 1],
36                dot:                            [1, 3],
37                dash:                           [4, 3],
38                longdash:                       [8, 3],
39                dashdot:                        [4, 3, 1, 3],
40                longdashdot:            [8, 3, 1, 3],
41                longdashdotdot:         [8, 3, 1, 3, 1, 3]
42        };
43
44        function drawDashedArc(/*CanvasRenderingContext2D*/ctx, /*Number[]*/dash,  /*int*/cx,  /*int*/cy,  /*int*/r, /*Number*/sa, /*Number*/ea, /*Boolean*/ccw, /*Boolean?*/apply, prevResidue){
45                var residue, angle, l = dash.length, i= 0;
46                // if there's a previous dash residue from the previous arc, start with it.
47                if(prevResidue){
48                        angle = prevResidue.l/r;
49                        i = prevResidue.i;
50                }else{
51                        angle = dash[0]/r;
52                }
53                while(sa < ea){
54                        // if the dash segment length is longer than what remains to stroke, keep it for next arc. (aka residue)
55                        if(sa+angle > ea){
56                                residue = {l: (sa+angle-ea)*r, i: i};
57                                angle = ea-sa;
58                        }
59                        if(!(i%2)){
60                                ctx.beginPath();
61                                ctx.arc(cx, cy, r, sa, sa+angle, ccw);
62                                if(apply) ctx.stroke();
63                        }
64                        sa += angle;
65                        ++i;
66                        angle = dash[i%l]/r;
67                }
68                return residue;
69        }
70
71        function splitToDashedBezier(/*Number[]*/points, /*Number[]*/dashArray, /*Number[]*/newPoints, /*Object*/prevResidue){
72                var residue = 0, t = 0, dash, i = 0;
73                if(prevResidue){
74                        dash = prevResidue.l;
75                        i = prevResidue.i;
76                }else{
77                        dash = dashArray[0];
78                }
79                while(t<1){
80                        // get the 't' corresponding to the given dash value.
81                        t = bezierUtils.tAtLength(points, dash);
82                        if(t==1){
83                                var rl = bezierUtils.computeLength(points);
84                                residue = {l: dash-rl, i: i};
85                        }
86                        // split bezier at t: left part is the "dash" curve, right part is the remaining bezier points
87                        var curves = bezierUtils.splitBezierAtT(points, t);
88                        if(!(i%2)){
89                                // only keep the "dash" curve
90                                newPoints.push(curves[0]);
91                        }
92                        points = curves[1];
93                        ++i;
94                        dash = dashArray[i%dashArray.length];
95                }
96                return residue;
97        }
98
99        function toDashedCurveTo(/*Array||CanvasRenderingContext2D*/ctx, /*shape.Path*/shape, /*Number[]*/points, /*Object*/prevResidue){
100                // summary:
101                //              Builds a set of bezier (cubic || quadratic)curveTo' canvas instructions that represents a dashed stroke of the specified bezier geometry.
102
103                var pts = [shape.last.x, shape.last.y].concat(points),
104                        quadratic = points.length === 4, ctx2d = !(ctx instanceof Array),
105                        api = quadratic ? "quadraticCurveTo" : "bezierCurveTo",
106                        curves = [];
107                var residue = splitToDashedBezier(pts, shape.canvasDash, curves, prevResidue);
108                for(var c=0; c<curves.length;++c){
109                        var curve = curves[c];
110                        if(ctx2d){
111                                ctx.moveTo(curve[0], curve[1]);
112                                ctx[api].apply(ctx, curve.slice(2));
113                        }else{
114                                ctx.push("moveTo", [curve[0], curve[1]]);
115                                ctx.push(api, curve.slice(2));
116                        }
117                }
118                return residue;
119        }
120
121        function toDashedLineTo(/*Array||CanvasRenderingContext2D*/ctx, /*shape.Shape*/shape, /*int*/x1, /*int*/y1, /*int*/x2, /*int*/y2, /*Object*/prevResidue){
122                // summary:
123                //              Builds a set of moveTo/lineTo' canvas instructions that represents a dashed stroke of the specified line geometry.
124
125                var residue = 0, r = 0, dal = 0, tlength = bezierUtils.distance(x1, y1, x2, y2), i = 0, dash = shape.canvasDash,
126                        prevx = x1, prevy = y1, x, y, ctx2d = !(ctx instanceof Array);
127                if(prevResidue){
128                        dal=prevResidue.l;
129                        i = prevResidue.i;
130                }else{
131                        dal += dash[0];
132                }
133                while(Math.abs(1-r)>0.01){
134                        if(dal>tlength){
135                                residue = {l:dal-tlength,i:i};
136                                dal=tlength;
137                        }
138                        r = dal/tlength;
139                        x = x1 + (x2-x1)*r;
140                        y = y1 + (y2-y1)*r;
141                        if(!(i++%2)){
142                                if(ctx2d){
143                                        ctx.moveTo(prevx, prevy);
144                                        ctx.lineTo(x, y);
145                                }else{
146                                        ctx.push("moveTo", [prevx, prevy]);
147                                        ctx.push("lineTo", [x, y]);
148                                }
149                        }
150                        prevx = x;
151                        prevy = y;
152                        dal += dash[i%dash.length];
153                }
154                return residue;
155        }
156
157        canvas.Shape = declare("dojox.gfx.canvas.Shape", gs.Shape, {
158                _render: function(/* Object */ ctx){
159                        // summary:
160                        //              render the shape
161                        ctx.save();
162                        this._renderTransform(ctx);
163                        this._renderClip(ctx);
164                        this._renderShape(ctx);
165                        this._renderFill(ctx, true);
166                        this._renderStroke(ctx, true);
167                        ctx.restore();
168                },
169                _renderClip: function(ctx){
170                        if (this.canvasClip){
171                                this.canvasClip.render(ctx);
172                                ctx.clip();
173                        }
174                },
175                _renderTransform: function(/* Object */ ctx){
176                        if("canvasTransform" in this){
177                                var t = this.canvasTransform;
178                                ctx.translate(t.dx, t.dy);
179                                ctx.rotate(t.angle2);
180                                ctx.scale(t.sx, t.sy);
181                                ctx.rotate(t.angle1);
182                                // The future implementation when vendors catch up with the spec:
183                                // var t = this.matrix;
184                                // ctx.transform(t.xx, t.yx, t.xy, t.yy, t.dx, t.dy);
185                        }
186                },
187                _renderShape: function(/* Object */ ctx){
188                        // nothing
189                },
190                _renderFill: function(/* Object */ ctx, /* Boolean */ apply){
191                        if("canvasFill" in this){
192                                var fs = this.fillStyle;
193                                if("canvasFillImage" in this){
194                                        var w = fs.width, h = fs.height,
195                                                iw = this.canvasFillImage.width, ih = this.canvasFillImage.height,
196                                                // let's match the svg default behavior wrt. aspect ratio: xMidYMid meet
197                                                sx = w == iw ? 1 : w / iw,
198                                                sy = h == ih ? 1 : h / ih,
199                                                s = Math.min(sx,sy), //meet->math.min , slice->math.max
200                                                dx = (w - s * iw)/2,
201                                                dy = (h - s * ih)/2;
202                                        // the buffer used to scaled the image
203                                        pattrnbuffer.width = w; pattrnbuffer.height = h;
204                                        var copyctx = pattrnbuffer.getContext("2d");
205                                        copyctx.clearRect(0, 0, w, h);
206                                        copyctx.drawImage(this.canvasFillImage, 0, 0, iw, ih, dx, dy, s*iw, s*ih);
207                                        this.canvasFill = ctx.createPattern(pattrnbuffer, "repeat");
208                                        delete this.canvasFillImage;
209                                }
210                                ctx.fillStyle = this.canvasFill;
211                                if(apply){
212                                        // offset the pattern
213                                        if (fs.type==="pattern" && (fs.x !== 0 || fs.y !== 0)) {
214                                                ctx.translate(fs.x,fs.y);
215                                        }
216                                        ctx.fill();
217                                }
218                        }else{
219                                ctx.fillStyle = "rgba(0,0,0,0.0)";
220                        }
221                },
222                _renderStroke: function(/* Object */ ctx, /* Boolean */ apply){
223                        var s = this.strokeStyle;
224                        if(s){
225                                ctx.strokeStyle = s.color.toString();
226                                ctx.lineWidth = s.width;
227                                ctx.lineCap = s.cap;
228                                if(typeof s.join == "number"){
229                                        ctx.lineJoin = "miter";
230                                        ctx.miterLimit = s.join;
231                                }else{
232                                        ctx.lineJoin = s.join;
233                                }
234                                if(this.canvasDash){
235                                        if(hasNativeDash){
236                                                ctx.setLineDash(this.canvasDash);
237                                                if(apply){ ctx.stroke(); }
238                                        }else{
239                                                this._renderDashedStroke(ctx, apply);
240                                        }
241                                }else{
242                                        if(apply){ ctx.stroke(); }
243                                }
244                        }else if(!apply){
245                                ctx.strokeStyle = "rgba(0,0,0,0.0)";
246                        }
247                },
248                _renderDashedStroke: function(ctx, apply){},
249
250                // events are not implemented
251                getEventSource: function(){ return null; },
252                on:                             function(){},
253                connect:                function(){},
254                disconnect:             function(){},
255
256                canvasClip:null,
257                setClip: function(/*Object*/clip){
258                        this.inherited(arguments);
259                        var clipType = clip ? "width" in clip ? "rect" :
260                                                        "cx" in clip ? "ellipse" :
261                                                        "points" in clip ? "polyline" : "d" in clip ? "path" : null : null;
262                        if(clip && !clipType){
263                                return this;
264                        }
265                        this.canvasClip = clip ? makeClip(clipType, clip) : null;
266                        if(this.parent){this.parent._makeDirty();}
267                        return this;
268                }
269        });
270
271        var makeClip = function(clipType, geometry){
272                switch(clipType){
273                        case "ellipse":
274                                return {
275                                        canvasEllipse: makeEllipse({shape:geometry}),
276                                        render: function(ctx){return canvas.Ellipse.prototype._renderShape.call(this, ctx);}
277                                };
278                        case "rect":
279                                return {
280                                        shape: lang.delegate(geometry,{r:0}),
281                                        render: function(ctx){return canvas.Rect.prototype._renderShape.call(this, ctx);}
282                                };
283                        case "path":
284                                return {
285                                        canvasPath: makeClipPath(geometry),
286                                        render: function(ctx){this.canvasPath._renderShape(ctx);}
287                                };
288                        case "polyline":
289                                return {
290                                        canvasPolyline: geometry.points,
291                                        render: function(ctx){return canvas.Polyline.prototype._renderShape.call(this, ctx);}
292                                };
293                }
294                return null;
295        };
296
297        var makeClipPath = function(geo){
298                var p = new dojox.gfx.canvas.Path();
299                p.canvasPath = [];
300                p._setPath(geo.d);
301                return p;
302        };
303
304        var modifyMethod = function(shape, method, extra){
305                var old = shape.prototype[method];
306                shape.prototype[method] = extra ?
307                        function(){
308                                if(this.parent){this.parent._makeDirty();}
309                                old.apply(this, arguments);
310                                extra.call(this);
311                                return this;
312                        } :
313                        function(){
314                                if(this.parent){this.parent._makeDirty();}
315                                return old.apply(this, arguments);
316                        };
317        };
318
319        modifyMethod(canvas.Shape, "setTransform",
320                function(){
321                        // prepare Canvas-specific structures
322                        if(this.matrix){
323                                this.canvasTransform = g.decompose(this.matrix);
324                        }else{
325                                delete this.canvasTransform;
326                        }
327                });
328
329        modifyMethod(canvas.Shape, "setFill",
330                function(){
331                        // prepare Canvas-specific structures
332                        var fs = this.fillStyle, f;
333                        if(fs){
334                                if(typeof(fs) == "object" && "type" in fs){
335                                        var ctx = this.surface.rawNode.getContext("2d");
336                                        switch(fs.type){
337                                                case "linear":
338                                                case "radial":
339                                                        f = fs.type == "linear" ?
340                                                                ctx.createLinearGradient(fs.x1, fs.y1, fs.x2, fs.y2) :
341                                                                ctx.createRadialGradient(fs.cx, fs.cy, 0, fs.cx, fs.cy, fs.r);
342                                                        arr.forEach(fs.colors, function(step){
343                                                                f.addColorStop(step.offset, g.normalizeColor(step.color).toString());
344                                                        });
345                                                        break;
346                                                case "pattern":
347                                                        if (!pattrnbuffer) {
348                                                                pattrnbuffer = document.createElement("canvas");
349                                                        }
350                                                        // no need to scale the image since the canvas.createPattern uses
351                                                        // the original image data and not the scaled ones (see spec.)
352                                                        // the scaling needs to be done at rendering time in a context buffer
353                                                        var img =new Image();
354                                                        this.surface.downloadImage(img, fs.src);
355                                                        this.canvasFillImage = img;
356                                        }
357                                }else{
358                                        // Set fill color using CSS RGBA func style
359                                        f = fs.toString();
360                                }
361                                this.canvasFill = f;
362                        }else{
363                                delete this.canvasFill;
364                        }
365                });
366
367        modifyMethod(canvas.Shape, "setStroke",
368                function(){
369                        var st = this.strokeStyle;
370                        if(st){
371                                var da = this.strokeStyle.style.toLowerCase();
372                                if(da in dasharray){
373                                        da = dasharray[da];
374                                }
375                                if(da instanceof Array){
376                                        da = da.slice();
377                                        this.canvasDash = da;
378                                        var i;
379                                        for(i = 0; i < da.length; ++i){
380                                                da[i] *= st.width;
381                                        }
382                                        if(st.cap != "butt"){
383                                                for(i = 0; i < da.length; i += 2){
384                                                        da[i] -= st.width;
385                                                        if(da[i] < 1){ da[i] = 1; }
386                                                }
387                                                for(i = 1; i < da.length; i += 2){
388                                                        da[i] += st.width;
389                                                }
390                                        }
391                                }else{
392                                        delete this.canvasDash;
393                                }
394                        }else{
395                                delete this.canvasDash;
396                        }
397                        this._needsDash = !hasNativeDash && !!this.canvasDash;
398                });
399
400        modifyMethod(canvas.Shape, "setShape");
401
402        canvas.Group = declare("dojox.gfx.canvas.Group", canvas.Shape, {
403                // summary:
404                //              a group shape (Canvas), which can be used
405                //              to logically group shapes (e.g, to propagate matricies)
406                constructor: function(){
407                        gs.Container._init.call(this);
408                },
409                _render: function(/* Object */ ctx){
410                        // summary:
411                        //              render the group
412                        ctx.save();
413                        this._renderTransform(ctx);
414                        this._renderClip(ctx);
415                        for(var i = 0; i < this.children.length; ++i){
416                                this.children[i]._render(ctx);
417                        }
418                        ctx.restore();
419                },
420                destroy: function(){
421                        // summary:
422                        //              Releases all internal resources owned by this shape. Once this method has been called,
423                        //              the instance is considered disposed and should not be used anymore.
424
425                        // don't call canvas impl to avoid makeDirty'
426                        gs.Container.clear.call(this, true);
427                        // avoid this.inherited
428                        canvas.Shape.prototype.destroy.apply(this, arguments);
429                }
430        });
431
432
433
434        canvas.Rect = declare("dojox.gfx.canvas.Rect", [canvas.Shape, gs.Rect], {
435                // summary:
436                //              a rectangle shape (Canvas)
437                _renderShape: function(/* Object */ ctx){
438                        var s = this.shape, r = Math.min(s.r, s.height / 2, s.width / 2),
439                                xl = s.x, xr = xl + s.width, yt = s.y, yb = yt + s.height,
440                                xl2 = xl + r, xr2 = xr - r, yt2 = yt + r, yb2 = yb - r;
441                        ctx.beginPath();
442                        ctx.moveTo(xl2, yt);
443                        if(r){
444                                ctx.arc(xr2, yt2, r, -halfPI, 0, false);
445                                ctx.arc(xr2, yb2, r, 0, halfPI, false);
446                                ctx.arc(xl2, yb2, r, halfPI, pi, false);
447                                ctx.arc(xl2, yt2, r, pi, pi + halfPI, false);
448                        }else{
449                                ctx.lineTo(xr2, yt);
450                                ctx.lineTo(xr, yb2);
451                                ctx.lineTo(xl2, yb);
452                                ctx.lineTo(xl, yt2);
453                        }
454                        ctx.closePath();
455                },
456                _renderDashedStroke: function(ctx, apply){
457                        var s = this.shape, residue, r = Math.min(s.r, s.height / 2, s.width / 2),
458                                xl = s.x, xr = xl + s.width, yt = s.y, yb = yt + s.height,
459                                xl2 = xl + r, xr2 = xr - r, yt2 = yt + r, yb2 = yb - r;
460                        if(r){
461                                ctx.beginPath();
462                                residue = toDashedLineTo(ctx, this, xl2, yt, xr2, yt);
463                                if(apply) ctx.stroke();
464                                residue = drawDashedArc(ctx, this.canvasDash, xr2, yt2, r, -halfPI, 0, false, apply, residue);
465                                ctx.beginPath();
466                                residue = toDashedLineTo(ctx, this, xr, yt2, xr, yb2, residue);
467                                if(apply) ctx.stroke();
468                                residue = drawDashedArc(ctx, this.canvasDash, xr2, yb2, r, 0, halfPI, false, apply, residue);
469                                ctx.beginPath();
470                                residue = toDashedLineTo(ctx, this, xr2, yb, xl2, yb, residue);
471                                if(apply) ctx.stroke();
472                                residue = drawDashedArc(ctx, this.canvasDash, xl2, yb2, r, halfPI, pi, false, apply, residue);
473                                ctx.beginPath();
474                                residue = toDashedLineTo(ctx, this, xl, yb2, xl, yt2,residue);
475                                if(apply) ctx.stroke();
476                                drawDashedArc(ctx, this.canvasDash, xl2, yt2, r, pi, pi + halfPI, false, apply, residue);
477                        }else{
478                                ctx.beginPath();
479                                residue = toDashedLineTo(ctx, this, xl2, yt, xr2, yt);
480                                residue = toDashedLineTo(ctx, this, xr2, yt, xr, yb2, residue);
481                                residue = toDashedLineTo(ctx, this, xr, yb2, xl2, yb, residue);
482                                toDashedLineTo(ctx, this, xl2, yb, xl, yt2, residue);
483                                if(apply) ctx.stroke();
484                        }
485                }
486        });
487
488        var bezierCircle = [];
489        (function(){
490                var u = ga.curvePI4;
491                bezierCircle.push(u.s, u.c1, u.c2, u.e);
492                for(var a = 45; a < 360; a += 45){
493                        var r = m.rotateg(a);
494                        bezierCircle.push(mp(r, u.c1), mp(r, u.c2), mp(r, u.e));
495                }
496        })();
497
498        var makeEllipse = function(shape){
499                // prepare Canvas-specific structures
500                var t, c1, c2, r = [], s = shape.shape,
501                        M = m.normalize([m.translate(s.cx, s.cy), m.scale(s.rx, s.ry)]);
502                t = mp(M, bezierCircle[0]);
503                r.push([t.x, t.y]);
504                for(var i = 1; i < bezierCircle.length; i += 3){
505                        c1 = mp(M, bezierCircle[i]);
506                        c2 = mp(M, bezierCircle[i + 1]);
507                        t  = mp(M, bezierCircle[i + 2]);
508                        r.push([c1.x, c1.y, c2.x, c2.y, t.x, t.y]);
509                }
510                if(shape._needsDash){
511                        var points = [], p1 = r[0];
512                        for(i = 1; i < r.length; ++i){
513                                var curves = [];
514                                splitToDashedBezier(p1.concat(r[i]), shape.canvasDash, curves);
515                                p1 = [r[i][4],r[i][5]];
516                                points.push(curves);
517                        }
518                        shape._dashedPoints = points;
519                }
520                return r;
521        };
522
523        canvas.Ellipse = declare("dojox.gfx.canvas.Ellipse", [canvas.Shape, gs.Ellipse], {
524                // summary:
525                //              an ellipse shape (Canvas)
526                setShape: function(){
527                        this.inherited(arguments);
528                        this.canvasEllipse = makeEllipse(this);
529                        return this;
530                },
531                setStroke: function(){
532                        this.inherited(arguments);
533                        if(!hasNativeDash){
534                                this.canvasEllipse = makeEllipse(this);
535                        }
536                        return this;
537                },
538                _renderShape: function(/* Object */ ctx){
539                        var r = this.canvasEllipse, i;
540                        ctx.beginPath();
541                        ctx.moveTo.apply(ctx, r[0]);
542                        for(i = 1; i < r.length; ++i){
543                                ctx.bezierCurveTo.apply(ctx, r[i]);
544                        }
545                        ctx.closePath();
546                },
547                _renderDashedStroke: function(ctx, apply){
548                        var r = this._dashedPoints;
549                        ctx.beginPath();
550                        for(var i = 0; i < r.length; ++i){
551                                var curves = r[i];
552                                for(var j=0;j<curves.length;++j){
553                                        var curve = curves[j];
554                                        ctx.moveTo(curve[0], curve[1]);
555                                        ctx.bezierCurveTo(curve[2],curve[3],curve[4],curve[5],curve[6],curve[7]);
556                                }
557                        }
558                        if(apply) ctx.stroke();
559                }
560        });
561
562        canvas.Circle = declare("dojox.gfx.canvas.Circle", [canvas.Shape, gs.Circle], {
563                // summary:
564                //              a circle shape (Canvas)
565                _renderShape: function(/* Object */ ctx){
566                        var s = this.shape;
567                        ctx.beginPath();
568                        ctx.arc(s.cx, s.cy, s.r, 0, twoPI, 1);
569                },
570                _renderDashedStroke: function(ctx, apply){
571                        var s = this.shape;
572                        var startAngle = 0, angle, l = this.canvasDash.length; i=0;
573                        while(startAngle < twoPI){
574                                angle = this.canvasDash[i%l]/s.r;
575                                if(!(i%2)){
576                                        ctx.beginPath();
577                                        ctx.arc(s.cx, s.cy, s.r, startAngle, startAngle+angle, 0);
578                                        if(apply) ctx.stroke();
579                                }
580                                startAngle+=angle;
581                                ++i;
582                        }
583                }
584        });
585
586        canvas.Line = declare("dojox.gfx.canvas.Line", [canvas.Shape, gs.Line], {
587                // summary:
588                //              a line shape (Canvas)
589                _renderShape: function(/* Object */ ctx){
590                        var s = this.shape;
591                        ctx.beginPath();
592                        ctx.moveTo(s.x1, s.y1);
593                        ctx.lineTo(s.x2, s.y2);
594                },
595                _renderDashedStroke: function(ctx, apply){
596                        var s = this.shape;
597                        ctx.beginPath();
598                        toDashedLineTo(ctx, this, s.x1, s.y1, s.x2, s.y2);
599                        if(apply) ctx.stroke();
600                }
601        });
602
603        canvas.Polyline = declare("dojox.gfx.canvas.Polyline", [canvas.Shape, gs.Polyline], {
604                // summary:
605                //              a polyline/polygon shape (Canvas)
606                setShape: function(){
607                        this.inherited(arguments);
608                        var p = this.shape.points, f = p[0], r, c, i;
609                        this.bbox = null;
610                        // normalize this.shape.points as array of points: [{x,y}, {x,y}, ...]
611                        this._normalizePoints();
612                        // after _normalizePoints, if shape.points was [x1,y1,x2,y2,..], shape.points references a new array
613                        // and p references the original points array
614                        // prepare Canvas-specific structures, if needed
615                        if(p.length){
616                                if(typeof f == "number"){ // already in the canvas format [x1,y1,x2,y2,...]
617                                        r = p;
618                                }else{ // convert into canvas-specific format
619                                        r = [];
620                                        for(i=0; i < p.length; ++i){
621                                                c = p[i];
622                                                r.push(c.x, c.y);
623                                        }
624                                }
625                        }else{
626                                r = [];
627                        }
628                        this.canvasPolyline = r;
629                        return this;
630                },
631                _renderShape: function(/* Object */ ctx){
632                        var p = this.canvasPolyline;
633                        if(p.length){
634                                ctx.beginPath();
635                                ctx.moveTo(p[0], p[1]);
636                                for(var i = 2; i < p.length; i += 2){
637                                        ctx.lineTo(p[i], p[i + 1]);
638                                }
639                        }
640                },
641                _renderDashedStroke: function(ctx, apply){
642                        var p = this.canvasPolyline, residue = 0;
643                        ctx.beginPath();
644                        for(var i = 0; i < p.length; i += 2){
645                                residue = toDashedLineTo(ctx, this, p[i], p[i + 1], p[i + 2], p[i + 3], residue);
646                        }
647                        if(apply) ctx.stroke();
648                }
649        });
650
651        canvas.Image = declare("dojox.gfx.canvas.Image", [canvas.Shape, gs.Image], {
652                // summary:
653                //              an image shape (Canvas)
654                setShape: function(){
655                        this.inherited(arguments);
656                        // prepare Canvas-specific structures
657                        var img = new Image();
658                        this.surface.downloadImage(img, this.shape.src);
659                        this.canvasImage = img;
660                        return this;
661                },
662                _renderShape: function(/* Object */ ctx){
663                        var s = this.shape;
664                        ctx.drawImage(this.canvasImage, s.x, s.y, s.width, s.height);
665                }
666        });
667
668        canvas.Text = declare("dojox.gfx.canvas.Text", [canvas.Shape, gs.Text], {
669                _setFont:function(){
670                        if(this.fontStyle){
671                                this.canvasFont = g.makeFontString(this.fontStyle);
672                        }else{
673                                delete this.canvasFont;
674                        }
675                },
676
677                getTextWidth: function(){
678                        // summary:
679                        //              get the text width in pixels
680                        var s = this.shape, w = 0, ctx;
681                        if(s.text){
682                                ctx = this.surface.rawNode.getContext("2d");
683                                ctx.save();
684                                this._renderTransform(ctx);
685                                this._renderFill(ctx, false);
686                                this._renderStroke(ctx, false);
687                                if (this.canvasFont)
688                                        ctx.font = this.canvasFont;
689                                w = ctx.measureText(s.text).width;
690                                ctx.restore();
691                        }
692                        return w;
693                },
694
695                // override to apply first fill and stroke (
696                // the base implementation is for path-based shape that needs to first define the path then to fill/stroke it.
697                // Here, we need the fillstyle or strokestyle to be set before calling fillText/strokeText.
698                _render: function(/* Object */ctx){
699                        // summary:
700                        //              render the shape
701                        // ctx: Object
702                        //              the drawing context.
703                        ctx.save();
704                        this._renderTransform(ctx);
705                        this._renderFill(ctx, false);
706                        this._renderStroke(ctx, false);
707                        this._renderShape(ctx);
708                        ctx.restore();
709                },
710
711                _renderShape: function(ctx){
712                        // summary:
713                        //              a text shape (Canvas)
714                        // ctx: Object
715                        //              the drawing context.
716                        var ta, s = this.shape;
717                        if(!s.text){
718                                return;
719                        }
720                        // text align
721                        ta = s.align === 'middle' ? 'center' : s.align;
722                        ctx.textAlign = ta;
723                        if(this.canvasFont){
724                                ctx.font = this.canvasFont;
725                        }
726                        if(this.canvasFill){
727                                ctx.fillText(s.text, s.x, s.y);
728                        }
729                        if(this.strokeStyle){
730                                ctx.beginPath(); // fix bug in FF3.6. Fixed in FF4b8
731                                ctx.strokeText(s.text, s.x, s.y);
732                                ctx.closePath();
733                        }
734                }
735        });
736        modifyMethod(canvas.Text, "setFont");
737
738        if(!hasFillText){
739                canvas.Text.extend({
740                        getTextWidth: function(){
741                                return 0;
742                        },
743                        getBoundingBox: function(){
744                                return null;
745                        },
746                        _renderShape: function(){
747                        }
748                });
749        }
750
751        var pathRenderers = {
752                        M: "_moveToA", m: "_moveToR",
753                        L: "_lineToA", l: "_lineToR",
754                        H: "_hLineToA", h: "_hLineToR",
755                        V: "_vLineToA", v: "_vLineToR",
756                        C: "_curveToA", c: "_curveToR",
757                        S: "_smoothCurveToA", s: "_smoothCurveToR",
758                        Q: "_qCurveToA", q: "_qCurveToR",
759                        T: "_qSmoothCurveToA", t: "_qSmoothCurveToR",
760                        A: "_arcTo", a: "_arcTo",
761                        Z: "_closePath", z: "_closePath"
762                };
763
764
765        canvas.Path = declare("dojox.gfx.canvas.Path", [canvas.Shape, pathLib.Path], {
766                // summary:
767                //              a path shape (Canvas)
768                constructor: function(){
769                        this.lastControl = {};
770                },
771                setShape: function(){
772                        this.canvasPath = [];
773                        this._dashedPath= [];
774                        return this.inherited(arguments);
775                },
776                setStroke:function(){
777                        this.inherited(arguments);
778                        if(!hasNativeDash){
779                                this.segmented = false;
780                                this._confirmSegmented();
781                        }
782                        return this;
783                },
784                _setPath: function(){
785                        this._dashResidue = null;
786                        this.inherited(arguments);
787                },
788                _updateWithSegment: function(segment){
789                        var last = lang.clone(this.last);
790                        this[pathRenderers[segment.action]](this.canvasPath, segment.action, segment.args, this._needsDash ? this._dashedPath : null);
791                        this.last = last;
792                        this.inherited(arguments);
793                },
794                _renderShape: function(/* Object */ ctx){
795                        var r = this.canvasPath;
796                        ctx.beginPath();
797                        for(var i = 0; i < r.length; i += 2){
798                                ctx[r[i]].apply(ctx, r[i + 1]);
799                        }
800                },
801                _renderDashedStroke: hasNativeDash ? function(){} : function(ctx, apply){
802                        var r = this._dashedPath;
803                        ctx.beginPath();
804                        for(var i = 0; i < r.length; i += 2){
805                                ctx[r[i]].apply(ctx, r[i + 1]);
806                        }
807                        if(apply) ctx.stroke();
808                },
809                _moveToA: function(result, action, args, doDash){
810                        result.push("moveTo", [args[0], args[1]]);
811                        if(doDash) doDash.push("moveTo", [args[0], args[1]]);
812                        for(var i = 2; i < args.length; i += 2){
813                                result.push("lineTo", [args[i], args[i + 1]]);
814                                if(doDash)
815                                        this._dashResidue = toDashedLineTo(doDash, this, args[i - 2], args[i - 1], args[i], args[i + 1], this._dashResidue);
816                        }
817                        this.last.x = args[args.length - 2];
818                        this.last.y = args[args.length - 1];
819                        this.lastControl = {};
820                },
821                _moveToR: function(result, action, args, doDash){
822                        var pts;
823                        if("x" in this.last){
824                                pts = [this.last.x += args[0], this.last.y += args[1]];
825                                result.push("moveTo", pts);
826                                if(doDash) doDash.push("moveTo", pts);
827                        }else{
828                                pts = [this.last.x = args[0], this.last.y = args[1]];
829                                result.push("moveTo", pts);
830                                if(doDash) doDash.push("moveTo", pts);
831                        }
832                        for(var i = 2; i < args.length; i += 2){
833                                result.push("lineTo", [this.last.x += args[i], this.last.y += args[i + 1]]);
834                                if(doDash)
835                                        this._dashResidue = toDashedLineTo(doDash, this, doDash[doDash.length - 1][0], doDash[doDash.length - 1][1], this.last.x, this.last.y, this._dashResidue);
836                        }
837                        this.lastControl = {};
838                },
839                _lineToA: function(result, action, args, doDash){
840                        for(var i = 0; i < args.length; i += 2){
841                                if(doDash)
842                                        this._dashResidue = toDashedLineTo(doDash, this, this.last.x, this.last.y, args[i], args[i + 1], this._dashResidue);
843                                result.push("lineTo", [args[i], args[i + 1]]);
844                        }
845                        this.last.x = args[args.length - 2];
846                        this.last.y = args[args.length - 1];
847                        this.lastControl = {};
848                },
849                _lineToR: function(result, action, args, doDash){
850                        for(var i = 0; i < args.length; i += 2){
851                                result.push("lineTo", [this.last.x += args[i], this.last.y += args[i + 1]]);
852                                if(doDash)
853                                        this._dashResidue = toDashedLineTo(doDash, this, doDash[doDash.length - 1][0], doDash[doDash.length - 1][1], this.last.x, this.last.y, this._dashResidue);
854                        }
855                        this.lastControl = {};
856                },
857                _hLineToA: function(result, action, args, doDash){
858                        for(var i = 0; i < args.length; ++i){
859                                result.push("lineTo", [args[i], this.last.y]);
860                                if(doDash)
861                                        this._dashResidue = toDashedLineTo(doDash, this, doDash[doDash.length - 1][0], doDash[doDash.length - 1][1], args[i], this.last.y, this._dashResidue);
862                        }
863                        this.last.x = args[args.length - 1];
864                        this.lastControl = {};
865                },
866                _hLineToR: function(result, action, args, doDash){
867                        for(var i = 0; i < args.length; ++i){
868                                result.push("lineTo", [this.last.x += args[i], this.last.y]);
869                                if(doDash)
870                                        this._dashResidue = toDashedLineTo(doDash, this, doDash[doDash.length - 1][0], doDash[doDash.length - 1][1], this.last.x, this.last.y, this._dashResidue);
871                        }
872                        this.lastControl = {};
873                },
874                _vLineToA: function(result, action, args, doDash){
875                        for(var i = 0; i < args.length; ++i){
876                                result.push("lineTo", [this.last.x, args[i]]);
877                                if(doDash)
878                                        this._dashResidue = toDashedLineTo(doDash, this, doDash[doDash.length - 1][0], doDash[doDash.length - 1][1], this.last.x, args[i], this._dashResidue);
879                        }
880                        this.last.y = args[args.length - 1];
881                        this.lastControl = {};
882                },
883                _vLineToR: function(result, action, args, doDash){
884                        for(var i = 0; i < args.length; ++i){
885                                result.push("lineTo", [this.last.x, this.last.y += args[i]]);
886                                if(doDash)
887                                        this._dashResidue = toDashedLineTo(doDash, this, doDash[doDash.length - 1][0], doDash[doDash.length - 1][1], this.last.x, this.last.y, this._dashResidue);
888                        }
889                        this.lastControl = {};
890                },
891                _curveToA: function(result, action, args, doDash){
892                        for(var i = 0; i < args.length; i += 6){
893                                result.push("bezierCurveTo", args.slice(i, i + 6));
894                                if(doDash)
895                                        this._dashResidue = toDashedCurveTo(doDash, this, result[result.length-1], this._dashResidue);
896                        }
897                        this.last.x = args[args.length - 2];
898                        this.last.y = args[args.length - 1];
899                        this.lastControl.x = args[args.length - 4];
900                        this.lastControl.y = args[args.length - 3];
901                        this.lastControl.type = "C";
902                },
903                _curveToR: function(result, action, args, doDash){
904                        for(var i = 0; i < args.length; i += 6){
905                                result.push("bezierCurveTo", [
906                                        this.last.x + args[i],
907                                        this.last.y + args[i + 1],
908                                        this.lastControl.x = this.last.x + args[i + 2],
909                                        this.lastControl.y = this.last.y + args[i + 3],
910                                        this.last.x + args[i + 4],
911                                        this.last.y + args[i + 5]
912                                ]);
913                                if(doDash)
914                                        this._dashResidue = toDashedCurveTo(doDash, this, result[result.length-1], this._dashResidue);
915                                this.last.x += args[i + 4];
916                                this.last.y += args[i + 5];
917                        }
918                        this.lastControl.type = "C";
919                },
920                _smoothCurveToA: function(result, action, args, doDash){
921                        for(var i = 0; i < args.length; i += 4){
922                                var valid = this.lastControl.type == "C";
923                                result.push("bezierCurveTo", [
924                                        valid ? 2 * this.last.x - this.lastControl.x : this.last.x,
925                                        valid ? 2 * this.last.y - this.lastControl.y : this.last.y,
926                                        args[i],
927                                        args[i + 1],
928                                        args[i + 2],
929                                        args[i + 3]
930                                ]);
931                                if(doDash)
932                                        this._dashResidue = toDashedCurveTo(doDash, this, result[result.length-1], this._dashResidue);
933                                this.lastControl.x = args[i];
934                                this.lastControl.y = args[i + 1];
935                                this.lastControl.type = "C";
936                        }
937                        this.last.x = args[args.length - 2];
938                        this.last.y = args[args.length - 1];
939                },
940                _smoothCurveToR: function(result, action, args, doDash){
941                        for(var i = 0; i < args.length; i += 4){
942                                var valid = this.lastControl.type == "C";
943                                result.push("bezierCurveTo", [
944                                        valid ? 2 * this.last.x - this.lastControl.x : this.last.x,
945                                        valid ? 2 * this.last.y - this.lastControl.y : this.last.y,
946                                        this.last.x + args[i],
947                                        this.last.y + args[i + 1],
948                                        this.last.x + args[i + 2],
949                                        this.last.y + args[i + 3]
950                                ]);
951                                if(doDash)
952                                        this._dashResidue = toDashedCurveTo(doDash, this, result[result.length-1], this._dashResidue);
953                                this.lastControl.x = this.last.x + args[i];
954                                this.lastControl.y = this.last.y + args[i + 1];
955                                this.lastControl.type = "C";
956                                this.last.x += args[i + 2];
957                                this.last.y += args[i + 3];
958                        }
959                },
960                _qCurveToA: function(result, action, args, doDash){
961                        for(var i = 0; i < args.length; i += 4){
962                                result.push("quadraticCurveTo", args.slice(i, i + 4));
963                        }
964                        if(doDash)
965                                this._dashResidue = toDashedCurveTo(doDash, this, result[result.length - 1], this._dashResidue);
966                        this.last.x = args[args.length - 2];
967                        this.last.y = args[args.length - 1];
968                        this.lastControl.x = args[args.length - 4];
969                        this.lastControl.y = args[args.length - 3];
970                        this.lastControl.type = "Q";
971                },
972                _qCurveToR: function(result, action, args, doDash){
973                        for(var i = 0; i < args.length; i += 4){
974                                result.push("quadraticCurveTo", [
975                                        this.lastControl.x = this.last.x + args[i],
976                                        this.lastControl.y = this.last.y + args[i + 1],
977                                        this.last.x + args[i + 2],
978                                        this.last.y + args[i + 3]
979                                ]);
980                                if(doDash)
981                                        this._dashResidue = toDashedCurveTo(doDash, this, result[result.length - 1], this._dashResidue);
982                                this.last.x += args[i + 2];
983                                this.last.y += args[i + 3];
984                        }
985                        this.lastControl.type = "Q";
986                },
987                _qSmoothCurveToA: function(result, action, args, doDash){
988                        for(var i = 0; i < args.length; i += 2){
989                                var valid = this.lastControl.type == "Q";
990                                result.push("quadraticCurveTo", [
991                                        this.lastControl.x = valid ? 2 * this.last.x - this.lastControl.x : this.last.x,
992                                        this.lastControl.y = valid ? 2 * this.last.y - this.lastControl.y : this.last.y,
993                                        args[i],
994                                        args[i + 1]
995                                ]);
996                                if(doDash)
997                                        this._dashResidue = toDashedCurveTo(doDash, this, result[result.length - 1], this._dashResidue);
998                                this.lastControl.type = "Q";
999                        }
1000                        this.last.x = args[args.length - 2];
1001                        this.last.y = args[args.length - 1];
1002                },
1003                _qSmoothCurveToR: function(result, action, args, doDash){
1004                        for(var i = 0; i < args.length; i += 2){
1005                                var valid = this.lastControl.type == "Q";
1006                                result.push("quadraticCurveTo", [
1007                                        this.lastControl.x = valid ? 2 * this.last.x - this.lastControl.x : this.last.x,
1008                                        this.lastControl.y = valid ? 2 * this.last.y - this.lastControl.y : this.last.y,
1009                                        this.last.x + args[i],
1010                                        this.last.y + args[i + 1]
1011                                ]);
1012                                if(doDash)
1013                                        this._dashResidue = toDashedCurveTo(doDash, this, result[result.length - 1], this._dashResidue);
1014                                this.lastControl.type = "Q";
1015                                this.last.x += args[i];
1016                                this.last.y += args[i + 1];
1017                        }
1018                },
1019                _arcTo: function(result, action, args, doDash){
1020                        var relative = action == "a";
1021                        for(var i = 0; i < args.length; i += 7){
1022                                var x1 = args[i + 5], y1 = args[i + 6];
1023                                if(relative){
1024                                        x1 += this.last.x;
1025                                        y1 += this.last.y;
1026                                }
1027                                var arcs = ga.arcAsBezier(
1028                                        this.last, args[i], args[i + 1], args[i + 2],
1029                                        args[i + 3] ? 1 : 0, args[i + 4] ? 1 : 0,
1030                                        x1, y1
1031                                );
1032                                arr.forEach(arcs, function(p){
1033                                        result.push("bezierCurveTo", p);
1034                                });
1035                                if(doDash)
1036                                        this._dashResidue = toDashedCurveTo(doDash, this, p, this._dashResidue);
1037                                this.last.x = x1;
1038                                this.last.y = y1;
1039                        }
1040                        this.lastControl = {};
1041                },
1042                _closePath: function(result, action, args, doDash){
1043                        result.push("closePath", []);
1044                        if(doDash)
1045                                this._dashResidue = toDashedLineTo(doDash, this, this.last.x, this.last.y, doDash[1][0], doDash[1][1], this._dashResidue);
1046                        this.lastControl = {};
1047                }
1048        });
1049        arr.forEach(["moveTo", "lineTo", "hLineTo", "vLineTo", "curveTo",
1050                "smoothCurveTo", "qCurveTo", "qSmoothCurveTo", "arcTo", "closePath"],
1051                function(method){ modifyMethod(canvas.Path, method); }
1052        );
1053
1054        canvas.TextPath = declare("dojox.gfx.canvas.TextPath", [canvas.Shape, pathLib.TextPath], {
1055                // summary:
1056                //              a text shape (Canvas)
1057                _renderShape: function(/* Object */ ctx){
1058                        var s = this.shape;
1059                        // nothing for the moment
1060                },
1061                _setText: function(){
1062                        // not implemented
1063                },
1064                _setFont: function(){
1065                        // not implemented
1066                }
1067        });
1068
1069        canvas.Surface = declare("dojox.gfx.canvas.Surface", gs.Surface, {
1070                // summary:
1071                //              a surface object to be used for drawings (Canvas)
1072                constructor: function(){
1073                        gs.Container._init.call(this);
1074                        this.pendingImageCount = 0;
1075                        this.makeDirty();
1076                },
1077                destroy: function(){
1078                        gs.Container.clear.call(this, true); // avoid makeDirty() from canvas.Container.clear impl.
1079                        this.inherited(arguments);
1080                },
1081                setDimensions: function(width, height){
1082                        // summary:
1083                        //              sets the width and height of the rawNode
1084                        // width: String
1085                        //              width of surface, e.g., "100px"
1086                        // height: String
1087                        //              height of surface, e.g., "100px"
1088                        this.width  = g.normalizedLength(width);        // in pixels
1089                        this.height = g.normalizedLength(height);       // in pixels
1090                        if(!this.rawNode) return this;
1091                        var dirty = false;
1092                        if (this.rawNode.width != this.width){
1093                                this.rawNode.width = this.width;
1094                                dirty = true;
1095                        }
1096                        if (this.rawNode.height != this.height){
1097                                this.rawNode.height = this.height;
1098                                dirty = true;
1099                        }
1100                        if (dirty)
1101                                this.makeDirty();
1102                        return this;    // self
1103                },
1104                getDimensions: function(){
1105                        // summary:
1106                        //              returns an object with properties "width" and "height"
1107                        return this.rawNode ? {width:  this.rawNode.width, height: this.rawNode.height} : null; // Object
1108                },
1109                _render: function(force){
1110                        // summary:
1111                        //              render the all shapes
1112                        if(!force && this.pendingImageCount){ return; }
1113                        var ctx = this.rawNode.getContext("2d");
1114                        ctx.clearRect(0, 0, this.rawNode.width, this.rawNode.height);
1115                        this.render(ctx);
1116                        if("pendingRender" in this){
1117                                clearTimeout(this.pendingRender);
1118                                delete this.pendingRender;
1119                        }
1120                },
1121                render: function(ctx){
1122                        // summary:
1123                        //              Renders the gfx scene.
1124                        // description:
1125                        //              this method is called to render the gfx scene to the specified context.
1126                        //              This method should not be invoked directly but should be used instead
1127                        //              as an extension point on which user can connect to with aspect.before/aspect.after
1128                        //              to implement pre- or post- image processing jobs on the drawing surface.
1129                        // ctx: CanvasRenderingContext2D
1130                        //              The surface Canvas rendering context.
1131                        ctx.save();
1132                        for(var i = 0; i < this.children.length; ++i){
1133                                this.children[i]._render(ctx);
1134                        }
1135                        ctx.restore();
1136                },
1137                makeDirty: function(){
1138                        // summary:
1139                        //              internal method, which is called when we may need to redraw
1140                        if(!this.pendingImagesCount && !("pendingRender" in this) && !this._batch){
1141                                this.pendingRender = setTimeout(lang.hitch(this, this._render), 0);
1142                        }
1143                },
1144                downloadImage: function(img, url){
1145                        // summary:
1146                        //              internal method, which starts an image download and renders, when it is ready
1147                        // img: Image
1148                        //              the image object
1149                        // url: String
1150                        //              the url of the image
1151                        var handler = lang.hitch(this, this.onImageLoad);
1152                        if(!this.pendingImageCount++ && "pendingRender" in this){
1153                                clearTimeout(this.pendingRender);
1154                                delete this.pendingRender;
1155                        }
1156                        img.onload  = handler;
1157                        img.onerror = handler;
1158                        img.onabort = handler;
1159                        img.src = url;
1160                },
1161                onImageLoad: function(){
1162                        if(!--this.pendingImageCount){
1163                                this.onImagesLoaded();
1164                                this._render();
1165                        }
1166                },
1167                onImagesLoaded: function(){
1168                        // summary:
1169                        //              An extension point called when all pending images downloads have been completed.
1170                        // description:
1171                        //              This method is invoked when all pending images downloads have been completed, just before
1172                        //              the gfx scene is redrawn. User can connect to this method to get notified when a
1173                        //              gfx scene containing images is fully resolved.
1174                },
1175
1176                // events are not implemented
1177                getEventSource: function(){ return null; },
1178                connect:                function(){},
1179                disconnect:             function(){},
1180                on:                             function(){}
1181        });
1182
1183        canvas.createSurface = function(parentNode, width, height){
1184                // summary:
1185                //              creates a surface (Canvas)
1186                // parentNode: Node
1187                //              a parent node
1188                // width: String
1189                //              width of surface, e.g., "100px"
1190                // height: String
1191                //              height of surface, e.g., "100px"
1192
1193                if(!width && !height){
1194                        var pos = domGeom.position(parentNode);
1195                        width  = width  || pos.w;
1196                        height = height || pos.h;
1197                }
1198                if(typeof width == "number"){
1199                        width = width + "px";
1200                }
1201                if(typeof height == "number"){
1202                        height = height + "px";
1203                }
1204
1205                var s = new canvas.Surface(),
1206                        p = dom.byId(parentNode),
1207                        c = p.ownerDocument.createElement("canvas");
1208
1209                c.width  = g.normalizedLength(width);   // in pixels
1210                c.height = g.normalizedLength(height);  // in pixels
1211
1212                p.appendChild(c);
1213                s.rawNode = c;
1214                s._parent = p;
1215                s.surface = s;
1216                return s;       // dojox/gfx.Surface
1217        };
1218
1219        // Extenders
1220
1221        var C = gs.Container, Container = {
1222                openBatch: function() {
1223                        // summary:
1224                        //              starts a new batch, subsequent new child shapes will be held in
1225                        //              the batch instead of appending to the container directly.
1226                        // description:
1227                        //              Because the canvas renderer has no DOM hierarchy, the canvas implementation differs
1228                        //              such that it suspends the repaint requests for this container until the current batch is closed by a call to closeBatch().
1229                        ++this._batch;
1230                        return this;
1231                },
1232                closeBatch: function() {
1233                        // summary:
1234                        //              submits the current batch.
1235                        // description:
1236                        //              On canvas, this method flushes the pending redraws queue.
1237                        this._batch = this._batch > 0 ? --this._batch : 0;
1238                        this._makeDirty();
1239                        return this;
1240                },
1241                _makeDirty: function(){
1242                        if(!this._batch){
1243                                this.surface.makeDirty();
1244                        }
1245                },
1246                add: function(shape){
1247                        this._makeDirty();
1248                        return C.add.apply(this, arguments);
1249                },
1250                remove: function(shape, silently){
1251                        this._makeDirty();
1252                        return C.remove.apply(this, arguments);
1253                },
1254                clear: function(){
1255                        this._makeDirty();
1256                        return C.clear.apply(this, arguments);
1257                },
1258                getBoundingBox: C.getBoundingBox,
1259                _moveChildToFront: function(shape){
1260                        this._makeDirty();
1261                        return C._moveChildToFront.apply(this, arguments);
1262                },
1263                _moveChildToBack: function(shape){
1264                        this._makeDirty();
1265                        return C._moveChildToBack.apply(this, arguments);
1266                }
1267        };
1268
1269        var Creator = {
1270                // summary:
1271                //              Canvas shape creators
1272                createObject: function(shapeType, rawShape) {
1273                        // summary:
1274                        //              creates an instance of the passed shapeType class
1275                        // shapeType: Function
1276                        //              a class constructor to create an instance of
1277                        // rawShape: Object
1278                        //              properties to be passed in to the classes "setShape" method
1279                        // overrideSize: Boolean
1280                        //              set the size explicitly, if true
1281                        var shape = new shapeType();
1282                        shape.surface = this.surface;
1283                        shape.setShape(rawShape);
1284                        this.add(shape);
1285                        return shape;   // dojox/gfx/shape.Shape
1286                }
1287        };
1288
1289        extend(canvas.Group, Container);
1290        extend(canvas.Group, gs.Creator);
1291        extend(canvas.Group, Creator);
1292
1293        extend(canvas.Surface, Container);
1294        extend(canvas.Surface, gs.Creator);
1295        extend(canvas.Surface, Creator);
1296
1297        // no event support -> nothing to fix.
1298        canvas.fixTarget = function(event, gfxElement){
1299                // tags:
1300                //              private
1301                return true;
1302        };
1303
1304        return canvas;
1305});
Note: See TracBrowser for help on using the repository browser.