path.js 36 KB

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