@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/marker-resolver.js
CHANGED
|
@@ -69,26 +69,37 @@ Decimal.set({ precision: 80 });
|
|
|
69
69
|
*/
|
|
70
70
|
export function parseMarkerElement(markerElement) {
|
|
71
71
|
// Validate required parameter
|
|
72
|
-
if (!markerElement)
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
if (!markerElement)
|
|
73
|
+
throw new Error("parseMarkerElement: markerElement is required");
|
|
74
|
+
if (typeof markerElement.getAttribute !== "function") {
|
|
75
|
+
throw new Error("parseMarkerElement: markerElement must be a DOM element");
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
// Parse numeric attributes with validation - NaN check ensures valid numbers
|
|
78
|
-
const markerWidth = parseFloat(
|
|
79
|
-
|
|
79
|
+
const markerWidth = parseFloat(
|
|
80
|
+
markerElement.getAttribute("markerWidth") || "3",
|
|
81
|
+
);
|
|
82
|
+
const markerHeight = parseFloat(
|
|
83
|
+
markerElement.getAttribute("markerHeight") || "3",
|
|
84
|
+
);
|
|
80
85
|
const refX = parseFloat(markerElement.getAttribute("refX") || "0");
|
|
81
86
|
const refY = parseFloat(markerElement.getAttribute("refY") || "0");
|
|
82
87
|
|
|
83
88
|
// Validate numeric values to prevent NaN propagation
|
|
84
89
|
if (isNaN(markerWidth) || markerWidth <= 0) {
|
|
85
|
-
throw new Error(
|
|
90
|
+
throw new Error(
|
|
91
|
+
"parseMarkerElement: markerWidth must be a positive number",
|
|
92
|
+
);
|
|
86
93
|
}
|
|
87
94
|
if (isNaN(markerHeight) || markerHeight <= 0) {
|
|
88
|
-
throw new Error(
|
|
95
|
+
throw new Error(
|
|
96
|
+
"parseMarkerElement: markerHeight must be a positive number",
|
|
97
|
+
);
|
|
89
98
|
}
|
|
90
|
-
if (isNaN(refX))
|
|
91
|
-
|
|
99
|
+
if (isNaN(refX))
|
|
100
|
+
throw new Error("parseMarkerElement: refX must be a valid number");
|
|
101
|
+
if (isNaN(refY))
|
|
102
|
+
throw new Error("parseMarkerElement: refY must be a valid number");
|
|
92
103
|
|
|
93
104
|
const data = {
|
|
94
105
|
id: markerElement.getAttribute("id") || "",
|
|
@@ -112,7 +123,7 @@ export function parseMarkerElement(markerElement) {
|
|
|
112
123
|
.split(/[\s,]+/)
|
|
113
124
|
.map(Number);
|
|
114
125
|
// Validate viewBox has exactly 4 valid finite numbers
|
|
115
|
-
if (parts.length === 4 && parts.every(n => !isNaN(n) && isFinite(n))) {
|
|
126
|
+
if (parts.length === 4 && parts.every((n) => !isNaN(n) && isFinite(n))) {
|
|
116
127
|
data.viewBox = {
|
|
117
128
|
x: parts[0],
|
|
118
129
|
y: parts[1],
|
|
@@ -158,8 +169,9 @@ export function parseMarkerElement(markerElement) {
|
|
|
158
169
|
*/
|
|
159
170
|
export function parseMarkerChild(element) {
|
|
160
171
|
// Validate required parameter
|
|
161
|
-
if (!element) throw new Error(
|
|
162
|
-
if (!element.tagName)
|
|
172
|
+
if (!element) throw new Error("parseMarkerChild: element is required");
|
|
173
|
+
if (!element.tagName)
|
|
174
|
+
throw new Error("parseMarkerChild: element must have a tagName property");
|
|
163
175
|
|
|
164
176
|
const tagName = element.tagName.toLowerCase();
|
|
165
177
|
const data = {
|
|
@@ -176,7 +188,9 @@ export function parseMarkerChild(element) {
|
|
|
176
188
|
const parseValidFloat = (value, defaultVal, name) => {
|
|
177
189
|
const parsed = parseFloat(value || String(defaultVal));
|
|
178
190
|
if (isNaN(parsed) || !isFinite(parsed)) {
|
|
179
|
-
throw new Error(
|
|
191
|
+
throw new Error(
|
|
192
|
+
`parseMarkerChild: ${name} must be a valid finite number`,
|
|
193
|
+
);
|
|
180
194
|
}
|
|
181
195
|
return parsed;
|
|
182
196
|
};
|
|
@@ -190,7 +204,11 @@ export function parseMarkerChild(element) {
|
|
|
190
204
|
data.x = parseValidFloat(element.getAttribute("x"), 0, "x");
|
|
191
205
|
data.y = parseValidFloat(element.getAttribute("y"), 0, "y");
|
|
192
206
|
data.width = parseValidFloat(element.getAttribute("width"), 0, "width");
|
|
193
|
-
data.height = parseValidFloat(
|
|
207
|
+
data.height = parseValidFloat(
|
|
208
|
+
element.getAttribute("height"),
|
|
209
|
+
0,
|
|
210
|
+
"height",
|
|
211
|
+
);
|
|
194
212
|
break;
|
|
195
213
|
case "circle":
|
|
196
214
|
// Validate all circle attributes are valid numbers
|
|
@@ -265,15 +283,33 @@ export function getMarkerTransform(
|
|
|
265
283
|
isStart = false,
|
|
266
284
|
) {
|
|
267
285
|
// Validate required parameters
|
|
268
|
-
if (!markerDef) throw new Error(
|
|
269
|
-
if (
|
|
270
|
-
|
|
286
|
+
if (!markerDef) throw new Error("getMarkerTransform: markerDef is required");
|
|
287
|
+
if (
|
|
288
|
+
!position ||
|
|
289
|
+
typeof position.x !== "number" ||
|
|
290
|
+
typeof position.y !== "number"
|
|
291
|
+
) {
|
|
292
|
+
throw new Error(
|
|
293
|
+
"getMarkerTransform: position must be an object with x and y numeric properties",
|
|
294
|
+
);
|
|
271
295
|
}
|
|
272
|
-
if (
|
|
273
|
-
|
|
296
|
+
if (
|
|
297
|
+
typeof tangentAngle !== "number" ||
|
|
298
|
+
isNaN(tangentAngle) ||
|
|
299
|
+
!isFinite(tangentAngle)
|
|
300
|
+
) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
"getMarkerTransform: tangentAngle must be a valid finite number",
|
|
303
|
+
);
|
|
274
304
|
}
|
|
275
|
-
if (
|
|
276
|
-
|
|
305
|
+
if (
|
|
306
|
+
typeof strokeWidth !== "number" ||
|
|
307
|
+
isNaN(strokeWidth) ||
|
|
308
|
+
strokeWidth <= 0
|
|
309
|
+
) {
|
|
310
|
+
throw new Error(
|
|
311
|
+
"getMarkerTransform: strokeWidth must be a positive number",
|
|
312
|
+
);
|
|
277
313
|
}
|
|
278
314
|
|
|
279
315
|
// Extract markerDef properties with validation
|
|
@@ -288,17 +324,25 @@ export function getMarkerTransform(
|
|
|
288
324
|
} = markerDef;
|
|
289
325
|
|
|
290
326
|
// Validate markerDef has required numeric properties
|
|
291
|
-
if (typeof markerWidth !==
|
|
292
|
-
throw new Error(
|
|
327
|
+
if (typeof markerWidth !== "number" || markerWidth <= 0) {
|
|
328
|
+
throw new Error(
|
|
329
|
+
"getMarkerTransform: markerDef.markerWidth must be a positive number",
|
|
330
|
+
);
|
|
293
331
|
}
|
|
294
|
-
if (typeof markerHeight !==
|
|
295
|
-
throw new Error(
|
|
332
|
+
if (typeof markerHeight !== "number" || markerHeight <= 0) {
|
|
333
|
+
throw new Error(
|
|
334
|
+
"getMarkerTransform: markerDef.markerHeight must be a positive number",
|
|
335
|
+
);
|
|
296
336
|
}
|
|
297
|
-
if (typeof refX !==
|
|
298
|
-
throw new Error(
|
|
337
|
+
if (typeof refX !== "number" || !isFinite(refX)) {
|
|
338
|
+
throw new Error(
|
|
339
|
+
"getMarkerTransform: markerDef.refX must be a finite number",
|
|
340
|
+
);
|
|
299
341
|
}
|
|
300
|
-
if (typeof refY !==
|
|
301
|
-
throw new Error(
|
|
342
|
+
if (typeof refY !== "number" || !isFinite(refY)) {
|
|
343
|
+
throw new Error(
|
|
344
|
+
"getMarkerTransform: markerDef.refY must be a finite number",
|
|
345
|
+
);
|
|
302
346
|
}
|
|
303
347
|
|
|
304
348
|
// Start with identity matrix
|
|
@@ -340,7 +384,12 @@ export function getMarkerTransform(
|
|
|
340
384
|
const vbHeight = viewBox.height;
|
|
341
385
|
|
|
342
386
|
// Prevent division by zero
|
|
343
|
-
if (
|
|
387
|
+
if (
|
|
388
|
+
vbWidth > 0 &&
|
|
389
|
+
vbHeight > 0 &&
|
|
390
|
+
isFinite(vbWidth) &&
|
|
391
|
+
isFinite(vbHeight)
|
|
392
|
+
) {
|
|
344
393
|
// Calculate uniform scale factor based on preserveAspectRatio
|
|
345
394
|
const scaleFactorX = markerWidth / vbWidth;
|
|
346
395
|
const scaleFactorY = markerHeight / vbHeight;
|
|
@@ -354,7 +403,10 @@ export function getMarkerTransform(
|
|
|
354
403
|
scaleY *= scaleFactor;
|
|
355
404
|
|
|
356
405
|
// Translate to account for viewBox origin
|
|
357
|
-
const viewBoxTranslate = Transforms2D.translation(
|
|
406
|
+
const viewBoxTranslate = Transforms2D.translation(
|
|
407
|
+
-viewBox.x,
|
|
408
|
+
-viewBox.y,
|
|
409
|
+
);
|
|
358
410
|
transform = transform.mul(viewBoxTranslate);
|
|
359
411
|
}
|
|
360
412
|
}
|
|
@@ -413,8 +465,8 @@ export function getMarkerTransform(
|
|
|
413
465
|
*/
|
|
414
466
|
export function getPathVertices(pathData) {
|
|
415
467
|
// Validate pathData parameter
|
|
416
|
-
if (typeof pathData !==
|
|
417
|
-
throw new Error(
|
|
468
|
+
if (typeof pathData !== "string") {
|
|
469
|
+
throw new Error("getPathVertices: pathData must be a string");
|
|
418
470
|
}
|
|
419
471
|
|
|
420
472
|
const vertices = [];
|
|
@@ -463,9 +515,12 @@ export function getPathVertices(pathData) {
|
|
|
463
515
|
// Calculate tangent angle (handle zero-length segments)
|
|
464
516
|
const dx = currentX - prevX;
|
|
465
517
|
const dy = currentY - prevY;
|
|
466
|
-
const lineAngle =
|
|
467
|
-
|
|
468
|
-
|
|
518
|
+
const lineAngle =
|
|
519
|
+
dx === 0 && dy === 0
|
|
520
|
+
? vertices.length > 0
|
|
521
|
+
? vertices[vertices.length - 1].tangentOut
|
|
522
|
+
: 0
|
|
523
|
+
: Math.atan2(dy, dx);
|
|
469
524
|
|
|
470
525
|
// Update previous vertex's outgoing tangent
|
|
471
526
|
if (vertices.length > 0) {
|
|
@@ -492,17 +547,19 @@ export function getPathVertices(pathData) {
|
|
|
492
547
|
// Handle degenerate case: if first control point coincides with start
|
|
493
548
|
const dx1 = cmd.x1 - prevX;
|
|
494
549
|
const dy1 = cmd.y1 - prevY;
|
|
495
|
-
const startTangent =
|
|
496
|
-
|
|
497
|
-
|
|
550
|
+
const startTangent =
|
|
551
|
+
dx1 === 0 && dy1 === 0
|
|
552
|
+
? Math.atan2(cmd.y2 - prevY, cmd.x2 - prevX) // Use second control point
|
|
553
|
+
: Math.atan2(dy1, dx1);
|
|
498
554
|
|
|
499
555
|
// Calculate tangent at end (direction from last control point)
|
|
500
556
|
// Handle degenerate case: if last control point coincides with end
|
|
501
557
|
const dx2 = currentX - cmd.x2;
|
|
502
558
|
const dy2 = currentY - cmd.y2;
|
|
503
|
-
const endTangent =
|
|
504
|
-
|
|
505
|
-
|
|
559
|
+
const endTangent =
|
|
560
|
+
dx2 === 0 && dy2 === 0
|
|
561
|
+
? Math.atan2(currentY - cmd.y1, currentX - cmd.x1) // Use first control point
|
|
562
|
+
: Math.atan2(dy2, dx2);
|
|
506
563
|
|
|
507
564
|
// Update previous vertex's outgoing tangent
|
|
508
565
|
if (vertices.length > 0) {
|
|
@@ -528,16 +585,18 @@ export function getPathVertices(pathData) {
|
|
|
528
585
|
// Calculate tangent at start (handle degenerate case)
|
|
529
586
|
const dxq1 = cmd.x1 - prevX;
|
|
530
587
|
const dyq1 = cmd.y1 - prevY;
|
|
531
|
-
const qStartTangent =
|
|
532
|
-
|
|
533
|
-
|
|
588
|
+
const qStartTangent =
|
|
589
|
+
dxq1 === 0 && dyq1 === 0
|
|
590
|
+
? Math.atan2(currentY - prevY, currentX - prevX) // Use endpoint
|
|
591
|
+
: Math.atan2(dyq1, dxq1);
|
|
534
592
|
|
|
535
593
|
// Calculate tangent at end (handle degenerate case)
|
|
536
594
|
const dxq2 = currentX - cmd.x1;
|
|
537
595
|
const dyq2 = currentY - cmd.y1;
|
|
538
|
-
const qEndTangent =
|
|
539
|
-
|
|
540
|
-
|
|
596
|
+
const qEndTangent =
|
|
597
|
+
dxq2 === 0 && dyq2 === 0
|
|
598
|
+
? Math.atan2(currentY - prevY, currentX - prevX) // Use startpoint
|
|
599
|
+
: Math.atan2(dyq2, dxq2);
|
|
541
600
|
|
|
542
601
|
// Update previous vertex's outgoing tangent
|
|
543
602
|
if (vertices.length > 0) {
|
|
@@ -563,9 +622,12 @@ export function getPathVertices(pathData) {
|
|
|
563
622
|
// Simplified tangent calculation (handle zero-length arc)
|
|
564
623
|
const dxa = currentX - prevX;
|
|
565
624
|
const dya = currentY - prevY;
|
|
566
|
-
const arcAngle =
|
|
567
|
-
|
|
568
|
-
|
|
625
|
+
const arcAngle =
|
|
626
|
+
dxa === 0 && dya === 0
|
|
627
|
+
? vertices.length > 0
|
|
628
|
+
? vertices[vertices.length - 1].tangentOut
|
|
629
|
+
: 0
|
|
630
|
+
: Math.atan2(dya, dxa);
|
|
569
631
|
|
|
570
632
|
// Update previous vertex's outgoing tangent
|
|
571
633
|
if (vertices.length > 0) {
|
|
@@ -591,9 +653,12 @@ export function getPathVertices(pathData) {
|
|
|
591
653
|
// Calculate tangent from last point to start (handle zero-length close)
|
|
592
654
|
const dxz = currentX - prevX;
|
|
593
655
|
const dyz = currentY - prevY;
|
|
594
|
-
const closeAngle =
|
|
595
|
-
|
|
596
|
-
|
|
656
|
+
const closeAngle =
|
|
657
|
+
dxz === 0 && dyz === 0
|
|
658
|
+
? vertices.length > 0
|
|
659
|
+
? vertices[vertices.length - 1].tangentOut
|
|
660
|
+
: 0
|
|
661
|
+
: Math.atan2(dyz, dxz);
|
|
597
662
|
|
|
598
663
|
// Update previous vertex's outgoing tangent
|
|
599
664
|
if (vertices.length > 0) {
|
|
@@ -622,14 +687,14 @@ export function getPathVertices(pathData) {
|
|
|
622
687
|
*/
|
|
623
688
|
export function parsePathCommands(pathData) {
|
|
624
689
|
// Validate pathData parameter
|
|
625
|
-
if (typeof pathData !==
|
|
626
|
-
throw new Error(
|
|
690
|
+
if (typeof pathData !== "string") {
|
|
691
|
+
throw new Error("parsePathCommands: pathData must be a string");
|
|
627
692
|
}
|
|
628
693
|
|
|
629
694
|
const commands = [];
|
|
630
695
|
|
|
631
696
|
// Handle empty path data
|
|
632
|
-
if (pathData.trim() ===
|
|
697
|
+
if (pathData.trim() === "") {
|
|
633
698
|
return commands;
|
|
634
699
|
}
|
|
635
700
|
|
|
@@ -675,11 +740,15 @@ export function parsePathCommands(pathData) {
|
|
|
675
740
|
// Helper to safely parse token with bounds checking
|
|
676
741
|
const parseToken = (index, name) => {
|
|
677
742
|
if (index >= tokens.length) {
|
|
678
|
-
throw new Error(
|
|
743
|
+
throw new Error(
|
|
744
|
+
`parsePathCommands: missing ${name} parameter at token ${index}`,
|
|
745
|
+
);
|
|
679
746
|
}
|
|
680
747
|
const value = parseFloat(tokens[index]);
|
|
681
748
|
if (isNaN(value) || !isFinite(value)) {
|
|
682
|
-
throw new Error(
|
|
749
|
+
throw new Error(
|
|
750
|
+
`parsePathCommands: invalid ${name} value "${tokens[index]}" at token ${index}`,
|
|
751
|
+
);
|
|
683
752
|
}
|
|
684
753
|
return value;
|
|
685
754
|
};
|
|
@@ -690,8 +759,8 @@ export function parsePathCommands(pathData) {
|
|
|
690
759
|
switch (cmdType.toUpperCase()) {
|
|
691
760
|
case "M": {
|
|
692
761
|
// moveto - requires 2 parameters (x, y)
|
|
693
|
-
const mx = parseToken(i + 1,
|
|
694
|
-
const my = parseToken(i + 2,
|
|
762
|
+
const mx = parseToken(i + 1, "M.x");
|
|
763
|
+
const my = parseToken(i + 2, "M.y");
|
|
695
764
|
commands.push({
|
|
696
765
|
type: "M",
|
|
697
766
|
x: cmdType === "M" ? mx : currentX + mx,
|
|
@@ -705,8 +774,8 @@ export function parsePathCommands(pathData) {
|
|
|
705
774
|
|
|
706
775
|
case "L": {
|
|
707
776
|
// lineto - requires 2 parameters (x, y)
|
|
708
|
-
const lx = parseToken(i + 1,
|
|
709
|
-
const ly = parseToken(i + 2,
|
|
777
|
+
const lx = parseToken(i + 1, "L.x");
|
|
778
|
+
const ly = parseToken(i + 2, "L.y");
|
|
710
779
|
commands.push({
|
|
711
780
|
type: "L",
|
|
712
781
|
x: cmdType === "L" ? lx : currentX + lx,
|
|
@@ -720,7 +789,7 @@ export function parsePathCommands(pathData) {
|
|
|
720
789
|
|
|
721
790
|
case "H": {
|
|
722
791
|
// horizontal lineto - requires 1 parameter (x)
|
|
723
|
-
const hx = parseToken(i + 1,
|
|
792
|
+
const hx = parseToken(i + 1, "H.x");
|
|
724
793
|
commands.push({
|
|
725
794
|
type: "L",
|
|
726
795
|
x: cmdType === "H" ? hx : currentX + hx,
|
|
@@ -733,7 +802,7 @@ export function parsePathCommands(pathData) {
|
|
|
733
802
|
|
|
734
803
|
case "V": {
|
|
735
804
|
// vertical lineto - requires 1 parameter (y)
|
|
736
|
-
const vy = parseToken(i + 1,
|
|
805
|
+
const vy = parseToken(i + 1, "V.y");
|
|
737
806
|
commands.push({
|
|
738
807
|
type: "L",
|
|
739
808
|
x: currentX,
|
|
@@ -746,12 +815,12 @@ export function parsePathCommands(pathData) {
|
|
|
746
815
|
|
|
747
816
|
case "C": {
|
|
748
817
|
// cubic bezier - requires 6 parameters (x1, y1, x2, y2, x, y)
|
|
749
|
-
const c1x = parseToken(i + 1,
|
|
750
|
-
const c1y = parseToken(i + 2,
|
|
751
|
-
const c2x = parseToken(i + 3,
|
|
752
|
-
const c2y = parseToken(i + 4,
|
|
753
|
-
const cx = parseToken(i + 5,
|
|
754
|
-
const cy = parseToken(i + 6,
|
|
818
|
+
const c1x = parseToken(i + 1, "C.x1");
|
|
819
|
+
const c1y = parseToken(i + 2, "C.y1");
|
|
820
|
+
const c2x = parseToken(i + 3, "C.x2");
|
|
821
|
+
const c2y = parseToken(i + 4, "C.y2");
|
|
822
|
+
const cx = parseToken(i + 5, "C.x");
|
|
823
|
+
const cy = parseToken(i + 6, "C.y");
|
|
755
824
|
commands.push({
|
|
756
825
|
type: "C",
|
|
757
826
|
x1: cmdType === "C" ? c1x : currentX + c1x,
|
|
@@ -769,10 +838,10 @@ export function parsePathCommands(pathData) {
|
|
|
769
838
|
|
|
770
839
|
case "Q": {
|
|
771
840
|
// quadratic bezier - requires 4 parameters (x1, y1, x, y)
|
|
772
|
-
const q1x = parseToken(i + 1,
|
|
773
|
-
const q1y = parseToken(i + 2,
|
|
774
|
-
const qx = parseToken(i + 3,
|
|
775
|
-
const qy = parseToken(i + 4,
|
|
841
|
+
const q1x = parseToken(i + 1, "Q.x1");
|
|
842
|
+
const q1y = parseToken(i + 2, "Q.y1");
|
|
843
|
+
const qx = parseToken(i + 3, "Q.x");
|
|
844
|
+
const qy = parseToken(i + 4, "Q.y");
|
|
776
845
|
commands.push({
|
|
777
846
|
type: "Q",
|
|
778
847
|
x1: cmdType === "Q" ? q1x : currentX + q1x,
|
|
@@ -788,9 +857,9 @@ export function parsePathCommands(pathData) {
|
|
|
788
857
|
|
|
789
858
|
case "A": {
|
|
790
859
|
// arc - requires 7 parameters (rx, ry, rotation, largeArc, sweep, x, y)
|
|
791
|
-
const rx = parseToken(i + 1,
|
|
792
|
-
const ry = parseToken(i + 2,
|
|
793
|
-
const xAxisRotation = parseToken(i + 3,
|
|
860
|
+
const rx = parseToken(i + 1, "A.rx");
|
|
861
|
+
const ry = parseToken(i + 2, "A.ry");
|
|
862
|
+
const xAxisRotation = parseToken(i + 3, "A.rotation");
|
|
794
863
|
// Flags must be 0 or 1
|
|
795
864
|
if (i + 4 >= tokens.length || i + 5 >= tokens.length) {
|
|
796
865
|
throw new Error(`parsePathCommands: missing arc flag parameters`);
|
|
@@ -800,8 +869,8 @@ export function parsePathCommands(pathData) {
|
|
|
800
869
|
if (isNaN(largeArcFlag) || isNaN(sweepFlag)) {
|
|
801
870
|
throw new Error(`parsePathCommands: invalid arc flag values`);
|
|
802
871
|
}
|
|
803
|
-
const ax = parseToken(i + 6,
|
|
804
|
-
const ay = parseToken(i + 7,
|
|
872
|
+
const ax = parseToken(i + 6, "A.x");
|
|
873
|
+
const ay = parseToken(i + 7, "A.y");
|
|
805
874
|
commands.push({
|
|
806
875
|
type: "A",
|
|
807
876
|
rx,
|
|
@@ -864,9 +933,9 @@ export function parsePathCommands(pathData) {
|
|
|
864
933
|
*/
|
|
865
934
|
export function resolveMarkers(pathElement, defsMap) {
|
|
866
935
|
// Validate required parameters
|
|
867
|
-
if (!pathElement) throw new Error(
|
|
868
|
-
if (!defsMap || typeof defsMap !==
|
|
869
|
-
throw new Error(
|
|
936
|
+
if (!pathElement) throw new Error("resolveMarkers: pathElement is required");
|
|
937
|
+
if (!defsMap || typeof defsMap !== "object") {
|
|
938
|
+
throw new Error("resolveMarkers: defsMap must be an object");
|
|
870
939
|
}
|
|
871
940
|
|
|
872
941
|
const instances = [];
|
|
@@ -881,7 +950,7 @@ export function resolveMarkers(pathElement, defsMap) {
|
|
|
881
950
|
const strokeWidth = parseFloat(strokeWidthStr);
|
|
882
951
|
// Validate strokeWidth is a positive number
|
|
883
952
|
if (isNaN(strokeWidth) || strokeWidth <= 0) {
|
|
884
|
-
throw new Error(
|
|
953
|
+
throw new Error("resolveMarkers: stroke-width must be a positive number");
|
|
885
954
|
}
|
|
886
955
|
|
|
887
956
|
// Get path data and extract vertices
|
|
@@ -1012,18 +1081,25 @@ export function resolveMarkers(pathElement, defsMap) {
|
|
|
1012
1081
|
*/
|
|
1013
1082
|
export function markerToPolygons(markerInstance, options = {}) {
|
|
1014
1083
|
// Validate markerInstance parameter
|
|
1015
|
-
if (!markerInstance)
|
|
1016
|
-
|
|
1017
|
-
if (!markerInstance.
|
|
1084
|
+
if (!markerInstance)
|
|
1085
|
+
throw new Error("markerToPolygons: markerInstance is required");
|
|
1086
|
+
if (!markerInstance.markerDef)
|
|
1087
|
+
throw new Error("markerToPolygons: markerInstance.markerDef is required");
|
|
1088
|
+
if (!markerInstance.transform)
|
|
1089
|
+
throw new Error("markerToPolygons: markerInstance.transform is required");
|
|
1018
1090
|
|
|
1019
1091
|
const { precision = 2, curveSegments = 10 } = options;
|
|
1020
1092
|
|
|
1021
1093
|
// Validate options
|
|
1022
|
-
if (typeof precision !==
|
|
1023
|
-
throw new Error(
|
|
1094
|
+
if (typeof precision !== "number" || precision < 0) {
|
|
1095
|
+
throw new Error(
|
|
1096
|
+
"markerToPolygons: precision must be a non-negative number",
|
|
1097
|
+
);
|
|
1024
1098
|
}
|
|
1025
|
-
if (typeof curveSegments !==
|
|
1026
|
-
throw new Error(
|
|
1099
|
+
if (typeof curveSegments !== "number" || curveSegments <= 0) {
|
|
1100
|
+
throw new Error(
|
|
1101
|
+
"markerToPolygons: curveSegments must be a positive number",
|
|
1102
|
+
);
|
|
1027
1103
|
}
|
|
1028
1104
|
|
|
1029
1105
|
const polygons = [];
|
|
@@ -1031,7 +1107,7 @@ export function markerToPolygons(markerInstance, options = {}) {
|
|
|
1031
1107
|
|
|
1032
1108
|
// Validate markerDef has children array
|
|
1033
1109
|
if (!Array.isArray(markerDef.children)) {
|
|
1034
|
-
throw new Error(
|
|
1110
|
+
throw new Error("markerToPolygons: markerDef.children must be an array");
|
|
1035
1111
|
}
|
|
1036
1112
|
|
|
1037
1113
|
// Process each child element
|
|
@@ -1112,11 +1188,11 @@ export function markerToPolygons(markerInstance, options = {}) {
|
|
|
1112
1188
|
*/
|
|
1113
1189
|
export function pathToPoints(pathData, segments = 10) {
|
|
1114
1190
|
// Validate parameters
|
|
1115
|
-
if (typeof pathData !==
|
|
1116
|
-
throw new Error(
|
|
1191
|
+
if (typeof pathData !== "string") {
|
|
1192
|
+
throw new Error("pathToPoints: pathData must be a string");
|
|
1117
1193
|
}
|
|
1118
|
-
if (typeof segments !==
|
|
1119
|
-
throw new Error(
|
|
1194
|
+
if (typeof segments !== "number" || segments <= 0) {
|
|
1195
|
+
throw new Error("pathToPoints: segments must be a positive number");
|
|
1120
1196
|
}
|
|
1121
1197
|
|
|
1122
1198
|
const points = [];
|
|
@@ -1197,17 +1273,17 @@ export function pathToPoints(pathData, segments = 10) {
|
|
|
1197
1273
|
*/
|
|
1198
1274
|
export function circleToPoints(cx, cy, r, segments = 32) {
|
|
1199
1275
|
// Validate parameters
|
|
1200
|
-
if (typeof cx !==
|
|
1201
|
-
throw new Error(
|
|
1276
|
+
if (typeof cx !== "number" || !isFinite(cx)) {
|
|
1277
|
+
throw new Error("circleToPoints: cx must be a finite number");
|
|
1202
1278
|
}
|
|
1203
|
-
if (typeof cy !==
|
|
1204
|
-
throw new Error(
|
|
1279
|
+
if (typeof cy !== "number" || !isFinite(cy)) {
|
|
1280
|
+
throw new Error("circleToPoints: cy must be a finite number");
|
|
1205
1281
|
}
|
|
1206
|
-
if (typeof r !==
|
|
1207
|
-
throw new Error(
|
|
1282
|
+
if (typeof r !== "number" || !isFinite(r) || r < 0) {
|
|
1283
|
+
throw new Error("circleToPoints: r must be a non-negative finite number");
|
|
1208
1284
|
}
|
|
1209
|
-
if (typeof segments !==
|
|
1210
|
-
throw new Error(
|
|
1285
|
+
if (typeof segments !== "number" || segments <= 0) {
|
|
1286
|
+
throw new Error("circleToPoints: segments must be a positive number");
|
|
1211
1287
|
}
|
|
1212
1288
|
|
|
1213
1289
|
const points = [];
|
|
@@ -1233,20 +1309,20 @@ export function circleToPoints(cx, cy, r, segments = 32) {
|
|
|
1233
1309
|
*/
|
|
1234
1310
|
export function ellipseToPoints(cx, cy, rx, ry, segments = 32) {
|
|
1235
1311
|
// Validate parameters
|
|
1236
|
-
if (typeof cx !==
|
|
1237
|
-
throw new Error(
|
|
1312
|
+
if (typeof cx !== "number" || !isFinite(cx)) {
|
|
1313
|
+
throw new Error("ellipseToPoints: cx must be a finite number");
|
|
1238
1314
|
}
|
|
1239
|
-
if (typeof cy !==
|
|
1240
|
-
throw new Error(
|
|
1315
|
+
if (typeof cy !== "number" || !isFinite(cy)) {
|
|
1316
|
+
throw new Error("ellipseToPoints: cy must be a finite number");
|
|
1241
1317
|
}
|
|
1242
|
-
if (typeof rx !==
|
|
1243
|
-
throw new Error(
|
|
1318
|
+
if (typeof rx !== "number" || !isFinite(rx) || rx < 0) {
|
|
1319
|
+
throw new Error("ellipseToPoints: rx must be a non-negative finite number");
|
|
1244
1320
|
}
|
|
1245
|
-
if (typeof ry !==
|
|
1246
|
-
throw new Error(
|
|
1321
|
+
if (typeof ry !== "number" || !isFinite(ry) || ry < 0) {
|
|
1322
|
+
throw new Error("ellipseToPoints: ry must be a non-negative finite number");
|
|
1247
1323
|
}
|
|
1248
|
-
if (typeof segments !==
|
|
1249
|
-
throw new Error(
|
|
1324
|
+
if (typeof segments !== "number" || segments <= 0) {
|
|
1325
|
+
throw new Error("ellipseToPoints: segments must be a positive number");
|
|
1250
1326
|
}
|
|
1251
1327
|
|
|
1252
1328
|
const points = [];
|
|
@@ -1268,8 +1344,8 @@ export function ellipseToPoints(cx, cy, rx, ry, segments = 32) {
|
|
|
1268
1344
|
*/
|
|
1269
1345
|
export function parsePoints(pointsStr) {
|
|
1270
1346
|
// Validate parameter
|
|
1271
|
-
if (typeof pointsStr !==
|
|
1272
|
-
throw new Error(
|
|
1347
|
+
if (typeof pointsStr !== "string") {
|
|
1348
|
+
throw new Error("parsePoints: pointsStr must be a string");
|
|
1273
1349
|
}
|
|
1274
1350
|
|
|
1275
1351
|
const points = [];
|
|
@@ -1277,11 +1353,13 @@ export function parsePoints(pointsStr) {
|
|
|
1277
1353
|
.trim()
|
|
1278
1354
|
.split(/[\s,]+/)
|
|
1279
1355
|
.map(Number)
|
|
1280
|
-
.filter(n => !isNaN(n) && isFinite(n)); // Filter out invalid numbers
|
|
1356
|
+
.filter((n) => !isNaN(n) && isFinite(n)); // Filter out invalid numbers
|
|
1281
1357
|
|
|
1282
1358
|
// Ensure we have an even number of coordinates
|
|
1283
1359
|
if (coords.length % 2 !== 0) {
|
|
1284
|
-
throw new Error(
|
|
1360
|
+
throw new Error(
|
|
1361
|
+
"parsePoints: pointsStr must contain an even number of valid coordinates",
|
|
1362
|
+
);
|
|
1285
1363
|
}
|
|
1286
1364
|
|
|
1287
1365
|
for (let i = 0; i < coords.length; i += 2) {
|
|
@@ -1310,10 +1388,12 @@ export function parsePoints(pointsStr) {
|
|
|
1310
1388
|
export function markersToPathData(markerInstances, precision = 2) {
|
|
1311
1389
|
// Validate parameters
|
|
1312
1390
|
if (!Array.isArray(markerInstances)) {
|
|
1313
|
-
throw new Error(
|
|
1391
|
+
throw new Error("markersToPathData: markerInstances must be an array");
|
|
1314
1392
|
}
|
|
1315
|
-
if (typeof precision !==
|
|
1316
|
-
throw new Error(
|
|
1393
|
+
if (typeof precision !== "number" || precision < 0) {
|
|
1394
|
+
throw new Error(
|
|
1395
|
+
"markersToPathData: precision must be a non-negative number",
|
|
1396
|
+
);
|
|
1317
1397
|
}
|
|
1318
1398
|
|
|
1319
1399
|
const pathParts = [];
|