@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.
Files changed (46) hide show
  1. package/README.md +325 -0
  2. package/bin/svg-matrix.js +994 -378
  3. package/bin/svglinter.cjs +4172 -433
  4. package/bin/svgm.js +744 -184
  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 +404 -0
  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 +48 -19
  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 +16411 -3298
  34. package/src/svg2-polyfills.js +114 -245
  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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emasoft/svg-matrix",
3
- "version": "1.0.27",
3
+ "version": "1.0.29",
4
4
  "description": "Arbitrary-precision matrix, vector and affine transformation library for JavaScript using decimal.js",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -8,6 +8,11 @@
8
8
  "test": "node test/examples.js",
9
9
  "test:browser": "node test/browser-verify.mjs",
10
10
  "test:playwright": "node test/playwright-diagnose.js",
11
+ "test:embed": "node test/test-embed-real-world.js",
12
+ "test:embed:w3c": "node test/test-embed-w3c.js",
13
+ "test:embed:w3c:all": "node test/test-embed-w3c-all.js",
14
+ "test:toolbox:8bits": "node test/test-toolbox-matrix-8bits.js",
15
+ "test:embed:browser": "node test/test-embed-playwright.js",
11
16
  "ci-test": "npm ci && npm test",
12
17
  "lint": "node bin/svglinter.cjs",
13
18
  "lint:fix": "node bin/svglinter.cjs --fix",
@@ -69,10 +74,13 @@
69
74
  "README.md"
70
75
  ],
71
76
  "dependencies": {
72
- "decimal.js": "^10.6.0"
77
+ "decimal.js": "^10.6.0",
78
+ "js-yaml": "^4.1.0",
79
+ "jsdom": "^27.3.0",
80
+ "toml": "^3.0.0"
73
81
  },
74
82
  "peerDependencies": {
75
- "playwright": "^1.57.0"
83
+ "playwright": "^1.49.0"
76
84
  },
77
85
  "peerDependenciesMeta": {
78
86
  "playwright": {
@@ -80,6 +88,10 @@
80
88
  }
81
89
  },
82
90
  "devDependencies": {
83
- "svgo": "^4.0.0"
91
+ "@eslint/js": "^9.39.2",
92
+ "globals": "^16.5.0",
93
+ "playwright": "^1.49.0",
94
+ "svgo": "^4.0.0",
95
+ "typescript": "^5.9.3"
84
96
  }
85
97
  }
@@ -28,26 +28,39 @@
28
28
  * @module animation-references
29
29
  */
30
30
 
31
- import { SVGElement } from './svg-parser.js';
31
+ import { SVGElement } from "./svg-parser.js";
32
32
 
33
33
  // Animation elements that can reference other elements
34
- const ANIMATION_ELEMENTS = ['animate', 'animateTransform', 'animateMotion', 'animateColor', 'set'];
34
+ const ANIMATION_ELEMENTS = [
35
+ "animate",
36
+ "animateTransform",
37
+ "animateMotion",
38
+ "animateColor",
39
+ "set",
40
+ ];
35
41
 
36
42
  // Attributes that can contain ID references
37
- const HREF_ATTRIBUTES = ['href', 'xlink:href'];
43
+ const HREF_ATTRIBUTES = ["href", "xlink:href"];
38
44
 
39
45
  // Attributes that use url(#id) syntax
40
46
  const URL_ATTRIBUTES = [
41
- 'fill', 'stroke', 'clip-path', 'mask', 'filter',
42
- 'marker-start', 'marker-mid', 'marker-end',
43
- 'cursor', 'color-profile'
47
+ "fill",
48
+ "stroke",
49
+ "clip-path",
50
+ "mask",
51
+ "filter",
52
+ "marker-start",
53
+ "marker-mid",
54
+ "marker-end",
55
+ "cursor",
56
+ "color-profile",
44
57
  ];
45
58
 
46
59
  // Animation timing attributes that can reference other elements by ID
47
- const TIMING_ATTRIBUTES = ['begin', 'end'];
60
+ const TIMING_ATTRIBUTES = ["begin", "end"];
48
61
 
49
62
  // Animation value attributes that can contain ID references
50
- const VALUE_ATTRIBUTES = ['values', 'from', 'to', 'by'];
63
+ const VALUE_ATTRIBUTES = ["values", "from", "to", "by"];
51
64
 
