svg.js 26 KB

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