svg-path-simplify 0.4.1 → 0.4.2

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.
@@ -1729,6 +1729,7 @@
1729
1729
  const transVertical = ['scaleY', 'translateY', 'skewY'];
1730
1730
 
1731
1731
  const colorProps = ['fill', 'stroke', 'stop-color'];
1732
+ const geometryProps = ['d', 'points', 'cx', 'cy', 'x1', 'x2', 'y1', 'y2', 'width', 'height', 'r', 'rx', 'ry', 'x', 'y'];
1732
1733
 
1733
1734
  const geometryEls = [
1734
1735
  "path",
@@ -2070,7 +2071,14 @@
2070
2071
 
2071
2072
  let attributes = [...el.attributes];
2072
2073
  let attNames = attributes.map(att => att.name);
2073
- let attValues = attributes.map(att => att.nodeValue);
2074
+
2075
+ // doesn't work in node!
2076
+
2077
+
2078
+ let attValues = [];
2079
+ attNames.forEach(att=>{
2080
+ attValues.push(el.getAttribute(att));
2081
+ });
2074
2082
 
2075
2083
  let isSquare = width === height;
2076
2084
 
@@ -3904,6 +3912,7 @@
3904
3912
  toShorthands = true,
3905
3913
  toLonghands = false,
3906
3914
  toRelative = true,
3915
+ toMixed = false,
3907
3916
  toAbsolute = false,
3908
3917
  decimals = 3,
3909
3918
  arcToCubic = false,
@@ -3919,6 +3928,8 @@
3919
3928
 
3920
3929
  } = {}) {
3921
3930
 
3931
+ let pathDataAbs = [];
3932
+
3922
3933
  // pathdata properties - test= true adds a manual test
3923
3934
  if (testTypes) {
3924
3935
 
@@ -3946,12 +3957,38 @@
3946
3957
 
3947
3958
  if (hasQuadratics && quadraticToCubic) pathData = pathDataQuadraticToCubic(pathData);
3948
3959
 
3960
+ if(toMixed) toRelative = true;
3961
+
3949
3962
  // pre round - before relative conversion to minimize distortions
3950
3963
  if (decimals > -1 && toRelative) pathData = roundPathData(pathData, decimals);
3951
3964
 
3965
+ // clone absolute pathdata
3966
+ if(toMixed){
3967
+ pathDataAbs = JSON.parse(JSON.stringify(pathData));
3968
+ }
3969
+
3952
3970
  if (toRelative) pathData = pathDataToRelative(pathData);
3953
3971
  if (decimals > -1) pathData = roundPathData(pathData, decimals);
3954
3972
 
3973
+ // choose most compact commands: relative or absolute
3974
+ if(toMixed){
3975
+ for(let i=0; i<pathData.length; i++){
3976
+ let com = pathData[i];
3977
+ let comA = pathDataAbs[i];
3978
+ // compare Lengths
3979
+ let comStr = [com.type, com.values.join(' ')].join('').replaceAll(' -', '-').replaceAll(' 0.', ' .');
3980
+ let comStrA = [comA.type, comA.values.join(' ')].join('').replaceAll(' -', '-').replaceAll(' 0.', ' .');
3981
+
3982
+ let lenR = comStr.length;
3983
+ let lenA = comStrA.length;
3984
+
3985
+ if(lenA<lenR){
3986
+
3987
+ pathData[i] = pathDataAbs[i];
3988
+ }
3989
+ }
3990
+ }
3991
+
3955
3992
  return pathData
3956
3993
  }
3957
3994
 
@@ -3962,6 +3999,9 @@
3962
3999
  */
3963
4000
 
3964
4001
  function optimizeArcPathData(pathData = []) {
4002
+
4003
+ let remove =[];
4004
+
3965
4005
  pathData.forEach((com, i) => {
3966
4006
  let { type, values } = com;
3967
4007
  if (type === 'A') {
@@ -3971,6 +4011,12 @@
3971
4011
  let M = { x: x0, y: y0 };
3972
4012
  let p = { x, y };
3973
4013
 
4014
+ if(rx===0 || ry===0){
4015
+ pathData[i]= null;
4016
+ remove.push(i);
4017
+
4018
+ }
4019
+
3974
4020
  // rx and ry are large enough
3975
4021
  if (rx >= 1 && (x === x0 || y === y0)) {
3976
4022
  let diff = Math.abs(rx - ry) / rx;
@@ -3993,6 +4039,8 @@
3993
4039
  }
3994
4040
  }
3995
4041
  });
4042
+
4043
+ if(remove.length) pathData = pathData.filter(Boolean);
3996
4044
  return pathData;
3997
4045
  }
3998
4046
 
@@ -5618,133 +5666,6 @@
5618
5666
  return transObj;
5619
5667
  }
5620
5668
 
