@emasoft/svg-matrix 1.0.28 → 1.0.30

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 +985 -378
  3. package/bin/svglinter.cjs +4172 -433
  4. package/bin/svgm.js +723 -180
  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 +18 -7
  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 +22 -18
  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 +16381 -3370
  34. package/src/svg2-polyfills.js +93 -224
  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,9 +31,9 @@
31
31
  * @module svg-flatten
32
32
  */
33
33
 
34
- import Decimal from 'decimal.js';
35
- import { Matrix } from './matrix.js';
36
- import * as Transforms2D from './transforms2d.js';
34
+ import Decimal from "decimal.js";
35
+ import { Matrix } from "./matrix.js";
36
+ import * as Transforms2D from "./transforms2d.js";
37
37
 
38
38
  // Set high precision for all calculations
39
39
  Decimal.set({ precision: 80 });
@@ -69,11 +69,14 @@ Decimal.set({ precision: 80 });
69
69
  * // Shows region from (-50,-50) to (150,150) in user space
70
70
  */
71
71
  export function parseViewBox(viewBoxStr) {
72
- if (!viewBoxStr || viewBoxStr.trim() === '') {
72
+ if (!viewBoxStr || viewBoxStr.trim() === "") {
73
73
  return null;
74
74
  }
75
75
 
76
- const parts = viewBoxStr.trim().split(/[\s,]+/).map(s => new Decimal(s));
76
+ const parts = viewBoxStr
77
+ .trim()
78
+ .split(/[\s,]+/)
79
+ .map((s) => new Decimal(s));
77
80
  if (parts.length !== 4) {
78
81
  console.warn(`Invalid viewBox: ${viewBoxStr}`);
79
82
  return null;
@@ -83,7 +86,7 @@ export function parseViewBox(viewBoxStr) {
83
86
  minX: parts[0],
84
87
  minY: parts[1],
85
88
  width: parts[2],
86
- height: parts[3]
89
+ height: parts[3],
87
90
  };
88
91
  }
89
92
 
@@ -123,11 +126,11 @@ export function parseViewBox(viewBoxStr) {
123
126
  export function parsePreserveAspectRatio(parStr) {
124
127
  const result = {
125
128
  defer: false,
126
- align: 'xMidYMid', // default
127
- meetOrSlice: 'meet' // default
129
+ align: "xMidYMid", // default
130
+ meetOrSlice: "meet", // default
128
131
  };
129
132
 
130
- if (!parStr || parStr.trim() === '') {
133
+ if (!parStr || parStr.trim() === "") {
131
134
  return result;
132
135
  }
133
136
 
@@ -135,7 +138,7 @@ export function parsePreserveAspectRatio(parStr) {
135
138
  let idx = 0;
136
139
 
137
140
  // Check for 'defer' (only applies to <image>)
138
- if (parts[idx] === 'defer') {
141
+ if (parts[idx] === "defer") {
139
142
  result.defer = true;
140
143
  idx++;
141
144
  }
@@ -206,8 +209,13 @@ export function parsePreserveAspectRatio(parStr) {
206
209
  * const matrix = computeViewBoxTransform(vb, 800, 400, par);
207
210
  * // Uniform scale of 8 (max(800/100, 400/100)), centered, top/bottom cropped
208
211
  */
209
- export function computeViewBoxTransform(viewBox, viewportWidth, viewportHeight, par = null) {
210
- const D = x => new Decimal(x);
212
+ export function computeViewBoxTransform(
213
+ viewBox,
214
+ viewportWidth,
215
+ viewportHeight,
216
+ par = null,
217
+ ) {
218
+ const D = (x) => new Decimal(x);
211
219
 
212
220
  if (!viewBox) {
213
221
  return Matrix.identity(3);
@@ -221,12 +229,10 @@ export function computeViewBoxTransform(viewBox, viewportWidth, viewportHeight,
221
229
  const vpH = D(viewportHeight);
222
230
 
223
231
  // Default preserveAspectRatio
224
- if (!par) {
225
- par = { align: 'xMidYMid', meetOrSlice: 'meet' };
226
- }
232
+ const parValue = par || { align: "xMidYMid", meetOrSlice: "meet" };
227
233
 
228
234
  // Handle 'none' - stretch to fill
229
- if (par.align === 'none') {
235
+ if (parValue.align === "none") {
230
236
  const scaleX = vpW.div(vbW);
231
237
  const scaleY = vpH.div(vbH);
232
238
  // translate(-minX, -minY) then scale
@@ -236,11 +242,11 @@ export function computeViewBoxTransform(viewBox, viewportWidth, viewportHeight,
236
242
  }
237
243
 
238
244
  // Compute uniform scale factor
239
- let scaleX = vpW.div(vbW);
240
- let scaleY = vpH.div(vbH);
245
+ const scaleX = vpW.div(vbW);
246
+ const scaleY = vpH.div(vbH);
241
247
  let scale;
242
248
 
243
- if (par.meetOrSlice === 'slice') {
249
+ if (parValue.meetOrSlice === "slice") {
244
250
  // Use larger scale (content may overflow)
245
251
  scale = Decimal.max(scaleX, scaleY);
246
252
  } else {
@@ -256,20 +262,20 @@ export function computeViewBoxTransform(viewBox, viewportWidth, viewportHeight,
256
262
  let translateY = D(0);
257
263
 
258
264
  // Parse alignment string (e.g., 'xMidYMid', 'xMinYMax')
259
- const align = par.align;
265
+ const align = parValue.align;
260
266
 
261
267
  // X alignment
262
- if (align.includes('xMid')) {
268
+ if (align.includes("xMid")) {
263
269
  translateX = vpW.minus(scaledW).div(2);
264
- } else if (align.includes('xMax')) {
270
+ } else if (align.includes("xMax")) {
265
271
  translateX = vpW.minus(scaledW);
266
272
  }
267
273
  // xMin is default (translateX = 0)
268
274
 
269
275
  // Y alignment
270
- if (align.includes('YMid')) {
276
+ if (align.includes("YMid")) {
271
277
  translateY = vpH.minus(scaledH).div(2);
272
- } else if (align.includes('YMax')) {
278
+ } else if (align.includes("YMax")) {
273
279
  translateY = vpH.minus(scaledH);
274
280
  }
275
281
  // YMin is default (translateY = 0)
@@ -327,7 +333,13 @@ export class SVGViewport {
327
333
  * "rotate(45 50 25)"
328
334
  * );
329
335
  */
330
- constructor(width, height, viewBox = null, preserveAspectRatio = null, transform = null) {
336
+ constructor(
337
+ width,
338
+ height,
339
+ viewBox = null,
340
+ preserveAspectRatio = null,
341
+ transform = null,
342
+ ) {
331
343
  this.width = new Decimal(width);
332
344
  this.height = new Decimal(height);
333
345
  this.viewBox = viewBox ? parseViewBox(viewBox) : null;
@@ -361,7 +373,7 @@ export class SVGViewport {
361
373
  this.viewBox,
362
374
  this.width,
363
375
  this.height,
364
- this.preserveAspectRatio
376
+ this.preserveAspectRatio,
365
377
  );
366
378
  result = result.mul(vbTransform);
367
379
  }
@@ -429,23 +441,23 @@ export function buildFullCTM(hierarchy) {
429
441
  let ctm = Matrix.identity(3);
430
442
 
431
443
  for (const item of hierarchy) {
432
- if (typeof item === 'string') {
444
+ if (typeof item === "string") {
433
445
  // Backwards compatibility: treat string as transform attribute
434
446
  if (item) {
435
447
  const matrix = parseTransformAttribute(item);
436
448
  ctm = ctm.mul(matrix);
437
449
  }
438
- } else if (item.type === 'svg') {
450
+ } else if (item.type === "svg") {
439
451
  // SVG viewport with potential viewBox
440
452
  const viewport = new SVGViewport(
441
453
  item.width,
442
454
  item.height,
443
455
  item.viewBox || null,
444
456
  item.preserveAspectRatio || null,
445
- item.transform || null
457
+ item.transform || null,
446
458
  );
447
459
  ctm = ctm.mul(viewport.getTransformMatrix());
448
- } else if (item.type === 'g' || item.type === 'element') {
460
+ } else if (item.type === "g" || item.type === "element") {
449
461
  // Group or element with optional transform
450
462
  if (item.transform) {
451
463
  const matrix = parseTransformAttribute(item.transform);
@@ -506,16 +518,16 @@ export function buildFullCTM(hierarchy) {
506
518
  * // Returns: Decimal(32) // 2 × 16px
507
519
  */
508
520
  export function resolveLength(value, referenceSize, dpi = 96) {
509
- const D = x => new Decimal(x);
521
+ const D = (x) => new Decimal(x);
510
522
 
511
- if (typeof value === 'number') {
523
+ if (typeof value === "number") {
512
524
  return D(value);
513
525
  }
514
526
 
515
527
  const str = String(value).trim();
516
528
 
517
529
  // Percentage
518
- if (str.endsWith('%')) {
530
+ if (str.endsWith("%")) {
519
531
  const pct = D(str.slice(0, -1));
520
532
  return pct.div(100).mul(referenceSize);
521
533
  }
@@ -527,26 +539,26 @@ export function resolveLength(value, referenceSize, dpi = 96) {
527
539
  }
528
540
 
529
541
  const num = D(match[1]);
530
- const unit = (match[2] || '').toLowerCase().trim();
542
+ const unit = (match[2] || "").toLowerCase().trim();
531
543
 
532
544
  // Convert to user units (px)
533
545
  switch (unit) {
534
- case '':
535
- case 'px':
546
+ case "":
547
+ case "px":
536
548
  return num;
537
- case 'em':
549
+ case "em":
538
550
  return num.mul(16); // Assume 16px font-size
539
- case 'rem':
551
+ case "rem":
540
552
  return num.mul(16);
541
- case 'pt':
553
+ case "pt":
542
554
  return num.mul(dpi).div(72);
543
- case 'pc':
555
+ case "pc":
544
556
  return num.mul(dpi).div(6);
545
- case 'in':
557
+ case "in":
546
558
  return num.mul(dpi);
547
- case 'cm':
559
+ case "cm":
548
560
  return num.mul(dpi).div(2.54);
549
- case 'mm':
561
+ case "mm":
550
562
  return num.mul(dpi).div(25.4);
551
563
  default:
552
564
  return num; // Unknown unit, treat as px
@@ -563,10 +575,15 @@ export function resolveLength(value, referenceSize, dpi = 96) {
563
575
  * @param {Decimal} viewportHeight - Viewport height for reference
564
576
  * @returns {{x: Decimal, y: Decimal}} Resolved coordinates
565
577
  */
566
- export function resolvePercentages(xOrWidth, yOrHeight, viewportWidth, viewportHeight) {
578
+ export function resolvePercentages(
579
+ xOrWidth,
580
+ yOrHeight,
581
+ viewportWidth,
582
+ viewportHeight,
583
+ ) {
567
584
  return {
568
585
  x: resolveLength(xOrWidth, viewportWidth),
569
- y: resolveLength(yOrHeight, viewportHeight)
586
+ y: resolveLength(yOrHeight, viewportHeight),
570
587
  };
571
588
  }
572
589
 
@@ -641,8 +658,13 @@ export function normalizedDiagonal(width, height) {
641
658
  * const transform = objectBoundingBoxTransform(bbox.x, bbox.y, bbox.width, bbox.height);
642
659
  * // Gradient with x1="0" y1="0" x2="1" y2="1" spans from (0,0) to (100,100)
643
660
  */
644
- export function objectBoundingBoxTransform(bboxX, bboxY, bboxWidth, bboxHeight) {
645
- const D = x => new Decimal(x);
661
+ export function objectBoundingBoxTransform(
662
+ bboxX,
663
+ bboxY,
664
+ bboxWidth,
665
+ bboxHeight,
666
+ ) {
667
+ const _D = (x) => new Decimal(x);
646
668
  // Transform: scale(bboxWidth, bboxHeight) then translate(bboxX, bboxY)
647
669
  const scaleM = Transforms2D.scale(bboxWidth, bboxHeight);
648
670
  const translateM = Transforms2D.translation(bboxX, bboxY);
@@ -708,20 +730,27 @@ export function circleToPath(cx, cy, r) {
708
730
  * // Equivalent to circleToPath(50, 50, 25)
709
731
  */
710
732
  export function ellipseToPath(cx, cy, rx, ry) {
711
- const D = x => new Decimal(x);
712
- const cxD = D(cx), cyD = D(cy), rxD = D(rx), ryD = D(ry);
733
+ const D = (x) => new Decimal(x);
734
+ const cxD = D(cx),
735
+ cyD = D(cy),
736
+ rxD = D(rx),
737
+ ryD = D(ry);
713
738
 
714
739
  // Kappa for bezier approximation of circle/ellipse: 4 * (sqrt(2) - 1) / 3
715
- const kappa = D('0.5522847498307936');
740
+ const kappa = D("0.5522847498307936");
716
741
  const kx = rxD.mul(kappa);
717
742
  const ky = ryD.mul(kappa);
718
743
 
719
744
  // Four bezier curves forming the ellipse
720
745
  // Start at (cx + rx, cy) and go counterclockwise
721
- const x1 = cxD.plus(rxD), y1 = cyD;
722
- const x2 = cxD, y2 = cyD.minus(ryD);
723
- const x3 = cxD.minus(rxD), y3 = cyD;
724
- const x4 = cxD, y4 = cyD.plus(ryD);
746
+ const x1 = cxD.plus(rxD),
747
+ y1 = cyD;
748
+ const x2 = cxD,
749
+ y2 = cyD.minus(ryD);
750
+ const x3 = cxD.minus(rxD),
751
+ y3 = cyD;
752
+ const x4 = cxD,
753
+ y4 = cyD.plus(ryD);
725
754
 
726
755
  return [
727
756
  `M ${x1.toFixed(6)} ${y1.toFixed(6)}`,
@@ -729,8 +758,8 @@ export function ellipseToPath(cx, cy, rx, ry) {
729
758
  `C ${x2.minus(kx).toFixed(6)} ${y2.toFixed(6)} ${x3.toFixed(6)} ${y3.minus(ky).toFixed(6)} ${x3.toFixed(6)} ${y3.toFixed(6)}`,
730
759
  `C ${x3.toFixed(6)} ${y3.plus(ky).toFixed(6)} ${x4.minus(kx).toFixed(6)} ${y4.toFixed(6)} ${x4.toFixed(6)} ${y4.toFixed(6)}`,
731
760
  `C ${x4.plus(kx).toFixed(6)} ${y4.toFixed(6)} ${x1.toFixed(6)} ${y1.plus(ky).toFixed(6)} ${x1.toFixed(6)} ${y1.toFixed(6)}`,
732
- 'Z'
733
- ].join(' ');
761
+ "Z",
762
+ ].join(" ");
734
763
  }
735
764
 
736
765
  /**
@@ -776,8 +805,11 @@ export function ellipseToPath(cx, cy, rx, ry) {
776
805
  * // rx is clamped to 10 (half of 20)
777
806
  */
778
807
  export function rectToPath(x, y, width, height, rx = 0, ry = null) {
779
- const D = n => new Decimal(n);
780
- const xD = D(x), yD = D(y), wD = D(width), hD = D(height);
808
+ const D = (n) => new Decimal(n);
809
+ const xD = D(x),
810
+ yD = D(y),
811
+ wD = D(width),
812
+ hD = D(height);
781
813
  let rxD = D(rx);
782
814
  let ryD = ry !== null ? D(ry) : rxD;
783
815
 
@@ -796,8 +828,8 @@ export function rectToPath(x, y, width, height, rx = 0, ry = null) {
796
828
  `L ${xD.plus(wD).toFixed(6)} ${yD.toFixed(6)}`,
797
829
  `L ${xD.plus(wD).toFixed(6)} ${yD.plus(hD).toFixed(6)}`,
798
830
  `L ${xD.toFixed(6)} ${yD.plus(hD).toFixed(6)}`,
799
- 'Z'
800
- ].join(' ');
831
+ "Z",
832
+ ].join(" ");
801
833
  }
802
834
 
803
835
  // Rounded rectangle using arcs
@@ -810,8 +842,8 @@ export function rectToPath(x, y, width, height, rx = 0, ry = null) {
810
842
  `L ${xD.plus(rxD).toFixed(6)} ${yD.plus(hD).toFixed(6)}`,
811
843
  `A ${rxD.toFixed(6)} ${ryD.toFixed(6)} 0 0 1 ${xD.toFixed(6)} ${yD.plus(hD).minus(ryD).toFixed(6)}`,
812
844
  `L ${xD.toFixed(6)} ${yD.plus(ryD).toFixed(6)}`,
813
- `A ${rxD.toFixed(6)} ${ryD.toFixed(6)} 0 0 1 ${xD.plus(rxD).toFixed(6)} ${yD.toFixed(6)}`
814
- ].join(' ');
845
+ `A ${rxD.toFixed(6)} ${ryD.toFixed(6)} 0 0 1 ${xD.plus(rxD).toFixed(6)} ${yD.toFixed(6)}`,
846
+ ].join(" ");
815
847
  }
816
848
 
817
849
  /**
@@ -824,7 +856,7 @@ export function rectToPath(x, y, width, height, rx = 0, ry = null) {
824
856
  * @returns {string} Path data string
825
857
  */
826
858
  export function lineToPath(x1, y1, x2, y2) {
827
- const D = n => new Decimal(n);
859
+ const D = (n) => new Decimal(n);
828
860
  return `M ${D(x1).toFixed(6)} ${D(y1).toFixed(6)} L ${D(x2).toFixed(6)} ${D(y2).toFixed(6)}`;
829
861
  }
830
862
 
@@ -856,12 +888,12 @@ export function lineToPath(x1, y1, x2, y2) {
856
888
  */
857
889
  export function polygonToPath(points) {
858
890
  const pairs = parsePointPairs(points);
859
- if (pairs.length === 0) return '';
891
+ if (pairs.length === 0) return "";
860
892
  let d = `M ${pairs[0][0]} ${pairs[0][1]}`;
861
893
  for (let i = 1; i < pairs.length; i++) {
862
894
  d += ` L ${pairs[i][0]} ${pairs[i][1]}`;
863
895
  }
864
- return d + ' Z';
896
+ return d + " Z";
865
897
  }
866
898
 
867
899
  /**
@@ -889,7 +921,7 @@ export function polygonToPath(points) {
889
921
  */
890
922
  export function polylineToPath(points) {
891
923
  const pairs = parsePointPairs(points);
892
- if (pairs.length === 0) return '';
924
+ if (pairs.length === 0) return "";
893
925
  let d = `M ${pairs[0][0]} ${pairs[0][1]}`;
894
926
  for (let i = 1; i < pairs.length; i++) {
895
927
  d += ` L ${pairs[i][0]} ${pairs[i][1]}`;
@@ -916,9 +948,12 @@ export function polylineToPath(points) {
916
948
  function parsePointPairs(points) {
917
949
  let coords;
918
950
  if (Array.isArray(points)) {
919
- coords = points.flat().map(n => new Decimal(n).toFixed(6));
951
+ coords = points.flat().map((n) => new Decimal(n).toFixed(6));
920
952
  } else {
921
- coords = points.trim().split(/[\s,]+/).map(s => new Decimal(s).toFixed(6));
953
+ coords = points
954
+ .trim()
955
+ .split(/[\s,]+/)
956
+ .map((s) => new Decimal(s).toFixed(6));
922
957
  }
923
958
  const pairs = [];
924
959
  for (let i = 0; i < coords.length - 1; i += 2) {
@@ -999,9 +1034,18 @@ function parsePointPairs(points) {
999
1034
  * const arc = transformArc(10, 10, 0, 0, 1, 100, 0, matrix);
1000
1035
  * // Result: sweepFlag flipped from 1 to 0 (direction reversed)
1001
1036
  */
1002
- export function transformArc(rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y, matrix) {
1003
- const D = n => new Decimal(n);
1004
- const NEAR_ZERO = D('1e-16');
1037
+ export function transformArc(
1038
+ rx,
1039
+ ry,
1040
+ xAxisRotation,
1041
+ largeArcFlag,
1042
+ sweepFlag,
1043
+ x,
1044
+ y,
1045
+ matrix,
1046
+ ) {
1047
+ const D = (n) => new Decimal(n);
1048
+ const NEAR_ZERO = D("1e-16");
1005
1049
 
1006
1050
  // Get matrix components
1007
1051
  const a = matrix.data[0][0];
@@ -1012,7 +1056,8 @@ export function transformArc(rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x,
1012
1056
  const f = matrix.data[1][2];
1013
1057
 
1014
1058
  // Transform the endpoint
1015
- const xD = D(x), yD = D(y);
1059
+ const xD = D(x),
1060
+ yD = D(y);
1016
1061
  const newX = a.mul(xD).plus(c.mul(yD)).plus(e);
1017
1062
  const newY = b.mul(xD).plus(d.mul(yD)).plus(f);
1018
1063
 
@@ -1021,7 +1066,8 @@ export function transformArc(rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x,
1021
1066
  const sinRot = Decimal.sin(rotRad);
1022
1067
  const cosRot = Decimal.cos(rotRad);
1023
1068
 
1024
- const rxD = D(rx), ryD = D(ry);
1069
+ const rxD = D(rx),
1070
+ ryD = D(ry);
1025
1071
 
1026
1072
  // Transform the ellipse axes using the algorithm from lean-svg
1027
1073
  // m0, m1 represent the transformed X-axis direction of the ellipse
@@ -1049,12 +1095,14 @@ export function transformArc(rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x,
1049
1095
  C2 = C;
1050
1096
  } else if (AC.abs().lt(NEAR_ZERO)) {
1051
1097
  // 45 degree case
1052
- A2 = A.plus(B.mul('0.5'));
1053
- C2 = A.minus(B.mul('0.5'));
1098
+ A2 = A.plus(B.mul("0.5"));
1099
+ C2 = A.minus(B.mul("0.5"));
1054
1100
  newRotRad = D(Math.PI).div(4);
1055
1101
  } else {
1056
1102
  // General case - compute eigenvalues
1057
- const K = D(1).plus(B.mul(B).div(AC.mul(AC))).sqrt();
1103
+ const K = D(1)
1104
+ .plus(B.mul(B).div(AC.mul(AC)))
1105
+ .sqrt();
1058
1106
  A2 = A.plus(C).plus(K.mul(AC)).div(2);
1059
1107
  C2 = A.plus(C).minus(K.mul(AC)).div(2);
1060
1108
  newRotRad = Decimal.atan(B.div(AC)).div(2);
@@ -1102,7 +1150,7 @@ export function transformArc(rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x,
1102
1150
  largeArcFlag: largeArcFlag,
1103
1151
  sweepFlag: newSweepFlag,
1104
1152
  x: newX.toNumber(),
1105
- y: newY.toNumber()
1153
+ y: newY.toNumber(),
1106
1154
  };
1107
1155
  }
1108
1156
 
@@ -1162,22 +1210,22 @@ export function transformArc(rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x,
1162
1210
  * // Translation by (50, 50) specified in matrix form
1163
1211
  */
1164
1212
  export function parseTransformFunction(func, args) {
1165
- const D = x => new Decimal(x);
1213
+ const D = (x) => new Decimal(x);
1166
1214
 
1167
1215
  switch (func.toLowerCase()) {
1168
- case 'translate': {
1216
+ case "translate": {
1169
1217
  const tx = args[0] || 0;
1170
1218
  const ty = args[1] || 0;
1171
1219
  return Transforms2D.translation(tx, ty);
1172
1220
  }
1173
1221
 
1174
- case 'scale': {
1222
+ case "scale": {
1175
1223
  const sx = args[0] || 1;
1176
1224
  const sy = args[1] !== undefined ? args[1] : sx;
1177
1225
  return Transforms2D.scale(sx, sy);
1178
1226
  }
1179
1227
 
1180
- case 'rotate': {
1228
+ case "rotate": {
1181
1229
  // SVG rotate is in degrees, can have optional cx, cy
1182
1230
  const angleDeg = args[0] || 0;
1183
1231
  const angleRad = D(angleDeg).mul(D(Math.PI)).div(180);
@@ -1191,29 +1239,29 @@ export function parseTransformFunction(func, args) {
1191
1239
  return Transforms2D.rotate(angleRad);
1192
1240
  }
1193
1241
 
1194
- case 'skewx': {
1242
+ case "skewx": {
1195
1243
  const angleDeg = args[0] || 0;
1196
1244
  const angleRad = D(angleDeg).mul(D(Math.PI)).div(180);
1197
1245
  const tanVal = Decimal.tan(angleRad);
1198
1246
  return Transforms2D.skew(tanVal, 0);
1199
1247
  }
1200
1248
 
1201
- case 'skewy': {
1249
+ case "skewy": {
1202
1250
  const angleDeg = args[0] || 0;
1203
1251
  const angleRad = D(angleDeg).mul(D(Math.PI)).div(180);
1204
1252
  const tanVal = Decimal.tan(angleRad);
1205
1253
  return Transforms2D.skew(0, tanVal);
1206
1254
  }
1207
1255
 
1208
- case 'matrix': {
1256
+ case "matrix": {
1209
1257
  // matrix(a, b, c, d, e, f) -> | a c e |
1210
1258
  // | b d f |
1211
1259
  // | 0 0 1 |
1212
- const [a, b, c, d, e, f] = args.map(x => D(x || 0));
1260
+ const [a, b, c, d, e, f] = args.map((x) => D(x || 0));
1213
1261
  return Matrix.from([
1214
1262
  [a, c, e],
1215
1263
  [b, d, f],
1216
- [D(0), D(0), D(1)]
1264
+ [D(0), D(0), D(1)],
1217
1265
  ]);
1218
1266
  }
1219
1267
 
@@ -1267,7 +1315,7 @@ export function parseTransformFunction(func, args) {
1267
1315
  * // Returns: Identity matrix (no transformation)
1268
1316
  */
1269
1317
  export function parseTransformAttribute(transformStr) {
1270
- if (!transformStr || transformStr.trim() === '') {
1318
+ if (!transformStr || transformStr.trim() === "") {
1271
1319
  return Matrix.identity(3);
1272
1320
  }
1273
1321
 
@@ -1283,8 +1331,8 @@ export function parseTransformAttribute(transformStr) {
1283
1331
  // Parse arguments (comma or space separated)
1284
1332
  const args = argsStr
1285
1333
  .split(/[\s,]+/)
1286
- .filter(s => s.length > 0)
1287
- .map(s => parseFloat(s));
1334
+ .filter((s) => s.length > 0)
1335
+ .map((s) => parseFloat(s));
1288
1336
 
1289
1337
  const matrix = parseTransformFunction(func, args);
1290
1338
  // Transforms are applied left-to-right in SVG, so we multiply in order
@@ -1470,7 +1518,7 @@ export function toSVGMatrix(ctm, precision = 6) {
1470
1518
  * const result = isIdentity(translation);
1471
1519
  * // Returns: false
1472
1520
  */
1473
- export function isIdentity(m, tolerance = '1e-10') {
1521
+ export function isIdentity(m, tolerance = "1e-10") {
1474
1522
  const identity = Matrix.identity(3);
1475
1523
  return m.equals(identity, tolerance);
1476
1524
  }
@@ -1538,89 +1586,118 @@ export function isIdentity(m, tolerance = '1e-10') {
1538
1586
  */
1539
1587
  export function transformPathData(pathData, ctm, options = {}) {
1540
1588
  const { toAbsolute = true, precision = 6 } = options;
1541
- const D = x => new Decimal(x);
1589
+ const D = (x) => new Decimal(x);
1542
1590
 
1543
1591
  // Parse path into commands
1544
1592
  const commands = parsePathCommands(pathData);
1545
1593
  const result = [];
1546
1594
 
1547
1595
  // Track current position for relative commands
1548
- let curX = D(0), curY = D(0);
1549
- let subpathStartX = D(0), subpathStartY = D(0);
1596
+ let curX = D(0),
1597
+ curY = D(0);
1598
+ let subpathStartX = D(0),
1599
+ subpathStartY = D(0);
1550
1600
 
1551
1601
  for (const { cmd, args } of commands) {
1552
1602
  const isRelative = cmd === cmd.toLowerCase();
1553
1603
  const cmdUpper = cmd.toUpperCase();
1554
1604
 
1555
1605
  switch (cmdUpper) {
1556
- case 'M': {
1606
+ case "M": {
1557
1607
  const transformed = [];
1558
1608
  for (let i = 0; i < args.length; i += 2) {
1559
- let x = D(args[i]), y = D(args[i + 1]);
1560
- if (isRelative) { x = x.plus(curX); y = y.plus(curY); }
1609
+ let x = D(args[i]),
1610
+ y = D(args[i + 1]);
1611
+ if (isRelative) {
1612
+ x = x.plus(curX);
1613
+ y = y.plus(curY);
1614
+ }
1561
1615
 
1562
1616
  const pt = applyToPoint(ctm, x, y);
1563
1617
  transformed.push(pt.x.toFixed(precision), pt.y.toFixed(precision));
1564
1618
 
1565
- curX = x; curY = y;
1566
- if (i === 0) { subpathStartX = x; subpathStartY = y; }
1619
+ curX = x;
1620
+ curY = y;
1621
+ if (i === 0) {
1622
+ subpathStartX = x;
1623
+ subpathStartY = y;
1624
+ }
1567
1625
  }
1568
- result.push((toAbsolute ? 'M' : cmd) + ' ' + transformed.join(' '));
1626
+ result.push((toAbsolute ? "M" : cmd) + " " + transformed.join(" "));
1569
1627
  break;
1570
1628
  }
1571
1629
 
1572
- case 'L': {
1630
+ case "L": {
1573
1631
  const transformed = [];
1574
1632
  for (let i = 0; i < args.length; i += 2) {
1575
- let x = D(args[i]), y = D(args[i + 1]);
1576
- if (isRelative) { x = x.plus(curX); y = y.plus(curY); }
1633
+ let x = D(args[i]),
1634
+ y = D(args[i + 1]);
1635
+ if (isRelative) {
1636
+ x = x.plus(curX);
1637
+ y = y.plus(curY);
1638
+ }
1577
1639
 
1578
1640
  const pt = applyToPoint(ctm, x, y);
1579
1641
  transformed.push(pt.x.toFixed(precision), pt.y.toFixed(precision));
1580
1642
 
1581
- curX = x; curY = y;
1643
+ curX = x;
1644
+ curY = y;
1582
1645
  }
1583
- result.push((toAbsolute ? 'L' : cmd) + ' ' + transformed.join(' '));
1646
+ result.push((toAbsolute ? "L" : cmd) + " " + transformed.join(" "));
1584
1647
  break;
1585
1648
  }
1586
1649
 
1587
- case 'H': {
1650
+ case "H": {
1588
1651
  // Horizontal line becomes L after transform (may have Y component)
1589
1652
  let x = D(args[0]);
1590
- if (isRelative) { x = x.plus(curX); }
1653
+ if (isRelative) {
1654
+ x = x.plus(curX);
1655
+ }
1591
1656
  const y = curY;
1592
1657
 
1593
1658
  const pt = applyToPoint(ctm, x, y);
1594
- result.push('L ' + pt.x.toFixed(precision) + ' ' + pt.y.toFixed(precision));
1659
+ result.push(
1660
+ "L " + pt.x.toFixed(precision) + " " + pt.y.toFixed(precision),
1661
+ );
1595
1662
 
1596
1663
  curX = x;
1597
1664
  break;
1598
1665
  }
1599
1666
 
1600
- case 'V': {
1667
+ case "V": {
1601
1668
  // Vertical line becomes L after transform (may have X component)
1602
1669
  const x = curX;
1603
1670
  let y = D(args[0]);
1604
- if (isRelative) { y = y.plus(curY); }
1671
+ if (isRelative) {
1672
+ y = y.plus(curY);
1673
+ }
1605
1674
 
1606
1675
  const pt = applyToPoint(ctm, x, y);
1607
- result.push('L ' + pt.x.toFixed(precision) + ' ' + pt.y.toFixed(precision));
1676
+ result.push(
1677
+ "L " + pt.x.toFixed(precision) + " " + pt.y.toFixed(precision),
1678
+ );
1608
1679
 
1609
1680
  curY = y;
1610
1681
  break;
1611
1682
  }
1612
1683
 
1613
- case 'C': {
1684
+ case "C": {
1614
1685
  const transformed = [];
1615
1686
  for (let i = 0; i < args.length; i += 6) {
1616
- let x1 = D(args[i]), y1 = D(args[i + 1]);
1617
- let x2 = D(args[i + 2]), y2 = D(args[i + 3]);
1618
- let x = D(args[i + 4]), y = D(args[i + 5]);
1687
+ let x1 = D(args[i]),
1688
+ y1 = D(args[i + 1]);
1689
+ let x2 = D(args[i + 2]),
1690
+ y2 = D(args[i + 3]);
1691
+ let x = D(args[i + 4]),
1692
+ y = D(args[i + 5]);
1619
1693
 
1620
1694
  if (isRelative) {
1621
- x1 = x1.plus(curX); y1 = y1.plus(curY);
1622
- x2 = x2.plus(curX); y2 = y2.plus(curY);
1623
- x = x.plus(curX); y = y.plus(curY);
1695
+ x1 = x1.plus(curX);
1696
+ y1 = y1.plus(curY);
1697
+ x2 = x2.plus(curX);
1698
+ y2 = y2.plus(curY);
1699
+ x = x.plus(curX);
1700
+ y = y.plus(curY);
1624
1701
  }
1625
1702
 
1626
1703
  const p1 = applyToPoint(ctm, x1, y1);
@@ -1628,83 +1705,106 @@ export function transformPathData(pathData, ctm, options = {}) {
1628
1705
  const p = applyToPoint(ctm, x, y);
1629
1706
 
1630
1707
  transformed.push(
1631
- p1.x.toFixed(precision), p1.y.toFixed(precision),
1632
- p2.x.toFixed(precision), p2.y.toFixed(precision),
1633
- p.x.toFixed(precision), p.y.toFixed(precision)
1708
+ p1.x.toFixed(precision),
1709
+ p1.y.toFixed(precision),
1710
+ p2.x.toFixed(precision),
1711
+ p2.y.toFixed(precision),
1712
+ p.x.toFixed(precision),
1713
+ p.y.toFixed(precision),
1634
1714
  );
1635
1715
 
1636
- curX = x; curY = y;
1716
+ curX = x;
1717
+ curY = y;
1637
1718
  }
1638
- result.push((toAbsolute ? 'C' : cmd) + ' ' + transformed.join(' '));
1719
+ result.push((toAbsolute ? "C" : cmd) + " " + transformed.join(" "));
1639
1720
  break;
1640
1721
  }
1641
1722
 
1642
- case 'S': {
1723
+ case "S": {
1643
1724
  const transformed = [];
1644
1725
  for (let i = 0; i < args.length; i += 4) {
1645
- let x2 = D(args[i]), y2 = D(args[i + 1]);
1646
- let x = D(args[i + 2]), y = D(args[i + 3]);
1726
+ let x2 = D(args[i]),
1727
+ y2 = D(args[i + 1]);
1728
+ let x = D(args[i + 2]),
1729
+ y = D(args[i + 3]);
1647
1730
 
1648
1731
  if (isRelative) {
1649
- x2 = x2.plus(curX); y2 = y2.plus(curY);
1650
- x = x.plus(curX); y = y.plus(curY);
1732
+ x2 = x2.plus(curX);
1733
+ y2 = y2.plus(curY);
1734
+ x = x.plus(curX);
1735
+ y = y.plus(curY);
1651
1736
  }
1652
1737
 
1653
1738
  const p2 = applyToPoint(ctm, x2, y2);
1654
1739
  const p = applyToPoint(ctm, x, y);
1655
1740
 
1656
1741
  transformed.push(
1657
- p2.x.toFixed(precision), p2.y.toFixed(precision),
1658
- p.x.toFixed(precision), p.y.toFixed(precision)
1742
+ p2.x.toFixed(precision),
1743
+ p2.y.toFixed(precision),
1744
+ p.x.toFixed(precision),
1745
+ p.y.toFixed(precision),
1659
1746
  );
1660
1747
 
1661
- curX = x; curY = y;
1748
+ curX = x;
1749
+ curY = y;
1662
1750
  }
1663
- result.push((toAbsolute ? 'S' : cmd) + ' ' + transformed.join(' '));
1751
+ result.push((toAbsolute ? "S" : cmd) + " " + transformed.join(" "));
1664
1752
  break;
1665
1753
  }
1666
1754
 
1667
- case 'Q': {
1755
+ case "Q": {
1668
1756
  const transformed = [];
1669
1757
  for (let i = 0; i < args.length; i += 4) {
1670
- let x1 = D(args[i]), y1 = D(args[i + 1]);
1671
- let x = D(args[i + 2]), y = D(args[i + 3]);
1758
+ let x1 = D(args[i]),
1759
+ y1 = D(args[i + 1]);
1760
+ let x = D(args[i + 2]),
1761
+ y = D(args[i + 3]);
1672
1762
 
1673
1763
  if (isRelative) {
1674
- x1 = x1.plus(curX); y1 = y1.plus(curY);
1675
- x = x.plus(curX); y = y.plus(curY);
1764
+ x1 = x1.plus(curX);
1765
+ y1 = y1.plus(curY);
1766
+ x = x.plus(curX);
1767
+ y = y.plus(curY);
1676
1768
  }
1677
1769
 
1678
1770
  const p1 = applyToPoint(ctm, x1, y1);
1679
1771
  const p = applyToPoint(ctm, x, y);
1680
1772
 
1681
1773
  transformed.push(
1682
- p1.x.toFixed(precision), p1.y.toFixed(precision),
1683
- p.x.toFixed(precision), p.y.toFixed(precision)
1774
+ p1.x.toFixed(precision),
1775
+ p1.y.toFixed(precision),
1776
+ p.x.toFixed(precision),
1777
+ p.y.toFixed(precision),
1684
1778
  );
1685
1779
 
1686
- curX = x; curY = y;
1780
+ curX = x;
1781
+ curY = y;
1687
1782
  }
1688
- result.push((toAbsolute ? 'Q' : cmd) + ' ' + transformed.join(' '));
1783
+ result.push((toAbsolute ? "Q" : cmd) + " " + transformed.join(" "));
1689
1784
  break;
1690
1785
  }
1691
1786
 
1692
- case 'T': {
1787
+ case "T": {
1693
1788
  const transformed = [];
1694
1789
  for (let i = 0; i < args.length; i += 2) {
1695
- let x = D(args[i]), y = D(args[i + 1]);
1696
- if (isRelative) { x = x.plus(curX); y = y.plus(curY); }
1790
+ let x = D(args[i]),
1791
+ y = D(args[i + 1]);
1792
+ if (isRelative) {
1793
+ x = x.plus(curX);
1794
+ y = y.plus(curY);
1795
+ }
1697
1796
 
1698
1797
  const pt = applyToPoint(ctm, x, y);
1699
1798
  transformed.push(pt.x.toFixed(precision), pt.y.toFixed(precision));
1700
1799
 
1701
- curX = x; curY = y;
1800
+ curX = x;
1801
+ curY = y;
1702
1802
  }
1703
- result.push((toAbsolute ? 'T' : cmd) + ' ' + transformed.join(' '));
1803
+ result.push((toAbsolute ? "T" : cmd) + " " + transformed.join(" "));
1704
1804
  break;
1705
1805
  }
1706
1806
 
1707
- case 'A': {
1807
+ case "A": {
1708
1808
  // Use proper arc transformation
1709
1809
  const transformed = [];
1710
1810
  for (let i = 0; i < args.length; i += 7) {
@@ -1713,11 +1813,24 @@ export function transformPathData(pathData, ctm, options = {}) {
1713
1813
  const rotation = args[i + 2];
1714
1814
  const largeArc = args[i + 3];
1715
1815
  const sweep = args[i + 4];
1716
- let x = D(args[i + 5]), y = D(args[i + 6]);
1816
+ let x = D(args[i + 5]),
1817
+ y = D(args[i + 6]);
1717
1818
 
1718
- if (isRelative) { x = x.plus(curX); y = y.plus(curY); }
1819
+ if (isRelative) {
1820
+ x = x.plus(curX);
1821
+ y = y.plus(curY);
1822
+ }
1719
1823
 
1720
- const arc = transformArc(rx, ry, rotation, largeArc, sweep, x.toNumber(), y.toNumber(), ctm);
1824
+ const arc = transformArc(
1825
+ rx,
1826
+ ry,
1827
+ rotation,
1828
+ largeArc,
1829
+ sweep,
1830
+ x.toNumber(),
1831
+ y.toNumber(),
1832
+ ctm,
1833
+ );
1721
1834
 
1722
1835
  transformed.push(
1723
1836
  arc.rx.toFixed(precision),
@@ -1726,17 +1839,18 @@ export function transformPathData(pathData, ctm, options = {}) {
1726
1839
  arc.largeArcFlag,
1727
1840
  arc.sweepFlag,
1728
1841
  arc.x.toFixed(precision),
1729
- arc.y.toFixed(precision)
1842
+ arc.y.toFixed(precision),
1730
1843
  );
1731
1844
 
1732
- curX = x; curY = y;
1845
+ curX = x;
1846
+ curY = y;
1733
1847
  }
1734
- result.push((toAbsolute ? 'A' : cmd) + ' ' + transformed.join(' '));
1848
+ result.push((toAbsolute ? "A" : cmd) + " " + transformed.join(" "));
1735
1849
  break;
1736
1850
  }
1737
1851
 
1738
- case 'Z': {
1739
- result.push('Z');
1852
+ case "Z": {
1853
+ result.push("Z");
1740
1854
  curX = subpathStartX;
1741
1855
  curY = subpathStartY;
1742
1856
  break;
@@ -1744,11 +1858,11 @@ export function transformPathData(pathData, ctm, options = {}) {
1744
1858
 
1745
1859
  default:
1746
1860
  // Keep unknown commands as-is
1747
- result.push(cmd + ' ' + args.join(' '));
1861
+ result.push(cmd + " " + args.join(" "));
1748
1862
  }
1749
1863
  }
1750
1864
 
1751
- return result.join(' ');
1865
+ return result.join(" ");
1752
1866
  }
1753
1867
 
1754
1868
  /**
@@ -1792,9 +1906,13 @@ function parsePathCommands(pathData) {
1792
1906
  while ((match = commandRegex.exec(pathData)) !== null) {
1793
1907
  const cmd = match[1];
1794
1908
  const argsStr = match[2].trim();
1795
- const args = argsStr.length > 0
1796
- ? argsStr.split(/[\s,]+/).filter(s => s.length > 0).map(s => parseFloat(s))
1797
- : [];
1909
+ const args =
1910
+ argsStr.length > 0
1911
+ ? argsStr
1912
+ .split(/[\s,]+/)
1913
+ .filter((s) => s.length > 0)
1914
+ .map((s) => parseFloat(s))
1915
+ : [];
1798
1916
  commands.push({ cmd, args });
1799
1917
  }
1800
1918
 
@@ -1833,11 +1951,11 @@ function parsePathCommands(pathData) {
1833
1951
  * @property {string} improvementFactorGIS - Improvement for GIS/CAD ('1e+93')
1834
1952
  */
1835
1953
  export const PRECISION_INFO = {
1836
- floatErrorGIS: 1.69e-7, // Error with 1e6+ scale coordinates
1837
- floatErrorTypical: 1.14e-13, // Error with typical 6-level SVG hierarchy
1954
+ floatErrorGIS: 1.69e-7, // Error with 1e6+ scale coordinates
1955
+ floatErrorTypical: 1.14e-13, // Error with typical 6-level SVG hierarchy
1838
1956
  decimalPrecision: 80,
1839
- typicalRoundTripError: '1e-77',
1840
- improvementFactorGIS: '1e+93'
1957
+ typicalRoundTripError: "1e-77",
1958
+ improvementFactorGIS: "1e+93",
1841
1959
  };
1842
1960
 
1843
1961
  export default {
@@ -1870,5 +1988,5 @@ export default {
1870
1988
  toSVGMatrix,
1871
1989
  isIdentity,
1872
1990
  transformPathData,
1873
- PRECISION_INFO
1991
+ PRECISION_INFO,
1874
1992
  };