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