path2curve.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. var pathToAbsolute = require('./path2absolute');
  2. var a2c = function a2c(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
  3. // for more information of where this math came from visit:
  4. // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
  5. if (rx === ry) {
  6. rx += 1;
  7. }
  8. var _120 = Math.PI * 120 / 180;
  9. var rad = Math.PI / 180 * (+angle || 0);
  10. var res = [];
  11. var xy = void 0;
  12. var f1 = void 0;
  13. var f2 = void 0;
  14. var cx = void 0;
  15. var cy = void 0;
  16. var rotate = function rotate(x, y, rad) {
  17. var X = x * Math.cos(rad) - y * Math.sin(rad);
  18. var Y = x * Math.sin(rad) + y * Math.cos(rad);
  19. return {
  20. x: X,
  21. y: Y
  22. };
  23. };
  24. if (!recursive) {
  25. xy = rotate(x1, y1, -rad);
  26. x1 = xy.x;
  27. y1 = xy.y;
  28. xy = rotate(x2, y2, -rad);
  29. x2 = xy.x;
  30. y2 = xy.y;
  31. if (x1 === x2 && y1 === y2) {
  32. // 若弧的起始点和终点重叠则错开一点
  33. x2 += 1;
  34. y2 += 1;
  35. }
  36. // const cos = Math.cos(Math.PI / 180 * angle);
  37. // const sin = Math.sin(Math.PI / 180 * angle);
  38. var x = (x1 - x2) / 2;
  39. var y = (y1 - y2) / 2;
  40. var h = x * x / (rx * rx) + y * y / (ry * ry);
  41. if (h > 1) {
  42. h = Math.sqrt(h);
  43. rx = h * rx;
  44. ry = h * ry;
  45. }
  46. var rx2 = rx * rx;
  47. var ry2 = ry * ry;
  48. var k = (large_arc_flag === sweep_flag ? -1 : 1) * Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
  49. cx = k * rx * y / ry + (x1 + x2) / 2;
  50. cy = k * -ry * x / rx + (y1 + y2) / 2;
  51. f1 = Math.asin(((y1 - cy) / ry).toFixed(9));
  52. f2 = Math.asin(((y2 - cy) / ry).toFixed(9));
  53. f1 = x1 < cx ? Math.PI - f1 : f1;
  54. f2 = x2 < cx ? Math.PI - f2 : f2;
  55. f1 < 0 && (f1 = Math.PI * 2 + f1);
  56. f2 < 0 && (f2 = Math.PI * 2 + f2);
  57. if (sweep_flag && f1 > f2) {
  58. f1 = f1 - Math.PI * 2;
  59. }
  60. if (!sweep_flag && f2 > f1) {
  61. f2 = f2 - Math.PI * 2;
  62. }
  63. } else {
  64. f1 = recursive[0];
  65. f2 = recursive[1];
  66. cx = recursive[2];
  67. cy = recursive[3];
  68. }
  69. var df = f2 - f1;
  70. if (Math.abs(df) > _120) {
  71. var f2old = f2;
  72. var x2old = x2;
  73. var y2old = y2;
  74. f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
  75. x2 = cx + rx * Math.cos(f2);
  76. y2 = cy + ry * Math.sin(f2);
  77. res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
  78. }
  79. df = f2 - f1;
  80. var c1 = Math.cos(f1);
  81. var s1 = Math.sin(f1);
  82. var c2 = Math.cos(f2);
  83. var s2 = Math.sin(f2);
  84. var t = Math.tan(df / 4);
  85. var hx = 4 / 3 * rx * t;
  86. var hy = 4 / 3 * ry * t;
  87. var m1 = [x1, y1];
  88. var m2 = [x1 + hx * s1, y1 - hy * c1];
  89. var m3 = [x2 + hx * s2, y2 - hy * c2];
  90. var m4 = [x2, y2];
  91. m2[0] = 2 * m1[0] - m2[0];
  92. m2[1] = 2 * m1[1] - m2[1];
  93. if (recursive) {
  94. return [m2, m3, m4].concat(res);
  95. }
  96. res = [m2, m3, m4].concat(res).join().split(',');
  97. var newres = [];
  98. for (var i = 0, ii = res.length; i < ii; i++) {
  99. newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
  100. }
  101. return newres;
  102. };
  103. var l2c = function l2c(x1, y1, x2, y2) {
  104. return [x1, y1, x2, y2, x2, y2];
  105. };
  106. var q2c = function q2c(x1, y1, ax, ay, x2, y2) {
  107. var _13 = 1 / 3;
  108. var _23 = 2 / 3;
  109. return [_13 * x1 + _23 * ax, _13 * y1 + _23 * ay, _13 * x2 + _23 * ax, _13 * y2 + _23 * ay, x2, y2];
  110. };
  111. module.exports = function pathTocurve(path, path2) {
  112. var p = pathToAbsolute(path);
  113. var p2 = path2 && pathToAbsolute(path2);
  114. var attrs = {
  115. x: 0,
  116. y: 0,
  117. bx: 0,
  118. by: 0,
  119. X: 0,
  120. Y: 0,
  121. qx: null,
  122. qy: null
  123. };
  124. var attrs2 = {
  125. x: 0,
  126. y: 0,
  127. bx: 0,
  128. by: 0,
  129. X: 0,
  130. Y: 0,
  131. qx: null,
  132. qy: null
  133. };
  134. var pcoms1 = []; // path commands of original path p
  135. var pcoms2 = []; // path commands of original path p2
  136. var pfirst = ''; // temporary holder for original path command
  137. var pcom = ''; // holder for previous path command of original path
  138. var ii = void 0;
  139. var processPath = function processPath(path, d, pcom) {
  140. var nx = void 0,
  141. ny = void 0;
  142. if (!path) {
  143. return ['C', d.x, d.y, d.x, d.y, d.x, d.y];
  144. }!(path[0] in {
  145. T: 1,
  146. Q: 1
  147. }) && (d.qx = d.qy = null);
  148. switch (path[0]) {
  149. case 'M':
  150. d.X = path[1];
  151. d.Y = path[2];
  152. break;
  153. case 'A':
  154. path = ['C'].concat(a2c.apply(0, [d.x, d.y].concat(path.slice(1))));
  155. break;
  156. case 'S':
  157. if (pcom === 'C' || pcom === 'S') {
  158. // In "S" case we have to take into account, if the previous command is C/S.
  159. nx = d.x * 2 - d.bx; // And reflect the previous
  160. ny = d.y * 2 - d.by; // command's control point relative to the current point.
  161. } else {
  162. // or some else or nothing
  163. nx = d.x;
  164. ny = d.y;
  165. }
  166. path = ['C', nx, ny].concat(path.slice(1));
  167. break;
  168. case 'T':
  169. if (pcom === 'Q' || pcom === 'T') {
  170. // In "T" case we have to take into account, if the previous command is Q/T.
  171. d.qx = d.x * 2 - d.qx; // And make a reflection similar
  172. d.qy = d.y * 2 - d.qy; // to case "S".
  173. } else {
  174. // or something else or nothing
  175. d.qx = d.x;
  176. d.qy = d.y;
  177. }
  178. path = ['C'].concat(q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]));
  179. break;
  180. case 'Q':
  181. d.qx = path[1];
  182. d.qy = path[2];
  183. path = ['C'].concat(q2c(d.x, d.y, path[1], path[2], path[3], path[4]));
  184. break;
  185. case 'L':
  186. path = ['C'].concat(l2c(d.x, d.y, path[1], path[2]));
  187. break;
  188. case 'H':
  189. path = ['C'].concat(l2c(d.x, d.y, path[1], d.y));
  190. break;
  191. case 'V':
  192. path = ['C'].concat(l2c(d.x, d.y, d.x, path[1]));
  193. break;
  194. case 'Z':
  195. path = ['C'].concat(l2c(d.x, d.y, d.X, d.Y));
  196. break;
  197. default:
  198. break;
  199. }
  200. return path;
  201. };
  202. var fixArc = function fixArc(pp, i) {
  203. if (pp[i].length > 7) {
  204. pp[i].shift();
  205. var pi = pp[i];
  206. while (pi.length) {
  207. pcoms1[i] = 'A'; // if created multiple C:s, their original seg is saved
  208. p2 && (pcoms2[i] = 'A'); // the same as above
  209. pp.splice(i++, 0, ['C'].concat(pi.splice(0, 6)));
  210. }
  211. pp.splice(i, 1);
  212. ii = Math.max(p.length, p2 && p2.length || 0);
  213. }
  214. };
  215. var fixM = function fixM(path1, path2, a1, a2, i) {
  216. if (path1 && path2 && path1[i][0] === 'M' && path2[i][0] !== 'M') {
  217. path2.splice(i, 0, ['M', a2.x, a2.y]);
  218. a1.bx = 0;
  219. a1.by = 0;
  220. a1.x = path1[i][1];
  221. a1.y = path1[i][2];
  222. ii = Math.max(p.length, p2 && p2.length || 0);
  223. }
  224. };
  225. ii = Math.max(p.length, p2 && p2.length || 0);
  226. for (var i = 0; i < ii; i++) {
  227. p[i] && (pfirst = p[i][0]); // save current path command
  228. if (pfirst !== 'C') {
  229. // C is not saved yet, because it may be result of conversion
  230. pcoms1[i] = pfirst; // Save current path command
  231. i && (pcom = pcoms1[i - 1]); // Get previous path command pcom
  232. }
  233. p[i] = processPath(p[i], attrs, pcom); // Previous path command is inputted to processPath
  234. if (pcoms1[i] !== 'A' && pfirst === 'C') pcoms1[i] = 'C'; // A is the only command
  235. // which may produce multiple C:s
  236. // so we have to make sure that C is also C in original path
  237. fixArc(p, i); // fixArc adds also the right amount of A:s to pcoms1
  238. if (p2) {
  239. // the same procedures is done to p2
  240. p2[i] && (pfirst = p2[i][0]);
  241. if (pfirst !== 'C') {
  242. pcoms2[i] = pfirst;
  243. i && (pcom = pcoms2[i - 1]);
  244. }
  245. p2[i] = processPath(p2[i], attrs2, pcom);
  246. if (pcoms2[i] !== 'A' && pfirst === 'C') {
  247. pcoms2[i] = 'C';
  248. }
  249. fixArc(p2, i);
  250. }
  251. fixM(p, p2, attrs, attrs2, i);
  252. fixM(p2, p, attrs2, attrs, i);
  253. var seg = p[i];
  254. var seg2 = p2 && p2[i];
  255. var seglen = seg.length;
  256. var seg2len = p2 && seg2.length;
  257. attrs.x = seg[seglen - 2];
  258. attrs.y = seg[seglen - 1];
  259. attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
  260. attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
  261. attrs2.bx = p2 && (parseFloat(seg2[seg2len - 4]) || attrs2.x);
  262. attrs2.by = p2 && (parseFloat(seg2[seg2len - 3]) || attrs2.y);
  263. attrs2.x = p2 && seg2[seg2len - 2];
  264. attrs2.y = p2 && seg2[seg2len - 1];
  265. }
  266. return p2 ? [p, p2] : p;
  267. };