@emasoft/svg-matrix 1.0.30 → 1.0.31

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 (45) hide show
  1. package/bin/svg-matrix.js +310 -61
  2. package/bin/svglinter.cjs +102 -3
  3. package/bin/svgm.js +236 -27
  4. package/package.json +1 -1
  5. package/src/animation-optimization.js +137 -17
  6. package/src/animation-references.js +123 -6
  7. package/src/arc-length.js +213 -4
  8. package/src/bezier-analysis.js +217 -21
  9. package/src/bezier-intersections.js +275 -12
  10. package/src/browser-verify.js +237 -4
  11. package/src/clip-path-resolver.js +168 -0
  12. package/src/convert-path-data.js +479 -28
  13. package/src/css-specificity.js +73 -10
  14. package/src/douglas-peucker.js +219 -2
  15. package/src/flatten-pipeline.js +284 -26
  16. package/src/geometry-to-path.js +250 -25
  17. package/src/gjk-collision.js +236 -33
  18. package/src/index.js +261 -3
  19. package/src/inkscape-support.js +86 -28
  20. package/src/logger.js +48 -3
  21. package/src/marker-resolver.js +278 -74
  22. package/src/mask-resolver.js +265 -66
  23. package/src/matrix.js +44 -5
  24. package/src/mesh-gradient.js +352 -102
  25. package/src/off-canvas-detection.js +382 -13
  26. package/src/path-analysis.js +192 -18
  27. package/src/path-data-plugins.js +309 -5
  28. package/src/path-optimization.js +129 -5
  29. package/src/path-simplification.js +188 -32
  30. package/src/pattern-resolver.js +454 -106
  31. package/src/polygon-clip.js +324 -1
  32. package/src/svg-boolean-ops.js +226 -9
  33. package/src/svg-collections.js +7 -5
  34. package/src/svg-flatten.js +386 -62
  35. package/src/svg-parser.js +179 -8
  36. package/src/svg-rendering-context.js +235 -6
  37. package/src/svg-toolbox.js +45 -8
  38. package/src/svg2-polyfills.js +40 -10
  39. package/src/transform-decomposition.js +258 -32
  40. package/src/transform-optimization.js +259 -13
  41. package/src/transforms2d.js +82 -9
  42. package/src/transforms3d.js +62 -10
  43. package/src/use-symbol-resolver.js +286 -42
  44. package/src/vector.js +64 -8
  45. package/src/verification.js +392 -1
@@ -68,12 +68,34 @@ Decimal.set({ precision: 80 });
68
68
  * // }
69
69
  */
