@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.
- package/bin/svg-matrix.js +310 -61
- package/bin/svglinter.cjs +102 -3
- package/bin/svgm.js +236 -27
- package/package.json +1 -1
- package/src/animation-optimization.js +137 -17
- package/src/animation-references.js +123 -6
- package/src/arc-length.js +213 -4
- package/src/bezier-analysis.js +217 -21
- package/src/bezier-intersections.js +275 -12
- package/src/browser-verify.js +237 -4
- package/src/clip-path-resolver.js +168 -0
- package/src/convert-path-data.js +479 -28
- package/src/css-specificity.js +73 -10
- package/src/douglas-peucker.js +219 -2
- package/src/flatten-pipeline.js +284 -26
- package/src/geometry-to-path.js +250 -25
- package/src/gjk-collision.js +236 -33
- package/src/index.js +261 -3
- package/src/inkscape-support.js +86 -28
- package/src/logger.js +48 -3
- package/src/marker-resolver.js +278 -74
- package/src/mask-resolver.js +265 -66
- package/src/matrix.js +44 -5
- package/src/mesh-gradient.js +352 -102
- package/src/off-canvas-detection.js +382 -13
- package/src/path-analysis.js +192 -18
- package/src/path-data-plugins.js +309 -5
- package/src/path-optimization.js +129 -5
- package/src/path-simplification.js +188 -32
- package/src/pattern-resolver.js +454 -106
- package/src/polygon-clip.js +324 -1
- package/src/svg-boolean-ops.js +226 -9
- package/src/svg-collections.js +7 -5
- package/src/svg-flatten.js +386 -62
- package/src/svg-parser.js +179 -8
- package/src/svg-rendering-context.js +235 -6
- package/src/svg-toolbox.js +45 -8
- package/src/svg2-polyfills.js +40 -10
- package/src/transform-decomposition.js +258 -32
- package/src/transform-optimization.js +259 -13
- package/src/transforms2d.js +82 -9
- package/src/transforms3d.js +62 -10
- package/src/use-symbol-resolver.js +286 -42
- package/src/vector.js +64 -8
- 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
|
-
//
|
|
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:
|
|
205
|
-
translateY:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
}
|