5621
- function pathElToShape(el, {
5622
- convert_rects = false,
5623
- convert_ellipses = false,
5624
- convert_poly = false,
5625
- convert_lines = false
5626
- } = {}) {
5627
-
5628
- let pathData = parsePathDataNormalized(el.getAttribute('d'));
5629
- let coms = Array.from(new Set(pathData.map(com => com.type))).join('');
5630
-
5631
- let hasArcs = (/[a]/gi).test(coms);
5632
- let hasBeziers = (/[csqt]/gi).test(coms);
5633
- let hasLines = (/[l]/gi).test(coms);
5634
- let isPoly = !(/[acqts]/gi).test(coms);
5635
- let closed = (/[z]/gi).test(coms);
5636
- let shape = null;
5637
- let type = null;
5638
-
5639
- let attributes = getElementAtts(el);
5640
- let attsNew = {};
5641
- let decimals = 7;
5642
-
5643
- if (isPoly) {
5644
-
5645
- // is line
5646
- if (pathData.length === 2 && convert_lines) {
5647
- type = 'line';
5648
- shape = document.createElementNS(svgNs, type);
5649
- let [x1, y1, x2, y2] = [...pathData[0].values, ...pathData[1].values].map(val => roundTo(val, decimals));
5650
- attsNew = { x1, y1, x2, y2 };
5651
- }
5652
- // polygon, polyline or rect
5653
- else {
5654
-
5655
- let vertices = getPathDataVertices(pathData);
5656
- let bb = getPolyBBox(vertices);
5657
- let areaPoly = getPolygonArea(vertices, true);
5658
- let areaRect = bb.width * bb.height;
5659
- let areaDiff = Math.abs(1 - areaRect / areaPoly);
5660
-
5661
- // is rect
5662
- if (convert_rects && areaDiff < 0.01) {
5663
- type = 'rect';
5664
- shape = document.createElementNS(svgNs, type);
5665
- let { x, y, width, height } = bb;
5666
- attsNew = { x, y, width, height };
5667
-
5668
- }
5669
- // polyline or polygon
5670
- else if(convert_poly) {
5671
- type = closed ? 'polygon' : 'polyline';
5672
- shape = document.createElementNS(svgNs, type);
5673
- let points = vertices.map(pt => { return [pt.x, pt.y] }).flat().map(val => roundTo(val, decimals)).join(' ');
5674
- attsNew = { points };
5675
- }
5676
- }
5677
- }
5678
- // circles or ellipses
5679
- else if (!hasLines && convert_ellipses) {
5680
-
5681
- // try to convert cubics to arcs
5682
- if (!hasArcs && hasBeziers) {
5683
- pathData = pathDataCubicsToArc(pathData, { areaThreshold: 2.5 });
5684
- hasArcs = pathData.filter(com => com.type === 'A').length;
5685
- }
5686
-
5687
- if (hasArcs) {
5688
- let pathData2 = getPathDataVerbose(pathData, { addArcParams: true });
5689
- let arcComs = pathData2.filter(com => com.type === 'A');
5690
-
5691
- let cxVals = new Set();
5692
- let cyVals = new Set();
5693
- let rxVals = new Set();
5694
- let ryVals = new Set();
5695
-
5696
- if (arcComs.length > 1) {
5697
-
5698
- pathData2.forEach(com => {
5699
- if (com.type === 'A') {
5700
-
5701
- cxVals.add(roundTo(com.cx, decimals));
5702
- cyVals.add(roundTo(com.cy, decimals));
5703
- rxVals.add(roundTo(com.rx, decimals));
5704
- ryVals.add(roundTo(com.ry, decimals));
5705
- }
5706
- });
5707
- }
5708
-
5709
- cxVals = Array.from(cxVals);
5710
- cyVals = Array.from(cyVals);
5711
- rxVals = Array.from(rxVals);
5712
- ryVals = Array.from(ryVals);
5713
-
5714
- if(cxVals.length===1 && cyVals.length===1 && rxVals.length===1 && ryVals.length===1){
5715
- let [rx, ry, cx, cy] = [rxVals[0], ryVals[0], cxVals[0], cyVals[0]];
5716
- type = rx===ry ? 'circle' : 'ellipse';
5717
- shape = document.createElementNS(svgNs, type);
5718
- attsNew = type==='circle' ? { r:rx, cx, cy } : {rx, ry, cx, cy};
5719
- }
5720
- }
5721
- }
5722
-
5723
- // if el could be replaced
5724
- if (shape) {
5725
- let ignore = ['id', 'class'];
5726
-
5727
- // set shape attributes
5728
- for (let att in attsNew) {
5729
- shape.setAttribute(att, attsNew[att]);
5730
- }
5731
-
5732
- // copy old attributes
5733
- for (let att in attributes) {
5734
-
5735
- if (attLookup.atts[att].includes(type) || ignore.includes(att) || att.startsWith('data-')) {
5736
- shape.setAttribute(att, attributes[att]);
5737
- }
5738
- }
5739
-
5740
- // replace
5741
- el = shape;
5742
- }
5743
-
5744
- return el;
5745
-
5746
- }
5747
-
5748
5669
  function shapeElToPath(el, { width = 0,
5749
5670
  height = 0,
5750
5671
  convert_rects = false,
@@ -5923,6 +5844,133 @@
5923
5844
 
5924
5845
  }
5925
5846
 
5847
+ function pathElToShape(el, {
5848
+ convert_rects = false,
5849
+ convert_ellipses = false,
5850
+ convert_poly = false,
5851
+ convert_lines = false
5852
+ } = {}) {
5853
+
5854
+ let pathData = parsePathDataNormalized(el.getAttribute('d'));
5855
+ let coms = Array.from(new Set(pathData.map(com => com.type))).join('');
5856
+
5857
+ let hasArcs = (/[a]/gi).test(coms);
5858
+ let hasBeziers = (/[csqt]/gi).test(coms);
5859
+ let hasLines = (/[l]/gi).test(coms);
5860
+ let isPoly = !(/[acqts]/gi).test(coms);
5861
+ let closed = (/[z]/gi).test(coms);
5862
+ let shape = null;
5863
+ let type = null;
5864
+
5865
+ let attributes = getElementAtts(el);
5866
+ let attsNew = {};
5867
+ let decimals = 7;
5868
+
5869
+ if (isPoly) {
5870
+
5871
+ // is line
5872
+ if (pathData.length === 2 && convert_lines) {
5873
+ type = 'line';
5874
+ shape = document.createElementNS(svgNs, type);
5875
+ let [x1, y1, x2, y2] = [...pathData[0].values, ...pathData[1].values].map(val => roundTo(val, decimals));
5876
+ attsNew = { x1, y1, x2, y2 };
5877
+ }
5878
+ // polygon, polyline or rect
5879
+ else {
5880
+
5881
+ let vertices = getPathDataVertices(pathData);
5882
+ let bb = getPolyBBox(vertices);
5883
+ let areaPoly = getPolygonArea(vertices, true);
5884
+ let areaRect = bb.width * bb.height;
5885
+ let areaDiff = Math.abs(1 - areaRect / areaPoly);
5886
+
5887
+ // is rect
5888
+ if (convert_rects && areaDiff < 0.01) {
5889
+ type = 'rect';
5890
+ shape = document.createElementNS(svgNs, type);
5891
+ let { x, y, width, height } = bb;
5892
+ attsNew = { x, y, width, height };
5893
+
5894
+ }
5895
+ // polyline or polygon
5896
+ else if(convert_poly) {
5897
+ type = closed ? 'polygon' : 'polyline';
5898
+ shape = document.createElementNS(svgNs, type);
5899
+ let points = vertices.map(pt => { return [pt.x, pt.y] }).flat().map(val => roundTo(val, decimals)).join(' ');
5900
+ attsNew = { points };
5901
+ }
5902
+ }
5903
+ }
5904
+ // circles or ellipses
5905
+ else if (!hasLines && convert_ellipses) {
5906
+
5907
+ // try to convert cubics to arcs
5908
+ if (!hasArcs && hasBeziers) {
5909
+ pathData = pathDataCubicsToArc(pathData, { areaThreshold: 2.5 });
5910
+ hasArcs = pathData.filter(com => com.type === 'A').length;
5911
+ }
5912
+
5913
+ if (hasArcs) {
5914
+ let pathData2 = getPathDataVerbose(pathData, { addArcParams: true });
5915
+ let arcComs = pathData2.filter(com => com.type === 'A');
5916
+
5917
+ let cxVals = new Set();
5918
+ let cyVals = new Set();
5919
+ let rxVals = new Set();
5920
+ let ryVals = new Set();
5921
+
5922
+ if (arcComs.length > 1) {
5923
+
5924
+ pathData2.forEach(com => {
5925
+ if (com.type === 'A') {
5926
+
5927
+ cxVals.add(roundTo(com.cx, decimals));
5928
+ cyVals.add(roundTo(com.cy, decimals));
5929
+ rxVals.add(roundTo(com.rx, decimals));
5930
+ ryVals.add(roundTo(com.ry, decimals));
5931
+ }
5932
+ });
5933
+ }
5934
+
5935
+ cxVals = Array.from(cxVals);
5936
+ cyVals = Array.from(cyVals);
5937
+ rxVals = Array.from(rxVals);
5938
+ ryVals = Array.from(ryVals);
5939
+
5940
+ if(cxVals.length===1 && cyVals.length===1 && rxVals.length===1 && ryVals.length===1){
5941
+ let [rx, ry, cx, cy] = [rxVals[0], ryVals[0], cxVals[0], cyVals[0]];
5942
+ type = rx===ry ? 'circle' : 'ellipse';
5943
+ shape = document.createElementNS(svgNs, type);
5944
+ attsNew = type==='circle' ? { r:rx, cx, cy } : {rx, ry, cx, cy};
5945
+ }
5946
+ }
5947
+ }
5948
+
5949
+ // if el could be replaced
5950
+ if (shape) {
5951
+ let ignore = ['id', 'class'];
5952
+
5953
+ // set shape attributes
5954
+ for (let att in attsNew) {
5955
+ shape.setAttribute(att, attsNew[att]);
5956
+ }
5957
+
5958
+ // copy old attributes
5959
+ for (let att in attributes) {
5960
+
5961
+ if (attLookup.atts[att].includes(type) || ignore.includes(att) || att.startsWith('data-')) {
5962
+ shape.setAttribute(att, attributes[att]);
5963
+ }
5964
+ }
5965
+
5966
+ // replace
5967
+ el = shape;
5968
+ }
5969
+
5970
+ return el;
5971
+
5972
+ }
5973
+
5926
5974
  function pathDataRemoveColinear(pathData, {
5927
5975
  tolerance = 1,
5928
5976
 
@@ -6785,6 +6833,10 @@
6785
6833
  removeDefaults = true,
6786
6834
  cleanUpStrokes = true,
6787
6835
  normalizeTransforms = true,
6836
+ removeIds=false,
6837
+ removeClassNames=false,
6838
+
6839
+ include=[],
6788
6840
  exclude = [],
6789
6841
  width = 0,
6790
6842
  height = 0,
@@ -6815,7 +6867,7 @@
6815
6867
  */
6816
6868
 
6817
6869
  if (removeInvalid || removeDefaults || removeNameSpaced) {
6818
- let propsFilteredObj = filterSvgElProps(nodeName, props, { removeDefaults, removeNameSpaced, exclude, cleanUpStrokes, include: transformsStandalone, cleanUpStrokes: false });
6870
+ let propsFilteredObj = filterSvgElProps(nodeName, props, { removeIds, removeClassNames, removeDefaults, removeNameSpaced, exclude, cleanUpStrokes, include: [...transformsStandalone, ...include], cleanUpStrokes: false });
6819
6871
  props = propsFilteredObj.propsFiltered;
6820
6872
  remove.push(...propsFilteredObj.remove);
6821
6873
  }
@@ -7149,13 +7201,21 @@
7149
7201
  removeInvalid = true,
7150
7202
  removeDefaults = true,
7151
7203
  allowDataAtts = true,
7204
+ allowMeta = false,
7205
+ allowAriaAtts = false,
7152
7206
  cleanUpStrokes = true,
7153
- include = ['id', 'class'],
7207
+
7208
+ include=[],
7209
+ removeIds=false,
7210
+ removeClassNames=false,
7154
7211
  exclude = [],
7155
7212
  } = {}) {
7156
7213
  let propsFiltered = {};
7157
7214
  let remove = [];
7158
7215
 
7216
+ if(!removeIds) include.push('id');
7217
+ if(!removeClassNames) include.push('class');
7218
+
7159
7219
  // allow defaults for nested
7160
7220
 
7161
7221
  let noStrokeColor = cleanUpStrokes ? (props['stroke'] === undefined) : false;
@@ -7170,25 +7230,35 @@
7170
7230
  false;
7171
7231
 
7172
7232
  // remove null transforms
7173
- if(prop==='transform' && value==='matrix(1 0 0 1 0 0)') isValid = false;
7233
+ if (prop === 'transform' && value === 'matrix(1 0 0 1 0 0)') isValid = false;
7174
7234
 
7175
7235
  // allow data attributes
7176
7236
  let isDataAtt = allowDataAtts ? prop.startsWith('data-') : false;
7237
+ let isMeta = allowMeta && prop === 'title';
7238
+ let isAria = allowAriaAtts && prop.startsWith('aria-');
7177
7239
 
7178
7240
  // filter out defaults
7179
7241
  let isDefault = removeDefaults ?
7180
7242
  (attLookup.defaults[prop] ? attLookup.defaults[prop] !== undefined && attLookup.defaults[prop].includes(value) : false) :
7181
7243
  false;
7182
7244
 
7245
+ let isFutileStroke = noStrokeColor && strokeAtts.includes(prop);
7246
+
7247
+ if (isDefault || isDataAtt || isMeta || isAria || isFutileStroke) isValid = false;
7248
+ if (include.includes(prop)) isValid = true;
7249
+
7250
+ /*
7183
7251
  if (isDataAtt || include.includes(prop)) isValid = true;
7184
- if (isDefault) isValid = false;
7252
+ if (isDefault) isValid = false
7185
7253
  if (exclude.length && exclude.includes(prop)) isValid = false;
7186
- if (noStrokeColor && strokeAtts.includes(prop)) isValid = false;
7254
+ if (noStrokeColor && strokeAtts.includes(prop)) isValid = false
7255
+ */
7187
7256
 
7188
7257
  if (isValid) {
7189
7258
  propsFiltered[prop] = props[prop];
7190
7259
  }
7191
7260
  else {
7261
+
7192
7262
  remove.push(prop);
7193
7263
  }
7194
7264
  }
@@ -7287,6 +7357,23 @@
7287
7357
  return props
7288
7358
  }
