svg-path-simplify 0.3.4 → 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 (39) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.yml +28 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.yml +35 -0
  3. package/dist/svg-path-simplify.esm.js +4104 -3481
  4. package/dist/svg-path-simplify.esm.min.js +2 -8
  5. package/dist/svg-path-simplify.js +4105 -3480
  6. package/dist/svg-path-simplify.min.js +2 -8
  7. package/dist/svg-path-simplify.pathdata.esm.js +1090 -1039
  8. package/dist/svg-path-simplify.pathdata.esm.min.js +2 -8
  9. package/index.html +493 -116
  10. package/package.json +1 -1
  11. package/site.webmanifest +21 -0
  12. package/src/constants.js +3 -1
  13. package/src/index.js +7 -1
  14. package/src/pathData_simplify_cubic.js +1 -10
  15. package/src/pathSimplify-main.js +71 -28
  16. package/src/pathSimplify-only-pathdata.js +2 -2
  17. package/src/svg-getAttributes.js +13 -0
  18. package/src/svg_flatten_transforms.js +43 -0
  19. package/src/svg_getViewbox.js +23 -11
  20. package/src/svg_rootSVG.js +9 -0
  21. package/src/svgii/convert_colors.js +98 -0
  22. package/src/svgii/convert_units.js +144 -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 +1 -1
  27. package/src/svgii/pathData_convert.js +143 -29
  28. package/src/svgii/pathData_parse.js +2 -99
  29. package/src/svgii/pathData_parse_els.js +198 -125
  30. package/src/svgii/pathData_simplify_refineCorners.js +72 -43
  31. package/src/svgii/pathData_stringify.js +6 -5
  32. package/src/svgii/poly_normalize.js +21 -1
  33. package/src/svgii/rounding.js +36 -5
  34. package/src/svgii/svg-styles-getTransforms.js +43 -5
  35. package/src/svgii/svg-styles-to-attributes-const.js +8 -3
  36. package/src/svgii/svg-styles-to-attributes.js +106 -9
  37. package/src/svgii/svg_cleanup.js +291 -35
  38. package/src/svgii/svg_el_parse_style_props.js +423 -0
  39. package/src/svgii/stringify.js +0 -103
@@ -1,20 +1,176 @@
1
1
  //import { pathDataToAbsoluteOrRelative, pathDataToLonghands, cubicToArc } from './pathData_convert.js';
