svg-path-simplify 0.2.3 → 0.2.6

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.
@@ -0,0 +1,139 @@
1
+ import { getMatrix, parseCSSTransform } from './svg-styles-getTransforms';
2
+ import { attLookup } from './svg-styles-to-attributes-const';
3
+
4
+ export function svgStylesToAttributes(el, {
5
+ removeNameSpaced = true,
6
+ decimals = -1
7
+ } = {}) {
8
+
9
+ let nodeName = el.nodeName.toLowerCase();
10
+ let attProps = getElAttributes(el)
11
+ let cssProps = getElStyleProps(el)
12
+
13
+ // merge properties
14
+ let props = {
15
+ ...attProps,
16
+ ...cssProps
17
+ }
18
+
19
+ // filter out obsolete properties
20
+ let propsFiltered = {}
21
+
22
+ // parse CSS transforms
23
+ let cssTrans = cssProps['transform']
24
+
25
+ if (cssTrans) {
26
+ let transStr = `${cssTrans}`
27
+ let transformObj = parseCSSTransform(transStr)
28
+ let matrix = getMatrix(transformObj)
29
+
30
+ // apply as SVG matrix transform
31
+ props['transform'] = `matrix(${Object.values(matrix).join(',')})`
32
+ }
33
+
34
+
35
+ // can't be replaced with attributes
36
+ let cssOnlyProps = ['inline-size']
37
+ let styleProps = [];
38
+
39
+ for (let prop in props) {
40
+
41
+ let value = props[prop];
42
+
43
+ // CSS variable
44
+ if (value && prop.startsWith('--') || cssOnlyProps.includes(prop) ||
45
+ (!removeNameSpaced && prop.startsWith('-'))) {
46
+ styleProps.push(`${prop}:${value}`)
47
+ continue
48
+ }
49
+
50
+ // check if property is valid
51
+ if (value && attLookup.atts[prop] &&
52
+ (attLookup.atts[prop] === '*' ||
53
+ attLookup.atts[prop].includes(nodeName) ||
54
+ !removeNameSpaced && (prop.includes(':'))
55
+ )
56
+ ) {
57
+ propsFiltered[prop] = value
58
+ }
59
+
60
+ // remove property
61
+ el.removeAttribute(prop)
62
+
63
+ }
64
+
65
+ // apply filtered attributes
66
+ for (let prop in propsFiltered) {
67
+ let value = propsFiltered[prop]
68
+ el.setAttribute(prop, value)
69
+ }
70
+
71
+ if (styleProps.length) {
72
+ el.setAttribute('style', styleProps.join(';'));
73
+ }
74
+
75
+ //console.log('propsFiltered', propsFiltered);
76
+
77
+ return propsFiltered;
78
+
79
+ }
80
+
81
+
82
+ function parseInlineStyle(styleAtt = '') {
83
+
84
+ let props = {}
85
+ if (!styleAtt) return props;
86
+
87
+ let styleArr = styleAtt.split(';').filter(Boolean).map(prop => prop.trim());
88
+ let l = styleArr.length
89
+ if (!l) return props;
90
+
91
+ for (let i = 0; l && i < l; i++) {
92
+ let style = styleArr[i]
93
+ let [prop, value] = style.split(':').filter(Boolean)
94
+ props[prop] = value;
95
+ //props.push(`${prop}:${value}`)
96
+ }
97
+
98
+ return props
99
+ }
100
+
101
+ function getElStyleProps(el) {
102
+ let styleAtt = el.getAttribute('style')
103
+ let props = styleAtt ? parseInlineStyle(styleAtt) : {}
104
+ return props
105
+ }
106
+
107
+ function getElAttributes(el) {
108
+ let props = {}
109
+ let atts = [...el.attributes].map((att) => att.name);
110
+ let l = atts.length;
111
+ if (!l) return props;
112
+
113
+ for (let i = 0; i < l; i++) {
114
+ let att = atts[i];
115
+ let value = el.getAttribute(att);
116
+ props[att] = value
117
+ }
118
+
119
+ return props;
120
+ }
121
+
122
+
123
+ function getUnit(val) {
124
+ return val && isNaN(val) ? val.match(/[^\d|.]+/g)[0] : '';
125
+ }
126
+
127
+
128
+ function roundValue(value = '', decimals = -1) {
129
+ if (decimals < 0) return value;
130
+ value = value.replace(/["]/g, '').trim()
131
+ let valueNum = parseFloat(value);
132
+ let valueHasNumber = !isNaN(valueNum);
133
+ if (!valueHasNumber) return value;
134
+
135
+ let unit = valueHasNumber ? getUnit(value) : '';
136
+ if (valueHasNumber) value = `${valueNum.toFixed(decimals)}${unit}`;
137
+ //console.log('rounded', value)
138
+ return value;
139
+ }
@@ -1,3 +1,5 @@
1
+ import { parsePathDataString } from "./pathData_parse";
2
+ import { svgStylesToAttributes } from "./svg-styles-to-attributes";
1
3
 
2
4
 
3
5
  export function removeEmptySVGEls(svg) {
@@ -13,61 +15,177 @@ export function cleanUpSVG(svgMarkup, {
13
15
  returnDom = false,
14
16
  removeHidden = true,
15
17
  removeUnused = true,
18
+ stylesToAttributes = true,
19
+ removePrologue = true,
20
+ fixHref = true,
21
+ mergePaths = false,
22
+ cleanupSVGAtts = true,
23
+ removeNameSpaced = true,
24
+ attributesToGroup = true,
25
+ decimals = -1,
26
+ excludedEls = [],
16
27
  } = {}) {
17
28
 
18
29
  svgMarkup = cleanSvgPrologue(svgMarkup);
19
30
 
20
31
  // replace namespaced refs
21
- svgMarkup = svgMarkup.replaceAll("xlink:href=", "href=");
22
- //console.log('!!!svgMarkup', svgMarkup);
23
-
32
+ if (fixHref) svgMarkup = svgMarkup.replaceAll("xlink:href=", "href=");
24
33
 
25
34
  let svg = new DOMParser()
26
- //.parseFromString(svgMarkup, "image/svg+xml")
27
- .parseFromString(svgMarkup, "text/html")
28
- .querySelector("svg");
35
+ //.parseFromString(svgMarkup, "image/svg+xml")
36
+ .parseFromString(svgMarkup, "text/html")
37
+ .querySelector("svg");
29
38
  //console.log(svg);
30
39
 
31
40
 
32
- let allowed = ['viewBox', 'xmlns', 'width', 'height', 'id', 'class', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin'];
33
- removeExcludedAttribues(svg, allowed)
41
+ if (cleanupSVGAtts) {
42
+ let allowed = ['viewBox', 'xmlns', 'width', 'height', 'id', 'class', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin'];
43
+ removeExcludedAttribues(svg, allowed)
44
+
45
+ }
34
46
 
35
- let removeEls = ['metadata', 'script']
47
+ // always remove scripts
48
+ let removeEls = ['metadata', 'script', ...excludedEls]
36
49
 
37
50
  let els = svg.querySelectorAll('*')
51
+ let elProps = []
52
+
53
+ for (let i = 0; i < els.length; i++) {
54
+ let el = els[i];
55
+
56
+ let name = el.nodeName.toLowerCase();
38
57
 
39
- let textEls = svg.querySelectorAll('text')
40
- let remove = !textEls.length ? ['font-family', 'font-weight', 'font-style', 'font-size'] : [];
41
-
42
- els.forEach(el => {
43
- let name = el.nodeName;
44
58
  // remove hidden elements
45
59
  let style = el.getAttribute('style') || ''
46
60
  let isHiddenByStyle = style ? style.trim().includes('display:none') : false;
47
61
  let isHidden = (el.getAttribute('display') && el.getAttribute('display') === 'none') || isHiddenByStyle;
48
62
  if (name.includes(':') || removeEls.includes(name) || (removeHidden && isHidden)) {
49
63
  el.remove();
50
- } else {
51
- // remove BS elements
52
- removeNameSpaceAtts(el)
53
- removeAtts(el,remove)
64
+ continue;
54
65
  }
55
- })
56
66
 
57
- //console.log('svg', svg);
58
- //return
67
+ // styles to attributes
68
+ if (stylesToAttributes || attributesToGroup || mergePaths) {
69
+ let propsFiltered = svgStylesToAttributes(el, { removeNameSpaced, decimals })
70
+ if (name === 'path') {
71
+ elProps.push({ el, name, idx: i, propsFiltered })
72
+ }
73
+ }
74
+ }
59
75
 
60
- if (returnDom) return svg
76
+ // group styles
77
+ //console.log('elProps', elProps);
78
+ if (attributesToGroup || mergePaths) {
79
+ moveAttributesToGroup(elProps, mergePaths)
80
+ }
61
81
 
82
+ if (returnDom) return svg
62
83
  let markup = stringifySVG(svg)
63
84
  //console.log('!!!markup', markup);
64
85
 
65
- //return
86
+ return markup;
87
+ }
66
88
 
67
89
 
68
- return markup;
90
+ function moveAttributesToGroup(elProps = [], mergePaths = true) {
91
+
92
+ let combine = [[elProps[0]]]
93
+ let idx = 0;
94
+ let lastProps = '';
95
+ for (let i = 0; i < elProps.length; i++) {
96
+ let item = elProps[i];
97
+ let props = item.propsFiltered;
98
+ let propstr = [];
99
+ //let atts = []
100
+ for (let prop in props) {
101
+ if (prop !== 'd' && prop !== 'id') {
102
+ propstr.push(`${prop}:${props[prop]}`)
103
+ }
104
+ }
105
+ propstr = propstr.join('_')
106
+ item.propstr = propstr;
107
+
108
+ if (propstr === lastProps) {
109
+ combine[idx].push(item)
110
+ } else {
111
+ if (combine[idx].length) {
112
+ combine.push([])
113
+ idx++
114
+ }
115
+ }
116
+ lastProps = propstr;
117
+
118
+ }
119
+
120
+
121
+ // add att groups
122
+ for (let i = 0; i < combine.length; i++) {
123
+ let group = combine[i]
124
+
125
+ if (group.length > 1) {
126
+ // 1st el
127
+ let el0 = group[0].el;
128
+ let props = group[0].propsFiltered;
129
+ let g = el0.parentNode.closest('g') ? el0.parentNode.closest('g') : null;
130
+
131
+ // wrap in group if not existent
132
+ if (!g) {
133
+ g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
134
+ el0.parentNode.insertBefore(g, el0)
135
+ group.forEach(item => {
136
+ g.append(item.el)
137
+ })
138
+ }
139
+
140
+ let children = [...g.children]
141
+ for (let prop in props) {
142
+ if (prop !== 'd' && prop !== 'id') {
143
+ let value = props[prop]
144
+ // apply to parent group
145
+ g.setAttribute(prop, value)
146
+
147
+ // remove from children
148
+ children.forEach(el => {
149
+ if (el.getAttribute(prop) === value) {
150
+ el.removeAttribute(prop)
151
+ }
152
+ })
153
+ }
154
+
155
+
156
+ //console.log('mergePaths', mergePaths);
157
+
158
+ if (mergePaths) {
159
+ let path0 = group[0].el;
160
+ let dCombined = group[0].propsFiltered.d;
161
+
162
+ for (let i = 1; i < group.length; i++) {
163
+ let item = group[i]
164
+ let path = item.el;
165
+ let d = item.propsFiltered.d
166
+ let isAbs = d.startsWith('M')
167
+
168
+ let dAbs = isAbs ? d : parsePathDataString(d).pathData.map(com => `${com.type} ${com.values.join(' ')}`).join(' ')
169
+
170
+ //console.log(isAbs, dAbs);
171
+ // concat pathdata string
172
+ dCombined += dAbs;
173
+
174
+ // delete path el
175
+ path.remove();
176
+ }
177
+
178
+ path0.setAttribute('d', dCombined)
179
+
180
+ }
181
+
182
+ }
183
+ }
184
+ }
185
+
69
186
  }
70
187
 
188
+
71
189
  function cleanSvgPrologue(svgString) {
72
190
  return (
73
191
  svgString
@@ -92,17 +210,16 @@ function removeExcludedAttribues(el, allowed = ['viewBox', 'xmlns', 'width', 'he
92
210
  }
93
211
 
94
212
 
95
- function removeAtts(el, remove=[]) {
213
+ function removeAtts(el, exclude = [], include = []) {
96
214
  let atts = [...el.attributes].map((att) => att.name);
97
215
  atts.forEach((att) => {
98
- if (remove.includes(att)) {
216
+ if (exclude.includes(att) && !include.includes(att)) {
99
217
  el.removeAttribute(att);
100
218
  }
101
219
  });
102
220
  }
103
221
 
104
222
 
105
-
106
223
  function removeNameSpaceAtts(el) {
107
224
  let atts = [...el.attributes].map((att) => att.name);
108
225
  atts.forEach((att) => {
package/tests/testSVG.js CHANGED
@@ -7,6 +7,7 @@ import 'svg-path-simplify/node';
7
7
  import { svgPathSimplify } from 'svg-path-simplify';
8
8
 
9
9
 
10
+
10
11
  let svgMarkup =
11
12
  `<?xml version="1.0" encoding="utf-8"?>
12
13
  <!-- Generator: Super Adobe Illustrator 33.0.0 Turbo, SVG Export Plug-In . SVG Version: 123.00 Build 0) -->