svg-path-commander 0.1.23 → 0.2.0-alpha1
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 +6 -4
- package/dist/svg-path-commander.es5.js +3948 -0
- package/dist/svg-path-commander.es5.min.js +2 -0
- package/dist/svg-path-commander.esm.js +438 -411
- package/dist/svg-path-commander.esm.min.js +2 -2
- package/dist/svg-path-commander.js +1455 -1530
- package/dist/svg-path-commander.min.js +2 -2
- package/package.json +15 -2
- package/src/convert/pathToAbsolute.js +4 -4
- package/src/convert/pathToCurve.js +2 -2
- package/src/convert/pathToRelative.js +4 -4
- package/src/convert/pathToString.js +2 -2
- package/src/math/polygonLength.js +1 -2
- package/src/options/options.js +1 -1
- package/src/parser/error.js +2 -0
- package/src/parser/finalizeSegment.js +1 -1
- package/src/parser/isSpace.js +1 -1
- package/src/parser/paramsParser.js +1 -1
- package/src/parser/parsePathString.js +6 -11
- package/src/parser/pathParser.js +1 -1
- package/src/parser/scanFlag.js +3 -3
- package/src/parser/scanParam.js +7 -6
- package/src/parser/scanSegment.js +3 -2
- package/src/parser/skipSpaces.js +1 -1
- package/src/process/clonePath.js +1 -1
- package/src/process/fixArc.js +1 -1
- package/src/process/fixPath.js +2 -2
- package/src/process/getSVGMatrix.js +3 -2
- package/src/process/normalizePath.js +3 -3
- package/src/process/normalizeSegment.js +2 -2
- package/src/process/optimizePath.js +2 -2
- package/src/process/projection2d.js +2 -2
- package/src/process/reverseCurve.js +3 -3
- package/src/process/reversePath.js +4 -5
- package/src/process/roundPath.js +7 -6
- package/src/process/segmentToCubic.js +3 -3
- package/src/process/shortenSegment.js +3 -3
- package/src/process/splitCubic.js +1 -1
- package/src/process/splitPath.js +1 -1
- package/src/process/transformPath.js +8 -7
- package/src/svg-path-commander.js +60 -20
- package/src/util/getClosestPoint.js +1 -1
- package/src/util/getCubicSize.js +1 -1
- package/src/util/getDrawDirection.js +1 -1
- package/src/util/getPathArea.js +1 -1
- package/src/util/getPathBBox.js +2 -2
- package/src/util/getPathLength.js +1 -1
- package/src/util/getPointAtLength.js +1 -1
- package/src/util/getPropertiesAtLength.js +5 -4
- package/src/util/getPropertiesAtPoint.js +5 -4
- package/src/util/getSegmentAtLength.js +2 -2
- package/src/util/getSegmentOfPoint.js +2 -2
- package/src/util/getTotalLength.js +1 -1
- package/src/util/isAbsoluteArray.js +1 -1
- package/src/util/isCurveArray.js +1 -1
- package/src/util/isNormalizedArray.js +1 -1
- package/src/util/isPathArray.js +1 -1
- package/src/util/isPointInStroke.js +2 -2
- package/src/util/isRelativeArray.js +1 -1
- package/src/util/pathLengthFactory.js +12 -10
- package/src/util/segmentArcFactory.js +6 -5
- package/src/util/segmentCubicFactory.js +6 -5
- package/src/util/segmentLineFactory.js +2 -2
- package/src/util/segmentQuadFactory.js +6 -5
- package/src/util/shapeToPath.js +14 -14
- package/src/util/util.js +0 -2
- package/types/index.d.ts +1 -3
- package/types/more/modules.ts +0 -3
- package/types/more/svg.d.ts +11 -10
- package/types/svg-path-commander.d.ts +218 -228
- package/src/util/createPath.js +0 -17
- package/src/util/getPointAtPathLength.js +0 -15
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* SVGPathCommander v0.
|
|
3
|
-
* Copyright
|
|
2
|
+
* SVGPathCommander v0.2.0alpha1 (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
|
/**
|
|
7
7
|
* SVGPathCommander default options
|
|
8
|
-
* @type {
|
|
8
|
+
* @type {SVGPath.options}
|
|
9
9
|
*/
|
|
10
10
|
const defaultOptions = {
|
|
11
11
|
origin: [0, 0, 0],
|
|
@@ -23,7 +23,7 @@ const paramsCount = {
|
|
|
23
23
|
/**
|
|
24
24
|
* Breaks the parsing of a pathString once a segment is finalized.
|
|
25
25
|
*
|
|
26
|
-
* @param {
|
|
26
|
+
* @param {SVGPath.PathParser} path the `PathParser` instance
|
|
27
27
|
*/
|
|
28
28
|
function finalizeSegment(path) {
|
|
29
29
|
let pathCommand = path.pathValue[path.segmentStart];
|
|
@@ -51,13 +51,13 @@ function finalizeSegment(path) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
const
|
|
54
|
+
const error = 'SVGPathCommander error';
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* Validates an A (arc-to) specific path command value.
|
|
58
58
|
* Usually a `large-arc-flag` or `sweep-flag`.
|
|
59
59
|
*
|
|
60
|
-
* @param {
|
|
60
|
+
* @param {SVGPath.PathParser} path the `PathParser` instance
|
|
61
61
|
*/
|
|
62
62
|
function scanFlag(path) {
|
|
63
63
|
const { index } = path;
|
|
@@ -75,7 +75,7 @@ function scanFlag(path) {
|
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
path.err = `${
|
|
78
|
+
path.err = `${error}: invalid Arc flag "${ch}", expecting 0 or 1 at index ${index}`;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/**
|
|
@@ -88,11 +88,13 @@ function isDigit(code) {
|
|
|
88
88
|
return (code >= 48 && code <= 57); // 0..9
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
const invalidPathValue = 'Invalid path value';
|
|
92
|
+
|
|
91
93
|
/**
|
|
92
94
|
* Validates every character of the path string,
|
|
93
95
|
* every path command, negative numbers or floating point numbers.
|
|
94
96
|
*
|
|
95
|
-
* @param {
|
|
97
|
+
* @param {SVGPath.PathParser} path the `PathParser` instance
|
|
96
98
|
*/
|
|
97
99
|
function scanParam(path) {
|
|
98
100
|
const { max, pathValue, index: start } = path;
|
|
@@ -105,7 +107,7 @@ function scanParam(path) {
|
|
|
105
107
|
|
|
106
108
|
if (index >= max) {
|
|
107
109
|
// path.err = 'SvgPath: missed param (at pos ' + index + ')';
|
|
108
|
-
path.err = `${invalidPathValue} at ${index}
|
|
110
|
+
path.err = `${error}: ${invalidPathValue} at index ${index}, "pathValue" is missing param`;
|
|
109
111
|
return;
|
|
110
112
|
}
|
|
111
113
|
ch = pathValue.charCodeAt(index);
|
|
@@ -119,7 +121,7 @@ function scanParam(path) {
|
|
|
119
121
|
// https://github.com/ariya/esprimas
|
|
120
122
|
if (!isDigit(ch) && ch !== 0x2E/* . */) {
|
|
121
123
|
// path.err = 'SvgPath: param should start with 0..9 or `.` (at pos ' + index + ')';
|
|
122
|
-
path.err = `${invalidPathValue} at index ${index}
|
|
124
|
+
path.err = `${error}: ${invalidPathValue} at index ${index}, "${pathValue[index]}" is not a number`;
|
|
123
125
|
return;
|
|
124
126
|
}
|
|
125
127
|
|
|
@@ -134,7 +136,7 @@ function scanParam(path) {
|
|
|
134
136
|
if (ch && isDigit(ch)) {
|
|
135
137
|
// path.err = 'SvgPath: numbers started with `0` such as `09`
|
|
136
138
|
// are illegal (at pos ' + start + ')';
|
|
137
|
-
path.err = `${invalidPathValue} at index ${start}
|
|
139
|
+
path.err = `${error}: ${invalidPathValue} at index ${start}, "${pathValue[start]}" illegal number`;
|
|
138
140
|
return;
|
|
139
141
|
}
|
|
140
142
|
}
|
|
@@ -158,7 +160,7 @@ function scanParam(path) {
|
|
|
158
160
|
|
|
159
161
|
if (ch === 0x65/* e */ || ch === 0x45/* E */) {
|
|
160
162
|
if (hasDot && !hasCeiling && !hasDecimal) {
|
|
161
|
-
path.err = `${invalidPathValue} at index ${index}
|
|
163
|
+
path.err = `${error}: ${invalidPathValue} at index ${index}, "${pathValue[index]}" invalid float exponent`;
|
|
162
164
|
return;
|
|
163
165
|
}
|
|
164
166
|
|
|
@@ -174,7 +176,7 @@ function scanParam(path) {
|
|
|
174
176
|
}
|
|
175
177
|
} else {
|
|
176
178
|
// path.err = 'SvgPath: invalid float exponent (at pos ' + index + ')';
|
|
177
|
-
path.err = `${invalidPathValue} at index ${index}: ${pathValue[index]} invalid float exponent`;
|
|
179
|
+
path.err = `${error}: ${invalidPathValue} at index ${index}: "${pathValue[index]}" invalid float exponent`;
|
|
178
180
|
return;
|
|
179
181
|
}
|
|
180
182
|
}
|
|
@@ -196,7 +198,7 @@ function isSpace(ch) {
|
|
|
196
198
|
return (ch === 0x0A) || (ch === 0x0D) || (ch === 0x2028) || (ch === 0x2029) // Line terminators
|
|
197
199
|
// White spaces
|
|
198
200
|
|| (ch === 0x20) || (ch === 0x09) || (ch === 0x0B) || (ch === 0x0C) || (ch === 0xA0)
|
|
199
|
-
|| (ch >= 0x1680 && specialSpaces.
|
|
201
|
+
|| (ch >= 0x1680 && specialSpaces.includes(ch));
|
|
200
202
|
}
|
|
201
203
|
|
|
202
204
|
/**
|
|
@@ -204,7 +206,7 @@ function isSpace(ch) {
|
|
|
204
206
|
* path string every time it encounters any kind of
|
|
205
207
|
* space character.
|
|
206
208
|
*
|
|
207
|
-
* @param {
|
|
209
|
+
* @param {SVGPath.PathParser} path the `PathParser` instance
|
|
208
210
|
*/
|
|
209
211
|
function skipSpaces(path) {
|
|
210
212
|
const { pathValue, max } = path;
|
|
@@ -268,7 +270,7 @@ function isArcCommand(code) {
|
|
|
268
270
|
* Scans every character in the path string to determine
|
|
269
271
|
* where a segment starts and where it ends.
|
|
270
272
|
*
|
|
271
|
-
* @param {
|
|
273
|
+
* @param {SVGPath.PathParser} path the `PathParser` instance
|
|
272
274
|
*/
|
|
273
275
|
function scanSegment(path) {
|
|
274
276
|
const { max, pathValue, index } = path;
|
|
@@ -278,7 +280,7 @@ function scanSegment(path) {
|
|
|
278
280
|
path.segmentStart = index;
|
|
279
281
|
|
|
280
282
|
if (!isPathCommand(cmdCode)) {
|
|
281
|
-
path.err = `${
|
|
283
|
+
path.err = `${error}: ${invalidPathValue} "${pathValue[index]}" is not a path command`;
|
|
282
284
|
return;
|
|
283
285
|
}
|
|
284
286
|
|
|
@@ -328,7 +330,7 @@ function scanSegment(path) {
|
|
|
328
330
|
/**
|
|
329
331
|
* Returns a clone of an existing `pathArray`.
|
|
330
332
|
*
|
|
331
|
-
* @param {
|
|
333
|
+
* @param {SVGPath.pathArray | SVGPath.pathSegment} path the source `pathArray`
|
|
332
334
|
* @returns {any} the cloned `pathArray`
|
|
333
335
|
*/
|
|
334
336
|
function clonePath(path) {
|
|
@@ -342,7 +344,7 @@ function clonePath(path) {
|
|
|
342
344
|
* @param {string} pathString
|
|
343
345
|
*/
|
|
344
346
|
function PathParser(pathString) {
|
|
345
|
-
/** @type {
|
|
347
|
+
/** @type {SVGPath.pathArray} */
|
|
346
348
|
// @ts-ignore
|
|
347
349
|
this.segments = [];
|
|
348
350
|
/** @type {string} */
|
|
@@ -364,7 +366,7 @@ function PathParser(pathString) {
|
|
|
364
366
|
/**
|
|
365
367
|
* Iterates an array to check if it's an actual `pathArray`.
|
|
366
368
|
*
|
|
367
|
-
* @param {string |
|
|
369
|
+
* @param {string | SVGPath.pathArray} path the `pathArray` to be checked
|
|
368
370
|
* @returns {boolean} iteration result
|
|
369
371
|
*/
|
|
370
372
|
function isPathArray(path) {
|
|
@@ -378,8 +380,8 @@ function isPathArray(path) {
|
|
|
378
380
|
* Parses a path string value and returns an array
|
|
379
381
|
* of segments we like to call `pathArray`.
|
|
380
382
|
*
|
|
381
|
-
* @param {
|
|
382
|
-
* @returns {
|
|
383
|
+
* @param {SVGPath.pathArray | string} pathInput the string to be parsed
|
|
384
|
+
* @returns {SVGPath.pathArray | string} the resulted `pathArray`
|
|
383
385
|
*/
|
|
384
386
|
function parsePathString(pathInput) {
|
|
385
387
|
if (isPathArray(pathInput)) {
|
|
@@ -396,27 +398,22 @@ function parsePathString(pathInput) {
|
|
|
396
398
|
scanSegment(path);
|
|
397
399
|
}
|
|
398
400
|
|
|
399
|
-
if (path.err.length) {
|
|
400
|
-
// @ts-ignore
|
|
401
|
-
path.segments = [];
|
|
402
|
-
} else if (path.segments.length) {
|
|
401
|
+
if (!path.err.length) {
|
|
403
402
|
if (!'mM'.includes(path.segments[0][0])) {
|
|
404
|
-
path.err = `${
|
|
405
|
-
// @ts-ignore
|
|
406
|
-
path.segments = [];
|
|
403
|
+
path.err = `${error}: missing M/m`;
|
|
407
404
|
} else {
|
|
408
405
|
path.segments[0][0] = 'M';
|
|
409
406
|
}
|
|
410
407
|
}
|
|
411
408
|
|
|
412
|
-
return path.segments;
|
|
409
|
+
return path.err ? path.err : path.segments;
|
|
413
410
|
}
|
|
414
411
|
|
|
415
412
|
/**
|
|
416
413
|
* Iterates an array to check if it's a `pathArray`
|
|
417
414
|
* with all absolute values.
|
|
418
415
|
*
|
|
419
|
-
* @param {string |
|
|
416
|
+
* @param {string | SVGPath.pathArray} path the `pathArray` to be checked
|
|
420
417
|
* @returns {boolean} iteration result
|
|
421
418
|
*/
|
|
422
419
|
function isAbsoluteArray(path) {
|
|
@@ -429,8 +426,8 @@ function isAbsoluteArray(path) {
|
|
|
429
426
|
* Parses a path string value or object and returns an array
|
|
430
427
|
* of segments, all converted to absolute values.
|
|
431
428
|
*
|
|
432
|
-
* @param {string |
|
|
433
|
-
* @returns {
|
|
429
|
+
* @param {string | SVGPath.pathArray} pathInput the path string | object
|
|
430
|
+
* @returns {SVGPath.absoluteArray} the resulted `pathArray` with absolute values
|
|
434
431
|
*/
|
|
435
432
|
function pathToAbsolute(pathInput) {
|
|
436
433
|
if (isAbsoluteArray(pathInput)) {
|
|
@@ -446,7 +443,7 @@ function pathToAbsolute(pathInput) {
|
|
|
446
443
|
return path.map((segment) => {
|
|
447
444
|
const values = segment.slice(1).map(Number);
|
|
448
445
|
const [pathCommand] = segment;
|
|
449
|
-
/** @type {
|
|
446
|
+
/** @type {SVGPath.absoluteCommand} */
|
|
450
447
|
// @ts-ignore
|
|
451
448
|
const absCommand = pathCommand.toUpperCase();
|
|
452
449
|
|
|
@@ -456,7 +453,7 @@ function pathToAbsolute(pathInput) {
|
|
|
456
453
|
my = y;
|
|
457
454
|
return ['M', x, y];
|
|
458
455
|
}
|
|
459
|
-
/** @type {
|
|
456
|
+
/** @type {SVGPath.absoluteSegment} */
|
|
460
457
|
// @ts-ignore
|
|
461
458
|
let absoluteSegment = [];
|
|
462
459
|
|
|
@@ -519,7 +516,7 @@ function pathToAbsolute(pathInput) {
|
|
|
519
516
|
* Iterates an array to check if it's a `pathArray`
|
|
520
517
|
* with relative values.
|
|
521
518
|
*
|
|
522
|
-
* @param {string |
|
|
519
|
+
* @param {string | SVGPath.pathArray} path the `pathArray` to be checked
|
|
523
520
|
* @returns {boolean} iteration result
|
|
524
521
|
*/
|
|
525
522
|
function isRelativeArray(path) {
|
|
@@ -532,8 +529,8 @@ function isRelativeArray(path) {
|
|
|
532
529
|
* Parses a path string value or object and returns an array
|
|
533
530
|
* of segments, all converted to relative values.
|
|
534
531
|
*
|
|
535
|
-
* @param {string |
|
|
536
|
-
* @returns {
|
|
532
|
+
* @param {string | SVGPath.pathArray} pathInput the path string | object
|
|
533
|
+
* @returns {SVGPath.relativeArray} the resulted `pathArray` with relative values
|
|
537
534
|
*/
|
|
538
535
|
function pathToRelative(pathInput) {
|
|
539
536
|
if (isRelativeArray(pathInput)) {
|
|
@@ -549,7 +546,7 @@ function pathToRelative(pathInput) {
|
|
|
549
546
|
return path.map((segment) => {
|
|
550
547
|
const values = segment.slice(1).map(Number);
|
|
551
548
|
const [pathCommand] = segment;
|
|
552
|
-
/** @type {
|
|
549
|
+
/** @type {SVGPath.relativeCommand} */
|
|
553
550
|
// @ts-ignore
|
|
554
551
|
const relativeCommand = pathCommand.toLowerCase();
|
|
555
552
|
|
|
@@ -560,7 +557,7 @@ function pathToRelative(pathInput) {
|
|
|
560
557
|
return ['M', x, y];
|
|
561
558
|
}
|
|
562
559
|
|
|
563
|
-
/** @type {
|
|
560
|
+
/** @type {SVGPath.relativeSegment} */
|
|
564
561
|
// @ts-ignore -- trust me DON'T CHANGE
|
|
565
562
|
let relativeSegment = [];
|
|
566
563
|
|
|
@@ -627,7 +624,7 @@ function pathToRelative(pathInput) {
|
|
|
627
624
|
/**
|
|
628
625
|
* Splits an extended A (arc-to) segment into two cubic-bezier segments.
|
|
629
626
|
*
|
|
630
|
-
* @param {
|
|
627
|
+
* @param {SVGPath.pathArray} path the `pathArray` this segment belongs to
|
|
631
628
|
* @param {string[]} allPathCommands all previous path commands
|
|
632
629
|
* @param {number} i the segment index
|
|
633
630
|
*/
|
|
@@ -684,10 +681,10 @@ function shorthandToCubic(x1, y1, x2, y2, prevCommand) {
|
|
|
684
681
|
/**
|
|
685
682
|
* Normalizes a single segment of a `pathArray` object.
|
|
686
683
|
*
|
|
687
|
-
* @param {
|
|
684
|
+
* @param {SVGPath.pathSegment} segment the segment object
|
|
688
685
|
* @param {any} params the coordinates of the previous segment
|
|
689
686
|
* @param {string} prevCommand the path command of the previous segment
|
|
690
|
-
* @returns {
|
|
687
|
+
* @returns {SVGPath.normalSegment} the normalized segment
|
|
691
688
|
*/
|
|
692
689
|
function normalizeSegment(segment, params, prevCommand) {
|
|
693
690
|
const [pathCommand] = segment;
|
|
@@ -734,7 +731,7 @@ function normalizeSegment(segment, params, prevCommand) {
|
|
|
734
731
|
* with all segments are in non-shorthand notation
|
|
735
732
|
* with absolute values.
|
|
736
733
|
*
|
|
737
|
-
* @param {string |
|
|
734
|
+
* @param {string | SVGPath.pathArray} path the `pathArray` to be checked
|
|
738
735
|
* @returns {boolean} iteration result
|
|
739
736
|
*/
|
|
740
737
|
function isNormalizedArray(path) {
|
|
@@ -743,7 +740,7 @@ function isNormalizedArray(path) {
|
|
|
743
740
|
}
|
|
744
741
|
|
|
745
742
|
/**
|
|
746
|
-
* @type {
|
|
743
|
+
* @type {SVGPath.parserParams}
|
|
747
744
|
*/
|
|
748
745
|
const paramsParser = {
|
|
749
746
|
x1: 0, y1: 0, x2: 0, y2: 0, x: 0, y: 0, qx: null, qy: null,
|
|
@@ -754,8 +751,8 @@ const paramsParser = {
|
|
|
754
751
|
* * convert segments to absolute values
|
|
755
752
|
* * convert shorthand path commands to their non-shorthand notation
|
|
756
753
|
*
|
|
757
|
-
* @param {string |
|
|
758
|
-
* @returns {
|
|
754
|
+
* @param {string | SVGPath.pathArray} pathInput the string to be parsed or 'pathArray'
|
|
755
|
+
* @returns {SVGPath.normalArray} the normalized `pathArray`
|
|
759
756
|
*/
|
|
760
757
|
function normalizePath(pathInput) {
|
|
761
758
|
if (isNormalizedArray(pathInput)) {
|
|
@@ -763,7 +760,7 @@ function normalizePath(pathInput) {
|
|
|
763
760
|
return clonePath(pathInput);
|
|
764
761
|
}
|
|
765
762
|
|
|
766
|
-
/** @type {
|
|
763
|
+
/** @type {SVGPath.normalArray} */
|
|
767
764
|
// @ts-ignore -- `absoluteArray` will become a `normalArray`
|
|
768
765
|
const path = pathToAbsolute(pathInput);
|
|
769
766
|
const params = { ...paramsParser };
|
|
@@ -804,8 +801,8 @@ function normalizePath(pathInput) {
|
|
|
804
801
|
* use `splitPath` first and apply this utility on each
|
|
805
802
|
* sub-path separately.
|
|
806
803
|
*
|
|
807
|
-
* @param {
|
|
808
|
-
* @return {
|
|
804
|
+
* @param {SVGPath.pathArray | string} pathInput the `pathArray` source
|
|
805
|
+
* @return {SVGPath.pathArray} a fixed `pathArray`
|
|
809
806
|
*/
|
|
810
807
|
function fixPath(pathInput) {
|
|
811
808
|
const pathArray = parsePathString(pathInput);
|
|
@@ -828,7 +825,7 @@ function fixPath(pathInput) {
|
|
|
828
825
|
* Iterates an array to check if it's a `pathArray`
|
|
829
826
|
* with all C (cubic bezier) segments.
|
|
830
827
|
*
|
|
831
|
-
* @param {string |
|
|
828
|
+
* @param {string | SVGPath.pathArray} path the `Array` to be checked
|
|
832
829
|
* @returns {boolean} iteration result
|
|
833
830
|
*/
|
|
834
831
|
function isCurveArray(path) {
|
|
@@ -1017,7 +1014,7 @@ function distanceSquareRoot(a, b) {
|
|
|
1017
1014
|
}
|
|
1018
1015
|
|
|
1019
1016
|
/**
|
|
1020
|
-
* Returns the length of a line (L,V,H,Z) segment
|
|
1017
|
+
* Returns the length of a line (L,V,H,Z) segment
|
|
1021
1018
|
* or a point at a given length.
|
|
1022
1019
|
*
|
|
1023
1020
|
* @param {number} x1 the starting point X
|
|
@@ -1035,7 +1032,7 @@ function segmentLineFactory(x1, y1, x2, y2, distance) {
|
|
|
1035
1032
|
if (distance < margin) {
|
|
1036
1033
|
return { x: x1, y: y1 };
|
|
1037
1034
|
}
|
|
1038
|
-
if (distance > length
|
|
1035
|
+
if (distance > length) {
|
|
1039
1036
|
return { x: x2, y: y2 };
|
|
1040
1037
|
}
|
|
1041
1038
|
const [x, y] = midPoint([x1, y1], [x2, y2], distance / length);
|
|
@@ -1078,9 +1075,9 @@ function lineToCubic(x1, y1, x2, y2) {
|
|
|
1078
1075
|
/**
|
|
1079
1076
|
* Converts any segment to C (cubic-bezier).
|
|
1080
1077
|
*
|
|
1081
|
-
* @param {
|
|
1082
|
-
* @param {
|
|
1083
|
-
* @returns {
|
|
1078
|
+
* @param {SVGPath.pathSegment} segment the source segment
|
|
1079
|
+
* @param {SVGPath.parserParams} params the source segment parameters
|
|
1080
|
+
* @returns {SVGPath.cubicSegment | SVGPath.MSegment} the cubic-bezier segment
|
|
1084
1081
|
*/
|
|
1085
1082
|
function segmentToCubic(segment, params) {
|
|
1086
1083
|
const [pathCommand] = segment;
|
|
@@ -1129,8 +1126,8 @@ function segmentToCubic(segment, params) {
|
|
|
1129
1126
|
* In addition, un-necessary `Z` segment is removed if previous segment
|
|
1130
1127
|
* extends to the `M` segment.
|
|
1131
1128
|
*
|
|
1132
|
-
* @param {string |
|
|
1133
|
-
* @returns {
|
|
1129
|
+
* @param {string | SVGPath.pathArray} pathInput the string to be parsed or 'pathArray'
|
|
1130
|
+
* @returns {SVGPath.curveArray} the resulted `pathArray` converted to cubic-bezier
|
|
1134
1131
|
*/
|
|
1135
1132
|
function pathToCurve(pathInput) {
|
|
1136
1133
|
if (isCurveArray(pathInput)) {
|
|
@@ -1169,23 +1166,24 @@ function pathToCurve(pathInput) {
|
|
|
1169
1166
|
* Rounds the values of a `pathArray` instance to
|
|
1170
1167
|
* a specified amount of decimals and returns it.
|
|
1171
1168
|
*
|
|
1172
|
-
* @param {
|
|
1173
|
-
* @param {number |
|
|
1174
|
-
* @returns {
|
|
1169
|
+
* @param {SVGPath.pathArray} path the source `pathArray`
|
|
1170
|
+
* @param {number | false} roundOption the amount of decimals to round numbers to
|
|
1171
|
+
* @returns {SVGPath.pathArray} the resulted `pathArray` with rounded values
|
|
1175
1172
|
*/
|
|
1176
1173
|
function roundPath(path, roundOption) {
|
|
1177
1174
|
let { round } = defaultOptions;
|
|
1178
1175
|
if (roundOption === false || round === false) return clonePath(path);
|
|
1179
|
-
round = roundOption >= 1 ? roundOption : round;
|
|
1176
|
+
// round = roundOption >= 1 ? roundOption : round;
|
|
1177
|
+
// allow for ZERO decimals
|
|
1178
|
+
round = roundOption >= 0 ? roundOption : round;
|
|
1180
1179
|
// to round values to the power
|
|
1181
1180
|
// the `round` value must be integer
|
|
1182
|
-
// @ts-ignore
|
|
1183
1181
|
const pow = round >= 1 ? (10 ** round) : 1;
|
|
1184
1182
|
|
|
1185
1183
|
// @ts-ignore -- `pathSegment[]` is `pathArray`
|
|
1186
1184
|
return path.map((pi) => {
|
|
1187
1185
|
const values = pi.slice(1).map(Number)
|
|
1188
|
-
.map((n) => (
|
|
1186
|
+
.map((n) => (round ? (Math.round(n * pow) / pow) : Math.round(n)));
|
|
1189
1187
|
return [pi[0], ...values];
|
|
1190
1188
|
});
|
|
1191
1189
|
}
|
|
@@ -1194,8 +1192,8 @@ function roundPath(path, roundOption) {
|
|
|
1194
1192
|
* Returns a valid `d` attribute string value created
|
|
1195
1193
|
* by rounding values and concatenating the `pathArray` segments.
|
|
1196
1194
|
*
|
|
1197
|
-
* @param {
|
|
1198
|
-
* @param {
|
|
1195
|
+
* @param {SVGPath.pathArray} path the `pathArray` object
|
|
1196
|
+
* @param {number | false} round amount of decimals to round values to
|
|
1199
1197
|
* @returns {string} the concatenated path string
|
|
1200
1198
|
*/
|
|
1201
1199
|
function pathToString(path, round) {
|
|
@@ -1204,11 +1202,10 @@ function pathToString(path, round) {
|
|
|
1204
1202
|
}
|
|
1205
1203
|
|
|
1206
1204
|
/**
|
|
1207
|
-
* Reverses all segments
|
|
1208
|
-
* and returns a new instance.
|
|
1205
|
+
* Reverses all segments of a `pathArray` and returns a new `pathArray` instance.
|
|
1209
1206
|
*
|
|
1210
|
-
* @param {
|
|
1211
|
-
* @returns {
|
|
1207
|
+
* @param {SVGPath.pathArray} pathInput the source `pathArray`
|
|
1208
|
+
* @returns {SVGPath.pathArray} the reversed `pathArray`
|
|
1212
1209
|
*/
|
|
1213
1210
|
function reversePath(pathInput) {
|
|
1214
1211
|
const absolutePath = pathToAbsolute(pathInput);
|
|
@@ -1233,7 +1230,7 @@ function reversePath(pathInput) {
|
|
|
1233
1230
|
/** @type {number} */
|
|
1234
1231
|
const x = i ? path[i - 1].x : path[pLen - 1].x;
|
|
1235
1232
|
const y = i ? path[i - 1].y : path[pLen - 1].y;
|
|
1236
|
-
/** @type {
|
|
1233
|
+
/** @type {SVGPath.pathSegment} */
|
|
1237
1234
|
// @ts-ignore
|
|
1238
1235
|
let result = [];
|
|
1239
1236
|
|
|
@@ -1307,7 +1304,7 @@ function reversePath(pathInput) {
|
|
|
1307
1304
|
* In the process, values are converted to absolute
|
|
1308
1305
|
* for visual consistency.
|
|
1309
1306
|
*
|
|
1310
|
-
* @param {
|
|
1307
|
+
* @param {SVGPath.pathArray | string} pathInput the source `pathArray`
|
|
1311
1308
|
* @return {string[]} an array with all sub-path strings
|
|
1312
1309
|
*/
|
|
1313
1310
|
function splitPath(pathInput) {
|
|
@@ -1321,11 +1318,11 @@ function splitPath(pathInput) {
|
|
|
1321
1318
|
/**
|
|
1322
1319
|
* Shorten a single segment of a `pathArray` object.
|
|
1323
1320
|
*
|
|
1324
|
-
* @param {
|
|
1325
|
-
* @param {
|
|
1321
|
+
* @param {SVGPath.absoluteSegment} segment the `absoluteSegment` object
|
|
1322
|
+
* @param {SVGPath.normalSegment} normalSegment the `normalSegment` object
|
|
1326
1323
|
* @param {any} params the coordinates of the previous segment
|
|
1327
1324
|
* @param {string} prevCommand the path command of the previous segment
|
|
1328
|
-
* @returns {
|
|
1325
|
+
* @returns {SVGPath.shortSegment | SVGPath.pathSegment} the shortened segment
|
|
1329
1326
|
*/
|
|
1330
1327
|
function shortenSegment(segment, normalSegment, params, prevCommand) {
|
|
1331
1328
|
const [pathCommand] = segment;
|
|
@@ -1389,9 +1386,9 @@ function shortenSegment(segment, normalSegment, params, prevCommand) {
|
|
|
1389
1386
|
* * implement `auto` for rounding values based on pathBBox
|
|
1390
1387
|
* * also revers path check if it's smaller string, maybe?
|
|
1391
1388
|
*
|
|
1392
|
-
* @param {
|
|
1389
|
+
* @param {SVGPath.pathArray} pathInput a string or `pathArray`
|
|
1393
1390
|
* @param {number | boolean} round the amount of decimals to round values to
|
|
1394
|
-
* @returns {
|
|
1391
|
+
* @returns {SVGPath.pathArray} the optimized `pathArray`
|
|
1395
1392
|
*/
|
|
1396
1393
|
function optimizePath(pathInput, round) {
|
|
1397
1394
|
const path = pathToAbsolute(pathInput);
|
|
@@ -2277,14 +2274,15 @@ Object.assign(CSSMatrix, { Version: Version$1 });
|
|
|
2277
2274
|
/**
|
|
2278
2275
|
* Returns a transformation matrix to apply to `<path>` elements.
|
|
2279
2276
|
*
|
|
2280
|
-
* @see
|
|
2277
|
+
* @see SVGPath.transformObject
|
|
2281
2278
|
*
|
|
2282
|
-
* @param {
|
|
2279
|
+
* @param {SVGPath.transformObject} transform the `transformObject`
|
|
2283
2280
|
* @returns {CSSMatrix} a new transformation matrix
|
|
2284
2281
|
*/
|
|
2285
2282
|
function getSVGMatrix(transform) {
|
|
2286
2283
|
let matrix = new CSSMatrix();
|
|
2287
2284
|
const { origin } = transform;
|
|
2285
|
+
// @ts-ignore -- at this point the origin is surely defined
|
|
2288
2286
|
const [originX, originY] = origin;
|
|
2289
2287
|
const { translate } = transform;
|
|
2290
2288
|
const { rotate } = transform;
|
|
@@ -2415,9 +2413,9 @@ function transformEllipse(m, rx, ry, ax) {
|
|
|
2415
2413
|
* Details =>
|
|
2416
2414
|
* https://stackoverflow.com/questions/23792505/predicted-rendering-of-css-3d-transformed-pixel
|
|
2417
2415
|
*
|
|
2418
|
-
* @param {
|
|
2416
|
+
* @param {SVGPath.CSSMatrix} m the transformation matrix
|
|
2419
2417
|
* @param {[number, number]} point2D the initial [x,y] coordinates
|
|
2420
|
-
* @param {number[]} origin the
|
|
2418
|
+
* @param {number[]} origin the [x,y,z] transform origin
|
|
2421
2419
|
* @returns {[number, number]} the projected [x,y] coordinates
|
|
2422
2420
|
*/
|
|
2423
2421
|
function projection2d(m, point2D, origin) {
|
|
@@ -2443,9 +2441,9 @@ function projection2d(m, point2D, origin) {
|
|
|
2443
2441
|
* Since *SVGElement* doesn't support 3D transformation, this function
|
|
2444
2442
|
* creates a 2D projection of the <path> element.
|
|
2445
2443
|
*
|
|
2446
|
-
* @param {
|
|
2447
|
-
* @param {
|
|
2448
|
-
* @returns {
|
|
2444
|
+
* @param {SVGPath.pathArray} path the `pathArray` to apply transformation
|
|
2445
|
+
* @param {SVGPath.transformObject} transform the transform functions `Object`
|
|
2446
|
+
* @returns {SVGPath.pathArray} the resulted `pathArray`
|
|
2449
2447
|
*/
|
|
2450
2448
|
function transformPath(path, transform) {
|
|
2451
2449
|
let x = 0; let y = 0; let i; let j; let ii; let jj; let lx; let ly; let te;
|
|
@@ -2460,12 +2458,12 @@ function transformPath(path, transform) {
|
|
|
2460
2458
|
const matrix2d = [a, b, c, d, e, f];
|
|
2461
2459
|
const params = { ...paramsParser };
|
|
2462
2460
|
/** @ts-ignore */
|
|
2463
|
-
/** @type {
|
|
2461
|
+
/** @type {SVGPath.pathSegment} */
|
|
2464
2462
|
// @ts-ignore
|
|
2465
2463
|
let segment = [];
|
|
2466
2464
|
let seglen = 0;
|
|
2467
2465
|
let pathCommand = '';
|
|
2468
|
-
/** @type {
|
|
2466
|
+
/** @type {SVGPath.pathTransformList[]} */
|
|
2469
2467
|
let transformedPath = [];
|
|
2470
2468
|
const allPathCommands = []; // needed for arc to curve transformation
|
|
2471
2469
|
|
|
@@ -2504,7 +2502,7 @@ function transformPath(path, transform) {
|
|
|
2504
2502
|
params.x2 = +(segment[seglen - 4]) || params.x1;
|
|
2505
2503
|
params.y2 = +(segment[seglen - 3]) || params.y1;
|
|
2506
2504
|
|
|
2507
|
-
/** @type {
|
|
2505
|
+
/** @type {SVGPath.pathTransformList} */
|
|
2508
2506
|
const result = {
|
|
2509
2507
|
s: absolutePath[i], c: absolutePath[i][0], x: params.x1, y: params.y1,
|
|
2510
2508
|
};
|
|
@@ -2541,6 +2539,7 @@ function transformPath(path, transform) {
|
|
|
2541
2539
|
case 'L':
|
|
2542
2540
|
case 'H':
|
|
2543
2541
|
case 'V':
|
|
2542
|
+
// @ts-ignore
|
|
2544
2543
|
[lx, ly] = projection2d(matrixInstance, [seg.x, seg.y], origin);
|
|
2545
2544
|
|
|
2546
2545
|
if (x !== lx && y !== ly) {
|
|
@@ -2557,7 +2556,7 @@ function transformPath(path, transform) {
|
|
|
2557
2556
|
default:
|
|
2558
2557
|
|
|
2559
2558
|
for (j = 1, jj = segment.length; j < jj; j += 2) {
|
|
2560
|
-
// compute line coordinates without altering previous coordinates
|
|
2559
|
+
// @ts-ignore compute line coordinates without altering previous coordinates
|
|
2561
2560
|
[x, y] = projection2d(matrixInstance, [+segment[j], +segment[j + 1]], origin);
|
|
2562
2561
|
segment[j] = x;
|
|
2563
2562
|
segment[j + 1] = y;
|
|
@@ -2599,7 +2598,7 @@ function getPointAtCubicSegmentLength(x1, y1, c1x, c1y, c2x, c2y, x2, y2, t) {
|
|
|
2599
2598
|
}
|
|
2600
2599
|
|
|
2601
2600
|
/**
|
|
2602
|
-
* Returns the length of a C (cubic-bezier) segment
|
|
2601
|
+
* Returns the length of a C (cubic-bezier) segment
|
|
2603
2602
|
* or an {x,y} point at a given length.
|
|
2604
2603
|
*
|
|
2605
2604
|
* @param {number} x1 the starting point X
|
|
@@ -2614,15 +2613,16 @@ function getPointAtCubicSegmentLength(x1, y1, c1x, c1y, c2x, c2y, x2, y2, t) {
|
|
|
2614
2613
|
* @returns {{x: number, y: number} | number} the segment length or point
|
|
2615
2614
|
*/
|
|
2616
2615
|
function segmentCubicFactory(x1, y1, c1x, c1y, c2x, c2y, x2, y2, distance) {
|
|
2617
|
-
|
|
2616
|
+
const distanceIsNumber = typeof distance === 'number';
|
|
2618
2617
|
const lengthMargin = 0.001;
|
|
2618
|
+
let x = x1; let y = y1;
|
|
2619
2619
|
let totalLength = 0;
|
|
2620
2620
|
let prev = [x1, y1, totalLength];
|
|
2621
2621
|
/** @type {[number, number]} */
|
|
2622
2622
|
let cur = [x1, y1];
|
|
2623
2623
|
let t = 0;
|
|
2624
2624
|
|
|
2625
|
-
if (
|
|
2625
|
+
if (distanceIsNumber && distance < lengthMargin) {
|
|
2626
2626
|
return { x, y };
|
|
2627
2627
|
}
|
|
2628
2628
|
|
|
@@ -2634,7 +2634,7 @@ function segmentCubicFactory(x1, y1, c1x, c1y, c2x, c2y, x2, y2, distance) {
|
|
|
2634
2634
|
totalLength += distanceSquareRoot(cur, [x, y]);
|
|
2635
2635
|
cur = [x, y];
|
|
2636
2636
|
|
|
2637
|
-
if (
|
|
2637
|
+
if (distanceIsNumber && totalLength >= distance) {
|
|
2638
2638
|
const dv = (totalLength - distance) / (totalLength - prev[2]);
|
|
2639
2639
|
|
|
2640
2640
|
return {
|
|
@@ -2645,7 +2645,7 @@ function segmentCubicFactory(x1, y1, c1x, c1y, c2x, c2y, x2, y2, distance) {
|
|
|
2645
2645
|
prev = [x, y, totalLength];
|
|
2646
2646
|
}
|
|
2647
2647
|
|
|
2648
|
-
if (
|
|
2648
|
+
if (distanceIsNumber && distance >= totalLength) {
|
|
2649
2649
|
return { x: x2, y: y2 };
|
|
2650
2650
|
}
|
|
2651
2651
|
return totalLength;
|
|
@@ -2662,7 +2662,7 @@ function segmentCubicFactory(x1, y1, c1x, c1y, c2x, c2y, x2, y2, distance) {
|
|
|
2662
2662
|
* @param {number} c2y the second control point Y
|
|
2663
2663
|
* @param {number} x2 the ending point X
|
|
2664
2664
|
* @param {number} y2 the ending point Y
|
|
2665
|
-
* @returns {
|
|
2665
|
+
* @returns {SVGPath.segmentLimits} the bounding box of the cubic-bezier segment
|
|
2666
2666
|
*/
|
|
2667
2667
|
function getCubicSize(x1, y1, c1x, c1y, c2x, c2y, x2, y2) {
|
|
2668
2668
|
let a = (c2x - 2 * c1x + x1) - (x2 - 2 * c2x + c1x);
|
|
@@ -2719,8 +2719,8 @@ function getCubicSize(x1, y1, c1x, c1y, c2x, c2y, x2, y2) {
|
|
|
2719
2719
|
/**
|
|
2720
2720
|
* Returns the bounding box of a shape.
|
|
2721
2721
|
*
|
|
2722
|
-
* @param {
|
|
2723
|
-
* @returns {
|
|
2722
|
+
* @param {SVGPath.pathArray} path the shape `pathArray`
|
|
2723
|
+
* @returns {SVGPath.pathBBox} the length of the cubic-bezier segment
|
|
2724
2724
|
*/
|
|
2725
2725
|
function getPathBBox(path) {
|
|
2726
2726
|
if (!path) {
|
|
@@ -2777,6 +2777,254 @@ function getPathBBox(path) {
|
|
|
2777
2777
|
};
|
|
2778
2778
|
}
|
|
2779
2779
|
|
|
2780
|
+
/**
|
|
2781
|
+
* Returns the length of an A (arc-to) segment
|
|
2782
|
+
* or an {x,y} point at a given length.
|
|
2783
|
+
*
|
|
2784
|
+
* @param {number} X1 the starting x position
|
|
2785
|
+
* @param {number} Y1 the starting y position
|
|
2786
|
+
* @param {number} RX x-radius of the arc
|
|
2787
|
+
* @param {number} RY y-radius of the arc
|
|
2788
|
+
* @param {number} angle x-axis-rotation of the arc
|
|
2789
|
+
* @param {number} LAF large-arc-flag of the arc
|
|
2790
|
+
* @param {number} SF sweep-flag of the arc
|
|
2791
|
+
* @param {number} X2 the ending x position
|
|
2792
|
+
* @param {number} Y2 the ending y position
|
|
2793
|
+
* @param {number} distance the point distance
|
|
2794
|
+
* @returns {{x: number, y: number} | number} the segment length or point
|
|
2795
|
+
*/
|
|
2796
|
+
function segmentArcFactory(X1, Y1, RX, RY, angle, LAF, SF, X2, Y2, distance) {
|
|
2797
|
+
const cubicSeg = arcToCubic(X1, Y1, RX, RY, angle, LAF, SF, X2, Y2);
|
|
2798
|
+
const distanceIsNumber = typeof distance === 'number';
|
|
2799
|
+
let [x, y] = [X1, Y1];
|
|
2800
|
+
const lengthMargin = 0.001;
|
|
2801
|
+
let totalLength = 0;
|
|
2802
|
+
let cubicSubseg = [];
|
|
2803
|
+
let argsc = [];
|
|
2804
|
+
let segLen = 0;
|
|
2805
|
+
|
|
2806
|
+
if (distanceIsNumber && distance < lengthMargin) {
|
|
2807
|
+
return { x, y };
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
for (let i = 0, ii = cubicSeg.length; i < ii; i += 6) {
|
|
2811
|
+
cubicSubseg = cubicSeg.slice(i, i + 6);
|
|
2812
|
+
argsc = [x, y, ...cubicSubseg];
|
|
2813
|
+
// @ts-ignore
|
|
2814
|
+
segLen = segmentCubicFactory(...argsc);
|
|
2815
|
+
if (distanceIsNumber && totalLength + segLen >= distance) {
|
|
2816
|
+
// @ts-ignore -- this is a `cubicSegment`
|
|
2817
|
+
return segmentCubicFactory(...argsc, distance - totalLength);
|
|
2818
|
+
}
|
|
2819
|
+
totalLength += segLen;
|
|
2820
|
+
[x, y] = cubicSubseg.slice(-2);
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
if (distanceIsNumber && distance >= totalLength) {
|
|
2824
|
+
return { x: X2, y: Y2 };
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
return totalLength;
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
/**
|
|
2831
|
+
* Returns the {x,y} coordinates of a point at a
|
|
2832
|
+
* given length of a quad-bezier segment.
|
|
2833
|
+
*
|
|
2834
|
+
* @see https://github.com/substack/point-at-length
|
|
2835
|
+
*
|
|
2836
|
+
* @param {number} x1 the starting point X
|
|
2837
|
+
* @param {number} y1 the starting point Y
|
|
2838
|
+
* @param {number} cx the control point X
|
|
2839
|
+
* @param {number} cy the control point Y
|
|
2840
|
+
* @param {number} x2 the ending point X
|
|
2841
|
+
* @param {number} y2 the ending point Y
|
|
2842
|
+
* @param {number} t a [0-1] ratio
|
|
2843
|
+
* @returns {{x: number, y: number}} the requested {x,y} coordinates
|
|
2844
|
+
*/
|
|
2845
|
+
function getPointAtQuadSegmentLength(x1, y1, cx, cy, x2, y2, t) {
|
|
2846
|
+
const t1 = 1 - t;
|
|
2847
|
+
return {
|
|
2848
|
+
x: (t1 ** 2) * x1
|
|
2849
|
+
+ 2 * t1 * t * cx
|
|
2850
|
+
+ (t ** 2) * x2,
|
|
2851
|
+
y: (t1 ** 2) * y1
|
|
2852
|
+
+ 2 * t1 * t * cy
|
|
2853
|
+
+ (t ** 2) * y2,
|
|
2854
|
+
};
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2857
|
+
/**
|
|
2858
|
+
* Returns the Q (quadratic-bezier) segment length
|
|
2859
|
+
* or an {x,y} point at a given length.
|
|
2860
|
+
*
|
|
2861
|
+
* @param {number} x1 the starting point X
|
|
2862
|
+
* @param {number} y1 the starting point Y
|
|
2863
|
+
* @param {number} qx the control point X
|
|
2864
|
+
* @param {number} qy the control point Y
|
|
2865
|
+
* @param {number} x2 the ending point X
|
|
2866
|
+
* @param {number} y2 the ending point Y
|
|
2867
|
+
* @param {number=} distance the distance to point
|
|
2868
|
+
* @returns {{x: number, y: number} | number} the segment length or point
|
|
2869
|
+
*/
|
|
2870
|
+
function segmentQuadFactory(x1, y1, qx, qy, x2, y2, distance) {
|
|
2871
|
+
const distanceIsNumber = typeof distance === 'number';
|
|
2872
|
+
const lengthMargin = 0.001;
|
|
2873
|
+
let x = x1; let y = y1;
|
|
2874
|
+
let totalLength = 0;
|
|
2875
|
+
let prev = [x1, y1, totalLength];
|
|
2876
|
+
/** @type {[number, number]} */
|
|
2877
|
+
let cur = [x1, y1];
|
|
2878
|
+
let t = 0;
|
|
2879
|
+
|
|
2880
|
+
if (distanceIsNumber && distance < lengthMargin) {
|
|
2881
|
+
return { x, y };
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
const n = 100;
|
|
2885
|
+
for (let j = 0; j <= n; j += 1) {
|
|
2886
|
+
t = j / n;
|
|
2887
|
+
|
|
2888
|
+
({ x, y } = getPointAtQuadSegmentLength(x1, y1, qx, qy, x2, y2, t));
|
|
2889
|
+
totalLength += distanceSquareRoot(cur, [x, y]);
|
|
2890
|
+
cur = [x, y];
|
|
2891
|
+
|
|
2892
|
+
if (distanceIsNumber && totalLength >= distance) {
|
|
2893
|
+
const dv = (totalLength - distance) / (totalLength - prev[2]);
|
|
2894
|
+
|
|
2895
|
+
return {
|
|
2896
|
+
x: cur[0] * (1 - dv) + prev[0] * dv,
|
|
2897
|
+
y: cur[1] * (1 - dv) + prev[1] * dv,
|
|
2898
|
+
};
|
|
2899
|
+
}
|
|
2900
|
+
prev = [x, y, totalLength];
|
|
2901
|
+
}
|
|
2902
|
+
if (distanceIsNumber && distance >= totalLength) {
|
|
2903
|
+
return { x: x2, y: y2 };
|
|
2904
|
+
}
|
|
2905
|
+
return totalLength;
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2908
|
+
/**
|
|
2909
|
+
* Returns a {x,y} point at a given length
|
|
2910
|
+
* of a shape or the shape total length.
|
|
2911
|
+
*
|
|
2912
|
+
* @param {string | SVGPath.pathArray} pathInput the `pathArray` to look into
|
|
2913
|
+
* @param {number=} distance the length of the shape to look at
|
|
2914
|
+
* @returns {{x: number, y: number} | number} the total length or point
|
|
2915
|
+
*/
|
|
2916
|
+
function pathLengthFactory(pathInput, distance) {
|
|
2917
|
+
const path = fixPath(normalizePath(pathInput));
|
|
2918
|
+
const distanceIsNumber = typeof distance === 'number';
|
|
2919
|
+
let totalLength = 0;
|
|
2920
|
+
let isM = true;
|
|
2921
|
+
/** @type {number[]} */
|
|
2922
|
+
let data = [];
|
|
2923
|
+
let pathCommand = 'M';
|
|
2924
|
+
let segLen = 0;
|
|
2925
|
+
let x = 0;
|
|
2926
|
+
let y = 0;
|
|
2927
|
+
let mx = 0;
|
|
2928
|
+
let my = 0;
|
|
2929
|
+
let seg;
|
|
2930
|
+
|
|
2931
|
+
for (let i = 0, ll = path.length; i < ll; i += 1) {
|
|
2932
|
+
seg = path[i];
|
|
2933
|
+
[pathCommand] = seg;
|
|
2934
|
+
isM = pathCommand === 'M';
|
|
2935
|
+
// @ts-ignore
|
|
2936
|
+
data = !isM ? [x, y, ...seg.slice(1)] : data;
|
|
2937
|
+
|
|
2938
|
+
// this segment is always ZERO
|
|
2939
|
+
if (isM) {
|
|
2940
|
+
// remember mx, my for Z
|
|
2941
|
+
// @ts-ignore
|
|
2942
|
+
[, mx, my] = seg;
|
|
2943
|
+
if (distanceIsNumber && distance < 0.001) {
|
|
2944
|
+
return { x: mx, y: my };
|
|
2945
|
+
}
|
|
2946
|
+
} else if (pathCommand === 'L') {
|
|
2947
|
+
// @ts-ignore
|
|
2948
|
+
segLen = segmentLineFactory(...data);
|
|
2949
|
+
if (distanceIsNumber && totalLength + segLen >= distance) {
|
|
2950
|
+
// @ts-ignore
|
|
2951
|
+
return segmentLineFactory(...data, distance - totalLength);
|
|
2952
|
+
}
|
|
2953
|
+
totalLength += segLen;
|
|
2954
|
+
} else if (pathCommand === 'A') {
|
|
2955
|
+
// @ts-ignore
|
|
2956
|
+
segLen = segmentArcFactory(...data);
|
|
2957
|
+
if (distanceIsNumber && totalLength + segLen >= distance) {
|
|
2958
|
+
// @ts-ignore
|
|
2959
|
+
return segmentArcFactory(...data, distance - totalLength);
|
|
2960
|
+
}
|
|
2961
|
+
totalLength += segLen;
|
|
2962
|
+
} else if (pathCommand === 'C') {
|
|
2963
|
+
// @ts-ignore
|
|
2964
|
+
segLen = segmentCubicFactory(...data);
|
|
2965
|
+
if (distanceIsNumber && totalLength + segLen >= distance) {
|
|
2966
|
+
// @ts-ignore
|
|
2967
|
+
return segmentCubicFactory(...data, distance - totalLength);
|
|
2968
|
+
}
|
|
2969
|
+
totalLength += segLen;
|
|
2970
|
+
} else if (pathCommand === 'Q') {
|
|
2971
|
+
// @ts-ignore
|
|
2972
|
+
segLen = segmentQuadFactory(...data);
|
|
2973
|
+
if (distanceIsNumber && totalLength + segLen >= distance) {
|
|
2974
|
+
// @ts-ignore
|
|
2975
|
+
return segmentQuadFactory(...data, distance - totalLength);
|
|
2976
|
+
}
|
|
2977
|
+
totalLength += segLen;
|
|
2978
|
+
} else if (pathCommand === 'Z') {
|
|
2979
|
+
data = [x, y, mx, my];
|
|
2980
|
+
// @ts-ignore
|
|
2981
|
+
segLen = segmentLineFactory(...data);
|
|
2982
|
+
if (distanceIsNumber && totalLength + segLen >= distance) {
|
|
2983
|
+
// @ts-ignore
|
|
2984
|
+
return segmentLineFactory(...data, distance - totalLength);
|
|
2985
|
+
}
|
|
2986
|
+
totalLength += segLen;
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2989
|
+
// @ts-ignore -- needed for the below
|
|
2990
|
+
[x, y] = pathCommand !== 'Z' ? seg.slice(-2) : [mx, my];
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
// native `getPointAtLength` behavior when the given distance
|
|
2994
|
+
// is higher than total length
|
|
2995
|
+
if (distanceIsNumber && distance >= totalLength) {
|
|
2996
|
+
return { x, y };
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
return totalLength;
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
/**
|
|
3003
|
+
* Returns the shape total length, or the equivalent to `shape.getTotalLength()`.
|
|
3004
|
+
*
|
|
3005
|
+
* The `normalizePath` version is lighter, faster, more efficient and more accurate
|
|
3006
|
+
* with paths that are not `curveArray`.
|
|
3007
|
+
*
|
|
3008
|
+
* @param {string | SVGPath.pathArray} pathInput the target `pathArray`
|
|
3009
|
+
* @returns {number} the shape total length
|
|
3010
|
+
*/
|
|
3011
|
+
function getTotalLength(pathInput) {
|
|
3012
|
+
// @ts-ignore - it's fine
|
|
3013
|
+
return pathLengthFactory(pathInput);
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
/**
|
|
3017
|
+
* Returns [x,y] coordinates of a point at a given length of a shape.
|
|
3018
|
+
*
|
|
3019
|
+
* @param {string | SVGPath.pathArray} pathInput the `pathArray` to look into
|
|
3020
|
+
* @param {number} distance the length of the shape to look at
|
|
3021
|
+
* @returns {{x: number, y: number}} the requested {x, y} point coordinates
|
|
3022
|
+
*/
|
|
3023
|
+
function getPointAtLength(pathInput, distance) {
|
|
3024
|
+
// @ts-ignore
|
|
3025
|
+
return pathLengthFactory(pathInput, distance);
|
|
3026
|
+
}
|
|
3027
|
+
|
|
2780
3028
|
/**
|
|
2781
3029
|
* Creates a new SVGPathCommander instance with the following properties:
|
|
2782
3030
|
* * segments: `pathArray`
|
|
@@ -2796,18 +3044,25 @@ class SVGPathCommander {
|
|
|
2796
3044
|
constructor(pathValue, config) {
|
|
2797
3045
|
const instanceOptions = config || {};
|
|
2798
3046
|
|
|
3047
|
+
const undefPath = typeof pathValue === 'undefined';
|
|
3048
|
+
|
|
3049
|
+
if (undefPath || !pathValue.length) {
|
|
3050
|
+
throw TypeError(`${error}: "pathValue" is ${undefPath ? 'undefined' : 'empty'}`);
|
|
3051
|
+
}
|
|
3052
|
+
|
|
3053
|
+
const segments = parsePathString(pathValue);
|
|
3054
|
+
if (typeof segments === 'string') {
|
|
3055
|
+
throw TypeError(segments);
|
|
3056
|
+
}
|
|
3057
|
+
|
|
2799
3058
|
/**
|
|
2800
|
-
* @type {
|
|
3059
|
+
* @type {SVGPath.pathArray}
|
|
2801
3060
|
*/
|
|
2802
|
-
this.segments =
|
|
2803
|
-
|
|
3061
|
+
this.segments = segments;
|
|
3062
|
+
|
|
2804
3063
|
const {
|
|
2805
|
-
width,
|
|
2806
|
-
|
|
2807
|
-
cx,
|
|
2808
|
-
cy,
|
|
2809
|
-
cz,
|
|
2810
|
-
} = BBox;
|
|
3064
|
+
width, height, cx, cy, cz,
|
|
3065
|
+
} = this.getBBox();
|
|
2811
3066
|
|
|
2812
3067
|
// set instance options.round
|
|
2813
3068
|
let { round, origin } = defaultOptions;
|
|
@@ -2816,7 +3071,7 @@ class SVGPathCommander {
|
|
|
2816
3071
|
if (roundOption === 'auto') {
|
|
2817
3072
|
const pathScale = (`${Math.floor(Math.max(width, height))}`).length;
|
|
2818
3073
|
round = pathScale >= 4 ? 0 : 4 - pathScale;
|
|
2819
|
-
} else if (
|
|
3074
|
+
} else if (Number.isInteger(roundOption) || roundOption === false) {
|
|
2820
3075
|
round = roundOption;
|
|
2821
3076
|
}
|
|
2822
3077
|
|
|
@@ -2826,15 +3081,14 @@ class SVGPathCommander {
|
|
|
2826
3081
|
origin = [
|
|
2827
3082
|
!Number.isNaN(originX) ? originX : cx,
|
|
2828
3083
|
!Number.isNaN(originY) ? originY : cy,
|
|
2829
|
-
originZ
|
|
3084
|
+
!Number.isNaN(originZ) ? originZ : cz,
|
|
2830
3085
|
];
|
|
2831
3086
|
} else {
|
|
2832
3087
|
origin = [cx, cy, cz];
|
|
2833
3088
|
}
|
|
2834
3089
|
|
|
2835
3090
|
/**
|
|
2836
|
-
* @type {number |
|
|
2837
|
-
* @default 4
|
|
3091
|
+
* @type {number | false}
|
|
2838
3092
|
*/
|
|
2839
3093
|
this.round = round;
|
|
2840
3094
|
this.origin = origin;
|
|
@@ -2842,6 +3096,36 @@ class SVGPathCommander {
|
|
|
2842
3096
|
return this;
|
|
2843
3097
|
}
|
|
2844
3098
|
|
|
3099
|
+
/**
|
|
3100
|
+
* Returns the path bounding box, equivalent to native `path.getBBox()`.
|
|
3101
|
+
* @public
|
|
3102
|
+
* @returns {SVGPath.pathBBox}
|
|
3103
|
+
*/
|
|
3104
|
+
getBBox() {
|
|
3105
|
+
return getPathBBox(this.segments);
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
/**
|
|
3109
|
+
* Returns the total path length, equivalent to native `path.getTotalLength()`.
|
|
3110
|
+
* @public
|
|
3111
|
+
* @returns {number}
|
|
3112
|
+
*/
|
|
3113
|
+
getTotalLength() {
|
|
3114
|
+
return getTotalLength(this.segments);
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
/**
|
|
3118
|
+
* Returns an `{x,y}` point in the path stroke at a given length,
|
|
3119
|
+
* equivalent to the native `path.getPointAtLength()`.
|
|
3120
|
+
*
|
|
3121
|
+
* @public
|
|
3122
|
+
* @param {number} length the length
|
|
3123
|
+
* @returns {{x: number, y:number}} the requested point
|
|
3124
|
+
*/
|
|
3125
|
+
getPointAtLength(length) {
|
|
3126
|
+
return getPointAtLength(this.segments, length);
|
|
3127
|
+
}
|
|
3128
|
+
|
|
2845
3129
|
/**
|
|
2846
3130
|
* Convert path to absolute values
|
|
2847
3131
|
* @public
|
|
@@ -2933,16 +3217,16 @@ class SVGPathCommander {
|
|
|
2933
3217
|
|
|
2934
3218
|
/**
|
|
2935
3219
|
* Transform path using values from an `Object` defined as `transformObject`.
|
|
2936
|
-
* @see
|
|
3220
|
+
* @see SVGPath.transformObject for a quick refference
|
|
2937
3221
|
*
|
|
2938
|
-
* @param {
|
|
3222
|
+
* @param {SVGPath.transformObject} source a `transformObject`as described above
|
|
2939
3223
|
* @public
|
|
2940
3224
|
*/
|
|
2941
3225
|
transform(source) {
|
|
2942
3226
|
if (!source || typeof source !== 'object' || (typeof source === 'object'
|
|
2943
3227
|
&& !['translate', 'rotate', 'skew', 'scale'].some((x) => x in source))) return this;
|
|
2944
3228
|
|
|
2945
|
-
/** @type {
|
|
3229
|
+
/** @type {SVGPath.transformObject} */
|
|
2946
3230
|
const transform = {};
|
|
2947
3231
|
Object.keys(source).forEach((fn) => {
|
|
2948
3232
|
// @ts-ignore
|
|
@@ -2952,18 +3236,18 @@ class SVGPathCommander {
|
|
|
2952
3236
|
|
|
2953
3237
|
// if origin is not specified
|
|
2954
3238
|
// it's important that we have one
|
|
3239
|
+
const [cx, cy, cz] = this.origin;
|
|
2955
3240
|
const { origin } = transform;
|
|
2956
|
-
|
|
3241
|
+
|
|
3242
|
+
if (Array.isArray(origin) && origin.length >= 2) {
|
|
2957
3243
|
const [originX, originY, originZ] = origin.map(Number);
|
|
2958
|
-
const [cx, cy, cz] = this.origin;
|
|
2959
3244
|
transform.origin = [
|
|
2960
3245
|
!Number.isNaN(originX) ? originX : cx,
|
|
2961
3246
|
!Number.isNaN(originY) ? originY : cy,
|
|
2962
3247
|
originZ || cz,
|
|
2963
3248
|
];
|
|
2964
3249
|
} else {
|
|
2965
|
-
|
|
2966
|
-
transform.origin = { ...this.origin };
|
|
3250
|
+
transform.origin = [cx, cy, cz];
|
|
2967
3251
|
}
|
|
2968
3252
|
|
|
2969
3253
|
this.segments = transformPath(segments, transform);
|
|
@@ -3026,7 +3310,7 @@ function getCubicSegArea(x1, y1, c1x, c1y, c2x, c2y, x2, y2) {
|
|
|
3026
3310
|
*
|
|
3027
3311
|
* @see https://github.com/paperjs/paper.js/blob/develop/src/path/Path.js
|
|
3028
3312
|
*
|
|
3029
|
-
* @param {
|
|
3313
|
+
* @param {SVGPath.pathArray} path the shape `pathArray`
|
|
3030
3314
|
* @returns {number} the length of the cubic-bezier segment
|
|
3031
3315
|
*/
|
|
3032
3316
|
function getPathArea(path) {
|
|
@@ -3053,7 +3337,7 @@ function getPathArea(path) {
|
|
|
3053
3337
|
* This is the `pathToCurve` version which is faster and more efficient for
|
|
3054
3338
|
* paths that are `curveArray`.
|
|
3055
3339
|
*
|
|
3056
|
-
* @param {string |
|
|
3340
|
+
* @param {string | SVGPath.curveArray} path the target `pathArray`
|
|
3057
3341
|
* @returns {number} the `curveArray` total length
|
|
3058
3342
|
*/
|
|
3059
3343
|
function getPathLength(path) {
|
|
@@ -3066,243 +3350,11 @@ function getPathLength(path) {
|
|
|
3066
3350
|
return totalLength;
|
|
3067
3351
|
}
|
|
3068
3352
|
|
|
3069
|
-
/**
|
|
3070
|
-
* Returns the length of a A (arc-to) segment,
|
|
3071
|
-
* or an {x,y} point at a given length.
|
|
3072
|
-
*
|
|
3073
|
-
* @param {number} X1 the starting x position
|
|
3074
|
-
* @param {number} Y1 the starting y position
|
|
3075
|
-
* @param {number} RX x-radius of the arc
|
|
3076
|
-
* @param {number} RY y-radius of the arc
|
|
3077
|
-
* @param {number} angle x-axis-rotation of the arc
|
|
3078
|
-
* @param {number} LAF large-arc-flag of the arc
|
|
3079
|
-
* @param {number} SF sweep-flag of the arc
|
|
3080
|
-
* @param {number} X2 the ending x position
|
|
3081
|
-
* @param {number} Y2 the ending y position
|
|
3082
|
-
* @param {number} distance the point distance
|
|
3083
|
-
* @returns {{x: number, y: number} | number} the segment length or point
|
|
3084
|
-
*/
|
|
3085
|
-
function segmentArcFactory(X1, Y1, RX, RY, angle, LAF, SF, X2, Y2, distance) {
|
|
3086
|
-
let [x, y] = [X1, Y1];
|
|
3087
|
-
const cubicSeg = arcToCubic(X1, Y1, RX, RY, angle, LAF, SF, X2, Y2);
|
|
3088
|
-
const lengthMargin = 0.001;
|
|
3089
|
-
let totalLength = 0;
|
|
3090
|
-
let cubicSubseg = [];
|
|
3091
|
-
let argsc = [];
|
|
3092
|
-
let segLen = 0;
|
|
3093
|
-
|
|
3094
|
-
if (typeof distance === 'number' && distance < lengthMargin) {
|
|
3095
|
-
return { x, y };
|
|
3096
|
-
}
|
|
3097
|
-
|
|
3098
|
-
for (let i = 0, ii = cubicSeg.length; i < ii; i += 6) {
|
|
3099
|
-
cubicSubseg = cubicSeg.slice(i, i + 6);
|
|
3100
|
-
argsc = [x, y, ...cubicSubseg];
|
|
3101
|
-
// @ts-ignore
|
|
3102
|
-
segLen = segmentCubicFactory(...argsc);
|
|
3103
|
-
if (typeof distance === 'number' && totalLength + segLen >= distance) {
|
|
3104
|
-
// @ts-ignore -- this is a `cubicSegment`
|
|
3105
|
-
return segmentCubicFactory(...argsc, distance - totalLength);
|
|
3106
|
-
}
|
|
3107
|
-
totalLength += segLen;
|
|
3108
|
-
[x, y] = cubicSubseg.slice(-2);
|
|
3109
|
-
}
|
|
3110
|
-
|
|
3111
|
-
if (typeof distance === 'number' && distance >= totalLength) {
|
|
3112
|
-
return { x: X2, y: Y2 };
|
|
3113
|
-
}
|
|
3114
|
-
|
|
3115
|
-
return totalLength;
|
|
3116
|
-
}
|
|
3117
|
-
|
|
3118
|
-
/**
|
|
3119
|
-
* Returns the {x,y} coordinates of a point at a
|
|
3120
|
-
* given length of a quad-bezier segment.
|
|
3121
|
-
*
|
|
3122
|
-
* @see https://github.com/substack/point-at-length
|
|
3123
|
-
*
|
|
3124
|
-
* @param {number} x1 the starting point X
|
|
3125
|
-
* @param {number} y1 the starting point Y
|
|
3126
|
-
* @param {number} cx the control point X
|
|
3127
|
-
* @param {number} cy the control point Y
|
|
3128
|
-
* @param {number} x2 the ending point X
|
|
3129
|
-
* @param {number} y2 the ending point Y
|
|
3130
|
-
* @param {number} t a [0-1] ratio
|
|
3131
|
-
* @returns {{x: number, y: number}} the requested {x,y} coordinates
|
|
3132
|
-
*/
|
|
3133
|
-
function getPointAtQuadSegmentLength(x1, y1, cx, cy, x2, y2, t) {
|
|
3134
|
-
const t1 = 1 - t;
|
|
3135
|
-
return {
|
|
3136
|
-
x: (t1 ** 2) * x1
|
|
3137
|
-
+ 2 * t1 * t * cx
|
|
3138
|
-
+ (t ** 2) * x2,
|
|
3139
|
-
y: (t1 ** 2) * y1
|
|
3140
|
-
+ 2 * t1 * t * cy
|
|
3141
|
-
+ (t ** 2) * y2,
|
|
3142
|
-
};
|
|
3143
|
-
}
|
|
3144
|
-
|
|
3145
|
-
/**
|
|
3146
|
-
* Returns the Q (quadratic-bezier) segment length,
|
|
3147
|
-
* or an {x,y} point at a given length.
|
|
3148
|
-
*
|
|
3149
|
-
* @param {number} x1 the starting point X
|
|
3150
|
-
* @param {number} y1 the starting point Y
|
|
3151
|
-
* @param {number} qx the control point X
|
|
3152
|
-
* @param {number} qy the control point Y
|
|
3153
|
-
* @param {number} x2 the ending point X
|
|
3154
|
-
* @param {number} y2 the ending point Y
|
|
3155
|
-
* @param {number=} distance the distance to point
|
|
3156
|
-
* @returns {{x: number, y: number} | number} the segment length or point
|
|
3157
|
-
*/
|
|
3158
|
-
function segmentQuadFactory(x1, y1, qx, qy, x2, y2, distance) {
|
|
3159
|
-
let x = x1; let y = y1;
|
|
3160
|
-
const lengthMargin = 0.001;
|
|
3161
|
-
let totalLength = 0;
|
|
3162
|
-
let prev = [x1, y1, totalLength];
|
|
3163
|
-
/** @type {[number, number]} */
|
|
3164
|
-
let cur = [x1, y1];
|
|
3165
|
-
let t = 0;
|
|
3166
|
-
|
|
3167
|
-
if (typeof distance === 'number' && distance < lengthMargin) {
|
|
3168
|
-
return { x, y };
|
|
3169
|
-
}
|
|
3170
|
-
|
|
3171
|
-
const n = 100;
|
|
3172
|
-
for (let j = 0; j <= n; j += 1) {
|
|
3173
|
-
t = j / n;
|
|
3174
|
-
|
|
3175
|
-
({ x, y } = getPointAtQuadSegmentLength(x1, y1, qx, qy, x2, y2, t));
|
|
3176
|
-
totalLength += distanceSquareRoot(cur, [x, y]);
|
|
3177
|
-
cur = [x, y];
|
|
3178
|
-
|
|
3179
|
-
if (typeof distance === 'number' && totalLength >= distance) {
|
|
3180
|
-
const dv = (totalLength - distance) / (totalLength - prev[2]);
|
|
3181
|
-
|
|
3182
|
-
return {
|
|
3183
|
-
x: cur[0] * (1 - dv) + prev[0] * dv,
|
|
3184
|
-
y: cur[1] * (1 - dv) + prev[1] * dv,
|
|
3185
|
-
};
|
|
3186
|
-
}
|
|
3187
|
-
prev = [x, y, totalLength];
|
|
3188
|
-
}
|
|
3189
|
-
if (typeof distance === 'number' && distance >= totalLength) {
|
|
3190
|
-
return { x: x2, y: y2 };
|
|
3191
|
-
}
|
|
3192
|
-
return totalLength;
|
|
3193
|
-
}
|
|
3194
|
-
|
|
3195
|
-
/**
|
|
3196
|
-
* Returns a {x,y} point at a given length of a shape or the shape total length.
|
|
3197
|
-
*
|
|
3198
|
-
* @param {string | SVGPathCommander.pathArray} pathInput the `pathArray` to look into
|
|
3199
|
-
* @param {number=} distance the length of the shape to look at
|
|
3200
|
-
* @returns {{x: number, y: number} | number} the total length or point
|
|
3201
|
-
*/
|
|
3202
|
-
function pathLengthFactory(pathInput, distance) {
|
|
3203
|
-
let totalLength = 0;
|
|
3204
|
-
let isM = true;
|
|
3205
|
-
/** @type {number[]} */
|
|
3206
|
-
let data = [];
|
|
3207
|
-
let pathCommand = 'M';
|
|
3208
|
-
let segLen = 0;
|
|
3209
|
-
let x = 0;
|
|
3210
|
-
let y = 0;
|
|
3211
|
-
let mx = 0;
|
|
3212
|
-
let my = 0;
|
|
3213
|
-
let seg;
|
|
3214
|
-
const path = fixPath(normalizePath(pathInput));
|
|
3215
|
-
|
|
3216
|
-
for (let i = 0, ll = path.length; i < ll; i += 1) {
|
|
3217
|
-
seg = path[i];
|
|
3218
|
-
[pathCommand] = seg;
|
|
3219
|
-
isM = pathCommand === 'M';
|
|
3220
|
-
// @ts-ignore
|
|
3221
|
-
data = !isM ? [x, y, ...seg.slice(1)] : data;
|
|
3222
|
-
|
|
3223
|
-
// this segment is always ZERO
|
|
3224
|
-
if (isM) {
|
|
3225
|
-
// remember mx, my for Z
|
|
3226
|
-
// @ts-ignore
|
|
3227
|
-
[, mx, my] = seg;
|
|
3228
|
-
if (typeof distance === 'number' && distance < 0.001) {
|
|
3229
|
-
return { x: mx, y: my };
|
|
3230
|
-
}
|
|
3231
|
-
} else if (pathCommand === 'L') {
|
|
3232
|
-
// @ts-ignore
|
|
3233
|
-
segLen = segmentLineFactory(...data);
|
|
3234
|
-
if (distance && totalLength + segLen >= distance) {
|
|
3235
|
-
// @ts-ignore
|
|
3236
|
-
return segmentLineFactory(...data, distance - totalLength);
|
|
3237
|
-
}
|
|
3238
|
-
totalLength += segLen;
|
|
3239
|
-
} else if (pathCommand === 'A') {
|
|
3240
|
-
// @ts-ignore
|
|
3241
|
-
segLen = segmentArcFactory(...data);
|
|
3242
|
-
if (distance && totalLength + segLen >= distance) {
|
|
3243
|
-
// @ts-ignore
|
|
3244
|
-
return segmentArcFactory(...data, distance - totalLength);
|
|
3245
|
-
}
|
|
3246
|
-
totalLength += segLen;
|
|
3247
|
-
} else if (pathCommand === 'C') {
|
|
3248
|
-
// @ts-ignore
|
|
3249
|
-
segLen = segmentCubicFactory(...data);
|
|
3250
|
-
if (distance && totalLength + segLen >= distance) {
|
|
3251
|
-
// @ts-ignore
|
|
3252
|
-
return segmentCubicFactory(...data, distance - totalLength);
|
|
3253
|
-
}
|
|
3254
|
-
totalLength += segLen;
|
|
3255
|
-
} else if (pathCommand === 'Q') {
|
|
3256
|
-
// @ts-ignore
|
|
3257
|
-
segLen = segmentQuadFactory(...data);
|
|
3258
|
-
if (distance && totalLength + segLen >= distance) {
|
|
3259
|
-
// @ts-ignore
|
|
3260
|
-
return segmentQuadFactory(...data, distance - totalLength);
|
|
3261
|
-
}
|
|
3262
|
-
totalLength += segLen;
|
|
3263
|
-
} else if (pathCommand === 'Z') {
|
|
3264
|
-
data = [x, y, mx, my];
|
|
3265
|
-
// @ts-ignore
|
|
3266
|
-
segLen = segmentLineFactory(...data);
|
|
3267
|
-
if (distance && totalLength + segLen >= distance) {
|
|
3268
|
-
// @ts-ignore
|
|
3269
|
-
return segmentLineFactory(...data, distance - totalLength);
|
|
3270
|
-
}
|
|
3271
|
-
totalLength += segLen;
|
|
3272
|
-
}
|
|
3273
|
-
|
|
3274
|
-
// @ts-ignore -- needed for the below
|
|
3275
|
-
[x, y] = pathCommand !== 'Z' ? seg.slice(-2) : [mx, my];
|
|
3276
|
-
}
|
|
3277
|
-
|
|
3278
|
-
// native `getPointAtLength` behavior when the given distance
|
|
3279
|
-
// is higher than total length
|
|
3280
|
-
if (distance && distance >= totalLength) {
|
|
3281
|
-
return { x, y };
|
|
3282
|
-
}
|
|
3283
|
-
|
|
3284
|
-
return totalLength;
|
|
3285
|
-
}
|
|
3286
|
-
|
|
3287
|
-
/**
|
|
3288
|
-
* Returns the shape total length, or the equivalent to `shape.getTotalLength()`.
|
|
3289
|
-
*
|
|
3290
|
-
* The `normalizePath` version is lighter, faster, more efficient and more accurate
|
|
3291
|
-
* with paths that are not `curveArray`.
|
|
3292
|
-
*
|
|
3293
|
-
* @param {string | SVGPathCommander.pathArray} pathInput the target `pathArray`
|
|
3294
|
-
* @returns {number} the shape total length
|
|
3295
|
-
*/
|
|
3296
|
-
function getTotalLength(pathInput) {
|
|
3297
|
-
// @ts-ignore - it's fine
|
|
3298
|
-
return pathLengthFactory(pathInput);
|
|
3299
|
-
}
|
|
3300
|
-
|
|
3301
3353
|
/**
|
|
3302
3354
|
* Check if a path is drawn clockwise and returns true if so,
|
|
3303
3355
|
* false otherwise.
|
|
3304
3356
|
*
|
|
3305
|
-
* @param {
|
|
3357
|
+
* @param {SVGPath.pathArray} path the path string or `pathArray`
|
|
3306
3358
|
* @returns {boolean} true when clockwise or false if not
|
|
3307
3359
|
*/
|
|
3308
3360
|
function getDrawDirection(path) {
|
|
@@ -3310,37 +3362,12 @@ function getDrawDirection(path) {
|
|
|
3310
3362
|
}
|
|
3311
3363
|
|
|
3312
3364
|
/**
|
|
3313
|
-
* Returns
|
|
3314
|
-
*
|
|
3315
|
-
* @param {string | SVGPathCommander.pathArray} pathInput the `pathArray` to look into
|
|
3316
|
-
* @param {number} distance the length of the shape to look at
|
|
3317
|
-
* @returns {{x: number, y: number}} the requested {x, y} point coordinates
|
|
3318
|
-
*/
|
|
3319
|
-
function getPointAtLength(pathInput, distance) {
|
|
3320
|
-
// @ts-ignore
|
|
3321
|
-
return pathLengthFactory(pathInput, distance);
|
|
3322
|
-
}
|
|
3323
|
-
|
|
3324
|
-
/**
|
|
3325
|
-
* Returns [x,y] coordinates of a point at a given length of a shape.
|
|
3326
|
-
* `pathToCurve` version.
|
|
3365
|
+
* Returns the segment, its index and length as well as
|
|
3366
|
+
* the length to that segment at a given length in a path.
|
|
3327
3367
|
*
|
|
3328
|
-
* @
|
|
3329
|
-
*
|
|
3330
|
-
* @param {string | SVGPathCommander.pathArray} pathInput the `pathArray` to look into
|
|
3331
|
-
* @param {number} distance the length of the shape to look at
|
|
3332
|
-
* @returns {{x: number, y: number}} the requested {x, y} point coordinates
|
|
3333
|
-
*/
|
|
3334
|
-
function getPointAtPathLength(pathInput, distance) {
|
|
3335
|
-
return getPointAtLength(pathInput, distance);
|
|
3336
|
-
}
|
|
3337
|
-
|
|
3338
|
-
/**
|
|
3339
|
-
* Returns the properties at a given length in path.
|
|
3340
|
-
*
|
|
3341
|
-
* @param {string | SVGPathCommander.pathArray} pathInput target `pathArray`
|
|
3368
|
+
* @param {string | SVGPath.pathArray} pathInput target `pathArray`
|
|
3342
3369
|
* @param {number=} distance the given length
|
|
3343
|
-
* @returns {
|
|
3370
|
+
* @returns {SVGPath.segmentProperties=} the requested properties
|
|
3344
3371
|
*/
|
|
3345
3372
|
function getPropertiesAtLength(pathInput, distance) {
|
|
3346
3373
|
const pathArray = parsePathString(pathInput);
|
|
@@ -3352,7 +3379,7 @@ function getPropertiesAtLength(pathInput, distance) {
|
|
|
3352
3379
|
let index = pathTemp.length - 1;
|
|
3353
3380
|
let lengthAtSegment = 0;
|
|
3354
3381
|
let length = 0;
|
|
3355
|
-
/** @type {
|
|
3382
|
+
/** @type {SVGPath.pathSegment} */
|
|
3356
3383
|
let segment = pathArray[0];
|
|
3357
3384
|
const [x, y] = segment.slice(-2);
|
|
3358
3385
|
const point = { x, y };
|
|
@@ -3396,12 +3423,13 @@ function getPropertiesAtLength(pathInput, distance) {
|
|
|
3396
3423
|
}
|
|
3397
3424
|
|
|
3398
3425
|
/**
|
|
3399
|
-
* Returns the point in path closest to a given point
|
|
3426
|
+
* Returns the point and segment in path closest to a given point as well as
|
|
3427
|
+
* the distance to the path stroke.
|
|
3400
3428
|
* @see https://bl.ocks.org/mbostock/8027637
|
|
3401
3429
|
*
|
|
3402
|
-
* @param {string |
|
|
3430
|
+
* @param {string | SVGPath.pathArray} pathInput target `pathArray`
|
|
3403
3431
|
* @param {{x: number, y: number}} point the given point
|
|
3404
|
-
* @returns {
|
|
3432
|
+
* @returns {SVGPath.pointProperties} the requested properties
|
|
3405
3433
|
*/
|
|
3406
3434
|
function getPropertiesAtPoint(pathInput, point) {
|
|
3407
3435
|
const path = fixPath(parsePathString(pathInput));
|
|
@@ -3460,7 +3488,7 @@ function getPropertiesAtPoint(pathInput, point) {
|
|
|
3460
3488
|
}
|
|
3461
3489
|
}
|
|
3462
3490
|
|
|
3463
|
-
const segment = getPropertiesAtLength(path,
|
|
3491
|
+
const segment = getPropertiesAtLength(path, bestLength);
|
|
3464
3492
|
const distance = Math.sqrt(bestDistance);
|
|
3465
3493
|
|
|
3466
3494
|
return { closest, distance, segment };
|
|
@@ -3469,7 +3497,7 @@ function getPropertiesAtPoint(pathInput, point) {
|
|
|
3469
3497
|
/**
|
|
3470
3498
|
* Returns the point in path closest to a given point.
|
|
3471
3499
|
*
|
|
3472
|
-
* @param {string |
|
|
3500
|
+
* @param {string | SVGPath.pathArray} pathInput target `pathArray`
|
|
3473
3501
|
* @param {{x: number, y: number}} point the given point
|
|
3474
3502
|
* @returns {{x: number, y: number}} the best match
|
|
3475
3503
|
*/
|
|
@@ -3480,9 +3508,9 @@ function getClosestPoint(pathInput, point) {
|
|
|
3480
3508
|
/**
|
|
3481
3509
|
* Returns the path segment which contains a given point.
|
|
3482
3510
|
*
|
|
3483
|
-
* @param {string |
|
|
3511
|
+
* @param {string | SVGPath.pathArray} path the `pathArray` to look into
|
|
3484
3512
|
* @param {{x: number, y: number}} point the point of the shape to look for
|
|
3485
|
-
* @returns {
|
|
3513
|
+
* @returns {SVGPath.pathSegment?} the requested segment
|
|
3486
3514
|
*/
|
|
3487
3515
|
function getSegmentOfPoint(path, point) {
|
|
3488
3516
|
const props = getPropertiesAtPoint(path, point);
|
|
@@ -3492,9 +3520,9 @@ function getSegmentOfPoint(path, point) {
|
|
|
3492
3520
|
|
|
3493
3521
|
/**
|
|
3494
3522
|
* Returns the segment at a given length.
|
|
3495
|
-
* @param {string |
|
|
3523
|
+
* @param {string | SVGPath.pathArray} pathInput the target `pathArray`
|
|
3496
3524
|
* @param {number} distance the distance in path to look at
|
|
3497
|
-
* @returns {
|
|
3525
|
+
* @returns {SVGPath.pathSegment?} the requested segment
|
|
3498
3526
|
*/
|
|
3499
3527
|
function getSegmentAtLength(pathInput, distance) {
|
|
3500
3528
|
const props = getPropertiesAtLength(pathInput, distance);
|
|
@@ -3505,8 +3533,8 @@ function getSegmentAtLength(pathInput, distance) {
|
|
|
3505
3533
|
/**
|
|
3506
3534
|
* Checks if a given point is in the stroke of a path.
|
|
3507
3535
|
*
|
|
3508
|
-
* @param {string |
|
|
3509
|
-
* @param {{x: number, y: number}} point
|
|
3536
|
+
* @param {string | SVGPath.pathArray} pathInput target path
|
|
3537
|
+
* @param {{x: number, y: number}} point the given `{x,y}` point
|
|
3510
3538
|
* @returns {boolean} the query result
|
|
3511
3539
|
*/
|
|
3512
3540
|
function isPointInStroke(pathInput, point) {
|
|
@@ -3553,8 +3581,8 @@ const shapeParams = {
|
|
|
3553
3581
|
/**
|
|
3554
3582
|
* Returns a new `pathArray` from line attributes.
|
|
3555
3583
|
*
|
|
3556
|
-
* @param {
|
|
3557
|
-
* @returns {
|
|
3584
|
+
* @param {SVGPath.lineAttr} attr shape configuration
|
|
3585
|
+
* @returns {SVGPath.pathArray} a new line `pathArray`
|
|
3558
3586
|
*/
|
|
3559
3587
|
function getLinePath(attr) {
|
|
3560
3588
|
const {
|
|
@@ -3566,14 +3594,14 @@ function getLinePath(attr) {
|
|
|
3566
3594
|
/**
|
|
3567
3595
|
* Returns a new `pathArray` like from polyline/polygon attributes.
|
|
3568
3596
|
*
|
|
3569
|
-
* @param {
|
|
3570
|
-
* @return {
|
|
3597
|
+
* @param {SVGPath.polyAttr} attr shape configuration
|
|
3598
|
+
* @return {SVGPath.pathArray} a new polygon/polyline `pathArray`
|
|
3571
3599
|
*/
|
|
3572
3600
|
function getPolyPath(attr) {
|
|
3573
|
-
/** @type {
|
|
3601
|
+
/** @type {SVGPath.pathArray} */
|
|
3574
3602
|
// @ts-ignore -- it's an empty `pathArray`
|
|
3575
3603
|
const pathArray = [];
|
|
3576
|
-
const points = attr.points.split(/[\s|,]/).map(Number);
|
|
3604
|
+
const points = attr.points.trim().split(/[\s|,]/).map(Number);
|
|
3577
3605
|
|
|
3578
3606
|
let index = 0;
|
|
3579
3607
|
while (index < points.length) {
|
|
@@ -3587,8 +3615,8 @@ function getPolyPath(attr) {
|
|
|
3587
3615
|
/**
|
|
3588
3616
|
* Returns a new `pathArray` from circle attributes.
|
|
3589
3617
|
*
|
|
3590
|
-
* @param {
|
|
3591
|
-
* @return {
|
|
3618
|
+
* @param {SVGPath.circleAttr} attr shape configuration
|
|
3619
|
+
* @return {SVGPath.pathArray} a circle `pathArray`
|
|
3592
3620
|
*/
|
|
3593
3621
|
function getCirclePath(attr) {
|
|
3594
3622
|
const {
|
|
@@ -3605,8 +3633,8 @@ function getCirclePath(attr) {
|
|
|
3605
3633
|
/**
|
|
3606
3634
|
* Returns a new `pathArray` from ellipse attributes.
|
|
3607
3635
|
*
|
|
3608
|
-
* @param {
|
|
3609
|
-
* @return {
|
|
3636
|
+
* @param {SVGPath.ellipseAttr} attr shape configuration
|
|
3637
|
+
* @return {SVGPath.pathArray} an ellipse `pathArray`
|
|
3610
3638
|
*/
|
|
3611
3639
|
function getEllipsePath(attr) {
|
|
3612
3640
|
const {
|
|
@@ -3623,8 +3651,8 @@ function getEllipsePath(attr) {
|
|
|
3623
3651
|
/**
|
|
3624
3652
|
* Returns a new `pathArray` like from rect attributes.
|
|
3625
3653
|
*
|
|
3626
|
-
* @param {
|
|
3627
|
-
* @return {
|
|
3654
|
+
* @param {SVGPath.rectAttr} attr object with properties above
|
|
3655
|
+
* @return {SVGPath.pathArray} a new `pathArray` from `<rect>` attributes
|
|
3628
3656
|
*/
|
|
3629
3657
|
function getRectanglePath(attr) {
|
|
3630
3658
|
const x = +attr.x || 0;
|
|
@@ -3670,12 +3698,12 @@ function getRectanglePath(attr) {
|
|
|
3670
3698
|
* is `true`, it will replace the target.
|
|
3671
3699
|
*
|
|
3672
3700
|
* It can also work with an options object,
|
|
3673
|
-
* @see
|
|
3701
|
+
* @see SVGPath.shapeOps
|
|
3674
3702
|
*
|
|
3675
3703
|
* The newly created `<path>` element keeps all non-specific
|
|
3676
3704
|
* attributes like `class`, `fill`, etc.
|
|
3677
3705
|
*
|
|
3678
|
-
* @param {
|
|
3706
|
+
* @param {SVGPath.shapeTypes | SVGPath.shapeOps} element target shape
|
|
3679
3707
|
* @param {boolean=} replace option to replace target
|
|
3680
3708
|
* @return {SVGPathElement | boolean} the newly created `<path>` element
|
|
3681
3709
|
*/
|
|
@@ -3729,11 +3757,11 @@ function shapeToPath(element, replace) {
|
|
|
3729
3757
|
}
|
|
3730
3758
|
|
|
3731
3759
|
/**
|
|
3732
|
-
* Reverses all segments
|
|
3760
|
+
* Reverses all segments of a `pathArray`
|
|
3733
3761
|
* which consists of only C (cubic-bezier) path commands.
|
|
3734
3762
|
*
|
|
3735
|
-
* @param {
|
|
3736
|
-
* @returns {
|
|
3763
|
+
* @param {SVGPath.curveArray} path the source `pathArray`
|
|
3764
|
+
* @returns {SVGPath.curveArray} the reversed `pathArray`
|
|
3737
3765
|
*/
|
|
3738
3766
|
function reverseCurve(path) {
|
|
3739
3767
|
const rotatedCurve = path.slice(1)
|
|
@@ -3770,7 +3798,6 @@ const Util = {
|
|
|
3770
3798
|
getTotalLength,
|
|
3771
3799
|
getPathLength,
|
|
3772
3800
|
getPointAtLength,
|
|
3773
|
-
getPointAtPathLength,
|
|
3774
3801
|
getClosestPoint,
|
|
3775
3802
|
getSegmentOfPoint,
|
|
3776
3803
|
getPropertiesAtPoint,
|
|
@@ -3790,7 +3817,7 @@ const Util = {
|
|
|
3790
3817
|
options: defaultOptions,
|
|
3791
3818
|
};
|
|
3792
3819
|
|
|
3793
|
-
var version = "0.
|
|
3820
|
+
var version = "0.2.0alpha1";
|
|
3794
3821
|
|
|
3795
3822
|
// @ts-ignore
|
|
3796
3823
|
|