svgutils.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  1. /*globals $, svgedit, unescape, DOMParser, ActiveXObject, getStrokedBBox*/
  2. /*jslint vars: true, eqeq: true, bitwise: true, continue: true, forin: true*/
  3. /**
  4. * Package: svgedit.utilities
  5. *
  6. * Licensed under the MIT License
  7. *
  8. * Copyright(c) 2010 Alexis Deveria
  9. * Copyright(c) 2010 Jeff Schiller
  10. */
  11. // Dependencies:
  12. // 1) jQuery
  13. // 2) browser.js
  14. // 3) svgtransformlist.js
  15. // 4) units.js
  16. (function() {'use strict';
  17. if (!svgedit.utilities) {
  18. svgedit.utilities = {};
  19. }
  20. // Constants
  21. // String used to encode base64.
  22. var KEYSTR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
  23. var NS = svgedit.NS;
  24. // Much faster than running getBBox() every time
  25. var visElems = 'a,circle,ellipse,foreignObject,g,image,line,path,polygon,polyline,rect,svg,text,tspan,use';
  26. var visElems_arr = visElems.split(',');
  27. //var hidElems = 'clipPath,defs,desc,feGaussianBlur,filter,linearGradient,marker,mask,metadata,pattern,radialGradient,stop,switch,symbol,title,textPath';
  28. var editorContext_ = null;
  29. var domdoc_ = null;
  30. var domcontainer_ = null;
  31. var svgroot_ = null;
  32. svgedit.utilities.init = function(editorContext) {
  33. editorContext_ = editorContext;
  34. domdoc_ = editorContext.getDOMDocument();
  35. domcontainer_ = editorContext.getDOMContainer();
  36. svgroot_ = editorContext.getSVGRoot();
  37. };
  38. // Function: svgedit.utilities.toXml
  39. // Converts characters in a string to XML-friendly entities.
  40. //
  41. // Example: '&' becomes '&'
  42. //
  43. // Parameters:
  44. // str - The string to be converted
  45. //
  46. // Returns:
  47. // The converted string
  48. svgedit.utilities.toXml = function(str) {
  49. // ' is ok in XML, but not HTML
  50. // > does not normally need escaping, though it can if within a CDATA expression (and preceded by "]]")
  51. return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/, '&#x27;');
  52. };
  53. // Function: svgedit.utilities.fromXml
  54. // Converts XML entities in a string to single characters.
  55. // Example: '&amp;' becomes '&'
  56. //
  57. // Parameters:
  58. // str - The string to be converted
  59. //
  60. // Returns:
  61. // The converted string
  62. svgedit.utilities.fromXml = function(str) {
  63. return $('<p/>').html(str).text();
  64. };
  65. // This code was written by Tyler Akins and has been placed in the
  66. // public domain. It would be nice if you left this header intact.
  67. // Base64 code from Tyler Akins -- http://rumkin.com
  68. // schiller: Removed string concatenation in favour of Array.join() optimization,
  69. // also precalculate the size of the array needed.
  70. // Function: svgedit.utilities.encode64
  71. // Converts a string to base64
  72. svgedit.utilities.encode64 = function(input) {
  73. // base64 strings are 4/3 larger than the original string
  74. input = svgedit.utilities.encodeUTF8(input); // convert non-ASCII characters
  75. // input = svgedit.utilities.convertToXMLReferences(input);
  76. if (window.btoa) {
  77. return window.btoa(input); // Use native if available
  78. }
  79. var output = new Array( Math.floor( (input.length + 2) / 3 ) * 4 );
  80. var chr1, chr2, chr3;
  81. var enc1, enc2, enc3, enc4;
  82. var i = 0, p = 0;
  83. do {
  84. chr1 = input.charCodeAt(i++);
  85. chr2 = input.charCodeAt(i++);
  86. chr3 = input.charCodeAt(i++);
  87. enc1 = chr1 >> 2;
  88. enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
  89. enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
  90. enc4 = chr3 & 63;
  91. if (isNaN(chr2)) {
  92. enc3 = enc4 = 64;
  93. } else if (isNaN(chr3)) {
  94. enc4 = 64;
  95. }
  96. output[p++] = KEYSTR.charAt(enc1);
  97. output[p++] = KEYSTR.charAt(enc2);
  98. output[p++] = KEYSTR.charAt(enc3);
  99. output[p++] = KEYSTR.charAt(enc4);
  100. } while (i < input.length);
  101. return output.join('');
  102. };
  103. // Function: svgedit.utilities.decode64
  104. // Converts a string from base64
  105. svgedit.utilities.decode64 = function(input) {
  106. if(window.atob) {
  107. return window.atob(input);
  108. }
  109. var output = '';
  110. var chr1, chr2, chr3 = '';
  111. var enc1, enc2, enc3, enc4 = '';
  112. var i = 0;
  113. // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
  114. input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
  115. do {
  116. enc1 = KEYSTR.indexOf(input.charAt(i++));
  117. enc2 = KEYSTR.indexOf(input.charAt(i++));
  118. enc3 = KEYSTR.indexOf(input.charAt(i++));
  119. enc4 = KEYSTR.indexOf(input.charAt(i++));
  120. chr1 = (enc1 << 2) | (enc2 >> 4);
  121. chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
  122. chr3 = ((enc3 & 3) << 6) | enc4;
  123. output = output + String.fromCharCode(chr1);
  124. if (enc3 != 64) {
  125. output = output + String.fromCharCode(chr2);
  126. }
  127. if (enc4 != 64) {
  128. output = output + String.fromCharCode(chr3);
  129. }
  130. chr1 = chr2 = chr3 = '';
  131. enc1 = enc2 = enc3 = enc4 = '';
  132. } while (i < input.length);
  133. return unescape(output);
  134. };
  135. // based on http://phpjs.org/functions/utf8_encode
  136. // codedread:does not seem to work with webkit-based browsers on OSX // Brettz9: please test again as function upgraded
  137. svgedit.utilities.encodeUTF8 = function (argString) {
  138. //return unescape(encodeURIComponent(input)); //may or may not work
  139. if (argString === null || typeof argString === 'undefined') {
  140. return '';
  141. }
  142. // .replace(/\r\n/g, "\n").replace(/\r/g, "\n");
  143. var string = String(argString);
  144. var utftext = '',
  145. start, end, stringl = 0;
  146. start = end = 0;
  147. stringl = string.length;
  148. var n;
  149. for (n = 0; n < stringl; n++) {
  150. var c1 = string.charCodeAt(n);
  151. var enc = null;
  152. if (c1 < 128) {
  153. end++;
  154. } else if (c1 > 127 && c1 < 2048) {
  155. enc = String.fromCharCode(
  156. (c1 >> 6) | 192, (c1 & 63) | 128
  157. );
  158. } else if ((c1 & 0xF800) != 0xD800) {
  159. enc = String.fromCharCode(
  160. (c1 >> 12) | 224, ((c1 >> 6) & 63) | 128, (c1 & 63) | 128
  161. );
  162. } else {
  163. // surrogate pairs
  164. if ((c1 & 0xFC00) != 0xD800) {
  165. throw new RangeError('Unmatched trail surrogate at ' + n);
  166. }
  167. var c2 = string.charCodeAt(++n);
  168. if ((c2 & 0xFC00) != 0xDC00) {
  169. throw new RangeError('Unmatched lead surrogate at ' + (n - 1));
  170. }
  171. c1 = ((c1 & 0x3FF) << 10) + (c2 & 0x3FF) + 0x10000;
  172. enc = String.fromCharCode(
  173. (c1 >> 18) | 240, ((c1 >> 12) & 63) | 128, ((c1 >> 6) & 63) | 128, (c1 & 63) | 128
  174. );
  175. }
  176. if (enc !== null) {
  177. if (end > start) {
  178. utftext += string.slice(start, end);
  179. }
  180. utftext += enc;
  181. start = end = n + 1;
  182. }
  183. }
  184. if (end > start) {
  185. utftext += string.slice(start, stringl);
  186. }
  187. return utftext;
  188. };
  189. // Function: svgedit.utilities.convertToXMLReferences
  190. // Converts a string to use XML references
  191. svgedit.utilities.convertToXMLReferences = function(input) {
  192. var n,
  193. output = '';
  194. for (n = 0; n < input.length; n++){
  195. var c = input.charCodeAt(n);
  196. if (c < 128) {
  197. output += input[n];
  198. } else if(c > 127) {
  199. output += ('&#' + c + ';');
  200. }
  201. }
  202. return output;
  203. };
  204. // Function: svgedit.utilities.text2xml
  205. // Cross-browser compatible method of converting a string to an XML tree
  206. // found this function here: http://groups.google.com/group/jquery-dev/browse_thread/thread/c6d11387c580a77f
  207. svgedit.utilities.text2xml = function(sXML) {
  208. if(sXML.indexOf('<svg:svg') >= 0) {
  209. sXML = sXML.replace(/<(\/?)svg:/g, '<$1').replace('xmlns:svg', 'xmlns');
  210. }
  211. var out, dXML;
  212. try{
  213. dXML = (window.DOMParser)?new DOMParser():new ActiveXObject('Microsoft.XMLDOM');
  214. dXML.async = false;
  215. } catch(e){
  216. throw new Error('XML Parser could not be instantiated');
  217. }
  218. try{
  219. if (dXML.loadXML) {
  220. out = (dXML.loadXML(sXML)) ? dXML : false;
  221. }
  222. else {
  223. out = dXML.parseFromString(sXML, 'text/xml');
  224. }
  225. }
  226. catch(e2){ throw new Error('Error parsing XML string'); }
  227. return out;
  228. };
  229. // Function: svgedit.utilities.bboxToObj
  230. // Converts a SVGRect into an object.
  231. //
  232. // Parameters:
  233. // bbox - a SVGRect
  234. //
  235. // Returns:
  236. // An object with properties names x, y, width, height.
  237. svgedit.utilities.bboxToObj = function(bbox) {
  238. return {
  239. x: bbox.x,
  240. y: bbox.y,
  241. width: bbox.width,
  242. height: bbox.height
  243. };
  244. };
  245. // Function: svgedit.utilities.walkTree
  246. // Walks the tree and executes the callback on each element in a top-down fashion
  247. //
  248. // Parameters:
  249. // elem - DOM element to traverse
  250. // cbFn - Callback function to run on each element
  251. svgedit.utilities.walkTree = function(elem, cbFn){
  252. if (elem && elem.nodeType == 1) {
  253. cbFn(elem);
  254. var i = elem.childNodes.length;
  255. while (i--) {
  256. svgedit.utilities.walkTree(elem.childNodes.item(i), cbFn);
  257. }
  258. }
  259. };
  260. // Function: svgedit.utilities.walkTreePost
  261. // Walks the tree and executes the callback on each element in a depth-first fashion
  262. // TODO: FIXME: Shouldn't this be calling walkTreePost?
  263. //
  264. // Parameters:
  265. // elem - DOM element to traverse
  266. // cbFn - Callback function to run on each element
  267. svgedit.utilities.walkTreePost = function(elem, cbFn) {
  268. if (elem && elem.nodeType == 1) {
  269. var i = elem.childNodes.length;
  270. while (i--) {
  271. svgedit.utilities.walkTree(elem.childNodes.item(i), cbFn);
  272. }
  273. cbFn(elem);
  274. }
  275. };
  276. // Function: svgedit.utilities.getUrlFromAttr
  277. // Extracts the URL from the url(...) syntax of some attributes.
  278. // Three variants:
  279. // * <circle fill="url(someFile.svg#foo)" />
  280. // * <circle fill="url('someFile.svg#foo')" />
  281. // * <circle fill='url("someFile.svg#foo")' />
  282. //
  283. // Parameters:
  284. // attrVal - The attribute value as a string
  285. //
  286. // Returns:
  287. // String with just the URL, like someFile.svg#foo
  288. svgedit.utilities.getUrlFromAttr = function(attrVal) {
  289. if (attrVal) {
  290. // url("#somegrad")
  291. if (attrVal.indexOf('url("') === 0) {
  292. return attrVal.substring(5, attrVal.indexOf('"',6));
  293. }
  294. // url('#somegrad')
  295. if (attrVal.indexOf("url('") === 0) {
  296. return attrVal.substring(5, attrVal.indexOf("'",6));
  297. }
  298. if (attrVal.indexOf("url(") === 0) {
  299. return attrVal.substring(4, attrVal.indexOf(')'));
  300. }
  301. }
  302. return null;
  303. };
  304. // Function: svgedit.utilities.getHref
  305. // Returns the given element's xlink:href value
  306. svgedit.utilities.getHref = function(elem) {
  307. return elem.getAttributeNS(NS.XLINK, 'href');
  308. };
  309. // Function: svgedit.utilities.setHref
  310. // Sets the given element's xlink:href value
  311. svgedit.utilities.setHref = function(elem, val) {
  312. elem.setAttributeNS(NS.XLINK, 'xlink:href', val);
  313. };
  314. // Function: findDefs
  315. //
  316. // Returns:
  317. // The document's <defs> element, create it first if necessary
  318. svgedit.utilities.findDefs = function() {
  319. var svgElement = editorContext_.getSVGContent();
  320. var defs = svgElement.getElementsByTagNameNS(NS.SVG, 'defs');
  321. if (defs.length > 0) {
  322. defs = defs[0];
  323. } else {
  324. defs = svgElement.ownerDocument.createElementNS(NS.SVG, 'defs');
  325. if (svgElement.firstChild) {
  326. // first child is a comment, so call nextSibling
  327. svgElement.insertBefore(defs, svgElement.firstChild.nextSibling);
  328. } else {
  329. svgElement.appendChild(defs);
  330. }
  331. }
  332. return defs;
  333. };
  334. // TODO(codedread): Consider moving the next to functions to bbox.js
  335. // Function: svgedit.utilities.getPathBBox
  336. // Get correct BBox for a path in Webkit
  337. // Converted from code found here:
  338. // http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
  339. //
  340. // Parameters:
  341. // path - The path DOM element to get the BBox for
  342. //
  343. // Returns:
  344. // A BBox-like object
  345. svgedit.utilities.getPathBBox = function(path) {
  346. var seglist = path.pathSegList;
  347. var tot = seglist.numberOfItems;
  348. var bounds = [[], []];
  349. var start = seglist.getItem(0);
  350. var P0 = [start.x, start.y];
  351. var i;
  352. for (i = 0; i < tot; i++) {
  353. var seg = seglist.getItem(i);
  354. if(typeof seg.x === 'undefined') {continue;}
  355. // Add actual points to limits
  356. bounds[0].push(P0[0]);
  357. bounds[1].push(P0[1]);
  358. if(seg.x1) {
  359. var P1 = [seg.x1, seg.y1],
  360. P2 = [seg.x2, seg.y2],
  361. P3 = [seg.x, seg.y];
  362. var j;
  363. for (j = 0; j < 2; j++) {
  364. var calc = function(t) {
  365. return Math.pow(1-t,3) * P0[j]
  366. + 3 * Math.pow(1-t,2) * t * P1[j]
  367. + 3 * (1-t) * Math.pow(t, 2) * P2[j]
  368. + Math.pow(t,3) * P3[j];
  369. };
  370. var b = 6 * P0[j] - 12 * P1[j] + 6 * P2[j];
  371. var a = -3 * P0[j] + 9 * P1[j] - 9 * P2[j] + 3 * P3[j];
  372. var c = 3 * P1[j] - 3 * P0[j];
  373. if (a == 0) {
  374. if (b == 0) {
  375. continue;
  376. }
  377. var t = -c / b;
  378. if (0 < t && t < 1) {
  379. bounds[j].push(calc(t));
  380. }
  381. continue;
  382. }
  383. var b2ac = Math.pow(b,2) - 4 * c * a;
  384. if (b2ac < 0) {continue;}
  385. var t1 = (-b + Math.sqrt(b2ac))/(2 * a);
  386. if (0 < t1 && t1 < 1) {bounds[j].push(calc(t1));}
  387. var t2 = (-b - Math.sqrt(b2ac))/(2 * a);
  388. if (0 < t2 && t2 < 1) {bounds[j].push(calc(t2));}
  389. }
  390. P0 = P3;
  391. } else {
  392. bounds[0].push(seg.x);
  393. bounds[1].push(seg.y);
  394. }
  395. }
  396. var x = Math.min.apply(null, bounds[0]);
  397. var w = Math.max.apply(null, bounds[0]) - x;
  398. var y = Math.min.apply(null, bounds[1]);
  399. var h = Math.max.apply(null, bounds[1]) - y;
  400. return {
  401. 'x': x,
  402. 'y': y,
  403. 'width': w,
  404. 'height': h
  405. };
  406. };
  407. // Function: groupBBFix
  408. // Get the given/selected element's bounding box object, checking for
  409. // horizontal/vertical lines (see issue 717)
  410. // Note that performance is currently terrible, so some way to improve would
  411. // be great.
  412. //
  413. // Parameters:
  414. // selected - Container or <use> DOM element
  415. function groupBBFix(selected) {
  416. if(svgedit.browser.supportsHVLineContainerBBox()) {
  417. try { return selected.getBBox();} catch(e){}
  418. }
  419. var ref = $.data(selected, 'ref');
  420. var matched = null;
  421. var ret, copy;
  422. if(ref) {
  423. copy = $(ref).children().clone().attr('visibility', 'hidden');
  424. $(svgroot_).append(copy);
  425. matched = copy.filter('line, path');
  426. } else {
  427. matched = $(selected).find('line, path');
  428. }
  429. var issue = false;
  430. if(matched.length) {
  431. matched.each(function() {
  432. var bb = this.getBBox();
  433. if(!bb.width || !bb.height) {
  434. issue = true;
  435. }
  436. });
  437. if(issue) {
  438. var elems = ref ? copy : $(selected).children();
  439. ret = getStrokedBBox(elems); // getStrokedBBox defined in svgcanvas
  440. } else {
  441. ret = selected.getBBox();
  442. }
  443. } else {
  444. ret = selected.getBBox();
  445. }
  446. if(ref) {
  447. copy.remove();
  448. }
  449. return ret;
  450. }
  451. // Function: svgedit.utilities.getBBox
  452. // Get the given/selected element's bounding box object, convert it to be more
  453. // usable when necessary
  454. //
  455. // Parameters:
  456. // elem - Optional DOM element to get the BBox for
  457. svgedit.utilities.getBBox = function(elem) {
  458. var selected = elem || editorContext_.geSelectedElements()[0];
  459. if (elem.nodeType != 1) {return null;}
  460. var ret = null;
  461. var elname = selected.nodeName;
  462. switch ( elname ) {
  463. case 'text':
  464. if(selected.textContent === '') {
  465. selected.textContent = 'a'; // Some character needed for the selector to use.
  466. ret = selected.getBBox();
  467. selected.textContent = '';
  468. } else {
  469. try { ret = selected.getBBox();} catch(e){}
  470. }
  471. break;
  472. case 'path':
  473. if(!svgedit.browser.supportsPathBBox()) {
  474. ret = svgedit.utilities.getPathBBox(selected);
  475. } else {
  476. try { ret = selected.getBBox();} catch(e2){}
  477. }
  478. break;
  479. case 'g':
  480. case 'a':
  481. ret = groupBBFix(selected);
  482. break;
  483. default:
  484. if(elname === 'use') {
  485. ret = groupBBFix(selected, true);
  486. }
  487. if(elname === 'use' || ( elname === 'foreignObject' && svgedit.browser.isWebkit() ) ) {
  488. if(!ret) {ret = selected.getBBox();}
  489. // This is resolved in later versions of webkit, perhaps we should
  490. // have a featured detection for correct 'use' behavior?
  491. // ——————————
  492. //if(!svgedit.browser.isWebkit()) {
  493. var bb = {};
  494. bb.width = ret.width;
  495. bb.height = ret.height;
  496. bb.x = ret.x + parseFloat(selected.getAttribute('x')||0);
  497. bb.y = ret.y + parseFloat(selected.getAttribute('y')||0);
  498. ret = bb;
  499. //}
  500. } else if(~visElems_arr.indexOf(elname)) {
  501. try { ret = selected.getBBox();}
  502. catch(e3) {
  503. // Check if element is child of a foreignObject
  504. var fo = $(selected).closest('foreignObject');
  505. if(fo.length) {
  506. try {
  507. ret = fo[0].getBBox();
  508. } catch(e4) {
  509. ret = null;
  510. }
  511. } else {
  512. ret = null;
  513. }
  514. }
  515. }
  516. }
  517. if(ret) {
  518. ret = svgedit.utilities.bboxToObj(ret);
  519. }
  520. // get the bounding box from the DOM (which is in that element's coordinate system)
  521. return ret;
  522. };
  523. // Function: svgedit.utilities.getRotationAngle
  524. // Get the rotation angle of the given/selected DOM element
  525. //
  526. // Parameters:
  527. // elem - Optional DOM element to get the angle for
  528. // to_rad - Boolean that when true returns the value in radians rather than degrees
  529. //
  530. // Returns:
  531. // Float with the angle in degrees or radians
  532. svgedit.utilities.getRotationAngle = function(elem, to_rad) {
  533. var selected = elem || editorContext_.getSelectedElements()[0];
  534. // find the rotation transform (if any) and set it
  535. var tlist = svgedit.transformlist.getTransformList(selected);
  536. if(!tlist) {return 0;} // <svg> elements have no tlist
  537. var N = tlist.numberOfItems;
  538. var i;
  539. for (i = 0; i < N; ++i) {
  540. var xform = tlist.getItem(i);
  541. if (xform.type == 4) {
  542. return to_rad ? xform.angle * Math.PI / 180.0 : xform.angle;
  543. }
  544. }
  545. return 0.0;
  546. };
  547. // Function getRefElem
  548. // Get the reference element associated with the given attribute value
  549. //
  550. // Parameters:
  551. // attrVal - The attribute value as a string
  552. svgedit.utilities.getRefElem = function(attrVal) {
  553. return svgedit.utilities.getElem(svgedit.utilities.getUrlFromAttr(attrVal).substr(1));
  554. };
  555. // Function: getElem
  556. // Get a DOM element by ID within the SVG root element.
  557. //
  558. // Parameters:
  559. // id - String with the element's new ID
  560. if (svgedit.browser.supportsSelectors()) {
  561. svgedit.utilities.getElem = function(id) {
  562. // querySelector lookup
  563. return svgroot_.querySelector('#'+id);
  564. };
  565. } else if (svgedit.browser.supportsXpath()) {
  566. svgedit.utilities.getElem = function(id) {
  567. // xpath lookup
  568. return domdoc_.evaluate(
  569. 'svg:svg[@id="svgroot"]//svg:*[@id="'+id+'"]',
  570. domcontainer_,
  571. function() { return svgedit.NS.SVG; },
  572. 9,
  573. null).singleNodeValue;
  574. };
  575. } else {
  576. svgedit.utilities.getElem = function(id) {
  577. // jQuery lookup: twice as slow as xpath in FF
  578. return $(svgroot_).find('[id=' + id + ']')[0];
  579. };
  580. }
  581. // Function: assignAttributes
  582. // Assigns multiple attributes to an element.
  583. //
  584. // Parameters:
  585. // node - DOM element to apply new attribute values to
  586. // attrs - Object with attribute keys/values
  587. // suspendLength - Optional integer of milliseconds to suspend redraw
  588. // unitCheck - Boolean to indicate the need to use svgedit.units.setUnitAttr
  589. svgedit.utilities.assignAttributes = function(node, attrs, suspendLength, unitCheck) {
  590. if(!suspendLength) {suspendLength = 0;}
  591. // Opera has a problem with suspendRedraw() apparently
  592. var handle = null;
  593. if (!svgedit.browser.isOpera()) {svgroot_.suspendRedraw(suspendLength);}
  594. var i;
  595. for (i in attrs) {
  596. var ns = (i.substr(0,4) === 'xml:' ? NS.XML :
  597. i.substr(0,6) === 'xlink:' ? NS.XLINK : null);
  598. if(ns) {
  599. node.setAttributeNS(ns, i, attrs[i]);
  600. } else if(!unitCheck) {
  601. node.setAttribute(i, attrs[i]);
  602. } else {
  603. svgedit.units.setUnitAttr(node, i, attrs[i]);
  604. }
  605. }
  606. if (!svgedit.browser.isOpera()) {svgroot_.unsuspendRedraw(handle);}
  607. };
  608. // Function: cleanupElement
  609. // Remove unneeded (default) attributes, makes resulting SVG smaller
  610. //
  611. // Parameters:
  612. // element - DOM element to clean up
  613. svgedit.utilities.cleanupElement = function(element) {
  614. var handle = svgroot_.suspendRedraw(60);
  615. var defaults = {
  616. 'fill-opacity':1,
  617. 'stop-opacity':1,
  618. 'opacity':1,
  619. 'stroke':'none',
  620. 'stroke-dasharray':'none',
  621. 'stroke-linejoin':'miter',
  622. 'stroke-linecap':'butt',
  623. 'stroke-opacity':1,
  624. 'stroke-width':1,
  625. 'rx':0,
  626. 'ry':0
  627. };
  628. var attr;
  629. for (attr in defaults) {
  630. var val = defaults[attr];
  631. if(element.getAttribute(attr) == val) {
  632. element.removeAttribute(attr);
  633. }
  634. }
  635. svgroot_.unsuspendRedraw(handle);
  636. };
  637. // Function: snapToGrid
  638. // round value to for snapping
  639. // NOTE: This function did not move to svgutils.js since it depends on curConfig.
  640. svgedit.utilities.snapToGrid = function(value) {
  641. var stepSize = editorContext_.getSnappingStep();
  642. var unit = editorContext_.getBaseUnit();
  643. if (unit !== "px") {
  644. stepSize *= svgedit.units.getTypeMap()[unit];
  645. }
  646. value = Math.round(value/stepSize)*stepSize;
  647. return value;
  648. };
  649. svgedit.utilities.preg_quote = function (str, delimiter) {
  650. // From: http://phpjs.org/functions
  651. return String(str).replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\' + (delimiter || '') + '-]', 'g'), '\\$&');
  652. };
  653. }());