svg-path-simplify 0.4.1 → 0.4.3

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 (48) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +6 -4
  3. package/dist/svg-path-simplify.esm.js +2450 -888
  4. package/dist/svg-path-simplify.esm.min.js +2 -2
  5. package/dist/svg-path-simplify.js +2450 -888
  6. package/dist/svg-path-simplify.min.js +2 -2
  7. package/dist/svg-path-simplify.pathdata.esm.js +167 -85
  8. package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
  9. package/docs/privacy-webapp.md +24 -0
  10. package/index.html +333 -132
  11. package/package.json +5 -2
  12. package/src/css_parse.js +317 -0
  13. package/src/detect_input.js +34 -4
  14. package/src/pathData_simplify_harmonize_cpts.js +77 -1
  15. package/src/pathSimplify-main.js +246 -262
  16. package/src/pathSimplify-presets.js +243 -0
  17. package/src/poly-fit-curve-schneider.js +14 -7
  18. package/src/simplify_poly_RC.js +102 -0
  19. package/src/simplify_poly_RDP.js +109 -1
  20. package/src/simplify_poly_radial_distance.js +3 -3
  21. package/src/string_helpers.js +144 -0
  22. package/src/svgii/convert_units.js +8 -2
  23. package/src/svgii/geometry.js +182 -3
  24. package/src/svgii/geometry_length.js +237 -0
  25. package/src/svgii/pathData_convert.js +43 -1
  26. package/src/svgii/pathData_fix_directions.js +6 -0
  27. package/src/svgii/pathData_fromPoly.js +3 -3
  28. package/src/svgii/pathData_getLength.js +86 -0
  29. package/src/svgii/pathData_parse.js +2 -0
  30. package/src/svgii/pathData_parse_els.js +189 -189
  31. package/src/svgii/pathData_split_to_groups.js +168 -0
  32. package/src/svgii/pathData_stringify.js +26 -64
  33. package/src/svgii/pathData_toPolygon.js +3 -4
  34. package/src/svgii/poly_analyze.js +61 -0
  35. package/src/svgii/poly_normalize.js +11 -2
  36. package/src/svgii/poly_to_pathdata.js +85 -24
  37. package/src/svgii/rounding.js +8 -7
  38. package/src/svgii/svg-styles-to-attributes-const.js +1 -0
  39. package/src/svgii/svg_cleanup.js +467 -421
  40. package/src/svgii/svg_cleanup_convertPathLength.js +32 -0
  41. package/src/svgii/svg_cleanup_general_svg_atts.js +97 -0
  42. package/src/svgii/svg_cleanup_normalize_transforms.js +83 -0
  43. package/src/svgii/svg_cleanup_remove_els_and_atts.js +72 -0
  44. package/src/svgii/svg_cleanup_ungroup.js +36 -0
  45. package/src/svgii/svg_el_parse_style_props.js +76 -28
  46. package/src/svgii/svg_getElementLength.js +67 -0
  47. package/tests/testSVG_shape.js +59 -0
  48. package/tests/testSVG_transform.js +61 -0
@@ -3,7 +3,8 @@ import {abs, acos, asin, atan, atan2, ceil, cos, exp, floor,
3
3
  log, max, min, pow, random, round, sin, sqrt, tan, PI} from '/.constants.js';
4
4
  */
5
5
 
6
- import { rad2Deg, root2 } from "../constants";
6
+ import { deg2rad, rad2Deg, root2 } from "../constants";
7
+ //import { roundTo } from "./rounding";
7
8
  //import { getPolyBBox } from "./geometry_bbox";
8
9
  import { renderPoint } from "./visualize";
9
10
 
@@ -23,6 +24,11 @@ export function getAngle(p1, p2, normalize = false) {
23
24
  }
24
25
 
25
26
 
