svg-path-simplify 0.3.5 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/svg-path-simplify.esm.js +4665 -4063
  2. package/dist/svg-path-simplify.esm.min.js +2 -8
  3. package/dist/svg-path-simplify.js +4666 -4062
  4. package/dist/svg-path-simplify.min.js +2 -8
  5. package/dist/svg-path-simplify.pathdata.esm.js +1090 -1039
  6. package/dist/svg-path-simplify.pathdata.esm.min.js +2 -8
  7. package/index.html +118 -33
  8. package/package.json +1 -1
  9. package/src/constants.js +3 -1
  10. package/src/index.js +7 -1
  11. package/src/pathData_simplify_cubic.js +1 -10
  12. package/src/pathSimplify-main.js +69 -28
  13. package/src/pathSimplify-only-pathdata.js +2 -2
  14. package/src/svg-getAttributes.js +13 -0
  15. package/src/svg_getViewbox.js +23 -11
  16. package/src/svg_rootSVG.js +9 -0
  17. package/src/svgii/convert_colors.js +98 -0
  18. package/src/svgii/convert_units.js +144 -0
  19. package/src/svgii/geometry.js +24 -4
  20. package/src/svgii/geometry_bbox.js +2 -1
  21. package/src/svgii/geometry_bbox_element.js +46 -0
  22. package/src/svgii/pathData_analyze.js +1 -1
  23. package/src/svgii/pathData_convert.js +143 -29
  24. package/src/svgii/pathData_parse.js +2 -99
  25. package/src/svgii/pathData_parse_els.js +198 -125
  26. package/src/svgii/pathData_simplify_refineCorners.js +72 -43
  27. package/src/svgii/pathData_stringify.js +6 -5
  28. package/src/svgii/poly_normalize.js +21 -1
  29. package/src/svgii/rounding.js +36 -5
  30. package/src/svgii/svg-styles-to-attributes-const.js +8 -3
  31. package/src/svgii/svg-styles-to-attributes.js +25 -6
  32. package/src/svgii/svg_cleanup.js +285 -40
  33. package/src/svgii/svg_el_parse_style_props.js +423 -0
  34. package/src/svgii/stringify.js +0 -103
@@ -85,32 +85,63 @@ export function detectAccuracy(pathData) {
85
85
  }
86
86
 
87
87
 
88
+
89
+
88
90
  export function roundTo(num = 0, decimals = 3) {
89
91
  if (!decimals) return Math.round(num);
90
92
  let factor = 10 ** decimals;
91
93
  return Math.round(num * factor) / factor;
92
94
  }
93
95
 
96
+ /**
97
+ * round to reasonable
98
+ * floating point accuracy
99
+ * based on numeric value
100
+ */
101
+ export function autoRound(val, integerThresh = 10){
102
+ let decimals=8;
103
+
104
+ //console.log('val', val);
105
+
106
+ if(val>integerThresh){
107
+ decimals=0
108
+ }
109
+ else if(val>5){
110
+ decimals=1
111
+ }else{
112
+ decimals=Math.ceil(integerThresh/val).toString().length
113
+ }
114
+
115
+ let factor = 10 ** decimals;
116
+ return Math.round(val * factor) / factor;
117
+ }
118
+
119
+
120
+
94
121
 
95
122
  /**
96
123
  * round path data
97
124
  * either by explicit decimal value or
98
125
  * based on suggested accuracy in path data
99
126
  */
