svg-path-commander 2.1.0 → 2.1.1
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 +61 -5
- package/dist/svg-path-commander.cjs +1 -1
- package/dist/svg-path-commander.cjs.map +1 -1
- package/dist/svg-path-commander.d.ts +119 -33
- package/dist/svg-path-commander.js +1 -1
- package/dist/svg-path-commander.js.map +1 -1
- package/dist/svg-path-commander.mjs +798 -828
- package/dist/svg-path-commander.mjs.map +1 -1
- package/package.json +3 -3
- package/src/convert/pathToAbsolute.ts +16 -70
- package/src/convert/pathToCurve.ts +36 -28
- package/src/convert/pathToRelative.ts +33 -62
- package/src/index.ts +13 -19
- package/src/interface.ts +1 -1
- package/src/math/arcTools.ts +248 -71
- package/src/math/bezier.ts +19 -27
- package/src/math/cubicTools.ts +67 -26
- package/src/math/distanceSquareRoot.ts +3 -1
- package/src/math/lineTools.ts +41 -26
- package/src/math/midPoint.ts +3 -1
- package/src/math/polygonArea.ts +3 -1
- package/src/math/polygonLength.ts +2 -1
- package/src/math/quadTools.ts +45 -26
- package/src/parser/parsePathString.ts +4 -4
- package/src/process/absolutizeSegment.ts +58 -0
- package/src/process/iterate.ts +33 -0
- package/src/process/normalizePath.ts +34 -28
- package/src/process/normalizeSegment.ts +8 -9
- package/src/process/projection2d.ts +2 -1
- package/src/process/relativizeSegment.ts +61 -0
- package/src/process/reversePath.ts +1 -1
- package/src/process/roundPath.ts +8 -10
- package/src/process/segmentToCubic.ts +1 -1
- package/src/process/shortenSegment.ts +3 -3
- package/src/process/splitCubic.ts +8 -7
- package/src/process/splitPath.ts +38 -4
- package/src/process/transformPath.ts +80 -73
- package/src/types.ts +35 -1
- package/src/util/getPathArea.ts +3 -3
- package/src/util/getPathBBox.ts +86 -19
- package/src/util/getPointAtLength.ts +98 -4
- package/src/util/getPropertiesAtLength.ts +2 -2
- package/src/util/getTotalLength.ts +71 -4
- package/test/class.test.ts +15 -14
- package/test/fixtures/shapes.js +27 -27
- package/test/fixtures/simpleShapes.js +18 -18
- package/test/static.test.ts +37 -17
- package/cypress.config.ts +0 -29
- package/src/process/fixArc.ts +0 -23
- package/src/process/replaceArc.ts +0 -52
- package/src/util/pathFactory.ts +0 -130
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import midPoint from '../math/midPoint';
|
|
2
|
-
import type { CubicSegment } from '../types';
|
|
2
|
+
import type { CubicSegment, PointTuple } from '../types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Split a cubic-bezier segment into two.
|
|
6
6
|
*
|
|
7
7
|
* @param pts the cubic-bezier parameters
|
|
8
|
+
* @param ratio the cubic-bezier parameters
|
|
8
9
|
* @return two new cubic-bezier segments
|
|
9
10
|
*/
|
|
10
|
-
const splitCubic = (pts: number[]
|
|
11
|
-
const t =
|
|
12
|
-
const p0 = pts.slice(0, 2) as
|
|
13
|
-
const p1 = pts.slice(2, 4) as
|
|
14
|
-
const p2 = pts.slice(4, 6) as
|
|
15
|
-
const p3 = pts.slice(6, 8) as
|
|
11
|
+
const splitCubic = (pts: number[], ratio = 0.5): [CubicSegment, CubicSegment] => {
|
|
12
|
+
const t = ratio;
|
|
13
|
+
const p0 = pts.slice(0, 2) as PointTuple;
|
|
14
|
+
const p1 = pts.slice(2, 4) as PointTuple;
|
|
15
|
+
const p2 = pts.slice(4, 6) as PointTuple;
|
|
16
|
+
const p3 = pts.slice(6, 8) as PointTuple;
|
|
16
17
|
const p4 = midPoint(p0, p1, t);
|
|
17
18
|
const p5 = midPoint(p1, p2, t);
|
|
18
19
|
const p6 = midPoint(p2, p3, t);
|
package/src/process/splitPath.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import paramsParser from '../parser/paramsParser';
|
|
2
|
+
import type { AbsoluteCommand, HSegment, MSegment, PathArray, PointTuple, RelativeCommand, VSegment } from '../types';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Split a path into an `Array` of sub-path strings.
|
|
@@ -7,20 +8,53 @@ import type { PathArray } from '../types';
|
|
|
7
8
|
* for visual consistency.
|
|
8
9
|
*
|
|
9
10
|
* @param pathInput the source `pathArray`
|
|
10
|
-
* @return
|
|
11
|
+
* @return an array with all sub-path strings
|
|
11
12
|
*/
|
|
12
13
|
const splitPath = (pathInput: PathArray): PathArray[] => {
|
|
13
14
|
const composite = [] as PathArray[];
|
|
14
15
|
let path: PathArray;
|
|
15
16
|
let pi = -1;
|
|
17
|
+
let x = 0;
|
|
18
|
+
let y = 0;
|
|
19
|
+
let mx = 0;
|
|
20
|
+
let my = 0;
|
|
21
|
+
const params = { ...paramsParser };
|
|
16
22
|
|
|
17
23
|
pathInput.forEach(seg => {
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
const [pathCommand] = seg;
|
|
25
|
+
const absCommand = pathCommand.toUpperCase() as AbsoluteCommand;
|
|
26
|
+
const relCommand = pathCommand.toLowerCase() as RelativeCommand;
|
|
27
|
+
const isRelative = pathCommand === relCommand;
|
|
28
|
+
const values = seg.slice(1) as number[];
|
|
29
|
+
|
|
30
|
+
if (absCommand === 'M') {
|
|
20
31
|
pi += 1;
|
|
32
|
+
[x, y] = values as PointTuple;
|
|
33
|
+
x += isRelative ? params.x : 0;
|
|
34
|
+
y += isRelative ? params.y : 0;
|
|
35
|
+
mx = x;
|
|
36
|
+
my = y;
|
|
37
|
+
path = [(isRelative ? [absCommand, mx, my] : seg) as MSegment];
|
|
21
38
|
} else {
|
|
39
|
+
if (absCommand === 'Z') {
|
|
40
|
+
x = mx;
|
|
41
|
+
y = my;
|
|
42
|
+
} else if (absCommand === 'H') {
|
|
43
|
+
[, x] = seg as HSegment;
|
|
44
|
+
x += isRelative ? params.x : /* istanbul ignore next @preserve */ 0;
|
|
45
|
+
} else if (absCommand === 'V') {
|
|
46
|
+
[, y] = seg as VSegment;
|
|
47
|
+
y += isRelative ? params.y : /* istanbul ignore next @preserve */ 0;
|
|
48
|
+
} else {
|
|
49
|
+
[x, y] = seg.slice(-2) as PointTuple;
|
|
50
|
+
x += isRelative ? params.x : 0;
|
|
51
|
+
y += isRelative ? params.y : 0;
|
|
52
|
+
}
|
|
22
53
|
path.push(seg);
|
|
23
54
|
}
|
|
55
|
+
|
|
56
|
+
params.x = x;
|
|
57
|
+
params.y = y;
|
|
24
58
|
composite[pi] = path;
|
|
25
59
|
});
|
|
26
60
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import normalizePath from './normalizePath';
|
|
2
|
-
// import pathToAbsolute from '../convert/pathToAbsolute';
|
|
3
|
-
// import segmentToCubic from './segmentToCubic';
|
|
4
|
-
// import fixArc from './fixArc';
|
|
5
1
|
import getSVGMatrix from './getSVGMatrix';
|
|
6
2
|
import projection2d from './projection2d';
|
|
7
|
-
import paramsParser from '../parser/paramsParser';
|
|
8
|
-
import replaceArc from './replaceArc';
|
|
9
3
|
import defaultOptions from '../options/options';
|
|
10
|
-
import type { AbsoluteArray, PathArray, TransformObjectValues } from '../types';
|
|
11
|
-
import type {
|
|
4
|
+
import type { AbsoluteArray, CSegment, PathArray, PointTuple, TransformObjectValues } from '../types';
|
|
5
|
+
import type { TransformObject } from '../interface';
|
|
6
|
+
import iterate from './iterate';
|
|
7
|
+
import parsePathString from '../parser/parsePathString';
|
|
8
|
+
import absolutizeSegment from './absolutizeSegment';
|
|
9
|
+
import segmentToCubic from './segmentToCubic';
|
|
10
|
+
import normalizeSegment from './normalizeSegment';
|
|
11
|
+
import paramsParser from '../parser/paramsParser';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Apply a 2D / 3D transformation to a `pathArray` instance.
|
|
@@ -20,89 +20,96 @@ import type { PathTransform, TransformObject } from '../interface';
|
|
|
20
20
|
* @param transform the transform functions `Object`
|
|
21
21
|
* @returns the resulted `pathArray`
|
|
22
22
|
*/
|
|
23
|
-
const transformPath = (
|
|
23
|
+
const transformPath = (pathInput: PathArray | string, transform?: Partial<TransformObject>) => {
|
|
24
24
|
let x = 0;
|
|
25
25
|
let y = 0;
|
|
26
|
-
let
|
|
27
|
-
let
|
|
28
|
-
let
|
|
29
|
-
let
|
|
30
|
-
let
|
|
31
|
-
let
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
let mx = 0;
|
|
27
|
+
let my = 0;
|
|
28
|
+
let lx = 0;
|
|
29
|
+
let ly = 0;
|
|
30
|
+
let j = 0;
|
|
31
|
+
let jj = 0;
|
|
32
|
+
let nx = 0;
|
|
33
|
+
let ny = 0;
|
|
34
|
+
let pathCommand = 'M';
|
|
35
|
+
// transform uses it's own set of params
|
|
36
|
+
const transformParams = { ...paramsParser };
|
|
37
|
+
const path = parsePathString(pathInput);
|
|
36
38
|
const transformProps = transform && Object.keys(transform);
|
|
37
39
|
|
|
38
40
|
// when used as a static method, invalidate somehow
|
|
39
|
-
if (!transform || (transformProps && !transformProps.length)) return
|
|
41
|
+
if (!transform || (transformProps && !transformProps.length)) return path;
|
|
40
42
|
|
|
41
|
-
const normalizedPath = normalizePath(absolutePath);
|
|
42
43
|
// transform origin is extremely important
|
|
43
44
|
if (!transform.origin) {
|
|
44
|
-
|
|
45
|
-
Object.assign(transform, { origin: defaultOrigin });
|
|
45
|
+
Object.assign(transform, { origin: defaultOptions.origin });
|
|
46
46
|
}
|
|
47
|
+
const origin = transform.origin as [number, number, number];
|
|
47
48
|
const matrixInstance = getSVGMatrix(transform as TransformObjectValues);
|
|
48
|
-
const { origin } = transform;
|
|
49
|
-
const params = { ...paramsParser };
|
|
50
|
-
let segment = [];
|
|
51
|
-
let seglen = 0;
|
|
52
|
-
let pathCommand = '';
|
|
53
|
-
const transformedPath = [] as PathTransform[];
|
|
54
49
|
|
|
55
|
-
if (
|
|
56
|
-
for (i = 0, ii = absolutePath.length; i < ii; i += 1) {
|
|
57
|
-
segment = normalizedPath[i];
|
|
58
|
-
seglen = segment.length;
|
|
50
|
+
if (matrixInstance.isIdentity) return path;
|
|
59
51
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
params.y2 = +segment[seglen - 3] || params.y1;
|
|
52
|
+
return iterate<AbsoluteArray>(path, (seg, _, i) => {
|
|
53
|
+
const absSegment = absolutizeSegment(seg, transformParams);
|
|
54
|
+
[pathCommand] = absSegment;
|
|
64
55
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
56
|
+
let result =
|
|
57
|
+
pathCommand === 'A'
|
|
58
|
+
? segmentToCubic(absSegment, transformParams)
|
|
59
|
+
: ['V', 'H'].includes(pathCommand)
|
|
60
|
+
? normalizeSegment(absSegment, transformParams)
|
|
61
|
+
: absSegment;
|
|
62
|
+
const isLongArc = result[0] === 'C' && result.length > 7;
|
|
63
|
+
const normalizedSegment = (isLongArc ? result.slice(0, 7) : result.slice(0)) as typeof result;
|
|
71
64
|
|
|
72
|
-
|
|
65
|
+
if (isLongArc) {
|
|
66
|
+
path.splice(i + 1, 0, ['C', ...result.slice(7)] as CSegment);
|
|
67
|
+
result = result.slice(0, 7) as CSegment;
|
|
73
68
|
}
|
|
74
69
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (pathCommand === 'L' || pathCommand === 'H' || pathCommand === 'V') {
|
|
79
|
-
[lx, ly] = projection2d(matrixInstance, [seg.x, seg.y], origin as [number, number, number]);
|
|
80
|
-
|
|
81
|
-
/* istanbul ignore else @preserve */
|
|
82
|
-
if (x !== lx && y !== ly) {
|
|
83
|
-
segment = ['L', lx, ly];
|
|
84
|
-
} else if (y === ly) {
|
|
85
|
-
segment = ['H', lx];
|
|
86
|
-
} else if (x === lx) {
|
|
87
|
-
segment = ['V', ly];
|
|
88
|
-
}
|
|
70
|
+
if (result[0] === 'L') {
|
|
71
|
+
const values = result.slice(-2) as PointTuple;
|
|
72
|
+
[lx, ly] = projection2d(matrixInstance, values, origin);
|
|
89
73
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
} else {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
74
|
+
/* istanbul ignore else @preserve */
|
|
75
|
+
if (x !== lx && y !== ly) {
|
|
76
|
+
result = ['L', lx, ly];
|
|
77
|
+
} else if (y === ly) {
|
|
78
|
+
result = ['H', lx];
|
|
79
|
+
} else if (x === lx) {
|
|
80
|
+
result = ['V', ly];
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
for (j = 1, jj = result.length; j < jj; j += 2) {
|
|
84
|
+
[lx, ly] = projection2d(matrixInstance, [+result[j], +result[j + 1]], origin);
|
|
85
|
+
result[j] = lx;
|
|
86
|
+
result[j + 1] = ly;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// now update x and y
|
|
90
|
+
x = lx;
|
|
91
|
+
y = ly;
|
|
101
92
|
|
|
102
|
-
|
|
93
|
+
if (pathCommand === 'Z') {
|
|
94
|
+
nx = mx;
|
|
95
|
+
ny = my;
|
|
96
|
+
} else {
|
|
97
|
+
[nx, ny] = normalizedSegment.slice(-2) as PointTuple;
|
|
98
|
+
if (pathCommand === 'M') {
|
|
99
|
+
mx = nx;
|
|
100
|
+
my = ny;
|
|
103
101
|
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const seglen = normalizedSegment.length;
|
|
105
|
+
transformParams.x1 = +normalizedSegment[seglen - 2];
|
|
106
|
+
transformParams.y1 = +normalizedSegment[seglen - 1];
|
|
107
|
+
transformParams.x2 = +normalizedSegment[seglen - 4] || transformParams.x1;
|
|
108
|
+
transformParams.y2 = +normalizedSegment[seglen - 3] || transformParams.y1;
|
|
109
|
+
transformParams.x = nx;
|
|
110
|
+
transformParams.y = ny;
|
|
111
|
+
return result;
|
|
112
|
+
});
|
|
107
113
|
};
|
|
114
|
+
|
|
108
115
|
export default transformPath;
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
LineAttr,
|
|
3
|
+
CircleAttr,
|
|
4
|
+
PolyAttr,
|
|
5
|
+
RectAttr,
|
|
6
|
+
EllipseAttr,
|
|
7
|
+
GlyphAttr,
|
|
8
|
+
TransformObject,
|
|
9
|
+
ParserParams,
|
|
10
|
+
} from './interface';
|
|
2
11
|
|
|
3
12
|
export type SpaceNumber =
|
|
4
13
|
| 0x1680
|
|
@@ -196,3 +205,28 @@ export type Point = {
|
|
|
196
205
|
x: number;
|
|
197
206
|
y: number;
|
|
198
207
|
};
|
|
208
|
+
|
|
209
|
+
export type PointTuple = [number, number];
|
|
210
|
+
|
|
211
|
+
export type DerivedPoint = Point & { t: number };
|
|
212
|
+
export type QuadPoints = [Point, Point, Point, Point, Point, Point];
|
|
213
|
+
export type CubicPoints = [Point, Point, Point, Point, Point, Point, Point, Point];
|
|
214
|
+
export type DerivedQuadPoints = [DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint];
|
|
215
|
+
export type DerivedCubicPoints = [
|
|
216
|
+
DerivedPoint,
|
|
217
|
+
DerivedPoint,
|
|
218
|
+
DerivedPoint,
|
|
219
|
+
DerivedPoint,
|
|
220
|
+
DerivedPoint,
|
|
221
|
+
DerivedPoint,
|
|
222
|
+
DerivedPoint,
|
|
223
|
+
DerivedPoint,
|
|
224
|
+
];
|
|
225
|
+
export type QuadCoordinates = [number, number, number, number, number, number];
|
|
226
|
+
export type CubicCoordinates = [number, number, number, number, number, number, number, number];
|
|
227
|
+
export type ArcCoordinates = [number, number, number, number, number, number, number, number, number];
|
|
228
|
+
export type LineCoordinates = [number, number, number, number];
|
|
229
|
+
|
|
230
|
+
export type DeriveCallback = (t: number) => Point;
|
|
231
|
+
|
|
232
|
+
export type IteratorCallback = (segment: PathSegment, params: ParserParams, index: number) => PathSegment;
|
package/src/util/getPathArea.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import pathToCurve from '../convert/pathToCurve';
|
|
2
|
-
import type { PathArray } from '../types';
|
|
2
|
+
import type { PointTuple, PathArray, QuadCoordinates } from '../types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Returns the area of a single cubic-bezier segment.
|
|
@@ -60,8 +60,8 @@ const getPathArea = (path: PathArray) => {
|
|
|
60
60
|
[, x, y] = seg;
|
|
61
61
|
return 0;
|
|
62
62
|
default:
|
|
63
|
-
len = getCubicSegArea(x, y, ...(seg.slice(1) as
|
|
64
|
-
[x, y] = seg.slice(-2) as
|
|
63
|
+
len = getCubicSegArea(x, y, ...(seg.slice(1) as QuadCoordinates));
|
|
64
|
+
[x, y] = seg.slice(-2) as PointTuple;
|
|
65
65
|
return len;
|
|
66
66
|
}
|
|
67
67
|
})
|
package/src/util/getPathBBox.ts
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import iterate from '../process/iterate';
|
|
2
|
+
import { PathBBox } from '../interface';
|
|
3
|
+
import {
|
|
4
|
+
ArcCoordinates,
|
|
5
|
+
CubicCoordinates,
|
|
6
|
+
LineCoordinates,
|
|
7
|
+
MSegment,
|
|
8
|
+
PathArray,
|
|
9
|
+
Point,
|
|
10
|
+
PointTuple,
|
|
11
|
+
QuadCoordinates,
|
|
12
|
+
} from '../types';
|
|
13
|
+
// import pathFactory from './pathFactory';
|
|
14
|
+
import absolutizeSegment from '../process/absolutizeSegment';
|
|
15
|
+
import normalizeSegment from '../process/normalizeSegment';
|
|
16
|
+
import parsePathString from '../parser/parsePathString';
|
|
17
|
+
import { getLineBBox } from '../math/lineTools';
|
|
18
|
+
import { getArcBBox } from '../math/arcTools';
|
|
19
|
+
import { getCubicBBox } from '../math/cubicTools';
|
|
20
|
+
import { getQuadBBox } from '../math/quadTools';
|
|
21
|
+
|
|
22
|
+
const getPathBBox = (pathInput: PathArray | string) => {
|
|
23
|
+
if (!pathInput) {
|
|
13
24
|
return {
|
|
14
25
|
x: 0,
|
|
15
26
|
y: 0,
|
|
@@ -23,12 +34,67 @@ const getPathBBox = (path: PathArray | string): PathBBox => {
|
|
|
23
34
|
};
|
|
24
35
|
}
|
|
25
36
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
const path = parsePathString(pathInput);
|
|
38
|
+
let data = [] as number[];
|
|
39
|
+
let pathCommand = 'M';
|
|
40
|
+
let x = 0;
|
|
41
|
+
let y = 0;
|
|
42
|
+
let mx = 0;
|
|
43
|
+
let my = 0;
|
|
44
|
+
const MIN = [] as Point[];
|
|
45
|
+
const MAX = [] as Point[];
|
|
46
|
+
let min = { x, y };
|
|
47
|
+
let max = { x, y };
|
|
48
|
+
|
|
49
|
+
iterate(path, (seg, params) => {
|
|
50
|
+
const absoluteSegment = absolutizeSegment(seg, params);
|
|
51
|
+
const normalSegment = normalizeSegment(absoluteSegment, params);
|
|
52
|
+
[pathCommand] = normalSegment;
|
|
53
|
+
data = [x, y, ...normalSegment.slice(1)] as number[];
|
|
54
|
+
|
|
55
|
+
// this segment is always ZERO
|
|
56
|
+
/* istanbul ignore else @preserve */
|
|
57
|
+
if (pathCommand === 'M') {
|
|
58
|
+
// remember mx, my for Z
|
|
59
|
+
[, mx, my] = normalSegment as MSegment;
|
|
60
|
+
min = { x: mx, y: my };
|
|
61
|
+
max = { x: mx, y: my };
|
|
62
|
+
} else if (pathCommand === 'L') {
|
|
63
|
+
({ min, max } = getLineBBox(...(data as LineCoordinates)));
|
|
64
|
+
} else if (pathCommand === 'A') {
|
|
65
|
+
({ min, max } = getArcBBox(...(data as ArcCoordinates)));
|
|
66
|
+
} else if (pathCommand === 'C') {
|
|
67
|
+
({ min, max } = getCubicBBox(...(data as CubicCoordinates)));
|
|
68
|
+
} else if (pathCommand === 'Q') {
|
|
69
|
+
({ min, max } = getQuadBBox(...(data as QuadCoordinates)));
|
|
70
|
+
} else if (pathCommand === 'Z') {
|
|
71
|
+
data = [x, y, mx, my];
|
|
72
|
+
({ min, max } = getLineBBox(...(data as LineCoordinates)));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
MIN.push(min);
|
|
76
|
+
MAX.push(max);
|
|
31
77
|
|
|
78
|
+
if (pathCommand === 'Z') {
|
|
79
|
+
x = mx;
|
|
80
|
+
y = my;
|
|
81
|
+
} else {
|
|
82
|
+
[x, y] = normalSegment.slice(-2) as PointTuple;
|
|
83
|
+
|
|
84
|
+
if (pathCommand === 'M') {
|
|
85
|
+
mx = x;
|
|
86
|
+
my = y;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
params.x = x;
|
|
90
|
+
params.y = y;
|
|
91
|
+
return normalSegment;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const xMin = Math.min(...MIN.map(n => n.x));
|
|
95
|
+
const xMax = Math.max(...MAX.map(n => n.x));
|
|
96
|
+
const yMin = Math.min(...MIN.map(n => n.y));
|
|
97
|
+
const yMax = Math.max(...MAX.map(n => n.y));
|
|
32
98
|
const width = xMax - xMin;
|
|
33
99
|
const height = yMax - yMin;
|
|
34
100
|
|
|
@@ -41,8 +107,9 @@ const getPathBBox = (path: PathArray | string): PathBBox => {
|
|
|
41
107
|
y2: yMax,
|
|
42
108
|
cx: xMin + width / 2,
|
|
43
109
|
cy: yMin + height / 2,
|
|
44
|
-
// an
|
|
110
|
+
// an estimated guess
|
|
45
111
|
cz: Math.max(width, height) + Math.min(width, height) / 2,
|
|
46
|
-
};
|
|
112
|
+
} satisfies PathBBox;
|
|
47
113
|
};
|
|
114
|
+
|
|
48
115
|
export default getPathBBox;
|
|
@@ -1,5 +1,21 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import DISTANCE_EPSILON from './distanceEpsilon';
|
|
2
|
+
import parsePathString from '../parser/parsePathString';
|
|
3
|
+
import type {
|
|
4
|
+
ArcCoordinates,
|
|
5
|
+
CubicCoordinates,
|
|
6
|
+
LineCoordinates,
|
|
7
|
+
MSegment,
|
|
8
|
+
PathArray,
|
|
9
|
+
PointTuple,
|
|
10
|
+
QuadCoordinates,
|
|
11
|
+
} from '../types';
|
|
12
|
+
import iterate from '../process/iterate';
|
|
13
|
+
import absolutizeSegment from '../process/absolutizeSegment';
|
|
14
|
+
import normalizeSegment from '../process/normalizeSegment';
|
|
15
|
+
import { getLineLength, getPointAtLineLength } from '../math/lineTools';
|
|
16
|
+
import { getArcLength, getPointAtArcLength } from '../math/arcTools';
|
|
17
|
+
import { getCubicLength, getPointAtCubicLength } from '../math/cubicTools';
|
|
18
|
+
import { getQuadLength, getPointAtQuadLength } from '../math/quadTools';
|
|
3
19
|
|
|
4
20
|
/**
|
|
5
21
|
* Returns [x,y] coordinates of a point at a given length of a shape.
|
|
@@ -8,7 +24,85 @@ import pathFactory from './pathFactory';
|
|
|
8
24
|
* @param distance the length of the shape to look at
|
|
9
25
|
* @returns the requested {x, y} point coordinates
|
|
10
26
|
*/
|
|
11
|
-
const getPointAtLength = (pathInput: string | PathArray, distance
|
|
12
|
-
|
|
27
|
+
const getPointAtLength = (pathInput: string | PathArray, distance?: number) => {
|
|
28
|
+
const path = parsePathString(pathInput);
|
|
29
|
+
let isM = false;
|
|
30
|
+
let data = [] as number[];
|
|
31
|
+
let pathCommand = 'M';
|
|
32
|
+
let x = 0;
|
|
33
|
+
let y = 0;
|
|
34
|
+
let [mx, my] = path[0].slice(1) as PointTuple;
|
|
35
|
+
const distanceIsNumber = typeof distance === 'number';
|
|
36
|
+
let point = { x: mx, y: my };
|
|
37
|
+
let length = 0;
|
|
38
|
+
let POINT = point;
|
|
39
|
+
let totalLength = 0;
|
|
40
|
+
|
|
41
|
+
if (!distanceIsNumber) return point;
|
|
42
|
+
|
|
43
|
+
if (distance < DISTANCE_EPSILON) {
|
|
44
|
+
POINT = point;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
iterate(path, (seg, params) => {
|
|
48
|
+
const absoluteSegment = absolutizeSegment(seg, params);
|
|
49
|
+
const normalSegment = normalizeSegment(absoluteSegment, params);
|
|
50
|
+
[pathCommand] = normalSegment;
|
|
51
|
+
isM = pathCommand === 'M';
|
|
52
|
+
data = !isM ? [x, y, ...(normalSegment.slice(1) as number[])] : data;
|
|
53
|
+
|
|
54
|
+
// this segment is always ZERO
|
|
55
|
+
/* istanbul ignore else @preserve */
|
|
56
|
+
if (isM) {
|
|
57
|
+
// remember mx, my for Z
|
|
58
|
+
[, mx, my] = seg as MSegment;
|
|
59
|
+
point = { x: mx, y: my };
|
|
60
|
+
length = 0;
|
|
61
|
+
} else if (pathCommand === 'L') {
|
|
62
|
+
point = getPointAtLineLength(...(data as LineCoordinates), distance - totalLength);
|
|
63
|
+
length = getLineLength(...(data as LineCoordinates));
|
|
64
|
+
} else if (pathCommand === 'A') {
|
|
65
|
+
point = getPointAtArcLength(...(data as ArcCoordinates), distance - totalLength);
|
|
66
|
+
length = getArcLength(...(data as ArcCoordinates));
|
|
67
|
+
} else if (pathCommand === 'C') {
|
|
68
|
+
point = getPointAtCubicLength(...(data as CubicCoordinates), distance - totalLength);
|
|
69
|
+
length = getCubicLength(...(data as CubicCoordinates));
|
|
70
|
+
} else if (pathCommand === 'Q') {
|
|
71
|
+
point = getPointAtQuadLength(...(data as QuadCoordinates), distance - totalLength);
|
|
72
|
+
length = getQuadLength(...(data as QuadCoordinates));
|
|
73
|
+
} else if (pathCommand === 'Z') {
|
|
74
|
+
data = [x, y, mx, my];
|
|
75
|
+
point = { x: mx, y: my };
|
|
76
|
+
length = getLineLength(...(data as LineCoordinates));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (totalLength < distance && totalLength + length >= distance) {
|
|
80
|
+
POINT = point;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
totalLength += length;
|
|
84
|
+
if (pathCommand === 'Z') {
|
|
85
|
+
x = mx;
|
|
86
|
+
y = my;
|
|
87
|
+
} else {
|
|
88
|
+
[x, y] = normalSegment.slice(-2) as PointTuple;
|
|
89
|
+
|
|
90
|
+
if (pathCommand === 'M') {
|
|
91
|
+
mx = x;
|
|
92
|
+
my = y;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
params.x = x;
|
|
96
|
+
params.y = y;
|
|
97
|
+
return normalSegment;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// native `getPointAtLength` behavior when the given distance
|
|
101
|
+
// is higher than total length
|
|
102
|
+
if (distance > totalLength - DISTANCE_EPSILON) {
|
|
103
|
+
POINT = { x, y };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return POINT;
|
|
13
107
|
};
|
|
14
108
|
export default getPointAtLength;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PathArray, PathSegment } from '../types';
|
|
1
|
+
import type { PointTuple, PathArray, PathSegment } from '../types';
|
|
2
2
|
import type { SegmentProperties } from '../interface';
|
|
3
3
|
import parsePathString from '../parser/parsePathString';
|
|
4
4
|
import getTotalLength from './getTotalLength';
|
|
@@ -20,7 +20,7 @@ const getPropertiesAtLength = (pathInput: string | PathArray, distance?: number)
|
|
|
20
20
|
let lengthAtSegment = 0;
|
|
21
21
|
let length = 0;
|
|
22
22
|
let segment = pathArray[0] as PathSegment;
|
|
23
|
-
const [x, y] = segment.slice(-2) as
|
|
23
|
+
const [x, y] = segment.slice(-2) as PointTuple;
|
|
24
24
|
const point = { x, y };
|
|
25
25
|
|
|
26
26
|
// If the path is empty, return 0.
|
|
@@ -1,5 +1,21 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import parsePathString from '../parser/parsePathString';
|
|
2
|
+
import type {
|
|
3
|
+
ArcCoordinates,
|
|
4
|
+
CubicCoordinates,
|
|
5
|
+
LineCoordinates,
|
|
6
|
+
MSegment,
|
|
7
|
+
PathArray,
|
|
8
|
+
PointTuple,
|
|
9
|
+
QuadCoordinates,
|
|
10
|
+
} from '../types';
|
|
11
|
+
import { getLineLength } from '../math/lineTools';
|
|
12
|
+
import { getArcLength } from '../math/arcTools';
|
|
13
|
+
import { getCubicLength } from '../math/cubicTools';
|
|
14
|
+
import { getQuadLength } from '../math/quadTools';
|
|
15
|
+
import iterate from '../process/iterate';
|
|
16
|
+
import absolutizeSegment from '../process/absolutizeSegment';
|
|
17
|
+
import normalizeSegment from '../process/normalizeSegment';
|
|
18
|
+
// import pathFactory from './pathFactory';
|
|
3
19
|
|
|
4
20
|
/**
|
|
5
21
|
* Returns the shape total length, or the equivalent to `shape.getTotalLength()`.
|
|
@@ -10,7 +26,58 @@ import pathFactory from './pathFactory';
|
|
|
10
26
|
* @param pathInput the target `pathArray`
|
|
11
27
|
* @returns the shape total length
|
|
12
28
|
*/
|
|
13
|
-
const getTotalLength = (pathInput: string | PathArray)
|
|
14
|
-
|
|
29
|
+
const getTotalLength = (pathInput: string | PathArray) => {
|
|
30
|
+
const path = parsePathString(pathInput);
|
|
31
|
+
let isM = false;
|
|
32
|
+
let data = [] as number[];
|
|
33
|
+
let pathCommand = 'M';
|
|
34
|
+
let x = 0;
|
|
35
|
+
let y = 0;
|
|
36
|
+
let mx = 0;
|
|
37
|
+
let my = 0;
|
|
38
|
+
let totalLength = 0;
|
|
39
|
+
|
|
40
|
+
iterate(path, (seg, params) => {
|
|
41
|
+
const absoluteSegment = absolutizeSegment(seg, params);
|
|
42
|
+
const normalSegment = normalizeSegment(absoluteSegment, params);
|
|
43
|
+
[pathCommand] = normalSegment;
|
|
44
|
+
isM = pathCommand === 'M';
|
|
45
|
+
data = !isM ? [x, y, ...(normalSegment.slice(1) as number[])] : data;
|
|
46
|
+
|
|
47
|
+
// this segment is always ZERO
|
|
48
|
+
/* istanbul ignore else @preserve */
|
|
49
|
+
if (isM) {
|
|
50
|
+
// remember mx, my for Z
|
|
51
|
+
[, mx, my] = seg as MSegment;
|
|
52
|
+
} else if (pathCommand === 'L') {
|
|
53
|
+
totalLength += getLineLength(...(data as LineCoordinates));
|
|
54
|
+
} else if (pathCommand === 'A') {
|
|
55
|
+
totalLength += getArcLength(...(data as ArcCoordinates));
|
|
56
|
+
} else if (pathCommand === 'C') {
|
|
57
|
+
totalLength += getCubicLength(...(data as CubicCoordinates));
|
|
58
|
+
} else if (pathCommand === 'Q') {
|
|
59
|
+
totalLength += getQuadLength(...(data as QuadCoordinates));
|
|
60
|
+
} else if (pathCommand === 'Z') {
|
|
61
|
+
data = [x, y, mx, my];
|
|
62
|
+
totalLength += getLineLength(...(data as LineCoordinates));
|
|
63
|
+
}
|
|
64
|
+
if (pathCommand === 'Z') {
|
|
65
|
+
x = mx;
|
|
66
|
+
y = my;
|
|
67
|
+
} else {
|
|
68
|
+
[x, y] = normalSegment.slice(-2) as PointTuple;
|
|
69
|
+
|
|
70
|
+
if (isM) {
|
|
71
|
+
mx = x;
|
|
72
|
+
my = y;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
params.x = x;
|
|
76
|
+
params.y = y;
|
|
77
|
+
return normalSegment;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return totalLength;
|
|
15
81
|
};
|
|
82
|
+
|
|
16
83
|
export default getTotalLength;
|