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