@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
@@ -10,13 +10,13 @@
10
10
 
11
11
  // Selector component types
12
12
  const SELECTOR_TYPES = {
13
- ID: 'id', // #foo
14
- CLASS: 'class', // .foo
15
- ATTRIBUTE: 'attr', // [foo]
16
- PSEUDO_CLASS: 'pseudo-class', // :hover
17
- PSEUDO_ELEMENT: 'pseudo-element', // ::before
18
- TYPE: 'type', // div
19
- UNIVERSAL: 'universal' // *
13
+ ID: "id", // #foo
14
+ CLASS: "class", // .foo
15
+ ATTRIBUTE: "attr", // [foo]
16
+ PSEUDO_CLASS: "pseudo-class", // :hover
17
+ PSEUDO_ELEMENT: "pseudo-element", // ::before
18
+ TYPE: "type", // div
19
+ UNIVERSAL: "universal", // *
20
20
  };
21
21
 
22
22
  /**
@@ -40,13 +40,13 @@ const SELECTOR_TYPES = {
40
40
  * // Returns array of component objects with type and value
41
41
  */
42
42
  export function parseSelector(selectorString) {
43
- if (typeof selectorString !== 'string' || !selectorString.trim()) {
44
- throw new Error('Selector must be a non-empty string');
43
+ if (typeof selectorString !== "string" || !selectorString.trim()) {
44
+ throw new Error("Selector must be a non-empty string");
45
45
  }
46
46
 
47
47
  const selector = selectorString.trim();
48
48
  const components = [];
49
- let i = 0;
49
+ const _i = 0;
50
50
 
51
51
  // Split by combinators first (>, +, ~, space) to handle complex selectors
52
52
  const parts = splitByCombinators(selector);
@@ -71,7 +71,7 @@ export function parseSelector(selectorString) {
71
71
  function splitByCombinators(selector) {
72
72
  // Match combinators outside of brackets and parentheses
73
73
  const parts = [];
74
- let current = '';
74
+ let current = "";
75
75
  let depth = 0; // Track nesting in [] and ()
76
76
  let inBracket = false;
77
77
  let inParen = false;
@@ -79,31 +79,31 @@ function splitByCombinators(selector) {
79
79
  for (let i = 0; i < selector.length; i++) {
80
80
  const char = selector[i];
81
81
 
82
- if (char === '[') {
82
+ if (char === "[") {
83
83
  inBracket = true;
84
84
  depth++;
85
- } else if (char === ']') {
85
+ } else if (char === "]") {
86
86
  depth--;
87
87
  if (depth === 0) inBracket = false;
88
- } else if (char === '(') {
88
+ } else if (char === "(") {
89
89
  inParen = true;
90
90
  depth++;
91
- } else if (char === ')') {
91
+ } else if (char === ")") {
92
92
  depth--;
93
93
  if (depth === 0) inParen = false;
94
94
  }
95
95
 
96
96
  // Only split on combinators when not inside brackets or parens
97
97
  if (depth === 0 && !inBracket && !inParen) {
98
- if (char === '>' || char === '+' || char === '~') {
98
+ if (char === ">" || char === "+" || char === "~") {
99
99
  if (current.trim()) parts.push(current.trim());
100
100
  parts.push(char); // Keep combinator as separate part for context
101
- current = '';
101
+ current = "";
102
102
  continue;
103
- } else if (char === ' ' && current.trim()) {
103
+ } else if (char === " " && current.trim()) {
104
104
  // Space combinator (descendant)
105
105
  parts.push(current.trim());
106
- current = '';
106
+ current = "";
107
107
  continue;
108
108
  }
109
109
  }
@@ -132,34 +132,38 @@ function parseSimpleSelector(selector) {
132
132
  const char = selector[i];
133
133
 
134
134
  // ID selector
135
- if (char === '#') {
135
+ if (char === "#") {
136
136
  const match = selector.slice(i).match(/^#([\w-]+)/);
137
137
  if (!match) throw new Error(`Invalid ID selector at position ${i}`);
138
138
  components.push({ type: SELECTOR_TYPES.ID, value: match[1] });
139
139
  i += match[0].length;
140
140
  }
141
141
  // Class selector
142
- else if (char === '.') {
142
+ else if (char === ".") {
143
143
  const match = selector.slice(i).match(/^\.([\w-]+)/);
144
144
  if (!match) throw new Error(`Invalid class selector at position ${i}`);
145
145
  components.push({ type: SELECTOR_TYPES.CLASS, value: match[1] });
146
146
  i += match[0].length;
147
147
  }
148
148
  // Attribute selector
149
- else if (char === '[') {
149
+ else if (char === "[") {
150
150
  const endIdx = findMatchingBracket(selector, i);
151
- if (endIdx === -1) throw new Error(`Unclosed attribute selector at position ${i}`);
151
+ if (endIdx === -1)
152
+ throw new Error(`Unclosed attribute selector at position ${i}`);
152
153
  const attrContent = selector.slice(i + 1, endIdx);
153
154
  components.push({ type: SELECTOR_TYPES.ATTRIBUTE, value: attrContent });
154
155
  i = endIdx + 1;
155
156
  }
156
157
  // Pseudo-element (::) or pseudo-class (:)
157
- else if (char === ':') {
158
- if (selector[i + 1] === ':') {
158
+ else if (char === ":") {
159
+ if (selector[i + 1] === ":") {
159
160
  // Pseudo-element
160
161
  const match = selector.slice(i).match(/^::([\w-]+)/);
161
162
  if (!match) throw new Error(`Invalid pseudo-element at position ${i}`);
162
- components.push({ type: SELECTOR_TYPES.PSEUDO_ELEMENT, value: match[1] });
163
+ components.push({
164
+ type: SELECTOR_TYPES.PSEUDO_ELEMENT,
165
+ value: match[1],
166
+ });
163
167
  i += match[0].length;
164
168
  } else {
165
169
  // Pseudo-class (may have arguments like :not())
@@ -170,19 +174,23 @@ function parseSimpleSelector(selector) {
170
174
  i += match[0].length;
171
175
 
172
176
  // Check for function notation like :not()
173
- if (selector[i] === '(') {
177
+ if (selector[i] === "(") {
174
178
  const endIdx = findMatchingParen(selector, i);
175
- if (endIdx === -1) throw new Error(`Unclosed pseudo-class function at position ${i}`);
179
+ if (endIdx === -1)
180
+ throw new Error(`Unclosed pseudo-class function at position ${i}`);
176
181
  pseudoValue += selector.slice(i, endIdx + 1);
177
182
  i = endIdx + 1;
178
183
  }
179
184
 
180
- components.push({ type: SELECTOR_TYPES.PSEUDO_CLASS, value: pseudoValue });
185
+ components.push({
186
+ type: SELECTOR_TYPES.PSEUDO_CLASS,
187
+ value: pseudoValue,
188
+ });
181
189
  }
182
190
  }
183
191
  // Universal selector
184
- else if (char === '*') {
185
- components.push({ type: SELECTOR_TYPES.UNIVERSAL, value: '*' });
192
+ else if (char === "*") {
193
+ components.push({ type: SELECTOR_TYPES.UNIVERSAL, value: "*" });
186
194
  i++;
187
195
  }
188
196
  // Type selector (element name)
@@ -193,10 +201,9 @@ function parseSimpleSelector(selector) {
193
201
  i += match[0].length;
194
202
  }
195
203
  // Skip combinators (should not be in simple selector)
196
- else if (char === '>' || char === '+' || char === '~' || char === ' ') {
204
+ else if (char === ">" || char === "+" || char === "~" || char === " ") {
197
205
  i++;
198
- }
199
- else {
206
+ } else {
200
207
  throw new Error(`Unexpected character '${char}' at position ${i}`);
201
208
  }
202
209
  }
@@ -214,8 +221,8 @@ function parseSimpleSelector(selector) {
214
221
  function findMatchingBracket(str, startIdx) {
215
222
  let depth = 0;
216
223
  for (let i = startIdx; i < str.length; i++) {
217
- if (str[i] === '[') depth++;
218
- if (str[i] === ']') {
224
+ if (str[i] === "[") depth++;
225
+ if (str[i] === "]") {
219
226
  depth--;
220
227
  if (depth === 0) return i;
221
228
  }
@@ -233,8 +240,8 @@ function findMatchingBracket(str, startIdx) {
233
240
  function findMatchingParen(str, startIdx) {
234
241
  let depth = 0;
235
242
  for (let i = startIdx; i < str.length; i++) {
236
- if (str[i] === '(') depth++;
237
- if (str[i] === ')') {
243
+ if (str[i] === "(") depth++;
244
+ if (str[i] === ")") {
238
245
  depth--;
239
246
  if (depth === 0) return i;
240
247
  }
@@ -262,9 +269,8 @@ function findMatchingParen(str, startIdx) {
262
269
  */
263
270
  export function calculateSpecificity(selector) {
264
271
  // Parse if string, otherwise assume it's already parsed
265
- const components = typeof selector === 'string'
266
- ? parseSelector(selector)
267
- : selector;
272
+ const components =
273
+ typeof selector === "string" ? parseSelector(selector) : selector;
268
274
 
269
275
  let a = 0; // IDs
270
276
  let b = 0; // Classes, attributes, pseudo-classes
@@ -283,7 +289,7 @@ export function calculateSpecificity(selector) {
283
289
 
284
290
  case SELECTOR_TYPES.PSEUDO_CLASS:
285
291
  // Handle :not() - it doesn't count itself, but its argument does
286
- if (component.value.startsWith('not(')) {
292
+ if (component.value.startsWith("not(")) {
287
293
  const notContent = component.value.slice(4, -1); // Extract content inside :not()
288
294
  const notSpec = calculateSpecificity(notContent);
289
295
  a += notSpec[0];
@@ -325,10 +331,10 @@ export function calculateSpecificity(selector) {
325
331
  */
326
332
  export function compareSpecificity(spec1, spec2) {
327
333
  if (!Array.isArray(spec1) || spec1.length !== 3) {
328
- throw new Error('spec1 must be an array of 3 numbers');
334
+ throw new Error("spec1 must be an array of 3 numbers");
329
335
  }
330
336
  if (!Array.isArray(spec2) || spec2.length !== 3) {
331
- throw new Error('spec2 must be an array of 3 numbers');
337
+ throw new Error("spec2 must be an array of 3 numbers");
332
338
  }
333
339
 
334
340
  // Compare lexicographically: a first, then b, then c
@@ -358,12 +364,12 @@ export function compareSpecificity(spec1, spec2) {
358
364
  */
359
365
  export function sortBySpecificity(rules) {
360
366
  if (!Array.isArray(rules)) {
361
- throw new Error('rules must be an array');
367
+ throw new Error("rules must be an array");
362
368
  }
363
369
 
364
370
  // Create array of [rule, specificity, originalIndex] tuples
365
371
  const withSpec = rules.map((rule, index) => {
366
- if (!rule || typeof rule.selector !== 'string') {
372
+ if (!rule || typeof rule.selector !== "string") {
367
373
  throw new Error(`Rule at index ${index} must have a 'selector' property`);
368
374
  }
369
375
  return [rule, calculateSpecificity(rule.selector), index];
@@ -378,7 +384,7 @@ export function sortBySpecificity(rules) {
378
384
  });
379
385
 
380
386
  // Extract just the rules
381
- return withSpec.map(tuple => tuple[0]);
387
+ return withSpec.map((tuple) => tuple[0]);
382
388
  }
383
389
 
384
390
  /**
@@ -390,29 +396,31 @@ export function sortBySpecificity(rules) {
390
396
  */
391
397
  export function stringifySelector(components) {
392
398
  if (!Array.isArray(components)) {
393
- throw new Error('components must be an array');
399
+ throw new Error("components must be an array");
394
400
  }
395
401
 
396
- return components.map(component => {
397
- switch (component.type) {
398
- case SELECTOR_TYPES.ID:
399
- return `#${component.value}`;
400
- case SELECTOR_TYPES.CLASS:
401
- return `.${component.value}`;
402
- case SELECTOR_TYPES.ATTRIBUTE:
403
- return `[${component.value}]`;
404
- case SELECTOR_TYPES.PSEUDO_CLASS:
405
- return `:${component.value}`;
406
- case SELECTOR_TYPES.PSEUDO_ELEMENT:
407
- return `::${component.value}`;
408
- case SELECTOR_TYPES.TYPE:
409
- return component.value;
410
- case SELECTOR_TYPES.UNIVERSAL:
411
- return '*';
412
- default:
413
- return '';
414
- }
415
- }).join('');
402
+ return components
403
+ .map((component) => {
404
+ switch (component.type) {
405
+ case SELECTOR_TYPES.ID:
406
+ return `#${component.value}`;
407
+ case SELECTOR_TYPES.CLASS:
408
+ return `.${component.value}`;
409
+ case SELECTOR_TYPES.ATTRIBUTE:
410
+ return `[${component.value}]`;
411
+ case SELECTOR_TYPES.PSEUDO_CLASS:
412
+ return `:${component.value}`;
413
+ case SELECTOR_TYPES.PSEUDO_ELEMENT:
414
+ return `::${component.value}`;
415
+ case SELECTOR_TYPES.TYPE:
416
+ return component.value;
417
+ case SELECTOR_TYPES.UNIVERSAL:
418
+ return "*";
419
+ default:
420
+ return "";
421
+ }
422
+ })
423
+ .join("");
416
424
  }
417
425
 
418
426
  /**
@@ -427,7 +435,7 @@ export function verifySelector(selector) {
427
435
  const reconstructed = stringifySelector(components);
428
436
 
429
437
  // Normalize whitespace for comparison
430
- const normalize = s => s.replace(/\s+/g, '');
438
+ const normalize = (s) => s.replace(/\s+/g, "");
431
439
 
432
440
  return normalize(selector) === normalize(reconstructed);
433
441
  }
@@ -439,5 +447,5 @@ export default {
439
447
  compareSpecificity,
440
448
  sortBySpecificity,
441
449
  stringifySelector,
442
- verifySelector
450
+ verifySelector,
443
451
  };