svg-path-commander 0.1.21 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +72 -12
  2. package/dist/svg-path-commander.esm.js +647 -190
  3. package/dist/svg-path-commander.esm.min.js +2 -2
  4. package/dist/svg-path-commander.js +670 -189
  5. package/dist/svg-path-commander.min.js +2 -2
  6. package/package.json +1 -1
  7. package/src/convert/pathToAbsolute.js +3 -2
  8. package/src/convert/pathToCurve.js +2 -1
  9. package/src/convert/pathToRelative.js +2 -1
  10. package/src/parser/paramsCount.js +1 -0
  11. package/src/parser/parsePathString.js +4 -3
  12. package/src/parser/pathParser.js +2 -1
  13. package/src/parser/scanSegment.js +0 -1
  14. package/src/process/fixPath.js +1 -1
  15. package/src/process/lineToCubic.js +4 -5
  16. package/src/process/normalizePath.js +4 -2
  17. package/src/util/getClosestPoint.js +12 -0
  18. package/src/util/getCubicSize.js +41 -39
  19. package/src/util/getPathArea.js +19 -19
  20. package/src/util/getPathBBox.js +0 -11
  21. package/src/util/getPathLength.js +9 -9
  22. package/src/util/getPointAtLength.js +6 -29
  23. package/src/util/getPointAtPathLength.js +44 -0
  24. package/src/util/getPropertiesAtLength.js +62 -0
  25. package/src/util/getPropertiesAtPoint.js +77 -0
  26. package/src/util/getSegmentAtLength.js +13 -0
  27. package/src/util/getSegmentOfPoint.js +14 -0
  28. package/src/util/getTotalLength.js +15 -0
  29. package/src/util/isAbsoluteArray.js +2 -1
  30. package/src/util/isCurveArray.js +2 -1
  31. package/src/util/isNormalizedArray.js +2 -1
  32. package/src/util/isPointInStroke.js +13 -0
  33. package/src/util/isRelativeArray.js +2 -1
  34. package/src/util/pathLengthFactory.js +99 -0
  35. package/src/util/segmentArcFactory.js +42 -0
  36. package/src/util/segmentCubicFactory.js +73 -0
  37. package/src/util/segmentLineFactory.js +30 -0
  38. package/src/util/segmentQuadFactory.js +70 -0
  39. package/src/util/util.js +19 -1
  40. package/types/index.d.ts +14 -5
  41. package/types/more/modules.ts +17 -5
  42. package/types/more/svg.d.ts +21 -0
  43. package/types/svg-path-commander.d.ts +242 -122
  44. package/src/util/getPointAtSegLength.js +0 -28
  45. package/src/util/getSegArcLength.js +0 -27
  46. package/src/util/getSegCubicLength.js +0 -52
  47. package/src/util/getSegLineLength.js +0 -14
  48. package/src/util/getSegQuadLength.js +0 -31
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * SVGPathCommander v0.1.21 (http://thednp.github.io/svg-path-commander)
2
+ * SVGPathCommander v0.1.22 (http://thednp.github.io/svg-path-commander)
3
3
  * Copyright 2021 © thednp
4
4
  * Licensed under MIT (https://github.com/thednp/svg-path-commander/blob/master/LICENSE)
5
5
  */
@@ -20,6 +20,7 @@
20
20
 
21
21
  /**
22
22
  * Segment params length
23
+ * @type {Record<string, number>}
23
24
  */
24
25
  var paramsCount = {
25
26
  a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0,
@@ -283,7 +284,6 @@
283
284
  var pathValue = path.pathValue;
284
285
  var index = path.index;
285
286
  var cmdCode = pathValue.charCodeAt(index);
286
- // @ts-ignore
287
287
  var reqParams = paramsCount[pathValue[index].toLowerCase()];
288
288
 
289
289
  path.segmentStart = index;
@@ -347,7 +347,8 @@
347
347
  }
348
348
 
349
349
  /**
350
- * The `PathParser` used by the parser.
350
+ * The `PathParser` is used by the `parsePathString` static method
351
+ * to generate a `pathArray`.
351
352
  *
352
353
  * @param {string} pathString
353
354
  */
@@ -392,12 +393,13 @@
392
393
  * @returns {SVGPathCommander.pathArray} the resulted `pathArray`
393
394
  */
394
395
  function parsePathString(pathInput) {
395
- if (Array.isArray(pathInput) && isPathArray(pathInput)) {
396
+ if (isPathArray(pathInput)) {
397
+ // @ts-ignore -- isPathArray also checks if it's an `Array`
396
398
  return clonePath(pathInput);
397
399
  }
398
400
 
399
- // @ts-ignore
400
- var path = new PathParser(pathInput); // TS expects string
401
+ // @ts-ignore -- pathInput is now string
402
+ var path = new PathParser(pathInput);
401
403
 
402
404
  skipSpaces(path);
403
405
 
@@ -425,11 +427,12 @@
425
427
  * Iterates an array to check if it's a `pathArray`
426
428
  * with all absolute values.
427
429
  *
428
- * @param {SVGPathCommander.pathArray} path the `pathArray` to be checked
430
+ * @param {string | SVGPathCommander.pathArray} path the `pathArray` to be checked
429
431
  * @returns {boolean} iteration result
430
432
  */
431
433
  function isAbsoluteArray(path) {
432
434
  return isPathArray(path)
435
+ // @ts-ignore -- `isPathArray` also checks if it's `Array`
433
436
  && path.every(function (x) { return x[0] === x[0].toUpperCase(); });
434
437
  }
435
438
 
@@ -437,11 +440,12 @@
437
440
  * Parses a path string value or object and returns an array
438
441
  * of segments, all converted to absolute values.
439
442
  *
440
- * @param {SVGPathCommander.pathArray | string} pathInput the path string | object
443
+ * @param {string | SVGPathCommander.pathArray} pathInput the path string | object
441
444
  * @returns {SVGPathCommander.absoluteArray} the resulted `pathArray` with absolute values
442
445
  */
443
446
  function pathToAbsolute(pathInput) {
444
- if (Array.isArray(pathInput) && isAbsoluteArray(pathInput)) {
447
+ if (isAbsoluteArray(pathInput)) {
448
+ // @ts-ignore -- `isAbsoluteArray` checks if it's `pathArray`
445
449
  return clonePath(pathInput);
446
450
  }
447
451
 
@@ -528,11 +532,12 @@
528
532
  * Iterates an array to check if it's a `pathArray`
529
533
  * with relative values.
530
534
  *
531
- * @param {SVGPathCommander.pathArray} path the `pathArray` to be checked
535
+ * @param {string | SVGPathCommander.pathArray} path the `pathArray` to be checked
532
536
  * @returns {boolean} iteration result
533
537
  */
534
538
  function isRelativeArray(path) {
535
539
  return isPathArray(path)
540
+ // @ts-ignore -- `isPathArray` checks if it's `Array`
536
541
  && path.slice(1).every(function (seg) { return seg[0] === seg[0].toLowerCase(); });
537
542
  }
538
543
 
@@ -540,11 +545,12 @@
540
545
  * Parses a path string value or object and returns an array
541
546
  * of segments, all converted to relative values.
542
547
  *
543
- * @param {SVGPathCommander.pathArray} pathInput the path string | object
548
+ * @param {string | SVGPathCommander.pathArray} pathInput the path string | object
544
549
  * @returns {SVGPathCommander.relativeArray} the resulted `pathArray` with relative values
545
550
  */
546
551
  function pathToRelative(pathInput) {
547
552
  if (isRelativeArray(pathInput)) {
553
+ // @ts-ignore -- `isRelativeArray` checks if it's `pathArray`
548
554
  return clonePath(pathInput);
549
555
  }
550
556
 
@@ -749,10 +755,11 @@
749
755
  * with all segments are in non-shorthand notation
750
756
  * with absolute values.
751
757
  *
752
- * @param {SVGPathCommander.pathArray} path the `pathArray` to be checked
758
+ * @param {string | SVGPathCommander.pathArray} path the `pathArray` to be checked
753
759
  * @returns {boolean} iteration result
754
760
  */
755
761
  function isNormalizedArray(path) {
762
+ // @ts-ignore -- `isAbsoluteArray` also checks if it's `Array`
756
763
  return isAbsoluteArray(path) && path.every(function (seg) { return 'ACLMQZ'.includes(seg[0]); });
757
764
  }
758
765
 
@@ -768,16 +775,19 @@
768
775
  * * convert segments to absolute values
769
776
  * * convert shorthand path commands to their non-shorthand notation
770
777
  *
771
- * @param {SVGPathCommander.pathArray} pathInput the string to be parsed or 'pathArray'
778
+ * @param {string | SVGPathCommander.pathArray} pathInput the string to be parsed or 'pathArray'
772
779
  * @returns {SVGPathCommander.normalArray} the normalized `pathArray`
773
780
  */
774
781
  function normalizePath(pathInput) {
775
782
  var assign;
776
783
 
777
784
  if (isNormalizedArray(pathInput)) {
785
+ // @ts-ignore -- `isNormalizedArray` checks if it's `pathArray`
778
786
  return clonePath(pathInput);
779
787
  }
780
788
 
789
+ /** @type {SVGPathCommander.normalArray} */
790
+ // @ts-ignore -- `absoluteArray` will become a `normalArray`
781
791
  var path = pathToAbsolute(pathInput);
782
792
  var params = Object.assign({}, paramsParser);
783
793
  var allPathCommands = [];
@@ -805,7 +815,6 @@
805
815
  params.y2 = +(segment[seglen - 3]) || params.y1;
806
816
  }
807
817
 
808
- // @ts-ignore -- a `normalArray` is absolutely an `absoluteArray`
809
818
  return path;
810
819
  }
811
820
 
@@ -836,7 +845,7 @@
836
845
  var y = ref$1[1];
837
846
 
838
847
  if (isClosed && mx === x && my === y) {
839
- // @ts-ignore -- `pathSegment[]` is a `pathArray`
848
+ // @ts-ignore -- `pathSegment[]` is quite a `pathArray`
840
849
  return pathArray.slice(0, -1);
841
850
  }
842
851
  return pathArray;
@@ -846,10 +855,11 @@
846
855
  * Iterates an array to check if it's a `pathArray`
847
856
  * with all C (cubic bezier) segments.
848
857
  *
849
- * @param {SVGPathCommander.pathArray} path the `Array` to be checked
858
+ * @param {string | SVGPathCommander.pathArray} path the `Array` to be checked
850
859
  * @returns {boolean} iteration result
851
860
  */
852
861
  function isCurveArray(path) {
862
+ // @ts-ignore -- `isPathArray` also checks if it's `Array`
853
863
  return isPathArray(path) && path.every(function (seg) { return 'MC'.includes(seg[0]); });
854
864
  }
855
865
 
@@ -1005,35 +1015,6 @@
1005
1015
  x2, y2 ];
1006
1016
  }
1007
1017
 
1008
- /**
1009
- * Returns the {x,y} coordinates of a point at a
1010
- * given length of a cubic-bezier segment.
1011
- *
1012
- * @param {number} p1x the starting point X
1013
- * @param {number} p1y the starting point Y
1014
- * @param {number} c1x the first control point X
1015
- * @param {number} c1y the first control point Y
1016
- * @param {number} c2x the second control point X
1017
- * @param {number} c2y the second control point Y
1018
- * @param {number} p2x the ending point X
1019
- * @param {number} p2y the ending point Y
1020
- * @param {number} t a [0-1] ratio
1021
- * @returns {{x: number, y: number}} the requested {x,y} coordinates
1022
- */
1023
- function getPointAtSegLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
1024
- var t1 = 1 - t;
1025
- return {
1026
- x: (Math.pow( t1, 3 )) * p1x
1027
- + t1 * t1 * 3 * t * c1x
1028
- + t1 * 3 * t * t * c2x
1029
- + (Math.pow( t, 3 )) * p2x,
1030
- y: (Math.pow( t1, 3 )) * p1y
1031
- + t1 * t1 * 3 * t * c1y
1032
- + t1 * 3 * t * t * c2y
1033
- + (Math.pow( t, 3 )) * p2y,
1034
- };
1035
- }
1036
-
1037
1018
  /**
1038
1019
  * Returns the coordinates of a specified distance
1039
1020
  * ratio between two points.
@@ -1050,6 +1031,51 @@
1050
1031
  return [ax + (bx - ax) * t, ay + (by - ay) * t];
1051
1032
  }
1052
1033
 
1034
+ /**
1035
+ * Returns the square root of the distance
1036
+ * between two given points.
1037
+ *
1038
+ * @param {[number, number]} a the first point coordinates
1039
+ * @param {[number, number]} b the second point coordinates
1040
+ * @returns {number} the distance value
1041
+ */
1042
+ function distanceSquareRoot(a, b) {
1043
+ return Math.sqrt(
1044
+ (a[0] - b[0]) * (a[0] - b[0])
1045
+ + (a[1] - b[1]) * (a[1] - b[1])
1046
+ );
1047
+ }
1048
+
1049
+ /**
1050
+ * Returns the length of a line (L,V,H,Z) segment,
1051
+ * or a point at a given length.
1052
+ *
1053
+ * @param {number} x1 the starting point X
1054
+ * @param {number} y1 the starting point Y
1055
+ * @param {number} x2 the ending point X
1056
+ * @param {number} y2 the ending point Y
1057
+ * @param {number=} distance the distance to point
1058
+ * @returns {{x: number, y: number} | number} the segment length or point
1059
+ */
1060
+ function segmentLineFactory(x1, y1, x2, y2, distance) {
1061
+ var length = distanceSquareRoot([x1, y1], [x2, y2]);
1062
+ var margin = 0.001;
1063
+
1064
+ if (typeof distance === 'number') {
1065
+ if (distance < margin) {
1066
+ return { x: x1, y: y1 };
1067
+ }
1068
+ if (distance > length + margin) {
1069
+ return { x: x2, y: y2 };
1070
+ }
1071
+ var ref = midPoint([x1, y1], [x2, y2], distance / length);
1072
+ var x = ref[0];
1073
+ var y = ref[1];
1074
+ return { x: x, y: y };
1075
+ }
1076
+ return length;
1077
+ }
1078
+
1053
1079
  /**
1054
1080
  * Converts an L (line-to) segment to C (cubic-bezier).
1055
1081
  *
@@ -1070,15 +1096,14 @@
1070
1096
  var p4 = midPoint(p2, p3, t);
1071
1097
  var p5 = midPoint(p3, p4, t);
1072
1098
  var p6 = midPoint(p4, p5, t);
1073
- // const cp1 = getPointAtSegLength.apply(0, p0.concat(p2, p4, p6, t));
1074
1099
  var seg1 = p0.concat( p2, p4, p6, [t]);
1075
1100
  // @ts-ignore
1076
- var cp1 = getPointAtSegLength.apply(void 0, seg1);
1077
- // const cp2 = getPointAtSegLength.apply(0, p6.concat(p5, p3, p1, 0));
1101
+ var cp1 = segmentLineFactory.apply(void 0, seg1);
1078
1102
  var seg2 = p6.concat( p5, p3, p1, [0]);
1079
1103
  // @ts-ignore
1080
- var cp2 = getPointAtSegLength.apply(void 0, seg2);
1104
+ var cp2 = segmentLineFactory.apply(void 0, seg2);
1081
1105
 
1106
+ // @ts-ignore
1082
1107
  return [cp1.x, cp1.y, cp2.x, cp2.y, x2, y2];
1083
1108
  }
1084
1109
 
@@ -1138,13 +1163,14 @@
1138
1163
  * In addition, un-necessary `Z` segment is removed if previous segment
1139
1164
  * extends to the `M` segment.
1140
1165
  *
1141
- * @param {SVGPathCommander.pathArray} pathInput the string to be parsed or 'pathArray'
1166
+ * @param {string | SVGPathCommander.pathArray} pathInput the string to be parsed or 'pathArray'
1142
1167
  * @returns {SVGPathCommander.curveArray} the resulted `pathArray` converted to cubic-bezier
1143
1168
  */
1144
1169
  function pathToCurve(pathInput) {
1145
1170
  var assign;
1146
1171
 
1147
1172
  if (isCurveArray(pathInput)) {
1173
+ // @ts-ignore -- `isCurveArray` checks if it's `pathArray`
1148
1174
  return clonePath(pathInput);
1149
1175
  }
1150
1176
 
@@ -2622,65 +2648,143 @@
2622
2648
  }
2623
2649
 
2624
2650
  /**
2625
- * Returns the cubic-bezier segment length.
2651
+ * Returns a point at a given length of a C (cubic-bezier) segment.
2652
+ *
2653
+ * @param {number} x1 the starting point X
2654
+ * @param {number} y1 the starting point Y
2655
+ * @param {number} c1x the first control point X
2656
+ * @param {number} c1y the first control point Y
2657
+ * @param {number} c2x the second control point X
2658
+ * @param {number} c2y the second control point Y
2659
+ * @param {number} x2 the ending point X
2660
+ * @param {number} y2 the ending point Y
2661
+ * @param {number} t a [0-1] ratio
2662
+ * @returns {{x: number, y: number}} the cubic-bezier segment length
2663
+ */
2664
+ function getPointAtCubicSegmentLength(x1, y1, c1x, c1y, c2x, c2y, x2, y2, t) {
2665
+ var t1 = 1 - t;
2666
+ return {
2667
+ x: (Math.pow( t1, 3 )) * x1
2668
+ + 3 * (Math.pow( t1, 2 )) * t * c1x
2669
+ + 3 * t1 * (Math.pow( t, 2 )) * c2x
2670
+ + (Math.pow( t, 3 )) * x2,
2671
+ y: (Math.pow( t1, 3 )) * y1
2672
+ + 3 * (Math.pow( t1, 2 )) * t * c1y
2673
+ + 3 * t1 * (Math.pow( t, 2 )) * c2y
2674
+ + (Math.pow( t, 3 )) * y2,
2675
+ };
2676
+ }
2677
+
2678
+ /**
2679
+ * Returns the length of a C (cubic-bezier) segment,
2680
+ * or an {x,y} point at a given length.
2681
+ *
2682
+ * @param {number} x1 the starting point X
2683
+ * @param {number} y1 the starting point Y
2684
+ * @param {number} c1x the first control point X
2685
+ * @param {number} c1y the first control point Y
2686
+ * @param {number} c2x the second control point X
2687
+ * @param {number} c2y the second control point Y
2688
+ * @param {number} x2 the ending point X
2689
+ * @param {number} y2 the ending point Y
2690
+ * @param {number=} distance the point distance
2691
+ * @returns {{x: number, y: number} | number} the segment length or point
2692
+ */
2693
+ function segmentCubicFactory(x1, y1, c1x, c1y, c2x, c2y, x2, y2, distance) {
2694
+ var assign;
2695
+
2696
+ var x = x1; var y = y1;
2697
+ var totalLength = 0;
2698
+ var prev = [x1, y1, totalLength];
2699
+ /** @type {[number, number]} */
2700
+ var cur = [x1, y1];
2701
+ var t = 0;
2702
+
2703
+ var n = 101;
2704
+ for (var j = 0; j <= n; j += 1) {
2705
+ t = j / n;
2706
+
2707
+ ((assign = getPointAtCubicSegmentLength(x1, y1, c1x, c1y, c2x, c2y, x2, y2, t), x = assign.x, y = assign.y));
2708
+ totalLength += distanceSquareRoot(cur, [x, y]);
2709
+ cur = [x, y];
2710
+
2711
+ if (typeof distance === 'number' && totalLength >= distance) {
2712
+ var dv = (totalLength - distance) / (totalLength - prev[2]);
2713
+
2714
+ return {
2715
+ x: cur[0] * (1 - dv) + prev[0] * dv,
2716
+ y: cur[1] * (1 - dv) + prev[1] * dv,
2717
+ };
2718
+ }
2719
+ prev = [x, y, totalLength];
2720
+ }
2721
+ return totalLength;
2722
+ }
2723
+
2724
+ /**
2725
+ * Returns the cubic-bezier segment bounding box.
2626
2726
  *
2627
- * @param {number} p1x the starting point X
2628
- * @param {number} p1y the starting point Y
2727
+ * @param {number} x1 the starting point X
2728
+ * @param {number} y1 the starting point Y
2629
2729
  * @param {number} c1x the first control point X
2630
2730
  * @param {number} c1y the first control point Y
2631
2731
  * @param {number} c2x the second control point X
2632
2732
  * @param {number} c2y the second control point Y
2633
- * @param {number} p2x the ending point X
2634
- * @param {number} p2y the ending point Y
2635
- * @returns {SVGPathCommander.segmentLimits} the length of the cubic-bezier segment
2636
- */
2637
- function getCubicSize(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
2638
- var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x);
2639
- var b = 2 * (c1x - p1x) - 2 * (c2x - c1x);
2640
- var c = p1x - c1x;
2733
+ * @param {number} x2 the ending point X
2734
+ * @param {number} y2 the ending point Y
2735
+ * @returns {SVGPathCommander.segmentLimits} the bounding box of the cubic-bezier segment
2736
+ */
2737
+ function getCubicSize(x1, y1, c1x, c1y, c2x, c2y, x2, y2) {
2738
+ var assign, assign$1, assign$2, assign$3;
2739
+
2740
+ var a = (c2x - 2 * c1x + x1) - (x2 - 2 * c2x + c1x);
2741
+ var b = 2 * (c1x - x1) - 2 * (c2x - c1x);
2742
+ var c = x1 - c1x;
2641
2743
  var t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
2642
2744
  var t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
2643
- var y = [p1y, p2y];
2644
- var x = [p1x, p2x];
2645
- var dot;
2646
- // @ts-ignore
2647
- if (Math.abs(t1) > '1e12') { t1 = 0.5; }
2648
- // @ts-ignore
2649
- if (Math.abs(t2) > '1e12') { t2 = 0.5; }
2745
+ var X = [x1, x2];
2746
+ var Y = [y1, y2];
2747
+ var x = 0;
2748
+ var y = 0;
2749
+
2750
+ if (Math.abs(t1) > 1e12) { t1 = 0.5; }
2751
+ if (Math.abs(t2) > 1e12) { t2 = 0.5; }
2650
2752
 
2651
2753
  if (t1 > 0 && t1 < 1) {
2652
- dot = getPointAtSegLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
2653
- x.push(dot.x);
2654
- y.push(dot.y);
2754
+ // @ts-ignore
2755
+ ((assign = segmentCubicFactory(x1, y1, c1x, c1y, c2x, c2y, x2, y2, t1), x = assign.x, y = assign.y));
2756
+ X.push(x);
2757
+ Y.push(y);
2655
2758
  }
2656
2759
  if (t2 > 0 && t2 < 1) {
2657
- dot = getPointAtSegLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
2658
- x.push(dot.x);
2659
- y.push(dot.y);
2760
+ // @ts-ignore
2761
+ ((assign$1 = segmentCubicFactory(x1, y1, c1x, c1y, c2x, c2y, x2, y2, t2), x = assign$1.x, y = assign$1.y));
2762
+ X.push(x);
2763
+ Y.push(y);
2660
2764
  }
2661
- a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
2662
- b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
2663
- c = p1y - c1y;
2765
+ a = (c2y - 2 * c1y + y1) - (y2 - 2 * c2y + c1y);
2766
+ b = 2 * (c1y - y1) - 2 * (c2y - c1y);
2767
+ c = y1 - c1y;
2664
2768
  t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
2665
2769
  t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
2666
- // @ts-ignore
2667
- if (Math.abs(t1) > '1e12') { t1 = 0.5; }
2668
- // @ts-ignore
2669
- if (Math.abs(t2) > '1e12') { t2 = 0.5; }
2770
+ if (Math.abs(t1) > 1e12) { t1 = 0.5; }
2771
+ if (Math.abs(t2) > 1e12) { t2 = 0.5; }
2670
2772
 
2671
2773
  if (t1 > 0 && t1 < 1) {
2672
- dot = getPointAtSegLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
2673
- x.push(dot.x);
2674
- y.push(dot.y);
2774
+ // @ts-ignore
2775
+ ((assign$2 = segmentCubicFactory(x1, y1, c1x, c1y, c2x, c2y, x2, y2, t1), x = assign$2.x, y = assign$2.y));
2776
+ X.push(x);
2777
+ Y.push(y);
2675
2778
  }
2676
2779
  if (t2 > 0 && t2 < 1) {
2677
- dot = getPointAtSegLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
2678
- x.push(dot.x);
2679
- y.push(dot.y);
2780
+ // @ts-ignore
2781
+ ((assign$3 = segmentCubicFactory(x1, y1, c1x, c1y, c2x, c2y, x2, y2, t2), x = assign$3.x, y = assign$3.y));
2782
+ X.push(x);
2783
+ Y.push(y);
2680
2784
  }
2681
2785
  return {
2682
- min: { x: Math.min.apply(0, x), y: Math.min.apply(0, y) },
2683
- max: { x: Math.max.apply(0, x), y: Math.max.apply(0, y) },
2786
+ min: { x: Math.min.apply(Math, X), y: Math.min.apply(Math, Y) },
2787
+ max: { x: Math.max.apply(Math, X), y: Math.max.apply(Math, Y) },
2684
2788
  };
2685
2789
  }
2686
2790
 
@@ -2718,27 +2822,16 @@
2718
2822
  // @ts-ignore -- this should be fine
2719
2823
  var dim = getCubicSize.apply(void 0, sizeArgs);
2720
2824
 
2721
- // X = X.concat(dim.min.x, dim.max.x);
2722
2825
  X = X.concat( [dim.min.x], [dim.max.x]);
2723
-
2724
- // Y = Y.concat(dim.min.y, dim.max.y);
2725
2826
  Y = Y.concat( [dim.min.y], [dim.max.y]);
2726
2827
  x = s1;
2727
2828
  y = s2;
2728
2829
  }
2729
2830
  });
2730
2831
 
2731
- // @ts-ignore
2732
- // const xTop = Math.min.apply(0, X);
2733
2832
  var xTop = Math.min.apply(Math, X);
2734
- // @ts-ignore
2735
- // const yTop = Math.min.apply(0, Y);
2736
2833
  var yTop = Math.min.apply(Math, Y);
2737
- // @ts-ignore
2738
- // const xBot = Math.max.apply(0, X);
2739
2834
  var xBot = Math.max.apply(Math, X);
2740
- // @ts-ignore
2741
- // const yBot = Math.max.apply(0, Y);
2742
2835
  var yBot = Math.max.apply(Math, Y);
2743
2836
  var width = xBot - xTop;
2744
2837
  var height = yBot - yTop;
@@ -2988,38 +3081,38 @@
2988
3081
  };
