@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/transforms3d.js
CHANGED
|
@@ -30,7 +30,11 @@ function validateNumeric(value, name) {
|
|
|
30
30
|
if (value === undefined || value === null) {
|
|
31
31
|
throw new Error(`${name} is required`);
|
|
32
32
|
}
|
|
33
|
-
if (
|
|
33
|
+
if (
|
|
34
|
+
typeof value !== "number" &&
|
|
35
|
+
typeof value !== "string" &&
|
|
36
|
+
!(value instanceof Decimal)
|
|
37
|
+
) {
|
|
34
38
|
throw new Error(`${name} must be a number, string, or Decimal`);
|
|
35
39
|
}
|
|
36
40
|
}
|
|
@@ -52,7 +56,8 @@ function normalizeAngle(theta) {
|
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
// For small angles, no normalization needed - preserve full precision
|
|
55
|
-
if (Math.abs(tNum) <= 6.283185307179586) {
|
|
59
|
+
if (Math.abs(tNum) <= 6.283185307179586) {
|
|
60
|
+
// 2π
|
|
56
61
|
return tNum;
|
|
57
62
|
}
|
|
58
63
|
|
|
@@ -148,9 +153,9 @@ function normalizeAngle(theta) {
|
|
|
148
153
|
* // First translates by (10,0,0), then rotates around Z
|
|
149
154
|
*/
|
|
150
155
|
export function translation(tx, ty, tz) {
|
|
151
|
-
validateNumeric(tx,
|
|
152
|
-
validateNumeric(ty,
|
|
153
|
-
validateNumeric(tz,
|
|
156
|
+
validateNumeric(tx, "tx");
|
|
157
|
+
validateNumeric(ty, "ty");
|
|
158
|
+
validateNumeric(tz, "tz");
|
|
154
159
|
return Matrix.from([
|
|
155
160
|
[new Decimal(1), new Decimal(0), new Decimal(0), D(tx)],
|
|
156
161
|
[new Decimal(0), new Decimal(1), new Decimal(0), D(ty)],
|
|
@@ -209,12 +214,12 @@ export function translation(tx, ty, tz) {
|
|
|
209
214
|
* // Cannot invert this matrix - information about z coordinate is lost
|
|
210
215
|
*/
|
|
211
216
|
export function scale(sx, sy = null, sz = null) {
|
|
212
|
-
validateNumeric(sx,
|
|
217
|
+
validateNumeric(sx, "sx");
|
|
213
218
|
if (sy !== null) {
|
|
214
|
-
validateNumeric(sy,
|
|
219
|
+
validateNumeric(sy, "sy");
|
|
215
220
|
}
|
|
216
221
|
if (sz !== null) {
|
|
217
|
-
validateNumeric(sz,
|
|
222
|
+
validateNumeric(sz, "sz");
|
|
218
223
|
}
|
|
219
224
|
const syValue = sy === null ? sx : sy;
|
|
220
225
|
const szValue = sz === null ? sx : sz;
|
|
@@ -270,7 +275,7 @@ export function scale(sx, sy = null, sz = null) {
|
|
|
270
275
|
* const pitch = rotateX(-0.1); // Slight downward tilt
|
|
271
276
|
*/
|
|
272
277
|
export function rotateX(theta) {
|
|
273
|
-
validateNumeric(theta,
|
|
278
|
+
validateNumeric(theta, "theta");
|
|
274
279
|
const t = D(theta);
|
|
275
280
|
// Normalize angle to reduce precision loss for very large angles
|
|
276
281
|
const tNum = normalizeAngle(t);
|
|
@@ -317,7 +322,7 @@ export function rotateX(theta) {
|
|
|
317
322
|
* const yaw = rotateY(0.5); // Turn right by ~28.6°
|
|
318
323
|
*/
|
|
319
324
|
export function rotateY(theta) {
|
|
320
|
-
validateNumeric(theta,
|
|
325
|
+
validateNumeric(theta, "theta");
|
|
321
326
|
const t = D(theta);
|
|
322
327
|
// Normalize angle to reduce precision loss for very large angles
|
|
323
328
|
const tNum = normalizeAngle(t);
|
|
@@ -365,7 +370,7 @@ export function rotateY(theta) {
|
|
|
365
370
|
* const roll = rotateZ(0.2); // Slight clockwise tilt from viewer perspective
|
|
366
371
|
*/
|
|
367
372
|
export function rotateZ(theta) {
|
|
368
|
-
validateNumeric(theta,
|
|
373
|
+
validateNumeric(theta, "theta");
|
|
369
374
|
const t = D(theta);
|
|
370
375
|
// Normalize angle to reduce precision loss for very large angles
|
|
371
376
|
const tNum = normalizeAngle(t);
|
|
@@ -434,10 +439,10 @@ export function rotateZ(theta) {
|
|
|
434
439
|
* // Equivalent to rotateX(Math.PI / 2)
|
|
435
440
|
*/
|
|
436
441
|
export function rotateAroundAxis(ux, uy, uz, theta) {
|
|
437
|
-
validateNumeric(ux,
|
|
438
|
-
validateNumeric(uy,
|
|
439
|
-
validateNumeric(uz,
|
|
440
|
-
validateNumeric(theta,
|
|
442
|
+
validateNumeric(ux, "ux");
|
|
443
|
+
validateNumeric(uy, "uy");
|
|
444
|
+
validateNumeric(uz, "uz");
|
|
445
|
+
validateNumeric(theta, "theta");
|
|
441
446
|
const u = [D(ux), D(uy), D(uz)];
|
|
442
447
|
const norm = u[0].mul(u[0]).plus(u[1].mul(u[1])).plus(u[2].mul(u[2])).sqrt();
|
|
443
448
|
if (norm.isZero()) {
|
|
@@ -520,13 +525,13 @@ export function rotateAroundAxis(ux, uy, uz, theta) {
|
|
|
520
525
|
* // Complex rotation around axis (1,1,1) passing through (10,20,30)
|
|
521
526
|
*/
|
|
522
527
|
export function rotateAroundPoint(ux, uy, uz, theta, px, py, pz) {
|
|
523
|
-
validateNumeric(ux,
|
|
524
|
-
validateNumeric(uy,
|
|
525
|
-
validateNumeric(uz,
|
|
526
|
-
validateNumeric(theta,
|
|
527
|
-
validateNumeric(px,
|
|
528
|
-
validateNumeric(py,
|
|
529
|
-
validateNumeric(pz,
|
|
528
|
+
validateNumeric(ux, "ux");
|
|
529
|
+
validateNumeric(uy, "uy");
|
|
530
|
+
validateNumeric(uz, "uz");
|
|
531
|
+
validateNumeric(theta, "theta");
|
|
532
|
+
validateNumeric(px, "px");
|
|
533
|
+
validateNumeric(py, "py");
|
|
534
|
+
validateNumeric(pz, "pz");
|
|
530
535
|
const pxD = D(px),
|
|
531
536
|
pyD = D(py),
|
|
532
537
|
pzD = D(pz);
|
|
@@ -582,14 +587,14 @@ export function rotateAroundPoint(ux, uy, uz, theta, px, py, pz) {
|
|
|
582
587
|
*/
|
|
583
588
|
export function applyTransform(M, x, y, z) {
|
|
584
589
|
if (!(M instanceof Matrix)) {
|
|
585
|
-
throw new Error(
|
|
590
|
+
throw new Error("M must be a Matrix instance");
|
|
586
591
|
}
|
|
587
592
|
if (M.rows !== 4 || M.cols !== 4) {
|
|
588
593
|
throw new Error(`M must be a 4x4 matrix, got ${M.rows}x${M.cols}`);
|
|
589
594
|
}
|
|
590
|
-
validateNumeric(x,
|
|
591
|
-
validateNumeric(y,
|
|
592
|
-
validateNumeric(z,
|
|
595
|
+
validateNumeric(x, "x");
|
|
596
|
+
validateNumeric(y, "y");
|
|
597
|
+
validateNumeric(z, "z");
|
|
593
598
|
|
|
594
599
|
const P = Matrix.from([[D(x)], [D(y)], [D(z)], [new Decimal(1)]]);
|
|
595
600
|
const R = M.mul(P);
|
|
@@ -599,7 +604,9 @@ export function applyTransform(M, x, y, z) {
|
|
|
599
604
|
rw = R.data[3][0];
|
|
600
605
|
|
|
601
606
|
if (rw.isZero()) {
|
|
602
|
-
throw new Error(
|
|
607
|
+
throw new Error(
|
|
608
|
+
"Perspective division by zero: transformation results in point at infinity",
|
|
609
|
+
);
|
|
603
610
|
}
|
|
604
611
|
|
|
605
612
|
return [rx.div(rw), ry.div(rw), rz.div(rw)];
|
|
@@ -36,16 +36,18 @@ Decimal.set({ precision: 80 });
|
|
|
36
36
|
*/
|
|
37
37
|
function hasCircularReference(startId, getNextId, maxDepth = 100) {
|
|
38
38
|
// Parameter validation: startId must be a non-empty string
|
|
39
|
-
if (!startId || typeof startId !==
|
|
40
|
-
throw new Error(
|
|
39
|
+
if (!startId || typeof startId !== "string") {
|
|
40
|
+
throw new Error("hasCircularReference: startId must be a non-empty string");
|
|
41
41
|
}
|
|
42
42
|
// Parameter validation: getNextId must be a function
|
|
43
|
-
if (typeof getNextId !==
|
|
44
|
-
throw new Error(
|
|
43
|
+
if (typeof getNextId !== "function") {
|
|
44
|
+
throw new Error("hasCircularReference: getNextId must be a function");
|
|
45
45
|
}
|
|
46
46
|
// Parameter validation: maxDepth must be a positive finite number
|
|
47
|
-
if (typeof maxDepth !==
|
|
48
|
-
throw new Error(
|
|
47
|
+
if (typeof maxDepth !== "number" || maxDepth <= 0 || !isFinite(maxDepth)) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
"hasCircularReference: maxDepth must be a positive finite number",
|
|
50
|
+
);
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
const visited = new Set();
|
|
@@ -100,7 +102,7 @@ function hasCircularReference(startId, getNextId, maxDepth = 100) {
|
|
|
100
102
|
*/
|
|
101
103
|
export function parseUseElement(useElement) {
|
|
102
104
|
// Parameter validation: useElement must be defined
|
|
103
|
-
if (!useElement) throw new Error(
|
|
105
|
+
if (!useElement) throw new Error("parseUseElement: useElement is required");
|
|
104
106
|
|
|
105
107
|
const href =
|
|
106
108
|
useElement.getAttribute("href") ||
|
|
@@ -111,7 +113,9 @@ export function parseUseElement(useElement) {
|
|
|
111
113
|
|
|
112
114
|
// Validate href is not empty after parsing
|
|
113
115
|
if (!parsedHref) {
|
|
114
|
-
throw new Error(
|
|
116
|
+
throw new Error(
|
|
117
|
+
"parseUseElement: href attribute must reference a valid element id (found empty reference)",
|
|
118
|
+
);
|
|
115
119
|
}
|
|
116
120
|
|
|
117
121
|
// Parse numeric attributes and validate for NaN and finiteness
|
|
@@ -120,7 +124,9 @@ export function parseUseElement(useElement) {
|
|
|
120
124
|
|
|
121
125
|
// Validate that x and y are valid finite numbers
|
|
122
126
|
if (isNaN(x) || isNaN(y) || !isFinite(x) || !isFinite(y)) {
|
|
123
|
-
throw new Error(
|
|
127
|
+
throw new Error(
|
|
128
|
+
"parseUseElement: x and y attributes must be valid finite numbers",
|
|
129
|
+
);
|
|
124
130
|
}
|
|
125
131
|
|
|
126
132
|
// Parse width and height if present, validate for NaN and finiteness and positivity
|
|
@@ -130,14 +136,18 @@ export function parseUseElement(useElement) {
|
|
|
130
136
|
if (useElement.getAttribute("width")) {
|
|
131
137
|
width = parseFloat(useElement.getAttribute("width"));
|
|
132
138
|
if (isNaN(width) || !isFinite(width) || width <= 0) {
|
|
133
|
-
throw new Error(
|
|
139
|
+
throw new Error(
|
|
140
|
+
"parseUseElement: width attribute must be a valid positive finite number",
|
|
141
|
+
);
|
|
134
142
|
}
|
|
135
143
|
}
|
|
136
144
|
|
|
137
145
|
if (useElement.getAttribute("height")) {
|
|
138
146
|
height = parseFloat(useElement.getAttribute("height"));
|
|
139
147
|
if (isNaN(height) || !isFinite(height) || height <= 0) {
|
|
140
|
-
throw new Error(
|
|
148
|
+
throw new Error(
|
|
149
|
+
"parseUseElement: height attribute must be a valid positive finite number",
|
|
150
|
+
);
|
|
141
151
|
}
|
|
142
152
|
}
|
|
143
153
|
|
|
@@ -193,14 +203,17 @@ export function parseUseElement(useElement) {
|
|
|
193
203
|
*/
|
|
194
204
|
export function parseSymbolElement(symbolElement) {
|
|
195
205
|
// Parameter validation: symbolElement must be defined
|
|
196
|
-
if (!symbolElement)
|
|
206
|
+
if (!symbolElement)
|
|
207
|
+
throw new Error("parseSymbolElement: symbolElement is required");
|
|
197
208
|
|
|
198
209
|
// Parse refX and refY with NaN and finiteness validation
|
|
199
210
|
const refX = parseFloat(symbolElement.getAttribute("refX") || "0");
|
|
200
211
|
const refY = parseFloat(symbolElement.getAttribute("refY") || "0");
|
|
201
212
|
|
|
202
213
|
if (isNaN(refX) || isNaN(refY) || !isFinite(refX) || !isFinite(refY)) {
|
|
203
|
-
throw new Error(
|
|
214
|
+
throw new Error(
|
|
215
|
+
"parseSymbolElement: refX and refY must be valid finite numbers",
|
|
216
|
+
);
|
|
204
217
|
}
|
|
205
218
|
|
|
206
219
|
const data = {
|
|
@@ -302,7 +315,7 @@ export function parseSymbolElement(symbolElement) {
|
|
|
302
315
|
export function parseChildElement(element) {
|
|
303
316
|
// Parameter validation: element must be defined and have a tagName
|
|
304
317
|
if (!element || !element.tagName) {
|
|
305
|
-
throw new Error(
|
|
318
|
+
throw new Error("parseChildElement: element with tagName is required");
|
|
306
319
|
}
|
|
307
320
|
|
|
308
321
|
const tagName = element.tagName.toLowerCase();
|
|
@@ -318,7 +331,9 @@ export function parseChildElement(element) {
|
|
|
318
331
|
const safeParseFloat = (attrName, defaultValue = "0") => {
|
|
319
332
|
const value = parseFloat(element.getAttribute(attrName) || defaultValue);
|
|
320
333
|
if (isNaN(value) || !isFinite(value)) {
|
|
321
|
-
throw new Error(
|
|
334
|
+
throw new Error(
|
|
335
|
+
`parseChildElement: ${attrName} must be a valid finite number in ${tagName} element`,
|
|
336
|
+
);
|
|
322
337
|
}
|
|
323
338
|
return value;
|
|
324
339
|
};
|
|
@@ -428,7 +443,7 @@ export function parseChildElement(element) {
|
|
|
428
443
|
export function extractStyleAttributes(element) {
|
|
429
444
|
// Parameter validation: element must be defined
|
|
430
445
|
if (!element) {
|
|
431
|
-
throw new Error(
|
|
446
|
+
throw new Error("extractStyleAttributes: element is required");
|
|
432
447
|
}
|
|
433
448
|
|
|
434
449
|
return {
|
|
@@ -502,7 +517,12 @@ export function calculateViewBoxTransform(
|
|
|
502
517
|
}
|
|
503
518
|
|
|
504
519
|
// Validate targetWidth and targetHeight are finite positive numbers
|
|
505
|
-
if (
|
|
520
|
+
if (
|
|
521
|
+
!isFinite(targetWidth) ||
|
|
522
|
+
!isFinite(targetHeight) ||
|
|
523
|
+
targetWidth <= 0 ||
|
|
524
|
+
targetHeight <= 0
|
|
525
|
+
) {
|
|
506
526
|
return Matrix.identity(3);
|
|
507
527
|
}
|
|
508
528
|
|
|
@@ -512,7 +532,14 @@ export function calculateViewBoxTransform(
|
|
|
512
532
|
const vbY = viewBox.y;
|
|
513
533
|
|
|
514
534
|
// Validate viewBox dimensions are finite and positive
|
|
515
|
-
if (
|
|
535
|
+
if (
|
|
536
|
+
!isFinite(vbW) ||
|
|
537
|
+
!isFinite(vbH) ||
|
|
538
|
+
!isFinite(vbX) ||
|
|
539
|
+
!isFinite(vbY) ||
|
|
540
|
+
vbW <= 0 ||
|
|
541
|
+
vbH <= 0
|
|
542
|
+
) {
|
|
516
543
|
return Matrix.identity(3);
|
|
517
544
|
}
|
|
518
545
|
|
|
@@ -639,63 +666,99 @@ export function calculateViewBoxTransform(
|
|
|
639
666
|
export function resolveUse(useData, defs, options = {}) {
|
|
640
667
|
// Parameter validation: useData must be defined with href property
|
|
641
668
|
if (!useData || !useData.href) {
|
|
642
|
-
throw new Error(
|
|
669
|
+
throw new Error("resolveUse: useData with href property is required");
|
|
643
670
|
}
|
|
644
671
|
|
|
645
672
|
// Parameter validation: defs must be defined
|
|
646
673
|
if (!defs) {
|
|
647
|
-
throw new Error(
|
|
674
|
+
throw new Error("resolveUse: defs map is required");
|
|
648
675
|
}
|
|
649
676
|
|
|
650
677
|
// Validate useData.x and useData.y are valid numbers
|
|
651
|
-
if (
|
|
652
|
-
|
|
678
|
+
if (
|
|
679
|
+
typeof useData.x !== "number" ||
|
|
680
|
+
isNaN(useData.x) ||
|
|
681
|
+
!isFinite(useData.x)
|
|
682
|
+
) {
|
|
683
|
+
throw new Error("resolveUse: useData.x must be a valid finite number");
|
|
653
684
|
}
|
|
654
|
-
if (
|
|
655
|
-
|
|
685
|
+
if (
|
|
686
|
+
typeof useData.y !== "number" ||
|
|
687
|
+
isNaN(useData.y) ||
|
|
688
|
+
!isFinite(useData.y)
|
|
689
|
+
) {
|
|
690
|
+
throw new Error("resolveUse: useData.y must be a valid finite number");
|
|
656
691
|
}
|
|
657
692
|
|
|
658
693
|
// Validate useData.width and useData.height are null or valid numbers
|
|
659
|
-
if (
|
|
660
|
-
|
|
694
|
+
if (
|
|
695
|
+
useData.width !== null &&
|
|
696
|
+
(typeof useData.width !== "number" ||
|
|
697
|
+
isNaN(useData.width) ||
|
|
698
|
+
!isFinite(useData.width) ||
|
|
699
|
+
useData.width <= 0)
|
|
700
|
+
) {
|
|
701
|
+
throw new Error(
|
|
702
|
+
"resolveUse: useData.width must be null or a positive finite number",
|
|
703
|
+
);
|
|
661
704
|
}
|
|
662
|
-
if (
|
|
663
|
-
|
|
705
|
+
if (
|
|
706
|
+
useData.height !== null &&
|
|
707
|
+
(typeof useData.height !== "number" ||
|
|
708
|
+
isNaN(useData.height) ||
|
|
709
|
+
!isFinite(useData.height) ||
|
|
710
|
+
useData.height <= 0)
|
|
711
|
+
) {
|
|
712
|
+
throw new Error(
|
|
713
|
+
"resolveUse: useData.height must be null or a positive finite number",
|
|
714
|
+
);
|
|
664
715
|
}
|
|
665
716
|
|
|
666
717
|
// Validate useData.style is an object or null/undefined
|
|
667
718
|
if (useData.style !== null && useData.style !== undefined) {
|
|
668
|
-
if (typeof useData.style !==
|
|
669
|
-
throw new Error(
|
|
719
|
+
if (typeof useData.style !== "object" || Array.isArray(useData.style)) {
|
|
720
|
+
throw new Error(
|
|
721
|
+
"resolveUse: useData.style must be null, undefined, or a valid non-array object",
|
|
722
|
+
);
|
|
670
723
|
}
|
|
671
724
|
}
|
|
672
725
|
|
|
673
726
|
// Validate useData.transform is a string or null/undefined
|
|
674
|
-
if (
|
|
675
|
-
|
|
727
|
+
if (
|
|
728
|
+
useData.transform !== null &&
|
|
729
|
+
useData.transform !== undefined &&
|
|
730
|
+
typeof useData.transform !== "string"
|
|
731
|
+
) {
|
|
732
|
+
throw new Error(
|
|
733
|
+
"resolveUse: useData.transform must be null, undefined, or a string",
|
|
734
|
+
);
|
|
676
735
|
}
|
|
677
736
|
|
|
678
737
|
// Validate options parameter
|
|
679
|
-
if (options && typeof options !==
|
|
680
|
-
throw new Error(
|
|
738
|
+
if (options && typeof options !== "object") {
|
|
739
|
+
throw new Error("resolveUse: options must be an object or undefined");
|
|
681
740
|
}
|
|
682
741
|
|
|
683
742
|
const { maxDepth = 10, _visited = new Set() } = options;
|
|
684
743
|
|
|
685
744
|
// Validate maxDepth is a positive finite number
|
|
686
|
-
if (typeof maxDepth !==
|
|
687
|
-
throw new Error(
|
|
745
|
+
if (typeof maxDepth !== "number" || maxDepth <= 0 || !isFinite(maxDepth)) {
|
|
746
|
+
throw new Error("resolveUse: maxDepth must be a positive finite number");
|
|
688
747
|
}
|
|
689
748
|
|
|
690
749
|
// Detect circular references by tracking visited IDs
|
|
691
750
|
if (_visited.has(useData.href)) {
|
|
692
|
-
console.warn(
|
|
751
|
+
console.warn(
|
|
752
|
+
`resolveUse: circular reference detected for '#${useData.href}', skipping to prevent infinite loop`,
|
|
753
|
+
);
|
|
693
754
|
return null;
|
|
694
755
|
}
|
|
695
756
|
|
|
696
757
|
const target = defs[useData.href];
|
|
697
758
|
if (!target) {
|
|
698
|
-
console.warn(
|
|
759
|
+
console.warn(
|
|
760
|
+
`resolveUse: target element '#${useData.href}' not found in defs map`,
|
|
761
|
+
);
|
|
699
762
|
return null; // Target element not found
|
|
700
763
|
}
|
|
701
764
|
|
|
@@ -726,17 +789,28 @@ export function resolveUse(useData, defs, options = {}) {
|
|
|
726
789
|
if (target.type === "symbol" && target.viewBoxParsed) {
|
|
727
790
|
// Validate viewBoxParsed has all required properties with finite values (x, y, width, height)
|
|
728
791
|
// and width/height must be positive
|
|
729
|
-
if (
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
792
|
+
if (
|
|
793
|
+
typeof target.viewBoxParsed.x !== "number" ||
|
|
794
|
+
typeof target.viewBoxParsed.y !== "number" ||
|
|
795
|
+
typeof target.viewBoxParsed.width !== "number" ||
|
|
796
|
+
typeof target.viewBoxParsed.height !== "number" ||
|
|
797
|
+
!isFinite(target.viewBoxParsed.x) ||
|
|
798
|
+
!isFinite(target.viewBoxParsed.y) ||
|
|
799
|
+
!isFinite(target.viewBoxParsed.width) ||
|
|
800
|
+
!isFinite(target.viewBoxParsed.height) ||
|
|
801
|
+
target.viewBoxParsed.width <= 0 ||
|
|
802
|
+
target.viewBoxParsed.height <= 0
|
|
803
|
+
) {
|
|
804
|
+
throw new Error(
|
|
805
|
+
"resolveUse: target.viewBoxParsed must have finite x, y and positive finite width, height",
|
|
806
|
+
);
|
|
735
807
|
}
|
|
736
808
|
|
|
737
809
|
// Use explicit null check to allow width/height of 0 (though unusual)
|
|
738
|
-
const width =
|
|
739
|
-
|
|
810
|
+
const width =
|
|
811
|
+
useData.width !== null ? useData.width : target.viewBoxParsed.width;
|
|
812
|
+
const height =
|
|
813
|
+
useData.height !== null ? useData.height : target.viewBoxParsed.height;
|
|
740
814
|
|
|
741
815
|
const viewBoxTransform = calculateViewBoxTransform(
|
|
742
816
|
target.viewBoxParsed,
|
|
@@ -753,19 +827,27 @@ export function resolveUse(useData, defs, options = {}) {
|
|
|
753
827
|
const resolvedChildren = [];
|
|
754
828
|
// For symbols and groups, use children array; for leaf elements, wrap as single child
|
|
755
829
|
// Check for children property existence and if it's an array, not just truthiness
|
|
756
|
-
const children =
|
|
757
|
-
|
|
758
|
-
|
|
830
|
+
const children =
|
|
831
|
+
target.children &&
|
|
832
|
+
Array.isArray(target.children) &&
|
|
833
|
+
target.children.length > 0
|
|
834
|
+
? target.children
|
|
835
|
+
: [target];
|
|
759
836
|
|
|
760
837
|
for (const child of children) {
|
|
761
838
|
if (child.type === "use") {
|
|
762
839
|
// Check maxDepth before recursing to prevent infinite recursion
|
|
763
840
|
if (maxDepth <= 1) {
|
|
764
|
-
console.warn(
|
|
841
|
+
console.warn(
|
|
842
|
+
`resolveUse: maximum nesting depth reached for use element '#${child.href}', skipping nested resolution`,
|
|
843
|
+
);
|
|
765
844
|
continue;
|
|
766
845
|
}
|
|
767
846
|
// Recursive resolution with passed _visited set to track circular references across the chain
|
|
768
|
-
const resolved = resolveUse(child, defs, {
|
|
847
|
+
const resolved = resolveUse(child, defs, {
|
|
848
|
+
maxDepth: maxDepth - 1,
|
|
849
|
+
_visited,
|
|
850
|
+
});
|
|
769
851
|
if (resolved) {
|
|
770
852
|
resolvedChildren.push(resolved);
|
|
771
853
|
}
|
|
@@ -848,8 +930,10 @@ export function flattenResolvedUse(resolved, samples = 20) {
|
|
|
848
930
|
if (!resolved) return results;
|
|
849
931
|
|
|
850
932
|
// Parameter validation: samples must be a positive number
|
|
851
|
-
if (typeof samples !==
|
|
852
|
-
throw new Error(
|
|
933
|
+
if (typeof samples !== "number" || samples <= 0 || !isFinite(samples)) {
|
|
934
|
+
throw new Error(
|
|
935
|
+
"flattenResolvedUse: samples must be a positive finite number",
|
|
936
|
+
);
|
|
853
937
|
}
|
|
854
938
|
|
|
855
939
|
// Validate required properties exist
|
|
@@ -932,12 +1016,14 @@ export function flattenResolvedUse(resolved, samples = 20) {
|
|
|
932
1016
|
export function elementToPolygon(element, transform, samples = 20) {
|
|
933
1017
|
// Parameter validation: element must be defined
|
|
934
1018
|
if (!element) {
|
|
935
|
-
throw new Error(
|
|
1019
|
+
throw new Error("elementToPolygon: element is required");
|
|
936
1020
|
}
|
|
937
1021
|
|
|
938
1022
|
// Parameter validation: samples must be a positive finite number
|
|
939
|
-
if (typeof samples !==
|
|
940
|
-
throw new Error(
|
|
1023
|
+
if (typeof samples !== "number" || samples <= 0 || !isFinite(samples)) {
|
|
1024
|
+
throw new Error(
|
|
1025
|
+
"elementToPolygon: samples must be a positive finite number",
|
|
1026
|
+
);
|
|
941
1027
|
}
|
|
942
1028
|
|
|
943
1029
|
// Use ClipPathResolver's shapeToPolygon
|
|
@@ -1000,8 +1086,8 @@ export function elementToPolygon(element, transform, samples = 20) {
|
|
|
1000
1086
|
*/
|
|
1001
1087
|
export function mergeStyles(inherited, element) {
|
|
1002
1088
|
// Parameter validation: element must be defined (inherited can be null)
|
|
1003
|
-
if (!element || typeof element !==
|
|
1004
|
-
throw new Error(
|
|
1089
|
+
if (!element || typeof element !== "object" || Array.isArray(element)) {
|
|
1090
|
+
throw new Error("mergeStyles: element must be a valid non-array object");
|
|
1005
1091
|
}
|
|
1006
1092
|
|
|
1007
1093
|
const result = { ...element };
|
|
@@ -1009,8 +1095,10 @@ export function mergeStyles(inherited, element) {
|
|
|
1009
1095
|
// Inherited can be null/undefined, handle gracefully
|
|
1010
1096
|
// Also validate inherited is an object if not null/undefined
|
|
1011
1097
|
if (inherited !== null && inherited !== undefined) {
|
|
1012
|
-
if (typeof inherited !==
|
|
1013
|
-
throw new Error(
|
|
1098
|
+
if (typeof inherited !== "object" || Array.isArray(inherited)) {
|
|
1099
|
+
throw new Error(
|
|
1100
|
+
"mergeStyles: inherited must be null, undefined, or a valid non-array object",
|
|
1101
|
+
);
|
|
1014
1102
|
}
|
|
1015
1103
|
}
|
|
1016
1104
|
|
|
@@ -1069,8 +1157,10 @@ export function mergeStyles(inherited, element) {
|
|
|
1069
1157
|
*/
|
|
1070
1158
|
export function getResolvedBBox(resolved, samples = 20) {
|
|
1071
1159
|
// Parameter validation: samples must be a positive finite number
|
|
1072
|
-
if (typeof samples !==
|
|
1073
|
-
throw new Error(
|
|
1160
|
+
if (typeof samples !== "number" || samples <= 0 || !isFinite(samples)) {
|
|
1161
|
+
throw new Error(
|
|
1162
|
+
"getResolvedBBox: samples must be a positive finite number",
|
|
1163
|
+
);
|
|
1074
1164
|
}
|
|
1075
1165
|
|
|
1076
1166
|
const polygons = flattenResolvedUse(resolved, samples);
|
|
@@ -1160,17 +1250,21 @@ export function getResolvedBBox(resolved, samples = 20) {
|
|
|
1160
1250
|
export function clipResolvedUse(resolved, clipPolygon, samples = 20) {
|
|
1161
1251
|
// Parameter validation: clipPolygon must be defined and be an array
|
|
1162
1252
|
if (!clipPolygon || !Array.isArray(clipPolygon)) {
|
|
1163
|
-
throw new Error(
|
|
1253
|
+
throw new Error("clipResolvedUse: clipPolygon must be a valid array");
|
|
1164
1254
|
}
|
|
1165
1255
|
|
|
1166
1256
|
// Validate clipPolygon has at least 3 points
|
|
1167
1257
|
if (clipPolygon.length < 3) {
|
|
1168
|
-
throw new Error(
|
|
1258
|
+
throw new Error(
|
|
1259
|
+
"clipResolvedUse: clipPolygon must have at least 3 vertices",
|
|
1260
|
+
);
|
|
1169
1261
|
}
|
|
1170
1262
|
|
|
1171
1263
|
// Parameter validation: samples must be a positive finite number
|
|
1172
|
-
if (typeof samples !==
|
|
1173
|
-
throw new Error(
|
|
1264
|
+
if (typeof samples !== "number" || samples <= 0 || !isFinite(samples)) {
|
|
1265
|
+
throw new Error(
|
|
1266
|
+
"clipResolvedUse: samples must be a positive finite number",
|
|
1267
|
+
);
|
|
1174
1268
|
}
|
|
1175
1269
|
|
|
1176
1270
|
const polygons = flattenResolvedUse(resolved, samples);
|
|
@@ -1243,8 +1337,10 @@ export function clipResolvedUse(resolved, clipPolygon, samples = 20) {
|
|
|
1243
1337
|
*/
|
|
1244
1338
|
export function resolvedUseToPathData(resolved, samples = 20) {
|
|
1245
1339
|
// Parameter validation: samples must be a positive finite number
|
|
1246
|
-
if (typeof samples !==
|
|
1247
|
-
throw new Error(
|
|
1340
|
+
if (typeof samples !== "number" || samples <= 0 || !isFinite(samples)) {
|
|
1341
|
+
throw new Error(
|
|
1342
|
+
"resolvedUseToPathData: samples must be a positive finite number",
|
|
1343
|
+
);
|
|
1248
1344
|
}
|
|
1249
1345
|
|
|
1250
1346
|
const polygons = flattenResolvedUse(resolved, samples);
|
|
@@ -1263,7 +1359,10 @@ export function resolvedUseToPathData(resolved, samples = 20) {
|
|
|
1263
1359
|
continue;
|
|
1264
1360
|
}
|
|
1265
1361
|
|
|
1266
|
-
d +=
|
|
1362
|
+
d +=
|
|
1363
|
+
i === 0
|
|
1364
|
+
? `M ${x.toFixed(6)} ${y.toFixed(6)}`
|
|
1365
|
+
: ` L ${x.toFixed(6)} ${y.toFixed(6)}`;
|
|
1267
1366
|
}
|
|
1268
1367
|
d += " Z";
|
|
1269
1368
|
paths.push(d);
|
|
@@ -1326,8 +1425,8 @@ export function resolvedUseToPathData(resolved, samples = 20) {
|
|
|
1326
1425
|
*/
|
|
1327
1426
|
export function buildDefsMap(svgRoot) {
|
|
1328
1427
|
// Parameter validation: svgRoot must be defined and have querySelectorAll method
|
|
1329
|
-
if (!svgRoot || typeof svgRoot.querySelectorAll !==
|
|
1330
|
-
throw new Error(
|
|
1428
|
+
if (!svgRoot || typeof svgRoot.querySelectorAll !== "function") {
|
|
1429
|
+
throw new Error("buildDefsMap: svgRoot must be a valid DOM element");
|
|
1331
1430
|
}
|
|
1332
1431
|
|
|
1333
1432
|
const defs = {};
|
|
@@ -1427,13 +1526,13 @@ export function buildDefsMap(svgRoot) {
|
|
|
1427
1526
|
*/
|
|
1428
1527
|
export function resolveAllUses(svgRoot, options = {}) {
|
|
1429
1528
|
// Parameter validation: svgRoot must be defined and have querySelectorAll method
|
|
1430
|
-
if (!svgRoot || typeof svgRoot.querySelectorAll !==
|
|
1431
|
-
throw new Error(
|
|
1529
|
+
if (!svgRoot || typeof svgRoot.querySelectorAll !== "function") {
|
|
1530
|
+
throw new Error("resolveAllUses: svgRoot must be a valid DOM element");
|
|
1432
1531
|
}
|
|
1433
1532
|
|
|
1434
1533
|
// Validate options parameter
|
|
1435
|
-
if (options && typeof options !==
|
|
1436
|
-
throw new Error(
|
|
1534
|
+
if (options && typeof options !== "object") {
|
|
1535
|
+
throw new Error("resolveAllUses: options must be an object or undefined");
|
|
1437
1536
|
}
|
|
1438
1537
|
|
|
1439
1538
|
const defs = buildDefsMap(svgRoot);
|
|
@@ -1443,7 +1542,7 @@ export function resolveAllUses(svgRoot, options = {}) {
|
|
|
1443
1542
|
// Helper to get the next use reference from a definition
|
|
1444
1543
|
const getUseRef = (id) => {
|
|
1445
1544
|
// Validate id parameter
|
|
1446
|
-
if (!id || typeof id !==
|
|
1545
|
+
if (!id || typeof id !== "string") {
|
|
1447
1546
|
return null;
|
|
1448
1547
|
}
|
|
1449
1548
|
|