From a1654f1e9945538a7862afe377dc9cbb306080b1 Mon Sep 17 00:00:00 2001 From: Jacques Distler Date: Mon, 22 Feb 2010 18:53:15 -0600 Subject: [PATCH] Sync with latest SVG-Edit --- public/svg-edit/clipart/moon.svg | 2 +- public/svg-edit/clipart/star.svg | 2 +- public/svg-edit/editor/svg-editor.js | 3 +- public/svg-edit/editor/svgcanvas.js | 218 ++++++++++++++++++--------- public/svg-edit/test/test1.html | 136 ++++++++++++----- 5 files changed, 248 insertions(+), 113 deletions(-) diff --git a/public/svg-edit/clipart/moon.svg b/public/svg-edit/clipart/moon.svg index d5caac04..179fa669 100644 --- a/public/svg-edit/clipart/moon.svg +++ b/public/svg-edit/clipart/moon.svg @@ -1,5 +1,5 @@ - + Layer 1 diff --git a/public/svg-edit/clipart/star.svg b/public/svg-edit/clipart/star.svg index c5aa58a1..62bb89a4 100644 --- a/public/svg-edit/clipart/star.svg +++ b/public/svg-edit/clipart/star.svg @@ -1,4 +1,4 @@ - + diff --git a/public/svg-edit/editor/svg-editor.js b/public/svg-edit/editor/svg-editor.js index 41f1cbd9..ef242952 100644 --- a/public/svg-edit/editor/svg-editor.js +++ b/public/svg-edit/editor/svg-editor.js @@ -1460,7 +1460,6 @@ function svg_edit_setup() { svgCanvas.open(); }; var clickImport = function(){ - svgCanvas.import(); }; var clickUndo = function(){ @@ -2780,7 +2779,7 @@ function svg_edit_setup() { updateCanvas(true); }); -// var revnums = "svg-editor.js ($Rev: 1421 $) "; +// var revnums = "svg-editor.js ($Rev: 1423 $) "; // revnums += svgCanvas.getVersion(); // $('#copyright')[0].setAttribute("title", revnums); return svgCanvas; diff --git a/public/svg-edit/editor/svgcanvas.js b/public/svg-edit/editor/svgcanvas.js index 4666c83a..770db140 100644 --- a/public/svg-edit/editor/svgcanvas.js +++ b/public/svg-edit/editor/svgcanvas.js @@ -1393,7 +1393,7 @@ function BatchCommand(text) { } // if the element has attributes pointing to a non-local reference, // need to remove the attribute - $.each(["clip-path", "fill", "marker-end", "marker-mid", "marker-start", "mask", "stroke"],function(i,attr) { + $.each(["clip-path", "fill", "filter", "marker-end", "marker-mid", "marker-start", "mask", "stroke"],function(i,attr) { var val = node.getAttribute(attr); if (val) { val = getUrlFromAttr(val); @@ -2164,6 +2164,8 @@ function BatchCommand(text) { start_transform = child.getAttribute("transform"); var childTlist = canvas.getTransformList(child); + // some children might not have a transform (, , etc) + if (childTlist) { var newxlate = svgroot.createSVGTransform(); newxlate.setTranslate(tx,ty); childTlist.insertItemBefore(newxlate, 0); @@ -2171,6 +2173,7 @@ function BatchCommand(text) { start_transform = old_start_transform; } } + } start_transform = old_start_transform; } } @@ -2263,6 +2266,7 @@ function BatchCommand(text) { } // else, it's a non-group else { + // FIXME: box might be null for some elements ( etc), need to handle this var box = canvas.getBBox(selected), 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, @@ -2706,7 +2710,6 @@ function BatchCommand(text) { } tobj.text = "rotate(" + xform.angle + " " + tobj.cx*z + "," + tobj.cy*z + ")"; break; - // TODO: matrix, skewX, skewY } return tobj; }; @@ -4484,7 +4487,7 @@ function BatchCommand(text) { return; } - // TODO: Make sure current_path isn't null at this point + // Make sure current_path isn't null at this point if(!current_path) return; current_path_oldd = getD(); @@ -5023,7 +5026,7 @@ function BatchCommand(text) { current_path_pts.splice(pt*2 + 2, 0, abs_x, abs_y); // TODO: This should select the new path points but breaks - // when doing several times seleting many nodes + // when doing several times selecting many nodes nums.push(pt + i); nums.push(pt + i + 1); } @@ -5525,9 +5528,6 @@ function BatchCommand(text) { this.open = function() { // Nothing by default, handled by optional widget/extension }; - this.import = function() { - // Nothing by default, handled by optional widget/extension - }; // Function: save // Serializes the current drawing into SVG XML text and returns it to the 'saved' handler. @@ -5548,6 +5548,7 @@ function BatchCommand(text) { call("saved", str); }; + // Walks the tree and executes the callback on each element in a top-down fashion var walkTree = function(elem, cbFn){ if (elem && elem.nodeType == 1) { cbFn(elem); @@ -5557,6 +5558,16 @@ function BatchCommand(text) { } } }; + // Walks the tree and executes the callback on each element in a depth-first fashion + var walkTreePost = function(elem, cbFn) { + if (elem && elem.nodeType == 1) { + var i = elem.childNodes.length; + while (i--) { + walkTree(elem.childNodes.item(i), cbFn); + } + cbFn(elem); + } + }; // Function: getSvgString // Returns the current drawing as raw SVG XML text. @@ -5617,17 +5628,7 @@ function BatchCommand(text) { // recalculate dimensions on the top-level children so that unnecessary transforms // are removed - var deepdive = function(node) { - if (node.nodeType == 1) { - var children = node.childNodes; - var i = children.length; - while (i--) { deepdive(children.item(i)); } - try { - recalculateDimensions(node); - } catch(e) { console.log(e); } - } - } - deepdive(svgcontent); + walkTreePost(svgcontent, function(n){try{recalculateDimensions(n)}catch(e){console.log(e)}}); var content = $(svgcontent); @@ -5693,8 +5694,8 @@ function BatchCommand(text) { // Returns: // This function returns false if the import was unsuccessful, true otherwise. + // TODO: import should happen in top-left of current zoomed viewport // TODO: create a new layer for the imported SVG - // TODO: properly size the new group this.importSvgString = function(xmlString) { try { // convert string into XML document @@ -5708,9 +5709,120 @@ function BatchCommand(text) { var importedNode = svgdoc.importNode(newDoc.documentElement, true); if (current_layer) { + // TODO: properly handle if width/height are not specified or if in percentages + // TODO: properly handle if width/height are in units (px, etc) + var innerw = importedNode.getAttribute("width"), + innerh = importedNode.getAttribute("height"), + innervb = importedNode.getAttribute("viewBox"), + // if no explicit viewbox, create one out of the width and height + vb = innervb ? innervb.split(" ") : [0,0,innerw,innerh]; + for (var j = 0; j < 4; ++j) + vb[j] = Number(vb[j]); + + // TODO: properly handle preserveAspectRatio + var canvasw = Number(svgcontent.getAttribute("width")), + canvash = Number(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] + ")"; + } + if (vb[0] != 0 || vb[1] != 0) + ts = "translate(" + (-vb[0]) + "," + (-vb[1]) + ") " + ts; + // add all children of the imported to the we create var g = svgdoc.createElementNS(svgns, "g"); - while (importedNode.hasChildNodes()) { g.appendChild(importedNode.firstChild); } + while (importedNode.hasChildNodes()) + g.appendChild(importedNode.firstChild); + if (ts) + g.setAttribute("transform", ts); + + // now ensure each element has a unique ID + var ids = {}; + walkTree(g, function(n) { + // if it's an element node + if (n.nodeType == 1) { + // and the element has an ID + if (n.id) { + // and we haven't tracked this ID yet + if (!(n.id in ids)) { + // add this id to our map + ids[n.id] = {elem:null, attrs:[], hrefs:[]}; + } + ids[n.id]["elem"] = n; + } + + // now search for all attributes on this element that might refer + // to other elements + $.each(["clip-path", "fill", "filter", "marker-end", "marker-mid", "marker-start", "mask", "stroke"],function(i,attr) { + var attrnode = n.getAttributeNode(attr); + if (attrnode) { + // the incoming file has been sanitized, so we should be able to safely just strip off the leading # + var url = getUrlFromAttr(attrnode.value), + refid = url ? url.substr(1) : null; + if (refid) { + if (!(refid in ids)) { + // add this id to our map + ids[refid] = {elem:null, attrs:[], hrefs:[]}; + } + ids[refid]["attrs"].push(attrnode); + } + } + }); + + // check xlink:href now + var href = n.getAttributeNS(xlinkns,"href"); + // TODO: what if an or element refers to an element internally? + if(href && + $.inArray(n.nodeName, ["filter", "linearGradient", "pattern", + "radialGradient", "textPath", "use"]) != -1) + { + var refid = href.substr(1); + if (!(refid in ids)) { + // add this id to our map + ids[refid] = {elem:null, attrs:[], hrefs:[]}; + } + 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) { + var elem = ids[oldid]["elem"]; + if (elem) { + var newid = getNextId(); + // manually increment obj_num because our cloned elements are not in the DOM yet + obj_num++; + + // assign element its new id + elem.id = newid; + + // remap all url() attributes + var attrs = ids[oldid]["attrs"]; + var j = attrs.length; + while (j--) { + var attr = attrs[j]; + attr.ownerElement.setAttribute(attr.name, "url(#" + newid + ")"); + } + + // remap all href attributes + var hreffers = ids[oldid]["hrefs"]; + var k = hreffers.length; + while (k--) { + var hreffer = hreffers[k]; + hreffer.setAttributeNS(xlinkns, "xlink:href", "#"+newid); + } + } + } + + // now give the g itself a new id + g.id = getNextId(); + // manually increment obj_num because our cloned elements are not in the DOM yet + obj_num++; + current_layer.appendChild(g); } @@ -5740,54 +5852,13 @@ function BatchCommand(text) { // recalculate dimensions on the top-level children so that unnecessary transforms // are removed - var deepdive = function(node) { - if (node.nodeType == 1) { - var children = node.childNodes; - var i = children.length; - while (i--) { deepdive(children.item(i)); } - try { - recalculateDimensions(node); - } catch(e) { console.log(e); } - } - } - deepdive(importedNode); - -// var content = $(svgcontent); + walkTreePost(importedNode, function(n){try{recalculateDimensions(n)}catch(e){console.log(e)}}); -// var attrs = { -// id: 'svgcontent', -// overflow: 'visible' -// }; - // determine proper size -// if (content.attr("viewBox")) { -// var vb = content.attr("viewBox").split(' '); -// attrs.width = vb[2]; -// attrs.height = vb[3]; -// } -// // handle content that doesn't have a viewBox -// else { -// $.each(['width', 'height'], function(i, dim) { -// // Set to 100 if not given -// var val = content.attr(dim) || 100; -// -// if((val+'').substr(-1) === "%") { -// // Use user units if percentage given -// attrs[dim] = parseInt(val); -// } else { -// attrs[dim] = convertToNum(dim, val); -// } -// }); -// } - -// content.attr(attrs); batchCmd.addSubCommand(new InsertElementCommand(svgcontent)); - // update root to the correct size -// var changes = content.attr(["width", "height"]); -// batchCmd.addSubCommand(new ChangeElementCommand(svgroot, changes)); - // reset zoom - current_zoom = 1; + // reset zoom - TODO: why? +// current_zoom = 1; // identify layers // identifyLayers(); @@ -6734,12 +6805,12 @@ function BatchCommand(text) { ret = getPathBBox(selected); } else if(elem.nodeName == 'use' && !isWebkit) { ret = selected.getBBox(); - ret.x += parseFloat(selected.getAttribute('x')); - ret.y += parseFloat(selected.getAttribute('y')); + ret.x += parseFloat(selected.getAttribute('x')||0); + ret.y += parseFloat(selected.getAttribute('y')||0); } else if(elem.nodeName == 'foreignObject') { ret = selected.getBBox(); - ret.x += parseFloat(selected.getAttribute('x')); - ret.y += parseFloat(selected.getAttribute('y')); + ret.x += parseFloat(selected.getAttribute('x')||0); + ret.y += parseFloat(selected.getAttribute('y')||0); } else { try { ret = selected.getBBox(); } catch(e) { @@ -7474,9 +7545,6 @@ function BatchCommand(text) { if(!elems) elems = canvas.getVisibleElements(); if(!elems.length) return false; // Make sure the expected BBox is returned if the element is a group - // FIXME: doesn't this mean that every time we call getStrokedBBox() that we are - // re-creating the getCheckedBBox() function? shouldn't we make this a function - // at the 'canvas' level var getCheckedBBox = function(elem) { try { @@ -7588,11 +7656,12 @@ function BatchCommand(text) { var bboxes = []; $.each(elems, function(i, elem) { var cur_bb = getCheckedBBox(elem); - if(!cur_bb) return; + if(cur_bb) { var offset = getOffset(elem); min_x = Math.min(min_x, cur_bb.x - offset); min_y = Math.min(min_y, cur_bb.y - offset); bboxes.push(cur_bb); + } }); full_bb.x = min_x; @@ -7600,7 +7669,8 @@ function BatchCommand(text) { $.each(elems, function(i, elem) { var cur_bb = bboxes[i]; - if (cur_bb) { + // ensure that elem is really an element node + if (cur_bb && elem.nodeType == 1) { var offset = getOffset(elem); max_x = Math.max(max_x, cur_bb.x + cur_bb.width + offset); max_y = Math.max(max_y, cur_bb.y + cur_bb.height + offset); @@ -7881,7 +7951,7 @@ function BatchCommand(text) { // Function: getVersion // Returns a string which describes the revision number of SvgCanvas. this.getVersion = function() { - return "svgcanvas.js ($Rev: 1422 $)"; + return "svgcanvas.js ($Rev: 1427 $)"; }; this.setUiStrings = function(strs) { diff --git a/public/svg-edit/test/test1.html b/public/svg-edit/test/test1.html index b29589d2..657f4a42 100644 --- a/public/svg-edit/test/test1.html +++ b/public/svg-edit/test/test1.html @@ -33,7 +33,7 @@ test("Test existence of SvgCanvas object", function() { expect(1); - equals(typeof {}, typeof svgCanvas); + equal(typeof {}, typeof svgCanvas); }); module("Path Module"); @@ -58,21 +58,21 @@ seglist = p1.pathSegList, curseg = null; - equals(p1.nodeName, "path", "Expected 'path', got"); + equal(p1.nodeName, "path", "Expected 'path', got"); - equals(seglist.numberOfItems, 3, "Number of segments before conversion"); + equal(seglist.numberOfItems, 3, "Number of segments before conversion"); // verify segments before conversion curseg = seglist.getItem(0); - equals(curseg.pathSegTypeAsLetter, "M", "Before conversion, segment #1 type"); + equal(curseg.pathSegTypeAsLetter, "M", "Before conversion, segment #1 type"); curseg = seglist.getItem(1); - equals(curseg.pathSegTypeAsLetter, "L", "Before conversion, segment #2 type"); + equal(curseg.pathSegTypeAsLetter, "L", "Before conversion, segment #2 type"); curseg = seglist.getItem(2); - equals(curseg.pathSegType, 1, "Before conversion, segment #3 type"); + equal(curseg.pathSegType, 1, "Before conversion, segment #3 type"); // convert and verify segments var d = convert(p1, true); - equals(d, "m100,100l100,0z", "Converted path to relative string"); + equal(d, "m100,100l100,0z", "Converted path to relative string"); // TODO: see why this isn't working in SVG-edit d = convert(p2, true); @@ -91,7 +91,7 @@ tr_2 = svgroot.createSVGMatrix().translate(-90,0), tr_3 = svgroot.createSVGMatrix().translate(-10,-50), I = svgCanvas.matrixMultiply(tr_1,tr_2,tr_3); - equals(true, isIdentity(I), + equal(isIdentity(I), true, "Expected identity matrix when translating there and back, got " + matrixString(I)); // rotate there and back @@ -101,7 +101,7 @@ rot_back = svgroot.createSVGMatrix().rotate(-90); // TODO: set this to -50 rot_back_more = svgroot.createSVGMatrix().rotate(0); // TODO: set this to -40 I = svgCanvas.matrixMultiply(rot_there, rot_back, rot_back_more); - equals(true, isIdentity(I), + equal(isIdentity(I), true, "Expected identity matrix when rotating there and back, got " + matrixString(I)); // scale up and down @@ -109,15 +109,15 @@ scale_down = svgroot.createSVGMatrix().scaleNonUniform(0.25,1); scale_down_more = svgroot.createSVGMatrix().scaleNonUniform(1,0.25); I = svgCanvas.matrixMultiply(scale_up, scale_down, scale_down_more); - equals(true, isIdentity(I), + equal(isIdentity(I), true, "Expected identity matrix when scaling up and down, got " + matrixString(I)); // test multiplication with its inverse I = svgCanvas.matrixMultiply(rot_there, rot_there.inverse()); - equals(true, isIdentity(I), + equal(isIdentity(I), true, "Expected identity matrix when multiplying a matrix by its inverse, got " + matrixString(I)); I = svgCanvas.matrixMultiply(rot_there.inverse(), rot_there); - equals(true, isIdentity(I), + equal(isIdentity(I), true, "Expected identity matrix when multiplying a matrix by its inverse, got " + matrixString(I)); }); @@ -137,18 +137,18 @@ fu = document.getElementById("foreign-use"), nfu = document.getElementById("no-use"); - equals((u && u.nodeName == "use"), true, "Did not import element"); - equals(fu, null, "Removed element that had a foreign href"); - equals(nfu, null, "Removed element that had no href"); + equal((u && u.nodeName == "use"), true, "Did not import element"); + equal(fu, null, "Removed element that had a foreign href"); + equal(nfu, null, "Removed element that had no href"); }); test("Test getUrlFromAttr", function() { expect(4); - equals(svgCanvas.getUrlFromAttr("url(#foo)"), "#foo"); - equals(svgCanvas.getUrlFromAttr("url(somefile.svg#foo)"), "somefile.svg#foo"); - equals(svgCanvas.getUrlFromAttr("url('#foo')"), "#foo"); - equals(svgCanvas.getUrlFromAttr('url("#foo")'), "#foo"); + equal(svgCanvas.getUrlFromAttr("url(#foo)"), "#foo"); + equal(svgCanvas.getUrlFromAttr("url(somefile.svg#foo)"), "somefile.svg#foo"); + equal(svgCanvas.getUrlFromAttr("url('#foo')"), "#foo"); + equal(svgCanvas.getUrlFromAttr('url("#foo")'), "#foo"); }); // This test shows that an element with an invalid attribute is still parsed in properly @@ -162,8 +162,8 @@ var t = document.getElementById("the-text"); - equals(true, (t && t.nodeName == "text"), "Did not import element"); - equals(null, t.getAttribute("d"), "Imported a with a d attribute"); + equal(true, (t && t.nodeName == "text"), "Did not import element"); + equal(null, t.getAttribute("d"), "Imported a with a d attribute"); }); // This test makes sure import/export properly handles namespaced attributes @@ -175,7 +175,7 @@ ''); var attrVal = document.getElementById('se_test_elem').getAttributeNS("http://svg-edit.googlecode.com", "foo"); - equals(attrVal === "bar", true, "Preserved namespaced attribute on import"); + equal(attrVal === "bar", true, "Preserved namespaced attribute on import"); var output = svgCanvas.getSvgString(); var has_xlink = output.indexOf('xmlns:xlink="http://www.w3.org/1999/xlink"') !== -1; @@ -183,10 +183,10 @@ var has_foo = output.indexOf('xmlns:foo=') !== -1; var has_attr = output.indexOf('se:foo="bar"') !== -1; - equals(has_attr, true, "Preserved namespaced attribute on export"); - equals(has_xlink, true, "Included xlink: xmlns"); - equals(has_se, true, "Included se: xmlns"); - equals(has_foo, false, "Did not include foo: xmlns"); + equal(has_attr, true, "Preserved namespaced attribute on export"); + equal(has_xlink, true, "Included xlink: xmlns"); + equal(has_se, true, "Included se: xmlns"); + equal(has_foo, false, "Did not include foo: xmlns"); }); test("Test import math elements inside a foreignObject", function() { @@ -205,20 +205,86 @@ // see Bug https://bugs.webkit.org/show_bug.cgi?id=35042 var math = fo.firstChild; - equals(!!math, true, "Math element exists"); - equals(math.nodeName, 'math', "Math element has the proper nodeName"); - equals(math.getAttribute('id'), 'm', "Math element has an id"); - equals(math.namespaceURI, "http://www.w3.org/1998/Math/MathML", "Preserved MathML namespace"); + equal(!!math, true, "Math element exists"); + equal(math.nodeName, 'math', "Math element has the proper nodeName"); + equal(math.getAttribute('id'), 'm', "Math element has an id"); + equal(math.namespaceURI, "http://www.w3.org/1998/Math/MathML", "Preserved MathML namespace"); }); - test("Test XML entities in attribute", function() { + test("Test escaping XML entities", function() { expect(3); - equals(svgCanvas.getPrivateMethods().toXml("<"), "<", "Escaped < properly"); - equals(svgCanvas.getPrivateMethods().toXml(">"), ">", "Escaped > properly"); - equals(svgCanvas.getPrivateMethods().toXml("&"), "&", "Escaped & properly"); + equal(svgCanvas.getPrivateMethods().toXml("<"), "<", "Escaped < properly"); + equal(svgCanvas.getPrivateMethods().toXml(">"), ">", "Escaped > properly"); + equal(svgCanvas.getPrivateMethods().toXml("&"), "&", "Escaped & properly"); // TODO: what about " and ' ? - }); + }); + + test("Test importing SVG into existing drawing", function() { + expect(3); + + var doc = svgCanvas.setSvgString(''+ + 'Layer 1'+ + ''+ + ''+ + ''+ + ''); + + svgCanvas.importSvgString(''+ + ''+ + ''+ + ''); + + var svgcontent = document.getElementById("svgcontent"), + circles = svgcontent.getElementsByTagNameNS(svgns, "circle"), + rects = svgcontent.getElementsByTagNameNS(svgns, "rect"), + ellipses = svgcontent.getElementsByTagNameNS(svgns, "ellipse"); + equal(circles.length, 2, "Found two circles upon importing"); + equal(rects.length, 1, "Found one rectangle upon importing"); + equal(ellipses.length, 1, "Found one ellipse upon importing"); + }); + + test("Test importing SVG remaps IDs", function() { + expect(6); + + var doc = svgCanvas.setSvgString(''+ + 'Layer 1'+ + ''+ + ''+ + ''+ + ''+ + ''); + + svgCanvas.importSvgString(''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''); + + var svgcontent = document.getElementById("svgcontent"), + circles = svgcontent.getElementsByTagNameNS(svgns, "circle"), + rects = svgcontent.getElementsByTagNameNS(svgns, "rect"), + ellipses = svgcontent.getElementsByTagNameNS(svgns, "ellipse"), + defs = svgcontent.getElementsByTagNameNS(svgns, "defs"), + grads = svgcontent.getElementsByTagNameNS(svgns, "linearGradient"), + uses = svgcontent.getElementsByTagNameNS(svgns, "use"); + notEqual(circles.item(0).id, "svg_1", "Circle not re-identified"); + notEqual(rects.item(0).id, "svg_3", "Rectangle not re-identified"); +// // TODO: determine why this test fails in WebKit browsers +// equal(grads.length, 1, "Linear gradient imported"); + var grad = defs.item(0).firstChild; + notEqual(grad.id, "svg_2", "Linear gradient not re-identified"); + notEqual(circles.item(0).getAttribute("fill"), "url(#svg_2)", "Circle fill value not remapped"); + notEqual(rects.item(0).getAttribute("stroke"), "url(#svg_2)", "Rectangle stroke value not remapped"); + notEqual(uses.item(0).getAttributeNS(xlinkns, "href"), "#svg_3"); + }); + });