@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/svg2-polyfills.js
CHANGED
|
@@ -13,25 +13,26 @@
|
|
|
13
13
|
* @module svg2-polyfills
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { SVGElement } from
|
|
16
|
+
import { SVGElement } from "./svg-parser.js";
|
|
17
17
|
|
|
18
18
|
// Environment detection
|
|
19
|
-
const IS_NODE =
|
|
20
|
-
|
|
19
|
+
const IS_NODE =
|
|
20
|
+
typeof process !== "undefined" && process.versions && process.versions.node;
|
|
21
|
+
const IS_BROWSER = typeof window !== "undefined";
|
|
21
22
|
|
|
22
23
|
// Load Inkscape polyfills at module initialization (minified + full versions)
|
|
23
|
-
let INKSCAPE_MESH_POLYFILL_MIN =
|
|
24
|
-
let INKSCAPE_MESH_POLYFILL_FULL =
|
|
25
|
-
let INKSCAPE_HATCH_POLYFILL_MIN =
|
|
26
|
-
let INKSCAPE_HATCH_POLYFILL_FULL =
|
|
24
|
+
let INKSCAPE_MESH_POLYFILL_MIN = "";
|
|
25
|
+
let INKSCAPE_MESH_POLYFILL_FULL = "";
|
|
26
|
+
let INKSCAPE_HATCH_POLYFILL_MIN = "";
|
|
27
|
+
let INKSCAPE_HATCH_POLYFILL_FULL = "";
|
|
27
28
|
|
|
28
29
|
// In Node.js, load polyfills from filesystem
|
|
29
30
|
if (IS_NODE) {
|
|
30
31
|
try {
|
|
31
32
|
// Dynamic imports for Node.js-only modules
|
|
32
|
-
const fs = await import(
|
|
33
|
-
const url = await import(
|
|
34
|
-
const path = await import(
|
|
33
|
+
const fs = await import("fs");
|
|
34
|
+
const url = await import("url");
|
|
35
|
+
const path = await import("path");
|
|
35
36
|
|
|
36
37
|
const __filename = url.fileURLToPath(import.meta.url);
|
|
37
38
|
const __dirname = path.dirname(__filename);
|
|
@@ -39,32 +40,36 @@ if (IS_NODE) {
|
|
|
39
40
|
// Load mesh gradient polyfills (GPLv3 by Tavmjong Bah)
|
|
40
41
|
// Minified version (default, ~16KB)
|
|
41
42
|
INKSCAPE_MESH_POLYFILL_MIN = fs.readFileSync(
|
|
42
|
-
path.join(__dirname,
|
|
43
|
-
|
|
43
|
+
path.join(__dirname, "vendor", "inkscape-mesh-polyfill.min.js"),
|
|
44
|
+
"utf-8",
|
|
44
45
|
);
|
|
45
46
|
// Full version (~35KB, for debugging or when --no-minify-polyfills is used)
|
|
46
47
|
INKSCAPE_MESH_POLYFILL_FULL = fs.readFileSync(
|
|
47
|
-
path.join(__dirname,
|
|
48
|
-
|
|
48
|
+
path.join(__dirname, "vendor", "inkscape-mesh-polyfill.js"),
|
|
49
|
+
"utf-8",
|
|
49
50
|
);
|
|
50
51
|
|
|
51
52
|
// Load hatch polyfills (CC0/Public Domain by Valentin Ionita)
|
|
52
53
|
// Minified version (default, ~5KB)
|
|
53
54
|
INKSCAPE_HATCH_POLYFILL_MIN = fs.readFileSync(
|
|
54
|
-
path.join(__dirname,
|
|
55
|
-
|
|
55
|
+
path.join(__dirname, "vendor", "inkscape-hatch-polyfill.min.js"),
|
|
56
|
+
"utf-8",
|
|
56
57
|
);
|
|
57
58
|
// Full version (~10KB, for debugging)
|
|
58
59
|
INKSCAPE_HATCH_POLYFILL_FULL = fs.readFileSync(
|
|
59
|
-
path.join(__dirname,
|
|
60
|
-
|
|
60
|
+
path.join(__dirname, "vendor", "inkscape-hatch-polyfill.js"),
|
|
61
|
+
"utf-8",
|
|
61
62
|
);
|
|
62
63
|
} catch (e) {
|
|
63
|
-
throw new Error(
|
|
64
|
+
throw new Error(
|
|
65
|
+
`Failed to load SVG2 polyfill files from vendor/ directory: ${e.message}. Ensure all vendor files are present.`,
|
|
66
|
+
);
|
|
64
67
|
}
|
|
65
68
|
} else if (IS_BROWSER) {
|
|
66
69
|
// In browser, polyfills must be set via setPolyfillContent() or loaded separately
|
|
67
|
-
console.warn(
|
|
70
|
+
console.warn(
|
|
71
|
+
"SVG2 polyfills: Running in browser mode. Use setPolyfillContent() to provide polyfill scripts.",
|
|
72
|
+
);
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
// Module-level option for minification (default: true)
|
|
@@ -75,8 +80,10 @@ let useMinifiedPolyfills = true;
|
|
|
75
80
|
* @param {boolean} minify - True to use minified (default), false for full version
|
|
76
81
|
*/
|
|
77
82
|
export function setPolyfillMinification(minify) {
|
|
78
|
-
if (typeof minify !==
|
|
79
|
-
throw new Error(
|
|
83
|
+
if (typeof minify !== "boolean") {
|
|
84
|
+
throw new Error(
|
|
85
|
+
"setPolyfillMinification: minify parameter must be a boolean",
|
|
86
|
+
);
|
|
80
87
|
}
|
|
81
88
|
useMinifiedPolyfills = minify;
|
|
82
89
|
}
|
|
@@ -92,32 +99,34 @@ export function setPolyfillMinification(minify) {
|
|
|
92
99
|
*/
|
|
93
100
|
export function setPolyfillContent(polyfills) {
|
|
94
101
|
// Validate input - polyfills must be an object with string values
|
|
95
|
-
if (!polyfills || typeof polyfills !==
|
|
96
|
-
throw new Error(
|
|
102
|
+
if (!polyfills || typeof polyfills !== "object") {
|
|
103
|
+
throw new Error(
|
|
104
|
+
"setPolyfillContent: polyfills parameter must be an object",
|
|
105
|
+
);
|
|
97
106
|
}
|
|
98
107
|
|
|
99
108
|
// Validate and set each polyfill if provided
|
|
100
109
|
if (polyfills.meshMin !== undefined) {
|
|
101
|
-
if (typeof polyfills.meshMin !==
|
|
102
|
-
throw new Error(
|
|
110
|
+
if (typeof polyfills.meshMin !== "string") {
|
|
111
|
+
throw new Error("setPolyfillContent: meshMin must be a string");
|
|
103
112
|
}
|
|
104
113
|
INKSCAPE_MESH_POLYFILL_MIN = polyfills.meshMin;
|
|
105
114
|
}
|
|
106
115
|
if (polyfills.meshFull !== undefined) {
|
|
107
|
-
if (typeof polyfills.meshFull !==
|
|
108
|
-
throw new Error(
|
|
116
|
+
if (typeof polyfills.meshFull !== "string") {
|
|
117
|
+
throw new Error("setPolyfillContent: meshFull must be a string");
|
|
109
118
|
}
|
|
110
119
|
INKSCAPE_MESH_POLYFILL_FULL = polyfills.meshFull;
|
|
111
120
|
}
|
|
112
121
|
if (polyfills.hatchMin !== undefined) {
|
|
113
|
-
if (typeof polyfills.hatchMin !==
|
|
114
|
-
throw new Error(
|
|
122
|
+
if (typeof polyfills.hatchMin !== "string") {
|
|
123
|
+
throw new Error("setPolyfillContent: hatchMin must be a string");
|
|
115
124
|
}
|
|
116
125
|
INKSCAPE_HATCH_POLYFILL_MIN = polyfills.hatchMin;
|
|
117
126
|
}
|
|
118
127
|
if (polyfills.hatchFull !== undefined) {
|
|
119
|
-
if (typeof polyfills.hatchFull !==
|
|
120
|
-
throw new Error(
|
|
128
|
+
if (typeof polyfills.hatchFull !== "string") {
|
|
129
|
+
throw new Error("setPolyfillContent: hatchFull must be a string");
|
|
121
130
|
}
|
|
122
131
|
INKSCAPE_HATCH_POLYFILL_FULL = polyfills.hatchFull;
|
|
123
132
|
}
|
|
@@ -127,10 +136,10 @@ export function setPolyfillContent(polyfills) {
|
|
|
127
136
|
* SVG 2.0 features that can be polyfilled
|
|
128
137
|
*/
|
|
129
138
|
export const SVG2_FEATURES = {
|
|
130
|
-
MESH_GRADIENT:
|
|
131
|
-
HATCH:
|
|
132
|
-
CONTEXT_PAINT:
|
|
133
|
-
AUTO_START_REVERSE:
|
|
139
|
+
MESH_GRADIENT: "meshGradient",
|
|
140
|
+
HATCH: "hatch",
|
|
141
|
+
CONTEXT_PAINT: "context-paint",
|
|
142
|
+
AUTO_START_REVERSE: "auto-start-reverse",
|
|
134
143
|
};
|
|
135
144
|
|
|
136
145
|
/**
|
|
@@ -141,15 +150,20 @@ export const SVG2_FEATURES = {
|
|
|
141
150
|
*/
|
|
142
151
|
export function detectSVG2Features(doc) {
|
|
143
152
|
// WHY: Validate doc is an object before attempting to traverse it
|
|
144
|
-
if (!doc || typeof doc !==
|
|
145
|
-
return {
|
|
153
|
+
if (!doc || typeof doc !== "object") {
|
|
154
|
+
return {
|
|
155
|
+
meshGradients: [],
|
|
156
|
+
hatches: [],
|
|
157
|
+
contextPaint: false,
|
|
158
|
+
autoStartReverse: false,
|
|
159
|
+
};
|
|
146
160
|
}
|
|
147
161
|
|
|
148
162
|
const features = {
|
|
149
163
|
meshGradients: [],
|
|
150
164
|
hatches: [],
|
|
151
165
|
contextPaint: false,
|
|
152
|
-
autoStartReverse: false
|
|
166
|
+
autoStartReverse: false,
|
|
153
167
|
};
|
|
154
168
|
|
|
155
169
|
const walk = (el) => {
|
|
@@ -157,44 +171,62 @@ export function detectSVG2Features(doc) {
|
|
|
157
171
|
|
|
158
172
|
// Check tag name for mesh gradient (case-insensitive)
|
|
159
173
|
const tagName = el.tagName?.toLowerCase();
|
|
160
|
-
if (tagName ===
|
|
161
|
-
const id = el.getAttribute(
|
|
174
|
+
if (tagName === "meshgradient") {
|
|
175
|
+
const id = el.getAttribute("id");
|
|
162
176
|
// WHY: Validate ID is non-empty string and not already in array to prevent duplicates
|
|
163
|
-
if (
|
|
177
|
+
if (
|
|
178
|
+
id &&
|
|
179
|
+
typeof id === "string" &&
|
|
180
|
+
id.trim() &&
|
|
181
|
+
!features.meshGradients.includes(id)
|
|
182
|
+
) {
|
|
164
183
|
features.meshGradients.push(id);
|
|
165
184
|
}
|
|
166
185
|
}
|
|
167
186
|
|
|
168
187
|
// Check for hatch element
|
|
169
|
-
if (tagName ===
|
|
170
|
-
const id = el.getAttribute(
|
|
188
|
+
if (tagName === "hatch") {
|
|
189
|
+
const id = el.getAttribute("id");
|
|
171
190
|
// WHY: Validate ID is non-empty string and not already in array to prevent duplicates
|
|
172
|
-
if (
|
|
191
|
+
if (
|
|
192
|
+
id &&
|
|
193
|
+
typeof id === "string" &&
|
|
194
|
+
id.trim() &&
|
|
195
|
+
!features.hatches.includes(id)
|
|
196
|
+
) {
|
|
173
197
|
features.hatches.push(id);
|
|
174
198
|
}
|
|
175
199
|
}
|
|
176
200
|
|
|
177
201
|
// Check for context-paint in fill/stroke attributes and style attribute
|
|
178
|
-
const fill = el.getAttribute(
|
|
179
|
-
const stroke = el.getAttribute(
|
|
180
|
-
const style = el.getAttribute(
|
|
202
|
+
const fill = el.getAttribute("fill");
|
|
203
|
+
const stroke = el.getAttribute("stroke");
|
|
204
|
+
const style = el.getAttribute("style");
|
|
181
205
|
// WHY: context-paint can appear in style attribute, not just fill/stroke attributes
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
206
|
+
if (
|
|
207
|
+
fill === "context-fill" ||
|
|
208
|
+
fill === "context-stroke" ||
|
|
209
|
+
stroke === "context-fill" ||
|
|
210
|
+
stroke === "context-stroke" ||
|
|
211
|
+
(style &&
|
|
212
|
+
(style.includes("context-fill") || style.includes("context-stroke")))
|
|
213
|
+
) {
|
|
185
214
|
features.contextPaint = true;
|
|
186
215
|
}
|
|
187
216
|
|
|
188
217
|
// WHY: Check <style> elements for context-paint in CSS rules
|
|
189
|
-
if (tagName ===
|
|
190
|
-
if (
|
|
218
|
+
if (tagName === "style" && el.textContent) {
|
|
219
|
+
if (
|
|
220
|
+
el.textContent.includes("context-fill") ||
|
|
221
|
+
el.textContent.includes("context-stroke")
|
|
222
|
+
) {
|
|
191
223
|
features.contextPaint = true;
|
|
192
224
|
}
|
|
193
225
|
}
|
|
194
226
|
|
|
195
227
|
// Check for auto-start-reverse in markers
|
|
196
|
-
const orient = el.getAttribute(
|
|
197
|
-
if (orient ===
|
|
228
|
+
const orient = el.getAttribute("orient");
|
|
229
|
+
if (orient === "auto-start-reverse") {
|
|
198
230
|
features.autoStartReverse = true;
|
|
199
231
|
}
|
|
200
232
|
|
|
@@ -220,10 +252,12 @@ export function needsPolyfills(doc) {
|
|
|
220
252
|
if (!doc) return false;
|
|
221
253
|
|
|
222
254
|
const features = detectSVG2Features(doc);
|
|
223
|
-
return
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
255
|
+
return (
|
|
256
|
+
features.meshGradients.length > 0 ||
|
|
257
|
+
features.hatches.length > 0 ||
|
|
258
|
+
features.contextPaint ||
|
|
259
|
+
features.autoStartReverse
|
|
260
|
+
);
|
|
227
261
|
}
|
|
228
262
|
|
|
229
263
|
/**
|
|
@@ -242,10 +276,14 @@ export function needsPolyfills(doc) {
|
|
|
242
276
|
*/
|
|
243
277
|
function generateMeshPolyfillCode() {
|
|
244
278
|
// Return minified or full Inkscape mesh.js polyfill based on setting
|
|
245
|
-
const polyfill = useMinifiedPolyfills
|
|
279
|
+
const polyfill = useMinifiedPolyfills
|
|
280
|
+
? INKSCAPE_MESH_POLYFILL_MIN
|
|
281
|
+
: INKSCAPE_MESH_POLYFILL_FULL;
|
|
246
282
|
// WHY: Throw error instead of silently returning empty string to prevent broken SVG output
|
|
247
|
-
if (!polyfill || polyfill.trim() ===
|
|
248
|
-
throw new Error(
|
|
283
|
+
if (!polyfill || polyfill.trim() === "") {
|
|
284
|
+
throw new Error(
|
|
285
|
+
"svg2-polyfills: Inkscape mesh polyfill not available. Ensure vendor/inkscape-mesh-polyfill files are present.",
|
|
286
|
+
);
|
|
249
287
|
}
|
|
250
288
|
return polyfill;
|
|
251
289
|
}
|
|
@@ -265,10 +303,14 @@ function generateMeshPolyfillCode() {
|
|
|
265
303
|
*/
|
|
266
304
|
function generateHatchPolyfillCode() {
|
|
267
305
|
// Return minified or full Inkscape hatch polyfill based on setting
|
|
268
|
-
const polyfill = useMinifiedPolyfills
|
|
306
|
+
const polyfill = useMinifiedPolyfills
|
|
307
|
+
? INKSCAPE_HATCH_POLYFILL_MIN
|
|
308
|
+
: INKSCAPE_HATCH_POLYFILL_FULL;
|
|
269
309
|
// WHY: Throw error instead of silently returning empty string to prevent broken SVG output
|
|
270
|
-
if (!polyfill || polyfill.trim() ===
|
|
271
|
-
throw new Error(
|
|
310
|
+
if (!polyfill || polyfill.trim() === "") {
|
|
311
|
+
throw new Error(
|
|
312
|
+
"svg2-polyfills: Inkscape hatch polyfill not available. Ensure vendor/inkscape-hatch-polyfill files are present.",
|
|
313
|
+
);
|
|
272
314
|
}
|
|
273
315
|
return polyfill;
|
|
274
316
|
}
|
|
@@ -281,33 +323,47 @@ function generateHatchPolyfillCode() {
|
|
|
281
323
|
*/
|
|
282
324
|
export function generatePolyfillScript(features) {
|
|
283
325
|
// WHY: Explicit null check before typeof check prevents null passing as object
|
|
284
|
-
if (!features || typeof features !==
|
|
285
|
-
throw new Error(
|
|
326
|
+
if (!features || typeof features !== "object") {
|
|
327
|
+
throw new Error(
|
|
328
|
+
"generatePolyfillScript: features parameter must be an object",
|
|
329
|
+
);
|
|
286
330
|
}
|
|
287
331
|
if (!Array.isArray(features.meshGradients)) {
|
|
288
|
-
throw new Error(
|
|
332
|
+
throw new Error(
|
|
333
|
+
"generatePolyfillScript: features.meshGradients must be an array",
|
|
334
|
+
);
|
|
289
335
|
}
|
|
290
336
|
if (!Array.isArray(features.hatches)) {
|
|
291
|
-
throw new Error(
|
|
337
|
+
throw new Error(
|
|
338
|
+
"generatePolyfillScript: features.hatches must be an array",
|
|
339
|
+
);
|
|
292
340
|
}
|
|
293
341
|
// WHY: Validate array contents are all strings to prevent type errors
|
|
294
|
-
if (!features.meshGradients.every(id => typeof id ===
|
|
295
|
-
throw new Error(
|
|
342
|
+
if (!features.meshGradients.every((id) => typeof id === "string")) {
|
|
343
|
+
throw new Error(
|
|
344
|
+
"generatePolyfillScript: features.meshGradients must contain only strings",
|
|
345
|
+
);
|
|
296
346
|
}
|
|
297
|
-
if (!features.hatches.every(id => typeof id ===
|
|
298
|
-
throw new Error(
|
|
347
|
+
if (!features.hatches.every((id) => typeof id === "string")) {
|
|
348
|
+
throw new Error(
|
|
349
|
+
"generatePolyfillScript: features.hatches must contain only strings",
|
|
350
|
+
);
|
|
299
351
|
}
|
|
300
352
|
// WHY: Validate boolean properties to prevent undefined/wrong type usage
|
|
301
|
-
if (typeof features.contextPaint !==
|
|
302
|
-
throw new Error(
|
|
353
|
+
if (typeof features.contextPaint !== "boolean") {
|
|
354
|
+
throw new Error(
|
|
355
|
+
"generatePolyfillScript: features.contextPaint must be a boolean",
|
|
356
|
+
);
|
|
303
357
|
}
|
|
304
|
-
if (typeof features.autoStartReverse !==
|
|
305
|
-
throw new Error(
|
|
358
|
+
if (typeof features.autoStartReverse !== "boolean") {
|
|
359
|
+
throw new Error(
|
|
360
|
+
"generatePolyfillScript: features.autoStartReverse must be a boolean",
|
|
361
|
+
);
|
|
306
362
|
}
|
|
307
363
|
|
|
308
364
|
const parts = [];
|
|
309
365
|
|
|
310
|
-
parts.push(
|
|
366
|
+
parts.push("/* SVG 2.0 Polyfills - Generated by svg-matrix */");
|
|
311
367
|
|
|
312
368
|
if (features.meshGradients.length > 0) {
|
|
313
369
|
parts.push(generateMeshPolyfillCode());
|
|
@@ -321,7 +377,7 @@ export function generatePolyfillScript(features) {
|
|
|
321
377
|
return null; // Only header, no actual polyfills
|
|
322
378
|
}
|
|
323
379
|
|
|
324
|
-
return parts.join(
|
|
380
|
+
return parts.join("\n");
|
|
325
381
|
}
|
|
326
382
|
|
|
327
383
|
/**
|
|
@@ -340,9 +396,11 @@ export function injectPolyfills(doc, options = {}) {
|
|
|
340
396
|
const features = options.features || detectSVG2Features(doc);
|
|
341
397
|
|
|
342
398
|
// Check if polyfills are needed
|
|
343
|
-
if (
|
|
344
|
-
|
|
345
|
-
|
|
399
|
+
if (
|
|
400
|
+
!options.force &&
|
|
401
|
+
features.meshGradients.length === 0 &&
|
|
402
|
+
features.hatches.length === 0
|
|
403
|
+
) {
|
|
346
404
|
return doc;
|
|
347
405
|
}
|
|
348
406
|
|
|
@@ -357,10 +415,15 @@ export function injectPolyfills(doc, options = {}) {
|
|
|
357
415
|
const wrappedScript = `\n// <![CDATA[\n${script}\n// ]]>\n`;
|
|
358
416
|
|
|
359
417
|
// Create a proper SVGElement for the script
|
|
360
|
-
const scriptEl = new SVGElement(
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
418
|
+
const scriptEl = new SVGElement(
|
|
419
|
+
"script",
|
|
420
|
+
{
|
|
421
|
+
type: "text/javascript",
|
|
422
|
+
id: "svg-matrix-polyfill",
|
|
423
|
+
},
|
|
424
|
+
[],
|
|
425
|
+
wrappedScript,
|
|
426
|
+
);
|
|
364
427
|
|
|
365
428
|
// Insert script at beginning of SVG (after defs if present, else at start)
|
|
366
429
|
if (!Array.isArray(svg.children)) {
|
|
@@ -373,7 +436,7 @@ export function injectPolyfills(doc, options = {}) {
|
|
|
373
436
|
let insertIdx = 0;
|
|
374
437
|
for (let i = 0; i < svg.children.length; i++) {
|
|
375
438
|
// WHY: Optional chaining prevents errors if array contains null/undefined elements
|
|
376
|
-
if (svg.children[i]?.tagName ===
|
|
439
|
+
if (svg.children[i]?.tagName === "defs") {
|
|
377
440
|
insertIdx = i + 1; // Position after this defs (don't break - continue to find last defs)
|
|
378
441
|
}
|
|
379
442
|
}
|
|
@@ -398,17 +461,17 @@ export function removePolyfills(doc) {
|
|
|
398
461
|
if (!el || !el.children || !Array.isArray(el.children)) return;
|
|
399
462
|
|
|
400
463
|
// Remove script elements that are svg-matrix polyfills
|
|
401
|
-
el.children = el.children.filter(child => {
|
|
464
|
+
el.children = el.children.filter((child) => {
|
|
402
465
|
// WHY: Optional chaining prevents errors if child is null/undefined
|
|
403
|
-
if (child?.tagName ===
|
|
466
|
+
if (child?.tagName === "script") {
|
|
404
467
|
// WHY: Check id attribute first (more reliable), then fallback to content check
|
|
405
|
-
const id = child.getAttribute?.(
|
|
406
|
-
if (id ===
|
|
468
|
+
const id = child.getAttribute?.("id");
|
|
469
|
+
if (id === "svg-matrix-polyfill") {
|
|
407
470
|
return false;
|
|
408
471
|
}
|
|
409
472
|
// WHY: Fallback for older polyfills without id attribute
|
|
410
|
-
const content = child.textContent ||
|
|
411
|
-
if (content.includes(
|
|
473
|
+
const content = child.textContent || "";
|
|
474
|
+
if (content.includes("SVG 2.0 Polyfills - Generated by svg-matrix")) {
|
|
412
475
|
return false;
|
|
413
476
|
}
|
|
414
477
|
}
|
package/src/svgm-lib.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* comprehensive SVG manipulation (SVGToolbox). Works in Node.js and browser.
|
|
6
6
|
*
|
|
7
7
|
* @module svgm-lib
|
|
8
|
-
* @version 1.
|
|
8
|
+
* @version 1.2.0
|
|
9
9
|
* @license MIT
|
|
10
10
|
*
|
|
11
11
|
* @example Browser usage:
|
|
@@ -49,7 +49,7 @@ Decimal.set({ precision: 80 });
|
|
|
49
49
|
/**
|
|
50
50
|
* Library version
|
|
51
51
|
*/
|
|
52
|
-
export const VERSION = "1.
|
|
52
|
+
export const VERSION = "1.2.0";
|
|
53
53
|
|
|
54
54
|
// Export math classes
|
|
55
55
|
export { Decimal, Matrix, Vector };
|
|
@@ -63,8 +63,12 @@ export * from "./svg-toolbox.js";
|
|
|
63
63
|
export const { parseSVG, serializeSVG } = SVGParser;
|
|
64
64
|
|
|
65
65
|
// Export polyfills
|
|
66
|
-
export const {
|
|
67
|
-
|
|
66
|
+
export const {
|
|
67
|
+
detectSVG2Features,
|
|
68
|
+
injectPolyfills,
|
|
69
|
+
setPolyfillMinification,
|
|
70
|
+
setPolyfillContent,
|
|
71
|
+
} = SVG2Polyfills;
|
|
68
72
|
|
|
69
73
|
/**
|
|
70
74
|
* High-level process function - load, optimize, save in one call.
|