svg-path-simplify 0.4.2 → 0.4.4
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/CHANGELOG.md +21 -0
- package/README.md +7 -4
- package/dist/svg-path-simplify.esm.js +3593 -1279
- package/dist/svg-path-simplify.esm.min.js +2 -2
- package/dist/svg-path-simplify.js +3594 -1278
- package/dist/svg-path-simplify.min.js +2 -2
- package/dist/svg-path-simplify.pathdata.esm.js +1017 -538
- package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
- package/dist/svg-path-simplify.poly.cjs +9 -8
- package/docs/privacy-webapp.md +24 -0
- package/index.html +331 -152
- package/package.json +1 -1
- package/src/constants.js +4 -0
- package/src/css_parse.js +317 -0
- package/src/detect_input.js +76 -28
- package/src/index.js +8 -0
- package/src/pathData_simplify_cubic.js +26 -16
- package/src/pathData_simplify_harmonize_cpts.js +77 -1
- package/src/pathData_simplify_revertToquadratics.js +0 -1
- package/src/pathSimplify-main.js +304 -276
- package/src/pathSimplify-only-pathdata.js +7 -2
- package/src/pathSimplify-presets.js +254 -0
- package/src/poly-fit-curve-schneider.js +14 -7
- package/src/simplify_poly_RC.js +102 -0
- package/src/simplify_poly_RDP.js +109 -1
- package/src/simplify_poly_radial_distance.js +3 -3
- package/src/string_helpers.js +130 -4
- package/src/svg-getAttributes.js +4 -2
- package/src/svgii/convert_units.js +1 -1
- package/src/svgii/geometry.js +322 -5
- package/src/svgii/geometry_bbox_element.js +1 -1
- package/src/svgii/geometry_deduceRadius.js +116 -27
- package/src/svgii/geometry_length.js +253 -0
- package/src/svgii/pathData_analyze.js +18 -0
- package/src/svgii/pathData_convert.js +193 -89
- package/src/svgii/pathData_fix_directions.js +12 -14
- package/src/svgii/pathData_fromPoly.js +3 -3
- package/src/svgii/pathData_getLength.js +86 -0
- package/src/svgii/pathData_parse.js +2 -0
- package/src/svgii/pathData_parse_els.js +66 -68
- package/src/svgii/pathData_reorder.js +122 -16
- package/src/svgii/pathData_simplify_refineCorners.js +130 -35
- package/src/svgii/pathData_simplify_refine_round.js +420 -0
- package/src/svgii/pathData_split_to_groups.js +168 -0
- package/src/svgii/pathData_stringify.js +26 -64
- package/src/svgii/pathData_toPolygon.js +3 -4
- package/src/svgii/poly_analyze.js +61 -0
- package/src/svgii/poly_normalize.js +11 -2
- package/src/svgii/poly_to_pathdata.js +85 -24
- package/src/svgii/rounding.js +80 -78
- package/src/svgii/svg_cleanup.js +421 -619
- package/src/svgii/svg_cleanup_convertPathLength.js +39 -0
- package/src/svgii/svg_cleanup_general_svg_atts.js +97 -0
- package/src/svgii/svg_cleanup_normalize_transforms.js +83 -0
- package/src/svgii/svg_cleanup_remove_els_and_atts.js +77 -0
- package/src/svgii/svg_cleanup_ungroup.js +36 -0
- package/src/svgii/svg_el_parse_style_props.js +72 -47
- package/src/svgii/svg_getElementLength.js +67 -0
- package/src/svgii/svg_validate.js +220 -0
- package/tests/testSVG.js +14 -1
- package/src/svgii/pathData_refine_round.js +0 -222
package/src/svgii/svg_cleanup.js
CHANGED
|
@@ -11,21 +11,18 @@ import { pathElToShape, shapeElToPath } from "./pathData_parse_els";
|
|
|
11
11
|
//import { scaleProps } from "./svg-styles-to-attributes";
|
|
12
12
|
import { geometryEls, geometryProps, renderedEls, shapeEls, strokeAtts } from "./svg-styles-to-attributes-const";
|
|
13
13
|
import { addTransFormProps, filterSvgElProps, parseStylesProperties } from "./svg_el_parse_style_props";
|
|
14
|
-
import { autoRound } from "./rounding";
|
|
14
|
+
import { autoRound, roundTo } from "./rounding";
|
|
15
15
|
import { getMatrixFromTransform } from "./svg-styles-getTransforms";
|
|
16
16
|
import { qrDecomposeMatrix } from "./transform_qr_decompose";
|
|
17
17
|
import { svgNs } from "../constants";
|
|
18
18
|
import { toCamelCase, toShortStr } from "../string_helpers";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
//const DOMParserPoly = globalThis.DOMParser;
|
|
19
|
+
import { getElementLength } from "./svg_getElementLength";
|
|
20
|
+
import { removeAtts, removeHiddenSvgEls, removeSvgAtts, removeSvgChildAtts, removeSvgEls } from "./svg_cleanup_remove_els_and_atts";
|
|
21
|
+
import { cleanupSVGAttributes, removeElAtts } from "./svg_cleanup_general_svg_atts";
|
|
22
|
+
import { convertPathLengthAtt } from "./svg_cleanup_convertPathLength";
|
|
23
|
+
import { removeGroupProps, ungroupElements } from "./svg_cleanup_ungroup";
|
|
24
|
+
import { parseSvgCss } from "../css_parse";
|
|
25
|
+
import { setNormalizedTransformsToEl } from "./svg_cleanup_normalize_transforms";
|
|
29
26
|
|
|
30
27
|
|
|
31
28
|
export function cleanUpSVG(svgMarkup, {
|
|
@@ -56,6 +53,11 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
56
53
|
|
|
57
54
|
cleanupSVGAtts = true,
|
|
58
55
|
removeNameSpaced = true,
|
|
56
|
+
removeNameSpacedAtts = true,
|
|
57
|
+
|
|
58
|
+
// unit conversions
|
|
59
|
+
convertPathLength = false,
|
|
60
|
+
toAbsoluteUnits = false,
|
|
59
61
|
|
|
60
62
|
// meta
|
|
61
63
|
allowMeta = false,
|
|
@@ -64,10 +66,14 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
64
66
|
|
|
65
67
|
//shapesToPaths = false,
|
|
66
68
|
shapeConvert = false,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
convertShapes = [],
|
|
70
|
+
|
|
71
|
+
// remove elements
|
|
72
|
+
removeElements = [],
|
|
73
|
+
|
|
74
|
+
// remove attributes
|
|
75
|
+
removeSVGAttributes = [],
|
|
76
|
+
removeElAttributes = [],
|
|
71
77
|
|
|
72
78
|
convertTransforms = false,
|
|
73
79
|
removeDefaults = true,
|
|
@@ -76,8 +82,13 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
76
82
|
excludedEls = [],
|
|
77
83
|
} = {}) {
|
|
78
84
|
|
|
79
|
-
|
|
80
|
-
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
// resolve dependencies
|
|
88
|
+
if (unGroup || convertTransforms || minifyRgbColors || attributesToGroup)
|
|
89
|
+
stylesToAttributes = true;
|
|
90
|
+
|
|
91
|
+
if (stylesToAttributes) cleanUpStrokes = true;
|
|
81
92
|
|
|
82
93
|
// replace namespaced refs
|
|
83
94
|
if (fixHref) svgMarkup = svgMarkup.replaceAll("xlink:href=", "href=");
|
|
@@ -87,17 +98,39 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
87
98
|
.parseFromString(svgMarkup, "text/html")
|
|
88
99
|
.querySelector("svg");
|
|
89
100
|
|
|
101
|
+
|
|
90
102
|
let viewBox = getViewBox(svg)
|
|
91
103
|
let { x, y, width, height } = viewBox;
|
|
104
|
+
let remove = []
|
|
92
105
|
|
|
93
106
|
|
|
94
|
-
|
|
107
|
+
|
|
108
|
+
// add viewBox
|
|
109
|
+
if (addViewBox) addSvgViewBox(svg, { x, y, width, height })
|
|
110
|
+
if (addDimensions) {
|
|
111
|
+
svg.setAttribute('width', width);
|
|
112
|
+
svg.setAttribute('height', height);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// remove unused defs or optimize order
|
|
116
|
+
if (cleanupDefs) cleanupSvgDefs(svg, { x, y, width, height, cleanupClip });
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
// remove off canvas
|
|
120
|
+
if (removeOffCanvas) removeOffCanvasEls(svg, { x, y, width, height });
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* collect svg styles
|
|
125
|
+
* and properties
|
|
126
|
+
*/
|
|
95
127
|
let propOptions = {
|
|
96
|
-
width
|
|
97
|
-
height
|
|
128
|
+
width,
|
|
129
|
+
height,
|
|
98
130
|
normalizeTransforms,
|
|
99
131
|
removeDefaults: false,
|
|
100
132
|
cleanUpStrokes: false,
|
|
133
|
+
//cleanUpStrokes,
|
|
101
134
|
allowMeta,
|
|
102
135
|
allowDataAtts,
|
|
103
136
|
allowAriaAtts,
|
|
@@ -105,18 +138,54 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
105
138
|
removeIds,
|
|
106
139
|
removeClassNames,
|
|
107
140
|
minifyRgbColors,
|
|
141
|
+
stylesheetProps: {},
|
|
142
|
+
exclude: []
|
|
108
143
|
}
|
|
109
144
|
|
|
110
|
-
// root svg properties
|
|
145
|
+
// root svg inline style properties
|
|
111
146
|
let stylePropsSVG = parseStylesProperties(svg, propOptions)
|
|
112
147
|
//console.log('stylePropsSVG', stylePropsSVG);
|
|
113
148
|
|
|
149
|
+
let styleEl = svg.querySelector('style')
|
|
150
|
+
let cssStylePropsSVG = {}
|
|
151
|
+
|
|
152
|
+
if (styleEl) {
|
|
153
|
+
cssStylePropsSVG = parseSvgCss(styleEl, { parent: svg })
|
|
154
|
+
|
|
155
|
+
//save stylesheet dependencies in node
|
|
156
|
+
for (let selector in cssStylePropsSVG) {
|
|
157
|
+
let els = svg.querySelectorAll(`${selector}`);
|
|
158
|
+
els.forEach(el => {
|
|
159
|
+
if (!el['cssRules']) el['cssRules'] = [];
|
|
160
|
+
el['cssRules'].push(selector)
|
|
161
|
+
|
|
162
|
+
// remove class names only used for styling
|
|
163
|
+
if (stylesToAttributes) {
|
|
164
|
+
let className = selector.substring(1)
|
|
165
|
+
el.classList.remove(className)
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
//console.log('cssStylePropsSVG', cssStylePropsSVG);
|
|
171
|
+
|
|
172
|
+
// remove style element from element
|
|
173
|
+
if (stylesToAttributes) {
|
|
174
|
+
styleEl.remove()
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// remove style element from root SVG
|
|
178
|
+
if (stylesToAttributes) svg.removeAttribute('style')
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
// add stylesheet props
|
|
182
|
+
propOptions.stylesheetProps = cssStylePropsSVG;
|
|
183
|
+
|
|
114
184
|
|
|
115
185
|
// add svg font size for scaling relative
|
|
116
186
|
propOptions.fontSize = stylePropsSVG['font-size'] ? stylePropsSVG['font-size'][0] : 16;
|
|
117
187
|
|
|
118
188
|
|
|
119
|
-
|
|
120
189
|
/**
|
|
121
190
|
* get group styles
|
|
122
191
|
* especially transformations to
|
|
@@ -126,7 +195,10 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
126
195
|
let groupProps = [];
|
|
127
196
|
|
|
128
197
|
groups.forEach(g => {
|
|
198
|
+
//propOptions.exclude.push('class', 'id');
|
|
129
199
|
let stylePropsG = parseStylesProperties(g, propOptions)
|
|
200
|
+
//console.log('stylePropsG', stylePropsG);
|
|
201
|
+
|
|
130
202
|
groupProps.push(stylePropsG);
|
|
131
203
|
let children = g.querySelectorAll(`${renderedEls.join(', ')}`)
|
|
132
204
|
|
|
@@ -141,85 +213,36 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
141
213
|
|
|
142
214
|
|
|
143
215
|
|
|
144
|
-
|
|
145
|
-
//console.log('cleanupSVGAtts');
|
|
146
|
-
let allowed = new Set(['viewBox', 'xmlns', 'width', 'height']);
|
|
147
|
-
|
|
148
|
-
if (!removeIds) allowed.add('id')
|
|
149
|
-
if (!removeClassNames) allowed.add('class')
|
|
150
|
-
if (removeDimensions) {
|
|
151
|
-
allowed.delete('width')
|
|
152
|
-
allowed.delete('height')
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
allowed = Array.from(allowed)
|
|
156
|
-
if (!stylesToAttributes) {
|
|
157
|
-
allowed.push('fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'font-size', 'font-family', 'font-style', 'style');
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
//console.log(allowed);
|
|
161
|
-
//if(allow)
|
|
162
|
-
removeExcludedAttribues(svg, { allowed, allowMeta, allowAriaAtts, allowDataAtts })
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// remove meta
|
|
166
|
-
if (!allowMeta) {
|
|
167
|
-
let metaEls = svg.querySelectorAll('meta, metadata, desc, title')
|
|
168
|
-
metaEls.forEach(meta => {
|
|
169
|
-
meta.remove()
|
|
170
|
-
})
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// add viewBox
|
|
174
|
-
if (addViewBox) addSvgViewBox(svg, { x, y, width, height })
|
|
175
|
-
if (addDimensions) {
|
|
176
|
-
svg.setAttribute('width', width);
|
|
177
|
-
svg.setAttribute('height', height);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
// remove unused defs or optimize order
|
|
182
|
-
if (cleanupDefs) cleanupSvgDefs(svg, { x, y, width, height, cleanupClip });
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
// remove off canvas
|
|
186
|
-
if (removeOffCanvas) removeOffCanvasEls(svg, { x, y, width, height });
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
// always remove scripts
|
|
190
|
-
//let removeEls = ['metadata', 'script', ...excludedEls]
|
|
191
|
-
let removeEls = ['script', ...excludedEls]
|
|
192
|
-
|
|
193
|
-
removeSVGEls(svg, { remove: removeEls, removeNameSpaced });
|
|
194
|
-
|
|
195
|
-
// an array of all elements' properties
|
|
216
|
+
// collect all elements' properties
|
|
196
217
|
let svgElProps = []
|
|
197
218
|
let els = svg.querySelectorAll(`${renderedEls.join(', ')}`)
|
|
198
219
|
|
|
199
220
|
|
|
221
|
+
/**
|
|
222
|
+
* loop all geometry elements
|
|
223
|
+
*/
|
|
200
224
|
for (let i = 0; i < els.length; i++) {
|
|
201
225
|
let el = els[i];
|
|
202
226
|
|
|
203
227
|
let name = el.nodeName.toLowerCase();
|
|
204
228
|
//console.log(name);
|
|
205
229
|
|
|
206
|
-
// 1. remove hidden elements
|
|
207
|
-
let style = el.getAttribute('style') || ''
|
|
208
|
-
let isHiddenByStyle = style ? style.trim().includes('display:none') : false;
|
|
209
|
-
let isHidden = (el.getAttribute('display') && el.getAttribute('display') === 'none') || isHiddenByStyle;
|
|
210
|
-
if (name.includes(':') || removeEls.includes(name) || (removeHidden && isHidden)) {
|
|
211
|
-
el.remove();
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
|
|
216
230
|
/**
|
|
217
|
-
* get all style properties
|
|
231
|
+
* get all element style properties
|
|
218
232
|
* convert relative or physical units
|
|
219
233
|
* to user units
|
|
220
234
|
*/
|
|
221
235
|
let styleProps = parseStylesProperties(el, propOptions)
|
|
222
236
|
let stylePropsFiltered = {}
|
|
237
|
+
//console.log('styleProps', name, styleProps);
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
// convert pathLength before transforming
|
|
241
|
+
if (convertPathLength) {
|
|
242
|
+
styleProps = convertPathLengthAtt(el, { styleProps });
|
|
243
|
+
remove = [...new Set([...remove, ...styleProps.remove])];
|
|
244
|
+
}
|
|
245
|
+
|
|
223
246
|
|
|
224
247
|
// get parent styles
|
|
225
248
|
let { parentStyleProps = [] } = el;
|
|
@@ -227,7 +250,10 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
227
250
|
let transFormInherited = []
|
|
228
251
|
|
|
229
252
|
|
|
230
|
-
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* consolidate all properties:
|
|
256
|
+
* merge with inherited transforms
|
|
231
257
|
* and styles from group
|
|
232
258
|
*/
|
|
233
259
|
parentStyleProps.forEach(props => {
|
|
@@ -243,178 +269,170 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
243
269
|
})
|
|
244
270
|
|
|
245
271
|
|
|
246
|
-
//merge transforms
|
|
272
|
+
// merge all transforms
|
|
247
273
|
transFormInherited = [...transFormInherited, ...styleProps.transformArr]
|
|
248
274
|
styleProps.transformArr = transFormInherited
|
|
249
275
|
|
|
250
276
|
|
|
251
|
-
//
|
|
277
|
+
// don't inherit class from SVG
|
|
278
|
+
if (stylePropsSVG['class']) delete stylePropsSVG['class']
|
|
279
|
+
if (stylePropsSVG['id']) delete stylePropsSVG['id']
|
|
252
280
|
|
|
253
|
-
|
|
281
|
+
// add svg props
|
|
282
|
+
inheritedProps = {
|
|
254
283
|
...stylePropsSVG,
|
|
255
284
|
...inheritedProps,
|
|
256
|
-
|
|
257
|
-
}
|
|
285
|
+
};
|
|
258
286
|
|
|
287
|
+
//console.log('inheritedProps', inheritedProps);
|
|
259
288
|
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
289
|
+
// merge with svg props
|
|
290
|
+
styleProps = {
|
|
291
|
+
...inheritedProps,
|
|
292
|
+
...styleProps
|
|
263
293
|
}
|
|
264
294
|
|
|
295
|
+
|
|
265
296
|
// add combined transforms
|
|
266
297
|
addTransFormProps(styleProps, transFormInherited);
|
|
267
298
|
//console.log('transFormInherited', transFormInherited);
|
|
268
299
|
//console.log('styleProps', styleProps);
|
|
300
|
+
remove = [...new Set([...remove, ...styleProps.remove])];
|
|
269
301
|
|
|
270
302
|
|
|
271
|
-
|
|
303
|
+
/**
|
|
304
|
+
* remove els and attributes
|
|
305
|
+
*/
|
|
272
306
|
|
|
273
|
-
//
|
|
274
|
-
if (
|
|
307
|
+
// remove meta
|
|
308
|
+
if (!allowMeta) removeElements.push('meta', 'metadata', 'desc', 'title')
|
|
275
309
|
|
|
276
|
-
if (
|
|
310
|
+
if (removeClassNames) {
|
|
311
|
+
removeSVGAttributes.push('class');
|
|
312
|
+
removeElAttributes.push('class');
|
|
313
|
+
}
|
|
277
314
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
let { rotate, scaleX, scaleY, skewX, translateX, translateY } = transComponents;
|
|
283
|
-
//console.log(rotate, scaleX, scaleY, skewX, skewY, translateX, translateY);
|
|
315
|
+
if (removeIds) {
|
|
316
|
+
removeSVGAttributes.push('id')
|
|
317
|
+
removeElAttributes.push('id')
|
|
318
|
+
}
|
|
284
319
|
|
|
285
|
-
// scale attributes instead of transform
|
|
286
|
-
let hasRot = rotate !== 0 || skewX !== 0;
|
|
287
|
-
let unProportional = scaleX !== scaleY;
|
|
288
|
-
let scalableByAtt = ['circle', 'ellipse', 'rect']
|
|
289
|
-
let needsTrans = convertTransforms || (name === 'g') || (hasRot) || unProportional
|
|
290
320
|
|
|
291
|
-
|
|
321
|
+
// remove hidden elements
|
|
322
|
+
removeHiddenSvgEls(svg)
|
|
292
323
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
styleProps.cy[0] = [styleProps.cy[0] * scaleX + translateY]
|
|
324
|
+
// remove SVG elements
|
|
325
|
+
removeSvgEls(svg, { removeElements, removeNameSpaced });
|
|
296
326
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (styleProps.ry) styleProps.ry[0] = [styleProps.ry[0] * scaleX]
|
|
327
|
+
// remove SVG attributes
|
|
328
|
+
removeSvgAtts(svg, removeSVGAttributes);
|
|
300
329
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
let x = styleProps.x ? styleProps.x[0] + translateX : translateX;
|
|
304
|
-
let y = styleProps.y ? styleProps.y[0] + translateY : translateY;
|
|
330
|
+
// remove SVG child element attributes
|
|
331
|
+
removeSvgChildAtts(svg, removeElAttributes);
|
|
305
332
|
|
|
306
|
-
let rx = styleProps.rx ? styleProps.rx[0] * scaleX : 0;
|
|
307
|
-
let ry = styleProps.ry ? styleProps.ry[0] * scaleY : 0;
|
|
308
333
|
|
|
309
|
-
|
|
310
|
-
|
|
334
|
+
// general cleanup
|
|
335
|
+
if (cleanupSVGAtts) cleanupSVGAttributes(svg, { removeIds, removeClassNames, removeDimensions, stylesToAttributes, allowMeta, allowAriaAtts, allowDataAtts });
|
|
311
336
|
|
|
312
|
-
|
|
313
|
-
|
|
337
|
+
// all relative units to absolute
|
|
338
|
+
if (toAbsoluteUnits) {
|
|
339
|
+
normalizeTransforms = true;
|
|
340
|
+
//stylesToAttributes = true
|
|
341
|
+
//console.log(name, styleProps);
|
|
314
342
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
343
|
+
/**
|
|
344
|
+
* apply consolidated
|
|
345
|
+
* element attributes
|
|
346
|
+
* remove non-supported element props
|
|
347
|
+
*/
|
|
348
|
+
stylePropsFiltered = filterSvgElProps(name, styleProps,
|
|
349
|
+
{ removeDefaults: true, cleanUpStrokes, allowMeta, allowAriaAtts, allowDataAtts, removeIds, inheritedProps });
|
|
318
350
|
|
|
319
|
-
// remove now obsolete transform properties
|
|
320
|
-
delete styleProps.matrix
|
|
321
|
-
delete styleProps.transformArr
|
|
322
|
-
delete styleProps.transComponents
|
|
323
351
|
|
|
324
|
-
|
|
325
|
-
|
|
352
|
+
for (let prop in stylePropsFiltered.propsFiltered) {
|
|
353
|
+
let values = styleProps[prop]
|
|
354
|
+
let val = values.length ? values.join(' ') : values[0]
|
|
355
|
+
el.setAttribute(prop, val)
|
|
356
|
+
}
|
|
326
357
|
|
|
327
|
-
|
|
328
|
-
|
|
358
|
+
//console.log('inheritedProps', inheritedProps);
|
|
359
|
+
//console.log('current props', stylePropsFiltered.propsFiltered);
|
|
329
360
|
|
|
330
|
-
|
|
331
|
-
el.setAttribute('transform', transComponents.matrixAtt)
|
|
361
|
+
let removeAttsEl = [...new Set([...remove, ...stylePropsFiltered.remove])];
|
|
332
362
|
|
|
363
|
+
// check if same value is in inherited
|
|
364
|
+
for (let prop in stylePropsFiltered.propsFiltered) {
|
|
365
|
+
let valInh = inheritedProps[prop] || [];
|
|
366
|
+
let val = stylePropsFiltered.propsFiltered[prop] || [];
|
|
367
|
+
if (valInh.join() === val.join()) {
|
|
368
|
+
removeAttsEl.push(prop)
|
|
333
369
|
}
|
|
334
370
|
}
|
|
335
371
|
|
|
372
|
+
// remove obsolete/inherited
|
|
373
|
+
removeAtts(el, removeAttsEl)
|
|
374
|
+
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
if (stylesToAttributes) {
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* normalize transforms
|
|
383
|
+
*/
|
|
384
|
+
if (normalizeTransforms) {
|
|
385
|
+
styleProps = setNormalizedTransformsToEl(el, { styleProps });
|
|
386
|
+
//remove = styleProps.remove;
|
|
387
|
+
remove = [...new Set([...remove, ...styleProps.remove])];
|
|
388
|
+
|
|
389
|
+
}
|
|
336
390
|
|
|
337
391
|
/**
|
|
338
392
|
* apply consolidated
|
|
339
393
|
* element attributes
|
|
394
|
+
* remove non-supported element props
|
|
340
395
|
*/
|
|
341
396
|
stylePropsFiltered = filterSvgElProps(name, styleProps,
|
|
342
|
-
{ removeDefaults: true, cleanUpStrokes, allowMeta, allowAriaAtts, allowDataAtts, removeIds });
|
|
397
|
+
{ removeDefaults: true, cleanUpStrokes, allowMeta, allowAriaAtts, allowDataAtts, removeIds, inheritedProps });
|
|
343
398
|
|
|
344
|
-
remove = [...remove, ...stylePropsFiltered.remove];
|
|
399
|
+
remove = [...new Set([...remove, ...stylePropsFiltered.remove])];
|
|
345
400
|
|
|
346
401
|
for (let prop in stylePropsFiltered.propsFiltered) {
|
|
347
402
|
let values = styleProps[prop]
|
|
348
403
|
let val = values.length ? values.join(' ') : values[0]
|
|
349
404
|
el.setAttribute(prop, val)
|
|
350
|
-
|
|
351
405
|
}
|
|
352
406
|
|
|
353
407
|
|
|
354
|
-
// remove obsolete attributes
|
|
355
|
-
for (let i = 0; i < remove.length; i++) {
|
|
356
|
-
let att = remove[i];
|
|
357
|
-
if (!stylesToAttributes && att === 'style') continue
|
|
358
|
-
//console.log('remove att', att, name);
|
|
359
|
-
el.removeAttribute(att)
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
408
|
/**
|
|
366
|
-
* remove
|
|
367
|
-
*
|
|
368
|
-
* or remove nesting
|
|
409
|
+
* remove obsolete
|
|
410
|
+
* attributes
|
|
369
411
|
*/
|
|
412
|
+
removeAtts(el, remove)
|
|
370
413
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
children.forEach(child => {
|
|
376
|
-
g.parentNode.insertBefore(child, g)
|
|
377
|
-
})
|
|
378
|
-
g.remove()
|
|
379
|
-
})
|
|
380
|
-
} else {
|
|
381
|
-
groups.forEach((g, i) => {
|
|
382
|
-
//let atts = [...Object.keys(groupProps[i]), 'style', 'transform', 'id'];
|
|
383
|
-
let atts = Object.keys(getElementAtts(g));
|
|
384
|
-
|
|
385
|
-
atts.forEach(att => {
|
|
386
|
-
//console.log('remove', remove);
|
|
387
|
-
let isData = !allowDataAtts && att.startsWith('data-')
|
|
388
|
-
let isAria = !allowAriaAtts && att.startsWith('aria-')
|
|
389
|
-
//console.log(isData, att);
|
|
390
|
-
remove.push('transform', 'style')
|
|
391
|
-
|
|
392
|
-
if (remove.includes(att) || isData || isAria) {
|
|
393
|
-
g.removeAttribute(att)
|
|
394
|
-
}
|
|
395
|
-
})
|
|
396
|
-
})
|
|
397
|
-
|
|
414
|
+
/*
|
|
415
|
+
for (let i = 0; i < remove.length; i++) {
|
|
416
|
+
let att = remove[i];
|
|
417
|
+
el.removeAttribute(att)
|
|
398
418
|
}
|
|
419
|
+
*/
|
|
399
420
|
|
|
400
421
|
|
|
401
422
|
} // endof style processing
|
|
402
423
|
|
|
403
424
|
|
|
425
|
+
|
|
404
426
|
/**
|
|
405
427
|
* element conversions:
|
|
406
428
|
* shapes to paths or
|
|
407
429
|
* paths to shapes
|
|
408
430
|
*/
|
|
409
431
|
|
|
410
|
-
|
|
411
432
|
// force shape conversion when transform conversion is enabled
|
|
412
433
|
if (convertTransforms) {
|
|
413
434
|
shapeConvert = 'toPaths';
|
|
414
|
-
|
|
415
|
-
convert_ellipses = true;
|
|
416
|
-
convert_poly = true;
|
|
417
|
-
convert_lines = true;
|
|
435
|
+
convertShapes = ['path', 'rect', 'ellipse', 'circle', 'line', 'polygon', 'polyline'];
|
|
418
436
|
}
|
|
419
437
|
|
|
420
438
|
// convert shapes to paths
|
|
@@ -422,8 +440,8 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
422
440
|
|
|
423
441
|
let { matrix = null, transComponents = null } = styleProps;
|
|
424
442
|
|
|
443
|
+
// scale props like stroke width or dash-array before conversion
|
|
425
444
|
if (matrix && transComponents) {
|
|
426
|
-
// scale props like stroke width or dash-array before conversion
|
|
427
445
|
['stroke-width', 'stroke-dasharray'].forEach(att => {
|
|
428
446
|
let attVal = el.getAttribute(att)
|
|
429
447
|
let vals = attVal ? attVal.split(' ').filter(Boolean).map(Number).map(val => val * transComponents.scaleX) : []
|
|
@@ -433,10 +451,9 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
433
451
|
|
|
434
452
|
// convert paths only if a matrix transform is required
|
|
435
453
|
if (matrix ? geometryEls.includes(name) : shapeEls.includes(name)) {
|
|
436
|
-
|
|
437
|
-
let path = shapeElToPath(el, { width, height,
|
|
454
|
+
//console.log('detrans', name, el.id, matrix);
|
|
455
|
+
let path = shapeElToPath(el, { width, height, convertShapes, matrix });
|
|
438
456
|
el.replaceWith(path)
|
|
439
|
-
|
|
440
457
|
name = 'path'
|
|
441
458
|
el = path; // required for node
|
|
442
459
|
|
|
@@ -444,15 +461,19 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
444
461
|
|
|
445
462
|
}
|
|
446
463
|
|
|
447
|
-
|
|
464
|
+
/**
|
|
465
|
+
* Reverse conversion:
|
|
466
|
+
* paths to shapes
|
|
467
|
+
*/
|
|
448
468
|
else if (shapeConvert === 'toShapes') {
|
|
449
469
|
let paths = svg.querySelectorAll('path')
|
|
450
470
|
paths.forEach(path => {
|
|
451
|
-
let shape = pathElToShape(path, {
|
|
452
|
-
|
|
471
|
+
let shape = pathElToShape(path, { convertShapes })
|
|
472
|
+
path.replaceWith(shape)
|
|
453
473
|
path = shape;
|
|
474
|
+
//name = shape.nodeName.toLowerCase()
|
|
475
|
+
//console.log('shape', shape);
|
|
454
476
|
})
|
|
455
|
-
|
|
456
477
|
}
|
|
457
478
|
|
|
458
479
|
|
|
@@ -460,7 +481,6 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
460
481
|
* combine styles
|
|
461
482
|
* store in node property
|
|
462
483
|
*/
|
|
463
|
-
|
|
464
484
|
if (mergePaths || attributesToGroup) {
|
|
465
485
|
|
|
466
486
|
let options = { allowMeta, allowAriaAtts, removeIds, removeClassNames, allowDataAtts }
|
|
@@ -470,7 +490,7 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
470
490
|
* adjacent path merging
|
|
471
491
|
* e.g ignore classnames or ids
|
|
472
492
|
*/
|
|
473
|
-
if(mergePaths){
|
|
493
|
+
if (mergePaths) {
|
|
474
494
|
options.removeIds = true;
|
|
475
495
|
options.removeClassNames = true;
|
|
476
496
|
options.allowAriaAtts = false;
|
|
@@ -486,226 +506,253 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
486
506
|
let values = stylePropsFiltered[prop]
|
|
487
507
|
let val = values.length ? values.join(' ') : values[0]
|
|
488
508
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
509
|
+
if (prop !== 'class' && prop !== 'id') {
|
|
510
|
+
|
|
511
|
+
let propShort = toShortStr(prop)
|
|
512
|
+
let valShort = toShortStr(val)
|
|
513
|
+
let propStr = `${propShort}-${valShort}`;
|
|
492
514
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
515
|
+
// store in node property
|
|
516
|
+
if (!el.styleSet) el.styleSet = new Set()
|
|
517
|
+
if (propStr) el.styleSet.add(propStr)
|
|
518
|
+
}
|
|
496
519
|
}
|
|
497
520
|
|
|
498
521
|
}
|
|
499
522
|
|
|
500
|
-
|
|
501
523
|
}//endof element loop
|
|
502
524
|
|
|
503
525
|
|
|
526
|
+
/**
|
|
527
|
+
* remove group styles
|
|
528
|
+
* copied to children
|
|
529
|
+
* or remove nesting
|
|
530
|
+
*/
|
|
531
|
+
|
|
532
|
+
if (unGroup) {
|
|
533
|
+
ungroupElements(groups)
|
|
534
|
+
} else {
|
|
535
|
+
|
|
536
|
+
if (stylesToAttributes) {
|
|
537
|
+
groups.forEach(g => {
|
|
538
|
+
removeElAtts(g, ['style', 'transform']);
|
|
539
|
+
})
|
|
540
|
+
}
|
|
541
|
+
//removeGroupProps(groups, { remove, allowDataAtts, allowAriaAtts })
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
// styles to group
|
|
546
|
+
if (attributesToGroup) sharedAttributesToGroup(svg);
|
|
504
547
|
|
|
505
548
|
/**
|
|
506
549
|
* merge paths with same styles
|
|
507
550
|
*/
|
|
508
551
|
if (mergePaths) {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
if (len) {
|
|
513
|
-
let path0 = paths[0]
|
|
514
|
-
let d0 = path0.getAttribute('d')
|
|
515
|
-
let stylePrev = path0.styleSet !== undefined ? [...path0.styleSet].join('_') : ''
|
|
516
|
-
|
|
517
|
-
for (let i = 1; i < len; i++) {
|
|
518
|
-
let path = paths[i];
|
|
519
|
-
let style = path.styleSet !== undefined ? [...path.styleSet].join('_') : ''
|
|
520
|
-
let isSibling = path.previousElementSibling === path0;
|
|
521
|
-
let d = path.getAttribute('d');
|
|
522
|
-
let isAbs = d.startsWith('M')
|
|
523
|
-
|
|
524
|
-
if (isSibling && style === stylePrev) {
|
|
525
|
-
let dAbs = isAbs ? d : parsePathDataString(d).pathData.map(com => `${com.type} ${com.values.join(' ')}`).join(' ')
|
|
526
|
-
//console.log('same style', dAbs, isAbs);
|
|
527
|
-
d0 += dAbs;
|
|
528
|
-
path0.setAttribute('d', d0)
|
|
529
|
-
path.remove();
|
|
530
|
-
|
|
531
|
-
} else {
|
|
532
|
-
path0 = path
|
|
533
|
-
d0 = isAbs ? d : parsePathDataString(d).pathData.map(com => `${com.type} ${com.values.join(' ')}`).join(' ')
|
|
534
|
-
}
|
|
552
|
+
mergePathsWithSameProps(svg)
|
|
553
|
+
}
|
|
535
554
|
|
|
536
|
-
|
|
537
|
-
stylePrev = style
|
|
538
|
-
}
|
|
539
|
-
}
|
|
555
|
+
//console.log('svg', svg);
|
|
540
556
|
|
|
557
|
+
// remove futile clip-paths
|
|
558
|
+
if (cleanupClip) removeFutileClipPaths(svg, { x, y, width, height })
|
|
541
559
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
*/
|
|
560
|
+
// replace href attributes with namespace - required by many older applications
|
|
561
|
+
if (legacyHref) hrefToXlink(svg);
|
|
545
562
|
|
|
546
|
-
if (attributesToGroup) {
|
|
547
|
-
let els = svg.querySelectorAll(geometryEls.join(', '))
|
|
548
|
-
let len = els.length;
|
|
549
563
|
|
|
550
|
-
|
|
551
|
-
|
|
564
|
+
// remove empty class attributes
|
|
565
|
+
removeEmptyClassAtts(svg);
|
|
566
|
+
return { svg, svgElProps }
|
|
552
567
|
|
|
568
|
+
}
|
|
553
569
|
|
|
554
|
-
// all props
|
|
555
|
-
let allProps = {}
|
|
556
570
|
|
|
557
|
-
// find attributes shared by all
|
|
558
|
-
let globalAtts = []
|
|
559
571
|
|
|
560
|
-
|
|
572
|
+
function removeEmptyClassAtts(svg) {
|
|
573
|
+
let emptyClassEls = svg.querySelectorAll('[class=""]');
|
|
574
|
+
emptyClassEls.forEach(el => {
|
|
575
|
+
el.removeAttribute('class')
|
|
576
|
+
})
|
|
577
|
+
}
|
|
561
578
|
|
|
562
|
-
let groups = [[el0]];
|
|
563
|
-
let idx = 0;
|
|
564
|
-
let elPrev = el0
|
|
565
579
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
let att_str = `${att}_${atts[att]}`
|
|
580
|
+
/**
|
|
581
|
+
* shared styles to group
|
|
582
|
+
*/
|
|
583
|
+
function sharedAttributesToGroup(svg) {
|
|
571
584
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
allProps[att_str].push(el)
|
|
576
|
-
//
|
|
577
|
-
if (allProps[att_str].length === len) {
|
|
578
|
-
globalAtts.push(att)
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
}
|
|
585
|
+
let els = svg.querySelectorAll(renderedEls.join(', '))
|
|
586
|
+
let len = els.length;
|
|
587
|
+
if (len === 1) return;
|
|
582
588
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
// apply global to parent SVG
|
|
587
|
-
if (globalAtts.length) {
|
|
588
|
-
let atts0 = getElementAtts(el0)
|
|
589
|
-
for (let att in atts0) {
|
|
590
|
-
// &&
|
|
591
|
-
if (globalAtts.includes(att) && att !== 'transform') {
|
|
592
|
-
svg.setAttribute(att, atts0[att])
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
}
|
|
589
|
+
let el0 = els[0] || null
|
|
590
|
+
let stylePrev = el0.styleSet !== undefined ? [...el0.styleSet].join('_') : ''
|
|
596
591
|
|
|
597
|
-
// detect groups
|
|
598
|
-
for (let i = 1; i < len; i++) {
|
|
599
|
-
let el = els[i];
|
|
600
|
-
let styleArr = el.styleSet !== undefined ? [...el.styleSet] : [];
|
|
601
|
-
let style = styleArr.length ? styleArr.join('_') : '';
|
|
602
|
-
//console.log('style', style);
|
|
603
592
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
else {
|
|
610
|
-
groups.push([el])
|
|
611
|
-
idx++
|
|
612
|
-
}
|
|
613
|
-
// update style
|
|
614
|
-
stylePrev = style
|
|
615
|
-
elPrev = el
|
|
593
|
+
// all props
|
|
594
|
+
let allProps = {}
|
|
595
|
+
|
|
596
|
+
// find attributes shared by all
|
|
597
|
+
let globalAtts = []
|
|
616
598
|
|
|
617
|
-
|
|
599
|
+
if (len) {
|
|
618
600
|
|
|
601
|
+
let groups = [[el0]];
|
|
602
|
+
let idx = 0;
|
|
603
|
+
let elPrev = el0
|
|
619
604
|
|
|
605
|
+
for (let i = 0; i < len; i++) {
|
|
606
|
+
let el = els[i];
|
|
607
|
+
let atts = getElementAtts(el)
|
|
608
|
+
for (let att in atts) {
|
|
609
|
+
let att_str = `${att}_${atts[att]}`
|
|
620
610
|
|
|
611
|
+
if (!allProps[att_str]) {
|
|
612
|
+
allProps[att_str] = []
|
|
613
|
+
}
|
|
614
|
+
allProps[att_str].push(el)
|
|
615
|
+
//
|
|
616
|
+
if (allProps[att_str].length === len) {
|
|
617
|
+
globalAtts.push(att)
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
621
|
|
|
622
|
+
//console.log('allProps', allProps);
|
|
623
|
+
//console.log('globalAtts', globalAtts);
|
|
622
624
|
|
|
625
|
+
// apply global to parent SVG
|
|
626
|
+
if (globalAtts.length) {
|
|
627
|
+
let atts0 = getElementAtts(el0)
|
|
628
|
+
for (let att in atts0) {
|
|
629
|
+
if (globalAtts.includes(att) && att !== 'transform') {
|
|
630
|
+
svg.setAttribute(att, atts0[att])
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
623
634
|
|
|
635
|
+
// detect groups
|
|
636
|
+
for (let i = 1; i < len; i++) {
|
|
637
|
+
let el = els[i];
|
|
638
|
+
let styleArr = el.styleSet !== undefined ? [...el.styleSet] : [];
|
|
639
|
+
let style = styleArr.length ? styleArr.join('_') : '';
|
|
640
|
+
//console.log('style', style);
|
|
641
|
+
//console.log('style === stylePrev', style, stylePrev);
|
|
642
|
+
|
|
643
|
+
// same style add to group
|
|
644
|
+
if (style === stylePrev && elPrev.nextElementSibling === el) {
|
|
645
|
+
groups[idx].push(el)
|
|
646
|
+
}
|
|
647
|
+
// start new group
|
|
648
|
+
else {
|
|
649
|
+
groups.push([el])
|
|
650
|
+
idx++
|
|
651
|
+
}
|
|
652
|
+
// update style
|
|
653
|
+
stylePrev = style
|
|
654
|
+
elPrev = el
|
|
624
655
|
|
|
625
|
-
|
|
656
|
+
}// endof el loop
|
|
626
657
|
|
|
627
|
-
|
|
628
|
-
for (let i = 0; i < groups.length; i++) {
|
|
629
|
-
let children = groups[i];
|
|
630
|
-
let child0 = children[0]
|
|
631
|
-
let atts = getElementAtts(child0)
|
|
632
|
-
let groupEl = child0.parentNode.closest('g')
|
|
658
|
+
//console.log('g', groups);
|
|
633
659
|
|
|
660
|
+
// create groups
|
|
661
|
+
for (let i = 0; i < groups.length; i++) {
|
|
662
|
+
let children = groups[i];
|
|
663
|
+
let child0 = children[0]
|
|
664
|
+
let atts = getElementAtts(child0)
|
|
665
|
+
let groupEl = child0.parentNode.closest('g')
|
|
634
666
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
let globalTransform = globalAtts.includes('transform')
|
|
638
|
-
if (globalTransform) {
|
|
639
|
-
//console.log(globalTransform, globalAtts);
|
|
640
|
-
groupEl.setAttribute('transform', atts['transform'])
|
|
641
|
-
}
|
|
667
|
+
// only 1 child - nothing to group
|
|
668
|
+
if (children.length === 1) continue
|
|
642
669
|
|
|
643
|
-
for (let att in atts) {
|
|
644
|
-
//&& att!=='transform'
|
|
645
|
-
if (globalAtts.includes(att)) {
|
|
646
|
-
child0.removeAttribute(att)
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
continue
|
|
651
|
-
}
|
|
652
670
|
|
|
671
|
+
// create new group
|
|
672
|
+
if (!groupEl || groups.length > 1) {
|
|
673
|
+
//console.log('new group');
|
|
674
|
+
groupEl = document.createElementNS(svgNs, 'g')
|
|
675
|
+
child0.parentNode.insertBefore(groupEl, child0)
|
|
676
|
+
groupEl.append(...children)
|
|
677
|
+
}
|
|
653
678
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
679
|
+
// move attributes to group
|
|
680
|
+
for (let att in atts) {
|
|
681
|
+
let val = atts[att];
|
|
682
|
+
//console.log('att', atts, val);
|
|
683
|
+
|
|
684
|
+
//|| att === 'transform'
|
|
685
|
+
let excludeAtts = ['id', 'class'];
|
|
686
|
+
if (!geometryProps.includes(att) && !excludeAtts.includes(att)) {
|
|
687
|
+
if (!globalAtts.includes(att) || att === 'transform') {
|
|
688
|
+
groupEl.setAttribute(att, val)
|
|
660
689
|
}
|
|
690
|
+
children.forEach(child => {
|
|
691
|
+
child.removeAttribute(att)
|
|
692
|
+
})
|
|
693
|
+
}
|
|
694
|
+
}
|
|
661
695
|
|
|
662
696
|
|
|
697
|
+
} // endof groups
|
|
663
698
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
let val = atts[att];
|
|
667
|
-
//console.log('att', atts, val);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
668
701
|
|
|
669
|
-
//|| att === 'transform'
|
|
670
|
-
if (!geometryProps.includes(att)) {
|
|
671
|
-
if (!globalAtts.includes(att) || att === 'transform') {
|
|
672
|
-
groupEl.setAttribute(att, val)
|
|
673
|
-
}
|
|
674
|
-
children.forEach(child => {
|
|
675
|
-
child.removeAttribute(att)
|
|
676
|
-
})
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
702
|
|
|
680
|
-
|
|
703
|
+
// merge adjacent paths
|
|
704
|
+
function mergePathsWithSameProps(svg) {
|
|
705
|
+
let paths = svg.querySelectorAll('path')
|
|
706
|
+
let len = paths.length;
|
|
681
707
|
|
|
682
|
-
|
|
683
|
-
|
|
708
|
+
if (len) {
|
|
709
|
+
let path0 = paths[0]
|
|
710
|
+
let d0 = path0.getAttribute('d')
|
|
711
|
+
let stylePrev = path0.styleSet !== undefined ? [...path0.styleSet].join(' ') : ''
|
|
712
|
+
//console.log('path0', path0);
|
|
684
713
|
|
|
685
|
-
|
|
714
|
+
let remove = []
|
|
686
715
|
|
|
716
|
+
for (let i = 1; i < len; i++) {
|
|
717
|
+
let path = paths[i];
|
|
718
|
+
let style = path.styleSet !== undefined ? [...path.styleSet].join(' ') : ''
|
|
719
|
+
let isSibling = path.previousElementSibling === path0;
|
|
720
|
+
let d = path.getAttribute('d');
|
|
721
|
+
let isAbs = d.startsWith('M')
|
|
722
|
+
//console.log('path.previousElementSibling', path.previousElementSibling);
|
|
723
|
+
//console.log(isSibling, style, stylePrev, path.id);
|
|
724
|
+
|
|
725
|
+
if (isSibling && style === stylePrev) {
|
|
726
|
+
let dAbs = isAbs ? d : parsePathDataString(d).pathData.map(com => `${com.type} ${com.values.join(' ')}`).join(' ')
|
|
727
|
+
//console.log('same style', dAbs, isAbs);
|
|
728
|
+
d0 += dAbs;
|
|
729
|
+
path0.setAttribute('d', d0)
|
|
730
|
+
//console.log('remove', path);
|
|
731
|
+
remove.push(path)
|
|
732
|
+
//path.remove();
|
|
687
733
|
|
|
734
|
+
} else {
|
|
735
|
+
path0 = path
|
|
736
|
+
//console.log('path0', path0, path);
|
|
737
|
+
d0 = isAbs ? d : parsePathDataString(d).pathData.map(com => `${com.type} ${com.values.join(' ')}`).join(' ')
|
|
688
738
|
|
|
689
|
-
|
|
690
|
-
if (cleanupClip) removeFutileClipPaths(svg, { x, y, width, height })
|
|
739
|
+
}
|
|
691
740
|
|
|
741
|
+
// update style
|
|
742
|
+
stylePrev = style
|
|
743
|
+
}
|
|
692
744
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
hrefs.forEach(el => {
|
|
698
|
-
let href = el.getAttribute('href')
|
|
699
|
-
el.setAttribute('xlink:href', href)
|
|
700
|
-
el.removeAttribute('href')
|
|
745
|
+
|
|
746
|
+
//console.log('remove', remove);
|
|
747
|
+
remove.forEach(el => {
|
|
748
|
+
el.remove()
|
|
701
749
|
})
|
|
750
|
+
|
|
702
751
|
}
|
|
703
752
|
|
|
753
|
+
}
|
|
704
754
|
|
|
705
755
|
|
|
706
|
-
return { svg, svgElProps }
|
|
707
|
-
|
|
708
|
-
}
|
|
709
756
|
|
|
710
757
|
|
|
711
758
|
function removeOffCanvasEls(svg, { x = 0, y = 0, width = 0, height = 0 } = {}) {
|
|
@@ -718,7 +765,9 @@ function removeOffCanvasEls(svg, { x = 0, y = 0, width = 0, height = 0 } = {}) {
|
|
|
718
765
|
bb0.bottom = y + height
|
|
719
766
|
|
|
720
767
|
els.forEach(el => {
|
|
768
|
+
//console.log(el);
|
|
721
769
|
let bb = getElBBox(el)
|
|
770
|
+
//console.log('!!bb', bb);
|
|
722
771
|
let outside = bb.right < bb0.x || bb.bottom < bb0.y || bb.x > bb0.right || bb.y > bb.bottom
|
|
723
772
|
if (outside) el.remove();
|
|
724
773
|
})
|
|
@@ -733,6 +782,13 @@ function addSvgViewBox(svg, { x = 0, y = 0, width = 0, height = 0 } = {}) {
|
|
|
733
782
|
svg.setAttribute('viewBox', [x, y, width, height].join(' '))
|
|
734
783
|
}
|
|
735
784
|
|
|
785
|
+
export function removeEmptySVGEls(svg) {
|
|
786
|
+
let els = svg.querySelectorAll('g, defs');
|
|
787
|
+
els.forEach(el => {
|
|
788
|
+
if (!el.children.length) el.remove()
|
|
789
|
+
})
|
|
790
|
+
}
|
|
791
|
+
|
|
736
792
|
|
|
737
793
|
function cleanupSvgDefs(svg, { x = 0, y = 0, width = 0, height = 0, cleanupClip = true } = {}) {
|
|
738
794
|
let defs = svg.querySelectorAll('defs')
|
|
@@ -769,9 +825,6 @@ function cleanupSvgDefs(svg, { x = 0, y = 0, width = 0, height = 0, cleanupClip
|
|
|
769
825
|
}
|
|
770
826
|
})
|
|
771
827
|
|
|
772
|
-
// remove futile clip-paths
|
|
773
|
-
//if (cleanupClip) removeFutileClipPaths(svg, {x, y, width, height})
|
|
774
|
-
|
|
775
828
|
}
|
|
776
829
|
|
|
777
830
|
|
|
@@ -825,274 +878,23 @@ function removeFutileClipPaths(svg, { x = 0, y = 0, width = 0, height = 0 } = {}
|
|
|
825
878
|
}
|
|
826
879
|
|
|
827
880
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
let itemsWithProps = svgElProps.filter(item => item.propstr)
|
|
836
|
-
let path0;
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
// merge paths without properties
|
|
840
|
-
let dCombined = ''
|
|
841
|
-
if (!itemsWithProps.length && mergePaths) {
|
|
842
|
-
let path0 = null;
|
|
843
|
-
|
|
844
|
-
for (let i = 0; i < l; i++) {
|
|
845
|
-
let item = svgElProps[i]
|
|
846
|
-
if (item.name !== 'path') continue;
|
|
847
|
-
let remove = true;
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
let path = item.el;
|
|
851
|
-
|
|
852
|
-
// set 1st path
|
|
853
|
-
if (!path0) {
|
|
854
|
-
path0 = path;
|
|
855
|
-
remove = false;
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
let d = item.propsFiltered.d
|
|
859
|
-
let isAbs = d.startsWith('M')
|
|
860
|
-
let dAbs = isAbs ? d : parsePathDataString(d).pathData.map(com => `${com.type} ${com.values.join(' ')}`).join(' ')
|
|
861
|
-
|
|
862
|
-
dCombined += dAbs;
|
|
863
|
-
|
|
864
|
-
// delete path el
|
|
865
|
-
if (remove) path.remove();
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
//console.log('dCombined', dCombined);
|
|
869
|
-
if (path0) path0.setAttribute('d', dCombined)
|
|
870
|
-
return
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
// add to combine chunks
|
|
875
|
-
for (let i = 0; i < l; i++) {
|
|
876
|
-
let item = svgElProps[i];
|
|
877
|
-
let props = item.propsFiltered;
|
|
878
|
-
let propstr = [];
|
|
879
|
-
for (let prop in props) {
|
|
880
|
-
if (prop !== 'd' && prop !== 'id') {
|
|
881
|
-
propstr.push(`${prop}:${props[prop]}`)
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
propstr = propstr.join('_')
|
|
885
|
-
item.propstr = propstr;
|
|
886
|
-
|
|
887
|
-
if (l > 1 && propstr === lastProps) {
|
|
888
|
-
combine[idx].push(item)
|
|
889
|
-
} else {
|
|
890
|
-
if (l > 1 && combine[idx].length) {
|
|
891
|
-
combine.push([])
|
|
892
|
-
idx++
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
lastProps = propstr;
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
// add att groups
|
|
900
|
-
for (let i = 0; i < combine.length; i++) {
|
|
901
|
-
let group = combine[i]
|
|
902
|
-
|
|
903
|
-
if (group.length > 1) {
|
|
904
|
-
// 1st el
|
|
905
|
-
let el0 = group[0].el;
|
|
906
|
-
let props = group[0].propsFiltered;
|
|
907
|
-
let g = el0.parentNode.closest('g') ? el0.parentNode.closest('g') : null;
|
|
908
|
-
|
|
909
|
-
// wrap in group if not existent
|
|
910
|
-
if (!g) {
|
|
911
|
-
g = document.createElementNS(svgNs, 'g');
|
|
912
|
-
el0.parentNode.insertBefore(g, el0)
|
|
913
|
-
group.forEach(item => {
|
|
914
|
-
g.append(item.el)
|
|
915
|
-
})
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
let children = [...g.children]
|
|
919
|
-
for (let prop in props) {
|
|
920
|
-
if (prop !== 'd' && prop !== 'id') {
|
|
921
|
-
let value = props[prop]
|
|
922
|
-
// apply to parent group
|
|
923
|
-
g.setAttribute(prop, value)
|
|
924
|
-
|
|
925
|
-
// remove from children
|
|
926
|
-
children.forEach(el => {
|
|
927
|
-
if (el.getAttribute(prop) === value) {
|
|
928
|
-
el.removeAttribute(prop)
|
|
929
|
-
}
|
|
930
|
-
})
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
if (mergePaths) {
|
|
935
|
-
group = group.filter(Boolean)
|
|
936
|
-
let l = group.length
|
|
937
|
-
// nothing to merge
|
|
938
|
-
if (l === 1) return group[0].el;
|
|
939
|
-
|
|
940
|
-
path0 = group[0].el;
|
|
941
|
-
let dCombined = group[0].propsFiltered.d;
|
|
942
|
-
|
|
943
|
-
for (let i = 1; i < l; i++) {
|
|
944
|
-
let item = group[i]
|
|
945
|
-
let path = item.el;
|
|
946
|
-
let d = item.propsFiltered.d
|
|
947
|
-
let isAbs = d.startsWith('M')
|
|
948
|
-
|
|
949
|
-
let dAbs = isAbs ? d : parsePathDataString(d).pathData.map(com => `${com.type} ${com.values.join(' ')}`).join(' ')
|
|
950
|
-
|
|
951
|
-
console.log('dAbs', dAbs);
|
|
952
|
-
|
|
953
|
-
//console.log(isAbs, dAbs);
|
|
954
|
-
// concat pathdata string
|
|
955
|
-
dCombined += dAbs;
|
|
956
|
-
|
|
957
|
-
// delete path el
|
|
958
|
-
path.remove();
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
path0.setAttribute('d', dCombined)
|
|
962
|
-
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
export function scaleProps(styleProps = {}, { props = [], scale = 1 } = {}) {
|
|
973
|
-
if (scale === 1 || !props.length) return props;
|
|
974
|
-
|
|
975
|
-
for (let i = 0; i < props.length; i++) {
|
|
976
|
-
let prop = props[i];
|
|
977
|
-
|
|
978
|
-
if (styleProps[prop] !== undefined) {
|
|
979
|
-
styleProps[prop] = styleProps[prop].map(val => val * scale)
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
return styleProps
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
export function removeSVGEls(svg, {
|
|
986
|
-
remove = ['metadata', 'script'],
|
|
987
|
-
removeNameSpaced = true,
|
|
988
|
-
} = {}) {
|
|
989
|
-
|
|
990
|
-
let els = svg.querySelectorAll('*')
|
|
991
|
-
|
|
992
|
-
let allowMeta = !remove.includes('metadata');
|
|
993
|
-
|
|
994
|
-
els.forEach(el => {
|
|
995
|
-
let nodeName = el.nodeName;
|
|
996
|
-
let isMeta = allowMeta && el.closest('metadata')
|
|
997
|
-
if (
|
|
998
|
-
!isMeta &&
|
|
999
|
-
((removeNameSpaced && nodeName.includes(':')) ||
|
|
1000
|
-
remove.includes(nodeName))
|
|
1001
|
-
) {
|
|
1002
|
-
el.remove()
|
|
1003
|
-
}
|
|
881
|
+
function hrefToXlink(svg) {
|
|
882
|
+
svg.setAttribute('xmlns:xlink', "http://www.w3.org/1999/xlink")
|
|
883
|
+
let hrefs = svg.querySelectorAll('[href]')
|
|
884
|
+
hrefs.forEach(el => {
|
|
885
|
+
let href = el.getAttribute('href')
|
|
886
|
+
el.setAttribute('xlink:href', href)
|
|
887
|
+
//el.removeAttribute('href')
|
|
1004
888
|
})
|
|
1005
889
|
}
|
|
1006
890
|
|
|
1007
891
|
|
|
1008
|
-
function cleanSvgPrologue(svgString) {
|
|
1009
|
-
return (
|
|
1010
|
-
svgString
|
|
1011
|
-
// Remove XML prologues like <?xml ... ?>
|
|
1012
|
-
.replace(/<\?xml[\s\S]*?\?>/gi, "")
|
|
1013
|
-
// Remove DOCTYPE declarations
|
|
1014
|
-
.replace(/<!DOCTYPE[\s\S]*?>/gi, "")
|
|
1015
|
-
// Remove comments <!-- ... -->
|
|
1016
|
-
.replace(/<!--[\s\S]*?-->/g, "")
|
|
1017
|
-
// Trim extra whitespace
|
|
1018
|
-
.trim()
|
|
1019
|
-
);
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
function removeExcludedAttribues(el, {
|
|
1023
|
-
allowed = ['viewBox', 'xmlns', 'width', 'height', 'id', 'class'],
|
|
1024
|
-
allowAriaAtts = true,
|
|
1025
|
-
allowDataAtts = true,
|
|
1026
|
-
allowMeta = false
|
|
1027
|
-
} = {}) {
|
|
1028
|
-
let atts = [...el.attributes].map((att) => att.name);
|
|
1029
|
-
atts.forEach((att) => {
|
|
1030
|
-
|
|
1031
|
-
let isMeta = allowMeta && (att === 'title')
|
|
1032
|
-
let isAria = allowAriaAtts && att.startsWith('aria-')
|
|
1033
|
-
let isData = allowDataAtts && att.startsWith('data-')
|
|
1034
|
-
//console.log(att, isData, 'allowDataAtts', allowDataAtts);
|
|
1035
|
-
|
|
1036
|
-
if (
|
|
1037
|
-
!allowed.includes(att) &&
|
|
1038
|
-
!isAria && !isData && !isMeta
|
|
1039
|
-
) {
|
|
1040
|
-
el.removeAttribute(att);
|
|
1041
|
-
}
|
|
1042
|
-
});
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
function removeAtts(el, exclude = [], include = []) {
|
|
1047
|
-
let atts = [...el.attributes].map((att) => att.name);
|
|
1048
|
-
atts.forEach((att) => {
|
|
1049
|
-
if (exclude.includes(att) && !include.includes(att)) {
|
|
1050
|
-
el.removeAttribute(att);
|
|
1051
|
-
}
|
|
1052
|
-
});
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
function removeNameSpaceAtts(el, {
|
|
1057
|
-
include = ['xlink:href']
|
|
1058
|
-
} = {}) {
|
|
1059
|
-
let atts = [...el.attributes].map((att) => att.name);
|
|
1060
|
-
atts.forEach((att) => {
|
|
1061
|
-
if (att.includes(":") && !include.includes(att)) {
|
|
1062
|
-
el.removeAttribute(att);
|
|
1063
|
-
}
|
|
1064
|
-
});
|
|
1065
|
-
}
|
|
1066
892
|
|
|
1067
|
-
export function stringifySVG(svg, {
|
|
1068
|
-
omitNamespace = false,
|
|
1069
|
-
removeComments = true,
|
|
1070
|
-
} = {}) {
|
|
1071
|
-
let markup = new XMLSerializer().serializeToString(svg);
|
|
1072
893
|
|
|
1073
|
-
if (omitNamespace) {
|
|
1074
|
-
markup = markup.replaceAll('xmlns="http://www.w3.org/2000/svg"', '')
|
|
1075
|
-
}
|
|
1076
894
|
|
|
1077
|
-
if (removeComments) {
|
|
1078
|
-
markup = markup
|
|
1079
|
-
.replace(/(<!--.*?-->)|(<!--[\S\s]+?-->)|(<!--[\S\s]*?$)/g, '')
|
|
1080
|
-
}
|
|
1081
895
|
|
|
1082
|
-
markup = markup
|
|
1083
|
-
.replace(/\t/g, "")
|
|
1084
|
-
.replace(/[\n\r|]/g, "\n")
|
|
1085
|
-
.replace(/\n\s*\n/g, '\n')
|
|
1086
|
-
.replace(/ +/g, ' ')
|
|
1087
|
-
//.replace(/ +/g, ' ')
|
|
1088
|
-
.replace(/> </g, '><')
|
|
1089
|
-
.trim()
|
|
1090
|
-
// sanitize linebreaks within pathdata
|
|
1091
|
-
.replaceAll(' ', '\n');
|
|
1092
896
|
|
|
1093
897
|
|
|
1094
898
|
|
|
1095
899
|
|
|
1096
900
|
|
|
1097
|
-
return markup
|
|
1098
|
-
}
|