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.
Files changed (61) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +7 -4
  3. package/dist/svg-path-simplify.esm.js +3593 -1279
  4. package/dist/svg-path-simplify.esm.min.js +2 -2
  5. package/dist/svg-path-simplify.js +3594 -1278
  6. package/dist/svg-path-simplify.min.js +2 -2
  7. package/dist/svg-path-simplify.pathdata.esm.js +1017 -538
  8. package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
  9. package/dist/svg-path-simplify.poly.cjs +9 -8
  10. package/docs/privacy-webapp.md +24 -0
  11. package/index.html +331 -152
  12. package/package.json +1 -1
  13. package/src/constants.js +4 -0
  14. package/src/css_parse.js +317 -0
  15. package/src/detect_input.js +76 -28
  16. package/src/index.js +8 -0
  17. package/src/pathData_simplify_cubic.js +26 -16
  18. package/src/pathData_simplify_harmonize_cpts.js +77 -1
  19. package/src/pathData_simplify_revertToquadratics.js +0 -1
  20. package/src/pathSimplify-main.js +304 -276
  21. package/src/pathSimplify-only-pathdata.js +7 -2
  22. package/src/pathSimplify-presets.js +254 -0
  23. package/src/poly-fit-curve-schneider.js +14 -7
  24. package/src/simplify_poly_RC.js +102 -0
  25. package/src/simplify_poly_RDP.js +109 -1
  26. package/src/simplify_poly_radial_distance.js +3 -3
  27. package/src/string_helpers.js +130 -4
  28. package/src/svg-getAttributes.js +4 -2
  29. package/src/svgii/convert_units.js +1 -1
  30. package/src/svgii/geometry.js +322 -5
  31. package/src/svgii/geometry_bbox_element.js +1 -1
  32. package/src/svgii/geometry_deduceRadius.js +116 -27
  33. package/src/svgii/geometry_length.js +253 -0
  34. package/src/svgii/pathData_analyze.js +18 -0
  35. package/src/svgii/pathData_convert.js +193 -89
  36. package/src/svgii/pathData_fix_directions.js +12 -14
  37. package/src/svgii/pathData_fromPoly.js +3 -3
  38. package/src/svgii/pathData_getLength.js +86 -0
  39. package/src/svgii/pathData_parse.js +2 -0
  40. package/src/svgii/pathData_parse_els.js +66 -68
  41. package/src/svgii/pathData_reorder.js +122 -16
  42. package/src/svgii/pathData_simplify_refineCorners.js +130 -35
  43. package/src/svgii/pathData_simplify_refine_round.js +420 -0
  44. package/src/svgii/pathData_split_to_groups.js +168 -0
  45. package/src/svgii/pathData_stringify.js +26 -64
  46. package/src/svgii/pathData_toPolygon.js +3 -4
  47. package/src/svgii/poly_analyze.js +61 -0
  48. package/src/svgii/poly_normalize.js +11 -2
  49. package/src/svgii/poly_to_pathdata.js +85 -24
  50. package/src/svgii/rounding.js +80 -78
  51. package/src/svgii/svg_cleanup.js +421 -619
  52. package/src/svgii/svg_cleanup_convertPathLength.js +39 -0
  53. package/src/svgii/svg_cleanup_general_svg_atts.js +97 -0
  54. package/src/svgii/svg_cleanup_normalize_transforms.js +83 -0
  55. package/src/svgii/svg_cleanup_remove_els_and_atts.js +77 -0
  56. package/src/svgii/svg_cleanup_ungroup.js +36 -0
  57. package/src/svgii/svg_el_parse_style_props.js +72 -47
  58. package/src/svgii/svg_getElementLength.js +67 -0
  59. package/src/svgii/svg_validate.js +220 -0
  60. package/tests/testSVG.js +14 -1
  61. package/src/svgii/pathData_refine_round.js +0 -222
@@ -16,29 +16,25 @@ import { autoRound, roundTo } from './rounding.js';
16
16
  import { attLookup } from './svg-styles-to-attributes-const.js';
17
17
  import { qrDecomposeMatrix } from './transform_qr_decompose.js';
18
18
 