7289
7359
 
7360
+ function toCamelCase(str) {
7361
+ return str
7362
+ .split(/[-| ]/)
7363
+ .map((e,i) => i
7364
+ ? e.charAt(0).toUpperCase() + e.slice(1).toLowerCase()
7365
+ : e.toLowerCase()
7366
+ )
7367
+ .join('')
7368
+ }
7369
+
7370
+ function toShortStr(str){
7371
+ if(isNumericValue(str)) return str
7372
+ let strShort = str.split('-').map(str=>{return str.replace(/a|e|i|o|u/g,'') }).join('-');
7373
+ strShort = toCamelCase(strShort);
7374
+ return strShort
7375
+ }
7376
+
7290
7377
  function removeEmptySVGEls(svg) {
7291
7378
  let els = svg.querySelectorAll('g, defs');
7292
7379
  els.forEach(el => {
@@ -7298,6 +7385,8 @@
7298
7385
  removeHidden = true,
7299
7386
 
7300
7387
  stylesToAttributes = true,
7388
+ attributesToGroup = false,
7389
+
7301
7390
  removePrologue = true,
7302
7391
  removeIds = false,
7303
7392
  removeClassNames = false,
@@ -7317,9 +7406,14 @@
7317
7406
 
7318
7407
  mergePaths = false,
7319
7408
  removeOffCanvas = true,
7409
+
7320
7410
  cleanupSVGAtts = true,
7321
7411
  removeNameSpaced = true,
7322
- attributesToGroup = true,
7412
+
7413
+ // meta
7414
+ allowMeta = false,
7415
+ allowDataAtts = true,
7416
+ allowAriaAtts = true,
7323
7417
 
7324
7418
  shapeConvert = false,
7325
7419
  convert_rects = false,
@@ -7334,6 +7428,8 @@
7334
7428
  excludedEls = [],
7335
7429
  } = {}) {
7336
7430
 
7431
+ if (attributesToGroup) stylesToAttributes = true;
7432
+
7337
7433
  // replace namespaced refs
7338
7434
  if (fixHref) svgMarkup = svgMarkup.replaceAll("xlink:href=", "href=");
7339
7435
 
@@ -7351,9 +7447,16 @@
7351
7447
  normalizeTransforms,
7352
7448
  removeDefaults: false,
7353
7449
  cleanUpStrokes: false,
7450
+ allowMeta,
7451
+ allowDataAtts,
7452
+ allowAriaAtts,
7354
7453
  autoRoundValues,
7454
+ removeIds,
7455
+ removeClassNames,
7355
7456
  minifyRgbColors,
7356
7457
  };
7458
+
7459
+ // root svg properties
7357
7460
  let stylePropsSVG = parseStylesProperties(svg, propOptions);
7358
7461
 
7359
7462
  // add svg font size for scaling relative
@@ -7365,11 +7468,9 @@
7365
7468
  * be inherited by children
7366
7469
  */
7367
7470
  let groups = svg.querySelectorAll('g');
7368
- let groupProps = [];
7369
7471
 
7370
7472
  groups.forEach(g => {
7371
7473
  let stylePropsG = parseStylesProperties(g, propOptions);
7372
- groupProps.push(stylePropsG);
7373
7474
  let children = g.querySelectorAll(`${renderedEls.join(', ')}`);
7374
7475
 
7375
7476
  // store parent styles to child property
@@ -7383,12 +7484,29 @@
7383
7484
 
7384
7485
  if (cleanupSVGAtts) {
7385
7486
 
7386
- let allowed = ['viewBox', 'xmlns', 'width', 'height', 'id', 'class'];
7487
+ let allowed = new Set(['viewBox', 'xmlns', 'width', 'height']);
7488
+
7489
+ if (!removeIds) allowed.add('id');
7490
+ if (!removeClassNames) allowed.add('class');
7491
+ if (removeDimensions) {
7492
+ allowed.delete('width');
7493
+ allowed.delete('height');
7494
+ }
7495
+
7496
+ allowed = Array.from(allowed);
7387
7497
  if (!stylesToAttributes) {
7388
7498
  allowed.push('fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'font-size', 'font-family', 'font-style', 'style');
7389
7499
  }
7390
7500
 
7391
- removeExcludedAttribues(svg, allowed);
7501
+ removeExcludedAttribues(svg, { allowed, allowMeta, allowAriaAtts, allowDataAtts });
7502
+ }
7503
+
7504
+ // remove meta
7505
+ if (!allowMeta) {
7506
+ let metaEls = svg.querySelectorAll('meta, metadata, desc, title');
7507
+ metaEls.forEach(meta => {
7508
+ meta.remove();
7509
+ });
7392
7510
  }
7393
7511
 
7394
7512
  // add viewBox
@@ -7405,9 +7523,10 @@
7405
7523
  if (removeOffCanvas) removeOffCanvasEls(svg, { x, y, width, height });
7406
7524
 
7407
7525
  // always remove scripts
7408
- let removeEls = ['metadata', 'script', ...excludedEls];
7409
7526
 
7410
- removeSVGEls(svg, { removeEls, removeNameSpaced });
7527
+ let removeEls = ['script', ...excludedEls];
7528
+
7529
+ removeSVGEls(svg, { remove: removeEls, removeNameSpaced });
7411
7530
 
7412
7531
  // an array of all elements' properties
7413
7532
  let svgElProps = [];
@@ -7433,6 +7552,7 @@
7433
7552
  * to user units
7434
7553
  */
7435
7554
  let styleProps = parseStylesProperties(el, propOptions);
7555
+ let stylePropsFiltered = {};
7436
7556
 
7437
7557
  // get parent styles
7438
7558
  let { parentStyleProps = [] } = el;
@@ -7458,27 +7578,25 @@
7458
7578
  styleProps.transformArr = transFormInherited;
7459
7579
 
7460
7580
  // merge with svg props
7581
+
7461
7582
  styleProps = {
7462
7583
  ...stylePropsSVG,
7463
7584
  ...inheritedProps,
7464
7585
  ...styleProps
7465
7586
  };
7466
7587
 
7588
+ // dont inherit class
7589
+ if (stylePropsSVG['class'] === styleProps['class']) {
7590
+ delete styleProps['class'];
7591
+ }
7592
+
7467
7593
  // add combined transforms
7468
7594
  addTransFormProps(styleProps, transFormInherited);
7469
7595
 
7470
7596
  let { remove, matrix, transComponents } = styleProps;
7471
7597
 
7472
- // mark attributes for removal
7473
- if (removeClassNames) styleProps.remove.push('class');
7474
- if (removeIds) styleProps.remove.push('id');
7475
- if (removeDimensions) {
7476
- styleProps.remove.push('width');
7477
- styleProps.remove.push('height');
7478
- }
7479
-
7480
7598
  // styles to atts
7481
- if (unGroup || convertTransforms || minifyRgbColors ) stylesToAttributes = true;
7599
+ if (unGroup || convertTransforms || minifyRgbColors) stylesToAttributes = true;
7482
7600
 
7483
7601
  if (stylesToAttributes) {
7484
7602
 
@@ -7501,7 +7619,6 @@
7501
7619
  styleProps.cy[0] = [styleProps.cy[0] * scaleX + translateY];
7502
7620
 
7503
7621
  if (styleProps.r) styleProps.r[0] = [styleProps.r[0] * scaleX];
7504
-
7505
7622
  if (styleProps.rx) styleProps.rx[0] = [styleProps.rx[0] * scaleX];
7506
7623
  if (styleProps.ry) styleProps.ry[0] = [styleProps.ry[0] * scaleX];
7507
7624
 
@@ -7523,6 +7640,12 @@
7523
7640
  styleProps.height = [styleProps.height[0] * scaleX];
7524
7641
  }
7525
7642
 
7643
+ // remove now obsolete transform properties
7644
+ delete styleProps.matrix;
7645
+ delete styleProps.transformArr;
7646
+ delete styleProps.transComponents;
7647
+
7648
+ // mark transform attribute for removal
7526
7649
  remove.push('transform');
7527
7650
 
7528
7651
  // scale props like stroke width or dash-array
@@ -7538,17 +7661,16 @@
7538
7661
  * apply consolidated
7539
7662
  * element attributes
7540
7663
  */
7541
-
7542
- let stylePropsFiltered = filterSvgElProps(name, styleProps,
7543
- { removeDefaults: true, cleanUpStrokes });
7664
+ stylePropsFiltered = filterSvgElProps(name, styleProps,
7665
+ { removeDefaults: true, cleanUpStrokes, allowMeta, allowAriaAtts, allowDataAtts, removeIds });
7544
7666
 
7545
7667
  remove = [...remove, ...stylePropsFiltered.remove];
7546
7668
 
7547
7669
  for (let prop in stylePropsFiltered.propsFiltered) {
7548
7670
  let values = styleProps[prop];
7549
-
7550
7671
  let val = values.length ? values.join(' ') : values[0];
7551
7672
  el.setAttribute(prop, val);
7673
+
7552
7674
  }
7553
7675
 
7554
7676
  // remove obsolete attributes
@@ -7576,9 +7698,19 @@
7576
7698
  });
7577
7699
  } else {
7578
7700
  groups.forEach((g, i) => {
7579
- let atts = [...Object.keys(groupProps[i]), 'style', 'transform'];
7701
+
7702
+ let atts = Object.keys(getElementAtts(g));
7703
+
7580
7704
  atts.forEach(att => {
7581
- g.removeAttribute(att);
7705
+
7706
+ let isData = !allowDataAtts && att.startsWith('data-');
7707
+ let isAria = !allowAriaAtts && att.startsWith('aria-');
7708
+
7709
+ remove.push('transform', 'style');
7710
+
7711
+ if (remove.includes(att) || isData || isAria) {
7712
+ g.removeAttribute(att);
7713
+ }
7582
7714
  });
7583
7715
  });
7584
7716
 
@@ -7622,7 +7754,7 @@
7622
7754
  el.replaceWith(path);
7623
7755
 
7624
7756
  name = 'path';
7625
- el = path;
7757
+ el = path; // required for node
7626
7758
 
7627
7759
  }
7628
7760
 
@@ -7633,14 +7765,217 @@
7633
7765
  let paths = svg.querySelectorAll('path');
7634
7766
  paths.forEach(path => {
7635
7767
  let shape = pathElToShape(path, { convert_rects, convert_ellipses, convert_poly, convert_lines });
7636
- path.replaceWith(shape);
7768
+
7637
7769
  path = shape;
7638
7770
  });
7639
7771
 
7640
7772
  }
