@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.
Files changed (45) hide show
  1. package/bin/svg-matrix.js +310 -61
  2. package/bin/svglinter.cjs +102 -3
  3. package/bin/svgm.js +236 -27
  4. package/package.json +1 -1
  5. package/src/animation-optimization.js +137 -17
  6. package/src/animation-references.js +123 -6
  7. package/src/arc-length.js +213 -4
  8. package/src/bezier-analysis.js +217 -21
  9. package/src/bezier-intersections.js +275 -12
  10. package/src/browser-verify.js +237 -4
  11. package/src/clip-path-resolver.js +168 -0
  12. package/src/convert-path-data.js +479 -28
  13. package/src/css-specificity.js +73 -10
  14. package/src/douglas-peucker.js +219 -2
  15. package/src/flatten-pipeline.js +284 -26
  16. package/src/geometry-to-path.js +250 -25
  17. package/src/gjk-collision.js +236 -33
  18. package/src/index.js +261 -3
  19. package/src/inkscape-support.js +86 -28
  20. package/src/logger.js +48 -3
  21. package/src/marker-resolver.js +278 -74
  22. package/src/mask-resolver.js +265 -66
  23. package/src/matrix.js +44 -5
  24. package/src/mesh-gradient.js +352 -102
  25. package/src/off-canvas-detection.js +382 -13
  26. package/src/path-analysis.js +192 -18
  27. package/src/path-data-plugins.js +309 -5
  28. package/src/path-optimization.js +129 -5
  29. package/src/path-simplification.js +188 -32
  30. package/src/pattern-resolver.js +454 -106
  31. package/src/polygon-clip.js +324 -1
  32. package/src/svg-boolean-ops.js +226 -9
  33. package/src/svg-collections.js +7 -5
  34. package/src/svg-flatten.js +386 -62
  35. package/src/svg-parser.js +179 -8
  36. package/src/svg-rendering-context.js +235 -6
  37. package/src/svg-toolbox.js +45 -8
  38. package/src/svg2-polyfills.js +40 -10
  39. package/src/transform-decomposition.js +258 -32
  40. package/src/transform-optimization.js +259 -13
  41. package/src/transforms2d.js +82 -9
  42. package/src/transforms3d.js +62 -10
  43. package/src/use-symbol-resolver.js +286 -42
  44. package/src/vector.js +64 -8
  45. package/src/verification.js +392 -1
