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