svg-path-commander 2.0.10 → 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/.eslintrc.cjs +1 -0
- package/README.md +64 -8
- package/dist/svg-path-commander.cjs +1 -1
- package/dist/svg-path-commander.cjs.map +1 -1
- package/dist/svg-path-commander.d.ts +236 -43
- package/dist/svg-path-commander.js +1 -1
- package/dist/svg-path-commander.js.map +1 -1
- package/dist/svg-path-commander.mjs +790 -650
- package/dist/svg-path-commander.mjs.map +1 -1
- package/package.json +20 -22
- 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 +37 -39
- package/src/interface.ts +33 -33
- package/src/math/arcTools.ts +394 -0
- package/src/math/bezier.ts +253 -0
- package/src/math/cubicTools.ts +122 -0
- package/src/math/distanceSquareRoot.ts +3 -1
- package/src/math/lineTools.ts +67 -0
- 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 +98 -0
- package/src/parser/isMoveCommand.ts +17 -0
- package/src/parser/parsePathString.ts +5 -5
- package/src/parser/scanSegment.ts +12 -3
- 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 +39 -5
- package/src/process/transformPath.ts +81 -94
- package/src/types.ts +40 -1
- package/src/util/distanceEpsilon.ts +3 -0
- package/src/util/getClosestPoint.ts +1 -1
- package/src/util/getPathArea.ts +3 -3
- package/src/util/getPathBBox.ts +86 -18
- package/src/util/getPointAtLength.ts +98 -4
- package/src/util/getPropertiesAtLength.ts +4 -3
- package/src/util/getPropertiesAtPoint.ts +4 -1
- package/src/util/getTotalLength.ts +71 -4
- package/src/util/isPointInStroke.ts +2 -1
- package/src/util/shapeToPathArray.ts +8 -4
- package/test/class.test.ts +502 -0
- package/test/fixtures/getMarkup.ts +17 -0
- package/{cypress → test}/fixtures/shapes.js +39 -39
- package/test/fixtures/simpleShapes.js +75 -0
- package/test/static.test.ts +324 -0
- package/tsconfig.json +9 -4
- package/{vite.config.ts → vite.config.mts} +10 -1
- package/vitest.config-ui.mts +26 -0
- package/vitest.config.mts +26 -0
- package/cypress/e2e/svg-path-commander.spec.ts +0 -868
- package/cypress/fixtures/simpleShapes.js +0 -75
- package/cypress/plugins/esbuild-istanbul.ts +0 -50
- package/cypress/plugins/tsCompile.ts +0 -34
- package/cypress/support/commands.ts +0 -37
- package/cypress/support/e2e.ts +0 -21
- package/cypress/test.html +0 -36
- package/cypress.config.ts +0 -29
- package/src/process/fixArc.ts +0 -23
- package/src/util/pathLengthFactory.ts +0 -114
- package/src/util/segmentArcFactory.ts +0 -219
- package/src/util/segmentCubicFactory.ts +0 -114
- package/src/util/segmentLineFactory.ts +0 -45
- package/src/util/segmentQuadFactory.ts +0 -109
- /package/{cypress/fixtures/shapeObjects.js → test/fixtures/shapeObjects.ts} +0 -0
package/src/interface.ts
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
1
|
import type { PathSegment } from './types';
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export type SegmentProperties = {
|
|
4
4
|
segment: PathSegment;
|
|
5
5
|
index: number;
|
|
6
6
|
length: number;
|
|
7
7
|
lengthAtSegment: number;
|
|
8
8
|
[key: string]: any;
|
|
9
|
-
}
|
|
9
|
+
};
|
|
10
10
|
|
|
11
|
-
export
|
|
11
|
+
export type PointProperties = {
|
|
12
12
|
closest: {
|
|
13
13
|
x: number;
|
|
14
14
|
y: number;
|
|
15
15
|
};
|
|
16
16
|
distance: number;
|
|
17
17
|
segment?: SegmentProperties;
|
|
18
|
-
}
|
|
18
|
+
};
|
|
19
19
|
|
|
20
|
-
export
|
|
20
|
+
export type LineAttr = {
|
|
21
21
|
type: 'line';
|
|
22
22
|
x1: number;
|
|
23
23
|
y1: number;
|
|
24
24
|
x2: number;
|
|
25
25
|
y2: number;
|
|
26
26
|
[key: string]: string | number;
|
|
27
|
-
}
|
|
28
|
-
export
|
|
27
|
+
};
|
|
28
|
+
export type PolyAttr = {
|
|
29
29
|
type: 'polygon' | 'polyline';
|
|
30
30
|
points: string;
|
|
31
31
|
[key: string]: string | number;
|
|
32
|
-
}
|
|
33
|
-
export
|
|
32
|
+
};
|
|
33
|
+
export type CircleAttr = {
|
|
34
34
|
type: 'circle';
|
|
35
35
|
cx: number;
|
|
36
36
|
cy: number;
|
|
37
37
|
r: number;
|
|
38
38
|
[key: string]: string | number;
|
|
39
|
-
}
|
|
40
|
-
export
|
|
39
|
+
};
|
|
40
|
+
export type EllipseAttr = {
|
|
41
41
|
type: 'ellipse';
|
|
42
42
|
cx: number;
|
|
43
43
|
cy: number;
|
|
44
44
|
rx: number;
|
|
45
45
|
ry?: number;
|
|
46
46
|
[key: string]: string | number | undefined;
|
|
47
|
-
}
|
|
48
|
-
export
|
|
47
|
+
};
|
|
48
|
+
export type RectAttr = {
|
|
49
49
|
type: 'rect';
|
|
50
50
|
width: number;
|
|
51
51
|
height: number;
|
|
@@ -54,14 +54,14 @@ export interface RectAttr {
|
|
|
54
54
|
rx?: number;
|
|
55
55
|
ry?: number;
|
|
56
56
|
[key: string]: string | number | undefined;
|
|
57
|
-
}
|
|
58
|
-
export
|
|
57
|
+
};
|
|
58
|
+
export type GlyphAttr = {
|
|
59
59
|
type: 'glyph';
|
|
60
60
|
d: string;
|
|
61
61
|
[key: string]: string | number;
|
|
62
|
-
}
|
|
62
|
+
};
|
|
63
63
|
|
|
64
|
-
export
|
|
64
|
+
export type ShapeParams = {
|
|
65
65
|
line: ['x1', 'y1', 'x2', 'y2'];
|
|
66
66
|
circle: ['cx', 'cy', 'r'];
|
|
67
67
|
ellipse: ['cx', 'cy', 'rx', 'ry'];
|
|
@@ -69,9 +69,9 @@ export interface ShapeParams {
|
|
|
69
69
|
polygon: ['points'];
|
|
70
70
|
polyline: ['points'];
|
|
71
71
|
glyph: ['d'];
|
|
72
|
-
}
|
|
72
|
+
};
|
|
73
73
|
|
|
74
|
-
export
|
|
74
|
+
export type PathBBox = {
|
|
75
75
|
width: number;
|
|
76
76
|
height: number;
|
|
77
77
|
x: number;
|
|
@@ -81,13 +81,13 @@ export interface PathBBox {
|
|
|
81
81
|
cx: number;
|
|
82
82
|
cy: number;
|
|
83
83
|
cz: number;
|
|
84
|
-
}
|
|
85
|
-
export
|
|
84
|
+
};
|
|
85
|
+
export type SegmentLimits = {
|
|
86
86
|
min: { x: number; y: number };
|
|
87
87
|
max: { x: number; y: number };
|
|
88
|
-
}
|
|
88
|
+
};
|
|
89
89
|
|
|
90
|
-
export
|
|
90
|
+
export type ParserParams = {
|
|
91
91
|
x1: number;
|
|
92
92
|
y1: number;
|
|
93
93
|
x2: number;
|
|
@@ -96,34 +96,34 @@ export interface ParserParams {
|
|
|
96
96
|
y: number;
|
|
97
97
|
qx: number | null;
|
|
98
98
|
qy: number | null;
|
|
99
|
-
}
|
|
99
|
+
};
|
|
100
100
|
|
|
101
|
-
export
|
|
101
|
+
export type LengthFactory = {
|
|
102
102
|
length: number;
|
|
103
103
|
point: { x: number; y: number };
|
|
104
104
|
min: { x: number; y: number };
|
|
105
105
|
max: { x: number; y: number };
|
|
106
|
-
}
|
|
106
|
+
};
|
|
107
107
|
|
|
108
|
-
export
|
|
109
|
-
round: '
|
|
108
|
+
export type Options = {
|
|
109
|
+
round: 'off' | number;
|
|
110
110
|
origin: number[];
|
|
111
|
-
}
|
|
111
|
+
};
|
|
112
112
|
|
|
113
|
-
export
|
|
113
|
+
export type PathTransform = {
|
|
114
114
|
s: PathSegment;
|
|
115
115
|
c: string;
|
|
116
116
|
x: number;
|
|
117
117
|
y: number;
|
|
118
|
-
}
|
|
118
|
+
};
|
|
119
119
|
|
|
120
|
-
export
|
|
120
|
+
export type TransformObject = {
|
|
121
121
|
translate: number | number[];
|
|
122
122
|
rotate: number | number[];
|
|
123
123
|
scale: number | number[];
|
|
124
124
|
skew: number | number[];
|
|
125
125
|
origin: number[];
|
|
126
|
-
}
|
|
126
|
+
};
|
|
127
127
|
|
|
128
128
|
export type TransformProps = keyof TransformObject;
|
|
129
129
|
export type TransformEntries = [TransformProps, TransformObject[TransformProps]][];
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import { getPointAtLineLength } from './lineTools';
|
|
2
|
+
import type { Point } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns the Arc segment length.
|
|
6
|
+
* @param rx radius along X axis
|
|
7
|
+
* @param ry radius along Y axis
|
|
8
|
+
* @param theta the angle in radians
|
|
9
|
+
* @returns the arc length
|
|
10
|
+
*/
|
|
11
|
+
const ellipticalArcLength = (rx: number, ry: number, theta: number) => {
|
|
12
|
+
const halfTheta = theta / 2;
|
|
13
|
+
const sinHalfTheta = Math.sin(halfTheta);
|
|
14
|
+
const cosHalfTheta = Math.cos(halfTheta);
|
|
15
|
+
const term1 = rx ** 2 * sinHalfTheta ** 2;
|
|
16
|
+
const term2 = ry ** 2 * cosHalfTheta ** 2;
|
|
17
|
+
const arcLength = Math.sqrt(term1 + term2) * theta;
|
|
18
|
+
return Math.abs(arcLength);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Compute point on ellipse from angle around ellipse (theta);
|
|
23
|
+
* @param theta the arc sweep angle
|
|
24
|
+
* @param cx the center X
|
|
25
|
+
* @param cy the center Y
|
|
26
|
+
* @param rx the radius X
|
|
27
|
+
* @param ry the radius Y
|
|
28
|
+
* @param alpha the angle
|
|
29
|
+
* @returns a point around ellipse
|
|
30
|
+
*/
|
|
31
|
+
const arc = (theta: number, cx: number, cy: number, rx: number, ry: number, alpha: number) => {
|
|
32
|
+
// theta is angle in radians around arc
|
|
33
|
+
// alpha is angle of rotation of ellipse in radians
|
|
34
|
+
const cos = Math.cos(alpha);
|
|
35
|
+
const sin = Math.sin(alpha);
|
|
36
|
+
const x = rx * Math.cos(theta);
|
|
37
|
+
const y = ry * Math.sin(theta);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
x: cx + cos * x - sin * y,
|
|
41
|
+
y: cy + sin * x + cos * y,
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Returns the angle between two points.
|
|
47
|
+
* @param v0 starting point
|
|
48
|
+
* @param v1 ending point
|
|
49
|
+
* @returns the angle
|
|
50
|
+
*/
|
|
51
|
+
const angleBetween = (v0: Point, v1: Point) => {
|
|
52
|
+
const { x: v0x, y: v0y } = v0;
|
|
53
|
+
const { x: v1x, y: v1y } = v1;
|
|
54
|
+
const p = v0x * v1x + v0y * v1y;
|
|
55
|
+
const n = Math.sqrt((v0x ** 2 + v0y ** 2) * (v1x ** 2 + v1y ** 2));
|
|
56
|
+
const sign = v0x * v1y - v0y * v1x < 0 ? -1 : 1;
|
|
57
|
+
const angle = sign * Math.acos(p / n);
|
|
58
|
+
|
|
59
|
+
return angle;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Returns the following properties for an Arc segment: center, start angle
|
|
64
|
+
* and radiuses on X and Y coordinates.
|
|
65
|
+
*
|
|
66
|
+
* @param x1 the starting point X
|
|
67
|
+
* @param y1 the starting point Y
|
|
68
|
+
* @param RX the radius on X axis
|
|
69
|
+
* @param RY the radius on Y axis
|
|
70
|
+
* @param angle the ellipse rotation in degrees
|
|
71
|
+
* @param LAF the large arc flag
|
|
72
|
+
* @param SF the sweep flag
|
|
73
|
+
* @param x2 the ending point X
|
|
74
|
+
* @param y2 the ending point Y
|
|
75
|
+
* @returns properties specific to Arc segments
|
|
76
|
+
*/
|
|
77
|
+
const getArcProps = (
|
|
78
|
+
x1: number,
|
|
79
|
+
y1: number,
|
|
80
|
+
RX: number,
|
|
81
|
+
RY: number,
|
|
82
|
+
angle: number,
|
|
83
|
+
LAF: number,
|
|
84
|
+
SF: number,
|
|
85
|
+
x: number,
|
|
86
|
+
y: number,
|
|
87
|
+
) => {
|
|
88
|
+
const { abs, sin, cos, sqrt, PI } = Math;
|
|
89
|
+
let rx = abs(RX);
|
|
90
|
+
let ry = abs(RY);
|
|
91
|
+
const xRot = ((angle % 360) + 360) % 360;
|
|
92
|
+
const xRotRad = xRot * (PI / 180);
|
|
93
|
+
|
|
94
|
+
if (x1 === x && y1 === y) {
|
|
95
|
+
return {
|
|
96
|
+
rx,
|
|
97
|
+
ry,
|
|
98
|
+
startAngle: 0,
|
|
99
|
+
endAngle: 0,
|
|
100
|
+
center: { x, y },
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (rx === 0 || ry === 0) {
|
|
105
|
+
return {
|
|
106
|
+
rx,
|
|
107
|
+
ry,
|
|
108
|
+
startAngle: 0,
|
|
109
|
+
endAngle: 0,
|
|
110
|
+
center: { x, y },
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const dx = (x1 - x) / 2;
|
|
115
|
+
const dy = (y1 - y) / 2;
|
|
116
|
+
|
|
117
|
+
const transformedPoint = {
|
|
118
|
+
x: cos(xRotRad) * dx + sin(xRotRad) * dy,
|
|
119
|
+
y: -sin(xRotRad) * dx + cos(xRotRad) * dy,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const radiiCheck = transformedPoint.x ** 2 / rx ** 2 + transformedPoint.y ** 2 / ry ** 2;
|
|
123
|
+
|
|
124
|
+
if (radiiCheck > 1) {
|
|
125
|
+
rx *= sqrt(radiiCheck);
|
|
126
|
+
ry *= sqrt(radiiCheck);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const cSquareNumerator = rx ** 2 * ry ** 2 - rx ** 2 * transformedPoint.y ** 2 - ry ** 2 * transformedPoint.x ** 2;
|
|
130
|
+
const cSquareRootDenom = rx ** 2 * transformedPoint.y ** 2 + ry ** 2 * transformedPoint.x ** 2;
|
|
131
|
+
|
|
132
|
+
let cRadicand = cSquareNumerator / cSquareRootDenom;
|
|
133
|
+
/* istanbul ignore next @preserve */
|
|
134
|
+
cRadicand = cRadicand < 0 ? 0 : cRadicand;
|
|
135
|
+
const cCoef = (LAF !== SF ? 1 : -1) * sqrt(cRadicand);
|
|
136
|
+
const transformedCenter = {
|
|
137
|
+
x: cCoef * ((rx * transformedPoint.y) / ry),
|
|
138
|
+
y: cCoef * (-(ry * transformedPoint.x) / rx),
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const center = {
|
|
142
|
+
x: cos(xRotRad) * transformedCenter.x - sin(xRotRad) * transformedCenter.y + (x1 + x) / 2,
|
|
143
|
+
y: sin(xRotRad) * transformedCenter.x + cos(xRotRad) * transformedCenter.y + (y1 + y) / 2,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const startVector = {
|
|
147
|
+
x: (transformedPoint.x - transformedCenter.x) / rx,
|
|
148
|
+
y: (transformedPoint.y - transformedCenter.y) / ry,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const startAngle = angleBetween({ x: 1, y: 0 }, startVector);
|
|
152
|
+
|
|
153
|
+
const endVector = {
|
|
154
|
+
x: (-transformedPoint.x - transformedCenter.x) / rx,
|
|
155
|
+
y: (-transformedPoint.y - transformedCenter.y) / ry,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
let sweepAngle = angleBetween(startVector, endVector);
|
|
159
|
+
if (!SF && sweepAngle > 0) {
|
|
160
|
+
sweepAngle -= 2 * PI;
|
|
161
|
+
} else if (SF && sweepAngle < 0) {
|
|
162
|
+
sweepAngle += 2 * PI;
|
|
163
|
+
}
|
|
164
|
+
sweepAngle %= 2 * PI;
|
|
165
|
+
|
|
166
|
+
const endAngle = startAngle + sweepAngle;
|
|
167
|
+
|
|
168
|
+
// to be used later
|
|
169
|
+
// point.ellipticalArcStartAngle = startAngle;
|
|
170
|
+
// point.ellipticalArcEndAngle = startAngle + sweepAngle;
|
|
171
|
+
// point.ellipticalArcAngle = alpha;
|
|
172
|
+
|
|
173
|
+
// point.ellipticalArcCenter = center;
|
|
174
|
+
// point.resultantRx = rx;
|
|
175
|
+
// point.resultantRy = ry;
|
|
176
|
+
// point.length = ellipticalArcLength(rx, ry, sweepAngle);
|
|
177
|
+
// point.box = minmax(center.x, center.y, rx, ry, xRotRad, startAngle, startAngle + sweepAngle);
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
center,
|
|
181
|
+
startAngle,
|
|
182
|
+
endAngle,
|
|
183
|
+
rx,
|
|
184
|
+
ry,
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Returns the length of an Arc segment.
|
|
190
|
+
*
|
|
191
|
+
* @param x1 the starting point X
|
|
192
|
+
* @param y1 the starting point Y
|
|
193
|
+
* @param c1x the first control point X
|
|
194
|
+
* @param c1y the first control point Y
|
|
195
|
+
* @param c2x the second control point X
|
|
196
|
+
* @param c2y the second control point Y
|
|
197
|
+
* @param x2 the ending point X
|
|
198
|
+
* @param y2 the ending point Y
|
|
199
|
+
* @returns the length of the Arc segment
|
|
200
|
+
*/
|
|
201
|
+
export const getArcLength = (
|
|
202
|
+
x1: number,
|
|
203
|
+
y1: number,
|
|
204
|
+
RX: number,
|
|
205
|
+
RY: number,
|
|
206
|
+
angle: number,
|
|
207
|
+
LAF: number,
|
|
208
|
+
SF: number,
|
|
209
|
+
x: number,
|
|
210
|
+
y: number,
|
|
211
|
+
) => {
|
|
212
|
+
const { rx, ry, startAngle, endAngle } = getArcProps(x1, y1, RX, RY, angle, LAF, SF, x, y);
|
|
213
|
+
return ellipticalArcLength(rx, ry, endAngle - startAngle);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Returns a point along an Arc segment at a given distance.
|
|
218
|
+
*
|
|
219
|
+
* @param x1 the starting point X
|
|
220
|
+
* @param y1 the starting point Y
|
|
221
|
+
* @param RX the radius on X axis
|
|
222
|
+
* @param RY the radius on Y axis
|
|
223
|
+
* @param angle the ellipse rotation in degrees
|
|
224
|
+
* @param LAF the large arc flag
|
|
225
|
+
* @param SF the sweep flag
|
|
226
|
+
* @param x2 the ending point X
|
|
227
|
+
* @param y2 the ending point Y
|
|
228
|
+
* @param distance a [0-1] ratio
|
|
229
|
+
* @returns a point along the Arc segment
|
|
230
|
+
*/
|
|
231
|
+
export const getPointAtArcLength = (
|
|
232
|
+
x1: number,
|
|
233
|
+
y1: number,
|
|
234
|
+
RX: number,
|
|
235
|
+
RY: number,
|
|
236
|
+
angle: number,
|
|
237
|
+
LAF: number,
|
|
238
|
+
SF: number,
|
|
239
|
+
x: number,
|
|
240
|
+
y: number,
|
|
241
|
+
distance?: number,
|
|
242
|
+
) => {
|
|
243
|
+
let point = { x: x1, y: y1 };
|
|
244
|
+
const { center, rx, ry, startAngle, endAngle } = getArcProps(x1, y1, RX, RY, angle, LAF, SF, x, y);
|
|
245
|
+
const length = ellipticalArcLength(rx, ry, endAngle - startAngle);
|
|
246
|
+
|
|
247
|
+
/* istanbul ignore else @preserve */
|
|
248
|
+
if (typeof distance === 'number') {
|
|
249
|
+
if (distance <= 0) {
|
|
250
|
+
point = { x: x1, y: y1 };
|
|
251
|
+
} else if (distance >= length) {
|
|
252
|
+
point = { x, y };
|
|
253
|
+
} else {
|
|
254
|
+
/* istanbul ignore next @preserve */
|
|
255
|
+
if (x1 === x && y1 === y) {
|
|
256
|
+
return { x, y };
|
|
257
|
+
}
|
|
258
|
+
/* istanbul ignore next @preserve */
|
|
259
|
+
if (rx === 0 || ry === 0) {
|
|
260
|
+
return getPointAtLineLength(x1, y1, x, y, distance);
|
|
261
|
+
}
|
|
262
|
+
const { PI, cos, sin } = Math;
|
|
263
|
+
const sweepAngle = endAngle - startAngle;
|
|
264
|
+
const xRot = ((angle % 360) + 360) % 360;
|
|
265
|
+
const xRotRad = xRot * (PI / 180);
|
|
266
|
+
const alpha = startAngle + sweepAngle * (distance / length);
|
|
267
|
+
const ellipseComponentX = rx * cos(alpha);
|
|
268
|
+
const ellipseComponentY = ry * sin(alpha);
|
|
269
|
+
|
|
270
|
+
point = {
|
|
271
|
+
x: cos(xRotRad) * ellipseComponentX - sin(xRotRad) * ellipseComponentY + center.x,
|
|
272
|
+
y: sin(xRotRad) * ellipseComponentX + cos(xRotRad) * ellipseComponentY + center.y,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return point;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Returns the bounding box for an Arc segment.
|
|
282
|
+
* @see https://github.com/herrstrietzel/svg-pathdata-getbbox
|
|
283
|
+
*
|
|
284
|
+
* @param x1 the starting point X
|
|
285
|
+
* @param y1 the starting point Y
|
|
286
|
+
* @param RX the radius on X axis
|
|
287
|
+
* @param RY the radius on Y axis
|
|
288
|
+
* @param angle the ellipse rotation in degrees
|
|
289
|
+
* @param LAF the large arc flag
|
|
290
|
+
* @param SF the sweep flag
|
|
291
|
+
* @param x2 the ending point X
|
|
292
|
+
* @param y2 the ending point Y
|
|
293
|
+
* @returns the extrema of the Arc segment
|
|
294
|
+
*
|
|
295
|
+
*/
|
|
296
|
+
export const getArcBBox = (
|
|
297
|
+
x1: number,
|
|
298
|
+
y1: number,
|
|
299
|
+
RX: number,
|
|
300
|
+
RY: number,
|
|
301
|
+
angle: number,
|
|
302
|
+
LAF: number,
|
|
303
|
+
SF: number,
|
|
304
|
+
x: number,
|
|
305
|
+
y: number,
|
|
306
|
+
) => {
|
|
307
|
+
const { center, rx, ry, startAngle, endAngle } = getArcProps(x1, y1, RX, RY, angle, LAF, SF, x, y);
|
|
308
|
+
const deltaAngle = endAngle - startAngle;
|
|
309
|
+
|
|
310
|
+
// final on path point
|
|
311
|
+
const p = { x, y };
|
|
312
|
+
|
|
313
|
+
// circle/elipse center coordinates
|
|
314
|
+
const [cx, cy] = [center.x, center.y];
|
|
315
|
+
|
|
316
|
+
// collect extreme points – add end point
|
|
317
|
+
const extremes = [p];
|
|
318
|
+
|
|
319
|
+
// rotation to radians
|
|
320
|
+
const alpha = (angle * Math.PI) / 180;
|
|
321
|
+
const tan = Math.tan(alpha);
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* find min/max from zeroes of directional derivative along x and y
|
|
325
|
+
* along x axis
|
|
326
|
+
*/
|
|
327
|
+
const theta = Math.atan2(-ry * tan, rx);
|
|
328
|
+
const angle1 = theta;
|
|
329
|
+
const angle2 = theta + Math.PI;
|
|
330
|
+
const angle3 = Math.atan2(ry, rx * tan);
|
|
331
|
+
const angle4 = angle3 + Math.PI;
|
|
332
|
+
|
|
333
|
+
// inner bounding box
|
|
334
|
+
const xArr = [x1, x];
|
|
335
|
+
const yArr = [y1, y];
|
|
336
|
+
const xMin = Math.min(...xArr);
|
|
337
|
+
const xMax = Math.max(...xArr);
|
|
338
|
+
const yMin = Math.min(...yArr);
|
|
339
|
+
const yMax = Math.max(...yArr);
|
|
340
|
+
|
|
341
|
+
// on path point close after start
|
|
342
|
+
const angleAfterStart = endAngle - deltaAngle * 0.001;
|
|
343
|
+
const pP2 = arc(angleAfterStart, cx, cy, rx, ry, alpha);
|
|
344
|
+
|
|
345
|
+
// on path point close before end
|
|
346
|
+
const angleBeforeEnd = endAngle - deltaAngle * 0.999;
|
|
347
|
+
const pP3 = arc(angleBeforeEnd, cx, cy, rx, ry, alpha);
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* expected extremes
|
|
351
|
+
* if leaving inner bounding box
|
|
352
|
+
* (between segment start and end point)
|
|
353
|
+
* otherwise exclude elliptic extreme points
|
|
354
|
+
*/
|
|
355
|
+
|
|
356
|
+
// right
|
|
357
|
+
// istanbul ignore if @preserve
|
|
358
|
+
if (pP2.x > xMax || pP3.x > xMax) {
|
|
359
|
+
// get point for this theta
|
|
360
|
+
extremes.push(arc(angle1, cx, cy, rx, ry, alpha));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// left
|
|
364
|
+
// istanbul ignore if @preserve
|
|
365
|
+
if (pP2.x < xMin || pP3.x < xMin) {
|
|
366
|
+
// get anti-symmetric point
|
|
367
|
+
extremes.push(arc(angle2, cx, cy, rx, ry, alpha));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// top
|
|
371
|
+
// istanbul ignore if @preserve
|
|
372
|
+
if (pP2.y < yMin || pP3.y < yMin) {
|
|
373
|
+
// get anti-symmetric point
|
|
374
|
+
extremes.push(arc(angle4, cx, cy, rx, ry, alpha));
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// bottom
|
|
378
|
+
// istanbul ignore if @preserve
|
|
379
|
+
if (pP2.y > yMax || pP3.y > yMax) {
|
|
380
|
+
// get point for this theta
|
|
381
|
+
extremes.push(arc(angle3, cx, cy, rx, ry, alpha));
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
min: {
|
|
386
|
+
x: Math.min(...extremes.map(n => n.x)),
|
|
387
|
+
y: Math.min(...extremes.map(n => n.y)),
|
|
388
|
+
},
|
|
389
|
+
max: {
|
|
390
|
+
x: Math.max(...extremes.map(n => n.x)),
|
|
391
|
+
y: Math.max(...extremes.map(n => n.y)),
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
};
|