svg-path-simplify 0.0.8 → 0.0.9

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 (34) hide show
  1. package/README.md +25 -5
  2. package/dist/svg-path-simplify.esm.js +576 -494
  3. package/dist/svg-path-simplify.esm.min.js +1 -1
  4. package/dist/svg-path-simplify.js +576 -494
  5. package/dist/svg-path-simplify.min.js +1 -1
  6. package/dist/svg-path-simplify.node.js +576 -494
  7. package/dist/svg-path-simplify.node.min.js +1 -1
  8. package/index.html +86 -29
  9. package/package.json +1 -1
  10. package/src/detect_input.js +17 -10
  11. package/src/index.js +3 -0
  12. package/src/pathData_simplify_cubic.js +113 -106
  13. package/src/pathData_simplify_cubic_extrapolate.js +25 -11
  14. package/src/pathSimplify-main.js +89 -182
  15. package/src/svgii/geometry_flatness.js +29 -36
  16. package/src/svgii/pathData_analyze.js +4 -0
  17. package/src/svgii/pathData_convert.js +26 -17
  18. package/src/svgii/pathData_interpolate.js +65 -0
  19. package/src/svgii/pathData_parse.js +25 -9
  20. package/src/svgii/pathData_parse_els.js +18 -12
  21. package/src/svgii/pathData_remove_collinear.js +31 -28
  22. package/src/svgii/pathData_remove_orphaned.js +5 -4
  23. package/src/svgii/pathData_remove_zerolength.js +8 -4
  24. package/src/svgii/pathData_reorder.js +6 -2
  25. package/src/svgii/pathData_simplify_refineCorners.js +160 -0
  26. package/src/svgii/{simplify_refineExtremes.js → pathData_simplify_refineExtremes.js} +78 -43
  27. package/src/svgii/pathData_split.js +42 -15
  28. package/src/svgii/pathData_stringify.js +3 -12
  29. package/src/svgii/rounding.js +16 -14
  30. package/src/svgii/svg_cleanup.js +1 -1
  31. package/src/pathData_simplify_cubic_arr.js +0 -50
  32. package/src/svgii/simplify.js +0 -248
  33. package/src/svgii/simplify_bezier.js +0 -470
  34. package/src/svgii/simplify_linetos.js +0 -93
