@emasoft/svg-matrix 1.0.30 → 1.0.31

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.
Files changed (45) hide show
  1. package/bin/svg-matrix.js +310 -61
  2. package/bin/svglinter.cjs +102 -3
  3. package/bin/svgm.js +236 -27
  4. package/package.json +1 -1
  5. package/src/animation-optimization.js +137 -17
  6. package/src/animation-references.js +123 -6
  7. package/src/arc-length.js +213 -4
  8. package/src/bezier-analysis.js +217 -21
  9. package/src/bezier-intersections.js +275 -12
  10. package/src/browser-verify.js +237 -4
  11. package/src/clip-path-resolver.js +168 -0
  12. package/src/convert-path-data.js +479 -28
  13. package/src/css-specificity.js +73 -10
  14. package/src/douglas-peucker.js +219 -2
  15. package/src/flatten-pipeline.js +284 -26
  16. package/src/geometry-to-path.js +250 -25
  17. package/src/gjk-collision.js +236 -33
  18. package/src/index.js +261 -3
  19. package/src/inkscape-support.js +86 -28
  20. package/src/logger.js +48 -3
  21. package/src/marker-resolver.js +278 -74
  22. package/src/mask-resolver.js +265 -66
  23. package/src/matrix.js +44 -5
  24. package/src/mesh-gradient.js +352 -102
  25. package/src/off-canvas-detection.js +382 -13
  26. package/src/path-analysis.js +192 -18
  27. package/src/path-data-plugins.js +309 -5
  28. package/src/path-optimization.js +129 -5
  29. package/src/path-simplification.js +188 -32
  30. package/src/pattern-resolver.js +454 -106
  31. package/src/polygon-clip.js +324 -1
  32. package/src/svg-boolean-ops.js +226 -9
  33. package/src/svg-collections.js +7 -5
  34. package/src/svg-flatten.js +386 -62
  35. package/src/svg-parser.js +179 -8
  36. package/src/svg-rendering-context.js +235 -6
  37. package/src/svg-toolbox.js +45 -8
  38. package/src/svg2-polyfills.js +40 -10
  39. package/src/transform-decomposition.js +258 -32
  40. package/src/transform-optimization.js +259 -13
  41. package/src/transforms2d.js +82 -9
  42. package/src/transforms3d.js +62 -10
  43. package/src/use-symbol-resolver.js +286 -42
  44. package/src/vector.js +64 -8
  45. package/src/verification.js +392 -1
@@ -42,7 +42,15 @@ const SUBDIVISION_THRESHOLD = 2;
42
42
  * const p = point("3.14159265358979323846", "2.71828182845904523536");
43
43
  */
44
44
  export function point(x, y) {
45
- return { x: D(x), y: D(y) };
45
+ if (x === null || x === undefined)
46
+ throw new Error("point: x parameter is required");
47
+ if (y === null || y === undefined)
48
+ throw new Error("point: y parameter is required");
49
+ const dx = D(x);
50
+ const dy = D(y);
51
+ if (!dx.isFinite()) throw new Error(`point: x must be finite, got ${x}`);
52
+ if (!dy.isFinite()) throw new Error(`point: y must be finite, got ${y}`);
53
+ return { x: dx, y: dy };
46
54
  }
47
55
 
48
56
  /**
@@ -66,12 +74,32 @@ export function point(x, y) {
66
74
  * const blue = color(0, 0, 255, 128);
67
75
  */
68
76
  export function color(r, g, b, a = 255) {
69
- return {
70
- r: Math.round(r),
71
- g: Math.round(g),
72
- b: Math.round(b),
73
- a: Math.round(a),
74
- };
77
+ if (r === null || r === undefined)
78
+ throw new Error("color: r parameter is required");
79
+ if (g === null || g === undefined)
80
+ throw new Error("color: g parameter is required");
81
+ if (b === null || b === undefined)
82
+ throw new Error("color: b parameter is required");
83
+ if (!Number.isFinite(r)) throw new Error(`color: r must be finite, got ${r}`);
84
+ if (!Number.isFinite(g)) throw new Error(`color: g must be finite, got ${g}`);
85
+ if (!Number.isFinite(b)) throw new Error(`color: b must be finite, got ${b}`);
86
+ if (!Number.isFinite(a)) throw new Error(`color: a must be finite, got ${a}`);
87
+
88
+ const rVal = Math.round(r);
89
+ const gVal = Math.round(g);
90
+ const bVal = Math.round(b);
91
+ const aVal = Math.round(a);
92
+
93
+ if (rVal < 0 || rVal > 255)
94
+ throw new Error(`color: r must be 0-255, got ${rVal}`);
95
+ if (gVal < 0 || gVal > 255)
96
+ throw new Error(`color: g must be 0-255, got ${gVal}`);
97
+ if (bVal < 0 || bVal > 255)
98
+ throw new Error(`color: b must be 0-255, got ${bVal}`);
99
+ if (aVal < 0 || aVal > 255)
100
+ throw new Error(`color: a must be 0-255, got ${aVal}`);
101
+
102
+ return { r: rVal, g: gVal, b: bVal, a: aVal };
75
103
  }
