@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.
- package/bin/svg-matrix.js +7 -6
- package/bin/svgm.js +109 -40
- package/dist/svg-matrix.min.js +7 -7
- package/dist/svg-toolbox.min.js +148 -228
- package/dist/svgm.min.js +152 -232
- package/dist/version.json +5 -5
- package/package.json +1 -1
- package/scripts/postinstall.js +72 -41
- package/scripts/test-postinstall.js +18 -16
- package/scripts/version-sync.js +78 -60
- package/src/animation-optimization.js +190 -98
- package/src/animation-references.js +11 -3
- package/src/arc-length.js +23 -20
- package/src/bezier-analysis.js +9 -13
- package/src/bezier-intersections.js +18 -4
- package/src/browser-verify.js +35 -8
- package/src/clip-path-resolver.js +285 -114
- package/src/convert-path-data.js +20 -8
- package/src/css-specificity.js +33 -9
- package/src/douglas-peucker.js +272 -141
- package/src/geometry-to-path.js +79 -22
- package/src/gjk-collision.js +287 -126
- package/src/index.js +56 -21
- package/src/inkscape-support.js +122 -101
- package/src/logger.js +43 -27
- package/src/marker-resolver.js +201 -121
- package/src/mask-resolver.js +231 -98
- package/src/matrix.js +9 -5
- package/src/mesh-gradient.js +22 -14
- package/src/off-canvas-detection.js +53 -17
- package/src/path-optimization.js +356 -171
- package/src/path-simplification.js +671 -256
- package/src/pattern-resolver.js +1 -3
- package/src/polygon-clip.js +396 -78
- package/src/svg-boolean-ops.js +90 -23
- package/src/svg-collections.js +1546 -667
- package/src/svg-flatten.js +152 -38
- package/src/svg-matrix-lib.js +2 -2
- package/src/svg-parser.js +5 -1
- package/src/svg-rendering-context.js +3 -1
- package/src/svg-toolbox-lib.js +2 -2
- package/src/svg-toolbox.js +99 -457
- package/src/svg-validation-data.js +513 -345
- package/src/svg2-polyfills.js +156 -93
- package/src/svgm-lib.js +8 -4
- package/src/transform-optimization.js +168 -51
- package/src/transforms2d.js +73 -40
- package/src/transforms3d.js +34 -27
- package/src/use-symbol-resolver.js +175 -76
- package/src/vector.js +80 -44
- package/src/vendor/inkscape-hatch-polyfill.js +143 -108
- package/src/vendor/inkscape-hatch-polyfill.min.js +291 -1
- package/src/vendor/inkscape-mesh-polyfill.js +953 -766
- package/src/vendor/inkscape-mesh-polyfill.min.js +896 -1
- package/src/verification.js +3 -4
package/src/mask-resolver.js
CHANGED
|
@@ -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)
|
|
135
|
-
|
|
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 =
|
|
163
|
-
|
|
164
|
-
data.
|
|
165
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
296
|
-
if (!targetBBox) throw new Error(
|
|
297
|
-
if (
|
|
298
|
-
|
|
299
|
-
|
|
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(
|
|
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(
|
|
327
|
+
throw new Error(
|
|
328
|
+
"getMaskRegion: targetBBox width and height must be greater than zero",
|
|
329
|
+
);
|
|
306
330
|
}
|
|
307
|
-
if (!maskData.maskUnits)
|
|
308
|
-
|
|
309
|
-
|
|
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(
|
|
380
|
-
if (!targetBBox)
|
|
381
|
-
|
|
382
|
-
if (
|
|
383
|
-
throw new Error(
|
|
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 (
|
|
386
|
-
|
|
387
|
-
|
|
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(
|
|
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 !==
|
|
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(
|
|
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 =
|
|
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(
|
|
574
|
-
if (!maskType) throw new Error(
|
|
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(
|
|
621
|
+
throw new Error(
|
|
622
|
+
'getMaskChildOpacity: maskType must be "luminance" or "alpha"',
|
|
623
|
+
);
|
|
577
624
|
}
|
|
578
625
|
|
|
579
|
-
const fillOpacity =
|
|
580
|
-
|
|
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(
|
|
636
|
-
if (!targetBBox) throw new Error(
|
|
637
|
-
if (!Array.isArray(maskData.children))
|
|
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 !==
|
|
641
|
-
throw new Error(
|
|
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(
|
|
722
|
-
if (!Array.isArray(targetPolygon))
|
|
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(
|
|
725
|
-
if (!targetBBox) throw new Error(
|
|
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(
|
|
804
|
-
if (!targetBBox) throw new Error(
|
|
805
|
-
if (
|
|
806
|
-
|
|
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(
|
|
872
|
-
if (!targetBBox) throw new Error(
|
|
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))
|
|
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 !==
|
|
1041
|
+
if (typeof color !== "object") return 0;
|
|
980
1042
|
|
|
981
1043
|
// Validate and clamp RGB values to 0-255 range
|
|
982
|
-
const rVal =
|
|
983
|
-
|
|
984
|
-
const
|
|
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)
|
|
1050
|
-
|
|
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(
|
|
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 !==
|
|
1057
|
-
throw new Error(
|
|
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 =
|
|
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)
|
|
1144
|
-
|
|
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(
|
|
1147
|
-
if (!targetBBox)
|
|
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)
|
|
1249
|
-
|
|
1250
|
-
if (!
|
|
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 !==
|
|
1254
|
-
throw new Error(
|
|
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 !==
|
|
1257
|
-
throw new Error(
|
|
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 =
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
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)
|
|
1400
|
-
|
|
1401
|
-
if (
|
|
1402
|
-
|
|
1403
|
-
|
|
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)
|
|
1572
|
+
if (!meshData)
|
|
1573
|
+
throw new Error("getMeshGradientBoundary: meshData is required");
|
|
1469
1574
|
|
|
1470
1575
|
const { samples = 20 } = options;
|
|
1471
|
-
if (typeof samples !==
|
|
1472
|
-
throw new Error(
|
|
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 (
|
|
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)
|
|
1587
|
-
|
|
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)
|
|
1704
|
+
if (!meshData)
|
|
1705
|
+
throw new Error("clipWithMeshGradientShape: meshData is required");
|
|
1590
1706
|
|
|
1591
1707
|
const { subdivisions = 4 } = options;
|
|
1592
|
-
if (typeof subdivisions !==
|
|
1593
|
-
throw new Error(
|
|
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 (
|
|
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 (
|
|
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)
|
|
1801
|
+
if (!meshData)
|
|
1802
|
+
throw new Error("meshGradientToClipPath: meshData is required");
|
|
1676
1803
|
|
|
1677
1804
|
const { subdivisions = 4 } = options;
|
|
1678
|
-
if (typeof subdivisions !==
|
|
1679
|
-
throw new Error(
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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)));
|