2989
3082
 
2990
3083
  /**
2991
- * Returns the area of a single segment shape.
3084
+ * Returns the area of a single cubic-bezier segment.
2992
3085
  *
2993
3086
  * http://objectmix.com/graphics/133553-area-closed-bezier-curve.html
2994
3087
  *
2995
- * @param {number} x0 the starting point X
2996
- * @param {number} y0 the starting point Y
2997
- * @param {number} x1 the first control point X
2998
- * @param {number} y1 the first control point Y
2999
- * @param {number} x2 the second control point X
3000
- * @param {number} y2 the second control point Y
3001
- * @param {number} x3 the ending point X
3002
- * @param {number} y3 the ending point Y
3088
+ * @param {number} x1 the starting point X
3089
+ * @param {number} y1 the starting point Y
3090
+ * @param {number} c1x the first control point X
3091
+ * @param {number} c1y the first control point Y
3092
+ * @param {number} c2x the second control point X
3093
+ * @param {number} c2y the second control point Y
3094
+ * @param {number} x2 the ending point X
3095
+ * @param {number} y2 the ending point Y
3003
3096
  * @returns {number} the area of the cubic-bezier segment
3004
3097
  */
3005
- function getCubicSegArea(x0, y0, x1, y1, x2, y2, x3, y3) {
3006
- return (3 * ((y3 - y0) * (x1 + x2) - (x3 - x0) * (y1 + y2)
3007
- + (y1 * (x0 - x2)) - (x1 * (y0 - y2))
3008
- + (y3 * (x2 + x0 / 3)) - (x3 * (y2 + y0 / 3)))) / 20;
3098
+ function getCubicSegArea(x1, y1, c1x, c1y, c2x, c2y, x2, y2) {
3099
+ return (3 * ((y2 - y1) * (c1x + c2x) - (x2 - x1) * (c1y + c2y)
3100
+ + (c1y * (x1 - c2x)) - (c1x * (y1 - c2y))
3101
+ + (y2 * (c2x + x1 / 3)) - (x2 * (c2y + y1 / 3)))) / 20;
3009
3102
  }
3010
3103
 
3011
3104
  /**
3012
3105
  * Returns the area of a shape.
3013
3106
  * @author Jürg Lehni & Jonathan Puckey
3014
3107
  *
3015
- * => https://github.com/paperjs/paper.js/blob/develop/src/path/Path.js
3108
+ * @see https://github.com/paperjs/paper.js/blob/develop/src/path/Path.js
3016
3109
  *
3017
3110
  * @param {SVGPathCommander.pathArray} path the shape `pathArray`
3018
3111
  * @returns {number} the length of the cubic-bezier segment
3019
3112
  */
3020
3113
  function getPathArea(path) {
3021
- var x = 0; var y = 0;
3022
- var len = 0;
3114
+ var x = 0; var y = 0; var len = 0;
3115
+
3023
3116
  return pathToCurve(path).map(function (seg) {
3024
3117
  var assign, assign$1;
3025
3118
 
@@ -3030,85 +3123,255 @@
3030
3123
  default:
3031
3124
  // @ts-ignore -- the utility will have proper amount of params
3032
3125
  len = getCubicSegArea.apply(void 0, [ x, y ].concat( seg.slice(1) ));
3033
-
3034
- (assign$1 = seg.slice(-2).map(Number), x = assign$1[0], y = assign$1[1]);
3126
+ // @ts-ignore -- the segment always has numbers
3127
+ (assign$1 = seg.slice(-2), x = assign$1[0], y = assign$1[1]);
3035
3128
  return len;
3036
3129
  }
3037
3130
  }).reduce(function (a, b) { return a + b; }, 0);
3038
3131
  }