@@ -47,23 +47,29 @@ function detectInputType(input) {
47
47
  if (input instanceof ArrayBuffer) return "buffer";
48
48
  if (input instanceof Blob) return "blob";
49
49
  */
50
- if (Array.isArray(input)) return "array";
50
+ if (Array.isArray(input)) {
51
+ if (input[0]?.type && input[0]?.values
52
+ ) {
53
+ return "pathData";
54
+ }
55
+
56
+ return "array";
57
+ }
51
58
 
52
59
  if (typeof input === "string") {
53
60
  input = input.trim();
54
61
  let isSVG = input.includes('<svg') && input.includes('</svg');
55
62
  let isPathData = input.startsWith('M') || input.startsWith('m');
56
- let isPolyString = !isNaN(input.substring(0, 1)) && !isNaN(input.substring(input.length-1, input.length));
63
+ let isPolyString = !isNaN(input.substring(0, 1)) && !isNaN(input.substring(input.length - 1, input.length));
57
64
 
58
-
59
- if(isSVG) {
60
- type='svgMarkup';
65
+ if (isSVG) {
66
+ type = 'svgMarkup';
61
67
  }
62
- else if(isPathData) {
63
- type='pathDataString';
68
+ else if (isPathData) {
69
+ type = 'pathDataString';
64
70
  }
65
- else if(isPolyString) {
66
- type='polyString';
71
+ else if (isPolyString) {
72
+ type = 'polyString';
67
73
  }
68
74
 
69
75
  else {
@@ -772,27 +778,23 @@ function getDistAv(pt1, pt2) {
772
778
  * split compound paths into
773
779
  * sub path data array
774
780
  */
775
- function splitSubpaths(pathData) {
776
781
 
782
+ function splitSubpaths(pathData) {
777
783
  let subPathArr = [];
784
+ let current = [pathData[0]];
785
+ let l = pathData.length;
778
786
 
779
-
780
- try{
781
- let subPathIndices = pathData.map((com, i) => (com.type.toLowerCase() === 'm' ? i : -1)).filter(i => i !== -1);
787
+ for (let i = 1; i < l; i++) {
788
+ let com = pathData[i];
782
789
 
783
- }catch{
784
- console.log('catch', pathData);
790
+ if (com.type === 'M' || com.type === 'm') {
791
+ subPathArr.push(current);
792
+ current = [];
793
+ }
794
+ current.push(com);
785
795
  }
786
796
 
787
- let subPathIndices = pathData.map((com, i) => (com.type.toLowerCase() === 'm' ? i : -1)).filter(i => i !== -1);
788
-
789
- // no compound path
790
- if (subPathIndices.length === 1) {
791
- return [pathData]
792
- }
793
- subPathIndices.forEach((index, i) => {
794
- subPathArr.push(pathData.slice(index, subPathIndices[i + 1]));
795
- });
797
+ if (current.length) subPathArr.push(current);
796
798
 
797
799
  return subPathArr;
798
800
  }
@@ -1476,13 +1478,6 @@ function pathDataToD(pathData, optimize = 0) {
1476
1478
  let beautify = optimize > 1;
1477
1479
  let minify = beautify || optimize ? false : true;
1478
1480
 
1479
- // Convert first "M" to "m" if followed by "l" (when minified)
1480
- /*
1481
- if (pathData[1].type === "l" && minify) {
1482
- pathData[0].type = "m";
1483
- }
1484
- */
1485
-
1486
1481
  let d = '';
1487
1482
  let separator_command = beautify ? `\n` : (minify ? '' : ' ');
1488
1483
  let separator_type = !minify ? ' ' : '';
@@ -1504,13 +1499,11 @@ function pathDataToD(pathData, optimize = 0) {
1504
1499
  }
1505
1500
 
1506
1501
  // Omit type for repeated commands
1507
- type = (com0.type === com.type && com.type.toLowerCase() !== 'm' && minify)
1502
+ type = (minify && com0.type === com.type && com.type.toLowerCase() !== 'm' )
1508
1503
  ? " "
1509
- : (
1510
- (com0.type === "M" && com.type === "L")
1511
- ) && minify
1504
+ : (minify && com0.type === "M" && com.type === "L"
1512
1505
  ? " "
1513
- : com.type;
1506
+ : com.type);
1514
1507
 
1515
1508
  // concatenate subsequent floating point values
1516
1509
  if (minify) {
@@ -1660,6 +1653,7 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, debug = f
1660
1653
  cp1: Q1,
1661
1654
  cp2: Q2,
1662
1655
  p: Q3,
1656
+ t0
1663
1657
  };
1664
1658
 
1665
1659
  if (reverse) {
@@ -1668,6 +1662,7 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, debug = f
1668
1662
  cp1: Q2,
1669
1663
  cp2: Q1,
1670
1664
  p: Q0,
1665
+ t0
1671
1666
  };
1672
1667
  }
1673
1668
 
@@ -1679,8 +1674,8 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, debug = f
1679
1674
  let ptI_1 = checkLineIntersection(ptM, seg1_cp2, result.p0, ptI, false);
1680
1675
  let ptI_2 = checkLineIntersection(ptM, seg1_cp2, result.p, ptI, false);
1681
1676
 
1682
- let cp1_2 = interpolate(result.p0, ptI_1, 1.333);
1683
- let cp2_2 = interpolate(result.p, ptI_2, 1.333);
1677
+ let cp1_2 = interpolate(result.p0, ptI_1, 1.333 );
1678
+ let cp2_2 = interpolate(result.p, ptI_2, 1.333 );
1684
1679
 
1685
1680
  // test self intersections and exit
1686
1681
  let cp_intersection = checkLineIntersection(com1_o.p0, cp1_2, com2_o.p, cp2_2, true);
@@ -1712,14 +1707,24 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, debug = f
1712
1707
  // extrapolated starting point is not completely off
1713
1708
  if (dist5 < maxDist) {
1714
1709
 
1710
+ /*
1711
+ let tTotal = 1 + Math.abs(t0);
1712
+ let tSplit = reverse ? 1 + t0 : Math.abs(t0);
1713
+
1714
+ let pO = pointAtT([com2_o.p0, com2_o.cp1, com2_o.cp2, com2_o.p], t0);
1715
+ */
1716
+
1715
1717
  // split t to meet original mid segment start point
1716
1718
  let tSplit = reverse ? 1 + t0 : Math.abs(t0);
1717
1719
 
1720
+ let tTotal = 1 + Math.abs(t0);
1721
+ tSplit = reverse ? 1 + t0 : Math.abs(t0) / tTotal;
1722
+
1718
1723
  let ptSplit = pointAtT([result.p0, result.cp1, result.cp2, result.p], tSplit);
1719
1724
  let distSplit = getDistAv(ptSplit, com1.p);
1720
1725
 
1721
1726
  // not close enough - exit
1722
- if (distSplit > maxDist * tolerance) {
1727
+ if (distSplit > maxDist * tolerance ) {
1723
1728
 
1724
1729
  return commands;
1725
1730
  }
@@ -1758,21 +1763,122 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, debug = f
1758
1763
 
1759
1764
  }
1760
1765
 
1761
- function combineCubicPairs(com1, com2, extrapolateDominant = false, tolerance = 1) {
1766
+ function simplifyPathDataCubic(pathData, {
1767
+ keepExtremes = true,
1768
+ keepInflections = true,
1769
+ keepCorners = true,
1770
+ extrapolateDominant = true,
1771
+ tolerance = 1,
1772
+ } = {}) {
1773
+
1774
+ let pathDataN = [pathData[0]];
1775
+ let l = pathData.length;
1776
+
1777
+ for (let i = 2; l && i <= l; i++) {
1778
+ let com = pathData[i - 1];
1779
+ let comN = i < l ? pathData[i] : null;
1780
+ let typeN = comN?.type || null;
1781
+
1782
+ let isDirChange = com?.directionChange || null;
1783
+ let isDirChangeN = comN?.directionChange || null;
1784
+
1785
+ let { type, values, p0, p, cp1 = null, cp2 = null, extreme = false, corner = false, dimA = 0 } = com;
1786
+
1787
+ // next is also cubic
1788
+ if (type === 'C' && typeN === 'C') {
1789
+
1790
+ // cannot be combined as crossing extremes or corners
1791
+ if (
1792
+ (keepInflections && isDirChangeN) ||
1793
+ (keepCorners && corner) ||
1794
+ (!isDirChange && keepExtremes && extreme)
1795
+ ) {
1796
+
1797
+ pathDataN.push(com);
1798
+ }
1799
+
1800
+ // try simplification
1801
+ else {
1802
+
1803
+ let combined = combineCubicPairs(com, comN, {tolerance});
1804
+ let error = 0;
1805
+
1806
+ // combining successful! try next segment
1807
+ if (combined.length === 1) {
1808
+ com = combined[0];
1809
+ let offset = 1;
1810
+
1811
+ // add cumulative error to prevent distortions
1812
+ error += com.error;
1813
+
1814
+ // find next candidates
1815
+
1816
+ for (let n = i + 1; error < tolerance && n < l; n++) {
1817
+ let comN = pathData[n];
1818
+ if (comN.type !== 'C' ||
1819
+ (
1820
+ (keepInflections && comN.directionChange) ||
1821
+ (keepCorners && com.corner) ||
1822
+ (keepExtremes && com.extreme)
1823
+ )
1824
+ ) {
1825
+ break
1826
+ }
1827
+
1828
+ let combined = combineCubicPairs(com, comN, {tolerance});
1829
+ if (combined.length === 1) {
1830
+ // add cumulative error to prevent distortions
1831
+
1832
+ error += combined[0].error * 0.5;
1833
+
1834
+ offset++;
1835
+ }
1836
+ com = combined[0];
1837
+ }
1838
+
1839
+ pathDataN.push(com);
1840
+
1841
+ if (i < l) {
1842
+ i += offset;
1843
+ }
1844
+
1845
+ } else {
1846
+ pathDataN.push(com);
1847
+ }
1848
+ }
1849
+
1850
+ } // end of bezier command
1851
+
1852
+ // other commands
1853
+ else {
1854
+ pathDataN.push(com);
1855
+ }
1856
+
1857
+ } // end command loop
1858
+
1859
+ return pathDataN
1860
+ }
1861
+
1862
+ function combineCubicPairs(com1, com2, {
1863
+ tolerance = 1
1864
+ } = {}) {
1762
1865
 
1763
1866
  let commands = [com1, com2];
1867
+
1868
+ // assume 2 segments are result of a segment split
1764
1869
  let t = findSplitT(com1, com2);
1765
1870
 
1766
1871
  let distAv1 = getDistAv(com1.p0, com1.p);
1767
1872
  let distAv2 = getDistAv(com2.p0, com2.p);
1768
- let distMin = Math.min(distAv1, distAv2);
1873
+ let distMin = Math.max(0, Math.min(distAv1, distAv2));
1769
1874
 
1770
- let distScale = 0.05;
1875
+ let distScale = 0.06;
1771
1876
  let maxDist = distMin * distScale * tolerance;
1772
1877
 
1773
- let comS = getExtrapolatedCommand(com1, com2, t, t);
1878
+ // get hypothetical combined command
1879
+ let comS = getExtrapolatedCommand(com1, com2, t);
1774
1880
 
1775
- // test on path point against original
1881
+ // test new point-at-t against original mid segment starting point
1776
1882
  let pt = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t);
1777
1883
 
1778
1884
  let dist0 = getDistAv(com1.p, pt);
@@ -1783,12 +1889,6 @@ function combineCubicPairs(com1, com2, extrapolateDominant = false, tolerance =
1783
1889
  // collect error data
1784
1890
  let error = dist0;
1785
1891
 
1786
- /*
1787
- if (com2.directionChange) {
1788
-
1789
- }
1790
- */
1791
-
1792
1892
  if (close) {
1793
1893
 
1794
1894
  /**
@@ -1834,29 +1934,9 @@ function combineCubicPairs(com1, com2, extrapolateDominant = false, tolerance =
1834
1934
 
1835
1935
  } // end 1st try
1836
1936
 
1837
-
1838
-
1839
- // try extrapolated dominant curve
1840
-
1841
- // && !com1.extreme
1842
- if (extrapolateDominant && !success ) {
1843
-
1844
- let combinedEx = getCombinedByDominant(com1, com2, maxDist, tolerance);
1845
-
1846
- if(combinedEx.length===1){
1847
- success = true;
1848
- comS = combinedEx[0];
1849
- error = comS.error;
1850
-
1851
- }
1852
-
1853
-
1854
- }
1855
-
1856
1937
  // add meta
1857
1938
  if (success) {
1858
1939
 
1859
-
1860
1940
  // correct to exact start and end points
1861
1941
  comS.p0 = com1.p0;
1862
1942
  comS.p = com2.p;
@@ -1879,26 +1959,23 @@ function combineCubicPairs(com1, com2, extrapolateDominant = false, tolerance =
1879
1959
  return commands;
1880
1960
  }
1881
1961
 
1882
- function getExtrapolatedCommand(com1, com2, t1 = 0, t2 = 0) {
1962
+ function getExtrapolatedCommand(com1, com2, t = 0) {
1883
1963
 
1884
1964
  let { p0, cp1 } = com1;
1885
1965
  let { p, cp2 } = com2;
1886
1966
 
1887
1967
  // extrapolate control points
1888
- let cp1_S = {
1889
- x: (cp1.x - (1 - t1) * p0.x) / t1,
1890
- y: (cp1.y - (1 - t1) * p0.y) / t1
1968
+ cp1 = {
1969
+ x: (cp1.x - (1 - t) * p0.x) / t,
1970
+ y: (cp1.y - (1 - t) * p0.y) / t
1891
1971
  };
1892
1972
 
1893
- let cp2_S = {
1894
- x: (cp2.x - t2 * p.x) / (1 - t2),
1895
- y: (cp2.y - t2 * p.y) / (1 - t2)
1973
+ cp2 = {
1974
+ x: (cp2.x - t * p.x) / (1 - t),
1975
+ y: (cp2.y - t * p.y) / (1 - t)
1896
1976
  };
1897
1977
 
1898
- let comS = { p0, cp1: cp1_S, cp2: cp2_S, p };
1899
-
1900
- return comS
1901
-
1978
+ return { p0, cp1, cp2, p };
1902
1979
  }
1903
1980
 
1904
1981
  function findSplitT(com1, com2) {
@@ -1911,52 +1988,40 @@ function findSplitT(com1, com2) {
1911
1988
  return t
1912
1989
  }
1913
1990
 
1914
- function checkBezierFlatness(p0, cpts, p) {
1915
-
1916
- let isFlat = false;
1917
-
1918
- let isCubic = cpts.length === 2;
1919
-
1920
- let cp1 = cpts[0];
1921
- let cp2 = isCubic ? cpts[1] : cp1;
1922
-
1923
- if (p0.x === cp1.x && p0.y === cp1.y && p.x === cp2.x && p.y === cp2.y) return true;
1924
-
1925
- let dx1 = cp1.x - p0.x;
1926
- let dy1 = cp1.y - p0.y;
1927
-
1928
- let dx2 = p.x - cp2.x;
1929
- let dy2 = p.y - cp2.y;
1930
-
1931
- let cross1 = Math.abs(dx1 * dy2 - dy1 * dx2);
1991
+ function commandIsFlat(points, {
1992
+ tolerance = 1,
1993
+ debug=false
1994
+ } = {}) {
1932
1995
 
1933
- if (!cross1) return true
1996
+ let isFlat=false;
1997
+ let report = {
1998
+ flat:true,
1999
+ steepness:0
2000
+ };
1934
2001
 
1935
- let dx0 = p.x - p0.x;
1936
- let dy0 = p.y - p0.y;
1937
- let cross0 = Math.abs(dx0 * dy1 - dy0 * dx1);
2002
+ let p0 = points[0];
2003
+ let p = points[points.length - 1];
1938
2004
 
1939
- if (!cross0) return true
2005
+ let xSet = new Set([...points.map(pt => +pt.x.toFixed(8))]);
2006
+ let ySet = new Set([...points.map(pt => +pt.y.toFixed(8))]);
1940
2007
 
1941
- let area = getPolygonArea([p0,...cpts, p], true);
1942
- let dist1 = getSquareDistance(p0, p);
1943
- let thresh = dist1/200;
2008
+ // must be flat
2009
+ if(xSet.size===1 || ySet.size===1) return !debug ? true : report;
1944
2010
 
1945
- // if(area<thresh) return true;
1946
- isFlat = area<thresh;
2011
+ let squareDist = getSquareDistance(p0, p);
2012
+ let threshold = squareDist / 1000 * tolerance;
2013
+ let area = getPolygonArea(points, true);
1947
2014
 
1948
- /*
2015
+ // flat enough
2016
+ if(area < threshold) isFlat = true;
1949
2017
 
1950
- let rat = (cross0 / cross1)
2018
+ if(debug){
2019
+ report.flat = isFlat;
1951
2020
 
1952
- if (rat < 1.1) {
1953
- console.log('cross', cross0, cross1, 'rat', rat );
1954
- isFlat = true;
2021
+ report.steepness = area/squareDist*10;
1955
2022
  }
1956
- */
1957
-
1958
- return isFlat;
1959
2023
 
2024
+ return !debug ? isFlat : report;
1960
2025
  }
1961
2026
 
1962
2027
  function analyzePathData(pathData = []) {
@@ -2072,14 +2137,17 @@ function analyzePathData(pathData = []) {
2072
2137
  commandPts.push(p);
2073
2138
 
2074
2139
  /*
2140
+
2075
2141
  let commandFlatness = commandIsFlat(commandPts);
2076
2142
  isFlat = commandFlatness.flat;
2077
2143
  com.flat = isFlat;
2078
2144
 
2079
2145
  if (isFlat) {
2080
2146
  com.extreme = false;
2147
+
2081
2148
  }
2082
2149
  */
2150
+
2083
2151
  }
2084
2152
 
2085
2153
  /**
@@ -2189,7 +2257,7 @@ function detectAccuracy(pathData) {
2189
2257
  let p = M;
2190
2258
  pathData[0].decimals = 0;
2191
2259
 
2192
- let dims = new Set();
2260
+ let dims = [];
2193
2261
 
2194
2262
  // add average distances
2195
2263
  for (let i = 0, len = pathData.length; i < len; i++) {
@@ -2202,7 +2270,7 @@ function detectAccuracy(pathData) {
2202
2270
  // use existing averave dimension value or calculate
2203
2271
  let dimA = com.dimA ? +com.dimA.toFixed(8) : type !== 'M' ? +getDistAv(p0, p).toFixed(8) : 0;
2204
2272
 
2205
- if (dimA) dims.add(dimA);
2273
+ if (dimA) dims.push(dimA);
2206
2274
 
2207
2275
  if (type === 'M') {
2208
2276
  M = p;
@@ -2210,7 +2278,7 @@ function detectAccuracy(pathData) {
2210
2278
  p0 = p;
2211
2279
  }
2212
2280
 
2213
- let dim_min = Array.from(dims).sort();
2281
+ let dim_min = dims.sort();
2214
2282
 
2215
2283
  /*
2216
2284
  let minVal = dim_min.length > 15 ?
@@ -2236,19 +2304,24 @@ function detectAccuracy(pathData) {
2236
2304
  * based on suggested accuracy in path data
2237
2305
  */
2238
2306
  function roundPathData(pathData, decimals = -1) {
2239
- // has recommended decimals
2240
- let hasDecimal = decimals == 'auto' && pathData[0].hasOwnProperty('decimals') ? true : false;
2241
2307
 
2242
- for (let c = 0, len = pathData.length; c < len; c++) {
2243
- let com = pathData[c];
2308
+ let len = pathData.length;
2244
2309
 
2245
- if (decimals > -1 || hasDecimal) {
2246
- decimals = hasDecimal ? com.decimals : decimals;
2310
+ for (let c = 0; c < len; c++) {
2247
2311
 
2248
- pathData[c].values = com.values.map(val => { return val ? +val.toFixed(decimals) : val });
2312
+ let values = pathData[c].values;
2313
+ let valLen = values.length;
2249
2314
 
2315
+ if (valLen && (decimals > -1) ) {
2316
+
2317
+ for(let v=0; v<valLen; v++){
2318
+
2319
+ pathData[c].values[v] = +values[v].toFixed(decimals);
2320
+ }
2250
2321
  }
2251
- } return pathData;
2322
+ }
2323
+
2324
+ return pathData;
2252
2325
  }
2253
2326
 
2254
2327
  function revertCubicQuadratic(p0 = {}, cp1 = {}, cp2 = {}, p = {}) {
@@ -2266,7 +2339,7 @@ function revertCubicQuadratic(p0 = {}, cp1 = {}, cp2 = {}, p = {}) {
2266
2339
  let values = [cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y];
2267
2340
  let comN = {type, values};
2268
2341
 
2269
- if (dist1 < threshold) {
2342
+ if (dist1 && threshold && dist1 < threshold) {
2270
2343
  cp1_Q = checkLineIntersection(p0, cp1, p, cp2, false);
2271
2344
  if (cp1_Q) {
2272
2345
 
@@ -2291,9 +2364,10 @@ function convertPathData(pathData, {
2291
2364
  if (toShorthands) pathData = pathDataToShorthands(pathData);
2292
2365
 
2293
2366
  // pre round - before relative conversion to minimize distortions
2294
- pathData = roundPathData(pathData, decimals);
2367
+ if(decimals>-1 && toRelative) pathData = roundPathData(pathData, decimals);
2295
2368
  if (toRelative) pathData = pathDataToRelative(pathData);
2296
2369
  if (decimals > -1) pathData = roundPathData(pathData, decimals);
2370
+
2297
2371
  return pathData
2298
2372
  }
2299
2373
 
@@ -2595,7 +2669,7 @@ function pathDataToLonghands(pathData, decimals = -1, test = true) {
2595
2669
  * L, L, C, Q => H, V, S, T
2596
2670
  * reversed method: pathDataToLonghands()
2597
2671
  */
2598
- function pathDataToShorthands(pathData, decimals = -1, test = true) {
2672
+ function pathDataToShorthands(pathData, decimals = -1, test = false) {
2599
2673
 
2600
2674
  /**
2601
2675
  * analyze pathdata – if you're sure your data is already absolute skip it via test=false
@@ -2606,29 +2680,28 @@ function pathDataToShorthands(pathData, decimals = -1, test = true) {
2606
2680
  hasRel = /[astvqmhlc]/g.test(commandTokens);
2607
2681
  }
2608
2682
 
2609
- pathData = test && hasRel ? pathDataToAbsolute(pathData, decimals) : pathData;
2683
+ pathData = test && hasRel ? pathDataToAbsoluteOrRelative(pathData) : pathData;
2684
+
2685
+ let len = pathData.length;
2686
+ let pathDataShorts = new Array(len);
2610
2687
 
2611
2688
  let comShort = {
2612
2689
  type: "M",
2613
2690
  values: pathData[0].values
2614
2691
  };
2615
2692
 
2616
- if (pathData[0].decimals) {
2617
-
2618
- comShort.decimals = pathData[0].decimals;
2619
- }
2620
-
2621
- let pathDataShorts = [comShort];
2693
+ pathDataShorts[0] = comShort;
2622
2694
 
2623
2695
  let p0 = { x: pathData[0].values[0], y: pathData[0].values[1] };
2624
2696
  let p;
2625
2697
  let tolerance = 0.01;
2626
2698
 
2627
- for (let i = 1, len = pathData.length; i < len; i++) {
2699
+ for (let i = 1; i < len; i++) {
2628
2700
 
2629
2701
  let com = pathData[i];
2630
2702
  let { type, values } = com;
2631
- let valuesLast = values.slice(-2);
2703
+ let valuesLen = values.length;
2704
+ let valuesLast = [values[valuesLen-2], values[valuesLen-1]];
2632
2705
 
2633
2706
  // previoius command
2634
2707
  let comPrev = pathData[i - 1];
@@ -2676,7 +2749,8 @@ function pathDataToShorthands(pathData, decimals = -1, test = true) {
2676
2749
  if (typePrev !== 'Q') {
2677
2750
 
2678
2751
  p0 = { x: valuesLast[0], y: valuesLast[1] };
2679
- pathDataShorts.push(com);
2752
+
2753
+ pathDataShorts[i] = com;
2680
2754
  continue;
2681
2755
  }
2682
2756
 
@@ -2705,7 +2779,8 @@ function pathDataToShorthands(pathData, decimals = -1, test = true) {
2705
2779
 
2706
2780
  if (typePrev !== 'C') {
2707
2781
 
2708
- pathDataShorts.push(com);
2782
+ pathDataShorts[i] = com;
2783
+
2709
2784
  p0 = { x: valuesLast[0], y: valuesLast[1] };
2710
2785
  continue;
2711
2786
  }
@@ -2747,8 +2822,10 @@ function pathDataToShorthands(pathData, decimals = -1, test = true) {
2747
2822
  }
2748
2823
 
2749
2824
  p0 = { x: valuesLast[0], y: valuesLast[1] };
2750
- pathDataShorts.push(comShort);
2825
+ pathDataShorts[i] = comShort;
2826
+
2751
2827
  }
2828
+
2752
2829
  return pathDataShorts;
2753
2830
  }
2754
2831
 
@@ -3193,10 +3270,10 @@ function normalizePathData(pathData = [],
3193
3270
  quadraticToCubic = false,
3194
3271
  arcToCubic = false,
3195
3272
  arcAccuracy = 2,
3196
- } = {},
3197
3273
 
3198
- {
3274
+ // assume we need full normalization
3199
3275
  hasRelatives = true, hasShorthands = true, hasQuadratics = true, hasArcs = true, testTypes = false
3276
+
3200
3277
  } = {}
3201
3278
  ) {
3202
3279
 
@@ -3247,15 +3324,28 @@ function parsePathDataNormalized(d,
3247
3324
  } = {}
3248
3325
  ) {
3249
3326
 
3250
- let pathDataObj = parsePathDataString(d);
3251
- let { hasRelatives, hasShorthands, hasQuadratics, hasArcs } = pathDataObj;
3252
- let pathData = pathDataObj.pathData;
3327
+ // is already array
3328
+ let isArray = Array.isArray(d);
3329
+
3330
+ // normalize native pathData to regular array
3331
+ let hasConstructor = isArray && typeof d[0] === 'object' && typeof d[0].constructor === 'function';
3332
+ /*
3333
+ if (hasConstructor) {
3334
+ d = d.map(com => { return { type: com.type, values: com.values } })
3335
+ console.log('hasConstructor', hasConstructor, (typeof d[0].constructor), d);
3336
+ }
3337
+ */
3338
+
3339
+ let pathDataObj = isArray ? d : parsePathDataString(d);
3340
+
3341
+ let { hasRelatives = true, hasShorthands = true, hasQuadratics = true, hasArcs = true } = pathDataObj;
3342
+ let pathData = hasConstructor ? pathDataObj : pathDataObj.pathData;
3253
3343
 
3254
3344
  // normalize
3255
3345
  pathData = normalizePathData(pathData,
3256
- { toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy },
3257
-
3258
- { hasRelatives, hasShorthands, hasQuadratics, hasArcs }
3346
+ { toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy,
3347
+ hasRelatives, hasShorthands, hasQuadratics, hasArcs
3348
+ },
3259
3349
  );
3260
3350
 
3261
3351
  return pathData;
@@ -3607,7 +3697,8 @@ function parsePathDataString(d, debug = true) {
3607
3697
  if (debug === 'log') {
3608
3698
  console.log(feedback);
3609
3699
  } else {
3610
- throw new Error(feedback)
3700
+
3701
+ console.warn(feedback);
3611
3702
  }
3612
3703
  }
3613
3704
 
@@ -3639,22 +3730,22 @@ function stringifyPathData(pathData) {
3639
3730
  return pathData.map(com => { return `${com.type} ${com.values.join(' ')}` }).join(' ');
3640
3731
  }
3641
3732
 
3642
- function shapeElToPath(el){
3733
+ function shapeElToPath(el) {
3643
3734
 
3644
3735
  let nodeName = el.nodeName.toLowerCase();
3645
- if(nodeName==='path')return el;
3736
+ if (nodeName === 'path') return el;
3646
3737
 
3647
3738
  let pathData = getPathDataFromEl(el);
3648
- let d = pathData.map(com=>{return `${com.type} ${com.values} `}).join(' ');
3649
- let attributes = [...el.attributes].map(att=>att.name);
3739
+ let d = pathData.map(com => { return `${com.type} ${com.values} ` }).join(' ');
3740
+ let attributes = [...el.attributes].map(att => att.name);
3650
3741
 
3651
3742
  let pathN = document.createElementNS('http://www.w3.org/2000/svg', 'path');
3652
- pathN.setAttribute('d', d );
3743
+ pathN.setAttribute('d', d);
3653
3744
 
3654
3745
  let exclude = ['x', 'y', 'cx', 'cy', 'dx', 'dy', 'r', 'rx', 'ry', 'width', 'height', 'points'];
3655
3746
 
3656
- attributes.forEach(att=>{
3657
- if(!exclude.includes(att)){
3747
+ attributes.forEach(att => {
3748
+ if (!exclude.includes(att)) {
3658
3749
  let val = el.getAttribute(att);
3659
3750
  pathN.setAttribute(att, val);
3660
3751
  }
@@ -3665,7 +3756,7 @@ function shapeElToPath(el){
3665
3756
  }
3666
3757
 
3667
3758
  // retrieve pathdata from svg geometry elements
3668
- function getPathDataFromEl(el, stringify=false) {
3759
+ function getPathDataFromEl(el, stringify = false) {
3669
3760
 
3670
3761
  let pathData = [];
3671
3762
  let type = el.nodeName;
@@ -3823,7 +3914,9 @@ function getPathDataFromEl(el, stringify=false) {
3823
3914
  attNames = ['cx', 'cy', 'rx', 'ry', 'r'];
3824
3915
  ({ cx, cy, r, rx, ry } = getAtts(attNames));
3825
3916
 
3826
- if (type === 'circle') {
3917
+ let isCircle = type === 'circle';
3918
+
3919
+ if (isCircle) {
3827
3920
  r = r;
3828
3921
  rx = r;
3829
3922
  ry = r;
@@ -3832,10 +3925,14 @@ function getPathDataFromEl(el, stringify=false) {
3832
3925
  ry = ry ? ry : r;
3833
3926
  }
3834
3927
 
3928
+ // simplified radii for cirecles
3929
+ let rxS = isCircle && r>=1 ? 1 : rx;
3930
+ let ryS = isCircle && r>=1 ? 1 : rx;
3931
+
3835
3932
  pathData = [
3836
3933
  { type: "M", values: [cx + rx, cy] },
3837
- { type: "A", values: [rx, ry, 0, 1, 1, cx - rx, cy] },
3838
- { type: "A", values: [rx, ry, 0, 1, 1, cx + rx, cy] },
3934
+ { type: "A", values: [rxS, ryS, 0, 1, 1, cx - rx, cy] },
3935
+ { type: "A", values: [rxS, ryS, 0, 1, 1, cx + rx, cy] },
3839
3936
  ];
3840
3937
 
3841
3938
  break;
@@ -3867,22 +3964,28 @@ function getPathDataFromEl(el, stringify=false) {
3867
3964
  break;
3868
3965
  }
3869
3966
 
3870
- return stringify ? stringifyPathData(pathData): pathData;
3967
+ return stringify ? stringifyPathData(pathData) : pathData;
3871
3968
 
3872
3969
  }
3873
3970
 
3874
- function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLinetos = true) {
3971
+ function pathDataRemoveColinear(pathData, {
3972
+ tolerance = 1,
3973
+
3974
+ flatBezierToLinetos = true
3975
+ }={}) {
3875
3976
 
3876
3977
  let pathDataN = [pathData[0]];
3978
+
3877
3979
  let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
3878
3980
  let p0 = M;
3879
3981
  let p = M;
3880
3982
  pathData[pathData.length - 1].type.toLowerCase() === 'z';
3881
3983
 
3882
3984
  for (let c = 1, l = pathData.length; c < l; c++) {
3883
- let comPrev = pathData[c - 1];
3985
+
3884
3986
  let com = pathData[c];
3885
3987
  let comN = pathData[c + 1] || pathData[l - 1];
3988
+
3886
3989
  let p1 = comN.type.toLowerCase() === 'z' ? M : { x: comN.values[comN.values.length - 2], y: comN.values[comN.values.length - 1] };
3887
3990
 
3888
3991
  let { type, values } = com;
@@ -3891,11 +3994,9 @@ function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLinetos = t
3891
3994
 
3892
3995
  let area = getPolygonArea([p0, p, p1], true);
3893
3996
 
3894
- getSquareDistance(p0, p);
3895
- getSquareDistance(p, p1);
3896
3997
  let distSquare = getSquareDistance(p0, p1);
3897
3998
 
3898
- let distMax = distSquare / 200 * tolerance;
3999
+ let distMax = distSquare ? distSquare / 333 * tolerance : 0;
3899
4000
 
3900
4001
  let isFlat = area < distMax;
3901
4002
  let isFlatBez = false;
@@ -3909,31 +4010,38 @@ function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLinetos = t
3909
4010
  [{ x: values[0], y: values[1] }, { x: values[2], y: values[3] }] :
3910
4011
  (type === 'Q' ? [{ x: values[0], y: values[1] }] : []);
3911
4012
 
3912
- isFlatBez = checkBezierFlatness(p0, cpts, p);
3913
-
3914
- // console.log();
4013
+ isFlatBez = commandIsFlat([p0, ...cpts, p],{tolerance});
3915
4014
 
3916
- if (isFlatBez && c < l - 1 && comPrev.type !== 'C') {
4015
+ if (isFlatBez && c < l - 1 ) {
3917
4016
  type = "L";
3918
4017
  com.type = "L";
3919
4018
  com.values = valsL;
3920
4019
 
3921
4020
  }
3922
-
3923
4021
  }
3924
4022
 
3925
- // update end point
3926
- p0 = p;
3927
-
3928
4023
  // colinear – exclude arcs (as always =) as semicircles won't have an area
3929
4024
 
3930
4025
  if ( isFlat && c < l - 1 && (type === 'L' || (flatBezierToLinetos && isFlatBez)) ) {
3931
-
3932
4026
 
4027
+ /*
4028
+ console.log(area, distMax );
4029
+
4030
+ if(p0.x === p.x && p0.y === p.y){
4031
+
4032
+ }
4033
+
4034
+ renderPoint(markers, p0, 'blue', '1.5%', '1')
4035
+ renderPoint(markers, p, 'red', '1%', '1')
4036
+ renderPoint(markers, p1, 'cyan', '0.5%', '1')
4037
+ */
3933
4038
 
3934
4039
  continue;
3935
4040
  }
3936
4041
 
4042
+ // update end point
4043
+ p0 = p;
4044
+
3937
4045
  if (type === 'M') {
3938
4046
  M = p;
3939
4047
  p0 = M;
@@ -3954,6 +4062,7 @@ function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLinetos = t
3954
4062
 
3955
4063
  function removeOrphanedM(pathData) {
3956
4064
 
4065
+ let pathDataN = [];
3957
4066
  for (let i = 0, l = pathData.length; i < l; i++) {
3958
4067
  let com = pathData[i];
3959
4068
  if (!com) continue;
@@ -3962,14 +4071,14 @@ function removeOrphanedM(pathData) {
3962
4071
  if ((type === 'M' || type === 'm')) {
3963
4072
 
3964
4073
  if (!comN || (comN && (comN.type === 'Z' || comN.type === 'z'))) {
3965
- pathData[i] = null;
3966
- pathData[i + 1] = null;
4074
+ if(comN) i++;
4075
+ continue
3967
4076
  }
3968
4077
  }
4078
+ pathDataN.push(com);
3969
4079
  }
3970
4080
 
3971
- pathData = pathData.filter(Boolean);
3972
- return pathData;
4081
+ return pathDataN;
3973
4082
 
3974
4083
  }
3975
4084
 
@@ -4000,19 +4109,23 @@ function removeZeroLengthLinetos(pathData) {
4000
4109
 
4001
4110
  for (let c = 1, l = pathData.length; c < l; c++) {
4002
4111
  let com = pathData[c];
4112
+ let comPrev = pathData[c-1];
4113
+ let comNext = pathData[c+1] || null;
4003
4114
  let { type, values } = com;
4004
4115
 
4005
- let valsLen = values.length;
4116
+ // zero length segments are simetimes used in icons for dots
4117
+ let isDot = comPrev.type.toLowerCase() ==='m' && !comNext;
4006
4118
 
4119
+ let valsLen = values.length;
4007
4120
  p = { x: values[valsLen-2], y: values[valsLen-1] };
4008
4121
 
4009
4122
  // skip lineto
4010
- if (type === 'L' && p.x === p0.x && p.y === p0.y) {
4123
+ if (!isDot && type === 'L' && p.x === p0.x && p.y === p0.y) {
4011
4124
  continue
4012
4125
  }
4013
4126
 
4014
4127
  // skip minified zero length
4015
- if (type === 'l' || type === 'v' || type === 'h') {
4128
+ if (!isDot && (type === 'l' || type === 'v' || type === 'h')) {
4016
4129
  let noLength = type === 'l' ? (values.join('') === '00') : values[0] === 0;
4017
4130
  if(noLength) continue
4018
4131
  }
@@ -4114,7 +4227,7 @@ function optimizeClosePath(pathData, removeFinalLineto = true, reorder = true) {
4114
4227
  }
4115
4228
  // use top most command
4116
4229
  else {
4117
- indices = indices.sort((a, b) => +a.y.toFixed(1) - +b.y.toFixed(1) || a.x - b.x);
4230
+ indices = indices.sort((a, b) => +a.y.toFixed(8) - +b.y.toFixed(8) || a.x - b.x);
4118
4231
  newIndex = indices[0].index;
4119
4232
  }
4120
4233
 
@@ -4122,7 +4235,7 @@ function optimizeClosePath(pathData, removeFinalLineto = true, reorder = true) {
4122
4235
  pathData = newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
4123
4236
  }
4124
4237
 
4125
- M = { x: +pathData[0].values[0].toFixed(8), y: +pathData[0].values[1].toFixed(7) };
4238
+ M = { x: +pathData[0].values[0].toFixed(8), y: +pathData[0].values[1].toFixed(8) };
4126
4239
 
4127
4240
  len = pathData.length;
4128
4241
 
@@ -4236,149 +4349,61 @@ function addClosePathLineto(pathData) {
4236
4349
  return pathData;
4237
4350
  }
4238
4351
 
4239
- /**
4240
- * reverse pathdata
4241
- * make sure all command coordinates are absolute and
4242
- * shorthands are converted to long notation
4243
- */
4244
- function reversePathData(pathData, {
4245
- arcToCubic = false,
4246
- quadraticToCubic = false,
4247
- toClockwise = false,
4248
- returnD = false
4352
+ function refineAdjacentExtremes(pathData, {
4353
+ threshold = null, tolerance = 1
4249
4354
  } = {}) {
4250
4355
 
4251
- /**
4252
- * Add closing lineto:
4253
- * needed for path reversing or adding points
4254
- */
4255
- const addClosePathLineto = (pathData) => {
4256
- let closed = pathData[pathData.length - 1].type.toLowerCase() === "z";
4257
- let M = pathData[0];
4258
- let [x0, y0] = [M.values[0], M.values[1]];
4259
- let lastCom = closed ? pathData[pathData.length - 2] : pathData[pathData.length - 1];
4260
- let [xE, yE] = [lastCom.values[lastCom.values.length - 2], lastCom.values[lastCom.values.length - 1]];
4261
-
4262
- if (closed && (x0 != xE || y0 != yE)) {
4263
-
4264
- pathData.pop();
4265
- pathData.push(
4266
- {
4267
- type: "L",
4268
- values: [x0, y0]
4269
- },
4270
- {
4271
- type: "Z",
4272
- values: []
4273
- }
4274
- );
4275
- }
4276
- return pathData;
4277
- };
4278
-
4279
- // helper to rearrange control points for all command types
4280
- const reverseControlPoints = (type, values) => {
4281
- let controlPoints = [];
4282
- let endPoints = [];
4283
- if (type !== "A") {
4284
- for (let p = 0; p < values.length; p += 2) {
4285
- controlPoints.push([values[p], values[p + 1]]);
4286
- }
4287
- endPoints = controlPoints.pop();
4288
- controlPoints.reverse();
4289
- }
4290
- // is arc
4291
- else {
4292
-
4293
- let sweep = values[4] == 0 ? 1 : 0;
4294
- controlPoints = [values[0], values[1], values[2], values[3], sweep];
4295
- endPoints = [values[5], values[6]];
4296
- }
4297
- return { controlPoints, endPoints };
4298
- };
4299
-
4300
- // start compiling new path data
4301
- let pathDataNew = [];
4356
+ if (!threshold) {
4357
+ let bb = getPathDataBBox(pathData);
4358
+ threshold = (bb.width + bb.height) / 2 * 0.05;
4302
4359
 
4303
- let closed =
4304
- pathData[pathData.length - 1].type.toLowerCase() === "z" ? true : false;
4305
- if (closed) {
4306
- // add lineto closing space between Z and M
4307
- pathData = addClosePathLineto(pathData);
4308
- // remove Z closepath
4309
- pathData.pop();
4310
4360
  }
4311
4361
 
4312
- // define last point as new M if path isn't closed
4313
- let valuesLast = pathData[pathData.length - 1].values;
4314
- let valuesLastL = valuesLast.length;
4315
- let M = closed
4316
- ? pathData[0]
4317
- : {
4318
- type: "M",
4319
- values: [valuesLast[valuesLastL - 2], valuesLast[valuesLastL - 1]]
4320
- };
4321
- // starting M stays the same – unless the path is not closed
4322
- pathDataNew.push(M);
4362
+ let l = pathData.length;
4323
4363
 
4324
- // reverse path data command order for processing
4325
- pathData.reverse();
4326
- for (let i = 1; i < pathData.length; i++) {
4364
+ for (let i = 0; i < l; i++) {
4327
4365
  let com = pathData[i];
4328
- let type = com.type;
4329
- let values = com.values;
4330
- let comPrev = pathData[i - 1];
4331
- let typePrev = comPrev.type;
4332
- let valuesPrev = comPrev.values;
4366
+ let { type, values, extreme, corner = false, dimA, p0, p } = com;
4367
+ let comN = pathData[i + 1] ? pathData[i + 1] : null;
4368
+ let comN2 = pathData[i + 2] ? pathData[i + 2] : null;
4333
4369
 
4334
- // get reversed control points and new end coordinates
4335
- let controlPointsPrev = reverseControlPoints(typePrev, valuesPrev).controlPoints;
4336
- let endPoints = reverseControlPoints(type, values).endPoints;
4370
+ // check dist
4371
+ let diff = comN ? getDistAv(p, comN.p) : Infinity;
4372
+ let isCose = diff < threshold;
4337
4373
 
4338
- // create new path data
4339
- let newValues = [];
4340
- newValues = [controlPointsPrev, endPoints].flat();
4341
- pathDataNew.push({
4342
- type: typePrev,
4343
- values: newValues.flat()
4344
- });
4345
- }
4374
+ let diff2 = comN2 ? getDistAv(comN2.p, comN.p) : Infinity;
4375
+ let isCose2 = diff2 < threshold;
4346
4376
 
4347
- // add previously removed Z close path
4348
- if (closed) {
4349
- pathDataNew.push({
4350
- type: "z",
4351
- values: []
4352
- });
4353
- }
4377
+ // next is extreme
4378
+ if (comN && type === 'C' && comN.type === 'C' && extreme && comN2 && comN2.extreme) {
4354
4379
 
4355
- return pathDataNew;
4356
- }
4380
+ if (isCose2 || isCose) {
4357
4381
 
4358
- function refineAdjacentExtremes(pathData, {
4359
- threshold = null, tolerance = 1
4360
- } = {}) {
4382
+ // extrapolate
4383
+ let comEx = getCombinedByDominant(comN, comN2, threshold, tolerance, false);
4361
4384
 
4362
- if (!threshold) {
4363
- let bb = getPathDataBBox(pathData);
4364
- threshold = (bb.width + bb.height) / 2 * 0.05;
4385
+ if (comEx.length === 1) {
4365
4386
 
4366
- }
4387
+ pathData[i + 1] = null;
4388
+ comEx = comEx[0];
4367
4389
 
4368
- let l = pathData.length;
4390
+ pathData[i + 2].values = [comEx.cp1.x, comEx.cp1.y, comEx.cp2.x, comEx.cp2.y, comEx.p.x, comEx.p.y];
4391
+ pathData[i + 2].cp1 = comEx.cp1;
4392
+ pathData[i + 2].cp2 = comEx.cp2;
4393
+ pathData[i + 2].p0 = comEx.p0;
4394
+ pathData[i + 2].p = comEx.p;
4395
+ pathData[i + 2].extreme = comEx.extreme;
4369
4396
 
4370
- for (let i = 0; i < l; i++) {
4371
- let com = pathData[i];
4372
- let { type, values, extreme, corner=false, dimA, p0, p } = com;
4373
- let comN = pathData[i + 1] ? pathData[i + 1] : null;
4397
+ i++;
4398
+ continue
4399
+ }
4400
+ }
4374
4401
 
4375
- // adjacent
4402
+ }
4376
4403
 
4377
- if (comN && type === 'C' && comN.type === 'C' && extreme && !corner) {
4404
+ // short after extreme
4378
4405
 
4379
- // check dist
4380
- let diff = getDistAv(p, comN.p);
4381
- let isCose = diff < threshold;
4406
+ if (comN && type === 'C' && comN.type === 'C' && extreme ) {
4382
4407
 
4383
4408
  if (isCose) {
4384
4409
 
@@ -4391,11 +4416,23 @@ function refineAdjacentExtremes(pathData, {
4391
4416
  let ptI;
4392
4417
  let t = 1;
4393
4418
 
4419
+ let area0 = getPolygonArea([com.p0, com.p , comN.p]);
4420
+ // cpts area
4421
+ let area1 = getPolygonArea([com.p0, com.cp1, com.cp2, com.p]);
4422
+
4423
+ // sign change: is corner => skip
4424
+ if ( (area0<0 && area1>0) || (area0>0 && area1<0)) {
4425
+
4426
+ continue;
4427
+ }
4428
+
4429
+
4394
4430
  if (comN.extreme) {
4395
4431
 
4396
4432
  // extend cp2
4397
4433
  if (horizontal) {
4398
4434
  t = Math.abs(Math.abs(comN.cp2.x - comN.p.x) / Math.abs(com.cp2.x - com.p.x));
4435
+ t = Math.min(1, t);
4399
4436
 
4400
4437
  ptI = interpolate(comN.p, com.cp2, 1 + t);
4401
4438
  com.cp2.x = ptI.x;
@@ -4404,6 +4441,8 @@ function refineAdjacentExtremes(pathData, {
4404
4441
  else {
4405
4442
 
4406
4443
  t = Math.abs(Math.abs(comN.cp2.y - comN.p.y) / Math.abs(com.cp2.y - com.p.y));
4444
+ t = Math.min(1, t);
4445
+
4407
4446
  ptI = interpolate(comN.p, com.cp2, 1 + t);
4408
4447
  com.cp2.y = ptI.y;
4409
4448
  }
@@ -4421,35 +4460,12 @@ function refineAdjacentExtremes(pathData, {
4421
4460
 
4422
4461
  }
4423
4462
 
4424
- // extend fist command
4425
- else {
4426
-
4427
- let comN2 = pathData[i + 2] ? pathData[i + 2] : null;
4428
- if (!comN2 && comN2.type !== 'C') continue
4429
-
4430
- // extrapolate
4431
- let comEx = getCombinedByDominant(comN, comN2, threshold, tolerance, false);
4432
-
4433
- if (comEx.length === 1) {
4434
- pathData[i + 1] = null;
4435
-
4436
- comEx = comEx[0];
4437
-
4438
- pathData[i + 2].values = [comEx.cp1.x, comEx.cp1.y, comEx.cp2.x, comEx.cp2.y, comEx.p.x, comEx.p.y];
4439
- pathData[i + 2].cp1 = comEx.cp1;
4440
- pathData[i + 2].cp2 = comEx.cp2;
4441
- pathData[i + 2].p0 = comEx.p0;
4442
- pathData[i + 2].p = comEx.p;
4443
- pathData[i + 2].extreme = comEx.extreme;
4444
-
4445
- i++;
4446
- continue
4447
- }
4448
-
4449
- }
4450
-
4451
4463
  }
4452
4464
  }
4465
+
4466
+ /*
4467
+ */
4468
+
4453
4469
  }
4454
4470
 
4455
4471
  // remove commands
@@ -4476,11 +4492,7 @@ function refineAdjacentExtremes(pathData, {
4476
4492
 
4477
4493
  if (penultimateCom && penultimateCom.type === 'C' && isCose && isClosingTo && fistExt) {
4478
4494
 
4479
- Math.abs(fistExt.cp1.x - M.x);
4480
- Math.abs(fistExt.cp1.y - M.y);
4481
-
4482
4495
  let comEx = getCombinedByDominant(penultimateCom, lastCom, threshold, tolerance, false);
4483
- console.log('comEx', comEx);
4484
4496
 
4485
4497
  if (comEx.length === 1) {
4486
4498
  pathData[lastIdx - 1] = comEx[0];
@@ -4516,7 +4528,7 @@ function cleanUpSVG(svgMarkup, {
4516
4528
  .querySelector("svg");
4517
4529
 
4518
4530
 
4519
- let allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class', 'fill', 'stroke', 'stroke-width'];
4531
+ let allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin'];
4520
4532
  removeExcludedAttribues(svg, allowed);
4521
4533
 
4522
4534
  let removeEls = ['metadata', 'script'];
@@ -4587,6 +4599,156 @@ function stringifySVG(svg){
4587
4599
  return markup
4588
4600
  }
4589
4601
 
4602
+ function refineRoundedCorners(pathData, {
4603
+ threshold = 0,
4604
+ tolerance = 1
4605
+ } = {}) {
4606
+
4607
+ let l = pathData.length;
4608
+
4609
+ // add fist command
4610
+ let pathDataN = [pathData[0]];
4611
+
4612
+ let isClosed = pathData[l - 1].type.toLowerCase() === 'z';
4613
+ let lastOff = isClosed ? 2 : 1;
4614
+
4615
+ let comLast = pathData[l - lastOff];
4616
+ let lastIsLine = comLast.type === 'L';
4617
+ let lastIsBez = comLast.type === 'C';
4618
+ let firstIsLine = pathData[1].type === 'L';
4619
+ let firstIsBez = pathData[1].type === 'C';
4620
+
4621
+ let normalizeClose = isClosed && firstIsBez;
4622
+
4623
+ // normalize closepath to lineto
4624
+ if (normalizeClose) {
4625
+ pathData[l - 1].values = pathData[0].values;
4626
+ pathData[l - 1].type = 'L';
4627
+ lastIsLine = true;
4628
+ }
4629
+
4630
+ for (let i = 1; i < l; i++) {
4631
+ let com = pathData[i];
4632
+ let { type } = com;
4633
+ let comN = pathData[i + 1] ? pathData[i + 1] : null;
4634
+
4635
+ // search small cubic segments enclosed by linetos
4636
+ if ((type === 'L' && comN && comN.type === 'C') ||
4637
+ (type === 'C' && comN && comN.type === 'L')
4638
+
4639
+ ) {
4640
+ let comL0 = com;
4641
+ let comL1 = null;
4642
+ let comBez = [];
4643
+ let offset = 0;
4644
+
4645
+ // start to end
4646
+ if (i === 1 && firstIsBez && lastIsLine) {
4647
+ comBez = [pathData[1]];
4648
+ comL0 = pathData[l - 1];
4649
+ comL1 = comN;
4650
+
4651
+ }
4652
+
4653
+ // closing corner to start
4654
+ if (isClosed && lastIsBez && firstIsLine && i === l - lastOff - 1) {
4655
+ comL1 = pathData[1];
4656
+ comBez = [pathData[l - lastOff]];
4657
+
4658
+ }
4659
+
4660
+ for (let j = i + 1; j < l; j++) {
4661
+ let comN = pathData[j] ? pathData[j] : null;
4662
+ let comPrev = pathData[j - 1];
4663
+
4664
+ if (comPrev.type === 'C') {
4665
+ comBez.push(comPrev);
4666
+ }
4667
+
4668
+ if (comN.type === 'L' && comPrev.type === 'C') {
4669
+ comL1 = comN;
4670
+ break;
4671
+ }
4672
+ offset++;
4673
+ }
4674
+
4675
+ if (comL1) {
4676
+
4677
+ // linetos
4678
+ let len1 = getDistAv(comL0.p0, comL0.p);
4679
+ let len2 = getDistAv(comL1.p0, comL1.p);
4680
+
4681
+ // bezier
4682
+
4683
+ let comBezLen = comBez.length;
4684
+ let len3 = getDistAv(comBez[0].p0, comBez[comBezLen - 1].p);
4685
+
4686
+ // check concaveness by area sign change
4687
+ let area1 = getPolygonArea([comL0.p0, comL0.p, comL1.p0, comL1.p], false);
4688
+ let area2 = getPolygonArea([comBez[0].p0, comBez[0].cp1, comBez[0].cp2, comBez[0].p], false);
4689
+
4690
+ let signChange = (area1 < 0 && area2 > 0) || (area1 > 0 && area2 < 0);
4691
+
4692
+ if (comBez && !signChange && len3 < threshold && len1 > len3 && len2 > len3) {
4693
+
4694
+ let ptQ = checkLineIntersection(comL0.p0, comL0.p, comL1.p0, comL1.p, false);
4695
+ if (ptQ) {
4696
+
4697
+ /*
4698
+ let dist1 = getDistAv(ptQ, comL0.p)
4699
+ let dist2 = getDistAv(ptQ, comL1.p0)
4700
+ let diff = Math.abs(dist1-dist2)
4701
+ let rat = diff/Math.max(dist1, dist2)
4702
+ console.log('rat', rat);
4703
+ */
4704
+
4705
+ /*
4706
+ // adjust curve start and end to meet original
4707
+ let t = 1
4708
+
4709
+ let p0_2 = pointAtT([ptQ, comL0.p], t)
4710
+
4711
+ comL0.p = p0_2
4712
+ comL0.values = [p0_2.x, p0_2.y]
4713
+
4714
+ let p_2 = pointAtT([ptQ, comL1.p0], t)
4715
+
4716
+ comL1.p0 = p_2
4717
+
4718
+ */
4719
+
4720
+ let comQ = { type: 'Q', values: [ptQ.x, ptQ.y, comL1.p0.x, comL1.p0.y] };
4721
+ comQ.p0 = comL0.p;
4722
+ comQ.cp1 = ptQ;
4723
+ comQ.p = comL1.p0;
4724
+
4725
+ // add quadratic command
4726
+ pathDataN.push(comL0, comQ);
4727
+ i += offset;
4728
+ continue;
4729
+ }
4730
+ }
4731
+ }
4732
+ }
4733
+
4734
+ // skip last lineto
4735
+ if (normalizeClose && i === l - 1 && type === 'L') {
4736
+ continue
4737
+ }
4738
+
4739
+ pathDataN.push(com);
4740
+
4741
+ }
4742
+
4743
+ // revert close path normalization
4744
+ if (normalizeClose) {
4745
+ pathDataN.push({ type: 'Z', values: [] });
4746
+ }
4747
+
4748
+ return pathDataN;
4749
+
4750
+ }
4751
+
4590
4752
  function svgPathSimplify(input = '', {
4591
4753
 
4592
4754
  // return svg markup or object
@@ -4605,11 +4767,14 @@ function svgPathSimplify(input = '', {
4605
4767
 
4606
4768
  simplifyBezier = true,
4607
4769
  optimizeOrder = true,
4770
+ removeZeroLength = true,
4608
4771
  removeColinear = true,
4609
4772
  flatBezierToLinetos = true,
4610
4773
  revertToQuadratics = true,
4611
4774
 
4612
4775
  refineExtremes = true,
4776
+ refineCorners = false,
4777
+
4613
4778
  keepExtremes = true,
4614
4779
  keepCorners = true,
4615
4780
  extrapolateDominant = true,
@@ -4654,27 +4819,38 @@ function svgPathSimplify(input = '', {
4654
4819
  */
4655
4820
 
4656
4821
  // original size
4657
- svgSize = new Blob([input]).size;
4658
4822
 
4659
- // single path
4823
+ svgSize = input.length;
4824
+
4825
+ // mode:0 – single path
4660
4826
  if (!mode) {
4661
4827
  if (inputType === 'pathDataString') {
4662
4828
  d = input;
4663
4829
  } else if (inputType === 'polyString') {
4664
4830
  d = 'M' + input;
4665
4831
  }
4832
+ else if (inputType === 'pathData') {
4833
+ d = input;
4834
+
4835
+ // stringify to compare lengths
4836
+
4837
+ let dStr = d.map(com=>{return `${com.type} ${com.values.join(' ')}`}).join(' ') ;
4838
+ svgSize = dStr.length;
4839
+
4840
+ }
4841
+
4666
4842
  paths.push({ d, el: null });
4667
4843
  }
4668
- // process svg
4844
+ // mode:1 – process complete svg DOM
4669
4845
  else {
4670
4846
 
4671
4847
  let returnDom = true;
4672
4848
  svg = cleanUpSVG(input, { returnDom, removeHidden, removeUnused }
4673
4849
  );
4674
4850
 
4675
- if(shapesToPaths){
4851
+ if (shapesToPaths) {
4676
4852
  let shapes = svg.querySelectorAll('polygon, polyline, line, rect, circle, ellipse');
4677
- shapes.forEach(shape=>{
4853
+ shapes.forEach(shape => {
4678
4854
  let path = shapeElToPath(shape);
4679
4855
  shape.replaceWith(path);
4680
4856
  });
@@ -4689,6 +4865,7 @@ function svgPathSimplify(input = '', {
4689
4865
 
4690
4866
  /**
4691
4867
  * process all paths
4868
+ * try simplifications and removals
4692
4869
  */
4693
4870
 
4694
4871
  // SVG optimization options
@@ -4701,37 +4878,35 @@ function svgPathSimplify(input = '', {
4701
4878
  // combinded path data for SVGs with mergePaths enabled
4702
4879
  let pathData_merged = [];
4703
4880
 
4704
- paths.forEach(path => {
4881
+ for (let i = 0, l = paths.length; l && i < l; i++) {
4882
+
4883
+ let path = paths[i];
4705
4884
  let { d, el } = path;
4706
4885
 
4707
- let pathDataO = parsePathDataNormalized(d, { quadraticToCubic, toAbsolute, arcToCubic });
4886
+ let pathData = parsePathDataNormalized(d, { quadraticToCubic, toAbsolute, arcToCubic });
4708
4887
 
4709
4888
  // count commands for evaluation
4710
- let comCount = pathDataO.length;
4711
-
4712
- // create clone for fallback
4713
-
4714
- let pathData = pathDataO;
4889
+ let comCount = pathData.length;
4715
4890
 
4716
- if(removeOrphanSubpaths) pathData = removeOrphanedM(pathData);
4891
+ if (removeOrphanSubpaths) pathData = removeOrphanedM(pathData);
4717
4892
 
4718
4893
  /**
4719
4894
  * get sub paths
4720
4895
  */
4721
4896
  let subPathArr = splitSubpaths(pathData);
4897
+ let lenSub = subPathArr.length;
4722
4898
 
4723
4899
  // cleaned up pathData
4724
- let pathDataArrN = [];
4725
4900
 
4726
- for (let i = 0, l = subPathArr.length; i < l; i++) {
4901
+ // reset array
4902
+ let pathDataFlat = [];
4727
4903
 
4728
- let pathDataSub = subPathArr[i];
4904
+ for (let i = 0; i < lenSub; i++) {
4729
4905
 
4730
- // try simplification in reversed order
4731
- if (reverse) pathDataSub = reversePathData(pathDataSub);
4906
+ let pathDataSub = subPathArr[i];
4732
4907
 
4733
4908
  // remove zero length linetos
4734
- if (removeColinear) pathDataSub = removeZeroLengthLinetos(pathDataSub);
4909
+ if (removeColinear || removeZeroLength) pathDataSub = removeZeroLengthLinetos(pathDataSub);
4735
4910
 
4736
4911
  // add extremes
4737
4912
 
@@ -4741,56 +4916,63 @@ function svgPathSimplify(input = '', {
4741
4916
  // sort to top left
4742
4917
  if (optimizeOrder) pathDataSub = pathDataToTopLeft(pathDataSub);
4743
4918
 
4744
- // remove colinear/flat
4745
- if (removeColinear) pathDataSub = pathDataRemoveColinear(pathDataSub, tolerance, flatBezierToLinetos);
4919
+ // Preprocessing: remove colinear - ignore flat beziers (removed later)
4920
+ if (removeColinear) pathDataSub = pathDataRemoveColinear(pathDataSub, { tolerance, flatBezierToLinetos: false });
4746
4921
 
4747
4922
  // analyze pathdata to add info about signicant properties such as extremes, corners
4748
4923
  let pathDataPlus = analyzePathData(pathDataSub);
4749
4924
 
4750
4925
  // simplify beziers
4751
4926
  let { pathData, bb, dimA } = pathDataPlus;
4752
-
4753
4927
  pathData = simplifyBezier ? simplifyPathDataCubic(pathData, { simplifyBezier, keepInflections, keepExtremes, keepCorners, extrapolateDominant, revertToQuadratics, tolerance, reverse }) : pathData;
4754
4928
 
4755
4929
  // refine extremes
4756
- if(refineExtremes){
4930
+ if (refineExtremes) {
4757
4931
  let thresholdEx = (bb.width + bb.height) / 2 * 0.05;
4758
- pathData = refineAdjacentExtremes(pathData, {threshold:thresholdEx, tolerance});
4932
+ pathData = refineAdjacentExtremes(pathData, { threshold: thresholdEx, tolerance });
4759
4933
  }
4760
4934
 
4761
4935
  // cubic to arcs
4762
4936
  if (cubicToArc) {
4763
4937
 
4764
- let thresh = 3;
4938
+ let thresh = 1;
4765
4939
 
4766
- pathData.forEach((com, c) => {
4940
+ for(let c=0, l=pathData.length; c<l; c++){
4941
+ let com = pathData[c];
4767
4942
  let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
4768
4943
  if (type === 'C') {
4769
4944
 
4770
4945
  let comA = cubicCommandToArc(p0, cp1, cp2, p, thresh);
4771
4946
  if (comA.isArc) pathData[c] = comA.com;
4772
-
4773
4947
  }
4774
- });
4948
+ }
4775
4949
 
4776
4950
  // combine adjacent cubics
4777
4951
  pathData = combineArcs(pathData);
4778
4952
 
4779
4953
  }
4780
4954
 
4955
+ // post processing: remove flat beziers
4956
+ if (removeColinear && flatBezierToLinetos) {
4957
+ pathData = pathDataRemoveColinear(pathData, { tolerance, flatBezierToLinetos });
4958
+ }
4959
+
4960
+ // refine corners
4961
+ if(refineCorners){
4962
+ let threshold = (bb.width + bb.height) / 2 * 0.1;
4963
+ pathData = refineRoundedCorners(pathData, { threshold, tolerance });
4964
+
4965
+ }
4966
+
4781
4967
  // simplify to quadratics
4782
4968
  if (revertToQuadratics) {
4783
- pathData.forEach((com, c) => {
4969
+ for(let c=0, l=pathData.length; c<l; c++){
4970
+ let com = pathData[c];
4784
4971
  let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
4785
4972
  if (type === 'C') {
4786
4973
 
4787
4974
  let comQ = revertCubicQuadratic(p0, cp1, cp2, p);
4788
4975
  if (comQ.type === 'Q') {
4789
- /*
4790
- comQ.p0 = com.p0
4791
- comQ.cp1 = {x:comQ.values[0], y:comQ.values[1]}
4792
- comQ.p = com.p
4793
- */
4794
4976
  comQ.extreme = com.extreme;
4795
4977
  comQ.corner = com.corner;
4796
4978
  comQ.dimA = com.dimA;
@@ -4798,20 +4980,25 @@ function svgPathSimplify(input = '', {
4798
4980
  pathData[c] = comQ;
4799
4981
  }
4800
4982
  }
4801
- });
4983
+ }
4802
4984
  }
4803
4985
 
4804
4986
  // optimize close path
4805
4987
  if (optimizeOrder) pathData = optimizeClosePath(pathData);
4806
4988
 
4807
- // poly
4808
-
4809
4989
  // update
4810
- pathDataArrN.push(pathData);
4990
+ pathDataFlat.push(...pathData);
4991
+
4811
4992
  }
4812
4993
 
4813
4994
  // flatten compound paths
4814
- pathData = pathDataArrN.flat();
4995
+ pathData = pathDataFlat;
4996
+
4997
+ if (autoAccuracy) {
4998
+ decimals = detectAccuracy(pathData);
4999
+ pathOptions.decimals = decimals;
5000
+
5001
+ }
4815
5002
 
4816
5003
  // collect for merged svg paths
4817
5004
  if (el && mergePaths) {
@@ -4820,15 +5007,6 @@ function svgPathSimplify(input = '', {
4820
5007
  // single output
4821
5008
  else {
4822
5009
 
4823
- /**
4824
- * detect accuracy
4825
- */
4826
- if (autoAccuracy) {
4827
- decimals = detectAccuracy(pathData);
4828
- pathOptions.decimals = decimals;
4829
-
4830
- }
4831
-
4832
5010
  // optimize path data
4833
5011
  pathData = convertPathData(pathData, pathOptions);
4834
5012
 
@@ -4839,7 +5017,9 @@ function svgPathSimplify(input = '', {
4839
5017
  let comCountS = pathData.length;
4840
5018
 
4841
5019
  let dOpt = pathDataToD(pathData, minifyD);
4842
- svgSizeOpt = new Blob([dOpt]).size;
5020
+
5021
+ svgSizeOpt = dOpt.length;
5022
+
4843
5023
  compression = +(100 / svgSize * (svgSizeOpt)).toFixed(2);
4844
5024
 
4845
5025
  path.d = dOpt;
@@ -4855,19 +5035,18 @@ function svgPathSimplify(input = '', {
4855
5035
  // apply new path for svgs
4856
5036
  if (el) el.setAttribute('d', dOpt);
4857
5037
  }
4858
- });
4859
-
5038
+ }
4860
5039
  /**
4861
5040
  * stringify new SVG
4862
5041
  */
4863
5042
  if (mode) {
4864
5043
 
4865
5044
  if (pathData_merged.length) {
5045
+
4866
5046
  // optimize path data
4867
5047
  let pathData = convertPathData(pathData_merged, pathOptions);
4868
5048
 
4869
5049
  // remove zero-length segments introduced by rounding
4870
-
4871
5050
  pathData = removeZeroLengthLinetos(pathData);
4872
5051
 
4873
5052
  let dOpt = pathDataToD(pathData, minifyD);
@@ -4886,7 +5065,8 @@ function svgPathSimplify(input = '', {
4886
5065
  }
4887
5066
 
4888
5067
  svg = stringifySVG(svg);
4889
- svgSizeOpt = new Blob([svg]).size;
5068
+
5069
+ svgSizeOpt = svg.length;
4890
5070
 
4891
5071
  compression = +(100 / svgSize * (svgSizeOpt)).toFixed(2);
4892
5072
 
@@ -4907,104 +5087,6 @@ function svgPathSimplify(input = '', {
4907
5087
 
4908
5088
  }
4909
5089
 
4910
- function simplifyPathDataCubic(pathData, {
4911
- keepExtremes = true,
4912
- keepInflections = true,
4913
- keepCorners = true,
4914
- extrapolateDominant = true,
4915
- tolerance = 1,
4916
- reverse = false
4917
- } = {}) {
4918
-
4919
- let pathDataN = [pathData[0]];
4920
-
4921
- for (let i = 2, l = pathData.length; l && i <= l; i++) {
4922
- let com = pathData[i - 1];
4923
- let comN = i < l ? pathData[i] : null;
4924
- let typeN = comN?.type || null;
4925
-
4926
- let isDirChange = com?.directionChange || null;
4927
- let isDirChangeN = comN?.directionChange || null;
4928
-
4929
- let { type, values, p0, p, cp1 = null, cp2 = null, extreme = false, corner = false, dimA = 0 } = com;
4930
-
4931
- // next is also cubic
4932
- if (type === 'C' && typeN === 'C') {
4933
-
4934
- // cannot be combined as crossing extremes or corners
4935
- if (
4936
- (keepInflections && isDirChangeN) ||
4937
- (keepCorners && corner) ||
4938
- (!isDirChange && keepExtremes && extreme)
4939
- ) {
4940
-
4941
- pathDataN.push(com);
4942
- }
4943
-
4944
- // try simplification
4945
- else {
4946
-
4947
- let combined = combineCubicPairs(com, comN, extrapolateDominant, tolerance);
4948
- let error = 0;
4949
-
4950
- // combining successful! try next segment
4951
- if (combined.length === 1) {
4952
- com = combined[0];
4953
- let offset = 1;
4954
-
4955
- // add cumulative error to prevent distortions
4956
- error += com.error;
4957
-
4958
- // find next candidates
4959
- for (let n = i + 1; error < tolerance && n < l; n++) {
4960
- let comN = pathData[n];
4961
- if (comN.type !== 'C' ||
4962
- (
4963
- (keepInflections && comN.directionChange) ||
4964
- (keepCorners && com.corner) ||
4965
- (keepExtremes && com.extreme)
4966
- )
4967
- ) {
4968
- break
4969
- }
4970
-
4971
- let combined = combineCubicPairs(com, comN, extrapolateDominant, tolerance);
4972
- if (combined.length === 1) {
4973
- // add cumulative error to prevent distortions
4974
-
4975
- error += combined[0].error * 0.5;
4976
-
4977
- offset++;
4978
- }
4979
- com = combined[0];
4980
- }
4981
-
4982
- pathDataN.push(com);
4983
-
4984
- if (i < l) {
4985
- i += offset;
4986
- }
4987
-
4988
- } else {
4989
- pathDataN.push(com);
4990
- }
4991
- }
4992
-
4993
- } // end of bezier command
4994
-
4995
- // other commands
4996
- else {
4997
- pathDataN.push(com);
4998
- }
4999
-
5000
- } // end command loop
5001
-
5002
- // reverse back
5003
- if (reverse) pathDataN = reversePathData(pathDataN);
5004
-
5005
- return pathDataN
5006
- }
5007
-
5008
5090
  const {
5009
5091
  abs, acos, asin, atan, atan2, ceil, cos, exp, floor,
5010
5092
  log, hypot, max, min, pow, random, round, sin, sqrt, tan, PI