@emasoft/svg-matrix 1.0.27 → 1.0.29

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 (46) hide show
  1. package/README.md +325 -0
  2. package/bin/svg-matrix.js +994 -378
  3. package/bin/svglinter.cjs +4172 -433
  4. package/bin/svgm.js +744 -184
  5. package/package.json +16 -4
  6. package/src/animation-references.js +71 -52
  7. package/src/arc-length.js +160 -96
  8. package/src/bezier-analysis.js +257 -117
  9. package/src/bezier-intersections.js +411 -148
  10. package/src/browser-verify.js +240 -100
  11. package/src/clip-path-resolver.js +350 -142
  12. package/src/convert-path-data.js +279 -134
  13. package/src/css-specificity.js +78 -70
  14. package/src/flatten-pipeline.js +751 -263
  15. package/src/geometry-to-path.js +511 -182
  16. package/src/index.js +191 -46
  17. package/src/inkscape-support.js +404 -0
  18. package/src/marker-resolver.js +278 -164
  19. package/src/mask-resolver.js +209 -98
  20. package/src/matrix.js +147 -67
  21. package/src/mesh-gradient.js +187 -96
  22. package/src/off-canvas-detection.js +201 -104
  23. package/src/path-analysis.js +187 -107
  24. package/src/path-data-plugins.js +628 -167
  25. package/src/path-simplification.js +0 -1
  26. package/src/pattern-resolver.js +125 -88
  27. package/src/polygon-clip.js +111 -66
  28. package/src/svg-boolean-ops.js +194 -118
  29. package/src/svg-collections.js +48 -19
  30. package/src/svg-flatten.js +282 -164
  31. package/src/svg-parser.js +427 -200
  32. package/src/svg-rendering-context.js +147 -104
  33. package/src/svg-toolbox.js +16411 -3298
  34. package/src/svg2-polyfills.js +114 -245
  35. package/src/transform-decomposition.js +46 -41
  36. package/src/transform-optimization.js +89 -68
  37. package/src/transforms2d.js +49 -16
  38. package/src/transforms3d.js +58 -22
  39. package/src/use-symbol-resolver.js +150 -110
  40. package/src/vector.js +67 -15
  41. package/src/vendor/README.md +110 -0
  42. package/src/vendor/inkscape-hatch-polyfill.js +401 -0
  43. package/src/vendor/inkscape-hatch-polyfill.min.js +8 -0
  44. package/src/vendor/inkscape-mesh-polyfill.js +843 -0
  45. package/src/vendor/inkscape-mesh-polyfill.min.js +8 -0
  46. package/src/verification.js +288 -124
@@ -31,20 +31,20 @@
31
31
  * @module transform-optimization
32
32
  */
33
33
 
34
- import Decimal from 'decimal.js';
35
- import { Matrix } from './matrix.js';
34
+ import Decimal from "decimal.js";
35
+ import { Matrix } from "./matrix.js";
36
36
 
37
37
  // Set high precision for all calculations
38
38
  Decimal.set({ precision: 80 });
39
39
 
40
40
  // Helper to convert to Decimal
41
- const D = x => (x instanceof Decimal ? x : new Decimal(x));
41
+ const D = (x) => (x instanceof Decimal ? x : new Decimal(x));
42
42
 
43
43
  // Near-zero threshold for comparisons
44
- const EPSILON = new Decimal('1e-40');
44
+ const EPSILON = new Decimal("1e-40");
45
45
 
46
46
  // Verification tolerance (larger than EPSILON for practical use)
47
- const VERIFICATION_TOLERANCE = new Decimal('1e-30');
47
+ const VERIFICATION_TOLERANCE = new Decimal("1e-30");
48
48
 
49
49
  // ============================================================================
50
50
  // Matrix Utilities (imported patterns)
@@ -68,7 +68,7 @@ export function translationMatrix(tx, ty) {
68
68
  return Matrix.from([
69
69
  [1, 0, D(tx)],
70
70
  [0, 1, D(ty)],
71
- [0, 0, 1]
71
+ [0, 0, 1],
72
72
  ]);
73
73
  }