2
- import { parsePathDataString, parsePathDataNormalized, stringifyPathData } from './pathData_parse.js';
2
+ import { svgNs } from '../constants.js';
3
+ import { pathDataCubicsToArc } from '../pathData_simplify_cubicsToArcs.js';
4
+ import { getElementAtts } from '../svg-getAttributes.js';
5
+ import { getViewBox } from '../svg_getViewbox.js';
6
+ import { getRootSvg } from '../svg_rootSVG.js';
7
+ import { svgElUnitsToPixel } from './convert_units.js';
8
+ import { getPathDataVertices } from './geometry.js';
9
+ import { getPolygonArea } from './geometry_area.js';
10
+ import { getPolyBBox } from './geometry_bbox.js';
11
+ import { getPathDataVerbose } from './pathData_analyze.js';
12
+ import { parsePathDataNormalized } from './pathData_convert.js';
13
+ import { parsePathDataString, stringifyPathData } from './pathData_parse.js';
14
+ import { autoRound, roundTo } from './rounding.js';
15
+ import { attLookup } from './svg-styles-to-attributes-const.js';
16
+
17
+
18
+ export function pathElToShape(el, {
19
+ convert_rects = false,
20
+ convert_ellipses = false,
21
+ convert_poly = false,
22
+ convert_lines = false
23
+ } = {}) {
24
+
25
+ //console.log('pathElToShape', convert_rects, convert_ellipses, convert_lines );
26
+
27
+ let pathData = parsePathDataNormalized(el.getAttribute('d'));
28
+ let coms = Array.from(new Set(pathData.map(com => com.type))).join('')
29
+
30
+ let hasArcs = (/[a]/gi).test(coms)
31
+ let hasBeziers = (/[csqt]/gi).test(coms)
32
+ let hasLines = (/[l]/gi).test(coms)
33
+ let isPoly = !(/[acqts]/gi).test(coms)
34
+ let closed = (/[z]/gi).test(coms)
35
+ let shape = null;
36
+ let type = null
37
+
38
+ let attributes = getElementAtts(el)
39
+ let attsNew = {}
40
+ let decimals = 7;
41
+
42
+ if (isPoly) {
43
+
44
+ // is line
45
+ if (pathData.length === 2 && convert_lines) {
46
+ type = 'line'
47
+ shape = document.createElementNS(svgNs, type)
48
+ let [x1, y1, x2, y2] = [...pathData[0].values, ...pathData[1].values].map(val => roundTo(val, decimals))
49
+ attsNew = { x1, y1, x2, y2 }
50
+ }
51
+ // polygon, polyline or rect
52
+ else {
53
+
54
+ let vertices = getPathDataVertices(pathData);
55
+ let bb = getPolyBBox(vertices)
56
+ let areaPoly = getPolygonArea(vertices, true)
57
+ let areaRect = bb.width * bb.height;
58
+ let areaDiff = Math.abs(1 - areaRect / areaPoly);
59
+
60
+ // is rect
61
+ if (convert_rects && areaDiff < 0.01) {
62
+ type = 'rect'
63
+ shape = document.createElementNS(svgNs, type)
64
+ let { x, y, width, height } = bb
65
+ attsNew = { x, y, width, height }
66
+
67
+ }
68
+ // polyline or polygon
69
+ else if(convert_poly) {
70
+ type = closed ? 'polygon' : 'polyline';
71
+ shape = document.createElementNS(svgNs, type)
72
+ let points = vertices.map(pt => { return [pt.x, pt.y] }).flat().map(val => roundTo(val, decimals)).join(' ')
73
+ attsNew = { points }
74
+ }
75
+ }
76
+ }
77
+ // circles or ellipses
78
+ else if (!hasLines && convert_ellipses) {
79
+
80
+ // try to convert cubics to arcs
81
+ if (!hasArcs && hasBeziers) {
82
+ pathData = pathDataCubicsToArc(pathData, { areaThreshold: 2.5 })
83
+ hasArcs = pathData.filter(com => com.type === 'A').length;
84
+ }
85
+
86
+
87
+ if (hasArcs) {
88
+ let pathData2 = getPathDataVerbose(pathData, { addArcParams: true })
89
+ let arcComs = pathData2.filter(com => com.type === 'A')
90
+
91
+ let cxVals = new Set();
92
+ let cyVals = new Set();
93
+ let rxVals = new Set();
94
+ let ryVals = new Set();
95
+
96
+ if (arcComs.length > 1) {
97
+ //console.log('!!!arcComs', arcComs);
98
+ pathData2.forEach(com => {
99
+ if (com.type === 'A') {
100
+ //console.log('params', com, com.cx, com.cy, com.rx, com.ry);
101
+ cxVals.add(roundTo(com.cx, decimals))
102
+ cyVals.add(roundTo(com.cy, decimals))
103
+ rxVals.add(roundTo(com.rx, decimals))
104
+ ryVals.add(roundTo(com.ry, decimals))
105
+ }
106
+ })
107
+ }
108
+
109
+ cxVals = Array.from(cxVals)
110
+ cyVals = Array.from(cyVals)
111
+ rxVals = Array.from(rxVals)
112
+ ryVals = Array.from(ryVals)
113
+
114
+ if(cxVals.length===1 && cyVals.length===1 && rxVals.length===1 && ryVals.length===1){
115
+ let [rx, ry, cx, cy] = [rxVals[0], ryVals[0], cxVals[0], cyVals[0]]
116
+ type = rx===ry ? 'circle' : 'ellipse';
117
+ shape = document.createElementNS(svgNs, type)
118
+ attsNew = type==='circle' ? { r:rx, cx, cy } : {rx, ry, cx, cy}
119
+ }
120
+ }
121
+ }
122
+
123
+
124
+ // if el could be replaced
125
+ if (shape) {
126
+ let ignore = ['id', 'class']
127
+
128
+ // set shape attributes
129
+ for (let att in attsNew) {
130
+ shape.setAttribute(att, attsNew[att])
131
+ }
3
132
 
4
- export function shapeElToPath(el) {
133
+ // copy old attributes
134
+ for (let att in attributes) {
135
+ //console.log(attributes);
136
+ if (attLookup.atts[att].includes(type) || ignore.includes(att) || att.startsWith('data-')) {
137
+ shape.setAttribute(att, attributes[att])
138
+ }
139
+ }
140
+
141
+ // replace
142
+ el = shape;
143
+ }
144
+
145
+ return el;
146
+
147
+ }
148
+
149
+ export function shapeElToPath(el, { width = 0,
150
+ height = 0,
151
+ convert_rects = false,
152
+ convert_ellipses = false,
153
+ convert_poly = false,
154
+ convert_lines = false
155
+
156
+ } = {}) {
5
157
 
6
158
  let nodeName = el.nodeName.toLowerCase();
7
- if (nodeName === 'path') return el;
8
159
 
9
- let pathData = getPathDataFromEl(el);
160
+ if (
161
+ nodeName === 'path' ||
162
+ nodeName === 'rect' && !convert_rects ||
163
+ (nodeName === 'circle' || nodeName === 'ellipse') && !convert_ellipses ||
164
+ (nodeName === 'polygon' || nodeName === 'polyline') && !convert_poly ||
165
+ (nodeName === 'line') && !convert_lines
166
+ ) return el;
167
+
168
+ let pathData = getPathDataFromEl(el, { width, height });
10
169
  let d = pathData.map(com => { return `${com.type} ${com.values} ` }).join(' ')
11
170
  let attributes = [...el.attributes].map(att => att.name);
12
171
 
13
- //console.log(d);
14
- //return []
15
172
 
16
- let pathN = document.createElementNS('http://www.w3.org/2000/svg', 'path');
17
- //let pathN = document.createElement('path');
173
+ let pathN = document.createElementNS(svgNs, 'path');
18
174
  pathN.setAttribute('d', d);
19
175
 
20
176
  let exclude = ['x', 'y', 'x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'dx', 'dy', 'r', 'rx', 'ry', 'width', 'height', 'points'];
@@ -31,121 +187,37 @@ export function shapeElToPath(el) {
31
187
  return pathN
32
188
 
33
189
  }
190
+ /*
191
+ export function copyAttributes(newEl, oldEl){
34
192
 
193
+ let attributes = [...oldEl.attributes].map(att => att.name);
35
194
 
36
- // retrieve pathdata from svg geometry elements
37
- export function getPathDataFromEl(el, stringify = false) {
38
-
39
- let pathData = [];
40
- let type = el.nodeName.toLowerCase();
41
- let atts, attNames, d, x, y, width, height, r, rx, ry, cx, cy, x1, x2, y1, y2;
42
-
43
- // convert relative or absolute units
44
- const svgElUnitsToPixel = (el, decimals = 9) => {
45
- //console.log(this);
46
- const svg = el.nodeName !== "svg" ? el.closest("svg") : el;
47
-
48
- // convert real life units to pixels
49
- const translateUnitToPixel = (value) => {
50
195
 
51
- if (value === null) {
52
- return 0
53
- }
54
- //default dpi = 96
55
- let dpi = 96;
56
- let unit = value.match(/([a-z]+)/gi);
57
- unit = unit ? unit[0] : "";
58
- let val = parseFloat(value);
59
- let rat;
60
-
61
- // no unit - already pixes/user unit
62
- if (!unit) {
63
- return val;
64
- }
65
-
66
- switch (unit) {
67
- case "in":
68
- rat = dpi;
69
- break;
70
- case "pt":
71
- rat = (1 / 72) * 96;
72
- break;
73
- case "cm":
74
- rat = (1 / 2.54) * 96;
75
- break;
76
- case "mm":
77
- rat = ((1 / 2.54) * 96) / 10;
78
- break;
79
- // just a default approximation
80
- case "em":
81
- case "rem":
82
- rat = 16;
83
- break;
84
- default:
85
- rat = 1;
86
- }
87
- let valuePx = val * rat;
88
- return +valuePx.toFixed(decimals);
89
- };
90
-
91
- // svg width and height attributes
92
- let width = svg.getAttribute("width");
93
- width = width ? translateUnitToPixel(width) : 300;
94
- let height = svg.getAttribute("height");
95
- height = width ? translateUnitToPixel(height) : 150;
96
-
97
- //prefer viewBox values
98
- let vB = svg.getAttribute("viewBox");
99
- vB = vB
100
- ? vB
101
- .replace(/,/g, " ")
102
- .split(" ")
103
- .filter(Boolean)
104
- .map((val) => {
105
- return +val;
106
- })
107
- : [];
196
+ }
197
+ */
108
198
 
109
- let w = vB.length ? vB[2] : width;
110
- let h = vB.length ? vB[3] : height;
111
- let scaleX = w / 100;
112
- let scaleY = h / 100;
113
- let scalRoot = Math.sqrt((Math.pow(scaleX, 2) + Math.pow(scaleY, 2)) / 2);
114
199
 
115
- let attsH = ["x", "width", "x1", "x2", "rx", "cx", "r"];
116
- let attsV = ["y", "height", "y1", "y2", "ry", "cy"];
200
+ // retrieve pathdata from svg geometry elements
201
+ export function getPathDataFromEl(el, {
202
+ stringify = false,
203
+ width = 0,
204
+ height = 0
205
+ } = {}) {
117
206
 
207
+ let pathData = [];
208
+ let type = el.nodeName.toLowerCase();
209
+ let attNames, d, x, y, r, rx, ry, cx, cy, x1, x2, y1, y2;
118
210
 
119
- let atts = el.getAttributeNames();
120
- atts.forEach((att) => {
121
- let val = el.getAttribute(att);
122
- let valAbs = val;
123
- if (attsH.includes(att) || attsV.includes(att)) {
124
- let scale = attsH.includes(att) ? scaleX : scaleY;
125
- scale = att === "r" && w != h ? scalRoot : scale;
126
- let unit = val.match(/([a-z|%]+)/gi);
127
- unit = unit ? unit[0] : "";
128
- if (val.includes("%")) {
129
- valAbs = parseFloat(val) * scale;
130
- }
131
- //absolute units
132
- else {
133
- valAbs = translateUnitToPixel(val);
134
- }
135
- el.setAttribute(att, +valAbs);
136
- }
137
- });
211
+ if (!width || !height) {
212
+ let svg = getRootSvg(el);
213
+ let viewBox = getViewBox(svg)
214
+ width = viewBox.width;
215
+ height = viewBox.height;
138
216
  }
139
217
 
140
- svgElUnitsToPixel(el)
141
-
142
- const getAtts = (attNames) => {
143
- atts = {}
144
- attNames.forEach(att => {
145
- atts[att] = +el.getAttribute(att)
146
- })
147
- return atts
148
- }
218
+ // convert relative and physical units to user-units
219
+ let atts = svgElUnitsToPixel(el, { width, height })
220
+ //console.log('atts', atts);
149
221
 
150
222
  switch (type) {
151
223
  case 'path':
@@ -155,8 +227,7 @@ export function getPathDataFromEl(el, stringify = false) {
155
227
 
156
228
  case 'rect':
157
229
  attNames = ['x', 'y', 'width', 'height', 'rx', 'ry'];
158
- ({ x, y, width, height, rx, ry } = getAtts(attNames));
159
-
230
+ ({ x=0, y=0, width=0, height=0, rx=0, ry=0 } = atts);
160
231
 
161
232
  if (!rx && !ry) {
162
233
  pathData = [
@@ -168,8 +239,8 @@ export function getPathDataFromEl(el, stringify = false) {
168
239
  ];
169
240
  } else {
170
241
 
171
- rx=rx? rx : ry;
172
- ry=ry ? ry : rx;
242
+ rx = rx ? rx : ry;
243
+ ry = ry ? ry : rx;
173
244
 
174
245
  if (rx > width / 2) {
175
246
  rx = width / 2;
@@ -196,7 +267,7 @@ export function getPathDataFromEl(el, stringify = false) {
196
267
  case 'ellipse':
197
268
 
198
269
  attNames = ['cx', 'cy', 'rx', 'ry', 'r'];
199
- ({ cx, cy, r, rx, ry } = getAtts(attNames));
270
+ ({ cx=0, cy=0, r, rx, ry } = atts);
200
271
 
201
272
  let isCircle = type === 'circle';
202
273
 
@@ -209,9 +280,11 @@ export function getPathDataFromEl(el, stringify = false) {
209
280
  ry = ry ? ry : r;
210
281
  }
211
282
 
212
- // simplified radii for cirecles
213
- let rxS = isCircle && r>=1 ? 1 : rx;
214
- let ryS = isCircle && r>=1 ? 1 : rx;
283
+
284
+ // simplified radii for circles
285
+ let rxS = isCircle && r >= 1 ? 1 : rx;
286
+ let ryS = isCircle && r >= 1 ? 1 : ry;
287
+
215
288
 
216
289
  pathData = [
217
290
  { type: "M", values: [cx + rx, cy] },
@@ -222,7 +295,7 @@ export function getPathDataFromEl(el, stringify = false) {
222
295
  break;
223
296
  case 'line':
224
297
  attNames = ['x1', 'y1', 'x2', 'y2'];
225
- ({ x1, y1, x2, y2 } = getAtts(attNames));
298
+ ({ x1, y1, x2, y2 } = atts);
226
299
  pathData = [
227
300
  { type: "M", values: [x1, y1] },
228
301
  { type: "L", values: [x2, y2] }
@@ -231,12 +304,12 @@ export function getPathDataFromEl(el, stringify = false) {
231
304
  case 'polygon':
232
305
  case 'polyline':
233
306
 
234
- let points = el.getAttribute('points').replaceAll(',', ' ').split(' ').filter(Boolean)
307
+ let points = el.getAttribute('points').split(/,| /).filter(Boolean).map(Number)
235
308
 
236
309
  for (let i = 0; i < points.length; i += 2) {
237
310
  pathData.push({
238
311
  type: (i === 0 ? "M" : "L"),
239
- values: [+points[i], +points[i + 1]]
312
+ values: [points[i], points[i + 1]]
240
313
  });
241
314
  }
242
315
  if (type === 'polygon') {
@@ -19,8 +19,8 @@ export function refineRoundedCorners(pathData, {
19
19
 
20
20
  let isClosed = pathData[l - 1].type.toLowerCase() === 'z';
21
21
  let zIsLineto = isClosed ?
22
- (pathData[l-1].p.x === pathData[0].p0.x && pathData[l-1].p.y === pathData[0].p0.y)
23
- : false ;
22
+ (pathData[l - 1].p.x === pathData[0].p0.x && pathData[l - 1].p.y === pathData[0].p0.y)
23
+ : false;
24
24
 
25
25
  let lastOff = isClosed ? 2 : 1;
26
26
 
@@ -30,12 +30,8 @@ export function refineRoundedCorners(pathData, {
30
30
  let firstIsLine = pathData[1].type === 'L';
31
31
  let firstIsBez = pathData[1].type === 'C';
32
32
 
33
- //console.log('lastIsLine', lastIsLine, 'firstIsLine', firstIsLine, 'lastIsBez', lastIsBez, 'firstIsBez', firstIsBez, 'isClosed', isClosed, 'comLast1', comLast1);
34
33
 
35
34
  let normalizeClose = isClosed && firstIsBez && (lastIsLine || zIsLineto);
36
- //let adjustStart = false
37
- //normalizeClose = false
38
- //console.log('normalizeClose', normalizeClose);
39
35
 
40
36
  // normalize closepath to lineto
41
37
  if (normalizeClose) {
@@ -53,7 +49,7 @@ export function refineRoundedCorners(pathData, {
53
49
  if ((type === 'L' && comN && comN.type === 'C') ||
54
50
  (type === 'C' && comN && comN.type === 'L')
55
51
  ) {
56
- let comL0 = type==='L' ? com : null;
52
+ let comL0 = type === 'L' ? com : null;
57
53
  let comL1 = null;
58
54
  let comBez = [];
59
55
  let offset = 0;
@@ -66,7 +62,7 @@ export function refineRoundedCorners(pathData, {
66
62
  //renderPoint(markers, com.p, 'purple')
67
63
  }
68
64
 
69
- if(!comL0) {
65
+ if (!comL0) {
70
66
  pathDataN.push(com)
71
67
  continue
72
68
  }
@@ -94,14 +90,14 @@ export function refineRoundedCorners(pathData, {
94
90
  }
95
91
 
96
92
  if (comL1) {
93
+ //console.log('comL1', comL1);
97
94
 
98
95
  // linetos
99
96
  let len1 = getDistManhattan(comL0.p0, comL0.p)
100
97
  let len2 = getDistManhattan(comL1.p0, comL1.p)
101
98
 
102
99
  // bezier
103
- //comBez = comBez[0];
104
- let comBezLen = comBez.length;
100
+ //let comBezLen = comBez.length;
105
101
  //let len3 = getDistManhattan(comBez[0].p0, comBez[comBezLen - 1].p)
106
102
  let len3 = getDistManhattan(comL0.p, comL1.p0)
107
103
 
@@ -113,54 +109,87 @@ export function refineRoundedCorners(pathData, {
113
109
  let signChange = (area1 < 0 && area2 > 0) || (area1 > 0 && area2 < 0)
114
110
 
115
111
  // exclude mid bezier segments that are larger than surrounding linetos
116
- let bezThresh = len3*0.5 * tolerance
117
- let isSmall = bezThresh < len1 && bezThresh < len2 ;
112
+ let bezThresh = len3 * 0.5 * tolerance
113
+ let isSmall = bezThresh < len1 && bezThresh < len2;
118
114
 
119
115
 
120
116
  //len1 > len3 && len2 > len3
121
- if (comBez.length && !signChange && isSmall ) {
117
+ if (comBez.length && !signChange && isSmall) {
122
118
 
123
- let isFlatBezier = Math.abs(area2) <= getSquareDistance(comBez[0].p0, comBez[0].p)*0.005
124
- let ptQ = !isFlatBezier ? checkLineIntersection(comL0.p0, comL0.p, comL1.p0, comL1.p, false) : null
119
+ let isFlatBezier = Math.abs(area2) < getSquareDistance(comBez[0].p0, comBez[0].p) * 0.005
120
+ let ptQ = !isFlatBezier ? checkLineIntersection(comL0.p0, comL0.p, comL1.p, comL1.p0, false, true) : null
125
121
 
126
- if (!isFlatBezier && ptQ) {
122
+ if (!ptQ) {
123
+ pathDataN.push(com);
124
+ continue
125
+ }
126
+
127
+ // check sign change
128
+ if (ptQ) {
129
+ let area0 = getPolygonArea([comL0.p0, comL0.p, comL1.p0, comL1.p], false);
130
+ let area0_abs = Math.abs(area0);
131
+ let area1 = getPolygonArea([comL0.p0, comL0.p, ptQ, comL1.p0, comL1.p], false);
132
+ let area1_abs = Math.abs(area1);
133
+ let areaDiff = Math.abs(area0_abs - area1_abs) / area0_abs
134
+ //console.log('areaDiff', areaDiff);
135
+
136
+
137
+ /*
138
+ renderPoint(markers, comL0.p0, 'green', '0.5%', '0.5')
139
+ renderPoint(markers, comL0.p, 'red', '1.5%', '0.5')
140
+ renderPoint(markers, comL1.p0, 'blue', '0.5%', '0.5')
141
+ renderPoint(markers, comL1.p, 'orange', '0.5%', '0.5')
142
+ if(!area0) {
143
+ pathDataN.push(com);
144
+ continue
145
+ }
146
+ */
147
+
148
+ let signChange = area0 < 0 && area1 > 0 || area0 > 0 && area1 < 0;
149
+
150
+ if (!ptQ || signChange || areaDiff > 0.5) {
151
+ //console.log(signChange, area0, area1, 'pts', comL0.p0, comL0.p, comL1.p0, comL1.p);
152
+ //renderPoint(markers, ptQ, 'cyan', '0.5%', '0.5')
153
+ pathDataN.push(com);
154
+ continue
155
+ }
127
156
 
128
- // final check: mid point proximity
129
- let ptM = pointAtT([comL0.p, ptQ, comL1.p0], 0.5)
157
+ }
130
158
 
131
- let ptM_bez = comBez.length===1 ? pointAtT( [comBez[0].p0, comBez[0].cp1, comBez[0].cp2, comBez[0].p], 0.5 ) : comBez[0].p ;
132
159
 
133
- let dist1 = getDistManhattan(ptM, ptM_bez) * 0.75
160
+ // final check: mid point proximity
161
+ let ptM = pointAtT([comL0.p, ptQ, comL1.p0], 0.5)
162
+ let ptM_bez = comBez.length === 1 ? pointAtT([comBez[0].p0, comBez[0].cp1, comBez[0].cp2, comBez[0].p], 0.5) : comBez[0].p;
134
163
 
135
- //renderPoint(markers, ptM, 'red', '0.5%', '0.5')
136
- //renderPoint(markers, ptM_bez, 'green', '0.5%', '0.5')
164
+ let dist1 = getDistManhattan(ptM, ptM_bez) * 0.75
137
165
 
138
- // not in tolerance – return original command
139
- if(bezThresh && dist1>bezThresh && dist1>len3*0.3){
140
- //renderPoint(markers, ptM_bez, 'cyan', '0.5%', '0.5')
141
- //renderPoint(markers, ptQ, 'magenta', '0.5%', '0.5')
142
- pathDataN.push(com);
143
- continue;
166
+ //renderPoint(markers, ptM, 'red', '0.5%', '0.5')
167
+ //renderPoint(markers, ptM_bez, 'green', '0.5%', '0.5')
144
168
 
145
- } else{
169
+ // not in tolerance – return original command
170
+ if (bezThresh && dist1 > bezThresh && dist1 > len3 * 0.3) {
171
+ //renderPoint(markers, ptM_bez, 'cyan', '0.5%', '0.5')
172
+ //renderPoint(markers, ptQ, 'magenta', '0.5%', '0.5')
173
+ pathDataN.push(com);
174
+ continue;
146
175
 
147
- //renderPoint(markers, ptQ, 'magenta', '0.5%', '0.5')
176
+ } else {
148
177
 
149
- let comQ = { type: 'Q', values: [ptQ.x, ptQ.y, comL1.p0.x, comL1.p0.y] }
150
- comQ.p0 = comL0.p;
151
- comQ.cp1 = ptQ;
152
- comQ.p = comL1.p0;
153
-
154
- // add quadratic command
155
- pathDataN.push(comL0, comQ);
156
- i += offset;
157
- //i++
178
+ //renderPoint(markers, ptQ, 'magenta', '0.5%', '0.5')
179
+ let comQ = { type: 'Q', values: [ptQ.x, ptQ.y, comL1.p0.x, comL1.p0.y] }
180
+ comQ.p0 = comL0.p;
181
+ comQ.cp1 = ptQ;
182
+ comQ.p = comL1.p0;
158
183
 
159
- //offset++
160
- continue;
161
- }
184
+ // add quadratic command
185
+ pathDataN.push(comL0, comQ);
186
+ i += offset;
187
+ //i++
162
188
 
189
+ //offset++
190
+ continue;
163
191
  }
192
+
164
193
  }
165
194
  }
166
195
  }
@@ -177,7 +206,7 @@ export function refineRoundedCorners(pathData, {
177
206
 
178
207
 
179
208
  // revert close path normalization
180
- if (normalizeClose || (isClosed && pathDataN[pathDataN.length-1].type!=='Z') ) {
209
+ if (normalizeClose || (isClosed && pathDataN[pathDataN.length - 1].type !== 'Z')) {
181
210
  pathDataN.push({ type: 'Z', values: [] })
182
211
  }
183
212
 
@@ -15,16 +15,18 @@ export function pathDataToD(pathData, optimize = 0) {
15
15
 
16
16
 
17
17
  let d = '';
18
+ let valsString = pathData[0].values.join(" ");
18
19
  let separator_command = beautify ? `\n` : (minify ? '' : ' ');
19
- let separator_type = !minify ? ' ' : '';
20
+ let separator_type = !minify ? ' ' : '';
20
21
 
21
- d = `${pathData[0].type}${separator_type}${pathData[0].values.join(" ")}${separator_command}`;
22
+ d = `${pathData[0].type}${separator_type}${valsString}${separator_command}`;
22
23
 
23
24
 
24
25
  for (let i = 1; i < len; i++) {
25
26
  let com0 = pathData[i - 1];
26
27
  let com = pathData[i];
27
28
  let { type, values } = com;
29
+ valsString = '';
28
30
 
29
31
  // Minify Arc commands (A/a) – actually sucks!
30
32
  if (minify && (type === 'A' || type === 'a')) {
@@ -36,7 +38,7 @@ export function pathDataToD(pathData, optimize = 0) {
36
38
  }
37
39
 
38
40
  // Omit type for repeated commands
39
- type = (minify && com0.type === com.type && com.type.toLowerCase() !== 'm' )
41
+ type = (minify && com0.type === com.type && com.type.toLowerCase() !== 'm')
40
42
  ? " "
41
43
  : (minify && com0.type === "M" && com.type === "L"
42
44
  ? " "
@@ -46,9 +48,7 @@ export function pathDataToD(pathData, optimize = 0) {
46
48
  // concatenate subsequent floating point values
47
49
  if (minify) {
48
50
 
49
- //console.log(optimize, beautify, minify);
50
51
 
51
- let valsString = '';
52
52
  let prevWasFloat = false;
53
53
 
54
54
  for (let v = 0, l = values.length; v < l; v++) {
@@ -85,6 +85,7 @@ export function pathDataToD(pathData, optimize = 0) {
85
85
 
86
86
  if (minify) {
87
87
  d = d
88
+ .replace(/[A-Za-z]0(?=\.)/g, m => m[0])
88
89
  .replace(/ 0\./g, " .") // Space before small decimals
89
90
  .replace(/ -/g, "-") // Remove space before negatives
90
91
  .replace(/-0\./g, "-.") // Remove leading zero from negative decimals
@@ -4,6 +4,12 @@ export function normalizePoly(pts, {
4
4
  flatten = false
5
5
  } = {}) {
6
6
 
7
+ // is stringified flat point attribute
8
+ if(typeof pts === 'string' && !isNaN(pts[0])){
9
+ pts = toPointArray(pts.split(/,| /).filter(Boolean).map(Number));
10
+ return pts
11
+ }
12
+
7
13
  if (flatten) pts = pts.flat(2);
8
14
  let poly = toArray ? polyPtsToArray(pts) : polyArrayToObject(pts)
9
15
  return poly
@@ -29,6 +35,11 @@ export function polyArrayToObject(pts) {
29
35
  return poly
30
36
  }
31
37
 
38
+ else if(pts.length>3){
39
+ pts = toPointArray(pts)
40
+ return pts
41
+ }
42
+
32
43
  return pts.map(pt => { return { x: pt[0], y: pt[1] } })
33
44
  }
34
45
 
@@ -48,4 +59,13 @@ export function polyPtsToArray(pts) {
48
59
 
49
60
  poly = Array.from(pts).map(pt => [pt.x, pt.y])
50
61
  return poly
51
- }
62
+ }
63
+
64
+ // convert flat point value array to point object array
65
+ export function toPointArray(pts) {
66
+ let ptArr = [];
67
+ for (let i = 1, l = pts.length; i < l; i += 2) {
68
+ ptArr.push({ x: pts[i - 1], y: pts[i] });
69
+ }
70
+ return ptArr;
71
+ };