From 29534a7d4b7659002474731219d908fd67d07acf Mon Sep 17 00:00:00 2001 From: Jeff Schiller Date: Sun, 17 Feb 2013 04:58:04 +0000 Subject: [PATCH] Move remapElement() to coords and add a first unit test git-svn-id: http://svg-edit.googlecode.com/svn/trunk@2416 eee81c28-f429-11dd-99c0-75d572ba1ddd --- editor/coords.js | 307 +++++++++++++++++++++++++++++- editor/svgcanvas.js | 427 +++++++----------------------------------- editor/svgutils.js | 14 ++ test/coords_test.html | 72 +++++-- 4 files changed, 445 insertions(+), 375 deletions(-) diff --git a/editor/coords.js b/editor/coords.js index e5cec667..2318996a 100644 --- a/editor/coords.js +++ b/editor/coords.js @@ -6,11 +6,12 @@ */ // Dependencies: -// 1) svgtransformlist.js +// 1) jquery.js // 2) math.js -// 3) svgutils.js -// 4) jquery.js -// 5) history.js +// 3) browser.js +// 4) svgutils.js +// 5) units.js +// 6) svgtransformlist.js var svgedit = svgedit || {}; @@ -20,7 +21,303 @@ if (!svgedit.coords) { svgedit.coords = {}; } -// TODO: Move remapElement() into here. +// this is how we map paths to our preferred relative segment types +var pathMap = [0, 'z', 'M', 'm', 'L', 'l', 'C', 'c', 'Q', 'q', 'A', 'a', + 'H', 'h', 'V', 'v', 'S', 's', 'T', 't']; + +var editorContext_ = null; + +svgedit.coords.init = function(editorContext) { + editorContext_ = editorContext; +}; + +// Function: remapElement +// Applies coordinate changes to an element based on the given matrix +// +// Parameters: +// selected - DOM element to be changed +// changes - Object with changes to be remapped +// m - Matrix object to use for remapping coordinates +svgedit.coords.remapElement = function(selected, changes, m) { + + var remap = function(x, y) { return svgedit.math.transformPoint(x, y, m); }, + scalew = function(w) { return m.a * w; }, + scaleh = function(h) { return m.d * h; }, + doSnapping = editorContext_.getGridSnapping() && selected.parentNode.parentNode.localName === 'svg', + finishUp = function() { + if (doSnapping) for (var o in changes) changes[o] = svgedit.utilities.snapToGrid(changes[o]); + svgedit.utilities.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 (attrVal && attrVal.indexOf('url(') === 0) { + if (m.a < 0 || m.d < 0) { + var grad = svgedit.utilities.getRefElem(attrVal); + var newgrad = grad.cloneNode(true); + + if (m.a < 0) { + // flip x + var x1 = newgrad.getAttribute('x1'); + var x2 = newgrad.getAttribute('x2'); + newgrad.setAttribute('x1', -(x1 - 1)); + newgrad.setAttribute('x2', -(x2 - 1)); + } + + if (m.d < 0) { + // flip y + var y1 = newgrad.getAttribute('y1'); + var y2 = newgrad.getAttribute('y2'); + newgrad.setAttribute('y1', -(y1 - 1)); + newgrad.setAttribute('y2', -(y2 - 1)); + } + newgrad.id = editorContext_.getDrawing().getNextId(); + svgedit.utilities.findDefs().appendChild(newgrad); + selected.setAttribute(type, 'url(#' + newgrad.id + ')'); + } + + // Not really working :( +// if (selected.tagName === 'path') { +// reorientGrads(selected, m); +// } + } + } + + var elName = selected.tagName; + if (elName === 'g' || elName === 'text' || elName == 'tspan' || elName === 'use') { + // if it was a translate, then just update x,y + if (m.a == 1 && m.b == 0 && m.c == 0 && m.d == 1 && + (m.e != 0 || m.f != 0) ) + { + // [T][M] = [M][T'] + // therefore [T'] = [M_inv][T][M] + var existing = svgedit.math.transformListToTransform(selected).matrix, + t_new = svgedit.math.matrixMultiply(existing.inverse(), m, existing); + changes.x = parseFloat(changes.x) + t_new.e; + changes.y = parseFloat(changes.y) + t_new.f; + // TODO(codedread): Special handing for tspans: + // + // + // ... + // + // + // + // Note that if the element's x/y coordinates are being + // adjusted, the tspan's x/y coordinates also need to be similarly + // transformed as the coordinate space is not nested: + // + // ... + // + } + else { + // we just absorb all matrices into the element and don't do any remapping + var chlist = svgedit.transformlist.getTransformList(selected); + var mt = svgroot.createSVGTransform(); + mt.setMatrix(svgedit.math.matrixMultiply(svgedit.math.transformListToTransform(chlist).matrix, m)); + chlist.clear(); + 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 + var chlist = svgedit.transformlist.getTransformList(selected); + var mt = svgroot.createSVGTransform(); + mt.setMatrix(svgedit.math.matrixMultiply(svgedit.math.transformListToTransform(chlist).matrix, m)); + chlist.clear(); + 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.height = Math.abs(changes.height); + } + finishUp(); + break; + case 'ellipse': + var c = remap(changes.cx, changes.cy); + changes.cx = c.x; + changes.cy = c.y; + changes.rx = scalew(changes.rx); + changes.ry = scaleh(changes.ry); + + changes.rx = Math.abs(changes.rx); + changes.ry = Math.abs(changes.ry); + finishUp(); + break; + case 'circle': + var c = remap(changes.cx,changes.cy); + changes.cx = c.x; + changes.cy = c.y; + // take the minimum of the new selected box's dimensions for the new circle radius + var tbox = svgedit.math.transformBox(box.x, box.y, box.width, box.height, m); + var w = tbox.tr.x - tbox.tl.x, h = tbox.bl.y - tbox.tl.y; + changes.r = Math.min(w/2, h/2); + + if (changes.r) changes.r = Math.abs(changes.r); + finishUp(); + break; + case 'line': + var pt1 = remap(changes.x1, changes.y1), + pt2 = remap(changes.x2, changes.y2); + changes.x1 = pt1.x; + changes.y1 = pt1.y; + changes.x2 = pt2.x; + changes.y2 = pt2.y; + + case 'text': + case 'use': + finishUp(); + break; + case 'g': + var gsvg = $(selected).data('gsvg'); + if (gsvg) { + svgedit.utilities.assignAttributes(gsvg, changes, 1000, true); + } + break; + case 'polyline': + case 'polygon': + var len = changes.points.length; + for (var i = 0; i < len; ++i) { + var pt = changes.points[i]; + pt = remap(pt.x, pt.y); + changes.points[i].x = pt.x; + changes.points[i].y = pt.y; + } + + var len = changes.points.length; + var pstr = ''; + for (var i = 0; i < len; ++i) { + var pt = changes.points[i]; + pstr += pt.x + ',' + pt.y + ' '; + } + selected.setAttribute('points', pstr); + break; + case 'path': + + var segList = selected.pathSegList; + var len = segList.numberOfItems; + changes.d = new Array(len); + for (var i = 0; i < len; ++i) { + var seg = segList.getItem(i); + changes.d[i] = { + type: seg.pathSegType, + x: seg.x, + y: seg.y, + x1: seg.x1, + y1: seg.y1, + x2: seg.x2, + y2: seg.y2, + r1: seg.r1, + r2: seg.r2, + angle: seg.angle, + largeArcFlag: seg.largeArcFlag, + sweepFlag: seg.sweepFlag + }; + } + + var len = changes.d.length, + firstseg = changes.d[0], + currentpt = remap(firstseg.x, firstseg.y); + changes.d[0].x = currentpt.x; + changes.d[0].y = currentpt.y; + for (var i = 1; i < len; ++i) { + var seg = changes.d[i]; + var type = seg.type; + // if absolute or first segment, we want to remap x, y, x1, y1, x2, y2 + // if relative, we want to scalew, scaleh + if (type % 2 == 0) { // absolute + var thisx = (seg.x != undefined) ? seg.x : currentpt.x, // for V commands + thisy = (seg.y != undefined) ? seg.y : currentpt.y, // for H commands + pt = remap(thisx,thisy), + pt1 = remap(seg.x1, seg.y1), + pt2 = remap(seg.x2, seg.y2); + seg.x = pt.x; + seg.y = pt.y; + seg.x1 = pt1.x; + seg.y1 = pt1.y; + seg.x2 = pt2.x; + seg.y2 = pt2.y; + seg.r1 = scalew(seg.r1), + seg.r2 = scaleh(seg.r2); + } + else { // relative + seg.x = scalew(seg.x); + seg.y = scaleh(seg.y); + seg.x1 = scalew(seg.x1); + seg.y1 = scaleh(seg.y1); + seg.x2 = scalew(seg.x2); + seg.y2 = scaleh(seg.y2); + seg.r1 = scalew(seg.r1), + seg.r2 = scaleh(seg.r2); + } + } // for each segment + + var dstr = ''; + var len = changes.d.length; + for (var i = 0; i < len; ++i) { + var seg = changes.d[i]; + var type = seg.type; + dstr += pathMap[type]; + switch (type) { + case 13: // relative horizontal line (h) + case 12: // absolute horizontal line (H) + dstr += seg.x + ' '; + break; + case 15: // relative vertical line (v) + case 14: // absolute vertical line (V) + dstr += seg.y + ' '; + break; + case 3: // relative move (m) + case 5: // relative line (l) + case 19: // relative smooth quad (t) + case 2: // absolute move (M) + case 4: // absolute line (L) + case 18: // absolute smooth quad (T) + dstr += seg.x + ',' + seg.y + ' '; + break; + case 7: // relative cubic (c) + case 6: // absolute cubic (C) + dstr += seg.x1 + ',' + seg.y1 + ' ' + seg.x2 + ',' + seg.y2 + ' ' + + seg.x + ',' + seg.y + ' '; + break; + case 9: // relative quad (q) + case 8: // absolute quad (Q) + dstr += seg.x1 + ',' + seg.y1 + ' ' + seg.x + ',' + seg.y + ' '; + break; + case 11: // relative elliptical arc (a) + case 10: // absolute elliptical arc (A) + dstr += seg.r1 + ',' + seg.r2 + ' ' + seg.angle + ' ' + (+seg.largeArcFlag) + + ' ' + (+seg.sweepFlag) + ' ' + seg.x + ',' + seg.y + ' '; + break; + case 17: // relative smooth cubic (s) + case 16: // absolute smooth cubic (S) + dstr += seg.x2 + ',' + seg.y2 + ' ' + seg.x + ',' + seg.y + ' '; + break; + } + } + + selected.setAttribute('d', dstr); + break; + } +}; + // TODO: Move updateClipPath() into here. // TODO: Move recalculateDimensions() into here. diff --git a/editor/svgcanvas.js b/editor/svgcanvas.js index 909a4919..dad9b365 100644 --- a/editor/svgcanvas.js +++ b/editor/svgcanvas.js @@ -21,6 +21,7 @@ // 9) select.js // 10) draw.js // 11) path.js +// 12) coords.js if (!window.console) { window.console = {}; @@ -303,7 +304,9 @@ svgedit.utilities.init({ getSVGRoot: function() { return svgroot; }, // TODO: replace this mostly with a way to get the current drawing. getSelectedElements: function() { return selectedElements; }, - getSVGContent: function() { return svgcontent; } + getSVGContent: function() { return svgcontent; }, + getBaseUnit: function() { return curConfig.baseUnit; }, + getStepSize: function() { return curConfig.stepSize; } }); var findDefs = canvas.findDefs = svgedit.utilities.findDefs; var getUrlFromAttr = canvas.getUrlFromAttr = svgedit.utilities.getUrlFromAttr; @@ -317,6 +320,13 @@ var getRefElem = canvas.getRefElem = svgedit.utilities.getRefElem; var assignAttributes = canvas.assignAttributes = svgedit.utilities.assignAttributes; var cleanupElement = this.cleanupElement = svgedit.utilities.cleanupElement; +// import from coords.js +svgedit.coords.init({ + getDrawing: function() { return getCurrentDrawing(); }, + getGridSnapping: function() { return curConfig.gridSnapping; } +}); +var remapElement = this.remapElement = svgedit.coords.remapElement; + // import from sanitize.js var nsMap = svgedit.getReverseNS(); var sanitizeSvg = canvas.sanitizeSvg = svgedit.sanitize.sanitizeSvg; @@ -407,19 +417,6 @@ svgedit.path.init({ getSVGRoot: function() { return svgroot; } }); -// Function: snapToGrid -// round value to for snapping -// NOTE: This function did not move to svgutils.js since it depends on curConfig. -svgedit.utilities.snapToGrid = function(value){ - var stepSize = curConfig.snappingStep; - var unit = curConfig.baseUnit; - if (unit !== "px") { - stepSize *= svgedit.units.getTypeMap()[unit]; - } - return Math.round(value/stepSize)*stepSize; -}; -var snapToGrid = svgedit.utilities.snapToGrid; - // Interface strings, usually for title elements var uiStrings = { "exportNoBlur": "Blurred elements will appear as un-blurred", @@ -1064,290 +1061,6 @@ var logMatrix = function(m) { console.log([m.a, m.b, m.c, m.d, m.e, m.f]); }; -// Function: remapElement -// Applies coordinate changes to an element based on the given matrix -// -// Parameters: -// selected - DOM element to be changed -// changes - Object with changes to be remapped -// m - Matrix object to use for remapping coordinates -var remapElement = this.remapElement = function(selected, changes, m) { - - var remap = function(x, y) { return svgedit.math.transformPoint(x, y, m); }, - scalew = function(w) { return m.a*w; }, - scaleh = function(h) { return m.d*h; }, - doSnapping = curConfig.gridSnapping && selected.parentNode.parentNode.localName === "svg", - finishUp = function() { - if (doSnapping) for (var o in changes) changes[o] = snapToGrid(changes[o]); - 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 (attrVal && attrVal.indexOf('url(') === 0) { - if (m.a < 0 || m.d < 0) { - var grad = svgedit.utilities.getRefElem(attrVal); - var newgrad = grad.cloneNode(true); - - if (m.a < 0) { - //flip x - var x1 = newgrad.getAttribute('x1'); - var x2 = newgrad.getAttribute('x2'); - newgrad.setAttribute('x1', -(x1 - 1)); - newgrad.setAttribute('x2', -(x2 - 1)); - } - - if (m.d < 0) { - //flip y - var y1 = newgrad.getAttribute('y1'); - var y2 = newgrad.getAttribute('y2'); - newgrad.setAttribute('y1', -(y1 - 1)); - newgrad.setAttribute('y2', -(y2 - 1)); - } - newgrad.id = getNextId(); - svgedit.utilities.findDefs().appendChild(newgrad); - selected.setAttribute(type, 'url(#' + newgrad.id + ')'); - } - - // Not really working :( -// if (selected.tagName === 'path') { -// reorientGrads(selected, m); -// } - } - } - - var elName = selected.tagName; - if (elName === "g" || elName === "text" || elName == "tspan" || elName === "use") { - // if it was a translate, then just update x,y - if (m.a == 1 && m.b == 0 && m.c == 0 && m.d == 1 && - (m.e != 0 || m.f != 0) ) - { - // [T][M] = [M][T'] - // therefore [T'] = [M_inv][T][M] - var existing = svgedit.math.transformListToTransform(selected).matrix, - t_new = svgedit.math.matrixMultiply(existing.inverse(), m, existing); - changes.x = parseFloat(changes.x) + t_new.e; - changes.y = parseFloat(changes.y) + t_new.f; - // TODO(codedread): Special handing for tspans: - // - // - // ... - // - // - // - // Note that if the element's x/y coordinates are being - // adjusted, the tspan's x/y coordinates also need to be similarly - // transformed as the coordinate space is not nested: - // - // ... - // - - } else { - // we just absorb all matrices into the element and don't do any remapping - var chlist = svgedit.transformlist.getTransformList(selected); - var mt = svgroot.createSVGTransform(); - mt.setMatrix(matrixMultiply(svgedit.math.transformListToTransform(chlist).matrix, m)); - chlist.clear(); - 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 - var chlist = svgedit.transformlist.getTransformList(selected); - var mt = svgroot.createSVGTransform(); - mt.setMatrix(svgedit.math.matrixMultiply(svgedit.math.transformListToTransform(chlist).matrix, m)); - chlist.clear(); - 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.height = Math.abs(changes.height); - } - finishUp(); - break; - case "ellipse": - var c = remap(changes.cx, changes.cy); - changes.cx = c.x; - changes.cy = c.y; - changes.rx = scalew(changes.rx); - changes.ry = scaleh(changes.ry); - - changes.rx = Math.abs(changes.rx); - changes.ry = Math.abs(changes.ry); - finishUp(); - break; - case "circle": - var c = remap(changes.cx, changes.cy); - changes.cx = c.x; - changes.cy = c.y; - // take the minimum of the new selected box's dimensions for the new circle radius - var tbox = svgedit.math.transformBox(box.x, box.y, box.width, box.height, m); - var w = tbox.tr.x - tbox.tl.x, h = tbox.bl.y - tbox.tl.y; - changes.r = Math.min(w/2, h/2); - - if (changes.r) changes.r = Math.abs(changes.r); - finishUp(); - break; - case "line": - var pt1 = remap(changes.x1, changes.y1), - pt2 = remap(changes.x2, changes.y2); - changes.x1 = pt1.x; - changes.y1 = pt1.y; - changes.x2 = pt2.x; - changes.y2 = pt2.y; - - case "text": - case "use": - finishUp(); - break; - case "g": - var gsvg = $(selected).data('gsvg'); - if (gsvg) { - assignAttributes(gsvg, changes, 1000, true); - } - break; - case "polyline": - case "polygon": - var len = changes.points.length; - for (var i = 0; i < len; ++i) { - var pt = changes.points[i]; - pt = remap(pt.x, pt.y); - changes.points[i].x = pt.x; - changes.points[i].y = pt.y; - } - - var len = changes.points.length; - var pstr = ""; - for (var i = 0; i < len; ++i) { - var pt = changes.points[i]; - pstr += pt.x + "," + pt.y + " "; - } - selected.setAttribute("points", pstr); - break; - case "path": - - var segList = selected.pathSegList; - var len = segList.numberOfItems; - changes.d = new Array(len); - for (var i = 0; i < len; ++i) { - var seg = segList.getItem(i); - changes.d[i] = { - type: seg.pathSegType, - x: seg.x, - y: seg.y, - x1: seg.x1, - y1: seg.y1, - x2: seg.x2, - y2: seg.y2, - r1: seg.r1, - r2: seg.r2, - angle: seg.angle, - largeArcFlag: seg.largeArcFlag, - sweepFlag: seg.sweepFlag - }; - } - - var len = changes.d.length, - firstseg = changes.d[0], - currentpt = remap(firstseg.x, firstseg.y); - changes.d[0].x = currentpt.x; - changes.d[0].y = currentpt.y; - for (var i = 1; i < len; ++i) { - var seg = changes.d[i]; - var type = seg.type; - // if absolute or first segment, we want to remap x, y, x1, y1, x2, y2 - // if relative, we want to scalew, scaleh - if (type % 2 == 0) { // absolute - var thisx = (seg.x != undefined) ? seg.x : currentpt.x, // for V commands - thisy = (seg.y != undefined) ? seg.y : currentpt.y, // for H commands - pt = remap(thisx, thisy), - pt1 = remap(seg.x1, seg.y1), - pt2 = remap(seg.x2, seg.y2); - seg.x = pt.x; - seg.y = pt.y; - seg.x1 = pt1.x; - seg.y1 = pt1.y; - seg.x2 = pt2.x; - seg.y2 = pt2.y; - seg.r1 = scalew(seg.r1), - seg.r2 = scaleh(seg.r2); - } else { // relative - seg.x = scalew(seg.x); - seg.y = scaleh(seg.y); - seg.x1 = scalew(seg.x1); - seg.y1 = scaleh(seg.y1); - seg.x2 = scalew(seg.x2); - seg.y2 = scaleh(seg.y2); - seg.r1 = scalew(seg.r1), - seg.r2 = scaleh(seg.r2); - } - } // for each segment - - var dstr = ""; - var len = changes.d.length; - for (var i = 0; i < len; ++i) { - var seg = changes.d[i]; - var type = seg.type; - dstr += pathMap[type]; - switch(type) { - case 13: // relative horizontal line (h) - case 12: // absolute horizontal line (H) - dstr += seg.x + " "; - break; - case 15: // relative vertical line (v) - case 14: // absolute vertical line (V) - dstr += seg.y + " "; - break; - case 3: // relative move (m) - case 5: // relative line (l) - case 19: // relative smooth quad (t) - case 2: // absolute move (M) - case 4: // absolute line (L) - case 18: // absolute smooth quad (T) - dstr += seg.x + "," + seg.y + " "; - break; - case 7: // relative cubic (c) - case 6: // absolute cubic (C) - dstr += seg.x1 + "," + seg.y1 + " " + seg.x2 + "," + seg.y2 + " " + - seg.x + "," + seg.y + " "; - break; - case 9: // relative quad (q) - case 8: // absolute quad (Q) - dstr += seg.x1 + "," + seg.y1 + " " + seg.x + "," + seg.y + " "; - break; - case 11: // relative elliptical arc (a) - case 10: // absolute elliptical arc (A) - dstr += seg.r1 + "," + seg.r2 + " " + seg.angle + " " + (+seg.largeArcFlag) + - " " + (+seg.sweepFlag) + " " + seg.x + "," + seg.y + " "; - break; - case 17: // relative smooth cubic (s) - case 16: // absolute smooth cubic (S) - dstr += seg.x2 + "," + seg.y2 + " " + seg.x + "," + seg.y + " "; - break; - } - } - - selected.setAttribute("d", dstr); - break; - } -}; // Function: updateClipPath // Updates a s values based on the given translation of an element @@ -2048,7 +1761,7 @@ var recalculateDimensions = this.recalculateDimensions = function(selected) { // 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); + svgedit.coords.remapElement(selected,changes,m); } // if we are remapping // if it was a translate, put back the rotate at the new center @@ -2084,7 +1797,7 @@ var recalculateDimensions = this.recalculateDimensions = function(selected) { var m_inv = m.inverse(); var extrat = svgedit.math.matrixMultiply(m_inv, rnew_inv, rold, m); - remapElement(selected, changes, extrat); + svgedit.coords.remapElement(selected,changes,extrat); if (angle) { if (tlist.numberOfItems) { tlist.insertItemBefore(rnew, 0); @@ -2429,10 +2142,10 @@ var getMouseTarget = this.getMouseTarget = function(evt) { var real_y = r_start_y = start_y = y; if (curConfig.gridSnapping){ - x = snapToGrid(x); - y = snapToGrid(y); - start_x = snapToGrid(start_x); - start_y = snapToGrid(start_y); + x = svgedit.utilities.snapToGrid(x); + y = svgedit.utilities.snapToGrid(y); + start_x = svgedit.utilities.snapToGrid(start_x); + start_y = svgedit.utilities.snapToGrid(start_y); } // if it is a selector grip, then it must be a single element selected, @@ -2502,7 +2215,7 @@ var getMouseTarget = this.getMouseTarget = function(evt) { // console.log('o',[evt.offsetX, evt.offsetY]); // console.log('s',[start_x, start_y]); - assignAttributes(rubberBox, { + svgedit.utilities.assignAttributes(rubberBox, { 'x': r_start_x, 'y': r_start_y, 'width': 0, @@ -2516,7 +2229,7 @@ var getMouseTarget = this.getMouseTarget = function(evt) { if (rubberBox == null) { rubberBox = selectorManager.getRubberBandBox(); } - assignAttributes(rubberBox, { + svgedit.utilities.assignAttributes(rubberBox, { 'x': real_x * current_zoom, 'y': real_x * current_zoom, 'width': 0, @@ -2768,8 +2481,8 @@ var getMouseTarget = this.getMouseTarget = function(evt) { var real_y = y = mouse_y / current_zoom; if (curConfig.gridSnapping){ - x = snapToGrid(x); - y = snapToGrid(y); + x = svgedit.utilities.snapToGrid(x); + y = svgedit.utilities.snapToGrid(y); } evt.preventDefault(); @@ -2784,8 +2497,8 @@ var getMouseTarget = this.getMouseTarget = function(evt) { var dy = y - start_y; if (curConfig.gridSnapping){ - dx = snapToGrid(dx); - dy = snapToGrid(dy); + dx = svgedit.utilities.snapToGrid(dx); + dy = svgedit.utilities.snapToGrid(dy); } if (evt.shiftKey) { @@ -2831,7 +2544,7 @@ var getMouseTarget = this.getMouseTarget = function(evt) { case "multiselect": real_x *= current_zoom; real_y *= current_zoom; - assignAttributes(rubberBox, { + svgedit.utilities.assignAttributes(rubberBox, { 'x': Math.min(r_start_x, real_x), 'y': Math.min(r_start_y, real_y), 'width': Math.abs(real_x - r_start_x), @@ -2876,10 +2589,10 @@ var getMouseTarget = this.getMouseTarget = function(evt) { height = box.height, dx = (x-start_x), dy = (y-start_y); if (curConfig.gridSnapping){ - dx = snapToGrid(dx); - dy = snapToGrid(dy); - height = snapToGrid(height); - width = snapToGrid(width); + dx = svgedit.utilities.snapToGrid(dx); + dy = svgedit.utilities.snapToGrid(dy); + height = svgedit.utilities.snapToGrid(height); + width = svgedit.utilities.snapToGrid(width); } // if rotated, adjust the dx,dy values @@ -2922,10 +2635,10 @@ var getMouseTarget = this.getMouseTarget = function(evt) { translateBack = svgroot.createSVGTransform(); if (curConfig.gridSnapping){ - left = snapToGrid(left); - tx = snapToGrid(tx); - top = snapToGrid(top); - ty = snapToGrid(ty); + left = svgedit.utilities.snapToGrid(left); + tx = svgedit.utilities.snapToGrid(tx); + top = svgedit.utilities.snapToGrid(top); + ty = svgedit.utilities.snapToGrid(ty); } translateOrigin.setTranslate(-(left+tx), -(top+ty)); @@ -2956,7 +2669,7 @@ var getMouseTarget = this.getMouseTarget = function(evt) { case "zoom": real_x *= current_zoom; real_y *= current_zoom; - assignAttributes(rubberBox, { + svgedit.utilities.assignAttributes(rubberBox, { 'x': Math.min(r_start_x*current_zoom, real_x), 'y': Math.min(r_start_y*current_zoom, real_y), 'width': Math.abs(real_x - r_start_x*current_zoom), @@ -2964,7 +2677,7 @@ var getMouseTarget = this.getMouseTarget = function(evt) { }, 100); break; case "text": - assignAttributes(shape,{ + svgedit.utilities.assignAttributes(shape,{ 'x': x, 'y': y }, 1000); @@ -2975,8 +2688,8 @@ var getMouseTarget = this.getMouseTarget = function(evt) { if (!window.opera) svgroot.suspendRedraw(1000); if (curConfig.gridSnapping){ - x = snapToGrid(x); - y = snapToGrid(y); + x = svgedit.utilities.snapToGrid(x); + y = svgedit.utilities.snapToGrid(y); } var x2 = x; @@ -3013,13 +2726,13 @@ var getMouseTarget = this.getMouseTarget = function(evt) { } if (curConfig.gridSnapping){ - w = snapToGrid(w); - h = snapToGrid(h); - new_x = snapToGrid(new_x); - new_y = snapToGrid(new_y); + w = svgedit.utilities.snapToGrid(w); + h = svgedit.utilities.snapToGrid(h); + new_x = svgedit.utilities.snapToGrid(new_x); + new_y = svgedit.utilities.snapToGrid(new_y); } - assignAttributes(shape,{ + svgedit.utilities.assignAttributes(shape,{ 'width': w, 'height': h, 'x': new_x, @@ -3032,7 +2745,7 @@ var getMouseTarget = this.getMouseTarget = function(evt) { var cx = c.cx, cy = c.cy, rad = Math.sqrt( (x-cx)*(x-cx) + (y-cy)*(y-cy) ); if (curConfig.gridSnapping){ - rad = snapToGrid(rad); + rad = svgedit.utilities.snapToGrid(rad); } shape.setAttributeNS(null, "r", rad); break; @@ -3043,10 +2756,10 @@ var getMouseTarget = this.getMouseTarget = function(evt) { handle = null; if (!window.opera) svgroot.suspendRedraw(1000); if (curConfig.gridSnapping){ - x = snapToGrid(x); - cx = snapToGrid(cx); - y = snapToGrid(y); - cy = snapToGrid(cy); + x = svgedit.utilities.snapToGrid(x); + cx = svgedit.utilities.snapToGrid(cx); + y = svgedit.utilities.snapToGrid(y); + cy = svgedit.utilities.snapToGrid(cy); } shape.setAttributeNS(null, "rx", Math.abs(x - cx) ); var ry = Math.abs(evt.shiftKey?(x - cx):(y - cy)); @@ -3091,10 +2804,10 @@ var getMouseTarget = this.getMouseTarget = function(evt) { y *= current_zoom; if (curConfig.gridSnapping){ - x = snapToGrid(x); - y = snapToGrid(y); - start_x = snapToGrid(start_x); - start_y = snapToGrid(start_y); + x = svgedit.utilities.snapToGrid(x); + y = svgedit.utilities.snapToGrid(y); + start_x = svgedit.utilities.snapToGrid(start_x); + start_y = svgedit.utilities.snapToGrid(start_y); } if (evt.shiftKey) { var path = svgedit.path.path; @@ -3113,7 +2826,7 @@ var getMouseTarget = this.getMouseTarget = function(evt) { if (rubberBox && rubberBox.getAttribute('display') !== 'none') { real_x *= current_zoom; real_y *= current_zoom; - assignAttributes(rubberBox, { + svgedit.utilities.assignAttributes(rubberBox, { 'x': Math.min(r_start_x*current_zoom, real_x), 'y': Math.min(r_start_y*current_zoom, real_y), 'width': Math.abs(real_x - r_start_x*current_zoom), @@ -3127,7 +2840,7 @@ var getMouseTarget = this.getMouseTarget = function(evt) { x *= current_zoom; y *= current_zoom; // if (rubberBox && rubberBox.getAttribute('display') != 'none') { -// assignAttributes(rubberBox, { +// svgedit.utilities.assignAttributes(rubberBox, { // 'x': Math.min(start_x,x), // 'y': Math.min(start_y,y), // 'width': Math.abs(x-start_x), @@ -3148,7 +2861,7 @@ var getMouseTarget = this.getMouseTarget = function(evt) { cy = center.y; var angle = ((Math.atan2(cy-y, cx-x) * (180/Math.PI))-90) % 360; if (curConfig.gridSnapping){ - angle = snapToGrid(angle); + angle = svgedit.utilities.snapToGrid(angle); } if (evt.shiftKey) { // restrict rotations to nice angles (WRS) var snap = 45; @@ -3610,7 +3323,7 @@ var textActions = canvas.textActions = function() { cursor = svgedit.utilities.getElem("text_cursor"); if (!cursor) { cursor = document.createElementNS(NS.SVG, "line"); - assignAttributes(cursor, { + svgedit.utilities.assignAttributes(cursor, { 'id': "text_cursor", 'stroke': "#333", 'stroke-width': 1 @@ -3628,7 +3341,7 @@ var textActions = canvas.textActions = function() { var start_pt = ptToScreen(charbb.x, textbb.y); var end_pt = ptToScreen(charbb.x, (textbb.y + textbb.height)); - assignAttributes(cursor, { + svgedit.utilities.assignAttributes(cursor, { x1: start_pt.x, y1: start_pt.y, x2: end_pt.x, @@ -3654,7 +3367,7 @@ var textActions = canvas.textActions = function() { if (!selblock) { selblock = document.createElementNS(NS.SVG, "path"); - assignAttributes(selblock, { + svgedit.utilities.assignAttributes(selblock, { 'id': "text_selectblock", 'fill': "green", 'opacity': 0.5, @@ -3678,7 +3391,7 @@ var textActions = canvas.textActions = function() { + " " + br.x + "," + br.y + " " + bl.x + "," + bl.y + "z"; - assignAttributes(selblock, { + svgedit.utilities.assignAttributes(selblock, { d: dstr, 'display': 'inline' }); @@ -4106,15 +3819,15 @@ var pathActions = canvas.pathActions = function() { newPoint = [x, y]; if (curConfig.gridSnapping){ - x = snapToGrid(x); - y = snapToGrid(y); - mouse_x = snapToGrid(mouse_x); - mouse_y = snapToGrid(mouse_y); + x = svgedit.utilities.snapToGrid(x); + y = svgedit.utilities.snapToGrid(y); + mouse_x = svgedit.utilities.snapToGrid(mouse_x); + mouse_y = svgedit.utilities.snapToGrid(mouse_y); } if (!stretchy) { stretchy = document.createElementNS(NS.SVG, "path"); - assignAttributes(stretchy, { + svgedit.utilities.assignAttributes(stretchy, { 'id': "path_stretch_line", 'stroke': "#22C", 'stroke-width': "0.5", @@ -4211,7 +3924,7 @@ var pathActions = canvas.pathActions = function() { if (subpath) { if (svgedit.path.path.matrix) { - remapElement(newpath, {}, svgedit.path.path.matrix.inverse()); + svgedit.coords.remapElement(newpath, {}, svgedit.path.path.matrix.inverse()); } var new_d = newpath.getAttribute("d"); @@ -4316,7 +4029,7 @@ var pathActions = canvas.pathActions = function() { if (rubberBox == null) { rubberBox = selectorManager.getRubberBandBox(); } - assignAttributes(rubberBox, { + svgedit.utilities.assignAttributes(rubberBox, { 'x': start_x * current_zoom, 'y': start_y * current_zoom, 'width': 0, @@ -4360,7 +4073,7 @@ var pathActions = canvas.pathActions = function() { pointGrip2.setAttribute('display', 'inline'); var ctrlLine = svgedit.path.getCtrlLine(1); - assignAttributes(ctrlLine, { + svgedit.utilities.assignAttributes(ctrlLine, { x1: mouse_x, y1: mouse_y, x2: alt_x * current_zoom, @@ -7221,7 +6934,7 @@ this.getBlur = function(elem) { canvas.setBlurOffsets = function(filter, stdDev) { if (stdDev > 3) { // TODO: Create algorithm here where size is based on expected blur - assignAttributes(filter, { + svgedit.utilities.assignAttributes(filter, { x: '-50%', y: '-50%', width: '200%', @@ -8654,7 +8367,7 @@ this.updateCanvas = function(w, h) { var x = (w/2 - this.contentW*current_zoom/2); var y = (h/2 - this.contentH*current_zoom/2); - assignAttributes(svgcontent, { + svgedit.utilities.assignAttributes(svgcontent, { width: this.contentW*current_zoom, height: this.contentH*current_zoom, 'x': x, @@ -8662,7 +8375,7 @@ this.updateCanvas = function(w, h) { "viewBox" : "0 0 " + this.contentW + " " + this.contentH }); - assignAttributes(bg, { + svgedit.utilities.assignAttributes(bg, { width: svgcontent.getAttribute('width'), height: svgcontent.getAttribute('height'), x: x, @@ -8671,7 +8384,7 @@ this.updateCanvas = function(w, h) { var bg_img = svgedit.utilities.getElem('background_image'); if (bg_img) { - assignAttributes(bg_img, { + svgedit.utilities.assignAttributes(bg_img, { 'width': '100%', 'height': '100%' }); @@ -8696,7 +8409,7 @@ this.setBackground = function(color, url) { if (url) { if (!bg_img) { bg_img = svgdoc.createElementNS(NS.SVG, "image"); - assignAttributes(bg_img, { + svgedit.utilities.assignAttributes(bg_img, { 'id': 'background_image', 'width': '100%', 'height': '100%', diff --git a/editor/svgutils.js b/editor/svgutils.js index 637442dd..322682bd 100644 --- a/editor/svgutils.js +++ b/editor/svgutils.js @@ -11,6 +11,7 @@ // 1) jQuery // 2) browser.js // 3) svgtransformlist.js +// 4) units.js (function() { @@ -649,4 +650,17 @@ svgedit.utilities.cleanupElement = function(element) { svgroot_.unsuspendRedraw(handle); }; +// Function: snapToGrid +// round value to for snapping +// NOTE: This function did not move to svgutils.js since it depends on curConfig. +svgedit.utilities.snapToGrid = function(value) { + var stepSize = editorContext_.getSnappingStep(); + var unit = editorContext_.getBaseUnit(); + if (unit !== "px") { + stepSize *= svgedit.units.getTypeMap()[unit]; + } + value = Math.round(value/stepSize)*stepSize; + return value; +}; + })(); diff --git a/test/coords_test.html b/test/coords_test.html index 1b8bc01f..fd88fd39 100644 --- a/test/coords_test.html +++ b/test/coords_test.html @@ -1,15 +1,17 @@ - - - - - - - - + + + + + + + + - - + test('Test remapElement(translate) for rect', function() { + expect(4); + + setUp(); + + var rect = document.createElementNS(svgns, 'rect'); + var attrs = { + x: '200', + y: '150', + width: '125', + height: '75' + } + + // Create a translate. + var m = svg.createSVGMatrix(); + m.a = 1; m.b = 0; + m.c = 0; m.d = 1; + m.e = 100; m.f = -50; + + svgedit.coords.remapElement(rect, attrs, m); + + equals(rect.getAttribute('x'), '300'); + equals(rect.getAttribute('y'), '100'); + equals(rect.getAttribute('width'), '125'); + equals(rect.getAttribute('height'), '75'); + }); + }); + + +

Unit Tests for svgedit.coords