@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
@@ -70,6 +70,8 @@ const DEFAULT_TOLERANCE = new Decimal('1e-10');
70
70
  * @returns {{x: Decimal, y: Decimal}} Point object
71
71
  */
72
72
  export function point(x, y) {
73
+ if (x === null || x === undefined) throw new Error('point: x coordinate is required');
74
+ if (y === null || y === undefined) throw new Error('point: y coordinate is required');
73
75
  return { x: D(x), y: D(y) };
74
76
  }
75
77
 
@@ -80,8 +82,16 @@ export function point(x, y) {
80
82
  * @returns {Decimal} Distance
81
83
  */
82
84
  export function distance(p1, p2) {
83
- const dx = p2.x.minus(p1.x);
84
- const dy = p2.y.minus(p1.y);
85
+ if (!p1 || !p2) throw new Error('distance: both points are required');
86
+ if (p1.x === undefined || p1.y === undefined) throw new Error('distance: p1 must have x and y properties');
87
+ if (p2.x === undefined || p2.y === undefined) throw new Error('distance: p2 must have x and y properties');
88
+ // Ensure coordinates are Decimal objects
89
+ const x1 = p1.x instanceof Decimal ? p1.x : D(p1.x);
90
+ const y1 = p1.y instanceof Decimal ? p1.y : D(p1.y);
91
+ const x2 = p2.x instanceof Decimal ? p2.x : D(p2.x);
92
+ const y2 = p2.y instanceof Decimal ? p2.y : D(p2.y);
93
+ const dx = x2.minus(x1);
94
+ const dy = y2.minus(y1);
85
95
  return dx.mul(dx).plus(dy.mul(dy)).sqrt();
86
96
  }
87
97
 
@@ -93,8 +103,16 @@ export function distance(p1, p2) {
93
103
  * @returns {boolean} True if points are equal
94
104
  */
95
105
  export function pointsEqual(p1, p2, tolerance = EPSILON) {
106
+ if (!p1 || !p2) throw new Error('pointsEqual: both points are required');
107
+ if (p1.x === undefined || p1.y === undefined) throw new Error('pointsEqual: p1 must have x and y properties');
108
+ if (p2.x === undefined || p2.y === undefined) throw new Error('pointsEqual: p2 must have x and y properties');
96
109
  const tol = D(tolerance);
97
- return p1.x.minus(p2.x).abs().lessThan(tol) && p1.y.minus(p2.y).abs().lessThan(tol);
110
+ // Ensure coordinates are Decimal objects
111
+ const x1 = p1.x instanceof Decimal ? p1.x : D(p1.x);
112
+ const y1 = p1.y instanceof Decimal ? p1.y : D(p1.y);
113
+ const x2 = p2.x instanceof Decimal ? p2.x : D(p2.x);
114
+ const y2 = p2.y instanceof Decimal ? p2.y : D(p2.y);
115
+ return x1.minus(x2).abs().lessThan(tol) && y1.minus(y2).abs().lessThan(tol);
98
116
  }
99
117
 
100
118
  // ============================================================================
@@ -113,7 +131,16 @@ export function pointsEqual(p1, p2, tolerance = EPSILON) {
113
131
  * @returns {{x: Decimal, y: Decimal}} Point on curve
114
132
  */
115
133
  export function evaluateCubicBezier(p0, p1, p2, p3, t) {
134
+ if (!p0 || !p1 || !p2 || !p3) throw new Error('evaluateCubicBezier: all points are required');
135
+ if (p0.x === undefined || p0.y === undefined) throw new Error('evaluateCubicBezier: p0 must have x and y properties');
136
+ if (p1.x === undefined || p1.y === undefined) throw new Error('evaluateCubicBezier: p1 must have x and y properties');
137
+ if (p2.x === undefined || p2.y === undefined) throw new Error('evaluateCubicBezier: p2 must have x and y properties');
138
+ if (p3.x === undefined || p3.y === undefined) throw new Error('evaluateCubicBezier: p3 must have x and y properties');
139
+ if (t === null || t === undefined) throw new Error('evaluateCubicBezier: parameter t is required');
140
+
116
141
  const tD = D(t);
142
+ // Validate t is within valid range [0, 1] for Bezier curves
143
+ if (tD.lessThan(0) || tD.greaterThan(1)) throw new Error('evaluateCubicBezier: parameter t must be between 0 and 1');
117
144
  const oneMinusT = D(1).minus(tD);
118
145
 
119
146
  // Bernstein basis polynomials
@@ -139,7 +166,15 @@ export function evaluateCubicBezier(p0, p1, p2, p3, t) {
139
166
  * @returns {{x: Decimal, y: Decimal}} Point on curve
140
167
  */
141
168
  export function evaluateQuadraticBezier(p0, p1, p2, t) {
169
+ if (!p0 || !p1 || !p2) throw new Error('evaluateQuadraticBezier: all points are required');
170
+ if (p0.x === undefined || p0.y === undefined) throw new Error('evaluateQuadraticBezier: p0 must have x and y properties');
171
+ if (p1.x === undefined || p1.y === undefined) throw new Error('evaluateQuadraticBezier: p1 must have x and y properties');
172
+ if (p2.x === undefined || p2.y === undefined) throw new Error('evaluateQuadraticBezier: p2 must have x and y properties');
173
+ if (t === null || t === undefined) throw new Error('evaluateQuadraticBezier: parameter t is required');
174
+
142
175
  const tD = D(t);
176
+ // Validate t is within valid range [0, 1] for Bezier curves
177
+ if (tD.lessThan(0) || tD.greaterThan(1)) throw new Error('evaluateQuadraticBezier: parameter t must be between 0 and 1');
143
178
  const oneMinusT = D(1).minus(tD);
144
179
 
145
180
  // Bernstein basis polynomials
@@ -170,6 +205,11 @@ export function evaluateQuadraticBezier(p0, p1, p2, t) {
170
205
  * @returns {{canConvert: boolean, endX: Decimal, verified: boolean}} Conversion result
171
206
  */
172
207
  export function lineToHorizontal(x1, y1, x2, y2, tolerance = EPSILON) {
208
+ if (x1 === null || x1 === undefined) throw new Error('lineToHorizontal: x1 is required');
209
+ if (y1 === null || y1 === undefined) throw new Error('lineToHorizontal: y1 is required');
210
+ if (x2 === null || x2 === undefined) throw new Error('lineToHorizontal: x2 is required');
211
+ if (y2 === null || y2 === undefined) throw new Error('lineToHorizontal: y2 is required');
212
+
173
213
  const tol = D(tolerance);
174
214
  const startY = D(y1);
175
215
  const endY = D(y2);
@@ -203,6 +243,11 @@ export function lineToHorizontal(x1, y1, x2, y2, tolerance = EPSILON) {
203
243
  * @returns {{canConvert: boolean, endY: Decimal, verified: boolean}} Conversion result
204
244
  */
205
245
  export function lineToVertical(x1, y1, x2, y2, tolerance = EPSILON) {
246
+ if (x1 === null || x1 === undefined) throw new Error('lineToVertical: x1 is required');
247
+ if (y1 === null || y1 === undefined) throw new Error('lineToVertical: y1 is required');
248
+ if (x2 === null || x2 === undefined) throw new Error('lineToVertical: x2 is required');
249
+ if (y2 === null || y2 === undefined) throw new Error('lineToVertical: y2 is required');
250
+
206
251
  const tol = D(tolerance);
207
252
  const startX = D(x1);
208
253
  const endX = D(x2);
@@ -236,6 +281,10 @@ export function lineToVertical(x1, y1, x2, y2, tolerance = EPSILON) {
236
281
  * @returns {{x: Decimal, y: Decimal}} Reflected point
237
282
  */
238
283
  export function reflectPoint(control, center) {
284
+ if (!control || !center) throw new Error('reflectPoint: both control and center points are required');
285
+ if (control.x === undefined || control.y === undefined) throw new Error('reflectPoint: control must have x and y properties');
286
+ if (center.x === undefined || center.y === undefined) throw new Error('reflectPoint: center must have x and y properties');
287
+
239
288
  // Reflection formula: reflected = center + (center - control) = 2*center - control
240
289
  return {
241
290
  x: D(2).mul(center.x).minus(control.x),
@@ -266,6 +315,15 @@ export function reflectPoint(control, center) {
266
315
  * @returns {{canConvert: boolean, cp2X: Decimal, cp2Y: Decimal, endX: Decimal, endY: Decimal, maxDeviation: Decimal, verified: boolean}}
267
316
  */
268
317
  export function curveToSmooth(prevControl, x0, y0, x1, y1, x2, y2, x3, y3, tolerance = DEFAULT_TOLERANCE) {
318
+ if (x0 === null || x0 === undefined) throw new Error('curveToSmooth: x0 is required');
319
+ if (y0 === null || y0 === undefined) throw new Error('curveToSmooth: y0 is required');
320
+ if (x1 === null || x1 === undefined) throw new Error('curveToSmooth: x1 is required');
321
+ if (y1 === null || y1 === undefined) throw new Error('curveToSmooth: y1 is required');
322
+ if (x2 === null || x2 === undefined) throw new Error('curveToSmooth: x2 is required');
323
+ if (y2 === null || y2 === undefined) throw new Error('curveToSmooth: y2 is required');
324
+ if (x3 === null || x3 === undefined) throw new Error('curveToSmooth: x3 is required');
325
+ if (y3 === null || y3 === undefined) throw new Error('curveToSmooth: y3 is required');
326
+
269
327
  const tol = D(tolerance);
270
328
  const p0 = point(x0, y0);
271
329
  const p1 = point(x1, y1);
@@ -351,6 +409,13 @@ export function curveToSmooth(prevControl, x0, y0, x1, y1, x2, y2, x3, y3, toler
351
409
  * @returns {{canConvert: boolean, endX: Decimal, endY: Decimal, maxDeviation: Decimal, verified: boolean}}
352
410
  */
353
411
  export function quadraticToSmooth(prevControl, x0, y0, x1, y1, x2, y2, tolerance = DEFAULT_TOLERANCE) {
412
+ if (x0 === null || x0 === undefined) throw new Error('quadraticToSmooth: x0 is required');
413
+ if (y0 === null || y0 === undefined) throw new Error('quadraticToSmooth: y0 is required');
414
+ if (x1 === null || x1 === undefined) throw new Error('quadraticToSmooth: x1 is required');
415
+ if (y1 === null || y1 === undefined) throw new Error('quadraticToSmooth: y1 is required');
416
+ if (x2 === null || x2 === undefined) throw new Error('quadraticToSmooth: x2 is required');
417
+ if (y2 === null || y2 === undefined) throw new Error('quadraticToSmooth: y2 is required');
418
+
354
419
  const tol = D(tolerance);
355
420
  const p0 = point(x0, y0);
356
421
  const p1 = point(x1, y1);
@@ -417,7 +482,13 @@ export function quadraticToSmooth(prevControl, x0, y0, x1, y1, x2, y2, tolerance
417
482
  * Used to avoid infinite recursion in verification.
418
483
  */
419
484
  function _toRelativeArgs(cmd, args, cx, cy) {
485
+ if (!cmd || typeof cmd !== 'string') throw new Error('_toRelativeArgs: cmd must be a non-empty string');
486
+ if (!Array.isArray(args)) throw new Error('_toRelativeArgs: args must be an array');
487
+ if (cx === null || cx === undefined) throw new Error('_toRelativeArgs: cx is required');
488
+ if (cy === null || cy === undefined) throw new Error('_toRelativeArgs: cy is required');
489
+
420
490
  if (cmd === 'M' || cmd === 'L' || cmd === 'T') {
491
+ if (args.length % 2 !== 0) throw new Error(`_toRelativeArgs: ${cmd} requires pairs of coordinates, got ${args.length} args`);
421
492
  const relativeArgs = [];
422
493
  for (let i = 0; i < args.length; i += 2) {
423
494
  relativeArgs.push(args[i].minus(cx));
@@ -432,6 +503,7 @@ function _toRelativeArgs(cmd, args, cx, cy) {
432
503
  return args.map(y => y.minus(cy));
433
504
  }
434
505
  if (cmd === 'C' || cmd === 'S' || cmd === 'Q') {
506
+ if (args.length % 2 !== 0) throw new Error(`_toRelativeArgs: ${cmd} requires pairs of coordinates, got ${args.length} args`);
435
507
  const relativeArgs = [];
436
508
  for (let i = 0; i < args.length; i += 2) {
437
509
  relativeArgs.push(args[i].minus(cx));
@@ -440,6 +512,7 @@ function _toRelativeArgs(cmd, args, cx, cy) {
440
512
  return relativeArgs;
441
513
  }
442
514
  if (cmd === 'A') {
515
+ if (args.length % 7 !== 0) throw new Error(`_toRelativeArgs: A requires groups of 7 args, got ${args.length} args`);
443
516
  const relativeArgs = [];
444
517
  for (let i = 0; i < args.length; i += 7) {
445
518
  relativeArgs.push(args[i]);
@@ -460,7 +533,13 @@ function _toRelativeArgs(cmd, args, cx, cy) {
460
533
  * Used to avoid infinite recursion in verification.
461
534
  */
462
535
  function _toAbsoluteArgs(cmd, args, cx, cy) {
536
+ if (!cmd || typeof cmd !== 'string') throw new Error('_toAbsoluteArgs: cmd must be a non-empty string');
537
+ if (!Array.isArray(args)) throw new Error('_toAbsoluteArgs: args must be an array');
538
+ if (cx === null || cx === undefined) throw new Error('_toAbsoluteArgs: cx is required');
539
+ if (cy === null || cy === undefined) throw new Error('_toAbsoluteArgs: cy is required');
540
+
463
541
  if (cmd === 'm' || cmd === 'l' || cmd === 't') {
542
+ if (args.length % 2 !== 0) throw new Error(`_toAbsoluteArgs: ${cmd} requires pairs of coordinates, got ${args.length} args`);
464
543
  const absoluteArgs = [];
465
544
  for (let i = 0; i < args.length; i += 2) {
466
545
  absoluteArgs.push(args[i].plus(cx));
@@ -475,6 +554,7 @@ function _toAbsoluteArgs(cmd, args, cx, cy) {
475
554
  return args.map(dy => dy.plus(cy));
476
555
  }
477
556
  if (cmd === 'c' || cmd === 's' || cmd === 'q') {
557
+ if (args.length % 2 !== 0) throw new Error(`_toAbsoluteArgs: ${cmd} requires pairs of coordinates, got ${args.length} args`);
478
558
  const absoluteArgs = [];
479
559
  for (let i = 0; i < args.length; i += 2) {
480
560
  absoluteArgs.push(args[i].plus(cx));
@@ -483,6 +563,7 @@ function _toAbsoluteArgs(cmd, args, cx, cy) {
483
563
  return absoluteArgs;
484
564
  }
485
565
  if (cmd === 'a') {
566
+ if (args.length % 7 !== 0) throw new Error(`_toAbsoluteArgs: a requires groups of 7 args, got ${args.length} args`);
486
567
  const absoluteArgs = [];
487
568
  for (let i = 0; i < args.length; i += 7) {
488
569
  absoluteArgs.push(args[i]);
@@ -510,6 +591,12 @@ function _toAbsoluteArgs(cmd, args, cx, cy) {
510
591
  * @returns {{command: string, args: Array<Decimal>, verified: boolean}}
511
592
  */
512
593
  export function toRelative(command, currentX, currentY) {
594
+ if (!command) throw new Error('toRelative: command object is required');
595
+ if (!command.command) throw new Error('toRelative: command.command is required');
596
+ if (!Array.isArray(command.args)) throw new Error('toRelative: command.args must be an array');
597
+ if (currentX === null || currentX === undefined) throw new Error('toRelative: currentX is required');
598
+ if (currentY === null || currentY === undefined) throw new Error('toRelative: currentY is required');
599
+
513
600
  const cmd = command.command;
514
601
  const args = command.args.map(D);
515
602
  const cx = D(currentX);
@@ -566,6 +653,12 @@ export function toRelative(command, currentX, currentY) {
566
653
  * @returns {{command: string, args: Array<Decimal>, verified: boolean}}
567
654
  */
568
655
  export function toAbsolute(command, currentX, currentY) {
656
+ if (!command) throw new Error('toAbsolute: command object is required');
657
+ if (!command.command) throw new Error('toAbsolute: command.command is required');
658
+ if (!Array.isArray(command.args)) throw new Error('toAbsolute: command.args must be an array');
659
+ if (currentX === null || currentX === undefined) throw new Error('toAbsolute: currentX is required');
660
+ if (currentY === null || currentY === undefined) throw new Error('toAbsolute: currentY is required');
661
+
569
662
  const cmd = command.command;
570
663
  const args = command.args.map(D);
571
664
  const cx = D(currentX);
@@ -622,9 +715,18 @@ export function toAbsolute(command, currentX, currentY) {
622
715
  * @returns {string} Formatted command string
623
716
  */
624
717
  function formatCommand(command, precision = 6) {
718
+ if (!command) throw new Error('formatCommand: command object is required');
719
+ if (!command.command) throw new Error('formatCommand: command.command is required');
720
+ if (!Array.isArray(command.args)) throw new Error('formatCommand: command.args must be an array');
721
+ if (typeof precision !== 'number' || precision < 0) throw new Error('formatCommand: precision must be a non-negative number');
722
+
625
723
  const cmd = command.command;
626
- const args = command.args.map(arg => {
627
- const num = arg.toNumber();
724
+ const args = command.args.map((arg, index) => {
725
+ // Convert to Decimal if not already, then to number
726
+ const argDecimal = arg instanceof Decimal ? arg : D(arg);
727
+ const num = argDecimal.toNumber();
728
+ // Validate the number is finite
729
+ if (!Number.isFinite(num)) throw new Error(`formatCommand: argument at index ${index} converted to non-finite number: ${num}`);
628
730
  // Format with specified precision and remove trailing zeros
629
731
  return parseFloat(num.toFixed(precision)).toString();
630
732
  }).join(',');
@@ -643,6 +745,14 @@ function formatCommand(command, precision = 6) {
643
745
  * @returns {{command: string, args: Array<Decimal>, isShorter: boolean, savedBytes: number, verified: boolean}}
644
746
  */
645
747
  export function chooseShorterForm(absCommand, relCommand, precision = 6) {
748
+ if (!absCommand) throw new Error('chooseShorterForm: absCommand is required');
749
+ if (!absCommand.command) throw new Error('chooseShorterForm: absCommand.command is required');
750
+ if (!Array.isArray(absCommand.args)) throw new Error('chooseShorterForm: absCommand.args must be an array');
751
+ if (!relCommand) throw new Error('chooseShorterForm: relCommand is required');
752
+ if (!relCommand.command) throw new Error('chooseShorterForm: relCommand.command is required');
753
+ if (!Array.isArray(relCommand.args)) throw new Error('chooseShorterForm: relCommand.args must be an array');
754
+ if (typeof precision !== 'number' || precision < 0) throw new Error('chooseShorterForm: precision must be a non-negative number');
755
+
646
756
  const absStr = formatCommand({ command: absCommand.command, args: absCommand.args.map(D) }, precision);
647
757
  const relStr = formatCommand({ command: relCommand.command, args: relCommand.args.map(D) }, precision);
648
758
 
@@ -690,6 +800,15 @@ export function chooseShorterForm(absCommand, relCommand, precision = 6) {
690
800
  * @returns {{commands: Array<{command: string, args: Array<Decimal>}>, collapseCount: number, verified: boolean}}
691
801
  */
692
802
  export function collapseRepeated(commands) {
803
+ if (!Array.isArray(commands)) throw new Error('collapseRepeated: commands must be an array');
804
+
805
+ // Validate each command object
806
+ for (let i = 0; i < commands.length; i++) {
807
+ if (!commands[i]) throw new Error(`collapseRepeated: command at index ${i} is null or undefined`);
808
+ if (!commands[i].command) throw new Error(`collapseRepeated: command at index ${i} is missing 'command' property`);
809
+ if (!Array.isArray(commands[i].args)) throw new Error(`collapseRepeated: command at index ${i} is missing 'args' array property`);
810
+ }
811
+
693
812
  if (commands.length < 2) {
694
813
  return {
695
814
  commands: commands.map(cmd => ({ command: cmd.command, args: cmd.args.map(D) })),
@@ -759,6 +878,11 @@ export function collapseRepeated(commands) {
759
878
  * @returns {{canConvert: boolean, deviation: Decimal, verified: boolean}}
760
879
  */
761
880
  export function lineToZ(lastX, lastY, startX, startY, tolerance = EPSILON) {
881
+ if (lastX === null || lastX === undefined) throw new Error('lineToZ: lastX is required');
882
+ if (lastY === null || lastY === undefined) throw new Error('lineToZ: lastY is required');
883
+ if (startX === null || startX === undefined) throw new Error('lineToZ: startX is required');
884
+ if (startY === null || startY === undefined) throw new Error('lineToZ: startY is required');
885
+
762
886
  const tol = D(tolerance);
763
887
  const last = point(lastX, lastY);
764
888
  const start = point(startX, startY);