svg-path-commander 2.1.1 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/svg-path-commander.cjs +1 -1
- package/dist/svg-path-commander.cjs.map +1 -1
- package/dist/svg-path-commander.d.ts +207 -19
- package/dist/svg-path-commander.js +1 -1
- package/dist/svg-path-commander.js.map +1 -1
- package/dist/svg-path-commander.mjs +1052 -969
- package/dist/svg-path-commander.mjs.map +1 -1
- package/package.json +7 -7
- package/src/convert/pathToAbsolute.ts +2 -31
- package/src/convert/pathToCurve.ts +13 -27
- package/src/convert/pathToRelative.ts +2 -47
- package/src/convert/pathToString.ts +40 -7
- package/src/index.ts +132 -39
- package/src/interface.ts +2 -1
- package/src/math/arcTools.ts +53 -51
- package/src/math/bezier.ts +41 -33
- package/src/math/cubicTools.ts +10 -8
- package/src/math/distanceSquareRoot.ts +1 -1
- package/src/math/lineTools.ts +6 -4
- package/src/math/polygonTools.ts +48 -0
- package/src/math/quadTools.ts +8 -6
- package/src/math/rotateVector.ts +3 -2
- package/src/math/roundTo.ts +7 -0
- package/src/parser/finalizeSegment.ts +11 -7
- package/src/parser/parsePathString.ts +2 -3
- package/src/parser/pathParser.ts +1 -1
- package/src/process/absolutizeSegment.ts +35 -30
- package/src/process/arcToCubic.ts +2 -2
- package/src/process/iterate.ts +41 -16
- package/src/process/lineToCubic.ts +1 -1
- package/src/process/normalizePath.ts +14 -31
- package/src/process/normalizeSegment.ts +53 -15
- package/src/process/optimizePath.ts +40 -60
- package/src/process/projection2d.ts +2 -2
- package/src/process/relativizeSegment.ts +33 -35
- package/src/process/reverseCurve.ts +8 -5
- package/src/process/reversePath.ts +87 -74
- package/src/process/roundPath.ts +14 -8
- package/src/process/roundSegment.ts +9 -0
- package/src/process/segmentToCubic.ts +9 -7
- package/src/process/shortenSegment.ts +24 -32
- package/src/process/splitCubic.ts +2 -2
- package/src/process/transformPath.ts +35 -40
- package/src/types.ts +7 -11
- package/src/util/getPathArea.ts +2 -2
- package/src/util/getPathBBox.ts +25 -42
- package/src/util/getPointAtLength.ts +51 -49
- package/src/util/getPropertiesAtLength.ts +4 -5
- package/src/util/getPropertiesAtPoint.ts +5 -5
- package/src/util/getTotalLength.ts +23 -38
- package/test/class.test.ts +2 -0
- package/test/fixtures/shapes.js +5 -5
- package/test/static.test.ts +18 -12
- package/src/math/polygonArea.ts +0 -29
- package/src/math/polygonLength.ts +0 -22
package/src/math/arcTools.ts
CHANGED
|
@@ -8,37 +8,38 @@ import type { Point } from '../types';
|
|
|
8
8
|
* @param theta the angle in radians
|
|
9
9
|
* @returns the arc length
|
|
10
10
|
*/
|
|
11
|
-
const
|
|
11
|
+
const arcLength = (rx: number, ry: number, theta: number) => {
|
|
12
12
|
const halfTheta = theta / 2;
|
|
13
13
|
const sinHalfTheta = Math.sin(halfTheta);
|
|
14
14
|
const cosHalfTheta = Math.cos(halfTheta);
|
|
15
15
|
const term1 = rx ** 2 * sinHalfTheta ** 2;
|
|
16
16
|
const term2 = ry ** 2 * cosHalfTheta ** 2;
|
|
17
|
-
const
|
|
18
|
-
return Math.abs(
|
|
17
|
+
const length = Math.sqrt(term1 + term2) * theta;
|
|
18
|
+
return Math.abs(length);
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
* @param theta the arc sweep angle
|
|
22
|
+
* Find point on ellipse at given angle around ellipse (theta);
|
|
24
23
|
* @param cx the center X
|
|
25
24
|
* @param cy the center Y
|
|
26
25
|
* @param rx the radius X
|
|
27
26
|
* @param ry the radius Y
|
|
28
|
-
* @param alpha the angle
|
|
29
|
-
* @
|
|
27
|
+
* @param alpha the arc rotation angle in radians
|
|
28
|
+
* @param theta the arc sweep angle in radians
|
|
29
|
+
* @returns a point around ellipse at given angle
|
|
30
30
|
*/
|
|
31
|
-
const
|
|
31
|
+
const arcPoint = (cx: number, cy: number, rx: number, ry: number, alpha: number, theta: number) => {
|
|
32
|
+
const { sin, cos } = Math;
|
|
32
33
|
// theta is angle in radians around arc
|
|
33
34
|
// alpha is angle of rotation of ellipse in radians
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const x = rx *
|
|
37
|
-
const y = ry *
|
|
35
|
+
const cosA = cos(alpha);
|
|
36
|
+
const sinA = sin(alpha);
|
|
37
|
+
const x = rx * cos(theta);
|
|
38
|
+
const y = ry * sin(theta);
|
|
38
39
|
|
|
39
40
|
return {
|
|
40
|
-
x: cx +
|
|
41
|
-
y: cy +
|
|
41
|
+
x: cx + cosA * x - sinA * y,
|
|
42
|
+
y: cy + sinA * x + cosA * y,
|
|
42
43
|
};
|
|
43
44
|
};
|
|
44
45
|
|
|
@@ -46,7 +47,7 @@ const arc = (theta: number, cx: number, cy: number, rx: number, ry: number, alph
|
|
|
46
47
|
* Returns the angle between two points.
|
|
47
48
|
* @param v0 starting point
|
|
48
49
|
* @param v1 ending point
|
|
49
|
-
* @returns the angle
|
|
50
|
+
* @returns the angle in radian
|
|
50
51
|
*/
|
|
51
52
|
const angleBetween = (v0: Point, v1: Point) => {
|
|
52
53
|
const { x: v0x, y: v0y } = v0;
|
|
@@ -54,14 +55,12 @@ const angleBetween = (v0: Point, v1: Point) => {
|
|
|
54
55
|
const p = v0x * v1x + v0y * v1y;
|
|
55
56
|
const n = Math.sqrt((v0x ** 2 + v0y ** 2) * (v1x ** 2 + v1y ** 2));
|
|
56
57
|
const sign = v0x * v1y - v0y * v1x < 0 ? -1 : 1;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return angle;
|
|
58
|
+
return sign * Math.acos(p / n);
|
|
60
59
|
};
|
|
61
60
|
|
|
62
61
|
/**
|
|
63
|
-
* Returns the following properties for an Arc segment: center, start angle
|
|
64
|
-
* and radiuses on X and Y
|
|
62
|
+
* Returns the following properties for an Arc segment: center, start angle,
|
|
63
|
+
* end angle, and radiuses on X and Y axis.
|
|
65
64
|
*
|
|
66
65
|
* @param x1 the starting point X
|
|
67
66
|
* @param y1 the starting point Y
|
|
@@ -91,6 +90,7 @@ const getArcProps = (
|
|
|
91
90
|
const xRot = ((angle % 360) + 360) % 360;
|
|
92
91
|
const xRotRad = xRot * (PI / 180);
|
|
93
92
|
|
|
93
|
+
// istanbul ignore next @preserve
|
|
94
94
|
if (x1 === x && y1 === y) {
|
|
95
95
|
return {
|
|
96
96
|
rx,
|
|
@@ -107,7 +107,7 @@ const getArcProps = (
|
|
|
107
107
|
ry,
|
|
108
108
|
startAngle: 0,
|
|
109
109
|
endAngle: 0,
|
|
110
|
-
center: { x, y },
|
|
110
|
+
center: { x: (x + x1) / 2, y: (y + y1) / 2 },
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
113
|
|
|
@@ -165,7 +165,6 @@ const getArcProps = (
|
|
|
165
165
|
|
|
166
166
|
const endAngle = startAngle + sweepAngle;
|
|
167
167
|
|
|
168
|
-
// to be used later
|
|
169
168
|
// point.ellipticalArcStartAngle = startAngle;
|
|
170
169
|
// point.ellipticalArcEndAngle = startAngle + sweepAngle;
|
|
171
170
|
// point.ellipticalArcAngle = alpha;
|
|
@@ -173,8 +172,6 @@ const getArcProps = (
|
|
|
173
172
|
// point.ellipticalArcCenter = center;
|
|
174
173
|
// point.resultantRx = rx;
|
|
175
174
|
// 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
175
|
|
|
179
176
|
return {
|
|
180
177
|
center,
|
|
@@ -198,7 +195,7 @@ const getArcProps = (
|
|
|
198
195
|
* @param y2 the ending point Y
|
|
199
196
|
* @returns the length of the Arc segment
|
|
200
197
|
*/
|
|
201
|
-
|
|
198
|
+
const getArcLength = (
|
|
202
199
|
x1: number,
|
|
203
200
|
y1: number,
|
|
204
201
|
RX: number,
|
|
@@ -210,7 +207,7 @@ export const getArcLength = (
|
|
|
210
207
|
y: number,
|
|
211
208
|
) => {
|
|
212
209
|
const { rx, ry, startAngle, endAngle } = getArcProps(x1, y1, RX, RY, angle, LAF, SF, x, y);
|
|
213
|
-
return
|
|
210
|
+
return arcLength(rx, ry, endAngle - startAngle);
|
|
214
211
|
};
|
|
215
212
|
|
|
216
213
|
/**
|
|
@@ -228,7 +225,7 @@ export const getArcLength = (
|
|
|
228
225
|
* @param distance a [0-1] ratio
|
|
229
226
|
* @returns a point along the Arc segment
|
|
230
227
|
*/
|
|
231
|
-
|
|
228
|
+
const getPointAtArcLength = (
|
|
232
229
|
x1: number,
|
|
233
230
|
y1: number,
|
|
234
231
|
RX: number,
|
|
@@ -242,10 +239,10 @@ export const getPointAtArcLength = (
|
|
|
242
239
|
) => {
|
|
243
240
|
let point = { x: x1, y: y1 };
|
|
244
241
|
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
242
|
|
|
247
243
|
/* istanbul ignore else @preserve */
|
|
248
244
|
if (typeof distance === 'number') {
|
|
245
|
+
const length = arcLength(rx, ry, endAngle - startAngle);
|
|
249
246
|
if (distance <= 0) {
|
|
250
247
|
point = { x: x1, y: y1 };
|
|
251
248
|
} else if (distance >= length) {
|
|
@@ -278,7 +275,7 @@ export const getPointAtArcLength = (
|
|
|
278
275
|
};
|
|
279
276
|
|
|
280
277
|
/**
|
|
281
|
-
* Returns the
|
|
278
|
+
* Returns the extrema for an Arc segment.
|
|
282
279
|
* @see https://github.com/herrstrietzel/svg-pathdata-getbbox
|
|
283
280
|
*
|
|
284
281
|
* @param x1 the starting point X
|
|
@@ -293,7 +290,7 @@ export const getPointAtArcLength = (
|
|
|
293
290
|
* @returns the extrema of the Arc segment
|
|
294
291
|
*
|
|
295
292
|
*/
|
|
296
|
-
|
|
293
|
+
const getArcBBox = (
|
|
297
294
|
x1: number,
|
|
298
295
|
y1: number,
|
|
299
296
|
RX: number,
|
|
@@ -306,45 +303,46 @@ export const getArcBBox = (
|
|
|
306
303
|
) => {
|
|
307
304
|
const { center, rx, ry, startAngle, endAngle } = getArcProps(x1, y1, RX, RY, angle, LAF, SF, x, y);
|
|
308
305
|
const deltaAngle = endAngle - startAngle;
|
|
306
|
+
const { min, max, tan, atan2, PI } = Math;
|
|
309
307
|
|
|
310
308
|
// final on path point
|
|
311
309
|
const p = { x, y };
|
|
312
310
|
|
|
313
311
|
// circle/elipse center coordinates
|
|
314
|
-
const
|
|
312
|
+
const { x: cx, y: cy } = center;
|
|
315
313
|
|
|
316
314
|
// collect extreme points – add end point
|
|
317
315
|
const extremes = [p];
|
|
318
316
|
|
|
319
317
|
// rotation to radians
|
|
320
|
-
const alpha = (angle *
|
|
321
|
-
const
|
|
318
|
+
const alpha = (angle * PI) / 180;
|
|
319
|
+
const tangent = tan(alpha);
|
|
322
320
|
|
|
323
321
|
/**
|
|
324
322
|
* find min/max from zeroes of directional derivative along x and y
|
|
325
323
|
* along x axis
|
|
326
324
|
*/
|
|
327
|
-
const theta =
|
|
325
|
+
const theta = atan2(-ry * tangent, rx);
|
|
328
326
|
const angle1 = theta;
|
|
329
|
-
const angle2 = theta +
|
|
330
|
-
const angle3 =
|
|
331
|
-
const angle4 = angle3 +
|
|
327
|
+
const angle2 = theta + PI;
|
|
328
|
+
const angle3 = atan2(ry, rx * tangent);
|
|
329
|
+
const angle4 = angle3 + PI;
|
|
332
330
|
|
|
333
331
|
// inner bounding box
|
|
334
332
|
const xArr = [x1, x];
|
|
335
333
|
const yArr = [y1, y];
|
|
336
|
-
const xMin =
|
|
337
|
-
const xMax =
|
|
338
|
-
const yMin =
|
|
339
|
-
const yMax =
|
|
334
|
+
const xMin = min(...xArr);
|
|
335
|
+
const xMax = max(...xArr);
|
|
336
|
+
const yMin = min(...yArr);
|
|
337
|
+
const yMax = max(...yArr);
|
|
340
338
|
|
|
341
339
|
// on path point close after start
|
|
342
340
|
const angleAfterStart = endAngle - deltaAngle * 0.001;
|
|
343
|
-
const pP2 =
|
|
341
|
+
const pP2 = arcPoint(cx, cy, rx, ry, alpha, angleAfterStart);
|
|
344
342
|
|
|
345
343
|
// on path point close before end
|
|
346
344
|
const angleBeforeEnd = endAngle - deltaAngle * 0.999;
|
|
347
|
-
const pP3 =
|
|
345
|
+
const pP3 = arcPoint(cx, cy, rx, ry, alpha, angleBeforeEnd);
|
|
348
346
|
|
|
349
347
|
/**
|
|
350
348
|
* expected extremes
|
|
@@ -357,38 +355,42 @@ export const getArcBBox = (
|
|
|
357
355
|
// istanbul ignore if @preserve
|
|
358
356
|
if (pP2.x > xMax || pP3.x > xMax) {
|
|
359
357
|
// get point for this theta
|
|
360
|
-
extremes.push(
|
|
358
|
+
extremes.push(arcPoint(cx, cy, rx, ry, alpha, angle1));
|
|
361
359
|
}
|
|
362
360
|
|
|
363
361
|
// left
|
|
364
362
|
// istanbul ignore if @preserve
|
|
365
363
|
if (pP2.x < xMin || pP3.x < xMin) {
|
|
366
364
|
// get anti-symmetric point
|
|
367
|
-
extremes.push(
|
|
365
|
+
extremes.push(arcPoint(cx, cy, rx, ry, alpha, angle2));
|
|
368
366
|
}
|
|
369
367
|
|
|
370
368
|
// top
|
|
371
369
|
// istanbul ignore if @preserve
|
|
372
370
|
if (pP2.y < yMin || pP3.y < yMin) {
|
|
373
371
|
// get anti-symmetric point
|
|
374
|
-
extremes.push(
|
|
372
|
+
extremes.push(arcPoint(cx, cy, rx, ry, alpha, angle4));
|
|
375
373
|
}
|
|
376
374
|
|
|
377
375
|
// bottom
|
|
378
376
|
// istanbul ignore if @preserve
|
|
379
377
|
if (pP2.y > yMax || pP3.y > yMax) {
|
|
380
378
|
// get point for this theta
|
|
381
|
-
extremes.push(
|
|
379
|
+
extremes.push(arcPoint(cx, cy, rx, ry, alpha, angle3));
|
|
382
380
|
}
|
|
383
381
|
|
|
384
382
|
return {
|
|
385
383
|
min: {
|
|
386
|
-
x:
|
|
387
|
-
y:
|
|
384
|
+
x: min(...extremes.map(n => n.x)),
|
|
385
|
+
y: min(...extremes.map(n => n.y)),
|
|
388
386
|
},
|
|
389
387
|
max: {
|
|
390
|
-
x:
|
|
391
|
-
y:
|
|
388
|
+
x: max(...extremes.map(n => n.x)),
|
|
389
|
+
y: max(...extremes.map(n => n.y)),
|
|
392
390
|
},
|
|
393
391
|
};
|
|
394
392
|
};
|
|
393
|
+
|
|
394
|
+
export { arcPoint, angleBetween, getArcLength, arcLength, getArcBBox, getArcProps, getPointAtArcLength };
|
|
395
|
+
|
|
396
|
+
export {};
|
package/src/math/bezier.ts
CHANGED
|
@@ -15,8 +15,6 @@ import type {
|
|
|
15
15
|
* @see https://github.com/Pomax/bezierjs
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
const ZERO = { x: 0, y: 0 };
|
|
19
|
-
|
|
20
18
|
const Tvalues = [
|
|
21
19
|
-0.0640568928626056260850430826247450385909, 0.0640568928626056260850430826247450385909,
|
|
22
20
|
-0.1911188674736163091586398207570696318404, 0.1911188674736163091586398207570696318404,
|
|
@@ -52,7 +50,7 @@ const Cvalues = [
|
|
|
52
50
|
* @param points
|
|
53
51
|
* @returns
|
|
54
52
|
*/
|
|
55
|
-
const
|
|
53
|
+
const deriveBezier = (points: QuadPoints | CubicPoints) => {
|
|
56
54
|
const dpoints = [] as (DerivedCubicPoints | DerivedQuadPoints)[];
|
|
57
55
|
for (let p = points, d = p.length, c = d - 1; d > 1; d -= 1, c -= 1) {
|
|
58
56
|
const list = [] as unknown as DerivedCubicPoints | DerivedQuadPoints;
|
|
@@ -74,7 +72,7 @@ const derive = (points: QuadPoints | CubicPoints) => {
|
|
|
74
72
|
* @param points
|
|
75
73
|
* @param t
|
|
76
74
|
*/
|
|
77
|
-
const
|
|
75
|
+
const computeBezier = (points: DerivedQuadPoints | DerivedCubicPoints, t: number) => {
|
|
78
76
|
// shortcuts
|
|
79
77
|
/* istanbul ignore next @preserve */
|
|
80
78
|
if (t === 0) {
|
|
@@ -119,7 +117,7 @@ const compute = (points: DerivedQuadPoints | DerivedCubicPoints, t: number) => {
|
|
|
119
117
|
let d = 0;
|
|
120
118
|
/* istanbul ignore else @preserve */
|
|
121
119
|
if (order === 2) {
|
|
122
|
-
p = [p[0], p[1], p[2],
|
|
120
|
+
p = [p[0], p[1], p[2], { x: 0, y: 0 } as DerivedPoint];
|
|
123
121
|
a = mt2;
|
|
124
122
|
b = mt * t * 2;
|
|
125
123
|
c = t2;
|
|
@@ -136,14 +134,14 @@ const compute = (points: DerivedQuadPoints | DerivedCubicPoints, t: number) => {
|
|
|
136
134
|
};
|
|
137
135
|
};
|
|
138
136
|
|
|
139
|
-
const
|
|
137
|
+
const calculateBezier = (derivativeFn: DeriveCallback, t: number) => {
|
|
140
138
|
const d = derivativeFn(t);
|
|
141
139
|
const l = d.x * d.x + d.y * d.y;
|
|
142
140
|
|
|
143
141
|
return Math.sqrt(l);
|
|
144
142
|
};
|
|
145
143
|
|
|
146
|
-
const
|
|
144
|
+
const bezierLength = (derivativeFn: DeriveCallback) => {
|
|
147
145
|
const z = 0.5;
|
|
148
146
|
const len = Tvalues.length;
|
|
149
147
|
|
|
@@ -151,7 +149,7 @@ const lengthFn = (derivativeFn: DeriveCallback) => {
|
|
|
151
149
|
|
|
152
150
|
for (let i = 0, t; i < len; i++) {
|
|
153
151
|
t = z * Tvalues[i] + z;
|
|
154
|
-
sum += Cvalues[i] *
|
|
152
|
+
sum += Cvalues[i] * calculateBezier(derivativeFn, t);
|
|
155
153
|
}
|
|
156
154
|
return z * sum;
|
|
157
155
|
};
|
|
@@ -160,7 +158,7 @@ const lengthFn = (derivativeFn: DeriveCallback) => {
|
|
|
160
158
|
* Returns the length of CubicBezier / Quad segment.
|
|
161
159
|
* @param curve cubic / quad bezier segment
|
|
162
160
|
*/
|
|
163
|
-
|
|
161
|
+
const getBezierLength = (curve: CubicCoordinates | QuadCoordinates) => {
|
|
164
162
|
const points = [] as unknown as CubicPoints | QuadPoints;
|
|
165
163
|
for (let idx = 0, len = curve.length, step = 2; idx < len; idx += step) {
|
|
166
164
|
points.push({
|
|
@@ -168,9 +166,9 @@ export const length = (curve: CubicCoordinates | QuadCoordinates) => {
|
|
|
168
166
|
y: curve[idx + 1],
|
|
169
167
|
});
|
|
170
168
|
}
|
|
171
|
-
const dpoints =
|
|
172
|
-
return
|
|
173
|
-
return
|
|
169
|
+
const dpoints = deriveBezier(points);
|
|
170
|
+
return bezierLength((t: number) => {
|
|
171
|
+
return computeBezier(dpoints[0], t);
|
|
174
172
|
});
|
|
175
173
|
};
|
|
176
174
|
|
|
@@ -179,67 +177,64 @@ const CBEZIER_MINMAX_EPSILON = 0.00000001;
|
|
|
179
177
|
|
|
180
178
|
/**
|
|
181
179
|
* Returns the most extreme points in a Quad Bezier segment.
|
|
182
|
-
* @param A
|
|
180
|
+
* @param A an array which consist of X/Y values
|
|
183
181
|
*/
|
|
184
182
|
// https://github.com/kpym/SVGPathy/blob/acd1a50c626b36d81969f6e98e8602e128ba4302/lib/box.js#L89
|
|
185
|
-
|
|
186
|
-
const min = Math.min(
|
|
187
|
-
const max = Math.max(
|
|
183
|
+
const minmaxQ = ([v1, cp, v2]: [number, number, number]) => {
|
|
184
|
+
const min = Math.min(v1, v2);
|
|
185
|
+
const max = Math.max(v1, v2);
|
|
188
186
|
|
|
189
187
|
/* istanbul ignore next @preserve */
|
|
190
|
-
if (
|
|
188
|
+
if (cp >= v1 ? v2 >= cp : v2 <= cp) {
|
|
191
189
|
// if no extremum in ]0,1[
|
|
192
190
|
return [min, max] as PointTuple;
|
|
193
191
|
}
|
|
194
192
|
|
|
195
193
|
// check if the extremum E is min or max
|
|
196
|
-
const E = (
|
|
194
|
+
const E = (v1 * v2 - cp * cp) / (v1 - 2 * cp + v2);
|
|
197
195
|
return (E < min ? [E, max] : [min, E]) as PointTuple;
|
|
198
196
|
};
|
|
199
197
|
|
|
200
198
|
/**
|
|
201
199
|
* Returns the most extreme points in a Cubic Bezier segment.
|
|
202
|
-
* @param A
|
|
200
|
+
* @param A an array which consist of X/Y values
|
|
203
201
|
* @see https://github.com/kpym/SVGPathy/blob/acd1a50c626b36d81969f6e98e8602e128ba4302/lib/box.js#L127
|
|
204
202
|
*/
|
|
205
|
-
|
|
206
|
-
const K =
|
|
203
|
+
const minmaxC = ([v1, cp1, cp2, v2]: [number, number, number, number]) => {
|
|
204
|
+
const K = v1 - 3 * cp1 + 3 * cp2 - v2;
|
|
207
205
|
|
|
208
206
|
// if the polynomial is (almost) quadratic and not cubic
|
|
209
207
|
/* istanbul ignore next @preserve */
|
|
210
208
|
if (Math.abs(K) < CBEZIER_MINMAX_EPSILON) {
|
|
211
|
-
if (
|
|
209
|
+
if (v1 === v2 && v1 === cp1) {
|
|
212
210
|
// no curve, point targeting same location
|
|
213
|
-
return [
|
|
211
|
+
return [v1, v2] as PointTuple;
|
|
214
212
|
}
|
|
215
213
|
|
|
216
|
-
return minmaxQ([
|
|
214
|
+
return minmaxQ([v1, -0.5 * v1 + 1.5 * cp1, v1 - 3 * cp1 + 3 * cp2]);
|
|
217
215
|
}
|
|
218
216
|
|
|
219
217
|
// the reduced discriminant of the derivative
|
|
220
|
-
const T = -
|
|
218
|
+
const T = -v1 * cp2 + v1 * v2 - cp1 * cp2 - cp1 * v2 + cp1 * cp1 + cp2 * cp2;
|
|
221
219
|
|
|
222
220
|
// if the polynomial is monotone in [0,1]
|
|
223
221
|
if (T <= 0) {
|
|
224
|
-
return [Math.min(
|
|
222
|
+
return [Math.min(v1, v2), Math.max(v1, v2)] as PointTuple;
|
|
225
223
|
}
|
|
226
224
|
const S = Math.sqrt(T);
|
|
227
225
|
|
|
228
226
|
// potential extrema
|
|
229
|
-
let min = Math.min(
|
|
230
|
-
let max = Math.max(
|
|
227
|
+
let min = Math.min(v1, v2);
|
|
228
|
+
let max = Math.max(v1, v2);
|
|
231
229
|
|
|
232
|
-
const L =
|
|
230
|
+
const L = v1 - 2 * cp1 + cp2;
|
|
233
231
|
// check local extrema
|
|
234
232
|
for (let R = (L + S) / K, i = 1; i <= 2; R = (L - S) / K, i++) {
|
|
235
233
|
// istanbul ignore next @preserve
|
|
236
234
|
if (R > 0 && R < 1) {
|
|
237
235
|
// if the extrema is for R in [0,1]
|
|
238
236
|
const Q =
|
|
239
|
-
|
|
240
|
-
A[1] * 3 * (1 - R) * (1 - R) * R +
|
|
241
|
-
A[2] * 3 * (1 - R) * R * R +
|
|
242
|
-
A[3] * R * R * R;
|
|
237
|
+
v1 * (1 - R) * (1 - R) * (1 - R) + cp1 * 3 * (1 - R) * (1 - R) * R + cp2 * 3 * (1 - R) * R * R + v2 * R * R * R;
|
|
243
238
|
if (Q < min) {
|
|
244
239
|
min = Q;
|
|
245
240
|
}
|
|
@@ -251,3 +246,16 @@ export const minmaxC = (A: [number, number, number, number]) => {
|
|
|
251
246
|
|
|
252
247
|
return [min, max] as PointTuple;
|
|
253
248
|
};
|
|
249
|
+
|
|
250
|
+
export {
|
|
251
|
+
Cvalues,
|
|
252
|
+
Tvalues,
|
|
253
|
+
minmaxC,
|
|
254
|
+
minmaxQ,
|
|
255
|
+
getBezierLength,
|
|
256
|
+
bezierLength,
|
|
257
|
+
calculateBezier,
|
|
258
|
+
computeBezier,
|
|
259
|
+
deriveBezier,
|
|
260
|
+
CBEZIER_MINMAX_EPSILON,
|
|
261
|
+
};
|
package/src/math/cubicTools.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getBezierLength, minmaxC } from './bezier';
|
|
2
2
|
import { type CubicCoordinates } from '../types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Returns a
|
|
5
|
+
* Returns a point at a given length of a CubicBezier segment.
|
|
6
6
|
*
|
|
7
7
|
* @param x1 the starting point X
|
|
8
8
|
* @param y1 the starting point Y
|
|
@@ -15,7 +15,7 @@ import { type CubicCoordinates } from '../types';
|
|
|
15
15
|
* @param t a [0-1] ratio
|
|
16
16
|
* @returns the point at cubic-bezier segment length
|
|
17
17
|
*/
|
|
18
|
-
|
|
18
|
+
const getPointAtCubicSegmentLength = ([x1, y1, c1x, c1y, c2x, c2y, x2, y2]: CubicCoordinates, t: number) => {
|
|
19
19
|
const t1 = 1 - t;
|
|
20
20
|
return {
|
|
21
21
|
x: t1 ** 3 * x1 + 3 * t1 ** 2 * t * c1x + 3 * t1 * t ** 2 * c2x + t ** 3 * x2,
|
|
@@ -36,7 +36,7 @@ export const getPointAtCubicSegmentLength = ([x1, y1, c1x, c1y, c2x, c2y, x2, y2
|
|
|
36
36
|
* @param y2 the ending point Y
|
|
37
37
|
* @returns the CubicBezier segment length
|
|
38
38
|
*/
|
|
39
|
-
|
|
39
|
+
const getCubicLength = (
|
|
40
40
|
x1: number,
|
|
41
41
|
y1: number,
|
|
42
42
|
c1x: number,
|
|
@@ -46,7 +46,7 @@ export const getCubicLength = (
|
|
|
46
46
|
x2: number,
|
|
47
47
|
y2: number,
|
|
48
48
|
) => {
|
|
49
|
-
return
|
|
49
|
+
return getBezierLength([x1, y1, c1x, c1y, c2x, c2y, x2, y2]);
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
/**
|
|
@@ -63,7 +63,7 @@ export const getCubicLength = (
|
|
|
63
63
|
* @param distance the distance to look at
|
|
64
64
|
* @returns the point at CubicBezier length
|
|
65
65
|
*/
|
|
66
|
-
|
|
66
|
+
const getPointAtCubicLength = (
|
|
67
67
|
x1: number,
|
|
68
68
|
y1: number,
|
|
69
69
|
c1x: number,
|
|
@@ -78,7 +78,7 @@ export const getPointAtCubicLength = (
|
|
|
78
78
|
let point = { x: x1, y: y1 };
|
|
79
79
|
/* istanbul ignore else @preserve */
|
|
80
80
|
if (distanceIsNumber) {
|
|
81
|
-
const currentLength =
|
|
81
|
+
const currentLength = getBezierLength([x1, y1, c1x, c1y, c2x, c2y, x2, y2]);
|
|
82
82
|
if (distance <= 0) {
|
|
83
83
|
// first point already defined
|
|
84
84
|
} else if (distance >= currentLength) {
|
|
@@ -103,7 +103,7 @@ export const getPointAtCubicLength = (
|
|
|
103
103
|
* @param y2 the ending point Y
|
|
104
104
|
* @returns the point at CubicBezier length
|
|
105
105
|
*/
|
|
106
|
-
|
|
106
|
+
const getCubicBBox = (
|
|
107
107
|
x1: number,
|
|
108
108
|
y1: number,
|
|
109
109
|
c1x: number,
|
|
@@ -120,3 +120,5 @@ export const getCubicBBox = (
|
|
|
120
120
|
max: { x: cxMinMax[1], y: cyMinMax[1] },
|
|
121
121
|
};
|
|
122
122
|
};
|
|
123
|
+
|
|
124
|
+
export { getCubicLength, getCubicBBox, getPointAtCubicLength, getPointAtCubicSegmentLength };
|
|
@@ -8,7 +8,7 @@ import { type PointTuple } from '../types';
|
|
|
8
8
|
* @param b the second point coordinates
|
|
9
9
|
* @returns the distance value
|
|
10
10
|
*/
|
|
11
|
-
const distanceSquareRoot = (a: PointTuple, b: PointTuple)
|
|
11
|
+
const distanceSquareRoot = (a: PointTuple, b: PointTuple) => {
|
|
12
12
|
return Math.sqrt((a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1]));
|
|
13
13
|
};
|
|
14
14
|
|
package/src/math/lineTools.ts
CHANGED
|
@@ -10,7 +10,7 @@ import distanceSquareRoot from './distanceSquareRoot';
|
|
|
10
10
|
* @param y2 the ending point Y
|
|
11
11
|
* @returns the line segment length
|
|
12
12
|
*/
|
|
13
|
-
|
|
13
|
+
const getLineLength = (x1: number, y1: number, x2: number, y2: number) => {
|
|
14
14
|
return distanceSquareRoot([x1, y1], [x2, y2]);
|
|
15
15
|
};
|
|
16
16
|
|
|
@@ -24,12 +24,12 @@ export const getLineLength = (x1: number, y1: number, x2: number, y2: number) =>
|
|
|
24
24
|
* @param distance the distance to point in [0-1] range
|
|
25
25
|
* @returns the point at length
|
|
26
26
|
*/
|
|
27
|
-
|
|
28
|
-
const length = distanceSquareRoot([x1, y1], [x2, y2]);
|
|
27
|
+
const getPointAtLineLength = (x1: number, y1: number, x2: number, y2: number, distance?: number) => {
|
|
29
28
|
let point = { x: x1, y: y1 };
|
|
30
29
|
|
|
31
30
|
/* istanbul ignore else @preserve */
|
|
32
31
|
if (typeof distance === 'number') {
|
|
32
|
+
const length = distanceSquareRoot([x1, y1], [x2, y2]);
|
|
33
33
|
if (distance <= 0) {
|
|
34
34
|
point = { x: x1, y: y1 };
|
|
35
35
|
} else if (distance >= length) {
|
|
@@ -52,7 +52,7 @@ export const getPointAtLineLength = (x1: number, y1: number, x2: number, y2: num
|
|
|
52
52
|
* @param distance the distance to point in [0-1] range
|
|
53
53
|
* @returns the extrema for line segments
|
|
54
54
|
*/
|
|
55
|
-
|
|
55
|
+
const getLineBBox = (x1: number, y1: number, x2: number, y2: number) => {
|
|
56
56
|
const { min, max } = Math;
|
|
57
57
|
return {
|
|
58
58
|
min: {
|
|
@@ -65,3 +65,5 @@ export const getLineBBox = (x1: number, y1: number, x2: number, y2: number) => {
|
|
|
65
65
|
},
|
|
66
66
|
};
|
|
67
67
|
};
|
|
68
|
+
|
|
69
|
+
export { getPointAtLineLength, getLineBBox, getLineLength };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import distanceSquareRoot from './distanceSquareRoot';
|
|
2
|
+
import { type PointTuple } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* d3-polygon-area
|
|
6
|
+
* https://github.com/d3/d3-polygon
|
|
7
|
+
*
|
|
8
|
+
* Returns the area of a polygon.
|
|
9
|
+
*
|
|
10
|
+
* @param polygon an array of coordinates
|
|
11
|
+
* @returns the polygon area
|
|
12
|
+
*/
|
|
13
|
+
const polygonArea = (polygon: PointTuple[]) => {
|
|
14
|
+
const n = polygon.length;
|
|
15
|
+
let i = -1;
|
|
16
|
+
let a: PointTuple;
|
|
17
|
+
let b = polygon[n - 1];
|
|
18
|
+
let area = 0;
|
|
19
|
+
|
|
20
|
+
/* eslint-disable-next-line */
|
|
21
|
+
while (++i < n) {
|
|
22
|
+
a = b;
|
|
23
|
+
b = polygon[i];
|
|
24
|
+
area += a[1] * b[0] - a[0] * b[1];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return area / 2;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* d3-polygon-length
|
|
32
|
+
* https://github.com/d3/d3-polygon
|
|
33
|
+
*
|
|
34
|
+
* Returns the perimeter of a polygon.
|
|
35
|
+
*
|
|
36
|
+
* @param polygon an array of coordinates
|
|
37
|
+
* @returns the polygon length
|
|
38
|
+
*/
|
|
39
|
+
const polygonLength = (polygon: PointTuple[]) => {
|
|
40
|
+
return polygon.reduce((length, point, i) => {
|
|
41
|
+
if (i) {
|
|
42
|
+
return length + distanceSquareRoot(polygon[i - 1], point);
|
|
43
|
+
}
|
|
44
|
+
return 0;
|
|
45
|
+
}, 0);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export { polygonArea, polygonLength };
|
package/src/math/quadTools.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getBezierLength, minmaxQ } from './bezier';
|
|
2
2
|
import { type QuadCoordinates } from '../types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -35,8 +35,8 @@ const getPointAtQuadSegmentLength = ([x1, y1, cx, cy, x2, y2]: QuadCoordinates,
|
|
|
35
35
|
* @param y2 the ending point Y
|
|
36
36
|
* @returns the QuadraticBezier segment length
|
|
37
37
|
*/
|
|
38
|
-
|
|
39
|
-
return
|
|
38
|
+
const getQuadLength = (x1: number, y1: number, cx: number, cy: number, x2: number, y2: number) => {
|
|
39
|
+
return getBezierLength([x1, y1, cx, cy, x2, y2]);
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
/**
|
|
@@ -51,7 +51,7 @@ export const getQuadLength = (x1: number, y1: number, cx: number, cy: number, x2
|
|
|
51
51
|
* @param distance the distance to look at
|
|
52
52
|
* @returns the point at QuadraticBezier length
|
|
53
53
|
*/
|
|
54
|
-
|
|
54
|
+
const getPointAtQuadLength = (
|
|
55
55
|
x1: number,
|
|
56
56
|
y1: number,
|
|
57
57
|
cx: number,
|
|
@@ -65,7 +65,7 @@ export const getPointAtQuadLength = (
|
|
|
65
65
|
|
|
66
66
|
/* istanbul ignore else @preserve */
|
|
67
67
|
if (distanceIsNumber) {
|
|
68
|
-
const currentLength =
|
|
68
|
+
const currentLength = getBezierLength([x1, y1, cx, cy, x2, y2]);
|
|
69
69
|
if (distance <= 0) {
|
|
70
70
|
// first point already defined
|
|
71
71
|
} else if (distance >= currentLength) {
|
|
@@ -88,7 +88,7 @@ export const getPointAtQuadLength = (
|
|
|
88
88
|
* @param y2 the ending point Y
|
|
89
89
|
* @returns the point at CubicBezier length
|
|
90
90
|
*/
|
|
91
|
-
|
|
91
|
+
const getQuadBBox = (x1: number, y1: number, cx: number, cy: number, x2: number, y2: number) => {
|
|
92
92
|
const cxMinMax = minmaxQ([x1, cx, x2]);
|
|
93
93
|
const cyMinMax = minmaxQ([y1, cy, y2]);
|
|
94
94
|
return {
|
|
@@ -96,3 +96,5 @@ export const getQuadBBox = (x1: number, y1: number, cx: number, cy: number, x2:
|
|
|
96
96
|
max: { x: cxMinMax[1], y: cyMinMax[1] },
|
|
97
97
|
};
|
|
98
98
|
};
|
|
99
|
+
|
|
100
|
+
export { getPointAtQuadSegmentLength, getQuadLength, getQuadBBox, getPointAtQuadLength };
|