3039
3132
 
3040
3133
  /**
3041
- * @param {number} p1
3042
- * @param {number} p2
3043
- * @param {number} p3
3044
- * @param {number} p4
3134
+ * Returns the shape total length, or the equivalent to `shape.getTotalLength()`.
3135
+ *
3136
+ * This is the `pathToCurve` version which is faster and more efficient for
3137
+ * paths that are `curveArray`.
3138
+ *
3139
+ * @param {string | SVGPathCommander.curveArray} path the target `pathArray`
3140
+ * @returns {number} the `curveArray` total length
3141
+ */
3142
+ function getPathLength(path) {
3143
+ var totalLength = 0;
3144
+ pathToCurve(path).forEach(function (s, i, curveArray) {
3145
+ var args = s[0] !== 'M' ? curveArray[i - 1].slice(-2).concat( s.slice(1)) : [];
3146
+ // @ts-ignore
3147
+ totalLength += s[0] === 'M' ? 0 : segmentCubicFactory.apply(void 0, args);
3148
+ });
3149
+ return totalLength;
3150
+ }
3151
+
3152
+ /**
3153
+ * Returns the length of a A (arc-to) segment,
3154
+ * or an {x,y} point at a given length.
3155
+ *
3156
+ * @param {number} X1 the starting x position
3157
+ * @param {number} Y1 the starting y position
3158
+ * @param {number} RX x-radius of the arc
3159
+ * @param {number} RY y-radius of the arc
3160
+ * @param {number} angle x-axis-rotation of the arc
3161
+ * @param {number} LAF large-arc-flag of the arc
3162
+ * @param {number} SF sweep-flag of the arc
3163
+ * @param {number} X2 the ending x position
3164
+ * @param {number} Y2 the ending y position
3165
+ * @param {number} distance the point distance
3166
+ * @returns {{x: number, y: number} | number} the segment length or point
3167
+ */
3168
+ function segmentArcFactory(X1, Y1, RX, RY, angle, LAF, SF, X2, Y2, distance) {
3169
+ var assign;
3170
+
3171
+ var ref = [X1, Y1];
3172
+ var x1 = ref[0];
3173
+ var y1 = ref[1];
3174
+ var cubicSeg = arcToCubic(X1, Y1, RX, RY, angle, LAF, SF, X2, Y2);
3175
+ var totalLength = 0;
3176
+ var cubicSubseg = [];
3177
+ var argsc = [];
3178
+ var segLen = 0;
3179
+
3180
+ for (var i = 0, ii = cubicSeg.length; i < ii; i += 6) {
3181
+ cubicSubseg = cubicSeg.slice(i, i + 6);
3182
+ argsc = [x1, y1 ].concat( cubicSubseg);
3183
+ // @ts-ignore
3184
+ segLen = segmentCubicFactory.apply(void 0, argsc);
3185
+ if (typeof distance === 'number' && totalLength + segLen >= distance) {
3186
+ // @ts-ignore -- this is a `cubicSegment`
3187
+ return segmentCubicFactory.apply(void 0, argsc.concat( [distance - totalLength] ));
3188
+ }
3189
+ totalLength += segLen;
3190
+ (assign = cubicSubseg.slice(-2), x1 = assign[0], y1 = assign[1]);
3191
+ }
3192
+
3193
+ return totalLength;
3194
+ }
3195
+
3196
+ /**
3197
+ * Returns the {x,y} coordinates of a point at a
3198
+ * given length of a quad-bezier segment.
3199
+ *
3200
+ * @see https://github.com/substack/point-at-length
3201
+ *
3202
+ * @param {number} x1 the starting point X
3203
+ * @param {number} y1 the starting point Y
3204
+ * @param {number} cx the control point X
3205
+ * @param {number} cy the control point Y
3206
+ * @param {number} x2 the ending point X
3207
+ * @param {number} y2 the ending point Y
3045
3208
  * @param {number} t a [0-1] ratio
3046
- * @returns {number}
3209
+ * @returns {{x: number, y: number}} the requested {x,y} coordinates
3047
3210
  */
