diff --git a/editor/draw.js b/editor/draw.js index c658a43b..c21c394f 100644 --- a/editor/draw.js +++ b/editor/draw.js @@ -300,10 +300,20 @@ svgedit.draw.Drawing.prototype.getCurrentLayerName = function () { /** * Set the current layer's name. * @param {string} name - The new name. - * @returns {Object} If the name was changed, returns {title:SVGGElement, previousName:string}; otherwise null. + * @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) { - return this.current_layer ? this.current_layer.setName(name) : 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; }; /** @@ -354,7 +364,7 @@ svgedit.draw.Drawing.prototype.setCurrentLayerPosition = function (newpos) { svgedit.draw.Drawing.prototype.mergeLayer = function (hrService) { var current_group = this.current_layer.getGroup(); var prevGroup = $(current_group).prev()[0]; - if (!prevGroup) {return null;} + if (!prevGroup) {return;} hrService.startBatchCommand('Merge Layer'); @@ -508,13 +518,14 @@ svgedit.draw.Drawing.prototype.identifyLayers = function() { }; /** - * Creates a new top-level layer in the drawing with the given name and - * sets the current layer to it. + * 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) { +svgedit.draw.Drawing.prototype.createLayer = function(name, hrService) { if (this.current_layer) { this.current_layer.deactivate(); } @@ -522,13 +533,69 @@ svgedit.draw.Drawing.prototype.createLayer = function(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; +}; + /** * Returns whether the layer is visible. If the layer name is not valid, * then this function returns false. @@ -589,4 +656,16 @@ svgedit.draw.Drawing.prototype.setLayerOpacity = function(layername, 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/editor/layer.js b/editor/layer.js index bab48197..8b5a09d2 100644 --- a/editor/layer.js +++ b/editor/layer.js @@ -1,4 +1,4 @@ -/*globals svgedit*/ +/*globals $ svgedit*/ /*jslint vars: true, eqeq: true */ /** * Package: svgedit.history @@ -17,7 +17,7 @@ 'use strict'; if (!svgedit.draw) { - svgedit.draw = {}; + svgedit.draw = {}; } var NS = svgedit.NS; @@ -25,29 +25,42 @@ 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} group - SVG group element that constitutes the layer or null if a group should be created and added to the DOM.. - * @param {SVGGElement} svgElem - The SVG DOM element. If defined, use this to add + * @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_ = group; + this.name_ = name; + this.group_ = svgElem ? null : group; - if (!group) { - // 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); - svgElem.appendChild(this.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");}); + 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"); + this.group_.setAttribute("style", svgElem ? "pointer-events:all" : "pointer-events:none"); }; /** @@ -66,7 +79,7 @@ Layer.CLASS_REGEX = new RegExp('(\\s|^)' + Layer.CLASS_NAME + '(\\s|$)'); * @returns {string} The layer name */ Layer.prototype.getName = function() { - return this.name_; + return this.name_; }; /** @@ -74,21 +87,21 @@ Layer.prototype.getName = function() { * @returns {SVGGElement} The layer SVG group */ Layer.prototype.getGroup = function() { - return this.group_; + return this.group_; }; /** * Active this layer so it takes pointer events. */ Layer.prototype.activate = function() { - this.group_.setAttribute("style", "pointer-events:all"); + 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"); + this.group_.setAttribute("style", "pointer-events:none"); }; /** @@ -96,11 +109,11 @@ Layer.prototype.deactivate = function() { * @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); - } + var expected = visible === undefined || visible ? "inline" : "none"; + var oldDisplay = this.group_.getAttribute("display"); + if (oldDisplay !== expected) { + this.group_.setAttribute("display", expected); + } }; /** @@ -108,7 +121,7 @@ Layer.prototype.setVisible = function(visible) { * @returns {boolean} True if visible. */ Layer.prototype.isVisible = function() { - return this.group_.getAttribute('display') !== 'none'; + return this.group_.getAttribute('display') !== 'none'; }; /** @@ -116,11 +129,11 @@ Layer.prototype.isVisible = function() { * @returns {number} Opacity value. */ Layer.prototype.getOpacity = function() { - var opacity = this.group_.getAttribute('opacity'); - if (opacity === null || opacity === undefined) { - return 1; - } - return parseFloat(opacity); + var opacity = this.group_.getAttribute('opacity'); + if (opacity === null || opacity === undefined) { + return 1; + } + return parseFloat(opacity); }; /** @@ -129,9 +142,9 @@ Layer.prototype.getOpacity = function() { * @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); - } + if (typeof opacity === 'number' && opacity >= 0.0 && opacity <= 1.0) { + this.group_.setAttribute('opacity', opacity); + } }; /** @@ -139,34 +152,43 @@ Layer.prototype.setOpacity = function(opacity) { * @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]); - } + 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; + 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; }; -Layer.prototype.setName = function(name) { - 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; - return {title: title, previousName: previousName}; - } - 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; }; /** @@ -175,10 +197,10 @@ Layer.prototype.setName = function(name) { * @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; + var parent = this.group_.parentNode; + var group = parent.removeChild(this.group_); + this.group_ = undefined; + return group; }; @@ -189,12 +211,12 @@ Layer.prototype.removeGroup = function() { * @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); - } + 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/editor/svgcanvas.js b/editor/svgcanvas.js index af4f153f..754745fc 100644 --- a/editor/svgcanvas.js +++ b/editor/svgcanvas.js @@ -355,6 +355,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); }, @@ -666,56 +675,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; @@ -882,10 +841,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]); @@ -1404,7 +1360,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 @@ -2353,14 +2309,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 @@ -3684,165 +3632,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 @@ -4676,7 +4466,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) { @@ -4966,44 +4756,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]); }; @@ -5058,11 +4829,8 @@ this.renameCurrentLayer = function(newname) { var drawing = getCurrentDrawing(); var layer = drawing.getCurrentLayer(); if (layer) { - var result = drawing.setCurrentLayerName( newname); + var result = drawing.setCurrentLayerName(newname, historyRecordingService()); if (result) { - var batchCmd = new svgedit.history.BatchCommand('Rename Layer'); - batchCmd.addSubCommand(new svgedit.history.ChangeElementCommand(result.title, {'#text':result.previousName})); - addCommandToHistory(batchCmd); call('changed', [layer]); return true; } @@ -5158,20 +4926,14 @@ this.moveSelectedToLayer = function(layername) { this.mergeLayer = function(hrService) { - if (!hrService) { - hrService = new svgedit.history.HistoryRecordingService(this.undoMgr); - } - getCurrentDrawing().mergeLayer(hrService); + getCurrentDrawing().mergeLayer(historyRecordingService(hrService)); clearSelection(); leaveContext(); call('changed', [svgcontent]); }; this.mergeAllLayers = function(hrService) { - if (!hrService) { - hrService = new svgedit.history.HistoryRecordingService(this.undoMgr); - } - getCurrentDrawing().mergeAllLayers(hrService); + getCurrentDrawing().mergeAllLayers(historyRecordingService(hrService)); clearSelection(); leaveContext(); call('changed', [svgcontent]); @@ -6622,19 +6384,19 @@ 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); @@ -6758,6 +6520,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]; @@ -6788,7 +6551,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 { @@ -7167,11 +6930,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)); } @@ -7410,7 +7174,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, @@ -7428,7 +7192,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/editor/svgutils.js b/editor/svgutils.js index 3fef5302..e01916e6 100644 --- a/editor/svgutils.js +++ b/editor/svgutils.js @@ -1137,4 +1137,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.>} points - x,y points. + * @param {Array.>=} morePoints - x,y points + * @param {Array.=}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; +}; + + }()); diff --git a/test/draw_test.html b/test/draw_test.html index 531d949a..87a5f3be 100644 --- a/test/draw_test.html +++ b/test/draw_test.html @@ -8,10 +8,13 @@ + + +