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,151 +1,239 @@
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 { transformPathData } from './pathData_transform.js';
15
+ import { autoRound, roundTo } from './rounding.js';
16
+ import { attLookup } from './svg-styles-to-attributes-const.js';
17
+ import { qrDecomposeMatrix } from './transform_qr_decompose.js';
18
+
19
+
20
+ export function pathElToShape(el, {
21
+ convert_rects = false,
22
+ convert_ellipses = false,
23
+ convert_poly = false,
24
+ convert_lines = false
25
+ } = {}) {
26
+
27
+ //console.log('pathElToShape', convert_rects, convert_ellipses, convert_lines );
28
+
29
+ let pathData = parsePathDataNormalized(el.getAttribute('d'));
30
+ let coms = Array.from(new Set(pathData.map(com => com.type))).join('')
31
+
32
+ let hasArcs = (/[a]/gi).test(coms)
33
+ let hasBeziers = (/[csqt]/gi).test(coms)
34
+ let hasLines = (/[l]/gi).test(coms)
35
+ let isPoly = !(/[acqts]/gi).test(coms)
36
+ let closed = (/[z]/gi).test(coms)
37
+ let shape = null;
38
+ let type = null
39
+
40
+ let attributes = getElementAtts(el)
41
+ let attsNew = {}
42
+ let decimals = 7;
43
+
44
+ if (isPoly) {
45
+
46
+ // is line
47
+ if (pathData.length === 2 && convert_lines) {
48
+ type = 'line'
49
+ shape = document.createElementNS(svgNs, type)
50
+ let [x1, y1, x2, y2] = [...pathData[0].values, ...pathData[1].values].map(val => roundTo(val, decimals))
51
+ attsNew = { x1, y1, x2, y2 }
52
+ }
53
+ // polygon, polyline or rect
54
+ else {
55
+
56
+ let vertices = getPathDataVertices(pathData);
57
+ let bb = getPolyBBox(vertices)
58
+ let areaPoly = getPolygonArea(vertices, true)
59
+ let areaRect = bb.width * bb.height;
60
+ let areaDiff = Math.abs(1 - areaRect / areaPoly);
61
+
62
+ // is rect
63
+ if (convert_rects && areaDiff < 0.01) {
64
+ type = 'rect'
65
+ shape = document.createElementNS(svgNs, type)
66
+ let { x, y, width, height } = bb
67
+ attsNew = { x, y, width, height }
68
+
69
+ }
70
+ // polyline or polygon
71
+ else if(convert_poly) {
72
+ type = closed ? 'polygon' : 'polyline';
73
+ shape = document.createElementNS(svgNs, type)
74
+ let points = vertices.map(pt => { return [pt.x, pt.y] }).flat().map(val => roundTo(val, decimals)).join(' ')
75
+ attsNew = { points }
76
+ }
77
+ }
78
+ }
79
+ // circles or ellipses
80
+ else if (!hasLines && convert_ellipses) {
81
+
82
+ // try to convert cubics to arcs
83
+ if (!hasArcs && hasBeziers) {
84
+ pathData = pathDataCubicsToArc(pathData, { areaThreshold: 2.5 })
85
+ hasArcs = pathData.filter(com => com.type === 'A').length;
86
+ }
3
87
 
4
- export function shapeElToPath(el) {
88
+
89
+ if (hasArcs) {
90
+ let pathData2 = getPathDataVerbose(pathData, { addArcParams: true })
91
+ let arcComs = pathData2.filter(com => com.type === 'A')
92
+
93
+ let cxVals = new Set();
94
+ let cyVals = new Set();
95
+ let rxVals = new Set();
96
+ let ryVals = new Set();
97
+
98
+ if (arcComs.length > 1) {
99
+ //console.log('!!!arcComs', arcComs);
100
+ pathData2.forEach(com => {
101
+ if (com.type === 'A') {
102
+ //console.log('params', com, com.cx, com.cy, com.rx, com.ry);
103
+ cxVals.add(roundTo(com.cx, decimals))
104
+ cyVals.add(roundTo(com.cy, decimals))
105
+ rxVals.add(roundTo(com.rx, decimals))
106
+ ryVals.add(roundTo(com.ry, decimals))
107
+ }
108
+ })
109
+ }
110
+
111
+ cxVals = Array.from(cxVals)
112
+ cyVals = Array.from(cyVals)
113
+ rxVals = Array.from(rxVals)
114
+ ryVals = Array.from(ryVals)
115
+
116
+ if(cxVals.length===1 && cyVals.length===1 && rxVals.length===1 && ryVals.length===1){
117
+ let [rx, ry, cx, cy] = [rxVals[0], ryVals[0], cxVals[0], cyVals[0]]
118
+ type = rx===ry ? 'circle' : 'ellipse';
119
+ shape = document.createElementNS(svgNs, type)
120
+ attsNew = type==='circle' ? { r:rx, cx, cy } : {rx, ry, cx, cy}
121
+ }
122
+ }
123
+ }
124
+
125
+
126
+ // if el could be replaced
127
+ if (shape) {
128
+ let ignore = ['id', 'class']
129
+
130
+ // set shape attributes
131
+ for (let att in attsNew) {
132
+ shape.setAttribute(att, attsNew[att])
133
+ }
134
+
135
+ // copy old attributes
136
+ for (let att in attributes) {
137
+ //console.log(attributes);
138
+ if (attLookup.atts[att].includes(type) || ignore.includes(att) || att.startsWith('data-')) {
139
+ shape.setAttribute(att, attributes[att])
140
+ }
141
+ }
142
+
143
+ // replace
144
+ el = shape;
145
+ }
146
+
147
+ return el;
148
+
149
+ }
150
+
151
+ export function shapeElToPath(el, { width = 0,
152
+ height = 0,
153
+ convert_rects = false,
154
+ convert_ellipses = false,
155
+ convert_poly = false,
156
+ convert_lines = false,
157
+ //matrix={a:1, b:0, c:0, d:1, e:0, f:0},
158
+ matrix=null
159
+
160
+ } = {}) {
5
161
 
6
162
  let nodeName = el.nodeName.toLowerCase();
7
- if (nodeName === 'path') return el;
163
+ //console.log('shapeElToPath', nodeName);
164
+
165
+
166
+ if (
167
+ nodeName === 'path' && !matrix ||
168
+ nodeName === 'rect' && !convert_rects ||
169
+ (nodeName === 'circle' || nodeName === 'ellipse') && !convert_ellipses ||
170
+ (nodeName === 'polygon' || nodeName === 'polyline') && !convert_poly ||
171
+ (nodeName === 'line') && !convert_lines
172
+ ) return el;
173
+
174
+
175
+ let pathData = getPathDataFromEl(el, { width, height });
176
+
177
+ // shape attributes – obsolete for path els
178
+ let exclude = ['d', 'x', 'y', 'x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'dx', 'dy', 'r', 'rx', 'ry', 'width', 'height', 'points'];
179
+
180
+ // transform pathData
181
+ if(matrix && Object.values(matrix).join('')!=='100100'){
182
+ pathData = transformPathData(pathData, matrix)
183
+ exclude.push('transform', 'transform-origin')
184
+ }
8
185
 
9
- let pathData = getPathDataFromEl(el);
10
186
  let d = pathData.map(com => { return `${com.type} ${com.values} ` }).join(' ')
11
187
  let attributes = [...el.attributes].map(att => att.name);
12
188
 
13
- //console.log(d);
14
- //return []
15
-
16
- let pathN = document.createElementNS('http://www.w3.org/2000/svg', 'path');
17
- //let pathN = document.createElement('path');
189
+ let pathN = document.createElementNS(svgNs, 'path');
18
190
  pathN.setAttribute('d', d);
19
191
 
20
- let exclude = ['x', 'y', 'x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'dx', 'dy', 'r', 'rx', 'ry', 'width', 'height', 'points'];
21
192
 
193
+ // copy attributes
22
194
  attributes.forEach(att => {
23
195
  if (!exclude.includes(att)) {
24
- //console.log(att, attributes, exclude);
25
196
  let val = el.getAttribute(att);
26
197
  pathN.setAttribute(att, val)
27
198
  }
28
199
  })
29
200
 
30
201
  //el.replaceWith(pathN)
202
+ //console.log(pathN.outerHTML, d);
31
203
  return pathN
32
204
 
33
205
  }
206
+ /*
207
+ export function copyAttributes(newEl, oldEl){
34
208
 
209
+ let attributes = [...oldEl.attributes].map(att => att.name);
35
210
 
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
-
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
211
 
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
- : [];
212
+ }
213
+ */
108
214
 
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
215
 
115
- let attsH = ["x", "width", "x1", "x2", "rx", "cx", "r"];
116
- let attsV = ["y", "height", "y1", "y2", "ry", "cy"];
216
+ // retrieve pathdata from svg geometry elements
217
+ export function getPathDataFromEl(el, {
218
+ stringify = false,
219
+ width = 0,
220
+ height = 0
221
+ } = {}) {
117
222
 
223
+ let pathData = [];
224
+ let type = el.nodeName.toLowerCase();
225
+ let attNames, d, x, y, r, rx, ry, cx, cy, x1, x2, y1, y2;
118
226
 
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
- });
227
+ if (!width || !height) {
228
+ let svg = getRootSvg(el);
229
+ let viewBox = getViewBox(svg)
230
+ width = viewBox.width;
231
+ height = viewBox.height;
138
232
  }
139
233
 
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
- }
234
+ // convert relative and physical units to user-units
235
+ let atts = svgElUnitsToPixel(el, { width, height })
236
+ //console.log('atts', atts);
149
237
 
150
238
  switch (type) {
151
239
  case 'path':
@@ -155,8 +243,7 @@ export function getPathDataFromEl(el, stringify = false) {
155
243
 
156
244
  case 'rect':
157
245
  attNames = ['x', 'y', 'width', 'height', 'rx', 'ry'];
158
- ({ x, y, width, height, rx, ry } = getAtts(attNames));
159
-
246
+ ({ x=0, y=0, width=0, height=0, rx=0, ry=0 } = atts);
160
247
 
161
248
  if (!rx && !ry) {
162
249
  pathData = [
@@ -168,8 +255,8 @@ export function getPathDataFromEl(el, stringify = false) {
168
255
  ];
169
256
  } else {
170
257
 
171
- rx=rx? rx : ry;
172
- ry=ry ? ry : rx;
258
+ rx = rx ? rx : ry;
259
+ ry = ry ? ry : rx;
173
260
 
174
261
  if (rx > width / 2) {
175
262
  rx = width / 2;
@@ -196,7 +283,7 @@ export function getPathDataFromEl(el, stringify = false) {
196
283
  case 'ellipse':
197
284
 
198
285
  attNames = ['cx', 'cy', 'rx', 'ry', 'r'];
199
- ({ cx, cy, r, rx, ry } = getAtts(attNames));
286
+ ({ cx=0, cy=0, r, rx, ry } = atts);
200
287
 
201
288
  let isCircle = type === 'circle';
202
289
 
@@ -209,9 +296,11 @@ export function getPathDataFromEl(el, stringify = false) {
209
296
  ry = ry ? ry : r;
210
297
  }
211
298
 
212
- // simplified radii for cirecles
213
- let rxS = isCircle && r>=1 ? 1 : rx;
214
- let ryS = isCircle && r>=1 ? 1 : rx;
299
+
300
+ // simplified radii for circles
301
+ let rxS = isCircle && r >= 1 ? 1 : rx;
302
+ let ryS = isCircle && r >= 1 ? 1 : ry;
303
+
215
304
 
216
305
  pathData = [
217
306
  { type: "M", values: [cx + rx, cy] },
@@ -222,7 +311,7 @@ export function getPathDataFromEl(el, stringify = false) {
222
311
  break;
223
312
  case 'line':
224
313
  attNames = ['x1', 'y1', 'x2', 'y2'];
225
- ({ x1, y1, x2, y2 } = getAtts(attNames));
314
+ ({ x1, y1, x2, y2 } = atts);
226
315
  pathData = [
227
316
  { type: "M", values: [x1, y1] },
228
317
  { type: "L", values: [x2, y2] }
@@ -231,12 +320,12 @@ export function getPathDataFromEl(el, stringify = false) {
231
320
  case 'polygon':
232
321
  case 'polyline':
233
322
 
234
- let points = el.getAttribute('points').replaceAll(',', ' ').split(' ').filter(Boolean)
323
+ let points = el.getAttribute('points').split(/,| /).filter(Boolean).map(Number)
235
324
 
236
325
  for (let i = 0; i < points.length; i += 2) {
237
326
  pathData.push({
238
327
  type: (i === 0 ? "M" : "L"),
239
- values: [+points[i], +points[i + 1]]
328
+ values: [points[i], points[i + 1]]
240
329
  });
241
330
  }
242
331
  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
@@ -129,9 +129,11 @@ simplifyRDP=1,
129
129
  poly = poly.map(pt => { return [pt.x, pt.y] })
130
130
  }
131
131
  else if(polyFormat==='string'){
132
- poly = poly.map(pt => { return [pt.x, pt.y].join(',') }).join(' ')
132
+ poly = poly.map(pt => { return [pt.x, pt.y].join(',') }).flat().join(' ')
133
133
  }
134
134
 
135
+ //console.log(pathData);
136
+
135
137
  return { pathData, poly }
136
138
 
137
139
  }