74
74
 
@@ -84,7 +84,7 @@ export function rotationMatrix(angle) {
84
84
  return Matrix.from([
85
85
  [cos, sin.neg(), 0],
86
86
  [sin, cos, 0],
87
- [0, 0, 1]
87
+ [0, 0, 1],
88
88
  ]);
89
89
  }
90
90
 
@@ -114,7 +114,7 @@ export function scaleMatrix(sx, sy) {
114
114
  return Matrix.from([
115
115
  [D(sx), 0, 0],
116
116
  [0, D(sy), 0],
117
- [0, 0, 1]
117
+ [0, 0, 1],
118
118
  ]);
119
119
  }
120
120
 
@@ -194,7 +194,7 @@ export function mergeTranslations(t1, t2) {
194
194
  tx,
195
195
  ty,
196
196
  verified,
197
- maxError
197
+ maxError,
198
198
  };
199
199
  }
200
200
 
@@ -248,7 +248,7 @@ export function mergeRotations(r1, r2) {
248
248
  return {
249
249
  angle: normalizedAngle,
250
250
  verified,
251
- maxError
251
+ maxError,
252
252
  };
253
253
  }
254
254
 
@@ -292,7 +292,7 @@ export function mergeScales(s1, s2) {
292
292
  sx,
293
293
  sy,
294
294
  verified,
295
- maxError
295
+ maxError,
296
296
  };
297
297
  }
298
298
 
@@ -341,7 +341,7 @@ export function matrixToTranslate(matrix) {
341
341
  tx: null,
342
342
  ty: null,
343
343
  verified: false,
344
- maxError: D(0)
344
+ maxError: D(0),
345
345
  };
346
346
  }
347
347
 
@@ -359,7 +359,7 @@ export function matrixToTranslate(matrix) {
359
359
  tx,
360
360
  ty,
361
361
  verified,
362
- maxError
362
+ maxError,
363
363
  };
364
364
  }
365
365
 
@@ -410,7 +410,7 @@ export function matrixToRotate(matrix) {
410
410
  isRotation: false,
411
411
  angle: null,
412
412
  verified: false,
413
- maxError: D(0)
413
+ maxError: D(0),
414
414
  };
415
415
  }
416
416
 
@@ -420,7 +420,8 @@ export function matrixToRotate(matrix) {
420
420
  // Check unit columns: a² + b² = 1, c² + d² = 1
421
421
  const col1Norm = a.mul(a).plus(b.mul(b));
422
422
  const col2Norm = c.mul(c).plus(d.mul(d));
423
- const unitNorm = col1Norm.minus(1).abs().lessThan(EPSILON) &&
423
+ const unitNorm =
424
+ col1Norm.minus(1).abs().lessThan(EPSILON) &&
424
425
  col2Norm.minus(1).abs().lessThan(EPSILON);
425
426
 
426
427
  // Check determinant = 1 (no reflection)
@@ -432,7 +433,7 @@ export function matrixToRotate(matrix) {
432
433
  isRotation: false,
433
434
  angle: null,
434
435
  verified: false,
435
- maxError: D(0)
436
+ maxError: D(0),
436
437
  };
437
438
  }
438
439
 
@@ -448,7 +449,7 @@ export function matrixToRotate(matrix) {
448
449
  isRotation: true,
449
450
  angle,
450
451
  verified,
451
- maxError
452
+ maxError,
452
453
  };
453
454
  }
454
455
 
@@ -501,7 +502,7 @@ export function matrixToScale(matrix) {
501
502
  sy: null,
502
503
  isUniform: false,
503
504
  verified: false,
504
- maxError: D(0)
505
+ maxError: D(0),
505
506
  };
506
507
  }
507
508
 
@@ -513,7 +514,7 @@ export function matrixToScale(matrix) {
513
514
  sy: null,
514
515
  isUniform: false,
515
516
  verified: false,
516
- maxError: D(0)
517
+ maxError: D(0),
517
518
  };