76
104
 
77
105
  /**
@@ -105,20 +133,26 @@ export function color(r, g, b, a = 255) {
105
133
  * // {r: 0, g: 255, b: 255, a: 255}
106
134
  */
107
135
  export function parseColor(colorStr, opacity = 1) {
108
- if (!colorStr) return color(0, 0, 0, 255);
136
+ if (!colorStr || typeof colorStr !== "string") return color(0, 0, 0, 255);
137
+ if (!Number.isFinite(opacity))
138
+ throw new Error(`parseColor: opacity must be finite, got ${opacity}`);
139
+ if (opacity < 0 || opacity > 1)
140
+ throw new Error(`parseColor: opacity must be 0-1, got ${opacity}`);
109
141
 
110
142
  // Handle rgb() and rgba()
111
143
  const rgbMatch = colorStr.match(
112
144
  /rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)/i,
113
145
  );
114
146
  if (rgbMatch) {
115
- const a = rgbMatch[4] !== undefined ? parseFloat(rgbMatch[4]) * 255 : 255;
116
- return color(
117
- parseInt(rgbMatch[1], 10),
118
- parseInt(rgbMatch[2], 10),
119
- parseInt(rgbMatch[3], 10),
120
- a * opacity,
121
- );
147
+ const r = parseInt(rgbMatch[1], 10);
148
+ const g = parseInt(rgbMatch[2], 10);
149
+ const b = parseInt(rgbMatch[3], 10);
150
+ if (!Number.isFinite(r) || !Number.isFinite(g) || !Number.isFinite(b)) {
151
+ return color(0, 0, 0, 255 * opacity);
152
+ }
153
+ const a = rgbMatch[4] !== undefined ? parseFloat(rgbMatch[4]) : 1;
154
+ if (!Number.isFinite(a)) return color(r, g, b, 255 * opacity);
155
+ return color(r, g, b, Math.min(255, a * 255 * opacity));
122
156
  }
123
157
 
124
158
  // Handle hex colors
