@emasoft/svg-matrix 1.0.26 → 1.0.28
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 +57 -0
- package/bin/svg-matrix.js +28 -3
- package/bin/svgm.js +46 -3
- package/package.json +1 -1
- package/src/index.js +7 -2
- package/src/inkscape-support.js +641 -0
- package/src/svg-collections.js +26 -1
- package/src/svg-toolbox.js +155 -41
- package/src/svg2-polyfills.js +444 -0
package/README.md
CHANGED
|
@@ -255,6 +255,36 @@ svgm --show-plugins
|
|
|
255
255
|
|
|
256
256
|
Run `svgm --help` for all options.
|
|
257
257
|
|
|
258
|
+
### Namespace Preservation
|
|
259
|
+
|
|
260
|
+
Preserve vendor-specific namespaces during optimization:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
# Preserve Inkscape/Sodipodi namespaces (layers, guides, document settings)
|
|
264
|
+
svgm --preserve-ns inkscape input.svg -o output.svg
|
|
265
|
+
|
|
266
|
+
# Preserve multiple vendor namespaces
|
|
267
|
+
svgm --preserve-ns inkscape,illustrator input.svg -o output.svg
|
|
268
|
+
|
|
269
|
+
# Available namespaces: inkscape, sodipodi, illustrator, sketch, ai, serif, vectornator, figma
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### SVG 2.0 Polyfills
|
|
273
|
+
|
|
274
|
+
Enable browser compatibility for SVG 2.0 features:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
# Add polyfills for mesh gradients and hatches
|
|
278
|
+
svgm --svg2-polyfills input.svg -o output.svg
|
|
279
|
+
|
|
280
|
+
# Combine with namespace preservation
|
|
281
|
+
svgm --preserve-ns inkscape --svg2-polyfills input.svg -o output.svg
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Supported SVG 2.0 features:
|
|
285
|
+
- **Mesh gradients** (`<meshGradient>`) - Rendered via canvas fallback
|
|
286
|
+
- **Hatches** (`<hatch>`) - Converted to SVG 1.1 patterns
|
|
287
|
+
|
|
258
288
|
---
|
|
259
289
|
|
|
260
290
|
### `svg-matrix` - Advanced SVG Processing
|
|
@@ -360,6 +390,33 @@ const fixed = await fixInvalidSvg('broken.svg');
|
|
|
360
390
|
console.log(fixed.svg); // Fixed SVG string
|
|
361
391
|
```
|
|
362
392
|
|
|
393
|
+
### Inkscape Support Module
|
|
394
|
+
|
|
395
|
+
```javascript
|
|
396
|
+
import { InkscapeSupport } from '@emasoft/svg-matrix';
|
|
397
|
+
|
|
398
|
+
// Check if element is an Inkscape layer
|
|
399
|
+
InkscapeSupport.isInkscapeLayer(element);
|
|
400
|
+
|
|
401
|
+
// Find all layers in document
|
|
402
|
+
const layers = InkscapeSupport.findLayers(doc);
|
|
403
|
+
|
|
404
|
+
// Get document settings from sodipodi:namedview
|
|
405
|
+
const settings = InkscapeSupport.getNamedViewSettings(doc);
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### SVG 2.0 Polyfills Module
|
|
409
|
+
|
|
410
|
+
```javascript
|
|
411
|
+
import { SVG2Polyfills } from '@emasoft/svg-matrix';
|
|
412
|
+
|
|
413
|
+
// Detect SVG 2.0 features in document
|
|
414
|
+
const features = SVG2Polyfills.detectSVG2Features(doc);
|
|
415
|
+
|
|
416
|
+
// Inject polyfills into document
|
|
417
|
+
SVG2Polyfills.injectPolyfills(doc);
|
|
418
|
+
```
|
|
419
|
+
|
|
363
420
|
### Exclusive Features (Not in SVGO)
|
|
364
421
|
|
|
365
422
|
| Function | Description |
|
package/bin/svg-matrix.js
CHANGED
|
@@ -91,6 +91,7 @@ let currentOutputFile = null; // Track for cleanup on interrupt
|
|
|
91
91
|
* @property {boolean} resolveMarkers - Expand marker references
|
|
92
92
|
* @property {boolean} resolvePatterns - Expand pattern fills
|
|
93
93
|
* @property {boolean} bakeGradients - Bake gradientTransform into coordinates
|
|
94
|
+
* @property {string[]} preserveNamespaces - Array of namespace prefixes to preserve
|
|
94
95
|
*/
|
|
95
96
|
|
|
96
97
|
const DEFAULT_CONFIG = {
|
|
@@ -120,6 +121,7 @@ const DEFAULT_CONFIG = {
|
|
|
120
121
|
bezierArcs: 8, // Bezier arcs for circles/ellipses (multiple of 4; 8=π/4 optimal)
|
|
121
122
|
e2eTolerance: '1e-10', // E2E verification tolerance (tighter with more segments)
|
|
122
123
|
preserveVendor: false, // If true, preserve vendor-prefixed properties and editor namespaces
|
|
124
|
+
preserveNamespaces: [], // Array of namespace prefixes to preserve (e.g., ['inkscape', 'sodipodi'])
|
|
123
125
|
};
|
|
124
126
|
|
|
125
127
|
/** @type {CLIConfig} */
|
|
@@ -603,6 +605,8 @@ ${boxLine(` ${colors.dim}--no-patterns${colors.reset} Skip pattern ex
|
|
|
603
605
|
${boxLine(` ${colors.dim}--no-gradients${colors.reset} Skip gradient transform baking`, W)}
|
|
604
606
|
${boxLine(` ${colors.dim}--preserve-vendor${colors.reset} Keep vendor prefixes and editor namespaces`, W)}
|
|
605
607
|
${boxLine(` ${colors.dim}(inkscape, sodipodi, -webkit-*, etc.)${colors.reset}`, W)}
|
|
608
|
+
${boxLine(` ${colors.dim}--preserve-ns <list>${colors.reset} Preserve specific namespaces (comma-separated)`, W)}
|
|
609
|
+
${boxLine(` ${colors.dim}Example: --preserve-ns inkscape,sodipodi${colors.reset}`, W)}
|
|
606
610
|
${boxLine('', W)}
|
|
607
611
|
${boxDivider(W)}
|
|
608
612
|
${boxLine('', W)}
|
|
@@ -628,6 +632,7 @@ ${boxLine('', W)}
|
|
|
628
632
|
${boxLine(` ${colors.green}svg-matrix flatten${colors.reset} input.svg -o output.svg`, W)}
|
|
629
633
|
${boxLine(` ${colors.green}svg-matrix flatten${colors.reset} ./svgs/ -o ./out/ --transform-only`, W)}
|
|
630
634
|
${boxLine(` ${colors.green}svg-matrix flatten${colors.reset} --list files.txt -o ./out/ --no-patterns`, W)}
|
|
635
|
+
${boxLine(` ${colors.green}svg-matrix flatten${colors.reset} input.svg -o out.svg --preserve-ns inkscape,sodipodi`, W)}
|
|
631
636
|
${boxLine(` ${colors.green}svg-matrix convert${colors.reset} input.svg -o output.svg -p 10`, W)}
|
|
632
637
|
${boxLine(` ${colors.green}svg-matrix info${colors.reset} input.svg`, W)}
|
|
633
638
|
${boxLine('', W)}
|
|
@@ -867,6 +872,7 @@ function processFlatten(inputPath, outputPath) {
|
|
|
867
872
|
flattenTransforms: true, // Always flatten transforms
|
|
868
873
|
bakeGradients: config.bakeGradients,
|
|
869
874
|
removeUnusedDefs: true,
|
|
875
|
+
preserveNamespaces: config.preserveNamespaces, // Pass namespace preservation to pipeline
|
|
870
876
|
// NOTE: Verification is ALWAYS enabled - precision is non-negotiable
|
|
871
877
|
};
|
|
872
878
|
|
|
@@ -1221,8 +1227,8 @@ async function testToolboxFunction(fnName, originalContent, originalSize, output
|
|
|
1221
1227
|
|
|
1222
1228
|
const startTime = Date.now();
|
|
1223
1229
|
const doc = parseSVG(originalContent);
|
|
1224
|
-
// Pass preserveVendor
|
|
1225
|
-
await fn(doc, { preserveVendor: config.preserveVendor });
|
|
1230
|
+
// Pass preserveVendor and preserveNamespaces options from config to toolbox functions
|
|
1231
|
+
await fn(doc, { preserveVendor: config.preserveVendor, preserveNamespaces: config.preserveNamespaces });
|
|
1226
1232
|
const output = serializeSVG(doc);
|
|
1227
1233
|
const outputSize = Buffer.byteLength(output);
|
|
1228
1234
|
result.timeMs = Date.now() - startTime;
|
|
@@ -1347,7 +1353,16 @@ function parseArgs(args) {
|
|
|
1347
1353
|
let i = 0;
|
|
1348
1354
|
|
|
1349
1355
|
while (i < args.length) {
|
|
1350
|
-
|
|
1356
|
+
let arg = args[i];
|
|
1357
|
+
let argValue = null;
|
|
1358
|
+
|
|
1359
|
+
// Handle --arg=value format
|
|
1360
|
+
if (arg.includes('=') && arg.startsWith('--')) {
|
|
1361
|
+
const eqIdx = arg.indexOf('=');
|
|
1362
|
+
argValue = arg.substring(eqIdx + 1);
|
|
1363
|
+
arg = arg.substring(0, eqIdx);
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1351
1366
|
switch (arg) {
|
|
1352
1367
|
case '-o': case '--output': cfg.output = args[++i]; break;
|
|
1353
1368
|
case '-l': case '--list': cfg.listFile = args[++i]; break;
|
|
@@ -1385,6 +1400,16 @@ function parseArgs(args) {
|
|
|
1385
1400
|
case '--no-gradients': cfg.bakeGradients = false; break;
|
|
1386
1401
|
// Vendor preservation option
|
|
1387
1402
|
case '--preserve-vendor': cfg.preserveVendor = true; break;
|
|
1403
|
+
// Namespace preservation option (comma-separated list)
|
|
1404
|
+
case '--preserve-ns': {
|
|
1405
|
+
const namespaces = argValue || args[++i];
|
|
1406
|
+
if (!namespaces) {
|
|
1407
|
+
logError('--preserve-ns requires a comma-separated list of namespaces');
|
|
1408
|
+
process.exit(CONSTANTS.EXIT_ERROR);
|
|
1409
|
+
}
|
|
1410
|
+
cfg.preserveNamespaces = namespaces.split(',').map(ns => ns.trim()).filter(ns => ns.length > 0);
|
|
1411
|
+
break;
|
|
1412
|
+
}
|
|
1388
1413
|
// E2E verification precision options
|
|
1389
1414
|
case '--clip-segments': {
|
|
1390
1415
|
const segs = parseInt(args[++i], 10);
|
package/bin/svgm.js
CHANGED
|
@@ -20,6 +20,7 @@ import { join, dirname, basename, extname, resolve, isAbsolute } from 'path';
|
|
|
20
20
|
import { VERSION } from '../src/index.js';
|
|
21
21
|
import * as SVGToolbox from '../src/svg-toolbox.js';
|
|
22
22
|
import { parseSVG, serializeSVG } from '../src/svg-parser.js';
|
|
23
|
+
import { injectPolyfills, detectSVG2Features } from '../src/svg2-polyfills.js';
|
|
23
24
|
|
|
24
25
|
// ============================================================================
|
|
25
26
|
// CONSTANTS
|
|
@@ -62,6 +63,8 @@ const DEFAULT_CONFIG = {
|
|
|
62
63
|
exclude: [],
|
|
63
64
|
quiet: false,
|
|
64
65
|
datauri: null,
|
|
66
|
+
preserveNamespaces: [],
|
|
67
|
+
svg2Polyfills: false,
|
|
65
68
|
showPlugins: false,
|
|
66
69
|
};
|
|
67
70
|
|
|
@@ -217,12 +220,19 @@ async function optimizeSvg(content, options = {}) {
|
|
|
217
220
|
const doc = parseSVG(content);
|
|
218
221
|
const pipeline = DEFAULT_PIPELINE;
|
|
219
222
|
|
|
223
|
+
// Detect SVG 2 features BEFORE optimization pipeline removes them
|
|
224
|
+
// (removeUnknownsAndDefaults strips elements not in SVG 1.1 spec)
|
|
225
|
+
let svg2Features = null;
|
|
226
|
+
if (options.svg2Polyfills) {
|
|
227
|
+
svg2Features = detectSVG2Features(doc);
|
|
228
|
+
}
|
|
229
|
+
|
|
220
230
|
// Run optimization pipeline
|
|
221
231
|
for (const pluginName of pipeline) {
|
|
222
232
|
const fn = SVGToolbox[pluginName];
|
|
223
233
|
if (fn && typeof fn === 'function') {
|
|
224
234
|
try {
|
|
225
|
-
await fn(doc, { precision: options.precision });
|
|
235
|
+
await fn(doc, { precision: options.precision, preserveNamespaces: options.preserveNamespaces });
|
|
226
236
|
} catch (e) {
|
|
227
237
|
// Skip failed optimizations silently
|
|
228
238
|
}
|
|
@@ -235,7 +245,7 @@ async function optimizeSvg(content, options = {}) {
|
|
|
235
245
|
const fn = SVGToolbox[pluginName];
|
|
236
246
|
if (fn && typeof fn === 'function') {
|
|
237
247
|
try {
|
|
238
|
-
await fn(doc, { precision: options.precision });
|
|
248
|
+
await fn(doc, { precision: options.precision, preserveNamespaces: options.preserveNamespaces });
|
|
239
249
|
} catch (e) {
|
|
240
250
|
// Skip failed optimizations silently
|
|
241
251
|
}
|
|
@@ -243,6 +253,14 @@ async function optimizeSvg(content, options = {}) {
|
|
|
243
253
|
}
|
|
244
254
|
}
|
|
245
255
|
|
|
256
|
+
// Inject SVG 2 polyfills if requested (using pre-detected features)
|
|
257
|
+
if (options.svg2Polyfills && svg2Features) {
|
|
258
|
+
if (svg2Features.meshGradients.length > 0 || svg2Features.hatches.length > 0) {
|
|
259
|
+
// Pass pre-detected features since pipeline may have removed SVG2 elements
|
|
260
|
+
injectPolyfills(doc, { features: svg2Features });
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
246
264
|
let result = serializeSVG(doc);
|
|
247
265
|
|
|
248
266
|
// Pretty print if requested, otherwise minify (SVGO default behavior)
|
|
@@ -384,6 +402,10 @@ Options:
|
|
|
384
402
|
regular expression pattern.
|
|
385
403
|
-q, --quiet Only output error messages
|
|
386
404
|
--show-plugins Show available plugins and exit
|
|
405
|
+
--preserve-ns <NS,...> Preserve vendor namespaces (inkscape, sodipodi,
|
|
406
|
+
illustrator, figma, etc.). Comma-separated.
|
|
407
|
+
--svg2-polyfills Inject JavaScript polyfills for SVG 2 features
|
|
408
|
+
(mesh gradients, hatches) for browser support
|
|
387
409
|
--no-color Output plain text without color
|
|
388
410
|
-h, --help Display help for command
|
|
389
411
|
|
|
@@ -417,7 +439,15 @@ function parseArgs(args) {
|
|
|
417
439
|
let i = 0;
|
|
418
440
|
|
|
419
441
|
while (i < args.length) {
|
|
420
|
-
|
|
442
|
+
let arg = args[i];
|
|
443
|
+
let argValue = null;
|
|
444
|
+
|
|
445
|
+
// Handle --arg=value format
|
|
446
|
+
if (arg.includes('=') && arg.startsWith('--')) {
|
|
447
|
+
const eqIdx = arg.indexOf('=');
|
|
448
|
+
argValue = arg.substring(eqIdx + 1);
|
|
449
|
+
arg = arg.substring(0, eqIdx);
|
|
450
|
+
}
|
|
421
451
|
|
|
422
452
|
switch (arg) {
|
|
423
453
|
case '-v':
|
|
@@ -517,6 +547,17 @@ function parseArgs(args) {
|
|
|
517
547
|
cfg.showPlugins = true;
|
|
518
548
|
break;
|
|
519
549
|
|
|
550
|
+
case '--preserve-ns':
|
|
551
|
+
{
|
|
552
|
+
const val = argValue || args[++i];
|
|
553
|
+
cfg.preserveNamespaces = val.split(',').map(s => s.trim().toLowerCase());
|
|
554
|
+
}
|
|
555
|
+
break;
|
|
556
|
+
|
|
557
|
+
case '--svg2-polyfills':
|
|
558
|
+
cfg.svg2Polyfills = true;
|
|
559
|
+
break;
|
|
560
|
+
|
|
520
561
|
case '--no-color':
|
|
521
562
|
// Already handled in colors initialization
|
|
522
563
|
break;
|
|
@@ -561,6 +602,8 @@ async function main() {
|
|
|
561
602
|
eol: config.eol,
|
|
562
603
|
finalNewline: config.finalNewline,
|
|
563
604
|
datauri: config.datauri,
|
|
605
|
+
preserveNamespaces: config.preserveNamespaces,
|
|
606
|
+
svg2Polyfills: config.svg2Polyfills,
|
|
564
607
|
};
|
|
565
608
|
|
|
566
609
|
// Handle string input
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* SVG path conversion, and 2D/3D affine transformations using Decimal.js.
|
|
6
6
|
*
|
|
7
7
|
* @module @emasoft/svg-matrix
|
|
8
|
-
* @version 1.0.
|
|
8
|
+
* @version 1.0.28
|
|
9
9
|
* @license MIT
|
|
10
10
|
*
|
|
11
11
|
* @example
|
|
@@ -46,6 +46,8 @@ import * as MeshGradient from './mesh-gradient.js';
|
|
|
46
46
|
import * as SVGParser from './svg-parser.js';
|
|
47
47
|
import * as FlattenPipeline from './flatten-pipeline.js';
|
|
48
48
|
import * as Verification from './verification.js';
|
|
49
|
+
import * as InkscapeSupport from './inkscape-support.js';
|
|
50
|
+
import * as SVG2Polyfills from './svg2-polyfills.js';
|
|
49
51
|
import { Logger, LogLevel, setLogLevel, getLogLevel as getLoggerLevel, enableFileLogging, disableFileLogging } from './logger.js';
|
|
50
52
|
|
|
51
53
|
// SVGO-inspired precision modules
|
|
@@ -85,7 +87,7 @@ Decimal.set({ precision: 80 });
|
|
|
85
87
|
* Library version
|
|
86
88
|
* @constant {string}
|
|
87
89
|
*/
|
|
88
|
-
export const VERSION = '1.0.
|
|
90
|
+
export const VERSION = '1.0.28';
|
|
89
91
|
|
|
90
92
|
/**
|
|
91
93
|
* Default precision for path output (decimal places)
|
|
@@ -120,6 +122,7 @@ export { ClipPathResolver, MaskResolver, PatternResolver };
|
|
|
120
122
|
export { UseSymbolResolver, MarkerResolver };
|
|
121
123
|
export { MeshGradient };
|
|
122
124
|
export { SVGParser, FlattenPipeline, Verification };
|
|
125
|
+
export { InkscapeSupport, SVG2Polyfills };
|
|
123
126
|
|
|
124
127
|
// ============================================================================
|
|
125
128
|
// SVGO-INSPIRED PRECISION MODULES
|
|
@@ -577,6 +580,8 @@ export default {
|
|
|
577
580
|
SVGParser,
|
|
578
581
|
FlattenPipeline,
|
|
579
582
|
Verification,
|
|
583
|
+
InkscapeSupport,
|
|
584
|
+
SVG2Polyfills,
|
|
580
585
|
|
|
581
586
|
// Logging
|
|
582
587
|
Logger,
|