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
|
@@ -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))
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
786
|
-
|
|
792
|
+
if (com.type === 'M' || com.type === 'm') {
|
|
793
|
+
subPathArr.push(current);
|
|
794
|
+
current = [];
|
|
795
|
+
}
|
|
796
|
+
current.push(com);
|
|
787
797
|
}
|
|
788
798
|
|
|
789
|
-
|
|
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'
|
|
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
|
|
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.
|
|
1877
|
+
let distScale = 0.06;
|
|
1773
1878
|
let maxDist = distMin * distScale * tolerance;
|
|
1774
1879
|
|
|
1775
|
-
|
|
1880
|
+
// get hypothetical combined command
|
|
1881
|
+
let comS = getExtrapolatedCommand(com1, com2, t);
|
|
1776
1882
|
|
|
1777
|
-
// test
|
|
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,
|
|
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
|
-
|
|
1891
|
-
x: (cp1.x - (1 -
|
|
1892
|
-
y: (cp1.y - (1 -
|
|
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
|
-
|
|
1896
|
-
x: (cp2.x -
|
|
1897
|
-
y: (cp2.y -
|
|
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
|
-
|
|
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
|
|
1917
|
-
|
|
1918
|
-
|
|
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
|
-
|
|
1998
|
+
let isFlat=false;
|
|
1999
|
+
let report = {
|
|
2000
|
+
flat:true,
|
|
2001
|
+
steepness:0
|
|
2002
|
+
};
|
|
1936
2003
|
|
|
1937
|
-
let
|
|
1938
|
-
let
|
|
1939
|
-
let cross0 = Math.abs(dx0 * dy1 - dy0 * dx1);
|
|
2004
|
+
let p0 = points[0];
|
|
2005
|
+
let p = points[points.length - 1];
|
|
1940
2006
|
|
|
1941
|
-
|
|
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
|
-
|
|
1944
|
-
|
|
1945
|
-
let thresh = dist1/200;
|
|
2010
|
+
// must be flat
|
|
2011
|
+
if(xSet.size===1 || ySet.size===1) return !debug ? true : report;
|
|
1946
2012
|
|
|
1947
|
-
|
|
1948
|
-
|
|
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
|
-
|
|
2020
|
+
if(debug){
|
|
2021
|
+
report.flat = isFlat;
|
|
1953
2022
|
|
|
1954
|
-
|
|
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 =
|
|
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.
|
|
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 =
|
|
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
|
-
|
|
2245
|
-
let com = pathData[c];
|
|
2310
|
+
let len = pathData.length;
|
|
2246
2311
|
|
|
2247
|
-
|
|
2248
|
-
decimals = hasDecimal ? com.decimals : decimals;
|
|
2312
|
+
for (let c = 0; c < len; c++) {
|
|
2249
2313
|
|
|
2250
|
-
|
|
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
|
-
}
|
|
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 =
|
|
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 ?
|
|
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
|
-
|
|
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
|
|
2701
|
+
for (let i = 1; i < len; i++) {
|
|
2630
2702
|
|
|
2631
2703
|
let com = pathData[i];
|
|
2632
2704
|
let { type, values } = com;
|
|
2633
|
-
let
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
3253
|
-
let
|
|
3254
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: [
|
|
3840
|
-
{ type: "A", values: [
|
|
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,
|
|
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
|
-
|
|
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 /
|
|
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 =
|
|
3915
|
-
|
|
3916
|
-
// console.log();
|
|
4015
|
+
isFlatBez = commandIsFlat([p0, ...cpts, p],{tolerance});
|
|
3917
4016
|
|
|
3918
|
-
if (isFlatBez && c < l - 1
|
|
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
|
-
|
|
3968
|
-
|
|
4076
|
+
if(comN) i++;
|
|
4077
|
+
continue
|
|
3969
4078
|
}
|
|
3970
4079
|
}
|
|
4080
|
+
pathDataN.push(com);
|
|
3971
4081
|
}
|
|
3972
4082
|
|
|
3973
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
4255
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
4331
|
-
let
|
|
4332
|
-
let
|
|
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
|
-
//
|
|
4337
|
-
let
|
|
4338
|
-
let
|
|
4372
|
+
// check dist
|
|
4373
|
+
let diff = comN ? getDistAv(p, comN.p) : Infinity;
|
|
4374
|
+
let isCose = diff < threshold;
|
|
4339
4375
|
|
|
4340
|
-
|
|
4341
|
-
let
|
|
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
|
-
|
|
4350
|
-
|
|
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
|
-
|
|
4358
|
-
}
|
|
4382
|
+
if (isCose2 || isCose) {
|
|
4359
4383
|
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
} = {}) {
|
|
4384
|
+
// extrapolate
|
|
4385
|
+
let comEx = getCombinedByDominant(comN, comN2, threshold, tolerance, false);
|
|
4363
4386
|
|
|
4364
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4399
|
+
i++;
|
|
4400
|
+
continue
|
|
4401
|
+
}
|
|
4402
|
+
}
|
|
4376
4403
|
|
|
4377
|
-
|
|
4404
|
+
}
|
|
4378
4405
|
|
|
4379
|
-
|
|
4406
|
+
// short after extreme
|
|
4380
4407
|
|
|
4381
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
4888
|
+
let pathData = parsePathDataNormalized(d, { quadraticToCubic, toAbsolute, arcToCubic });
|
|
4710
4889
|
|
|
4711
4890
|
// count commands for evaluation
|
|
4712
|
-
let comCount =
|
|
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
|
-
|
|
4903
|
+
// reset array
|
|
4904
|
+
let pathDataFlat = [];
|
|
4729
4905
|
|
|
4730
|
-
|
|
4906
|
+
for (let i = 0; i < lenSub; i++) {
|
|
4731
4907
|
|
|
4732
|
-
|
|
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
|
|
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 =
|
|
4940
|
+
let thresh = 1;
|
|
4767
4941
|
|
|
4768
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4992
|
+
pathDataFlat.push(...pathData);
|
|
4993
|
+
|
|
4813
4994
|
}
|
|
4814
4995
|
|
|
4815
4996
|
// flatten compound paths
|
|
4816
|
-
pathData =
|
|
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
|
-
|
|
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
|
-
|
|
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
|