svg-path-commander 2.0.10 → 2.1.0
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 +4 -4
- package/dist/svg-path-commander.cjs +1 -1
- package/dist/svg-path-commander.cjs.map +1 -1
- package/dist/svg-path-commander.d.ts +137 -30
- package/dist/svg-path-commander.js +1 -1
- package/dist/svg-path-commander.js.map +1 -1
- package/dist/svg-path-commander.mjs +868 -698
- package/dist/svg-path-commander.mjs.map +1 -1
- package/package.json +20 -22
- package/src/convert/pathToAbsolute.ts +1 -1
- package/src/convert/pathToCurve.ts +1 -1
- package/src/convert/pathToRelative.ts +1 -1
- package/src/index.ts +30 -26
- package/src/interface.ts +32 -32
- package/src/math/arcTools.ts +217 -0
- package/src/math/bezier.ts +261 -0
- package/src/math/cubicTools.ts +81 -0
- package/src/math/lineTools.ts +52 -0
- package/src/math/quadTools.ts +79 -0
- package/src/parser/isMoveCommand.ts +17 -0
- package/src/parser/parsePathString.ts +1 -1
- package/src/parser/scanSegment.ts +12 -3
- package/src/process/normalizePath.ts +1 -1
- package/src/process/replaceArc.ts +52 -0
- package/src/process/splitPath.ts +1 -1
- package/src/process/transformPath.ts +14 -34
- package/src/types.ts +5 -0
- package/src/util/distanceEpsilon.ts +3 -0
- package/src/util/getClosestPoint.ts +1 -1
- package/src/util/getPathBBox.ts +4 -3
- package/src/util/getPointAtLength.ts +3 -3
- package/src/util/getPropertiesAtLength.ts +2 -1
- package/src/util/getPropertiesAtPoint.ts +4 -1
- package/src/util/getTotalLength.ts +2 -2
- package/src/util/isPointInStroke.ts +2 -1
- package/src/util/pathFactory.ts +130 -0
- package/src/util/shapeToPathArray.ts +8 -4
- package/test/class.test.ts +501 -0
- package/test/fixtures/getMarkup.ts +17 -0
- package/{cypress → test}/fixtures/shapes.js +18 -18
- package/{cypress → test}/fixtures/simpleShapes.js +6 -6
- package/test/static.test.ts +304 -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/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/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,261 @@
|
|
|
1
|
+
import { type Point } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tools from bezier.js by Mike 'Pomax' Kamermans
|
|
5
|
+
* @see https://github.com/Pomax/bezierjs
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const ZERO = { x: 0, y: 0 };
|
|
9
|
+
|
|
10
|
+
const Tvalues = [
|
|
11
|
+
-0.0640568928626056260850430826247450385909, 0.0640568928626056260850430826247450385909,
|
|
12
|
+
-0.1911188674736163091586398207570696318404, 0.1911188674736163091586398207570696318404,
|
|
13
|
+
-0.3150426796961633743867932913198102407864, 0.3150426796961633743867932913198102407864,
|
|
14
|
+
-0.4337935076260451384870842319133497124524, 0.4337935076260451384870842319133497124524,
|
|
15
|
+
-0.5454214713888395356583756172183723700107, 0.5454214713888395356583756172183723700107,
|
|
16
|
+
-0.6480936519369755692524957869107476266696, 0.6480936519369755692524957869107476266696,
|
|
17
|
+
-0.7401241915785543642438281030999784255232, 0.7401241915785543642438281030999784255232,
|
|
18
|
+
-0.8200019859739029219539498726697452080761, 0.8200019859739029219539498726697452080761,
|
|
19
|
+
-0.8864155270044010342131543419821967550873, 0.8864155270044010342131543419821967550873,
|
|
20
|
+
-0.9382745520027327585236490017087214496548, 0.9382745520027327585236490017087214496548,
|
|
21
|
+
-0.9747285559713094981983919930081690617411, 0.9747285559713094981983919930081690617411,
|
|
22
|
+
-0.9951872199970213601799974097007368118745, 0.9951872199970213601799974097007368118745,
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const Cvalues = [
|
|
26
|
+
0.1279381953467521569740561652246953718517, 0.1279381953467521569740561652246953718517,
|
|
27
|
+
0.1258374563468282961213753825111836887264, 0.1258374563468282961213753825111836887264,
|
|
28
|
+
0.121670472927803391204463153476262425607, 0.121670472927803391204463153476262425607,
|
|
29
|
+
0.1155056680537256013533444839067835598622, 0.1155056680537256013533444839067835598622,
|
|
30
|
+
0.1074442701159656347825773424466062227946, 0.1074442701159656347825773424466062227946,
|
|
31
|
+
0.0976186521041138882698806644642471544279, 0.0976186521041138882698806644642471544279,
|
|
32
|
+
0.086190161531953275917185202983742667185, 0.086190161531953275917185202983742667185,
|
|
33
|
+
0.0733464814110803057340336152531165181193, 0.0733464814110803057340336152531165181193,
|
|
34
|
+
0.0592985849154367807463677585001085845412, 0.0592985849154367807463677585001085845412,
|
|
35
|
+
0.0442774388174198061686027482113382288593, 0.0442774388174198061686027482113382288593,
|
|
36
|
+
0.0285313886289336631813078159518782864491, 0.0285313886289336631813078159518782864491,
|
|
37
|
+
0.0123412297999871995468056670700372915759, 0.0123412297999871995468056670700372915759,
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
type DerivedPoint = Point & { t: number };
|
|
41
|
+
type QuadPoints = [Point, Point, Point, Point, Point, Point];
|
|
42
|
+
type CubicPoints = [Point, Point, Point, Point, Point, Point, Point, Point];
|
|
43
|
+
type DerivedQuadPoints = [DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint];
|
|
44
|
+
type DerivedCubicPoints = [
|
|
45
|
+
DerivedPoint,
|
|
46
|
+
DerivedPoint,
|
|
47
|
+
DerivedPoint,
|
|
48
|
+
DerivedPoint,
|
|
49
|
+
DerivedPoint,
|
|
50
|
+
DerivedPoint,
|
|
51
|
+
DerivedPoint,
|
|
52
|
+
DerivedPoint,
|
|
53
|
+
];
|
|
54
|
+
export type QuadCoordinates = [number, number, number, number, number, number];
|
|
55
|
+
export type CubicCoordinates = [number, number, number, number, number, number, number, number];
|
|
56
|
+
|
|
57
|
+
type DeriveCallback = (t: number) => Point;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
*
|
|
61
|
+
* @param points
|
|
62
|
+
* @returns
|
|
63
|
+
*/
|
|
64
|
+
const derive = (points: QuadPoints | CubicPoints) => {
|
|
65
|
+
const dpoints = [] as (DerivedCubicPoints | DerivedQuadPoints)[];
|
|
66
|
+
for (let p = points, d = p.length, c = d - 1; d > 1; d -= 1, c -= 1) {
|
|
67
|
+
const list = [] as unknown as DerivedCubicPoints | DerivedQuadPoints;
|
|
68
|
+
for (let j = 0; j < c; j += 1) {
|
|
69
|
+
list.push({
|
|
70
|
+
x: c * (p[j + 1].x - p[j].x),
|
|
71
|
+
y: c * (p[j + 1].y - p[j].y),
|
|
72
|
+
t: 0,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
dpoints.push(list);
|
|
76
|
+
p = list;
|
|
77
|
+
}
|
|
78
|
+
return dpoints;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
*
|
|
83
|
+
* @param points
|
|
84
|
+
* @param t
|
|
85
|
+
*/
|
|
86
|
+
const compute = (points: DerivedQuadPoints | DerivedCubicPoints, t: number) => {
|
|
87
|
+
// shortcuts
|
|
88
|
+
/* istanbul ignore next @preserve */
|
|
89
|
+
if (t === 0) {
|
|
90
|
+
points[0].t = 0;
|
|
91
|
+
return points[0];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const order = points.length - 1;
|
|
95
|
+
|
|
96
|
+
/* istanbul ignore next @preserve */
|
|
97
|
+
if (t === 1) {
|
|
98
|
+
points[order].t = 1;
|
|
99
|
+
return points[order];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const mt = 1 - t;
|
|
103
|
+
let p = points as typeof points | [DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint];
|
|
104
|
+
|
|
105
|
+
// constant?
|
|
106
|
+
/* istanbul ignore next @preserve */
|
|
107
|
+
if (order === 0) {
|
|
108
|
+
points[0].t = t;
|
|
109
|
+
return points[0];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// linear?
|
|
113
|
+
/* istanbul ignore else @preserve */
|
|
114
|
+
if (order === 1) {
|
|
115
|
+
return {
|
|
116
|
+
x: mt * p[0].x + t * p[1].x,
|
|
117
|
+
y: mt * p[0].y + t * p[1].y,
|
|
118
|
+
t,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// quadratic/cubic curve?
|
|
123
|
+
const mt2 = mt * mt;
|
|
124
|
+
const t2 = t * t;
|
|
125
|
+
let a = 0;
|
|
126
|
+
let b = 0;
|
|
127
|
+
let c = 0;
|
|
128
|
+
let d = 0;
|
|
129
|
+
/* istanbul ignore else @preserve */
|
|
130
|
+
if (order === 2) {
|
|
131
|
+
p = [p[0], p[1], p[2], ZERO as DerivedPoint];
|
|
132
|
+
a = mt2;
|
|
133
|
+
b = mt * t * 2;
|
|
134
|
+
c = t2;
|
|
135
|
+
} else if (order === 3) {
|
|
136
|
+
a = mt2 * mt;
|
|
137
|
+
b = mt2 * t * 3;
|
|
138
|
+
c = mt * t2 * 3;
|
|
139
|
+
d = t * t2;
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
x: a * p[0].x + b * p[1].x + c * p[2].x + d * p[3].x,
|
|
143
|
+
y: a * p[0].y + b * p[1].y + c * p[2].y + d * p[3].y,
|
|
144
|
+
t,
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const arcfn = (derivativeFn: DeriveCallback, t: number) => {
|
|
149
|
+
const d = derivativeFn(t);
|
|
150
|
+
const l = d.x * d.x + d.y * d.y;
|
|
151
|
+
|
|
152
|
+
return Math.sqrt(l);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const lengthFn = (derivativeFn: DeriveCallback) => {
|
|
156
|
+
const z = 0.5;
|
|
157
|
+
const len = Tvalues.length;
|
|
158
|
+
|
|
159
|
+
let sum = 0;
|
|
160
|
+
|
|
161
|
+
for (let i = 0, t; i < len; i++) {
|
|
162
|
+
t = z * Tvalues[i] + z;
|
|
163
|
+
sum += Cvalues[i] * arcfn(derivativeFn, t);
|
|
164
|
+
}
|
|
165
|
+
return z * sum;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Returns the length of CubicBezier / Quad segment.
|
|
170
|
+
* @param curve cubic / quad bezier segment
|
|
171
|
+
*/
|
|
172
|
+
export const length = (curve: CubicCoordinates | QuadCoordinates) => {
|
|
173
|
+
const points = [] as unknown as CubicPoints | QuadPoints;
|
|
174
|
+
for (let idx = 0, len = curve.length, step = 2; idx < len; idx += step) {
|
|
175
|
+
points.push({
|
|
176
|
+
x: curve[idx],
|
|
177
|
+
y: curve[idx + 1],
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
const dpoints = derive(points);
|
|
181
|
+
return lengthFn((t: number) => {
|
|
182
|
+
return compute(dpoints[0], t);
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Precision for consider cubic polynom as quadratic one
|
|
187
|
+
const CBEZIER_MINMAX_EPSILON = 0.00000001;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Returns the most extreme points in a Quad Bezier segment.
|
|
191
|
+
* @param A
|
|
192
|
+
*/
|
|
193
|
+
// https://github.com/kpym/SVGPathy/blob/acd1a50c626b36d81969f6e98e8602e128ba4302/lib/box.js#L89
|
|
194
|
+
export const minmaxQ = (A: [number, number, number]) => {
|
|
195
|
+
const min = Math.min(A[0], A[2]);
|
|
196
|
+
const max = Math.max(A[0], A[2]);
|
|
197
|
+
|
|
198
|
+
/* istanbul ignore else @preserve */
|
|
199
|
+
if (A[1] >= A[0] ? A[2] >= A[1] : A[2] <= A[1]) {
|
|
200
|
+
// if no extremum in ]0,1[
|
|
201
|
+
return [min, max] as [number, number];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// check if the extremum E is min or max
|
|
205
|
+
const E = (A[0] * A[2] - A[1] * A[1]) / (A[0] - 2 * A[1] + A[2]);
|
|
206
|
+
return (E < min ? [E, max] : [min, E]) as [number, number];
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Returns the most extreme points in a Cubic Bezier segment.
|
|
211
|
+
* @param A
|
|
212
|
+
* @see https://github.com/kpym/SVGPathy/blob/acd1a50c626b36d81969f6e98e8602e128ba4302/lib/box.js#L127
|
|
213
|
+
*/
|
|
214
|
+
export const minmaxC = (A: [number, number, number, number]) => {
|
|
215
|
+
const K = A[0] - 3 * A[1] + 3 * A[2] - A[3];
|
|
216
|
+
|
|
217
|
+
// if the polynomial is (almost) quadratic and not cubic
|
|
218
|
+
/* istanbul ignore else @preserve */
|
|
219
|
+
if (Math.abs(K) < CBEZIER_MINMAX_EPSILON) {
|
|
220
|
+
if (A[0] === A[3] && A[0] === A[1]) {
|
|
221
|
+
// no curve, point targeting same location
|
|
222
|
+
return [A[0], A[3]] as [number, number];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return minmaxQ([A[0], -0.5 * A[0] + 1.5 * A[1], A[0] - 3 * A[1] + 3 * A[2]]);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// the reduced discriminant of the derivative
|
|
229
|
+
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];
|
|
230
|
+
|
|
231
|
+
// if the polynomial is monotone in [0,1]
|
|
232
|
+
if (T <= 0) {
|
|
233
|
+
return [Math.min(A[0], A[3]), Math.max(A[0], A[3])] as [number, number];
|
|
234
|
+
}
|
|
235
|
+
const S = Math.sqrt(T);
|
|
236
|
+
|
|
237
|
+
// potential extrema
|
|
238
|
+
let min = Math.min(A[0], A[3]);
|
|
239
|
+
let max = Math.max(A[0], A[3]);
|
|
240
|
+
|
|
241
|
+
const L = A[0] - 2 * A[1] + A[2];
|
|
242
|
+
// check local extrema
|
|
243
|
+
for (let R = (L + S) / K, i = 1; i <= 2; R = (L - S) / K, i++) {
|
|
244
|
+
if (R > 0 && R < 1) {
|
|
245
|
+
// if the extrema is for R in [0,1]
|
|
246
|
+
const Q =
|
|
247
|
+
A[0] * (1 - R) * (1 - R) * (1 - R) +
|
|
248
|
+
A[1] * 3 * (1 - R) * (1 - R) * R +
|
|
249
|
+
A[2] * 3 * (1 - R) * R * R +
|
|
250
|
+
A[3] * R * R * R;
|
|
251
|
+
if (Q < min) {
|
|
252
|
+
min = Q;
|
|
253
|
+
}
|
|
254
|
+
if (Q > max) {
|
|
255
|
+
max = Q;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return [min, max] as [number, number];
|
|
261
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { length, minmaxC, type CubicCoordinates } from './bezier';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns a {x,y} point at a given length of a CubicBezier segment.
|
|
5
|
+
*
|
|
6
|
+
* @param x1 the starting point X
|
|
7
|
+
* @param y1 the starting point Y
|
|
8
|
+
* @param c1x the first control point X
|
|
9
|
+
* @param c1y the first control point Y
|
|
10
|
+
* @param c2x the second control point X
|
|
11
|
+
* @param c2y the second control point Y
|
|
12
|
+
* @param x2 the ending point X
|
|
13
|
+
* @param y2 the ending point Y
|
|
14
|
+
* @param t a [0-1] ratio
|
|
15
|
+
* @returns the point at cubic-bezier segment length
|
|
16
|
+
*/
|
|
17
|
+
const getPointAtCubicSegmentLength = ([x1, y1, c1x, c1y, c2x, c2y, x2, y2]: CubicCoordinates, t: number) => {
|
|
18
|
+
const t1 = 1 - t;
|
|
19
|
+
return {
|
|
20
|
+
x: t1 ** 3 * x1 + 3 * t1 ** 2 * t * c1x + 3 * t1 * t ** 2 * c2x + t ** 3 * x2,
|
|
21
|
+
y: t1 ** 3 * y1 + 3 * t1 ** 2 * t * c1y + 3 * t1 * t ** 2 * c2y + t ** 3 * y2,
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Returns the properties of a CubicBezier segment.
|
|
27
|
+
*
|
|
28
|
+
* @param x1 the starting point X
|
|
29
|
+
* @param y1 the starting point Y
|
|
30
|
+
* @param c1x the first control point X
|
|
31
|
+
* @param c1y the first control point Y
|
|
32
|
+
* @param c2x the second control point X
|
|
33
|
+
* @param c2y the second control point Y
|
|
34
|
+
* @param x2 the ending point X
|
|
35
|
+
* @param y2 the ending point Y
|
|
36
|
+
* @param distance the point distance
|
|
37
|
+
* @returns the segment length, point at length and the bounding box
|
|
38
|
+
*/
|
|
39
|
+
const getSegmentProperties = (
|
|
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
|
+
distance?: number,
|
|
49
|
+
) => {
|
|
50
|
+
const distanceIsNumber = typeof distance === 'number';
|
|
51
|
+
let POINT = { x: x1, y: y1 };
|
|
52
|
+
const getLength = () => length([x1, y1, c1x, c1y, c2x, c2y, x2, y2]);
|
|
53
|
+
|
|
54
|
+
if (distanceIsNumber) {
|
|
55
|
+
const currentLength = getLength();
|
|
56
|
+
if (distance <= 0) {
|
|
57
|
+
// first point already defined
|
|
58
|
+
} else if (distance >= currentLength) {
|
|
59
|
+
POINT = { x: x2, y: y2 };
|
|
60
|
+
} else {
|
|
61
|
+
POINT = getPointAtCubicSegmentLength([x1, y1, c1x, c1y, c2x, c2y, x2, y2], distance / currentLength);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
point: POINT,
|
|
67
|
+
get length() {
|
|
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
|
+
},
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export default getSegmentProperties;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import midPoint from './midPoint';
|
|
2
|
+
import distanceSquareRoot from './distanceSquareRoot';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns properties 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
|
+
* @param distance the distance to point
|
|
12
|
+
* @returns the segment length, point at length and the bounding box
|
|
13
|
+
*/
|
|
14
|
+
const getSegmentProperties = (x1: number, y1: number, x2: number, y2: number, distance?: number) => {
|
|
15
|
+
const { min, max } = Math;
|
|
16
|
+
let point = { x: 0, y: 0 };
|
|
17
|
+
const length = () => distanceSquareRoot([x1, y1], [x2, y2]);
|
|
18
|
+
|
|
19
|
+
/* istanbul ignore else @preserve */
|
|
20
|
+
if (typeof distance === 'number') {
|
|
21
|
+
const currentLength = length();
|
|
22
|
+
if (distance <= 0) {
|
|
23
|
+
point = { x: x1, y: y1 };
|
|
24
|
+
} else if (distance >= currentLength) {
|
|
25
|
+
point = { x: x2, y: y2 };
|
|
26
|
+
} else {
|
|
27
|
+
const [x, y] = midPoint([x1, y1], [x2, y2], distance / currentLength);
|
|
28
|
+
point = { x, y };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
point,
|
|
34
|
+
get length() {
|
|
35
|
+
return length();
|
|
36
|
+
},
|
|
37
|
+
get bbox() {
|
|
38
|
+
return {
|
|
39
|
+
min: {
|
|
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
|
+
};
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default getSegmentProperties;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { length, minmaxQ, type QuadCoordinates } from './bezier';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns the {x,y} coordinates of a point at a
|
|
5
|
+
* given length of a quadratic-bezier segment.
|
|
6
|
+
*
|
|
7
|
+
* @see https://github.com/substack/point-at-length
|
|
8
|
+
*
|
|
9
|
+
* @param x1 the starting point X
|
|
10
|
+
* @param y1 the starting point Y
|
|
11
|
+
* @param cx the control point X
|
|
12
|
+
* @param cy the 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 requested {x,y} coordinates
|
|
17
|
+
*/
|
|
18
|
+
const getPointAtQuadSegmentLength = ([x1, y1, cx, cy, x2, y2]: QuadCoordinates, t: number) => {
|
|
19
|
+
const t1 = 1 - t;
|
|
20
|
+
return {
|
|
21
|
+
x: t1 ** 2 * x1 + 2 * t1 * t * cx + t ** 2 * x2,
|
|
22
|
+
y: t1 ** 2 * y1 + 2 * t1 * t * cy + t ** 2 * y2,
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Returns properties of a QuadraticBezier segment.
|
|
28
|
+
*
|
|
29
|
+
* @param x1 the starting point X
|
|
30
|
+
* @param y1 the starting point Y
|
|
31
|
+
* @param cx the control point X
|
|
32
|
+
* @param cy the control point Y
|
|
33
|
+
* @param x2 the ending point X
|
|
34
|
+
* @param y2 the ending point Y
|
|
35
|
+
* @param distance the point distance
|
|
36
|
+
* @returns the segment length, point at length and the bounding box
|
|
37
|
+
*/
|
|
38
|
+
const getSegmentProperties = (
|
|
39
|
+
x1: number,
|
|
40
|
+
y1: number,
|
|
41
|
+
cx: number,
|
|
42
|
+
cy: number,
|
|
43
|
+
x2: number,
|
|
44
|
+
y2: number,
|
|
45
|
+
distance: number | undefined,
|
|
46
|
+
) => {
|
|
47
|
+
const distanceIsNumber = typeof distance === 'number';
|
|
48
|
+
let POINT = { x: x1, y: y1 };
|
|
49
|
+
const getLength = () => length([x1, y1, cx, cy, x2, y2]);
|
|
50
|
+
|
|
51
|
+
if (distanceIsNumber) {
|
|
52
|
+
const currentLength = getLength();
|
|
53
|
+
|
|
54
|
+
if (distance <= 0) {
|
|
55
|
+
// first point already defined
|
|
56
|
+
} else if (distance >= currentLength) {
|
|
57
|
+
POINT = { x: x2, y: y2 };
|
|
58
|
+
} else {
|
|
59
|
+
POINT = getPointAtQuadSegmentLength([x1, y1, cx, cy, x2, y2], distance / currentLength);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
point: POINT,
|
|
65
|
+
get length() {
|
|
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
|
+
},
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export default getSegmentProperties;
|
|
@@ -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;
|
|
@@ -13,7 +13,7 @@ import type { PathArray } from '../types';
|
|
|
13
13
|
*/
|
|
14
14
|
const parsePathString = (pathInput: string | PathArray): PathArray => {
|
|
15
15
|
if (isPathArray(pathInput)) {
|
|
16
|
-
return
|
|
16
|
+
return pathInput.slice(0) as PathArray;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const path = new PathParser(pathInput);
|
|
@@ -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
|
|
|
@@ -14,7 +14,7 @@ import type { NormalArray, PathArray } from '../types';
|
|
|
14
14
|
*/
|
|
15
15
|
const normalizePath = (pathInput: string | PathArray): NormalArray => {
|
|
16
16
|
if (isNormalizedArray(pathInput)) {
|
|
17
|
-
return
|
|
17
|
+
return pathInput.slice(0) as NormalArray;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const path = pathToAbsolute(pathInput);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { PathArray, PathCommand, PathSegment } from '../types';
|
|
2
|
+
import isNormalizedArray from '../util/isNormalizedArray';
|
|
3
|
+
import segmentToCubic from './segmentToCubic';
|
|
4
|
+
import paramsParser from '../parser/paramsParser';
|
|
5
|
+
import normalizePath from './normalizePath';
|
|
6
|
+
import fixArc from './fixArc';
|
|
7
|
+
import isAbsoluteArray from '../util/isAbsoluteArray';
|
|
8
|
+
import pathToAbsolute from '../convert/pathToAbsolute';
|
|
9
|
+
|
|
10
|
+
const replaceArc = (pathInput: PathArray | string): PathArray => {
|
|
11
|
+
const absolutePath = isAbsoluteArray(pathInput) ? pathInput : pathToAbsolute(pathInput);
|
|
12
|
+
const normalizedPath = isNormalizedArray(absolutePath) ? absolutePath : normalizePath(absolutePath);
|
|
13
|
+
const params = { ...paramsParser };
|
|
14
|
+
const allPathCommands = [] as PathCommand[]; // needed for arc to curve transformation
|
|
15
|
+
let segment = [] as unknown as PathSegment;
|
|
16
|
+
let seglen = 0;
|
|
17
|
+
let pathCommand = '';
|
|
18
|
+
const resultedPath = [] as unknown as PathArray;
|
|
19
|
+
let i = 0;
|
|
20
|
+
let ii = absolutePath.length;
|
|
21
|
+
|
|
22
|
+
for (i = 0; i < ii; i += 1) {
|
|
23
|
+
/* istanbul ignore else @preserve */
|
|
24
|
+
if (absolutePath[i]) [pathCommand] = absolutePath[i];
|
|
25
|
+
allPathCommands[i] = pathCommand as PathCommand;
|
|
26
|
+
|
|
27
|
+
/* istanbul ignore else @preserve */
|
|
28
|
+
if (pathCommand === 'A') {
|
|
29
|
+
segment = segmentToCubic(normalizedPath[i], params);
|
|
30
|
+
|
|
31
|
+
absolutePath[i] = segmentToCubic(normalizedPath[i], params);
|
|
32
|
+
fixArc(absolutePath, allPathCommands, i);
|
|
33
|
+
|
|
34
|
+
normalizedPath[i] = segmentToCubic(normalizedPath[i], params);
|
|
35
|
+
fixArc(normalizedPath, allPathCommands, i);
|
|
36
|
+
ii = Math.max(absolutePath.length, normalizedPath.length);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
segment = normalizedPath[i];
|
|
40
|
+
seglen = segment.length;
|
|
41
|
+
|
|
42
|
+
params.x1 = +segment[seglen - 2];
|
|
43
|
+
params.y1 = +segment[seglen - 1];
|
|
44
|
+
params.x2 = +segment[seglen - 4] || params.x1;
|
|
45
|
+
params.y2 = +segment[seglen - 3] || params.y1;
|
|
46
|
+
|
|
47
|
+
resultedPath.push(absolutePath[i]);
|
|
48
|
+
}
|
|
49
|
+
return resultedPath;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default replaceArc;
|