path2d.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. /**
  2. * 点方法。
  3. * 此库来自konva
  4. * 移植:https://jx2d.cn
  5. * tmzdy
  6. */
  7. export function parsePathData(data) {
  8. if (!data) {
  9. return [];
  10. }
  11. var cs = data;
  12. var cc = [
  13. 'm',
  14. 'M',
  15. 'l',
  16. 'L',
  17. 'v',
  18. 'V',
  19. 'h',
  20. 'H',
  21. 'z',
  22. 'Z',
  23. 'c',
  24. 'C',
  25. 'q',
  26. 'Q',
  27. 't',
  28. 'T',
  29. 's',
  30. 'S',
  31. 'a',
  32. 'A',
  33. ];
  34. cs = cs.replace(new RegExp(' ', 'g'), ',');
  35. for (var n = 0; n < cc.length; n++) {
  36. cs = cs.replace(new RegExp(cc[n], 'g'), '|' + cc[n]);
  37. }
  38. var arr = cs.split('|');
  39. var ca = [];
  40. var coords = [];
  41. var cpx = 0;
  42. var cpy = 0;
  43. var re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/gi;
  44. var match;
  45. for (n = 1; n < arr.length; n++) {
  46. var str = arr[n];
  47. var c = str.charAt(0);
  48. str = str.slice(1);
  49. coords.length = 0;
  50. while ((match = re.exec(str))) {
  51. coords.push(match[0]);
  52. }
  53. var p = [];
  54. for (var j = 0, jlen = coords.length; j < jlen; j++) {
  55. if (coords[j] === '00') {
  56. p.push(0, 0);
  57. continue;
  58. }
  59. var parsed = parseFloat(coords[j]);
  60. if (!isNaN(parsed)) {
  61. p.push(parsed);
  62. } else {
  63. p.push(0);
  64. }
  65. }
  66. while (p.length > 0) {
  67. if (isNaN(p[0])) {
  68. break;
  69. }
  70. var cmd = null;
  71. var points = [];
  72. var startX = cpx,
  73. startY = cpy;
  74. var prevCmd, ctlPtx, ctlPty;
  75. var rx, ry, psi, fa, fs, x1, y1;
  76. switch (c) {
  77. case 'l':
  78. cpx += p.shift();
  79. cpy += p.shift();
  80. cmd = 'L';
  81. points.push(cpx, cpy);
  82. break;
  83. case 'L':
  84. cpx = p.shift();
  85. cpy = p.shift();
  86. points.push(cpx, cpy);
  87. break;
  88. case 'm':
  89. var dx = p.shift();
  90. var dy = p.shift();
  91. cpx += dx;
  92. cpy += dy;
  93. cmd = 'M';
  94. if (ca.length > 2 && ca[ca.length - 1].command === 'z') {
  95. for (var idx = ca.length - 2; idx >= 0; idx--) {
  96. if (ca[idx].command === 'M') {
  97. cpx = ca[idx].points[0] + dx;
  98. cpy = ca[idx].points[1] + dy;
  99. break;
  100. }
  101. }
  102. }
  103. points.push(cpx, cpy);
  104. c = 'l';
  105. break;
  106. case 'M':
  107. cpx = p.shift();
  108. cpy = p.shift();
  109. cmd = 'M';
  110. points.push(cpx, cpy);
  111. c = 'L';
  112. break;
  113. case 'h':
  114. cpx += p.shift();
  115. cmd = 'L';
  116. points.push(cpx, cpy);
  117. break;
  118. case 'H':
  119. cpx = p.shift();
  120. cmd = 'L';
  121. points.push(cpx, cpy);
  122. break;
  123. case 'v':
  124. cpy += p.shift();
  125. cmd = 'L';
  126. points.push(cpx, cpy);
  127. break;
  128. case 'V':
  129. cpy = p.shift();
  130. cmd = 'L';
  131. points.push(cpx, cpy);
  132. break;
  133. case 'C':
  134. points.push(p.shift(), p.shift(), p.shift(), p.shift());
  135. cpx = p.shift();
  136. cpy = p.shift();
  137. points.push(cpx, cpy);
  138. break;
  139. case 'c':
  140. points.push(cpx + p.shift(), cpy + p.shift(), cpx + p.shift(), cpy + p.shift());
  141. cpx += p.shift();
  142. cpy += p.shift();
  143. cmd = 'C';
  144. points.push(cpx, cpy);
  145. break;
  146. case 'S':
  147. ctlPtx = cpx;
  148. ctlPty = cpy;
  149. prevCmd = ca[ca.length - 1];
  150. if (prevCmd.command === 'C') {
  151. ctlPtx = cpx + (cpx - prevCmd.points[2]);
  152. ctlPty = cpy + (cpy - prevCmd.points[3]);
  153. }
  154. points.push(ctlPtx, ctlPty, p.shift(), p.shift());
  155. cpx = p.shift();
  156. cpy = p.shift();
  157. cmd = 'C';
  158. points.push(cpx, cpy);
  159. break;
  160. case 's':
  161. ctlPtx = cpx;
  162. ctlPty = cpy;
  163. prevCmd = ca[ca.length - 1];
  164. if (prevCmd.command === 'C') {
  165. ctlPtx = cpx + (cpx - prevCmd.points[2]);
  166. ctlPty = cpy + (cpy - prevCmd.points[3]);
  167. }
  168. points.push(ctlPtx, ctlPty, cpx + p.shift(), cpy + p.shift());
  169. cpx += p.shift();
  170. cpy += p.shift();
  171. cmd = 'C';
  172. points.push(cpx, cpy);
  173. break;
  174. case 'Q':
  175. points.push(p.shift(), p.shift());
  176. cpx = p.shift();
  177. cpy = p.shift();
  178. points.push(cpx, cpy);
  179. break;
  180. case 'q':
  181. points.push(cpx + p.shift(), cpy + p.shift());
  182. cpx += p.shift();
  183. cpy += p.shift();
  184. cmd = 'Q';
  185. points.push(cpx, cpy);
  186. break;
  187. case 'T':
  188. ctlPtx = cpx;
  189. ctlPty = cpy;
  190. prevCmd = ca[ca.length - 1];
  191. if (prevCmd.command === 'Q') {
  192. ctlPtx = cpx + (cpx - prevCmd.points[0]);
  193. ctlPty = cpy + (cpy - prevCmd.points[1]);
  194. }
  195. cpx = p.shift();
  196. cpy = p.shift();
  197. cmd = 'Q';
  198. points.push(ctlPtx, ctlPty, cpx, cpy);
  199. break;
  200. case 't':
  201. ctlPtx = cpx;
  202. ctlPty = cpy;
  203. prevCmd = ca[ca.length - 1];
  204. if (prevCmd.command === 'Q') {
  205. ctlPtx = cpx + (cpx - prevCmd.points[0]);
  206. ctlPty = cpy + (cpy - prevCmd.points[1]);
  207. }
  208. cpx += p.shift();
  209. cpy += p.shift();
  210. cmd = 'Q';
  211. points.push(ctlPtx, ctlPty, cpx, cpy);
  212. break;
  213. case 'A':
  214. rx = p.shift();
  215. ry = p.shift();
  216. psi = p.shift();
  217. fa = p.shift();
  218. fs = p.shift();
  219. x1 = cpx;
  220. y1 = cpy;
  221. cpx = p.shift();
  222. cpy = p.shift();
  223. cmd = 'A';
  224. points = convertEndpointToCenterParameterization(x1, y1, cpx, cpy, fa, fs, rx, ry, psi);
  225. break;
  226. case 'a':
  227. rx = p.shift();
  228. ry = p.shift();
  229. psi = p.shift();
  230. fa = p.shift();
  231. fs = p.shift();
  232. x1 = cpx;
  233. y1 = cpy;
  234. cpx += p.shift();
  235. cpy += p.shift();
  236. cmd = 'A';
  237. points = convertEndpointToCenterParameterization(x1, y1, cpx, cpy, fa, fs, rx, ry, psi);
  238. break;
  239. }
  240. ca.push({
  241. command: cmd || c,
  242. points: points,
  243. start: {
  244. x: startX,
  245. y: startY,
  246. },
  247. pathLength: calcLength(startX, startY, cmd || c, points),
  248. });
  249. }
  250. if (c === 'z' || c === 'Z') {
  251. ca.push({
  252. command: 'z',
  253. points: [],
  254. start: undefined,
  255. pathLength: 0,
  256. });
  257. }
  258. }
  259. return ca;
  260. }
  261. export function calcLength(x, y, cmd, points) {
  262. var len, p1, p2, t;
  263. switch (cmd) {
  264. case 'L':
  265. return getLineLength(x, y, points[0], points[1]);
  266. case 'C':
  267. len = 0.0;
  268. p1 = getPointOnCubicBezier(0, x, y, points[0], points[1], points[2], points[3], points[4], points[5]);
  269. for (t = 0.01; t <= 1; t += 0.01) {
  270. p2 = getPointOnCubicBezier(t, x, y, points[0], points[1], points[2], points[3], points[4], points[
  271. 5]);
  272. len += getLineLength(p1.x, p1.y, p2.x, p2.y);
  273. p1 = p2;
  274. }
  275. return len;
  276. case 'Q':
  277. len = 0.0;
  278. p1 = getPointOnQuadraticBezier(0, x, y, points[0], points[1], points[2], points[3]);
  279. for (t = 0.01; t <= 1; t += 0.01) {
  280. p2 = getPointOnQuadraticBezier(t, x, y, points[0], points[1], points[2], points[3]);
  281. len += getLineLength(p1.x, p1.y, p2.x, p2.y);
  282. p1 = p2;
  283. }
  284. return len;
  285. case 'A':
  286. len = 0.0;
  287. var start = points[4];
  288. var dTheta = points[5];
  289. var end = points[4] + dTheta;
  290. var inc = Math.PI / 180.0;
  291. if (Math.abs(start - end) < inc) {
  292. inc = Math.abs(start - end);
  293. }
  294. p1 = getPointOnEllipticalArc(points[0], points[1], points[2], points[3], start, 0);
  295. if (dTheta < 0) {
  296. for (t = start - inc; t > end; t -= inc) {
  297. p2 = getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0);
  298. len += getLineLength(p1.x, p1.y, p2.x, p2.y);
  299. p1 = p2;
  300. }
  301. } else {
  302. for (t = start + inc; t < end; t += inc) {
  303. p2 = getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0);
  304. len += getLineLength(p1.x, p1.y, p2.x, p2.y);
  305. p1 = p2;
  306. }
  307. }
  308. p2 = getPointOnEllipticalArc(points[0], points[1], points[2], points[3], end, 0);
  309. len += getLineLength(p1.x, p1.y, p2.x, p2.y);
  310. return len;
  311. }
  312. return 0;
  313. }
  314. export function convertEndpointToCenterParameterization(x1, y1, x2, y2, fa, fs, rx, ry, psiDeg) {
  315. var psi = psiDeg * (Math.PI / 180.0);
  316. var xp = (Math.cos(psi) * (x1 - x2)) / 2.0 + (Math.sin(psi) * (y1 - y2)) / 2.0;
  317. var yp = (-1 * Math.sin(psi) * (x1 - x2)) / 2.0 +
  318. (Math.cos(psi) * (y1 - y2)) / 2.0;
  319. var lambda = (xp * xp) / (rx * rx) + (yp * yp) / (ry * ry);
  320. if (lambda > 1) {
  321. rx *= Math.sqrt(lambda);
  322. ry *= Math.sqrt(lambda);
  323. }
  324. var f = Math.sqrt((rx * rx * (ry * ry) - rx * rx * (yp * yp) - ry * ry * (xp * xp)) /
  325. (rx * rx * (yp * yp) + ry * ry * (xp * xp)));
  326. if (fa === fs) {
  327. f *= -1;
  328. }
  329. if (isNaN(f)) {
  330. f = 0;
  331. }
  332. var cxp = (f * rx * yp) / ry;
  333. var cyp = (f * -ry * xp) / rx;
  334. var cx = (x1 + x2) / 2.0 + Math.cos(psi) * cxp - Math.sin(psi) * cyp;
  335. var cy = (y1 + y2) / 2.0 + Math.sin(psi) * cxp + Math.cos(psi) * cyp;
  336. var vMag = function(v) {
  337. return Math.sqrt(v[0] * v[0] + v[1] * v[1]);
  338. };
  339. var vRatio = function(u, v) {
  340. return (u[0] * v[0] + u[1] * v[1]) / (vMag(u) * vMag(v));
  341. };
  342. var vAngle = function(u, v) {
  343. return (u[0] * v[1] < u[1] * v[0] ? -1 : 1) * Math.acos(vRatio(u, v));
  344. };
  345. var theta = vAngle([1, 0], [(xp - cxp) / rx, (yp - cyp) / ry]);
  346. var u = [(xp - cxp) / rx, (yp - cyp) / ry];
  347. var v = [(-1 * xp - cxp) / rx, (-1 * yp - cyp) / ry];
  348. var dTheta = vAngle(u, v);
  349. if (vRatio(u, v) <= -1) {
  350. dTheta = Math.PI;
  351. }
  352. if (vRatio(u, v) >= 1) {
  353. dTheta = 0;
  354. }
  355. if (fs === 0 && dTheta > 0) {
  356. dTheta = dTheta - 2 * Math.PI;
  357. }
  358. if (fs === 1 && dTheta < 0) {
  359. dTheta = dTheta + 2 * Math.PI;
  360. }
  361. return [cx, cy, rx, ry, theta, dTheta, psi, fs];
  362. }
  363. export function getSelfRect() {
  364. var points = [];
  365. this.dataArray.forEach(function(data) {
  366. if (data.command === 'A') {
  367. var start = data.points[4];
  368. var dTheta = data.points[5];
  369. var end = data.points[4] + dTheta;
  370. var inc = Math.PI / 180.0;
  371. if (Math.abs(start - end) < inc) {
  372. inc = Math.abs(start - end);
  373. }
  374. if (dTheta < 0) {
  375. for (let t = start - inc; t > end; t -= inc) {
  376. const point = Path.getPointOnEllipticalArc(data.points[0], data.points[1], data.points[2],
  377. data.points[3], t, 0);
  378. points.push(point.x, point.y);
  379. }
  380. } else {
  381. for (let t = start + inc; t < end; t += inc) {
  382. const point = Path.getPointOnEllipticalArc(data.points[0], data.points[1], data.points[2],
  383. data.points[3], t, 0);
  384. points.push(point.x, point.y);
  385. }
  386. }
  387. } else if (data.command === 'C') {
  388. for (let t = 0.0; t <= 1; t += 0.01) {
  389. const point = Path.getPointOnCubicBezier(t, data.start.x, data.start.y, data.points[0], data
  390. .points[1], data.points[2], data.points[3], data.points[4], data.points[5]);
  391. points.push(point.x, point.y);
  392. }
  393. } else {
  394. points = points.concat(data.points);
  395. }
  396. });
  397. var minX = points[0];
  398. var maxX = points[0];
  399. var minY = points[1];
  400. var maxY = points[1];
  401. var x, y;
  402. for (var i = 0; i < points.length / 2; i++) {
  403. x = points[i * 2];
  404. y = points[i * 2 + 1];
  405. if (!isNaN(x)) {
  406. minX = Math.min(minX, x);
  407. maxX = Math.max(maxX, x);
  408. }
  409. if (!isNaN(y)) {
  410. minY = Math.min(minY, y);
  411. maxY = Math.max(maxY, y);
  412. }
  413. }
  414. return {
  415. x: Math.round(minX),
  416. y: Math.round(minY),
  417. width: Math.round(maxX - minX),
  418. height: Math.round(maxY - minY),
  419. };
  420. }
  421. export function expandPoints(p, tension) {
  422. var len = p.length, allPoints = [], n, cp;
  423. for (n = 2; n < len - 2; n += 2) {
  424. cp = getControlPoints(p[n - 2], p[n - 1], p[n], p[n + 1], p[n + 2], p[n + 3], tension);
  425. if (isNaN(cp[0])) {
  426. continue;
  427. }
  428. allPoints.push(cp[0]);
  429. allPoints.push(cp[1]);
  430. allPoints.push(p[n]);
  431. allPoints.push(p[n + 1]);
  432. allPoints.push(cp[2]);
  433. allPoints.push(cp[3]);
  434. }
  435. return allPoints;
  436. }
  437. export function getControlPoints(x0, y0, x1, y1, x2, y2, t) {
  438. var d01 = Math.sqrt(Math.pow(x1 - x0, 2) + Math.pow(y1 - y0, 2)), d12 = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)), fa = (t * d01) / (d01 + d12), fb = (t * d12) / (d01 + d12), p1x = x1 - fa * (x2 - x0), p1y = y1 - fa * (y2 - y0), p2x = x1 + fb * (x2 - x0), p2y = y1 + fb * (y2 - y0);
  439. return [p1x, p1y, p2x, p2y];
  440. }
  441. export function getTensionPointsClosed(points,tension) {
  442. var p = points ,len = p.length, firstControlPoints = getControlPoints(p[len - 2], p[len - 1], p[0], p[1], p[2], p[3], tension), lastControlPoints = getControlPoints(p[len - 4], p[len - 3], p[len - 2], p[len - 1], p[0], p[1], tension), middle = expandPoints(p, tension), tp = [firstControlPoints[2], firstControlPoints[3]]
  443. .concat(middle)
  444. .concat([
  445. lastControlPoints[0],
  446. lastControlPoints[1],
  447. p[len - 2],
  448. p[len - 1],
  449. lastControlPoints[2],
  450. lastControlPoints[3],
  451. firstControlPoints[0],
  452. firstControlPoints[1],
  453. p[0],
  454. p[1],
  455. ]);
  456. return tp;
  457. }