3048
- function base3(p1, p2, p3, p4, t) {
3049
- var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4;
3050
- var t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
3051
- return t * t2 - 3 * p1 + 3 * p2;
3211
+ function getPointAtQuadSegmentLength(x1, y1, cx, cy, x2, y2, t) {
3212
+ var t1 = 1 - t;
3213
+ return {
3214
+ x: (Math.pow( t1, 2 )) * x1
3215
+ + 2 * t1 * t * cx
3216
+ + (Math.pow( t, 2 )) * x2,
3217
+ y: (Math.pow( t1, 2 )) * y1
3218
+ + 2 * t1 * t * cy
3219
+ + (Math.pow( t, 2 )) * y2,
3220
+ };
3052
3221
  }
3053
3222
 
3054
3223
  /**
3055
- * Returns the C (cubic-bezier) segment length.
3224
+ * Returns the Q (quadratic-bezier) segment length,
3225
+ * or an {x,y} point at a given length.
3056
3226
  *
3057
3227
  * @param {number} x1 the starting point X
3058
3228
  * @param {number} y1 the starting point Y
3059
- * @param {number} x2 the first control point X
3060
- * @param {number} y2 the first control point Y
3061
- * @param {number} x3 the second control point X
3062
- * @param {number} y3 the second control point Y
3063
- * @param {number} x4 the ending point X
3064
- * @param {number} y4 the ending point Y
3065
- * @param {number} z a [0-1] ratio
3066
- * @returns {number} the cubic-bezier segment length
3067
- */
3068
- function getSegCubicLength(x1, y1, x2, y2, x3, y3, x4, y4, z) {
3069
- var Z = z;
3070
- if (z === null || Number.isNaN(+z)) { Z = 1; }
3071
-
3072
- // Z = Z > 1 ? 1 : Z < 0 ? 0 : Z;
3073
- if (Z > 1) { Z = 1; }
3074
- if (Z < 0) { Z = 0; }
3075
-
3076
- var z2 = Z / 2; var ct = 0; var xbase = 0; var ybase = 0; var sum = 0;
3077
- var Tvalues = [-0.1252, 0.1252, -0.3678, 0.3678,
3078
- -0.5873, 0.5873, -0.7699, 0.7699,
3079
- -0.9041, 0.9041, -0.9816, 0.9816];
3080
- var Cvalues = [0.2491, 0.2491, 0.2335, 0.2335,
3081
- 0.2032, 0.2032, 0.1601, 0.1601,
3082
- 0.1069, 0.1069, 0.0472, 0.0472];
3083
-
3084
- Tvalues.forEach(function (T, i) {
3085
- ct = z2 * T + z2;
3086
- xbase = base3(x1, x2, x3, x4, ct);
3087
- ybase = base3(y1, y2, y3, y4, ct);
3088
- sum += Cvalues[i] * Math.sqrt(xbase * xbase + ybase * ybase);
3089
- });
3090
- return z2 * sum;
3229
+ * @param {number} qx the control point X
3230
+ * @param {number} qy the control point Y
3231
+ * @param {number} x2 the ending point X
3232
+ * @param {number} y2 the ending point Y
3233
+ * @param {number=} distance the distance to point
3234
+ * @returns {{x: number, y: number} | number} the segment length or point
3235
+ */
3236
+ function segmentQuadFactory(x1, y1, qx, qy, x2, y2, distance) {
3237
+ var assign;
3238
+
3239
+ var x = x1; var y = y1;
3240
+ var totalLength = 0;
3241
+ var prev = [x1, y1, totalLength];
3242
+ /** @type {[number, number]} */
3243
+ var cur = [x1, y1];
3244
+ var t = 0;
3245
+
3246
+ var n = 101;
3247
+ for (var j = 0; j <= n; j += 1) {
3248
+ t = j / n;
3249
+
3250
+ ((assign = getPointAtQuadSegmentLength(x1, y1, qx, qy, x2, y2, t), x = assign.x, y = assign.y));
3251
+ totalLength += distanceSquareRoot(cur, [x, y]);
3252
+ cur = [x, y];
3253
+
3254
+ if (typeof distance === 'number' && totalLength >= distance) {
3255
+ var dv = (totalLength - distance) / (totalLength - prev[2]);
3256
+
3257
+ return {
3258
+ x: cur[0] * (1 - dv) + prev[0] * dv,
3259
+ y: cur[1] * (1 - dv) + prev[1] * dv,
3260
+ };
3261
+ }
3262
+ prev = [x, y, totalLength];
3263
+ }
3264
+ return totalLength;
3091
3265
  }
3092
3266
 
3093
3267
  /**
3094
- * Returns the shape total length,
3095
- * or the equivalent to `shape.getTotalLength()`
3096
- * pathToCurve version
3268
+ * Returns a {x,y} point at a given length of a shape or the shape total length.
3097
3269
  *
3098
- * @param {SVGPathCommander.pathArray} path the target `pathArray`
3099
- * @returns {number} the shape total length
3270
+ * @param {string | SVGPathCommander.pathArray} pathInput the `pathArray` to look into
3271
+ * @param {number=} distance the length of the shape to look at
3272
+ * @returns {{x: number, y: number} | number} the total length or point
3100
3273
  */
3101
- function getPathLength(path) {
3274
+ function pathLengthFactory(pathInput, distance) {
3275
+ var assign, assign$1, assign$2;
3276
+
3102
3277
  var totalLength = 0;
3103
- pathToCurve(path).forEach(function (s, i, curveArray) {
3104
- var args = s[0] !== 'M' ? curveArray[i - 1].slice(-2).concat( s.slice(1)) : [];
3105
- totalLength += s[0] === 'M' ? 0
3278
+ var isM = true;
3279
+ /** @type {number[]} */
3280
+ var data = [];
3281
+ var pathCommand = 'M';
3282
+ var segLen = 0;
3283
+ var x = 0;
3284
+ var y = 0;
3285
+ var mx = 0;
3286
+ var my = 0;
3287
+ var seg;
3288
+ var path = fixPath(normalizePath(pathInput));
3289
+
3290
+ for (var i = 0, ll = path.length; i < ll; i += 1) {
3291
+ seg = path[i];
3292
+ (assign = seg, pathCommand = assign[0]);
3293
+ isM = pathCommand === 'M';
3294
+ // @ts-ignore
3295
+ data = !isM ? [x, y ].concat( seg.slice(1)) : data;
3296
+
3297
+ // this segment is always ZERO
3298
+ if (isM) {
3299
+ // remember mx, my for Z
3106
3300
  // @ts-ignore
3107
- : getSegCubicLength.apply(void 0, args);
3108
- });
3301
+ (assign$1 = seg, mx = assign$1[1], my = assign$1[2]);
3302
+ if (typeof distance === 'number' && distance < 0.001) {
3303
+ return { x: mx, y: my };
3304
+ }
3305
+ } else if (pathCommand === 'L') {
3306
+ // @ts-ignore
3307
+ segLen = segmentLineFactory.apply(void 0, data);
3308
+ if (distance && totalLength + segLen >= distance) {
3309
+ // @ts-ignore
3310
+ return segmentLineFactory.apply(void 0, data.concat( [distance - totalLength] ));
3311
+ }
3312
+ totalLength += segLen;
3313
+ } else if (pathCommand === 'A') {
3314
+ // @ts-ignore
3315
+ segLen = segmentArcFactory.apply(void 0, data);
3316
+ if (distance && totalLength + segLen >= distance) {
3317
+ // @ts-ignore
3318
+ return segmentArcFactory.apply(void 0, data.concat( [distance - totalLength] ));
3319
+ }
3320
+ totalLength += segLen;
3321
+ } else if (pathCommand === 'C') {
3322
+ // @ts-ignore
3323
+ segLen = segmentCubicFactory.apply(void 0, data);
3324
+ if (distance && totalLength + segLen >= distance) {
3325
+ // @ts-ignore
3326
+ return segmentCubicFactory.apply(void 0, data.concat( [distance - totalLength] ));
3327
+ }
3328
+ totalLength += segLen;
3329
+ } else if (pathCommand === 'Q') {
3330
+ // @ts-ignore
3331
+ segLen = segmentQuadFactory.apply(void 0, data);
3332
+ if (distance && totalLength + segLen >= distance) {
3333
+ // @ts-ignore
3334
+ return segmentQuadFactory.apply(void 0, data.concat( [distance - totalLength] ));
3335
+ }
3336
+ totalLength += segLen;
3337
+ } else if (pathCommand === 'Z') {
3338
+ data = [x, y, mx, my];
3339
+ // @ts-ignore
3340
+ segLen = segmentLineFactory.apply(void 0, data);
3341
+ if (distance && totalLength + segLen >= distance) {
3342
+ // @ts-ignore
3343
+ return segmentLineFactory.apply(void 0, data.concat( [distance - totalLength] ));
3344
+ }
3345
+ totalLength += segLen;
3346
+ }
3347
+
3348
+ // @ts-ignore -- needed for the below
3349
+ (assign$2 = pathCommand !== 'Z' ? seg.slice(-2) : [mx, my], x = assign$2[0], y = assign$2[1]);
3350
+ }
3351
+
3352
+ // native `getPointAtLength` behavior when the given distance
3353
+ // is higher than total length
3354
+ if (distance && distance >= totalLength) {
3355
+ return { x: x, y: y };
3356
+ }
3357
+
3109
3358
  return totalLength;
3110
3359
  }
3111
3360
 
3361
+ /**
3362
+ * Returns the shape total length, or the equivalent to `shape.getTotalLength()`.
3363
+ *
3364
+ * The `normalizePath` version is lighter, faster, more efficient and more accurate
3365
+ * with paths that are not `curveArray`.
3366
+ *
3367
+ * @param {string | SVGPathCommander.pathArray} pathInput the target `pathArray`
3368
+ * @returns {number} the shape total length
3369
+ */
3370
+ function getTotalLength(pathInput) {
3371
+ // @ts-ignore - it's fine
3372
+ return pathLengthFactory(pathInput);
3373
+ }
3374
+
3112
3375
  /**
3113
3376
  * Check if a path is drawn clockwise and returns true if so,
3114
3377
  * false otherwise.
@@ -3123,34 +3386,244 @@
3123
3386
  /**
3124
3387
  * Returns [x,y] coordinates of a point at a given length of a shape.
3125
3388
  *
3126
- * @param {string | SVGPathCommander.pathArray} path the `pathArray` to look into
3389
+ * @param {string | SVGPathCommander.pathArray} pathInput the `pathArray` to look into
3390
+ * @param {number} distance the length of the shape to look at
3391
+ * @returns {{x: number, y: number}} the requested {x, y} point coordinates
3392
+ */
3393
+ function getPointAtLength(pathInput, distance) {
3394
+ // @ts-ignore
3395
+ return pathLengthFactory(pathInput, distance);
3396
+ }
3397
+
3398
+ /**
3399
+ * Returns [x,y] coordinates of a point at a given length of a shape.
3400
+ * `pathToCurve` version.
3401
+ *
3402
+ * @param {string | SVGPathCommander.pathArray} pathInput the `pathArray` to look into
3127
3403
  * @param {number} length the length of the shape to look at
3128
- * @returns {number[]} the requested [x,y] coordinates
3404
+ * @returns {{x: number, y: number}} the requested {x, y} point coordinates
3129
3405
  */
3130
- function getPointAtLength(path, length) {
3406
+ function getPointAtPathLength(pathInput, length) {
3407
+ var assign, assign$1;
3408
+
3131
3409
  var totalLength = 0;
3132
- var segLen;
3410
+ var isM = true;
3411
+ var segLen = 0;
3133
3412
  var data;
3134
- var result;
3135
- // @ts-ignore
3136
- return pathToCurve(path).map(function (seg, i, curveArray) {
3137
- data = i ? curveArray[i - 1].slice(-2).concat( seg.slice(1)) : seg.slice(1);
3413
+ var result = { x: 0, y: 0 };
3414
+ var x = 0;
3415
+ var y = 0;
3416
+ var path = pathToCurve(pathInput);
3417
+ var seg;
3418
+
3419
+ for (var i = 0, l = path.length; i < l; i += 1) {
3420
+ seg = path[i];
3421
+ isM = seg[0] === 'M';
3422
+ data = !isM ? [x, y ].concat( seg.slice(1)) : seg.slice(1);
3138
3423
  // @ts-ignore
3139
- segLen = i ? getSegCubicLength.apply(void 0, data) : 0;
3424
+ segLen = !isM ? segmentCubicFactory.apply(void 0, data) : 0;
3425
+
3426
+ if (isM) {
3427
+ (assign = seg, x = assign[1], y = assign[2]);
3428
+ } else if (totalLength + segLen > length) {
3429
+ var args = data.concat( [length - totalLength]);
3430
+
3431
+ // @ts-ignore -- `args` values are correct
3432
+ result = segmentCubicFactory.apply(void 0, args);
3433
+ break;
3434
+ }
3140
3435
  totalLength += segLen;
3436
+ // @ts-ignore - these are usually numbers
3437
+ (assign$1 = seg.slice(-2), x = assign$1[0], y = assign$1[1]);
3438
+ }
3439
+ return result;
3440
+ }
3141
3441
 
3142
- if (i === 0) {
3143
- result = { x: data[0], y: data[1] };
3144
- } else if (totalLength > length && length > totalLength - segLen) {
3145
- var args = data.concat( [1 - ((totalLength - length) / segLen)]);
3442
+ /**
3443
+ * Returns the properties at a given length in path.
3444
+ *
3445
+ * @param {string | SVGPathCommander.pathArray} pathInput target `pathArray`
3446
+ * @param {number=} distance the given length
3447
+ * @returns {SVGPathCommander.segmentProperties=} the requested properties
3448
+ */
3449
+ function getPropertiesAtLength(pathInput, distance) {
3450
+ var pathArray = parsePathString(pathInput);
3451
+ var segments = [];
3452
+
3453
+ var pathTemp = [].concat( pathArray );
3454
+ // @ts-ignore
3455
+ var pathLength = getTotalLength(pathTemp);
3456
+ var index = pathTemp.length - 1;
3457
+ var lengthAtSegment = 0;
3458
+ var length = 0;
3459
+ /** @type {SVGPathCommander.pathSegment} */
3460
+ var segment = pathArray[0];
3461
+ var ref = segment.slice(-2);
3462
+ var x = ref[0];
3463
+ var y = ref[1];
3464
+ var point = { x: x, y: y };
3465
+
3466
+ // If the path is empty, return 0.
3467
+ if (index <= 0 || !distance || !Number.isFinite(distance)) {
3468
+ return {
3469
+ // @ts-ignore
3470
+ segment: segment, index: 0, length: length, point: point, lengthAtSegment: lengthAtSegment,
3471
+ };
3472
+ }
3473
+
3474
+ if (distance >= pathLength) {
3475
+ pathTemp = pathArray.slice(0, -1);
3476
+ // @ts-ignore
3477
+ lengthAtSegment = getTotalLength(pathTemp);
3478
+ // @ts-ignore
3479
+ length = pathLength - lengthAtSegment;
3480
+ return {
3146
3481
  // @ts-ignore
3147
- result = getPointAtSegLength.apply(void 0, args);
3482
+ segment: pathArray[index], index: index, length: length, lengthAtSegment: lengthAtSegment,
3483
+ };
3484
+ }
3485
+
3486
+ while (index > 0) {
3487
+ segment = pathTemp[index];
3488
+ pathTemp = pathTemp.slice(0, -1);
3489
+ // @ts-ignore -- `pathTemp` === `pathSegment[]` === `pathArray`
3490
+ lengthAtSegment = getTotalLength(pathTemp);
3491
+ // @ts-ignore
3492
+ length = pathLength - lengthAtSegment;
3493
+ pathLength = lengthAtSegment;
3494
+ segments.push({
3495
+ segment: segment, index: index, length: length, lengthAtSegment: lengthAtSegment,
3496
+ });
3497
+ index -= 1;
3498
+ }
3499
+
3500
+ // @ts-ignore
3501
+ return segments.find(function (ref) {
3502
+ var l = ref.lengthAtSegment;
3503
+
3504
+ return l <= distance;
3505
+ });
3506
+ }
3507
+
3508
+ /**
3509
+ * Returns the point in path closest to a given point.
3510
+ * @see https://bl.ocks.org/mbostock/8027637
3511
+ *
3512
+ * @param {string | SVGPathCommander.pathArray} pathInput target `pathArray`
3513
+ * @param {{x: number, y: number}} point the given point
3514
+ * @returns {SVGPathCommander.pointProperties} the requested properties
3515
+ */
3516
+ function getPropertiesAtPoint(pathInput, point) {
3517
+ var path = fixPath(parsePathString(pathInput));
3518
+ var normalPath = normalizePath(path);
3519
+ var pathLength = getTotalLength(path);
3520
+ /** @param {{x: number, y: number}} p */
3521
+ var distanceTo = function (p) {
3522
+ var dx = p.x - point.x;
3523
+ var dy = p.y - point.y;
3524
+ return dx * dx + dy * dy;
3525
+ };
3526
+ var precision = 8;
3527
+ var scan = { x: 0, y: 0 };
3528
+ var scanDistance = 0;
3529
+ var closest = scan;
3530
+ var bestLength = 0;
3531
+ var bestDistance = Infinity;
3532
+
3533
+ // linear scan for coarse approximation
3534
+ for (var scanLength = 0; scanLength <= pathLength; scanLength += precision) {
3535
+ scan = getPointAtLength(normalPath, scanLength);
3536
+ scanDistance = distanceTo(scan);
3537
+ if (scanDistance < bestDistance) {
3538
+ closest = scan;
3539
+ bestLength = scanLength;
3540
+ bestDistance = scanDistance;
3541
+ }
3542
+ }
3543
+
3544
+ // binary search for precise estimate
3545
+ precision /= 2;
3546
+ var before = { x: 0, y: 0 };
3547
+ var after = before;
3548
+ var beforeLength = 0;
3549
+ var afterLength = 0;
3550
+ var beforeDistance = 0;
3551
+ var afterDistance = 0;
3552
+
3553
+ while (precision > 0.5) {
3554
+ beforeLength = bestLength - precision;
3555
+ before = getPointAtLength(normalPath, beforeLength);
3556
+ beforeDistance = distanceTo(before);
3557
+ afterLength = bestLength + precision;
3558
+ after = getPointAtLength(normalPath, afterLength);
3559
+ afterDistance = distanceTo(after);
3560
+ if (beforeLength >= 0 && beforeDistance < bestDistance) {
3561
+ closest = before;
3562
+ bestLength = beforeLength;
3563
+ bestDistance = beforeDistance;
3564
+ } else if (afterLength <= pathLength && afterDistance < bestDistance) {
3565
+ closest = after;
3566
+ bestLength = afterLength;
3567
+ bestDistance = afterDistance;
3148
3568
  } else {
3149
- result = null;
3569
+ precision /= 2;
3150
3570
  }
3571
+ }
3151
3572
 
3152
- return result;
3153
- }).filter(function (x) { return x; }).slice(-1)[0]; // isolate last segment
3573
+ var segment = getPropertiesAtLength(path, bestDistance);
3574
+ var distance = Math.sqrt(bestDistance);
3575
+
3576
+ return { closest: closest, distance: distance, segment: segment };
3577
+ }
3578
+
3579
+ /**
3580
+ * Returns the point in path closest to a given point.
3581
+ *
3582
+ * @param {string | SVGPathCommander.pathArray} pathInput target `pathArray`
3583
+ * @param {{x: number, y: number}} point the given point
3584
+ * @returns {{x: number, y: number}} the best match
3585
+ */
3586
+ function getClosestPoint(pathInput, point) {
3587
+ return getPropertiesAtPoint(pathInput, point).closest;
3588
+ }
3589
+
3590
+ /**
3591
+ * Returns the path segment which contains a given point.
3592
+ *
3593
+ * @param {string | SVGPathCommander.pathArray} path the `pathArray` to look into
3594
+ * @param {{x: number, y: number}} point the point of the shape to look for
3595
+ * @returns {SVGPathCommander.pathSegment?} the requested segment
3596
+ */
3597
+ function getSegmentOfPoint(path, point) {
3598
+ var props = getPropertiesAtPoint(path, point);
3599
+ var segment = props.segment;
3600
+ return typeof segment !== 'undefined' ? segment.segment : null;
3601
+ }
3602
+
3603
+ /**
3604
+ * Returns the segment at a given length.
3605
+ * @param {string | SVGPathCommander.pathArray} pathInput the target `pathArray`
3606
+ * @param {number} distance the distance in path to look at
3607
+ * @returns {SVGPathCommander.pathSegment?} the requested segment
3608
+ */
3609
+ function getSegmentAtLength(pathInput, distance) {
3610
+ var props = getPropertiesAtLength(pathInput, distance);
3611
+ var ref = typeof props !== 'undefined' ? props : { segment: null };
3612
+ var segment = ref.segment;
3613
+ return segment;
3614
+ }
3615
+
3616
+ /**
3617
+ * Checks if a given point is in the stroke of a path.
3618
+ *
3619
+ * @param {string | SVGPathCommander.pathArray} pathInput target path
3620
+ * @param {{x: number, y: number}} point
3621
+ * @returns {boolean} the query result
3622
+ */
3623
+ function isPointInStroke(pathInput, point) {
3624
+ var ref = getPropertiesAtPoint(pathInput, point);
3625
+ var distance = ref.distance;
3626
+ return Math.abs(distance) < 0.01;
3154
3627
  }
3155
3628
 
3156
3629
  /**
@@ -3406,8 +3879,16 @@
3406
3879
  getDrawDirection: getDrawDirection,
3407
3880
  getPathArea: getPathArea,
3408
3881
  getPathBBox: getPathBBox,
3882
+ getTotalLength: getTotalLength,
3409
3883
  getPathLength: getPathLength,
3410
3884
  getPointAtLength: getPointAtLength,
3885
+ getPointAtPathLength: getPointAtPathLength,
3886
+ getClosestPoint: getClosestPoint,
3887
+ getSegmentOfPoint: getSegmentOfPoint,
3888
+ getPropertiesAtPoint: getPropertiesAtPoint,
3889
+ getPropertiesAtLength: getPropertiesAtLength,
3890
+ getSegmentAtLength: getSegmentAtLength,
3891
+ isPointInStroke: isPointInStroke,
3411
3892
  clonePath: clonePath,
3412
3893
  splitPath: splitPath,
3413
3894
  fixPath: fixPath,
@@ -3421,7 +3902,7 @@
3421
3902
  options: defaultOptions,
3422
3903
  };
3423
3904
 
3424
- var version = "0.1.21";
3905
+ var version = "0.1.22";
3425
3906
 
3426
3907
  // @ts-ignore
3427
3908