source: Dev/trunk/src/client/dojox/gfx/vml.js @ 532

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

Added Dojo 1.9.3 release.

File size: 42.9 KB
Line 
1define(["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/array", "dojo/_base/Color", "dojo/_base/sniff",
2                "dojo/_base/config", "dojo/dom", "dojo/dom-geometry", "dojo/_base/kernel",
3                "./_base", "./shape", "./path", "./arc", "./gradient", "./matrix"],
4function(lang, declare, arr, Color, has, config, dom, domGeom, kernel, g, gs, pathLib, arcLib, gradient, m){
5        var vml = g.vml = {
6                // summary:
7                //              This the default graphics rendering bridge for IE6-7.
8                //              This renderer is very slow.  For best performance on IE6-8, use Silverlight plugin.
9                //              IE9+ defaults to the standard W3C SVG renderer.
10
11                // TODO: Everything exported from this file should be inside these braces.
12                // For now, I'll just put the declarations needed for documentation.
13
14                // xmlns: String
15                //              a VML's namespace
16
17                // text_alignment: Object
18                //              mapping from SVG alignment to VML alignment
19        };
20
21        vml.xmlns = "urn:schemas-microsoft-com:vml";
22
23        document.namespaces.add("v", vml.xmlns);
24        var vmlElems = ["*", "group", "roundrect", "oval", "shape", "rect", "imagedata", "path", "textpath", "text"],
25                i = 0, l = 1, s = document.createStyleSheet();
26        if(has("ie") >= 8){
27                i = 1;
28                l = vmlElems.length;
29        }
30        for (; i < l; ++i) {
31                s.addRule("v\\:" + vmlElems[i], "behavior:url(#default#VML); display:inline-block");
32        }
33
34        vml.text_alignment = {start: "left", middle: "center", end: "right"};
35
36        vml._parseFloat = function(str) {
37                // summary:
38                //              a helper function to parse VML-specific floating-point values
39                // str: String
40                //              a representation of a floating-point number
41                return str.match(/^\d+f$/i) ? parseInt(str) / 65536 : parseFloat(str);  // Number
42        };
43
44        vml._bool = {"t": 1, "true": 1};
45
46        vml._reparentEvents = function(dst, src){
47                for(var name in src){
48                        if(name.substr(0, 2).toLowerCase() == "on"){
49                                dst[name] = src[name];
50                                src[name] = null;
51                        }
52                }
53        };
54
55        vml.Shape = declare("dojox.gfx.vml.Shape", gs.Shape, {
56                // summary:
57                //              VML-specific implementation of dojox/gfx/shape.Shape methods
58
59                setFill: function(fill){
60                        // summary:
61                        //              sets a fill object (VML)
62                        // fill: Object
63                        //              a fill object
64                        //              (see dojox/gfx.defaultLinearGradient,
65                        //              dojox/gfx.defaultRadialGradient,
66                        //              dojox/gfx.defaultPattern,
67                        //              or dojo/_base/Color)
68
69                        if(!fill){
70                                // don't fill
71                                this.fillStyle = null;
72                                this.rawNode.filled = "f";
73                                return this;
74                        }
75                        var i, f, fo, a, s;
76                        if(typeof fill == "object" && "type" in fill){
77                                // gradient
78                                switch(fill.type){
79                                        case "linear":
80                                                var matrix = this._getRealMatrix(), bbox = this.getBoundingBox(),
81                                                        tbbox = this._getRealBBox ? this._getRealBBox() : this.getTransformedBoundingBox();
82                                                s = [];
83                                                if(this.fillStyle !== fill){
84                                                        this.fillStyle = g.makeParameters(g.defaultLinearGradient, fill);
85                                                }
86                                                f = g.gradient.project(matrix, this.fillStyle,
87                                                                {x: bbox.x, y: bbox.y},
88                                                                {x: bbox.x + bbox.width, y: bbox.y + bbox.height},
89                                                                tbbox[0], tbbox[2]);
90                                                a = f.colors;
91                                                if(a[0].offset.toFixed(5) != "0.00000"){
92                                                        s.push("0 " + g.normalizeColor(a[0].color).toHex());
93                                                }
94                                                for(i = 0; i < a.length; ++i){
95                                                        s.push(a[i].offset.toFixed(5) + " " + g.normalizeColor(a[i].color).toHex());
96                                                }
97                                                i = a.length - 1;
98                                                if(a[i].offset.toFixed(5) != "1.00000"){
99                                                        s.push("1 " + g.normalizeColor(a[i].color).toHex());
100                                                }
101                                                fo = this.rawNode.fill;
102                                                fo.colors.value = s.join(";");
103                                                fo.method = "sigma";
104                                                fo.type = "gradient";
105                                                fo.angle = (270 - m._radToDeg(f.angle)) % 360;
106                                                fo.on = true;
107                                                break;
108                                        case "radial":
109                                                f = g.makeParameters(g.defaultRadialGradient, fill);
110                                                this.fillStyle = f;
111                                                var l = parseFloat(this.rawNode.style.left),
112                                                        t = parseFloat(this.rawNode.style.top),
113                                                        w = parseFloat(this.rawNode.style.width),
114                                                        h = parseFloat(this.rawNode.style.height),
115                                                        c = isNaN(w) ? 1 : 2 * f.r / w;
116                                                a = [];
117                                                // add a color at the offset 0 (1 in VML coordinates)
118                                                if(f.colors[0].offset > 0){
119                                                        a.push({offset: 1, color: g.normalizeColor(f.colors[0].color)});
120                                                }
121                                                // massage colors
122                                                arr.forEach(f.colors, function(v, i){
123                                                        a.push({offset: 1 - v.offset * c, color: g.normalizeColor(v.color)});
124                                                });
125                                                i = a.length - 1;
126                                                while(i >= 0 && a[i].offset < 0){ --i; }
127                                                if(i < a.length - 1){
128                                                        // correct excessive colors
129                                                        var q = a[i], p = a[i + 1];
130                                                        p.color = Color.blendColors(q.color, p.color, q.offset / (q.offset - p.offset));
131                                                        p.offset = 0;
132                                                        while(a.length - i > 2) a.pop();
133                                                }
134                                                // set colors
135                                                i = a.length - 1, s = [];
136                                                if(a[i].offset > 0){
137                                                        s.push("0 " + a[i].color.toHex());
138                                                }
139                                                for(; i >= 0; --i){
140                                                        s.push(a[i].offset.toFixed(5) + " " + a[i].color.toHex());
141                                                }
142                                                fo = this.rawNode.fill;
143                                                fo.colors.value = s.join(";");
144                                                fo.method = "sigma";
145                                                fo.type = "gradientradial";
146                                                if(isNaN(w) || isNaN(h) || isNaN(l) || isNaN(t)){
147                                                        fo.focusposition = "0.5 0.5";
148                                                }else{
149                                                        fo.focusposition = ((f.cx - l) / w).toFixed(5) + " " + ((f.cy - t) / h).toFixed(5);
150                                                }
151                                                fo.focussize = "0 0";
152                                                fo.on = true;
153                                                break;
154                                        case "pattern":
155                                                f = g.makeParameters(g.defaultPattern, fill);
156                                                this.fillStyle = f;
157                                                fo = this.rawNode.fill;
158                                                fo.type = "tile";
159                                                fo.src = f.src;
160                                                if(f.width && f.height){
161                                                        // in points
162                                                        fo.size.x = g.px2pt(f.width);
163                                                        fo.size.y = g.px2pt(f.height);
164                                                }
165                                                fo.alignShape = "f";
166                                                fo.position.x = 0;
167                                                fo.position.y = 0;
168                                                fo.origin.x = f.width  ? f.x / f.width  : 0;
169                                                fo.origin.y = f.height ? f.y / f.height : 0;
170                                                fo.on = true;
171                                                break;
172                                }
173                                this.rawNode.fill.opacity = 1;
174                                return this;
175                        }
176                        // color object
177                        this.fillStyle = g.normalizeColor(fill);
178                        fo = this.rawNode.fill;
179                        if(!fo){
180                                fo = this.rawNode.ownerDocument.createElement("v:fill");
181                        }
182                        fo.method = "any";
183                        fo.type = "solid";
184                        fo.opacity = this.fillStyle.a;
185                        var alphaFilter = this.rawNode.filters["DXImageTransform.Microsoft.Alpha"];
186                        if(alphaFilter){
187                                alphaFilter.opacity = Math.round(this.fillStyle.a * 100);
188                        }
189                        this.rawNode.fillcolor = this.fillStyle.toHex();
190                        this.rawNode.filled = true;
191                        return this;    // self
192                },
193
194                setStroke: function(stroke){
195                        // summary:
196                        //              sets a stroke object (VML)
197                        // stroke: Object
198                        //              a stroke object
199                        //              (see dojox/gfx.defaultStroke)
200
201                        if(!stroke){
202                                // don't stroke
203                                this.strokeStyle = null;
204                                this.rawNode.stroked = "f";
205                                return this;
206                        }
207                        // normalize the stroke
208                        if(typeof stroke == "string" || lang.isArray(stroke) || stroke instanceof Color){
209                                stroke = {color: stroke};
210                        }
211                        var s = this.strokeStyle = g.makeParameters(g.defaultStroke, stroke);
212                        s.color = g.normalizeColor(s.color);
213                        // generate attributes
214                        var rn = this.rawNode;
215                        rn.stroked = true;
216                        rn.strokecolor = s.color.toCss();
217                        rn.strokeweight = s.width + "px";       // TODO: should we assume that the width is always in pixels?
218                        if(rn.stroke) {
219                                rn.stroke.opacity = s.color.a;
220                                rn.stroke.endcap = this._translate(this._capMap, s.cap);
221                                if(typeof s.join == "number") {
222                                        rn.stroke.joinstyle = "miter";
223                                        rn.stroke.miterlimit = s.join;
224                                }else{
225                                        rn.stroke.joinstyle = s.join;
226                                        // rn.stroke.miterlimit = s.width;
227                                }
228                                rn.stroke.dashstyle = s.style == "none" ? "Solid" : s.style;
229                        }
230                        return this;    // self
231                },
232
233                _capMap: { butt: 'flat' },
234                _capMapReversed: { flat: 'butt' },
235
236                _translate: function(dict, value) {
237                        return (value in dict) ? dict[value] : value;
238                },
239
240                _applyTransform: function() {
241                        var matrix = this._getRealMatrix();
242                        if(matrix){
243                                var skew = this.rawNode.skew;
244                                if(typeof skew == "undefined"){
245                                        for(var i = 0; i < this.rawNode.childNodes.length; ++i){
246                                                if(this.rawNode.childNodes[i].tagName == "skew"){
247                                                        skew = this.rawNode.childNodes[i];
248                                                        break;
249                                                }
250                                        }
251                                }
252                                if(skew){
253                                        skew.on = "f";
254                                        var mt = matrix.xx.toFixed(8) + " " + matrix.xy.toFixed(8) + " " +
255                                                matrix.yx.toFixed(8) + " " + matrix.yy.toFixed(8) + " 0 0",
256                                                offset = Math.floor(matrix.dx).toFixed() + "px " + Math.floor(matrix.dy).toFixed() + "px",
257                                                s = this.rawNode.style,
258                                                l = parseFloat(s.left),
259                                                t = parseFloat(s.top),
260                                                w = parseFloat(s.width),
261                                                h = parseFloat(s.height);
262                                        if(isNaN(l)) l = 0;
263                                        if(isNaN(t)) t = 0;
264                                        if(isNaN(w) || !w) w = 1;
265                                        if(isNaN(h) || !h) h = 1;
266                                        var origin = (-l / w - 0.5).toFixed(8) + " " + (-t / h - 0.5).toFixed(8);
267                                        skew.matrix =  mt;
268                                        skew.origin = origin;
269                                        skew.offset = offset;
270                                        skew.on = true;
271                                }
272                        }
273                        if(this.fillStyle && this.fillStyle.type == "linear"){
274                                this.setFill(this.fillStyle);
275                        }
276                        if(this.clip){
277                                this.setClip(this.clip);
278                        }
279                        return this;
280                },
281
282                _setDimensions: function(width, height){
283                        // summary:
284                        //              sets the width and height of the rawNode,
285                        //              if the surface size has been changed
286                        // width: String
287                        //              width in pixels
288                        // height: String
289                        //              height in pixels
290
291                        // default implementation does nothing
292                        return this; // self
293                },
294
295                setRawNode: function(rawNode){
296                        // summary:
297                        //              assigns and clears the underlying node that will represent this
298                        //              shape. Once set, transforms, gradients, etc, can be applied.
299                        //              (no fill & stroke by default)
300                        rawNode.stroked = "f";
301                        rawNode.filled  = "f";
302                        this.rawNode = rawNode;
303                        this.rawNode.__gfxObject__ = this;
304                },
305
306                // move family
307
308                _moveToFront: function(){
309                        // summary:
310                        //              moves a shape to front of its parent's list of shapes (VML)
311                        this.rawNode.parentNode.appendChild(this.rawNode);
312                        return this;
313                },
314                _moveToBack: function(){
315                        // summary:
316                        //              moves a shape to back of its parent's list of shapes (VML)
317                        var r = this.rawNode, p = r.parentNode, n = p.firstChild;
318                        p.insertBefore(r, n);
319                        if(n.tagName == "rect"){
320                                // surface has a background rectangle, which position should be preserved
321                                n.swapNode(r);
322                        }
323                        return this;
324                },
325
326                _getRealMatrix: function(){
327                        // summary:
328                        //              returns the cumulative ("real") transformation matrix
329                        //              by combining the shape's matrix with its parent's matrix
330                        return this.parentMatrix ? new m.Matrix2D([this.parentMatrix, this.matrix]) : this.matrix;      // dojox/gfx/matrix.Matrix2D
331                },
332               
333                setClip: function(clip){
334                        // summary:
335                        //              sets the clipping area of this shape.
336                        // description:
337                        //              This method overrides the dojox/gfx/shape.Shape.setClip() method. Only rectangular geometry is supported.
338                        // clip: Object
339                        //              an object that defines the clipping geometry, or null to remove clip.
340                        this.inherited(arguments);
341                        var nodeStyle = this.rawNode.style;
342                        if(!clip){
343                                // remove clip
344                                nodeStyle.position = "absolute";
345                            nodeStyle.clip = "rect(0px "+nodeStyle.width+" "+nodeStyle.height+" 0px)";
346                        }else{
347                                if("width" in clip){
348                                        var matrix = this._getRealMatrix(),
349                                                l = parseFloat(nodeStyle.left),
350                                                t = parseFloat(nodeStyle.top);
351                                        if(isNaN(l)) l = 0;
352                                        if(isNaN(t)) t = 0;
353                                        // transform the clip with the shape transform to compute the correct w/h (e.g. after a scale)
354                                        var clipt = m.multiplyRectangle(matrix, clip);
355                                        var pt = m.multiplyPoint(matrix, {x:l,y:t});
356                                        // clip property is relative to the elt border box
357                                        nodeStyle.clip = "rect(" + Math.round(clipt.y-pt.y) + "px " + Math.round(clipt.x-pt.x + clipt.width ) + "px " +
358                                                                                        Math.round(clipt.y-pt.y + clipt.height ) + "px " + Math.round(clipt.x -pt.x) + "px)";
359                                }
360                        }
361                        return this;
362                }
363        });
364
365          vml.Group = declare("dojox.gfx.vml.Group", vml.Shape, {
366                // summary:
367                //              a group shape (VML), which can be used
368                //              to logically group shapes (e.g, to propagate matricies)
369                constructor: function(){
370                        gs.Container._init.call(this);
371                },
372                // apply transformation
373                _applyTransform: function(){
374                        // summary:
375                        //              applies a transformation matrix to a group
376                        var matrix = this._getRealMatrix();
377                        for(var i = 0; i < this.children.length; ++i){
378                                this.children[i]._updateParentMatrix(matrix);
379                        }
380                        if(this.clip){
381                                this.setClip(this.clip);
382                        }
383                        return this;    // self
384                },
385                _setDimensions: function(width, height){
386                        // summary:
387                        //              sets the width and height of the rawNode,
388                        //              if the surface size has been changed
389                        // width: String
390                        //              width in pixels
391                        // height: String
392                        //              height in pixels
393                        var r = this.rawNode, rs = r.style,
394                                bs = this.bgNode.style;
395                        rs.width = width;
396                        rs.height = height;
397                        r.coordsize = width + " " + height;
398                        bs.width = width;
399                        bs.height = height;
400                        for(var i = 0; i < this.children.length; ++i){
401                                this.children[i]._setDimensions(width, height);
402                        }
403                        return this; // self
404                },
405                setClip: function(clip){
406                        // summary:
407                        //              sets the clipping area of this shape.
408                        // description:
409                        //              This method overrides the dojox/gfx/shape.Shape.setClip() method.
410                        // clip: Object
411                        //              an object that defines the clipping geometry, or null to remove clip.
412
413                        this.clip = clip;
414                        var nodeStyle = this.rawNode.style;
415                        if(!clip){
416                                // remove clip
417                                nodeStyle.position = "absolute";
418                            nodeStyle.clip = "rect(0px "+nodeStyle.width+" "+nodeStyle.height+" 0px)";
419                        }else if("width" in clip){
420                                var matrix = this._getRealMatrix();
421                                // transform the clip with group transform
422                                var clipt = m.multiplyRectangle(matrix, clip);
423                                // vml feature :-( ): if the group rawNode bbox x/y are < 0,
424                                // need to adjust clip accordingly
425                                var bbox = this.getBoundingBox();
426                                bbox = bbox ? m.multiplyRectangle(matrix, bbox) : null;
427                                var offx = bbox && bbox.x < 0 ? bbox.x : 0,
428                                        offy = bbox && bbox.y < 0 ? bbox.y : 0;
429                                nodeStyle.position = "absolute";
430                                nodeStyle.clip = "rect(" +
431                                        Math.round(clipt.y - offy) + "px " +
432                                        Math.round(clipt.x + clipt.width - offx) + "px " +
433                                        Math.round(clipt.y + clipt.height - offy)  + "px " +
434                                        Math.round(clipt.x - offx) + "px)";
435                        }
436                        return this;
437                },
438                destroy: function(){
439                        // summary:
440                        //              Releases all internal resources owned by this shape. Once this method has been called,
441                        //              the instance is considered disposed and should not be used anymore.
442                        this.clear(true);
443                        // avoid this.inherited
444                        vml.Shape.prototype.destroy.apply(this, arguments);
445                }
446        });
447        vml.Group.nodeType = "group";
448
449          vml.Rect = declare("dojox.gfx.vml.Rect", [vml.Shape, gs.Rect], {
450                // summary:
451                //              a rectangle shape (VML)
452                setShape: function(newShape){
453                        // summary:
454                        //              sets a rectangle shape object (VML)
455                        // newShape: Object
456                        //              a rectangle shape object
457                        var shape = this.shape = g.makeParameters(this.shape, newShape);
458                        this.bbox = null;
459                        var r = Math.min(1, (shape.r / Math.min(parseFloat(shape.width), parseFloat(shape.height)))).toFixed(8);
460                        // a workaround for the VML's arcsize bug: cannot read arcsize of an instantiated node
461                        var parent = this.rawNode.parentNode, before = null;
462                        if(parent){
463                                if(parent.lastChild !== this.rawNode){
464                                        for(var i = 0; i < parent.childNodes.length; ++i){
465                                                if(parent.childNodes[i] === this.rawNode){
466                                                        before = parent.childNodes[i + 1];
467                                                        break;
468                                                }
469                                        }
470                                }
471                                parent.removeChild(this.rawNode);
472                        }
473                        if(has("ie") > 7){
474                                var node = this.rawNode.ownerDocument.createElement("v:roundrect");
475                                node.arcsize = r;
476                                node.style.display = "inline-block";
477                                vml._reparentEvents(node, this.rawNode);
478                                this.rawNode = node;
479                                this.rawNode.__gfxObject__ = this;
480                        }else{
481                                this.rawNode.arcsize = r;
482                        }
483                        if(parent){
484                                if(before){
485                                        parent.insertBefore(this.rawNode, before);
486                                }else{
487                                        parent.appendChild(this.rawNode);
488                                }
489                        }
490                        var style = this.rawNode.style;
491                        style.left   = shape.x.toFixed();
492                        style.top    = shape.y.toFixed();
493                        style.width  = (typeof shape.width == "string" && shape.width.indexOf("%") >= 0)  ? shape.width  : Math.max(shape.width.toFixed(),0);
494                        style.height = (typeof shape.height == "string" && shape.height.indexOf("%") >= 0) ? shape.height : Math.max(shape.height.toFixed(),0);
495                        // set all necessary styles, which are lost by VML (yes, it's a VML's bug)
496                        return this.setTransform(this.matrix).setFill(this.fillStyle).setStroke(this.strokeStyle);      // self
497                }
498        });
499        vml.Rect.nodeType = "roundrect"; // use a roundrect so the stroke join type is respected
500
501        vml.Ellipse = declare("dojox.gfx.vml.Ellipse", [vml.Shape, gs.Ellipse], {
502                // summary:
503                //              an ellipse shape (VML)
504                setShape: function(newShape){
505                        // summary:
506                        //              sets an ellipse shape object (VML)
507                        // newShape: Object
508                        //              an ellipse shape object
509                        var shape = this.shape = g.makeParameters(this.shape, newShape);
510                        this.bbox = null;
511                        var style = this.rawNode.style;
512                        style.left   = (shape.cx - shape.rx).toFixed();
513                        style.top    = (shape.cy - shape.ry).toFixed();
514                        style.width  = (shape.rx * 2).toFixed();
515                        style.height = (shape.ry * 2).toFixed();
516                        return this.setTransform(this.matrix);  // self
517                }
518        });
519        vml.Ellipse.nodeType = "oval";
520
521        vml.Circle = declare("dojox.gfx.vml.Circle", [vml.Shape, gs.Circle], {
522                // summary:
523                //              a circle shape (VML)
524                setShape: function(newShape){
525                        // summary:
526                        //              sets a circle shape object (VML)
527                        // newShape: Object
528                        //              a circle shape object
529                        var shape = this.shape = g.makeParameters(this.shape, newShape);
530                        this.bbox = null;
531                        var style = this.rawNode.style;
532                        style.left   = (shape.cx - shape.r).toFixed();
533                        style.top    = (shape.cy - shape.r).toFixed();
534                        style.width  = (shape.r * 2).toFixed();
535                        style.height = (shape.r * 2).toFixed();
536                        return this;    // self
537                }
538        });
539        vml.Circle.nodeType = "oval";
540
541        vml.Line = declare("dojox.gfx.vml.Line", [vml.Shape, gs.Line], {
542                // summary:
543                //              a line shape (VML)
544                constructor: function(rawNode){
545                        if(rawNode) rawNode.setAttribute("dojoGfxType", "line");
546                },
547                setShape: function(newShape){
548                        // summary:
549                        //              sets a line shape object (VML)
550                        // newShape: Object
551                        //              a line shape object
552                        var shape = this.shape = g.makeParameters(this.shape, newShape);
553                        this.bbox = null;
554                        this.rawNode.path.v = "m" + shape.x1.toFixed() + " " + shape.y1.toFixed() +
555                                "l" + shape.x2.toFixed() + " " + shape.y2.toFixed() + "e";
556                        return this.setTransform(this.matrix);  // self
557                }
558        });
559        vml.Line.nodeType = "shape";
560
561        vml.Polyline = declare("dojox.gfx.vml.Polyline", [vml.Shape, gs.Polyline], {
562                // summary:
563                //              a polyline/polygon shape (VML)
564                constructor: function(rawNode){
565                        if(rawNode) rawNode.setAttribute("dojoGfxType", "polyline");
566                },
567                setShape: function(points, closed){
568                        // summary:
569                        //              sets a polyline/polygon shape object (VML)
570                        // points: Object|Array
571                        //              a polyline/polygon shape object, or an array of points
572                        // closed: Boolean?
573                        //              if true, close the polyline explicitly
574                        if(points && points instanceof Array){
575                                this.shape = g.makeParameters(this.shape, { points: points });
576                                if(closed && this.shape.points.length) this.shape.points.push(this.shape.points[0]);
577                        }else{
578                                this.shape = g.makeParameters(this.shape, points);
579                        }
580                        this.bbox = null;
581                        this._normalizePoints();
582                        var attr = [], p = this.shape.points;
583                        if(p.length > 0){
584                                attr.push("m");
585                                attr.push(p[0].x.toFixed(), p[0].y.toFixed());
586                                if(p.length > 1){
587                                        attr.push("l");
588                                        for(var i = 1; i < p.length; ++i){
589                                                attr.push(p[i].x.toFixed(), p[i].y.toFixed());
590                                        }
591                                }
592                        }
593                        attr.push("e");
594                        this.rawNode.path.v = attr.join(" ");
595                        return this.setTransform(this.matrix);  // self
596                }
597        });
598        vml.Polyline.nodeType = "shape";
599
600        vml.Image = declare("dojox.gfx.vml.Image", [vml.Shape, gs.Image], {
601                // summary:
602                //              an image (VML)
603                setShape: function(newShape){
604                        // summary:
605                        //              sets an image shape object (VML)
606                        // newShape: Object
607                        //              an image shape object
608                        var shape = this.shape = g.makeParameters(this.shape, newShape);
609                        this.bbox = null;
610                        this.rawNode.firstChild.src = shape.src;
611                        return this.setTransform(this.matrix);  // self
612                },
613                _applyTransform: function() {
614                        var matrix = this._getRealMatrix(),
615                                rawNode = this.rawNode,
616                                s = rawNode.style,
617                                shape = this.shape;
618                        if(matrix){
619                                matrix = m.multiply(matrix, {dx: shape.x, dy: shape.y});
620                        }else{
621                                matrix = m.normalize({dx: shape.x, dy: shape.y});
622                        }
623                        if(matrix.xy == 0 && matrix.yx == 0 && matrix.xx > 0 && matrix.yy > 0){
624                                // special case to avoid filters
625                                s.filter = "";
626                                s.width  = Math.floor(matrix.xx * shape.width);
627                                s.height = Math.floor(matrix.yy * shape.height);
628                                s.left   = Math.floor(matrix.dx);
629                                s.top    = Math.floor(matrix.dy);
630                        }else{
631                                var ps = rawNode.parentNode.style;
632                                s.left   = "0px";
633                                s.top    = "0px";
634                                s.width  = ps.width;
635                                s.height = ps.height;
636                                matrix = m.multiply(matrix,
637                                        {xx: shape.width / parseInt(s.width), yy: shape.height / parseInt(s.height)});
638                                var f = rawNode.filters["DXImageTransform.Microsoft.Matrix"];
639                                if(f){
640                                        f.M11 = matrix.xx;
641                                        f.M12 = matrix.xy;
642                                        f.M21 = matrix.yx;
643                                        f.M22 = matrix.yy;
644                                        f.Dx = matrix.dx;
645                                        f.Dy = matrix.dy;
646                                }else{
647                                        s.filter = "progid:DXImageTransform.Microsoft.Matrix(M11=" + matrix.xx +
648                                                ", M12=" + matrix.xy + ", M21=" + matrix.yx + ", M22=" + matrix.yy +
649                                                ", Dx=" + matrix.dx + ", Dy=" + matrix.dy + ")";
650                                }
651                        }
652                        return this; // self
653                },
654                _setDimensions: function(width, height){
655                        // summary:
656                        //              sets the width and height of the rawNode,
657                        //              if the surface size has been changed
658                        // width: String
659                        //              width in pixels
660                        // height: String
661                        //              height in pixels
662
663                        var r = this.rawNode, f = r.filters["DXImageTransform.Microsoft.Matrix"];
664                        if(f){
665                                var s = r.style;
666                                s.width  = width;
667                                s.height = height;
668                                return this._applyTransform(); // self
669                        }
670                        return this;    // self
671                }
672        });
673        vml.Image.nodeType = "rect";
674
675        vml.Text = declare("dojox.gfx.vml.Text", [vml.Shape, gs.Text], {
676                // summary:
677                //              an anchored text (VML)
678                constructor: function(rawNode){
679                        if(rawNode){rawNode.setAttribute("dojoGfxType", "text");}
680                        this.fontStyle = null;
681                },
682                _alignment: {start: "left", middle: "center", end: "right"},
683                setShape: function(newShape){
684                        // summary:
685                        //              sets a text shape object (VML)
686                        // newShape: Object
687                        //              a text shape object
688                        this.shape = g.makeParameters(this.shape, newShape);
689                        this.bbox = null;
690                        var r = this.rawNode, s = this.shape, x = s.x, y = s.y.toFixed(), path;
691                        switch(s.align){
692                                case "middle":
693                                        x -= 5;
694                                        break;
695                                case "end":
696                                        x -= 10;
697                                        break;
698                        }
699                        path = "m" + x.toFixed() + "," + y + "l" + (x + 10).toFixed() + "," + y + "e";
700                        // find path and text path
701                        var p = null, t = null, c = r.childNodes;
702                        for(var i = 0; i < c.length; ++i){
703                                var tag = c[i].tagName;
704                                if(tag == "path"){
705                                        p = c[i];
706                                        if(t) break;
707                                }else if(tag == "textpath"){
708                                        t = c[i];
709                                        if(p) break;
710                                }
711                        }
712                        if(!p){
713                                p = r.ownerDocument.createElement("v:path");
714                                r.appendChild(p);
715                        }
716                        if(!t){
717                                t = r.ownerDocument.createElement("v:textpath");
718                                r.appendChild(t);
719                        }
720                        p.v = path;
721                        p.textPathOk = true;
722                        t.on = true;
723                        var a = vml.text_alignment[s.align];
724                        t.style["v-text-align"] = a ? a : "left";
725                        t.style["text-decoration"] = s.decoration;
726                        t.style["v-rotate-letters"] = s.rotated;
727                        t.style["v-text-kern"] = s.kerning;
728                        t.string = s.text;
729                        return this.setTransform(this.matrix);  // self
730                },
731                _setFont: function(){
732                        // summary:
733                        //              sets a font object (VML)
734                        var f = this.fontStyle, c = this.rawNode.childNodes;
735                        for(var i = 0; i < c.length; ++i){
736                                if(c[i].tagName == "textpath"){
737                                        c[i].style.font = g.makeFontString(f);
738                                        break;
739                                }
740                        }
741                        this.setTransform(this.matrix);
742                },
743                _getRealMatrix: function(){
744                        // summary:
745                        //              returns the cumulative ("real") transformation matrix
746                        //              by combining the shape's matrix with its parent's matrix;
747                        //              it makes a correction for a font size
748                        var matrix = this.inherited(arguments);
749                        // It appears that text is always aligned vertically at a middle of x-height (???).
750                        // It is impossible to obtain these metrics from VML => I try to approximate it with
751                        // more-or-less util value of 0.7 * FontSize, which is typical for European fonts.
752                        if(matrix){
753                                matrix = m.multiply(matrix,
754                                        {dy: -g.normalizedLength(this.fontStyle ? this.fontStyle.size : "10pt") * 0.35});
755                        }
756                        return matrix;  // dojox/gfx/matrix.Matrix2D
757                },
758                getTextWidth: function(){
759                        // summary:
760                        //              get the text width, in px
761                        var rawNode = this.rawNode, _display = rawNode.style.display;
762                        rawNode.style.display = "inline";
763                        var _width = g.pt2px(parseFloat(rawNode.currentStyle.width));
764                        rawNode.style.display = _display;
765                        return _width;
766                }
767        });
768        vml.Text.nodeType = "shape";
769
770        vml.Path = declare("dojox.gfx.vml.Path", [vml.Shape, pathLib.Path], {
771                // summary:
772                //              a path shape (VML)
773                constructor: function(rawNode){
774                        if(rawNode && !rawNode.getAttribute("dojoGfxType")){
775                                rawNode.setAttribute("dojoGfxType", "path");
776                        }
777                        this.vmlPath = "";
778                        this.lastControl = {};
779                },
780                _updateWithSegment: function(segment){
781                        // summary:
782                        //              updates the bounding box of path with new segment
783                        // segment: Object
784                        //              a segment
785                        var last = lang.clone(this.last);
786                        this.inherited(arguments);
787                        if(arguments.length > 1){ return; } // skip transfomed bbox calculations
788                        // add a VML path segment
789                        var path = this[this.renderers[segment.action]](segment, last);
790                        if(typeof this.vmlPath == "string"){
791                                this.vmlPath += path.join("");
792                                this.rawNode.path.v = this.vmlPath + " r0,0 e";
793                        }else{
794                                Array.prototype.push.apply(this.vmlPath, path); //FIXME: why not push()?
795                        }
796                },
797                setShape: function(newShape){
798                        // summary:
799                        //              forms a path using a shape (VML)
800                        // newShape: Object
801                        //              a VML path string or a path object (see dojox/gfx.defaultPath)
802                        this.vmlPath = [];
803                        this.lastControl.type = "";     // no prior control point
804                        this.inherited(arguments);
805                        this.vmlPath = this.vmlPath.join("");
806                        this.rawNode.path.v = this.vmlPath + " r0,0 e";
807                        return this;
808                },
809                _pathVmlToSvgMap: {m: "M", l: "L", t: "m", r: "l", c: "C", v: "c", qb: "Q", x: "z", e: ""},
810                // VML-specific segment renderers
811                renderers: {
812                        M: "_moveToA", m: "_moveToR",
813                        L: "_lineToA", l: "_lineToR",
814                        H: "_hLineToA", h: "_hLineToR",
815                        V: "_vLineToA", v: "_vLineToR",
816                        C: "_curveToA", c: "_curveToR",
817                        S: "_smoothCurveToA", s: "_smoothCurveToR",
818                        Q: "_qCurveToA", q: "_qCurveToR",
819                        T: "_qSmoothCurveToA", t: "_qSmoothCurveToR",
820                        A: "_arcTo", a: "_arcTo",
821                        Z: "_closePath", z: "_closePath"
822                },
823                _addArgs: function(path, segment, from, upto){
824                        var n = segment instanceof Array ? segment : segment.args;
825                        for(var i = from; i < upto; ++i){
826                                path.push(" ", n[i].toFixed());
827                        }
828                },
829                _adjustRelCrd: function(last, segment, step){
830                        var n = segment instanceof Array ? segment : segment.args, l = n.length,
831                                result = new Array(l), i = 0, x = last.x, y = last.y;
832                        if(typeof x != "number"){
833                                // there is no last coordinate =>
834                                // treat the first pair as an absolute coordinate
835                                result[0] = x = n[0];
836                                result[1] = y = n[1];
837                                i = 2;
838                        }
839                        if(typeof step == "number" && step != 2){
840                                var j = step;
841                                while(j <= l){
842                                        for(; i < j; i += 2){
843                                                result[i] = x + n[i];
844                                                result[i + 1] = y + n[i + 1];
845                                        }
846                                        x = result[j - 2];
847                                        y = result[j - 1];
848                                        j += step;
849                                }
850                        }else{
851                                for(; i < l; i += 2){
852                                        result[i] = (x += n[i]);
853                                        result[i + 1] = (y += n[i + 1]);
854                                }
855                        }
856                        return result;
857                },
858                _adjustRelPos: function(last, segment){
859                        var n = segment instanceof Array ? segment : segment.args, l = n.length,
860                                result = new Array(l);
861                        for(var i = 0; i < l; ++i){
862                                result[i] = (last += n[i]);
863                        }
864                        return result;
865                },
866                _moveToA: function(segment){
867                        var p = [" m"], n = segment instanceof Array ? segment : segment.args, l = n.length;
868                        this._addArgs(p, n, 0, 2);
869                        if(l > 2){
870                                p.push(" l");
871                                this._addArgs(p, n, 2, l);
872                        }
873                        this.lastControl.type = "";     // no control point after this primitive
874                        return p;
875                },
876                _moveToR: function(segment, last){
877                        return this._moveToA(this._adjustRelCrd(last, segment));
878                },
879                _lineToA: function(segment){
880                        var p = [" l"], n = segment instanceof Array ? segment : segment.args;
881                        this._addArgs(p, n, 0, n.length);
882                        this.lastControl.type = "";     // no control point after this primitive
883                        return p;
884                },
885                _lineToR: function(segment, last){
886                        return this._lineToA(this._adjustRelCrd(last, segment));
887                },
888                _hLineToA: function(segment, last){
889                        var p = [" l"], y = " " + last.y.toFixed(),
890                                n = segment instanceof Array ? segment : segment.args, l = n.length;
891                        for(var i = 0; i < l; ++i){
892                                p.push(" ", n[i].toFixed(), y);
893                        }
894                        this.lastControl.type = "";     // no control point after this primitive
895                        return p;
896                },
897                _hLineToR: function(segment, last){
898                        return this._hLineToA(this._adjustRelPos(last.x, segment), last);
899                },
900                _vLineToA: function(segment, last){
901                        var p = [" l"], x = " " + last.x.toFixed(),
902                                n = segment instanceof Array ? segment : segment.args, l = n.length;
903                        for(var i = 0; i < l; ++i){
904                                p.push(x, " ", n[i].toFixed());
905                        }
906                        this.lastControl.type = "";     // no control point after this primitive
907                        return p;
908                },
909                _vLineToR: function(segment, last){
910                        return this._vLineToA(this._adjustRelPos(last.y, segment), last);
911                },
912                _curveToA: function(segment){
913                        var p = [], n = segment instanceof Array ? segment : segment.args, l = n.length,
914                                lc = this.lastControl;
915                        for(var i = 0; i < l; i += 6){
916                                p.push(" c");
917                                this._addArgs(p, n, i, i + 6);
918                        }
919                        lc.x = n[l - 4];
920                        lc.y = n[l - 3];
921                        lc.type = "C";
922                        return p;
923                },
924                _curveToR: function(segment, last){
925                        return this._curveToA(this._adjustRelCrd(last, segment, 6));
926                },
927                _smoothCurveToA: function(segment, last){
928                        var p = [], n = segment instanceof Array ? segment : segment.args, l = n.length,
929                                lc = this.lastControl, i = 0;
930                        if(lc.type != "C"){
931                                p.push(" c");
932                                this._addArgs(p, [last.x, last.y], 0, 2);
933                                this._addArgs(p, n, 0, 4);
934                                lc.x = n[0];
935                                lc.y = n[1];
936                                lc.type = "C";
937                                i = 4;
938                        }
939                        for(; i < l; i += 4){
940                                p.push(" c");
941                                this._addArgs(p, [
942                                        2 * last.x - lc.x,
943                                        2 * last.y - lc.y
944                                ], 0, 2);
945                                this._addArgs(p, n, i, i + 4);
946                                lc.x = n[i];
947                                lc.y = n[i + 1];
948                        }
949                        return p;
950                },
951                _smoothCurveToR: function(segment, last){
952                        return this._smoothCurveToA(this._adjustRelCrd(last, segment, 4), last);
953                },
954                _qCurveToA: function(segment){
955                        var p = [], n = segment instanceof Array ? segment : segment.args, l = n.length,
956                                lc = this.lastControl;
957                        for(var i = 0; i < l; i += 4){
958                                p.push(" qb");
959                                this._addArgs(p, n, i, i + 4);
960                        }
961                        lc.x = n[l - 4];
962                        lc.y = n[l - 3];
963                        lc.type = "Q";
964                        return p;
965                },
966                _qCurveToR: function(segment, last){
967                        return this._qCurveToA(this._adjustRelCrd(last, segment, 4));
968                },
969                _qSmoothCurveToA: function(segment, last){
970                        var p = [], n = segment instanceof Array ? segment : segment.args, l = n.length,
971                                lc = this.lastControl, i = 0;
972                        if(lc.type != "Q"){
973                                p.push(" qb");
974                                this._addArgs(p, [
975                                        lc.x = last.x,
976                                        lc.y = last.y
977                                ], 0, 2);
978                                lc.type = "Q";
979                                this._addArgs(p, n, 0, 2);
980                                i = 2;
981                        }
982                        for(; i < l; i += 2){
983                                p.push(" qb");
984                                this._addArgs(p, [
985                                        lc.x = 2 * last.x - lc.x,
986                                        lc.y = 2 * last.y - lc.y
987                                ], 0, 2);
988                                this._addArgs(p, n, i, i + 2);
989                        }
990                        return p;
991                },
992                _qSmoothCurveToR: function(segment, last){
993                        return this._qSmoothCurveToA(this._adjustRelCrd(last, segment, 2), last);
994                },
995                _arcTo: function(segment, last){
996                        var p = [], n = segment.args, l = n.length, relative = segment.action == "a";
997                        for(var i = 0; i < l; i += 7){
998                                var x1 = n[i + 5], y1 = n[i + 6];
999                                if(relative){
1000                                        x1 += last.x;
1001                                        y1 += last.y;
1002                                }
1003                                var result = arcLib.arcAsBezier(
1004                                        last, n[i], n[i + 1], n[i + 2],
1005                                        n[i + 3] ? 1 : 0, n[i + 4] ? 1 : 0,
1006                                        x1, y1
1007                                );
1008                                for(var j = 0; j < result.length; ++j){
1009                                        p.push(" c");
1010                                        var t = result[j];
1011                                        this._addArgs(p, t, 0, t.length);
1012                                        this._updateBBox(t[0], t[1]);
1013                                        this._updateBBox(t[2], t[3]);
1014                                        this._updateBBox(t[4], t[5]);
1015                                }
1016                                last.x = x1;
1017                                last.y = y1;
1018                        }
1019                        this.lastControl.type = "";     // no control point after this primitive
1020                        return p;
1021                },
1022                _closePath: function(){
1023                        this.lastControl.type = "";     // no control point after this primitive
1024                        return ["x"];
1025                }
1026        });
1027        vml.Path.nodeType = "shape";
1028
1029        vml.TextPath = declare("dojox.gfx.vml.TextPath", [vml.Path, pathLib.TextPath], {
1030                // summary:
1031                //              a textpath shape (VML)
1032                constructor: function(rawNode){
1033                        if(rawNode){rawNode.setAttribute("dojoGfxType", "textpath");}
1034                        this.fontStyle = null;
1035                        if(!("text" in this)){
1036                                this.text = lang.clone(g.defaultTextPath);
1037                        }
1038                        if(!("fontStyle" in this)){
1039                                this.fontStyle = lang.clone(g.defaultFont);
1040                        }
1041                },
1042                setText: function(newText){
1043                        // summary:
1044                        //              sets a text to be drawn along the path
1045                        this.text = g.makeParameters(this.text,
1046                                typeof newText == "string" ? {text: newText} : newText);
1047                        this._setText();
1048                        return this;    // self
1049                },
1050                setFont: function(newFont){
1051                        // summary:
1052                        //              sets a font for text
1053                        this.fontStyle = typeof newFont == "string" ?
1054                                g.splitFontString(newFont) :
1055                                g.makeParameters(g.defaultFont, newFont);
1056                        this._setFont();
1057                        return this;    // self
1058                },
1059
1060                _setText: function(){
1061                        // summary:
1062                        //              sets a text shape object (VML)
1063                        this.bbox = null;
1064                        var r = this.rawNode, s = this.text,
1065                                // find path and text path
1066                                p = null, t = null, c = r.childNodes;
1067                        for(var i = 0; i < c.length; ++i){
1068                                var tag = c[i].tagName;
1069                                if(tag == "path"){
1070                                        p = c[i];
1071                                        if(t) break;
1072                                }else if(tag == "textpath"){
1073                                        t = c[i];
1074                                        if(p) break;
1075                                }
1076                        }
1077                        if(!p){
1078                                p = this.rawNode.ownerDocument.createElement("v:path");
1079                                r.appendChild(p);
1080                        }
1081                        if(!t){
1082                                t = this.rawNode.ownerDocument.createElement("v:textpath");
1083                                r.appendChild(t);
1084                        }
1085                        p.textPathOk = true;
1086                        t.on = true;
1087                        var a = vml.text_alignment[s.align];
1088                        t.style["v-text-align"] = a ? a : "left";
1089                        t.style["text-decoration"] = s.decoration;
1090                        t.style["v-rotate-letters"] = s.rotated;
1091                        t.style["v-text-kern"] = s.kerning;
1092                        t.string = s.text;
1093                },
1094                _setFont: function(){
1095                        // summary:
1096                        //              sets a font object (VML)
1097                        var f = this.fontStyle, c = this.rawNode.childNodes;
1098                        for(var i = 0; i < c.length; ++i){
1099                                if(c[i].tagName == "textpath"){
1100                                        c[i].style.font = g.makeFontString(f);
1101                                        break;
1102                                }
1103                        }
1104                }
1105        });
1106        vml.TextPath.nodeType = "shape";
1107
1108        vml.Surface = declare("dojox.gfx.vml.Surface", gs.Surface, {
1109                // summary:
1110                //              a surface object to be used for drawings (VML)
1111                constructor: function(){
1112                        gs.Container._init.call(this);
1113                },
1114                destroy: function(){
1115                        this.clear(true);
1116                        this.inherited(arguments);
1117                },
1118                setDimensions: function(width, height){
1119                        // summary:
1120                        //              sets the width and height of the rawNode
1121                        // width: String
1122                        //              width of surface, e.g., "100px"
1123                        // height: String
1124                        //              height of surface, e.g., "100px"
1125                        this.width  = g.normalizedLength(width);        // in pixels
1126                        this.height = g.normalizedLength(height);       // in pixels
1127                        if(!this.rawNode) return this;
1128                        var cs = this.clipNode.style,
1129                                r = this.rawNode, rs = r.style,
1130                                bs = this.bgNode.style,
1131                                ps = this._parent.style, i;
1132                        ps.width = width;
1133                        ps.height = height;
1134                        cs.width  = width;
1135                        cs.height = height;
1136                        cs.clip = "rect(0px " + width + "px " + height + "px 0px)";
1137                        rs.width = width;
1138                        rs.height = height;
1139                        r.coordsize = width + " " + height;
1140                        bs.width = width;
1141                        bs.height = height;
1142                        for(i = 0; i < this.children.length; ++i){
1143                                this.children[i]._setDimensions(width, height);
1144                        }
1145                        return this;    // self
1146                },
1147                getDimensions: function(){
1148                        // summary:
1149                        //              returns an object with properties "width" and "height"
1150                        var t = this.rawNode ? {
1151                                width:  g.normalizedLength(this.rawNode.style.width),
1152                                height: g.normalizedLength(this.rawNode.style.height)} : null;
1153                        if(t.width  <= 0){ t.width  = this.width; }
1154                        if(t.height <= 0){ t.height = this.height; }
1155                        return t;       // Object
1156                }
1157        });
1158
1159        vml.createSurface = function(parentNode, width, height){
1160                // summary:
1161                //              creates a surface (VML)
1162                // parentNode: Node
1163                //              a parent node
1164                // width: String|Number
1165                //              width of surface, e.g., "100px" or 100
1166                // height: String|NUmber
1167                //              height of surface, e.g., "100px" or 100
1168                // returns:
1169                //     newly created surface
1170
1171                if(!width && !height){
1172                        var pos = domGeom.position(parentNode);
1173                        width  = width  || pos.w;
1174                        height = height || pos.h;
1175                }
1176                if(typeof width == "number"){
1177                        width = width + "px";
1178                }
1179                if(typeof height == "number"){
1180                        height = height + "px";
1181                }
1182
1183                var s = new vml.Surface(), p = dom.byId(parentNode),
1184                        c = s.clipNode = p.ownerDocument.createElement("div"),
1185                        r = s.rawNode = p.ownerDocument.createElement("v:group"),
1186                        cs = c.style, rs = r.style;
1187
1188                if(has("ie") > 7){
1189                        rs.display = "inline-block";
1190                }
1191
1192                s._parent = p;
1193                s._nodes.push(c);       // other elements will be deleted as parts of "c"
1194
1195                p.style.width  = width;
1196                p.style.height = height;
1197
1198                cs.position = "absolute";
1199                cs.width  = width;
1200                cs.height = height;
1201                cs.clip = "rect(0px " + width + " " + height + " 0px)";
1202                rs.position = "absolute";
1203                rs.width  = width;
1204                rs.height = height;
1205                r.coordsize = (width === "100%" ? width : parseFloat(width)) + " " +
1206                        (height === "100%" ? height : parseFloat(height));
1207                r.coordorigin = "0 0";
1208
1209                // create a background rectangle, which is required to show all other shapes
1210                var b = s.bgNode = r.ownerDocument.createElement("v:rect"), bs = b.style;
1211                bs.left = bs.top = 0;
1212                bs.width  = rs.width;
1213                bs.height = rs.height;
1214                b.filled = b.stroked = "f";
1215
1216                r.appendChild(b);
1217                c.appendChild(r);
1218                p.appendChild(c);
1219
1220                s.width  = g.normalizedLength(width);   // in pixels
1221                s.height = g.normalizedLength(height);  // in pixels
1222
1223                return s;       // dojox/gfx/shape.Surface
1224        };
1225
1226        // Extenders
1227       
1228        // copied from dojox/gfx/utils
1229        function forEach(object, f, o){
1230                o = o || kernel.global;
1231                f.call(o, object);
1232                if(object instanceof g.Surface || object instanceof g.Group){
1233                        arr.forEach(object.children, function(shape){
1234                                forEach(shape, f, o);
1235                        });
1236                }
1237        }
1238
1239        var addPatch9624 = function(shape){
1240                if(this != shape.getParent()){
1241                        // cleanup from old parent
1242                        var oldParent = shape.getParent();
1243                        if(oldParent) { oldParent.remove(shape); }
1244                        // then move the raw node
1245                        this.rawNode.appendChild(shape.rawNode);
1246                        C.add.apply(this, arguments);
1247                        // reapply visual attributes (slow..)
1248                        forEach(this, function(s){
1249                                if (typeof(s.getFont) == 'function'){ // text shapes need to be completely refreshed
1250                                        s.setShape(s.getShape());
1251                                        s.setFont(s.getFont());
1252                                }
1253                                if (typeof(s.setFill) == 'function'){ // if setFill is available a setStroke should be safe to assume also
1254                                        s.setFill(s.getFill());
1255                                        s.setStroke(s.getStroke());
1256                                }
1257                        });
1258                }
1259                return this;    // self
1260        };
1261       
1262        var add15 = function(shape){
1263                if(this != shape.getParent()){
1264                        this.rawNode.appendChild(shape.rawNode);
1265                        if(!shape.getParent()){
1266                                // reapply visual attributes
1267                                shape.setFill(shape.getFill());
1268                                shape.setStroke(shape.getStroke());
1269                        }
1270                        C.add.apply(this, arguments);
1271                }
1272                return this;    // self
1273        };
1274
1275        var C = gs.Container, Container = {
1276                add: config.fixVmlAdd === true ? addPatch9624 : add15,
1277                remove: function(shape, silently){
1278                        // summary:
1279                        //              remove a shape from a group/surface
1280                        // shape: dojox/gfx/shape.Shape
1281                        //              a VML shape object
1282                        // silently: Boolean?
1283                        //              if true, regenerate a picture
1284                        if(this == shape.getParent()){
1285                                if(this.rawNode == shape.rawNode.parentNode){
1286                                        this.rawNode.removeChild(shape.rawNode);
1287                                }
1288                                C.remove.apply(this, arguments);
1289                        }
1290                        return this;    // self
1291                },
1292                clear: function(){
1293                        // summary:
1294                        //              removes all shapes from a group/surface
1295                        var r = this.rawNode;
1296                        while(r.firstChild != r.lastChild){
1297                                if(r.firstChild != this.bgNode){
1298                                        r.removeChild(r.firstChild);
1299                                }
1300                                if(r.lastChild != this.bgNode){
1301                                        r.removeChild(r.lastChild);
1302                                }
1303                        }
1304                        return C.clear.apply(this, arguments);
1305                },
1306                getBoundingBox: C.getBoundingBox,
1307                _moveChildToFront: C._moveChildToFront,
1308                _moveChildToBack:  C._moveChildToBack
1309        };
1310
1311        var Creator = {
1312                // summary:
1313                //              VML shape creators
1314                createGroup: function(){
1315                        // summary:
1316                        //              creates a VML group shape
1317                        var node = this.createObject(vml.Group, null);  // dojox/gfx.Group
1318                        // create a background rectangle, which is required to show all other shapes
1319                        var r = node.rawNode.ownerDocument.createElement("v:rect");
1320                        r.style.left = r.style.top = 0;
1321                        r.style.width  = node.rawNode.style.width;
1322                        r.style.height = node.rawNode.style.height;
1323                        r.filled = r.stroked = "f";
1324                        node.rawNode.appendChild(r);
1325                        node.bgNode = r;
1326                        return node;    // dojox/gfx.Group
1327                },
1328                createImage: function(image){
1329                        // summary:
1330                        //              creates a VML image shape
1331                        // image: Object
1332                        //              an image object (see dojox/gfx.defaultImage)
1333                        if(!this.rawNode) return null;
1334                        var shape = new vml.Image(),
1335                                doc = this.rawNode.ownerDocument,
1336                                node = doc.createElement('v:rect');
1337                        node.stroked = "f";
1338                        node.style.width  = this.rawNode.style.width;
1339                        node.style.height = this.rawNode.style.height;
1340                        var img  = doc.createElement('v:imagedata');
1341                        node.appendChild(img);
1342                        shape.setRawNode(node);
1343                        this.rawNode.appendChild(node);
1344                        shape.setShape(image);
1345                        this.add(shape);
1346                        return shape;   // dojox/gfx/shape.Image
1347                },
1348                createRect: function(rect){
1349                        // summary:
1350                        //              creates a rectangle shape
1351                        // rect: Object
1352                        //              a path object (see dojox/gfx.defaultRect)
1353                        if(!this.rawNode) return null;
1354                        var shape = new vml.Rect,
1355                                node = this.rawNode.ownerDocument.createElement("v:roundrect");
1356                        if(has("ie") > 7){
1357                                node.style.display = "inline-block";
1358                        }
1359                        shape.setRawNode(node);
1360                        this.rawNode.appendChild(node);
1361                        shape.setShape(rect);
1362                        this.add(shape);
1363                        return shape;   // dojox/gfx.Rect
1364                },
1365                createObject: function(shapeType, rawShape) {
1366                        // summary:
1367                        //              creates an instance of the passed shapeType class
1368                        // shapeType: Function
1369                        //              a class constructor to create an instance of
1370                        // rawShape: Object
1371                        //              properties to be passed in to the classes "setShape" method
1372                        // overrideSize: Boolean
1373                        //              set the size explicitly, if true
1374                        if(!this.rawNode) return null;
1375                        var shape = new shapeType(),
1376                                node = this.rawNode.ownerDocument.createElement('v:' + shapeType.nodeType);
1377                        shape.setRawNode(node);
1378                        this.rawNode.appendChild(node);
1379                        switch(shapeType){
1380                                case vml.Group:
1381                                case vml.Line:
1382                                case vml.Polyline:
1383                                case vml.Image:
1384                                case vml.Text:
1385                                case vml.Path:
1386                                case vml.TextPath:
1387                                        this._overrideSize(node);
1388                        }
1389                        shape.setShape(rawShape);
1390                        this.add(shape);
1391                        return shape;   // dojox/gfx/shape.Shape
1392                },
1393                _overrideSize: function(node){
1394                        var s = this.rawNode.style, w = s.width, h = s.height;
1395                        node.style.width  = w;
1396                        node.style.height = h;
1397                        node.coordsize = parseInt(w) + " " + parseInt(h);
1398                }
1399        };
1400
1401        lang.extend(vml.Group, Container);
1402        lang.extend(vml.Group, gs.Creator);
1403        lang.extend(vml.Group, Creator);
1404
1405        lang.extend(vml.Surface, Container);
1406        lang.extend(vml.Surface, gs.Creator);
1407        lang.extend(vml.Surface, Creator);
1408
1409        // Mouse/Touch event
1410        vml.fixTarget = function(event, gfxElement){
1411                // summary:
1412                //              Adds the gfxElement to event.gfxTarget if none exists. This new
1413                //              property will carry the GFX element associated with this event.
1414                // event: Object
1415                //              The current input event (MouseEvent or TouchEvent)
1416                // gfxElement: Object
1417                //              The GFX target element
1418                if (!event.gfxTarget) {
1419                        event.gfxTarget = event.target.__gfxObject__;
1420                }
1421                return true;
1422        };
1423       
1424        return vml;
1425});
Note: See TracBrowser for help on using the repository browser.