source: Dev/trunk/src/client/dojox/gfx/svg.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: 30.1 KB
Line 
1define(["dojo/_base/lang", "dojo/_base/sniff", "dojo/_base/window", "dojo/dom", "dojo/_base/declare", "dojo/_base/array",
2  "dojo/dom-geometry", "dojo/dom-attr", "dojo/_base/Color", "./_base", "./shape", "./path"],
3function(lang, has, win, dom, declare, arr, domGeom, domAttr, Color, g, gs, pathLib){
4
5        var svg = g.svg = {
6                // summary:
7                //              This the graphics rendering bridge for browsers compliant with W3C SVG1.0.
8                //              This is the preferred renderer to use for interactive and accessible graphics.
9        };
10        svg.useSvgWeb = (typeof window.svgweb != "undefined");
11
12        // Need to detect iOS in order to workaround bug when
13        // touching nodes with text
14        var uagent = navigator.userAgent,
15                safMobile = has("ios"),
16                android = has("android"),
17                textRenderingFix = has("chrome") || (android && android>=4) ? "auto" : "optimizeLegibility";// #16099, #16461
18
19        function _createElementNS(ns, nodeType){
20                // summary:
21                //              Internal helper to deal with creating elements that
22                //              are namespaced.  Mainly to get SVG markup output
23                //              working on IE.
24                if(win.doc.createElementNS){
25                        return win.doc.createElementNS(ns,nodeType);
26                }else{
27                        return win.doc.createElement(nodeType);
28                }
29        }
30       
31        function _setAttributeNS(node, ns, attr, value){
32                if(node.setAttributeNS){
33                        return node.setAttributeNS(ns, attr, value);
34                }else{
35                        return node.setAttribute(attr, value);
36                }
37        }
38
39        function _createTextNode(text){
40                if(svg.useSvgWeb){
41                        return win.doc.createTextNode(text, true);
42                }else{
43                        return win.doc.createTextNode(text);
44                }
45        }
46
47        function _createFragment(){
48                if(svg.useSvgWeb){
49                        return win.doc.createDocumentFragment(true);
50                }else{
51                        return win.doc.createDocumentFragment();
52                }
53        }
54
55        svg.xmlns = {
56                xlink: "http://www.w3.org/1999/xlink",
57                svg:   "http://www.w3.org/2000/svg"
58        };
59
60        svg.getRef = function(name){
61                // summary:
62                //              looks up a node by its external name
63                // name: String
64                //              an SVG external reference
65                // returns:
66                //      returns a DOM Node specified by the name argument or null
67                if(!name || name == "none") return null;
68                if(name.match(/^url\(#.+\)$/)){
69                        return dom.byId(name.slice(5, -1));     // Node
70                }
71                // alternative representation of a reference
72                if(name.match(/^#dojoUnique\d+$/)){
73                        // we assume here that a reference was generated by dojox/gfx
74                        return dom.byId(name.slice(1)); // Node
75                }
76                return null;    // Node
77        };
78
79        svg.dasharray = {
80                solid:                          "none",
81                shortdash:                      [4, 1],
82                shortdot:                       [1, 1],
83                shortdashdot:           [4, 1, 1, 1],
84                shortdashdotdot:        [4, 1, 1, 1, 1, 1],
85                dot:                            [1, 3],
86                dash:                           [4, 3],
87                longdash:                       [8, 3],
88                dashdot:                        [4, 3, 1, 3],
89                longdashdot:            [8, 3, 1, 3],
90                longdashdotdot:         [8, 3, 1, 3, 1, 3]
91        };
92
93        var clipCount = 0;
94
95        svg.Shape = declare("dojox.gfx.svg.Shape", gs.Shape, {
96                // summary:
97                //              SVG-specific implementation of dojox/gfx/shape.Shape methods
98
99                destroy: function(){
100                        if(this.fillStyle && "type" in this.fillStyle){
101                                var fill = this.rawNode.getAttribute("fill"),
102                                        ref  = svg.getRef(fill);
103                                if(ref){
104                                        ref.parentNode.removeChild(ref);
105                                }
106                        }
107                        if(this.clip){
108                                var clipPathProp = this.rawNode.getAttribute("clip-path");
109                                if(clipPathProp){
110                                        var clipNode = dom.byId(clipPathProp.match(/gfx_clip[\d]+/)[0]);
111                                        if(clipNode){ clipNode.parentNode.removeChild(clipNode); }
112                                }
113                        }
114                        gs.Shape.prototype.destroy.apply(this, arguments);
115                },
116
117                setFill: function(fill){
118                        // summary:
119                        //              sets a fill object (SVG)
120                        // fill: Object
121                        //              a fill object
122                        //              (see dojox/gfx.defaultLinearGradient,
123                        //              dojox/gfx.defaultRadialGradient,
124                        //              dojox/gfx.defaultPattern,
125                        //              or dojo/_base/Color)
126
127                        if(!fill){
128                                // don't fill
129                                this.fillStyle = null;
130                                this.rawNode.setAttribute("fill", "none");
131                                this.rawNode.setAttribute("fill-opacity", 0);
132                                return this;
133                        }
134                        var f;
135                        // FIXME: slightly magical. We're using the outer scope's "f", but setting it later
136                        var setter = function(x){
137                                        // we assume that we're executing in the scope of the node to mutate
138                                        this.setAttribute(x, f[x].toFixed(8));
139                                };
140                        if(typeof(fill) == "object" && "type" in fill){
141                                // gradient
142                                switch(fill.type){
143                                        case "linear":
144                                                f = g.makeParameters(g.defaultLinearGradient, fill);
145                                                var gradient = this._setFillObject(f, "linearGradient");
146                                                arr.forEach(["x1", "y1", "x2", "y2"], setter, gradient);
147                                                break;
148                                        case "radial":
149                                                f = g.makeParameters(g.defaultRadialGradient, fill);
150                                                var grad = this._setFillObject(f, "radialGradient");
151                                                arr.forEach(["cx", "cy", "r"], setter, grad);
152                                                break;
153                                        case "pattern":
154                                                f = g.makeParameters(g.defaultPattern, fill);
155                                                var pattern = this._setFillObject(f, "pattern");
156                                                arr.forEach(["x", "y", "width", "height"], setter, pattern);
157                                                break;
158                                }
159                                this.fillStyle = f;
160                                return this;
161                        }
162                        // color object
163                        f = g.normalizeColor(fill);
164                        this.fillStyle = f;
165                        this.rawNode.setAttribute("fill", f.toCss());
166                        this.rawNode.setAttribute("fill-opacity", f.a);
167                        this.rawNode.setAttribute("fill-rule", "evenodd");
168                        return this;    // self
169                },
170
171                setStroke: function(stroke){
172                        // summary:
173                        //              sets a stroke object (SVG)
174                        // stroke: Object
175                        //              a stroke object (see dojox/gfx.defaultStroke)
176
177                        var rn = this.rawNode;
178                        if(!stroke){
179                                // don't stroke
180                                this.strokeStyle = null;
181                                rn.setAttribute("stroke", "none");
182                                rn.setAttribute("stroke-opacity", 0);
183                                return this;
184                        }
185                        // normalize the stroke
186                        if(typeof stroke == "string" || lang.isArray(stroke) || stroke instanceof Color){
187                                stroke = { color: stroke };
188                        }
189                        var s = this.strokeStyle = g.makeParameters(g.defaultStroke, stroke);
190                        s.color = g.normalizeColor(s.color);
191                        // generate attributes
192                        if(s){
193                                rn.setAttribute("stroke", s.color.toCss());
194                                rn.setAttribute("stroke-opacity", s.color.a);
195                                rn.setAttribute("stroke-width",   s.width);
196                                rn.setAttribute("stroke-linecap", s.cap);
197                                if(typeof s.join == "number"){
198                                        rn.setAttribute("stroke-linejoin",   "miter");
199                                        rn.setAttribute("stroke-miterlimit", s.join);
200                                }else{
201                                        rn.setAttribute("stroke-linejoin",   s.join);
202                                }
203                                var da = s.style.toLowerCase();
204                                if(da in svg.dasharray){
205                                        da = svg.dasharray[da];
206                                }
207                                if(da instanceof Array){
208                                        da = lang._toArray(da);
209                                        var i;
210                                        for(i = 0; i < da.length; ++i){
211                                                da[i] *= s.width;
212                                        }
213                                        if(s.cap != "butt"){
214                                                for(i = 0; i < da.length; i += 2){
215                                                        da[i] -= s.width;
216                                                        if(da[i] < 1){ da[i] = 1; }
217                                                }
218                                                for(i = 1; i < da.length; i += 2){
219                                                        da[i] += s.width;
220                                                }
221                                        }
222                                        da = da.join(",");
223                                }
224                                rn.setAttribute("stroke-dasharray", da);
225                                rn.setAttribute("dojoGfxStrokeStyle", s.style);
226                        }
227                        return this;    // self
228                },
229
230                _getParentSurface: function(){
231                        var surface = this.parent;
232                        for(; surface && !(surface instanceof g.Surface); surface = surface.parent);
233                        return surface;
234                },
235
236                _setFillObject: function(f, nodeType){
237                        var svgns = svg.xmlns.svg;
238                        this.fillStyle = f;
239                        var surface = this._getParentSurface(),
240                                defs = surface.defNode,
241                                fill = this.rawNode.getAttribute("fill"),
242                                ref  = svg.getRef(fill);
243                        if(ref){
244                                fill = ref;
245                                if(fill.tagName.toLowerCase() != nodeType.toLowerCase()){
246                                        var id = fill.id;
247                                        fill.parentNode.removeChild(fill);
248                                        fill = _createElementNS(svgns, nodeType);
249                                        fill.setAttribute("id", id);
250                                        defs.appendChild(fill);
251                                }else{
252                                        while(fill.childNodes.length){
253                                                fill.removeChild(fill.lastChild);
254                                        }
255                                }
256                        }else{
257                                fill = _createElementNS(svgns, nodeType);
258                                fill.setAttribute("id", g._base._getUniqueId());
259                                defs.appendChild(fill);
260                        }
261                        if(nodeType == "pattern"){
262                                fill.setAttribute("patternUnits", "userSpaceOnUse");
263                                var img = _createElementNS(svgns, "image");
264                                img.setAttribute("x", 0);
265                                img.setAttribute("y", 0);
266                                img.setAttribute("width",  f.width .toFixed(8));
267                                img.setAttribute("height", f.height.toFixed(8));
268                                _setAttributeNS(img, svg.xmlns.xlink, "xlink:href", f.src);
269                                fill.appendChild(img);
270                        }else{
271                                fill.setAttribute("gradientUnits", "userSpaceOnUse");
272                                for(var i = 0; i < f.colors.length; ++i){
273                                        var c = f.colors[i], t = _createElementNS(svgns, "stop"),
274                                                cc = c.color = g.normalizeColor(c.color);
275                                        t.setAttribute("offset",       c.offset.toFixed(8));
276                                        t.setAttribute("stop-color",   cc.toCss());
277                                        t.setAttribute("stop-opacity", cc.a);
278                                        fill.appendChild(t);
279                                }
280                        }
281                        this.rawNode.setAttribute("fill", "url(#" + fill.getAttribute("id") +")");
282                        this.rawNode.removeAttribute("fill-opacity");
283                        this.rawNode.setAttribute("fill-rule", "evenodd");
284                        return fill;
285                },
286
287                _applyTransform: function() {
288                        var matrix = this.matrix;
289                        if(matrix){
290                                var tm = this.matrix;
291                                this.rawNode.setAttribute("transform", "matrix(" +
292                                        tm.xx.toFixed(8) + "," + tm.yx.toFixed(8) + "," +
293                                        tm.xy.toFixed(8) + "," + tm.yy.toFixed(8) + "," +
294                                        tm.dx.toFixed(8) + "," + tm.dy.toFixed(8) + ")");
295                        }else{
296                                this.rawNode.removeAttribute("transform");
297                        }
298                        return this;
299                },
300
301                setRawNode: function(rawNode){
302                        // summary:
303                        //              assigns and clears the underlying node that will represent this
304                        //              shape. Once set, transforms, gradients, etc, can be applied.
305                        //              (no fill & stroke by default)
306                        var r = this.rawNode = rawNode;
307                        if(this.shape.type!="image"){
308                                r.setAttribute("fill", "none");
309                        }
310                        r.setAttribute("fill-opacity", 0);
311                        r.setAttribute("stroke", "none");
312                        r.setAttribute("stroke-opacity", 0);
313                        r.setAttribute("stroke-width", 1);
314                        r.setAttribute("stroke-linecap", "butt");
315                        r.setAttribute("stroke-linejoin", "miter");
316                        r.setAttribute("stroke-miterlimit", 4);
317                        // Bind GFX object with SVG node for ease of retrieval - that is to
318                        // save code/performance to keep this association elsewhere
319                        r.__gfxObject__ = this;
320                },
321
322                setShape: function(newShape){
323                        // summary:
324                        //              sets a shape object (SVG)
325                        // newShape: Object
326                        //              a shape object
327                        //              (see dojox/gfx.defaultPath,
328                        //              dojox/gfx.defaultPolyline,
329                        //              dojox/gfx.defaultRect,
330                        //              dojox/gfx.defaultEllipse,
331                        //              dojox/gfx.defaultCircle,
332                        //              dojox/gfx.defaultLine,
333                        //              or dojox/gfx.defaultImage)
334                        this.shape = g.makeParameters(this.shape, newShape);
335                        for(var i in this.shape){
336                                if(i != "type"){
337                                        this.rawNode.setAttribute(i, this.shape[i]);
338                                }
339                        }
340                        this.bbox = null;
341                        return this;    // self
342                },
343
344                // move family
345
346                _moveToFront: function(){
347                        // summary:
348                        //              moves a shape to front of its parent's list of shapes (SVG)
349                        this.rawNode.parentNode.appendChild(this.rawNode);
350                        return this;    // self
351                },
352                _moveToBack: function(){
353                        // summary:
354                        //              moves a shape to back of its parent's list of shapes (SVG)
355                        this.rawNode.parentNode.insertBefore(this.rawNode, this.rawNode.parentNode.firstChild);
356                        return this;    // self
357                },
358                setClip: function(clip){
359                        // summary:
360                        //              sets the clipping area of this shape.
361                        // description:
362                        //              This method overrides the dojox/gfx/shape.Shape.setClip() method.
363                        // clip: Object
364                        //              an object that defines the clipping geometry, or null to remove clip.
365                        this.inherited(arguments);
366                        var clipType = clip ? "width" in clip ? "rect" :
367                                                        "cx" in clip ? "ellipse" :
368                                                        "points" in clip ? "polyline" : "d" in clip ? "path" : null : null;
369                        if(clip && !clipType){
370                                return this;
371                        }
372                        if(clipType === "polyline"){
373                                clip = lang.clone(clip);
374                                clip.points = clip.points.join(",");
375                        }
376                        var clipNode, clipShape,
377                                clipPathProp = domAttr.get(this.rawNode, "clip-path");
378                        if(clipPathProp){
379                                clipNode = dom.byId(clipPathProp.match(/gfx_clip[\d]+/)[0]);
380                                if(clipNode){ // may be null if not in the DOM anymore
381                                        clipNode.removeChild(clipNode.childNodes[0]);
382                                }
383                        }
384                        if(clip){
385                                if(clipNode){
386                                        clipShape = _createElementNS(svg.xmlns.svg, clipType);
387                                        clipNode.appendChild(clipShape);
388                                }else{
389                                        var idIndex = ++clipCount;
390                                        var clipId = "gfx_clip" + idIndex;
391                                        var clipUrl = "url(#" + clipId + ")";
392                                        this.rawNode.setAttribute("clip-path", clipUrl);
393                                        clipNode = _createElementNS(svg.xmlns.svg, "clipPath");
394                                        clipShape = _createElementNS(svg.xmlns.svg, clipType);
395                                        clipNode.appendChild(clipShape);
396                                        this.rawNode.parentNode.appendChild(clipNode);
397                                        domAttr.set(clipNode, "id", clipId);
398                                }
399                                domAttr.set(clipShape, clip);
400                        }else{
401                                //remove clip-path
402                                this.rawNode.removeAttribute("clip-path");
403                                if(clipNode){
404                                        clipNode.parentNode.removeChild(clipNode);
405                                }
406                        }
407                        return this;
408                },
409                _removeClipNode: function(){
410                        var clipNode, clipPathProp = domAttr.get(this.rawNode, "clip-path");
411                        if(clipPathProp){
412                                clipNode = dom.byId(clipPathProp.match(/gfx_clip[\d]+/)[0]);
413                                if(clipNode){
414                                        clipNode.parentNode.removeChild(clipNode);
415                                }
416                        }
417                        return clipNode;
418                }
419        });
420
421
422        svg.Group = declare("dojox.gfx.svg.Group", svg.Shape, {
423                // summary:
424                //              a group shape (SVG), which can be used
425                //              to logically group shapes (e.g, to propagate matricies)
426                constructor: function(){
427                        gs.Container._init.call(this);
428                },
429                setRawNode: function(rawNode){
430                        // summary:
431                        //              sets a raw SVG node to be used by this shape
432                        // rawNode: Node
433                        //              an SVG node
434                        this.rawNode = rawNode;
435                        // Bind GFX object with SVG node for ease of retrieval - that is to
436                        // save code/performance to keep this association elsewhere
437                        this.rawNode.__gfxObject__ = this;
438                },
439                destroy: function(){
440                        // summary:
441                        //              Releases all internal resources owned by this shape. Once this method has been called,
442                        //              the instance is considered disposed and should not be used anymore.
443                        this.clear(true);
444                        // avoid this.inherited
445                        svg.Shape.prototype.destroy.apply(this, arguments);
446                }
447        });
448        svg.Group.nodeType = "g";
449
450        svg.Rect = declare("dojox.gfx.svg.Rect", [svg.Shape, gs.Rect], {
451                // summary:
452                //              a rectangle shape (SVG)
453                setShape: function(newShape){
454                        // summary:
455                        //              sets a rectangle shape object (SVG)
456                        // newShape: Object
457                        //              a rectangle shape object
458                        this.shape = g.makeParameters(this.shape, newShape);
459                        this.bbox = null;
460                        for(var i in this.shape){
461                                if(i != "type" && i != "r"){
462                                        this.rawNode.setAttribute(i, this.shape[i]);
463                                }
464                        }
465                        if(this.shape.r != null){
466                                this.rawNode.setAttribute("ry", this.shape.r);
467                                this.rawNode.setAttribute("rx", this.shape.r);
468                        }
469                        return this;    // self
470                }
471        });
472        svg.Rect.nodeType = "rect";
473
474        svg.Ellipse = declare("dojox.gfx.svg.Ellipse", [svg.Shape, gs.Ellipse], {});
475        svg.Ellipse.nodeType = "ellipse";
476
477        svg.Circle = declare("dojox.gfx.svg.Circle", [svg.Shape, gs.Circle], {});
478        svg.Circle.nodeType = "circle";
479
480        svg.Line = declare("dojox.gfx.svg.Line", [svg.Shape, gs.Line], {});
481        svg.Line.nodeType = "line";
482
483        svg.Polyline = declare("dojox.gfx.svg.Polyline", [svg.Shape, gs.Polyline], {
484                // summary:
485                //              a polyline/polygon shape (SVG)
486                setShape: function(points, closed){
487                        // summary:
488                        //              sets a polyline/polygon shape object (SVG)
489                        // points: Object|Array
490                        //              a polyline/polygon shape object, or an array of points
491                        if(points && points instanceof Array){
492                                this.shape = g.makeParameters(this.shape, { points: points });
493                                if(closed && this.shape.points.length){
494                                        this.shape.points.push(this.shape.points[0]);
495                                }
496                        }else{
497                                this.shape = g.makeParameters(this.shape, points);
498                        }
499                        this.bbox = null;
500                        this._normalizePoints();
501                        var attr = [], p = this.shape.points;
502                        for(var i = 0; i < p.length; ++i){
503                                attr.push(p[i].x.toFixed(8), p[i].y.toFixed(8));
504                        }
505                        this.rawNode.setAttribute("points", attr.join(" "));
506                        return this;    // self
507                }
508        });
509        svg.Polyline.nodeType = "polyline";
510
511        svg.Image = declare("dojox.gfx.svg.Image", [svg.Shape, gs.Image], {
512                // summary:
513                //              an image (SVG)
514                setShape: function(newShape){
515                        // summary:
516                        //              sets an image shape object (SVG)
517                        // newShape: Object
518                        //              an image shape object
519                        this.shape = g.makeParameters(this.shape, newShape);
520                        this.bbox = null;
521                        var rawNode = this.rawNode;
522                        for(var i in this.shape){
523                                if(i != "type" && i != "src"){
524                                        rawNode.setAttribute(i, this.shape[i]);
525                                }
526                        }
527                        rawNode.setAttribute("preserveAspectRatio", "none");
528                        _setAttributeNS(rawNode, svg.xmlns.xlink, "xlink:href", this.shape.src);
529                        // Bind GFX object with SVG node for ease of retrieval - that is to
530                        // save code/performance to keep this association elsewhere
531                        rawNode.__gfxObject__ = this;
532                        return this;    // self
533                }
534        });
535        svg.Image.nodeType = "image";
536
537        svg.Text = declare("dojox.gfx.svg.Text", [svg.Shape, gs.Text], {
538                // summary:
539                //              an anchored text (SVG)
540                setShape: function(newShape){
541                        // summary:
542                        //              sets a text shape object (SVG)
543                        // newShape: Object
544                        //              a text shape object
545                        this.shape = g.makeParameters(this.shape, newShape);
546                        this.bbox = null;
547                        var r = this.rawNode, s = this.shape;
548                        r.setAttribute("x", s.x);
549                        r.setAttribute("y", s.y);
550                        r.setAttribute("text-anchor", s.align);
551                        r.setAttribute("text-decoration", s.decoration);
552                        r.setAttribute("rotate", s.rotated ? 90 : 0);
553                        r.setAttribute("kerning", s.kerning ? "auto" : 0);
554                        r.setAttribute("text-rendering", textRenderingFix);
555
556                        // update the text content
557                        if(r.firstChild){
558                                r.firstChild.nodeValue = s.text;
559                        }else{
560                                r.appendChild(_createTextNode(s.text));
561                        }
562                        return this;    // self
563                },
564                getTextWidth: function(){
565                        // summary:
566                        //              get the text width in pixels
567                        var rawNode = this.rawNode,
568                                oldParent = rawNode.parentNode,
569                                _measurementNode = rawNode.cloneNode(true);
570                        _measurementNode.style.visibility = "hidden";
571
572                        // solution to the "orphan issue" in FF
573                        var _width = 0, _text = _measurementNode.firstChild.nodeValue;
574                        oldParent.appendChild(_measurementNode);
575
576                        // solution to the "orphan issue" in Opera
577                        // (nodeValue == "" hangs firefox)
578                        if(_text!=""){
579                                while(!_width){
580//Yang: work around svgweb bug 417 -- http://code.google.com/p/svgweb/issues/detail?id=417
581if (_measurementNode.getBBox)
582                                        _width = parseInt(_measurementNode.getBBox().width);
583else
584        _width = 68;
585                                }
586                        }
587                        oldParent.removeChild(_measurementNode);
588                        return _width;
589                },
590                getBoundingBox: function(){
591                        var s = this.getShape(), bbox = null;
592                        if(s.text){
593                                // try/catch the FF native getBBox error.
594                                try {
595                                        bbox = this.rawNode.getBBox();
596                                } catch (e) {
597                                        // under FF when the node is orphan (all other browsers return a 0ed bbox.
598                                        bbox = {x:0, y:0, width:0, height:0};
599                                }
600                        }
601                        return bbox;
602                }
603        });
604        svg.Text.nodeType = "text";
605
606        svg.Path = declare("dojox.gfx.svg.Path", [svg.Shape, pathLib.Path], {
607                // summary:
608                //              a path shape (SVG)
609                _updateWithSegment: function(segment){
610                        // summary:
611                        //              updates the bounding box of path with new segment
612                        // segment: Object
613                        //              a segment
614                        this.inherited(arguments);
615                        if(typeof(this.shape.path) == "string"){
616                                this.rawNode.setAttribute("d", this.shape.path);
617                        }
618                },
619                setShape: function(newShape){
620                        // summary:
621                        //              forms a path using a shape (SVG)
622                        // newShape: Object
623                        //              an SVG path string or a path object (see dojox/gfx.defaultPath)
624                        this.inherited(arguments);
625                        if(this.shape.path){
626                                this.rawNode.setAttribute("d", this.shape.path);
627                        }else{
628                                this.rawNode.removeAttribute("d");
629                        }
630                        return this;    // self
631                }
632        });
633        svg.Path.nodeType = "path";
634
635        svg.TextPath = declare("dojox.gfx.svg.TextPath", [svg.Shape, pathLib.TextPath], {
636                // summary:
637                //              a textpath shape (SVG)
638                _updateWithSegment: function(segment){
639                        // summary:
640                        //              updates the bounding box of path with new segment
641                        // segment: Object
642                        //              a segment
643                        this.inherited(arguments);
644                        this._setTextPath();
645                },
646                setShape: function(newShape){
647                        // summary:
648                        //              forms a path using a shape (SVG)
649                        // newShape: Object
650                        //              an SVG path string or a path object (see dojox/gfx.defaultPath)
651                        this.inherited(arguments);
652                        this._setTextPath();
653                        return this;    // self
654                },
655                _setTextPath: function(){
656                        if(typeof this.shape.path != "string"){ return; }
657                        var r = this.rawNode;
658                        if(!r.firstChild){
659                                var tp = _createElementNS(svg.xmlns.svg, "textPath"),
660                                        tx = _createTextNode("");
661                                tp.appendChild(tx);
662                                r.appendChild(tp);
663                        }
664                        var ref  = r.firstChild.getAttributeNS(svg.xmlns.xlink, "href"),
665                                path = ref && svg.getRef(ref);
666                        if(!path){
667                                var surface = this._getParentSurface();
668                                if(surface){
669                                        var defs = surface.defNode;
670                                        path = _createElementNS(svg.xmlns.svg, "path");
671                                        var id = g._base._getUniqueId();
672                                        path.setAttribute("id", id);
673                                        defs.appendChild(path);
674                                        _setAttributeNS(r.firstChild, svg.xmlns.xlink, "xlink:href", "#" + id);
675                                }
676                        }
677                        if(path){
678                                path.setAttribute("d", this.shape.path);
679                        }
680                },
681                _setText: function(){
682                        var r = this.rawNode;
683                        if(!r.firstChild){
684                                var tp = _createElementNS(svg.xmlns.svg, "textPath"),
685                                        tx = _createTextNode("");
686                                tp.appendChild(tx);
687                                r.appendChild(tp);
688                        }
689                        r = r.firstChild;
690                        var t = this.text;
691                        r.setAttribute("alignment-baseline", "middle");
692                        switch(t.align){
693                                case "middle":
694                                        r.setAttribute("text-anchor", "middle");
695                                        r.setAttribute("startOffset", "50%");
696                                        break;
697                                case "end":
698                                        r.setAttribute("text-anchor", "end");
699                                        r.setAttribute("startOffset", "100%");
700                                        break;
701                                default:
702                                        r.setAttribute("text-anchor", "start");
703                                        r.setAttribute("startOffset", "0%");
704                                        break;
705                        }
706                        //r.parentNode.setAttribute("alignment-baseline", "central");
707                        //r.setAttribute("dominant-baseline", "central");
708                        r.setAttribute("baseline-shift", "0.5ex");
709                        r.setAttribute("text-decoration", t.decoration);
710                        r.setAttribute("rotate", t.rotated ? 90 : 0);
711                        r.setAttribute("kerning", t.kerning ? "auto" : 0);
712                        r.firstChild.data = t.text;
713                }
714        });
715        svg.TextPath.nodeType = "text";
716
717        // Fix for setDimension bug:
718        // http://bugs.dojotoolkit.org/ticket/16100
719        // (https://code.google.com/p/chromium/issues/detail?id=162628)
720        var hasSvgSetAttributeBug = (function(){ var matches = /WebKit\/(\d*)/.exec(uagent); return matches ? matches[1] : 0})() > 534;
721
722        svg.Surface = declare("dojox.gfx.svg.Surface", gs.Surface, {
723                // summary:
724                //              a surface object to be used for drawings (SVG)
725                constructor: function(){
726                        gs.Container._init.call(this);
727                },
728                destroy: function(){
729                        // no need to call svg.Container.clear to remove the children raw
730                        // nodes since the surface raw node will be removed. So, only dispose at gfx level     
731                        gs.Container.clear.call(this, true);
732                        this.defNode = null;    // release the external reference
733                        this.inherited(arguments);
734                },
735                setDimensions: function(width, height){
736                        // summary:
737                        //              sets the width and height of the rawNode
738                        // width: String
739                        //              width of surface, e.g., "100px"
740                        // height: String
741                        //              height of surface, e.g., "100px"
742                        if(!this.rawNode){ return this; }
743                        this.rawNode.setAttribute("width",  width);
744                        this.rawNode.setAttribute("height", height);
745                        if(hasSvgSetAttributeBug){
746                                this.rawNode.style.width =  width;
747                                this.rawNode.style.height =  height;
748                        }
749                        return this;    // self
750                },
751                getDimensions: function(){
752                        // summary:
753                        //              returns an object with properties "width" and "height"
754                        var t = this.rawNode ? {
755                                width:  g.normalizedLength(this.rawNode.getAttribute("width")),
756                                height: g.normalizedLength(this.rawNode.getAttribute("height"))} : null;
757                        return t;       // Object
758                }
759        });
760
761        svg.createSurface = function(parentNode, width, height){
762                // summary:
763                //              creates a surface (SVG)
764                // parentNode: Node
765                //              a parent node
766                // width: String|Number
767                //              width of surface, e.g., "100px" or 100
768                // height: String|Number
769                //              height of surface, e.g., "100px" or 100
770                // returns: dojox/gfx/shape.Surface
771                //     newly created surface
772
773                var s = new svg.Surface();
774                s.rawNode = _createElementNS(svg.xmlns.svg, "svg");
775                s.rawNode.setAttribute("overflow", "hidden");
776                if(width){
777                        s.rawNode.setAttribute("width",  width);
778                }
779                if(height){
780                        s.rawNode.setAttribute("height", height);
781                }
782
783                var defNode = _createElementNS(svg.xmlns.svg, "defs");
784                s.rawNode.appendChild(defNode);
785                s.defNode = defNode;
786
787                s._parent = dom.byId(parentNode);
788                s._parent.appendChild(s.rawNode);
789
790                g._base._fixMsTouchAction(s);
791
792                return s;       // dojox/gfx.Surface
793        };
794
795        // Extenders
796
797        var Font = {
798                _setFont: function(){
799                        // summary:
800                        //              sets a font object (SVG)
801                        var f = this.fontStyle;
802                        // next line doesn't work in Firefox 2 or Opera 9
803                        //this.rawNode.setAttribute("font", dojox.gfx.makeFontString(this.fontStyle));
804                        this.rawNode.setAttribute("font-style", f.style);
805                        this.rawNode.setAttribute("font-variant", f.variant);
806                        this.rawNode.setAttribute("font-weight", f.weight);
807                        this.rawNode.setAttribute("font-size", f.size);
808                        this.rawNode.setAttribute("font-family", f.family);
809                }
810        };
811
812        var C = gs.Container, Container = {
813                openBatch: function() {
814                        // summary:
815                        //              starts a new batch, subsequent new child shapes will be held in
816                        //              the batch instead of appending to the container directly
817                        if(!this._batch){
818                                this.fragment = _createFragment();
819                        }
820                        ++this._batch;
821                        return this;
822                },
823                closeBatch: function() {
824                        // summary:
825                        //              submits the current batch, append all pending child shapes to DOM
826                        this._batch = this._batch > 0 ? --this._batch : 0;
827                        if (this.fragment && !this._batch) {
828                                this.rawNode.appendChild(this.fragment);
829                                delete this.fragment;
830                        }
831                        return this;
832                },
833                add: function(shape){
834                        // summary:
835                        //              adds a shape to a group/surface
836                        // shape: dojox/gfx/shape.Shape
837                        //              an VML shape object
838                        if(this != shape.getParent()){
839                                if (this.fragment) {
840                                        this.fragment.appendChild(shape.rawNode);
841                                } else {
842                                        this.rawNode.appendChild(shape.rawNode);
843                                }
844                                C.add.apply(this, arguments);
845                                // update clipnode with new parent
846                                shape.setClip(shape.clip);
847                        }
848                        return this;    // self
849                },
850                remove: function(shape, silently){
851                        // summary:
852                        //              remove a shape from a group/surface
853                        // shape: dojox/gfx/shape.Shape
854                        //              an VML shape object
855                        // silently: Boolean?
856                        //              if true, regenerate a picture
857                        if(this == shape.getParent()){
858                                if(this.rawNode == shape.rawNode.parentNode){
859                                        this.rawNode.removeChild(shape.rawNode);
860                                }
861                                if(this.fragment && this.fragment == shape.rawNode.parentNode){
862                                        this.fragment.removeChild(shape.rawNode);
863                                }
864                                // remove clip node from parent
865                                shape._removeClipNode();
866                                C.remove.apply(this, arguments);
867                        }
868                        return this;    // self
869                },
870                clear: function(){
871                        // summary:
872                        //              removes all shapes from a group/surface
873                        var r = this.rawNode;
874                        while(r.lastChild){
875                                r.removeChild(r.lastChild);
876                        }
877                        var defNode = this.defNode;
878                        if(defNode){
879                                while(defNode.lastChild){
880                                        defNode.removeChild(defNode.lastChild);
881                                }
882                                r.appendChild(defNode);
883                        }
884                        return C.clear.apply(this, arguments);
885                },
886                getBoundingBox: C.getBoundingBox,
887                _moveChildToFront: C._moveChildToFront,
888                _moveChildToBack:  C._moveChildToBack
889        };
890
891        var Creator = {
892                // summary:
893                //              SVG shape creators
894                createObject: function(shapeType, rawShape){
895                        // summary:
896                        //              creates an instance of the passed shapeType class
897                        // shapeType: Function
898                        //              a class constructor to create an instance of
899                        // rawShape: Object
900                        //              properties to be passed in to the classes "setShape" method
901                        if(!this.rawNode){ return null; }
902                        var shape = new shapeType(),
903                                node = _createElementNS(svg.xmlns.svg, shapeType.nodeType);
904
905                        shape.setRawNode(node);
906                        shape.setShape(rawShape);
907                        // rawNode.appendChild() will be done inside this.add(shape) below
908                        this.add(shape);
909                        return shape;   // dojox/gfx/shape.Shape
910                }
911        };
912
913        lang.extend(svg.Text, Font);
914        lang.extend(svg.TextPath, Font);
915
916        lang.extend(svg.Group, Container);
917        lang.extend(svg.Group, gs.Creator);
918        lang.extend(svg.Group, Creator);
919
920        lang.extend(svg.Surface, Container);
921        lang.extend(svg.Surface, gs.Creator);
922        lang.extend(svg.Surface, Creator);
923
924        // Mouse/Touch event
925        svg.fixTarget = function(event, gfxElement) {
926                // summary:
927                //              Adds the gfxElement to event.gfxTarget if none exists. This new
928                //              property will carry the GFX element associated with this event.
929                // event: Object
930                //              The current input event (MouseEvent or TouchEvent)
931                // gfxElement: Object
932                //              The GFX target element
933                if (!event.gfxTarget) {
934                        if (safMobile && event.target.wholeText) {
935                                // Workaround iOS bug when touching text nodes
936                                event.gfxTarget = event.target.parentElement.__gfxObject__;
937                        } else {
938                                event.gfxTarget = event.target.__gfxObject__;
939                        }
940                }
941                return true;
942        };
943
944        // some specific override for svgweb + flash
945        if(svg.useSvgWeb){
946                // override createSurface()
947                svg.createSurface = function(parentNode, width, height){
948                        var s = new svg.Surface();
949
950                        // ensure width / height
951                        if(!width || !height){
952                                var pos = domGeom.position(parentNode);
953                                width  = width  || pos.w;
954                                height = height || pos.h;
955                        }
956
957                        // ensure id
958                        parentNode = dom.byId(parentNode);
959                        var id = parentNode.id ? parentNode.id+'_svgweb' : g._base._getUniqueId();
960
961                        // create dynamic svg root
962                        var mockSvg = _createElementNS(svg.xmlns.svg, 'svg');
963                        mockSvg.id = id;
964                        mockSvg.setAttribute('width', width);
965                        mockSvg.setAttribute('height', height);
966                        svgweb.appendChild(mockSvg, parentNode);
967
968                        // notice: any call to the raw node before flash init will fail.
969                        mockSvg.addEventListener('SVGLoad', function(){
970                                // become loaded
971                                s.rawNode = this;
972                                s.isLoaded = true;
973
974                                // init defs
975                                var defNode = _createElementNS(svg.xmlns.svg, "defs");
976                                s.rawNode.appendChild(defNode);
977                                s.defNode = defNode;
978
979                                // notify application
980                                if (s.onLoad)
981                                        s.onLoad(s);
982                        }, false);
983
984                        // flash not loaded yet
985                        s.isLoaded = false;
986                        return s;
987                };
988
989                // override Surface.destroy()
990                svg.Surface.extend({
991                        destroy: function(){
992                                var mockSvg = this.rawNode;
993                                svgweb.removeChild(mockSvg, mockSvg.parentNode);
994                        }
995                });
996
997                // override connect() & disconnect() for Shape & Surface event processing
998                var _eventsProcessing = {
999                        connect: function(name, object, method){
1000                                // connect events using the mock addEventListener() provided by svgweb
1001                                if (name.substring(0, 2)==='on') { name = name.substring(2); }
1002                                if (arguments.length == 2) {
1003                                        method = object;
1004                                } else {
1005                                        method = lang.hitch(object, method);
1006                                }
1007                                this.getEventSource().addEventListener(name, method, false);
1008                                return [this, name, method];
1009                        },
1010                        disconnect: function(token){
1011                                // disconnect events using the mock removeEventListener() provided by svgweb
1012                                this.getEventSource().removeEventListener(token[1], token[2], false);
1013                                delete token[0];
1014                        }
1015                };
1016
1017                lang.extend(svg.Shape, _eventsProcessing);
1018                lang.extend(svg.Surface, _eventsProcessing);
1019        }
1020
1021        return svg;
1022});
Note: See TracBrowser for help on using the repository browser.