@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
@@ -41,7 +41,16 @@ export function getKappa() {
41
41
  * @returns {Decimal} Control point distance factor (multiply by radius)
42
42
  */
43
43
  export function getKappaForArc(thetaRadians) {
44
+ if (thetaRadians == null) {
45
+ throw new Error("getKappaForArc: thetaRadians parameter is required");
46
+ }
44
47
  const theta = D(thetaRadians);
48
+ if (!theta.isFinite()) {
49
+ throw new Error("getKappaForArc: thetaRadians must be finite");
50
+ }
51
+ if (theta.isZero()) {
52
+ throw new Error("getKappaForArc: thetaRadians cannot be zero");
53
+ }
45
54
  const four = new Decimal(4);
46
55
  const three = new Decimal(3);
47
56
  // L = (4/3) * tan(theta/4)
@@ -71,6 +80,19 @@ export function getKappaForArc(thetaRadians) {
71
80
  * @returns {string} SVG path data
72
81
  */
73
82
  export function circleToPathDataHP(cx, cy, r, arcs = 8, precision = 6) {
83
+ if (cx == null || cy == null || r == null) {
84
+ throw new Error("circleToPathDataHP: cx, cy, and r parameters are required");
85
+ }
86
+ const rD = D(r);
87
+ if (!rD.isFinite() || rD.isNegative()) {
88
+ throw new Error("circleToPathDataHP: radius must be finite and non-negative");
89
+ }
90
+ if (!Number.isFinite(arcs) || arcs <= 0) {
91
+ throw new Error("circleToPathDataHP: arcs must be a positive number");
92
+ }
93
+ if (!Number.isFinite(precision) || precision < 0) {
94
+ throw new Error("circleToPathDataHP: precision must be non-negative");
95
+ }
74
96
  return ellipseToPathDataHP(cx, cy, r, r, arcs, precision);
75
97
  }
76
98
 
@@ -90,6 +112,15 @@ export function circleToPathDataHP(cx, cy, r, arcs = 8, precision = 6) {
90
112
  * @returns {string} SVG path data
91
113
  */
92
114
  export function ellipseToPathDataHP(cx, cy, rx, ry, arcs = 8, precision = 6) {
115
+ if (cx == null || cy == null || rx == null || ry == null) {
116
+ throw new Error("ellipseToPathDataHP: cx, cy, rx, and ry parameters are required");
117
+ }
118
+ if (!Number.isFinite(arcs) || arcs <= 0) {
119
+ throw new Error("ellipseToPathDataHP: arcs must be a positive number");
120
+ }
121
+ if (!Number.isFinite(precision) || precision < 0) {
122
+ throw new Error("ellipseToPathDataHP: precision must be non-negative");
123
+ }
93
124
  // Enforce multiple of 4 for symmetry
94
125
  let numArcs = arcs;
95
126
  if (numArcs % 4 !== 0) {
@@ -99,6 +130,12 @@ export function ellipseToPathDataHP(cx, cy, rx, ry, arcs = 8, precision = 6) {
99
130
  cyD = D(cy),
100
131
  rxD = D(rx),
101
132
  ryD = D(ry);
133
+ if (!rxD.isFinite() || rxD.isNegative()) {
134
+ throw new Error("ellipseToPathDataHP: rx must be finite and non-negative");
135
+ }
136
+ if (!ryD.isFinite() || ryD.isNegative()) {
137
+ throw new Error("ellipseToPathDataHP: ry must be finite and non-negative");
138
+ }
102
139
  const f = (v) => formatNumber(v, precision);
103
140
 
104
141
  // Angle per arc in radians
@@ -158,8 +195,18 @@ export function ellipseToPathDataHP(cx, cy, rx, ry, arcs = 8, precision = 6) {
158
195
  * @returns {string} Formatted number string
159
196
  */
160
197
  function formatNumber(value, precision = 6) {
198
+ if (value == null) {
199
+ throw new Error("formatNumber: value parameter is required");
200
+ }
201
+ if (!Number.isFinite(precision) || precision < 0) {
202
+ throw new Error("formatNumber: precision must be non-negative");
203
+ }
204
+ const valueD = D(value);
205
+ if (!valueD.isFinite()) {
206
+ throw new Error("formatNumber: value must be finite");
207
+ }
161
208
  // Format with precision then remove trailing zeros for smaller output
162
- let str = value.toFixed(precision);
209
+ let str = valueD.toFixed(precision);
163
210
  // Remove trailing zeros after decimal point
164
211
  if (str.includes(".")) {
165
212
  str = str.replace(/\.?0+$/, "");
@@ -176,9 +223,18 @@ function formatNumber(value, precision = 6) {
176
223
  * @returns {string} SVG path data string
177
224
  */
178
225
  export function circleToPathData(cx, cy, r, precision = 6) {
226
+ if (cx == null || cy == null || r == null) {
227
+ throw new Error("circleToPathData: cx, cy, and r parameters are required");
228
+ }
229
+ if (!Number.isFinite(precision) || precision < 0) {
230
+ throw new Error("circleToPathData: precision must be non-negative");
231
+ }
179
232
  const cxD = D(cx),
180
233
  cyD = D(cy),
181
234
  rD = D(r);
235
+ if (!rD.isFinite() || rD.isNegative()) {
236
+ throw new Error("circleToPathData: radius must be finite and non-negative");
237
+ }
182
238
  const k = getKappa().mul(rD);
183
239
  const x0 = cxD.plus(rD),
184
240
  y0 = cyD;
@@ -218,10 +274,22 @@ export function circleToPathData(cx, cy, r, precision = 6) {
218
274
  * @returns {string} SVG path data string
219
275
  */
220
276
  export function ellipseToPathData(cx, cy, rx, ry, precision = 6) {
277
+ if (cx == null || cy == null || rx == null || ry == null) {
278
+ throw new Error("ellipseToPathData: cx, cy, rx, and ry parameters are required");
279
+ }
280
+ if (!Number.isFinite(precision) || precision < 0) {
281
+ throw new Error("ellipseToPathData: precision must be non-negative");
282
+ }
221
283
  const cxD = D(cx),
222
284
  cyD = D(cy),
223
285
  rxD = D(rx),
224
286
  ryD = D(ry);
287
+ if (!rxD.isFinite() || rxD.isNegative()) {
288
+ throw new Error("ellipseToPathData: rx must be finite and non-negative");
289
+ }
290
+ if (!ryD.isFinite() || ryD.isNegative()) {
291
+ throw new Error("ellipseToPathData: ry must be finite and non-negative");
292
+ }
225
293
  const kappa = getKappa(),
226
294
  kx = kappa.mul(rxD),
227
295
  ky = kappa.mul(ryD);
@@ -275,10 +343,28 @@ export function rectToPathData(
275
343
  useArcs = false,
276
344
  precision = 6,
277
345
  ) {
346
+ if (x == null || y == null || width == null || height == null) {
347
+ throw new Error("rectToPathData: x, y, width, and height parameters are required");
348
+ }
349
+ if (!Number.isFinite(precision) || precision < 0) {
350
+ throw new Error("rectToPathData: precision must be non-negative");
351
+ }
278
352
  const xD = D(x),
279
353
  yD = D(y),
280
354
  wD = D(width),
281
355
  hD = D(height);
356
+ if (!xD.isFinite()) {
357
+ throw new Error("rectToPathData: x must be finite");
358
+ }
359
+ if (!yD.isFinite()) {
360
+ throw new Error("rectToPathData: y must be finite");
361
+ }
362
+ if (!wD.isFinite() || wD.isNegative()) {
363
+ throw new Error("rectToPathData: width must be finite and non-negative");
364
+ }
365
+ if (!hD.isFinite() || hD.isNegative()) {
366
+ throw new Error("rectToPathData: height must be finite and non-negative");
367
+ }
282
368
  let rxD = D(rx || 0),
283
369
  ryD = ry !== null ? D(ry) : rxD;
284
370
  const halfW = wD.div(2),
@@ -322,8 +408,18 @@ export function rectToPathData(
322
408
  * @returns {string} SVG path data string
323
409
  */
324
410
  export function lineToPathData(x1, y1, x2, y2, precision = 6) {
325
- const f = (v) => formatNumber(D(v), precision);
326
- return `M${f(x1)} ${f(y1)}L${f(x2)} ${f(y2)}`;
411
+ if (x1 == null || y1 == null || x2 == null || y2 == null) {
412
+ throw new Error("lineToPathData: x1, y1, x2, and y2 parameters are required");
413
+ }
414
+ if (!Number.isFinite(precision) || precision < 0) {
415
+ throw new Error("lineToPathData: precision must be non-negative");
416
+ }
417
+ const x1D = D(x1), y1D = D(y1), x2D = D(x2), y2D = D(y2);
418
+ if (!x1D.isFinite() || !y1D.isFinite() || !x2D.isFinite() || !y2D.isFinite()) {
419
+ throw new Error("lineToPathData: all coordinates must be finite");
420
+ }
421
+ const f = (v) => formatNumber(v, precision);
422
+ return `M${f(x1D)} ${f(y1D)}L${f(x2D)} ${f(y2D)}`;
327
423
  }
328
424
 
329
425
  /**
@@ -335,7 +431,13 @@ function parsePoints(points) {
335
431
  // Handle null/undefined
336
432
  if (points == null) return [];
337
433
  // Handle arrays
338
- if (Array.isArray(points)) return points.map(([x, y]) => [D(x), D(y)]);
434
+ if (Array.isArray(points)) {
435
+ try {
436
+ return points.map(([x, y]) => [D(x), D(y)]);
437
+ } catch (_e) {
438
+ return [];
439
+ }
440
+ }
339
441
  // Handle SVGAnimatedPoints or objects with baseVal
340
442
  let pointsValue = points;
341
443
  if (typeof pointsValue === "object" && pointsValue.baseVal !== undefined) {
@@ -349,10 +451,16 @@ function parsePoints(points) {
349
451
  return [];
350
452
  }
351
453
  }
352
- const nums = pointsValue
353
- .split(/[\s,]+/)
354
- .filter((s) => s.length > 0)
355
- .map((s) => D(s));
454
+ const nums = [];
455
+ const parts = pointsValue.split(/[\s,]+/).filter((s) => s.length > 0);
456
+ for (const s of parts) {
457
+ try {
458
+ nums.push(D(s));
459
+ } catch (_e) {
460
+ // Skip invalid numbers
461
+ continue;
462
+ }
463
+ }
356
464
  const pairs = [];
357
465
  for (let i = 0; i < nums.length; i += 2) {
358
466
  if (i + 1 < nums.length) pairs.push([nums[i], nums[i + 1]]);
@@ -367,6 +475,9 @@ function parsePoints(points) {
367
475
  * @returns {string} SVG path data string
368
476
  */
369
477
  export function polylineToPathData(points, precision = 6) {
478
+ if (!Number.isFinite(precision) || precision < 0) {
479
+ throw new Error("polylineToPathData: precision must be non-negative");
480
+ }
370
481
  const pairs = parsePoints(points);
371
482
  if (pairs.length === 0) return "";
372
483
  const f = (v) => formatNumber(v, precision);
@@ -386,6 +497,9 @@ export function polylineToPathData(points, precision = 6) {
386
497
  * @returns {string} SVG path data string with Z (closepath) command
387
498
  */
388
499
  export function polygonToPathData(points, precision = 6) {
500
+ if (!Number.isFinite(precision) || precision < 0) {
501
+ throw new Error("polygonToPathData: precision must be non-negative");
502
+ }
389
503
  const path = polylineToPathData(points, precision);
390
504
  return path ? path + " Z" : "";
391
505
  }
@@ -420,6 +534,12 @@ const COMMAND_PARAMS = {
420
534
  * @returns {Array<Object>} Array of {command, args} objects
421
535
  */
422
536
  export function parsePathData(pathData) {
537
+ if (pathData == null) {
538
+ throw new Error("parsePathData: pathData parameter is required");
539
+ }
540
+ if (typeof pathData !== "string") {
541
+ throw new Error("parsePathData: pathData must be a string");
542
+ }
423
543
  const commands = [];
424
544
  const commandRegex = /([MmLlHhVvCcSsQqTtAaZz])\s*([^MmLlHhVvCcSsQqTtAaZz]*)/g;
425
545
  let match;
@@ -430,10 +550,17 @@ export function parsePathData(pathData) {
430
550
  // FIX: Use regex to extract numbers, handles implicit negative separators (e.g., "0.8-2.9" -> ["0.8", "-2.9"])
431
551
  // Per W3C SVG spec, negative signs can act as delimiters without spaces
432
552
  const numRegex = /-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/g;
433
- const allArgs =
434
- argsStr.length > 0
435
- ? Array.from(argsStr.matchAll(numRegex), (m) => D(m[0]))
436
- : [];
553
+ const allArgs = [];
554
+ if (argsStr.length > 0) {
555
+ for (const m of argsStr.matchAll(numRegex)) {
556
+ try {
557
+ allArgs.push(D(m[0]));
558
+ } catch (_e) {
559
+ // Skip invalid numbers per SVG error handling
560
+ continue;
561
+ }
562
+ }
563
+ }
437
564
 
438
565
  const paramCount = COMMAND_PARAMS[command];
439
566
 
@@ -467,8 +594,21 @@ export function parsePathData(pathData) {
467
594
  * @returns {string} SVG path data string
468
595
  */
469
596
  export function pathArrayToString(commands, precision = 6) {
597
+ if (commands == null) {
598
+ throw new Error("pathArrayToString: commands parameter is required");
599
+ }
600
+ if (!Array.isArray(commands)) {
601
+ throw new Error("pathArrayToString: commands must be an array");
602
+ }
603
+ if (!Number.isFinite(precision) || precision < 0) {
604
+ throw new Error("pathArrayToString: precision must be non-negative");
605
+ }
470
606
  return commands
471
- .map(({ command, args }) => {
607
+ .map((cmd) => {
608
+ if (!cmd || typeof cmd.command !== "string" || !Array.isArray(cmd.args)) {
609
+ throw new Error("pathArrayToString: each command must have 'command' (string) and 'args' (array) properties");
610
+ }
611
+ const { command, args } = cmd;
472
612
  const argsStr = args.map((a) => formatNumber(a, precision)).join(" ");
473
613
  return argsStr.length > 0 ? `${command} ${argsStr}` : command;
474
614
  })
@@ -481,6 +621,9 @@ export function pathArrayToString(commands, precision = 6) {
481
621
  * @returns {string} Path data with all absolute commands
482
622
  */
483
623
  export function pathToAbsolute(pathData) {
624
+ if (pathData == null) {
625
+ throw new Error("pathToAbsolute: pathData parameter is required");
626
+ }
484
627
  const commands = parsePathData(pathData);
485
628
  const result = [];
486
629
  let currentX = new Decimal(0),
@@ -495,6 +638,7 @@ export function pathToAbsolute(pathData) {
495
638
  const isRelative = command === command.toLowerCase();
496
639
  const upperCmd = command.toUpperCase();
497
640
  if (upperCmd === "M") {
641
+ if (args.length < 2) continue; // Skip malformed command
498
642
  const x = isRelative ? currentX.plus(args[0]) : args[0];
499
643
  const y = isRelative ? currentY.plus(args[1]) : args[1];
500
644
  currentX = x;
@@ -504,6 +648,7 @@ export function pathToAbsolute(pathData) {
504
648
  result.push({ command: "M", args: [x, y] });
505
649
  lastCommand = "M";
506
650
  } else if (upperCmd === "L") {
651
+ if (args.length < 2) continue; // Skip malformed command
507
652
  const x = isRelative ? currentX.plus(args[0]) : args[0];
508
653
  const y = isRelative ? currentY.plus(args[1]) : args[1];
509
654
  currentX = x;
@@ -511,16 +656,19 @@ export function pathToAbsolute(pathData) {
511
656
  result.push({ command: "L", args: [x, y] });
512
657
  lastCommand = "L";
513
658
  } else if (upperCmd === "H") {
659
+ if (args.length < 1) continue; // Skip malformed command
514
660
  const x = isRelative ? currentX.plus(args[0]) : args[0];
515
661
  currentX = x;
516
662
  result.push({ command: "L", args: [x, currentY] });
517
663
  lastCommand = "H";
518
664
  } else if (upperCmd === "V") {
665
+ if (args.length < 1) continue; // Skip malformed command
519
666
  const y = isRelative ? currentY.plus(args[0]) : args[0];
520
667
  currentY = y;
521
668
  result.push({ command: "L", args: [currentX, y] });
522
669
  lastCommand = "V";
523
670
  } else if (upperCmd === "C") {
671
+ if (args.length < 6) continue; // Skip malformed command
524
672
  const x1 = isRelative ? currentX.plus(args[0]) : args[0];
525
673
  const y1 = isRelative ? currentY.plus(args[1]) : args[1];
526
674
  const x2 = isRelative ? currentX.plus(args[2]) : args[2];
@@ -534,6 +682,7 @@ export function pathToAbsolute(pathData) {
534
682
  result.push({ command: "C", args: [x1, y1, x2, y2, x, y] });
535
683
  lastCommand = "C";
536
684
  } else if (upperCmd === "S") {
685
+ if (args.length < 4) continue; // Skip malformed command
537
686
  // Smooth cubic Bezier: 4 args (x2, y2, x, y)
538
687
  // First control point is reflection of previous second control point
539
688
  let x1, y1;
@@ -555,6 +704,7 @@ export function pathToAbsolute(pathData) {
555
704
  result.push({ command: "C", args: [x1, y1, x2, y2, x, y] });
556
705
  lastCommand = "S";
557
706
  } else if (upperCmd === "Q") {
707
+ if (args.length < 4) continue; // Skip malformed command
558
708
  // Quadratic Bezier: 4 args (x1, y1, x, y)
559
709
  const x1 = isRelative ? currentX.plus(args[0]) : args[0];
560
710
  const y1 = isRelative ? currentY.plus(args[1]) : args[1];
@@ -567,6 +717,7 @@ export function pathToAbsolute(pathData) {
567
717
  result.push({ command: "Q", args: [x1, y1, x, y] });
568
718
  lastCommand = "Q";
569
719
  } else if (upperCmd === "T") {
720
+ if (args.length < 2) continue; // Skip malformed command
570
721
  // Smooth quadratic Bezier: 2 args (x, y)
571
722
  // Control point is reflection of previous control point
572
723
  let x1, y1;
@@ -586,6 +737,7 @@ export function pathToAbsolute(pathData) {
586
737
  result.push({ command: "Q", args: [x1, y1, x, y] });
587
738
  lastCommand = "T";
588
739
  } else if (upperCmd === "A") {
740
+ if (args.length < 7) continue; // Skip malformed command
589
741
  const x = isRelative ? currentX.plus(args[5]) : args[5];
590
742
  const y = isRelative ? currentY.plus(args[6]) : args[6];
591
743
  currentX = x;
@@ -630,6 +782,20 @@ export function transformArcParams(
630
782
  endY,
631
783
  matrix,
632
784
  ) {
785
+ if (rx == null || ry == null || xAxisRotation == null || largeArc == null || sweep == null || endX == null || endY == null) {
786
+ throw new Error("transformArcParams: all parameters (rx, ry, xAxisRotation, largeArc, sweep, endX, endY) are required");
787
+ }
788
+ if (matrix == null) {
789
+ throw new Error("transformArcParams: matrix parameter is required");
790
+ }
791
+ if (!matrix.data || !Array.isArray(matrix.data) || matrix.data.length !== 3) {
792
+ throw new Error("transformArcParams: matrix must be 3x3");
793
+ }
794
+ if (!Array.isArray(matrix.data[0]) || matrix.data[0].length < 2 ||
795
+ !Array.isArray(matrix.data[1]) || matrix.data[1].length < 2 ||
796
+ !Array.isArray(matrix.data[2]) || matrix.data[2].length < 1) {
797
+ throw new Error("transformArcParams: matrix must have valid 3x3 structure");
798
+ }
633
799
  const rxD = D(rx),
634
800
  ryD = D(ry),
635
801
  rotD = D(xAxisRotation);
@@ -639,8 +805,12 @@ export function transformArcParams(
639
805
  // Transform the endpoint
640
806
  const endPoint = Matrix.from([[endXD], [endYD], [new Decimal(1)]]);
641
807
  const transformedEnd = matrix.mul(endPoint);
642
- const newEndX = transformedEnd.data[0][0].div(transformedEnd.data[2][0]);
643
- const newEndY = transformedEnd.data[1][0].div(transformedEnd.data[2][0]);
808
+ const w = transformedEnd.data[2][0];
809
+ if (w.isZero()) {
810
+ throw new Error("transformArcParams: division by zero in homogeneous coordinate transformation");
811
+ }
812
+ const newEndX = transformedEnd.data[0][0].div(w);
813
+ const newEndY = transformedEnd.data[1][0].div(w);
644
814
 
645
815
  // Extract the 2x2 linear part of the affine transformation
646
816
  const a = matrix.data[0][0],
@@ -697,30 +867,54 @@ export function transformArcParams(
697
867
  * @returns {string} Transformed SVG path data
698
868
  */
699
869
  export function transformPathData(pathData, matrix, precision = 6) {
870
+ if (pathData == null) {
871
+ throw new Error("transformPathData: pathData parameter is required");
872
+ }
873
+ if (matrix == null) {
874
+ throw new Error("transformPathData: matrix parameter is required");
875
+ }
876
+ if (!matrix.data || !Array.isArray(matrix.data) || matrix.data.length !== 3) {
877
+ throw new Error("transformPathData: matrix must be 3x3");
878
+ }
879
+ if (!Array.isArray(matrix.data[0]) || matrix.data[0].length < 2 ||
880
+ !Array.isArray(matrix.data[1]) || matrix.data[1].length < 2 ||
881
+ !Array.isArray(matrix.data[2]) || matrix.data[2].length < 1) {
882
+ throw new Error("transformPathData: matrix must have valid 3x3 structure");
883
+ }
884
+ if (!Number.isFinite(precision) || precision < 0) {
885
+ throw new Error("transformPathData: precision must be non-negative");
886
+ }
700
887
  const absPath = pathToAbsolute(pathData);
701
888
  const commands = parsePathData(absPath);
702
889
  const result = [];
703
890
  for (const { command, args } of commands) {
704
891
  if (command === "M" || command === "L") {
892
+ if (args.length < 2) continue; // Skip malformed command
705
893
  const pt = Matrix.from([[args[0]], [args[1]], [new Decimal(1)]]);
706
894
  const transformed = matrix.mul(pt);
707
- const x = transformed.data[0][0].div(transformed.data[2][0]);
708
- const y = transformed.data[1][0].div(transformed.data[2][0]);
895
+ const w = transformed.data[2][0];
896
+ if (w.isZero()) {
897
+ throw new Error("transformPathData: division by zero in homogeneous coordinate transformation");
898
+ }
899
+ const x = transformed.data[0][0].div(w);
900
+ const y = transformed.data[1][0].div(w);
709
901
  result.push({ command, args: [x, y] });
710
902
  } else if (command === "C") {
903
+ if (args.length < 6) continue; // Skip malformed command
711
904
  const transformedArgs = [];
712
905
  for (let i = 0; i < 6; i += 2) {
713
906
  const pt = Matrix.from([[args[i]], [args[i + 1]], [new Decimal(1)]]);
714
907
  const transformed = matrix.mul(pt);
715
- transformedArgs.push(
716
- transformed.data[0][0].div(transformed.data[2][0]),
717
- );
718
- transformedArgs.push(
719
- transformed.data[1][0].div(transformed.data[2][0]),
720
- );
908
+ const w = transformed.data[2][0];
909
+ if (w.isZero()) {
910
+ throw new Error("transformPathData: division by zero in homogeneous coordinate transformation");
911
+ }
912
+ transformedArgs.push(transformed.data[0][0].div(w));
913
+ transformedArgs.push(transformed.data[1][0].div(w));
721
914
  }
722
915
  result.push({ command, args: transformedArgs });
723
916
  } else if (command === "A") {
917
+ if (args.length < 7) continue; // Skip malformed command
724
918
  const [newRx, newRy, newRot, newLarge, newSweep, newEndX, newEndY] =
725
919
  transformArcParams(
726
920
  args[0],
@@ -754,6 +948,9 @@ export function transformPathData(pathData, matrix, precision = 6) {
754
948
  * @returns {Array<Decimal>} Cubic Bezier control points [cp1x, cp1y, cp2x, cp2y, x2, y2]
755
949
  */
756
950
  function quadraticToCubic(x0, y0, x1, y1, x2, y2) {
951
+ if (x0 == null || y0 == null || x1 == null || y1 == null || x2 == null || y2 == null) {
952
+ throw new Error("quadraticToCubic: all parameters (x0, y0, x1, y1, x2, y2) are required");
953
+ }
757
954
  const twoThirds = new Decimal(2).div(3);
758
955
  const cp1x = x0.plus(twoThirds.mul(x1.minus(x0)));
759
956
  const cp1y = y0.plus(twoThirds.mul(y1.minus(y0)));
@@ -768,6 +965,9 @@ function quadraticToCubic(x0, y0, x1, y1, x2, y2) {
768
965
  * @returns {string} Path data with only M, C, and Z commands
769
966
  */
770
967
  export function pathToCubics(pathData) {
968
+ if (pathData == null) {
969
+ throw new Error("pathToCubics: pathData parameter is required");
970
+ }
771
971
  const absPath = pathToAbsolute(pathData);
772
972
  const commands = parsePathData(absPath);
773
973
  const result = [];
@@ -778,11 +978,13 @@ export function pathToCubics(pathData) {
778
978
  let lastCommand = "";
779
979
  for (const { command, args } of commands) {
780
980
  if (command === "M") {
981
+ if (args.length < 2) continue; // Skip malformed command
781
982
  currentX = args[0];
782
983
  currentY = args[1];
783
984
  result.push({ command: "M", args: [currentX, currentY] });
784
985
  lastCommand = "M";
785
986
  } else if (command === "L") {
987
+ if (args.length < 2) continue; // Skip malformed command
786
988
  const x = args[0],
787
989
  y = args[1];
788
990
  result.push({ command: "C", args: [currentX, currentY, x, y, x, y] });
@@ -790,6 +992,7 @@ export function pathToCubics(pathData) {
790
992
  currentY = y;
791
993
  lastCommand = "L";
792
994
  } else if (command === "C") {
995
+ if (args.length < 6) continue; // Skip malformed command
793
996
  const [x1, y1, x2, y2, x, y] = args;
794
997
  result.push({ command: "C", args: [x1, y1, x2, y2, x, y] });
795
998
  lastControlX = x2;
@@ -798,6 +1001,7 @@ export function pathToCubics(pathData) {
798
1001
  currentY = y;
799
1002
  lastCommand = "C";
800
1003
  } else if (command === "S") {
1004
+ if (args.length < 4) continue; // Skip malformed command
801
1005
  let x1, y1;
802
1006
  if (lastCommand === "C" || lastCommand === "S") {
803
1007
  x1 = currentX.mul(2).minus(lastControlX);
@@ -814,6 +1018,7 @@ export function pathToCubics(pathData) {
814
1018
  currentY = y;
815
1019
  lastCommand = "S";
816
1020
  } else if (command === "Q") {
1021
+ if (args.length < 4) continue; // Skip malformed command
817
1022
  const [x1, y1, x, y] = args;
818
1023
  const cubic = quadraticToCubic(currentX, currentY, x1, y1, x, y);
819
1024
  result.push({ command: "C", args: cubic });
@@ -823,6 +1028,7 @@ export function pathToCubics(pathData) {
823
1028
  currentY = y;
824
1029
  lastCommand = "Q";
825
1030
  } else if (command === "T") {
1031
+ if (args.length < 2) continue; // Skip malformed command
826
1032
  let x1, y1;
827
1033
  if (lastCommand === "Q" || lastCommand === "T") {
828
1034
  x1 = currentX.mul(2).minus(lastControlX);
@@ -857,6 +1063,12 @@ export function pathToCubics(pathData) {
857
1063
  * @returns {string|null} SVG path data string, or null if element type not supported
858
1064
  */
859
1065
  export function convertElementToPath(element, precision = 6) {
1066
+ if (element == null) {
1067
+ throw new Error("convertElementToPath: element parameter is required");
1068
+ }
1069
+ if (!Number.isFinite(precision) || precision < 0) {
1070
+ throw new Error("convertElementToPath: precision must be non-negative");
1071
+ }
860
1072
  const getAttr = (name, defaultValue = 0) => {
861
1073
  const rawValue = element.getAttribute
862
1074
  ? element.getAttribute(name)
@@ -916,8 +1128,21 @@ export function convertElementToPath(element, precision = 6) {
916
1128
  * @returns {number} Numeric value without units
917
1129
  */
918
1130
  function stripUnits(val) {
1131
+ if (val == null) {
1132
+ return 0;
1133
+ }
919
1134
  if (typeof val === "string") {
920
- return parseFloat(val) || 0;
1135
+ const parsed = parseFloat(val);
1136
+ return isNaN(parsed) ? 0 : parsed;
1137
+ }
1138
+ if (typeof val === "number") {
1139
+ return isNaN(val) ? 0 : val;
1140
+ }
1141
+ // Handle Decimal type
1142
+ if (val instanceof Decimal) {
1143
+ return val.toNumber();
921
1144
  }
922
- return val;
1145
+ // Fallback: try to convert to number
1146
+ const num = Number(val);
1147
+ return isNaN(num) ? 0 : num;
923
1148
  }