libsvg.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. /*******************************************************************************
  2. AToMPM - A Tool for Multi-Paradigm Modelling
  3. Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
  4. This file is part of AToMPM.
  5. AToMPM is free software: you can redistribute it and/or modify it under the
  6. terms of the GNU Lesser General Public License as published by the Free Software
  7. Foundation, either version 3 of the License, or (at your option) any later
  8. version.
  9. AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
  11. PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
  12. You should have received a copy of the GNU Lesser General Public License along
  13. with AToMPM. If not, see <http://www.gnu.org/licenses/>.
  14. *******************************************************************************/
  15. /********************************** IMPORTS ***********************************/
  16. try
  17. {
  18. var _utils = (typeof(utils) == 'undefined' ? require('./utils') : utils);
  19. }
  20. catch(ex)
  21. {
  22. throw 'missing utils.js dependency';
  23. }
  24. var SVG = {'types':{},'fns':{}};
  25. /**************************** CONVENIENCE WRAPPERS ****************************/
  26. /*---------------------------------- PUBLIC ----------------------------------*/
  27. SVG.fns.getPointOnPathAtRatio =
  28. function(path,ratio)
  29. {
  30. return new SVG.types.LinearPath(path).getPointOnPathAtRatio(ratio);
  31. };
  32. /*--------------------------------- INTERNAL ---------------------------------*/
  33. SVG.fns.__getRotationMatrix =
  34. function(a,cx,cy)
  35. {
  36. cx = cx || 0;
  37. cy = cy || 0;
  38. a *= Math.PI/180;
  39. var cosa = Math.cos(a),
  40. sina = Math.sin(a),
  41. _T = SVG.fns.__getTranslationMatrix(cx,cy),
  42. R = new SVG.types.Matrix(cosa,sina,-sina,cosa,0,0),
  43. T = SVG.fns.__getTranslationMatrix(-cx,-cy);
  44. return T.mult(R).mult(_T);
  45. };
  46. SVG.fns.__getScalingMatrix =
  47. function(sx,sy,cx,cy)
  48. {
  49. cx = cx || 0;
  50. cy = cy || 0;
  51. sy = sy||sx;
  52. var _T = SVG.fns.__getTranslationMatrix(cx,cy),
  53. S = new SVG.types.Matrix(sx,0,0,sy,0,0),
  54. T = SVG.fns.__getTranslationMatrix(-cx,-cy);
  55. return T.mult(S).mult(_T);
  56. };
  57. /* return a transformation matrix given a transformation string
  58. supported string formats are SVG and RaphaelJS
  59. SVG: space separated mix of
  60. rotate(_[,_,_])
  61. translate(_,_)
  62. scale(_[,_])
  63. matrix(_,_,_,_,_,_)
  64. RaphaelJS: mix of
  65. r_[,_,_]
  66. t_,_
  67. s_[_[,_,_]]
  68. m_,_,_,_,_,_,_ */
  69. SVG.fns.__getTransformationMatrix =
  70. function(tstr)
  71. {
  72. if( tstr == undefined )
  73. return;
  74. var M = new SVG.types.Matrix(),
  75. f;
  76. if( tstr.indexOf('translate') >= 0 ||
  77. tstr.indexOf('rotate') >= 0 ||
  78. tstr.indexOf('scale') >= 0 )
  79. {
  80. tstr.split(' ').forEach(
  81. function(t)
  82. {
  83. if( (_ = t.match(/^rotate\((.*),(.*),(.*)\)$/)) ||
  84. (_ = t.match(/^rotate\((.*)\)$/)) )
  85. f = SVG.fns.__getRotationMatrix;
  86. else if( (_ = t.match(/^translate\((.*),(.*)\)$/)) )
  87. f = SVG.fns.__getTranslationMatrix;
  88. else if( (_ = t.match(/^scale\((.*),(.*)\)$/)) ||
  89. (_ = t.match(/^scale\((.*)\)$/)) )
  90. f = SVG.fns.__getScalingMatrix;
  91. else if( (_ = t.match(
  92. /^matrix\((.*),(.*),(.*),(.*),(.*),(.*)\)$/)) )
  93. f = SVG.types.Matrix;
  94. else
  95. throw 'invalid transformation string :: '+tstr;
  96. M = M.mult( f.apply(this,_.slice(1).map(parseFloat)) );
  97. });
  98. }
  99. else
  100. {
  101. tstr.match(/([rst][-\d\.,]+)/g).forEach(
  102. function(t)
  103. {
  104. if( (_ = t.match(/r(.*),(.*),(.*)$/)) ||
  105. (_ = t.match(/^r(.*)$/)) )
  106. f = SVG.fns.__getRotationMatrix;
  107. else if( (_ = t.match(/^t(.*),(.*)$/)) )
  108. f = SVG.fns.__getTranslationMatrix;
  109. else if( (_ = t.match(/^s(.*),(.*),(.*),(.*)$/)) ||
  110. (_ = t.match(/^s(.*),(.*)$/)) ||
  111. (_ = t.match(/^s(.*)$/)) )
  112. f = SVG.fns.__getScalingMatrix;
  113. else if( (_ = t.match(/^m(.*),(.*),(.*),(.*),(.*),(.*)$/)) )
  114. f = SVG.types.Matrix;
  115. else
  116. throw 'invalid transformation string :: '+tstr;
  117. M = M.mult( f.apply(this,_.slice(1).map(parseFloat)) );
  118. });
  119. }
  120. return M;
  121. };
  122. SVG.fns.__getTranslationMatrix =
  123. function(dx,dy)
  124. {
  125. return new SVG.types.Matrix(1,0,0,1,dx,dy);
  126. };
  127. /*********************************** TYPES ************************************/
  128. /*------------------------------ TRANSFORMABLE -------------------------------*/
  129. SVG.types.ATransformable =
  130. function()
  131. {
  132. throw 'can\'t instantiate abstract type ATransformable';
  133. };
  134. /* rotate this element by 'a' degrees around (cx,cy) or (0,0) */
  135. SVG.types.ATransformable.prototype.rotate =
  136. function(a,cx,cy)
  137. {
  138. return this.__transform(SVG.fns.__getRotationMatrix(a,cx,cy));
  139. };
  140. /* scale this element by 'sx,sy' w.r.t. (cx,cy) or (0,0) */
  141. SVG.types.ATransformable.prototype.scale =
  142. function(sx,sy,cx,cy)
  143. {
  144. return this.__transform(SVG.fns.__getScalingMatrix(sx,sy,cx,cy));
  145. };
  146. /* apply the given transformation string to this element */
  147. SVG.types.ATransformable.prototype.transform =
  148. function(tstr)
  149. {
  150. return this.__transform(SVG.fns.__getTransformationMatrix(tstr));
  151. }
  152. /* apply the given transformation matrix to this element */
  153. SVG.types.ATransformable.prototype.__transform =
  154. function(T)
  155. {
  156. throw 'ATransformable subtype must overwrite __transform :: '+
  157. this.constructor.name;
  158. };
  159. /* translate this element by 'dx,dy' */
  160. SVG.types.ATransformable.prototype.translate =
  161. function(dx,dy)
  162. {
  163. return this.__transform(SVG.fns.__getTranslationMatrix(dx,dy));
  164. };
  165. /*---------------------------------- MATRIX ----------------------------------*/
  166. /* an SVG matrix:
  167. [ a c e
  168. b d f
  169. 0 0 1 ]
  170. (default init is Identity) */
  171. SVG.types.Matrix =
  172. function(a,b,c,d,e,f)
  173. {
  174. if( !(this instanceof SVG.types.Matrix) )
  175. return new SVG.types.Matrix(a,b,c,d,e,f);
  176. if( a == undefined )
  177. {
  178. this.a = 1;
  179. this.b = 0;
  180. this.c = 0;
  181. this.d = 1;
  182. this.e = 0;
  183. this.f = 0;
  184. }
  185. else
  186. {
  187. this.a = a;
  188. this.b = b;
  189. this.c = c;
  190. this.d = d;
  191. this.e = e;
  192. this.f = f;
  193. }
  194. };
  195. /* return the result of multiplying this matrix by another */
  196. SVG.types.Matrix.prototype.mult =
  197. function(M)
  198. {
  199. return new SVG.types.Matrix(
  200. this.a*M.a + this.c*M.b,
  201. this.b*M.a + this.d*M.b,
  202. this.a*M.c + this.c*M.d,
  203. this.b*M.c + this.d*M.d,
  204. this.a*M.e + this.c*M.f + this.e,
  205. this.b*M.e + this.d*M.f + this.f );
  206. };
  207. /*---------------------------------- POINT -----------------------------------*/
  208. /* a 2D point:
  209. {x:_,y:_}
  210. callable via
  211. SVG.types.Point(x,y)
  212. SVG.types.Point(pointStr)
  213. SVG.types.Point(point) */
  214. SVG.types.Point =
  215. function(__1,__2)
  216. {
  217. if( !(this instanceof SVG.types.Point) )
  218. return new SVG.types.Point(__1,__2);
  219. if( typeof(__1) == 'string' )
  220. {
  221. var _p = __1.split(',');
  222. this.x = parseFloat(_p[0]);
  223. this.y = parseFloat(_p[1]);
  224. }
  225. else if( __1 instanceof SVG.types.Point )
  226. {
  227. this.x = __1.x;
  228. this.y = __1.y;
  229. }
  230. else
  231. {
  232. this.x = __1;
  233. this.y = __2;
  234. }
  235. this._x = this.x;
  236. this._y = this.y;
  237. };
  238. /* return the distance between this point and another */
  239. SVG.types.Point.prototype.dist =
  240. function(p)
  241. {
  242. return Math.sqrt(Math.pow(this.x-p.x,2) + Math.pow(this.y-p.y,2));
  243. };
  244. /* return the slope of an imaginary line between 2 points as the counter-
  245. clockwise angle between line p1->p2 and the X-axis */
  246. SVG.types.Point.prototype.slope =
  247. function(p)
  248. {
  249. var dx = p.x - this.x,
  250. dy = p.y - this.y;
  251. return Math.atan2(dy,dx)*180/Math.PI;
  252. };
  253. /* apply the given transformation matrix to this point */
  254. SVG.types.Point.prototype.__transform =
  255. function(T)
  256. {
  257. if( T )
  258. {
  259. var x = T.a*this.x + T.c*this.y + T.e,
  260. y = T.b*this.x + T.d*this.y + T.f;
  261. this.x = x;
  262. this.y = y;
  263. }
  264. else
  265. {
  266. this.x = this._x;
  267. this.y = this._y;
  268. }
  269. return this;
  270. };
  271. _utils.extend(SVG.types.Point, SVG.types.ATransformable);
  272. /*---------------------------------- PATH ------------------------------------*/
  273. /* an SVG path made only of Ms and Ls
  274. callable via
  275. SVG.types.LinearPath(pathstr)
  276. SVG.types.Point(points) */
  277. SVG.types.LinearPath =
  278. function(__1)
  279. {
  280. if( !(this instanceof SVG.types.LinearPath) )
  281. return new SVG.types.LinearPath(__1);
  282. if( typeof(__1) == 'string' )
  283. this.points = __1.split(/[ML]/).slice(1).map(SVG.types.Point);
  284. else
  285. this.points = _utils.clone(__1);
  286. this.length = -1;
  287. this.dists = [];
  288. };
  289. /* compute total length while remembering each control point's distance
  290. from the start */
  291. SVG.types.LinearPath.prototype.__buildControlPointToLengthMap =
  292. function()
  293. {
  294. this.length = 0;
  295. this.dists = [0];
  296. for( var i = 1; i<this.points.length; i++ )
  297. {
  298. this.length += this.points[i-1].dist(this.points[i]);
  299. this.dists.push(this.length);
  300. }
  301. };
  302. /* return the coordinates and orientation of the point along the path at the
  303. specified ratio of the path's length
  304. 1. return first point if ratio is <= 0
  305. 2. return last point if ratio is >= 1
  306. 3. otherwise,
  307. a. traverse 'dists' until find control points between which answer lies
  308. b. interpolate between said points and return answer
  309. #. in every case, the points orientation is returned along with it...
  310. this is computed by finding the slope between
  311. case 1: first and second point
  312. case 2: next-to-last and last point
  313. case 3: identified bounding points */
  314. SVG.types.LinearPath.prototype.getPointOnPathAtRatio =
  315. function(ratio)
  316. {
  317. var p;
  318. if( ratio <= 0 )
  319. {
  320. p = new SVG.types.Point(_utils.head(this.points));
  321. p.O = p.slope(this.points[1]);
  322. }
  323. else if( ratio >= 1 )
  324. {
  325. p = new SVG.types.Point(_utils.tail(this.points));
  326. p.O = this.points[this.points.length-2].slope(p);
  327. }
  328. else
  329. {
  330. if( this.length == -1 )
  331. this.__buildControlPointToLengthMap();
  332. for( var i = 1; i<this.points.length; i++ )
  333. if( this.dists[i]/this.length >= ratio )
  334. {
  335. var a = this.dists[i-1]/this.length,
  336. b = this.dists[i]/this.length,
  337. t = (ratio-a)/(b-a),
  338. p1 = this.points[i-1],
  339. p2 = this.points[i];
  340. p = new SVG.types.Point(
  341. p1.x+t*(p2.x-p1.x),
  342. p1.y+t*(p2.y-p1.y));
  343. p.O = p1.slope(p2);
  344. break;
  345. }
  346. }
  347. return p;
  348. };
  349. /* add/remove control points */
  350. SVG.types.LinearPath.prototype.splice =
  351. function(i,n,toadd)
  352. {
  353. //TBC
  354. this.__buildControlPointToLengthMap();
  355. };
  356. /* apply the given transformation matrix to this path */
  357. SVG.types.LinearPath.prototype.__transform =
  358. function(T)
  359. {
  360. this.points.forEach(
  361. function(p)
  362. {
  363. p.__transform(T);
  364. });
  365. return this;
  366. };
  367. _utils.extend(SVG.types.LinearPath, SVG.types.ATransformable);
  368. /*----------------------------------------------------------------------------*/
  369. SVG.types.CubicPath = function() {} //TBC
  370. SVG.types.Circle = function() {} //TBC
  371. SVG.types.Ellipse = function() {} //TBC
  372. SVG.types.Rectangle = function() {} //TBC
  373. SVG.types.Polygon = function() {} //TBC
  374. SVG.types.Star = function() {} //TBC
  375. SVG.types.Image = function() {} //TBC
  376. /* NOTE: 'exports' exists in back-end 'require', but not in browser import...
  377. this ensures no errors are reported during browser imports */
  378. var exports = exports || {};
  379. exports.SVG = SVG;