27
+
28
+
29
+
30
+
31
+
26
32
  export function getDeltaAngle2(centerPoint, startPoint, endPoint, largeArc = false) {
27
33
 
28
34
  const normalizeAngle = (angle) => {
@@ -370,9 +376,40 @@ export function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, retu
370
376
 
371
377
 
372
378
 
379
+
373
380
  /**
374
381
  * get vertices from path command final on-path points
375
382
  */
383
+
384
+ export function getPathDataVertices(pathData=[], includeCpts = false, decimals = -1) {
385
+ let polyPoints = [];
386
+ //console.log(pathData);
387
+
388
+ pathData.forEach((com) => {
389
+ let { type, values } = com;
390
+
391
+ // get final on path point from last 2 values
392
+ if (values.length) {
393
+
394
+ // round
395
+ if (decimals > -1) values = values.map(val => +val.toFixed(decimals))
396
+
397
+ if (includeCpts) {
398
+
399
+ for (let i = 1; i < values.length; i += 2) {
400
+ polyPoints.push({ x: values[i - 1], y: values[i] });
401
+ }
402
+
403
+ } else {
404
+ polyPoints.push({ x: values[values.length - 2], y: values[values.length - 1] });
405
+ }
406
+
407
+ }
408
+ });
409
+ return polyPoints;
410
+ }
411
+
412
+ /*
376
413
  export function getPathDataVertices(pathData) {
377
414
  let polyPoints = [];
378
415
  let p0 = { x: pathData[0].values[0], y: pathData[0].values[1] };
@@ -389,6 +426,7 @@ export function getPathDataVertices(pathData) {
389
426
  });
390
427
  return polyPoints;
391
428
  };
429
+ */
392
430
 
393
431
 
394
432
 
@@ -396,7 +434,148 @@ export function getPathDataVertices(pathData) {
396
434
  * based on @cuixiping;
397
435
  * https://stackoverflow.com/questions/9017100/calculate-center-of-svg-arc/12329083#12329083
398
436
  */
399
- export function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2, y2) {
437
+
438
+
439
+ export function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2, y2, normalize = true
440
+ ) {
441
+
442
+ // helper for angle calculation
443
+ const getAngle = (cx, cy, x, y, normalize = true) => {
444
+ let angle = Math.atan2(y - cy, x - cx);
445
+ if (normalize && angle < 0) angle += Math.PI * 2
446
+ return angle
447
+ };
448
+
449
+ // make sure rx, ry are positive
450
+ rx = Math.abs(rx);
451
+ ry = Math.abs(ry);
452
+
453
+ // normalize xAxis rotation
454
+ xAxisRotation = rx === ry ? 0 : (xAxisRotation < 0 && normalize ? xAxisRotation + 360 : xAxisRotation);
455
+
456
+
457
+ // create data object
458
+ let arcData = {
459
+ cx: 0,
460
+ cy: 0,
461
+ // rx/ry values may be deceptive in arc commands
462
+ rx: rx,
463
+ ry: ry,
464
+ startAngle: 0,
465
+ endAngle: 0,
466
+ deltaAngle: 0,
467
+ clockwise: sweep,
468
+ // copy explicit arc properties
469
+ xAxisRotation,
470
+ largeArc,
471
+ sweep
472
+ };
473
+
474
+
475
+ if (rx == 0 || ry == 0) {
476
+ // invalid arguments
477
+ throw Error("rx and ry can not be 0");
478
+ }
479
+
480
+
481
+ /**
482
+ * if rx===ry x-axis rotation is ignored
483
+ * otherwise convert degrees to radians
484
+ */
485
+ let phi = rx === ry ? 0 : xAxisRotation * deg2rad;
486
+ let cx, cy
487
+
488
+ let s_phi = !phi ? 0 : Math.sin(phi);
489
+ let c_phi = !phi ? 1 : Math.cos(phi);
490
+
491
+ let hd_x = (x1 - x2) / 2;
492
+ let hd_y = (y1 - y2) / 2;
493
+ let hs_x = (x1 + x2) / 2;
494
+ let hs_y = (y1 + y2) / 2;
495
+
496
+ // F6.5.1
497
+ let x1_ = !phi ? hd_x : c_phi * hd_x + s_phi * hd_y;
498
+ let y1_ = !phi ? hd_y : c_phi * hd_y - s_phi * hd_x;
499
+
500
+ // F.6.6 Correction of out-of-range radii
501
+ // Step 3: Ensure radii are large enough
502
+ let lambda = (x1_ * x1_) / (rx * rx) + (y1_ * y1_) / (ry * ry);
503
+ if (lambda > 1) {
504
+ let lambdaRoot = Math.sqrt(lambda);
505
+ rx = rx * lambdaRoot;
506
+ ry = ry * lambdaRoot;
507
+
508
+ // save real rx/ry
509
+ arcData.rx = rx;
510
+ arcData.ry = ry;
511
+ }
512
+
513
+ let rxry = rx * ry;
514
+ let rxy1_ = rx * y1_;
515
+ let ryx1_ = ry * x1_;
516
+ let sum_of_sq = rxy1_ ** 2 + ryx1_ ** 2; // sum of square
517
+ if (!sum_of_sq) {
518
+ //console.log('error:', rx, ry, rxy1_, ryx1_);
519
+ throw Error("start point can not be same as end point");
520
+ }
521
+ let coe = Math.sqrt(Math.abs((rxry * rxry - sum_of_sq) / sum_of_sq));
522
+ if (largeArc === sweep) {
523
+ coe = -coe;
524
+ }
525
+
526
+ // F6.5.2
527
+ let cx_ = (coe * rxy1_) / ry;
528
+ let cy_ = (-coe * ryx1_) / rx;
529
+
530
+ /** F6.5.3
531
+ * center point of ellipse
532
+ */
533
+ cx = !phi ? hs_x + cx_ : c_phi * cx_ - s_phi * cy_ + hs_x;
534
+ cy = !phi ? hs_y + cy_ : s_phi * cx_ + c_phi * cy_ + hs_y;
535
+ arcData.cy = cy;
536
+ arcData.cx = cx;
537
+
538
+ /** F6.5.5
539
+ * calculate angles between center point and
540
+ * commands starting and final on path point
541
+ */
542
+ let startAngle = getAngle(cx, cy, x1, y1, normalize);
543
+ let endAngle = getAngle(cx, cy, x2, y2, normalize);
544
+
545
+ // adjust end angle
546
+
547
+ // Adjust angles based on sweep direction
548
+ if (sweep) {
549
+ // Clockwise
550
+ if (endAngle < startAngle) {
551
+ endAngle += Math.PI * 2;
552
+ }
553
+ } else {
554
+ // Counterclockwise
555
+ if (endAngle > startAngle) {
556
+ endAngle -= Math.PI * 2;
557
+ }
558
+ }
559
+
560
+ let deltaAngle = endAngle - startAngle;
561
+
562
+ // The rest of your code remains the same
563
+ arcData.startAngle = startAngle;
564
+ arcData.startAngle_deg = startAngle * rad2Deg;
565
+ arcData.endAngle = endAngle;
566
+ arcData.endAngle_deg = endAngle * rad2Deg;
567
+ arcData.deltaAngle = deltaAngle;
568
+ arcData.deltaAngle_deg = deltaAngle * rad2Deg;
569
+
570
+ //console.log('arc', arcData);
571
+ return arcData;
572
+ }
573
+
574
+
575
+
576
+
577
+
578
+ export function svgArcToCenterParam_back(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2, y2) {
400
579
 
401
580
  // helper for angle calculation
402
581
  const getAngle = (cx, cy, x, y) => {
@@ -1292,7 +1471,7 @@ export function getDistance(p1, p2, isArray = false) {
1292
1471
  let dy = isArray ? p2[1] - p1[1] : (p2.y - p1.y);
1293
1472
 
1294
1473
  //console.log('dx', dx, dy, p1, p2);
1295
- return sqrt(dx * dx + dy * dy);
1474
+ return Math.sqrt(dx * dx + dy * dy);
1296
1475
  }
1297
1476
 
1298
1477
  // just an alias
@@ -0,0 +1,237 @@
1
+ import { getDistance } from "./geometry";
2
+
3
+ // Legendre Gauss weight and abscissa values
4
+ export const waArr_global = [];
5
+
6
+
7
+
8
+ export function getLength(pts, {
9
+ t = 1,
10
+ waArr = []
11
+ } = {}) {
12
+
13
+
14
+ const cubicBezierLength = (p0, cp1, cp2, p, t = 0, wa = []) => {
15
+ if (t === 0) {
16
+ return 0;
17
+ }
18
+
19
+ t = t > 1 ? 1 : t < 0 ? 0 : t;
20
+ let t2 = t / 2;
21
+
22
+ /**
23
+ * set higher legendre gauss weight abscissae values
24
+ * by more accurate weight/abscissae lookups
25
+ * https://pomax.github.io/bezierinfo/legendre-gauss.html
26
+ */
27
+
28
+
29
+ let sum = 0;
30
+
31
+ let x0 = p0.x, y0 = p0.y, cp1x = cp1.x, cp1y = cp1.y, cp2x = cp2.x, cp2y = cp2.y, px = p.x, py = p.y;
32
+
33
+
34
+ for (let i = 0, len = wa.length; i < len; i++) {
35
+ // weight and abscissae
36
+ let [w, a] = [wa[i][0], wa[i][1]];
37
+ let ct1_t = t2 * a;
38
+ let ct0 = -ct1_t + t2;
39
+
40
+ let xbase0 = base3(ct0, x0, cp1x, cp2x, px)
41
+ let ybase0 = base3(ct0, y0, cp1y, cp2y, py)
42
+
43
+ let comb0 = xbase0 * xbase0 + ybase0 * ybase0;
44
+
45
+ sum += w * Math.sqrt(comb0)
46
+
47
+ }
48
+ return t2 * sum;
49
+ }
50
+
51
+
52
+ const quadraticBezierLength = (p0, cp1, p, t, checkFlat = false) => {
53
+ if (t === 0) {
54
+ return 0;
55
+ }
56
+ // is flat/linear – treat as line
57
+ if (checkFlat) {
58
+ let l1 = getDistance(p0, cp1) + getDistance(cp1, p);
59
+ let l2 = getDistance(p0, p);
60
+ if (l1 === l2) {
61
+ return l2;
62
+ }
63
+ }
64
+
65
+ let a, b, c, d, e, e1, d1, v1x, v1y;
66
+ v1x = cp1.x * 2;
67
+ v1y = cp1.y * 2;
68
+ d = p0.x - v1x + p.x;
69
+ d1 = p0.y - v1y + p.y;
70
+ e = v1x - 2 * p0.x;
71
+ e1 = v1y - 2 * p0.y;
72
+ a = 4 * (d * d + d1 * d1);
73
+ b = 4 * (d * e + d1 * e1);
74
+ c = e * e + e1 * e1;
75
+
76
+ const bt = b / (2 * a),
77
+ ct = c / a,
78
+ ut = t + bt,
79
+ //k = ct - bt ** 2;
80
+ k = ct - bt * bt;
81
+
82
+ return (
83
+ (Math.sqrt(a) / 2) *
84
+ (ut * Math.sqrt(ut * ut + k) -
85
+ bt * Math.sqrt(bt * bt + k) +
86
+ k *
87
+ Math.log((ut + Math.sqrt(ut * ut + k)) / (bt + Math.sqrt(bt * bt + k))))
88
+ );
89
+ }
90
+
91
+
92
+ let length
93
+ if (pts.length === 4) {
94
+ length = cubicBezierLength(pts[0], pts[1], pts[2], pts[3], t, waArr)
95
+
96
+ }
97
+ else if (pts.length === 3) {
98
+ length = quadraticBezierLength(pts[0], pts[1], pts[2], t)
99
+ }
100
+ else {
101
+ length = getDistance(pts[0], pts[1])
102
+ }
103
+
104
+ return length;
105
+ }
106
+
107
+
108
+
109
+
110
+
111
+
112
+ // LG weight/abscissae generator
113
+ export function getLegendreGaussValues(n, x1 = -1, x2 = 1) {
114
+ console.log('add new LG', n);
115
+
116
+ let waArr = []
117
+ let z1, z, xm, xl, pp, p3, p2, p1;
118
+ const m = (n + 1) >> 1;
119
+ xm = 0.5 * (x2 + x1);
120
+ xl = 0.5 * (x2 - x1);
121
+
122
+ for (let i = m - 1; i >= 0; i--) {
123
+ z = Math.cos((Math.PI * (i + 0.75)) / (n + 0.5));
124
+ do {
125
+ p1 = 1;
126
+ p2 = 0;
127
+ for (let j = 0; j < n; j++) {
128
+ //Loop up the recurrence relation to get the Legendre polynomial evaluated at z.
129
+ p3 = p2;
130
+ p2 = p1;
131
+ p1 = ((2 * j + 1) * z * p2 - j * p3) / (j + 1);
132
+ }
133
+
134
+ pp = (n * (z * p1 - p2)) / (z * z - 1);
135
+ z1 = z;
136
+ z = z1 - p1 / pp; //Newton’s method
137
+
138
+ } while (Math.abs(z - z1) > 1.0e-14);
139
+
140
+ let weight = (2 * xl) / ((1 - z * z) * pp * pp);
141
+ let abscissa = xm + xl * z;
142
+
143
+ waArr.push(
144
+ [weight, -abscissa],
145
+ [weight, abscissa],
146
+ )
147
+ }
148
+
149
+ return waArr;
150
+ }
151
+
152
+
153
+
154
+
155
+
156
+ export function base3(t, p1, p2, p3, p4) {
157
+ let t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
158
+ t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
159
+ return t * t2 - 3 * p1 + 3 * p2;
160
+ };
161
+
162
+
163
+ export function getPolygonLength(pts=[], isPoly=false){
164
+
165
+ let len = 0;
166
+ let l=pts.length;
167
+
168
+ for(let i=1; i<l; i++){
169
+ let p1 = pts[i-1]
170
+ let p2 = pts[i]
171
+ len += getDistance(p1, p2)
172
+ }
173
+ if(isPoly){
174
+ len += getDistance(pts[l-1], pts[0])
175
+ }
176
+ return len
177
+ }
178
+
179
+
180
+ /**
181
+ * Ramanujan approximation
182
+ * based on: https://www.mathsisfun.com/geometry/ellipse-perimeter.html#tool
183
+ */
184
+ export function getEllipseLength(rx=0, ry=0) {
185
+ // is circle
186
+ if (rx === ry) {
187
+ //console.log('is circle')
188
+ return 2 * Math.PI * rx;
189
+ }
190
+
191
+ let c=rx+ry
192
+ let d = (rx - ry) / c;
193
+ let h = d*d
194
+
195
+ let totalLength = Math.PI * c * (1 + 3 * h / (10 + Math.sqrt(4 - 3 * h) ));
196
+ return totalLength;
197
+ };
198
+
199
+
200
+
201
+ /**
202
+ * ellipse helpers
203
+ * approximate ellipse length
204
+ * by Legendre-Gauss
205
+ */
206
+
207
+ export function getCircleArcLength(r = 0, deltaAngle = 0) {
208
+ if(r===0) {
209
+ console.warn('Radius must be positive');
210
+ return 0;
211
+ }
212
+ let len = 2 * Math.PI * r * (1 / 360 * Math.abs(deltaAngle * 180 / Math.PI))
213
+ return len
214
+ }
215
+
216
+ export function getEllipseLengthLG(rx, ry, startAngle, endAngle, wa = []) {
217
+
218
+ // Transform [-1, 1] interval to [startAngle, endAngle]
219
+ let halfInterval = (endAngle - startAngle) * 0.5;
220
+ let midpoint = (endAngle + startAngle) * 0.5;
221
+
222
+ // Arc length integral approximation
223
+ let arcLength = 0;
224
+ for (let i = 0; i < wa.length; i++) {
225
+ let [weight, abscissae] = wa[i];
226
+ let theta = midpoint + halfInterval * abscissae;
227
+
228
+ let a = rx * Math.sin(theta);
229
+ let b = ry * Math.cos(theta);
230
+ let integrand = Math.sqrt(
231
+ a * a + b * b
232
+ );
233
+ arcLength += weight * integrand;
234
+ }
235
+
236
+ return Math.abs(halfInterval * arcLength)
237
+ }
@@ -69,6 +69,7 @@ export function convertPathData(pathData, {
69
69
  toShorthands = true,
70
70
  toLonghands = false,
71
71
  toRelative = true,
72
+ toMixed = false,
72
73
  toAbsolute = false,
73
74
  decimals = 3,
74
75
  arcToCubic = false,
@@ -79,6 +80,7 @@ export function convertPathData(pathData, {
79
80
  hasShorthands = true,
80
81
  hasQuadratics = true,
81
82
  hasArcs = true,
83
+ isPoly = false,
82
84
  optimizeArcs = true,
83
85
  testTypes = false
84
86
 
@@ -86,6 +88,7 @@ export function convertPathData(pathData, {
86
88
  } = {}) {
87
89
 
88
90
 
91
+ let pathDataAbs = []
89
92
 
90
93
  // pathdata properties - test= true adds a manual test
91
94
  if (testTypes) {
@@ -101,7 +104,9 @@ export function convertPathData(pathData, {
101
104
 
102
105
  // some params exclude each other
103
106
  toRelative = toAbsolute ? false : toRelative;
104
- toShorthands = toLonghands ? false : toShorthands
107
+ //toAbsolute = !toRelative ? true : toAbsolute;
108
+ toShorthands = toLonghands ? false : toShorthands;
109
+ //toLonghands = !toShorthands ? true : toLonghands;
105
110
 
106
111
 
107
112
  if (toAbsolute) pathData = pathDataToAbsolute(pathData);
@@ -115,19 +120,45 @@ export function convertPathData(pathData, {
115
120
  //if(decimals>-1 && decimals<2) pathData = roundPathData(pathData, decimals);
116
121
  if (toShorthands) pathData = pathDataToShorthands(pathData);
117
122
 
123
+ //console.log('hasArcs', hasArcs, arcToCubic, pathData);
118
124
  if (hasArcs && arcToCubic) pathData = pathDataArcsToCubics(pathData)
119
125
 
120
126
  //console.log(toShorthands, toRelative, decimals);
121
127
  if (hasQuadratics && quadraticToCubic) pathData = pathDataQuadraticToCubic(pathData);
122
128
 
129
+ if(toMixed) toRelative = true
123
130
 
124
131
  // pre round - before relative conversion to minimize distortions
125
132
  if (decimals > -1 && toRelative) pathData = roundPathData(pathData, decimals);
126
133
 
134
+ // clone absolute pathdata
135
+ if(toMixed){
136
+ pathDataAbs = JSON.parse(JSON.stringify(pathData))
137
+ }
127
138
 
128
139
  if (toRelative) pathData = pathDataToRelative(pathData);
129
140
  if (decimals > -1) pathData = roundPathData(pathData, decimals);
130
141
 
142
+
143
+ // choose most compact commands: relative or absolute
144
+ if(toMixed){
145
+ for(let i=0; i<pathData.length; i++){
146
+ let com = pathData[i]
147
+ let comA = pathDataAbs[i]
148
+ // compare Lengths
149
+ let comStr = [com.type, com.values.join(' ')].join('').replaceAll(' -', '-').replaceAll(' 0.', ' .')
150
+ let comStrA = [comA.type, comA.values.join(' ')].join('').replaceAll(' -', '-').replaceAll(' 0.', ' .')
151
+
152
+ let lenR = comStr.length;
153
+ let lenA = comStrA.length;
154
+
155
+ if(lenA<lenR){
156
+ //console.log('absolute is shorter', comStrA, comStr);
157
+ pathData[i] = pathDataAbs[i]
158
+ }
159
+ }
160
+ }
161
+
131
162
  return pathData
132
163
  }
133
164
 
@@ -138,6 +169,9 @@ export function convertPathData(pathData, {
138
169
  */
139
170
 
140
171
  export function optimizeArcPathData(pathData = []) {
172
+
173
+ let remove =[]
174
+
141
175
  pathData.forEach((com, i) => {
142
176
  let { type, values } = com;
143
177
  if (type === 'A') {
@@ -149,6 +183,12 @@ export function optimizeArcPathData(pathData = []) {
149
183
  //largeArc=true
150
184
  //let pMid = {x: Math.abs(x-x0), y:Math.abs(y-y0)}
151
185
 
186
+ if(rx===0 || ry===0){
187
+ pathData[i]= null
188
+ remove.push(i)
189
+ //console.log('!!!');
190
+ }
191
+
152
192
  // rx and ry are large enough
153
193
  if (rx >= 1 && (x === x0 || y === y0)) {
154
194
  let diff = Math.abs(rx - ry) / rx;
@@ -171,6 +211,8 @@ export function optimizeArcPathData(pathData = []) {
171
211
  }
172
212
  }
173
213
  })
214
+
215
+ if(remove.length) pathData = pathData.filter(Boolean)
174
216
  return pathData;
175
217
  }
176
218
 
@@ -10,6 +10,7 @@ import { getPolygonArea } from "./geometry_area";
10
10
  import { getPolyBBox } from "./geometry_bbox";
11
11
  import { reversePathData } from "./pathData_reverse";
12
12
  import { getPathDataPolyPrecise } from "./pathData_toPolygon";
13
+ import { renderPoint, renderPoly } from "./visualize";
13
14
 
14
15
  export function fixPathDataDirections(pathDataArr = [], toClockwise = false) {
15
16
 
@@ -42,6 +43,7 @@ export function fixPathDataDirections(pathDataArr = [], toClockwise = false) {
42
43
  let ptMid = { x: bb.left + bb.width / 2, y: bb.top + bb.height / 2 }
43
44
  let inPoly = isPointInPolygon(ptMid, prev.pts, bb0)
44
45
 
46
+
45
47
  if (inPoly) {
46
48
  polys[j].inter += 1
47
49
  poly.includedIn.push(i)
@@ -61,9 +63,12 @@ export function fixPathDataDirections(pathDataArr = [], toClockwise = false) {
61
63
  if (!includedIn.length && cw && !toClockwise
62
64
  || !includedIn.length && !cw && toClockwise
63
65
  ) {
66
+ //console.log('reverse outer');
67
+
64
68
  pathDataArr[i].pathData = reversePathData(pathDataArr[i].pathData);
65
69
  polys[i].cw = polys[i].cw ? false : true
66
70
  cw = polys[i].cw
71
+
67
72
  }
68
73
 
69
74
  // reverse inner sub paths
@@ -72,6 +77,7 @@ export function fixPathDataDirections(pathDataArr = [], toClockwise = false) {
72
77
  let child = polys[ind];
73
78
 
74
79
  if (child.cw === cw) {
80
+ //console.log('reverse', child.cw, cw);
75
81
  pathDataArr[ind].pathData = reversePathData(pathDataArr[ind].pathData);
76
82
  polys[ind].cw = polys[ind].cw ? false : true
77
83
  }
@@ -3,17 +3,17 @@ export function pathDataFromPoly(pts, closed = true) {
3
3
  let pathData = []
4
4
  let subPath = []
5
5
 
6
-
7
6
  // complex polygon
8
7
  if (Array.isArray(pts[0])) {
9
- pts.forEach(sub => {
8
+ pts.forEach(sub => {
10
9
  subPath = [
11
10
  { type: 'M', values: [sub[0].x, sub[0].y] },
12
11
  ...sub.slice(1).map(pt => { return { type: 'L', values: [pt.x, pt.y] } })
13
12
  ];
13
+ if (closed) subPath.push({ type: 'Z', values: [] })
14
14
  pathData.push(...subPath)
15
15
  })
16
- }else{
16
+ } else {
17
17
  pathData = [
18
18
  { type: 'M', values: [pts[0].x, pts[0].y] },
19
19
  ...pts.slice(1).map(pt => { return { type: 'L', values: [pt.x, pt.y] } })