@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
@@ -63,8 +63,14 @@ export function identityMatrix() {
63
63
  * @param {number|string|Decimal} tx - X translation
64
64
  * @param {number|string|Decimal} ty - Y translation
65
65
  * @returns {Matrix} 3x3 translation matrix
66
+ * @throws {TypeError} If tx or ty is null or undefined
66
67
  */
67
68
  export function translationMatrix(tx, ty) {
69
+ // Parameter validation: why - D() cannot convert null/undefined to meaningful values
70
+ if (tx == null)
71
+ throw new TypeError("translationMatrix: tx parameter is required");
72
+ if (ty == null)
73
+ throw new TypeError("translationMatrix: ty parameter is required");
68
74
  return Matrix.from([
69
75
  [1, 0, D(tx)],
70
76
  [0, 1, D(ty)],
@@ -76,8 +82,12 @@ export function translationMatrix(tx, ty) {
76
82
  * Create a 2D rotation matrix.
77
83
  * @param {number|string|Decimal} angle - Rotation angle in radians
78
84
  * @returns {Matrix} 3x3 rotation matrix
85
+ * @throws {TypeError} If angle is null or undefined
79
86
  */
80
87
  export function rotationMatrix(angle) {
88
+ // Parameter validation: why - D() cannot convert null/undefined to meaningful values
89
+ if (angle == null)
90
+ throw new TypeError("rotationMatrix: angle parameter is required");
81
91
  const theta = D(angle);
82
92
  const cos = Decimal.cos(theta);
83
93
  const sin = Decimal.sin(theta);
@@ -93,8 +103,12 @@ export function rotationMatrix(angle) {
93
103
  * @param {number|string|Decimal} sx - X scale factor
94
104
  * @param {number|string|Decimal} sy - Y scale factor
95
105
  * @returns {Matrix} 3x3 scale matrix
106
+ * @throws {TypeError} If sx or sy is null or undefined
96
107
  */
97
108
  export function scaleMatrix(sx, sy) {
109
+ // Parameter validation: why - D() cannot convert null/undefined to meaningful values
110
+ if (sx == null) throw new TypeError("scaleMatrix: sx parameter is required");
111
+ if (sy == null) throw new TypeError("scaleMatrix: sy parameter is required");
98
112
  return Matrix.from([
99
113
  [D(sx), 0, 0],
100
114
  [0, D(sy), 0],
@@ -106,8 +120,12 @@ export function scaleMatrix(sx, sy) {
106
120
  * Create a 2D skewX matrix.
107
121
  * @param {number|string|Decimal} angle - Skew angle in radians
108
122
  * @returns {Matrix} 3x3 skewX matrix
123
+ * @throws {TypeError} If angle is null or undefined
109
124
  */
110
125
  export function skewXMatrix(angle) {
126
+ // Parameter validation: why - D() cannot convert null/undefined to meaningful values
127
+ if (angle == null)
128
+ throw new TypeError("skewXMatrix: angle parameter is required");
111
129
  const tan = Decimal.tan(D(angle));
112
130
  return Matrix.from([
113
131
  [1, tan, 0],
@@ -120,8 +138,12 @@ export function skewXMatrix(angle) {
120
138
  * Create a 2D skewY matrix.
121
139
  * @param {number|string|Decimal} angle - Skew angle in radians
122
140
  * @returns {Matrix} 3x3 skewY matrix
141
+ * @throws {TypeError} If angle is null or undefined
123
142
  */
124
143
  export function skewYMatrix(angle) {
144
+ // Parameter validation: why - D() cannot convert null/undefined to meaningful values
145
+ if (angle == null)
146
+ throw new TypeError("skewYMatrix: angle parameter is required");
125
147
  const tan = Decimal.tan(D(angle));
126
148
  return Matrix.from([
127
149
  [1, 0, 0],
@@ -134,8 +156,19 @@ export function skewYMatrix(angle) {
134
156
  * Extract the 2x2 linear transformation submatrix from a 3x3 affine matrix.
135
157
  * @param {Matrix} matrix - 3x3 affine transformation matrix
136
158
  * @returns {{a: Decimal, b: Decimal, c: Decimal, d: Decimal}}
159
+ * @throws {TypeError} If matrix is null or undefined
160
+ * @throws {RangeError} If matrix is not at least 2x2
137
161
  */
138
162
  export function extractLinearPart(matrix) {
163
+ // Parameter validation: why - accessing matrix.data without validation will crash on null
164
+ if (!matrix)
165
+ throw new TypeError("extractLinearPart: matrix parameter is required");
166
+ if (!matrix.data)
167
+ throw new TypeError("extractLinearPart: matrix must have data property");
168
+ // Bounds checking: why - accessing out of bounds indices will return undefined causing errors
169
+ if (matrix.rows < 2 || matrix.cols < 2) {
170
+ throw new RangeError("extractLinearPart: matrix must be at least 2x2");
171
+ }
139
172
  const data = matrix.data;
140
173
  return {
141
174
  a: data[0][0],
@@ -149,8 +182,19 @@ export function extractLinearPart(matrix) {
149
182
  * Extract the translation from a 3x3 affine matrix.
150
183
  * @param {Matrix} matrix - 3x3 affine transformation matrix
151
184
  * @returns {{tx: Decimal, ty: Decimal}}
185
+ * @throws {TypeError} If matrix is null or undefined
186
+ * @throws {RangeError} If matrix is not at least 2x3
152
187
  */
153
188
  export function extractTranslation(matrix) {
189
+ // Parameter validation: why - accessing matrix.data without validation will crash on null
190
+ if (!matrix)
191
+ throw new TypeError("extractTranslation: matrix parameter is required");
192
+ if (!matrix.data)
193
+ throw new TypeError("extractTranslation: matrix must have data property");
194
+ // Bounds checking: why - accessing out of bounds indices will return undefined causing errors
195
+ if (matrix.rows < 2 || matrix.cols < 3) {
196
+ throw new RangeError("extractTranslation: matrix must be at least 2x3");
197
+ }
154
198
  const data = matrix.data;
155
199
  return {
156
200
  tx: data[0][2],
@@ -183,11 +227,15 @@ export function extractTranslation(matrix) {
183
227
  * skewY: Decimal,
184
228
  * verified: boolean,
185
229
  * maxError?: Decimal,
186
- * verificationError?: Decimal,
187
230
  * singular?: boolean
188
231
  * }}
232
+ * @throws {TypeError} If matrix is null or undefined
189
233
  */
190
234
  export function decomposeMatrix(matrix) {
235
+ // Parameter validation: why - extractLinearPart/extractTranslation will validate, but we document it here
236
+ if (!matrix)
237
+ throw new TypeError("decomposeMatrix: matrix parameter is required");
238
+
191
239
  const { a, b, c, d } = extractLinearPart(matrix);
192
240
  const { tx, ty } = extractTranslation(matrix);
193
241
 
@@ -197,40 +245,33 @@ export function decomposeMatrix(matrix) {
197
245
  // Calculate scale factors using column norms
198
246
  let scaleX = a.mul(a).plus(b.mul(b)).sqrt();
199
247
 
200
- // BUG FIX 1: Check for singular matrix (scaleX = 0) before division
248
+ // Check for singular matrix (scaleX = 0) before division: why - prevents division by zero
201
249
  if (scaleX.abs().lessThan(EPSILON)) {
202
250
  // Handle degenerate/singular matrix case (e.g., scale(0, y))
203
251
  return {
204
- translateX: D(0),
205
- translateY: D(0),
252
+ translateX: tx, // BUG FIX: preserve translation from input matrix, not zero
253
+ translateY: ty, // BUG FIX: preserve translation from input matrix, not zero
206
254
  rotation: D(0),
207
255
  scaleX: D(0),
208
256
  scaleY: D(0),
209
257
  skewX: D(0),
210
258
  skewY: D(0),
211
259
  verified: false,
212
- verificationError: D("Infinity"),
260
+ maxError: D("Infinity"), // Consistent with normal return path property name
213
261
  singular: true, // Flag to indicate singular matrix
214
262
  };
215
263
  }
216
264
 
217
265
  const scaleY = det.div(scaleX);
218
266
 
219
- // Handle reflection (negative determinant)
267
+ // Handle reflection (negative determinant): why - negative determinant means reflection
220
268
  // We put the reflection in scaleX
221
269
  if (det.lessThan(0)) {
222
270
  scaleX = scaleX.neg();
223
271
  }
224
272
 
225
- // Calculate rotation angle
226
- // theta = atan2(b, a) for the first column after normalization
227
- let rotation;
228
- if (scaleX.abs().lessThan(EPSILON)) {
229
- // Degenerate case: first column is zero
230
- rotation = Decimal.atan2(d, c);
231
- } else {
232
- rotation = Decimal.atan2(b, a);
233
- }
273
+ // Calculate rotation angle from first column
274
+ const rotation = Decimal.atan2(b, a);
234
275
 
235
276
  // Calculate skew
236
277
  // After removing rotation and scale, we get the skew
@@ -248,17 +289,15 @@ export function decomposeMatrix(matrix) {
248
289
  const cosTheta = Decimal.cos(rotation);
249
290
  const sinTheta = Decimal.sin(rotation);
250
291
 
251
- // Rotate back to get the scale+skew matrix
292
+ // Rotate back to get the scale+skew matrix: why - separates rotation from scale+skew
252
293
  const aPrime = a.mul(cosTheta).plus(b.mul(sinTheta));
253
- const _bPrime = a.neg().mul(sinTheta).plus(b.mul(cosTheta));
254
294
  const cPrime = c.mul(cosTheta).plus(d.mul(sinTheta));
255
- const _dPrime = c.neg().mul(sinTheta).plus(d.mul(cosTheta));
295
+ // Note: bPrime and dPrime are not used in this decomposition approach
256
296
 
257
297
  // Now [aPrime cPrime] should be [scaleX, scaleX*tan(skewX)]
258
298
  // [bPrime dPrime] [0, scaleY ]
259
299
 
260
- // BUG FIX 2: Calculate skewX from tan(skewX) = cPrime/aPrime
261
- // Note: atan2 gives the angle of a vector, but we need atan of the ratio
300
+ // Calculate skewX from tan(skewX) = cPrime/aPrime
262
301
  let skewX;
263
302
  if (aPrime.abs().greaterThan(EPSILON)) {
264
303
  // skewX = atan(cPrime/aPrime) because tan(skewX) = cPrime/aPrime
@@ -268,7 +307,6 @@ export function decomposeMatrix(matrix) {
268
307
  skewX = D(0);
269
308
  }
270
309
 
271
- // BUG FIX 3: Document skewY limitation
272
310
  // LIMITATION: This decomposition order (T * R * SkX * S) can only handle skewX.
273
311
  // A matrix with both skewX and skewY cannot be uniquely decomposed into this form.
274
312
  // To decompose matrices with skewY, a different decomposition order would be needed
@@ -276,13 +314,6 @@ export function decomposeMatrix(matrix) {
276
314
  // For standard 2D affine transforms, skewY is typically 0.
277
315
  const skewY = D(0);
278
316
 
279
- // Recalculate scaleY from dPrime
280
- // dPrime should equal scaleY after removing skew
281
- const cosSkewX = Decimal.cos(skewX);
282
- if (cosSkewX.abs().greaterThan(EPSILON)) {
283
- // scaleY = dPrime / cos(skewX) - but we already have it from det/scaleX
284
- }
285
-
286
317
  // VERIFICATION: Recompose and compare
287
318
  const recomposed = composeTransform({
288
319
  translateX: tx,
@@ -324,23 +355,44 @@ export function decomposeMatrix(matrix) {
324
355
  * scaleX: Decimal,
325
356
  * scaleY: Decimal,
326
357
  * verified: boolean,
327
- * maxError: Decimal
358
+ * maxError: Decimal,
359
+ * singular?: boolean
328
360
  * }}
361
+ * @throws {TypeError} If matrix is null or undefined
329
362
  */
330
363
  export function decomposeMatrixNoSkew(matrix) {
364
+ // Parameter validation: why - extractLinearPart/extractTranslation will validate, but we document it here
365
+ if (!matrix)
366
+ throw new TypeError("decomposeMatrixNoSkew: matrix parameter is required");
367
+
331
368
  const { a, b, c, d } = extractLinearPart(matrix);
332
369
  const { tx, ty } = extractTranslation(matrix);
333
370
 
334
371
  // Calculate determinant
335
372
  const det = a.mul(d).minus(b.mul(c));
336
373
 
337
- // Calculate rotation from the first column
338
- const rotation = Decimal.atan2(b, a);
339
-
340
374
  // Calculate scales
341
375
  const scaleX = a.mul(a).plus(b.mul(b)).sqrt();
342
376
  let scaleY = c.mul(c).plus(d.mul(d)).sqrt();
343
377
 
378
+ // Check for singular matrix (scaleX or scaleY near zero): why - prevents invalid decomposition
379
+ if (scaleX.abs().lessThan(EPSILON) || scaleY.abs().lessThan(EPSILON)) {
380
+ // Handle degenerate/singular matrix case
381
+ return {
382
+ translateX: tx,
383
+ translateY: ty,
384
+ rotation: D(0),
385
+ scaleX: scaleX.abs().lessThan(EPSILON) ? D(0) : scaleX,
386
+ scaleY: scaleY.abs().lessThan(EPSILON) ? D(0) : scaleY,
387
+ verified: false,
388
+ maxError: D("Infinity"),
389
+ singular: true, // Flag to indicate singular matrix
390
+ };
391
+ }
392
+
393
+ // Calculate rotation from the first column
394
+ const rotation = Decimal.atan2(b, a);
395
+
344
396
  // Adjust scaleY sign based on determinant
345
397
  if (det.lessThan(0)) {
346
398
  scaleY = scaleY.neg();
@@ -425,8 +477,25 @@ export function decomposeMatrixSVG(matrix) {
425
477
  * skewY?: number|string|Decimal
426
478
  * }} components - Transform components
427
479
  * @returns {Matrix} 3x3 transformation matrix
480
+ * @throws {TypeError} If components is null or missing required properties
428
481
  */
429
482
  export function composeTransform(components) {
483
+ // Parameter validation: why - accessing properties of null/undefined will crash
484
+ if (!components)
485
+ throw new TypeError("composeTransform: components parameter is required");
486
+ if (components.translateX == null)
487
+ throw new TypeError("composeTransform: components.translateX is required");
488
+ if (components.translateY == null)
489
+ throw new TypeError("composeTransform: components.translateY is required");
490
+ if (components.rotation == null)
491
+ throw new TypeError("composeTransform: components.rotation is required");
492
+ if (components.scaleX == null)
493
+ throw new TypeError("composeTransform: components.scaleX is required");
494
+ if (components.scaleY == null)
495
+ throw new TypeError("composeTransform: components.scaleY is required");
496
+ if (components.skewX == null)
497
+ throw new TypeError("composeTransform: components.skewX is required");
498
+
430
499
  const {
431
500
  translateX,
432
501
  translateY,
@@ -460,8 +529,35 @@ export function composeTransform(components) {
460
529
  * scaleY: number|string|Decimal
461
530
  * }} components - Transform components
462
531
  * @returns {Matrix} 3x3 transformation matrix
532
+ * @throws {TypeError} If components is null or missing required properties
463
533
  */
464
534
  export function composeTransformNoSkew(components) {
535
+ // Parameter validation: why - accessing properties of null/undefined will crash
536
+ if (!components)
537
+ throw new TypeError(
538
+ "composeTransformNoSkew: components parameter is required",
539
+ );
540
+ if (components.translateX == null)
541
+ throw new TypeError(
542
+ "composeTransformNoSkew: components.translateX is required",
543
+ );
544
+ if (components.translateY == null)
545
+ throw new TypeError(
546
+ "composeTransformNoSkew: components.translateY is required",
547
+ );
548
+ if (components.rotation == null)
549
+ throw new TypeError(
550
+ "composeTransformNoSkew: components.rotation is required",
551
+ );
552
+ if (components.scaleX == null)
553
+ throw new TypeError(
554
+ "composeTransformNoSkew: components.scaleX is required",
555
+ );
556
+ if (components.scaleY == null)
557
+ throw new TypeError(
558
+ "composeTransformNoSkew: components.scaleY is required",
559
+ );
560
+
465
561
  const { translateX, translateY, rotation, scaleX, scaleY } = components;
466
562
 
467
563
  const T = translationMatrix(translateX, translateY);
@@ -481,8 +577,24 @@ export function composeTransformNoSkew(components) {
481
577
  * @param {Matrix} m1 - First matrix
482
578
  * @param {Matrix} m2 - Second matrix
483
579
  * @returns {Decimal} Maximum absolute difference
580
+ * @throws {TypeError} If m1 or m2 is null or undefined
581
+ * @throws {RangeError} If matrices have different dimensions
484
582
  */
485
583
  export function matrixMaxDifference(m1, m2) {
584
+ // Parameter validation: why - accessing properties of null/undefined will crash
585
+ if (!m1) throw new TypeError("matrixMaxDifference: m1 parameter is required");
586
+ if (!m2) throw new TypeError("matrixMaxDifference: m2 parameter is required");
587
+ if (!m1.data)
588
+ throw new TypeError("matrixMaxDifference: m1 must have data property");
589
+ if (!m2.data)
590
+ throw new TypeError("matrixMaxDifference: m2 must have data property");
591
+ // Dimension validation: why - comparing different sized matrices makes no sense
592
+ if (m1.rows !== m2.rows || m1.cols !== m2.cols) {
593
+ throw new RangeError(
594
+ `matrixMaxDifference: matrices must have same dimensions (${m1.rows}x${m1.cols} vs ${m2.rows}x${m2.cols})`,
595
+ );
596
+ }
597
+
486
598
  let maxDiff = D(0);
487
599
 
488
600
  for (let i = 0; i < m1.rows; i++) {
@@ -504,8 +616,13 @@ export function matrixMaxDifference(m1, m2) {
504
616
  * @param {Matrix} m2 - Second matrix
505
617
  * @param {Decimal} [tolerance=VERIFICATION_TOLERANCE] - Maximum allowed difference
506
618
  * @returns {boolean} True if matrices are equal within tolerance
619
+ * @throws {TypeError} If tolerance is explicitly null (not undefined)
507
620
  */
508
621
  export function matricesEqual(m1, m2, tolerance = VERIFICATION_TOLERANCE) {
622
+ // Parameter validation: why - D() cannot convert null to meaningful value
623
+ if (tolerance === null) {
624
+ throw new TypeError("matricesEqual: tolerance cannot be null");
625
+ }
509
626
  return matrixMaxDifference(m1, m2).lessThan(D(tolerance));
510
627
  }
511
628
 
@@ -515,8 +632,17 @@ export function matricesEqual(m1, m2, tolerance = VERIFICATION_TOLERANCE) {
515
632
  * @param {Matrix} original - Original matrix
516
633
  * @param {Object} decomposition - Decomposition result from decomposeMatrix
517
634
  * @returns {{verified: boolean, maxError: Decimal}}
635
+ * @throws {TypeError} If original or decomposition is null or undefined
518
636
  */
519
637
  export function verifyDecomposition(original, decomposition) {
638
+ // Parameter validation: why - prevents crashes when accessing properties
639
+ if (!original)
640
+ throw new TypeError("verifyDecomposition: original parameter is required");
641
+ if (!decomposition)
642
+ throw new TypeError(
643
+ "verifyDecomposition: decomposition parameter is required",
644
+ );
645
+
520
646
  const recomposed = composeTransform(decomposition);
521
647
  const maxError = matrixMaxDifference(original, recomposed);
522
648
  return {
@@ -544,8 +670,23 @@ export function verifyDecomposition(original, decomposition) {
544
670
  * @param {number|string|Decimal} e - Translate X
545
671
  * @param {number|string|Decimal} f - Translate Y
546
672
  * @returns {Matrix} 3x3 transformation matrix
673
+ * @throws {TypeError} If any parameter is null or undefined
547
674
  */
548
675
  export function matrixFromSVGValues(a, b, c, d, e, f) {
676
+ // Parameter validation: why - D() cannot convert null/undefined to meaningful values
677
+ if (a == null)
678
+ throw new TypeError("matrixFromSVGValues: a parameter is required");
679
+ if (b == null)
680
+ throw new TypeError("matrixFromSVGValues: b parameter is required");
681
+ if (c == null)
682
+ throw new TypeError("matrixFromSVGValues: c parameter is required");
683
+ if (d == null)
684
+ throw new TypeError("matrixFromSVGValues: d parameter is required");
685
+ if (e == null)
686
+ throw new TypeError("matrixFromSVGValues: e parameter is required");
687
+ if (f == null)
688
+ throw new TypeError("matrixFromSVGValues: f parameter is required");
689
+
549
690
  return Matrix.from([
550
691
  [D(a), D(c), D(e)],
551
692
  [D(b), D(d), D(f)],
@@ -559,8 +700,26 @@ export function matrixFromSVGValues(a, b, c, d, e, f) {
559
700
  * @param {Matrix} matrix - 3x3 transformation matrix
560
701
  * @param {number} [precision=6] - Decimal places for output
561
702
  * @returns {{a: string, b: string, c: string, d: string, e: string, f: string}}
703
+ * @throws {TypeError} If matrix is null or undefined
704
+ * @throws {RangeError} If matrix is not at least 2x3 or precision is negative
562
705
  */
563
706
  export function matrixToSVGValues(matrix, precision = 6) {
707
+ // Parameter validation: why - accessing matrix.data without validation will crash on null
708
+ if (!matrix)
709
+ throw new TypeError("matrixToSVGValues: matrix parameter is required");
710
+ if (!matrix.data)
711
+ throw new TypeError("matrixToSVGValues: matrix must have data property");
712
+ // Bounds checking: why - accessing out of bounds indices will return undefined causing errors
713
+ if (matrix.rows < 2 || matrix.cols < 3) {
714
+ throw new RangeError("matrixToSVGValues: matrix must be at least 2x3");
715
+ }
716
+ // Precision validation: why - negative precision makes no sense
717
+ if (precision < 0 || !Number.isInteger(precision)) {
718
+ throw new RangeError(
719
+ "matrixToSVGValues: precision must be a non-negative integer",
720
+ );
721
+ }
722
+
564
723
  const data = matrix.data;
565
724
  return {
566
725
  a: data[0][0].toFixed(precision),
@@ -578,8 +737,42 @@ export function matrixToSVGValues(matrix, precision = 6) {
578
737
  * @param {Object} decomposition - Decomposition result
579
738
  * @param {number} [precision=6] - Decimal places for output
580
739
  * @returns {string} SVG transform string
740
+ * @throws {TypeError} If decomposition is null or missing required properties
741
+ * @throws {RangeError} If precision is negative
581
742
  */
582
743
  export function decompositionToSVGString(decomposition, precision = 6) {
744
+ // Parameter validation: why - accessing properties of null/undefined will crash
745
+ if (!decomposition)
746
+ throw new TypeError(
747
+ "decompositionToSVGString: decomposition parameter is required",
748
+ );
749
+ if (decomposition.translateX == null)
750
+ throw new TypeError(
751
+ "decompositionToSVGString: decomposition.translateX is required",
752
+ );
753
+ if (decomposition.translateY == null)
754
+ throw new TypeError(
755
+ "decompositionToSVGString: decomposition.translateY is required",
756
+ );
757
+ if (decomposition.rotation == null)
758
+ throw new TypeError(
759
+ "decompositionToSVGString: decomposition.rotation is required",
760
+ );
761
+ if (decomposition.scaleX == null)
762
+ throw new TypeError(
763
+ "decompositionToSVGString: decomposition.scaleX is required",
764
+ );
765
+ if (decomposition.scaleY == null)
766
+ throw new TypeError(
767
+ "decompositionToSVGString: decomposition.scaleY is required",
768
+ );
769
+ // Precision validation: why - negative precision makes no sense
770
+ if (precision < 0 || !Number.isInteger(precision)) {
771
+ throw new RangeError(
772
+ "decompositionToSVGString: precision must be a non-negative integer",
773
+ );
774
+ }
775
+
583
776
  const { translateX, translateY, rotation, scaleX, scaleY, skewX, skewY } =
584
777
  decomposition;
585
778
 
@@ -639,8 +832,22 @@ export function decompositionToSVGString(decomposition, precision = 6) {
639
832
  * @param {Matrix} matrix - 3x3 transformation matrix
640
833
  * @param {number} [precision=6] - Decimal places for output
641
834
  * @returns {{transform: string, isIdentity: boolean, verified: boolean}}
835
+ * @throws {TypeError} If matrix is null or undefined
836
+ * @throws {RangeError} If precision is negative
642
837
  */
643
838
  export function matrixToMinimalSVGTransform(matrix, precision = 6) {
839
+ // Parameter validation: why - prevents crashes when accessing properties
840
+ if (!matrix)
841
+ throw new TypeError(
842
+ "matrixToMinimalSVGTransform: matrix parameter is required",
843
+ );
844
+ // Precision validation: why - negative precision makes no sense
845
+ if (precision < 0 || !Number.isInteger(precision)) {
846
+ throw new RangeError(
847
+ "matrixToMinimalSVGTransform: precision must be a non-negative integer",
848
+ );
849
+ }
850
+
644
851
  // Check if identity
645
852
  const identity = Matrix.identity(3);
646
853
  if (matricesEqual(matrix, identity, EPSILON)) {
@@ -669,8 +876,13 @@ export function matrixToMinimalSVGTransform(matrix, precision = 6) {
669
876
  *
670
877
  * @param {Matrix} matrix - 3x3 transformation matrix
671
878
  * @returns {{isTranslation: boolean, tx: Decimal, ty: Decimal}}
879
+ * @throws {TypeError} If matrix is null or undefined
672
880
  */
673
881
  export function isPureTranslation(matrix) {
882
+ // Parameter validation: why - extractLinearPart/extractTranslation will validate, but we document it here
883
+ if (!matrix)
884
+ throw new TypeError("isPureTranslation: matrix parameter is required");
885
+
674
886
  const { a, b, c, d } = extractLinearPart(matrix);
675
887
  const { tx, ty } = extractTranslation(matrix);
676
888
 
@@ -692,8 +904,13 @@ export function isPureTranslation(matrix) {
692
904
  *
693
905
  * @param {Matrix} matrix - 3x3 transformation matrix
694
906
  * @returns {{isRotation: boolean, angle: Decimal}}
907
+ * @throws {TypeError} If matrix is null or undefined
695
908
  */
696
909
  export function isPureRotation(matrix) {
910
+ // Parameter validation: why - extractLinearPart/extractTranslation will validate, but we document it here
911
+ if (!matrix)
912
+ throw new TypeError("isPureRotation: matrix parameter is required");
913
+
697
914
  const { a, b, c, d } = extractLinearPart(matrix);
698
915
  const { tx, ty } = extractTranslation(matrix);
699
916
 
@@ -729,8 +946,12 @@ export function isPureRotation(matrix) {
729
946
  *
730
947
  * @param {Matrix} matrix - 3x3 transformation matrix
731
948
  * @returns {{isScale: boolean, scaleX: Decimal, scaleY: Decimal, isUniform: boolean}}
949
+ * @throws {TypeError} If matrix is null or undefined
732
950
  */
733
951
  export function isPureScale(matrix) {
952
+ // Parameter validation: why - extractLinearPart/extractTranslation will validate, but we document it here
953
+ if (!matrix) throw new TypeError("isPureScale: matrix parameter is required");
954
+
734
955
  const { a, b, c, d } = extractLinearPart(matrix);
735
956
  const { tx, ty } = extractTranslation(matrix);
736
957
 
@@ -759,8 +980,13 @@ export function isPureScale(matrix) {
759
980
  *
760
981
  * @param {Matrix} matrix - 3x3 transformation matrix
761
982
  * @returns {boolean} True if identity
983
+ * @throws {TypeError} If matrix is null or undefined
762
984
  */
763
985
  export function isIdentityMatrix(matrix) {
986
+ // Parameter validation: why - matricesEqual will validate, but we document it here
987
+ if (!matrix)
988
+ throw new TypeError("isIdentityMatrix: matrix parameter is required");
989
+
764
990
  const identity = Matrix.identity(3);
765
991
  return matricesEqual(matrix, identity, EPSILON);
766
992
  }