svg-path-simplify 0.1.3 → 0.2.2

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/README.md +10 -0
  2. package/dist/svg-path-simplify.esm.js +3905 -1533
  3. package/dist/svg-path-simplify.esm.min.js +13 -1
  4. package/dist/svg-path-simplify.js +3923 -1551
  5. package/dist/svg-path-simplify.min.js +13 -1
  6. package/dist/svg-path-simplify.min.js.gz +0 -0
  7. package/index.html +61 -31
  8. package/package.json +3 -5
  9. package/src/constants.js +3 -0
  10. package/src/index-node.js +0 -1
  11. package/src/index.js +26 -0
  12. package/src/pathData_simplify_cubic.js +74 -31
  13. package/src/pathData_simplify_cubicsToArcs.js +566 -0
  14. package/src/pathData_simplify_harmonize_cpts.js +170 -0
  15. package/src/pathData_simplify_revertToquadratics.js +21 -0
  16. package/src/pathSimplify-main.js +253 -86
  17. package/src/poly-fit-curve-schneider.js +570 -0
  18. package/src/simplify_poly_RDP.js +146 -0
  19. package/src/simplify_poly_radial_distance.js +100 -0
  20. package/src/svg_getViewbox.js +1 -1
  21. package/src/svgii/geometry.js +389 -63
  22. package/src/svgii/geometry_area.js +2 -1
  23. package/src/svgii/pathData_analyze.js +259 -212
  24. package/src/svgii/pathData_convert.js +91 -663
  25. package/src/svgii/pathData_fromPoly.js +12 -0
  26. package/src/svgii/pathData_parse.js +90 -89
  27. package/src/svgii/pathData_parse_els.js +3 -0
  28. package/src/svgii/pathData_parse_fontello.js +449 -0
  29. package/src/svgii/pathData_remove_collinear.js +44 -37
  30. package/src/svgii/pathData_reorder.js +2 -1
  31. package/src/svgii/pathData_simplify_redraw.js +343 -0
  32. package/src/svgii/pathData_simplify_refineCorners.js +18 -9
  33. package/src/svgii/pathData_simplify_refineExtremes.js +19 -78
  34. package/src/svgii/pathData_split.js +42 -45
  35. package/src/svgii/pathData_toPolygon.js +130 -4
  36. package/src/svgii/poly_analyze.js +470 -14
  37. package/src/svgii/poly_to_pathdata.js +224 -19
  38. package/src/svgii/rounding.js +55 -112
  39. package/src/svgii/svg_cleanup.js +13 -1
  40. package/src/svgii/visualize.js +8 -3
  41. package/{debug.cjs → tests/debug.cjs} +3 -0
  42. /package/{test.js → tests/test.js} +0 -0
  43. /package/{testSVG.js → tests/testSVG.js} +0 -0
