From cbbddedd702d62e0c5c5a25147940534e4e29d09 Mon Sep 17 00:00:00 2001 From: Jacques Distler Date: Fri, 6 May 2016 14:17:39 -0500 Subject: [PATCH] Latest SVG-Edit --- .../javascripts/svg-edit/editor/browser.js | 4 +- .../svg-edit/editor/canvg/canvg.js | 5 +- .../svg-edit/editor/config-sample.js | 146 +++ .../javascripts/svg-edit/editor/coords.js | 9 +- .../javascripts/svg-edit/editor/draw.js | 444 ++++++--- .../svg-edit/editor/historyrecording.js | 173 ++++ .../svg-edit/editor/images/shape_group.png | Bin 553 -> 0 bytes .../javascripts/svg-edit/editor/layer.js | 222 +++++ .../javascripts/svg-edit/editor/path.js | 18 +- .../javascripts/svg-edit/editor/pathseg.js | 821 ++++++++++++++++ .../svg-edit/editor/recalculate.js | 15 +- .../javascripts/svg-edit/editor/sanitize.js | 5 +- .../javascripts/svg-edit/editor/select.js | 46 +- .../svg-edit/editor/svg-editor.html | 19 +- .../javascripts/svg-edit/editor/svg-editor.js | 82 +- .../javascripts/svg-edit/editor/svgcanvas.js | 929 +++--------------- .../javascripts/svg-edit/editor/svgutils.js | 740 +++++++++++++- 17 files changed, 2642 insertions(+), 1036 deletions(-) create mode 100644 vendor/assets/javascripts/svg-edit/editor/config-sample.js create mode 100644 vendor/assets/javascripts/svg-edit/editor/historyrecording.js delete mode 100644 vendor/assets/javascripts/svg-edit/editor/images/shape_group.png create mode 100644 vendor/assets/javascripts/svg-edit/editor/layer.js create mode 100644 vendor/assets/javascripts/svg-edit/editor/pathseg.js diff --git a/vendor/assets/javascripts/svg-edit/editor/browser.js b/vendor/assets/javascripts/svg-edit/editor/browser.js index dc6ee6eb0..4a70f4246 100644 --- a/vendor/assets/javascripts/svg-edit/editor/browser.js +++ b/vendor/assets/javascripts/svg-edit/editor/browser.js @@ -59,7 +59,7 @@ var supportsPathReplaceItem_ = (function() { var seglist = path.pathSegList; var seg = path.createSVGPathSegLinetoAbs(5,5); try { - seglist.replaceItem(seg, 0); + seglist.replaceItem(seg, 1); return true; } catch(err) {} return false; @@ -71,7 +71,7 @@ var supportsPathInsertItemBefore_ = (function() { var seglist = path.pathSegList; var seg = path.createSVGPathSegLinetoAbs(5,5); try { - seglist.insertItemBefore(seg, 0); + seglist.insertItemBefore(seg, 1); return true; } catch(err) {} return false; diff --git a/vendor/assets/javascripts/svg-edit/editor/canvg/canvg.js b/vendor/assets/javascripts/svg-edit/editor/canvg/canvg.js index 50545c982..f0d6d98c4 100644 --- a/vendor/assets/javascripts/svg-edit/editor/canvg/canvg.js +++ b/vendor/assets/javascripts/svg-edit/editor/canvg/canvg.js @@ -1437,7 +1437,8 @@ var cp = pp.getAsCurrentPoint(); // Conversion from endpoint to center parameterization - // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + // https://www.w3.org/TR/SVG11/implnote.html#ArcConversionEndpointToCenter + // 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, @@ -2918,4 +2919,4 @@ if (typeof(CanvasRenderingContext2D) != 'undefined') { scaleHeight: dh }); } -} \ No newline at end of file +} diff --git a/vendor/assets/javascripts/svg-edit/editor/config-sample.js b/vendor/assets/javascripts/svg-edit/editor/config-sample.js new file mode 100644 index 000000000..0336b2431 --- /dev/null +++ b/vendor/assets/javascripts/svg-edit/editor/config-sample.js @@ -0,0 +1,146 @@ +// DO NOT EDIT THIS FILE! +// THIS FILE IS JUST A SAMPLE; TO APPLY, YOU MUST +// CREATE A NEW FILE config.js AND ADD CONTENTS +// SUCH AS SHOWN BELOW INTO THAT FILE. + +/*globals svgEditor*/ +/* +The config.js file is intended for the setting of configuration or + preferences which must run early on; if this is not needed, it is + recommended that you create an extension instead (for greater + reusability and modularity). +*/ + +// CONFIG AND EXTENSION SETTING +/* +See defaultConfig and defaultExtensions in svg-editor.js for a list + of possible configuration settings. + +See svg-editor.js for documentation on using setConfig(). +*/ + +// URL OVERRIDE CONFIG +svgEditor.setConfig({ + /** + To override the ability for URLs to set URL-based SVG content, + uncomment the following: + */ + // preventURLContentLoading: true, + /** + To override the ability for URLs to set other configuration (including + extension config), uncomment the following: + */ + // preventAllURLConfig: true, + /** + To override the ability for URLs to set their own extensions, + uncomment the following (note that if setConfig() is used in + extension code, it will still be additive to extensions, + however): + */ + // lockExtensions: true, +}); + +svgEditor.setConfig({ + /* + Provide default values here which differ from that of the editor but + which the URL can override + */ +}, {allowInitialUserOverride: true}); + +// EXTENSION CONFIG +svgEditor.setConfig({ + extensions: [ + // 'ext-overview_window.js', 'ext-markers.js', 'ext-connector.js', 'ext-eyedropper.js', 'ext-shapes.js', 'ext-imagelib.js', 'ext-grid.js', 'ext-polygon.js', 'ext-star.js', 'ext-panning.js', 'ext-storage.js' + ] + // , noDefaultExtensions: false, // noDefaultExtensions can only be meaningfully used in config.js or in the URL +}); + +// OTHER CONFIG +svgEditor.setConfig({ + // canvasName: 'default', + // canvas_expansion: 3, + // initFill: { + // color: 'FF0000', // solid red + // opacity: 1 + // }, + // initStroke: { + // width: 5, + // color: '000000', // solid black + // opacity: 1 + // }, + // initOpacity: 1, + // colorPickerCSS: null, + // initTool: 'select', + // exportWindowType: 'new', // 'same' + // wireframe: false, + // showlayers: false, + // no_save_warning: false, + // PATH CONFIGURATION + // imgPath: 'images/', + // langPath: 'locale/', + // extPath: 'extensions/', + // jGraduatePath: 'jgraduate/images/', + /* + Uncomment the following to allow at least same domain (embedded) access, + including file:// access. + Setting as `['*']` would allow any domain to access but would be unsafe to + data privacy and integrity. + */ + // allowedOrigins: [window.location.origin || 'null'], // May be 'null' (as a string) when used as a file:// URL + // DOCUMENT PROPERTIES + // dimensions: [640, 480], + // EDITOR OPTIONS + // gridSnapping: false, + // gridColor: '#000', + // baseUnit: 'px', + // snappingStep: 10, + // showRulers: true, + // EXTENSION-RELATED (GRID) + // showGrid: false, // Set by ext-grid.js + // EXTENSION-RELATED (STORAGE) + // noStorageOnLoad: false, // Some interaction with ext-storage.js; prevent even the loading of previously saved local storage + // forceStorage: false, // Some interaction with ext-storage.js; strongly discouraged from modification as it bypasses user privacy by preventing them from choosing whether to keep local storage or not + // emptyStorageOnDecline: true, // Used by ext-storage.js; empty any prior storage if the user declines to store +}); + +// PREF CHANGES +/** +setConfig() can also be used to set preferences in addition to + configuration (see defaultPrefs in svg-editor.js for a list of + possible settings), but at least if you are using ext-storage.js + to store preferences, it will probably be better to let your + users control these. +As with configuration, one may use allowInitialUserOverride, but + in the case of preferences, any previously stored preferences + will also thereby be enabled to override this setting (and at a + higher priority than any URL preference setting overrides). + Failing to use allowInitialUserOverride will ensure preferences + are hard-coded here regardless of URL or prior user storage setting. +*/ +svgEditor.setConfig( + { + // lang: '', // Set dynamically within locale.js if not previously set + // iconsize: '', // Will default to 's' if the window height is smaller than the minimum height and 'm' otherwise + /** + * When showing the preferences dialog, svg-editor.js currently relies + * on curPrefs instead of $.pref, so allowing an override for bkgd_color + * means that this value won't have priority over block auto-detection as + * far as determining which color shows initially in the preferences + * dialog (though it can be changed and saved). + */ + // bkgd_color: '#FFF', + // bkgd_url: '', + // img_save: 'embed', + // Only shows in UI as far as alert notices + // save_notice_done: false, + // export_notice_done: false + } +); +svgEditor.setConfig( + { + // Indicate pref settings here if you wish to allow user storage or URL settings + // to be able to override your default preferences (unless other config options + // have already explicitly prevented one or the other) + }, + {allowInitialUserOverride: true} +); diff --git a/vendor/assets/javascripts/svg-edit/editor/coords.js b/vendor/assets/javascripts/svg-edit/editor/coords.js index 7105aeef1..7495ee369 100644 --- a/vendor/assets/javascripts/svg-edit/editor/coords.js +++ b/vendor/assets/javascripts/svg-edit/editor/coords.js @@ -10,10 +10,11 @@ // Dependencies: // 1) jquery.js // 2) math.js -// 3) browser.js -// 4) svgutils.js -// 5) units.js -// 6) svgtransformlist.js +// 3) pathseg.js +// 4) browser.js +// 5) svgutils.js +// 6) units.js +// 7) svgtransformlist.js var svgedit = svgedit || {}; diff --git a/vendor/assets/javascripts/svg-edit/editor/draw.js b/vendor/assets/javascripts/svg-edit/editor/draw.js index f79b8032f..c21c394f6 100644 --- a/vendor/assets/javascripts/svg-edit/editor/draw.js +++ b/vendor/assets/javascripts/svg-edit/editor/draw.js @@ -30,32 +30,10 @@ var RandomizeModes = { }; 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 @@ -111,17 +89,26 @@ svgedit.draw.Drawing = function(svgElem, opt_idPrefix) { this.releasedNums = []; /** - * The z-ordered array of tuples containing layer names and elements. + * The z-ordered array of Layer objects. Each layer has a name + * and group element. * The first layer is the one at the bottom of the rendering. - * TODO: Turn this into an Array. - * @type {Array.>} + * @type {Array.} */ this.all_layers = []; + /** + * Map of all_layers by name. + * + * Note: Layers are ordered, but referenced externally by name; so, we need both container + * types depending on which function is called (i.e. all_layers and layer_map). + * + * @type {Object.} + */ + this.layer_map = {}; + /** * The current layer being used. - * TODO: Make this a {Layer}. - * @type {SVGGElement} + * @type {Layer} */ this.current_layer = null; @@ -272,11 +259,7 @@ svgedit.draw.Drawing.prototype.getNumLayers = function() { * @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; + return this.layer_map[name] !== undefined; }; @@ -286,32 +269,143 @@ svgedit.draw.Drawing.prototype.hasLayer = function (name) { * @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 ''; + return i >= 0 && i < this.getNumLayers() ? this.all_layers[i].getName() : ''; }; /** * @returns {SVGGElement} The SVGGElement representing the current layer. */ svgedit.draw.Drawing.prototype.getCurrentLayer = function() { - return this.current_layer; + return this.current_layer ? this.current_layer.getGroup() : null; +}; + +/** + * Get a layer by name. + * @returns {SVGGElement} The SVGGElement representing the named layer or null. + */ +svgedit.draw.Drawing.prototype.getLayerByName = function(name) { + var layer = this.layer_map[name]; + return layer ? layer.getGroup() : null; }; /** * 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). + * @returns {string} 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 this.current_layer ? this.current_layer.getName() : ''; +}; + +/** + * Set the current layer's name. + * @param {string} name - The new name. + * @param {svgedit.history.HistoryRecordingService} hrService - History recording service + * @returns {string|null} The new name if changed; otherwise, null. + */ +svgedit.draw.Drawing.prototype.setCurrentLayerName = function (name, hrService) { + var finalName = null; + if (this.current_layer) { + var oldName = this.current_layer.getName(); + finalName = this.current_layer.setName(name, hrService); + if (finalName) { + delete this.layer_map[oldName]; + this.layer_map[finalName] = this.current_layer; + } + } + return finalName; +}; + +/** + * Set the current layer's position. + * @param {number} newpos - The zero-based index of the new position of the layer. Range should be 0 to layers-1 + * @returns {Object} If the name was changed, returns {title:SVGGElement, previousName:string}; otherwise null. + */ +svgedit.draw.Drawing.prototype.setCurrentLayerPosition = function (newpos) { + var layer_count = this.getNumLayers(); + if (!this.current_layer || newpos < 0 || newpos >= layer_count) { + return null; + } + + var oldpos; + for (oldpos = 0; oldpos < layer_count; ++oldpos) { + if (this.all_layers[oldpos] == this.current_layer) {break;} + } + // some unknown error condition (current_layer not in all_layers) + if (oldpos == layer_count) { return null; } + + if (oldpos != newpos) { + // if our new position is below us, we need to insert before the node after newpos + var refGroup = null; + var current_group = this.current_layer.getGroup(); + var oldNextSibling = current_group.nextSibling; + if (newpos > oldpos ) { + if (newpos < layer_count-1) { + refGroup = this.all_layers[newpos+1].getGroup(); + } + } + // if our new position is above us, we need to insert before the node at newpos + else { + refGroup = this.all_layers[newpos].getGroup(); + } + this.svgElem_.insertBefore(current_group, refGroup); + + this.identifyLayers(); + this.setCurrentLayer(this.getLayerName(newpos)); + + return { + currentGroup: current_group, + oldNextSibling: oldNextSibling + }; + } + return null; +}; + +svgedit.draw.Drawing.prototype.mergeLayer = function (hrService) { + var current_group = this.current_layer.getGroup(); + var prevGroup = $(current_group).prev()[0]; + if (!prevGroup) {return;} + + hrService.startBatchCommand('Merge Layer'); + + var layerNextSibling = current_group.nextSibling; + hrService.removeElement(current_group, layerNextSibling, this.svgElem_); + + while (current_group.firstChild) { + var child = current_group.firstChild; + if (child.localName == 'title') { + hrService.removeElement(child, child.nextSibling, current_group); + current_group.removeChild(child); + continue; } + var oldNextSibling = child.nextSibling; + prevGroup.appendChild(child); + hrService.moveElement(child, oldNextSibling, current_group); + } + + // Remove current layer's group + this.current_layer.removeGroup(); + // Remove the current layer and set the previous layer as the new current layer + var index = this.all_layers.indexOf(this.current_layer); + if (index > 0) { + var name = this.current_layer.getName(); + this.current_layer = this.all_layers[index-1] + this.all_layers.splice(index, 1); + delete this.layer_map[name]; + } + + hrService.endBatchCommand(); +}; + +svgedit.draw.Drawing.prototype.mergeAllLayers = function (hrService) { + // Set the current layer to the last layer. + this.current_layer = this.all_layers[this.all_layers.length-1]; + + hrService.startBatchCommand('Merge all Layers'); + while (this.all_layers.length > 1) { + this.mergeLayer(hrService); } - return ''; + hrService.endBatchCommand(); }; /** @@ -322,16 +416,14 @@ svgedit.draw.Drawing.prototype.getCurrentLayerName = function () { * @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; + var layer = this.layer_map[name]; + if (layer) { + if (this.current_layer) { + this.current_layer.deactivate(); } + this.current_layer = layer; + this.current_layer.activate(); + return true; } return false; }; @@ -344,101 +436,164 @@ svgedit.draw.Drawing.prototype.setCurrentLayer = function(name) { */ 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); + var oldLayerGroup = this.current_layer.removeGroup(); this.identifyLayers(); return oldLayerGroup; } return null; }; +/** + * Find the layer name in a group element. + * @param group The group element to search in. + * @returns {string} The layer name or empty string. + */ +function findLayerNameInGroup(group) { + var name = $("title", group).text(); + + // Hack for Opera 10.60 + if (!name && svgedit.browser.isOpera() && group.querySelectorAll) { + name = $(group.querySelectorAll('title')).text(); + } + return name; +} + +/** + * Given a set of names, return a new unique name. + * @param {Array.} existingLayerNames - Existing layer names. + * @returns {string} - The new name. + */ +function getNewLayerName(existingLayerNames) { + var i = 1; + // TODO(codedread): What about internationalization of "Layer"? + while (existingLayerNames.indexOf(("Layer " + i)) >= 0) { i++; } + return "Layer " + i; +} + /** * Updates layer system and sets the current layer to the * top-most layer (last child of this drawing). */ svgedit.draw.Drawing.prototype.identifyLayers = function() { this.all_layers = []; + this.layer_map = {}; var numchildren = this.svgElem_.childNodes.length; // loop through all children of SVG element var orphans = [], layernames = []; - var a_layer = null; + var layer = null; var childgroups = false; - var i; - for (i = 0; i < numchildren; ++i) { + for (var 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 + var name = findLayerNameInGroup(child); 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 { + layer = new svgedit.draw.Layer(name, child); + this.all_layers.push(layer); + this.layer_map[name] = layer; + } else { + // if group did not have a name, it is an orphan orphans.push(child); } - } - // if child has is "visible" (i.e. not a or <defs> element), then it is an orphan - else if(~visElems.indexOf(child.nodeName)) { - var bb = svgedit.utilities.getBBox(child); + } else if (~visElems.indexOf(child.nodeName)) { + // Child is "visible" (i.e. not a <title> or <defs> element), so it is an orphan orphans.push(child); } } } - // create a new layer and add all the orphans to it - var svgdoc = this.svgElem_.ownerDocument; + // If orphans or no layers found, create a new layer and add all the orphans to it 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] ); + layer = new svgedit.draw.Layer(getNewLayerName(layernames), null, this.svgElem_); + layer.appendChildren(orphans); + this.all_layers.push(layer); + this.layer_map[name] = layer; + } else { + layer.activate(); } - 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"); + this.current_layer = layer; }; /** - * 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 + * Creates a new top-level layer in the drawing with the given name and + * makes it the current layer. + * @param {string} name - The given name. If the layer name exists, a new name will be generated. + * @param {svgedit.history.HistoryRecordingService} hrService - History recording service * @returns {SVGGElement} The SVGGElement of the new layer, which is - * also the current layer of this drawing. + * 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; +svgedit.draw.Drawing.prototype.createLayer = function(name, hrService) { + if (this.current_layer) { + this.current_layer.deactivate(); + } + // Check for duplicate name. + if (name === undefined || name === null || name === '' || this.layer_map[name]) { + name = getNewLayerName(Object.keys(this.layer_map)); + } + + // Crate new layer and add to DOM as last layer + var layer = new svgedit.draw.Layer(name, null, this.svgElem_); + // Like to assume hrService exists, but this is backwards compatible with old version of createLayer. + if (hrService) { + hrService.startBatchCommand('Create Layer'); + hrService.insertElement(layer.getGroup()); + hrService.endBatchCommand(); + } + + this.all_layers.push(layer); + this.layer_map[name] = layer; + this.current_layer = layer; + return layer.getGroup(); +}; + +/** + * Creates a copy of the current layer with the given name and makes it the current layer. + * @param {string} name - The given name. If the layer name exists, a new name will be generated. + * @param {svgedit.history.HistoryRecordingService} hrService - History recording service + * @returns {SVGGElement} The SVGGElement of the new layer, which is + * also the current layer of this drawing. +*/ +svgedit.draw.Drawing.prototype.cloneLayer = function(name, hrService) { + if (!this.current_layer) {return null;} + this.current_layer.deactivate(); + // Check for duplicate name. + if (name === undefined || name === null || name === '' || this.layer_map[name]) { + name = getNewLayerName(Object.keys(this.layer_map)); + } + + // Create new group and add to DOM just after current_layer + var currentGroup = this.current_layer.getGroup(); + var layer = new svgedit.draw.Layer(name, currentGroup, this.svgElem_); + var group = layer.getGroup(); + + // Clone children + var children = currentGroup.childNodes; + var index; + for (index = 0; index < children.length; index++) { + var ch = children[index]; + if (ch.localName == 'title') {continue;} + group.appendChild(this.copyElem(ch)); + } + + if (hrService) { + hrService.startBatchCommand('Duplicate Layer'); + hrService.insertElement(group); + hrService.endBatchCommand(); + } + + // Update layer containers and current_layer. + index = this.all_layers.indexOf(this.current_layer); + if (index >= 0) { + this.all_layers.splice(index + 1, 0, layer); + } else { + this.all_layers.push(layer); + } + this.layer_map[name] = layer; + this.current_layer = layer; + return group; }; /** @@ -448,17 +603,8 @@ svgedit.draw.Drawing.prototype.createLayer = function(name) { * @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'); + var layer = this.layer_map[layername]; + return layer ? layer.isVisible() : false; }; /** @@ -474,21 +620,10 @@ 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; - } - } + var layer = this.layer_map[layername]; if (!layer) {return null;} - - var oldDisplay = layer.getAttribute("display"); - if (!oldDisplay) {oldDisplay = "inline";} - layer.setAttribute("display", bVisible ? "inline" : "none"); - return layer; + layer.setVisible(bVisible); + return layer.getGroup(); }; @@ -499,18 +634,9 @@ svgedit.draw.Drawing.prototype.setLayerVisibility = function(layername, bVisible * 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; + var layer = this.layer_map[layername]; + if (!layer) {return null;} + return layer.getOpacity(); }; /** @@ -524,14 +650,22 @@ 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; - } + var layer = this.layer_map[layername]; + if (layer) { + layer.setOpacity(opacity); } }; +/** + * Create a clone of an element, updating its ID and its children's IDs when needed + * @param {Element} el - DOM element to clone + * @returns {Element} + */ +svgedit.draw.Drawing.prototype.copyElem = function(el) { + var self = this; + var getNextIdClosure = function() { return self.getNextId();} + return svgedit.utilities.copyElem(el, getNextIdClosure) +} + + }()); diff --git a/vendor/assets/javascripts/svg-edit/editor/historyrecording.js b/vendor/assets/javascripts/svg-edit/editor/historyrecording.js new file mode 100644 index 000000000..da89a7f3d --- /dev/null +++ b/vendor/assets/javascripts/svg-edit/editor/historyrecording.js @@ -0,0 +1,173 @@ +/*globals svgedit*/ +/*jslint vars: true, eqeq: true */ +/** + * Package: svgedit.history + * + * Licensed under the MIT License + * + * Copyright(c) 2016 Flint O'Brien + */ + +// Dependencies: +// 1) history.js + +(function() { + 'use strict'; + +if (!svgedit.history) { + svgedit.history = {}; +} +var history = svgedit.history; + +/** + * History recording service. + * + * A self-contained service interface for recording history. Once injected, no other dependencies + * or globals are required (example: UndoManager, command types, etc.). Easy to mock for unit tests. + * Built on top of history classes in history.js. + * + * There is a simple start/end interface for batch commands. + * + * HistoryRecordingService.NO_HISTORY is a singleton that can be passed in to functions + * that record history. This helps when the caller requires that no history be recorded. + * + * Usage: + * The following will record history: insert, batch, insert. + * ``` + * hrService = new svgedit.history.HistoryRecordingService(this.undoMgr); + * hrService.insertElement(elem, text); // add simple command to history. + * hrService.startBatchCommand('create two elements'); + * hrService.changeElement(elem, attrs, text); // add to batchCommand + * hrService.changeElement(elem, attrs2, text); // add to batchCommand + * hrService.endBatchCommand(); // add batch command with two change commands to history. + * hrService.insertElement(elem, text); // add simple command to history. + * ``` + * + * Note that all functions return this, so commands can be chained, like so: + * + * ``` + * hrService + * .startBatchCommand('create two elements') + * .insertElement(elem, text) + * .changeElement(elem, attrs, text) + * .endBatchCommand(); + * ``` + * + * @param {svgedit.history.UndoManager} undoManager - The undo manager. + * A value of null is valid for cases where no history recording is required. + * See singleton: HistoryRecordingService.NO_HISTORY + */ +var HistoryRecordingService = history.HistoryRecordingService = function(undoManager) { + this.undoManager_ = undoManager; + this.currentBatchCommand_ = null; + this.batchCommandStack_ = []; +}; + +/** + * @type {svgedit.history.HistoryRecordingService} NO_HISTORY - Singleton that can be passed + * in to functions that record history, but the caller requires that no history be recorded. + */ +HistoryRecordingService.NO_HISTORY = new HistoryRecordingService(); + +/** + * Start a batch command so multiple commands can recorded as a single history command. + * Requires a corresponding call to endBatchCommand. Start and end commands can be nested. + * + * @param {string} text - Optional string describing the batch command. + * @returns {svgedit.history.HistoryRecordingService} + */ +HistoryRecordingService.prototype.startBatchCommand = function(text) { + if (!this.undoManager_) {return this;} + this.currentBatchCommand_ = new history.BatchCommand(text); + this.batchCommandStack_.push(this.currentBatchCommand_); + return this; +}; + +/** + * End a batch command and add it to the history or a parent batch command. + * @returns {svgedit.history.HistoryRecordingService} + */ +HistoryRecordingService.prototype.endBatchCommand = function() { + if (!this.undoManager_) {return this;} + if (this.currentBatchCommand_) { + var batchCommand = this.currentBatchCommand_; + this.batchCommandStack_.pop(); + var length = this.batchCommandStack_.length; + this.currentBatchCommand_ = length ? this.batchCommandStack_[length-1] : null; + this.addCommand_(batchCommand); + } + return this; +}; + +/** + * Add a MoveElementCommand to the history or current batch command + * @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 + * @returns {svgedit.history.HistoryRecordingService} + */ +HistoryRecordingService.prototype.moveElement = function(elem, oldNextSibling, oldParent, text) { + if (!this.undoManager_) {return this;} + this.addCommand_(new history.MoveElementCommand(elem, oldNextSibling, oldParent, text)); + return this; +}; + +/** + * Add an InsertElementCommand to the history or current batch command + * @param {Element} elem - The DOM element that was added + * @param {string} [text] - An optional string visible to user related to this change + * @returns {svgedit.history.HistoryRecordingService} + */ +HistoryRecordingService.prototype.insertElement = function(elem, text) { + if (!this.undoManager_) {return this;} + this.addCommand_(new history.InsertElementCommand(elem, text)); + return this; +}; + + +/** + * Add a RemoveElementCommand to the history or current batch command + * @param {Element} elem - The DOM element that was removed + * @param {Element} oldNextSibling - The element's next sibling before it was removed + * @param {Element} oldParent - The element's parent before it was removed + * @param {string} [text] - An optional string visible to user related to this change + * @returns {svgedit.history.HistoryRecordingService} + */ +HistoryRecordingService.prototype.removeElement = function(elem, oldNextSibling, oldParent, text) { + if (!this.undoManager_) {return this;} + this.addCommand_(new history.RemoveElementCommand(elem, oldNextSibling, oldParent, text)); + return this; +}; + + +/** + * Add a ChangeElementCommand to the history or current batch command + * @param {Element} elem - The DOM element that was changed + * @param {object} attrs - An object with the attributes to be changed and the values they had *before* the change + * @param {string} [text] - An optional string visible to user related to this change + * @returns {svgedit.history.HistoryRecordingService} + */ +HistoryRecordingService.prototype.changeElement = function(elem, attrs, text) { + if (!this.undoManager_) {return this;} + this.addCommand_(new history.ChangeElementCommand(elem, attrs, text)); + return this; +}; + +/** + * Private function to add a command to the history or current batch command. + * @param cmd + * @returns {svgedit.history.HistoryRecordingService} + * @private + */ +HistoryRecordingService.prototype.addCommand_ = function(cmd) { + if (!this.undoManager_) {return this;} + if (this.currentBatchCommand_) { + this.currentBatchCommand_.addSubCommand(cmd); + } else { + this.undoManager_.addCommandToHistory(cmd); + } +}; + + +}()); diff --git a/vendor/assets/javascripts/svg-edit/editor/images/shape_group.png b/vendor/assets/javascripts/svg-edit/editor/images/shape_group.png deleted file mode 100644 index bb2ff516d35dc9a92ed6ffdc79595d61513e65e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 553 zcmV+^0@nSBP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzyGcYrR5;6x zlQD}^Q4odC3(>+TVU5_x7IrE&y4G<6LW)%R0m7z{{Dic2rZN9Q$X1&!1i?xJlHEd! zMb<)AaLd@37v7sQ$Kt*h-|9m$FwES`%*UCz+-Nu)o=e)5bUGLe9$*XW_xtbCG~Fk8 zS^==z>2yHy9=5bNj`w@L9^yE@UnO}mnM@vCy?#47TC5}hP9TpCzy*-N^X9b(0M_gE z+tFzB%*@_Lk^lfli<Ne26+od$9vY<(FMc`>o(4eCs(C^W#d$;S65tM)myfdn<b}#4 zyZ;4YIbk!x;fIxO-l#wVKAkUvDhVVYI?Mq#hZ$fbULD+xE-5epER1kx=SooF0$p&1 zND5znq$N9GK{l101$PG>ARS}?B&nAG1Y9f~bPvTYI2|OiyFW_ORR02sPxSI|DVlF2 z(;aRKd!WOt1W+_H!3vT$JIHb~{vVJggjsddXjHaLhZC0-bi3U}k|cX1kEhe=697of zarXTeQU_=bBVa~2!_$6z^8&YCf03D;q1|q4Hk+y0Y)TTg@kXPe@p!CSt){5eY8@v@ r@|fhwd_LdX<~P>s_0MMZkmR@D2_>Rn5)Y(r00000NkvXXu0mjf$qDH! diff --git a/vendor/assets/javascripts/svg-edit/editor/layer.js b/vendor/assets/javascripts/svg-edit/editor/layer.js new file mode 100644 index 000000000..8b5a09d2c --- /dev/null +++ b/vendor/assets/javascripts/svg-edit/editor/layer.js @@ -0,0 +1,222 @@ +/*globals $ svgedit*/ +/*jslint vars: true, eqeq: true */ +/** + * Package: svgedit.history + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Jeff Schiller + * Copyright(c) 2016 Flint O'Brien + */ + +// Dependencies: +// 1) svgedit.js +// 2) draw.js + +(function() { + 'use strict'; + +if (!svgedit.draw) { + svgedit.draw = {}; +} +var NS = svgedit.NS; + + +/** + * This class encapsulates the concept of a layer in the drawing. It can be constructed with + * an existing group element or, with three parameters, will create a new layer group element. + * + * Usage: + * new Layer'name', group) // Use the existing group for this layer. + * new Layer('name', group, svgElem) // Create a new group and add it to the DOM after group. + * new Layer('name', null, svgElem) // Create a new group and add it to the DOM as the last layer. + * + * @param {string} name - Layer name + * @param {SVGGElement|null} group - An existing SVG group element or null. + * If group and no svgElem, use group for this layer. + * If group and svgElem, create a new group element and insert it in the DOM after group. + * If no group and svgElem, create a new group element and insert it in the DOM as the last layer. + * @param {SVGGElement=} svgElem - The SVG DOM element. If defined, use this to add + * a new layer to the document. + */ +var Layer = svgedit.draw.Layer = function(name, group, svgElem) { + this.name_ = name; + this.group_ = svgElem ? null : group; + + if (svgElem) { + // Create a group element with title and add it to the DOM. + var svgdoc = svgElem.ownerDocument; + this.group_ = svgdoc.createElementNS(NS.SVG, "g"); + var layer_title = svgdoc.createElementNS(NS.SVG, "title"); + layer_title.textContent = name; + this.group_.appendChild(layer_title); + if (group) { + $(group).after(this.group_); + } else { + svgElem.appendChild(this.group_); + } + } + + addLayerClass(this.group_); + svgedit.utilities.walkTree(this.group_, function(e){e.setAttribute("style", "pointer-events:inherit");}); + + this.group_.setAttribute("style", svgElem ? "pointer-events:all" : "pointer-events:none"); +}; + +/** + * @type {string} CLASS_NAME - class attribute assigned to all layer groups. + */ +Layer.CLASS_NAME = 'layer'; + +/** + * @type {RegExp} CLASS_REGEX - Used to test presence of class Layer.CLASS_NAME + */ +Layer.CLASS_REGEX = new RegExp('(\\s|^)' + Layer.CLASS_NAME + '(\\s|$)'); + + +/** + * Get the layer's name. + * @returns {string} The layer name + */ +Layer.prototype.getName = function() { + return this.name_; +}; + +/** + * Get the group element for this layer. + * @returns {SVGGElement} The layer SVG group + */ +Layer.prototype.getGroup = function() { + return this.group_; +}; + +/** + * Active this layer so it takes pointer events. + */ +Layer.prototype.activate = function() { + this.group_.setAttribute("style", "pointer-events:all"); +}; + +/** + * Deactive this layer so it does NOT take pointer events. + */ +Layer.prototype.deactivate = function() { + this.group_.setAttribute("style", "pointer-events:none"); +}; + +/** + * Set this layer visible or hidden based on 'visible' parameter. + * @param {boolean} visible - If true, make visible; otherwise, hide it. + */ +Layer.prototype.setVisible = function(visible) { + var expected = visible === undefined || visible ? "inline" : "none"; + var oldDisplay = this.group_.getAttribute("display"); + if (oldDisplay !== expected) { + this.group_.setAttribute("display", expected); + } +}; + +/** + * Is this layer visible? + * @returns {boolean} True if visible. + */ +Layer.prototype.isVisible = function() { + return this.group_.getAttribute('display') !== 'none'; +}; + +/** + * Get layer opacity. + * @returns {number} Opacity value. + */ +Layer.prototype.getOpacity = function() { + var opacity = this.group_.getAttribute('opacity'); + if (opacity === null || opacity === undefined) { + return 1; + } + return parseFloat(opacity); +}; + +/** + * Sets the opacity of this layer. If opacity is not a value between 0.0 and 1.0, + * nothing happens. + * @param {number} opacity - A float value in the range 0.0-1.0 + */ +Layer.prototype.setOpacity = function(opacity) { + if (typeof opacity === 'number' && opacity >= 0.0 && opacity <= 1.0) { + this.group_.setAttribute('opacity', opacity); + } +}; + +/** + * Append children to this layer. + * @param {SVGGElement} children - The children to append to this layer. + */ +Layer.prototype.appendChildren = function(children) { + for (var i = 0; i < children.length; ++i) { + this.group_.appendChild(children[i]); + } +}; + +Layer.prototype.getTitleElement = function() { + var len = this.group_.childNodes.length; + for (var i = 0; i < len; ++i) { + var child = this.group_.childNodes.item(i); + if (child && child.tagName === 'title') { + return child; + } + } + return null; +}; + +/** + * Set the name of this layer. + * @param {string} name - The new name. + * @param {svgedit.history.HistoryRecordingService} hrService - History recording service + * @returns {string|null} The new name if changed; otherwise, null. + */ +Layer.prototype.setName = function(name, hrService) { + var previousName = this.name_; + name = svgedit.utilities.toXml(name); + // now change the underlying title element contents + var title = this.getTitleElement(); + if (title) { + while (title.firstChild) { title.removeChild(title.firstChild); } + title.textContent = name; + this.name_ = name; + if (hrService) { + hrService.changeElement(title, {'#text':previousName}); + } + return this.name_; + } + return null; +}; + +/** + * Remove this layer's group from the DOM. No more functions on group can be called after this. + * @param {SVGGElement} children - The children to append to this layer. + * @returns {SVGGElement} The layer SVG group that was just removed. + */ +Layer.prototype.removeGroup = function() { + var parent = this.group_.parentNode; + var group = parent.removeChild(this.group_); + this.group_ = undefined; + return group; +}; + + +/** + * Add class Layer.CLASS_NAME to the element (usually class='layer'). + * + * Parameters: + * @param {SVGGElement} elem - The SVG element to update + */ +function addLayerClass(elem) { + var classes = elem.getAttribute('class'); + if (classes === null || classes === undefined || classes.length === 0) { + elem.setAttribute('class', Layer.CLASS_NAME); + } else if (! Layer.CLASS_REGEX.test(classes)) { + elem.setAttribute('class', classes + ' ' + Layer.CLASS_NAME); + } +} + +}()); diff --git a/vendor/assets/javascripts/svg-edit/editor/path.js b/vendor/assets/javascripts/svg-edit/editor/path.js index 529c1b60c..ccdf54866 100644 --- a/vendor/assets/javascripts/svg-edit/editor/path.js +++ b/vendor/assets/javascripts/svg-edit/editor/path.js @@ -283,6 +283,7 @@ svgedit.path.getControlPoints = function(seg) { // This replaces the segment at the given index. Type is given as number. svgedit.path.replacePathSeg = function(type, index, pts, elem) { var path = elem || svgedit.path.path.elem; + var func = 'createSVGPathSeg' + pathFuncs[type]; var seg = path[func].apply(path, pts); @@ -478,11 +479,12 @@ svgedit.path.Segment.prototype.move = function(dx, dy) { var cur_pts, item = this.item; if (this.ctrlpts) { - cur_pts = [item.x += dx, item.y += dy, + cur_pts = [item.x += dx, item.y += dy, item.x1, item.y1, item.x2 += dx, item.y2 += dy]; } else { cur_pts = [item.x += dx, item.y += dy]; } + svgedit.path.replacePathSeg(this.type, this.index, cur_pts); if (this.next && this.next.ctrlpts) { @@ -520,9 +522,8 @@ svgedit.path.Segment.prototype.setLinked = function(num) { } var item = seg.item; - - item['x' + anum] = pt.x + (pt.x - this.item['x' + num]); - item['y' + anum] = pt.y + (pt.y - this.item['y' + num]); + item['x' + anum ] = pt.x + (pt.x - this.item['x' + num]); + item['y' + anum ] = pt.y + (pt.y - this.item['y' + num]); var pts = [item.x, item.y, item.x1, item.y1, @@ -534,11 +535,11 @@ svgedit.path.Segment.prototype.setLinked = function(num) { svgedit.path.Segment.prototype.moveCtrl = function(num, dx, dy) { var item = this.item; - item['x' + num] += dx; item['y' + num] += dy; - var pts = [item.x, item.y, item.x1, item.y1, item.x2, item.y2]; + var pts = [item.x,item.y, + item.x1,item.y1, item.x2,item.y2]; svgedit.path.replacePathSeg(this.type, this.index, pts); this.update(true); @@ -569,7 +570,10 @@ svgedit.path.Path = function(elem) { // Reset path data svgedit.path.Path.prototype.init = function() { // Hide all grips, etc - $(svgedit.path.getGripContainer()).find('*').attr('display', 'none'); + + //fixed, needed to work on all found elements, not just first + $(svgedit.path.getGripContainer()).find('*').each( function() { $(this).attr('display', 'none') }); + var segList = this.elem.pathSegList; var len = segList.numberOfItems; this.segs = []; diff --git a/vendor/assets/javascripts/svg-edit/editor/pathseg.js b/vendor/assets/javascripts/svg-edit/editor/pathseg.js new file mode 100644 index 000000000..14d19fa86 --- /dev/null +++ b/vendor/assets/javascripts/svg-edit/editor/pathseg.js @@ -0,0 +1,821 @@ +// SVGPathSeg API polyfill +// https://github.com/progers/pathseg +// +// This is a drop-in replacement for the SVGPathSeg and SVGPathSegList APIs that were removed from +// SVG2 (https://lists.w3.org/Archives/Public/www-svg/2015Jun/0044.html), including the latest spec +// changes which were implemented in Firefox 43 and Chrome 46. +// +// Example API usage: +// var path = document.createElementNS("http://www.w3.org/2000/svg", "path"); +// var moveToSeg = path.createSVGPathSegMovetoRel(10, 10); +// var lineToSeg = path.createSVGPathSegLinetoRel(100, 100); +// path.pathSegList.appendItem(moveToSeg); +// path.pathSegList.appendItem(lineToSeg); +// console.log(path.getAttribute('d')); // m 10 10 l 100 100 +// moveToSeg.x += 200; +// moveToSeg.y += 200; +// console.log(path.getAttribute('d')); // m 210 210 l 100 100 + +(function() { "use strict"; + if (!window.SVGPathSeg) { + // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGPathSeg + window.SVGPathSeg = function(type, typeAsLetter, owningPathSegList) { + this.pathSegType = type; + this.pathSegTypeAsLetter = typeAsLetter; + this._owningPathSegList = owningPathSegList; + } + + SVGPathSeg.PATHSEG_UNKNOWN = 0; + SVGPathSeg.PATHSEG_CLOSEPATH = 1; + SVGPathSeg.PATHSEG_MOVETO_ABS = 2; + SVGPathSeg.PATHSEG_MOVETO_REL = 3; + SVGPathSeg.PATHSEG_LINETO_ABS = 4; + SVGPathSeg.PATHSEG_LINETO_REL = 5; + SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS = 6; + SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL = 7; + SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS = 8; + SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL = 9; + SVGPathSeg.PATHSEG_ARC_ABS = 10; + SVGPathSeg.PATHSEG_ARC_REL = 11; + SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS = 12; + SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL = 13; + SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS = 14; + SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL = 15; + SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS = 16; + SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL = 17; + SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS = 18; + SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL = 19; + + // Notify owning PathSegList on any changes so they can be synchronized back to the path element. + SVGPathSeg.prototype._segmentChanged = function() { + if (this._owningPathSegList) + this._owningPathSegList.segmentChanged(this); + } + + window.SVGPathSegClosePath = function(owningPathSegList) { + SVGPathSeg.call(this, SVGPathSeg.PATHSEG_CLOSEPATH, "z", owningPathSegList); + } + SVGPathSegClosePath.prototype = Object.create(SVGPathSeg.prototype); + SVGPathSegClosePath.prototype.toString = function() { return "[object SVGPathSegClosePath]"; } + SVGPathSegClosePath.prototype._asPathString = function() { return this.pathSegTypeAsLetter; } + SVGPathSegClosePath.prototype.clone = function() { return new SVGPathSegClosePath(undefined); } + + window.SVGPathSegMovetoAbs = function(owningPathSegList, x, y) { + SVGPathSeg.call(this, SVGPathSeg.PATHSEG_MOVETO_ABS, "M", owningPathSegList); + this._x = x; + this._y = y; + } + SVGPathSegMovetoAbs.prototype = Object.create(SVGPathSeg.prototype); + SVGPathSegMovetoAbs.prototype.toString = function() { return "[object SVGPathSegMovetoAbs]"; } + SVGPathSegMovetoAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x + " " + this._y; } + SVGPathSegMovetoAbs.prototype.clone = function() { return new SVGPathSegMovetoAbs(undefined, this._x, this._y); } + Object.defineProperty(SVGPathSegMovetoAbs.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegMovetoAbs.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true }); + + window.SVGPathSegMovetoRel = function(owningPathSegList, x, y) { + SVGPathSeg.call(this, SVGPathSeg.PATHSEG_MOVETO_REL, "m", owningPathSegList); + this._x = x; + this._y = y; + } + SVGPathSegMovetoRel.prototype = Object.create(SVGPathSeg.prototype); + SVGPathSegMovetoRel.prototype.toString = function() { return "[object SVGPathSegMovetoRel]"; } + SVGPathSegMovetoRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x + " " + this._y; } + SVGPathSegMovetoRel.prototype.clone = function() { return new SVGPathSegMovetoRel(undefined, this._x, this._y); } + Object.defineProperty(SVGPathSegMovetoRel.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegMovetoRel.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true }); + + window.SVGPathSegLinetoAbs = function(owningPathSegList, x, y) { + SVGPathSeg.call(this, SVGPathSeg.PATHSEG_LINETO_ABS, "L", owningPathSegList); + this._x = x; + this._y = y; + } + SVGPathSegLinetoAbs.prototype = Object.create(SVGPathSeg.prototype); + SVGPathSegLinetoAbs.prototype.toString = function() { return "[object SVGPathSegLinetoAbs]"; } + SVGPathSegLinetoAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x + " " + this._y; } + SVGPathSegLinetoAbs.prototype.clone = function() { return new SVGPathSegLinetoAbs(undefined, this._x, this._y); } + Object.defineProperty(SVGPathSegLinetoAbs.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegLinetoAbs.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true }); + + window.SVGPathSegLinetoRel = function(owningPathSegList, x, y) { + SVGPathSeg.call(this, SVGPathSeg.PATHSEG_LINETO_REL, "l", owningPathSegList); + this._x = x; + this._y = y; + } + SVGPathSegLinetoRel.prototype = Object.create(SVGPathSeg.prototype); + SVGPathSegLinetoRel.prototype.toString = function() { return "[object SVGPathSegLinetoRel]"; } + SVGPathSegLinetoRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x + " " + this._y; } + SVGPathSegLinetoRel.prototype.clone = function() { return new SVGPathSegLinetoRel(undefined, this._x, this._y); } + Object.defineProperty(SVGPathSegLinetoRel.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegLinetoRel.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true }); + + window.SVGPathSegCurvetoCubicAbs = function(owningPathSegList, x, y, x1, y1, x2, y2) { + SVGPathSeg.call(this, SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS, "C", owningPathSegList); + this._x = x; + this._y = y; + this._x1 = x1; + this._y1 = y1; + this._x2 = x2; + this._y2 = y2; + } + SVGPathSegCurvetoCubicAbs.prototype = Object.create(SVGPathSeg.prototype); + SVGPathSegCurvetoCubicAbs.prototype.toString = function() { return "[object SVGPathSegCurvetoCubicAbs]"; } + SVGPathSegCurvetoCubicAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x1 + " " + this._y1 + " " + this._x2 + " " + this._y2 + " " + this._x + " " + this._y; } + SVGPathSegCurvetoCubicAbs.prototype.clone = function() { return new SVGPathSegCurvetoCubicAbs(undefined, this._x, this._y, this._x1, this._y1, this._x2, this._y2); } + Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype, "x1", { get: function() { return this._x1; }, set: function(x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype, "y1", { get: function() { return this._y1; }, set: function(y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype, "x2", { get: function() { return this._x2; }, set: function(x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype, "y2", { get: function() { return this._y2; }, set: function(y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true }); + + window.SVGPathSegCurvetoCubicRel = function(owningPathSegList, x, y, x1, y1, x2, y2) { + SVGPathSeg.call(this, SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL, "c", owningPathSegList); + this._x = x; + this._y = y; + this._x1 = x1; + this._y1 = y1; + this._x2 = x2; + this._y2 = y2; + } + SVGPathSegCurvetoCubicRel.prototype = Object.create(SVGPathSeg.prototype); + SVGPathSegCurvetoCubicRel.prototype.toString = function() { return "[object SVGPathSegCurvetoCubicRel]"; } + SVGPathSegCurvetoCubicRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x1 + " " + this._y1 + " " + this._x2 + " " + this._y2 + " " + this._x + " " + this._y; } + SVGPathSegCurvetoCubicRel.prototype.clone = function() { return new SVGPathSegCurvetoCubicRel(undefined, this._x, this._y, this._x1, this._y1, this._x2, this._y2); } + Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype, "x1", { get: function() { return this._x1; }, set: function(x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype, "y1", { get: function() { return this._y1; }, set: function(y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype, "x2", { get: function() { return this._x2; }, set: function(x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype, "y2", { get: function() { return this._y2; }, set: function(y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true }); + + window.SVGPathSegCurvetoQuadraticAbs = function(owningPathSegList, x, y, x1, y1) { + SVGPathSeg.call(this, SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS, "Q", owningPathSegList); + this._x = x; + this._y = y; + this._x1 = x1; + this._y1 = y1; + } + SVGPathSegCurvetoQuadraticAbs.prototype = Object.create(SVGPathSeg.prototype); + SVGPathSegCurvetoQuadraticAbs.prototype.toString = function() { return "[object SVGPathSegCurvetoQuadraticAbs]"; } + SVGPathSegCurvetoQuadraticAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x1 + " " + this._y1 + " " + this._x + " " + this._y; } + SVGPathSegCurvetoQuadraticAbs.prototype.clone = function() { return new SVGPathSegCurvetoQuadraticAbs(undefined, this._x, this._y, this._x1, this._y1); } + Object.defineProperty(SVGPathSegCurvetoQuadraticAbs.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoQuadraticAbs.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoQuadraticAbs.prototype, "x1", { get: function() { return this._x1; }, set: function(x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoQuadraticAbs.prototype, "y1", { get: function() { return this._y1; }, set: function(y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true }); + + window.SVGPathSegCurvetoQuadraticRel = function(owningPathSegList, x, y, x1, y1) { + SVGPathSeg.call(this, SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL, "q", owningPathSegList); + this._x = x; + this._y = y; + this._x1 = x1; + this._y1 = y1; + } + SVGPathSegCurvetoQuadraticRel.prototype = Object.create(SVGPathSeg.prototype); + SVGPathSegCurvetoQuadraticRel.prototype.toString = function() { return "[object SVGPathSegCurvetoQuadraticRel]"; } + SVGPathSegCurvetoQuadraticRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x1 + " " + this._y1 + " " + this._x + " " + this._y; } + SVGPathSegCurvetoQuadraticRel.prototype.clone = function() { return new SVGPathSegCurvetoQuadraticRel(undefined, this._x, this._y, this._x1, this._y1); } + Object.defineProperty(SVGPathSegCurvetoQuadraticRel.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoQuadraticRel.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoQuadraticRel.prototype, "x1", { get: function() { return this._x1; }, set: function(x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoQuadraticRel.prototype, "y1", { get: function() { return this._y1; }, set: function(y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true }); + + window.SVGPathSegArcAbs = function(owningPathSegList, x, y, r1, r2, angle, largeArcFlag, sweepFlag) { + SVGPathSeg.call(this, SVGPathSeg.PATHSEG_ARC_ABS, "A", owningPathSegList); + this._x = x; + this._y = y; + this._r1 = r1; + this._r2 = r2; + this._angle = angle; + this._largeArcFlag = largeArcFlag; + this._sweepFlag = sweepFlag; + } + SVGPathSegArcAbs.prototype = Object.create(SVGPathSeg.prototype); + SVGPathSegArcAbs.prototype.toString = function() { return "[object SVGPathSegArcAbs]"; } + SVGPathSegArcAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._r1 + " " + this._r2 + " " + this._angle + " " + (this._largeArcFlag ? "1" : "0") + " " + (this._sweepFlag ? "1" : "0") + " " + this._x + " " + this._y; } + SVGPathSegArcAbs.prototype.clone = function() { return new SVGPathSegArcAbs(undefined, this._x, this._y, this._r1, this._r2, this._angle, this._largeArcFlag, this._sweepFlag); } + Object.defineProperty(SVGPathSegArcAbs.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegArcAbs.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegArcAbs.prototype, "r1", { get: function() { return this._r1; }, set: function(r1) { this._r1 = r1; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegArcAbs.prototype, "r2", { get: function() { return this._r2; }, set: function(r2) { this._r2 = r2; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegArcAbs.prototype, "angle", { get: function() { return this._angle; }, set: function(angle) { this._angle = angle; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegArcAbs.prototype, "largeArcFlag", { get: function() { return this._largeArcFlag; }, set: function(largeArcFlag) { this._largeArcFlag = largeArcFlag; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegArcAbs.prototype, "sweepFlag", { get: function() { return this._sweepFlag; }, set: function(sweepFlag) { this._sweepFlag = sweepFlag; this._segmentChanged(); }, enumerable: true }); + + window.SVGPathSegArcRel = function(owningPathSegList, x, y, r1, r2, angle, largeArcFlag, sweepFlag) { + SVGPathSeg.call(this, SVGPathSeg.PATHSEG_ARC_REL, "a", owningPathSegList); + this._x = x; + this._y = y; + this._r1 = r1; + this._r2 = r2; + this._angle = angle; + this._largeArcFlag = largeArcFlag; + this._sweepFlag = sweepFlag; + } + SVGPathSegArcRel.prototype = Object.create(SVGPathSeg.prototype); + SVGPathSegArcRel.prototype.toString = function() { return "[object SVGPathSegArcRel]"; } + SVGPathSegArcRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._r1 + " " + this._r2 + " " + this._angle + " " + (this._largeArcFlag ? "1" : "0") + " " + (this._sweepFlag ? "1" : "0") + " " + this._x + " " + this._y; } + SVGPathSegArcRel.prototype.clone = function() { return new SVGPathSegArcRel(undefined, this._x, this._y, this._r1, this._r2, this._angle, this._largeArcFlag, this._sweepFlag); } + Object.defineProperty(SVGPathSegArcRel.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegArcRel.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegArcRel.prototype, "r1", { get: function() { return this._r1; }, set: function(r1) { this._r1 = r1; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegArcRel.prototype, "r2", { get: function() { return this._r2; }, set: function(r2) { this._r2 = r2; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegArcRel.prototype, "angle", { get: function() { return this._angle; }, set: function(angle) { this._angle = angle; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegArcRel.prototype, "largeArcFlag", { get: function() { return this._largeArcFlag; }, set: function(largeArcFlag) { this._largeArcFlag = largeArcFlag; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegArcRel.prototype, "sweepFlag", { get: function() { return this._sweepFlag; }, set: function(sweepFlag) { this._sweepFlag = sweepFlag; this._segmentChanged(); }, enumerable: true }); + + window.SVGPathSegLinetoHorizontalAbs = function(owningPathSegList, x) { + SVGPathSeg.call(this, SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS, "H", owningPathSegList); + this._x = x; + } + SVGPathSegLinetoHorizontalAbs.prototype = Object.create(SVGPathSeg.prototype); + SVGPathSegLinetoHorizontalAbs.prototype.toString = function() { return "[object SVGPathSegLinetoHorizontalAbs]"; } + SVGPathSegLinetoHorizontalAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x; } + SVGPathSegLinetoHorizontalAbs.prototype.clone = function() { return new SVGPathSegLinetoHorizontalAbs(undefined, this._x); } + Object.defineProperty(SVGPathSegLinetoHorizontalAbs.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true }); + + window.SVGPathSegLinetoHorizontalRel = function(owningPathSegList, x) { + SVGPathSeg.call(this, SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL, "h", owningPathSegList); + this._x = x; + } + SVGPathSegLinetoHorizontalRel.prototype = Object.create(SVGPathSeg.prototype); + SVGPathSegLinetoHorizontalRel.prototype.toString = function() { return "[object SVGPathSegLinetoHorizontalRel]"; } + SVGPathSegLinetoHorizontalRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x; } + SVGPathSegLinetoHorizontalRel.prototype.clone = function() { return new SVGPathSegLinetoHorizontalRel(undefined, this._x); } + Object.defineProperty(SVGPathSegLinetoHorizontalRel.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true }); + + window.SVGPathSegLinetoVerticalAbs = function(owningPathSegList, y) { + SVGPathSeg.call(this, SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS, "V", owningPathSegList); + this._y = y; + } + SVGPathSegLinetoVerticalAbs.prototype = Object.create(SVGPathSeg.prototype); + SVGPathSegLinetoVerticalAbs.prototype.toString = function() { return "[object SVGPathSegLinetoVerticalAbs]"; } + SVGPathSegLinetoVerticalAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._y; } + SVGPathSegLinetoVerticalAbs.prototype.clone = function() { return new SVGPathSegLinetoVerticalAbs(undefined, this._y); } + Object.defineProperty(SVGPathSegLinetoVerticalAbs.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true }); + + window.SVGPathSegLinetoVerticalRel = function(owningPathSegList, y) { + SVGPathSeg.call(this, SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL, "v", owningPathSegList); + this._y = y; + } + SVGPathSegLinetoVerticalRel.prototype = Object.create(SVGPathSeg.prototype); + SVGPathSegLinetoVerticalRel.prototype.toString = function() { return "[object SVGPathSegLinetoVerticalRel]"; } + SVGPathSegLinetoVerticalRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._y; } + SVGPathSegLinetoVerticalRel.prototype.clone = function() { return new SVGPathSegLinetoVerticalRel(undefined, this._y); } + Object.defineProperty(SVGPathSegLinetoVerticalRel.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true }); + + window.SVGPathSegCurvetoCubicSmoothAbs = function(owningPathSegList, x, y, x2, y2) { + SVGPathSeg.call(this, SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS, "S", owningPathSegList); + this._x = x; + this._y = y; + this._x2 = x2; + this._y2 = y2; + } + SVGPathSegCurvetoCubicSmoothAbs.prototype = Object.create(SVGPathSeg.prototype); + SVGPathSegCurvetoCubicSmoothAbs.prototype.toString = function() { return "[object SVGPathSegCurvetoCubicSmoothAbs]"; } + SVGPathSegCurvetoCubicSmoothAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x2 + " " + this._y2 + " " + this._x + " " + this._y; } + SVGPathSegCurvetoCubicSmoothAbs.prototype.clone = function() { return new SVGPathSegCurvetoCubicSmoothAbs(undefined, this._x, this._y, this._x2, this._y2); } + Object.defineProperty(SVGPathSegCurvetoCubicSmoothAbs.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoCubicSmoothAbs.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoCubicSmoothAbs.prototype, "x2", { get: function() { return this._x2; }, set: function(x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoCubicSmoothAbs.prototype, "y2", { get: function() { return this._y2; }, set: function(y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true }); + + window.SVGPathSegCurvetoCubicSmoothRel = function(owningPathSegList, x, y, x2, y2) { + SVGPathSeg.call(this, SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL, "s", owningPathSegList); + this._x = x; + this._y = y; + this._x2 = x2; + this._y2 = y2; + } + SVGPathSegCurvetoCubicSmoothRel.prototype = Object.create(SVGPathSeg.prototype); + SVGPathSegCurvetoCubicSmoothRel.prototype.toString = function() { return "[object SVGPathSegCurvetoCubicSmoothRel]"; } + SVGPathSegCurvetoCubicSmoothRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x2 + " " + this._y2 + " " + this._x + " " + this._y; } + SVGPathSegCurvetoCubicSmoothRel.prototype.clone = function() { return new SVGPathSegCurvetoCubicSmoothRel(undefined, this._x, this._y, this._x2, this._y2); } + Object.defineProperty(SVGPathSegCurvetoCubicSmoothRel.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoCubicSmoothRel.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoCubicSmoothRel.prototype, "x2", { get: function() { return this._x2; }, set: function(x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoCubicSmoothRel.prototype, "y2", { get: function() { return this._y2; }, set: function(y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true }); + + window.SVGPathSegCurvetoQuadraticSmoothAbs = function(owningPathSegList, x, y) { + SVGPathSeg.call(this, SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS, "T", owningPathSegList); + this._x = x; + this._y = y; + } + SVGPathSegCurvetoQuadraticSmoothAbs.prototype = Object.create(SVGPathSeg.prototype); + SVGPathSegCurvetoQuadraticSmoothAbs.prototype.toString = function() { return "[object SVGPathSegCurvetoQuadraticSmoothAbs]"; } + SVGPathSegCurvetoQuadraticSmoothAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x + " " + this._y; } + SVGPathSegCurvetoQuadraticSmoothAbs.prototype.clone = function() { return new SVGPathSegCurvetoQuadraticSmoothAbs(undefined, this._x, this._y); } + Object.defineProperty(SVGPathSegCurvetoQuadraticSmoothAbs.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoQuadraticSmoothAbs.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true }); + + window.SVGPathSegCurvetoQuadraticSmoothRel = function(owningPathSegList, x, y) { + SVGPathSeg.call(this, SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL, "t", owningPathSegList); + this._x = x; + this._y = y; + } + SVGPathSegCurvetoQuadraticSmoothRel.prototype = Object.create(SVGPathSeg.prototype); + SVGPathSegCurvetoQuadraticSmoothRel.prototype.toString = function() { return "[object SVGPathSegCurvetoQuadraticSmoothRel]"; } + SVGPathSegCurvetoQuadraticSmoothRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + " " + this._x + " " + this._y; } + SVGPathSegCurvetoQuadraticSmoothRel.prototype.clone = function() { return new SVGPathSegCurvetoQuadraticSmoothRel(undefined, this._x, this._y); } + Object.defineProperty(SVGPathSegCurvetoQuadraticSmoothRel.prototype, "x", { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true }); + Object.defineProperty(SVGPathSegCurvetoQuadraticSmoothRel.prototype, "y", { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true }); + + // Add createSVGPathSeg* functions to SVGPathElement. + // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGPathElement. + SVGPathElement.prototype.createSVGPathSegClosePath = function() { return new SVGPathSegClosePath(undefined); } + SVGPathElement.prototype.createSVGPathSegMovetoAbs = function(x, y) { return new SVGPathSegMovetoAbs(undefined, x, y); } + SVGPathElement.prototype.createSVGPathSegMovetoRel = function(x, y) { return new SVGPathSegMovetoRel(undefined, x, y); } + SVGPathElement.prototype.createSVGPathSegLinetoAbs = function(x, y) { return new SVGPathSegLinetoAbs(undefined, x, y); } + SVGPathElement.prototype.createSVGPathSegLinetoRel = function(x, y) { return new SVGPathSegLinetoRel(undefined, x, y); } + SVGPathElement.prototype.createSVGPathSegCurvetoCubicAbs = function(x, y, x1, y1, x2, y2) { return new SVGPathSegCurvetoCubicAbs(undefined, x, y, x1, y1, x2, y2); } + SVGPathElement.prototype.createSVGPathSegCurvetoCubicRel = function(x, y, x1, y1, x2, y2) { return new SVGPathSegCurvetoCubicRel(undefined, x, y, x1, y1, x2, y2); } + SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticAbs = function(x, y, x1, y1) { return new SVGPathSegCurvetoQuadraticAbs(undefined, x, y, x1, y1); } + SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticRel = function(x, y, x1, y1) { return new SVGPathSegCurvetoQuadraticRel(undefined, x, y, x1, y1); } + SVGPathElement.prototype.createSVGPathSegArcAbs = function(x, y, r1, r2, angle, largeArcFlag, sweepFlag) { return new SVGPathSegArcAbs(undefined, x, y, r1, r2, angle, largeArcFlag, sweepFlag); } + SVGPathElement.prototype.createSVGPathSegArcRel = function(x, y, r1, r2, angle, largeArcFlag, sweepFlag) { return new SVGPathSegArcRel(undefined, x, y, r1, r2, angle, largeArcFlag, sweepFlag); } + SVGPathElement.prototype.createSVGPathSegLinetoHorizontalAbs = function(x) { return new SVGPathSegLinetoHorizontalAbs(undefined, x); } + SVGPathElement.prototype.createSVGPathSegLinetoHorizontalRel = function(x) { return new SVGPathSegLinetoHorizontalRel(undefined, x); } + SVGPathElement.prototype.createSVGPathSegLinetoVerticalAbs = function(y) { return new SVGPathSegLinetoVerticalAbs(undefined, y); } + SVGPathElement.prototype.createSVGPathSegLinetoVerticalRel = function(y) { return new SVGPathSegLinetoVerticalRel(undefined, y); } + SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothAbs = function(x, y, x2, y2) { return new SVGPathSegCurvetoCubicSmoothAbs(undefined, x, y, x2, y2); } + SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothRel = function(x, y, x2, y2) { return new SVGPathSegCurvetoCubicSmoothRel(undefined, x, y, x2, y2); } + SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothAbs = function(x, y) { return new SVGPathSegCurvetoQuadraticSmoothAbs(undefined, x, y); } + SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothRel = function(x, y) { return new SVGPathSegCurvetoQuadraticSmoothRel(undefined, x, y); } + } + + if (!window.SVGPathSegList) { + // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGPathSegList + window.SVGPathSegList = function(pathElement) { + this._pathElement = pathElement; + this._list = this._parsePath(this._pathElement.getAttribute("d")); + + // Use a MutationObserver to catch changes to the path's "d" attribute. + this._mutationObserverConfig = { "attributes": true, "attributeFilter": ["d"] }; + this._pathElementMutationObserver = new MutationObserver(this._updateListFromPathMutations.bind(this)); + this._pathElementMutationObserver.observe(this._pathElement, this._mutationObserverConfig); + } + + Object.defineProperty(SVGPathSegList.prototype, "numberOfItems", { + get: function() { + this._checkPathSynchronizedToList(); + return this._list.length; + } + }); + + // Process any pending mutations to the path element and update the list as needed. + // This should be the first call of all public functions and is needed because + // MutationObservers are not synchronous so we can have pending asynchronous mutations. + SVGPathSegList.prototype._checkPathSynchronizedToList = function() { + this._updateListFromPathMutations(this._pathElementMutationObserver.takeRecords()); + } + + SVGPathSegList.prototype._updateListFromPathMutations = function(mutationRecords) { + if (!this._pathElement) + return; + var hasPathMutations = false; + mutationRecords.forEach(function(record) { + if (record.attributeName == "d") + hasPathMutations = true; + }); + if (hasPathMutations) + this._list = this._parsePath(this._pathElement.getAttribute("d")); + } + + // Serialize the list and update the path's 'd' attribute. + SVGPathSegList.prototype._writeListToPath = function() { + this._pathElementMutationObserver.disconnect(); + this._pathElement.setAttribute("d", SVGPathSegList._pathSegArrayAsString(this._list)); + this._pathElementMutationObserver.observe(this._pathElement, this._mutationObserverConfig); + } + + // When a path segment changes the list needs to be synchronized back to the path element. + SVGPathSegList.prototype.segmentChanged = function(pathSeg) { + this._writeListToPath(); + } + + SVGPathSegList.prototype.clear = function() { + this._checkPathSynchronizedToList(); + + this._list.forEach(function(pathSeg) { + pathSeg._owningPathSegList = null; + }); + this._list = []; + this._writeListToPath(); + } + + SVGPathSegList.prototype.initialize = function(newItem) { + this._checkPathSynchronizedToList(); + + this._list = [newItem]; + newItem._owningPathSegList = this; + this._writeListToPath(); + return newItem; + } + + SVGPathSegList.prototype._checkValidIndex = function(index) { + if (isNaN(index) || index < 0 || index >= this.numberOfItems) + throw "INDEX_SIZE_ERR"; + } + + SVGPathSegList.prototype.getItem = function(index) { + this._checkPathSynchronizedToList(); + + this._checkValidIndex(index); + return this._list[index]; + } + + SVGPathSegList.prototype.insertItemBefore = function(newItem, index) { + this._checkPathSynchronizedToList(); + + // Spec: If the index is greater than or equal to numberOfItems, then the new item is appended to the end of the list. + if (index > this.numberOfItems) + index = this.numberOfItems; + if (newItem._owningPathSegList) { + // SVG2 spec says to make a copy. + newItem = newItem.clone(); + } + this._list.splice(index, 0, newItem); + newItem._owningPathSegList = this; + this._writeListToPath(); + return newItem; + } + + SVGPathSegList.prototype.replaceItem = function(newItem, index) { + this._checkPathSynchronizedToList(); + + if (newItem._owningPathSegList) { + // SVG2 spec says to make a copy. + newItem = newItem.clone(); + } + this._checkValidIndex(index); + this._list[index] = newItem; + newItem._owningPathSegList = this; + this._writeListToPath(); + return newItem; + } + + SVGPathSegList.prototype.removeItem = function(index) { + this._checkPathSynchronizedToList(); + + this._checkValidIndex(index); + var item = this._list[index]; + this._list.splice(index, 1); + this._writeListToPath(); + return item; + } + + SVGPathSegList.prototype.appendItem = function(newItem) { + this._checkPathSynchronizedToList(); + + if (newItem._owningPathSegList) { + // SVG2 spec says to make a copy. + newItem = newItem.clone(); + } + this._list.push(newItem); + newItem._owningPathSegList = this; + // TODO: Optimize this to just append to the existing attribute. + this._writeListToPath(); + return newItem; + } + + SVGPathSegList._pathSegArrayAsString = function(pathSegArray) { + var string = ""; + var first = true; + pathSegArray.forEach(function(pathSeg) { + if (first) { + first = false; + string += pathSeg._asPathString(); + } else { + string += " " + pathSeg._asPathString(); + } + }); + return string; + } + + // This closely follows SVGPathParser::parsePath from Source/core/svg/SVGPathParser.cpp. + SVGPathSegList.prototype._parsePath = function(string) { + if (!string || string.length == 0) + return []; + + var owningPathSegList = this; + + var Builder = function() { + this.pathSegList = []; + } + + Builder.prototype.appendSegment = function(pathSeg) { + this.pathSegList.push(pathSeg); + } + + var Source = function(string) { + this._string = string; + this._currentIndex = 0; + this._endIndex = this._string.length; + this._previousCommand = SVGPathSeg.PATHSEG_UNKNOWN; + + this._skipOptionalSpaces(); + } + + Source.prototype._isCurrentSpace = function() { + var character = this._string[this._currentIndex]; + return character <= " " && (character == " " || character == "\n" || character == "\t" || character == "\r" || character == "\f"); + } + + Source.prototype._skipOptionalSpaces = function() { + while (this._currentIndex < this._endIndex && this._isCurrentSpace()) + this._currentIndex++; + return this._currentIndex < this._endIndex; + } + + Source.prototype._skipOptionalSpacesOrDelimiter = function() { + if (this._currentIndex < this._endIndex && !this._isCurrentSpace() && this._string.charAt(this._currentIndex) != ",") + return false; + if (this._skipOptionalSpaces()) { + if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) == ",") { + this._currentIndex++; + this._skipOptionalSpaces(); + } + } + return this._currentIndex < this._endIndex; + } + + Source.prototype.hasMoreData = function() { + return this._currentIndex < this._endIndex; + } + + Source.prototype.peekSegmentType = function() { + var lookahead = this._string[this._currentIndex]; + return this._pathSegTypeFromChar(lookahead); + } + + Source.prototype._pathSegTypeFromChar = function(lookahead) { + switch (lookahead) { + case "Z": + case "z": + return SVGPathSeg.PATHSEG_CLOSEPATH; + case "M": + return SVGPathSeg.PATHSEG_MOVETO_ABS; + case "m": + return SVGPathSeg.PATHSEG_MOVETO_REL; + case "L": + return SVGPathSeg.PATHSEG_LINETO_ABS; + case "l": + return SVGPathSeg.PATHSEG_LINETO_REL; + case "C": + return SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS; + case "c": + return SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL; + case "Q": + return SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS; + case "q": + return SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL; + case "A": + return SVGPathSeg.PATHSEG_ARC_ABS; + case "a": + return SVGPathSeg.PATHSEG_ARC_REL; + case "H": + return SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS; + case "h": + return SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL; + case "V": + return SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS; + case "v": + return SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL; + case "S": + return SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS; + case "s": + return SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL; + case "T": + return SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS; + case "t": + return SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL; + default: + return SVGPathSeg.PATHSEG_UNKNOWN; + } + } + + Source.prototype._nextCommandHelper = function(lookahead, previousCommand) { + // Check for remaining coordinates in the current command. + if ((lookahead == "+" || lookahead == "-" || lookahead == "." || (lookahead >= "0" && lookahead <= "9")) && previousCommand != SVGPathSeg.PATHSEG_CLOSEPATH) { + if (previousCommand == SVGPathSeg.PATHSEG_MOVETO_ABS) + return SVGPathSeg.PATHSEG_LINETO_ABS; + if (previousCommand == SVGPathSeg.PATHSEG_MOVETO_REL) + return SVGPathSeg.PATHSEG_LINETO_REL; + return previousCommand; + } + return SVGPathSeg.PATHSEG_UNKNOWN; + } + + Source.prototype.initialCommandIsMoveTo = function() { + // If the path is empty it is still valid, so return true. + if (!this.hasMoreData()) + return true; + var command = this.peekSegmentType(); + // Path must start with moveTo. + return command == SVGPathSeg.PATHSEG_MOVETO_ABS || command == SVGPathSeg.PATHSEG_MOVETO_REL; + } + + // Parse a number from an SVG path. This very closely follows genericParseNumber(...) from Source/core/svg/SVGParserUtilities.cpp. + // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-PathDataBNF + Source.prototype._parseNumber = function() { + var exponent = 0; + var integer = 0; + var frac = 1; + var decimal = 0; + var sign = 1; + var expsign = 1; + + var startIndex = this._currentIndex; + + this._skipOptionalSpaces(); + + // Read the sign. + if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) == "+") + this._currentIndex++; + else if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) == "-") { + this._currentIndex++; + sign = -1; + } + + if (this._currentIndex == this._endIndex || ((this._string.charAt(this._currentIndex) < "0" || this._string.charAt(this._currentIndex) > "9") && this._string.charAt(this._currentIndex) != ".")) + // The first character of a number must be one of [0-9+-.]. + return undefined; + + // Read the integer part, build right-to-left. + var startIntPartIndex = this._currentIndex; + while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= "0" && this._string.charAt(this._currentIndex) <= "9") + this._currentIndex++; // Advance to first non-digit. + + if (this._currentIndex != startIntPartIndex) { + var scanIntPartIndex = this._currentIndex - 1; + var multiplier = 1; + while (scanIntPartIndex >= startIntPartIndex) { + integer += multiplier * (this._string.charAt(scanIntPartIndex--) - "0"); + multiplier *= 10; + } + } + + // Read the decimals. + if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) == ".") { + this._currentIndex++; + + // There must be a least one digit following the . + if (this._currentIndex >= this._endIndex || this._string.charAt(this._currentIndex) < "0" || this._string.charAt(this._currentIndex) > "9") + return undefined; + while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= "0" && this._string.charAt(this._currentIndex) <= "9") + decimal += (this._string.charAt(this._currentIndex++) - "0") * (frac *= 0.1); + } + + // Read the exponent part. + if (this._currentIndex != startIndex && this._currentIndex + 1 < this._endIndex && (this._string.charAt(this._currentIndex) == "e" || this._string.charAt(this._currentIndex) == "E") && (this._string.charAt(this._currentIndex + 1) != "x" && this._string.charAt(this._currentIndex + 1) != "m")) { + this._currentIndex++; + + // Read the sign of the exponent. + if (this._string.charAt(this._currentIndex) == "+") { + this._currentIndex++; + } else if (this._string.charAt(this._currentIndex) == "-") { + this._currentIndex++; + expsign = -1; + } + + // There must be an exponent. + if (this._currentIndex >= this._endIndex || this._string.charAt(this._currentIndex) < "0" || this._string.charAt(this._currentIndex) > "9") + return undefined; + + while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= "0" && this._string.charAt(this._currentIndex) <= "9") { + exponent *= 10; + exponent += (this._string.charAt(this._currentIndex) - "0"); + this._currentIndex++; + } + } + + var number = integer + decimal; + number *= sign; + + if (exponent) + number *= Math.pow(10, expsign * exponent); + + if (startIndex == this._currentIndex) + return undefined; + + this._skipOptionalSpacesOrDelimiter(); + + return number; + } + + Source.prototype._parseArcFlag = function() { + if (this._currentIndex >= this._endIndex) + return undefined; + var flag = false; + var flagChar = this._string.charAt(this._currentIndex++); + if (flagChar == "0") + flag = false; + else if (flagChar == "1") + flag = true; + else + return undefined; + + this._skipOptionalSpacesOrDelimiter(); + return flag; + } + + Source.prototype.parseSegment = function() { + var lookahead = this._string[this._currentIndex]; + var command = this._pathSegTypeFromChar(lookahead); + if (command == SVGPathSeg.PATHSEG_UNKNOWN) { + // Possibly an implicit command. Not allowed if this is the first command. + if (this._previousCommand == SVGPathSeg.PATHSEG_UNKNOWN) + return null; + command = this._nextCommandHelper(lookahead, this._previousCommand); + if (command == SVGPathSeg.PATHSEG_UNKNOWN) + return null; + } else { + this._currentIndex++; + } + + this._previousCommand = command; + + switch (command) { + case SVGPathSeg.PATHSEG_MOVETO_REL: + return new SVGPathSegMovetoRel(owningPathSegList, this._parseNumber(), this._parseNumber()); + case SVGPathSeg.PATHSEG_MOVETO_ABS: + return new SVGPathSegMovetoAbs(owningPathSegList, this._parseNumber(), this._parseNumber()); + case SVGPathSeg.PATHSEG_LINETO_REL: + return new SVGPathSegLinetoRel(owningPathSegList, this._parseNumber(), this._parseNumber()); + case SVGPathSeg.PATHSEG_LINETO_ABS: + return new SVGPathSegLinetoAbs(owningPathSegList, this._parseNumber(), this._parseNumber()); + case SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL: + return new SVGPathSegLinetoHorizontalRel(owningPathSegList, this._parseNumber()); + case SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS: + return new SVGPathSegLinetoHorizontalAbs(owningPathSegList, this._parseNumber()); + case SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL: + return new SVGPathSegLinetoVerticalRel(owningPathSegList, this._parseNumber()); + case SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS: + return new SVGPathSegLinetoVerticalAbs(owningPathSegList, this._parseNumber()); + case SVGPathSeg.PATHSEG_CLOSEPATH: + this._skipOptionalSpaces(); + return new SVGPathSegClosePath(owningPathSegList); + case SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL: + var points = {x1: this._parseNumber(), y1: this._parseNumber(), x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()}; + return new SVGPathSegCurvetoCubicRel(owningPathSegList, points.x, points.y, points.x1, points.y1, points.x2, points.y2); + case SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS: + var points = {x1: this._parseNumber(), y1: this._parseNumber(), x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()}; + return new SVGPathSegCurvetoCubicAbs(owningPathSegList, points.x, points.y, points.x1, points.y1, points.x2, points.y2); + case SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL: + var points = {x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()}; + return new SVGPathSegCurvetoCubicSmoothRel(owningPathSegList, points.x, points.y, points.x2, points.y2); + case SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS: + var points = {x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()}; + return new SVGPathSegCurvetoCubicSmoothAbs(owningPathSegList, points.x, points.y, points.x2, points.y2); + case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL: + var points = {x1: this._parseNumber(), y1: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()}; + return new SVGPathSegCurvetoQuadraticRel(owningPathSegList, points.x, points.y, points.x1, points.y1); + case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS: + var points = {x1: this._parseNumber(), y1: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()}; + return new SVGPathSegCurvetoQuadraticAbs(owningPathSegList, points.x, points.y, points.x1, points.y1); + case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL: + return new SVGPathSegCurvetoQuadraticSmoothRel(owningPathSegList, this._parseNumber(), this._parseNumber()); + case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS: + return new SVGPathSegCurvetoQuadraticSmoothAbs(owningPathSegList, this._parseNumber(), this._parseNumber()); + case SVGPathSeg.PATHSEG_ARC_REL: + var points = {x1: this._parseNumber(), y1: this._parseNumber(), arcAngle: this._parseNumber(), arcLarge: this._parseArcFlag(), arcSweep: this._parseArcFlag(), x: this._parseNumber(), y: this._parseNumber()}; + return new SVGPathSegArcRel(owningPathSegList, points.x, points.y, points.x1, points.y1, points.arcAngle, points.arcLarge, points.arcSweep); + case SVGPathSeg.PATHSEG_ARC_ABS: + var points = {x1: this._parseNumber(), y1: this._parseNumber(), arcAngle: this._parseNumber(), arcLarge: this._parseArcFlag(), arcSweep: this._parseArcFlag(), x: this._parseNumber(), y: this._parseNumber()}; + return new SVGPathSegArcAbs(owningPathSegList, points.x, points.y, points.x1, points.y1, points.arcAngle, points.arcLarge, points.arcSweep); + default: + throw "Unknown path seg type." + } + } + + var builder = new Builder(); + var source = new Source(string); + + if (!source.initialCommandIsMoveTo()) + return []; + while (source.hasMoreData()) { + var pathSeg = source.parseSegment(); + if (!pathSeg) + return []; + builder.appendSegment(pathSeg); + } + + return builder.pathSegList; + } + + // Add the pathSegList accessors to SVGPathElement. + // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGAnimatedPathData + Object.defineProperty(SVGPathElement.prototype, "pathSegList", { + get: function() { + if (!this._pathSegList) + this._pathSegList = new SVGPathSegList(this); + return this._pathSegList; + }, + enumerable: true + }); + // FIXME: The following are not implemented and simply return SVGPathElement.pathSegList. + Object.defineProperty(SVGPathElement.prototype, "normalizedPathSegList", { get: function() { return this.pathSegList; }, enumerable: true }); + Object.defineProperty(SVGPathElement.prototype, "animatedPathSegList", { get: function() { return this.pathSegList; }, enumerable: true }); + Object.defineProperty(SVGPathElement.prototype, "animatedNormalizedPathSegList", { get: function() { return this.pathSegList; }, enumerable: true }); + } +}()); \ No newline at end of file diff --git a/vendor/assets/javascripts/svg-edit/editor/recalculate.js b/vendor/assets/javascripts/svg-edit/editor/recalculate.js index 3585147cf..6ae2046db 100644 --- a/vendor/assets/javascripts/svg-edit/editor/recalculate.js +++ b/vendor/assets/javascripts/svg-edit/editor/recalculate.js @@ -11,13 +11,14 @@ // 1) jquery // 2) jquery-svg.js // 3) svgedit.js -// 4) browser.js -// 5) math.js -// 6) history.js -// 7) units.js -// 8) svgtransformlist.js -// 9) svgutils.js -// 10) coords.js +// 4) pathseg.js +// 5) browser.js +// 6) math.js +// 7) history.js +// 8) units.js +// 9) svgtransformlist.js +// 10) svgutils.js +// 11) coords.js var svgedit = svgedit || {}; diff --git a/vendor/assets/javascripts/svg-edit/editor/sanitize.js b/vendor/assets/javascripts/svg-edit/editor/sanitize.js index f289f50a3..a6d6f9f65 100644 --- a/vendor/assets/javascripts/svg-edit/editor/sanitize.js +++ b/vendor/assets/javascripts/svg-edit/editor/sanitize.js @@ -11,8 +11,9 @@ // Dependencies: // 1) jQuery -// 2) browser.js -// 3) svgutils.js +// 2) pathseg.js +// 3) browser.js +// 4) svgutils.js (function() {'use strict'; diff --git a/vendor/assets/javascripts/svg-edit/editor/select.js b/vendor/assets/javascripts/svg-edit/editor/select.js index 587d3198a..3b2e35dec 100644 --- a/vendor/assets/javascripts/svg-edit/editor/select.js +++ b/vendor/assets/javascripts/svg-edit/editor/select.js @@ -11,9 +11,10 @@ // Dependencies: // 1) jQuery -// 2) browser.js -// 3) math.js -// 4) svgutils.js +// 2) pathseg.js +// 3) browser.js +// 4) math.js +// 5) svgutils.js (function() {'use strict'; @@ -32,7 +33,8 @@ var gripRadius = svgedit.browser.isTouch() ? 10 : 4; // Parameters: // id - integer to internally indentify the selector // elem - DOM element associated with this selector -svgedit.select.Selector = function(id, elem) { +// bbox - Optional bbox to use for initialization (prevents duplicate getBBox call). +svgedit.select.Selector = function(id, elem, bbox) { // this is the selector's unique number this.id = id; @@ -76,7 +78,7 @@ svgedit.select.Selector = function(id, elem) { 'w' : null }; - this.reset(this.selectedElement); + this.reset(this.selectedElement, bbox); }; @@ -85,13 +87,15 @@ svgedit.select.Selector = function(id, elem) { // // Parameters: // e - DOM element associated with this selector -svgedit.select.Selector.prototype.reset = function(e) { +// bbox - Optional bbox to use for reset (prevents duplicate getBBox call). +svgedit.select.Selector.prototype.reset = function(e, bbox) { this.locked = true; this.selectedElement = e; - this.resize(); + this.resize(bbox); this.selectorGroup.setAttribute('display', 'inline'); }; + // Function: svgedit.select.Selector.updateGripCursors // Updates cursors for corner grips on rotation so arrows point the right way // @@ -122,7 +126,6 @@ svgedit.select.Selector.prototype.updateGripCursors = function(angle) { // Parameters: // show - boolean indicating whether grips should be shown or not svgedit.select.Selector.prototype.showGrips = function(show) { - // TODO: use suspendRedraw() here var bShow = show ? 'inline' : 'none'; selectorManager_.selectorGripsGroup.setAttribute('display', bShow); var elem = this.selectedElement; @@ -135,7 +138,8 @@ svgedit.select.Selector.prototype.showGrips = function(show) { // Function: svgedit.select.Selector.resize // Updates the selector to match the element's size -svgedit.select.Selector.prototype.resize = function() { +// bbox - Optional bbox to use for resize (prevents duplicate getBBox call). +svgedit.select.Selector.prototype.resize = function(bbox) { var selectedBox = this.selectorRect, mgr = selectorManager_, selectedGrips = mgr.selectorGrips, @@ -161,7 +165,11 @@ svgedit.select.Selector.prototype.resize = function() { m.e *= current_zoom; m.f *= current_zoom; - var bbox = svgedit.utilities.getBBox(selected); + if (!bbox) { + bbox = svgedit.utilities.getBBox(selected); + } + // TODO: svgedit.utilities.getBBox (previous line) already knows to call getStrokedBBox when tagName === 'g'. Remove this? + // TODO: svgedit.utilities.getBBox doesn't exclude 'gsvg' and calls getStrokedBBox for any 'g'. Should getBBox be updated? if (tagName === 'g' && !$.data(selected, 'gsvg')) { // The bbox for a group does not include stroke vals, so we // get the bbox based on its children. @@ -221,7 +229,6 @@ svgedit.select.Selector.prototype.resize = function() { nbaw = (maxx-minx); nbah = (maxy-miny); } - var sr_handle = svgFactory_.svgRoot().suspendRedraw(100); var dstr = 'M' + nbax + ',' + nbay + ' L' + (nbax+nbaw) + ',' + nbay @@ -260,8 +267,6 @@ svgedit.select.Selector.prototype.resize = function() { mgr.rotateGrip.setAttribute('cx', nbax + (nbaw)/2); mgr.rotateGrip.setAttribute('cy', nbay - (gripRadius*5)); // } - - svgFactory_.svgRoot().unsuspendRedraw(sr_handle); }; @@ -415,7 +420,8 @@ svgedit.select.SelectorManager.prototype.initGroup = function() { // // Parameters: // elem - DOM element to get the selector for -svgedit.select.SelectorManager.prototype.requestSelector = function(elem) { +// bbox - Optional bbox to use for reset (prevents duplicate getBBox call). +svgedit.select.SelectorManager.prototype.requestSelector = function(elem, bbox) { if (elem == null) {return null;} var i, N = this.selectors.length; @@ -427,13 +433,13 @@ svgedit.select.SelectorManager.prototype.requestSelector = function(elem) { for (i = 0; i < N; ++i) { if (this.selectors[i] && !this.selectors[i].locked) { this.selectors[i].locked = true; - this.selectors[i].reset(elem); + this.selectors[i].reset(elem, bbox); this.selectorMap[elem.id] = this.selectors[i]; return this.selectors[i]; } } // if we reached here, no available selectors were found, we create one - this.selectors[N] = new svgedit.select.Selector(N, elem); + this.selectors[N] = new svgedit.select.Selector(N, elem, bbox); this.selectorParentGroup.appendChild(this.selectors[N].selectorGroup); this.selectorMap[elem.id] = this.selectors[N]; return this.selectors[N]; @@ -449,12 +455,12 @@ svgedit.select.SelectorManager.prototype.releaseSelector = function(elem) { var i, N = this.selectors.length, sel = this.selectorMap[elem.id]; + if (!sel.locked) { + // TODO(codedread): Ensure this exists in this module. + console.log('WARNING! selector was released but was already unlocked'); + } for (i = 0; i < N; ++i) { if (this.selectors[i] && this.selectors[i] == sel) { - if (sel.locked == false) { - // TODO(codedread): Ensure this exists in this module. - console.log('WARNING! selector was released but was already unlocked'); - } delete this.selectorMap[elem.id]; sel.locked = false; sel.selectedElement = null; diff --git a/vendor/assets/javascripts/svg-edit/editor/svg-editor.html b/vendor/assets/javascripts/svg-edit/editor/svg-editor.html index bd4c35a80..175a41dc9 100644 --- a/vendor/assets/javascripts/svg-edit/editor/svg-editor.html +++ b/vendor/assets/javascripts/svg-edit/editor/svg-editor.html @@ -13,7 +13,7 @@ <link rel="stylesheet" href="spinbtn/JQuerySpinBtn.css" type="text/css"/> <link rel="stylesheet" href="custom.css" type="text/css"/> <!--{if jquery_release}> - <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script> <!{else}--> <script src="jquery.js"></script> <!--{endif}--> @@ -31,6 +31,7 @@ <script src="svgedit.js"></script> <script src="jquery-svg.js"></script> <script src="contextmenu/jquery.contextMenu.js"></script> + <script src="pathseg.js"></script> <script src="browser.js"></script> <script src="svgtransformlist.js"></script> <script src="math.js"></script> @@ -38,10 +39,12 @@ <script src="svgutils.js"></script> <script src="sanitize.js"></script> <script src="history.js"></script> + <script src="historyrecording.js"></script> <script src="coords.js"></script> <script src="recalculate.js"></script> <script src="select.js"></script> <script src="draw.js"></script> + <script src="layer.js"></script> <script src="path.js"></script> <script src="svgcanvas.js"></script> <script src="svg-editor.js"></script> @@ -49,14 +52,14 @@ <script src="contextmenu.js"></script> <!--{endif}--> -<!-- If you do not wish to add extensions by URL, you can load them -by creating the following file and adding by calls to svgEditor.setConfig --> -<script src="config.js"></script> - <!-- always minified scripts --> <script src="jquery-ui/jquery-ui-1.8.17.custom.min.js"></script> <script src="jgraduate/jpicker.js"></script> +<!-- If you do not wish to add extensions by URL, you can load them +by creating the following file and adding by calls to svgEditor.setConfig --> +<script src="config.js"></script> + <!-- feeds --> <link rel="alternate" type="application/atom+xml" title="SVG-edit General Discussion" href="http://groups.google.com/group/svg-edit/feed/atom_v1_0_msgs.xml" /> <link rel="alternate" type="application/atom+xml" title="SVG-edit Updates (Issues/Fixes/Commits)" href="http://code.google.com/feeds/p/svg-edit/updates/basic" /> @@ -161,7 +164,7 @@ <h3 id="layersLabel">Layers</h3> </ul> <p> - <a href="http://svg-edit.googlecode.com/" target="_blank"> + <a href="https://github.com/SVG-Edit/svgedit" target="_blank"> SVG-edit Home Page </a> </p> @@ -210,6 +213,10 @@ <h3 id="layersLabel">Layers</h3> <input id="elem_id" class="attr_changer" data-attr="id" size="10" type="text"/> </label> --> + <label id="classLabel" title="Element class"> + <span>class:</span> + <input id="elem_class" class="attr_changer" data-attr="class" size="10" type="text"/> + </label> </div> <label id="tool_angle" title="Change rotation angle" class="toolset"> diff --git a/vendor/assets/javascripts/svg-edit/editor/svg-editor.js b/vendor/assets/javascripts/svg-edit/editor/svg-editor.js index f2155bcad..a4497521d 100644 --- a/vendor/assets/javascripts/svg-edit/editor/svg-editor.js +++ b/vendor/assets/javascripts/svg-edit/editor/svg-editor.js @@ -91,13 +91,13 @@ TODOS 'ext-markers.js', 'ext-connector.js', 'ext-eyedropper.js', - 'ext-shapes.js', 'ext-imagelib.js', 'ext-grid.js', 'ext-polygon.js', 'ext-star.js', 'ext-panning.js', - 'ext-storage.js' + 'ext-storage.js', + 'ext-itex.js' ], defaultConfig = { // Todo: svgcanvas.js also sets and checks: show_outside_canvas, selectNew; add here? @@ -109,10 +109,15 @@ TODOS opacity: 1 }, initStroke: { - width: 5, + width: 2, color: '000000', // solid black opacity: 1 }, + text: { + stroke_width: 0, + font_size: 24, + font_family: 'serif' + }, initOpacity: 1, colorPickerCSS: null, // Defaults to 'left' with a position equal to that of the fill_color or stroke_color element minus 140, and a 'bottom' equal to 40 initTool: 'select', @@ -529,21 +534,6 @@ TODOS setupCurPrefs(); } }()); - - // For external openers - (function() { - // let the opener know SVG Edit is ready (now that config is set up) - var svgEditorReadyEvent, - w = window.opener; - if (w) { - try { - svgEditorReadyEvent = w.document.createEvent('Event'); - svgEditorReadyEvent.initEvent('svgEditorReady', true, true); - w.document.documentElement.dispatchEvent(svgEditorReadyEvent); - } - catch(e) {} - } - }()); var setIcon = editor.setIcon = function(elem, icon_id, forcedSize) { var icon = (typeof icon_id === 'string') ? $.getSvgIcon(icon_id, true) : icon_id.clone(); @@ -803,6 +793,21 @@ TODOS ui_context = 'toolbars', origSource = '', paintBox = {fill: null, stroke:null}; + + // For external openers + (function() { + // let the opener know SVG Edit is ready (now that config is set up) + var svgEditorReadyEvent, + w = window.opener; + if (w) { + try { + svgEditorReadyEvent = w.document.createEvent('Event'); + svgEditorReadyEvent.initEvent('svgEditorReady', true, true); + w.document.documentElement.dispatchEvent(svgEditorReadyEvent); + } + catch(e) {} + } + }()); // This sets up alternative dialog boxes. They mostly work the same way as // their UI counterparts, expect instead of returning the result, a callback @@ -1527,6 +1532,7 @@ TODOS $('#group_opacity').val(opac_perc); $('#opac_slider').slider('option', 'value', opac_perc); $('#elem_id').val(selectedElement.id); + $('#elem_class').val(selectedElement.getAttribute("class")); } updateToolButtonState(); @@ -1562,7 +1568,7 @@ TODOS $('#blur_slider').slider('option', 'value', blurval); if (svgCanvas.addedNew) { - if (elname === 'image') { + if (elname === 'image' && svgCanvas.getMode() === 'image') { // Prompt for URL if not a data URL if (svgCanvas.getHref(elem).indexOf('data:') !== 0) { promptImgURL(); @@ -1687,6 +1693,7 @@ TODOS if (el_name == 'text') { $('#text_panel').css('display', 'inline'); + $('#tool_font_size').css('display', 'inline'); if (svgCanvas.getItalic()) { $('#tool_italic').addClass('push_button_pressed').removeClass('tool_button'); } else { @@ -1707,7 +1714,7 @@ TODOS }, 100); } } // text - else if (el_name == 'image') { + else if (el_name == 'image' && svgCanvas.getMode() == 'image') { setImageURL(svgCanvas.getHref(elem)); } // image else if (el_name === 'g' || el_name === 'use') { @@ -1828,6 +1835,15 @@ TODOS }); }; + /** + * Test whether an element is a layer or not. + * @param {SVGGElement} elem - The SVGGElement to test. + * @returns {boolean} True if the element is a layer + */ + function isLayer(elem) { + return elem && elem.tagName === 'g' && svgedit.draw.Layer.CLASS_REGEX.test(elem.getAttribute('class')) + } + // called when any element has changed var elementChanged = function(win, elems) { var i, @@ -1839,10 +1855,13 @@ TODOS for (i = 0; i < elems.length; ++i) { var elem = elems[i]; - // if the element changed was the svg, then it could be a resolution change - if (elem && elem.tagName === 'svg') { + var isSvgElem = (elem && elem.tagName === 'svg'); + if (isSvgElem || isLayer(elem)) { populateLayers(); - updateCanvas(); + // if the element changed was the svg, then it could be a resolution change + if (isSvgElem) { + updateCanvas(); + } } // Update selectedElement if element is no longer part of the image. // This occurs for the text elements in Firefox @@ -2171,7 +2190,7 @@ TODOS // var elems = $('.tool_button, .push_button, .tool_button_current, .disabled, .icon_label, #url_notice, #tool_open'); var sel_toscale = '#tools_top .toolset, #editor_panel > *, #history_panel > *,'+ ' #main_button, #tools_left > *, #path_node_panel > *, #multiselected_panel > *,'+ -' #g_panel > *, .tools_flyout'; +' #g_panel > *, #tool_font_size > *, .tools_flyout'; var elems = $(sel_toscale); var scale = 1; @@ -2960,7 +2979,7 @@ TODOS svgCanvas.setSegType($(this).val()); }); - $('#text').keyup(function() { + $('#text').bind("keyup input", function() { svgCanvas.setTextContent(this.value); }); @@ -2991,7 +3010,7 @@ TODOS return false; } - if (attr !== 'id') { + if (attr !== 'id' && attr !== 'class') { if (isNaN(val)) { val = svgCanvas.convertToNum(attr, val); } else if (curConfig.baseUnit !== 'px') { @@ -4493,8 +4512,8 @@ TODOS {sel: '#tool_move_bottom', fn: moveToBottomSelected, evt: 'click', key: 'ctrl+shift+['}, {sel: '#tool_topath', fn: convertToPath, evt: 'click'}, {sel: '#tool_make_link,#tool_make_link_multi', fn: makeHyperlink, evt: 'click'}, - {sel: '#tool_undo', fn: clickUndo, evt: 'click', key: ['Z', true]}, - {sel: '#tool_redo', fn: clickRedo, evt: 'click', key: ['Y', true]}, + {sel: '#tool_undo', fn: clickUndo, evt: 'click'}, + {sel: '#tool_redo', fn: clickRedo, evt: 'click'}, {sel: '#tool_clone,#tool_clone_multi', fn: clickClone, evt: 'click', key: ['D', true]}, {sel: '#tool_group_elements', fn: clickGroup, evt: 'click', key: ['G', true]}, {sel: '#tool_ungroup', fn: clickGroup, evt: 'click'}, @@ -4767,6 +4786,7 @@ TODOS case 'paste_in_place': svgCanvas.pasteElements('in_place'); break; + case 'group': case 'group_elements': svgCanvas.groupSelectedElements(); break; @@ -4904,12 +4924,14 @@ TODOS if (file.type.indexOf('svg') != -1) { reader = new FileReader(); reader.onloadend = function(e) { - svgCanvas.importSvgString(e.target.result, true); + var newElement = svgCanvas.importSvgString(e.target.result, true); svgCanvas.ungroupSelectedElement(); svgCanvas.ungroupSelectedElement(); svgCanvas.groupSelectedElements(); svgCanvas.alignSelectedElements('m', 'page'); svgCanvas.alignSelectedElements('c', 'page'); + // highlight imported element, otherwise we get strange empty selectbox + svgCanvas.selectOnly([newElement]); $('#dialog_box').hide(); }; reader.readAsText(file); @@ -4986,7 +5008,7 @@ TODOS updateCanvas(true); // }); - // var revnums = "svg-editor.js ($Rev: 2875 $) "; + // var revnums = "svg-editor.js ($Rev$) "; // revnums += svgCanvas.getVersion(); // $('#copyright')[0].setAttribute('title', revnums); diff --git a/vendor/assets/javascripts/svg-edit/editor/svgcanvas.js b/vendor/assets/javascripts/svg-edit/editor/svgcanvas.js index b35d067c7..b30a8e4c9 100644 --- a/vendor/assets/javascripts/svg-edit/editor/svgcanvas.js +++ b/vendor/assets/javascripts/svg-edit/editor/svgcanvas.js @@ -13,18 +13,19 @@ // Dependencies: // 1) jQuery -// 2) browser.js -// 3) svgtransformlist.js -// 4) math.js -// 5) units.js -// 6) svgutils.js -// 7) sanitize.js -// 8) history.js -// 9) select.js -// 10) draw.js -// 11) path.js -// 12) coords.js -// 13) recalculate.js +// 2) pathseg.js +// 3) browser.js +// 4) svgtransformlist.js +// 5) math.js +// 6) units.js +// 7) svgutils.js +// 8) sanitize.js +// 9) history.js +// 10) select.js +// 11) draw.js +// 12) path.js +// 13) coords.js +// 14) recalculate.js (function () { @@ -110,9 +111,9 @@ var clearSvgContentElement = canvas.clearSvgContentElement = function() { }).appendTo(svgroot); // TODO: make this string optional and set by the client - var comment = svgdoc.createComment(" Created with SVG-edit - http://svg-edit.googlecode.com/ "); - // Lead to invalid content with Instiki's Sanitizer - // svgcontent.appendChild(comment); + var comment = svgdoc.createComment(" Created with SVG-edit - https://github.com/SVG-Edit/svgedit"); + // Lead to invalid content with Instiki's Sanitizer + // svgcontent.appendChild(comment); }; clearSvgContentElement(); @@ -165,9 +166,9 @@ var all_properties = { all_properties.text = $.extend(true, {}, all_properties.shape); $.extend(all_properties.text, { fill: '#000000', - stroke_width: 0, - font_size: 24, - font_family: 'serif' + stroke_width: curConfig.text.stroke_width, + font_size: curConfig.text.font_size, + font_family: curConfig.text.font_family }); // Current shape style properties @@ -175,7 +176,7 @@ var cur_shape = all_properties.shape; // Array with all the currently selected elements // default size of 1 until it needs to grow bigger -var selectedElements = new Array(1); +var selectedElements = []; // Function: addSvgElementFromJson // Create a new SVG element based on the given object keys/values and add it to the current layer @@ -355,6 +356,15 @@ var addCommandToHistory = function(cmd) { canvas.undoMgr.addCommandToHistory(cmd); }; +/** + * Get a HistoryRecordingService. + * @param {svgedit.history.HistoryRecordingService=} hrService - if exists, return it instead of creating a new service. + * @returns {svgedit.history.HistoryRecordingService} + */ +function historyRecordingService(hrService) { + return hrService ? hrService : new svgedit.history.HistoryRecordingService(canvas.undoMgr); +} + // import from select.js svgedit.select.init(curConfig, { createSVGElement: function(jsonMap) { return canvas.addSvgElementFromJson(jsonMap); }, @@ -472,7 +482,7 @@ var encodableImages = {}, // DOM element for selection rectangle drawn by the user rubberBox = null, - // Array of current BBoxes (still needed?) + // Array of current BBoxes, used in getIntersectionList(). curBBoxes = [], // Object to contain all included extensions @@ -537,42 +547,45 @@ var round = this.round = function(val) { // This method sends back an array or a NodeList full of elements that // intersect the multi-select rubber-band-box on the current_layer only. // -// Since the only browser that supports the SVG DOM getIntersectionList is Opera, -// we need to provide an implementation here. We brute-force it for now. +// We brute-force getIntersectionList for browsers that do not support it (Firefox). // // Reference: // Firefox does not implement getIntersectionList(), see https://bugzilla.mozilla.org/show_bug.cgi?id=501421 -// Webkit does not implement getIntersectionList(), see https://bugs.webkit.org/show_bug.cgi?id=11274 var getIntersectionList = this.getIntersectionList = function(rect) { if (rubberBox == null) { return null; } var parent = current_group || getCurrentDrawing().getCurrentLayer(); - - if (!curBBoxes.length) { - // Cache all bboxes - curBBoxes = getVisibleElementsAndBBoxes(parent); + + var rubberBBox; + if (!rect) { + rubberBBox = rubberBox.getBBox(); + var o, bb = svgcontent.createSVGRect(); + + for (o in rubberBBox) { + bb[o] = rubberBBox[o] / current_zoom; + } + rubberBBox = bb; + } else { + rubberBBox = svgcontent.createSVGRect(rect.x, rect.y, rect.width, rect.height); } var resultList = null; - try { - resultList = parent.getIntersectionList(rect, null); - } catch(e) { } + if (!svgedit.browser.isIE) { + if (typeof(svgroot.getIntersectionList) == 'function') { + // Offset the bbox of the rubber box by the offset of the svgcontent element. + rubberBBox.x += parseInt(svgcontent.getAttribute('x'), 10); + rubberBBox.y += parseInt(svgcontent.getAttribute('y'), 10); + + resultList = svgroot.getIntersectionList(rubberBBox, parent); + } + } if (resultList == null || typeof(resultList.item) != 'function') { resultList = []; - var rubberBBox; - if (!rect) { - rubberBBox = rubberBox.getBBox(); - var o, - bb = {}; - - for (o in rubberBBox) { - bb[o] = rubberBBox[o] / current_zoom; - } - rubberBBox = bb; - - } else { - rubberBBox = rect; + + if (!curBBoxes.length) { + // Cache all bboxes + curBBoxes = getVisibleElementsAndBBoxes(parent); } var i = curBBoxes.length; while (i--) { @@ -582,6 +595,7 @@ var getIntersectionList = this.getIntersectionList = function(rect) { } } } + // addToSelection expects an array, but it's ok to pass a NodeList // because using square-bracket notation is allowed: // http://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html @@ -597,143 +611,9 @@ var getIntersectionList = this.getIntersectionList = function(rect) { // // Returns: // A single bounding box object -getStrokedBBox = this.getStrokedBBox = function(elems) { +var getStrokedBBox = this.getStrokedBBox = function(elems) { if (!elems) {elems = getVisibleElements();} - if (!elems.length) {return false;} - // Make sure the expected BBox is returned if the element is a group - var getCheckedBBox = function(elem) { - - try { - // TODO: Fix issue with rotated groups. Currently they work - // fine in FF, but not in other browsers (same problem mentioned - // in Issue 339 comment #2). - - var bb = svgedit.utilities.getBBox(elem); - var angle = svgedit.utilities.getRotationAngle(elem); - - if ((angle && angle % 90) || - svgedit.math.hasMatrixTransform(svgedit.transformlist.getTransformList(elem))) { - // Accurate way to get BBox of rotated element in Firefox: - // Put element in group and get its BBox - var good_bb = false; - // Get the BBox from the raw path for these elements - var elemNames = ['ellipse', 'path', 'line', 'polyline', 'polygon']; - if (elemNames.indexOf(elem.tagName) >= 0) { - bb = good_bb = canvas.convertToPath(elem, true); - } else if (elem.tagName == 'rect') { - // Look for radius - var rx = elem.getAttribute('rx'); - var ry = elem.getAttribute('ry'); - if (rx || ry) { - bb = good_bb = canvas.convertToPath(elem, true); - } - } - - if (!good_bb) { - // Must use clone else FF freaks out - var clone = elem.cloneNode(true); - var g = document.createElementNS(NS.SVG, 'g'); - var parent = elem.parentNode; - parent.appendChild(g); - g.appendChild(clone); - bb = svgedit.utilities.bboxToObj(g.getBBox()); - parent.removeChild(g); - } - - // Old method: Works by giving the rotated BBox, - // this is (unfortunately) what Opera and Safari do - // natively when getting the BBox of the parent group -// var angle = angle * Math.PI / 180.0; -// var rminx = Number.MAX_VALUE, rminy = Number.MAX_VALUE, -// rmaxx = Number.MIN_VALUE, rmaxy = Number.MIN_VALUE; -// var cx = round(bb.x + bb.width/2), -// cy = round(bb.y + bb.height/2); -// var pts = [ [bb.x - cx, bb.y - cy], -// [bb.x + bb.width - cx, bb.y - cy], -// [bb.x + bb.width - cx, bb.y + bb.height - cy], -// [bb.x - cx, bb.y + bb.height - cy] ]; -// var j = 4; -// while (j--) { -// var x = pts[j][0], -// y = pts[j][1], -// r = Math.sqrt( x*x + y*y ); -// var theta = Math.atan2(y,x) + angle; -// x = round(r * Math.cos(theta) + cx); -// y = round(r * Math.sin(theta) + cy); -// -// // now set the bbox for the shape after it's been rotated -// if (x < rminx) rminx = x; -// if (y < rminy) rminy = y; -// if (x > rmaxx) rmaxx = x; -// if (y > rmaxy) rmaxy = y; -// } -// -// bb.x = rminx; -// bb.y = rminy; -// bb.width = rmaxx - rminx; -// bb.height = rmaxy - rminy; - } - return bb; - } catch(e) { - console.log(elem, e); - return null; - } - }; - - var full_bb; - $.each(elems, function() { - if (full_bb) {return;} - if (!this.parentNode) {return;} - full_bb = getCheckedBBox(this); - }); - - // This shouldn't ever happen... - if (full_bb == null) {return null;} - - // full_bb doesn't include the stoke, so this does no good! -// if (elems.length == 1) return full_bb; - - var max_x = full_bb.x + full_bb.width; - var max_y = full_bb.y + full_bb.height; - var min_x = full_bb.x; - var min_y = full_bb.y; - - // FIXME: same re-creation problem with this function as getCheckedBBox() above - var getOffset = function(elem) { - var sw = elem.getAttribute('stroke-width'); - var offset = 0; - if (elem.getAttribute('stroke') != 'none' && !isNaN(sw)) { - offset += sw/2; - } - return offset; - }; - var bboxes = []; - $.each(elems, function(i, elem) { - var cur_bb = getCheckedBBox(elem); - if (cur_bb) { - var offset = getOffset(elem); - min_x = Math.min(min_x, cur_bb.x - offset); - min_y = Math.min(min_y, cur_bb.y - offset); - bboxes.push(cur_bb); - } - }); - - full_bb.x = min_x; - full_bb.y = min_y; - - $.each(elems, function(i, elem) { - var cur_bb = bboxes[i]; - // ensure that elem is really an element node - if (cur_bb && elem.nodeType == 1) { - var offset = getOffset(elem); - max_x = Math.max(max_x, cur_bb.x + cur_bb.width + offset); - max_y = Math.max(max_y, cur_bb.y + cur_bb.height + offset); - } - }); - - full_bb.width = max_x - min_x; - full_bb.height = max_y - min_y; - return full_bb; + return svgedit.utilities.getStrokedBBox(elems, addSvgElementFromJson, pathActions) }; // Function: getVisibleElements @@ -753,11 +633,9 @@ var getVisibleElements = this.getVisibleElements = function(parent) { var contentElems = []; $(parent).children().each(function(i, elem) { - try { - if (elem.getBBox()) { - contentElems.push(elem); - } - } catch(e) {} + if (elem.getBBox) { + contentElems.push(elem); + } }); return contentElems.reverse(); }; @@ -780,11 +658,9 @@ var getVisibleElementsAndBBoxes = this.getVisibleElementsAndBBoxes = function(pa } var contentElems = []; $(parent).children().each(function(i, elem) { - try { - if (elem.getBBox()) { - contentElems.push({'elem':elem, 'bbox':getStrokedBBox([elem])}); - } - } catch(e) {} + if (elem.getBBox) { + contentElems.push({'elem':elem, 'bbox':getStrokedBBox([elem])}); + } }); return contentElems.reverse(); }; @@ -800,56 +676,6 @@ var groupSvgElem = this.groupSvgElem = function(elem) { $(g).append(elem).data('gsvg', elem)[0].id = getNextId(); }; -// Function: copyElem -// Create a clone of an element, updating its ID and its children's IDs when needed -// -// Parameters: -// el - DOM element to clone -// -// Returns: The cloned element -var copyElem = function(el) { - // manually create a copy of the element - var new_el = document.createElementNS(el.namespaceURI, el.nodeName); - $.each(el.attributes, function(i, attr) { - if (attr.localName != '-moz-math-font-style') { - new_el.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.value); - } - }); - // set the copied element's new id - new_el.removeAttribute('id'); - new_el.id = getNextId(); - - // Opera's "d" value needs to be reset for Opera/Win/non-EN - // Also needed for webkit (else does not keep curved segments on clone) - if (svgedit.browser.isWebkit() && el.nodeName == 'path') { - var fixed_d = pathActions.convertPath(el); - new_el.setAttribute('d', fixed_d); - } - - // now create copies of all children - $.each(el.childNodes, function(i, child) { - switch(child.nodeType) { - case 1: // element node - new_el.appendChild(copyElem(child)); - break; - case 3: // text node - new_el.textContent = child.nodeValue; - break; - default: - break; - } - }); - - if ($(el).data('gsvg')) { - $(new_el).data('gsvg', new_el.firstChild); - } else if ($(el).data('symbol')) { - var ref = $(el).data('symbol'); - $(new_el).data('ref', ref).data('symbol', ref); - } else if (new_el.tagName == 'image') { - preventClickDefault(new_el); - } - return new_el; -}; // Set scope for these functions var getId, getNextId; @@ -1016,10 +842,7 @@ var recalculateAllSelectedDimensions = this.recalculateAllSelectedDimensions = f } }; -// 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']; - + // Debug tool to easily see the current matrix in the browser's console var logMatrix = function(m) { console.log([m.a, m.b, m.c, m.d, m.e, m.f]); @@ -1074,7 +897,9 @@ var addToSelection = this.addToSelection = function(elemsToAdd, showGrips) { var i = elemsToAdd.length; while (i--) { var elem = elemsToAdd[i]; - if (!elem || !svgedit.utilities.getBBox(elem)) {continue;} + if (!elem) {continue;} + var bbox = svgedit.utilities.getBBox(elem); + if (!bbox) {continue;} if (elem.tagName === 'a' && elem.childNodes.length === 1) { // Make "a" element's child be the selected element @@ -1089,7 +914,7 @@ var addToSelection = this.addToSelection = function(elemsToAdd, showGrips) { // only the first selectedBBoxes element is ever used in the codebase these days // if (j == 0) selectedBBoxes[0] = svgedit.utilities.getBBox(elem); j++; - var sel = selectorManager.requestSelector(elem); + var sel = selectorManager.requestSelector(elem, bbox); if (selectedElements.length > 1) { sel.showGrips(false); @@ -1536,7 +1361,7 @@ var getMouseTarget = this.getMouseTarget = function(evt) { } }); setHref(newImage, last_good_img_url); - preventClickDefault(newImage); + svgedit.utilities.preventClickDefault(newImage); break; case 'square': // FIXME: once we create the rect, we lose information that this was a square @@ -1762,22 +1587,22 @@ var getMouseTarget = this.getMouseTarget = function(evt) { // - if newList contains selected, do nothing // - if newList doesn't contain selected, remove it from selected // - for any newList that was not in selectedElements, add it to selected - var elemsToRemove = [], elemsToAdd = [], + var elemsToRemove = selectedElements.slice(), elemsToAdd = [], newList = getIntersectionList(); - len = selectedElements.length; - - for (i = 0; i < len; ++i) { - var ind = newList.indexOf(selectedElements[i]); - if (ind == -1) { - elemsToRemove.push(selectedElements[i]); - } else { - newList[ind] = null; - } - } - + + // For every element in the intersection, add if not present in selectedElements. len = newList.length; for (i = 0; i < len; ++i) { - if (newList[i]) {elemsToAdd.push(newList[i]);} + var intElem = newList[i]; + // Found an element that was not selected before, so we should add it. + if (selectedElements.indexOf(intElem) == -1) { + elemsToAdd.push(intElem); + } + // Found an element that was already selected, so we shouldn't remove it. + var foundInd = elemsToRemove.indexOf(intElem); + if (foundInd != -1) { + elemsToRemove.splice(foundInd, 1) + } } if (elemsToRemove.length > 0) { @@ -1785,7 +1610,7 @@ var getMouseTarget = this.getMouseTarget = function(evt) { } if (elemsToAdd.length > 0) { - addToSelection(elemsToAdd); + canvas.addToSelection(elemsToAdd); } break; @@ -1896,10 +1721,6 @@ var getMouseTarget = this.getMouseTarget = function(evt) { }, 1000); break; case 'line': - // Opera has a problem with suspendRedraw() apparently - var handle = null; - if (!window.opera) {svgroot.suspendRedraw(1000);} - if (curConfig.gridSnapping) { x = svgedit.utilities.snapToGrid(x); y = svgedit.utilities.snapToGrid(y); @@ -1916,7 +1737,6 @@ var getMouseTarget = this.getMouseTarget = function(evt) { shape.setAttributeNS(null, 'x2', x2); shape.setAttributeNS(null, 'y2', y2); - if (!window.opera) {svgroot.unsuspendRedraw(handle);} break; case 'foreignObject': // fall through @@ -1967,9 +1787,6 @@ var getMouseTarget = this.getMouseTarget = function(evt) { c = $(shape).attr(['cx', 'cy']); cx = c.cx; cy = c.cy; - // Opera has a problem with suspendRedraw() apparently - handle = null; - if (!window.opera) {svgroot.suspendRedraw(1000);} if (curConfig.gridSnapping) { x = svgedit.utilities.snapToGrid(x); cx = svgedit.utilities.snapToGrid(cx); @@ -1979,7 +1796,6 @@ var getMouseTarget = this.getMouseTarget = function(evt) { shape.setAttributeNS(null, 'rx', Math.abs(x - cx) ); var ry = Math.abs(evt.shiftKey?(x - cx):(y - cy)); shape.setAttributeNS(null, 'ry', ry ); - if (!window.opera) {svgroot.unsuspendRedraw(handle);} break; case 'fhellipse': case 'fhrect': @@ -2494,14 +2310,6 @@ var getMouseTarget = this.getMouseTarget = function(evt) { }()); -// Function: preventClickDefault -// Prevents default browser click behaviour on the given element -// -// Parameters: -// img - The DOM element to prevent the cilck on -var preventClickDefault = function(img) { - $(img).click(function(e){e.preventDefault();}); -}; // Group: Text edit functions // Functions relating to editing text elements @@ -3825,165 +3633,7 @@ pathActions = canvas.pathActions = function() { if (svgedit.browser.isWebkit()) {resetD(elem);} }, // Convert a path to one with only absolute or relative values - convertPath: function(path, toRel) { - var i; - var segList = path.pathSegList; - var len = segList.numberOfItems; - var curx = 0, cury = 0; - var d = ''; - var last_m = null; - - for (i = 0; i < len; ++i) { - var seg = segList.getItem(i); - // if these properties are not in the segment, set them to zero - var x = seg.x || 0, - y = seg.y || 0, - x1 = seg.x1 || 0, - y1 = seg.y1 || 0, - x2 = seg.x2 || 0, - y2 = seg.y2 || 0; - - var type = seg.pathSegType; - var letter = pathMap[type]['to'+(toRel?'Lower':'Upper')+'Case'](); - - var addToD = function(pnts, more, last) { - var str = ''; - more = more ? ' ' + more.join(' ') : ''; - last = last ? ' ' + svgedit.units.shortFloat(last) : ''; - $.each(pnts, function(i, pnt) { - pnts[i] = svgedit.units.shortFloat(pnt); - }); - d += letter + pnts.join(' ') + more + last; - }; - - switch (type) { - case 1: // z,Z closepath (Z/z) - d += 'z'; - break; - case 12: // absolute horizontal line (H) - x -= curx; - case 13: // relative horizontal line (h) - if (toRel) { - curx += x; - letter = 'l'; - } else { - x += curx; - curx = x; - letter = 'L'; - } - // Convert to "line" for easier editing - addToD([[x, cury]]); - break; - case 14: // absolute vertical line (V) - y -= cury; - case 15: // relative vertical line (v) - if (toRel) { - cury += y; - letter = 'l'; - } else { - y += cury; - cury = y; - letter = 'L'; - } - // Convert to "line" for easier editing - addToD([[curx, y]]); - break; - case 2: // absolute move (M) - case 4: // absolute line (L) - case 18: // absolute smooth quad (T) - x -= curx; - y -= cury; - case 5: // relative line (l) - case 3: // relative move (m) - // If the last segment was a "z", this must be relative to - if (last_m && segList.getItem(i-1).pathSegType === 1 && !toRel) { - curx = last_m[0]; - cury = last_m[1]; - } - - case 19: // relative smooth quad (t) - if (toRel) { - curx += x; - cury += y; - } else { - x += curx; - y += cury; - curx = x; - cury = y; - } - if (type === 3) {last_m = [curx, cury];} - - addToD([[x, y]]); - break; - case 6: // absolute cubic (C) - x -= curx; x1 -= curx; x2 -= curx; - y -= cury; y1 -= cury; y2 -= cury; - case 7: // relative cubic (c) - if (toRel) { - curx += x; - cury += y; - } else { - x += curx; x1 += curx; x2 += curx; - y += cury; y1 += cury; y2 += cury; - curx = x; - cury = y; - } - addToD([[x1, y1], [x2, y2], [x, y]]); - break; - case 8: // absolute quad (Q) - x -= curx; x1 -= curx; - y -= cury; y1 -= cury; - case 9: // relative quad (q) - if (toRel) { - curx += x; - cury += y; - } else { - x += curx; x1 += curx; - y += cury; y1 += cury; - curx = x; - cury = y; - } - addToD([[x1, y1],[x, y]]); - break; - case 10: // absolute elliptical arc (A) - x -= curx; - y -= cury; - case 11: // relative elliptical arc (a) - if (toRel) { - curx += x; - cury += y; - } else { - x += curx; - y += cury; - curx = x; - cury = y; - } - addToD([[seg.r1, seg.r2]], [ - seg.angle, - (seg.largeArcFlag ? 1 : 0), - (seg.sweepFlag ? 1 : 0) - ], [x, y] - ); - break; - case 16: // absolute smooth cubic (S) - x -= curx; x2 -= curx; - y -= cury; y2 -= cury; - case 17: // relative smooth cubic (s) - if (toRel) { - curx += x; - cury += y; - } else { - x += curx; x2 += curx; - y += cury; y2 += cury; - curx = x; - cury = y; - } - addToD([[x2, y2],[x, y]]); - break; - } // switch on path segment type - } // for each segment - return d; - } + convertPath: svgedit.utilities.convertPath }; }(); // end pathActions @@ -4817,7 +4467,7 @@ this.setSvgString = function(xmlString) { // change image href vals if possible content.find('image').each(function() { var image = this; - preventClickDefault(image); + svgedit.utilities.preventClickDefault(image); var val = getHref(this); if (val) { if (val.indexOf('data:') === 0) { @@ -4960,7 +4610,7 @@ this.setSvgString = function(xmlString) { // xmlString - The SVG as XML text. // // Returns: -// This function returns false if the import was unsuccessful, true otherwise. +// This function returns null if the import was unsuccessful, or the element otherwise. // TODO: // * properly handle if namespace is introduced by imported content (must add to svgcontent // and update all prefixes in the imported node) @@ -5081,10 +4731,11 @@ this.importSvgString = function(xmlString) { } catch(e) { console.log(e); - return false; + return null; } - return true; + // we want to return the element so we can automatically select it + return use_el; }; // TODO(codedread): Move all layer/context functions in draw.js @@ -5106,44 +4757,25 @@ var identifyLayers = canvas.identifyLayers = function() { // // Parameters: // name - The given name -this.createLayer = function(name) { - var batchCmd = new svgedit.history.BatchCommand('Create Layer'); - var new_layer = getCurrentDrawing().createLayer(name); - batchCmd.addSubCommand(new svgedit.history.InsertElementCommand(new_layer)); - addCommandToHistory(batchCmd); +this.createLayer = function(name, hrService) { + var new_layer = getCurrentDrawing().createLayer(name, historyRecordingService(hrService)); clearSelection(); call('changed', [new_layer]); }; -// Function: cloneLayer -// Creates a new top-level layer in the drawing with the given name, copies all the current layer's contents -// to it, and then clears the selection. This function then calls the 'changed' handler. -// This is an undoable action. -// -// Parameters: -// name - The given name -this.cloneLayer = function(name) { - var batchCmd = new svgedit.history.BatchCommand('Duplicate Layer'); - 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); - var current_layer = getCurrentDrawing().getCurrentLayer(); - $(current_layer).after(new_layer); - var childs = current_layer.childNodes; - var i; - for (i = 0; i < childs.length; i++) { - var ch = childs[i]; - if (ch.localName == 'title') {continue;} - new_layer.appendChild(copyElem(ch)); - } - - clearSelection(); - identifyLayers(); +/** + * Creates a new top-level layer in the drawing with the given name, copies all the current layer's contents + * to it, and then clears the selection. This function then calls the 'changed' handler. + * This is an undoable action. + * @param {string} name - The given name. If the layer name exists, a new name will be generated. + * @param {svgedit.history.HistoryRecordingService} hrService - History recording service + */ +this.cloneLayer = function(name, hrService) { + // Clone the current layer and make the cloned layer the new current layer + var new_layer = getCurrentDrawing().cloneLayer(name, historyRecordingService(hrService)); - batchCmd.addSubCommand(new svgedit.history.InsertElementCommand(new_layer)); - addCommandToHistory(batchCmd); - canvas.setCurrentLayer(name); + clearSelection(); + leaveContext(); call('changed', [new_layer]); }; @@ -5195,39 +4827,14 @@ this.setCurrentLayer = function(name) { // Returns: // true if the rename succeeded, false otherwise. this.renameCurrentLayer = function(newname) { - var i; var drawing = getCurrentDrawing(); - if (drawing.current_layer) { - var oldLayer = drawing.current_layer; - // setCurrentLayer will return false if the name doesn't already exist - // this means we are free to rename our oldLayer - if (!canvas.setCurrentLayer(newname)) { - var batchCmd = new svgedit.history.BatchCommand('Rename Layer'); - // find the index of the layer - for (i = 0; i < drawing.getNumLayers(); ++i) { - if (drawing.all_layers[i][1] == oldLayer) {break;} - } - var oldname = drawing.getLayerName(i); - drawing.all_layers[i][0] = svgedit.utilities.toXml(newname); - - // now change the underlying title element contents - var len = oldLayer.childNodes.length; - for (i = 0; i < len; ++i) { - var child = oldLayer.childNodes.item(i); - // found the <title> element, now append all the - if (child && child.tagName == 'title') { - // wipe out old name - while (child.firstChild) { child.removeChild(child.firstChild); } - child.textContent = newname; - - batchCmd.addSubCommand(new svgedit.history.ChangeElementCommand(child, {'#text':oldname})); - addCommandToHistory(batchCmd); - call('changed', [oldLayer]); - return true; - } - } + var layer = drawing.getCurrentLayer(); + if (layer) { + var result = drawing.setCurrentLayerName(newname, historyRecordingService()); + if (result) { + call('changed', [layer]); + return true; } - drawing.current_layer = oldLayer; } return false; }; @@ -5245,36 +4852,11 @@ this.renameCurrentLayer = function(newname) { // true if the current layer position was changed, false otherwise. this.setCurrentLayerPosition = function(newpos) { var oldpos, drawing = getCurrentDrawing(); - if (drawing.current_layer && newpos >= 0 && newpos < drawing.getNumLayers()) { - for (oldpos = 0; oldpos < drawing.getNumLayers(); ++oldpos) { - if (drawing.all_layers[oldpos][1] == drawing.current_layer) {break;} - } - // some unknown error condition (current_layer not in all_layers) - if (oldpos == drawing.getNumLayers()) { return false; } - - if (oldpos != newpos) { - // if our new position is below us, we need to insert before the node after newpos - var refLayer = null; - var oldNextSibling = drawing.current_layer.nextSibling; - if (newpos > oldpos ) { - if (newpos < drawing.getNumLayers()-1) { - refLayer = drawing.all_layers[newpos+1][1]; - } - } - // if our new position is above us, we need to insert before the node at newpos - else { - refLayer = drawing.all_layers[newpos][1]; - } - svgcontent.insertBefore(drawing.current_layer, refLayer); - addCommandToHistory(new svgedit.history.MoveElementCommand(drawing.current_layer, oldNextSibling, svgcontent)); - - identifyLayers(); - canvas.setCurrentLayer(drawing.getLayerName(newpos)); - - return true; - } + var result = drawing.setCurrentLayerPosition(newpos); + if (result) { + addCommandToHistory(new svgedit.history.MoveElementCommand(result.currentGroup, result.oldNextSibling, svgcontent)); + return true; } - return false; }; @@ -5319,14 +4901,8 @@ this.setLayerVisibility = function(layername, bVisible) { this.moveSelectedToLayer = function(layername) { // find the layer var i; - var layer = null; var drawing = getCurrentDrawing(); - for (i = 0; i < drawing.getNumLayers(); ++i) { - if (drawing.getLayerName(i) == layername) { - layer = drawing.all_layers[i][1]; - break; - } - } + var layer = drawing.getLayerByName(layername); if (!layer) {return false;} var batchCmd = new svgedit.history.BatchCommand('Move Elements to Layer'); @@ -5349,57 +4925,19 @@ this.moveSelectedToLayer = function(layername) { return true; }; -this.mergeLayer = function(skipHistory) { - var batchCmd = new svgedit.history.BatchCommand('Merge Layer'); - var drawing = getCurrentDrawing(); - var prev = $(drawing.current_layer).prev()[0]; - if (!prev) {return;} - var childs = drawing.current_layer.childNodes; - var len = childs.length; - var layerNextSibling = drawing.current_layer.nextSibling; - batchCmd.addSubCommand(new svgedit.history.RemoveElementCommand(drawing.current_layer, layerNextSibling, svgcontent)); - - while (drawing.current_layer.firstChild) { - var ch = drawing.current_layer.firstChild; - if (ch.localName == 'title') { - var chNextSibling = ch.nextSibling; - batchCmd.addSubCommand(new svgedit.history.RemoveElementCommand(ch, chNextSibling, drawing.current_layer)); - drawing.current_layer.removeChild(ch); - continue; - } - var oldNextSibling = ch.nextSibling; - prev.appendChild(ch); - batchCmd.addSubCommand(new svgedit.history.MoveElementCommand(ch, oldNextSibling, drawing.current_layer)); - } - - // Remove current layer - svgcontent.removeChild(drawing.current_layer); - - if (!skipHistory) { - clearSelection(); - identifyLayers(); - call('changed', [svgcontent]); - - addCommandToHistory(batchCmd); - } - - drawing.current_layer = prev; - return batchCmd; +this.mergeLayer = function(hrService) { + getCurrentDrawing().mergeLayer(historyRecordingService(hrService)); + clearSelection(); + leaveContext(); + call('changed', [svgcontent]); }; -this.mergeAllLayers = function() { - var batchCmd = new svgedit.history.BatchCommand('Merge all Layers'); - var drawing = getCurrentDrawing(); - drawing.current_layer = drawing.all_layers[drawing.getNumLayers()-1][1]; - while ($(svgcontent).children('g').length > 1) { - batchCmd.addSubCommand(canvas.mergeLayer(true)); - } - +this.mergeAllLayers = function(hrService) { + getCurrentDrawing().mergeAllLayers(historyRecordingService(hrService)); clearSelection(); - identifyLayers(); + leaveContext(); call('changed', [svgcontent]); - addCommandToHistory(batchCmd); }; // Function: leaveContext @@ -5516,10 +5054,15 @@ var getResolution = this.getResolution = function() { // Returns the current zoom level this.getZoom = function(){return current_zoom;}; +// Function: getSnapToGrid +// Returns the current snap to grid setting +this.getSnapToGrid = function(){return curConfig.gridSnapping;}; + + // Function: getVersion // Returns a string which describes the revision number of SvgCanvas. this.getVersion = function() { - return 'svgcanvas.js ($Rev: 2875 $)'; + return 'svgcanvas.js ($Rev$)'; }; // Function: setUiStrings @@ -5681,7 +5224,6 @@ this.setResolution = function(x, y) { } } if (x != w || y != h) { - var handle = svgroot.suspendRedraw(1000); if (!batchCmd) { batchCmd = new svgedit.history.BatchCommand('Change Image Dimensions'); } @@ -5700,7 +5242,6 @@ this.setResolution = function(x, y) { batchCmd.addSubCommand(new svgedit.history.ChangeElementCommand(svgcontent, {'viewBox': ['0 0', w, h].join(' ')})); addCommandToHistory(batchCmd); - svgroot.unsuspendRedraw(handle); call('changed', [svgcontent]); } return true; @@ -6584,179 +6125,29 @@ this.setSegType = function(new_type) { this.convertToPath = function(elem, getBBox) { if (elem == null) { var elems = selectedElements; - $.each(selectedElements, function(i, elem) { + $.each(elems, function(i, elem) { if (elem) {canvas.convertToPath(elem);} }); return; } - - if (!getBBox) { - var batchCmd = new svgedit.history.BatchCommand('Convert element to Path'); - } - - var attrs = getBBox?{}:{ - 'fill': cur_shape.fill, - 'fill-opacity': cur_shape.fill_opacity, - 'stroke': cur_shape.stroke, - 'stroke-width': cur_shape.stroke_width, - 'stroke-dasharray': cur_shape.stroke_dasharray, - 'stroke-linejoin': cur_shape.stroke_linejoin, - 'stroke-linecap': cur_shape.stroke_linecap, - 'stroke-opacity': cur_shape.stroke_opacity, - 'opacity': cur_shape.opacity, - 'visibility':'hidden' - }; - - // any attribute on the element not covered by the above - // TODO: make this list global so that we can properly maintain it - // TODO: what about @transform, @clip-rule, @fill-rule, etc? - $.each(['marker-start', 'marker-end', 'marker-mid', 'filter', 'clip-path'], function() { - if (elem.getAttribute(this)) { - attrs[this] = elem.getAttribute(this); - } - }); - - var path = addSvgElementFromJson({ - 'element': 'path', - 'attr': attrs - }); - - var eltrans = elem.getAttribute('transform'); - if (eltrans) { - path.setAttribute('transform', eltrans); - } - - var id = elem.id; - var parent = elem.parentNode; - if (elem.nextSibling) { - parent.insertBefore(path, elem); + if (getBBox) { + return svgedit.utilities.getBBoxOfElementAsPath(elem, addSvgElementFromJson, pathActions) } else { - parent.appendChild(path); - } - - var d = ''; - - var joinSegs = function(segs) { - $.each(segs, function(j, seg) { - var i; - var l = seg[0], pts = seg[1]; - d += l; - for (i = 0; i < pts.length; i+=2) { - d += (pts[i] +','+pts[i+1]) + ' '; - } - }); - }; - - // Possibly the cubed root of 6, but 1.81 works best - var num = 1.81; - var a, rx; - switch (elem.tagName) { - case 'ellipse': - case 'circle': - a = $(elem).attr(['rx', 'ry', 'cx', 'cy']); - var cx = a.cx, cy = a.cy; - rx = a.rx; - ry = a.ry; - if (elem.tagName == 'circle') { - rx = ry = $(elem).attr('r'); - } - - joinSegs([ - ['M',[(cx-rx),(cy)]], - ['C',[(cx-rx),(cy-ry/num), (cx-rx/num),(cy-ry), (cx),(cy-ry)]], - ['C',[(cx+rx/num),(cy-ry), (cx+rx),(cy-ry/num), (cx+rx),(cy)]], - ['C',[(cx+rx),(cy+ry/num), (cx+rx/num),(cy+ry), (cx),(cy+ry)]], - ['C',[(cx-rx/num),(cy+ry), (cx-rx),(cy+ry/num), (cx-rx),(cy)]], - ['Z',[]] - ]); - break; - case 'path': - d = elem.getAttribute('d'); - break; - case 'line': - a = $(elem).attr(['x1', 'y1', 'x2', 'y2']); - d = 'M'+a.x1+','+a.y1+'L'+a.x2+','+a.y2; - break; - case 'polyline': - case 'polygon': - d = 'M' + elem.getAttribute('points'); - break; - case 'rect': - var r = $(elem).attr(['rx', 'ry']); - rx = r.rx; - ry = r.ry; - var b = elem.getBBox(); - var x = b.x, y = b.y, w = b.width, h = b.height; - num = 4 - num; // Why? Because! - - if (!rx && !ry) { - // Regular rect - joinSegs([ - ['M',[x, y]], - ['L',[x+w, y]], - ['L',[x+w, y+h]], - ['L',[x, y+h]], - ['L',[x, y]], - ['Z',[]] - ]); - } else { - joinSegs([ - ['M',[x, y+ry]], - ['C',[x, y+ry/num, x+rx/num, y, x+rx, y]], - ['L',[x+w-rx, y]], - ['C',[x+w-rx/num, y, x+w, y+ry/num, x+w, y+ry]], - ['L',[x+w, y+h-ry]], - ['C',[x+w, y+h-ry/num, x+w-rx/num, y+h, x+w-rx, y+h]], - ['L',[x+rx, y+h]], - ['C',[x+rx/num, y+h, x, y+h-ry/num, x, y+h-ry]], - ['L',[x, y+ry]], - ['Z',[]] - ]); - } - break; - default: - path.parentNode.removeChild(path); - break; - } - - if (d) { - path.setAttribute('d', d); - } - - if (!getBBox) { - // Replace the current element with the converted one - - // Reorient if it has a matrix - if (eltrans) { - var tlist = svgedit.transformlist.getTransformList(path); - if (svgedit.math.hasMatrixTransform(tlist)) { - pathActions.resetOrientation(path); - } - } - - var nextSibling = elem.nextSibling; - batchCmd.addSubCommand(new svgedit.history.RemoveElementCommand(elem, nextSibling, parent)); - batchCmd.addSubCommand(new svgedit.history.InsertElementCommand(path)); - - clearSelection(); - elem.parentNode.removeChild(elem); - path.setAttribute('id', id); - path.removeAttribute('visibility'); - addToSelection([path], true); - - addCommandToHistory(batchCmd); - - } else { - // Get the correct BBox of the new path, then discard it - pathActions.resetOrientation(path); - var bb = false; - try { - bb = path.getBBox(); - } catch(e) { - // Firefox fails - } - path.parentNode.removeChild(path); - return bb; + // TODO: Why is this applying attributes from cur_shape, then inside utilities.convertToPath it's pulling addition attributes from elem? + // TODO: If convertToPath is called with one elem, cur_shape and elem are probably the same; but calling with multiple is a bug or cool feature. + var attrs = { + 'fill': cur_shape.fill, + 'fill-opacity': cur_shape.fill_opacity, + 'stroke': cur_shape.stroke, + 'stroke-width': cur_shape.stroke_width, + 'stroke-dasharray': cur_shape.stroke_dasharray, + 'stroke-linejoin': cur_shape.stroke_linejoin, + 'stroke-linecap': cur_shape.stroke_linecap, + 'stroke-opacity': cur_shape.stroke_opacity, + 'opacity': cur_shape.opacity, + 'visibility':'hidden' + }; + return svgedit.utilities.convertToPath(elem, attrs, addSvgElementFromJson, pathActions, clearSelection, addToSelection, svgedit.history, addCommandToHistory); } }; @@ -6770,7 +6161,6 @@ this.convertToPath = function(elem, getBBox) { // newValue - String or number with the new attribute value // elems - The DOM elements to apply the change to var changeSelectedAttributeNoUndo = function(attr, newValue, elems) { - var handle = svgroot.suspendRedraw(1000); if (current_mode == 'pathedit') { // Editing node pathActions.moveNode(attr, newValue); @@ -6883,7 +6273,6 @@ var changeSelectedAttributeNoUndo = function(attr, newValue, elems) { } } // if oldValue != newValue } // for each elem - svgroot.unsuspendRedraw(handle); }; // Function: changeSelectedAttribute @@ -6996,20 +6385,22 @@ this.pasteElements = function(type, x, y) { var pasted = []; var batchCmd = new svgedit.history.BatchCommand('Paste elements'); - - // Move elements to lastClickPoint + var drawing = getCurrentDrawing(); + // Move elements to lastClickPoint while (len--) { var elem = cb[len]; if (!elem) {continue;} - var copy = copyElem(elem); + var copy = drawing.copyElem(elem); // See if elem with elem ID is in the DOM already if (!svgedit.utilities.getElem(elem.id)) {copy.id = elem.id;} pasted.push(copy); - (current_group || getCurrentDrawing().getCurrentLayer()).appendChild(copy); + (current_group || drawing.getCurrentLayer()).appendChild(copy); batchCmd.addSubCommand(new svgedit.history.InsertElementCommand(copy)); + + restoreRefElems(copy); } selectOnly(pasted); @@ -7130,6 +6521,7 @@ var pushGroupProperties = this.pushGroupProperties = function(g, undoable) { var gattrs = $(g).attr(['filter', 'opacity']); var gfilter, gblur, changes; + var drawing = getCurrentDrawing(); for (i = 0; i < len; i++) { var elem = children[i]; @@ -7160,7 +6552,7 @@ var pushGroupProperties = this.pushGroupProperties = function(g, undoable) { gfilter = svgedit.utilities.getRefElem(gattrs.filter); } else { // Clone the group's filter - gfilter = copyElem(gfilter); + gfilter = drawing.copyElem(gfilter); svgedit.utilities.findDefs().appendChild(gfilter); } } else { @@ -7539,11 +6931,12 @@ this.cloneSelectedElements = function(x, y) { this.clearSelection(true); // note that we loop in the reverse way because of the way elements are added // to the selectedElements array (top-first) + var drawing = getCurrentDrawing(); i = copiedElements.length; while (i--) { // clone each element and replace it within copiedElements - elem = copiedElements[i] = copyElem(copiedElements[i]); - (current_group || getCurrentDrawing().getCurrentLayer()).appendChild(elem); + elem = copiedElements[i] = drawing.copyElem(copiedElements[i]); + (current_group || drawing.getCurrentLayer()).appendChild(elem); batchCmd.addSubCommand(new svgedit.history.InsertElementCommand(elem)); } @@ -7782,7 +7175,7 @@ this.getPrivateMethods = function() { BatchCommand: BatchCommand, call: call, ChangeElementCommand: ChangeElementCommand, - copyElem: copyElem, + copyElem: function(elem) {return getCurrentDrawing().copyElem(elem)}, ffClone: ffClone, findDefs: findDefs, findDuplicateGradient: findDuplicateGradient, @@ -7800,7 +7193,7 @@ this.getPrivateMethods = function() { logMatrix: logMatrix, matrixMultiply: matrixMultiply, MoveElementCommand: MoveElementCommand, - preventClickDefault: preventClickDefault, + preventClickDefault: svgedit.utilities.preventClickDefault, recalculateAllSelectedDimensions: recalculateAllSelectedDimensions, recalculateDimensions: recalculateDimensions, remapElement: remapElement, diff --git a/vendor/assets/javascripts/svg-edit/editor/svgutils.js b/vendor/assets/javascripts/svg-edit/editor/svgutils.js index 9e7a1f755..3bdd4327f 100644 --- a/vendor/assets/javascripts/svg-edit/editor/svgutils.js +++ b/vendor/assets/javascripts/svg-edit/editor/svgutils.js @@ -11,9 +11,10 @@ // Dependencies: // 1) jQuery -// 2) browser.js -// 3) svgtransformlist.js -// 4) units.js +// 2) pathseg.js +// 3) browser.js +// 4) svgtransformlist.js +// 5) units.js (function(undef) {'use strict'; @@ -475,14 +476,14 @@ svgedit.utilities.getBBox = function(elem) { ret = selected.getBBox(); selected.textContent = ''; } else { - try { ret = selected.getBBox();} catch(e){} + if (selected.getBBox) { ret = selected.getBBox(); } } break; case 'path': if(!svgedit.browser.supportsPathBBox()) { ret = svgedit.utilities.getPathBBox(selected); } else { - try { ret = selected.getBBox();} catch(e2){} + if (selected.getBBox) { ret = selected.getBBox(); } } break; case 'g': @@ -499,27 +500,23 @@ svgedit.utilities.getBBox = function(elem) { // This is resolved in later versions of webkit, perhaps we should // have a featured detection for correct 'use' behavior? // —————————— - //if(!svgedit.browser.isWebkit()) { + if(!svgedit.browser.isWebkit()) { var bb = {}; bb.width = ret.width; bb.height = ret.height; bb.x = ret.x + parseFloat(selected.getAttribute('x')||0); bb.y = ret.y + parseFloat(selected.getAttribute('y')||0); ret = bb; - //} + } } else if(~visElems_arr.indexOf(elname)) { - try { ret = selected.getBBox();} - catch(e3) { + if (selected) { ret = selected.getBBox(); } + else { // Check if element is child of a foreignObject var fo = $(selected).closest('foreignObject'); - if(fo.length) { - try { + if (fo.length) { + if (fo[0].getBBox) { ret = fo[0].getBBox(); - } catch(e4) { - ret = null; } - } else { - ret = null; } } } @@ -532,20 +529,436 @@ svgedit.utilities.getBBox = function(elem) { return ret; }; -// Function: svgedit.utilities.getRotationAngle -// Get the rotation angle of the given/selected DOM element +// Function: getPathDFromSegments +// Create a path 'd' attribute from path segments. +// Each segment is an array of the form: [singleChar, [x,y, x,y, ...]] // // Parameters: -// elem - Optional DOM element to get the angle for +// pathSegments - An array of path segments to be converted +// +// Returns: +// The converted path d attribute. +svgedit.utilities.getPathDFromSegments = function(pathSegments) { + var d = ''; + + $.each(pathSegments, function(j, seg) { + var i; + var pts = seg[1]; + d += seg[0]; + for (i = 0; i < pts.length; i+=2) { + d += (pts[i] +','+pts[i+1]) + ' '; + } + }); + + return d; +}; + +// Function: getPathDFromElement +// Make a path 'd' attribute from a simple SVG element shape. +// +// Parameters: +// elem - The element to be converted +// +// Returns: +// The path d attribute or undefined if the element type is unknown. +svgedit.utilities.getPathDFromElement = function(elem) { + + // Possibly the cubed root of 6, but 1.81 works best + var num = 1.81; + var d, a, rx, ry; + switch (elem.tagName) { + case 'ellipse': + case 'circle': + a = $(elem).attr(['rx', 'ry', 'cx', 'cy']); + var cx = a.cx, cy = a.cy; + rx = a.rx; + ry = a.ry; + if (elem.tagName == 'circle') { + rx = ry = $(elem).attr('r'); + } + + d = svgedit.utilities.getPathDFromSegments([ + ['M',[(cx-rx),(cy)]], + ['C',[(cx-rx),(cy-ry/num), (cx-rx/num),(cy-ry), (cx),(cy-ry)]], + ['C',[(cx+rx/num),(cy-ry), (cx+rx),(cy-ry/num), (cx+rx),(cy)]], + ['C',[(cx+rx),(cy+ry/num), (cx+rx/num),(cy+ry), (cx),(cy+ry)]], + ['C',[(cx-rx/num),(cy+ry), (cx-rx),(cy+ry/num), (cx-rx),(cy)]], + ['Z',[]] + ]); + break; + case 'path': + d = elem.getAttribute('d'); + break; + case 'line': + a = $(elem).attr(['x1', 'y1', 'x2', 'y2']); + d = 'M'+a.x1+','+a.y1+'L'+a.x2+','+a.y2; + break; + case 'polyline': + d = 'M' + elem.getAttribute('points'); + break; + case 'polygon': + d = 'M' + elem.getAttribute('points') + ' Z'; + break; + case 'rect': + var r = $(elem).attr(['rx', 'ry']); + rx = r.rx; + ry = r.ry; + var b = elem.getBBox(); + var x = b.x, y = b.y, w = b.width, h = b.height; + num = 4 - num; // Why? Because! + + if (!rx && !ry) { + // Regular rect + d = svgedit.utilities.getPathDFromSegments([ + ['M',[x, y]], + ['L',[x+w, y]], + ['L',[x+w, y+h]], + ['L',[x, y+h]], + ['L',[x, y]], + ['Z',[]] + ]); + } else { + d = svgedit.utilities.getPathDFromSegments([ + ['M',[x, y+ry]], + ['C',[x, y+ry/num, x+rx/num, y, x+rx, y]], + ['L',[x+w-rx, y]], + ['C',[x+w-rx/num, y, x+w, y+ry/num, x+w, y+ry]], + ['L',[x+w, y+h-ry]], + ['C',[x+w, y+h-ry/num, x+w-rx/num, y+h, x+w-rx, y+h]], + ['L',[x+rx, y+h]], + ['C',[x+rx/num, y+h, x, y+h-ry/num, x, y+h-ry]], + ['L',[x, y+ry]], + ['Z',[]] + ]); + } + break; + default: + break; + } + + return d; + +}; + +// Function: getExtraAttributesForConvertToPath +// Get a set of attributes from an element that is useful for convertToPath. +// +// Parameters: +// elem - The element to be probed +// +// Returns: +// An object with attributes. +svgedit.utilities.getExtraAttributesForConvertToPath = function(elem) { + var attrs = {} ; + // TODO: make this list global so that we can properly maintain it + // TODO: what about @transform, @clip-rule, @fill-rule, etc? + $.each(['marker-start', 'marker-end', 'marker-mid', 'filter', 'clip-path'], function() { + var a = elem.getAttribute(this); + if (a) { + attrs[this] = a; + } + }); + return attrs; +}; + +// Function: getBBoxOfElementAsPath +// Get the BBox of an element-as-path +// +// Parameters: +// elem - The DOM element to be probed +// addSvgElementFromJson - Function to add the path element to the current layer. See canvas.addSvgElementFromJson +// pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions. +// +// Returns: +// The resulting path's bounding box object. +svgedit.utilities.getBBoxOfElementAsPath = function(elem, addSvgElementFromJson, pathActions) { + + var path = addSvgElementFromJson({ + 'element': 'path', + 'attr': svgedit.utilities.getExtraAttributesForConvertToPath(elem) + }); + + var eltrans = elem.getAttribute('transform'); + if (eltrans) { + path.setAttribute('transform', eltrans); + } + + var parent = elem.parentNode; + if (elem.nextSibling) { + parent.insertBefore(path, elem); + } else { + parent.appendChild(path); + } + + var d = svgedit.utilities.getPathDFromElement(elem); + if (d) + path.setAttribute('d', d); + else + path.parentNode.removeChild(path); + + // Get the correct BBox of the new path, then discard it + pathActions.resetOrientation(path); + var bb = false; + try { + bb = path.getBBox(); + } catch(e) { + // Firefox fails + } + path.parentNode.removeChild(path); + return bb; +}; + +// Function: convertToPath +// Convert selected element to a path. +// +// Parameters: +// elem - The DOM element to be converted +// attrs - Apply attributes to new path. see canvas.convertToPath +// addSvgElementFromJson - Function to add the path element to the current layer. See canvas.addSvgElementFromJson +// pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions. +// clearSelection - see canvas.clearSelection +// addToSelection - see canvas.addToSelection +// history - see svgedit.history +// addCommandToHistory - see canvas.addCommandToHistory +// +// Returns: +// The converted path element or null if the DOM element was not recognized. +svgedit.utilities.convertToPath = function(elem, attrs, addSvgElementFromJson, pathActions, clearSelection, addToSelection, history, addCommandToHistory) { + + var batchCmd = new history.BatchCommand('Convert element to Path'); + + // Any attribute on the element not covered by the passed-in attributes + attrs = $.extend({}, attrs, svgedit.utilities.getExtraAttributesForConvertToPath(elem)); + + var path = addSvgElementFromJson({ + 'element': 'path', + 'attr': attrs + }); + + var eltrans = elem.getAttribute('transform'); + if (eltrans) { + path.setAttribute('transform', eltrans); + } + + var id = elem.id; + var parent = elem.parentNode; + if (elem.nextSibling) { + parent.insertBefore(path, elem); + } else { + parent.appendChild(path); + } + + var d = svgedit.utilities.getPathDFromElement(elem); + if (d) { + path.setAttribute('d', d); + + // Replace the current element with the converted one + + // Reorient if it has a matrix + if (eltrans) { + var tlist = svgedit.transformlist.getTransformList(path); + if (svgedit.math.hasMatrixTransform(tlist)) { + pathActions.resetOrientation(path); + } + } + + var nextSibling = elem.nextSibling; + batchCmd.addSubCommand(new history.RemoveElementCommand(elem, nextSibling, parent)); + batchCmd.addSubCommand(new history.InsertElementCommand(path)); + + clearSelection(); + elem.parentNode.removeChild(elem); + path.setAttribute('id', id); + path.removeAttribute('visibility'); + addToSelection([path], true); + + addCommandToHistory(batchCmd); + + return path; + } else { + // the elem.tagName was not recognized, so no "d" attribute. Remove it, so we've haven't changed anything. + path.parentNode.removeChild(path); + return null; + } + +}; + +// Function: bBoxCanBeOptimizedOverNativeGetBBox +// Can the bbox be optimized over the native getBBox? The optimized bbox is the same as the native getBBox when +// the rotation angle is a multiple of 90 degrees and there are no complex transforms. +// Getting an optimized bbox can be dramatically slower, so we want to make sure it's worth it. +// +// The best example for this is a circle rotate 45 degrees. The circle doesn't get wider or taller when rotated +// about it's center. +// +// The standard, unoptimized technique gets the native bbox of the circle, rotates the box 45 degrees, uses +// that width and height, and applies any transforms to get the final bbox. This means the calculated bbox +// is much wider than the original circle. If the angle had been 0, 90, 180, etc. both techniques render the +// same bbox. +// +// The optimization is not needed if the rotation is a multiple 90 degrees. The default technique is to call +// getBBox then apply the angle and any transforms. +// +// Parameters: +// angle - The rotation angle in degrees +// hasMatrixTransform - True if there is a matrix transform +// +// Returns: +// True if the bbox can be optimized. +function bBoxCanBeOptimizedOverNativeGetBBox(angle, hasMatrixTransform) { + var angleModulo90 = angle % 90; + var closeTo90 = angleModulo90 < -89.99 || angleModulo90 > 89.99; + var closeTo0 = angleModulo90 > -0.001 && angleModulo90 < 0.001; + return hasMatrixTransform || ! (closeTo0 || closeTo90); +} + +// Function: getBBoxWithTransform +// Get bounding box that includes any transforms. +// +// Parameters: +// elem - The DOM element to be converted +// addSvgElementFromJson - Function to add the path element to the current layer. See canvas.addSvgElementFromJson +// pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions. +// +// Returns: +// A single bounding box object +svgedit.utilities.getBBoxWithTransform = function(elem, addSvgElementFromJson, pathActions) { + // TODO: Fix issue with rotated groups. Currently they work + // fine in FF, but not in other browsers (same problem mentioned + // in Issue 339 comment #2). + + var bb = svgedit.utilities.getBBox(elem); + + if (!bb) { + return null; + } + + var tlist = svgedit.transformlist.getTransformList(elem); + var angle = svgedit.utilities.getRotationAngleFromTransformList(tlist); + var hasMatrixTransform = svgedit.math.hasMatrixTransform(tlist); + + if (angle || hasMatrixTransform) { + + var good_bb = false; + if (bBoxCanBeOptimizedOverNativeGetBBox(angle, hasMatrixTransform)) { + // Get the BBox from the raw path for these elements + // TODO: why ellipse and not circle + var elemNames = ['ellipse', 'path', 'line', 'polyline', 'polygon']; + if (elemNames.indexOf(elem.tagName) >= 0) { + bb = good_bb = svgedit.utilities.getBBoxOfElementAsPath(elem, addSvgElementFromJson, pathActions); + } else if (elem.tagName == 'rect') { + // Look for radius + var rx = elem.getAttribute('rx'); + var ry = elem.getAttribute('ry'); + if (rx || ry) { + bb = good_bb = svgedit.utilities.getBBoxOfElementAsPath(elem, addSvgElementFromJson, pathActions); + } + } + } + + if (!good_bb) { + + var matrix = svgedit.math.transformListToTransform(tlist).matrix; + bb = svgedit.math.transformBox(bb.x, bb.y, bb.width, bb.height, matrix).aabox; + + // Old technique that was exceedingly slow with large documents. + // + // Accurate way to get BBox of rotated element in Firefox: + // Put element in group and get its BBox + // + // Must use clone else FF freaks out + //var clone = elem.cloneNode(true); + //var g = document.createElementNS(NS.SVG, 'g'); + //var parent = elem.parentNode; + //parent.appendChild(g); + //g.appendChild(clone); + //var bb2 = svgedit.utilities.bboxToObj(g.getBBox()); + //parent.removeChild(g); + } + + } + return bb; +}; + +// TODO: This is problematic with large stroke-width and, for example, a single horizontal line. The calculated BBox extends way beyond left and right sides. +function getStrokeOffsetForBBox(elem) { + var sw = elem.getAttribute('stroke-width'); + return (!isNaN(sw) && elem.getAttribute('stroke') != 'none') ? sw/2 : 0; +}; + +// Function: getStrokedBBox +// Get the bounding box for one or more stroked and/or transformed elements +// +// Parameters: +// elems - Array with DOM elements to check +// addSvgElementFromJson - Function to add the path element to the current layer. See canvas.addSvgElementFromJson +// pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions. +// +// Returns: +// A single bounding box object +svgedit.utilities.getStrokedBBox = function(elems, addSvgElementFromJson, pathActions) { + if (!elems || !elems.length) {return false;} + + var full_bb; + $.each(elems, function() { + if (full_bb) {return;} + if (!this.parentNode) {return;} + full_bb = svgedit.utilities.getBBoxWithTransform(this, addSvgElementFromJson, pathActions); + }); + + // This shouldn't ever happen... + if (full_bb === undefined) {return null;} + + // full_bb doesn't include the stoke, so this does no good! + // if (elems.length == 1) return full_bb; + + var max_x = full_bb.x + full_bb.width; + var max_y = full_bb.y + full_bb.height; + var min_x = full_bb.x; + var min_y = full_bb.y; + + // If only one elem, don't call the potentially slow getBBoxWithTransform method again. + if (elems.length === 1) { + var offset = getStrokeOffsetForBBox(elems[0]); + min_x -= offset; + min_y -= offset; + max_x += offset; + max_y += offset; + } else { + $.each(elems, function(i, elem) { + var cur_bb = svgedit.utilities.getBBoxWithTransform(elem, addSvgElementFromJson, pathActions); + if (cur_bb) { + var offset = getStrokeOffsetForBBox(elem); + min_x = Math.min(min_x, cur_bb.x - offset); + min_y = Math.min(min_y, cur_bb.y - offset); + // TODO: The old code had this test for max, but not min. I suspect this test should be for both min and max + if (elem.nodeType == 1) { + max_x = Math.max(max_x, cur_bb.x + cur_bb.width + offset); + max_y = Math.max(max_y, cur_bb.y + cur_bb.height + offset); + } + } + }); + } + + full_bb.x = min_x; + full_bb.y = min_y; + full_bb.width = max_x - min_x; + full_bb.height = max_y - min_y; + return full_bb; +}; + + +// Function: svgedit.utilities.getRotationAngleFromTransformList +// Get the rotation angle of the given transform list. +// +// Parameters: +// tlist - List of transforms // to_rad - Boolean that when true returns the value in radians rather than degrees // // Returns: // Float with the angle in degrees or radians -svgedit.utilities.getRotationAngle = function(elem, to_rad) { - var selected = elem || editorContext_.getSelectedElements()[0]; - // find the rotation transform (if any) and set it - var tlist = svgedit.transformlist.getTransformList(selected); - if(!tlist) {return 0;} // <svg> elements have no tlist +svgedit.utilities.getRotationAngleFromTransformList = function(tlist, to_rad) { + if (!tlist) {return 0;} // <svg> elements have no tlist var N = tlist.numberOfItems; var i; for (i = 0; i < N; ++i) { @@ -557,6 +970,22 @@ svgedit.utilities.getRotationAngle = function(elem, to_rad) { return 0.0; }; +// Function: svgedit.utilities.getRotationAngle +// Get the rotation angle of the given/selected DOM element +// +// Parameters: +// elem - Optional DOM element to get the angle for +// to_rad - Boolean that when true returns the value in radians rather than degrees +// +// Returns: +// Float with the angle in degrees or radians +svgedit.utilities.getRotationAngle = function(elem, to_rad) { + var selected = elem || editorContext_.getSelectedElements()[0]; + // find the rotation transform (if any) and set it + var tlist = svgedit.transformlist.getTransformList(selected); + return svgedit.utilities.getRotationAngleFromTransformList(tlist, to_rad) +}; + // Function getRefElem // Get the reference element associated with the given attribute value // @@ -602,11 +1031,6 @@ if (svgedit.browser.supportsSelectors()) { // suspendLength - Optional integer of milliseconds to suspend redraw // unitCheck - Boolean to indicate the need to use svgedit.units.setUnitAttr svgedit.utilities.assignAttributes = function(node, attrs, suspendLength, unitCheck) { - if(!suspendLength) {suspendLength = 0;} - // Opera has a problem with suspendRedraw() apparently - var handle = null; - if (!svgedit.browser.isOpera()) {svgroot_.suspendRedraw(suspendLength);} - var i; for (i in attrs) { var ns = (i.substr(0,4) === 'xml:' ? NS.XML : @@ -615,12 +1039,13 @@ svgedit.utilities.assignAttributes = function(node, attrs, suspendLength, unitCh if(ns) { node.setAttributeNS(ns, i, attrs[i]); } else if(!unitCheck) { - node.setAttribute(i, attrs[i]); + if (attrs[i] != null) { + node.setAttribute(i, attrs[i]); + } } else { svgedit.units.setUnitAttr(node, i, attrs[i]); } } - if (!svgedit.browser.isOpera()) {svgroot_.unsuspendRedraw(handle);} }; // Function: cleanupElement @@ -629,7 +1054,6 @@ svgedit.utilities.assignAttributes = function(node, attrs, suspendLength, unitCh // Parameters: // element - DOM element to clean up svgedit.utilities.cleanupElement = function(element) { - var handle = svgroot_.suspendRedraw(60); var defaults = { 'fill-opacity':1, 'stop-opacity':1, @@ -644,6 +1068,12 @@ svgedit.utilities.cleanupElement = function(element) { 'ry':0 }; + if (element.nodeName === 'ellipse') { + // Ellipse elements requires rx and ry attributes + delete defaults.rx; + delete defaults.ry; + } + var attr; for (attr in defaults) { var val = defaults[attr]; @@ -651,8 +1081,6 @@ svgedit.utilities.cleanupElement = function(element) { element.removeAttribute(attr); } } - - svgroot_.unsuspendRedraw(handle); }; // Function: snapToGrid @@ -711,4 +1139,250 @@ svgedit.utilities.buildJSPDFCallback = function (callJSPDF) { }); }; + +/** + * Prevents default browser click behaviour on the given element + * @param img - The DOM element to prevent the click on + */ +svgedit.utilities.preventClickDefault = function(img) { + $(img).click(function(e){e.preventDefault();}); +}; + +/** + * Create a clone of an element, updating its ID and its children's IDs when needed + * @param {Element} el - DOM element to clone + * @param {function()} getNextId - function the get the next unique ID. + * @returns {Element} + */ +svgedit.utilities.copyElem = function(el, getNextId) { + // manually create a copy of the element + var new_el = document.createElementNS(el.namespaceURI, el.nodeName); + $.each(el.attributes, function(i, attr) { + if (attr.localName != '-moz-math-font-style') { + new_el.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.value); + } + }); + // set the copied element's new id + new_el.removeAttribute('id'); + new_el.id = getNextId(); + + // Opera's "d" value needs to be reset for Opera/Win/non-EN + // Also needed for webkit (else does not keep curved segments on clone) + if (svgedit.browser.isWebkit() && el.nodeName == 'path') { + var fixed_d = svgedit.utilities.convertPath(el); + new_el.setAttribute('d', fixed_d); + } + + // now create copies of all children + $.each(el.childNodes, function(i, child) { + switch(child.nodeType) { + case 1: // element node + new_el.appendChild(svgedit.utilities.copyElem(child, getNextId)); + break; + case 3: // text node + new_el.textContent = child.nodeValue; + break; + default: + break; + } + }); + + if ($(el).data('gsvg')) { + $(new_el).data('gsvg', new_el.firstChild); + } else if ($(el).data('symbol')) { + var ref = $(el).data('symbol'); + $(new_el).data('ref', ref).data('symbol', ref); + } else if (new_el.tagName == 'image') { + preventClickDefault(new_el); + } + return new_el; +}; + + +/** + * TODO: refactor callers in convertPath to use getPathDFromSegments instead of this function. + * Legacy code refactored from svgcanvas.pathActions.convertPath + * @param letter - path segment command + * @param {Array.<Array.<number>>} points - x,y points. + * @param {Array.<Array.<number>>=} morePoints - x,y points + * @param {Array.<number>=}lastPoint - x,y point + * @returns {string} + */ +function pathDSegment(letter, points, morePoints, lastPoint) { + $.each(points, function(i, pnt) { + points[i] = svgedit.units.shortFloat(pnt); + }); + var segment = letter + points.join(' '); + if (morePoints) { + segment += ' ' + morePoints.join(' '); + } + if (lastPoint) { + segment += ' ' + svgedit.units.shortFloat(lastPoint); + } + return segment; +} + +// 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']; + + +/** + * TODO: move to pathActions.js when migrating rest of pathActions out of svgcanvas.js + * Convert a path to one with only absolute or relative values + * @param {Object} path - the path to convert + * @param {boolean} toRel - true of convert to relative + * @returns {string} + */ +svgedit.utilities.convertPath = function(path, toRel) { + var i; + var segList = path.pathSegList; + var len = segList.numberOfItems; + var curx = 0, cury = 0; + var d = ''; + var last_m = null; + + for (i = 0; i < len; ++i) { + var seg = segList.getItem(i); + // if these properties are not in the segment, set them to zero + var x = seg.x || 0, + y = seg.y || 0, + x1 = seg.x1 || 0, + y1 = seg.y1 || 0, + x2 = seg.x2 || 0, + y2 = seg.y2 || 0; + + var type = seg.pathSegType; + var letter = pathMap[type]['to'+(toRel?'Lower':'Upper')+'Case'](); + + switch (type) { + case 1: // z,Z closepath (Z/z) + d += 'z'; + break; + case 12: // absolute horizontal line (H) + x -= curx; + case 13: // relative horizontal line (h) + if (toRel) { + curx += x; + letter = 'l'; + } else { + x += curx; + curx = x; + letter = 'L'; + } + // Convert to "line" for easier editing + d += pathDSegment(letter,[[x, cury]]); + break; + case 14: // absolute vertical line (V) + y -= cury; + case 15: // relative vertical line (v) + if (toRel) { + cury += y; + letter = 'l'; + } else { + y += cury; + cury = y; + letter = 'L'; + } + // Convert to "line" for easier editing + d += pathDSegment(letter,[[curx, y]]); + break; + case 2: // absolute move (M) + case 4: // absolute line (L) + case 18: // absolute smooth quad (T) + x -= curx; + y -= cury; + case 5: // relative line (l) + case 3: // relative move (m) + // If the last segment was a "z", this must be relative to + if (last_m && segList.getItem(i-1).pathSegType === 1 && !toRel) { + curx = last_m[0]; + cury = last_m[1]; + } + + case 19: // relative smooth quad (t) + if (toRel) { + curx += x; + cury += y; + } else { + x += curx; + y += cury; + curx = x; + cury = y; + } + if (type === 3) {last_m = [curx, cury];} + + d += pathDSegment(letter,[[x, y]]); + break; + case 6: // absolute cubic (C) + x -= curx; x1 -= curx; x2 -= curx; + y -= cury; y1 -= cury; y2 -= cury; + case 7: // relative cubic (c) + if (toRel) { + curx += x; + cury += y; + } else { + x += curx; x1 += curx; x2 += curx; + y += cury; y1 += cury; y2 += cury; + curx = x; + cury = y; + } + d += pathDSegment(letter,[[x1, y1], [x2, y2], [x, y]]); + break; + case 8: // absolute quad (Q) + x -= curx; x1 -= curx; + y -= cury; y1 -= cury; + case 9: // relative quad (q) + if (toRel) { + curx += x; + cury += y; + } else { + x += curx; x1 += curx; + y += cury; y1 += cury; + curx = x; + cury = y; + } + d += pathDSegment(letter,[[x1, y1],[x, y]]); + break; + case 10: // absolute elliptical arc (A) + x -= curx; + y -= cury; + case 11: // relative elliptical arc (a) + if (toRel) { + curx += x; + cury += y; + } else { + x += curx; + y += cury; + curx = x; + cury = y; + } + d += pathDSegment(letter,[[seg.r1, seg.r2]], [ + seg.angle, + (seg.largeArcFlag ? 1 : 0), + (seg.sweepFlag ? 1 : 0) + ], [x, y] + ); + break; + case 16: // absolute smooth cubic (S) + x -= curx; x2 -= curx; + y -= cury; y2 -= cury; + case 17: // relative smooth cubic (s) + if (toRel) { + curx += x; + cury += y; + } else { + x += curx; x2 += curx; + y += cury; y2 += cury; + curx = x; + cury = y; + } + d += pathDSegment(letter,[[x2, y2],[x, y]]); + break; + } // switch on path segment type + } // for each segment + return d; +}; + + }());