@@ -199,7 +233,18 @@ export function parseColor(colorStr, opacity = 1) {
199
233
  * // {r: 100, g: 150, b: 200, a: 64}
200
234
  */
201
235
  export function lerpColor(c1, c2, t) {
236
+ if (!c1 || !c2) throw new Error("lerpColor: c1 and c2 are required");
237
+ if (!("r" in c1 && "g" in c1 && "b" in c1 && "a" in c1))
238
+ throw new Error("lerpColor: c1 must have r, g, b, a properties");
239
+ if (!("r" in c2 && "g" in c2 && "b" in c2 && "a" in c2))
240
+ throw new Error("lerpColor: c2 must have r, g, b, a properties");
241
+ if (t === null || t === undefined)
242
+ throw new Error("lerpColor: t parameter is required");
243
+
202
244
  const tNum = Number(t);
245
+ if (!Number.isFinite(tNum))
246
+ throw new Error(`lerpColor: t must be finite, got ${t}`);
247
+
203
248
  const mt = 1 - tNum;
204
249
  return color(
205
250
  c1.r * mt + c2.r * tNum,
@@ -238,8 +283,28 @@ export function lerpColor(c1, c2, t) {
238
283
  * // Average of all four corners
239
284
  */
240
285
  export function bilinearColor(c00, c10, c01, c11, u, v) {
286
+ if (!c00 || !c10 || !c01 || !c11)
287
+ throw new Error("bilinearColor: all corner colors are required");
288
+ if (!("r" in c00 && "g" in c00 && "b" in c00 && "a" in c00))
289
+ throw new Error("bilinearColor: c00 must have r, g, b, a properties");
290
+ if (!("r" in c10 && "g" in c10 && "b" in c10 && "a" in c10))
291
+ throw new Error("bilinearColor: c10 must have r, g, b, a properties");
292
+ if (!("r" in c01 && "g" in c01 && "b" in c01 && "a" in c01))
293
+ throw new Error("bilinearColor: c01 must have r, g, b, a properties");
294
+ if (!("r" in c11 && "g" in c11 && "b" in c11 && "a" in c11))
295
+ throw new Error("bilinearColor: c11 must have r, g, b, a properties");
296
+ if (u === null || u === undefined)
297
+ throw new Error("bilinearColor: u parameter is required");
298
+ if (v === null || v === undefined)
299
+ throw new Error("bilinearColor: v parameter is required");
300
+
241
301
  const uNum = Number(u);
242
302
  const vNum = Number(v);
303
+ if (!Number.isFinite(uNum))
304
+ throw new Error(`bilinearColor: u must be finite, got ${u}`);
305
+ if (!Number.isFinite(vNum))
306
+ throw new Error(`bilinearColor: v must be finite, got ${v}`);
307
+
243
308
  const mu = 1 - uNum;
244
309
  const mv = 1 - vNum;
245
310
 
@@ -302,21 +367,38 @@ export function bilinearColor(c00, c10, c01, c11, u, v) {
302
367
  * }
303
368
  */
304
369
  export function evalCubicBezier(p0, p1, p2, p3, t) {
305
- const mt = D(1).minus(t);
370
+ if (!p0 || !p1 || !p2 || !p3)
371
+ throw new Error("evalCubicBezier: all points are required");
372
+ if (!("x" in p0 && "y" in p0))
373
+ throw new Error("evalCubicBezier: p0 must have x, y properties");
374
+ if (!("x" in p1 && "y" in p1))
375
+ throw new Error("evalCubicBezier: p1 must have x, y properties");
376
+ if (!("x" in p2 && "y" in p2))
377
+ throw new Error("evalCubicBezier: p2 must have x, y properties");
378
+ if (!("x" in p3 && "y" in p3))
379
+ throw new Error("evalCubicBezier: p3 must have x, y properties");
380
+ if (t === null || t === undefined)
381
+ throw new Error("evalCubicBezier: t parameter is required");
382
+
383
+ const tDecimal = D(t);
384
+ if (!tDecimal.isFinite())
385
+ throw new Error(`evalCubicBezier: t must be finite, got ${t}`);
386
+
387
+ const mt = D(1).minus(tDecimal);
306
388
  const mt2 = mt.mul(mt);
307
389
  const mt3 = mt2.mul(mt);
308
- const t2 = t.mul(t);
309
- const t3 = t2.mul(t);
390
+ const t2 = tDecimal.mul(tDecimal);
391
+ const t3 = t2.mul(tDecimal);
310
392
 
311
393
  return {
312
394
  x: mt3
313
395
  .mul(p0.x)
314
- .plus(D(3).mul(mt2).mul(t).mul(p1.x))
396
+ .plus(D(3).mul(mt2).mul(tDecimal).mul(p1.x))
315
397
  .plus(D(3).mul(mt).mul(t2).mul(p2.x))
316
398
  .plus(t3.mul(p3.x)),
317
399
  y: mt3
318
400
  .mul(p0.y)
319
- .plus(D(3).mul(mt2).mul(t).mul(p1.y))
401
+ .plus(D(3).mul(mt2).mul(tDecimal).mul(p1.y))
320
402
  .plus(D(3).mul(mt).mul(t2).mul(p2.y))
321
403
  .plus(t3.mul(p3.y)),
322
404
  };
@@ -358,7 +440,24 @@ export function evalCubicBezier(p0, p1, p2, p3, t) {
358
440
  * }
359
441
  */
360
442
  export function splitBezier(curve) {
443
+ if (!Array.isArray(curve))
444
+ throw new Error("splitBezier: curve must be an array");
445
+ if (curve.length !== 4)
446
+ throw new Error(
447
+ `splitBezier: curve must have exactly 4 points, got ${curve.length}`,
448
+ );
449
+
361
450
  const [p0, p1, p2, p3] = curve;
451
+ if (!p0 || !p1 || !p2 || !p3)
452
+ throw new Error("splitBezier: all curve points must be defined");
453
+ if (!("x" in p0 && "y" in p0))
454
+ throw new Error("splitBezier: p0 must have x, y properties");
455
+ if (!("x" in p1 && "y" in p1))
456
+ throw new Error("splitBezier: p1 must have x, y properties");
457
+ if (!("x" in p2 && "y" in p2))
458
+ throw new Error("splitBezier: p2 must have x, y properties");
459
+ if (!("x" in p3 && "y" in p3))
460
+ throw new Error("splitBezier: p3 must have x, y properties");
362
461
 
363
462
  // De Casteljau subdivision at t=0.5
364
463
  const mid = (a, b) => ({
@@ -434,6 +533,55 @@ export class CoonsPatch {
434
533
  * );
435
534
  */
436
535
  constructor(top, right, bottom, left, colors) {
536
+ if (!Array.isArray(top) || top.length !== 4)
537
+ throw new Error("CoonsPatch: top must be an array of 4 points");
538
+ if (!Array.isArray(right) || right.length !== 4)
539
+ throw new Error("CoonsPatch: right must be an array of 4 points");
540
+ if (!Array.isArray(bottom) || bottom.length !== 4)
541
+ throw new Error("CoonsPatch: bottom must be an array of 4 points");
542
+ if (!Array.isArray(left) || left.length !== 4)
543
+ throw new Error("CoonsPatch: left must be an array of 4 points");
544
+
545
+ // Validate all points have x, y properties
546
+ for (let i = 0; i < 4; i++) {
547
+ if (!top[i] || !("x" in top[i]) || !("y" in top[i]))
548
+ throw new Error(`CoonsPatch: top[${i}] must have x, y properties`);
549
+ if (!right[i] || !("x" in right[i]) || !("y" in right[i]))
550
+ throw new Error(`CoonsPatch: right[${i}] must have x, y properties`);
551
+ if (!bottom[i] || !("x" in bottom[i]) || !("y" in bottom[i]))
552
+ throw new Error(`CoonsPatch: bottom[${i}] must have x, y properties`);
553
+ if (!left[i] || !("x" in left[i]) || !("y" in left[i]))
554
+ throw new Error(`CoonsPatch: left[${i}] must have x, y properties`);
555
+ }
556
+
557
+ if (
558
+ !Array.isArray(colors) ||
559
+ colors.length !== 2 ||
560
+ !Array.isArray(colors[0]) ||
561
+ colors[0].length !== 2 ||
562
+ !Array.isArray(colors[1]) ||
563
+ colors[1].length !== 2
564
+ ) {
565
+ throw new Error("CoonsPatch: colors must be a 2x2 array");
566
+ }
567
+
568
+ // Validate all colors have r, g, b, a properties
569
+ for (let i = 0; i < 2; i++) {
570
+ for (let j = 0; j < 2; j++) {
571
+ if (
572
+ !colors[i][j] ||
573
+ !("r" in colors[i][j]) ||
574
+ !("g" in colors[i][j]) ||
575
+ !("b" in colors[i][j]) ||
576
+ !("a" in colors[i][j])
577
+ ) {
578
+ throw new Error(
579
+ `CoonsPatch: colors[${i}][${j}] must have r, g, b, a properties`,
580
+ );
581
+ }
582
+ }
583
+ }
584
+
437
585
  this.top = top;
438
586
  this.right = right;
439
587
  this.bottom = bottom;
@@ -482,11 +630,23 @@ export class CoonsPatch {
482
630
  * }
483
631
  */
484
632
  evaluate(u, v) {
633
+ if (u === null || u === undefined)
634
+ throw new Error("CoonsPatch.evaluate: u parameter is required");
635
+ if (v === null || v === undefined)
636
+ throw new Error("CoonsPatch.evaluate: v parameter is required");
637
+
638
+ const uDecimal = D(u);
639
+ const vDecimal = D(v);
640
+ if (!uDecimal.isFinite())
641
+ throw new Error(`CoonsPatch.evaluate: u must be finite, got ${u}`);
642
+ if (!vDecimal.isFinite())
643
+ throw new Error(`CoonsPatch.evaluate: v must be finite, got ${v}`);
644
+
485
645
  // Boundary curves
486
- const Lc = evalCubicBezier(...this.top, u); // L_c(u,0)
487
- const Ld = evalCubicBezier(...this.bottom, u); // L_d(u,1)
488
- const La = evalCubicBezier(...this.left, v); // L_a(0,v)
489
- const Lb = evalCubicBezier(...this.right, v); // L_b(1,v)
646
+ const Lc = evalCubicBezier(...this.top, uDecimal); // L_c(u,0)
647
+ const Ld = evalCubicBezier(...this.bottom, uDecimal); // L_d(u,1)
648
+ const La = evalCubicBezier(...this.left, vDecimal); // L_a(0,v)
649
+ const Lb = evalCubicBezier(...this.right, vDecimal); // L_b(1,v)
490
650
 
491
651
  // Corner points
492
652
  const P00 = this.top[0];
@@ -496,30 +656,30 @@ export class CoonsPatch {
496
656
 
497
657
  // Coons patch formula: S(u,v) = Lc(u) + Ld(u) - B(u,v)
498
658
  // where B is bilinear interpolation of corners
499
- const mu = D(1).minus(u);
500
- const mv = D(1).minus(v);
659
+ const mu = D(1).minus(uDecimal);
660
+ const mv = D(1).minus(vDecimal);
501
661
 
502
662
  // Ruled surface in u direction
503
- const Sc_x = mv.mul(Lc.x).plus(v.mul(Ld.x));
504
- const Sc_y = mv.mul(Lc.y).plus(v.mul(Ld.y));
663
+ const Sc_x = mv.mul(Lc.x).plus(vDecimal.mul(Ld.x));
664
+ const Sc_y = mv.mul(Lc.y).plus(vDecimal.mul(Ld.y));
505
665
 
506
666
  // Ruled surface in v direction
507
- const Sd_x = mu.mul(La.x).plus(u.mul(Lb.x));
508
- const Sd_y = mu.mul(La.y).plus(u.mul(Lb.y));
667
+ const Sd_x = mu.mul(La.x).plus(uDecimal.mul(Lb.x));
668
+ const Sd_y = mu.mul(La.y).plus(uDecimal.mul(Lb.y));
509
669
 
510
670
  // Bilinear interpolation of corners
511
671
  const B_x = mu
512
672
  .mul(mv)
513
673
  .mul(P00.x)
514
- .plus(u.mul(mv).mul(P10.x))
515
- .plus(mu.mul(v).mul(P01.x))
516
- .plus(u.mul(v).mul(P11.x));
674
+ .plus(uDecimal.mul(mv).mul(P10.x))
675
+ .plus(mu.mul(vDecimal).mul(P01.x))
676
+ .plus(uDecimal.mul(vDecimal).mul(P11.x));
517
677
  const B_y = mu
518
678
  .mul(mv)
519
679
  .mul(P00.y)
520
- .plus(u.mul(mv).mul(P10.y))
521
- .plus(mu.mul(v).mul(P01.y))
522
- .plus(u.mul(v).mul(P11.y));
680
+ .plus(uDecimal.mul(mv).mul(P10.y))
681
+ .plus(mu.mul(vDecimal).mul(P01.y))
682
+ .plus(uDecimal.mul(vDecimal).mul(P11.y));
523
683
 
524
684
  // Coons formula: Sc + Sd - B
525
685
  const pt = {
@@ -533,8 +693,8 @@ export class CoonsPatch {
533
693
  this.colors[0][1],
534
694
  this.colors[1][0],
535
695
  this.colors[1][1],
536
- u,
537
- v,
696
+ uDecimal,
697
+ vDecimal,
538
698
  );
539
699
 
540
700
  return { point: pt, color: col };
@@ -723,12 +883,18 @@ export class CoonsPatch {
723
883
  ...this.bottom,
724
884
  ...this.left,
725
885
  ];
886
+
887
+ if (allPoints.length === 0)
888
+ throw new Error("CoonsPatch.getBBox: no points in patch");
889
+
726
890
  let minX = allPoints[0].x,
727
891
  maxX = allPoints[0].x;
728
892
  let minY = allPoints[0].y,
729
893
  maxY = allPoints[0].y;
730
894
 
731
895
  for (const p of allPoints) {
896
+ if (!p || !("x" in p) || !("y" in p))
897
+ throw new Error("CoonsPatch.getBBox: invalid point in patch");
732
898
  if (p.x.lt(minX)) minX = p.x;
733
899
  if (p.x.gt(maxX)) maxX = p.x;
734
900
  if (p.y.lt(minY)) minY = p.y;
@@ -797,71 +963,30 @@ export class CoonsPatch {
797
963
  * console.log(`Parsed ${meshData.patches.length} patches`);
798
964
  */
799
965
  export function parseMeshGradient(meshGradientDef) {
966
+ if (!meshGradientDef || typeof meshGradientDef !== "object") {
967
+ throw new Error("parseMeshGradient: meshGradientDef must be an object");
968
+ }
969
+
800
970
  const x = D(meshGradientDef.x || 0);
801
971
  const y = D(meshGradientDef.y || 0);
972
+ if (!x.isFinite())
973
+ throw new Error(
974
+ `parseMeshGradient: x must be finite, got ${meshGradientDef.x}`,
975
+ );
976
+ if (!y.isFinite())
977
+ throw new Error(
978
+ `parseMeshGradient: y must be finite, got ${meshGradientDef.y}`,
979
+ );
980
+
802
981
  const type = meshGradientDef.type || "bilinear";
803
982
  const gradientUnits = meshGradientDef.gradientUnits || "userSpaceOnUse";
804
983
  const gradientTransform = meshGradientDef.gradientTransform || null;
805
984
 
806
985
  const patches = [];
807
- const meshRows = meshGradientDef.meshrows || [];
808
-
809
- // Build the mesh grid
810
- // nodes[row][col] = point, colors[row][col] = color
811
- const nodes = [[point(x, y)]];
812
- const colors = [[]];
813
-
814
- const _currentX = x;
815
- const _currentY = y;
816
-
817
- for (let rowIdx = 0; rowIdx < meshRows.length; rowIdx++) {
818
- const row = meshRows[rowIdx];
819
- const meshPatches = row.meshpatches || [];
820
-
821
- if (rowIdx > 0) {
822
- nodes.push([]);
823
- colors.push([]);
824
- }
825
-
826
- for (let colIdx = 0; colIdx < meshPatches.length; colIdx++) {
827
- const patch = meshPatches[colIdx];
828
- const stops = patch.stops || [];
829
-
830
- // Each patch has up to 4 stops defining edges
831
- for (let stopIdx = 0; stopIdx < stops.length; stopIdx++) {
832
- const stop = stops[stopIdx];
833
- const pathData = stop.path || "";
834
- const stopColor = stop.color
835
- ? parseColor(stop.color, stop.opacity || 1)
836
- : null;
837
-
838
- // Parse path command (c/C/l/L for bezier/line)
839
- const pathMatch = pathData.match(/^\s*([cClL])\s*(.*)/);
840
- if (pathMatch) {
841
- const cmd = pathMatch[1];
842
- const _coords = pathMatch[2]
843
- .trim()
844
- .split(/[\s,]+/)
845
- .map(Number);
846
-
847
- if (cmd === "c" || cmd === "C") {
848
- // Cubic bezier: c x1,y1 x2,y2 x3,y3 (relative)
849
- // or C x1,y1 x2,y2 x3,y3 (absolute)
850
- const _isRelative = cmd === "c";
851
- // Store bezier control points for patch construction
852
- } else if (cmd === "l" || cmd === "L") {
853
- // Line: l dx,dy (relative) or L x,y (absolute)
854
- const _isRelative = cmd === "l";
855
- }
856
- }
986
+ const _meshRows = meshGradientDef.meshrows || [];
857
987
 
858
- // Store color at corner
859
- if (stopColor && (stopIdx === 0 || stopIdx === 3)) {
860
- // Corner colors
861
- }
862
- }
863
- }
864
- }
988
+ // NOTE: This is a stub implementation - full parsing logic needs to be added
989
+ // The current implementation only extracts metadata without building actual patches
865
990
 
866
991
  return {
867
992
  patches,
@@ -903,6 +1028,10 @@ export function parseMeshGradient(meshGradientDef) {
903
1028
  * const imageData = rasterizeMeshGradient(meshData, 800, 600);
904
1029
  */
905
1030
  export function parseMeshGradientElement(element) {
1031
+ if (!element || typeof element !== "object" || !element.getAttribute) {
1032
+ throw new Error("parseMeshGradientElement: element must be a DOM element");
1033
+ }
1034
+
906
1035
  const data = {
907
1036
  x: element.getAttribute("x") || "0",
908
1037
  y: element.getAttribute("y") || "0",
@@ -926,10 +1055,13 @@ export function parseMeshGradientElement(element) {
926
1055
  const colorMatch = style.match(/stop-color:\s*([^;]+)/);
927
1056
  const opacityMatch = style.match(/stop-opacity:\s*([^;]+)/);
928
1057
 
1058
+ const opacityValue = opacityMatch ? parseFloat(opacityMatch[1]) : 1;
1059
+ const validOpacity = Number.isFinite(opacityValue) ? opacityValue : 1;
1060
+
929
1061
  patchData.stops.push({
930
1062
  path: stop.getAttribute("path") || "",
931
1063
  color: colorMatch ? colorMatch[1].trim() : null,
932
- opacity: opacityMatch ? parseFloat(opacityMatch[1]) : 1,
1064
+ opacity: validOpacity,
933
1065
  });
934
1066
  });
935
1067
 
@@ -984,13 +1116,30 @@ export function parseMeshGradientElement(element) {
984
1116
  * const imageData = rasterizeMeshGradient(meshData, 1920, 1080, { samples: 32 });
985
1117
  */
986
1118
  export function rasterizeMeshGradient(meshData, width, height, options = {}) {
1119
+ if (!meshData || typeof meshData !== "object") {
1120
+ throw new Error("rasterizeMeshGradient: meshData must be an object");
1121
+ }
1122
+ if (!Number.isInteger(width) || width <= 0) {
1123
+ throw new Error(
1124
+ `rasterizeMeshGradient: width must be a positive integer, got ${width}`,
1125
+ );
1126
+ }
1127
+ if (!Number.isInteger(height) || height <= 0) {
1128
+ throw new Error(
1129
+ `rasterizeMeshGradient: height must be a positive integer, got ${height}`,
1130
+ );
1131
+ }
1132
+
987
1133
  const { samples = DEFAULT_PATCH_SAMPLES } = options;
988
1134
 
989
1135
  // Create image data buffer
990
1136
  const imageData = new Uint8ClampedArray(width * height * 4);
991
1137
 
992
1138
  // For each patch, rasterize to the buffer
993
- for (const patch of meshData.patches || []) {
1139
+ const patches = meshData.patches || [];
1140
+ for (const patch of patches) {
1141
+ if (!patch)
1142
+ throw new Error("rasterizeMeshGradient: patch is null or undefined");
994
1143
  rasterizePatch(patch, imageData, width, height, samples);
995
1144
  }
996
1145
 
@@ -1012,14 +1161,37 @@ export function rasterizeMeshGradient(meshData, width, height, options = {}) {
1012
1161
  * @param {number} height - Image height in pixels
1013
1162
  * @param {number} samples - Subdivision control parameter (unused in adaptive mode)
1014
1163
  */
1015
- function rasterizePatch(patch, imageData, width, height, samples) {
1164
+ function rasterizePatch(patch, imageData, width, height, _samples) {
1165
+ if (
1166
+ !patch ||
1167
+ typeof patch.isFlat !== "function" ||
1168
+ typeof patch.subdivide !== "function"
1169
+ ) {
1170
+ throw new Error(
1171
+ "rasterizePatch: patch must be a valid CoonsPatch instance",
1172
+ );
1173
+ }
1174
+ if (!imageData || !(imageData instanceof Uint8ClampedArray)) {
1175
+ throw new Error("rasterizePatch: imageData must be a Uint8ClampedArray");
1176
+ }
1177
+ if (!Number.isInteger(width) || width <= 0) {
1178
+ throw new Error(
1179
+ `rasterizePatch: width must be a positive integer, got ${width}`,
1180
+ );
1181
+ }
1182
+ if (!Number.isInteger(height) || height <= 0) {
1183
+ throw new Error(
1184
+ `rasterizePatch: height must be a positive integer, got ${height}`,
1185
+ );
1186
+ }
1187
+
1016
1188
  // Adaptive subdivision approach
1017
1189
  const stack = [patch];
1018
1190
 
1019
1191
  while (stack.length > 0) {
1020
1192
  const currentPatch = stack.pop();
1021
1193
 
1022
- if (currentPatch.isFlat() || samples <= 2) {
1194
+ if (currentPatch.isFlat()) {
1023
1195
  // Render as quad
1024
1196
  renderPatchQuad(currentPatch, imageData, width, height);
1025
1197
  } else {
@@ -1047,6 +1219,29 @@ function rasterizePatch(patch, imageData, width, height, samples) {
1047
1219
  * @param {number} height - Image height in pixels
1048
1220
  */
1049
1221
  function renderPatchQuad(patch, imageData, width, height) {
1222
+ if (
1223
+ !patch ||
1224
+ typeof patch.getBBox !== "function" ||
1225
+ typeof patch.evaluate !== "function"
1226
+ ) {
1227
+ throw new Error(
1228
+ "renderPatchQuad: patch must be a valid CoonsPatch instance",
1229
+ );
1230
+ }
1231
+ if (!imageData || !(imageData instanceof Uint8ClampedArray)) {
1232
+ throw new Error("renderPatchQuad: imageData must be a Uint8ClampedArray");
1233
+ }
1234
+ if (!Number.isInteger(width) || width <= 0) {
1235
+ throw new Error(
1236
+ `renderPatchQuad: width must be a positive integer, got ${width}`,
1237
+ );
1238
+ }
1239
+ if (!Number.isInteger(height) || height <= 0) {
1240
+ throw new Error(
1241
+ `renderPatchQuad: height must be a positive integer, got ${height}`,
1242
+ );
1243
+ }
1244
+
1050
1245
  const bbox = patch.getBBox();
1051
1246
  const minX = Math.max(0, Math.floor(Number(bbox.minX)));
1052
1247
  const maxX = Math.min(width - 1, Math.ceil(Number(bbox.maxX)));
@@ -1057,12 +1252,14 @@ function renderPatchQuad(patch, imageData, width, height) {
1057
1252
  for (let y = minY; y <= maxY; y++) {
1058
1253
  for (let x = minX; x <= maxX; x++) {
1059
1254
  // Convert pixel to patch (u,v) coordinates
1255
+ const uDenom = bbox.maxX.minus(bbox.minX);
1256
+ const vDenom = bbox.maxY.minus(bbox.minY);
1060
1257
  const u = D(x)
1061
1258
  .minus(bbox.minX)
1062
- .div(bbox.maxX.minus(bbox.minX) || D(1));
1259
+ .div(uDenom.isZero() ? D(1) : uDenom);
1063
1260
  const v = D(y)
1064
1261
  .minus(bbox.minY)
1065
- .div(bbox.maxY.minus(bbox.minY) || D(1));
1262
+ .div(vDenom.isZero() ? D(1) : vDenom);
1066
1263
 
1067
1264
  if (u.gte(0) && u.lte(1) && v.gte(0) && v.lte(1)) {
1068
1265
  const { color: patchColor } = patch.evaluate(u, v);
@@ -1129,10 +1326,17 @@ function renderPatchQuad(patch, imageData, width, height) {
1129
1326
  * console.log(`Generated ${polygons.length} polygons`);
1130
1327
  */
1131
1328
  export function meshGradientToPolygons(meshData, options = {}) {
1329
+ if (!meshData || typeof meshData !== "object") {
1330
+ throw new Error("meshGradientToPolygons: meshData must be an object");
1331
+ }
1332
+
1132
1333
  const { subdivisions = 8 } = options;
1133
1334
  const result = [];
1134
1335
 
1135
- for (const patch of meshData.patches || []) {
1336
+ const patches = meshData.patches || [];
1337
+ for (const patch of patches) {
1338
+ if (!patch)
1339
+ throw new Error("meshGradientToPolygons: patch is null or undefined");
1136
1340
  const polys = patchToPolygons(patch, subdivisions);
1137
1341
  result.push(...polys);
1138
1342
  }
@@ -1153,6 +1357,17 @@ export function meshGradientToPolygons(meshData, options = {}) {
1153
1357
  * @returns {Array<{polygon: Array, color: Object}>} Array of colored quads
1154
1358
  */
1155
1359
  function patchToPolygons(patch, subdivisions) {
1360
+ if (!patch || typeof patch.evaluate !== "function") {
1361
+ throw new Error(
1362
+ "patchToPolygons: patch must be a valid CoonsPatch instance",
1363
+ );
1364
+ }
1365
+ if (!Number.isInteger(subdivisions) || subdivisions <= 0) {
1366
+ throw new Error(
1367
+ `patchToPolygons: subdivisions must be a positive integer, got ${subdivisions}`,
1368
+ );
1369
+ }
1370
+
1156
1371
  const result = [];
1157
1372
  const step = D(1).div(subdivisions);
1158
1373
 
@@ -1234,6 +1449,13 @@ function patchToPolygons(patch, subdivisions) {
1234
1449
  * const clipped = clipMeshGradient(meshData, viewport, { subdivisions: 32 });
1235
1450
  */
1236
1451
  export function clipMeshGradient(meshData, clipPolygon, options = {}) {
1452
+ if (!meshData || typeof meshData !== "object") {
1453
+ throw new Error("clipMeshGradient: meshData must be an object");
1454
+ }
1455
+ if (!Array.isArray(clipPolygon)) {
1456
+ throw new Error("clipMeshGradient: clipPolygon must be an array");
1457
+ }
1458
+
1237
1459
  const { subdivisions = 16 } = options;
1238
1460
 
1239
1461
  // First convert mesh to polygons
@@ -1243,6 +1465,8 @@ export function clipMeshGradient(meshData, clipPolygon, options = {}) {
1243
1465
  const clippedPolygons = [];
1244
1466
 
1245
1467
  for (const { polygon, color: polyColor } of meshPolygons) {
1468
+ if (!Array.isArray(polygon))
1469
+ throw new Error("clipMeshGradient: polygon must be an array");
1246
1470
  const clipped = PolygonClip.polygonIntersection(polygon, clipPolygon);
1247
1471
 
1248
1472
  for (const clippedPoly of clipped) {
@@ -1287,10 +1511,36 @@ export function clipMeshGradient(meshData, clipPolygon, options = {}) {
1287
1511
  * ).join('\n');
1288
1512
  */
1289
1513
  export function clippedMeshToSVG(clippedPolygons) {
1514
+ if (!Array.isArray(clippedPolygons)) {
1515
+ throw new Error("clippedMeshToSVG: clippedPolygons must be an array");
1516
+ }
1517
+
1290
1518
  return clippedPolygons.map(({ polygon, color: polyColor }) => {
1519
+ if (!Array.isArray(polygon))
1520
+ throw new Error("clippedMeshToSVG: polygon must be an array");
1521
+ if (
1522
+ !polyColor ||
1523
+ !(
1524
+ "r" in polyColor &&
1525
+ "g" in polyColor &&
1526
+ "b" in polyColor &&
1527
+ "a" in polyColor
1528
+ )
1529
+ ) {
1530
+ throw new Error(
1531
+ "clippedMeshToSVG: polygon must have color with r, g, b, a properties",
1532
+ );
1533
+ }
1534
+
1291
1535
  let pathData = "";
1292
1536
  for (let i = 0; i < polygon.length; i++) {
1293
1537
  const p = polygon[i];
1538
+ if (!p || !("x" in p && "y" in p)) {
1539
+ throw new Error(
1540
+ `clippedMeshToSVG: point at index ${i} must have x, y properties`,
1541
+ );
1542
+ }
1543
+
1294
1544
  if (i === 0) {
1295
1545
  pathData += `M ${Number(p.x).toFixed(6)} ${Number(p.y).toFixed(6)}`;
1296
1546
  } else {