svg-path-commander 2.1.0 → 2.1.2
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 +63 -7
- package/dist/svg-path-commander.cjs +1 -1
- package/dist/svg-path-commander.cjs.map +1 -1
- package/dist/svg-path-commander.d.ts +314 -40
- package/dist/svg-path-commander.js +1 -1
- package/dist/svg-path-commander.js.map +1 -1
- package/dist/svg-path-commander.mjs +1085 -1032
- package/dist/svg-path-commander.mjs.map +1 -1
- package/package.json +8 -8
- package/src/convert/pathToAbsolute.ts +5 -88
- package/src/convert/pathToCurve.ts +22 -28
- package/src/convert/pathToRelative.ts +4 -78
- package/src/convert/pathToString.ts +40 -7
- package/src/index.ts +145 -58
- package/src/interface.ts +3 -2
- package/src/math/arcTools.ts +259 -80
- package/src/math/bezier.ts +58 -58
- package/src/math/cubicTools.ts +68 -25
- package/src/math/distanceSquareRoot.ts +3 -1
- package/src/math/lineTools.ts +42 -25
- package/src/math/midPoint.ts +3 -1
- package/src/math/polygonTools.ts +48 -0
- package/src/math/quadTools.ts +46 -25
- package/src/math/rotateVector.ts +3 -2
- package/src/math/roundTo.ts +7 -0
- package/src/parser/finalizeSegment.ts +11 -7
- package/src/parser/parsePathString.ts +4 -5
- package/src/parser/pathParser.ts +1 -1
- package/src/process/absolutizeSegment.ts +63 -0
- package/src/process/arcToCubic.ts +2 -2
- package/src/process/iterate.ts +58 -0
- package/src/process/lineToCubic.ts +1 -1
- package/src/process/normalizePath.ts +17 -28
- package/src/process/normalizeSegment.ts +55 -18
- package/src/process/optimizePath.ts +40 -60
- package/src/process/projection2d.ts +4 -3
- package/src/process/relativizeSegment.ts +59 -0
- package/src/process/reverseCurve.ts +8 -5
- package/src/process/reversePath.ts +88 -75
- package/src/process/roundPath.ts +18 -14
- package/src/process/roundSegment.ts +9 -0
- package/src/process/segmentToCubic.ts +10 -8
- package/src/process/shortenSegment.ts +26 -34
- package/src/process/splitCubic.ts +10 -9
- package/src/process/splitPath.ts +38 -4
- package/src/process/transformPath.ts +75 -73
- package/src/types.ts +30 -0
- package/src/util/getPathArea.ts +3 -3
- package/src/util/getPathBBox.ts +69 -19
- package/src/util/getPointAtLength.ts +100 -4
- package/src/util/getPropertiesAtLength.ts +3 -4
- package/src/util/getPropertiesAtPoint.ts +5 -5
- package/src/util/getTotalLength.ts +56 -4
- package/test/class.test.ts +17 -14
- package/test/fixtures/shapes.js +26 -26
- package/test/fixtures/simpleShapes.js +18 -18
- package/test/static.test.ts +54 -28
- package/cypress.config.ts +0 -29
- package/src/math/polygonArea.ts +0 -27
- package/src/math/polygonLength.ts +0 -21
- package/src/process/fixArc.ts +0 -23
- package/src/process/replaceArc.ts +0 -52
- package/src/util/pathFactory.ts +0 -130
package/src/math/cubicTools.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getBezierLength, minmaxC } from './bezier';
|
|
2
|
+
import { type CubicCoordinates } from '../types';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
* Returns a
|
|
5
|
+
* Returns a point at a given length of a CubicBezier segment.
|
|
5
6
|
*
|
|
6
7
|
* @param x1 the starting point X
|
|
7
8
|
* @param y1 the starting point Y
|
|
@@ -23,7 +24,7 @@ const getPointAtCubicSegmentLength = ([x1, y1, c1x, c1y, c2x, c2y, x2, y2]: Cubi
|
|
|
23
24
|
};
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
|
-
* Returns the
|
|
27
|
+
* Returns the length of a CubicBezier segment.
|
|
27
28
|
*
|
|
28
29
|
* @param x1 the starting point X
|
|
29
30
|
* @param y1 the starting point Y
|
|
@@ -33,10 +34,36 @@ const getPointAtCubicSegmentLength = ([x1, y1, c1x, c1y, c2x, c2y, x2, y2]: Cubi
|
|
|
33
34
|
* @param c2y the second control point Y
|
|
34
35
|
* @param x2 the ending point X
|
|
35
36
|
* @param y2 the ending point Y
|
|
36
|
-
* @
|
|
37
|
-
* @returns the segment length, point at length and the bounding box
|
|
37
|
+
* @returns the CubicBezier segment length
|
|
38
38
|
*/
|
|
39
|
-
const
|
|
39
|
+
const getCubicLength = (
|
|
40
|
+
x1: number,
|
|
41
|
+
y1: number,
|
|
42
|
+
c1x: number,
|
|
43
|
+
c1y: number,
|
|
44
|
+
c2x: number,
|
|
45
|
+
c2y: number,
|
|
46
|
+
x2: number,
|
|
47
|
+
y2: number,
|
|
48
|
+
) => {
|
|
49
|
+
return getBezierLength([x1, y1, c1x, c1y, c2x, c2y, x2, y2]);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Returns the point along a CubicBezier segment at a given distance.
|
|
54
|
+
*
|
|
55
|
+
* @param x1 the starting point X
|
|
56
|
+
* @param y1 the starting point Y
|
|
57
|
+
* @param c1x the first control point X
|
|
58
|
+
* @param c1y the first control point Y
|
|
59
|
+
* @param c2x the second control point X
|
|
60
|
+
* @param c2y the second control point Y
|
|
61
|
+
* @param x2 the ending point X
|
|
62
|
+
* @param y2 the ending point Y
|
|
63
|
+
* @param distance the distance to look at
|
|
64
|
+
* @returns the point at CubicBezier length
|
|
65
|
+
*/
|
|
66
|
+
const getPointAtCubicLength = (
|
|
40
67
|
x1: number,
|
|
41
68
|
y1: number,
|
|
42
69
|
c1x: number,
|
|
@@ -48,34 +75,50 @@ const getSegmentProperties = (
|
|
|
48
75
|
distance?: number,
|
|
49
76
|
) => {
|
|
50
77
|
const distanceIsNumber = typeof distance === 'number';
|
|
51
|
-
let
|
|
52
|
-
|
|
53
|
-
|
|
78
|
+
let point = { x: x1, y: y1 };
|
|
79
|
+
/* istanbul ignore else @preserve */
|
|
54
80
|
if (distanceIsNumber) {
|
|
55
|
-
const currentLength =
|
|
81
|
+
const currentLength = getBezierLength([x1, y1, c1x, c1y, c2x, c2y, x2, y2]);
|
|
56
82
|
if (distance <= 0) {
|
|
57
83
|
// first point already defined
|
|
58
84
|
} else if (distance >= currentLength) {
|
|
59
|
-
|
|
85
|
+
point = { x: x2, y: y2 };
|
|
60
86
|
} else {
|
|
61
|
-
|
|
87
|
+
point = getPointAtCubicSegmentLength([x1, y1, c1x, c1y, c2x, c2y, x2, y2], distance / currentLength);
|
|
62
88
|
}
|
|
63
89
|
}
|
|
90
|
+
return point;
|
|
91
|
+
};
|
|
64
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Returns the boundig box of a CubicBezier segment.
|
|
95
|
+
*
|
|
96
|
+
* @param x1 the starting point X
|
|
97
|
+
* @param y1 the starting point Y
|
|
98
|
+
* @param c1x the first control point X
|
|
99
|
+
* @param c1y the first control point Y
|
|
100
|
+
* @param c2x the second control point X
|
|
101
|
+
* @param c2y the second control point Y
|
|
102
|
+
* @param x2 the ending point X
|
|
103
|
+
* @param y2 the ending point Y
|
|
104
|
+
* @returns the point at CubicBezier length
|
|
105
|
+
*/
|
|
106
|
+
const getCubicBBox = (
|
|
107
|
+
x1: number,
|
|
108
|
+
y1: number,
|
|
109
|
+
c1x: number,
|
|
110
|
+
c1y: number,
|
|
111
|
+
c2x: number,
|
|
112
|
+
c2y: number,
|
|
113
|
+
x2: number,
|
|
114
|
+
y2: number,
|
|
115
|
+
) => {
|
|
116
|
+
const cxMinMax = minmaxC([x1, c1x, c2x, x2]);
|
|
117
|
+
const cyMinMax = minmaxC([y1, c1y, c2y, y2]);
|
|
65
118
|
return {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return getLength();
|
|
69
|
-
},
|
|
70
|
-
get bbox() {
|
|
71
|
-
const cxMinMax = minmaxC([x1, c1x, c2x, x2]);
|
|
72
|
-
const cyMinMax = minmaxC([y1, c1y, c2y, y2]);
|
|
73
|
-
return {
|
|
74
|
-
min: { x: cxMinMax[0], y: cyMinMax[0] },
|
|
75
|
-
max: { x: cxMinMax[1], y: cyMinMax[1] },
|
|
76
|
-
};
|
|
77
|
-
},
|
|
119
|
+
min: { x: cxMinMax[0], y: cyMinMax[0] },
|
|
120
|
+
max: { x: cxMinMax[1], y: cyMinMax[1] },
|
|
78
121
|
};
|
|
79
122
|
};
|
|
80
123
|
|
|
81
|
-
export
|
|
124
|
+
export { getCubicLength, getCubicBBox, getPointAtCubicLength, getPointAtCubicSegmentLength };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type PointTuple } from '../types';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Returns the square root of the distance
|
|
3
5
|
* between two given points.
|
|
@@ -6,7 +8,7 @@
|
|
|
6
8
|
* @param b the second point coordinates
|
|
7
9
|
* @returns the distance value
|
|
8
10
|
*/
|
|
9
|
-
const distanceSquareRoot = (a:
|
|
11
|
+
const distanceSquareRoot = (a: PointTuple, b: PointTuple) => {
|
|
10
12
|
return Math.sqrt((a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1]));
|
|
11
13
|
};
|
|
12
14
|
|
package/src/math/lineTools.ts
CHANGED
|
@@ -2,51 +2,68 @@ import midPoint from './midPoint';
|
|
|
2
2
|
import distanceSquareRoot from './distanceSquareRoot';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Returns
|
|
5
|
+
* Returns length for line segments (MoveTo, LineTo).
|
|
6
6
|
*
|
|
7
7
|
* @param x1 the starting point X
|
|
8
8
|
* @param y1 the starting point Y
|
|
9
9
|
* @param x2 the ending point X
|
|
10
10
|
* @param y2 the ending point Y
|
|
11
|
-
* @
|
|
12
|
-
* @returns the segment length, point at length and the bounding box
|
|
11
|
+
* @returns the line segment length
|
|
13
12
|
*/
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
const getLineLength = (x1: number, y1: number, x2: number, y2: number) => {
|
|
14
|
+
return distanceSquareRoot([x1, y1], [x2, y2]);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Returns a point along the line segments (MoveTo, LineTo).
|
|
19
|
+
*
|
|
20
|
+
* @param x1 the starting point X
|
|
21
|
+
* @param y1 the starting point Y
|
|
22
|
+
* @param x2 the ending point X
|
|
23
|
+
* @param y2 the ending point Y
|
|
24
|
+
* @param distance the distance to point in [0-1] range
|
|
25
|
+
* @returns the point at length
|
|
26
|
+
*/
|
|
27
|
+
const getPointAtLineLength = (x1: number, y1: number, x2: number, y2: number, distance?: number) => {
|
|
28
|
+
let point = { x: x1, y: y1 };
|
|
18
29
|
|
|
19
30
|
/* istanbul ignore else @preserve */
|
|
20
31
|
if (typeof distance === 'number') {
|
|
21
|
-
const
|
|
32
|
+
const length = distanceSquareRoot([x1, y1], [x2, y2]);
|
|
22
33
|
if (distance <= 0) {
|
|
23
34
|
point = { x: x1, y: y1 };
|
|
24
|
-
} else if (distance >=
|
|
35
|
+
} else if (distance >= length) {
|
|
25
36
|
point = { x: x2, y: y2 };
|
|
26
37
|
} else {
|
|
27
|
-
const [x, y] = midPoint([x1, y1], [x2, y2], distance /
|
|
38
|
+
const [x, y] = midPoint([x1, y1], [x2, y2], distance / length);
|
|
28
39
|
point = { x, y };
|
|
29
40
|
}
|
|
30
41
|
}
|
|
42
|
+
return point;
|
|
43
|
+
};
|
|
31
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Returns bounding box for line segments (MoveTo, LineTo).
|
|
47
|
+
*
|
|
48
|
+
* @param x1 the starting point X
|
|
49
|
+
* @param y1 the starting point Y
|
|
50
|
+
* @param x2 the ending point X
|
|
51
|
+
* @param y2 the ending point Y
|
|
52
|
+
* @param distance the distance to point in [0-1] range
|
|
53
|
+
* @returns the extrema for line segments
|
|
54
|
+
*/
|
|
55
|
+
const getLineBBox = (x1: number, y1: number, x2: number, y2: number) => {
|
|
56
|
+
const { min, max } = Math;
|
|
32
57
|
return {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
58
|
+
min: {
|
|
59
|
+
x: min(x1, x2),
|
|
60
|
+
y: min(y1, y2),
|
|
36
61
|
},
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
x: min(x1, x2),
|
|
41
|
-
y: min(y1, y2),
|
|
42
|
-
},
|
|
43
|
-
max: {
|
|
44
|
-
x: max(x1, x2),
|
|
45
|
-
y: max(y1, y2),
|
|
46
|
-
},
|
|
47
|
-
};
|
|
62
|
+
max: {
|
|
63
|
+
x: max(x1, x2),
|
|
64
|
+
y: max(y1, y2),
|
|
48
65
|
},
|
|
49
66
|
};
|
|
50
67
|
};
|
|
51
68
|
|
|
52
|
-
export
|
|
69
|
+
export { getPointAtLineLength, getLineBBox, getLineLength };
|
package/src/math/midPoint.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { PointTuple } from '../types';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Returns the coordinates of a specified distance
|
|
3
5
|
* ratio between two points.
|
|
@@ -7,7 +9,7 @@
|
|
|
7
9
|
* @param t the ratio
|
|
8
10
|
* @returns the midpoint coordinates
|
|
9
11
|
*/
|
|
10
|
-
const midPoint = (a:
|
|
12
|
+
const midPoint = (a: PointTuple, b: PointTuple, t: number): PointTuple => {
|
|
11
13
|
const [ax, ay] = a;
|
|
12
14
|
const [bx, by] = b;
|
|
13
15
|
return [ax + (bx - ax) * t, ay + (by - ay) * t];
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import distanceSquareRoot from './distanceSquareRoot';
|
|
2
|
+
import { type PointTuple } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* d3-polygon-area
|
|
6
|
+
* https://github.com/d3/d3-polygon
|
|
7
|
+
*
|
|
8
|
+
* Returns the area of a polygon.
|
|
9
|
+
*
|
|
10
|
+
* @param polygon an array of coordinates
|
|
11
|
+
* @returns the polygon area
|
|
12
|
+
*/
|
|
13
|
+
const polygonArea = (polygon: PointTuple[]) => {
|
|
14
|
+
const n = polygon.length;
|
|
15
|
+
let i = -1;
|
|
16
|
+
let a: PointTuple;
|
|
17
|
+
let b = polygon[n - 1];
|
|
18
|
+
let area = 0;
|
|
19
|
+
|
|
20
|
+
/* eslint-disable-next-line */
|
|
21
|
+
while (++i < n) {
|
|
22
|
+
a = b;
|
|
23
|
+
b = polygon[i];
|
|
24
|
+
area += a[1] * b[0] - a[0] * b[1];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return area / 2;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* d3-polygon-length
|
|
32
|
+
* https://github.com/d3/d3-polygon
|
|
33
|
+
*
|
|
34
|
+
* Returns the perimeter of a polygon.
|
|
35
|
+
*
|
|
36
|
+
* @param polygon an array of coordinates
|
|
37
|
+
* @returns the polygon length
|
|
38
|
+
*/
|
|
39
|
+
const polygonLength = (polygon: PointTuple[]) => {
|
|
40
|
+
return polygon.reduce((length, point, i) => {
|
|
41
|
+
if (i) {
|
|
42
|
+
return length + distanceSquareRoot(polygon[i - 1], point);
|
|
43
|
+
}
|
|
44
|
+
return 0;
|
|
45
|
+
}, 0);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export { polygonArea, polygonLength };
|
package/src/math/quadTools.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getBezierLength, minmaxQ } from './bezier';
|
|
2
|
+
import { type QuadCoordinates } from '../types';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Returns the {x,y} coordinates of a point at a
|
|
@@ -24,7 +25,7 @@ const getPointAtQuadSegmentLength = ([x1, y1, cx, cy, x2, y2]: QuadCoordinates,
|
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
|
-
* Returns
|
|
28
|
+
* Returns the length of a QuadraticBezier segment.
|
|
28
29
|
*
|
|
29
30
|
* @param x1 the starting point X
|
|
30
31
|
* @param y1 the starting point Y
|
|
@@ -32,48 +33,68 @@ const getPointAtQuadSegmentLength = ([x1, y1, cx, cy, x2, y2]: QuadCoordinates,
|
|
|
32
33
|
* @param cy the control point Y
|
|
33
34
|
* @param x2 the ending point X
|
|
34
35
|
* @param y2 the ending point Y
|
|
35
|
-
* @
|
|
36
|
-
* @returns the segment length, point at length and the bounding box
|
|
36
|
+
* @returns the QuadraticBezier segment length
|
|
37
37
|
*/
|
|
38
|
-
const
|
|
38
|
+
const getQuadLength = (x1: number, y1: number, cx: number, cy: number, x2: number, y2: number) => {
|
|
39
|
+
return getBezierLength([x1, y1, cx, cy, x2, y2]);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Returns the point along a QuadraticBezier segment at a given distance.
|
|
44
|
+
*
|
|
45
|
+
* @param x1 the starting point X
|
|
46
|
+
* @param y1 the starting point Y
|
|
47
|
+
* @param cx the control point X
|
|
48
|
+
* @param cy the control point Y
|
|
49
|
+
* @param x2 the ending point X
|
|
50
|
+
* @param y2 the ending point Y
|
|
51
|
+
* @param distance the distance to look at
|
|
52
|
+
* @returns the point at QuadraticBezier length
|
|
53
|
+
*/
|
|
54
|
+
const getPointAtQuadLength = (
|
|
39
55
|
x1: number,
|
|
40
56
|
y1: number,
|
|
41
57
|
cx: number,
|
|
42
58
|
cy: number,
|
|
43
59
|
x2: number,
|
|
44
60
|
y2: number,
|
|
45
|
-
distance
|
|
61
|
+
distance?: number,
|
|
46
62
|
) => {
|
|
47
63
|
const distanceIsNumber = typeof distance === 'number';
|
|
48
|
-
let
|
|
49
|
-
const getLength = () => length([x1, y1, cx, cy, x2, y2]);
|
|
64
|
+
let point = { x: x1, y: y1 };
|
|
50
65
|
|
|
66
|
+
/* istanbul ignore else @preserve */
|
|
51
67
|
if (distanceIsNumber) {
|
|
52
|
-
const currentLength =
|
|
53
|
-
|
|
68
|
+
const currentLength = getBezierLength([x1, y1, cx, cy, x2, y2]);
|
|
54
69
|
if (distance <= 0) {
|
|
55
70
|
// first point already defined
|
|
56
71
|
} else if (distance >= currentLength) {
|
|
57
|
-
|
|
72
|
+
point = { x: x2, y: y2 };
|
|
58
73
|
} else {
|
|
59
|
-
|
|
74
|
+
point = getPointAtQuadSegmentLength([x1, y1, cx, cy, x2, y2], distance / currentLength);
|
|
60
75
|
}
|
|
61
76
|
}
|
|
77
|
+
return point;
|
|
78
|
+
};
|
|
62
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Returns the boundig box of a QuadraticBezier segment.
|
|
82
|
+
*
|
|
83
|
+
* @param x1 the starting point X
|
|
84
|
+
* @param y1 the starting point Y
|
|
85
|
+
* @param cx the control point X
|
|
86
|
+
* @param cy the control point Y
|
|
87
|
+
* @param x2 the ending point X
|
|
88
|
+
* @param y2 the ending point Y
|
|
89
|
+
* @returns the point at CubicBezier length
|
|
90
|
+
*/
|
|
91
|
+
const getQuadBBox = (x1: number, y1: number, cx: number, cy: number, x2: number, y2: number) => {
|
|
92
|
+
const cxMinMax = minmaxQ([x1, cx, x2]);
|
|
93
|
+
const cyMinMax = minmaxQ([y1, cy, y2]);
|
|
63
94
|
return {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return getLength();
|
|
67
|
-
},
|
|
68
|
-
get bbox() {
|
|
69
|
-
const cxMinMax = minmaxQ([x1, cx, x2]);
|
|
70
|
-
const cyMinMax = minmaxQ([y1, cy, y2]);
|
|
71
|
-
return {
|
|
72
|
-
min: { x: cxMinMax[0], y: cyMinMax[0] },
|
|
73
|
-
max: { x: cxMinMax[1], y: cyMinMax[1] },
|
|
74
|
-
};
|
|
75
|
-
},
|
|
95
|
+
min: { x: cxMinMax[0], y: cyMinMax[0] },
|
|
96
|
+
max: { x: cxMinMax[1], y: cyMinMax[1] },
|
|
76
97
|
};
|
|
77
98
|
};
|
|
78
99
|
|
|
79
|
-
export
|
|
100
|
+
export { getPointAtQuadSegmentLength, getQuadLength, getQuadBBox, getPointAtQuadLength };
|
package/src/math/rotateVector.ts
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* @returns the rotated vector
|
|
9
9
|
*/
|
|
10
10
|
const rotateVector = (x: number, y: number, rad: number): { x: number; y: number } => {
|
|
11
|
-
const
|
|
12
|
-
const
|
|
11
|
+
const { sin, cos } = Math;
|
|
12
|
+
const X = x * cos(rad) - y * sin(rad);
|
|
13
|
+
const Y = x * sin(rad) + y * cos(rad);
|
|
13
14
|
return { x: X, y: Y };
|
|
14
15
|
};
|
|
15
16
|
|
|
@@ -9,21 +9,25 @@ import type { PathCommand, PathSegment, RelativeCommand } from '../types';
|
|
|
9
9
|
*/
|
|
10
10
|
const finalizeSegment = (path: PathParser) => {
|
|
11
11
|
let pathCommand = path.pathValue[path.segmentStart] as PathCommand;
|
|
12
|
-
let
|
|
12
|
+
let relativeCommand = pathCommand.toLowerCase() as RelativeCommand;
|
|
13
13
|
const { data } = path;
|
|
14
14
|
|
|
15
|
-
while (data.length >= paramsCount[
|
|
15
|
+
while (data.length >= paramsCount[relativeCommand]) {
|
|
16
16
|
// overloaded `moveTo`
|
|
17
17
|
// https://github.com/rveciana/svg-path-properties/blob/master/src/parse.ts
|
|
18
|
-
if (
|
|
19
|
-
path.segments.push([pathCommand
|
|
20
|
-
|
|
18
|
+
if (relativeCommand === 'm' && data.length > 2) {
|
|
19
|
+
path.segments.push([pathCommand as PathCommand | number].concat(data.splice(0, 2) as number[]) as PathSegment);
|
|
20
|
+
relativeCommand = 'l';
|
|
21
21
|
pathCommand = pathCommand === 'm' ? 'l' : 'L';
|
|
22
22
|
} else {
|
|
23
|
-
path.segments.push(
|
|
23
|
+
path.segments.push(
|
|
24
|
+
[pathCommand as PathCommand | number].concat(
|
|
25
|
+
data.splice(0, paramsCount[relativeCommand]) as number[],
|
|
26
|
+
) as PathSegment,
|
|
27
|
+
);
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
if (!paramsCount[
|
|
30
|
+
if (!paramsCount[relativeCommand]) {
|
|
27
31
|
break;
|
|
28
32
|
}
|
|
29
33
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import scanSegment from './scanSegment';
|
|
2
2
|
import skipSpaces from './skipSpaces';
|
|
3
3
|
import PathParser from './pathParser';
|
|
4
|
-
import isPathArray from '../util/isPathArray';
|
|
5
4
|
import type { PathArray } from '../types';
|
|
6
5
|
|
|
7
6
|
/**
|
|
@@ -11,9 +10,9 @@ import type { PathArray } from '../types';
|
|
|
11
10
|
* @param pathInput the string to be parsed
|
|
12
11
|
* @returns the resulted `pathArray` or error string
|
|
13
12
|
*/
|
|
14
|
-
const parsePathString = (pathInput: string |
|
|
15
|
-
if (
|
|
16
|
-
return pathInput.slice(0) as
|
|
13
|
+
const parsePathString = <T extends PathArray>(pathInput: string | T) => {
|
|
14
|
+
if (typeof pathInput !== 'string') {
|
|
15
|
+
return pathInput.slice(0) as typeof pathInput;
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
const path = new PathParser(pathInput);
|
|
@@ -24,7 +23,7 @@ const parsePathString = (pathInput: string | PathArray): PathArray => {
|
|
|
24
23
|
scanSegment(path);
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
if (path
|
|
26
|
+
if (path?.err.length) {
|
|
28
27
|
throw TypeError(path.err);
|
|
29
28
|
}
|
|
30
29
|
|
package/src/parser/pathParser.ts
CHANGED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AbsoluteSegment,
|
|
3
|
+
AbsoluteCommand,
|
|
4
|
+
ASegment,
|
|
5
|
+
VSegment,
|
|
6
|
+
HSegment,
|
|
7
|
+
QSegment,
|
|
8
|
+
SSegment,
|
|
9
|
+
TSegment,
|
|
10
|
+
CSegment,
|
|
11
|
+
PathSegment,
|
|
12
|
+
MSegment,
|
|
13
|
+
LSegment,
|
|
14
|
+
} from '../types';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns an absolute segment of a `PathArray` object.
|
|
18
|
+
*
|
|
19
|
+
* @param segment the segment object
|
|
20
|
+
* @param index the segment index
|
|
21
|
+
* @param lastX the last known X value
|
|
22
|
+
* @param lastY the last known Y value
|
|
23
|
+
* @returns the absolute segment
|
|
24
|
+
*/
|
|
25
|
+
const absolutizeSegment = (segment: PathSegment, index: number, lastX: number, lastY: number) => {
|
|
26
|
+
const [pathCommand] = segment;
|
|
27
|
+
const absCommand = pathCommand.toUpperCase() as AbsoluteCommand;
|
|
28
|
+
const isAbsolute = absCommand === pathCommand;
|
|
29
|
+
|
|
30
|
+
/* istanbul ignore else @preserve */
|
|
31
|
+
if (index === 0 || isAbsolute) return segment as MSegment | AbsoluteSegment;
|
|
32
|
+
// const values = segment.slice(1) as number[];
|
|
33
|
+
if (absCommand === 'A') {
|
|
34
|
+
return [
|
|
35
|
+
absCommand,
|
|
36
|
+
segment[1],
|
|
37
|
+
segment[2],
|
|
38
|
+
segment[3],
|
|
39
|
+
segment[4],
|
|
40
|
+
segment[5],
|
|
41
|
+
(segment as ASegment)[6] + lastX,
|
|
42
|
+
(segment as ASegment)[7] + lastY,
|
|
43
|
+
] as ASegment;
|
|
44
|
+
} else if (absCommand === 'V') {
|
|
45
|
+
return [absCommand, (segment as VSegment)[1] + lastY] as VSegment;
|
|
46
|
+
} else if (absCommand === 'H') {
|
|
47
|
+
return [absCommand, (segment as HSegment)[1] + lastX] as HSegment;
|
|
48
|
+
} else if (absCommand === 'L') {
|
|
49
|
+
return [absCommand, (segment as LSegment)[1] + lastX, (segment as LSegment)[2] + lastY] as LSegment;
|
|
50
|
+
} else {
|
|
51
|
+
// use brakets for `eslint: no-case-declaration`
|
|
52
|
+
// https://stackoverflow.com/a/50753272/803358
|
|
53
|
+
const absValues = (segment.slice(1) as number[]).map((n, j) => n + (j % 2 ? lastY : lastX));
|
|
54
|
+
// for c, s, q, t
|
|
55
|
+
return [absCommand as typeof absCommand | number].concat(absValues) as
|
|
56
|
+
| MSegment
|
|
57
|
+
| QSegment
|
|
58
|
+
| TSegment
|
|
59
|
+
| SSegment
|
|
60
|
+
| CSegment;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
export default absolutizeSegment;
|
|
@@ -116,9 +116,9 @@ const arcToCubic = (
|
|
|
116
116
|
m2[0] = 2 * m1[0] - m2[0];
|
|
117
117
|
m2[1] = 2 * m1[1] - m2[1];
|
|
118
118
|
if (recursive) {
|
|
119
|
-
return [
|
|
119
|
+
return [m2[0], m2[1], m3[0], m3[1], m4[0], m4[1]].concat(res);
|
|
120
120
|
}
|
|
121
|
-
res = [
|
|
121
|
+
res = [m2[0], m2[1], m3[0], m3[1], m4[0], m4[1]].concat(res);
|
|
122
122
|
const newres = [];
|
|
123
123
|
for (let i = 0, ii = res.length; i < ii; i += 1) {
|
|
124
124
|
newres[i] = i % 2 ? rotateVector(res[i - 1], res[i], rad).y : rotateVector(res[i], res[i + 1], rad).x;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// import paramsParser from '../parser/paramsParser';
|
|
2
|
+
import type { PathArray, PathCommand, PathSegment, IteratorCallback, AbsoluteCommand } from '../types';
|
|
3
|
+
|
|
4
|
+
const iterate = <T extends PathArray>(path: PathArray, iterator: IteratorCallback) => {
|
|
5
|
+
let pathLen = path.length;
|
|
6
|
+
let segment: PathSegment;
|
|
7
|
+
let pathCommand = 'M' as PathCommand;
|
|
8
|
+
let absCommand = 'M' as AbsoluteCommand;
|
|
9
|
+
let isRelative = false;
|
|
10
|
+
let x = 0;
|
|
11
|
+
let y = 0;
|
|
12
|
+
let mx = 0;
|
|
13
|
+
let my = 0;
|
|
14
|
+
let segLen = 0;
|
|
15
|
+
|
|
16
|
+
for (let i = 0; i < pathLen; i += 1) {
|
|
17
|
+
segment = path[i];
|
|
18
|
+
[pathCommand] = segment;
|
|
19
|
+
segLen = segment.length;
|
|
20
|
+
absCommand = pathCommand.toUpperCase() as AbsoluteCommand;
|
|
21
|
+
isRelative = absCommand !== pathCommand;
|
|
22
|
+
|
|
23
|
+
const iteratorResult = iterator(segment, i, x, y);
|
|
24
|
+
// some methods like getPointAtLength would like to break
|
|
25
|
+
// when task is complete
|
|
26
|
+
if (iteratorResult === false) {
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// segment = path[i];
|
|
31
|
+
if (absCommand === 'Z') {
|
|
32
|
+
x = mx;
|
|
33
|
+
y = my;
|
|
34
|
+
} else if (absCommand === 'H') {
|
|
35
|
+
x = (segment[1] as number) + (isRelative ? x : 0);
|
|
36
|
+
} else if (absCommand === 'V') {
|
|
37
|
+
y = (segment[1] as number) + (isRelative ? y : 0);
|
|
38
|
+
} else {
|
|
39
|
+
x = (segment[segLen - 2] as number) + (isRelative ? x : 0);
|
|
40
|
+
y = (segment[segLen - 1] as number) + (isRelative ? y : 0);
|
|
41
|
+
|
|
42
|
+
if (absCommand === 'M') {
|
|
43
|
+
mx = x;
|
|
44
|
+
my = y;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (iteratorResult) {
|
|
49
|
+
path[i] = iteratorResult;
|
|
50
|
+
if (iteratorResult[0] === 'C') {
|
|
51
|
+
pathLen = path.length;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return path as T;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export default iterate;
|
|
@@ -12,6 +12,6 @@ import midPoint from '../math/midPoint';
|
|
|
12
12
|
const lineToCubic = (x1: number, y1: number, x2: number, y2: number) => {
|
|
13
13
|
const c1 = midPoint([x1, y1], [x2, y2], 1.0 / 3.0);
|
|
14
14
|
const c2 = midPoint([x1, y1], [x2, y2], 2.0 / 3.0);
|
|
15
|
-
return [
|
|
15
|
+
return [c1[0], c1[1], c2[0], c2[1], x2, y2];
|
|
16
16
|
};
|
|
17
17
|
export default lineToCubic;
|