The SVGPathSeg API is being removed from the spec [1] and is being removed in Chromium 47 [2]. I implemented a drop-in polyfill[3] so svg-edit users are not broken as browsers migrate away from the path seg api. This patch simply imports the upstream pathseg.js and updates all dependencies. With this change all tests pass in Chrome 46 (with the path seg api), Chrome 47 (without the path seg api), and there are no changes to tests in Safari 9.01 or Firefox 43. I also manually tested svg-edit while developing the polyfill and could not find any broken features. [1] https://lists.w3.org/Archives/Public/www-svg/2015Jun/0044.html [2] https://groups.google.com/a/chromium.org/d/msg/blink-dev/EDC3cBg9mCU/OvElJgOWCgAJ [3] https://github.com/progers/pathseg
821 lines
28 KiB
JavaScript
821 lines
28 KiB
JavaScript
/*globals $*/
|
|
/*jslint vars: true, eqeq: true, continue: true*/
|
|
/**
|
|
* Recalculate.
|
|
*
|
|
* Licensed under the MIT License
|
|
*
|
|
*/
|
|
|
|
// Dependencies:
|
|
// 1) jquery
|
|
// 2) jquery-svg.js
|
|
// 3) svgedit.js
|
|
// 4) pathseg.js
|
|
// 5) browser.js
|
|
// 6) math.js
|
|
// 7) history.js
|
|
// 8) units.js
|
|
// 9) svgtransformlist.js
|
|
// 10) svgutils.js
|
|
// 11) coords.js
|
|
|
|
var svgedit = svgedit || {};
|
|
|
|
(function() {
|
|
|
|
if (!svgedit.recalculate) {
|
|
svgedit.recalculate = {};
|
|
}
|
|
|
|
var NS = svgedit.NS;
|
|
var context_;
|
|
|
|
// Function: svgedit.recalculate.init
|
|
svgedit.recalculate.init = function(editorContext) {
|
|
context_ = editorContext;
|
|
};
|
|
|
|
|
|
// Function: svgedit.recalculate.updateClipPath
|
|
// Updates a <clipPath>s values based on the given translation of an element
|
|
//
|
|
// Parameters:
|
|
// attr - The clip-path attribute value with the clipPath's ID
|
|
// tx - The translation's x value
|
|
// ty - The translation's y value
|
|
svgedit.recalculate.updateClipPath = function(attr, tx, ty) {
|
|
var path = getRefElem(attr).firstChild;
|
|
var cp_xform = svgedit.transformlist.getTransformList(path);
|
|
var newxlate = context_.getSVGRoot().createSVGTransform();
|
|
newxlate.setTranslate(tx, ty);
|
|
|
|
cp_xform.appendItem(newxlate);
|
|
|
|
// Update clipPath's dimensions
|
|
svgedit.recalculate.recalculateDimensions(path);
|
|
};
|
|
|
|
|
|
// Function: svgedit.recalculate.recalculateDimensions
|
|
// Decides the course of action based on the element's transform list
|
|
//
|
|
// Parameters:
|
|
// selected - The DOM element to recalculate
|
|
//
|
|
// Returns:
|
|
// Undo command object with the resulting change
|
|
svgedit.recalculate.recalculateDimensions = function(selected) {
|
|
if (selected == null) {return null;}
|
|
|
|
// Firefox Issue - 1081
|
|
if (selected.nodeName == "svg" && navigator.userAgent.indexOf("Firefox/20") >= 0) {
|
|
return null;
|
|
}
|
|
|
|
var svgroot = context_.getSVGRoot();
|
|
var tlist = svgedit.transformlist.getTransformList(selected);
|
|
var k;
|
|
// remove any unnecessary transforms
|
|
if (tlist && tlist.numberOfItems > 0) {
|
|
k = tlist.numberOfItems;
|
|
while (k--) {
|
|
var xform = tlist.getItem(k);
|
|
if (xform.type === 0) {
|
|
tlist.removeItem(k);
|
|
}
|
|
// remove identity matrices
|
|
else if (xform.type === 1) {
|
|
if (svgedit.math.isIdentity(xform.matrix)) {
|
|
tlist.removeItem(k);
|
|
}
|
|
}
|
|
// remove zero-degree rotations
|
|
else if (xform.type === 4) {
|
|
if (xform.angle === 0) {
|
|
tlist.removeItem(k);
|
|
}
|
|
}
|
|
}
|
|
// End here if all it has is a rotation
|
|
if (tlist.numberOfItems === 1 &&
|
|
svgedit.utilities.getRotationAngle(selected)) {return null;}
|
|
}
|
|
|
|
// if this element had no transforms, we are done
|
|
if (!tlist || tlist.numberOfItems == 0) {
|
|
// Chrome has a bug that requires clearing the attribute first.
|
|
selected.setAttribute('transform', '');
|
|
selected.removeAttribute('transform');
|
|
return null;
|
|
}
|
|
|
|
// TODO: Make this work for more than 2
|
|
if (tlist) {
|
|
k = tlist.numberOfItems;
|
|
var mxs = [];
|
|
while (k--) {
|
|
var xform = tlist.getItem(k);
|
|
if (xform.type === 1) {
|
|
mxs.push([xform.matrix, k]);
|
|
} else if (mxs.length) {
|
|
mxs = [];
|
|
}
|
|
}
|
|
if (mxs.length === 2) {
|
|
var m_new = svgroot.createSVGTransformFromMatrix(svgedit.math.matrixMultiply(mxs[1][0], mxs[0][0]));
|
|
tlist.removeItem(mxs[0][1]);
|
|
tlist.removeItem(mxs[1][1]);
|
|
tlist.insertItemBefore(m_new, mxs[1][1]);
|
|
}
|
|
|
|
// combine matrix + translate
|
|
k = tlist.numberOfItems;
|
|
if (k >= 2 && tlist.getItem(k-2).type === 1 && tlist.getItem(k-1).type === 2) {
|
|
var mt = svgroot.createSVGTransform();
|
|
|
|
var m = svgedit.math.matrixMultiply(
|
|
tlist.getItem(k-2).matrix,
|
|
tlist.getItem(k-1).matrix);
|
|
mt.setMatrix(m);
|
|
tlist.removeItem(k-2);
|
|
tlist.removeItem(k-2);
|
|
tlist.appendItem(mt);
|
|
}
|
|
}
|
|
|
|
// If it still has a single [M] or [R][M], return null too (prevents BatchCommand from being returned).
|
|
switch ( selected.tagName ) {
|
|
// Ignore these elements, as they can absorb the [M]
|
|
case 'line':
|
|
case 'polyline':
|
|
case 'polygon':
|
|
case 'path':
|
|
break;
|
|
default:
|
|
if ((tlist.numberOfItems === 1 && tlist.getItem(0).type === 1) ||
|
|
(tlist.numberOfItems === 2 && tlist.getItem(0).type === 1 && tlist.getItem(0).type === 4)) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Grouped SVG element
|
|
var gsvg = $(selected).data('gsvg');
|
|
|
|
// we know we have some transforms, so set up return variable
|
|
var batchCmd = new svgedit.history.BatchCommand('Transform');
|
|
|
|
// store initial values that will be affected by reducing the transform list
|
|
var changes = {}, initial = null, attrs = [];
|
|
switch (selected.tagName) {
|
|
case 'line':
|
|
attrs = ['x1', 'y1', 'x2', 'y2'];
|
|
break;
|
|
case 'circle':
|
|
attrs = ['cx', 'cy', 'r'];
|
|
break;
|
|
case 'ellipse':
|
|
attrs = ['cx', 'cy', 'rx', 'ry'];
|
|
break;
|
|
case 'foreignObject':
|
|
case 'rect':
|
|
case 'image':
|
|
attrs = ['width', 'height', 'x', 'y'];
|
|
break;
|
|
case 'use':
|
|
case 'text':
|
|
case 'tspan':
|
|
attrs = ['x', 'y'];
|
|
break;
|
|
case 'polygon':
|
|
case 'polyline':
|
|
initial = {};
|
|
initial.points = selected.getAttribute('points');
|
|
var list = selected.points;
|
|
var len = list.numberOfItems;
|
|
changes.points = new Array(len);
|
|
var i;
|
|
for (i = 0; i < len; ++i) {
|
|
var pt = list.getItem(i);
|
|
changes.points[i] = {x:pt.x, y:pt.y};
|
|
}
|
|
break;
|
|
case 'path':
|
|
initial = {};
|
|
initial.d = selected.getAttribute('d');
|
|
changes.d = selected.getAttribute('d');
|
|
break;
|
|
} // switch on element type to get initial values
|
|
|
|
if (attrs.length) {
|
|
changes = $(selected).attr(attrs);
|
|
$.each(changes, function(attr, val) {
|
|
changes[attr] = svgedit.units.convertToNum(attr, val);
|
|
});
|
|
} else if (gsvg) {
|
|
// GSVG exception
|
|
changes = {
|
|
x: $(gsvg).attr('x') || 0,
|
|
y: $(gsvg).attr('y') || 0
|
|
};
|
|
}
|
|
|
|
// if we haven't created an initial array in polygon/polyline/path, then
|
|
// make a copy of initial values and include the transform
|
|
if (initial == null) {
|
|
initial = $.extend(true, {}, changes);
|
|
$.each(initial, function(attr, val) {
|
|
initial[attr] = svgedit.units.convertToNum(attr, val);
|
|
});
|
|
}
|
|
// save the start transform value too
|
|
initial.transform = context_.getStartTransform() || '';
|
|
|
|
// if it's a regular group, we have special processing to flatten transforms
|
|
if ((selected.tagName == 'g' && !gsvg) || selected.tagName == 'a') {
|
|
var box = svgedit.utilities.getBBox(selected),
|
|
oldcenter = {x: box.x+box.width/2, y: box.y+box.height/2},
|
|
newcenter = svgedit.math.transformPoint(box.x+box.width/2,
|
|
box.y+box.height/2,
|
|
svgedit.math.transformListToTransform(tlist).matrix),
|
|
m = svgroot.createSVGMatrix();
|
|
|
|
// temporarily strip off the rotate and save the old center
|
|
var gangle = svgedit.utilities.getRotationAngle(selected);
|
|
if (gangle) {
|
|
var a = gangle * Math.PI / 180;
|
|
if ( Math.abs(a) > (1.0e-10) ) {
|
|
var s = Math.sin(a)/(1 - Math.cos(a));
|
|
} else {
|
|
// FIXME: This blows up if the angle is exactly 0!
|
|
var s = 2/a;
|
|
}
|
|
var i;
|
|
for (i = 0; i < tlist.numberOfItems; ++i) {
|
|
var xform = tlist.getItem(i);
|
|
if (xform.type == 4) {
|
|
// extract old center through mystical arts
|
|
var rm = xform.matrix;
|
|
oldcenter.y = (s*rm.e + rm.f)/2;
|
|
oldcenter.x = (rm.e - s*rm.f)/2;
|
|
tlist.removeItem(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
var tx = 0, ty = 0,
|
|
operation = 0,
|
|
N = tlist.numberOfItems;
|
|
|
|
if (N) {
|
|
var first_m = tlist.getItem(0).matrix;
|
|
}
|
|
|
|
// first, if it was a scale then the second-last transform will be it
|
|
if (N >= 3 && tlist.getItem(N-2).type == 3 &&
|
|
tlist.getItem(N-3).type == 2 && tlist.getItem(N-1).type == 2)
|
|
{
|
|
operation = 3; // scale
|
|
|
|
// if the children are unrotated, pass the scale down directly
|
|
// otherwise pass the equivalent matrix() down directly
|
|
var tm = tlist.getItem(N-3).matrix,
|
|
sm = tlist.getItem(N-2).matrix,
|
|
tmn = tlist.getItem(N-1).matrix;
|
|
|
|
var children = selected.childNodes;
|
|
var c = children.length;
|
|
while (c--) {
|
|
var child = children.item(c);
|
|
tx = 0;
|
|
ty = 0;
|
|
if (child.nodeType == 1) {
|
|
var childTlist = svgedit.transformlist.getTransformList(child);
|
|
|
|
// some children might not have a transform (<metadata>, <defs>, etc)
|
|
if (!childTlist) {continue;}
|
|
|
|
var m = svgedit.math.transformListToTransform(childTlist).matrix;
|
|
|
|
// Convert a matrix to a scale if applicable
|
|
// if (svgedit.math.hasMatrixTransform(childTlist) && childTlist.numberOfItems == 1) {
|
|
// if (m.b==0 && m.c==0 && m.e==0 && m.f==0) {
|
|
// childTlist.removeItem(0);
|
|
// var translateOrigin = svgroot.createSVGTransform(),
|
|
// scale = svgroot.createSVGTransform(),
|
|
// translateBack = svgroot.createSVGTransform();
|
|
// translateOrigin.setTranslate(0, 0);
|
|
// scale.setScale(m.a, m.d);
|
|
// translateBack.setTranslate(0, 0);
|
|
// childTlist.appendItem(translateBack);
|
|
// childTlist.appendItem(scale);
|
|
// childTlist.appendItem(translateOrigin);
|
|
// }
|
|
// }
|
|
|
|
var angle = svgedit.utilities.getRotationAngle(child);
|
|
var oldStartTransform = context_.getStartTransform();
|
|
var childxforms = [];
|
|
context_.setStartTransform(child.getAttribute('transform'));
|
|
if (angle || svgedit.math.hasMatrixTransform(childTlist)) {
|
|
var e2t = svgroot.createSVGTransform();
|
|
e2t.setMatrix(svgedit.math.matrixMultiply(tm, sm, tmn, m));
|
|
childTlist.clear();
|
|
childTlist.appendItem(e2t);
|
|
childxforms.push(e2t);
|
|
}
|
|
// if not rotated or skewed, push the [T][S][-T] down to the child
|
|
else {
|
|
// update the transform list with translate,scale,translate
|
|
|
|
// slide the [T][S][-T] from the front to the back
|
|
// [T][S][-T][M] = [M][T2][S2][-T2]
|
|
|
|
// (only bringing [-T] to the right of [M])
|
|
// [T][S][-T][M] = [T][S][M][-T2]
|
|
// [-T2] = [M_inv][-T][M]
|
|
var t2n = svgedit.math.matrixMultiply(m.inverse(), tmn, m);
|
|
// [T2] is always negative translation of [-T2]
|
|
var t2 = svgroot.createSVGMatrix();
|
|
t2.e = -t2n.e;
|
|
t2.f = -t2n.f;
|
|
|
|
// [T][S][-T][M] = [M][T2][S2][-T2]
|
|
// [S2] = [T2_inv][M_inv][T][S][-T][M][-T2_inv]
|
|
var s2 = svgedit.math.matrixMultiply(t2.inverse(), m.inverse(), tm, sm, tmn, m, t2n.inverse());
|
|
|
|
var translateOrigin = svgroot.createSVGTransform(),
|
|
scale = svgroot.createSVGTransform(),
|
|
translateBack = svgroot.createSVGTransform();
|
|
translateOrigin.setTranslate(t2n.e, t2n.f);
|
|
scale.setScale(s2.a, s2.d);
|
|
translateBack.setTranslate(t2.e, t2.f);
|
|
childTlist.appendItem(translateBack);
|
|
childTlist.appendItem(scale);
|
|
childTlist.appendItem(translateOrigin);
|
|
childxforms.push(translateBack);
|
|
childxforms.push(scale);
|
|
childxforms.push(translateOrigin);
|
|
// logMatrix(translateBack.matrix);
|
|
// logMatrix(scale.matrix);
|
|
} // not rotated
|
|
batchCmd.addSubCommand( svgedit.recalculate.recalculateDimensions(child) );
|
|
// TODO: If any <use> have this group as a parent and are
|
|
// referencing this child, then we need to impose a reverse
|
|
// scale on it so that when it won't get double-translated
|
|
// var uses = selected.getElementsByTagNameNS(NS.SVG, 'use');
|
|
// var href = '#' + child.id;
|
|
// var u = uses.length;
|
|
// while (u--) {
|
|
// var useElem = uses.item(u);
|
|
// if (href == svgedit.utilities.getHref(useElem)) {
|
|
// var usexlate = svgroot.createSVGTransform();
|
|
// usexlate.setTranslate(-tx,-ty);
|
|
// svgedit.transformlist.getTransformList(useElem).insertItemBefore(usexlate,0);
|
|
// batchCmd.addSubCommand( svgedit.recalculate.recalculateDimensions(useElem) );
|
|
// }
|
|
// }
|
|
context_.setStartTransform(oldStartTransform);
|
|
} // element
|
|
} // for each child
|
|
// Remove these transforms from group
|
|
tlist.removeItem(N-1);
|
|
tlist.removeItem(N-2);
|
|
tlist.removeItem(N-3);
|
|
} else if (N >= 3 && tlist.getItem(N-1).type == 1) {
|
|
operation = 3; // scale
|
|
m = svgedit.math.transformListToTransform(tlist).matrix;
|
|
var e2t = svgroot.createSVGTransform();
|
|
e2t.setMatrix(m);
|
|
tlist.clear();
|
|
tlist.appendItem(e2t);
|
|
}
|
|
// next, check if the first transform was a translate
|
|
// if we had [ T1 ] [ M ] we want to transform this into [ M ] [ T2 ]
|
|
// therefore [ T2 ] = [ M_inv ] [ T1 ] [ M ]
|
|
else if ( (N == 1 || (N > 1 && tlist.getItem(1).type != 3)) &&
|
|
tlist.getItem(0).type == 2)
|
|
{
|
|
operation = 2; // translate
|
|
var T_M = svgedit.math.transformListToTransform(tlist).matrix;
|
|
tlist.removeItem(0);
|
|
var M_inv = svgedit.math.transformListToTransform(tlist).matrix.inverse();
|
|
var M2 = svgedit.math.matrixMultiply( M_inv, T_M );
|
|
|
|
tx = M2.e;
|
|
ty = M2.f;
|
|
|
|
if (tx != 0 || ty != 0) {
|
|
// we pass the translates down to the individual children
|
|
var children = selected.childNodes;
|
|
var c = children.length;
|
|
|
|
var clipPaths_done = [];
|
|
|
|
while (c--) {
|
|
var child = children.item(c);
|
|
if (child.nodeType == 1) {
|
|
|
|
// Check if child has clip-path
|
|
if (child.getAttribute('clip-path')) {
|
|
// tx, ty
|
|
var attr = child.getAttribute('clip-path');
|
|
if (clipPaths_done.indexOf(attr) === -1) {
|
|
svgedit.recalculate.updateClipPath(attr, tx, ty);
|
|
clipPaths_done.push(attr);
|
|
}
|
|
}
|
|
|
|
var oldStartTransform = context_.getStartTransform();
|
|
context_.setStartTransform(child.getAttribute('transform'));
|
|
|
|
var childTlist = svgedit.transformlist.getTransformList(child);
|
|
// some children might not have a transform (<metadata>, <defs>, etc)
|
|
if (childTlist) {
|
|
var newxlate = svgroot.createSVGTransform();
|
|
newxlate.setTranslate(tx, ty);
|
|
if (childTlist.numberOfItems) {
|
|
childTlist.insertItemBefore(newxlate, 0);
|
|
} else {
|
|
childTlist.appendItem(newxlate);
|
|
}
|
|
batchCmd.addSubCommand(svgedit.recalculate.recalculateDimensions(child));
|
|
// If any <use> have this group as a parent and are
|
|
// referencing this child, then impose a reverse translate on it
|
|
// so that when it won't get double-translated
|
|
var uses = selected.getElementsByTagNameNS(NS.SVG, 'use');
|
|
var href = '#' + child.id;
|
|
var u = uses.length;
|
|
while (u--) {
|
|
var useElem = uses.item(u);
|
|
if (href == svgedit.utilities.getHref(useElem)) {
|
|
var usexlate = svgroot.createSVGTransform();
|
|
usexlate.setTranslate(-tx,-ty);
|
|
svgedit.transformlist.getTransformList(useElem).insertItemBefore(usexlate, 0);
|
|
batchCmd.addSubCommand( svgedit.recalculate.recalculateDimensions(useElem) );
|
|
}
|
|
}
|
|
context_.setStartTransform(oldStartTransform);
|
|
}
|
|
}
|
|
}
|
|
|
|
clipPaths_done = [];
|
|
context_.setStartTransform(oldStartTransform);
|
|
}
|
|
}
|
|
// else, a matrix imposition from a parent group
|
|
// keep pushing it down to the children
|
|
else if (N == 1 && tlist.getItem(0).type == 1 && !gangle) {
|
|
operation = 1;
|
|
var m = tlist.getItem(0).matrix,
|
|
children = selected.childNodes,
|
|
c = children.length;
|
|
while (c--) {
|
|
var child = children.item(c);
|
|
if (child.nodeType == 1) {
|
|
var oldStartTransform = context_.getStartTransform();
|
|
context_.setStartTransform(child.getAttribute('transform'));
|
|
var childTlist = svgedit.transformlist.getTransformList(child);
|
|
|
|
if (!childTlist) {continue;}
|
|
|
|
var em = svgedit.math.matrixMultiply(m, svgedit.math.transformListToTransform(childTlist).matrix);
|
|
var e2m = svgroot.createSVGTransform();
|
|
e2m.setMatrix(em);
|
|
childTlist.clear();
|
|
childTlist.appendItem(e2m, 0);
|
|
|
|
batchCmd.addSubCommand( svgedit.recalculate.recalculateDimensions(child) );
|
|
context_.setStartTransform(oldStartTransform);
|
|
|
|
// Convert stroke
|
|
// TODO: Find out if this should actually happen somewhere else
|
|
var sw = child.getAttribute('stroke-width');
|
|
if (child.getAttribute('stroke') !== 'none' && !isNaN(sw)) {
|
|
var avg = (Math.abs(em.a) + Math.abs(em.d)) / 2;
|
|
child.setAttribute('stroke-width', sw * avg);
|
|
}
|
|
|
|
}
|
|
}
|
|
tlist.clear();
|
|
}
|
|
// else it was just a rotate
|
|
else {
|
|
if (gangle) {
|
|
var newRot = svgroot.createSVGTransform();
|
|
newRot.setRotate(gangle, newcenter.x, newcenter.y);
|
|
if (tlist.numberOfItems) {
|
|
tlist.insertItemBefore(newRot, 0);
|
|
} else {
|
|
tlist.appendItem(newRot);
|
|
}
|
|
}
|
|
if (tlist.numberOfItems == 0) {
|
|
selected.removeAttribute('transform');
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// if it was a translate, put back the rotate at the new center
|
|
if (operation == 2) {
|
|
if (gangle) {
|
|
newcenter = {
|
|
x: oldcenter.x + first_m.e,
|
|
y: oldcenter.y + first_m.f
|
|
};
|
|
|
|
var newRot = svgroot.createSVGTransform();
|
|
newRot.setRotate(gangle, newcenter.x, newcenter.y);
|
|
if (tlist.numberOfItems) {
|
|
tlist.insertItemBefore(newRot, 0);
|
|
} else {
|
|
tlist.appendItem(newRot);
|
|
}
|
|
}
|
|
}
|
|
// if it was a resize
|
|
else if (operation == 3) {
|
|
var m = svgedit.math.transformListToTransform(tlist).matrix;
|
|
var roldt = svgroot.createSVGTransform();
|
|
roldt.setRotate(gangle, oldcenter.x, oldcenter.y);
|
|
var rold = roldt.matrix;
|
|
var rnew = svgroot.createSVGTransform();
|
|
rnew.setRotate(gangle, newcenter.x, newcenter.y);
|
|
var rnew_inv = rnew.matrix.inverse(),
|
|
m_inv = m.inverse(),
|
|
extrat = svgedit.math.matrixMultiply(m_inv, rnew_inv, rold, m);
|
|
|
|
tx = extrat.e;
|
|
ty = extrat.f;
|
|
|
|
if (tx != 0 || ty != 0) {
|
|
// now push this transform down to the children
|
|
// we pass the translates down to the individual children
|
|
var children = selected.childNodes;
|
|
var c = children.length;
|
|
while (c--) {
|
|
var child = children.item(c);
|
|
if (child.nodeType == 1) {
|
|
var oldStartTransform = context_.getStartTransform();
|
|
context_.setStartTransform(child.getAttribute('transform'));
|
|
var childTlist = svgedit.transformlist.getTransformList(child);
|
|
var newxlate = svgroot.createSVGTransform();
|
|
newxlate.setTranslate(tx, ty);
|
|
if (childTlist.numberOfItems) {
|
|
childTlist.insertItemBefore(newxlate, 0);
|
|
} else {
|
|
childTlist.appendItem(newxlate);
|
|
}
|
|
|
|
batchCmd.addSubCommand( svgedit.recalculate.recalculateDimensions(child) );
|
|
context_.setStartTransform(oldStartTransform);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (gangle) {
|
|
if (tlist.numberOfItems) {
|
|
tlist.insertItemBefore(rnew, 0);
|
|
} else {
|
|
tlist.appendItem(rnew);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// else, it's a non-group
|
|
else {
|
|
|
|
// FIXME: box might be null for some elements (<metadata> etc), need to handle this
|
|
var box = svgedit.utilities.getBBox(selected);
|
|
|
|
// Paths (and possbly other shapes) will have no BBox while still in <defs>,
|
|
// but we still may need to recalculate them (see issue 595).
|
|
// TODO: Figure out how to get BBox from these elements in case they
|
|
// have a rotation transform
|
|
|
|
if (!box && selected.tagName != 'path') return null;
|
|
|
|
|
|
var m = svgroot.createSVGMatrix(),
|
|
// temporarily strip off the rotate and save the old center
|
|
angle = svgedit.utilities.getRotationAngle(selected);
|
|
if (angle) {
|
|
var oldcenter = {x: box.x+box.width/2, y: box.y+box.height/2},
|
|
newcenter = svgedit.math.transformPoint(box.x+box.width/2, box.y+box.height/2,
|
|
svgedit.math.transformListToTransform(tlist).matrix);
|
|
|
|
var a = angle * Math.PI / 180;
|
|
if ( Math.abs(a) > (1.0e-10) ) {
|
|
var s = Math.sin(a)/(1 - Math.cos(a));
|
|
} else {
|
|
// FIXME: This blows up if the angle is exactly 0!
|
|
var s = 2/a;
|
|
}
|
|
for (var i = 0; i < tlist.numberOfItems; ++i) {
|
|
var xform = tlist.getItem(i);
|
|
if (xform.type == 4) {
|
|
// extract old center through mystical arts
|
|
var rm = xform.matrix;
|
|
oldcenter.y = (s*rm.e + rm.f)/2;
|
|
oldcenter.x = (rm.e - s*rm.f)/2;
|
|
tlist.removeItem(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2 = translate, 3 = scale, 4 = rotate, 1 = matrix imposition
|
|
var operation = 0;
|
|
var N = tlist.numberOfItems;
|
|
|
|
// Check if it has a gradient with userSpaceOnUse, in which case
|
|
// adjust it by recalculating the matrix transform.
|
|
// TODO: Make this work in Webkit using svgedit.transformlist.SVGTransformList
|
|
if (!svgedit.browser.isWebkit()) {
|
|
var fill = selected.getAttribute('fill');
|
|
if (fill && fill.indexOf('url(') === 0) {
|
|
var paint = getRefElem(fill);
|
|
var type = 'pattern';
|
|
if (paint.tagName !== type) type = 'gradient';
|
|
var attrVal = paint.getAttribute(type + 'Units');
|
|
if (attrVal === 'userSpaceOnUse') {
|
|
//Update the userSpaceOnUse element
|
|
m = svgedit.math.transformListToTransform(tlist).matrix;
|
|
var gtlist = svgedit.transformlist.getTransformList(paint);
|
|
var gmatrix = svgedit.math.transformListToTransform(gtlist).matrix;
|
|
m = svgedit.math.matrixMultiply(m, gmatrix);
|
|
var m_str = 'matrix(' + [m.a, m.b, m.c, m.d, m.e, m.f].join(',') + ')';
|
|
paint.setAttribute(type + 'Transform', m_str);
|
|
}
|
|
}
|
|
}
|
|
|
|
// first, if it was a scale of a non-skewed element, then the second-last
|
|
// transform will be the [S]
|
|
// if we had [M][T][S][T] we want to extract the matrix equivalent of
|
|
// [T][S][T] and push it down to the element
|
|
if (N >= 3 && tlist.getItem(N-2).type == 3 &&
|
|
tlist.getItem(N-3).type == 2 && tlist.getItem(N-1).type == 2)
|
|
|
|
// Removed this so a <use> with a given [T][S][T] would convert to a matrix.
|
|
// Is that bad?
|
|
// && selected.nodeName != 'use'
|
|
{
|
|
operation = 3; // scale
|
|
m = svgedit.math.transformListToTransform(tlist, N-3, N-1).matrix;
|
|
tlist.removeItem(N-1);
|
|
tlist.removeItem(N-2);
|
|
tlist.removeItem(N-3);
|
|
} // if we had [T][S][-T][M], then this was a skewed element being resized
|
|
// Thus, we simply combine it all into one matrix
|
|
else if (N == 4 && tlist.getItem(N-1).type == 1) {
|
|
operation = 3; // scale
|
|
m = svgedit.math.transformListToTransform(tlist).matrix;
|
|
var e2t = svgroot.createSVGTransform();
|
|
e2t.setMatrix(m);
|
|
tlist.clear();
|
|
tlist.appendItem(e2t);
|
|
// reset the matrix so that the element is not re-mapped
|
|
m = svgroot.createSVGMatrix();
|
|
} // if we had [R][T][S][-T][M], then this was a rotated matrix-element
|
|
// if we had [T1][M] we want to transform this into [M][T2]
|
|
// therefore [ T2 ] = [ M_inv ] [ T1 ] [ M ] and we can push [T2]
|
|
// down to the element
|
|
else if ( (N == 1 || (N > 1 && tlist.getItem(1).type != 3)) &&
|
|
tlist.getItem(0).type == 2)
|
|
{
|
|
operation = 2; // translate
|
|
var oldxlate = tlist.getItem(0).matrix,
|
|
meq = svgedit.math.transformListToTransform(tlist,1).matrix,
|
|
meq_inv = meq.inverse();
|
|
m = svgedit.math.matrixMultiply( meq_inv, oldxlate, meq );
|
|
tlist.removeItem(0);
|
|
}
|
|
// else if this child now has a matrix imposition (from a parent group)
|
|
// we might be able to simplify
|
|
else if (N == 1 && tlist.getItem(0).type == 1 && !angle) {
|
|
// Remap all point-based elements
|
|
m = svgedit.math.transformListToTransform(tlist).matrix;
|
|
switch (selected.tagName) {
|
|
case 'line':
|
|
changes = $(selected).attr(['x1', 'y1', 'x2', 'y2']);
|
|
case 'polyline':
|
|
case 'polygon':
|
|
changes.points = selected.getAttribute('points');
|
|
if (changes.points) {
|
|
var list = selected.points;
|
|
var len = list.numberOfItems;
|
|
changes.points = new Array(len);
|
|
for (var i = 0; i < len; ++i) {
|
|
var pt = list.getItem(i);
|
|
changes.points[i] = {x:pt.x, y:pt.y};
|
|
}
|
|
}
|
|
case 'path':
|
|
changes.d = selected.getAttribute('d');
|
|
operation = 1;
|
|
tlist.clear();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
// if it was a rotation, put the rotate back and return without a command
|
|
// (this function has zero work to do for a rotate())
|
|
else {
|
|
operation = 4; // rotation
|
|
if (angle) {
|
|
var newRot = svgroot.createSVGTransform();
|
|
newRot.setRotate(angle, newcenter.x, newcenter.y);
|
|
|
|
if (tlist.numberOfItems) {
|
|
tlist.insertItemBefore(newRot, 0);
|
|
} else {
|
|
tlist.appendItem(newRot);
|
|
}
|
|
}
|
|
if (tlist.numberOfItems == 0) {
|
|
selected.removeAttribute('transform');
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// if it was a translate or resize, we need to remap the element and absorb the xform
|
|
if (operation == 1 || operation == 2 || operation == 3) {
|
|
svgedit.coords.remapElement(selected, changes, m);
|
|
} // if we are remapping
|
|
|
|
// if it was a translate, put back the rotate at the new center
|
|
if (operation == 2) {
|
|
if (angle) {
|
|
if (!svgedit.math.hasMatrixTransform(tlist)) {
|
|
newcenter = {
|
|
x: oldcenter.x + m.e,
|
|
y: oldcenter.y + m.f
|
|
};
|
|
}
|
|
var newRot = svgroot.createSVGTransform();
|
|
newRot.setRotate(angle, newcenter.x, newcenter.y);
|
|
if (tlist.numberOfItems) {
|
|
tlist.insertItemBefore(newRot, 0);
|
|
} else {
|
|
tlist.appendItem(newRot);
|
|
}
|
|
}
|
|
// We have special processing for tspans: Tspans are not transformable
|
|
// but they can have x,y coordinates (sigh). Thus, if this was a translate,
|
|
// on a text element, also translate any tspan children.
|
|
if (selected.tagName == 'text') {
|
|
var children = selected.childNodes;
|
|
var c = children.length;
|
|
while (c--) {
|
|
var child = children.item(c);
|
|
if (child.tagName == 'tspan') {
|
|
var tspanChanges = {
|
|
x: $(child).attr('x') || 0,
|
|
y: $(child).attr('y') || 0
|
|
};
|
|
svgedit.coords.remapElement(child, tspanChanges, m);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// [Rold][M][T][S][-T] became [Rold][M]
|
|
// we want it to be [Rnew][M][Tr] where Tr is the
|
|
// translation required to re-center it
|
|
// Therefore, [Tr] = [M_inv][Rnew_inv][Rold][M]
|
|
else if (operation == 3 && angle) {
|
|
var m = svgedit.math.transformListToTransform(tlist).matrix;
|
|
var roldt = svgroot.createSVGTransform();
|
|
roldt.setRotate(angle, oldcenter.x, oldcenter.y);
|
|
var rold = roldt.matrix;
|
|
var rnew = svgroot.createSVGTransform();
|
|
rnew.setRotate(angle, newcenter.x, newcenter.y);
|
|
var rnew_inv = rnew.matrix.inverse();
|
|
var m_inv = m.inverse();
|
|
var extrat = svgedit.math.matrixMultiply(m_inv, rnew_inv, rold, m);
|
|
|
|
svgedit.coords.remapElement(selected, changes, extrat);
|
|
if (angle) {
|
|
if (tlist.numberOfItems) {
|
|
tlist.insertItemBefore(rnew, 0);
|
|
} else {
|
|
tlist.appendItem(rnew);
|
|
}
|
|
}
|
|
}
|
|
} // a non-group
|
|
|
|
// if the transform list has been emptied, remove it
|
|
if (tlist.numberOfItems == 0) {
|
|
selected.removeAttribute('transform');
|
|
}
|
|
|
|
batchCmd.addSubCommand(new svgedit.history.ChangeElementCommand(selected, initial));
|
|
|
|
return batchCmd;
|
|
};
|
|
})();
|