7641
7773
 
7774
+ /**
7775
+ * combine styles
7776
+ * store in node property
7777
+ */
7778
+
7779
+ if (mergePaths || attributesToGroup) {
7780
+
7781
+ let options = { allowMeta, allowAriaAtts, removeIds, removeClassNames, allowDataAtts };
7782
+
7783
+ /**
7784
+ * exclude properties for
7785
+ * adjacent path merging
7786
+ * e.g ignore classnames or ids
7787
+ */
7788
+ if(mergePaths){
7789
+ options.removeIds = true;
7790
+ options.removeClassNames = true;
7791
+ options.allowAriaAtts = false;
7792
+ options.allowMeta = false;
7793
+ }
7794
+
7795
+ stylePropsFiltered = filterSvgElProps(name, styleProps, options).propsFiltered;
7796
+
7797
+ for (let prop in stylePropsFiltered) {
7798
+
7799
+ if (geometryProps.includes(prop)) continue;
7800
+
7801
+ let values = stylePropsFiltered[prop];
7802
+ let val = values.length ? values.join(' ') : values[0];
7803
+
7804
+ let propShort = toShortStr(prop);
7805
+ let valShort = toShortStr(val);
7806
+ let propStr = `${propShort}-${valShort}`;
7807
+
7808
+ // store in node property
7809
+ if (!el.styleSet) el.styleSet = new Set();
7810
+ el.styleSet.add(propStr);
7811
+ }
7812
+
7813
+ }
7814
+
7642
7815
  }//endof element loop
