svg-path-simplify 0.4.3 → 0.4.5

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 (41) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +2 -1
  3. package/dist/svg-path-simplify.esm.js +1670 -509
  4. package/dist/svg-path-simplify.esm.min.js +2 -2
  5. package/dist/svg-path-simplify.js +1671 -508
  6. package/dist/svg-path-simplify.min.js +2 -2
  7. package/dist/svg-path-simplify.pathdata.esm.js +936 -463
  8. package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
  9. package/dist/svg-path-simplify.poly.cjs +9 -8
  10. package/index.html +60 -20
  11. package/package.json +1 -1
  12. package/src/constants.js +4 -0
  13. package/src/detect_input.js +47 -29
  14. package/src/index.js +8 -0
  15. package/src/pathData_simplify_cubic.js +46 -18
  16. package/src/pathData_simplify_revertToquadratics.js +0 -1
  17. package/src/pathSimplify-main.js +81 -20
  18. package/src/pathSimplify-only-pathdata.js +7 -2
  19. package/src/pathSimplify-presets.js +14 -4
  20. package/src/svg-getAttributes.js +5 -3
  21. package/src/svgii/convert_units.js +1 -1
  22. package/src/svgii/geometry.js +140 -2
  23. package/src/svgii/geometry_bbox_element.js +1 -1
  24. package/src/svgii/geometry_deduceRadius.js +116 -27
  25. package/src/svgii/geometry_length.js +18 -2
  26. package/src/svgii/pathData_analyze.js +18 -0
  27. package/src/svgii/pathData_convert.js +188 -88
  28. package/src/svgii/pathData_fix_directions.js +10 -18
  29. package/src/svgii/pathData_reorder.js +123 -16
  30. package/src/svgii/pathData_simplify_refineCorners.js +130 -35
  31. package/src/svgii/pathData_simplify_refine_round.js +420 -0
  32. package/src/svgii/poly_normalize.js +9 -8
  33. package/src/svgii/rounding.js +112 -80
  34. package/src/svgii/svg_cleanup.js +75 -22
  35. package/src/svgii/svg_cleanup_convertPathLength.js +27 -15
  36. package/src/svgii/svg_cleanup_normalize_transforms.js +1 -1
  37. package/src/svgii/svg_cleanup_remove_els_and_atts.js +6 -1
  38. package/src/svgii/svg_el_parse_style_props.js +13 -10
  39. package/src/svgii/svg_validate.js +220 -0
  40. package/tests/testSVG.js +14 -1
  41. package/src/svgii/pathData_refine_round.js +0 -222
@@ -17,54 +17,10 @@ import { pathDataToD } from './pathData_stringify';
17
17
  import { roundPathData } from './rounding';
18
18
  import { renderPoint } from './visualize';
19
19
 