@@ -59,6 +59,15 @@ export function pointInPolygonWithRule(
59
59
  polygon,
60
60
  fillRule = FillRule.NONZERO,
61
61
  ) {
62
+ if (!pt || typeof pt !== "object") {
63
+ throw new Error("pointInPolygonWithRule: pt must be an object with x, y properties");
64
+ }
65
+ if (pt.x === undefined || pt.x === null || pt.y === undefined || pt.y === null) {
66
+ throw new Error("pointInPolygonWithRule: pt must have x and y properties");
67
+ }
68
+ if (!Array.isArray(polygon)) {
69
+ throw new Error("pointInPolygonWithRule: polygon must be an array");
70
+ }
62
71
  const n = polygon.length;
63
72
  if (n < 3) return -1;
64
73
 
@@ -109,6 +118,14 @@ export function pointInPolygonWithRule(
109
118
  * @returns {boolean} True if point is on the segment
110
119
  */
111
120
  function pointOnSegment(pt, a, b) {
121
+ if (!pt || !a || !b) {
122
+ throw new Error("pointOnSegment: pt, a, and b must be defined");
123
+ }
124
+ if (pt.x === undefined || pt.x === null || pt.y === undefined || pt.y === null ||
125
+ a.x === undefined || a.x === null || a.y === undefined || a.y === null ||
126
+ b.x === undefined || b.x === null || b.y === undefined || b.y === null) {
127
+ throw new Error("pointOnSegment: all points must have x and y properties");
128
+ }
112
129
  const crossVal = cross(a, b, pt);
113
130
  if (crossVal.abs().gt(EPSILON)) {
114
131
  return false;
@@ -138,10 +155,26 @@ function pointOnSegment(pt, a, b) {
138
155
  * @returns {Array} Polygon vertices (or path with curves for rounded corners)
139
156
  */
140
157
  export function rectToPolygon(rect) {
158
+ if (!rect || typeof rect !== "object") {
159
+ throw new Error("rectToPolygon: rect must be an object");
160
+ }
161
+ if (rect.width === undefined || rect.width === null) {
162
+ throw new Error("rectToPolygon: rect must have width property");
163
+ }
164
+ if (rect.height === undefined || rect.height === null) {
165
+ throw new Error("rectToPolygon: rect must have height property");
166
+ }
141
167
  const x = D(rect.x || 0);
142
168
  const y = D(rect.y || 0);
143
169
  const w = D(rect.width);
144
170
  const h = D(rect.height);
171
+
172
+ if (w.lte(0)) {
173
+ throw new Error("rectToPolygon: width must be positive");
174
+ }
175
+ if (h.lte(0)) {
176
+ throw new Error("rectToPolygon: height must be positive");
177
+ }
145
178
  const rx = D(rect.rx || 0);
146
179
  const ry = D(rect.ry || rx); // ry defaults to rx if not specified
147
180
 
@@ -230,10 +263,23 @@ export function rectToPolygon(rect) {
230
263
  * @returns {Array} Polygon vertices
231
264
  */
232
265
  export function circleToPolygon(circle, segments = 32) {
266
+ if (!circle || typeof circle !== "object") {
267
+ throw new Error("circleToPolygon: circle must be an object");
268
+ }
269
+ if (circle.r === undefined || circle.r === null) {
270
+ throw new Error("circleToPolygon: circle must have r property");
271
+ }
272
+ if (typeof segments !== "number" || segments <= 0 || !Number.isFinite(segments)) {
273
+ throw new Error("circleToPolygon: segments must be a positive finite number");
274
+ }
233
275
  const cx = D(circle.cx || 0);
234
276
  const cy = D(circle.cy || 0);
235
277
  const r = D(circle.r);
236
278
 
279
+ if (r.lte(0)) {
280
+ throw new Error("circleToPolygon: radius must be positive");
281
+ }
282
+
237
283
  const vertices = [];
238
284
  for (let i = 0; i < segments; i++) {
239
285
  const angle = (2 * Math.PI * i) / segments;
@@ -256,11 +302,30 @@ export function circleToPolygon(circle, segments = 32) {
256
302
  * @returns {Array} Polygon vertices
257
303
  */
258
304
  export function ellipseToPolygon(ellipse, segments = 32) {
305
+ if (!ellipse || typeof ellipse !== "object") {
306
+ throw new Error("ellipseToPolygon: ellipse must be an object");
307
+ }
308
+ if (ellipse.rx === undefined || ellipse.rx === null) {
309
+ throw new Error("ellipseToPolygon: ellipse must have rx property");
310
+ }
311
+ if (ellipse.ry === undefined || ellipse.ry === null) {
312
+ throw new Error("ellipseToPolygon: ellipse must have ry property");
313
+ }
314
+ if (typeof segments !== "number" || segments <= 0 || !Number.isFinite(segments)) {
315
+ throw new Error("ellipseToPolygon: segments must be a positive finite number");
316
+ }
259
317
  const cx = D(ellipse.cx || 0);
260
318
  const cy = D(ellipse.cy || 0);
261
319
  const rx = D(ellipse.rx);
262
320
  const ry = D(ellipse.ry);
263
321
 
322
+ if (rx.lte(0)) {
323
+ throw new Error("ellipseToPolygon: rx must be positive");
324
+ }
325
+ if (ry.lte(0)) {
326
+ throw new Error("ellipseToPolygon: ry must be positive");
327
+ }
328
+
264
329
  const vertices = [];
265
330
  for (let i = 0; i < segments; i++) {
266
331
  const angle = (2 * Math.PI * i) / segments;
@@ -283,12 +348,37 @@ export function ellipseToPolygon(ellipse, segments = 32) {
283
348
  * @returns {Array} Polygon vertices representing stroked line
284
349
  */
285
350
  export function lineToPolygon(line, stroke = { width: 1, linecap: "butt" }) {
351
+ if (!line || typeof line !== "object") {
352
+ throw new Error("lineToPolygon: line must be an object");
353
+ }
354
+ if (line.x1 === undefined || line.x1 === null) {
355
+ throw new Error("lineToPolygon: line must have x1 property");
356
+ }
357
+ if (line.y1 === undefined || line.y1 === null) {
358
+ throw new Error("lineToPolygon: line must have y1 property");
359
+ }
360
+ if (line.x2 === undefined || line.x2 === null) {
361
+ throw new Error("lineToPolygon: line must have x2 property");
362
+ }
363
+ if (line.y2 === undefined || line.y2 === null) {
364
+ throw new Error("lineToPolygon: line must have y2 property");
365
+ }
366
+ if (!stroke || typeof stroke !== "object") {
367
+ throw new Error("lineToPolygon: stroke must be an object");
368
+ }
369
+ if (stroke.width === undefined || stroke.width === null) {
370
+ throw new Error("lineToPolygon: stroke must have width property");
371
+ }
286
372
  const x1 = D(line.x1);
287
373
  const y1 = D(line.y1);
288
374
  const x2 = D(line.x2);
289
375
  const y2 = D(line.y2);
290
376
  const halfWidth = D(stroke.width).div(2);
291
377
 
378
+ if (halfWidth.lte(0)) {
379
+ throw new Error("lineToPolygon: stroke width must be positive");
380
+ }
381
+
292
382
  // Direction vector
293
383
  const dx = x2.minus(x1);
294
384
  const dy = y2.minus(y1);
@@ -365,15 +455,46 @@ export function lineToPolygon(line, stroke = { width: 1, linecap: "butt" }) {
365
455
  * @returns {Array} Polygon vertices
366
456
  */
367
457
  export function svgPolygonToPolygon(points) {
458
+ if (!points) {
459
+ throw new Error("svgPolygonToPolygon: points must be defined");
460
+ }
461
+
368
462
  if (Array.isArray(points)) {
369
- return points.map((p) => point(p.x, p.y));
463
+ if (points.length === 0) {
464
+ throw new Error("svgPolygonToPolygon: points array cannot be empty");
465
+ }
466
+ return points.map((p) => {
467
+ if (!p || typeof p !== "object" || p.x === undefined || p.y === undefined) {
468
+ throw new Error("svgPolygonToPolygon: each point must have x and y properties");
469
+ }
470
+ return point(p.x, p.y);
471
+ });
472
+ }
473
+
474
+ if (typeof points !== "string") {
475
+ throw new Error("svgPolygonToPolygon: points must be a string or array");
370
476
  }
371
477
 
372
478
  // Parse SVG points string
373
- const coords = points
374
- .trim()
375
- .split(/[\s,]+/)
376
- .map(Number);
479
+ const trimmed = points.trim();
480
+ if (trimmed === "") {
481
+ throw new Error("svgPolygonToPolygon: points string cannot be empty");
482
+ }
483
+
484
+ const coords = trimmed.split(/[\s,]+/).map(Number);
485
+
486
+ if (coords.length < 2) {
487
+ throw new Error("svgPolygonToPolygon: points must contain at least one coordinate pair");
488
+ }
489
+
490
+ if (coords.length % 2 !== 0) {
491
+ throw new Error("svgPolygonToPolygon: points must contain an even number of coordinates");
492
+ }
493
+
494
+ if (coords.some((c) => !Number.isFinite(c))) {
495
+ throw new Error("svgPolygonToPolygon: all coordinates must be finite numbers");
496
+ }
497
+
377
498
  const vertices = [];
378
499
  for (let i = 0; i < coords.length; i += 2) {
379
500
  vertices.push(point(coords[i], coords[i + 1]));
@@ -397,7 +518,21 @@ export function svgPolygonToPolygon(points) {
397
518
  * @returns {Object} {outer: Array, inner: Array} offset polygons
398
519
  */
399
520
  export function offsetPolygon(polygon, distance, options = {}) {
521
+ if (!Array.isArray(polygon)) {
522
+ throw new Error("offsetPolygon: polygon must be an array");
523
+ }
524
+ if (distance === undefined || distance === null) {
525
+ throw new Error("offsetPolygon: distance must be defined");
526
+ }
527
+ if (!options || typeof options !== "object") {
528
+ throw new Error("offsetPolygon: options must be an object");
529
+ }
530
+
400
531
  const dist = D(distance);
532
+ if (dist.lte(0)) {
533
+ throw new Error("offsetPolygon: distance must be positive");
534
+ }
535
+
401
536
  const linejoin = options.linejoin || "miter";
402
537
  const miterLimit = D(options.miterLimit || 4);
403
538
 
@@ -545,7 +680,21 @@ export function offsetPolygon(polygon, distance, options = {}) {
545
680
  * @returns {Array} Polygon representing the stroke area
546
681
  */
547
682
  export function strokeToFilledPolygon(polygon, strokeProps) {
548
- const halfWidth = D(strokeProps.width || 1).div(2);
683
+ if (!Array.isArray(polygon)) {
684
+ throw new Error("strokeToFilledPolygon: polygon must be an array");
685
+ }
686
+ if (!strokeProps || typeof strokeProps !== "object") {
687
+ throw new Error("strokeToFilledPolygon: strokeProps must be an object");
688
+ }
689
+ if (strokeProps.width === undefined || strokeProps.width === null) {
690
+ throw new Error("strokeToFilledPolygon: strokeProps must have width property");
691
+ }
692
+
693
+ const halfWidth = D(strokeProps.width).div(2);
694
+ if (halfWidth.lte(0)) {
695
+ throw new Error("strokeToFilledPolygon: width must be positive");
696
+ }
697
+
549
698
  const offset = offsetPolygon(polygon, halfWidth, strokeProps);
550
699
 
551
700
  // The stroke area is the outer path with the inner path as a hole
@@ -567,10 +716,25 @@ export function strokeToFilledPolygon(polygon, strokeProps) {
567
716
  * @returns {Array<Array>} Array of polygon segments
568
717
  */
569
718
  export function applyDashArray(polygon, dashArray, dashOffset = 0) {
719
+ if (!Array.isArray(polygon)) {
720
+ throw new Error("applyDashArray: polygon must be an array");
721
+ }
722
+ if (dashOffset === undefined || dashOffset === null) {
723
+ throw new Error("applyDashArray: dashOffset must be defined");
724
+ }
725
+
570
726
  if (!dashArray || dashArray.length === 0) {
571
727
  return [polygon];
572
728
  }
573
729
 
730
+ if (!Array.isArray(dashArray)) {
731
+ throw new Error("applyDashArray: dashArray must be an array");
732
+ }
733
+
734
+ if (dashArray.some((d) => typeof d !== "number" || !Number.isFinite(d) || d < 0)) {
735
+ throw new Error("applyDashArray: all dash values must be non-negative finite numbers");
736
+ }
737
+
574
738
  // Normalize dash array (must have even length)
575
739
  const dashes =
576
740
  dashArray.length % 2 === 0
@@ -680,9 +844,19 @@ export function applyDashArray(polygon, dashArray, dashOffset = 0) {
680
844
  */
681
845
  export class SVGRegion {
682
846
  constructor(options = {}) {
847
+ if (!options || typeof options !== "object") {
848
+ throw new Error("SVGRegion constructor: options must be an object");
849
+ }
683
850
  this.fillPolygons = options.fillPolygons || []; // Array of polygons
684
851
  this.fillRule = options.fillRule || FillRule.NONZERO;
685
852
  this.strokePolygons = options.strokePolygons || []; // Array of stroked regions
853
+
854
+ if (!Array.isArray(this.fillPolygons)) {
855
+ throw new Error("SVGRegion constructor: fillPolygons must be an array");
856
+ }
857
+ if (!Array.isArray(this.strokePolygons)) {
858
+ throw new Error("SVGRegion constructor: strokePolygons must be an array");
859
+ }
686
860
  }
687
861
 
688
862
  /**
@@ -694,6 +868,16 @@ export class SVGRegion {
694
868
  * @returns {SVGRegion}
695
869
  */
696
870
  static fromElement(type, props, style = {}) {
871
+ if (!type || typeof type !== "string") {
872
+ throw new Error("SVGRegion.fromElement: type must be a non-empty string");
873
+ }
874
+ if (!props || typeof props !== "object") {
875
+ throw new Error("SVGRegion.fromElement: props must be an object");
876
+ }
877
+ if (!style || typeof style !== "object") {
878
+ throw new Error("SVGRegion.fromElement: style must be an object");
879
+ }
880
+
697
881
  let polygon;
698
882
 
699
883
  switch (type) {
@@ -727,7 +911,7 @@ export class SVGRegion {
727
911
  }
728
912
 
729
913
  // Add stroke region if element has stroke
730
- if (style.stroke !== "none" && style.strokeWidth > 0) {
914
+ if (style.stroke !== "none" && typeof style.strokeWidth === "number" && style.strokeWidth > 0) {
731
915
  const sourcePolygon =
732
916
  type === "line"
733
917
  ? lineToPolygon(props, {
@@ -740,7 +924,7 @@ export class SVGRegion {
740
924
  let strokePolygons;
741
925
 
742
926
  // Apply dash array if present
743
- if (style.strokeDasharray && style.strokeDasharray.length > 0) {
927
+ if (Array.isArray(style.strokeDasharray) && style.strokeDasharray.length > 0) {
744
928
  const dashedSegments = applyDashArray(
745
929
  sourcePolygon,
746
930
  style.strokeDasharray,
@@ -786,6 +970,13 @@ export class SVGRegion {
786
970
  * @returns {boolean}
787
971
  */
788
972
  containsPoint(pt) {
973
+ if (!pt || typeof pt !== "object") {
974
+ throw new Error("SVGRegion.containsPoint: pt must be an object");
975
+ }
976
+ if (pt.x === undefined || pt.x === null || pt.y === undefined || pt.y === null) {
977
+ throw new Error("SVGRegion.containsPoint: pt must have x and y properties");
978
+ }
979
+
789
980
  // Check fill polygons with fill rule
790
981
  for (const poly of this.fillPolygons) {
791
982
  if (pointInPolygonWithRule(pt, poly, this.fillRule) >= 0) {
@@ -816,6 +1007,13 @@ export class SVGRegion {
816
1007
  * @returns {SVGRegion} Intersection region
817
1008
  */
818
1009
  export function regionIntersection(regionA, regionB) {
1010
+ if (!regionA || !(regionA instanceof SVGRegion)) {
1011
+ throw new Error("regionIntersection: regionA must be an SVGRegion instance");
1012
+ }
1013
+ if (!regionB || !(regionB instanceof SVGRegion)) {
1014
+ throw new Error("regionIntersection: regionB must be an SVGRegion instance");
1015
+ }
1016
+
819
1017
  const resultPolygons = [];
820
1018
 
821
1019
  const polygonsA = regionA.getAllPolygons();
@@ -846,7 +1044,12 @@ export function regionIntersection(regionA, regionB) {
846
1044
  * @returns {SVGRegion} Union region
847
1045
  */
848
1046
  export function regionUnion(regionA, regionB) {
849
- const _resultPolygons = [];
1047
+ if (!regionA || !(regionA instanceof SVGRegion)) {
1048
+ throw new Error("regionUnion: regionA must be an SVGRegion instance");
1049
+ }
1050
+ if (!regionB || !(regionB instanceof SVGRegion)) {
1051
+ throw new Error("regionUnion: regionB must be an SVGRegion instance");
1052
+ }
850
1053
 
851
1054
  const polygonsA = regionA.getAllPolygons();
852
1055
  const polygonsB = regionB.getAllPolygons();
@@ -899,6 +1102,13 @@ export function regionUnion(regionA, regionB) {
899
1102
  * @returns {SVGRegion} Difference region
900
1103
  */
901
1104
  export function regionDifference(regionA, regionB) {
1105
+ if (!regionA || !(regionA instanceof SVGRegion)) {
1106
+ throw new Error("regionDifference: regionA must be an SVGRegion instance");
1107
+ }
1108
+ if (!regionB || !(regionB instanceof SVGRegion)) {
1109
+ throw new Error("regionDifference: regionB must be an SVGRegion instance");
1110
+ }
1111
+
902
1112
  let resultPolygons = regionA.getAllPolygons().map((p) => [...p]);
903
1113
 
904
1114
  const polygonsB = regionB.getAllPolygons();
@@ -933,6 +1143,13 @@ export function regionDifference(regionA, regionB) {
933
1143
  * @returns {SVGRegion} XOR region
934
1144
  */
935
1145
  export function regionXOR(regionA, regionB) {
1146
+ if (!regionA || !(regionA instanceof SVGRegion)) {
1147
+ throw new Error("regionXOR: regionA must be an SVGRegion instance");
1148
+ }
1149
+ if (!regionB || !(regionB instanceof SVGRegion)) {
1150
+ throw new Error("regionXOR: regionB must be an SVGRegion instance");
1151
+ }
1152
+
936
1153
  const diffAB = regionDifference(regionA, regionB);
937
1154
  const diffBA = regionDifference(regionB, regionA);
938
1155
 
@@ -173,6 +173,11 @@ export const editorNamespaces = new Set([
173
173
  'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
174
174
  'http://www.serif.com/',
175
175
  'http://www.vector.evaxdesign.sk',
176
+ // Additional editor namespaces
177
+ 'http://www.corel.com/coreldraw/svg',
178
+ 'http://gravit.io/ns',
179
+ 'http://serif.com/affinity',
180
+ 'http://canva.com/ns',
176
181
  ]);
177
182
 
178
183
  // ============================================================================
@@ -921,6 +926,8 @@ export const pseudoClasses = {
921
926
 
922
927
  /**
923
928
  * Additional editor namespaces beyond the base set
929
+ * Note: These are now integrated directly into editorNamespaces above
930
+ * to avoid post-export mutation issues. This export is kept for backwards compatibility.
924
931
  */
925
932
  export const additionalEditorNamespaces = new Set([
926
933
  // CorelDraw
@@ -932,8 +939,3 @@ export const additionalEditorNamespaces = new Set([
932
939
  // Canva
933
940
  'http://canva.com/ns',
934
941
  ]);
935
-
936
- // Merge additional namespaces into main set at initialization (avoid post-export mutation)
937
- for (const ns of additionalEditorNamespaces) {
938
- editorNamespaces.add(ns);
939
- }