52
65
  /**
53
66
  * Parse ID from url(#id) or url("#id") syntax
@@ -55,7 +68,7 @@ const VALUE_ATTRIBUTES = ['values', 'from', 'to', 'by'];
55
68
  * @returns {string|null} Parsed ID or null
56
69
  */
57
70
  export function parseUrlId(value) {
58
- if (!value || typeof value !== 'string') return null;
71
+ if (!value || typeof value !== "string") return null;
59
72
 
60
73
  // Match url(#id) or url("#id") or url('#id') with optional whitespace
61
74
  const match = value.match(/url\(\s*["']?#([^"')\s]+)\s*["']?\s*\)/);
@@ -68,8 +81,8 @@ export function parseUrlId(value) {
68
81
  * @returns {string|null} Parsed ID or null
69
82
  */
70
83
  export function parseHrefId(value) {
71
- if (!value || typeof value !== 'string') return null;
72
- if (value.startsWith('#')) {
84
+ if (!value || typeof value !== "string") return null;
85
+ if (value.startsWith("#")) {
73
86
  return value.substring(1);
74
87
  }
75
88
  return null;
@@ -82,14 +95,14 @@ export function parseHrefId(value) {
82
95
  * @returns {string[]} Array of parsed IDs
83
96
  */
84
97
  export function parseAnimationValueIds(value) {
85
- if (!value || typeof value !== 'string') return [];
98
+ if (!value || typeof value !== "string") return [];
86
99
 
87
100
  const ids = [];
88
101
  // Split by semicolon and find #id references
89
- const parts = value.split(';');
102
+ const parts = value.split(";");
90
103
  for (const part of parts) {
91
104
  const trimmed = part.trim();
92
- if (trimmed.startsWith('#')) {
105
+ if (trimmed.startsWith("#")) {
93
106
  ids.push(trimmed.substring(1));
94
107
  }
95
108
  // Also check for url(#id) within values
@@ -111,17 +124,19 @@ export function parseAnimationValueIds(value) {
111
124
  * @returns {string[]} Array of parsed IDs
112
125
  */
113
126
  export function parseTimingIds(value) {
114
- if (!value || typeof value !== 'string') return [];
127
+ if (!value || typeof value !== "string") return [];
115
128
 
116
129
  const ids = [];
117
130
  // Split by semicolon for multiple timing values
118
- const parts = value.split(';');
131
+ const parts = value.split(";");
119
132
 
120
133
  for (const part of parts) {
121
134
  const trimmed = part.trim();
122
135
  // Match patterns like "id.event" or "id.begin" or "id.end"
123
136
  // Events: click, mousedown, mouseup, mouseover, mouseout, focusin, focusout, etc.
124
- const match = trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)\.(begin|end|click|mousedown|mouseup|mouseover|mouseout|mousemove|mouseenter|mouseleave|focusin|focusout|activate|repeat)/);
137
+ const match = trimmed.match(
138
+ /^([a-zA-Z_][a-zA-Z0-9_-]*)\.(begin|end|click|mousedown|mouseup|mouseover|mouseout|mousemove|mouseenter|mouseleave|focusin|focusout|activate|repeat)/,
139
+ );
125
140
  if (match) {
126
141
  ids.push(match[1]);
127
142
  }
@@ -135,7 +150,7 @@ export function parseTimingIds(value) {
135
150
  * @returns {string[]} Array of parsed IDs
136
151
  */
137
152
  export function parseCSSIds(css) {
138
- if (!css || typeof css !== 'string') return [];
153
+ if (!css || typeof css !== "string") return [];
139
154
 
140
155
  const ids = [];
141
156
 
@@ -155,7 +170,7 @@ export function parseCSSIds(css) {
155
170
  * @returns {string[]} Array of parsed IDs
156
171
  */
157
172
  export function parseJavaScriptIds(js) {
158
- if (!js || typeof js !== 'string') return [];
173
+ if (!js || typeof js !== "string") return [];
159
174
 
160
175
  const ids = [];
161
176
 
@@ -167,7 +182,8 @@ export function parseJavaScriptIds(js) {
167
182
  }
168
183
 
169
184
  // querySelector('#id') or querySelector("#id")
170
- const querySelectorRegex = /querySelector(?:All)?\(\s*["']#([^"'#\s]+)["']\s*\)/g;
185
+ const querySelectorRegex =
186
+ /querySelector(?:All)?\(\s*["']#([^"'#\s]+)["']\s*\)/g;
171
187
  while ((match = querySelectorRegex.exec(js)) !== null) {
172
188
  ids.push(match[1]);
173
189
  }
@@ -185,10 +201,10 @@ export function collectElementReferences(el) {
185
201
  static: new Set(),
186
202
  animation: new Set(),
187
203
  css: new Set(),
188
- js: new Set()
204
+ js: new Set(),
189
205
  };
190
206
 
191
- const tagName = el.tagName?.toLowerCase() || '';
207
+ const tagName = el.tagName?.toLowerCase() || "";
192
208
  const isAnimationElement = ANIMATION_ELEMENTS.includes(tagName);
193
209
 
194
210
  for (const attrName of el.getAttributeNames()) {
@@ -208,36 +224,36 @@ export function collectElementReferences(el) {
208
224
  }
209
225
 
210
226
  // Check url() attributes
211
- if (URL_ATTRIBUTES.includes(attrName) || attrName === 'style') {
227
+ if (URL_ATTRIBUTES.includes(attrName) || attrName === "style") {
212
228
  const id = parseUrlId(value);
213
229
  if (id) refs.static.add(id);
214
230
 
215
231
  // For style attribute, also parse CSS IDs
216
- if (attrName === 'style') {
217
- parseCSSIds(value).forEach(id => refs.css.add(id));
232
+ if (attrName === "style") {
233
+ parseCSSIds(value).forEach((cssId) => refs.css.add(cssId));
218
234
  }
219
235
  }
220
236
 
221
237
  // Check animation timing attributes (begin, end)
222
238
  if (TIMING_ATTRIBUTES.includes(attrName)) {
223
- parseTimingIds(value).forEach(id => refs.animation.add(id));
239
+ parseTimingIds(value).forEach((id) => refs.animation.add(id));
224
240
  }
225
241
 
226
242
  // Check animation value attributes (values, from, to, by)
227
243
  if (VALUE_ATTRIBUTES.includes(attrName)) {
228
- parseAnimationValueIds(value).forEach(id => refs.animation.add(id));
244
+ parseAnimationValueIds(value).forEach((id) => refs.animation.add(id));
229
245
  }
230
246
 
231
247
  // Also check for url() in any attribute (some custom attributes may use it)
232
- if (!URL_ATTRIBUTES.includes(attrName) && value.includes('url(')) {
248
+ if (!URL_ATTRIBUTES.includes(attrName) && value.includes("url(")) {
233
249
  const id = parseUrlId(value);
234
250
  if (id) refs.static.add(id);
235
251
  }
236
252
  }
237
253
 
238
254
  // Check <mpath> element inside <animateMotion>
239
- if (tagName === 'animatemotion') {
240
- const mpaths = el.getElementsByTagName('mpath');
255
+ if (tagName === "animatemotion") {
256
+ const mpaths = el.getElementsByTagName("mpath");
241
257
  for (const mpath of mpaths) {
242
258
  for (const attr of HREF_ATTRIBUTES) {
243
259
  const value = mpath.getAttribute(attr);
@@ -271,7 +287,7 @@ export function collectAllReferences(root) {
271
287
  animation: new Set(),
272
288
  css: new Set(),
273
289
  js: new Set(),
274
- details: new Map() // ID -> { sources: [], type: 'static'|'animation'|'css'|'js' }
290
+ details: new Map(), // ID -> { sources: [], type: 'static'|'animation'|'css'|'js' }
275
291
  };
276
292
 
277
293
  // Type priority: animation > js > css > static
@@ -294,30 +310,32 @@ export function collectAllReferences(root) {
294
310
  result.details.get(id).sources.push(source);
295
311
  };
296
312
 
297
- const processElement = (el, path = '') => {
298
- const tagName = el.tagName?.toLowerCase() || '';
313
+ const processElement = (el, path = "") => {
314
+ const tagName = el.tagName?.toLowerCase() || "";
299
315
  const currentPath = path ? `${path}>${tagName}` : tagName;
300
- const elId = el.getAttribute('id');
316
+ const elId = el.getAttribute("id");
301
317
  const elPath = elId ? `${tagName}#${elId}` : currentPath;
302
318
 
303
319
  // Collect element references
304
320
  const refs = collectElementReferences(el);
305
321
 
306
- refs.static.forEach(id => addRef(id, 'static', elPath));
307
- refs.animation.forEach(id => addRef(id, 'animation', elPath));
308
- refs.css.forEach(id => addRef(id, 'css', elPath));
309
- refs.js.forEach(id => addRef(id, 'js', elPath));
322
+ refs.static.forEach((id) => addRef(id, "static", elPath));
323
+ refs.animation.forEach((id) => addRef(id, "animation", elPath));
324
+ refs.css.forEach((id) => addRef(id, "css", elPath));
325
+ refs.js.forEach((id) => addRef(id, "js", elPath));
310
326
 
311
327
  // Process <style> elements
312
- if (tagName === 'style') {
313
- const cssContent = el.textContent || '';
314
- parseCSSIds(cssContent).forEach(id => addRef(id, 'css', `<style>`));
328
+ if (tagName === "style") {
329
+ const cssContent = el.textContent || "";
330
+ parseCSSIds(cssContent).forEach((id) => addRef(id, "css", `<style>`));
315
331
  }
316
332
 
317
333
  // Process <script> elements
318
- if (tagName === 'script') {
319
- const jsContent = el.textContent || '';
320
- parseJavaScriptIds(jsContent).forEach(id => addRef(id, 'js', `<script>`));
334
+ if (tagName === "script") {
335
+ const jsContent = el.textContent || "";
336
+ parseJavaScriptIds(jsContent).forEach((id) =>
337
+ addRef(id, "js", `<script>`),
338
+ );
321
339
  }
322
340
 
323
341
  // Recurse to children
@@ -357,7 +375,7 @@ export function getIdReferenceInfo(root, id) {
357
375
  return {
358
376
  referenced: refs.all.has(id),
359
377
  type: details?.type || null,
360
- sources: details?.sources || []
378
+ sources: details?.sources || [],
361
379
  };
362
380
  }
363
381
 
@@ -376,11 +394,11 @@ export function findUnreferencedDefs(root) {
376
394
  const animationReferenced = [];
377
395
 
378
396
  // Scan all defs
379
- const defsElements = root.getElementsByTagName('defs');
397
+ const defsElements = root.getElementsByTagName("defs");
380
398
  for (const defs of defsElements) {
381
399
  for (const child of defs.children) {
382
400
  if (child instanceof SVGElement) {
383
- const id = child.getAttribute('id');
401
+ const id = child.getAttribute("id");
384
402
  if (!id) continue;
385
403
 
386
404
  if (refs.animation.has(id)) {
@@ -405,16 +423,17 @@ export function findUnreferencedDefs(root) {
405
423
  * @returns {{removed: string[], kept: string[], keptForAnimation: string[]}}
406
424
  */
407
425
  export function removeUnreferencedDefsSafe(root) {
408
- const { safeToRemove, referenced, animationReferenced } = findUnreferencedDefs(root);
426
+ const { safeToRemove, referenced, animationReferenced } =
427
+ findUnreferencedDefs(root);
409
428
 
410
429
  const removed = [];
411
430
 
412
431
  // Only remove elements that are truly unreferenced
413
- const defsElements = root.getElementsByTagName('defs');
432
+ const defsElements = root.getElementsByTagName("defs");
414
433
  for (const defs of defsElements) {
415
434
  for (const child of [...defs.children]) {
416
435
  if (child instanceof SVGElement) {
417
- const id = child.getAttribute('id');
436
+ const id = child.getAttribute("id");
418
437
  if (id && safeToRemove.includes(id)) {
419
438
  defs.removeChild(child);
420
439
  removed.push(id);
@@ -426,7 +445,7 @@ export function removeUnreferencedDefsSafe(root) {
426
445
  return {
427
446
  removed,
428
447
  kept: referenced,
429
- keptForAnimation: animationReferenced
448
+ keptForAnimation: animationReferenced,
430
449
  };
431
450
  }
432
451
 
@@ -436,5 +455,5 @@ export {
436
455
  HREF_ATTRIBUTES,
437
456
  URL_ATTRIBUTES,
438
457
  TIMING_ATTRIBUTES,
439
- VALUE_ATTRIBUTES
458
+ VALUE_ATTRIBUTES,
440
459
  };