@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.
- package/README.md +325 -0
- package/bin/svg-matrix.js +994 -378
- package/bin/svglinter.cjs +4172 -433
- package/bin/svgm.js +744 -184
- package/package.json +16 -4
- package/src/animation-references.js +71 -52
- package/src/arc-length.js +160 -96
- package/src/bezier-analysis.js +257 -117
- package/src/bezier-intersections.js +411 -148
- package/src/browser-verify.js +240 -100
- package/src/clip-path-resolver.js +350 -142
- package/src/convert-path-data.js +279 -134
- package/src/css-specificity.js +78 -70
- package/src/flatten-pipeline.js +751 -263
- package/src/geometry-to-path.js +511 -182
- package/src/index.js +191 -46
- package/src/inkscape-support.js +404 -0
- package/src/marker-resolver.js +278 -164
- package/src/mask-resolver.js +209 -98
- package/src/matrix.js +147 -67
- package/src/mesh-gradient.js +187 -96
- package/src/off-canvas-detection.js +201 -104
- package/src/path-analysis.js +187 -107
- package/src/path-data-plugins.js +628 -167
- package/src/path-simplification.js +0 -1
- package/src/pattern-resolver.js +125 -88
- package/src/polygon-clip.js +111 -66
- package/src/svg-boolean-ops.js +194 -118
- package/src/svg-collections.js +48 -19
- package/src/svg-flatten.js +282 -164
- package/src/svg-parser.js +427 -200
- package/src/svg-rendering-context.js +147 -104
- package/src/svg-toolbox.js +16411 -3298
- package/src/svg2-polyfills.js +114 -245
- package/src/transform-decomposition.js +46 -41
- package/src/transform-optimization.js +89 -68
- package/src/transforms2d.js +49 -16
- package/src/transforms3d.js +58 -22
- package/src/use-symbol-resolver.js +150 -110
- package/src/vector.js +67 -15
- package/src/vendor/README.md +110 -0
- package/src/vendor/inkscape-hatch-polyfill.js +401 -0
- package/src/vendor/inkscape-hatch-polyfill.min.js +8 -0
- package/src/vendor/inkscape-mesh-polyfill.js +843 -0
- package/src/vendor/inkscape-mesh-polyfill.min.js +8 -0
- package/src/verification.js +288 -124
package/src/css-specificity.js
CHANGED
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|
// Selector component types
|
|
12
12
|
const SELECTOR_TYPES = {
|
|
13
|
-
ID:
|
|
14
|
-
CLASS:
|
|
15
|
-
ATTRIBUTE:
|
|
16
|
-
PSEUDO_CLASS:
|
|
17
|
-
PSEUDO_ELEMENT:
|
|
18
|
-
TYPE:
|
|
19
|
-
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 !==
|
|
44
|
-
throw new Error(
|
|
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
|
-
|
|
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 ===
|
|
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 ===
|
|
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)
|
|
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({
|
|
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)
|
|
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({
|
|
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 ===
|
|
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] ===
|
|
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] ===
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 !==
|
|
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(
|
|
399
|
+
throw new Error("components must be an array");
|
|
394
400
|
}
|
|
395
401
|
|
|
396
|
-
return components
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
};
|