100
- export function roundPathData(pathData, decimals = -1) {
127
+ export function roundPathData(pathData, decimalsGlobal = -1) {
101
128
 
102
- if (decimals < 0) return pathData;
129
+ if (decimalsGlobal < 0) return pathData;
103
130
 
104
131
  let len = pathData.length;
132
+ //let decimals = pathData[0].decimals ? pathData[0].decimals+1 : decimalsGlobal
133
+ let decimals = decimalsGlobal
134
+ //decimals = decimalsGlobal;
135
+ //console.log('decimals subpath', decimals, pathData[0].decimals, 'decimalsGlobal', decimalsGlobal);
105
136
 
106
137
  for (let c = 0; c < len; c++) {
107
- //let com = pathData[c];
108
- let values = pathData[c].values
138
+ let com = pathData[c];
139
+ let {values} = com
140
+ //let values = pathData[c].values
109
141
  let valLen = values.length;
110
142
  if (!valLen) continue
111
143
 
112
144
  for (let v = 0; v < valLen; v++) {
113
- //pathData[c].values[v] = +values[v].toFixed(decimals);
114
145
  pathData[c].values[v] = roundTo(values[v], decimals);
115
146
  }
116
147
  };
@@ -4,7 +4,7 @@
4
4
  * used to remove unnecessary attribution
5
5
  */
6
6
 
7
- const shapeEls = [
7
+ export const shapeEls = [
8
8
  "polygon",
9
9
  "polyline",
10
10
  "line",
@@ -13,17 +13,22 @@ const shapeEls = [
13
13
  "ellipse",
14
14
  ]
15
15
 
16
- const geometryEls = [
16
+ export const horizontalProps = ['x', 'cx', 'rx', 'dx', 'width', 'translateX'];
17
+ export const verticalProps = ['y', 'cy', 'ry', 'dy', 'height', 'translateY'];
18
+
19
+ export const geometryEls = [
17
20
  "path",
18
21
  ...shapeEls
19
22
  ]
20
23
 
21
- const textEls = [
24
+ export const textEls = [
22
25
  "textPath",
23
26
  "text",
24
27
  "tspan",
25
28
  ]
26
29
 
30
+ export const strokeAtts = ['stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin','stroke-linecap', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-miterlimit', 'stroke-opacity' ];
31
+
27
32
  export const attLookup = {
28
33
 
29
34
  atts: {
@@ -1,6 +1,26 @@
1
- import { getMatrix, parseCSSTransform } from './svg-styles-getTransforms';
1
+ import { getMatrix, parseCSSTransform, parseTransform } from './svg-styles-getTransforms';
2
2
  import { attLookup } from './svg-styles-to-attributes-const';
3
3
 
4
+ /*
5
+ export function parseStyleProperty(propName='', value=''){
6
+
7
+ let propObj = {}
8
+ //console.log(propName, value);
9
+
10
+ // check for numbers or units
11
+ if(propName==='transform'){
12
+
13
+ //let trans = parseCSSTransform()
14
+
15
+ }
16
+ else if(propName==='d'){
17
+ propObj = {name:propName, value}
18
+ }
19
+
20
+ return propObj
21
+
22
+ }
23
+ */
4
24
 
5
25
 
6
26
  export function getElementProps(el, {
@@ -84,9 +104,11 @@ export function svgStylesToAttributes(el, {
84
104
  let cssProps = getElStyleProps(el)
85
105
 
86
106
  // normalize transform attributes
107
+ /*
87
108
  if (attProps['transform']) {
88
109
  console.log(`attProps['transform']`, attProps['transform']);
89
110
  }
111
+ */
90
112
 
91
113
  // merge properties
92
114
  let props = {
@@ -198,11 +220,7 @@ export function getElAttributes(el) {
198
220
  }
199
221
 
200
222
 
201
- function getUnit(val) {
202
- return val && isNaN(val) ? val.match(/[^\d|.]+/g)[0] : '';
203
- }
204
-
205
-
223
+ /*
206
224
  function roundValue(value = '', decimals = -1) {
207
225
  if (decimals < 0) return value;
208
226
  value = value.replace(/["]/g, '').trim()
@@ -215,3 +233,4 @@ function roundValue(value = '', decimals = -1) {
215
233
  //console.log('rounded', value)
216
234
  return value;
217
235
  }
236
+ */
@@ -1,7 +1,16 @@
1
+ import { getElementAtts } from "../svg-getAttributes";
1
2
  import { flattenTransforms } from "../svg_flatten_transforms";
3
+ import { getViewBox } from "../svg_getViewbox";
4
+ import { 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";
9
+ import { parsePathDataNormalized } from "./pathData_convert";
10
+ import { pathElToShape, shapeElToPath } from "./pathData_parse_els";
4
11
  import { svgStylesToAttributes } from "./svg-styles-to-attributes";
12
+ import { strokeAtts } from "./svg-styles-to-attributes-const";
13
+ import { parseStylesProperties } from "./svg_el_parse_style_props";
5
14
 
6
15
 
7
16
  export function removeEmptySVGEls(svg) {
@@ -14,28 +23,41 @@ export function removeEmptySVGEls(svg) {
14
23
  //const DOMParserPoly = globalThis.DOMParser;
15
24
 
16
25
  export function cleanUpSVG(svgMarkup, {
17
- returnDom = false,
18
26
  removeHidden = true,
19
- removeUnused = true,
27
+ //removeUnused = true,
20
28
  stylesToAttributes = true,
21
29
  removePrologue = true,
22
30
  removeIds = false,
23
31
  removeClassNames = false,
24
32
  removeDimensions = false,
25
- fixHref = true,
33
+ fixHref = false,
34
+ legacyHref = false,
35
+ cleanupDefs = true,
36
+ cleanupClip = true,
37
+ addViewBox = false,
38
+ addDimensions = false,
39
+
26
40
  mergePaths = false,
41
+ removeOffCanvas = true,
27
42
  cleanupSVGAtts = true,
28
43
  removeNameSpaced = true,
29
44
  attributesToGroup = true,
30
- shapesToPaths = false,
45
+ //shapesToPaths = false,
46
+ shapeConvert = false,
47
+ convert_rects = false,
48
+ convert_ellipses = false,
49
+ convert_poly = false,
50
+ convert_lines=false,
51
+
31
52
  convertTransforms = false,
32
- cleanUpStrokes=true,
53
+ cleanUpStrokes = true,
33
54
  decimals = -1,
34
55
  excludedEls = [],
35
56
  } = {}) {
36
57
 
37
58
  attributesToGroup = cleanupSVGAtts ? true : false;
38
59
 
60
+
39
61
  // replace namespaced refs
40
62
  if (fixHref) svgMarkup = svgMarkup.replaceAll("xlink:href=", "href=");
41
63
 
@@ -43,22 +65,57 @@ export function cleanUpSVG(svgMarkup, {
43
65
  .parseFromString(svgMarkup, "text/html")
44
66
  .querySelector("svg");
45
67
 
68
+ let viewBox = getViewBox(svg)
69
+ let { x, y, width, height } = viewBox;
70
+
46
71
 
47
72
  if (cleanupSVGAtts) {
48
73
  //console.log('cleanupSVGAtts');
49
74
  let allowed = ['viewBox', 'xmlns', 'width', 'height', 'id', 'class', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin'];
50
75
  removeExcludedAttribues(svg, allowed)
76
+ }
51
77
 
78
+ // add viewBox
79
+ if (addViewBox) addSvgViewBox(svg, { x, y, width, height })
80
+ if (addDimensions) {
81
+ svg.setAttribute('width', width);
82
+ svg.setAttribute('height', height);
52
83
  }
53
84
 
85
+
86
+ // remove unused defs or optimize order
87
+ if (cleanupDefs) cleanupSvgDefs(svg, { x, y, width, height, cleanupClip });
88
+
89
+
90
+ // remove off canvas
91
+ if (removeOffCanvas) removeOffCanvasEls(svg, { x, y, width, height });
92
+
93
+
54
94
  // always remove scripts
55
95
  let removeEls = ['metadata', 'script', ...excludedEls]
56
96
 
57
97
  let els = svg.querySelectorAll('*')
58
- let elProps = []
98
+
99
+ // an array of all elements' properties
100
+ let svgElProps = []
59
101
 
60
102
  let geometryElements = ['polygon', 'polyline', 'line', 'rect', 'circle', 'ellipse']
61
103
 
104
+ //console.log('shapeConvert', shapeConvert);
105
+
106
+
107
+ /** convert paths to shapes */
108
+ if(shapeConvert === 'toShapes'){
109
+ let paths = svg.querySelectorAll('path')
110
+ paths.forEach(path=>{
111
+ let shape = pathElToShape(path, {convert_rects, convert_ellipses, convert_poly, convert_lines})
112
+ path.replaceWith(shape)
113
+ path = shape;
114
+ //console.log('path', path);
115
+ })
116
+
117
+ }
118
+
62
119
 
63
120
  for (let i = 0; i < els.length; i++) {
64
121
  let el = els[i];
@@ -66,8 +123,8 @@ export function cleanUpSVG(svgMarkup, {
66
123
  let name = el.nodeName.toLowerCase();
67
124
 
68
125
  // convert shapes
69
- if (shapesToPaths && name !== 'path' && geometryElements.includes(name)) {
70
- let path = shapeElToPath(el);
126
+ if (shapeConvert === 'toPaths' && name !== 'path' && geometryElements.includes(name)) {
127
+ let path = shapeElToPath(el, { width, height, convert_rects, convert_ellipses, convert_poly, convert_lines });
71
128
  el.replaceWith(path)
72
129
  name = 'path'
73
130
  el = path;
@@ -83,19 +140,61 @@ export function cleanUpSVG(svgMarkup, {
83
140
  continue;
84
141
  }
85
142
 
143
+
144
+ /**
145
+ * get all style properties
146
+ * convert relative or physical units
147
+ * to user units
148
+ */
149
+
150
+
151
+ /*
152
+ let styleProps = parseStylesProperties(el, {
153
+ width:viewBox.width,
154
+ height:viewBox.height
155
+ })
156
+ */
157
+
158
+
159
+ /*
160
+ let propTest = normalizeUnits('50%',{width:200, height:100, isHorizontal:true})
161
+ console.log('propTest', propTest);
162
+ */
163
+
164
+
86
165
  // styles to attributes
87
- if (stylesToAttributes || attributesToGroup || mergePaths) {
166
+ if (stylesToAttributes || attributesToGroup || mergePaths || cleanUpStrokes) {
88
167
  let propsFiltered = svgStylesToAttributes(el, { removeNameSpaced, decimals })
89
- if (name === 'path') {
90
- elProps.push({ el, name, idx: i, propsFiltered })
91
- }
168
+ //if (name === 'path') {}
169
+ svgElProps.push({ el, name, idx: i, propsFiltered })
92
170
  }
171
+
93
172
  }
94
173
 
95
174
 
175
+
176
+ // remove stroke properties if no stroke color applied - common inkscape issue
177
+ if (cleanUpStrokes) {
178
+
179
+ for (let item of svgElProps) {
180
+
181
+ let { el, propsFiltered } = item;
182
+ let strokeProps = Object.keys(propsFiltered)
183
+
184
+ if (!strokeProps.includes('stroke')) {
185
+ strokeAtts.forEach(att => {
186
+ el.removeAttribute(att)
187
+
188
+ // delete in property object
189
+ if (item['propsFiltered'][att] !== undefined) delete item['propsFiltered'][att]
190
+ })
191
+ }
192
+ }
193
+ }
194
+
96
195
  // group styles
97
196
  if (attributesToGroup || mergePaths) {
98
- moveAttributesToGroup(elProps, mergePaths)
197
+ moveAttributesToGroup(svgElProps, mergePaths)
99
198
  }
100
199
 
101
200
  if (removeDimensions) {
@@ -116,54 +215,195 @@ export function cleanUpSVG(svgMarkup, {
116
215
  //console.log('!!!svgMarkup', svgMarkup);
117
216
 
118
217
 
119
- // remove empty defs
218
+
219
+ /**
220
+ * refine properties
221
+ * such as transforms or properties including units
222
+ */
223
+
224
+ /*
225
+ for(let i=0; i<svgElProps.length; i++){
226
+ let item = svgElProps[i];
227
+ let {propsFiltered} = item;
228
+
229
+ for(let prop in propsFiltered){
230
+
231
+ let propOb = parseStyleProperty(prop, propsFiltered[prop])
232
+
233
+ }
234
+
235
+ }
236
+ */
237
+
238
+ // remove futile clip-paths
239
+ if (cleanupClip) removeFutileClipPaths(svg, { x, y, width, height })
240
+
241
+
242
+ // replace href attributes with namespace - required by many older applications
243
+ if (legacyHref) {
244
+ svg.setAttribute('xmlns:xlink', "http://www.w3.org/1999/xlink")
245
+ let hrefs = svg.querySelectorAll('[href]')
246
+ hrefs.forEach(el => {
247
+ let href = el.getAttribute('href')
248
+ el.setAttribute('xlink:href', href)
249
+ el.removeAttribute('href')
250
+ })
251
+ }
252
+
253
+
254
+
255
+ return { svg, svgElProps }
256
+
257
+ }
258
+
259
+
260
+ function removeOffCanvasEls(svg, { x = 0, y = 0, width = 0, height = 0 } = {}) {
261
+ let els = [...svg.querySelectorAll('path, polygon, polyline, line, rect, circle, ellipse, text')];
262
+ els = els.filter(el => !el.parentNode.closest('defs') && !el.parentNode.closest('symbol') && !el.parentNode.closest('clipPath') && !el.parentNode.closest('mask') && !el.parentNode.closest('pattern'))
263
+ //console.log('removeOffCanvasEls', els, width, height);
264
+
265
+ let bb0 = { x, y, width, height }
266
+ bb0.right = x + width
267
+ bb0.bottom = y + height
268
+
269
+ els.forEach(el => {
270
+ let bb = getElBBox(el)
271
+ let outside = bb.right < bb0.x || bb.bottom < bb0.y || bb.x > bb0.right || bb.y > bb.bottom
272
+ if (outside) el.remove();
273
+ })
274
+
275
+ }
276
+
277
+ function addSvgViewBox(svg, { x = 0, y = 0, width = 0, height = 0 } = {}) {
278
+ if (svg.hasAttribute('viewBox')) return;
279
+ if (!width || !height) {
280
+ ({ x, y, width, height } = getViewBox(svg));
281
+ }
282
+ svg.setAttribute('viewBox', [x, y, width, height].join(' '))
283
+ }
284
+
285
+
286
+ function cleanupSvgDefs(svg, { x = 0, y = 0, width = 0, height = 0, cleanupClip = true } = {}) {
120
287
  let defs = svg.querySelectorAll('defs')
121
- defs.forEach(def=>{
122
- let children=[...def.children]
123
- let attributes = [...def.attributes]
124
- if(!children.length){
288
+ let defEls = svg.querySelectorAll('symbol, pattern, linearGradient, radialGradient, clipPath, mask, marker, filter')
289
+
290
+ // no defs to remove/optimize
291
+ if (!defs.length && !defEls.length) return;
292
+
293
+ defs.forEach(def => {
294
+ // remove empty defs
295
+ let children = [...def.children]
296
+ if (!children.length) {
125
297
  def.remove()
126
298
  }
299
+ // move defs to top
300
+ else {
301
+ svg.insertBefore(def, svg.children[0])
302
+ }
127
303
  })
128
304
 
305
+ //clean up unused defs
306
+ let refIds = new Set([])
307
+ defEls.forEach(def => {
308
+ refIds.add(def.id)
309
+ })
129
310
 
130
- /*
131
- if(convertTransforms){
132
- flattenTransforms(svg)
311
+ Array.from(refIds).forEach(id => {
312
+ let els = svg.querySelectorAll(`[href="#${id}"], [xlink\\:href="#${id}"], [clip-path="url(#${id})"], [mask="url(#${id})"], [fill="url(#${id})"], [stroke="url(#${id})"]`);
313
+
314
+ //definition is unused – remove
315
+ if (!els.length) {
316
+ //console.log('remove', id);
317
+ svg.getElementById(id).remove()
318
+ }
319
+ })
320
+
321
+ // remove futile clip-paths
322
+ //if (cleanupClip) removeFutileClipPaths(svg, {x, y, width, height})
323
+
324
+ }
325
+
326
+
327
+ function removeFutileClipPaths(svg, { x = 0, y = 0, width = 0, height = 0 } = {}) {
328
+ let clipPaths = svg.querySelectorAll('clipPath');
329
+
330
+ if (!clipPaths.length) return
331
+
332
+ if (!width || !height) {
333
+ ({ x, y, width, height } = getViewBox(svg));
133
334
  }
134
- */
135
335
 
336
+ clipPaths.forEach(clip => {
337
+ let children = [...clip.children];
338
+ if (children.length > 1) return;
339
+
340
+ let clipEl = children[0]
341
+ let type = clipEl.nodeName.toLowerCase();
342
+
343
+ if (type === 'path' || type === 'rect') {
344
+ let bb = { x: 0, y: 0, width: 0, height: 0 }
136
345
 
137
- if (returnDom) return svg
138
- let markup = stringifySVG(svg)
346
+ if (type === 'path') {
347
+ let pathData = parsePathDataNormalized(clipEl.getAttribute('d'));
348
+ let coms = Array.from(new Set(pathData.map(com => com.type.toLowerCase()))).join('');
349
+ let isPolygon = !(/[acqts]/gi).test(coms);
139
350
 
140
- //console.log(svg.outerHTML);
351
+ // path is too complex - unlikely to be a rectangle
352
+ if (!isPolygon || pathData.length > 5) return
141
353
 
354
+ let vertices = getPathDataVertices(pathData)
355
+ bb = getPolyBBox(vertices)
356
+ }
357
+
358
+ else if (type === 'rect') {
359
+ bb = { x: +clipEl.getAttribute('x'), y: +clipEl.getAttribute('y'), width: +clipEl.getAttribute('width'), height: +clipEl.getAttribute('height') }
360
+ }
361
+
362
+ // is futile if clip path's bbox equals the SVG's viewBox
363
+ if (bb.x === x && bb.y === y && bb.width === width && bb.height === height) {
364
+ clip.remove();
365
+ let clippedEls = svg.querySelectorAll(`[clip-path="url(#${clip.id})"]`);
366
+ //console.log('clippedEls', clippedEls);
367
+ clippedEls.forEach(clipped => {
368
+ clipped.removeAttribute('clip-path')
369
+ })
370
+ }
371
+ }
372
+ })
142
373
 
143
- return markup;
144
374
  }
145
375
 
146
376
 
147
- function moveAttributesToGroup(elProps = [], mergePaths = true) {
148
377
 
149
- let combine = [[elProps[0]]]
378
+ function moveAttributesToGroup(svgElProps = [], mergePaths = true) {
379
+
380
+ let combine = [[svgElProps[0]]]
150
381
  let idx = 0;
151
382
  let lastProps = '';
152
- let l = elProps.length;
153
- let itemsWithProps = elProps.filter(item => item.propstr)
383
+ let l = svgElProps.length;
384
+ let itemsWithProps = svgElProps.filter(item => item.propstr)
154
385
  let path0;
155
386
 
156
387
 
157
388
  // merge paths without properties
389
+ let dCombined = ''
158
390
  if (!itemsWithProps.length && mergePaths) {
159
- let item0 = elProps[0]
160
- path0 = item0.el
161
- let dCombined = item0.propsFiltered.d
391
+ let path0 = null;
392
+
393
+ for (let i = 0; i < l; i++) {
394
+ let item = svgElProps[i]
395
+ if (item.name !== 'path') continue;
396
+ let remove = true;
397
+
162
398
 
163
- for (let i = 1; i < l; i++) {
164
- let item = elProps[i]
165
399
  let path = item.el;
166
400
 
401
+ // set 1st path
402
+ if (!path0) {
403
+ path0 = path;
404
+ remove = false;
405
+ }
406
+
167
407
  let d = item.propsFiltered.d
168
408
  let isAbs = d.startsWith('M')
169
409
  let dAbs = isAbs ? d : parsePathDataString(d).pathData.map(com => `${com.type} ${com.values.join(' ')}`).join(' ')
@@ -171,17 +411,18 @@ function moveAttributesToGroup(elProps = [], mergePaths = true) {
171
411
  dCombined += dAbs;
172
412
 
173
413
  // delete path el
174
- path.remove();
414
+ if (remove) path.remove();
175
415
  }
176
416
 
177
- path0.setAttribute('d', dCombined)
417
+ //console.log('dCombined', dCombined);
418
+ if (path0) path0.setAttribute('d', dCombined)
178
419
  return
179
420
  }
180
421
 
181
422
 
182
423
  // add to combine chunks
183
424
  for (let i = 0; i < l; i++) {
184
- let item = elProps[i];
425
+ let item = svgElProps[i];
185
426
  let props = item.propsFiltered;
186
427
  let propstr = [];
187
428
  for (let prop in props) {
@@ -256,6 +497,8 @@ function moveAttributesToGroup(elProps = [], mergePaths = true) {
256
497
 
257
498
  let dAbs = isAbs ? d : parsePathDataString(d).pathData.map(com => `${com.type} ${com.values.join(' ')}`).join(' ')
258
499
 
500
+ console.log('dAbs', dAbs);
501
+
259
502
  //console.log(isAbs, dAbs);
260
503
  // concat pathdata string
261
504
  dCombined += dAbs;
@@ -333,7 +576,9 @@ export function stringifySVG(svg, omitNamespace = false) {
333
576
  //.replace(/ +/g, ' ')
334
577
  .replace(/> </g, '><')
335
578
  .trim()
336
-
579
+ // sanitize linebreaks within pathdata
580
+ .replaceAll('&#10;', '\n');
581
+
337
582
 
338
583
  return markup
339
584
  }