@@ -0,0 +1,100 @@
1
+
2
+ /**
3
+ * radialDistance simplification
4
+ * sloppy but fast
5
+ */
6
+
7
+ import { getDistManhattan, getSquareDistance, reducePoints } from "./svgii/geometry";
8
+ import { getPolyBBox } from "./svgii/geometry_bbox";
9
+ import { renderPoint } from "./svgii/visualize";
10
+
11
+ export function simplifyRD(pts, {
12
+ quality = 0.9,
13
+ width = 0,
14
+ height = 0,
15
+ absolute = false,
16
+ // use square or manhattan distances
17
+ manhattan = false,
18
+ exclude = []
19
+ } = {}) {
20
+
21
+ /**
22
+ * switch between absolute or
23
+ * quality based relative thresholds
24
+ */
25
+
26
+ if (typeof quality === 'string') {
27
+ let value = parseFloat(quality);
28
+ absolute = true;
29
+ quality = value;
30
+ }
31
+
32
+ // nothing to do - exit
33
+ if (pts.length < 4 || (!absolute && quality) >= 1) return pts;
34
+
35
+
36
+ // convert quality to squaredistance tolerance
37
+ let tolerance = quality;
38
+
39
+ if (!absolute) {
40
+
41
+ // quality to tolerance
42
+ tolerance = 1 - quality;
43
+
44
+ /**
45
+ * approximate dimensions
46
+ * adjust tolerance for
47
+ * very small polygons e.g geodata
48
+ */
49
+
50
+ if (!width && !height) {
51
+ let polyS = reducePoints(pts, 12);
52
+ ({ width, height } = getPolyBBox(polyS));
53
+ }
54
+
55
+ if (!manhattan) {
56
+ // average side lengths
57
+ let dimAvg = (width + height) / 2;
58
+ let scale = dimAvg / 25;
59
+ tolerance = (tolerance * (scale)) ** 2
60
+ } else {
61
+ // use manhattan
62
+ tolerance = (width + height) * 0.05 * (1 - quality)
63
+ }
64
+
65
+ }
66
+
67
+ let p0 = pts[0];
68
+ let pt;
69
+
70
+ // new simplified point array
71
+ let ptsSmp = [p0];
72
+ let l = pts.length
73
+ let lenExclude = exclude.length;
74
+ let dist = 0
75
+
76
+ for (let i = 1; i < l; i++) {
77
+ pt = pts[i];
78
+
79
+ // skip
80
+ dist = manhattan ? getDistManhattan(p0, pt) : getSquareDistance(p0, pt);
81
+
82
+ if ( dist < tolerance && (!lenExclude || !exclude.includes(i)) ) {
83
+ p0 = pt;
84
+ continue
85
+ }
86
+
87
+ ptsSmp.push(pt);
88
+ p0 = pt;
89
+
90
+ }
91
+
92
+ // add last point - if not coinciding with first point
93
+ if (p0.x !== pt.x && p0.y !== pt.y) {
94
+ ptsSmp.push(pt);
95
+ }
96
+
97
+ //console.log('ptsSmp', ptsSmp, l);
98
+ return ptsSmp;
99
+
100
+ }
@@ -7,7 +7,7 @@
7
7
  export function getViewBox(svg = null, decimals = -1) {
8
8
 
9
9
  const getUnit=(val)=>{
10
- return val && isNaN(val) ? val.match(/[^\d]+/g)[0] : '';
10
+ return val && isNaN(val) ? val.match(/[^\d|.]+/g)[0] : '';
11
11
  }
12
12
 
13
13
  // browser default
@@ -3,6 +3,9 @@ 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 } from "../constants";
7
+ import { renderPoint } from "./visualize";
8
+
6
9
  export const {
7
10
  abs, acos, asin, atan, atan2, ceil, cos, exp, floor,
8
11
  log, max, min, pow, random, round, sin, sqrt, tan, PI
@@ -18,6 +21,47 @@ export function getAngle(p1, p2, normalize = false) {
18
21
  }
19
22
 
20
23
 
24
+ export function getDeltaAngle2(centerPoint, startPoint, endPoint, largeArc = false) {
25
+
26
+ const normalizeAngle = (angle) => {
27
+ let normalized = angle % (2 * Math.PI);
28
+
29
+ if (normalized > Math.PI) {
30
+ normalized -= 2 * Math.PI;
31
+ } else if (normalized <= -Math.PI) {
32
+ normalized += 2 * Math.PI;
33
+ }
34
+ return normalized;
35
+ }
36
+
37
+ let startAngle = getAngle(centerPoint, startPoint, false)
38
+
39
+ let endAngle = getAngle(centerPoint, endPoint, false)
40
+
41
+
42
+ // Calculate raw delta angle (difference)
43
+ let deltaAngle = endAngle - startAngle;
44
+
45
+ // Normalize the delta angle to range (-π, π]
46
+ //deltaAngle = normalizeAngle(deltaAngle);
47
+
48
+ //if (largeArc) deltaAngle = Math.PI * 2 - Math.abs(deltaAngle);
49
+
50
+ let phi = 180 / Math.PI
51
+ let startAngleDeg = startAngle * phi
52
+ let endAngleDeg = endAngle * phi
53
+ let deltaAngleDeg = deltaAngle * phi
54
+
55
+ return {
56
+ startAngle, endAngle, deltaAngle, startAngleDeg,
57
+ endAngleDeg,
58
+ deltaAngleDeg
59
+ };
60
+
61
+ }
62
+
63
+
64
+
21
65
  export function getDeltaAngle(centerPoint, startPoint, endPoint, largeArc = false) {
22
66
 
23
67
  const normalizeAngle = (angle) => {
@@ -47,7 +91,7 @@ export function getDeltaAngle(centerPoint, startPoint, endPoint, largeArc = fals
47
91
  // Normalize the delta angle to range (-π, π]
48
92
  deltaAngle = normalizeAngle(deltaAngle);
49
93
 
50
- if (largeArc) deltaAngle = Math.PI*2 - Math.abs(deltaAngle);
94
+ if (largeArc) deltaAngle = Math.PI * 2 - Math.abs(deltaAngle);
51
95
 
52
96
  let phi = 180 / Math.PI
53
97
  let startAngleDeg = startAngle * phi
@@ -127,25 +171,7 @@ export function checkLineIntersection(p1 = null, p2 = null, p3 = null, p4 = null
127
171
 
128
172
 
129
173
 
130
- /**
131
- * get distance between 2 points
132
- * pythagorean theorem
133
- */
134
- export function getDistance(p1, p2) {
135
- return sqrt(
136
- (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y)
137
- );
138
- }
139
174
 
140
- export function getSquareDistance(p1, p2) {
141
- return (p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2
142
- }
143
-
144
- export function lineLength(p1, p2) {
145
- return sqrt(
146
- (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y)
147
- );
148
- }
149
175
 
150
176
 
151
177
  /**
@@ -169,7 +195,7 @@ export function interpolate(p1, p2, t, getTangent = false) {
169
195
  }
170
196
 
171
197
 
172
- export function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false) {
198
+ export function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, returnArray = false) {
173
199
 
174
200
  const getPointAtBezierT = (pts, t, getTangent = false) => {
175
201
 
@@ -261,6 +287,15 @@ export function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false) {
261
287
 
262
288
  }
263
289
 
290
+
291
+ // normalize if input was array not pt object
292
+ if (Array.isArray(pts[0])) {
293
+ pts = pts.map(pt => { return { x: pt[0], y: pt[1] } })
294
+ // also output array if not explicitely defined
295
+ returnArray = true
296
+ }
297
+
298
+
264
299
  let pt;
265
300
  if (pts.length > 2) {
266
301
  pt = getPointAtBezierT(pts, t, getTangent);
@@ -273,7 +308,7 @@ export function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false) {
273
308
  // normalize negative angles
274
309
  if (getTangent && pt.angle < 0) pt.angle += PI * 2
275
310
 
276
- return pt
311
+ return returnArray ? [pt.x, pt.y] : pt
277
312
  }
278
313
 
279
314
 
@@ -392,8 +427,8 @@ export function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, swe
392
427
  let phi = rx === ry ? 0 : (xAxisRotation * PI) / 180;
393
428
  let cx, cy
394
429
 
395
- let s_phi = !phi ? 0 : sin(phi);
396
- let c_phi = !phi ? 1 : cos(phi);
430
+ let s_phi = !phi ? 0 : Math.sin(phi);
431
+ let c_phi = !phi ? 1 : Math.cos(phi);
397
432
 
398
433
  let hd_x = (x1 - x2) / 2;
399
434
  let hd_y = (y1 - y2) / 2;
@@ -408,8 +443,8 @@ export function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, swe
408
443
  // Step 3: Ensure radii are large enough
409
444
  let lambda = (x1_ * x1_) / (rx * rx) + (y1_ * y1_) / (ry * ry);
410
445
  if (lambda > 1) {
411
- rx = rx * sqrt(lambda);
412
- ry = ry * sqrt(lambda);
446
+ rx = rx * Math.sqrt(lambda);
447
+ ry = ry * Math.sqrt(lambda);
413
448
 
414
449
  // save real rx/ry
415
450
  arcData.rx = rx;
@@ -424,7 +459,7 @@ export function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, swe
424
459
  //console.log('error:', rx, ry, rxy1_, ryx1_);
425
460
  throw Error("start point can not be same as end point");
426
461
  }
427
- let coe = sqrt(abs((rxry * rxry - sum_of_sq) / sum_of_sq));
462
+ let coe = Math.sqrt(Math.abs((rxry * rxry - sum_of_sq) / sum_of_sq));
428
463
  if (largeArc == sweep) {
429
464
  coe = -coe;
430
465
  }
@@ -596,33 +631,92 @@ export function getTangentAngle(rx, ry, parametricAngle) {
596
631
  return tangentAngle;
597
632
  }
598
633
 
599
- export function bezierhasExtreme(p0, cpts = [], angleThreshold = 0.05) {
600
- let isCubic = cpts.length === 3 ? true : false;
601
- let cp1 = cpts[0] || null
602
- let cp2 = isCubic ? cpts[1] : null;
603
- let p = isCubic ? cpts[2] : cpts[1];
604
- let PIquarter = Math.PI * 0.5;
605
634
 
606
- let extCp1 = false,
607
- extCp2 = false;
635
+ export function bezierhasExtreme(p0 = null, cpts = []) {
608
636
 
609
- //console.log('ang', cp1);
610
- let ang1 = cp1 ? getAngle(p, cp1, true) : null;
637
+ if (!p0) {
638
+ p0 = cpts[0]
639
+ cpts = cpts.slice(1, cpts.length)
640
+ }
611
641
 
612
- extCp1 = Math.abs((ang1 % PIquarter)) < angleThreshold || Math.abs((ang1 % PIquarter) - PIquarter) < angleThreshold;
642
+ let l = cpts.length;
643
+ let p = cpts[l - 1];
644
+ let cp1 = cpts[0]
645
+ let cp2 = l === 3 ? cpts[1] : cp1
613
646
 
614
- if (isCubic) {
615
- let ang2 = cp2 ? getAngle(cp2, p, true) : 0;
616
- extCp2 = Math.abs((ang2 % PIquarter)) <= angleThreshold ||
617
- Math.abs((ang2 % PIquarter) - PIquarter) <= angleThreshold;
647
+ // get bounding box
648
+ /**
649
+ * if control points are within
650
+ * bounding box of start and end point
651
+ * we cant't have extremes
652
+ */
653
+ let top = Math.min(p0.y, p.y)
654
+ let left = Math.min(p0.x, p.x)
655
+ let right = Math.max(p0.x, p.x)
656
+ let bottom = Math.max(p0.y, p.y)
657
+
658
+ // within bbox - can't have extremes
659
+ if (
660
+ cp1.y >= top && cp1.y <= bottom &&
661
+ cp2.y >= top && cp2.y <= bottom &&
662
+ cp1.x >= left && cp1.x <= right &&
663
+ cp2.x >= left && cp2.x <= right
664
+ ) {
665
+ return false
618
666
  }
619
- return (extCp1 || extCp2)
667
+
668
+ return true
669
+
620
670
  }
621
671
 
622
672
 
673
+ export function getSemiExtremesT(p0, cp1, cp2, p, angleRad = Math.PI / 2) {
674
+ // Rotate all points by -angleRad
675
+ let cos = Math.cos(-angleRad);
676
+ let sin = Math.sin(-angleRad);
677
+
678
+ const rotatePoint = (point) => ({
679
+ x: point.x * cos - point.y * sin,
680
+ y: point.x * sin + point.y * cos
681
+ });
682
+
683
+ let p0Rot = rotatePoint(p0);
684
+ let cp1Rot = rotatePoint(cp1);
685
+ let cp2Rot = rotatePoint(cp2);
686
+ let pRot = rotatePoint(p);
687
+
688
+ // Now find x-extrema in rotated coordinate system
689
+ // These correspond to points where original tangent angle = angleRad
623
690
 
624
- export function getBezierExtremeT(pts) {
625
- let tArr = pts.length === 4 ? cubicBezierExtremeT(pts[0], pts[1], pts[2], pts[3]) : quadraticBezierExtremeT(pts[0], pts[1], pts[2]);
691
+ let a = -3 * p0Rot.x + 9 * cp1Rot.x - 9 * cp2Rot.x + 3 * pRot.x;
692
+ let b = 6 * p0Rot.x - 12 * cp1Rot.x + 6 * cp2Rot.x;
693
+ let c = 3 * cp1Rot.x - 3 * p0Rot.x;
694
+
695
+ let extremes = [];
696
+
697
+ if (Math.abs(a) < 1e-12) {
698
+ if (Math.abs(b) > 1e-12) {
699
+ let t = -c / b;
700
+ if (t > 0 && t < 1) extremes.push(t);
701
+ }
702
+ return extremes;
703
+ }
704
+
705
+ let discriminant = b * b - 4 * a * c;
706
+ if (discriminant >= 0) {
707
+ let sqrtDisc = Math.sqrt(discriminant);
708
+ let t1 = (-b + sqrtDisc) / (2 * a);
709
+ let t2 = (-b - sqrtDisc) / (2 * a);
710
+
711
+ if (t1 > 0 && t1 < 1) extremes.push(t1);
712
+ if (t2 > 0 && t2 < 1) extremes.push(t2);
713
+ }
714
+
715
+ return extremes;
716
+ }
717
+
718
+ export function getBezierExtremeT(pts, { addExtremes = true, addSemiExtremes = false } = {}) {
719
+ let tArr = pts.length === 4 ? cubicBezierExtremeT(pts[0], pts[1], pts[2], pts[3], { addExtremes, addSemiExtremes }) : quadraticBezierExtremeT(pts[0], pts[1], pts[2], { addExtremes, addSemiExtremes });
626
720
  return tArr;
627
721
  }
628
722
 
@@ -737,10 +831,179 @@ export function getArcExtemes(p0, values) {
737
831
 
738
832
 
739
833
 
834
+ export function getTatAngles(cpts = [], angles = []) {
835
+
836
+ let l = cpts.length;
837
+ let isCubic = l === 4;
838
+ //console.log(angles, isCubic);
839
+
840
+ if (!angles.length) angles = [0];
841
+
842
+ let anglesL = angles.length;
843
+ let tArr = []
844
+
845
+ for (let i = 0; i < anglesL; i++) {
846
+
847
+ let ang = angles[i];
848
+ let cptsN = ang ? [] : cpts.slice(0)
849
+
850
+ // rotate cpts
851
+ if (ang) {
852
+ for (let j = 0; j < l; j++) {
853
+ let pt = cpts[j]
854
+ cptsN.push(rotatePoint(pt, 0, 0, ang))
855
+ }
856
+ }
857
+
858
+ // get t arr
859
+ let tVals = isCubic ? getTatCubicExtreme(...cptsN) : getTatQuadraticExtreme(...cptsN)
860
+ tArr.push(...tVals)
861
+
862
+ }
863
+
864
+ // deduplicate and sort
865
+ tArr = [... new Set(tArr)].sort()
866
+
867
+ return tArr;
868
+
869
+ }
870
+
871
+
872
+
873
+ // t at cubic bezier extreme
874
+ export function getTatCubicExtreme(p0, cp1, cp2, p) {
875
+
876
+ /**
877
+ * if control points are within
878
+ * bounding box of start and end point
879
+ * we cant't have extremes
880
+ */
881
+ if (!bezierhasExtreme(p0, [cp1, cp2, p])) {
882
+ //console.log('no extreme');
883
+ return []
884
+ }
885
+
886
+ let [x0, y0, x1, y1, x2, y2, x3, y3] = [p0.x, p0.y, cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y];
887
+ let tArr = [], a, b, c, t, t1, t2, b2ac, sqrt_b2ac;
888
+ let e = 1e-8
889
+
890
+ for (let i = 0; i < 2; ++i) {
891
+
892
+ if (i == 0) {
893
+ b = 6 * x0 - 12 * x1 + 6 * x2;
894
+ a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
895
+ c = 3 * x1 - 3 * x0;
896
+ } else {
897
+ b = 6 * y0 - 12 * y1 + 6 * y2;
898
+ a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
899
+ c = 3 * y1 - 3 * y0;
900
+ }
901
+ if (Math.abs(a) < e) {
902
+ if (Math.abs(b) < e) {
903
+ continue;
904
+ }
905
+ t = -c / b;
906
+ if (t > 0 && t < 1) {
907
+ tArr.push(t);
908
+ }
909
+ continue;
910
+ }
911
+ b2ac = b * b - 4 * c * a;
912
+ if (b2ac < 0) {
913
+ if (Math.abs(b2ac) < e) {
914
+ t = -b / (2 * a);
915
+ if (t > 0 && t < 1) {
916
+ tArr.push(t);
917
+ }
918
+ }
919
+ continue;
920
+ }
921
+ sqrt_b2ac = Math.sqrt(b2ac);
922
+ t1 = (-b + sqrt_b2ac) / (2 * a);
923
+ if (t1 > 0 && t1 < 1) {
924
+ tArr.push(t1);
925
+ }
926
+ t2 = (-b - sqrt_b2ac) / (2 * a);
927
+ if (t2 > 0 && t2 < 1) {
928
+ tArr.push(t2);
929
+ }
930
+ }
931
+
932
+ let j = tArr.length;
933
+ while (j--) {
934
+ t = tArr[j];
935
+ }
936
+
937
+ return [...new Set(tArr)].sort();
938
+ }
939
+
940
+
941
+
942
+
943
+ //For quadratic bezier.
944
+ export function getTatQuadraticExtreme(p0, cp1, p) {
945
+
946
+ /**
947
+ * if control points are within
948
+ * bounding box of start and end point
949
+ * we cant't have extremes
950
+ */
951
+ if (!bezierhasExtreme(p0, [cp1, p])) {
952
+ //console.log('no extreme');
953
+ return []
954
+ }
955
+
956
+ let a, b, c, t;
957
+ let [x0, y0, x1, y1, x2, y2] = [p0.x, p0.y, cp1.x, cp1.y, p.x, p.y];
958
+ let tArr = [];
959
+
960
+ for (let i = 0; i < 2; ++i) {
961
+ a = i == 0 ? x0 - 2 * x1 + x2 : y0 - 2 * y1 + y2;
962
+ b = i == 0 ? -2 * x0 + 2 * x1 : -2 * y0 + 2 * y1;
963
+ c = i == 0 ? x0 : y0;
964
+ if (Math.abs(a) > 1e-12) {
965
+ t = -b / (2 * a);
966
+ if (t > 0 && t < 1) {
967
+ tArr.push(t);
968
+ }
969
+ }
970
+ }
971
+
972
+ return [...new Set(tArr)].sort();
973
+ }
974
+
975
+
976
+
740
977
  // cubic bezier.
741
- export function cubicBezierExtremeT(p0, cp1, cp2, p) {
978
+ export function cubicBezierExtremeT(p0, cp1, cp2, p,
979
+ { addExtremes = true, addSemiExtremes = false } = {}) {
980
+
981
+
982
+ // rotate cpts for semi extremes
983
+ const rotatePoint = (pt) => {
984
+
985
+ const angleRad = Math.PI / 4
986
+ const cos = Math.cos(angleRad);
987
+ const sin = Math.sin(angleRad);
988
+
989
+ return {
990
+ x: pt.x * cos - pt.y * sin,
991
+ y: pt.x * sin + pt.y * cos
992
+ }
993
+ };
994
+
995
+
996
+ if (addSemiExtremes) {
997
+ p0 = rotatePoint(p0)
998
+ cp1 = rotatePoint(cp1)
999
+ cp2 = rotatePoint(cp2)
1000
+ p = rotatePoint(p)
1001
+ }
1002
+
1003
+
742
1004
  let [x0, y0, x1, y1, x2, y2, x3, y3] = [p0.x, p0.y, cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y];
743
1005
 
1006
+
744
1007
  /**
745
1008
  * if control points are within
746
1009
  * bounding box of start and end point
@@ -760,8 +1023,8 @@ export function cubicBezierExtremeT(p0, cp1, cp2, p) {
760
1023
  return []
761
1024
  }
762
1025
 
763
- let tArr = [],
764
- a, b, c, t, t1, t2, b2ac, sqrt_b2ac;
1026
+ let tArr = [], a, b, c, t, t1, t2, b2ac, sqrt_b2ac;
1027
+
765
1028
  for (let i = 0; i < 2; ++i) {
766
1029
  if (i == 0) {
767
1030
  b = 6 * x0 - 12 * x1 + 6 * x2;
@@ -772,8 +1035,8 @@ export function cubicBezierExtremeT(p0, cp1, cp2, p) {
772
1035
  a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
773
1036
  c = 3 * y1 - 3 * y0;
774
1037
  }
775
- if (Math.abs(a) < 1e-12) {
776
- if (Math.abs(b) < 1e-12) {
1038
+ if (Math.abs(a) < 1e-8) {
1039
+ if (Math.abs(b) < 1e-8) {
777
1040
  continue;
778
1041
  }
779
1042
  t = -c / b;
@@ -784,7 +1047,7 @@ export function cubicBezierExtremeT(p0, cp1, cp2, p) {
784
1047
  }
785
1048
  b2ac = b * b - 4 * c * a;
786
1049
  if (b2ac < 0) {
787
- if (Math.abs(b2ac) < 1e-12) {
1050
+ if (Math.abs(b2ac) < 1e-8) {
788
1051
  t = -b / (2 * a);
789
1052
  if (0 < t && t < 1) {
790
1053
  tArr.push(t);
@@ -807,13 +1070,44 @@ export function cubicBezierExtremeT(p0, cp1, cp2, p) {
807
1070
  while (j--) {
808
1071
  t = tArr[j];
809
1072
  }
1073
+
1074
+ //console.log('addSemiExtremes', addSemiExtremes, addExtremes, tArr);
1075
+
810
1076
  return tArr;
811
1077
  }
812
1078
 
1079
+ export function isMultipleOf45(angleRad, epsilon = 0.001) {
1080
+ let rad90 = Math.PI / 2;
1081
+ let rad45 = rad90 / 2;
1082
+ let isRightAngle = Math.abs(angleRad / rad90 - Math.round(angleRad / rad90)) < epsilon
1083
+ return !isRightAngle ? Math.abs(angleRad / rad45 - Math.round(angleRad / rad45)) < epsilon : false;
1084
+ }
813
1085
 
814
1086
 
815
1087
  //For quadratic bezier.
816
- export function quadraticBezierExtremeT(p0, cp1, p) {
1088
+ export function quadraticBezierExtremeT(p0, cp1, p, { addExtremes = true, addSemiExtremes = false } = {}) {
1089
+
1090
+
1091
+ // rotate cpts for semi extremes
1092
+ const rotatePoint = (pt) => {
1093
+ const angleRad = -Math.PI / 4
1094
+ const cos = Math.cos(angleRad);
1095
+ const sin = Math.sin(angleRad);
1096
+
1097
+ return {
1098
+ x: pt.x * cos - pt.y * sin,
1099
+ y: pt.x * sin + pt.y * cos
1100
+ }
1101
+ };
1102
+
1103
+
1104
+ if (addSemiExtremes) {
1105
+ p0 = rotatePoint(p0)
1106
+ cp1 = rotatePoint(cp1)
1107
+ p = rotatePoint(p)
1108
+ }
1109
+
1110
+
817
1111
  /**
818
1112
  * if control points are within
819
1113
  * bounding box of start and end point
@@ -930,24 +1224,56 @@ export function intersectLines(p1, p2, p3, p4) {
930
1224
 
931
1225
 
932
1226
  /**
933
- * sloppy distance calculation
934
- * based on x/y differences
1227
+ * get distance between 2 points
1228
+ * pythagorean theorem
935
1229
  */
936
- export function getDistAv(pt1, pt2) {
1230
+ export function getDistance(p1, p2, isArray = false) {
1231
+ //if(Array.isArray(p1)) isArray = true;
1232
+ //console.log(p1, p2);
1233
+ let dx = isArray ? p2[0] - p1[0] : (p2.x - p1.x);
1234
+ let dy = isArray ? p2[1] - p1[1] : (p2.y - p1.y);
1235
+
1236
+ //console.log('dx', dx, dy, p1, p2);
1237
+ return sqrt(dx * dx + dy * dy);
1238
+ }
937
1239
 
938
- let diffX = Math.abs(pt2.x - pt1.x);
939
- let diffY = Math.abs(pt2.y - pt1.y);
940
- let diff = (diffX + diffY) / 2;
1240
+ // just an alias
1241
+ export function lineLength(p1, p2) {
1242
+ return getDistance(p1, p2)
1243
+ }
941
1244
 
942
- /*
943
- let diffX = pt2.x - pt1.x;
944
- let diffY = pt2.y - pt1.y;
945
- let diff = Math.abs(diffX + diffY) / 2;
946
- */
947
1245
 
948
- return diff;
1246
+ export function getSquareDistance(p1, p2) {
1247
+ let dx = (p2.x - p1.x);
1248
+ let dy = (p2.y - p1.y);
1249
+ return dx * dx + dy * dy
1250
+ }
1251
+
1252
+
1253
+
1254
+ /**
1255
+ * get Manhattan/Cab distance
1256
+ * based on x/y deltas
1257
+ * sloppy but fast
1258
+ */
1259
+ export function getDistManhattan(pt1, pt2) {
1260
+ let dx = Math.abs(pt2.x - pt1.x);
1261
+ let dy = Math.abs(pt2.y - pt1.y);
1262
+ return dx + dy;
949
1263
  }
950
1264
 
1265
+ /**
1266
+ * sloppy distance calculation
1267
+ * based on "half Manhattan/Cab" distance
1268
+ */
1269
+
1270
+ export function getDistAv(pt1, pt2) {
1271
+ let dx = Math.abs(pt2.x - pt1.x);
1272
+ let dy = Math.abs(pt2.y - pt1.y);
1273
+ return (dx + dy) * 0.5;
1274
+ }
1275
+
1276
+
951
1277
  /**
952
1278
  * get command dimensions
953
1279
  * for threshold value
@@ -253,7 +253,8 @@ export function getBezierArea(pts, absolute=false) {
253
253
 
254
254
  export function getPolygonArea(points, absolute=false) {
255
255
  let area = 0;
256
- for (let i = 0, len = points.length; len && i < len; i++) {
256
+ let l = points.length;
257
+ for (let i = 0; l && i < l; i++) {
257
258
  let addX = points[i].x;
258
259
  let addY = points[i === points.length - 1 ? 0 : i + 1].y;
259
260
  let subX = points[i === points.length - 1 ? 0 : i + 1].x;