@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
@@ -52,7 +52,6 @@ const DEFAULT_TOLERANCE = new Decimal('1e-10');
52
52
  /**
53
53
  * Implementation of atan2 using Decimal.js (which doesn't provide it natively).
54
54
  * Returns the angle in radians between the positive x-axis and the ray from (0,0) to (x,y).
55
- *
56
55
  * @param {Decimal} y - Y coordinate
57
56
  * @param {Decimal} x - X coordinate
58
57
  * @returns {Decimal} Angle in radians (-π to π)
@@ -14,15 +14,15 @@
14
14
  * @module pattern-resolver
15
15
  */
16
16
 
17
- import Decimal from 'decimal.js';
18
- import { Matrix } from './matrix.js';
19
- import * as Transforms2D from './transforms2d.js';
20
- import * as PolygonClip from './polygon-clip.js';
21
- import * as ClipPathResolver from './clip-path-resolver.js';
17
+ import Decimal from "decimal.js";
18
+ import { Matrix } from "./matrix.js";
19
+ import * as Transforms2D from "./transforms2d.js";
20
+ import * as PolygonClip from "./polygon-clip.js";
21
+ import * as ClipPathResolver from "./clip-path-resolver.js";
22
22
 
23
23
  Decimal.set({ precision: 80 });
24
24
 
25
- const D = x => (x instanceof Decimal ? x : new Decimal(x));
25
+ const D = (x) => (x instanceof Decimal ? x : new Decimal(x));
26
26
 
27
27
  /**
28
28
  * Parse pattern element to structured data
@@ -72,19 +72,24 @@ const D = x => (x instanceof Decimal ? x : new Decimal(x));
72
72
  */
