commit
3ef423e30a
|
@ -33,7 +33,8 @@ var gripRadius = svgedit.browser.isTouch() ? 10 : 4;
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// id - integer to internally indentify the selector
|
// id - integer to internally indentify the selector
|
||||||
// elem - DOM element associated with this selector
|
// elem - DOM element associated with this selector
|
||||||
svgedit.select.Selector = function(id, elem) {
|
// bbox - Optional bbox to use for initialization (prevents duplicate getBBox call).
|
||||||
|
svgedit.select.Selector = function(id, elem, bbox) {
|
||||||
// this is the selector's unique number
|
// this is the selector's unique number
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
|
||||||
|
@ -77,7 +78,7 @@ svgedit.select.Selector = function(id, elem) {
|
||||||
'w' : null
|
'w' : null
|
||||||
};
|
};
|
||||||
|
|
||||||
this.reset(this.selectedElement);
|
this.reset(this.selectedElement, bbox);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -86,10 +87,11 @@ svgedit.select.Selector = function(id, elem) {
|
||||||
//
|
//
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// e - DOM element associated with this selector
|
// e - DOM element associated with this selector
|
||||||
svgedit.select.Selector.prototype.reset = function(e) {
|
// bbox - Optional bbox to use for reset (prevents duplicate getBBox call).
|
||||||
|
svgedit.select.Selector.prototype.reset = function(e, bbox) {
|
||||||
this.locked = true;
|
this.locked = true;
|
||||||
this.selectedElement = e;
|
this.selectedElement = e;
|
||||||
this.resize();
|
this.resize(bbox);
|
||||||
this.selectorGroup.setAttribute('display', 'inline');
|
this.selectorGroup.setAttribute('display', 'inline');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -136,7 +138,8 @@ svgedit.select.Selector.prototype.showGrips = function(show) {
|
||||||
|
|
||||||
// Function: svgedit.select.Selector.resize
|
// Function: svgedit.select.Selector.resize
|
||||||
// Updates the selector to match the element's size
|
// Updates the selector to match the element's size
|
||||||
svgedit.select.Selector.prototype.resize = function() {
|
// bbox - Optional bbox to use for resize (prevents duplicate getBBox call).
|
||||||
|
svgedit.select.Selector.prototype.resize = function(bbox) {
|
||||||
var selectedBox = this.selectorRect,
|
var selectedBox = this.selectorRect,
|
||||||
mgr = selectorManager_,
|
mgr = selectorManager_,
|
||||||
selectedGrips = mgr.selectorGrips,
|
selectedGrips = mgr.selectorGrips,
|
||||||
|
@ -162,7 +165,11 @@ svgedit.select.Selector.prototype.resize = function() {
|
||||||
m.e *= current_zoom;
|
m.e *= current_zoom;
|
||||||
m.f *= current_zoom;
|
m.f *= current_zoom;
|
||||||
|
|
||||||
var bbox = svgedit.utilities.getBBox(selected);
|
if (!bbox) {
|
||||||
|
bbox = svgedit.utilities.getBBox(selected);
|
||||||
|
}
|
||||||
|
// TODO: svgedit.utilities.getBBox (previous line) already knows to call getStrokedBBox when tagName === 'g'. Remove this?
|
||||||
|
// TODO: svgedit.utilities.getBBox doesn't exclude 'gsvg' and calls getStrokedBBox for any 'g'. Should getBBox be updated?
|
||||||
if (tagName === 'g' && !$.data(selected, 'gsvg')) {
|
if (tagName === 'g' && !$.data(selected, 'gsvg')) {
|
||||||
// The bbox for a group does not include stroke vals, so we
|
// The bbox for a group does not include stroke vals, so we
|
||||||
// get the bbox based on its children.
|
// get the bbox based on its children.
|
||||||
|
@ -413,7 +420,8 @@ svgedit.select.SelectorManager.prototype.initGroup = function() {
|
||||||
//
|
//
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// elem - DOM element to get the selector for
|
// elem - DOM element to get the selector for
|
||||||
svgedit.select.SelectorManager.prototype.requestSelector = function(elem) {
|
// bbox - Optional bbox to use for reset (prevents duplicate getBBox call).
|
||||||
|
svgedit.select.SelectorManager.prototype.requestSelector = function(elem, bbox) {
|
||||||
if (elem == null) {return null;}
|
if (elem == null) {return null;}
|
||||||
var i,
|
var i,
|
||||||
N = this.selectors.length;
|
N = this.selectors.length;
|
||||||
|
@ -425,13 +433,13 @@ svgedit.select.SelectorManager.prototype.requestSelector = function(elem) {
|
||||||
for (i = 0; i < N; ++i) {
|
for (i = 0; i < N; ++i) {
|
||||||
if (this.selectors[i] && !this.selectors[i].locked) {
|
if (this.selectors[i] && !this.selectors[i].locked) {
|
||||||
this.selectors[i].locked = true;
|
this.selectors[i].locked = true;
|
||||||
this.selectors[i].reset(elem);
|
this.selectors[i].reset(elem, bbox);
|
||||||
this.selectorMap[elem.id] = this.selectors[i];
|
this.selectorMap[elem.id] = this.selectors[i];
|
||||||
return this.selectors[i];
|
return this.selectors[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if we reached here, no available selectors were found, we create one
|
// if we reached here, no available selectors were found, we create one
|
||||||
this.selectors[N] = new svgedit.select.Selector(N, elem);
|
this.selectors[N] = new svgedit.select.Selector(N, elem, bbox);
|
||||||
this.selectorParentGroup.appendChild(this.selectors[N].selectorGroup);
|
this.selectorParentGroup.appendChild(this.selectors[N].selectorGroup);
|
||||||
this.selectorMap[elem.id] = this.selectors[N];
|
this.selectorMap[elem.id] = this.selectors[N];
|
||||||
return this.selectors[N];
|
return this.selectors[N];
|
||||||
|
|
|
@ -601,140 +601,9 @@ var getIntersectionList = this.getIntersectionList = function(rect) {
|
||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
// A single bounding box object
|
// A single bounding box object
|
||||||
getStrokedBBox = this.getStrokedBBox = function(elems) {
|
var getStrokedBBox = this.getStrokedBBox = function(elems) {
|
||||||
if (!elems) {elems = getVisibleElements();}
|
if (!elems) {elems = getVisibleElements();}
|
||||||
if (!elems.length) {return false;}
|
return svgedit.utilities.getStrokedBBox(elems, addSvgElementFromJson, pathActions)
|
||||||
// Make sure the expected BBox is returned if the element is a group
|
|
||||||
var getCheckedBBox = function(elem) {
|
|
||||||
// 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);
|
|
||||||
if (!bb) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
} else if (elem.tagName == 'rect') {
|
|
||||||
// Look for radius
|
|
||||||
var rx = elem.getAttribute('rx');
|
|
||||||
var ry = elem.getAttribute('ry');
|
|
||||||
if (rx || ry) {
|
|
||||||
bb = good_bb = canvas.convertToPath(elem, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!good_bb) {
|
|
||||||
// Must use clone else FF freaks out
|
|
||||||
var clone = elem.cloneNode(true);
|
|
||||||
var g = document.createElementNS(NS.SVG, 'g');
|
|
||||||
var parent = elem.parentNode;
|
|
||||||
parent.appendChild(g);
|
|
||||||
g.appendChild(clone);
|
|
||||||
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,
|
|
||||||
// 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],
|
|
||||||
// [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 j = 4;
|
|
||||||
// while (j--) {
|
|
||||||
// var x = pts[j][0],
|
|
||||||
// y = pts[j][1],
|
|
||||||
// r = Math.sqrt( x*x + y*y );
|
|
||||||
// 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
var full_bb;
|
|
||||||
$.each(elems, function() {
|
|
||||||
if (full_bb) {return;}
|
|
||||||
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');
|
|
||||||
var offset = 0;
|
|
||||||
if (elem.getAttribute('stroke') != 'none' && !isNaN(sw)) {
|
|
||||||
offset += sw/2;
|
|
||||||
}
|
|
||||||
return offset;
|
|
||||||
};
|
|
||||||
var bboxes = [];
|
|
||||||
$.each(elems, function(i, elem) {
|
|
||||||
var cur_bb = getCheckedBBox(elem);
|
|
||||||
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;
|
|
||||||
full_bb.y = min_y;
|
|
||||||
|
|
||||||
$.each(elems, function(i, elem) {
|
|
||||||
var cur_bb = bboxes[i];
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
full_bb.width = max_x - min_x;
|
|
||||||
full_bb.height = max_y - min_y;
|
|
||||||
return full_bb;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function: getVisibleElements
|
// Function: getVisibleElements
|
||||||
|
@ -1071,7 +940,9 @@ var addToSelection = this.addToSelection = function(elemsToAdd, showGrips) {
|
||||||
var i = elemsToAdd.length;
|
var i = elemsToAdd.length;
|
||||||
while (i--) {
|
while (i--) {
|
||||||
var elem = elemsToAdd[i];
|
var elem = elemsToAdd[i];
|
||||||
if (!elem || !svgedit.utilities.getBBox(elem)) {continue;}
|
if (!elem) {continue;}
|
||||||
|
var bbox = svgedit.utilities.getBBox(elem);
|
||||||
|
if (!bbox) {continue;}
|
||||||
|
|
||||||
if (elem.tagName === 'a' && elem.childNodes.length === 1) {
|
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
|
||||||
|
@ -1086,7 +957,7 @@ var addToSelection = this.addToSelection = function(elemsToAdd, showGrips) {
|
||||||
// only the first selectedBBoxes element is ever used in the codebase these days
|
// only the first selectedBBoxes element is ever used in the codebase these days
|
||||||
// if (j == 0) selectedBBoxes[0] = svgedit.utilities.getBBox(elem);
|
// if (j == 0) selectedBBoxes[0] = svgedit.utilities.getBBox(elem);
|
||||||
j++;
|
j++;
|
||||||
var sel = selectorManager.requestSelector(elem);
|
var sel = selectorManager.requestSelector(elem, bbox);
|
||||||
|
|
||||||
if (selectedElements.length > 1) {
|
if (selectedElements.length > 1) {
|
||||||
sel.showGrips(false);
|
sel.showGrips(false);
|
||||||
|
@ -6576,179 +6447,29 @@ this.setSegType = function(new_type) {
|
||||||
this.convertToPath = function(elem, getBBox) {
|
this.convertToPath = function(elem, getBBox) {
|
||||||
if (elem == null) {
|
if (elem == null) {
|
||||||
var elems = selectedElements;
|
var elems = selectedElements;
|
||||||
$.each(selectedElements, function(i, elem) {
|
$.each(elems, function(i, elem) {
|
||||||
if (elem) {canvas.convertToPath(elem);}
|
if (elem) {canvas.convertToPath(elem);}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (getBBox) {
|
||||||
if (!getBBox) {
|
return svgedit.utilities.getBBoxOfElementAsPath(elem, addSvgElementFromJson, pathActions)
|
||||||
var batchCmd = new svgedit.history.BatchCommand('Convert element to Path');
|
|
||||||
}
|
|
||||||
|
|
||||||
var attrs = getBBox?{}:{
|
|
||||||
'fill': cur_shape.fill,
|
|
||||||
'fill-opacity': cur_shape.fill_opacity,
|
|
||||||
'stroke': cur_shape.stroke,
|
|
||||||
'stroke-width': cur_shape.stroke_width,
|
|
||||||
'stroke-dasharray': cur_shape.stroke_dasharray,
|
|
||||||
'stroke-linejoin': cur_shape.stroke_linejoin,
|
|
||||||
'stroke-linecap': cur_shape.stroke_linecap,
|
|
||||||
'stroke-opacity': cur_shape.stroke_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?
|
|
||||||
$.each(['marker-start', 'marker-end', 'marker-mid', 'filter', 'clip-path'], function() {
|
|
||||||
if (elem.getAttribute(this)) {
|
|
||||||
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) {
|
|
||||||
parent.insertBefore(path, elem);
|
|
||||||
} else {
|
} else {
|
||||||
parent.appendChild(path);
|
// TODO: Why is this applying attributes from cur_shape, then inside utilities.convertToPath it's pulling addition attributes from elem?
|
||||||
}
|
// TODO: If convertToPath is called with one elem, cur_shape and elem are probably the same; but calling with multiple is a bug or cool feature.
|
||||||
|
var attrs = {
|
||||||
var d = '';
|
'fill': cur_shape.fill,
|
||||||
|
'fill-opacity': cur_shape.fill_opacity,
|
||||||
var joinSegs = function(segs) {
|
'stroke': cur_shape.stroke,
|
||||||
$.each(segs, function(j, seg) {
|
'stroke-width': cur_shape.stroke_width,
|
||||||
var i;
|
'stroke-dasharray': cur_shape.stroke_dasharray,
|
||||||
var l = seg[0], pts = seg[1];
|
'stroke-linejoin': cur_shape.stroke_linejoin,
|
||||||
d += l;
|
'stroke-linecap': cur_shape.stroke_linecap,
|
||||||
for (i = 0; i < pts.length; i+=2) {
|
'stroke-opacity': cur_shape.stroke_opacity,
|
||||||
d += (pts[i] +','+pts[i+1]) + ' ';
|
'opacity': cur_shape.opacity,
|
||||||
}
|
'visibility':'hidden'
|
||||||
});
|
};
|
||||||
};
|
return svgedit.utilities.convertToPath(elem, attrs, addSvgElementFromJson, pathActions, clearSelection, addToSelection, svgedit.history, addCommandToHistory);
|
||||||
|
|
||||||
// Possibly the cubed root of 6, but 1.81 works best
|
|
||||||
var num = 1.81;
|
|
||||||
var a, rx;
|
|
||||||
switch (elem.tagName) {
|
|
||||||
case 'ellipse':
|
|
||||||
case 'circle':
|
|
||||||
a = $(elem).attr(['rx', 'ry', 'cx', 'cy']);
|
|
||||||
var cx = a.cx, cy = a.cy;
|
|
||||||
rx = a.rx;
|
|
||||||
ry = a.ry;
|
|
||||||
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)]],
|
|
||||||
['C',[(cx+rx/num),(cy-ry), (cx+rx),(cy-ry/num), (cx+rx),(cy)]],
|
|
||||||
['C',[(cx+rx),(cy+ry/num), (cx+rx/num),(cy+ry), (cx),(cy+ry)]],
|
|
||||||
['C',[(cx-rx/num),(cy+ry), (cx-rx),(cy+ry/num), (cx-rx),(cy)]],
|
|
||||||
['Z',[]]
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
case 'path':
|
|
||||||
d = elem.getAttribute('d');
|
|
||||||
break;
|
|
||||||
case 'line':
|
|
||||||
a = $(elem).attr(['x1', 'y1', 'x2', 'y2']);
|
|
||||||
d = 'M'+a.x1+','+a.y1+'L'+a.x2+','+a.y2;
|
|
||||||
break;
|
|
||||||
case 'polyline':
|
|
||||||
case 'polygon':
|
|
||||||
d = 'M' + elem.getAttribute('points');
|
|
||||||
break;
|
|
||||||
case 'rect':
|
|
||||||
var r = $(elem).attr(['rx', 'ry']);
|
|
||||||
rx = r.rx;
|
|
||||||
ry = r.ry;
|
|
||||||
var b = elem.getBBox();
|
|
||||||
var x = b.x, y = b.y, w = b.width, h = b.height;
|
|
||||||
num = 4 - num; // Why? Because!
|
|
||||||
|
|
||||||
if (!rx && !ry) {
|
|
||||||
// Regular rect
|
|
||||||
joinSegs([
|
|
||||||
['M',[x, y]],
|
|
||||||
['L',[x+w, y]],
|
|
||||||
['L',[x+w, y+h]],
|
|
||||||
['L',[x, y+h]],
|
|
||||||
['L',[x, y]],
|
|
||||||
['Z',[]]
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
joinSegs([
|
|
||||||
['M',[x, y+ry]],
|
|
||||||
['C',[x, y+ry/num, x+rx/num, y, x+rx, y]],
|
|
||||||
['L',[x+w-rx, y]],
|
|
||||||
['C',[x+w-rx/num, y, x+w, y+ry/num, x+w, y+ry]],
|
|
||||||
['L',[x+w, y+h-ry]],
|
|
||||||
['C',[x+w, y+h-ry/num, x+w-rx/num, y+h, x+w-rx, y+h]],
|
|
||||||
['L',[x+rx, y+h]],
|
|
||||||
['C',[x+rx/num, y+h, x, y+h-ry/num, x, y+h-ry]],
|
|
||||||
['L',[x, y+ry]],
|
|
||||||
['Z',[]]
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
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 = svgedit.transformlist.getTransformList(path);
|
|
||||||
if (svgedit.math.hasMatrixTransform(tlist)) {
|
|
||||||
pathActions.resetOrientation(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var nextSibling = elem.nextSibling;
|
|
||||||
batchCmd.addSubCommand(new svgedit.history.RemoveElementCommand(elem, nextSibling, parent));
|
|
||||||
batchCmd.addSubCommand(new svgedit.history.InsertElementCommand(path));
|
|
||||||
|
|
||||||
clearSelection();
|
|
||||||
elem.parentNode.removeChild(elem);
|
|
||||||
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);
|
|
||||||
var bb = false;
|
|
||||||
try {
|
|
||||||
bb = path.getBBox();
|
|
||||||
} catch(e) {
|
|
||||||
// Firefox fails
|
|
||||||
}
|
|
||||||
path.parentNode.removeChild(path);
|
|
||||||
return bb;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -529,6 +529,447 @@ svgedit.utilities.getBBox = function(elem) {
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Function: getPathDFromSegments
|
||||||
|
// Create a path 'd' attribute from path segments.
|
||||||
|
// Each segment is an array of the form: [singleChar, [x,y, x,y, ...]]
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// pathSegments - An array of path segments to be converted
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// The converted path d attribute.
|
||||||
|
svgedit.utilities.getPathDFromSegments = function(pathSegments) {
|
||||||
|
var d = '';
|
||||||
|
|
||||||
|
$.each(pathSegments, function(j, seg) {
|
||||||
|
var i;
|
||||||
|
var pts = seg[1];
|
||||||
|
d += seg[0];
|
||||||
|
for (i = 0; i < pts.length; i+=2) {
|
||||||
|
d += (pts[i] +','+pts[i+1]) + ' ';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return d;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function: getPathDFromElement
|
||||||
|
// Make a path 'd' attribute from a simple SVG element shape.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// elem - The element to be converted
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// The path d attribute or undefined if the element type is unknown.
|
||||||
|
svgedit.utilities.getPathDFromElement = function(elem) {
|
||||||
|
|
||||||
|
// Possibly the cubed root of 6, but 1.81 works best
|
||||||
|
var num = 1.81;
|
||||||
|
var d, a, rx, ry;
|
||||||
|
switch (elem.tagName) {
|
||||||
|
case 'ellipse':
|
||||||
|
case 'circle':
|
||||||
|
a = $(elem).attr(['rx', 'ry', 'cx', 'cy']);
|
||||||
|
var cx = a.cx, cy = a.cy;
|
||||||
|
rx = a.rx;
|
||||||
|
ry = a.ry;
|
||||||
|
if (elem.tagName == 'circle') {
|
||||||
|
rx = ry = $(elem).attr('r');
|
||||||
|
}
|
||||||
|
|
||||||
|
d = svgedit.utilities.getPathDFromSegments([
|
||||||
|
['M',[(cx-rx),(cy)]],
|
||||||
|
['C',[(cx-rx),(cy-ry/num), (cx-rx/num),(cy-ry), (cx),(cy-ry)]],
|
||||||
|
['C',[(cx+rx/num),(cy-ry), (cx+rx),(cy-ry/num), (cx+rx),(cy)]],
|
||||||
|
['C',[(cx+rx),(cy+ry/num), (cx+rx/num),(cy+ry), (cx),(cy+ry)]],
|
||||||
|
['C',[(cx-rx/num),(cy+ry), (cx-rx),(cy+ry/num), (cx-rx),(cy)]],
|
||||||
|
['Z',[]]
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
case 'path':
|
||||||
|
d = elem.getAttribute('d');
|
||||||
|
break;
|
||||||
|
case 'line':
|
||||||
|
a = $(elem).attr(['x1', 'y1', 'x2', 'y2']);
|
||||||
|
d = 'M'+a.x1+','+a.y1+'L'+a.x2+','+a.y2;
|
||||||
|
break;
|
||||||
|
case 'polyline':
|
||||||
|
d = 'M' + elem.getAttribute('points');
|
||||||
|
break;
|
||||||
|
case 'polygon':
|
||||||
|
d = 'M' + elem.getAttribute('points') + ' Z';
|
||||||
|
break;
|
||||||
|
case 'rect':
|
||||||
|
var r = $(elem).attr(['rx', 'ry']);
|
||||||
|
rx = r.rx;
|
||||||
|
ry = r.ry;
|
||||||
|
var b = elem.getBBox();
|
||||||
|
var x = b.x, y = b.y, w = b.width, h = b.height;
|
||||||
|
num = 4 - num; // Why? Because!
|
||||||
|
|
||||||
|
if (!rx && !ry) {
|
||||||
|
// Regular rect
|
||||||
|
d = svgedit.utilities.getPathDFromSegments([
|
||||||
|
['M',[x, y]],
|
||||||
|
['L',[x+w, y]],
|
||||||
|
['L',[x+w, y+h]],
|
||||||
|
['L',[x, y+h]],
|
||||||
|
['L',[x, y]],
|
||||||
|
['Z',[]]
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
d = svgedit.utilities.getPathDFromSegments([
|
||||||
|
['M',[x, y+ry]],
|
||||||
|
['C',[x, y+ry/num, x+rx/num, y, x+rx, y]],
|
||||||
|
['L',[x+w-rx, y]],
|
||||||
|
['C',[x+w-rx/num, y, x+w, y+ry/num, x+w, y+ry]],
|
||||||
|
['L',[x+w, y+h-ry]],
|
||||||
|
['C',[x+w, y+h-ry/num, x+w-rx/num, y+h, x+w-rx, y+h]],
|
||||||
|
['L',[x+rx, y+h]],
|
||||||
|
['C',[x+rx/num, y+h, x, y+h-ry/num, x, y+h-ry]],
|
||||||
|
['L',[x, y+ry]],
|
||||||
|
['Z',[]]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return d;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function: getExtraAttributesForConvertToPath
|
||||||
|
// Get a set of attributes from an element that is useful for convertToPath.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// elem - The element to be probed
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// An object with attributes.
|
||||||
|
svgedit.utilities.getExtraAttributesForConvertToPath = function(elem) {
|
||||||
|
var attrs = {} ;
|
||||||
|
// TODO: make this list global so that we can properly maintain it
|
||||||
|
// TODO: what about @transform, @clip-rule, @fill-rule, etc?
|
||||||
|
$.each(['marker-start', 'marker-end', 'marker-mid', 'filter', 'clip-path'], function() {
|
||||||
|
var a = elem.getAttribute(this);
|
||||||
|
if (a) {
|
||||||
|
attrs[this] = a;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return attrs;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function: getBBoxOfElementAsPath
|
||||||
|
// Get the BBox of an element-as-path
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// elem - The DOM element to be probed
|
||||||
|
// addSvgElementFromJson - Function to add the path element to the current layer. See canvas.addSvgElementFromJson
|
||||||
|
// pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// The resulting path's bounding box object.
|
||||||
|
svgedit.utilities.getBBoxOfElementAsPath = function(elem, addSvgElementFromJson, pathActions) {
|
||||||
|
|
||||||
|
var path = addSvgElementFromJson({
|
||||||
|
'element': 'path',
|
||||||
|
'attr': svgedit.utilities.getExtraAttributesForConvertToPath(elem)
|
||||||
|
});
|
||||||
|
|
||||||
|
var eltrans = elem.getAttribute('transform');
|
||||||
|
if (eltrans) {
|
||||||
|
path.setAttribute('transform', eltrans);
|
||||||
|
}
|
||||||
|
|
||||||
|
var parent = elem.parentNode;
|
||||||
|
if (elem.nextSibling) {
|
||||||
|
parent.insertBefore(path, elem);
|
||||||
|
} else {
|
||||||
|
parent.appendChild(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
var d = svgedit.utilities.getPathDFromElement(elem);
|
||||||
|
if (d)
|
||||||
|
path.setAttribute('d', d);
|
||||||
|
else
|
||||||
|
path.parentNode.removeChild(path);
|
||||||
|
|
||||||
|
// Get the correct BBox of the new path, then discard it
|
||||||
|
pathActions.resetOrientation(path);
|
||||||
|
var bb = false;
|
||||||
|
try {
|
||||||
|
bb = path.getBBox();
|
||||||
|
} catch(e) {
|
||||||
|
// Firefox fails
|
||||||
|
}
|
||||||
|
path.parentNode.removeChild(path);
|
||||||
|
return bb;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function: convertToPath
|
||||||
|
// Convert selected element to a path.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// elem - The DOM element to be converted
|
||||||
|
// attrs - Apply attributes to new path. see canvas.convertToPath
|
||||||
|
// addSvgElementFromJson - Function to add the path element to the current layer. See canvas.addSvgElementFromJson
|
||||||
|
// pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
|
||||||
|
// clearSelection - see canvas.clearSelection
|
||||||
|
// addToSelection - see canvas.addToSelection
|
||||||
|
// history - see svgedit.history
|
||||||
|
// addCommandToHistory - see canvas.addCommandToHistory
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// The converted path element or null if the DOM element was not recognized.
|
||||||
|
svgedit.utilities.convertToPath = function(elem, attrs, addSvgElementFromJson, pathActions, clearSelection, addToSelection, history, addCommandToHistory) {
|
||||||
|
|
||||||
|
var batchCmd = new history.BatchCommand('Convert element to Path');
|
||||||
|
|
||||||
|
// Any attribute on the element not covered by the passed-in attributes
|
||||||
|
attrs = $.extend({}, attrs, svgedit.utilities.getExtraAttributesForConvertToPath(elem));
|
||||||
|
|
||||||
|
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) {
|
||||||
|
parent.insertBefore(path, elem);
|
||||||
|
} else {
|
||||||
|
parent.appendChild(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
var d = svgedit.utilities.getPathDFromElement(elem);
|
||||||
|
if (d) {
|
||||||
|
path.setAttribute('d', d);
|
||||||
|
|
||||||
|
// Replace the current element with the converted one
|
||||||
|
|
||||||
|
// Reorient if it has a matrix
|
||||||
|
if (eltrans) {
|
||||||
|
var tlist = svgedit.transformlist.getTransformList(path);
|
||||||
|
if (svgedit.math.hasMatrixTransform(tlist)) {
|
||||||
|
pathActions.resetOrientation(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextSibling = elem.nextSibling;
|
||||||
|
batchCmd.addSubCommand(new history.RemoveElementCommand(elem, nextSibling, parent));
|
||||||
|
batchCmd.addSubCommand(new history.InsertElementCommand(path));
|
||||||
|
|
||||||
|
clearSelection();
|
||||||
|
elem.parentNode.removeChild(elem);
|
||||||
|
path.setAttribute('id', id);
|
||||||
|
path.removeAttribute('visibility');
|
||||||
|
addToSelection([path], true);
|
||||||
|
|
||||||
|
addCommandToHistory(batchCmd);
|
||||||
|
|
||||||
|
return path;
|
||||||
|
} else {
|
||||||
|
// the elem.tagName was not recognized, so no "d" attribute. Remove it, so we've haven't changed anything.
|
||||||
|
path.parentNode.removeChild(path);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function: bBoxCanBeOptimizedOverNativeGetBBox
|
||||||
|
// Can the bbox be optimized over the native getBBox? The optimized bbox is the same as the native getBBox when
|
||||||
|
// the rotation angle is a multiple of 90 degrees and there are no complex transforms.
|
||||||
|
// Getting an optimized bbox can be dramatically slower, so we want to make sure it's worth it.
|
||||||
|
//
|
||||||
|
// The best example for this is a circle rotate 45 degrees. The circle doesn't get wider or taller when rotated
|
||||||
|
// about it's center.
|
||||||
|
//
|
||||||
|
// The standard, unoptimized technique gets the native bbox of the circle, rotates the box 45 degrees, uses
|
||||||
|
// that width and height, and applies any transforms to get the final bbox. This means the calculated bbox
|
||||||
|
// is much wider than the original circle. If the angle had been 0, 90, 180, etc. both techniques render the
|
||||||
|
// same bbox.
|
||||||
|
//
|
||||||
|
// The optimization is not needed if the rotation is a multiple 90 degrees. The default technique is to call
|
||||||
|
// getBBox then apply the angle and any transforms.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// angle - The rotation angle in degrees
|
||||||
|
// hasMatrixTransform - True if there is a matrix transform
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// True if the bbox can be optimized.
|
||||||
|
function bBoxCanBeOptimizedOverNativeGetBBox(angle, hasMatrixTransform) {
|
||||||
|
var angleModulo90 = angle % 90;
|
||||||
|
var closeTo90 = angleModulo90 < -89.99 || angleModulo90 > 89.99;
|
||||||
|
var closeTo0 = angleModulo90 > -0.001 && angleModulo90 < 0.001;
|
||||||
|
return hasMatrixTransform || ! (closeTo0 || closeTo90);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function: getBBoxWithTransform
|
||||||
|
// Get bounding box that includes any transforms.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// elem - The DOM element to be converted
|
||||||
|
// addSvgElementFromJson - Function to add the path element to the current layer. See canvas.addSvgElementFromJson
|
||||||
|
// pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// A single bounding box object
|
||||||
|
svgedit.utilities.getBBoxWithTransform = function(elem, addSvgElementFromJson, pathActions) {
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
if (!bb) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tlist = svgedit.transformlist.getTransformList(elem);
|
||||||
|
var angle = svgedit.utilities.getRotationAngleFromTransformList(tlist);
|
||||||
|
var hasMatrixTransform = svgedit.math.hasMatrixTransform(tlist);
|
||||||
|
|
||||||
|
if (angle || hasMatrixTransform) {
|
||||||
|
|
||||||
|
var good_bb = false;
|
||||||
|
if (bBoxCanBeOptimizedOverNativeGetBBox(angle, hasMatrixTransform)) {
|
||||||
|
// Get the BBox from the raw path for these elements
|
||||||
|
// TODO: why ellipse and not circle
|
||||||
|
var elemNames = ['ellipse', 'path', 'line', 'polyline', 'polygon'];
|
||||||
|
if (elemNames.indexOf(elem.tagName) >= 0) {
|
||||||
|
bb = good_bb = svgedit.utilities.getBBoxOfElementAsPath(elem, addSvgElementFromJson, pathActions);
|
||||||
|
} else if (elem.tagName == 'rect') {
|
||||||
|
// Look for radius
|
||||||
|
var rx = elem.getAttribute('rx');
|
||||||
|
var ry = elem.getAttribute('ry');
|
||||||
|
if (rx || ry) {
|
||||||
|
bb = good_bb = svgedit.utilities.getBBoxOfElementAsPath(elem, addSvgElementFromJson, pathActions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!good_bb) {
|
||||||
|
|
||||||
|
var matrix = svgedit.math.transformListToTransform(tlist).matrix;
|
||||||
|
bb = svgedit.math.transformBox(bb.x, bb.y, bb.width, bb.height, matrix).aabox;
|
||||||
|
|
||||||
|
// Old technique that was exceedingly slow with large documents.
|
||||||
|
//
|
||||||
|
// Accurate way to get BBox of rotated element in Firefox:
|
||||||
|
// Put element in group and get its BBox
|
||||||
|
//
|
||||||
|
// Must use clone else FF freaks out
|
||||||
|
//var clone = elem.cloneNode(true);
|
||||||
|
//var g = document.createElementNS(NS.SVG, 'g');
|
||||||
|
//var parent = elem.parentNode;
|
||||||
|
//parent.appendChild(g);
|
||||||
|
//g.appendChild(clone);
|
||||||
|
//var bb2 = svgedit.utilities.bboxToObj(g.getBBox());
|
||||||
|
//parent.removeChild(g);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return bb;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: This is problematic with large stroke-width and, for example, a single horizontal line. The calculated BBox extends way beyond left and right sides.
|
||||||
|
function getStrokeOffsetForBBox(elem) {
|
||||||
|
var sw = elem.getAttribute('stroke-width');
|
||||||
|
return (!isNaN(sw) && elem.getAttribute('stroke') != 'none') ? sw/2 : 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function: getStrokedBBox
|
||||||
|
// Get the bounding box for one or more stroked and/or transformed elements
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// elems - Array with DOM elements to check
|
||||||
|
// addSvgElementFromJson - Function to add the path element to the current layer. See canvas.addSvgElementFromJson
|
||||||
|
// pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// A single bounding box object
|
||||||
|
svgedit.utilities.getStrokedBBox = function(elems, addSvgElementFromJson, pathActions) {
|
||||||
|
if (!elems || !elems.length) {return false;}
|
||||||
|
|
||||||
|
var full_bb;
|
||||||
|
$.each(elems, function() {
|
||||||
|
if (full_bb) {return;}
|
||||||
|
if (!this.parentNode) {return;}
|
||||||
|
full_bb = svgedit.utilities.getBBoxWithTransform(this, addSvgElementFromJson, pathActions);
|
||||||
|
});
|
||||||
|
|
||||||
|
// This shouldn't ever happen...
|
||||||
|
if (full_bb === undefined) {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;
|
||||||
|
|
||||||
|
// If only one elem, don't call the potentially slow getBBoxWithTransform method again.
|
||||||
|
if (elems.length === 1) {
|
||||||
|
var offset = getStrokeOffsetForBBox(elems[0]);
|
||||||
|
min_x -= offset;
|
||||||
|
min_y -= offset;
|
||||||
|
max_x += offset;
|
||||||
|
max_y += offset;
|
||||||
|
} else {
|
||||||
|
$.each(elems, function(i, elem) {
|
||||||
|
var cur_bb = svgedit.utilities.getBBoxWithTransform(elem, addSvgElementFromJson, pathActions);
|
||||||
|
if (cur_bb) {
|
||||||
|
var offset = getStrokeOffsetForBBox(elem);
|
||||||
|
min_x = Math.min(min_x, cur_bb.x - offset);
|
||||||
|
min_y = Math.min(min_y, cur_bb.y - offset);
|
||||||
|
// TODO: The old code had this test for max, but not min. I suspect this test should be for both min and max
|
||||||
|
if (elem.nodeType == 1) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
full_bb.x = min_x;
|
||||||
|
full_bb.y = min_y;
|
||||||
|
full_bb.width = max_x - min_x;
|
||||||
|
full_bb.height = max_y - min_y;
|
||||||
|
return full_bb;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Function: svgedit.utilities.getRotationAngleFromTransformList
|
||||||
|
// Get the rotation angle of the given transform list.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// tlist - List of transforms
|
||||||
|
// to_rad - Boolean that when true returns the value in radians rather than degrees
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// Float with the angle in degrees or radians
|
||||||
|
svgedit.utilities.getRotationAngleFromTransformList = function(tlist, to_rad) {
|
||||||
|
if (!tlist) {return 0;} // <svg> elements have no tlist
|
||||||
|
var N = tlist.numberOfItems;
|
||||||
|
var i;
|
||||||
|
for (i = 0; i < N; ++i) {
|
||||||
|
var xform = tlist.getItem(i);
|
||||||
|
if (xform.type == 4) {
|
||||||
|
return to_rad ? xform.angle * Math.PI / 180.0 : xform.angle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
// Function: svgedit.utilities.getRotationAngle
|
// Function: svgedit.utilities.getRotationAngle
|
||||||
// Get the rotation angle of the given/selected DOM element
|
// Get the rotation angle of the given/selected DOM element
|
||||||
//
|
//
|
||||||
|
@ -542,16 +983,7 @@ svgedit.utilities.getRotationAngle = function(elem, to_rad) {
|
||||||
var selected = elem || editorContext_.getSelectedElements()[0];
|
var selected = elem || editorContext_.getSelectedElements()[0];
|
||||||
// find the rotation transform (if any) and set it
|
// find the rotation transform (if any) and set it
|
||||||
var tlist = svgedit.transformlist.getTransformList(selected);
|
var tlist = svgedit.transformlist.getTransformList(selected);
|
||||||
if(!tlist) {return 0;} // <svg> elements have no tlist
|
return svgedit.utilities.getRotationAngleFromTransformList(tlist, to_rad)
|
||||||
var N = tlist.numberOfItems;
|
|
||||||
var i;
|
|
||||||
for (i = 0; i < N; ++i) {
|
|
||||||
var xform = tlist.getItem(i);
|
|
||||||
if (xform.type == 4) {
|
|
||||||
return to_rad ? xform.angle * Math.PI / 180.0 : xform.angle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0.0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function getRefElem
|
// Function getRefElem
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
<iframe src='contextmenu_test.html' width='100%' height='70' scrolling='no'></iframe>
|
<iframe src='contextmenu_test.html' width='100%' height='70' scrolling='no'></iframe>
|
||||||
<iframe src='math_test.html' width='100%' height='70' scrolling='no'></iframe>
|
<iframe src='math_test.html' width='100%' height='70' scrolling='no'></iframe>
|
||||||
<iframe src='svgutils_test.html' width='100%' height='70' scrolling='no'></iframe>
|
<iframe src='svgutils_test.html' width='100%' height='70' scrolling='no'></iframe>
|
||||||
|
<iframe src='svgutils_bbox_test.html' width='100%' height='70' scrolling='no'></iframe>
|
||||||
<iframe src='history_test.html' width='100%' height='70' scrolling='no'></iframe>
|
<iframe src='history_test.html' width='100%' height='70' scrolling='no'></iframe>
|
||||||
<iframe src='select_test.html' width='100%' height='70' scrolling='no'></iframe>
|
<iframe src='select_test.html' width='100%' height='70' scrolling='no'></iframe>
|
||||||
<iframe src='draw_test.html' width='100%' height='70' scrolling='no'></iframe>
|
<iframe src='draw_test.html' width='100%' height='70' scrolling='no'></iframe>
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
/**
|
||||||
|
* Checks that the first two arguments are equal, or are numbers close enough to be considered equal
|
||||||
|
* based on a specified maximum allowable difference.
|
||||||
|
*
|
||||||
|
* @example assert.close(3.141, Math.PI, 0.001);
|
||||||
|
*
|
||||||
|
* @param Number actual
|
||||||
|
* @param Number expected
|
||||||
|
* @param Number maxDifference (the maximum inclusive difference allowed between the actual and expected numbers)
|
||||||
|
* @param String message (optional)
|
||||||
|
*/
|
||||||
|
function close(actual, expected, maxDifference, message) {
|
||||||
|
var actualDiff = (actual === expected) ? 0 : Math.abs(actual - expected),
|
||||||
|
result = actualDiff <= maxDifference;
|
||||||
|
message = message || (actual + " should be within " + maxDifference + " (inclusive) of " + expected + (result ? "" : ". Actual: " + actualDiff));
|
||||||
|
QUnit.push(result, actual, expected, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that the first two arguments are equal, or are numbers close enough to be considered equal
|
||||||
|
* based on a specified maximum allowable difference percentage.
|
||||||
|
*
|
||||||
|
* @example assert.close.percent(155, 150, 3.4); // Difference is ~3.33%
|
||||||
|
*
|
||||||
|
* @param Number actual
|
||||||
|
* @param Number expected
|
||||||
|
* @param Number maxPercentDifference (the maximum inclusive difference percentage allowed between the actual and expected numbers)
|
||||||
|
* @param String message (optional)
|
||||||
|
*/
|
||||||
|
close.percent = function closePercent(actual, expected, maxPercentDifference, message) {
|
||||||
|
var actualDiff, result;
|
||||||
|
if (actual === expected) {
|
||||||
|
actualDiff = 0;
|
||||||
|
result = actualDiff <= maxPercentDifference;
|
||||||
|
}
|
||||||
|
else if (actual !== 0 && expected !== 0 && expected !== Infinity && expected !== -Infinity) {
|
||||||
|
actualDiff = Math.abs(100 * (actual - expected) / expected);
|
||||||
|
result = actualDiff <= maxPercentDifference;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Dividing by zero (0)! Should return `false` unless the max percentage was `Infinity`
|
||||||
|
actualDiff = Infinity;
|
||||||
|
result = maxPercentDifference === Infinity;
|
||||||
|
}
|
||||||
|
message = message || (actual + " should be within " + maxPercentDifference + "% (inclusive) of " + expected + (result ? "" : ". Actual: " + actualDiff + "%"));
|
||||||
|
|
||||||
|
QUnit.push(result, actual, expected, message);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that the first two arguments are numbers with differences greater than the specified
|
||||||
|
* minimum difference.
|
||||||
|
*
|
||||||
|
* @example assert.notClose(3.1, Math.PI, 0.001);
|
||||||
|
*
|
||||||
|
* @param Number actual
|
||||||
|
* @param Number expected
|
||||||
|
* @param Number minDifference (the minimum exclusive difference allowed between the actual and expected numbers)
|
||||||
|
* @param String message (optional)
|
||||||
|
*/
|
||||||
|
function notClose(actual, expected, minDifference, message) {
|
||||||
|
var actualDiff = Math.abs(actual - expected),
|
||||||
|
result = actualDiff > minDifference;
|
||||||
|
message = message || (actual + " should not be within " + minDifference + " (exclusive) of " + expected + (result ? "" : ". Actual: " + actualDiff));
|
||||||
|
QUnit.push(result, actual, expected, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that the first two arguments are numbers with differences greater than the specified
|
||||||
|
* minimum difference percentage.
|
||||||
|
*
|
||||||
|
* @example assert.notClose.percent(156, 150, 3.5); // Difference is 4.0%
|
||||||
|
*
|
||||||
|
* @param Number actual
|
||||||
|
* @param Number expected
|
||||||
|
* @param Number minPercentDifference (the minimum exclusive difference percentage allowed between the actual and expected numbers)
|
||||||
|
* @param String message (optional)
|
||||||
|
*/
|
||||||
|
notClose.percent = function notClosePercent(actual, expected, minPercentDifference, message) {
|
||||||
|
var actualDiff, result;
|
||||||
|
if (actual === expected) {
|
||||||
|
actualDiff = 0;
|
||||||
|
result = actualDiff > minPercentDifference;
|
||||||
|
}
|
||||||
|
else if (actual !== 0 && expected !== 0 && expected !== Infinity && expected !== -Infinity) {
|
||||||
|
actualDiff = Math.abs(100 * (actual - expected) / expected);
|
||||||
|
result = actualDiff > minPercentDifference;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Dividing by zero (0)! Should only return `true` if the min percentage was `Infinity`
|
||||||
|
actualDiff = Infinity;
|
||||||
|
result = minPercentDifference !== Infinity;
|
||||||
|
}
|
||||||
|
message = message || (actual + " should not be within " + minPercentDifference + "% (exclusive) of " + expected + (result ? "" : ". Actual: " + actualDiff + "%"));
|
||||||
|
|
||||||
|
QUnit.push(result, actual, expected, message);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//QUnit.extend(QUnit.assert, {
|
||||||
|
// close: close,
|
||||||
|
// notClose: notClose
|
||||||
|
//});
|
|
@ -0,0 +1,525 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Unit Tests for svgutils.js BBox functions</title>
|
||||||
|
<link rel='stylesheet' href='qunit/qunit.css' type='text/css'/>
|
||||||
|
<script src='../editor/jquery.js'></script>
|
||||||
|
<script src='../editor/svgedit.js'></script>
|
||||||
|
<!-- svgutils.js depends on these three... mock out? -->
|
||||||
|
<script src='../editor/pathseg.js'></script>
|
||||||
|
<script src='../editor/browser.js'></script>
|
||||||
|
<script src='../editor/math.js'></script>
|
||||||
|
<script src='../editor/svgtransformlist.js'></script>
|
||||||
|
<script src='../editor/jquery-svg.js'></script> <!-- has $.attr() that takes an array . Used by svgedit.utilities.getPathDFromElement -->
|
||||||
|
<script src='../editor/path.js'></script>
|
||||||
|
<script src='../editor/svgutils.js'></script>
|
||||||
|
<script src='qunit/qunit.js'></script>
|
||||||
|
<script src='qunit/qunit-assert-close.js'></script>
|
||||||
|
<script>
|
||||||
|
$(function() {
|
||||||
|
// log function
|
||||||
|
QUnit.log = function(details) {
|
||||||
|
if (window.console && window.console.log) {
|
||||||
|
window.console.log(details.result +' :: '+ details.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function mockCreateSVGElement(jsonMap) {
|
||||||
|
var elem = document.createElementNS(svgedit.NS.SVG, jsonMap['element']);
|
||||||
|
for (var attr in jsonMap['attr']) {
|
||||||
|
elem.setAttribute(attr, jsonMap['attr'][attr]);
|
||||||
|
}
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
|
var mockAddSvgElementFromJsonCallCount = 0;
|
||||||
|
function mockAddSvgElementFromJson( json) {
|
||||||
|
var elem = mockCreateSVGElement( json)
|
||||||
|
svgroot.appendChild( elem)
|
||||||
|
mockAddSvgElementFromJsonCallCount++;
|
||||||
|
return elem
|
||||||
|
}
|
||||||
|
var mockPathActions = {
|
||||||
|
resetOrientation: function(path) {
|
||||||
|
if (path == null || path.nodeName != 'path') {return false;}
|
||||||
|
var tlist = svgedit.transformlist.getTransformList(path);
|
||||||
|
var m = svgedit.math.transformListToTransform(tlist).matrix;
|
||||||
|
tlist.clear();
|
||||||
|
path.removeAttribute('transform');
|
||||||
|
var segList = path.pathSegList;
|
||||||
|
|
||||||
|
var len = segList.numberOfItems;
|
||||||
|
var i, last_x, last_y;
|
||||||
|
|
||||||
|
for (i = 0; i < len; ++i) {
|
||||||
|
var seg = segList.getItem(i);
|
||||||
|
var type = seg.pathSegType;
|
||||||
|
if (type == 1) {continue;}
|
||||||
|
var pts = [];
|
||||||
|
$.each(['',1,2], function(j, n) {
|
||||||
|
var x = seg['x'+n], y = seg['y'+n];
|
||||||
|
if (x !== undefined && y !== undefined) {
|
||||||
|
var pt = svgedit.math.transformPoint(x, y, m);
|
||||||
|
pts.splice(pts.length, 0, pt.x, pt.y);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
svgedit.path.replacePathSeg(type, i, pts, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
//svgedit.utilities.reorientGrads(path, m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var EPSILON = 0.001
|
||||||
|
var svg = document.createElementNS(svgedit.NS.SVG, 'svg');
|
||||||
|
var sandbox = document.getElementById('sandbox');
|
||||||
|
var svgroot = mockCreateSVGElement({
|
||||||
|
'element': 'svg',
|
||||||
|
'attr': {'id': 'svgroot'}
|
||||||
|
});
|
||||||
|
sandbox.appendChild(svgroot);
|
||||||
|
|
||||||
|
|
||||||
|
module('svgedit.utilities_bbox', {
|
||||||
|
setup: function() {
|
||||||
|
// We're reusing ID's so we need to do this for transforms.
|
||||||
|
svgedit.transformlist.resetListMap();
|
||||||
|
svgedit.path.init(null);
|
||||||
|
mockAddSvgElementFromJsonCallCount = 0;
|
||||||
|
},
|
||||||
|
teardown: function() {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('Test svgedit.utilities package', function() {
|
||||||
|
ok(svgedit.utilities);
|
||||||
|
ok(svgedit.utilities.getBBoxWithTransform);
|
||||||
|
ok(svgedit.utilities.getStrokedBBox);
|
||||||
|
ok(svgedit.utilities.getRotationAngleFromTransformList);
|
||||||
|
ok(svgedit.utilities.getRotationAngle);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test("Test getBBoxWithTransform and no transform", function() {
|
||||||
|
var getBBoxWithTransform = svgedit.utilities.getBBoxWithTransform
|
||||||
|
|
||||||
|
var bbox;
|
||||||
|
var elem = mockCreateSVGElement({
|
||||||
|
'element': 'path',
|
||||||
|
'attr': { 'id': 'path', 'd': 'M0,1 L2,3'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem)
|
||||||
|
bbox = getBBoxWithTransform(elem, mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
deepEqual(bbox, {"x": 0, "y": 1, "width": 2, "height": 2 });
|
||||||
|
equal( mockAddSvgElementFromJsonCallCount, 0);
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'rect',
|
||||||
|
'attr': { 'id': 'rect', 'x': '0', 'y': '1', 'width': '5', 'height': '10'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem);
|
||||||
|
bbox = getBBoxWithTransform( elem, mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
deepEqual( bbox, { "x": 0, "y": 1, "width": 5, "height": 10});
|
||||||
|
equal( mockAddSvgElementFromJsonCallCount, 0);
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'line',
|
||||||
|
'attr': { 'id': 'line', 'x1': '0', 'y1': '1', 'x2': '5', 'y2': '6'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem);
|
||||||
|
bbox = getBBoxWithTransform( elem, mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
deepEqual( bbox, { "x": 0, "y": 1, "width": 5, "height": 5});
|
||||||
|
equal( mockAddSvgElementFromJsonCallCount, 0);
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'rect',
|
||||||
|
'attr': { 'id': 'rect', 'x': '0', 'y': '1', 'width': '5', 'height': '10'}
|
||||||
|
});
|
||||||
|
var g = mockCreateSVGElement({
|
||||||
|
'element': 'g',
|
||||||
|
'attr': {}
|
||||||
|
});
|
||||||
|
g.appendChild( elem);
|
||||||
|
svgroot.appendChild( g);
|
||||||
|
bbox = getBBoxWithTransform( elem, mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
deepEqual( bbox, { "x": 0, "y": 1, "width": 5, "height": 10});
|
||||||
|
equal( mockAddSvgElementFromJsonCallCount, 0);
|
||||||
|
svgroot.removeChild( g);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test("Test getBBoxWithTransform and a rotation transform", function() {
|
||||||
|
var getBBoxWithTransform = svgedit.utilities.getBBoxWithTransform
|
||||||
|
|
||||||
|
var bbox;
|
||||||
|
var elem = mockCreateSVGElement({
|
||||||
|
'element': 'path',
|
||||||
|
'attr': { 'id': 'path', 'd': 'M10,10 L20,20', 'transform': 'rotate(45 10,10)'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem)
|
||||||
|
bbox = getBBoxWithTransform(elem, mockAddSvgElementFromJson, mockPathActions);
|
||||||
|
close( bbox.x, 10, EPSILON);
|
||||||
|
close( bbox.y, 10, EPSILON);
|
||||||
|
close( bbox.width, 0, EPSILON);
|
||||||
|
close( bbox.height, Math.sqrt(100 + 100), EPSILON);
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'rect',
|
||||||
|
'attr': { 'id': 'rect', 'x': '10', 'y': '10', 'width': '10', 'height': '20', 'transform': 'rotate(90 15,20)'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem);
|
||||||
|
bbox = getBBoxWithTransform( elem, mockAddSvgElementFromJson, mockPathActions);
|
||||||
|
close( bbox.x, 5, EPSILON);
|
||||||
|
close( bbox.y, 15, EPSILON);
|
||||||
|
close( bbox.width, 20, EPSILON);
|
||||||
|
close( bbox.height, 10, EPSILON);
|
||||||
|
equal( mockAddSvgElementFromJsonCallCount, 1);
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
var rect = {x: 10, y: 10, width: 10, height: 20};
|
||||||
|
var angle = 45;
|
||||||
|
var origin = { x: 15, y: 20};
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'rect',
|
||||||
|
'attr': { 'id': 'rect2', 'x': rect.x, 'y': rect.y, 'width': rect.width, 'height': rect.height, 'transform': 'rotate(' + angle + ' ' + origin.x + ',' + origin.y + ')'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem);
|
||||||
|
mockAddSvgElementFromJsonCallCount = 0;
|
||||||
|
bbox = getBBoxWithTransform( elem, mockAddSvgElementFromJson, mockPathActions);
|
||||||
|
var r2 = rotateRect( rect, angle, origin);
|
||||||
|
close( bbox.x, r2.x, EPSILON, 'rect2 x is ' + r2.x);
|
||||||
|
close( bbox.y, r2.y, EPSILON, 'rect2 y is ' + r2.y);
|
||||||
|
close( bbox.width, r2.width, EPSILON, 'rect2 width is' + r2.width);
|
||||||
|
close( bbox.height, r2.height, EPSILON, 'rect2 height is ' + r2.height);
|
||||||
|
equal( mockAddSvgElementFromJsonCallCount, 0);
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
|
||||||
|
// Same as previous but wrapped with g and the transform is with the g.
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'rect',
|
||||||
|
'attr': { 'id': 'rect3', 'x': rect.x, 'y': rect.y, 'width': rect.width, 'height': rect.height}
|
||||||
|
});
|
||||||
|
var g = mockCreateSVGElement({
|
||||||
|
'element': 'g',
|
||||||
|
'attr': {'transform': 'rotate(' + angle + ' ' + origin.x + ',' + origin.y + ')'}
|
||||||
|
});
|
||||||
|
g.appendChild( elem);
|
||||||
|
svgroot.appendChild( g);
|
||||||
|
mockAddSvgElementFromJsonCallCount = 0;
|
||||||
|
bbox = getBBoxWithTransform( g, mockAddSvgElementFromJson, mockPathActions);
|
||||||
|
close( bbox.x, r2.x, EPSILON, 'rect2 x is ' + r2.x);
|
||||||
|
close( bbox.y, r2.y, EPSILON, 'rect2 y is ' + r2.y);
|
||||||
|
close( bbox.width, r2.width, EPSILON, 'rect2 width is' + r2.width);
|
||||||
|
close( bbox.height, r2.height, EPSILON, 'rect2 height is ' + r2.height);
|
||||||
|
equal( mockAddSvgElementFromJsonCallCount, 0);
|
||||||
|
svgroot.removeChild( g);
|
||||||
|
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'ellipse',
|
||||||
|
'attr': { 'id': 'ellipse1', 'cx': '100', 'cy': '100', 'rx': '50', 'ry': '50', 'transform': 'rotate(45 100,100)'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem);
|
||||||
|
mockAddSvgElementFromJsonCallCount = 0;
|
||||||
|
bbox = getBBoxWithTransform( elem, mockAddSvgElementFromJson, mockPathActions);
|
||||||
|
// TODO: the BBox algorithm is using the bezier control points to calculate the bounding box. Should be 50, 50, 100, 100.
|
||||||
|
ok( bbox.x > 45 && bbox.x <= 50);
|
||||||
|
ok( bbox.y > 45 && bbox.y <= 50);
|
||||||
|
ok( bbox.width >= 100 && bbox.width < 110);
|
||||||
|
ok( bbox.height >= 100 && bbox.height < 110);
|
||||||
|
equal( mockAddSvgElementFromJsonCallCount, 1);
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
test("Test getBBoxWithTransform with rotation and matrix transforms", function() {
|
||||||
|
var getBBoxWithTransform = svgedit.utilities.getBBoxWithTransform
|
||||||
|
|
||||||
|
var bbox;
|
||||||
|
var tx = 10; // tx right
|
||||||
|
var ty = 10; // tx down
|
||||||
|
var txInRotatedSpace = Math.sqrt(tx*tx + ty*ty); // translate in rotated 45 space.
|
||||||
|
var tyInRotatedSpace = 0;
|
||||||
|
var matrix = 'matrix(1,0,0,1,' + txInRotatedSpace + ',' + tyInRotatedSpace + ')'
|
||||||
|
var elem = mockCreateSVGElement({
|
||||||
|
'element': 'path',
|
||||||
|
'attr': { 'id': 'path', 'd': 'M10,10 L20,20', 'transform': 'rotate(45 10,10) ' + matrix}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem)
|
||||||
|
bbox = getBBoxWithTransform(elem, mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
close( bbox.x, 10 + tx, EPSILON);
|
||||||
|
close( bbox.y, 10 + ty, EPSILON);
|
||||||
|
close( bbox.width, 0, EPSILON)
|
||||||
|
close( bbox.height, Math.sqrt(100 + 100), EPSILON)
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
txInRotatedSpace = tx; // translate in rotated 90 space.
|
||||||
|
tyInRotatedSpace = -ty;
|
||||||
|
matrix = 'matrix(1,0,0,1,' + txInRotatedSpace + ',' + tyInRotatedSpace + ')'
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'rect',
|
||||||
|
'attr': { 'id': 'rect', 'x': '10', 'y': '10', 'width': '10', 'height': '20', 'transform': 'rotate(90 15,20) ' + matrix}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem);
|
||||||
|
bbox = getBBoxWithTransform( elem, mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
close( bbox.x, 5 + tx, EPSILON);
|
||||||
|
close( bbox.y, 15 + ty, EPSILON);
|
||||||
|
close( bbox.width, 20, EPSILON);
|
||||||
|
close( bbox.height, 10, EPSILON);
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
var rect = {x: 10, y: 10, width: 10, height: 20}
|
||||||
|
var angle = 45
|
||||||
|
var origin = { x: 15, y: 20}
|
||||||
|
tx = 10; // tx right
|
||||||
|
ty = 10; // tx down
|
||||||
|
txInRotatedSpace = Math.sqrt(tx*tx + ty*ty); // translate in rotated 45 space.
|
||||||
|
tyInRotatedSpace = 0;
|
||||||
|
matrix = 'matrix(1,0,0,1,' + txInRotatedSpace + ',' + tyInRotatedSpace + ')'
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'rect',
|
||||||
|
'attr': { 'id': 'rect2', 'x': rect.x, 'y': rect.y, 'width': rect.width, 'height': rect.height, 'transform': 'rotate(' + angle + ' ' + origin.x + ',' + origin.y + ') ' + matrix}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem);
|
||||||
|
bbox = getBBoxWithTransform( elem, mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
var r2 = rotateRect( rect, angle, origin)
|
||||||
|
close( bbox.x, r2.x + tx, EPSILON, 'rect2 x is ' + r2.x);
|
||||||
|
close( bbox.y, r2.y + ty, EPSILON, 'rect2 y is ' + r2.y);
|
||||||
|
close( bbox.width, r2.width, EPSILON, 'rect2 width is' + r2.width);
|
||||||
|
close( bbox.height, r2.height, EPSILON, 'rect2 height is ' + r2.height);
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
|
||||||
|
// Same as previous but wrapped with g and the transform is with the g.
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'rect',
|
||||||
|
'attr': { 'id': 'rect3', 'x': rect.x, 'y': rect.y, 'width': rect.width, 'height': rect.height}
|
||||||
|
});
|
||||||
|
var g = mockCreateSVGElement({
|
||||||
|
'element': 'g',
|
||||||
|
'attr': {'transform': 'rotate(' + angle + ' ' + origin.x + ',' + origin.y + ') ' + matrix}
|
||||||
|
});
|
||||||
|
g.appendChild( elem);
|
||||||
|
svgroot.appendChild( g);
|
||||||
|
bbox = getBBoxWithTransform( g, mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
close( bbox.x, r2.x + tx, EPSILON, 'rect2 x is ' + r2.x);
|
||||||
|
close( bbox.y, r2.y + ty, EPSILON, 'rect2 y is ' + r2.y);
|
||||||
|
close( bbox.width, r2.width, EPSILON, 'rect2 width is' + r2.width);
|
||||||
|
close( bbox.height, r2.height, EPSILON, 'rect2 height is ' + r2.height);
|
||||||
|
svgroot.removeChild( g);
|
||||||
|
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'ellipse',
|
||||||
|
'attr': { 'id': 'ellipse1', 'cx': '100', 'cy': '100', 'rx': '50', 'ry': '50', 'transform': 'rotate(45 100,100) ' + matrix}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem);
|
||||||
|
bbox = getBBoxWithTransform( elem, mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
// TODO: the BBox algorithm is using the bezier control points to calculate the bounding box. Should be 50, 50, 100, 100.
|
||||||
|
ok( bbox.x > 45 + tx && bbox.x <= 50 + tx);
|
||||||
|
ok( bbox.y > 45 + ty && bbox.y <= 50 + ty);
|
||||||
|
ok( bbox.width >= 100 && bbox.width < 110);
|
||||||
|
ok( bbox.height >= 100 && bbox.height < 110);
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test("Test getStrokedBBox with stroke-width 10", function() {
|
||||||
|
var getStrokedBBox = svgedit.utilities.getStrokedBBox
|
||||||
|
|
||||||
|
var bbox;
|
||||||
|
var strokeWidth = 10
|
||||||
|
var elem = mockCreateSVGElement({
|
||||||
|
'element': 'path',
|
||||||
|
'attr': { 'id': 'path', 'd': 'M0,1 L2,3', 'stroke-width': strokeWidth}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem)
|
||||||
|
bbox = getStrokedBBox( [elem], mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
deepEqual(bbox, {"x": 0 - strokeWidth / 2, "y": 1 - strokeWidth / 2, "width": 2 + strokeWidth, "height": 2 + strokeWidth});
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'rect',
|
||||||
|
'attr': { 'id': 'rect', 'x': '0', 'y': '1', 'width': '5', 'height': '10', 'stroke-width': strokeWidth}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem);
|
||||||
|
bbox = getStrokedBBox( [elem], mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
deepEqual( bbox, { "x": 0 - strokeWidth / 2, "y": 1 - strokeWidth / 2, "width": 5 + strokeWidth, "height": 10 + strokeWidth});
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'line',
|
||||||
|
'attr': { 'id': 'line', 'x1': '0', 'y1': '1', 'x2': '5', 'y2': '6', 'stroke-width': strokeWidth}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem);
|
||||||
|
bbox = getStrokedBBox( [elem], mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
deepEqual( bbox, { "x": 0 - strokeWidth / 2, "y": 1 - strokeWidth / 2, "width": 5 + strokeWidth, "height": 5 + strokeWidth});
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'rect',
|
||||||
|
'attr': { 'id': 'rect', 'x': '0', 'y': '1', 'width': '5', 'height': '10', 'stroke-width': strokeWidth}
|
||||||
|
});
|
||||||
|
var g = mockCreateSVGElement({
|
||||||
|
'element': 'g',
|
||||||
|
'attr': {}
|
||||||
|
});
|
||||||
|
g.appendChild( elem);
|
||||||
|
svgroot.appendChild( g);
|
||||||
|
bbox = getStrokedBBox( [elem], mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
deepEqual( bbox, { "x": 0 - strokeWidth / 2, "y": 1 - strokeWidth / 2, "width": 5 + strokeWidth, "height": 10 + strokeWidth});
|
||||||
|
svgroot.removeChild( g);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test("Test getStrokedBBox with stroke-width 'none'", function() {
|
||||||
|
var getStrokedBBox = svgedit.utilities.getStrokedBBox
|
||||||
|
|
||||||
|
var bbox;
|
||||||
|
var elem = mockCreateSVGElement({
|
||||||
|
'element': 'path',
|
||||||
|
'attr': { 'id': 'path', 'd': 'M0,1 L2,3', 'stroke-width': 'none'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem)
|
||||||
|
bbox = getStrokedBBox( [elem], mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
deepEqual(bbox, {"x": 0, "y": 1, "width": 2, "height": 2});
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'rect',
|
||||||
|
'attr': { 'id': 'rect', 'x': '0', 'y': '1', 'width': '5', 'height': '10', 'stroke-width': 'none'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem);
|
||||||
|
bbox = getStrokedBBox( [elem], mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
deepEqual( bbox, { "x": 0, "y": 1, "width": 5, "height": 10});
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'line',
|
||||||
|
'attr': { 'id': 'line', 'x1': '0', 'y1': '1', 'x2': '5', 'y2': '6', 'stroke-width': 'none'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem);
|
||||||
|
bbox = getStrokedBBox( [elem], mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
deepEqual( bbox, { "x": 0, "y": 1, "width": 5, "height": 5});
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'rect',
|
||||||
|
'attr': { 'id': 'rect', 'x': '0', 'y': '1', 'width': '5', 'height': '10', 'stroke-width': 'none'}
|
||||||
|
});
|
||||||
|
var g = mockCreateSVGElement({
|
||||||
|
'element': 'g',
|
||||||
|
'attr': {}
|
||||||
|
});
|
||||||
|
g.appendChild( elem);
|
||||||
|
svgroot.appendChild( g);
|
||||||
|
bbox = getStrokedBBox( [elem], mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
deepEqual( bbox, { "x": 0, "y": 1, "width": 5, "height": 10});
|
||||||
|
svgroot.removeChild( g);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test("Test getStrokedBBox with no stroke-width attribute", function() {
|
||||||
|
var getStrokedBBox = svgedit.utilities.getStrokedBBox
|
||||||
|
|
||||||
|
var bbox;
|
||||||
|
var elem = mockCreateSVGElement({
|
||||||
|
'element': 'path',
|
||||||
|
'attr': { 'id': 'path', 'd': 'M0,1 L2,3'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem)
|
||||||
|
bbox = getStrokedBBox( [elem], mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
deepEqual(bbox, {"x": 0, "y": 1, "width": 2, "height": 2});
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'rect',
|
||||||
|
'attr': { 'id': 'rect', 'x': '0', 'y': '1', 'width': '5', 'height': '10'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem);
|
||||||
|
bbox = getStrokedBBox( [elem], mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
deepEqual( bbox, { "x": 0, "y": 1, "width": 5, "height": 10});
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'line',
|
||||||
|
'attr': { 'id': 'line', 'x1': '0', 'y1': '1', 'x2': '5', 'y2': '6'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem);
|
||||||
|
bbox = getStrokedBBox( [elem], mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
deepEqual( bbox, { "x": 0, "y": 1, "width": 5, "height": 5});
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'rect',
|
||||||
|
'attr': { 'id': 'rect', 'x': '0', 'y': '1', 'width': '5', 'height': '10'}
|
||||||
|
});
|
||||||
|
var g = mockCreateSVGElement({
|
||||||
|
'element': 'g',
|
||||||
|
'attr': {}
|
||||||
|
});
|
||||||
|
g.appendChild( elem);
|
||||||
|
svgroot.appendChild( g);
|
||||||
|
bbox = getStrokedBBox( [elem], mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
deepEqual( bbox, { "x": 0, "y": 1, "width": 5, "height": 10});
|
||||||
|
svgroot.removeChild( g);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function radians( degrees) {
|
||||||
|
return degrees * Math.PI / 180;
|
||||||
|
}
|
||||||
|
function rotatePoint( point, angle, origin) {
|
||||||
|
if( !origin)
|
||||||
|
origin = {x: 0, y: 0};
|
||||||
|
var x = point.x - origin.x;
|
||||||
|
var y = point.y - origin.y;
|
||||||
|
var theta = radians( angle);
|
||||||
|
return {
|
||||||
|
x: x * Math.cos(theta) + y * Math.sin(theta) + origin.x,
|
||||||
|
y: x * Math.sin(theta) + y * Math.cos(theta) + origin.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function rotateRect( rect, angle, origin) {
|
||||||
|
var tl = rotatePoint( { x: rect.x, y: rect.y}, angle, origin);
|
||||||
|
var tr = rotatePoint( { x: rect.x + rect.width, y: rect.y}, angle, origin);
|
||||||
|
var br = rotatePoint( { x: rect.x + rect.width, y: rect.y + rect.height}, angle, origin);
|
||||||
|
var bl = rotatePoint( { x: rect.x, y: rect.y + rect.height}, angle, origin);
|
||||||
|
|
||||||
|
var minx = Math.min(tl.x, tr.x, bl.x, br.x);
|
||||||
|
var maxx = Math.max(tl.x, tr.x, bl.x, br.x);
|
||||||
|
var miny = Math.min(tl.y, tr.y, bl.y, br.y);
|
||||||
|
var maxy = Math.max(tl.y, tr.y, bl.y, br.y);
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: minx,
|
||||||
|
y: miny,
|
||||||
|
width: (maxx - minx),
|
||||||
|
height: (maxy - miny)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 id='qunit-header'>Unit Tests for svgutils.js BBox functions</h1>
|
||||||
|
<h2 id='qunit-banner'></h2>
|
||||||
|
<h2 id='qunit-userAgent'></h2>
|
||||||
|
<ol id='qunit-tests'></ol>
|
||||||
|
<div id='sandbox'></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,247 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=Edge, chrome=1"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
|
||||||
|
<title>Performance Unit Tests for svgutils.js</title>
|
||||||
|
|
||||||
|
<link rel='stylesheet' href='qunit/qunit.css' type='text/css'/>
|
||||||
|
<script src='../editor/jquery.js'></script>
|
||||||
|
<script src='../editor/svgedit.js'></script>
|
||||||
|
<!-- svgutils.js depends on these three... mock out? -->
|
||||||
|
<script src='../editor/pathseg.js'></script>
|
||||||
|
<script src='../editor/browser.js'></script>
|
||||||
|
<script src='../editor/svgtransformlist.js'></script>
|
||||||
|
<script src='../editor/math.js'></script>
|
||||||
|
<script src='../editor/jquery-svg.js'></script> <!-- has $.attr() that takes an array . Used jby svgedit.utilities.getPathDFromElement -->
|
||||||
|
<script src='../editor/units.js'></script>
|
||||||
|
<script src='../editor/svgutils.js'></script>
|
||||||
|
<script src='qunit/qunit.js'></script>
|
||||||
|
<script>
|
||||||
|
$(function() {
|
||||||
|
// log function
|
||||||
|
QUnit.log = function(details) {
|
||||||
|
if (window.console && window.console.log) {
|
||||||
|
window.console.log(details.result +' :: '+ details.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var current_layer = document.getElementById('layer1');
|
||||||
|
|
||||||
|
|
||||||
|
function mockCreateSVGElement(jsonMap) {
|
||||||
|
var elem = document.createElementNS(svgedit.NS.SVG, jsonMap['element']);
|
||||||
|
for (var attr in jsonMap['attr']) {
|
||||||
|
elem.setAttribute(attr, jsonMap['attr'][attr]);
|
||||||
|
}
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
|
function mockAddSvgElementFromJson( json) {
|
||||||
|
var elem = mockCreateSVGElement( json)
|
||||||
|
current_layer.appendChild( elem)
|
||||||
|
return elem
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var svg = document.createElementNS(svgedit.NS.SVG, 'svg');
|
||||||
|
var groupWithMatrixTransform = document.getElementById('svg_group_with_matrix_transform');
|
||||||
|
var textWithMatrixTransform = document.getElementById('svg_text_with_matrix_transform');
|
||||||
|
|
||||||
|
function fillDocumentByCloningElement( elem, count) {
|
||||||
|
var elemId = elem.getAttribute( 'id') + '-'
|
||||||
|
for( var index = 0; index < count; index++) {
|
||||||
|
var clone = elem.cloneNode(true); // t: deep clone
|
||||||
|
// Make sure you set a unique ID like a real document.
|
||||||
|
clone.setAttribute( 'id', elemId + index)
|
||||||
|
var parent = elem.parentNode;
|
||||||
|
parent.appendChild(clone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module('svgedit.utilities_performance', {
|
||||||
|
setup: function() {
|
||||||
|
},
|
||||||
|
teardown: function() {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var mockPathActions = {
|
||||||
|
resetOrientation: function(path) {
|
||||||
|
if (path == null || path.nodeName != 'path') {return false;}
|
||||||
|
var tlist = svgedit.transformlist.getTransformList(path);
|
||||||
|
var m = svgedit.math.transformListToTransform(tlist).matrix;
|
||||||
|
tlist.clear();
|
||||||
|
path.removeAttribute('transform');
|
||||||
|
var segList = path.pathSegList;
|
||||||
|
|
||||||
|
var len = segList.numberOfItems;
|
||||||
|
var i, last_x, last_y;
|
||||||
|
|
||||||
|
for (i = 0; i < len; ++i) {
|
||||||
|
var seg = segList.getItem(i);
|
||||||
|
var type = seg.pathSegType;
|
||||||
|
if (type == 1) {continue;}
|
||||||
|
var pts = [];
|
||||||
|
$.each(['',1,2], function(j, n) {
|
||||||
|
var x = seg['x'+n], y = seg['y'+n];
|
||||||
|
if (x !== undefined && y !== undefined) {
|
||||||
|
var pt = svgedit.math.transformPoint(x, y, m);
|
||||||
|
pts.splice(pts.length, 0, pt.x, pt.y);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//svgedit.path.replacePathSeg(type, i, pts, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
//svgedit.utilities.reorientGrads(path, m);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
// Performance times with various browsers on Macbook 2011 8MB RAM OS X El Capitan 10.11.4
|
||||||
|
//
|
||||||
|
// To see 'Before Optimization' performance, making the following two edits.
|
||||||
|
// 1. svgedit.utilities.getStrokedBBox - change if( elems.length === 1) to if( false && elems.length === 1)
|
||||||
|
// 2. svgedit.utilities.getBBoxWithTransform - uncomment 'Old technique that was very slow'
|
||||||
|
|
||||||
|
// Chrome
|
||||||
|
// Before Optimization
|
||||||
|
// Pass1 svgCanvas.getStrokedBBox total ms 4,218, ave ms 41.0, min/max 37 51
|
||||||
|
// Pass2 svgCanvas.getStrokedBBox total ms 4,458, ave ms 43.3, min/max 32 63
|
||||||
|
// Optimized Code
|
||||||
|
// Pass1 svgCanvas.getStrokedBBox total ms 1,112, ave ms 10.8, min/max 9 20
|
||||||
|
// Pass2 svgCanvas.getStrokedBBox total ms 34, ave ms 0.3, min/max 0 20
|
||||||
|
|
||||||
|
// Firefox
|
||||||
|
// Before Optimization
|
||||||
|
// Pass1 svgCanvas.getStrokedBBox total ms 3,794, ave ms 36.8, min/max 33 48
|
||||||
|
// Pass2 svgCanvas.getStrokedBBox total ms 4,049, ave ms 39.3, min/max 28 53
|
||||||
|
// Optimized Code
|
||||||
|
// Pass1 svgCanvas.getStrokedBBox total ms 104, ave ms 1.0, min/max 0 23
|
||||||
|
// Pass2 svgCanvas.getStrokedBBox total ms 71, ave ms 0.7, min/max 0 23
|
||||||
|
|
||||||
|
// Safari
|
||||||
|
// Before Optimization
|
||||||
|
// Pass1 svgCanvas.getStrokedBBox total ms 4,840, ave ms 47.0, min/max 45 62
|
||||||
|
// Pass2 svgCanvas.getStrokedBBox total ms 4,849, ave ms 47.1, min/max 34 62
|
||||||
|
// Optimized Code
|
||||||
|
// Pass1 svgCanvas.getStrokedBBox total ms 42, ave ms 0.4, min/max 0 23
|
||||||
|
// Pass2 svgCanvas.getStrokedBBox total ms 17, ave ms 0.2, min/max 0 23
|
||||||
|
|
||||||
|
|
||||||
|
asyncTest('Test svgCanvas.getStrokedBBox() performance with matrix transforms', function( ) {
|
||||||
|
expect(2);
|
||||||
|
var getStrokedBBox = svgedit.utilities.getStrokedBBox;
|
||||||
|
var children = current_layer.children;
|
||||||
|
|
||||||
|
|
||||||
|
var obj, index, count, child, start, delta, lastTime, now, ave,
|
||||||
|
min = Number.MAX_VALUE,
|
||||||
|
max = 0,
|
||||||
|
total = 0;
|
||||||
|
|
||||||
|
fillDocumentByCloningElement( groupWithMatrixTransform, 50)
|
||||||
|
fillDocumentByCloningElement( textWithMatrixTransform, 50)
|
||||||
|
|
||||||
|
// The first pass through all elements is slower.
|
||||||
|
count = children.length;
|
||||||
|
start = lastTime = now = Date.now();
|
||||||
|
// Skip the first child which is the title.
|
||||||
|
for( index = 1; index< count; index++) {
|
||||||
|
child = children[index]
|
||||||
|
obj = getStrokedBBox([child], mockAddSvgElementFromJson, mockPathActions );
|
||||||
|
now = Date.now(); delta = now - lastTime; lastTime = now;
|
||||||
|
total += delta;
|
||||||
|
min = Math.min( min, delta);
|
||||||
|
max = Math.max( max, delta);
|
||||||
|
}
|
||||||
|
total = lastTime - start;
|
||||||
|
ave = total / count;
|
||||||
|
ok( ave < 20, 'svgedit.utilities.getStrokedBBox average execution time is less than 20 ms')
|
||||||
|
console.log( 'Pass1 svgCanvas.getStrokedBBox total ms ' + total + ', ave ms ' + ave.toFixed(1) + ', min/max ' + min + ' ' + max)
|
||||||
|
|
||||||
|
// The second pass is two to ten times faster.
|
||||||
|
setTimeout(function() {
|
||||||
|
count = children.length;
|
||||||
|
|
||||||
|
start = lastTime = now = Date.now();
|
||||||
|
// Skip the first child which is the title.
|
||||||
|
for( index = 1; index< count; index++) {
|
||||||
|
child = children[index]
|
||||||
|
obj = getStrokedBBox([child], mockAddSvgElementFromJson, mockPathActions );
|
||||||
|
now = Date.now(); delta = now - lastTime; lastTime = now;
|
||||||
|
total += delta;
|
||||||
|
min = Math.min( min, delta);
|
||||||
|
max = Math.max( max, delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
total = lastTime - start;
|
||||||
|
ave = total / count;
|
||||||
|
ok( ave < 2, 'svgedit.utilities.getStrokedBBox average execution time is less than 1 ms')
|
||||||
|
console.log( 'Pass2 svgCanvas.getStrokedBBox total ms ' + total + ', ave ms ' + ave.toFixed(1) + ', min/max ' + min + ' ' + max)
|
||||||
|
|
||||||
|
QUnit.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 id='qunit-header'>Performance Unit Tests for svgutils.js</h1>
|
||||||
|
<h2 id='qunit-banner'></h2>
|
||||||
|
<h2 id='qunit-userAgent'></h2>
|
||||||
|
<ol id='qunit-tests'></ol>
|
||||||
|
|
||||||
|
<div id="svg_editor">
|
||||||
|
<div id="workarea" style="cursor: auto; overflow: scroll; line-height: 12px; right: 100px;">
|
||||||
|
|
||||||
|
<!-- Must include this thumbnail view to see some of the performance issues -->
|
||||||
|
<svg id="overviewMiniView" width="150" height="112.5" x="0" y="0" viewBox="100 100 1000 1000" style="float: right;"
|
||||||
|
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<use x="0" y="0" xlink:href="#svgroot"></use>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
<style id="styleoverrides" type="text/css" media="screen" scoped="scoped">#svgcanvas svg *{cursor:move;pointer-events:all}, #svgcanvas svg{cursor:default}</style>
|
||||||
|
<div id="svgcanvas" style="position: relative; width: 1000px; height: 1000px;">
|
||||||
|
<svg id="svgroot" xmlns="http://www.w3.org/2000/svg" xlinkns="http://www.w3.org/1999/xlink" width="1000" height="1000" x="640" y="480" overflow="visible">
|
||||||
|
<defs><filter id="canvashadow" filterUnits="objectBoundingBox"><feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur"></feGaussianBlur><feOffset in="blur" dx="5" dy="5" result="offsetBlur"></feOffset><feMerge><feMergeNode in="offsetBlur"></feMergeNode><feMergeNode in="SourceGraphic"></feMergeNode></feMerge></filter><pattern id="gridpattern" patternUnits="userSpaceOnUse" x="0" y="0" width="100" height="100"><image x="0" y="0" width="100" height="100"></image></pattern></defs>
|
||||||
|
<svg id="canvasBackground" width="1000" height="200" x="10" y="10" overflow="none" style="pointer-events:none"><rect width="100%" height="100%" x="0" y="0" stroke="#000" fill="#000" style="pointer-events:none"></rect><svg id="canvasGrid" width="100%" height="100%" x="0" y="0" overflow="visible" display="none" style="display: inline;"><rect width="100%" height="100%" x="0" y="0" stroke-width="0" stroke="none" fill="url(#gridpattern)" style="pointer-events: none; display:visible;"></rect></svg></svg>
|
||||||
|
<animate attributeName="opacity" begin="indefinite" dur="1" fill="freeze"></animate>
|
||||||
|
|
||||||
|
<svg id="svgcontent" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1000 480" overflow="visible" width="1000" height="200" x="100" y="20">
|
||||||
|
|
||||||
|
<g id="layer1">
|
||||||
|
<title>Layer 1</title>
|
||||||
|
|
||||||
|
<g id="svg_group_with_matrix_transform" transform="matrix(0.5, 0, 0, 0.5, 10, 10)">
|
||||||
|
<svg id="svg_2" x="100" y="0" class="symbol" preserveAspectRatio="xMaxYMax">
|
||||||
|
<g id="svg_3">
|
||||||
|
<rect id="svg_4" x="0" y="0" width="20" height="20" fill="#00FF00"></rect>
|
||||||
|
</g>
|
||||||
|
<g id="svg_5" display="none">
|
||||||
|
<rect id="svg_6" x="0" y="0" width="20" height="20" fill="#A40000"></rect>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</g>
|
||||||
|
<text id="svg_text_with_matrix_transform" transform="matrix(0.433735, 0, 0, 0.433735, 2, 4)" xml:space="preserve" text-anchor="middle" font-family="serif" font-size="24" y="0" x="61" stroke="#999999" fill="#999999">Some text</text>
|
||||||
|
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<title>Layer 2</title>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -10,6 +10,7 @@
|
||||||
<script src='../editor/pathseg.js'></script>
|
<script src='../editor/pathseg.js'></script>
|
||||||
<script src='../editor/browser.js'></script>
|
<script src='../editor/browser.js'></script>
|
||||||
<script src='../editor/svgtransformlist.js'></script>
|
<script src='../editor/svgtransformlist.js'></script>
|
||||||
|
<script src='../editor/jquery-svg.js'></script> <!-- has $.attr() that takes an array . Used by svgedit.utilities.getPathDFromElement -->
|
||||||
<script src='../editor/svgutils.js'></script>
|
<script src='../editor/svgutils.js'></script>
|
||||||
<script src='qunit/qunit.js'></script>
|
<script src='qunit/qunit.js'></script>
|
||||||
<script>
|
<script>
|
||||||
|
@ -21,9 +22,64 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var svg = document.createElementNS(svgedit.NS.SVG, 'svg');
|
function mockCreateSVGElement(jsonMap) {
|
||||||
|
var elem = document.createElementNS(svgedit.NS.SVG, jsonMap['element']);
|
||||||
|
for (var attr in jsonMap['attr']) {
|
||||||
|
elem.setAttribute(attr, jsonMap['attr'][attr]);
|
||||||
|
}
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
|
function mockAddSvgElementFromJson( json) {
|
||||||
|
var elem = mockCreateSVGElement( json)
|
||||||
|
svgroot.appendChild( elem)
|
||||||
|
return elem
|
||||||
|
}
|
||||||
|
var mockPathActions = { resetOrientation: function () {}}
|
||||||
|
var mockHistorySubCommands = []
|
||||||
|
var mockHistory = {
|
||||||
|
BatchCommand: function() {
|
||||||
|
return {
|
||||||
|
addSubCommand: function( cmd) { mockHistorySubCommands.push(cmd)}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RemoveElementCommand: function(elem, nextSibling, parent) {
|
||||||
|
this.elem = elem;
|
||||||
|
this.nextSibling = nextSibling;
|
||||||
|
this.parent = parent
|
||||||
|
},
|
||||||
|
InsertElementCommand: function(path){
|
||||||
|
this.path = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var mockCount = {
|
||||||
|
clearSelection: 0,
|
||||||
|
addToSelection: 0,
|
||||||
|
addCommandToHistory: 0
|
||||||
|
}
|
||||||
|
function mockClearSelection() { mockCount.clearSelection ++}
|
||||||
|
function mockAddToSelection() { mockCount.addToSelection ++}
|
||||||
|
function mockAddCommandToHistory() {mockCount.addCommandToHistory ++}
|
||||||
|
|
||||||
|
var svg = document.createElementNS(svgedit.NS.SVG, 'svg');
|
||||||
|
var sandbox = document.getElementById('sandbox');
|
||||||
|
var svgroot = mockCreateSVGElement({
|
||||||
|
'element': 'svg',
|
||||||
|
'attr': {'id': 'svgroot'}
|
||||||
|
});
|
||||||
|
sandbox.appendChild(svgroot);
|
||||||
|
|
||||||
|
|
||||||
|
module('svgedit.utilities', {
|
||||||
|
setup: function() {
|
||||||
|
mockHistorySubCommands = [];
|
||||||
|
mockCount.clearSelection = 0;
|
||||||
|
mockCount.addToSelection = 0;
|
||||||
|
mockCount.addCommandToHistory = 0;
|
||||||
|
},
|
||||||
|
teardown: function() {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
module('svgedit.utilities');
|
|
||||||
|
|
||||||
test('Test svgedit.utilities package', function() {
|
test('Test svgedit.utilities package', function() {
|
||||||
expect(3);
|
expect(3);
|
||||||
|
@ -82,7 +138,7 @@
|
||||||
|
|
||||||
var convert = svgedit.utilities.convertToXMLReferences;
|
var convert = svgedit.utilities.convertToXMLReferences;
|
||||||
equals(convert('ABC'), 'ABC');
|
equals(convert('ABC'), 'ABC');
|
||||||
// equals(convert('ÀBC'), 'ÀBC');
|
// equals(convert('<EFBFBD>BC'), 'ÀBC');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Test svgedit.utilities.bboxToObj() function', function() {
|
test('Test svgedit.utilities.bboxToObj() function', function() {
|
||||||
|
@ -123,6 +179,181 @@
|
||||||
ok(bb.y && !isNaN(bb.y));
|
ok(bb.y && !isNaN(bb.y));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Test getPathDFromSegments", function() {
|
||||||
|
var d;
|
||||||
|
var getPathDFromSegments = svgedit.utilities.getPathDFromSegments;
|
||||||
|
|
||||||
|
var doc = svgedit.utilities.text2xml('<svg></svg>');
|
||||||
|
var path = doc.createElementNS(svgedit.NS.SVG, 'path');
|
||||||
|
path.setAttributeNS(null, 'd', 'm0,0l5,0l0,5l-5,0l0,-5z');
|
||||||
|
d = getPathDFromSegments( [
|
||||||
|
['M', [1, 2]],
|
||||||
|
['Z', []],
|
||||||
|
]);
|
||||||
|
equal( d, 'M1,2 Z');
|
||||||
|
|
||||||
|
d = getPathDFromSegments( [
|
||||||
|
['M', [1, 2]],
|
||||||
|
['M', [3, 4]],
|
||||||
|
['Z', []],
|
||||||
|
]);
|
||||||
|
equal( d, 'M1,2 M3,4 Z');
|
||||||
|
|
||||||
|
d = getPathDFromSegments( [
|
||||||
|
['M', [1, 2]],
|
||||||
|
['C', [3, 4, 5, 6]],
|
||||||
|
['Z', []],
|
||||||
|
]);
|
||||||
|
equal( d, 'M1,2 C3,4 5,6 Z');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test getPathDFromElement", function() {
|
||||||
|
var getPathDFromElement = svgedit.utilities.getPathDFromElement;
|
||||||
|
|
||||||
|
var elem = mockCreateSVGElement({
|
||||||
|
'element': 'path',
|
||||||
|
'attr': { 'id': 'path', 'd': 'M0,1 Z'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem)
|
||||||
|
equal( getPathDFromElement( elem), 'M0,1 Z');
|
||||||
|
svgroot.removeChild( elem)
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'rect',
|
||||||
|
'attr': { 'id': 'rect', 'x': '0', 'y': '1', 'width': '5', 'height': '10'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem)
|
||||||
|
equal( getPathDFromElement( elem), 'M0,1 L5,1 L5,11 L0,11 L0,1 Z');
|
||||||
|
svgroot.removeChild( elem)
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'rect',
|
||||||
|
'attr': { 'id': 'roundrect', 'x': '0', 'y': '1', 'rx': '2', 'ry': '3', 'width': '10', 'height': '11'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem)
|
||||||
|
var closeEnough = new RegExp( 'M0,4 C0,2.3[0-9]* 0.9[0-9]*,1 2,1 L8,1 C9.0[0-9]*,1 10,2.3[0-9]* 10,4 L10,9 C10,10.6[0-9]* 9.08675799086758,12 8,12 L2,12 C0.9[0-9]*,12 0,10.6[0-9]* 0,9 L0,4 Z')
|
||||||
|
equal(closeEnough.test(getPathDFromElement( elem)), true);
|
||||||
|
svgroot.removeChild( elem)
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'line',
|
||||||
|
'attr': { 'id': 'line', 'x1': '0', 'y1': '1', 'x2': '5', 'y2': '6'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem)
|
||||||
|
equal( getPathDFromElement( elem), 'M0,1L5,6');
|
||||||
|
svgroot.removeChild( elem)
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'circle',
|
||||||
|
'attr': { 'id': 'circle', 'cx': '10', 'cy': '11', 'rx': '5', 'ry': '10'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem)
|
||||||
|
equal( getPathDFromElement( elem), 'M10,11 C10,11 10,11 10,11 C10,11 10,11 10,11 C10,11 10,11 10,11 C10,11 10,11 10,11 Z');
|
||||||
|
svgroot.removeChild( elem)
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'polyline',
|
||||||
|
'attr': { 'id': 'polyline', 'points': '0,1 5,1 5,11 0,11'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem)
|
||||||
|
equal( getPathDFromElement( elem), 'M0,1 5,1 5,11 0,11');
|
||||||
|
svgroot.removeChild( elem)
|
||||||
|
|
||||||
|
equal( getPathDFromElement( {tagName: 'something unknown'}), undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test getBBoxOfElementAsPath", function() {
|
||||||
|
function getBBoxOfElementAsPath( elem, addSvgElementFromJson, pathActions) {
|
||||||
|
var bbox = svgedit.utilities.getBBoxOfElementAsPath(elem, addSvgElementFromJson, pathActions);
|
||||||
|
return svgedit.utilities.bboxToObj( bbox) // need this for QUnit equal() to work.
|
||||||
|
}
|
||||||
|
|
||||||
|
var bbox;
|
||||||
|
var elem = mockCreateSVGElement({
|
||||||
|
'element': 'path',
|
||||||
|
'attr': { 'id': 'path', 'd': 'M0,1 Z'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem)
|
||||||
|
bbox = getBBoxOfElementAsPath(elem, mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
deepEqual(bbox, {"x": 0, "y": 1, "width": 0, "height": 0 });
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'rect',
|
||||||
|
'attr': { 'id': 'rect', 'x': '0', 'y': '1', 'width': '5', 'height': '10'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem);
|
||||||
|
bbox = getBBoxOfElementAsPath( elem, mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
deepEqual( bbox, { "x": 0, "y": 1, "width": 5, "height": 10});
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
elem = mockCreateSVGElement({
|
||||||
|
'element': 'line',
|
||||||
|
'attr': { 'id': 'line', 'x1': '0', 'y1': '1', 'x2': '5', 'y2': '6'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem);
|
||||||
|
bbox = getBBoxOfElementAsPath( elem, mockAddSvgElementFromJson, mockPathActions)
|
||||||
|
deepEqual( bbox, { "x": 0, "y": 1, "width": 5, "height": 5});
|
||||||
|
svgroot.removeChild( elem);
|
||||||
|
|
||||||
|
// TODO: test element with transform. Need resetOrientation above to be working or mock it.
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test convertToPath rect", function() {
|
||||||
|
var convertToPath = svgedit.utilities.convertToPath;
|
||||||
|
var attrs = {
|
||||||
|
'fill': 'red',
|
||||||
|
'stroke': 'white',
|
||||||
|
'stroke-width': '1',
|
||||||
|
'visibility':'hidden'
|
||||||
|
};
|
||||||
|
|
||||||
|
var elem = mockCreateSVGElement({
|
||||||
|
'element': 'rect',
|
||||||
|
'attr': { 'id': 'rect', 'x': '0', 'y': '1', 'width': '5', 'height': '10'}
|
||||||
|
});
|
||||||
|
svgroot.appendChild( elem)
|
||||||
|
var path = convertToPath( elem, attrs, mockAddSvgElementFromJson, mockPathActions, mockClearSelection, mockAddToSelection, mockHistory, mockAddCommandToHistory);
|
||||||
|
equal( path.getAttribute('d'), 'M0,1 L5,1 L5,11 L0,11 L0,1 Z');
|
||||||
|
equal( path.getAttribute('visibilituy'), null);
|
||||||
|
equal( path.id, 'rect');
|
||||||
|
equal( path.parentNode, svgroot);
|
||||||
|
equal( elem.parentNode, null);
|
||||||
|
equal( mockHistorySubCommands.length, 2);
|
||||||
|
equal( mockCount.clearSelection, 1);
|
||||||
|
equal( mockCount.addToSelection, 1);
|
||||||
|
equal( mockCount.addCommandToHistory, 1);
|
||||||
|
svgroot.removeChild( path);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test("Test convertToPath unknown element", function() {
|
||||||
|
var convertToPath = svgedit.utilities.convertToPath;
|
||||||
|
var attrs = {
|
||||||
|
'fill': 'red',
|
||||||
|
'stroke': 'white',
|
||||||
|
'stroke-width': '1',
|
||||||
|
'visibility':'hidden'
|
||||||
|
};
|
||||||
|
|
||||||
|
var elem = {
|
||||||
|
tagName: 'something unknown',
|
||||||
|
id: 'something-unknown',
|
||||||
|
getAttribute: function( attr) { return '';},
|
||||||
|
parentNode: svgroot
|
||||||
|
};
|
||||||
|
var path = convertToPath( elem, attrs, mockAddSvgElementFromJson, mockPathActions, mockClearSelection, mockAddToSelection, mockHistory, mockAddCommandToHistory);
|
||||||
|
equal( path, null);
|
||||||
|
equal( elem.parentNode, svgroot);
|
||||||
|
equal( mockHistorySubCommands.length, 0);
|
||||||
|
equal( mockCount.clearSelection, 0);
|
||||||
|
equal( mockCount.addToSelection, 0);
|
||||||
|
equal( mockCount.addCommandToHistory, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
@ -131,5 +362,6 @@
|
||||||
<h2 id='qunit-banner'></h2>
|
<h2 id='qunit-banner'></h2>
|
||||||
<h2 id='qunit-userAgent'></h2>
|
<h2 id='qunit-userAgent'></h2>
|
||||||
<ol id='qunit-tests'></ol>
|
<ol id='qunit-tests'></ol>
|
||||||
|
<div id='sandbox'></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in New Issue