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.
Files changed (43) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +3 -76
  3. package/dist/svg-path-simplify.esm.js +5505 -4030
  4. package/dist/svg-path-simplify.esm.min.js +2 -8
  5. package/dist/svg-path-simplify.js +5506 -4029
  6. package/dist/svg-path-simplify.min.js +2 -8
  7. package/dist/svg-path-simplify.pathdata.esm.js +1154 -1042
  8. package/dist/svg-path-simplify.pathdata.esm.min.js +2 -8
  9. package/docs/api.md +127 -0
  10. package/index.html +279 -257
  11. package/package.json +1 -1
  12. package/src/constants.js +10 -1
  13. package/src/index.js +7 -1
  14. package/src/pathData_simplify_cubic.js +1 -18
  15. package/src/pathSimplify-main.js +87 -30
  16. package/src/pathSimplify-only-pathdata.js +2 -2
  17. package/src/svg-getAttributes.js +13 -0
  18. package/src/svg_flatten_transforms.js +1 -1
  19. package/src/svg_getViewbox.js +23 -11
  20. package/src/svg_rootSVG.js +9 -0
  21. package/src/svgii/convert_colors.js +145 -0
  22. package/src/svgii/convert_units.js +159 -0
  23. package/src/svgii/geometry.js +24 -4
  24. package/src/svgii/geometry_bbox.js +2 -1
  25. package/src/svgii/geometry_bbox_element.js +46 -0
  26. package/src/svgii/pathData_analyze.js +34 -14
  27. package/src/svgii/pathData_convert.js +204 -34
  28. package/src/svgii/pathData_parse.js +2 -99
  29. package/src/svgii/pathData_parse_els.js +217 -128
  30. package/src/svgii/pathData_simplify_refineCorners.js +72 -43
  31. package/src/svgii/pathData_stringify.js +6 -5
  32. package/src/svgii/pathData_toPolygon.js +3 -1
  33. package/src/svgii/pathData_transform.js +307 -0
  34. package/src/svgii/poly_normalize.js +21 -1
  35. package/src/svgii/rounding.js +36 -5
  36. package/src/svgii/svg-styles-getTransforms.js +119 -8
  37. package/src/svgii/svg-styles-to-attributes-const.js +26 -6
  38. package/src/svgii/svg_cleanup.js +540 -74
  39. package/src/svgii/svg_el_parse_style_props.js +561 -0
  40. package/src/svgii/transform_qr_decompose.js +74 -0
  41. package/src/svgii/pathData_scale.js +0 -42
  42. package/src/svgii/stringify.js +0 -103
  43. package/src/svgii/svg-styles-to-attributes.js +0 -217
@@ -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 { shapeElToPath } from "./pathData_parse_els";
4
- import { svgStylesToAttributes } from "./svg-styles-to-attributes";
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 = true,
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
- cleanUpStrokes=true,
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', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin'];
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
- let els = svg.querySelectorAll('*')
58
- let elProps = []
154
+ removeSVGEls(svg, { removeEls, removeNameSpaced });
59
155
 
60
- let geometryElements = ['polygon', 'polyline', 'line', 'rect', 'circle', 'ellipse']
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
- // convert shapes
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
- // styles to attributes
87
- if (stylesToAttributes || attributesToGroup || mergePaths) {
88
- let propsFiltered = svgStylesToAttributes(el, { removeNameSpaced, decimals })
89
- if (name === 'path') {
90
- elProps.push({ el, name, idx: i, propsFiltered })
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
- // group styles
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
- if (removeClassNames || removeIds) {
107
- let att = removeClassNames ? 'class' : 'id';
108
- let selector = `[${att}]`
109
- let els = svg.querySelectorAll(selector)
110
- svg.removeAttribute(att)
111
- els.forEach(el => {
112
- el.removeAttribute(att)
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
- // remove empty defs
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
- defs.forEach(def=>{
122
- let children=[...def.children]
123
- let attributes = [...def.attributes]
124
- if(!children.length){
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
- if(convertTransforms){
132
- flattenTransforms(svg)
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
- if (returnDom) return svg
138
- let markup = stringifySVG(svg)
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
- //console.log(svg.outerHTML);
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
- let combine = [[elProps[0]]]
557
+ function moveAttributesToGroup(svgElProps = [], mergePaths = true) {
558
+
559
+ let combine = [[svgElProps[0]]]
150
560
  let idx = 0;
151
561
  let lastProps = '';
152
- let l = elProps.length;
153
- let itemsWithProps = elProps.filter(item => item.propstr)
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 item0 = elProps[0]
160
- path0 = item0.el
161
- let dCombined = item0.propsFiltered.d
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
- path0.setAttribute('d', dCombined)
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 = elProps[i];
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('http://www.w3.org/2000/svg', 'g');
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, omitNamespace = false) {
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('&#10;', '\n');
799
+
800
+
801
+
802
+
337
803
 
338
804
  return markup
339
805
  }