@emasoft/svg-matrix 1.0.28 → 1.0.30
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 +985 -378
- package/bin/svglinter.cjs +4172 -433
- package/bin/svgm.js +723 -180
- 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 +18 -7
- 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 +22 -18
- 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 +16381 -3370
- package/src/svg2-polyfills.js +93 -224
- 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,13 +4,67 @@
|
|
|
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
|
|
|
13
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
|
+
}
|
|
14
68
|
|
|
15
69
|
/**
|
|
16
70
|
* SVG 2.0 features that can be polyfilled
|
|
@@ -29,6 +83,8 @@ export const SVG2_FEATURES = {
|
|
|
29
83
|
* @returns {{meshGradients: string[], hatches: string[], contextPaint: boolean, autoStartReverse: boolean}} Detected features
|
|
30
84
|
*/
|
|
31
85
|
export function detectSVG2Features(doc) {
|
|
86
|
+
if (!doc) return { meshGradients: [], hatches: [], contextPaint: false, autoStartReverse: false };
|
|
87
|
+
|
|
32
88
|
const features = {
|
|
33
89
|
meshGradients: [],
|
|
34
90
|
hatches: [],
|
|
@@ -85,6 +141,8 @@ export function detectSVG2Features(doc) {
|
|
|
85
141
|
* @returns {boolean} True if polyfills are needed
|
|
86
142
|
*/
|
|
87
143
|
export function needsPolyfills(doc) {
|
|
144
|
+
if (!doc) return false;
|
|
145
|
+
|
|
88
146
|
const features = detectSVG2Features(doc);
|
|
89
147
|
return features.meshGradients.length > 0 ||
|
|
90
148
|
features.hatches.length > 0 ||
|
|
@@ -94,244 +152,51 @@ export function needsPolyfills(doc) {
|
|
|
94
152
|
|
|
95
153
|
/**
|
|
96
154
|
* Generate the mesh gradient polyfill code.
|
|
155
|
+
* Uses the Inkscape mesh.js polyfill by Tavmjong Bah (GPLv3).
|
|
97
156
|
* This polyfill renders mesh gradients to canvas and uses them as image fills.
|
|
98
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
|
+
*
|
|
99
165
|
* @returns {string} JavaScript polyfill code
|
|
100
166
|
*/
|
|
101
167
|
function generateMeshPolyfillCode() {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
'use strict';
|
|
107
|
-
|
|
108
|
-
// Skip if browser supports mesh gradients natively
|
|
109
|
-
if (typeof document.createElementNS('http://www.w3.org/2000/svg', 'meshGradient').x !== 'undefined') {
|
|
110
|
-
return;
|
|
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;
|
|
111
172
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (!meshes.length) return;
|
|
116
|
-
|
|
117
|
-
// Parse color string to RGBA
|
|
118
|
-
function parseColor(str) {
|
|
119
|
-
if (!str) return {r: 0, g: 0, b: 0, a: 255};
|
|
120
|
-
var m = str.match(/rgba?\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)(?:\\s*,\\s*([\\d.]+))?\\s*\\)/);
|
|
121
|
-
if (m) return {r: +m[1], g: +m[2], b: +m[3], a: m[4] ? Math.round(+m[4] * 255) : 255};
|
|
122
|
-
if (str[0] === '#') {
|
|
123
|
-
var hex = str.slice(1);
|
|
124
|
-
if (hex.length === 3) hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
|
|
125
|
-
return {r: parseInt(hex.slice(0,2), 16), g: parseInt(hex.slice(2,4), 16), b: parseInt(hex.slice(4,6), 16), a: 255};
|
|
126
|
-
}
|
|
127
|
-
return {r: 0, g: 0, b: 0, a: 255};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Bilinear color interpolation
|
|
131
|
-
function bilinearColor(c00, c10, c01, c11, u, v) {
|
|
132
|
-
var mu = 1 - u, mv = 1 - v;
|
|
133
|
-
return {
|
|
134
|
-
r: Math.round(mu*mv*c00.r + u*mv*c10.r + mu*v*c01.r + u*v*c11.r),
|
|
135
|
-
g: Math.round(mu*mv*c00.g + u*mv*c10.g + mu*v*c01.g + u*v*c11.g),
|
|
136
|
-
b: Math.round(mu*mv*c00.b + u*mv*c10.b + mu*v*c01.b + u*v*c11.b),
|
|
137
|
-
a: Math.round(mu*mv*c00.a + u*mv*c10.a + mu*v*c01.a + u*v*c11.a)
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Evaluate cubic Bezier at t
|
|
142
|
-
function evalBezier(p0, p1, p2, p3, t) {
|
|
143
|
-
var mt = 1 - t, mt2 = mt * mt, mt3 = mt2 * mt;
|
|
144
|
-
var t2 = t * t, t3 = t2 * t;
|
|
145
|
-
return {
|
|
146
|
-
x: mt3*p0.x + 3*mt2*t*p1.x + 3*mt*t2*p2.x + t3*p3.x,
|
|
147
|
-
y: mt3*p0.y + 3*mt2*t*p1.y + 3*mt*t2*p2.y + t3*p3.y
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Process each mesh gradient
|
|
152
|
-
meshes.forEach(function(mesh) {
|
|
153
|
-
var id = mesh.getAttribute('id');
|
|
154
|
-
if (!id) return;
|
|
155
|
-
|
|
156
|
-
// Create canvas for rasterization
|
|
157
|
-
var canvas = document.createElement('canvas');
|
|
158
|
-
var ctx = canvas.getContext('2d');
|
|
159
|
-
|
|
160
|
-
// Get bounding box from referencing elements
|
|
161
|
-
var refs = document.querySelectorAll('[fill="url(#' + id + ')"], [stroke="url(#' + id + ')"]');
|
|
162
|
-
if (!refs.length) return;
|
|
163
|
-
|
|
164
|
-
var bbox = refs[0].getBBox();
|
|
165
|
-
var size = Math.max(bbox.width, bbox.height, 256);
|
|
166
|
-
canvas.width = canvas.height = size;
|
|
167
|
-
|
|
168
|
-
// Parse mesh patches
|
|
169
|
-
var patches = [];
|
|
170
|
-
var rows = mesh.querySelectorAll('meshrow');
|
|
171
|
-
rows.forEach(function(row) {
|
|
172
|
-
var rowPatches = row.querySelectorAll('meshpatch');
|
|
173
|
-
rowPatches.forEach(function(patch) {
|
|
174
|
-
var stops = patch.querySelectorAll('stop');
|
|
175
|
-
if (stops.length >= 4) {
|
|
176
|
-
patches.push({
|
|
177
|
-
colors: [
|
|
178
|
-
parseColor(stops[0].getAttribute('stop-color') || stops[0].style.stopColor),
|
|
179
|
-
parseColor(stops[1].getAttribute('stop-color') || stops[1].style.stopColor),
|
|
180
|
-
parseColor(stops[2].getAttribute('stop-color') || stops[2].style.stopColor),
|
|
181
|
-
parseColor(stops[3].getAttribute('stop-color') || stops[3].style.stopColor)
|
|
182
|
-
]
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
// Render patches with bilinear interpolation
|
|
189
|
-
if (patches.length > 0) {
|
|
190
|
-
var imgData = ctx.createImageData(size, size);
|
|
191
|
-
var data = imgData.data;
|
|
192
|
-
var patch = patches[0];
|
|
193
|
-
for (var y = 0; y < size; y++) {
|
|
194
|
-
for (var x = 0; x < size; x++) {
|
|
195
|
-
var u = x / (size - 1);
|
|
196
|
-
var v = y / (size - 1);
|
|
197
|
-
var c = bilinearColor(patch.colors[0], patch.colors[1], patch.colors[2], patch.colors[3], u, v);
|
|
198
|
-
var i = (y * size + x) * 4;
|
|
199
|
-
data[i] = c.r;
|
|
200
|
-
data[i+1] = c.g;
|
|
201
|
-
data[i+2] = c.b;
|
|
202
|
-
data[i+3] = c.a;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
ctx.putImageData(imgData, 0, 0);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Create pattern from canvas
|
|
209
|
-
var dataUrl = canvas.toDataURL('image/png');
|
|
210
|
-
var pattern = document.createElementNS('http://www.w3.org/2000/svg', 'pattern');
|
|
211
|
-
pattern.setAttribute('id', id + '_polyfill');
|
|
212
|
-
pattern.setAttribute('patternUnits', 'objectBoundingBox');
|
|
213
|
-
pattern.setAttribute('width', '1');
|
|
214
|
-
pattern.setAttribute('height', '1');
|
|
215
|
-
|
|
216
|
-
var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
|
|
217
|
-
img.setAttribute('href', dataUrl);
|
|
218
|
-
img.setAttribute('width', '1');
|
|
219
|
-
img.setAttribute('height', '1');
|
|
220
|
-
img.setAttribute('preserveAspectRatio', 'none');
|
|
221
|
-
pattern.appendChild(img);
|
|
222
|
-
|
|
223
|
-
// Add pattern to defs
|
|
224
|
-
var defs = mesh.closest('svg').querySelector('defs') || (function() {
|
|
225
|
-
var d = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
|
226
|
-
mesh.closest('svg').insertBefore(d, mesh.closest('svg').firstChild);
|
|
227
|
-
return d;
|
|
228
|
-
})();
|
|
229
|
-
defs.appendChild(pattern);
|
|
230
|
-
|
|
231
|
-
// Update references to use polyfill pattern
|
|
232
|
-
refs.forEach(function(ref) {
|
|
233
|
-
if (ref.getAttribute('fill') === 'url(#' + id + ')') {
|
|
234
|
-
ref.setAttribute('fill', 'url(#' + id + '_polyfill)');
|
|
235
|
-
}
|
|
236
|
-
if (ref.getAttribute('stroke') === 'url(#' + id + ')') {
|
|
237
|
-
ref.setAttribute('stroke', 'url(#' + id + '_polyfill)');
|
|
238
|
-
}
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
})();
|
|
242
|
-
`;
|
|
173
|
+
// Fallback: return empty string if polyfill couldn't be loaded
|
|
174
|
+
console.warn('svg2-polyfills: Inkscape mesh polyfill not available');
|
|
175
|
+
return '';
|
|
243
176
|
}
|
|
244
177
|
|
|
245
178
|
/**
|
|
246
179
|
* Generate the hatch pattern polyfill code.
|
|
180
|
+
* Uses the Inkscape hatch polyfill by Valentin Ionita (CC0/Public Domain).
|
|
247
181
|
* Converts SVG 2 hatch elements to SVG 1.1 pattern elements.
|
|
248
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
|
+
*
|
|
249
189
|
* @returns {string} JavaScript polyfill code
|
|
250
190
|
*/
|
|
251
191
|
function generateHatchPolyfillCode() {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (!hatches.length) return;
|
|
261
|
-
|
|
262
|
-
hatches.forEach(function(hatch) {
|
|
263
|
-
var id = hatch.getAttribute('id');
|
|
264
|
-
if (!id) return;
|
|
265
|
-
|
|
266
|
-
// Get hatch properties
|
|
267
|
-
var href = hatch.getAttribute('href') || hatch.getAttribute('xlink:href');
|
|
268
|
-
var hatchUnits = hatch.getAttribute('hatchUnits') || 'objectBoundingBox';
|
|
269
|
-
var hatchContentUnits = hatch.getAttribute('hatchContentUnits') || 'userSpaceOnUse';
|
|
270
|
-
var pitch = parseFloat(hatch.getAttribute('pitch')) || 8;
|
|
271
|
-
var rotate = parseFloat(hatch.getAttribute('rotate')) || 0;
|
|
272
|
-
var transform = hatch.getAttribute('transform') || '';
|
|
273
|
-
|
|
274
|
-
// Create equivalent pattern
|
|
275
|
-
var pattern = document.createElementNS('http://www.w3.org/2000/svg', 'pattern');
|
|
276
|
-
pattern.setAttribute('id', id + '_polyfill');
|
|
277
|
-
pattern.setAttribute('patternUnits', hatchUnits);
|
|
278
|
-
pattern.setAttribute('width', pitch);
|
|
279
|
-
pattern.setAttribute('height', pitch);
|
|
280
|
-
|
|
281
|
-
if (transform || rotate) {
|
|
282
|
-
var fullTransform = transform;
|
|
283
|
-
if (rotate) fullTransform += ' rotate(' + rotate + ')';
|
|
284
|
-
pattern.setAttribute('patternTransform', fullTransform.trim());
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Copy hatchpath children as lines
|
|
288
|
-
var hatchpaths = hatch.querySelectorAll('hatchpath, hatchPath');
|
|
289
|
-
hatchpaths.forEach(function(hp) {
|
|
290
|
-
var d = hp.getAttribute('d');
|
|
291
|
-
var strokeColor = hp.getAttribute('stroke') || 'black';
|
|
292
|
-
var strokeWidth = hp.getAttribute('stroke-width') || '1';
|
|
293
|
-
|
|
294
|
-
var line = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
295
|
-
line.setAttribute('d', d || 'M0,0 L' + pitch + ',0');
|
|
296
|
-
line.setAttribute('stroke', strokeColor);
|
|
297
|
-
line.setAttribute('stroke-width', strokeWidth);
|
|
298
|
-
line.setAttribute('fill', 'none');
|
|
299
|
-
pattern.appendChild(line);
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
// If no hatchpaths, create default diagonal line
|
|
303
|
-
if (!hatchpaths.length) {
|
|
304
|
-
var line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
305
|
-
line.setAttribute('x1', '0');
|
|
306
|
-
line.setAttribute('y1', '0');
|
|
307
|
-
line.setAttribute('x2', pitch);
|
|
308
|
-
line.setAttribute('y2', pitch);
|
|
309
|
-
line.setAttribute('stroke', 'black');
|
|
310
|
-
line.setAttribute('stroke-width', '1');
|
|
311
|
-
pattern.appendChild(line);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Add pattern to defs
|
|
315
|
-
var defs = hatch.closest('svg').querySelector('defs') || (function() {
|
|
316
|
-
var d = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
|
317
|
-
hatch.closest('svg').insertBefore(d, hatch.closest('svg').firstChild);
|
|
318
|
-
return d;
|
|
319
|
-
})();
|
|
320
|
-
defs.appendChild(pattern);
|
|
321
|
-
|
|
322
|
-
// Update references
|
|
323
|
-
var refs = document.querySelectorAll('[fill="url(#' + id + ')"], [stroke="url(#' + id + ')"]');
|
|
324
|
-
refs.forEach(function(ref) {
|
|
325
|
-
if (ref.getAttribute('fill') === 'url(#' + id + ')') {
|
|
326
|
-
ref.setAttribute('fill', 'url(#' + id + '_polyfill)');
|
|
327
|
-
}
|
|
328
|
-
if (ref.getAttribute('stroke') === 'url(#' + id + ')') {
|
|
329
|
-
ref.setAttribute('stroke', 'url(#' + id + '_polyfill)');
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
});
|
|
333
|
-
})();
|
|
334
|
-
`;
|
|
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 '';
|
|
335
200
|
}
|
|
336
201
|
|
|
337
202
|
/**
|
|
@@ -370,6 +235,8 @@ export function generatePolyfillScript(features) {
|
|
|
370
235
|
* @returns {Object} The document (modified in place)
|
|
371
236
|
*/
|
|
372
237
|
export function injectPolyfills(doc, options = {}) {
|
|
238
|
+
if (!doc) return doc;
|
|
239
|
+
|
|
373
240
|
// Use pre-detected features if provided (for when pipeline has removed SVG2 elements)
|
|
374
241
|
const features = options.features || detectSVG2Features(doc);
|
|
375
242
|
|
|
@@ -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
|
|
|
@@ -31,20 +31,20 @@
|
|
|
31
31
|
* @module transform-decomposition
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
|
-
import Decimal from
|
|
35
|
-
import { Matrix } from
|
|
34
|
+
import Decimal from "decimal.js";
|
|
35
|
+
import { Matrix } from "./matrix.js";
|
|
36
36
|
|
|
37
37
|
// Set high precision for all calculations
|
|
38
38
|
Decimal.set({ precision: 80 });
|
|
39
39
|
|
|
40
40
|
// Helper to convert to Decimal
|
|
41
|
-
const D = x => (x instanceof Decimal ? x : new Decimal(x));
|
|
41
|
+
const D = (x) => (x instanceof Decimal ? x : new Decimal(x));
|
|
42
42
|
|
|
43
43
|
// Near-zero threshold for comparisons
|
|
44
|
-
const EPSILON = new Decimal(
|
|
44
|
+
const EPSILON = new Decimal("1e-40");
|
|
45
45
|
|
|
46
46
|
// Verification tolerance (larger than EPSILON for practical use)
|
|
47
|
-
const VERIFICATION_TOLERANCE = new Decimal(
|
|
47
|
+
const VERIFICATION_TOLERANCE = new Decimal("1e-30");
|
|
48
48
|
|
|
49
49
|
// ============================================================================
|
|
50
50
|
// Matrix Utilities
|
|
@@ -68,7 +68,7 @@ export function translationMatrix(tx, ty) {
|
|
|
68
68
|
return Matrix.from([
|
|
69
69
|
[1, 0, D(tx)],
|
|
70
70
|
[0, 1, D(ty)],
|
|
71
|
-
[0, 0, 1]
|
|
71
|
+
[0, 0, 1],
|
|
72
72
|
]);
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -84,7 +84,7 @@ export function rotationMatrix(angle) {
|
|
|
84
84
|
return Matrix.from([
|
|
85
85
|
[cos, sin.neg(), 0],
|
|
86
86
|
[sin, cos, 0],
|
|
87
|
-
[0, 0, 1]
|
|
87
|
+
[0, 0, 1],
|
|
88
88
|
]);
|
|
89
89
|
}
|
|
90
90
|
|
|
@@ -98,7 +98,7 @@ export function scaleMatrix(sx, sy) {
|
|
|
98
98
|
return Matrix.from([
|
|
99
99
|
[D(sx), 0, 0],
|
|
100
100
|
[0, D(sy), 0],
|
|
101
|
-
[0, 0, 1]
|
|
101
|
+
[0, 0, 1],
|
|
102
102
|
]);
|
|
103
103
|
}
|
|
104
104
|
|
|
@@ -112,7 +112,7 @@ export function skewXMatrix(angle) {
|
|
|
112
112
|
return Matrix.from([
|
|
113
113
|
[1, tan, 0],
|
|
114
114
|
[0, 1, 0],
|
|
115
|
-
[0, 0, 1]
|
|
115
|
+
[0, 0, 1],
|
|
116
116
|
]);
|
|
117
117
|
}
|
|
118
118
|
|
|
@@ -126,7 +126,7 @@ export function skewYMatrix(angle) {
|
|
|
126
126
|
return Matrix.from([
|
|
127
127
|
[1, 0, 0],
|
|
128
128
|
[tan, 1, 0],
|
|
129
|
-
[0, 0, 1]
|
|
129
|
+
[0, 0, 1],
|
|
130
130
|
]);
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -141,7 +141,7 @@ export function extractLinearPart(matrix) {
|
|
|
141
141
|
a: data[0][0],
|
|
142
142
|
b: data[1][0],
|
|
143
143
|
c: data[0][1],
|
|
144
|
-
d: data[1][1]
|
|
144
|
+
d: data[1][1],
|
|
145
145
|
};
|
|
146
146
|
}
|
|
147
147
|
|
|
@@ -154,7 +154,7 @@ export function extractTranslation(matrix) {
|
|
|
154
154
|
const data = matrix.data;
|
|
155
155
|
return {
|
|
156
156
|
tx: data[0][2],
|
|
157
|
-
ty: data[1][2]
|
|
157
|
+
ty: data[1][2],
|
|
158
158
|
};
|
|
159
159
|
}
|
|
160
160
|
|
|
@@ -209,12 +209,12 @@ export function decomposeMatrix(matrix) {
|
|
|
209
209
|
skewX: D(0),
|
|
210
210
|
skewY: D(0),
|
|
211
211
|
verified: false,
|
|
212
|
-
verificationError: D(
|
|
213
|
-
singular: true
|
|
212
|
+
verificationError: D("Infinity"),
|
|
213
|
+
singular: true, // Flag to indicate singular matrix
|
|
214
214
|
};
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
|
|
217
|
+
const scaleY = det.div(scaleX);
|
|
218
218
|
|
|
219
219
|
// Handle reflection (negative determinant)
|
|
220
220
|
// We put the reflection in scaleX
|
|
@@ -250,9 +250,9 @@ export function decomposeMatrix(matrix) {
|
|
|
250
250
|
|
|
251
251
|
// Rotate back to get the scale+skew matrix
|
|
252
252
|
const aPrime = a.mul(cosTheta).plus(b.mul(sinTheta));
|
|
253
|
-
const
|
|
253
|
+
const _bPrime = a.neg().mul(sinTheta).plus(b.mul(cosTheta));
|
|
254
254
|
const cPrime = c.mul(cosTheta).plus(d.mul(sinTheta));
|
|
255
|
-
const
|
|
255
|
+
const _dPrime = c.neg().mul(sinTheta).plus(d.mul(cosTheta));
|
|
256
256
|
|
|
257
257
|
// Now [aPrime cPrime] should be [scaleX, scaleX*tan(skewX)]
|
|
258
258
|
// [bPrime dPrime] [0, scaleY ]
|
|
@@ -291,7 +291,7 @@ export function decomposeMatrix(matrix) {
|
|
|
291
291
|
scaleX,
|
|
292
292
|
scaleY,
|
|
293
293
|
skewX,
|
|
294
|
-
skewY: D(0)
|
|
294
|
+
skewY: D(0),
|
|
295
295
|
});
|
|
296
296
|
|
|
297
297
|
const maxError = matrixMaxDifference(matrix, recomposed);
|
|
@@ -306,7 +306,7 @@ export function decomposeMatrix(matrix) {
|
|
|
306
306
|
skewX,
|
|
307
307
|
skewY,
|
|
308
308
|
verified,
|
|
309
|
-
maxError
|
|
309
|
+
maxError,
|
|
310
310
|
};
|
|
311
311
|
}
|
|
312
312
|
|
|
@@ -352,7 +352,7 @@ export function decomposeMatrixNoSkew(matrix) {
|
|
|
352
352
|
translateY: ty,
|
|
353
353
|
rotation,
|
|
354
354
|
scaleX,
|
|
355
|
-
scaleY
|
|
355
|
+
scaleY,
|
|
356
356
|
});
|
|
357
357
|
|
|
358
358
|
const maxError = matrixMaxDifference(matrix, recomposed);
|
|
@@ -365,7 +365,7 @@ export function decomposeMatrixNoSkew(matrix) {
|
|
|
365
365
|
scaleX,
|
|
366
366
|
scaleY,
|
|
367
367
|
verified,
|
|
368
|
-
maxError
|
|
368
|
+
maxError,
|
|
369
369
|
};
|
|
370
370
|
}
|
|
371
371
|
|
|
@@ -434,7 +434,7 @@ export function composeTransform(components) {
|
|
|
434
434
|
scaleX,
|
|
435
435
|
scaleY,
|
|
436
436
|
skewX,
|
|
437
|
-
skewY = 0
|
|
437
|
+
skewY = 0,
|
|
438
438
|
} = components;
|
|
439
439
|
|
|
440
440
|
// Build matrices
|
|
@@ -521,7 +521,7 @@ export function verifyDecomposition(original, decomposition) {
|
|
|
521
521
|
const maxError = matrixMaxDifference(original, recomposed);
|
|
522
522
|
return {
|
|
523
523
|
verified: maxError.lessThan(VERIFICATION_TOLERANCE),
|
|
524
|
-
maxError
|
|
524
|
+
maxError,
|
|
525
525
|
};
|
|
526
526
|
}
|
|
527
527
|
|
|
@@ -549,7 +549,7 @@ export function matrixFromSVGValues(a, b, c, d, e, f) {
|
|
|
549
549
|
return Matrix.from([
|
|
550
550
|
[D(a), D(c), D(e)],
|
|
551
551
|
[D(b), D(d), D(f)],
|
|
552
|
-
[0, 0, 1]
|
|
552
|
+
[0, 0, 1],
|
|
553
553
|
]);
|
|
554
554
|
}
|
|
555
555
|
|
|
@@ -568,7 +568,7 @@ export function matrixToSVGValues(matrix, precision = 6) {
|
|
|
568
568
|
c: data[0][1].toFixed(precision),
|
|
569
569
|
d: data[1][1].toFixed(precision),
|
|
570
570
|
e: data[0][2].toFixed(precision),
|
|
571
|
-
f: data[1][2].toFixed(precision)
|
|
571
|
+
f: data[1][2].toFixed(precision),
|
|
572
572
|
};
|
|
573
573
|
}
|
|
574
574
|
|
|
@@ -580,13 +580,19 @@ export function matrixToSVGValues(matrix, precision = 6) {
|
|
|
580
580
|
* @returns {string} SVG transform string
|
|
581
581
|
*/
|
|
582
582
|
export function decompositionToSVGString(decomposition, precision = 6) {
|
|
583
|
-
const { translateX, translateY, rotation, scaleX, scaleY, skewX, skewY } =
|
|
583
|
+
const { translateX, translateY, rotation, scaleX, scaleY, skewX, skewY } =
|
|
584
|
+
decomposition;
|
|
584
585
|
|
|
585
586
|
const parts = [];
|
|
586
587
|
|
|
587
588
|
// Add translate if non-zero
|
|
588
|
-
if (
|
|
589
|
-
|
|
589
|
+
if (
|
|
590
|
+
!D(translateX).abs().lessThan(EPSILON) ||
|
|
591
|
+
!D(translateY).abs().lessThan(EPSILON)
|
|
592
|
+
) {
|
|
593
|
+
parts.push(
|
|
594
|
+
`translate(${D(translateX).toFixed(precision)}, ${D(translateY).toFixed(precision)})`,
|
|
595
|
+
);
|
|
590
596
|
}
|
|
591
597
|
|
|
592
598
|
// Add rotate if non-zero
|
|
@@ -616,11 +622,13 @@ export function decompositionToSVGString(decomposition, precision = 6) {
|
|
|
616
622
|
if (D(scaleX).minus(D(scaleY)).abs().lessThan(EPSILON)) {
|
|
617
623
|
parts.push(`scale(${D(scaleX).toFixed(precision)})`);
|
|
618
624
|
} else {
|
|
619
|
-
parts.push(
|
|
625
|
+
parts.push(
|
|
626
|
+
`scale(${D(scaleX).toFixed(precision)}, ${D(scaleY).toFixed(precision)})`,
|
|
627
|
+
);
|
|
620
628
|
}
|
|
621
629
|
}
|
|
622
630
|
|
|
623
|
-
return parts.length > 0 ? parts.join(
|
|
631
|
+
return parts.length > 0 ? parts.join(" ") : "";
|
|
624
632
|
}
|
|
625
633
|
|
|
626
634
|
/**
|
|
@@ -636,7 +644,7 @@ export function matrixToMinimalSVGTransform(matrix, precision = 6) {
|
|
|
636
644
|
// Check if identity
|
|
637
645
|
const identity = Matrix.identity(3);
|
|
638
646
|
if (matricesEqual(matrix, identity, EPSILON)) {
|
|
639
|
-
return { transform:
|
|
647
|
+
return { transform: "", isIdentity: true, verified: true };
|
|
640
648
|
}
|
|
641
649
|
|
|
642
650
|
// Decompose
|
|
@@ -648,7 +656,7 @@ export function matrixToMinimalSVGTransform(matrix, precision = 6) {
|
|
|
648
656
|
return {
|
|
649
657
|
transform,
|
|
650
658
|
isIdentity: false,
|
|
651
|
-
verified: decomposition.verified
|
|
659
|
+
verified: decomposition.verified,
|
|
652
660
|
};
|
|
653
661
|
}
|
|
654
662
|
|
|
@@ -675,7 +683,7 @@ export function isPureTranslation(matrix) {
|
|
|
675
683
|
return {
|
|
676
684
|
isTranslation: isIdentityLinear,
|
|
677
685
|
tx,
|
|
678
|
-
ty
|
|
686
|
+
ty,
|
|
679
687
|
};
|
|
680
688
|
}
|
|
681
689
|
|
|
@@ -700,7 +708,8 @@ export function isPureRotation(matrix) {
|
|
|
700
708
|
// Check unit columns: a² + b² = 1, c² + d² = 1
|
|
701
709
|
const col1Norm = a.mul(a).plus(b.mul(b));
|
|
702
710
|
const col2Norm = c.mul(c).plus(d.mul(d));
|
|
703
|
-
const unitNorm =
|
|
711
|
+
const unitNorm =
|
|
712
|
+
col1Norm.minus(1).abs().lessThan(EPSILON) &&
|
|
704
713
|
col2Norm.minus(1).abs().lessThan(EPSILON);
|
|
705
714
|
|
|
706
715
|
// Check determinant = 1 (no reflection)
|
|
@@ -741,7 +750,7 @@ export function isPureScale(matrix) {
|
|
|
741
750
|
isScale: true,
|
|
742
751
|
scaleX: a,
|
|
743
752
|
scaleY: d,
|
|
744
|
-
isUniform
|
|
753
|
+
isUniform,
|
|
745
754
|
};
|
|
746
755
|
}
|
|
747
756
|
|
|
@@ -760,11 +769,7 @@ export function isIdentityMatrix(matrix) {
|
|
|
760
769
|
// Exports
|
|
761
770
|
// ============================================================================
|
|
762
771
|
|
|
763
|
-
export {
|
|
764
|
-
EPSILON,
|
|
765
|
-
VERIFICATION_TOLERANCE,
|
|
766
|
-
D
|
|
767
|
-
};
|
|
772
|
+
export { EPSILON, VERIFICATION_TOLERANCE, D };
|
|
768
773
|
|
|
769
774
|
export default {
|
|
770
775
|
// Matrix utilities
|
|
@@ -806,5 +811,5 @@ export default {
|
|
|
806
811
|
|
|
807
812
|
// Constants
|
|
808
813
|
EPSILON,
|
|
809
|
-
VERIFICATION_TOLERANCE
|
|
814
|
+
VERIFICATION_TOLERANCE,
|
|
810
815
|
};
|