7643
7816
 
7817
+ /**
7818
+ * merge paths with same styles
7819
+ */
7820
+ if (mergePaths) {
7821
+ let paths = svg.querySelectorAll('path');
7822
+ let len = paths.length;
7823
+
7824
+ if (len) {
7825
+ let path0 = paths[0];
7826
+ let d0 = path0.getAttribute('d');
7827
+ let stylePrev = path0.styleSet !== undefined ? [...path0.styleSet].join('_') : '';
7828
+
7829
+ for (let i = 1; i < len; i++) {
7830
+ let path = paths[i];
7831
+ let style = path.styleSet !== undefined ? [...path.styleSet].join('_') : '';
7832
+ let isSibling = path.previousElementSibling === path0;
7833
+ let d = path.getAttribute('d');
7834
+ let isAbs = d.startsWith('M');
7835
+
7836
+ if (isSibling && style === stylePrev) {
7837
+ let dAbs = isAbs ? d : parsePathDataString(d).pathData.map(com => `${com.type} ${com.values.join(' ')}`).join(' ');
7838
+
7839
+ d0 += dAbs;
7840
+ path0.setAttribute('d', d0);
7841
+ path.remove();
7842
+
7843
+ } else {
7844
+ path0 = path;
7845
+ d0 = isAbs ? d : parsePathDataString(d).pathData.map(com => `${com.type} ${com.values.join(' ')}`).join(' ');
7846
+ }
7847
+
7848
+ // update style
7849
+ stylePrev = style;
7850
+ }
7851
+ }
7852
+
7853
+ /**
7854
+ * shared styles to group
7855
+ */
7856
+
7857
+ if (attributesToGroup) {
7858
+ let els = svg.querySelectorAll(geometryEls.join(', '));
7859
+ let len = els.length;
7860
+
7861
+ let el0 = els[0] || null;
7862
+ let stylePrev = el0.styleSet !== undefined ? [...el0.styleSet].join('_') : '';
7863
+
7864
+ // all props
7865
+ let allProps = {};
7866
+
7867
+ // find attributes shared by all
7868
+ let globalAtts = [];
7869
+
7870
+ if (len) {
7871
+
7872
+ let groups = [[el0]];
7873
+ let idx = 0;
7874
+ let elPrev = el0;
7875
+
7876
+ for (let i = 0; i < len; i++) {
7877
+ let el = els[i];
7878
+ let atts = getElementAtts(el);
7879
+ for (let att in atts) {
7880
+ let att_str = `${att}_${atts[att]}`;
7881
+
7882
+ if (!allProps[att_str]) {
7883
+ allProps[att_str] = [];
7884
+ }
7885
+ allProps[att_str].push(el);
7886
+ //
7887
+ if (allProps[att_str].length === len) {
7888
+ globalAtts.push(att);
7889
+ }
7890
+ }
7891
+ }
7892
+
7893
+ // apply global to parent SVG
7894
+ if (globalAtts.length) {
7895
+ let atts0 = getElementAtts(el0);
7896
+ for (let att in atts0) {
7897
+ // &&
7898
+ if (globalAtts.includes(att) && att !== 'transform') {
7899
+ svg.setAttribute(att, atts0[att]);
7900
+ }
7901
+ }
7902
+ }
7903
+
7904
+ // detect groups
7905
+ for (let i = 1; i < len; i++) {
7906
+ let el = els[i];
7907
+ let styleArr = el.styleSet !== undefined ? [...el.styleSet] : [];
7908
+ let style = styleArr.length ? styleArr.join('_') : '';
7909
+
7910
+ // same style add to group
7911
+ if (style === stylePrev && elPrev.nextElementSibling === el) {
7912
+ groups[idx].push(el);
7913
+ }
7914
+ // start new group
7915
+ else {
7916
+ groups.push([el]);
7917
+ idx++;
7918
+ }
7919
+ // update style
7920
+ stylePrev = style;
7921
+ elPrev = el;
7922
+
7923
+ }// endof el loop
7924
+
7925
+ // create groups
7926
+ for (let i = 0; i < groups.length; i++) {
7927
+ let children = groups[i];
7928
+ let child0 = children[0];
7929
+ let atts = getElementAtts(child0);
7930
+ let groupEl = child0.parentNode.closest('g');
7931
+
7932
+ if (children.length === 1) {
7933
+ if (globalAtts.length) {
7934
+ let globalTransform = globalAtts.includes('transform');
7935
+ if (globalTransform) {
7936
+
7937
+ groupEl.setAttribute('transform', atts['transform']);
7938
+ }
7939
+
7940
+ for (let att in atts) {
7941
+
7942
+ if (globalAtts.includes(att)) {
7943
+ child0.removeAttribute(att);
7944
+ }
7945
+ }
7946
+ }
7947
+ continue
7948
+ }
7949
+
7950
+ // create new group
7951
+ if (!groupEl) {
7952
+
7953
+ groupEl = document.createElementNS(svgNs, 'g');
7954
+ child0.parentNode.insertBefore(groupEl, child0);
7955
+ groupEl.append(...children);
7956
+ }
7957
+
7958
+ // move attributes to group
7959
+ for (let att in atts) {
7960
+ let val = atts[att];
7961
+
7962
+ if (!geometryProps.includes(att)) {
7963
+ if (!globalAtts.includes(att) || att === 'transform') {
7964
+ groupEl.setAttribute(att, val);
7965
+ }
7966
+ children.forEach(child => {
7967
+ child.removeAttribute(att);
7968
+ });
7969
+ }
7970
+ }
7971
+
7972
+ } // endof groups
7973
+
7974
+ }
7975
+ }
7976
+
7977
+ }
7978
+
7644
7979
  // remove futile clip-paths