73
73
  export function parsePatternElement(patternElement) {
74
74
  const data = {
75
- id: patternElement.getAttribute('id') || '',
76
- patternUnits: patternElement.getAttribute('patternUnits') || 'objectBoundingBox',
77
- patternContentUnits: patternElement.getAttribute('patternContentUnits') || 'userSpaceOnUse',
78
- patternTransform: patternElement.getAttribute('patternTransform') || null,
79
- x: patternElement.getAttribute('x'),
80
- y: patternElement.getAttribute('y'),
81
- width: patternElement.getAttribute('width'),
82
- height: patternElement.getAttribute('height'),
83
- viewBox: patternElement.getAttribute('viewBox') || null,
84
- preserveAspectRatio: patternElement.getAttribute('preserveAspectRatio') || 'xMidYMid meet',
85
- href: patternElement.getAttribute('href') ||
86
- patternElement.getAttribute('xlink:href') || null,
87
- children: []
75
+ id: patternElement.getAttribute("id") || "",
76
+ patternUnits:
77
+ patternElement.getAttribute("patternUnits") || "objectBoundingBox",
78
+ patternContentUnits:
79
+ patternElement.getAttribute("patternContentUnits") || "userSpaceOnUse",
80
+ patternTransform: patternElement.getAttribute("patternTransform") || null,
81
+ x: patternElement.getAttribute("x"),
82
+ y: patternElement.getAttribute("y"),
83
+ width: patternElement.getAttribute("width"),
84
+ height: patternElement.getAttribute("height"),
85
+ viewBox: patternElement.getAttribute("viewBox") || null,
86
+ preserveAspectRatio:
87
+ patternElement.getAttribute("preserveAspectRatio") || "xMidYMid meet",
88
+ href:
89
+ patternElement.getAttribute("href") ||
90
+ patternElement.getAttribute("xlink:href") ||
91
+ null,
92
+ children: [],
88
93
  };
89
94
 
90
95
  // Parse numeric values
@@ -95,13 +100,16 @@ export function parsePatternElement(patternElement) {
95
100
 
96
101
  // Parse viewBox if present
97
102
  if (data.viewBox) {
98
- const parts = data.viewBox.trim().split(/[\s,]+/).map(Number);
103
+ const parts = data.viewBox
104
+ .trim()
105
+ .split(/[\s,]+/)
106
+ .map(Number);
99
107
  if (parts.length === 4) {
100
108
  data.viewBoxParsed = {
101
109
  x: parts[0],
102
110
  y: parts[1],
103
111
  width: parts[2],
104
- height: parts[3]
112
+ height: parts[3],
105
113
  };
106
114
  }
107
115
  }
@@ -111,62 +119,62 @@ export function parsePatternElement(patternElement) {
111
119
  const tagName = child.tagName.toLowerCase();
112
120
  const childData = {
113
121
  type: tagName,
114
- fill: child.getAttribute('fill') || 'black',
115
- stroke: child.getAttribute('stroke') || 'none',
116
- strokeWidth: parseFloat(child.getAttribute('stroke-width') || '1'),
117
- opacity: parseFloat(child.getAttribute('opacity') || '1'),
118
- transform: child.getAttribute('transform') || null
122
+ fill: child.getAttribute("fill") || "black",
123
+ stroke: child.getAttribute("stroke") || "none",
124
+ strokeWidth: parseFloat(child.getAttribute("stroke-width") || "1"),
125
+ opacity: parseFloat(child.getAttribute("opacity") || "1"),
126
+ transform: child.getAttribute("transform") || null,
119
127
  };
120
128
 
121
129
  // Parse shape-specific attributes
122
130
  switch (tagName) {
123
- case 'rect':
124
- childData.x = parseFloat(child.getAttribute('x') || '0');
125
- childData.y = parseFloat(child.getAttribute('y') || '0');
126
- childData.width = parseFloat(child.getAttribute('width') || '0');
127
- childData.height = parseFloat(child.getAttribute('height') || '0');
128
- childData.rx = parseFloat(child.getAttribute('rx') || '0');
129
- childData.ry = parseFloat(child.getAttribute('ry') || '0');
131
+ case "rect":
132
+ childData.x = parseFloat(child.getAttribute("x") || "0");
133
+ childData.y = parseFloat(child.getAttribute("y") || "0");
134
+ childData.width = parseFloat(child.getAttribute("width") || "0");
135
+ childData.height = parseFloat(child.getAttribute("height") || "0");
136
+ childData.rx = parseFloat(child.getAttribute("rx") || "0");
137
+ childData.ry = parseFloat(child.getAttribute("ry") || "0");
130
138
  break;
131
- case 'circle':
132
- childData.cx = parseFloat(child.getAttribute('cx') || '0');
133
- childData.cy = parseFloat(child.getAttribute('cy') || '0');
134
- childData.r = parseFloat(child.getAttribute('r') || '0');
139
+ case "circle":
140
+ childData.cx = parseFloat(child.getAttribute("cx") || "0");
141
+ childData.cy = parseFloat(child.getAttribute("cy") || "0");
142
+ childData.r = parseFloat(child.getAttribute("r") || "0");
135
143
  break;
136
- case 'ellipse':
137
- childData.cx = parseFloat(child.getAttribute('cx') || '0');
138
- childData.cy = parseFloat(child.getAttribute('cy') || '0');
139
- childData.rx = parseFloat(child.getAttribute('rx') || '0');
140
- childData.ry = parseFloat(child.getAttribute('ry') || '0');
144
+ case "ellipse":
145
+ childData.cx = parseFloat(child.getAttribute("cx") || "0");
146
+ childData.cy = parseFloat(child.getAttribute("cy") || "0");
147
+ childData.rx = parseFloat(child.getAttribute("rx") || "0");
148
+ childData.ry = parseFloat(child.getAttribute("ry") || "0");
141
149
  break;
142
- case 'path':
143
- childData.d = child.getAttribute('d') || '';
150
+ case "path":
151
+ childData.d = child.getAttribute("d") || "";
144
152
  break;
145
- case 'polygon':
146
- childData.points = child.getAttribute('points') || '';
153
+ case "polygon":
154
+ childData.points = child.getAttribute("points") || "";
147
155
  break;
148
- case 'polyline':
149
- childData.points = child.getAttribute('points') || '';
156
+ case "polyline":
157
+ childData.points = child.getAttribute("points") || "";
150
158
  break;
151
- case 'line':
152
- childData.x1 = parseFloat(child.getAttribute('x1') || '0');
153
- childData.y1 = parseFloat(child.getAttribute('y1') || '0');
154
- childData.x2 = parseFloat(child.getAttribute('x2') || '0');
155
- childData.y2 = parseFloat(child.getAttribute('y2') || '0');
159
+ case "line":
160
+ childData.x1 = parseFloat(child.getAttribute("x1") || "0");
161
+ childData.y1 = parseFloat(child.getAttribute("y1") || "0");
162
+ childData.x2 = parseFloat(child.getAttribute("x2") || "0");
163
+ childData.y2 = parseFloat(child.getAttribute("y2") || "0");
156
164
  break;
157
- case 'use':
158
- childData.href = child.getAttribute('href') ||
159
- child.getAttribute('xlink:href') || '';
160
- childData.x = parseFloat(child.getAttribute('x') || '0');
161
- childData.y = parseFloat(child.getAttribute('y') || '0');
165
+ case "use":
166
+ childData.href =
167
+ child.getAttribute("href") || child.getAttribute("xlink:href") || "";
168
+ childData.x = parseFloat(child.getAttribute("x") || "0");
169
+ childData.y = parseFloat(child.getAttribute("y") || "0");
162
170
  break;
163
- case 'g':
171
+ case "g":
164
172
  // Groups can contain nested shapes
165
173
  childData.children = [];
166
174
  for (const gc of child.children) {
167
175
  childData.children.push({
168
176
  type: gc.tagName.toLowerCase(),
169
- fill: gc.getAttribute('fill') || 'inherit'
177
+ fill: gc.getAttribute("fill") || "inherit",
170
178
  });
171
179
  }
172
180
  break;
@@ -225,13 +233,13 @@ export function parsePatternElement(patternElement) {
225
233
  * // Tile uses absolute coordinates, bbox is ignored
226
234
  */
227
235
  export function getPatternTile(patternData, targetBBox) {
228
- if (patternData.patternUnits === 'objectBoundingBox') {
236
+ if (patternData.patternUnits === "objectBoundingBox") {
229
237
  // Dimensions are fractions of target bbox
230
238
  return {
231
239
  x: D(targetBBox.x).plus(D(patternData.x).mul(targetBBox.width)),
232
240
  y: D(targetBBox.y).plus(D(patternData.y).mul(targetBBox.height)),
233
241
  width: D(patternData.width).mul(targetBBox.width),
234
- height: D(patternData.height).mul(targetBBox.height)
242
+ height: D(patternData.height).mul(targetBBox.height),
235
243
  };
236
244
  }
237
245
 
@@ -240,7 +248,7 @@ export function getPatternTile(patternData, targetBBox) {
240
248
  x: D(patternData.x),
241
249
  y: D(patternData.y),
242
250
  width: D(patternData.width),
243
- height: D(patternData.height)
251
+ height: D(patternData.height),
244
252
  };
245
253
  }
246
254
 
@@ -319,12 +327,14 @@ export function getPatternContentTransform(patternData, tile, targetBBox) {
319
327
  const offsetX = (tileWidth - vb.width * scale) / 2;
320
328
  const offsetY = (tileHeight - vb.height * scale) / 2;
321
329
 
322
- M = M.mul(Transforms2D.translation(offsetX - vb.x * scale, offsetY - vb.y * scale));
330
+ M = M.mul(
331
+ Transforms2D.translation(offsetX - vb.x * scale, offsetY - vb.y * scale),
332
+ );
323
333
  M = M.mul(Transforms2D.scale(scale, scale));
324
334
  }
325
335
 
326
336
  // Apply objectBoundingBox scaling if needed
327
- if (patternData.patternContentUnits === 'objectBoundingBox') {
337
+ if (patternData.patternContentUnits === "objectBoundingBox") {
328
338
  M = M.mul(Transforms2D.translation(targetBBox.x, targetBBox.y));
329
339
  M = M.mul(Transforms2D.scale(targetBBox.width, targetBBox.height));
330
340
  }
@@ -361,7 +371,7 @@ export function patternChildToPolygon(child, transform = null, samples = 20) {
361
371
  const element = {
362
372
  type: child.type,
363
373
  ...child,
364
- transform: child.transform
374
+ transform: child.transform,
365
375
  };
366
376
 
367
377
  // Get polygon using ClipPathResolver
@@ -369,7 +379,7 @@ export function patternChildToPolygon(child, transform = null, samples = 20) {
369
379
 
370
380
  // Apply additional transform if provided
371
381
  if (transform && polygon.length > 0) {
372
- polygon = polygon.map(p => {
382
+ polygon = polygon.map((p) => {
373
383
  const [x, y] = Transforms2D.applyTransform(transform, p.x, p.y);
374
384
  return { x, y };
375
385
  });
@@ -432,7 +442,7 @@ export function getTilePositions(tile, coverBBox) {
432
442
  for (let j = startJ; j < endJ; j++) {
433
443
  positions.push({
434
444
  x: D(tileX).plus(D(tileW).mul(i)),
435
- y: D(tileY).plus(D(tileH).mul(j))
445
+ y: D(tileY).plus(D(tileH).mul(j)),
436
446
  });
437
447
  }
438
448
  }
@@ -489,7 +499,11 @@ export function resolvePattern(patternData, targetBBox, options = {}) {
489
499
  }
490
500
 
491
501
  // Get content transform
492
- const contentTransform = getPatternContentTransform(patternData, tile, targetBBox);
502
+ const contentTransform = getPatternContentTransform(
503
+ patternData,
504
+ tile,
505
+ targetBBox,
506
+ );
493
507
 
494
508
  // Get tile positions
495
509
  const positions = getTilePositions(tile, targetBBox);
@@ -503,9 +517,9 @@ export function resolvePattern(patternData, targetBBox, options = {}) {
503
517
 
504
518
  for (const child of patternData.children) {
505
519
  // Combine transforms: tile position + content transform + child transform
506
- let M = tileTranslate.mul(contentTransform);
520
+ const M = tileTranslate.mul(contentTransform);
507
521
 
508
- let polygon = patternChildToPolygon(child, M, samples);
522
+ const polygon = patternChildToPolygon(child, M, samples);
509
523
 
510
524
  if (polygon.length >= 3) {
511
525
  result.push({
@@ -513,7 +527,7 @@ export function resolvePattern(patternData, targetBBox, options = {}) {
513
527
  fill: child.fill,
514
528
  stroke: child.stroke,
515
529
  strokeWidth: child.strokeWidth,
516
- opacity: child.opacity
530
+ opacity: child.opacity,
517
531
  });
518
532
  }
519
533
  }
@@ -558,21 +572,29 @@ export function resolvePattern(patternData, targetBBox, options = {}) {
558
572
  * const clippedParts = applyPattern(circlePolygon, patternData, bbox);
559
573
  * // Returns only the stripe portions visible within the circle
560
574
  */
561
- export function applyPattern(targetPolygon, patternData, targetBBox, options = {}) {
575
+ export function applyPattern(
576
+ targetPolygon,
577
+ patternData,
578
+ targetBBox,
579
+ options = {},
580
+ ) {
562
581
  const patternPolygons = resolvePattern(patternData, targetBBox, options);
563
582
  const result = [];
564
583
 
565
584
  for (const { polygon, fill, opacity } of patternPolygons) {
566
585
  if (opacity <= 0) continue;
567
586
 
568
- const intersection = PolygonClip.polygonIntersection(targetPolygon, polygon);
587
+ const intersection = PolygonClip.polygonIntersection(
588
+ targetPolygon,
589
+ polygon,
590
+ );
569
591
 
570
592
  for (const clippedPoly of intersection) {
571
593
  if (clippedPoly.length >= 3) {
572
594
  result.push({
573
595
  polygon: clippedPoly,
574
596
  fill,
575
- opacity
597
+ opacity,
576
598
  });
577
599
  }
578
600
  }
@@ -655,16 +677,16 @@ export function patternToClipPath(patternData, targetBBox, options = {}) {
655
677
  export function patternToPathData(patternData, targetBBox, options = {}) {
656
678
  const polygon = patternToClipPath(patternData, targetBBox, options);
657
679
 
658
- if (polygon.length < 3) return '';
680
+ if (polygon.length < 3) return "";
659
681
 
660
- let d = '';
682
+ let d = "";
661
683
  for (let i = 0; i < polygon.length; i++) {
662
684
  const p = polygon[i];
663
685
  const x = Number(p.x).toFixed(6);
664
686
  const y = Number(p.y).toFixed(6);
665
687
  d += i === 0 ? `M ${x} ${y}` : ` L ${x} ${y}`;
666
688
  }
667
- d += ' Z';
689
+ d += " Z";
668
690
 
669
691
  return d;
670
692
  }
@@ -707,7 +729,7 @@ export function getPatternTileCount(patternData, targetBBox) {
707
729
  return {
708
730
  columns,
709
731
  rows,
710
- total: columns * rows
732
+ total: columns * rows,
711
733
  };
712
734
  }
713
735
 
@@ -751,36 +773,36 @@ export function getPatternContentBBox(patternData) {
751
773
  let childBBox = null;
752
774
 
753
775
  switch (child.type) {
754
- case 'rect':
776
+ case "rect":
755
777
  childBBox = {
756
778
  x: child.x,
757
779
  y: child.y,
758
780
  width: child.width,
759
- height: child.height
781
+ height: child.height,
760
782
  };
761
783
  break;
762
- case 'circle':
784
+ case "circle":
763
785
  childBBox = {
764
786
  x: child.cx - child.r,
765
787
  y: child.cy - child.r,
766
788
  width: child.r * 2,
767
- height: child.r * 2
789
+ height: child.r * 2,
768
790
  };
769
791
  break;
770
- case 'ellipse':
792
+ case "ellipse":
771
793
  childBBox = {
772
794
  x: child.cx - child.rx,
773
795
  y: child.cy - child.ry,
774
796
  width: child.rx * 2,
775
- height: child.ry * 2
797
+ height: child.ry * 2,
776
798
  };
777
799
  break;
778
- case 'line':
800
+ case "line":
779
801
  childBBox = {
780
802
  x: Math.min(child.x1, child.x2),
781
803
  y: Math.min(child.y1, child.y2),
782
804
  width: Math.abs(child.x2 - child.x1),
783
- height: Math.abs(child.y2 - child.y1)
805
+ height: Math.abs(child.y2 - child.y1),
784
806
  };
785
807
  break;
786
808
  }
@@ -801,7 +823,7 @@ export function getPatternContentBBox(patternData) {
801
823
  x: minX,
802
824
  y: minY,
803
825
  width: maxX - minX,
804
- height: maxY - minY
826
+ height: maxY - minY,
805
827
  };
806
828
  }
807
829
 
@@ -842,3 +864,18 @@ export function parsePatternTransform(transformStr) {
842
864
  // Use ClipPathResolver's transform parser
843
865
  return ClipPathResolver.parseTransform(transformStr);
844
866
  }
867
+
868
+ export default {
869
+ parsePatternElement,
870
+ getPatternTile,
871
+ getPatternContentTransform,
872
+ patternChildToPolygon,
873
+ getTilePositions,
874
+ resolvePattern,
875
+ applyPattern,
876
+ patternToClipPath,
877
+ patternToPathData,
878
+ getPatternTileCount,
879
+ getPatternContentBBox,
880
+ parsePatternTransform,
881
+ };