@emasoft/svg-matrix 1.1.0 → 1.2.0
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/bin/svg-matrix.js +7 -6
- package/bin/svgm.js +109 -40
- package/dist/svg-matrix.min.js +7 -7
- package/dist/svg-toolbox.min.js +148 -228
- package/dist/svgm.min.js +152 -232
- package/dist/version.json +5 -5
- package/package.json +1 -1
- package/scripts/postinstall.js +72 -41
- package/scripts/test-postinstall.js +18 -16
- package/scripts/version-sync.js +78 -60
- package/src/animation-optimization.js +190 -98
- package/src/animation-references.js +11 -3
- package/src/arc-length.js +23 -20
- package/src/bezier-analysis.js +9 -13
- package/src/bezier-intersections.js +18 -4
- package/src/browser-verify.js +35 -8
- package/src/clip-path-resolver.js +285 -114
- package/src/convert-path-data.js +20 -8
- package/src/css-specificity.js +33 -9
- package/src/douglas-peucker.js +272 -141
- package/src/geometry-to-path.js +79 -22
- package/src/gjk-collision.js +287 -126
- package/src/index.js +56 -21
- package/src/inkscape-support.js +122 -101
- package/src/logger.js +43 -27
- package/src/marker-resolver.js +201 -121
- package/src/mask-resolver.js +231 -98
- package/src/matrix.js +9 -5
- package/src/mesh-gradient.js +22 -14
- package/src/off-canvas-detection.js +53 -17
- package/src/path-optimization.js +356 -171
- package/src/path-simplification.js +671 -256
- package/src/pattern-resolver.js +1 -3
- package/src/polygon-clip.js +396 -78
- package/src/svg-boolean-ops.js +90 -23
- package/src/svg-collections.js +1546 -667
- package/src/svg-flatten.js +152 -38
- package/src/svg-matrix-lib.js +2 -2
- package/src/svg-parser.js +5 -1
- package/src/svg-rendering-context.js +3 -1
- package/src/svg-toolbox-lib.js +2 -2
- package/src/svg-toolbox.js +99 -457
- package/src/svg-validation-data.js +513 -345
- package/src/svg2-polyfills.js +156 -93
- package/src/svgm-lib.js +8 -4
- package/src/transform-optimization.js +168 -51
- package/src/transforms2d.js +73 -40
- package/src/transforms3d.js +34 -27
- package/src/use-symbol-resolver.js +175 -76
- package/src/vector.js +80 -44
- package/src/vendor/inkscape-hatch-polyfill.js +143 -108
- package/src/vendor/inkscape-hatch-polyfill.min.js +291 -1
- package/src/vendor/inkscape-mesh-polyfill.js +953 -766
- package/src/vendor/inkscape-mesh-polyfill.min.js +896 -1
- package/src/verification.js +3 -4
package/src/inkscape-support.js
CHANGED
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
* @module inkscape-support
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { SVGElement } from
|
|
10
|
+
import { SVGElement } from "./svg-parser.js";
|
|
11
11
|
|
|
12
12
|
// Inkscape namespace URIs
|
|
13
|
-
export const INKSCAPE_NS =
|
|
14
|
-
export const SODIPODI_NS =
|
|
13
|
+
export const INKSCAPE_NS = "http://www.inkscape.org/namespaces/inkscape";
|
|
14
|
+
export const SODIPODI_NS = "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
|
|
15
15
|
|
|
16
16
|
// Inkscape-specific element and attribute prefixes
|
|
17
|
-
export const INKSCAPE_PREFIXES = [
|
|
17
|
+
export const INKSCAPE_PREFIXES = ["inkscape", "sodipodi"];
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Check if an element is an Inkscape layer.
|
|
@@ -24,10 +24,10 @@ export const INKSCAPE_PREFIXES = ['inkscape', 'sodipodi'];
|
|
|
24
24
|
* @returns {boolean} True if the element is an Inkscape layer
|
|
25
25
|
*/
|
|
26
26
|
export function isInkscapeLayer(element) {
|
|
27
|
-
if (!element || element.tagName !==
|
|
27
|
+
if (!element || element.tagName !== "g") return false;
|
|
28
28
|
// Safety check: ensure getAttribute method exists before calling it
|
|
29
|
-
if (typeof element.getAttribute !==
|
|
30
|
-
return element.getAttribute(
|
|
29
|
+
if (typeof element.getAttribute !== "function") return false;
|
|
30
|
+
return element.getAttribute("inkscape:groupmode") === "layer";
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
@@ -37,8 +37,8 @@ export function isInkscapeLayer(element) {
|
|
|
37
37
|
* @returns {string|null} Layer label or null if not set
|
|
38
38
|
*/
|
|
39
39
|
export function getLayerLabel(element) {
|
|
40
|
-
if (!element || typeof element.getAttribute !==
|
|
41
|
-
return element.getAttribute(
|
|
40
|
+
if (!element || typeof element.getAttribute !== "function") return null;
|
|
41
|
+
return element.getAttribute("inkscape:label") || null;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
@@ -56,7 +56,8 @@ export function findLayers(doc) {
|
|
|
56
56
|
layers.push({
|
|
57
57
|
element: el,
|
|
58
58
|
label: getLayerLabel(el),
|
|
59
|
-
id:
|
|
59
|
+
id:
|
|
60
|
+
typeof el.getAttribute === "function" ? el.getAttribute("id") : null,
|
|
60
61
|
});
|
|
61
62
|
}
|
|
62
63
|
// Safety check: ensure children is an array before iteration
|
|
@@ -87,7 +88,7 @@ export function getNamedViewSettings(doc) {
|
|
|
87
88
|
|
|
88
89
|
const findNamedview = (el) => {
|
|
89
90
|
if (!el) return;
|
|
90
|
-
if (el.tagName ===
|
|
91
|
+
if (el.tagName === "sodipodi:namedview") {
|
|
91
92
|
namedview = el;
|
|
92
93
|
return;
|
|
93
94
|
}
|
|
@@ -100,21 +101,21 @@ export function getNamedViewSettings(doc) {
|
|
|
100
101
|
};
|
|
101
102
|
|
|
102
103
|
findNamedview(doc);
|
|
103
|
-
if (!namedview || typeof namedview.getAttribute !==
|
|
104
|
+
if (!namedview || typeof namedview.getAttribute !== "function") return null;
|
|
104
105
|
|
|
105
106
|
return {
|
|
106
|
-
pagecolor: namedview.getAttribute(
|
|
107
|
-
bordercolor: namedview.getAttribute(
|
|
108
|
-
borderopacity: namedview.getAttribute(
|
|
109
|
-
showgrid: namedview.getAttribute(
|
|
110
|
-
showguides: namedview.getAttribute(
|
|
111
|
-
guidetolerance: namedview.getAttribute(
|
|
112
|
-
inkscapeZoom: namedview.getAttribute(
|
|
113
|
-
inkscapeCx: namedview.getAttribute(
|
|
114
|
-
inkscapeCy: namedview.getAttribute(
|
|
115
|
-
inkscapeWindowWidth: namedview.getAttribute(
|
|
116
|
-
inkscapeWindowHeight: namedview.getAttribute(
|
|
117
|
-
inkscapeCurrentLayer: namedview.getAttribute(
|
|
107
|
+
pagecolor: namedview.getAttribute("pagecolor"),
|
|
108
|
+
bordercolor: namedview.getAttribute("bordercolor"),
|
|
109
|
+
borderopacity: namedview.getAttribute("borderopacity"),
|
|
110
|
+
showgrid: namedview.getAttribute("showgrid"),
|
|
111
|
+
showguides: namedview.getAttribute("showguides"),
|
|
112
|
+
guidetolerance: namedview.getAttribute("guidetolerance"),
|
|
113
|
+
inkscapeZoom: namedview.getAttribute("inkscape:zoom"),
|
|
114
|
+
inkscapeCx: namedview.getAttribute("inkscape:cx"),
|
|
115
|
+
inkscapeCy: namedview.getAttribute("inkscape:cy"),
|
|
116
|
+
inkscapeWindowWidth: namedview.getAttribute("inkscape:window-width"),
|
|
117
|
+
inkscapeWindowHeight: namedview.getAttribute("inkscape:window-height"),
|
|
118
|
+
inkscapeCurrentLayer: namedview.getAttribute("inkscape:current-layer"),
|
|
118
119
|
};
|
|
119
120
|
}
|
|
120
121
|
|
|
@@ -132,9 +133,9 @@ export function findGuides(doc) {
|
|
|
132
133
|
|
|
133
134
|
const walk = (el) => {
|
|
134
135
|
if (!el) return;
|
|
135
|
-
if (el.tagName ===
|
|
136
|
-
const position = el.getAttribute?.(
|
|
137
|
-
const orientation = el.getAttribute?.(
|
|
136
|
+
if (el.tagName === "sodipodi:guide") {
|
|
137
|
+
const position = el.getAttribute?.("position") || null;
|
|
138
|
+
const orientation = el.getAttribute?.("orientation") || null;
|
|
138
139
|
|
|
139
140
|
// Validate guide has required attributes (position and orientation)
|
|
140
141
|
if (!position || !orientation) {
|
|
@@ -150,9 +151,9 @@ export function findGuides(doc) {
|
|
|
150
151
|
guides.push({
|
|
151
152
|
position,
|
|
152
153
|
orientation,
|
|
153
|
-
id: el.getAttribute?.(
|
|
154
|
-
inkscapeColor: el.getAttribute?.(
|
|
155
|
-
inkscapeLabel: el.getAttribute?.(
|
|
154
|
+
id: el.getAttribute?.("id") || null,
|
|
155
|
+
inkscapeColor: el.getAttribute?.("inkscape:color") || null,
|
|
156
|
+
inkscapeLabel: el.getAttribute?.("inkscape:label") || null,
|
|
156
157
|
});
|
|
157
158
|
}
|
|
158
159
|
// Safety check: ensure children is an array before iteration
|
|
@@ -175,29 +176,29 @@ export function findGuides(doc) {
|
|
|
175
176
|
* @returns {Object|null} Arc parameters or null if not an arc
|
|
176
177
|
*/
|
|
177
178
|
export function getArcParameters(element) {
|
|
178
|
-
if (!element || typeof element.getAttribute !==
|
|
179
|
+
if (!element || typeof element.getAttribute !== "function") return null;
|
|
179
180
|
|
|
180
|
-
const type = element.getAttribute(
|
|
181
|
-
if (type !==
|
|
181
|
+
const type = element.getAttribute("sodipodi:type");
|
|
182
|
+
if (type !== "arc") return null;
|
|
182
183
|
|
|
183
184
|
// Validate that required arc parameters exist
|
|
184
|
-
const cx = element.getAttribute(
|
|
185
|
-
const cy = element.getAttribute(
|
|
186
|
-
const rx = element.getAttribute(
|
|
187
|
-
const ry = element.getAttribute(
|
|
185
|
+
const cx = element.getAttribute("sodipodi:cx");
|
|
186
|
+
const cy = element.getAttribute("sodipodi:cy");
|
|
187
|
+
const rx = element.getAttribute("sodipodi:rx");
|
|
188
|
+
const ry = element.getAttribute("sodipodi:ry");
|
|
188
189
|
|
|
189
190
|
// Arc must have center and radii
|
|
190
191
|
if (!cx || !cy || !rx || !ry) return null;
|
|
191
192
|
|
|
192
193
|
return {
|
|
193
|
-
type:
|
|
194
|
+
type: "arc",
|
|
194
195
|
cx,
|
|
195
196
|
cy,
|
|
196
197
|
rx,
|
|
197
198
|
ry,
|
|
198
|
-
start: element.getAttribute(
|
|
199
|
-
end: element.getAttribute(
|
|
200
|
-
open: element.getAttribute(
|
|
199
|
+
start: element.getAttribute("sodipodi:start"),
|
|
200
|
+
end: element.getAttribute("sodipodi:end"),
|
|
201
|
+
open: element.getAttribute("sodipodi:open"),
|
|
201
202
|
};
|
|
202
203
|
}
|
|
203
204
|
|
|
@@ -209,9 +210,9 @@ export function getArcParameters(element) {
|
|
|
209
210
|
* @returns {string|null} Node types string (c=corner, s=smooth, z=symmetric, a=auto)
|
|
210
211
|
*/
|
|
211
212
|
export function getNodeTypes(element) {
|
|
212
|
-
if (!element || typeof element.getAttribute !==
|
|
213
|
+
if (!element || typeof element.getAttribute !== "function") return null;
|
|
213
214
|
|
|
214
|
-
const nodeTypes = element.getAttribute(
|
|
215
|
+
const nodeTypes = element.getAttribute("sodipodi:nodetypes");
|
|
215
216
|
if (!nodeTypes) return null;
|
|
216
217
|
|
|
217
218
|
// Validate format: should only contain c, s, z, a characters
|
|
@@ -227,11 +228,11 @@ export function getNodeTypes(element) {
|
|
|
227
228
|
* @returns {Object|null} Export settings or null if not set
|
|
228
229
|
*/
|
|
229
230
|
export function getExportSettings(element) {
|
|
230
|
-
if (!element || typeof element.getAttribute !==
|
|
231
|
+
if (!element || typeof element.getAttribute !== "function") return null;
|
|
231
232
|
|
|
232
|
-
const filename = element.getAttribute(
|
|
233
|
-
const xdpi = element.getAttribute(
|
|
234
|
-
const ydpi = element.getAttribute(
|
|
233
|
+
const filename = element.getAttribute("inkscape:export-filename");
|
|
234
|
+
const xdpi = element.getAttribute("inkscape:export-xdpi");
|
|
235
|
+
const ydpi = element.getAttribute("inkscape:export-ydpi");
|
|
235
236
|
|
|
236
237
|
if (!filename && !xdpi && !ydpi) return null;
|
|
237
238
|
|
|
@@ -241,8 +242,8 @@ export function getExportSettings(element) {
|
|
|
241
242
|
|
|
242
243
|
return {
|
|
243
244
|
filename,
|
|
244
|
-
xdpi:
|
|
245
|
-
ydpi:
|
|
245
|
+
xdpi: parsedXdpi !== null && !isNaN(parsedXdpi) ? parsedXdpi : null,
|
|
246
|
+
ydpi: parsedYdpi !== null && !isNaN(parsedYdpi) ? parsedYdpi : null,
|
|
246
247
|
};
|
|
247
248
|
}
|
|
248
249
|
|
|
@@ -253,8 +254,8 @@ export function getExportSettings(element) {
|
|
|
253
254
|
* @returns {boolean} True if element is a tiled clone
|
|
254
255
|
*/
|
|
255
256
|
export function isTiledClone(element) {
|
|
256
|
-
if (!element || typeof element.hasAttribute !==
|
|
257
|
-
return element.hasAttribute(
|
|
257
|
+
if (!element || typeof element.hasAttribute !== "function") return false;
|
|
258
|
+
return element.hasAttribute("inkscape:tiled-clone-of");
|
|
258
259
|
}
|
|
259
260
|
|
|
260
261
|
/**
|
|
@@ -264,8 +265,8 @@ export function isTiledClone(element) {
|
|
|
264
265
|
* @returns {string|null} Source element ID or null
|
|
265
266
|
*/
|
|
266
267
|
export function getTiledCloneSource(element) {
|
|
267
|
-
if (!element || typeof element.getAttribute !==
|
|
268
|
-
return element.getAttribute(
|
|
268
|
+
if (!element || typeof element.getAttribute !== "function") return null;
|
|
269
|
+
return element.getAttribute("inkscape:tiled-clone-of") || null;
|
|
269
270
|
}
|
|
270
271
|
|
|
271
272
|
/**
|
|
@@ -279,11 +280,11 @@ export function hasInkscapeNamespaces(doc) {
|
|
|
279
280
|
|
|
280
281
|
// Try documentElement first, fall back to doc itself
|
|
281
282
|
const svg = doc.documentElement || doc;
|
|
282
|
-
if (!svg || typeof svg.getAttribute !==
|
|
283
|
+
if (!svg || typeof svg.getAttribute !== "function") return false;
|
|
283
284
|
|
|
284
285
|
// Check for exact namespace URI matches
|
|
285
|
-
const inkscapeNs = svg.getAttribute(
|
|
286
|
-
const sodipodiNs = svg.getAttribute(
|
|
286
|
+
const inkscapeNs = svg.getAttribute("xmlns:inkscape");
|
|
287
|
+
const sodipodiNs = svg.getAttribute("xmlns:sodipodi");
|
|
287
288
|
|
|
288
289
|
const hasInkscape = inkscapeNs === INKSCAPE_NS;
|
|
289
290
|
const hasSodipodi = sodipodiNs === SODIPODI_NS;
|
|
@@ -305,15 +306,18 @@ export function ensureInkscapeNamespaces(doc) {
|
|
|
305
306
|
const svg = doc.documentElement || doc;
|
|
306
307
|
|
|
307
308
|
// Safety check: ensure getAttribute and setAttribute methods exist
|
|
308
|
-
if (
|
|
309
|
+
if (
|
|
310
|
+
typeof svg.getAttribute !== "function" ||
|
|
311
|
+
typeof svg.setAttribute !== "function"
|
|
312
|
+
) {
|
|
309
313
|
return doc;
|
|
310
314
|
}
|
|
311
315
|
|
|
312
|
-
if (!svg.getAttribute(
|
|
313
|
-
svg.setAttribute(
|
|
316
|
+
if (!svg.getAttribute("xmlns:inkscape")) {
|
|
317
|
+
svg.setAttribute("xmlns:inkscape", INKSCAPE_NS);
|
|
314
318
|
}
|
|
315
|
-
if (!svg.getAttribute(
|
|
316
|
-
svg.setAttribute(
|
|
319
|
+
if (!svg.getAttribute("xmlns:sodipodi")) {
|
|
320
|
+
svg.setAttribute("xmlns:sodipodi", SODIPODI_NS);
|
|
317
321
|
}
|
|
318
322
|
|
|
319
323
|
return doc;
|
|
@@ -339,24 +343,30 @@ export function findReferencedIds(element) {
|
|
|
339
343
|
|
|
340
344
|
// Attributes that can contain url(#id) references
|
|
341
345
|
const urlRefAttrs = [
|
|
342
|
-
|
|
343
|
-
|
|
346
|
+
"fill",
|
|
347
|
+
"stroke",
|
|
348
|
+
"clip-path",
|
|
349
|
+
"mask",
|
|
350
|
+
"filter",
|
|
351
|
+
"marker-start",
|
|
352
|
+
"marker-mid",
|
|
353
|
+
"marker-end",
|
|
344
354
|
];
|
|
345
355
|
|
|
346
356
|
// Attributes that can contain #id or url(#id) references
|
|
347
|
-
const hrefAttrs = [
|
|
357
|
+
const hrefAttrs = ["href", "xlink:href"];
|
|
348
358
|
|
|
349
359
|
const extractUrlId = (value) => {
|
|
350
|
-
if (!value || typeof value !==
|
|
360
|
+
if (!value || typeof value !== "string") return null;
|
|
351
361
|
// Match url(#id) or url("#id")
|
|
352
362
|
const match = value.match(/url\(["']?#([^"')]+)["']?\)/);
|
|
353
363
|
return match ? match[1] : null;
|
|
354
364
|
};
|
|
355
365
|
|
|
356
366
|
const extractHrefId = (value) => {
|
|
357
|
-
if (!value || typeof value !==
|
|
367
|
+
if (!value || typeof value !== "string") return null;
|
|
358
368
|
// Match #id references
|
|
359
|
-
if (value.startsWith(
|
|
369
|
+
if (value.startsWith("#")) {
|
|
360
370
|
return value.slice(1);
|
|
361
371
|
}
|
|
362
372
|
return null;
|
|
@@ -378,7 +388,7 @@ export function findReferencedIds(element) {
|
|
|
378
388
|
}
|
|
379
389
|
|
|
380
390
|
// Check style attribute for url() references
|
|
381
|
-
const style = el.getAttribute?.(
|
|
391
|
+
const style = el.getAttribute?.("style");
|
|
382
392
|
if (style) {
|
|
383
393
|
const urlMatches = style.matchAll(/url\(["']?#([^"')]+)["']?\)/g);
|
|
384
394
|
for (const match of urlMatches) {
|
|
@@ -414,7 +424,7 @@ export function buildDefsMapFromDefs(doc) {
|
|
|
414
424
|
if (!el) return;
|
|
415
425
|
|
|
416
426
|
// If element has an ID, add to map
|
|
417
|
-
const id = el.getAttribute?.(
|
|
427
|
+
const id = el.getAttribute?.("id");
|
|
418
428
|
if (id) {
|
|
419
429
|
defsMap.set(id, el);
|
|
420
430
|
}
|
|
@@ -430,7 +440,7 @@ export function buildDefsMapFromDefs(doc) {
|
|
|
430
440
|
// Only scan defs elements for efficiency
|
|
431
441
|
const findDefs = (el) => {
|
|
432
442
|
if (!el) return;
|
|
433
|
-
if (el.tagName ===
|
|
443
|
+
if (el.tagName === "defs") {
|
|
434
444
|
walk(el);
|
|
435
445
|
}
|
|
436
446
|
if (el.children && Array.isArray(el.children)) {
|
|
@@ -455,10 +465,12 @@ export function buildDefsMapFromDefs(doc) {
|
|
|
455
465
|
export function resolveDefsDependencies(initialIds, defsMap) {
|
|
456
466
|
// Validate parameters
|
|
457
467
|
if (!initialIds || !(initialIds instanceof Set)) {
|
|
458
|
-
throw new Error(
|
|
468
|
+
throw new Error(
|
|
469
|
+
"resolveDefsDependencies: initialIds parameter must be a Set",
|
|
470
|
+
);
|
|
459
471
|
}
|
|
460
472
|
if (!defsMap || !(defsMap instanceof Map)) {
|
|
461
|
-
throw new Error(
|
|
473
|
+
throw new Error("resolveDefsDependencies: defsMap parameter must be a Map");
|
|
462
474
|
}
|
|
463
475
|
|
|
464
476
|
const resolved = new Set();
|
|
@@ -498,7 +510,7 @@ export function cloneElement(element) {
|
|
|
498
510
|
const attrs = {};
|
|
499
511
|
if (element._attributes) {
|
|
500
512
|
Object.assign(attrs, element._attributes);
|
|
501
|
-
} else if (typeof element.getAttributeNames ===
|
|
513
|
+
} else if (typeof element.getAttributeNames === "function") {
|
|
502
514
|
for (const name of element.getAttributeNames()) {
|
|
503
515
|
attrs[name] = element.getAttribute(name);
|
|
504
516
|
}
|
|
@@ -520,7 +532,7 @@ export function cloneElement(element) {
|
|
|
520
532
|
element.tagName,
|
|
521
533
|
attrs,
|
|
522
534
|
clonedChildren,
|
|
523
|
-
element.textContent || null
|
|
535
|
+
element.textContent || null,
|
|
524
536
|
);
|
|
525
537
|
|
|
526
538
|
return clone;
|
|
@@ -540,16 +552,18 @@ export function cloneElement(element) {
|
|
|
540
552
|
export function extractLayer(doc, layerOrId, options = {}) {
|
|
541
553
|
// Validate doc parameter
|
|
542
554
|
if (!doc) {
|
|
543
|
-
throw new Error(
|
|
555
|
+
throw new Error("doc parameter is required");
|
|
544
556
|
}
|
|
545
557
|
|
|
546
558
|
const { preserveTransform = true } = options;
|
|
547
559
|
|
|
548
560
|
// Find the layer element
|
|
549
561
|
let layer;
|
|
550
|
-
if (typeof layerOrId ===
|
|
562
|
+
if (typeof layerOrId === "string") {
|
|
551
563
|
const layers = findLayers(doc);
|
|
552
|
-
const found = layers.find(
|
|
564
|
+
const found = layers.find(
|
|
565
|
+
(l) => l.id === layerOrId || l.label === layerOrId,
|
|
566
|
+
);
|
|
553
567
|
if (!found) {
|
|
554
568
|
throw new Error(`Layer not found: ${layerOrId}`);
|
|
555
569
|
}
|
|
@@ -559,13 +573,13 @@ export function extractLayer(doc, layerOrId, options = {}) {
|
|
|
559
573
|
layer = found.element;
|
|
560
574
|
} else {
|
|
561
575
|
if (!layerOrId) {
|
|
562
|
-
throw new Error(
|
|
576
|
+
throw new Error("layerOrId parameter is required");
|
|
563
577
|
}
|
|
564
578
|
layer = layerOrId;
|
|
565
579
|
}
|
|
566
580
|
|
|
567
581
|
if (!isInkscapeLayer(layer)) {
|
|
568
|
-
throw new Error(
|
|
582
|
+
throw new Error("Element is not an Inkscape layer");
|
|
569
583
|
}
|
|
570
584
|
|
|
571
585
|
// Get SVG root element
|
|
@@ -584,7 +598,7 @@ export function extractLayer(doc, layerOrId, options = {}) {
|
|
|
584
598
|
const svgAttrs = {};
|
|
585
599
|
if (svgRoot._attributes) {
|
|
586
600
|
Object.assign(svgAttrs, svgRoot._attributes);
|
|
587
|
-
} else if (typeof svgRoot.getAttributeNames ===
|
|
601
|
+
} else if (typeof svgRoot.getAttributeNames === "function") {
|
|
588
602
|
for (const name of svgRoot.getAttributeNames()) {
|
|
589
603
|
svgAttrs[name] = svgRoot.getAttribute(name);
|
|
590
604
|
}
|
|
@@ -603,7 +617,7 @@ export function extractLayer(doc, layerOrId, options = {}) {
|
|
|
603
617
|
}
|
|
604
618
|
}
|
|
605
619
|
if (defsChildren.length > 0) {
|
|
606
|
-
const newDefs = new SVGElement(
|
|
620
|
+
const newDefs = new SVGElement("defs", {}, defsChildren, null);
|
|
607
621
|
svgChildren.push(newDefs);
|
|
608
622
|
}
|
|
609
623
|
}
|
|
@@ -619,12 +633,15 @@ export function extractLayer(doc, layerOrId, options = {}) {
|
|
|
619
633
|
svgChildren.push(clonedLayer);
|
|
620
634
|
|
|
621
635
|
// Create new SVG document using SVGElement
|
|
622
|
-
const newSvg = new SVGElement(
|
|
636
|
+
const newSvg = new SVGElement("svg", svgAttrs, svgChildren, null);
|
|
623
637
|
|
|
624
638
|
// Get layer info
|
|
625
639
|
const layerInfo = {
|
|
626
|
-
id:
|
|
627
|
-
|
|
640
|
+
id:
|
|
641
|
+
typeof layer.getAttribute === "function"
|
|
642
|
+
? layer.getAttribute("id")
|
|
643
|
+
: null,
|
|
644
|
+
label: getLayerLabel(layer),
|
|
628
645
|
};
|
|
629
646
|
|
|
630
647
|
return { svg: newSvg, layerInfo };
|
|
@@ -642,7 +659,7 @@ export function extractLayer(doc, layerOrId, options = {}) {
|
|
|
642
659
|
export function extractAllLayers(doc, options = {}) {
|
|
643
660
|
// Validate doc parameter
|
|
644
661
|
if (!doc) {
|
|
645
|
-
throw new Error(
|
|
662
|
+
throw new Error("doc parameter is required");
|
|
646
663
|
}
|
|
647
664
|
|
|
648
665
|
const { includeHidden = false } = options;
|
|
@@ -655,20 +672,22 @@ export function extractAllLayers(doc, options = {}) {
|
|
|
655
672
|
// Skip hidden layers unless requested
|
|
656
673
|
if (!includeHidden) {
|
|
657
674
|
// Validate getAttribute method exists
|
|
658
|
-
if (typeof layer.getAttribute !==
|
|
675
|
+
if (typeof layer.getAttribute !== "function") continue;
|
|
659
676
|
|
|
660
|
-
const style = layer.getAttribute(
|
|
661
|
-
const display = layer.getAttribute(
|
|
662
|
-
const visibility = layer.getAttribute(
|
|
677
|
+
const style = layer.getAttribute("style") || "";
|
|
678
|
+
const display = layer.getAttribute("display");
|
|
679
|
+
const visibility = layer.getAttribute("visibility");
|
|
663
680
|
|
|
664
681
|
// Use regex to avoid partial matches in style attribute
|
|
665
682
|
const hasDisplayNone = /display\s*:\s*none/i.test(style);
|
|
666
683
|
const hasVisibilityHidden = /visibility\s*:\s*hidden/i.test(style);
|
|
667
684
|
|
|
668
|
-
if (
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
685
|
+
if (
|
|
686
|
+
display === "none" ||
|
|
687
|
+
visibility === "hidden" ||
|
|
688
|
+
hasDisplayNone ||
|
|
689
|
+
hasVisibilityHidden
|
|
690
|
+
) {
|
|
672
691
|
continue;
|
|
673
692
|
}
|
|
674
693
|
}
|
|
@@ -678,7 +697,9 @@ export function extractAllLayers(doc, options = {}) {
|
|
|
678
697
|
results.push(extracted);
|
|
679
698
|
} catch (e) {
|
|
680
699
|
// Skip layers that fail to extract
|
|
681
|
-
console.warn(
|
|
700
|
+
console.warn(
|
|
701
|
+
`Failed to extract layer ${layerData.id || layerData.label}: ${e.message}`,
|
|
702
|
+
);
|
|
682
703
|
}
|
|
683
704
|
}
|
|
684
705
|
|
|
@@ -695,17 +716,17 @@ export function extractAllLayers(doc, options = {}) {
|
|
|
695
716
|
export function analyzeLayerDependencies(doc) {
|
|
696
717
|
// Validate doc parameter
|
|
697
718
|
if (!doc) {
|
|
698
|
-
throw new Error(
|
|
719
|
+
throw new Error("doc parameter is required");
|
|
699
720
|
}
|
|
700
721
|
|
|
701
722
|
const layers = findLayers(doc);
|
|
702
723
|
const defsMap = buildDefsMapFromDefs(doc);
|
|
703
724
|
const layerRefs = new Map(); // layer ID -> Set of referenced def IDs
|
|
704
|
-
const defUsage = new Map();
|
|
725
|
+
const defUsage = new Map(); // def ID -> Set of layer IDs that use it
|
|
705
726
|
|
|
706
727
|
for (const layerData of layers) {
|
|
707
728
|
const layer = layerData.element;
|
|
708
|
-
const layerId = layerData.id || layerData.label ||
|
|
729
|
+
const layerId = layerData.id || layerData.label || "unnamed";
|
|
709
730
|
|
|
710
731
|
// Find refs for this layer
|
|
711
732
|
const refs = findReferencedIds(layer);
|
|
@@ -730,7 +751,7 @@ export function analyzeLayerDependencies(doc) {
|
|
|
730
751
|
if (layerSet.size > 1) {
|
|
731
752
|
sharedDefs.push({
|
|
732
753
|
id: defId,
|
|
733
|
-
usedBy: [...layerSet]
|
|
754
|
+
usedBy: [...layerSet],
|
|
734
755
|
});
|
|
735
756
|
} else {
|
|
736
757
|
const layerId = [...layerSet][0];
|
|
@@ -742,13 +763,13 @@ export function analyzeLayerDependencies(doc) {
|
|
|
742
763
|
}
|
|
743
764
|
|
|
744
765
|
return {
|
|
745
|
-
layers: layers.map(l => ({
|
|
766
|
+
layers: layers.map((l) => ({
|
|
746
767
|
id: l.id,
|
|
747
768
|
label: l.label,
|
|
748
|
-
referencedDefs: [...(layerRefs.get(l.id || l.label ||
|
|
769
|
+
referencedDefs: [...(layerRefs.get(l.id || l.label || "unnamed") || [])],
|
|
749
770
|
})),
|
|
750
771
|
sharedDefs,
|
|
751
772
|
exclusiveDefs: Object.fromEntries(exclusiveDefs),
|
|
752
|
-
totalDefs: defsMap.size
|
|
773
|
+
totalDefs: defsMap.size,
|
|
753
774
|
};
|
|
754
775
|
}
|