path.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957
  1. import { GeometryUtil } from '../util';
  2. import * as PathUtil from './util';
  3. import { Line } from '../line';
  4. import { Point } from '../point';
  5. import { Curve } from '../curve';
  6. import { Polyline } from '../polyline';
  7. import { Rectangle } from '../rectangle';
  8. import { Geometry } from '../geometry';
  9. import { Close } from './close';
  10. import { LineTo } from './lineto';
  11. import { MoveTo } from './moveto';
  12. import { CurveTo } from './curveto';
  13. import { normalizePathData } from './normalize';
  14. export class Path extends Geometry {
  15. constructor(args) {
  16. super();
  17. this.PRECISION = 3;
  18. this.segments = [];
  19. if (Array.isArray(args)) {
  20. if (Line.isLine(args[0]) || Curve.isCurve(args[0])) {
  21. let previousObj = null;
  22. const arr = args;
  23. arr.forEach((o, i) => {
  24. if (i === 0) {
  25. this.appendSegment(Path.createSegment('M', o.start));
  26. }
  27. if (previousObj != null && !previousObj.end.equals(o.start)) {
  28. this.appendSegment(Path.createSegment('M', o.start));
  29. }
  30. if (Line.isLine(o)) {
  31. this.appendSegment(Path.createSegment('L', o.end));
  32. }
  33. else if (Curve.isCurve(o)) {
  34. this.appendSegment(Path.createSegment('C', o.controlPoint1, o.controlPoint2, o.end));
  35. }
  36. previousObj = o;
  37. });
  38. }
  39. else {
  40. const arr = args;
  41. arr.forEach((s) => {
  42. if (s.isSegment) {
  43. this.appendSegment(s);
  44. }
  45. });
  46. }
  47. }
  48. else if (args != null) {
  49. if (Line.isLine(args)) {
  50. this.appendSegment(Path.createSegment('M', args.start));
  51. this.appendSegment(Path.createSegment('L', args.end));
  52. }
  53. else if (Curve.isCurve(args)) {
  54. this.appendSegment(Path.createSegment('M', args.start));
  55. this.appendSegment(Path.createSegment('C', args.controlPoint1, args.controlPoint2, args.end));
  56. }
  57. else if (Polyline.isPolyline(args)) {
  58. if (args.points && args.points.length) {
  59. args.points.forEach((point, index) => {
  60. const segment = index === 0
  61. ? Path.createSegment('M', point)
  62. : Path.createSegment('L', point);
  63. this.appendSegment(segment);
  64. });
  65. }
  66. }
  67. else if (args.isSegment) {
  68. this.appendSegment(args);
  69. }
  70. }
  71. }
  72. get start() {
  73. const segments = this.segments;
  74. const count = segments.length;
  75. if (count === 0) {
  76. return null;
  77. }
  78. for (let i = 0; i < count; i += 1) {
  79. const segment = segments[i];
  80. if (segment.isVisible) {
  81. return segment.start;
  82. }
  83. }
  84. // if no visible segment, return last segment end point
  85. return segments[count - 1].end;
  86. }
  87. get end() {
  88. const segments = this.segments;
  89. const count = segments.length;
  90. if (count === 0) {
  91. return null;
  92. }
  93. for (let i = count - 1; i >= 0; i -= 1) {
  94. const segment = segments[i];
  95. if (segment.isVisible) {
  96. return segment.end;
  97. }
  98. }
  99. // if no visible segment, return last segment end point
  100. return segments[count - 1].end;
  101. }
  102. moveTo(...args) {
  103. return this.appendSegment(MoveTo.create.call(null, ...args));
  104. }
  105. lineTo(...args) {
  106. return this.appendSegment(LineTo.create.call(null, ...args));
  107. }
  108. curveTo(...args) {
  109. return this.appendSegment(CurveTo.create.call(null, ...args));
  110. }
  111. arcTo(rx, ry, xAxisRotation, largeArcFlag, sweepFlag, endX, endY) {
  112. const start = this.end || new Point();
  113. const points = typeof endX === 'number'
  114. ? PathUtil.arcToCurves(start.x, start.y, rx, ry, xAxisRotation, largeArcFlag, sweepFlag, endX, endY)
  115. : PathUtil.arcToCurves(start.x, start.y, rx, ry, xAxisRotation, largeArcFlag, sweepFlag, endX.x, endX.y);
  116. if (points != null) {
  117. for (let i = 0, ii = points.length; i < ii; i += 6) {
  118. this.curveTo(points[i], points[i + 1], points[i + 2], points[i + 3], points[i + 4], points[i + 5]);
  119. }
  120. }
  121. return this;
  122. }
  123. quadTo(x1, y1, x, y) {
  124. const start = this.end || new Point();
  125. const data = ['M', start.x, start.y];
  126. if (typeof x1 === 'number') {
  127. data.push('Q', x1, y1, x, y);
  128. }
  129. else {
  130. const p = y1;
  131. data.push(`Q`, x1.x, x1.y, p.x, p.y);
  132. }
  133. const path = Path.parse(data.join(' '));
  134. this.appendSegment(path.segments.slice(1));
  135. return this;
  136. }
  137. close() {
  138. return this.appendSegment(Close.create());
  139. }
  140. drawPoints(points, options = {}) {
  141. const raw = PathUtil.drawPoints(points, options);
  142. const sub = Path.parse(raw);
  143. if (sub && sub.segments) {
  144. this.appendSegment(sub.segments);
  145. }
  146. }
  147. bbox() {
  148. const segments = this.segments;
  149. const count = segments.length;
  150. if (count === 0) {
  151. return null;
  152. }
  153. let bbox;
  154. for (let i = 0; i < count; i += 1) {
  155. const segment = segments[i];
  156. if (segment.isVisible) {
  157. const segmentBBox = segment.bbox();
  158. if (segmentBBox != null) {
  159. bbox = bbox ? bbox.union(segmentBBox) : segmentBBox;
  160. }
  161. }
  162. }
  163. if (bbox != null) {
  164. return bbox;
  165. }
  166. // if the path has only invisible elements, return end point of last segment
  167. const lastSegment = segments[count - 1];
  168. return new Rectangle(lastSegment.end.x, lastSegment.end.y, 0, 0);
  169. }
  170. appendSegment(seg) {
  171. const count = this.segments.length;
  172. let previousSegment = count !== 0 ? this.segments[count - 1] : null;
  173. let currentSegment;
  174. const nextSegment = null;
  175. if (Array.isArray(seg)) {
  176. for (let i = 0, ii = seg.length; i < ii; i += 1) {
  177. const segment = seg[i];
  178. currentSegment = this.prepareSegment(segment, previousSegment, nextSegment);
  179. this.segments.push(currentSegment);
  180. previousSegment = currentSegment;
  181. }
  182. }
  183. else if (seg != null && seg.isSegment) {
  184. currentSegment = this.prepareSegment(seg, previousSegment, nextSegment);
  185. this.segments.push(currentSegment);
  186. }
  187. return this;
  188. }
  189. insertSegment(index, seg) {
  190. const count = this.segments.length;
  191. if (index < 0) {
  192. index = count + index + 1; // eslint-disable-line
  193. }
  194. if (index > count || index < 0) {
  195. throw new Error('Index out of range.');
  196. }
  197. let currentSegment;
  198. let previousSegment = null;
  199. let nextSegment = null;
  200. if (count !== 0) {
  201. if (index >= 1) {
  202. previousSegment = this.segments[index - 1];
  203. nextSegment = previousSegment.nextSegment;
  204. }
  205. else {
  206. previousSegment = null;
  207. nextSegment = this.segments[0];
  208. }
  209. }
  210. if (!Array.isArray(seg)) {
  211. currentSegment = this.prepareSegment(seg, previousSegment, nextSegment);
  212. this.segments.splice(index, 0, currentSegment);
  213. }
  214. else {
  215. for (let i = 0, ii = seg.length; i < ii; i += 1) {
  216. const segment = seg[i];
  217. currentSegment = this.prepareSegment(segment, previousSegment, nextSegment);
  218. this.segments.splice(index + i, 0, currentSegment);
  219. previousSegment = currentSegment;
  220. }
  221. }
  222. return this;
  223. }
  224. removeSegment(index) {
  225. const idx = this.fixIndex(index);
  226. const removedSegment = this.segments.splice(idx, 1)[0];
  227. const previousSegment = removedSegment.previousSegment;
  228. const nextSegment = removedSegment.nextSegment;
  229. // link the previous and next segments together (if present)
  230. if (previousSegment) {
  231. previousSegment.nextSegment = nextSegment;
  232. }
  233. if (nextSegment) {
  234. nextSegment.previousSegment = previousSegment;
  235. }
  236. if (removedSegment.isSubpathStart && nextSegment) {
  237. this.updateSubpathStartSegment(nextSegment);
  238. }
  239. return removedSegment;
  240. }
  241. replaceSegment(index, seg) {
  242. const idx = this.fixIndex(index);
  243. let currentSegment;
  244. const replacedSegment = this.segments[idx];
  245. let previousSegment = replacedSegment.previousSegment;
  246. const nextSegment = replacedSegment.nextSegment;
  247. let updateSubpathStart = replacedSegment.isSubpathStart;
  248. if (!Array.isArray(seg)) {
  249. currentSegment = this.prepareSegment(seg, previousSegment, nextSegment);
  250. this.segments.splice(idx, 1, currentSegment);
  251. if (updateSubpathStart && currentSegment.isSubpathStart) {
  252. // already updated by `prepareSegment`
  253. updateSubpathStart = false;
  254. }
  255. }
  256. else {
  257. this.segments.splice(index, 1);
  258. for (let i = 0, ii = seg.length; i < ii; i += 1) {
  259. const segment = seg[i];
  260. currentSegment = this.prepareSegment(segment, previousSegment, nextSegment);
  261. this.segments.splice(index + i, 0, currentSegment);
  262. previousSegment = currentSegment;
  263. if (updateSubpathStart && currentSegment.isSubpathStart) {
  264. updateSubpathStart = false;
  265. }
  266. }
  267. }
  268. if (updateSubpathStart && nextSegment) {
  269. this.updateSubpathStartSegment(nextSegment);
  270. }
  271. }
  272. getSegment(index) {
  273. const idx = this.fixIndex(index);
  274. return this.segments[idx];
  275. }
  276. fixIndex(index) {
  277. const length = this.segments.length;
  278. if (length === 0) {
  279. throw new Error('Path has no segments.');
  280. }
  281. let i = index;
  282. while (i < 0) {
  283. i = length + i;
  284. }
  285. if (i >= length || i < 0) {
  286. throw new Error('Index out of range.');
  287. }
  288. return i;
  289. }
  290. segmentAt(ratio, options = {}) {
  291. const index = this.segmentIndexAt(ratio, options);
  292. if (!index) {
  293. return null;
  294. }
  295. return this.getSegment(index);
  296. }
  297. segmentAtLength(length, options = {}) {
  298. const index = this.segmentIndexAtLength(length, options);
  299. if (!index)
  300. return null;
  301. return this.getSegment(index);
  302. }
  303. segmentIndexAt(ratio, options = {}) {
  304. if (this.segments.length === 0) {
  305. return null;
  306. }
  307. const rate = GeometryUtil.clamp(ratio, 0, 1);
  308. const opt = this.getOptions(options);
  309. const len = this.length(opt);
  310. const length = len * rate;
  311. return this.segmentIndexAtLength(length, opt);
  312. }
  313. segmentIndexAtLength(length, options = {}) {
  314. const count = this.segments.length;
  315. if (count === 0) {
  316. return null;
  317. }
  318. let fromStart = true;
  319. if (length < 0) {
  320. fromStart = false;
  321. length = -length; // eslint-disable-line
  322. }
  323. const precision = this.getPrecision(options);
  324. const segmentSubdivisions = this.getSubdivisions(options);
  325. let memo = 0;
  326. let lastVisibleIndex = null;
  327. for (let i = 0; i < count; i += 1) {
  328. const index = fromStart ? i : count - 1 - i;
  329. const segment = this.segments[index];
  330. const subdivisions = segmentSubdivisions[index];
  331. const len = segment.length({ precision, subdivisions });
  332. if (segment.isVisible) {
  333. if (length <= memo + len) {
  334. return index;
  335. }
  336. lastVisibleIndex = index;
  337. }
  338. memo += len;
  339. }
  340. // If length requested is higher than the length of the path, return
  341. // last visible segment index. If no visible segment, return null.
  342. return lastVisibleIndex;
  343. }
  344. getSegmentSubdivisions(options = {}) {
  345. const precision = this.getPrecision(options);
  346. const segmentSubdivisions = [];
  347. for (let i = 0, ii = this.segments.length; i < ii; i += 1) {
  348. const segment = this.segments[i];
  349. const subdivisions = segment.getSubdivisions({ precision });
  350. segmentSubdivisions.push(subdivisions);
  351. }
  352. return segmentSubdivisions;
  353. }
  354. updateSubpathStartSegment(segment) {
  355. let previous = segment.previousSegment;
  356. let current = segment;
  357. while (current && !current.isSubpathStart) {
  358. // assign previous segment's subpath start segment to this segment
  359. if (previous != null) {
  360. current.subpathStartSegment = previous.subpathStartSegment;
  361. }
  362. else {
  363. current.subpathStartSegment = null;
  364. }
  365. previous = current;
  366. current = current.nextSegment;
  367. }
  368. }
  369. prepareSegment(segment, previousSegment, nextSegment) {
  370. segment.previousSegment = previousSegment;
  371. segment.nextSegment = nextSegment;
  372. if (previousSegment != null) {
  373. previousSegment.nextSegment = segment;
  374. }
  375. if (nextSegment != null) {
  376. nextSegment.previousSegment = segment;
  377. }
  378. let updateSubpathStart = segment;
  379. if (segment.isSubpathStart) {
  380. // move to
  381. segment.subpathStartSegment = segment;
  382. updateSubpathStart = nextSegment;
  383. }
  384. // assign previous segment's subpath start (or self if it is a subpath start) to subsequent segments
  385. if (updateSubpathStart != null) {
  386. this.updateSubpathStartSegment(updateSubpathStart);
  387. }
  388. return segment;
  389. }
  390. closestPoint(p, options = {}) {
  391. const t = this.closestPointT(p, options);
  392. if (!t) {
  393. return null;
  394. }
  395. return this.pointAtT(t);
  396. }
  397. closestPointLength(p, options = {}) {
  398. const opts = this.getOptions(options);
  399. const t = this.closestPointT(p, opts);
  400. if (!t) {
  401. return 0;
  402. }
  403. return this.lengthAtT(t, opts);
  404. }
  405. closestPointNormalizedLength(p, options = {}) {
  406. const opts = this.getOptions(options);
  407. const cpLength = this.closestPointLength(p, opts);
  408. if (cpLength === 0) {
  409. return 0;
  410. }
  411. const length = this.length(opts);
  412. if (length === 0) {
  413. return 0;
  414. }
  415. return cpLength / length;
  416. }
  417. closestPointT(p, options = {}) {
  418. if (this.segments.length === 0) {
  419. return null;
  420. }
  421. const precision = this.getPrecision(options);
  422. const segmentSubdivisions = this.getSubdivisions(options);
  423. let closestPointT;
  424. let minSquaredDistance = Infinity;
  425. for (let i = 0, ii = this.segments.length; i < ii; i += 1) {
  426. const segment = this.segments[i];
  427. const subdivisions = segmentSubdivisions[i];
  428. if (segment.isVisible) {
  429. const segmentClosestPointT = segment.closestPointT(p, {
  430. precision,
  431. subdivisions,
  432. });
  433. const segmentClosestPoint = segment.pointAtT(segmentClosestPointT);
  434. const squaredDistance = GeometryUtil.squaredLength(segmentClosestPoint, p);
  435. if (squaredDistance < minSquaredDistance) {
  436. closestPointT = { segmentIndex: i, value: segmentClosestPointT };
  437. minSquaredDistance = squaredDistance;
  438. }
  439. }
  440. }
  441. if (closestPointT) {
  442. return closestPointT;
  443. }
  444. return { segmentIndex: this.segments.length - 1, value: 1 };
  445. }
  446. closestPointTangent(p, options = {}) {
  447. if (this.segments.length === 0) {
  448. return null;
  449. }
  450. const precision = this.getPrecision(options);
  451. const segmentSubdivisions = this.getSubdivisions(options);
  452. let closestPointTangent;
  453. let minSquaredDistance = Infinity;
  454. for (let i = 0, ii = this.segments.length; i < ii; i += 1) {
  455. const segment = this.segments[i];
  456. const subdivisions = segmentSubdivisions[i];
  457. if (segment.isDifferentiable()) {
  458. const segmentClosestPointT = segment.closestPointT(p, {
  459. precision,
  460. subdivisions,
  461. });
  462. const segmentClosestPoint = segment.pointAtT(segmentClosestPointT);
  463. const squaredDistance = GeometryUtil.squaredLength(segmentClosestPoint, p);
  464. if (squaredDistance < minSquaredDistance) {
  465. closestPointTangent = segment.tangentAtT(segmentClosestPointT);
  466. minSquaredDistance = squaredDistance;
  467. }
  468. }
  469. }
  470. if (closestPointTangent) {
  471. return closestPointTangent;
  472. }
  473. return null;
  474. }
  475. containsPoint(p, options = {}) {
  476. const polylines = this.toPolylines(options);
  477. if (!polylines) {
  478. return false;
  479. }
  480. let numIntersections = 0;
  481. for (let i = 0, ii = polylines.length; i < ii; i += 1) {
  482. const polyline = polylines[i];
  483. if (polyline.containsPoint(p)) {
  484. numIntersections += 1;
  485. }
  486. }
  487. // returns `true` for odd numbers of intersections (even-odd algorithm)
  488. return numIntersections % 2 === 1;
  489. }
  490. pointAt(ratio, options = {}) {
  491. if (this.segments.length === 0) {
  492. return null;
  493. }
  494. if (ratio <= 0) {
  495. return this.start.clone();
  496. }
  497. if (ratio >= 1) {
  498. return this.end.clone();
  499. }
  500. const opts = this.getOptions(options);
  501. const pathLength = this.length(opts);
  502. const length = pathLength * ratio;
  503. return this.pointAtLength(length, opts);
  504. }
  505. pointAtLength(length, options = {}) {
  506. if (this.segments.length === 0) {
  507. return null;
  508. }
  509. if (length === 0) {
  510. return this.start.clone();
  511. }
  512. let fromStart = true;
  513. if (length < 0) {
  514. fromStart = false;
  515. length = -length; // eslint-disable-line
  516. }
  517. const precision = this.getPrecision(options);
  518. const segmentSubdivisions = this.getSubdivisions(options);
  519. let lastVisibleSegment;
  520. let memo = 0;
  521. for (let i = 0, ii = this.segments.length; i < ii; i += 1) {
  522. const index = fromStart ? i : ii - 1 - i;
  523. const segment = this.segments[index];
  524. const subdivisions = segmentSubdivisions[index];
  525. const d = segment.length({
  526. precision,
  527. subdivisions,
  528. });
  529. if (segment.isVisible) {
  530. if (length <= memo + d) {
  531. return segment.pointAtLength((fromStart ? 1 : -1) * (length - memo), {
  532. precision,
  533. subdivisions,
  534. });
  535. }
  536. lastVisibleSegment = segment;
  537. }
  538. memo += d;
  539. }
  540. // if length requested is higher than the length of the path,
  541. // return last visible segment endpoint
  542. if (lastVisibleSegment) {
  543. return fromStart ? lastVisibleSegment.end : lastVisibleSegment.start;
  544. }
  545. // if no visible segment, return last segment end point
  546. const lastSegment = this.segments[this.segments.length - 1];
  547. return lastSegment.end.clone();
  548. }
  549. pointAtT(t) {
  550. const segments = this.segments;
  551. const numSegments = segments.length;
  552. if (numSegments === 0)
  553. return null; // if segments is an empty array
  554. const segmentIndex = t.segmentIndex;
  555. if (segmentIndex < 0)
  556. return segments[0].pointAtT(0);
  557. if (segmentIndex >= numSegments) {
  558. return segments[numSegments - 1].pointAtT(1);
  559. }
  560. const tValue = GeometryUtil.clamp(t.value, 0, 1);
  561. return segments[segmentIndex].pointAtT(tValue);
  562. }
  563. divideAt(ratio, options = {}) {
  564. if (this.segments.length === 0) {
  565. return null;
  566. }
  567. const rate = GeometryUtil.clamp(ratio, 0, 1);
  568. const opts = this.getOptions(options);
  569. const len = this.length(opts);
  570. const length = len * rate;
  571. return this.divideAtLength(length, opts);
  572. }
  573. divideAtLength(length, options = {}) {
  574. if (this.segments.length === 0) {
  575. return null;
  576. }
  577. let fromStart = true;
  578. if (length < 0) {
  579. fromStart = false;
  580. length = -length; // eslint-disable-line
  581. }
  582. const precision = this.getPrecision(options);
  583. const segmentSubdivisions = this.getSubdivisions(options);
  584. let memo = 0;
  585. let divided;
  586. let dividedSegmentIndex;
  587. let lastValidSegment;
  588. let lastValidSegmentIndex;
  589. let t;
  590. for (let i = 0, ii = this.segments.length; i < ii; i += 1) {
  591. const index = fromStart ? i : ii - 1 - i;
  592. const segment = this.getSegment(index);
  593. const subdivisions = segmentSubdivisions[index];
  594. const opts = { precision, subdivisions };
  595. const len = segment.length(opts);
  596. if (segment.isDifferentiable()) {
  597. lastValidSegment = segment;
  598. lastValidSegmentIndex = index;
  599. if (length <= memo + len) {
  600. dividedSegmentIndex = index;
  601. divided = segment.divideAtLength((fromStart ? 1 : -1) * (length - memo), opts);
  602. break;
  603. }
  604. }
  605. memo += len;
  606. }
  607. if (!lastValidSegment) {
  608. return null;
  609. }
  610. if (!divided) {
  611. dividedSegmentIndex = lastValidSegmentIndex;
  612. t = fromStart ? 1 : 0;
  613. divided = lastValidSegment.divideAtT(t);
  614. }
  615. // create a copy of this path and replace the identified segment with its two divided parts:
  616. const pathCopy = this.clone();
  617. const index = dividedSegmentIndex;
  618. pathCopy.replaceSegment(index, divided);
  619. const divisionStartIndex = index;
  620. let divisionMidIndex = index + 1;
  621. let divisionEndIndex = index + 2;
  622. // do not insert the part if it looks like a point
  623. if (!divided[0].isDifferentiable()) {
  624. pathCopy.removeSegment(divisionStartIndex);
  625. divisionMidIndex -= 1;
  626. divisionEndIndex -= 1;
  627. }
  628. // insert a Moveto segment to ensure secondPath will be valid:
  629. const movetoEnd = pathCopy.getSegment(divisionMidIndex).start;
  630. pathCopy.insertSegment(divisionMidIndex, Path.createSegment('M', movetoEnd));
  631. divisionEndIndex += 1;
  632. // do not insert the part if it looks like a point
  633. if (!divided[1].isDifferentiable()) {
  634. pathCopy.removeSegment(divisionEndIndex - 1);
  635. divisionEndIndex -= 1;
  636. }
  637. // ensure that Closepath segments in secondPath will be assigned correct subpathStartSegment:
  638. const secondPathSegmentIndexConversion = divisionEndIndex - divisionStartIndex - 1;
  639. for (let i = divisionEndIndex, ii = pathCopy.segments.length; i < ii; i += 1) {
  640. const originalSegment = this.getSegment(i - secondPathSegmentIndexConversion);
  641. const segment = pathCopy.getSegment(i);
  642. if (segment.type === 'Z' &&
  643. !originalSegment.subpathStartSegment.end.equals(segment.subpathStartSegment.end)) {
  644. // pathCopy segment's subpathStartSegment is different from original segment's one
  645. // convert this Closepath segment to a Lineto and replace it in pathCopy
  646. const convertedSegment = Path.createSegment('L', originalSegment.end);
  647. pathCopy.replaceSegment(i, convertedSegment);
  648. }
  649. }
  650. // distribute pathCopy segments into two paths and return those:
  651. const firstPath = new Path(pathCopy.segments.slice(0, divisionMidIndex));
  652. const secondPath = new Path(pathCopy.segments.slice(divisionMidIndex));
  653. return [firstPath, secondPath];
  654. }
  655. intersectsWithLine(line, options = {}) {
  656. const polylines = this.toPolylines(options);
  657. if (polylines == null) {
  658. return null;
  659. }
  660. let intersections = null;
  661. for (let i = 0, ii = polylines.length; i < ii; i += 1) {
  662. const polyline = polylines[i];
  663. const intersection = line.intersect(polyline);
  664. if (intersection) {
  665. if (intersections == null) {
  666. intersections = [];
  667. }
  668. if (Array.isArray(intersection)) {
  669. intersections.push(...intersection);
  670. }
  671. else {
  672. intersections.push(intersection);
  673. }
  674. }
  675. }
  676. return intersections;
  677. }
  678. isDifferentiable() {
  679. for (let i = 0, ii = this.segments.length; i < ii; i += 1) {
  680. const segment = this.segments[i];
  681. if (segment.isDifferentiable()) {
  682. return true;
  683. }
  684. }
  685. return false;
  686. }
  687. isValid() {
  688. const segments = this.segments;
  689. const isValid = segments.length === 0 || segments[0].type === 'M';
  690. return isValid;
  691. }
  692. length(options = {}) {
  693. if (this.segments.length === 0) {
  694. return 0;
  695. }
  696. const segmentSubdivisions = this.getSubdivisions(options);
  697. let length = 0;
  698. for (let i = 0, ii = this.segments.length; i < ii; i += 1) {
  699. const segment = this.segments[i];
  700. const subdivisions = segmentSubdivisions[i];
  701. length += segment.length({ subdivisions });
  702. }
  703. return length;
  704. }
  705. lengthAtT(t, options = {}) {
  706. const count = this.segments.length;
  707. if (count === 0) {
  708. return 0;
  709. }
  710. let segmentIndex = t.segmentIndex;
  711. if (segmentIndex < 0) {
  712. return 0;
  713. }
  714. let tValue = GeometryUtil.clamp(t.value, 0, 1);
  715. if (segmentIndex >= count) {
  716. segmentIndex = count - 1;
  717. tValue = 1;
  718. }
  719. const precision = this.getPrecision(options);
  720. const segmentSubdivisions = this.getSubdivisions(options);
  721. let length = 0;
  722. for (let i = 0; i < segmentIndex; i += 1) {
  723. const segment = this.segments[i];
  724. const subdivisions = segmentSubdivisions[i];
  725. length += segment.length({ precision, subdivisions });
  726. }
  727. const segment = this.segments[segmentIndex];
  728. const subdivisions = segmentSubdivisions[segmentIndex];
  729. length += segment.lengthAtT(tValue, { precision, subdivisions });
  730. return length;
  731. }
  732. tangentAt(ratio, options = {}) {
  733. if (this.segments.length === 0) {
  734. return null;
  735. }
  736. const rate = GeometryUtil.clamp(ratio, 0, 1);
  737. const opts = this.getOptions(options);
  738. const len = this.length(opts);
  739. const length = len * rate;
  740. return this.tangentAtLength(length, opts);
  741. }
  742. tangentAtLength(length, options = {}) {
  743. if (this.segments.length === 0) {
  744. return null;
  745. }
  746. let fromStart = true;
  747. if (length < 0) {
  748. fromStart = false;
  749. length = -length; // eslint-disable-line
  750. }
  751. const precision = this.getPrecision(options);
  752. const segmentSubdivisions = this.getSubdivisions(options);
  753. let lastValidSegment;
  754. let memo = 0;
  755. for (let i = 0, ii = this.segments.length; i < ii; i += 1) {
  756. const index = fromStart ? i : ii - 1 - i;
  757. const segment = this.segments[index];
  758. const subdivisions = segmentSubdivisions[index];
  759. const len = segment.length({ precision, subdivisions });
  760. if (segment.isDifferentiable()) {
  761. if (length <= memo + len) {
  762. return segment.tangentAtLength((fromStart ? 1 : -1) * (length - memo), {
  763. precision,
  764. subdivisions,
  765. });
  766. }
  767. lastValidSegment = segment;
  768. }
  769. memo += len;
  770. }
  771. // if length requested is higher than the length of the path, return tangent of endpoint of last valid segment
  772. if (lastValidSegment) {
  773. const t = fromStart ? 1 : 0;
  774. return lastValidSegment.tangentAtT(t);
  775. }
  776. // if no valid segment, return null
  777. return null;
  778. }
  779. tangentAtT(t) {
  780. const count = this.segments.length;
  781. if (count === 0) {
  782. return null;
  783. }
  784. const segmentIndex = t.segmentIndex;
  785. if (segmentIndex < 0) {
  786. return this.segments[0].tangentAtT(0);
  787. }
  788. if (segmentIndex >= count) {
  789. return this.segments[count - 1].tangentAtT(1);
  790. }
  791. const tValue = GeometryUtil.clamp(t.value, 0, 1);
  792. return this.segments[segmentIndex].tangentAtT(tValue);
  793. }
  794. getPrecision(options = {}) {
  795. return options.precision == null ? this.PRECISION : options.precision;
  796. }
  797. getSubdivisions(options = {}) {
  798. if (options.segmentSubdivisions == null) {
  799. const precision = this.getPrecision(options);
  800. return this.getSegmentSubdivisions({ precision });
  801. }
  802. return options.segmentSubdivisions;
  803. }
  804. getOptions(options = {}) {
  805. const precision = this.getPrecision(options);
  806. const segmentSubdivisions = this.getSubdivisions(options);
  807. return { precision, segmentSubdivisions };
  808. }
  809. toPoints(options = {}) {
  810. const segments = this.segments;
  811. const count = segments.length;
  812. if (count === 0) {
  813. return null;
  814. }
  815. const segmentSubdivisions = this.getSubdivisions(options);
  816. const points = [];
  817. let partialPoints = [];
  818. for (let i = 0; i < count; i += 1) {
  819. const segment = segments[i];
  820. if (segment.isVisible) {
  821. const divisions = segmentSubdivisions[i];
  822. if (divisions.length > 0) {
  823. // eslint-disable-next-line no-loop-func
  824. divisions.forEach((c) => partialPoints.push(c.start));
  825. }
  826. else {
  827. partialPoints.push(segment.start);
  828. }
  829. }
  830. else if (partialPoints.length > 0) {
  831. partialPoints.push(segments[i - 1].end);
  832. points.push(partialPoints);
  833. partialPoints = [];
  834. }
  835. }
  836. if (partialPoints.length > 0) {
  837. partialPoints.push(this.end);
  838. points.push(partialPoints);
  839. }
  840. return points;
  841. }
  842. toPolylines(options = {}) {
  843. const points = this.toPoints(options);
  844. if (!points) {
  845. return null;
  846. }
  847. return points.map((arr) => new Polyline(arr));
  848. }
  849. scale(sx, sy, origin) {
  850. this.segments.forEach((s) => s.scale(sx, sy, origin));
  851. return this;
  852. }
  853. rotate(angle, origin) {
  854. this.segments.forEach((segment) => segment.rotate(angle, origin));
  855. return this;
  856. }
  857. translate(tx, ty) {
  858. if (typeof tx === 'number') {
  859. this.segments.forEach((s) => s.translate(tx, ty));
  860. }
  861. else {
  862. this.segments.forEach((s) => s.translate(tx));
  863. }
  864. return this;
  865. }
  866. clone() {
  867. const path = new Path();
  868. this.segments.forEach((s) => path.appendSegment(s.clone()));
  869. return path;
  870. }
  871. equals(p) {
  872. if (p == null) {
  873. return false;
  874. }
  875. const segments = this.segments;
  876. const otherSegments = p.segments;
  877. const count = segments.length;
  878. if (otherSegments.length !== count) {
  879. return false;
  880. }
  881. for (let i = 0; i < count; i += 1) {
  882. const a = segments[i];
  883. const b = otherSegments[i];
  884. if (a.type !== b.type || !a.equals(b)) {
  885. return false;
  886. }
  887. }
  888. return true;
  889. }
  890. toJSON() {
  891. return this.segments.map((s) => s.toJSON());
  892. }
  893. serialize() {
  894. if (!this.isValid()) {
  895. throw new Error('Invalid path segments.');
  896. }
  897. return this.segments.map((s) => s.serialize()).join(' ');
  898. }
  899. toString() {
  900. return this.serialize();
  901. }
  902. }
  903. (function (Path) {
  904. function isPath(instance) {
  905. return instance != null && instance instanceof Path;
  906. }
  907. Path.isPath = isPath;
  908. })(Path || (Path = {}));
  909. (function (Path) {
  910. function parse(pathData) {
  911. if (!pathData) {
  912. return new Path();
  913. }
  914. const path = new Path();
  915. const commandRe = /(?:[a-zA-Z] *)(?:(?:-?\d+(?:\.\d+)?(?:e[-+]?\d+)? *,? *)|(?:-?\.\d+ *,? *))+|(?:[a-zA-Z] *)(?! |\d|-|\.)/g;
  916. const commands = Path.normalize(pathData).match(commandRe);
  917. if (commands != null) {
  918. for (let i = 0, ii = commands.length; i < ii; i += 1) {
  919. const command = commands[i];
  920. const argRe = /(?:[a-zA-Z])|(?:(?:-?\d+(?:\.\d+)?(?:e[-+]?\d+)?))|(?:(?:-?\.\d+))/g;
  921. const args = command.match(argRe); // [type, coordinate1, coordinate2...]
  922. if (args != null) {
  923. const type = args[0];
  924. const coords = args.slice(1).map((a) => +a);
  925. const segment = createSegment.call(null, type, ...coords);
  926. path.appendSegment(segment);
  927. }
  928. }
  929. }
  930. return path;
  931. }
  932. Path.parse = parse;
  933. function createSegment(type, ...args) {
  934. if (type === 'M') {
  935. return MoveTo.create.call(null, ...args);
  936. }
  937. if (type === 'L') {
  938. return LineTo.create.call(null, ...args);
  939. }
  940. if (type === 'C') {
  941. return CurveTo.create.call(null, ...args);
  942. }
  943. if (type === 'z' || type === 'Z') {
  944. return Close.create();
  945. }
  946. throw new Error(`Invalid path segment type "${type}"`);
  947. }
  948. Path.createSegment = createSegment;
  949. })(Path || (Path = {}));
  950. (function (Path) {
  951. Path.normalize = normalizePathData;
  952. Path.isValid = PathUtil.isValid;
  953. Path.drawArc = PathUtil.drawArc;
  954. Path.drawPoints = PathUtil.drawPoints;
  955. Path.arcToCurves = PathUtil.arcToCurves;
  956. })(Path || (Path = {}));
  957. //# sourceMappingURL=path.js.map