@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
@@ -131,8 +131,10 @@ export const MaskType = {
131
131
  * console.log(`Number of shapes: ${maskData.children.length}`);
132
132
  */
133
133
  export function parseMaskElement(maskElement) {
134
- if (!maskElement) throw new Error('parseMaskElement: maskElement is required');
135
- if (!maskElement.getAttribute) throw new Error('parseMaskElement: maskElement must be a DOM element');
134
+ if (!maskElement)
135
+ throw new Error("parseMaskElement: maskElement is required");
136
+ if (!maskElement.getAttribute)
137
+ throw new Error("parseMaskElement: maskElement must be a DOM element");
136
138
 
137
139
  const data = {
138
140
  id: maskElement.getAttribute("id") || "",
@@ -159,15 +161,24 @@ export function parseMaskElement(maskElement) {
159
161
 
160
162
  // Set defaults based on maskUnits
161
163
  if (data.maskUnits === "objectBoundingBox") {
162
- data.x = data.x !== null ? safeParseFloat(data.x, DEFAULT_MASK_X) : DEFAULT_MASK_X;
163
- data.y = data.y !== null ? safeParseFloat(data.y, DEFAULT_MASK_Y) : DEFAULT_MASK_Y;
164
- data.width = data.width !== null ? safeParseFloat(data.width, DEFAULT_MASK_WIDTH) : DEFAULT_MASK_WIDTH;
165
- data.height = data.height !== null ? safeParseFloat(data.height, DEFAULT_MASK_HEIGHT) : DEFAULT_MASK_HEIGHT;
164
+ data.x =
165
+ data.x !== null ? safeParseFloat(data.x, DEFAULT_MASK_X) : DEFAULT_MASK_X;
166
+ data.y =
167
+ data.y !== null ? safeParseFloat(data.y, DEFAULT_MASK_Y) : DEFAULT_MASK_Y;
168
+ data.width =
169
+ data.width !== null
170
+ ? safeParseFloat(data.width, DEFAULT_MASK_WIDTH)
171
+ : DEFAULT_MASK_WIDTH;
172
+ data.height =
173
+ data.height !== null
174
+ ? safeParseFloat(data.height, DEFAULT_MASK_HEIGHT)
175
+ : DEFAULT_MASK_HEIGHT;
166
176
  } else {
167
177
  data.x = data.x !== null ? safeParseFloat(data.x, null) : null;
168
178
  data.y = data.y !== null ? safeParseFloat(data.y, null) : null;
169
179
  data.width = data.width !== null ? safeParseFloat(data.width, null) : null;
170
- data.height = data.height !== null ? safeParseFloat(data.height, null) : null;
180
+ data.height =
181
+ data.height !== null ? safeParseFloat(data.height, null) : null;
171
182
  }
172
183
 
173
184
  // Parse child elements
@@ -182,11 +193,11 @@ export function parseMaskElement(maskElement) {
182
193
  fill: child.getAttribute("fill") || child.style?.fill || "black",
183
194
  fillOpacity: safeParseFloat(
184
195
  child.getAttribute("fill-opacity") || child.style?.fillOpacity || "1",
185
- 1
196
+ 1,
186
197
  ),
187
198
  opacity: safeParseFloat(
188
199
  child.getAttribute("opacity") || child.style?.opacity || "1",
189
- 1
200
+ 1,
190
201
  ),
191
202
  transform: child.getAttribute("transform") || null,
192
203
  };
@@ -197,7 +208,10 @@ export function parseMaskElement(maskElement) {
197
208
  childData.x = safeParseFloat(child.getAttribute("x") || "0", 0);
198
209
  childData.y = safeParseFloat(child.getAttribute("y") || "0", 0);
199
210
  childData.width = safeParseFloat(child.getAttribute("width") || "0", 0);
200
- childData.height = safeParseFloat(child.getAttribute("height") || "0", 0);
211
+ childData.height = safeParseFloat(
212
+ child.getAttribute("height") || "0",
213
+ 0,
214
+ );
201
215
  childData.rx = safeParseFloat(child.getAttribute("rx") || "0", 0);
202
216
  childData.ry = safeParseFloat(child.getAttribute("ry") || "0", 0);
203
217
  break;
@@ -292,21 +306,39 @@ export function parseMaskElement(maskElement) {
292
306
  * // region.y = 50 (absolute)
293
307
  */
294
308
  export function getMaskRegion(maskData, targetBBox) {
295
- if (!maskData) throw new Error('getMaskRegion: maskData is required');
296
- if (!targetBBox) throw new Error('getMaskRegion: targetBBox is required');
297
- if (typeof targetBBox.x !== 'number' || typeof targetBBox.y !== 'number' ||
298
- typeof targetBBox.width !== 'number' || typeof targetBBox.height !== 'number') {
299
- throw new Error('getMaskRegion: targetBBox must have numeric x, y, width, height properties');
309
+ if (!maskData) throw new Error("getMaskRegion: maskData is required");
310
+ if (!targetBBox) throw new Error("getMaskRegion: targetBBox is required");
311
+ if (
312
+ typeof targetBBox.x !== "number" ||
313
+ typeof targetBBox.y !== "number" ||
314
+ typeof targetBBox.width !== "number" ||
315
+ typeof targetBBox.height !== "number"
316
+ ) {
317
+ throw new Error(
318
+ "getMaskRegion: targetBBox must have numeric x, y, width, height properties",
319
+ );
300
320
  }
301
321
  if (targetBBox.width < 0 || targetBBox.height < 0) {
302
- throw new Error('getMaskRegion: targetBBox width and height cannot be negative');
322
+ throw new Error(
323
+ "getMaskRegion: targetBBox width and height cannot be negative",
324
+ );
303
325
  }
304
326
  if (targetBBox.width === 0 || targetBBox.height === 0) {
305
- throw new Error('getMaskRegion: targetBBox width and height must be greater than zero');
327
+ throw new Error(
328
+ "getMaskRegion: targetBBox width and height must be greater than zero",
329
+ );
306
330
  }
307
- if (!maskData.maskUnits) throw new Error('getMaskRegion: maskData.maskUnits is required');
308
- if (maskData.x == null || maskData.y == null || maskData.width == null || maskData.height == null) {
309
- throw new Error('getMaskRegion: maskData must have x, y, width, height properties');
331
+ if (!maskData.maskUnits)
332
+ throw new Error("getMaskRegion: maskData.maskUnits is required");
333
+ if (
334
+ maskData.x == null ||
335
+ maskData.y == null ||
336
+ maskData.width == null ||
337
+ maskData.height == null
338
+ ) {
339
+ throw new Error(
340
+ "getMaskRegion: maskData must have x, y, width, height properties",
341
+ );
310
342
  }
311
343
 
312
344
  if (maskData.maskUnits === "objectBoundingBox") {
@@ -376,18 +408,28 @@ export function maskChildToPolygon(
376
408
  contentUnits,
377
409
  samples = 20,
378
410
  ) {
379
- if (!child) throw new Error('maskChildToPolygon: child is required');
380
- if (!targetBBox) throw new Error('maskChildToPolygon: targetBBox is required');
381
- if (!contentUnits) throw new Error('maskChildToPolygon: contentUnits is required');
382
- if (typeof samples !== 'number' || samples <= 0) {
383
- throw new Error('maskChildToPolygon: samples must be a positive number');
411
+ if (!child) throw new Error("maskChildToPolygon: child is required");
412
+ if (!targetBBox)
413
+ throw new Error("maskChildToPolygon: targetBBox is required");
414
+ if (!contentUnits)
415
+ throw new Error("maskChildToPolygon: contentUnits is required");
416
+ if (typeof samples !== "number" || samples <= 0) {
417
+ throw new Error("maskChildToPolygon: samples must be a positive number");
384
418
  }
385
- if (typeof targetBBox.x !== 'number' || typeof targetBBox.y !== 'number' ||
386
- typeof targetBBox.width !== 'number' || typeof targetBBox.height !== 'number') {
387
- throw new Error('maskChildToPolygon: targetBBox must have numeric x, y, width, height properties');
419
+ if (
420
+ typeof targetBBox.x !== "number" ||
421
+ typeof targetBBox.y !== "number" ||
422
+ typeof targetBBox.width !== "number" ||
423
+ typeof targetBBox.height !== "number"
424
+ ) {
425
+ throw new Error(
426
+ "maskChildToPolygon: targetBBox must have numeric x, y, width, height properties",
427
+ );
388
428
  }
389
429
  if (targetBBox.width < 0 || targetBBox.height < 0) {
390
- throw new Error('maskChildToPolygon: targetBBox width and height cannot be negative');
430
+ throw new Error(
431
+ "maskChildToPolygon: targetBBox width and height cannot be negative",
432
+ );
391
433
  }
392
434
 
393
435
  // Create element-like object for ClipPathResolver
@@ -470,12 +512,14 @@ export function colorToLuminance(colorStr) {
470
512
  return 0;
471
513
  }
472
514
 
473
- if (typeof colorStr !== 'string') {
515
+ if (typeof colorStr !== "string") {
474
516
  return 0; // Invalid input: treat as black (luminance = 0)
475
517
  }
476
518
 
477
519
  // Parse RGB values (note: alpha channel in rgba() is intentionally ignored for luminance calculation)
478
- const rgbMatch = colorStr.match(/rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i);
520
+ const rgbMatch = colorStr.match(
521
+ /rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i,
522
+ );
479
523
  if (rgbMatch) {
480
524
  const r = parseInt(rgbMatch[1], 10);
481
525
  const g = parseInt(rgbMatch[2], 10);
@@ -501,7 +545,8 @@ export function colorToLuminance(colorStr) {
501
545
  const b = parseInt(hex.slice(4, 6), 16);
502
546
  // Validate parsed values
503
547
  if (isNaN(r) || isNaN(g) || isNaN(b)) return 0; // Invalid hex: treat as black
504
- const luminance = 0.2126 * (r / 255) + 0.7152 * (g / 255) + 0.0722 * (b / 255);
548
+ const luminance =
549
+ 0.2126 * (r / 255) + 0.7152 * (g / 255) + 0.0722 * (b / 255);
505
550
  return isNaN(luminance) ? 0 : luminance; // NaN: treat as black
506
551
  }
507
552
 
@@ -570,14 +615,22 @@ export function colorToLuminance(colorStr) {
570
615
  * // Returns: 1.0 × 1.0 × 0.0 = 0.0 (black = fully transparent in luminance mask)
571
616
  */
572
617
  export function getMaskChildOpacity(child, maskType) {
573
- if (!child) throw new Error('getMaskChildOpacity: child is required');
574
- if (!maskType) throw new Error('getMaskChildOpacity: maskType is required');
618
+ if (!child) throw new Error("getMaskChildOpacity: child is required");
619
+ if (!maskType) throw new Error("getMaskChildOpacity: maskType is required");
575
620
  if (maskType !== MaskType.LUMINANCE && maskType !== MaskType.ALPHA) {
576
- throw new Error('getMaskChildOpacity: maskType must be "luminance" or "alpha"');
621
+ throw new Error(
622
+ 'getMaskChildOpacity: maskType must be "luminance" or "alpha"',
623
+ );
577
624
  }
578
625
 
579
- const fillOpacity = typeof child.fillOpacity === 'number' && !isNaN(child.fillOpacity) ? child.fillOpacity : 1;
580
- const opacity = typeof child.opacity === 'number' && !isNaN(child.opacity) ? child.opacity : 1;
626
+ const fillOpacity =
627
+ typeof child.fillOpacity === "number" && !isNaN(child.fillOpacity)
628
+ ? child.fillOpacity
629
+ : 1;
630
+ const opacity =
631
+ typeof child.opacity === "number" && !isNaN(child.opacity)
632
+ ? child.opacity
633
+ : 1;
581
634
  const baseOpacity = fillOpacity * opacity;
582
635
 
583
636
  if (maskType === MaskType.ALPHA) {
@@ -632,13 +685,14 @@ export function getMaskChildOpacity(child, maskType) {
632
685
  * });
633
686
  */
634
687
  export function resolveMask(maskData, targetBBox, options = {}) {
635
- if (!maskData) throw new Error('resolveMask: maskData is required');
636
- if (!targetBBox) throw new Error('resolveMask: targetBBox is required');
637
- if (!Array.isArray(maskData.children)) throw new Error('resolveMask: maskData.children must be an array');
688
+ if (!maskData) throw new Error("resolveMask: maskData is required");
689
+ if (!targetBBox) throw new Error("resolveMask: targetBBox is required");
690
+ if (!Array.isArray(maskData.children))
691
+ throw new Error("resolveMask: maskData.children must be an array");
638
692
 
639
693
  const { samples = 20 } = options;
640
- if (typeof samples !== 'number' || samples <= 0) {
641
- throw new Error('resolveMask: samples must be a positive number');
694
+ if (typeof samples !== "number" || samples <= 0) {
695
+ throw new Error("resolveMask: samples must be a positive number");
642
696
  }
643
697
 
644
698
  const maskType = maskData.maskType || MaskType.LUMINANCE;
@@ -718,11 +772,12 @@ export function resolveMask(maskData, targetBBox, options = {}) {
718
772
  * });
719
773
  */
720
774
  export function applyMask(targetPolygon, maskData, targetBBox, options = {}) {
721
- if (!targetPolygon) throw new Error('applyMask: targetPolygon is required');
722
- if (!Array.isArray(targetPolygon)) throw new Error('applyMask: targetPolygon must be an array');
775
+ if (!targetPolygon) throw new Error("applyMask: targetPolygon is required");
776
+ if (!Array.isArray(targetPolygon))
777
+ throw new Error("applyMask: targetPolygon must be an array");
723
778
  if (targetPolygon.length === 0) return []; // Empty polygon returns empty result
724
- if (!maskData) throw new Error('applyMask: maskData is required');
725
- if (!targetBBox) throw new Error('applyMask: targetBBox is required');
779
+ if (!maskData) throw new Error("applyMask: maskData is required");
780
+ if (!targetBBox) throw new Error("applyMask: targetBBox is required");
726
781
 
727
782
  const maskRegions = resolveMask(maskData, targetBBox, options);
728
783
  const result = [];
@@ -800,10 +855,16 @@ export function maskToClipPath(
800
855
  opacityThreshold = 0.5,
801
856
  options = {},
802
857
  ) {
803
- if (!maskData) throw new Error('maskToClipPath: maskData is required');
804
- if (!targetBBox) throw new Error('maskToClipPath: targetBBox is required');
805
- if (typeof opacityThreshold !== 'number' || opacityThreshold < 0 || opacityThreshold > 1) {
806
- throw new Error('maskToClipPath: opacityThreshold must be a number between 0 and 1');
858
+ if (!maskData) throw new Error("maskToClipPath: maskData is required");
859
+ if (!targetBBox) throw new Error("maskToClipPath: targetBBox is required");
860
+ if (
861
+ typeof opacityThreshold !== "number" ||
862
+ opacityThreshold < 0 ||
863
+ opacityThreshold > 1
864
+ ) {
865
+ throw new Error(
866
+ "maskToClipPath: opacityThreshold must be a number between 0 and 1",
867
+ );
807
868
  }
808
869
 
809
870
  const maskRegions = resolveMask(maskData, targetBBox, options);
@@ -868,8 +929,8 @@ export function maskToClipPath(
868
929
  * `;
869
930
  */
870
931
  export function maskToPathData(maskData, targetBBox, options = {}) {
871
- if (!maskData) throw new Error('maskToPathData: maskData is required');
872
- if (!targetBBox) throw new Error('maskToPathData: targetBBox is required');
932
+ if (!maskData) throw new Error("maskToPathData: maskData is required");
933
+ if (!targetBBox) throw new Error("maskToPathData: targetBBox is required");
873
934
 
874
935
  const polygon = maskToClipPath(maskData, targetBBox, 0.5, options);
875
936
 
@@ -882,7 +943,8 @@ export function maskToPathData(maskData, targetBBox, options = {}) {
882
943
  if (!p || p.x === undefined || p.y === undefined) continue; // Skip invalid points
883
944
  const xNum = Number(p.x);
884
945
  const yNum = Number(p.y);
885
- if (isNaN(xNum) || isNaN(yNum) || !isFinite(xNum) || !isFinite(yNum)) continue; // Skip NaN/Infinity
946
+ if (isNaN(xNum) || isNaN(yNum) || !isFinite(xNum) || !isFinite(yNum))
947
+ continue; // Skip NaN/Infinity
886
948
  const x = xNum.toFixed(6);
887
949
  const y = yNum.toFixed(6);
888
950
  d += validPointCount === 0 ? `M ${x} ${y}` : ` L ${x} ${y}`;
@@ -976,12 +1038,15 @@ export function parseGradientReference(fill) {
976
1038
  */
977
1039
  export function rgbToLuminance(color) {
978
1040
  if (!color) return 0;
979
- if (typeof color !== 'object') return 0;
1041
+ if (typeof color !== "object") return 0;
980
1042
 
981
1043
  // Validate and clamp RGB values to 0-255 range
982
- const rVal = typeof color.r === 'number' ? Math.max(0, Math.min(255, color.r)) : 0;
983
- const gVal = typeof color.g === 'number' ? Math.max(0, Math.min(255, color.g)) : 0;
984
- const bVal = typeof color.b === 'number' ? Math.max(0, Math.min(255, color.b)) : 0;
1044
+ const rVal =
1045
+ typeof color.r === "number" ? Math.max(0, Math.min(255, color.r)) : 0;
1046
+ const gVal =
1047
+ typeof color.g === "number" ? Math.max(0, Math.min(255, color.g)) : 0;
1048
+ const bVal =
1049
+ typeof color.b === "number" ? Math.max(0, Math.min(255, color.b)) : 0;
985
1050
 
986
1051
  const r = rVal / 255;
987
1052
  const g = gVal / 255;
@@ -1046,15 +1111,21 @@ export function sampleMeshGradientForMask(
1046
1111
  maskType = "luminance",
1047
1112
  options = {},
1048
1113
  ) {
1049
- if (!meshData) throw new Error('sampleMeshGradientForMask: meshData is required');
1050
- if (!shapeBBox) throw new Error('sampleMeshGradientForMask: shapeBBox is required');
1114
+ if (!meshData)
1115
+ throw new Error("sampleMeshGradientForMask: meshData is required");
1116
+ if (!shapeBBox)
1117
+ throw new Error("sampleMeshGradientForMask: shapeBBox is required");
1051
1118
  if (maskType !== MaskType.LUMINANCE && maskType !== MaskType.ALPHA) {
1052
- throw new Error('sampleMeshGradientForMask: maskType must be "luminance" or "alpha"');
1119
+ throw new Error(
1120
+ 'sampleMeshGradientForMask: maskType must be "luminance" or "alpha"',
1121
+ );
1053
1122
  }
1054
1123
 
1055
1124
  const { subdivisions = 4 } = options;
1056
- if (typeof subdivisions !== 'number' || subdivisions <= 0) {
1057
- throw new Error('sampleMeshGradientForMask: subdivisions must be a positive number');
1125
+ if (typeof subdivisions !== "number" || subdivisions <= 0) {
1126
+ throw new Error(
1127
+ "sampleMeshGradientForMask: subdivisions must be a positive number",
1128
+ );
1058
1129
  }
1059
1130
 
1060
1131
  const result = [];
@@ -1073,7 +1144,8 @@ export function sampleMeshGradientForMask(
1073
1144
  let opacity;
1074
1145
  if (maskType === MaskType.ALPHA) {
1075
1146
  // Alpha mask: use alpha channel
1076
- const alphaVal = typeof color.a === 'number' ? Math.max(0, Math.min(255, color.a)) : 255;
1147
+ const alphaVal =
1148
+ typeof color.a === "number" ? Math.max(0, Math.min(255, color.a)) : 255;
1077
1149
  opacity = alphaVal / 255;
1078
1150
  } else {
1079
1151
  // Luminance mask: calculate from RGB
@@ -1140,11 +1212,14 @@ export function applyMeshGradientMask(
1140
1212
  maskType = "luminance",
1141
1213
  options = {},
1142
1214
  ) {
1143
- if (!targetPolygon) throw new Error('applyMeshGradientMask: targetPolygon is required');
1144
- if (!Array.isArray(targetPolygon)) throw new Error('applyMeshGradientMask: targetPolygon must be an array');
1215
+ if (!targetPolygon)
1216
+ throw new Error("applyMeshGradientMask: targetPolygon is required");
1217
+ if (!Array.isArray(targetPolygon))
1218
+ throw new Error("applyMeshGradientMask: targetPolygon must be an array");
1145
1219
  if (targetPolygon.length === 0) return []; // Empty polygon returns empty result
1146
- if (!meshData) throw new Error('applyMeshGradientMask: meshData is required');
1147
- if (!targetBBox) throw new Error('applyMeshGradientMask: targetBBox is required');
1220
+ if (!meshData) throw new Error("applyMeshGradientMask: meshData is required");
1221
+ if (!targetBBox)
1222
+ throw new Error("applyMeshGradientMask: targetBBox is required");
1148
1223
 
1149
1224
  const meshMaskRegions = sampleMeshGradientForMask(
1150
1225
  meshData,
@@ -1245,16 +1320,25 @@ export function resolveMaskWithGradients(
1245
1320
  gradientDefs = {},
1246
1321
  options = {},
1247
1322
  ) {
1248
- if (!maskData) throw new Error('resolveMaskWithGradients: maskData is required');
1249
- if (!targetBBox) throw new Error('resolveMaskWithGradients: targetBBox is required');
1250
- if (!Array.isArray(maskData.children)) throw new Error('resolveMaskWithGradients: maskData.children must be an array');
1323
+ if (!maskData)
1324
+ throw new Error("resolveMaskWithGradients: maskData is required");
1325
+ if (!targetBBox)
1326
+ throw new Error("resolveMaskWithGradients: targetBBox is required");
1327
+ if (!Array.isArray(maskData.children))
1328
+ throw new Error(
1329
+ "resolveMaskWithGradients: maskData.children must be an array",
1330
+ );
1251
1331
 
1252
1332
  const { samples = 20, subdivisions = 4 } = options;
1253
- if (typeof samples !== 'number' || samples <= 0) {
1254
- throw new Error('resolveMaskWithGradients: samples must be a positive number');
1333
+ if (typeof samples !== "number" || samples <= 0) {
1334
+ throw new Error(
1335
+ "resolveMaskWithGradients: samples must be a positive number",
1336
+ );
1255
1337
  }
1256
- if (typeof subdivisions !== 'number' || subdivisions <= 0) {
1257
- throw new Error('resolveMaskWithGradients: subdivisions must be a positive number');
1338
+ if (typeof subdivisions !== "number" || subdivisions <= 0) {
1339
+ throw new Error(
1340
+ "resolveMaskWithGradients: subdivisions must be a positive number",
1341
+ );
1258
1342
  }
1259
1343
 
1260
1344
  const maskType = maskData.maskType || MaskType.LUMINANCE;
@@ -1307,10 +1391,22 @@ export function resolveMaskWithGradients(
1307
1391
  for (const clippedPoly of clipped) {
1308
1392
  if (clippedPoly && clippedPoly.length >= 3) {
1309
1393
  // Combine mesh opacity with child opacity - validate all values
1310
- const childOpacity = typeof child.opacity === 'number' && !isNaN(child.opacity) ? child.opacity : 1;
1311
- const childFillOpacity = typeof child.fillOpacity === 'number' && !isNaN(child.fillOpacity) ? child.fillOpacity : 1;
1312
- const combinedOpacity = meshOpacity * childOpacity * childFillOpacity;
1313
- if (combinedOpacity > 0 && !isNaN(combinedOpacity) && isFinite(combinedOpacity)) {
1394
+ const childOpacity =
1395
+ typeof child.opacity === "number" && !isNaN(child.opacity)
1396
+ ? child.opacity
1397
+ : 1;
1398
+ const childFillOpacity =
1399
+ typeof child.fillOpacity === "number" &&
1400
+ !isNaN(child.fillOpacity)
1401
+ ? child.fillOpacity
1402
+ : 1;
1403
+ const combinedOpacity =
1404
+ meshOpacity * childOpacity * childFillOpacity;
1405
+ if (
1406
+ combinedOpacity > 0 &&
1407
+ !isNaN(combinedOpacity) &&
1408
+ isFinite(combinedOpacity)
1409
+ ) {
1314
1410
  result.push({ polygon: clippedPoly, opacity: combinedOpacity });
1315
1411
  }
1316
1412
  }
@@ -1396,11 +1492,19 @@ export function createMeshGradientMask(
1396
1492
  maskType = "luminance",
1397
1493
  options = {},
1398
1494
  ) {
1399
- if (!meshData) throw new Error('createMeshGradientMask: meshData is required');
1400
- if (!bounds) throw new Error('createMeshGradientMask: bounds is required');
1401
- if (typeof bounds !== 'object' || typeof bounds.x !== 'number' || typeof bounds.y !== 'number' ||
1402
- typeof bounds.width !== 'number' || typeof bounds.height !== 'number') {
1403
- throw new Error('createMeshGradientMask: bounds must have numeric x, y, width, height properties');
1495
+ if (!meshData)
1496
+ throw new Error("createMeshGradientMask: meshData is required");
1497
+ if (!bounds) throw new Error("createMeshGradientMask: bounds is required");
1498
+ if (
1499
+ typeof bounds !== "object" ||
1500
+ typeof bounds.x !== "number" ||
1501
+ typeof bounds.y !== "number" ||
1502
+ typeof bounds.width !== "number" ||
1503
+ typeof bounds.height !== "number"
1504
+ ) {
1505
+ throw new Error(
1506
+ "createMeshGradientMask: bounds must have numeric x, y, width, height properties",
1507
+ );
1404
1508
  }
1405
1509
 
1406
1510
  const regions = sampleMeshGradientForMask(
@@ -1465,14 +1569,21 @@ export function createMeshGradientMask(
1465
1569
  * console.log(`Boundary has ${boundary.length} vertices`);
1466
1570
  */
1467
1571
  export function getMeshGradientBoundary(meshData, options = {}) {
1468
- if (!meshData) throw new Error('getMeshGradientBoundary: meshData is required');
1572
+ if (!meshData)
1573
+ throw new Error("getMeshGradientBoundary: meshData is required");
1469
1574
 
1470
1575
  const { samples = 20 } = options;
1471
- if (typeof samples !== 'number' || samples <= 0) {
1472
- throw new Error('getMeshGradientBoundary: samples must be a positive number');
1576
+ if (typeof samples !== "number" || samples <= 0) {
1577
+ throw new Error(
1578
+ "getMeshGradientBoundary: samples must be a positive number",
1579
+ );
1473
1580
  }
1474
1581
 
1475
- if (!meshData.patches || !Array.isArray(meshData.patches) || meshData.patches.length === 0) {
1582
+ if (
1583
+ !meshData.patches ||
1584
+ !Array.isArray(meshData.patches) ||
1585
+ meshData.patches.length === 0
1586
+ ) {
1476
1587
  return [];
1477
1588
  }
1478
1589
 
@@ -1583,14 +1694,21 @@ export function clipWithMeshGradientShape(
1583
1694
  meshData,
1584
1695
  options = {},
1585
1696
  ) {
1586
- if (!targetPolygon) throw new Error('clipWithMeshGradientShape: targetPolygon is required');
1587
- if (!Array.isArray(targetPolygon)) throw new Error('clipWithMeshGradientShape: targetPolygon must be an array');
1697
+ if (!targetPolygon)
1698
+ throw new Error("clipWithMeshGradientShape: targetPolygon is required");
1699
+ if (!Array.isArray(targetPolygon))
1700
+ throw new Error(
1701
+ "clipWithMeshGradientShape: targetPolygon must be an array",
1702
+ );
1588
1703
  if (targetPolygon.length === 0) return []; // Empty polygon returns empty result
1589
- if (!meshData) throw new Error('clipWithMeshGradientShape: meshData is required');
1704
+ if (!meshData)
1705
+ throw new Error("clipWithMeshGradientShape: meshData is required");
1590
1706
 
1591
1707
  const { subdivisions = 4 } = options;
1592
- if (typeof subdivisions !== 'number' || subdivisions <= 0) {
1593
- throw new Error('clipWithMeshGradientShape: subdivisions must be a positive number');
1708
+ if (typeof subdivisions !== "number" || subdivisions <= 0) {
1709
+ throw new Error(
1710
+ "clipWithMeshGradientShape: subdivisions must be a positive number",
1711
+ );
1594
1712
  }
1595
1713
 
1596
1714
  // Get all patch polygons (ignoring colors)
@@ -1609,14 +1727,22 @@ export function clipWithMeshGradientShape(
1609
1727
  }
1610
1728
 
1611
1729
  for (let i = 1; i < meshPolygons.length; i++) {
1612
- if (!meshPolygons[i] || !meshPolygons[i].polygon || meshPolygons[i].polygon.length < 3) {
1730
+ if (
1731
+ !meshPolygons[i] ||
1732
+ !meshPolygons[i].polygon ||
1733
+ meshPolygons[i].polygon.length < 3
1734
+ ) {
1613
1735
  continue; // Skip invalid polygons
1614
1736
  }
1615
1737
  const unionResult = PolygonClip.polygonUnion(
1616
1738
  meshShape,
1617
1739
  meshPolygons[i].polygon,
1618
1740
  );
1619
- if (Array.isArray(unionResult) && unionResult.length > 0 && unionResult[0].length >= 3) {
1741
+ if (
1742
+ Array.isArray(unionResult) &&
1743
+ unionResult.length > 0 &&
1744
+ unionResult[0].length >= 3
1745
+ ) {
1620
1746
  meshShape = unionResult[0];
1621
1747
  }
1622
1748
  }
@@ -1672,11 +1798,14 @@ export function clipWithMeshGradientShape(
1672
1798
  * const svgClipPath = `<clipPath id="mesh-shape"><path d="${pathData}"/></clipPath>`;
1673
1799
  */
1674
1800
  export function meshGradientToClipPath(meshData, options = {}) {
1675
- if (!meshData) throw new Error('meshGradientToClipPath: meshData is required');
1801
+ if (!meshData)
1802
+ throw new Error("meshGradientToClipPath: meshData is required");
1676
1803
 
1677
1804
  const { subdivisions = 4 } = options;
1678
- if (typeof subdivisions !== 'number' || subdivisions <= 0) {
1679
- throw new Error('meshGradientToClipPath: subdivisions must be a positive number');
1805
+ if (typeof subdivisions !== "number" || subdivisions <= 0) {
1806
+ throw new Error(
1807
+ "meshGradientToClipPath: subdivisions must be a positive number",
1808
+ );
1680
1809
  }
1681
1810
 
1682
1811
  const meshPolygons = MeshGradient.meshGradientToPolygons(meshData, {
@@ -1699,7 +1828,11 @@ export function meshGradientToClipPath(meshData, options = {}) {
1699
1828
  const poly = meshPolygons[i].polygon;
1700
1829
  if (poly.length >= 3) {
1701
1830
  const unionResult = PolygonClip.polygonUnion(result, poly);
1702
- if (Array.isArray(unionResult) && unionResult.length > 0 && unionResult[0].length >= 3) {
1831
+ if (
1832
+ Array.isArray(unionResult) &&
1833
+ unionResult.length > 0 &&
1834
+ unionResult[0].length >= 3
1835
+ ) {
1703
1836
  result = unionResult[0];
1704
1837
  }
1705
1838
  }
package/src/matrix.js CHANGED
@@ -96,7 +96,7 @@ export class Matrix {
96
96
  throw new Error("size must be a positive integer");
97
97
  const out = Array.from({ length: n }, (_, i) =>
98
98
  Array.from({ length: n }, (_, j) =>
99
- (i === j ? new Decimal(1) : new Decimal(0)),
99
+ i === j ? new Decimal(1) : new Decimal(0),
100
100
  ),
101
101
  );
102
102
  return new Matrix(out);
@@ -400,11 +400,11 @@ export class Matrix {
400
400
  // Create augmented matrix [A | I]
401
401
  const aug = Array.from({ length: n }, (_, i) =>
402
402
  Array.from({ length: 2 * n }, (_, j) =>
403
- (j < n
403
+ j < n
404
404
  ? new Decimal(this.data[i][j])
405
405
  : j - n === i
406
406
  ? new Decimal(1)
407
- : new Decimal(0)),
407
+ : new Decimal(0),
408
408
  ),
409
409
  );
410
410
  // Gauss-Jordan elimination
@@ -498,7 +498,9 @@ export class Matrix {
498
498
  for (let i = n - 1; i >= 0; i--) {
499
499
  // Check for zero diagonal element (should not happen after forward elimination)
500
500
  if (aug[i][i].isZero())
501
- throw new Error("Zero diagonal element in back substitution: system is singular");
501
+ throw new Error(
502
+ "Zero diagonal element in back substitution: system is singular",
503
+ );
502
504
  let sum = new Decimal(0);
503
505
  for (let j = i + 1; j < n; j++) sum = sum.plus(aug[i][j].mul(x[j]));
504
506
  x[i] = aug[i][n].minus(sum).div(aug[i][i]);
@@ -614,7 +616,9 @@ export class Matrix {
614
616
  s = Math.max(0, Math.ceil(Math.log2(logVal)));
615
617
  // Cap scaling factor to prevent DoS - 50 iterations provides 2^50 scaling which is sufficient
616
618
  if (s > 50)
617
- throw new Error(`Matrix norm too large: requires ${s} scaling steps (max 50 allowed)`);
619
+ throw new Error(
620
+ `Matrix norm too large: requires ${s} scaling steps (max 50 allowed)`,
621
+ );
618
622
  }
619
623
  let A = this;
620
624
  if (s > 0) A = this.mul(new Decimal(1).div(new Decimal(2).pow(s)));