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.
- package/dist/svg-path-simplify.esm.js +796 -46
- package/dist/svg-path-simplify.esm.min.js +6 -6
- package/dist/svg-path-simplify.js +796 -46
- package/dist/svg-path-simplify.min.js +6 -6
- package/dist/svg-path-simplify.min.js.gz +0 -0
- package/index.html +19 -3
- package/package.json +1 -1
- package/src/pathData_simplify_revertToquadratics.js +2 -2
- package/src/pathSimplify-main.js +10 -3
- package/src/svgii/pathData_convert.js +5 -5
- package/src/svgii/pathData_parse.js +6 -1
- package/src/svgii/svg-styles-getTransforms.js +273 -0
- package/src/svgii/svg-styles-to-attributes-const.js +341 -0
- package/src/svgii/svg-styles-to-attributes.js +139 -0
- package/src/svgii/svg_cleanup.js +144 -27
- package/tests/testSVG.js +1 -0
|
@@ -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
|
+
}
|
package/src/svgii/svg_cleanup.js
CHANGED
|
@@ -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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
//.parseFromString(svgMarkup, "image/svg+xml")
|
|
36
|
+
.parseFromString(svgMarkup, "text/html")
|
|
37
|
+
.querySelector("svg");
|
|
29
38
|
//console.log(svg);
|
|
30
39
|
|
|
31
40
|
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
51
|
-
// remove BS elements
|
|
52
|
-
removeNameSpaceAtts(el)
|
|
53
|
-
removeAtts(el,remove)
|
|
64
|
+
continue;
|
|
54
65
|
}
|
|
55
|
-
})
|
|
56
66
|
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
+
return markup;
|
|
87
|
+
}
|
|
66
88
|
|
|
67
89
|
|
|
68
|
-
|
|
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,
|
|
213
|
+
function removeAtts(el, exclude = [], include = []) {
|
|
96
214
|
let atts = [...el.attributes].map((att) => att.name);
|
|
97
215
|
atts.forEach((att) => {
|
|
98
|
-
if (
|
|
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) -->
|