@emasoft/svg-matrix 1.1.0 → 1.2.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.
Files changed (55) hide show
  1. package/bin/svg-matrix.js +7 -6
  2. package/bin/svgm.js +109 -40
  3. package/dist/svg-matrix.min.js +7 -7
  4. package/dist/svg-toolbox.min.js +148 -228
  5. package/dist/svgm.min.js +152 -232
  6. package/dist/version.json +5 -5
  7. package/package.json +1 -1
  8. package/scripts/postinstall.js +72 -41
  9. package/scripts/test-postinstall.js +18 -16
  10. package/scripts/version-sync.js +78 -60
  11. package/src/animation-optimization.js +190 -98
  12. package/src/animation-references.js +11 -3
  13. package/src/arc-length.js +23 -20
  14. package/src/bezier-analysis.js +9 -13
  15. package/src/bezier-intersections.js +18 -4
  16. package/src/browser-verify.js +35 -8
  17. package/src/clip-path-resolver.js +285 -114
  18. package/src/convert-path-data.js +20 -8
  19. package/src/css-specificity.js +33 -9
  20. package/src/douglas-peucker.js +272 -141
  21. package/src/geometry-to-path.js +79 -22
  22. package/src/gjk-collision.js +287 -126
  23. package/src/index.js +56 -21
  24. package/src/inkscape-support.js +122 -101
  25. package/src/logger.js +43 -27
  26. package/src/marker-resolver.js +201 -121
  27. package/src/mask-resolver.js +231 -98
  28. package/src/matrix.js +9 -5
  29. package/src/mesh-gradient.js +22 -14
  30. package/src/off-canvas-detection.js +53 -17
  31. package/src/path-optimization.js +356 -171
  32. package/src/path-simplification.js +671 -256
  33. package/src/pattern-resolver.js +1 -3
  34. package/src/polygon-clip.js +396 -78
  35. package/src/svg-boolean-ops.js +90 -23
  36. package/src/svg-collections.js +1546 -667
  37. package/src/svg-flatten.js +152 -38
  38. package/src/svg-matrix-lib.js +2 -2
  39. package/src/svg-parser.js +5 -1
  40. package/src/svg-rendering-context.js +3 -1
  41. package/src/svg-toolbox-lib.js +2 -2
  42. package/src/svg-toolbox.js +99 -457
  43. package/src/svg-validation-data.js +513 -345
  44. package/src/svg2-polyfills.js +156 -93
  45. package/src/svgm-lib.js +8 -4
  46. package/src/transform-optimization.js +168 -51
  47. package/src/transforms2d.js +73 -40
  48. package/src/transforms3d.js +34 -27
  49. package/src/use-symbol-resolver.js +175 -76
  50. package/src/vector.js +80 -44
  51. package/src/vendor/inkscape-hatch-polyfill.js +143 -108
  52. package/src/vendor/inkscape-hatch-polyfill.min.js +291 -1
  53. package/src/vendor/inkscape-mesh-polyfill.js +953 -766
  54. package/src/vendor/inkscape-mesh-polyfill.min.js +896 -1
  55. package/src/verification.js +3 -4
