@emasoft/svg-matrix 1.1.0 → 1.2.0

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 (55) hide show
  1. package/bin/svg-matrix.js +7 -6
  2. package/bin/svgm.js +109 -40
  3. package/dist/svg-matrix.min.js +7 -7
  4. package/dist/svg-toolbox.min.js +148 -228
  5. package/dist/svgm.min.js +152 -232
  6. package/dist/version.json +5 -5
  7. package/package.json +1 -1
  8. package/scripts/postinstall.js +72 -41
  9. package/scripts/test-postinstall.js +18 -16
  10. package/scripts/version-sync.js +78 -60
  11. package/src/animation-optimization.js +190 -98
  12. package/src/animation-references.js +11 -3
  13. package/src/arc-length.js +23 -20
  14. package/src/bezier-analysis.js +9 -13
  15. package/src/bezier-intersections.js +18 -4
  16. package/src/browser-verify.js +35 -8
  17. package/src/clip-path-resolver.js +285 -114
  18. package/src/convert-path-data.js +20 -8
  19. package/src/css-specificity.js +33 -9
  20. package/src/douglas-peucker.js +272 -141
  21. package/src/geometry-to-path.js +79 -22
  22. package/src/gjk-collision.js +287 -126
  23. package/src/index.js +56 -21
  24. package/src/inkscape-support.js +122 -101
  25. package/src/logger.js +43 -27
  26. package/src/marker-resolver.js +201 -121
  27. package/src/mask-resolver.js +231 -98
  28. package/src/matrix.js +9 -5
  29. package/src/mesh-gradient.js +22 -14
  30. package/src/off-canvas-detection.js +53 -17
  31. package/src/path-optimization.js +356 -171
  32. package/src/path-simplification.js +671 -256
  33. package/src/pattern-resolver.js +1 -3
  34. package/src/polygon-clip.js +396 -78
  35. package/src/svg-boolean-ops.js +90 -23
  36. package/src/svg-collections.js +1546 -667
  37. package/src/svg-flatten.js +152 -38
  38. package/src/svg-matrix-lib.js +2 -2
  39. package/src/svg-parser.js +5 -1
  40. package/src/svg-rendering-context.js +3 -1
  41. package/src/svg-toolbox-lib.js +2 -2
  42. package/src/svg-toolbox.js +99 -457
  43. package/src/svg-validation-data.js +513 -345
  44. package/src/svg2-polyfills.js +156 -93
  45. package/src/svgm-lib.js +8 -4
  46. package/src/transform-optimization.js +168 -51
  47. package/src/transforms2d.js +73 -40
  48. package/src/transforms3d.js +34 -27
  49. package/src/use-symbol-resolver.js +175 -76
  50. package/src/vector.js +80 -44
  51. package/src/vendor/inkscape-hatch-polyfill.js +143 -108
  52. package/src/vendor/inkscape-hatch-polyfill.min.js +291 -1
  53. package/src/vendor/inkscape-mesh-polyfill.js +953 -766
  54. package/src/vendor/inkscape-mesh-polyfill.min.js +896 -1
  55. package/src/verification.js +3 -4
