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
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PointTuple,
|
|
3
|
+
DerivedPoint,
|
|
4
|
+
QuadPoints,
|
|
5
|
+
CubicPoints,
|
|
6
|
+
DerivedQuadPoints,
|
|
7
|
+
DerivedCubicPoints,
|
|
8
|
+
QuadCoordinates,
|
|
9
|
+
CubicCoordinates,
|
|
10
|
+
DeriveCallback,
|
|
11
|
+
} from '../types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Tools from bezier.js by Mike 'Pomax' Kamermans
|
|
15
|
+
* @see https://github.com/Pomax/bezierjs
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const ZERO = { x: 0, y: 0 };
|
|
19
|
+
|
|
20
|
+
const Tvalues = [
|
|
21
|
+
-0.0640568928626056260850430826247450385909, 0.0640568928626056260850430826247450385909,
|
|
22
|
+
-0.1911188674736163091586398207570696318404, 0.1911188674736163091586398207570696318404,
|
|
23
|
+
-0.3150426796961633743867932913198102407864, 0.3150426796961633743867932913198102407864,
|
|
24
|
+
-0.4337935076260451384870842319133497124524, 0.4337935076260451384870842319133497124524,
|
|
25
|
+
-0.5454214713888395356583756172183723700107, 0.5454214713888395356583756172183723700107,
|
|
26
|
+
-0.6480936519369755692524957869107476266696, 0.6480936519369755692524957869107476266696,
|
|
27
|
+
-0.7401241915785543642438281030999784255232, 0.7401241915785543642438281030999784255232,
|
|
28
|
+
-0.8200019859739029219539498726697452080761, 0.8200019859739029219539498726697452080761,
|
|
29
|
+
-0.8864155270044010342131543419821967550873, 0.8864155270044010342131543419821967550873,
|
|
30
|
+
-0.9382745520027327585236490017087214496548, 0.9382745520027327585236490017087214496548,
|
|
31
|
+
-0.9747285559713094981983919930081690617411, 0.9747285559713094981983919930081690617411,
|
|
32
|
+
-0.9951872199970213601799974097007368118745, 0.9951872199970213601799974097007368118745,
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const Cvalues = [
|
|
36
|
+
0.1279381953467521569740561652246953718517, 0.1279381953467521569740561652246953718517,
|
|
37
|
+
0.1258374563468282961213753825111836887264, 0.1258374563468282961213753825111836887264,
|
|
38
|
+
0.121670472927803391204463153476262425607, 0.121670472927803391204463153476262425607,
|
|
39
|
+
0.1155056680537256013533444839067835598622, 0.1155056680537256013533444839067835598622,
|
|
40
|
+
0.1074442701159656347825773424466062227946, 0.1074442701159656347825773424466062227946,
|
|
41
|
+
0.0976186521041138882698806644642471544279, 0.0976186521041138882698806644642471544279,
|
|
42
|
+
0.086190161531953275917185202983742667185, 0.086190161531953275917185202983742667185,
|
|
43
|
+
0.0733464814110803057340336152531165181193, 0.0733464814110803057340336152531165181193,
|
|
44
|
+
0.0592985849154367807463677585001085845412, 0.0592985849154367807463677585001085845412,
|
|
45
|
+
0.0442774388174198061686027482113382288593, 0.0442774388174198061686027482113382288593,
|
|
46
|
+
0.0285313886289336631813078159518782864491, 0.0285313886289336631813078159518782864491,
|
|
47
|
+
0.0123412297999871995468056670700372915759, 0.0123412297999871995468056670700372915759,
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
*
|
|
52
|
+
* @param points
|
|
53
|
+
* @returns
|
|
54
|
+
*/
|
|
55
|
+
const derive = (points: QuadPoints | CubicPoints) => {
|
|
56
|
+
const dpoints = [] as (DerivedCubicPoints | DerivedQuadPoints)[];
|
|
57
|
+
for (let p = points, d = p.length, c = d - 1; d > 1; d -= 1, c -= 1) {
|
|
58
|
+
const list = [] as unknown as DerivedCubicPoints | DerivedQuadPoints;
|
|
59
|
+
for (let j = 0; j < c; j += 1) {
|
|
60
|
+
list.push({
|
|
61
|
+
x: c * (p[j + 1].x - p[j].x),
|
|
62
|
+
y: c * (p[j + 1].y - p[j].y),
|
|
63
|
+
t: 0,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
dpoints.push(list);
|
|
67
|
+
p = list;
|
|
68
|
+
}
|
|
69
|
+
return dpoints;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
*
|
|
74
|
+
* @param points
|
|
75
|
+
* @param t
|
|
76
|
+
*/
|
|
77
|
+
const compute = (points: DerivedQuadPoints | DerivedCubicPoints, t: number) => {
|
|
78
|
+
// shortcuts
|
|
79
|
+
/* istanbul ignore next @preserve */
|
|
80
|
+
if (t === 0) {
|
|
81
|
+
points[0].t = 0;
|
|
82
|
+
return points[0];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const order = points.length - 1;
|
|
86
|
+
|
|
87
|
+
/* istanbul ignore next @preserve */
|
|
88
|
+
if (t === 1) {
|
|
89
|
+
points[order].t = 1;
|
|
90
|
+
return points[order];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const mt = 1 - t;
|
|
94
|
+
let p = points as typeof points | [DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint];
|
|
95
|
+
|
|
96
|
+
// constant?
|
|
97
|
+
/* istanbul ignore next @preserve */
|
|
98
|
+
if (order === 0) {
|
|
99
|
+
points[0].t = t;
|
|
100
|
+
return points[0];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// linear?
|
|
104
|
+
/* istanbul ignore else @preserve */
|
|
105
|
+
if (order === 1) {
|
|
106
|
+
return {
|
|
107
|
+
x: mt * p[0].x + t * p[1].x,
|
|
108
|
+
y: mt * p[0].y + t * p[1].y,
|
|
109
|
+
t,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// quadratic/cubic curve?
|
|
114
|
+
const mt2 = mt * mt;
|
|
115
|
+
const t2 = t * t;
|
|
116
|
+
let a = 0;
|
|
117
|
+
let b = 0;
|
|
118
|
+
let c = 0;
|
|
119
|
+
let d = 0;
|
|
120
|
+
/* istanbul ignore else @preserve */
|
|
121
|
+
if (order === 2) {
|
|
122
|
+
p = [p[0], p[1], p[2], ZERO as DerivedPoint];
|
|
123
|
+
a = mt2;
|
|
124
|
+
b = mt * t * 2;
|
|
125
|
+
c = t2;
|
|
126
|
+
} else if (order === 3) {
|
|
127
|
+
a = mt2 * mt;
|
|
128
|
+
b = mt2 * t * 3;
|
|
129
|
+
c = mt * t2 * 3;
|
|
130
|
+
d = t * t2;
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
x: a * p[0].x + b * p[1].x + c * p[2].x + d * p[3].x,
|
|
134
|
+
y: a * p[0].y + b * p[1].y + c * p[2].y + d * p[3].y,
|
|
135
|
+
t,
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const arcfn = (derivativeFn: DeriveCallback, t: number) => {
|
|
140
|
+
const d = derivativeFn(t);
|
|
141
|
+
const l = d.x * d.x + d.y * d.y;
|
|
142
|
+
|
|
143
|
+
return Math.sqrt(l);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const lengthFn = (derivativeFn: DeriveCallback) => {
|
|
147
|
+
const z = 0.5;
|
|
148
|
+
const len = Tvalues.length;
|
|
149
|
+
|
|
150
|
+
let sum = 0;
|
|
151
|
+
|
|
152
|
+
for (let i = 0, t; i < len; i++) {
|
|
153
|
+
t = z * Tvalues[i] + z;
|
|
154
|
+
sum += Cvalues[i] * arcfn(derivativeFn, t);
|
|
155
|
+
}
|
|
156
|
+
return z * sum;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Returns the length of CubicBezier / Quad segment.
|
|
161
|
+
* @param curve cubic / quad bezier segment
|
|
162
|
+
*/
|
|
163
|
+
export const length = (curve: CubicCoordinates | QuadCoordinates) => {
|
|
164
|
+
const points = [] as unknown as CubicPoints | QuadPoints;
|
|
165
|
+
for (let idx = 0, len = curve.length, step = 2; idx < len; idx += step) {
|
|
166
|
+
points.push({
|
|
167
|
+
x: curve[idx],
|
|
168
|
+
y: curve[idx + 1],
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
const dpoints = derive(points);
|
|
172
|
+
return lengthFn((t: number) => {
|
|
173
|
+
return compute(dpoints[0], t);
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// Precision for consider cubic polynom as quadratic one
|
|
178
|
+
const CBEZIER_MINMAX_EPSILON = 0.00000001;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Returns the most extreme points in a Quad Bezier segment.
|
|
182
|
+
* @param A
|
|
183
|
+
*/
|
|
184
|
+
// https://github.com/kpym/SVGPathy/blob/acd1a50c626b36d81969f6e98e8602e128ba4302/lib/box.js#L89
|
|
185
|
+
export const minmaxQ = (A: [number, number, number]) => {
|
|
186
|
+
const min = Math.min(A[0], A[2]);
|
|
187
|
+
const max = Math.max(A[0], A[2]);
|
|
188
|
+
|
|
189
|
+
/* istanbul ignore next @preserve */
|
|
190
|
+
if (A[1] >= A[0] ? A[2] >= A[1] : A[2] <= A[1]) {
|
|
191
|
+
// if no extremum in ]0,1[
|
|
192
|
+
return [min, max] as PointTuple;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// check if the extremum E is min or max
|
|
196
|
+
const E = (A[0] * A[2] - A[1] * A[1]) / (A[0] - 2 * A[1] + A[2]);
|
|
197
|
+
return (E < min ? [E, max] : [min, E]) as PointTuple;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Returns the most extreme points in a Cubic Bezier segment.
|
|
202
|
+
* @param A
|
|
203
|
+
* @see https://github.com/kpym/SVGPathy/blob/acd1a50c626b36d81969f6e98e8602e128ba4302/lib/box.js#L127
|
|
204
|
+
*/
|
|
205
|
+
export const minmaxC = (A: [number, number, number, number]) => {
|
|
206
|
+
const K = A[0] - 3 * A[1] + 3 * A[2] - A[3];
|
|
207
|
+
|
|
208
|
+
// if the polynomial is (almost) quadratic and not cubic
|
|
209
|
+
/* istanbul ignore next @preserve */
|
|
210
|
+
if (Math.abs(K) < CBEZIER_MINMAX_EPSILON) {
|
|
211
|
+
if (A[0] === A[3] && A[0] === A[1]) {
|
|
212
|
+
// no curve, point targeting same location
|
|
213
|
+
return [A[0], A[3]] as PointTuple;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return minmaxQ([A[0], -0.5 * A[0] + 1.5 * A[1], A[0] - 3 * A[1] + 3 * A[2]]);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// the reduced discriminant of the derivative
|
|
220
|
+
const T = -A[0] * A[2] + A[0] * A[3] - A[1] * A[2] - A[1] * A[3] + A[1] * A[1] + A[2] * A[2];
|
|
221
|
+
|
|
222
|
+
// if the polynomial is monotone in [0,1]
|
|
223
|
+
if (T <= 0) {
|
|
224
|
+
return [Math.min(A[0], A[3]), Math.max(A[0], A[3])] as PointTuple;
|
|
225
|
+
}
|
|
226
|
+
const S = Math.sqrt(T);
|
|
227
|
+
|
|
228
|
+
// potential extrema
|
|
229
|
+
let min = Math.min(A[0], A[3]);
|
|
230
|
+
let max = Math.max(A[0], A[3]);
|
|
231
|
+
|
|
232
|
+
const L = A[0] - 2 * A[1] + A[2];
|
|
233
|
+
// check local extrema
|
|
234
|
+
for (let R = (L + S) / K, i = 1; i <= 2; R = (L - S) / K, i++) {
|
|
235
|
+
// istanbul ignore next @preserve
|
|
236
|
+
if (R > 0 && R < 1) {
|
|
237
|
+
// if the extrema is for R in [0,1]
|
|
238
|
+
const Q =
|
|
239
|
+
A[0] * (1 - R) * (1 - R) * (1 - R) +
|
|
240
|
+
A[1] * 3 * (1 - R) * (1 - R) * R +
|
|
241
|
+
A[2] * 3 * (1 - R) * R * R +
|
|
242
|
+
A[3] * R * R * R;
|
|
243
|
+
if (Q < min) {
|
|
244
|
+
min = Q;
|
|
245
|
+
}
|
|
246
|
+
if (Q > max) {
|
|
247
|
+
max = Q;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return [min, max] as PointTuple;
|
|
253
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { length, minmaxC } from './bezier';
|
|
2
|
+
import { type CubicCoordinates } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns a {x,y} point at a given length of a CubicBezier segment.
|
|
6
|
+
*
|
|
7
|
+
* @param x1 the starting point X
|
|
8
|
+
* @param y1 the starting point Y
|
|
9
|
+
* @param c1x the first control point X
|
|
10
|
+
* @param c1y the first control point Y
|
|
11
|
+
* @param c2x the second control point X
|
|
12
|
+
* @param c2y the second control point Y
|
|
13
|
+
* @param x2 the ending point X
|
|
14
|
+
* @param y2 the ending point Y
|
|
15
|
+
* @param t a [0-1] ratio
|
|
16
|
+
* @returns the point at cubic-bezier segment length
|
|
17
|
+
*/
|
|
18
|
+
export const getPointAtCubicSegmentLength = ([x1, y1, c1x, c1y, c2x, c2y, x2, y2]: CubicCoordinates, t: number) => {
|
|
19
|
+
const t1 = 1 - t;
|
|
20
|
+
return {
|
|
21
|
+
x: t1 ** 3 * x1 + 3 * t1 ** 2 * t * c1x + 3 * t1 * t ** 2 * c2x + t ** 3 * x2,
|
|
22
|
+
y: t1 ** 3 * y1 + 3 * t1 ** 2 * t * c1y + 3 * t1 * t ** 2 * c2y + t ** 3 * y2,
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Returns the length of a CubicBezier segment.
|
|
28
|
+
*
|
|
29
|
+
* @param x1 the starting point X
|
|
30
|
+
* @param y1 the starting point Y
|
|
31
|
+
* @param c1x the first control point X
|
|
32
|
+
* @param c1y the first control point Y
|
|
33
|
+
* @param c2x the second control point X
|
|
34
|
+
* @param c2y the second control point Y
|
|
35
|
+
* @param x2 the ending point X
|
|
36
|
+
* @param y2 the ending point Y
|
|
37
|
+
* @returns the CubicBezier segment length
|
|
38
|
+
*/
|
|
39
|
+
export 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 length([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
|
+
export const getPointAtCubicLength = (
|
|
67
|
+
x1: number,
|
|
68
|
+
y1: number,
|
|
69
|
+
c1x: number,
|
|
70
|
+
c1y: number,
|
|
71
|
+
c2x: number,
|
|
72
|
+
c2y: number,
|
|
73
|
+
x2: number,
|
|
74
|
+
y2: number,
|
|
75
|
+
distance?: number,
|
|
76
|
+
) => {
|
|
77
|
+
const distanceIsNumber = typeof distance === 'number';
|
|
78
|
+
let point = { x: x1, y: y1 };
|
|
79
|
+
/* istanbul ignore else @preserve */
|
|
80
|
+
if (distanceIsNumber) {
|
|
81
|
+
const currentLength = length([x1, y1, c1x, c1y, c2x, c2y, x2, y2]);
|
|
82
|
+
if (distance <= 0) {
|
|
83
|
+
// first point already defined
|
|
84
|
+
} else if (distance >= currentLength) {
|
|
85
|
+
point = { x: x2, y: y2 };
|
|
86
|
+
} else {
|
|
87
|
+
point = getPointAtCubicSegmentLength([x1, y1, c1x, c1y, c2x, c2y, x2, y2], distance / currentLength);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return point;
|
|
91
|
+
};
|
|
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
|
+
export 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]);
|
|
118
|
+
return {
|
|
119
|
+
min: { x: cxMinMax[0], y: cyMinMax[0] },
|
|
120
|
+
max: { x: cxMinMax[1], y: cyMinMax[1] },
|
|
121
|
+
};
|
|
122
|
+
};
|
|
@@ -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): number => {
|
|
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
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import midPoint from './midPoint';
|
|
2
|
+
import distanceSquareRoot from './distanceSquareRoot';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns length for line segments (MoveTo, LineTo).
|
|
6
|
+
*
|
|
7
|
+
* @param x1 the starting point X
|
|
8
|
+
* @param y1 the starting point Y
|
|
9
|
+
* @param x2 the ending point X
|
|
10
|
+
* @param y2 the ending point Y
|
|
11
|
+
* @returns the line segment length
|
|
12
|
+
*/
|
|
13
|
+
export 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
|
+
export const getPointAtLineLength = (x1: number, y1: number, x2: number, y2: number, distance?: number) => {
|
|
28
|
+
const length = distanceSquareRoot([x1, y1], [x2, y2]);
|
|
29
|
+
let point = { x: x1, y: y1 };
|
|
30
|
+
|
|
31
|
+
/* istanbul ignore else @preserve */
|
|
32
|
+
if (typeof distance === 'number') {
|
|
33
|
+
if (distance <= 0) {
|
|
34
|
+
point = { x: x1, y: y1 };
|
|
35
|
+
} else if (distance >= length) {
|
|
36
|
+
point = { x: x2, y: y2 };
|
|
37
|
+
} else {
|
|
38
|
+
const [x, y] = midPoint([x1, y1], [x2, y2], distance / length);
|
|
39
|
+
point = { x, y };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return point;
|
|
43
|
+
};
|
|
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
|
+
export const getLineBBox = (x1: number, y1: number, x2: number, y2: number) => {
|
|
56
|
+
const { min, max } = Math;
|
|
57
|
+
return {
|
|
58
|
+
min: {
|
|
59
|
+
x: min(x1, x2),
|
|
60
|
+
y: min(y1, y2),
|
|
61
|
+
},
|
|
62
|
+
max: {
|
|
63
|
+
x: max(x1, x2),
|
|
64
|
+
y: max(y1, y2),
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
};
|
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];
|
package/src/math/polygonArea.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type PointTuple } from '../types';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* d3-polygon-area
|
|
3
5
|
* https://github.com/d3/d3-polygon
|
|
@@ -7,7 +9,7 @@
|
|
|
7
9
|
* @param polygon an array of coordinates
|
|
8
10
|
* @returns the polygon area
|
|
9
11
|
*/
|
|
10
|
-
const polygonArea = (polygon: [
|
|
12
|
+
const polygonArea = (polygon: PointTuple[]): number => {
|
|
11
13
|
const n = polygon.length;
|
|
12
14
|
let i = -1;
|
|
13
15
|
let a;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PointTuple } from '../types';
|
|
1
2
|
import distanceSquareRoot from './distanceSquareRoot';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -9,7 +10,7 @@ import distanceSquareRoot from './distanceSquareRoot';
|
|
|
9
10
|
* @param polygon an array of coordinates
|
|
10
11
|
* @returns {number} the polygon length
|
|
11
12
|
*/
|
|
12
|
-
const polygonLength = (polygon: [
|
|
13
|
+
const polygonLength = (polygon: PointTuple[]): number => {
|
|
13
14
|
return polygon.reduce((length, point, i) => {
|
|
14
15
|
if (i) {
|
|
15
16
|
return length + distanceSquareRoot(polygon[i - 1], point);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { length, minmaxQ } from './bezier';
|
|
2
|
+
import { type QuadCoordinates } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns the {x,y} coordinates of a point at a
|
|
6
|
+
* given length of a quadratic-bezier segment.
|
|
7
|
+
*
|
|
8
|
+
* @see https://github.com/substack/point-at-length
|
|
9
|
+
*
|
|
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
|
|
18
|
+
*/
|
|
19
|
+
const getPointAtQuadSegmentLength = ([x1, y1, cx, cy, x2, y2]: QuadCoordinates, t: number) => {
|
|
20
|
+
const t1 = 1 - t;
|
|
21
|
+
return {
|
|
22
|
+
x: t1 ** 2 * x1 + 2 * t1 * t * cx + t ** 2 * x2,
|
|
23
|
+
y: t1 ** 2 * y1 + 2 * t1 * t * cy + t ** 2 * y2,
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Returns the length of a QuadraticBezier segment.
|
|
29
|
+
*
|
|
30
|
+
* @param x1 the starting point X
|
|
31
|
+
* @param y1 the starting point Y
|
|
32
|
+
* @param cx the control point X
|
|
33
|
+
* @param cy the control point Y
|
|
34
|
+
* @param x2 the ending point X
|
|
35
|
+
* @param y2 the ending point Y
|
|
36
|
+
* @returns the QuadraticBezier segment length
|
|
37
|
+
*/
|
|
38
|
+
export const getQuadLength = (x1: number, y1: number, cx: number, cy: number, x2: number, y2: number) => {
|
|
39
|
+
return length([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
|
+
export const getPointAtQuadLength = (
|
|
55
|
+
x1: number,
|
|
56
|
+
y1: number,
|
|
57
|
+
cx: number,
|
|
58
|
+
cy: number,
|
|
59
|
+
x2: number,
|
|
60
|
+
y2: number,
|
|
61
|
+
distance?: number,
|
|
62
|
+
) => {
|
|
63
|
+
const distanceIsNumber = typeof distance === 'number';
|
|
64
|
+
let point = { x: x1, y: y1 };
|
|
65
|
+
|
|
66
|
+
/* istanbul ignore else @preserve */
|
|
67
|
+
if (distanceIsNumber) {
|
|
68
|
+
const currentLength = length([x1, y1, cx, cy, x2, y2]);
|
|
69
|
+
if (distance <= 0) {
|
|
70
|
+
// first point already defined
|
|
71
|
+
} else if (distance >= currentLength) {
|
|
72
|
+
point = { x: x2, y: y2 };
|
|
73
|
+
} else {
|
|
74
|
+
point = getPointAtQuadSegmentLength([x1, y1, cx, cy, x2, y2], distance / currentLength);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return point;
|
|
78
|
+
};
|
|
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
|
+
export 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]);
|
|
94
|
+
return {
|
|
95
|
+
min: { x: cxMinMax[0], y: cyMinMax[0] },
|
|
96
|
+
max: { x: cxMinMax[1], y: cyMinMax[1] },
|
|
97
|
+
};
|
|
98
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if the character is a MoveTo command.
|
|
3
|
+
*
|
|
4
|
+
* @param code the character to check
|
|
5
|
+
* @returns check result
|
|
6
|
+
*/
|
|
7
|
+
const isMoveCommand = (code: number): code is 0x6d | 0x4d => {
|
|
8
|
+
// eslint-disable-next-line no-bitwise -- Impossible to satisfy
|
|
9
|
+
switch (code | 0x20) {
|
|
10
|
+
case 0x6d /* m */:
|
|
11
|
+
case 0x4d /* M */:
|
|
12
|
+
return true;
|
|
13
|
+
default:
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
export default isMoveCommand;
|
|
@@ -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,10 @@ 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 | PathArray)
|
|
15
|
-
if (
|
|
16
|
-
return
|
|
13
|
+
const parsePathString = (pathInput: string | PathArray) => {
|
|
14
|
+
if (typeof pathInput !== 'string') {
|
|
15
|
+
return pathInput.slice(0) as PathArray;
|
|
16
|
+
// return pathInput;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const path = new PathParser(pathInput);
|
|
@@ -24,7 +24,7 @@ const parsePathString = (pathInput: string | PathArray): PathArray => {
|
|
|
24
24
|
scanSegment(path);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
if (path
|
|
27
|
+
if (path?.err.length) {
|
|
28
28
|
throw TypeError(path.err);
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -6,11 +6,12 @@ import skipSpaces from './skipSpaces';
|
|
|
6
6
|
import isPathCommand from './isPathCommand';
|
|
7
7
|
import isDigitStart from './isDigitStart';
|
|
8
8
|
import isArcCommand from './isArcCommand';
|
|
9
|
+
import isMoveCommand from './isMoveCommand';
|
|
9
10
|
import invalidPathValue from './invalidPathValue';
|
|
10
11
|
import error from './error';
|
|
11
12
|
|
|
12
13
|
import type PathParser from './pathParser';
|
|
13
|
-
import { RelativeCommand } from '../types';
|
|
14
|
+
import type { PathSegment, RelativeCommand } from '../types';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Scans every character in the path string to determine
|
|
@@ -19,14 +20,22 @@ import { RelativeCommand } from '../types';
|
|
|
19
20
|
* @param path the `PathParser` instance
|
|
20
21
|
*/
|
|
21
22
|
const scanSegment = (path: PathParser) => {
|
|
22
|
-
const { max, pathValue, index } = path;
|
|
23
|
+
const { max, pathValue, index, segments } = path;
|
|
23
24
|
const cmdCode = pathValue.charCodeAt(index);
|
|
24
25
|
const reqParams = paramCounts[pathValue[index].toLowerCase() as RelativeCommand];
|
|
25
26
|
|
|
26
27
|
path.segmentStart = index;
|
|
27
28
|
|
|
29
|
+
// segments always start with a path command
|
|
28
30
|
if (!isPathCommand(cmdCode)) {
|
|
29
|
-
path.err = `${error}: ${invalidPathValue} "${pathValue[index]}" is not a path command`;
|
|
31
|
+
path.err = `${error}: ${invalidPathValue} "${pathValue[index]}" is not a path command at index ${index}`;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// after a Z segment, we only expect a MoveTo path command
|
|
36
|
+
const lastSegment = segments[segments.length - 1] as PathSegment | undefined;
|
|
37
|
+
if (!isMoveCommand(cmdCode) && lastSegment?.[0]?.toLocaleLowerCase() === 'z') {
|
|
38
|
+
path.err = `${error}: ${invalidPathValue} "${pathValue[index]}" is not a MoveTo path command at index ${index}`;
|
|
30
39
|
return;
|
|
31
40
|
}
|
|
32
41
|
|