@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.
- package/README.md +325 -0
- package/bin/svg-matrix.js +994 -378
- package/bin/svglinter.cjs +4172 -433
- package/bin/svgm.js +744 -184
- package/package.json +16 -4
- package/src/animation-references.js +71 -52
- package/src/arc-length.js +160 -96
- package/src/bezier-analysis.js +257 -117
- package/src/bezier-intersections.js +411 -148
- package/src/browser-verify.js +240 -100
- package/src/clip-path-resolver.js +350 -142
- package/src/convert-path-data.js +279 -134
- package/src/css-specificity.js +78 -70
- package/src/flatten-pipeline.js +751 -263
- package/src/geometry-to-path.js +511 -182
- package/src/index.js +191 -46
- package/src/inkscape-support.js +404 -0
- package/src/marker-resolver.js +278 -164
- package/src/mask-resolver.js +209 -98
- package/src/matrix.js +147 -67
- package/src/mesh-gradient.js +187 -96
- package/src/off-canvas-detection.js +201 -104
- package/src/path-analysis.js +187 -107
- package/src/path-data-plugins.js +628 -167
- package/src/path-simplification.js +0 -1
- package/src/pattern-resolver.js +125 -88
- package/src/polygon-clip.js +111 -66
- package/src/svg-boolean-ops.js +194 -118
- package/src/svg-collections.js +48 -19
- package/src/svg-flatten.js +282 -164
- package/src/svg-parser.js +427 -200
- package/src/svg-rendering-context.js +147 -104
- package/src/svg-toolbox.js +16411 -3298
- package/src/svg2-polyfills.js +114 -245
- package/src/transform-decomposition.js +46 -41
- package/src/transform-optimization.js +89 -68
- package/src/transforms2d.js +49 -16
- package/src/transforms3d.js +58 -22
- package/src/use-symbol-resolver.js +150 -110
- package/src/vector.js +67 -15
- package/src/vendor/README.md +110 -0
- package/src/vendor/inkscape-hatch-polyfill.js +401 -0
- package/src/vendor/inkscape-hatch-polyfill.min.js +8 -0
- package/src/vendor/inkscape-mesh-polyfill.js +843 -0
- package/src/vendor/inkscape-mesh-polyfill.min.js +8 -0
- package/src/verification.js +288 -124
|
@@ -31,20 +31,20 @@
|
|
|
31
31
|
* @module transform-optimization
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
|
-
import Decimal from
|
|
35
|
-
import { Matrix } from
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
593
|
+
case "scale": {
|
|
593
594
|
const sx = D(t.params.sx);
|
|
594
595
|
const sy = D(t.params.sy);
|
|
595
|
-
return
|
|
596
|
+
return (
|
|
597
|
+
!sx.minus(1).abs().lessThan(EPSILON) ||
|
|
598
|
+
!sy.minus(1).abs().lessThan(EPSILON)
|
|
599
|
+
);
|
|
596
600
|
}
|
|
597
601
|
|
|
598
|
-
case
|
|
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
|
|
719
|
+
case "translate":
|
|
716
720
|
m = translationMatrix(t.params.tx, t.params.ty);
|
|
717
721
|
break;
|
|
718
|
-
case
|
|
722
|
+
case "rotate":
|
|
719
723
|
if (t.params.cx !== undefined && t.params.cy !== undefined) {
|
|
720
|
-
m = rotationMatrixAroundPoint(
|
|
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
|
|
733
|
+
case "scale":
|
|
726
734
|
m = scaleMatrix(t.params.sx, t.params.sy);
|
|
727
735
|
break;
|
|
728
|
-
case
|
|
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 } =
|
|
739
|
-
|
|
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 ===
|
|
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:
|
|
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 ===
|
|
767
|
+
} else if (current.type === "rotate" && next.type === "rotate") {
|
|
759
768
|
// Only merge if both are around origin
|
|
760
|
-
if (
|
|
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:
|
|
765
|
-
params: { angle: result.angle }
|
|
778
|
+
type: "rotate",
|
|
779
|
+
params: { angle: result.angle },
|
|
766
780
|
};
|
|
767
781
|
}
|
|
768
782
|
}
|
|
769
|
-
} else if (current.type ===
|
|
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:
|
|
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 (
|
|
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 (
|
|
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:
|
|
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 ===
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
885
|
+
case "translate":
|
|
865
886
|
m = translationMatrix(t.params.tx, t.params.ty);
|
|
866
887
|
break;
|
|
867
|
-
case
|
|
888
|
+
case "rotate":
|
|
868
889
|
if (t.params.cx !== undefined && t.params.cy !== undefined) {
|
|
869
|
-
m = rotationMatrixAroundPoint(
|
|
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
|
|
899
|
+
case "scale":
|
|
875
900
|
m = scaleMatrix(t.params.sx, t.params.sy);
|
|
876
901
|
break;
|
|
877
|
-
case
|
|
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
|
};
|
package/src/transforms2d.js
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
|
-
import Decimal from
|
|
2
|
-
import { Matrix } from
|
|
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
|
-
|
|
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(
|
|
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)
|
|
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),
|
|
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],
|
|
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
|
}
|