@@ -155,7 +155,9 @@ export function scaleMatrix(sx, sy) {
155
155
  */
156
156
  export function matrixMaxDifference(m1, m2) {
157
157
  if (!m1 || !m2) {
158
- throw new Error("matrixMaxDifference: both m1 and m2 parameters are required");
158
+ throw new Error(
159
+ "matrixMaxDifference: both m1 and m2 parameters are required",
160
+ );
159
161
  }
160
162
  if (!m1.rows || !m1.cols || !m1.data) {
161
163
  throw new Error("matrixMaxDifference: m1 must be a valid Matrix object");
@@ -164,7 +166,9 @@ export function matrixMaxDifference(m1, m2) {
164
166
  throw new Error("matrixMaxDifference: m2 must be a valid Matrix object");
165
167
  }
166
168
  if (m1.rows !== m2.rows || m1.cols !== m2.cols) {
167
- throw new Error(`matrixMaxDifference: matrix dimensions must match (m1: ${m1.rows}x${m1.cols}, m2: ${m2.rows}x${m2.cols})`);
169
+ throw new Error(
170
+ `matrixMaxDifference: matrix dimensions must match (m1: ${m1.rows}x${m1.cols}, m2: ${m2.rows}x${m2.cols})`,
171
+ );
168
172
  }
169
173
 
170
174
  let maxDiff = D(0);
@@ -221,12 +225,24 @@ export function matricesEqual(m1, m2, tolerance = VERIFICATION_TOLERANCE) {
221
225
  */
222
226
  export function mergeTranslations(t1, t2) {
223
227
  if (!t1 || !t2) {
224
- throw new Error("mergeTranslations: both t1 and t2 parameters are required");
228
+ throw new Error(
229
+ "mergeTranslations: both t1 and t2 parameters are required",
230
+ );
225
231
  }
226
- if (t1.tx === null || t1.tx === undefined || t1.ty === null || t1.ty === undefined) {
232
+ if (
233
+ t1.tx === null ||
234
+ t1.tx === undefined ||
235
+ t1.ty === null ||
236
+ t1.ty === undefined
237
+ ) {
227
238
  throw new Error("mergeTranslations: t1 must have tx and ty properties");
228
239
  }
229
- if (t2.tx === null || t2.tx === undefined || t2.ty === null || t2.ty === undefined) {
240
+ if (
241
+ t2.tx === null ||
242
+ t2.tx === undefined ||
243
+ t2.ty === null ||
244
+ t2.ty === undefined
245
+ ) {
230
246
  throw new Error("mergeTranslations: t2 must have tx and ty properties");
231
247
  }
232
248
 
@@ -343,10 +359,20 @@ export function mergeScales(s1, s2) {
343
359
  if (!s1 || !s2) {
344
360
  throw new Error("mergeScales: both s1 and s2 parameters are required");
345
361
  }
346
- if (s1.sx === null || s1.sx === undefined || s1.sy === null || s1.sy === undefined) {
362
+ if (
363
+ s1.sx === null ||
364
+ s1.sx === undefined ||
365
+ s1.sy === null ||
366
+ s1.sy === undefined
367
+ ) {
347
368
  throw new Error("mergeScales: s1 must have sx and sy properties");
348
369
  }
349
- if (s2.sx === null || s2.sx === undefined || s2.sy === null || s2.sy === undefined) {
370
+ if (
371
+ s2.sx === null ||
372
+ s2.sx === undefined ||
373
+ s2.sy === null ||
374
+ s2.sy === undefined
375
+ ) {
350
376
  throw new Error("mergeScales: s2 must have sx and sy properties");
351
377
  }
352
378
 
@@ -409,7 +435,9 @@ export function matrixToTranslate(matrix) {
409
435
  throw new Error("matrixToTranslate: matrix must be a valid Matrix object");
410
436
  }
411
437
  if (matrix.rows !== 3 || matrix.cols !== 3) {
412
- throw new Error(`matrixToTranslate: matrix must be 3x3 (got ${matrix.rows}x${matrix.cols})`);
438
+ throw new Error(
439
+ `matrixToTranslate: matrix must be 3x3 (got ${matrix.rows}x${matrix.cols})`,
440
+ );
413
441
  }
414
442
 
415
443
  const data = matrix.data;
@@ -488,7 +516,9 @@ export function matrixToRotate(matrix) {
488
516
  throw new Error("matrixToRotate: matrix must be a valid Matrix object");
489
517
  }
490
518
  if (matrix.rows !== 3 || matrix.cols !== 3) {
491
- throw new Error(`matrixToRotate: matrix must be 3x3 (got ${matrix.rows}x${matrix.cols})`);
519
+ throw new Error(
520
+ `matrixToRotate: matrix must be 3x3 (got ${matrix.rows}x${matrix.cols})`,
521
+ );
492
522
  }
493
523
 
494
524
  const data = matrix.data;
@@ -589,7 +619,9 @@ export function matrixToScale(matrix) {
589
619
  throw new Error("matrixToScale: matrix must be a valid Matrix object");
590
620
  }
591
621
  if (matrix.rows !== 3 || matrix.cols !== 3) {
592
- throw new Error(`matrixToScale: matrix must be 3x3 (got ${matrix.rows}x${matrix.cols})`);
622
+ throw new Error(
623
+ `matrixToScale: matrix must be 3x3 (got ${matrix.rows}x${matrix.cols})`,
624
+ );
593
625
  }
594
626
 
595
627
  const data = matrix.data;
@@ -682,7 +714,9 @@ export function matrixToScale(matrix) {
682
714
  */
683
715
  export function removeIdentityTransforms(transforms) {
684
716
  if (!transforms) {
685
- throw new Error("removeIdentityTransforms: transforms parameter is required");
717
+ throw new Error(
718
+ "removeIdentityTransforms: transforms parameter is required",
719
+ );
686
720
  }
687
721
  if (!Array.isArray(transforms)) {
688
722
  throw new Error("removeIdentityTransforms: transforms must be an array");
@@ -693,17 +727,21 @@ export function removeIdentityTransforms(transforms) {
693
727
 
694
728
  const filtered = transforms.filter((t) => {
695
729
  // Validate transform object structure
696
- if (!t || typeof t !== 'object') {
730
+ if (!t || typeof t !== "object") {
697
731
  return true; // Keep malformed transforms for debugging
698
732
  }
699
- if (!t.type || !t.params || typeof t.params !== 'object') {
733
+ if (!t.type || !t.params || typeof t.params !== "object") {
700
734
  return true; // Keep malformed transforms for debugging
701
735
  }
702
736
 
703
737
  switch (t.type) {
704
738
  case "translate": {
705
- if (t.params.tx === null || t.params.tx === undefined ||
706
- t.params.ty === null || t.params.ty === undefined) {
739
+ if (
740
+ t.params.tx === null ||
741
+ t.params.tx === undefined ||
742
+ t.params.ty === null ||
743
+ t.params.ty === undefined
744
+ ) {
707
745
  return true; // Keep transforms with missing params for debugging
708
746
  }
709
747
  const tx = D(t.params.tx);
@@ -725,8 +763,12 @@ export function removeIdentityTransforms(transforms) {
725
763
  }
726
764
 
727
765
  case "scale": {
728
- if (t.params.sx === null || t.params.sx === undefined ||
729
- t.params.sy === null || t.params.sy === undefined) {
766
+ if (
767
+ t.params.sx === null ||
768
+ t.params.sx === undefined ||
769
+ t.params.sy === null ||
770
+ t.params.sy === undefined
771
+ ) {
730
772
  return true; // Keep transforms with missing params for debugging
731
773
  }
732
774
  const sx = D(t.params.sx);
@@ -881,15 +923,25 @@ export function optimizeTransformList(transforms) {
881
923
  let originalMatrix = identityMatrix();
882
924
  for (const t of transforms) {
883
925
  // Validate transform object structure
884
- if (!t || typeof t !== 'object' || !t.type || !t.params || typeof t.params !== 'object') {
926
+ if (
927
+ !t ||
928
+ typeof t !== "object" ||
929
+ !t.type ||
930
+ !t.params ||
931
+ typeof t.params !== "object"
932
+ ) {
885
933
  continue; // Skip malformed transforms
886
934
  }
887
935
 
888
936
  let m = null; // Initialize m to null to catch missing assignments
889
937
  switch (t.type) {
890
938
  case "translate":
891
- if (t.params.tx === null || t.params.tx === undefined ||
892
- t.params.ty === null || t.params.ty === undefined) {
939
+ if (
940
+ t.params.tx === null ||
941
+ t.params.tx === undefined ||
942
+ t.params.ty === null ||
943
+ t.params.ty === undefined
944
+ ) {
893
945
  continue; // Skip transforms with missing params
894
946
  }
895
947
  m = translationMatrix(t.params.tx, t.params.ty);
@@ -898,8 +950,12 @@ export function optimizeTransformList(transforms) {
898
950
  if (t.params.angle === null || t.params.angle === undefined) {
899
951
  continue; // Skip transforms with missing angle
900
952
  }
901
- if (t.params.cx !== undefined && t.params.cx !== null &&
902
- t.params.cy !== undefined && t.params.cy !== null) {
953
+ if (
954
+ t.params.cx !== undefined &&
955
+ t.params.cx !== null &&
956
+ t.params.cy !== undefined &&
957
+ t.params.cy !== null
958
+ ) {
903
959
  m = rotationMatrixAroundPoint(
904
960
  t.params.angle,
905
961
  t.params.cx,
@@ -910,8 +966,12 @@ export function optimizeTransformList(transforms) {
910
966
  }
911
967
  break;
912
968
  case "scale":
913
- if (t.params.sx === null || t.params.sx === undefined ||
914
- t.params.sy === null || t.params.sy === undefined) {
969
+ if (
970
+ t.params.sx === null ||
971
+ t.params.sx === undefined ||
972
+ t.params.sy === null ||
973
+ t.params.sy === undefined
974
+ ) {
915
975
  continue; // Skip transforms with missing params
916
976
  }
917
977
  m = scaleMatrix(t.params.sx, t.params.sy);
@@ -944,8 +1004,14 @@ export function optimizeTransformList(transforms) {
944
1004
  const next = optimized[i + 1];
945
1005
 
946
1006
  // Validate transform objects before processing
947
- if (!current || !current.type || !current.params ||
948
- !next || !next.type || !next.params) {
1007
+ if (
1008
+ !current ||
1009
+ !current.type ||
1010
+ !current.params ||
1011
+ !next ||
1012
+ !next.type ||
1013
+ !next.params
1014
+ ) {
949
1015
  i++;
950
1016
  continue;
951
1017
  }
@@ -963,18 +1029,36 @@ export function optimizeTransformList(transforms) {
963
1029
  }
964
1030
  } else if (current.type === "rotate" && next.type === "rotate") {
965
1031
  // Only merge if both are around origin: cx and cy are undefined/null OR both are ≈0
966
- const currentCx = current.params.cx !== undefined && current.params.cx !== null ? D(current.params.cx) : null;
967
- const currentCy = current.params.cy !== undefined && current.params.cy !== null ? D(current.params.cy) : null;
968
- const nextCx = next.params.cx !== undefined && next.params.cx !== null ? D(next.params.cx) : null;
969
- const nextCy = next.params.cy !== undefined && next.params.cy !== null ? D(next.params.cy) : null;
1032
+ const currentCx =
1033
+ current.params.cx !== undefined && current.params.cx !== null
1034
+ ? D(current.params.cx)
1035
+ : null;
1036
+ const currentCy =
1037
+ current.params.cy !== undefined && current.params.cy !== null
1038
+ ? D(current.params.cy)
1039
+ : null;
1040
+ const nextCx =
1041
+ next.params.cx !== undefined && next.params.cx !== null
1042
+ ? D(next.params.cx)
1043
+ : null;
1044
+ const nextCy =
1045
+ next.params.cy !== undefined && next.params.cy !== null
1046
+ ? D(next.params.cy)
1047
+ : null;
970
1048
 
971
1049
  // Check if rotation is effectively around origin
972
- const currentIsOrigin = (currentCx === null && currentCy === null) ||
973
- (currentCx !== null && currentCy !== null &&
974
- currentCx.abs().lessThan(EPSILON) && currentCy.abs().lessThan(EPSILON));
975
- const nextIsOrigin = (nextCx === null && nextCy === null) ||
976
- (nextCx !== null && nextCy !== null &&
977
- nextCx.abs().lessThan(EPSILON) && nextCy.abs().lessThan(EPSILON));
1050
+ const currentIsOrigin =
1051
+ (currentCx === null && currentCy === null) ||
1052
+ (currentCx !== null &&
1053
+ currentCy !== null &&
1054
+ currentCx.abs().lessThan(EPSILON) &&
1055
+ currentCy.abs().lessThan(EPSILON));
1056
+ const nextIsOrigin =
1057
+ (nextCx === null && nextCy === null) ||
1058
+ (nextCx !== null &&
1059
+ nextCy !== null &&
1060
+ nextCx.abs().lessThan(EPSILON) &&
1061
+ nextCy.abs().lessThan(EPSILON));
978
1062
 
979
1063
  if (currentIsOrigin && nextIsOrigin) {
980
1064
  const result = mergeRotations(current.params, next.params);
@@ -1012,9 +1096,17 @@ export function optimizeTransformList(transforms) {
1012
1096
  const t3 = optimized[i + 2];
1013
1097
 
1014
1098
  // Validate transform objects before processing
1015
- if (!t1 || !t1.type || !t1.params ||
1016
- !t2 || !t2.type || !t2.params ||
1017
- !t3 || !t3.type || !t3.params) {
1099
+ if (
1100
+ !t1 ||
1101
+ !t1.type ||
1102
+ !t1.params ||
1103
+ !t2 ||
1104
+ !t2.type ||
1105
+ !t2.params ||
1106
+ !t3 ||
1107
+ !t3.type ||
1108
+ !t3.params
1109
+ ) {
1018
1110
  i++;
1019
1111
  continue;
1020
1112
  }
@@ -1025,11 +1117,18 @@ export function optimizeTransformList(transforms) {
1025
1117
  t3.type === "translate"
1026
1118
  ) {
1027
1119
  // Validate required parameters exist
1028
- if (t1.params.tx === null || t1.params.tx === undefined ||
1029
- t1.params.ty === null || t1.params.ty === undefined ||
1030
- t2.params.angle === null || t2.params.angle === undefined ||
1031
- t3.params.tx === null || t3.params.tx === undefined ||
1032
- t3.params.ty === null || t3.params.ty === undefined) {
1120
+ if (
1121
+ t1.params.tx === null ||
1122
+ t1.params.tx === undefined ||
1123
+ t1.params.ty === null ||
1124
+ t1.params.ty === undefined ||
1125
+ t2.params.angle === null ||
1126
+ t2.params.angle === undefined ||
1127
+ t3.params.tx === null ||
1128
+ t3.params.tx === undefined ||
1129
+ t3.params.ty === null ||
1130
+ t3.params.ty === undefined
1131
+ ) {
1033
1132
  i++;
1034
1133
  continue;
1035
1134
  }
@@ -1114,15 +1213,25 @@ export function optimizeTransformList(transforms) {
1114
1213
  let optimizedMatrix = identityMatrix();
1115
1214
  for (const t of final) {
1116
1215
  // Validate transform object structure
1117
- if (!t || typeof t !== 'object' || !t.type || !t.params || typeof t.params !== 'object') {
1216
+ if (
1217
+ !t ||
1218
+ typeof t !== "object" ||
1219
+ !t.type ||
1220
+ !t.params ||
1221
+ typeof t.params !== "object"
1222
+ ) {
1118
1223
  continue; // Skip malformed transforms
1119
1224
  }
1120
1225
 
1121
1226
  let m = null; // Initialize m to null to catch missing assignments
1122
1227
  switch (t.type) {
1123
1228
  case "translate":
1124
- if (t.params.tx === null || t.params.tx === undefined ||
1125
- t.params.ty === null || t.params.ty === undefined) {
1229
+ if (
1230
+ t.params.tx === null ||
1231
+ t.params.tx === undefined ||
1232
+ t.params.ty === null ||
1233
+ t.params.ty === undefined
1234
+ ) {
1126
1235
  continue; // Skip transforms with missing params
1127
1236
  }
1128
1237
  m = translationMatrix(t.params.tx, t.params.ty);
@@ -1131,8 +1240,12 @@ export function optimizeTransformList(transforms) {
1131
1240
  if (t.params.angle === null || t.params.angle === undefined) {
1132
1241
  continue; // Skip transforms with missing angle
1133
1242
  }
1134
- if (t.params.cx !== undefined && t.params.cx !== null &&
1135
- t.params.cy !== undefined && t.params.cy !== null) {
1243
+ if (
1244
+ t.params.cx !== undefined &&
1245
+ t.params.cx !== null &&
1246
+ t.params.cy !== undefined &&
1247
+ t.params.cy !== null
1248
+ ) {
1136
1249
  m = rotationMatrixAroundPoint(
1137
1250
  t.params.angle,
1138
1251
  t.params.cx,
@@ -1143,8 +1256,12 @@ export function optimizeTransformList(transforms) {
1143
1256
  }
1144
1257
  break;
1145
1258
  case "scale":
1146
- if (t.params.sx === null || t.params.sx === undefined ||
1147
- t.params.sy === null || t.params.sy === undefined) {
1259
+ if (
1260
+ t.params.sx === null ||
1261
+ t.params.sx === undefined ||
1262
+ t.params.sy === null ||
1263
+ t.params.sy === undefined
1264
+ ) {
1148
1265
  continue; // Skip transforms with missing params
1149
1266
  }
1150
1267
  m = scaleMatrix(t.params.sx, t.params.sy);
@@ -35,16 +35,22 @@ function validateNumeric(value, name) {
35
35
  if (value === undefined || value === null) {
36
36
  throw new Error(`${name} is required`);
37
37
  }
38
- if (typeof value !== 'number' && typeof value !== 'string' && !(value instanceof Decimal)) {
38
+ if (
39
+ typeof value !== "number" &&
40
+ typeof value !== "string" &&
41
+ !(value instanceof Decimal)
42
+ ) {
39
43
  throw new Error(`${name} must be a number, string, or Decimal`);
40
44
  }
41
45
  // Check for NaN and Infinity in numeric values
42
- if (typeof value === 'number' && !isFinite(value)) {
46
+ if (typeof value === "number" && !isFinite(value)) {
43
47
  throw new Error(`${name} must be a finite number (got ${value})`);
44
48
  }
45
49
  // Check for Infinity in Decimal instances
46
50
  if (value instanceof Decimal && !value.isFinite()) {
47
- throw new Error(`${name} must be a finite Decimal (got ${value.toString()})`);
51
+ throw new Error(
52
+ `${name} must be a finite Decimal (got ${value.toString()})`,
53
+ );
48
54
  }
49
55
  }
50
56
 
@@ -138,8 +144,8 @@ function validateNumeric(value, name) {
138
144
  * // This rotates 45° AFTER translating 10 units right
139
145
  */
140
146
  export function translation(tx, ty) {
141
- validateNumeric(tx, 'tx');
142
- validateNumeric(ty, 'ty');
147
+ validateNumeric(tx, "tx");
148
+ validateNumeric(ty, "ty");
143
149
  return Matrix.from([
144
150
  [new Decimal(1), new Decimal(0), D(tx)],
145
151
  [new Decimal(0), new Decimal(1), D(ty)],
@@ -195,16 +201,16 @@ export function translation(tx, ty) {
195
201
  * // This scales by 2× around point (100, 50) instead of origin
196
202
  */
197
203
  export function scale(sx, sy = null) {
198
- validateNumeric(sx, 'sx');
204
+ validateNumeric(sx, "sx");
199
205
  if (sy !== null) {
200
- validateNumeric(sy, 'sy');
206
+ validateNumeric(sy, "sy");
201
207
  }
202
208
  const syValue = sy === null ? sx : sy;
203
209
  // Check for zero scale factors which create singular matrices
204
210
  const sxD = D(sx);
205
211
  const syD = D(syValue);
206
212
  if (sxD.isZero() || syD.isZero()) {
207
- throw new Error('Scale factors cannot be zero (creates singular matrix)');
213
+ throw new Error("Scale factors cannot be zero (creates singular matrix)");
208
214
  }
209
215
  return Matrix.from([
210
216
  [sxD, new Decimal(0), new Decimal(0)],
@@ -266,21 +272,25 @@ export function scale(sx, sy = null) {
266
272
  * // Rotates 90° around point (100, 100)
267
273
  */
268
274
  export function rotate(theta) {
269
- validateNumeric(theta, 'theta');
275
+ validateNumeric(theta, "theta");
270
276
  const t = D(theta);
271
277
  // Normalize angle to [-π, π] to prevent NaN from Math.cos/sin with very large angles
272
278
  const tNum = t.toNumber();
273
279
  const pi = Math.PI;
274
- const normalizedAngle = tNum - (2 * pi) * Math.floor((tNum + pi) / (2 * pi));
280
+ const normalizedAngle = tNum - 2 * pi * Math.floor((tNum + pi) / (2 * pi));
275
281
  // Check if normalization produced valid number
276
282
  if (!isFinite(normalizedAngle)) {
277
- throw new Error(`Angle normalization failed for theta=${theta} (too large or invalid)`);
283
+ throw new Error(
284
+ `Angle normalization failed for theta=${theta} (too large or invalid)`,
285
+ );
278
286
  }
279
287
  const c = new Decimal(Math.cos(normalizedAngle));
280
288
  const s = new Decimal(Math.sin(normalizedAngle));
281
289
  // Validate trig results are finite
282
290
  if (!c.isFinite() || !s.isFinite()) {
283
- throw new Error(`Trigonometric computation failed for theta=${theta} (produced non-finite values)`);
291
+ throw new Error(
292
+ `Trigonometric computation failed for theta=${theta} (produced non-finite values)`,
293
+ );
284
294
  }
285
295
  return Matrix.from([
286
296
  [c, s.negated(), new Decimal(0)],
@@ -335,9 +345,9 @@ export function rotate(theta) {
335
345
  * // Result: point moves 30° counterclockwise around the center
336
346
  */
337
347
  export function rotateAroundPoint(theta, px, py) {
338
- validateNumeric(theta, 'theta');
339
- validateNumeric(px, 'px');
340
- validateNumeric(py, 'py');
348
+ validateNumeric(theta, "theta");
349
+ validateNumeric(px, "px");
350
+ validateNumeric(py, "py");
341
351
  const pxD = D(px);
342
352
  const pyD = D(py);
343
353
  return translation(pxD, pyD)
@@ -400,14 +410,16 @@ export function rotateAroundPoint(theta, px, py) {
400
410
  * // Both coordinates affect each other, creating complex shearing
401
411
  */
402
412
  export function skew(ax, ay) {
403
- validateNumeric(ax, 'ax');
404
- validateNumeric(ay, 'ay');
413
+ validateNumeric(ax, "ax");
414
+ validateNumeric(ay, "ay");
405
415
  const axD = D(ax);
406
416
  const ayD = D(ay);
407
417
  // Check determinant: det = 1 - ax*ay. If det <= 0, matrix is singular or inverts orientation
408
418
  const det = new Decimal(1).minus(axD.mul(ayD));
409
419
  if (det.lessThanOrEqualTo(0)) {
410
- throw new Error(`Skew parameters create singular or orientation-inverting matrix (ax*ay = ${axD.mul(ayD).toString()}, must be < 1)`);
420
+ throw new Error(
421
+ `Skew parameters create singular or orientation-inverting matrix (ax*ay = ${axD.mul(ayD).toString()}, must be < 1)`,
422
+ );
411
423
  }
412
424
  return Matrix.from([
413
425
  [new Decimal(1), axD, new Decimal(0)],
@@ -477,20 +489,22 @@ export function skew(ax, ay) {
477
489
  * // Result: x = 10, y = 10 (Y compressed to half)
478
490
  */
479
491
  export function stretchAlongAxis(ux, uy, k) {
480
- validateNumeric(ux, 'ux');
481
- validateNumeric(uy, 'uy');
482
- validateNumeric(k, 'k');
492
+ validateNumeric(ux, "ux");
493
+ validateNumeric(uy, "uy");
494
+ validateNumeric(k, "k");
483
495
  const uxD = D(ux);
484
496
  const uyD = D(uy);
485
497
  const kD = D(k);
486
498
  // Check if k is zero which creates singular matrix
487
499
  if (kD.isZero()) {
488
- throw new Error('Stretch factor k cannot be zero (creates singular matrix)');
500
+ throw new Error(
501
+ "Stretch factor k cannot be zero (creates singular matrix)",
502
+ );
489
503
  }
490
504
  // Validate axis vector is not zero
491
505
  const normSquared = uxD.mul(uxD).plus(uyD.mul(uyD));
492
506
  if (normSquared.isZero()) {
493
- throw new Error('Axis vector (ux, uy) cannot be zero');
507
+ throw new Error("Axis vector (ux, uy) cannot be zero");
494
508
  }
495
509
  // Validate axis vector is normalized (required for correct stretch factor)
496
510
  const normDiff = normSquared.minus(1).abs();
@@ -498,8 +512,8 @@ export function stretchAlongAxis(ux, uy, k) {
498
512
  if (normDiff.greaterThan(tolerance)) {
499
513
  throw new Error(
500
514
  `Axis vector (ux, uy) must be normalized (||u|| = 1). ` +
501
- `Current magnitude squared: ${normSquared.toString()}, expected: 1. ` +
502
- `Use normalized vector: (${uxD.div(normSquared.sqrt()).toString()}, ${uyD.div(normSquared.sqrt()).toString()})`
515
+ `Current magnitude squared: ${normSquared.toString()}, expected: 1. ` +
516
+ `Use normalized vector: (${uxD.div(normSquared.sqrt()).toString()}, ${uyD.div(normSquared.sqrt()).toString()})`,
503
517
  );
504
518
  }
505
519
  const one = new Decimal(1);
@@ -573,33 +587,52 @@ export function stretchAlongAxis(ux, uy, k) {
573
587
  * // Efficiently reuses the same transformation matrix
574
588
  */
575
589
  export function applyTransform(M, x, y) {
576
- if (!M || typeof M.mul !== 'function') {
577
- throw new Error('applyTransform: first argument must be a Matrix');
590
+ if (!M || typeof M.mul !== "function") {
591
+ throw new Error("applyTransform: first argument must be a Matrix");
578
592
  }
579
593
  // Check matrix dimensions
580
- if (!M.data || !Array.isArray(M.data) || M.data.length !== 3 ||
581
- !M.data[0] || M.data[0].length !== 3 ||
582
- !M.data[1] || M.data[1].length !== 3 ||
583
- !M.data[2] || M.data[2].length !== 3) {
584
- throw new Error('applyTransform: matrix must be 3x3');
594
+ if (
595
+ !M.data ||
596
+ !Array.isArray(M.data) ||
597
+ M.data.length !== 3 ||
598
+ !M.data[0] ||
599
+ M.data[0].length !== 3 ||
600
+ !M.data[1] ||
601
+ M.data[1].length !== 3 ||
602
+ !M.data[2] ||
603
+ M.data[2].length !== 3
604
+ ) {
605
+ throw new Error("applyTransform: matrix must be 3x3");
585
606
  }
586
- validateNumeric(x, 'x');
587
- validateNumeric(y, 'y');
607
+ validateNumeric(x, "x");
608
+ validateNumeric(y, "y");
588
609
  const P = Matrix.from([[D(x)], [D(y)], [new Decimal(1)]]);
589
610
  const R = M.mul(P);
590
611
  // Validate result matrix structure
591
- if (!R || !R.data || !Array.isArray(R.data) || R.data.length !== 3 ||
592
- !R.data[0] || !R.data[0][0] ||
593
- !R.data[1] || !R.data[1][0] ||
594
- !R.data[2] || !R.data[2][0]) {
595
- throw new Error('applyTransform: matrix multiplication produced invalid result');
612
+ if (
613
+ !R ||
614
+ !R.data ||
615
+ !Array.isArray(R.data) ||
616
+ R.data.length !== 3 ||
617
+ !R.data[0] ||
618
+ !R.data[0][0] ||
619
+ !R.data[1] ||
620
+ !R.data[1][0] ||
621
+ !R.data[2] ||
622
+ !R.data[2][0]
623
+ ) {
624
+ throw new Error(
625
+ "applyTransform: matrix multiplication produced invalid result",
626
+ );
596
627
  }
597
628
  const rx = R.data[0][0],
598
629
  ry = R.data[1][0],
599
630
  rw = R.data[2][0];
600
631
  // Check for zero division in perspective division
601
632
  if (rw.isZero()) {
602
- throw new Error('applyTransform: perspective division by zero (invalid transformation matrix)');
633
+ throw new Error(
634
+ "applyTransform: perspective division by zero (invalid transformation matrix)",
635
+ );
603
636
  }
604
637
  // Perspective division (for affine transforms, rw is always 1)
605
638
  return [rx.div(rw), ry.div(rw)];