518
519
  }
519
520
 
@@ -535,7 +536,7 @@ export function matrixToScale(matrix) {
535
536
  sy,
536
537
  isUniform,
537
538
  verified,
538
- maxError
539
+ maxError,
539
540
  };
540
541
  }
541
542
 
@@ -574,28 +575,31 @@ export function removeIdentityTransforms(transforms) {
574
575
  const PI = Decimal.acos(-1);
575
576
  const TWO_PI = PI.mul(2);
576
577
 
577
- const filtered = transforms.filter(t => {
578
+ const filtered = transforms.filter((t) => {
578
579
  switch (t.type) {
579
- case 'translate': {
580
+ case "translate": {
580
581
  const tx = D(t.params.tx);
581
582
  const ty = D(t.params.ty);
582
583
  return !tx.abs().lessThan(EPSILON) || !ty.abs().lessThan(EPSILON);
583
584
  }
584
585
 
585
- case 'rotate': {
586
+ case "rotate": {
586
587
  const angle = D(t.params.angle);
587
588
  // Normalize angle to [0, 2π)
588
589
  const normalized = angle.mod(TWO_PI);
589
590
  return !normalized.abs().lessThan(EPSILON);
590
591
  }
591
592
 
592
- case 'scale': {
593
+ case "scale": {
593
594
  const sx = D(t.params.sx);
594
595
  const sy = D(t.params.sy);
595
- return !sx.minus(1).abs().lessThan(EPSILON) || !sy.minus(1).abs().lessThan(EPSILON);
596
+ return (
597
+ !sx.minus(1).abs().lessThan(EPSILON) ||
598
+ !sy.minus(1).abs().lessThan(EPSILON)
599
+ );
596
600
  }
597
601
 
598
- case 'matrix': {
602
+ case "matrix": {
599
603
  // Check if matrix is identity
600
604
  const m = t.params.matrix;
601
605
  const identity = identityMatrix();
@@ -610,7 +614,7 @@ export function removeIdentityTransforms(transforms) {
610
614
 
611
615
  return {
612
616
  transforms: filtered,
613
- removedCount: transforms.length - filtered.length
617
+ removedCount: transforms.length - filtered.length,
614
618
  };
615
619
  }
616
620
 
@@ -670,7 +674,7 @@ export function shortRotate(translateX, translateY, angle, centerX, centerY) {
670
674
  cx: cxD,
671
675
  cy: cyD,
672
676
  verified,
673
- maxError
677
+ maxError,
674
678
  };
675
679
  }
676
680
 
@@ -712,20 +716,24 @@ export function optimizeTransformList(transforms) {
712
716
  for (const t of transforms) {
713
717
  let m;
714
718
  switch (t.type) {
715
- case 'translate':
719
+ case "translate":
716
720
  m = translationMatrix(t.params.tx, t.params.ty);
717
721
  break;
718
- case 'rotate':
722
+ case "rotate":
719
723
  if (t.params.cx !== undefined && t.params.cy !== undefined) {
720
- m = rotationMatrixAroundPoint(t.params.angle, t.params.cx, t.params.cy);
724
+ m = rotationMatrixAroundPoint(
725
+ t.params.angle,
726
+ t.params.cx,
727
+ t.params.cy,
728
+ );
721
729
  } else {
722
730
  m = rotationMatrix(t.params.angle);
723
731
  }
724
732
  break;
725
- case 'scale':
733
+ case "scale":
726
734
  m = scaleMatrix(t.params.sx, t.params.sy);
727
735
  break;
728
- case 'matrix':
736
+ case "matrix":
729
737
  m = t.params.matrix;
730
738
  break;
731
739
  default:
@@ -735,8 +743,9 @@ export function optimizeTransformList(transforms) {
735
743
  }
736
744
 
737
745
  // Step 1: Remove identity transforms
738
- const { transforms: step1, removedCount } = removeIdentityTransforms(transforms);
739
- let optimized = step1.slice();
746
+ const { transforms: step1, removedCount: _removedCount } =
747
+ removeIdentityTransforms(transforms);
748
+ const optimized = step1.slice();
740
749
 
741
750
  // Step 2: Merge consecutive transforms of the same type
742
751
  let i = 0;
@@ -747,31 +756,36 @@ export function optimizeTransformList(transforms) {
747
756
  // Try to merge
748
757
  let merged = null;
749
758
 
750
- if (current.type === 'translate' && next.type === 'translate') {
759
+ if (current.type === "translate" && next.type === "translate") {
751
760
  const result = mergeTranslations(current.params, next.params);
752
761
  if (result.verified) {
753
762
  merged = {
754
- type: 'translate',
755
- params: { tx: result.tx, ty: result.ty }
763
+ type: "translate",
764
+ params: { tx: result.tx, ty: result.ty },
756
765
  };
757
766
  }
758
- } else if (current.type === 'rotate' && next.type === 'rotate') {
767
+ } else if (current.type === "rotate" && next.type === "rotate") {
759
768
  // Only merge if both are around origin
760
- if (!current.params.cx && !current.params.cy && !next.params.cx && !next.params.cy) {
769
+ if (
770
+ !current.params.cx &&
771
+ !current.params.cy &&
772
+ !next.params.cx &&
773
+ !next.params.cy
774
+ ) {
761
775
  const result = mergeRotations(current.params, next.params);
762
776
  if (result.verified) {
763
777
  merged = {
764
- type: 'rotate',
765
- params: { angle: result.angle }
778
+ type: "rotate",
779
+ params: { angle: result.angle },
766
780
  };
767
781
  }
768
782
  }
769
- } else if (current.type === 'scale' && next.type === 'scale') {
783
+ } else if (current.type === "scale" && next.type === "scale") {
770
784
  const result = mergeScales(current.params, next.params);
771
785
  if (result.verified) {
772
786
  merged = {
773
- type: 'scale',
774
- params: { sx: result.sx, sy: result.sy }
787
+ type: "scale",
788
+ params: { sx: result.sx, sy: result.sy },
775
789
  };
776
790
  }
777
791
  }
@@ -792,20 +806,27 @@ export function optimizeTransformList(transforms) {
792
806
  const t2 = optimized[i + 1];
793
807
  const t3 = optimized[i + 2];
794
808
 
795
- if (t1.type === 'translate' && t2.type === 'rotate' && t3.type === 'translate') {
809
+ if (
810
+ t1.type === "translate" &&
811
+ t2.type === "rotate" &&
812
+ t3.type === "translate"
813
+ ) {
796
814
  // Check if t3 is inverse of t1
797
815
  const tx1 = D(t1.params.tx);
798
816
  const ty1 = D(t1.params.ty);
799
817
  const tx3 = D(t3.params.tx);
800
818
  const ty3 = D(t3.params.ty);
801
819
 
802
- if (tx1.plus(tx3).abs().lessThan(EPSILON) && ty1.plus(ty3).abs().lessThan(EPSILON)) {
820
+ if (
821
+ tx1.plus(tx3).abs().lessThan(EPSILON) &&
822
+ ty1.plus(ty3).abs().lessThan(EPSILON)
823
+ ) {
803
824
  // This is a rotate around point pattern
804
825
  const result = shortRotate(tx1, ty1, t2.params.angle, tx1, ty1);
805
826
  if (result.verified) {
806
827
  const merged = {
807
- type: 'rotate',
808
- params: { angle: result.angle, cx: result.cx, cy: result.cy }
828
+ type: "rotate",
829
+ params: { angle: result.angle, cx: result.cx, cy: result.cy },
809
830
  };
810
831
  optimized.splice(i, 3, merged);
811
832
  // Don't increment i, might be able to merge more
@@ -820,15 +841,15 @@ export function optimizeTransformList(transforms) {
820
841
  // Step 4: Convert matrices to simpler forms
821
842
  for (let j = 0; j < optimized.length; j++) {
822
843
  const t = optimized[j];
823
- if (t.type === 'matrix') {
844
+ if (t.type === "matrix") {
824
845
  const m = t.params.matrix;
825
846
 
826
847
  // Try to convert to simpler forms
827
848
  const translateResult = matrixToTranslate(m);
828
849
  if (translateResult.isTranslation && translateResult.verified) {
829
850
  optimized[j] = {
830
- type: 'translate',
831
- params: { tx: translateResult.tx, ty: translateResult.ty }
851
+ type: "translate",
852
+ params: { tx: translateResult.tx, ty: translateResult.ty },
832
853
  };
833
854
  continue;
834
855
  }
@@ -836,8 +857,8 @@ export function optimizeTransformList(transforms) {
836
857
  const rotateResult = matrixToRotate(m);
837
858
  if (rotateResult.isRotation && rotateResult.verified) {
838
859
  optimized[j] = {
839
- type: 'rotate',
840
- params: { angle: rotateResult.angle }
860
+ type: "rotate",
861
+ params: { angle: rotateResult.angle },
841
862
  };
842
863
  continue;
843
864
  }
@@ -845,8 +866,8 @@ export function optimizeTransformList(transforms) {
845
866
  const scaleResult = matrixToScale(m);
846
867
  if (scaleResult.isScale && scaleResult.verified) {
847
868
  optimized[j] = {
848
- type: 'scale',
849
- params: { sx: scaleResult.sx, sy: scaleResult.sy }
869
+ type: "scale",
870
+ params: { sx: scaleResult.sx, sy: scaleResult.sy },
850
871
  };
851
872
  continue;
852
873
  }
@@ -861,20 +882,24 @@ export function optimizeTransformList(transforms) {
861
882
  for (const t of final) {
862
883
  let m;
863
884
  switch (t.type) {
864
- case 'translate':
885
+ case "translate":
865
886
  m = translationMatrix(t.params.tx, t.params.ty);
866
887
  break;
867
- case 'rotate':
888
+ case "rotate":
868
889
  if (t.params.cx !== undefined && t.params.cy !== undefined) {
869
- m = rotationMatrixAroundPoint(t.params.angle, t.params.cx, t.params.cy);
890
+ m = rotationMatrixAroundPoint(
891
+ t.params.angle,
892
+ t.params.cx,
893
+ t.params.cy,
894
+ );
870
895
  } else {
871
896
  m = rotationMatrix(t.params.angle);
872
897
  }
873
898
  break;
874
- case 'scale':
899
+ case "scale":
875
900
  m = scaleMatrix(t.params.sx, t.params.sy);
876
901
  break;
877
- case 'matrix':
902
+ case "matrix":
878
903
  m = t.params.matrix;
879
904
  break;
880
905
  default:
@@ -891,7 +916,7 @@ export function optimizeTransformList(transforms) {
891
916
  transforms: final,
892
917
  optimizationCount: transforms.length - final.length,
893
918
  verified,
894
- maxError
919
+ maxError,
895
920
  };
896
921
  }
897
922
 
@@ -899,11 +924,7 @@ export function optimizeTransformList(transforms) {
899
924
  // Exports
900
925
  // ============================================================================
901
926
 
902
- export {
903
- EPSILON,
904
- VERIFICATION_TOLERANCE,
905
- D
906
- };
927
+ export { EPSILON, VERIFICATION_TOLERANCE, D };
907
928
 
908
929
  export default {
909
930
  // Matrix utilities
@@ -932,5 +953,5 @@ export default {
932
953
 
933
954
  // Constants
934
955
  EPSILON,
935
- VERIFICATION_TOLERANCE
956
+ VERIFICATION_TOLERANCE,
936
957
  };
@@ -1,12 +1,27 @@
1
- import Decimal from 'decimal.js';
2
- import { Matrix } from './matrix.js';
1
+ import Decimal from "decimal.js";
2
+ import { Matrix } from "./matrix.js";
3
3
 
4
4
  /**
5
5
  * Helper to convert any numeric input to Decimal.
6
6
  * @param {number|string|Decimal} x - The value to convert
7
7
  * @returns {Decimal} The Decimal representation
8
8
  */
9
- const D = x => (x instanceof Decimal ? x : new Decimal(x));
9
+ const D = (x) => (x instanceof Decimal ? x : new Decimal(x));
10
+
11
+ /**
12
+ * Validates that a value can be converted to Decimal.
13
+ * @param {any} value - The value to validate
14
+ * @param {string} name - The parameter name (for error messages)
15
+ * @throws {Error} If the value is null, undefined, or not a number/string/Decimal
16
+ */
17
+ function validateNumeric(value, name) {
18
+ if (value === undefined || value === null) {
19
+ throw new Error(`${name} is required`);
20
+ }
21
+ if (typeof value !== 'number' && typeof value !== 'string' && !(value instanceof Decimal)) {
22
+ throw new Error(`${name} must be a number, string, or Decimal`);
23
+ }
24
+ }
10
25
 
11
26
  /**
12
27
  * 2D Affine Transforms using 3x3 homogeneous matrices.
@@ -98,10 +113,12 @@ const D = x => (x instanceof Decimal ? x : new Decimal(x));
98
113
  * // This rotates 45° AFTER translating 10 units right
99
114
  */
100
115
  export function translation(tx, ty) {
116
+ validateNumeric(tx, 'tx');
117
+ validateNumeric(ty, 'ty');
101
118
  return Matrix.from([
102
119
  [new Decimal(1), new Decimal(0), D(tx)],
103
120
  [new Decimal(0), new Decimal(1), D(ty)],
104
- [new Decimal(0), new Decimal(0), new Decimal(1)]
121
+ [new Decimal(0), new Decimal(0), new Decimal(1)],
105
122
  ]);
106
123
  }
107
124
 
@@ -153,11 +170,15 @@ export function translation(tx, ty) {
153
170
  * // This scales by 2× around point (100, 50) instead of origin
154
171
  */
155
172
  export function scale(sx, sy = null) {
156
- if (sy === null) sy = sx;
173
+ validateNumeric(sx, 'sx');
174
+ if (sy !== null) {
175
+ validateNumeric(sy, 'sy');
176
+ }
177
+ const syValue = sy === null ? sx : sy;
157
178
  return Matrix.from([
158
179
  [D(sx), new Decimal(0), new Decimal(0)],
159
- [new Decimal(0), D(sy), new Decimal(0)],
160
- [new Decimal(0), new Decimal(0), new Decimal(1)]
180
+ [new Decimal(0), D(syValue), new Decimal(0)],
181
+ [new Decimal(0), new Decimal(0), new Decimal(1)],
161
182
  ]);
162
183
  }
163
184
 
@@ -214,13 +235,14 @@ export function scale(sx, sy = null) {
214
235
  * // Rotates 90° around point (100, 100)
215
236
  */
216
237
  export function rotate(theta) {
238
+ validateNumeric(theta, 'theta');
217
239
  const t = D(theta);
218
240
  const c = new Decimal(Math.cos(t.toNumber()));
219
241
  const s = new Decimal(Math.sin(t.toNumber()));
220
242
  return Matrix.from([
221
243
  [c, s.negated(), new Decimal(0)],
222
244
  [s, c, new Decimal(0)],
223
- [new Decimal(0), new Decimal(0), new Decimal(1)]
245
+ [new Decimal(0), new Decimal(0), new Decimal(1)],
224
246
  ]);
225
247
  }
226
248
 
@@ -272,7 +294,9 @@ export function rotate(theta) {
272
294
  export function rotateAroundPoint(theta, px, py) {
273
295
  const pxD = D(px);
274
296
  const pyD = D(py);
275
- return translation(pxD, pyD).mul(rotate(theta)).mul(translation(pxD.negated(), pyD.negated()));
297
+ return translation(pxD, pyD)
298
+ .mul(rotate(theta))
299
+ .mul(translation(pxD.negated(), pyD.negated()));
276
300
  }
277
301
 
278
302
  /**
@@ -333,7 +357,7 @@ export function skew(ax, ay) {
333
357
  return Matrix.from([
334
358
  [new Decimal(1), D(ax), new Decimal(0)],
335
359
  [D(ay), new Decimal(1), new Decimal(0)],
336
- [new Decimal(0), new Decimal(0), new Decimal(1)]
360
+ [new Decimal(0), new Decimal(0), new Decimal(1)],
337
361
  ]);
338
362
  }
339
363
 
@@ -397,7 +421,9 @@ export function skew(ax, ay) {
397
421
  * // Result: x = 10, y = 10 (Y compressed to half)
398
422
  */
399
423
  export function stretchAlongAxis(ux, uy, k) {
400
- const uxD = D(ux), uyD = D(uy), kD = D(k);
424
+ const uxD = D(ux),
425
+ uyD = D(uy),
426
+ kD = D(k);
401
427
  const one = new Decimal(1);
402
428
  const factor = kD.minus(one);
403
429
  const m00 = one.plus(factor.mul(uxD.mul(uxD)));
@@ -407,7 +433,7 @@ export function stretchAlongAxis(ux, uy, k) {
407
433
  return Matrix.from([
408
434
  [m00, m01, new Decimal(0)],
409
435
  [m10, m11, new Decimal(0)],
410
- [new Decimal(0), new Decimal(0), new Decimal(1)]
436
+ [new Decimal(0), new Decimal(0), new Decimal(1)],
411
437
  ]);
412
438
  }
413
439
 
@@ -469,9 +495,16 @@ export function stretchAlongAxis(ux, uy, k) {
469
495
  * // Efficiently reuses the same transformation matrix
470
496
  */
471
497
  export function applyTransform(M, x, y) {
498
+ if (!M || typeof M.mul !== 'function') {
499
+ throw new Error('applyTransform: first argument must be a Matrix');
500
+ }
501
+ validateNumeric(x, 'x');
502
+ validateNumeric(y, 'y');
472
503
  const P = Matrix.from([[D(x)], [D(y)], [new Decimal(1)]]);
473
504
  const R = M.mul(P);
474
- const rx = R.data[0][0], ry = R.data[1][0], rw = R.data[2][0];
505
+ const rx = R.data[0][0],
506
+ ry = R.data[1][0],
507
+ rw = R.data[2][0];
475
508
  // Perspective division (for affine transforms, rw is always 1)
476
509
  return [rx.div(rw), ry.div(rw)];
477
510
  }
@@ -522,7 +555,7 @@ export function reflectX() {
522
555
  return Matrix.from([
523
556
  [new Decimal(1), new Decimal(0), new Decimal(0)],
524
557
  [new Decimal(0), new Decimal(-1), new Decimal(0)],
525
- [new Decimal(0), new Decimal(0), new Decimal(1)]
558
+ [new Decimal(0), new Decimal(0), new Decimal(1)],
526
559
  ]);
527
560
  }
528
561
 
@@ -572,7 +605,7 @@ export function reflectY() {
572
605
  return Matrix.from([
573
606
  [new Decimal(-1), new Decimal(0), new Decimal(0)],
574
607
  [new Decimal(0), new Decimal(1), new Decimal(0)],
575
- [new Decimal(0), new Decimal(0), new Decimal(1)]
608
+ [new Decimal(0), new Decimal(0), new Decimal(1)],
576
609
  ]);
577
610
  }
578
611
 
@@ -634,6 +667,6 @@ export function reflectOrigin() {
634
667
  return Matrix.from([
635
668
  [new Decimal(-1), new Decimal(0), new Decimal(0)],
636
669
  [new Decimal(0), new Decimal(-1), new Decimal(0)],
637
- [new Decimal(0), new Decimal(0), new Decimal(1)]
670
+ [new Decimal(0), new Decimal(0), new Decimal(1)],
638
671
  ]);
639
672
  }