19
-
19
+ /**
20
+ * Convert shapes to paths
21
+ * converts also transforms
22
+ */
20
23
  export function shapeElToPath(el, { width = 0,
21
24
  height = 0,
22
- convert_rects = false,
23
- convert_ellipses = false,
24
- convert_poly = false,
25
- convert_lines = false,
26
- //matrix={a:1, b:0, c:0, d:1, e:0, f:0},
27
- matrix=null
25
+ convertShapes = [],
26
+ matrix = null
28
27
 
29
28
  } = {}) {
30
29
 
30
+
31
31
  let nodeName = el.nodeName.toLowerCase();
32
32
  //console.log('shapeElToPath', nodeName);
33
33
 
34
34
 
35
- if (
36
- nodeName === 'path' && !matrix ||
37
- nodeName === 'rect' && !convert_rects ||
38
- (nodeName === 'circle' || nodeName === 'ellipse') && !convert_ellipses ||
39
- (nodeName === 'polygon' || nodeName === 'polyline') && !convert_poly ||
40
- (nodeName === 'line') && !convert_lines
41
- ) return el;
35
+
36
+ if (!convertShapes.includes(nodeName)) return el;
37
+ //console.log(convertShapes);
42
38
 
43
39
 
44
40
  let pathData = getPathDataFromEl(el, { width, height });
@@ -47,8 +43,9 @@ export function shapeElToPath(el, { width = 0,
47
43
  let exclude = ['d', 'x', 'y', 'x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'dx', 'dy', 'r', 'rx', 'ry', 'width', 'height', 'points'];
48
44
 
49
45
  // transform pathData
50
- if(matrix && Object.values(matrix).join('')!=='100100'){
46
+ if (matrix && Object.values(matrix).join('') !== '100100') {
51
47
  pathData = transformPathData(pathData, matrix)
48
+ //console.log('transformPathData', pathData);
52
49
  exclude.push('transform', 'transform-origin')
53
50
  }
54
51
 
@@ -72,14 +69,7 @@ export function shapeElToPath(el, { width = 0,
72
69
  return pathN
73
70
 
74
71
  }
75
- /*
76
- export function copyAttributes(newEl, oldEl){
77
72
 
78
- let attributes = [...oldEl.attributes].map(att => att.name);
79
-
80
-
81
- }
82
- */
83
73
 
84
74
 
85
75
  // retrieve pathdata from svg geometry elements
@@ -112,39 +102,7 @@ export function getPathDataFromEl(el, {
112
102
  case 'rect':
113
103
  attNames = ['x', 'y', 'width', 'height', 'rx', 'ry'];
114
104
  ({ x=0, y=0, width=0, height=0, rx=0, ry=0 } = atts);
115
-
116
- if (!rx && !ry) {
117
- pathData = [
118
- { type: "M", values: [x, y] },
119
- { type: "L", values: [x + width, y] },
120
- { type: "L", values: [x + width, y + height] },
121
- { type: "L", values: [x, y + height] },
122
- { type: "Z", values: [] }
123
- ];
124
- } else {
125
-
126
- rx = rx ? rx : ry;
127
- ry = ry ? ry : rx;
128
-
129
- if (rx > width / 2) {
130
- rx = width / 2;
131
- }
132
- if (ry > height / 2) {
133
- ry = height / 2;
134
- }
135
- pathData = [
136
- { type: "M", values: [x + rx, y] },
137
- { type: "L", values: [x + width - rx, y] },
138
- { type: "A", values: [rx, ry, 0, 0, 1, x + width, y + ry] },
139
- { type: "L", values: [x + width, y + height - ry] },
140
- { type: "A", values: [rx, ry, 0, 0, 1, x + width - rx, y + height] },
141
- { type: "L", values: [x + rx, y + height] },
142
- { type: "A", values: [rx, ry, 0, 0, 1, x, y + height - ry] },
143
- { type: "L", values: [x, y + ry] },
144
- { type: "A", values: [rx, ry, 0, 0, 1, x + rx, y] },
145
- { type: "Z", values: [] }
146
- ];
147
- }
105
+ pathData = rectToPathData(x, y, width, height, rx, ry);
148
106
  break;
149
107
 
150
108
  case 'circle':
@@ -164,7 +122,6 @@ export function getPathDataFromEl(el, {
164
122
  ry = ry ? ry : r;
165
123
  }
166
124
 
167
-
168
125
  // simplified radii for circles
169
126
  let rxS = isCircle && r >= 1 ? 1 : rx;
170
127
  let ryS = isCircle && r >= 1 ? 1 : ry;
@@ -210,13 +167,51 @@ export function getPathDataFromEl(el, {
210
167
  };
211
168
 
212
169
 
170
+ export function rectToPathData(x = 0, y = 0, width = 0, height = 0, rx = 0, ry = 0) {
171
+ let pathData = [];
172
+
173
+ if (!rx && !ry) {
174
+ pathData = [
175
+ { type: "M", values: [x, y] },
176
+ { type: "L", values: [x + width, y] },
177
+ { type: "L", values: [x + width, y + height] },
178
+ { type: "L", values: [x, y + height] },
179
+ { type: "Z", values: [] }
180
+ ];
181
+ } else {
182
+
183
+ rx = rx ? rx : ry;
184
+ ry = ry ? ry : rx;
185
+
186
+ if (rx > width / 2) {
187
+ rx = width / 2;
188
+ }
189
+ if (ry > height / 2) {
190
+ ry = height / 2;
191
+ }
192
+ pathData = [
193
+ { type: "M", values: [x + rx, y] },
194
+ { type: "L", values: [x + width - rx, y] },
195
+ { type: "A", values: [rx, ry, 0, 0, 1, x + width, y + ry] },
196
+ { type: "L", values: [x + width, y + height - ry] },
197
+ { type: "A", values: [rx, ry, 0, 0, 1, x + width - rx, y + height] },
198
+ { type: "L", values: [x + rx, y + height] },
199
+ { type: "A", values: [rx, ry, 0, 0, 1, x, y + height - ry] },
200
+ { type: "L", values: [x, y + ry] },
201
+ { type: "A", values: [rx, ry, 0, 0, 1, x + rx, y] },
202
+ { type: "Z", values: [] }
203
+ ];
204
+ }
205
+
206
+ return pathData
207
+ }
208
+
209
+
210
+
213
211
 
214
212
 
215
213
  export function pathElToShape(el, {
216
- convert_rects = false,
217
- convert_ellipses = false,
218
- convert_poly = false,
219
- convert_lines = false
214
+ convertShapes = [],
220
215
  } = {}) {
221
216
 
222
217
  //console.log('pathElToShape', convert_rects, convert_ellipses, convert_lines );
@@ -236,10 +231,13 @@ export function pathElToShape(el, {
236
231
  let attsNew = {}
237
232
  let decimals = 7;
238
233
 
234
+
239
235
  if (isPoly) {
240
236
 
237
+ //console.log('pathsToShapes', isPoly);
238
+
241
239
  // is line
242
- if (pathData.length === 2 && convert_lines) {
240
+ if (pathData.length === 2 && convertShapes.includes('line')) {
243
241
  type = 'line'
244
242
  shape = document.createElementNS(svgNs, type)
245
243
  let [x1, y1, x2, y2] = [...pathData[0].values, ...pathData[1].values].map(val => roundTo(val, decimals))
@@ -255,7 +253,7 @@ export function pathElToShape(el, {
255
253
  let areaDiff = Math.abs(1 - areaRect / areaPoly);
256
254
 
257
255
  // is rect
258
- if (convert_rects && areaDiff < 0.01) {
256
+ if (convertShapes.includes('rect') && areaDiff < 0.01) {
259
257
  type = 'rect'
260
258
  shape = document.createElementNS(svgNs, type)
261
259
  let { x, y, width, height } = bb
@@ -263,7 +261,7 @@ export function pathElToShape(el, {
263
261
 
264
262
  }
265
263
  // polyline or polygon
266
- else if(convert_poly) {
264
+ else if (convertShapes.includes('polygon') || convertShapes.includes('polyline')) {
267
265
  type = closed ? 'polygon' : 'polyline';
268
266
  shape = document.createElementNS(svgNs, type)
269
267
  let points = vertices.map(pt => { return [pt.x, pt.y] }).flat().map(val => roundTo(val, decimals)).join(' ')
@@ -272,7 +270,7 @@ export function pathElToShape(el, {
272
270
  }
273
271
  }
274
272
  // circles or ellipses
275
- else if (!hasLines && convert_ellipses) {
273
+ else if (!hasLines && (convertShapes.includes('circle') || convertShapes.includes('ellipse'))) {
276
274
 
277
275
  // try to convert cubics to arcs
278
276
  if (!hasArcs && hasBeziers) {
@@ -308,11 +306,11 @@ export function pathElToShape(el, {
308
306
  rxVals = Array.from(rxVals)
309
307
  ryVals = Array.from(ryVals)
310
308
 
311
- if(cxVals.length===1 && cyVals.length===1 && rxVals.length===1 && ryVals.length===1){
309
+ if (cxVals.length === 1 && cyVals.length === 1 && rxVals.length === 1 && ryVals.length === 1) {
312
310
  let [rx, ry, cx, cy] = [rxVals[0], ryVals[0], cxVals[0], cyVals[0]]
313
- type = rx===ry ? 'circle' : 'ellipse';
311
+ type = rx === ry ? 'circle' : 'ellipse';
314
312
  shape = document.createElementNS(svgNs, type)
315
- attsNew = type==='circle' ? { r:rx, cx, cy } : {rx, ry, cx, cy}
313
+ attsNew = type === 'circle' ? { r: rx, cx, cy } : { rx, ry, cx, cy }
316
314
  }
317
315
  }
318
316
  }
@@ -334,11 +332,11 @@ export function pathElToShape(el, {
334
332
  shape.setAttribute(att, attributes[att])
335
333
  }
336
334
  }
337
-
338
335
  // replace
339
336
  el = shape;
340
337
  }
341
338
 
339
+ //console.log(el);
342
340
  return el;
343
341
 
344
342
  }
@@ -14,7 +14,6 @@ export function pathDataToTopLeft(pathData) {
14
14
  let len = pathData.length;
15
15
  let isClosed = pathData[len - 1].type.toLowerCase() === 'z'
16
16
 
17
- //return pathData;
18
17
 
19
18
  // we can't change starting point for non closed paths
20
19
  if (!isClosed) {
@@ -30,7 +29,7 @@ export function pathDataToTopLeft(pathData) {
30
29
  let { type, values } = com;
31
30
  let valsLen = values.length
32
31
  if (valsLen) {
33
- let p = { type: type, x: values[valsLen-2], y: values[valsLen-1], index: 0}
32
+ let p = { type: type, x: values[valsLen - 2], y: values[valsLen - 1], index: 0 }
34
33
  p.index = i
35
34
  indices.push(p)
36
35
  }
@@ -38,16 +37,124 @@ export function pathDataToTopLeft(pathData) {
38
37
 
39
38
  // reorder to top left most
40
39
  //|| a.x - b.x
41
- indices = indices.sort((a, b) => +a.y.toFixed(8) - +b.y.toFixed(8) || a.x-b.x );
40
+ indices = indices.sort((a, b) => +a.y.toFixed(8) - +b.y.toFixed(8) || a.x - b.x);
42
41
  newIndex = indices[0].index
43
42
 
44
- return newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
43
+ return newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
45
44
  }
46
45
 
47
46
 
47
+ export function optimizeClosePath(pathData, { removeFinalLineto = true, autoClose = true } = {}) {
48
48
 
49
+ let pathDataN = pathData;
50
+ let l = pathData.length;
51
+ let M = { x: +pathData[0].values[0].toFixed(8), y: +pathData[0].values[1].toFixed(8) }
52
+ let isClosed = pathData[l - 1].type.toLowerCase() === 'z'
53
+ //let linetos = pathData.filter(com => com.type === 'L')
54
+ //let hasLinetos = linetos.length;
55
+ let hasLinetos = false
56
+
57
+
58
+ // check if path is closed by explicit lineto
59
+ let idxPenultimate = isClosed ? l - 2 : l - 1
60
+ let penultimateCom = pathData[idxPenultimate];
61
+ let penultimateType = penultimateCom.type;
62
+ let penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8))
63
+
64
+ // last L command ends at M
65
+ let hasClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y
66
+ let lastIsLine = penultimateType === 'L'
67
+ //console.log(pathData);
68
+
69
+ // create index
70
+ let indices = [];
71
+ for (let i = 0; i < l; i++) {
72
+ let com = pathData[i];
73
+ let { type, values, p0, p } = com;
74
+
75
+ if(type==='L') hasLinetos = true;
76
+
77
+ // exclude Z
78
+ if (values.length) {
79
+ let valsL = values.slice(-2)
80
+
81
+ let x = Math.min(p0.x, p.x)
82
+ let y = Math.min(p0.y, p.y)
83
+
84
+ let prevCom = pathData[i - 1] ? pathData[i - 1] : pathData[idxPenultimate]
85
+ let prevType = prevCom.type
86
+ //let p = { type: type, x: valsL[0], y: valsL[1], dist: 0, index: 0, prevType }
87
+ let item = { type: type, x, y, index: 0, prevType }
88
+ item.index = i
89
+ indices.push(item)
90
+ }
91
+
92
+ }
93
+
94
+ let xMin = Infinity;
95
+ let yMin = Infinity;
96
+ let idx_top = null;
97
+ let len = indices.length
98
+
99
+
100
+ for (let i = 0; i < len; i++) {
101
+ let com = indices[i];
102
+ let { type, index, x, y, prevType } = com;
103
+
104
+ if (hasLinetos && prevType === 'L') {
105
+ if (x < xMin && y < yMin) {
106
+ idx_top = index-1;
107
+ }
108
+
109
+ if (y < yMin) {
110
+ yMin = y
111
+ }
112
+
113
+ if (x < xMin) {
114
+ xMin = x
115
+ }
116
+ }
117
+ }
118
+
119
+
120
+ // shift to better starting point
121
+ if (idx_top) {
122
+ pathDataN = shiftSvgStartingPoint(pathDataN, idx_top)
123
+
124
+ // update penultimate - reorder might have added new close paths
125
+ l = pathDataN.length
126
+ M = { x: +pathDataN[0].values[0].toFixed(8), y: +pathDataN[0].values[1].toFixed(8) }
127
+
128
+ idxPenultimate = isClosed ? l - 2 : l - 1
129
+ penultimateCom = pathDataN[idxPenultimate];
130
+ penultimateType = penultimateCom.type;
131
+ penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8))
132
+ lastIsLine = penultimateType ==='L'
133
+
134
+ // last L command ends at M
135
+ hasClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y
136
+
137
+ }
49
138
 
50
- export function optimizeClosePath(pathData, {removeFinalLineto = true, autoClose = true}={}) {
139
+
140
+ // remove unnecessary closing lineto
141
+ if (removeFinalLineto && hasClosingCommand && lastIsLine) {
142
+ pathDataN.splice(l - 2, 1)
143
+ }
144
+
145
+ // add close path
146
+ if (autoClose && !isClosed && hasClosingCommand) {
147
+ pathDataN.push({ type: 'Z', values: [] })
148
+ }
149
+
150
+ return pathDataN
151
+
152
+ }
153
+
154
+
155
+
156
+
157
+ export function optimizeClosePath__(pathData, { removeFinalLineto = true, autoClose = true } = {}) {
51
158
 
52
159
  let pathDataNew = [];
53
160
  let l = pathData.length;
@@ -58,17 +165,16 @@ export function optimizeClosePath(pathData, {removeFinalLineto = true, autoClose
58
165
 
59
166
 
60
167
  // check if order is ideal
61
- let idxPenultimate = isClosed ? l-2 : l-1
62
-
168
+ let idxPenultimate = isClosed ? l - 2 : l - 1
63
169
  let penultimateCom = pathData[idxPenultimate];
64
170
  let penultimateType = penultimateCom.type;
65
171
  let penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8))
66
172
 
67
173
  // last L command ends at M
68
- let isClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y
174
+ let hasClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y
69
175
 
70
176
  // add closepath Z to enable order optimizations
71
- if(!isClosed && autoClose && isClosingCommand){
177
+ if (!isClosed && autoClose && hasClosingCommand) {
72
178
 
73
179
  /*
74
180
  // adjust final coords
@@ -77,14 +183,14 @@ export function optimizeClosePath(pathData, {removeFinalLineto = true, autoClose
77
183
  pathData[idxPenultimate].values[valsLastLen-2] = M.x
78
184
  pathData[idxPenultimate].values[valsLastLen-1] = M.y
79
185
  */
80
-
81
- pathData.push({type:'Z', values:[]})
186
+
187
+ pathData.push({ type: 'Z', values: [] })
82
188
  isClosed = true;
83
189
  l++
84
190
  }
85
191
 
86
192
  // if last segment is not closing or a lineto
87
- let skipReorder = pathData[1].type !== 'L' && (!isClosingCommand || penultimateCom.type === 'L')
193
+ let skipReorder = pathData[1].type !== 'L' && (!hasClosingCommand || penultimateCom.type === 'L')
88
194
  skipReorder = false
89
195
 
90
196
 
@@ -143,13 +249,13 @@ export function optimizeClosePath(pathData, {removeFinalLineto = true, autoClose
143
249
  // remove last lineto
144
250
  penultimateCom = pathData[l - 2];
145
251
  penultimateType = penultimateCom.type;
146
- penultimateComCoords = penultimateCom.values.slice(-2).map(val=>+val.toFixed(8))
252
+ penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8))
147
253
 
148
- isClosingCommand = penultimateType === 'L' && penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y
254
+ hasClosingCommand = penultimateType === 'L' && penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y
149
255
 
150
- //console.log('penultimateCom', isClosingCommand, penultimateCom.values, M);
256
+ //console.log('penultimateCom', hasClosingCommand, penultimateCom.values, M);
151
257
 
152
- if (removeFinalLineto && isClosingCommand) {
258
+ if (removeFinalLineto && hasClosingCommand) {
153
259
  pathData.splice(l - 2, 1)
154
260
  }
155
261