@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 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 option from config to toolbox functions
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
- const arg = args[i];
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
- const arg = args[i];
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emasoft/svg-matrix",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "description": "Arbitrary-precision matrix, vector and affine transformation library for JavaScript using decimal.js",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
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.26
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.26';
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,