7645
7980
  if (cleanupClip) removeFutileClipPaths(svg, { x, y, width, height });
7646
7981
 
@@ -7786,21 +8121,41 @@
7786
8121
  remove = ['metadata', 'script'],
7787
8122
  removeNameSpaced = true,
7788
8123
  } = {}) {
8124
+
7789
8125
  let els = svg.querySelectorAll('*');
8126
+
8127
+ let allowMeta = !remove.includes('metadata');
8128
+
7790
8129
  els.forEach(el => {
7791
8130
  let nodeName = el.nodeName;
7792
- if ((removeNameSpaced && nodeName.includes(':')) ||
7793
- remove.includes(nodeName)
8131
+ let isMeta = allowMeta && el.closest('metadata');
8132
+ if (
8133
+ !isMeta &&
8134
+ ((removeNameSpaced && nodeName.includes(':')) ||
8135
+ remove.includes(nodeName))
7794
8136
  ) {
7795
8137
  el.remove();
7796
8138
  }
7797
8139
  });
7798
8140
  }
7799
8141
 
7800
- function removeExcludedAttribues(el, allowed = ['viewBox', 'xmlns', 'width', 'height', 'id', 'class']) {
8142
+ function removeExcludedAttribues(el, {
8143
+ allowed = ['viewBox', 'xmlns', 'width', 'height', 'id', 'class'],
8144
+ allowAriaAtts = true,
8145
+ allowDataAtts = true,
8146
+ allowMeta = false
8147
+ } = {}) {
7801
8148
  let atts = [...el.attributes].map((att) => att.name);
7802
8149
  atts.forEach((att) => {
7803
- if (!allowed.includes(att)) {
8150
+
8151
+ let isMeta = allowMeta && (att === 'title');
8152
+ let isAria = allowAriaAtts && att.startsWith('aria-');
8153
+ let isData = allowDataAtts && att.startsWith('data-');
8154
+
8155
+ if (
8156
+ !allowed.includes(att) &&
8157
+ !isAria && !isData && !isMeta
8158
+ ) {
7804
8159
  el.removeAttribute(att);
7805
8160
  }
7806
8161
  });
@@ -9843,6 +10198,7 @@
9843
10198
 
9844
10199
  toAbsolute = false,
9845
10200
  toRelative = true,
10201
+ toMixed = false,
9846
10202
  toShorthands = true,
9847
10203
  toLonghands = false,
9848
10204
 
@@ -9908,7 +10264,7 @@
9908
10264
  tolerance = 1,
9909
10265
  reversePath = false,
9910
10266
 
9911
- minifyRgbColors = false,
10267
+ minifyRgbColors = true,
9912
10268
  removePrologue = true,
9913
10269
  removeHidden = true,
9914
10270
  removeUnused = true,
@@ -9921,6 +10277,11 @@
9921
10277
  legacyHref = false,
9922
10278
  removeNameSpaced = true,
9923
10279
 
10280
+ allowMeta = false,
10281
+ allowDataAtts = true,
10282
+ allowAriaAtts = true,
10283
+
10284
+ attributesToGroup = false,
9924
10285
  removeOffCanvas = false,
9925
10286
  unGroup = false,
9926
10287
  mergePaths = false,
@@ -10021,7 +10382,7 @@
10021
10382
 
10022
10383
  // convert all shapes to paths
10023
10384
  if (shapesToPaths) {
10024
- shapeConvert = true;
10385
+ shapeConvert = 'toPaths';
10025
10386
  convert_rects = true;
10026
10387
  convert_ellipses = true;
10027
10388
  convert_poly = true;
@@ -10030,7 +10391,8 @@
10030
10391
 
10031
10392
  let svgPropObject = cleanUpSVG(input, {
10032
10393
  removeIds, removeClassNames, removeDimensions, cleanupSVGAtts, cleanUpStrokes, removeHidden, removeUnused, removeNameSpaced, stylesToAttributes, removePrologue, fixHref, mergePaths, convertTransforms, legacyHref, cleanupDefs, cleanupClip, addViewBox, removeOffCanvas, addDimensions,
10033
- shapeConvert, convert_rects, convert_ellipses, convert_poly, convert_lines, minifyRgbColors, unGroup, convertTransforms
10394
+ shapeConvert, convert_rects, convert_ellipses, convert_poly, convert_lines, minifyRgbColors, unGroup, convertTransforms,
10395
+ allowMeta, allowDataAtts, allowAriaAtts, allowMeta, attributesToGroup
10034
10396
  }
10035
10397
  );
10036
10398
  svg = svgPropObject.svg;
@@ -10055,6 +10417,7 @@
10055
10417
  // SVG optimization options
10056
10418
  let pathOptions = {
10057
10419
  toRelative,
10420
+ toMixed,
10058
10421
  toAbsolute,
10059
10422
  toLonghands,
10060
10423
  toShorthands,