Sync with latest SVG-Edit

This commit is contained in:
Jacques Distler 2010-02-22 18:53:15 -06:00
parent 3fbff19997
commit a1654f1e99
5 changed files with 248 additions and 113 deletions

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="205.5" xmlns="http://www.w3.org/2000/svg" height="255.87">
<svg width="100" xmlns="http://www.w3.org/2000/svg" height="100" viewBox="0 0 205.5 255.87">
<g display="inline">
<title>Layer 1</title>
<path id="svg_1" d="m10.671,231.42c89.543,56.086,188.11-10.029,193.33-94.559-1.7286-101.53-92.171-150.4-160.16-128.01,35.129,15.844,73.4,62.729,73.871,88.013-3.4428,7.5157-5.8714,16.373,1.5286,18.844-2,12.857-18.486,33.571-21.043,40.643-2.5571,7.0714,2.2143,9.8443,5.4429,10.357,0.2,6.0586-4.4429,8.3572-5.3572,12.156-7.9,3.13-8.4714,9.9872-7.3286,14.844-19.714,18.571-20.571,27.143-80.286,37.714z" stroke="#000" stroke-width="5" fill="#f6c700"/>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" height="100" width="100">
<path stroke-linejoin="round" d="M36.536,32.822c12.699-31.856,13.17-31.734,26.356-1.061,34.15,2.198,39.388,9.622,6.513,23.932,19.821,46.047-0.792,28.383-19.842,13.178-22.358,25.013-29.541,22.639-18.782-12.118-7.282-8.501-45.095-18.741,5.755-23.931z" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-miterlimit="4" stroke-width="5" fill="#FF0"/>
<path id="svg_1" stroke-linejoin="round" d="M36.536,32.822c12.699-31.856,13.17-31.734,26.356-1.061,34.15,2.198,39.388,9.622,6.513,23.932,19.821,46.047-0.792,28.383-19.842,13.178-22.358,25.013-29.541,22.639-18.782-12.118-7.282-8.501-45.095-18.741,5.755-23.931z" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-miterlimit="4" stroke-width="5" fill="#FF0"/>
</svg>

Before

Width:  |  Height:  |  Size: 489 B

After

Width:  |  Height:  |  Size: 500 B

View file

@ -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;

View file

@ -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 (<metadata>, <defs>, 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 (<metadata> 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 <svg> to the <g> 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 <image> or <a> 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) {

View file

@ -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 <use> element");
equals(fu, null, "Removed <use> element that had a foreign href");
equals(nfu, null, "Removed <use> element that had no href");
equal((u && u.nodeName == "use"), true, "Did not import <use> element");
equal(fu, null, "Removed <use> element that had a foreign href");
equal(nfu, null, "Removed <use> 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 <text> element");
equals(null, t.getAttribute("d"), "Imported a <text> with a d attribute");
equal(true, (t && t.nodeName == "text"), "Did not import <text> element");
equal(null, t.getAttribute("d"), "Imported a <text> with a d attribute");
});
// This test makes sure import/export properly handles namespaced attributes
@ -175,7 +175,7 @@
'</svg>');
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("<"), "&lt;", "Escaped < properly");
equals(svgCanvas.getPrivateMethods().toXml(">"), "&gt;", "Escaped > properly");
equals(svgCanvas.getPrivateMethods().toXml("&"), "&amp;", "Escaped & properly");
equal(svgCanvas.getPrivateMethods().toXml("<"), "&lt;", "Escaped < properly");
equal(svgCanvas.getPrivateMethods().toXml(">"), "&gt;", "Escaped > properly");
equal(svgCanvas.getPrivateMethods().toXml("&"), "&amp;", "Escaped & properly");
// TODO: what about &quot; and &apos; ?
});
});
test("Test importing SVG into existing drawing", function() {
expect(3);
var doc = svgCanvas.setSvgString('<svg width="640" height="480" xmlns="http://www.w3.org/2000/svg">'+
'<g><title>Layer 1</title>'+
'<circle cx="200" cy="200" r="50" fill="blue"/>'+
'<ellipse cx="300" cy="100" rx="40" ry="30" fill="green"/>'+
'</g>'+
'</svg>');
svgCanvas.importSvgString('<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">'+
'<circle cx="50" cy="50" r="40" fill="yellow"/>'+
'<rect width="20" height="20" fill="blue"/>'+
'</svg>');
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('<svg width="640" height="480" xmlns="http://www.w3.org/2000/svg">'+
'<g><title>Layer 1</title>'+
'<ellipse id="svg_1" cx="200" cy="200" rx="50" ry="20" fill="blue"/>'+
'<ellipse id="svg_2" cx="300" cy="100" rx="40" ry="30" fill="green"/>'+
'<ellipse id="svg_3" cx="300" cy="100" rx="40" ry="30" fill="green"/>'+
'</g>'+
'</svg>');
svgCanvas.importSvgString('<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink">'+
'<defs>'+
'<linearGradient id="svg_2">'+
'<stop stop-color="red" offset="0"/>'+
'<stop stop-color="green" offset="1"/>'+
'</linearGradient>'+
'<rect id="svg_3" width="20" height="20" fill="blue" stroke="url(#svg_2)"/>'+
'</defs>'+
'<circle id="svg_1" cx="50" cy="50" r="40" fill="url(#svg_2)"/>'+
'<use id="svg_4" width="30" height="30" xl:href="#svg_3"/>'+
'</svg>');
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");
});
});
</script>
</head>