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