svg-path-simplify 0.3.5 → 0.4.0
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 +19 -0
- package/README.md +3 -76
- package/dist/svg-path-simplify.esm.js +5505 -4030
- package/dist/svg-path-simplify.esm.min.js +2 -8
- package/dist/svg-path-simplify.js +5506 -4029
- package/dist/svg-path-simplify.min.js +2 -8
- package/dist/svg-path-simplify.pathdata.esm.js +1154 -1042
- package/dist/svg-path-simplify.pathdata.esm.min.js +2 -8
- package/docs/api.md +127 -0
- package/index.html +279 -257
- package/package.json +1 -1
- package/src/constants.js +10 -1
- package/src/index.js +7 -1
- package/src/pathData_simplify_cubic.js +1 -18
- package/src/pathSimplify-main.js +87 -30
- package/src/pathSimplify-only-pathdata.js +2 -2
- package/src/svg-getAttributes.js +13 -0
- package/src/svg_flatten_transforms.js +1 -1
- package/src/svg_getViewbox.js +23 -11
- package/src/svg_rootSVG.js +9 -0
- package/src/svgii/convert_colors.js +145 -0
- package/src/svgii/convert_units.js +159 -0
- package/src/svgii/geometry.js +24 -4
- package/src/svgii/geometry_bbox.js +2 -1
- package/src/svgii/geometry_bbox_element.js +46 -0
- package/src/svgii/pathData_analyze.js +34 -14
- package/src/svgii/pathData_convert.js +204 -34
- package/src/svgii/pathData_parse.js +2 -99
- package/src/svgii/pathData_parse_els.js +217 -128
- package/src/svgii/pathData_simplify_refineCorners.js +72 -43
- package/src/svgii/pathData_stringify.js +6 -5
- package/src/svgii/pathData_toPolygon.js +3 -1
- package/src/svgii/pathData_transform.js +307 -0
- package/src/svgii/poly_normalize.js +21 -1
- package/src/svgii/rounding.js +36 -5
- package/src/svgii/svg-styles-getTransforms.js +119 -8
- package/src/svgii/svg-styles-to-attributes-const.js +26 -6
- package/src/svgii/svg_cleanup.js +540 -74
- package/src/svgii/svg_el_parse_style_props.js +561 -0
- package/src/svgii/transform_qr_decompose.js +74 -0
- package/src/svgii/pathData_scale.js +0 -42
- package/src/svgii/stringify.js +0 -103
- package/src/svgii/svg-styles-to-attributes.js +0 -217
package/src/svgii/svg_cleanup.js
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
|
+
import { getElementAtts } from "../svg-getAttributes";
|
|
1
2
|
import { flattenTransforms } from "../svg_flatten_transforms";
|
|
3
|
+
import { getViewBox } from "../svg_getViewbox";
|
|
4
|
+
import { isNumericValue, normalizeUnits } from "./convert_units";
|
|
5
|
+
import { getPathDataVertices } from "./geometry";
|
|
6
|
+
import { checkBBoxIntersections, getPathDataBBox, getPolyBBox } from "./geometry_bbox";
|
|
7
|
+
import { getElBBox } from "./geometry_bbox_element";
|
|
2
8
|
import { parsePathDataString } from "./pathData_parse";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
9
|
+
import { parsePathDataNormalized } from "./pathData_convert";
|
|
10
|
+
import { pathElToShape, shapeElToPath } from "./pathData_parse_els";
|
|
11
|
+
//import { scaleProps } from "./svg-styles-to-attributes";
|
|
12
|
+
import { geometryEls, renderedEls, shapeEls, strokeAtts } from "./svg-styles-to-attributes-const";
|
|
13
|
+
import { addTransFormProps, filterSvgElProps, parseStylesProperties } from "./svg_el_parse_style_props";
|
|
14
|
+
import { autoRound } from "./rounding";
|
|
15
|
+
import { getMatrixFromTransform } from "./svg-styles-getTransforms";
|
|
16
|
+
import { qrDecomposeMatrix } from "./transform_qr_decompose";
|
|
17
|
+
import { svgNs } from "../constants";
|
|
5
18
|
|
|
6
19
|
|
|
7
20
|
export function removeEmptySVGEls(svg) {
|
|
@@ -13,68 +26,145 @@ export function removeEmptySVGEls(svg) {
|
|
|
13
26
|
|
|
14
27
|
//const DOMParserPoly = globalThis.DOMParser;
|
|
15
28
|
|
|
29
|
+
|
|
16
30
|
export function cleanUpSVG(svgMarkup, {
|
|
17
|
-
returnDom = false,
|
|
18
31
|
removeHidden = true,
|
|
19
|
-
removeUnused = true,
|
|
32
|
+
//removeUnused = true,
|
|
20
33
|
stylesToAttributes = true,
|
|
21
34
|
removePrologue = true,
|
|
22
35
|
removeIds = false,
|
|
23
36
|
removeClassNames = false,
|
|
24
37
|
removeDimensions = false,
|
|
25
|
-
fixHref =
|
|
38
|
+
fixHref = false,
|
|
39
|
+
legacyHref = false,
|
|
40
|
+
cleanupDefs = true,
|
|
41
|
+
cleanupClip = true,
|
|
42
|
+
addViewBox = false,
|
|
43
|
+
addDimensions = false,
|
|
44
|
+
minifyRgbColors = false,
|
|
45
|
+
|
|
46
|
+
normalizeTransforms = true,
|
|
47
|
+
autoRoundValues = true,
|
|
48
|
+
|
|
49
|
+
unGroup = false,
|
|
50
|
+
|
|
26
51
|
mergePaths = false,
|
|
52
|
+
removeOffCanvas = true,
|
|
27
53
|
cleanupSVGAtts = true,
|
|
28
54
|
removeNameSpaced = true,
|
|
29
55
|
attributesToGroup = true,
|
|
30
|
-
shapesToPaths = false,
|
|
56
|
+
//shapesToPaths = false,
|
|
57
|
+
shapeConvert = false,
|
|
58
|
+
convert_rects = false,
|
|
59
|
+
convert_ellipses = false,
|
|
60
|
+
convert_poly = false,
|
|
61
|
+
convert_lines = false,
|
|
62
|
+
|
|
31
63
|
convertTransforms = false,
|
|
32
|
-
|
|
64
|
+
removeDefaults = true,
|
|
65
|
+
cleanUpStrokes = true,
|
|
33
66
|
decimals = -1,
|
|
34
67
|
excludedEls = [],
|
|
35
68
|
} = {}) {
|
|
36
69
|
|
|
37
|
-
attributesToGroup = cleanupSVGAtts ? true : false;
|
|
70
|
+
//attributesToGroup = cleanupSVGAtts ? true : false;
|
|
71
|
+
|
|
38
72
|
|
|
39
73
|
// replace namespaced refs
|
|
40
74
|
if (fixHref) svgMarkup = svgMarkup.replaceAll("xlink:href=", "href=");
|
|
41
75
|
|
|
76
|
+
|
|
42
77
|
let svg = new DOMParser()
|
|
43
78
|
.parseFromString(svgMarkup, "text/html")
|
|
44
79
|
.querySelector("svg");
|
|
45
80
|
|
|
81
|
+
let viewBox = getViewBox(svg)
|
|
82
|
+
let { x, y, width, height } = viewBox;
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
// get svg styles
|
|
86
|
+
let propOptions = {
|
|
87
|
+
width: width,
|
|
88
|
+
height: height,
|
|
89
|
+
normalizeTransforms,
|
|
90
|
+
removeDefaults: false,
|
|
91
|
+
cleanUpStrokes: false,
|
|
92
|
+
autoRoundValues,
|
|
93
|
+
minifyRgbColors,
|
|
94
|
+
}
|
|
95
|
+
let stylePropsSVG = parseStylesProperties(svg, propOptions)
|
|
96
|
+
|
|
97
|
+
// add svg font size for scaling relative
|
|
98
|
+
propOptions.fontSize = stylePropsSVG['font-size'] ? stylePropsSVG['font-size'][0] : 16;
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* get group styles
|
|
103
|
+
* especially transformations to
|
|
104
|
+
* be inherited by children
|
|
105
|
+
*/
|
|
106
|
+
let groups = svg.querySelectorAll('g')
|
|
107
|
+
let groupProps = [];
|
|
108
|
+
|
|
109
|
+
groups.forEach(g => {
|
|
110
|
+
let stylePropsG = parseStylesProperties(g, propOptions)
|
|
111
|
+
groupProps.push(stylePropsG);
|
|
112
|
+
let children = g.querySelectorAll(`${renderedEls.join(', ')}`)
|
|
113
|
+
|
|
114
|
+
// store parent styles to child property
|
|
115
|
+
children.forEach(child => {
|
|
116
|
+
if (child.parentStyleProps === undefined) {
|
|
117
|
+
child.parentStyleProps = []
|
|
118
|
+
}
|
|
119
|
+
child.parentStyleProps.push(stylePropsG)
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
|
|
46
124
|
|
|
47
125
|
if (cleanupSVGAtts) {
|
|
48
126
|
//console.log('cleanupSVGAtts');
|
|
49
|
-
let allowed = ['viewBox', 'xmlns', 'width', 'height', 'id', 'class'
|
|
127
|
+
let allowed = ['viewBox', 'xmlns', 'width', 'height', 'id', 'class'];
|
|
128
|
+
if (!stylesToAttributes) {
|
|
129
|
+
allowed.push('fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'font-size', 'font-family', 'font-style', 'style');
|
|
130
|
+
}
|
|
131
|
+
|
|
50
132
|
removeExcludedAttribues(svg, allowed)
|
|
133
|
+
}
|
|
51
134
|
|
|
135
|
+
// add viewBox
|
|
136
|
+
if (addViewBox) addSvgViewBox(svg, { x, y, width, height })
|
|
137
|
+
if (addDimensions) {
|
|
138
|
+
svg.setAttribute('width', width);
|
|
139
|
+
svg.setAttribute('height', height);
|
|
52
140
|
}
|
|
53
141
|
|
|
142
|
+
|
|
143
|
+
// remove unused defs or optimize order
|
|
144
|
+
if (cleanupDefs) cleanupSvgDefs(svg, { x, y, width, height, cleanupClip });
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
// remove off canvas
|
|
148
|
+
if (removeOffCanvas) removeOffCanvasEls(svg, { x, y, width, height });
|
|
149
|
+
|
|
150
|
+
|
|
54
151
|
// always remove scripts
|
|
55
152
|
let removeEls = ['metadata', 'script', ...excludedEls]
|
|
56
153
|
|
|
57
|
-
|
|
58
|
-
let elProps = []
|
|
154
|
+
removeSVGEls(svg, { removeEls, removeNameSpaced });
|
|
59
155
|
|
|
60
|
-
|
|
156
|
+
// an array of all elements' properties
|
|
157
|
+
let svgElProps = []
|
|
158
|
+
let els = svg.querySelectorAll(`${renderedEls.join(', ')}`)
|
|
61
159
|
|
|
62
160
|
|
|
63
161
|
for (let i = 0; i < els.length; i++) {
|
|
64
162
|
let el = els[i];
|
|
65
163
|
|
|
66
164
|
let name = el.nodeName.toLowerCase();
|
|
165
|
+
//console.log(name);
|
|
67
166
|
|
|
68
|
-
//
|
|
69
|
-
if (shapesToPaths && name !== 'path' && geometryElements.includes(name)) {
|
|
70
|
-
let path = shapeElToPath(el);
|
|
71
|
-
el.replaceWith(path)
|
|
72
|
-
name = 'path'
|
|
73
|
-
el = path;
|
|
74
|
-
//console.log('shapesToPaths', el.outerHTML);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// remove hidden elements
|
|
167
|
+
// 1. remove hidden elements
|
|
78
168
|
let style = el.getAttribute('style') || ''
|
|
79
169
|
let isHiddenByStyle = style ? style.trim().includes('display:none') : false;
|
|
80
170
|
let isHidden = (el.getAttribute('display') && el.getAttribute('display') === 'none') || isHiddenByStyle;
|
|
@@ -83,87 +173,416 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
83
173
|
continue;
|
|
84
174
|
}
|
|
85
175
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* get all style properties
|
|
179
|
+
* convert relative or physical units
|
|
180
|
+
* to user units
|
|
181
|
+
*/
|
|
182
|
+
let styleProps = parseStylesProperties(el, propOptions)
|
|
183
|
+
|
|
184
|
+
// get parent styles
|
|
185
|
+
let { parentStyleProps = [] } = el;
|
|
186
|
+
let inheritedProps = {}
|
|
187
|
+
let transFormInherited = []
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
/** inherit transforms
|
|
191
|
+
* and styles from group
|
|
192
|
+
*/
|
|
193
|
+
parentStyleProps.forEach(props => {
|
|
194
|
+
// transforms from groups are applied cumulatively
|
|
195
|
+
let { transformArr = [] } = props
|
|
196
|
+
transFormInherited.push(...transformArr)
|
|
197
|
+
|
|
198
|
+
// merge
|
|
199
|
+
inheritedProps = {
|
|
200
|
+
...inheritedProps,
|
|
201
|
+
...props
|
|
202
|
+
};
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
//merge transforms
|
|
207
|
+
transFormInherited = [...transFormInherited, ...styleProps.transformArr]
|
|
208
|
+
styleProps.transformArr = transFormInherited
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
// merge with svg props
|
|
212
|
+
styleProps = {
|
|
213
|
+
...stylePropsSVG,
|
|
214
|
+
...inheritedProps,
|
|
215
|
+
...styleProps
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
//console.log('inheritedProps', inheritedProps, name);
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
// add combined transforms
|
|
222
|
+
addTransFormProps(styleProps, transFormInherited);
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
let { remove, matrix, transComponents } = styleProps;
|
|
226
|
+
|
|
227
|
+
// mark attributes for removal
|
|
228
|
+
if (removeClassNames) styleProps.remove.push('class')
|
|
229
|
+
if (removeIds) styleProps.remove.push('id')
|
|
230
|
+
if (removeDimensions) {
|
|
231
|
+
styleProps.remove.push('width')
|
|
232
|
+
styleProps.remove.push('height')
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
// styles to atts
|
|
237
|
+
if (unGroup || convertTransforms || minifyRgbColors ) stylesToAttributes = true;
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
if (stylesToAttributes) {
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* normalize transforms
|
|
244
|
+
*/
|
|
245
|
+
if (normalizeTransforms && matrix) {
|
|
246
|
+
let { rotate, scaleX, scaleY, skewX, translateX, translateY } = transComponents;
|
|
247
|
+
//console.log(rotate, scaleX, scaleY, skewX, skewY, translateX, translateY);
|
|
248
|
+
|
|
249
|
+
// scale attributes instead of transform
|
|
250
|
+
let hasRot = rotate !== 0 || skewX !== 0;
|
|
251
|
+
let unProportional = scaleX !== scaleY;
|
|
252
|
+
let scalableByAtt = ['circle', 'ellipse', 'rect']
|
|
253
|
+
let needsTrans = convertTransforms || (name === 'g') || (hasRot) || unProportional
|
|
254
|
+
//needsTrans = true
|
|
255
|
+
|
|
256
|
+
if (!needsTrans && scalableByAtt.includes(name)) {
|
|
257
|
+
|
|
258
|
+
if (name === 'circle' || name === 'ellipse') {
|
|
259
|
+
styleProps.cx[0] = [styleProps.cx[0] * scaleX + translateX]
|
|
260
|
+
styleProps.cy[0] = [styleProps.cy[0] * scaleX + translateY]
|
|
261
|
+
|
|
262
|
+
if (styleProps.r) styleProps.r[0] = [styleProps.r[0] * scaleX]
|
|
263
|
+
|
|
264
|
+
if (styleProps.rx) styleProps.rx[0] = [styleProps.rx[0] * scaleX]
|
|
265
|
+
if (styleProps.ry) styleProps.ry[0] = [styleProps.ry[0] * scaleX]
|
|
266
|
+
|
|
267
|
+
}
|
|
268
|
+
else if (name === 'rect') {
|
|
269
|
+
let x = styleProps.x ? styleProps.x[0] + translateX : translateX;
|
|
270
|
+
let y = styleProps.y ? styleProps.y[0] + translateY : translateY;
|
|
271
|
+
|
|
272
|
+
let rx = styleProps.rx ? styleProps.rx[0] * scaleX : 0;
|
|
273
|
+
let ry = styleProps.ry ? styleProps.ry[0] * scaleY : 0;
|
|
274
|
+
|
|
275
|
+
styleProps.x = [x]
|
|
276
|
+
styleProps.y = [y]
|
|
277
|
+
|
|
278
|
+
styleProps.rx = [rx]
|
|
279
|
+
styleProps.ry = [ry]
|
|
280
|
+
|
|
281
|
+
styleProps.width = [styleProps.width[0] * scaleX]
|
|
282
|
+
styleProps.height = [styleProps.height[0] * scaleX]
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
remove.push('transform')
|
|
286
|
+
|
|
287
|
+
// scale props like stroke width or dash-array
|
|
288
|
+
styleProps = scaleProps(styleProps, { props: ['stroke-width', 'stroke-dasharray'], scale: scaleX })
|
|
289
|
+
|
|
290
|
+
} else {
|
|
291
|
+
el.setAttribute('transform', transComponents.matrixAtt)
|
|
292
|
+
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* apply consolidated
|
|
299
|
+
* element attributes
|
|
300
|
+
*/
|
|
301
|
+
|
|
302
|
+
let stylePropsFiltered = filterSvgElProps(name, styleProps,
|
|
303
|
+
{ removeDefaults: true, cleanUpStrokes });
|
|
304
|
+
|
|
305
|
+
remove = [...remove, ...stylePropsFiltered.remove];
|
|
306
|
+
|
|
307
|
+
for (let prop in stylePropsFiltered.propsFiltered) {
|
|
308
|
+
let values = styleProps[prop]
|
|
309
|
+
//console.log('add', prop);
|
|
310
|
+
let val = values.length ? values.join(' ') : values[0]
|
|
311
|
+
el.setAttribute(prop, val)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// remove obsolete attributes
|
|
315
|
+
for (let i = 0; i < remove.length; i++) {
|
|
316
|
+
let att = remove[i];
|
|
317
|
+
if (!stylesToAttributes && att === 'style') continue
|
|
318
|
+
|
|
319
|
+
//console.log('remove att', att, name);
|
|
320
|
+
el.removeAttribute(att)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* remove group styles
|
|
327
|
+
* copied to children
|
|
328
|
+
* or remove nesting
|
|
329
|
+
*/
|
|
330
|
+
|
|
331
|
+
if (unGroup) {
|
|
332
|
+
groups.forEach((g, i) => {
|
|
333
|
+
let children = [...g.children];
|
|
334
|
+
|
|
335
|
+
children.forEach(child => {
|
|
336
|
+
g.parentNode.insertBefore(child, g)
|
|
337
|
+
})
|
|
338
|
+
g.remove()
|
|
339
|
+
})
|
|
340
|
+
} else {
|
|
341
|
+
groups.forEach((g, i) => {
|
|
342
|
+
let atts = [...Object.keys(groupProps[i]), 'style', 'transform'];
|
|
343
|
+
atts.forEach(att => {
|
|
344
|
+
g.removeAttribute(att)
|
|
345
|
+
})
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
} // endof style processing
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* element conversions:
|
|
356
|
+
* shapes to paths or
|
|
357
|
+
* paths to shapes
|
|
358
|
+
*/
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
// force shape conversion when transform conversion is enabled
|
|
362
|
+
if (convertTransforms) {
|
|
363
|
+
shapeConvert = 'toPaths';
|
|
364
|
+
convert_rects = true;
|
|
365
|
+
convert_ellipses = true;
|
|
366
|
+
convert_poly = true;
|
|
367
|
+
convert_lines = true;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// convert shapes to paths
|
|
371
|
+
if (shapeConvert === 'toPaths') {
|
|
372
|
+
|
|
373
|
+
let { matrix = null, transComponents = null } = styleProps;
|
|
374
|
+
|
|
375
|
+
if (matrix && transComponents) {
|
|
376
|
+
// scale props like stroke width or dash-array before conversion
|
|
377
|
+
['stroke-width', 'stroke-dasharray'].forEach(att => {
|
|
378
|
+
let attVal = el.getAttribute(att)
|
|
379
|
+
let vals = attVal ? attVal.split(' ').filter(Boolean).map(Number).map(val => val * transComponents.scaleX) : []
|
|
380
|
+
if (vals.length) el.setAttribute(att, vals.join(' '))
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// convert paths only if a matrix transform is required
|
|
385
|
+
if (matrix ? geometryEls.includes(name) : shapeEls.includes(name)) {
|
|
386
|
+
|
|
387
|
+
let path = shapeElToPath(el, { width, height, convert_rects, convert_ellipses, convert_poly, convert_lines, matrix });
|
|
388
|
+
el.replaceWith(path)
|
|
389
|
+
|
|
390
|
+
name = 'path'
|
|
391
|
+
el = path;
|
|
392
|
+
|
|
393
|
+
|
|
91
394
|
}
|
|
395
|
+
|
|
92
396
|
}
|
|
93
|
-
}
|
|
94
397
|
|
|
398
|
+
// convert paths to shapes
|
|
399
|
+
else if (shapeConvert === 'toShapes') {
|
|
400
|
+
let paths = svg.querySelectorAll('path')
|
|
401
|
+
paths.forEach(path => {
|
|
402
|
+
let shape = pathElToShape(path, { convert_rects, convert_ellipses, convert_poly, convert_lines })
|
|
403
|
+
path.replaceWith(shape)
|
|
404
|
+
path = shape;
|
|
405
|
+
})
|
|
95
406
|
|
|
96
|
-
|
|
97
|
-
if (attributesToGroup || mergePaths) {
|
|
98
|
-
moveAttributesToGroup(elProps, mergePaths)
|
|
99
|
-
}
|
|
407
|
+
}
|
|
100
408
|
|
|
101
|
-
if (removeDimensions) {
|
|
102
|
-
svg.removeAttribute('width')
|
|
103
|
-
svg.removeAttribute('height')
|
|
104
|
-
}
|
|
105
409
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
410
|
+
|
|
411
|
+
}//endof element loop
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
// remove futile clip-paths
|
|
418
|
+
if (cleanupClip) removeFutileClipPaths(svg, { x, y, width, height })
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
// replace href attributes with namespace - required by many older applications
|
|
422
|
+
if (legacyHref) {
|
|
423
|
+
svg.setAttribute('xmlns:xlink', "http://www.w3.org/1999/xlink")
|
|
424
|
+
let hrefs = svg.querySelectorAll('[href]')
|
|
425
|
+
hrefs.forEach(el => {
|
|
426
|
+
let href = el.getAttribute('href')
|
|
427
|
+
el.setAttribute('xlink:href', href)
|
|
428
|
+
el.removeAttribute('href')
|
|
113
429
|
})
|
|
114
430
|
}
|
|
115
431
|
|
|
116
|
-
//console.log('!!!svgMarkup', svgMarkup);
|
|
117
432
|
|
|
118
433
|
|
|
119
|
-
|
|
434
|
+
return { svg, svgElProps }
|
|
435
|
+
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
function removeOffCanvasEls(svg, { x = 0, y = 0, width = 0, height = 0 } = {}) {
|
|
440
|
+
let els = [...svg.querySelectorAll('path, polygon, polyline, line, rect, circle, ellipse, text')];
|
|
441
|
+
els = els.filter(el => !el.parentNode.closest('defs') && !el.parentNode.closest('symbol') && !el.parentNode.closest('clipPath') && !el.parentNode.closest('mask') && !el.parentNode.closest('pattern'))
|
|
442
|
+
//console.log('removeOffCanvasEls', els, width, height);
|
|
443
|
+
|
|
444
|
+
let bb0 = { x, y, width, height }
|
|
445
|
+
bb0.right = x + width
|
|
446
|
+
bb0.bottom = y + height
|
|
447
|
+
|
|
448
|
+
els.forEach(el => {
|
|
449
|
+
let bb = getElBBox(el)
|
|
450
|
+
let outside = bb.right < bb0.x || bb.bottom < bb0.y || bb.x > bb0.right || bb.y > bb.bottom
|
|
451
|
+
if (outside) el.remove();
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function addSvgViewBox(svg, { x = 0, y = 0, width = 0, height = 0 } = {}) {
|
|
457
|
+
if (svg.hasAttribute('viewBox')) return;
|
|
458
|
+
if (!width || !height) {
|
|
459
|
+
({ x, y, width, height } = getViewBox(svg));
|
|
460
|
+
}
|
|
461
|
+
svg.setAttribute('viewBox', [x, y, width, height].join(' '))
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
function cleanupSvgDefs(svg, { x = 0, y = 0, width = 0, height = 0, cleanupClip = true } = {}) {
|
|
120
466
|
let defs = svg.querySelectorAll('defs')
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
467
|
+
let defEls = svg.querySelectorAll('symbol, pattern, linearGradient, radialGradient, clipPath, mask, marker, filter')
|
|
468
|
+
|
|
469
|
+
// no defs to remove/optimize
|
|
470
|
+
if (!defs.length && !defEls.length) return;
|
|
471
|
+
|
|
472
|
+
defs.forEach(def => {
|
|
473
|
+
// remove empty defs
|
|
474
|
+
let children = [...def.children]
|
|
475
|
+
if (!children.length) {
|
|
125
476
|
def.remove()
|
|
126
477
|
}
|
|
478
|
+
// move defs to top
|
|
479
|
+
else {
|
|
480
|
+
svg.insertBefore(def, svg.children[0])
|
|
481
|
+
}
|
|
482
|
+
})
|
|
483
|
+
|
|
484
|
+
//clean up unused defs
|
|
485
|
+
let refIds = new Set([])
|
|
486
|
+
defEls.forEach(def => {
|
|
487
|
+
refIds.add(def.id)
|
|
127
488
|
})
|
|
128
489
|
|
|
490
|
+
Array.from(refIds).forEach(id => {
|
|
491
|
+
let els = svg.querySelectorAll(`[href="#${id}"], [xlink\\:href="#${id}"], [clip-path="url(#${id})"], [mask="url(#${id})"], [fill="url(#${id})"], [stroke="url(#${id})"]`);
|
|
129
492
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
493
|
+
//definition is unused – remove
|
|
494
|
+
if (!els.length) {
|
|
495
|
+
//console.log('remove', id);
|
|
496
|
+
svg.getElementById(id).remove()
|
|
497
|
+
}
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
// remove futile clip-paths
|
|
501
|
+
//if (cleanupClip) removeFutileClipPaths(svg, {x, y, width, height})
|
|
502
|
+
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
function removeFutileClipPaths(svg, { x = 0, y = 0, width = 0, height = 0 } = {}) {
|
|
507
|
+
let clipPaths = svg.querySelectorAll('clipPath');
|
|
508
|
+
|
|
509
|
+
if (!clipPaths.length) return
|
|
510
|
+
|
|
511
|
+
if (!width || !height) {
|
|
512
|
+
({ x, y, width, height } = getViewBox(svg));
|
|
133
513
|
}
|
|
134
|
-
*/
|
|
135
514
|
|
|
515
|
+
clipPaths.forEach(clip => {
|
|
516
|
+
let children = [...clip.children];
|
|
517
|
+
if (children.length > 1) return;
|
|
518
|
+
|
|
519
|
+
let clipEl = children[0]
|
|
520
|
+
let type = clipEl.nodeName.toLowerCase();
|
|
521
|
+
|
|
522
|
+
if (type === 'path' || type === 'rect') {
|
|
523
|
+
let bb = { x: 0, y: 0, width: 0, height: 0 }
|
|
136
524
|
|
|
137
|
-
|
|
138
|
-
|
|
525
|
+
if (type === 'path') {
|
|
526
|
+
let pathData = parsePathDataNormalized(clipEl.getAttribute('d'));
|
|
527
|
+
let coms = Array.from(new Set(pathData.map(com => com.type.toLowerCase()))).join('');
|
|
528
|
+
let isPolygon = !(/[acqts]/gi).test(coms);
|
|
139
529
|
|
|
140
|
-
|
|
530
|
+
// path is too complex - unlikely to be a rectangle
|
|
531
|
+
if (!isPolygon || pathData.length > 5) return
|
|
141
532
|
|
|
533
|
+
let vertices = getPathDataVertices(pathData)
|
|
534
|
+
bb = getPolyBBox(vertices)
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
else if (type === 'rect') {
|
|
538
|
+
bb = { x: +clipEl.getAttribute('x'), y: +clipEl.getAttribute('y'), width: +clipEl.getAttribute('width'), height: +clipEl.getAttribute('height') }
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// is futile if clip path's bbox equals the SVG's viewBox
|
|
542
|
+
if (bb.x === x && bb.y === y && bb.width === width && bb.height === height) {
|
|
543
|
+
clip.remove();
|
|
544
|
+
let clippedEls = svg.querySelectorAll(`[clip-path="url(#${clip.id})"]`);
|
|
545
|
+
//console.log('clippedEls', clippedEls);
|
|
546
|
+
clippedEls.forEach(clipped => {
|
|
547
|
+
clipped.removeAttribute('clip-path')
|
|
548
|
+
})
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
})
|
|
142
552
|
|
|
143
|
-
return markup;
|
|
144
553
|
}
|
|
145
554
|
|
|
146
555
|
|
|
147
|
-
function moveAttributesToGroup(elProps = [], mergePaths = true) {
|
|
148
556
|
|
|
149
|
-
|
|
557
|
+
function moveAttributesToGroup(svgElProps = [], mergePaths = true) {
|
|
558
|
+
|
|
559
|
+
let combine = [[svgElProps[0]]]
|
|
150
560
|
let idx = 0;
|
|
151
561
|
let lastProps = '';
|
|
152
|
-
let l =
|
|
153
|
-
let itemsWithProps =
|
|
562
|
+
let l = svgElProps.length;
|
|
563
|
+
let itemsWithProps = svgElProps.filter(item => item.propstr)
|
|
154
564
|
let path0;
|
|
155
565
|
|
|
156
566
|
|
|
157
567
|
// merge paths without properties
|
|
568
|
+
let dCombined = ''
|
|
158
569
|
if (!itemsWithProps.length && mergePaths) {
|
|
159
|
-
let
|
|
160
|
-
|
|
161
|
-
let
|
|
570
|
+
let path0 = null;
|
|
571
|
+
|
|
572
|
+
for (let i = 0; i < l; i++) {
|
|
573
|
+
let item = svgElProps[i]
|
|
574
|
+
if (item.name !== 'path') continue;
|
|
575
|
+
let remove = true;
|
|
576
|
+
|
|
162
577
|
|
|
163
|
-
for (let i = 1; i < l; i++) {
|
|
164
|
-
let item = elProps[i]
|
|
165
578
|
let path = item.el;
|
|
166
579
|
|
|
580
|
+
// set 1st path
|
|
581
|
+
if (!path0) {
|
|
582
|
+
path0 = path;
|
|
583
|
+
remove = false;
|
|
584
|
+
}
|
|
585
|
+
|
|
167
586
|
let d = item.propsFiltered.d
|
|
168
587
|
let isAbs = d.startsWith('M')
|
|
169
588
|
let dAbs = isAbs ? d : parsePathDataString(d).pathData.map(com => `${com.type} ${com.values.join(' ')}`).join(' ')
|
|
@@ -171,17 +590,18 @@ function moveAttributesToGroup(elProps = [], mergePaths = true) {
|
|
|
171
590
|
dCombined += dAbs;
|
|
172
591
|
|
|
173
592
|
// delete path el
|
|
174
|
-
path.remove();
|
|
593
|
+
if (remove) path.remove();
|
|
175
594
|
}
|
|
176
595
|
|
|
177
|
-
|
|
596
|
+
//console.log('dCombined', dCombined);
|
|
597
|
+
if (path0) path0.setAttribute('d', dCombined)
|
|
178
598
|
return
|
|
179
599
|
}
|
|
180
600
|
|
|
181
601
|
|
|
182
602
|
// add to combine chunks
|
|
183
603
|
for (let i = 0; i < l; i++) {
|
|
184
|
-
let item =
|
|
604
|
+
let item = svgElProps[i];
|
|
185
605
|
let props = item.propsFiltered;
|
|
186
606
|
let propstr = [];
|
|
187
607
|
for (let prop in props) {
|
|
@@ -216,7 +636,7 @@ function moveAttributesToGroup(elProps = [], mergePaths = true) {
|
|
|
216
636
|
|
|
217
637
|
// wrap in group if not existent
|
|
218
638
|
if (!g) {
|
|
219
|
-
g = document.createElementNS(
|
|
639
|
+
g = document.createElementNS(svgNs, 'g');
|
|
220
640
|
el0.parentNode.insertBefore(g, el0)
|
|
221
641
|
group.forEach(item => {
|
|
222
642
|
g.append(item.el)
|
|
@@ -256,6 +676,8 @@ function moveAttributesToGroup(elProps = [], mergePaths = true) {
|
|
|
256
676
|
|
|
257
677
|
let dAbs = isAbs ? d : parsePathDataString(d).pathData.map(com => `${com.type} ${com.values.join(' ')}`).join(' ')
|
|
258
678
|
|
|
679
|
+
console.log('dAbs', dAbs);
|
|
680
|
+
|
|
259
681
|
//console.log(isAbs, dAbs);
|
|
260
682
|
// concat pathdata string
|
|
261
683
|
dCombined += dAbs;
|
|
@@ -275,6 +697,35 @@ function moveAttributesToGroup(elProps = [], mergePaths = true) {
|
|
|
275
697
|
}
|
|
276
698
|
|
|
277
699
|
|
|
700
|
+
export function scaleProps(styleProps = {}, { props = [], scale = 1 } = {}) {
|
|
701
|
+
if (scale === 1 || !props.length) return props;
|
|
702
|
+
|
|
703
|
+
for (let i = 0; i < props.length; i++) {
|
|
704
|
+
let prop = props[i];
|
|
705
|
+
|
|
706
|
+
if (styleProps[prop] !== undefined) {
|
|
707
|
+
styleProps[prop] = styleProps[prop].map(val => val * scale)
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return styleProps
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
export function removeSVGEls(svg, {
|
|
714
|
+
remove = ['metadata', 'script'],
|
|
715
|
+
removeNameSpaced = true,
|
|
716
|
+
} = {}) {
|
|
717
|
+
let els = svg.querySelectorAll('*')
|
|
718
|
+
els.forEach(el => {
|
|
719
|
+
let nodeName = el.nodeName;
|
|
720
|
+
if ((removeNameSpaced && nodeName.includes(':')) ||
|
|
721
|
+
remove.includes(nodeName)
|
|
722
|
+
) {
|
|
723
|
+
el.remove()
|
|
724
|
+
}
|
|
725
|
+
})
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
|
|
278
729
|
function cleanSvgPrologue(svgString) {
|
|
279
730
|
return (
|
|
280
731
|
svgString
|
|
@@ -309,22 +760,32 @@ function removeAtts(el, exclude = [], include = []) {
|
|
|
309
760
|
}
|
|
310
761
|
|
|
311
762
|
|
|
312
|
-
function removeNameSpaceAtts(el
|
|
763
|
+
function removeNameSpaceAtts(el, {
|
|
764
|
+
include = ['xlink:href']
|
|
765
|
+
} = {}) {
|
|
313
766
|
let atts = [...el.attributes].map((att) => att.name);
|
|
314
767
|
atts.forEach((att) => {
|
|
315
|
-
if (att.includes(":")) {
|
|
768
|
+
if (att.includes(":") && !include.includes(att)) {
|
|
316
769
|
el.removeAttribute(att);
|
|
317
770
|
}
|
|
318
771
|
});
|
|
319
772
|
}
|
|
320
773
|
|
|
321
|
-
export function stringifySVG(svg,
|
|
774
|
+
export function stringifySVG(svg, {
|
|
775
|
+
omitNamespace = false,
|
|
776
|
+
removeComments = true,
|
|
777
|
+
} = {}) {
|
|
322
778
|
let markup = new XMLSerializer().serializeToString(svg);
|
|
323
779
|
|
|
324
780
|
if (omitNamespace) {
|
|
325
781
|
markup = markup.replaceAll('xmlns="http://www.w3.org/2000/svg"', '')
|
|
326
782
|
}
|
|
327
783
|
|
|
784
|
+
if (removeComments) {
|
|
785
|
+
markup = markup
|
|
786
|
+
.replace(/(<!--.*?-->)|(<!--[\S\s]+?-->)|(<!--[\S\s]*?$)/g, '')
|
|
787
|
+
}
|
|
788
|
+
|
|
328
789
|
markup = markup
|
|
329
790
|
.replace(/\t/g, "")
|
|
330
791
|
.replace(/[\n\r|]/g, "\n")
|
|
@@ -333,7 +794,12 @@ export function stringifySVG(svg, omitNamespace = false) {
|
|
|
333
794
|
//.replace(/ +/g, ' ')
|
|
334
795
|
.replace(/> </g, '><')
|
|
335
796
|
.trim()
|
|
336
|
-
|
|
797
|
+
// sanitize linebreaks within pathdata
|
|
798
|
+
.replaceAll(' ', '\n');
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
|
|
337
803
|
|
|
338
804
|
return markup
|
|
339
805
|
}
|