recalculate.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  1. /*globals $*/
  2. /*jslint vars: true, eqeq: true, continue: true*/
  3. /**
  4. * Recalculate.
  5. *
  6. * Licensed under the MIT License
  7. *
  8. */
  9. // Dependencies:
  10. // 1) jquery
  11. // 2) jquery-svg.js
  12. // 3) svgedit.js
  13. // 4) pathseg.js
  14. // 5) browser.js
  15. // 6) math.js
  16. // 7) history.js
  17. // 8) units.js
  18. // 9) svgtransformlist.js
  19. // 10) svgutils.js
  20. // 11) coords.js
  21. var svgedit = svgedit || {};
  22. (function() {
  23. if (!svgedit.recalculate) {
  24. svgedit.recalculate = {};
  25. }
  26. var NS = svgedit.NS;
  27. var context_;
  28. // Function: svgedit.recalculate.init
  29. svgedit.recalculate.init = function(editorContext) {
  30. context_ = editorContext;
  31. };
  32. // Function: svgedit.recalculate.updateClipPath
  33. // Updates a <clipPath>s values based on the given translation of an element
  34. //
  35. // Parameters:
  36. // attr - The clip-path attribute value with the clipPath's ID
  37. // tx - The translation's x value
  38. // ty - The translation's y value
  39. svgedit.recalculate.updateClipPath = function(attr, tx, ty) {
  40. var path = getRefElem(attr).firstChild;
  41. var cp_xform = svgedit.transformlist.getTransformList(path);
  42. var newxlate = context_.getSVGRoot().createSVGTransform();
  43. newxlate.setTranslate(tx, ty);
  44. cp_xform.appendItem(newxlate);
  45. // Update clipPath's dimensions
  46. svgedit.recalculate.recalculateDimensions(path);
  47. };
  48. // Function: svgedit.recalculate.recalculateDimensions
  49. // Decides the course of action based on the element's transform list
  50. //
  51. // Parameters:
  52. // selected - The DOM element to recalculate
  53. //
  54. // Returns:
  55. // Undo command object with the resulting change
  56. svgedit.recalculate.recalculateDimensions = function(selected) {
  57. if (selected == null) {return null;}
  58. // Firefox Issue - 1081
  59. if (selected.nodeName == "svg" && navigator.userAgent.indexOf("Firefox/20") >= 0) {
  60. return null;
  61. }
  62. var svgroot = context_.getSVGRoot();
  63. var tlist = svgedit.transformlist.getTransformList(selected);
  64. var k;
  65. // remove any unnecessary transforms
  66. if (tlist && tlist.numberOfItems > 0) {
  67. k = tlist.numberOfItems;
  68. while (k--) {
  69. var xform = tlist.getItem(k);
  70. if (xform.type === 0) {
  71. tlist.removeItem(k);
  72. }
  73. // remove identity matrices
  74. else if (xform.type === 1) {
  75. if (svgedit.math.isIdentity(xform.matrix)) {
  76. tlist.removeItem(k);
  77. }
  78. }
  79. // remove zero-degree rotations
  80. else if (xform.type === 4) {
  81. if (xform.angle === 0) {
  82. tlist.removeItem(k);
  83. }
  84. }
  85. }
  86. // End here if all it has is a rotation
  87. if (tlist.numberOfItems === 1 &&
  88. svgedit.utilities.getRotationAngle(selected)) {return null;}
  89. }
  90. // if this element had no transforms, we are done
  91. if (!tlist || tlist.numberOfItems == 0) {
  92. // Chrome has a bug that requires clearing the attribute first.
  93. selected.setAttribute('transform', '');
  94. selected.removeAttribute('transform');
  95. return null;
  96. }
  97. // TODO: Make this work for more than 2
  98. if (tlist) {
  99. k = tlist.numberOfItems;
  100. var mxs = [];
  101. while (k--) {
  102. var xform = tlist.getItem(k);
  103. if (xform.type === 1) {
  104. mxs.push([xform.matrix, k]);
  105. } else if (mxs.length) {
  106. mxs = [];
  107. }
  108. }
  109. if (mxs.length === 2) {
  110. var m_new = svgroot.createSVGTransformFromMatrix(svgedit.math.matrixMultiply(mxs[1][0], mxs[0][0]));
  111. tlist.removeItem(mxs[0][1]);
  112. tlist.removeItem(mxs[1][1]);
  113. tlist.insertItemBefore(m_new, mxs[1][1]);
  114. }
  115. // combine matrix + translate
  116. k = tlist.numberOfItems;
  117. if (k >= 2 && tlist.getItem(k-2).type === 1 && tlist.getItem(k-1).type === 2) {
  118. var mt = svgroot.createSVGTransform();
  119. var m = svgedit.math.matrixMultiply(
  120. tlist.getItem(k-2).matrix,
  121. tlist.getItem(k-1).matrix);
  122. mt.setMatrix(m);
  123. tlist.removeItem(k-2);
  124. tlist.removeItem(k-2);
  125. tlist.appendItem(mt);
  126. }
  127. }
  128. // If it still has a single [M] or [R][M], return null too (prevents BatchCommand from being returned).
  129. switch ( selected.tagName ) {
  130. // Ignore these elements, as they can absorb the [M]
  131. case 'line':
  132. case 'polyline':
  133. case 'polygon':
  134. case 'path':
  135. break;
  136. default:
  137. if ((tlist.numberOfItems === 1 && tlist.getItem(0).type === 1) ||
  138. (tlist.numberOfItems === 2 && tlist.getItem(0).type === 1 && tlist.getItem(0).type === 4)) {
  139. return null;
  140. }
  141. }
  142. // Grouped SVG element
  143. var gsvg = $(selected).data('gsvg');
  144. // we know we have some transforms, so set up return variable
  145. var batchCmd = new svgedit.history.BatchCommand('Transform');
  146. // store initial values that will be affected by reducing the transform list
  147. var changes = {}, initial = null, attrs = [];
  148. switch (selected.tagName) {
  149. case 'line':
  150. attrs = ['x1', 'y1', 'x2', 'y2'];
  151. break;
  152. case 'circle':
  153. attrs = ['cx', 'cy', 'r'];
  154. break;
  155. case 'ellipse':
  156. attrs = ['cx', 'cy', 'rx', 'ry'];
  157. break;
  158. case 'foreignObject':
  159. case 'rect':
  160. case 'image':
  161. attrs = ['width', 'height', 'x', 'y'];
  162. break;
  163. case 'use':
  164. case 'text':
  165. case 'tspan':
  166. attrs = ['x', 'y'];
  167. break;
  168. case 'polygon':
  169. case 'polyline':
  170. initial = {};
  171. initial.points = selected.getAttribute('points');
  172. var list = selected.points;
  173. var len = list.numberOfItems;
  174. changes.points = new Array(len);
  175. var i;
  176. for (i = 0; i < len; ++i) {
  177. var pt = list.getItem(i);
  178. changes.points[i] = {x:pt.x, y:pt.y};
  179. }
  180. break;
  181. case 'path':
  182. initial = {};
  183. initial.d = selected.getAttribute('d');
  184. changes.d = selected.getAttribute('d');
  185. break;
  186. } // switch on element type to get initial values
  187. if (attrs.length) {
  188. changes = $(selected).attr(attrs);
  189. $.each(changes, function(attr, val) {
  190. changes[attr] = svgedit.units.convertToNum(attr, val);
  191. });
  192. } else if (gsvg) {
  193. // GSVG exception
  194. changes = {
  195. x: $(gsvg).attr('x') || 0,
  196. y: $(gsvg).attr('y') || 0
  197. };
  198. }
  199. // if we haven't created an initial array in polygon/polyline/path, then
  200. // make a copy of initial values and include the transform
  201. if (initial == null) {
  202. initial = $.extend(true, {}, changes);
  203. $.each(initial, function(attr, val) {
  204. initial[attr] = svgedit.units.convertToNum(attr, val);
  205. });
  206. }
  207. // save the start transform value too
  208. initial.transform = context_.getStartTransform() || '';
  209. // if it's a regular group, we have special processing to flatten transforms
  210. if ((selected.tagName == 'g' && !gsvg) || selected.tagName == 'a') {
  211. var box = svgedit.utilities.getBBox(selected),
  212. oldcenter = {x: box.x+box.width/2, y: box.y+box.height/2},
  213. newcenter = svgedit.math.transformPoint(box.x+box.width/2,
  214. box.y+box.height/2,
  215. svgedit.math.transformListToTransform(tlist).matrix),
  216. m = svgroot.createSVGMatrix();
  217. // temporarily strip off the rotate and save the old center
  218. var gangle = svgedit.utilities.getRotationAngle(selected);
  219. if (gangle) {
  220. var a = gangle * Math.PI / 180;
  221. if ( Math.abs(a) > (1.0e-10) ) {
  222. var s = Math.sin(a)/(1 - Math.cos(a));
  223. } else {
  224. // FIXME: This blows up if the angle is exactly 0!
  225. var s = 2/a;
  226. }
  227. var i;
  228. for (i = 0; i < tlist.numberOfItems; ++i) {
  229. var xform = tlist.getItem(i);
  230. if (xform.type == 4) {
  231. // extract old center through mystical arts
  232. var rm = xform.matrix;
  233. oldcenter.y = (s*rm.e + rm.f)/2;
  234. oldcenter.x = (rm.e - s*rm.f)/2;
  235. tlist.removeItem(i);
  236. break;
  237. }
  238. }
  239. }
  240. var tx = 0, ty = 0,
  241. operation = 0,
  242. N = tlist.numberOfItems;
  243. if (N) {
  244. var first_m = tlist.getItem(0).matrix;
  245. }
  246. // first, if it was a scale then the second-last transform will be it
  247. if (N >= 3 && tlist.getItem(N-2).type == 3 &&
  248. tlist.getItem(N-3).type == 2 && tlist.getItem(N-1).type == 2)
  249. {
  250. operation = 3; // scale
  251. // if the children are unrotated, pass the scale down directly
  252. // otherwise pass the equivalent matrix() down directly
  253. var tm = tlist.getItem(N-3).matrix,
  254. sm = tlist.getItem(N-2).matrix,
  255. tmn = tlist.getItem(N-1).matrix;
  256. var children = selected.childNodes;
  257. var c = children.length;
  258. while (c--) {
  259. var child = children.item(c);
  260. tx = 0;
  261. ty = 0;
  262. if (child.nodeType == 1) {
  263. var childTlist = svgedit.transformlist.getTransformList(child);
  264. // some children might not have a transform (<metadata>, <defs>, etc)
  265. if (!childTlist) {continue;}
  266. var m = svgedit.math.transformListToTransform(childTlist).matrix;
  267. // Convert a matrix to a scale if applicable
  268. // if (svgedit.math.hasMatrixTransform(childTlist) && childTlist.numberOfItems == 1) {
  269. // if (m.b==0 && m.c==0 && m.e==0 && m.f==0) {
  270. // childTlist.removeItem(0);
  271. // var translateOrigin = svgroot.createSVGTransform(),
  272. // scale = svgroot.createSVGTransform(),
  273. // translateBack = svgroot.createSVGTransform();
  274. // translateOrigin.setTranslate(0, 0);
  275. // scale.setScale(m.a, m.d);
  276. // translateBack.setTranslate(0, 0);
  277. // childTlist.appendItem(translateBack);
  278. // childTlist.appendItem(scale);
  279. // childTlist.appendItem(translateOrigin);
  280. // }
  281. // }
  282. var angle = svgedit.utilities.getRotationAngle(child);
  283. var oldStartTransform = context_.getStartTransform();
  284. var childxforms = [];
  285. context_.setStartTransform(child.getAttribute('transform'));
  286. if (angle || svgedit.math.hasMatrixTransform(childTlist)) {
  287. var e2t = svgroot.createSVGTransform();
  288. e2t.setMatrix(svgedit.math.matrixMultiply(tm, sm, tmn, m));
  289. childTlist.clear();
  290. childTlist.appendItem(e2t);
  291. childxforms.push(e2t);
  292. }
  293. // if not rotated or skewed, push the [T][S][-T] down to the child
  294. else {
  295. // update the transform list with translate,scale,translate
  296. // slide the [T][S][-T] from the front to the back
  297. // [T][S][-T][M] = [M][T2][S2][-T2]
  298. // (only bringing [-T] to the right of [M])
  299. // [T][S][-T][M] = [T][S][M][-T2]
  300. // [-T2] = [M_inv][-T][M]
  301. var t2n = svgedit.math.matrixMultiply(m.inverse(), tmn, m);
  302. // [T2] is always negative translation of [-T2]
  303. var t2 = svgroot.createSVGMatrix();
  304. t2.e = -t2n.e;
  305. t2.f = -t2n.f;
  306. // [T][S][-T][M] = [M][T2][S2][-T2]
  307. // [S2] = [T2_inv][M_inv][T][S][-T][M][-T2_inv]
  308. var s2 = svgedit.math.matrixMultiply(t2.inverse(), m.inverse(), tm, sm, tmn, m, t2n.inverse());
  309. var translateOrigin = svgroot.createSVGTransform(),
  310. scale = svgroot.createSVGTransform(),
  311. translateBack = svgroot.createSVGTransform();
  312. translateOrigin.setTranslate(t2n.e, t2n.f);
  313. scale.setScale(s2.a, s2.d);
  314. translateBack.setTranslate(t2.e, t2.f);
  315. childTlist.appendItem(translateBack);
  316. childTlist.appendItem(scale);
  317. childTlist.appendItem(translateOrigin);
  318. childxforms.push(translateBack);
  319. childxforms.push(scale);
  320. childxforms.push(translateOrigin);
  321. // logMatrix(translateBack.matrix);
  322. // logMatrix(scale.matrix);
  323. } // not rotated
  324. batchCmd.addSubCommand( svgedit.recalculate.recalculateDimensions(child) );
  325. // TODO: If any <use> have this group as a parent and are
  326. // referencing this child, then we need to impose a reverse
  327. // scale on it so that when it won't get double-translated
  328. // var uses = selected.getElementsByTagNameNS(NS.SVG, 'use');
  329. // var href = '#' + child.id;
  330. // var u = uses.length;
  331. // while (u--) {
  332. // var useElem = uses.item(u);
  333. // if (href == svgedit.utilities.getHref(useElem)) {
  334. // var usexlate = svgroot.createSVGTransform();
  335. // usexlate.setTranslate(-tx,-ty);
  336. // svgedit.transformlist.getTransformList(useElem).insertItemBefore(usexlate,0);
  337. // batchCmd.addSubCommand( svgedit.recalculate.recalculateDimensions(useElem) );
  338. // }
  339. // }
  340. context_.setStartTransform(oldStartTransform);
  341. } // element
  342. } // for each child
  343. // Remove these transforms from group
  344. tlist.removeItem(N-1);
  345. tlist.removeItem(N-2);
  346. tlist.removeItem(N-3);
  347. } else if (N >= 3 && tlist.getItem(N-1).type == 1) {
  348. operation = 3; // scale
  349. m = svgedit.math.transformListToTransform(tlist).matrix;
  350. var e2t = svgroot.createSVGTransform();
  351. e2t.setMatrix(m);
  352. tlist.clear();
  353. tlist.appendItem(e2t);
  354. }
  355. // next, check if the first transform was a translate
  356. // if we had [ T1 ] [ M ] we want to transform this into [ M ] [ T2 ]
  357. // therefore [ T2 ] = [ M_inv ] [ T1 ] [ M ]
  358. else if ( (N == 1 || (N > 1 && tlist.getItem(1).type != 3)) &&
  359. tlist.getItem(0).type == 2)
  360. {
  361. operation = 2; // translate
  362. var T_M = svgedit.math.transformListToTransform(tlist).matrix;
  363. tlist.removeItem(0);
  364. var M_inv = svgedit.math.transformListToTransform(tlist).matrix.inverse();
  365. var M2 = svgedit.math.matrixMultiply( M_inv, T_M );
  366. tx = M2.e;
  367. ty = M2.f;
  368. if (tx != 0 || ty != 0) {
  369. // we pass the translates down to the individual children
  370. var children = selected.childNodes;
  371. var c = children.length;
  372. var clipPaths_done = [];
  373. while (c--) {
  374. var child = children.item(c);
  375. if (child.nodeType == 1) {
  376. // Check if child has clip-path
  377. if (child.getAttribute('clip-path')) {
  378. // tx, ty
  379. var attr = child.getAttribute('clip-path');
  380. if (clipPaths_done.indexOf(attr) === -1) {
  381. svgedit.recalculate.updateClipPath(attr, tx, ty);
  382. clipPaths_done.push(attr);
  383. }
  384. }
  385. var oldStartTransform = context_.getStartTransform();
  386. context_.setStartTransform(child.getAttribute('transform'));
  387. var childTlist = svgedit.transformlist.getTransformList(child);
  388. // some children might not have a transform (<metadata>, <defs>, etc)
  389. if (childTlist) {
  390. var newxlate = svgroot.createSVGTransform();
  391. newxlate.setTranslate(tx, ty);
  392. if (childTlist.numberOfItems) {
  393. childTlist.insertItemBefore(newxlate, 0);
  394. } else {
  395. childTlist.appendItem(newxlate);
  396. }
  397. batchCmd.addSubCommand(svgedit.recalculate.recalculateDimensions(child));
  398. // If any <use> have this group as a parent and are
  399. // referencing this child, then impose a reverse translate on it
  400. // so that when it won't get double-translated
  401. var uses = selected.getElementsByTagNameNS(NS.SVG, 'use');
  402. var href = '#' + child.id;
  403. var u = uses.length;
  404. while (u--) {
  405. var useElem = uses.item(u);
  406. if (href == svgedit.utilities.getHref(useElem)) {
  407. var usexlate = svgroot.createSVGTransform();
  408. usexlate.setTranslate(-tx,-ty);
  409. svgedit.transformlist.getTransformList(useElem).insertItemBefore(usexlate, 0);
  410. batchCmd.addSubCommand( svgedit.recalculate.recalculateDimensions(useElem) );
  411. }
  412. }
  413. context_.setStartTransform(oldStartTransform);
  414. }
  415. }
  416. }
  417. clipPaths_done = [];
  418. context_.setStartTransform(oldStartTransform);
  419. }
  420. }
  421. // else, a matrix imposition from a parent group
  422. // keep pushing it down to the children
  423. else if (N == 1 && tlist.getItem(0).type == 1 && !gangle) {
  424. operation = 1;
  425. var m = tlist.getItem(0).matrix,
  426. children = selected.childNodes,
  427. c = children.length;
  428. while (c--) {
  429. var child = children.item(c);
  430. if (child.nodeType == 1) {
  431. var oldStartTransform = context_.getStartTransform();
  432. context_.setStartTransform(child.getAttribute('transform'));
  433. var childTlist = svgedit.transformlist.getTransformList(child);
  434. if (!childTlist) {continue;}
  435. var em = svgedit.math.matrixMultiply(m, svgedit.math.transformListToTransform(childTlist).matrix);
  436. var e2m = svgroot.createSVGTransform();
  437. e2m.setMatrix(em);
  438. childTlist.clear();
  439. childTlist.appendItem(e2m, 0);
  440. batchCmd.addSubCommand( svgedit.recalculate.recalculateDimensions(child) );
  441. context_.setStartTransform(oldStartTransform);
  442. // Convert stroke
  443. // TODO: Find out if this should actually happen somewhere else
  444. var sw = child.getAttribute('stroke-width');
  445. if (child.getAttribute('stroke') !== 'none' && !isNaN(sw)) {
  446. var avg = (Math.abs(em.a) + Math.abs(em.d)) / 2;
  447. child.setAttribute('stroke-width', sw * avg);
  448. }
  449. }
  450. }
  451. tlist.clear();
  452. }
  453. // else it was just a rotate
  454. else {
  455. if (gangle) {
  456. var newRot = svgroot.createSVGTransform();
  457. newRot.setRotate(gangle, newcenter.x, newcenter.y);
  458. if (tlist.numberOfItems) {
  459. tlist.insertItemBefore(newRot, 0);
  460. } else {
  461. tlist.appendItem(newRot);
  462. }
  463. }
  464. if (tlist.numberOfItems == 0) {
  465. selected.removeAttribute('transform');
  466. }
  467. return null;
  468. }
  469. // if it was a translate, put back the rotate at the new center
  470. if (operation == 2) {
  471. if (gangle) {
  472. newcenter = {
  473. x: oldcenter.x + first_m.e,
  474. y: oldcenter.y + first_m.f
  475. };
  476. var newRot = svgroot.createSVGTransform();
  477. newRot.setRotate(gangle, newcenter.x, newcenter.y);
  478. if (tlist.numberOfItems) {
  479. tlist.insertItemBefore(newRot, 0);
  480. } else {
  481. tlist.appendItem(newRot);
  482. }
  483. }
  484. }
  485. // if it was a resize
  486. else if (operation == 3) {
  487. var m = svgedit.math.transformListToTransform(tlist).matrix;
  488. var roldt = svgroot.createSVGTransform();
  489. roldt.setRotate(gangle, oldcenter.x, oldcenter.y);
  490. var rold = roldt.matrix;
  491. var rnew = svgroot.createSVGTransform();
  492. rnew.setRotate(gangle, newcenter.x, newcenter.y);
  493. var rnew_inv = rnew.matrix.inverse(),
  494. m_inv = m.inverse(),
  495. extrat = svgedit.math.matrixMultiply(m_inv, rnew_inv, rold, m);
  496. tx = extrat.e;
  497. ty = extrat.f;
  498. if (tx != 0 || ty != 0) {
  499. // now push this transform down to the children
  500. // we pass the translates down to the individual children
  501. var children = selected.childNodes;
  502. var c = children.length;
  503. while (c--) {
  504. var child = children.item(c);
  505. if (child.nodeType == 1) {
  506. var oldStartTransform = context_.getStartTransform();
  507. context_.setStartTransform(child.getAttribute('transform'));
  508. var childTlist = svgedit.transformlist.getTransformList(child);
  509. var newxlate = svgroot.createSVGTransform();
  510. newxlate.setTranslate(tx, ty);
  511. if (childTlist.numberOfItems) {
  512. childTlist.insertItemBefore(newxlate, 0);
  513. } else {
  514. childTlist.appendItem(newxlate);
  515. }
  516. batchCmd.addSubCommand( svgedit.recalculate.recalculateDimensions(child) );
  517. context_.setStartTransform(oldStartTransform);
  518. }
  519. }
  520. }
  521. if (gangle) {
  522. if (tlist.numberOfItems) {
  523. tlist.insertItemBefore(rnew, 0);
  524. } else {
  525. tlist.appendItem(rnew);
  526. }
  527. }
  528. }
  529. }
  530. // else, it's a non-group
  531. else {
  532. // FIXME: box might be null for some elements (<metadata> etc), need to handle this
  533. var box = svgedit.utilities.getBBox(selected);
  534. // Paths (and possbly other shapes) will have no BBox while still in <defs>,
  535. // but we still may need to recalculate them (see issue 595).
  536. // TODO: Figure out how to get BBox from these elements in case they
  537. // have a rotation transform
  538. if (!box && selected.tagName != 'path') return null;
  539. var m = svgroot.createSVGMatrix(),
  540. // temporarily strip off the rotate and save the old center
  541. angle = svgedit.utilities.getRotationAngle(selected);
  542. if (angle) {
  543. var oldcenter = {x: box.x+box.width/2, y: box.y+box.height/2},
  544. newcenter = svgedit.math.transformPoint(box.x+box.width/2, box.y+box.height/2,
  545. svgedit.math.transformListToTransform(tlist).matrix);
  546. var a = angle * Math.PI / 180;
  547. if ( Math.abs(a) > (1.0e-10) ) {
  548. var s = Math.sin(a)/(1 - Math.cos(a));
  549. } else {
  550. // FIXME: This blows up if the angle is exactly 0!
  551. var s = 2/a;
  552. }
  553. for (var i = 0; i < tlist.numberOfItems; ++i) {
  554. var xform = tlist.getItem(i);
  555. if (xform.type == 4) {
  556. // extract old center through mystical arts
  557. var rm = xform.matrix;
  558. oldcenter.y = (s*rm.e + rm.f)/2;
  559. oldcenter.x = (rm.e - s*rm.f)/2;
  560. tlist.removeItem(i);
  561. break;
  562. }
  563. }
  564. }
  565. // 2 = translate, 3 = scale, 4 = rotate, 1 = matrix imposition
  566. var operation = 0;
  567. var N = tlist.numberOfItems;
  568. // Check if it has a gradient with userSpaceOnUse, in which case
  569. // adjust it by recalculating the matrix transform.
  570. // TODO: Make this work in Webkit using svgedit.transformlist.SVGTransformList
  571. if (!svgedit.browser.isWebkit()) {
  572. var fill = selected.getAttribute('fill');
  573. if (fill && fill.indexOf('url(') === 0) {
  574. var paint = getRefElem(fill);
  575. var type = 'pattern';
  576. if (paint.tagName !== type) type = 'gradient';
  577. var attrVal = paint.getAttribute(type + 'Units');
  578. if (attrVal === 'userSpaceOnUse') {
  579. //Update the userSpaceOnUse element
  580. m = svgedit.math.transformListToTransform(tlist).matrix;
  581. var gtlist = svgedit.transformlist.getTransformList(paint);
  582. var gmatrix = svgedit.math.transformListToTransform(gtlist).matrix;
  583. m = svgedit.math.matrixMultiply(m, gmatrix);
  584. var m_str = 'matrix(' + [m.a, m.b, m.c, m.d, m.e, m.f].join(',') + ')';
  585. paint.setAttribute(type + 'Transform', m_str);
  586. }
  587. }
  588. }
  589. // first, if it was a scale of a non-skewed element, then the second-last
  590. // transform will be the [S]
  591. // if we had [M][T][S][T] we want to extract the matrix equivalent of
  592. // [T][S][T] and push it down to the element
  593. if (N >= 3 && tlist.getItem(N-2).type == 3 &&
  594. tlist.getItem(N-3).type == 2 && tlist.getItem(N-1).type == 2)
  595. // Removed this so a <use> with a given [T][S][T] would convert to a matrix.
  596. // Is that bad?
  597. // && selected.nodeName != 'use'
  598. {
  599. operation = 3; // scale
  600. m = svgedit.math.transformListToTransform(tlist, N-3, N-1).matrix;
  601. tlist.removeItem(N-1);
  602. tlist.removeItem(N-2);
  603. tlist.removeItem(N-3);
  604. } // if we had [T][S][-T][M], then this was a skewed element being resized
  605. // Thus, we simply combine it all into one matrix
  606. else if (N == 4 && tlist.getItem(N-1).type == 1) {
  607. operation = 3; // scale
  608. m = svgedit.math.transformListToTransform(tlist).matrix;
  609. var e2t = svgroot.createSVGTransform();
  610. e2t.setMatrix(m);
  611. tlist.clear();
  612. tlist.appendItem(e2t);
  613. // reset the matrix so that the element is not re-mapped
  614. m = svgroot.createSVGMatrix();
  615. } // if we had [R][T][S][-T][M], then this was a rotated matrix-element
  616. // if we had [T1][M] we want to transform this into [M][T2]
  617. // therefore [ T2 ] = [ M_inv ] [ T1 ] [ M ] and we can push [T2]
  618. // down to the element
  619. else if ( (N == 1 || (N > 1 && tlist.getItem(1).type != 3)) &&
  620. tlist.getItem(0).type == 2)
  621. {
  622. operation = 2; // translate
  623. var oldxlate = tlist.getItem(0).matrix,
  624. meq = svgedit.math.transformListToTransform(tlist,1).matrix,
  625. meq_inv = meq.inverse();
  626. m = svgedit.math.matrixMultiply( meq_inv, oldxlate, meq );
  627. tlist.removeItem(0);
  628. }
  629. // else if this child now has a matrix imposition (from a parent group)
  630. // we might be able to simplify
  631. else if (N == 1 && tlist.getItem(0).type == 1 && !angle) {
  632. // Remap all point-based elements
  633. m = svgedit.math.transformListToTransform(tlist).matrix;
  634. switch (selected.tagName) {
  635. case 'line':
  636. changes = $(selected).attr(['x1', 'y1', 'x2', 'y2']);
  637. case 'polyline':
  638. case 'polygon':
  639. changes.points = selected.getAttribute('points');
  640. if (changes.points) {
  641. var list = selected.points;
  642. var len = list.numberOfItems;
  643. changes.points = new Array(len);
  644. for (var i = 0; i < len; ++i) {
  645. var pt = list.getItem(i);
  646. changes.points[i] = {x:pt.x, y:pt.y};
  647. }
  648. }
  649. case 'path':
  650. changes.d = selected.getAttribute('d');
  651. operation = 1;
  652. tlist.clear();
  653. break;
  654. default:
  655. break;
  656. }
  657. }
  658. // if it was a rotation, put the rotate back and return without a command
  659. // (this function has zero work to do for a rotate())
  660. else {
  661. operation = 4; // rotation
  662. if (angle) {
  663. var newRot = svgroot.createSVGTransform();
  664. newRot.setRotate(angle, newcenter.x, newcenter.y);
  665. if (tlist.numberOfItems) {
  666. tlist.insertItemBefore(newRot, 0);
  667. } else {
  668. tlist.appendItem(newRot);
  669. }
  670. }
  671. if (tlist.numberOfItems == 0) {
  672. selected.removeAttribute('transform');
  673. }
  674. return null;
  675. }
  676. // if it was a translate or resize, we need to remap the element and absorb the xform
  677. if (operation == 1 || operation == 2 || operation == 3) {
  678. svgedit.coords.remapElement(selected, changes, m);
  679. } // if we are remapping
  680. // if it was a translate, put back the rotate at the new center
  681. if (operation == 2) {
  682. if (angle) {
  683. if (!svgedit.math.hasMatrixTransform(tlist)) {
  684. newcenter = {
  685. x: oldcenter.x + m.e,
  686. y: oldcenter.y + m.f
  687. };
  688. }
  689. var newRot = svgroot.createSVGTransform();
  690. newRot.setRotate(angle, newcenter.x, newcenter.y);
  691. if (tlist.numberOfItems) {
  692. tlist.insertItemBefore(newRot, 0);
  693. } else {
  694. tlist.appendItem(newRot);
  695. }
  696. }
  697. // We have special processing for tspans: Tspans are not transformable
  698. // but they can have x,y coordinates (sigh). Thus, if this was a translate,
  699. // on a text element, also translate any tspan children.
  700. if (selected.tagName == 'text') {
  701. var children = selected.childNodes;
  702. var c = children.length;
  703. while (c--) {
  704. var child = children.item(c);
  705. if (child.tagName == 'tspan') {
  706. var tspanChanges = {
  707. x: $(child).attr('x') || 0,
  708. y: $(child).attr('y') || 0
  709. };
  710. svgedit.coords.remapElement(child, tspanChanges, m);
  711. }
  712. }
  713. }
  714. }
  715. // [Rold][M][T][S][-T] became [Rold][M]
  716. // we want it to be [Rnew][M][Tr] where Tr is the
  717. // translation required to re-center it
  718. // Therefore, [Tr] = [M_inv][Rnew_inv][Rold][M]
  719. else if (operation == 3 && angle) {
  720. var m = svgedit.math.transformListToTransform(tlist).matrix;
  721. var roldt = svgroot.createSVGTransform();
  722. roldt.setRotate(angle, oldcenter.x, oldcenter.y);
  723. var rold = roldt.matrix;
  724. var rnew = svgroot.createSVGTransform();
  725. rnew.setRotate(angle, newcenter.x, newcenter.y);
  726. var rnew_inv = rnew.matrix.inverse();
  727. var m_inv = m.inverse();
  728. var extrat = svgedit.math.matrixMultiply(m_inv, rnew_inv, rold, m);
  729. svgedit.coords.remapElement(selected, changes, extrat);
  730. if (angle) {
  731. if (tlist.numberOfItems) {
  732. tlist.insertItemBefore(rnew, 0);
  733. } else {
  734. tlist.appendItem(rnew);
  735. }
  736. }
  737. }
  738. } // a non-group
  739. // if the transform list has been emptied, remove it
  740. if (tlist.numberOfItems == 0) {
  741. selected.removeAttribute('transform');
  742. }
  743. batchCmd.addSubCommand(new svgedit.history.ChangeElementCommand(selected, initial));
  744. return batchCmd;
  745. };
  746. })();