@@ -94,9 +94,16 @@ export function pathToPolygon(
94
94
  pathData,
95
95
  samplesPerCurve = DEFAULT_CURVE_SAMPLES,
96
96
  ) {
97
- if (typeof pathData !== 'string') throw new Error('pathToPolygon: pathData must be a string');
98
- if (typeof samplesPerCurve !== 'number' || samplesPerCurve <= 0 || !Number.isFinite(samplesPerCurve)) {
99
- throw new Error(`pathToPolygon: samplesPerCurve must be a positive finite number, got ${samplesPerCurve}`);
97
+ if (typeof pathData !== "string")
98
+ throw new Error("pathToPolygon: pathData must be a string");
99
+ if (
100
+ typeof samplesPerCurve !== "number" ||
101
+ samplesPerCurve <= 0 ||
102
+ !Number.isFinite(samplesPerCurve)
103
+ ) {
104
+ throw new Error(
105
+ `pathToPolygon: samplesPerCurve must be a positive finite number, got ${samplesPerCurve}`,
106
+ );
100
107
  }
101
108
  const points = [];
102
109
  let currentX = D(0),
@@ -106,18 +113,23 @@ export function pathToPolygon(
106
113
 
107
114
  const commands = parsePathCommands(pathData);
108
115
  if (!Array.isArray(commands)) {
109
- throw new Error('pathToPolygon: parsePathCommands must return an array');
116
+ throw new Error("pathToPolygon: parsePathCommands must return an array");
110
117
  }
111
118
 
112
119
  for (const cmd of commands) {
113
120
  const { type, args } = cmd;
114
121
  if (!Array.isArray(args)) {
115
- throw new Error(`pathToPolygon: command args must be an array for command ${type}`);
122
+ throw new Error(
123
+ `pathToPolygon: command args must be an array for command ${type}`,
124
+ );
116
125
  }
117
126
 
118
127
  switch (type) {
119
128
  case "M":
120
- if (args.length < 2) throw new Error(`pathToPolygon: M command requires 2 arguments, got ${args.length}`);
129
+ if (args.length < 2)
130
+ throw new Error(
131
+ `pathToPolygon: M command requires 2 arguments, got ${args.length}`,
132
+ );
121
133
  currentX = D(args[0]);
122
134
  currentY = D(args[1]);
123
135
  startX = currentX;
@@ -125,7 +137,10 @@ export function pathToPolygon(
125
137
  points.push(PolygonClip.point(currentX, currentY));
126
138
  break;
127
139
  case "m":
128
- if (args.length < 2) throw new Error(`pathToPolygon: m command requires 2 arguments, got ${args.length}`);
140
+ if (args.length < 2)
141
+ throw new Error(
142
+ `pathToPolygon: m command requires 2 arguments, got ${args.length}`,
143
+ );
129
144
  currentX = currentX.plus(args[0]);
130
145
  currentY = currentY.plus(args[1]);
131
146
  startX = currentX;
@@ -133,39 +148,60 @@ export function pathToPolygon(
133
148
  points.push(PolygonClip.point(currentX, currentY));
134
149
  break;
135
150
  case "L":
136
- if (args.length < 2) throw new Error(`pathToPolygon: L command requires 2 arguments, got ${args.length}`);
151
+ if (args.length < 2)
152
+ throw new Error(
153
+ `pathToPolygon: L command requires 2 arguments, got ${args.length}`,
154
+ );
137
155
  currentX = D(args[0]);
138
156
  currentY = D(args[1]);
139
157
  points.push(PolygonClip.point(currentX, currentY));
140
158
  break;
141
159
  case "l":
142
- if (args.length < 2) throw new Error(`pathToPolygon: l command requires 2 arguments, got ${args.length}`);
160
+ if (args.length < 2)
161
+ throw new Error(
162
+ `pathToPolygon: l command requires 2 arguments, got ${args.length}`,
163
+ );
143
164
  currentX = currentX.plus(args[0]);
144
165
  currentY = currentY.plus(args[1]);
145
166
  points.push(PolygonClip.point(currentX, currentY));
146
167
  break;
147
168
  case "H":
148
- if (args.length < 1) throw new Error(`pathToPolygon: H command requires 1 argument, got ${args.length}`);
169
+ if (args.length < 1)
170
+ throw new Error(
171
+ `pathToPolygon: H command requires 1 argument, got ${args.length}`,
172
+ );
149
173
  currentX = D(args[0]);
150
174
  points.push(PolygonClip.point(currentX, currentY));
151
175
  break;
152
176
  case "h":
153
- if (args.length < 1) throw new Error(`pathToPolygon: h command requires 1 argument, got ${args.length}`);
177
+ if (args.length < 1)
178
+ throw new Error(
179
+ `pathToPolygon: h command requires 1 argument, got ${args.length}`,
180
+ );
154
181
  currentX = currentX.plus(args[0]);
155
182
  points.push(PolygonClip.point(currentX, currentY));
156
183
  break;
157
184
  case "V":
158
- if (args.length < 1) throw new Error(`pathToPolygon: V command requires 1 argument, got ${args.length}`);
185
+ if (args.length < 1)
186
+ throw new Error(
187
+ `pathToPolygon: V command requires 1 argument, got ${args.length}`,
188
+ );
159
189
  currentY = D(args[0]);
160
190
  points.push(PolygonClip.point(currentX, currentY));
161
191
  break;
162
192
  case "v":
163
- if (args.length < 1) throw new Error(`pathToPolygon: v command requires 1 argument, got ${args.length}`);
193
+ if (args.length < 1)
194
+ throw new Error(
195
+ `pathToPolygon: v command requires 1 argument, got ${args.length}`,
196
+ );
164
197
  currentY = currentY.plus(args[0]);
165
198
  points.push(PolygonClip.point(currentX, currentY));
166
199
  break;
167
200
  case "C":
168
- if (args.length < 6) throw new Error(`pathToPolygon: C command requires 6 arguments, got ${args.length}`);
201
+ if (args.length < 6)
202
+ throw new Error(
203
+ `pathToPolygon: C command requires 6 arguments, got ${args.length}`,
204
+ );
169
205
  sampleCubicBezier(
170
206
  points,
171
207
  currentX,
@@ -182,7 +218,10 @@ export function pathToPolygon(
182
218
  currentY = D(args[5]);
183
219
  break;
184
220
  case "c":
185
- if (args.length < 6) throw new Error(`pathToPolygon: c command requires 6 arguments, got ${args.length}`);
221
+ if (args.length < 6)
222
+ throw new Error(
223
+ `pathToPolygon: c command requires 6 arguments, got ${args.length}`,
224
+ );
186
225
  sampleCubicBezier(
187
226
  points,
188
227
  currentX,
@@ -199,7 +238,10 @@ export function pathToPolygon(
199
238
  currentY = currentY.plus(args[5]);
200
239
  break;
201
240
  case "Q":
202
- if (args.length < 4) throw new Error(`pathToPolygon: Q command requires 4 arguments, got ${args.length}`);
241
+ if (args.length < 4)
242
+ throw new Error(
243
+ `pathToPolygon: Q command requires 4 arguments, got ${args.length}`,
244
+ );
203
245
  sampleQuadraticBezier(
204
246
  points,
205
247
  currentX,
@@ -214,7 +256,10 @@ export function pathToPolygon(
214
256
  currentY = D(args[3]);
215
257
  break;
216
258
  case "q":
217
- if (args.length < 4) throw new Error(`pathToPolygon: q command requires 4 arguments, got ${args.length}`);
259
+ if (args.length < 4)
260
+ throw new Error(
261
+ `pathToPolygon: q command requires 4 arguments, got ${args.length}`,
262
+ );
218
263
  sampleQuadraticBezier(
219
264
  points,
220
265
  currentX,
@@ -229,7 +274,10 @@ export function pathToPolygon(
229
274
  currentY = currentY.plus(args[3]);
230
275
  break;
231
276
  case "A":
232
- if (args.length < 7) throw new Error(`pathToPolygon: A command requires 7 arguments, got ${args.length}`);
277
+ if (args.length < 7)
278
+ throw new Error(
279
+ `pathToPolygon: A command requires 7 arguments, got ${args.length}`,
280
+ );
233
281
  sampleArc(
234
282
  points,
235
283
  currentX,
@@ -247,7 +295,10 @@ export function pathToPolygon(
247
295
  currentY = D(args[6]);
248
296
  break;
249
297
  case "a":
250
- if (args.length < 7) throw new Error(`pathToPolygon: a command requires 7 arguments, got ${args.length}`);
298
+ if (args.length < 7)
299
+ throw new Error(
300
+ `pathToPolygon: a command requires 7 arguments, got ${args.length}`,
301
+ );
251
302
  sampleArc(
252
303
  points,
253
304
  currentX,
@@ -293,8 +344,10 @@ export function pathToPolygon(
293
344
  * // Returns: [{type: 'M', args: [10, 20]}, {type: 'L', args: [30, 40]}]
294
345
  */
295
346
  function parsePathCommands(pathData) {
296
- if (typeof pathData !== 'string') {
297
- throw new Error(`parsePathCommands: pathData must be a string, got ${typeof pathData}`);
347
+ if (typeof pathData !== "string") {
348
+ throw new Error(
349
+ `parsePathCommands: pathData must be a string, got ${typeof pathData}`,
350
+ );
298
351
  }
299
352
  const commands = [];
300
353
  const regex = /([MmLlHhVvCcSsQqTtAaZz])([^MmLlHhVvCcSsQqTtAaZz]*)/g;
@@ -340,14 +393,30 @@ function parsePathCommands(pathData) {
340
393
  */
341
394
  function sampleCubicBezier(points, x0, y0, x1, y1, x2, y2, x3, y3, samples) {
342
395
  if (!Array.isArray(points)) {
343
- throw new Error('sampleCubicBezier: points must be an array');
344
- }
345
- if (typeof samples !== 'number' || samples <= 0 || !Number.isFinite(samples)) {
346
- throw new Error(`sampleCubicBezier: samples must be a positive finite number, got ${samples}`);
396
+ throw new Error("sampleCubicBezier: points must be an array");
397
+ }
398
+ if (
399
+ typeof samples !== "number" ||
400
+ samples <= 0 ||
401
+ !Number.isFinite(samples)
402
+ ) {
403
+ throw new Error(
404
+ `sampleCubicBezier: samples must be a positive finite number, got ${samples}`,
405
+ );
347
406
  }
348
- if (!(x0 instanceof Decimal) || !(y0 instanceof Decimal) || !(x1 instanceof Decimal) || !(y1 instanceof Decimal) ||
349
- !(x2 instanceof Decimal) || !(y2 instanceof Decimal) || !(x3 instanceof Decimal) || !(y3 instanceof Decimal)) {
350
- throw new Error('sampleCubicBezier: all coordinate parameters must be Decimal instances');
407
+ if (
408
+ !(x0 instanceof Decimal) ||
409
+ !(y0 instanceof Decimal) ||
410
+ !(x1 instanceof Decimal) ||
411
+ !(y1 instanceof Decimal) ||
412
+ !(x2 instanceof Decimal) ||
413
+ !(y2 instanceof Decimal) ||
414
+ !(x3 instanceof Decimal) ||
415
+ !(y3 instanceof Decimal)
416
+ ) {
417
+ throw new Error(
418
+ "sampleCubicBezier: all coordinate parameters must be Decimal instances",
419
+ );
351
420
  }
352
421
  for (let i = 1; i <= samples; i++) {
353
422
  const t = D(i).div(samples);
@@ -393,14 +462,28 @@ function sampleCubicBezier(points, x0, y0, x1, y1, x2, y2, x3, y3, samples) {
393
462
  */
394
463
  function sampleQuadraticBezier(points, x0, y0, x1, y1, x2, y2, samples) {
395
464
  if (!Array.isArray(points)) {
396
- throw new Error('sampleQuadraticBezier: points must be an array');
397
- }
398
- if (typeof samples !== 'number' || samples <= 0 || !Number.isFinite(samples)) {
399
- throw new Error(`sampleQuadraticBezier: samples must be a positive finite number, got ${samples}`);
465
+ throw new Error("sampleQuadraticBezier: points must be an array");
466
+ }
467
+ if (
468
+ typeof samples !== "number" ||
469
+ samples <= 0 ||
470
+ !Number.isFinite(samples)
471
+ ) {
472
+ throw new Error(
473
+ `sampleQuadraticBezier: samples must be a positive finite number, got ${samples}`,
474
+ );
400
475
  }
401
- if (!(x0 instanceof Decimal) || !(y0 instanceof Decimal) || !(x1 instanceof Decimal) ||
402
- !(y1 instanceof Decimal) || !(x2 instanceof Decimal) || !(y2 instanceof Decimal)) {
403
- throw new Error('sampleQuadraticBezier: all coordinate parameters must be Decimal instances');
476
+ if (
477
+ !(x0 instanceof Decimal) ||
478
+ !(y0 instanceof Decimal) ||
479
+ !(x1 instanceof Decimal) ||
480
+ !(y1 instanceof Decimal) ||
481
+ !(x2 instanceof Decimal) ||
482
+ !(y2 instanceof Decimal)
483
+ ) {
484
+ throw new Error(
485
+ "sampleQuadraticBezier: all coordinate parameters must be Decimal instances",
486
+ );
404
487
  }
405
488
  for (let i = 1; i <= samples; i++) {
406
489
  const t = D(i).div(samples);
@@ -451,19 +534,34 @@ function sampleArc(
451
534
  samples,
452
535
  ) {
453
536
  if (!Array.isArray(points)) {
454
- throw new Error('sampleArc: points must be an array');
455
- }
456
- if (typeof samples !== 'number' || samples <= 0 || !Number.isFinite(samples)) {
457
- throw new Error(`sampleArc: samples must be a positive finite number, got ${samples}`);
537
+ throw new Error("sampleArc: points must be an array");
538
+ }
539
+ if (
540
+ typeof samples !== "number" ||
541
+ samples <= 0 ||
542
+ !Number.isFinite(samples)
543
+ ) {
544
+ throw new Error(
545
+ `sampleArc: samples must be a positive finite number, got ${samples}`,
546
+ );
458
547
  }
459
- if (!(x0 instanceof Decimal) || !(y0 instanceof Decimal) || !(rx instanceof Decimal) || !(ry instanceof Decimal) ||
460
- !(xAxisRotation instanceof Decimal) || !(x1 instanceof Decimal) || !(y1 instanceof Decimal)) {
461
- throw new Error('sampleArc: all coordinate and angle parameters must be Decimal instances');
548
+ if (
549
+ !(x0 instanceof Decimal) ||
550
+ !(y0 instanceof Decimal) ||
551
+ !(rx instanceof Decimal) ||
552
+ !(ry instanceof Decimal) ||
553
+ !(xAxisRotation instanceof Decimal) ||
554
+ !(x1 instanceof Decimal) ||
555
+ !(y1 instanceof Decimal)
556
+ ) {
557
+ throw new Error(
558
+ "sampleArc: all coordinate and angle parameters must be Decimal instances",
559
+ );
462
560
  }
463
- if (typeof largeArc !== 'number' || (largeArc !== 0 && largeArc !== 1)) {
561
+ if (typeof largeArc !== "number" || (largeArc !== 0 && largeArc !== 1)) {
464
562
  throw new Error(`sampleArc: largeArc must be 0 or 1, got ${largeArc}`);
465
563
  }
466
- if (typeof sweep !== 'number' || (sweep !== 0 && sweep !== 1)) {
564
+ if (typeof sweep !== "number" || (sweep !== 0 && sweep !== 1)) {
467
565
  throw new Error(`sampleArc: sweep must be 0 or 1, got ${sweep}`);
468
566
  }
469
567
  if (rx.eq(0) || ry.eq(0)) {
@@ -514,7 +612,9 @@ function sampleArc(
514
612
  const n1 = ux.mul(ux).plus(uy.mul(uy)).sqrt();
515
613
  if (n1.eq(0)) {
516
614
  // Degenerate arc: center point coincides with start point
517
- Logger.warn('sampleArc: degenerate arc - center coincides with start point, treating as line to end');
615
+ Logger.warn(
616
+ "sampleArc: degenerate arc - center coincides with start point, treating as line to end",
617
+ );
518
618
  points.push(PolygonClip.point(x1, y1));
519
619
  return;
520
620
  }
@@ -524,7 +624,9 @@ function sampleArc(
524
624
  const n2 = n1.mul(vx.mul(vx).plus(vy.mul(vy)).sqrt());
525
625
  if (n2.eq(0)) {
526
626
  // Degenerate arc: center coincides with both endpoints (zero-length arc)
527
- Logger.warn('sampleArc: degenerate arc - zero length, treating as single point');
627
+ Logger.warn(
628
+ "sampleArc: degenerate arc - zero length, treating as single point",
629
+ );
528
630
  points.push(PolygonClip.point(x1, y1));
529
631
  return;
530
632
  }
@@ -571,7 +673,7 @@ function sampleArc(
571
673
  */
572
674
  function removeDuplicateConsecutive(points) {
573
675
  if (!Array.isArray(points)) {
574
- throw new Error('removeDuplicateConsecutive: points must be an array');
676
+ throw new Error("removeDuplicateConsecutive: points must be an array");
575
677
  }
576
678
  if (points.length < 2) return points;
577
679
  const result = [points[0]];
@@ -625,20 +727,32 @@ export function shapeToPolygon(
625
727
  samples = DEFAULT_CURVE_SAMPLES,
626
728
  bezierArcs = 4,
627
729
  ) {
628
- if (!element || typeof element !== 'object') {
629
- throw new Error('shapeToPolygon: element must be an object');
630
- }
631
- if (!element.type || typeof element.type !== 'string') {
632
- throw new Error('shapeToPolygon: element.type must be a string');
633
- }
634
- if (typeof samples !== 'number' || samples <= 0 || !Number.isFinite(samples)) {
635
- throw new Error(`shapeToPolygon: samples must be a positive finite number, got ${samples}`);
730
+ if (!element || typeof element !== "object") {
731
+ throw new Error("shapeToPolygon: element must be an object");
732
+ }
733
+ if (!element.type || typeof element.type !== "string") {
734
+ throw new Error("shapeToPolygon: element.type must be a string");
735
+ }
736
+ if (
737
+ typeof samples !== "number" ||
738
+ samples <= 0 ||
739
+ !Number.isFinite(samples)
740
+ ) {
741
+ throw new Error(
742
+ `shapeToPolygon: samples must be a positive finite number, got ${samples}`,
743
+ );
636
744
  }
637
- if (typeof bezierArcs !== 'number' || bezierArcs <= 0 || !Number.isFinite(bezierArcs)) {
638
- throw new Error(`shapeToPolygon: bezierArcs must be a positive finite number, got ${bezierArcs}`);
745
+ if (
746
+ typeof bezierArcs !== "number" ||
747
+ bezierArcs <= 0 ||
748
+ !Number.isFinite(bezierArcs)
749
+ ) {
750
+ throw new Error(
751
+ `shapeToPolygon: bezierArcs must be a positive finite number, got ${bezierArcs}`,
752
+ );
639
753
  }
640
754
  if (ctm !== null && !(ctm instanceof Matrix)) {
641
- throw new Error('shapeToPolygon: ctm must be null or a Matrix instance');
755
+ throw new Error("shapeToPolygon: ctm must be null or a Matrix instance");
642
756
  }
643
757
  let pathData;
644
758
  switch (element.type) {
@@ -714,7 +828,9 @@ export function shapeToPolygon(
714
828
  if (element.transform) {
715
829
  const elementTransform = parseTransform(element.transform);
716
830
  if (!(elementTransform instanceof Matrix)) {
717
- throw new Error(`shapeToPolygon: parseTransform must return a Matrix, got ${typeof elementTransform}`);
831
+ throw new Error(
832
+ `shapeToPolygon: parseTransform must return a Matrix, got ${typeof elementTransform}`,
833
+ );
718
834
  }
719
835
  pathData = transformPathData(pathData, elementTransform);
720
836
  }
@@ -785,24 +901,35 @@ export function resolveClipPath(
785
901
  ctm = null,
786
902
  options = {},
787
903
  ) {
788
- if (!clipPathDef || typeof clipPathDef !== 'object') {
789
- throw new Error('resolveClipPath: clipPathDef must be an object');
904
+ if (!clipPathDef || typeof clipPathDef !== "object") {
905
+ throw new Error("resolveClipPath: clipPathDef must be an object");
790
906
  }
791
907
  if (ctm !== null && !(ctm instanceof Matrix)) {
792
- throw new Error('resolveClipPath: ctm must be null or a Matrix instance');
908
+ throw new Error("resolveClipPath: ctm must be null or a Matrix instance");
793
909
  }
794
- if (typeof options !== 'object' || options === null) {
795
- throw new Error('resolveClipPath: options must be an object');
910
+ if (typeof options !== "object" || options === null) {
911
+ throw new Error("resolveClipPath: options must be an object");
796
912
  }
797
913
  const { samples = DEFAULT_CURVE_SAMPLES } = options;
798
- if (typeof samples !== 'number' || samples <= 0 || !Number.isFinite(samples)) {
799
- throw new Error(`resolveClipPath: samples must be a positive finite number, got ${samples}`);
914
+ if (
915
+ typeof samples !== "number" ||
916
+ samples <= 0 ||
917
+ !Number.isFinite(samples)
918
+ ) {
919
+ throw new Error(
920
+ `resolveClipPath: samples must be a positive finite number, got ${samples}`,
921
+ );
800
922
  }
801
923
  const clipPathUnits = clipPathDef.clipPathUnits || "userSpaceOnUse";
802
924
 
803
925
  // Validate clipPathUnits value (SVG spec: case-sensitive)
804
- if (clipPathUnits !== "userSpaceOnUse" && clipPathUnits !== "objectBoundingBox") {
805
- Logger.warn(`resolveClipPath: invalid clipPathUnits '${clipPathUnits}', defaulting to 'userSpaceOnUse' (valid values: 'userSpaceOnUse', 'objectBoundingBox')`);
926
+ if (
927
+ clipPathUnits !== "userSpaceOnUse" &&
928
+ clipPathUnits !== "objectBoundingBox"
929
+ ) {
930
+ Logger.warn(
931
+ `resolveClipPath: invalid clipPathUnits '${clipPathUnits}', defaulting to 'userSpaceOnUse' (valid values: 'userSpaceOnUse', 'objectBoundingBox')`,
932
+ );
806
933
  }
807
934
 
808
935
  let clipTransform = ctm ? ctm.clone() : Matrix.identity(3);
@@ -810,23 +937,31 @@ export function resolveClipPath(
810
937
  if (clipPathDef.transform) {
811
938
  const clipPathTransformMatrix = parseTransform(clipPathDef.transform);
812
939
  if (!(clipPathTransformMatrix instanceof Matrix)) {
813
- throw new Error(`resolveClipPath: parseTransform must return a Matrix, got ${typeof clipPathTransformMatrix}`);
940
+ throw new Error(
941
+ `resolveClipPath: parseTransform must return a Matrix, got ${typeof clipPathTransformMatrix}`,
942
+ );
814
943
  }
815
944
  clipTransform = clipTransform.mul(clipPathTransformMatrix);
816
945
  }
817
946
 
818
947
  if (clipPathUnits === "objectBoundingBox") {
819
948
  if (!targetElement) {
820
- throw new Error('resolveClipPath: targetElement required for objectBoundingBox clipPathUnits');
949
+ throw new Error(
950
+ "resolveClipPath: targetElement required for objectBoundingBox clipPathUnits",
951
+ );
821
952
  }
822
953
  const bbox = getElementBoundingBox(targetElement);
823
954
  if (!bbox) {
824
- Logger.warn('resolveClipPath: failed to compute bounding box for objectBoundingBox clipPath');
955
+ Logger.warn(
956
+ "resolveClipPath: failed to compute bounding box for objectBoundingBox clipPath",
957
+ );
825
958
  return [];
826
959
  }
827
960
  // Validate bounding box dimensions - degenerate bbox clips everything
828
961
  if (bbox.width.lte(0) || bbox.height.lte(0)) {
829
- Logger.warn(`resolveClipPath: degenerate bounding box (width=${bbox.width}, height=${bbox.height}), clipping entire element`);
962
+ Logger.warn(
963
+ `resolveClipPath: degenerate bounding box (width=${bbox.width}, height=${bbox.height}), clipping entire element`,
964
+ );
830
965
  return [];
831
966
  }
832
967
  const bboxTransform = Transforms2D.translation(bbox.x, bbox.y).mul(
@@ -838,7 +973,9 @@ export function resolveClipPath(
838
973
  // Validate children array before processing
839
974
  const children = clipPathDef.children || [];
840
975
  if (!Array.isArray(children)) {
841
- throw new Error(`resolveClipPath: clipPathDef.children must be an array, got ${typeof children}`);
976
+ throw new Error(
977
+ `resolveClipPath: clipPathDef.children must be an array, got ${typeof children}`,
978
+ );
842
979
  }
843
980
 
844
981
  const clipPolygons = [];
@@ -849,7 +986,9 @@ export function resolveClipPath(
849
986
 
850
987
  // Empty clipPath returns empty polygon (clips everything)
851
988
  if (clipPolygons.length === 0) {
852
- Logger.warn('resolveClipPath: clipPath has no valid child shapes, clipping entire element');
989
+ Logger.warn(
990
+ "resolveClipPath: clipPath has no valid child shapes, clipping entire element",
991
+ );
853
992
  return [];
854
993
  }
855
994
  if (clipPolygons.length === 1) return clipPolygons[0];
@@ -886,13 +1025,15 @@ export function resolveClipPath(
886
1025
  */
887
1026
  function clipPolygonWithRule(elementPolygon, clipPolygon, clipRule) {
888
1027
  if (!Array.isArray(elementPolygon)) {
889
- throw new Error('clipPolygonWithRule: elementPolygon must be an array');
1028
+ throw new Error("clipPolygonWithRule: elementPolygon must be an array");
890
1029
  }
891
1030
  if (!Array.isArray(clipPolygon)) {
892
- throw new Error('clipPolygonWithRule: clipPolygon must be an array');
1031
+ throw new Error("clipPolygonWithRule: clipPolygon must be an array");
893
1032
  }
894
- if (clipRule !== 'nonzero' && clipRule !== 'evenodd') {
895
- throw new Error(`clipPolygonWithRule: clipRule must be 'nonzero' or 'evenodd', got '${clipRule}' (valid values: 'nonzero', 'evenodd')`);
1033
+ if (clipRule !== "nonzero" && clipRule !== "evenodd") {
1034
+ throw new Error(
1035
+ `clipPolygonWithRule: clipRule must be 'nonzero' or 'evenodd', got '${clipRule}' (valid values: 'nonzero', 'evenodd')`,
1036
+ );
896
1037
  }
897
1038
  if (elementPolygon.length < 3 || clipPolygon.length < 3) {
898
1039
  return [];
@@ -941,10 +1082,10 @@ function clipPolygonWithRule(elementPolygon, clipPolygon, clipRule) {
941
1082
  */
942
1083
  function computeCentroid(polygon) {
943
1084
  if (!Array.isArray(polygon)) {
944
- throw new Error('computeCentroid: polygon must be an array');
1085
+ throw new Error("computeCentroid: polygon must be an array");
945
1086
  }
946
1087
  if (polygon.length === 0) {
947
- throw new Error('computeCentroid: polygon must not be empty');
1088
+ throw new Error("computeCentroid: polygon must not be empty");
948
1089
  }
949
1090
  let cx = new Decimal(0);
950
1091
  let cy = new Decimal(0);
@@ -961,7 +1102,7 @@ function computeCentroid(polygon) {
961
1102
 
962
1103
  area = area.div(2);
963
1104
  // Use Decimal comparison for degenerate polygon detection
964
- const epsilon = new Decimal('1e-10');
1105
+ const epsilon = new Decimal("1e-10");
965
1106
  if (area.abs().lt(epsilon)) {
966
1107
  // Degenerate polygon (zero area) - return average of vertices
967
1108
  let sumX = new Decimal(0);
@@ -1033,24 +1174,32 @@ function computeCentroid(polygon) {
1033
1174
  * const clipped = applyClipPath(ellipse, clipDef, null, { samples: 50 });
1034
1175
  */
1035
1176
  export function applyClipPath(element, clipPathDef, ctm = null, options = {}) {
1036
- if (!element || typeof element !== 'object') {
1037
- throw new Error('applyClipPath: element must be an object');
1177
+ if (!element || typeof element !== "object") {
1178
+ throw new Error("applyClipPath: element must be an object");
1038
1179
  }
1039
- if (!clipPathDef || typeof clipPathDef !== 'object') {
1040
- throw new Error('applyClipPath: clipPathDef must be an object');
1180
+ if (!clipPathDef || typeof clipPathDef !== "object") {
1181
+ throw new Error("applyClipPath: clipPathDef must be an object");
1041
1182
  }
1042
1183
  if (ctm !== null && !(ctm instanceof Matrix)) {
1043
- throw new Error('applyClipPath: ctm must be null or a Matrix instance');
1184
+ throw new Error("applyClipPath: ctm must be null or a Matrix instance");
1044
1185
  }
1045
- if (typeof options !== 'object' || options === null) {
1046
- throw new Error('applyClipPath: options must be an object');
1186
+ if (typeof options !== "object" || options === null) {
1187
+ throw new Error("applyClipPath: options must be an object");
1047
1188
  }
1048
1189
  const { samples = DEFAULT_CURVE_SAMPLES, clipRule = "nonzero" } = options;
1049
- if (typeof samples !== 'number' || samples <= 0 || !Number.isFinite(samples)) {
1050
- throw new Error(`applyClipPath: samples must be a positive finite number, got ${samples}`);
1190
+ if (
1191
+ typeof samples !== "number" ||
1192
+ samples <= 0 ||
1193
+ !Number.isFinite(samples)
1194
+ ) {
1195
+ throw new Error(
1196
+ `applyClipPath: samples must be a positive finite number, got ${samples}`,
1197
+ );
1051
1198
  }
1052
- if (clipRule !== 'nonzero' && clipRule !== 'evenodd') {
1053
- throw new Error(`applyClipPath: clipRule must be 'nonzero' or 'evenodd', got '${clipRule}' (valid values: 'nonzero', 'evenodd')`);
1199
+ if (clipRule !== "nonzero" && clipRule !== "evenodd") {
1200
+ throw new Error(
1201
+ `applyClipPath: clipRule must be 'nonzero' or 'evenodd', got '${clipRule}' (valid values: 'nonzero', 'evenodd')`,
1202
+ );
1054
1203
  }
1055
1204
  const clipPolygon = resolveClipPath(clipPathDef, element, ctm, options);
1056
1205
  if (clipPolygon.length < 3) return [];
@@ -1087,11 +1236,11 @@ export function applyClipPath(element, clipPathDef, ctm = null, options = {}) {
1087
1236
  * // Returns: {x: Decimal(25), y: Decimal(25), width: Decimal(50), height: Decimal(50)}
1088
1237
  */
1089
1238
  function getElementBoundingBox(element) {
1090
- if (!element || typeof element !== 'object') {
1091
- throw new Error('getElementBoundingBox: element must be an object');
1239
+ if (!element || typeof element !== "object") {
1240
+ throw new Error("getElementBoundingBox: element must be an object");
1092
1241
  }
1093
- if (!element.type || typeof element.type !== 'string') {
1094
- throw new Error('getElementBoundingBox: element.type must be a string');
1242
+ if (!element.type || typeof element.type !== "string") {
1243
+ throw new Error("getElementBoundingBox: element.type must be a string");
1095
1244
  }
1096
1245
  switch (element.type) {
1097
1246
  case "rect": {
@@ -1099,7 +1248,9 @@ function getElementBoundingBox(element) {
1099
1248
  const height = D(element.height || 0);
1100
1249
  // Negative width/height are invalid in SVG
1101
1250
  if (width.lt(0) || height.lt(0)) {
1102
- Logger.warn(`getElementBoundingBox: rect has negative dimensions (width=${width}, height=${height})`);
1251
+ Logger.warn(
1252
+ `getElementBoundingBox: rect has negative dimensions (width=${width}, height=${height})`,
1253
+ );
1103
1254
  return null;
1104
1255
  }
1105
1256
  return {
@@ -1115,7 +1266,9 @@ function getElementBoundingBox(element) {
1115
1266
  r = D(element.r || 0);
1116
1267
  // Negative radius is invalid in SVG
1117
1268
  if (r.lt(0)) {
1118
- Logger.warn(`getElementBoundingBox: circle has negative radius (r=${r})`);
1269
+ Logger.warn(
1270
+ `getElementBoundingBox: circle has negative radius (r=${r})`,
1271
+ );
1119
1272
  return null;
1120
1273
  }
1121
1274
  return {
@@ -1132,7 +1285,9 @@ function getElementBoundingBox(element) {
1132
1285
  ry = D(element.ry || 0);
1133
1286
  // Negative radii are invalid in SVG
1134
1287
  if (rx.lt(0) || ry.lt(0)) {
1135
- Logger.warn(`getElementBoundingBox: ellipse has negative radii (rx=${rx}, ry=${ry})`);
1288
+ Logger.warn(
1289
+ `getElementBoundingBox: ellipse has negative radii (rx=${rx}, ry=${ry})`,
1290
+ );
1136
1291
  return null;
1137
1292
  }
1138
1293
  return {
@@ -1147,14 +1302,18 @@ function getElementBoundingBox(element) {
1147
1302
  if (polygon.length > 0) {
1148
1303
  const bbox = PolygonClip.boundingBox(polygon);
1149
1304
  if (!bbox || !bbox.minX || !bbox.minY || !bbox.maxX || !bbox.maxY) {
1150
- Logger.warn('getElementBoundingBox: failed to compute bounding box from polygon');
1305
+ Logger.warn(
1306
+ "getElementBoundingBox: failed to compute bounding box from polygon",
1307
+ );
1151
1308
  return null;
1152
1309
  }
1153
1310
  const width = bbox.maxX.minus(bbox.minX);
1154
1311
  const height = bbox.maxY.minus(bbox.minY);
1155
1312
  // Check for degenerate bounding box
1156
1313
  if (width.lt(0) || height.lt(0)) {
1157
- Logger.warn(`getElementBoundingBox: invalid bounding box dimensions (width=${width}, height=${height})`);
1314
+ Logger.warn(
1315
+ `getElementBoundingBox: invalid bounding box dimensions (width=${width}, height=${height})`,
1316
+ );
1158
1317
  return null;
1159
1318
  }
1160
1319
  return {
@@ -1201,10 +1360,16 @@ function getElementBoundingBox(element) {
1201
1360
  */
1202
1361
  export function polygonToPathData(polygon, precision = 6) {
1203
1362
  if (!Array.isArray(polygon)) {
1204
- throw new Error('polygonToPathData: polygon must be an array');
1205
- }
1206
- if (typeof precision !== 'number' || precision < 0 || !Number.isFinite(precision)) {
1207
- throw new Error(`polygonToPathData: precision must be a non-negative finite number, got ${precision}`);
1363
+ throw new Error("polygonToPathData: polygon must be an array");
1364
+ }
1365
+ if (
1366
+ typeof precision !== "number" ||
1367
+ precision < 0 ||
1368
+ !Number.isFinite(precision)
1369
+ ) {
1370
+ throw new Error(
1371
+ `polygonToPathData: precision must be a non-negative finite number, got ${precision}`,
1372
+ );
1208
1373
  }
1209
1374
  if (polygon.length < 2) return "";
1210
1375
  const fmt = (n) =>
@@ -1272,20 +1437,22 @@ export function resolveNestedClipPath(
1272
1437
  visited = new Set(),
1273
1438
  options = {},
1274
1439
  ) {
1275
- if (!clipPathDef || typeof clipPathDef !== 'object') {
1276
- throw new Error('resolveNestedClipPath: clipPathDef must be an object');
1440
+ if (!clipPathDef || typeof clipPathDef !== "object") {
1441
+ throw new Error("resolveNestedClipPath: clipPathDef must be an object");
1277
1442
  }
1278
1443
  if (!(defsMap instanceof Map)) {
1279
- throw new Error('resolveNestedClipPath: defsMap must be a Map');
1444
+ throw new Error("resolveNestedClipPath: defsMap must be a Map");
1280
1445
  }
1281
1446
  if (ctm !== null && !(ctm instanceof Matrix)) {
1282
- throw new Error('resolveNestedClipPath: ctm must be null or a Matrix instance');
1447
+ throw new Error(
1448
+ "resolveNestedClipPath: ctm must be null or a Matrix instance",
1449
+ );
1283
1450
  }
1284
1451
  if (!(visited instanceof Set)) {
1285
- throw new Error('resolveNestedClipPath: visited must be a Set');
1452
+ throw new Error("resolveNestedClipPath: visited must be a Set");
1286
1453
  }
1287
- if (typeof options !== 'object' || options === null) {
1288
- throw new Error('resolveNestedClipPath: options must be an object');
1454
+ if (typeof options !== "object" || options === null) {
1455
+ throw new Error("resolveNestedClipPath: options must be an object");
1289
1456
  }
1290
1457
  const clipId = clipPathDef.id;
1291
1458
  if (clipId && visited.has(clipId)) {
@@ -1300,7 +1467,9 @@ export function resolveNestedClipPath(
1300
1467
  const nestedRef = clipPathDef["clip-path"].replace(/^url\(#?|[)'"]/g, "");
1301
1468
  const nestedClipDef = defsMap.get(nestedRef);
1302
1469
  if (!nestedClipDef) {
1303
- Logger.warn(`resolveNestedClipPath: referenced clipPath not found: ${nestedRef}`);
1470
+ Logger.warn(
1471
+ `resolveNestedClipPath: referenced clipPath not found: ${nestedRef}`,
1472
+ );
1304
1473
  // Continue with current clipPolygon, ignoring missing reference
1305
1474
  } else {
1306
1475
  const nestedClip = resolveNestedClipPath(
@@ -1318,7 +1487,9 @@ export function resolveNestedClipPath(
1318
1487
  );
1319
1488
  clipPolygon = intersection.length > 0 ? intersection[0] : [];
1320
1489
  } else {
1321
- Logger.warn(`resolveNestedClipPath: nested clipPath ${nestedRef} resolved to empty polygon`);
1490
+ Logger.warn(
1491
+ `resolveNestedClipPath: nested clipPath ${nestedRef} resolved to empty polygon`,
1492
+ );
1322
1493
  clipPolygon = [];
1323
1494
  }
1324
1495
  }