coords.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. /*globals $, svgroot */
  2. /*jslint vars: true, eqeq: true, forin: true*/
  3. /**
  4. * Coords.
  5. *
  6. * Licensed under the MIT License
  7. *
  8. */
  9. // Dependencies:
  10. // 1) jquery.js
  11. // 2) math.js
  12. // 3) pathseg.js
  13. // 4) browser.js
  14. // 5) svgutils.js
  15. // 6) units.js
  16. // 7) svgtransformlist.js
  17. var svgedit = svgedit || {};
  18. (function() {'use strict';
  19. if (!svgedit.coords) {
  20. svgedit.coords = {};
  21. }
  22. // this is how we map paths to our preferred relative segment types
  23. var pathMap = [0, 'z', 'M', 'm', 'L', 'l', 'C', 'c', 'Q', 'q', 'A', 'a',
  24. 'H', 'h', 'V', 'v', 'S', 's', 'T', 't'];
  25. /**
  26. * @typedef editorContext
  27. * @type {?object}
  28. * @property {function} getGridSnapping
  29. * @property {function} getDrawing
  30. */
  31. var editorContext_ = null;
  32. /**
  33. * @param {editorContext} editorContext
  34. */
  35. svgedit.coords.init = function(editorContext) {
  36. editorContext_ = editorContext;
  37. };
  38. /**
  39. * Applies coordinate changes to an element based on the given matrix
  40. * @param {Element} selected - DOM element to be changed
  41. * @param {object} changes - Object with changes to be remapped
  42. * @param {SVGMatrix} m - Matrix object to use for remapping coordinates
  43. */
  44. svgedit.coords.remapElement = function(selected, changes, m) {
  45. var i, type,
  46. remap = function(x, y) { return svgedit.math.transformPoint(x, y, m); },
  47. scalew = function(w) { return m.a * w; },
  48. scaleh = function(h) { return m.d * h; },
  49. doSnapping = editorContext_.getGridSnapping() && selected.parentNode.parentNode.localName === 'svg',
  50. finishUp = function() {
  51. var o;
  52. if (doSnapping) {
  53. for (o in changes) {
  54. changes[o] = svgedit.utilities.snapToGrid(changes[o]);
  55. }
  56. }
  57. svgedit.utilities.assignAttributes(selected, changes, 1000, true);
  58. },
  59. box = svgedit.utilities.getBBox(selected);
  60. for (i = 0; i < 2; i++) {
  61. type = i === 0 ? 'fill' : 'stroke';
  62. var attrVal = selected.getAttribute(type);
  63. if (attrVal && attrVal.indexOf('url(') === 0) {
  64. if (m.a < 0 || m.d < 0) {
  65. var grad = svgedit.utilities.getRefElem(attrVal);
  66. var newgrad = grad.cloneNode(true);
  67. if (m.a < 0) {
  68. // flip x
  69. var x1 = newgrad.getAttribute('x1');
  70. var x2 = newgrad.getAttribute('x2');
  71. newgrad.setAttribute('x1', -(x1 - 1));
  72. newgrad.setAttribute('x2', -(x2 - 1));
  73. }
  74. if (m.d < 0) {
  75. // flip y
  76. var y1 = newgrad.getAttribute('y1');
  77. var y2 = newgrad.getAttribute('y2');
  78. newgrad.setAttribute('y1', -(y1 - 1));
  79. newgrad.setAttribute('y2', -(y2 - 1));
  80. }
  81. newgrad.id = editorContext_.getDrawing().getNextId();
  82. svgedit.utilities.findDefs().appendChild(newgrad);
  83. selected.setAttribute(type, 'url(#' + newgrad.id + ')');
  84. }
  85. // Not really working :(
  86. // if (selected.tagName === 'path') {
  87. // reorientGrads(selected, m);
  88. // }
  89. }
  90. }
  91. var elName = selected.tagName;
  92. var chlist, mt;
  93. if (elName === 'g' || elName === 'text' || elName == 'tspan' || elName === 'use') {
  94. // if it was a translate, then just update x,y
  95. if (m.a == 1 && m.b == 0 && m.c == 0 && m.d == 1 && (m.e != 0 || m.f != 0) ) {
  96. // [T][M] = [M][T']
  97. // therefore [T'] = [M_inv][T][M]
  98. var existing = svgedit.math.transformListToTransform(selected).matrix,
  99. t_new = svgedit.math.matrixMultiply(existing.inverse(), m, existing);
  100. changes.x = parseFloat(changes.x) + t_new.e;
  101. changes.y = parseFloat(changes.y) + t_new.f;
  102. } else {
  103. // we just absorb all matrices into the element and don't do any remapping
  104. chlist = svgedit.transformlist.getTransformList(selected);
  105. mt = svgroot.createSVGTransform();
  106. mt.setMatrix(svgedit.math.matrixMultiply(svgedit.math.transformListToTransform(chlist).matrix, m));
  107. chlist.clear();
  108. chlist.appendItem(mt);
  109. }
  110. }
  111. var c, pt, pt1, pt2, len;
  112. // now we have a set of changes and an applied reduced transform list
  113. // we apply the changes directly to the DOM
  114. switch (elName) {
  115. case 'foreignObject':
  116. case 'rect':
  117. case 'image':
  118. // Allow images to be inverted (give them matrix when flipped)
  119. if (elName === 'image' && (m.a < 0 || m.d < 0)) {
  120. // Convert to matrix
  121. chlist = svgedit.transformlist.getTransformList(selected);
  122. mt = svgroot.createSVGTransform();
  123. mt.setMatrix(svgedit.math.matrixMultiply(svgedit.math.transformListToTransform(chlist).matrix, m));
  124. chlist.clear();
  125. chlist.appendItem(mt);
  126. } else {
  127. pt1 = remap(changes.x, changes.y);
  128. changes.width = scalew(changes.width);
  129. changes.height = scaleh(changes.height);
  130. changes.x = pt1.x + Math.min(0, changes.width);
  131. changes.y = pt1.y + Math.min(0, changes.height);
  132. changes.width = Math.abs(changes.width);
  133. changes.height = Math.abs(changes.height);
  134. }
  135. finishUp();
  136. break;
  137. case 'ellipse':
  138. c = remap(changes.cx, changes.cy);
  139. changes.cx = c.x;
  140. changes.cy = c.y;
  141. changes.rx = scalew(changes.rx);
  142. changes.ry = scaleh(changes.ry);
  143. changes.rx = Math.abs(changes.rx);
  144. changes.ry = Math.abs(changes.ry);
  145. finishUp();
  146. break;
  147. case 'circle':
  148. c = remap(changes.cx,changes.cy);
  149. changes.cx = c.x;
  150. changes.cy = c.y;
  151. // take the minimum of the new selected box's dimensions for the new circle radius
  152. var tbox = svgedit.math.transformBox(box.x, box.y, box.width, box.height, m);
  153. var w = tbox.tr.x - tbox.tl.x, h = tbox.bl.y - tbox.tl.y;
  154. changes.r = Math.min(w/2, h/2);
  155. if (changes.r) {changes.r = Math.abs(changes.r);}
  156. finishUp();
  157. break;
  158. case 'line':
  159. pt1 = remap(changes.x1, changes.y1);
  160. pt2 = remap(changes.x2, changes.y2);
  161. changes.x1 = pt1.x;
  162. changes.y1 = pt1.y;
  163. changes.x2 = pt2.x;
  164. changes.y2 = pt2.y;
  165. // deliberately fall through here
  166. case 'text':
  167. case 'tspan':
  168. case 'use':
  169. finishUp();
  170. break;
  171. case 'g':
  172. var gsvg = $(selected).data('gsvg');
  173. if (gsvg) {
  174. svgedit.utilities.assignAttributes(gsvg, changes, 1000, true);
  175. }
  176. break;
  177. case 'polyline':
  178. case 'polygon':
  179. len = changes.points.length;
  180. for (i = 0; i < len; ++i) {
  181. pt = changes.points[i];
  182. pt = remap(pt.x, pt.y);
  183. changes.points[i].x = pt.x;
  184. changes.points[i].y = pt.y;
  185. }
  186. len = changes.points.length;
  187. var pstr = '';
  188. for (i = 0; i < len; ++i) {
  189. pt = changes.points[i];
  190. pstr += pt.x + ',' + pt.y + ' ';
  191. }
  192. selected.setAttribute('points', pstr);
  193. break;
  194. case 'path':
  195. var seg;
  196. var segList = selected.pathSegList;
  197. len = segList.numberOfItems;
  198. changes.d = [];
  199. for (i = 0; i < len; ++i) {
  200. seg = segList.getItem(i);
  201. changes.d[i] = {
  202. type: seg.pathSegType,
  203. x: seg.x,
  204. y: seg.y,
  205. x1: seg.x1,
  206. y1: seg.y1,
  207. x2: seg.x2,
  208. y2: seg.y2,
  209. r1: seg.r1,
  210. r2: seg.r2,
  211. angle: seg.angle,
  212. largeArcFlag: seg.largeArcFlag,
  213. sweepFlag: seg.sweepFlag
  214. };
  215. }
  216. len = changes.d.length;
  217. var firstseg = changes.d[0],
  218. currentpt = remap(firstseg.x, firstseg.y);
  219. changes.d[0].x = currentpt.x;
  220. changes.d[0].y = currentpt.y;
  221. for (i = 1; i < len; ++i) {
  222. seg = changes.d[i];
  223. type = seg.type;
  224. // if absolute or first segment, we want to remap x, y, x1, y1, x2, y2
  225. // if relative, we want to scalew, scaleh
  226. if (type % 2 == 0) { // absolute
  227. var thisx = (seg.x != undefined) ? seg.x : currentpt.x, // for V commands
  228. thisy = (seg.y != undefined) ? seg.y : currentpt.y; // for H commands
  229. pt = remap(thisx,thisy);
  230. pt1 = remap(seg.x1, seg.y1);
  231. pt2 = remap(seg.x2, seg.y2);
  232. seg.x = pt.x;
  233. seg.y = pt.y;
  234. seg.x1 = pt1.x;
  235. seg.y1 = pt1.y;
  236. seg.x2 = pt2.x;
  237. seg.y2 = pt2.y;
  238. seg.r1 = scalew(seg.r1);
  239. seg.r2 = scaleh(seg.r2);
  240. }
  241. else { // relative
  242. seg.x = scalew(seg.x);
  243. seg.y = scaleh(seg.y);
  244. seg.x1 = scalew(seg.x1);
  245. seg.y1 = scaleh(seg.y1);
  246. seg.x2 = scalew(seg.x2);
  247. seg.y2 = scaleh(seg.y2);
  248. seg.r1 = scalew(seg.r1);
  249. seg.r2 = scaleh(seg.r2);
  250. }
  251. } // for each segment
  252. var dstr = '';
  253. len = changes.d.length;
  254. for (i = 0; i < len; ++i) {
  255. seg = changes.d[i];
  256. type = seg.type;
  257. dstr += pathMap[type];
  258. switch (type) {
  259. case 13: // relative horizontal line (h)
  260. case 12: // absolute horizontal line (H)
  261. dstr += seg.x + ' ';
  262. break;
  263. case 15: // relative vertical line (v)
  264. case 14: // absolute vertical line (V)
  265. dstr += seg.y + ' ';
  266. break;
  267. case 3: // relative move (m)
  268. case 5: // relative line (l)
  269. case 19: // relative smooth quad (t)
  270. case 2: // absolute move (M)
  271. case 4: // absolute line (L)
  272. case 18: // absolute smooth quad (T)
  273. dstr += seg.x + ',' + seg.y + ' ';
  274. break;
  275. case 7: // relative cubic (c)
  276. case 6: // absolute cubic (C)
  277. dstr += seg.x1 + ',' + seg.y1 + ' ' + seg.x2 + ',' + seg.y2 + ' ' +
  278. seg.x + ',' + seg.y + ' ';
  279. break;
  280. case 9: // relative quad (q)
  281. case 8: // absolute quad (Q)
  282. dstr += seg.x1 + ',' + seg.y1 + ' ' + seg.x + ',' + seg.y + ' ';
  283. break;
  284. case 11: // relative elliptical arc (a)
  285. case 10: // absolute elliptical arc (A)
  286. dstr += seg.r1 + ',' + seg.r2 + ' ' + seg.angle + ' ' + (+seg.largeArcFlag) +
  287. ' ' + (+seg.sweepFlag) + ' ' + seg.x + ',' + seg.y + ' ';
  288. break;
  289. case 17: // relative smooth cubic (s)
  290. case 16: // absolute smooth cubic (S)
  291. dstr += seg.x2 + ',' + seg.y2 + ' ' + seg.x + ',' + seg.y + ' ';
  292. break;
  293. }
  294. }
  295. selected.setAttribute('d', dstr);
  296. break;
  297. }
  298. };
  299. }());