svg-path-simplify 0.4.4 → 0.4.6
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/CHANGELOG.md +19 -0
- package/README.md +9 -1
- package/dist/svg-path-simplify.esm.js +1649 -1205
- package/dist/svg-path-simplify.esm.min.js +2 -2
- package/dist/svg-path-simplify.js +1649 -1204
- package/dist/svg-path-simplify.min.js +2 -2
- package/dist/svg-path-simplify.pathdata.esm.js +301 -637
- package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
- package/dist/svg-path-simplify.poly.cjs +8 -9
- package/drawing.svg +62 -0
- package/index.html +13 -8
- package/package.json +1 -1
- package/src/index.js +3 -1
- package/src/pathData_get_intersections.js +777 -0
- package/src/pathData_offset.js +201 -0
- package/src/pathData_simplify_cubic.js +19 -21
- package/src/pathData_simplify_cubic_extrapolate.js +147 -8
- package/src/pathData_simplify_cubicsToArcs.js +65 -21
- package/src/pathSimplify-main.js +267 -64
- package/src/pathSimplify-presets.js +1 -1
- package/src/poly-fit-curve-schneider.js +171 -132
- package/src/poly-fit-curve-schneider_check_bulge.js +131 -0
- package/src/poly-offset.js +133 -0
- package/src/simplify_poly_RC.js +29 -7
- package/src/svg-getAttributes.js +1 -1
- package/src/svgii/geometry.js +130 -27
- package/src/svgii/geometry_bbox.js +1 -1
- package/src/svgii/geometry_length.js +1 -1
- package/src/svgii/pathData_analyze.js +6 -28
- package/src/svgii/pathData_convert.js +63 -15
- package/src/svgii/pathData_remove_collinear.js +32 -36
- package/src/svgii/pathData_reorder.js +3 -2
- package/src/svgii/pathData_simplify_refineExtremes.js +1 -3
- package/src/svgii/pathData_stringify.js +132 -6
- package/src/svgii/pathData_toPolygon.js +33 -23
- package/src/svgii/poly_analyze.js +272 -125
- package/src/svgii/poly_analyze_cleanup.js +311 -0
- package/src/svgii/poly_analyze_getTangents.js +125 -0
- package/src/svgii/poly_analyze_get_chunks.js +3 -1
- package/src/svgii/poly_normalize.js +21 -8
- package/src/svgii/poly_to_pathdata.js +477 -8
- package/src/svgii/rounding.js +35 -14
- package/src/svgii/svg_cleanup.js +44 -51
- package/src/svgii/svg_cleanup_convertPathLength.js +5 -0
- package/src/svgii/svg_cleanup_normalize_transforms.js +2 -2
- package/src/svgii/visualize.js +33 -2
- package/v/0.4.5/index.html +1040 -0
- package/v/0.4.5/svg-path-simplify.js +13227 -0
|
@@ -363,7 +363,7 @@ const {
|
|
|
363
363
|
|
|
364
364
|
// get angle helper
|
|
365
365
|
function getAngle(p1, p2, normalize = false) {
|
|
366
|
-
let angle = atan2(p2.y - p1.y, p2.x - p1.x);
|
|
366
|
+
let angle = Math.atan2(p2.y - p1.y, p2.x - p1.x);
|
|
367
367
|
// normalize negative angles
|
|
368
368
|
if (normalize && angle < 0) angle += Math.PI * 2;
|
|
369
369
|
return angle
|
|
@@ -459,7 +459,9 @@ function checkLineIntersection(p1 = null, p2 = null, p3 = null, p4 = null, exact
|
|
|
459
459
|
// if we cast these lines infinitely in both directions, they intersect here:
|
|
460
460
|
intersectionPoint = {
|
|
461
461
|
x: p1.x + (a * (p2.x - p1.x)),
|
|
462
|
-
y: p1.y + (a * (p2.y - p1.y))
|
|
462
|
+
y: p1.y + (a * (p2.y - p1.y)),
|
|
463
|
+
t1: a,
|
|
464
|
+
t2: b
|
|
463
465
|
};
|
|
464
466
|
|
|
465
467
|
let intersection = false;
|
|
@@ -523,13 +525,13 @@ function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, returnArray
|
|
|
523
525
|
if (t === 0 && !shortCp1) {
|
|
524
526
|
pt.x = p0.x;
|
|
525
527
|
pt.y = p0.y;
|
|
526
|
-
pt.angle = getAngle(p0, cp1);
|
|
528
|
+
if (getTangent) pt.angle = getAngle(p0, cp1);
|
|
527
529
|
}
|
|
528
530
|
|
|
529
531
|
else if (t === 1 && !shortCp2) {
|
|
530
532
|
pt.x = p.x;
|
|
531
533
|
pt.y = p.y;
|
|
532
|
-
pt.angle = getAngle(cp2, p);
|
|
534
|
+
if (getTangent) pt.angle = getAngle(cp2, p);
|
|
533
535
|
}
|
|
534
536
|
|
|
535
537
|
else {
|
|
@@ -546,18 +548,38 @@ function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, returnArray
|
|
|
546
548
|
pt = interpolate(m3, m4, t);
|
|
547
549
|
|
|
548
550
|
// add angles
|
|
549
|
-
pt.angle = getAngle(m3, m4);
|
|
551
|
+
if (getTangent) pt.angle = getAngle(m3, m4);
|
|
552
|
+
|
|
553
|
+
/*
|
|
554
|
+
renderPoint(markers, m0, 'cyan')
|
|
555
|
+
renderPoint(markers, m3, 'magenta')
|
|
556
|
+
renderPoint(markers, pt, 'green')
|
|
557
|
+
renderPoint(markers, m4, 'cyan')
|
|
558
|
+
renderPoint(markers, m2, 'magenta')
|
|
559
|
+
*/
|
|
550
560
|
|
|
551
561
|
// add control points
|
|
552
|
-
if (getCpts)
|
|
562
|
+
if (getCpts) {
|
|
563
|
+
pt.cpts = [m1, m2, m3, m4];
|
|
564
|
+
pt.segments = [
|
|
565
|
+
{ p0, cp1: m0, cp2: m3, p: pt },
|
|
566
|
+
{ p0: pt, cp1: m4, cp2: m2, p }
|
|
567
|
+
];
|
|
568
|
+
}
|
|
553
569
|
} else {
|
|
554
570
|
m1 = interpolate(p0, cp1, t);
|
|
555
571
|
m2 = interpolate(cp1, p, t);
|
|
556
572
|
pt = interpolate(m1, m2, t);
|
|
557
|
-
pt.angle = getAngle(m1, m2);
|
|
573
|
+
if (getTangent) pt.angle = getAngle(m1, m2);
|
|
558
574
|
|
|
559
575
|
// add control points
|
|
560
|
-
if (getCpts)
|
|
576
|
+
if (getCpts) {
|
|
577
|
+
pt.cpts = [m1, m2];
|
|
578
|
+
pt.segments = [
|
|
579
|
+
{ p0, cp1: m1, p: pt },
|
|
580
|
+
{ p0: p, cp1: m2, p }
|
|
581
|
+
];
|
|
582
|
+
}
|
|
561
583
|
}
|
|
562
584
|
}
|
|
563
585
|
|
|
@@ -641,7 +663,7 @@ function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, returnArray
|
|
|
641
663
|
* get vertices from path command final on-path points
|
|
642
664
|
*/
|
|
643
665
|
|
|
644
|
-
function getPathDataVertices(pathData=[], includeCpts = false, decimals = -1) {
|
|
666
|
+
function getPathDataVertices(pathData = [], includeCpts = false, decimals = -1) {
|
|
645
667
|
let polyPoints = [];
|
|
646
668
|
|
|
647
669
|
pathData.forEach((com) => {
|
|
@@ -728,7 +750,8 @@ function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2,
|
|
|
728
750
|
|
|
729
751
|
if (rx == 0 || ry == 0) {
|
|
730
752
|
// invalid arguments
|
|
731
|
-
|
|
753
|
+
console.warn("rx and ry can not be 0");
|
|
754
|
+
return arcData
|
|
732
755
|
}
|
|
733
756
|
|
|
734
757
|
/**
|
|
@@ -741,10 +764,10 @@ function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2,
|
|
|
741
764
|
let s_phi = !phi ? 0 : Math.sin(phi);
|
|
742
765
|
let c_phi = !phi ? 1 : Math.cos(phi);
|
|
743
766
|
|
|
744
|
-
let hd_x = (x1 - x2)
|
|
745
|
-
let hd_y = (y1 - y2)
|
|
746
|
-
let hs_x = (x1 + x2)
|
|
747
|
-
let hs_y = (y1 + y2)
|
|
767
|
+
let hd_x = (x1 - x2) * 0.5;
|
|
768
|
+
let hd_y = (y1 - y2) * 0.5;
|
|
769
|
+
let hs_x = (x1 + x2) * 0.5;
|
|
770
|
+
let hs_y = (y1 + y2) * 0.5;
|
|
748
771
|
|
|
749
772
|
// F6.5.1
|
|
750
773
|
let x1_ = !phi ? hd_x : c_phi * hd_x + s_phi * hd_y;
|
|
@@ -766,10 +789,11 @@ function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2,
|
|
|
766
789
|
let rxry = rx * ry;
|
|
767
790
|
let rxy1_ = rx * y1_;
|
|
768
791
|
let ryx1_ = ry * x1_;
|
|
769
|
-
let sum_of_sq = rxy1_
|
|
792
|
+
let sum_of_sq = rxy1_ * rxy1_ + ryx1_ * ryx1_; // sum of square
|
|
770
793
|
if (!sum_of_sq) {
|
|
794
|
+
console.warn("start point can not be same as end point");
|
|
795
|
+
return arcData
|
|
771
796
|
|
|
772
|
-
throw Error("start point can not be same as end point");
|
|
773
797
|
}
|
|
774
798
|
let coe = Math.sqrt(Math.abs((rxry * rxry - sum_of_sq) / sum_of_sq));
|
|
775
799
|
if (largeArc === sweep) {
|
|
@@ -834,39 +858,6 @@ function rotatePoint(pt, cx, cy, rotation = 0, convertToRadians = false) {
|
|
|
834
858
|
};
|
|
835
859
|
}
|
|
836
860
|
|
|
837
|
-
function getPointOnEllipse(cx, cy, rx, ry, angle, ellipseRotation = 0, parametricAngle = true, degrees = false) {
|
|
838
|
-
|
|
839
|
-
// Convert degrees to radians
|
|
840
|
-
angle = degrees ? (angle * PI) / 180 : angle;
|
|
841
|
-
ellipseRotation = degrees ? (ellipseRotation * PI) / 180 : ellipseRotation;
|
|
842
|
-
// reset rotation for circles or 360 degree
|
|
843
|
-
ellipseRotation = rx !== ry ? (ellipseRotation !== PI * 2 ? ellipseRotation : 0) : 0;
|
|
844
|
-
|
|
845
|
-
// is ellipse
|
|
846
|
-
if (parametricAngle && rx !== ry) {
|
|
847
|
-
// adjust angle for ellipse rotation
|
|
848
|
-
angle = ellipseRotation ? angle - ellipseRotation : angle;
|
|
849
|
-
// Get the parametric angle for the ellipse
|
|
850
|
-
let angleParametric = atan(tan(angle) * (rx / ry));
|
|
851
|
-
// Ensure the parametric angle is in the correct quadrant
|
|
852
|
-
angle = cos(angle) < 0 ? angleParametric + PI : angleParametric;
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
// Calculate the point on the ellipse without rotation
|
|
856
|
-
let x = cx + rx * cos(angle),
|
|
857
|
-
y = cy + ry * sin(angle);
|
|
858
|
-
let pt = {
|
|
859
|
-
x: x,
|
|
860
|
-
y: y
|
|
861
|
-
};
|
|
862
|
-
|
|
863
|
-
if (ellipseRotation) {
|
|
864
|
-
pt.x = cx + (x - cx) * cos(ellipseRotation) - (y - cy) * sin(ellipseRotation);
|
|
865
|
-
pt.y = cy + (x - cx) * sin(ellipseRotation) + (y - cy) * cos(ellipseRotation);
|
|
866
|
-
}
|
|
867
|
-
return pt
|
|
868
|
-
}
|
|
869
|
-
|
|
870
861
|
// to parametric angle helper
|
|
871
862
|
function toParametricAngle(angle, rx, ry) {
|
|
872
863
|
|
|
@@ -918,7 +909,11 @@ function bezierhasExtreme(p0 = null, cpts = []) {
|
|
|
918
909
|
|
|
919
910
|
function getBezierExtremeT(pts, { addExtremes = true, addSemiExtremes = false } = {}) {
|
|
920
911
|
let tArr = pts.length === 4 ? cubicBezierExtremeT(pts[0], pts[1], pts[2], pts[3], { addExtremes, addSemiExtremes }) : quadraticBezierExtremeT(pts[0], pts[1], pts[2], { addExtremes, addSemiExtremes });
|
|
921
|
-
|
|
912
|
+
if (tArr.length) {
|
|
913
|
+
tArr = tArr.map(t => +t.toFixed(9)).sort();
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
return tArr
|
|
922
917
|
}
|
|
923
918
|
|
|
924
919
|
function getArcExtemes(p0, values) {
|
|
@@ -1248,13 +1243,6 @@ function cubicBezierExtremeT(p0, cp1, cp2, p,
|
|
|
1248
1243
|
return tArr;
|
|
1249
1244
|
}
|
|
1250
1245
|
|
|
1251
|
-
function isMultipleOf45(angleRad, epsilon = 0.001) {
|
|
1252
|
-
let rad90 = Math.PI / 2;
|
|
1253
|
-
let rad45 = rad90 / 2;
|
|
1254
|
-
let isRightAngle = Math.abs(angleRad / rad90 - Math.round(angleRad / rad90)) < epsilon;
|
|
1255
|
-
return !isRightAngle ? Math.abs(angleRad / rad45 - Math.round(angleRad / rad45)) < epsilon : false;
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
1246
|
function quadraticBezierExtremeT(p0, cp1, p, { addExtremes = true, addSemiExtremes = false } = {}) {
|
|
1259
1247
|
|
|
1260
1248
|
// rotate cpts for semi extremes
|
|
@@ -1318,6 +1306,11 @@ function getDistance(p1, p2, isArray = false) {
|
|
|
1318
1306
|
let dx = isArray ? p2[0] - p1[0] : (p2.x - p1.x);
|
|
1319
1307
|
let dy = isArray ? p2[1] - p1[1] : (p2.y - p1.y);
|
|
1320
1308
|
|
|
1309
|
+
/*
|
|
1310
|
+
let sqrt2 = 1.4142135623730951
|
|
1311
|
+
return dx===dy ? Math.abs(dx) * sqrt2 : Math.sqrt(dx * dx + dy * dy);
|
|
1312
|
+
*/
|
|
1313
|
+
|
|
1321
1314
|
return Math.sqrt(dx * dx + dy * dy);
|
|
1322
1315
|
}
|
|
1323
1316
|
|
|
@@ -1333,6 +1326,7 @@ function getSquareDistance(p1, p2) {
|
|
|
1333
1326
|
* sloppy but fast
|
|
1334
1327
|
*/
|
|
1335
1328
|
function getDistManhattan(pt1, pt2) {
|
|
1329
|
+
|
|
1336
1330
|
let dx = Math.abs(pt2.x - pt1.x);
|
|
1337
1331
|
let dy = Math.abs(pt2.y - pt1.y);
|
|
1338
1332
|
return dx + dy;
|
|
@@ -1657,7 +1651,7 @@ function roundPathData(pathData, decimalsGlobal = -1) {
|
|
|
1657
1651
|
|
|
1658
1652
|
let len = pathData.length;
|
|
1659
1653
|
let decimals = decimalsGlobal;
|
|
1660
|
-
let decimalsArc = decimals < 3 ? decimals+
|
|
1654
|
+
let decimalsArc = decimals < 3 ? decimals + 1 : decimals;
|
|
1661
1655
|
|
|
1662
1656
|
for (let c = 0; c < len; c++) {
|
|
1663
1657
|
let com = pathData[c];
|
|
@@ -1684,25 +1678,30 @@ function detectAccuracy(pathData) {
|
|
|
1684
1678
|
let com = pathData[i];
|
|
1685
1679
|
let { type, values, p0 = null, p = null, dimA = 0 } = com;
|
|
1686
1680
|
|
|
1687
|
-
// use existing
|
|
1681
|
+
// use existing average dimension value or calculate
|
|
1688
1682
|
if (values.length && p && p0) {
|
|
1689
|
-
|
|
1690
1683
|
dimA = dimA ? dimA : getDistManhattan(p0, p);
|
|
1691
1684
|
|
|
1692
|
-
if (
|
|
1685
|
+
if (type === 'A') dimA *= 0.5;
|
|
1693
1686
|
|
|
1687
|
+
if (dimA) dims.push(+dimA.toFixed(8));
|
|
1694
1688
|
}
|
|
1695
1689
|
|
|
1696
1690
|
}
|
|
1697
1691
|
|
|
1698
|
-
|
|
1692
|
+
dims = dims.sort((a,b)=>a-b);
|
|
1693
|
+
let len = dims.length;
|
|
1694
|
+
let dim_mid = dims[Math.floor(len * 0.5)];
|
|
1699
1695
|
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
let
|
|
1696
|
+
// smallest 25% of values
|
|
1697
|
+
let idx_q = Math.ceil(len * 0.25);
|
|
1698
|
+
let dims_min = dims.slice(0, idx_q);
|
|
1699
|
+
|
|
1700
|
+
// average smallest values with mid value
|
|
1701
|
+
let dim_min = ((dims_min.reduce((a, b) => a + b, 0) / idx_q) + dim_mid) * 0.5;
|
|
1703
1702
|
|
|
1704
1703
|
let threshold = 75;
|
|
1705
|
-
let decimalsAuto =
|
|
1704
|
+
let decimalsAuto = dim_min > threshold * 1.5 ? 0 : Math.floor(threshold / dim_min).toString().length;
|
|
1706
1705
|
|
|
1707
1706
|
// clamp
|
|
1708
1707
|
return Math.min(Math.max(0, decimalsAuto), 8)
|
|
@@ -1766,117 +1765,6 @@ function getPolyBBox(vertices, decimals = -1) {
|
|
|
1766
1765
|
return bb;
|
|
1767
1766
|
}
|
|
1768
1767
|
|
|
1769
|
-
function getSubPathBBoxes(subPaths) {
|
|
1770
|
-
let bboxArr = [];
|
|
1771
|
-
subPaths.forEach((pathData) => {
|
|
1772
|
-
|
|
1773
|
-
let bb = getPathDataBBox_sloppy(pathData);
|
|
1774
|
-
bboxArr.push(bb);
|
|
1775
|
-
});
|
|
1776
|
-
|
|
1777
|
-
return bboxArr;
|
|
1778
|
-
}
|
|
1779
|
-
|
|
1780
|
-
function checkBBoxIntersections(bb, bb1) {
|
|
1781
|
-
let [x, y, width, height, right, bottom] = [
|
|
1782
|
-
bb.x,
|
|
1783
|
-
bb.y,
|
|
1784
|
-
bb.width,
|
|
1785
|
-
bb.height,
|
|
1786
|
-
bb.x + bb.width,
|
|
1787
|
-
bb.y + bb.height
|
|
1788
|
-
];
|
|
1789
|
-
let [x1, y1, width1, height1, right1, bottom1] = [
|
|
1790
|
-
bb1.x,
|
|
1791
|
-
bb1.y,
|
|
1792
|
-
bb1.width,
|
|
1793
|
-
bb1.height,
|
|
1794
|
-
bb1.x + bb1.width,
|
|
1795
|
-
bb1.y + bb1.height
|
|
1796
|
-
];
|
|
1797
|
-
let intersects = false;
|
|
1798
|
-
if (width * height != width1 * height1) {
|
|
1799
|
-
if (width * height > width1 * height1) {
|
|
1800
|
-
if (x < x1 && right > right1 && y < y1 && bottom > bottom1) {
|
|
1801
|
-
intersects = true;
|
|
1802
|
-
}
|
|
1803
|
-
}
|
|
1804
|
-
}
|
|
1805
|
-
return intersects;
|
|
1806
|
-
}
|
|
1807
|
-
|
|
1808
|
-
/**
|
|
1809
|
-
* sloppy path bbox aaproximation
|
|
1810
|
-
*/
|
|
1811
|
-
|
|
1812
|
-
function getPathDataBBox_sloppy(pathData) {
|
|
1813
|
-
let pts = getPathDataPoly(pathData);
|
|
1814
|
-
let bb = getPolyBBox(pts);
|
|
1815
|
-
return bb;
|
|
1816
|
-
}
|
|
1817
|
-
|
|
1818
|
-
/**
|
|
1819
|
-
* get path data poly
|
|
1820
|
-
* including command points
|
|
1821
|
-
* handy for faster/sloppy bbox approximations
|
|
1822
|
-
*/
|
|
1823
|
-
|
|
1824
|
-
function getPathDataPoly(pathData) {
|
|
1825
|
-
|
|
1826
|
-
let poly = [];
|
|
1827
|
-
for (let i = 0; i < pathData.length; i++) {
|
|
1828
|
-
let com = pathData[i];
|
|
1829
|
-
let prev = i > 0 ? pathData[i - 1] : pathData[i];
|
|
1830
|
-
let { type, values } = com;
|
|
1831
|
-
let p0 = { x: prev.values[prev.values.length - 2], y: prev.values[prev.values.length - 1] };
|
|
1832
|
-
let p = values.length ? { x: values[values.length - 2], y: values[values.length - 1] } : '';
|
|
1833
|
-
let cp1 = values.length ? { x: values[0], y: values[1] } : '';
|
|
1834
|
-
|
|
1835
|
-
switch (type) {
|
|
1836
|
-
|
|
1837
|
-
// convert to cubic to get polygon
|
|
1838
|
-
case 'A':
|
|
1839
|
-
|
|
1840
|
-
if (typeof arcToBezier !== 'function') {
|
|
1841
|
-
|
|
1842
|
-
// get real radii
|
|
1843
|
-
let rx = getDistance(p0, p) / 2;
|
|
1844
|
-
let ptMid = interpolate(p0, p, 0.5);
|
|
1845
|
-
|
|
1846
|
-
let pt1 = getPointOnEllipse(ptMid.x, ptMid.y, rx, rx, 0);
|
|
1847
|
-
let pt2 = getPointOnEllipse(ptMid.x, ptMid.y, rx, rx, Math.PI);
|
|
1848
|
-
poly.push(pt1, pt2, p);
|
|
1849
|
-
|
|
1850
|
-
break;
|
|
1851
|
-
}
|
|
1852
|
-
let cubic = arcToBezier(p0, values);
|
|
1853
|
-
cubic.forEach(com => {
|
|
1854
|
-
let vals = com.values;
|
|
1855
|
-
let cp1 = { x: vals[0], y: vals[1] };
|
|
1856
|
-
let cp2 = { x: vals[2], y: vals[3] };
|
|
1857
|
-
let p = { x: vals[4], y: vals[5] };
|
|
1858
|
-
poly.push(cp1, cp2, p);
|
|
1859
|
-
});
|
|
1860
|
-
break;
|
|
1861
|
-
|
|
1862
|
-
case 'C':
|
|
1863
|
-
let cp2 = { x: values[2], y: values[3] };
|
|
1864
|
-
poly.push(cp1, cp2);
|
|
1865
|
-
break;
|
|
1866
|
-
case 'Q':
|
|
1867
|
-
poly.push(cp1);
|
|
1868
|
-
break;
|
|
1869
|
-
}
|
|
1870
|
-
|
|
1871
|
-
// M and L commands
|
|
1872
|
-
if (type.toLowerCase() !== 'z') {
|
|
1873
|
-
poly.push(p);
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
|
-
|
|
1877
|
-
return poly;
|
|
1878
|
-
}
|
|
1879
|
-
|
|
1880
1768
|
/**
|
|
1881
1769
|
* get exact path BBox
|
|
1882
1770
|
* calculating extremes for all command types
|
|
@@ -1940,166 +1828,10 @@ function getPathDataBBox(pathData) {
|
|
|
1940
1828
|
}
|
|
1941
1829
|
}
|
|
1942
1830
|
|
|
1943
|
-
let bbox = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin };
|
|
1831
|
+
let bbox = { x: xMin, y: yMin, right: xMax, width: xMax - xMin, bottom: yMax, height: yMax - yMin };
|
|
1944
1832
|
return bbox
|
|
1945
1833
|
}
|
|
1946
1834
|
|
|
1947
|
-
/**
|
|
1948
|
-
* get pathdata area
|
|
1949
|
-
*/
|
|
1950
|
-
|
|
1951
|
-
function getPathArea(pathData, decimals = 9) {
|
|
1952
|
-
let totalArea = 0;
|
|
1953
|
-
let polyPoints = [];
|
|
1954
|
-
|
|
1955
|
-
let subPathsData = splitSubpaths(pathData);
|
|
1956
|
-
let isCompoundPath = subPathsData.length > 1 ? true : false;
|
|
1957
|
-
let counterShapes = [];
|
|
1958
|
-
|
|
1959
|
-
// check intersections for compund paths
|
|
1960
|
-
if (isCompoundPath) {
|
|
1961
|
-
let bboxArr = getSubPathBBoxes(subPathsData);
|
|
1962
|
-
|
|
1963
|
-
bboxArr.forEach(function (bb, b) {
|
|
1964
|
-
|
|
1965
|
-
for (let i = 0; i < bboxArr.length; i++) {
|
|
1966
|
-
let bb2 = bboxArr[i];
|
|
1967
|
-
if (bb != bb2) {
|
|
1968
|
-
let intersects = checkBBoxIntersections(bb, bb2);
|
|
1969
|
-
if (intersects) {
|
|
1970
|
-
counterShapes.push(i);
|
|
1971
|
-
}
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
|
-
});
|
|
1975
|
-
}
|
|
1976
|
-
|
|
1977
|
-
subPathsData.forEach((pathData, d) => {
|
|
1978
|
-
|
|
1979
|
-
polyPoints = [];
|
|
1980
|
-
let comArea = 0;
|
|
1981
|
-
let pathArea = 0;
|
|
1982
|
-
let multiplier = 1;
|
|
1983
|
-
let pts = [];
|
|
1984
|
-
|
|
1985
|
-
pathData.forEach(function (com, i) {
|
|
1986
|
-
let [type, values] = [com.type, com.values];
|
|
1987
|
-
let valuesL = values.length;
|
|
1988
|
-
|
|
1989
|
-
if (values.length) {
|
|
1990
|
-
let prevC = i > 0 ? pathData[i - 1] : pathData[0];
|
|
1991
|
-
let prevCVals = prevC.values;
|
|
1992
|
-
let prevCValsL = prevCVals.length;
|
|
1993
|
-
let p0 = { x: prevCVals[prevCValsL - 2], y: prevCVals[prevCValsL - 1] };
|
|
1994
|
-
let p = { x: values[valuesL - 2], y: values[valuesL - 1] };
|
|
1995
|
-
|
|
1996
|
-
// C commands
|
|
1997
|
-
if (type === 'C' || type === 'Q') {
|
|
1998
|
-
let cp1 = { x: values[0], y: values[1] };
|
|
1999
|
-
pts = type === 'C' ? [p0, cp1, { x: values[2], y: values[3] }, p] : [p0, cp1, p];
|
|
2000
|
-
let areaBez = Math.abs(getBezierArea(pts));
|
|
2001
|
-
comArea += areaBez;
|
|
2002
|
-
|
|
2003
|
-
polyPoints.push(p0, p);
|
|
2004
|
-
}
|
|
2005
|
-
|
|
2006
|
-
// A commands
|
|
2007
|
-
else if (type === 'A') {
|
|
2008
|
-
let arcData = svgArcToCenterParam(p0.x, p0.y, com.values[0], com.values[1], com.values[2], com.values[3], com.values[4], p.x, p.y);
|
|
2009
|
-
let { cx, cy, rx, ry, startAngle, endAngle, deltaAngle } = arcData;
|
|
2010
|
-
|
|
2011
|
-
let arcArea = Math.abs(getEllipseArea(rx, ry, startAngle, endAngle));
|
|
2012
|
-
|
|
2013
|
-
// subtract remaining polygon between p0, center and p
|
|
2014
|
-
let polyArea = Math.abs(getPolygonArea([p0, { x: cx, y: cy }, p]));
|
|
2015
|
-
arcArea -= polyArea;
|
|
2016
|
-
|
|
2017
|
-
polyPoints.push(p0, p);
|
|
2018
|
-
comArea += arcArea;
|
|
2019
|
-
}
|
|
2020
|
-
|
|
2021
|
-
// L commands
|
|
2022
|
-
else {
|
|
2023
|
-
polyPoints.push(p0, p);
|
|
2024
|
-
}
|
|
2025
|
-
}
|
|
2026
|
-
});
|
|
2027
|
-
|
|
2028
|
-
let areaPoly = getPolygonArea(polyPoints);
|
|
2029
|
-
|
|
2030
|
-
if (counterShapes.indexOf(d) !== -1) {
|
|
2031
|
-
multiplier = -1;
|
|
2032
|
-
}
|
|
2033
|
-
|
|
2034
|
-
if (
|
|
2035
|
-
(areaPoly < 0 && comArea < 0)
|
|
2036
|
-
) {
|
|
2037
|
-
// are negative
|
|
2038
|
-
pathArea = (Math.abs(comArea) - Math.abs(areaPoly)) * multiplier;
|
|
2039
|
-
|
|
2040
|
-
} else {
|
|
2041
|
-
pathArea = (Math.abs(comArea) + Math.abs(areaPoly)) * multiplier;
|
|
2042
|
-
}
|
|
2043
|
-
|
|
2044
|
-
totalArea += pathArea;
|
|
2045
|
-
});
|
|
2046
|
-
|
|
2047
|
-
return totalArea;
|
|
2048
|
-
}
|
|
2049
|
-
|
|
2050
|
-
/**
|
|
2051
|
-
* get ellipse area
|
|
2052
|
-
* skips to circle calculation if rx===ry
|
|
2053
|
-
*/
|
|
2054
|
-
|
|
2055
|
-
function getEllipseArea(rx, ry, startAngle, endAngle) {
|
|
2056
|
-
const totalArea = Math.PI * rx * ry;
|
|
2057
|
-
let angleDiff = (endAngle - startAngle + 2 * Math.PI) % (2 * Math.PI);
|
|
2058
|
-
// If circle, use simple circular formula
|
|
2059
|
-
if (rx === ry) return totalArea * (angleDiff / (2 * Math.PI));
|
|
2060
|
-
|
|
2061
|
-
// Convert absolute angles to parametric angles
|
|
2062
|
-
const absoluteToParametric = (phi)=>{
|
|
2063
|
-
return Math.atan2(rx * Math.sin(phi), ry * Math.cos(phi));
|
|
2064
|
-
};
|
|
2065
|
-
startAngle = absoluteToParametric(startAngle);
|
|
2066
|
-
endAngle = absoluteToParametric(endAngle);
|
|
2067
|
-
angleDiff = (endAngle - startAngle + 2 * Math.PI) % (2 * Math.PI);
|
|
2068
|
-
return totalArea * (angleDiff / (2 * Math.PI));
|
|
2069
|
-
}
|
|
2070
|
-
|
|
2071
|
-
/**
|
|
2072
|
-
* get bezier area
|
|
2073
|
-
*/
|
|
2074
|
-
function getBezierArea(pts, absolute=false) {
|
|
2075
|
-
|
|
2076
|
-
let [p0, cp1, cp2, p] = [pts[0], pts[1], pts[2], pts[pts.length - 1]];
|
|
2077
|
-
let area;
|
|
2078
|
-
|
|
2079
|
-
if (pts.length < 3) return 0;
|
|
2080
|
-
|
|
2081
|
-
// quadratic beziers
|
|
2082
|
-
if (pts.length === 3) {
|
|
2083
|
-
cp1 = {
|
|
2084
|
-
x: pts[0].x * 1 / 3 + pts[1].x * 2 / 3,
|
|
2085
|
-
y: pts[0].y * 1 / 3 + pts[1].y * 2 / 3
|
|
2086
|
-
};
|
|
2087
|
-
|
|
2088
|
-
cp2 = {
|
|
2089
|
-
x: pts[2].x * 1 / 3 + pts[1].x * 2 / 3,
|
|
2090
|
-
y: pts[2].y * 1 / 3 + pts[1].y * 2 / 3
|
|
2091
|
-
};
|
|
2092
|
-
}
|
|
2093
|
-
|
|
2094
|
-
area = ((p0.x * (-2 * cp1.y - cp2.y + 3 * p.y) +
|
|
2095
|
-
cp1.x * (2 * p0.y - cp2.y - p.y) +
|
|
2096
|
-
cp2.x * (p0.y + cp1.y - 2 * p.y) +
|
|
2097
|
-
p.x * (-3 * p0.y + cp1.y + 2 * cp2.y)) *
|
|
2098
|
-
3) / 20;
|
|
2099
|
-
|
|
2100
|
-
return absolute ? Math.abs(area) : area;
|
|
2101
|
-
}
|
|
2102
|
-
|
|
2103
1835
|
function getPolygonArea(points, absolute=false) {
|
|
2104
1836
|
let area = 0;
|
|
2105
1837
|
let l = points.length;
|
|
@@ -2119,7 +1851,7 @@ function getPolygonArea(points, absolute=false) {
|
|
|
2119
1851
|
* d attribute string
|
|
2120
1852
|
*/
|
|
2121
1853
|
|
|
2122
|
-
function pathDataToD(pathData, mode = 0) {
|
|
1854
|
+
function pathDataToD(pathData = [], mode = 0) {
|
|
2123
1855
|
|
|
2124
1856
|
mode = parseFloat(mode);
|
|
2125
1857
|
/*
|
|
@@ -2128,80 +1860,99 @@ function pathDataToD(pathData, mode = 0) {
|
|
|
2128
1860
|
1 = verbose
|
|
2129
1861
|
2 = beautify
|
|
2130
1862
|
*/
|
|
1863
|
+
|
|
2131
1864
|
let len = pathData.length;
|
|
1865
|
+
let d = '';
|
|
2132
1866
|
|
|
2133
|
-
|
|
2134
|
-
let
|
|
2135
|
-
|
|
2136
|
-
let separator_type = mode > 0.5 ? ' ' : '';
|
|
1867
|
+
// group same types
|
|
1868
|
+
let pathDataGrouped = mode >0.5 ? JSON.parse(JSON.stringify(pathData)) : [];
|
|
1869
|
+
let typePrev = 'M';
|
|
2137
1870
|
|
|
2138
|
-
|
|
2139
|
-
|
|
1871
|
+
if (mode < 1) {
|
|
1872
|
+
pathDataGrouped = [pathData[0]];
|
|
2140
1873
|
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
let
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
1874
|
+
let idx = 0;
|
|
1875
|
+
|
|
1876
|
+
for (let i = 1; i < len; i++) {
|
|
1877
|
+
let com = pathData[i];
|
|
1878
|
+
let { type } = com;
|
|
1879
|
+
// decouple from object
|
|
1880
|
+
let values = [...com.values];
|
|
1881
|
+
|
|
1882
|
+
// new type
|
|
1883
|
+
if (type !== typePrev) {
|
|
1884
|
+
pathDataGrouped.push({type, values});
|
|
1885
|
+
idx++;
|
|
1886
|
+
} else {
|
|
1887
|
+
pathDataGrouped[idx].values.push(...values);
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
// update type
|
|
1891
|
+
typePrev = type;
|
|
2154
1892
|
}
|
|
1893
|
+
}
|
|
2155
1894
|
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
? " "
|
|
2161
|
-
: com.type);
|
|
1895
|
+
// stringify grouped
|
|
1896
|
+
len = pathDataGrouped.length;
|
|
1897
|
+
let separator_type = mode < 1 ? '' : ' ';
|
|
1898
|
+
let separator_command = mode < 1 ? '' : (mode === 1 ? ' ' : `\n`);
|
|
2162
1899
|
|
|
2163
|
-
|
|
2164
|
-
if (!mode) {
|
|
1900
|
+
typePrev = 'M';
|
|
2165
1901
|
|
|
2166
|
-
|
|
1902
|
+
for (let i = 0; i < len; i++) {
|
|
1903
|
+
let com = pathDataGrouped[i];
|
|
1904
|
+
let { type, values } = com;
|
|
2167
1905
|
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
let valStr = val.toString();
|
|
2171
|
-
let isFloat = valStr.includes('.');
|
|
2172
|
-
let isSmallFloat = isFloat && Math.abs(val) < 1;
|
|
1906
|
+
// we're always starting a path with absolute M!
|
|
1907
|
+
let omitType = mode < 1 && ((typePrev === 'M' && type === 'L') || (typePrev === 'm' && type === 'l'));
|
|
2173
1908
|
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
valStr = valStr.replace(/^0\./, '.');
|
|
2177
|
-
}
|
|
1909
|
+
// add type
|
|
1910
|
+
if (!omitType) d += type + separator_type;
|
|
2178
1911
|
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
1912
|
+
// add values
|
|
1913
|
+
let wasSmallFloat = false;
|
|
1914
|
+
let separatorVal = ' ';
|
|
1915
|
+
|
|
1916
|
+
for (let v = 0, vlen = values.length; vlen && v < vlen; v++) {
|
|
1917
|
+
let val = values[v];
|
|
1918
|
+
let valAbs = Math.abs(val);
|
|
1919
|
+
let valStr = val.toString();
|
|
1920
|
+
let isNegative = val < 0;
|
|
1921
|
+
let sign = isNegative ? '-' : '';
|
|
1922
|
+
let isSmallFloat = mode > 0.5 ? false : (val && valAbs < 1);
|
|
1923
|
+
let idxSub = isSmallFloat ? (isNegative ? 2 : 1) : 0;
|
|
1924
|
+
|
|
1925
|
+
// we don't need whitespace for first value
|
|
1926
|
+
separatorVal = v === 0 || isNegative ? '' : ' ';
|
|
1927
|
+
|
|
1928
|
+
if (mode < 1) {
|
|
1929
|
+
// omit leading zero
|
|
1930
|
+
if (isSmallFloat) valStr = sign + valStr.substring(idxSub);
|
|
1931
|
+
|
|
1932
|
+
// omit whitespace for subsequent small floats
|
|
1933
|
+
separatorVal = (v === 0 && !omitType) || (wasSmallFloat && isSmallFloat) ?
|
|
1934
|
+
(!mode ? '' : (isNegative ? '' : ' '))
|
|
1935
|
+
: (isNegative ? '' : ' ');
|
|
2183
1936
|
|
|
2184
|
-
valsString += valStr;
|
|
2185
|
-
prevWasFloat = isSmallFloat;
|
|
2186
1937
|
}
|
|
2187
1938
|
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
1939
|
+
// omit separator between large Arc sweep and final x in minify mode
|
|
1940
|
+
if (!mode && (type === 'a' || type === 'A')) {
|
|
1941
|
+
let pos = (v % 7);
|
|
1942
|
+
if (pos > 3 && pos < 6) separatorVal = '';
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
d += `${separatorVal}${valStr}`;
|
|
1946
|
+
wasSmallFloat = isSmallFloat;
|
|
1947
|
+
|
|
2192
1948
|
}
|
|
2193
1949
|
|
|
2194
|
-
|
|
2195
|
-
d +=
|
|
2196
|
-
|
|
1950
|
+
// add command separator
|
|
1951
|
+
if (mode) d += separator_command;
|
|
1952
|
+
|
|
1953
|
+
// update previous type
|
|
1954
|
+
typePrev = type;
|
|
2197
1955
|
|
|
2198
|
-
if (mode < 1) {
|
|
2199
|
-
d = d
|
|
2200
|
-
.replace(/[A-Za-z]0(?=\.)/g, m => m[0])
|
|
2201
|
-
.replace(/ 0\./g, " .") // Space before small decimals
|
|
2202
|
-
.replace(/ -/g, "-") // Remove space before negatives
|
|
2203
|
-
.replace(/-0\./g, "-.") // Remove leading zero from negative decimals
|
|
2204
|
-
.replace(/Z/g, "z"); // Convert uppercase 'Z' to lowercase
|
|
2205
1956
|
}
|
|
2206
1957
|
|
|
2207
1958
|
return d;
|
|
@@ -2209,39 +1960,26 @@ function pathDataToD(pathData, mode = 0) {
|
|
|
2209
1960
|
|
|
2210
1961
|
function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, debug = false) {
|
|
2211
1962
|
|
|
2212
|
-
// cubic Bézier derivative
|
|
2213
|
-
const cubicDerivative = (p0, p1, p2, p3, t) => {
|
|
2214
|
-
let mt = 1 - t;
|
|
2215
|
-
|
|
2216
|
-
return {
|
|
2217
|
-
x:
|
|
2218
|
-
3 * mt * mt * (p1.x - p0.x) +
|
|
2219
|
-
6 * mt * t * (p2.x - p1.x) +
|
|
2220
|
-
3 * t * t * (p3.x - p2.x),
|
|
2221
|
-
y:
|
|
2222
|
-
3 * mt * mt * (p1.y - p0.y) +
|
|
2223
|
-
6 * mt * t * (p2.y - p1.y) +
|
|
2224
|
-
3 * t * t * (p3.y - p2.y)
|
|
2225
|
-
};
|
|
2226
|
-
};
|
|
2227
|
-
|
|
2228
1963
|
// if combining fails return original commands
|
|
2229
1964
|
let commands = [com1, com2];
|
|
2230
1965
|
|
|
2231
1966
|
// detect dominant
|
|
2232
|
-
let dist1 =
|
|
2233
|
-
let dist2 =
|
|
1967
|
+
let dist1 = getDistManhattan(com1.p0, com1.p);
|
|
1968
|
+
let dist2 = getDistManhattan(com2.p0, com2.p);
|
|
1969
|
+
let thresh = (dist1 + dist2) * 0.5 * 0.075 * tolerance;
|
|
2234
1970
|
|
|
2235
|
-
|
|
1971
|
+
// take longer command
|
|
1972
|
+
let reverse = dist1 < dist2;
|
|
2236
1973
|
|
|
2237
1974
|
// backup original commands
|
|
2238
1975
|
let com1_o = JSON.parse(JSON.stringify(com1));
|
|
2239
1976
|
let com2_o = JSON.parse(JSON.stringify(com2));
|
|
2240
1977
|
|
|
2241
|
-
|
|
1978
|
+
// intersection of control tangents
|
|
1979
|
+
let ptI = checkLineIntersection(com1_o.p0, com1_o.cp1, com2_o.p, com2_o.cp2, false, true);
|
|
2242
1980
|
|
|
1981
|
+
// no intersection - we can't combine
|
|
2243
1982
|
if (!ptI) {
|
|
2244
|
-
|
|
2245
1983
|
return commands
|
|
2246
1984
|
}
|
|
2247
1985
|
|
|
@@ -2264,153 +2002,70 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, debug = f
|
|
|
2264
2002
|
com2 = com2_R;
|
|
2265
2003
|
}
|
|
2266
2004
|
|
|
2267
|
-
|
|
2268
|
-
let
|
|
2269
|
-
let
|
|
2270
|
-
let dot = (a, b) => a.x * b.x + a.y * b.y;
|
|
2005
|
+
// etsimate t for extrapolation
|
|
2006
|
+
let PtI = checkLineIntersection(com1.cp2, com1.p, com2.p, com2.cp2, false, true);
|
|
2007
|
+
let cp1_I = interpolate(com1.p, PtI, 0.666);
|
|
2271
2008
|
|
|
2272
|
-
|
|
2009
|
+
let dist1_2 = getDistManhattan(com1.cp2, com1.p);
|
|
2010
|
+
let dist2_2 = getDistManhattan(com1.cp2, cp1_I);
|
|
2011
|
+
let t = dist2_2 / dist1_2;
|
|
2273
2012
|
|
|
2274
|
-
|
|
2275
|
-
let
|
|
2276
|
-
com2.p0,
|
|
2277
|
-
com2.cp1,
|
|
2278
|
-
com2.cp2,
|
|
2279
|
-
com2.p,
|
|
2280
|
-
0
|
|
2281
|
-
);
|
|
2013
|
+
// extrapolate
|
|
2014
|
+
let segs = pointAtT([com1.p0, com1.cp1, com1.cp2, com1.p], t, false, true).segments;
|
|
2282
2015
|
|
|
2283
|
-
let
|
|
2016
|
+
let seg = segs[0];
|
|
2284
2017
|
|
|
2285
|
-
//
|
|
2286
|
-
let
|
|
2018
|
+
// if it worked - points should be nearby
|
|
2019
|
+
let dist = getDistManhattan(seg.p, com2.p);
|
|
2287
2020
|
|
|
2288
|
-
//
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2021
|
+
// if close enough adjust
|
|
2022
|
+
if (dist < thresh) {
|
|
2023
|
+
let angle = getAngle(seg.p, seg.cp2);
|
|
2024
|
+
let angle2 = getAngle(com2.p, com2.cp2);
|
|
2292
2025
|
|
|
2293
|
-
|
|
2026
|
+
let angleDiff = (angle2 - angle);
|
|
2294
2027
|
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2028
|
+
// adjust cp angle
|
|
2029
|
+
seg.cp2 = rotatePoint(seg.cp2, seg.p.x, seg.p.y, angleDiff);
|
|
2030
|
+
let dist1 = getDistManhattan(seg.p, seg.cp2);
|
|
2298
2031
|
|
|
2299
|
-
|
|
2300
|
-
|
|
2032
|
+
// copy original final point coordinates
|
|
2033
|
+
seg.p = com2.p;
|
|
2301
2034
|
|
|
2302
|
-
|
|
2035
|
+
// after rotation
|
|
2036
|
+
let dist2 = getDistManhattan(seg.p, seg.cp2);
|
|
2037
|
+
let scale = dist2 / dist1;
|
|
2303
2038
|
|
|
2304
|
-
|
|
2305
|
-
|
|
2039
|
+
// adjust tangent length
|
|
2040
|
+
seg.cp2 = interpolate(seg.p, seg.cp2, scale);
|
|
2306
2041
|
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
if (reverse) {
|
|
2316
|
-
result = {
|
|
2317
|
-
p0: Q3,
|
|
2318
|
-
cp1: Q2,
|
|
2319
|
-
cp2: Q1,
|
|
2320
|
-
p: Q0,
|
|
2321
|
-
t0
|
|
2322
|
-
};
|
|
2323
|
-
}
|
|
2324
|
-
|
|
2325
|
-
let tMid = (1 - t0) * 0.5;
|
|
2326
|
-
|
|
2327
|
-
let ptM = pointAtT([result.p0, result.cp1, result.cp2, result.p], tMid, false, true);
|
|
2328
|
-
let seg1_cp2 = ptM.cpts[2];
|
|
2329
|
-
|
|
2330
|
-
let ptI_1 = checkLineIntersection(ptM, seg1_cp2, result.p0, ptI, false);
|
|
2331
|
-
let ptI_2 = checkLineIntersection(ptM, seg1_cp2, result.p, ptI, false);
|
|
2332
|
-
|
|
2333
|
-
let cp1_2 = interpolate(result.p0, ptI_1, 1.333 );
|
|
2334
|
-
let cp2_2 = interpolate(result.p, ptI_2, 1.333 );
|
|
2335
|
-
|
|
2336
|
-
// test self intersections and exit
|
|
2337
|
-
let cp_intersection = checkLineIntersection(com1_o.p0, cp1_2, com2_o.p, cp2_2, true);
|
|
2338
|
-
if (cp_intersection) {
|
|
2339
|
-
|
|
2340
|
-
return commands;
|
|
2341
|
-
}
|
|
2342
|
-
|
|
2343
|
-
result.cp1 = cp1_2;
|
|
2344
|
-
result.cp2 = cp2_2;
|
|
2345
|
-
|
|
2346
|
-
// check distances between original starting point and extrapolated
|
|
2347
|
-
let dist3 = getDistAv(com1_o.p0, result.p0);
|
|
2348
|
-
let dist4 = getDistAv(com2_o.p, result.p);
|
|
2349
|
-
let dist5 = (dist3 + dist4);
|
|
2350
|
-
|
|
2351
|
-
// use original points
|
|
2352
|
-
result.p0 = com1_o.p0;
|
|
2353
|
-
result.p = com2_o.p;
|
|
2354
|
-
result.extreme = com2_o.extreme;
|
|
2355
|
-
result.corner = com2_o.corner;
|
|
2356
|
-
result.dimA = com2_o.dimA;
|
|
2357
|
-
result.directionChange = com2_o.directionChange;
|
|
2358
|
-
result.type = 'C';
|
|
2359
|
-
result.values = [result.cp1.x, result.cp1.y, result.cp2.x, result.cp2.y, result.p.x, result.p.y];
|
|
2360
|
-
|
|
2361
|
-
// extrapolated starting point is not completely off
|
|
2362
|
-
if (dist5 < maxDist) {
|
|
2363
|
-
|
|
2364
|
-
/*
|
|
2365
|
-
let tTotal = 1 + Math.abs(t0);
|
|
2366
|
-
let tSplit = reverse ? 1 + t0 : Math.abs(t0);
|
|
2367
|
-
|
|
2368
|
-
let pO = pointAtT([com2_o.p0, com2_o.cp1, com2_o.cp2, com2_o.p], t0);
|
|
2369
|
-
*/
|
|
2370
|
-
|
|
2371
|
-
// split t to meet original mid segment start point
|
|
2372
|
-
let tSplit = reverse ? 1 + t0 : Math.abs(t0);
|
|
2373
|
-
|
|
2374
|
-
let tTotal = 1 + Math.abs(t0);
|
|
2375
|
-
tSplit = reverse ? 1 + t0 : Math.abs(t0) / tTotal;
|
|
2376
|
-
|
|
2377
|
-
let ptSplit = pointAtT([result.p0, result.cp1, result.cp2, result.p], tSplit);
|
|
2378
|
-
let distSplit = getDistAv(ptSplit, com1.p);
|
|
2379
|
-
|
|
2380
|
-
// not close enough - exit
|
|
2381
|
-
if (distSplit > maxDist * tolerance ) {
|
|
2382
|
-
|
|
2383
|
-
return commands;
|
|
2042
|
+
// reverse back
|
|
2043
|
+
if (reverse) {
|
|
2044
|
+
seg = {
|
|
2045
|
+
p0: seg.p,
|
|
2046
|
+
p: seg.p0,
|
|
2047
|
+
cp1: seg.cp2,
|
|
2048
|
+
cp2: seg.cp1,
|
|
2049
|
+
};
|
|
2384
2050
|
}
|
|
2385
2051
|
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2052
|
+
commands = [
|
|
2053
|
+
{
|
|
2054
|
+
type: 'C',
|
|
2055
|
+
values: [seg.cp1.x, seg.cp1.y, seg.cp2.x, seg.cp2.y, seg.p.x, seg.p.y],
|
|
2056
|
+
p0: seg.p0,
|
|
2057
|
+
cp1: seg.cp1,
|
|
2058
|
+
cp2: seg.cp2,
|
|
2059
|
+
p: seg.p,
|
|
2060
|
+
extreme: com2_o.extreme,
|
|
2061
|
+
corner: com2_o.corner,
|
|
2062
|
+
directionChange: com2_o.directionChange,
|
|
2063
|
+
dimA: getDistManhattan(seg.p0, seg.p),
|
|
2064
|
+
error: dist
|
|
2392
2065
|
|
|
2393
|
-
|
|
2394
|
-
let pathDataN = [
|
|
2395
|
-
{ type: 'M', values: [result.p0.x, result.p0.y] },
|
|
2396
|
-
{ type: 'C', values: [result.cp1.x, result.cp1.y, result.cp2.x, result.cp2.y, result.p.x, result.p.y] },
|
|
2066
|
+
}
|
|
2397
2067
|
];
|
|
2398
2068
|
|
|
2399
|
-
let areaN = getPathArea(pathDataN);
|
|
2400
|
-
let areaDiff = Math.abs(areaN / area0 - 1);
|
|
2401
|
-
|
|
2402
|
-
result.error = areaDiff * 5 * tolerance;
|
|
2403
|
-
|
|
2404
|
-
if (debug) {
|
|
2405
|
-
pathDataToD(pathDataN);
|
|
2406
|
-
|
|
2407
|
-
}
|
|
2408
|
-
|
|
2409
|
-
// success!!!
|
|
2410
|
-
if (areaDiff < 0.05 * tolerance) {
|
|
2411
|
-
commands = [result];
|
|
2412
|
-
|
|
2413
|
-
}
|
|
2414
2069
|
}
|
|
2415
2070
|
|
|
2416
2071
|
return commands
|
|
@@ -2462,7 +2117,7 @@ function simplifyPathDataCubic(pathData, {
|
|
|
2462
2117
|
error += com.error;
|
|
2463
2118
|
|
|
2464
2119
|
// find next candidates
|
|
2465
|
-
for (let n = i +
|
|
2120
|
+
for (let n = i + offset; error < tolerance && n < l; n++) {
|
|
2466
2121
|
let comN = pathData[n];
|
|
2467
2122
|
|
|
2468
2123
|
if (comN.type !== 'C' ||
|
|
@@ -2472,6 +2127,7 @@ function simplifyPathDataCubic(pathData, {
|
|
|
2472
2127
|
(keepExtremes && com.extreme)
|
|
2473
2128
|
)
|
|
2474
2129
|
) {
|
|
2130
|
+
|
|
2475
2131
|
break
|
|
2476
2132
|
}
|
|
2477
2133
|
|
|
@@ -2479,6 +2135,7 @@ function simplifyPathDataCubic(pathData, {
|
|
|
2479
2135
|
|
|
2480
2136
|
// failure - could not be combined - exit loop
|
|
2481
2137
|
if (combined.length > 1) {
|
|
2138
|
+
|
|
2482
2139
|
break
|
|
2483
2140
|
}
|
|
2484
2141
|
|
|
@@ -2492,6 +2149,7 @@ function simplifyPathDataCubic(pathData, {
|
|
|
2492
2149
|
|
|
2493
2150
|
// return combined
|
|
2494
2151
|
com = combined[0];
|
|
2152
|
+
|
|
2495
2153
|
}
|
|
2496
2154
|
|
|
2497
2155
|
pathDataN.push(com);
|
|
@@ -2530,11 +2188,12 @@ function combineCubicPairs(com1, com2, {
|
|
|
2530
2188
|
// quit if t is start
|
|
2531
2189
|
if (!t) return commands;
|
|
2532
2190
|
|
|
2191
|
+
// get averaged threshold
|
|
2533
2192
|
let distAv1 = getDistManhattan(com1.p0, com1.p);
|
|
2534
2193
|
let distAv2 = getDistManhattan(com2.p0, com2.p);
|
|
2535
2194
|
let distMin = Math.max(0, Math.min(distAv1, distAv2));
|
|
2536
2195
|
|
|
2537
|
-
let distScale = 0.
|
|
2196
|
+
let distScale = 0.075;
|
|
2538
2197
|
let maxDist = distMin * distScale * tolerance;
|
|
2539
2198
|
|
|
2540
2199
|
// get hypothetical combined command
|
|
@@ -2582,16 +2241,6 @@ function combineCubicPairs(com1, com2, {
|
|
|
2582
2241
|
|
|
2583
2242
|
if (error < maxDist) success = true;
|
|
2584
2243
|
|
|
2585
|
-
/*
|
|
2586
|
-
renderPoint(markers, ptM_seg1, 'cyan')
|
|
2587
|
-
renderPoint(markers, pt, 'orange', '1.5%', '1')
|
|
2588
|
-
renderPoint(markers, ptM_seg2, 'orange')
|
|
2589
|
-
|
|
2590
|
-
renderPoint(markers, com1.p, 'green')
|
|
2591
|
-
|
|
2592
|
-
renderPoint(markers, ptI_seg1, 'purple')
|
|
2593
|
-
*/
|
|
2594
|
-
|
|
2595
2244
|
}
|
|
2596
2245
|
|
|
2597
2246
|
} // end 1st try
|
|
@@ -2605,9 +2254,9 @@ function combineCubicPairs(com1, com2, {
|
|
|
2605
2254
|
|
|
2606
2255
|
comS.dimA = getDistManhattan(comS.p0, comS.p);
|
|
2607
2256
|
comS.type = 'C';
|
|
2257
|
+
|
|
2608
2258
|
comS.extreme = com2.extreme;
|
|
2609
2259
|
comS.directionChange = com2.directionChange;
|
|
2610
|
-
|
|
2611
2260
|
comS.corner = com2.corner;
|
|
2612
2261
|
|
|
2613
2262
|
comS.values = [comS.cp1.x, comS.cp1.y, comS.cp2.x, comS.cp2.y, comS.p.x, comS.p.y];
|
|
@@ -2668,7 +2317,9 @@ function findSplitT(com1, com2) {
|
|
|
2668
2317
|
}
|
|
2669
2318
|
*/
|
|
2670
2319
|
|
|
2671
|
-
|
|
2320
|
+
let t = l1 / l3;
|
|
2321
|
+
|
|
2322
|
+
return t;
|
|
2672
2323
|
}
|
|
2673
2324
|
|
|
2674
2325
|
function commandIsFlat(points, {
|
|
@@ -2721,7 +2372,7 @@ function analyzePathData(pathData = [], {
|
|
|
2721
2372
|
detectDirection = true,
|
|
2722
2373
|
detectSemiExtremes = false,
|
|
2723
2374
|
debug = false,
|
|
2724
|
-
addSquareLength =
|
|
2375
|
+
addSquareLength = false,
|
|
2725
2376
|
addArea = true,
|
|
2726
2377
|
|
|
2727
2378
|
} = {}) {
|
|
@@ -2770,8 +2421,8 @@ function analyzePathData(pathData = [], {
|
|
|
2770
2421
|
let commandPts = (type === 'C' || type === 'Q') ?
|
|
2771
2422
|
(type === 'C' ? [p0, cp1, cp2, p] : [p0, cp1, p]) :
|
|
2772
2423
|
([p0, p]);
|
|
2773
|
-
|
|
2774
|
-
let threshold =
|
|
2424
|
+
|
|
2425
|
+
let threshold = dimA * 0.005;
|
|
2775
2426
|
|
|
2776
2427
|
// bezier types
|
|
2777
2428
|
let isBezier = type === 'Q' || type === 'C';
|
|
@@ -2784,7 +2435,7 @@ function analyzePathData(pathData = [], {
|
|
|
2784
2435
|
*/
|
|
2785
2436
|
let hasExtremes = false;
|
|
2786
2437
|
|
|
2787
|
-
if (isBezier) {
|
|
2438
|
+
if (detectExtremes && isBezier) {
|
|
2788
2439
|
|
|
2789
2440
|
let dx = type === 'C' ? Math.abs(com.cp2.x - com.p.x) : Math.abs(com.cp1.x - com.p.x);
|
|
2790
2441
|
let dy = type === 'C' ? Math.abs(com.cp2.y - com.p.y) : Math.abs(com.cp1.y - com.p.y);
|
|
@@ -2823,7 +2474,7 @@ function analyzePathData(pathData = [], {
|
|
|
2823
2474
|
}
|
|
2824
2475
|
|
|
2825
2476
|
// check extremes introduce by small arcs
|
|
2826
|
-
else if(isArc && comN && ((comPrev.type==='C' || comPrev.type==='Q') || (comN.type==='C' || comN.type==='Q')) ){
|
|
2477
|
+
else if(detectExtremes && isArc && comN && ((comPrev.type==='C' || comPrev.type==='Q') || (comN.type==='C' || comN.type==='Q')) ){
|
|
2827
2478
|
let distN = comN ? comN.dimA : 0;
|
|
2828
2479
|
let isShort = com.dimA < (comPrev.dimA + distN) * 0.1;
|
|
2829
2480
|
let smallRadius = com.values[0] === com.values[1] && (com.values[0] < 1);
|
|
@@ -2841,28 +2492,7 @@ function analyzePathData(pathData = [], {
|
|
|
2841
2492
|
if (hasExtremes) com.extreme = true;
|
|
2842
2493
|
|
|
2843
2494
|
// Corners and semi extremes
|
|
2844
|
-
if (isBezier && isBezierN) {
|
|
2845
|
-
|
|
2846
|
-
// semi extremes
|
|
2847
|
-
if (detectSemiExtremes && !com.extreme) {
|
|
2848
|
-
|
|
2849
|
-
let dx1 = Math.abs(p.x - cp2.x);
|
|
2850
|
-
let dy1 = Math.abs(p.y - cp2.y);
|
|
2851
|
-
let hasSemiExtreme = false;
|
|
2852
|
-
|
|
2853
|
-
// exclude extremes or small deltas
|
|
2854
|
-
if (dx1 && dy1 && dx1 > thresholdLength || dy1 > thresholdLength) {
|
|
2855
|
-
let ang1 = getAngle(cp2, p);
|
|
2856
|
-
let ang2 = getAngle(p, comN.cp1);
|
|
2857
|
-
|
|
2858
|
-
let ang3 = Math.abs(ang1 + ang2) / 2;
|
|
2859
|
-
hasSemiExtreme = isMultipleOf45(ang3);
|
|
2860
|
-
}
|
|
2861
|
-
|
|
2862
|
-
if (hasSemiExtreme) {
|
|
2863
|
-
com.semiExtreme = true;
|
|
2864
|
-
}
|
|
2865
|
-
}
|
|
2495
|
+
if (detectCorners && isBezier && isBezierN) {
|
|
2866
2496
|
|
|
2867
2497
|
/**
|
|
2868
2498
|
* Detect direction change points
|
|
@@ -3438,7 +3068,12 @@ function convertPathData(pathData, {
|
|
|
3438
3068
|
if (hasShorthands && toLonghands) pathData = pathDataToLonghands(pathData);
|
|
3439
3069
|
|
|
3440
3070
|
// minify semicircle radii
|
|
3441
|
-
if (optimizeArcs)
|
|
3071
|
+
if (optimizeArcs) {
|
|
3072
|
+
pathData = optimizeArcPathData(pathData);
|
|
3073
|
+
} else {
|
|
3074
|
+
// get true absolute radii
|
|
3075
|
+
pathData = pathDataToTrueArcValues(pathData);
|
|
3076
|
+
}
|
|
3442
3077
|
|
|
3443
3078
|
if (toShorthands) pathData = pathDataToShorthands(pathData);
|
|
3444
3079
|
|
|
@@ -3528,9 +3163,42 @@ function parsePathDataNormalized(d,
|
|
|
3528
3163
|
}
|
|
3529
3164
|
|
|
3530
3165
|
/**
|
|
3531
|
-
*
|
|
3532
|
-
*
|
|
3533
|
-
|
|
3166
|
+
* Converts minified arc
|
|
3167
|
+
* values to true rx and ry values
|
|
3168
|
+
*/
|
|
3169
|
+
function pathDataToTrueArcValues(pathData = []) {
|
|
3170
|
+
let l = pathData.length;
|
|
3171
|
+
let pathDataN = [pathData[0]];
|
|
3172
|
+
|
|
3173
|
+
for (let i = 1; i < l; i++) {
|
|
3174
|
+
let com = pathData[i];
|
|
3175
|
+
let comPrev = pathData[i-1];
|
|
3176
|
+
let { type, values } = com;
|
|
3177
|
+
|
|
3178
|
+
if (type === 'A') {
|
|
3179
|
+
|
|
3180
|
+
// previous commands final on-path point
|
|
3181
|
+
let [x1, y1] = comPrev.values.slice(-2);
|
|
3182
|
+
let [rx, ry, xAxisRotation, largeArc, sweep, x2, y2] = values;
|
|
3183
|
+
let arcData = svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2, y2);
|
|
3184
|
+
|
|
3185
|
+
// set true arc values
|
|
3186
|
+
com.values[0] = arcData.rx;
|
|
3187
|
+
com.values[1] = arcData.ry;
|
|
3188
|
+
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
pathDataN.push(com);
|
|
3192
|
+
|
|
3193
|
+
}
|
|
3194
|
+
return pathDataN;
|
|
3195
|
+
}
|
|
3196
|
+
|
|
3197
|
+
/**
|
|
3198
|
+
* Minify arc radii
|
|
3199
|
+
* for semi circles
|
|
3200
|
+
* returns smaller rx and ry
|
|
3201
|
+
* values
|
|
3534
3202
|
*/
|
|
3535
3203
|
|
|
3536
3204
|
function optimizeArcPathData(pathData = []) {
|
|
@@ -3587,7 +3255,7 @@ function optimizeArcPathData(pathData = []) {
|
|
|
3587
3255
|
if (isHorizontal || isVertical) {
|
|
3588
3256
|
|
|
3589
3257
|
// check if semi circle
|
|
3590
|
-
let needsTrueR = isHorizontal ? rx*1.9 > diffX : ry*1.9 > diffY;
|
|
3258
|
+
let needsTrueR = isHorizontal ? rx * 1.9 > diffX : ry * 1.9 > diffY;
|
|
3591
3259
|
|
|
3592
3260
|
// is semicircle we can simplify rx
|
|
3593
3261
|
if (!needsTrueR) {
|
|
@@ -3692,7 +3360,7 @@ function pathDataArcsToCubics(pathData, {
|
|
|
3692
3360
|
|
|
3693
3361
|
if (com.type === 'A') {
|
|
3694
3362
|
// add all C commands instead of Arc
|
|
3695
|
-
let cubicArcs = arcToBezier
|
|
3363
|
+
let cubicArcs = arcToBezier(p0, com.values, arcAccuracy);
|
|
3696
3364
|
cubicArcs.forEach((cubicArc) => {
|
|
3697
3365
|
pathDataCubic.push(cubicArc);
|
|
3698
3366
|
});
|
|
@@ -4193,7 +3861,7 @@ function arcToBezierResolved({
|
|
|
4193
3861
|
* returns pathData array
|
|
4194
3862
|
*/
|
|
4195
3863
|
|
|
4196
|
-
function arcToBezier
|
|
3864
|
+
function arcToBezier(p0, values, splitSegments = 1) {
|
|
4197
3865
|
const TAU = Math.PI * 2;
|
|
4198
3866
|
let [rx, ry, rotation, largeArcFlag, sweepFlag, x, y] = values;
|
|
4199
3867
|
|
|
@@ -4204,8 +3872,8 @@ function arcToBezier$1(p0, values, splitSegments = 1) {
|
|
|
4204
3872
|
let phi = rotation ? rotation * TAU / 360 : 0;
|
|
4205
3873
|
let sinphi = phi ? Math.sin(phi) : 0;
|
|
4206
3874
|
let cosphi = phi ? Math.cos(phi) : 1;
|
|
4207
|
-
let pxp = cosphi * (p0.x - x)
|
|
4208
|
-
let pyp = -sinphi * (p0.x - x)
|
|
3875
|
+
let pxp = cosphi * (p0.x - x) *0.5 + sinphi * (p0.y - y) *0.5;
|
|
3876
|
+
let pyp = -sinphi * (p0.x - x) *0.5 + cosphi * (p0.y - y) *0.5;
|
|
4209
3877
|
|
|
4210
3878
|
if (pxp === 0 && pyp === 0) {
|
|
4211
3879
|
return []
|
|
@@ -4241,8 +3909,8 @@ function arcToBezier$1(p0, values, splitSegments = 1) {
|
|
|
4241
3909
|
|
|
4242
3910
|
let centerxp = radicant ? radicant * rx / ry * pyp : 0;
|
|
4243
3911
|
let centeryp = radicant ? radicant * -ry / rx * pxp : 0;
|
|
4244
|
-
let centerx = cosphi * centerxp - sinphi * centeryp + (p0.x + x)
|
|
4245
|
-
let centery = sinphi * centerxp + cosphi * centeryp + (p0.y + y)
|
|
3912
|
+
let centerx = cosphi * centerxp - sinphi * centeryp + (p0.x + x) * 0.5;
|
|
3913
|
+
let centery = sinphi * centerxp + cosphi * centeryp + (p0.y + y) * 0.5;
|
|
4246
3914
|
|
|
4247
3915
|
let vx1 = (pxp - centerxp) / rx;
|
|
4248
3916
|
let vy1 = (pyp - centeryp) / ry;
|
|
@@ -4264,15 +3932,17 @@ function arcToBezier$1(p0, values, splitSegments = 1) {
|
|
|
4264
3932
|
ang2 = vectorAngle(vx1, vy1, vx2, vy2);
|
|
4265
3933
|
|
|
4266
3934
|
if (sweepFlag === 0 && ang2 > 0) {
|
|
4267
|
-
|
|
3935
|
+
|
|
3936
|
+
ang2 -= TAU;
|
|
4268
3937
|
}
|
|
4269
3938
|
else if (sweepFlag === 1 && ang2 < 0) {
|
|
4270
|
-
|
|
3939
|
+
|
|
3940
|
+
ang2 += TAU;
|
|
4271
3941
|
}
|
|
4272
3942
|
|
|
4273
|
-
let ratio = +(Math.abs(ang2) / (TAU
|
|
3943
|
+
let ratio = +(Math.abs(ang2) / (TAU * 0.25)).toFixed(0) || 1;
|
|
4274
3944
|
|
|
4275
|
-
// increase segments for more
|
|
3945
|
+
// increase segments for more accurate length calculations
|
|
4276
3946
|
let segments = ratio * splitSegments;
|
|
4277
3947
|
ang2 /= segments;
|
|
4278
3948
|
let pathDataArc = [];
|
|
@@ -4284,7 +3954,8 @@ function arcToBezier$1(p0, values, splitSegments = 1) {
|
|
|
4284
3954
|
const k = 0.551785;
|
|
4285
3955
|
let a = ang2 === angle90 ? k :
|
|
4286
3956
|
(
|
|
4287
|
-
|
|
3957
|
+
|
|
3958
|
+
ang2 === -angle90 ? -k : 1.33333 * Math.tan(ang2 * 0.25)
|
|
4288
3959
|
);
|
|
4289
3960
|
|
|
4290
3961
|
let cos2 = ang2 ? Math.cos(ang2) : 1;
|
|
@@ -4334,59 +4005,50 @@ function pathDataRemoveColinear(pathData, {
|
|
|
4334
4005
|
pathData[pathData.length - 1].type.toLowerCase() === 'z';
|
|
4335
4006
|
|
|
4336
4007
|
for (let c = 1, l = pathData.length; c < l; c++) {
|
|
4337
|
-
|
|
4338
4008
|
let com = pathData[c];
|
|
4009
|
+
let { type, values } = com;
|
|
4339
4010
|
let comN = pathData[c + 1] || pathData[l - 1];
|
|
4340
|
-
|
|
4011
|
+
let valuesN = comN.values;
|
|
4341
4012
|
let p1 = comN.type.toLowerCase() === 'z' ? M : { x: comN.values[comN.values.length - 2], y: comN.values[comN.values.length - 1] };
|
|
4342
4013
|
|
|
4343
|
-
let { type, values } = com;
|
|
4344
4014
|
let valsL = values.slice(-2);
|
|
4345
4015
|
p = type !== 'Z' ? { x: valsL[0], y: valsL[1] } : M;
|
|
4346
4016
|
|
|
4017
|
+
let nextBezier = type!==comN.type && (comN.type==='C' || comN.type==='Q');
|
|
4018
|
+
|
|
4347
4019
|
let area = p1 ? getPolygonArea([p0, p, p1], true) : Infinity;
|
|
4348
4020
|
let distSquare = getSquareDistance(p0, p1);
|
|
4349
4021
|
let distMax = distSquare ? distSquare / 333 * tolerance : 0;
|
|
4350
4022
|
|
|
4351
4023
|
let isFlat = area < distMax;
|
|
4352
|
-
|
|
4353
4024
|
let isFlatBez = false;
|
|
4025
|
+
let cpts = [];
|
|
4354
4026
|
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
let dx2 = Math.abs(p1.x - p.x)
|
|
4364
|
-
let dy2 = Math.abs(p1.y - p.y)
|
|
4365
|
-
|
|
4366
|
-
// zero length segments are flat
|
|
4367
|
-
let isZeroLength = (!dy1 && !dx1) || (!dy2 && !dx2)
|
|
4368
|
-
if (isZeroLength) isFlat = true;
|
|
4369
|
-
|
|
4370
|
-
// check cross products for colinearity
|
|
4371
|
-
if (!isFlat) {
|
|
4372
|
-
|
|
4373
|
-
let cross0 = Math.abs(dx0 * dy1 - dy0 * dx1);
|
|
4027
|
+
/**
|
|
4028
|
+
* type change
|
|
4029
|
+
* check flatness
|
|
4030
|
+
*/
|
|
4031
|
+
if (nextBezier) {
|
|
4032
|
+
cpts = comN.type === 'C' ?
|
|
4033
|
+
[{ x: valuesN[0], y: valuesN[1] }, { x: valuesN[2], y: valuesN[3] }] :
|
|
4034
|
+
(comN.type === 'Q' ? [{ x: valuesN[0], y: valuesN[1] }] : []);
|
|
4374
4035
|
|
|
4375
|
-
|
|
4036
|
+
isFlat = commandIsFlat([p0, ...cpts, p], { tolerance });
|
|
4376
4037
|
|
|
4377
|
-
|
|
4038
|
+
/*
|
|
4378
4039
|
|
|
4379
|
-
|
|
4040
|
+
if(!isFlatBez){
|
|
4041
|
+
pathDataN.push(com)
|
|
4042
|
+
continue
|
|
4380
4043
|
}
|
|
4381
|
-
|
|
4382
|
-
*/
|
|
4044
|
+
*/
|
|
4383
4045
|
|
|
4384
|
-
|
|
4046
|
+
}
|
|
4385
4047
|
|
|
4386
4048
|
// convert flat beziers to linetos
|
|
4387
4049
|
if (flatBezierToLinetos && (type === 'C' || type === 'Q')) {
|
|
4388
4050
|
|
|
4389
|
-
|
|
4051
|
+
cpts = type === 'C' ?
|
|
4390
4052
|
[{ x: values[0], y: values[1] }, { x: values[2], y: values[3] }] :
|
|
4391
4053
|
(type === 'Q' ? [{ x: values[0], y: values[1] }] : []);
|
|
4392
4054
|
|
|
@@ -4400,10 +4062,13 @@ function pathDataRemoveColinear(pathData, {
|
|
|
4400
4062
|
}
|
|
4401
4063
|
}
|
|
4402
4064
|
|
|
4403
|
-
|
|
4065
|
+
/**
|
|
4066
|
+
* colinear = simplification success
|
|
4067
|
+
* exclude arcs (as always =)
|
|
4068
|
+
* as semicircles won't have an area
|
|
4069
|
+
*/
|
|
4404
4070
|
|
|
4405
4071
|
if (isFlat && c < l - 1 && comN.type !== 'A' && (type === 'L' || (flatBezierToLinetos && isFlatBez))) {
|
|
4406
|
-
|
|
4407
4072
|
continue;
|
|
4408
4073
|
}
|
|
4409
4074
|
|
|
@@ -4519,7 +4184,8 @@ function pathDataToTopLeft(pathData) {
|
|
|
4519
4184
|
let { type, values } = com;
|
|
4520
4185
|
let valsLen = values.length;
|
|
4521
4186
|
if (valsLen) {
|
|
4522
|
-
|
|
4187
|
+
// we need rounding otherwise sorting may crash due to e notation
|
|
4188
|
+
let p = { type: type, x: +values[valsLen - 2].toFixed(8), y: +values[valsLen - 1].toFixed(8), index: 0 };
|
|
4523
4189
|
p.index = i;
|
|
4524
4190
|
indices.push(p);
|
|
4525
4191
|
}
|
|
@@ -4527,7 +4193,7 @@ function pathDataToTopLeft(pathData) {
|
|
|
4527
4193
|
|
|
4528
4194
|
// reorder to top left most
|
|
4529
4195
|
|
|
4530
|
-
indices = indices.sort((a, b) =>
|
|
4196
|
+
indices = indices.sort((a, b) => a.y - b.y || a.x - b.x);
|
|
4531
4197
|
newIndex = indices[0].index;
|
|
4532
4198
|
|
|
4533
4199
|
return newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
|
|
@@ -4767,9 +4433,7 @@ function refineAdjacentExtremes(pathData, {
|
|
|
4767
4433
|
if (comEx.length === 1) {
|
|
4768
4434
|
|
|
4769
4435
|
comEx = comEx[0];
|
|
4770
|
-
|
|
4771
4436
|
pathData[i + 1] = null;
|
|
4772
|
-
|
|
4773
4437
|
pathData[i + 2].values = [comEx.cp1.x, comEx.cp1.y, comEx.cp2.x, comEx.cp2.y, comEx.p.x, comEx.p.y];
|
|
4774
4438
|
pathData[i + 2].cp1 = comEx.cp1;
|
|
4775
4439
|
pathData[i + 2].cp2 = comEx.cp2;
|