1 | define(["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"], |
---|
3 | function(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 |
---|
581 | if (_measurementNode.getBBox) |
---|
582 | _width = parseInt(_measurementNode.getBBox().width); |
---|
583 | else |
---|
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 | }); |
---|