20
-
21
- export function parsePathDataNormalized(d,
22
- {
23
- // necessary for most calculations
24
- toAbsolute = true,
25
- toLonghands = true,
26
-
27
- // not necessary unless you need cubics only
28
- quadraticToCubic = false,
29
-
30
- // mostly a fallback if arc calculations fail
31
- arcToCubic = false,
32
- // arc to cubic precision - adds more segments for better precision
33
- arcAccuracy = 4,
34
- } = {}
35
- ) {
36
-
37
- // is already array
38
- let isArray = Array.isArray(d);
39
-
40
- // normalize native pathData to regular array
41
- let hasConstructor = isArray && typeof d[0] === 'object' && typeof d[0].constructor === 'function'
42
- /*
43
- if (hasConstructor) {
44
- d = d.map(com => { return { type: com.type, values: com.values } })
45
- console.log('hasConstructor', hasConstructor, (typeof d[0].constructor), d);
46
- }
47
- */
48
-
49
- let pathDataObj = isArray ? d : parsePathDataString(d);
50
-
51
- let { hasRelatives = true, hasShorthands = true, hasQuadratics = true, hasArcs = true } = pathDataObj;
52
- let pathData = hasConstructor ? pathDataObj : pathDataObj.pathData;
53
-
54
- //console.log('???quadraticToCubic', quadraticToCubic);
55
-
56
- // normalize
57
- pathData = normalizePathData(pathData,
58
- {
59
- toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy,
60
- hasRelatives, hasShorthands, hasQuadratics, hasArcs
61
- },
62
- )
63
-
64
- return pathData;
65
- }
66
-
67
-
20
+ /**
21
+ * wrapper function for
22
+ * all path data conversion
23
+ */
68
24
  export function convertPathData(pathData, {
69
25
  toShorthands = true,
70
26
  toLonghands = false,
@@ -126,23 +82,25 @@ export function convertPathData(pathData, {
126
82
  //console.log(toShorthands, toRelative, decimals);
127
83
  if (hasQuadratics && quadraticToCubic) pathData = pathDataQuadraticToCubic(pathData);
128
84
 
129
- if(toMixed) toRelative = true
85
+ if (toMixed) toRelative = true
130
86
 
131
87
  // pre round - before relative conversion to minimize distortions
132
88
  if (decimals > -1 && toRelative) pathData = roundPathData(pathData, decimals);
133
89
 
134
90
  // clone absolute pathdata
135
- if(toMixed){
91
+ if (toMixed) {
136
92
  pathDataAbs = JSON.parse(JSON.stringify(pathData))
137
93
  }
138
94
 
139
95
  if (toRelative) pathData = pathDataToRelative(pathData);
96
+
97
+ // final rounding
140
98
  if (decimals > -1) pathData = roundPathData(pathData, decimals);
141
99
 
142
100
 
143
101
  // choose most compact commands: relative or absolute
144
- if(toMixed){
145
- for(let i=0; i<pathData.length; i++){
102
+ if (toMixed) {
103
+ for (let i = 0; i < pathData.length; i++) {
146
104
  let com = pathData[i]
147
105
  let comA = pathDataAbs[i]
148
106
  // compare Lengths
@@ -152,7 +110,7 @@ export function convertPathData(pathData, {
152
110
  let lenR = comStr.length;
153
111
  let lenA = comStrA.length;
154
112
 
155
- if(lenA<lenR){
113
+ if (lenA < lenR) {
156
114
  //console.log('absolute is shorter', comStrA, comStr);
157
115
  pathData[i] = pathDataAbs[i]
158
116
  }
@@ -162,6 +120,56 @@ export function convertPathData(pathData, {
162
120
  return pathData
163
121
  }
164
122
 
123
+
124
+
125
+
126
+ export function parsePathDataNormalized(d,
127
+ {
128
+ // necessary for most calculations
129
+ toAbsolute = true,
130
+ toLonghands = true,
131
+
132
+ // not necessary unless you need cubics only
133
+ quadraticToCubic = false,
134
+
135
+ // mostly a fallback if arc calculations fail
136
+ arcToCubic = false,
137
+ // arc to cubic precision - adds more segments for better precision
138
+ arcAccuracy = 4,
139
+ } = {}
140
+ ) {
141
+
142
+ // is already array
143
+ let isArray = Array.isArray(d);
144
+
145
+ // normalize native pathData to regular array
146
+ let hasConstructor = isArray && typeof d[0] === 'object' && typeof d[0].constructor === 'function'
147
+ /*
148
+ if (hasConstructor) {
149
+ d = d.map(com => { return { type: com.type, values: com.values } })
150
+ console.log('hasConstructor', hasConstructor, (typeof d[0].constructor), d);
151
+ }
152
+ */
153
+
154
+ let pathDataObj = isArray ? d : parsePathDataString(d);
155
+
156
+ let { hasRelatives = true, hasShorthands = true, hasQuadratics = true, hasArcs = true } = pathDataObj;
157
+ let pathData = hasConstructor ? pathDataObj : pathDataObj.pathData;
158
+
159
+ //console.log('???quadraticToCubic', quadraticToCubic);
160
+
161
+ // normalize
162
+ pathData = normalizePathData(pathData,
163
+ {
164
+ toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy,
165
+ hasRelatives, hasShorthands, hasQuadratics, hasArcs
166
+ },
167
+ )
168
+
169
+ return pathData;
170
+ }
171
+
172
+
165
173
  /**
166
174
  *
167
175
  * @param {*} pathData
@@ -170,50 +178,100 @@ export function convertPathData(pathData, {
170
178
 
171
179
  export function optimizeArcPathData(pathData = []) {
172
180
 
173
- let remove =[]
181
+ //return pathData
174
182
 
175
- pathData.forEach((com, i) => {
183
+ let remove = []
184
+ let l = pathData.length;
185
+ let pathDataN = [];
186
+
187
+ for (let i = 0; i < l; i++) {
188
+ let com = pathData[i];
176
189
  let { type, values } = com;
177
- if (type === 'A') {
178
- let [rx, ry, largeArc, x, y] = [values[0], values[1], values[3], values[5], values[6]];
179
- let comPrev = pathData[i - 1]
180
- let [x0, y0] = [comPrev.values[comPrev.values.length - 2], comPrev.values[comPrev.values.length - 1]];
181
- let M = { x: x0, y: y0 };
182
- let p = { x, y };
183
- //largeArc=true
184
- //let pMid = {x: Math.abs(x-x0), y:Math.abs(y-y0)}
185
-
186
- if(rx===0 || ry===0){
187
- pathData[i]= null
188
- remove.push(i)
189
- //console.log('!!!');
190
- }
191
190
 
192
- // rx and ry are large enough
193
- if (rx >= 1 && (x === x0 || y === y0)) {
194
- let diff = Math.abs(rx - ry) / rx;
191
+ if (type !== 'A') {
192
+ pathDataN.push(com)
193
+ continue
194
+ }
195
195
 
196
- // rx~==ry
197
- if (diff < 0.01) {
198
196
 
199
- // test radius against mid point
200
- let pMid = interpolate(M, p, 0.5)
201
- let distM = getDistance(pMid, M)
202
- let rDiff = Math.abs(distM - rx) / rx
197
+ let [rx, ry, largeArc, x, y] = [values[0], values[1], values[3], values[5], values[6]];
198
+ let comPrev = pathData[i - 1]
199
+ let [x0, y0] = [comPrev.values[comPrev.values.length - 2], comPrev.values[comPrev.values.length - 1]];
200
+ let M = { x: x0, y: y0 };
201
+ let p = { x, y };
202
+ //largeArc=true
203
+ //let pMid = {x: Math.abs(x-x0), y:Math.abs(y-y0)}
203
204
 
204
- // half distance between mid and start point should be ~ equal
205
- if(rDiff<0.01){
206
- pathData[i].values[0] = 1;
207
- pathData[i].values[1] = 1;
208
- pathData[i].values[2] = 0;
209
- }
210
- }
205
+ if (rx === 0 || ry === 0) {
206
+ pathData[i] = null
207
+ remove.push(i)
208
+ }
209
+
210
+ // test for elliptic
211
+ let rat = rx / ry
212
+ let error = rx !== ry ? Math.abs(1 - rat) : 0
213
+
214
+ if (error > 0.01) {
215
+ //console.log('is elliptic');
216
+ pathDataN.push(com)
217
+ continue
218
+
219
+ }
220
+
221
+ // xAxis rotation is futile for circular arcs - reset
222
+ com.values[2] = 0
223
+
224
+ /**
225
+ * test semi circles
226
+ * rx and ry are large enough
227
+ */
228
+
229
+
230
+ // 1. horizontal or vertical
231
+ let thresh = getDistManhattan(M, p) * 0.001;
232
+ let diffX = Math.abs(x - x0)
233
+ let diffY = Math.abs(y - y0)
234
+
235
+ let isHorizontal = diffY < thresh
236
+ let isVertical = diffX < thresh
237
+
238
+
239
+ // minify rx and ry
240
+ if (isHorizontal || isVertical) {
241
+
242
+ // check if semi circle
243
+ let needsTrueR = isHorizontal ? rx*1.9 > diffX : ry*1.9 > diffY;
244
+
245
+ // is semicircle we can simplify rx
246
+ if (!needsTrueR) {
247
+ //console.log('needsTrueR', needsTrueR, diffX, rx, diffY, ry);
248
+ rx = rx >= 1 ? 1 : (rx > 0.5 ? 0.5 : rx);
211
249
  }
250
+
251
+ com.values[0] = rx
252
+ com.values[1] = rx
253
+ pathDataN.push(com)
254
+ continue
255
+
256
+
212
257
  }
213
- })
214
258
 
215
- if(remove.length) pathData = pathData.filter(Boolean)
216
- return pathData;
259
+ // 2. get true radius - if rx ~= diameter/distance we have a semicircle
260
+ let r = getDistance(M, p) * 0.5
261
+ error = rx / r
262
+ //console.log('rx', rx, r, error);
263
+
264
+ if (error < 0.5) {
265
+ rx = r >= 1 ? 1 : (r > 0.5 ? 0.5 : r);
266
+ }
267
+
268
+ com.values[0] = rx;
269
+ com.values[1] = rx;
270
+ pathDataN.push(com)
271
+
272
+ }
273
+
274
+ return pathDataN;
217
275
  }
218
276
 
219
277
 
@@ -281,6 +339,48 @@ export function normalizePathData(pathData = [],
281
339
  }
282
340
  */
283
341
 
342
+ export function convertSmallArcsToLinetos(pathData) {
343
+
344
+ let l = pathData.length;
345
+
346
+ // add fist command
347
+ let pathDataN = [pathData[0]]
348
+
349
+ for (let i = 1; i < l; i++) {
350
+ let com = pathData[i];
351
+ let comPrev = pathData[i - 1];
352
+ let comN = pathData[i + 1] || null;
353
+
354
+ if (!comN) {
355
+ pathDataN.push(com);
356
+ break
357
+ }
358
+
359
+ let { type, values, extreme = false, p0, p, dimA = 0 } = com;
360
+ // for short segment detection
361
+ let dimAN = comN.dimA;
362
+ let dimA0 = comPrev.dimA + dimA + dimAN;
363
+ let thresh = 0.05
364
+ let isShort = dimA < dimA0 * thresh;
365
+ //let isShortN = dimAN < dimA0 * thresh;
366
+
367
+ if (type === 'A' && isShort && values[0] < 1 && values[1] < 1) {
368
+
369
+ //renderPoint(markers, p0, 'green', '0.1%')
370
+ //renderPoint(markers, p, 'magenta', '0.1%')
371
+ com.type = 'L';
372
+ com.values = [p.x, p.y];
373
+ }
374
+
375
+ pathDataN.push(com)
376
+
377
+ }
378
+
379
+
380
+ return pathDataN;
381
+
382
+
383
+ }
284
384
 
285
385
 
286
386
 
@@ -54,33 +54,25 @@ export function fixPathDataDirections(pathDataArr = [], toClockwise = false) {
54
54
 
55
55
 
56
56
  // reverse paths
57
- for (let i = 0; i < l; i++) {
57
+ for (let i = 0; l && i < l; i++) {
58
58
 
59
59
  let poly = polys[i]
60
60
  let { cw, includedIn, includes } = poly
61
61
 
62
- // outer path direction to counter clockwise
63
- if (!includedIn.length && cw && !toClockwise
64
- || !includedIn.length && !cw && toClockwise
65
- ) {
66
- //console.log('reverse outer');
67
-
68
- pathDataArr[i].pathData = reversePathData(pathDataArr[i].pathData);
69
- polys[i].cw = polys[i].cw ? false : true
70
- cw = polys[i].cw
71
-
72
- }
62
+ let len = includes.length;
63
+ //console.log('try reverse', includes);
73
64
 
74
65
  // reverse inner sub paths
75
- for (let j = 0; j < includes.length; j++) {
66
+ for (let j = 0; len && j < len; j++) {
76
67
  let ind = includes[j];
77
68
  let child = polys[ind];
78
69
 
79
- if (child.cw === cw) {
80
- //console.log('reverse', child.cw, cw);
81
- pathDataArr[ind].pathData = reversePathData(pathDataArr[ind].pathData);
82
- polys[ind].cw = polys[ind].cw ? false : true
83
- }
70
+ // nothing to do
71
+ if (child.cw !== cw) continue
72
+
73
+ pathDataArr[ind].pathData = reversePathData(pathDataArr[ind].pathData);
74
+ polys[ind].cw = polys[ind].cw ? false : true
75
+
84
76
  }
85
77
  }
86
78
 
@@ -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,8 @@ 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
+ // we need rounding otherwise sorting may crash due to e notation
33
+ let p = { type: type, x: +values[valsLen - 2].toFixed(8), y: +values[valsLen - 1].toFixed(8), index: 0 }
34
34
  p.index = i
35
35
  indices.push(p)
36
36
  }
@@ -38,16 +38,124 @@ export function pathDataToTopLeft(pathData) {
38
38
 
39
39
  // reorder to top left most
40
40
  //|| a.x - b.x
41
- indices = indices.sort((a, b) => +a.y.toFixed(8) - +b.y.toFixed(8) || a.x-b.x );
41
+ indices = indices.sort((a, b) => a.y - b.y || a.x - b.x);
42
42
  newIndex = indices[0].index
43
43
 
44
- return newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
44
+ return newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
45
45
  }
46
46
 
47
47
 
48
+ export function optimizeClosePath(pathData, { removeFinalLineto = true, autoClose = true } = {}) {
48
49
 
50
+ let pathDataN = pathData;
51
+ let l = pathData.length;
52
+ let M = { x: +pathData[0].values[0].toFixed(8), y: +pathData[0].values[1].toFixed(8) }
53
+ let isClosed = pathData[l - 1].type.toLowerCase() === 'z'
54
+ //let linetos = pathData.filter(com => com.type === 'L')
55
+ //let hasLinetos = linetos.length;
56
+ let hasLinetos = false
57
+
58
+
59
+ // check if path is closed by explicit lineto
60
+ let idxPenultimate = isClosed ? l - 2 : l - 1
61
+ let penultimateCom = pathData[idxPenultimate];
62
+ let penultimateType = penultimateCom.type;
63
+ let penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8))
64
+
65
+ // last L command ends at M
66
+ let hasClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y
67
+ let lastIsLine = penultimateType === 'L'
68
+ //console.log(pathData);
69
+
70
+ // create index
71
+ let indices = [];
72
+ for (let i = 0; i < l; i++) {
73
+ let com = pathData[i];
74
+ let { type, values, p0, p } = com;
75
+
76
+ if(type==='L') hasLinetos = true;
77
+
78
+ // exclude Z
79
+ if (values.length) {
80
+ let valsL = values.slice(-2)
81
+
82
+ let x = Math.min(p0.x, p.x)
83
+ let y = Math.min(p0.y, p.y)
84
+
85
+ let prevCom = pathData[i - 1] ? pathData[i - 1] : pathData[idxPenultimate]
86
+ let prevType = prevCom.type
87
+ //let p = { type: type, x: valsL[0], y: valsL[1], dist: 0, index: 0, prevType }
88
+ let item = { type: type, x, y, index: 0, prevType }
89
+ item.index = i
90
+ indices.push(item)
91
+ }
92
+
93
+ }
94
+
95
+ let xMin = Infinity;
96
+ let yMin = Infinity;
97
+ let idx_top = null;
98
+ let len = indices.length
99
+
100
+
101
+ for (let i = 0; i < len; i++) {
102
+ let com = indices[i];
103
+ let { type, index, x, y, prevType } = com;
104
+
105
+ if (hasLinetos && prevType === 'L') {
106
+ if (x < xMin && y < yMin) {
107
+ idx_top = index-1;
108
+ }
109
+
110
+ if (y < yMin) {
111
+ yMin = y
112
+ }
113
+
114
+ if (x < xMin) {
115
+ xMin = x
116
+ }
117
+ }
118
+ }
119
+
120
+
121
+ // shift to better starting point
122
+ if (idx_top) {
123
+ pathDataN = shiftSvgStartingPoint(pathDataN, idx_top)
124
+
125
+ // update penultimate - reorder might have added new close paths
126
+ l = pathDataN.length
127
+ M = { x: +pathDataN[0].values[0].toFixed(8), y: +pathDataN[0].values[1].toFixed(8) }
128
+
129
+ idxPenultimate = isClosed ? l - 2 : l - 1
130
+ penultimateCom = pathDataN[idxPenultimate];
131
+ penultimateType = penultimateCom.type;
132
+ penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8))
133
+ lastIsLine = penultimateType ==='L'
134
+
135
+ // last L command ends at M
136
+ hasClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y
137
+
138
+ }
49
139
 
50
- export function optimizeClosePath(pathData, {removeFinalLineto = true, autoClose = true}={}) {
140
+
141
+ // remove unnecessary closing lineto
142
+ if (removeFinalLineto && hasClosingCommand && lastIsLine) {
143
+ pathDataN.splice(l - 2, 1)
144
+ }
145
+
146
+ // add close path
147
+ if (autoClose && !isClosed && hasClosingCommand) {
148
+ pathDataN.push({ type: 'Z', values: [] })
149
+ }
150
+
151
+ return pathDataN
152
+
153
+ }
154
+
155
+
156
+
157
+
158
+ export function optimizeClosePath__(pathData, { removeFinalLineto = true, autoClose = true } = {}) {
51
159
 
52
160
  let pathDataNew = [];
53
161
  let l = pathData.length;
@@ -58,17 +166,16 @@ export function optimizeClosePath(pathData, {removeFinalLineto = true, autoClose
58
166
 
59
167
 
60
168
  // check if order is ideal
61
- let idxPenultimate = isClosed ? l-2 : l-1
62
-
169
+ let idxPenultimate = isClosed ? l - 2 : l - 1
63
170
  let penultimateCom = pathData[idxPenultimate];
64
171
  let penultimateType = penultimateCom.type;
65
172
  let penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8))
66
173
 
67
174
  // last L command ends at M
68
- let isClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y
175
+ let hasClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y
69
176
 
70
177
  // add closepath Z to enable order optimizations
71
- if(!isClosed && autoClose && isClosingCommand){
178
+ if (!isClosed && autoClose && hasClosingCommand) {
72
179
 
73
180
  /*
74
181
  // adjust final coords
@@ -77,14 +184,14 @@ export function optimizeClosePath(pathData, {removeFinalLineto = true, autoClose
77
184
  pathData[idxPenultimate].values[valsLastLen-2] = M.x
78
185
  pathData[idxPenultimate].values[valsLastLen-1] = M.y
79
186
  */
80
-
81
- pathData.push({type:'Z', values:[]})
187
+
188
+ pathData.push({ type: 'Z', values: [] })
82
189
  isClosed = true;
83
190
  l++
84
191
  }
85
192
 
86
193
  // if last segment is not closing or a lineto
87
- let skipReorder = pathData[1].type !== 'L' && (!isClosingCommand || penultimateCom.type === 'L')
194
+ let skipReorder = pathData[1].type !== 'L' && (!hasClosingCommand || penultimateCom.type === 'L')
88
195
  skipReorder = false
89
196
 
90
197
 
@@ -143,13 +250,13 @@ export function optimizeClosePath(pathData, {removeFinalLineto = true, autoClose
143
250
  // remove last lineto
144
251
  penultimateCom = pathData[l - 2];
145
252
  penultimateType = penultimateCom.type;
146
- penultimateComCoords = penultimateCom.values.slice(-2).map(val=>+val.toFixed(8))
253
+ penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8))
147
254
 
148
- isClosingCommand = penultimateType === 'L' && penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y
255
+ hasClosingCommand = penultimateType === 'L' && penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y
149
256
 
150
- //console.log('penultimateCom', isClosingCommand, penultimateCom.values, M);
257
+ //console.log('penultimateCom', hasClosingCommand, penultimateCom.values, M);
151
258
 
152
- if (removeFinalLineto && isClosingCommand) {
259
+ if (removeFinalLineto && hasClosingCommand) {
153
260
  pathData.splice(l - 2, 1)
154
261
  }
155
262