@emasoft/svg-matrix 1.0.19 → 1.0.21
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 +256 -759
- package/bin/svg-matrix.js +171 -2
- package/bin/svglinter.cjs +1162 -0
- package/package.json +9 -3
- package/src/animation-optimization.js +394 -0
- package/src/animation-references.js +440 -0
- package/src/arc-length.js +940 -0
- package/src/bezier-analysis.js +1626 -0
- package/src/bezier-intersections.js +1369 -0
- package/src/clip-path-resolver.js +110 -2
- package/src/convert-path-data.js +583 -0
- package/src/css-specificity.js +443 -0
- package/src/douglas-peucker.js +356 -0
- package/src/flatten-pipeline.js +109 -4
- package/src/geometry-to-path.js +126 -16
- package/src/gjk-collision.js +840 -0
- package/src/index.js +175 -2
- package/src/off-canvas-detection.js +1222 -0
- package/src/path-analysis.js +1241 -0
- package/src/path-data-plugins.js +928 -0
- package/src/path-optimization.js +825 -0
- package/src/path-simplification.js +1140 -0
- package/src/polygon-clip.js +376 -99
- package/src/svg-boolean-ops.js +898 -0
- package/src/svg-collections.js +910 -0
- package/src/svg-parser.js +175 -16
- package/src/svg-rendering-context.js +627 -0
- package/src/svg-toolbox.js +7495 -0
- package/src/svg-validation-data.js +944 -0
- package/src/transform-decomposition.js +810 -0
- package/src/transform-optimization.js +936 -0
- package/src/use-symbol-resolver.js +75 -7
|
@@ -24,6 +24,34 @@ Decimal.set({ precision: 80 });
|
|
|
24
24
|
|
|
25
25
|
const D = x => (x instanceof Decimal ? x : new Decimal(x));
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Detect circular references when resolving use/symbol references.
|
|
29
|
+
* Prevents infinite loops when SVG contains circular reference chains like:
|
|
30
|
+
* - <use href="#a"> where #a contains <use href="#a"> (self-reference)
|
|
31
|
+
* - <use href="#a"> → #a contains <use href="#b"> → #b contains <use href="#a"> (circular chain)
|
|
32
|
+
*
|
|
33
|
+
* @param {string} startId - Starting ID to check
|
|
34
|
+
* @param {Function} getNextId - Function that returns the next referenced ID given current ID
|
|
35
|
+
* @param {number} maxDepth - Maximum chain depth before considering circular (default 100)
|
|
36
|
+
* @returns {boolean} True if circular reference detected
|
|
37
|
+
*/
|
|
38
|
+
function hasCircularReference(startId, getNextId, maxDepth = 100) {
|
|
39
|
+
const visited = new Set();
|
|
40
|
+
let currentId = startId;
|
|
41
|
+
let depth = 0;
|
|
42
|
+
|
|
43
|
+
while (currentId && depth < maxDepth) {
|
|
44
|
+
if (visited.has(currentId)) {
|
|
45
|
+
return true; // Circular reference detected!
|
|
46
|
+
}
|
|
47
|
+
visited.add(currentId);
|
|
48
|
+
currentId = getNextId(currentId);
|
|
49
|
+
depth++;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return depth >= maxDepth; // Too deep = likely circular
|
|
53
|
+
}
|
|
54
|
+
|
|
27
55
|
/**
|
|
28
56
|
* Parse SVG <use> element to structured data.
|
|
29
57
|
*
|
|
@@ -438,13 +466,13 @@ export function calculateViewBoxTransform(viewBox, targetWidth, targetHeight, pr
|
|
|
438
466
|
*
|
|
439
467
|
* This is the core use/symbol resolution algorithm. It:
|
|
440
468
|
* 1. Looks up the target element by id (can be symbol, shape, group, or nested use)
|
|
441
|
-
* 2. Composes transforms: translation from x,y →
|
|
469
|
+
* 2. Composes transforms: use's transform → translation from x,y → viewBox mapping
|
|
442
470
|
* 3. Recursively resolves nested <use> elements (with depth limit)
|
|
443
471
|
* 4. Propagates style inheritance from <use> to referenced content
|
|
444
472
|
*
|
|
445
473
|
* Transform composition order (right-to-left multiplication):
|
|
446
|
-
* - First:
|
|
447
|
-
* - Second:
|
|
474
|
+
* - First: apply use element's transform attribute
|
|
475
|
+
* - Second: translate by (x, y) to position the reference
|
|
448
476
|
* - Third: apply viewBox→viewport mapping (symbols only)
|
|
449
477
|
*
|
|
450
478
|
* For symbols with viewBox, if <use> specifies width/height, those establish the
|
|
@@ -515,16 +543,26 @@ export function resolveUse(useData, defs, options = {}) {
|
|
|
515
543
|
return null;
|
|
516
544
|
}
|
|
517
545
|
|
|
518
|
-
//
|
|
546
|
+
// CORRECT ORDER per SVG spec:
|
|
547
|
+
// 1. Apply use element's transform attribute first
|
|
548
|
+
// 2. Then apply translate(x, y) for use element's x/y attributes
|
|
549
|
+
// 3. Then apply viewBox transform (for symbols)
|
|
550
|
+
//
|
|
551
|
+
// Matrix multiplication order: rightmost transform is applied first
|
|
552
|
+
// So to apply transforms in order 1→2→3, we build: 3.mul(2).mul(1)
|
|
553
|
+
|
|
554
|
+
// Start with x,y translation (step 2)
|
|
519
555
|
let transform = Transforms2D.translation(useData.x, useData.y);
|
|
520
556
|
|
|
521
|
-
//
|
|
557
|
+
// Pre-multiply by use element's transform if present (step 1)
|
|
558
|
+
// This makes useTransform apply FIRST, then translation
|
|
522
559
|
if (useData.transform) {
|
|
523
560
|
const useTransform = ClipPathResolver.parseTransform(useData.transform);
|
|
524
561
|
transform = transform.mul(useTransform);
|
|
525
562
|
}
|
|
526
563
|
|
|
527
|
-
// Handle symbol with viewBox
|
|
564
|
+
// Handle symbol with viewBox (step 3)
|
|
565
|
+
// ViewBox transform applies LAST (after translation and useTransform)
|
|
528
566
|
if (target.type === 'symbol' && target.viewBoxParsed) {
|
|
529
567
|
const width = useData.width || target.viewBoxParsed.width;
|
|
530
568
|
const height = useData.height || target.viewBoxParsed.height;
|
|
@@ -536,7 +574,8 @@ export function resolveUse(useData, defs, options = {}) {
|
|
|
536
574
|
target.preserveAspectRatio
|
|
537
575
|
);
|
|
538
576
|
|
|
539
|
-
transform
|
|
577
|
+
// ViewBox transform is applied LAST, so it's the leftmost in multiplication
|
|
578
|
+
transform = viewBoxTransform.mul(transform);
|
|
540
579
|
}
|
|
541
580
|
|
|
542
581
|
// Resolve children
|
|
@@ -1110,8 +1149,37 @@ export function resolveAllUses(svgRoot, options = {}) {
|
|
|
1110
1149
|
const useElements = svgRoot.querySelectorAll('use');
|
|
1111
1150
|
const resolved = [];
|
|
1112
1151
|
|
|
1152
|
+
// Helper to get the next use reference from a definition
|
|
1153
|
+
const getUseRef = (id) => {
|
|
1154
|
+
const target = defs[id];
|
|
1155
|
+
if (!target) return null;
|
|
1156
|
+
|
|
1157
|
+
// Check if target is itself a use element
|
|
1158
|
+
if (target.type === 'use') {
|
|
1159
|
+
return target.href;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// Check if target contains use elements in its children
|
|
1163
|
+
if (target.children && target.children.length > 0) {
|
|
1164
|
+
for (const child of target.children) {
|
|
1165
|
+
if (child.type === 'use') {
|
|
1166
|
+
return child.href;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
return null;
|
|
1172
|
+
};
|
|
1173
|
+
|
|
1113
1174
|
for (const useEl of useElements) {
|
|
1114
1175
|
const useData = parseUseElement(useEl);
|
|
1176
|
+
|
|
1177
|
+
// Check for circular reference before attempting to resolve
|
|
1178
|
+
if (hasCircularReference(useData.href, getUseRef)) {
|
|
1179
|
+
console.warn(`Circular use reference detected: #${useData.href}, skipping resolution`);
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1115
1183
|
const result = resolveUse(useData, defs, options);
|
|
1116
1184
|
if (result) {
|
|
1117
1185
|
resolved.push({
|