svg-path-commander 1.0.5 → 2.0.0-alpha2
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 +26 -4
- package/dist/svg-path-commander.cjs +2 -0
- package/dist/svg-path-commander.cjs.map +1 -0
- package/dist/svg-path-commander.d.ts +454 -0
- package/dist/svg-path-commander.js +2 -3762
- package/dist/svg-path-commander.js.map +1 -0
- package/dist/svg-path-commander.mjs +1194 -0
- package/dist/svg-path-commander.mjs.map +1 -0
- package/package.json +39 -42
- package/src/convert/pathToAbsolute.ts +101 -0
- package/src/convert/{pathToCurve.js → pathToCurve.ts} +12 -14
- package/src/convert/pathToRelative.ts +92 -0
- package/src/convert/pathToString.ts +17 -0
- package/src/{svg-path-commander.js → index.ts} +143 -66
- package/src/interface.ts +129 -0
- package/src/math/distanceSquareRoot.ts +13 -0
- package/src/math/midPoint.ts +16 -0
- package/src/math/{polygonArea.js → polygonArea.ts} +6 -4
- package/src/math/{polygonLength.js → polygonLength.ts} +5 -3
- package/src/math/rotateVector.ts +16 -0
- package/src/options/options.ts +9 -0
- package/src/parser/error.ts +2 -0
- package/src/parser/finalizeSegment.ts +31 -0
- package/src/parser/{invalidPathValue.js → invalidPathValue.ts} +0 -0
- package/src/parser/{isArcCommand.js → isArcCommand.ts} +5 -4
- package/src/parser/isDigit.ts +12 -0
- package/src/parser/isDigitStart.ts +14 -0
- package/src/parser/isPathCommand.ts +28 -0
- package/src/parser/isSpace.ts +23 -0
- package/src/parser/paramsCount.ts +16 -0
- package/src/parser/paramsParser.ts +14 -0
- package/src/parser/{parsePathString.js → parsePathString.ts} +13 -7
- package/src/parser/pathParser.ts +29 -0
- package/src/parser/{scanFlag.js → scanFlag.ts} +8 -5
- package/src/parser/{scanParam.js → scanParam.ts} +12 -11
- package/src/parser/{scanSegment.js → scanSegment.ts} +9 -5
- package/src/parser/{skipSpaces.js → skipSpaces.ts} +5 -3
- package/src/process/{arcToCubic.js → arcToCubic.ts} +42 -28
- package/src/process/fixArc.ts +23 -0
- package/src/process/getSVGMatrix.ts +70 -0
- package/src/process/lineToCubic.ts +17 -0
- package/src/process/{normalizePath.js → normalizePath.ts} +10 -10
- package/src/process/normalizeSegment.ts +47 -0
- package/src/process/{optimizePath.js → optimizePath.ts} +18 -21
- package/src/process/{projection2d.js → projection2d.ts} +16 -15
- package/src/process/quadToCubic.ts +31 -0
- package/src/process/reverseCurve.ts +21 -0
- package/src/process/reversePath.ts +101 -0
- package/src/process/roundPath.ts +29 -0
- package/src/process/segmentToCubic.ts +46 -0
- package/src/process/shortenSegment.ts +79 -0
- package/src/process/splitCubic.ts +28 -0
- package/src/process/{splitPath.js → splitPath.ts} +9 -8
- package/src/process/{transformPath.js → transformPath.ts} +55 -48
- package/src/types.ts +193 -0
- package/src/util/getClosestPoint.ts +15 -0
- package/src/util/{getDrawDirection.js → getDrawDirection.ts} +7 -4
- package/src/util/getPathArea.ts +70 -0
- package/src/util/{getPathBBox.js → getPathBBox.ts} +16 -5
- package/src/util/getPointAtLength.ts +14 -0
- package/src/util/{getPropertiesAtLength.js → getPropertiesAtLength.ts} +28 -19
- package/src/util/{getPropertiesAtPoint.js → getPropertiesAtPoint.ts} +16 -12
- package/src/util/getSegmentAtLength.ts +15 -0
- package/src/util/getSegmentOfPoint.ts +18 -0
- package/src/util/{getTotalLength.js → getTotalLength.ts} +6 -4
- package/src/util/isAbsoluteArray.ts +18 -0
- package/src/util/{isCurveArray.js → isCurveArray.ts} +6 -4
- package/src/util/{isNormalizedArray.js → isNormalizedArray.ts} +4 -2
- package/src/util/isPathArray.ts +19 -0
- package/src/util/isPointInStroke.ts +15 -0
- package/src/util/isRelativeArray.ts +18 -0
- package/src/util/{isValidPath.js → isValidPath.ts} +5 -4
- package/src/util/{pathLengthFactory.js → pathLengthFactory.ts} +38 -31
- package/src/util/{segmentArcFactory.js → segmentArcFactory.ts} +71 -55
- package/src/util/segmentCubicFactory.ts +114 -0
- package/src/util/{segmentLineFactory.js → segmentLineFactory.ts} +10 -8
- package/src/util/{segmentQuadFactory.js → segmentQuadFactory.ts} +47 -32
- package/src/util/shapeToPath.ts +214 -0
- package/dist/svg-path-commander.es5.js +0 -3875
- package/dist/svg-path-commander.es5.min.js +0 -2
- package/dist/svg-path-commander.esm.js +0 -3754
- package/dist/svg-path-commander.esm.min.js +0 -2
- package/dist/svg-path-commander.min.js +0 -2
- package/src/convert/pathToAbsolute.js +0 -86
- package/src/convert/pathToRelative.js +0 -84
- package/src/convert/pathToString.js +0 -14
- package/src/index.js +0 -10
- package/src/math/distanceSquareRoot.js +0 -14
- package/src/math/epsilon.js +0 -8
- package/src/math/midPoint.js +0 -13
- package/src/math/rotateVector.js +0 -14
- package/src/options/options.js +0 -10
- package/src/parser/error.js +0 -2
- package/src/parser/finalizeSegment.js +0 -28
- package/src/parser/isDigit.js +0 -9
- package/src/parser/isDigitStart.js +0 -13
- package/src/parser/isPathCommand.js +0 -25
- package/src/parser/isSpace.js +0 -16
- package/src/parser/paramsCount.js +0 -9
- package/src/parser/paramsParser.js +0 -8
- package/src/parser/pathParser.js +0 -24
- package/src/process/clonePath.js +0 -9
- package/src/process/fixArc.js +0 -21
- package/src/process/fixPath.js +0 -31
- package/src/process/getSVGMatrix.js +0 -61
- package/src/process/lineToCubic.js +0 -30
- package/src/process/normalizeSegment.js +0 -45
- package/src/process/quadToCubic.js +0 -22
- package/src/process/reverseCurve.js +0 -18
- package/src/process/reversePath.js +0 -89
- package/src/process/roundPath.js +0 -26
- package/src/process/segmentToCubic.js +0 -46
- package/src/process/shortenSegment.js +0 -58
- package/src/process/splitCubic.js +0 -26
- package/src/util/getClosestPoint.js +0 -12
- package/src/util/getPathArea.js +0 -47
- package/src/util/getPointAtLength.js +0 -12
- package/src/util/getSegmentAtLength.js +0 -11
- package/src/util/getSegmentOfPoint.js +0 -12
- package/src/util/isAbsoluteArray.js +0 -14
- package/src/util/isPathArray.js +0 -14
- package/src/util/isPointInStroke.js +0 -13
- package/src/util/isRelativeArray.js +0 -14
- package/src/util/segmentCubicFactory.js +0 -97
- package/src/util/shapeToPath.js +0 -204
- package/src/util/util.js +0 -82
- package/src/version.js +0 -8
- package/types/index.d.ts +0 -120
- package/types/more/modules.ts +0 -82
- package/types/more/svg.d.ts +0 -211
- package/types/svg-path-commander.d.ts +0 -1089
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
import type { LengthFactory } from '../interface';
|
|
1
2
|
import segmentLineFactory from './segmentLineFactory';
|
|
2
3
|
import distanceSquareRoot from '../math/distanceSquareRoot';
|
|
3
4
|
|
|
4
5
|
/**
|
|
6
|
+
* Returns an angle value between two points.
|
|
5
7
|
*
|
|
6
|
-
* @param
|
|
7
|
-
* @param
|
|
8
|
-
* @returns
|
|
8
|
+
* @param v0
|
|
9
|
+
* @param v1
|
|
10
|
+
* @returns a number representing an angle
|
|
9
11
|
*/
|
|
10
|
-
|
|
12
|
+
const angleBetween = (v0: { x: number; y: number }, v1: { x: number; y: number }): number => {
|
|
11
13
|
const { x: v0x, y: v0y } = v0;
|
|
12
14
|
const { x: v1x, y: v1y } = v1;
|
|
13
15
|
const p = v0x * v1x + v0y * v1y;
|
|
@@ -16,29 +18,39 @@ function angleBetween(v0, v1) {
|
|
|
16
18
|
const angle = sign * Math.acos(p / n);
|
|
17
19
|
|
|
18
20
|
return angle;
|
|
19
|
-
}
|
|
21
|
+
};
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* Returns a {x,y} point at a given length, the total length and
|
|
23
25
|
* the minimum and maximum {x,y} coordinates of a C (cubic-bezier) segment.
|
|
26
|
+
*
|
|
24
27
|
* @see https://github.com/MadLittleMods/svg-curve-lib/blob/master/src/js/svg-curve-lib.js
|
|
25
28
|
*
|
|
26
|
-
* @param
|
|
27
|
-
* @param
|
|
28
|
-
* @param
|
|
29
|
-
* @param
|
|
30
|
-
* @param
|
|
31
|
-
* @param
|
|
32
|
-
* @param
|
|
33
|
-
* @param
|
|
34
|
-
* @param
|
|
35
|
-
* @param
|
|
36
|
-
* @returns
|
|
29
|
+
* @param x1 the starting x position
|
|
30
|
+
* @param y1 the starting y position
|
|
31
|
+
* @param RX x-radius of the arc
|
|
32
|
+
* @param RY y-radius of the arc
|
|
33
|
+
* @param angle x-axis-rotation of the arc
|
|
34
|
+
* @param LAF large-arc-flag of the arc
|
|
35
|
+
* @param SF sweep-flag of the arc
|
|
36
|
+
* @param x the ending x position
|
|
37
|
+
* @param y the ending y position
|
|
38
|
+
* @param t the point distance
|
|
39
|
+
* @returns the requested point
|
|
37
40
|
*/
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
const getPointAtArcSegmentLength = (
|
|
42
|
+
x1: number,
|
|
43
|
+
y1: number,
|
|
44
|
+
RX: number,
|
|
45
|
+
RY: number,
|
|
46
|
+
angle: number,
|
|
47
|
+
LAF: number,
|
|
48
|
+
SF: number,
|
|
49
|
+
x: number,
|
|
50
|
+
y: number,
|
|
51
|
+
t: number,
|
|
52
|
+
): { x: number; y: number } => {
|
|
53
|
+
const { abs, sin, cos, sqrt, PI } = Math;
|
|
42
54
|
let rx = abs(RX);
|
|
43
55
|
let ry = abs(RY);
|
|
44
56
|
const xRot = ((angle % 360) + 360) % 360;
|
|
@@ -67,12 +79,9 @@ function getPointAtArcSegmentLength(x1, y1, RX, RY, angle, LAF, SF, x, y, t) {
|
|
|
67
79
|
ry *= sqrt(radiiCheck);
|
|
68
80
|
}
|
|
69
81
|
|
|
70
|
-
const cSquareNumerator = rx ** 2 * ry ** 2
|
|
71
|
-
- rx ** 2 * transformedPoint.y ** 2
|
|
72
|
-
- ry ** 2 * transformedPoint.x ** 2;
|
|
82
|
+
const cSquareNumerator = rx ** 2 * ry ** 2 - rx ** 2 * transformedPoint.y ** 2 - ry ** 2 * transformedPoint.x ** 2;
|
|
73
83
|
|
|
74
|
-
const cSquareRootDenom = rx ** 2 * transformedPoint.y ** 2
|
|
75
|
-
+ ry ** 2 * transformedPoint.x ** 2;
|
|
84
|
+
const cSquareRootDenom = rx ** 2 * transformedPoint.y ** 2 + ry ** 2 * transformedPoint.x ** 2;
|
|
76
85
|
|
|
77
86
|
let cRadicand = cSquareNumerator / cSquareRootDenom;
|
|
78
87
|
cRadicand = cRadicand < 0 ? 0 : cRadicand;
|
|
@@ -83,10 +92,8 @@ function getPointAtArcSegmentLength(x1, y1, RX, RY, angle, LAF, SF, x, y, t) {
|
|
|
83
92
|
};
|
|
84
93
|
|
|
85
94
|
const center = {
|
|
86
|
-
x: cos(xRotRad) * transformedCenter.x
|
|
87
|
-
|
|
88
|
-
y: sin(xRotRad) * transformedCenter.x
|
|
89
|
-
+ cos(xRotRad) * transformedCenter.y + (y1 + y) / 2,
|
|
95
|
+
x: cos(xRotRad) * transformedCenter.x - sin(xRotRad) * transformedCenter.y + (x1 + x) / 2,
|
|
96
|
+
y: sin(xRotRad) * transformedCenter.x + cos(xRotRad) * transformedCenter.y + (y1 + y) / 2,
|
|
90
97
|
};
|
|
91
98
|
|
|
92
99
|
const startVector = {
|
|
@@ -114,12 +121,8 @@ function getPointAtArcSegmentLength(x1, y1, RX, RY, angle, LAF, SF, x, y, t) {
|
|
|
114
121
|
const ellipseComponentY = ry * sin(alpha);
|
|
115
122
|
|
|
116
123
|
const point = {
|
|
117
|
-
x: cos(xRotRad) * ellipseComponentX
|
|
118
|
-
|
|
119
|
-
+ center.x,
|
|
120
|
-
y: sin(xRotRad) * ellipseComponentX
|
|
121
|
-
+ cos(xRotRad) * ellipseComponentY
|
|
122
|
-
+ center.y,
|
|
124
|
+
x: cos(xRotRad) * ellipseComponentX - sin(xRotRad) * ellipseComponentY + center.x,
|
|
125
|
+
y: sin(xRotRad) * ellipseComponentX + cos(xRotRad) * ellipseComponentY + center.y,
|
|
123
126
|
};
|
|
124
127
|
|
|
125
128
|
// to be used later
|
|
@@ -132,30 +135,42 @@ function getPointAtArcSegmentLength(x1, y1, RX, RY, angle, LAF, SF, x, y, t) {
|
|
|
132
135
|
// point.resultantRy = ry;
|
|
133
136
|
|
|
134
137
|
return point;
|
|
135
|
-
}
|
|
138
|
+
};
|
|
136
139
|
|
|
137
140
|
/**
|
|
138
141
|
* Returns a {x,y} point at a given length, the total length and
|
|
139
142
|
* the shape minimum and maximum {x,y} coordinates of an A (arc-to) segment.
|
|
140
143
|
*
|
|
141
|
-
* @param
|
|
142
|
-
* @param
|
|
143
|
-
* @param
|
|
144
|
-
* @param
|
|
145
|
-
* @param
|
|
146
|
-
* @param
|
|
147
|
-
* @param
|
|
148
|
-
* @param
|
|
149
|
-
* @param
|
|
150
|
-
* @param
|
|
151
|
-
* @returns
|
|
144
|
+
* @param X1 the starting x position
|
|
145
|
+
* @param Y1 the starting y position
|
|
146
|
+
* @param RX x-radius of the arc
|
|
147
|
+
* @param RY y-radius of the arc
|
|
148
|
+
* @param angle x-axis-rotation of the arc
|
|
149
|
+
* @param LAF large-arc-flag of the arc
|
|
150
|
+
* @param SF sweep-flag of the arc
|
|
151
|
+
* @param X2 the ending x position
|
|
152
|
+
* @param Y2 the ending y position
|
|
153
|
+
* @param distance the point distance
|
|
154
|
+
* @returns the segment length, point, min & max
|
|
152
155
|
*/
|
|
153
|
-
|
|
156
|
+
const segmentArcFactory = (
|
|
157
|
+
X1: number,
|
|
158
|
+
Y1: number,
|
|
159
|
+
RX: number,
|
|
160
|
+
RY: number,
|
|
161
|
+
angle: number,
|
|
162
|
+
LAF: number,
|
|
163
|
+
SF: number,
|
|
164
|
+
X2: number,
|
|
165
|
+
Y2: number,
|
|
166
|
+
distance: number,
|
|
167
|
+
): LengthFactory => {
|
|
154
168
|
const distanceIsNumber = typeof distance === 'number';
|
|
155
|
-
let x = X1;
|
|
169
|
+
let x = X1;
|
|
170
|
+
let y = Y1;
|
|
156
171
|
let LENGTH = 0;
|
|
157
172
|
let prev = [x, y, LENGTH];
|
|
158
|
-
let cur = [x, y];
|
|
173
|
+
let cur = [x, y] as [number, number];
|
|
159
174
|
let t = 0;
|
|
160
175
|
let POINT = { x: 0, y: 0 };
|
|
161
176
|
let POINTS = [{ x, y }];
|
|
@@ -192,12 +207,13 @@ export default function segmentArcFactory(X1, Y1, RX, RY, angle, LAF, SF, X2, Y2
|
|
|
192
207
|
length: LENGTH,
|
|
193
208
|
point: POINT,
|
|
194
209
|
min: {
|
|
195
|
-
x: Math.min(...POINTS.map(
|
|
196
|
-
y: Math.min(...POINTS.map(
|
|
210
|
+
x: Math.min(...POINTS.map(n => n.x)),
|
|
211
|
+
y: Math.min(...POINTS.map(n => n.y)),
|
|
197
212
|
},
|
|
198
213
|
max: {
|
|
199
|
-
x: Math.max(...POINTS.map(
|
|
200
|
-
y: Math.max(...POINTS.map(
|
|
214
|
+
x: Math.max(...POINTS.map(n => n.x)),
|
|
215
|
+
y: Math.max(...POINTS.map(n => n.y)),
|
|
201
216
|
},
|
|
202
217
|
};
|
|
203
|
-
}
|
|
218
|
+
};
|
|
219
|
+
export default segmentArcFactory;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { LengthFactory } from 'src/interface';
|
|
2
|
+
import distanceSquareRoot from '../math/distanceSquareRoot';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns a {x,y} point at a given length, the total length and
|
|
6
|
+
* the minimum and maximum {x,y} coordinates of a C (cubic-bezier) segment.
|
|
7
|
+
*
|
|
8
|
+
* @param x1 the starting point X
|
|
9
|
+
* @param y1 the starting point Y
|
|
10
|
+
* @param c1x the first control point X
|
|
11
|
+
* @param c1y the first control point Y
|
|
12
|
+
* @param c2x the second control point X
|
|
13
|
+
* @param c2y the second control point Y
|
|
14
|
+
* @param x2 the ending point X
|
|
15
|
+
* @param y2 the ending point Y
|
|
16
|
+
* @param t a [0-1] ratio
|
|
17
|
+
* @returns the point at cubic-bezier segment length
|
|
18
|
+
*/
|
|
19
|
+
const getPointAtCubicSegmentLength = (
|
|
20
|
+
x1: number,
|
|
21
|
+
y1: number,
|
|
22
|
+
c1x: number,
|
|
23
|
+
c1y: number,
|
|
24
|
+
c2x: number,
|
|
25
|
+
c2y: number,
|
|
26
|
+
x2: number,
|
|
27
|
+
y2: number,
|
|
28
|
+
t: number,
|
|
29
|
+
): { x: number; y: number } => {
|
|
30
|
+
const t1 = 1 - t;
|
|
31
|
+
return {
|
|
32
|
+
x: t1 ** 3 * x1 + 3 * t1 ** 2 * t * c1x + 3 * t1 * t ** 2 * c2x + t ** 3 * x2,
|
|
33
|
+
y: t1 ** 3 * y1 + 3 * t1 ** 2 * t * c1y + 3 * t1 * t ** 2 * c2y + t ** 3 * y2,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Returns the length of a C (cubic-bezier) segment
|
|
39
|
+
* or an {x,y} point at a given length.
|
|
40
|
+
*
|
|
41
|
+
* @param x1 the starting point X
|
|
42
|
+
* @param y1 the starting point Y
|
|
43
|
+
* @param c1x the first control point X
|
|
44
|
+
* @param c1y the first control point Y
|
|
45
|
+
* @param c2x the second control point X
|
|
46
|
+
* @param c2y the second control point Y
|
|
47
|
+
* @param x2 the ending point X
|
|
48
|
+
* @param y2 the ending point Y
|
|
49
|
+
* @param distance the point distance
|
|
50
|
+
* @returns the segment length, point, min & max
|
|
51
|
+
*/
|
|
52
|
+
const segmentCubicFactory = (
|
|
53
|
+
x1: number,
|
|
54
|
+
y1: number,
|
|
55
|
+
c1x: number,
|
|
56
|
+
c1y: number,
|
|
57
|
+
c2x: number,
|
|
58
|
+
c2y: number,
|
|
59
|
+
x2: number,
|
|
60
|
+
y2: number,
|
|
61
|
+
distance?: number,
|
|
62
|
+
): LengthFactory => {
|
|
63
|
+
const distanceIsNumber = typeof distance === 'number';
|
|
64
|
+
let x = x1;
|
|
65
|
+
let y = y1;
|
|
66
|
+
let LENGTH = 0;
|
|
67
|
+
let prev = [x, y, LENGTH];
|
|
68
|
+
let cur = [x, y] as [number, number];
|
|
69
|
+
let t = 0;
|
|
70
|
+
let POINT = { x: 0, y: 0 };
|
|
71
|
+
let POINTS = [{ x, y }];
|
|
72
|
+
|
|
73
|
+
if (distanceIsNumber && distance <= 0) {
|
|
74
|
+
POINT = { x, y };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const sampleSize = 300;
|
|
78
|
+
for (let j = 0; j <= sampleSize; j += 1) {
|
|
79
|
+
t = j / sampleSize;
|
|
80
|
+
|
|
81
|
+
({ x, y } = getPointAtCubicSegmentLength(x1, y1, c1x, c1y, c2x, c2y, x2, y2, t));
|
|
82
|
+
POINTS = [...POINTS, { x, y }];
|
|
83
|
+
LENGTH += distanceSquareRoot(cur, [x, y]);
|
|
84
|
+
cur = [x, y];
|
|
85
|
+
|
|
86
|
+
if (distanceIsNumber && LENGTH > distance && distance > prev[2]) {
|
|
87
|
+
const dv = (LENGTH - distance) / (LENGTH - prev[2]);
|
|
88
|
+
|
|
89
|
+
POINT = {
|
|
90
|
+
x: cur[0] * (1 - dv) + prev[0] * dv,
|
|
91
|
+
y: cur[1] * (1 - dv) + prev[1] * dv,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
prev = [x, y, LENGTH];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (distanceIsNumber && distance >= LENGTH) {
|
|
98
|
+
POINT = { x: x2, y: y2 };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
length: LENGTH,
|
|
103
|
+
point: POINT,
|
|
104
|
+
min: {
|
|
105
|
+
x: Math.min(...POINTS.map(n => n.x)),
|
|
106
|
+
y: Math.min(...POINTS.map(n => n.y)),
|
|
107
|
+
},
|
|
108
|
+
max: {
|
|
109
|
+
x: Math.max(...POINTS.map(n => n.x)),
|
|
110
|
+
y: Math.max(...POINTS.map(n => n.y)),
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
export default segmentCubicFactory;
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import midPoint from '../math/midPoint';
|
|
2
2
|
import distanceSquareRoot from '../math/distanceSquareRoot';
|
|
3
|
+
import type { LengthFactory } from '../interface';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Returns a {x,y} point at a given length, the total length and
|
|
6
7
|
* the minimum and maximum {x,y} coordinates of a line (L,V,H,Z) segment.
|
|
7
8
|
*
|
|
8
|
-
* @param
|
|
9
|
-
* @param
|
|
10
|
-
* @param
|
|
11
|
-
* @param
|
|
12
|
-
* @param
|
|
13
|
-
* @returns
|
|
9
|
+
* @param x1 the starting point X
|
|
10
|
+
* @param y1 the starting point Y
|
|
11
|
+
* @param x2 the ending point X
|
|
12
|
+
* @param y2 the ending point Y
|
|
13
|
+
* @param distance the distance to point
|
|
14
|
+
* @returns the segment length, point, min & max
|
|
14
15
|
*/
|
|
15
|
-
|
|
16
|
+
const segmentLineFactory = (x1: number, y1: number, x2: number, y2: number, distance?: number): LengthFactory => {
|
|
16
17
|
const length = distanceSquareRoot([x1, y1], [x2, y2]);
|
|
17
18
|
let point = { x: 0, y: 0 };
|
|
18
19
|
|
|
@@ -40,4 +41,5 @@ export default function segmentLineFactory(x1, y1, x2, y2, distance) {
|
|
|
40
41
|
y: Math.max(y1, y2),
|
|
41
42
|
},
|
|
42
43
|
};
|
|
43
|
-
}
|
|
44
|
+
};
|
|
45
|
+
export default segmentLineFactory;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LengthFactory } from 'src/interface';
|
|
1
2
|
import distanceSquareRoot from '../math/distanceSquareRoot';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -6,46 +7,59 @@ import distanceSquareRoot from '../math/distanceSquareRoot';
|
|
|
6
7
|
*
|
|
7
8
|
* @see https://github.com/substack/point-at-length
|
|
8
9
|
*
|
|
9
|
-
* @param
|
|
10
|
-
* @param
|
|
11
|
-
* @param
|
|
12
|
-
* @param
|
|
13
|
-
* @param
|
|
14
|
-
* @param
|
|
15
|
-
* @param
|
|
16
|
-
* @returns
|
|
10
|
+
* @param x1 the starting point X
|
|
11
|
+
* @param y1 the starting point Y
|
|
12
|
+
* @param cx the control point X
|
|
13
|
+
* @param cy the control point Y
|
|
14
|
+
* @param x2 the ending point X
|
|
15
|
+
* @param y2 the ending point Y
|
|
16
|
+
* @param t a [0-1] ratio
|
|
17
|
+
* @returns the requested {x,y} coordinates
|
|
17
18
|
*/
|
|
18
|
-
|
|
19
|
+
const getPointAtQuadSegmentLength = (
|
|
20
|
+
x1: number,
|
|
21
|
+
y1: number,
|
|
22
|
+
cx: number,
|
|
23
|
+
cy: number,
|
|
24
|
+
x2: number,
|
|
25
|
+
y2: number,
|
|
26
|
+
t: number,
|
|
27
|
+
): { x: number; y: number } => {
|
|
19
28
|
const t1 = 1 - t;
|
|
20
29
|
return {
|
|
21
|
-
x:
|
|
22
|
-
|
|
23
|
-
+ (t ** 2) * x2,
|
|
24
|
-
y: (t1 ** 2) * y1
|
|
25
|
-
+ 2 * t1 * t * cy
|
|
26
|
-
+ (t ** 2) * y2,
|
|
30
|
+
x: t1 ** 2 * x1 + 2 * t1 * t * cx + t ** 2 * x2,
|
|
31
|
+
y: t1 ** 2 * y1 + 2 * t1 * t * cy + t ** 2 * y2,
|
|
27
32
|
};
|
|
28
|
-
}
|
|
33
|
+
};
|
|
29
34
|
|
|
30
35
|
/**
|
|
31
36
|
* Returns a {x,y} point at a given length, the total length and
|
|
32
37
|
* the minimum and maximum {x,y} coordinates of a Q (quadratic-bezier) segment.
|
|
33
38
|
*
|
|
34
|
-
* @param
|
|
35
|
-
* @param
|
|
36
|
-
* @param
|
|
37
|
-
* @param
|
|
38
|
-
* @param
|
|
39
|
-
* @param
|
|
40
|
-
* @param
|
|
41
|
-
* @returns
|
|
39
|
+
* @param x1 the starting point X
|
|
40
|
+
* @param y1 the starting point Y
|
|
41
|
+
* @param qx the control point X
|
|
42
|
+
* @param qy the control point Y
|
|
43
|
+
* @param x2 the ending point X
|
|
44
|
+
* @param y2 the ending point Y
|
|
45
|
+
* @param distance the distance to point
|
|
46
|
+
* @returns the segment length, point, min & max
|
|
42
47
|
*/
|
|
43
|
-
|
|
48
|
+
const segmentQuadFactory = (
|
|
49
|
+
x1: number,
|
|
50
|
+
y1: number,
|
|
51
|
+
qx: number,
|
|
52
|
+
qy: number,
|
|
53
|
+
x2: number,
|
|
54
|
+
y2: number,
|
|
55
|
+
distance?: number,
|
|
56
|
+
): LengthFactory => {
|
|
44
57
|
const distanceIsNumber = typeof distance === 'number';
|
|
45
|
-
let x = x1;
|
|
58
|
+
let x = x1;
|
|
59
|
+
let y = y1;
|
|
46
60
|
let LENGTH = 0;
|
|
47
61
|
let prev = [x, y, LENGTH];
|
|
48
|
-
let cur = [x, y];
|
|
62
|
+
let cur = [x, y] as [number, number];
|
|
49
63
|
let t = 0;
|
|
50
64
|
let POINT = { x: 0, y: 0 };
|
|
51
65
|
let POINTS = [{ x, y }];
|
|
@@ -83,12 +97,13 @@ export default function segmentQuadFactory(x1, y1, qx, qy, x2, y2, distance) {
|
|
|
83
97
|
length: LENGTH,
|
|
84
98
|
point: POINT,
|
|
85
99
|
min: {
|
|
86
|
-
x: Math.min(...POINTS.map(
|
|
87
|
-
y: Math.min(...POINTS.map(
|
|
100
|
+
x: Math.min(...POINTS.map(n => n.x)),
|
|
101
|
+
y: Math.min(...POINTS.map(n => n.y)),
|
|
88
102
|
},
|
|
89
103
|
max: {
|
|
90
|
-
x: Math.max(...POINTS.map(
|
|
91
|
-
y: Math.max(...POINTS.map(
|
|
104
|
+
x: Math.max(...POINTS.map(n => n.x)),
|
|
105
|
+
y: Math.max(...POINTS.map(n => n.y)),
|
|
92
106
|
},
|
|
93
107
|
};
|
|
94
|
-
}
|
|
108
|
+
};
|
|
109
|
+
export default segmentQuadFactory;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import type { CircleAttr, EllipseAttr, GlyphAttr, LineAttr, PolyAttr, RectAttr, ShapeParams } from '../interface';
|
|
2
|
+
import type { PathArray, PathSegment, ShapeOps, ShapeTypes } from '../types';
|
|
3
|
+
import pathToString from '../convert/pathToString';
|
|
4
|
+
import defaultOptions from '../options/options';
|
|
5
|
+
import error from '../parser/error';
|
|
6
|
+
import isValidPath from './isValidPath';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Supported shapes and their specific parameters.
|
|
10
|
+
*/
|
|
11
|
+
const shapeParams: ShapeParams = {
|
|
12
|
+
line: ['x1', 'y1', 'x2', 'y2'],
|
|
13
|
+
circle: ['cx', 'cy', 'r'],
|
|
14
|
+
ellipse: ['cx', 'cy', 'rx', 'ry'],
|
|
15
|
+
rect: ['width', 'height', 'x', 'y', 'rx', 'ry'],
|
|
16
|
+
polygon: ['points'],
|
|
17
|
+
polyline: ['points'],
|
|
18
|
+
glyph: ['d'],
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Returns a new `pathArray` from line attributes.
|
|
23
|
+
*
|
|
24
|
+
* @param attr shape configuration
|
|
25
|
+
* @returns a new line `pathArray`
|
|
26
|
+
*/
|
|
27
|
+
export const getLinePath = (attr: LineAttr): PathArray => {
|
|
28
|
+
const { x1, y1, x2, y2 } = attr;
|
|
29
|
+
return [
|
|
30
|
+
['M', x1, y1],
|
|
31
|
+
['L', x2, y2],
|
|
32
|
+
];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns a new `pathArray` like from polyline/polygon attributes.
|
|
37
|
+
*
|
|
38
|
+
* @param attr shape configuration
|
|
39
|
+
* @return a new polygon/polyline `pathArray`
|
|
40
|
+
*/
|
|
41
|
+
export const getPolyPath = (attr: PolyAttr): PathArray => {
|
|
42
|
+
const pathArray = [] as PathSegment[];
|
|
43
|
+
const points = (attr.points || '')
|
|
44
|
+
.trim()
|
|
45
|
+
.split(/[\s|,]/)
|
|
46
|
+
.map(Number);
|
|
47
|
+
|
|
48
|
+
let index = 0;
|
|
49
|
+
while (index < points.length) {
|
|
50
|
+
pathArray.push([index ? 'L' : 'M', points[index], points[index + 1]]);
|
|
51
|
+
index += 2;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (attr.type === 'polygon' ? [...pathArray, ['z']] : pathArray) as PathArray;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Returns a new `pathArray` from circle attributes.
|
|
59
|
+
*
|
|
60
|
+
* @param attr shape configuration
|
|
61
|
+
* @return a circle `pathArray`
|
|
62
|
+
*/
|
|
63
|
+
export const getCirclePath = (attr: CircleAttr): PathArray => {
|
|
64
|
+
const { cx, cy, r } = attr;
|
|
65
|
+
|
|
66
|
+
return [
|
|
67
|
+
['M', cx - r, cy],
|
|
68
|
+
['a', r, r, 0, 1, 0, 2 * r, 0],
|
|
69
|
+
['a', r, r, 0, 1, 0, -2 * r, 0],
|
|
70
|
+
];
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Returns a new `pathArray` from ellipse attributes.
|
|
75
|
+
*
|
|
76
|
+
* @param attr shape configuration
|
|
77
|
+
* @return an ellipse `pathArray`
|
|
78
|
+
*/
|
|
79
|
+
export const getEllipsePath = (attr: EllipseAttr): PathArray => {
|
|
80
|
+
const { cx, cy, rx, ry } = attr;
|
|
81
|
+
|
|
82
|
+
return [
|
|
83
|
+
['M', cx - rx, cy],
|
|
84
|
+
['a', rx, ry, 0, 1, 0, 2 * rx, 0],
|
|
85
|
+
['a', rx, ry, 0, 1, 0, -2 * rx, 0],
|
|
86
|
+
];
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Returns a new `pathArray` like from rect attributes.
|
|
91
|
+
*
|
|
92
|
+
* @param attr object with properties above
|
|
93
|
+
* @return a new `pathArray` from `<rect>` attributes
|
|
94
|
+
*/
|
|
95
|
+
export const getRectanglePath = (attr: RectAttr): PathArray => {
|
|
96
|
+
const x = +attr.x || 0;
|
|
97
|
+
const y = +attr.y || 0;
|
|
98
|
+
const w = +attr.width;
|
|
99
|
+
const h = +attr.height;
|
|
100
|
+
let rx = +attr.rx;
|
|
101
|
+
let ry = +attr.ry;
|
|
102
|
+
|
|
103
|
+
// Validity checks from http://www.w3.org/TR/SVG/shapes.html#RectElement:
|
|
104
|
+
if (rx || ry) {
|
|
105
|
+
rx = !rx ? ry : rx;
|
|
106
|
+
ry = !ry ? rx : ry;
|
|
107
|
+
|
|
108
|
+
/* istanbul ignore else */
|
|
109
|
+
if (rx * 2 > w) rx -= (rx * 2 - w) / 2;
|
|
110
|
+
/* istanbul ignore else */
|
|
111
|
+
if (ry * 2 > h) ry -= (ry * 2 - h) / 2;
|
|
112
|
+
|
|
113
|
+
return [
|
|
114
|
+
['M', x + rx, y],
|
|
115
|
+
['h', w - rx * 2],
|
|
116
|
+
['s', rx, 0, rx, ry],
|
|
117
|
+
['v', h - ry * 2],
|
|
118
|
+
['s', 0, ry, -rx, ry],
|
|
119
|
+
['h', -w + rx * 2],
|
|
120
|
+
['s', -rx, 0, -rx, -ry],
|
|
121
|
+
['v', -h + ry * 2],
|
|
122
|
+
['s', 0, -ry, rx, -ry],
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return [['M', x, y], ['h', w], ['v', h], ['H', x], ['Z']];
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Returns a new `<path>` element created from attributes of a `<line>`, `<polyline>`,
|
|
131
|
+
* `<polygon>`, `<rect>`, `<ellipse>`, `<circle>` or `<glyph>`. If `replace` parameter
|
|
132
|
+
* is `true`, it will replace the target. The default `ownerDocument` is your current
|
|
133
|
+
* `document` browser page, if you want to use in server-side using `jsdom`, you can
|
|
134
|
+
* pass the `jsdom` `document` to `ownDocument`.
|
|
135
|
+
*
|
|
136
|
+
* It can also work with an options object,
|
|
137
|
+
*
|
|
138
|
+
* @see ShapeOps
|
|
139
|
+
*
|
|
140
|
+
* The newly created `<path>` element keeps all non-specific
|
|
141
|
+
* attributes like `class`, `fill`, etc.
|
|
142
|
+
*
|
|
143
|
+
* @param element target shape
|
|
144
|
+
* @param replace option to replace target
|
|
145
|
+
* @param ownerDocument document for create element
|
|
146
|
+
* @return the newly created `<path>` element
|
|
147
|
+
*/
|
|
148
|
+
const shapeToPath = (
|
|
149
|
+
element: ShapeTypes | ShapeOps,
|
|
150
|
+
replace?: boolean,
|
|
151
|
+
ownerDocument?: Document,
|
|
152
|
+
): SVGPathElement | false => {
|
|
153
|
+
const doc = ownerDocument || document;
|
|
154
|
+
const win = doc.defaultView || /* istanbul ignore next */ window;
|
|
155
|
+
const supportedShapes = Object.keys(shapeParams) as (keyof ShapeParams)[];
|
|
156
|
+
const elementIsElement = element instanceof win.SVGElement;
|
|
157
|
+
const tagName = elementIsElement ? element.tagName : null;
|
|
158
|
+
|
|
159
|
+
if (tagName && supportedShapes.every(s => tagName !== s)) {
|
|
160
|
+
throw TypeError(`${error}: "${tagName}" is not SVGElement`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const path = doc.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
164
|
+
const type = (elementIsElement ? tagName : element.type) as ShapeOps['type'];
|
|
165
|
+
const shapeAttrs = shapeParams[type] as string[];
|
|
166
|
+
const config = { type } as Record<string, string>;
|
|
167
|
+
|
|
168
|
+
if (elementIsElement) {
|
|
169
|
+
shapeAttrs.forEach(p => {
|
|
170
|
+
if (shapeAttrs.includes(p)) config[p] = element.getAttribute(p) as string;
|
|
171
|
+
});
|
|
172
|
+
// set no-specific shape attributes: fill, stroke, etc
|
|
173
|
+
Object.values(element.attributes).forEach(({ name, value }) => {
|
|
174
|
+
if (!shapeAttrs.includes(name)) path.setAttribute(name, value);
|
|
175
|
+
});
|
|
176
|
+
} else {
|
|
177
|
+
Object.assign(config, element);
|
|
178
|
+
// set no-specific shape attributes: fill, stroke, etc
|
|
179
|
+
Object.keys(config).forEach(k => {
|
|
180
|
+
if (!shapeAttrs.includes(k) && k !== 'type') {
|
|
181
|
+
path.setAttribute(
|
|
182
|
+
k.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`),
|
|
183
|
+
config[k],
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// set d
|
|
190
|
+
let description = '';
|
|
191
|
+
const round = defaultOptions.round as number;
|
|
192
|
+
|
|
193
|
+
/* istanbul ignore else */
|
|
194
|
+
if (type === 'circle') description = pathToString(getCirclePath(config as unknown as CircleAttr), round);
|
|
195
|
+
else if (type === 'ellipse') description = pathToString(getEllipsePath(config as unknown as EllipseAttr), round);
|
|
196
|
+
else if (['polyline', 'polygon'].includes(type))
|
|
197
|
+
description = pathToString(getPolyPath(config as unknown as PolyAttr), round);
|
|
198
|
+
else if (type === 'rect') description = pathToString(getRectanglePath(config as unknown as RectAttr), round);
|
|
199
|
+
else if (type === 'line') description = pathToString(getLinePath(config as unknown as LineAttr), round);
|
|
200
|
+
else if (type === 'glyph')
|
|
201
|
+
description = elementIsElement ? (element.getAttribute('d') as string) : (element as GlyphAttr).d;
|
|
202
|
+
|
|
203
|
+
// replace target element
|
|
204
|
+
if (isValidPath(description)) {
|
|
205
|
+
path.setAttribute('d', description);
|
|
206
|
+
if (replace && elementIsElement) {
|
|
207
|
+
element.before(path, element);
|
|
208
|
+
element.remove();
|
|
209
|
+
}
|
|
210
|
+
return path;
|
|
211
|
+
}
|
|
212
|
+
return false;
|
|
213
|
+
};
|
|
214
|
+
export default shapeToPath;
|