libsvg.js 10 KB

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