source: other-projects/nz-flag-design/trunk/design-2d/Original editor.method.ac/editor/src/svgutils.js@ 29468

Last change on this file since 29468 was 29468, checked in by sjs49, 9 years ago

Initial commit for editor.method.ac for flag design

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