modern-path2d 1.2.19 → 1.3.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/dist/index.cjs CHANGED
@@ -62,6 +62,11 @@ class Vector2 {
62
62
  get array() {
63
63
  return [this.x, this.y];
64
64
  }
65
+ finite() {
66
+ this.x = Number.isFinite(this.x) ? this.x : 0;
67
+ this.y = Number.isFinite(this.y) ? this.y : 0;
68
+ return this;
69
+ }
65
70
  set(x, y) {
66
71
  this.x = x;
67
72
  this.y = y;
@@ -1752,7 +1757,7 @@ class Curve {
1752
1757
  min.min(p);
1753
1758
  max.max(p);
1754
1759
  }
1755
- return { min, max };
1760
+ return { min: min.finite(), max: max.finite() };
1756
1761
  }
1757
1762
  getBoundingBox() {
1758
1763
  const { min, max } = this.getMinMax();
@@ -1788,7 +1793,7 @@ class Curve {
1788
1793
  const p1 = getPoint(indices[i]);
1789
1794
  const p2 = getPoint(indices[i + 1]);
1790
1795
  const p3 = getPoint(indices[i + 2]);
1791
- polygonStr += `<polygon points="${p1.join(",")} ${p2.join(",")} ${p3.join(",")}" fill="none" stroke="black" />`;
1796
+ polygonStr += `<polygon points="${p1.join(",")} ${p2.join(",")} ${p3.join(",")}" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round" />`;
1792
1797
  }
1793
1798
  const viewBox = [min.x, min.y, max.x - min.x, max.y - min.y];
1794
1799
  return `<svg width="${viewBox[2]}" height="${viewBox[3]}" viewBox="${viewBox.join(" ")}" xmlns="http://www.w3.org/2000/svg">${polygonStr}</svg>`;
@@ -1971,76 +1976,24 @@ class RoundCurve extends Curve {
1971
1976
  return [this._center];
1972
1977
  }
1973
1978
  getAdaptivePointArray(output = []) {
1974
- const { cx, cy, rx, ry, dx, dy } = this;
1975
- if (!(rx >= 0 && ry >= 0 && dx >= 0 && dy >= 0)) {
1976
- return output;
1977
- }
1978
- const n = Math.ceil(2.3 * Math.sqrt(rx + ry));
1979
- const x = cx;
1980
- const y = cy;
1981
- const m = n * 8 + (dx ? 4 : 0) + (dy ? 4 : 0);
1982
- if (m === 0) {
1979
+ const { cx, cy, rx, ry, startAngle, endAngle, clockwise } = this;
1980
+ if (!(rx >= 0 && ry >= 0)) {
1983
1981
  return output;
1984
1982
  }
1985
- if (n === 0) {
1986
- output[0] = output[6] = x + dx;
1987
- output[1] = output[3] = y + dy;
1988
- output[2] = output[4] = x - dx;
1989
- output[5] = output[7] = y - dy;
1990
- return output;
1991
- }
1992
- let j1 = 0;
1993
- let j2 = n * 4 + (dx ? 2 : 0) + 2;
1994
- let j3 = j2;
1995
- let j4 = m;
1996
- let x0 = dx + rx;
1997
- let y0 = dy;
1998
- let x1 = x + x0;
1999
- let x2 = x - x0;
2000
- let y1 = y + y0;
2001
- output[j1++] = x1;
2002
- output[j1++] = y1;
2003
- output[--j2] = y1;
2004
- output[--j2] = x2;
2005
- if (dy) {
2006
- const y22 = y - y0;
2007
- output[j3++] = x2;
2008
- output[j3++] = y22;
2009
- output[--j4] = y22;
2010
- output[--j4] = x1;
1983
+ let deltaAngle = endAngle - startAngle;
1984
+ if (!clockwise && deltaAngle > 0) {
1985
+ deltaAngle -= 2 * Math.PI;
1986
+ } else if (clockwise && deltaAngle < 0) {
1987
+ deltaAngle += 2 * Math.PI;
2011
1988
  }
2012
- for (let i = 1; i < n; i++) {
2013
- const a = Math.PI / 2 * (i / n);
2014
- const x02 = dx + Math.cos(a) * rx;
2015
- const y02 = dy + Math.sin(a) * ry;
2016
- const x12 = x + x02;
2017
- const x22 = x - x02;
2018
- const y12 = y + y02;
2019
- const y22 = y - y02;
2020
- output[j1++] = x12;
2021
- output[j1++] = y12;
2022
- output[--j2] = y12;
2023
- output[--j2] = x22;
2024
- output[j3++] = x22;
2025
- output[j3++] = y22;
2026
- output[--j4] = y22;
2027
- output[--j4] = x12;
2028
- }
2029
- x0 = dx;
2030
- y0 = dy + ry;
2031
- x1 = x + x0;
2032
- x2 = x - x0;
2033
- y1 = y + y0;
2034
- const y2 = y - y0;
2035
- output[j1++] = x1;
2036
- output[j1++] = y1;
2037
- output[--j4] = y2;
2038
- output[--j4] = x1;
2039
- if (dx) {
2040
- output[j1++] = x2;
2041
- output[j1++] = y1;
2042
- output[--j4] = y2;
2043
- output[--j4] = x2;
1989
+ const arcLength = Math.abs(deltaAngle);
1990
+ const n = Math.max(1, Math.ceil(arcLength / (Math.PI / 16)));
1991
+ for (let i = 0; i <= n; i++) {
1992
+ const t = i / n;
1993
+ const angle = startAngle + deltaAngle * t;
1994
+ const x = cx + Math.cos(angle) * rx;
1995
+ const y = cy + Math.sin(angle) * ry;
1996
+ output.push(x, y);
2044
1997
  }
2045
1998
  return output;
2046
1999
  }
@@ -2100,7 +2053,7 @@ class RoundCurve extends Curve {
2100
2053
  min.y = Math.min(min.y, cy - halfHeight);
2101
2054
  max.x = Math.max(max.x, cx + halfWidth);
2102
2055
  max.y = Math.max(max.y, cy + halfHeight);
2103
- return { min, max };
2056
+ return { min: min.finite(), max: max.finite() };
2104
2057
  }
2105
2058
  copy(source) {
2106
2059
  super.copy(source);
@@ -2306,121 +2259,6 @@ class ArcCurve extends RoundCurve {
2306
2259
  }
2307
2260
  }
2308
2261
 
2309
- class LineCurve extends Curve {
2310
- constructor(p1 = new Vector2(), p2 = new Vector2()) {
2311
- super();
2312
- this.p1 = p1;
2313
- this.p2 = p2;
2314
- }
2315
- static from(p1x, p1y, p2x, p2y) {
2316
- return new LineCurve(
2317
- new Vector2(p1x, p1y),
2318
- new Vector2(p2x, p2y)
2319
- );
2320
- }
2321
- getPoint(t, output = new Vector2()) {
2322
- if (t === 1) {
2323
- output.copy(this.p2);
2324
- } else {
2325
- output.copy(this.p2).sub(this.p1).scale(t).add(this.p1);
2326
- }
2327
- return output;
2328
- }
2329
- getPointAt(u, output = new Vector2()) {
2330
- return this.getPoint(u, output);
2331
- }
2332
- getTangent(_t, output = new Vector2()) {
2333
- return output.subVectors(this.p2, this.p1).normalize();
2334
- }
2335
- getTangentAt(u, output = new Vector2()) {
2336
- return this.getTangent(u, output);
2337
- }
2338
- getControlPointRefs() {
2339
- return [this.p1, this.p2];
2340
- }
2341
- getAdaptivePointArray(output = []) {
2342
- output.push(
2343
- this.p1.x,
2344
- this.p1.y,
2345
- this.p2.x,
2346
- this.p2.y
2347
- );
2348
- return output;
2349
- }
2350
- getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
2351
- const { p1, p2 } = this;
2352
- min.x = Math.min(min.x, p1.x, p2.x);
2353
- min.y = Math.min(min.y, p1.y, p2.y);
2354
- max.x = Math.max(max.x, p1.x, p2.x);
2355
- max.y = Math.max(max.y, p1.y, p2.y);
2356
- return { min, max };
2357
- }
2358
- toCommands() {
2359
- const { p1, p2 } = this;
2360
- return [
2361
- { type: "M", x: p1.x, y: p1.y },
2362
- { type: "L", x: p2.x, y: p2.y }
2363
- ];
2364
- }
2365
- fillTriangulate(options = {}) {
2366
- let {
2367
- vertices = [],
2368
- indices = [],
2369
- verticesStride = 2,
2370
- verticesOffset = vertices.length / verticesStride,
2371
- indicesOffset = indices.length
2372
- } = options;
2373
- const x = this.p1.x;
2374
- const y = this.p1.y;
2375
- const width = this.p2.x - this.p1.x || 1;
2376
- const height = this.p2.y - this.p2.y || 1;
2377
- const points = [
2378
- x,
2379
- y,
2380
- x + width,
2381
- y,
2382
- x + width,
2383
- y + height,
2384
- x,
2385
- y + height
2386
- ];
2387
- let count = 0;
2388
- verticesOffset *= verticesStride;
2389
- vertices[verticesOffset + count] = points[0];
2390
- vertices[verticesOffset + count + 1] = points[1];
2391
- count += verticesStride;
2392
- vertices[verticesOffset + count] = points[2];
2393
- vertices[verticesOffset + count + 1] = points[3];
2394
- count += verticesStride;
2395
- vertices[verticesOffset + count] = points[6];
2396
- vertices[verticesOffset + count + 1] = points[7];
2397
- count += verticesStride;
2398
- vertices[verticesOffset + count] = points[4];
2399
- vertices[verticesOffset + count + 1] = points[5];
2400
- count += verticesStride;
2401
- const verticesIndex = verticesOffset / verticesStride;
2402
- indices[indicesOffset++] = verticesIndex;
2403
- indices[indicesOffset++] = verticesIndex + 1;
2404
- indices[indicesOffset++] = verticesIndex + 2;
2405
- indices[indicesOffset++] = verticesIndex + 1;
2406
- indices[indicesOffset++] = verticesIndex + 3;
2407
- indices[indicesOffset++] = verticesIndex + 2;
2408
- return { vertices, indices };
2409
- }
2410
- drawTo(ctx) {
2411
- const { p1, p2 } = this;
2412
- ctx.lineTo(p1.x, p1.y);
2413
- ctx.lineTo(p2.x, p2.y);
2414
- return this;
2415
- }
2416
- copy(source) {
2417
- super.copy(source);
2418
- this.p1.copy(source.p1);
2419
- this.p2.copy(source.p2);
2420
- return this;
2421
- }
2422
- }
2423
-
2424
2262
  class CompositeCurve extends Curve {
2425
2263
  constructor(curves = []) {
2426
2264
  super();
@@ -2498,39 +2336,13 @@ class CompositeCurve extends Curve {
2498
2336
  fillTriangulate(options) {
2499
2337
  const indices = options?.indices ?? [];
2500
2338
  const vertices = options?.vertices ?? [];
2501
- const lines = [];
2502
- const fillLines = () => {
2503
- if (lines.length) {
2504
- const pointArray = [];
2505
- lines.forEach((line) => {
2506
- const x = line.p1.x;
2507
- const y = line.p1.y;
2508
- if (pointArray[pointArray.length - 2] !== x || pointArray[pointArray.length - 1] !== y) {
2509
- pointArray.push(x, y);
2510
- }
2511
- pointArray.push(line.p2.x, line.p2.y);
2512
- });
2513
- fillTriangulate(pointArray, {
2514
- ...options,
2515
- indices,
2516
- vertices
2517
- });
2518
- lines.length = 0;
2519
- }
2520
- };
2521
2339
  this.curves.forEach((curve) => {
2522
- if (curve instanceof LineCurve) {
2523
- lines.push(curve);
2524
- } else {
2525
- fillLines();
2526
- curve.fillTriangulate({
2527
- ...options,
2528
- indices,
2529
- vertices
2530
- });
2531
- }
2340
+ curve.fillTriangulate({
2341
+ ...options,
2342
+ indices,
2343
+ vertices
2344
+ });
2532
2345
  });
2533
- fillLines();
2534
2346
  return { indices, vertices };
2535
2347
  }
2536
2348
  applyTransform(transform) {
@@ -2539,7 +2351,7 @@ class CompositeCurve extends Curve {
2539
2351
  }
2540
2352
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
2541
2353
  this.curves.forEach((curve) => curve.getMinMax(min, max));
2542
- return { min, max };
2354
+ return { min: min.finite(), max: max.finite() };
2543
2355
  }
2544
2356
  getBoundingBox() {
2545
2357
  const { min, max } = this.getMinMax();
@@ -2639,7 +2451,7 @@ class CubicBezierCurve extends Curve {
2639
2451
  }
2640
2452
  };
2641
2453
  samplePoints(tValues, 10);
2642
- return { min, max };
2454
+ return { min: min.finite(), max: max.finite() };
2643
2455
  }
2644
2456
  toCommands() {
2645
2457
  const { p1, cp1, cp2, p2 } = this;
@@ -2691,6 +2503,125 @@ class EllipseCurve extends RoundCurve {
2691
2503
  }
2692
2504
  }
2693
2505
 
2506
+ class LineCurve extends Curve {
2507
+ constructor(p1 = new Vector2(), p2 = new Vector2()) {
2508
+ super();
2509
+ this.p1 = p1;
2510
+ this.p2 = p2;
2511
+ }
2512
+ static from(p1x, p1y, p2x, p2y) {
2513
+ return new LineCurve(
2514
+ new Vector2(p1x, p1y),
2515
+ new Vector2(p2x, p2y)
2516
+ );
2517
+ }
2518
+ getPoint(t, output = new Vector2()) {
2519
+ if (t === 1) {
2520
+ output.copy(this.p2);
2521
+ } else {
2522
+ output.copy(this.p2).sub(this.p1).scale(t).add(this.p1);
2523
+ }
2524
+ return output;
2525
+ }
2526
+ getPointAt(u, output = new Vector2()) {
2527
+ return this.getPoint(u, output);
2528
+ }
2529
+ getTangent(_t, output = new Vector2()) {
2530
+ return output.subVectors(this.p2, this.p1).normalize();
2531
+ }
2532
+ getTangentAt(u, output = new Vector2()) {
2533
+ return this.getTangent(u, output);
2534
+ }
2535
+ getControlPointRefs() {
2536
+ return [this.p1, this.p2];
2537
+ }
2538
+ getAdaptivePointArray(output = []) {
2539
+ output.push(
2540
+ this.p1.x,
2541
+ this.p1.y,
2542
+ this.p2.x,
2543
+ this.p2.y
2544
+ );
2545
+ return output;
2546
+ }
2547
+ getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
2548
+ const { p1, p2 } = this;
2549
+ min.x = Math.min(min.x, p1.x, p2.x);
2550
+ min.y = Math.min(min.y, p1.y, p2.y);
2551
+ max.x = Math.max(max.x, p1.x, p2.x);
2552
+ max.y = Math.max(max.y, p1.y, p2.y);
2553
+ return { min: min.finite(), max: max.finite() };
2554
+ }
2555
+ toCommands() {
2556
+ const { p1, p2 } = this;
2557
+ return [
2558
+ { type: "M", x: p1.x, y: p1.y },
2559
+ { type: "L", x: p2.x, y: p2.y }
2560
+ ];
2561
+ }
2562
+ fillTriangulate(options = {}) {
2563
+ let {
2564
+ vertices = [],
2565
+ indices = [],
2566
+ verticesStride = 2,
2567
+ verticesOffset = vertices.length / verticesStride,
2568
+ indicesOffset = indices.length
2569
+ } = options;
2570
+ const minX = Math.min(this.p1.x, this.p2.x);
2571
+ const maxX = Math.max(this.p1.x, this.p2.x);
2572
+ const minY = Math.min(this.p1.y, this.p2.y);
2573
+ const maxY = Math.max(this.p1.y, this.p2.y);
2574
+ const x = minX;
2575
+ const y = minY;
2576
+ const width = maxX - minX;
2577
+ const height = maxY - minY;
2578
+ const points = [
2579
+ x,
2580
+ y,
2581
+ x + width,
2582
+ y,
2583
+ x + width,
2584
+ y + height,
2585
+ x,
2586
+ y + height
2587
+ ];
2588
+ let count = 0;
2589
+ verticesOffset *= verticesStride;
2590
+ vertices[verticesOffset + count] = points[0];
2591
+ vertices[verticesOffset + count + 1] = points[1];
2592
+ count += verticesStride;
2593
+ vertices[verticesOffset + count] = points[2];
2594
+ vertices[verticesOffset + count + 1] = points[3];
2595
+ count += verticesStride;
2596
+ vertices[verticesOffset + count] = points[6];
2597
+ vertices[verticesOffset + count + 1] = points[7];
2598
+ count += verticesStride;
2599
+ vertices[verticesOffset + count] = points[4];
2600
+ vertices[verticesOffset + count + 1] = points[5];
2601
+ count += verticesStride;
2602
+ const verticesIndex = verticesOffset / verticesStride;
2603
+ indices[indicesOffset++] = verticesIndex;
2604
+ indices[indicesOffset++] = verticesIndex + 1;
2605
+ indices[indicesOffset++] = verticesIndex + 2;
2606
+ indices[indicesOffset++] = verticesIndex + 1;
2607
+ indices[indicesOffset++] = verticesIndex + 3;
2608
+ indices[indicesOffset++] = verticesIndex + 2;
2609
+ return { vertices, indices };
2610
+ }
2611
+ drawTo(ctx) {
2612
+ const { p1, p2 } = this;
2613
+ ctx.lineTo(p1.x, p1.y);
2614
+ ctx.lineTo(p2.x, p2.y);
2615
+ return this;
2616
+ }
2617
+ copy(source) {
2618
+ super.copy(source);
2619
+ this.p1.copy(source.p1);
2620
+ this.p2.copy(source.p2);
2621
+ return this;
2622
+ }
2623
+ }
2624
+
2694
2625
  class PloygonCurve extends CompositeCurve {
2695
2626
  //
2696
2627
  }
@@ -2786,7 +2717,7 @@ class QuadraticBezierCurve extends Curve {
2786
2717
  min.y = Math.min(min.y, p1.y, p2.y, y1, y2);
2787
2718
  max.x = Math.max(max.x, p1.x, p2.x, x1, x2);
2788
2719
  max.y = Math.max(max.y, p1.y, p2.y, y1, y2);
2789
- return { min, max };
2720
+ return { min: min.finite(), max: max.finite() };
2790
2721
  }
2791
2722
  toCommands() {
2792
2723
  const { p1, cp, p2 } = this;
@@ -3428,7 +3359,7 @@ class Path2D extends CompositeCurve {
3428
3359
  }
3429
3360
  }
3430
3361
  });
3431
- return { min, max };
3362
+ return { min: min.finite(), max: max.finite() };
3432
3363
  }
3433
3364
  strokeTriangulate(options) {
3434
3365
  const indices = options?.indices ?? [];
@@ -3564,6 +3495,61 @@ class Path2DSet {
3564
3495
  }
3565
3496
  }
3566
3497
 
3498
+ class FFDControlGrid {
3499
+ constructor(rows, cols, width = 1, height = 1) {
3500
+ this.rows = rows;
3501
+ this.cols = cols;
3502
+ this.width = width;
3503
+ this.height = height;
3504
+ for (let i = 0; i < rows; i++) {
3505
+ this.controlPoints[i] = [];
3506
+ for (let j = 0; j < cols; j++) {
3507
+ this.controlPoints[i][j] = {
3508
+ x: j / (cols - 1) * width,
3509
+ y: i / (rows - 1) * height
3510
+ };
3511
+ }
3512
+ }
3513
+ }
3514
+ controlPoints = [];
3515
+ moveControlPoint(i, j, dx, dy) {
3516
+ this.controlPoints[i][j].x += dx;
3517
+ this.controlPoints[i][j].y += dy;
3518
+ return this;
3519
+ }
3520
+ }
3521
+ function bsplineBasis(t) {
3522
+ const B = [];
3523
+ B[0] = (1 - t) ** 3 / 6;
3524
+ B[1] = (3 * t ** 3 - 6 * t ** 2 + 4) / 6;
3525
+ B[2] = (-3 * t ** 3 + 3 * t ** 2 + 3 * t + 1) / 6;
3526
+ B[3] = t ** 3 / 6;
3527
+ return B;
3528
+ }
3529
+ function applyFFD(point, grid, width = grid.width, height = grid.height) {
3530
+ const s = point.x / width * (grid.cols - 1);
3531
+ const t = point.y / height * (grid.rows - 1);
3532
+ const i = Math.floor(s);
3533
+ const j = Math.floor(t);
3534
+ const u = s - i;
3535
+ const v = t - j;
3536
+ const bu = bsplineBasis(u);
3537
+ const bv = bsplineBasis(v);
3538
+ let x = 0;
3539
+ let y = 0;
3540
+ for (let m = 0; m < 4; m++) {
3541
+ for (let n = 0; n < 4; n++) {
3542
+ const row = Math.min(Math.max(j - 1 + m, 0), grid.rows - 1);
3543
+ const col = Math.min(Math.max(i - 1 + n, 0), grid.cols - 1);
3544
+ const cp = grid.controlPoints[row][col];
3545
+ const weight = bu[n] * bv[m];
3546
+ x += cp.x * weight;
3547
+ y += cp.y * weight;
3548
+ }
3549
+ }
3550
+ point.set(x, y);
3551
+ }
3552
+
3567
3553
  const dataUri = "data:image/svg+xml;";
3568
3554
  const base64DataUri = `${dataUri}base64,`;
3569
3555
  const utf8DataUri = `${dataUri}charset=utf8,`;
@@ -4101,6 +4087,7 @@ exports.Curve = Curve;
4101
4087
  exports.CurvePath = CurvePath;
4102
4088
  exports.EllipseCurve = EllipseCurve;
4103
4089
  exports.EquilateralPloygonCurve = EquilateralPloygonCurve;
4090
+ exports.FFDControlGrid = FFDControlGrid;
4104
4091
  exports.LineCurve = LineCurve;
4105
4092
  exports.Matrix3 = Matrix3;
4106
4093
  exports.Path2D = Path2D;
@@ -4111,6 +4098,7 @@ exports.RectangleCurve = RectangleCurve;
4111
4098
  exports.RoundRectangleCurve = RoundRectangleCurve;
4112
4099
  exports.SplineCurve = SplineCurve;
4113
4100
  exports.Vector2 = Vector2;
4101
+ exports.applyFFD = applyFFD;
4114
4102
  exports.catmullRom = catmullRom;
4115
4103
  exports.cubicBezier = cubicBezier;
4116
4104
  exports.drawPoint = drawPoint;
package/dist/index.d.cts CHANGED
@@ -81,6 +81,7 @@ declare class Vector2 {
81
81
  static get MIN(): Vector2;
82
82
  get array(): [number, number];
83
83
  constructor(x?: number, y?: number);
84
+ finite(): this;
84
85
  set(x: number, y: number): this;
85
86
  add(vec: VectorLike): this;
86
87
  sub(vec: VectorLike): this;
@@ -521,6 +522,20 @@ declare class Path2DSet {
521
522
 
522
523
  declare function setCanvasContext(ctx: CanvasRenderingContext2D, style: Partial<Path2DStyle>): void;
523
524
 
525
+ declare class FFDControlGrid {
526
+ rows: number;
527
+ cols: number;
528
+ width: number;
529
+ height: number;
530
+ controlPoints: {
531
+ x: number;
532
+ y: number;
533
+ }[][];
534
+ constructor(rows: number, cols: number, width?: number, height?: number);
535
+ moveControlPoint(i: number, j: number, dx: number, dy: number): this;
536
+ }
537
+ declare function applyFFD(point: Vector2, grid: FFDControlGrid, width?: number, height?: number): void;
538
+
524
539
  /**
525
540
  * @link https://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
526
541
  * @link https://mortoray.com/2017/02/16/rendering-an-svg-elliptical-arc-as-bezier-curves/ Appendix: Endpoint to center arc conversion
@@ -549,4 +564,4 @@ declare function svgToDOM(svg: string | SVGElement): SVGElement;
549
564
 
550
565
  declare function svgToPath2DSet(svg: string | SVGElement): Path2DSet;
551
566
 
552
- export { ArcCurve, BoundingBox, CompositeCurve, CubicBezierCurve, Curve, CurvePath, type DrawPointOptions, EllipseCurve, EquilateralPloygonCurve, type FillRule, type FillTriangulateOptions, type FillTriangulatedResult, type LineCap, LineCurve, type LineJoin, type LineStyle, Matrix3, Path2D, type Path2DCommand, type Path2DData, type Path2DDrawStyle, Path2DSet, type Path2DStyle, PloygonCurve, QuadraticBezierCurve, RectangleCurve, RoundRectangleCurve, SplineCurve, type StrokeLinecap, type StrokeLinejoin, type StrokeTriangulateOptions, type StrokeTriangulatedResult, Vector2, type VectorLike, catmullRom, cubicBezier, drawPoint, fillTriangulate, getAdaptiveCubicBezierCurvePoints, getAdaptiveQuadraticBezierCurvePoints, parseArcCommand, parsePathDataArgs, quadraticBezier, setCanvasContext, strokeTriangulate, svgPathCommandsAddToPath2D, svgPathCommandsToData, svgPathDataToCommands, svgToDOM, svgToPath2DSet };
567
+ export { ArcCurve, BoundingBox, CompositeCurve, CubicBezierCurve, Curve, CurvePath, type DrawPointOptions, EllipseCurve, EquilateralPloygonCurve, FFDControlGrid, type FillRule, type FillTriangulateOptions, type FillTriangulatedResult, type LineCap, LineCurve, type LineJoin, type LineStyle, Matrix3, Path2D, type Path2DCommand, type Path2DData, type Path2DDrawStyle, Path2DSet, type Path2DStyle, PloygonCurve, QuadraticBezierCurve, RectangleCurve, RoundRectangleCurve, SplineCurve, type StrokeLinecap, type StrokeLinejoin, type StrokeTriangulateOptions, type StrokeTriangulatedResult, Vector2, type VectorLike, applyFFD, catmullRom, cubicBezier, drawPoint, fillTriangulate, getAdaptiveCubicBezierCurvePoints, getAdaptiveQuadraticBezierCurvePoints, parseArcCommand, parsePathDataArgs, quadraticBezier, setCanvasContext, strokeTriangulate, svgPathCommandsAddToPath2D, svgPathCommandsToData, svgPathDataToCommands, svgToDOM, svgToPath2DSet };
package/dist/index.d.mts CHANGED
@@ -81,6 +81,7 @@ declare class Vector2 {
81
81
  static get MIN(): Vector2;
82
82
  get array(): [number, number];
83
83
  constructor(x?: number, y?: number);
84
+ finite(): this;
84
85
  set(x: number, y: number): this;
85
86
  add(vec: VectorLike): this;
86
87
  sub(vec: VectorLike): this;
@@ -521,6 +522,20 @@ declare class Path2DSet {
521
522
 
522
523
  declare function setCanvasContext(ctx: CanvasRenderingContext2D, style: Partial<Path2DStyle>): void;
523
524
 
525
+ declare class FFDControlGrid {
526
+ rows: number;
527
+ cols: number;
528
+ width: number;
529
+ height: number;
530
+ controlPoints: {
531
+ x: number;
532
+ y: number;
533
+ }[][];
534
+ constructor(rows: number, cols: number, width?: number, height?: number);
535
+ moveControlPoint(i: number, j: number, dx: number, dy: number): this;
536
+ }
537
+ declare function applyFFD(point: Vector2, grid: FFDControlGrid, width?: number, height?: number): void;
538
+
524
539
  /**
525
540
  * @link https://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
526
541
  * @link https://mortoray.com/2017/02/16/rendering-an-svg-elliptical-arc-as-bezier-curves/ Appendix: Endpoint to center arc conversion
@@ -549,4 +564,4 @@ declare function svgToDOM(svg: string | SVGElement): SVGElement;
549
564
 
550
565
  declare function svgToPath2DSet(svg: string | SVGElement): Path2DSet;
551
566
 
552
- export { ArcCurve, BoundingBox, CompositeCurve, CubicBezierCurve, Curve, CurvePath, type DrawPointOptions, EllipseCurve, EquilateralPloygonCurve, type FillRule, type FillTriangulateOptions, type FillTriangulatedResult, type LineCap, LineCurve, type LineJoin, type LineStyle, Matrix3, Path2D, type Path2DCommand, type Path2DData, type Path2DDrawStyle, Path2DSet, type Path2DStyle, PloygonCurve, QuadraticBezierCurve, RectangleCurve, RoundRectangleCurve, SplineCurve, type StrokeLinecap, type StrokeLinejoin, type StrokeTriangulateOptions, type StrokeTriangulatedResult, Vector2, type VectorLike, catmullRom, cubicBezier, drawPoint, fillTriangulate, getAdaptiveCubicBezierCurvePoints, getAdaptiveQuadraticBezierCurvePoints, parseArcCommand, parsePathDataArgs, quadraticBezier, setCanvasContext, strokeTriangulate, svgPathCommandsAddToPath2D, svgPathCommandsToData, svgPathDataToCommands, svgToDOM, svgToPath2DSet };
567
+ export { ArcCurve, BoundingBox, CompositeCurve, CubicBezierCurve, Curve, CurvePath, type DrawPointOptions, EllipseCurve, EquilateralPloygonCurve, FFDControlGrid, type FillRule, type FillTriangulateOptions, type FillTriangulatedResult, type LineCap, LineCurve, type LineJoin, type LineStyle, Matrix3, Path2D, type Path2DCommand, type Path2DData, type Path2DDrawStyle, Path2DSet, type Path2DStyle, PloygonCurve, QuadraticBezierCurve, RectangleCurve, RoundRectangleCurve, SplineCurve, type StrokeLinecap, type StrokeLinejoin, type StrokeTriangulateOptions, type StrokeTriangulatedResult, Vector2, type VectorLike, applyFFD, catmullRom, cubicBezier, drawPoint, fillTriangulate, getAdaptiveCubicBezierCurvePoints, getAdaptiveQuadraticBezierCurvePoints, parseArcCommand, parsePathDataArgs, quadraticBezier, setCanvasContext, strokeTriangulate, svgPathCommandsAddToPath2D, svgPathCommandsToData, svgPathDataToCommands, svgToDOM, svgToPath2DSet };
package/dist/index.d.ts CHANGED
@@ -81,6 +81,7 @@ declare class Vector2 {
81
81
  static get MIN(): Vector2;
82
82
  get array(): [number, number];
83
83
  constructor(x?: number, y?: number);
84
+ finite(): this;
84
85
  set(x: number, y: number): this;
85
86
  add(vec: VectorLike): this;
86
87
  sub(vec: VectorLike): this;
@@ -521,6 +522,20 @@ declare class Path2DSet {
521
522
 
522
523
  declare function setCanvasContext(ctx: CanvasRenderingContext2D, style: Partial<Path2DStyle>): void;
523
524
 
525
+ declare class FFDControlGrid {
526
+ rows: number;
527
+ cols: number;
528
+ width: number;
529
+ height: number;
530
+ controlPoints: {
531
+ x: number;
532
+ y: number;
533
+ }[][];
534
+ constructor(rows: number, cols: number, width?: number, height?: number);
535
+ moveControlPoint(i: number, j: number, dx: number, dy: number): this;
536
+ }
537
+ declare function applyFFD(point: Vector2, grid: FFDControlGrid, width?: number, height?: number): void;
538
+
524
539
  /**
525
540
  * @link https://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
526
541
  * @link https://mortoray.com/2017/02/16/rendering-an-svg-elliptical-arc-as-bezier-curves/ Appendix: Endpoint to center arc conversion
@@ -549,4 +564,4 @@ declare function svgToDOM(svg: string | SVGElement): SVGElement;
549
564
 
550
565
  declare function svgToPath2DSet(svg: string | SVGElement): Path2DSet;
551
566
 
552
- export { ArcCurve, BoundingBox, CompositeCurve, CubicBezierCurve, Curve, CurvePath, type DrawPointOptions, EllipseCurve, EquilateralPloygonCurve, type FillRule, type FillTriangulateOptions, type FillTriangulatedResult, type LineCap, LineCurve, type LineJoin, type LineStyle, Matrix3, Path2D, type Path2DCommand, type Path2DData, type Path2DDrawStyle, Path2DSet, type Path2DStyle, PloygonCurve, QuadraticBezierCurve, RectangleCurve, RoundRectangleCurve, SplineCurve, type StrokeLinecap, type StrokeLinejoin, type StrokeTriangulateOptions, type StrokeTriangulatedResult, Vector2, type VectorLike, catmullRom, cubicBezier, drawPoint, fillTriangulate, getAdaptiveCubicBezierCurvePoints, getAdaptiveQuadraticBezierCurvePoints, parseArcCommand, parsePathDataArgs, quadraticBezier, setCanvasContext, strokeTriangulate, svgPathCommandsAddToPath2D, svgPathCommandsToData, svgPathDataToCommands, svgToDOM, svgToPath2DSet };
567
+ export { ArcCurve, BoundingBox, CompositeCurve, CubicBezierCurve, Curve, CurvePath, type DrawPointOptions, EllipseCurve, EquilateralPloygonCurve, FFDControlGrid, type FillRule, type FillTriangulateOptions, type FillTriangulatedResult, type LineCap, LineCurve, type LineJoin, type LineStyle, Matrix3, Path2D, type Path2DCommand, type Path2DData, type Path2DDrawStyle, Path2DSet, type Path2DStyle, PloygonCurve, QuadraticBezierCurve, RectangleCurve, RoundRectangleCurve, SplineCurve, type StrokeLinecap, type StrokeLinejoin, type StrokeTriangulateOptions, type StrokeTriangulatedResult, Vector2, type VectorLike, applyFFD, catmullRom, cubicBezier, drawPoint, fillTriangulate, getAdaptiveCubicBezierCurvePoints, getAdaptiveQuadraticBezierCurvePoints, parseArcCommand, parsePathDataArgs, quadraticBezier, setCanvasContext, strokeTriangulate, svgPathCommandsAddToPath2D, svgPathCommandsToData, svgPathDataToCommands, svgToDOM, svgToPath2DSet };