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
@@ -1,10 +1,14 @@
1
1
  import { checkLineIntersection, getDistManhattan, getSquareDistance, interpolate, pointAtT } from "./geometry";
2
2
  import { getPolygonArea } from "./geometry_area";
3
+ import { getArcFromPoly } from "./geometry_deduceRadius";
3
4
  import { commandIsFlat } from "./geometry_flatness";
5
+ import { pathDataToD } from "./pathData_stringify";
6
+ import { roundTo } from "./rounding";
4
7
  import { renderPoint } from "./visualize";
5
8
 
6
9
  export function refineRoundedCorners(pathData, {
7
10
  threshold = 0,
11
+ simplifyQuadraticCorners = false,
8
12
  tolerance = 1
9
13
  } = {}) {
10
14
 
@@ -30,6 +34,9 @@ export function refineRoundedCorners(pathData, {
30
34
  let firstIsLine = pathData[1].type === 'L';
31
35
  let firstIsBez = pathData[1].type === 'C';
32
36
 
37
+ // in case we have simplified a corner connecting to the start
38
+ let M_adj = null;
39
+
33
40
 
34
41
  let normalizeClose = isClosed && firstIsBez && (lastIsLine || zIsLineto);
35
42
 
@@ -70,15 +77,18 @@ export function refineRoundedCorners(pathData, {
70
77
  // closing corner to start
71
78
  if (isClosed && lastIsBez && firstIsLine && i === l - lastOff - 1) {
72
79
  comL1 = pathData[1]
80
+ //???
73
81
  comBez = [pathData[l - lastOff]]
82
+
74
83
  //renderPoint(markers, com.p)
75
84
  }
76
85
 
86
+ // collect enclosed bezier segments
77
87
  for (let j = i + 1; j < l; j++) {
78
88
  let comN = pathData[j] ? pathData[j] : null;
79
89
  let comPrev = pathData[j - 1];
80
90
 
81
- if (comPrev.type === 'C') {
91
+ if (comPrev.type === 'C' && j > 2) {
82
92
  comBez.push(comPrev)
83
93
  }
84
94
 
@@ -89,9 +99,13 @@ export function refineRoundedCorners(pathData, {
89
99
  offset++
90
100
  }
91
101
 
102
+
103
+ //comBez = comBez.filter(com=> com.values.join(''))
104
+
92
105
  if (comL1) {
93
106
  //console.log('comL1', comL1);
94
107
 
108
+
95
109
  // linetos
96
110
  let len1 = getDistManhattan(comL0.p0, comL0.p)
97
111
  let len2 = getDistManhattan(comL1.p0, comL1.p)
@@ -113,43 +127,77 @@ export function refineRoundedCorners(pathData, {
113
127
  let isSmall = bezThresh < len1 && bezThresh < len2;
114
128
 
115
129
 
130
+ /*
131
+ */
132
+
133
+
116
134
  //len1 > len3 && len2 > len3
117
135
  if (comBez.length && !signChange && isSmall) {
118
136
 
119
- let isFlatBezier = Math.abs(area2) < getSquareDistance(comBez[0].p0, comBez[0].p) * 0.005
137
+
138
+ let isSquare = false;
139
+
140
+ if (comBez.length === 1) {
141
+ let dx = Math.abs(comBez[0].p.x - comBez[0].p0.x)
142
+ let dy = Math.abs(comBez[0].p.y - comBez[0].p0.y)
143
+ let diff = (dx - dy)
144
+ let rat = Math.abs(diff / dx)
145
+ isSquare = rat < 0.01;
146
+ }
147
+
148
+
149
+ let preferArcs = true;
150
+ preferArcs = false;
151
+
152
+
153
+ // if rectangular prefer arcs
154
+ if (preferArcs && isSquare) {
155
+
156
+ let pM = pointAtT([comBez[0].p0, comBez[0].cp1, comBez[0].cp2, comBez[0].p], 0.5)
157
+
158
+ let arcProps = getArcFromPoly([comBez[0].p0, pM, comBez[0].p])
159
+ let { r, centroid, deltaAngle } = arcProps;
160
+
161
+ let sweep = deltaAngle > 0 ? 1 : 0;
162
+ //let largeArc = Math.abs(deltaAngle) > Math.PI ? 1 : 0;
163
+ let largeArc = 0;
164
+
165
+ let comArc = { type: 'A', values: [r, r, 0, largeArc, sweep, comBez[0].p.x, comBez[0].p.y] }
166
+
167
+ pathDataN.push(comL0, comArc);
168
+ i += offset
169
+ continue
170
+
171
+ }
172
+
173
+
174
+
175
+
176
+ let areaThresh = getSquareDistance(comBez[0].p0, comBez[0].p) * 0.005
177
+ let isFlatBezier = Math.abs(area2) < areaThresh;
178
+ let isFlatBezier2 = Math.abs(area2) < areaThresh * 10
179
+
180
+
120
181
  let ptQ = !isFlatBezier ? checkLineIntersection(comL0.p0, comL0.p, comL1.p, comL1.p0, false, true) : null
121
182
 
122
- if (!ptQ) {
183
+
184
+ // exit: is rather flat or has no intersection
185
+ //|| (isFlatBezier && comBez.length === 1)
186
+ if (!ptQ || (isFlatBezier2 && comBez.length === 1)) {
123
187
  pathDataN.push(com);
124
188
  continue
125
189
  }
126
190
 
127
- // check sign change
191
+ // check sign change - exit if present
128
192
  if (ptQ) {
129
193
  let area0 = getPolygonArea([comL0.p0, comL0.p, comL1.p0, comL1.p], false);
130
194
  let area0_abs = Math.abs(area0);
131
195
  let area1 = getPolygonArea([comL0.p0, comL0.p, ptQ, comL1.p0, comL1.p], false);
132
196
  let area1_abs = Math.abs(area1);
133
197
  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
198
  let signChange = area0 < 0 && area1 > 0 || area0 > 0 && area1 < 0;
149
199
 
150
200
  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
201
  pathDataN.push(com);
154
202
  continue
155
203
  }
@@ -168,28 +216,71 @@ export function refineRoundedCorners(pathData, {
168
216
 
169
217
  // not in tolerance – return original command
170
218
  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
219
  pathDataN.push(com);
174
220
  continue;
175
221
 
176
- } else {
222
+ }
177
223
 
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;
224
+ // return simplified quadratic Bézier command
225
+ let p_Q = comL1.p0;
183
226
 
184
- // add quadratic command
185
- pathDataN.push(comL0, comQ);
186
- i += offset;
187
- //i++
227
+ // adjust previous end point to better fit the cubic curvature
228
+ let adjustQ = !simplifyQuadraticCorners;
229
+
230
+
231
+ if (adjustQ) {
232
+ //let t = 0.1333
233
+ let t = 0.1666
234
+ let p0_adj = interpolate(ptQ, comL0.p, (1 + t))
235
+ p_Q = interpolate(ptQ, comL1.p0, (1 + t))
236
+
237
+ // round for large enough segments
238
+ let isH = ptQ.y===comL0.p.y
239
+ let isV = ptQ.x===comL0.p.x
240
+ let isH2 = ptQ.y===comL1.p0.y
241
+ let isV2 = ptQ.x===comL1.p0.x
242
+
243
+ if(isSquare && com.dimA>3){
244
+ let dec = 0.5;
245
+ if(isH) p0_adj.x = roundTo(p0_adj.x, dec)
246
+ if(isV) p0_adj.y = roundTo(p0_adj.y, dec)
247
+ if(isH2) p_Q.x = roundTo(p_Q.x, dec)
248
+ if(isV2) p_Q.y = roundTo(p_Q.y, dec)
249
+ }
250
+
251
+
252
+ /*
253
+ renderPoint(markers, p0_adj, 'orange')
254
+ renderPoint(markers, p_Q, 'orange')
255
+ renderPoint(markers, comL0.p, 'green')
256
+ renderPoint(markers, comL1.p0, 'magenta')
257
+ */
258
+
259
+ // set new M starting point
260
+ if (i === l - lastOff - 1) {
261
+ //renderPoint(markers, p0_adj, 'red')
262
+ M_adj = p_Q
263
+ }
264
+
265
+ // adjust previous lineto end point
266
+ comL0.values = [p0_adj.x, p0_adj.y]
267
+ comL0.p = p0_adj;
188
268
 
189
- //offset++
190
- continue;
191
269
  }
192
270
 
271
+ let comQ = { type: 'Q', values: [ptQ.x, ptQ.y, p_Q.x, p_Q.y] }
272
+ comQ.cp1 = ptQ;
273
+ comQ.p0 = comL0.p;
274
+ comQ.p = p_Q;
275
+
276
+ // add quadratic command
277
+ pathDataN.push(comL0, comQ);
278
+
279
+
280
+
281
+ i += offset;
282
+ continue;
283
+
193
284
  }
194
285
  }
195
286
  }
@@ -203,6 +294,11 @@ export function refineRoundedCorners(pathData, {
203
294
 
204
295
  }
205
296
 
297
+ // correct starting point connecting with last corner rounding
298
+ if (M_adj) {
299
+ pathDataN[0].values = [M_adj.x, M_adj.y]
300
+ pathDataN[0].p0 = M_adj;
301
+ }
206
302
 
207
303
 
208
304
  // revert close path normalization
@@ -210,7 +306,6 @@ export function refineRoundedCorners(pathData, {
210
306
  pathDataN.push({ type: 'Z', values: [] })
211
307
  }
212
308
 
213
-
214
309
  //console.log(pathDataN);
215
310
 
216
311
  return pathDataN;
@@ -0,0 +1,420 @@
1
+ import { checkLineIntersection, getAngle, getDistAv, getDistManhattan, getDistance, getPointOnEllipse, getSquareDistance, pointAtT, rotatePoint } from "./geometry";
2
+ import { getPolygonArea } from "./geometry_area";
3
+ import { getArcFromPoly } from "./geometry_deduceRadius";
4
+ import { arcToBezierResolved, convertSmallArcsToLinetos, revertCubicQuadratic } from "./pathData_convert";
5
+ import { pathDataToD } from "./pathData_stringify";
6
+ import { renderPath, renderPoint, renderPoly } from "./visualize";
7
+
8
+
9
+ export function simplifyAdjacentRound(pathData, {
10
+ threshold = 0,
11
+ tolerance = 1,
12
+ // take arcs or cubic beziers
13
+ toCubic = false,
14
+ debug = false
15
+ } = {}) {
16
+
17
+
18
+ // fix small Arcs
19
+ pathData = convertSmallArcsToLinetos(pathData);
20
+ //return pathData
21
+
22
+ // min size threshold for corners
23
+ threshold *= tolerance;
24
+
25
+ let l = pathData.length;
26
+
27
+ // add fist command
28
+ let pathDataN = [pathData[0]]
29
+
30
+
31
+ // find adjacent cubics between extremes
32
+ //console.log(pathData);
33
+
34
+ for (let i = 1; i < l; i++) {
35
+ let comPrev = pathData[i - 1];
36
+ let com = pathData[i];
37
+ let comN = pathData[i + 1] || null;
38
+
39
+ if (!comN) {
40
+ pathDataN.push(com);
41
+ break
42
+ }
43
+
44
+ let { type, extreme = false, p0, p, dimA = 0 } = com;
45
+ // for short segment detection
46
+ let dimAN = comN.dimA;
47
+ let dimA0 = dimA + dimAN;
48
+ let thresh = 0.1
49
+ let extreme0 = extreme
50
+
51
+ // ignore short linetos
52
+ let isShortN = dimAN < dimA0 * thresh;
53
+ //let isFlat =
54
+
55
+
56
+ // adjacent cubic commands - accept short in between linetos
57
+ if ((type === 'C') && (comN.type === 'C' || isShortN)) {
58
+
59
+ //console.log((comN.type !== 'C' && isShortN), comN);
60
+ let candidates = []
61
+
62
+ for (let j = i + 1; j < l; j++) {
63
+ let comN = pathData[j];
64
+ let { type, extreme = false, corner = false, dimA = 0 } = comN;
65
+ let isShort = dimA < dimA0 * thresh;
66
+
67
+ // skip for type change(unless very short), extremes or corners
68
+ /*
69
+ if ( (comN.extreme || comN.corner) ) {
70
+ if(!extreme && !corner) candidates.push(comN)
71
+ break;
72
+ }
73
+ */
74
+
75
+ //|| (type !== 'C' && !isShort && !corner && !extreme)
76
+ if (extreme || corner) {
77
+
78
+ /*
79
+ if (comN.extreme) {
80
+ renderPoint(markers, comN.p, 'cyan')
81
+ }
82
+ else if (comN.corner) {
83
+ renderPoint(markers, comN.p, 'magenta')
84
+ }
85
+ else if (type !== 'C') {
86
+ console.log(type);
87
+ renderPoint(markers, comN.p, 'orange')
88
+ }
89
+ */
90
+
91
+ if ((extreme || corner) && type === 'C') {
92
+ //renderPoint(markers, com.p, 'purple')
93
+ //break
94
+ }
95
+
96
+ //&& comN.type !== 'C'
97
+ if (isShort && comN.type !== 'C') {
98
+ //renderPoint(markers, comN.p, 'purple')
99
+ //candidates.push(comN)
100
+ }
101
+
102
+
103
+ if ((extreme && !corner)) {
104
+ //console.log(comN);
105
+ //if(extreme) renderPoint(markers, comN.p0, 'purple')
106
+ //candidates.push(comN)
107
+ candidates.push(comN)
108
+ }
109
+
110
+ break;
111
+ }
112
+
113
+
114
+ candidates.push(comN)
115
+ }
116
+
117
+ // try to create arc command
118
+ if (candidates.length > 1) {
119
+
120
+ let clen = candidates.length;
121
+ let pts = [com.p0, com.p,];
122
+
123
+ // add interpolated points to prevent wrong arc replacements
124
+ candidates.forEach(c => {
125
+ if (c.type === 'C') {
126
+ let pt = pointAtT([c.p0, c.cp1, c.cp2, c.p], 0.5)
127
+ pts.push(pt)
128
+ }
129
+ pts.push(c.p)
130
+ })
131
+
132
+ //let pts = [com.p0, com.p, ...candidates.map(com => com.p)];
133
+ //console.log('pts', pts);
134
+
135
+ let precise = true
136
+ let arcProps = getArcFromPoly(pts, precise)
137
+
138
+ // could be combined
139
+ if (arcProps) {
140
+ //console.log(arcProps, pts);
141
+
142
+ let { centroid, r, deltaAngle, startAngle, endAngle } = arcProps;
143
+ let sweep = deltaAngle > 0 ? 1 : 0;
144
+ //let area = getPolygonArea(pts)
145
+ //let sweep = area > 0 ? 1 : 0;
146
+ let largeArc = Math.abs(deltaAngle) > Math.PI ? 1 : 0;
147
+ largeArc = 0;
148
+ let comLast = candidates[clen - 1]
149
+ let p = comLast.p
150
+
151
+ let comArc = { type: 'A', values: [r, r, 0, largeArc, sweep, p.x, p.y] }
152
+
153
+ //console.log(comArc);
154
+
155
+ comArc.dimA = getDistManhattan(p0, p)
156
+ comArc.p0 = p0
157
+ comArc.p = p
158
+ comArc.error = 0
159
+ comArc.directionChange = comLast.directionChange
160
+ comArc.extreme = comLast.extreme
161
+ comArc.corner = comLast.corner
162
+ pathDataN.push(comArc)
163
+
164
+ i += candidates.length
165
+ continue
166
+
167
+ }
168
+
169
+ // arc radius calculation failed - return original
170
+ else {
171
+ pathDataN.push(com)
172
+ }
173
+ }
174
+
175
+ // could not be simplified – return original command
176
+ else {
177
+ pathDataN.push(com)
178
+ }
179
+
180
+ }
181
+ // all other commands
182
+ else {
183
+ pathDataN.push(com)
184
+ }
185
+ }
186
+
187
+ //console.log(pathDataN);
188
+ return pathDataN
189
+ }
190
+
191
+
192
+ export function refineRoundSegments(pathData, {
193
+ threshold = 0,
194
+ tolerance = 1,
195
+ // take arcs or cubic beziers
196
+ toCubic = false,
197
+ debug = false
198
+ } = {}) {
199
+
200
+
201
+ // min size threshold for corners
202
+ threshold *= tolerance;
203
+
204
+ let l = pathData.length;
205
+
206
+ // add fist command
207
+ let pathDataN = [pathData[0]]
208
+
209
+ // just for debugging
210
+ let pathDataTest = []
211
+
212
+ for (let i = 1; i < l; i++) {
213
+ let com = pathData[i];
214
+ let { type } = com;
215
+ let comP = pathData[i - 1];
216
+ let comN = pathData[i + 1] ? pathData[i + 1] : null;
217
+ let comN2 = pathData[i + 2] ? pathData[i + 2] : null;
218
+ let comN3 = pathData[i + 3] ? pathData[i + 3] : null;
219
+ let comBez = null;
220
+
221
+ if ((com.type === 'C' || com.type === 'Q')) comBez = com;
222
+ else if (comN && (comN.type === 'C' || comN.type === 'Q')) comBez = comN;
223
+
224
+
225
+ let cpts = comBez ? (comBez.type === 'C' ? [comBez.p0, comBez.cp1, comBez.cp2, comBez.p] : [comBez.p0, comBez.cp1, comBez.p]) : []
226
+
227
+ let areaBez = 0;
228
+ let areaLines = 0;
229
+ let signChange = false;
230
+ let L1, L2;
231
+ let combine = false
232
+
233
+ let p0_S, p_S;
234
+ let poly = []
235
+ let pMid;
236
+
237
+
238
+ // 2. line-line-bezier-line-line
239
+ if (
240
+ comN2 && comN3 &&
241
+ comP.type === 'L' &&
242
+ type === 'L' &&
243
+ comBez &&
244
+ comN2.type === 'L' &&
245
+ (comN3.type === 'L' || comN3.type === 'Z')
246
+ ) {
247
+
248
+ L1 = [com.p0, com.p];
249
+ L2 = [comN2.p0, comN2.p];
250
+ p0_S = com.p0
251
+ p_S = comN2.p
252
+
253
+ // don't allow sign changes
254
+ areaBez = getPolygonArea(cpts, false)
255
+ areaLines = getPolygonArea([...L1, ...L2], false)
256
+ signChange = (areaBez < 0 && areaLines > 0) || (areaBez > 0 && areaLines < 0)
257
+
258
+ if (!signChange) {
259
+
260
+ // mid point of mid bezier
261
+ pMid = pointAtT(cpts, 0.5)
262
+
263
+ // add to poly
264
+ poly = [p0_S, pMid, p_S]
265
+
266
+ combine = true
267
+ }
268
+
269
+ }
270
+
271
+ // 1. line-bezier-bezier-line
272
+ else if (comN && (type === 'C' || type === 'Q') && comP.type === 'L') {
273
+
274
+ //renderPoint(markers, com.p)
275
+
276
+ // 1.2 next is cubic next is lineto
277
+ if (comN2 && comN2.type === 'L' && (comN.type === 'C' || comN.type === 'Q')) {
278
+
279
+ combine = true
280
+
281
+ L1 = [comP.p0, comP.p];
282
+ L2 = [comN2.p0, comN2.p];
283
+ p0_S = comP.p
284
+ p_S = comN2.p0
285
+
286
+ // mid point of mid bezier
287
+ pMid = comBez.p
288
+
289
+ // add to poly
290
+ poly = [p0_S, comBez.p, p_S]
291
+
292
+
293
+ }
294
+ }
295
+
296
+
297
+ /**
298
+ * calculate either combined
299
+ * cubic or arc commands
300
+ */
301
+ if (combine) {
302
+
303
+
304
+ // try to find center of arc
305
+ let arcProps = getArcFromPoly(poly)
306
+ if (arcProps) {
307
+
308
+ let { centroid, r, deltaAngle, startAngle, endAngle } = arcProps;
309
+
310
+ let xAxisRotation = 0;
311
+ let sweep = deltaAngle > 0 ? 1 : 0;
312
+ let largeArc = Math.abs(deltaAngle) > Math.PI ? 1 : 0;
313
+
314
+ let pCM = rotatePoint(p0_S, centroid.x, centroid.y, deltaAngle * 0.5)
315
+
316
+
317
+ let dist2 = getDistAv(pCM, pMid)
318
+ let thresh = getDistAv(p0_S, p_S) * 0.05
319
+ let bezierCommands;
320
+
321
+ // point is close enough
322
+ if (dist2 < thresh) {
323
+
324
+ //toCubic = false;
325
+
326
+ bezierCommands = arcToBezierResolved(
327
+ {
328
+ p0: p0_S,
329
+ p: p_S,
330
+ centroid,
331
+ rx: r,
332
+ ry: r,
333
+ xAxisRotation,
334
+ sweep,
335
+ largeArc,
336
+ deltaAngle,
337
+ startAngle,
338
+ endAngle
339
+ }
340
+ );
341
+
342
+ if (bezierCommands.length === 1) {
343
+
344
+ // prefer more compact quadratic - otherwise arcs
345
+ let comBezier = revertCubicQuadratic(p0_S, bezierCommands[0].cp1, bezierCommands[0].cp2, p_S)
346
+
347
+ if (comBezier.type === 'Q') {
348
+ toCubic = true
349
+ }else{
350
+ comBezier = bezierCommands[0]
351
+ }
352
+
353
+ com = comBezier
354
+ //console.log('bezierCommands', comBezier);
355
+
356
+ }
357
+
358
+
359
+ // prefer arcs if 2 cubics are required
360
+ if (bezierCommands.length > 1) toCubic = false;
361
+
362
+
363
+ //toCubic = false
364
+
365
+ // return elliptic arc commands
366
+ if (!toCubic) {
367
+ // rewrite simplified command
368
+ com.type = 'A'
369
+ com.values = [r, r, xAxisRotation, largeArc, sweep, p_S.x, p_S.y];
370
+ }
371
+
372
+ //console.log(com);
373
+
374
+ com.p0 = p0_S;
375
+ com.p = p_S;
376
+ com.extreme = false;
377
+ com.corner = false;
378
+
379
+ // test rendering
380
+ //debug=true
381
+
382
+ /*
383
+ if (debug) {
384
+ // arcs
385
+ if (!toCubic) {
386
+ pathDataTest = [
387
+ { type: 'M', values: [p0_S.x, p0_S.y] },
388
+ { type: 'A', values: [r, r, xAxisRotation, largeArc, sweep, p_S.x, p_S.y] },
389
+ ]
390
+ }
391
+ // cubics
392
+ else {
393
+ pathDataTest = [
394
+ { type: 'M', values: [p0_S.x, p0_S.y] },
395
+ ...bezierCommands
396
+ ]
397
+
398
+ }
399
+
400
+ let d = pathDataToD(pathDataTest);
401
+ renderPath(markers, d, 'orange', '0.5%', '0.5')
402
+ }
403
+ */
404
+
405
+ pathDataN.push(com);
406
+ i++
407
+ continue
408
+
409
+ }
410
+ }
411
+ }
412
+
413
+ // pass through
414
+ pathDataN.push(com)
415
+ }
416
+
417
+ //let d= pathDataToD(pathDataN)
418
+ //console.log('!pathDataN', d);
419
+ return pathDataN;
420
+ }