svg-path-commander 0.1.21 → 0.1.25
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.
- package/README.md +72 -12
- package/dist/svg-path-commander.esm.js +658 -197
- package/dist/svg-path-commander.esm.min.js +2 -2
- package/dist/svg-path-commander.js +679 -196
- package/dist/svg-path-commander.min.js +2 -2
- package/package.json +1 -1
- package/src/convert/pathToAbsolute.js +3 -2
- package/src/convert/pathToCurve.js +2 -1
- package/src/convert/pathToRelative.js +2 -1
- package/src/math/polygonArea.js +4 -4
- package/src/math/polygonLength.js +2 -2
- package/src/parser/isSpace.js +1 -1
- package/src/parser/paramsCount.js +1 -0
- package/src/parser/parsePathString.js +4 -3
- package/src/parser/pathParser.js +2 -1
- package/src/parser/scanSegment.js +0 -1
- package/src/process/fixPath.js +1 -1
- package/src/process/lineToCubic.js +4 -5
- package/src/process/normalizePath.js +4 -2
- package/src/util/getClosestPoint.js +12 -0
- package/src/util/getCubicSize.js +41 -39
- package/src/util/getPathArea.js +19 -19
- package/src/util/getPathBBox.js +0 -11
- package/src/util/getPathLength.js +9 -9
- package/src/util/getPointAtLength.js +6 -29
- package/src/util/getPointAtPathLength.js +15 -0
- package/src/util/getPropertiesAtLength.js +63 -0
- package/src/util/getPropertiesAtPoint.js +78 -0
- package/src/util/getSegmentAtLength.js +13 -0
- package/src/util/getSegmentOfPoint.js +14 -0
- package/src/util/getTotalLength.js +15 -0
- package/src/util/isAbsoluteArray.js +2 -1
- package/src/util/isCurveArray.js +2 -1
- package/src/util/isNormalizedArray.js +2 -1
- package/src/util/isPointInStroke.js +13 -0
- package/src/util/isRelativeArray.js +2 -1
- package/src/util/pathLengthFactory.js +100 -0
- package/src/util/segmentArcFactory.js +52 -0
- package/src/util/segmentCubicFactory.js +83 -0
- package/src/util/segmentLineFactory.js +30 -0
- package/src/util/segmentQuadFactory.js +79 -0
- package/src/util/shapeToPath.js +1 -1
- package/src/util/util.js +19 -1
- package/types/index.d.ts +14 -5
- package/types/more/modules.ts +17 -5
- package/types/more/svg.d.ts +21 -0
- package/types/svg-path-commander.d.ts +253 -129
- package/src/util/getPointAtSegLength.js +0 -28
- package/src/util/getSegArcLength.js +0 -27
- package/src/util/getSegCubicLength.js +0 -52
- package/src/util/getSegLineLength.js +0 -14
- package/src/util/getSegQuadLength.js +0 -31
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* SVGPathCommander v0.1.
|
|
3
|
-
* Copyright
|
|
2
|
+
* SVGPathCommander v0.1.25 (http://thednp.github.io/svg-path-commander)
|
|
3
|
+
* Copyright 2022 © thednp
|
|
4
4
|
* Licensed under MIT (https://github.com/thednp/svg-path-commander/blob/master/LICENSE)
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
@@ -14,6 +14,7 @@ const defaultOptions = {
|
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Segment params length
|
|
17
|
+
* @type {Record<string, number>}
|
|
17
18
|
*/
|
|
18
19
|
const paramsCount = {
|
|
19
20
|
a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0,
|
|
@@ -195,7 +196,7 @@ function isSpace(ch) {
|
|
|
195
196
|
return (ch === 0x0A) || (ch === 0x0D) || (ch === 0x2028) || (ch === 0x2029) // Line terminators
|
|
196
197
|
// White spaces
|
|
197
198
|
|| (ch === 0x20) || (ch === 0x09) || (ch === 0x0B) || (ch === 0x0C) || (ch === 0xA0)
|
|
198
|
-
|| (ch >= 0x1680 && specialSpaces.
|
|
199
|
+
|| (ch >= 0x1680 && specialSpaces.includes(ch));
|
|
199
200
|
}
|
|
200
201
|
|
|
201
202
|
/**
|
|
@@ -272,7 +273,6 @@ function isArcCommand(code) {
|
|
|
272
273
|
function scanSegment(path) {
|
|
273
274
|
const { max, pathValue, index } = path;
|
|
274
275
|
const cmdCode = pathValue.charCodeAt(index);
|
|
275
|
-
// @ts-ignore
|
|
276
276
|
const reqParams = paramsCount[pathValue[index].toLowerCase()];
|
|
277
277
|
|
|
278
278
|
path.segmentStart = index;
|
|
@@ -336,7 +336,8 @@ function clonePath(path) {
|
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
/**
|
|
339
|
-
* The `PathParser` used by the
|
|
339
|
+
* The `PathParser` is used by the `parsePathString` static method
|
|
340
|
+
* to generate a `pathArray`.
|
|
340
341
|
*
|
|
341
342
|
* @param {string} pathString
|
|
342
343
|
*/
|
|
@@ -381,12 +382,13 @@ function isPathArray(path) {
|
|
|
381
382
|
* @returns {SVGPathCommander.pathArray} the resulted `pathArray`
|
|
382
383
|
*/
|
|
383
384
|
function parsePathString(pathInput) {
|
|
384
|
-
if (
|
|
385
|
+
if (isPathArray(pathInput)) {
|
|
386
|
+
// @ts-ignore -- isPathArray also checks if it's an `Array`
|
|
385
387
|
return clonePath(pathInput);
|
|
386
388
|
}
|
|
387
389
|
|
|
388
|
-
// @ts-ignore
|
|
389
|
-
const path = new PathParser(pathInput);
|
|
390
|
+
// @ts-ignore -- pathInput is now string
|
|
391
|
+
const path = new PathParser(pathInput);
|
|
390
392
|
|
|
391
393
|
skipSpaces(path);
|
|
392
394
|
|
|
@@ -414,11 +416,12 @@ function parsePathString(pathInput) {
|
|
|
414
416
|
* Iterates an array to check if it's a `pathArray`
|
|
415
417
|
* with all absolute values.
|
|
416
418
|
*
|
|
417
|
-
* @param {SVGPathCommander.pathArray} path the `pathArray` to be checked
|
|
419
|
+
* @param {string | SVGPathCommander.pathArray} path the `pathArray` to be checked
|
|
418
420
|
* @returns {boolean} iteration result
|
|
419
421
|
*/
|
|
420
422
|
function isAbsoluteArray(path) {
|
|
421
423
|
return isPathArray(path)
|
|
424
|
+
// @ts-ignore -- `isPathArray` also checks if it's `Array`
|
|
422
425
|
&& path.every((x) => x[0] === x[0].toUpperCase());
|
|
423
426
|
}
|
|
424
427
|
|
|
@@ -426,11 +429,12 @@ function isAbsoluteArray(path) {
|
|
|
426
429
|
* Parses a path string value or object and returns an array
|
|
427
430
|
* of segments, all converted to absolute values.
|
|
428
431
|
*
|
|
429
|
-
* @param {SVGPathCommander.pathArray
|
|
432
|
+
* @param {string | SVGPathCommander.pathArray} pathInput the path string | object
|
|
430
433
|
* @returns {SVGPathCommander.absoluteArray} the resulted `pathArray` with absolute values
|
|
431
434
|
*/
|
|
432
435
|
function pathToAbsolute(pathInput) {
|
|
433
|
-
if (
|
|
436
|
+
if (isAbsoluteArray(pathInput)) {
|
|
437
|
+
// @ts-ignore -- `isAbsoluteArray` checks if it's `pathArray`
|
|
434
438
|
return clonePath(pathInput);
|
|
435
439
|
}
|
|
436
440
|
|
|
@@ -515,11 +519,12 @@ function pathToAbsolute(pathInput) {
|
|
|
515
519
|
* Iterates an array to check if it's a `pathArray`
|
|
516
520
|
* with relative values.
|
|
517
521
|
*
|
|
518
|
-
* @param {SVGPathCommander.pathArray} path the `pathArray` to be checked
|
|
522
|
+
* @param {string | SVGPathCommander.pathArray} path the `pathArray` to be checked
|
|
519
523
|
* @returns {boolean} iteration result
|
|
520
524
|
*/
|
|
521
525
|
function isRelativeArray(path) {
|
|
522
526
|
return isPathArray(path)
|
|
527
|
+
// @ts-ignore -- `isPathArray` checks if it's `Array`
|
|
523
528
|
&& path.slice(1).every((seg) => seg[0] === seg[0].toLowerCase());
|
|
524
529
|
}
|
|
525
530
|
|
|
@@ -527,11 +532,12 @@ function isRelativeArray(path) {
|
|
|
527
532
|
* Parses a path string value or object and returns an array
|
|
528
533
|
* of segments, all converted to relative values.
|
|
529
534
|
*
|
|
530
|
-
* @param {SVGPathCommander.pathArray} pathInput the path string | object
|
|
535
|
+
* @param {string | SVGPathCommander.pathArray} pathInput the path string | object
|
|
531
536
|
* @returns {SVGPathCommander.relativeArray} the resulted `pathArray` with relative values
|
|
532
537
|
*/
|
|
533
538
|
function pathToRelative(pathInput) {
|
|
534
539
|
if (isRelativeArray(pathInput)) {
|
|
540
|
+
// @ts-ignore -- `isRelativeArray` checks if it's `pathArray`
|
|
535
541
|
return clonePath(pathInput);
|
|
536
542
|
}
|
|
537
543
|
|
|
@@ -728,10 +734,11 @@ function normalizeSegment(segment, params, prevCommand) {
|
|
|
728
734
|
* with all segments are in non-shorthand notation
|
|
729
735
|
* with absolute values.
|
|
730
736
|
*
|
|
731
|
-
* @param {SVGPathCommander.pathArray} path the `pathArray` to be checked
|
|
737
|
+
* @param {string | SVGPathCommander.pathArray} path the `pathArray` to be checked
|
|
732
738
|
* @returns {boolean} iteration result
|
|
733
739
|
*/
|
|
734
740
|
function isNormalizedArray(path) {
|
|
741
|
+
// @ts-ignore -- `isAbsoluteArray` also checks if it's `Array`
|
|
735
742
|
return isAbsoluteArray(path) && path.every((seg) => 'ACLMQZ'.includes(seg[0]));
|
|
736
743
|
}
|
|
737
744
|
|
|
@@ -747,14 +754,17 @@ const paramsParser = {
|
|
|
747
754
|
* * convert segments to absolute values
|
|
748
755
|
* * convert shorthand path commands to their non-shorthand notation
|
|
749
756
|
*
|
|
750
|
-
* @param {SVGPathCommander.pathArray} pathInput the string to be parsed or 'pathArray'
|
|
757
|
+
* @param {string | SVGPathCommander.pathArray} pathInput the string to be parsed or 'pathArray'
|
|
751
758
|
* @returns {SVGPathCommander.normalArray} the normalized `pathArray`
|
|
752
759
|
*/
|
|
753
760
|
function normalizePath(pathInput) {
|
|
754
761
|
if (isNormalizedArray(pathInput)) {
|
|
762
|
+
// @ts-ignore -- `isNormalizedArray` checks if it's `pathArray`
|
|
755
763
|
return clonePath(pathInput);
|
|
756
764
|
}
|
|
757
765
|
|
|
766
|
+
/** @type {SVGPathCommander.normalArray} */
|
|
767
|
+
// @ts-ignore -- `absoluteArray` will become a `normalArray`
|
|
758
768
|
const path = pathToAbsolute(pathInput);
|
|
759
769
|
const params = { ...paramsParser };
|
|
760
770
|
const allPathCommands = [];
|
|
@@ -782,7 +792,6 @@ function normalizePath(pathInput) {
|
|
|
782
792
|
params.y2 = +(segment[seglen - 3]) || params.y1;
|
|
783
793
|
}
|
|
784
794
|
|
|
785
|
-
// @ts-ignore -- a `normalArray` is absolutely an `absoluteArray`
|
|
786
795
|
return path;
|
|
787
796
|
}
|
|
788
797
|
|
|
@@ -809,7 +818,7 @@ function fixPath(pathInput) {
|
|
|
809
818
|
const [x, y] = normalArray[segBeforeZ].slice(-2);
|
|
810
819
|
|
|
811
820
|
if (isClosed && mx === x && my === y) {
|
|
812
|
-
// @ts-ignore -- `pathSegment[]` is a `pathArray`
|
|
821
|
+
// @ts-ignore -- `pathSegment[]` is quite a `pathArray`
|
|
813
822
|
return pathArray.slice(0, -1);
|
|
814
823
|
}
|
|
815
824
|
return pathArray;
|
|
@@ -819,10 +828,11 @@ function fixPath(pathInput) {
|
|
|
819
828
|
* Iterates an array to check if it's a `pathArray`
|
|
820
829
|
* with all C (cubic bezier) segments.
|
|
821
830
|
*
|
|
822
|
-
* @param {SVGPathCommander.pathArray} path the `Array` to be checked
|
|
831
|
+
* @param {string | SVGPathCommander.pathArray} path the `Array` to be checked
|
|
823
832
|
* @returns {boolean} iteration result
|
|
824
833
|
*/
|
|
825
834
|
function isCurveArray(path) {
|
|
835
|
+
// @ts-ignore -- `isPathArray` also checks if it's `Array`
|
|
826
836
|
return isPathArray(path) && path.every((seg) => 'MC'.includes(seg[0]));
|
|
827
837
|
}
|
|
828
838
|
|
|
@@ -977,35 +987,6 @@ function quadToCubic(x1, y1, qx, qy, x2, y2) {
|
|
|
977
987
|
];
|
|
978
988
|
}
|
|
979
989
|
|
|
980
|
-
/**
|
|
981
|
-
* Returns the {x,y} coordinates of a point at a
|
|
982
|
-
* given length of a cubic-bezier segment.
|
|
983
|
-
*
|
|
984
|
-
* @param {number} p1x the starting point X
|
|
985
|
-
* @param {number} p1y the starting point Y
|
|
986
|
-
* @param {number} c1x the first control point X
|
|
987
|
-
* @param {number} c1y the first control point Y
|
|
988
|
-
* @param {number} c2x the second control point X
|
|
989
|
-
* @param {number} c2y the second control point Y
|
|
990
|
-
* @param {number} p2x the ending point X
|
|
991
|
-
* @param {number} p2y the ending point Y
|
|
992
|
-
* @param {number} t a [0-1] ratio
|
|
993
|
-
* @returns {{x: number, y: number}} the requested {x,y} coordinates
|
|
994
|
-
*/
|
|
995
|
-
function getPointAtSegLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
|
|
996
|
-
const t1 = 1 - t;
|
|
997
|
-
return {
|
|
998
|
-
x: (t1 ** 3) * p1x
|
|
999
|
-
+ t1 * t1 * 3 * t * c1x
|
|
1000
|
-
+ t1 * 3 * t * t * c2x
|
|
1001
|
-
+ (t ** 3) * p2x,
|
|
1002
|
-
y: (t1 ** 3) * p1y
|
|
1003
|
-
+ t1 * t1 * 3 * t * c1y
|
|
1004
|
-
+ t1 * 3 * t * t * c2y
|
|
1005
|
-
+ (t ** 3) * p2y,
|
|
1006
|
-
};
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
990
|
/**
|
|
1010
991
|
* Returns the coordinates of a specified distance
|
|
1011
992
|
* ratio between two points.
|
|
@@ -1020,6 +1001,49 @@ function midPoint(a, b, t) {
|
|
|
1020
1001
|
return [ax + (bx - ax) * t, ay + (by - ay) * t];
|
|
1021
1002
|
}
|
|
1022
1003
|
|
|
1004
|
+
/**
|
|
1005
|
+
* Returns the square root of the distance
|
|
1006
|
+
* between two given points.
|
|
1007
|
+
*
|
|
1008
|
+
* @param {[number, number]} a the first point coordinates
|
|
1009
|
+
* @param {[number, number]} b the second point coordinates
|
|
1010
|
+
* @returns {number} the distance value
|
|
1011
|
+
*/
|
|
1012
|
+
function distanceSquareRoot(a, b) {
|
|
1013
|
+
return Math.sqrt(
|
|
1014
|
+
(a[0] - b[0]) * (a[0] - b[0])
|
|
1015
|
+
+ (a[1] - b[1]) * (a[1] - b[1]),
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* Returns the length of a line (L,V,H,Z) segment,
|
|
1021
|
+
* or a point at a given length.
|
|
1022
|
+
*
|
|
1023
|
+
* @param {number} x1 the starting point X
|
|
1024
|
+
* @param {number} y1 the starting point Y
|
|
1025
|
+
* @param {number} x2 the ending point X
|
|
1026
|
+
* @param {number} y2 the ending point Y
|
|
1027
|
+
* @param {number=} distance the distance to point
|
|
1028
|
+
* @returns {{x: number, y: number} | number} the segment length or point
|
|
1029
|
+
*/
|
|
1030
|
+
function segmentLineFactory(x1, y1, x2, y2, distance) {
|
|
1031
|
+
const length = distanceSquareRoot([x1, y1], [x2, y2]);
|
|
1032
|
+
const margin = 0.001;
|
|
1033
|
+
|
|
1034
|
+
if (typeof distance === 'number') {
|
|
1035
|
+
if (distance < margin) {
|
|
1036
|
+
return { x: x1, y: y1 };
|
|
1037
|
+
}
|
|
1038
|
+
if (distance > length) {
|
|
1039
|
+
return { x: x2, y: y2 };
|
|
1040
|
+
}
|
|
1041
|
+
const [x, y] = midPoint([x1, y1], [x2, y2], distance / length);
|
|
1042
|
+
return { x, y };
|
|
1043
|
+
}
|
|
1044
|
+
return length;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1023
1047
|
/**
|
|
1024
1048
|
* Converts an L (line-to) segment to C (cubic-bezier).
|
|
1025
1049
|
*
|
|
@@ -1040,15 +1064,14 @@ function lineToCubic(x1, y1, x2, y2) {
|
|
|
1040
1064
|
const p4 = midPoint(p2, p3, t);
|
|
1041
1065
|
const p5 = midPoint(p3, p4, t);
|
|
1042
1066
|
const p6 = midPoint(p4, p5, t);
|
|
1043
|
-
// const cp1 = getPointAtSegLength.apply(0, p0.concat(p2, p4, p6, t));
|
|
1044
1067
|
const seg1 = [...p0, ...p2, ...p4, ...p6, t];
|
|
1045
1068
|
// @ts-ignore
|
|
1046
|
-
const cp1 =
|
|
1047
|
-
// const cp2 = getPointAtSegLength.apply(0, p6.concat(p5, p3, p1, 0));
|
|
1069
|
+
const cp1 = segmentLineFactory(...seg1);
|
|
1048
1070
|
const seg2 = [...p6, ...p5, ...p3, ...p1, 0];
|
|
1049
1071
|
// @ts-ignore
|
|
1050
|
-
const cp2 =
|
|
1072
|
+
const cp2 = segmentLineFactory(...seg2);
|
|
1051
1073
|
|
|
1074
|
+
// @ts-ignore
|
|
1052
1075
|
return [cp1.x, cp1.y, cp2.x, cp2.y, x2, y2];
|
|
1053
1076
|
}
|
|
1054
1077
|
|
|
@@ -1106,11 +1129,12 @@ function segmentToCubic(segment, params) {
|
|
|
1106
1129
|
* In addition, un-necessary `Z` segment is removed if previous segment
|
|
1107
1130
|
* extends to the `M` segment.
|
|
1108
1131
|
*
|
|
1109
|
-
* @param {SVGPathCommander.pathArray} pathInput the string to be parsed or 'pathArray'
|
|
1132
|
+
* @param {string | SVGPathCommander.pathArray} pathInput the string to be parsed or 'pathArray'
|
|
1110
1133
|
* @returns {SVGPathCommander.curveArray} the resulted `pathArray` converted to cubic-bezier
|
|
1111
1134
|
*/
|
|
1112
1135
|
function pathToCurve(pathInput) {
|
|
1113
1136
|
if (isCurveArray(pathInput)) {
|
|
1137
|
+
// @ts-ignore -- `isCurveArray` checks if it's `pathArray`
|
|
1114
1138
|
return clonePath(pathInput);
|
|
1115
1139
|
}
|
|
1116
1140
|
|
|
@@ -2547,65 +2571,149 @@ function transformPath(path, transform) {
|
|
|
2547
2571
|
}
|
|
2548
2572
|
|
|
2549
2573
|
/**
|
|
2550
|
-
* Returns
|
|
2574
|
+
* Returns a point at a given length of a C (cubic-bezier) segment.
|
|
2575
|
+
*
|
|
2576
|
+
* @param {number} x1 the starting point X
|
|
2577
|
+
* @param {number} y1 the starting point Y
|
|
2578
|
+
* @param {number} c1x the first control point X
|
|
2579
|
+
* @param {number} c1y the first control point Y
|
|
2580
|
+
* @param {number} c2x the second control point X
|
|
2581
|
+
* @param {number} c2y the second control point Y
|
|
2582
|
+
* @param {number} x2 the ending point X
|
|
2583
|
+
* @param {number} y2 the ending point Y
|
|
2584
|
+
* @param {number} t a [0-1] ratio
|
|
2585
|
+
* @returns {{x: number, y: number}} the cubic-bezier segment length
|
|
2586
|
+
*/
|
|
2587
|
+
function getPointAtCubicSegmentLength(x1, y1, c1x, c1y, c2x, c2y, x2, y2, t) {
|
|
2588
|
+
const t1 = 1 - t;
|
|
2589
|
+
return {
|
|
2590
|
+
x: (t1 ** 3) * x1
|
|
2591
|
+
+ 3 * (t1 ** 2) * t * c1x
|
|
2592
|
+
+ 3 * t1 * (t ** 2) * c2x
|
|
2593
|
+
+ (t ** 3) * x2,
|
|
2594
|
+
y: (t1 ** 3) * y1
|
|
2595
|
+
+ 3 * (t1 ** 2) * t * c1y
|
|
2596
|
+
+ 3 * t1 * (t ** 2) * c2y
|
|
2597
|
+
+ (t ** 3) * y2,
|
|
2598
|
+
};
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
/**
|
|
2602
|
+
* Returns the length of a C (cubic-bezier) segment,
|
|
2603
|
+
* or an {x,y} point at a given length.
|
|
2604
|
+
*
|
|
2605
|
+
* @param {number} x1 the starting point X
|
|
2606
|
+
* @param {number} y1 the starting point Y
|
|
2607
|
+
* @param {number} c1x the first control point X
|
|
2608
|
+
* @param {number} c1y the first control point Y
|
|
2609
|
+
* @param {number} c2x the second control point X
|
|
2610
|
+
* @param {number} c2y the second control point Y
|
|
2611
|
+
* @param {number} x2 the ending point X
|
|
2612
|
+
* @param {number} y2 the ending point Y
|
|
2613
|
+
* @param {number=} distance the point distance
|
|
2614
|
+
* @returns {{x: number, y: number} | number} the segment length or point
|
|
2615
|
+
*/
|
|
2616
|
+
function segmentCubicFactory(x1, y1, c1x, c1y, c2x, c2y, x2, y2, distance) {
|
|
2617
|
+
const distanceIsNumber = typeof distance === 'number';
|
|
2618
|
+
const lengthMargin = 0.001;
|
|
2619
|
+
let x = x1; let y = y1;
|
|
2620
|
+
let totalLength = 0;
|
|
2621
|
+
let prev = [x1, y1, totalLength];
|
|
2622
|
+
/** @type {[number, number]} */
|
|
2623
|
+
let cur = [x1, y1];
|
|
2624
|
+
let t = 0;
|
|
2625
|
+
|
|
2626
|
+
if (distanceIsNumber && distance < lengthMargin) {
|
|
2627
|
+
return { x, y };
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
const n = 100;
|
|
2631
|
+
for (let j = 0; j <= n; j += 1) {
|
|
2632
|
+
t = j / n;
|
|
2633
|
+
|
|
2634
|
+
({ x, y } = getPointAtCubicSegmentLength(x1, y1, c1x, c1y, c2x, c2y, x2, y2, t));
|
|
2635
|
+
totalLength += distanceSquareRoot(cur, [x, y]);
|
|
2636
|
+
cur = [x, y];
|
|
2637
|
+
|
|
2638
|
+
if (distanceIsNumber && totalLength >= distance) {
|
|
2639
|
+
const dv = (totalLength - distance) / (totalLength - prev[2]);
|
|
2640
|
+
|
|
2641
|
+
return {
|
|
2642
|
+
x: cur[0] * (1 - dv) + prev[0] * dv,
|
|
2643
|
+
y: cur[1] * (1 - dv) + prev[1] * dv,
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
prev = [x, y, totalLength];
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
if (distanceIsNumber && distance >= totalLength) {
|
|
2650
|
+
return { x: x2, y: y2 };
|
|
2651
|
+
}
|
|
2652
|
+
return totalLength;
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
/**
|
|
2656
|
+
* Returns the cubic-bezier segment bounding box.
|
|
2551
2657
|
*
|
|
2552
|
-
* @param {number}
|
|
2553
|
-
* @param {number}
|
|
2658
|
+
* @param {number} x1 the starting point X
|
|
2659
|
+
* @param {number} y1 the starting point Y
|
|
2554
2660
|
* @param {number} c1x the first control point X
|
|
2555
2661
|
* @param {number} c1y the first control point Y
|
|
2556
2662
|
* @param {number} c2x the second control point X
|
|
2557
2663
|
* @param {number} c2y the second control point Y
|
|
2558
|
-
* @param {number}
|
|
2559
|
-
* @param {number}
|
|
2560
|
-
* @returns {SVGPathCommander.segmentLimits} the
|
|
2561
|
-
*/
|
|
2562
|
-
function getCubicSize(
|
|
2563
|
-
let a = (c2x - 2 * c1x +
|
|
2564
|
-
let b = 2 * (c1x -
|
|
2565
|
-
let c =
|
|
2664
|
+
* @param {number} x2 the ending point X
|
|
2665
|
+
* @param {number} y2 the ending point Y
|
|
2666
|
+
* @returns {SVGPathCommander.segmentLimits} the bounding box of the cubic-bezier segment
|
|
2667
|
+
*/
|
|
2668
|
+
function getCubicSize(x1, y1, c1x, c1y, c2x, c2y, x2, y2) {
|
|
2669
|
+
let a = (c2x - 2 * c1x + x1) - (x2 - 2 * c2x + c1x);
|
|
2670
|
+
let b = 2 * (c1x - x1) - 2 * (c2x - c1x);
|
|
2671
|
+
let c = x1 - c1x;
|
|
2566
2672
|
let t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
|
|
2567
2673
|
let t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
|
|
2568
|
-
const
|
|
2569
|
-
const
|
|
2570
|
-
let
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
if (Math.abs(t2) >
|
|
2674
|
+
const X = [x1, x2];
|
|
2675
|
+
const Y = [y1, y2];
|
|
2676
|
+
let x = 0;
|
|
2677
|
+
let y = 0;
|
|
2678
|
+
|
|
2679
|
+
if (Math.abs(t1) > 1e12) t1 = 0.5;
|
|
2680
|
+
if (Math.abs(t2) > 1e12) t2 = 0.5;
|
|
2575
2681
|
|
|
2576
2682
|
if (t1 > 0 && t1 < 1) {
|
|
2577
|
-
|
|
2578
|
-
x
|
|
2579
|
-
|
|
2683
|
+
// @ts-ignore
|
|
2684
|
+
({ x, y } = segmentCubicFactory(x1, y1, c1x, c1y, c2x, c2y, x2, y2, t1));
|
|
2685
|
+
X.push(x);
|
|
2686
|
+
Y.push(y);
|
|
2580
2687
|
}
|
|
2581
2688
|
if (t2 > 0 && t2 < 1) {
|
|
2582
|
-
|
|
2583
|
-
x
|
|
2584
|
-
|
|
2689
|
+
// @ts-ignore
|
|
2690
|
+
({ x, y } = segmentCubicFactory(x1, y1, c1x, c1y, c2x, c2y, x2, y2, t2));
|
|
2691
|
+
X.push(x);
|
|
2692
|
+
Y.push(y);
|
|
2585
2693
|
}
|
|
2586
|
-
a = (c2y - 2 * c1y +
|
|
2587
|
-
b = 2 * (c1y -
|
|
2588
|
-
c =
|
|
2694
|
+
a = (c2y - 2 * c1y + y1) - (y2 - 2 * c2y + c1y);
|
|
2695
|
+
b = 2 * (c1y - y1) - 2 * (c2y - c1y);
|
|
2696
|
+
c = y1 - c1y;
|
|
2589
2697
|
t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
|
|
2590
2698
|
t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
|
|
2591
|
-
|
|
2592
|
-
if (Math.abs(
|
|
2593
|
-
// @ts-ignore
|
|
2594
|
-
if (Math.abs(t2) > '1e12') t2 = 0.5;
|
|
2699
|
+
if (Math.abs(t1) > 1e12) t1 = 0.5;
|
|
2700
|
+
if (Math.abs(t2) > 1e12) t2 = 0.5;
|
|
2595
2701
|
|
|
2596
2702
|
if (t1 > 0 && t1 < 1) {
|
|
2597
|
-
|
|
2598
|
-
x
|
|
2599
|
-
|
|
2703
|
+
// @ts-ignore
|
|
2704
|
+
({ x, y } = segmentCubicFactory(x1, y1, c1x, c1y, c2x, c2y, x2, y2, t1));
|
|
2705
|
+
X.push(x);
|
|
2706
|
+
Y.push(y);
|
|
2600
2707
|
}
|
|
2601
2708
|
if (t2 > 0 && t2 < 1) {
|
|
2602
|
-
|
|
2603
|
-
x
|
|
2604
|
-
|
|
2709
|
+
// @ts-ignore
|
|
2710
|
+
({ x, y } = segmentCubicFactory(x1, y1, c1x, c1y, c2x, c2y, x2, y2, t2));
|
|
2711
|
+
X.push(x);
|
|
2712
|
+
Y.push(y);
|
|
2605
2713
|
}
|
|
2606
2714
|
return {
|
|
2607
|
-
min: { x: Math.min
|
|
2608
|
-
max: { x: Math.max
|
|
2715
|
+
min: { x: Math.min(...X), y: Math.min(...Y) },
|
|
2716
|
+
max: { x: Math.max(...X), y: Math.max(...Y) },
|
|
2609
2717
|
};
|
|
2610
2718
|
}
|
|
2611
2719
|
|
|
@@ -2641,27 +2749,16 @@ function getPathBBox(path) {
|
|
|
2641
2749
|
// @ts-ignore -- this should be fine
|
|
2642
2750
|
const dim = getCubicSize(...sizeArgs);
|
|
2643
2751
|
|
|
2644
|
-
// X = X.concat(dim.min.x, dim.max.x);
|
|
2645
2752
|
X = [...X, ...[dim.min.x, dim.max.x]];
|
|
2646
|
-
|
|
2647
|
-
// Y = Y.concat(dim.min.y, dim.max.y);
|
|
2648
2753
|
Y = [...Y, ...[dim.min.y, dim.max.y]];
|
|
2649
2754
|
x = s1;
|
|
2650
2755
|
y = s2;
|
|
2651
2756
|
}
|
|
2652
2757
|
});
|
|
2653
2758
|
|
|
2654
|
-
// @ts-ignore
|
|
2655
|
-
// const xTop = Math.min.apply(0, X);
|
|
2656
2759
|
const xTop = Math.min(...X);
|
|
2657
|
-
// @ts-ignore
|
|
2658
|
-
// const yTop = Math.min.apply(0, Y);
|
|
2659
2760
|
const yTop = Math.min(...Y);
|
|
2660
|
-
// @ts-ignore
|
|
2661
|
-
// const xBot = Math.max.apply(0, X);
|
|
2662
2761
|
const xBot = Math.max(...X);
|
|
2663
|
-
// @ts-ignore
|
|
2664
|
-
// const yBot = Math.max.apply(0, Y);
|
|
2665
2762
|
const yBot = Math.max(...Y);
|
|
2666
2763
|
const width = xBot - xTop;
|
|
2667
2764
|
const height = yBot - yTop;
|
|
@@ -2904,38 +3001,38 @@ class SVGPathCommander {
|
|
|
2904
3001
|
}
|
|
2905
3002
|
|
|
2906
3003
|
/**
|
|
2907
|
-
* Returns the area of a single segment
|
|
3004
|
+
* Returns the area of a single cubic-bezier segment.
|
|
2908
3005
|
*
|
|
2909
3006
|
* http://objectmix.com/graphics/133553-area-closed-bezier-curve.html
|
|
2910
3007
|
*
|
|
2911
|
-
* @param {number}
|
|
2912
|
-
* @param {number}
|
|
2913
|
-
* @param {number}
|
|
2914
|
-
* @param {number}
|
|
2915
|
-
* @param {number}
|
|
2916
|
-
* @param {number}
|
|
2917
|
-
* @param {number}
|
|
2918
|
-
* @param {number}
|
|
3008
|
+
* @param {number} x1 the starting point X
|
|
3009
|
+
* @param {number} y1 the starting point Y
|
|
3010
|
+
* @param {number} c1x the first control point X
|
|
3011
|
+
* @param {number} c1y the first control point Y
|
|
3012
|
+
* @param {number} c2x the second control point X
|
|
3013
|
+
* @param {number} c2y the second control point Y
|
|
3014
|
+
* @param {number} x2 the ending point X
|
|
3015
|
+
* @param {number} y2 the ending point Y
|
|
2919
3016
|
* @returns {number} the area of the cubic-bezier segment
|
|
2920
3017
|
*/
|
|
2921
|
-
function getCubicSegArea(
|
|
2922
|
-
return (3 * ((
|
|
2923
|
-
+ (
|
|
2924
|
-
+ (
|
|
3018
|
+
function getCubicSegArea(x1, y1, c1x, c1y, c2x, c2y, x2, y2) {
|
|
3019
|
+
return (3 * ((y2 - y1) * (c1x + c2x) - (x2 - x1) * (c1y + c2y)
|
|
3020
|
+
+ (c1y * (x1 - c2x)) - (c1x * (y1 - c2y))
|
|
3021
|
+
+ (y2 * (c2x + x1 / 3)) - (x2 * (c2y + y1 / 3)))) / 20;
|
|
2925
3022
|
}
|
|
2926
3023
|
|
|
2927
3024
|
/**
|
|
2928
3025
|
* Returns the area of a shape.
|
|
2929
3026
|
* @author Jürg Lehni & Jonathan Puckey
|
|
2930
3027
|
*
|
|
2931
|
-
*
|
|
3028
|
+
* @see https://github.com/paperjs/paper.js/blob/develop/src/path/Path.js
|
|
2932
3029
|
*
|
|
2933
3030
|
* @param {SVGPathCommander.pathArray} path the shape `pathArray`
|
|
2934
3031
|
* @returns {number} the length of the cubic-bezier segment
|
|
2935
3032
|
*/
|
|
2936
3033
|
function getPathArea(path) {
|
|
2937
|
-
let x = 0; let y = 0;
|
|
2938
|
-
|
|
3034
|
+
let x = 0; let y = 0; let len = 0;
|
|
3035
|
+
|
|
2939
3036
|
return pathToCurve(path).map((seg) => {
|
|
2940
3037
|
switch (seg[0]) {
|
|
2941
3038
|
case 'M':
|
|
@@ -2943,86 +3040,268 @@ function getPathArea(path) {
|
|
|
2943
3040
|
return 0;
|
|
2944
3041
|
default:
|
|
2945
3042
|
// @ts-ignore -- the utility will have proper amount of params
|
|
2946
|
-
len = getCubicSegArea(
|
|
2947
|
-
|
|
2948
|
-
[x, y] = seg.slice(-2)
|
|
3043
|
+
len = getCubicSegArea(x, y, ...seg.slice(1));
|
|
3044
|
+
// @ts-ignore -- the segment always has numbers
|
|
3045
|
+
[x, y] = seg.slice(-2);
|
|
2949
3046
|
return len;
|
|
2950
3047
|
}
|
|
2951
3048
|
}).reduce((a, b) => a + b, 0);
|
|
2952
3049
|
}
|
|
2953
3050
|
|
|
2954
3051
|
/**
|
|
2955
|
-
*
|
|
2956
|
-
*
|
|
2957
|
-
*
|
|
2958
|
-
*
|
|
3052
|
+
* Returns the shape total length, or the equivalent to `shape.getTotalLength()`.
|
|
3053
|
+
*
|
|
3054
|
+
* This is the `pathToCurve` version which is faster and more efficient for
|
|
3055
|
+
* paths that are `curveArray`.
|
|
3056
|
+
*
|
|
3057
|
+
* @param {string | SVGPathCommander.curveArray} path the target `pathArray`
|
|
3058
|
+
* @returns {number} the `curveArray` total length
|
|
3059
|
+
*/
|
|
3060
|
+
function getPathLength(path) {
|
|
3061
|
+
let totalLength = 0;
|
|
3062
|
+
pathToCurve(path).forEach((s, i, curveArray) => {
|
|
3063
|
+
const args = s[0] !== 'M' ? [...curveArray[i - 1].slice(-2), ...s.slice(1)] : [];
|
|
3064
|
+
// @ts-ignore
|
|
3065
|
+
totalLength += s[0] === 'M' ? 0 : segmentCubicFactory(...args);
|
|
3066
|
+
});
|
|
3067
|
+
return totalLength;
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
/**
|
|
3071
|
+
* Returns the length of a A (arc-to) segment,
|
|
3072
|
+
* or an {x,y} point at a given length.
|
|
3073
|
+
*
|
|
3074
|
+
* @param {number} X1 the starting x position
|
|
3075
|
+
* @param {number} Y1 the starting y position
|
|
3076
|
+
* @param {number} RX x-radius of the arc
|
|
3077
|
+
* @param {number} RY y-radius of the arc
|
|
3078
|
+
* @param {number} angle x-axis-rotation of the arc
|
|
3079
|
+
* @param {number} LAF large-arc-flag of the arc
|
|
3080
|
+
* @param {number} SF sweep-flag of the arc
|
|
3081
|
+
* @param {number} X2 the ending x position
|
|
3082
|
+
* @param {number} Y2 the ending y position
|
|
3083
|
+
* @param {number} distance the point distance
|
|
3084
|
+
* @returns {{x: number, y: number} | number} the segment length or point
|
|
3085
|
+
*/
|
|
3086
|
+
function segmentArcFactory(X1, Y1, RX, RY, angle, LAF, SF, X2, Y2, distance) {
|
|
3087
|
+
const cubicSeg = arcToCubic(X1, Y1, RX, RY, angle, LAF, SF, X2, Y2);
|
|
3088
|
+
const distanceIsNumber = typeof distance === 'number';
|
|
3089
|
+
let [x, y] = [X1, Y1];
|
|
3090
|
+
const lengthMargin = 0.001;
|
|
3091
|
+
let totalLength = 0;
|
|
3092
|
+
let cubicSubseg = [];
|
|
3093
|
+
let argsc = [];
|
|
3094
|
+
let segLen = 0;
|
|
3095
|
+
|
|
3096
|
+
if (distanceIsNumber && distance < lengthMargin) {
|
|
3097
|
+
return { x, y };
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
for (let i = 0, ii = cubicSeg.length; i < ii; i += 6) {
|
|
3101
|
+
cubicSubseg = cubicSeg.slice(i, i + 6);
|
|
3102
|
+
argsc = [x, y, ...cubicSubseg];
|
|
3103
|
+
// @ts-ignore
|
|
3104
|
+
segLen = segmentCubicFactory(...argsc);
|
|
3105
|
+
if (distanceIsNumber && totalLength + segLen >= distance) {
|
|
3106
|
+
// @ts-ignore -- this is a `cubicSegment`
|
|
3107
|
+
return segmentCubicFactory(...argsc, distance - totalLength);
|
|
3108
|
+
}
|
|
3109
|
+
totalLength += segLen;
|
|
3110
|
+
[x, y] = cubicSubseg.slice(-2);
|
|
3111
|
+
}
|
|
3112
|
+
|
|
3113
|
+
if (distanceIsNumber && distance >= totalLength) {
|
|
3114
|
+
return { x: X2, y: Y2 };
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
return totalLength;
|
|
3118
|
+
}
|
|
3119
|
+
|
|
3120
|
+
/**
|
|
3121
|
+
* Returns the {x,y} coordinates of a point at a
|
|
3122
|
+
* given length of a quad-bezier segment.
|
|
3123
|
+
*
|
|
3124
|
+
* @see https://github.com/substack/point-at-length
|
|
3125
|
+
*
|
|
3126
|
+
* @param {number} x1 the starting point X
|
|
3127
|
+
* @param {number} y1 the starting point Y
|
|
3128
|
+
* @param {number} cx the control point X
|
|
3129
|
+
* @param {number} cy the control point Y
|
|
3130
|
+
* @param {number} x2 the ending point X
|
|
3131
|
+
* @param {number} y2 the ending point Y
|
|
2959
3132
|
* @param {number} t a [0-1] ratio
|
|
2960
|
-
* @returns {number}
|
|
3133
|
+
* @returns {{x: number, y: number}} the requested {x,y} coordinates
|
|
2961
3134
|
*/
|
|
2962
|
-
function
|
|
2963
|
-
const t1 =
|
|
2964
|
-
|
|
2965
|
-
|
|
3135
|
+
function getPointAtQuadSegmentLength(x1, y1, cx, cy, x2, y2, t) {
|
|
3136
|
+
const t1 = 1 - t;
|
|
3137
|
+
return {
|
|
3138
|
+
x: (t1 ** 2) * x1
|
|
3139
|
+
+ 2 * t1 * t * cx
|
|
3140
|
+
+ (t ** 2) * x2,
|
|
3141
|
+
y: (t1 ** 2) * y1
|
|
3142
|
+
+ 2 * t1 * t * cy
|
|
3143
|
+
+ (t ** 2) * y2,
|
|
3144
|
+
};
|
|
2966
3145
|
}
|
|
2967
3146
|
|
|
2968
3147
|
/**
|
|
2969
|
-
* Returns the
|
|
3148
|
+
* Returns the Q (quadratic-bezier) segment length,
|
|
3149
|
+
* or an {x,y} point at a given length.
|
|
2970
3150
|
*
|
|
2971
3151
|
* @param {number} x1 the starting point X
|
|
2972
3152
|
* @param {number} y1 the starting point Y
|
|
2973
|
-
* @param {number}
|
|
2974
|
-
* @param {number}
|
|
2975
|
-
* @param {number}
|
|
2976
|
-
* @param {number}
|
|
2977
|
-
* @param {number}
|
|
2978
|
-
* @
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
let
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
const
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3153
|
+
* @param {number} qx the control point X
|
|
3154
|
+
* @param {number} qy the control point Y
|
|
3155
|
+
* @param {number} x2 the ending point X
|
|
3156
|
+
* @param {number} y2 the ending point Y
|
|
3157
|
+
* @param {number=} distance the distance to point
|
|
3158
|
+
* @returns {{x: number, y: number} | number} the segment length or point
|
|
3159
|
+
*/
|
|
3160
|
+
function segmentQuadFactory(x1, y1, qx, qy, x2, y2, distance) {
|
|
3161
|
+
const distanceIsNumber = typeof distance === 'number';
|
|
3162
|
+
const lengthMargin = 0.001;
|
|
3163
|
+
let x = x1; let y = y1;
|
|
3164
|
+
let totalLength = 0;
|
|
3165
|
+
let prev = [x1, y1, totalLength];
|
|
3166
|
+
/** @type {[number, number]} */
|
|
3167
|
+
let cur = [x1, y1];
|
|
3168
|
+
let t = 0;
|
|
3169
|
+
|
|
3170
|
+
if (distanceIsNumber && distance < lengthMargin) {
|
|
3171
|
+
return { x, y };
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
const n = 100;
|
|
3175
|
+
for (let j = 0; j <= n; j += 1) {
|
|
3176
|
+
t = j / n;
|
|
3177
|
+
|
|
3178
|
+
({ x, y } = getPointAtQuadSegmentLength(x1, y1, qx, qy, x2, y2, t));
|
|
3179
|
+
totalLength += distanceSquareRoot(cur, [x, y]);
|
|
3180
|
+
cur = [x, y];
|
|
3181
|
+
|
|
3182
|
+
if (distanceIsNumber && totalLength >= distance) {
|
|
3183
|
+
const dv = (totalLength - distance) / (totalLength - prev[2]);
|
|
3184
|
+
|
|
3185
|
+
return {
|
|
3186
|
+
x: cur[0] * (1 - dv) + prev[0] * dv,
|
|
3187
|
+
y: cur[1] * (1 - dv) + prev[1] * dv,
|
|
3188
|
+
};
|
|
3189
|
+
}
|
|
3190
|
+
prev = [x, y, totalLength];
|
|
3191
|
+
}
|
|
3192
|
+
if (distanceIsNumber && distance >= totalLength) {
|
|
3193
|
+
return { x: x2, y: y2 };
|
|
3194
|
+
}
|
|
3195
|
+
return totalLength;
|
|
3005
3196
|
}
|
|
3006
3197
|
|
|
3007
3198
|
/**
|
|
3008
|
-
* Returns the shape total length
|
|
3009
|
-
* or the equivalent to `shape.getTotalLength()`
|
|
3010
|
-
* pathToCurve version
|
|
3199
|
+
* Returns a {x,y} point at a given length of a shape or the shape total length.
|
|
3011
3200
|
*
|
|
3012
|
-
* @param {SVGPathCommander.pathArray}
|
|
3013
|
-
* @
|
|
3201
|
+
* @param {string | SVGPathCommander.pathArray} pathInput the `pathArray` to look into
|
|
3202
|
+
* @param {number=} distance the length of the shape to look at
|
|
3203
|
+
* @returns {{x: number, y: number} | number} the total length or point
|
|
3014
3204
|
*/
|
|
3015
|
-
function
|
|
3205
|
+
function pathLengthFactory(pathInput, distance) {
|
|
3206
|
+
const path = fixPath(normalizePath(pathInput));
|
|
3207
|
+
const distanceIsNumber = typeof distance === 'number';
|
|
3016
3208
|
let totalLength = 0;
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3209
|
+
let isM = true;
|
|
3210
|
+
/** @type {number[]} */
|
|
3211
|
+
let data = [];
|
|
3212
|
+
let pathCommand = 'M';
|
|
3213
|
+
let segLen = 0;
|
|
3214
|
+
let x = 0;
|
|
3215
|
+
let y = 0;
|
|
3216
|
+
let mx = 0;
|
|
3217
|
+
let my = 0;
|
|
3218
|
+
let seg;
|
|
3219
|
+
|
|
3220
|
+
for (let i = 0, ll = path.length; i < ll; i += 1) {
|
|
3221
|
+
seg = path[i];
|
|
3222
|
+
[pathCommand] = seg;
|
|
3223
|
+
isM = pathCommand === 'M';
|
|
3224
|
+
// @ts-ignore
|
|
3225
|
+
data = !isM ? [x, y, ...seg.slice(1)] : data;
|
|
3226
|
+
|
|
3227
|
+
// this segment is always ZERO
|
|
3228
|
+
if (isM) {
|
|
3229
|
+
// remember mx, my for Z
|
|
3020
3230
|
// @ts-ignore
|
|
3021
|
-
|
|
3022
|
-
|
|
3231
|
+
[, mx, my] = seg;
|
|
3232
|
+
if (distanceIsNumber && distance < 0.001) {
|
|
3233
|
+
return { x: mx, y: my };
|
|
3234
|
+
}
|
|
3235
|
+
} else if (pathCommand === 'L') {
|
|
3236
|
+
// @ts-ignore
|
|
3237
|
+
segLen = segmentLineFactory(...data);
|
|
3238
|
+
if (distanceIsNumber && totalLength + segLen >= distance) {
|
|
3239
|
+
// @ts-ignore
|
|
3240
|
+
return segmentLineFactory(...data, distance - totalLength);
|
|
3241
|
+
}
|
|
3242
|
+
totalLength += segLen;
|
|
3243
|
+
} else if (pathCommand === 'A') {
|
|
3244
|
+
// @ts-ignore
|
|
3245
|
+
segLen = segmentArcFactory(...data);
|
|
3246
|
+
if (distanceIsNumber && totalLength + segLen >= distance) {
|
|
3247
|
+
// @ts-ignore
|
|
3248
|
+
return segmentArcFactory(...data, distance - totalLength);
|
|
3249
|
+
}
|
|
3250
|
+
totalLength += segLen;
|
|
3251
|
+
} else if (pathCommand === 'C') {
|
|
3252
|
+
// @ts-ignore
|
|
3253
|
+
segLen = segmentCubicFactory(...data);
|
|
3254
|
+
if (distanceIsNumber && totalLength + segLen >= distance) {
|
|
3255
|
+
// @ts-ignore
|
|
3256
|
+
return segmentCubicFactory(...data, distance - totalLength);
|
|
3257
|
+
}
|
|
3258
|
+
totalLength += segLen;
|
|
3259
|
+
} else if (pathCommand === 'Q') {
|
|
3260
|
+
// @ts-ignore
|
|
3261
|
+
segLen = segmentQuadFactory(...data);
|
|
3262
|
+
if (distanceIsNumber && totalLength + segLen >= distance) {
|
|
3263
|
+
// @ts-ignore
|
|
3264
|
+
return segmentQuadFactory(...data, distance - totalLength);
|
|
3265
|
+
}
|
|
3266
|
+
totalLength += segLen;
|
|
3267
|
+
} else if (pathCommand === 'Z') {
|
|
3268
|
+
data = [x, y, mx, my];
|
|
3269
|
+
// @ts-ignore
|
|
3270
|
+
segLen = segmentLineFactory(...data);
|
|
3271
|
+
if (distanceIsNumber && totalLength + segLen >= distance) {
|
|
3272
|
+
// @ts-ignore
|
|
3273
|
+
return segmentLineFactory(...data, distance - totalLength);
|
|
3274
|
+
}
|
|
3275
|
+
totalLength += segLen;
|
|
3276
|
+
}
|
|
3277
|
+
|
|
3278
|
+
// @ts-ignore -- needed for the below
|
|
3279
|
+
[x, y] = pathCommand !== 'Z' ? seg.slice(-2) : [mx, my];
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
// native `getPointAtLength` behavior when the given distance
|
|
3283
|
+
// is higher than total length
|
|
3284
|
+
if (distanceIsNumber && distance >= totalLength) {
|
|
3285
|
+
return { x, y };
|
|
3286
|
+
}
|
|
3287
|
+
|
|
3023
3288
|
return totalLength;
|
|
3024
3289
|
}
|
|
3025
3290
|
|
|
3291
|
+
/**
|
|
3292
|
+
* Returns the shape total length, or the equivalent to `shape.getTotalLength()`.
|
|
3293
|
+
*
|
|
3294
|
+
* The `normalizePath` version is lighter, faster, more efficient and more accurate
|
|
3295
|
+
* with paths that are not `curveArray`.
|
|
3296
|
+
*
|
|
3297
|
+
* @param {string | SVGPathCommander.pathArray} pathInput the target `pathArray`
|
|
3298
|
+
* @returns {number} the shape total length
|
|
3299
|
+
*/
|
|
3300
|
+
function getTotalLength(pathInput) {
|
|
3301
|
+
// @ts-ignore - it's fine
|
|
3302
|
+
return pathLengthFactory(pathInput);
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3026
3305
|
/**
|
|
3027
3306
|
* Check if a path is drawn clockwise and returns true if so,
|
|
3028
3307
|
* false otherwise.
|
|
@@ -3037,34 +3316,208 @@ function getDrawDirection(path) {
|
|
|
3037
3316
|
/**
|
|
3038
3317
|
* Returns [x,y] coordinates of a point at a given length of a shape.
|
|
3039
3318
|
*
|
|
3040
|
-
* @param {string | SVGPathCommander.pathArray}
|
|
3041
|
-
* @param {number}
|
|
3042
|
-
* @returns {number
|
|
3319
|
+
* @param {string | SVGPathCommander.pathArray} pathInput the `pathArray` to look into
|
|
3320
|
+
* @param {number} distance the length of the shape to look at
|
|
3321
|
+
* @returns {{x: number, y: number}} the requested {x, y} point coordinates
|
|
3043
3322
|
*/
|
|
3044
|
-
function getPointAtLength(
|
|
3045
|
-
let totalLength = 0;
|
|
3046
|
-
let segLen;
|
|
3047
|
-
let data;
|
|
3048
|
-
let result;
|
|
3323
|
+
function getPointAtLength(pathInput, distance) {
|
|
3049
3324
|
// @ts-ignore
|
|
3050
|
-
return
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3325
|
+
return pathLengthFactory(pathInput, distance);
|
|
3326
|
+
}
|
|
3327
|
+
|
|
3328
|
+
/**
|
|
3329
|
+
* Returns [x,y] coordinates of a point at a given length of a shape.
|
|
3330
|
+
* `pathToCurve` version.
|
|
3331
|
+
*
|
|
3332
|
+
* @deprecated
|
|
3333
|
+
*
|
|
3334
|
+
* @param {string | SVGPathCommander.pathArray} pathInput the `pathArray` to look into
|
|
3335
|
+
* @param {number} distance the length of the shape to look at
|
|
3336
|
+
* @returns {{x: number, y: number}} the requested {x, y} point coordinates
|
|
3337
|
+
*/
|
|
3338
|
+
function getPointAtPathLength(pathInput, distance) {
|
|
3339
|
+
return getPointAtLength(pathInput, distance);
|
|
3340
|
+
}
|
|
3055
3341
|
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3342
|
+
/**
|
|
3343
|
+
* Returns the segment, its index and length as well as
|
|
3344
|
+
* the length to that segment at a given length in a path.
|
|
3345
|
+
*
|
|
3346
|
+
* @param {string | SVGPathCommander.pathArray} pathInput target `pathArray`
|
|
3347
|
+
* @param {number=} distance the given length
|
|
3348
|
+
* @returns {SVGPathCommander.segmentProperties=} the requested properties
|
|
3349
|
+
*/
|
|
3350
|
+
function getPropertiesAtLength(pathInput, distance) {
|
|
3351
|
+
const pathArray = parsePathString(pathInput);
|
|
3352
|
+
const segments = [];
|
|
3353
|
+
|
|
3354
|
+
let pathTemp = [...pathArray];
|
|
3355
|
+
// @ts-ignore
|
|
3356
|
+
let pathLength = getTotalLength(pathTemp);
|
|
3357
|
+
let index = pathTemp.length - 1;
|
|
3358
|
+
let lengthAtSegment = 0;
|
|
3359
|
+
let length = 0;
|
|
3360
|
+
/** @type {SVGPathCommander.pathSegment} */
|
|
3361
|
+
let segment = pathArray[0];
|
|
3362
|
+
const [x, y] = segment.slice(-2);
|
|
3363
|
+
const point = { x, y };
|
|
3364
|
+
|
|
3365
|
+
// If the path is empty, return 0.
|
|
3366
|
+
if (index <= 0 || !distance || !Number.isFinite(distance)) {
|
|
3367
|
+
return {
|
|
3368
|
+
// @ts-ignore
|
|
3369
|
+
segment, index: 0, length, point, lengthAtSegment,
|
|
3370
|
+
};
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3373
|
+
if (distance >= pathLength) {
|
|
3374
|
+
pathTemp = pathArray.slice(0, -1);
|
|
3375
|
+
// @ts-ignore
|
|
3376
|
+
lengthAtSegment = getTotalLength(pathTemp);
|
|
3377
|
+
// @ts-ignore
|
|
3378
|
+
length = pathLength - lengthAtSegment;
|
|
3379
|
+
return {
|
|
3060
3380
|
// @ts-ignore
|
|
3061
|
-
|
|
3381
|
+
segment: pathArray[index], index, length, lengthAtSegment,
|
|
3382
|
+
};
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
while (index > 0) {
|
|
3386
|
+
segment = pathTemp[index];
|
|
3387
|
+
pathTemp = pathTemp.slice(0, -1);
|
|
3388
|
+
// @ts-ignore -- `pathTemp` === `pathSegment[]` === `pathArray`
|
|
3389
|
+
lengthAtSegment = getTotalLength(pathTemp);
|
|
3390
|
+
// @ts-ignore
|
|
3391
|
+
length = pathLength - lengthAtSegment;
|
|
3392
|
+
pathLength = lengthAtSegment;
|
|
3393
|
+
segments.push({
|
|
3394
|
+
segment, index, length, lengthAtSegment,
|
|
3395
|
+
});
|
|
3396
|
+
index -= 1;
|
|
3397
|
+
}
|
|
3398
|
+
|
|
3399
|
+
// @ts-ignore
|
|
3400
|
+
return segments.find(({ lengthAtSegment: l }) => l <= distance);
|
|
3401
|
+
}
|
|
3402
|
+
|
|
3403
|
+
/**
|
|
3404
|
+
* Returns the point and segment in path closest to a given point as well as
|
|
3405
|
+
* the distance to the path stroke.
|
|
3406
|
+
* @see https://bl.ocks.org/mbostock/8027637
|
|
3407
|
+
*
|
|
3408
|
+
* @param {string | SVGPathCommander.pathArray} pathInput target `pathArray`
|
|
3409
|
+
* @param {{x: number, y: number}} point the given point
|
|
3410
|
+
* @returns {SVGPathCommander.pointProperties} the requested properties
|
|
3411
|
+
*/
|
|
3412
|
+
function getPropertiesAtPoint(pathInput, point) {
|
|
3413
|
+
const path = fixPath(parsePathString(pathInput));
|
|
3414
|
+
const normalPath = normalizePath(path);
|
|
3415
|
+
const pathLength = getTotalLength(path);
|
|
3416
|
+
/** @param {{x: number, y: number}} p */
|
|
3417
|
+
const distanceTo = (p) => {
|
|
3418
|
+
const dx = p.x - point.x;
|
|
3419
|
+
const dy = p.y - point.y;
|
|
3420
|
+
return dx * dx + dy * dy;
|
|
3421
|
+
};
|
|
3422
|
+
let precision = 8;
|
|
3423
|
+
let scan = { x: 0, y: 0 };
|
|
3424
|
+
let scanDistance = 0;
|
|
3425
|
+
let closest = scan;
|
|
3426
|
+
let bestLength = 0;
|
|
3427
|
+
let bestDistance = Infinity;
|
|
3428
|
+
|
|
3429
|
+
// linear scan for coarse approximation
|
|
3430
|
+
for (let scanLength = 0; scanLength <= pathLength; scanLength += precision) {
|
|
3431
|
+
scan = getPointAtLength(normalPath, scanLength);
|
|
3432
|
+
scanDistance = distanceTo(scan);
|
|
3433
|
+
if (scanDistance < bestDistance) {
|
|
3434
|
+
closest = scan;
|
|
3435
|
+
bestLength = scanLength;
|
|
3436
|
+
bestDistance = scanDistance;
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
|
|
3440
|
+
// binary search for precise estimate
|
|
3441
|
+
precision /= 2;
|
|
3442
|
+
let before = { x: 0, y: 0 };
|
|
3443
|
+
let after = before;
|
|
3444
|
+
let beforeLength = 0;
|
|
3445
|
+
let afterLength = 0;
|
|
3446
|
+
let beforeDistance = 0;
|
|
3447
|
+
let afterDistance = 0;
|
|
3448
|
+
|
|
3449
|
+
while (precision > 0.5) {
|
|
3450
|
+
beforeLength = bestLength - precision;
|
|
3451
|
+
before = getPointAtLength(normalPath, beforeLength);
|
|
3452
|
+
beforeDistance = distanceTo(before);
|
|
3453
|
+
afterLength = bestLength + precision;
|
|
3454
|
+
after = getPointAtLength(normalPath, afterLength);
|
|
3455
|
+
afterDistance = distanceTo(after);
|
|
3456
|
+
if (beforeLength >= 0 && beforeDistance < bestDistance) {
|
|
3457
|
+
closest = before;
|
|
3458
|
+
bestLength = beforeLength;
|
|
3459
|
+
bestDistance = beforeDistance;
|
|
3460
|
+
} else if (afterLength <= pathLength && afterDistance < bestDistance) {
|
|
3461
|
+
closest = after;
|
|
3462
|
+
bestLength = afterLength;
|
|
3463
|
+
bestDistance = afterDistance;
|
|
3062
3464
|
} else {
|
|
3063
|
-
|
|
3465
|
+
precision /= 2;
|
|
3064
3466
|
}
|
|
3467
|
+
}
|
|
3065
3468
|
|
|
3066
|
-
|
|
3067
|
-
|
|
3469
|
+
const segment = getPropertiesAtLength(path, bestLength);
|
|
3470
|
+
const distance = Math.sqrt(bestDistance);
|
|
3471
|
+
|
|
3472
|
+
return { closest, distance, segment };
|
|
3473
|
+
}
|
|
3474
|
+
|
|
3475
|
+
/**
|
|
3476
|
+
* Returns the point in path closest to a given point.
|
|
3477
|
+
*
|
|
3478
|
+
* @param {string | SVGPathCommander.pathArray} pathInput target `pathArray`
|
|
3479
|
+
* @param {{x: number, y: number}} point the given point
|
|
3480
|
+
* @returns {{x: number, y: number}} the best match
|
|
3481
|
+
*/
|
|
3482
|
+
function getClosestPoint(pathInput, point) {
|
|
3483
|
+
return getPropertiesAtPoint(pathInput, point).closest;
|
|
3484
|
+
}
|
|
3485
|
+
|
|
3486
|
+
/**
|
|
3487
|
+
* Returns the path segment which contains a given point.
|
|
3488
|
+
*
|
|
3489
|
+
* @param {string | SVGPathCommander.pathArray} path the `pathArray` to look into
|
|
3490
|
+
* @param {{x: number, y: number}} point the point of the shape to look for
|
|
3491
|
+
* @returns {SVGPathCommander.pathSegment?} the requested segment
|
|
3492
|
+
*/
|
|
3493
|
+
function getSegmentOfPoint(path, point) {
|
|
3494
|
+
const props = getPropertiesAtPoint(path, point);
|
|
3495
|
+
const { segment } = props;
|
|
3496
|
+
return typeof segment !== 'undefined' ? segment.segment : null;
|
|
3497
|
+
}
|
|
3498
|
+
|
|
3499
|
+
/**
|
|
3500
|
+
* Returns the segment at a given length.
|
|
3501
|
+
* @param {string | SVGPathCommander.pathArray} pathInput the target `pathArray`
|
|
3502
|
+
* @param {number} distance the distance in path to look at
|
|
3503
|
+
* @returns {SVGPathCommander.pathSegment?} the requested segment
|
|
3504
|
+
*/
|
|
3505
|
+
function getSegmentAtLength(pathInput, distance) {
|
|
3506
|
+
const props = getPropertiesAtLength(pathInput, distance);
|
|
3507
|
+
const { segment } = typeof props !== 'undefined' ? props : { segment: null };
|
|
3508
|
+
return segment;
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3511
|
+
/**
|
|
3512
|
+
* Checks if a given point is in the stroke of a path.
|
|
3513
|
+
*
|
|
3514
|
+
* @param {string | SVGPathCommander.pathArray} pathInput target path
|
|
3515
|
+
* @param {{x: number, y: number}} point
|
|
3516
|
+
* @returns {boolean} the query result
|
|
3517
|
+
*/
|
|
3518
|
+
function isPointInStroke(pathInput, point) {
|
|
3519
|
+
const { distance } = getPropertiesAtPoint(pathInput, point);
|
|
3520
|
+
return Math.abs(distance) < 0.01;
|
|
3068
3521
|
}
|
|
3069
3522
|
|
|
3070
3523
|
/**
|
|
@@ -3126,7 +3579,7 @@ function getPolyPath(attr) {
|
|
|
3126
3579
|
/** @type {SVGPathCommander.pathArray} */
|
|
3127
3580
|
// @ts-ignore -- it's an empty `pathArray`
|
|
3128
3581
|
const pathArray = [];
|
|
3129
|
-
const points = attr.points.split(/[\s|,]/).map(Number);
|
|
3582
|
+
const points = attr.points.trim().split(/[\s|,]/).map(Number);
|
|
3130
3583
|
|
|
3131
3584
|
let index = 0;
|
|
3132
3585
|
while (index < points.length) {
|
|
@@ -3320,8 +3773,16 @@ const Util = {
|
|
|
3320
3773
|
getDrawDirection,
|
|
3321
3774
|
getPathArea,
|
|
3322
3775
|
getPathBBox,
|
|
3776
|
+
getTotalLength,
|
|
3323
3777
|
getPathLength,
|
|
3324
3778
|
getPointAtLength,
|
|
3779
|
+
getPointAtPathLength,
|
|
3780
|
+
getClosestPoint,
|
|
3781
|
+
getSegmentOfPoint,
|
|
3782
|
+
getPropertiesAtPoint,
|
|
3783
|
+
getPropertiesAtLength,
|
|
3784
|
+
getSegmentAtLength,
|
|
3785
|
+
isPointInStroke,
|
|
3325
3786
|
clonePath,
|
|
3326
3787
|
splitPath,
|
|
3327
3788
|
fixPath,
|
|
@@ -3335,7 +3796,7 @@ const Util = {
|
|
|
3335
3796
|
options: defaultOptions,
|
|
3336
3797
|
};
|
|
3337
3798
|
|
|
3338
|
-
var version = "0.1.
|
|
3799
|
+
var version = "0.1.25";
|
|
3339
3800
|
|
|
3340
3801
|
// @ts-ignore
|
|
3341
3802
|
|