@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/svg2-polyfills.js
CHANGED
|
@@ -4,12 +4,68 @@
|
|
|
4
4
|
* Detects SVG 2.0 features (mesh gradients, hatches) and generates inline
|
|
5
5
|
* JavaScript polyfills for browser compatibility.
|
|
6
6
|
*
|
|
7
|
-
* Uses the
|
|
7
|
+
* Uses the Inkscape mesh.js polyfill by Tavmjong Bah for mesh gradient support.
|
|
8
|
+
* The mesh polyfill is licensed under GPLv3 - see src/vendor/inkscape-mesh-polyfill.js
|
|
9
|
+
* Hatch polyfills use a simplified MIT-licensed implementation.
|
|
10
|
+
*
|
|
8
11
|
* All polyfills are embedded inline in the SVG for self-contained output.
|
|
9
12
|
*
|
|
10
13
|
* @module svg2-polyfills
|
|
11
14
|
*/
|
|
12
15
|
|
|
16
|
+
import { SVGElement } from './svg-parser.js';
|
|
17
|
+
import { readFileSync } from 'fs';
|
|
18
|
+
import { fileURLToPath } from 'url';
|
|
19
|
+
import { dirname, join } from 'path';
|
|
20
|
+
|
|
21
|
+
// Load Inkscape polyfills at module initialization (minified + full versions)
|
|
22
|
+
let INKSCAPE_MESH_POLYFILL_MIN = '';
|
|
23
|
+
let INKSCAPE_MESH_POLYFILL_FULL = '';
|
|
24
|
+
let INKSCAPE_HATCH_POLYFILL_MIN = '';
|
|
25
|
+
let INKSCAPE_HATCH_POLYFILL_FULL = '';
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
29
|
+
const __dirname = dirname(__filename);
|
|
30
|
+
|
|
31
|
+
// Load mesh gradient polyfills (GPLv3 by Tavmjong Bah)
|
|
32
|
+
// Minified version (default, ~16KB)
|
|
33
|
+
INKSCAPE_MESH_POLYFILL_MIN = readFileSync(
|
|
34
|
+
join(__dirname, 'vendor', 'inkscape-mesh-polyfill.min.js'),
|
|
35
|
+
'utf-8'
|
|
36
|
+
);
|
|
37
|
+
// Full version (~35KB, for debugging or when --no-minify-polyfills is used)
|
|
38
|
+
INKSCAPE_MESH_POLYFILL_FULL = readFileSync(
|
|
39
|
+
join(__dirname, 'vendor', 'inkscape-mesh-polyfill.js'),
|
|
40
|
+
'utf-8'
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// Load hatch polyfills (CC0/Public Domain by Valentin Ionita)
|
|
44
|
+
// Minified version (default, ~5KB)
|
|
45
|
+
INKSCAPE_HATCH_POLYFILL_MIN = readFileSync(
|
|
46
|
+
join(__dirname, 'vendor', 'inkscape-hatch-polyfill.min.js'),
|
|
47
|
+
'utf-8'
|
|
48
|
+
);
|
|
49
|
+
// Full version (~10KB, for debugging)
|
|
50
|
+
INKSCAPE_HATCH_POLYFILL_FULL = readFileSync(
|
|
51
|
+
join(__dirname, 'vendor', 'inkscape-hatch-polyfill.js'),
|
|
52
|
+
'utf-8'
|
|
53
|
+
);
|
|
54
|
+
} catch (e) {
|
|
55
|
+
throw new Error(`Failed to load SVG2 polyfill files from vendor/ directory: ${e.message}. Ensure all vendor files are present.`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Module-level option for minification (default: true)
|
|
59
|
+
let useMinifiedPolyfills = true;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Set whether to use minified polyfills.
|
|
63
|
+
* @param {boolean} minify - True to use minified (default), false for full version
|
|
64
|
+
*/
|
|
65
|
+
export function setPolyfillMinification(minify) {
|
|
66
|
+
useMinifiedPolyfills = minify;
|
|
67
|
+
}
|
|
68
|
+
|
|
13
69
|
/**
|
|
14
70
|
* SVG 2.0 features that can be polyfilled
|
|
15
71
|
*/
|
|
@@ -27,6 +83,8 @@ export const SVG2_FEATURES = {
|
|
|
27
83
|
* @returns {{meshGradients: string[], hatches: string[], contextPaint: boolean, autoStartReverse: boolean}} Detected features
|
|
28
84
|
*/
|
|
29
85
|
export function detectSVG2Features(doc) {
|
|
86
|
+
if (!doc) return { meshGradients: [], hatches: [], contextPaint: false, autoStartReverse: false };
|
|
87
|
+
|
|
30
88
|
const features = {
|
|
31
89
|
meshGradients: [],
|
|
32
90
|
hatches: [],
|
|
@@ -83,6 +141,8 @@ export function detectSVG2Features(doc) {
|
|
|
83
141
|
* @returns {boolean} True if polyfills are needed
|
|
84
142
|
*/
|
|
85
143
|
export function needsPolyfills(doc) {
|
|
144
|
+
if (!doc) return false;
|
|
145
|
+
|
|
86
146
|
const features = detectSVG2Features(doc);
|
|
87
147
|
return features.meshGradients.length > 0 ||
|
|
88
148
|
features.hatches.length > 0 ||
|
|
@@ -92,244 +152,51 @@ export function needsPolyfills(doc) {
|
|
|
92
152
|
|
|
93
153
|
/**
|
|
94
154
|
* Generate the mesh gradient polyfill code.
|
|
155
|
+
* Uses the Inkscape mesh.js polyfill by Tavmjong Bah (GPLv3).
|
|
95
156
|
* This polyfill renders mesh gradients to canvas and uses them as image fills.
|
|
96
157
|
*
|
|
158
|
+
* Features:
|
|
159
|
+
* - Multi-patch grid support
|
|
160
|
+
* - Bezier curve edge parsing (l, L, c, C commands)
|
|
161
|
+
* - Adaptive tessellation via de Casteljau subdivision
|
|
162
|
+
* - gradientTransform support
|
|
163
|
+
* - Proper shape clipping
|
|
164
|
+
*
|
|
97
165
|
* @returns {string} JavaScript polyfill code
|
|
98
166
|
*/
|
|
99
167
|
function generateMeshPolyfillCode() {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
'use strict';
|
|
105
|
-
|
|
106
|
-
// Skip if browser supports mesh gradients natively
|
|
107
|
-
if (typeof document.createElementNS('http://www.w3.org/2000/svg', 'meshGradient').x !== 'undefined') {
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Find all mesh gradients
|
|
112
|
-
var meshes = document.querySelectorAll('meshGradient, meshgradient');
|
|
113
|
-
if (!meshes.length) return;
|
|
114
|
-
|
|
115
|
-
// Parse color string to RGBA
|
|
116
|
-
function parseColor(str) {
|
|
117
|
-
if (!str) return {r: 0, g: 0, b: 0, a: 255};
|
|
118
|
-
var m = str.match(/rgba?\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)(?:\\s*,\\s*([\\d.]+))?\\s*\\)/);
|
|
119
|
-
if (m) return {r: +m[1], g: +m[2], b: +m[3], a: m[4] ? Math.round(+m[4] * 255) : 255};
|
|
120
|
-
if (str[0] === '#') {
|
|
121
|
-
var hex = str.slice(1);
|
|
122
|
-
if (hex.length === 3) hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
|
|
123
|
-
return {r: parseInt(hex.slice(0,2), 16), g: parseInt(hex.slice(2,4), 16), b: parseInt(hex.slice(4,6), 16), a: 255};
|
|
124
|
-
}
|
|
125
|
-
return {r: 0, g: 0, b: 0, a: 255};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Bilinear color interpolation
|
|
129
|
-
function bilinearColor(c00, c10, c01, c11, u, v) {
|
|
130
|
-
var mu = 1 - u, mv = 1 - v;
|
|
131
|
-
return {
|
|
132
|
-
r: Math.round(mu*mv*c00.r + u*mv*c10.r + mu*v*c01.r + u*v*c11.r),
|
|
133
|
-
g: Math.round(mu*mv*c00.g + u*mv*c10.g + mu*v*c01.g + u*v*c11.g),
|
|
134
|
-
b: Math.round(mu*mv*c00.b + u*mv*c10.b + mu*v*c01.b + u*v*c11.b),
|
|
135
|
-
a: Math.round(mu*mv*c00.a + u*mv*c10.a + mu*v*c01.a + u*v*c11.a)
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Evaluate cubic Bezier at t
|
|
140
|
-
function evalBezier(p0, p1, p2, p3, t) {
|
|
141
|
-
var mt = 1 - t, mt2 = mt * mt, mt3 = mt2 * mt;
|
|
142
|
-
var t2 = t * t, t3 = t2 * t;
|
|
143
|
-
return {
|
|
144
|
-
x: mt3*p0.x + 3*mt2*t*p1.x + 3*mt*t2*p2.x + t3*p3.x,
|
|
145
|
-
y: mt3*p0.y + 3*mt2*t*p1.y + 3*mt*t2*p2.y + t3*p3.y
|
|
146
|
-
};
|
|
168
|
+
// Return minified or full Inkscape mesh.js polyfill based on setting
|
|
169
|
+
const polyfill = useMinifiedPolyfills ? INKSCAPE_MESH_POLYFILL_MIN : INKSCAPE_MESH_POLYFILL_FULL;
|
|
170
|
+
if (polyfill) {
|
|
171
|
+
return polyfill;
|
|
147
172
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
var id = mesh.getAttribute('id');
|
|
152
|
-
if (!id) return;
|
|
153
|
-
|
|
154
|
-
// Create canvas for rasterization
|
|
155
|
-
var canvas = document.createElement('canvas');
|
|
156
|
-
var ctx = canvas.getContext('2d');
|
|
157
|
-
|
|
158
|
-
// Get bounding box from referencing elements
|
|
159
|
-
var refs = document.querySelectorAll('[fill="url(#' + id + ')"], [stroke="url(#' + id + ')"]');
|
|
160
|
-
if (!refs.length) return;
|
|
161
|
-
|
|
162
|
-
var bbox = refs[0].getBBox();
|
|
163
|
-
var size = Math.max(bbox.width, bbox.height, 256);
|
|
164
|
-
canvas.width = canvas.height = size;
|
|
165
|
-
|
|
166
|
-
// Parse mesh patches
|
|
167
|
-
var patches = [];
|
|
168
|
-
var rows = mesh.querySelectorAll('meshrow');
|
|
169
|
-
rows.forEach(function(row) {
|
|
170
|
-
var rowPatches = row.querySelectorAll('meshpatch');
|
|
171
|
-
rowPatches.forEach(function(patch) {
|
|
172
|
-
var stops = patch.querySelectorAll('stop');
|
|
173
|
-
if (stops.length >= 4) {
|
|
174
|
-
patches.push({
|
|
175
|
-
colors: [
|
|
176
|
-
parseColor(stops[0].getAttribute('stop-color') || stops[0].style.stopColor),
|
|
177
|
-
parseColor(stops[1].getAttribute('stop-color') || stops[1].style.stopColor),
|
|
178
|
-
parseColor(stops[2].getAttribute('stop-color') || stops[2].style.stopColor),
|
|
179
|
-
parseColor(stops[3].getAttribute('stop-color') || stops[3].style.stopColor)
|
|
180
|
-
]
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
// Render patches with bilinear interpolation
|
|
187
|
-
if (patches.length > 0) {
|
|
188
|
-
var imgData = ctx.createImageData(size, size);
|
|
189
|
-
var data = imgData.data;
|
|
190
|
-
var patch = patches[0];
|
|
191
|
-
for (var y = 0; y < size; y++) {
|
|
192
|
-
for (var x = 0; x < size; x++) {
|
|
193
|
-
var u = x / (size - 1);
|
|
194
|
-
var v = y / (size - 1);
|
|
195
|
-
var c = bilinearColor(patch.colors[0], patch.colors[1], patch.colors[2], patch.colors[3], u, v);
|
|
196
|
-
var i = (y * size + x) * 4;
|
|
197
|
-
data[i] = c.r;
|
|
198
|
-
data[i+1] = c.g;
|
|
199
|
-
data[i+2] = c.b;
|
|
200
|
-
data[i+3] = c.a;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
ctx.putImageData(imgData, 0, 0);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Create pattern from canvas
|
|
207
|
-
var dataUrl = canvas.toDataURL('image/png');
|
|
208
|
-
var pattern = document.createElementNS('http://www.w3.org/2000/svg', 'pattern');
|
|
209
|
-
pattern.setAttribute('id', id + '_polyfill');
|
|
210
|
-
pattern.setAttribute('patternUnits', 'objectBoundingBox');
|
|
211
|
-
pattern.setAttribute('width', '1');
|
|
212
|
-
pattern.setAttribute('height', '1');
|
|
213
|
-
|
|
214
|
-
var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
|
|
215
|
-
img.setAttribute('href', dataUrl);
|
|
216
|
-
img.setAttribute('width', '1');
|
|
217
|
-
img.setAttribute('height', '1');
|
|
218
|
-
img.setAttribute('preserveAspectRatio', 'none');
|
|
219
|
-
pattern.appendChild(img);
|
|
220
|
-
|
|
221
|
-
// Add pattern to defs
|
|
222
|
-
var defs = mesh.closest('svg').querySelector('defs') || (function() {
|
|
223
|
-
var d = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
|
224
|
-
mesh.closest('svg').insertBefore(d, mesh.closest('svg').firstChild);
|
|
225
|
-
return d;
|
|
226
|
-
})();
|
|
227
|
-
defs.appendChild(pattern);
|
|
228
|
-
|
|
229
|
-
// Update references to use polyfill pattern
|
|
230
|
-
refs.forEach(function(ref) {
|
|
231
|
-
if (ref.getAttribute('fill') === 'url(#' + id + ')') {
|
|
232
|
-
ref.setAttribute('fill', 'url(#' + id + '_polyfill)');
|
|
233
|
-
}
|
|
234
|
-
if (ref.getAttribute('stroke') === 'url(#' + id + ')') {
|
|
235
|
-
ref.setAttribute('stroke', 'url(#' + id + '_polyfill)');
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
})();
|
|
240
|
-
`;
|
|
173
|
+
// Fallback: return empty string if polyfill couldn't be loaded
|
|
174
|
+
console.warn('svg2-polyfills: Inkscape mesh polyfill not available');
|
|
175
|
+
return '';
|
|
241
176
|
}
|
|
242
177
|
|
|
243
178
|
/**
|
|
244
179
|
* Generate the hatch pattern polyfill code.
|
|
180
|
+
* Uses the Inkscape hatch polyfill by Valentin Ionita (CC0/Public Domain).
|
|
245
181
|
* Converts SVG 2 hatch elements to SVG 1.1 pattern elements.
|
|
246
182
|
*
|
|
183
|
+
* Features:
|
|
184
|
+
* - Full SVG path command support (M, L, C, S, Q, A, etc.)
|
|
185
|
+
* - Proper coordinate system handling (objectBoundingBox, userSpaceOnUse)
|
|
186
|
+
* - Hatch rotation and transform support
|
|
187
|
+
* - Multiple hatchpath elements with offset values
|
|
188
|
+
*
|
|
247
189
|
* @returns {string} JavaScript polyfill code
|
|
248
190
|
*/
|
|
249
191
|
function generateHatchPolyfillCode() {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if (!hatches.length) return;
|
|
259
|
-
|
|
260
|
-
hatches.forEach(function(hatch) {
|
|
261
|
-
var id = hatch.getAttribute('id');
|
|
262
|
-
if (!id) return;
|
|
263
|
-
|
|
264
|
-
// Get hatch properties
|
|
265
|
-
var href = hatch.getAttribute('href') || hatch.getAttribute('xlink:href');
|
|
266
|
-
var hatchUnits = hatch.getAttribute('hatchUnits') || 'objectBoundingBox';
|
|
267
|
-
var hatchContentUnits = hatch.getAttribute('hatchContentUnits') || 'userSpaceOnUse';
|
|
268
|
-
var pitch = parseFloat(hatch.getAttribute('pitch')) || 8;
|
|
269
|
-
var rotate = parseFloat(hatch.getAttribute('rotate')) || 0;
|
|
270
|
-
var transform = hatch.getAttribute('transform') || '';
|
|
271
|
-
|
|
272
|
-
// Create equivalent pattern
|
|
273
|
-
var pattern = document.createElementNS('http://www.w3.org/2000/svg', 'pattern');
|
|
274
|
-
pattern.setAttribute('id', id + '_polyfill');
|
|
275
|
-
pattern.setAttribute('patternUnits', hatchUnits);
|
|
276
|
-
pattern.setAttribute('width', pitch);
|
|
277
|
-
pattern.setAttribute('height', pitch);
|
|
278
|
-
|
|
279
|
-
if (transform || rotate) {
|
|
280
|
-
var fullTransform = transform;
|
|
281
|
-
if (rotate) fullTransform += ' rotate(' + rotate + ')';
|
|
282
|
-
pattern.setAttribute('patternTransform', fullTransform.trim());
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Copy hatchpath children as lines
|
|
286
|
-
var hatchpaths = hatch.querySelectorAll('hatchpath, hatchPath');
|
|
287
|
-
hatchpaths.forEach(function(hp) {
|
|
288
|
-
var d = hp.getAttribute('d');
|
|
289
|
-
var strokeColor = hp.getAttribute('stroke') || 'black';
|
|
290
|
-
var strokeWidth = hp.getAttribute('stroke-width') || '1';
|
|
291
|
-
|
|
292
|
-
var line = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
293
|
-
line.setAttribute('d', d || 'M0,0 L' + pitch + ',0');
|
|
294
|
-
line.setAttribute('stroke', strokeColor);
|
|
295
|
-
line.setAttribute('stroke-width', strokeWidth);
|
|
296
|
-
line.setAttribute('fill', 'none');
|
|
297
|
-
pattern.appendChild(line);
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
// If no hatchpaths, create default diagonal line
|
|
301
|
-
if (!hatchpaths.length) {
|
|
302
|
-
var line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
303
|
-
line.setAttribute('x1', '0');
|
|
304
|
-
line.setAttribute('y1', '0');
|
|
305
|
-
line.setAttribute('x2', pitch);
|
|
306
|
-
line.setAttribute('y2', pitch);
|
|
307
|
-
line.setAttribute('stroke', 'black');
|
|
308
|
-
line.setAttribute('stroke-width', '1');
|
|
309
|
-
pattern.appendChild(line);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Add pattern to defs
|
|
313
|
-
var defs = hatch.closest('svg').querySelector('defs') || (function() {
|
|
314
|
-
var d = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
|
315
|
-
hatch.closest('svg').insertBefore(d, hatch.closest('svg').firstChild);
|
|
316
|
-
return d;
|
|
317
|
-
})();
|
|
318
|
-
defs.appendChild(pattern);
|
|
319
|
-
|
|
320
|
-
// Update references
|
|
321
|
-
var refs = document.querySelectorAll('[fill="url(#' + id + ')"], [stroke="url(#' + id + ')"]');
|
|
322
|
-
refs.forEach(function(ref) {
|
|
323
|
-
if (ref.getAttribute('fill') === 'url(#' + id + ')') {
|
|
324
|
-
ref.setAttribute('fill', 'url(#' + id + '_polyfill)');
|
|
325
|
-
}
|
|
326
|
-
if (ref.getAttribute('stroke') === 'url(#' + id + ')') {
|
|
327
|
-
ref.setAttribute('stroke', 'url(#' + id + '_polyfill)');
|
|
328
|
-
}
|
|
329
|
-
});
|
|
330
|
-
});
|
|
331
|
-
})();
|
|
332
|
-
`;
|
|
192
|
+
// Return minified or full Inkscape hatch polyfill based on setting
|
|
193
|
+
const polyfill = useMinifiedPolyfills ? INKSCAPE_HATCH_POLYFILL_MIN : INKSCAPE_HATCH_POLYFILL_FULL;
|
|
194
|
+
if (polyfill) {
|
|
195
|
+
return polyfill;
|
|
196
|
+
}
|
|
197
|
+
// Fallback: return empty string if polyfill couldn't be loaded
|
|
198
|
+
console.warn('svg2-polyfills: Inkscape hatch polyfill not available');
|
|
199
|
+
return '';
|
|
333
200
|
}
|
|
334
201
|
|
|
335
202
|
/**
|
|
@@ -364,10 +231,14 @@ export function generatePolyfillScript(features) {
|
|
|
364
231
|
* @param {Object} doc - Parsed SVG document
|
|
365
232
|
* @param {Object} [options] - Options
|
|
366
233
|
* @param {boolean} [options.force=false] - Force injection even if no features detected
|
|
234
|
+
* @param {Object} [options.features] - Pre-detected features (use instead of re-detecting)
|
|
367
235
|
* @returns {Object} The document (modified in place)
|
|
368
236
|
*/
|
|
369
237
|
export function injectPolyfills(doc, options = {}) {
|
|
370
|
-
|
|
238
|
+
if (!doc) return doc;
|
|
239
|
+
|
|
240
|
+
// Use pre-detected features if provided (for when pipeline has removed SVG2 elements)
|
|
241
|
+
const features = options.features || detectSVG2Features(doc);
|
|
371
242
|
|
|
372
243
|
// Check if polyfills are needed
|
|
373
244
|
if (!options.force &&
|
|
@@ -382,28 +253,24 @@ export function injectPolyfills(doc, options = {}) {
|
|
|
382
253
|
// Find or create the SVG root
|
|
383
254
|
const svg = doc.documentElement || doc;
|
|
384
255
|
|
|
385
|
-
// Create script
|
|
386
|
-
//
|
|
387
|
-
const scriptEl = {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
textContent: script,
|
|
392
|
-
getAttribute(name) { return this.attributes.get(name); },
|
|
393
|
-
setAttribute(name, value) { this.attributes.set(name, value); },
|
|
394
|
-
hasAttribute(name) { return this.attributes.has(name); },
|
|
395
|
-
removeAttribute(name) { this.attributes.delete(name); },
|
|
396
|
-
getAttributeNames() { return [...this.attributes.keys()]; },
|
|
397
|
-
appendChild(child) { this.children.push(child); },
|
|
398
|
-
removeChild(child) {
|
|
399
|
-
const idx = this.children.indexOf(child);
|
|
400
|
-
if (idx >= 0) this.children.splice(idx, 1);
|
|
401
|
-
}
|
|
402
|
-
};
|
|
256
|
+
// Create a proper SVGElement for the script
|
|
257
|
+
// The script content uses CDATA to avoid XML escaping issues
|
|
258
|
+
const scriptEl = new SVGElement('script', {
|
|
259
|
+
type: 'text/javascript',
|
|
260
|
+
id: 'svg-matrix-polyfill'
|
|
261
|
+
}, [], script);
|
|
403
262
|
|
|
404
|
-
// Insert script at beginning of SVG (after
|
|
263
|
+
// Insert script at beginning of SVG (after defs if present, else at start)
|
|
405
264
|
if (svg.children && svg.children.length > 0) {
|
|
406
|
-
|
|
265
|
+
// Find first non-defs element to insert before
|
|
266
|
+
let insertIdx = 0;
|
|
267
|
+
for (let i = 0; i < svg.children.length; i++) {
|
|
268
|
+
if (svg.children[i].tagName === 'defs') {
|
|
269
|
+
insertIdx = i + 1;
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
svg.children.splice(insertIdx, 0, scriptEl);
|
|
407
274
|
} else if (svg.children) {
|
|
408
275
|
svg.children.push(scriptEl);
|
|
409
276
|
}
|
|
@@ -418,6 +285,8 @@ export function injectPolyfills(doc, options = {}) {
|
|
|
418
285
|
* @returns {Object} The document (modified in place)
|
|
419
286
|
*/
|
|
420
287
|
export function removePolyfills(doc) {
|
|
288
|
+
if (!doc) return doc;
|
|
289
|
+
|
|
421
290
|
const walk = (el) => {
|
|
422
291
|
if (!el || !el.children) return;
|
|
423
292
|
|