| | |
| | | |
| | | // This fixes $(...).attr() to work as expected with SVG elements. |
| | | // Does not currently use *AttributeNS() since we rarely need that. |
| | | |
| | | |
| | | // See http://api.jquery.com/attr/ for basic documentation of .attr() |
| | | |
| | | // Additional functionality: |
| | | |
| | | // Additional functionality: |
| | | // - When getting attributes, a string that's a number is return as type number. |
| | | // - If an array is supplied as first parameter, multiple values are returned |
| | | // as an object with values for each given attributes |
| | |
| | | obj[aname] = attr; |
| | | } |
| | | return obj; |
| | | |
| | | |
| | | } else if(typeof key === "object") { |
| | | // Setting attributes form object |
| | | for(var v in key) { |
| | |
| | | } |
| | | return this; |
| | | }; |
| | | |
| | | |
| | | }()); |
| | | |
| | | // Class: SvgCanvas |
| | |
| | | var curConfig = { |
| | | show_outside_canvas: true, |
| | | selectNew: true, |
| | | dimensions: [640, 480] |
| | | dimensions: [200, 200] |
| | | }; |
| | | |
| | | // Update config with new one if given |
| | |
| | | // Function: setIdPrefix |
| | | // Changes the ID prefix to the given value |
| | | // |
| | | // Parameters: |
| | | // p - String with the new prefix |
| | | // Parameters: |
| | | // p - String with the new prefix |
| | | canvas.setIdPrefix = function(p) { |
| | | idprefix = p; |
| | | }; |
| | |
| | | |
| | | // Function: addSvgElementFromJson |
| | | // Create a new SVG element based on the given object keys/values and add it to the current layer |
| | | // The element will be ran through cleanupElement before being returned |
| | | // The element will be ran through cleanupElement before being returned |
| | | // |
| | | // Parameters: |
| | | // data - Object with the following keys/values: |
| | |
| | | var elems = cmd.elements(); |
| | | canvas.pathActions.clear(); |
| | | call("changed", elems); |
| | | |
| | | |
| | | var cmdType = cmd.type(); |
| | | var isApply = (eventType == EventTypes.AFTER_APPLY); |
| | | if (cmdType == MoveElementCommand.type()) { |
| | |
| | | } else { |
| | | if (!isApply) restoreRefElems(cmd.elem); |
| | | } |
| | | |
| | | |
| | | if(cmd.elem.tagName === 'use') { |
| | | setUseData(cmd.elem); |
| | | } |
| | |
| | | if (values["stdDeviation"]) { |
| | | canvas.setBlurOffsets(cmd.elem.parentNode, values["stdDeviation"]); |
| | | } |
| | | |
| | | // Remove & Re-add hack for Webkit (issue 775) |
| | | |
| | | // Remove & Re-add hack for Webkit (issue 775) |
| | | //if(cmd.elem.tagName === 'use' && svgedit.browser.isWebkit()) { |
| | | // var elem = cmd.elem; |
| | | // if(!elem.getAttribute('x') && !elem.getAttribute('y')) { |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | var childs = elem.getElementsByTagName('*'); |
| | | |
| | | |
| | | if(childs.length) { |
| | | for(var i = 0, l = childs.length; i < l; i++) { |
| | | restoreRefElems(childs[i]); |
| | |
| | | |
| | | // Object to contain image data for raster images that were found encodable |
| | | var encodableImages = {}, |
| | | |
| | | |
| | | // String with image URL of last loadable image |
| | | last_good_img_url = curConfig.imgPath + 'logo.png', |
| | | |
| | | |
| | | // Array with current disabled elements (for in-group editing) |
| | | disabled_elems = [], |
| | | |
| | | |
| | | // Object with save options |
| | | save_options = {round_digits: 5}, |
| | | |
| | | |
| | | // Boolean indicating whether or not a draw action has been started |
| | | started = false, |
| | | |
| | | |
| | | // String with an element's initial transform attribute value |
| | | start_transform = null, |
| | | |
| | | |
| | | // String indicating the current editor mode |
| | | current_mode = "select", |
| | | |
| | | |
| | | // String with the current direction in which an element is being resized |
| | | current_resize_mode = "none", |
| | | |
| | | |
| | | // Object with IDs for imported files, to see if one was already added |
| | | import_ids = {}; |
| | | |
| | | // Current text style properties |
| | | var cur_text = all_properties.text, |
| | | |
| | | |
| | | // Current general properties |
| | | cur_properties = cur_shape, |
| | | |
| | | |
| | | // Array with selected elements' Bounding box object |
| | | // selectedBBoxes = new Array(1), |
| | | |
| | | |
| | | // The DOM element that was just selected |
| | | justSelected = null, |
| | | |
| | | |
| | | // DOM element for selection rectangle drawn by the user |
| | | rubberBox = null, |
| | | |
| | | |
| | | // Array of current BBoxes (still needed?) |
| | | curBBoxes = [], |
| | | |
| | | |
| | | // Object to contain all included extensions |
| | | extensions = {}, |
| | | |
| | | |
| | | // Canvas point for the most recent right click |
| | | lastClickPoint = null, |
| | | |
| | | |
| | | // Map of deleted reference elements |
| | | removedElements = {} |
| | | |
| | |
| | | |
| | | // Function: addExtension |
| | | // Add an extension to the editor |
| | | // |
| | | // |
| | | // Parameters: |
| | | // name - String with the ID of the extension |
| | | // ext_func - Function supplied by the extension with its data |
| | | this.addExtension = function(name, ext_func) { |
| | | if(!(name in extensions)) { |
| | | // Provide private vars/funcs here. Is there a better way to do this? |
| | | |
| | | |
| | | if($.isFunction(ext_func)) { |
| | | var ext = ext_func($.extend(canvas.getPrivateMethods(), { |
| | | svgroot: svgroot, |
| | |
| | | console.log('Cannot add extension "' + name + '", an extension by that name already exists"'); |
| | | } |
| | | }; |
| | | |
| | | |
| | | // This method rounds the incoming value to the nearest value based on the current_zoom |
| | | var round = this.round = function(val) { |
| | | return parseInt(val*current_zoom)/current_zoom; |
| | |
| | | |
| | | // 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, |
| | | // |
| | | // 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. |
| | | // |
| | | // |
| | | // 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 |
| | |
| | | if (rubberBox == null) { return null; } |
| | | |
| | | var parent = current_group || getCurrentDrawing().getCurrentLayer(); |
| | | |
| | | |
| | | if(!curBBoxes.length) { |
| | | // Cache all bboxes |
| | | curBBoxes = getVisibleElementsAndBBoxes(parent); |
| | | } |
| | | |
| | | |
| | | var resultList = null; |
| | | try { |
| | | resultList = parent.getIntersectionList(rect, null); |
| | |
| | | |
| | | if (resultList == null || typeof(resultList.item) != "function") { |
| | | resultList = []; |
| | | |
| | | |
| | | if(!rect) { |
| | | var rubberBBox = rubberBox.getBBox(); |
| | | var bb = {}; |
| | | |
| | | |
| | | for(var o in rubberBBox) { |
| | | bb[o] = rubberBBox[o] / current_zoom; |
| | | } |
| | | rubberBBox = bb; |
| | | |
| | | |
| | | } else { |
| | | var rubberBBox = rect; |
| | | } |
| | |
| | | } |
| | | } |
| | | } |
| | | // addToSelection expects an array, but it's ok to pass a NodeList |
| | | // because using square-bracket notation is allowed: |
| | | // 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 |
| | | return resultList; |
| | | }; |
| | |
| | | // TODO(codedread): Migrate this into svgutils.js |
| | | // Function: getStrokedBBox |
| | | // Get the bounding box for one or more stroked and/or transformed elements |
| | | // |
| | | // |
| | | // Parameters: |
| | | // elems - Array with DOM elements to check |
| | | // |
| | | // |
| | | // Returns: |
| | | // A single bounding box object |
| | | 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); |
| | | } |
| | | } |
| | | |
| | | |
| | | if(!good_bb) { |
| | | // Must use clone else FF freaks out |
| | | var clone = elem.cloneNode(true); |
| | | var clone = elem.cloneNode(true); |
| | | var g = document.createElementNS(svgns, "g"); |
| | | var parent = elem.parentNode; |
| | | parent.appendChild(g); |
| | |
| | | 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, |
| | | // 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], |
| | | // 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 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) { |
| | | } catch(e) { |
| | | console.log(elem, e); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | var full_bb; |
| | |
| | | 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"); |
| | |
| | | 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 |
| | |
| | | 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; |
| | |
| | | // * bbox - The element's BBox as retrieved from getStrokedBBox |
| | | var getVisibleElementsAndBBoxes = this.getVisibleElementsAndBBoxes = function(parent) { |
| | | if(!parent) parent = $(svgcontent).children(); // Prevent layers from being included |
| | | |
| | | |
| | | var contentElems = []; |
| | | $(parent).children().each(function(i, elem) { |
| | | try { |
| | |
| | | new_el.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.nodeValue); |
| | | } |
| | | }); |
| | | |
| | | |
| | | // 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') { |
| | |
| | | break; |
| | | } |
| | | }); |
| | | |
| | | |
| | | if($(el).data('gsvg')) { |
| | | $(new_el).data('gsvg', new_el.firstChild); |
| | | } else if($(el).data('symbol')) { |
| | |
| | | |
| | | getId = c.getId = function() { return getCurrentDrawing().getId(); }; |
| | | getNextId = c.getNextId = function() { return getCurrentDrawing().getNextId(); }; |
| | | |
| | | |
| | | // Function: call |
| | | // Run the callback function associated with the given event |
| | | // |
| | |
| | | return events[event](this, arg); |
| | | } |
| | | }; |
| | | |
| | | |
| | | // Function: bind |
| | | // Attaches a callback function to an event |
| | | // |
| | | // Parameters: |
| | | // event - String indicating the name of the event |
| | | // f - The callback function to bind to the event |
| | | // |
| | | // |
| | | // Return: |
| | | // The previous event |
| | | c.bind = function(event, f) { |
| | |
| | | events[event] = f; |
| | | return old; |
| | | }; |
| | | |
| | | |
| | | }(canvas)); |
| | | |
| | | // Function: canvas.prepareSvg |
| | |
| | | } |
| | | |
| | | // Function: ffClone |
| | | // Hack for Firefox bugs where text element features aren't updated or get |
| | | // Hack for Firefox bugs where text element features aren't updated or get |
| | | // messed up. See issue 136 and issue 137. |
| | | // This function clones the element and re-selects it |
| | | // TODO: Test for this bug on load and add it to "support" object instead of |
| | | // This function clones the element and re-selects it |
| | | // TODO: Test for this bug on load and add it to "support" object instead of |
| | | // browser sniffing |
| | | // |
| | | // Parameters: |
| | | // Parameters: |
| | | // elem - The (text) DOM element to clone |
| | | var ffClone = function(elem) { |
| | | if(!svgedit.browser.isGecko()) return elem; |
| | |
| | | var bbox = svgedit.utilities.getBBox(elem); |
| | | var cx = bbox.x+bbox.width/2, cy = bbox.y+bbox.height/2; |
| | | var tlist = getTransformList(elem); |
| | | |
| | | |
| | | // only remove the real rotational transform if present (i.e. at index=0) |
| | | if (tlist.numberOfItems > 0) { |
| | | var xform = tlist.getItem(0); |
| | |
| | | else if (tlist.numberOfItems == 0) { |
| | | elem.removeAttribute("transform"); |
| | | } |
| | | |
| | | |
| | | if (!preventUndo) { |
| | | // we need to undo it, then redo it so it can be undo-able! :) |
| | | // TODO: figure out how to make changes to transform list undo-able cross-browser? |
| | |
| | | }; |
| | | |
| | | // Function: recalculateAllSelectedDimensions |
| | | // Runs recalculateDimensions on the selected elements, |
| | | // Runs recalculateDimensions on the selected elements, |
| | | // adding the changes to a single batch command |
| | | var recalculateAllSelectedDimensions = this.recalculateAllSelectedDimensions = function() { |
| | | var text = (current_resize_mode == "none" ? "position" : "size"); |
| | |
| | | }; |
| | | |
| | | // 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', |
| | | 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]); |
| | |
| | | assignAttributes(selected, changes, 1000, true); |
| | | } |
| | | box = svgedit.utilities.getBBox(selected); |
| | | |
| | | |
| | | for(var i = 0; i < 2; i++) { |
| | | var type = i === 0 ? 'fill' : 'stroke'; |
| | | var attrVal = selected.getAttribute(type); |
| | |
| | | if(m.a < 0 || m.d < 0) { |
| | | var grad = getRefElem(attrVal); |
| | | var newgrad = grad.cloneNode(true); |
| | | |
| | | |
| | | if(m.a < 0) { |
| | | //flip x |
| | | var x1 = newgrad.getAttribute('x1'); |
| | | var x2 = newgrad.getAttribute('x2'); |
| | | newgrad.setAttribute('x1', -(x1 - 1)); |
| | | newgrad.setAttribute('x2', -(x2 - 1)); |
| | | } |
| | | |
| | | } |
| | | |
| | | if(m.d < 0) { |
| | | //flip y |
| | | var y1 = newgrad.getAttribute('y1'); |
| | |
| | | findDefs().appendChild(newgrad); |
| | | selected.setAttribute(type, 'url(#' + newgrad.id + ')'); |
| | | } |
| | | |
| | | |
| | | // Not really working :( |
| | | // if(selected.tagName === 'path') { |
| | | // reorientGrads(selected, m); |
| | |
| | | var elName = selected.tagName; |
| | | if(elName === "g" || elName === "text" || elName === "use") { |
| | | // if it was a translate, then just update x,y |
| | | if (m.a == 1 && m.b == 0 && m.c == 0 && m.d == 1 && |
| | | (m.e != 0 || m.f != 0) ) |
| | | if (m.a == 1 && m.b == 0 && m.c == 0 && m.d == 1 && |
| | | (m.e != 0 || m.f != 0) ) |
| | | { |
| | | // [T][M] = [M][T'] |
| | | // therefore [T'] = [M_inv][T][M] |
| | |
| | | chlist.appendItem(mt); |
| | | } |
| | | } |
| | | |
| | | |
| | | // now we have a set of changes and an applied reduced transform list |
| | | // we apply the changes directly to the DOM |
| | | switch (elName) |
| | |
| | | case "foreignObject": |
| | | case "rect": |
| | | case "image": |
| | | |
| | | |
| | | // Allow images to be inverted (give them matrix when flipped) |
| | | if(elName === 'image' && (m.a < 0 || m.d < 0)) { |
| | | // Convert to matrix |
| | |
| | | chlist.appendItem(mt); |
| | | } else { |
| | | var pt1 = remap(changes.x,changes.y); |
| | | |
| | | |
| | | changes.width = scalew(changes.width); |
| | | changes.height = scaleh(changes.height); |
| | | |
| | | |
| | | changes.x = pt1.x + Math.min(0,changes.width); |
| | | changes.y = pt1.y + Math.min(0,changes.height); |
| | | changes.width = Math.abs(changes.width); |
| | |
| | | changes.cy = c.y; |
| | | changes.rx = scalew(changes.rx); |
| | | changes.ry = scaleh(changes.ry); |
| | | |
| | | |
| | | changes.rx = Math.abs(changes.rx); |
| | | changes.ry = Math.abs(changes.ry); |
| | | finishUp(); |
| | |
| | | changes.y1 = pt1.y; |
| | | changes.x2 = pt2.x; |
| | | changes.y2 = pt2.y; |
| | | |
| | | |
| | | case "text": |
| | | var tspan = selected.querySelectorAll('tspan'); |
| | | var i = tspan.length |
| | |
| | | selected.setAttribute("points", pstr); |
| | | break; |
| | | case "path": |
| | | |
| | | |
| | | var segList = selected.pathSegList; |
| | | var len = segList.numberOfItems; |
| | | changes.d = new Array(len); |
| | |
| | | sweepFlag: seg.sweepFlag |
| | | }; |
| | | } |
| | | |
| | | |
| | | var len = changes.d.length, |
| | | firstseg = changes.d[0], |
| | | currentpt = remap(firstseg.x,firstseg.y); |
| | |
| | | seg.r2 = scaleh(seg.r2); |
| | | } |
| | | } // for each segment |
| | | |
| | | |
| | | var dstr = ""; |
| | | var len = changes.d.length; |
| | | for (var i = 0; i < len; ++i) { |
| | |
| | | dstr += seg.x1 + "," + seg.y1 + " " + seg.x2 + "," + seg.y2 + " " + |
| | | seg.x + "," + seg.y + " "; |
| | | break; |
| | | case 9: // relative quad (q) |
| | | case 9: // relative quad (q) |
| | | case 8: // absolute quad (Q) |
| | | dstr += seg.x1 + "," + seg.y1 + " " + seg.x + "," + seg.y + " "; |
| | | break; |
| | |
| | | // ty - The translation's y value |
| | | var updateClipPath = function(attr, tx, ty) { |
| | | var path = getRefElem(attr).firstChild; |
| | | |
| | | |
| | | var cp_xform = getTransformList(path); |
| | | |
| | | |
| | | var newxlate = svgroot.createSVGTransform(); |
| | | newxlate.setTranslate(tx, ty); |
| | | |
| | | cp_xform.appendItem(newxlate); |
| | | |
| | | |
| | | // Update clipPath's dimensions |
| | | recalculateDimensions(path); |
| | | } |
| | |
| | | // Parameters: |
| | | // selected - The DOM element to recalculate |
| | | // |
| | | // Returns: |
| | | // Returns: |
| | | // Undo command object with the resulting change |
| | | var recalculateDimensions = this.recalculateDimensions = function(selected) { |
| | | if (selected == null) return null; |
| | | |
| | | |
| | | var tlist = getTransformList(selected); |
| | | |
| | | |
| | | // remove any unnecessary transforms |
| | | if (tlist && tlist.numberOfItems > 0) { |
| | | var k = tlist.numberOfItems; |
| | |
| | | // End here if all it has is a rotation |
| | | if(tlist.numberOfItems === 1 && getRotationAngle(selected)) return null; |
| | | } |
| | | |
| | | |
| | | // if this element had no transforms, we are done |
| | | if (!tlist || tlist.numberOfItems == 0) { |
| | | selected.removeAttribute("transform"); |
| | | return null; |
| | | } |
| | | |
| | | |
| | | // TODO: Make this work for more than 2 |
| | | if (tlist) { |
| | | var k = tlist.numberOfItems; |
| | |
| | | tlist.removeItem(mxs[1][1]); |
| | | tlist.insertItemBefore(m_new, mxs[1][1]); |
| | | } |
| | | |
| | | |
| | | // combine matrix + translate |
| | | k = tlist.numberOfItems; |
| | | if(k >= 2 && tlist.getItem(k-2).type === 1 && tlist.getItem(k-1).type === 2) { |
| | | var mt = svgroot.createSVGTransform(); |
| | | |
| | | |
| | | var m = matrixMultiply( |
| | | tlist.getItem(k-2).matrix, |
| | | tlist.getItem(k-2).matrix, |
| | | tlist.getItem(k-1).matrix |
| | | ); |
| | | ); |
| | | mt.setMatrix(m); |
| | | tlist.removeItem(k-2); |
| | | tlist.removeItem(k-2); |
| | | tlist.appendItem(mt); |
| | | } |
| | | } |
| | | |
| | | |
| | | // If it still has a single [M] or [R][M], return null too (prevents BatchCommand from being returned). |
| | | switch ( selected.tagName ) { |
| | | // Ignore these elements, as they can absorb the [M] |
| | |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | // Grouped SVG element |
| | | |
| | | // Grouped SVG element |
| | | var gsvg = $(selected).data('gsvg'); |
| | | |
| | | // we know we have some transforms, so set up return variable |
| | | |
| | | // we know we have some transforms, so set up return variable |
| | | var batchCmd = new BatchCommand("Transform"); |
| | | |
| | | |
| | | // store initial values that will be affected by reducing the transform list |
| | | var changes = {}, initial = null, attrs = []; |
| | | switch (selected.tagName) |
| | |
| | | changes["d"] = selected.getAttribute("d"); |
| | | break; |
| | | } // switch on element type to get initial values |
| | | |
| | | |
| | | if(attrs.length) { |
| | | changes = $(selected).attr(attrs); |
| | | $.each(changes, function(attr, val) { |
| | |
| | | y: $(gsvg).attr('y') || 0 |
| | | }; |
| | | } |
| | | |
| | | // if we haven't created an initial array in polygon/polyline/path, then |
| | | |
| | | // if we haven't created an initial array in polygon/polyline/path, then |
| | | // make a copy of initial values and include the transform |
| | | if (initial == null) { |
| | | initial = $.extend(true, {}, changes); |
| | |
| | | } |
| | | // save the start transform value too |
| | | initial["transform"] = start_transform ? start_transform : ""; |
| | | |
| | | |
| | | // if it's a regular group, we have special processing to flatten transforms |
| | | if ((selected.tagName == "g" && !gsvg) || selected.tagName == "a") { |
| | | var box = svgedit.utilities.getBBox(selected), |
| | |
| | | newcenter = transformPoint(box.x+box.width/2, box.y+box.height/2, |
| | | transformListToTransform(tlist).matrix), |
| | | m = svgroot.createSVGMatrix(); |
| | | |
| | | |
| | | |
| | | |
| | | // temporarily strip off the rotate and save the old center |
| | | var gangle = getRotationAngle(selected); |
| | | if (gangle) { |
| | |
| | | } |
| | | |
| | | // first, if it was a scale then the second-last transform will be it |
| | | if (N >= 3 && tlist.getItem(N-2).type == 3 && |
| | | tlist.getItem(N-3).type == 2 && tlist.getItem(N-1).type == 2) |
| | | if (N >= 3 && tlist.getItem(N-2).type == 3 && |
| | | tlist.getItem(N-3).type == 2 && tlist.getItem(N-1).type == 2) |
| | | { |
| | | operation = 3; // scale |
| | | |
| | | |
| | | // if the children are unrotated, pass the scale down directly |
| | | // otherwise pass the equivalent matrix() down directly |
| | | var tm = tlist.getItem(N-3).matrix, |
| | | sm = tlist.getItem(N-2).matrix, |
| | | tmn = tlist.getItem(N-1).matrix; |
| | | |
| | | |
| | | var children = selected.childNodes; |
| | | var c = children.length; |
| | | while (c--) { |
| | |
| | | // childTlist.appendItem(translateOrigin); |
| | | // } |
| | | // } |
| | | |
| | | |
| | | var angle = getRotationAngle(child); |
| | | var old_start_transform = start_transform; |
| | | var childxforms = []; |
| | |
| | | // if not rotated or skewed, push the [T][S][-T] down to the child |
| | | else { |
| | | // update the transform list with translate,scale,translate |
| | | |
| | | |
| | | // slide the [T][S][-T] from the front to the back |
| | | // [T][S][-T][M] = [M][T2][S2][-T2] |
| | | |
| | | |
| | | // (only bringing [-T] to the right of [M]) |
| | | // [T][S][-T][M] = [T][S][M][-T2] |
| | | // [-T2] = [M_inv][-T][M] |
| | |
| | | var t2 = svgroot.createSVGMatrix(); |
| | | t2.e = -t2n.e; |
| | | t2.f = -t2n.f; |
| | | |
| | | |
| | | // [T][S][-T][M] = [M][T2][S2][-T2] |
| | | // [S2] = [T2_inv][M_inv][T][S][-T][M][-T2_inv] |
| | | var s2 = matrixMultiply(t2.inverse(), m.inverse(), tm, sm, tmn, m, t2n.inverse()); |
| | |
| | | // logMatrix(scale.matrix); |
| | | } // not rotated |
| | | batchCmd.addSubCommand( recalculateDimensions(child) ); |
| | | // TODO: If any <use> have this group as a parent and are |
| | | // referencing this child, then we need to impose a reverse |
| | | // TODO: If any <use> have this group as a parent and are |
| | | // referencing this child, then we need to impose a reverse |
| | | // scale on it so that when it won't get double-translated |
| | | // var uses = selected.getElementsByTagNameNS(svgns, "use"); |
| | | // var href = "#"+child.id; |
| | |
| | | e2t.setMatrix(m); |
| | | tlist.clear(); |
| | | tlist.appendItem(e2t); |
| | | } |
| | | // next, check if the first transform was a translate |
| | | } |
| | | // next, check if the first transform was a translate |
| | | // if we had [ T1 ] [ M ] we want to transform this into [ M ] [ T2 ] |
| | | // therefore [ T2 ] = [ M_inv ] [ T1 ] [ M ] |
| | | else if ( (N == 1 || (N > 1 && tlist.getItem(1).type != 3)) && |
| | | tlist.getItem(0).type == 2) |
| | | else if ( (N == 1 || (N > 1 && tlist.getItem(1).type != 3)) && |
| | | tlist.getItem(0).type == 2) |
| | | { |
| | | operation = 2; // translate |
| | | var T_M = transformListToTransform(tlist).matrix; |
| | | tlist.removeItem(0); |
| | | var M_inv = transformListToTransform(tlist).matrix.inverse(); |
| | | var M2 = matrixMultiply( M_inv, T_M ); |
| | | |
| | | |
| | | tx = M2.e; |
| | | ty = M2.f; |
| | | |
| | |
| | | // we pass the translates down to the individual children |
| | | var children = selected.childNodes; |
| | | var c = children.length; |
| | | |
| | | |
| | | var clipPaths_done = []; |
| | | |
| | | |
| | | while (c--) { |
| | | var child = children.item(c); |
| | | if (child.nodeType == 1) { |
| | | |
| | | |
| | | // Check if child has clip-path |
| | | if(child.getAttribute('clip-path')) { |
| | | // tx, ty |
| | |
| | | if(clipPaths_done.indexOf(attr) === -1) { |
| | | updateClipPath(attr, tx, ty); |
| | | clipPaths_done.push(attr); |
| | | } |
| | | } |
| | | } |
| | | |
| | | var old_start_transform = start_transform; |
| | | start_transform = child.getAttribute("transform"); |
| | | |
| | | |
| | | var childTlist = getTransformList(child); |
| | | // some children might not have a transform (<metadata>, <defs>, etc) |
| | | if (childTlist) { |
| | |
| | | childTlist.appendItem(newxlate); |
| | | } |
| | | batchCmd.addSubCommand( recalculateDimensions(child) ); |
| | | // If any <use> have this group as a parent and are |
| | | // If any <use> have this group as a parent and are |
| | | // referencing this child, then impose a reverse translate on it |
| | | // so that when it won't get double-translated |
| | | var uses = selected.getElementsByTagNameNS(svgns, "use"); |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | clipPaths_done = []; |
| | | |
| | | |
| | | start_transform = old_start_transform; |
| | | } |
| | | } |
| | |
| | | var old_start_transform = start_transform; |
| | | start_transform = child.getAttribute("transform"); |
| | | var childTlist = getTransformList(child); |
| | | |
| | | |
| | | if (!childTlist) continue; |
| | | |
| | | |
| | | var em = matrixMultiply(m, transformListToTransform(childTlist).matrix); |
| | | var e2m = svgroot.createSVGTransform(); |
| | | e2m.setMatrix(em); |
| | | childTlist.clear(); |
| | | childTlist.appendItem(e2m,0); |
| | | |
| | | |
| | | batchCmd.addSubCommand( recalculateDimensions(child) ); |
| | | start_transform = old_start_transform; |
| | | |
| | | |
| | | // Convert stroke |
| | | // TODO: Find out if this should actually happen somewhere else |
| | | var sw = child.getAttribute("stroke-width"); |
| | |
| | | if (tlist.numberOfItems == 0) { |
| | | selected.removeAttribute("transform"); |
| | | } |
| | | return null; |
| | | return null; |
| | | } |
| | | |
| | | |
| | | // if it was a translate, put back the rotate at the new center |
| | | if (operation == 2) { |
| | | if (gangle) { |
| | |
| | | x: oldcenter.x + first_m.e, |
| | | y: oldcenter.y + first_m.f |
| | | }; |
| | | |
| | | |
| | | var newRot = svgroot.createSVGTransform(); |
| | | newRot.setRotate(gangle,newcenter.x,newcenter.y); |
| | | if(tlist.numberOfItems) { |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | if (gangle) { |
| | | if(tlist.numberOfItems) { |
| | | tlist.insertItemBefore(rnew, 0); |
| | |
| | | // but we still may need to recalculate them (see issue 595). |
| | | // TODO: Figure out how to get BBox from these elements in case they |
| | | // have a rotation transform |
| | | |
| | | |
| | | if(!box && selected.tagName != 'path') return null; |
| | | |
| | | |
| | | |
| | | var m = svgroot.createSVGMatrix(), |
| | | // temporarily strip off the rotate and save the old center |
| | |
| | | var oldcenter = {x: box.x+box.width/2, y: box.y+box.height/2}, |
| | | newcenter = transformPoint(box.x+box.width/2, box.y+box.height/2, |
| | | transformListToTransform(tlist).matrix); |
| | | |
| | | |
| | | var a = angle * Math.PI / 180; |
| | | if ( Math.abs(a) > (1.0e-10) ) { |
| | | var s = Math.sin(a)/(1 - Math.cos(a)); |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // 2 = translate, 3 = scale, 4 = rotate, 1 = matrix imposition |
| | | var operation = 0; |
| | | var N = tlist.numberOfItems; |
| | | |
| | | |
| | | // Check if it has a gradient with userSpaceOnUse, in which case |
| | | // adjust it by recalculating the matrix transform. |
| | | // TODO: Make this work in Webkit using svgedit.transformlist.SVGTransformList |
| | |
| | | } |
| | | } |
| | | |
| | | // first, if it was a scale of a non-skewed element, then the second-last |
| | | // first, if it was a scale of a non-skewed element, then the second-last |
| | | // transform will be the [S] |
| | | // if we had [M][T][S][T] we want to extract the matrix equivalent of |
| | | // [T][S][T] and push it down to the element |
| | | if (N >= 3 && tlist.getItem(N-2).type == 3 && |
| | | tlist.getItem(N-3).type == 2 && tlist.getItem(N-1).type == 2) |
| | | |
| | | // Removed this so a <use> with a given [T][S][T] would convert to a matrix. |
| | | if (N >= 3 && tlist.getItem(N-2).type == 3 && |
| | | tlist.getItem(N-3).type == 2 && tlist.getItem(N-1).type == 2) |
| | | |
| | | // Removed this so a <use> with a given [T][S][T] would convert to a matrix. |
| | | // Is that bad? |
| | | // && selected.nodeName != "use" |
| | | { |
| | |
| | | tlist.appendItem(e2t); |
| | | // reset the matrix so that the element is not re-mapped |
| | | m = svgroot.createSVGMatrix(); |
| | | } // if we had [R][T][S][-T][M], then this was a rotated matrix-element |
| | | } // if we had [R][T][S][-T][M], then this was a rotated matrix-element |
| | | // if we had [T1][M] we want to transform this into [M][T2] |
| | | // therefore [ T2 ] = [ M_inv ] [ T1 ] [ M ] and we can push [T2] |
| | | // therefore [ T2 ] = [ M_inv ] [ T1 ] [ M ] and we can push [T2] |
| | | // down to the element |
| | | else if ( (N == 1 || (N > 1 && tlist.getItem(1).type != 3)) && |
| | | tlist.getItem(0).type == 2) |
| | | else if ( (N == 1 || (N > 1 && tlist.getItem(1).type != 3)) && |
| | | tlist.getItem(0).type == 2) |
| | | { |
| | | operation = 2; // translate |
| | | var oldxlate = tlist.getItem(0).matrix, |
| | |
| | | if (angle) { |
| | | var newRot = svgroot.createSVGTransform(); |
| | | newRot.setRotate(angle,newcenter.x,newcenter.y); |
| | | |
| | | |
| | | if(tlist.numberOfItems) { |
| | | tlist.insertItemBefore(newRot, 0); |
| | | } else { |
| | |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | |
| | | // if it was a translate or resize, we need to remap the element and absorb the xform |
| | | if (operation == 1 || operation == 2 || operation == 3) { |
| | | remapElement(selected,changes,m); |
| | | } // if we are remapping |
| | | |
| | | |
| | | // if it was a translate, put back the rotate at the new center |
| | | if (operation == 2) { |
| | | if (angle) { |
| | |
| | | var rnew_inv = rnew.matrix.inverse(); |
| | | var m_inv = m.inverse(); |
| | | var extrat = matrixMultiply(m_inv, rnew_inv, rold, m); |
| | | |
| | | |
| | | remapElement(selected,changes,extrat); |
| | | if (angle) { |
| | | if(tlist.numberOfItems) { |
| | |
| | | if (tlist.numberOfItems == 0) { |
| | | selected.removeAttribute("transform"); |
| | | } |
| | | |
| | | |
| | | batchCmd.addSubCommand(new ChangeElementCommand(selected, initial)); |
| | | |
| | | |
| | | return batchCmd; |
| | | }; |
| | | |
| | |
| | | |
| | | // Function: clearSelection |
| | | // Clears the selection. The 'selected' handler is then called. |
| | | // Parameters: |
| | | // Parameters: |
| | | // noCall - Optional boolean that when true does not call the "selected" handler |
| | | var clearSelection = this.clearSelection = function(noCall) { |
| | | if (selectedElements[0] != null) { |
| | |
| | | if (elemsToAdd.length == 0) { return; } |
| | | // find the first null in our selectedElements array |
| | | var j = 0; |
| | | |
| | | |
| | | while (j < selectedElements.length) { |
| | | if (selectedElements[j] == null) { |
| | | if (selectedElements[j] == null) { |
| | | break; |
| | | } |
| | | ++j; |
| | |
| | | if (!elem || !svgedit.utilities.getBBox(elem)) continue; |
| | | |
| | | if(elem.tagName === 'a' && elem.childNodes.length === 1) { |
| | | // Make "a" element's child be the selected element |
| | | // Make "a" element's child be the selected element |
| | | elem = elem.firstChild; |
| | | } |
| | | |
| | |
| | | // if (j == 0) selectedBBoxes[0] = svgedit.utilities.getBBox(elem); |
| | | j++; |
| | | var sel = selectorManager.requestSelector(elem); |
| | | |
| | | |
| | | if (selectedElements.length > 1) { |
| | | sel.showGrips(false); |
| | | } |
| | |
| | | |
| | | selectedElements.sort(function(a,b) { |
| | | if(a && b && a.compareDocumentPosition) { |
| | | return 3 - (b.compareDocumentPosition(a) & 6); |
| | | return 3 - (b.compareDocumentPosition(a) & 6); |
| | | } else if(a == null) { |
| | | return 1; |
| | | } |
| | | }); |
| | | |
| | | |
| | | // Make sure first elements are not null |
| | | while(selectedElements[0] == null) selectedElements.shift(0); |
| | | }; |
| | |
| | | |
| | | // Function: getMouseTarget |
| | | // Gets the desired element from a mouse event |
| | | // |
| | | // |
| | | // Parameters: |
| | | // evt - Event object from the mouse event |
| | | // |
| | | // |
| | | // Returns: |
| | | // DOM element we want |
| | | var getMouseTarget = this.getMouseTarget = function(evt) { |
| | |
| | | return null; |
| | | } |
| | | var mouse_target = evt.target; |
| | | |
| | | |
| | | // if it was a <use>, Opera and WebKit return the SVGElementInstance |
| | | if (mouse_target.correspondingUseElement) mouse_target = mouse_target.correspondingUseElement; |
| | | |
| | | |
| | | // for foreign content, go up until we find the foreignObject |
| | | // WebKit browsers set the mouse target to the svgcanvas div |
| | | if ([mathns, htmlns].indexOf(mouse_target.namespaceURI) >= 0 && |
| | | mouse_target.id != "svgcanvas") |
| | | // WebKit browsers set the mouse target to the svgcanvas div |
| | | if ([mathns, htmlns].indexOf(mouse_target.namespaceURI) >= 0 && |
| | | mouse_target.id != "svgcanvas") |
| | | { |
| | | while (mouse_target.nodeName != "foreignObject") { |
| | | mouse_target = mouse_target.parentNode; |
| | | if(!mouse_target) return svgroot; |
| | | } |
| | | } |
| | | |
| | | |
| | | // Get the desired mouse_target with jQuery selector-fu |
| | | // If it's root-like, select the root |
| | | var current_layer = getCurrentDrawing().getCurrentLayer(); |
| | | if([svgroot, container, svgcontent, current_layer].indexOf(mouse_target) >= 0) { |
| | | return svgroot; |
| | | } |
| | | |
| | | |
| | | var $target = $(mouse_target); |
| | | |
| | | // If it's a selection grip, return the grip parent |
| | | if($target.closest('#selectorParentGroup').length) { |
| | | // While we could instead have just returned mouse_target, |
| | | // While we could instead have just returned mouse_target, |
| | | // this makes it easier to indentify as being a selector grip |
| | | return selectorManager.selectorParentGroup; |
| | | } |
| | |
| | | while (mouse_target.parentNode && mouse_target.parentNode !== (current_group || current_layer)) { |
| | | mouse_target = mouse_target.parentNode; |
| | | } |
| | | |
| | | // |
| | | |
| | | // |
| | | // // go up until we hit a child of a layer |
| | | // while (mouse_target.parentNode.parentNode.tagName == 'g') { |
| | | // mouse_target = mouse_target.parentNode; |
| | |
| | | // if (mouse_target.nodeName.toLowerCase() == "div") { |
| | | // mouse_target = svgroot; |
| | | // } |
| | | |
| | | |
| | | return mouse_target; |
| | | }; |
| | | |
| | |
| | | maxx: null, |
| | | maxy: null |
| | | }; |
| | | |
| | | |
| | | // - when we are in a create mode, the element is added to the canvas |
| | | // but the action is not recorded until mousing up |
| | | // - when we are in select mode, select the element, remember the position |
| | |
| | | var pt = transformPoint( evt.pageX, evt.pageY, root_sctm ), |
| | | mouse_x = pt.x * current_zoom, |
| | | mouse_y = pt.y * current_zoom; |
| | | |
| | | |
| | | |
| | | evt.preventDefault(); |
| | | |
| | |
| | | current_mode = "select"; |
| | | lastClickPoint = pt; |
| | | } |
| | | |
| | | |
| | | var x = mouse_x / current_zoom, |
| | | y = mouse_y / current_zoom, |
| | | mouse_target = getMouseTarget(evt); |
| | | |
| | | |
| | | if(mouse_target.tagName === 'a' && mouse_target.childNodes.length === 1) { |
| | | mouse_target = mouse_target.firstChild; |
| | | } |
| | | |
| | | |
| | | // real_x/y ignores grid-snap value |
| | | var real_x = r_start_x = start_x = x; |
| | | var real_y = r_start_y = start_y = y; |
| | |
| | | start_y = snapToGrid(start_y); |
| | | } |
| | | |
| | | // if it is a selector grip, then it must be a single element selected, |
| | | // if it is a selector grip, then it must be a single element selected, |
| | | // set the mouse_target to that and update the mode to rotate/resize |
| | | |
| | | |
| | | if (mouse_target == selectorManager.selectorParentGroup && selectedElements[0] != null) { |
| | | var grip = evt.target; |
| | | var griptype = elData(grip, "type"); |
| | |
| | | } |
| | | mouse_target = selectedElements[0]; |
| | | } |
| | | |
| | | |
| | | start_transform = mouse_target.getAttribute("transform"); |
| | | var tlist = getTransformList(mouse_target); |
| | | switch (current_mode) { |
| | |
| | | started = true; |
| | | current_resize_mode = "none"; |
| | | if(right_click) started = false; |
| | | |
| | | |
| | | if (mouse_target != svgroot) { |
| | | // if this element is not yet selected, clear selection and select it |
| | | if (selectedElements.indexOf(mouse_target) == -1) { |
| | | // only clear selection if shift is not pressed (otherwise, add |
| | | // only clear selection if shift is not pressed (otherwise, add |
| | | // element to selection) |
| | | if (!evt.shiftKey) { |
| | | // No need to do the call here as it will be done on addToSelection |
| | |
| | | pathActions.clear(); |
| | | } |
| | | // else if it's a path, go into pathedit mode in mouseup |
| | | |
| | | |
| | | if(!right_click) { |
| | | // insert a dummy transform so if the element(s) are moved it will have |
| | | // a transform to use for its translate |
| | |
| | | } |
| | | r_start_x *= current_zoom; |
| | | r_start_y *= current_zoom; |
| | | // console.log('p',[evt.pageX, evt.pageY]); |
| | | // console.log('c',[evt.clientX, evt.clientY]); |
| | | // console.log('o',[evt.offsetX, evt.offsetY]); |
| | | // console.log('p',[evt.pageX, evt.pageY]); |
| | | // console.log('c',[evt.clientX, evt.clientY]); |
| | | // console.log('o',[evt.offsetX, evt.offsetY]); |
| | | // console.log('s',[start_x, start_y]); |
| | | |
| | | |
| | | assignAttributes(rubberBox, { |
| | | 'x': r_start_x, |
| | | 'y': r_start_y, |
| | |
| | | }, 100); |
| | | } |
| | | break; |
| | | case "zoom": |
| | | case "zoom": |
| | | started = true; |
| | | if (rubberBox == null) { |
| | | rubberBox = selectorManager.getRubberBandBox(); |
| | |
| | | started = true; |
| | | start_x = x; |
| | | start_y = y; |
| | | |
| | | |
| | | // Getting the BBox from the selection box, since we know we |
| | | // want to orient around it |
| | | init_bbox = svgedit.utilities.getBBox($('#selectedBox0')[0]); |
| | |
| | | // append three dummy transforms to the tlist so that |
| | | // we can translate,scale,translate in mousemove |
| | | var pos = getRotationAngle(mouse_target)?1:0; |
| | | |
| | | |
| | | if(hasMatrixTransform(tlist)) { |
| | | tlist.insertItemBefore(svgroot.createSVGTransform(), pos); |
| | | tlist.insertItemBefore(svgroot.createSVGTransform(), pos); |
| | |
| | | tlist.appendItem(svgroot.createSVGTransform()); |
| | | tlist.appendItem(svgroot.createSVGTransform()); |
| | | tlist.appendItem(svgroot.createSVGTransform()); |
| | | |
| | | |
| | | if(svgedit.browser.supportsNonScalingStroke()) { |
| | | //Handle crash for newer Webkit: https://code.google.com/p/svg-edit/issues/detail?id=904 |
| | | //Chromium issue: https://code.google.com/p/chromium/issues/detail?id=114625 |
| | |
| | | // This could occur in an extension |
| | | break; |
| | | } |
| | | |
| | | |
| | | var ext_result = runExtensions("mouseDown", { |
| | | event: evt, |
| | | start_x: start_x, |
| | | start_y: start_y, |
| | | selectedElements: selectedElements |
| | | }, true); |
| | | |
| | | |
| | | $.each(ext_result, function(i, r) { |
| | | if(r && r.started) { |
| | | started = true; |
| | | } |
| | | }); |
| | | if (current_mode) { |
| | | document.getElementById("workarea").className = |
| | | document.getElementById("workarea").className = |
| | | (current_mode == "resize") |
| | | ? evt.target.style.cursor |
| | | : current_mode |
| | | } |
| | | }; |
| | | |
| | | |
| | | // in this function we do not record any state changes yet (but we do update |
| | | // any elements that are still being created, moved or resized on the canvas) |
| | | var mouseMove = function(evt) { |
| | |
| | | } |
| | | |
| | | evt.preventDefault(); |
| | | |
| | | |
| | | switch (current_mode) |
| | | { |
| | | case "select": |
| | | // we temporarily use a translate on the element(s) being dragged |
| | | // this transform is removed upon mousing up and the element is |
| | | // this transform is removed upon mousing up and the element is |
| | | // relocated to the new location |
| | | if (selectedElements[0] !== null) { |
| | | var dx = x - start_x; |
| | | var dy = y - start_y; |
| | | |
| | | |
| | | if(curConfig.gridSnapping){ |
| | | dx = snapToGrid(dx); |
| | | dy = snapToGrid(dy); |
| | | } |
| | | |
| | | if(evt.shiftKey) { |
| | | |
| | | if(evt.shiftKey) { |
| | | var xya = snapToAngle(start_x,start_y,x,y); x=xya.x; y=xya.y; |
| | | } |
| | | if (dx != 0 || dy != 0) { |
| | |
| | | } else { |
| | | tlist.appendItem(xform); |
| | | } |
| | | |
| | | |
| | | // update our internal bbox that we're tracking while dragging |
| | | selectorManager.requestSelector(selected).resize(); |
| | | } |
| | | |
| | | //duplicate only once |
| | | // alt drag = create a clone and save the reference |
| | | // alt drag = create a clone and save the reference |
| | | if(evt.altKey) { |
| | | //clone doesn't exist yet |
| | | if (!canvas.addClones) { |
| | |
| | | window.addEventListener("keyup", canvas.removeClones) |
| | | } |
| | | } |
| | | |
| | | |
| | | call("transition", selectedElements); |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | } |
| | | break; |
| | | case "multiselect": |
| | |
| | | var elemsToRemove = [], elemsToAdd = [], |
| | | newList = getIntersectionList(), |
| | | len = selectedElements.length; |
| | | |
| | | |
| | | for (var i = 0; i < len; ++i) { |
| | | var ind = newList.indexOf(selectedElements[i]); |
| | | if (ind == -1) { |
| | |
| | | newList[ind] = null; |
| | | } |
| | | } |
| | | |
| | | |
| | | len = newList.length; |
| | | for (i = 0; i < len; ++i) { if (newList[i]) elemsToAdd.push(newList[i]); } |
| | | |
| | | if (elemsToRemove.length > 0) |
| | | |
| | | if (elemsToRemove.length > 0) |
| | | canvas.removeFromSelection(elemsToRemove); |
| | | |
| | | if (elemsToAdd.length > 0) |
| | | |
| | | if (elemsToAdd.length > 0) |
| | | addToSelection(elemsToAdd); |
| | | |
| | | |
| | | break; |
| | | case "resize": |
| | | // we track the resize bounding box and translate/scale the selected element |
| | |
| | | // the shape's coordinates |
| | | var tlist = getTransformList(selected), |
| | | hasMatrix = hasMatrixTransform(tlist), |
| | | box = hasMatrix ? init_bbox : svgedit.utilities.getBBox(selected), |
| | | box = hasMatrix ? init_bbox : svgedit.utilities.getBBox(selected), |
| | | left=box.x, top=box.y, width=box.width, |
| | | height=box.height, dx=(x-start_x), dy=(y-start_y); |
| | | |
| | | |
| | | if(curConfig.gridSnapping){ |
| | | dx = snapToGrid(dx); |
| | | dy = snapToGrid(dy); |
| | |
| | | } |
| | | if(current_resize_mode.indexOf("e")==-1 && current_resize_mode.indexOf("w")==-1) { |
| | | dx = 0; |
| | | } |
| | | |
| | | } |
| | | |
| | | var ts = null, |
| | | tx = 0, ty = 0, |
| | | sy = height ? (height+dy)/height : 1, |
| | | sy = height ? (height+dy)/height : 1, |
| | | sx = width ? (width+dx)/width : 1; |
| | | // if we are dragging on the north side, then adjust the scale factor and ty |
| | | if(current_resize_mode.indexOf("n") >= 0) { |
| | | sy = height ? (height-dy)/height : 1; |
| | | ty = height; |
| | | } |
| | | |
| | | |
| | | // if we dragging on the east side, then adjust the scale factor and tx |
| | | if(current_resize_mode.indexOf("w") >= 0) { |
| | | sx = width ? (width-dx)/width : 1; |
| | | tx = width; |
| | | } |
| | | |
| | | |
| | | // update the transform list with translate,scale,translate |
| | | var translateOrigin = svgroot.createSVGTransform(), |
| | | scale = svgroot.createSVGTransform(), |
| | |
| | | else sy = sx; |
| | | } |
| | | scale.setScale(sx,sy); |
| | | |
| | | |
| | | translateBack.setTranslate(left+tx,top+ty); |
| | | if(hasMatrix) { |
| | | var diff = angle?1:0; |
| | |
| | | } |
| | | |
| | | selectorManager.requestSelector(selected).resize(); |
| | | |
| | | |
| | | call("transition", selectedElements); |
| | | |
| | | |
| | | break; |
| | | case "zoom": |
| | | real_x *= current_zoom; |
| | |
| | | 'y': Math.min(r_start_y*current_zoom, real_y), |
| | | 'width': Math.abs(real_x - r_start_x*current_zoom), |
| | | 'height': Math.abs(real_y - r_start_y*current_zoom) |
| | | },100); |
| | | },100); |
| | | break; |
| | | case "text": |
| | | assignAttributes(shape,{ |
| | |
| | | } |
| | | |
| | | var x2 = x; |
| | | var y2 = y; |
| | | var y2 = y; |
| | | |
| | | if(evt.shiftKey) { var xya = snapToAngle(start_x,start_y,x2,y2); x2=xya.x; y2=xya.y; } |
| | | |
| | | |
| | | shape.setAttributeNS(null, "x2", x2); |
| | | shape.setAttributeNS(null, "y2", y2); |
| | | break; |
| | |
| | | } |
| | | if (evt.altKey){ |
| | | w *=2; |
| | | h *=2; |
| | | h *=2; |
| | | new_x = start_x - w/2; |
| | | new_y = start_y - h/2; |
| | | } |
| | | |
| | | |
| | | if(curConfig.gridSnapping){ |
| | | w = snapToGrid(w); |
| | | h = snapToGrid(h); |
| | |
| | | 'x': new_x, |
| | | 'y': new_y |
| | | },1000); |
| | | |
| | | |
| | | break; |
| | | case "circle": |
| | | var c = $(shape).attr(["cx", "cy"]); |
| | |
| | | if (evt.shiftKey) { |
| | | ry = rx |
| | | cy = (y > start_y) ? start_y + rx : start_y - rx |
| | | |
| | | |
| | | } |
| | | if (evt.altKey) { |
| | | cx = start_x |
| | |
| | | case "pathedit": |
| | | x *= current_zoom; |
| | | y *= current_zoom; |
| | | |
| | | |
| | | if(curConfig.gridSnapping){ |
| | | x = snapToGrid(x); |
| | | y = snapToGrid(y); |
| | |
| | | var xya = snapToAngle(x1,y1,x,y); |
| | | x=xya.x; y=xya.y; |
| | | } |
| | | |
| | | |
| | | if(rubberBox && rubberBox.getAttribute('display') !== 'none') { |
| | | real_x *= current_zoom; |
| | | real_y *= current_zoom; |
| | |
| | | 'y': Math.min(r_start_y*current_zoom, real_y), |
| | | 'width': Math.abs(real_x - r_start_x*current_zoom), |
| | | 'height': Math.abs(real_y - r_start_y*current_zoom) |
| | | },100); |
| | | },100); |
| | | } |
| | | pathActions.mouseMove(evt, x, y); |
| | | |
| | | |
| | | break; |
| | | case "textedit": |
| | | x *= current_zoom; |
| | |
| | | // 'height': Math.abs(y-start_y) |
| | | // },100); |
| | | // } |
| | | |
| | | |
| | | textActions.mouseMove(mouse_x, mouse_y); |
| | | |
| | | |
| | | break; |
| | | case "rotate": |
| | | var box = svgedit.utilities.getBBox(selected), |
| | | cx = box.x + box.width/2, |
| | | cx = box.x + box.width/2, |
| | | cy = box.y + box.height/2, |
| | | m = getMatrix(selected), |
| | | center = transformPoint(cx,cy,m); |
| | |
| | | default: |
| | | break; |
| | | } |
| | | |
| | | |
| | | runExtensions("mouseMove", { |
| | | event: evt, |
| | | mouse_x: mouse_x, |
| | |
| | | }); |
| | | |
| | | }; // mouseMove() |
| | | |
| | | |
| | | |
| | | |
| | | /* mouseover mode |
| | | var mouseOver = function(evt) { |
| | | |
| | | |
| | | if(canvas.spaceKey || evt.button === 1 || current_mode != "select") return; |
| | | evt.stopPropagation(); |
| | | mouse_target = getMouseTarget(evt); |
| | | if (svghover.lastChild) svghover.removeChild(svghover.lastChild); |
| | | |
| | | |
| | | if (mouse_target.id == "svgroot") return |
| | | switch (mouse_target.nodeName) { |
| | | case "polyline": |
| | |
| | | case "path": |
| | | case "ellipse": |
| | | case "rect": |
| | | var clone = mouse_target.cloneNode(true); |
| | | var clone = mouse_target.cloneNode(true); |
| | | clone.setAttribute("stroke", "#c00") |
| | | clone.setAttribute("stroke-width", "1") |
| | | clone.setAttribute("stroke-opacity", "1") |
| | |
| | | clone.setAttribute("fill", "none") |
| | | hover_group.appendChild(clone); |
| | | break; |
| | | |
| | | |
| | | default: |
| | | break; |
| | | } |
| | | } |
| | | */ |
| | | |
| | | |
| | | // - in create mode, the element's opacity is set properly, we create an InsertElementCommand |
| | | // and store it on the Undo stack |
| | | // - in move/resize mode, the element's attributes which were affected by the move/resize are |
| | |
| | | cur_text.font_family = selected.getAttribute("font-family"); |
| | | } |
| | | selectorManager.requestSelector(selected).showGrips(true); |
| | | |
| | | |
| | | // This shouldn't be necessary as it was done on mouseDown... |
| | | // call("selected", [selected]); |
| | | } |
| | |
| | | recalculateAllSelectedDimensions(); |
| | | |
| | | // if it was being dragged/resized |
| | | r_start_x = r_start_x; |
| | | r_start_y = r_start_y; |
| | | r_start_x = r_start_x; |
| | | r_start_y = r_start_y; |
| | | var difference_x = Math.abs(real_x-r_start_x); |
| | | var difference_y = Math.abs(real_y-r_start_y); |
| | | |
| | |
| | | } |
| | | } |
| | | } // no change in mouse position |
| | | |
| | | |
| | | // Remove non-scaling stroke |
| | | if(svgedit.browser.supportsNonScalingStroke()) { |
| | | var elem = selectedElements[0]; |
| | |
| | | element = null; |
| | | // continue to be set to true so that mouseMove happens |
| | | started = true; |
| | | |
| | | |
| | | var res = pathActions.mouseUp(evt, element, mouse_x, mouse_y); |
| | | element = res.element; |
| | | keep = res.keep; |
| | |
| | | element = null; |
| | | current_mode = "select"; |
| | | var batchCmd = canvas.undoMgr.finishUndoableChange(); |
| | | if (!batchCmd.isEmpty()) { |
| | | if (!batchCmd.isEmpty()) { |
| | | addCommandToHistory(batchCmd); |
| | | } |
| | | // perform recalculation to weed out any stray identity transforms that might get stuck |
| | |
| | | // This could occur in an extension |
| | | break; |
| | | } |
| | | |
| | | |
| | | var ext_result = runExtensions("mouseUp", { |
| | | event: evt, |
| | | mouse_x: mouse_x, |
| | | mouse_y: mouse_y |
| | | }, true); |
| | | |
| | | |
| | | $.each(ext_result, function(i, r) { |
| | | if(r) { |
| | | keep = r.keep || keep; |
| | |
| | | started = r.started || started; |
| | | } |
| | | }); |
| | | |
| | | |
| | | if (!keep && element != null) { |
| | | getCurrentDrawing().releaseId(getId()); |
| | | element.parentNode.removeChild(element); |
| | | element = null; |
| | | |
| | | |
| | | var t = evt.target; |
| | | |
| | | // if this element is in a group, go up until we reach the top-level group |
| | | |
| | | // if this element is in a group, go up until we reach the top-level group |
| | | // just below the layer groups |
| | | // TODO: once we implement links, we also would have to check for <a> elements |
| | | while (t.parentNode.parentNode.tagName == "g") { |
| | | t = t.parentNode; |
| | | } |
| | | // if we are not in the middle of creating a path, and we've clicked on some shape, |
| | | // if we are not in the middle of creating a path, and we've clicked on some shape, |
| | | // then go to Select mode. |
| | | // WebKit returns <div> when the canvas is clicked, Firefox/Opera return <svg> |
| | | if ( (current_mode != "path" || !drawn_path) && |
| | | t.parentNode.id != "selectorParentGroup" && |
| | | t.id != "svgcanvas" && t.id != "svgroot") |
| | | t.id != "svgcanvas" && t.id != "svgroot") |
| | | { |
| | | // switch into "select" mode if we've clicked on an element |
| | | canvas.setMode("select"); |
| | | selectOnly([t], true); |
| | | } |
| | | |
| | | |
| | | } else if (element != null) { |
| | | canvas.addedNew = true; |
| | | |
| | | |
| | | if(useUnit) svgedit.units.convertAttrs(element); |
| | | |
| | | |
| | | var ani_dur = .2, c_ani; |
| | | if(opac_ani.beginElement && element.getAttribute('opacity') != cur_shape.opacity) { |
| | | c_ani = $(opac_ani).clone().attr({ |
| | |
| | | } else { |
| | | ani_dur = 0; |
| | | } |
| | | |
| | | |
| | | // Ideally this would be done on the endEvent of the animation, |
| | | // but that doesn't seem to be supported in Webkit |
| | | setTimeout(function() { |
| | |
| | | // we create the insert command that is stored on the stack |
| | | // undo means to call cmd.unapply(), redo means to call cmd.apply() |
| | | addCommandToHistory(new InsertElementCommand(element)); |
| | | |
| | | |
| | | call("changed",[element]); |
| | | }, ani_dur * 1000); |
| | | } |
| | | |
| | | |
| | | start_transform = null; |
| | | }; |
| | | |
| | | |
| | | var dblClick = function(evt) { |
| | | var evt_target = evt.target; |
| | | var parent = evt_target.parentNode; |
| | |
| | | var tagName = mouse_target.tagName; |
| | | |
| | | if(parent === current_group) return; |
| | | |
| | | |
| | | if(tagName === 'text' && current_mode !== 'textedit') { |
| | | var pt = transformPoint( evt.pageX, evt.pageY, root_sctm ); |
| | | textActions.select(mouse_target, pt.x, pt.y); |
| | | } |
| | | |
| | | |
| | | if((tagName === "g" || tagName === "a") && getRotationAngle(mouse_target)) { |
| | | // TODO: Allow method of in-group editing without having to do |
| | | // TODO: Allow method of in-group editing without having to do |
| | | // this (similar to editing rotated paths) |
| | | |
| | | |
| | | // Ungroup and regroup |
| | | pushGroupProperties(mouse_target); |
| | | mouse_target = selectedElements[0]; |
| | |
| | | if(current_group) { |
| | | leaveContext(); |
| | | } |
| | | |
| | | |
| | | if((parent.tagName !== 'g' && parent.tagName !== 'a') || |
| | | parent === getCurrentDrawing().getCurrentLayer() || |
| | | mouse_target === selectorManager.selectorParentGroup) |
| | |
| | | e.preventDefault(); |
| | | return false; |
| | | }; |
| | | |
| | | |
| | | // Added mouseup to the container here. |
| | | // TODO(codedread): Figure out why after the Closure compiler, the window mouseup is ignored. |
| | | $(container).mousedown(mouseDown).mousemove(mouseMove).click(handleLinkInCanvas).dblclick(dblClick).mouseup(mouseUp); |
| | | // $(window).mouseup(mouseUp); |
| | | |
| | | |
| | | $(container).bind("mousewheel DOMMouseScroll", function(e){ |
| | | if(!e.shiftKey) return; |
| | | e.preventDefault(); |
| | |
| | | if (e.detail > 0) { |
| | | bbox.factor = .5; |
| | | } else if (e.detail < 0) { |
| | | bbox.factor = 2; |
| | | } |
| | | bbox.factor = 2; |
| | | } |
| | | } |
| | | |
| | | |
| | | if(!bbox.factor) return; |
| | | call("zoomed", bbox); |
| | | }); |
| | | |
| | | |
| | | }()); |
| | | |
| | | // Function: preventClickDefault |
| | |
| | | var matrix; |
| | | var last_x, last_y; |
| | | var allow_dbl; |
| | | |
| | | |
| | | function setCursor(index) { |
| | | var empty = (textinput.value === ""); |
| | | $(textinput).focus(); |
| | | |
| | | |
| | | if(!arguments.length) { |
| | | if(empty) { |
| | | index = 0; |
| | |
| | | index = textinput.selectionEnd; |
| | | } |
| | | } |
| | | |
| | | |
| | | var charbb; |
| | | charbb = chardata[index]; |
| | | if(!empty) { |
| | |
| | | }); |
| | | cursor = getElem("selectorParentGroup").appendChild(cursor); |
| | | } |
| | | |
| | | |
| | | if(!blinker) { |
| | | blinker = setInterval(function() { |
| | | var show = (cursor.getAttribute('display') === 'none'); |
| | |
| | | }, 600); |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | var start_pt = ptToScreen(charbb.x, textbb.y); |
| | | var end_pt = ptToScreen(charbb.x, (textbb.y + textbb.height)); |
| | | |
| | | |
| | | assignAttributes(cursor, { |
| | | x1: start_pt.x, |
| | | y1: start_pt.y, |
| | |
| | | visibility: 'visible', |
| | | display: 'inline' |
| | | }); |
| | | |
| | | |
| | | if(selblock) selblock.setAttribute('d', 'M 0 0'); |
| | | } |
| | | |
| | | |
| | | function setSelection(start, end, skipInput) { |
| | | if(start === end) { |
| | | setCursor(end); |
| | | return; |
| | | } |
| | | |
| | | |
| | | if(!skipInput) { |
| | | textinput.setSelectionRange(start, end); |
| | | } |
| | | |
| | | |
| | | selblock = getElem("text_selectblock"); |
| | | if (!selblock) { |
| | | |
| | |
| | | getElem("selectorParentGroup").appendChild(selblock); |
| | | } |
| | | |
| | | |
| | | |
| | | var startbb = chardata[start]; |
| | | |
| | | |
| | | var endbb = chardata[end]; |
| | | |
| | | |
| | | cursor.setAttribute('visibility', 'hidden'); |
| | | |
| | | |
| | | var tl = ptToScreen(startbb.x, textbb.y), |
| | | tr = ptToScreen(startbb.x + (endbb.x - startbb.x), textbb.y), |
| | | bl = ptToScreen(startbb.x, textbb.y + textbb.height), |
| | | br = ptToScreen(startbb.x + (endbb.x - startbb.x), textbb.y + textbb.height); |
| | | |
| | | |
| | | |
| | | |
| | | var dstr = "M" + tl.x + "," + tl.y |
| | | + " L" + tr.x + "," + tr.y |
| | | + " " + br.x + "," + br.y |
| | | + " " + bl.x + "," + bl.y + "z"; |
| | | |
| | | |
| | | assignAttributes(selblock, { |
| | | d: dstr, |
| | | 'display': 'inline' |
| | | }); |
| | | } |
| | | |
| | | |
| | | function getIndexFromPoint(mouse_x, mouse_y) { |
| | | // Position cursor here |
| | | var pt = svgroot.createSVGPoint(); |
| | |
| | | } |
| | | return charpos; |
| | | } |
| | | |
| | | |
| | | function setCursorFromPoint(mouse_x, mouse_y) { |
| | | setCursor(getIndexFromPoint(mouse_x, mouse_y)); |
| | | } |
| | | |
| | | |
| | | function setEndSelectionFromPoint(x, y, apply) { |
| | | var i1 = textinput.selectionStart; |
| | | var i2 = getIndexFromPoint(x, y); |
| | | |
| | | |
| | | var start = Math.min(i1, i2); |
| | | var end = Math.max(i1, i2); |
| | | setSelection(start, end, !apply); |
| | | } |
| | | |
| | | |
| | | function screenToPt(x_in, y_in) { |
| | | var out = { |
| | | x: x_in, |
| | | y: y_in |
| | | } |
| | | |
| | | |
| | | out.x /= current_zoom; |
| | | out.y /= current_zoom; |
| | | out.y /= current_zoom; |
| | | |
| | | if(matrix) { |
| | | var pt = transformPoint(out.x, out.y, matrix.inverse()); |
| | | out.x = pt.x; |
| | | out.y = pt.y; |
| | | } |
| | | |
| | | |
| | | return out; |
| | | } |
| | | |
| | | } |
| | | |
| | | function ptToScreen(x_in, y_in) { |
| | | var out = { |
| | | x: x_in, |
| | | y: y_in |
| | | } |
| | | |
| | | |
| | | if(matrix) { |
| | | var pt = transformPoint(out.x, out.y, matrix); |
| | | out.x = pt.x; |
| | | out.y = pt.y; |
| | | } |
| | | |
| | | |
| | | out.x *= current_zoom; |
| | | out.y *= current_zoom; |
| | | |
| | | |
| | | return out; |
| | | } |
| | | |
| | | |
| | | function hideCursor() { |
| | | if(cursor) { |
| | | cursor.setAttribute('visibility', 'hidden'); |
| | | } |
| | | } |
| | | |
| | | |
| | | function selectAll(evt) { |
| | | setSelection(0, curtext.textContent.length); |
| | | $(this).unbind(evt); |
| | |
| | | |
| | | function selectWord(evt) { |
| | | if(!allow_dbl || !curtext) return; |
| | | |
| | | |
| | | var ept = transformPoint( evt.pageX, evt.pageY, root_sctm ), |
| | | mouse_x = ept.x * current_zoom, |
| | | mouse_y = ept.y * current_zoom; |
| | | var pt = screenToPt(mouse_x, mouse_y); |
| | | |
| | | |
| | | var index = getIndexFromPoint(pt.x, pt.y); |
| | | var str = curtext.textContent; |
| | | var first = str.substr(0, index).replace(/[a-z0-9]+$/i, '').length; |
| | | var m = str.substr(index).match(/^[a-z0-9]+/i); |
| | | var last = (m?m[0].length:0) + index; |
| | | setSelection(first, last); |
| | | |
| | | |
| | | // Set tripleclick |
| | | $(evt.target).click(selectAll); |
| | | setTimeout(function() { |
| | | $(evt.target).unbind('click', selectAll); |
| | | }, 300); |
| | | |
| | | |
| | | } |
| | | |
| | | return { |
| | |
| | | }, |
| | | mouseDown: function(evt, mouse_target, start_x, start_y) { |
| | | var pt = screenToPt(start_x, start_y); |
| | | |
| | | |
| | | textinput.focus(); |
| | | setCursorFromPoint(pt.x, pt.y); |
| | | last_x = start_x; |
| | | last_y = start_y; |
| | | |
| | | |
| | | // TODO: Find way to block native selection |
| | | }, |
| | | mouseMove: function(mouse_x, mouse_y) { |
| | | var pt = screenToPt(mouse_x, mouse_y); |
| | | setEndSelectionFromPoint(pt.x, pt.y); |
| | | }, |
| | | }, |
| | | mouseUp: function(evt, mouse_x, mouse_y) { |
| | | var pt = screenToPt(mouse_x, mouse_y); |
| | | |
| | | |
| | | setEndSelectionFromPoint(pt.x, pt.y, true); |
| | | |
| | | // TODO: Find a way to make this work: Use transformed BBox instead of evt.target |
| | | |
| | | // TODO: Find a way to make this work: Use transformed BBox instead of evt.target |
| | | // if(last_x === mouse_x && last_y === mouse_y |
| | | // && !svgedit.math.rectsIntersect(transbb, {x: pt.x, y: pt.y, width:0, height:0})) { |
| | | // textActions.toSelectMode(true); |
| | | // textActions.toSelectMode(true); |
| | | // } |
| | | |
| | | if( |
| | |
| | | allow_dbl = false; |
| | | current_mode = "textedit"; |
| | | selectorManager.requestSelector(curtext).showGrips(false); |
| | | |
| | | |
| | | // Make selector group accept clicks |
| | | var sel = selectorManager.requestSelector(curtext).selectorRect; |
| | | |
| | | |
| | | textActions.init(); |
| | | |
| | | $(curtext).css('cursor', 'text'); |
| | | |
| | | |
| | | if(!arguments.length) { |
| | | setCursor(); |
| | | } else { |
| | | var pt = screenToPt(x, y); |
| | | setCursorFromPoint(pt.x, pt.y); |
| | | } |
| | | |
| | | |
| | | setTimeout(function() { |
| | | allow_dbl = true; |
| | | }, 300); |
| | |
| | | if(selblock) $(selblock).attr('display','none'); |
| | | if(cursor) $(cursor).attr('visibility','hidden'); |
| | | $(curtext).css('cursor', 'move'); |
| | | |
| | | |
| | | if(selectElem) { |
| | | clearSelection(); |
| | | $(curtext).css('cursor', 'move'); |
| | | |
| | | |
| | | call("selected", [curtext]); |
| | | addToSelection([curtext], true); |
| | | } |
| | |
| | | // No content, so delete |
| | | canvas.deleteSelectedElements(); |
| | | } |
| | | |
| | | |
| | | $(textinput).blur(); |
| | | |
| | | |
| | | curtext = false; |
| | | |
| | | |
| | | // if(svgedit.browser.supportsEditableText()) { |
| | | // curtext.removeAttribute('editable'); |
| | | // } |
| | |
| | | // curtext.select(); |
| | | // return; |
| | | // } |
| | | |
| | | |
| | | if(!curtext.parentNode) { |
| | | // Result of the ffClone, need to get correct element |
| | | curtext = selectedElements[0]; |
| | | selectorManager.requestSelector(curtext).showGrips(false); |
| | | } |
| | | |
| | | |
| | | var str = curtext.textContent; |
| | | var len = str.length; |
| | | |
| | | |
| | | var xform = curtext.getAttribute('transform'); |
| | | |
| | | textbb = svgedit.utilities.getBBox(curtext); |
| | | |
| | | |
| | | matrix = xform?getMatrix(curtext):null; |
| | | |
| | | chardata = Array(len); |
| | | textinput.focus(); |
| | | |
| | | |
| | | $(curtext).unbind('dblclick', selectWord).dblclick(selectWord); |
| | | |
| | | |
| | | if(!len) { |
| | | var end = {x: textbb.x + (textbb.width/2), width: 0}; |
| | | } |
| | | |
| | | |
| | | for(var i=0; i<len; i++) { |
| | | var start = curtext.getStartPositionOfChar(i); |
| | | var end = curtext.getEndPositionOfChar(i); |
| | | |
| | | |
| | | if(!svgedit.browser.supportsGoodTextCharPos()) { |
| | | var offset = canvas.contentW * current_zoom; |
| | | start.x -= offset; |
| | | end.x -= offset; |
| | | |
| | | |
| | | start.x /= current_zoom; |
| | | end.x /= current_zoom; |
| | | } |
| | | |
| | | |
| | | // Get a "bbox" equivalent for each character. Uses the |
| | | // bbox data of the actual text for y, height purposes |
| | | |
| | | |
| | | // TODO: Decide if y, width and height are actually necessary |
| | | chardata[i] = { |
| | | x: start.x, |
| | |
| | | height: textbb.height |
| | | }; |
| | | } |
| | | |
| | | |
| | | // Add a last bbox for cursor at end of text |
| | | chardata.push({ |
| | | x: end.x, |
| | |
| | | // Group: Path edit functions |
| | | // Functions relating to editing path elements |
| | | var pathActions = canvas.pathActions = function() { |
| | | |
| | | |
| | | var subpath = false; |
| | | var current_path; |
| | | var newPoint, firstCtrl; |
| | | |
| | | |
| | | function resetD(p) { |
| | | p.setAttribute("d", pathActions.convertPath(p)); |
| | | } |
| | |
| | | } |
| | | // TODO: Correct this: |
| | | pathActions.canDeleteNodes = true; |
| | | |
| | | |
| | | pathActions.closed_subpath = this.subpathIsClosed(this.selected_pts[0]); |
| | | |
| | | |
| | | call("selected", grips); |
| | | } |
| | | |
| | |
| | | stretchy = null; |
| | | |
| | | this.lastCtrlPoint = [0, 0]; |
| | | |
| | | |
| | | // This function converts a polyline (created by the fh_path tool) into |
| | | // a path element and coverts every three line segments into a single bezier |
| | | // curve in an attempt to smooth out the free-hand |
| | |
| | | var N = points.numberOfItems; |
| | | if (N >= 4) { |
| | | // loop through every 3 points and convert to a cubic bezier curve segment |
| | | // |
| | | // NOTE: this is cheating, it means that every 3 points has the potential to |
| | | // |
| | | // NOTE: this is cheating, it means that every 3 points has the potential to |
| | | // be a corner instead of treating each point in an equal manner. In general, |
| | | // this technique does not look that good. |
| | | // |
| | | // |
| | | // I am open to better ideas! |
| | | // |
| | | // |
| | | // Reading: |
| | | // - http://www.efg2.com/Lab/Graphics/Jean-YvesQueinecBezierCurves.htm |
| | | // - http://www.codeproject.com/KB/graphics/BezierSpline.aspx?msg=2956963 |
| | |
| | | var ct1 = points.getItem(i); |
| | | var ct2 = points.getItem(i+1); |
| | | var end = points.getItem(i+2); |
| | | |
| | | |
| | | // if the previous segment had a control point, we want to smooth out |
| | | // the control points on both sides |
| | | if (prevCtlPt) { |
| | |
| | | ct1 = newpts[1]; |
| | | } |
| | | } |
| | | |
| | | |
| | | d.push([ct1.x,ct1.y,ct2.x,ct2.y,end.x,end.y].join(',')); |
| | | |
| | | |
| | | curpos = end; |
| | | prevCtlPt = ct2; |
| | | } |
| | |
| | | if(current_mode === "path") { |
| | | mouse_x = start_x; |
| | | mouse_y = start_y; |
| | | |
| | | |
| | | var x = mouse_x/current_zoom, |
| | | y = mouse_y/current_zoom, |
| | | stretchy = getElem("path_stretch_line"); |
| | | newPoint = [x, y]; |
| | | |
| | | newPoint = [x, y]; |
| | | |
| | | if(curConfig.gridSnapping){ |
| | | x = snapToGrid(x); |
| | | y = snapToGrid(y); |
| | |
| | | stretchy.setAttribute("display", "inline"); |
| | | |
| | | this.stretchy = stretchy; |
| | | |
| | | |
| | | var keep = null; |
| | | |
| | | |
| | | // if pts array is empty, create path element with M at current point |
| | | if (!drawn_path) { |
| | | d_attr = "M" + x + "," + y + " "; |
| | |
| | | break; |
| | | } |
| | | } |
| | | |
| | | |
| | | // get path element that we are in the process of creating |
| | | var id = getId(); |
| | | |
| | | |
| | | // Remove previous path object if previously created |
| | | svgedit.path.removePath_(id); |
| | | |
| | | |
| | | var newpath = getElem(id); |
| | | |
| | | |
| | | var len = seglist.numberOfItems; |
| | | // if we clicked on an existing point, then we are done this path, commit it |
| | | // (i,i+1) are the x,y that were clicked on |
| | |
| | | var abs_y = seglist.getItem(0).y; |
| | | var grip_x = svgedit.path.first_grip ? svgedit.path.first_grip[0]/current_zoom : seglist.getItem(0).x; |
| | | var grip_y = svgedit.path.first_grip ? svgedit.path.first_grip[1]/current_zoom : seglist.getItem(0).y; |
| | | |
| | | |
| | | |
| | | var s_seg = stretchy.pathSegList.getItem(1); |
| | | if(s_seg.pathSegType === 4) { |
| | |
| | | return keep; |
| | | } |
| | | $(stretchy).remove(); |
| | | |
| | | |
| | | // this will signal to commit the path |
| | | element = newpath; |
| | | drawn_path = null; |
| | |
| | | if(svgedit.path.path.matrix) { |
| | | remapElement(newpath, {}, svgedit.path.path.matrix.inverse()); |
| | | } |
| | | |
| | | |
| | | var new_d = newpath.getAttribute("d"); |
| | | var orig_d = $(svgedit.path.path.elem).attr("d"); |
| | | $(svgedit.path.path.elem).attr("d", orig_d + new_d); |
| | |
| | | var lastx = last.x, lasty = last.y; |
| | | |
| | | if(evt.shiftKey) { var xya = snapToAngle(lastx,lasty,x,y); x=xya.x; y=xya.y; } |
| | | |
| | | |
| | | // Use the segment defined by stretchy |
| | | var s_seg = stretchy.pathSegList.getItem(1); |
| | | if(s_seg.pathSegType === 4) { |
| | |
| | | s_seg.y2 / current_zoom |
| | | ); |
| | | } |
| | | |
| | | |
| | | drawn_path.pathSegList.appendItem(newseg); |
| | | |
| | | |
| | | x *= current_zoom; |
| | | y *= current_zoom; |
| | | |
| | | |
| | | // update everything to the latest point |
| | | stretchy.setAttribute('d', ['M', x, y, x, y].join(' ')); |
| | | var pointGrip1 = svgedit.path.addCtrlGrip('1c1'); |
| | |
| | | } |
| | | return; |
| | | } |
| | | |
| | | |
| | | // TODO: Make sure current_path isn't null at this point |
| | | if(!svgedit.path.path) return; |
| | | |
| | | |
| | | svgedit.path.path.storeD(); |
| | | |
| | | |
| | | var id = evt.target.id; |
| | | if (id.substr(0,14) == "pathpointgrip_") { |
| | | // Select this point |
| | | var cur_pt = svgedit.path.path.cur_pt = parseInt(id.substr(14)); |
| | | svgedit.path.path.dragging = [start_x, start_y]; |
| | | var seg = svgedit.path.path.segs[cur_pt]; |
| | | |
| | | // only clear selection if shift is not pressed (otherwise, add |
| | | |
| | | // only clear selection if shift is not pressed (otherwise, add |
| | | // node to selection) |
| | | if (!evt.shiftKey) { |
| | | if(svgedit.path.path.selected_pts.length <= 1 || !seg.selected) { |
| | |
| | | } |
| | | } else if(id.indexOf("ctrlpointgrip_") == 0) { |
| | | svgedit.path.path.dragging = [start_x, start_y]; |
| | | |
| | | |
| | | var parts = id.split('_')[1].split('c'); |
| | | var cur_pt = parts[0]-0; |
| | | var ctrl_num = parts[1]-0; |
| | |
| | | if(!drawn_path) return; |
| | | var seglist = drawn_path.pathSegList; |
| | | var index = seglist.numberOfItems - 1; |
| | | var pointGrip1 = svgedit.path.addCtrlGrip('1c1'); |
| | | var pointGrip1 = svgedit.path.addCtrlGrip('1c1'); |
| | | var pointGrip2 = svgedit.path.addCtrlGrip('0c2'); |
| | | |
| | | if(newPoint) { |
| | |
| | | |
| | | var pt_x = newPoint[0]; |
| | | var pt_y = newPoint[1]; |
| | | |
| | | |
| | | // set curve |
| | | var seg = seglist.getItem(index); |
| | | var cur_x = mouse_x / current_zoom; |
| | | var cur_y = mouse_y / current_zoom; |
| | | var alt_x = (is_linked) ? (pt_x + (pt_x - cur_x)) : current_pointGrip2_x; |
| | | var alt_y = (is_linked) ? (pt_y + (pt_y - cur_y)) : current_pointGrip2_y; |
| | | |
| | | |
| | | |
| | | |
| | | pointGrip2.setAttribute('cx', alt_x * current_zoom); |
| | | pointGrip2.setAttribute('cy', alt_y * current_zoom); |
| | | pointGrip2.setAttribute('display', 'inline'); |
| | | |
| | | |
| | | |
| | | |
| | | var ctrlLine = svgedit.path.getCtrlLine(1); |
| | | var ctrlLine2 = svgedit.path.getCtrlLine(2); |
| | | assignAttributes(ctrlLine, { |
| | |
| | | y2: pt_y * current_zoom, |
| | | display: 'inline' |
| | | }); |
| | | |
| | | |
| | | |
| | | assignAttributes(ctrlLine2, { |
| | | x1: alt_x * current_zoom, |
| | |
| | | firstCtrl = [mouse_x, mouse_y]; |
| | | } else { |
| | | var last_x, last_y; |
| | | |
| | | |
| | | var last = seglist.getItem(index - 1); |
| | | var last_x = last.x; |
| | | var last_y = last.y |
| | | |
| | | |
| | | if(last.pathSegType === 6) { |
| | | last_x += (last_x - last.x2); |
| | | last_y += (last_y - last.y2); |
| | |
| | | if(prev.pathSegType === 6) { |
| | | var prev_x = this.lastCtrlPoint[0]/current_zoom || prev.x + (prev.x - prev.x2); |
| | | var prev_y = this.lastCtrlPoint[1]/current_zoom || prev.y + (prev.y - prev.y2); |
| | | svgedit.path.replacePathSeg(6, 1, [mouse_x, mouse_y, prev_x * current_zoom, prev_y * current_zoom, lastgripx, lastgripy], stretchy); |
| | | svgedit.path.replacePathSeg(6, 1, [mouse_x, mouse_y, prev_x * current_zoom, prev_y * current_zoom, lastgripx, lastgripy], stretchy); |
| | | } else if(firstCtrl) { |
| | | svgedit.path.replacePathSeg(6, 1, [mouse_x, mouse_y, firstCtrl[0], firstCtrl[1], mouse_x, mouse_y], stretchy); |
| | | } else { |
| | |
| | | if(!seg.next && !seg.prev) return; |
| | | var item = seg.item; |
| | | var rbb = rubberBox.getBBox(); |
| | | |
| | | |
| | | var pt = svgedit.path.getGripPt(seg); |
| | | var pt_bb = { |
| | | x: pt.x, |
| | |
| | | width: 0, |
| | | height: 0 |
| | | }; |
| | | |
| | | |
| | | var sel = svgedit.math.rectsIntersect(rbb, pt_bb); |
| | | |
| | | this.select(sel); |
| | |
| | | }); |
| | | |
| | | } |
| | | }, |
| | | }, |
| | | mouseUp: function(evt, element, mouse_x, mouse_y) { |
| | | var lastpointgrip = getElem('ctrlpointgrip_1c1'); |
| | | var firstpointgrip = getElem('ctrlpointgrip_0c2'); |
| | |
| | | element: element |
| | | } |
| | | } |
| | | |
| | | |
| | | // Edit mode |
| | | |
| | | |
| | | if (svgedit.path.path.dragging) { |
| | | var last_pt = svgedit.path.path.cur_pt; |
| | | |
| | | svgedit.path.path.dragging = false; |
| | | svgedit.path.path.dragctrl = false; |
| | | svgedit.path.path.update(); |
| | | |
| | | |
| | | |
| | | |
| | | if(hasMoved) { |
| | | svgedit.path.path.endChanges("Move path point(s)"); |
| | | } |
| | | |
| | | } |
| | | |
| | | if(!evt.shiftKey && !hasMoved) { |
| | | svgedit.path.path.selectPt(last_pt); |
| | | } |
| | | } |
| | | } |
| | | else if(rubberBox && rubberBox.getAttribute('display') != 'none') { |
| | | // Done with multi-node-select |
| | | rubberBox.setAttribute("display", "none"); |
| | | |
| | | |
| | | if(rubberBox.getAttribute('width') <= 2 && rubberBox.getAttribute('height') <= 2) { |
| | | pathActions.toSelectMode(evt.target); |
| | | } |
| | | |
| | | // else, move back to select mode |
| | | |
| | | // else, move back to select mode |
| | | } else { |
| | | pathActions.toSelectMode(evt.target); |
| | | } |
| | |
| | | svgedit.path.path.show(false); |
| | | current_path = false; |
| | | clearSelection(); |
| | | |
| | | |
| | | if(svgedit.path.path.matrix) { |
| | | // Rotated, so may need to re-calculate the center |
| | | svgedit.path.recalcRotatedPath(); |
| | | } |
| | | |
| | | |
| | | if(selPath) { |
| | | call("selected", [elem]); |
| | | addToSelection([elem], true); |
| | |
| | | } // going into pathedit mode |
| | | else { |
| | | current_path = target; |
| | | } |
| | | } |
| | | }, |
| | | reorient: function() { |
| | | var elem = selectedElements[0]; |
| | | if(!elem) return; |
| | | var angle = getRotationAngle(elem); |
| | | if(angle == 0) return; |
| | | |
| | | |
| | | var batchCmd = new BatchCommand("Reorient path"); |
| | | var changes = { |
| | | d: elem.getAttribute('d'), |
| | |
| | | batchCmd.addSubCommand(new ChangeElementCommand(elem, changes)); |
| | | clearSelection(); |
| | | this.resetOrientation(elem); |
| | | |
| | | |
| | | addCommandToHistory(batchCmd); |
| | | |
| | | // Set matrix to null |
| | | svgedit.path.getPath_(elem).show(false).matrix = null; |
| | | svgedit.path.getPath_(elem).show(false).matrix = null; |
| | | |
| | | this.clear(); |
| | | |
| | | |
| | | addToSelection([elem], true); |
| | | call("changed", selectedElements); |
| | | }, |
| | | |
| | | |
| | | clear: function(remove) { |
| | | current_path = null; |
| | | if (drawn_path) { |
| | |
| | | tlist.clear(); |
| | | path.removeAttribute("transform"); |
| | | var segList = path.pathSegList; |
| | | |
| | | |
| | | // Opera/win/non-EN throws an error here. |
| | | // TODO: Find out why! |
| | | // Presumed fixed in Opera 10.5, so commented out for now |
| | | |
| | | |
| | | // try { |
| | | var len = segList.numberOfItems; |
| | | // } catch(err) { |
| | |
| | | }); |
| | | svgedit.path.replacePathSeg(type, i, pts, path); |
| | | } |
| | | |
| | | |
| | | reorientGrads(path, m); |
| | | |
| | | |
| | |
| | | y: seg.item.y, |
| | | type: seg.type |
| | | }; |
| | | }, |
| | | }, |
| | | linkControlPoints: function(linkPoints) { |
| | | svgedit.path.setLinkControlPoints(linkPoints); |
| | | }, |
| | | clonePathNode: function() { |
| | | svgedit.path.path.storeD(); |
| | | |
| | | |
| | | var sel_pts = svgedit.path.path.selected_pts; |
| | | var segs = svgedit.path.path.segs; |
| | | |
| | | |
| | | var i = sel_pts.length; |
| | | var nums = []; |
| | | |
| | |
| | | nums.push(pt + i); |
| | | nums.push(pt + i + 1); |
| | | } |
| | | |
| | | |
| | | svgedit.path.path.init().addPtsToSelection(nums); |
| | | |
| | | svgedit.path.path.endChanges("Clone path node(s)"); |
| | |
| | | var sel_pts = svgedit.path.path.selected_pts; |
| | | // Only allow one selected node for now |
| | | if(sel_pts.length !== 1) return; |
| | | |
| | | |
| | | var elem = svgedit.path.path.elem; |
| | | var list = elem.pathSegList; |
| | | |
| | | var len = list.numberOfItems; |
| | | |
| | | var index = sel_pts[0]; |
| | | |
| | | |
| | | var open_pt = null; |
| | | var start_item = null; |
| | | |
| | |
| | | return false; |
| | | } |
| | | }); |
| | | |
| | | |
| | | if(open_pt == null) { |
| | | // Single path, so close last seg |
| | | open_pt = svgedit.path.path.segs.length - 1; |
| | |
| | | |
| | | if(open_pt !== false) { |
| | | // Close this path |
| | | |
| | | |
| | | // Create a line going to the previous "M" |
| | | var newseg = elem.createSVGPathSegLinetoAbs(start_item.x, start_item.y); |
| | | |
| | | |
| | | var closer = elem.createSVGPathSegClosePath(); |
| | | if(open_pt == svgedit.path.path.segs.length) { |
| | | list.appendItem(newseg); |
| | |
| | | svgedit.path.insertItemBefore(elem, closer, open_pt); |
| | | svgedit.path.insertItemBefore(elem, newseg, open_pt); |
| | | } |
| | | |
| | | |
| | | svgedit.path.path.init().selectPt(open_pt+1); |
| | | return; |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | // M 1,1 L 2,2 L 3,3 L 1,1 z // open at 2,2 |
| | | // M 2,2 L 3,3 L 1,1 |
| | | |
| | | // M 1,1 L 2,2 L 1,1 z M 4,4 L 5,5 L6,6 L 5,5 z |
| | | // M 1,1 L 2,2 L 1,1 z [M 4,4] L 5,5 L(M)6,6 L 5,5 z |
| | | |
| | | |
| | | // M 1,1 L 2,2 L 1,1 z M 4,4 L 5,5 L6,6 L 5,5 z |
| | | // M 1,1 L 2,2 L 1,1 z [M 4,4] L 5,5 L(M)6,6 L 5,5 z |
| | | |
| | | var seg = svgedit.path.path.segs[index]; |
| | | |
| | | |
| | | if(seg.mate) { |
| | | list.removeItem(index); // Removes last "L" |
| | | list.removeItem(index); // Removes the "Z" |
| | | svgedit.path.path.init().selectPt(index - 1); |
| | | return; |
| | | } |
| | | |
| | | |
| | | var last_m, z_seg; |
| | | |
| | | |
| | | // Find this sub-path's closing point and remove |
| | | for(var i=0; i<list.numberOfItems; i++) { |
| | | var item = list.getItem(i); |
| | |
| | | break; |
| | | } |
| | | } |
| | | |
| | | |
| | | var num = (index - last_m) - 1; |
| | | |
| | | |
| | | while(num--) { |
| | | svgedit.path.insertItemBefore(elem, list.getItem(last_m), z_seg); |
| | | } |
| | | |
| | | |
| | | var pt = list.getItem(last_m); |
| | | |
| | | |
| | | // Make this point the new "M" |
| | | svgedit.path.replacePathSeg(2, last_m, [pt.x, pt.y]); |
| | | |
| | | |
| | | var i = index |
| | | |
| | | |
| | | svgedit.path.path.init().selectPt(0); |
| | | }, |
| | | deletePathNode: function() { |
| | | if(!pathActions.canDeleteNodes) return; |
| | | svgedit.path.path.storeD(); |
| | | |
| | | |
| | | var sel_pts = svgedit.path.path.selected_pts; |
| | | var i = sel_pts.length; |
| | | |
| | |
| | | var pt = sel_pts[i]; |
| | | svgedit.path.path.deleteSeg(pt); |
| | | } |
| | | |
| | | |
| | | // Cleanup |
| | | var cleanup = function() { |
| | | var segList = svgedit.path.path.elem.pathSegList; |
| | | var len = segList.numberOfItems; |
| | | |
| | | |
| | | var remItems = function(pos, count) { |
| | | while(count--) { |
| | | segList.removeItem(pos); |
| | |
| | | } |
| | | |
| | | if(len <= 1) return true; |
| | | |
| | | |
| | | while(len--) { |
| | | var item = segList.getItem(len); |
| | | if(item.pathSegType === 1) { |
| | |
| | | } else if(item.pathSegType === 2) { |
| | | if(len > 0) { |
| | | var prev_type = segList.getItem(len-1).pathSegType; |
| | | // Path has M M |
| | | // Path has M M |
| | | if(prev_type === 2) { |
| | | remItems(len-1, 1); |
| | | cleanup(); |
| | | break; |
| | | // Entire path ends with Z M |
| | | // Entire path ends with Z M |
| | | } else if(prev_type === 1 && segList.numberOfItems-1 === len) { |
| | | remItems(len, 1); |
| | | cleanup(); |
| | |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | |
| | | cleanup(); |
| | | |
| | | |
| | | // Completely delete a path with 1 or 0 segments |
| | | if(svgedit.path.path.elem.pathSegList.numberOfItems <= 1) { |
| | | canvas.setMode("select") |
| | | canvas.deleteSelectedElements(); |
| | | return; |
| | | } |
| | | |
| | | |
| | | svgedit.path.path.init(); |
| | | |
| | | |
| | | svgedit.path.path.clearSelection(); |
| | | |
| | | |
| | | // TODO: Find right way to select point now |
| | | // path.selectPt(sel_pt); |
| | | if(window.opera) { // Opera repaints incorrectly |
| | |
| | | moveNode: function(attr, newValue) { |
| | | var sel_pts = svgedit.path.path.selected_pts; |
| | | if(!sel_pts.length) return; |
| | | |
| | | |
| | | svgedit.path.path.storeD(); |
| | | |
| | | |
| | | // Get first selected point |
| | | var seg = svgedit.path.path.segs[sel_pts[0]]; |
| | | var diff = {x:0, y:0}; |
| | | diff[attr] = newValue - seg.item[attr]; |
| | | |
| | | |
| | | seg.move(diff.x, diff.y); |
| | | svgedit.path.path.endChanges("Move path point"); |
| | | }, |
| | |
| | | if(item.pathSegType === 2) { |
| | | last_m = item; |
| | | } |
| | | |
| | | |
| | | if(item.pathSegType === 1) { |
| | | var prev = segList.getItem(i-1); |
| | | if(prev.x != last_m.x || prev.y != last_m.y) { |
| | |
| | | pathActions.fixEnd(elem); |
| | | break; |
| | | } |
| | | |
| | | |
| | | } |
| | | } |
| | | if(svgedit.browser.isWebkit()) resetD(elem); |
| | |
| | | var curx = 0, cury = 0; |
| | | var d = ""; |
| | | var last_m = null; |
| | | |
| | | |
| | | for (var i = 0; i < len; ++i) { |
| | | var seg = segList.getItem(i); |
| | | // if these properties are not in the segment, set them to zero |
| | |
| | | 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 = ''; |
| | | var more = more?' '+more.join(' '):''; |
| | |
| | | }); |
| | | d += letter + pnts.join(' ') + more + last; |
| | | } |
| | | |
| | | |
| | | switch (type) { |
| | | case 1: // z,Z closepath (Z/z) |
| | | d += "z"; |
| | |
| | | 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 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; |
| | | } |
| | | if(type === 3) last_m = [curx, cury]; |
| | | |
| | | |
| | | addToD([[x,y]]); |
| | | break; |
| | | case 6: // absolute cubic (C) |
| | |
| | | case 8: // absolute quad (Q) |
| | | x -= curx; x1 -= curx; |
| | | y -= cury; y1 -= cury; |
| | | case 9: // relative quad (q) |
| | | case 9: // relative quad (q) |
| | | if(toRel) { |
| | | curx += x; |
| | | cury += y; |
| | |
| | | // Function: removeUnusedDefElems |
| | | // Looks at DOM elements inside the <defs> to see if they are referred to, |
| | | // removes them from the DOM if they are not. |
| | | // |
| | | // |
| | | // Returns: |
| | | // The amount of elements that were removed |
| | | var removeUnusedDefElems = this.removeUnusedDefElems = function() { |
| | | var defs = svgcontent.getElementsByTagNameNS(svgns, "defs"); |
| | | if(!defs || !defs.length) return 0; |
| | | |
| | | |
| | | // if(!defs.firstChild) return; |
| | | |
| | | |
| | | var defelem_uses = [], |
| | | numRemoved = 0; |
| | | var attrs = ['fill', 'stroke', 'filter', 'marker-start', 'marker-mid', 'marker-end']; |
| | | var alen = attrs.length; |
| | | |
| | | |
| | | var all_els = svgcontent.getElementsByTagNameNS(svgns, '*'); |
| | | var all_len = all_els.length; |
| | | |
| | | |
| | | for(var i=0; i<all_len; i++) { |
| | | var el = all_els[i]; |
| | | for(var j = 0; j < alen; j++) { |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // gradients can refer to other gradients |
| | | var href = getHref(el); |
| | | if (href && href.indexOf('#') === 0) { |
| | | defelem_uses.push(href.substr(1)); |
| | | } |
| | | }; |
| | | |
| | | |
| | | var defelems = $(defs).find("linearGradient, radialGradient, filter, marker, svg, symbol"); |
| | | defelem_ids = [], |
| | | i = defelems.length; |
| | |
| | | } |
| | | |
| | | // Function: svgCanvasToString |
| | | // Main function to set up the SVG content for output |
| | | // Main function to set up the SVG content for output |
| | | // |
| | | // Returns: |
| | | // Returns: |
| | | // String containing the SVG image for output |
| | | this.svgCanvasToString = function() { |
| | | // keep calling it until there are none to remove |
| | | while (removeUnusedDefElems() > 0) {}; |
| | | |
| | | |
| | | pathActions.clear(true); |
| | | |
| | | |
| | | // Keep SVG-Edit comment on top |
| | | $.each(svgcontent.childNodes, function(i, node) { |
| | | if(i && node.nodeType === 8 && node.data.indexOf('Created with') >= 0) { |
| | | svgcontent.insertBefore(node, svgcontent.firstChild); |
| | | } |
| | | }); |
| | | |
| | | |
| | | // Move out of in-group editing mode |
| | | if(current_group) { |
| | | leaveContext(); |
| | | selectOnly([current_group]); |
| | | } |
| | | |
| | | |
| | | //hide grid, otherwise shows a black canvas |
| | | $('#canvasGrid').attr('display', 'none'); |
| | | |
| | | |
| | | var naked_svgs = []; |
| | | |
| | | |
| | | // Unwrap gsvg if it has no special attributes (only id and style) |
| | | $(svgcontent).find('g:data(gsvg)').each(function() { |
| | | var attrs = this.attributes; |
| | |
| | | } |
| | | }); |
| | | var output = this.svgToString(svgcontent, 0); |
| | | |
| | | |
| | | // Rewrap gsvg |
| | | if(naked_svgs.length) { |
| | | $(naked_svgs).each(function() { |
| | | groupSvgElem(this); |
| | | }); |
| | | } |
| | | |
| | | |
| | | return output; |
| | | }; |
| | | |
| | | // Function: svgToString |
| | | // Sub function ran on each SVG element to convert it to a string as desired |
| | | // |
| | | // Parameters: |
| | | // |
| | | // Parameters: |
| | | // elem - The SVG element to convert |
| | | // indent - Integer with the amount of spaces to indent this tag |
| | | // |
| | | // Returns: |
| | | // Returns: |
| | | // String with the given element as an SVG tag |
| | | this.svgToString = function(elem, indent) { |
| | | var out = new Array(), toXml = svgedit.utilities.toXml; |
| | |
| | | attr, |
| | | i, |
| | | childs = elem.childNodes; |
| | | |
| | | |
| | | for (var i=0; i<indent; i++) out.push(" "); |
| | | out.push("<"); out.push(elem.nodeName); |
| | | if(elem.id === 'svgcontent') { |
| | | // Process root element separately |
| | | var res = getResolution(); |
| | | |
| | | |
| | | var vb = ""; |
| | | // TODO: Allow this by dividing all values by current baseVal |
| | | // Note that this also means we should properly deal with this on import |
| | |
| | | // var unit_m = svgedit.units.getTypeMap()[unit]; |
| | | // res.w = svgedit.units.shortFloat(res.w / unit_m) |
| | | // res.h = svgedit.units.shortFloat(res.h / unit_m) |
| | | // vb = ' viewBox="' + [0, 0, res.w, res.h].join(' ') + '"'; |
| | | // vb = ' viewBox="' + [0, 0, res.w, res.h].join(' ') + '"'; |
| | | // res.w += unit; |
| | | // res.h += unit; |
| | | // } |
| | | |
| | | |
| | | if(unit !== "px") { |
| | | res.w = svgedit.units.convertUnit(res.w, unit) + unit; |
| | | res.h = svgedit.units.convertUnit(res.h, unit) + unit; |
| | | } |
| | | |
| | | |
| | | out.push(' width="' + res.w + '" height="' + res.h + '"' + vb + ' xmlns="'+svgns+'"'); |
| | | |
| | | |
| | | var nsuris = {}; |
| | | |
| | | |
| | | // Check elements for namespaces, add if found |
| | | $(elem).find('*').andSelf().each(function() { |
| | | var el = this; |
| | |
| | | } |
| | | }); |
| | | }); |
| | | |
| | | |
| | | var i = attrs.length; |
| | | var attr_names = ['width','height','xmlns','x','y','viewBox','id','overflow']; |
| | | while (i--) { |
| | | attr = attrs.item(i); |
| | | var attrVal = toXml(attr.nodeValue); |
| | | |
| | | |
| | | // Namespaces have already been dealt with, so skip |
| | | if(attr.nodeName.indexOf('xmlns:') === 0) continue; |
| | | |
| | | // only serialize attributes we don't use internally |
| | | if (attrVal != "" && attr_names.indexOf(attr.localName) == -1) |
| | | if (attrVal != "" && attr_names.indexOf(attr.localName) == -1) |
| | | { |
| | | |
| | | if(!attr.namespaceURI || nsMap[attr.namespaceURI]) { |
| | | out.push(' '); |
| | | out.push(' '); |
| | | out.push(attr.nodeName); out.push("=\""); |
| | | out.push(attrVal); out.push("\""); |
| | | } |
| | |
| | | } else { |
| | | // Skip empty defs |
| | | if(elem.nodeName === 'defs' && !elem.firstChild) return; |
| | | |
| | | |
| | | var moz_attrs = ['-moz-math-font-style', '_moz-math-font-style']; |
| | | for (var i=attrs.length-1; i>=0; i--) { |
| | | attr = attrs.item(i); |
| | |
| | | if (attrVal != "") { |
| | | if(attrVal.indexOf('pointer-events') === 0) continue; |
| | | if(attr.localName === "class" && attrVal.indexOf('se_') === 0) continue; |
| | | out.push(" "); |
| | | out.push(" "); |
| | | if(attr.localName === 'd') attrVal = pathActions.convertPath(elem, true); |
| | | if(!isNaN(attrVal)) { |
| | | attrVal = svgedit.units.shortFloat(attrVal); |
| | | } else if(unit_re.test(attrVal)) { |
| | | attrVal = svgedit.units.shortFloat(attrVal) + unit; |
| | | } |
| | | |
| | | // Embed images when saving |
| | | |
| | | // Embed images when saving |
| | | if(save_options.apply |
| | | && elem.nodeName === 'image' |
| | | && elem.nodeName === 'image' |
| | | && attr.localName === 'href' |
| | | && save_options.images |
| | | && save_options.images === 'embed') |
| | | && save_options.images === 'embed') |
| | | { |
| | | var img = encodableImages[attrVal]; |
| | | if(img) attrVal = img; |
| | | } |
| | | |
| | | |
| | | // map various namespaces to our fixed namespace prefixes |
| | | // (the default xmlns attribute itself does not get a prefix) |
| | | if(!attr.namespaceURI || attr.namespaceURI == svgns || nsMap[attr.namespaceURI]) { |
| | |
| | | out.push(">"); |
| | | indent++; |
| | | var bOneLine = false; |
| | | |
| | | |
| | | for (var i=0; i<childs.length; i++) |
| | | { |
| | | var child = childs.item(i); |
| | |
| | | // Function: embedImage |
| | | // Converts a given image file to a data URL when possible, then runs a given callback |
| | | // |
| | | // Parameters: |
| | | // Parameters: |
| | | // val - String with the path/URL of the image |
| | | // callback - Optional function to run when image data is found, supplies the |
| | | // result (data URL or false) as first parameter. |
| | |
| | | // This function also includes the XML prolog. Clients of the SvgCanvas bind their save |
| | | // function to the 'saved' event. |
| | | // |
| | | // Returns: |
| | | // Returns: |
| | | // Nothing |
| | | this.save = function(opts) { |
| | | // remove the selected outline before serializing |
| | |
| | | // Update save options if provided |
| | | if(opts) $.extend(save_options, opts); |
| | | save_options.apply = true; |
| | | |
| | | |
| | | // no need for doctype, see http://jwatt.org/svg/authoring/#doctype-declaration |
| | | var str = this.svgCanvasToString(); |
| | | if (svgedit.browser.supportsBlobs()) { |
| | |
| | | }; |
| | | |
| | | // Function: rasterExport |
| | | // Generates a PNG Data URL based on the current image, then calls "exported" |
| | | // Generates a PNG Data URL based on the current image, then calls "exported" |
| | | // with an object including the string and any issues found |
| | | this.rasterExport = function() { |
| | | // remove the selected outline before serializing |
| | | clearSelection(); |
| | | |
| | | // Check for known CanVG issues |
| | | |
| | | // Check for known CanVG issues |
| | | var issues = []; |
| | | |
| | | |
| | | // Selector and notice |
| | | var issue_list = { |
| | | 'feGaussianBlur': uiStrings.exportNoBlur, |
| | |
| | | '[stroke-dasharray]': uiStrings.exportNoDashArray |
| | | }; |
| | | var content = $(svgcontent); |
| | | |
| | | |
| | | // Add font/text check if Canvas Text API is not implemented |
| | | if(!("font" in $('<canvas>')[0].getContext('2d'))) { |
| | | issue_list['text'] = uiStrings.exportNoText; |
| | | } |
| | | |
| | | |
| | | $.each(issue_list, function(sel, descr) { |
| | | if(content.find(sel).length) { |
| | | issues.push(descr); |
| | |
| | | // Function: randomizeIds |
| | | // This function determines whether to use a nonce in the prefix, when |
| | | // generating IDs for future documents in SVG-Edit. |
| | | // |
| | | // |
| | | // Parameters: |
| | | // an opional boolean, which, if true, adds a nonce to the prefix. Thus |
| | | // svgCanvas.randomizeIds() <==> svgCanvas.randomizeIds(true) |
| | |
| | | // |
| | | // <marker id='se_marker_end_svg_7'/> |
| | | // <polyline id='svg_7' se:connector='svg_1 svg_6' marker-end='url(#se_marker_end_svg_7)'/> |
| | | // |
| | | // |
| | | // Problem #1: if svg_1 gets renamed, we do not update the polyline's se:connector attribute |
| | | // Problem #2: if the polyline svg_7 gets renamed, we do not update the marker id nor the polyline's marker-end attribute |
| | | var ref_elems = ["filter", "linearGradient", "pattern", "radialGradient", "symbol", "textPath", "use"]; |
| | | |
| | | |
| | | svgedit.utilities.walkTree(g, function(n) { |
| | | // if it's an element node |
| | | if (n.nodeType == 1) { |
| | |
| | | } |
| | | ids[n.id]["elem"] = n; |
| | | } |
| | | |
| | | |
| | | // now search for all attributes on this element that might refer |
| | | // to other elements |
| | | $.each(ref_attrs,function(i,attr) { |
| | |
| | | } |
| | | } |
| | | }); |
| | | |
| | | |
| | | // check xlink:href now |
| | | var href = svgedit.utilities.getHref(n); |
| | | // TODO: what if an <image> or <a> element refers to an element internally? |
| | |
| | | } |
| | | ids[refid]["hrefs"].push(n); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | }); |
| | | |
| | | |
| | | // in ids, we now have a map of ids, elements and attributes, let's re-identify |
| | | for (var oldid in ids) { |
| | | if (!oldid) continue; |
| | | var elem = ids[oldid]["elem"]; |
| | | if (elem) { |
| | | var newid = getNextId(); |
| | | |
| | | |
| | | // assign element its new id |
| | | elem.id = newid; |
| | | |
| | | |
| | | // remap all url() attributes |
| | | var attrs = ids[oldid]["attrs"]; |
| | | var j = attrs.length; |
| | |
| | | var attr = attrs[j]; |
| | | attr.ownerElement.setAttribute(attr.name, "url(#" + newid + ")"); |
| | | } |
| | | |
| | | |
| | | // remap all href attributes |
| | | var hreffers = ids[oldid]["hrefs"]; |
| | | var k = hreffers.length; |
| | |
| | | // Assigns reference data for each use element |
| | | var setUseData = this.setUseData = function(parent) { |
| | | var elems = $(parent); |
| | | |
| | | |
| | | if(parent.tagName !== 'use') { |
| | | elems = elems.find('use'); |
| | | } |
| | | |
| | | |
| | | elems.each(function() { |
| | | var id = getHref(this).substr(1); |
| | | var ref_elem = getElem(id); |
| | |
| | | return (this.tagName.indexOf('Gradient') >= 0); |
| | | }); |
| | | } |
| | | |
| | | |
| | | elems.each(function() { |
| | | var grad = this; |
| | | if($(grad).attr('gradientUnits') === 'userSpaceOnUse') { |
| | | // TODO: Support more than one element with this ref by duplicating parent grad |
| | | var elems = $(svgcontent).find('[fill="url(#' + grad.id + ')"],[stroke="url(#' + grad.id + ')"]'); |
| | | if(!elems.length) return; |
| | | |
| | | |
| | | // get object's bounding box |
| | | var bb = svgedit.utilities.getBBox(elems[0]); |
| | | |
| | | |
| | | // This will occur if the element is inside a <defs> or a <symbol>, |
| | | // in which we shouldn't need to convert anyway. |
| | | if(!bb) return; |
| | | |
| | | |
| | | if(grad.tagName === 'linearGradient') { |
| | | var g_coords = $(grad).attr(['x1', 'y1', 'x2', 'y2']); |
| | | |
| | | |
| | | // If has transform, convert |
| | | var tlist = grad.gradientTransform.baseVal; |
| | | if(tlist && tlist.numberOfItems > 0) { |
| | | var m = transformListToTransform(tlist).matrix; |
| | | var pt1 = transformPoint(g_coords.x1, g_coords.y1, m); |
| | | var pt2 = transformPoint(g_coords.x2, g_coords.y2, m); |
| | | |
| | | |
| | | g_coords.x1 = pt1.x; |
| | | g_coords.y1 = pt1.y; |
| | | g_coords.x2 = pt2.x; |
| | | g_coords.y2 = pt2.y; |
| | | grad.removeAttribute('gradientTransform'); |
| | | } |
| | | |
| | | |
| | | $(grad).attr({ |
| | | x1: (g_coords.x1 - bb.x) / bb.width, |
| | | y1: (g_coords.y1 - bb.y) / bb.height, |
| | |
| | | }); |
| | | grad.removeAttribute('gradientUnits'); |
| | | } else { |
| | | // Note: radialGradient elements cannot be easily converted |
| | | // Note: radialGradient elements cannot be easily converted |
| | | // because userSpaceOnUse will keep circular gradients, while |
| | | // objectBoundingBox will x/y scale the gradient according to |
| | | // its bbox. |
| | | |
| | | // its bbox. |
| | | |
| | | // For now we'll do nothing, though we should probably have |
| | | // the gradient be updated as the element is moved, as |
| | | // the gradient be updated as the element is moved, as |
| | | // inkscape/illustrator do. |
| | | |
| | | |
| | | // var g_coords = $(grad).attr(['cx', 'cy', 'r']); |
| | | // |
| | | // |
| | | // $(grad).attr({ |
| | | // cx: (g_coords.cx - bb.x) / bb.width, |
| | | // cy: (g_coords.cy - bb.y) / bb.height, |
| | | // r: g_coords.r |
| | | // }); |
| | | // |
| | | // |
| | | // grad.removeAttribute('gradientUnits'); |
| | | } |
| | | |
| | | |
| | | |
| | | } |
| | | }); |
| | |
| | | elem = selectedElements[0]; |
| | | } |
| | | var $elem = $(elem); |
| | | |
| | | |
| | | var batchCmd = new BatchCommand(); |
| | | |
| | | |
| | | var ts; |
| | | |
| | | |
| | | if($elem.data('gsvg')) { |
| | | // Use the gsvg as the new group |
| | | var svg = elem.firstChild; |
| | | var pt = $(svg).attr(['x', 'y']); |
| | | |
| | | |
| | | $(elem.firstChild.firstChild).unwrap(); |
| | | $(elem).removeData('gsvg'); |
| | | |
| | | |
| | | var tlist = getTransformList(elem); |
| | | var xform = svgroot.createSVGTransform(); |
| | | xform.setTranslate(pt.x, pt.y); |
| | |
| | | call("selected", [elem]); |
| | | } else if($elem.data('symbol')) { |
| | | elem = $elem.data('symbol'); |
| | | |
| | | |
| | | ts = $elem.attr('transform'); |
| | | var pos = $elem.attr(['x','y']); |
| | | |
| | | var vb = elem.getAttribute('viewBox'); |
| | | |
| | | |
| | | if(vb) { |
| | | var nums = vb.split(' '); |
| | | pos.x -= +nums[0]; |
| | | pos.y -= +nums[1]; |
| | | } |
| | | |
| | | |
| | | // Not ideal, but works |
| | | ts += " translate(" + (pos.x || 0) + "," + (pos.y || 0) + ")"; |
| | | |
| | | |
| | | var prev = $elem.prev(); |
| | | |
| | | |
| | | // Remove <use> element |
| | | batchCmd.addSubCommand(new RemoveElementCommand($elem[0], $elem[0].nextSibling, $elem[0].parentNode)); |
| | | $elem.remove(); |
| | | |
| | | |
| | | // See if other elements reference this symbol |
| | | var has_more = $(svgcontent).find('use:data(symbol)').length; |
| | | |
| | | |
| | | var g = svgdoc.createElementNS(svgns, "g"); |
| | | var childs = elem.childNodes; |
| | | |
| | | |
| | | for(var i = 0; i < childs.length; i++) { |
| | | g.appendChild(childs[i].cloneNode(true)); |
| | | } |
| | | |
| | | |
| | | // Duplicate the gradients for Gecko, since they weren't included in the <symbol> |
| | | if(svgedit.browser.isGecko()) { |
| | | var dupeGrads = $(findDefs()).children('linearGradient,radialGradient,pattern').clone(); |
| | | $(g).append(dupeGrads); |
| | | } |
| | | |
| | | |
| | | if (ts) { |
| | | g.setAttribute("transform", ts); |
| | | } |
| | | |
| | | |
| | | var parent = elem.parentNode; |
| | | |
| | | |
| | | uniquifyElems(g); |
| | | |
| | | |
| | | // Put the dupe gradients back into <defs> (after uniquifying them) |
| | | if(svgedit.browser.isGecko()) { |
| | | $(findDefs()).append( $(g).find('linearGradient,radialGradient,pattern') ); |
| | | } |
| | | |
| | | |
| | | // now give the g itself a new id |
| | | g.id = getNextId(); |
| | | |
| | | |
| | | prev.after(g); |
| | | |
| | | |
| | | if(parent) { |
| | | if(!has_more) { |
| | | // remove symbol/svg element |
| | |
| | | } |
| | | batchCmd.addSubCommand(new InsertElementCommand(g)); |
| | | } |
| | | |
| | | |
| | | setUseData(g); |
| | | |
| | | |
| | | if(svgedit.browser.isGecko()) { |
| | | convertGradients(findDefs()); |
| | | } else { |
| | | convertGradients(g); |
| | | } |
| | | |
| | | |
| | | // recalculate dimensions on the top-level children so that unnecessary transforms |
| | | // are removed |
| | | svgedit.utilities.walkTreePost(g, function(n){try{recalculateDimensions(n)}catch(e){console.log(e)}}); |
| | | |
| | | |
| | | // Give ID for any visible element missing one |
| | | $(g).find(visElems).each(function() { |
| | | if(!this.id) this.id = getNextId(); |
| | | }); |
| | | |
| | | |
| | | selectOnly([g]); |
| | | |
| | | |
| | | var cm = pushGroupProperties(g, true); |
| | | if(cm) { |
| | | batchCmd.addSubCommand(cm); |
| | | } |
| | | |
| | | addCommandToHistory(batchCmd); |
| | | |
| | | |
| | | } else { |
| | | console.log('Unexpected element to ungroup:', elem); |
| | | } |
| | | } |
| | | |
| | | // |
| | | // |
| | | // Function: setSvgString |
| | | // This function sets the current drawing as the input SVG XML. |
| | | // |
| | |
| | | var nextSibling = svgcontent.nextSibling; |
| | | var oldzoom = svgroot.removeChild(svgcontent); |
| | | batchCmd.addSubCommand(new RemoveElementCommand(oldzoom, nextSibling, svgroot)); |
| | | |
| | | |
| | | // set new svg document |
| | | // If DOM3 adoptNode() available, use it. Otherwise fall back to DOM2 importNode() |
| | | if(svgdoc.adoptNode) { |
| | |
| | | else { |
| | | svgcontent = svgdoc.importNode(newDoc.documentElement, true); |
| | | } |
| | | |
| | | |
| | | svgroot.appendChild(svgcontent); |
| | | var content = $(svgcontent); |
| | | |
| | | |
| | | canvas.current_drawing_ = new svgedit.draw.Drawing(svgcontent, idprefix); |
| | | |
| | | |
| | | // retrieve or set the nonce |
| | | var nonce = getCurrentDrawing().getNonce(); |
| | | if (nonce) { |
| | |
| | | } else { |
| | | call("unsetnonce"); |
| | | } |
| | | |
| | | |
| | | // change image href vals if possible |
| | | content.find('image').each(function() { |
| | | var image = this; |
| | |
| | | // Add to encodableImages if it loads |
| | | canvas.embedImage(val); |
| | | }); |
| | | |
| | | |
| | | // Wrap child SVGs in group elements |
| | | content.find('svg').each(function() { |
| | | // Skip if it's in a <defs> |
| | | if($(this).closest('defs').length) return; |
| | | |
| | | |
| | | uniquifyElems(this); |
| | | |
| | | |
| | | // Check if it already has a gsvg group |
| | | var pa = this.parentNode; |
| | | if(pa.childNodes.length === 1 && pa.nodeName === 'g') { |
| | |
| | | groupSvgElem(this); |
| | | } |
| | | }); |
| | | |
| | | |
| | | // Put all paint elems in defs |
| | | |
| | | |
| | | content.find('linearGradient, radialGradient, pattern').appendTo(findDefs()); |
| | | |
| | | |
| | | // Set ref element for <use> elements |
| | | |
| | | |
| | | // TODO: This should also be done if the object is re-added through "redo" |
| | | setUseData(content); |
| | | |
| | | |
| | | convertGradients(content[0]); |
| | | |
| | | |
| | | // recalculate dimensions on the top-level children so that unnecessary transforms |
| | | // are removed |
| | | svgedit.utilities.walkTreePost(svgcontent, function(n){try{recalculateDimensions(n)}catch(e){console.log(e)}}); |
| | | |
| | | |
| | | var attrs = { |
| | | id: 'svgcontent', |
| | | overflow: curConfig.show_outside_canvas?'visible':'hidden' |
| | | }; |
| | | |
| | | |
| | | var percs = false; |
| | | |
| | | |
| | | // determine proper size |
| | | if (content.attr("viewBox")) { |
| | | var vb = content.attr("viewBox").split(' '); |
| | |
| | | $.each(['width', 'height'], function(i, dim) { |
| | | // Set to 100 if not given |
| | | var val = content.attr(dim); |
| | | |
| | | |
| | | if(!val) val = '100%'; |
| | | |
| | | |
| | | if((val+'').substr(-1) === "%") { |
| | | // Use user units if percentage given |
| | | percs = true; |
| | |
| | | } |
| | | }); |
| | | } |
| | | |
| | | |
| | | // identify layers |
| | | identifyLayers(); |
| | | |
| | | |
| | | // Give ID for any visible layer children missing one |
| | | content.children().find(visElems).each(function() { |
| | | if(!this.id) this.id = getNextId(); |
| | | }); |
| | | |
| | | |
| | | // Percentage width/height, so let's base it on visible elements |
| | | if(percs) { |
| | | var bb = getStrokedBBox(); |
| | | attrs.width = bb.width + bb.x; |
| | | attrs.height = bb.height + bb.y; |
| | | } |
| | | |
| | | // Just in case negative numbers are given or |
| | | |
| | | // Just in case negative numbers are given or |
| | | // result from the percs calculation |
| | | if(attrs.width <= 0) attrs.width = 200; |
| | | if(attrs.height <= 0) attrs.height = 200; |
| | | |
| | | |
| | | content.attr(attrs); |
| | | this.contentW = attrs['width']; |
| | | this.contentH = attrs['height']; |
| | | |
| | | |
| | | $("#canvas_width").val(this.contentW) |
| | | $("#canvas_height").val(this.contentH) |
| | | var background = $("#canvas_background") |
| | |
| | | // update root to the correct size |
| | | var changes = content.attr(["width", "height"]); |
| | | batchCmd.addSubCommand(new ChangeElementCommand(svgroot, changes)); |
| | | |
| | | |
| | | // reset zoom |
| | | current_zoom = 1; |
| | | |
| | | |
| | | // reset transform lists |
| | | svgedit.transformlist.resetListMap(); |
| | | clearSelection(); |
| | | svgedit.path.clearData(); |
| | | svgroot.appendChild(selectorManager.selectorParentGroup); |
| | | |
| | | |
| | | addCommandToHistory(batchCmd); |
| | | call("changed", [svgcontent]); |
| | | } catch(e) { |
| | |
| | | |
| | | opts = { alpha: opac }; |
| | | opts[refElem.tagName] = refElem; |
| | | } |
| | | } |
| | | else if (color.indexOf("#") === 0) { |
| | | opts = { |
| | | alpha: opac, |
| | |
| | | // |
| | | // Returns: |
| | | // This function returns false if the import was unsuccessful, true otherwise. |
| | | // TODO: |
| | | // TODO: |
| | | // * properly handle if namespace is introduced by imported content (must add to svgcontent |
| | | // and update all prefixes in the imported node) |
| | | // * properly handle recalculating dimensions, recalculateDimensions() doesn't handle |
| | | // arbitrary transform lists, but makes some assumptions about how the transform list |
| | | // arbitrary transform lists, but makes some assumptions about how the transform list |
| | | // was obtained |
| | | // * import should happen in top-left of current zoomed viewport |
| | | // * import should happen in top-left of current zoomed viewport |
| | | this.importSvgString = function(xmlString) { |
| | | |
| | | try { |
| | | // Get unique ID |
| | | var uid = svgedit.utilities.encode64(xmlString.length + xmlString).substr(0,32); |
| | | |
| | | |
| | | var useExisting = false; |
| | | |
| | | // Look for symbol and make sure symbol exists in image |
| | |
| | | useExisting = true; |
| | | } |
| | | } |
| | | |
| | | |
| | | var batchCmd = new BatchCommand("Import SVG"); |
| | | |
| | | |
| | | if(useExisting) { |
| | | var symbol = import_ids[uid].symbol; |
| | | var ts = import_ids[uid].xform; |
| | | } else { |
| | | // convert string into XML document |
| | | var newDoc = svgedit.utilities.text2xml(xmlString); |
| | | |
| | | |
| | | this.prepareSvg(newDoc); |
| | | |
| | | |
| | | // import new svg document into our document |
| | | var svg; |
| | | // If DOM3 adoptNode() available, use it. Otherwise fall back to DOM2 importNode() |
| | |
| | | else { |
| | | svg = svgdoc.importNode(newDoc.documentElement, true); |
| | | } |
| | | |
| | | |
| | | uniquifyElems(svg); |
| | | |
| | | |
| | | var innerw = convertToNum('width', svg.getAttribute("width")), |
| | | innerh = convertToNum('height', svg.getAttribute("height")), |
| | | innervb = svg.getAttribute("viewBox"), |
| | |
| | | vb = innervb ? innervb.split(" ") : [0,0,innerw,innerh]; |
| | | for (var j = 0; j < 4; ++j) |
| | | vb[j] = +(vb[j]); |
| | | |
| | | |
| | | // TODO: properly handle preserveAspectRatio |
| | | var canvasw = +svgcontent.getAttribute("width"), |
| | | canvash = +svgcontent.getAttribute("height"); |
| | | // imported content should be 1/3 of the canvas on its largest dimension |
| | | |
| | | |
| | | if (innerh > innerw) { |
| | | var ts = "scale(" + (canvash/3)/vb[3] + ")"; |
| | | } |
| | | else { |
| | | var ts = "scale(" + (canvash/3)/vb[2] + ")"; |
| | | } |
| | | |
| | | |
| | | // Hack to make recalculateDimensions understand how to scale |
| | | ts = "translate(0) " + ts + " translate(0)"; |
| | | |
| | | |
| | | var symbol = svgdoc.createElementNS(svgns, "symbol"); |
| | | var defs = findDefs(); |
| | | |
| | | |
| | | if(svgedit.browser.isGecko()) { |
| | | // Move all gradients into root for Firefox, workaround for this bug: |
| | | // https://bugzilla.mozilla.org/show_bug.cgi?id=353575 |
| | | // TODO: Make this properly undo-able. |
| | | $(svg).find('linearGradient, radialGradient, pattern').appendTo(defs); |
| | | } |
| | | |
| | | |
| | | while (svg.firstChild) { |
| | | var first = svg.firstChild; |
| | | symbol.appendChild(first); |
| | |
| | | symbol.setAttribute(attr.nodeName, attr.nodeValue); |
| | | } |
| | | symbol.id = getNextId(); |
| | | |
| | | |
| | | // Store data |
| | | import_ids[uid] = { |
| | | symbol: symbol, |
| | | xform: ts |
| | | } |
| | | |
| | | |
| | | findDefs().appendChild(symbol); |
| | | batchCmd.addSubCommand(new InsertElementCommand(symbol)); |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | var use_el = svgdoc.createElementNS(svgns, "use"); |
| | | use_el.id = getNextId(); |
| | | setHref(use_el, "#" + symbol.id); |
| | | |
| | | |
| | | (current_group || getCurrentDrawing().getCurrentLayer()).appendChild(use_el); |
| | | batchCmd.addSubCommand(new InsertElementCommand(use_el)); |
| | | clearSelection(); |
| | | |
| | | |
| | | use_el.setAttribute("transform", ts); |
| | | recalculateDimensions(use_el); |
| | | $(use_el).data('symbol', symbol).data('ref', symbol); |
| | | addToSelection([use_el]); |
| | | |
| | | |
| | | // TODO: Find way to add this in a recalculateDimensions-parsable way |
| | | // if (vb[0] != 0 || vb[1] != 0) |
| | | // ts = "translate(" + (-vb[0]) + "," + (-vb[1]) + ") " + ts; |
| | |
| | | }; |
| | | |
| | | // Function: createLayer |
| | | // Creates a new top-level layer in the drawing with the given name, sets the current layer |
| | | // Creates a new top-level layer in the drawing with the given name, sets the current layer |
| | | // to it, and then clears the selection This function then calls the 'changed' handler. |
| | | // This is an undoable action. |
| | | // |
| | |
| | | if(ch.localName == 'title') continue; |
| | | new_layer.appendChild(copyElem(ch)); |
| | | } |
| | | |
| | | |
| | | clearSelection(); |
| | | identifyLayers(); |
| | | |
| | |
| | | }; |
| | | |
| | | // Function: deleteCurrentLayer |
| | | // Deletes the current layer from the drawing and then clears the selection. This function |
| | | // Deletes the current layer from the drawing and then clears the selection. This function |
| | | // then calls the 'changed' handler. This is an undoable action. |
| | | this.deleteCurrentLayer = function() { |
| | | var current_layer = getCurrentDrawing().getCurrentLayer(); |
| | |
| | | }; |
| | | |
| | | // Function: renameCurrentLayer |
| | | // Renames the current layer. If the layer name is not valid (i.e. unique), then this function |
| | | // Renames the current layer. If the layer name is not valid (i.e. unique), then this function |
| | | // does nothing and returns false, otherwise it returns true. This is an undo-able action. |
| | | // |
| | | // |
| | | // Parameters: |
| | | // newname - the new name you want to give the current layer. This name must be unique |
| | | // newname - the new name you want to give the current layer. This name must be unique |
| | | // among all layer names. |
| | | // |
| | | // Returns: |
| | |
| | | } |
| | | 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 (var 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 |
| | | // wipe out old name |
| | | while (child.firstChild) { child.removeChild(child.firstChild); } |
| | | child.textContent = newname; |
| | | |
| | |
| | | }; |
| | | |
| | | // Function: setCurrentLayerPosition |
| | | // Changes the position of the current layer to the new value. If the new index is not valid, |
| | | // Changes the position of the current layer to the new value. If the new index is not valid, |
| | | // this function does nothing and returns false, otherwise it returns true. This is an |
| | | // undo-able action. |
| | | // |
| | | // Parameters: |
| | | // newpos - The zero-based index of the new position of the layer. This should be between |
| | | // 0 and (number of layers - 1) |
| | | // |
| | | // |
| | | // Returns: |
| | | // true if the current layer position was changed, false otherwise. |
| | | this.setCurrentLayerPosition = function(newpos) { |
| | |
| | | } |
| | | // 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; |
| | |
| | | } |
| | | svgcontent.insertBefore(drawing.current_layer, refLayer); |
| | | addCommandToHistory(new MoveElementCommand(drawing.current_layer, oldNextSibling, svgcontent)); |
| | | |
| | | |
| | | identifyLayers(); |
| | | canvas.setCurrentLayer(drawing.getLayerName(newpos)); |
| | | |
| | | |
| | | return true; |
| | | } |
| | | } |
| | | |
| | | |
| | | return false; |
| | | }; |
| | | |
| | | // Function: setLayerVisibility |
| | | // Sets the visibility of the layer. If the layer name is not valid, this function return |
| | | // Sets the visibility of the layer. If the layer name is not valid, this function return |
| | | // false, otherwise it returns true. This is an undo-able action. |
| | | // |
| | | // Parameters: |
| | |
| | | } else { |
| | | return false; |
| | | } |
| | | |
| | | |
| | | if (layer == drawing.getCurrentLayer()) { |
| | | clearSelection(); |
| | | pathActions.clear(); |
| | | } |
| | | // call("changed", [selected]); |
| | | // call("changed", [selected]); |
| | | return true; |
| | | }; |
| | | |
| | | // Function: moveSelectedToLayer |
| | | // Moves the selected elements to layername. If the name is not a valid layer name, then false |
| | | // Moves the selected elements to layername. If the name is not a valid layer name, then false |
| | | // is returned. Otherwise it returns true. This is an undo-able action. |
| | | // |
| | | // Parameters: |
| | |
| | | } |
| | | } |
| | | if (!layer) return false; |
| | | |
| | | |
| | | var batchCmd = new BatchCommand("Move Elements to Layer"); |
| | | |
| | | |
| | | // loop for each selected element and move it |
| | | var selElems = selectedElements; |
| | | var i = selElems.length; |
| | |
| | | layer.appendChild(elem); |
| | | batchCmd.addSubCommand(new MoveElementCommand(elem, oldNextSibling, oldLayer)); |
| | | } |
| | | |
| | | |
| | | addCommandToHistory(batchCmd); |
| | | |
| | | |
| | | return true; |
| | | }; |
| | | |
| | |
| | | prev.appendChild(ch); |
| | | batchCmd.addSubCommand(new 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; |
| | | } |
| | |
| | | while($(svgcontent).children('g').length > 1) { |
| | | batchCmd.addSubCommand(canvas.mergeLayer(true)); |
| | | } |
| | | |
| | | |
| | | clearSelection(); |
| | | identifyLayers(); |
| | | call("changed", [svgcontent]); |
| | |
| | | if(len) { |
| | | for(var i = 0; i < len; i++) { |
| | | var elem = disabled_elems[i]; |
| | | |
| | | |
| | | var orig = elData(elem, 'orig_opac'); |
| | | if(orig !== 1) { |
| | | elem.setAttribute('opacity', orig); |
| | |
| | | |
| | | // Edit inside this group |
| | | current_group = elem; |
| | | |
| | | |
| | | // Disable other elements |
| | | $(elem).parentsUntil('#svgcontent').andSelf().siblings().each(function() { |
| | | var opac = this.getAttribute('opacity') || 1; |
| | |
| | | |
| | | // create empty first layer |
| | | canvas.createLayer("Layer 1"); |
| | | |
| | | |
| | | // clear the undo stack |
| | | canvas.undoMgr.resetUndoStack(); |
| | | |
| | |
| | | var getResolution = this.getResolution = function() { |
| | | // var vb = svgcontent.getAttribute("viewBox").split(' '); |
| | | // return {'w':vb[2], 'h':vb[3], 'zoom': current_zoom}; |
| | | |
| | | |
| | | var width = svgcontent.getAttribute("width")/current_zoom; |
| | | var height = svgcontent.getAttribute("height")/current_zoom; |
| | | |
| | | |
| | | return { |
| | | 'w': width, |
| | | 'h': height, |
| | |
| | | this.setGroupTitle = function(val) { |
| | | var elem = selectedElements[0]; |
| | | elem = $(elem).data('gsvg') || elem; |
| | | |
| | | |
| | | var ts = $(elem).children('title'); |
| | | |
| | | |
| | | var batchCmd = new BatchCommand("Set Label"); |
| | | |
| | | |
| | | if(!val.length) { |
| | | // Remove title element |
| | | var tsNextSibling = ts.nextSibling; |
| | |
| | | // newtitle - String with the new title |
| | | this.setDocumentTitle = function(newtitle) { |
| | | var childs = svgcontent.childNodes, doc_title = false, old_title = ''; |
| | | |
| | | |
| | | var batchCmd = new BatchCommand("Change Image Title"); |
| | | |
| | | |
| | | for (var i=0; i<childs.length; i++) { |
| | | if(childs[i].nodeName == 'title') { |
| | | doc_title = childs[i]; |
| | |
| | | if(!doc_title) { |
| | | doc_title = svgdoc.createElementNS(svgns, "title"); |
| | | svgcontent.insertBefore(doc_title, svgcontent.firstChild); |
| | | } |
| | | |
| | | } |
| | | |
| | | if(newtitle.length) { |
| | | doc_title.textContent = newtitle; |
| | | } else { |
| | |
| | | // Function: setResolution |
| | | // Changes the document's dimensions to the given size |
| | | // |
| | | // Parameters: |
| | | // x - Number with the width of the new dimensions in user units. |
| | | // Parameters: |
| | | // x - Number with the width of the new dimensions in user units. |
| | | // Can also be the string "fit" to indicate "fit to content" |
| | | // y - Number with the height of the new dimensions in user units. |
| | | // y - Number with the height of the new dimensions in user units. |
| | | // |
| | | // Returns: |
| | | // Boolean to indicate if resolution change was succesful. |
| | | // Boolean to indicate if resolution change was succesful. |
| | | // It will fail on "fit to content" option with no content to fit to. |
| | | this.setResolution = function(x, y) { |
| | | var res = getResolution(); |
| | |
| | | if(x == 'fit') { |
| | | // Get bounding box |
| | | var bbox = getStrokedBBox(); |
| | | |
| | | |
| | | if(bbox) { |
| | | batchCmd = new BatchCommand("Fit Canvas to Content"); |
| | | var visEls = getVisibleElements(); |
| | |
| | | dx.push(bbox.x*-1); |
| | | dy.push(bbox.y*-1); |
| | | }); |
| | | |
| | | |
| | | var cmd = canvas.moveSelectedElements(dx, dy, true); |
| | | batchCmd.addSubCommand(cmd); |
| | | clearSelection(); |
| | | |
| | | |
| | | x = Math.round(bbox.width); |
| | | y = Math.round(bbox.height); |
| | | } else { |
| | |
| | | |
| | | x = convertToNum('width', x); |
| | | y = convertToNum('height', y); |
| | | |
| | | |
| | | svgcontent.setAttribute('width', x); |
| | | svgcontent.setAttribute('height', y); |
| | | |
| | | |
| | | this.contentW = x; |
| | | this.contentH = y; |
| | | batchCmd.addSubCommand(new ChangeElementCommand(svgcontent, {"width":w, "height":h})); |
| | | |
| | | svgcontent.setAttribute("viewBox", [0, 0, x/current_zoom, y/current_zoom].join(' ')); |
| | | batchCmd.addSubCommand(new ChangeElementCommand(svgcontent, {"viewBox": ["0 0", w, h].join(' ')})); |
| | | |
| | | |
| | | addCommandToHistory(batchCmd); |
| | | background = document.getElementById("canvas_background"); |
| | | if (background) { |
| | |
| | | |
| | | // Function: setBBoxZoom |
| | | // Sets the zoom level on the canvas-side based on the given value |
| | | // |
| | | // |
| | | // Parameters: |
| | | // val - Bounding box object to zoom to or string indicating zoom option |
| | | // val - Bounding box object to zoom to or string indicating zoom option |
| | | // editor_w - Integer with the editor's workarea box's width |
| | | // editor_h - Integer with the editor's workarea box's height |
| | | this.setBBoxZoom = function(val, editor_w, editor_h) { |
| | |
| | | var calcZoom = function(bb) { |
| | | if(!bb) return false; |
| | | var w_zoom = Math.round((editor_w / bb.width)*100 * spacer)/100; |
| | | var h_zoom = Math.round((editor_h / bb.height)*100 * spacer)/100; |
| | | var h_zoom = Math.round((editor_h / bb.height)*100 * spacer)/100; |
| | | var zoomlevel = Math.min(w_zoom,h_zoom); |
| | | canvas.setZoom(zoomlevel); |
| | | return {'zoom': zoomlevel, 'bbox': bb}; |
| | | } |
| | | |
| | | |
| | | if(typeof val == 'object') { |
| | | bb = val; |
| | | if(bb.width == 0 || bb.height == 0) { |
| | |
| | | // Parameters: |
| | | // name - String with the new mode to change to |
| | | this.setMode = function(name) { |
| | | |
| | | |
| | | pathActions.clear(); |
| | | textActions.clear(); |
| | | $("#workarea").attr("class", name); |
| | |
| | | |
| | | // Function: setColor |
| | | // Change the current stroke/fill color/gradient value |
| | | // |
| | | // |
| | | // Parameters: |
| | | // type - String indicating fill or stroke |
| | | // val - The value to set the stroke attribute to |
| | |
| | | if (!preventUndo) { |
| | | changeSelectedAttribute(type, val, elems); |
| | | call("changed", elems); |
| | | } else |
| | | } else |
| | | changeSelectedAttributeNoUndo(type, val, elems); |
| | | } |
| | | } |
| | |
| | | if (grad.getAttribute('x1') != og.getAttribute('x1') || |
| | | grad.getAttribute('y1') != og.getAttribute('y1') || |
| | | grad.getAttribute('x2') != og.getAttribute('x2') || |
| | | grad.getAttribute('y2') != og.getAttribute('y2')) |
| | | grad.getAttribute('y2') != og.getAttribute('y2')) |
| | | { |
| | | continue; |
| | | } |
| | | } else { |
| | | var grad_attrs = $(grad).attr(rad_attrs); |
| | | var og_attrs = $(og).attr(rad_attrs); |
| | | |
| | | |
| | | var diff = false; |
| | | $.each(rad_attrs, function(i, attr) { |
| | | if(grad_attrs[attr] != og_attrs[attr]) diff = true; |
| | | }); |
| | | |
| | | |
| | | if(diff) continue; |
| | | } |
| | | |
| | | |
| | | // else could be a duplicate, iterate through stops |
| | | var stops = grad.getElementsByTagNameNS(svgns, "stop"); |
| | | var ostops = og.getElementsByTagNameNS(svgns, "stop"); |
| | |
| | | |
| | | if (stop.getAttribute('offset') != ostop.getAttribute('offset') || |
| | | stop.getAttribute('stop-opacity') != ostop.getAttribute('stop-opacity') || |
| | | stop.getAttribute('stop-color') != ostop.getAttribute('stop-color')) |
| | | stop.getAttribute('stop-color') != ostop.getAttribute('stop-color')) |
| | | { |
| | | break; |
| | | } |
| | |
| | | var y1 = grad.getAttribute('y1') || 0; |
| | | var x2 = grad.getAttribute('x2') || 1; |
| | | var y2 = grad.getAttribute('y2') || 0; |
| | | |
| | | |
| | | // Convert to USOU points |
| | | x1 = (bb.width * x1) + bb.x; |
| | | y1 = (bb.height * y1) + bb.y; |
| | | x2 = (bb.width * x2) + bb.x; |
| | | y2 = (bb.height * y2) + bb.y; |
| | | |
| | | |
| | | // Transform those points |
| | | var pt1 = transformPoint(x1, y1, m); |
| | | var pt2 = transformPoint(x2, y2, m); |
| | | |
| | | |
| | | // Convert back to BB points |
| | | var g_coords = {}; |
| | | |
| | | |
| | | g_coords.x1 = (pt1.x - bb.x) / bb.width; |
| | | g_coords.y1 = (pt1.y - bb.y) / bb.height; |
| | | g_coords.x2 = (pt2.x - bb.x) / bb.width; |
| | | g_coords.y2 = (pt2.y - bb.y) / bb.height; |
| | | |
| | | |
| | | var newgrad = grad.cloneNode(true); |
| | | $(newgrad).attr(g_coords); |
| | | |
| | | |
| | | newgrad.id = getNextId(); |
| | | findDefs().appendChild(newgrad); |
| | | elem.setAttribute(type, 'url(#' + newgrad.id + ')'); |
| | |
| | | // Function: setPaint |
| | | // Set a color/gradient to a fill/stroke |
| | | // |
| | | // Parameters: |
| | | // Parameters: |
| | | // type - String with "fill" or "stroke" |
| | | // paint - The jGraduate paint object to apply |
| | | this.setPaint = function(type, paint) { |
| | |
| | | cur_properties[type + '_paint'] = p; |
| | | switch ( p.type ) { |
| | | case "solidColor": |
| | | |
| | | |
| | | if (p.solidColor != "none" && p.solidColor != "#none") { |
| | | this.setColor(type, "#"+p.solidColor) |
| | | } |
| | | else { |
| | | this.setColor(type, "none"); |
| | | var selector = (type == "fill") ? "#fill_color rect" : "#stroke_color rect" |
| | | var selector = (type == "fill") ? "#fill_color rect" : "#stroke_color rect" |
| | | document.querySelector(selector).setAttribute('fill', 'none'); |
| | | } |
| | | break; |
| | |
| | | // // make a copy |
| | | // var p = new $.jGraduate.Paint(p); |
| | | // this.setStrokeOpacity(p.alpha/100); |
| | | // |
| | | // |
| | | // // now set the current paint object |
| | | // cur_properties.stroke_paint = p; |
| | | // switch ( p.type ) { |
| | |
| | | // case "linearGradient" |
| | | // case "radialGradient" |
| | | // canvas.strokeGrad = p[p.type]; |
| | | // setGradient(type); |
| | | // setGradient(type); |
| | | // default: |
| | | // // console.log("none!"); |
| | | // } |
| | | // }; |
| | | // |
| | | // |
| | | // this.setFillPaint = function(p, addGrad) { |
| | | // // make a copy |
| | | // var p = new $.jGraduate.Paint(p); |
| | | // this.setFillOpacity(p.alpha/100, true); |
| | | // |
| | | // |
| | | // // now set the current paint object |
| | | // cur_properties.fill_paint = p; |
| | | // if (p.type == "solidColor") { |
| | |
| | | // } |
| | | // else if(p.type == "linearGradient") { |
| | | // canvas.fillGrad = p.linearGradient; |
| | | // if(addGrad) setGradient(); |
| | | // if(addGrad) setGradient(); |
| | | // } |
| | | // else if(p.type == "radialGradient") { |
| | | // canvas.fillGrad = p.radialGradient; |
| | | // if(addGrad) setGradient(); |
| | | // if(addGrad) setGradient(); |
| | | // } |
| | | // else { |
| | | // // console.log("none!"); |
| | |
| | | return; |
| | | } |
| | | cur_properties.stroke_width = val; |
| | | |
| | | |
| | | var elems = []; |
| | | var i = selectedElements.length; |
| | | while (i--) { |
| | |
| | | if (elem) { |
| | | if (elem.tagName == "g") |
| | | svgedit.utilities.walkTree(elem, function(e){if(e.nodeName!="g") elems.push(e);}); |
| | | else |
| | | else |
| | | elems.push(elem); |
| | | } |
| | | } |
| | | } |
| | | if (elems.length > 0) { |
| | | changeSelectedAttribute("stroke-width", val, elems); |
| | | call("changed", selectedElements); |
| | |
| | | if (elem) { |
| | | if (elem.tagName == "g") |
| | | svgedit.utilities.walkTree(elem, function(e){if(e.nodeName!="g") elems.push(e);}); |
| | | else |
| | | else |
| | | elems.push(elem); |
| | | } |
| | | } |
| | | } |
| | | if (elems.length > 0) { |
| | | changeSelectedAttribute(attr, val, elems); |
| | | call("changed", selectedElements); |
| | |
| | | this.getBlur = function(elem) { |
| | | var val = 0; |
| | | // var elem = selectedElements[0]; |
| | | |
| | | |
| | | if(elem) { |
| | | var filter_url = elem.getAttribute('filter'); |
| | | if(filter_url) { |
| | |
| | | var cur_command = null; |
| | | var filter = null; |
| | | var filterHidden = false; |
| | | |
| | | |
| | | // Function: setBlurNoUndo |
| | | // Sets the stdDeviation blur value on the selected element without being undoable |
| | | // |
| | |
| | | canvas.setBlurOffsets(filter, val); |
| | | } |
| | | } |
| | | |
| | | |
| | | function finishChange() { |
| | | var bCmd = canvas.undoMgr.finishUndoableChange(); |
| | | cur_command.addSubCommand(bCmd); |
| | | addCommandToHistory(cur_command); |
| | | cur_command = null; |
| | | cur_command = null; |
| | | filter = null; |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | // Function: setBlur |
| | | // Function: setBlur |
| | | // Adds/updates the blur filter to the selected element |
| | | // |
| | | // Parameters: |
| | |
| | | finishChange(); |
| | | return; |
| | | } |
| | | |
| | | |
| | | // Looks for associated blur, creates one if not found |
| | | var elem = selectedElements[0]; |
| | | var elem_id = elem.id; |
| | | filter = getElem(elem_id + '_blur'); |
| | | |
| | | |
| | | val -= 0; |
| | | |
| | | |
| | | var batchCmd = new BatchCommand(); |
| | | |
| | | |
| | | // Blur found! |
| | | if(filter) { |
| | | if(val === 0) { |
| | |
| | | "stdDeviation": val |
| | | } |
| | | }); |
| | | |
| | | |
| | | filter = addSvgElementFromJson({ "element": "filter", |
| | | "attr": { |
| | | "id": elem_id + '_blur' |
| | | } |
| | | }); |
| | | |
| | | |
| | | filter.appendChild(newblur); |
| | | findDefs().appendChild(filter); |
| | | |
| | | |
| | | batchCmd.addSubCommand(new InsertElementCommand(filter)); |
| | | } |
| | | |
| | | var changes = {filter: elem.getAttribute('filter')}; |
| | | |
| | | |
| | | if(val === 0) { |
| | | elem.removeAttribute("filter"); |
| | | batchCmd.addSubCommand(new ChangeElementCommand(elem, changes)); |
| | | return; |
| | | } else { |
| | | changeSelectedAttribute("filter", 'url(#' + elem_id + '_blur)'); |
| | | |
| | | |
| | | batchCmd.addSubCommand(new ChangeElementCommand(elem, changes)); |
| | | |
| | | |
| | | canvas.setBlurOffsets(filter, val); |
| | | } |
| | | |
| | | |
| | | cur_command = batchCmd; |
| | | canvas.undoMgr.beginUndoableChange("stdDeviation", [filter?filter.firstChild:null]); |
| | | if(complete) { |
| | |
| | | // Function: setImageURL |
| | | // Sets the new image URL for the selected image element. Updates its size if |
| | | // a new URL is given |
| | | // |
| | | // |
| | | // Parameters: |
| | | // val - String with the image URL/path |
| | | this.setImageURL = function(val) { |
| | | var elem = selectedElements[0]; |
| | | if(!elem) return; |
| | | |
| | | |
| | | var attrs = $(elem).attr(['width', 'height']); |
| | | var setsize = (!attrs.width || !attrs.height); |
| | | |
| | | var cur_href = getHref(elem); |
| | | |
| | | |
| | | // Do nothing if no URL change or size change |
| | | if(cur_href !== val) { |
| | | setsize = true; |
| | |
| | | if(setsize) { |
| | | $(new Image()).load(function() { |
| | | var changes = $(elem).attr(['width', 'height']); |
| | | |
| | | |
| | | $(elem).attr({ |
| | | width: this.width, |
| | | height: this.height |
| | | }); |
| | | |
| | | |
| | | selectorManager.requestSelector(elem).resize(); |
| | | |
| | | |
| | | batchCmd.addSubCommand(new ChangeElementCommand(elem, changes)); |
| | | addCommandToHistory(batchCmd); |
| | | call("changed", [elem]); |
| | |
| | | |
| | | // Function: setLinkURL |
| | | // Sets the new link URL for the selected anchor element. |
| | | // |
| | | // |
| | | // Parameters: |
| | | // val - String with the link URL/path |
| | | this.setLinkURL = function(val) { |
| | |
| | | return; |
| | | } |
| | | } |
| | | |
| | | |
| | | var cur_href = getHref(elem); |
| | | |
| | | |
| | | if(cur_href === val) return; |
| | | |
| | | |
| | | var batchCmd = new BatchCommand("Change Link URL"); |
| | | |
| | | setHref(elem, val); |
| | |
| | | |
| | | // Function elementAreSame |
| | | // Checks if all the selected Elements are the same type |
| | | // |
| | | // |
| | | // Parameters: |
| | | // None |
| | | |
| | | this.elementsAreSame = function(elements) { |
| | | if (!elements.length || elements[0] == null) return null |
| | | else { |
| | | var isSameElement = function(el) { |
| | | var isSameElement = function(el) { |
| | | if (el && selectedElements[0]) |
| | | return (el.nodeName == selectedElements[0].nodeName); |
| | | else return null; |
| | |
| | | |
| | | // Function: setRectRadius |
| | | // Sets the rx & ry values to the selected rect element to change its corner radius |
| | | // |
| | | // |
| | | // Parameters: |
| | | // val - The new radius |
| | | this.setRectRadius = function(val) { |
| | |
| | | // Wraps the selected element(s) in an anchor element or converts group to one |
| | | this.makeHyperlink = function(url) { |
| | | canvas.groupSelectedElements('a', url); |
| | | |
| | | |
| | | // TODO: If element is a single "g", convert to "a" |
| | | // if(selectedElements.length > 1 && selectedElements[1]) { |
| | | |
| | |
| | | // Group: Element manipulation |
| | | |
| | | // Function: setSegType |
| | | // Sets the new segment type to the selected segment(s). |
| | | // Sets the new segment type to the selected segment(s). |
| | | // |
| | | // Parameters: |
| | | // new_type - Integer with the new segment type |
| | |
| | | // Function: convertToPath |
| | | // Convert selected element to a path, or get the BBox of an element-as-path |
| | | // |
| | | // Parameters: |
| | | // Parameters: |
| | | // elem - The DOM element to be converted |
| | | // getBBox - Boolean on whether or not to only return the path's BBox |
| | | // |
| | |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | |
| | | if(!getBBox) { |
| | | var batchCmd = new BatchCommand("Convert element to Path"); |
| | | } |
| | | |
| | | |
| | | var attrs = getBBox?{}:{ |
| | | "fill": cur_shape.fill, |
| | | "fill-opacity": cur_shape.fill_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? |
| | |
| | | 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) { |
| | |
| | | } else { |
| | | parent.appendChild(path); |
| | | } |
| | | |
| | | |
| | | var d = ''; |
| | | |
| | | |
| | | var joinSegs = function(segs) { |
| | | $.each(segs, function(j, seg) { |
| | | var l = seg[0], pts = seg[1]; |
| | |
| | | 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)]], |
| | |
| | | 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 = getTransformList(path); |
| | |
| | | pathActions.resetOrientation(path); |
| | | } |
| | | } |
| | | |
| | | |
| | | var nextSibling = elem.nextSibling; |
| | | batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, parent)); |
| | | batchCmd.addSubCommand(new InsertElementCommand(path)); |
| | |
| | | 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); |
| | |
| | | |
| | | // Function: changeSelectedAttributeNoUndo |
| | | // This function makes the changes to the elements. It does not add the change |
| | | // to the history stack. |
| | | // |
| | | // to the history stack. |
| | | // |
| | | // Parameters: |
| | | // attr - String with the attribute name |
| | | // newValue - String or number with the new attribute value |
| | |
| | | },0); |
| | | } |
| | | // if this element was rotated, and we changed the position of this element |
| | | // we need to update the rotational transform attribute |
| | | // we need to update the rotational transform attribute |
| | | var angle = getRotationAngle(elem); |
| | | if (angle != 0 && attr != "transform") { |
| | | var tlist = getTransformList(elem); |
| | |
| | | // If you want to change all selectedElements, ignore the elems argument. |
| | | // If you want to change only a subset of selectedElements, then send the |
| | | // subset to this function in the elems argument. |
| | | // |
| | | // |
| | | // Parameters: |
| | | // attr - String with the attribute name |
| | | // newValue - String or number with the new attribute value |
| | |
| | | changeSelectedAttributeNoUndo(attr, val, elems); |
| | | |
| | | var batchCmd = canvas.undoMgr.finishUndoableChange(); |
| | | if (!batchCmd.isEmpty()) { |
| | | if (!batchCmd.isEmpty()) { |
| | | addCommandToHistory(batchCmd); |
| | | } |
| | | }; |
| | | |
| | | // Function: deleteSelectedElements |
| | | // Removes all selected elements from the DOM and adds the change to the |
| | | // Removes all selected elements from the DOM and adds the change to the |
| | | // history stack |
| | | this.deleteSelectedElements = function() { |
| | | var batchCmd = new BatchCommand("Delete Elements"); |
| | |
| | | |
| | | var parent = selected.parentNode; |
| | | var t = selected; |
| | | |
| | | |
| | | // this will unselect the element and remove the selectedOutline |
| | | selectorManager.releaseSelector(t); |
| | | |
| | | |
| | | // Remove the path if present. |
| | | svgedit.path.removePath_(t.id); |
| | | |
| | | |
| | | // Get the parent if it's a single-child anchor |
| | | if(parent.tagName === 'a' && parent.childNodes.length === 1) { |
| | | t = parent; |
| | | parent = parent.parentNode; |
| | | } |
| | | |
| | | |
| | | var nextSibling = t.nextSibling; |
| | | var elem = parent.removeChild(t); |
| | | selectedCopy.push(selected); //for the copy |
| | |
| | | }; |
| | | |
| | | // Function: cutSelectedElements |
| | | // Removes all selected elements from the DOM and adds the change to the |
| | | // Removes all selected elements from the DOM and adds the change to the |
| | | // history stack. Remembers removed elements on the clipboard |
| | | |
| | | // TODO: Combine similar code with deleteSelectedElements |
| | |
| | | if (!batchCmd.isEmpty()) addCommandToHistory(batchCmd); |
| | | call("changed", selectedCopy); |
| | | clearSelection(); |
| | | |
| | | |
| | | canvas.clipBoard = selectedCopy; |
| | | }; |
| | | |
| | |
| | | var cb = canvas.clipBoard; |
| | | var len = cb.length; |
| | | if(!len) return; |
| | | |
| | | |
| | | var pasted = []; |
| | | var batchCmd = new BatchCommand('Paste elements'); |
| | | |
| | | |
| | | // Move elements to lastClickPoint |
| | | |
| | | while (len--) { |
| | |
| | | } |
| | | svgCanvas.clearSelection(); |
| | | setTimeout(function(){selectOnly(pasted)},100); |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | addCommandToHistory(batchCmd); |
| | | call("changed", pasted); |
| | | } |
| | |
| | | // Function: groupSelectedElements |
| | | // Wraps all the selected elements in a group (g) element |
| | | |
| | | // Parameters: |
| | | // Parameters: |
| | | // type - type of element to group into, defaults to <g> |
| | | this.groupSelectedElements = function(type) { |
| | | if(!type) type = 'g'; |
| | | var cmd_str = ''; |
| | | |
| | | |
| | | switch ( type ) { |
| | | case "a": |
| | | cmd_str = "Make hyperlink"; |
| | |
| | | cmd_str = "Group Elements"; |
| | | break; |
| | | } |
| | | |
| | | |
| | | var batchCmd = new BatchCommand(cmd_str); |
| | | |
| | | |
| | | // create and insert the group element |
| | | var g = addSvgElementFromJson({ |
| | | "element": type, |
| | |
| | | setHref(g, url); |
| | | } |
| | | batchCmd.addSubCommand(new InsertElementCommand(g)); |
| | | |
| | | |
| | | // now move all children into the group |
| | | var i = selectedElements.length; |
| | | while (i--) { |
| | | var elem = selectedElements[i]; |
| | | if (elem == null) continue; |
| | | |
| | | |
| | | if (elem.parentNode.tagName === 'a' && elem.parentNode.childNodes.length === 1) { |
| | | elem = elem.parentNode; |
| | | } |
| | | |
| | | |
| | | var oldNextSibling = elem.nextSibling; |
| | | var oldParent = elem.parentNode; |
| | | g.appendChild(elem); |
| | | batchCmd.addSubCommand(new MoveElementCommand(elem, oldNextSibling, oldParent)); |
| | | batchCmd.addSubCommand(new MoveElementCommand(elem, oldNextSibling, oldParent)); |
| | | } |
| | | if (!batchCmd.isEmpty()) addCommandToHistory(batchCmd); |
| | | |
| | | |
| | | // update selection |
| | | selectOnly([g], true); |
| | | }; |
| | |
| | | |
| | | var glist = getTransformList(g); |
| | | var m = transformListToTransform(glist).matrix; |
| | | |
| | | |
| | | var batchCmd = new BatchCommand("Push group properties"); |
| | | |
| | | // TODO: get all fill/stroke properties from the group that we are about to destroy |
| | | // "fill", "fill-opacity", "fill-rule", "stroke", "stroke-dasharray", "stroke-dashoffset", |
| | | // "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", |
| | | // "fill", "fill-opacity", "fill-rule", "stroke", "stroke-dasharray", "stroke-dashoffset", |
| | | // "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", |
| | | // "stroke-width" |
| | | // and then for each child, if they do not have the attribute (or the value is 'inherit') |
| | | // then set the child's attribute |
| | | |
| | | |
| | | var i = 0; |
| | | var gangle = getRotationAngle(g); |
| | | |
| | | |
| | | var gattrs = $(g).attr(['filter', 'opacity']); |
| | | var gfilter, gblur; |
| | | |
| | | |
| | | for(var i = 0; i < len; i++) { |
| | | var elem = children[i]; |
| | | |
| | | |
| | | if(elem.nodeType !== 1) continue; |
| | | |
| | | |
| | | if(gattrs.opacity !== null && gattrs.opacity !== 1) { |
| | | var c_opac = elem.getAttribute('opacity') || 1; |
| | | var new_opac = Math.round((elem.getAttribute('opacity') || 1) * gattrs.opacity * 100)/100; |
| | |
| | | } else if(cblur === 0) { |
| | | cblur = gblur; |
| | | } |
| | | |
| | | |
| | | // If child has no current filter, get group's filter or clone it. |
| | | if(!orig_cblur) { |
| | | // Set group's filter to use first child's ID |
| | |
| | | } |
| | | |
| | | // Change this in future for different filters |
| | | var suffix = (gfilter.firstChild.tagName === 'feGaussianBlur')?'blur':'filter'; |
| | | var suffix = (gfilter.firstChild.tagName === 'feGaussianBlur')?'blur':'filter'; |
| | | gfilter.id = elem.id + '_' + suffix; |
| | | changeSelectedAttribute('filter', 'url(#' + gfilter.id + ')', [elem]); |
| | | |
| | | // Update blur value |
| | | |
| | | // Update blur value |
| | | if(cblur) { |
| | | changeSelectedAttribute('stdDeviation', cblur, [gfilter.firstChild]); |
| | | canvas.setBlurOffsets(gfilter, cblur); |
| | | } |
| | | } |
| | | |
| | | |
| | | var chtlist = getTransformList(elem); |
| | | |
| | | // Don't process gradient transforms |
| | | if(~elem.tagName.indexOf('Gradient')) chtlist = null; |
| | | |
| | | |
| | | // Hopefully not a problem to add this. Necessary for elements like <desc/> |
| | | if(!chtlist) continue; |
| | | |
| | | |
| | | // Apparently <defs> can get get a transformlist, but we don't want it to have one! |
| | | if(elem.tagName === 'defs') continue; |
| | | |
| | | |
| | | if (glist.numberOfItems) { |
| | | // TODO: if the group's transform is just a rotate, we can always transfer the |
| | | // rotate() down to the children (collapsing consecutive rotates and factoring |
| | |
| | | if (gangle && glist.numberOfItems == 1) { |
| | | // [Rg] [Rc] [Mc] |
| | | // we want [Tr] [Rc2] [Mc] where: |
| | | // - [Rc2] is at the child's current center but has the |
| | | // - [Rc2] is at the child's current center but has the |
| | | // sum of the group and child's rotation angles |
| | | // - [Tr] is the equivalent translation that this child |
| | | // - [Tr] is the equivalent translation that this child |
| | | // undergoes if the group wasn't there |
| | | |
| | | |
| | | // [Tr] = [Rg] [Rc] [Rc2_inv] |
| | | |
| | | |
| | | // get group's rotation matrix (Rg) |
| | | var rgm = glist.getItem(0).matrix; |
| | | |
| | | |
| | | // get child's rotation matrix (Rc) |
| | | var rcm = svgroot.createSVGMatrix(); |
| | | var cangle = getRotationAngle(elem); |
| | | if (cangle) { |
| | | rcm = chtlist.getItem(0).matrix; |
| | | } |
| | | |
| | | |
| | | // get child's old center of rotation |
| | | var cbox = svgedit.utilities.getBBox(elem); |
| | | var ceqm = transformListToTransform(chtlist).matrix; |
| | | var coldc = transformPoint(cbox.x+cbox.width/2, cbox.y+cbox.height/2,ceqm); |
| | | |
| | | |
| | | // sum group and child's angles |
| | | var sangle = gangle + cangle; |
| | | |
| | | |
| | | // get child's rotation at the old center (Rc2_inv) |
| | | var r2 = svgroot.createSVGTransform(); |
| | | r2.setRotate(sangle, coldc.x, coldc.y); |
| | | |
| | | |
| | | // calculate equivalent translate |
| | | var trm = matrixMultiply(rgm, rcm, r2.matrix.inverse()); |
| | | |
| | | |
| | | // set up tlist |
| | | if (cangle) { |
| | | chtlist.removeItem(0); |
| | | } |
| | | |
| | | |
| | | if (sangle) { |
| | | if(chtlist.numberOfItems) { |
| | | chtlist.insertItemBefore(r2, 0); |
| | |
| | | } |
| | | } |
| | | else { // more complicated than just a rotate |
| | | |
| | | |
| | | // transfer the group's transform down to each child and then |
| | | // call recalculateDimensions() |
| | | // call recalculateDimensions() |
| | | var oldxform = elem.getAttribute("transform"); |
| | | var changes = {}; |
| | | changes["transform"] = oldxform ? oldxform : ""; |
| | |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | // remove transform and make it undo-able |
| | | if (xform) { |
| | | var changes = {}; |
| | | changes["transform"] = xform; |
| | | g.setAttribute("transform", ""); |
| | | g.removeAttribute("transform"); |
| | | g.removeAttribute("transform"); |
| | | batchCmd.addSubCommand(new ChangeElementCommand(g, changes)); |
| | | } |
| | | |
| | | |
| | | if (undoable && !batchCmd.isEmpty()) { |
| | | return batchCmd; |
| | | } |
| | |
| | | if(parents_a.length) { |
| | | g = parents_a[0]; |
| | | } |
| | | |
| | | |
| | | // Look for parent "a" |
| | | if (g.tagName === "g" || g.tagName === "a") { |
| | | |
| | | |
| | | var batchCmd = new BatchCommand("Ungroup Elements"); |
| | | var cmd = pushGroupProperties(g, true); |
| | | if(cmd) batchCmd.addSubCommand(cmd); |
| | | |
| | | |
| | | var parent = g.parentNode; |
| | | var anchor = g.nextSibling; |
| | | var children = new Array(g.childNodes.length); |
| | | |
| | | |
| | | var i = 0; |
| | | |
| | | |
| | | while (g.firstChild) { |
| | | var elem = g.firstChild; |
| | | var oldNextSibling = elem.nextSibling; |
| | | var oldParent = elem.parentNode; |
| | | |
| | | |
| | | // Remove child title elements |
| | | if(elem.tagName === 'title') { |
| | | var nextSibling = elem.nextSibling; |
| | |
| | | oldParent.removeChild(elem); |
| | | continue; |
| | | } |
| | | |
| | | |
| | | children[i++] = elem = parent.insertBefore(elem, anchor); |
| | | batchCmd.addSubCommand(new MoveElementCommand(elem, oldNextSibling, oldParent)); |
| | | } |
| | | |
| | | // remove the group from the selection |
| | | // remove the group from the selection |
| | | clearSelection(); |
| | | |
| | | |
| | | // delete the group element (but make undo-able) |
| | | var gNextSibling = g.nextSibling; |
| | | g = parent.removeChild(g); |
| | | batchCmd.addSubCommand(new RemoveElementCommand(g, gNextSibling, parent)); |
| | | |
| | | if (!batchCmd.isEmpty()) addCommandToHistory(batchCmd); |
| | | |
| | | |
| | | // update selection |
| | | addToSelection(children); |
| | | } |
| | |
| | | }; |
| | | |
| | | // Function: moveToBottomSelectedElement |
| | | // Repositions the selected element to the top in the DOM to appear under |
| | | // Repositions the selected element to the top in the DOM to appear under |
| | | // other elements |
| | | this.moveToBottomSelectedElement = function() { |
| | | var selected = selectedElements.filter(Boolean).reverse(); |
| | |
| | | // Moves the select element up or down the stack, based on the visibly |
| | | // intersecting elements |
| | | // |
| | | // Parameters: |
| | | // Parameters: |
| | | // dir - String that's either 'Up' or 'Down' |
| | | this.moveUpDownSelected = function(dir) { |
| | | var selected = selectedElements.filter(Boolean); |
| | |
| | | return false; |
| | | }); |
| | | if(!closest) return; |
| | | |
| | | |
| | | var t = selected; |
| | | var oldParent = t.parentNode; |
| | | var oldNextSibling = t.nextSibling; |
| | |
| | | }; |
| | | |
| | | // Function: moveSelectedElements |
| | | // Moves selected elements on the X/Y axis |
| | | // Moves selected elements on the X/Y axis |
| | | // |
| | | // Parameters: |
| | | // dx - Float with the distance to move on the x-axis |
| | |
| | | if (selected != null) { |
| | | // if (i==0) |
| | | // selectedBBoxes[0] = svgedit.utilities.getBBox(selected); |
| | | |
| | | |
| | | // var b = {}; |
| | | // for(var j in selectedBBoxes[i]) b[j] = selectedBBoxes[i][j]; |
| | | // selectedBBoxes[i] = b; |
| | | |
| | | |
| | | var xform = svgroot.createSVGTransform(); |
| | | var tlist = getTransformList(selected); |
| | | |
| | | |
| | | // dx and dy could be arrays |
| | | if (dx.constructor == Array) { |
| | | // if (i==0) { |
| | |
| | | } else { |
| | | tlist.appendItem(xform); |
| | | } |
| | | |
| | | |
| | | var cmd = recalculateDimensions(selected); |
| | | if (cmd) { |
| | | batchCmd.addSubCommand(cmd); |
| | | } |
| | | |
| | | |
| | | selectorManager.requestSelector(selected).resize(); |
| | | } |
| | | } |
| | |
| | | }; |
| | | |
| | | // Function: cloneSelectedElements |
| | | // Create deep DOM copies (clones) of all selected elements and move them slightly |
| | | // Create deep DOM copies (clones) of all selected elements and move them slightly |
| | | // from their originals |
| | | this.cloneSelectedElements = function(x,y, drag) { |
| | | var batchCmd = new BatchCommand("Clone Elements"); |
| | |
| | | clones = [] |
| | | while (i--) { |
| | | // clone each element and replace it within copiedElements |
| | | var elem = copiedElements[i] |
| | | var elem = copiedElements[i] |
| | | var clone = copyElem(copiedElements[i]); |
| | | var parent = (current_group || getCurrentDrawing().getCurrentLayer()) |
| | | if (drag) { |
| | |
| | | clones.push(clone) |
| | | batchCmd.addSubCommand(new InsertElementCommand(clone)); |
| | | } |
| | | |
| | | |
| | | if (!batchCmd.isEmpty()) { |
| | | addToSelection(copiedElements.reverse()); // Need to reverse for correct selection-adding |
| | | if (!drag) this.moveSelectedElements(x,y,false); |
| | |
| | | // |
| | | // Parameters: |
| | | // type - String with single character indicating the alignment type |
| | | // relative_to - String that must be one of the following: |
| | | // relative_to - String that must be one of the following: |
| | | // "selected", "largest", "smallest", "page" |
| | | this.alignSelectedElements = function(type, relative_to) { |
| | | var bboxes = [], angles = []; |
| | |
| | | if (selectedElements[i] == null) break; |
| | | var elem = selectedElements[i]; |
| | | bboxes[i] = getStrokedBBox([elem]); |
| | | |
| | | |
| | | // now bbox is axis-aligned and handles rotation |
| | | switch (relative_to) { |
| | | case 'smallest': |
| | |
| | | this.contentH = getResolution().h; |
| | | |
| | | // Function: updateCanvas |
| | | // Updates the editor canvas width/height/position after a zoom has occurred |
| | | // Updates the editor canvas width/height/position after a zoom has occurred |
| | | // |
| | | // Parameters: |
| | | // w - Float with the new width |
| | | // h - Float with the new height |
| | | // |
| | | // Returns: |
| | | // Returns: |
| | | // Object with the following values: |
| | | // * x - The canvas' new x coordinate |
| | | // * y - The canvas' new y coordinate |
| | |
| | | 'y': y, |
| | | "viewBox" : "0 0 " + this.contentW + " " + this.contentH |
| | | }); |
| | | |
| | | |
| | | assignAttributes(bg, { |
| | | width: svgcontent.getAttribute('width'), |
| | | height: svgcontent.getAttribute('height'), |
| | |
| | | 'height': '100%' |
| | | }); |
| | | } |
| | | |
| | | |
| | | selectorManager.selectorParentGroup.setAttribute("transform","translate(" + x + "," + y + ")"); |
| | | |
| | | |
| | | return {x:x, y:y, old_x:old_x, old_y:old_y, d_x:x - old_x, d_y:y - old_y}; |
| | | } |
| | | |
| | |
| | | num = 0; |
| | | } else if(num < 0) { |
| | | num = all_elems.length-1; |
| | | } |
| | | } |
| | | elem = all_elems[num]; |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | selectOnly([elem], true); |
| | | call("selected", selectedElements); |
| | | } |
| | |
| | | this.clear(); |
| | | |
| | | |
| | | // DEPRECATED: getPrivateMethods |
| | | // DEPRECATED: getPrivateMethods |
| | | // Since all methods are/should be public somehow, this function should be removed |
| | | |
| | | // Being able to access private methods publicly seems wrong somehow, |