70
70
  export function parseMarkerElement(markerElement) {
71
+ // Validate required parameter
72
+ if (!markerElement) throw new Error('parseMarkerElement: markerElement is required');
73
+ if (typeof markerElement.getAttribute !== 'function') {
74
+ throw new Error('parseMarkerElement: markerElement must be a DOM element');
75
+ }
76
+
77
+ // Parse numeric attributes with validation - NaN check ensures valid numbers
78
+ const markerWidth = parseFloat(markerElement.getAttribute("markerWidth") || "3");
79
+ const markerHeight = parseFloat(markerElement.getAttribute("markerHeight") || "3");
80
+ const refX = parseFloat(markerElement.getAttribute("refX") || "0");
81
+ const refY = parseFloat(markerElement.getAttribute("refY") || "0");
82
+
83
+ // Validate numeric values to prevent NaN propagation
84
+ if (isNaN(markerWidth) || markerWidth <= 0) {
85
+ throw new Error('parseMarkerElement: markerWidth must be a positive number');
86
+ }
87
+ if (isNaN(markerHeight) || markerHeight <= 0) {
88
+ throw new Error('parseMarkerElement: markerHeight must be a positive number');
89
+ }
90
+ if (isNaN(refX)) throw new Error('parseMarkerElement: refX must be a valid number');
91
+ if (isNaN(refY)) throw new Error('parseMarkerElement: refY must be a valid number');
92
+
71
93
  const data = {
72
94
  id: markerElement.getAttribute("id") || "",
73
- markerWidth: parseFloat(markerElement.getAttribute("markerWidth") || "3"),
74
- markerHeight: parseFloat(markerElement.getAttribute("markerHeight") || "3"),
75
- refX: parseFloat(markerElement.getAttribute("refX") || "0"),
76
- refY: parseFloat(markerElement.getAttribute("refY") || "0"),
95
+ markerWidth,
96
+ markerHeight,
97
+ refX,
98
+ refY,
77
99
  orient: markerElement.getAttribute("orient") || "auto",
78
100
  markerUnits: markerElement.getAttribute("markerUnits") || "strokeWidth",
79
101
  viewBox: null,
@@ -89,7 +111,8 @@ export function parseMarkerElement(markerElement) {
89
111
  .trim()
90
112
  .split(/[\s,]+/)
91
113
  .map(Number);
92
- if (parts.length === 4) {
114
+ // Validate viewBox has exactly 4 valid finite numbers
115
+ if (parts.length === 4 && parts.every(n => !isNaN(n) && isFinite(n))) {
93
116
  data.viewBox = {
94
117
  x: parts[0],
95
118
  y: parts[1],
@@ -103,14 +126,25 @@ export function parseMarkerElement(markerElement) {
103
126
  if (data.orient !== "auto" && data.orient !== "auto-start-reverse") {
104
127
  // Parse as angle in degrees
105
128
  const angle = parseFloat(data.orient);
106
- if (!isNaN(angle)) {
129
+ // Validate angle is a finite number
130
+ if (!isNaN(angle) && isFinite(angle)) {
107
131
  data.orient = angle;
132
+ } else {
133
+ // Invalid orient value defaults to auto
134
+ data.orient = "auto";
108
135
  }
109
136
  }
110
137
 
111
- // Parse child elements
112
- for (const child of markerElement.children) {
113
- data.children.push(parseMarkerChild(child));
138
+ // Parse child elements with error handling
139
+ if (markerElement.children) {
140
+ for (const child of markerElement.children) {
141
+ try {
142
+ data.children.push(parseMarkerChild(child));
143
+ } catch (error) {
144
+ // Skip invalid children but continue processing
145
+ console.warn(`Failed to parse marker child: ${error.message}`);
146
+ }
147
+ }
114
148
  }
115
149
 
116
150
  return data;
@@ -123,6 +157,10 @@ export function parseMarkerElement(markerElement) {
123
157
  * @returns {Object} Parsed element data
124
158
  */
125
159
  export function parseMarkerChild(element) {
160
+ // Validate required parameter
161
+ if (!element) throw new Error('parseMarkerChild: element is required');
162
+ if (!element.tagName) throw new Error('parseMarkerChild: element must have a tagName property');
163
+
126
164
  const tagName = element.tagName.toLowerCase();
127
165
  const data = {
128
166
  type: tagName,
@@ -134,32 +172,45 @@ export function parseMarkerChild(element) {
134
172
  opacity: element.getAttribute("opacity") || null,
135
173
  };
136
174
 
175
+ // Helper to parse and validate float attributes
176
+ const parseValidFloat = (value, defaultVal, name) => {
177
+ const parsed = parseFloat(value || String(defaultVal));
178
+ if (isNaN(parsed) || !isFinite(parsed)) {
179
+ throw new Error(`parseMarkerChild: ${name} must be a valid finite number`);
180
+ }
181
+ return parsed;
182
+ };
183
+
137
184
  switch (tagName) {
138
185
  case "path":
139
186
  data.d = element.getAttribute("d") || "";
140
187
  break;
141
188
  case "rect":
142
- data.x = parseFloat(element.getAttribute("x") || "0");
143
- data.y = parseFloat(element.getAttribute("y") || "0");
144
- data.width = parseFloat(element.getAttribute("width") || "0");
145
- data.height = parseFloat(element.getAttribute("height") || "0");
189
+ // Validate all rect attributes are valid numbers
190
+ data.x = parseValidFloat(element.getAttribute("x"), 0, "x");
191
+ data.y = parseValidFloat(element.getAttribute("y"), 0, "y");
192
+ data.width = parseValidFloat(element.getAttribute("width"), 0, "width");
193
+ data.height = parseValidFloat(element.getAttribute("height"), 0, "height");
146
194
  break;
147
195
  case "circle":
148
- data.cx = parseFloat(element.getAttribute("cx") || "0");
149
- data.cy = parseFloat(element.getAttribute("cy") || "0");
150
- data.r = parseFloat(element.getAttribute("r") || "0");
196
+ // Validate all circle attributes are valid numbers
197
+ data.cx = parseValidFloat(element.getAttribute("cx"), 0, "cx");
198
+ data.cy = parseValidFloat(element.getAttribute("cy"), 0, "cy");
199
+ data.r = parseValidFloat(element.getAttribute("r"), 0, "r");
151
200
  break;
152
201
  case "ellipse":
153
- data.cx = parseFloat(element.getAttribute("cx") || "0");
154
- data.cy = parseFloat(element.getAttribute("cy") || "0");
155
- data.rx = parseFloat(element.getAttribute("rx") || "0");
156
- data.ry = parseFloat(element.getAttribute("ry") || "0");
202
+ // Validate all ellipse attributes are valid numbers
203
+ data.cx = parseValidFloat(element.getAttribute("cx"), 0, "cx");
204
+ data.cy = parseValidFloat(element.getAttribute("cy"), 0, "cy");
205
+ data.rx = parseValidFloat(element.getAttribute("rx"), 0, "rx");
206
+ data.ry = parseValidFloat(element.getAttribute("ry"), 0, "ry");
157
207
  break;
158
208
  case "line":
159
- data.x1 = parseFloat(element.getAttribute("x1") || "0");
160
- data.y1 = parseFloat(element.getAttribute("y1") || "0");
161
- data.x2 = parseFloat(element.getAttribute("x2") || "0");
162
- data.y2 = parseFloat(element.getAttribute("y2") || "0");
209
+ // Validate all line attributes are valid numbers
210
+ data.x1 = parseValidFloat(element.getAttribute("x1"), 0, "x1");
211
+ data.y1 = parseValidFloat(element.getAttribute("y1"), 0, "y1");
212
+ data.x2 = parseValidFloat(element.getAttribute("x2"), 0, "x2");
213
+ data.y2 = parseValidFloat(element.getAttribute("y2"), 0, "y2");
163
214
  break;
164
215
  case "polygon":
165
216
  case "polyline":
@@ -168,8 +219,10 @@ export function parseMarkerChild(element) {
168
219
  default:
169
220
  // Store any additional attributes for unknown elements
170
221
  data.attributes = {};
171
- for (const attr of element.attributes) {
172
- data.attributes[attr.name] = attr.value;
222
+ if (element.attributes) {
223
+ for (const attr of element.attributes) {
224
+ data.attributes[attr.name] = attr.value;
225
+ }
173
226
  }
174
227
  }
175
228
 
@@ -211,6 +264,19 @@ export function getMarkerTransform(
211
264
  strokeWidth = 1,
212
265
  isStart = false,
213
266
  ) {
267
+ // Validate required parameters
268
+ if (!markerDef) throw new Error('getMarkerTransform: markerDef is required');
269
+ if (!position || typeof position.x !== 'number' || typeof position.y !== 'number') {
270
+ throw new Error('getMarkerTransform: position must be an object with x and y numeric properties');
271
+ }
272
+ if (typeof tangentAngle !== 'number' || isNaN(tangentAngle) || !isFinite(tangentAngle)) {
273
+ throw new Error('getMarkerTransform: tangentAngle must be a valid finite number');
274
+ }
275
+ if (typeof strokeWidth !== 'number' || isNaN(strokeWidth) || strokeWidth <= 0) {
276
+ throw new Error('getMarkerTransform: strokeWidth must be a positive number');
277
+ }
278
+
279
+ // Extract markerDef properties with validation
214
280
  const {
215
281
  markerWidth,
216
282
  markerHeight,
@@ -221,6 +287,20 @@ export function getMarkerTransform(
221
287
  viewBox,
222
288
  } = markerDef;
223
289
 
290
+ // Validate markerDef has required numeric properties
291
+ if (typeof markerWidth !== 'number' || markerWidth <= 0) {
292
+ throw new Error('getMarkerTransform: markerDef.markerWidth must be a positive number');
293
+ }
294
+ if (typeof markerHeight !== 'number' || markerHeight <= 0) {
295
+ throw new Error('getMarkerTransform: markerDef.markerHeight must be a positive number');
296
+ }
297
+ if (typeof refX !== 'number' || !isFinite(refX)) {
298
+ throw new Error('getMarkerTransform: markerDef.refX must be a finite number');
299
+ }
300
+ if (typeof refY !== 'number' || !isFinite(refY)) {
301
+ throw new Error('getMarkerTransform: markerDef.refY must be a finite number');
302
+ }
303
+
224
304
  // Start with identity matrix
225
305
  let transform = Matrix.identity(3);
226
306
 
@@ -258,20 +338,24 @@ export function getMarkerTransform(
258
338
  const vbWidth = viewBox.width;
259
339
  const vbHeight = viewBox.height;
260
340
 
261
- if (vbWidth > 0 && vbHeight > 0) {
341
+ // Prevent division by zero
342
+ if (vbWidth > 0 && vbHeight > 0 && isFinite(vbWidth) && isFinite(vbHeight)) {
262
343
  // Calculate uniform scale factor based on preserveAspectRatio
263
344
  const scaleFactorX = markerWidth / vbWidth;
264
345
  const scaleFactorY = markerHeight / vbHeight;
265
346
 
266
- // For now, use uniform scaling (can be enhanced with full preserveAspectRatio parsing)
267
- const scaleFactor = Math.min(scaleFactorX, scaleFactorY);
347
+ // Validate scale factors are finite
348
+ if (isFinite(scaleFactorX) && isFinite(scaleFactorY)) {
349
+ // For now, use uniform scaling (can be enhanced with full preserveAspectRatio parsing)
350
+ const scaleFactor = Math.min(scaleFactorX, scaleFactorY);
268
351
 
269
- scaleX *= scaleFactor;
270
- scaleY *= scaleFactor;
352
+ scaleX *= scaleFactor;
353
+ scaleY *= scaleFactor;
271
354
 
272
- // Translate to account for viewBox origin
273
- const viewBoxTranslate = Transforms2D.translation(-viewBox.x, -viewBox.y);
274
- transform = transform.mul(viewBoxTranslate);
355
+ // Translate to account for viewBox origin
356
+ const viewBoxTranslate = Transforms2D.translation(-viewBox.x, -viewBox.y);
357
+ transform = transform.mul(viewBoxTranslate);
358
+ }
275
359
  }
276
360
  }
277
361
 
@@ -327,13 +411,16 @@ export function getMarkerTransform(
327
411
  * // ]
328
412
  */
329
413
  export function getPathVertices(pathData) {
414
+ // Validate pathData parameter
415
+ if (typeof pathData !== 'string') {
416
+ throw new Error('getPathVertices: pathData must be a string');
417
+ }
418
+
330
419
  const vertices = [];
331
420
  let currentX = 0;
332
421
  let currentY = 0;
333
422
  let startX = 0;
334
423
  let startY = 0;
335
- let _lastControlX = 0;
336
- let _lastControlY = 0;
337
424
 
338
425
  // Parse path commands
339
426
  const commands = parsePathCommands(pathData);
@@ -393,8 +480,6 @@ export function getPathVertices(pathData) {
393
480
 
394
481
  case "C": {
395
482
  // cubic bezier
396
- _lastControlX = cmd.x2;
397
- _lastControlY = cmd.y2;
398
483
  currentX = cmd.x;
399
484
  currentY = cmd.y;
400
485
 
@@ -422,8 +507,6 @@ export function getPathVertices(pathData) {
422
507
 
423
508
  case "Q": {
424
509
  // quadratic bezier
425
- _lastControlX = cmd.x1;
426
- _lastControlY = cmd.y1;
427
510
  currentX = cmd.x;
428
511
  currentY = cmd.y;
429
512
 
@@ -492,6 +575,8 @@ export function getPathVertices(pathData) {
492
575
  }
493
576
  break;
494
577
  }
578
+ default:
579
+ break;
495
580
  }
496
581
  }
497
582
 
@@ -505,8 +590,18 @@ export function getPathVertices(pathData) {
505
590
  * @returns {Array<Object>} Array of command objects
506
591
  */
507
592
  export function parsePathCommands(pathData) {
593
+ // Validate pathData parameter
594
+ if (typeof pathData !== 'string') {
595
+ throw new Error('parsePathCommands: pathData must be a string');
596
+ }
597
+
508
598
  const commands = [];
509
599
 
600
+ // Handle empty path data
601
+ if (pathData.trim() === '') {
602
+ return commands;
603
+ }
604
+
510
605
  // Normalize path data: add spaces around command letters
511
606
  const normalized = pathData
512
607
  .replace(/([MmLlHhVvCcSsQqTtAaZz])/g, " $1 ")
@@ -546,14 +641,26 @@ export function parsePathCommands(pathData) {
546
641
  let currentX = 0;
547
642
  let currentY = 0;
548
643
 
644
+ // Helper to safely parse token with bounds checking
645
+ const parseToken = (index, name) => {
646
+ if (index >= tokens.length) {
647
+ throw new Error(`parsePathCommands: missing ${name} parameter at token ${index}`);
648
+ }
649
+ const value = parseFloat(tokens[index]);
650
+ if (isNaN(value) || !isFinite(value)) {
651
+ throw new Error(`parsePathCommands: invalid ${name} value "${tokens[index]}" at token ${index}`);
652
+ }
653
+ return value;
654
+ };
655
+
549
656
  while (i < tokens.length) {
550
657
  const cmdType = tokens[i];
551
658
 
552
659
  switch (cmdType.toUpperCase()) {
553
660
  case "M": {
554
- // moveto
555
- const mx = parseFloat(tokens[i + 1]);
556
- const my = parseFloat(tokens[i + 2]);
661
+ // moveto - requires 2 parameters (x, y)
662
+ const mx = parseToken(i + 1, 'M.x');
663
+ const my = parseToken(i + 2, 'M.y');
557
664
  commands.push({
558
665
  type: "M",
559
666
  x: cmdType === "M" ? mx : currentX + mx,
@@ -566,9 +673,9 @@ export function parsePathCommands(pathData) {
566
673
  }
567
674
 
568
675
  case "L": {
569
- // lineto
570
- const lx = parseFloat(tokens[i + 1]);
571
- const ly = parseFloat(tokens[i + 2]);
676
+ // lineto - requires 2 parameters (x, y)
677
+ const lx = parseToken(i + 1, 'L.x');
678
+ const ly = parseToken(i + 2, 'L.y');
572
679
  commands.push({
573
680
  type: "L",
574
681
  x: cmdType === "L" ? lx : currentX + lx,
@@ -581,8 +688,8 @@ export function parsePathCommands(pathData) {
581
688
  }
582
689
 
583
690
  case "H": {
584
- // horizontal lineto
585
- const hx = parseFloat(tokens[i + 1]);
691
+ // horizontal lineto - requires 1 parameter (x)
692
+ const hx = parseToken(i + 1, 'H.x');
586
693
  commands.push({
587
694
  type: "L",
588
695
  x: cmdType === "H" ? hx : currentX + hx,
@@ -594,8 +701,8 @@ export function parsePathCommands(pathData) {
594
701
  }
595
702
 
596
703
  case "V": {
597
- // vertical lineto
598
- const vy = parseFloat(tokens[i + 1]);
704
+ // vertical lineto - requires 1 parameter (y)
705
+ const vy = parseToken(i + 1, 'V.y');
599
706
  commands.push({
600
707
  type: "L",
601
708
  x: currentX,
@@ -607,13 +714,13 @@ export function parsePathCommands(pathData) {
607
714
  }
608
715
 
609
716
  case "C": {
610
- // cubic bezier
611
- const c1x = parseFloat(tokens[i + 1]);
612
- const c1y = parseFloat(tokens[i + 2]);
613
- const c2x = parseFloat(tokens[i + 3]);
614
- const c2y = parseFloat(tokens[i + 4]);
615
- const cx = parseFloat(tokens[i + 5]);
616
- const cy = parseFloat(tokens[i + 6]);
717
+ // cubic bezier - requires 6 parameters (x1, y1, x2, y2, x, y)
718
+ const c1x = parseToken(i + 1, 'C.x1');
719
+ const c1y = parseToken(i + 2, 'C.y1');
720
+ const c2x = parseToken(i + 3, 'C.x2');
721
+ const c2y = parseToken(i + 4, 'C.y2');
722
+ const cx = parseToken(i + 5, 'C.x');
723
+ const cy = parseToken(i + 6, 'C.y');
617
724
  commands.push({
618
725
  type: "C",
619
726
  x1: cmdType === "C" ? c1x : currentX + c1x,
@@ -630,11 +737,11 @@ export function parsePathCommands(pathData) {
630
737
  }
631
738
 
632
739
  case "Q": {
633
- // quadratic bezier
634
- const q1x = parseFloat(tokens[i + 1]);
635
- const q1y = parseFloat(tokens[i + 2]);
636
- const qx = parseFloat(tokens[i + 3]);
637
- const qy = parseFloat(tokens[i + 4]);
740
+ // quadratic bezier - requires 4 parameters (x1, y1, x, y)
741
+ const q1x = parseToken(i + 1, 'Q.x1');
742
+ const q1y = parseToken(i + 2, 'Q.y1');
743
+ const qx = parseToken(i + 3, 'Q.x');
744
+ const qy = parseToken(i + 4, 'Q.y');
638
745
  commands.push({
639
746
  type: "Q",
640
747
  x1: cmdType === "Q" ? q1x : currentX + q1x,
@@ -649,14 +756,21 @@ export function parsePathCommands(pathData) {
649
756
  }
650
757
 
651
758
  case "A": {
652
- // arc
653
- const rx = parseFloat(tokens[i + 1]);
654
- const ry = parseFloat(tokens[i + 2]);
655
- const xAxisRotation = parseFloat(tokens[i + 3]);
759
+ // arc - requires 7 parameters (rx, ry, rotation, largeArc, sweep, x, y)
760
+ const rx = parseToken(i + 1, 'A.rx');
761
+ const ry = parseToken(i + 2, 'A.ry');
762
+ const xAxisRotation = parseToken(i + 3, 'A.rotation');
763
+ // Flags must be 0 or 1
764
+ if (i + 4 >= tokens.length || i + 5 >= tokens.length) {
765
+ throw new Error(`parsePathCommands: missing arc flag parameters`);
766
+ }
656
767
  const largeArcFlag = parseInt(tokens[i + 4], 10);
657
768
  const sweepFlag = parseInt(tokens[i + 5], 10);
658
- const ax = parseFloat(tokens[i + 6]);
659
- const ay = parseFloat(tokens[i + 7]);
769
+ if (isNaN(largeArcFlag) || isNaN(sweepFlag)) {
770
+ throw new Error(`parsePathCommands: invalid arc flag values`);
771
+ }
772
+ const ax = parseToken(i + 6, 'A.x');
773
+ const ay = parseToken(i + 7, 'A.y');
660
774
  commands.push({
661
775
  type: "A",
662
776
  rx,
@@ -718,6 +832,12 @@ export function parsePathCommands(pathData) {
718
832
  * // Returns array of marker instances ready to be rendered
719
833
  */
720
834
  export function resolveMarkers(pathElement, defsMap) {
835
+ // Validate required parameters
836
+ if (!pathElement) throw new Error('resolveMarkers: pathElement is required');
837
+ if (!defsMap || typeof defsMap !== 'object') {
838
+ throw new Error('resolveMarkers: defsMap must be an object');
839
+ }
840
+
721
841
  const instances = [];
722
842
 
723
843
  // Get marker references
@@ -725,10 +845,13 @@ export function resolveMarkers(pathElement, defsMap) {
725
845
  const markerMid = pathElement.getAttribute("marker-mid");
726
846
  const markerEnd = pathElement.getAttribute("marker-end");
727
847
 
728
- // Get stroke width for scaling
729
- const strokeWidth = parseFloat(
730
- pathElement.getAttribute("stroke-width") || "1",
731
- );
848
+ // Get stroke width for scaling with validation
849
+ const strokeWidthStr = pathElement.getAttribute("stroke-width") || "1";
850
+ const strokeWidth = parseFloat(strokeWidthStr);
851
+ // Validate strokeWidth is a positive number
852
+ if (isNaN(strokeWidth) || strokeWidth <= 0) {
853
+ throw new Error('resolveMarkers: stroke-width must be a positive number');
854
+ }
732
855
 
733
856
  // Get path data and extract vertices
734
857
  const pathData = pathElement.getAttribute("d") || "";
@@ -850,10 +973,29 @@ export function resolveMarkers(pathElement, defsMap) {
850
973
  * // Returns: [[{x: 100.123, y: 200.456}, ...], ...]
851
974
  */
852
975
  export function markerToPolygons(markerInstance, options = {}) {
976
+ // Validate markerInstance parameter
977
+ if (!markerInstance) throw new Error('markerToPolygons: markerInstance is required');
978
+ if (!markerInstance.markerDef) throw new Error('markerToPolygons: markerInstance.markerDef is required');
979
+ if (!markerInstance.transform) throw new Error('markerToPolygons: markerInstance.transform is required');
980
+
853
981
  const { precision = 2, curveSegments = 10 } = options;
982
+
983
+ // Validate options
984
+ if (typeof precision !== 'number' || precision < 0) {
985
+ throw new Error('markerToPolygons: precision must be a non-negative number');
986
+ }
987
+ if (typeof curveSegments !== 'number' || curveSegments <= 0) {
988
+ throw new Error('markerToPolygons: curveSegments must be a positive number');
989
+ }
990
+
854
991
  const polygons = [];
855
992
  const { markerDef, transform } = markerInstance;
856
993
 
994
+ // Validate markerDef has children array
995
+ if (!Array.isArray(markerDef.children)) {
996
+ throw new Error('markerToPolygons: markerDef.children must be an array');
997
+ }
998
+
857
999
  // Process each child element
858
1000
  for (const child of markerDef.children) {
859
1001
  let points = [];
@@ -901,6 +1043,8 @@ export function markerToPolygons(markerInstance, options = {}) {
901
1043
  case "polyline":
902
1044
  points = parsePoints(child.points);
903
1045
  break;
1046
+ default:
1047
+ break;
904
1048
  }
905
1049
 
906
1050
  // Apply transform to all points
@@ -929,6 +1073,14 @@ export function markerToPolygons(markerInstance, options = {}) {
929
1073
  * @returns {Array<Object>} Array of {x, y} points
930
1074
  */
931
1075
  export function pathToPoints(pathData, segments = 10) {
1076
+ // Validate parameters
1077
+ if (typeof pathData !== 'string') {
1078
+ throw new Error('pathToPoints: pathData must be a string');
1079
+ }
1080
+ if (typeof segments !== 'number' || segments <= 0) {
1081
+ throw new Error('pathToPoints: segments must be a positive number');
1082
+ }
1083
+
932
1084
  const points = [];
933
1085
  const commands = parsePathCommands(pathData);
934
1086
  let currentX = 0;
@@ -988,6 +1140,8 @@ export function pathToPoints(pathData, segments = 10) {
988
1140
  currentY = cmd.y;
989
1141
  points.push({ x: currentX, y: currentY });
990
1142
  break;
1143
+ default:
1144
+ break;
991
1145
  }
992
1146
  }
993
1147
 
@@ -1004,6 +1158,20 @@ export function pathToPoints(pathData, segments = 10) {
1004
1158
  * @returns {Array<Object>} Array of {x, y} points
1005
1159
  */
1006
1160
  export function circleToPoints(cx, cy, r, segments = 32) {
1161
+ // Validate parameters
1162
+ if (typeof cx !== 'number' || !isFinite(cx)) {
1163
+ throw new Error('circleToPoints: cx must be a finite number');
1164
+ }
1165
+ if (typeof cy !== 'number' || !isFinite(cy)) {
1166
+ throw new Error('circleToPoints: cy must be a finite number');
1167
+ }
1168
+ if (typeof r !== 'number' || !isFinite(r) || r < 0) {
1169
+ throw new Error('circleToPoints: r must be a non-negative finite number');
1170
+ }
1171
+ if (typeof segments !== 'number' || segments <= 0) {
1172
+ throw new Error('circleToPoints: segments must be a positive number');
1173
+ }
1174
+
1007
1175
  const points = [];
1008
1176
  for (let i = 0; i < segments; i++) {
1009
1177
  const angle = (i / segments) * 2 * Math.PI;
@@ -1026,6 +1194,23 @@ export function circleToPoints(cx, cy, r, segments = 32) {
1026
1194
  * @returns {Array<Object>} Array of {x, y} points
1027
1195
  */
1028
1196
  export function ellipseToPoints(cx, cy, rx, ry, segments = 32) {
1197
+ // Validate parameters
1198
+ if (typeof cx !== 'number' || !isFinite(cx)) {
1199
+ throw new Error('ellipseToPoints: cx must be a finite number');
1200
+ }
1201
+ if (typeof cy !== 'number' || !isFinite(cy)) {
1202
+ throw new Error('ellipseToPoints: cy must be a finite number');
1203
+ }
1204
+ if (typeof rx !== 'number' || !isFinite(rx) || rx < 0) {
1205
+ throw new Error('ellipseToPoints: rx must be a non-negative finite number');
1206
+ }
1207
+ if (typeof ry !== 'number' || !isFinite(ry) || ry < 0) {
1208
+ throw new Error('ellipseToPoints: ry must be a non-negative finite number');
1209
+ }
1210
+ if (typeof segments !== 'number' || segments <= 0) {
1211
+ throw new Error('ellipseToPoints: segments must be a positive number');
1212
+ }
1213
+
1029
1214
  const points = [];
1030
1215
  for (let i = 0; i < segments; i++) {
1031
1216
  const angle = (i / segments) * 2 * Math.PI;
@@ -1044,13 +1229,24 @@ export function ellipseToPoints(cx, cy, rx, ry, segments = 32) {
1044
1229
  * @returns {Array<Object>} Array of {x, y} points
1045
1230
  */
1046
1231
  export function parsePoints(pointsStr) {
1232
+ // Validate parameter
1233
+ if (typeof pointsStr !== 'string') {
1234
+ throw new Error('parsePoints: pointsStr must be a string');
1235
+ }
1236
+
1047
1237
  const points = [];
1048
1238
  const coords = pointsStr
1049
1239
  .trim()
1050
1240
  .split(/[\s,]+/)
1051
- .map(Number);
1241
+ .map(Number)
1242
+ .filter(n => !isNaN(n) && isFinite(n)); // Filter out invalid numbers
1052
1243
 
1053
- for (let i = 0; i < coords.length - 1; i += 2) {
1244
+ // Ensure we have an even number of coordinates
1245
+ if (coords.length % 2 !== 0) {
1246
+ throw new Error('parsePoints: pointsStr must contain an even number of valid coordinates');
1247
+ }
1248
+
1249
+ for (let i = 0; i < coords.length; i += 2) {
1054
1250
  points.push({ x: coords[i], y: coords[i + 1] });
1055
1251
  }
1056
1252
 
@@ -1074,6 +1270,14 @@ export function parsePoints(pointsStr) {
1074
1270
  * // Returns: "M 100.000 200.000 L 105.000 205.000 ... Z M ..."
1075
1271
  */
1076
1272
  export function markersToPathData(markerInstances, precision = 2) {
1273
+ // Validate parameters
1274
+ if (!Array.isArray(markerInstances)) {
1275
+ throw new Error('markersToPathData: markerInstances must be an array');
1276
+ }
1277
+ if (typeof precision !== 'number' || precision < 0) {
1278
+ throw new Error('markersToPathData: precision must be a non-negative number');
1279
+ }
1280
+
1077
1281
  const pathParts = [];
1078
1282
 
1079
1283
  for (const instance of markerInstances) {