Sfoglia il codice sorgente

Add svg edit 2.8.1 as is.

Julio 8 anni fa
parent
commit
34e9b7442d
100 ha cambiato i file con 12442 aggiunte e 0 eliminazioni
  1. 31 0
      main/inc/lib/javascript/svgedit/browser-not-supported.html
  2. 180 0
      main/inc/lib/javascript/svgedit/browser.js
  3. 2921 0
      main/inc/lib/javascript/svgedit/canvg/canvg.js
  4. 294 0
      main/inc/lib/javascript/svgedit/canvg/rgbcolor.js
  5. 66 0
      main/inc/lib/javascript/svgedit/contextmenu.js
  6. 203 0
      main/inc/lib/javascript/svgedit/contextmenu/jquery.contextMenu.js
  7. 317 0
      main/inc/lib/javascript/svgedit/coords.js
  8. 537 0
      main/inc/lib/javascript/svgedit/draw.js
  9. 79 0
      main/inc/lib/javascript/svgedit/embedapi-dom.js
  10. 17 0
      main/inc/lib/javascript/svgedit/embedapi.html
  11. 122 0
      main/inc/lib/javascript/svgedit/embedapi.js
  12. 12 0
      main/inc/lib/javascript/svgedit/extensions/allowedMimeTypes.php
  13. 41 0
      main/inc/lib/javascript/svgedit/extensions/closepath_icons.svg
  14. 293 0
      main/inc/lib/javascript/svgedit/extensions/ext-arrows.js
  15. 91 0
      main/inc/lib/javascript/svgedit/extensions/ext-closepath.js
  16. 593 0
      main/inc/lib/javascript/svgedit/extensions/ext-connector.js
  17. 112 0
      main/inc/lib/javascript/svgedit/extensions/ext-eyedropper.js
  18. 268 0
      main/inc/lib/javascript/svgedit/extensions/ext-foreignobject.js
  19. 162 0
      main/inc/lib/javascript/svgedit/extensions/ext-grid.js
  20. 80 0
      main/inc/lib/javascript/svgedit/extensions/ext-helloworld.js
  21. 464 0
      main/inc/lib/javascript/svgedit/extensions/ext-imagelib.js
  22. 14 0
      main/inc/lib/javascript/svgedit/extensions/ext-imagelib.xml
  23. 579 0
      main/inc/lib/javascript/svgedit/extensions/ext-markers.js
  24. 273 0
      main/inc/lib/javascript/svgedit/extensions/ext-mathjax.js
  25. 142 0
      main/inc/lib/javascript/svgedit/extensions/ext-overview_window.js
  26. 46 0
      main/inc/lib/javascript/svgedit/extensions/ext-panning.js
  27. 7 0
      main/inc/lib/javascript/svgedit/extensions/ext-panning.xml
  28. 23 0
      main/inc/lib/javascript/svgedit/extensions/ext-php_savefile.js
  29. 294 0
      main/inc/lib/javascript/svgedit/extensions/ext-polygon.js
  30. 59 0
      main/inc/lib/javascript/svgedit/extensions/ext-server_moinsave.js
  31. 228 0
      main/inc/lib/javascript/svgedit/extensions/ext-server_opensave.js
  32. 357 0
      main/inc/lib/javascript/svgedit/extensions/ext-shapes.js
  33. 10 0
      main/inc/lib/javascript/svgedit/extensions/ext-shapes.xml
  34. 236 0
      main/inc/lib/javascript/svgedit/extensions/ext-star.js
  35. 293 0
      main/inc/lib/javascript/svgedit/extensions/ext-storage.js
  36. 67 0
      main/inc/lib/javascript/svgedit/extensions/ext-webappfind.js
  37. 42 0
      main/inc/lib/javascript/svgedit/extensions/ext-xdomain-messaging.js
  38. 34 0
      main/inc/lib/javascript/svgedit/extensions/eyedropper-icon.xml
  39. BIN
      main/inc/lib/javascript/svgedit/extensions/eyedropper.png
  40. 56 0
      main/inc/lib/javascript/svgedit/extensions/fileopen.php
  41. 60 0
      main/inc/lib/javascript/svgedit/extensions/filesave.php
  42. 96 0
      main/inc/lib/javascript/svgedit/extensions/foreignobject-icons.xml
  43. 30 0
      main/inc/lib/javascript/svgedit/extensions/grid-icon.xml
  44. 21 0
      main/inc/lib/javascript/svgedit/extensions/helloworld-icon.xml
  45. 70 0
      main/inc/lib/javascript/svgedit/extensions/imagelib/index.html
  46. 12 0
      main/inc/lib/javascript/svgedit/extensions/imagelib/smiley.svg
  47. 115 0
      main/inc/lib/javascript/svgedit/extensions/markers-icons.xml
  48. 11 0
      main/inc/lib/javascript/svgedit/extensions/mathjax-icons.xml
  49. 14 0
      main/inc/lib/javascript/svgedit/extensions/polygon-icons.svg
  50. 16 0
      main/inc/lib/javascript/svgedit/extensions/savefile.php
  51. 2 0
      main/inc/lib/javascript/svgedit/extensions/shapelib/animal.json
  52. 15 0
      main/inc/lib/javascript/svgedit/extensions/shapelib/arrow.json
  53. 9 0
      main/inc/lib/javascript/svgedit/extensions/shapelib/dialog_balloon.json
  54. 20 0
      main/inc/lib/javascript/svgedit/extensions/shapelib/electronics.json
  55. 25 0
      main/inc/lib/javascript/svgedit/extensions/shapelib/flowchart.json
  56. 13 0
      main/inc/lib/javascript/svgedit/extensions/shapelib/game.json
  57. 9 0
      main/inc/lib/javascript/svgedit/extensions/shapelib/math.json
  58. 4 0
      main/inc/lib/javascript/svgedit/extensions/shapelib/misc.json
  59. 10 0
      main/inc/lib/javascript/svgedit/extensions/shapelib/music.json
  60. 1 0
      main/inc/lib/javascript/svgedit/extensions/shapelib/object.json
  61. 12 0
      main/inc/lib/javascript/svgedit/extensions/shapelib/raphael.txt
  62. 8 0
      main/inc/lib/javascript/svgedit/extensions/shapelib/raphael_1.json
  63. 4 0
      main/inc/lib/javascript/svgedit/extensions/shapelib/raphael_2.json
  64. 2 0
      main/inc/lib/javascript/svgedit/extensions/shapelib/symbol.json
  65. 14 0
      main/inc/lib/javascript/svgedit/extensions/star-icons.svg
  66. 23 0
      main/inc/lib/javascript/svgedit/extensions/webappfind-icon.svg
  67. 613 0
      main/inc/lib/javascript/svgedit/history.js
  68. 61 0
      main/inc/lib/javascript/svgedit/images/README.txt
  69. BIN
      main/inc/lib/javascript/svgedit/images/align-bottom.png
  70. 277 0
      main/inc/lib/javascript/svgedit/images/align-bottom.svg
  71. BIN
      main/inc/lib/javascript/svgedit/images/align-center.png
  72. 252 0
      main/inc/lib/javascript/svgedit/images/align-center.svg
  73. BIN
      main/inc/lib/javascript/svgedit/images/align-left.png
  74. 235 0
      main/inc/lib/javascript/svgedit/images/align-left.svg
  75. BIN
      main/inc/lib/javascript/svgedit/images/align-middle.png
  76. 250 0
      main/inc/lib/javascript/svgedit/images/align-middle.svg
  77. BIN
      main/inc/lib/javascript/svgedit/images/align-right.png
  78. 233 0
      main/inc/lib/javascript/svgedit/images/align-right.svg
  79. BIN
      main/inc/lib/javascript/svgedit/images/align-top.png
  80. 233 0
      main/inc/lib/javascript/svgedit/images/align-top.svg
  81. BIN
      main/inc/lib/javascript/svgedit/images/bold.png
  82. BIN
      main/inc/lib/javascript/svgedit/images/cancel.png
  83. BIN
      main/inc/lib/javascript/svgedit/images/circle.png
  84. BIN
      main/inc/lib/javascript/svgedit/images/clear.png
  85. BIN
      main/inc/lib/javascript/svgedit/images/clone.png
  86. 29 0
      main/inc/lib/javascript/svgedit/images/conn.svg
  87. BIN
      main/inc/lib/javascript/svgedit/images/copy.png
  88. BIN
      main/inc/lib/javascript/svgedit/images/cut.png
  89. BIN
      main/inc/lib/javascript/svgedit/images/delete.png
  90. BIN
      main/inc/lib/javascript/svgedit/images/document-properties.png
  91. BIN
      main/inc/lib/javascript/svgedit/images/dropdown.gif
  92. BIN
      main/inc/lib/javascript/svgedit/images/ellipse.png
  93. BIN
      main/inc/lib/javascript/svgedit/images/eye.png
  94. BIN
      main/inc/lib/javascript/svgedit/images/fhpath.png
  95. BIN
      main/inc/lib/javascript/svgedit/images/flyouth.png
  96. BIN
      main/inc/lib/javascript/svgedit/images/flyup.gif
  97. BIN
      main/inc/lib/javascript/svgedit/images/freehand-circle.png
  98. BIN
      main/inc/lib/javascript/svgedit/images/freehand-square.png
  99. BIN
      main/inc/lib/javascript/svgedit/images/go-down.png
  100. BIN
      main/inc/lib/javascript/svgedit/images/go-up.png

+ 31 - 0
main/inc/lib/javascript/svgedit/browser-not-supported.html

@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8" />
+<meta http-equiv="X-UA-Compatible" content="chrome=1"/>
+<link rel="icon" type="image/png" href="images/logo.png"/>
+<link rel="stylesheet" href="svg-editor.css" type="text/css"/>
+<script src="jquery.js"></script>
+<title>Browser does not support SVG | SVG-edit</title>
+</head>
+<body>
+
+<div id="browser-not-supported">
+<img style="float:left;padding:10px;" src="images/logo.png" width="48" height="48" alt="SVG-edit logo" /><br />
+<p>Sorry, but your browser does not support SVG. Below is a list of alternate browsers and versions that support SVG and SVG-edit (from <a href="http://caniuse.com/#cats=SVG">caniuse.com</a>).</p>
+<p>Try the latest version of <a href="http://www.getfirefox.com">Firefox</a>, <a href="http://www.google.com/chrome/">Google Chrome</a>, <a href="http://www.apple.com/safari/download/">Safari</a>, <a href="http://www.opera.com/download/">Opera</a> or <a href="http://windows.microsoft.com/ie9">Internet Explorer</a>.</p>
+<p>If you are unable to install one of these and must use an old version of Internet Explorer, you can install the <a href="http://code.google.com/chrome/chromeframe/">Google Chrome Frame plugin</a>.</p>
+
+</div>
+
+<script>
+/*globals $*/
+var viewportHeight = window.innerHeight || ($(window).height() - 140);
+var iframe = document.createElement('iframe');
+iframe.style.width = '100%';
+iframe.style.height = viewportHeight + 'px';
+iframe.src = 'http://caniuse.com/#cats=SVG';
+document.body.appendChild(iframe);
+</script>
+</body>
+</html>

+ 180 - 0
main/inc/lib/javascript/svgedit/browser.js

@@ -0,0 +1,180 @@
+/*globals $, svgedit*/
+/*jslint vars: true, eqeq: true*/
+/**
+ * Package: svgedit.browser
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2010 Jeff Schiller
+ * Copyright(c) 2010 Alexis Deveria
+ */
+
+// Dependencies:
+// 1) jQuery (for $.alert())
+
+(function() {'use strict';
+
+if (!svgedit.browser) {
+	svgedit.browser = {};
+}
+
+// alias
+var NS = svgedit.NS;
+
+var supportsSvg_ = (function() {
+	return !!document.createElementNS && !!document.createElementNS(NS.SVG, 'svg').createSVGRect;
+}());
+
+svgedit.browser.supportsSvg = function() { return supportsSvg_; };
+if(!svgedit.browser.supportsSvg()) {
+	window.location = 'browser-not-supported.html';
+	return;
+}
+
+var userAgent = navigator.userAgent;
+var svg = document.createElementNS(NS.SVG, 'svg');
+
+// Note: Browser sniffing should only be used if no other detection method is possible
+var isOpera_ = !!window.opera;
+var isWebkit_ = userAgent.indexOf('AppleWebKit') >= 0;
+var isGecko_ = userAgent.indexOf('Gecko/') >= 0;
+var isIE_ = userAgent.indexOf('MSIE') >= 0;
+var isChrome_ = userAgent.indexOf('Chrome/') >= 0;
+var isWindows_ = userAgent.indexOf('Windows') >= 0;
+var isMac_ = userAgent.indexOf('Macintosh') >= 0;
+var isTouch_ = 'ontouchstart' in window;
+
+var supportsSelectors_ = (function() {
+	return !!svg.querySelector;
+}());
+
+var supportsXpath_ = (function() {
+	return !!document.evaluate;
+}());
+
+// segList functions (for FF1.5 and 2.0)
+var supportsPathReplaceItem_ = (function() {
+	var path = document.createElementNS(NS.SVG, 'path');
+	path.setAttribute('d', 'M0,0 10,10');
+	var seglist = path.pathSegList;
+	var seg = path.createSVGPathSegLinetoAbs(5,5);
+	try {
+		seglist.replaceItem(seg, 0);
+		return true;
+	} catch(err) {}
+	return false;
+}());
+
+var supportsPathInsertItemBefore_ = (function() {
+	var path = document.createElementNS(NS.SVG, 'path');
+	path.setAttribute('d', 'M0,0 10,10');
+	var seglist = path.pathSegList;
+	var seg = path.createSVGPathSegLinetoAbs(5,5);
+	try {
+		seglist.insertItemBefore(seg, 0);
+		return true;
+	} catch(err) {}
+	return false;
+}());
+
+// text character positioning (for IE9)
+var supportsGoodTextCharPos_ = (function() {
+	var svgroot = document.createElementNS(NS.SVG, 'svg');
+	var svgcontent = document.createElementNS(NS.SVG, 'svg');
+	document.documentElement.appendChild(svgroot);
+	svgcontent.setAttribute('x', 5);
+	svgroot.appendChild(svgcontent);
+	var text = document.createElementNS(NS.SVG, 'text');
+	text.textContent = 'a';
+	svgcontent.appendChild(text);
+	var pos = text.getStartPositionOfChar(0).x;
+	document.documentElement.removeChild(svgroot);
+	return (pos === 0);
+}());
+
+var supportsPathBBox_ = (function() {
+	var svgcontent = document.createElementNS(NS.SVG, 'svg');
+	document.documentElement.appendChild(svgcontent);
+	var path = document.createElementNS(NS.SVG, 'path');
+	path.setAttribute('d', 'M0,0 C0,0 10,10 10,0');
+	svgcontent.appendChild(path);
+	var bbox = path.getBBox();
+	document.documentElement.removeChild(svgcontent);
+	return (bbox.height > 4 && bbox.height < 5);
+}());
+
+// Support for correct bbox sizing on groups with horizontal/vertical lines
+var supportsHVLineContainerBBox_ = (function() {
+	var svgcontent = document.createElementNS(NS.SVG, 'svg');
+	document.documentElement.appendChild(svgcontent);
+	var path = document.createElementNS(NS.SVG, 'path');
+	path.setAttribute('d', 'M0,0 10,0');
+	var path2 = document.createElementNS(NS.SVG, 'path');
+	path2.setAttribute('d', 'M5,0 15,0');
+	var g = document.createElementNS(NS.SVG, 'g');
+	g.appendChild(path);
+	g.appendChild(path2);
+	svgcontent.appendChild(g);
+	var bbox = g.getBBox();
+	document.documentElement.removeChild(svgcontent);
+	// Webkit gives 0, FF gives 10, Opera (correctly) gives 15
+	return (bbox.width == 15);
+}());
+
+var supportsEditableText_ = (function() {
+	// TODO: Find better way to check support for this
+	return isOpera_;
+}());
+
+var supportsGoodDecimals_ = (function() {
+	// Correct decimals on clone attributes (Opera < 10.5/win/non-en)
+	var rect = document.createElementNS(NS.SVG, 'rect');
+	rect.setAttribute('x', 0.1);
+	var crect = rect.cloneNode(false);
+	var retValue = (crect.getAttribute('x').indexOf(',') == -1);
+	if(!retValue) {
+		$.alert('NOTE: This version of Opera is known to contain bugs in SVG-edit.\n'+
+		'Please upgrade to the <a href="http://opera.com">latest version</a> in which the problems have been fixed.');
+	}
+	return retValue;
+}());
+
+var supportsNonScalingStroke_ = (function() {
+	var rect = document.createElementNS(NS.SVG, 'rect');
+	rect.setAttribute('style', 'vector-effect:non-scaling-stroke');
+	return rect.style.vectorEffect === 'non-scaling-stroke';
+}());
+
+var supportsNativeSVGTransformLists_ = (function() {
+	var rect = document.createElementNS(NS.SVG, 'rect');
+	var rxform = rect.transform.baseVal;
+	var t1 = svg.createSVGTransform();
+	rxform.appendItem(t1);
+	return rxform.getItem(0) == t1;
+}());
+
+// Public API
+
+svgedit.browser.isOpera = function() { return isOpera_; };
+svgedit.browser.isWebkit = function() { return isWebkit_; };
+svgedit.browser.isGecko = function() { return isGecko_; };
+svgedit.browser.isIE = function() { return isIE_; };
+svgedit.browser.isChrome = function() { return isChrome_; };
+svgedit.browser.isWindows = function() { return isWindows_; };
+svgedit.browser.isMac = function() { return isMac_; };
+svgedit.browser.isTouch = function() { return isTouch_; };
+
+svgedit.browser.supportsSelectors = function() { return supportsSelectors_; };
+svgedit.browser.supportsXpath = function() { return supportsXpath_; };
+
+svgedit.browser.supportsPathReplaceItem = function() { return supportsPathReplaceItem_; };
+svgedit.browser.supportsPathInsertItemBefore = function() { return supportsPathInsertItemBefore_; };
+svgedit.browser.supportsPathBBox = function() { return supportsPathBBox_; };
+svgedit.browser.supportsHVLineContainerBBox = function() { return supportsHVLineContainerBBox_; };
+svgedit.browser.supportsGoodTextCharPos = function() { return supportsGoodTextCharPos_; };
+svgedit.browser.supportsEditableText = function() { return supportsEditableText_; };
+svgedit.browser.supportsGoodDecimals = function() { return supportsGoodDecimals_; };
+svgedit.browser.supportsNonScalingStroke = function() { return supportsNonScalingStroke_; };
+svgedit.browser.supportsNativeTransformLists = function() { return supportsNativeSVGTransformLists_; };
+
+}());

+ 2921 - 0
main/inc/lib/javascript/svgedit/canvg/canvg.js

@@ -0,0 +1,2921 @@
+/*
+ * canvg.js - Javascript SVG parser and renderer on Canvas
+ * MIT Licensed 
+ * Gabe Lerner (gabelerner@gmail.com)
+ * http://code.google.com/p/canvg/
+ *
+ * Requires: rgbcolor.js - http://www.phpied.com/rgb-color-parser-in-javascript/
+ */
+(function(){
+	// canvg(target, s)
+	// empty parameters: replace all 'svg' elements on page with 'canvas' elements
+	// target: canvas element or the id of a canvas element
+	// s: svg string, url to svg file, or xml document
+	// opts: optional hash of options
+	//		 ignoreMouse: true => ignore mouse events
+	//		 ignoreAnimation: true => ignore animations
+	//		 ignoreDimensions: true => does not try to resize canvas
+	//		 ignoreClear: true => does not clear canvas
+	//		 offsetX: int => draws at a x offset
+	//		 offsetY: int => draws at a y offset
+	//		 scaleWidth: int => scales horizontally to width
+	//		 scaleHeight: int => scales vertically to height
+	//		 renderCallback: function => will call the function after the first render is completed
+	//		 forceRedraw: function => will call the function on every frame, if it returns true, will redraw
+	this.canvg = function (target, s, opts) {
+		// no parameters
+		if (target == null && s == null && opts == null) {
+			var svgTags = document.querySelectorAll('svg');
+			for (var i=0; i<svgTags.length; i++) {
+				var svgTag = svgTags[i];
+				var c = document.createElement('canvas');
+				c.width = svgTag.clientWidth;
+				c.height = svgTag.clientHeight;
+				svgTag.parentNode.insertBefore(c, svgTag);
+				svgTag.parentNode.removeChild(svgTag);
+				var div = document.createElement('div');
+				div.appendChild(svgTag);
+				canvg(c, div.innerHTML);
+			}
+			return;
+		}
+	
+		if (typeof target == 'string') {
+			target = document.getElementById(target);
+		}
+		
+		// store class on canvas
+		if (target.svg != null) target.svg.stop();
+		var svg = build(opts || {});
+		// on i.e. 8 for flash canvas, we can't assign the property so check for it
+		if (!(target.childNodes.length == 1 && target.childNodes[0].nodeName == 'OBJECT')) target.svg = svg;
+		
+		var ctx = target.getContext('2d');
+		if (typeof(s.documentElement) != 'undefined') {
+			// load from xml doc
+			svg.loadXmlDoc(ctx, s);
+		}
+		else if (s.substr(0,1) == '<') {
+			// load from xml string
+			svg.loadXml(ctx, s);
+		}
+		else {
+			// load from url
+			svg.load(ctx, s);
+		}
+	}
+
+	function build(opts) {
+		var svg = { opts: opts };
+		
+		svg.FRAMERATE = 30;
+		svg.MAX_VIRTUAL_PIXELS = 30000;
+		
+		svg.log = function(msg) {};
+		if (svg.opts['log'] == true && typeof(console) != 'undefined') {
+			svg.log = function(msg) { console.log(msg); };
+		};
+		
+		// globals
+		svg.init = function(ctx) {
+			var uniqueId = 0;
+			svg.UniqueId = function () { uniqueId++; return 'canvg' + uniqueId;	};
+			svg.Definitions = {};
+			svg.Styles = {};
+			svg.Animations = [];
+			svg.Images = [];
+			svg.ctx = ctx;
+			svg.ViewPort = new (function () {
+				this.viewPorts = [];
+				this.Clear = function() { this.viewPorts = []; }
+				this.SetCurrent = function(width, height) { this.viewPorts.push({ width: width, height: height }); }
+				this.RemoveCurrent = function() { this.viewPorts.pop(); }
+				this.Current = function() { return this.viewPorts[this.viewPorts.length - 1]; }
+				this.width = function() { return this.Current().width; }
+				this.height = function() { return this.Current().height; }
+				this.ComputeSize = function(d) {
+					if (d != null && typeof(d) == 'number') return d;
+					if (d == 'x') return this.width();
+					if (d == 'y') return this.height();
+					return Math.sqrt(Math.pow(this.width(), 2) + Math.pow(this.height(), 2)) / Math.sqrt(2);			
+				}
+			});
+		}
+		svg.init();
+		
+		// images loaded
+		svg.ImagesLoaded = function() { 
+			for (var i=0; i<svg.Images.length; i++) {
+				if (!svg.Images[i].loaded) return false;
+			}
+			return true;
+		}
+
+		// trim
+		svg.trim = function(s) { return s.replace(/^\s+|\s+$/g, ''); }
+		
+		// compress spaces
+		svg.compressSpaces = function(s) { return s.replace(/[\s\r\t\n]+/gm,' '); }
+		
+		// ajax
+		svg.ajax = function(url) {
+			var AJAX;
+			if(window.XMLHttpRequest){AJAX=new XMLHttpRequest();}
+			else{AJAX=new ActiveXObject('Microsoft.XMLHTTP');}
+			if(AJAX){
+			   AJAX.open('GET',url,false);
+			   AJAX.send(null);
+			   return AJAX.responseText;
+			}
+			return null;
+		} 
+		
+		// parse xml
+		svg.parseXml = function(xml) {
+			if (window.DOMParser)
+			{
+				var parser = new DOMParser();
+				return parser.parseFromString(xml, 'text/xml');
+			}
+			else 
+			{
+				xml = xml.replace(/<!DOCTYPE svg[^>]*>/, '');
+				var xmlDoc = new ActiveXObject('Microsoft.XMLDOM');
+				xmlDoc.async = 'false';
+				xmlDoc.loadXML(xml); 
+				return xmlDoc;
+			}		
+		}
+		
+		svg.Property = function(name, value) {
+			this.name = name;
+			this.value = value;
+		}	
+			svg.Property.prototype.getValue = function() {
+				return this.value;
+			}
+		
+			svg.Property.prototype.hasValue = function() {
+				return (this.value != null && this.value !== '');
+			}
+							
+			// return the numerical value of the property
+			svg.Property.prototype.numValue = function() {
+				if (!this.hasValue()) return 0;
+				
+				var n = parseFloat(this.value);
+				if ((this.value + '').match(/%$/)) {
+					n = n / 100.0;
+				}
+				return n;
+			}
+			
+			svg.Property.prototype.valueOrDefault = function(def) {
+				if (this.hasValue()) return this.value;
+				return def;
+			}
+			
+			svg.Property.prototype.numValueOrDefault = function(def) {
+				if (this.hasValue()) return this.numValue();
+				return def;
+			}
+			
+			// color extensions
+				// augment the current color value with the opacity
+				svg.Property.prototype.addOpacity = function(opacityProp) {
+					var newValue = this.value;
+					if (opacityProp.value != null && opacityProp.value != '' && typeof(this.value)=='string') { // can only add opacity to colors, not patterns
+						var color = new RGBColor(this.value);
+						if (color.ok) {
+							newValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacityProp.numValue() + ')';
+						}
+					}
+					return new svg.Property(this.name, newValue);
+				}
+			
+			// definition extensions
+				// get the definition from the definitions table
+				svg.Property.prototype.getDefinition = function() {
+					var name = this.value.match(/#([^\)'"]+)/);
+					if (name) { name = name[1]; }
+					if (!name) { name = this.value; }
+					return svg.Definitions[name];
+				}
+				
+				svg.Property.prototype.isUrlDefinition = function() {
+					return this.value.indexOf('url(') == 0
+				}
+				
+				svg.Property.prototype.getFillStyleDefinition = function(e, opacityProp) {
+					var def = this.getDefinition();
+					
+					// gradient
+					if (def != null && def.createGradient) {
+						return def.createGradient(svg.ctx, e, opacityProp);
+					}
+					
+					// pattern
+					if (def != null && def.createPattern) {
+						if (def.getHrefAttribute().hasValue()) {
+							var pt = def.attribute('patternTransform');
+							def = def.getHrefAttribute().getDefinition();
+							if (pt.hasValue()) { def.attribute('patternTransform', true).value = pt.value; }
+						}
+						return def.createPattern(svg.ctx, e);
+					}
+					
+					return null;
+				}
+			
+			// length extensions
+				svg.Property.prototype.getDPI = function(viewPort) {
+					return 96.0; // TODO: compute?
+				}
+				
+				svg.Property.prototype.getEM = function(viewPort) {
+					var em = 12;
+					
+					var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);
+					if (fontSize.hasValue()) em = fontSize.toPixels(viewPort);
+					
+					return em;
+				}
+				
+				svg.Property.prototype.getUnits = function() {
+					var s = this.value+'';
+					return s.replace(/[0-9\.\-]/g,'');
+				}
+			
+				// get the length as pixels
+				svg.Property.prototype.toPixels = function(viewPort, processPercent) {
+					if (!this.hasValue()) return 0;
+					var s = this.value+'';
+					if (s.match(/em$/)) return this.numValue() * this.getEM(viewPort);
+					if (s.match(/ex$/)) return this.numValue() * this.getEM(viewPort) / 2.0;
+					if (s.match(/px$/)) return this.numValue();
+					if (s.match(/pt$/)) return this.numValue() * this.getDPI(viewPort) * (1.0 / 72.0);
+					if (s.match(/pc$/)) return this.numValue() * 15;
+					if (s.match(/cm$/)) return this.numValue() * this.getDPI(viewPort) / 2.54;
+					if (s.match(/mm$/)) return this.numValue() * this.getDPI(viewPort) / 25.4;
+					if (s.match(/in$/)) return this.numValue() * this.getDPI(viewPort);
+					if (s.match(/%$/)) return this.numValue() * svg.ViewPort.ComputeSize(viewPort);
+					var n = this.numValue();
+					if (processPercent && n < 1.0) return n * svg.ViewPort.ComputeSize(viewPort);
+					return n;
+				}
+
+			// time extensions
+				// get the time as milliseconds
+				svg.Property.prototype.toMilliseconds = function() {
+					if (!this.hasValue()) return 0;
+					var s = this.value+'';
+					if (s.match(/s$/)) return this.numValue() * 1000;
+					if (s.match(/ms$/)) return this.numValue();
+					return this.numValue();
+				}
+			
+			// angle extensions
+				// get the angle as radians
+				svg.Property.prototype.toRadians = function() {
+					if (!this.hasValue()) return 0;
+					var s = this.value+'';
+					if (s.match(/deg$/)) return this.numValue() * (Math.PI / 180.0);
+					if (s.match(/grad$/)) return this.numValue() * (Math.PI / 200.0);
+					if (s.match(/rad$/)) return this.numValue();
+					return this.numValue() * (Math.PI / 180.0);
+				}
+		
+			// text extensions
+				// get the text baseline
+				var textBaselineMapping = {
+					'baseline': 'alphabetic',
+					'before-edge': 'top',
+					'text-before-edge': 'top',
+					'middle': 'middle',
+					'central': 'middle',
+					'after-edge': 'bottom',
+					'text-after-edge': 'bottom',
+					'ideographic': 'ideographic',
+					'alphabetic': 'alphabetic',
+					'hanging': 'hanging',
+					'mathematical': 'alphabetic'
+				};
+				svg.Property.prototype.toTextBaseline = function () {
+					if (!this.hasValue()) return null;
+					return textBaselineMapping[this.value];
+				}
+				
+		// fonts
+		svg.Font = new (function() {
+			this.Styles = 'normal|italic|oblique|inherit';
+			this.Variants = 'normal|small-caps|inherit';
+			this.Weights = 'normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900|inherit';
+			
+			this.CreateFont = function(fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) {
+				var f = inherit != null ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font);
+				return { 
+					fontFamily: fontFamily || f.fontFamily, 
+					fontSize: fontSize || f.fontSize, 
+					fontStyle: fontStyle || f.fontStyle, 
+					fontWeight: fontWeight || f.fontWeight, 
+					fontVariant: fontVariant || f.fontVariant,
+					toString: function () { return [this.fontStyle, this.fontVariant, this.fontWeight, this.fontSize, this.fontFamily].join(' ') } 
+				} 
+			}
+			
+			var that = this;
+			this.Parse = function(s) {
+				var f = {};
+				var d = svg.trim(svg.compressSpaces(s || '')).split(' ');
+				var set = { fontSize: false, fontStyle: false, fontWeight: false, fontVariant: false }
+				var ff = '';
+				for (var i=0; i<d.length; i++) {
+					if (!set.fontStyle && that.Styles.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontStyle = d[i]; set.fontStyle = true; }
+					else if (!set.fontVariant && that.Variants.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontVariant = d[i]; set.fontStyle = set.fontVariant = true;	}
+					else if (!set.fontWeight && that.Weights.indexOf(d[i]) != -1) {	if (d[i] != 'inherit') f.fontWeight = d[i]; set.fontStyle = set.fontVariant = set.fontWeight = true; }
+					else if (!set.fontSize) { if (d[i] != 'inherit') f.fontSize = d[i].split('/')[0]; set.fontStyle = set.fontVariant = set.fontWeight = set.fontSize = true; }
+					else { if (d[i] != 'inherit') ff += d[i]; }
+				} if (ff != '') f.fontFamily = ff;
+				return f;
+			}
+		});
+		
+		// points and paths
+		svg.ToNumberArray = function(s) {
+			var a = svg.trim(svg.compressSpaces((s || '').replace(/,/g, ' '))).split(' ');
+			for (var i=0; i<a.length; i++) {
+				a[i] = parseFloat(a[i]);
+			}
+			return a;
+		}		
+		svg.Point = function(x, y) {
+			this.x = x;
+			this.y = y;
+		}	
+			svg.Point.prototype.angleTo = function(p) {
+				return Math.atan2(p.y - this.y, p.x - this.x);
+			}
+			
+			svg.Point.prototype.applyTransform = function(v) {
+				var xp = this.x * v[0] + this.y * v[2] + v[4];
+				var yp = this.x * v[1] + this.y * v[3] + v[5];
+				this.x = xp;
+				this.y = yp;
+			}
+
+		svg.CreatePoint = function(s) {
+			var a = svg.ToNumberArray(s);
+			return new svg.Point(a[0], a[1]);
+		}
+		svg.CreatePath = function(s) {
+			var a = svg.ToNumberArray(s);
+			var path = [];
+			for (var i=0; i<a.length; i+=2) {
+				path.push(new svg.Point(a[i], a[i+1]));
+			}
+			return path;
+		}
+		
+		// bounding box
+		svg.BoundingBox = function(x1, y1, x2, y2) { // pass in initial points if you want
+			this.x1 = Number.NaN;
+			this.y1 = Number.NaN;
+			this.x2 = Number.NaN;
+			this.y2 = Number.NaN;
+			
+			this.x = function() { return this.x1; }
+			this.y = function() { return this.y1; }
+			this.width = function() { return this.x2 - this.x1; }
+			this.height = function() { return this.y2 - this.y1; }
+			
+			this.addPoint = function(x, y) {	
+				if (x != null) {
+					if (isNaN(this.x1) || isNaN(this.x2)) {
+						this.x1 = x;
+						this.x2 = x;
+					}
+					if (x < this.x1) this.x1 = x;
+					if (x > this.x2) this.x2 = x;
+				}
+			
+				if (y != null) {
+					if (isNaN(this.y1) || isNaN(this.y2)) {
+						this.y1 = y;
+						this.y2 = y;
+					}
+					if (y < this.y1) this.y1 = y;
+					if (y > this.y2) this.y2 = y;
+				}
+			}			
+			this.addX = function(x) { this.addPoint(x, null); }
+			this.addY = function(y) { this.addPoint(null, y); }
+			
+			this.addBoundingBox = function(bb) {
+				this.addPoint(bb.x1, bb.y1);
+				this.addPoint(bb.x2, bb.y2);
+			}
+			
+			this.addQuadraticCurve = function(p0x, p0y, p1x, p1y, p2x, p2y) {
+				var cp1x = p0x + 2/3 * (p1x - p0x); // CP1 = QP0 + 2/3 *(QP1-QP0)
+				var cp1y = p0y + 2/3 * (p1y - p0y); // CP1 = QP0 + 2/3 *(QP1-QP0)
+				var cp2x = cp1x + 1/3 * (p2x - p0x); // CP2 = CP1 + 1/3 *(QP2-QP0)
+				var cp2y = cp1y + 1/3 * (p2y - p0y); // CP2 = CP1 + 1/3 *(QP2-QP0)
+				this.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y,	cp2y, p2x, p2y);
+			}
+			
+			this.addBezierCurve = function(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {
+				// from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
+				var p0 = [p0x, p0y], p1 = [p1x, p1y], p2 = [p2x, p2y], p3 = [p3x, p3y];
+				this.addPoint(p0[0], p0[1]);
+				this.addPoint(p3[0], p3[1]);
+				
+				for (i=0; i<=1; i++) {
+					var f = function(t) { 
+						return Math.pow(1-t, 3) * p0[i]
+						+ 3 * Math.pow(1-t, 2) * t * p1[i]
+						+ 3 * (1-t) * Math.pow(t, 2) * p2[i]
+						+ Math.pow(t, 3) * p3[i];
+					}
+					
+					var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];
+					var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];
+					var c = 3 * p1[i] - 3 * p0[i];
+					
+					if (a == 0) {
+						if (b == 0) continue;
+						var t = -c / b;
+						if (0 < t && t < 1) {
+							if (i == 0) this.addX(f(t));
+							if (i == 1) this.addY(f(t));
+						}
+						continue;
+					}
+					
+					var b2ac = Math.pow(b, 2) - 4 * c * a;
+					if (b2ac < 0) continue;
+					var t1 = (-b + Math.sqrt(b2ac)) / (2 * a);
+					if (0 < t1 && t1 < 1) {
+						if (i == 0) this.addX(f(t1));
+						if (i == 1) this.addY(f(t1));
+					}
+					var t2 = (-b - Math.sqrt(b2ac)) / (2 * a);
+					if (0 < t2 && t2 < 1) {
+						if (i == 0) this.addX(f(t2));
+						if (i == 1) this.addY(f(t2));
+					}
+				}
+			}
+			
+			this.isPointInBox = function(x, y) {
+				return (this.x1 <= x && x <= this.x2 && this.y1 <= y && y <= this.y2);
+			}
+			
+			this.addPoint(x1, y1);
+			this.addPoint(x2, y2);
+		}
+		
+		// transforms
+		svg.Transform = function(v) {	
+			var that = this;
+			this.Type = {}
+		
+			// translate
+			this.Type.translate = function(s) {
+				this.p = svg.CreatePoint(s);			
+				this.apply = function(ctx) {
+					ctx.translate(this.p.x || 0.0, this.p.y || 0.0);
+				}
+				this.unapply = function(ctx) {
+					ctx.translate(-1.0 * this.p.x || 0.0, -1.0 * this.p.y || 0.0);
+				}
+				this.applyToPoint = function(p) {
+					p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);
+				}
+			}
+			
+			// rotate
+			this.Type.rotate = function(s) {
+				var a = svg.ToNumberArray(s);
+				this.angle = new svg.Property('angle', a[0]);
+				this.cx = a[1] || 0;
+				this.cy = a[2] || 0;
+				this.apply = function(ctx) {
+					ctx.translate(this.cx, this.cy);
+					ctx.rotate(this.angle.toRadians());
+					ctx.translate(-this.cx, -this.cy);
+				}
+				this.unapply = function(ctx) {
+					ctx.translate(this.cx, this.cy);
+					ctx.rotate(-1.0 * this.angle.toRadians());
+					ctx.translate(-this.cx, -this.cy);
+				}
+				this.applyToPoint = function(p) {
+					var a = this.angle.toRadians();
+					p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);
+					p.applyTransform([Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0]);
+					p.applyTransform([1, 0, 0, 1, -this.p.x || 0.0, -this.p.y || 0.0]);
+				}			
+			}
+			
+			this.Type.scale = function(s) {
+				this.p = svg.CreatePoint(s);
+				this.apply = function(ctx) {
+					ctx.scale(this.p.x || 1.0, this.p.y || this.p.x || 1.0);
+				}
+				this.unapply = function(ctx) {
+					ctx.scale(1.0 / this.p.x || 1.0, 1.0 / this.p.y || this.p.x || 1.0);
+				}
+				this.applyToPoint = function(p) {
+					p.applyTransform([this.p.x || 0.0, 0, 0, this.p.y || 0.0, 0, 0]);
+				}				
+			}
+			
+			this.Type.matrix = function(s) {
+				this.m = svg.ToNumberArray(s);
+				this.apply = function(ctx) {
+					ctx.transform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]);
+				}
+				this.applyToPoint = function(p) {
+					p.applyTransform(this.m);
+				}					
+			}
+			
+			this.Type.SkewBase = function(s) {
+				this.base = that.Type.matrix;
+				this.base(s);
+				this.angle = new svg.Property('angle', s);
+			}
+			this.Type.SkewBase.prototype = new this.Type.matrix;
+			
+			this.Type.skewX = function(s) {
+				this.base = that.Type.SkewBase;
+				this.base(s);
+				this.m = [1, 0, Math.tan(this.angle.toRadians()), 1, 0, 0];
+			}
+			this.Type.skewX.prototype = new this.Type.SkewBase;
+			
+			this.Type.skewY = function(s) {
+				this.base = that.Type.SkewBase;
+				this.base(s);
+				this.m = [1, Math.tan(this.angle.toRadians()), 0, 1, 0, 0];
+			}
+			this.Type.skewY.prototype = new this.Type.SkewBase;
+		
+			this.transforms = [];
+			
+			this.apply = function(ctx) {
+				for (var i=0; i<this.transforms.length; i++) {
+					this.transforms[i].apply(ctx);
+				}
+			}
+			
+			this.unapply = function(ctx) {
+				for (var i=this.transforms.length-1; i>=0; i--) {
+					this.transforms[i].unapply(ctx);
+				}
+			}
+			
+			this.applyToPoint = function(p) {
+				for (var i=0; i<this.transforms.length; i++) {
+					this.transforms[i].applyToPoint(p);
+				}
+			}
+			
+			var data = svg.trim(svg.compressSpaces(v)).replace(/\)([a-zA-Z])/g, ') $1').replace(/\)(\s?,\s?)/g,') ').split(/\s(?=[a-z])/);
+			for (var i=0; i<data.length; i++) {
+				var type = svg.trim(data[i].split('(')[0]);
+				var s = data[i].split('(')[1].replace(')','');
+				var transform = new this.Type[type](s);
+				transform.type = type;
+				this.transforms.push(transform);
+			}
+		}
+		
+		// aspect ratio
+		svg.AspectRatio = function(ctx, aspectRatio, width, desiredWidth, height, desiredHeight, minX, minY, refX, refY) {
+			// aspect ratio - http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
+			aspectRatio = svg.compressSpaces(aspectRatio);
+			aspectRatio = aspectRatio.replace(/^defer\s/,''); // ignore defer
+			var align = aspectRatio.split(' ')[0] || 'xMidYMid';
+			var meetOrSlice = aspectRatio.split(' ')[1] || 'meet';					
+	
+			// calculate scale
+			var scaleX = width / desiredWidth;
+			var scaleY = height / desiredHeight;
+			var scaleMin = Math.min(scaleX, scaleY);
+			var scaleMax = Math.max(scaleX, scaleY);
+			if (meetOrSlice == 'meet') { desiredWidth *= scaleMin; desiredHeight *= scaleMin; }
+			if (meetOrSlice == 'slice') { desiredWidth *= scaleMax; desiredHeight *= scaleMax; }	
+			
+			refX = new svg.Property('refX', refX);
+			refY = new svg.Property('refY', refY);
+			if (refX.hasValue() && refY.hasValue()) {				
+				ctx.translate(-scaleMin * refX.toPixels('x'), -scaleMin * refY.toPixels('y'));
+			} 
+			else {					
+				// align
+				if (align.match(/^xMid/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width / 2.0 - desiredWidth / 2.0, 0); 
+				if (align.match(/YMid$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height / 2.0 - desiredHeight / 2.0); 
+				if (align.match(/^xMax/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width - desiredWidth, 0); 
+				if (align.match(/YMax$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height - desiredHeight); 
+			}
+			
+			// scale
+			if (align == 'none') ctx.scale(scaleX, scaleY);
+			else if (meetOrSlice == 'meet') ctx.scale(scaleMin, scaleMin); 
+			else if (meetOrSlice == 'slice') ctx.scale(scaleMax, scaleMax); 	
+			
+			// translate
+			ctx.translate(minX == null ? 0 : -minX, minY == null ? 0 : -minY);			
+		}
+		
+		// elements
+		svg.Element = {}
+		
+		svg.EmptyProperty = new svg.Property('EMPTY', '');
+		
+		svg.Element.ElementBase = function(node) {	
+			this.attributes = {};
+			this.styles = {};
+			this.children = [];
+			
+			// get or create attribute
+			this.attribute = function(name, createIfNotExists) {
+				var a = this.attributes[name];
+				if (a != null) return a;
+							
+				if (createIfNotExists == true) { a = new svg.Property(name, ''); this.attributes[name] = a; }
+				return a || svg.EmptyProperty;
+			}
+			
+			this.getHrefAttribute = function() {
+				for (var a in this.attributes) { 
+					if (a.match(/:href$/)) { 
+						return this.attributes[a]; 
+					} 
+				}
+				return svg.EmptyProperty;
+			}
+			
+			// get or create style, crawls up node tree
+			this.style = function(name, createIfNotExists, skipAncestors) {
+				var s = this.styles[name];
+				if (s != null) return s;
+				
+				var a = this.attribute(name);
+				if (a != null && a.hasValue()) {
+					this.styles[name] = a; // move up to me to cache
+					return a;
+				}
+				
+				if (skipAncestors != true) {
+					var p = this.parent;
+					if (p != null) {
+						var ps = p.style(name);
+						if (ps != null && ps.hasValue()) {
+							return ps;
+						}
+					}
+				}
+					
+				if (createIfNotExists == true) { s = new svg.Property(name, ''); this.styles[name] = s; }
+				return s || svg.EmptyProperty;
+			}
+			
+			// base render
+			this.render = function(ctx) {
+				// don't render display=none
+				if (this.style('display').value == 'none') return;
+				
+				// don't render visibility=hidden
+				if (this.style('visibility').value == 'hidden') return;
+			
+				ctx.save();
+				if (this.attribute('mask').hasValue()) { // mask
+					var mask = this.attribute('mask').getDefinition();
+					if (mask != null) mask.apply(ctx, this);
+				}
+				else if (this.style('filter').hasValue()) { // filter
+					var filter = this.style('filter').getDefinition();
+					if (filter != null) filter.apply(ctx, this);
+				}
+				else {	
+					this.setContext(ctx);
+					this.renderChildren(ctx);	
+					this.clearContext(ctx);							
+				}
+				ctx.restore();
+			}
+			
+			// base set context
+			this.setContext = function(ctx) {
+				// OVERRIDE ME!
+			}
+			
+			// base clear context
+			this.clearContext = function(ctx) {
+				// OVERRIDE ME!
+			}			
+			
+			// base render children
+			this.renderChildren = function(ctx) {
+				for (var i=0; i<this.children.length; i++) {
+					this.children[i].render(ctx);
+				}
+			}
+			
+			this.addChild = function(childNode, create) {
+				var child = childNode;
+				if (create) child = svg.CreateElement(childNode);
+				child.parent = this;
+				if (child.type != 'title') { this.children.push(child);	}
+			}
+				
+			if (node != null && node.nodeType == 1) { //ELEMENT_NODE
+				// add children
+				for (var i=0; i<node.childNodes.length; i++) {
+					var childNode = node.childNodes[i];
+					if (childNode.nodeType == 1) this.addChild(childNode, true); //ELEMENT_NODE
+					if (this.captureTextNodes && (childNode.nodeType == 3 || childNode.nodeType == 4)) {
+						var text = childNode.nodeValue || childNode.text || '';
+						if (svg.trim(svg.compressSpaces(text)) != '') {
+							this.addChild(new svg.Element.tspan(childNode), false); // TEXT_NODE
+						}
+					}
+				}
+				
+				// add attributes
+				for (var i=0; i<node.attributes.length; i++) {
+					var attribute = node.attributes[i];
+					this.attributes[attribute.nodeName] = new svg.Property(attribute.nodeName, attribute.nodeValue);
+				}
+										
+				// add tag styles
+				var styles = svg.Styles[node.nodeName];
+				if (styles != null) {
+					for (var name in styles) {
+						this.styles[name] = styles[name];
+					}
+				}					
+				
+				// add class styles
+				if (this.attribute('class').hasValue()) {
+					var classes = svg.compressSpaces(this.attribute('class').value).split(' ');
+					for (var j=0; j<classes.length; j++) {
+						styles = svg.Styles['.'+classes[j]];
+						if (styles != null) {
+							for (var name in styles) {
+								this.styles[name] = styles[name];
+							}
+						}
+						styles = svg.Styles[node.nodeName+'.'+classes[j]];
+						if (styles != null) {
+							for (var name in styles) {
+								this.styles[name] = styles[name];
+							}
+						}
+					}
+				}
+				
+				// add id styles
+				if (this.attribute('id').hasValue()) {
+					var styles = svg.Styles['#' + this.attribute('id').value];
+					if (styles != null) {
+						for (var name in styles) {
+							this.styles[name] = styles[name];
+						}
+					}
+				}
+				
+				// add inline styles
+				if (this.attribute('style').hasValue()) {
+					var styles = this.attribute('style').value.split(';');
+					for (var i=0; i<styles.length; i++) {
+						if (svg.trim(styles[i]) != '') {
+							var style = styles[i].split(':');
+							var name = svg.trim(style[0]);
+							var value = svg.trim(style[1]);
+							this.styles[name] = new svg.Property(name, value);
+						}
+					}
+				}	
+
+				// add id
+				if (this.attribute('id').hasValue()) {
+					if (svg.Definitions[this.attribute('id').value] == null) {
+						svg.Definitions[this.attribute('id').value] = this;
+					}
+				}
+			}
+		}
+		
+		svg.Element.RenderedElementBase = function(node) {
+			this.base = svg.Element.ElementBase;
+			this.base(node);
+			
+			this.setContext = function(ctx) {
+				// fill
+				if (this.style('fill').isUrlDefinition()) {
+					var fs = this.style('fill').getFillStyleDefinition(this, this.style('fill-opacity'));
+					if (fs != null) ctx.fillStyle = fs;
+				}
+				else if (this.style('fill').hasValue()) {
+					var fillStyle = this.style('fill');
+					if (fillStyle.value == 'currentColor') fillStyle.value = this.style('color').value;
+					ctx.fillStyle = (fillStyle.value == 'none' ? 'rgba(0,0,0,0)' : fillStyle.value);
+				}
+				if (this.style('fill-opacity').hasValue()) {
+					var fillStyle = new svg.Property('fill', ctx.fillStyle);
+					fillStyle = fillStyle.addOpacity(this.style('fill-opacity'));
+					ctx.fillStyle = fillStyle.value;
+				}
+									
+				// stroke
+				if (this.style('stroke').isUrlDefinition()) {
+					var fs = this.style('stroke').getFillStyleDefinition(this, this.style('stroke-opacity'));
+					if (fs != null) ctx.strokeStyle = fs;
+				}
+				else if (this.style('stroke').hasValue()) {
+					var strokeStyle = this.style('stroke');
+					if (strokeStyle.value == 'currentColor') strokeStyle.value = this.style('color').value;
+					ctx.strokeStyle = (strokeStyle.value == 'none' ? 'rgba(0,0,0,0)' : strokeStyle.value);
+				}
+				if (this.style('stroke-opacity').hasValue()) {
+					var strokeStyle = new svg.Property('stroke', ctx.strokeStyle);
+					strokeStyle = strokeStyle.addOpacity(this.style('stroke-opacity'));
+					ctx.strokeStyle = strokeStyle.value;
+				}
+				if (this.style('stroke-width').hasValue()) {
+					var newLineWidth = this.style('stroke-width').toPixels();
+					ctx.lineWidth = newLineWidth == 0 ? 0.001 : newLineWidth; // browsers don't respect 0
+			    }
+				if (this.style('stroke-linecap').hasValue()) ctx.lineCap = this.style('stroke-linecap').value;
+				if (this.style('stroke-linejoin').hasValue()) ctx.lineJoin = this.style('stroke-linejoin').value;
+				if (this.style('stroke-miterlimit').hasValue()) ctx.miterLimit = this.style('stroke-miterlimit').value;
+				if (this.style('stroke-dasharray').hasValue() && this.style('stroke-dasharray').value != 'none') {
+					var gaps = svg.ToNumberArray(this.style('stroke-dasharray').value);
+					if (typeof(ctx.setLineDash) != 'undefined') { ctx.setLineDash(gaps); }
+					else if (typeof(ctx.webkitLineDash) != 'undefined') { ctx.webkitLineDash = gaps; }
+					else if (typeof(ctx.mozDash) != 'undefined' && !(gaps.length==1 && gaps[0]==0)) { ctx.mozDash = gaps; }
+					
+					var offset = this.style('stroke-dashoffset').numValueOrDefault(1);
+					if (typeof(ctx.lineDashOffset) != 'undefined') { ctx.lineDashOffset = offset; }
+					else if (typeof(ctx.webkitLineDashOffset) != 'undefined') { ctx.webkitLineDashOffset = offset; }
+					else if (typeof(ctx.mozDashOffset) != 'undefined') { ctx.mozDashOffset = offset; }
+				}
+
+				// font
+				if (typeof(ctx.font) != 'undefined') {
+					ctx.font = svg.Font.CreateFont( 
+						this.style('font-style').value, 
+						this.style('font-variant').value, 
+						this.style('font-weight').value, 
+						this.style('font-size').hasValue() ? this.style('font-size').toPixels() + 'px' : '', 
+						this.style('font-family').value).toString();
+				}
+				
+				// transform
+				if (this.attribute('transform').hasValue()) { 
+					var transform = new svg.Transform(this.attribute('transform').value);
+					transform.apply(ctx);
+				}
+				
+				// clip
+				if (this.style('clip-path', false, true).hasValue()) {
+					var clip = this.style('clip-path', false, true).getDefinition();
+					if (clip != null) clip.apply(ctx);
+				}
+				
+				// opacity
+				if (this.style('opacity').hasValue()) {
+					ctx.globalAlpha = this.style('opacity').numValue();
+				}
+			}		
+		}
+		svg.Element.RenderedElementBase.prototype = new svg.Element.ElementBase;
+		
+		svg.Element.PathElementBase = function(node) {
+			this.base = svg.Element.RenderedElementBase;
+			this.base(node);
+			
+			this.path = function(ctx) {
+				if (ctx != null) ctx.beginPath();
+				return new svg.BoundingBox();
+			}
+			
+			this.renderChildren = function(ctx) {
+				this.path(ctx);
+				svg.Mouse.checkPath(this, ctx);
+				if (ctx.fillStyle != '') {
+					if (this.style('fill-rule').valueOrDefault('inherit') != 'inherit') { ctx.fill(this.style('fill-rule').value); }
+					else { ctx.fill(); }
+				}
+				if (ctx.strokeStyle != '') ctx.stroke();
+				
+				var markers = this.getMarkers();
+				if (markers != null) {
+					if (this.style('marker-start').isUrlDefinition()) {
+						var marker = this.style('marker-start').getDefinition();
+						marker.render(ctx, markers[0][0], markers[0][1]);
+					}
+					if (this.style('marker-mid').isUrlDefinition()) {
+						var marker = this.style('marker-mid').getDefinition();
+						for (var i=1;i<markers.length-1;i++) {
+							marker.render(ctx, markers[i][0], markers[i][1]);
+						}
+					}
+					if (this.style('marker-end').isUrlDefinition()) {
+						var marker = this.style('marker-end').getDefinition();
+						marker.render(ctx, markers[markers.length-1][0], markers[markers.length-1][1]);
+					}
+				}					
+			}
+			
+			this.getBoundingBox = function() {
+				return this.path();
+			}
+			
+			this.getMarkers = function() {
+				return null;
+			}
+		}
+		svg.Element.PathElementBase.prototype = new svg.Element.RenderedElementBase;
+		
+		// svg element
+		svg.Element.svg = function(node) {
+			this.base = svg.Element.RenderedElementBase;
+			this.base(node);
+			
+			this.baseClearContext = this.clearContext;
+			this.clearContext = function(ctx) {
+				this.baseClearContext(ctx);
+				svg.ViewPort.RemoveCurrent();
+			}
+			
+			this.baseSetContext = this.setContext;
+			this.setContext = function(ctx) {
+				// initial values and defaults
+				ctx.strokeStyle = 'rgba(0,0,0,0)';
+				ctx.lineCap = 'butt';
+				ctx.lineJoin = 'miter';
+				ctx.miterLimit = 4;	
+				if (typeof(ctx.font) != 'undefined' && typeof(window.getComputedStyle) != 'undefined') {
+					ctx.font = window.getComputedStyle(ctx.canvas).getPropertyValue('font');
+				}
+			
+				this.baseSetContext(ctx);
+				
+				// create new view port
+				if (!this.attribute('x').hasValue()) this.attribute('x', true).value = 0;
+				if (!this.attribute('y').hasValue()) this.attribute('y', true).value = 0;
+				ctx.translate(this.attribute('x').toPixels('x'), this.attribute('y').toPixels('y'));
+				
+				var width = svg.ViewPort.width();
+				var height = svg.ViewPort.height();
+				
+				if (!this.attribute('width').hasValue()) this.attribute('width', true).value = '100%';
+				if (!this.attribute('height').hasValue()) this.attribute('height', true).value = '100%';
+				if (typeof(this.root) == 'undefined') {
+					width = this.attribute('width').toPixels('x');
+					height = this.attribute('height').toPixels('y');
+					
+					var x = 0;
+					var y = 0;
+					if (this.attribute('refX').hasValue() && this.attribute('refY').hasValue()) {
+						x = -this.attribute('refX').toPixels('x');
+						y = -this.attribute('refY').toPixels('y');
+					}
+					
+					if (this.attribute('overflow').valueOrDefault('hidden') != 'visible') {
+						ctx.beginPath();
+						ctx.moveTo(x, y);
+						ctx.lineTo(width, y);
+						ctx.lineTo(width, height);
+						ctx.lineTo(x, height);
+						ctx.closePath();
+						ctx.clip();
+					}
+				}
+				svg.ViewPort.SetCurrent(width, height);	
+						
+				// viewbox
+				if (this.attribute('viewBox').hasValue()) {				
+					var viewBox = svg.ToNumberArray(this.attribute('viewBox').value);
+					var minX = viewBox[0];
+					var minY = viewBox[1];
+					width = viewBox[2];
+					height = viewBox[3];
+					
+					svg.AspectRatio(ctx,
+									this.attribute('preserveAspectRatio').value, 
+									svg.ViewPort.width(), 
+									width,
+									svg.ViewPort.height(),
+									height,
+									minX,
+									minY,
+									this.attribute('refX').value,
+									this.attribute('refY').value);
+					
+					svg.ViewPort.RemoveCurrent();
+					svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);
+				}				
+			}
+		}
+		svg.Element.svg.prototype = new svg.Element.RenderedElementBase;
+
+		// rect element
+		svg.Element.rect = function(node) {
+			this.base = svg.Element.PathElementBase;
+			this.base(node);
+			
+			this.path = function(ctx) {
+				var x = this.attribute('x').toPixels('x');
+				var y = this.attribute('y').toPixels('y');
+				var width = this.attribute('width').toPixels('x');
+				var height = this.attribute('height').toPixels('y');
+				var rx = this.attribute('rx').toPixels('x');
+				var ry = this.attribute('ry').toPixels('y');
+				if (this.attribute('rx').hasValue() && !this.attribute('ry').hasValue()) ry = rx;
+				if (this.attribute('ry').hasValue() && !this.attribute('rx').hasValue()) rx = ry;
+				rx = Math.min(rx, width / 2.0);
+				ry = Math.min(ry, height / 2.0);
+				if (ctx != null) {
+					ctx.beginPath();
+					ctx.moveTo(x + rx, y);
+					ctx.lineTo(x + width - rx, y);
+					ctx.quadraticCurveTo(x + width, y, x + width, y + ry)
+					ctx.lineTo(x + width, y + height - ry);
+					ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height)
+					ctx.lineTo(x + rx, y + height);
+					ctx.quadraticCurveTo(x, y + height, x, y + height - ry)
+					ctx.lineTo(x, y + ry);
+					ctx.quadraticCurveTo(x, y, x + rx, y)
+					ctx.closePath();
+				}
+				
+				return new svg.BoundingBox(x, y, x + width, y + height);
+			}
+		}
+		svg.Element.rect.prototype = new svg.Element.PathElementBase;
+		
+		// circle element
+		svg.Element.circle = function(node) {
+			this.base = svg.Element.PathElementBase;
+			this.base(node);
+			
+			this.path = function(ctx) {
+				var cx = this.attribute('cx').toPixels('x');
+				var cy = this.attribute('cy').toPixels('y');
+				var r = this.attribute('r').toPixels();
+			
+				if (ctx != null) {
+					ctx.beginPath();
+					ctx.arc(cx, cy, r, 0, Math.PI * 2, true); 
+					ctx.closePath();
+				}
+				
+				return new svg.BoundingBox(cx - r, cy - r, cx + r, cy + r);
+			}
+		}
+		svg.Element.circle.prototype = new svg.Element.PathElementBase;	
+
+		// ellipse element
+		svg.Element.ellipse = function(node) {
+			this.base = svg.Element.PathElementBase;
+			this.base(node);
+			
+			this.path = function(ctx) {
+				var KAPPA = 4 * ((Math.sqrt(2) - 1) / 3);
+				var rx = this.attribute('rx').toPixels('x');
+				var ry = this.attribute('ry').toPixels('y');
+				var cx = this.attribute('cx').toPixels('x');
+				var cy = this.attribute('cy').toPixels('y');
+				
+				if (ctx != null) {
+					ctx.beginPath();
+					ctx.moveTo(cx, cy - ry);
+					ctx.bezierCurveTo(cx + (KAPPA * rx), cy - ry,  cx + rx, cy - (KAPPA * ry), cx + rx, cy);
+					ctx.bezierCurveTo(cx + rx, cy + (KAPPA * ry), cx + (KAPPA * rx), cy + ry, cx, cy + ry);
+					ctx.bezierCurveTo(cx - (KAPPA * rx), cy + ry, cx - rx, cy + (KAPPA * ry), cx - rx, cy);
+					ctx.bezierCurveTo(cx - rx, cy - (KAPPA * ry), cx - (KAPPA * rx), cy - ry, cx, cy - ry);
+					ctx.closePath();
+				}
+				
+				return new svg.BoundingBox(cx - rx, cy - ry, cx + rx, cy + ry);
+			}
+		}
+		svg.Element.ellipse.prototype = new svg.Element.PathElementBase;			
+		
+		// line element
+		svg.Element.line = function(node) {
+			this.base = svg.Element.PathElementBase;
+			this.base(node);
+			
+			this.getPoints = function() {
+				return [
+					new svg.Point(this.attribute('x1').toPixels('x'), this.attribute('y1').toPixels('y')),
+					new svg.Point(this.attribute('x2').toPixels('x'), this.attribute('y2').toPixels('y'))];
+			}
+								
+			this.path = function(ctx) {
+				var points = this.getPoints();
+				
+				if (ctx != null) {
+					ctx.beginPath();
+					ctx.moveTo(points[0].x, points[0].y);
+					ctx.lineTo(points[1].x, points[1].y);
+				}
+				
+				return new svg.BoundingBox(points[0].x, points[0].y, points[1].x, points[1].y);
+			}
+			
+			this.getMarkers = function() {
+				var points = this.getPoints();	
+				var a = points[0].angleTo(points[1]);
+				return [[points[0], a], [points[1], a]];
+			}
+		}
+		svg.Element.line.prototype = new svg.Element.PathElementBase;		
+				
+		// polyline element
+		svg.Element.polyline = function(node) {
+			this.base = svg.Element.PathElementBase;
+			this.base(node);
+			
+			this.points = svg.CreatePath(this.attribute('points').value);
+			this.path = function(ctx) {
+				var bb = new svg.BoundingBox(this.points[0].x, this.points[0].y);
+				if (ctx != null) {
+					ctx.beginPath();
+					ctx.moveTo(this.points[0].x, this.points[0].y);
+				}
+				for (var i=1; i<this.points.length; i++) {
+					bb.addPoint(this.points[i].x, this.points[i].y);
+					if (ctx != null) ctx.lineTo(this.points[i].x, this.points[i].y);
+				}
+				return bb;
+			}
+			
+			this.getMarkers = function() {
+				var markers = [];
+				for (var i=0; i<this.points.length - 1; i++) {
+					markers.push([this.points[i], this.points[i].angleTo(this.points[i+1])]);
+				}
+				markers.push([this.points[this.points.length-1], markers[markers.length-1][1]]);
+				return markers;
+			}			
+		}
+		svg.Element.polyline.prototype = new svg.Element.PathElementBase;				
+				
+		// polygon element
+		svg.Element.polygon = function(node) {
+			this.base = svg.Element.polyline;
+			this.base(node);
+			
+			this.basePath = this.path;
+			this.path = function(ctx) {
+				var bb = this.basePath(ctx);
+				if (ctx != null) {
+					ctx.lineTo(this.points[0].x, this.points[0].y);
+					ctx.closePath();
+				}
+				return bb;
+			}
+		}
+		svg.Element.polygon.prototype = new svg.Element.polyline;
+
+		// path element
+		svg.Element.path = function(node) {
+			this.base = svg.Element.PathElementBase;
+			this.base(node);
+					
+			var d = this.attribute('d').value;
+			// TODO: convert to real lexer based on http://www.w3.org/TR/SVG11/paths.html#PathDataBNF
+			d = d.replace(/,/gm,' '); // get rid of all commas
+			d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands
+			d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands
+			d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([^\s])/gm,'$1 $2'); // separate commands from points
+			d = d.replace(/([^\s])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from points
+			d = d.replace(/([0-9])([+\-])/gm,'$1 $2'); // separate digits when no comma
+			d = d.replace(/(\.[0-9]*)(\.)/gm,'$1 $2'); // separate digits when no comma
+			d = d.replace(/([Aa](\s+[0-9]+){3})\s+([01])\s*([01])/gm,'$1 $3 $4 '); // shorthand elliptical arc path syntax
+			d = svg.compressSpaces(d); // compress multiple spaces
+			d = svg.trim(d);
+			this.PathParser = new (function(d) {
+				this.tokens = d.split(' ');
+				
+				this.reset = function() {
+					this.i = -1;
+					this.command = '';
+					this.previousCommand = '';
+					this.start = new svg.Point(0, 0);
+					this.control = new svg.Point(0, 0);
+					this.current = new svg.Point(0, 0);
+					this.points = [];
+					this.angles = [];
+				}
+								
+				this.isEnd = function() {
+					return this.i >= this.tokens.length - 1;
+				}
+				
+				this.isCommandOrEnd = function() {
+					if (this.isEnd()) return true;
+					return this.tokens[this.i + 1].match(/^[A-Za-z]$/) != null;
+				}
+				
+				this.isRelativeCommand = function() {
+					switch(this.command)
+					{
+						case 'm':
+						case 'l':
+						case 'h':
+						case 'v':
+						case 'c':
+						case 's':
+						case 'q':
+						case 't':
+						case 'a':
+						case 'z':
+							return true;
+							break;
+					}
+					return false;
+				}
+							
+				this.getToken = function() {
+					this.i++;
+					return this.tokens[this.i];
+				}
+				
+				this.getScalar = function() {
+					return parseFloat(this.getToken());
+				}
+				
+				this.nextCommand = function() {
+					this.previousCommand = this.command;
+					this.command = this.getToken();
+				}				
+				
+				this.getPoint = function() {
+					var p = new svg.Point(this.getScalar(), this.getScalar());
+					return this.makeAbsolute(p);
+				}
+				
+				this.getAsControlPoint = function() {
+					var p = this.getPoint();
+					this.control = p;
+					return p;
+				}
+				
+				this.getAsCurrentPoint = function() {
+					var p = this.getPoint();
+					this.current = p;
+					return p;	
+				}
+				
+				this.getReflectedControlPoint = function() {
+					if (this.previousCommand.toLowerCase() != 'c' && 
+					    this.previousCommand.toLowerCase() != 's' &&
+						this.previousCommand.toLowerCase() != 'q' && 
+						this.previousCommand.toLowerCase() != 't' ){
+						return this.current;
+					}
+					
+					// reflect point
+					var p = new svg.Point(2 * this.current.x - this.control.x, 2 * this.current.y - this.control.y);					
+					return p;
+				}
+				
+				this.makeAbsolute = function(p) {
+					if (this.isRelativeCommand()) {
+						p.x += this.current.x;
+						p.y += this.current.y;
+					}
+					return p;
+				}
+				
+				this.addMarker = function(p, from, priorTo) {
+					// if the last angle isn't filled in because we didn't have this point yet ...
+					if (priorTo != null && this.angles.length > 0 && this.angles[this.angles.length-1] == null) {
+						this.angles[this.angles.length-1] = this.points[this.points.length-1].angleTo(priorTo);
+					}
+					this.addMarkerAngle(p, from == null ? null : from.angleTo(p));
+				}
+				
+				this.addMarkerAngle = function(p, a) {
+					this.points.push(p);
+					this.angles.push(a);
+				}				
+				
+				this.getMarkerPoints = function() { return this.points; }
+				this.getMarkerAngles = function() {
+					for (var i=0; i<this.angles.length; i++) {
+						if (this.angles[i] == null) {
+							for (var j=i+1; j<this.angles.length; j++) {
+								if (this.angles[j] != null) {
+									this.angles[i] = this.angles[j];
+									break;
+								}
+							}
+						}
+					}
+					return this.angles;
+				}
+			})(d);
+
+			this.path = function(ctx) {
+				var pp = this.PathParser;
+				pp.reset();
+
+				var bb = new svg.BoundingBox();
+				if (ctx != null) ctx.beginPath();
+				while (!pp.isEnd()) {
+					pp.nextCommand();
+					switch (pp.command) {
+					case 'M':
+					case 'm':
+						var p = pp.getAsCurrentPoint();
+						pp.addMarker(p);
+						bb.addPoint(p.x, p.y);
+						if (ctx != null) ctx.moveTo(p.x, p.y);
+						pp.start = pp.current;
+						while (!pp.isCommandOrEnd()) {
+							var p = pp.getAsCurrentPoint();
+							pp.addMarker(p, pp.start);
+							bb.addPoint(p.x, p.y);
+							if (ctx != null) ctx.lineTo(p.x, p.y);
+						}
+						break;
+					case 'L':
+					case 'l':
+						while (!pp.isCommandOrEnd()) {
+							var c = pp.current;
+							var p = pp.getAsCurrentPoint();
+							pp.addMarker(p, c);
+							bb.addPoint(p.x, p.y);
+							if (ctx != null) ctx.lineTo(p.x, p.y);
+						}
+						break;
+					case 'H':
+					case 'h':
+						while (!pp.isCommandOrEnd()) {
+							var newP = new svg.Point((pp.isRelativeCommand() ? pp.current.x : 0) + pp.getScalar(), pp.current.y);
+							pp.addMarker(newP, pp.current);
+							pp.current = newP;
+							bb.addPoint(pp.current.x, pp.current.y);
+							if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
+						}
+						break;
+					case 'V':
+					case 'v':
+						while (!pp.isCommandOrEnd()) {
+							var newP = new svg.Point(pp.current.x, (pp.isRelativeCommand() ? pp.current.y : 0) + pp.getScalar());
+							pp.addMarker(newP, pp.current);
+							pp.current = newP;
+							bb.addPoint(pp.current.x, pp.current.y);
+							if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
+						}
+						break;
+					case 'C':
+					case 'c':
+						while (!pp.isCommandOrEnd()) {
+							var curr = pp.current;
+							var p1 = pp.getPoint();
+							var cntrl = pp.getAsControlPoint();
+							var cp = pp.getAsCurrentPoint();
+							pp.addMarker(cp, cntrl, p1);
+							bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
+							if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
+						}
+						break;
+					case 'S':
+					case 's':
+						while (!pp.isCommandOrEnd()) {
+							var curr = pp.current;
+							var p1 = pp.getReflectedControlPoint();
+							var cntrl = pp.getAsControlPoint();
+							var cp = pp.getAsCurrentPoint();
+							pp.addMarker(cp, cntrl, p1);
+							bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
+							if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
+						}
+						break;
+					case 'Q':
+					case 'q':
+						while (!pp.isCommandOrEnd()) {
+							var curr = pp.current;
+							var cntrl = pp.getAsControlPoint();
+							var cp = pp.getAsCurrentPoint();
+							pp.addMarker(cp, cntrl, cntrl);
+							bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
+							if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
+						}
+						break;
+					case 'T':
+					case 't':
+						while (!pp.isCommandOrEnd()) {
+							var curr = pp.current;
+							var cntrl = pp.getReflectedControlPoint();
+							pp.control = cntrl;
+							var cp = pp.getAsCurrentPoint();
+							pp.addMarker(cp, cntrl, cntrl);
+							bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
+							if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
+						}
+						break;
+					case 'A':
+					case 'a':
+						while (!pp.isCommandOrEnd()) {
+						    var curr = pp.current;
+							var rx = pp.getScalar();
+							var ry = pp.getScalar();
+							var xAxisRotation = pp.getScalar() * (Math.PI / 180.0);
+							var largeArcFlag = pp.getScalar();
+							var sweepFlag = pp.getScalar();
+							var cp = pp.getAsCurrentPoint();
+
+							// Conversion from endpoint to center parameterization
+							// http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+							// x1', y1'
+							var currp = new svg.Point(
+								Math.cos(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.sin(xAxisRotation) * (curr.y - cp.y) / 2.0,
+								-Math.sin(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.cos(xAxisRotation) * (curr.y - cp.y) / 2.0
+							);
+							// adjust radii
+							var l = Math.pow(currp.x,2)/Math.pow(rx,2)+Math.pow(currp.y,2)/Math.pow(ry,2);
+							if (l > 1) {
+								rx *= Math.sqrt(l);
+								ry *= Math.sqrt(l);
+							}
+							// cx', cy'
+							var s = (largeArcFlag == sweepFlag ? -1 : 1) * Math.sqrt(
+								((Math.pow(rx,2)*Math.pow(ry,2))-(Math.pow(rx,2)*Math.pow(currp.y,2))-(Math.pow(ry,2)*Math.pow(currp.x,2))) /
+								(Math.pow(rx,2)*Math.pow(currp.y,2)+Math.pow(ry,2)*Math.pow(currp.x,2))
+							);
+							if (isNaN(s)) s = 0;
+							var cpp = new svg.Point(s * rx * currp.y / ry, s * -ry * currp.x / rx);
+							// cx, cy
+							var centp = new svg.Point(
+								(curr.x + cp.x) / 2.0 + Math.cos(xAxisRotation) * cpp.x - Math.sin(xAxisRotation) * cpp.y,
+								(curr.y + cp.y) / 2.0 + Math.sin(xAxisRotation) * cpp.x + Math.cos(xAxisRotation) * cpp.y
+							);
+							// vector magnitude
+							var m = function(v) { return Math.sqrt(Math.pow(v[0],2) + Math.pow(v[1],2)); }
+							// ratio between two vectors
+							var r = function(u, v) { return (u[0]*v[0]+u[1]*v[1]) / (m(u)*m(v)) }
+							// angle between two vectors
+							var a = function(u, v) { return (u[0]*v[1] < u[1]*v[0] ? -1 : 1) * Math.acos(r(u,v)); }
+							// initial angle
+							var a1 = a([1,0], [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry]);
+							// angle delta
+							var u = [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry];
+							var v = [(-currp.x-cpp.x)/rx,(-currp.y-cpp.y)/ry];
+							var ad = a(u, v);
+							if (r(u,v) <= -1) ad = Math.PI;
+							if (r(u,v) >= 1) ad = 0;
+
+							// for markers
+							var dir = 1 - sweepFlag ? 1.0 : -1.0;
+							var ah = a1 + dir * (ad / 2.0);
+							var halfWay = new svg.Point(
+								centp.x + rx * Math.cos(ah),
+								centp.y + ry * Math.sin(ah)
+							);
+							pp.addMarkerAngle(halfWay, ah - dir * Math.PI / 2);
+							pp.addMarkerAngle(cp, ah - dir * Math.PI);
+
+							bb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better
+							if (ctx != null) {
+								var r = rx > ry ? rx : ry;
+								var sx = rx > ry ? 1 : rx / ry;
+								var sy = rx > ry ? ry / rx : 1;
+
+								ctx.translate(centp.x, centp.y);
+								ctx.rotate(xAxisRotation);
+								ctx.scale(sx, sy);
+								ctx.arc(0, 0, r, a1, a1 + ad, 1 - sweepFlag);
+								ctx.scale(1/sx, 1/sy);
+								ctx.rotate(-xAxisRotation);
+								ctx.translate(-centp.x, -centp.y);
+							}
+						}
+						break;
+					case 'Z':
+					case 'z':
+						if (ctx != null) ctx.closePath();
+						pp.current = pp.start;
+					}
+				}
+
+				return bb;
+			}
+
+			this.getMarkers = function() {
+				var points = this.PathParser.getMarkerPoints();
+				var angles = this.PathParser.getMarkerAngles();
+				
+				var markers = [];
+				for (var i=0; i<points.length; i++) {
+					markers.push([points[i], angles[i]]);
+				}
+				return markers;
+			}
+		}
+		svg.Element.path.prototype = new svg.Element.PathElementBase;
+		
+		// pattern element
+		svg.Element.pattern = function(node) {
+			this.base = svg.Element.ElementBase;
+			this.base(node);
+			
+			this.createPattern = function(ctx, element) {
+				var width = this.attribute('width').toPixels('x', true);
+				var height = this.attribute('height').toPixels('y', true);
+			
+				// render me using a temporary svg element
+				var tempSvg = new svg.Element.svg();
+				tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
+				tempSvg.attributes['width'] = new svg.Property('width', width + 'px');
+				tempSvg.attributes['height'] = new svg.Property('height', height + 'px');
+				tempSvg.attributes['transform'] = new svg.Property('transform', this.attribute('patternTransform').value);
+				tempSvg.children = this.children;
+				
+				var c = document.createElement('canvas');
+				c.width = width;
+				c.height = height;
+				var cctx = c.getContext('2d');
+				if (this.attribute('x').hasValue() && this.attribute('y').hasValue()) {
+					cctx.translate(this.attribute('x').toPixels('x', true), this.attribute('y').toPixels('y', true));
+				}
+				// render 3x3 grid so when we transform there's no white space on edges
+				for (var x=-1; x<=1; x++) {
+					for (var y=-1; y<=1; y++) {
+						cctx.save();
+						cctx.translate(x * c.width, y * c.height);
+						tempSvg.render(cctx);
+						cctx.restore();
+					}
+				}
+				var pattern = ctx.createPattern(c, 'repeat');
+				return pattern;
+			}
+		}
+		svg.Element.pattern.prototype = new svg.Element.ElementBase;
+		
+		// marker element
+		svg.Element.marker = function(node) {
+			this.base = svg.Element.ElementBase;
+			this.base(node);
+			
+			this.baseRender = this.render;
+			this.render = function(ctx, point, angle) {
+				ctx.translate(point.x, point.y);
+				if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(angle);
+				if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(ctx.lineWidth, ctx.lineWidth);
+				ctx.save();
+							
+				// render me using a temporary svg element
+				var tempSvg = new svg.Element.svg();
+				tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
+				tempSvg.attributes['refX'] = new svg.Property('refX', this.attribute('refX').value);
+				tempSvg.attributes['refY'] = new svg.Property('refY', this.attribute('refY').value);
+				tempSvg.attributes['width'] = new svg.Property('width', this.attribute('markerWidth').value);
+				tempSvg.attributes['height'] = new svg.Property('height', this.attribute('markerHeight').value);
+				tempSvg.attributes['fill'] = new svg.Property('fill', this.attribute('fill').valueOrDefault('black'));
+				tempSvg.attributes['stroke'] = new svg.Property('stroke', this.attribute('stroke').valueOrDefault('none'));
+				tempSvg.children = this.children;
+				tempSvg.render(ctx);
+				
+				ctx.restore();
+				if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(1/ctx.lineWidth, 1/ctx.lineWidth);
+				if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(-angle);
+				ctx.translate(-point.x, -point.y);
+			}
+		}
+		svg.Element.marker.prototype = new svg.Element.ElementBase;
+		
+		// definitions element
+		svg.Element.defs = function(node) {
+			this.base = svg.Element.ElementBase;
+			this.base(node);	
+			
+			this.render = function(ctx) {
+				// NOOP
+			}
+		}
+		svg.Element.defs.prototype = new svg.Element.ElementBase;
+		
+		// base for gradients
+		svg.Element.GradientBase = function(node) {
+			this.base = svg.Element.ElementBase;
+			this.base(node);
+			
+			this.gradientUnits = this.attribute('gradientUnits').valueOrDefault('objectBoundingBox');
+			
+			this.stops = [];			
+			for (var i=0; i<this.children.length; i++) {
+				var child = this.children[i];
+				if (child.type == 'stop') this.stops.push(child);
+			}	
+			
+			this.getGradient = function() {
+				// OVERRIDE ME!
+			}			
+
+			this.createGradient = function(ctx, element, parentOpacityProp) {
+				var stopsContainer = this;
+				if (this.getHrefAttribute().hasValue()) {
+					stopsContainer = this.getHrefAttribute().getDefinition();
+				}
+				
+				var addParentOpacity = function (color) {
+					if (parentOpacityProp.hasValue()) {
+						var p = new svg.Property('color', color);
+						return p.addOpacity(parentOpacityProp).value;
+					}
+					return color;
+				};
+			
+				var g = this.getGradient(ctx, element);
+				if (g == null) return addParentOpacity(stopsContainer.stops[stopsContainer.stops.length - 1].color);
+				for (var i=0; i<stopsContainer.stops.length; i++) {
+					g.addColorStop(stopsContainer.stops[i].offset, addParentOpacity(stopsContainer.stops[i].color));
+				}
+				
+				if (this.attribute('gradientTransform').hasValue()) {
+					// render as transformed pattern on temporary canvas
+					var rootView = svg.ViewPort.viewPorts[0];
+					
+					var rect = new svg.Element.rect();
+					rect.attributes['x'] = new svg.Property('x', -svg.MAX_VIRTUAL_PIXELS/3.0);
+					rect.attributes['y'] = new svg.Property('y', -svg.MAX_VIRTUAL_PIXELS/3.0);
+					rect.attributes['width'] = new svg.Property('width', svg.MAX_VIRTUAL_PIXELS);
+					rect.attributes['height'] = new svg.Property('height', svg.MAX_VIRTUAL_PIXELS);
+					
+					var group = new svg.Element.g();
+					group.attributes['transform'] = new svg.Property('transform', this.attribute('gradientTransform').value);
+					group.children = [ rect ];
+					
+					var tempSvg = new svg.Element.svg();
+					tempSvg.attributes['x'] = new svg.Property('x', 0);
+					tempSvg.attributes['y'] = new svg.Property('y', 0);
+					tempSvg.attributes['width'] = new svg.Property('width', rootView.width);
+					tempSvg.attributes['height'] = new svg.Property('height', rootView.height);
+					tempSvg.children = [ group ];
+					
+					var c = document.createElement('canvas');
+					c.width = rootView.width;
+					c.height = rootView.height;
+					var tempCtx = c.getContext('2d');
+					tempCtx.fillStyle = g;
+					tempSvg.render(tempCtx);		
+					return tempCtx.createPattern(c, 'no-repeat');
+				}
+				
+				return g;				
+			}
+		}
+		svg.Element.GradientBase.prototype = new svg.Element.ElementBase;
+		
+		// linear gradient element
+		svg.Element.linearGradient = function(node) {
+			this.base = svg.Element.GradientBase;
+			this.base(node);
+			
+			this.getGradient = function(ctx, element) {
+				var bb = this.gradientUnits == 'objectBoundingBox' ? element.getBoundingBox() : null;
+				
+				if (!this.attribute('x1').hasValue()
+				 && !this.attribute('y1').hasValue()
+				 && !this.attribute('x2').hasValue()
+				 && !this.attribute('y2').hasValue()) {
+					this.attribute('x1', true).value = 0;
+					this.attribute('y1', true).value = 0;
+					this.attribute('x2', true).value = 1;
+					this.attribute('y2', true).value = 0;
+				 }
+				
+				var x1 = (this.gradientUnits == 'objectBoundingBox' 
+					? bb.x() + bb.width() * this.attribute('x1').numValue() 
+					: this.attribute('x1').toPixels('x'));
+				var y1 = (this.gradientUnits == 'objectBoundingBox' 
+					? bb.y() + bb.height() * this.attribute('y1').numValue()
+					: this.attribute('y1').toPixels('y'));
+				var x2 = (this.gradientUnits == 'objectBoundingBox' 
+					? bb.x() + bb.width() * this.attribute('x2').numValue()
+					: this.attribute('x2').toPixels('x'));
+				var y2 = (this.gradientUnits == 'objectBoundingBox' 
+					? bb.y() + bb.height() * this.attribute('y2').numValue()
+					: this.attribute('y2').toPixels('y'));
+
+				if (x1 == x2 && y1 == y2) return null;
+				return ctx.createLinearGradient(x1, y1, x2, y2);
+			}
+		}
+		svg.Element.linearGradient.prototype = new svg.Element.GradientBase;
+		
+		// radial gradient element
+		svg.Element.radialGradient = function(node) {
+			this.base = svg.Element.GradientBase;
+			this.base(node);
+			
+			this.getGradient = function(ctx, element) {
+				var bb = element.getBoundingBox();
+				
+				if (!this.attribute('cx').hasValue()) this.attribute('cx', true).value = '50%';
+				if (!this.attribute('cy').hasValue()) this.attribute('cy', true).value = '50%';
+				if (!this.attribute('r').hasValue()) this.attribute('r', true).value = '50%';
+				
+				var cx = (this.gradientUnits == 'objectBoundingBox' 
+					? bb.x() + bb.width() * this.attribute('cx').numValue() 
+					: this.attribute('cx').toPixels('x'));
+				var cy = (this.gradientUnits == 'objectBoundingBox' 
+					? bb.y() + bb.height() * this.attribute('cy').numValue() 
+					: this.attribute('cy').toPixels('y'));
+				
+				var fx = cx;
+				var fy = cy;
+				if (this.attribute('fx').hasValue()) {
+					fx = (this.gradientUnits == 'objectBoundingBox' 
+					? bb.x() + bb.width() * this.attribute('fx').numValue() 
+					: this.attribute('fx').toPixels('x'));
+				}
+				if (this.attribute('fy').hasValue()) {
+					fy = (this.gradientUnits == 'objectBoundingBox' 
+					? bb.y() + bb.height() * this.attribute('fy').numValue() 
+					: this.attribute('fy').toPixels('y'));
+				}
+				
+				var r = (this.gradientUnits == 'objectBoundingBox' 
+					? (bb.width() + bb.height()) / 2.0 * this.attribute('r').numValue()
+					: this.attribute('r').toPixels());
+				
+				return ctx.createRadialGradient(fx, fy, 0, cx, cy, r);
+			}
+		}
+		svg.Element.radialGradient.prototype = new svg.Element.GradientBase;
+		
+		// gradient stop element
+		svg.Element.stop = function(node) {
+			this.base = svg.Element.ElementBase;
+			this.base(node);
+			
+			this.offset = this.attribute('offset').numValue();
+			if (this.offset < 0) this.offset = 0;
+			if (this.offset > 1) this.offset = 1;
+			
+			var stopColor = this.style('stop-color');
+			if (this.style('stop-opacity').hasValue()) stopColor = stopColor.addOpacity(this.style('stop-opacity'));
+			this.color = stopColor.value;
+		}
+		svg.Element.stop.prototype = new svg.Element.ElementBase;
+		
+		// animation base element
+		svg.Element.AnimateBase = function(node) {
+			this.base = svg.Element.ElementBase;
+			this.base(node);
+			
+			svg.Animations.push(this);
+			
+			this.duration = 0.0;
+			this.begin = this.attribute('begin').toMilliseconds();
+			this.maxDuration = this.begin + this.attribute('dur').toMilliseconds();
+			
+			this.getProperty = function() {
+				var attributeType = this.attribute('attributeType').value;
+				var attributeName = this.attribute('attributeName').value;
+				
+				if (attributeType == 'CSS') {
+					return this.parent.style(attributeName, true);
+				}
+				return this.parent.attribute(attributeName, true);			
+			};
+			
+			this.initialValue = null;
+			this.initialUnits = '';
+			this.removed = false;		
+
+			this.calcValue = function() {
+				// OVERRIDE ME!
+				return '';
+			}
+					
+			this.update = function(delta) {	
+				// set initial value
+				if (this.initialValue == null) {
+					this.initialValue = this.getProperty().value;
+					this.initialUnits = this.getProperty().getUnits();
+				}
+			
+				// if we're past the end time
+				if (this.duration > this.maxDuration) {
+					// loop for indefinitely repeating animations
+					if (this.attribute('repeatCount').value == 'indefinite'
+					 || this.attribute('repeatDur').value == 'indefinite') {
+						this.duration = 0.0
+					}
+					else if (this.attribute('fill').valueOrDefault('remove') == 'freeze' && !this.frozen) {
+						this.frozen = true;
+						this.parent.animationFrozen = true;
+						this.parent.animationFrozenValue = this.getProperty().value;
+					}
+					else if (this.attribute('fill').valueOrDefault('remove') == 'remove' && !this.removed) {
+						this.removed = true;
+						this.getProperty().value = this.parent.animationFrozen ? this.parent.animationFrozenValue : this.initialValue;
+						return true;
+					}
+					return false;
+				}			
+				this.duration = this.duration + delta;
+			
+				// if we're past the begin time
+				var updated = false;
+				if (this.begin < this.duration) {
+					var newValue = this.calcValue(); // tween
+					
+					if (this.attribute('type').hasValue()) {
+						// for transform, etc.
+						var type = this.attribute('type').value;
+						newValue = type + '(' + newValue + ')';
+					}
+					
+					this.getProperty().value = newValue;
+					updated = true;
+				}
+				
+				return updated;
+			}
+			
+			this.from = this.attribute('from');
+			this.to = this.attribute('to');
+			this.values = this.attribute('values');
+			if (this.values.hasValue()) this.values.value = this.values.value.split(';');
+			
+			// fraction of duration we've covered
+			this.progress = function() {
+				var ret = { progress: (this.duration - this.begin) / (this.maxDuration - this.begin) };
+				if (this.values.hasValue()) {
+					var p = ret.progress * (this.values.value.length - 1);
+					var lb = Math.floor(p), ub = Math.ceil(p);
+					ret.from = new svg.Property('from', parseFloat(this.values.value[lb]));
+					ret.to = new svg.Property('to', parseFloat(this.values.value[ub]));
+					ret.progress = (p - lb) / (ub - lb);
+				}
+				else {
+					ret.from = this.from;
+					ret.to = this.to;
+				}
+				return ret;
+			}			
+		}
+		svg.Element.AnimateBase.prototype = new svg.Element.ElementBase;
+		
+		// animate element
+		svg.Element.animate = function(node) {
+			this.base = svg.Element.AnimateBase;
+			this.base(node);
+			
+			this.calcValue = function() {
+				var p = this.progress();
+				
+				// tween value linearly
+				var newValue = p.from.numValue() + (p.to.numValue() - p.from.numValue()) * p.progress; 
+				return newValue + this.initialUnits;
+			};
+		}
+		svg.Element.animate.prototype = new svg.Element.AnimateBase;
+			
+		// animate color element
+		svg.Element.animateColor = function(node) {
+			this.base = svg.Element.AnimateBase;
+			this.base(node);
+
+			this.calcValue = function() {
+				var p = this.progress();
+				var from = new RGBColor(p.from.value);
+				var to = new RGBColor(p.to.value);
+				
+				if (from.ok && to.ok) {
+					// tween color linearly
+					var r = from.r + (to.r - from.r) * p.progress;
+					var g = from.g + (to.g - from.g) * p.progress;
+					var b = from.b + (to.b - from.b) * p.progress;
+					return 'rgb('+parseInt(r,10)+','+parseInt(g,10)+','+parseInt(b,10)+')';
+				}
+				return this.attribute('from').value;
+			};
+		}
+		svg.Element.animateColor.prototype = new svg.Element.AnimateBase;
+		
+		// animate transform element
+		svg.Element.animateTransform = function(node) {
+			this.base = svg.Element.AnimateBase;
+			this.base(node);
+			
+			this.calcValue = function() {
+				var p = this.progress();
+				
+				// tween value linearly
+				var from = svg.ToNumberArray(p.from.value);
+				var to = svg.ToNumberArray(p.to.value);
+				var newValue = '';
+				for (var i=0; i<from.length; i++) {
+					newValue += from[i] + (to[i] - from[i]) * p.progress + ' ';
+				}
+				return newValue;
+			};
+		}
+		svg.Element.animateTransform.prototype = new svg.Element.animate;
+		
+		// font element
+		svg.Element.font = function(node) {
+			this.base = svg.Element.ElementBase;
+			this.base(node);
+
+			this.horizAdvX = this.attribute('horiz-adv-x').numValue();			
+			
+			this.isRTL = false;
+			this.isArabic = false;
+			this.fontFace = null;
+			this.missingGlyph = null;
+			this.glyphs = [];			
+			for (var i=0; i<this.children.length; i++) {
+				var child = this.children[i];
+				if (child.type == 'font-face') {
+					this.fontFace = child;
+					if (child.style('font-family').hasValue()) {
+						svg.Definitions[child.style('font-family').value] = this;
+					}
+				}
+				else if (child.type == 'missing-glyph') this.missingGlyph = child;
+				else if (child.type == 'glyph') {
+					if (child.arabicForm != '') {
+						this.isRTL = true;
+						this.isArabic = true;
+						if (typeof(this.glyphs[child.unicode]) == 'undefined') this.glyphs[child.unicode] = [];
+						this.glyphs[child.unicode][child.arabicForm] = child;
+					}
+					else {
+						this.glyphs[child.unicode] = child;
+					}
+				}
+			}	
+		}
+		svg.Element.font.prototype = new svg.Element.ElementBase;
+		
+		// font-face element
+		svg.Element.fontface = function(node) {
+			this.base = svg.Element.ElementBase;
+			this.base(node);	
+			
+			this.ascent = this.attribute('ascent').value;
+			this.descent = this.attribute('descent').value;
+			this.unitsPerEm = this.attribute('units-per-em').numValue();				
+		}
+		svg.Element.fontface.prototype = new svg.Element.ElementBase;
+		
+		// missing-glyph element
+		svg.Element.missingglyph = function(node) {
+			this.base = svg.Element.path;
+			this.base(node);	
+			
+			this.horizAdvX = 0;
+		}
+		svg.Element.missingglyph.prototype = new svg.Element.path;
+		
+		// glyph element
+		svg.Element.glyph = function(node) {
+			this.base = svg.Element.path;
+			this.base(node);	
+			
+			this.horizAdvX = this.attribute('horiz-adv-x').numValue();
+			this.unicode = this.attribute('unicode').value;
+			this.arabicForm = this.attribute('arabic-form').value;
+		}
+		svg.Element.glyph.prototype = new svg.Element.path;
+		
+		// text element
+		svg.Element.text = function(node) {
+			this.captureTextNodes = true;
+			this.base = svg.Element.RenderedElementBase;
+			this.base(node);
+			
+			this.baseSetContext = this.setContext;
+			this.setContext = function(ctx) {
+				this.baseSetContext(ctx);
+				
+				var textBaseline = this.style('dominant-baseline').toTextBaseline();
+				if (textBaseline == null) textBaseline = this.style('alignment-baseline').toTextBaseline();
+				if (textBaseline != null) ctx.textBaseline = textBaseline;
+			}
+			
+			this.getBoundingBox = function () {
+				var x = this.attribute('x').toPixels('x');
+				var y = this.attribute('y').toPixels('y');
+				var fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);
+				return new svg.BoundingBox(x, y - fontSize, x + Math.floor(fontSize * 2.0 / 3.0) * this.children[0].getText().length, y);
+			}
+			
+			this.renderChildren = function(ctx) {
+				this.x = this.attribute('x').toPixels('x');
+				this.y = this.attribute('y').toPixels('y');
+				this.x += this.getAnchorDelta(ctx, this, 0);
+				for (var i=0; i<this.children.length; i++) {
+					this.renderChild(ctx, this, i);
+				}
+			}
+			
+			this.getAnchorDelta = function (ctx, parent, startI) {
+				var textAnchor = this.style('text-anchor').valueOrDefault('start');
+				if (textAnchor != 'start') {
+					var width = 0;
+					for (var i=startI; i<parent.children.length; i++) {
+						var child = parent.children[i];
+						if (i > startI && child.attribute('x').hasValue()) break; // new group
+						width += child.measureTextRecursive(ctx);
+					}
+					return -1 * (textAnchor == 'end' ? width : width / 2.0);
+				}
+				return 0;
+			}
+			
+			this.renderChild = function(ctx, parent, i) {
+				var child = parent.children[i];
+				if (child.attribute('x').hasValue()) {
+					child.x = child.attribute('x').toPixels('x') + this.getAnchorDelta(ctx, parent, i);
+					if (child.attribute('dx').hasValue()) child.x += child.attribute('dx').toPixels('x');
+				}
+				else {
+					if (this.attribute('dx').hasValue()) this.x += this.attribute('dx').toPixels('x');
+					if (child.attribute('dx').hasValue()) this.x += child.attribute('dx').toPixels('x');
+					child.x = this.x;
+				}
+				this.x = child.x + child.measureText(ctx);
+				
+				if (child.attribute('y').hasValue()) {
+					child.y = child.attribute('y').toPixels('y');
+					if (child.attribute('dy').hasValue()) child.y += child.attribute('dy').toPixels('y');
+				}
+				else {
+					if (this.attribute('dy').hasValue()) this.y += this.attribute('dy').toPixels('y');
+					if (child.attribute('dy').hasValue()) this.y += child.attribute('dy').toPixels('y');
+					child.y = this.y;
+				}
+				this.y = child.y;
+				
+				child.render(ctx);
+				
+				for (var i=0; i<child.children.length; i++) {
+					this.renderChild(ctx, child, i);
+				}
+			}
+		}
+		svg.Element.text.prototype = new svg.Element.RenderedElementBase;
+		
+		// text base
+		svg.Element.TextElementBase = function(node) {
+			this.base = svg.Element.RenderedElementBase;
+			this.base(node);
+			
+			this.getGlyph = function(font, text, i) {
+				var c = text[i];
+				var glyph = null;
+				if (font.isArabic) {
+					var arabicForm = 'isolated';
+					if ((i==0 || text[i-1]==' ') && i<text.length-2 && text[i+1]!=' ') arabicForm = 'terminal'; 
+					if (i>0 && text[i-1]!=' ' && i<text.length-2 && text[i+1]!=' ') arabicForm = 'medial';
+					if (i>0 && text[i-1]!=' ' && (i == text.length-1 || text[i+1]==' ')) arabicForm = 'initial';
+					if (typeof(font.glyphs[c]) != 'undefined') {
+						glyph = font.glyphs[c][arabicForm];
+						if (glyph == null && font.glyphs[c].type == 'glyph') glyph = font.glyphs[c];
+					}
+				}
+				else {
+					glyph = font.glyphs[c];
+				}
+				if (glyph == null) glyph = font.missingGlyph;
+				return glyph;
+			}
+			
+			this.renderChildren = function(ctx) {
+				var customFont = this.parent.style('font-family').getDefinition();
+				if (customFont != null) {
+					var fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);
+					var fontStyle = this.parent.style('font-style').valueOrDefault(svg.Font.Parse(svg.ctx.font).fontStyle);
+					var text = this.getText();
+					if (customFont.isRTL) text = text.split("").reverse().join("");
+					
+					var dx = svg.ToNumberArray(this.parent.attribute('dx').value);
+					for (var i=0; i<text.length; i++) {
+						var glyph = this.getGlyph(customFont, text, i);
+						var scale = fontSize / customFont.fontFace.unitsPerEm;
+						ctx.translate(this.x, this.y);
+						ctx.scale(scale, -scale);
+						var lw = ctx.lineWidth;
+						ctx.lineWidth = ctx.lineWidth * customFont.fontFace.unitsPerEm / fontSize;
+						if (fontStyle == 'italic') ctx.transform(1, 0, .4, 1, 0, 0);
+						glyph.render(ctx);
+						if (fontStyle == 'italic') ctx.transform(1, 0, -.4, 1, 0, 0);
+						ctx.lineWidth = lw;
+						ctx.scale(1/scale, -1/scale);
+						ctx.translate(-this.x, -this.y);	
+						
+						this.x += fontSize * (glyph.horizAdvX || customFont.horizAdvX) / customFont.fontFace.unitsPerEm;
+						if (typeof(dx[i]) != 'undefined' && !isNaN(dx[i])) {
+							this.x += dx[i];
+						}
+					}
+					return;
+				}
+			
+				if (ctx.fillStyle != '') ctx.fillText(svg.compressSpaces(this.getText()), this.x, this.y);
+				if (ctx.strokeStyle != '') ctx.strokeText(svg.compressSpaces(this.getText()), this.x, this.y);
+			}
+			
+			this.getText = function() {
+				// OVERRIDE ME
+			}
+			
+			this.measureTextRecursive = function(ctx) {
+				var width = this.measureText(ctx);
+				for (var i=0; i<this.children.length; i++) {
+					width += this.children[i].measureTextRecursive(ctx);
+				}
+				return width;
+			}
+			
+			this.measureText = function(ctx) {
+				var customFont = this.parent.style('font-family').getDefinition();
+				if (customFont != null) {
+					var fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);
+					var measure = 0;
+					var text = this.getText();
+					if (customFont.isRTL) text = text.split("").reverse().join("");
+					var dx = svg.ToNumberArray(this.parent.attribute('dx').value);
+					for (var i=0; i<text.length; i++) {
+						var glyph = this.getGlyph(customFont, text, i);
+						measure += (glyph.horizAdvX || customFont.horizAdvX) * fontSize / customFont.fontFace.unitsPerEm;
+						if (typeof(dx[i]) != 'undefined' && !isNaN(dx[i])) {
+							measure += dx[i];
+						}
+					}
+					return measure;
+				}
+			
+				var textToMeasure = svg.compressSpaces(this.getText());
+				if (!ctx.measureText) return textToMeasure.length * 10;
+				
+				ctx.save();
+				this.setContext(ctx);
+				var width = ctx.measureText(textToMeasure).width;
+				ctx.restore();
+				return width;
+			}
+		}
+		svg.Element.TextElementBase.prototype = new svg.Element.RenderedElementBase;
+		
+		// tspan 
+		svg.Element.tspan = function(node) {
+			this.captureTextNodes = true;
+			this.base = svg.Element.TextElementBase;
+			this.base(node);
+			
+			this.text = node.nodeValue || node.text || '';
+			this.getText = function() {
+				return this.text;
+			}
+		}
+		svg.Element.tspan.prototype = new svg.Element.TextElementBase;
+		
+		// tref
+		svg.Element.tref = function(node) {
+			this.base = svg.Element.TextElementBase;
+			this.base(node);
+			
+			this.getText = function() {
+				var element = this.getHrefAttribute().getDefinition();
+				if (element != null) return element.children[0].getText();
+			}
+		}
+		svg.Element.tref.prototype = new svg.Element.TextElementBase;		
+		
+		// a element
+		svg.Element.a = function(node) {
+			this.base = svg.Element.TextElementBase;
+			this.base(node);
+			
+			this.hasText = true;
+			for (var i=0; i<node.childNodes.length; i++) {
+				if (node.childNodes[i].nodeType != 3) this.hasText = false;
+			}
+			
+			// this might contain text
+			this.text = this.hasText ? node.childNodes[0].nodeValue : '';
+			this.getText = function() {
+				return this.text;
+			}		
+
+			this.baseRenderChildren = this.renderChildren;
+			this.renderChildren = function(ctx) {
+				if (this.hasText) {
+					// render as text element
+					this.baseRenderChildren(ctx);
+					var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);
+					svg.Mouse.checkBoundingBox(this, new svg.BoundingBox(this.x, this.y - fontSize.toPixels('y'), this.x + this.measureText(ctx), this.y));					
+				}
+				else {
+					// render as temporary group
+					var g = new svg.Element.g();
+					g.children = this.children;
+					g.parent = this;
+					g.render(ctx);
+				}
+			}
+			
+			this.onclick = function() {
+				window.open(this.getHrefAttribute().value);
+			}
+			
+			this.onmousemove = function() {
+				svg.ctx.canvas.style.cursor = 'pointer';
+			}
+		}
+		svg.Element.a.prototype = new svg.Element.TextElementBase;		
+		
+		// image element
+		svg.Element.image = function(node) {
+			this.base = svg.Element.RenderedElementBase;
+			this.base(node);
+			
+			var href = this.getHrefAttribute().value;
+			if (href === '') {
+				return;
+			}
+			var isSvg = href.match(/\.svg$/)
+			
+			svg.Images.push(this);
+			this.loaded = false;
+			if (!isSvg) {
+				this.img = document.createElement('img');
+				if (svg.opts['useCORS'] == true) { this.img.crossOrigin = 'Anonymous'; }
+				var self = this;
+				this.img.onload = function() { self.loaded = true; }
+				this.img.onerror = function() { svg.log('ERROR: image "' + href + '" not found'); self.loaded = true; }
+				this.img.src = href;
+			}
+			else {
+				this.img = svg.ajax(href);
+				this.loaded = true;
+			}
+			
+			this.renderChildren = function(ctx) {
+				var x = this.attribute('x').toPixels('x');
+				var y = this.attribute('y').toPixels('y');
+				
+				var width = this.attribute('width').toPixels('x');
+				var height = this.attribute('height').toPixels('y');			
+				if (width == 0 || height == 0) return;
+			
+				ctx.save();
+				if (isSvg) {
+					ctx.drawSvg(this.img, x, y, width, height);
+				}
+				else {
+					ctx.translate(x, y);
+					svg.AspectRatio(ctx,
+									this.attribute('preserveAspectRatio').value,
+									width,
+									this.img.width,
+									height,
+									this.img.height,
+									0,
+									0);	
+					ctx.drawImage(this.img, 0, 0);		
+				}
+				ctx.restore();
+			}
+			
+			this.getBoundingBox = function() {
+				var x = this.attribute('x').toPixels('x');
+				var y = this.attribute('y').toPixels('y');
+				var width = this.attribute('width').toPixels('x');
+				var height = this.attribute('height').toPixels('y');
+				return new svg.BoundingBox(x, y, x + width, y + height);
+			}
+		}
+		svg.Element.image.prototype = new svg.Element.RenderedElementBase;
+		
+		// group element
+		svg.Element.g = function(node) {
+			this.base = svg.Element.RenderedElementBase;
+			this.base(node);
+			
+			this.getBoundingBox = function() {
+				var bb = new svg.BoundingBox();
+				for (var i=0; i<this.children.length; i++) {
+					bb.addBoundingBox(this.children[i].getBoundingBox());
+				}
+				return bb;
+			};
+		}
+		svg.Element.g.prototype = new svg.Element.RenderedElementBase;
+
+		// symbol element
+		svg.Element.symbol = function(node) {
+			this.base = svg.Element.RenderedElementBase;
+			this.base(node);
+
+			this.render = function(ctx) {
+				// NO RENDER
+			};
+		}
+		svg.Element.symbol.prototype = new svg.Element.RenderedElementBase;		
+			
+		// style element
+		svg.Element.style = function(node) { 
+			this.base = svg.Element.ElementBase;
+			this.base(node);
+			
+			// text, or spaces then CDATA
+			var css = ''
+			for (var i=0; i<node.childNodes.length; i++) {
+			  css += node.childNodes[i].nodeValue;
+			}
+			css = css.replace(/(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(^[\s]*\/\/.*)/gm, ''); // remove comments
+			css = svg.compressSpaces(css); // replace whitespace
+			var cssDefs = css.split('}');
+			for (var i=0; i<cssDefs.length; i++) {
+				if (svg.trim(cssDefs[i]) != '') {
+					var cssDef = cssDefs[i].split('{');
+					var cssClasses = cssDef[0].split(',');
+					var cssProps = cssDef[1].split(';');
+					for (var j=0; j<cssClasses.length; j++) {
+						var cssClass = svg.trim(cssClasses[j]);
+						if (cssClass != '') {
+							var props = {};
+							for (var k=0; k<cssProps.length; k++) {
+								var prop = cssProps[k].indexOf(':');
+								var name = cssProps[k].substr(0, prop);
+								var value = cssProps[k].substr(prop + 1, cssProps[k].length - prop);
+								if (name != null && value != null) {
+									props[svg.trim(name)] = new svg.Property(svg.trim(name), svg.trim(value));
+								}
+							}
+							svg.Styles[cssClass] = props;
+							if (cssClass == '@font-face') {
+								var fontFamily = props['font-family'].value.replace(/"/g,'');
+								var srcs = props['src'].value.split(',');
+								for (var s=0; s<srcs.length; s++) {
+									if (srcs[s].indexOf('format("svg")') > 0) {
+										var urlStart = srcs[s].indexOf('url');
+										var urlEnd = srcs[s].indexOf(')', urlStart);
+										var url = srcs[s].substr(urlStart + 5, urlEnd - urlStart - 6);
+										var doc = svg.parseXml(svg.ajax(url));
+										var fonts = doc.getElementsByTagName('font');
+										for (var f=0; f<fonts.length; f++) {
+											var font = svg.CreateElement(fonts[f]);
+											svg.Definitions[fontFamily] = font;
+										}
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+		svg.Element.style.prototype = new svg.Element.ElementBase;
+		
+		// use element 
+		svg.Element.use = function(node) {
+			this.base = svg.Element.RenderedElementBase;
+			this.base(node);
+			
+			this.baseSetContext = this.setContext;
+			this.setContext = function(ctx) {
+				this.baseSetContext(ctx);
+				if (this.attribute('x').hasValue()) ctx.translate(this.attribute('x').toPixels('x'), 0);
+				if (this.attribute('y').hasValue()) ctx.translate(0, this.attribute('y').toPixels('y'));
+			}
+			
+			var element = this.getHrefAttribute().getDefinition();
+			
+			this.path = function(ctx) {
+				if (element != null) element.path(ctx);
+			}
+			
+			this.getBoundingBox = function() {
+				if (element != null) return element.getBoundingBox();
+			}
+			
+			this.renderChildren = function(ctx) {
+				if (element != null) {
+					var tempSvg = element;
+					if (element.type == 'symbol') {
+						// render me using a temporary svg element in symbol cases (http://www.w3.org/TR/SVG/struct.html#UseElement)
+						tempSvg = new svg.Element.svg();
+						tempSvg.type = 'svg';
+						tempSvg.attributes['viewBox'] = new svg.Property('viewBox', element.attribute('viewBox').value);
+						tempSvg.attributes['preserveAspectRatio'] = new svg.Property('preserveAspectRatio', element.attribute('preserveAspectRatio').value);
+						tempSvg.attributes['overflow'] = new svg.Property('overflow', element.attribute('overflow').value);
+						tempSvg.children = element.children;
+					}
+					if (tempSvg.type == 'svg') {
+						// if symbol or svg, inherit width/height from me
+						if (this.attribute('width').hasValue()) tempSvg.attributes['width'] = new svg.Property('width', this.attribute('width').value);
+						if (this.attribute('height').hasValue()) tempSvg.attributes['height'] = new svg.Property('height', this.attribute('height').value);
+					}
+					var oldParent = tempSvg.parent;
+					tempSvg.parent = null;
+					tempSvg.render(ctx);
+					tempSvg.parent = oldParent;
+				}
+			}
+		}
+		svg.Element.use.prototype = new svg.Element.RenderedElementBase;
+		
+		// mask element
+		svg.Element.mask = function(node) {
+			this.base = svg.Element.ElementBase;
+			this.base(node);
+						
+			this.apply = function(ctx, element) {
+				// render as temp svg	
+				var x = this.attribute('x').toPixels('x');
+				var y = this.attribute('y').toPixels('y');
+				var width = this.attribute('width').toPixels('x');
+				var height = this.attribute('height').toPixels('y');
+				
+				if (width == 0 && height == 0) {
+					var bb = new svg.BoundingBox();
+					for (var i=0; i<this.children.length; i++) {
+						bb.addBoundingBox(this.children[i].getBoundingBox());
+					}
+					var x = Math.floor(bb.x1);
+					var y = Math.floor(bb.y1);
+					var width = Math.floor(bb.width());
+					var	height = Math.floor(bb.height());
+				}
+				
+				// temporarily remove mask to avoid recursion
+				var mask = element.attribute('mask').value;
+				element.attribute('mask').value = '';
+				
+					var cMask = document.createElement('canvas');
+					cMask.width = x + width;
+					cMask.height = y + height;
+					var maskCtx = cMask.getContext('2d');
+					this.renderChildren(maskCtx);
+				
+					var c = document.createElement('canvas');
+					c.width = x + width;
+					c.height = y + height;
+					var tempCtx = c.getContext('2d');
+					element.render(tempCtx);
+					tempCtx.globalCompositeOperation = 'destination-in';
+					tempCtx.fillStyle = maskCtx.createPattern(cMask, 'no-repeat');
+					tempCtx.fillRect(0, 0, x + width, y + height);
+					
+					ctx.fillStyle = tempCtx.createPattern(c, 'no-repeat');
+					ctx.fillRect(0, 0, x + width, y + height);
+					
+				// reassign mask
+				element.attribute('mask').value = mask;	
+			}
+			
+			this.render = function(ctx) {
+				// NO RENDER
+			}
+		}
+		svg.Element.mask.prototype = new svg.Element.ElementBase;
+		
+		// clip element
+		svg.Element.clipPath = function(node) {
+			this.base = svg.Element.ElementBase;
+			this.base(node);
+			
+			this.apply = function(ctx) {
+				for (var i=0; i<this.children.length; i++) {
+					var child = this.children[i];
+					if (typeof(child.path) != 'undefined') {
+						var transform = null;
+						if (child.attribute('transform').hasValue()) { 
+							transform = new svg.Transform(child.attribute('transform').value);
+							transform.apply(ctx);
+						}
+						child.path(ctx);
+						ctx.clip();
+						if (transform) { transform.unapply(ctx); }
+					}
+				}
+			}
+			
+			this.render = function(ctx) {
+				// NO RENDER
+			}
+		}
+		svg.Element.clipPath.prototype = new svg.Element.ElementBase;
+
+		// filters
+		svg.Element.filter = function(node) {
+			this.base = svg.Element.ElementBase;
+			this.base(node);
+						
+			this.apply = function(ctx, element) {
+				// render as temp svg	
+				var bb = element.getBoundingBox();
+				var x = Math.floor(bb.x1);
+				var y = Math.floor(bb.y1);
+				var width = Math.floor(bb.width());
+				var	height = Math.floor(bb.height());
+
+				// temporarily remove filter to avoid recursion
+				var filter = element.style('filter').value;
+				element.style('filter').value = '';
+				
+				var px = 0, py = 0;
+				for (var i=0; i<this.children.length; i++) {
+					var efd = this.children[i].extraFilterDistance || 0;
+					px = Math.max(px, efd);
+					py = Math.max(py, efd);
+				}
+				
+				var c = document.createElement('canvas');
+				c.width = width + 2*px;
+				c.height = height + 2*py;
+				var tempCtx = c.getContext('2d');
+				tempCtx.translate(-x + px, -y + py);
+				element.render(tempCtx);
+			
+				// apply filters
+				for (var i=0; i<this.children.length; i++) {
+					this.children[i].apply(tempCtx, 0, 0, width + 2*px, height + 2*py);
+				}
+				
+				// render on me
+				ctx.drawImage(c, 0, 0, width + 2*px, height + 2*py, x - px, y - py, width + 2*px, height + 2*py);
+				
+				// reassign filter
+				element.style('filter', true).value = filter;	
+			}
+			
+			this.render = function(ctx) {
+				// NO RENDER
+			}		
+		}
+		svg.Element.filter.prototype = new svg.Element.ElementBase;
+		
+		svg.Element.feMorphology = function(node) {
+			this.base = svg.Element.ElementBase;
+			this.base(node);
+			
+			this.apply = function(ctx, x, y, width, height) {
+				// TODO: implement
+			}
+		}
+		svg.Element.feMorphology.prototype = new svg.Element.ElementBase;
+		
+		svg.Element.feComposite = function(node) {
+			this.base = svg.Element.ElementBase;
+			this.base(node);
+			
+			this.apply = function(ctx, x, y, width, height) {
+				// TODO: implement
+			}
+		}
+		svg.Element.feComposite.prototype = new svg.Element.ElementBase;
+		
+		svg.Element.feColorMatrix = function(node) {
+			this.base = svg.Element.ElementBase;
+			this.base(node);
+			
+			var matrix = svg.ToNumberArray(this.attribute('values').value);
+			switch (this.attribute('type').valueOrDefault('matrix')) { // http://www.w3.org/TR/SVG/filters.html#feColorMatrixElement
+				case 'saturate':
+					var s = matrix[0];
+					matrix = [0.213+0.787*s,0.715-0.715*s,0.072-0.072*s,0,0,
+							  0.213-0.213*s,0.715+0.285*s,0.072-0.072*s,0,0,
+							  0.213-0.213*s,0.715-0.715*s,0.072+0.928*s,0,0,
+							  0,0,0,1,0,
+							  0,0,0,0,1];
+					break;
+				case 'hueRotate':
+					var a = matrix[0] * Math.PI / 180.0;
+					var c = function (m1,m2,m3) { return m1 + Math.cos(a)*m2 + Math.sin(a)*m3; };
+					matrix = [c(0.213,0.787,-0.213),c(0.715,-0.715,-0.715),c(0.072,-0.072,0.928),0,0,
+							  c(0.213,-0.213,0.143),c(0.715,0.285,0.140),c(0.072,-0.072,-0.283),0,0,
+							  c(0.213,-0.213,-0.787),c(0.715,-0.715,0.715),c(0.072,0.928,0.072),0,0,
+							  0,0,0,1,0,
+							  0,0,0,0,1];
+					break;
+				case 'luminanceToAlpha':
+					matrix = [0,0,0,0,0,
+							  0,0,0,0,0,
+							  0,0,0,0,0,
+							  0.2125,0.7154,0.0721,0,0,
+							  0,0,0,0,1];
+					break;
+			}
+			
+			function imGet(img, x, y, width, height, rgba) {
+				return img[y*width*4 + x*4 + rgba];
+			}
+			
+			function imSet(img, x, y, width, height, rgba, val) {
+				img[y*width*4 + x*4 + rgba] = val;
+			}
+			
+			function m(i, v) {
+				var mi = matrix[i];
+				return mi * (mi < 0 ? v - 255 : v);
+			}
+						
+			this.apply = function(ctx, x, y, width, height) {
+				// assuming x==0 && y==0 for now
+				var srcData = ctx.getImageData(0, 0, width, height);
+				for (var y = 0; y < height; y++) {
+					for (var x = 0; x < width; x++) {
+						var r = imGet(srcData.data, x, y, width, height, 0);
+						var g = imGet(srcData.data, x, y, width, height, 1);
+						var b = imGet(srcData.data, x, y, width, height, 2);
+						var a = imGet(srcData.data, x, y, width, height, 3);
+						imSet(srcData.data, x, y, width, height, 0, m(0,r)+m(1,g)+m(2,b)+m(3,a)+m(4,1));
+						imSet(srcData.data, x, y, width, height, 1, m(5,r)+m(6,g)+m(7,b)+m(8,a)+m(9,1));
+						imSet(srcData.data, x, y, width, height, 2, m(10,r)+m(11,g)+m(12,b)+m(13,a)+m(14,1));
+						imSet(srcData.data, x, y, width, height, 3, m(15,r)+m(16,g)+m(17,b)+m(18,a)+m(19,1));
+					}
+				}
+				ctx.clearRect(0, 0, width, height);
+				ctx.putImageData(srcData, 0, 0);
+			}
+		}
+		svg.Element.feColorMatrix.prototype = new svg.Element.ElementBase;
+		
+		svg.Element.feGaussianBlur = function(node) {
+			this.base = svg.Element.ElementBase;
+			this.base(node);
+
+			this.blurRadius = Math.floor(this.attribute('stdDeviation').numValue());
+			this.extraFilterDistance = this.blurRadius;
+			
+			this.apply = function(ctx, x, y, width, height) {
+				if (typeof(stackBlurCanvasRGBA) == 'undefined') {
+					svg.log('ERROR: StackBlur.js must be included for blur to work');
+					return;
+				}
+				
+				// StackBlur requires canvas be on document
+				ctx.canvas.id = svg.UniqueId();
+				ctx.canvas.style.display = 'none';
+				document.body.appendChild(ctx.canvas);
+				stackBlurCanvasRGBA(ctx.canvas.id, x, y, width, height, this.blurRadius);
+				document.body.removeChild(ctx.canvas);
+			}
+		}
+		svg.Element.feGaussianBlur.prototype = new svg.Element.ElementBase;
+		
+		// title element, do nothing
+		svg.Element.title = function(node) {
+		}
+		svg.Element.title.prototype = new svg.Element.ElementBase;
+
+		// desc element, do nothing
+		svg.Element.desc = function(node) {
+		}
+		svg.Element.desc.prototype = new svg.Element.ElementBase;		
+		
+		svg.Element.MISSING = function(node) {
+			svg.log('ERROR: Element \'' + node.nodeName + '\' not yet implemented.');
+		}
+		svg.Element.MISSING.prototype = new svg.Element.ElementBase;
+		
+		// element factory
+		svg.CreateElement = function(node) {	
+			var className = node.nodeName.replace(/^[^:]+:/,''); // remove namespace
+			className = className.replace(/\-/g,''); // remove dashes
+			var e = null;
+			if (typeof(svg.Element[className]) != 'undefined') {
+				e = new svg.Element[className](node);
+			}
+			else {
+				e = new svg.Element.MISSING(node);
+			}
+
+			e.type = node.nodeName;
+			return e;
+		}
+				
+		// load from url
+		svg.load = function(ctx, url) {
+			svg.loadXml(ctx, svg.ajax(url));
+		}
+		
+		// load from xml
+		svg.loadXml = function(ctx, xml) {
+			svg.loadXmlDoc(ctx, svg.parseXml(xml));
+		}
+		
+		svg.loadXmlDoc = function(ctx, dom) {
+			svg.init(ctx);
+			
+			var mapXY = function(p) {
+				var e = ctx.canvas;
+				while (e) {
+					p.x -= e.offsetLeft;
+					p.y -= e.offsetTop;
+					e = e.offsetParent;
+				}
+				if (window.scrollX) p.x += window.scrollX;
+				if (window.scrollY) p.y += window.scrollY;
+				return p;
+			}
+			
+			// bind mouse
+			if (svg.opts['ignoreMouse'] != true) {
+				ctx.canvas.onclick = function(e) {
+					var p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));
+					svg.Mouse.onclick(p.x, p.y);
+				};
+				ctx.canvas.onmousemove = function(e) {
+					var p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));
+					svg.Mouse.onmousemove(p.x, p.y);
+				};
+			}
+		
+			var e = svg.CreateElement(dom.documentElement);
+			e.root = true;
+					
+			// render loop
+			var isFirstRender = true;
+			var draw = function() {
+				svg.ViewPort.Clear();
+				if (ctx.canvas.parentNode) svg.ViewPort.SetCurrent(ctx.canvas.parentNode.clientWidth, ctx.canvas.parentNode.clientHeight);
+			
+				if (svg.opts['ignoreDimensions'] != true) {
+					// set canvas size
+					if (e.style('width').hasValue()) {
+						ctx.canvas.width = e.style('width').toPixels('x');
+						ctx.canvas.style.width = ctx.canvas.width + 'px';
+					}
+					if (e.style('height').hasValue()) {
+						ctx.canvas.height = e.style('height').toPixels('y');
+						ctx.canvas.style.height = ctx.canvas.height + 'px';
+					}
+				}
+				var cWidth = ctx.canvas.clientWidth || ctx.canvas.width;
+				var cHeight = ctx.canvas.clientHeight || ctx.canvas.height;
+				if (svg.opts['ignoreDimensions'] == true && e.style('width').hasValue() && e.style('height').hasValue()) {
+					cWidth = e.style('width').toPixels('x');
+					cHeight = e.style('height').toPixels('y');
+				}
+				svg.ViewPort.SetCurrent(cWidth, cHeight);		
+				
+				if (svg.opts['offsetX'] != null) e.attribute('x', true).value = svg.opts['offsetX'];
+				if (svg.opts['offsetY'] != null) e.attribute('y', true).value = svg.opts['offsetY'];
+				if (svg.opts['scaleWidth'] != null || svg.opts['scaleHeight'] != null) {
+					var xRatio = null, yRatio = null, viewBox = svg.ToNumberArray(e.attribute('viewBox').value);
+					
+					if (svg.opts['scaleWidth'] != null) {
+						if (e.attribute('width').hasValue()) xRatio = e.attribute('width').toPixels('x') / svg.opts['scaleWidth'];
+						else if (!isNaN(viewBox[2])) xRatio = viewBox[2] / svg.opts['scaleWidth'];
+					}
+					
+					if (svg.opts['scaleHeight'] != null) {
+						if (e.attribute('height').hasValue()) yRatio = e.attribute('height').toPixels('y') / svg.opts['scaleHeight'];
+						else if (!isNaN(viewBox[3])) yRatio = viewBox[3] / svg.opts['scaleHeight'];
+					}
+
+					if (xRatio == null) { xRatio = yRatio; }
+					if (yRatio == null) { yRatio = xRatio; }
+					
+					e.attribute('width', true).value = svg.opts['scaleWidth'];
+					e.attribute('height', true).value = svg.opts['scaleHeight'];			
+					e.attribute('viewBox', true).value = '0 0 ' + (cWidth * xRatio) + ' ' + (cHeight * yRatio);
+					e.attribute('preserveAspectRatio', true).value = 'none';
+				}
+			
+				// clear and render
+				if (svg.opts['ignoreClear'] != true) {
+					ctx.clearRect(0, 0, cWidth, cHeight);
+				}
+				e.render(ctx);
+				if (isFirstRender) {
+					isFirstRender = false;
+					if (typeof(svg.opts['renderCallback']) == 'function') svg.opts['renderCallback'](dom);
+				}			
+			}
+			
+			var waitingForImages = true;
+			if (svg.ImagesLoaded()) {
+				waitingForImages = false;
+				draw();
+			}
+			svg.intervalID = setInterval(function() { 
+				var needUpdate = false;
+				
+				if (waitingForImages && svg.ImagesLoaded()) {
+					waitingForImages = false;
+					needUpdate = true;
+				}
+			
+				// need update from mouse events?
+				if (svg.opts['ignoreMouse'] != true) {
+					needUpdate = needUpdate | svg.Mouse.hasEvents();
+				}
+			
+				// need update from animations?
+				if (svg.opts['ignoreAnimation'] != true) {
+					for (var i=0; i<svg.Animations.length; i++) {
+						needUpdate = needUpdate | svg.Animations[i].update(1000 / svg.FRAMERATE);
+					}
+				}
+				
+				// need update from redraw?
+				if (typeof(svg.opts['forceRedraw']) == 'function') {
+					if (svg.opts['forceRedraw']() == true) needUpdate = true;
+				}
+				
+				// render if needed
+				if (needUpdate) {
+					draw();				
+					svg.Mouse.runEvents(); // run and clear our events
+				}
+			}, 1000 / svg.FRAMERATE);
+		}
+		
+		svg.stop = function() {
+			if (svg.intervalID) {
+				clearInterval(svg.intervalID);
+			}
+		}
+		
+		svg.Mouse = new (function() {
+			this.events = [];
+			this.hasEvents = function() { return this.events.length != 0; }
+		
+			this.onclick = function(x, y) {
+				this.events.push({ type: 'onclick', x: x, y: y, 
+					run: function(e) { if (e.onclick) e.onclick(); }
+				});
+			}
+			
+			this.onmousemove = function(x, y) {
+				this.events.push({ type: 'onmousemove', x: x, y: y,
+					run: function(e) { if (e.onmousemove) e.onmousemove(); }
+				});
+			}			
+			
+			this.eventElements = [];
+			
+			this.checkPath = function(element, ctx) {
+				for (var i=0; i<this.events.length; i++) {
+					var e = this.events[i];
+					if (ctx.isPointInPath && ctx.isPointInPath(e.x, e.y)) this.eventElements[i] = element;
+				}
+			}
+			
+			this.checkBoundingBox = function(element, bb) {
+				for (var i=0; i<this.events.length; i++) {
+					var e = this.events[i];
+					if (bb.isPointInBox(e.x, e.y)) this.eventElements[i] = element;
+				}			
+			}
+			
+			this.runEvents = function() {
+				svg.ctx.canvas.style.cursor = '';
+				
+				for (var i=0; i<this.events.length; i++) {
+					var e = this.events[i];
+					var element = this.eventElements[i];
+					while (element) {
+						e.run(element);
+						element = element.parent;
+					}
+				}		
+			
+				// done running, clear
+				this.events = []; 
+				this.eventElements = [];
+			}
+		});
+		
+		return svg;
+	}
+})();
+
+if (typeof(CanvasRenderingContext2D) != 'undefined') {
+	CanvasRenderingContext2D.prototype.drawSvg = function(s, dx, dy, dw, dh) {
+		canvg(this.canvas, s, { 
+			ignoreMouse: true, 
+			ignoreAnimation: true, 
+			ignoreDimensions: true, 
+			ignoreClear: true, 
+			offsetX: dx, 
+			offsetY: dy, 
+			scaleWidth: dw, 
+			scaleHeight: dh
+		});
+	}
+}

+ 294 - 0
main/inc/lib/javascript/svgedit/canvg/rgbcolor.js

@@ -0,0 +1,294 @@
+/*jslint vars: true*/
+/**
+ * A class to parse color values
+ * @author Stoyan Stefanov <sstoo@gmail.com>
+ * @link   http://www.phpied.com/rgb-color-parser-in-javascript/
+ * @license Use it if you like it
+ */
+function RGBColor(color_string) { 'use strict';
+    this.ok = false;
+
+    // strip any leading #
+    if (color_string.charAt(0) === '#') { // remove # if any
+        color_string = color_string.substr(1,6);
+    }
+
+    color_string = color_string.replace(/ /g,'');
+    color_string = color_string.toLowerCase();
+
+    // before getting into regexps, try simple matches
+    // and overwrite the input
+    var simple_colors = {
+        aliceblue: 'f0f8ff',
+        antiquewhite: 'faebd7',
+        aqua: '00ffff',
+        aquamarine: '7fffd4',
+        azure: 'f0ffff',
+        beige: 'f5f5dc',
+        bisque: 'ffe4c4',
+        black: '000000',
+        blanchedalmond: 'ffebcd',
+        blue: '0000ff',
+        blueviolet: '8a2be2',
+        brown: 'a52a2a',
+        burlywood: 'deb887',
+        cadetblue: '5f9ea0',
+        chartreuse: '7fff00',
+        chocolate: 'd2691e',
+        coral: 'ff7f50',
+        cornflowerblue: '6495ed',
+        cornsilk: 'fff8dc',
+        crimson: 'dc143c',
+        cyan: '00ffff',
+        darkblue: '00008b',
+        darkcyan: '008b8b',
+        darkgoldenrod: 'b8860b',
+        darkgray: 'a9a9a9',
+        darkgreen: '006400',
+        darkkhaki: 'bdb76b',
+        darkmagenta: '8b008b',
+        darkolivegreen: '556b2f',
+        darkorange: 'ff8c00',
+        darkorchid: '9932cc',
+        darkred: '8b0000',
+        darksalmon: 'e9967a',
+        darkseagreen: '8fbc8f',
+        darkslateblue: '483d8b',
+        darkslategray: '2f4f4f',
+        darkturquoise: '00ced1',
+        darkviolet: '9400d3',
+        deeppink: 'ff1493',
+        deepskyblue: '00bfff',
+        dimgray: '696969',
+        dodgerblue: '1e90ff',
+        feldspar: 'd19275',
+        firebrick: 'b22222',
+        floralwhite: 'fffaf0',
+        forestgreen: '228b22',
+        fuchsia: 'ff00ff',
+        gainsboro: 'dcdcdc',
+        ghostwhite: 'f8f8ff',
+        gold: 'ffd700',
+        goldenrod: 'daa520',
+        gray: '808080',
+        green: '008000',
+        greenyellow: 'adff2f',
+        honeydew: 'f0fff0',
+        hotpink: 'ff69b4',
+        indianred : 'cd5c5c',
+        indigo : '4b0082',
+        ivory: 'fffff0',
+        khaki: 'f0e68c',
+        lavender: 'e6e6fa',
+        lavenderblush: 'fff0f5',
+        lawngreen: '7cfc00',
+        lemonchiffon: 'fffacd',
+        lightblue: 'add8e6',
+        lightcoral: 'f08080',
+        lightcyan: 'e0ffff',
+        lightgoldenrodyellow: 'fafad2',
+        lightgrey: 'd3d3d3',
+        lightgreen: '90ee90',
+        lightpink: 'ffb6c1',
+        lightsalmon: 'ffa07a',
+        lightseagreen: '20b2aa',
+        lightskyblue: '87cefa',
+        lightslateblue: '8470ff',
+        lightslategray: '778899',
+        lightsteelblue: 'b0c4de',
+        lightyellow: 'ffffe0',
+        lime: '00ff00',
+        limegreen: '32cd32',
+        linen: 'faf0e6',
+        magenta: 'ff00ff',
+        maroon: '800000',
+        mediumaquamarine: '66cdaa',
+        mediumblue: '0000cd',
+        mediumorchid: 'ba55d3',
+        mediumpurple: '9370d8',
+        mediumseagreen: '3cb371',
+        mediumslateblue: '7b68ee',
+        mediumspringgreen: '00fa9a',
+        mediumturquoise: '48d1cc',
+        mediumvioletred: 'c71585',
+        midnightblue: '191970',
+        mintcream: 'f5fffa',
+        mistyrose: 'ffe4e1',
+        moccasin: 'ffe4b5',
+        navajowhite: 'ffdead',
+        navy: '000080',
+        oldlace: 'fdf5e6',
+        olive: '808000',
+        olivedrab: '6b8e23',
+        orange: 'ffa500',
+        orangered: 'ff4500',
+        orchid: 'da70d6',
+        palegoldenrod: 'eee8aa',
+        palegreen: '98fb98',
+        paleturquoise: 'afeeee',
+        palevioletred: 'd87093',
+        papayawhip: 'ffefd5',
+        peachpuff: 'ffdab9',
+        peru: 'cd853f',
+        pink: 'ffc0cb',
+        plum: 'dda0dd',
+        powderblue: 'b0e0e6',
+        purple: '800080',
+        red: 'ff0000',
+        rosybrown: 'bc8f8f',
+        royalblue: '4169e1',
+        saddlebrown: '8b4513',
+        salmon: 'fa8072',
+        sandybrown: 'f4a460',
+        seagreen: '2e8b57',
+        seashell: 'fff5ee',
+        sienna: 'a0522d',
+        silver: 'c0c0c0',
+        skyblue: '87ceeb',
+        slateblue: '6a5acd',
+        slategray: '708090',
+        snow: 'fffafa',
+        springgreen: '00ff7f',
+        steelblue: '4682b4',
+        tan: 'd2b48c',
+        teal: '008080',
+        thistle: 'd8bfd8',
+        tomato: 'ff6347',
+        turquoise: '40e0d0',
+        violet: 'ee82ee',
+        violetred: 'd02090',
+        wheat: 'f5deb3',
+        white: 'ffffff',
+        whitesmoke: 'f5f5f5',
+        yellow: 'ffff00',
+        yellowgreen: '9acd32'
+    };
+    var key;
+    for (key in simple_colors) {
+        if (simple_colors.hasOwnProperty(key)) {
+            if (color_string == key) {
+                color_string = simple_colors[key];
+            }
+        }
+    }
+    // emd of simple type-in colors
+
+    // array of color definition objects
+    var color_defs = [
+        {
+            re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,
+            example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],
+            process: function (bits){
+                return [
+                    parseInt(bits[1], 10),
+                    parseInt(bits[2], 10),
+                    parseInt(bits[3], 10)
+                ];
+            }
+        },
+        {
+            re: /^(\w{2})(\w{2})(\w{2})$/,
+            example: ['#00ff00', '336699'],
+            process: function (bits){
+                return [
+                    parseInt(bits[1], 16),
+                    parseInt(bits[2], 16),
+                    parseInt(bits[3], 16)
+                ];
+            }
+        },
+        {
+            re: /^(\w{1})(\w{1})(\w{1})$/,
+            example: ['#fb0', 'f0f'],
+            process: function (bits){
+                return [
+                    parseInt(bits[1] + bits[1], 16),
+                    parseInt(bits[2] + bits[2], 16),
+                    parseInt(bits[3] + bits[3], 16)
+                ];
+            }
+        }
+    ];
+
+    var i;
+    // search through the definitions to find a match
+    for (i = 0; i < color_defs.length; i++) {
+        var re = color_defs[i].re;
+        var processor = color_defs[i].process;
+        var bits = re.exec(color_string);
+        if (bits) {
+            var channels = processor(bits);
+            this.r = channels[0];
+            this.g = channels[1];
+            this.b = channels[2];
+            this.ok = true;
+        }
+
+    }
+
+    // validate/cleanup values
+    this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r);
+    this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g);
+    this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b);
+
+    // some getters
+    this.toRGB = function () {
+        return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')';
+    };
+    this.toHex = function () {
+        var r = this.r.toString(16);
+        var g = this.g.toString(16);
+        var b = this.b.toString(16);
+        if (r.length === 1) {r = '0' + r;}
+        if (g.length === 1) {g = '0' + g;}
+        if (b.length === 1) {b = '0' + b;}
+        return '#' + r + g + b;
+    };
+
+    // help
+    this.getHelpXML = function () {
+        var i, j;
+        var examples = [];
+        // add regexps
+        for (i = 0; i < color_defs.length; i++) {
+            var example = color_defs[i].example;
+            for (j = 0; j < example.length; j++) {
+                examples[examples.length] = example[j];
+            }
+        }
+        // add type-in colors
+        var sc;
+        for (sc in simple_colors) {
+            if (simple_colors.hasOwnProperty(sc)) {
+                examples[examples.length] = sc;
+            }
+        }
+
+        var xml = document.createElement('ul');
+        xml.setAttribute('id', 'rgbcolor-examples');
+        for (i = 0; i < examples.length; i++) {
+            try {
+                var list_item = document.createElement('li');
+                var list_color = new RGBColor(examples[i]);
+                var example_div = document.createElement('div');
+                example_div.style.cssText =
+                        'margin: 3px; '
+                        + 'border: 1px solid black; '
+                        + 'background:' + list_color.toHex() + '; '
+                        + 'color:' + list_color.toHex()
+                ;
+                example_div.appendChild(document.createTextNode('test'));
+                var list_item_value = document.createTextNode(
+                    ' ' + examples[i] + ' -> ' + list_color.toRGB() + ' -> ' + list_color.toHex()
+                );
+                list_item.appendChild(example_div);
+                list_item.appendChild(list_item_value);
+                xml.appendChild(list_item);
+
+            } catch(e){}
+        }
+        return xml;
+
+    };
+
+}

+ 66 - 0
main/inc/lib/javascript/svgedit/contextmenu.js

@@ -0,0 +1,66 @@
+/*globals $, svgEditor*/
+/*jslint vars: true, eqeq: true*/
+/**
+ * Package: svgedit.contextmenu
+ * 
+ * Licensed under the Apache License, Version 2
+ * 
+ * Author: Adam Bender
+ */
+// Dependencies:
+// 1) jQuery (for dom injection of context menus)
+var svgedit = svgedit || {};
+(function() {
+	var self = this;
+	if (!svgedit.contextmenu) {
+		svgedit.contextmenu = {};
+	}
+	self.contextMenuExtensions = {};
+	var menuItemIsValid = function(menuItem) {
+		return menuItem && menuItem.id && menuItem.label && menuItem.action && typeof menuItem.action == 'function';
+	};
+	var addContextMenuItem = function(menuItem) {
+		// menuItem: {id, label, shortcut, action}
+		if (!menuItemIsValid(menuItem)) {
+			console.error("Menu items must be defined and have at least properties: id, label, action, where action must be a function");
+			return;
+		}
+		if (menuItem.id in self.contextMenuExtensions) {
+			console.error('Cannot add extension "' + menuItem.id + '", an extension by that name already exists"');
+			return;
+		}
+		// Register menuItem action, see below for deferred menu dom injection
+		console.log("Registed contextmenu item: {id:"+ menuItem.id+", label:"+menuItem.label+"}");
+		self.contextMenuExtensions[menuItem.id] = menuItem;
+		//TODO: Need to consider how to handle custom enable/disable behavior
+	};
+	var hasCustomHandler = function(handlerKey) {
+		return self.contextMenuExtensions[handlerKey] && true;
+	};
+	var getCustomHandler = function(handlerKey) {
+		return self.contextMenuExtensions[handlerKey].action;
+	};
+	var injectExtendedContextMenuItemIntoDom = function(menuItem) {
+		if (Object.keys(self.contextMenuExtensions).length === 0) {
+			// all menuItems appear at the bottom of the menu in their own container.
+			// if this is the first extension menu we need to add the separator.
+			$("#cmenu_canvas").append("<li class='separator'>");
+		}
+		var shortcut = menuItem.shortcut || "";
+		$("#cmenu_canvas").append("<li class='disabled'><a href='#" + menuItem.id + "'>"
+									+ menuItem.label + "<span class='shortcut'>"
+									+ shortcut + "</span></a></li>");
+	};
+	// Defer injection to wait out initial menu processing. This probably goes away once all context
+	// menu behavior is brought here.
+	svgEditor.ready(function() {
+		var menuItem;
+		for (menuItem in contextMenuExtensions) {
+			injectExtendedContextMenuItemIntoDom(contextMenuExtensions[menuItem]);
+		}
+	});
+	svgedit.contextmenu.resetCustomMenus = function(){self.contextMenuExtensions = {};};
+	svgedit.contextmenu.add = addContextMenuItem;
+	svgedit.contextmenu.hasCustomHandler = hasCustomHandler;
+	svgedit.contextmenu.getCustomHandler = getCustomHandler;
+}());

+ 203 - 0
main/inc/lib/javascript/svgedit/contextmenu/jquery.contextMenu.js

@@ -0,0 +1,203 @@
+// jQuery Context Menu Plugin
+//
+// Version 1.01
+//
+// Cory S.N. LaViska
+// A Beautiful Site (http://abeautifulsite.net/)
+// Modified by Alexis Deveria
+//
+// More info: http://abeautifulsite.net/2008/09/jquery-context-menu-plugin/
+//
+// Terms of Use
+//
+// This plugin is dual-licensed under the GNU General Public License
+//   and the MIT License and is copyright A Beautiful Site, LLC.
+//
+if(jQuery)( function() {
+	var win = $(window);
+	var doc = $(document);
+
+	$.extend($.fn, {
+		
+		contextMenu: function(o, callback) {
+			// Defaults
+			if( o.menu == undefined ) return false;
+			if( o.inSpeed == undefined ) o.inSpeed = 150;
+			if( o.outSpeed == undefined ) o.outSpeed = 75;
+			// 0 needs to be -1 for expected results (no fade)
+			if( o.inSpeed == 0 ) o.inSpeed = -1;
+			if( o.outSpeed == 0 ) o.outSpeed = -1;
+			// Loop each context menu
+			$(this).each( function() {
+				var el = $(this);
+				var offset = $(el).offset();
+			
+				var menu = $('#' + o.menu);
+
+				// Add contextMenu class
+				menu.addClass('contextMenu');
+				// Simulate a true right click
+				$(this).bind( "mousedown", function(e) {
+					var evt = e;
+					$(this).mouseup( function(e) {
+						var srcElement = $(this);
+						srcElement.unbind('mouseup');
+						if( evt.button === 2 || o.allowLeft || (evt.ctrlKey && svgedit.browser.isMac()) ) {
+							e.stopPropagation();
+							// Hide context menus that may be showing
+							$(".contextMenu").hide();
+							// Get this context menu
+						
+							if( el.hasClass('disabled') ) return false;
+							
+							// Detect mouse position
+							var d = {}, x = e.pageX, y = e.pageY;
+							
+							var x_off = win.width() - menu.width(), 
+								y_off = win.height() - menu.height();
+
+							if(x > x_off - 15) x = x_off-15;
+							if(y > y_off - 30) y = y_off-30; // 30 is needed to prevent scrollbars in FF
+							
+							// Show the menu
+							doc.unbind('click');
+							menu.css({ top: y, left: x }).fadeIn(o.inSpeed);
+							// Hover events
+							menu.find('A').mouseover( function() {
+								menu.find('LI.hover').removeClass('hover');
+								$(this).parent().addClass('hover');
+							}).mouseout( function() {
+								menu.find('LI.hover').removeClass('hover');
+							});
+							
+							// Keyboard
+							doc.keypress( function(e) {
+								switch( e.keyCode ) {
+									case 38: // up
+										if( !menu.find('LI.hover').length ) {
+											menu.find('LI:last').addClass('hover');
+										} else {
+											menu.find('LI.hover').removeClass('hover').prevAll('LI:not(.disabled)').eq(0).addClass('hover');
+											if( !menu.find('LI.hover').length ) menu.find('LI:last').addClass('hover');
+										}
+									break;
+									case 40: // down
+										if( menu.find('LI.hover').length == 0 ) {
+											menu.find('LI:first').addClass('hover');
+										} else {
+											menu.find('LI.hover').removeClass('hover').nextAll('LI:not(.disabled)').eq(0).addClass('hover');
+											if( !menu.find('LI.hover').length ) menu.find('LI:first').addClass('hover');
+										}
+									break;
+									case 13: // enter
+										menu.find('LI.hover A').trigger('click');
+									break;
+									case 27: // esc
+										doc.trigger('click');
+									break
+								}
+							});
+							
+							// When items are selected
+							menu.find('A').unbind('mouseup');
+							menu.find('LI:not(.disabled) A').mouseup( function() {
+								doc.unbind('click').unbind('keypress');
+								$(".contextMenu").hide();
+								// Callback
+								if( callback ) callback( $(this).attr('href').substr(1), $(srcElement), {x: x - offset.left, y: y - offset.top, docX: x, docY: y} );
+								return false;
+							});
+							
+							// Hide bindings
+							setTimeout( function() { // Delay for Mozilla
+								doc.click( function() {
+									doc.unbind('click').unbind('keypress');
+									menu.fadeOut(o.outSpeed);
+									return false;
+								});
+							}, 0);
+						}
+					});
+				});
+				
+				// Disable text selection
+				if( $.browser.mozilla ) {
+					$('#' + o.menu).each( function() { $(this).css({ 'MozUserSelect' : 'none' }); });
+				} else if( $.browser.msie ) {
+					$('#' + o.menu).each( function() { $(this).bind('selectstart.disableTextSelect', function() { return false; }); });
+				} else {
+					$('#' + o.menu).each(function() { $(this).bind('mousedown.disableTextSelect', function() { return false; }); });
+				}
+				// Disable browser context menu (requires both selectors to work in IE/Safari + FF/Chrome)
+				$(el).add($('UL.contextMenu')).bind('contextmenu', function() { return false; });
+				
+			});
+			return $(this);
+		},
+		
+		// Disable context menu items on the fly
+		disableContextMenuItems: function(o) {
+			if( o == undefined ) {
+				// Disable all
+				$(this).find('LI').addClass('disabled');
+				return( $(this) );
+			}
+			$(this).each( function() {
+				if( o != undefined ) {
+					var d = o.split(',');
+					for( var i = 0; i < d.length; i++ ) {
+						$(this).find('A[href="' + d[i] + '"]').parent().addClass('disabled');
+						
+					}
+				}
+			});
+			return( $(this) );
+		},
+		
+		// Enable context menu items on the fly
+		enableContextMenuItems: function(o) {
+			if( o == undefined ) {
+				// Enable all
+				$(this).find('LI.disabled').removeClass('disabled');
+				return( $(this) );
+			}
+			$(this).each( function() {
+				if( o != undefined ) {
+					var d = o.split(',');
+					for( var i = 0; i < d.length; i++ ) {
+						$(this).find('A[href="' + d[i] + '"]').parent().removeClass('disabled');
+						
+					}
+				}
+			});
+			return( $(this) );
+		},
+		
+		// Disable context menu(s)
+		disableContextMenu: function() {
+			$(this).each( function() {
+				$(this).addClass('disabled');
+			});
+			return( $(this) );
+		},
+		
+		// Enable context menu(s)
+		enableContextMenu: function() {
+			$(this).each( function() {
+				$(this).removeClass('disabled');
+			});
+			return( $(this) );
+		},
+		
+		// Destroy context menu(s)
+		destroyContextMenu: function() {
+			// Destroy specified context menus
+			$(this).each( function() {
+				// Disable action
+				$(this).unbind('mousedown').unbind('mouseup');
+			});
+			return( $(this) );
+		}
+		
+	});
+})(jQuery);

+ 317 - 0
main/inc/lib/javascript/svgedit/coords.js

@@ -0,0 +1,317 @@
+/*globals $, svgroot */
+/*jslint vars: true, eqeq: true, forin: true*/
+/**
+ * Coords.
+ *
+ * Licensed under the MIT License
+ *
+ */
+
+// Dependencies:
+// 1) jquery.js
+// 2) math.js
+// 3) pathseg.js
+// 4) browser.js
+// 5) svgutils.js
+// 6) units.js
+// 7) svgtransformlist.js
+
+var svgedit = svgedit || {};
+
+(function() {'use strict';
+
+if (!svgedit.coords) {
+  svgedit.coords = {};
+}
+
+// this is how we map paths to our preferred relative segment types
+var pathMap = [0, 'z', 'M', 'm', 'L', 'l', 'C', 'c', 'Q', 'q', 'A', 'a', 
+    'H', 'h', 'V', 'v', 'S', 's', 'T', 't'];
+
+/**
+ * @typedef editorContext
+ * @type {?object}
+ * @property {function} getGridSnapping
+ * @property {function} getDrawing
+*/
+var editorContext_ = null;
+
+/**
+* @param {editorContext} editorContext
+*/
+svgedit.coords.init = function(editorContext) {
+  editorContext_ = editorContext;
+};
+
+/**
+ * Applies coordinate changes to an element based on the given matrix
+ * @param {Element} selected - DOM element to be changed
+ * @param {object} changes - Object with changes to be remapped
+ * @param {SVGMatrix} m - Matrix object to use for remapping coordinates
+*/
+svgedit.coords.remapElement = function(selected, changes, m) {
+  var i, type,
+    remap = function(x, y) { return svgedit.math.transformPoint(x, y, m); },
+    scalew = function(w) { return m.a * w; },
+    scaleh = function(h) { return m.d * h; },
+    doSnapping = editorContext_.getGridSnapping() && selected.parentNode.parentNode.localName === 'svg',
+    finishUp = function() {
+      var o;
+      if (doSnapping) {
+        for (o in changes) {
+          changes[o] = svgedit.utilities.snapToGrid(changes[o]);
+        }
+      }
+      svgedit.utilities.assignAttributes(selected, changes, 1000, true);
+    },
+    box = svgedit.utilities.getBBox(selected);
+
+  for (i = 0; i < 2; i++) {
+    type = i === 0 ? 'fill' : 'stroke';
+    var attrVal = selected.getAttribute(type);
+    if (attrVal && attrVal.indexOf('url(') === 0) {
+      if (m.a < 0 || m.d < 0) {
+        var grad = svgedit.utilities.getRefElem(attrVal);
+        var newgrad = grad.cloneNode(true);
+        if (m.a < 0) {
+          // flip x
+          var x1 = newgrad.getAttribute('x1');
+          var x2 = newgrad.getAttribute('x2');
+          newgrad.setAttribute('x1', -(x1 - 1));
+          newgrad.setAttribute('x2', -(x2 - 1));
+        } 
+
+        if (m.d < 0) {
+          // flip y
+          var y1 = newgrad.getAttribute('y1');
+          var y2 = newgrad.getAttribute('y2');
+          newgrad.setAttribute('y1', -(y1 - 1));
+          newgrad.setAttribute('y2', -(y2 - 1));
+        }
+        newgrad.id = editorContext_.getDrawing().getNextId();
+        svgedit.utilities.findDefs().appendChild(newgrad);
+        selected.setAttribute(type, 'url(#' + newgrad.id + ')');
+      }
+
+      // Not really working :(
+//      if (selected.tagName === 'path') {
+//        reorientGrads(selected, m);
+//      }
+    }
+  }
+
+  var elName = selected.tagName;
+  var chlist, mt;
+  if (elName === 'g' || elName === 'text' || elName == 'tspan' || elName === 'use') {
+    // if it was a translate, then just update x,y
+    if (m.a == 1 && m.b == 0 && m.c == 0 && m.d == 1 && (m.e != 0 || m.f != 0) ) {
+      // [T][M] = [M][T']
+      // therefore [T'] = [M_inv][T][M]
+      var existing = svgedit.math.transformListToTransform(selected).matrix,
+          t_new = svgedit.math.matrixMultiply(existing.inverse(), m, existing);
+      changes.x = parseFloat(changes.x) + t_new.e;
+      changes.y = parseFloat(changes.y) + t_new.f;
+    } else {
+      // we just absorb all matrices into the element and don't do any remapping
+      chlist = svgedit.transformlist.getTransformList(selected);
+      mt = svgroot.createSVGTransform();
+      mt.setMatrix(svgedit.math.matrixMultiply(svgedit.math.transformListToTransform(chlist).matrix, m));
+      chlist.clear();
+      chlist.appendItem(mt);
+    }
+  }
+  var c, pt, pt1, pt2, len;
+  // now we have a set of changes and an applied reduced transform list
+  // we apply the changes directly to the DOM
+  switch (elName) {
+    case 'foreignObject':
+    case 'rect':
+    case 'image':
+      // Allow images to be inverted (give them matrix when flipped)
+      if (elName === 'image' && (m.a < 0 || m.d < 0)) {
+        // Convert to matrix
+        chlist = svgedit.transformlist.getTransformList(selected);
+        mt = svgroot.createSVGTransform();
+        mt.setMatrix(svgedit.math.matrixMultiply(svgedit.math.transformListToTransform(chlist).matrix, m));
+        chlist.clear();
+        chlist.appendItem(mt);
+      } else {
+        pt1 = remap(changes.x, changes.y);
+        changes.width = scalew(changes.width);
+        changes.height = scaleh(changes.height);
+        changes.x = pt1.x + Math.min(0, changes.width);
+        changes.y = pt1.y + Math.min(0, changes.height);
+        changes.width = Math.abs(changes.width);
+        changes.height = Math.abs(changes.height);
+      }
+      finishUp();
+      break;
+    case 'ellipse':
+      c = remap(changes.cx, changes.cy);
+      changes.cx = c.x;
+      changes.cy = c.y;
+      changes.rx = scalew(changes.rx);
+      changes.ry = scaleh(changes.ry);
+      changes.rx = Math.abs(changes.rx);
+      changes.ry = Math.abs(changes.ry);
+      finishUp();
+      break;
+    case 'circle':
+      c = remap(changes.cx,changes.cy);
+      changes.cx = c.x;
+      changes.cy = c.y;
+      // take the minimum of the new selected box's dimensions for the new circle radius
+      var tbox = svgedit.math.transformBox(box.x, box.y, box.width, box.height, m);
+      var w = tbox.tr.x - tbox.tl.x, h = tbox.bl.y - tbox.tl.y;
+      changes.r = Math.min(w/2, h/2);
+
+      if (changes.r) {changes.r = Math.abs(changes.r);}
+      finishUp();
+      break;
+    case 'line':
+      pt1 = remap(changes.x1, changes.y1);
+      pt2 = remap(changes.x2, changes.y2);
+      changes.x1 = pt1.x;
+      changes.y1 = pt1.y;
+      changes.x2 = pt2.x;
+      changes.y2 = pt2.y;
+      // deliberately fall through here
+    case 'text':
+    case 'tspan':
+    case 'use':
+      finishUp();
+      break;
+    case 'g':
+      var gsvg = $(selected).data('gsvg');
+      if (gsvg) {
+          svgedit.utilities.assignAttributes(gsvg, changes, 1000, true);
+      }
+      break;
+    case 'polyline':
+    case 'polygon':
+      len = changes.points.length;
+      for (i = 0; i < len; ++i) {
+        pt = changes.points[i];
+        pt = remap(pt.x, pt.y);
+        changes.points[i].x = pt.x;
+        changes.points[i].y = pt.y;
+      }
+
+      len = changes.points.length;
+      var pstr = '';
+      for (i = 0; i < len; ++i) {
+        pt = changes.points[i];
+        pstr += pt.x + ',' + pt.y + ' ';
+      }
+      selected.setAttribute('points', pstr);
+      break;
+    case 'path':
+      var seg;
+      var segList = selected.pathSegList;
+      len = segList.numberOfItems;
+      changes.d = [];
+      for (i = 0; i < len; ++i) {
+          seg = segList.getItem(i);
+          changes.d[i] = {
+              type: seg.pathSegType,
+              x: seg.x,
+              y: seg.y,
+              x1: seg.x1,
+              y1: seg.y1,
+              x2: seg.x2,
+              y2: seg.y2,
+              r1: seg.r1,
+              r2: seg.r2,
+              angle: seg.angle,
+              largeArcFlag: seg.largeArcFlag,
+              sweepFlag: seg.sweepFlag
+          };
+      }
+
+      len = changes.d.length;
+      var firstseg = changes.d[0],
+          currentpt = remap(firstseg.x, firstseg.y);
+      changes.d[0].x = currentpt.x;
+      changes.d[0].y = currentpt.y;
+      for (i = 1; i < len; ++i) {
+        seg = changes.d[i];
+        type = seg.type;
+        // if absolute or first segment, we want to remap x, y, x1, y1, x2, y2
+        // if relative, we want to scalew, scaleh
+        if (type % 2 == 0) { // absolute
+          var thisx = (seg.x != undefined) ? seg.x : currentpt.x, // for V commands
+              thisy = (seg.y != undefined) ? seg.y : currentpt.y; // for H commands
+          pt = remap(thisx,thisy);
+          pt1 = remap(seg.x1, seg.y1);
+          pt2 = remap(seg.x2, seg.y2);
+          seg.x = pt.x;
+          seg.y = pt.y;
+          seg.x1 = pt1.x;
+          seg.y1 = pt1.y;
+          seg.x2 = pt2.x;
+          seg.y2 = pt2.y;
+          seg.r1 = scalew(seg.r1);
+          seg.r2 = scaleh(seg.r2);
+        }
+        else { // relative
+          seg.x = scalew(seg.x);
+          seg.y = scaleh(seg.y);
+          seg.x1 = scalew(seg.x1);
+          seg.y1 = scaleh(seg.y1);
+          seg.x2 = scalew(seg.x2);
+          seg.y2 = scaleh(seg.y2);
+          seg.r1 = scalew(seg.r1);
+          seg.r2 = scaleh(seg.r2);
+        }
+      } // for each segment
+
+      var dstr = '';
+      len = changes.d.length;
+      for (i = 0; i < len; ++i) {
+        seg = changes.d[i];
+        type = seg.type;
+        dstr += pathMap[type];
+        switch (type) {
+            case 13: // relative horizontal line (h)
+            case 12: // absolute horizontal line (H)
+                dstr += seg.x + ' ';
+                break;
+            case 15: // relative vertical line (v)
+            case 14: // absolute vertical line (V)
+                dstr += seg.y + ' ';
+                break;
+            case 3: // relative move (m)
+            case 5: // relative line (l)
+            case 19: // relative smooth quad (t)
+            case 2: // absolute move (M)
+            case 4: // absolute line (L)
+            case 18: // absolute smooth quad (T)
+                dstr += seg.x + ',' + seg.y + ' ';
+                break;
+            case 7: // relative cubic (c)
+            case 6: // absolute cubic (C)
+                dstr += seg.x1 + ',' + seg.y1 + ' ' + seg.x2 + ',' + seg.y2 + ' ' +
+                     seg.x + ',' + seg.y + ' ';
+                break;
+            case 9: // relative quad (q) 
+            case 8: // absolute quad (Q)
+                dstr += seg.x1 + ',' + seg.y1 + ' ' + seg.x + ',' + seg.y + ' ';
+                break;
+            case 11: // relative elliptical arc (a)
+            case 10: // absolute elliptical arc (A)
+                dstr += seg.r1 + ',' + seg.r2 + ' ' + seg.angle + ' ' + (+seg.largeArcFlag) +
+                    ' ' + (+seg.sweepFlag) + ' ' + seg.x + ',' + seg.y + ' ';
+                break;
+            case 17: // relative smooth cubic (s)
+            case 16: // absolute smooth cubic (S)
+                dstr += seg.x2 + ',' + seg.y2 + ' ' + seg.x + ',' + seg.y + ' ';
+                break;
+          }
+      }
+
+      selected.setAttribute('d', dstr);
+      break;
+    }
+};
+
+}());

+ 537 - 0
main/inc/lib/javascript/svgedit/draw.js

@@ -0,0 +1,537 @@
+/*globals $, svgedit*/
+/*jslint vars: true, eqeq: true, todo: true*/
+/**
+ * Package: svgedit.draw
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2011 Jeff Schiller
+ */
+
+// Dependencies:
+// 1) jQuery
+// 2) browser.js
+// 3) svgutils.js
+
+(function() {'use strict';
+
+if (!svgedit.draw) {
+	svgedit.draw = {};
+}
+// alias
+var NS = svgedit.NS;
+
+var visElems = 'a,circle,ellipse,foreignObject,g,image,line,path,polygon,polyline,rect,svg,text,tspan,use'.split(',');
+
+var RandomizeModes = {
+	LET_DOCUMENT_DECIDE: 0,
+	ALWAYS_RANDOMIZE: 1,
+	NEVER_RANDOMIZE: 2
+};
+var randomize_ids = RandomizeModes.LET_DOCUMENT_DECIDE;
+
+/**
+ * This class encapsulates the concept of a layer in the drawing
+ * @param {String} name - Layer name
+ * @param {SVGGElement} child - Layer SVG group.
+ */
+svgedit.draw.Layer = function(name, group) {
+	this.name_ = name;
+	this.group_ = group;
+};
+
+/**
+ * @returns {string} The layer name
+ */
+svgedit.draw.Layer.prototype.getName = function() {
+	return this.name_;
+};
+
+/**
+ * @returns {SVGGElement} The layer SVG group
+ */
+svgedit.draw.Layer.prototype.getGroup = function() {
+	return this.group_;
+};
+
+
+/**
+ * Called to ensure that drawings will or will not have randomized ids.
+ * The currentDrawing will have its nonce set if it doesn't already.
+ * @param {boolean} enableRandomization - flag indicating if documents should have randomized ids
+ * @param {svgedit.draw.Drawing} currentDrawing
+ */
+svgedit.draw.randomizeIds = function(enableRandomization, currentDrawing) {
+	randomize_ids = enableRandomization === false ?
+		RandomizeModes.NEVER_RANDOMIZE :
+		RandomizeModes.ALWAYS_RANDOMIZE;
+
+	if (randomize_ids == RandomizeModes.ALWAYS_RANDOMIZE && !currentDrawing.getNonce()) {
+		currentDrawing.setNonce(Math.floor(Math.random() * 100001));
+	} else if (randomize_ids == RandomizeModes.NEVER_RANDOMIZE && currentDrawing.getNonce()) {
+		currentDrawing.clearNonce();
+	}
+};
+
+/**
+ * This class encapsulates the concept of a SVG-edit drawing
+ * @param {SVGSVGElement} svgElem - The SVG DOM Element that this JS object
+ *     encapsulates.  If the svgElem has a se:nonce attribute on it, then
+ *     IDs will use the nonce as they are generated.
+ * @param {String=svg_} [opt_idPrefix] - The ID prefix to use.
+ */
+svgedit.draw.Drawing = function(svgElem, opt_idPrefix) {
+	if (!svgElem || !svgElem.tagName || !svgElem.namespaceURI ||
+		svgElem.tagName != 'svg' || svgElem.namespaceURI != NS.SVG) {
+		throw "Error: svgedit.draw.Drawing instance initialized without a <svg> element";
+	}
+
+	/**
+	 * The SVG DOM Element that represents this drawing.
+	 * @type {SVGSVGElement}
+	 */
+	this.svgElem_ = svgElem;
+	
+	/**
+	 * The latest object number used in this drawing.
+	 * @type {number}
+	 */
+	this.obj_num = 0;
+	
+	/**
+	 * The prefix to prepend to each element id in the drawing.
+	 * @type {String}
+	 */
+	this.idPrefix = opt_idPrefix || "svg_";
+	
+	/**
+	 * An array of released element ids to immediately reuse.
+	 * @type {Array.<number>}
+	 */
+	this.releasedNums = [];
+
+	/**
+	 * The z-ordered array of tuples containing layer names and <g> elements.
+	 * The first layer is the one at the bottom of the rendering.
+	 * TODO: Turn this into an Array.<Layer>
+	 * @type {Array.<Array.<String, SVGGElement>>}
+	 */
+	this.all_layers = [];
+
+	/**
+	 * The current layer being used.
+	 * TODO: Make this a {Layer}.
+	 * @type {SVGGElement}
+	 */
+	this.current_layer = null;
+
+	/**
+	 * The nonce to use to uniquely identify elements across drawings.
+	 * @type {!String}
+	 */
+	this.nonce_ = '';
+	var n = this.svgElem_.getAttributeNS(NS.SE, 'nonce');
+	// If already set in the DOM, use the nonce throughout the document
+	// else, if randomizeIds(true) has been called, create and set the nonce.
+	if (!!n && randomize_ids != RandomizeModes.NEVER_RANDOMIZE) {
+		this.nonce_ = n;
+	} else if (randomize_ids == RandomizeModes.ALWAYS_RANDOMIZE) {
+		this.setNonce(Math.floor(Math.random() * 100001));
+	}
+};
+
+/**
+ * @param {string} id Element ID to retrieve
+ * @returns {Element} SVG element within the root SVGSVGElement
+*/
+svgedit.draw.Drawing.prototype.getElem_ = function (id) {
+	if (this.svgElem_.querySelector) {
+		// querySelector lookup
+		return this.svgElem_.querySelector('#' + id);
+	}
+	// jQuery lookup: twice as slow as xpath in FF
+	return $(this.svgElem_).find('[id=' + id + ']')[0];
+};
+
+/**
+ * @returns {SVGSVGElement}
+ */
+svgedit.draw.Drawing.prototype.getSvgElem = function () {
+	return this.svgElem_;
+};
+
+/**
+ * @returns {!string|number} The previously set nonce
+ */
+svgedit.draw.Drawing.prototype.getNonce = function() {
+	return this.nonce_;
+};
+
+/**
+ * @param {!string|number} n The nonce to set
+ */
+svgedit.draw.Drawing.prototype.setNonce = function(n) {
+	this.svgElem_.setAttributeNS(NS.XMLNS, 'xmlns:se', NS.SE);
+	this.svgElem_.setAttributeNS(NS.SE, 'se:nonce', n);
+	this.nonce_ = n;
+};
+
+/**
+ * Clears any previously set nonce
+ */
+svgedit.draw.Drawing.prototype.clearNonce = function () {
+	// We deliberately leave any se:nonce attributes alone,
+	// we just don't use it to randomize ids.
+	this.nonce_ = '';
+};
+
+/**
+ * Returns the latest object id as a string.
+ * @return {String} The latest object Id.
+ */
+svgedit.draw.Drawing.prototype.getId = function () {
+	return this.nonce_ ?
+		this.idPrefix + this.nonce_ + '_' + this.obj_num :
+		this.idPrefix + this.obj_num;
+};
+
+/**
+ * Returns the next object Id as a string.
+ * @return {String} The next object Id to use.
+ */
+svgedit.draw.Drawing.prototype.getNextId = function () {
+	var oldObjNum = this.obj_num;
+	var restoreOldObjNum = false;
+
+	// If there are any released numbers in the release stack, 
+	// use the last one instead of the next obj_num.
+	// We need to temporarily use obj_num as that is what getId() depends on.
+	if (this.releasedNums.length > 0) {
+		this.obj_num = this.releasedNums.pop();
+		restoreOldObjNum = true;
+	} else {
+		// If we are not using a released id, then increment the obj_num.
+		this.obj_num++;
+	}
+
+	// Ensure the ID does not exist.
+	var id = this.getId();
+	while (this.getElem_(id)) {
+		if (restoreOldObjNum) {
+			this.obj_num = oldObjNum;
+			restoreOldObjNum = false;
+		}
+		this.obj_num++;
+		id = this.getId();
+	}
+	// Restore the old object number if required.
+	if (restoreOldObjNum) {
+		this.obj_num = oldObjNum;
+	}
+	return id;
+};
+
+/**
+ * Releases the object Id, letting it be used as the next id in getNextId().
+ * This method DOES NOT remove any elements from the DOM, it is expected
+ * that client code will do this.
+ * @param {string} id - The id to release.
+ * @returns {boolean} True if the id was valid to be released, false otherwise.
+*/
+svgedit.draw.Drawing.prototype.releaseId = function (id) {
+	// confirm if this is a valid id for this Document, else return false
+	var front = this.idPrefix + (this.nonce_ ? this.nonce_ + '_' : '');
+	if (typeof id !== 'string' || id.indexOf(front) !== 0) {
+		return false;
+	}
+	// extract the obj_num of this id
+	var num = parseInt(id.substr(front.length), 10);
+
+	// if we didn't get a positive number or we already released this number
+	// then return false.
+	if (typeof num !== 'number' || num <= 0 || this.releasedNums.indexOf(num) != -1) {
+		return false;
+	}
+	
+	// push the released number into the released queue
+	this.releasedNums.push(num);
+
+	return true;
+};
+
+/**
+ * Returns the number of layers in the current drawing.
+ * @returns {integer} The number of layers in the current drawing.
+*/
+svgedit.draw.Drawing.prototype.getNumLayers = function() {
+	return this.all_layers.length;
+};
+
+/**
+ * Check if layer with given name already exists
+ * @param {string} name - The layer name to check
+*/
+svgedit.draw.Drawing.prototype.hasLayer = function (name) {
+	var i;
+	for (i = 0; i < this.getNumLayers(); i++) {
+		if(this.all_layers[i][0] == name) {return true;}
+	}
+	return false;
+};
+
+
+/**
+ * Returns the name of the ith layer. If the index is out of range, an empty string is returned.
+ * @param {integer} i - The zero-based index of the layer you are querying.
+ * @returns {string} The name of the ith layer (or the empty string if none found)
+*/
+svgedit.draw.Drawing.prototype.getLayerName = function (i) {
+	if (i >= 0 && i < this.getNumLayers()) {
+		return this.all_layers[i][0];
+	}
+	return '';
+};
+
+/**
+ * @returns {SVGGElement} The SVGGElement representing the current layer.
+ */
+svgedit.draw.Drawing.prototype.getCurrentLayer = function() {
+	return this.current_layer;
+};
+
+/**
+ * Returns the name of the currently selected layer. If an error occurs, an empty string 
+ * is returned.
+ * @returns The name of the currently active layer (or the empty string if none found).
+*/
+svgedit.draw.Drawing.prototype.getCurrentLayerName = function () {
+	var i;
+	for (i = 0; i < this.getNumLayers(); ++i) {
+		if (this.all_layers[i][1] == this.current_layer) {
+			return this.getLayerName(i);
+		}
+	}
+	return '';
+};
+
+/**
+ * Sets the current layer. If the name is not a valid layer name, then this
+ * function returns false. Otherwise it returns true. This is not an
+ * undo-able action.
+ * @param {string} name - The name of the layer you want to switch to.
+ * @returns {boolean} true if the current layer was switched, otherwise false
+ */
+svgedit.draw.Drawing.prototype.setCurrentLayer = function(name) {
+	var i;
+	for (i = 0; i < this.getNumLayers(); ++i) {
+		if (name == this.getLayerName(i)) {
+			if (this.current_layer != this.all_layers[i][1]) {
+				this.current_layer.setAttribute("style", "pointer-events:none");
+				this.current_layer = this.all_layers[i][1];
+				this.current_layer.setAttribute("style", "pointer-events:all");
+			}
+			return true;
+		}
+	}
+	return false;
+};
+
+
+/**
+ * Deletes the current layer from the drawing and then clears the selection.
+ * This function then calls the 'changed' handler.  This is an undoable action.
+ * @returns {SVGGElement} The SVGGElement of the layer removed or null.
+ */
+svgedit.draw.Drawing.prototype.deleteCurrentLayer = function() {
+	if (this.current_layer && this.getNumLayers() > 1) {
+		// actually delete from the DOM and return it
+		var parent = this.current_layer.parentNode;
+		var nextSibling = this.current_layer.nextSibling;
+		var oldLayerGroup = parent.removeChild(this.current_layer);
+		this.identifyLayers();
+		return oldLayerGroup;
+	}
+	return null;
+};
+
+/**
+ * Updates layer system and sets the current layer to the
+ * top-most layer (last <g> child of this drawing).
+*/
+svgedit.draw.Drawing.prototype.identifyLayers = function() {
+	this.all_layers = [];
+	var numchildren = this.svgElem_.childNodes.length;
+	// loop through all children of SVG element
+	var orphans = [], layernames = [];
+	var a_layer = null;
+	var childgroups = false;
+	var i;
+	for (i = 0; i < numchildren; ++i) {
+		var child = this.svgElem_.childNodes.item(i);
+		// for each g, find its layer name
+		if (child && child.nodeType == 1) {
+			if (child.tagName == "g") {
+				childgroups = true;
+				var name = $("title", child).text();
+
+				// Hack for Opera 10.60
+				if(!name && svgedit.browser.isOpera() && child.querySelectorAll) {
+					name = $(child.querySelectorAll('title')).text();
+				}
+
+				// store layer and name in global variable
+				if (name) {
+					layernames.push(name);
+					this.all_layers.push( [name, child] );
+					a_layer = child;
+					svgedit.utilities.walkTree(child, function(e){e.setAttribute("style", "pointer-events:inherit");});
+					a_layer.setAttribute("style", "pointer-events:none");
+				}
+				// if group did not have a name, it is an orphan
+				else {
+					orphans.push(child);
+				}
+			}
+			// if child has is "visible" (i.e. not a <title> or <defs> element), then it is an orphan
+			else if(~visElems.indexOf(child.nodeName)) {
+				var bb = svgedit.utilities.getBBox(child);
+				orphans.push(child);
+			}
+		}
+	}
+	
+	// create a new layer and add all the orphans to it
+	var svgdoc = this.svgElem_.ownerDocument;
+	if (orphans.length > 0 || !childgroups) {
+		i = 1;
+		// TODO(codedread): What about internationalization of "Layer"?
+		while (layernames.indexOf(("Layer " + i)) >= 0) { i++; }
+		var newname = "Layer " + i;
+		a_layer = svgdoc.createElementNS(NS.SVG, "g");
+		var layer_title = svgdoc.createElementNS(NS.SVG, "title");
+		layer_title.textContent = newname;
+		a_layer.appendChild(layer_title);
+		var j;
+		for (j = 0; j < orphans.length; ++j) {
+			a_layer.appendChild(orphans[j]);
+		}
+		this.svgElem_.appendChild(a_layer);
+		this.all_layers.push( [newname, a_layer] );
+	}
+	svgedit.utilities.walkTree(a_layer, function(e){e.setAttribute("style", "pointer-events:inherit");});
+	this.current_layer = a_layer;
+	this.current_layer.setAttribute("style", "pointer-events:all");
+};
+
+/**
+ * Creates a new top-level layer in the drawing with the given name and 
+ * sets the current layer to it.
+ * @param {string} name - The given name
+ * @returns {SVGGElement} The SVGGElement of the new layer, which is
+ * also the current layer of this drawing.
+*/
+svgedit.draw.Drawing.prototype.createLayer = function(name) {
+	var svgdoc = this.svgElem_.ownerDocument;
+	var new_layer = svgdoc.createElementNS(NS.SVG, "g");
+	var layer_title = svgdoc.createElementNS(NS.SVG, "title");
+	layer_title.textContent = name;
+	new_layer.appendChild(layer_title);
+	this.svgElem_.appendChild(new_layer);
+	this.identifyLayers();
+	return new_layer;
+};
+
+/**
+ * Returns whether the layer is visible.  If the layer name is not valid,
+ * then this function returns false.
+ * @param {string} layername - The name of the layer which you want to query.
+ * @returns {boolean} The visibility state of the layer, or false if the layer name was invalid.
+*/
+svgedit.draw.Drawing.prototype.getLayerVisibility = function(layername) {
+	// find the layer
+	var layer = null;
+	var i;
+	for (i = 0; i < this.getNumLayers(); ++i) {
+		if (this.getLayerName(i) == layername) {
+			layer = this.all_layers[i][1];
+			break;
+		}
+	}
+	if (!layer) {return false;}
+	return (layer.getAttribute('display') !== 'none');
+};
+
+/**
+ * Sets the visibility of the layer. If the layer name is not valid, this
+ * function returns false, otherwise it returns true. This is an
+ * undo-able action.
+ * @param {string} layername - The name of the layer to change the visibility
+ * @param {boolean} bVisible - Whether the layer should be visible
+ * @returns {?SVGGElement} The SVGGElement representing the layer if the
+ *   layername was valid, otherwise null.
+*/
+svgedit.draw.Drawing.prototype.setLayerVisibility = function(layername, bVisible) {
+	if (typeof bVisible !== 'boolean') {
+		return null;
+	}
+	// find the layer
+	var layer = null;
+	var i;
+	for (i = 0; i < this.getNumLayers(); ++i) {
+		if (this.getLayerName(i) == layername) {
+			layer = this.all_layers[i][1];
+			break;
+		}
+	}
+	if (!layer) {return null;}
+	
+	var oldDisplay = layer.getAttribute("display");
+	if (!oldDisplay) {oldDisplay = "inline";}
+	layer.setAttribute("display", bVisible ? "inline" : "none");
+	return layer;
+};
+
+
+/**
+ * Returns the opacity of the given layer.  If the input name is not a layer, null is returned.
+ * @param {string} layername - name of the layer on which to get the opacity
+ * @returns {?number} The opacity value of the given layer.  This will be a value between 0.0 and 1.0, or null
+ * if layername is not a valid layer
+*/
+svgedit.draw.Drawing.prototype.getLayerOpacity = function(layername) {
+	var i;
+	for (i = 0; i < this.getNumLayers(); ++i) {
+		if (this.getLayerName(i) == layername) {
+			var g = this.all_layers[i][1];
+			var opacity = g.getAttribute('opacity');
+			if (!opacity) {
+				opacity = '1.0';
+			}
+			return parseFloat(opacity);
+		}
+	}
+	return null;
+};
+
+/**
+ * Sets the opacity of the given layer.  If the input name is not a layer,
+ * nothing happens. If opacity is not a value between 0.0 and 1.0, then
+ * nothing happens.
+ * @param {string} layername - Name of the layer on which to set the opacity
+ * @param {number} opacity - A float value in the range 0.0-1.0
+*/
+svgedit.draw.Drawing.prototype.setLayerOpacity = function(layername, opacity) {
+	if (typeof opacity !== 'number' || opacity < 0.0 || opacity > 1.0) {
+		return;
+	}
+	var i;
+	for (i = 0; i < this.getNumLayers(); ++i) {
+		if (this.getLayerName(i) == layername) {
+			var g = this.all_layers[i][1];
+			g.setAttribute("opacity", opacity);
+			break;
+		}
+	}
+};
+
+}());

+ 79 - 0
main/inc/lib/javascript/svgedit/embedapi-dom.js

@@ -0,0 +1,79 @@
+/*globals $, EmbeddedSVGEdit*/
+/*jslint vars: true */
+var initEmbed;
+
+// Todo: Get rid of frame.contentWindow dependencies so can be more easily adjusted to work cross-domain
+
+$(function () {'use strict';
+    
+    var svgCanvas = null;
+    var frame;
+
+    initEmbed = function () {
+        var doc, mainButton;
+        svgCanvas = new EmbeddedSVGEdit(frame);
+        // Hide main button, as we will be controlling new, load, save, etc. from the host document
+        doc = frame.contentDocument || frame.contentWindow.document;
+        mainButton = doc.getElementById('main_button');
+        mainButton.style.display = 'none';
+    };
+
+    function handleSvgData(data, error) {
+        if (error) {
+            alert('error ' + error);
+        } else {
+            alert('Congratulations. Your SVG string is back in the host page, do with it what you will\n\n' + data);
+        }
+    }
+
+    function loadSvg() {
+        var svgexample = '<svg width="640" height="480" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><g><title>Layer 1</title><rect stroke-width="5" stroke="#000000" fill="#FF0000" id="svg_1" height="35" width="51" y="35" x="32"/><ellipse ry="15" rx="24" stroke-width="5" stroke="#000000" fill="#0000ff" id="svg_2" cy="60" cx="66"/></g></svg>';
+        svgCanvas.setSvgString(svgexample);
+    }
+
+    function saveSvg() {
+        svgCanvas.getSvgString()(handleSvgData);
+    }
+    
+    function exportPNG() {
+        var str = frame.contentWindow.svgEditor.uiStrings.notification.loadingImage;
+
+        var exportWindow = window.open(
+            'data:text/html;charset=utf-8,' + encodeURIComponent('<title>' + str + '</title><h1>' + str + '</h1>'),
+            'svg-edit-exportWindow'
+        );
+        svgCanvas.rasterExport('PNG', null, exportWindow.name);
+    }
+    
+    function exportPDF() {
+        var str = frame.contentWindow.svgEditor.uiStrings.notification.loadingImage;
+        
+		/**
+        // If you want to handle the PDF blob yourself, do as follows
+		svgCanvas.bind('exportedPDF', function (win, data) {
+			alert(data.dataurlstring);
+		});
+		svgCanvas.exportPDF(); // Accepts two args: optionalWindowName supplied back to bound exportPDF handler and optionalOutputType (defaults to dataurlstring)
+        return;
+        */
+		
+        var exportWindow = window.open(
+            'data:text/html;charset=utf-8,' + encodeURIComponent('<title>' + str + '</title><h1>' + str + '</h1>'),
+            'svg-edit-exportWindow'
+        );
+		svgCanvas.exportPDF(exportWindow.name);
+    }
+    
+    // Add event handlers
+    $('#load').click(loadSvg);
+    $('#save').click(saveSvg);
+    $('#exportPNG').click(exportPNG);
+    $('#exportPDF').click(exportPDF);
+    $('body').append(
+        $('<iframe src="svg-editor.html?extensions=ext-xdomain-messaging.js' +
+            window.location.href.replace(/\?(.*)$/, '&$1') + // Append arguments to this file onto the iframe
+            '" width="900px" height="600px" id="svgedit" onload="initEmbed();"></iframe>'
+        )
+    );
+    frame = document.getElementById('svgedit');
+});

+ 17 - 0
main/inc/lib/javascript/svgedit/embedapi.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+    <meta charset="utf-8" />
+    <title>Embed API</title>
+    <script src="jquery.js"></script>
+    <script src="embedapi.js"></script>
+    <script src="embedapi-dom.js"></script>
+</head>
+<body>
+    <button id="load">Load example</button>
+    <button id="save">Save data</button>
+    <button id="exportPNG">Export data to PNG</button>
+    <button id="exportPDF">Export data to PDF</button>
+    <br/>
+</body>
+</html>

File diff suppressed because it is too large
+ 122 - 0
main/inc/lib/javascript/svgedit/embedapi.js


+ 12 - 0
main/inc/lib/javascript/svgedit/extensions/allowedMimeTypes.php

@@ -0,0 +1,12 @@
+<?php
+
+$allowedMimeTypesBySuffix = array(
+    'svg' => 'image/svg+xml;charset=UTF-8',
+    'png' => 'image/png',
+    'jpeg' => 'image/jpeg',
+    'bmp' => 'image/bmp',
+    'webp' => 'image/webp',
+	'pdf' => 'application/pdf'
+);
+
+?>

+ 41 - 0
main/inc/lib/javascript/svgedit/extensions/closepath_icons.svg

@@ -0,0 +1,41 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+<g id="tool_closepath">
+<svg viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <g>
+  <title>Layer 1</title>
+  <path stroke="#000" stroke-width="15" fill="#ffc8c8" d="m121.5,40l-84,106l27,115l166,2l29,-111"/>
+  <line x1="240" y1="136" x2="169.5" y2="74" stroke="#A00" stroke-width="25" fill="none"/>
+  <path stroke="none" fill ="#A00" d="m158,65l31,74l-3,-50l51,-3z"/>
+  <g stroke-width="15" stroke="#00f" fill="#0ff">
+  <circle r="30" cy="41" cx="123"/>
+  <circle r="30" cy="146" cx="40"/>
+  <circle r="30" cy="260" cx="69"/>
+  <circle r="30" cy="260" cx="228"/>
+  <circle r="30" cy="148" cx="260"/>
+  </g>
+ </g>
+</svg>
+</g>
+<g id="tool_openpath">
+<svg viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <g>
+  <title>Layer 1</title>
+  <path stroke="#000" stroke-width="15" fill="#ffc8c8" d="m123.5,38l-84,106l27,115l166,2l29,-111"/>
+  <line x1="276.5" y1="153" x2="108.5" y2="24" stroke="#000" stroke-width="10" fill="none"/>
+  <g stroke-width="15" stroke="#00f" fill="#0ff">
+   <circle r="30" cy="41" cx="123"/>
+   <circle r="30" cy="146" cx="40"/>
+   <circle r="30" cy="260" cx="69"/>
+   <circle r="30" cy="260" cx="228"/>
+   <circle r="30" cy="148" cx="260"/>
+  </g>
+  <g  stroke="#A00" stroke-width="15" fill="none">
+   <line x1="168" y1="24" x2="210" y2="150"/>
+   <line x1="210" y1="24" x2="168" y2="150"/>
+  </g>
+ </g>
+</svg>
+</g>
+
+<g id="svg_eof"/>
+</svg>

+ 293 - 0
main/inc/lib/javascript/svgedit/extensions/ext-arrows.js

@@ -0,0 +1,293 @@
+/*globals svgEditor, svgCanvas, $*/
+/*jslint vars: true, eqeq: true*/
+/*
+ * ext-arrows.js
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2010 Alexis Deveria
+ *
+ */
+
+svgEditor.addExtension('Arrows', function(S) {
+	var svgcontent = S.svgcontent,
+		addElem = S.addSvgElementFromJson,
+		nonce = S.nonce,
+		randomize_ids = S.randomize_ids,
+		selElems, pathdata,
+		lang_list = {
+			'en':[
+				{'id': 'arrow_none', 'textContent': 'No arrow' }
+			],
+			'fr':[
+				{'id': 'arrow_none', 'textContent': 'Sans flèche' }
+			]
+		},
+		arrowprefix,
+		prefix = 'se_arrow_';
+
+	function setArrowNonce(window, n) {
+		randomize_ids = true;
+		arrowprefix = prefix + n + '_';
+		pathdata.fw.id = arrowprefix + 'fw';
+		pathdata.bk.id = arrowprefix + 'bk';
+	}
+
+	function unsetArrowNonce(window) {
+		randomize_ids = false;
+		arrowprefix = prefix;
+		pathdata.fw.id = arrowprefix + 'fw';
+		pathdata.bk.id = arrowprefix + 'bk';
+	}
+
+
+	svgCanvas.bind('setnonce', setArrowNonce);
+	svgCanvas.bind('unsetnonce', unsetArrowNonce);
+
+	if (randomize_ids) {
+		arrowprefix = prefix + nonce + '_';
+	} else {
+		arrowprefix = prefix;
+	}
+
+	pathdata = {
+		fw: {d: 'm0,0l10,5l-10,5l5,-5l-5,-5z', refx: 8,  id: arrowprefix + 'fw'},
+		bk: {d: 'm10,0l-10,5l10,5l-5,-5l5,-5z', refx: 2, id: arrowprefix + 'bk'}
+	};
+
+	function getLinked(elem, attr) {
+		var str = elem.getAttribute(attr);
+		if(!str) {return null;}
+		var m = str.match(/\(\#(.*)\)/);
+		if(!m || m.length !== 2) {
+			return null;
+		}
+		return S.getElem(m[1]);
+	}
+
+	function showPanel(on) {
+		$('#arrow_panel').toggle(on);
+		if(on) {
+			var el = selElems[0];
+			var end = el.getAttribute('marker-end');
+			var start = el.getAttribute('marker-start');
+			var mid = el.getAttribute('marker-mid');
+			var val;
+
+			if (end && start) {
+				val = 'both';
+			} else if (end) {
+				val = 'end';
+			} else if (start) {
+				val = 'start';
+			} else if (mid) {
+				val = 'mid';
+				if (mid.indexOf('bk') !== -1) {
+					val = 'mid_bk';
+				}
+			}
+
+			if (!start && !mid && !end) {
+				val = 'none';
+			}
+
+			$('#arrow_list').val(val);
+		}
+	}
+
+	function resetMarker() {
+		var el = selElems[0];
+		el.removeAttribute('marker-start');
+		el.removeAttribute('marker-mid');
+		el.removeAttribute('marker-end');
+	}
+
+	function addMarker(dir, type, id) {
+		// TODO: Make marker (or use?) per arrow type, since refX can be different
+		id = id || arrowprefix + dir;
+
+		var marker = S.getElem(id);
+		var data = pathdata[dir];
+
+		if (type == 'mid') {
+			data.refx = 5;
+		}
+
+		if (!marker) {
+			marker = addElem({
+				'element': 'marker',
+				'attr': {
+					'viewBox': '0 0 10 10',
+					'id': id,
+					'refY': 5,
+					'markerUnits': 'strokeWidth',
+					'markerWidth': 5,
+					'markerHeight': 5,
+					'orient': 'auto',
+					'style': 'pointer-events:none' // Currently needed for Opera
+				}
+			});
+			var arrow = addElem({
+				'element': 'path',
+				'attr': {
+					'd': data.d,
+					'fill': '#000000'
+				}
+			});
+			marker.appendChild(arrow);
+			S.findDefs().appendChild(marker);
+		}
+
+		marker.setAttribute('refX', data.refx);
+
+		return marker;
+	}
+
+	function setArrow() {
+		var type = this.value;
+		resetMarker();
+
+		if (type == 'none') {
+			return;
+		}
+
+		// Set marker on element
+		var dir = 'fw';
+		if (type == 'mid_bk') {
+			type = 'mid';
+			dir = 'bk';
+		} else if (type == 'both') {
+			addMarker('bk', type);
+			svgCanvas.changeSelectedAttribute('marker-start', 'url(#' + pathdata.bk.id + ')');
+			type = 'end';
+			dir = 'fw';
+		} else if (type == 'start') {
+			dir = 'bk';
+		}
+
+		addMarker(dir, type);
+		svgCanvas.changeSelectedAttribute('marker-' + type, 'url(#' + pathdata[dir].id + ')');
+		S.call('changed', selElems);
+	}
+
+	function colorChanged(elem) {
+		var color = elem.getAttribute('stroke');
+		var mtypes = ['start', 'mid', 'end'];
+		var defs = S.findDefs();
+
+		$.each(mtypes, function(i, type) {
+			var marker = getLinked(elem, 'marker-'+type);
+			if(!marker) {return;}
+
+			var cur_color = $(marker).children().attr('fill');
+			var cur_d = $(marker).children().attr('d');
+			var new_marker = null;
+			if(cur_color === color) {return;}
+
+			var all_markers = $(defs).find('marker');
+			// Different color, check if already made
+			all_markers.each(function() {
+				var attrs = $(this).children().attr(['fill', 'd']);
+				if(attrs.fill === color && attrs.d === cur_d) {
+					// Found another marker with this color and this path
+					new_marker = this;
+				}
+			});
+
+			if(!new_marker) {
+				// Create a new marker with this color
+				var last_id = marker.id;
+				var dir = last_id.indexOf('_fw') !== -1?'fw':'bk';
+
+				new_marker = addMarker(dir, type, arrowprefix + dir + all_markers.length);
+
+				$(new_marker).children().attr('fill', color);
+			}
+
+			$(elem).attr('marker-'+type, 'url(#' + new_marker.id + ')');
+
+			// Check if last marker can be removed
+			var remove = true;
+			$(S.svgcontent).find('line, polyline, path, polygon').each(function() {
+				var elem = this;
+				$.each(mtypes, function(j, mtype) {
+					if($(elem).attr('marker-' + mtype) === 'url(#' + marker.id + ')') {
+						remove = false;
+						return remove;
+					}
+				});
+				if(!remove) {return false;}
+			});
+
+			// Not found, so can safely remove
+			if(remove) {
+				$(marker).remove();
+			}
+		});
+	}
+
+	return {
+		name: 'Arrows',
+		context_tools: [{
+			type: 'select',
+			panel: 'arrow_panel',
+			title: 'Select arrow type',
+			id: 'arrow_list',
+			options: {
+				none: 'No arrow',
+				end: '----&gt;',
+				start: '&lt;----',
+				both: '&lt;---&gt;',
+				mid: '--&gt;--',
+				mid_bk: '--&lt;--'
+			},
+			defval: 'none',
+			events: {
+				change: setArrow
+			}
+		}],
+		callback: function() {
+			$('#arrow_panel').hide();
+			// Set ID so it can be translated in locale file
+			$('#arrow_list option')[0].id = 'connector_no_arrow';
+		},
+		addLangData: function(lang) {
+			return {
+				data: lang_list[lang]
+			};
+		},
+		selectedChanged: function(opts) {
+			// Use this to update the current selected elements
+			selElems = opts.elems;
+
+			var i = selElems.length;
+			var marker_elems = ['line', 'path', 'polyline', 'polygon'];
+			while(i--) {
+				var elem = selElems[i];
+				if(elem && $.inArray(elem.tagName, marker_elems) !== -1) {
+					if(opts.selectedElement && !opts.multiselected) {
+						showPanel(true);
+					} else {
+						showPanel(false);
+					}
+				} else {
+					showPanel(false);
+				}
+			}
+		},
+		elementChanged: function(opts) {
+			var elem = opts.elems[0];
+			if(elem && (
+				elem.getAttribute('marker-start') ||
+				elem.getAttribute('marker-mid') ||
+				elem.getAttribute('marker-end')
+			)) {
+//								var start = elem.getAttribute('marker-start');
+//								var mid = elem.getAttribute('marker-mid');
+//								var end = elem.getAttribute('marker-end');
+				// Has marker, so see if it should match color
+				colorChanged(elem);
+			}
+		}
+	};
+});

+ 91 - 0
main/inc/lib/javascript/svgedit/extensions/ext-closepath.js

@@ -0,0 +1,91 @@
+/*globals svgEditor, $*/
+/*jslint vars: true, eqeq: true*/
+/*
+ * ext-closepath.js
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2010 Jeff Schiller
+ *
+ */
+
+// This extension adds a simple button to the contextual panel for paths
+// The button toggles whether the path is open or closed
+svgEditor.addExtension('ClosePath', function() {'use strict';
+	var selElems,
+		updateButton = function(path) {
+			var seglist = path.pathSegList,
+				closed = seglist.getItem(seglist.numberOfItems - 1).pathSegType == 1,
+				showbutton = closed ? '#tool_openpath' : '#tool_closepath',
+				hidebutton = closed ? '#tool_closepath' : '#tool_openpath';
+				$(hidebutton).hide();
+				$(showbutton).show();
+		},
+		showPanel = function(on) {
+			$('#closepath_panel').toggle(on);
+			if (on) {
+				var path = selElems[0];
+				if (path) {updateButton(path);}
+			}
+		},
+		toggleClosed = function() {
+			var path = selElems[0];
+			if (path) {
+				var seglist = path.pathSegList,
+					last = seglist.numberOfItems - 1;
+				// is closed
+				if (seglist.getItem(last).pathSegType == 1) {
+					seglist.removeItem(last);
+				} else {
+					seglist.appendItem(path.createSVGPathSegClosePath());
+				}
+				updateButton(path);
+			}
+		};
+
+	return {
+		name: 'ClosePath',
+		svgicons: svgEditor.curConfig.extPath + 'closepath_icons.svg',
+		buttons: [{
+			id: 'tool_openpath',
+			type: 'context',
+			panel: 'closepath_panel',
+			title: 'Open path',
+			events: {
+				click: function() {
+					toggleClosed();
+				}
+			}
+		},
+		{
+			id: 'tool_closepath',
+			type: 'context',
+			panel: 'closepath_panel',
+			title: 'Close path',
+			events: {
+				click: function() {
+					toggleClosed();
+				}
+			}
+		}],
+		callback: function() {
+			$('#closepath_panel').hide();
+		},
+		selectedChanged: function(opts) {
+			selElems = opts.elems;
+			var i = selElems.length;
+			while (i--) {
+				var elem = selElems[i];
+				if (elem && elem.tagName == 'path') {
+					if (opts.selectedElement && !opts.multiselected) {
+						showPanel(true);
+					} else {
+						showPanel(false);
+					}
+				} else {
+					showPanel(false);
+				}
+			}
+		}
+	};
+});

+ 593 - 0
main/inc/lib/javascript/svgedit/extensions/ext-connector.js

@@ -0,0 +1,593 @@
+/*globals svgEditor, svgCanvas, $*/
+/*jslint vars: true, continue: true, eqeq: true, todo: true*/
+/*
+ * ext-connector.js
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2010 Alexis Deveria
+ *
+ */
+ 
+svgEditor.addExtension("Connector", function(S) {
+	var svgcontent = S.svgcontent,
+		svgroot = S.svgroot,
+		getNextId = S.getNextId,
+		getElem = S.getElem,
+		addElem = S.addSvgElementFromJson,
+		selManager = S.selectorManager,
+		curConfig = svgEditor.curConfig,
+		started = false,
+		start_x,
+		start_y,
+		cur_line,
+		start_elem,
+		end_elem,
+		connections = [],
+		conn_sel = ".se_connector",
+		se_ns,
+//		connect_str = "-SE_CONNECT-",
+		selElems = [],
+		elData = $.data;
+		
+	var lang_list = {
+		"en":[
+			{"id": "mode_connect", "title": "Connect two objects" }
+		],
+		"fr":[
+			{"id": "mode_connect", "title": "Connecter deux objets"}
+		]
+	};
+
+	function getBBintersect(x, y, bb, offset) {
+		if(offset) {
+			offset -= 0;
+			bb = $.extend({}, bb);
+			bb.width += offset;
+			bb.height += offset;
+			bb.x -= offset/2;
+			bb.y -= offset/2;
+		}
+	
+		var mid_x = bb.x + bb.width/2;
+		var mid_y = bb.y + bb.height/2;
+		var len_x = x - mid_x;
+		var len_y = y - mid_y;
+		
+		var slope = Math.abs(len_y/len_x);
+		
+		var ratio;
+		
+		if(slope < bb.height/bb.width) {
+			ratio = (bb.width/2) / Math.abs(len_x);
+		} else {
+			ratio = (bb.height/2) / Math.abs(len_y);
+		}
+		
+		
+		return {
+			x: mid_x + len_x * ratio,
+			y: mid_y + len_y * ratio
+		};
+	}
+
+	function getOffset(side, line) {
+		var give_offset = !!line.getAttribute('marker-' + side);
+//		var give_offset = $(line).data(side+'_off');
+
+		// TODO: Make this number (5) be based on marker width/height
+		var size = line.getAttribute('stroke-width') * 5;
+		return give_offset ? size : 0;
+	}
+	
+	function showPanel(on) {
+		var conn_rules = $('#connector_rules');
+		if(!conn_rules.length) {
+			conn_rules = $('<style id="connector_rules"></style>').appendTo('head');
+		} 
+		conn_rules.text(!on?"":"#tool_clone, #tool_topath, #tool_angle, #xy_panel { display: none !important; }");
+		$('#connector_panel').toggle(on);
+	}
+	
+	function setPoint(elem, pos, x, y, setMid) {
+		var i, pts = elem.points;
+		var pt = svgroot.createSVGPoint();
+		pt.x = x;
+		pt.y = y;
+		if (pos === 'end') {pos = pts.numberOfItems - 1;}
+		// TODO: Test for this on init, then use alt only if needed
+		try {
+			pts.replaceItem(pt, pos);
+		} catch(err) {
+			// Should only occur in FF which formats points attr as "n,n n,n", so just split
+			var pt_arr = elem.getAttribute("points").split(" ");
+			for (i = 0; i < pt_arr.length; i++) {
+				if (i === pos) {
+					pt_arr[i] = x + ',' + y;
+				}
+			}
+			elem.setAttribute("points",pt_arr.join(" ")); 
+		}
+		
+		if(setMid) {
+			// Add center point
+			var pt_start = pts.getItem(0);
+			var pt_end = pts.getItem(pts.numberOfItems-1);
+			setPoint(elem, 1, (pt_end.x + pt_start.x)/2, (pt_end.y + pt_start.y)/2);
+		}
+	}
+	
+	function updateLine(diff_x, diff_y) {
+		// Update line with element
+		var i = connections.length;
+		while(i--) {
+			var conn = connections[i];
+			var line = conn.connector;
+			var elem = conn.elem;
+			
+			var pre = conn.is_start?'start':'end';
+//						var sw = line.getAttribute('stroke-width') * 5;
+			
+			// Update bbox for this element
+			var bb = elData(line, pre+'_bb');
+			bb.x = conn.start_x + diff_x;
+			bb.y = conn.start_y + diff_y;
+			elData(line, pre+'_bb', bb);
+			
+			var alt_pre = conn.is_start?'end':'start';
+			
+			// Get center pt of connected element
+			var bb2 = elData(line, alt_pre+'_bb');
+			var src_x = bb2.x + bb2.width/2;
+			var src_y = bb2.y + bb2.height/2;
+			
+			// Set point of element being moved
+			var pt = getBBintersect(src_x, src_y, bb, getOffset(pre, line)); // $(line).data(pre+'_off')?sw:0
+			setPoint(line, conn.is_start ? 0 : 'end', pt.x, pt.y, true);
+			
+			// Set point of connected element
+			var pt2 = getBBintersect(pt.x, pt.y, elData(line, alt_pre + '_bb'), getOffset(alt_pre, line));
+			setPoint(line, conn.is_start ? 'end' : 0, pt2.x, pt2.y, true);
+
+		}
+	}
+	
+	function findConnectors(elems) {
+		var i;
+		if (!elems) {elems = selElems;}
+		var connectors = $(svgcontent).find(conn_sel);
+		connections = [];
+
+		// Loop through connectors to see if one is connected to the element
+		connectors.each(function() {
+			var add_this;
+			function add () {
+				if ($.inArray(this, elems) !== -1) {
+					// Pretend this element is selected
+					add_this = true;
+				}
+			}
+			var start = elData(this, "c_start");
+			var end = elData(this, "c_end");
+			
+			var parts = [getElem(start), getElem(end)];
+			for (i = 0; i < 2; i++) {
+				var c_elem = parts[i];
+				add_this = false;
+				// The connected element might be part of a selected group
+				$(c_elem).parents().each(add);
+				
+				if(!c_elem || !c_elem.parentNode) {
+					$(this).remove();
+					continue;
+				}
+				if($.inArray(c_elem, elems) !== -1 || add_this) {
+					var bb = svgCanvas.getStrokedBBox([c_elem]);
+					connections.push({
+						elem: c_elem,
+						connector: this,
+						is_start: (i === 0),
+						start_x: bb.x,
+						start_y: bb.y
+					});	
+				}
+			}
+		});
+	}
+	
+	function updateConnectors(elems) {
+		// Updates connector lines based on selected elements
+		// Is not used on mousemove, as it runs getStrokedBBox every time,
+		// which isn't necessary there.
+		var i, j;
+		findConnectors(elems);
+		if (connections.length) {
+			// Update line with element
+			i = connections.length;
+			while (i--) {
+				var conn = connections[i];
+				var line = conn.connector;
+				var elem = conn.elem;
+
+				var sw = line.getAttribute('stroke-width') * 5;
+				var pre = conn.is_start?'start':'end';
+				
+				// Update bbox for this element
+				var bb = svgCanvas.getStrokedBBox([elem]);
+				bb.x = conn.start_x;
+				bb.y = conn.start_y;
+				elData(line, pre+'_bb', bb);
+				var add_offset = elData(line, pre+'_off');
+			
+				var alt_pre = conn.is_start?'end':'start';
+				
+				// Get center pt of connected element
+				var bb2 = elData(line, alt_pre+'_bb');
+				var src_x = bb2.x + bb2.width/2;
+				var src_y = bb2.y + bb2.height/2;
+				
+				// Set point of element being moved
+				var pt = getBBintersect(src_x, src_y, bb, getOffset(pre, line));
+				setPoint(line, conn.is_start ? 0 : 'end', pt.x, pt.y, true);
+				
+				// Set point of connected element
+				var pt2 = getBBintersect(pt.x, pt.y, elData(line, alt_pre + '_bb'), getOffset(alt_pre, line));
+				setPoint(line, conn.is_start ? 'end' : 0, pt2.x, pt2.y, true);
+				
+				// Update points attribute manually for webkit
+				if (navigator.userAgent.indexOf('AppleWebKit') !== -1) {
+					var pts = line.points;
+					var len = pts.numberOfItems;
+					var pt_arr = [];
+					for (j = 0; j < len; j++) {
+						pt = pts.getItem(j);
+						pt_arr[j] = pt.x + ',' + pt.y;
+					}
+					line.setAttribute('points', pt_arr.join(' ')); 
+				}
+			}
+		}
+	}
+	
+	// Do once
+	(function() {
+		var gse = svgCanvas.groupSelectedElements;
+		
+		svgCanvas.groupSelectedElements = function() {
+			svgCanvas.removeFromSelection($(conn_sel).toArray());
+			return gse.apply(this, arguments);
+		};
+		
+		var mse = svgCanvas.moveSelectedElements;
+		
+		svgCanvas.moveSelectedElements = function() {
+			svgCanvas.removeFromSelection($(conn_sel).toArray());
+			var cmd = mse.apply(this, arguments);
+			updateConnectors();
+			return cmd;
+		};
+		
+		se_ns = svgCanvas.getEditorNS();
+	}());
+	
+	// Do on reset
+	function init() {
+		// Make sure all connectors have data set
+		$(svgcontent).find('*').each(function() { 
+			var conn = this.getAttributeNS(se_ns, "connector");
+			if(conn) {
+				this.setAttribute('class', conn_sel.substr(1));
+				var conn_data = conn.split(' ');
+				var sbb = svgCanvas.getStrokedBBox([getElem(conn_data[0])]);
+				var ebb = svgCanvas.getStrokedBBox([getElem(conn_data[1])]);
+				$(this).data('c_start',conn_data[0])
+					.data('c_end',conn_data[1])
+					.data('start_bb', sbb)
+					.data('end_bb', ebb);
+				svgCanvas.getEditorNS(true);
+			}
+		});
+//		updateConnectors();
+	}
+
+//		$(svgroot).parent().mousemove(function(e) {
+// //			if(started 
+// //				|| svgCanvas.getMode() != "connector"
+// //				|| e.target.parentNode.parentNode != svgcontent) return;
+//
+//			console.log('y')
+// //			if(e.target.parentNode.parentNode === svgcontent) {
+// //					
+// //			}
+//		});
+
+	return {
+		name: "Connector",
+		svgicons: svgEditor.curConfig.imgPath + "conn.svg",
+		buttons: [{
+			id: "mode_connect",
+			type: "mode",
+			icon: svgEditor.curConfig.imgPath + "cut.png",
+			title: "Connect two objects",
+			includeWith: {
+				button: '#tool_line',
+				isDefault: false,
+				position: 1
+			},
+			events: {
+				'click': function() {
+					svgCanvas.setMode("connector");
+				}
+			}
+		}],
+		addLangData: function(lang) {
+			return {
+				data: lang_list[lang]
+			};
+		},
+		mouseDown: function(opts) {
+			var e = opts.event;
+			start_x = opts.start_x;
+			start_y = opts.start_y;
+			var mode = svgCanvas.getMode();
+			
+			if (mode == "connector") {
+				
+				if (started) {return;}
+
+				var mouse_target = e.target;
+				
+				var parents = $(mouse_target).parents();
+				
+				if($.inArray(svgcontent, parents) !== -1) {
+					// Connectable element
+					
+					// If child of foreignObject, use parent
+					var fo = $(mouse_target).closest("foreignObject");
+					start_elem = fo.length ? fo[0] : mouse_target;
+					
+					// Get center of source element
+					var bb = svgCanvas.getStrokedBBox([start_elem]);
+					var x = bb.x + bb.width/2;
+					var y = bb.y + bb.height/2;
+					
+					started = true;
+					cur_line = addElem({
+						"element": "polyline",
+						"attr": {
+							"id": getNextId(),
+							"points": (x+','+y+' '+x+','+y+' '+start_x+','+start_y),
+							"stroke": '#' + curConfig.initStroke.color,
+							"stroke-width": (!start_elem.stroke_width || start_elem.stroke_width == 0) ? curConfig.initStroke.width : start_elem.stroke_width,
+							"fill": "none",
+							"opacity": curConfig.initStroke.opacity,
+							"style": "pointer-events:none"
+						}
+					});
+					elData(cur_line, 'start_bb', bb);
+				}
+				return {
+					started: true
+				};
+			}
+			if (mode == "select") {
+				findConnectors();
+			}
+		},
+		mouseMove: function(opts) {
+			var zoom = svgCanvas.getZoom();
+			var e = opts.event;
+			var x = opts.mouse_x/zoom;
+			var y = opts.mouse_y/zoom;
+			
+			var	diff_x = x - start_x,
+				diff_y = y - start_y;
+								
+			var mode = svgCanvas.getMode();
+			
+			if (mode == "connector" && started) {
+				
+				var sw = cur_line.getAttribute('stroke-width') * 3;
+				// Set start point (adjusts based on bb)
+				var pt = getBBintersect(x, y, elData(cur_line, 'start_bb'), getOffset('start', cur_line));
+				start_x = pt.x;
+				start_y = pt.y;
+				
+				setPoint(cur_line, 0, pt.x, pt.y, true);
+				
+				// Set end point
+				setPoint(cur_line, 'end', x, y, true);
+			} else if (mode == "select") {
+				var slen = selElems.length;
+				
+				while(slen--) {
+					var elem = selElems[slen];
+					// Look for selected connector elements
+					if(elem && elData(elem, 'c_start')) {
+						// Remove the "translate" transform given to move
+						svgCanvas.removeFromSelection([elem]);
+						svgCanvas.getTransformList(elem).clear();
+
+					}
+				}
+				if(connections.length) {
+					updateLine(diff_x, diff_y);
+
+					
+				}
+			} 
+		},
+		mouseUp: function(opts) {
+			var zoom = svgCanvas.getZoom();
+			var e = opts.event,
+				x = opts.mouse_x/zoom,
+				y = opts.mouse_y/zoom,
+				mouse_target = e.target;
+			
+			if(svgCanvas.getMode() == "connector") {
+				var fo = $(mouse_target).closest("foreignObject");
+				if (fo.length) {mouse_target = fo[0];}
+				
+				var parents = $(mouse_target).parents();
+
+				if (mouse_target == start_elem) {
+					// Start line through click
+					started = true;
+					return {
+						keep: true,
+						element: null,
+						started: started
+					};
+				}
+				if ($.inArray(svgcontent, parents) === -1) {
+					// Not a valid target element, so remove line
+					$(cur_line).remove();
+					started = false;
+					return {
+						keep: false,
+						element: null,
+						started: started
+					};
+				}
+				// Valid end element
+				end_elem = mouse_target;
+					
+				var start_id = start_elem.id, end_id = end_elem.id;
+				var conn_str = start_id + " " + end_id;
+				var alt_str = end_id + " " + start_id;
+				// Don't create connector if one already exists
+				var dupe = $(svgcontent).find(conn_sel).filter(function() {
+					var conn = this.getAttributeNS(se_ns, "connector");
+					if (conn == conn_str || conn == alt_str) {return true;}
+				});
+				if(dupe.length) {
+					$(cur_line).remove();
+					return {
+						keep: false,
+						element: null,
+						started: false
+					};
+				}
+				
+				var bb = svgCanvas.getStrokedBBox([end_elem]);
+				
+				var pt = getBBintersect(start_x, start_y, bb, getOffset('start', cur_line));
+				setPoint(cur_line, 'end', pt.x, pt.y, true);
+				$(cur_line)
+					.data("c_start", start_id)
+					.data("c_end", end_id)
+					.data("end_bb", bb);
+				se_ns = svgCanvas.getEditorNS(true);
+				cur_line.setAttributeNS(se_ns, "se:connector", conn_str);
+				cur_line.setAttribute('class', conn_sel.substr(1));
+				cur_line.setAttribute('opacity', 1);
+				svgCanvas.addToSelection([cur_line]);
+				svgCanvas.moveToBottomSelectedElement();
+				selManager.requestSelector(cur_line).showGrips(false);
+				started = false;
+				return {
+					keep: true,
+					element: cur_line,
+					started: started
+				};
+			}
+		},
+		selectedChanged: function(opts) {
+			// TODO: Find better way to skip operations if no connectors are in use
+			if(!$(svgcontent).find(conn_sel).length) {return;}
+			
+			if(svgCanvas.getMode() == 'connector') {
+				svgCanvas.setMode('select');
+			}
+			
+			// Use this to update the current selected elements
+			selElems = opts.elems;
+			
+			var i = selElems.length;
+			
+			while(i--) {
+				var elem = selElems[i];
+				if(elem && elData(elem, 'c_start')) {
+					selManager.requestSelector(elem).showGrips(false);
+					if(opts.selectedElement && !opts.multiselected) {
+						// TODO: Set up context tools and hide most regular line tools
+						showPanel(true);
+					} else {
+						showPanel(false);
+					}
+				} else {
+					showPanel(false);
+				}
+			}
+			updateConnectors();
+		},
+		elementChanged: function(opts) {
+			var elem = opts.elems[0];
+			if (elem && elem.tagName === 'svg' && elem.id === 'svgcontent') {
+				// Update svgcontent (can change on import)
+				svgcontent = elem;
+				init();
+			}
+			
+			// Has marker, so change offset
+			var start;
+			if (elem && (
+				elem.getAttribute("marker-start") ||
+				elem.getAttribute("marker-mid") ||
+				elem.getAttribute("marker-end")
+			)) {
+				start = elem.getAttribute("marker-start");
+				var mid = elem.getAttribute("marker-mid");
+				var end = elem.getAttribute("marker-end");
+				cur_line = elem;
+				$(elem)
+					.data("start_off", !!start)
+					.data("end_off", !!end);
+				
+				if (elem.tagName === 'line' && mid) {
+					// Convert to polyline to accept mid-arrow
+					
+					var x1 = Number(elem.getAttribute('x1'));
+					var x2 = Number(elem.getAttribute('x2'));
+					var y1 = Number(elem.getAttribute('y1'));
+					var y2 = Number(elem.getAttribute('y2'));
+					var id = elem.id;
+					
+					var mid_pt = (' '+((x1+x2)/2)+','+((y1+y2)/2) + ' ');
+					var pline = addElem({
+						"element": "polyline",
+						"attr": {
+							"points": (x1+','+y1+ mid_pt +x2+','+y2),
+							"stroke": elem.getAttribute('stroke'),
+							"stroke-width": elem.getAttribute('stroke-width'),
+							"marker-mid": mid,
+							"fill": "none",
+							"opacity": elem.getAttribute('opacity') || 1
+						}
+					});
+					$(elem).after(pline).remove();
+					svgCanvas.clearSelection();
+					pline.id = id;
+					svgCanvas.addToSelection([pline]);
+					elem = pline;
+				}
+			}
+			// Update line if it's a connector
+			if (elem.getAttribute('class') == conn_sel.substr(1)) {
+				start = getElem(elData(elem, 'c_start'));
+				updateConnectors([start]);
+			} else {
+				updateConnectors();
+			}
+		},
+		toolButtonStateUpdate: function(opts) {
+			if (opts.nostroke) {
+				if ($('#mode_connect').hasClass('tool_button_current')) {
+					svgEditor.clickSelect();
+				}
+			}
+			$('#mode_connect')
+				.toggleClass('disabled',opts.nostroke);
+		}
+	};
+});

+ 112 - 0
main/inc/lib/javascript/svgedit/extensions/ext-eyedropper.js

@@ -0,0 +1,112 @@
+/*globals svgEditor, svgedit, $*/
+/*jslint vars: true, eqeq: true*/
+/*
+ * ext-eyedropper.js
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2010 Jeff Schiller
+ *
+ */
+
+// Dependencies:
+// 1) jQuery
+// 2) history.js
+// 3) svg_editor.js
+// 4) svgcanvas.js
+
+svgEditor.addExtension("eyedropper", function(S) {'use strict';
+	var // NS = svgedit.NS,
+		// svgcontent = S.svgcontent,
+		// svgdoc = S.svgroot.parentNode.ownerDocument,
+		svgCanvas = svgEditor.canvas,
+		ChangeElementCommand = svgedit.history.ChangeElementCommand,
+		addToHistory = function(cmd) { svgCanvas.undoMgr.addCommandToHistory(cmd); },
+		currentStyle = {
+			fillPaint: "red", fillOpacity: 1.0,
+			strokePaint: "black", strokeOpacity: 1.0, 
+			strokeWidth: 5, strokeDashArray: null,
+			opacity: 1.0,
+			strokeLinecap: 'butt',
+			strokeLinejoin: 'miter'
+		};
+
+	function getStyle(opts) {
+		// if we are in eyedropper mode, we don't want to disable the eye-dropper tool
+		var mode = svgCanvas.getMode();
+		if (mode == "eyedropper") {return;}
+
+		var elem = null;
+		var tool = $('#tool_eyedropper');
+		// enable-eye-dropper if one element is selected
+		if (!opts.multiselected && opts.elems[0] &&
+			$.inArray(opts.elems[0].nodeName, ['svg', 'g', 'use']) === -1
+		) {
+			elem = opts.elems[0];
+			tool.removeClass('disabled');
+			// grab the current style
+			currentStyle.fillPaint = elem.getAttribute("fill") || "black";
+			currentStyle.fillOpacity = elem.getAttribute("fill-opacity") || 1.0;
+			currentStyle.strokePaint = elem.getAttribute("stroke");
+			currentStyle.strokeOpacity = elem.getAttribute("stroke-opacity") || 1.0;
+			currentStyle.strokeWidth = elem.getAttribute("stroke-width");
+			currentStyle.strokeDashArray = elem.getAttribute("stroke-dasharray");
+			currentStyle.strokeLinecap = elem.getAttribute("stroke-linecap");
+			currentStyle.strokeLinejoin = elem.getAttribute("stroke-linejoin");
+			currentStyle.opacity = elem.getAttribute("opacity") || 1.0;
+		}
+		// disable eye-dropper tool
+		else {
+			tool.addClass('disabled');
+		}
+
+	}
+	
+	return {
+		name: "eyedropper",
+		svgicons: svgEditor.curConfig.extPath + "eyedropper-icon.xml",
+		buttons: [{
+			id: "tool_eyedropper",
+			type: "mode",
+			title: "Eye Dropper Tool",
+			key: "I",
+			events: {
+				"click": function() {
+					svgCanvas.setMode("eyedropper");
+				}
+			}
+		}],
+		
+		// if we have selected an element, grab its paint and enable the eye dropper button
+		selectedChanged: getStyle,
+		elementChanged: getStyle,
+		
+		mouseDown: function(opts) {
+			var mode = svgCanvas.getMode();
+			if (mode == "eyedropper") {
+				var e = opts.event;
+				var target = e.target;
+				if ($.inArray(target.nodeName, ['svg', 'g', 'use']) === -1) {
+					var changes = {};
+
+					var change = function(elem, attrname, newvalue) {
+						changes[attrname] = elem.getAttribute(attrname);
+						elem.setAttribute(attrname, newvalue);
+					};
+					
+					if (currentStyle.fillPaint) {change(target, "fill", currentStyle.fillPaint);}
+					if (currentStyle.fillOpacity) {change(target, "fill-opacity", currentStyle.fillOpacity);}
+					if (currentStyle.strokePaint) {change(target, "stroke", currentStyle.strokePaint);}
+					if (currentStyle.strokeOpacity) {change(target, "stroke-opacity", currentStyle.strokeOpacity);}
+					if (currentStyle.strokeWidth) {change(target, "stroke-width", currentStyle.strokeWidth);}
+					if (currentStyle.strokeDashArray) {change(target, "stroke-dasharray", currentStyle.strokeDashArray);}
+					if (currentStyle.opacity) {change(target, "opacity", currentStyle.opacity);}
+					if (currentStyle.strokeLinecap) {change(target, "stroke-linecap", currentStyle.strokeLinecap);}
+					if (currentStyle.strokeLinejoin) {change(target, "stroke-linejoin", currentStyle.strokeLinejoin);}
+					
+					addToHistory(new ChangeElementCommand(target, changes));
+				}
+			}
+		}
+	};
+});

+ 268 - 0
main/inc/lib/javascript/svgedit/extensions/ext-foreignobject.js

@@ -0,0 +1,268 @@
+/*globals svgEditor, svgedit, svgCanvas, $*/
+/*jslint vars: true, eqeq: true, todo: true*/
+/*
+ * ext-foreignobject.js
+ *
+ * Licensed under the Apache License, Version 2
+ *
+ * Copyright(c) 2010 Jacques Distler 
+ * Copyright(c) 2010 Alexis Deveria 
+ *
+ */
+
+svgEditor.addExtension("foreignObject", function(S) {
+	var NS = svgedit.NS,
+		Utils = svgedit.utilities,
+		svgcontent = S.svgcontent,
+		addElem = S.addSvgElementFromJson,
+		selElems,
+		editingforeign = false,
+		svgdoc = S.svgroot.parentNode.ownerDocument,
+		started,
+		newFO;
+
+	var properlySourceSizeTextArea = function () {
+		// TODO: remove magic numbers here and get values from CSS
+		var height = $('#svg_source_container').height() - 80;
+		$('#svg_source_textarea').css('height', height);
+	};
+
+	function showPanel(on) {
+		var fc_rules = $('#fc_rules');
+		if(!fc_rules.length) {
+			fc_rules = $('<style id="fc_rules"></style>').appendTo('head');
+		}
+		fc_rules.text(!on?"":" #tool_topath { display: none !important; }");
+		$('#foreignObject_panel').toggle(on);
+	}
+
+	function toggleSourceButtons(on) {
+		$('#tool_source_save, #tool_source_cancel').toggle(!on);
+		$('#foreign_save, #foreign_cancel').toggle(on);
+	}
+
+	// Function: setForeignString(xmlString, elt)
+	// This function sets the content of element elt to the input XML.
+	//
+	// Parameters:
+	// xmlString - The XML text.
+	// elt - the parent element to append to
+	//
+	// Returns:
+	// This function returns false if the set was unsuccessful, true otherwise.
+	function setForeignString(xmlString) {
+		var elt = selElems[0];
+		try {
+			// convert string into XML document
+			var newDoc = Utils.text2xml('<svg xmlns="' + NS.SVG + '" xmlns:xlink="' + NS.XLINK + '">' + xmlString + '</svg>');
+			// run it through our sanitizer to remove anything we do not support
+			S.sanitizeSvg(newDoc.documentElement);
+			elt.parentNode.replaceChild(svgdoc.importNode(newDoc.documentElement.firstChild, true), elt);
+			S.call("changed", [elt]);
+			svgCanvas.clearSelection();
+		} catch(e) {
+			console.log(e);
+			return false;
+		}
+
+		return true;
+	}
+
+	function showForeignEditor() {
+		var elt = selElems[0];
+		if (!elt || editingforeign) {return;}
+		editingforeign = true;
+		toggleSourceButtons(true);
+		elt.removeAttribute('fill');
+
+		var str = S.svgToString(elt, 0);
+		$('#svg_source_textarea').val(str);
+		$('#svg_source_editor').fadeIn();
+		properlySourceSizeTextArea();
+		$('#svg_source_textarea').focus();
+	}
+
+	function setAttr(attr, val) {
+		svgCanvas.changeSelectedAttribute(attr, val);
+		S.call("changed", selElems);
+	}
+
+	return {
+		name: "foreignObject",
+		svgicons: svgEditor.curConfig.extPath + "foreignobject-icons.xml",
+		buttons: [{
+			id: "tool_foreign",
+			type: "mode",
+			title: "Foreign Object Tool",
+			events: {
+				'click': function() {
+					svgCanvas.setMode('foreign');
+				}
+			}
+		},{
+			id: "edit_foreign",
+			type: "context",
+			panel: "foreignObject_panel",
+			title: "Edit ForeignObject Content",
+			events: {
+				'click': function() {
+					showForeignEditor();
+				}
+			}
+		}],
+
+		context_tools: [{
+			type: "input",
+			panel: "foreignObject_panel",
+			title: "Change foreignObject's width",
+			id: "foreign_width",
+			label: "w",
+			size: 3,
+			events: {
+				change: function() {
+					setAttr('width', this.value);
+				}
+			}
+		},{
+			type: "input",
+			panel: "foreignObject_panel",
+			title: "Change foreignObject's height",
+			id: "foreign_height",
+			label: "h",
+			events: {
+				change: function() {
+					setAttr('height', this.value);
+				}
+			}
+		}, {
+			type: "input",
+			panel: "foreignObject_panel",
+			title: "Change foreignObject's font size",
+			id: "foreign_font_size",
+			label: "font-size",
+			size: 2,
+			defval: 16,
+			events: {
+				change: function() {
+					setAttr('font-size', this.value);
+				}
+			}
+		}
+
+		],
+		callback: function() {
+			$('#foreignObject_panel').hide();
+
+			var endChanges = function() {
+				$('#svg_source_editor').hide();
+				editingforeign = false;
+				$('#svg_source_textarea').blur();
+				toggleSourceButtons(false);
+			};
+
+			// TODO: Needs to be done after orig icon loads
+			setTimeout(function() {
+				// Create source save/cancel buttons
+				var save = $('#tool_source_save').clone()
+					.hide().attr('id', 'foreign_save').unbind()
+					.appendTo("#tool_source_back").click(function() {
+
+						if (!editingforeign) {return;}
+
+						if (!setForeignString($('#svg_source_textarea').val())) {
+							$.confirm("Errors found. Revert to original?", function(ok) {
+								if(!ok) {return false;}
+								endChanges();
+							});
+						} else {
+							endChanges();
+						}
+						// setSelectMode();	
+					});
+
+				var cancel = $('#tool_source_cancel').clone()
+					.hide().attr('id', 'foreign_cancel').unbind()
+					.appendTo("#tool_source_back").click(function() {
+						endChanges();
+					});
+			}, 3000);
+		},
+		mouseDown: function(opts) {
+			var e = opts.event;
+
+			if(svgCanvas.getMode() == "foreign") {
+
+				started = true;
+				newFO = S.addSvgElementFromJson({
+					"element": "foreignObject",
+					"attr": {
+						"x": opts.start_x,
+						"y": opts.start_y,
+						"id": S.getNextId(),
+						"font-size": 16, //cur_text.font_size,
+						"width": "48",
+						"height": "20",
+						"style": "pointer-events:inherit"
+					}
+				});
+				var m = svgdoc.createElementNS(NS.MATH, 'math');
+				m.setAttributeNS(NS.XMLNS, 'xmlns', NS.MATH);
+				m.setAttribute('display', 'inline');
+				var mi = svgdoc.createElementNS(NS.MATH, 'mi');
+				mi.setAttribute('mathvariant', 'normal');
+				mi.textContent = "\u03A6";
+				var mo = svgdoc.createElementNS(NS.MATH, 'mo');
+				mo.textContent = "\u222A";
+				var mi2 = svgdoc.createElementNS(NS.MATH, 'mi');
+				mi2.textContent = "\u2133";
+				m.appendChild(mi);
+				m.appendChild(mo);
+				m.appendChild(mi2);
+				newFO.appendChild(m);
+				return {
+					started: true
+				};
+			}
+		},
+		mouseUp: function(opts) {
+			var e = opts.event;
+			if(svgCanvas.getMode() == "foreign" && started) {
+				var attrs = $(newFO).attr(["width", "height"]);
+				var keep = (attrs.width != 0 || attrs.height != 0);
+				svgCanvas.addToSelection([newFO], true);
+
+				return {
+					keep: keep,
+					element: newFO
+				};
+
+			}
+
+		},
+		selectedChanged: function(opts) {
+			// Use this to update the current selected elements
+			selElems = opts.elems;
+
+			var i = selElems.length;
+
+			while(i--) {
+				var elem = selElems[i];
+				if(elem && elem.tagName === 'foreignObject') {
+					if(opts.selectedElement && !opts.multiselected) {
+						$('#foreign_font_size').val(elem.getAttribute("font-size"));
+						$('#foreign_width').val(elem.getAttribute("width"));
+						$('#foreign_height').val(elem.getAttribute("height"));
+						showPanel(true);
+					} else {
+						showPanel(false);
+					}
+				} else {
+					showPanel(false);
+				}
+			}
+		},
+		elementChanged: function(opts) {
+			var elem = opts.elems[0];
+		}
+	};
+});

+ 162 - 0
main/inc/lib/javascript/svgedit/extensions/ext-grid.js

@@ -0,0 +1,162 @@
+/*globals svgEditor, svgedit, svgCanvas, $*/
+/*jslint vars: true*/
+/*
+ * ext-grid.js
+ *
+ * Licensed under the Apache License, Version 2
+ *
+ * Copyright(c) 2010 Redou Mine
+ * Copyright(c) 2010 Alexis Deveria
+ *
+ */
+
+// Dependencies:
+// 1) units.js
+// 2) everything else
+
+svgEditor.addExtension('view_grid', function() { 'use strict';
+
+	var NS = svgedit.NS,
+		svgdoc = document.getElementById('svgcanvas').ownerDocument,
+		showGrid = svgEditor.curConfig.showGrid || false,
+		assignAttributes = svgCanvas.assignAttributes,
+		hcanvas = document.createElement('canvas'),
+		canvBG = $('#canvasBackground'),
+		units = svgedit.units.getTypeMap(),
+		intervals = [0.01, 0.1, 1, 10, 100, 1000];
+
+	$(hcanvas).hide().appendTo('body');
+
+	var canvasGrid = svgdoc.createElementNS(NS.SVG, 'svg');
+	assignAttributes(canvasGrid, {
+		'id': 'canvasGrid',
+		'width': '100%',
+		'height': '100%',
+		'x': 0,
+		'y': 0,
+		'overflow': 'visible',
+		'display': 'none'
+	});
+	canvBG.append(canvasGrid);
+
+	// grid-pattern
+	var gridPattern = svgdoc.createElementNS(NS.SVG, 'pattern');
+	assignAttributes(gridPattern, {
+		'id': 'gridpattern',
+		'patternUnits': 'userSpaceOnUse',
+		'x': 0, //-(value.strokeWidth / 2), // position for strokewidth
+		'y': 0, //-(value.strokeWidth / 2), // position for strokewidth
+		'width': 100,
+		'height': 100
+	});
+
+	var gridimg = svgdoc.createElementNS(NS.SVG, 'image');
+	assignAttributes(gridimg, {
+		'x': 0,
+		'y': 0,
+		'width': 100,
+		'height': 100
+	});
+	gridPattern.appendChild(gridimg);
+	$('#svgroot defs').append(gridPattern);
+
+	// grid-box
+	var gridBox = svgdoc.createElementNS(NS.SVG, 'rect');
+	assignAttributes(gridBox, {
+		'width': '100%',
+		'height': '100%',
+		'x': 0,
+		'y': 0,
+		'stroke-width': 0,
+		'stroke': 'none',
+		'fill': 'url(#gridpattern)',
+		'style': 'pointer-events: none; display:visible;'
+	});
+	$('#canvasGrid').append(gridBox);
+
+	function updateGrid(zoom) {
+		var i;
+		// TODO: Try this with <line> elements, then compare performance difference
+		var unit = units[svgEditor.curConfig.baseUnit]; // 1 = 1px
+		var u_multi = unit * zoom;
+		// Calculate the main number interval
+		var raw_m = 100 / u_multi;
+		var multi = 1;
+		for (i = 0; i < intervals.length; i++) {
+			var num = intervals[i];
+			multi = num;
+			if (raw_m <= num) {
+				break;
+			}
+		}
+		var big_int = multi * u_multi;
+
+		// Set the canvas size to the width of the container
+		hcanvas.width = big_int;
+		hcanvas.height = big_int;
+		var ctx = hcanvas.getContext('2d');
+		var cur_d = 0.5;
+		var part = big_int / 10;
+
+		ctx.globalAlpha = 0.2;
+		ctx.strokeStyle = svgEditor.curConfig.gridColor;
+		for (i = 1; i < 10; i++) {
+			var sub_d = Math.round(part * i) + 0.5;
+			// var line_num = (i % 2)?12:10;
+			var line_num = 0;
+			ctx.moveTo(sub_d, big_int);
+			ctx.lineTo(sub_d, line_num);
+			ctx.moveTo(big_int, sub_d);
+			ctx.lineTo(line_num ,sub_d);
+		}
+		ctx.stroke();
+		ctx.beginPath();
+		ctx.globalAlpha = 0.5;
+		ctx.moveTo(cur_d, big_int);
+		ctx.lineTo(cur_d, 0);
+
+		ctx.moveTo(big_int, cur_d);
+		ctx.lineTo(0, cur_d);
+		ctx.stroke();
+
+		var datauri = hcanvas.toDataURL('image/png');
+		gridimg.setAttribute('width', big_int);
+		gridimg.setAttribute('height', big_int);
+		gridimg.parentNode.setAttribute('width', big_int);
+		gridimg.parentNode.setAttribute('height', big_int);
+		svgCanvas.setHref(gridimg, datauri);
+	}
+
+	function gridUpdate () {
+		if (showGrid) {
+			updateGrid(svgCanvas.getZoom());
+		}
+		$('#canvasGrid').toggle(showGrid);
+		$('#view_grid').toggleClass('push_button_pressed tool_button');
+	}
+	return {
+		name: 'view_grid',
+		svgicons: svgEditor.curConfig.extPath + 'grid-icon.xml',
+
+		zoomChanged: function(zoom) {
+			if (showGrid) {updateGrid(zoom);}
+		},
+		callback: function () {
+			if (showGrid) {
+				gridUpdate();
+			}
+		},
+		buttons: [{
+			id: 'view_grid',
+			type: 'context',
+			panel: 'editor_panel',
+			title: 'Show/Hide Grid',
+			events: {
+				click: function() {
+					svgEditor.curConfig.showGrid = showGrid = !showGrid;
+					gridUpdate();
+				}
+			}
+		}]
+	};
+});

+ 80 - 0
main/inc/lib/javascript/svgedit/extensions/ext-helloworld.js

@@ -0,0 +1,80 @@
+/*globals svgEditor, svgCanvas, $*/
+/*jslint vars: true, eqeq: true*/
+/*
+ * ext-helloworld.js
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2010 Alexis Deveria
+ *
+ */
+ 
+/* 
+	This is a very basic SVG-Edit extension. It adds a "Hello World" button in
+	the left panel. Clicking on the button, and then the canvas will show the
+	user the point on the canvas that was clicked on.
+*/
+ 
+svgEditor.addExtension("Hello World", function() {'use strict';
+
+		return {
+			name: "Hello World",
+			// For more notes on how to make an icon file, see the source of
+			// the helloworld-icon.xml
+			svgicons: svgEditor.curConfig.extPath + "helloworld-icon.xml",
+			
+			// Multiple buttons can be added in this array
+			buttons: [{
+				// Must match the icon ID in helloworld-icon.xml
+				id: "hello_world", 
+				
+				// This indicates that the button will be added to the "mode"
+				// button panel on the left side
+				type: "mode", 
+				
+				// Tooltip text
+				title: "Say 'Hello World'", 
+				
+				// Events
+				events: {
+					'click': function() {
+						// The action taken when the button is clicked on.
+						// For "mode" buttons, any other button will 
+						// automatically be de-pressed.
+						svgCanvas.setMode("hello_world");
+					}
+				}
+			}],
+			// This is triggered when the main mouse button is pressed down 
+			// on the editor canvas (not the tool panels)
+			mouseDown: function() {
+				// Check the mode on mousedown
+				if(svgCanvas.getMode() == "hello_world") {
+				
+					// The returned object must include "started" with 
+					// a value of true in order for mouseUp to be triggered
+					return {started: true};
+				}
+			},
+			
+			// This is triggered from anywhere, but "started" must have been set
+			// to true (see above). Note that "opts" is an object with event info
+			mouseUp: function(opts) {
+				// Check the mode on mouseup
+				if(svgCanvas.getMode() == "hello_world") {
+					var zoom = svgCanvas.getZoom();
+					
+					// Get the actual coordinate by dividing by the zoom value
+					var x = opts.mouse_x / zoom;
+					var y = opts.mouse_y / zoom;
+					
+					var text = "Hello World!\n\nYou clicked here: " 
+						+ x + ", " + y;
+						
+					// Show the text using the custom alert function
+					$.alert(text);
+				}
+			}
+		};
+});
+

+ 464 - 0
main/inc/lib/javascript/svgedit/extensions/ext-imagelib.js

@@ -0,0 +1,464 @@
+/*globals $, svgEditor, svgedit, svgCanvas, DOMParser*/
+/*jslint vars: true, eqeq: true, es5: true, todo: true */
+/*
+ * ext-imagelib.js
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2010 Alexis Deveria
+ *
+ */
+
+svgEditor.addExtension("imagelib", function() {'use strict';
+
+	var uiStrings = svgEditor.uiStrings;
+
+	$.extend(uiStrings, {
+		imagelib: {
+			select_lib: 'Select an image library',
+			show_list: 'Show library list',
+			import_single: 'Import single',
+			import_multi: 'Import multiple',
+			open: 'Open as new document'
+		}
+	});
+
+	var img_libs = [{
+			name: 'Demo library (local)',
+			url: svgEditor.curConfig.extPath + 'imagelib/index.html',
+			description: 'Demonstration library for SVG-edit on this server'
+		},
+		{
+			name: 'IAN Symbol Libraries',
+			url: 'https://ian.umces.edu/symbols/catalog/svgedit/album_chooser.php',
+			description: 'Free library of illustrations'
+		},
+		{
+			name: 'Openclipart',
+			url: 'https://openclipart.org/svgedit',
+			description: 'Share and Use Images. Over 50,000 Public Domain SVG Images and Growing.'
+		}
+	];
+
+	function closeBrowser() {
+		$('#imgbrowse_holder').hide();
+	}
+
+	function importImage(url) {
+		var newImage = svgCanvas.addSvgElementFromJson({
+			"element": "image",
+			"attr": {
+				"x": 0,
+				"y": 0,
+				"width": 0,
+				"height": 0,
+				"id": svgCanvas.getNextId(),
+				"style": "pointer-events:inherit"
+			}
+		});
+		svgCanvas.clearSelection();
+		svgCanvas.addToSelection([newImage]);
+		svgCanvas.setImageURL(url);
+	}
+
+	var mode = 's';
+	var multi_arr = [];
+	var tranfer_stopped = false;
+	var pending = {};
+	var preview, submit;
+
+	window.addEventListener("message", function(evt) {
+		// Receive postMessage data
+		var response = evt.data;
+		
+		if (!response || typeof response !== "string") { // Todo: Should namespace postMessage API for this extension and filter out here
+			// Do nothing
+			return;
+		}
+		try { // This block can be removed if embedAPI moves away from a string to an object (if IE9 support not needed)
+			var res = JSON.parse(response);
+			if (res.namespace) { // Part of embedAPI communications
+				return;
+			}
+		}
+		catch (e) {}
+		
+		var char1 = response.charAt(0);
+		var id;
+		var svg_str;
+		var img_str;
+		
+		if (char1 != "{" && tranfer_stopped) {
+			tranfer_stopped = false;
+			return;
+		}
+		
+		if (char1 == '|') {
+			var secondpos = response.indexOf('|', 1);
+			id = response.substr(1, secondpos-1);
+			response = response.substr(secondpos+1);
+			char1 = response.charAt(0);
+		}
+		
+		
+		// Hide possible transfer dialog box
+		$('#dialog_box').hide();
+		var entry, cur_meta;
+		switch (char1) {
+			case '{':
+				// Metadata
+				tranfer_stopped = false;
+				cur_meta = JSON.parse(response);
+				
+				pending[cur_meta.id] = cur_meta;
+				
+				var name = (cur_meta.name || 'file');
+				
+				var message = uiStrings.notification.retrieving.replace('%s', name);
+				
+				if (mode != 'm') {
+					$.process_cancel(message, function() {
+						tranfer_stopped = true;
+						// Should a message be sent back to the frame?
+						
+						$('#dialog_box').hide();
+					});
+				} else {
+					entry = $('<div>' + message + '</div>').data('id', cur_meta.id);
+					preview.append(entry);
+					cur_meta.entry = entry;
+				}
+				
+				return;
+			case '<':
+				svg_str = true;
+				break;
+			case 'd':
+				if (response.indexOf('data:image/svg+xml') === 0) {
+					var pre = 'data:image/svg+xml;base64,';
+					var src = response.substring(pre.length);
+					response = svgedit.utilities.decode64(src);
+					svg_str = true;
+					break;
+				} else if (response.indexOf('data:image/') === 0) {
+					img_str = true;
+					break;
+				}
+				// Else fall through
+			default:
+				// TODO: See if there's a way to base64 encode the binary data stream
+//				var str = 'data:;base64,' + svgedit.utilities.encode64(response, true);
+			
+				// Assume it's raw image data
+//				importImage(str);
+			
+				// Don't give warning as postMessage may have been used by something else
+				if (mode !== 'm') {
+					closeBrowser();
+				} else {
+					pending[id].entry.remove();
+				}
+//				$.alert('Unexpected data was returned: ' + response, function() {
+//					if (mode !== 'm') {
+//						closeBrowser();
+//					} else {
+//						pending[id].entry.remove();
+//					}
+//				});
+				return;
+		}
+		
+		switch (mode) {
+			case 's':
+				// Import one
+				if (svg_str) {
+					svgCanvas.importSvgString(response);
+				} else if (img_str) {
+					importImage(response);
+				}
+				closeBrowser();
+				break;
+			case 'm':
+				// Import multiple
+				multi_arr.push([(svg_str ? 'svg' : 'img'), response]);
+				var title;
+				cur_meta = pending[id];
+				if (svg_str) {
+					if (cur_meta && cur_meta.name) {
+						title = cur_meta.name;
+					} else {
+						// Try to find a title
+						var xml = new DOMParser().parseFromString(response, 'text/xml').documentElement;
+						title = $(xml).children('title').first().text() || '(SVG #' + response.length + ')';
+					}
+					if (cur_meta) {
+						preview.children().each(function() {
+							if ($(this).data('id') == id) {
+								if (cur_meta.preview_url) {
+									$(this).html('<img src="' + cur_meta.preview_url + '">' + title);
+								} else {
+									$(this).text(title);
+								}
+								submit.removeAttr('disabled');
+							}
+						});
+					} else {
+						preview.append('<div>'+title+'</div>');
+						submit.removeAttr('disabled');
+					}
+				} else {
+					if (cur_meta && cur_meta.preview_url) {
+						title = cur_meta.name || '';
+					}
+					if (cur_meta && cur_meta.preview_url) {
+						entry = '<img src="' + cur_meta.preview_url + '">' + title;
+					} else {
+						entry = '<img src="' + response + '">';
+					}
+				
+					if (cur_meta) {
+						preview.children().each(function() {
+							if ($(this).data('id') == id) {
+								$(this).html(entry);
+								submit.removeAttr('disabled');
+							}
+						});
+					} else {
+						preview.append($('<div>').append(entry));
+						submit.removeAttr('disabled');
+					}
+
+				}
+				break;
+			case 'o':
+				// Open
+				if (!svg_str) {break;}
+				svgEditor.openPrep(function(ok) {
+					if (!ok) {return;}
+					svgCanvas.clear();
+					svgCanvas.setSvgString(response);
+					// updateCanvas();
+				});
+				closeBrowser();
+				break;
+		}
+	}, true);
+
+	function toggleMulti(show) {
+	
+		$('#lib_framewrap, #imglib_opts').css({right: (show ? 200 : 10)});
+		if (!preview) {
+			preview = $('<div id=imglib_preview>').css({
+				position: 'absolute',
+				top: 45,
+				right: 10,
+				width: 180,
+				bottom: 45,
+				background: '#fff',
+				overflow: 'auto'
+			}).insertAfter('#lib_framewrap');
+			
+			submit = $('<button disabled>Import selected</button>')
+				.appendTo('#imgbrowse')
+				.on("click touchend", function() {
+				$.each(multi_arr, function(i) {
+					var type = this[0];
+					var data = this[1];
+					if (type == 'svg') {
+						svgCanvas.importSvgString(data);
+					} else {
+						importImage(data);
+					}
+					svgCanvas.moveSelectedElements(i*20, i*20, false);
+				});
+				preview.empty();
+				multi_arr = [];
+				$('#imgbrowse_holder').hide();
+			}).css({
+				position: 'absolute',
+				bottom: 10,
+				right: -10
+			});
+
+		}
+		
+		preview.toggle(show);
+		submit.toggle(show);
+	}
+
+	function showBrowser() {
+
+		var browser = $('#imgbrowse');
+		if (!browser.length) {
+			$('<div id=imgbrowse_holder><div id=imgbrowse class=toolbar_button>\
+			</div></div>').insertAfter('#svg_docprops');
+			browser = $('#imgbrowse');
+
+			var all_libs = uiStrings.imagelib.select_lib;
+
+			var lib_opts = $('<ul id=imglib_opts>').appendTo(browser);
+			var frame = $('<iframe/>').prependTo(browser).hide().wrap('<div id=lib_framewrap>');
+			
+			var header = $('<h1>').prependTo(browser).text(all_libs).css({
+				position: 'absolute',
+				top: 0,
+				left: 0,
+				width: '100%'
+			});
+			
+			var cancel = $('<button>' + uiStrings.common.cancel + '</button>')
+				.appendTo(browser)
+				.on("click touchend", function() {
+				$('#imgbrowse_holder').hide();
+			}).css({
+				position: 'absolute',
+				top: 5,
+				right: -10
+			});
+			
+			var leftBlock = $('<span>').css({position:'absolute',top:5,left:10}).appendTo(browser);
+			
+			var back = $('<button hidden>' + uiStrings.imagelib.show_list + '</button>')
+				.appendTo(leftBlock)
+				.on("click touchend", function() {
+				frame.attr('src', 'about:blank').hide();
+				lib_opts.show();
+				header.text(all_libs);
+				back.hide();
+			}).css({
+				'margin-right': 5
+			}).hide();
+			
+			var type = $('<select><option value=s>' + 
+			uiStrings.imagelib.import_single + '</option><option value=m>' +
+			uiStrings.imagelib.import_multi + '</option><option value=o>' +
+			uiStrings.imagelib.open + '</option></select>').appendTo(leftBlock).change(function() {
+				mode = $(this).val();
+				switch (mode) {
+					case 's':
+					case 'o':
+						toggleMulti(false);
+						break;
+					
+					case 'm':
+						// Import multiple
+						toggleMulti(true);
+						break;
+				}
+			}).css({
+				'margin-top': 10
+			});
+			
+			cancel.prepend($.getSvgIcon('cancel', true));
+			back.prepend($.getSvgIcon('tool_imagelib', true));
+			
+			$.each(img_libs, function(i, opts) {
+				$('<li>')
+					.appendTo(lib_opts)
+					.text(opts.name)
+					.on("click touchend", function() {
+					frame.attr('src', opts.url).show();
+					header.text(opts.name);
+					lib_opts.hide();
+					back.show();
+				}).append('<span>' + opts.description + '</span>');
+			});
+			
+		} else {
+			$('#imgbrowse_holder').show();
+		}
+	}
+	
+	return {
+		svgicons: svgEditor.curConfig.extPath + "ext-imagelib.xml",
+		buttons: [{
+			id: "tool_imagelib",
+			type: "app_menu", // _flyout
+			position: 4,
+			title: "Image library",
+			events: {
+				"mouseup": showBrowser
+			}
+		}],
+		callback: function() {
+		
+			$('<style>').text('\
+				#imgbrowse_holder {\
+					position: absolute;\
+					top: 0;\
+					left: 0;\
+					width: 100%;\
+					height: 100%;\
+					background-color: rgba(0, 0, 0, .5);\
+					z-index: 5;\
+				}\
+				\
+				#imgbrowse {\
+					position: absolute;\
+					top: 25px;\
+					left: 25px;\
+					right: 25px;\
+					bottom: 25px;\
+					min-width: 300px;\
+					min-height: 200px;\
+					background: #B0B0B0;\
+					border: 1px outset #777;\
+				}\
+				#imgbrowse h1 {\
+					font-size: 20px;\
+					margin: .4em;\
+					text-align: center;\
+				}\
+				#lib_framewrap,\
+				#imgbrowse > ul {\
+					position: absolute;\
+					top: 45px;\
+					left: 10px;\
+					right: 10px;\
+					bottom: 10px;\
+					background: white;\
+					margin: 0;\
+					padding: 0;\
+				}\
+				#imgbrowse > ul {\
+					overflow: auto;\
+				}\
+				#imgbrowse > div {\
+					border: 1px solid #666;\
+				}\
+				#imglib_preview > div {\
+					padding: 5px;\
+					font-size: 12px;\
+				}\
+				#imglib_preview img {\
+					display: block;\
+					margin: 0 auto;\
+					max-height: 100px;\
+				}\
+				#imgbrowse li {\
+					list-style: none;\
+					padding: .5em;\
+					background: #E8E8E8;\
+					border-bottom: 1px solid #B0B0B0;\
+					line-height: 1.2em;\
+					font-style: sans-serif;\
+					}\
+				#imgbrowse li > span {\
+					color: #666;\
+					font-size: 15px;\
+					display: block;\
+					}\
+				#imgbrowse li:hover {\
+					background: #FFC;\
+					cursor: pointer;\
+					}\
+				#imgbrowse iframe {\
+					width: 100%;\
+					height: 100%;\
+					border: 0;\
+				}\
+			').appendTo('head');
+		}
+	};
+});
+

+ 14 - 0
main/inc/lib/javascript/svgedit/extensions/ext-imagelib.xml

@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+	<g id="tool_imagelib">
+<svg width="201" height="211" xmlns="http://www.w3.org/2000/svg">
+ <g>
+  <path fill="#efe8b8" stroke="#d6c47c" stroke-linecap="round" d="m2.75,49.51761l56.56,-46.26761c12.73,8.25 25.71001,7 46.44,0.75l-56.03999,47.23944l-22.72002,25.01056l-24.23999,-26.73239z" id="svg_2" stroke-width="7"/>
+  <path fill="#a03333" stroke="#3f3f3f" d="m3.75,203.25002c14.33301,7 30.66699,7 46,0l0,-152.00002c-14.66699,8 -32.33301,8 -47,0l1,152.00002zm45.75,-152.25002l56.25,-46.75l0,151l-56,48.00002m-47.25,-154.25002l57.25,-46.5" id="svg_1" stroke-width="7" stroke-linecap="round"/>
+  <path fill="#efe8b8" stroke="#d6c47c" stroke-linecap="round" d="m49.75,49.51801l56.56,-46.26801c12.72998,8.25 25.71002,7 46.44,0.75l-56.03998,47.239l-22.72003,25.011l-24.23999,-26.73199z" stroke-width="7" id="svg_5"/>
+  <path fill="#2f8e2f" stroke="#3f3f3f" d="m50.75,202.25c14.33301,7 30.66699,7.04253 46,0.04253l0,-151.04253c-14.66699,8 -32.33301,8 -47,0l1,151zm45.75,-151.25l56.25,-46.75l0,144.01219l-56,51.98782m-47.25,-151.25002l57.25,-46.5" stroke-width="7" stroke-linecap="round" id="svg_6"/>
+  <path fill="#efe8b8" stroke="#d6c47c" stroke-linecap="round" d="m95.75,49.51801l56.56,-46.26801c12.72998,8.25 25.71002,7 46.44,0.75l-56.03998,47.239l-22.72003,25.011l-24.23999,-26.73199z" stroke-width="7" id="svg_10"/>
+  <path fill="#336393" stroke="#3f3f3f" d="m96.75,200.29445c14.33301,7 30.66699,7 46,0l0,-149.04445c-14.66699,8 -32.33301,8 -47,0l1,149.04445zm45.75,-149.29445l56.25,-46.75l0,148.04445l-56,48m-47.25,-151.29445l57.25,-46.5" stroke-width="7" stroke-linecap="round" id="svg_11"/>
+ </g>
+</svg>
+	</g>
+</svg>

+ 579 - 0
main/inc/lib/javascript/svgedit/extensions/ext-markers.js

@@ -0,0 +1,579 @@
+/*globals svgEditor, svgCanvas, $*/
+/*jslint vars: true, eqeq: true, todo: true */
+/*
+ * ext-markers.js
+ *
+ * Licensed under the Apache License, Version 2
+ *
+ * Copyright(c) 2010 Will Schleter 
+ *   based on ext-arrows.js by Copyright(c) 2010 Alexis Deveria
+ *
+ * This extension provides for the addition of markers to the either end
+ * or the middle of a line, polyline, path, polygon. 
+ * 
+ * Markers may be either a graphic or arbitary text
+ * 
+ * to simplify the coding and make the implementation as robust as possible,
+ * markers are not shared - every object has its own set of markers.
+ * this relationship is maintained by a naming convention between the
+ * ids of the markers and the ids of the object
+ * 
+ * The following restrictions exist for simplicty of use and programming
+ *    objects and their markers to have the same color
+ *    marker size is fixed
+ *    text marker font, size, and attributes are fixed
+ *    an application specific attribute - se_type - is added to each marker element
+ *        to store the type of marker
+ *
+ * TODO:
+ *    remove some of the restrictions above
+ *    add option for keeping text aligned to horizontal
+ *    add support for dimension extension lines
+ *
+ */
+
+svgEditor.addExtension("Markers", function(S) {
+	var // svgcontent = S.svgcontent,
+		addElem = S.addSvgElementFromJson,
+		selElems;
+
+	var mtypes = ['start','mid','end'];
+
+	var marker_prefix = 'se_marker_';
+	var id_prefix = 'mkr_';
+		
+	// note - to add additional marker types add them below with a unique id
+	// and add the associated icon(s) to marker-icons.svg
+	// the geometry is normallized to a 100x100 box with the origin at lower left
+	// Safari did not like negative values for low left of viewBox
+	// remember that the coordinate system has +y downward
+	var marker_types = {
+		nomarker: {},  
+		leftarrow:  
+			{element:'path', attr:{d:'M0,50 L100,90 L70,50 L100,10 Z'}},
+		rightarrow:
+			{element:'path', attr:{d:'M100,50 L0,90 L30,50 L0,10 Z'}},
+		textmarker:
+			{element:'text', attr: {x:0, y:0,'stroke-width':0,'stroke':'none','font-size':75,'font-family':'serif','text-anchor':'left',
+				'xml:space': 'preserve'}},
+		forwardslash:
+			{element:'path', attr:{d:'M30,100 L70,0'}},
+		reverseslash:
+			{element:'path', attr:{d:'M30,0 L70,100'}},
+		verticalslash:
+			{element:'path', attr:{d:'M50,0 L50,100'}},
+		box:
+			{element:'path', attr:{d:'M20,20 L20,80 L80,80 L80,20 Z'}},
+		star:
+			{element:'path', attr:{d:'M10,30 L90,30 L20,90 L50,10 L80,90 Z'}},
+		xmark:
+			{element:'path', attr:{d:'M20,80 L80,20 M80,80 L20,20'}},
+		triangle:
+			{element:'path', attr:{d:'M10,80 L50,20 L80,80 Z'}},
+		mcircle:
+			{element:'circle', attr:{r:30, cx:50, cy:50}}			
+	};
+	
+	
+	var lang_list = {
+		"en":[
+			{id: "start_marker_list", title: "Select start marker type" },
+			{id: "mid_marker_list", title: "Select mid marker type" },
+			{id: "end_marker_list", title: "Select end marker type" },
+			{id: "nomarker", title: "No Marker" },
+			{id: "leftarrow", title: "Left Arrow" },
+			{id: "rightarrow", title: "Right Arrow" },
+			{id: "textmarker", title: "Text Marker" },
+			{id: "forwardslash", title: "Forward Slash" },
+			{id: "reverseslash", title: "Reverse Slash" },
+			{id: "verticalslash", title: "Vertical Slash" },
+			{id: "box", title: "Box" },
+			{id: "star", title: "Star" },
+			{id: "xmark", title: "X" },
+			{id: "triangle", title: "Triangle" },
+			{id: "mcircle", title: "Circle" },
+			{id: "leftarrow_o", title: "Open Left Arrow" },
+			{id: "rightarrow_o", title: "Open Right Arrow" },
+			{id: "box_o", title: "Open Box" },
+			{id: "star_o", title: "Open Star" },
+			{id: "triangle_o", title: "Open Triangle" },
+			{id: "mcircle_o", title: "Open Circle" }
+		]
+	};
+
+
+	// duplicate shapes to support unfilled (open) marker types with an _o suffix
+	$.each(['leftarrow','rightarrow','box','star','mcircle','triangle'],function(i,v) {
+		marker_types[v+'_o'] = marker_types[v];
+	});
+	
+	// elem = a graphic element will have an attribute like marker-start
+	// attr - marker-start, marker-mid, or marker-end
+	// returns the marker element that is linked to the graphic element
+	function getLinked(elem, attr) {
+		var str = elem.getAttribute(attr);
+		if(!str) {return null;}
+		var m = str.match(/\(\#(.*)\)/);
+		if(!m || m.length !== 2) {
+			return null;
+		}
+		return S.getElem(m[1]);
+	}
+
+	function setIcon(pos,id) {
+		if (id.substr(0,1) !== '\\') {id = '\\textmarker';}
+		var ci = '#'+id_prefix+pos+'_'+id.substr(1);
+		svgEditor.setIcon('#cur_' + pos +'_marker_list', $(ci).children());
+		$(ci).addClass('current').siblings().removeClass('current');
+	}
+
+	// toggles context tool panel off/on
+	//sets the controls with the selected element's settings
+	function showPanel(on) {
+		$('#marker_panel').toggle(on);
+
+		if(on) {
+			var el = selElems[0];
+			var val;
+			var ci;
+
+			$.each(mtypes, function(i, pos) {
+				var m = getLinked(el, "marker-" + pos);
+				var txtbox = $('#' + pos + '_marker');
+				if (!m) {
+					val = '\\nomarker';
+					ci = val;
+					txtbox.hide(); // hide text box
+				} else {
+					if (!m.attributes.se_type) {return;} // not created by this extension
+					val = '\\' + m.attributes.se_type.textContent;
+					ci = val;
+					if (val === '\\textmarker') {
+						val = m.lastChild.textContent;
+						//txtbox.show(); // show text box
+					} else {
+						txtbox.hide(); // hide text box
+					}
+				}
+				txtbox.val(val);				
+				setIcon(pos,ci);
+			});
+		}
+	}	
+
+	function addMarker(id, val) {
+		var txt_box_bg = '#ffffff';
+		var txt_box_border = 'none';
+		var txt_box_stroke_width = 0;
+		
+		var marker = S.getElem(id);
+
+		if (marker) {return;}
+
+		if (val == '' || val == '\\nomarker') {return;}
+
+		var el = selElems[0];		 
+		var color = el.getAttribute('stroke');
+		//NOTE: Safari didn't like a negative value in viewBox
+		//so we use a standardized 0 0 100 100
+		//with 50 50 being mapped to the marker position
+		var refX = 50;
+		var refY = 50;
+		var viewBox = "0 0 100 100";
+		var markerWidth = 5;
+		var markerHeight = 5;
+		var strokeWidth = 10;
+		var se_type;
+		if (val.substr(0,1) === '\\') {se_type = val.substr(1);}
+		else {se_type = 'textmarker';}
+
+		if (!marker_types[se_type]) {return;} // an unknown type!
+		
+		// create a generic marker
+		marker = addElem({
+			"element": "marker",
+			"attr": {
+			"id": id,
+			"markerUnits": "strokeWidth",
+			"orient": "auto",
+			"style": "pointer-events:none",
+			"se_type": se_type
+		}
+		});
+
+		if (se_type != 'textmarker') {
+			var mel = addElem(marker_types[se_type]);
+			var fillcolor = color;
+			if (se_type.substr(-2) === '_o') {fillcolor = 'none';}
+			mel.setAttribute('fill', fillcolor);
+			mel.setAttribute('stroke', color);
+			mel.setAttribute('stroke-width', strokeWidth);
+			marker.appendChild(mel);
+		} else {
+			var text = addElem(marker_types[se_type]);
+			// have to add text to get bounding box
+			text.textContent = val;
+			var tb = text.getBBox();
+			//alert( tb.x + " " + tb.y + " " + tb.width + " " + tb.height);
+			var pad = 1;
+			var bb = tb;
+			bb.x = 0;
+			bb.y = 0;
+			bb.width += pad*2;
+			bb.height += pad*2;
+			// shift text according to its size
+			text.setAttribute('x', pad);
+			text.setAttribute('y', bb.height - pad - tb.height/4); // kludge?
+			text.setAttribute('fill',color);
+			refX = bb.width/2 + pad;
+			refY = bb.height/2 + pad;
+			viewBox = bb.x + " " + bb.y + " " + bb.width + " " + bb.height;
+			markerWidth = bb.width/10;
+			markerHeight = bb.height/10;
+
+			var box = addElem({
+				"element": "rect",
+				"attr": {
+					"x": bb.x,
+					"y": bb.y,
+					"width": bb.width,
+					"height": bb.height,
+					"fill": txt_box_bg,
+					"stroke": txt_box_border,
+					"stroke-width": txt_box_stroke_width
+				}
+			});
+			marker.setAttribute("orient",0);
+			marker.appendChild(box);
+			marker.appendChild(text);
+		} 
+
+		marker.setAttribute("viewBox",viewBox);
+		marker.setAttribute("markerWidth", markerWidth);
+		marker.setAttribute("markerHeight", markerHeight);
+		marker.setAttribute("refX", refX);
+		marker.setAttribute("refY", refY);
+		S.findDefs().appendChild(marker);
+
+		return marker;
+	}
+
+	function convertline(elem) {
+		// this routine came from the connectors extension
+		// it is needed because midpoint markers don't work with line elements
+		if (!(elem.tagName === 'line')) {return elem;}
+
+		// Convert to polyline to accept mid-arrow
+
+		var x1 = Number(elem.getAttribute('x1'));
+		var x2 = Number(elem.getAttribute('x2'));
+		var y1 = Number(elem.getAttribute('y1'));
+		var y2 = Number(elem.getAttribute('y2'));
+		var id = elem.id;
+
+		var mid_pt = (' '+((x1+x2)/2)+','+((y1+y2)/2) + ' ');
+		var pline = addElem({
+			"element": "polyline",
+			"attr": {
+				"points": (x1+','+y1+ mid_pt +x2+','+y2),
+				"stroke": elem.getAttribute('stroke'),
+				"stroke-width": elem.getAttribute('stroke-width'),
+				"fill": "none",
+				"opacity": elem.getAttribute('opacity') || 1
+			}
+		});
+		$.each(mtypes, function(i, pos) { // get any existing marker definitions
+			var nam = 'marker-'+pos;
+			var m = elem.getAttribute(nam);
+			if (m) {pline.setAttribute(nam,elem.getAttribute(nam));}
+		});
+		
+		var batchCmd = new S.BatchCommand();
+		batchCmd.addSubCommand(new S.RemoveElementCommand(elem, elem.parentNode));
+		batchCmd.addSubCommand(new S.InsertElementCommand(pline));
+		
+		$(elem).after(pline).remove();
+		svgCanvas.clearSelection();
+		pline.id = id;
+		svgCanvas.addToSelection([pline]);
+		S.addCommandToHistory(batchCmd);
+		return pline;
+	}
+
+	function setMarker() {
+		var poslist={'start_marker':'start','mid_marker':'mid','end_marker':'end'};
+		var pos = poslist[this.id];
+		var marker_name = 'marker-'+pos;
+		var val = this.value;
+		var el = selElems[0];
+		var marker = getLinked(el, marker_name);
+		if (marker) {$(marker).remove();}
+		el.removeAttribute(marker_name);
+		if (val == '') {val = '\\nomarker';}
+		if (val == '\\nomarker') {
+			setIcon(pos,val);
+			S.call("changed", selElems);
+			return;
+		}
+		// Set marker on element
+		var id = marker_prefix + pos + '_' + el.id;
+		addMarker(id, val);
+		svgCanvas.changeSelectedAttribute(marker_name, "url(#" + id + ")");
+		if (el.tagName === 'line' && pos == 'mid') {el = convertline(el);}
+		S.call("changed", selElems);
+		setIcon(pos,val);
+	}
+
+	// called when the main system modifies an object
+	// this routine changes the associated markers to be the same color
+	function colorChanged(elem) {
+		var color = elem.getAttribute('stroke');
+
+		$.each(mtypes, function(i, pos) {
+			var marker = getLinked(elem, 'marker-'+pos);
+			if (!marker) {return;}
+			if (!marker.attributes.se_type) {return;} // not created by this extension
+			var ch = marker.lastElementChild;
+			if (!ch) {return;}
+			var curfill = ch.getAttribute('fill');
+			var curstroke = ch.getAttribute('stroke');
+			if (curfill && curfill != 'none') {ch.setAttribute('fill', color);}
+			if (curstroke && curstroke != 'none') {ch.setAttribute('stroke', color);}
+		});
+	}
+
+	// called when the main system creates or modifies an object
+	// primary purpose is create new markers for cloned objects
+	function updateReferences(el) {
+		$.each(mtypes, function (i, pos) {
+			var id = marker_prefix + pos + '_' + el.id;
+			var marker_name = 'marker-'+pos;
+			var marker = getLinked(el, marker_name);
+			if (!marker || !marker.attributes.se_type) {return;} // not created by this extension
+			var url = el.getAttribute(marker_name);
+			if (url) {
+				var len = el.id.length;
+				var linkid = url.substr(-len-1, len);
+				if (el.id != linkid) {
+					var val = $('#'+pos + '_marker').attr('value');
+					addMarker(id, val);
+					svgCanvas.changeSelectedAttribute(marker_name, "url(#" + id + ")");
+					if (el.tagName === 'line' && pos == 'mid') {el = convertline(el);}
+					S.call("changed", selElems);
+				}
+			}
+		});
+	}
+
+	// simulate a change event a text box that stores the current element's marker type
+	function triggerTextEntry(pos, val) {
+		$('#'+pos+'_marker').val(val);
+		$('#'+pos+'_marker').change();
+		// var txtbox = $('#'+pos+'_marker');
+		// if (val.substr(0,1)=='\\') {txtbox.hide();}
+		// else {txtbox.show();}
+	}
+
+	function showTextPrompt(pos) {
+		var def = $('#'+pos+'_marker').val();
+		if (def.substr(0,1) === '\\') {def = '';}
+		$.prompt('Enter text for ' + pos + ' marker', def , function(txt) {
+			if (txt) {triggerTextEntry(pos, txt);}
+		});
+	}
+
+	/*
+	function setMarkerSet(obj) {
+		var parts = this.id.split('_');
+		var set = parts[2];
+		switch (set) {
+		case 'off':
+			triggerTextEntry('start','\\nomarker');
+			triggerTextEntry('mid','\\nomarker');
+			triggerTextEntry('end','\\nomarker');
+			break;
+		case 'dimension':
+			triggerTextEntry('start','\\leftarrow');
+			triggerTextEntry('end','\\rightarrow');
+			showTextPrompt('mid');
+			break;
+		case 'label':
+			triggerTextEntry('mid','\\nomarker');
+			triggerTextEntry('end','\\rightarrow');
+			showTextPrompt('start');
+			break;
+		}
+	}
+	*/
+	// callback function for a toolbar button click
+	function setArrowFromButton(obj) {
+		
+		var parts = this.id.split('_');
+		var pos = parts[1];
+		var val = parts[2];
+		if (parts[3]) { val += '_' + parts[3];}
+		
+		if (val != 'textmarker') {
+			triggerTextEntry(pos, '\\'+val);
+		} else {
+			showTextPrompt(pos);
+		}
+	}
+	
+	function getTitle(lang, id) {
+		var i, list = lang_list[lang];
+		for (i in list) {
+			if (list.hasOwnProperty(i) && list[i].id == id) {
+				return list[i].title;
+			}
+		}
+		return id;
+	}
+	
+	
+	// build the toolbar button array from the marker definitions
+	// TODO: need to incorporate language specific titles
+	function buildButtonList() {
+		var buttons=[];
+		// var i = 0;
+/*
+		buttons.push({
+			id:id_prefix + 'markers_off',
+			title:'Turn off all markers',
+			type:'context',
+			events: { 'click': setMarkerSet },
+			panel: 'marker_panel'
+		});
+		buttons.push({
+			id:id_prefix + 'markers_dimension',
+			title:'Dimension',
+			type:'context',
+			events: { 'click': setMarkerSet },
+			panel: 'marker_panel'
+		});
+		buttons.push({
+			id:id_prefix + 'markers_label',
+			title:'Label',
+			type:'context',
+			events: { 'click': setMarkerSet },
+			panel: 'marker_panel'
+		});
+*/
+		$.each(mtypes, function (k, pos) {
+			var listname = pos + "_marker_list";
+			var def = true;
+			$.each(marker_types, function (id, v) {
+				var title = getTitle('en', id);
+				buttons.push({
+					id: id_prefix + pos + '_' + id,
+					svgicon: id,
+					title: title,
+					type: 'context',
+					events: {'click': setArrowFromButton},
+					panel: 'marker_panel',
+					list: listname,
+					isDefault: def
+				});
+				def = false;
+			});
+		});
+		return buttons;
+	}
+
+	return {
+		name: "Markers",
+		svgicons: svgEditor.curConfig.extPath + "markers-icons.xml",
+		buttons: buildButtonList(),
+		context_tools: [
+		   {
+			type: "input",
+			panel: "marker_panel",
+			title: "Start marker",
+			id: "start_marker",
+			label: "s",
+			size: 3,
+			events: { change: setMarker }
+		},{
+			type: "button-select",
+			panel: "marker_panel",
+			title: getTitle('en', 'start_marker_list'),
+			id: "start_marker_list",
+			colnum: 3,
+			events: { change: setArrowFromButton }
+		},{
+			type: "input",
+			panel: "marker_panel",
+			title: "Middle marker",
+			id: "mid_marker",
+			label: "m",
+			defval: "",
+			size: 3,
+			events: { change: setMarker }
+		},{
+			type: "button-select",
+			panel: "marker_panel",
+			title: getTitle('en', 'mid_marker_list'),
+			id: "mid_marker_list",
+			colnum: 3,
+			events: { change: setArrowFromButton }
+		},{
+			type: "input",
+			panel: "marker_panel",
+			title: "End marker",
+			id: "end_marker",
+			label: "e",
+			size: 3,
+			events: { change: setMarker }
+		},{
+			type: "button-select",
+			panel: "marker_panel",
+			title: getTitle('en', 'end_marker_list'),
+			id: "end_marker_list",
+			colnum: 3,
+			events: { change: setArrowFromButton }
+		} ],
+		callback: function() {
+			$('#marker_panel').addClass('toolset').hide();
+			
+		},
+		addLangData: function(lang) {
+			return { data: lang_list[lang] };
+		},
+
+	selectedChanged: function(opts) {
+		// Use this to update the current selected elements
+		//console.log('selectChanged',opts);
+		selElems = opts.elems;
+
+		var i = selElems.length;
+		var marker_elems = ['line','path','polyline','polygon'];
+
+		while(i--) {
+			var elem = selElems[i];
+			if(elem && $.inArray(elem.tagName, marker_elems) !== -1) {
+				if (opts.selectedElement && !opts.multiselected) {
+					showPanel(true);
+				} else {
+					showPanel(false);
+				}
+			} else {
+				showPanel(false);
+			}
+		}
+	},
+
+	elementChanged: function(opts) {		
+		//console.log('elementChanged',opts);
+		var elem = opts.elems[0];
+		if (elem && (
+				elem.getAttribute("marker-start") ||
+				elem.getAttribute("marker-mid") ||
+				elem.getAttribute("marker-end")
+		)) {
+			colorChanged(elem);
+			updateReferences(elem);
+		}
+		// changing_flag = false; // Not apparently in use
+	}
+	};
+});

+ 273 - 0
main/inc/lib/javascript/svgedit/extensions/ext-mathjax.js

@@ -0,0 +1,273 @@
+/*globals MathJax, svgEditor, svgCanvas, $*/
+/*jslint es5: true, todo: true, vars: true*/
+/*
+ * ext-mathjax.js
+ *
+ * Licensed under the Apache License
+ *
+ * Copyright(c) 2013 Jo Segaert
+ *
+ */
+
+svgEditor.addExtension("mathjax", function() {'use strict';
+  // Configuration of the MathJax extention.
+
+  // This will be added to the head tag before MathJax is loaded.
+  var /*mathjaxConfiguration = '<script type="text/x-mathjax-config">\
+        MathJax.Hub.Config({\
+          extensions: ["tex2jax.js"],\
+			    jax: ["input/TeX","output/SVG"],\
+          showProcessingMessages: true,\
+          showMathMenu: false,\
+          showMathMenuMSIE: false,\
+          errorSettings: {\
+            message: ["[Math Processing Error]"],\
+            style: {color: "#CC0000", "font-style":"italic"}\
+          },\
+          elements: [],\
+            tex2jax: {\
+            ignoreClass: "tex2jax_ignore2", processClass: "tex2jax_process2",\
+          },\
+          TeX: {\
+            extensions: ["AMSmath.js","AMSsymbols.js","noErrors.js","noUndefined.js"]\
+          },\
+          "SVG": {\
+          }\
+      });\
+      </script>',*/
+    // mathjaxSrc = 'http://cdn.mathjax.org/mathjax/latest/MathJax.js',
+    mathjaxSrcSecure = 'https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS-MML_SVG.js',
+    math,
+    locationX,
+    locationY,
+    mathjaxLoaded = false,
+    uiStrings = svgEditor.uiStrings;
+
+  // TODO: Implement language support. Move these uiStrings to the locale files and the code to the langReady callback.
+  $.extend(uiStrings, {
+    mathjax: {
+      embed_svg: 'Save as mathematics',
+      embed_mathml: 'Save as figure',
+      svg_save_warning: 'The math will be transformed into a figure is manipulatable like everything else. You will not be able to manipulate the TeX-code anymore. ',
+      mathml_save_warning: 'Advised. The math will be saved as a figure.',
+      title: 'Mathematics code editor'
+    }
+  });
+
+
+  function saveMath() {
+    var code = $('#mathjax_code_textarea').val();
+    // displaystyle to force MathJax NOT to use the inline style. Because it is
+    // less fancy!
+    MathJax.Hub.queue.Push(['Text', math, '\\displaystyle{' + code + '}']);
+
+    /*
+     * The MathJax library doesn't want to bloat your webpage so it creates 
+     * every symbol (glymph) you need only once. These are saved in a <svg> on  
+     * the top of your html document, just under the body tag. Each glymph has
+     * its unique id and is saved as a <path> in the <defs> tag of the <svg>
+     * 
+     * Then when the symbols are needed in the rest of your html document they
+     * are refferd to by a <use> tag.
+     * Because of bug 1076 we can't just grab the defs tag on the top and add it 
+     * to your formula's <svg> and copy the lot. So we have to replace each 
+     * <use> tag by it's <path>.
+     */
+    MathJax.Hub.queue.Push(
+      function() {
+        var mathjaxMath = $('.MathJax_SVG');
+        var svg = $(mathjaxMath.html());
+        svg.find('use').each(function() {
+          var x, y, id, transform;
+
+          // TODO: find a less pragmatic and more elegant solution to this.
+          if ($(this).attr('href')) {
+            id = $(this).attr('href').slice(1); // Works in Chrome.
+          } else {
+            id = $(this).attr('xlink:href').slice(1); // Works in Firefox.
+          }
+
+          var glymph = $('#' + id).clone().removeAttr('id');
+          x = $(this).attr('x');
+          y = $(this).attr('y');
+          transform = $(this).attr('transform');
+          if (transform && ( x || y )) {
+            glymph.attr('transform', transform + ' translate(' + x + ',' + y + ')');
+          }
+          else if (transform) {
+            glymph.attr('transform', transform);
+          }
+          else if (x || y) {
+            glymph.attr('transform', 'translate(' + x + ',' + y + ')');
+          }
+          $(this).replaceWith(glymph);
+        });
+        // Remove the style tag because it interferes with SVG-Edit.
+        svg.removeAttr('style');
+        svg.attr('xmlns', 'http://www.w3.org/2000/svg');
+        svgCanvas.importSvgString($('<div>').append(svg.clone()).html(), true);
+        svgCanvas.ungroupSelectedElement();
+        // TODO: To undo the adding of the Formula you now have to undo twice.
+        // This should only be once!
+        svgCanvas.moveSelectedElements(locationX, locationY, true);
+      }
+    );
+  }
+
+  return {
+    name: "MathJax",
+    svgicons: svgEditor.curConfig.extPath + "mathjax-icons.xml",
+    buttons: [{
+        id: "tool_mathjax",
+        type: "mode",
+        title: "Add Mathematics",
+        events: {
+          click: function() {
+            // Only load Mathjax when needed, we don't want to strain Svg-Edit any more. 
+            // From this point on it is very probable that it will be needed, so load it.
+            if (mathjaxLoaded === false) {
+
+              $('<div id="mathjax">\
+                <!-- Here is where MathJax creates the math -->\
+                  <div id="mathjax_creator" class="tex2jax_process" style="display:none">\
+                    $${}$$\
+                  </div>\
+                  <div id="mathjax_overlay"></div>\
+                  <div id="mathjax_container">\
+                    <div id="tool_mathjax_back" class="toolbar_button">\
+                      <button id="tool_mathjax_save">OK</button>\
+                      <button id="tool_mathjax_cancel">Cancel</button>\
+                    </div>\
+                    <fieldset>\
+                      <legend id="mathjax_legend">Mathematics Editor</legend>\
+                      <label>\
+                        <span id="mathjax_explication">Please type your mathematics in \
+                        <a href="http://en.wikipedia.org/wiki/Help:Displaying_a_formula" target="_blank">TeX</a> code.</span></label>\
+                      <textarea id="mathjax_code_textarea" spellcheck="false"></textarea>\
+                    </fieldset>\
+                  </div>\
+                </div>'
+                ).insertAfter('#svg_prefs').hide();
+
+              // Make the MathEditor draggable.
+              $('#mathjax_container').draggable({cancel: 'button,fieldset', containment: 'window'});
+
+              // Add functionality and picture to cancel button.
+              $('#tool_mathjax_cancel').prepend($.getSvgIcon('cancel', true))
+                .on("click touched", function() {
+                $('#mathjax').hide();
+              });
+
+              // Add functionality and picture to the save button.
+              $('#tool_mathjax_save').prepend($.getSvgIcon('ok', true))
+                .on("click touched", function() {
+                saveMath();
+                $('#mathjax').hide();
+              });
+
+              // MathJax preprocessing has to ignore most of the page.
+              $('body').addClass('tex2jax_ignore');
+
+              // Now get (and run) the MathJax Library.
+              $.getScript(mathjaxSrcSecure)
+                .done(function(script, textStatus) {
+
+                // When MathJax is loaded get the div where the math will be rendered.
+                MathJax.Hub.queue.Push(function() {
+                  math = MathJax.Hub.getAllJax('#mathjax_creator')[0];
+                  console.log(math);
+                  mathjaxLoaded = true;
+                  console.log('MathJax Loaded');
+                });
+
+              })
+                // If it fails.
+                .fail(function() {
+                console.log("Failed loadeing MathJax.");
+                $.alert("Failed loading MathJax. You will not be able to change the mathematics.");
+              });
+            }
+            // Set the mode.
+            svgCanvas.setMode("mathjax");
+          }
+        }
+      }],
+    
+    mouseDown: function() {
+      if (svgCanvas.getMode() === "mathjax") {
+        return {started: true};
+      }
+    },
+    mouseUp: function(opts) {
+      if (svgCanvas.getMode() === "mathjax") {
+        // Get the coordinates from your mouse.
+        var zoom = svgCanvas.getZoom();
+        // Get the actual coordinate by dividing by the zoom value
+        locationX = opts.mouse_x / zoom;
+        locationY = opts.mouse_y / zoom;
+
+        $('#mathjax').show();
+        return {started: false}; // Otherwise the last selected object dissapears.
+      }
+    },
+    callback: function() {
+      $('<style>').text('\
+				#mathjax fieldset{\
+					padding: 5px;\
+					margin: 5px;\
+					border: 1px solid #DDD;\
+				}\
+				#mathjax label{\
+					display: block;\
+					margin: .5em;\
+				}\
+				#mathjax legend {\
+					max-width:195px;\
+				}\
+				#mathjax_overlay {\
+					position: absolute;\
+					top: 0;\
+					left: 0;\
+					right: 0;\
+					bottom: 0;\
+					background-color: black;\
+					opacity: 0.6;\
+					z-index: 20000;\
+				}\
+				\
+				#mathjax_container {\
+					position: absolute;\
+					top: 50px;\
+					padding: 10px;\
+					background-color: #B0B0B0;\
+					border: 1px outset #777;\
+					opacity: 1.0;\
+					font-family: Verdana, Helvetica, sans-serif;\
+					font-size: .8em;\
+					z-index: 20001;\
+				}\
+				\
+				#tool_mathjax_back {\
+					margin-left: 1em;\
+					overflow: auto;\
+				}\
+				\
+				#mathjax_legend{\
+					font-weight: bold;\
+					font-size:1.1em;\
+				}\
+				\
+				#mathjax_code_textarea {\\n\
+          margin: 5px .7em;\
+					overflow: hidden;\
+          width: 416px;\
+					display: block;\
+					height: 100px;\
+				}\
+			').appendTo('head');
+
+      // Add the MathJax configuration.
+      //$(mathjaxConfiguration).appendTo('head');
+    }
+  };
+});

+ 142 - 0
main/inc/lib/javascript/svgedit/extensions/ext-overview_window.js

@@ -0,0 +1,142 @@
+/*globals svgEditor, svgedit, $ */
+/*jslint es5: true, vars: true*/
+/*
+ * ext-overview_window.js
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2013 James Sacksteder
+ *
+ */
+
+var overviewWindowGlobals = {};
+svgEditor.addExtension("overview_window", function() {	'use strict';
+	//define and insert the base html element
+	var propsWindowHtml= "\
+		<div id=\"overview_window_content_pane\" style=\" width:100%; word-wrap:break-word;  display:inline-block; margin-top:20px;\">\
+			<div id=\"overview_window_content\" style=\"position:relative; left:12px; top:0px;\">\
+				<div style=\"background-color:#A0A0A0; display:inline-block; overflow:visible;\">\
+					<svg id=\"overviewMiniView\" width=\"150\" height=\"100\" x=\"0\" y=\"0\" viewBox=\"0 0 4800 3600\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\
+						<use x=\"0\" y=\"0\" xlink:href=\"#svgroot\"> </use>\
+					 </svg>\
+					 <div id=\"overview_window_view_box\" style=\"min-width:50px; min-height:50px; position:absolute; top:30px; left:30px; z-index:5; background-color:rgba(255,0,102,0.3);\">\
+					 </div>\
+				 </div>\
+			</div>\
+		</div>";
+	$("#sidepanels").append(propsWindowHtml);
+
+	//define dynamic animation of the view box.
+	var updateViewBox = function(){
+		var portHeight=parseFloat($("#workarea").css("height"));
+		var portWidth=parseFloat($("#workarea").css("width"));
+		var portX=$("#workarea").scrollLeft();
+		var portY=$("#workarea").scrollTop();
+		var windowWidth=parseFloat($("#svgcanvas").css("width"));
+		var windowHeight=parseFloat($("#svgcanvas").css("height"));
+		var overviewWidth=$("#overviewMiniView").attr("width");
+		var overviewHeight=$("#overviewMiniView").attr("height");
+		
+		var viewBoxX=portX/windowWidth*overviewWidth;
+		var viewBoxY=portY/windowHeight*overviewHeight;
+		var viewBoxWidth=portWidth/windowWidth*overviewWidth;
+		var viewBoxHeight=portHeight/windowHeight*overviewHeight;
+		
+		$("#overview_window_view_box").css("min-width",viewBoxWidth+"px");
+		$("#overview_window_view_box").css("min-height",viewBoxHeight+"px");
+		$("#overview_window_view_box").css("top",viewBoxY+"px");
+		$("#overview_window_view_box").css("left",viewBoxX+"px");
+	};
+	$("#workarea").scroll(function(){
+		if(!(overviewWindowGlobals.viewBoxDragging)){
+			updateViewBox();
+		}
+	});
+	$("#workarea").resize(updateViewBox);
+	updateViewBox();
+	
+	//comphensate for changes in zoom and canvas size
+	var updateViewDimensions= function(){
+		var viewWidth=$("#svgroot").attr("width");
+		var viewHeight=$("#svgroot").attr("height");
+		var viewX=640;
+		var viewY=480;
+		
+		if(svgedit.browser.isIE())
+		{
+			//This has only been tested with Firefox 10 and IE 9 (without chrome frame).
+			//I am not sure if if is Firefox or IE that is being non compliant here.
+			//Either way the one that is noncompliant may become more compliant later.
+			//TAG:HACK  
+			//TAG:VERSION_DEPENDENT
+			//TAG:BROWSER_SNIFFING
+			viewX=0;
+			viewY=0;
+		}
+		
+		var svgWidth_old=$("#overviewMiniView").attr("width");
+		var svgHeight_new=viewHeight/viewWidth*svgWidth_old;
+		$("#overviewMiniView").attr("viewBox",viewX+" "+viewY+" "+viewWidth+" "+viewHeight);
+		$("#overviewMiniView").attr("height",svgHeight_new);
+		updateViewBox();
+	};
+	updateViewDimensions();
+	
+	//set up the overview window as a controller for the view port.
+	overviewWindowGlobals.viewBoxDragging=false;
+	var updateViewPortFromViewBox = function(){
+	
+		var windowWidth =parseFloat($("#svgcanvas").css("width" ));
+		var windowHeight=parseFloat($("#svgcanvas").css("height"));
+		var overviewWidth =$("#overviewMiniView").attr("width" );
+		var overviewHeight=$("#overviewMiniView").attr("height");
+		var viewBoxX=parseFloat($("#overview_window_view_box").css("left"));
+		var viewBoxY=parseFloat($("#overview_window_view_box").css("top" ));
+		
+		var portX=viewBoxX/overviewWidth *windowWidth;
+		var portY=viewBoxY/overviewHeight*windowHeight;
+
+		$("#workarea").scrollLeft(portX);
+		$("#workarea").scrollTop(portY);
+	};
+	$( "#overview_window_view_box" ).draggable({  containment: "parent"
+		,drag: updateViewPortFromViewBox
+		,start:function(){overviewWindowGlobals.viewBoxDragging=true; }
+		,stop :function(){overviewWindowGlobals.viewBoxDragging=false;}
+	});  
+	$("#overviewMiniView").click(function(evt){
+		//Firefox doesn't support evt.offsetX and evt.offsetY
+		var mouseX=(evt.offsetX || evt.originalEvent.layerX);
+		var mouseY=(evt.offsetY || evt.originalEvent.layerY);
+		var overviewWidth =$("#overviewMiniView").attr("width" );
+		var overviewHeight=$("#overviewMiniView").attr("height");
+		var viewBoxWidth =parseFloat($("#overview_window_view_box").css("min-width" ));
+		var viewBoxHeight=parseFloat($("#overview_window_view_box").css("min-height"));
+ 
+		var viewBoxX=mouseX - 0.5 * viewBoxWidth;
+		var viewBoxY=mouseY- 0.5 * viewBoxHeight;
+		//deal with constraints
+		if(viewBoxX<0){
+			viewBoxX=0;
+		}
+		if(viewBoxY<0){
+			viewBoxY=0;
+		}
+		if(viewBoxX+viewBoxWidth>overviewWidth){
+			viewBoxX=overviewWidth-viewBoxWidth;
+		}
+		if(viewBoxY+viewBoxHeight>overviewHeight){
+			viewBoxY=overviewHeight-viewBoxHeight;
+		}
+		
+		$("#overview_window_view_box").css("top",viewBoxY+"px");
+		$("#overview_window_view_box").css("left",viewBoxX+"px");
+		updateViewPortFromViewBox();
+	});
+	
+	return {
+		name: "overview window",
+		canvasUpdated: updateViewDimensions,
+		workareaResized: updateViewBox
+	};
+});

+ 46 - 0
main/inc/lib/javascript/svgedit/extensions/ext-panning.js

@@ -0,0 +1,46 @@
+/*globals svgEditor, svgCanvas*/
+/*jslint eqeq: true*/
+/*
+ * ext-panning.js
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2013 Luis Aguirre
+ *
+ */
+ 
+/* 
+	This is a very basic SVG-Edit extension to let tablet/mobile devices panning without problem
+*/
+
+svgEditor.addExtension('ext-panning', function() {'use strict';
+	return {
+		name: 'Extension Panning',
+		svgicons: svgEditor.curConfig.extPath + 'ext-panning.xml',
+		buttons: [{
+			id: 'ext-panning',
+			type: 'mode',
+			title: 'Panning',
+			events: {
+				click: function() {
+					svgCanvas.setMode('ext-panning');
+				}
+			}
+		}],
+		mouseDown: function() {
+			if (svgCanvas.getMode() == 'ext-panning') {
+				svgEditor.setPanning(true);
+				return {started: true};
+			}
+		},
+		mouseUp: function() {
+			if (svgCanvas.getMode() == 'ext-panning') {
+				svgEditor.setPanning(false);
+				return {
+					keep: false,
+					element: null
+				};
+			}
+		}
+	};
+});

+ 7 - 0
main/inc/lib/javascript/svgedit/extensions/ext-panning.xml

@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+	<g id="ext-panning">
+	  <svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
+                  <path fill="#7f0000" stroke="#000000" stroke-width="10" d="m1.00037,150.34581l55.30305,-55.30267l0,27.65093l22.17356,0l0,-44.21833l44.21825,0l0,-22.17357l-27.65095,0l55.30267,-55.30292l55.3035,55.30292l-27.65175,0l0,22.17357l44.21835,0l0,44.21833l22.17357,0l0,-27.65093l55.30345,55.30267l-55.30345,55.3035l0,-27.65175l-22.17357,0l0,44.21834l-44.21835,0l0,22.17355l27.65175,0l-55.3035,55.30348l-55.30267,-55.30348l27.65095,0l0,-22.17355l-44.21825,0l0,-44.21834l-22.17356,0l0,27.65175l-55.30305,-55.3035z"></path>
+  	</svg>
+	</g>
+</svg>

+ 23 - 0
main/inc/lib/javascript/svgedit/extensions/ext-php_savefile.js

@@ -0,0 +1,23 @@
+/*globals $, svgCanvas, svgEditor*/
+/*jslint regexp:true*/
+// TODO: Might add support for "exportImage" custom
+//   handler as in "ext-server_opensave.js" (and in savefile.php)
+
+svgEditor.addExtension("php_savefile", {
+	callback: function() {
+		'use strict';
+		function getFileNameFromTitle () {
+			var title = svgCanvas.getDocumentTitle();
+			return $.trim(title);
+		}
+		var save_svg_action = svgEditor.curConfig.extPath + 'savefile.php';
+		svgEditor.setCustomHandlers({
+			save: function(win, data) {
+				var svg = '<?xml version="1.0" encoding="UTF-8"?>\n' + data,
+					filename = getFileNameFromTitle();
+
+				$.post(save_svg_action, {output_svg: svg, filename: filename});
+			}
+		});
+	}
+});

+ 294 - 0
main/inc/lib/javascript/svgedit/extensions/ext-polygon.js

@@ -0,0 +1,294 @@
+/*globals svgEditor, svgCanvas, svgedit, $*/
+/*jslint vars: true, eqeq: true, todo: true */
+/*
+ * ext-polygon.js
+ *
+ *
+ * Copyright(c) 2010 CloudCanvas, Inc.
+ * All rights reserved
+ *
+ */
+svgEditor.addExtension("polygon", function(S) {'use strict';
+
+    var // NS = svgedit.NS,
+		// svgcontent = S.svgcontent,
+		// addElem = S.addSvgElementFromJson,
+		selElems,
+		editingitex = false,
+		// svgdoc = S.svgroot.parentNode.ownerDocument,
+		// newFOG, newFOGParent, newDef, newImageName, newMaskID, modeChangeG,
+		// edg = 0,
+		// undoCommand = "Not image";
+		started, newFO;
+    
+    // var ccZoom;
+    // var wEl, hEl;
+    // var wOffset, hOffset;
+    // var ccRBG;
+	var ccRgbEl;
+    // var ccOpacity;
+    // var brushW, brushH;
+	var shape;
+    
+    // var ccDebug = document.getElementById('debugpanel');
+    
+    /* var properlySourceSizeTextArea = function(){
+     // TODO: remove magic numbers here and get values from CSS
+     var height = $('#svg_source_container').height() - 80;
+     $('#svg_source_textarea').css('height', height);
+     }; */
+    function showPanel(on){
+        var fc_rules = $('#fc_rules');
+        if (!fc_rules.length) {
+            fc_rules = $('<style id="fc_rules"></style>').appendTo('head');
+        }
+        fc_rules.text(!on ? "" : " #tool_topath { display: none !important; }");
+        $('#polygon_panel').toggle(on);
+    }
+    
+	/*
+    function toggleSourceButtons(on){
+        $('#tool_source_save, #tool_source_cancel').toggle(!on);
+        $('#polygon_save, #polygon_cancel').toggle(on);
+    }
+	*/
+    
+    function setAttr(attr, val){
+        svgCanvas.changeSelectedAttribute(attr, val);
+        S.call("changed", selElems);
+    }
+    
+    function cot(n){
+        return 1 / Math.tan(n);
+    }
+    
+    function sec(n){
+        return 1 / Math.cos(n);
+    }
+
+	/**
+	* Obtained from http://code.google.com/p/passenger-top/source/browse/instiki/public/svg-edit/editor/extensions/ext-itex.js?r=3
+	* This function sets the content of of the currently-selected foreignObject element,
+	*   based on the itex contained in string.
+	* @param {string} tex The itex text.
+	* @returns This function returns false if the set was unsuccessful, true otherwise.
+	*/
+	/*
+	function setItexString(tex) {
+		var mathns = 'http://www.w3.org/1998/Math/MathML',
+			xmlnsns = 'http://www.w3.org/2000/xmlns/',
+			ajaxEndpoint = '../../itex';
+		var elt = selElems[0];
+		try {
+			var math = svgdoc.createElementNS(mathns, 'math');
+			math.setAttributeNS(xmlnsns, 'xmlns', mathns);
+			math.setAttribute('display', 'inline');
+			var semantics = document.createElementNS(mathns, 'semantics');
+			var annotation = document.createElementNS(mathns, 'annotation');
+			annotation.setAttribute('encoding', 'application/x-tex');
+			annotation.textContent = tex;
+			var mrow = document.createElementNS(mathns, 'mrow');
+			semantics.appendChild(mrow);
+			semantics.appendChild(annotation);
+			math.appendChild(semantics);
+			// make an AJAX request to the server, to get the MathML
+			$.post(ajaxEndpoint, {'tex': tex, 'display': 'inline'}, function(data){
+				var children = data.documentElement.childNodes;
+				while (children.length > 0) {
+				     mrow.appendChild(svgdoc.adoptNode(children[0], true));
+				}
+				S.sanitizeSvg(math);
+				S.call("changed", [elt]);
+			});
+			elt.replaceChild(math, elt.firstChild);
+			S.call("changed", [elt]);
+			svgCanvas.clearSelection();
+		} catch(e) {
+			console.log(e);
+			return false;
+		}
+
+		return true;
+	}
+	*/
+    return {
+        name: "polygon",
+        svgicons: svgEditor.curConfig.extPath + "polygon-icons.svg",
+        buttons: [{
+            id: "tool_polygon",
+            type: "mode",
+            title: "Polygon Tool",
+            position: 11,
+            events: {
+                'click': function(){
+                    svgCanvas.setMode('polygon');
+					showPanel(true);
+                }
+            }
+        }],
+        
+        context_tools: [{
+            type: "input",
+            panel: "polygon_panel",
+            title: "Number of Sides",
+            id: "polySides",
+            label: "sides",
+            size: 3,
+            defval: 5,
+            events: {
+                change: function(){
+                    setAttr('sides', this.value);
+                }
+            }
+        }],
+        
+        callback: function(){
+        
+            $('#polygon_panel').hide();
+            
+            var endChanges = function(){
+            };
+            
+            // TODO: Needs to be done after orig icon loads
+            setTimeout(function(){
+                // Create source save/cancel buttons
+                var save = $('#tool_source_save').clone().hide().attr('id', 'polygon_save').unbind().appendTo("#tool_source_back").click(function(){
+                
+                    if (!editingitex) {
+						return;
+					}
+					// Todo: Uncomment the setItexString() function above and handle ajaxEndpoint?
+                    if (!setItexString($('#svg_source_textarea').val())) {
+                        $.confirm("Errors found. Revert to original?", function(ok){
+                            if (!ok) {
+								return false;
+							}
+                            endChanges();
+                        });
+                    }
+                    else {
+                        endChanges();
+                    }
+                    // setSelectMode();	
+                });
+                
+                var cancel = $('#tool_source_cancel').clone().hide().attr('id', 'polygon_cancel').unbind().appendTo("#tool_source_back").click(function(){
+                    endChanges();
+                });
+                
+            }, 3000);
+        },
+        mouseDown: function(opts){
+            // var e = opts.event;
+            var rgb = svgCanvas.getColor("fill");
+            ccRgbEl = rgb.substring(1, rgb.length);
+            var sRgb = svgCanvas.getColor("stroke");
+            // ccSRgbEl = sRgb.substring(1, rgb.length);
+            var sWidth = svgCanvas.getStrokeWidth();
+            
+            if (svgCanvas.getMode() == "polygon") {
+                started = true;
+                
+                newFO = S.addSvgElementFromJson({
+                    "element": "polygon",
+                    "attr": {
+                        "cx": opts.start_x,
+                        "cy": opts.start_y,
+                        "id": S.getNextId(),
+                        "shape": "regularPoly",
+                        "sides": document.getElementById("polySides").value,
+                        "orient": "x",
+                        "edge": 0,
+                        "fill": rgb,
+                        "strokecolor": sRgb,
+                        "strokeWidth": sWidth
+                    }
+                });
+                
+                return {
+                    started: true
+                };
+            }
+        },
+        mouseMove: function(opts){
+            if (!started) {
+                return;
+			}
+            if (svgCanvas.getMode() == "polygon") {
+                // var e = opts.event;
+                var x = opts.mouse_x;
+                var y = opts.mouse_y;
+                var c = $(newFO).attr(["cx", "cy", "sides", "orient", "fill", "strokecolor", "strokeWidth"]);
+                var cx = c.cx, cy = c.cy, fill = c.fill, strokecolor = c.strokecolor, strokewidth = c.strokeWidth, sides = c.sides,
+					// orient = c.orient,
+					edg = (Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy))) / 1.5;
+                newFO.setAttributeNS(null, "edge", edg);
+                
+                var inradius = (edg / 2) * cot(Math.PI / sides);
+                var circumradius = inradius * sec(Math.PI / sides);
+                var points = '';
+				var s;
+                for (s = 0; sides >= s; s++) {
+                    var angle = 2.0 * Math.PI * s / sides;
+                    x = (circumradius * Math.cos(angle)) + cx;
+                    y = (circumradius * Math.sin(angle)) + cy;
+                    
+                    points += x + ',' + y + ' ';
+                }
+                
+                //var poly = newFO.createElementNS(NS.SVG, 'polygon');
+                newFO.setAttributeNS(null, 'points', points);
+                newFO.setAttributeNS(null, 'fill', fill);
+                newFO.setAttributeNS(null, 'stroke', strokecolor);
+                newFO.setAttributeNS(null, 'stroke-width', strokewidth);
+				// newFO.setAttributeNS(null, 'transform', "rotate(-90)");
+                shape = newFO.getAttributeNS(null, 'shape');
+                //newFO.appendChild(poly);
+                //DrawPoly(cx, cy, sides, edg, orient);
+                return {
+                    started: true
+                };
+            }
+            
+        },
+        
+        mouseUp: function(opts){
+            if (svgCanvas.getMode() == "polygon") {
+                var attrs = $(newFO).attr("edge");
+                var keep = (attrs.edge != 0);
+               // svgCanvas.addToSelection([newFO], true);
+                return {
+                    keep: keep,
+                    element: newFO
+                };
+            }
+            
+        },
+        selectedChanged: function(opts){
+            // Use this to update the current selected elements
+            selElems = opts.elems;
+            
+            var i = selElems.length;
+            
+            while (i--) {
+                var elem = selElems[i];
+                if (elem && elem.getAttributeNS(null, 'shape') === 'regularPoly') {
+                    if (opts.selectedElement && !opts.multiselected) {
+                        $('#polySides').val(elem.getAttribute("sides"));
+                        
+                        showPanel(true);
+                    }
+                    else {
+                        showPanel(false);
+                    }
+                }
+                else {
+                    showPanel(false);
+                }
+            }
+        },
+        elementChanged: function(opts){
+            // var elem = opts.elems[0];
+        }
+    };
+});

+ 59 - 0
main/inc/lib/javascript/svgedit/extensions/ext-server_moinsave.js

@@ -0,0 +1,59 @@
+/*globals svgEditor, svgedit, svgCanvas, canvg, $, top*/
+/*jslint vars: true*/
+/*
+ * ext-server_moinsave.js
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2010 Alexis Deveria
+ *              2011 MoinMoin:ReimarBauer
+ *                   adopted for moinmoins item storage. it sends in one post png and svg data
+ *                   (I agree to dual license my work to additional GPLv2 or later)
+ *
+ */
+
+svgEditor.addExtension("server_opensave", {
+	callback: function() {'use strict';
+		var Utils = svgedit.utilities;
+		var save_svg_action = '/+modify';
+		
+		// Create upload target (hidden iframe)
+		var target = $('<iframe name="output_frame" src="#"/>').hide().appendTo('body');
+	
+		svgEditor.setCustomHandlers({
+			save: function(win, data) {
+				var svg = "<?xml version=\"1.0\"?>\n" + data;
+				var qstr = $.param.querystring();
+				var name = qstr.substr(9).split('/+get/')[1];
+				var svg_data = Utils.encode64(svg);
+				if(!$('#export_canvas').length) {
+					$('<canvas>', {id: 'export_canvas'}).hide().appendTo('body');
+				}
+				var c = $('#export_canvas')[0];
+				c.width = svgCanvas.contentW;
+				c.height = svgCanvas.contentH;
+				Utils.buildCanvgCallback(function () {
+					canvg(c, svg, {renderCallback: function() {
+						var datauri = c.toDataURL('image/png');
+						// var uiStrings = svgEditor.uiStrings;
+						var png_data = Utils.encode64(datauri); // Brett: This encoding seems unnecessary
+						var form = $('<form>').attr({
+						method: 'post',
+						action: save_svg_action + '/' + name,
+						target: 'output_frame'
+					}).append('<input type="hidden" name="png_data" value="' + png_data + '">')
+						.append('<input type="hidden" name="filepath" value="' + svg_data + '">')
+						.append('<input type="hidden" name="filename" value="' + 'drawing.svg">')
+						.append('<input type="hidden" name="contenttype" value="application/x-svgdraw">')
+						.appendTo('body')
+						.submit().remove();
+					}});
+				})();
+				alert("Saved! Return to Item View!");
+				top.window.location = '/'+name;
+			}
+		});
+	
+	}
+});
+

+ 228 - 0
main/inc/lib/javascript/svgedit/extensions/ext-server_opensave.js

@@ -0,0 +1,228 @@
+/*globals svgEditor, svgedit, svgCanvas, canvg, $*/
+/*jslint eqeq: true, browser:true*/
+/*
+ * ext-server_opensave.js
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2010 Alexis Deveria
+ *
+ */
+
+svgEditor.addExtension("server_opensave", {
+	callback: function() {
+		'use strict';
+		function getFileNameFromTitle () {
+			var title = svgCanvas.getDocumentTitle();
+			// We convert (to underscore) only those disallowed Win7 file name characters
+			return $.trim(title).replace(/[\/\\:*?"<>|]/g, '_');
+		}
+		function xhtmlEscape(str) {
+			return str.replace(/&(?!amp;)/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;'); // < is actually disallowed above anyways
+		}
+		function clientDownloadSupport (filename, suffix, uri) {
+			var a,
+				support = $('<a>')[0].download === '';
+			if (support) {
+				a = $('<a>hidden</a>').attr({download: (filename || 'image') + suffix, href: uri}).css('display', 'none').appendTo('body');
+				a[0].click();
+				return true;
+			}
+		}
+		var open_svg_action, import_svg_action, import_img_action,
+			open_svg_form, import_svg_form, import_img_form,
+			save_svg_action = svgEditor.curConfig.extPath + 'filesave.php',
+			save_img_action = svgEditor.curConfig.extPath + 'filesave.php',
+			// Create upload target (hidden iframe)
+			cancelled = false,
+			Utils = svgedit.utilities;
+	
+		$('<iframe name="output_frame" src="#"/>').hide().appendTo('body');
+		svgEditor.setCustomHandlers({
+			save: function(win, data) {
+				var svg = '<?xml version="1.0" encoding="UTF-8"?>\n' + data, // Firefox doesn't seem to know it is UTF-8 (no matter whether we use or skip the clientDownload code) despite the Content-Disposition header containing UTF-8, but adding the encoding works
+					filename = getFileNameFromTitle();
+
+				if (clientDownloadSupport(filename, '.svg', 'data:image/svg+xml;charset=UTF-8;base64,' + Utils.encode64(svg))) {
+					return;
+				}
+
+				$('<form>').attr({
+					method: 'post',
+					action: save_svg_action,
+					target: 'output_frame'
+				}).append('<input type="hidden" name="output_svg" value="' + xhtmlEscape(svg) + '">')
+					.append('<input type="hidden" name="filename" value="' + xhtmlEscape(filename) + '">')
+					.appendTo('body')
+					.submit().remove();
+			},
+			exportPDF: function (win, data) {
+				var filename = getFileNameFromTitle(),
+					datauri = data.dataurlstring;
+				if (clientDownloadSupport(filename, '.pdf', datauri)) {
+					return;
+				}
+				$('<form>').attr({
+					method: 'post',
+					action: save_img_action,
+					target: 'output_frame'
+				}).append('<input type="hidden" name="output_img" value="' + datauri + '">')
+					.append('<input type="hidden" name="mime" value="application/pdf">')
+					.append('<input type="hidden" name="filename" value="' + xhtmlEscape(filename) + '">')
+					.appendTo('body')
+					.submit().remove();
+			},
+			// Todo: Integrate this extension with a new built-in exportWindowType, "download"
+			exportImage: function(win, data) {
+				var c,
+					issues = data.issues,
+					mimeType = data.mimeType,
+					quality = data.quality;
+				
+				if(!$('#export_canvas').length) {
+					$('<canvas>', {id: 'export_canvas'}).hide().appendTo('body');
+				}
+				c = $('#export_canvas')[0];
+				
+				c.width = svgCanvas.contentW;
+				c.height = svgCanvas.contentH;
+				Utils.buildCanvgCallback(function () {
+					canvg(c, data.svg, {renderCallback: function() {
+						var pre, filename, suffix,
+							datauri = quality ? c.toDataURL(mimeType, quality) : c.toDataURL(mimeType),
+							// uiStrings = svgEditor.uiStrings,
+							note = '';
+						
+						// Check if there are issues
+						if (issues.length) {
+							pre = "\n \u2022 ";
+							note += ("\n\n" + pre + issues.join(pre));
+						} 
+						
+						if(note.length) {
+							alert(note);
+						}
+						
+						filename = getFileNameFromTitle();
+						suffix = '.' + data.type.toLowerCase();
+						
+						if (clientDownloadSupport(filename, suffix, datauri)) {
+							return;
+						}
+
+						$('<form>').attr({
+							method: 'post',
+							action: save_img_action,
+							target: 'output_frame'
+						}).append('<input type="hidden" name="output_img" value="' + datauri + '">')
+							.append('<input type="hidden" name="mime" value="' + mimeType + '">')
+							.append('<input type="hidden" name="filename" value="' + xhtmlEscape(filename) + '">')
+							.appendTo('body')
+							.submit().remove();
+					}});
+				})();
+			}
+		});
+
+		// Do nothing if client support is found
+		if (window.FileReader) {return;}
+		
+		// Change these to appropriate script file
+		open_svg_action = svgEditor.curConfig.extPath + 'fileopen.php?type=load_svg';
+		import_svg_action = svgEditor.curConfig.extPath + 'fileopen.php?type=import_svg';
+		import_img_action = svgEditor.curConfig.extPath + 'fileopen.php?type=import_img';
+		
+		// Set up function for PHP uploader to use
+		svgEditor.processFile = function(str64, type) {
+			var xmlstr;
+			if (cancelled) {
+				cancelled = false;
+				return;
+			}
+		
+			$('#dialog_box').hide();
+
+			if (type !== 'import_img') {
+				xmlstr = Utils.decode64(str64);
+			}
+			
+			switch (type) {
+				case 'load_svg':
+					svgCanvas.clear();
+					svgCanvas.setSvgString(xmlstr);
+					svgEditor.updateCanvas();
+					break;
+				case 'import_svg':
+					svgCanvas.importSvgString(xmlstr);
+					svgEditor.updateCanvas();					
+					break;
+				case 'import_img':
+					svgCanvas.setGoodImage(str64);
+					break;
+			}
+		};
+	
+		// Create upload form
+		open_svg_form = $('<form>');
+		open_svg_form.attr({
+			enctype: 'multipart/form-data',
+			method: 'post',
+			action: open_svg_action,
+			target: 'output_frame'
+		});
+		
+		// Create import form
+		import_svg_form = open_svg_form.clone().attr('action', import_svg_action);
+
+		// Create image form
+		import_img_form = open_svg_form.clone().attr('action', import_img_action);
+		
+		// It appears necessary to rebuild this input every time a file is 
+		// selected so the same file can be picked and the change event can fire.
+		function rebuildInput(form) {
+			form.empty();
+			var inp = $('<input type="file" name="svg_file">').appendTo(form);
+			
+			
+			function submit() {
+				// This submits the form, which returns the file data using svgEditor.processFile()
+				form.submit();
+				
+				rebuildInput(form);
+				$.process_cancel("Uploading...", function() {
+					cancelled = true;
+					$('#dialog_box').hide();
+				});
+			}
+			
+			if(form[0] == open_svg_form[0]) {
+				inp.change(function() {
+					// This takes care of the "are you sure" dialog box
+					svgEditor.openPrep(function(ok) {
+						if(!ok) {
+							rebuildInput(form);
+							return;
+						}
+						submit();
+					});
+				});
+			} else {
+				inp.change(function() {
+					// This submits the form, which returns the file data using svgEditor.processFile()
+					submit();
+				});
+			}
+		}
+		
+		// Create the input elements
+		rebuildInput(open_svg_form);
+		rebuildInput(import_svg_form);
+		rebuildInput(import_img_form);
+
+		// Add forms to buttons
+		$("#tool_open").show().prepend(open_svg_form);
+		$("#tool_import").show().prepend(import_svg_form);
+		$("#tool_image").prepend(import_img_form);
+	}
+});
+

+ 357 - 0
main/inc/lib/javascript/svgedit/extensions/ext-shapes.js

@@ -0,0 +1,357 @@
+/*globals svgEditor, $, DOMParser*/
+/*jslint es5: true, vars: true, eqeq: true*/
+/*
+ * ext-shapes.js
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2010 Christian Tzurcanu
+ * Copyright(c) 2010 Alexis Deveria
+ *
+ */
+
+svgEditor.addExtension('shapes', function() {'use strict';
+	var current_d, cur_shape_id;
+	var canv = svgEditor.canvas;
+	var cur_shape;
+	var start_x, start_y;
+	var svgroot = canv.getRootElem();
+	var lastBBox = {};
+
+	// This populates the category list
+	var categories = {
+		basic: 'Basic',
+		object: 'Objects',
+		symbol: 'Symbols',
+		arrow: 'Arrows',
+		flowchart: 'Flowchart',
+		animal: 'Animals',
+		game: 'Cards & Chess',
+		dialog_balloon: 'Dialog balloons',
+		electronics: 'Electronics',
+		math: 'Mathematical',
+		music: 'Music',
+		misc: 'Miscellaneous',
+		raphael_1: 'raphaeljs.com set 1',
+		raphael_2: 'raphaeljs.com set 2'
+	};
+
+	var library = {
+		basic: {
+			data: {
+				'heart': 'm150,73c61,-175 300,0 0,225c-300,-225 -61,-400 0,-225z',
+				'frame': 'm0,0l300,0l0,300l-300,0zm35,-265l0,230l230,0l0,-230z',
+				'donut': 'm1,150l0,0c0,-82.29042 66.70958,-149 149,-149l0,0c39.51724,0 77.41599,15.69816 105.35889,43.64108c27.94293,27.94293 43.64111,65.84165 43.64111,105.35892l0,0c0,82.29041 -66.70958,149 -149,149l0,0c-82.29041,0 -149,-66.70959 -149,-149zm74.5,0l0,0c0,41.1452 33.35481,74.5 74.5,74.5c41.14522,0 74.5,-33.3548 74.5,-74.5c0,-41.1452 -33.3548,-74.5 -74.5,-74.5l0,0c-41.14519,0 -74.5,33.35481 -74.5,74.5z',
+				'triangle': 'm1,280.375l149,-260.75l149,260.75z',
+				'right_triangle': 'm1,299l0,-298l298,298z',
+				'diamond': 'm1,150l149,-149l149,149l-149,149l-149,-149z',
+				'pentagon': 'm1.00035,116.97758l148.99963,-108.4053l148.99998,108.4053l-56.91267,175.4042l-184.1741,0l-56.91284,-175.4042z',
+				'hexagon': 'm1,149.99944l63.85715,-127.71428l170.28572,0l63.85713,127.71428l-63.85713,127.71428l-170.28572,0l-63.85715,-127.71428z',
+				'septagon1': 'm0.99917,191.06511l29.51249,-127.7108l119.48833,-56.83673l119.48836,56.83673l29.51303,127.7108l-82.69087,102.41679l-132.62103,0l-82.69031,-102.41679z',
+				'heptagon': 'm1,88.28171l87.28172,-87.28171l123.43653,0l87.28172,87.28171l0,123.43654l-87.28172,87.28172l-123.43653,0l-87.28172,-87.28172l0,-123.43654z',
+				'decagon': 'm1,150.00093l28.45646,-88.40318l74.49956,-54.63682l92.08794,0l74.50002,54.63682l28.45599,88.40318l-28.45599,88.40318l-74.50002,54.63681l-92.08794,0l-74.49956,-54.63681l-28.45646,-88.40318z',
+				'dodecagon': 'm1,110.07421l39.92579,-69.14842l69.14842,-39.92579l79.85159,0l69.14842,39.92579l39.92578,69.14842l0,79.85159l-39.92578,69.14842l-69.14842,39.92578l-79.85159,0l-69.14842,-39.92578l-39.92579,-69.14842l0,-79.85159z',
+				'star_points_5': 'm1,116.58409l113.82668,0l35.17332,-108.13487l35.17334,108.13487l113.82666,0l-92.08755,66.83026l35.17514,108.13487l-92.08759,-66.83208l-92.08757,66.83208l35.17515,-108.13487l-92.08758,-66.83026z',
+				'trapezoid': 'm1,299l55.875,-298l186.25001,0l55.87498,298z',
+				'arrow_up': 'm1.49805,149.64304l148.50121,-148.00241l148.50121,148.00241l-74.25061,0l0,148.71457l-148.5012,0l0,-148.71457z',
+				'vertical_scrool': 'm37.375,261.625l0,-242.9375l0,0c0,-10.32083 8.36669,-18.6875 18.6875,-18.6875l224.25,0c10.32083,0 18.6875,8.36667 18.6875,18.6875c0,10.32081 -8.36667,18.6875 -18.6875,18.6875l-18.6875,0l0,242.9375c0,10.32083 -8.36668,18.6875 -18.6875,18.6875l-224.25,0l0,0c-10.32083,0 -18.6875,-8.36667 -18.6875,-18.6875c0,-10.32083 8.36667,-18.6875 18.6875,-18.6875zm37.375,-261.625l0,0c10.32081,0 18.6875,8.36667 18.6875,18.6875c0,10.32081 -8.36669,18.6875 -18.6875,18.6875c-5.1604,0 -9.34375,-4.18335 -9.34375,-9.34375c0,-5.16041 4.18335,-9.34375 9.34375,-9.34375l18.6875,0m186.875,18.6875l-205.5625,0m-37.375,224.25l0,0c5.1604,0 9.34375,4.18335 9.34375,9.34375c0,5.1604 -4.18335,9.34375 -9.34375,9.34375l18.6875,0m-18.6875,18.6875l0,0c10.32081,0 18.6875,-8.36667 18.6875,-18.6875l0,-18.6875',
+				'smiley': 'm68.49886,214.78838q81.06408,55.67332 161.93891,0m-144.36983,-109.9558c0,-8.60432 6.97517,-15.57949 15.57948,-15.57949c8.60431,0 15.57948,6.97517 15.57948,15.57949c0,8.60431 -6.97517,15.57947 -15.57948,15.57947c-8.60431,0 -15.57948,-6.97516 -15.57948,-15.57947m95.83109,0c0,-8.60432 6.97517,-15.57949 15.57948,-15.57949c8.60431,0 15.57947,6.97517 15.57947,15.57949c0,8.60431 -6.97516,15.57947 -15.57947,15.57947c-8.60429,0 -15.57948,-6.97516 -15.57948,-15.57947m-181.89903,44.73038l0,0c0,-82.60133 66.96162,-149.56296 149.56296,-149.56296c82.60135,0 149.56296,66.96162 149.56296,149.56296c0,82.60135 -66.96161,149.56296 -149.56296,149.56296c-82.60133,0 -149.56296,-66.96161 -149.56296,-149.56296zm0,0l0,0c0,-82.60133 66.96162,-149.56296 149.56296,-149.56296c82.60135,0 149.56296,66.96162 149.56296,149.56296c0,82.60135 -66.96161,149.56296 -149.56296,149.56296c-82.60133,0 -149.56296,-66.96161 -149.56296,-149.56296z',
+				'left_braket': 'm174.24565,298.5c-13.39009,0 -24.24489,-1.80908 -24.24489,-4.04065l0,-140.4187c0,-2.23158 -10.85481,-4.04065 -24.2449,-4.04065l0,0c13.39009,0 24.2449,-1.80907 24.2449,-4.04065l0,-140.4187l0,0c0,-2.23159 10.8548,-4.04066 24.24489,-4.04066',
+				'uml_actor': 'm40.5,100l219,0m-108.99991,94.00006l107,105m-107.00009,-106.00006l-100,106m99.5,-231l0,125m33.24219,-158.75781c0,18.35916 -14.88303,33.24219 -33.24219,33.24219c-18.35916,0 -33.2422,-14.88303 -33.2422,-33.24219c0.00002,-18.35915 14.88304,-33.24219 33.2422,-33.24219c18.35916,0 33.24219,14.88304 33.24219,33.24219z',
+				'dialog_balloon_1': 'm0.99786,35.96579l0,0c0,-19.31077 15.28761,-34.96524 34.14583,-34.96524l15.52084,0l0,0l74.50001,0l139.68748,0c9.05606,0 17.74118,3.68382 24.14478,10.24108c6.40356,6.55726 10.00107,15.45081 10.00107,24.72416l0,87.41311l0,0l0,52.44785l0,0c0,19.31078 -15.2876,34.96524 -34.14584,34.96524l-139.68748,0l-97.32507,88.90848l22.82506,-88.90848l-15.52084,0c-18.85822,0 -34.14583,-15.65446 -34.14583,-34.96524l0,0l0,-52.44785l0,0z',
+				'cloud': 'm182.05086,34.31005c-0.64743,0.02048 -1.27309,0.07504 -1.92319,0.13979c-10.40161,1.03605 -19.58215,7.63722 -24.24597,17.4734l-2.47269,7.44367c0.53346,-2.57959 1.35258,-5.08134 2.47269,-7.44367c-8.31731,-8.61741 -19.99149,-12.59487 -31.52664,-10.72866c-11.53516,1.8662 -21.55294,9.3505 -27.02773,20.19925c-15.45544,-9.51897 -34.72095,-8.94245 -49.62526,1.50272c-14.90431,10.44516 -22.84828,28.93916 -20.43393,47.59753l1.57977,7.58346c-0.71388,-2.48442 -1.24701,-5.01186 -1.57977,-7.58346l-0.2404,0.69894c-12.95573,1.4119 -23.58103,11.46413 -26.34088,24.91708c-2.75985,13.45294 2.9789,27.25658 14.21789,34.21291l17.54914,4.26352c-6.1277,0.50439 -12.24542,-0.9808 -17.54914,-4.26352c-8.66903,9.71078 -10.6639,24.08736 -4.94535,35.96027c5.71854,11.87289 17.93128,18.70935 30.53069,17.15887l7.65843,-2.02692c-2.46413,1.0314 -5.02329,1.70264 -7.65843,2.02692c7.15259,13.16728 19.01251,22.77237 32.93468,26.5945c13.92217,3.82214 28.70987,1.56322 41.03957,-6.25546c10.05858,15.86252 27.91113,24.19412 45.81322,21.38742c17.90208,-2.8067 32.66954,-16.26563 37.91438,-34.52742l1.82016,-10.20447c-0.27254,3.46677 -0.86394,6.87508 -1.82016,10.20447c12.31329,8.07489 27.80199,8.52994 40.52443,1.18819c12.72244,-7.34175 20.6609,-21.34155 20.77736,-36.58929l-4.56108,-22.7823l-17.96776,-15.41455c13.89359,8.70317 22.6528,21.96329 22.52884,38.19685c16.5202,0.17313 30.55292,-13.98268 36.84976,-30.22897c6.29684,-16.24631 3.91486,-34.76801 -6.2504,-48.68089c4.21637,-10.35873 3.96622,-22.14172 -0.68683,-32.29084c-4.65308,-10.14912 -13.23602,-17.69244 -23.55914,-20.65356c-2.31018,-13.45141 -11.83276,-24.27162 -24.41768,-27.81765c-12.58492,-3.54603 -25.98557,0.82654 -34.41142,11.25287l-5.11707,8.63186c1.30753,-3.12148 3.01521,-6.03101 5.11707,-8.63186c-5.93959,-8.19432 -15.2556,-12.8181 -24.96718,-12.51096z',
+				'cylinder': 'm299.0007,83.77844c0,18.28676 -66.70958,33.11111 -149.00002,33.11111m149.00002,-33.11111l0,0c0,18.28676 -66.70958,33.11111 -149.00002,33.11111c-82.29041,0 -148.99997,-14.82432 -148.99997,-33.11111m0,0l0,0c0,-18.28674 66.70956,-33.1111 148.99997,-33.1111c82.29044,0 149.00002,14.82436 149.00002,33.1111l0,132.44449c0,18.28674 -66.70958,33.11105 -149.00002,33.11105c-82.29041,0 -148.99997,-14.82431 -148.99997,-33.11105z',
+				'arrow_u_turn': 'm1.00059,299.00055l0,-167.62497l0,0c0,-72.00411 58.37087,-130.37499 130.375,-130.37499l0,0l0,0c34.57759,0 67.73898,13.7359 92.18906,38.18595c24.45006,24.45005 38.18593,57.61144 38.18593,92.18904l0,18.625l37.24997,0l-74.49995,74.50002l-74.50002,-74.50002l37.25,0l0,-18.625c0,-30.8589 -25.0161,-55.87498 -55.87498,-55.87498l0,0l0,0c-30.85892,0 -55.875,25.01608 -55.875,55.87498l0,167.62497z',
+				'arrow_left_up': 'm0.99865,224.5l74.50004,-74.5l0,37.25l111.74991,0l0,-111.75l-37.25,0l74.5,-74.5l74.5,74.5l-37.25,0l0,186.25l-186.24989,0l0,37.25l-74.50005,-74.5z',
+				'maximize': 'm1.00037,150.34581l55.30305,-55.30267l0,27.65093l22.17356,0l0,-44.21833l44.21825,0l0,-22.17357l-27.65095,0l55.30267,-55.30292l55.3035,55.30292l-27.65175,0l0,22.17357l44.21835,0l0,44.21833l22.17357,0l0,-27.65093l55.30345,55.30267l-55.30345,55.3035l0,-27.65175l-22.17357,0l0,44.21834l-44.21835,0l0,22.17355l27.65175,0l-55.3035,55.30348l-55.30267,-55.30348l27.65095,0l0,-22.17355l-44.21825,0l0,-44.21834l-22.17356,0l0,27.65175l-55.30305,-55.3035z',
+				'cross': 'm0.99844,99.71339l98.71494,0l0,-98.71495l101.26279,0l0,98.71495l98.71495,0l0,101.2628l-98.71495,0l0,98.71494l-101.26279,0l0,-98.71494l-98.71494,0z',
+				'plaque': 'm-0.00197,49.94376l0,0c27.5829,0 49.94327,-22.36036 49.94327,-49.94327l199.76709,0l0,0c0,27.5829 22.36037,49.94327 49.94325,49.94327l0,199.7671l0,0c-27.58289,0 -49.94325,22.36034 -49.94325,49.94325l-199.76709,0c0,-27.58292 -22.36037,-49.94325 -49.94327,-49.94325z',
+				'page': 'm249.3298,298.99744l9.9335,-39.73413l39.73413,-9.93355l-49.66763,49.66768l-248.33237,0l0,-298.00001l298.00001,0l0,248.33234'
+
+			},
+			buttons: []
+		}
+	};
+
+	var cur_lib = library.basic;
+	var mode_id = 'shapelib';
+	var startClientPos = {};
+
+	function loadIcons() {
+		$('#shape_buttons').empty().append(cur_lib.buttons);
+	}
+
+	function makeButtons(cat, shapes) {
+		var size = cur_lib.size || 300;
+		var fill = cur_lib.fill || false;
+		var off = size * 0.05;
+		var vb = [-off, -off, size + off*2, size + off*2].join(' ');
+		var stroke = fill ? 0: (size/30);
+		var shape_icon = new DOMParser().parseFromString(
+			'<svg xmlns="http://www.w3.org/2000/svg"><svg viewBox="' + vb + '"><path fill="'+(fill?'#333':'none')+'" stroke="#000" stroke-width="' + stroke + '" /></svg></svg>',
+			'text/xml');
+
+		var width = 24;
+		var height = 24;
+		shape_icon.documentElement.setAttribute('width', width);
+		shape_icon.documentElement.setAttribute('height', height);
+		var svg_elem = $(document.importNode(shape_icon.documentElement,true));
+
+		var data = shapes.data;
+
+		cur_lib.buttons = [];
+		var id;
+		for (id in data) {
+			var path_d = data[id];
+			var icon = svg_elem.clone();
+			icon.find('path').attr('d', path_d);
+
+			var icon_btn = icon.wrap('<div class="tool_button">').parent().attr({
+				id: mode_id + '_' + id,
+				title: id
+			});
+			// Store for later use
+			cur_lib.buttons.push(icon_btn[0]);
+		}
+	}
+
+	function loadLibrary(cat_id) {
+
+		var lib = library[cat_id];
+
+		if (!lib) {
+			$('#shape_buttons').html('Loading...');
+			$.getJSON(svgEditor.curConfig.extPath + 'shapelib/' + cat_id + '.json', function(result) {
+				cur_lib = library[cat_id] = {
+					data: result.data,
+					size: result.size,
+					fill: result.fill
+				};
+				makeButtons(cat_id, result);
+				loadIcons();
+			});
+			return;
+		}
+		cur_lib = lib;
+		if (!lib.buttons.length) {makeButtons(cat_id, lib);}
+		loadIcons();
+	}
+
+	return {
+		svgicons: svgEditor.curConfig.extPath + 'ext-shapes.xml',
+		buttons: [{
+			id: 'tool_shapelib',
+			type: 'mode_flyout', // _flyout
+			position: 6,
+			title: 'Shape library',
+			events: {
+				click: function() {
+					canv.setMode(mode_id);
+				}
+			}
+		}],
+		callback: function() {
+			$('<style>').text('\
+			#shape_buttons {\
+				overflow: auto;\
+				width: 180px;\
+				max-height: 300px;\
+				display: table-cell;\
+				vertical-align: middle;\
+			}\
+			\
+			#shape_cats {\
+				min-width: 110px;\
+				display: table-cell;\
+				vertical-align: middle;\
+				height: 300px;\
+			}\
+			#shape_cats > div {\
+				line-height: 1em;\
+				padding: .5em;\
+				border:1px solid #B0B0B0;\
+				background: #E8E8E8;\
+				margin-bottom: -1px;\
+			}\
+			#shape_cats div:hover {\
+				background: #FFFFCC;\
+			}\
+			#shape_cats div.current {\
+				font-weight: bold;\
+			}').appendTo('head');
+
+			var btn_div = $('<div id="shape_buttons">');
+			$('#tools_shapelib > *').wrapAll(btn_div);
+
+			var shower = $('#tools_shapelib_show');
+
+			loadLibrary('basic');
+
+			// Do mouseup on parent element rather than each button
+			$('#shape_buttons').mouseup(function(evt) {
+				var btn = $(evt.target).closest('div.tool_button');
+
+				if (!btn.length) {return;}
+
+				var copy = btn.children().clone();
+				shower.children(':not(.flyout_arrow_horiz)').remove();
+				shower
+					.append(copy)
+					.attr('data-curopt', '#' + btn[0].id) // This sets the current mode
+					.mouseup();
+				canv.setMode(mode_id);
+
+				cur_shape_id = btn[0].id.substr((mode_id+'_').length);
+				current_d = cur_lib.data[cur_shape_id];
+
+				$('.tools_flyout').fadeOut();
+			});
+
+			var shape_cats = $('<div id="shape_cats">');
+			var cat_str = '';
+
+			$.each(categories, function(id, label) {
+				cat_str += '<div data-cat=' + id + '>' + label + '</div>';
+			});
+
+			shape_cats.html(cat_str).children().bind('mouseup', function() {
+				var catlink = $(this);
+				catlink.siblings().removeClass('current');
+				catlink.addClass('current');
+
+				loadLibrary(catlink.attr('data-cat'));
+				// Get stuff
+				return false;
+			});
+
+			shape_cats.children().eq(0).addClass('current');
+
+			$('#tools_shapelib').append(shape_cats);
+
+			shower.mouseup(function() {
+				canv.setMode(current_d ? mode_id : 'select');
+			});
+			$('#tool_shapelib').remove();
+
+			var h = $('#tools_shapelib').height();
+			$('#tools_shapelib').css({
+				'margin-top': -(h/2 - 15),
+				'margin-left': 3
+			});
+		},
+		mouseDown: function(opts) {
+			var mode = canv.getMode();
+			if (mode !== mode_id) {return;}
+
+			start_x = opts.start_x;
+			var x = start_x;
+			start_y = opts.start_y;
+			var y = start_y;
+			var cur_style = canv.getStyle();
+		 
+			startClientPos.x = opts.event.clientX;
+			startClientPos.y = opts.event.clientY;
+
+			cur_shape = canv.addSvgElementFromJson({
+				'element': 'path',
+				'curStyles': true,
+				'attr': {
+					'd': current_d,
+					'id': canv.getNextId(),
+					'opacity': cur_style.opacity / 2,
+					'style': 'pointer-events:none'
+				}
+			});
+
+			// Make sure shape uses absolute values
+			if (/[a-z]/.test(current_d)) {
+				current_d = cur_lib.data[cur_shape_id] = canv.pathActions.convertPath(cur_shape);
+				cur_shape.setAttribute('d', current_d);
+				canv.pathActions.fixEnd(cur_shape);
+			}
+			cur_shape.setAttribute('transform', 'translate(' + x + ',' + y + ') scale(0.005) translate(' + -x + ',' + -y + ')');
+
+			canv.recalculateDimensions(cur_shape);
+
+			var tlist = canv.getTransformList(cur_shape);
+
+			lastBBox = cur_shape.getBBox();
+
+			return {
+				started: true
+			};
+		},
+		mouseMove: function(opts) {
+			var mode = canv.getMode();
+			if (mode !== mode_id) {return;}
+
+			var zoom = canv.getZoom();
+			var evt = opts.event;
+
+			var x = opts.mouse_x/zoom;
+			var y = opts.mouse_y/zoom;
+
+			var tlist = canv.getTransformList(cur_shape),
+				box = cur_shape.getBBox(),
+				left = box.x, top = box.y, width = box.width,
+				height = box.height;
+			var dx = (x-start_x), dy = (y-start_y);
+
+			var newbox = {
+				'x': Math.min(start_x,x),
+				'y': Math.min(start_y,y),
+				'width': Math.abs(x-start_x),
+				'height': Math.abs(y-start_y)
+			};
+
+			var tx = 0, ty = 0,
+				sy = height ? (height+dy)/height : 1,
+				sx = width ? (width+dx)/width : 1;
+
+			sx = (newbox.width / lastBBox.width) || 1;
+			sy = (newbox.height / lastBBox.height) || 1;
+
+			// Not perfect, but mostly works...
+			if (x < start_x) {
+				tx = lastBBox.width;
+			}
+			if (y < start_y) {ty = lastBBox.height;}
+
+			// update the transform list with translate,scale,translate
+			var translateOrigin = svgroot.createSVGTransform(),
+				scale = svgroot.createSVGTransform(),
+				translateBack = svgroot.createSVGTransform();
+
+			translateOrigin.setTranslate(-(left+tx), -(top+ty));
+			if (!evt.shiftKey) {
+				var max = Math.min(Math.abs(sx), Math.abs(sy));
+
+				sx = max * (sx < 0 ? -1 : 1);
+				sy = max * (sy < 0 ? -1 : 1);
+			}
+			scale.setScale(sx,sy);
+
+			translateBack.setTranslate(left+tx, top+ty);
+			tlist.appendItem(translateBack);
+			tlist.appendItem(scale);
+			tlist.appendItem(translateOrigin);
+
+			canv.recalculateDimensions(cur_shape);
+
+			lastBBox = cur_shape.getBBox();
+		},
+		mouseUp: function(opts) {
+			var mode = canv.getMode();
+			if (mode !== mode_id) {return;}
+
+			var keepObject = (opts.event.clientX != startClientPos.x && opts.event.clientY != startClientPos.y);
+
+			return {
+				keep: keepObject,
+				element: cur_shape,
+				started: false
+			};
+		}
+	};
+});
+

+ 10 - 0
main/inc/lib/javascript/svgedit/extensions/ext-shapes.xml

@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+	<g id="tool_shapelib">
+	  <svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
+		  <path fill="#c0c0c0" stroke-linejoin="round" stroke-width="14" stroke="#202020" fill-rule="nonzero" d="m70,194.72501l0,0c0,-10.30901 35.8172,-18.666 80,-18.666c44.18298,0 80,8.35699 80,18.666l0,74.66699c0,10.30899 -35.81702,18.66699 -80,18.66699c-44.1828,0 -80,-8.358 -80,-18.66699l0,-74.66699z"/>
+		  <path fill="#c0c0c0" stroke-linejoin="round" stroke-width="14" stroke="#202020" fill-rule="nonzero" d="m70,114.608l0,0c0,-10.309 35.8172,-18.6668 80,-18.6668c44.18298,0 80,8.3578 80,18.6668l0,74.66699c0,10.30901 -35.81702,18.666 -80,18.666c-44.1828,0 -80,-8.35699 -80,-18.666l0,-74.66699z"/>
+		  <path fill="#c0c0c0" stroke-linejoin="round" stroke-width="14" stroke="#202020" fill-rule="nonzero" d="m70,33.6667l0,0c0,-10.3094 35.8172,-18.6667 80,-18.6667c44.18298,0 80,8.3573 80,18.6667l0,74.6663c0,10.31 -35.81702,18.667 -80,18.667c-44.1828,0 -80,-8.357 -80,-18.667l0,-74.6663z"/>
+		  <path id="svg_1" fill="#c0c0c0" stroke-linejoin="round" stroke-width="14" stroke="#202020" fill-rule="nonzero" d="m230,32.33334c0,10.30931 -35.81726,18.66666 -80,18.66666c-44.1828,0 -80,-8.35735 -80,-18.66666"/>
+  	</svg>
+	</g>
+</svg>

+ 236 - 0
main/inc/lib/javascript/svgedit/extensions/ext-star.js

@@ -0,0 +1,236 @@
+/*globals svgEditor, svgedit, svgCanvas, $*/
+/*jslint vars: true, eqeq: true*/
+/*
+ * ext-star.js
+ *
+ *
+ * Copyright(c) 2010 CloudCanvas, Inc.
+ * All rights reserved
+ *
+ */
+
+svgEditor.addExtension('star', function(S){'use strict';
+
+	var // NS = svgedit.NS,
+		// svgcontent = S.svgcontent,
+		selElems,
+		// editingitex = false,
+		// svgdoc = S.svgroot.parentNode.ownerDocument,
+		started,
+		newFO,
+		// edg = 0,
+		// newFOG, newFOGParent, newDef, newImageName, newMaskID,
+		// undoCommand = 'Not image',
+		// modeChangeG, ccZoom, wEl, hEl, wOffset, hOffset, ccRgbEl, brushW, brushH,
+		shape;
+
+	function showPanel(on){
+		var fc_rules = $('#fc_rules');
+		if (!fc_rules.length) {
+			fc_rules = $('<style id="fc_rules"></style>').appendTo('head');
+		}
+		fc_rules.text(!on ? '' : ' #tool_topath { display: none !important; }');
+		$('#star_panel').toggle(on);
+	}
+
+	/*
+	function toggleSourceButtons(on){
+		$('#star_save, #star_cancel').toggle(on);
+	}
+	*/
+
+	function setAttr(attr, val){
+		svgCanvas.changeSelectedAttribute(attr, val);
+		S.call('changed', selElems);
+	}
+
+	/*
+	function cot(n){
+		return 1 / Math.tan(n);
+	}
+
+	function sec(n){
+		return 1 / Math.cos(n);
+	}
+	*/
+
+	return {
+		name: 'star',
+		svgicons: svgEditor.curConfig.extPath + 'star-icons.svg',
+		buttons: [{
+			id: 'tool_star',
+			type: 'mode',
+			title: 'Star Tool',
+			position: 12,
+			events: {
+				click: function(){
+					showPanel(true);
+					svgCanvas.setMode('star');
+				}
+			}
+		}],
+
+		context_tools: [{
+			type: 'input',
+			panel: 'star_panel',
+			title: 'Number of Sides',
+			id: 'starNumPoints',
+			label: 'points',
+			size: 3,
+			defval: 5,
+			events: {
+				change: function(){
+					setAttr('point', this.value);
+				}
+			}
+		}, {
+			type: 'input',
+			panel: 'star_panel',
+			title: 'Pointiness',
+			id: 'starRadiusMulitplier',
+			label: 'Pointiness',
+			size: 3,
+			defval: 2.5
+		}, {
+			type: 'input',
+			panel: 'star_panel',
+			title: 'Twists the star',
+			id: 'radialShift',
+			label: 'Radial Shift',
+			size: 3,
+			defval: 0,
+			events: {
+				change: function(){
+					setAttr('radialshift', this.value);
+				}
+			}
+		}],
+		callback: function(){
+			$('#star_panel').hide();
+			// var endChanges = function(){};
+		},
+		mouseDown: function(opts){
+			var rgb = svgCanvas.getColor('fill');
+			// var ccRgbEl = rgb.substring(1, rgb.length);
+			var sRgb = svgCanvas.getColor('stroke');
+			// var ccSRgbEl = sRgb.substring(1, rgb.length);
+			var sWidth = svgCanvas.getStrokeWidth();
+
+			if (svgCanvas.getMode() == 'star') {
+				started = true;
+
+				newFO = S.addSvgElementFromJson({
+					'element': 'polygon',
+					'attr': {
+						'cx': opts.start_x,
+						'cy': opts.start_y,
+						'id': S.getNextId(),
+						'shape': 'star',
+						'point': document.getElementById('starNumPoints').value,
+						'r': 0,
+						'radialshift': document.getElementById('radialShift').value,
+						'r2': 0,
+						'orient': 'point',
+						'fill': rgb,
+						'strokecolor': sRgb,
+						'strokeWidth': sWidth
+					}
+				});
+				return {
+					started: true
+				};
+			}
+		},
+		mouseMove: function(opts){
+			if (!started) {
+				return;
+			}
+			if (svgCanvas.getMode() == 'star') {
+				var x = opts.mouse_x;
+				var y = opts.mouse_y;
+				var c = $(newFO).attr(['cx', 'cy', 'point', 'orient', 'fill', 'strokecolor', 'strokeWidth', 'radialshift']);
+
+				var cx = c.cx, cy = c.cy, fill = c.fill, strokecolor = c.strokecolor, strokewidth = c.strokeWidth, radialShift = c.radialshift, point = c.point, orient = c.orient, circumradius = (Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy))) / 1.5, inradius = circumradius / document.getElementById('starRadiusMulitplier').value;
+				newFO.setAttributeNS(null, 'r', circumradius);
+				newFO.setAttributeNS(null, 'r2', inradius);
+
+				var polyPoints = '';
+				var s;
+				for (s = 0; point >= s; s++) {
+					var angle = 2.0 * Math.PI * (s / point);
+					if ('point' == orient) {
+						angle -= (Math.PI / 2);
+					} else if ('edge' == orient) {
+						angle = (angle + (Math.PI / point)) - (Math.PI / 2);
+					}
+
+					x = (circumradius * Math.cos(angle)) + cx;
+					y = (circumradius * Math.sin(angle)) + cy;
+
+					polyPoints += x + ',' + y + ' ';
+
+					if (null != inradius) {
+						angle = (2.0 * Math.PI * (s / point)) + (Math.PI / point);
+						if ('point' == orient) {
+							angle -= (Math.PI / 2);
+						} else if ('edge' == orient) {
+							angle = (angle + (Math.PI / point)) - (Math.PI / 2);
+						}
+						angle += radialShift;
+
+						x = (inradius * Math.cos(angle)) + cx;
+						y = (inradius * Math.sin(angle)) + cy;
+
+						polyPoints += x + ',' + y + ' ';
+					}
+				}
+				newFO.setAttributeNS(null, 'points', polyPoints);
+				newFO.setAttributeNS(null, 'fill', fill);
+				newFO.setAttributeNS(null, 'stroke', strokecolor);
+				newFO.setAttributeNS(null, 'stroke-width', strokewidth);
+				shape = newFO.getAttributeNS(null, 'shape');
+
+				return {
+					started: true
+				};
+			}
+
+		},
+		mouseUp: function(){
+			if (svgCanvas.getMode() == 'star') {
+				var attrs = $(newFO).attr(['r']);
+				// svgCanvas.addToSelection([newFO], true);
+				return {
+					keep: (attrs.r != 0),
+					element: newFO
+				};
+			}
+		},
+		selectedChanged: function(opts){
+			// Use this to update the current selected elements
+			selElems = opts.elems;
+
+			var i = selElems.length;
+
+			while (i--) {
+				var elem = selElems[i];
+				if (elem && elem.getAttributeNS(null, 'shape') === 'star') {
+					if (opts.selectedElement && !opts.multiselected) {
+						// $('#starRadiusMulitplier').val(elem.getAttribute('r2'));
+						$('#starNumPoints').val(elem.getAttribute('point'));
+						$('#radialShift').val(elem.getAttribute('radialshift'));
+						showPanel(true);
+					}
+					else {
+						showPanel(false);
+					}
+				} else {
+					showPanel(false);
+				}
+			}
+		},
+		elementChanged: function(opts){
+			// var elem = opts.elems[0];
+		}
+	};
+});

+ 293 - 0
main/inc/lib/javascript/svgedit/extensions/ext-storage.js

@@ -0,0 +1,293 @@
+/*globals svgEditor, svgCanvas, $, widget*/
+/*jslint vars: true, eqeq: true, regexp: true, continue: true*/
+/*
+ * ext-storage.js
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2010 Brett Zamir
+ *
+ */
+/**
+* This extension allows automatic saving of the SVG canvas contents upon
+*  page unload (which can later be automatically retrieved upon future
+*  editor loads).
+*
+*  The functionality was originally part of the SVG Editor, but moved to a
+*  separate extension to make the setting behavior optional, and adapted
+*  to inform the user of its setting of local data.
+*/
+
+/*
+TODOS
+1. Revisit on whether to use $.pref over directly setting curConfig in all
+	extensions for a more public API (not only for extPath and imagePath,
+	but other currently used config in the extensions)
+2. We might provide control of storage settings through the UI besides the
+    initial (or URL-forced) dialog.
+*/
+svgEditor.addExtension('storage', function() {
+	// We could empty any already-set data for users when they decline storage,
+	//  but it would be a risk for users who wanted to store but accidentally
+	// said "no"; instead, we'll let those who already set it, delete it themselves;
+	// to change, set the "emptyStorageOnDecline" config setting to true
+	// in config.js.
+	var emptyStorageOnDecline = svgEditor.curConfig.emptyStorageOnDecline,
+		// When the code in svg-editor.js prevents local storage on load per
+		//  user request, we also prevent storing on unload here so as to
+		//  avoid third-party sites making XSRF requests or providing links
+		// which would cause the user's local storage not to load and then
+		// upon page unload (such as the user closing the window), the storage
+		//  would thereby be set with an empty value, erasing any of the
+		// user's prior work. To change this behavior so that no use of storage
+		// or adding of new storage takes place regardless of settings, set
+		// the "noStorageOnLoad" config setting to true in config.js.
+		noStorageOnLoad = svgEditor.curConfig.noStorageOnLoad,
+		forceStorage = svgEditor.curConfig.forceStorage,
+		storage = svgEditor.storage;
+
+	function replaceStoragePrompt (val) {
+		val = val ? 'storagePrompt=' + val : '';
+		var loc = top.location; // Allow this to work with the embedded editor as well
+		if (loc.href.indexOf('storagePrompt=') > -1) {
+			loc.href = loc.href.replace(/([&?])storagePrompt=[^&]*(&?)/, function (n0, n1, amp) {
+				return (val ? n1 : '') + val + (!val && amp ? n1 : (amp || ''));
+			});
+		}
+		else {
+			loc.href += (loc.href.indexOf('?') > -1 ? '&' : '?') + val;
+		}
+	}
+	function setSVGContentStorage (val) {
+		if (storage) {
+			var name = 'svgedit-' + svgEditor.curConfig.canvasName;
+			if (!val) {
+				storage.removeItem(name);
+			}
+			else {
+				storage.setItem(name, val);
+			}
+		}
+	}
+	
+	function expireCookie (cookie) {
+		document.cookie = encodeURIComponent(cookie) + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT';
+	}
+	
+	function removeStoragePrefCookie () {
+		expireCookie('store');
+	}
+	
+	function emptyStorage() {
+		setSVGContentStorage('');
+		var name;
+		for (name in svgEditor.curPrefs) {
+			if (svgEditor.curPrefs.hasOwnProperty(name)) {
+				name = 'svg-edit-' + name;
+				if (storage) {
+					storage.removeItem(name);
+				}
+				expireCookie(name);
+			}
+		}
+	}
+	
+//	emptyStorage();
+
+	/**
+	* Listen for unloading: If and only if opted in by the user, set the content
+	*   document and preferences into storage:
+	* 1. Prevent save warnings (since we're automatically saving unsaved
+	*       content into storage)
+	* 2. Use localStorage to set SVG contents (potentially too large to allow in cookies)
+	* 3. Use localStorage (where available) or cookies to set preferences.
+	*/
+	function setupBeforeUnloadListener () {
+		window.addEventListener('beforeunload', function(e) {
+			// Don't save anything unless the user opted in to storage
+			if (!document.cookie.match(/(?:^|;\s*)store=(?:prefsAndContent|prefsOnly)/)) {
+				return;
+			}
+			var key;
+			if (document.cookie.match(/(?:^|;\s*)store=prefsAndContent/)) {
+				setSVGContentStorage(svgCanvas.getSvgString());			
+			}
+
+			svgEditor.setConfig({no_save_warning: true}); // No need for explicit saving at all once storage is on
+			// svgEditor.showSaveWarning = false;
+
+			var curPrefs = svgEditor.curPrefs;
+
+			for (key in curPrefs) {
+				if (curPrefs.hasOwnProperty(key)) { // It's our own config, so we don't need to iterate up the prototype chain
+					var val = curPrefs[key],
+						store = (val != undefined);
+					key = 'svg-edit-' + key;
+					if (!store) {
+						continue;
+					}
+					if (storage) {
+						storage.setItem(key, val);
+					}
+					else if (window.widget) {
+						widget.setPreferenceForKey(val, key);
+					}
+					else {
+						val = encodeURIComponent(val);
+						document.cookie = encodeURIComponent(key) + '=' + val + '; expires=Fri, 31 Dec 9999 23:59:59 GMT';
+					}
+				}
+			}
+		}, false);
+	}
+
+	/*
+	// We could add locales here instead (and also thereby avoid the need
+	// to keep our content within "langReady"), but this would be less
+	// convenient for translators.
+	$.extend(uiStrings, {confirmSetStorage: {
+		message: "By default and where supported, SVG-Edit can store your editor "+
+		"preferences and SVG content locally on your machine so you do not "+
+		"need to add these back each time you load SVG-Edit. If, for privacy "+
+		"reasons, you do not wish to store this information on your machine, "+
+		"you can change away from the default option below.",
+		storagePrefsAndContent: "Store preferences and SVG content locally",
+		storagePrefsOnly: "Only store preferences locally",
+		storagePrefs: "Store preferences locally",
+		storageNoPrefsOrContent: "Do not store my preferences or SVG content locally",
+		storageNoPrefs: "Do not store my preferences locally",
+		rememberLabel: "Remember this choice?",
+		rememberTooltip: "If you choose to opt out of storage while remembering this choice, the URL will change so as to avoid asking again."
+	}});
+	*/
+	var loaded = false;
+	return {
+		name: 'storage',
+		langReady: function (data) {
+			var // lang = data.lang,
+				uiStrings = data.uiStrings, // No need to store as dialog should only run once
+				storagePrompt = $.deparam.querystring(true).storagePrompt;
+
+			// No need to run this one-time dialog again just because the user
+			//   changes the language
+			if (loaded) {
+				return;
+			}
+			loaded = true;
+
+			// Note that the following can load even if "noStorageOnLoad" is
+			//   set to false; to avoid any chance of storage, avoid this
+			//   extension! (and to avoid using any prior storage, set the
+			//   config option "noStorageOnLoad" to true).
+			if (!forceStorage && (
+				// If the URL has been explicitly set to always prompt the
+				//  user (e.g., so one can be pointed to a URL where one
+				// can alter one's settings, say to prevent future storage)...
+				storagePrompt === true ||
+				(
+					// ...or...if the URL at least doesn't explicitly prevent a
+					//  storage prompt (as we use for users who
+					// don't want to set cookies at all but who don't want
+					// continual prompts about it)...
+					storagePrompt !== false &&
+					// ...and this user hasn't previously indicated a desire for storage
+					!document.cookie.match(/(?:^|;\s*)store=(?:prefsAndContent|prefsOnly)/)
+				)
+				// ...then show the storage prompt.
+			)) {
+
+				var options = [];
+				if (storage) {
+					options.unshift(
+						{value: 'prefsAndContent', text: uiStrings.confirmSetStorage.storagePrefsAndContent},
+						{value: 'prefsOnly', text: uiStrings.confirmSetStorage.storagePrefsOnly},
+						{value: 'noPrefsOrContent', text: uiStrings.confirmSetStorage.storageNoPrefsOrContent}
+					);
+				}
+				else {
+					options.unshift(
+						{value: 'prefsOnly', text: uiStrings.confirmSetStorage.storagePrefs},
+						{value: 'noPrefsOrContent', text: uiStrings.confirmSetStorage.storageNoPrefs}
+					);
+				}
+
+				// Hack to temporarily provide a wide and high enough dialog
+				var oldContainerWidth = $('#dialog_container')[0].style.width,
+					oldContainerMarginLeft = $('#dialog_container')[0].style.marginLeft,
+					oldContentHeight = $('#dialog_content')[0].style.height,
+					oldContainerHeight = $('#dialog_container')[0].style.height;
+				$('#dialog_content')[0].style.height = '120px';
+				$('#dialog_container')[0].style.height = '170px';
+				$('#dialog_container')[0].style.width = '800px';
+				$('#dialog_container')[0].style.marginLeft = '-400px';
+
+				// Open select-with-checkbox dialog
+				$.select(
+					uiStrings.confirmSetStorage.message,
+					options,
+					function (pref, checked) {
+						if (pref && pref !== 'noPrefsOrContent') {
+							// Regardless of whether the user opted
+							// to remember the choice (and move to a URL which won't
+							// ask them again), we have to assume the user
+							// doesn't even want to remember their not wanting
+							// storage, so we don't set the cookie or continue on with
+							//  setting storage on beforeunload
+							document.cookie = 'store=' + encodeURIComponent(pref) + '; expires=Fri, 31 Dec 9999 23:59:59 GMT'; // 'prefsAndContent' | 'prefsOnly'
+							// If the URL was configured to always insist on a prompt, if
+							//    the user does indicate a wish to store their info, we
+							//    don't want ask them again upon page refresh so move
+							//    them instead to a URL which does not always prompt
+							if (storagePrompt === true && checked) {
+								replaceStoragePrompt();
+								return;
+							}
+						}
+						else { // The user does not wish storage (or cancelled, which we treat equivalently)
+							removeStoragePrefCookie();
+							if (pref && // If the user explicitly expresses wish for no storage
+								emptyStorageOnDecline
+							) {
+								emptyStorage();
+							}
+							if (pref && checked) {
+								// Open a URL which won't set storage and won't prompt user about storage
+								replaceStoragePrompt('false');
+								return;
+							}
+						}
+
+						// Reset width/height of dialog (e.g., for use by Export)
+						$('#dialog_container')[0].style.width = oldContainerWidth;
+						$('#dialog_container')[0].style.marginLeft = oldContainerMarginLeft;				
+						$('#dialog_content')[0].style.height = oldContentHeight;
+						$('#dialog_container')[0].style.height = oldContainerHeight;
+						
+						// It should be enough to (conditionally) add to storage on
+						//   beforeunload, but if we wished to update immediately,
+						//   we might wish to try setting:
+						//       svgEditor.setConfig({noStorageOnLoad: true});
+						//   and then call:
+						//       svgEditor.loadContentAndPrefs();
+						
+						// We don't check for noStorageOnLoad here because
+						//   the prompt gives the user the option to store data
+						setupBeforeUnloadListener();
+						
+						svgEditor.storagePromptClosed = true;
+					},
+					null,
+					null,
+					{
+						label: uiStrings.confirmSetStorage.rememberLabel,
+						checked: false,
+						tooltip: uiStrings.confirmSetStorage.rememberTooltip
+					}
+				);
+			}
+			else if (!noStorageOnLoad || forceStorage) {
+				setupBeforeUnloadListener();
+			}
+		}
+	};
+});

+ 67 - 0
main/inc/lib/javascript/svgedit/extensions/ext-webappfind.js

@@ -0,0 +1,67 @@
+/*globals svgEditor*/
+/*
+Depends on Firefox add-on and executables from https://github.com/brettz9/webappfind
+
+Todos:
+1. See WebAppFind Readme for SVG-related todos
+*/
+(function () {'use strict';
+
+var pathID,
+    saveMessage = 'webapp-save',
+    readMessage = 'webapp-read',
+    excludedMessages = [readMessage, saveMessage];
+
+window.addEventListener('message', function(e) {
+    if (e.origin !== window.location.origin || // PRIVACY AND SECURITY! (for viewing and saving, respectively)
+        (!Array.isArray(e.data) || excludedMessages.indexOf(e.data[0]) > -1) // Validate format and avoid our post below
+    ) {
+        return;
+    }
+    var svgString,
+        messageType = e.data[0];
+    switch (messageType) {
+        case 'webapp-view':
+            // Populate the contents
+            pathID = e.data[1];
+            
+            svgString = e.data[2];
+            svgEditor.loadFromString(svgString);
+            
+            /*if ($('#tool_save_file')) {
+                $('#tool_save_file').disabled = false;
+            }*/
+            break;
+        case 'webapp-save-end':
+            alert('save complete for pathID ' + e.data[1] + '!');
+            break;
+        default:
+            throw 'Unexpected mode';
+    }
+}, false);
+
+window.postMessage([readMessage], window.location.origin !== 'null' ? window.location.origin : '*'); // Avoid "null" string error for file: protocol (even though file protocol not currently supported by add-on)
+
+svgEditor.addExtension('WebAppFind', function() {
+
+    return {
+        name: 'WebAppFind',
+        svgicons: svgEditor.curConfig.extPath + 'webappfind-icon.svg',
+        buttons: [{
+            id: 'webappfind_save', // 
+            type: 'app_menu',
+            title: 'Save Image back to Disk',
+            position: 4, // Before 0-based index position 4 (after the regular "Save Image (S)")
+            events: {
+                click: function () {
+                    if (!pathID) { // Not ready yet as haven't received first payload
+                        return;
+                    }
+                    window.postMessage([saveMessage, pathID, svgEditor.canvas.getSvgString()], window.location.origin);
+                }
+            }
+        }]
+    };
+});
+
+}());

+ 42 - 0
main/inc/lib/javascript/svgedit/extensions/ext-xdomain-messaging.js

@@ -0,0 +1,42 @@
+/**
+* Should not be needed for same domain control (just call via child frame),
+*  but an API common for cross-domain and same domain use can be found
+*  in embedapi.js with a demo at embedapi.html
+*/
+/*globals svgEditor, svgCanvas*/
+svgEditor.addExtension('xdomain-messaging', function() {'use strict';
+	try {
+		window.addEventListener('message', function(e) {
+			// We accept and post strings for the sake of IE9 support
+			if (typeof e.data !== 'string' || e.data.charAt() === '|') {
+				return;
+			}
+			var cbid, name, args, message, allowedOrigins, data = JSON.parse(e.data);
+			if (!data || typeof data !== 'object' || data.namespace !== 'svgCanvas') {
+				return;
+			}
+			// The default is not to allow any origins, including even the same domain or if run on a file:// URL
+			//  See config-sample.js for an example of how to configure
+			allowedOrigins = svgEditor.curConfig.allowedOrigins;
+			if (allowedOrigins.indexOf('*') === -1 && allowedOrigins.indexOf(e.origin) === -1) {
+				return;
+			}
+			cbid = data.id;
+			name = data.name;
+			args = data.args;
+			message = {
+				namespace: 'svg-edit',
+				id: cbid
+			};
+			try {
+				message.result = svgCanvas[name].apply(svgCanvas, args);
+			} catch (err) {
+				message.error = err.message;
+			}
+			e.source.postMessage(JSON.stringify(message), '*');
+		}, false);
+	}
+	catch (err) {
+		console.log('Error with xdomain message listener: ' + err);
+	}
+});

+ 34 - 0
main/inc/lib/javascript/svgedit/extensions/eyedropper-icon.xml

@@ -0,0 +1,34 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<g id="tool_eyedropper">
+<svg viewBox="0 0 320 320" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
+ <defs>
+  <radialGradient id="eyedropper_svg_6" cx="0.5" cy="0.5" r="0.5">
+   <stop offset="0" stop-color="#ffffff" stop-opacity="1"/>
+   <stop offset="1" stop-color="#e5e5e5" stop-opacity="0.38"/>
+  </radialGradient>
+  <linearGradient id="eyedropper_svg_15" x1="0" y1="0" x2="0.58594" y2="0.55078">
+   <stop offset="0" stop-color="#ffffff" stop-opacity="0.57"/>
+   <stop offset="1" stop-color="#000056" stop-opacity="1"/>
+  </linearGradient>
+  <linearGradient id="eyedropper_svg_19" x1="0" y1="0" x2="1" y2="1">
+   <stop offset="0" stop-color="#ffffff" stop-opacity="1"/>
+   <stop offset="1" stop-color="#ffffff" stop-opacity="0"/>
+  </linearGradient>
+ </defs>
+ <g display="inline">
+  <title>Layer 1</title>
+  <path d="m193.899994,73l-119.899979,118l-15,39.5l10.25,4.5l43.750015,-20l108.999969,-112l-28.100006,-30z" id="svg_3" fill="none" stroke="#000000" stroke-width="5"/>
+  <path d="m58.649994,232c-2.75,28.200012 -26.399994,28.950012 -21.899994,59c4.5,30.049988 55,28 55.5,-1.25c0.5,-29.25 -20.25,-28.75 -22.25,-54.75l-11.350006,-3z" id="svg_4" fill="#aa56ff" stroke="#000000" stroke-width="7"/>
+  <path d="m45.474976,269.275024l13.775024,0.474976l-0.75,16.75l-14.25,-1.25l1.224976,-15.974976z" id="svg_5" fill="url(#eyedropper_svg_6)" stroke-width="5" fill-opacity="0.73"/>
+  <path d="m217.899994,46c21.5,-101.549999 141.600006,20.449997 28.100006,33l-5,44l-63,-66l39.899994,-11z" id="svg_2" fill="#000000" stroke-width="5"/>
+  <path d="m206.825012,61.075008c3.712494,-2.46249 7.637482,-3.53751 14.424988,-5.575008c10.125,-16.5 32.875,-41.5 40.5,-35c7.625,6.5 -21.25,35.625 -37.5,39.25c-5.5,10.125 -8,13.875 -17.25,16.5c-2.837494,-8.162514 -4.262482,-12.337486 -0.174988,-15.174992z" id="svg_7" fill="url(#eyedropper_svg_15)" stroke-width="5"/>
+  <path d="m133.049988,134.75l46.950012,9.25l-66,70l-42.5,20.5l-11.5,-5l14,-37.5l59.049988,-57.25z" id="svg_11" fill="#aa56ff" stroke="#000000" stroke-width="7"/>
+  <path d="m71.425034,212.350006l9.050888,-20.022537l51.516724,-49.327469l8.507355,0.97197l-69.074966,68.378036z" id="svg_16" fill="url(#eyedropper_svg_19)" stroke-width="5"/>
+ </g>
+</svg>
+</g>
+	
+	<g id="svg_eof"/>
+</svg>

BIN
main/inc/lib/javascript/svgedit/extensions/eyedropper.png


+ 56 - 0
main/inc/lib/javascript/svgedit/extensions/fileopen.php

@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<?php
+/*
+ * fileopen.php
+ * To be used with ext-server_opensave.js for SVG-edit
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2010 Alexis Deveria
+ *
+ */
+	// Very minimal PHP file, all we do is Base64 encode the uploaded file and
+	// return it to the editor
+	
+	if (!isset($_REQUEST['type'])) {
+		echo "No type given";
+		exit;
+	}
+	$type = $_REQUEST['type'];
+	if (!in_array($type, array('load_svg', 'import_svg', 'import_img'))) {
+		echo "Not a recognized type";
+		exit;
+	}
+
+	require('allowedMimeTypes.php');
+	
+	$file = $_FILES['svg_file']['tmp_name'];
+	
+	$output = file_get_contents($file);
+	
+	$prefix = '';
+
+	// Make Data URL prefix for import image
+	if ($type == 'import_img') {
+		$info = getimagesize($file);
+		if (!in_array($info['mime'], $allowedMimeTypesBySuffix)) {
+			echo "Disallowed MIME for supplied file";
+			exit;
+		}
+		$prefix = 'data:' . $info['mime'] . ';base64,';
+	}
+?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta charset="utf-8" />
+<script>
+
+top.svgEditor.processFile("<?php 
+
+// This should be safe since SVG edit does its own filtering (e.g., if an SVG file contains scripts)
+echo $prefix . base64_encode($output);
+
+?>", "<?php echo $type; ?>");
+</script>
+</head><body></body>
+</html>

+ 60 - 0
main/inc/lib/javascript/svgedit/extensions/filesave.php

@@ -0,0 +1,60 @@
+<?php
+/*
+ * filesave.php
+ * To be used with ext-server_opensave.js for SVG-edit
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2010 Alexis Deveria
+ *
+ */
+
+function encodeRFC5987ValueChars ($str) {
+	// See http://tools.ietf.org/html/rfc5987#section-3.2.1
+	// For better readability within headers, add back the characters escaped by rawurlencode but still allowable
+	// Although RFC3986 reserves "!" (%21), RFC5987 does not
+	return preg_replace_callback('@%(2[1346B]|5E|60|7C)@', function ($matches) {
+		return chr('0x' . $matches[1]);
+	}, rawurlencode($str));
+}
+
+require('allowedMimeTypes.php');
+
+$mime = (!isset($_POST['mime']) || !in_array($_POST['mime'], $allowedMimeTypesBySuffix)) ? 'image/svg+xml;charset=UTF-8' : $_POST['mime'];
+
+if (!isset($_POST['output_svg']) && !isset($_POST['output_img'])) {
+	die('post fail');
+}
+
+$file = '';
+
+$suffix = '.' . array_search($mime, $allowedMimeTypesBySuffix);
+
+if (isset($_POST['filename']) && strlen($_POST['filename']) > 0) {
+	$file = $_POST['filename'] . $suffix;
+} else {
+	$file = 'image' . $suffix;
+}
+
+if ($suffix == '.svg') {
+	$contents = $_POST['output_svg'];
+} else {
+	$contents = $_POST['output_img'];
+	$pos = (strpos($contents, 'base64,') + 7);
+	$contents = base64_decode(substr($contents, $pos));
+}
+
+header("Cache-Control: public");
+header("Content-Description: File Transfer");
+
+// See http://tools.ietf.org/html/rfc6266#section-4.1
+header("Content-Disposition: attachment; filename*=UTF-8''" . encodeRFC5987ValueChars(
+	// preg_replace('@[\\\\/:*?"<>|]@', '', $file) // If we wanted to strip Windows-disallowed characters server-side (but not a security issue, so we can strip client-side instead)
+	$file
+));
+header("Content-Type: " .  $mime);
+header("Content-Transfer-Encoding: binary");
+
+echo $contents;
+ 
+?>

+ 96 - 0
main/inc/lib/javascript/svgedit/extensions/foreignobject-icons.xml

@@ -0,0 +1,96 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+	<g id="tool_foreign">
+		<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 84 84">
+		  <g fill="#444" opacity="0.2" transform="translate(6,6)">
+		  <path d="M42.8,74.3c0,4.3,0,5.9,11.8,5.9l4.1,0l0,3.8c-4.5-0.4-16.1-0.4-21.2-0.3c-5.1,0-16.6,0-21,0.4l0-3.8l4.1,0
+			c11.8,0,11.8-1.7,11.8-5.9l0-6.9C13.9,65.6,0,54.6,0,42c0-12.2,13.3-23.5,32.4-25.4l0-6.9c0-4.3,0-5.9-11.8-5.9l-4.1,0l0-3.8
+			c4.5,0.4,16.1,0.4,21.2,0.3c5.1,0,16.6,0,21-0.4l0,3.8l-4.1,0c-11.8,0-11.8,1.7-11.8,5.9l0,6.9C61.6,18.1,75.8,29.2,75.8,42
+			c0,12.4-13.8,23.9-33.1,25.4L42.8,74.3z M32.4,19.4c-18.7,2.5-19.9,16.2-19.9,22.6c0,7.6,2.3,20.2,20,22.5L32.4,19.4z M42.7,64.7
+			c18.8-2.2,20.7-15.4,20.6-22.8c0-9.3-3.5-20.6-20.7-22.6L42.7,64.7z"/>
+		  </g>
+		  <g fill="#444" opacity="0.3" transform="translate(4,4)">
+		  <path d="M42.8,74.3c0,4.3,0,5.9,11.8,5.9l4.1,0l0,3.8c-4.5-0.4-16.1-0.4-21.2-0.3c-5.1,0-16.6,0-21,0.4l0-3.8l4.1,0
+			c11.8,0,11.8-1.7,11.8-5.9l0-6.9C13.9,65.6,0,54.6,0,42c0-12.2,13.3-23.5,32.4-25.4l0-6.9c0-4.3,0-5.9-11.8-5.9l-4.1,0l0-3.8
+			c4.5,0.4,16.1,0.4,21.2,0.3c5.1,0,16.6,0,21-0.4l0,3.8l-4.1,0c-11.8,0-11.8,1.7-11.8,5.9l0,6.9C61.6,18.1,75.8,29.2,75.8,42
+			c0,12.4-13.8,23.9-33.1,25.4L42.8,74.3z M32.4,19.4c-18.7,2.5-19.9,16.2-19.9,22.6c0,7.6,2.3,20.2,20,22.5L32.4,19.4z M42.7,64.7
+			c18.8-2.2,20.7-15.4,20.6-22.8c0-9.3-3.5-20.6-20.7-22.6L42.7,64.7z"/>
+		  </g>
+		  <g fill="#444" opacity="0.5" transform="translate(2,2)">
+		  <path d="M42.8,74.3c0,4.3,0,5.9,11.8,5.9l4.1,0l0,3.8c-4.5-0.4-16.1-0.4-21.2-0.3c-5.1,0-16.6,0-21,0.4l0-3.8l4.1,0
+			c11.8,0,11.8-1.7,11.8-5.9l0-6.9C13.9,65.6,0,54.6,0,42c0-12.2,13.3-23.5,32.4-25.4l0-6.9c0-4.3,0-5.9-11.8-5.9l-4.1,0l0-3.8
+			c4.5,0.4,16.1,0.4,21.2,0.3c5.1,0,16.6,0,21-0.4l0,3.8l-4.1,0c-11.8,0-11.8,1.7-11.8,5.9l0,6.9C61.6,18.1,75.8,29.2,75.8,42
+			c0,12.4-13.8,23.9-33.1,25.4L42.8,74.3z M32.4,19.4c-18.7,2.5-19.9,16.2-19.9,22.6c0,7.6,2.3,20.2,20,22.5L32.4,19.4z M42.7,64.7
+			c18.8-2.2,20.7-15.4,20.6-22.8c0-9.3-3.5-20.6-20.7-22.6L42.7,64.7z"/>
+		  </g>
+		  <g fill="#0000CC">
+		  <path id="xyz321" d="M42.8,74.3c0,4.3,0,5.9,11.8,5.9l4.1,0l0,3.8c-4.5-0.4-16.1-0.4-21.2-0.3c-5.1,0-16.6,0-21,0.4l0-3.8l4.1,0
+			c11.8,0,11.8-1.7,11.8-5.9l0-6.9C13.9,65.6,0,54.6,0,42c0-12.2,13.3-23.5,32.4-25.4l0-6.9c0-4.3,0-5.9-11.8-5.9l-4.1,0l0-3.8
+			c4.5,0.4,16.1,0.4,21.2,0.3c5.1,0,16.6,0,21-0.4l0,3.8l-4.1,0c-11.8,0-11.8,1.7-11.8,5.9l0,6.9C61.6,18.1,75.8,29.2,75.8,42
+			c0,12.4-13.8,23.9-33.1,25.4L42.8,74.3z M32.4,19.4c-18.7,2.5-19.9,16.2-19.9,22.6c0,7.6,2.3,20.2,20,22.5L32.4,19.4z M42.7,64.7
+			c18.8-2.2,20.7-15.4,20.6-22.8c0-9.3-3.5-20.6-20.7-22.6L42.7,64.7z"/>
+		  </g>
+		</svg>
+	</g>
+	
+	<g id="edit_foreign">
+		<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="34 38 170 170" overflow="hidden">
+		<g fill="#000088">
+			<path d="M30.1,63.9v-4.3l30.2-14.9V50L36.5,61.7l23.8,11.7v5.3L30.1,63.9z"/>
+			<path d="M106.1,79.7v-1.1c4.2-0.5,4.8-1.1,4.8-5.2V58.2c0-6-1.3-7.9-5.4-7.9c-3.3,0-5.7,1.3-7.8,4.4v18.1
+				c0,4.5,1.1,5.7,5.2,5.8v1.1H86.8v-1.1c4.1-0.3,4.9-1.1,4.9-5.1V57.9c0-5-1.6-7.6-4.8-7.6c-2.5,0-5.6,1.2-7.4,2.9
+				c-0.5,0.5-1.1,1.4-1.1,1.4v20.3c0,2.8,1.1,3.6,4.9,3.7v1.1h-16v-1.1c4-0.1,5-1.2,5-5V55.4c0-3.5-0.6-4.6-2.5-4.6
+				c-0.8,0-1.4,0.1-2.3,0.3v-1.2c4-1.1,6.4-1.9,10.1-3.2l0.5,0.1v5.4c6-4.5,8-5.5,11.2-5.5c3.9,0,6.3,1.9,7.6,6c3.9-4.2,7.6-6,11.7-6
+				c5.5,0,8.4,4.3,8.4,12.8v14.8c0,2.8,0.9,4.1,3.1,4.2l1.9,0.1v1.1H106.1z"/>
+			<path d="M147.3,80.5c-3,0-4.2-1.4-4.6-5.3c-4.4,3.7-7.3,5.3-10.5,5.3c-4.5,0-7.6-3.2-7.6-7.7c0-2.4,1-4.8,2.6-6.3
+				c3.1-2.7,4.3-3.3,15.4-7.8v-4.4c0-3.9-1.9-6-5.5-6c-2.9,0-5.2,1.6-5.2,3.5c0,0.5,0.1,1.1,0.2,1.7c0.1,0.5,0.1,0.9,0.1,1.2
+				c0,1.6-1.5,3-3.2,3s-3.1-1.4-3.1-3.1c0-1.8,1.2-3.9,3-5.4c2-1.7,5.5-2.7,9.1-2.7c4.4,0,7.5,1.4,9,4.2c1,1.7,1.4,3.7,1.4,7.3v14
+				c0,3.2,0.5,4.2,2.2,4.2c1.1,0,1.9-0.4,3.2-1.4v1.9C151.3,79.6,149.8,80.5,147.3,80.5z M142.6,60.5c-8.7,3.2-11.7,5.8-11.7,10v0.3
+				c0,3.1,2,5.5,4.5,5.5c1.5,0,3.5-0.6,5.3-1.6c1.5-0.9,1.9-1.6,1.9-3.8V60.5z"/>
+			<path d="M165.3,80.5c-4.2,0-6.3-3.1-6.3-9.1V49.7h-3.8c-0.2-0.1-0.3-0.3-0.3-0.5c0-0.4,0.4-0.9,1.2-1.4
+				c1.9-1.1,4.3-3.7,7-7.7c0.5-0.6,1-1.3,1.4-2c0.4,0,0.5,0.2,0.5,0.9v8.4h7.3v2.3h-7.3v20.6c0,4.6,1.1,6.5,3.7,6.5
+				c1.6,0,2.7-0.6,4.3-2.5l0.9,0.8C171.8,78.7,169,80.5,165.3,80.5z"/>
+			<path d="M193.8,79.7v-1.1c4.1-0.4,4.9-1.3,4.9-6.2V58.1c0-5-1.8-7.6-5.4-7.6c-2.8,0-5,1.2-8,4.5v17.4
+				c0,5,0.7,5.8,4.9,6.3v1.1h-15.6v-1.1c4.2-0.6,4.6-1.2,4.6-6.3V38.5c0-3.1-0.6-3.7-3.7-3.7c-0.4,0-0.6,0-0.9,0.1v-1.2l1.9-0.6
+				c4-1.2,5.8-1.7,8.3-2.6l0.4,0.2v21.9c3.3-4.3,6.3-6,10.6-6c5.9,0,8.9,3.9,8.9,11.5v14.3c0,5,0.4,5.5,4.3,6.3v1.1h-15.2V79.7z"/>
+			<path d="M59.1,116.1v-4.3l30.2-14.9v5.3l-23.8,11.7l23.8,11.7v5.3L59.1,116.1z"/>
+			<path d="M135.1,131.9v-1.1c4.2-0.5,4.8-1.1,4.8-5.2v-15.1c0-6-1.3-7.9-5.4-7.9c-3.3,0-5.7,1.3-7.8,4.4v18.1
+				c0,4.5,1.1,5.7,5.2,5.8v1.1h-16.1v-1.1c4.1-0.3,4.9-1.1,4.9-5.1v-15.7c0-5-1.6-7.6-4.8-7.6c-2.5,0-5.6,1.2-7.4,2.9
+				c-0.5,0.5-1.1,1.4-1.1,1.4v20.3c0,2.8,1.1,3.6,4.9,3.7v1.1h-16v-1.1c4-0.1,5-1.2,5-5v-18.2c0-3.5-0.6-4.6-2.5-4.6
+				c-0.8,0-1.4,0.1-2.3,0.3v-1.2c4-1.1,6.4-1.9,10.1-3.2l0.5,0.1v5.4c6-4.5,8-5.5,11.2-5.5c3.9,0,6.3,1.9,7.6,6c3.9-4.2,7.6-6,11.7-6
+				c5.5,0,8.4,4.3,8.4,12.8v14.8c0,2.8,0.9,4.1,3.1,4.2l1.9,0.1v1.1H135.1z"/>
+			<path d="M152.1,131.9v-1.1c5-0.3,5.7-1.1,5.7-6.3v-16.6c0-3.2-0.6-4.3-2.4-4.3c-0.6,0-1.6,0.1-2.4,0.2l-0.6,0.1v-1.1
+				l11.2-4L164,99v25.6c0,5.2,0.6,5.9,5.3,6.3v1.1L152.1,131.9L152.1,131.9z M160.8,93.1c-2,0-3.7-1.6-3.7-3.7c0-2,1.7-3.7,3.7-3.7
+				c2.1,0,3.7,1.7,3.7,3.7C164.6,91.6,163,93.1,160.8,93.1z"/>
+			<path d="M175.8,131v-5.3l23.7-11.8l-23.7-11.7v-5.3l30.1,14.9v4.3L175.8,131z"/>
+			<path d="M31.1,169.5v-4.3l30.2-14.9v5.3l-23.8,11.7L61.3,179v5.3L31.1,169.5z"/>
+			<path d="M71.3,186.4h-4.9l16.5-49.7h4.8L71.3,186.4z"/>
+			<path d="M127.1,185.3v-1.1c4.2-0.5,4.8-1.1,4.8-5.2v-15.2c0-6-1.3-7.9-5.4-7.9c-3.3,0-5.7,1.3-7.8,4.4v18.1
+				c0,4.5,1.1,5.7,5.2,5.8v1.1h-16.1v-1.1c4.1-0.3,4.9-1.1,4.9-5.1v-15.6c0-5-1.6-7.6-4.8-7.6c-2.5,0-5.6,1.2-7.4,2.9
+				c-0.5,0.5-1.1,1.4-1.1,1.4v20.3c0,2.8,1.1,3.6,4.9,3.7v1.1h-16v-1.1c4-0.1,5-1.2,5-5V161c0-3.5-0.6-4.6-2.5-4.6
+				c-0.8,0-1.4,0.1-2.3,0.3v-1.2c4-1.1,6.4-1.9,10.1-3.2l0.5,0.1v5.4c6-4.5,8-5.5,11.2-5.5c3.9,0,6.3,1.9,7.6,6c3.9-4.2,7.6-6,11.7-6
+				c5.5,0,8.4,4.3,8.4,12.8v14.8c0,2.8,0.9,4.1,3.1,4.2l1.9,0.1v1.1H127.1L127.1,185.3z"/>
+			<path d="M168.3,186.1c-3,0-4.2-1.4-4.6-5.3c-4.4,3.7-7.3,5.3-10.5,5.3c-4.5,0-7.6-3.2-7.6-7.7c0-2.4,1-4.8,2.6-6.3
+				c3.1-2.7,4.3-3.3,15.4-7.8v-4.4c0-3.9-1.9-6-5.5-6c-2.9,0-5.2,1.6-5.2,3.5c0,0.5,0.1,1.1,0.2,1.7c0.1,0.5,0.1,0.9,0.1,1.2
+				c0,1.6-1.5,3-3.2,3s-3.1-1.4-3.1-3.1c0-1.8,1.2-3.9,3-5.4c2-1.7,5.5-2.7,9.1-2.7c4.4,0,7.5,1.4,9,4.2c1,1.7,1.4,3.7,1.4,7.3v14
+				c0,3.2,0.5,4.2,2.2,4.2c1.1,0,1.9-0.4,3.2-1.4v1.9C172.3,185.2,170.8,186.1,168.3,186.1z M163.8,166.1c-8.7,3.2-11.7,5.8-11.7,10
+				v0.3c0,3.1,2,5.5,4.5,5.5c1.5,0,3.5-0.6,5.3-1.6c1.5-0.9,1.9-1.6,1.9-3.8V166.1z"/>
+			<path d="M186.3,186.1c-4.2,0-6.3-3.1-6.3-9.1v-21.7h-3.8c-0.2-0.1-0.3-0.3-0.3-0.5c0-0.4,0.4-0.9,1.2-1.4
+				c1.9-1.1,4.3-3.7,7-7.7c0.5-0.6,1-1.3,1.4-2c0.4,0,0.5,0.2,0.5,0.9v8.4h7.3v2.3h-7.3v20.6c0,4.6,1.1,6.5,3.7,6.5
+				c1.6,0,2.7-0.6,4.3-2.5l0.9,0.8C192.8,184.3,190,186.1,186.3,186.1z"/>
+			<path d="M209.1,185.3h-13.4v-1.1c4.2-0.6,4.6-1.2,4.6-6.3V144c0-3.1-0.6-3.7-3.7-3.7c-0.4,0-0.6,0-0.9,0.1v-1.2
+				l1.9-0.6c4-1.2,5.8-1.7,8.3-2.6l0.4,0.2v21.9c0.9-1.2,1.9-2.2,2.8-3.1"/>
+			<path d="M209.1,157.9c-0.8,0.7-1.7,1.5-2.7,2.6v17.4c0,4,0.4,5.3,2.7,5.9"/>
+		  </g>
+		  <g>
+			<polyline opacity="0.2" fill="#231F20" points="209.1,76.4 118.7,186.5 139.1,186.4 209.1,121 209.1,76.4 "/>
+			<polyline opacity="0.4" fill="#231F20" points="209.1,76.2 118.5,186.5 129.7,186.4 200.2,120.3 209.1,100.8 209.1,76.4 "/>
+			<path fill="#FFD761" d="M121.6,88.7l0.8,87.5l62.3-56.7c0,0-15.3-25.8-24.8-30C151.1,85.6,121.6,88.7,121.6,88.7z"/>
+			<path fill="#FEA01E" d="M209.1,19.5h-54l-33.5,69.2c0,0,29.7-3.4,38.3,0.8c8.9,4.4,25,30.8,25,30.8l24.2-50V19.5z"/>
+			<path d="M120.4,153.7l-0.6,25l23.8-16.9c0,0-8-7-11.2-8.1C129.4,152.8,120.4,153.7,120.4,153.7z"/>
+			<polyline fill="none" stroke="#231F20" stroke-width="5" points="153.9,19.5 121.6,88.7 120.7,181.2 186.6,120.3 209.1,70.3 "/>
+		  </g>
+		</svg>
+	</g>
+	
+	<g id="svg_eof"/>
+</svg>

+ 30 - 0
main/inc/lib/javascript/svgedit/extensions/grid-icon.xml

@@ -0,0 +1,30 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+<!-- 
+	Sample icons file. This file looks like an SVG file with groups as its
+	children. Each group element has an ID that must match the ID of the button given
+	in the extension. The SVG inside the group makes up the actual icon, and
+	needs use a viewBox instead of width/height for it to scale	properly.
+	
+	Multiple icons can be included, each within their own group.
+-->
+	<g id="view_grid">
+	  <svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+		<g>
+		  <rect fill="#ffffff" stroke="#848484" x="2" y="2" width="20" height="20"/>
+		  <line fill="none" stroke="#848484" x1="11.84375" y1="-1.53125" x2="11.84375" y2="18.46875" transform="rotate(90, 11.8429, 8.46955)"/>
+		  <line fill="none" stroke="#848484" x1="11.90625" y1="5.21875" x2="11.90625" y2="25.21875" transform="rotate(90, 11.9054, 15.2196)"/>
+		  <line fill="none" stroke="#848484" x1="8.5" y1="2.03125" x2="8.5" y2="22.03125"/>
+		  <line fill="none" stroke="#848484" x1="15.5" y1="2.03125" x2="15.5" y2="22.03125"/>
+		  <rect fill="#d8d8d8" stroke="#000000" stroke-width="0" x="3.25" y="3.28125" width="4" height="4"/>
+		  <rect fill="#d8d8d8" stroke="#000000" stroke-width="0" x="10" y="3.28125" width="4" height="4"/>
+		  <rect fill="#d8d8d8" stroke="#000000" stroke-width="0" x="16.75" y="3.28125" width="4" height="4"/>
+		  <rect fill="#d8d8d8" stroke="#000000" stroke-width="0" x="3.28125" y="9.75" width="4" height="4"/>
+		  <rect fill="#d8d8d8" stroke="#000000" stroke-width="0" x="10.03125" y="9.75" width="4" height="4"/>
+		  <rect fill="#d8d8d8" stroke="#000000" stroke-width="0" x="16.78125" y="9.75" width="4" height="4"/>
+		  <rect fill="#d8d8d8" stroke="#000000" stroke-width="0" x="3.3125" y="16.59375" width="4" height="4"/>
+		  <rect fill="#d8d8d8" stroke="#000000" stroke-width="0" x="10.0625" y="16.59375" width="4" height="4"/>
+		  <rect fill="#d8d8d8" stroke="#000000" stroke-width="0" x="16.8125" y="16.59375" width="4" height="4"/>
+		</g>
+	  </svg>
+	</g>
+</svg>

+ 21 - 0
main/inc/lib/javascript/svgedit/extensions/helloworld-icon.xml

@@ -0,0 +1,21 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+<!-- 
+	Sample icons file. This file looks like an SVG file with groups as its
+	children. Each group element has an ID that must match the ID of the button given
+	in the extension. The SVG inside the group makes up the actual icon, and
+	needs use a viewBox instead of width/height for it to scale	properly.
+	
+	Multiple icons can be included, each within their own group.
+-->
+	<g id="hello_world">
+		<svg width="102" height="102" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+		 <!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
+		 <g>
+		  <title>Layer 1</title>
+		  <rect ry="30" rx="30" x="2.5" y="2.5" width="97" height="97" id="svg_3" fill="#008000" stroke="#000000" stroke-width="5"/>
+		  <text x="52.668" y="42.5" id="svg_1" fill="#ffffff" stroke="#000000" stroke-width="0" font-size="24" font-family="Monospace" text-anchor="middle" xml:space="preserve">Hello</text>
+		  <text x="52.668" y="71.5" fill="#ffffff" stroke="#000000" stroke-width="0" font-size="24" font-family="Monospace" text-anchor="middle" xml:space="preserve" id="svg_2">World!</text>
+		 </g>
+		</svg>
+	</g>
+</svg>

+ 70 - 0
main/inc/lib/javascript/svgedit/extensions/imagelib/index.html

@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta charset="utf-8" />
+<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
+</head>
+<body>
+
+<h1>Select an image:</h1>
+<a href="smiley.svg">smiley.svg</a>
+<br>
+<a href="../../images/logo.png">logo.png</a>
+
+<script>
+/*globals $*/
+/*jslint vars: true*/
+$('a').click(function() {'use strict';
+	var meta_str;
+	var href = this.href;
+	var target = window.parent;
+	// Convert Non-SVG images to data URL first 
+	// (this could also have been done server-side by the library)
+	if (this.href.indexOf('.svg') === -1) {
+
+		meta_str = JSON.stringify({
+			name: $(this).text(),
+			id: href
+		});
+		target.postMessage(meta_str, '*');
+
+		var img = new Image();
+		img.onload = function() {
+			var canvas = document.createElement('canvas');
+			canvas.width = this.width;
+			canvas.height = this.height;
+			// load the raster image into the canvas
+			canvas.getContext('2d').drawImage(this, 0, 0);
+			// retrieve the data: URL
+			var dataurl;
+			try {
+				dataurl = canvas.toDataURL();
+			} catch(err) {
+				// This fails in Firefox with file:// URLs :(
+				alert("Data URL conversion failed: " + err);
+				dataurl = "";
+			}
+			target.postMessage('|' + href + '|' + dataurl, '*');
+		};
+		img.src = href;
+	} else {
+		// Send metadata (also indicates file is about to be sent)
+		meta_str = JSON.stringify({
+			name: $(this).text(),
+			id: href
+		});
+		target.postMessage(meta_str, '*');
+		// Do ajax request for image's href value
+		$.get(href, function(data) {
+			data = '|' + href + '|' + data;
+			// This is where the magic happens!
+			target.postMessage(data, '*');
+			
+		}, 'html'); // 'html' is necessary to keep returned data as a string
+	}
+	return false;
+});
+
+</script>
+</body>
+</html>

+ 12 - 0
main/inc/lib/javascript/svgedit/extensions/imagelib/smiley.svg

@@ -0,0 +1,12 @@
+<svg width="137" height="137" xmlns="http://www.w3.org/2000/svg">
+ <title>Cool smiley</title>
+  <path fill="url(#svg_4)" stroke="#000000" stroke-width="3" d="m32.18682,97.71674q36.3159,24.94076 72.54585,0m-64.67542,-49.25576c0,-3.8554 3.12526,-6.98079 6.98068,-6.98079c3.85449,0 6.97872,3.12539 6.97872,6.98079c0,3.85346 -3.12423,6.97867 -6.97872,6.97867c-3.85542,0 -6.98068,-3.12521 -6.98068,-6.97867m42.93047,0c0,-3.8554 3.12529,-6.98079 6.97963,-6.98079c3.8544,0 6.97971,3.12539 6.97971,6.98079c0,3.85346 -3.12531,6.97867 -6.97971,6.97867c-3.85434,0 -6.97963,-3.12521 -6.97963,-6.97867m-81.48596,20.036l0,0c0,-37.00197 29.99679,-66.99892 67.00095,-66.99892c37.00303,0 66.99998,29.99695 66.99998,66.99892c0,37.00409 -29.99695,67.00101 -66.99998,67.00101c-37.00416,0 -67.00095,-29.99692 -67.00095,-67.00101zm0,0l0,0c0,-37.00197 29.99679,-66.99892 67.00095,-66.99892c37.00303,0 66.99998,29.99695 66.99998,66.99892c0,37.00409 -29.99695,67.00101 -66.99998,67.00101c-37.00416,0 -67.00095,-29.99692 -67.00095,-67.00101z" id="svg_1"/>
+  <path id="svg_5" d="m23.84005,41.03445l17.57052,0l5.42937,-19.67914l5.42941,19.67914l17.5706,0l-14.21488,12.16242l5.42982,19.67939l-14.21495,-12.16281l-14.21489,12.16281l5.42991,-19.67939l-14.21491,-12.16242l0,0z" stroke-width="3" fill="#000000"/>
+  <path id="svg_6" d="m65.84005,41.03445l17.57052,0l5.42937,-19.67914l5.42941,19.67914l17.5706,0l-14.21487,12.16242l5.42982,19.67939l-14.21496,-12.1628l-14.2149,12.1628l5.42992,-19.67939l-14.21491,-12.16242l0,0z" stroke-width="3" fill="#000000"/>
+ <defs>
+  <linearGradient y2="0.25391" x2="0.46484" y1="0.94922" x1="0.44531" id="svg_4">
+   <stop stop-color="#ff0000" offset="0"/>
+   <stop stop-color="#ffff00" offset="1"/>
+  </linearGradient>
+ </defs>
+</svg>

+ 115 - 0
main/inc/lib/javascript/svgedit/extensions/markers-icons.xml

@@ -0,0 +1,115 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+    <!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
+    <g id="nomarker">
+        <svg viewBox="-60 -60 120 120" xmlns="http://www.w3.org/2000/svg">
+            <path stroke-width="10" stroke="#ff7f00" fill="#ff7f00" d="m-50,0l100,0"/>
+        </svg>
+    </g>
+    <g id="leftarrow">
+        <svg viewBox="-60 -60 120 120" xmlns="http://www.w3.org/2000/svg">
+            <path stroke-width="10" stroke="#ff7f00" fill="#ff7f00" d="m-50,0l100,40l-30,-40l30,-40z"/>
+        </svg>
+    </g>
+    <g id="rightarrow">
+        <svg viewBox="-60 -60 120 120" xmlns="http://www.w3.org/2000/svg">
+            <path stroke-width="10" stroke="#ff7f00" fill="#ff7f00" d="m50,0l-100,40l30,-40l-30,-40z"/>
+        </svg>
+    </g>
+    <g id="leftarrow_o">
+        <svg viewBox="-60 -60 120 120" xmlns="http://www.w3.org/2000/svg">
+            <path stroke-width="10" stroke="#ff7f00" fill="none" d="m-50,0l100,40l-30,-40l30,-40z"/>
+        </svg>
+    </g>
+    <g id="rightarrow_o">
+        <svg viewBox="-60 -60 120 120" xmlns="http://www.w3.org/2000/svg">
+            <path stroke-width="10" stroke="#ff7f00" fill="none" d="m50,0l-100,40l30,-40l-30,-40z"/>
+        </svg>
+    </g>
+    <g id="forwardslash">
+        <svg viewBox="-60 -60 120 120" xmlns="http://www.w3.org/2000/svg">
+            <path stroke-width="10" stroke="#ff7f00" fill="none" d="m-20,50l40,-100"/>
+        </svg>
+    </g>
+    <g id="reverseslash">
+        <svg viewBox="-60 -60 120 120" xmlns="http://www.w3.org/2000/svg">
+            <path stroke-width="10" stroke="#ff7f00" fill="none" d="m-20,-50l40,100"/>
+        </svg>
+    </g>
+    <g id="verticalslash">
+        <svg viewBox="-60 -60 120 120" xmlns="http://www.w3.org/2000/svg">
+            <path stroke-width="10" stroke="#ff7f00" fill="none" d="m0,-50l0,100"/>
+        </svg>
+    </g>
+    <g id="mcircle">
+        <svg viewBox="-60 -60 120 120" xmlns="http://www.w3.org/2000/svg">
+            <circle stroke-width="10" stroke="#ff7f00" fill="#ff7f00" cy="0" cx="0" r="30"/>
+        </svg>
+    </g>
+    <g id="mcircle_o">
+        <svg viewBox="-60 -60 120 120" xmlns="http://www.w3.org/2000/svg">
+            <circle stroke-width="10" stroke="#ff7f00" fill="none" cy="0" cx="0" r="30"/>
+        </svg>
+    </g>
+    <g id="xmark">
+        <svg viewBox="-60 -60 120 120" xmlns="http://www.w3.org/2000/svg">
+            <path stroke-width="10" stroke="#ff7f00" fill="#ff7f00" d="m-30,30l60,-60m0,60l-60,-60"/>
+        </svg>
+    </g>
+    <g id="box">
+        <svg viewBox="-60 -60 120 120" xmlns="http://www.w3.org/2000/svg">
+            <path stroke-width="10" stroke="#ff7f00" fill="#ff7f00" d="m-30,-30l0,60l60,0l0,-60z"/>
+        </svg>
+    </g>
+    <g id="star">
+        <svg viewBox="-60 -60 120 120" xmlns="http://www.w3.org/2000/svg">
+            <path stroke-width="10" stroke="#ff7f00" fill="#ff7f00" d="m-40,-20l80,0l-70,60l30,-80l30,80z"/>
+        </svg>
+    </g>
+    <g id="box_o">
+        <svg viewBox="-60 -60 120 120" xmlns="http://www.w3.org/2000/svg">
+            <path stroke-width="10" stroke="#ff7f00" fill="none" d="m-30,-30l0,60l60,0l0,-60z"/>
+        </svg>
+    </g>
+    <g id="star_o">
+        <svg viewBox="-60 -60 120 120" xmlns="http://www.w3.org/2000/svg">
+            <path stroke-width="10" stroke="#ff7f00" fill="none" d="m-40,-20l80,0l-70,60l30,-80l30,80z"/>
+        </svg>
+    </g>
+     <g id="triangle_o">
+        <svg viewBox="-60 -60 120 120" xmlns="http://www.w3.org/2000/svg">
+            <path stroke-width="10" stroke="#ff7f00" fill="none" d="M-30,30 L0,-30 L30,30 Z"/>
+        </svg>
+    </g>
+     <g id="triangle">
+        <svg viewBox="-60 -60 120 120" xmlns="http://www.w3.org/2000/svg">
+            <path stroke-width="10" stroke="#ff7f00" fill="#ff7f00" d="M-30,30 L0,-30 L30,30 Z"/>
+        </svg>
+    </g>
+   <g id="textmarker">
+        <svg viewBox="-60 -60 120 120" xmlns="http://www.w3.org/2000/svg">
+			<text xml:space="preserve" text-anchor="middle" font-family="serif" font-size="120"  y="40" x="0" stroke-width="0" stroke="#ff7f00" fill="#ff7f00">T</text>	
+        </svg>
+    </g>
+	<g id="mkr_markers_off">
+        <svg viewBox="-60 -60 120 120" xmlns="http://www.w3.org/2000/svg">
+		   <line y2="0" x2="50" y1="0" x1="-50" stroke-width="5" stroke="#ff7f00" fill="none"/>
+       </svg>
+    </g>
+	<g id="mkr_markers_dimension">
+        <svg viewBox="-60 -60 120 120" xmlns="http://www.w3.org/2000/svg">
+ 		   <line y2="0" x2="40" y1="0" x1="20" stroke-width="5" stroke="#ff7f00" fill="none"/>
+		   <line y2="0" x2="-40" y1="0" x1="-20" stroke-width="5" stroke="#ff7f00" fill="none"/>
+		   <text text-anchor="middle" font-family="serif" font-size="80"  y="20" x="0" stroke-width="0" stroke="#ff7f00" fill="#ff7f00">T</text>
+           <path stroke-width="5" stroke="#ff7f00" fill="#ff7f00" d="M-50,0 L-30,-15 L-30,15 Z"/>
+           <path stroke-width="5" stroke="#ff7f00" fill="#ff7f00" d="M50,0 L30,-15 L30,15 Z"/>
+ 		</svg>
+    </g>
+   	<g id="mkr_markers_label">
+        <svg viewBox="-60 -60 120 120" xmlns="http://www.w3.org/2000/svg">
+  		   <line y2="0" x2="40" y1="0" x1="-20" stroke-width="5" stroke="#ff7f00" fill="none"/>
+		   <text text-anchor="middle" font-family="serif" font-size="80"  y="20" x="-40" stroke-width="0" stroke="#ff7f00" fill="#ff7f00">T</text>
+           <path stroke-width="5" stroke="#ff7f00" fill="#ff7f00" d="M50,0 L30,-15 L30,15 Z"/>
+        </svg>
+    </g>
+	<g id="svg_eof"/>
+</svg>

+ 11 - 0
main/inc/lib/javascript/svgedit/extensions/mathjax-icons.xml

@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+  <g id="tool_mathjax">
+    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 43 43">
+      <g id="svg_20" stroke="black" fill="black" transform="matrix(0.0510725, 0, 0, -0.0510725, 0, 37.4108)">
+        <path id="svg_21" stroke-width="10" d="m20.45372,122.639297q0,57 19,104t49,78.000015t67,52t70,31t63.000015,9l3,0q45,0 78,-22t33,-65q0,-90.000015 -111,-118.000015q-49.000015,-13 -134.000015,-14q-37,0 -38,-2q0,-2 -6,-35t-7,-58q0,-46.999996 21,-73.999996t63,-28t93.000015,19t92,65.999996q9,10 12,10q4,0 13,-9t10,-13.999996t-9,-16t-30,-27t-46,-31t-63,-25t-76.000015,-10q-79,0 -122,53t-44,125.999996zm334.000015,185.000015q-6,52 -68,52q-33.000015,0 -61.000015,-14t-45,-34t-29,-41t-16,-36.000015t-5,-19q0,-1 20,-1q113.000015,0 158.000015,24t46,69.000015z"/>
+        <path id="svg_22" stroke-width="10" d="m489.156433,571.236389q4.949707,29.698486 38.183716,68.589355t82.024414,39.597961q24.748657,0 45.254761,-12.727905t30.40564,-31.819824q29.698486,44.547729 71.417847,44.547729q26.162842,0 45.254761,-15.556335t19.79895,-41.719299q0,-20.506104 -9.899414,-33.234009t-19.091919,-15.556396t-16.263428,-2.82843q-13.435059,0 -21.920288,7.778198t-8.485352,20.506104q0,32.526917 35.355347,44.547729q-7.778198,9.192383 -28.284302,9.192383q-9.192383,0 -13.434937,-1.414246q-26.870117,-11.31366 -39.598022,-46.669006q-42.426392,-156.270599 -42.426392,-182.433502q0,-19.799011 11.313721,-28.284302t24.748657,-8.485291q26.162964,0 51.618896,23.334534t34.648193,57.275635q2.121338,7.071106 4.242676,7.778198t11.313721,1.414185l2.828369,0q10.606567,0 10.606567,-5.65686q0,-0.707092 -1.414185,-7.778137q-11.313721,-40.305115 -43.840576,-71.417786t-75.6604,-31.112732q-49.497559,0 -74.953369,44.547729q-28.991333,-43.840576 -66.468018,-43.840576l-4.242676,0q-34.648193,0 -49.497437,18.384766t-15.556396,38.890869q0,22.62738 13.435059,36.769531t31.819824,14.142151q30.405518,0 30.405518,-29.698486q0,-14.142151 -8.485229,-24.748718t-16.263428,-14.142151t-9.192383,-3.535522l-2.121338,-0.707153q0,-0.707092 4.242676,-2.828369t11.313599,-4.949768t13.435059,-2.121338q25.455811,0 43.840698,31.819824q6.363892,11.313721 16.263428,48.083252t19.79895,76.367523t11.313721,46.669006q3.535522,19.091919 3.535522,27.577209q0,19.79895 -10.606567,28.284241t-24.041626,8.485291q-28.284302,0 -53.033081,-22.627441t-34.648193,-57.982727q-1.414185,-6.363953 -3.535522,-7.071106t-11.313721,-1.414185l-9.899536,0q-4.242554,4.242615 -4.242554,7.778198z"/>
+      </g>
+	  </svg>
+  </g>
+  <g id="svg_eof"/>
+</svg>

+ 14 - 0
main/inc/lib/javascript/svgedit/extensions/polygon-icons.svg

@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+	<g id="tool_polygon">
+     <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-1 -2 25 24">
+     	<defs>
+		<linearGradient y2="1" x2="1" y1="0.10156" x1="0.36328" id="svg_i22">
+			<stop stop-opacity="1" stop-color="#ffffff" offset="0"/>
+			<stop stop-opacity="1" stop-color="#666666" offset="1"/>
+		</linearGradient>
+		</defs>
+		<polygon points="6.09251,19.6032 0.577744,10.0516 6.09251,0.5 17.1217,0.5 22.6365,10.0516 17.1217,19.6032" stroke="#000000" fill="url(#svg_i22)"/>	 </svg>
+	</g>
+	
+	<g id="svg_eof"/>
+</svg>

+ 16 - 0
main/inc/lib/javascript/svgedit/extensions/savefile.php

@@ -0,0 +1,16 @@
+<?php
+	// You must first create a file "savefile_config.php" in this extensions directory and do whatever
+	//   checking of user credentials, etc. that you wish; otherwise anyone will be able to post SVG
+	//   files to your server which may cause disk space or possibly security problems
+  require('savefile_config.php');
+  if (!isset($_POST['output_svg'])) {
+		print "You must supply output_svg";
+		exit;
+	}
+	$svg = $_POST['output_svg'];
+	$filename = (isset($_POST['filename']) && !empty($_POST['filename']) ? preg_replace('@[\\\\/:*?"<>|]@u', '_', $_POST['filename']) : 'saved') . '.svg'; // These characters are indicated as prohibited by Windows
+
+	$fh = fopen($filename, 'w') or die("Can't open file");
+	fwrite($fh, $svg);
+	fclose($fh);
+?>

File diff suppressed because it is too large
+ 2 - 0
main/inc/lib/javascript/svgedit/extensions/shapelib/animal.json


File diff suppressed because it is too large
+ 15 - 0
main/inc/lib/javascript/svgedit/extensions/shapelib/arrow.json


+ 9 - 0
main/inc/lib/javascript/svgedit/extensions/shapelib/dialog_balloon.json

@@ -0,0 +1,9 @@
+{"data": {
+	"1": "m0.99786,35.96579l0,0c0,-19.31077 15.28761,-34.96524 34.14583,-34.96524l15.52084,0l0,0l74.50001,0l139.68748,0c9.05606,0 17.74118,3.68382 24.14478,10.24108c6.40356,6.55726 10.00107,15.45081 10.00107,24.72416l0,87.41311l0,0l0,52.44785l0,0c0,19.31078 -15.2876,34.96524 -34.14584,34.96524l-139.68748,0l-97.32507,88.90848l22.82506,-88.90848l-15.52084,0c-18.85822,0 -34.14583,-15.65446 -34.14583,-34.96524l0,0l0,-52.44785l0,0z",
+	"4": "m1,1l49.66667,0l0,0l74.5,0l173.83334,0l0,115.8889l0,0l0,49.66666l0,33.11111l-173.83334,0l-123.68433,97.37498l49.18433,-97.37498l-49.66667,0l0,-33.11111l0,-49.66666l0,0z",
+	"5": "m3.88165,296.34811l58.64952,-105.30074l0,0c-62.13446,-31.76456 -79.86445,-91.6022 -40.96117,-138.24044c38.90255,-46.63797 121.70818,-64.81269 191.29914,-41.98796c69.59094,22.8246 103.19446,79.17835 77.63046,130.19172c-25.56265,51.01335 -101.92546,79.99094 -176.41714,66.94487l-110.20081,88.39255z",
+	"6": "m4.33333,266.6662c0,-1.854 2.23757,-3.35571 5,-3.35571c2.76243,0 5,1.50171 5,3.35571c0,1.85394 -2.23757,3.35565 -5,3.35565c-2.76243,0 -5,-1.50171 -5,-3.35565zm10.25,-24.11072c0,-4.6351 5.59392,-8.38943 12.50001,-8.38943c6.90608,0 12.5,3.75433 12.5,8.38943c0,4.63489 -5.59392,8.38928 -12.5,8.38928c-6.90609,0 -12.50001,-3.75433 -12.50001,-8.38928zm23.75001,-36.55524c0,-12.81482 19.46685,-23.19473 43.50002,-23.19473c24.0331,0 43.49996,10.37991 43.49996,23.19473c0,12.81473 -19.46686,23.19455 -43.49996,23.19455c-24.03317,0 -43.50002,-10.37982 -43.50002,-23.19455zm-37.33334,-104.99994c0,-55.2486 66.67956,-100 149,-100c82.32047,0 149,44.7514 149,100c0,55.24866 -66.67953,100 -149,100c-82.32044,0 -149,-44.75134 -149,-100z",
+	"scream": "m299.67374,132.67729l-35.72574,1.97192l-9.55817,48.04506l-31.60561,-11.61551l-45.83566,36.86661l-17.45096,-21.51509l-146.98414,92.00807l81.6677,-102.60858l-67.83573,-13.33963l21.22697,-19.84731l-46.57336,-36.42733l33.47025,-8.80944l-10.52427,-47.94958l35.08694,5.02536l28.86619,-44.2482l25.5638,17.26465l59.09183,-26.49832l7.92432,24.02253l70.55626,-0.33542l-12.23108,23.15343l59.61954,25.93398l-28.50317,14.93327l29.75409,43.96953z",
+	"thought": "m12,1c-6.094,0 -11,4.906 -11,11l0,147c0,6.09399 4.906,11 11,11l49.15625,0c-2.03143,2.32526 -3.15625,4.84886 -3.15625,7.5c0,11.32597 20.36188,20.5 45.5,20.5c25.13812,0 45.5,-9.17403 45.5,-20.5c0,-2.65114 -1.12482,-5.17474 -3.15625,-7.5l142.15625,0c6.09399,0 11,-4.90601 11,-11l0,-147c0,-6.094 -4.90601,-11 -11,-11l-276,0zm54,199c-13.81215,0 -25,5.37016 -25,12c0,6.62984 11.18785,12 25,12c13.81216,0 25,-5.37016 25,-12c0,-6.62984 -11.18784,-12 -25,-12zm-25,30c-7.73481,0 -14,4.02762 -14,9c0,4.97238 6.26519,9 14,9c7.73481,0 14,-4.02762 14,-9c0,-4.97238 -6.26519,-9 -14,-9zm-24,22c-4.97238,0 -9,2.23756 -9,5c0,2.76242 4.02762,5 9,5c4.97238,0 9,-2.23758 9,-5c0,-2.76244 -4.02762,-5 -9,-5z"
+	}
+}

+ 20 - 0
main/inc/lib/javascript/svgedit/extensions/shapelib/electronics.json

@@ -0,0 +1,20 @@
+{"data": {
+	"capacitor": "m292.103577,149.999374l-117.073944,-0.445328m-167.175035,0.445328l116.628588,0m0.44532,-72.035179l11.364601,0l0,144.640358l-11.364601,0l0,-144.640358zm38.244209,-0.569977l11.364594,0l0,144.640297l-11.364594,0l0,-144.640297zm-162.171733,68.98156l6.905184,0l0,6.905212l-6.905184,0l0,-6.905212zm291.101741,0.325241l6.905182,0l0,6.905212l-6.905182,0l0,-6.905212z",
+	"diode": "m180.228439,90.39769l21.70816,0l0,117.211075l-21.70816,0l0,-117.211075zm23.345947,59.602753l88.556381,0m-284.3409,-1.995804l85.541058,0l0,-65.011185l87.251961,66.722031l-87.250778,67.291931l0,-68.720001m-92.331572,-3.917542l6.811423,0l0,6.811447l-6.811423,0l0,-6.811447zm291.20439,2.03891l6.811401,0l0,6.811462l-6.811401,0l0,-6.811462z",
+	"gate_and": "m7.454795,178.082489l67.605378,0m-67.605378,-54.850876l67.605393,0.000015m-0.155602,-30.065033l0,113.750015c194.70015,10.208389 199.234482,-124.687454 0,-113.750015zm217.618942,56.662766l-70.312149,0m-221.397258,-29.817062l6.68369,0l0,6.683685l-6.68369,0l0,-6.683685zm-0.314375,54.532364l6.68369,0l0,6.683685l-6.68369,0l0,-6.683685zm291.95109,-27.976547l6.683685,0l0,6.683685l-6.683685,0l0,-6.683685z",
+	"gate_inverter": "m292.351624,149.998962l-70.506393,0m-0.189026,0a19.883057,19.883057 0 1 1-39.766113,0a19.883057,19.883057 0 1 139.766113,0zm-213.972072,2.405243l69.321826,0l0,-61.05407l101.250404,58.4571l-101.19664,58.840652l0,-56.176727m-76.061115,-3.699677l6.780182,0l0,6.779526l-6.780182,0l0,-6.779526zm291.428455,-2.135864l6.780518,0l0,6.780548l-6.780518,0l0,-6.780548z",
+	"gate_nand": "m8.042537,173.038879l60.699101,-0.672531m-60.699101,-49.879471l61.371335,0m-0.785973,-24.868042l0,104.835098c179.441437,9.408417 183.619827,-114.915443 0,-104.835098zm223.921448,50.643158l-64.591507,0m0.637238,0a11.937837,11.937837 0 1 1-23.87648,0a11.937837,11.937837 0 1 123.87648,0zm-227.445681,-29.373505l6.739111,0l0,6.739143l-6.739111,0l0,-6.739143zm-0.150617,50.613495l6.73911,0l0,6.739151l-6.73911,0l0,-6.739151zm291.47287,-24.654327l6.739105,0l0,6.739151l-6.739105,0l0,-6.739151z",
+	"gate_nor": "m292.610077,150.214462l-69.483139,-0.215668m0.147217,0.215668a12.942393,12.942393 0 1 1-25.884689,0a12.942393,12.942393 0 1 125.884689,0zm-215.590108,29.264374l63.620397,0m-63.620397,-54.805801l65.561368,-0.431335m-20.75433,-33.139984c129.343479,0 143.580387,58.405624 143.580387,58.405624l-0.347778,0c-18.514755,69.097885 -143.580379,58.057999 -143.580379,58.057999c59.7962,-58.405655 0.347775,-116.463623 0.347775,-116.463623zm-51.490408,30.117874l6.644974,0l0,6.645012l-6.644974,0l0,-6.645012zm0.003831,54.852463l6.644983,0l0,6.64502l-6.644983,0l0,-6.64502zm291.530706,-29.571609l6.644989,0l0,6.64502l-6.644989,0l0,-6.64502z",
+	"gate_or": "m7.681484,183.57515l71.7616,0m-71.7616,-60.67144l73.093784,-0.000015m-23.092442,-37.784157c143.186604,0 158.947315,64.65654 158.947315,64.65654l75.817307,0l-76.202316,0c-20.49614,76.493118 -158.94717,64.271667 -158.94717,64.271667c66.195942,-64.656525 0.385136,-128.928207 0.385136,-128.928207zm-56.684011,33.939781l6.677925,0l0,6.677956l-6.677925,0l0,-6.677956zm291.510831,27.410866l6.677948,0l0,6.677948l-6.677948,0l0,-6.677948zm-291.404498,33.607208l6.677927,0l0,6.677917l-6.677927,0l0,-6.677917z",
+	"gate_xor": "m80.450493,91.498093c129.22271,0 143.446312,58.351089 143.446312,58.351089l68.423569,0l-68.770889,0c-18.497391,69.033295 -143.446304,58.003708 -143.446304,58.003708c59.740372,-58.351089 0.347511,-116.354797 0.347511,-116.354797zm-22.576313,4.515259c43.415966,54.530457 0,108.018921 0,108.018921m-50.015199,-26.867355l63.560987,0m-63.560987,-54.7547l63.560987,0m-70.418914,-3.722206l6.82584,0l0,6.825867l-6.82584,0l0,-6.825867zm0.057968,54.832268l6.825839,0l0,6.825867l-6.825839,0l0,-6.825867zm291.170364,-27.096024l6.825836,0l0,6.825867l-6.825836,0l0,-6.825867z",
+	"inductor": "m7.783882,182.663147l59.679306,0c0,0 -30.829735,-67.744125 15.054253,-68.81945c42.462807,-0.995041 37.635605,69.357201 24.194321,69.357201c-13.441284,0 -12.903625,-68.81955 22.043701,-68.81955c34.947357,0 40.323868,68.819366 20.968399,68.819366c-19.355423,0 -11.828323,-68.819366 22.58139,-68.819366c34.409683,0 41.399155,68.81955 19.893112,68.81955c-21.506073,0 -9.67775,-68.81955 24.19429,-68.81955c33.87207,0 29.570831,68.819366 18.280151,68.819366c-11.290665,0 57.528732,-0.537659 57.528732,-0.537659m-291.202282,-3.571106l6.772959,0l0,6.772995l-6.772959,0l0,-6.772995zm291.221844,0.301132l6.772949,0l0,6.772995l-6.772949,0l0,-6.772995z",
+	"junction_1": "m0.99971,146.64024l6.71786,0l0,6.7179l-6.71786,0l0,-6.7179zm7.44043,3.36145l283.11684,0m0.72388,-3.35979l6.71786,0l0,6.7179l-6.71786,0l0,-6.7179zm-145.6413,152.35712l0,-6.71786l6.7179,0l0,6.71786l-6.7179,0zm3.36145,-7.44043l0,-283.11688m-3.35944,-0.72348l0,-6.71786l6.71793,0l0,6.71786l-6.71793,0z",
+	"junction_2": "m0.99971,146.64024l6.71786,0l0,6.7179l-6.71786,0l0,-6.7179zm7.44043,3.36145l121.77922,0c0,-29.3896 38.77921,-31.3896 38.77921,0l122.55841,0m0.72391,-3.35979l6.71783,0l0,6.7179l-6.71783,0l0,-6.7179zm-145.6413,152.35712l0,-6.71786l6.7179,0l0,6.71786l-6.7179,0zm3.36145,-7.44043l0,-283.11688m-3.35945,-0.72348l0,-6.71786l6.71794,0l0,6.71786l-6.71794,0z",
+	"junction_3": "m143.58945,150.00009c0,-3.49425 2.83032,-6.32455 6.32455,-6.32455c3.49423,0 6.32455,2.83031 6.32455,6.32455c0,3.49423 -2.83032,6.32455 -6.32455,6.32455c-3.49423,0 -6.32455,-2.83032 -6.32455,-6.32455zm-142.59006,-3.35985l6.71783,0l0,6.7179l-6.71783,0l0,-6.7179zm7.44043,3.36145l283.11682,0m0.72394,-3.35979l6.71783,0l0,6.7179l-6.71783,0l0,-6.7179zm-145.6413,152.35712l0,-6.71786l6.7179,0l0,6.71786l-6.7179,0zm3.36145,-7.44043l0,-283.11688m-3.35947,-0.72348l0,-6.71786l6.71796,0l0,6.71786l-6.71796,0z",
+	"junction_tee": "m149.914,143.67554zm-148.91461,2.96471l6.71783,0l0,6.7179l-6.71783,0l0,-6.7179zm7.44043,3.36145l283.11682,0m0.72394,-3.35979l6.71783,0l0,6.7179l-6.71783,0l0,-6.7179zm-141.61324,2.91669l-0.66661,-141.11688m-3.35947,-0.72348l0,-6.71786l6.71796,0l0,6.71786l-6.71796,0z",
+	"resistor": "m7.868202,151.620193l82.343018,0l11.393402,-32.392784l18.643684,62.356071l20.71521,-63.165901l18.12587,62.356071l19.679459,-61.546242l19.679443,61.951149l10.875488,-30.368195l82.860886,0m-291.18655,-2.813812l6.844604,0l0,6.844635l-6.844604,0l0,-6.844635zm291.194058,-0.465622l6.844604,0l0,6.844635l-6.844604,0l0,-6.844635z",
+	"source_AC_h": "m7.841724,149.837311l67.250737,0m149.928139,0.389923l67.250793,0m-67.653702,-0.227753a74.615135,74.615135 0 1 1-149.230286,0a74.615135,74.615135 0 1 1149.230286,0zm-126.528297,-1.996506c49.250984,-78.535637 61.230949,87.853104 103.826454,2.662094m-200.917796,-4.522659l6.717863,0l0,6.717896l-6.717863,0l0,-6.717896zm291.36706,0.642181l6.717865,0l0,6.717896l-6.717865,0l0,-6.717896z",
+	"source_DC": "m221.862747,94.98175l0,31.813873m-21.510544,-15.906944l43.020996,0m48.613678,39.407722l-121.593582,0m-162.447085,0l115.809275,0m1.040596,-37.757935l7.284134,0l0,75.963058l-7.284134,0l0,-75.963058zm37.461227,-41.623596l7.284134,0l0,158.169647l-7.284134,0l0,-158.169647zm-161.255614,75.613235l6.954941,0l0,6.954971l-6.954941,0l0,-6.954971zm291.012953,0.175003l6.954956,0l0,6.954971l-6.954956,0l0,-6.954971z",
+	"speaker": "m21.35352,187l77,0m-83.70878,3.11937l0,-6.71786l6.71793,0l0,6.71786l-6.71793,0zm6.70878,-76.11937l77,0m-83.70878,3.11937l0,-6.71786l6.71793,0l0,6.71786l-6.71793,0zm155.70878,-32.61937l115,-83l0,296.5l-115,-82.5l0,-131zm-70.99999,0l70.99999,0l0,131l-70.99999,0l0,-131z"
+	}
+}

+ 25 - 0
main/inc/lib/javascript/svgedit/extensions/shapelib/flowchart.json

@@ -0,0 +1,25 @@
+{"data": {
+	"manual_input": "m1,103.64394l298,-30.9037l0,154.51852l-298,0z",
+	"callout_left_right": "m1,150.0006l58.10869,-58.1087l0,29.05434l46.81141,0l0,-87.16304l87.1598,0l0,87.16304l46.8114,0l0,-29.05434l58.1087,58.1087l-58.1087,58.10869l0,-29.05435l-46.8114,0l0,87.16306l-87.1598,0l0,-87.16306l-46.81141,0l0,29.05435l-58.10869,-58.10869z",
+	"card": "m1,60.5l59.5,-59.5l238.5,0l0,298l-298,0l0,-238.5z",
+	"collate": "m0,1l299,0l-149.5,149l149.5,149l-299.00031,0l149.50031,-149l-149.5,-149z",
+	"connector_offpage": "m0.99775,0.99775l297.99984,0l0,238.39982l-149.00002,59.60002l-148.99999,-59.60002l0.00017,-238.39982z",
+	"data_stored": "m50.83397,0.99813l249.16667,0c-27.52219,0 -49.83333,66.78392 -49.83333,149.16604c0,82.38213 22.31114,149.16603 49.83333,149.16603l-249.16667,0l0,0c-27.52219,0 -49.83333,-66.78391 -49.83333,-149.16603c0,-82.38212 22.31114,-149.16604 49.83333,-149.16604z",
+	"data": "m1.00038,249.33351l59.60001,-198.66668l238.40001,0l-59.60001,198.66668z",
+	"decision": "m0.99837,149.99953l148.79352,-102.86476l148.79387,102.86476l-148.79387,102.86476l-148.79352,-102.86476z",
+	"delay": "m1,1l149,0l0,0c82.29044,0 149,66.70957 149,149c0,82.29044 -66.70956,149 -149,149l-149,0z",
+	"display": "m1,149.99924l49.66672,-97.42307l198.66612,0c27.43034,0 49.66716,43.61774 49.66716,97.42307c0,53.80476 -22.23682,97.42308 -49.66716,97.42308l-198.66612,0l-49.66672,-97.42308z",
+	"document_multiple": "m1.00054,45.02563l253.99998,0l0,206.43799c-126.99997,0 -126.99997,78.65668 -253.99998,33.96539zm21.49946,-240.92902l0,-19.5l255,0l0,207l-22.5,1m-210.5,-207l0,-25l255,0l0,207l-21.5,0",
+	"document": "m1.00064,1.00098l298,0l0,242.19891c-149,0 -149,92.28223 -298,39.84915z",
+	"filter1": "m75.5,150l74.5,-149l74.5,149l-74.5,149l-74.5,-149zm0,0l149,0",
+	"or_junction": "m0.99865,149.9991l0,0c0,-82.29043 66.70957,-149 149.00001,-149l0,0c39.51724,0 77.41597,15.69817 105.3589,43.64109c27.94292,27.94292 43.64107,65.84166 43.64107,105.35891l0,0c0,82.29041 -66.70956,148.99998 -148.99997,148.99998l0,0c-82.29044,0 -149.00001,-66.70958 -149.00001,-148.99998zm149.00001,-149l0,297.99998m-149.00001,-148.99998l297.99998,0",
+	"preparation": "m1.00063,150.00006l59.58485,-82.24058l178.75446,0l59.58505,82.24058l-59.58505,82.24086l-178.75446,0l-59.58485,-82.24086z",
+	"process": "m1,51.87891l298,0l0,196.24391l-298,0zm37.25,-196.24391l0,196.24391m223.5,-196.24391l0,196.24391",
+	"punched_tape": "m1.00047,30.80047l0,0c0,16.45808 33.35479,29.8 74.50001,29.8c41.1452,0 74.49998,-13.34192 74.49998,-29.8l0,0c0,-16.45809 33.3548,-29.8 74.50002,-29.8c41.14522,0 74.49998,13.34192 74.49998,29.8l0,238.4c0,-16.45808 -33.35477,-29.80002 -74.49998,-29.80002c-41.14522,0 -74.50002,13.34193 -74.50002,29.80002c0,16.45807 -33.35478,29.79999 -74.49998,29.79999c-41.14522,0 -74.50001,-13.34192 -74.50001,-29.79999z",
+	"sequential_data_storage": "m150,299l0,0c-82.29043,0 -149,-66.70955 -149,-149l0,0c0,-82.29043 66.70957,-149 149,-149l0,0c39.51726,0 77.41599,15.69817 105.3589,43.64108c27.94292,27.94293 43.6411,65.84165 43.6411,105.35892l0,0c0,39.51726 -15.69818,77.41599 -43.6411,105.3589l43.6411,0l0,43.6411z",
+	"sort": "m-0.0038,150.00102l299.00334,0m-299.00334,-0.00002l149.50209,-150.00059l149.50131,150.00059l-149.50131,150.00018l-149.50209,-150.00018z",
+	"storage_internal": "m1,1l297.99997,0l0,297.99997l-297.99997,0zm37.25,-297.99997l0,297.99997m-37.25,-260.74997l297.99997,0",
+	"terminal": "m48.94167,99.12235l202.11729,0l0,0c26.47794,0 47.9425,22.7794 47.9425,50.8792c0,28.09979 -21.46457,50.87918 -47.9425,50.87918l-202.11729,0l0,0c-26.47791,0 -47.9425,-22.77939 -47.9425,-50.87918c0,-28.09981 21.46459,-50.8792 47.9425,-50.8792z",
+	"wave": "m1,37.20809c99.33355,-125.42461 198.66708,125.4246 298.00061,0l0,225.76426c-99.33353,125.42462 -198.66706,-125.42459 -298.00061,0z"
+	}
+}

+ 13 - 0
main/inc/lib/javascript/svgedit/extensions/shapelib/game.json

@@ -0,0 +1,13 @@
+{"data": {
+	"cards_clubs": "m107.57338,275.50809c15.10838,-15.77673 27.93053,-34.56763 33.34637,-55.90254c-16.19595,12.31328 -31.05006,32.11845 -53.64258,31.36813c-17.05595,0.97891 -37.37346,0.99548 -49.37947,-13.26945c-26.83,-21.5751 -34.03729,-64.69673 -12.00568,-92.15404c15.07669,-19.82526 41.4039,-28.23172 65.56467,-25.25816c15.22319,-6.45935 -2.97749,-22.81502 -4.80785,-33.02267c-11.33012,-37.02704 15.36169,-81.44029 54.60988,-85.70572c28.15103,-4.0415 55.67099,14.18231 69.44571,37.83293c7.4856,16.54877 3.58533,35.33045 1.83887,52.49866c-5.88113,8.62766 -20.94342,29.50022 0.55099,27.85616c21.2518,-0.33633 43.69397,5.90277 57.70761,22.8026c20.49747,22.76067 22.37766,60.37286 1.7551,83.63007c-10.90869,14.16582 -27.2782,25.50356 -45.80551,24.87234c-18.13391,1.83067 -37.77023,-2.10338 -50.62924,-15.92061c-5.48438,-3.84309 -18.92297,-18.36311 -18.91833,-15.17883c13.43222,27.98354 28.62112,57.04413 55.49167,74.38477c9.60062,7.71954 -14.62323,2.41226 -20.4874,3.98563c-35.53012,0.0314 -71.06009,0.06342 -106.59021,0.09497c7.31842,-7.63818 14.6373,-15.27603 21.9554,-22.91422z",
+	"cards_diamonds": "m34.92883,153.9321c25.56111,-56.62673 71.64644,-104.95768 110.85236,-152.92286c45.60773,30.78102 85.01025,98.49872 119.29071,145.66264c-30.57587,54.55344 -74.58923,104.23671 -114.23947,153.1615c-42.74368,-44.7616 -79.29648,-95.90262 -115.90359,-145.90128z",
+	"cards_hearts": "m106.76112,245.09012c-77.74644,-57.80281 -105.54389,-94.36783 -105.76917,-139.13003c-0.20544,-40.80623 34.10907,-80.19025 69.67002,-79.96313c17.75755,0.11364 55.84863,15.13257 69.33681,27.33919c6.79614,6.1504 10.01512,5.54391 25.146,-4.73779c41.17987,-27.98239 81.39243,-28.56973 107.43585,-1.56907c41.62292,43.15273 34.04501,94.68497 -21.78392,148.13782c-29.68187,28.41864 -94.50056,78.8349 -101.35565,78.8349c-2.08591,0 -21.29187,-13.01038 -42.67994,-28.9119z",
+	"cards_spades": "m92.84135,287.13989c18.3756,-17.73279 31.81261,-40.18849 43.07161,-62.94162c6.87787,-9.075 0.36623,-17.01425 -9.00183,-9.3188c-24.07579,16.07495 -56.84848,21.58751 -82.91551,6.92194c-29.46779,-15.23779 -42.75618,-51.47162 -36.07021,-83.04361c4.23415,-31.99545 27.52112,-57.07481 52.80524,-75.08997c29.04437,-20.7771 60.40868,-38.61331 86.95355,-62.67224c11.08365,0.22219 19.42508,17.61496 31.35349,22.21747c31.67316,23.59131 69.20874,40.95643 94.15042,72.50237c12.60098,17.9752 19.78281,40.10946 20.58459,61.98948c-3.83926,29.67093 -21.5314,60.96272 -52.04169,69.41241c-26.37521,7.98038 -53.51129,-2.14038 -76.49545,-15.01619c-2.80743,-0.60251 -13.10471,-8.7151 -9.02362,-2.41039c13.74066,28.19803 28.79581,56.19804 50.59952,79.09325c1.28156,2.89285 11.33243,9.75613 5.98334,9.64709c-44.76935,0 -89.53856,0 -134.30794,0c4.78471,-3.7637 9.5696,-7.5275 14.35449,-11.2912z",
+	"chess_bishop": "m61.92021,296.91153c0.43627,-9.82327 20.22808,-4.98053 9.33225,-14.55078c3.63447,-11.80536 14.91982,-19.66748 21.741,-29.79436c5.22913,-8.62125 17.00826,-19.01086 11.31252,-29.67047c-10.46021,-5.58662 -7.49181,-18.00824 5.30239,-15.28014c8.2272,-7.58801 8.79371,-20.26302 11.57766,-30.59467c2.52694,-12.36574 4.07327,-24.95554 3.66998,-37.5896c-14.99698,0.03661 -30.27584,-0.68196 -44.73978,-4.74928c2.8019,-11.20453 20.86148,-8.79659 28.26084,-15.67982c-6.87532,-6.18329 13.89957,-5.56496 8.46355,-15.34472c-0.37302,-11.20033 -9.19685,-14.44135 -16.26585,-22.60765c-9.44371,-12.76132 5.36173,-25.51221 13.58463,-34.13964c9.16566,-9.37672 19.64847,-19.36716 22.45389,-32.62941c-3.77451,-3.10155 -12.3967,-7.54239 -3.59866,-11.78228c12.01596,-2.03703 24.83499,-2.28521 36.61118,1.05654c7.81644,7.61585 -11.93045,8.03119 -3.43417,17.63365c10.07373,12.07176 -3.50795,18.30174 -11.52704,25.27969c-4.66763,5.89621 -18.59915,13.67189 -16.20224,19.78346c13.63968,-0.47554 21.5871,-13.67976 31.31615,-21.49704c10.67101,-13.68708 20.99446,8.43092 27.81822,15.94714c8.40642,11.39094 2.60674,26.70086 -10.05556,31.59287c-6.28001,6.46729 -10.44972,24.88914 4.57674,22.14657c8.72636,3.17196 -8.52979,3.51371 1.37608,6.446c6.58298,2.52787 32.25821,8.30554 18.1142,16.0547c-12.00471,2.71368 -24.40523,2.6017 -36.6002,1.50584c-0.75204,18.52477 1.89484,36.97644 7.35446,54.6958c1.35513,5.04123 2.71027,10.08244 4.06541,15.12361c6.52129,-0.129 19.98573,-1.55484 13.76321,9.18311c-13.08994,7.21928 -5.0789,22.41203 2.17738,31.05447c8.99446,11.37192 22.40833,22.01788 22.98288,37.58719c3.59734,2.14404 15.56946,8.03415 12.10645,12.64545c-57.30759,-0.2937 -114.6481,0.84897 -171.9265,-1.25046l-1.98901,-0.18246l-1.62205,-0.39337l0,0z",
+	"chess_king": "m75.6993,294.60599c-8.08068,-9.43317 12.65705,-9.68567 9.39906,-20.14252c5.95673,-13.85672 21.44485,-22.24414 23.72572,-37.99019c-6.28166,-4.37628 -9.89445,-14.96013 2.0899,-13.70361c8.78859,-6.61539 7.6902,-20.15297 10.33321,-30.0876c2.47588,-16.03471 3.03656,-32.26408 4.10001,-48.43053c-10.16894,-0.78108 -20.58942,-0.23701 -30.49778,-2.84268c0.28501,-10.75136 20.44619,-6.62604 21.3638,-16.77121c14.65907,-0.2649 0.81196,-22.15992 -5.15776,-27.38371c-7.64118,-8.81222 -12.7306,-22.72323 -6.92168,-33.60618c8.30865,-5.52043 27.09519,-2.1601 26.24604,-16.66769c-5.65058,-3.22095 -12.82484,-1.17552 -19.15805,-1.74514c0.38948,-6.7649 0.77895,-13.52979 1.16843,-20.29469c8.37558,-0.64424 16.75118,-1.28853 25.12676,-1.93283c-4.25133,-4.41846 -10.61392,-7.6702 -12.20608,-13.92034c5.41558,-8.90246 18.46751,-8.1166 27.81776,-8.03243c9.09207,-0.62713 25.66919,5.43749 13.27614,15.20592c-1.00447,2.39887 -10.78024,8.36352 -4.73895,7.71326c7.73515,0 15.47028,0 23.20541,0c-0.02223,6.6133 -0.20001,13.29232 1.35312,19.76423c-5.90448,4.39723 -25.05112,-3.75612 -19.59946,9.81149c6.13853,5.67249 15.53992,5.52279 23.32581,8.02098c4.54138,0.45371 8.15405,1.63713 6.5175,6.94557c0.85359,9.85596 -1.63307,19.77049 -8.40776,27.25217c-4.79567,7.80693 -15.56667,17.58031 -12.3781,26.61691c6.03265,-0.98199 10.87871,2.97905 6.06032,7.43356c4.94479,3.66121 22.35728,2.82278 18.59119,11.98875c-8.82205,2.92029 -18.29916,1.70366 -27.45192,2.00166c2.43703,25.01987 5.80666,50.04211 11.43709,74.54305c3.28979,5.43672 16.35808,9.61523 5.63309,16.70296c-4.00256,13.19919 8.78183,23.08223 16.81097,31.55379c8.82797,6.61176 4.54482,19.91519 17.0338,22.03693c9.83562,9.52774 -13.5036,9.27408 -19.49568,9.29486c-39.66827,0.42773 -79.37933,1.02615 -119.03208,-0.25211c-3.24605,-0.40967 -7.25645,-0.31595 -9.56982,-3.08264z",
+	"chess_knight": "m100.17753,299.2356c-10.0382,0.34137 -24.72987,-4.84531 -14.46609,-16.41525c11.17445,-4.40472 -1.98608,-19.00409 9.21265,-25.88123c8.98889,-12.79953 21.20518,-24.48807 24.89179,-40.11865c-0.57252,-10.60066 -13.22608,-16.87427 -7.18922,-28.60765c-5.92265,-18.77635 -4.55389,-40.38806 6.25748,-57.26643c9.18032,-15.67659 20.32635,-32.28713 19.15084,-51.25797c-11.5139,4.80804 -23.70148,9.0206 -36.37307,6.83708c-11.91311,-1.1064 -22.59742,8.54017 -34.74928,3.29494c-12.31807,-2.55921 -19.64501,-19.02957 -10.4606,-28.65753c10.03679,-8.57325 24.78339,-8.84916 34.35549,-18.41713c12.62932,-10.46186 24.31081,-24.61204 41.71716,-26.46155c7.69322,-1.76131 10.99294,-9.49197 15.25148,-15.2854c3.53894,9.18849 9.69408,17.31353 18.95801,21.387c18.83824,10.9118 23.5276,33.98066 30.47462,52.94444c5.13654,14.85179 9.41592,30.35814 18.01733,43.6171c0.09145,6.36343 -9.56343,9.05308 -3.04225,16.51302c3.39153,20.2325 3.53752,40.95071 3.23686,61.41966c-5.72005,10.01691 -10.93028,21.19722 -3.29993,32.69295c5.09689,14.05096 17.7905,23.26645 24.03563,36.52565c3.53024,6.8656 -6.88226,16.83319 6.09091,15.10654c11.84755,6.2681 2.28101,21.56821 -9.50232,17.11713c-44.15834,1.12289 -88.41394,2.24417 -132.5675,0.9133z",
+	"chess_pawn": "m76.17518,297.98557c-10.50418,1.59836 -25.59558,-8.37918 -12.29734,-17.44669c11.25366,-5.8967 0.45475,-21.25174 12.35514,-28.71019c12.10069,-16.52 24.98341,-33.40712 31.01369,-53.22789c-0.84142,-9.49573 -19.64921,-25.21422 -0.595,-28.29408c15.7114,1.82648 9.96503,-21.69583 15.39529,-31.88779c3.26528,-15.46995 5.63882,-31.19783 5.05293,-47.04268c-10.94164,-0.30554 -22.10724,0.96841 -32.83411,-1.63306c-6.84238,-8.98132 15.45903,-13.45317 19.13895,-21.55999c7.72121,-11.65172 -11.3031,-24.52544 -3.15941,-38.29919c5.20168,-20.85055 29.26575,-34.36854 49.62741,-26.73076c21.08499,5.46792 36.67119,30.37529 26.64961,51.09357c-0.80009,3.99703 -7.062,9.17959 -4.97066,12.36269c9.12987,6.33601 19.70087,11.85771 25.48528,21.64108c-8.18987,5.93826 -21.89375,1.4159 -32.1122,4.23674c-9.29645,8.24593 -0.11353,25.10609 0.36266,36.41936c2.90009,12.9261 5.46037,25.96617 8.78381,38.77452c6.98657,2.72525 21.33679,5.88095 13.4649,17.20207c-11.22217,9.11032 -5.7289,23.62137 0.60231,33.84465c7.87996,15.78793 21.40668,27.84862 29.69345,43.27188c3.07736,7.12057 -7.92374,19.7316 5.78708,16.78259c14.86404,3.15744 5.96938,23.76761 -7.3875,18.8981c-49.21407,1.73288 -98.52922,2.43631 -147.74446,0.51953l-2.31185,-0.21448l0,0z",
+	"chess_queen": "m59.54884,298.46313c-11.18457,2.51251 -19.80814,-14.30008 -5.94004,-16.129c12.20336,0.23074 -3.0349,-11.94995 7.98012,-16.05304c12.67021,-12.36537 25.23749,-25.26018 33.3575,-41.17609c-4.09126,-5.42482 -10.84344,-10.60782 -9.96117,-18.03085c7.964,-2.71161 19.82806,-0.87375 20.29981,-13.44502c7.24239,-22.68985 9.1741,-46.67986 10.76167,-70.3136c-7.99255,-6.33596 -24.45116,-0.33371 -35.62089,-3.56097c-16.94488,-4.5746 6.31873,-13.33291 13.78075,-12.6113c6.70493,0.01006 16.63324,-4.12222 5.41084,-7.2804c9.46686,-0.43687 23.08297,-12.44518 7.51486,-16.77373c11.54188,-8.28655 2.64816,-26.31929 -2.17102,-36.68976c-7.265,-12.52285 -19.21146,-21.59242 -32.71435,-26.42871c-2.18616,-12.77 18.63421,-8.99565 27.07909,-9.65835c12.78728,0.48775 25.82639,-0.15282 36.96732,-7.11507c15.05278,-6.96464 27.8495,4.65901 41.61934,7.11611c13.92807,0.89699 28.41634,-2.50577 41.97807,1.44028c4.20209,2.98911 11.18788,7.71034 2.77457,9.97613c-16.06789,8.94404 -31.07338,22.15693 -35.10127,40.92605c-6.0766,10.44077 4.6955,19.50048 -5.15381,26.98807c-0.10249,8.80961 22.85634,10.04067 10.00395,14.37878c8.80815,4.77542 27.69864,1.76332 29.62625,12.3696c-7.99612,6.2903 -19.2092,3.80788 -28.79007,4.39512c-3.2489,1.10706 -11.41316,-2.70125 -10.17032,2.89742c-0.6366,25.08775 5.87923,49.75521 12.1806,73.83221c0.00804,11.79608 29.09497,5.10777 12.92737,18.49597c-11.94247,10.28146 5.56685,24.68452 11.63272,33.82986c8.25099,10.03221 22.89711,15.11021 21.67468,29.8362c8.40468,0.60507 18.40166,13.69095 6.78131,16.95151c-62.8902,1.31946 -125.82766,2.22778 -188.72791,1.83258z",
+	"chess_rock": "m70.40736,299.11804c-15.60727,2.87628 -15.90823,-19.81082 -1.8931,-20.53482c-4.2011,-9.73361 -0.98556,-21.67557 5.22356,-30.68398c8.90442,-15.05035 22.29623,-30.00999 19.52936,-48.8515c-0.95786,-9.8022 -13.10349,-27.37677 5.72565,-24.85997c5.09087,-9.77498 2.13017,-24.16621 5.9483,-35.39389c3.33424,-21.28385 10.75552,-43.9948 2.86147,-65.09612c-4.97705,-11.23243 -17.62387,-18.62589 -16.26645,-32.35733c-1.42947,-13.39034 -0.95647,-26.88279 0.60455,-40.23392c6.07738,0.50975 12.84039,-1.27954 18.38155,1.475c-0.04153,12.96106 12.26991,10.08973 10.7935,-0.92486c3.23881,-1.28251 8.60017,-0.18413 12.6562,-0.55014c18.03256,0 36.06522,0 54.09778,0c-2.06311,7.51434 3.5195,17.19948 10.5887,8.35272c-1.93379,-11.75267 14.25911,-7.86334 10.37854,2.24694c0.22855,13.39515 1.87041,27.25403 -1.89201,40.29753c-6.12787,5.2086 -6.22449,15.45995 -13.90137,21.60233c-6.16908,11.51656 -3.45045,25.43306 -2.7644,37.9428c2.61279,18.51363 6.92676,36.79671 8.00221,55.52328c-0.76923,10.18126 20.18948,7.18474 11.15244,19.71645c-9.87662,8.41151 -4.0954,22.61668 -0.18413,32.66171c7.71916,17.36203 23.99019,32.95758 21.5343,53.32025c-1.94743,8.89606 14.16618,5.88821 9.97758,17.34372c-0.12151,14.11871 -21.10172,5.04239 -30.39526,8.00793c-46.7146,0.56656 -93.44374,1.44144 -140.159,0.99585z"
+	}
+}

+ 9 - 0
main/inc/lib/javascript/svgedit/extensions/shapelib/math.json

@@ -0,0 +1,9 @@
+{"data": {
+	"divide": "m150,0.99785l0,0c25.17819,0 45.58916,20.41097 45.58916,45.58916c0,25.17821 -20.41096,45.58916 -45.58916,45.58916c-25.17822,0 -45.58916,-20.41093 -45.58916,-45.58916c0,-25.1782 20.41093,-45.58916 45.58916,-45.58916zm0,296.25203c-25.17822,0 -45.58916,-20.41095 -45.58916,-45.58917c0,-25.17819 20.41093,-45.58916 45.58916,-45.58916c25.17819,0 45.58916,20.41096 45.58916,45.58916c0,25.17822 -20.41096,45.58917 -45.58916,45.58917zm-134.06754,-193.71518l268.13507,0l0,91.17833l-268.13507,0z",
+	"equal": "m0.99915,31.03476l297.3767,0l0,95.17349l-297.3767,0zm0,47.58677l297.3767,0l0,95.17349l-297.3767,0z",
+	"minus": "m0.99887,102.39503l297.49445,0l0,95.2112l-297.49445,0z",
+	"not_equal": "m40.81188,62.2131l103.7978,0l22.27972,-61.2131l65.67503,23.90375l-13.5795,37.30935l40.20317,0l0,69.88993l-65.64099,0l-12.71893,34.94495l78.35992,0l0,69.88991l-103.79779,0l-22.27972,61.21309l-65.67503,-23.90378l13.57949,-37.30933l-40.20319,0l0,-69.88991l65.64102,0l12.71894,-34.94498l-78.35995,0z",
+	"times": "m1.00089,73.36786l72.36697,-72.36697l76.87431,76.87368l76.87431,-76.87368l72.36765,72.36697l-76.87433,76.87431l76.87433,76.87431l-72.36765,72.36765l-76.87431,-76.87433l-76.87431,76.87433l-72.36697,-72.36765l76.87368,-76.87431l-76.87368,-76.87431z",
+	"plus": "m1.00211,102.40185l101.39974,0l0,-101.39975l95.45412,0l0,101.39975l101.3997,0l0,95.45412l-101.3997,0l0,101.3997l-95.45412,0l0,-101.3997l-101.39974,0z"
+	}
+}

File diff suppressed because it is too large
+ 4 - 0
main/inc/lib/javascript/svgedit/extensions/shapelib/misc.json


File diff suppressed because it is too large
+ 10 - 0
main/inc/lib/javascript/svgedit/extensions/shapelib/music.json


File diff suppressed because it is too large
+ 1 - 0
main/inc/lib/javascript/svgedit/extensions/shapelib/object.json


+ 12 - 0
main/inc/lib/javascript/svgedit/extensions/shapelib/raphael.txt

@@ -0,0 +1,12 @@
+All Raphaël icons were retrieved from here:
+http://raphaeljs.com/icons/
+
+And fall under the MIT license:
+
+Copyright © 2008 Dmitry Baranovskiy
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+The software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software.

File diff suppressed because it is too large
+ 8 - 0
main/inc/lib/javascript/svgedit/extensions/shapelib/raphael_1.json


File diff suppressed because it is too large
+ 4 - 0
main/inc/lib/javascript/svgedit/extensions/shapelib/raphael_2.json


File diff suppressed because it is too large
+ 2 - 0
main/inc/lib/javascript/svgedit/extensions/shapelib/symbol.json


+ 14 - 0
main/inc/lib/javascript/svgedit/extensions/star-icons.svg

@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+	<g id="tool_star">
+     <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-1 -1 26 26">
+     	<defs>
+		<linearGradient y2="1" x2="1" y1="0.10156" x1="0.36328" id="svg_i22">
+			<stop stop-opacity="1" stop-color="#ffffff" offset="0"/>
+			<stop stop-opacity="1" stop-color="#666666" offset="1"/>
+		</linearGradient>
+		</defs>
+		<polygon points="12.4062,1.55891 15.0578,9.39339 23.3283,9.49422 16.6965,14.4371 19.1564,22.3338 12.4062,17.5543 5.65596,22.3338 8.11588,14.4371 1.48406,9.49422 9.75455,9.39339" stroke="#000000" fill="url(#svg_i22)"/>	 </svg>
+	</g>
+	
+	<g id="svg_eof"/>
+</svg>

+ 23 - 0
main/inc/lib/javascript/svgedit/extensions/webappfind-icon.svg

@@ -0,0 +1,23 @@
+<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
+<!--
+Copied from /editor/images/svg_edit_icons.svg#save but with id renamed, first stop-color changed to red, and SVG namespaces moved to root
+-->
+<g id="webappfind_save">
+	<svg viewBox="0 0 24 24">
+	 <defs>
+	  <linearGradient y2="0" x2="1" y1="0" x1="0" id="svg_41">
+	   <stop stop-opacity="1" stop-color="red" offset="0"/>
+	   <stop stop-opacity="1" stop-color="#d6d6d6" offset="1"/>
+	  </linearGradient>
+	  <linearGradient y2="0.875" x2="0.21484" y1="0.00391" x1="0.04297" id="svg_46">
+	   <stop stop-opacity="1" stop-color="#81bbf4" offset="0"/>
+	   <stop stop-opacity="1" stop-color="#376eb7" offset="1"/>
+	  </linearGradient>
+	 </defs>
+	   <path stroke="#202020" fill="#e0e0e0" id="svg_21" d="m1.51669,22.3458l21.13245,-0.10111l0,-6.06673l-2.62892,-9.80789l-16.27907,0.10111l-2.32558,9.20121l0.10111,6.67341z"/>
+	   <rect stroke="#efefef" fill="url(#svg_41)" id="svg_32" height="4.75108" width="19.21031" y="16.58227" x="2.42667"/>
+	   <path stroke="#ffffff" fill="#c0c0c0" id="svg_42" d="m4.55005,11.12235l0.70779,-2.83114l13.04348,0l0.70779,3.13448c-0.70779,2.52781 -4.04479,3.84227 -7.17897,3.84227c-2.72977,0 -6.37007,-1.41557 -7.28008,-4.1456z"/>
+	  <path stroke="#285582" fill="url(#svg_46)" id="svg_45" d="m7.14286,9.74903l5.21236,5.79151l5.50193,-5.88803l-2.50965,-0.09653l0,-2.79923c0,-2.3166 -2.3166,-5.59846 -6.56371,-5.59846c-4.05405,0 -6.27413,3.37838 -6.56371,6.75676c0.48263,-1.5444 2.7027,-4.53668 4.44015,-4.44015c2.12355,-0.09653 2.79923,1.64093 2.79923,3.37838l0.09653,2.79923l-2.41313,0.09653z"/>
+	</svg>
+</g>
+</svg>

+ 613 - 0
main/inc/lib/javascript/svgedit/history.js

@@ -0,0 +1,613 @@
+/*globals svgedit*/
+/*jslint vars: true, eqeq: true, continue: true, forin: true*/
+/**
+ * Package: svedit.history
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2010 Jeff Schiller
+ */
+
+// Dependencies:
+// 1) jQuery
+// 2) svgtransformlist.js
+// 3) svgutils.js
+
+(function() {'use strict';
+
+if (!svgedit.history) {
+	svgedit.history = {};
+}
+
+// Group: Undo/Redo history management
+svgedit.history.HistoryEventTypes = {
+	BEFORE_APPLY: 'before_apply',
+	AFTER_APPLY: 'after_apply',
+	BEFORE_UNAPPLY: 'before_unapply',
+	AFTER_UNAPPLY: 'after_unapply'
+};
+
+var removedElements = {};
+
+/**
+ * An interface that all command objects must implement.
+ * @typedef svgedit.history.HistoryCommand
+ * @type {object}
+ *   void apply(svgedit.history.HistoryEventHandler);
+ *   void unapply(svgedit.history.HistoryEventHandler);
+ *   Element[] elements();
+ *   String getText();
+ *
+ *   static String type();
+ * }
+ *
+ * Interface: svgedit.history.HistoryEventHandler
+ * An interface for objects that will handle history events.
+ *
+ * interface svgedit.history.HistoryEventHandler {
+ *   void handleHistoryEvent(eventType, command);
+ * }
+ *
+ * eventType is a string conforming to one of the HistoryEvent types.
+ * command is an object fulfilling the HistoryCommand interface.
+ */
+
+/**
+ * @class svgedit.history.MoveElementCommand
+ * @implements svgedit.history.HistoryCommand
+ * History command for an element that had its DOM position changed
+ * @param {Element} elem - The DOM element that was moved
+ * @param {Element} oldNextSibling - The element's next sibling before it was moved
+ * @param {Element} oldParent - The element's parent before it was moved
+ * @param {string} [text] - An optional string visible to user related to this change
+*/
+svgedit.history.MoveElementCommand = function(elem, oldNextSibling, oldParent, text) {
+	this.elem = elem;
+	this.text = text ? ("Move " + elem.tagName + " to " + text) : ("Move " + elem.tagName);
+	this.oldNextSibling = oldNextSibling;
+	this.oldParent = oldParent;
+	this.newNextSibling = elem.nextSibling;
+	this.newParent = elem.parentNode;
+};
+svgedit.history.MoveElementCommand.type = function() { return 'svgedit.history.MoveElementCommand'; };
+svgedit.history.MoveElementCommand.prototype.type = svgedit.history.MoveElementCommand.type;
+
+svgedit.history.MoveElementCommand.prototype.getText = function() {
+	return this.text;
+};
+
+/**
+ * Re-positions the element
+ * @param {handleHistoryEvent: function}
+*/
+svgedit.history.MoveElementCommand.prototype.apply = function(handler) {
+	// TODO(codedread): Refactor this common event code into a base HistoryCommand class.
+	if (handler) {
+		handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.BEFORE_APPLY, this);
+	}
+
+	this.elem = this.newParent.insertBefore(this.elem, this.newNextSibling);
+
+	if (handler) {
+		handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.AFTER_APPLY, this);
+	}
+};
+
+/**
+ * Positions the element back to its original location
+ * @param {handleHistoryEvent: function}
+*/
+svgedit.history.MoveElementCommand.prototype.unapply = function(handler) {
+	if (handler) {
+		handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.BEFORE_UNAPPLY, this);
+	}
+
+	this.elem = this.oldParent.insertBefore(this.elem, this.oldNextSibling);
+
+	if (handler) {
+		handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.AFTER_UNAPPLY, this);
+	}
+};
+
+// Function: svgedit.history.MoveElementCommand.elements
+// Returns array with element associated with this command
+svgedit.history.MoveElementCommand.prototype.elements = function() {
+	return [this.elem];
+};
+
+
+// Class: svgedit.history.InsertElementCommand
+// implements svgedit.history.HistoryCommand
+// History command for an element that was added to the DOM
+//
+// Parameters:
+// elem - The newly added DOM element
+// text - An optional string visible to user related to this change
+svgedit.history.InsertElementCommand = function(elem, text) {
+	this.elem = elem;
+	this.text = text || ("Create " + elem.tagName);
+	this.parent = elem.parentNode;
+	this.nextSibling = this.elem.nextSibling;
+};
+svgedit.history.InsertElementCommand.type = function() { return 'svgedit.history.InsertElementCommand'; };
+svgedit.history.InsertElementCommand.prototype.type = svgedit.history.InsertElementCommand.type;
+
+// Function: svgedit.history.InsertElementCommand.getText
+svgedit.history.InsertElementCommand.prototype.getText = function() {
+	return this.text;
+};
+
+// Function: svgedit.history.InsertElementCommand.apply
+// Re-Inserts the new element
+svgedit.history.InsertElementCommand.prototype.apply = function(handler) {
+	if (handler) {
+		handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.BEFORE_APPLY, this);
+	}
+
+	this.elem = this.parent.insertBefore(this.elem, this.nextSibling);
+
+	if (handler) {
+		handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.AFTER_APPLY, this);
+	}
+};
+
+// Function: svgedit.history.InsertElementCommand.unapply
+// Removes the element
+svgedit.history.InsertElementCommand.prototype.unapply = function(handler) {
+	if (handler) {
+		handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.BEFORE_UNAPPLY, this);
+	}
+
+	this.parent = this.elem.parentNode;
+	this.elem = this.elem.parentNode.removeChild(this.elem);
+
+	if (handler) {
+		handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.AFTER_UNAPPLY, this);
+	}
+};
+
+// Function: svgedit.history.InsertElementCommand.elements
+// Returns array with element associated with this command
+svgedit.history.InsertElementCommand.prototype.elements = function() {
+	return [this.elem];
+};
+
+
+// Class: svgedit.history.RemoveElementCommand
+// implements svgedit.history.HistoryCommand
+// History command for an element removed from the DOM
+//
+// Parameters:
+// elem - The removed DOM element
+// oldNextSibling - the DOM element's nextSibling when it was in the DOM
+// oldParent - The DOM element's parent
+// text - An optional string visible to user related to this change
+svgedit.history.RemoveElementCommand = function(elem, oldNextSibling, oldParent, text) {
+	this.elem = elem;
+	this.text = text || ("Delete " + elem.tagName);
+	this.nextSibling = oldNextSibling;
+	this.parent = oldParent;
+
+	// special hack for webkit: remove this element's entry in the svgTransformLists map
+	svgedit.transformlist.removeElementFromListMap(elem);
+};
+svgedit.history.RemoveElementCommand.type = function() { return 'svgedit.history.RemoveElementCommand'; };
+svgedit.history.RemoveElementCommand.prototype.type = svgedit.history.RemoveElementCommand.type;
+
+// Function: svgedit.history.RemoveElementCommand.getText
+svgedit.history.RemoveElementCommand.prototype.getText = function() {
+	return this.text;
+};
+
+// Function: RemoveElementCommand.apply
+// Re-removes the new element
+svgedit.history.RemoveElementCommand.prototype.apply = function(handler) {
+	if (handler) {
+		handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.BEFORE_APPLY, this);
+	}
+
+	svgedit.transformlist.removeElementFromListMap(this.elem);
+	this.parent = this.elem.parentNode;
+	this.elem = this.parent.removeChild(this.elem);
+
+	if (handler) {
+		handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.AFTER_APPLY, this);
+	}
+};
+
+// Function: RemoveElementCommand.unapply
+// Re-adds the new element
+svgedit.history.RemoveElementCommand.prototype.unapply = function(handler) {
+	if (handler) {
+		handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.BEFORE_UNAPPLY, this);
+	}
+
+	svgedit.transformlist.removeElementFromListMap(this.elem);
+	if (this.nextSibling == null) {
+		if (window.console) {
+            console.log('Error: reference element was lost');
+        }
+	}
+	this.parent.insertBefore(this.elem, this.nextSibling);
+
+
+	if (handler) {
+		handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.AFTER_UNAPPLY, this);
+	}
+};
+
+// Function: RemoveElementCommand.elements
+// Returns array with element associated with this command
+svgedit.history.RemoveElementCommand.prototype.elements = function() {
+	return [this.elem];
+};
+
+
+// Class: svgedit.history.ChangeElementCommand
+// implements svgedit.history.HistoryCommand
+// History command to make a change to an element.
+// Usually an attribute change, but can also be textcontent.
+//
+// Parameters:
+// elem - The DOM element that was changed
+// attrs - An object with the attributes to be changed and the values they had *before* the change
+// text - An optional string visible to user related to this change
+svgedit.history.ChangeElementCommand = function(elem, attrs, text) {
+	this.elem = elem;
+	this.text = text ? ("Change " + elem.tagName + " " + text) : ("Change " + elem.tagName);
+	this.newValues = {};
+	this.oldValues = attrs;
+	var attr;
+	for (attr in attrs) {
+		if (attr == "#text") {this.newValues[attr] = elem.textContent;}
+		else if (attr == "#href") {this.newValues[attr] = svgedit.utilities.getHref(elem);}
+		else {this.newValues[attr] = elem.getAttribute(attr);}
+	}
+};
+svgedit.history.ChangeElementCommand.type = function() { return 'svgedit.history.ChangeElementCommand'; };
+svgedit.history.ChangeElementCommand.prototype.type = svgedit.history.ChangeElementCommand.type;
+
+// Function: svgedit.history.ChangeElementCommand.getText
+svgedit.history.ChangeElementCommand.prototype.getText = function() {
+	return this.text;
+};
+
+// Function: svgedit.history.ChangeElementCommand.apply
+// Performs the stored change action
+svgedit.history.ChangeElementCommand.prototype.apply = function(handler) {
+	if (handler) {
+		handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.BEFORE_APPLY, this);
+	}
+
+	var bChangedTransform = false;
+	var attr;
+	for (attr in this.newValues ) {
+		if (this.newValues[attr]) {
+			if (attr == "#text") {this.elem.textContent = this.newValues[attr];}
+			else if (attr == "#href") {svgedit.utilities.setHref(this.elem, this.newValues[attr]);}
+			else {this.elem.setAttribute(attr, this.newValues[attr]);}
+		}
+		else {
+			if (attr == "#text") {
+				this.elem.textContent = "";
+			}
+			else {
+				this.elem.setAttribute(attr, "");
+				this.elem.removeAttribute(attr);
+			}
+		}
+
+		if (attr == "transform") { bChangedTransform = true; }
+	}
+
+	// relocate rotational transform, if necessary
+	if (!bChangedTransform) {
+		var angle = svgedit.utilities.getRotationAngle(this.elem);
+		if (angle) {
+			// TODO: These instances of elem either need to be declared as global
+			//				(which would not be good for conflicts) or declare/use this.elem
+			var bbox = elem.getBBox();
+			var cx = bbox.x + bbox.width/2,
+				cy = bbox.y + bbox.height/2;
+			var rotate = ["rotate(", angle, " ", cx, ",", cy, ")"].join('');
+			if (rotate != elem.getAttribute("transform")) {
+				elem.setAttribute("transform", rotate);
+			}
+		}
+	}
+
+	if (handler) {
+		handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.AFTER_APPLY, this);
+	}
+
+	return true;
+};
+
+// Function: svgedit.history.ChangeElementCommand.unapply
+// Reverses the stored change action
+svgedit.history.ChangeElementCommand.prototype.unapply = function(handler) {
+	if (handler) {
+		handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.BEFORE_UNAPPLY, this);
+	}
+
+	var bChangedTransform = false;
+	var attr;
+	for (attr in this.oldValues ) {
+		if (this.oldValues[attr]) {
+			if (attr == "#text") {this.elem.textContent = this.oldValues[attr];}
+			else if (attr == "#href") {svgedit.utilities.setHref(this.elem, this.oldValues[attr]);}
+			else {
+				this.elem.setAttribute(attr, this.oldValues[attr]);
+			}
+		}
+		else {
+			if (attr == "#text") {
+				this.elem.textContent = "";
+			}
+			else {this.elem.removeAttribute(attr);}
+		}
+		if (attr == "transform") { bChangedTransform = true; }
+	}
+	// relocate rotational transform, if necessary
+	if (!bChangedTransform) {
+		var angle = svgedit.utilities.getRotationAngle(this.elem);
+		if (angle) {
+			var bbox = elem.getBBox();
+			var cx = bbox.x + bbox.width/2,
+				cy = bbox.y + bbox.height/2;
+			var rotate = ["rotate(", angle, " ", cx, ",", cy, ")"].join('');
+			if (rotate != elem.getAttribute("transform")) {
+				elem.setAttribute("transform", rotate);
+			}
+		}
+	}
+
+	// Remove transformlist to prevent confusion that causes bugs like 575.
+	svgedit.transformlist.removeElementFromListMap(this.elem);
+
+	if (handler) {
+		handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.AFTER_UNAPPLY, this);
+	}
+
+	return true;
+};
+
+// Function: ChangeElementCommand.elements
+// Returns array with element associated with this command
+svgedit.history.ChangeElementCommand.prototype.elements = function() {
+	return [this.elem];
+};
+
+
+// TODO: create a 'typing' command object that tracks changes in text
+// if a new Typing command is created and the top command on the stack is also a Typing
+// and they both affect the same element, then collapse the two commands into one
+
+
+// Class: svgedit.history.BatchCommand
+// implements svgedit.history.HistoryCommand
+// History command that can contain/execute multiple other commands
+//
+// Parameters:
+// text - An optional string visible to user related to this change
+svgedit.history.BatchCommand = function(text) {
+	this.text = text || "Batch Command";
+	this.stack = [];
+};
+svgedit.history.BatchCommand.type = function() { return 'svgedit.history.BatchCommand'; };
+svgedit.history.BatchCommand.prototype.type = svgedit.history.BatchCommand.type;
+
+// Function: svgedit.history.BatchCommand.getText
+svgedit.history.BatchCommand.prototype.getText = function() {
+	return this.text;
+};
+
+// Function: svgedit.history.BatchCommand.apply
+// Runs "apply" on all subcommands
+svgedit.history.BatchCommand.prototype.apply = function(handler) {
+	if (handler) {
+		handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.BEFORE_APPLY, this);
+	}
+
+	var i,
+		len = this.stack.length;
+	for (i = 0; i < len; ++i) {
+		this.stack[i].apply(handler);
+	}
+
+	if (handler) {
+		handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.AFTER_APPLY, this);
+	}
+};
+
+// Function: svgedit.history.BatchCommand.unapply
+// Runs "unapply" on all subcommands
+svgedit.history.BatchCommand.prototype.unapply = function(handler) {
+	if (handler) {
+		handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.BEFORE_UNAPPLY, this);
+	}
+
+	var i;
+	for (i = this.stack.length-1; i >= 0; i--) {
+		this.stack[i].unapply(handler);
+	}
+
+	if (handler) {
+		handler.handleHistoryEvent(svgedit.history.HistoryEventTypes.AFTER_UNAPPLY, this);
+	}
+};
+
+// Function: svgedit.history.BatchCommand.elements
+// Iterate through all our subcommands and returns all the elements we are changing
+svgedit.history.BatchCommand.prototype.elements = function() {
+	var elems = [];
+	var cmd = this.stack.length;
+	while (cmd--) {
+		var thisElems = this.stack[cmd].elements();
+		var elem = thisElems.length;
+		while (elem--) {
+			if (elems.indexOf(thisElems[elem]) == -1) {elems.push(thisElems[elem]);}
+		}
+	}
+	return elems;
+};
+
+// Function: svgedit.history.BatchCommand.addSubCommand
+// Adds a given command to the history stack
+//
+// Parameters:
+// cmd - The undo command object to add
+svgedit.history.BatchCommand.prototype.addSubCommand = function(cmd) {
+	this.stack.push(cmd);
+};
+
+// Function: svgedit.history.BatchCommand.isEmpty
+// Returns a boolean indicating whether or not the batch command is empty
+svgedit.history.BatchCommand.prototype.isEmpty = function() {
+	return this.stack.length === 0;
+};
+
+
+// Class: svgedit.history.UndoManager
+// Parameters:
+// historyEventHandler - an object that conforms to the HistoryEventHandler interface
+// (see above)
+svgedit.history.UndoManager = function(historyEventHandler) {
+	this.handler_ = historyEventHandler || null;
+	this.undoStackPointer = 0;
+	this.undoStack = [];
+
+	// this is the stack that stores the original values, the elements and
+	// the attribute name for begin/finish
+	this.undoChangeStackPointer = -1;
+	this.undoableChangeStack = [];
+};
+
+// Function: svgedit.history.UndoManager.resetUndoStack
+// Resets the undo stack, effectively clearing the undo/redo history
+svgedit.history.UndoManager.prototype.resetUndoStack = function() {
+	this.undoStack = [];
+	this.undoStackPointer = 0;
+};
+
+// Function: svgedit.history.UndoManager.getUndoStackSize
+// Returns:
+// Integer with the current size of the undo history stack
+svgedit.history.UndoManager.prototype.getUndoStackSize = function() {
+	return this.undoStackPointer;
+};
+
+// Function: svgedit.history.UndoManager.getRedoStackSize
+// Returns:
+// Integer with the current size of the redo history stack
+svgedit.history.UndoManager.prototype.getRedoStackSize = function() {
+	return this.undoStack.length - this.undoStackPointer;
+};
+
+// Function: svgedit.history.UndoManager.getNextUndoCommandText
+// Returns:
+// String associated with the next undo command
+svgedit.history.UndoManager.prototype.getNextUndoCommandText = function() {
+	return this.undoStackPointer > 0 ? this.undoStack[this.undoStackPointer-1].getText() : "";
+};
+
+// Function: svgedit.history.UndoManager.getNextRedoCommandText
+// Returns:
+// String associated with the next redo command
+svgedit.history.UndoManager.prototype.getNextRedoCommandText = function() {
+	return this.undoStackPointer < this.undoStack.length ? this.undoStack[this.undoStackPointer].getText() : "";
+};
+
+// Function: svgedit.history.UndoManager.undo
+// Performs an undo step
+svgedit.history.UndoManager.prototype.undo = function() {
+	if (this.undoStackPointer > 0) {
+		var cmd = this.undoStack[--this.undoStackPointer];
+		cmd.unapply(this.handler_);
+	}
+};
+
+// Function: svgedit.history.UndoManager.redo
+// Performs a redo step
+svgedit.history.UndoManager.prototype.redo = function() {
+	if (this.undoStackPointer < this.undoStack.length && this.undoStack.length > 0) {
+		var cmd = this.undoStack[this.undoStackPointer++];
+		cmd.apply(this.handler_);
+	}
+};
+
+// Function: svgedit.history.UndoManager.addCommandToHistory
+// Adds a command object to the undo history stack
+//
+// Parameters:
+// cmd - The command object to add
+svgedit.history.UndoManager.prototype.addCommandToHistory = function(cmd) {
+	// FIXME: we MUST compress consecutive text changes to the same element
+	// (right now each keystroke is saved as a separate command that includes the
+	// entire text contents of the text element)
+	// TODO: consider limiting the history that we store here (need to do some slicing)
+
+	// if our stack pointer is not at the end, then we have to remove
+	// all commands after the pointer and insert the new command
+	if (this.undoStackPointer < this.undoStack.length && this.undoStack.length > 0) {
+		this.undoStack = this.undoStack.splice(0, this.undoStackPointer);
+	}
+	this.undoStack.push(cmd);
+	this.undoStackPointer = this.undoStack.length;
+};
+
+
+// Function: svgedit.history.UndoManager.beginUndoableChange
+// This function tells the canvas to remember the old values of the 
+// attrName attribute for each element sent in.  The elements and values 
+// are stored on a stack, so the next call to finishUndoableChange() will 
+// pop the elements and old values off the stack, gets the current values
+// from the DOM and uses all of these to construct the undo-able command.
+//
+// Parameters:
+// attrName - The name of the attribute being changed
+// elems - Array of DOM elements being changed
+svgedit.history.UndoManager.prototype.beginUndoableChange = function(attrName, elems) {
+	var p = ++this.undoChangeStackPointer;
+	var i = elems.length;
+	var oldValues = new Array(i), elements = new Array(i);
+	while (i--) {
+		var elem = elems[i];
+		if (elem == null) {continue;}
+		elements[i] = elem;
+		oldValues[i] = elem.getAttribute(attrName);
+	}
+	this.undoableChangeStack[p] = {
+		'attrName': attrName,
+		'oldValues': oldValues,
+		'elements': elements
+	};
+};
+
+// Function: svgedit.history.UndoManager.finishUndoableChange
+// This function returns a BatchCommand object which summarizes the
+// change since beginUndoableChange was called.  The command can then
+// be added to the command history
+//
+// Returns:
+// Batch command object with resulting changes
+svgedit.history.UndoManager.prototype.finishUndoableChange = function() {
+	var p = this.undoChangeStackPointer--;
+	var changeset = this.undoableChangeStack[p];
+	var i = changeset.elements.length;
+	var attrName = changeset.attrName;
+	var batchCmd = new svgedit.history.BatchCommand("Change " + attrName);
+	while (i--) {
+		var elem = changeset.elements[i];
+		if (elem == null) {continue;}
+		var changes = {};
+		changes[attrName] = changeset.oldValues[i];
+		if (changes[attrName] != elem.getAttribute(attrName)) {
+			batchCmd.addSubCommand(new svgedit.history.ChangeElementCommand(elem, changes, attrName));
+		}
+	}
+	this.undoableChangeStack[p] = null;
+	return batchCmd;
+};
+
+}());

+ 61 - 0
main/inc/lib/javascript/svgedit/images/README.txt

@@ -0,0 +1,61 @@
+filename                        origin
+
+align-bottom.png                http://tango.freedesktop.org/static/cvs/tango-art-libre/22x22/actions/align-vertical-bottom.png
+align-bottom.svg                http://tango.freedesktop.org/static/cvs/tango-art-libre/22x22/actions/align-vertical-bottom.svg
+align-center.png                http://tango.freedesktop.org/static/cvs/tango-art-libre/22x22/actions/align-horizontal-center.png
+align-center.svg                http://tango.freedesktop.org/static/cvs/tango-art-libre/22x22/actions/align-horizontal-center.svg
+align-left.png                  http://tango.freedesktop.org/static/cvs/tango-art-libre/22x22/actions/align-horizontal-left.png
+align-left.svg                  http://tango.freedesktop.org/static/cvs/tango-art-libre/22x22/actions/align-horizontal-left.svg
+align-middle.png                http://tango.freedesktop.org/static/cvs/tango-art-libre/22x22/actions/align-vertical-center.png
+align-middle.svg                http://tango.freedesktop.org/static/cvs/tango-art-libre/22x22/actions/align-vertical-center.svg
+align-right.png                 http://tango.freedesktop.org/static/cvs/tango-art-libre/22x22/actions/align-horizontal-right.png
+align-right.svg                 http://tango.freedesktop.org/static/cvs/tango-art-libre/22x22/actions/align-horizontal-right.svg
+align-top.png                   http://tango.freedesktop.org/static/cvs/tango-art-libre/22x22/actions/align-vertical-top.png
+align-top.svg                   http://tango.freedesktop.org/static/cvs/tango-art-libre/22x22/actions/align-vertical-top.svg
+bold.png
+cancel.png
+circle.png
+clear.png
+clone.png
+copy.png
+cut.png
+delete.png
+document-properties.png
+dropdown.gif
+ellipse.png
+eye.png
+flyouth.png
+flyup.gif
+freehand-circle.png
+freehand-square.png
+go-down.png
+go-up.png
+image.png
+italic.png
+line.png
+logo.png
+logo.svg
+move_bottom.png
+move_top.png
+none.png
+open.png
+paste.png
+path.png
+polygon.png                     http://tango.freedesktop.org/static/cvs/tango-art-libre/22x22/tools/draw-polygon.png
+polygon.svg                     http://tango.freedesktop.org/static/cvs/tango-art-libre/22x22/tools/draw-polygon.svg
+rect.png
+redo.png
+rotate.png
+save.png
+select.png
+sep.png
+shape_group_elements.png
+shape_ungroup.png
+source.png
+square.png
+text.png                        http://tango.freedesktop.org/static/cvs/tango-art-libre/22x22/tools/draw-text.png
+text.svg                        http://tango.freedesktop.org/static/cvs/tango-art-libre/22x22/tools/draw-text.svg
+undo.png
+view-refresh.png
+wave.png
+zoom.png                        http://tango.freedesktop.org/static/cvs/tango-art-libre/22x22/tools/page-magnifier.png

BIN
main/inc/lib/javascript/svgedit/images/align-bottom.png


+ 277 - 0
main/inc/lib/javascript/svgedit/images/align-bottom.svg

@@ -0,0 +1,277 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://web.resource.org/cc/"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="22"
+   height="22"
+   id="svg5741"
+   sodipodi:version="0.32"
+   inkscape:version="0.44+devel"
+   version="1.0"
+   sodipodi:docbase="/home/andreas/project/inkscape/22x22/actions"
+   sodipodi:docname="align-bottom-vertical.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:export-filename="/home/andreas/project/inkscape/22x22/actions/align-bottom-vertical.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90"
+   sodipodi:modified="true">
+  <defs
+     id="defs5743">
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2968"
+       id="linearGradient6938"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-1,0,0,-1,395.9999,981)"
+       x1="187.60938"
+       y1="489.35938"
+       x2="186.93732"
+       y2="489.35938" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2974"
+       id="linearGradient6936"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-1,0,0,-1,395.9999,981)"
+       x1="187.81554"
+       y1="489.54688"
+       x2="187.1716"
+       y2="489.54688" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2986"
+       id="linearGradient6934"
+       gradientUnits="userSpaceOnUse"
+       x1="187.60938"
+       y1="489.35938"
+       x2="186.93732"
+       y2="489.35938" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2980"
+       id="linearGradient6932"
+       gradientUnits="userSpaceOnUse"
+       x1="187.81554"
+       y1="489.54688"
+       x2="187.1716"
+       y2="489.54688" />
+    <linearGradient
+       id="linearGradient2968"
+       inkscape:collect="always">
+      <stop
+         id="stop2970"
+         offset="0"
+         style="stop-color:#ce5c00;stop-opacity:1" />
+      <stop
+         id="stop2972"
+         offset="1"
+         style="stop-color:#ce5c00;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2968"
+       id="linearGradient6930"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-1,0,0,-1,395.9999,981)"
+       x1="187.60938"
+       y1="489.35938"
+       x2="186.93732"
+       y2="489.35938" />
+    <linearGradient
+       id="linearGradient2974"
+       inkscape:collect="always">
+      <stop
+         id="stop2976"
+         offset="0"
+         style="stop-color:#fcaf3e;stop-opacity:1" />
+      <stop
+         id="stop2978"
+         offset="1"
+         style="stop-color:#fcaf3e;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2974"
+       id="linearGradient6928"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-1,0,0,-1,395.9999,981)"
+       x1="187.81554"
+       y1="489.54688"
+       x2="187.1716"
+       y2="489.54688" />
+    <linearGradient
+       id="linearGradient2986"
+       inkscape:collect="always">
+      <stop
+         id="stop2988"
+         offset="0"
+         style="stop-color:#ce5c00;stop-opacity:1" />
+      <stop
+         id="stop2990"
+         offset="1"
+         style="stop-color:#ce5c00;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2986"
+       id="linearGradient6926"
+       gradientUnits="userSpaceOnUse"
+       x1="187.60938"
+       y1="489.35938"
+       x2="186.93732"
+       y2="489.35938" />
+    <linearGradient
+       id="linearGradient2980"
+       inkscape:collect="always">
+      <stop
+         id="stop2982"
+         offset="0"
+         style="stop-color:#fcaf3e;stop-opacity:1" />
+      <stop
+         id="stop2984"
+         offset="1"
+         style="stop-color:#fcaf3e;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2980"
+       id="linearGradient6924"
+       gradientUnits="userSpaceOnUse"
+       x1="187.81554"
+       y1="489.54688"
+       x2="187.1716"
+       y2="489.54688" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="22.197802"
+     inkscape:cx="8"
+     inkscape:cy="9.8019802"
+     inkscape:current-layer="g6828"
+     showgrid="false"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     width="22px"
+     height="22px"
+     inkscape:window-width="1078"
+     inkscape:window-height="786"
+     inkscape:window-x="243"
+     inkscape:window-y="71" />
+  <metadata
+     id="metadata5746">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer">
+    <g
+       style="display:inline"
+       id="g6828"
+       transform="translate(30.00011,90.000366)"
+       inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+       inkscape:export-xdpi="90"
+       inkscape:export-ydpi="90">
+      <g
+         style="display:inline"
+         id="g6838"
+         transform="translate(-30.00009,-1.0002798)"
+         inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+         inkscape:export-xdpi="90"
+         inkscape:export-ydpi="90">
+        <rect
+           style="fill:#d3d7cf;fill-opacity:1;stroke:#888a85;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+           id="rect3052"
+           width="12"
+           height="7"
+           x="69.500122"
+           y="12.5"
+           transform="matrix(0,-1,1,0,0,0)"
+           inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+           inkscape:export-xdpi="90"
+           inkscape:export-ydpi="90" />
+        <rect
+           style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+           id="rect3054"
+           width="10"
+           height="5.0000305"
+           x="70.500122"
+           y="13.5"
+           transform="matrix(0,-1,1,0,0,0)"
+           rx="0"
+           ry="0"
+           inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+           inkscape:export-xdpi="90"
+           inkscape:export-ydpi="90" />
+        <g
+           id="g3056"
+           transform="translate(-127,-559)"
+           inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+           inkscape:export-xdpi="90"
+           inkscape:export-ydpi="90">
+          <rect
+             transform="matrix(0,-1,1,0,0,0)"
+             y="129.49626"
+             x="-489.49979"
+             height="7.0035982"
+             width="17.999748"
+             id="rect3058"
+             style="color:#000000;fill:#d3d7cf;fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:1.00024867;stroke-linecap:butt;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" />
+          <rect
+             transform="matrix(0,-1,1,0,0,0)"
+             y="130.50006"
+             x="-488.50009"
+             height="4.9998937"
+             width="15.999757"
+             id="rect3060"
+             style="color:#000000;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00024891;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:2;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline"
+             rx="0"
+             ry="0" />
+        </g>
+        <g
+           id="g3294"
+           transform="translate(-187,-560)"
+           inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+           inkscape:export-xdpi="90"
+           inkscape:export-ydpi="90">
+          <rect
+             y="489.5"
+             x="196.49989"
+             height="1.9999999"
+             width="3.0000916"
+             id="rect3296"
+             style="fill:#fcaf3e;fill-opacity:1;stroke:#ce5c00;stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+          <path
+             style="fill:url(#linearGradient6932);fill-opacity:1;stroke:url(#linearGradient6934);stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dashoffset:0;stroke-opacity:1"
+             d="M 197.49998,491.5 L 186.49989,491.5 L 186.49989,489.5 L 197.49998,489.5"
+             id="path3298"
+             sodipodi:nodetypes="cccc" />
+          <path
+             style="fill:url(#linearGradient6936);fill-opacity:1;stroke:url(#linearGradient6938);stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dashoffset:0;stroke-opacity:1"
+             d="M 198.49989,489.5 L 209.49998,489.5 L 209.49998,491.5 L 198.49989,491.5"
+             id="path3300"
+             sodipodi:nodetypes="cccc" />
+        </g>
+      </g>
+    </g>
+  </g>
+</svg>

BIN
main/inc/lib/javascript/svgedit/images/align-center.png


+ 252 - 0
main/inc/lib/javascript/svgedit/images/align-center.svg

@@ -0,0 +1,252 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://web.resource.org/cc/"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="22"
+   height="22"
+   id="svg10958"
+   sodipodi:version="0.32"
+   inkscape:version="0.44+devel"
+   version="1.0"
+   sodipodi:docbase="/home/andreas/project/inkscape/22x22/actions"
+   sodipodi:docname="align-horisontal-center.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:export-filename="/home/andreas/project/inkscape/22x22/actions/align-horisontal-center.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90"
+   sodipodi:modified="true">
+  <defs
+     id="defs10960">
+    <linearGradient
+       id="linearGradient2968"
+       inkscape:collect="always">
+      <stop
+         id="stop2970"
+         offset="0"
+         style="stop-color:#ce5c00;stop-opacity:1" />
+      <stop
+         id="stop2972"
+         offset="1"
+         style="stop-color:#ce5c00;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2968"
+       id="linearGradient4708"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-395.9999,-981)"
+       x1="187.60938"
+       y1="489.35938"
+       x2="186.93732"
+       y2="489.35938" />
+    <linearGradient
+       id="linearGradient2974"
+       inkscape:collect="always">
+      <stop
+         id="stop2976"
+         offset="0"
+         style="stop-color:#fcaf3e;stop-opacity:1" />
+      <stop
+         id="stop2978"
+         offset="1"
+         style="stop-color:#fcaf3e;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2974"
+       id="linearGradient4706"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-395.9999,-981)"
+       x1="187.81554"
+       y1="489.54688"
+       x2="187.1716"
+       y2="489.54688" />
+    <linearGradient
+       id="linearGradient2986"
+       inkscape:collect="always">
+      <stop
+         id="stop2988"
+         offset="0"
+         style="stop-color:#ce5c00;stop-opacity:1" />
+      <stop
+         id="stop2990"
+         offset="1"
+         style="stop-color:#ce5c00;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2986"
+       id="linearGradient4704"
+       gradientUnits="userSpaceOnUse"
+       x1="187.60938"
+       y1="489.35938"
+       x2="186.93732"
+       y2="489.35938" />
+    <linearGradient
+       id="linearGradient2980"
+       inkscape:collect="always">
+      <stop
+         id="stop2982"
+         offset="0"
+         style="stop-color:#fcaf3e;stop-opacity:1" />
+      <stop
+         id="stop2984"
+         offset="1"
+         style="stop-color:#fcaf3e;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2980"
+       id="linearGradient4702"
+       gradientUnits="userSpaceOnUse"
+       x1="187.81554"
+       y1="489.54688"
+       x2="187.1716"
+       y2="489.54688" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="11.197802"
+     inkscape:cx="16"
+     inkscape:cy="11.460711"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     width="22px"
+     height="22px"
+     inkscape:window-width="797"
+     inkscape:window-height="628"
+     inkscape:window-x="0"
+     inkscape:window-y="47" />
+  <metadata
+     id="metadata10963">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer">
+    <g
+       style="display:inline"
+       id="g4044"
+       transform="matrix(0,-1,1,0,-59.999911,-168.00002)"
+       inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+       inkscape:export-xdpi="90"
+       inkscape:export-ydpi="90">
+      <rect
+         style="fill:#d3d7cf;fill-opacity:1;stroke:#888a85;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline"
+         id="rect3851"
+         width="12"
+         height="7"
+         x="-76.499878"
+         y="-177.5"
+         transform="matrix(0,-1,1,0,0,0)"
+         inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+         inkscape:export-xdpi="90"
+         inkscape:export-ydpi="90" />
+      <g
+         transform="translate(-317,-410)"
+         id="g3853"
+         inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+         inkscape:export-xdpi="90"
+         inkscape:export-ydpi="90"
+         style="display:inline">
+        <rect
+           transform="matrix(0,-1,1,0,0,0)"
+           y="129.49626"
+           x="-489.49979"
+           height="7.0035982"
+           width="17.999748"
+           id="rect3855"
+           style="color:#000000;fill:#d3d7cf;fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:1.00024867;stroke-linecap:butt;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" />
+        <rect
+           transform="matrix(0,-1,1,0,0,0)"
+           y="130.50006"
+           x="-488.50009"
+           height="4.9998937"
+           width="15.999757"
+           id="rect3857"
+           style="color:#000000;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00024891;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:2;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline"
+           rx="0"
+           ry="0" />
+      </g>
+      <rect
+         style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline"
+         id="rect3859"
+         width="10"
+         height="5.0000305"
+         x="-75.499878"
+         y="-176.5"
+         transform="matrix(0,-1,1,0,0,0)"
+         rx="0"
+         ry="0"
+         inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+         inkscape:export-xdpi="90"
+         inkscape:export-ydpi="90" />
+      <g
+         id="g3861"
+         transform="translate(-377,-420)"
+         inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+         inkscape:export-xdpi="90"
+         inkscape:export-ydpi="90"
+         style="display:inline">
+        <rect
+           y="489.5"
+           x="186.49989"
+           height="1.9999999"
+           width="3.0000916"
+           id="rect3863"
+           style="fill:url(#linearGradient4702);fill-opacity:1;stroke:url(#linearGradient4704);stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+        <rect
+           y="489.5"
+           x="191.49989"
+           height="1.9999999"
+           width="3.0000916"
+           id="rect3865"
+           style="fill:#fcaf3e;fill-opacity:1;stroke:#ce5c00;stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+        <rect
+           y="489.5"
+           x="196.49989"
+           height="1.9999999"
+           width="3.0000916"
+           id="rect3867"
+           style="fill:#fcaf3e;fill-opacity:1;stroke:#ce5c00;stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+        <rect
+           y="489.5"
+           x="201.49989"
+           height="1.9999999"
+           width="3.0000916"
+           id="rect3869"
+           style="fill:#fcaf3e;fill-opacity:1;stroke:#ce5c00;stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+        <rect
+           transform="scale(-1,-1)"
+           y="-491.5"
+           x="-209.49998"
+           height="1.9999999"
+           width="3.0000916"
+           id="rect3871"
+           style="fill:url(#linearGradient4706);fill-opacity:1;stroke:url(#linearGradient4708);stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+      </g>
+    </g>
+  </g>
+</svg>

BIN
main/inc/lib/javascript/svgedit/images/align-left.png


+ 235 - 0
main/inc/lib/javascript/svgedit/images/align-left.svg

@@ -0,0 +1,235 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://web.resource.org/cc/"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="22"
+   height="22"
+   id="svg11272"
+   sodipodi:version="0.32"
+   inkscape:version="0.44+devel"
+   version="1.0"
+   sodipodi:docbase="/home/andreas/project/inkscape/22x22/actions"
+   sodipodi:docname="align-horisontal-left.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:export-filename="/home/andreas/project/inkscape/22x22/actions/align-horisontal-left.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90"
+   sodipodi:modified="true">
+  <defs
+     id="defs11274">
+    <linearGradient
+       id="linearGradient2968"
+       inkscape:collect="always">
+      <stop
+         id="stop2970"
+         offset="0"
+         style="stop-color:#ce5c00;stop-opacity:1" />
+      <stop
+         id="stop2972"
+         offset="1"
+         style="stop-color:#ce5c00;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2968"
+       id="linearGradient4716"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-1,0,0,-1,395.9999,981)"
+       x1="187.60938"
+       y1="489.35938"
+       x2="186.93732"
+       y2="489.35938" />
+    <linearGradient
+       id="linearGradient2974"
+       inkscape:collect="always">
+      <stop
+         id="stop2976"
+         offset="0"
+         style="stop-color:#fcaf3e;stop-opacity:1" />
+      <stop
+         id="stop2978"
+         offset="1"
+         style="stop-color:#fcaf3e;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2974"
+       id="linearGradient4714"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-1,0,0,-1,395.9999,981)"
+       x1="187.81554"
+       y1="489.54688"
+       x2="187.1716"
+       y2="489.54688" />
+    <linearGradient
+       id="linearGradient2986"
+       inkscape:collect="always">
+      <stop
+         id="stop2988"
+         offset="0"
+         style="stop-color:#ce5c00;stop-opacity:1" />
+      <stop
+         id="stop2990"
+         offset="1"
+         style="stop-color:#ce5c00;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2986"
+       id="linearGradient4712"
+       gradientUnits="userSpaceOnUse"
+       x1="187.60938"
+       y1="489.35938"
+       x2="186.93732"
+       y2="489.35938" />
+    <linearGradient
+       id="linearGradient2980"
+       inkscape:collect="always">
+      <stop
+         id="stop2982"
+         offset="0"
+         style="stop-color:#fcaf3e;stop-opacity:1" />
+      <stop
+         id="stop2984"
+         offset="1"
+         style="stop-color:#fcaf3e;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2980"
+       id="linearGradient4710"
+       gradientUnits="userSpaceOnUse"
+       x1="187.81554"
+       y1="489.54688"
+       x2="187.1716"
+       y2="489.54688" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="11.197802"
+     inkscape:cx="16"
+     inkscape:cy="14.269093"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     width="22px"
+     height="22px"
+     inkscape:window-width="797"
+     inkscape:window-height="628"
+     inkscape:window-x="0"
+     inkscape:window-y="47" />
+  <metadata
+     id="metadata11277">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer">
+    <g
+       style="display:inline"
+       id="g4065"
+       transform="matrix(0,-1,1,0,8.9287758e-5,51.99998)"
+       inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+       inkscape:export-xdpi="90"
+       inkscape:export-ydpi="90">
+      <g
+         id="g3883"
+         transform="translate(-127,-473)"
+         style="fill:#d3d7cf;stroke:#888a85;display:inline"
+         inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+         inkscape:export-xdpi="90"
+         inkscape:export-ydpi="90">
+        <rect
+           transform="matrix(0,1,1,0,0,0)"
+           y="169.5"
+           x="475.50012"
+           height="7"
+           width="12"
+           id="rect3885"
+           style="fill:#d3d7cf;fill-opacity:1;stroke:#888a85;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+        <rect
+           ry="0"
+           rx="0"
+           transform="matrix(0,1,1,0,0,0)"
+           y="170.5"
+           x="476.50012"
+           height="5.0000305"
+           width="10"
+           id="rect3887"
+           style="opacity:1;fill:#d3d7cf;fill-opacity:1;stroke:#ffffff;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+      </g>
+      <g
+         id="g3889"
+         transform="translate(-97,-469)"
+         inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+         inkscape:export-xdpi="90"
+         inkscape:export-ydpi="90"
+         style="display:inline">
+        <rect
+           transform="matrix(0,-1,1,0,0,0)"
+           y="129.49626"
+           x="-489.49979"
+           height="7.0035982"
+           width="17.999748"
+           id="rect3891"
+           style="color:#000000;fill:#d3d7cf;fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:1.00024867;stroke-linecap:butt;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" />
+        <rect
+           transform="matrix(0,-1,1,0,0,0)"
+           y="130.50006"
+           x="-488.50009"
+           height="4.9998937"
+           width="15.999757"
+           id="rect3893"
+           style="color:#000000;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00024891;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:2;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline"
+           rx="0"
+           ry="0" />
+      </g>
+      <g
+         id="g3903"
+         transform="translate(-157,-488)"
+         inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+         inkscape:export-xdpi="90"
+         inkscape:export-ydpi="90"
+         style="display:inline">
+        <rect
+           y="489.5"
+           x="196.49989"
+           height="1.9999999"
+           width="3.0000916"
+           id="rect3905"
+           style="fill:#fcaf3e;fill-opacity:1;stroke:#ce5c00;stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+        <path
+           style="fill:url(#linearGradient4710);fill-opacity:1;stroke:url(#linearGradient4712);stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dashoffset:0;stroke-opacity:1"
+           d="M 197.49998,491.5 L 186.49989,491.5 L 186.49989,489.5 L 197.49998,489.5"
+           id="path3907"
+           sodipodi:nodetypes="cccc" />
+        <path
+           style="fill:url(#linearGradient4714);fill-opacity:1;stroke:url(#linearGradient4716);stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dashoffset:0;stroke-opacity:1"
+           d="M 198.49989,489.5 L 209.49998,489.5 L 209.49998,491.5 L 198.49989,491.5"
+           id="path3909"
+           sodipodi:nodetypes="cccc" />
+      </g>
+    </g>
+  </g>
+</svg>

BIN
main/inc/lib/javascript/svgedit/images/align-middle.png


+ 250 - 0
main/inc/lib/javascript/svgedit/images/align-middle.svg

@@ -0,0 +1,250 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://web.resource.org/cc/"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="22"
+   height="22"
+   id="svg10625"
+   sodipodi:version="0.32"
+   inkscape:version="0.44+devel"
+   version="1.0"
+   sodipodi:docbase="/home/andreas/project/inkscape/22x22/actions"
+   sodipodi:docname="align-vertical-center.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:export-filename="/home/andreas/project/inkscape/22x22/actions/align-vertical-center.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90"
+   sodipodi:modified="true">
+  <defs
+     id="defs10627">
+    <linearGradient
+       id="linearGradient2968"
+       inkscape:collect="always">
+      <stop
+         id="stop2970"
+         offset="0"
+         style="stop-color:#ce5c00;stop-opacity:1" />
+      <stop
+         id="stop2972"
+         offset="1"
+         style="stop-color:#ce5c00;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2968"
+       id="linearGradient6962"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-395.9999,-981)"
+       x1="187.60938"
+       y1="489.35938"
+       x2="186.93732"
+       y2="489.35938" />
+    <linearGradient
+       id="linearGradient2974"
+       inkscape:collect="always">
+      <stop
+         id="stop2976"
+         offset="0"
+         style="stop-color:#fcaf3e;stop-opacity:1" />
+      <stop
+         id="stop2978"
+         offset="1"
+         style="stop-color:#fcaf3e;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2974"
+       id="linearGradient6960"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-395.9999,-981)"
+       x1="187.81554"
+       y1="489.54688"
+       x2="187.1716"
+       y2="489.54688" />
+    <linearGradient
+       id="linearGradient2986"
+       inkscape:collect="always">
+      <stop
+         id="stop2988"
+         offset="0"
+         style="stop-color:#ce5c00;stop-opacity:1" />
+      <stop
+         id="stop2990"
+         offset="1"
+         style="stop-color:#ce5c00;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2986"
+       id="linearGradient6958"
+       gradientUnits="userSpaceOnUse"
+       x1="187.60938"
+       y1="489.35938"
+       x2="186.93732"
+       y2="489.35938" />
+    <linearGradient
+       id="linearGradient2980"
+       inkscape:collect="always">
+      <stop
+         id="stop2982"
+         offset="0"
+         style="stop-color:#fcaf3e;stop-opacity:1" />
+      <stop
+         id="stop2984"
+         offset="1"
+         style="stop-color:#fcaf3e;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2980"
+       id="linearGradient6956"
+       gradientUnits="userSpaceOnUse"
+       x1="187.81554"
+       y1="489.54688"
+       x2="187.1716"
+       y2="489.54688" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="11.197802"
+     inkscape:cx="16"
+     inkscape:cy="16"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     width="22px"
+     height="22px"
+     inkscape:window-width="797"
+     inkscape:window-height="628"
+     inkscape:window-x="0"
+     inkscape:window-y="47" />
+  <metadata
+     id="metadata10630">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer">
+    <g
+       style="display:inline"
+       id="g6849"
+       transform="translate(-29.999893,91.000089)"
+       inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+       inkscape:export-xdpi="90"
+       inkscape:export-ydpi="90">
+      <rect
+         style="fill:#d3d7cf;fill-opacity:1;stroke:#888a85;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+         id="rect1933"
+         width="12"
+         height="7"
+         x="73.500122"
+         y="42.5"
+         transform="matrix(0,-1,1,0,0,0)"
+         inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+         inkscape:export-xdpi="90"
+         inkscape:export-ydpi="90" />
+      <g
+         transform="translate(-97,-560)"
+         id="g2063"
+         inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+         inkscape:export-xdpi="90"
+         inkscape:export-ydpi="90">
+        <rect
+           transform="matrix(0,-1,1,0,0,0)"
+           y="129.49626"
+           x="-489.49979"
+           height="7.0035982"
+           width="17.999748"
+           id="rect1935"
+           style="color:#000000;fill:#d3d7cf;fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:1.00024867;stroke-linecap:butt;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" />
+        <rect
+           transform="matrix(0,-1,1,0,0,0)"
+           y="130.50006"
+           x="-488.50009"
+           height="4.9998937"
+           width="15.999757"
+           id="rect1937"
+           style="color:#000000;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00024891;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:2;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline"
+           rx="0"
+           ry="0" />
+      </g>
+      <rect
+         style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+         id="rect1939"
+         width="10"
+         height="5.0000305"
+         x="74.500122"
+         y="43.5"
+         transform="matrix(0,-1,1,0,0,0)"
+         rx="0"
+         ry="0"
+         inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+         inkscape:export-xdpi="90"
+         inkscape:export-ydpi="90" />
+      <g
+         id="g2992"
+         transform="translate(-157,-570)"
+         inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+         inkscape:export-xdpi="90"
+         inkscape:export-ydpi="90">
+        <rect
+           y="489.5"
+           x="186.49989"
+           height="1.9999999"
+           width="3.0000916"
+           id="rect2994"
+           style="fill:url(#linearGradient6956);fill-opacity:1;stroke:url(#linearGradient6958);stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+        <rect
+           y="489.5"
+           x="191.49989"
+           height="1.9999999"
+           width="3.0000916"
+           id="rect2996"
+           style="fill:#fcaf3e;fill-opacity:1;stroke:#ce5c00;stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+        <rect
+           y="489.5"
+           x="196.49989"
+           height="1.9999999"
+           width="3.0000916"
+           id="rect2998"
+           style="fill:#fcaf3e;fill-opacity:1;stroke:#ce5c00;stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+        <rect
+           y="489.5"
+           x="201.49989"
+           height="1.9999999"
+           width="3.0000916"
+           id="rect3000"
+           style="fill:#fcaf3e;fill-opacity:1;stroke:#ce5c00;stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+        <rect
+           transform="scale(-1,-1)"
+           y="-491.5"
+           x="-209.49998"
+           height="1.9999999"
+           width="3.0000916"
+           id="rect3002"
+           style="fill:url(#linearGradient6960);fill-opacity:1;stroke:url(#linearGradient6962);stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+      </g>
+    </g>
+  </g>
+</svg>

BIN
main/inc/lib/javascript/svgedit/images/align-right.png


+ 233 - 0
main/inc/lib/javascript/svgedit/images/align-right.svg

@@ -0,0 +1,233 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://web.resource.org/cc/"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="22"
+   height="22"
+   id="svg11187"
+   sodipodi:version="0.32"
+   inkscape:version="0.44+devel"
+   version="1.0"
+   sodipodi:docbase="/home/andreas/project/inkscape/22x22/actions"
+   sodipodi:docname="align-horisontal-right.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   sodipodi:modified="TRUE"
+   inkscape:export-filename="/home/andreas/project/inkscape/22x22/actions/align-horisontal-right.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs11189">
+    <linearGradient
+       id="linearGradient2968"
+       inkscape:collect="always">
+      <stop
+         id="stop2970"
+         offset="0"
+         style="stop-color:#ce5c00;stop-opacity:1" />
+      <stop
+         id="stop2972"
+         offset="1"
+         style="stop-color:#ce5c00;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2968"
+       id="linearGradient4732"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-1,0,0,-1,395.9999,981)"
+       x1="187.60938"
+       y1="489.35938"
+       x2="186.93732"
+       y2="489.35938" />
+    <linearGradient
+       id="linearGradient2974"
+       inkscape:collect="always">
+      <stop
+         id="stop2976"
+         offset="0"
+         style="stop-color:#fcaf3e;stop-opacity:1" />
+      <stop
+         id="stop2978"
+         offset="1"
+         style="stop-color:#fcaf3e;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2974"
+       id="linearGradient4730"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-1,0,0,-1,395.9999,981)"
+       x1="187.81554"
+       y1="489.54688"
+       x2="187.1716"
+       y2="489.54688" />
+    <linearGradient
+       id="linearGradient2986"
+       inkscape:collect="always">
+      <stop
+         id="stop2988"
+         offset="0"
+         style="stop-color:#ce5c00;stop-opacity:1" />
+      <stop
+         id="stop2990"
+         offset="1"
+         style="stop-color:#ce5c00;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2986"
+       id="linearGradient4728"
+       gradientUnits="userSpaceOnUse"
+       x1="187.60938"
+       y1="489.35938"
+       x2="186.93732"
+       y2="489.35938" />
+    <linearGradient
+       id="linearGradient2980"
+       inkscape:collect="always">
+      <stop
+         id="stop2982"
+         offset="0"
+         style="stop-color:#fcaf3e;stop-opacity:1" />
+      <stop
+         id="stop2984"
+         offset="1"
+         style="stop-color:#fcaf3e;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2980"
+       id="linearGradient4726"
+       gradientUnits="userSpaceOnUse"
+       x1="187.81554"
+       y1="489.54688"
+       x2="187.1716"
+       y2="489.54688" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="11.197802"
+     inkscape:cx="16"
+     inkscape:cy="16"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     width="22px"
+     height="22px"
+     inkscape:window-width="797"
+     inkscape:window-height="628"
+     inkscape:window-x="0"
+     inkscape:window-y="47" />
+  <metadata
+     id="metadata11192">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer">
+    <g
+       style="display:inline"
+       id="g4025"
+       transform="matrix(0,-1,1,0,-60.999914,-198.00011)"
+       inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+       inkscape:export-xdpi="90"
+       inkscape:export-ydpi="90">
+      <rect
+         style="fill:#d3d7cf;fill-opacity:1;stroke:#888a85;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline"
+         id="rect3873"
+         width="12"
+         height="7"
+         x="-80.499878"
+         y="-207.5"
+         transform="matrix(0,-1,1,0,0,0)"
+         inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+         inkscape:export-xdpi="90"
+         inkscape:export-ydpi="90" />
+      <rect
+         style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline"
+         id="rect3875"
+         width="10"
+         height="5.0000305"
+         x="-79.499878"
+         y="-206.5"
+         transform="matrix(0,-1,1,0,0,0)"
+         rx="0"
+         ry="0"
+         inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+         inkscape:export-xdpi="90"
+         inkscape:export-ydpi="90" />
+      <g
+         id="g3877"
+         transform="translate(-347,-409)"
+         inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+         inkscape:export-xdpi="90"
+         inkscape:export-ydpi="90"
+         style="display:inline">
+        <rect
+           transform="matrix(0,-1,1,0,0,0)"
+           y="129.49626"
+           x="-489.49979"
+           height="7.0035982"
+           width="17.999748"
+           id="rect3879"
+           style="color:#000000;fill:#d3d7cf;fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:1.00024867;stroke-linecap:butt;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" />
+        <rect
+           transform="matrix(0,-1,1,0,0,0)"
+           y="130.50006"
+           x="-488.50009"
+           height="4.9998937"
+           width="15.999757"
+           id="rect3881"
+           style="color:#000000;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00024891;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:2;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline"
+           rx="0"
+           ry="0" />
+      </g>
+      <g
+         id="g3919"
+         transform="translate(-407,-410)"
+         inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+         inkscape:export-xdpi="90"
+         inkscape:export-ydpi="90"
+         style="display:inline">
+        <rect
+           y="489.5"
+           x="196.49989"
+           height="1.9999999"
+           width="3.0000916"
+           id="rect3921"
+           style="fill:#fcaf3e;fill-opacity:1;stroke:#ce5c00;stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+        <path
+           style="fill:url(#linearGradient4726);fill-opacity:1;stroke:url(#linearGradient4728);stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dashoffset:0;stroke-opacity:1"
+           d="M 197.49998,491.5 L 186.49989,491.5 L 186.49989,489.5 L 197.49998,489.5"
+           id="path3923"
+           sodipodi:nodetypes="cccc" />
+        <path
+           style="fill:url(#linearGradient4730);fill-opacity:1;stroke:url(#linearGradient4732);stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dashoffset:0;stroke-opacity:1"
+           d="M 198.49989,489.5 L 209.49998,489.5 L 209.49998,491.5 L 198.49989,491.5"
+           id="path3925"
+           sodipodi:nodetypes="cccc" />
+      </g>
+    </g>
+  </g>
+</svg>

BIN
main/inc/lib/javascript/svgedit/images/align-top.png


+ 233 - 0
main/inc/lib/javascript/svgedit/images/align-top.svg

@@ -0,0 +1,233 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://web.resource.org/cc/"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="22"
+   height="22"
+   id="svg10699"
+   sodipodi:version="0.32"
+   inkscape:version="0.44+devel"
+   version="1.0"
+   sodipodi:docbase="/home/andreas/project/inkscape/22x22/actions"
+   sodipodi:docname="align-vertical-bottom.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:export-filename="/home/andreas/project/inkscape/22x22/actions/align-vertical-bottom.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90"
+   sodipodi:modified="true">
+  <defs
+     id="defs10701">
+    <linearGradient
+       id="linearGradient2968"
+       inkscape:collect="always">
+      <stop
+         id="stop2970"
+         offset="0"
+         style="stop-color:#ce5c00;stop-opacity:1" />
+      <stop
+         id="stop2972"
+         offset="1"
+         style="stop-color:#ce5c00;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2968"
+       id="linearGradient6954"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-1,0,0,-1,395.9999,981)"
+       x1="187.60938"
+       y1="489.35938"
+       x2="186.93732"
+       y2="489.35938" />
+    <linearGradient
+       id="linearGradient2974"
+       inkscape:collect="always">
+      <stop
+         id="stop2976"
+         offset="0"
+         style="stop-color:#fcaf3e;stop-opacity:1" />
+      <stop
+         id="stop2978"
+         offset="1"
+         style="stop-color:#fcaf3e;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2974"
+       id="linearGradient6952"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-1,0,0,-1,395.9999,981)"
+       x1="187.81554"
+       y1="489.54688"
+       x2="187.1716"
+       y2="489.54688" />
+    <linearGradient
+       id="linearGradient2986"
+       inkscape:collect="always">
+      <stop
+         id="stop2988"
+         offset="0"
+         style="stop-color:#ce5c00;stop-opacity:1" />
+      <stop
+         id="stop2990"
+         offset="1"
+         style="stop-color:#ce5c00;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2986"
+       id="linearGradient6950"
+       gradientUnits="userSpaceOnUse"
+       x1="187.60938"
+       y1="489.35938"
+       x2="186.93732"
+       y2="489.35938" />
+    <linearGradient
+       id="linearGradient2980"
+       inkscape:collect="always">
+      <stop
+         id="stop2982"
+         offset="0"
+         style="stop-color:#fcaf3e;stop-opacity:1" />
+      <stop
+         id="stop2984"
+         offset="1"
+         style="stop-color:#fcaf3e;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2980"
+       id="linearGradient6948"
+       gradientUnits="userSpaceOnUse"
+       x1="187.81554"
+       y1="489.54688"
+       x2="187.1716"
+       y2="489.54688" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="11.197802"
+     inkscape:cx="16"
+     inkscape:cy="16"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     width="22px"
+     height="22px"
+     inkscape:window-width="797"
+     inkscape:window-height="628"
+     inkscape:window-x="0"
+     inkscape:window-y="47" />
+  <metadata
+     id="metadata10704">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer">
+    <g
+       style="display:inline"
+       id="g6862"
+       transform="translate(-59.99998,90)"
+       inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+       inkscape:export-xdpi="90"
+       inkscape:export-ydpi="90">
+      <g
+         id="g3084"
+         transform="translate(-97,-563)"
+         style="fill:#d3d7cf;stroke:#888a85"
+         inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+         inkscape:export-xdpi="90"
+         inkscape:export-ydpi="90">
+        <rect
+           transform="matrix(0,1,1,0,0,0)"
+           y="169.5"
+           x="475.50012"
+           height="7"
+           width="12"
+           id="rect3086"
+           style="fill:#d3d7cf;fill-opacity:1;stroke:#888a85;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+        <rect
+           ry="0"
+           rx="0"
+           transform="matrix(0,1,1,0,0,0)"
+           y="170.5"
+           x="476.50012"
+           height="5.0000305"
+           width="10"
+           id="rect3088"
+           style="opacity:1;fill:#d3d7cf;fill-opacity:1;stroke:#ffffff;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+      </g>
+      <g
+         id="g3090"
+         transform="translate(-67,-559)"
+         inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+         inkscape:export-xdpi="90"
+         inkscape:export-ydpi="90">
+        <rect
+           transform="matrix(0,-1,1,0,0,0)"
+           y="129.49626"
+           x="-489.49979"
+           height="7.0035982"
+           width="17.999748"
+           id="rect3092"
+           style="color:#000000;fill:#d3d7cf;fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:1.00024867;stroke-linecap:butt;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" />
+        <rect
+           transform="matrix(0,-1,1,0,0,0)"
+           y="130.50006"
+           x="-488.50009"
+           height="4.9998937"
+           width="15.999757"
+           id="rect3094"
+           style="color:#000000;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00024891;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:2;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline"
+           rx="0"
+           ry="0" />
+      </g>
+      <g
+         id="g3262"
+         transform="translate(-127,-578)"
+         inkscape:export-filename="/home/lapo/Desktop/align-distribute.tar.gz_FILES/align-stuff.png"
+         inkscape:export-xdpi="90"
+         inkscape:export-ydpi="90">
+        <rect
+           y="489.5"
+           x="196.49989"
+           height="1.9999999"
+           width="3.0000916"
+           id="rect3264"
+           style="fill:#fcaf3e;fill-opacity:1;stroke:#ce5c00;stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+        <path
+           style="fill:url(#linearGradient6948);fill-opacity:1;stroke:url(#linearGradient6950);stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dashoffset:0;stroke-opacity:1"
+           d="M 197.49998,491.5 L 186.49989,491.5 L 186.49989,489.5 L 197.49998,489.5"
+           id="path3266"
+           sodipodi:nodetypes="cccc" />
+        <path
+           style="fill:url(#linearGradient6952);fill-opacity:1;stroke:url(#linearGradient6954);stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:0;stroke-dashoffset:0;stroke-opacity:1"
+           d="M 198.49989,489.5 L 209.49998,489.5 L 209.49998,491.5 L 198.49989,491.5"
+           id="path3268"
+           sodipodi:nodetypes="cccc" />
+      </g>
+    </g>
+  </g>
+</svg>

BIN
main/inc/lib/javascript/svgedit/images/bold.png


BIN
main/inc/lib/javascript/svgedit/images/cancel.png


BIN
main/inc/lib/javascript/svgedit/images/circle.png


BIN
main/inc/lib/javascript/svgedit/images/clear.png


BIN
main/inc/lib/javascript/svgedit/images/clone.png


+ 29 - 0
main/inc/lib/javascript/svgedit/images/conn.svg

@@ -0,0 +1,29 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+	<g id="mode_connect">
+		<svg viewBox="0 0 24 24" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
+		 <defs>
+
+		  <line stroke-width="5" fill="none" stroke="#000000" id="svg_2" y2="121" x2="136" y1="7" x1="136">
+		   <stop stop-opacity="1" stop-color="#4687a0"/>
+		   <stop stop-opacity="1" stop-color="#ffffff"/>
+		  </line>
+		  <linearGradient y2="0.18359" x2="0.29688" y1="0.92188" x1="0.62109" id="svg_3">
+		   <stop stop-opacity="1" stop-color="#417dad" offset="0"/>
+		   <stop stop-opacity="1" stop-color="#ffffff" offset="1"/>
+		  </linearGradient>
+		 </defs>
+		 <g>
+		  <title>Layer 1</title>
+		  <line x1="5.64676" y1="5.60056" x2="18.50037" y2="18.62557" id="svg_5" stroke="#000000" fill="none"/>
+		  <rect opacity="0.75" stroke-width="0.5" x="0.5" y="0.5" width="9.625" height="5.125" id="svg_1" fill="url(#svg_3)" stroke="#000000"/>
+		  <rect opacity="0.75" id="svg_4" stroke-width="0.5" x="13.75" y="18.25" width="9.625" height="5.125" fill="url(#svg_3)" stroke="#000000"/>
+		  <g id="svg_9">
+		   <path d="m14.57119,9.12143l-0.98244,5.18852l2.70861,-4.36084" id="svg_6" fill="#a0a0a0" stroke="#000000"/>
+		   <path d="m14.27564,6.76258c-0.25872,0.72562 -0.40735,1.65632 -0.33812,2.15432l2.90784,1.2509c0.30961,-0.21212 1.08198,-1.1814 1.08198,-1.73736" id="svg_7" fill="url(#svg_3)" stroke="#000000"/>
+		   <path d="m16.28893,0.37519l-2.46413,5.9304l4.76481,2.39435l2.13178,-4.96735" id="svg_8" fill="url(#svg_3)" stroke="#000000"/>
+		  </g>
+		 </g>
+		</svg>
+	</g>
+	<g id="svg_eof"/>
+</svg>

BIN
main/inc/lib/javascript/svgedit/images/copy.png


BIN
main/inc/lib/javascript/svgedit/images/cut.png


BIN
main/inc/lib/javascript/svgedit/images/delete.png


BIN
main/inc/lib/javascript/svgedit/images/document-properties.png


BIN
main/inc/lib/javascript/svgedit/images/dropdown.gif


BIN
main/inc/lib/javascript/svgedit/images/ellipse.png


BIN
main/inc/lib/javascript/svgedit/images/eye.png


BIN
main/inc/lib/javascript/svgedit/images/fhpath.png


BIN
main/inc/lib/javascript/svgedit/images/flyouth.png


BIN
main/inc/lib/javascript/svgedit/images/flyup.gif


BIN
main/inc/lib/javascript/svgedit/images/freehand-circle.png


BIN
main/inc/lib/javascript/svgedit/images/freehand-square.png


BIN
main/inc/lib/javascript/svgedit/images/go-down.png


BIN
main/inc/lib/javascript/svgedit/images/go-up.png


Some files were not shown because too many files changed in this diff