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