svg-path-simplify 0.4.5 → 0.4.7
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 +14 -0
- package/README.md +24 -14
- package/dist/svg-path-simplify.esm.js +1620 -1216
- package/dist/svg-path-simplify.esm.min.js +2 -2
- package/dist/svg-path-simplify.js +1620 -1215
- package/dist/svg-path-simplify.min.js +2 -2
- package/dist/svg-path-simplify.pathdata.esm.js +298 -665
- 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 +11 -5
- 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 +14 -34
- package/src/pathData_simplify_cubic_extrapolate.js +147 -8
- package/src/pathData_simplify_cubicsToArcs.js +65 -21
- package/src/pathSimplify-main.js +259 -60
- package/src/pathSimplify-presets.js +1 -0
- 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/svgii/geometry.js +130 -27
- package/src/svgii/geometry_bbox.js +1 -1
- package/src/svgii/pathData_analyze.js +6 -28
- package/src/svgii/pathData_convert.js +70 -15
- package/src/svgii/pathData_remove_collinear.js +32 -36
- 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 +12 -0
- package/src/svgii/poly_to_pathdata.js +477 -8
- package/src/svgii/rounding.js +29 -39
- package/src/svgii/svg_cleanup.js +42 -54
- package/src/svgii/svg_cleanup_normalize_transforms.js +1 -1
- package/src/svgii/visualize.js +33 -2
|
@@ -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,52 +1678,33 @@ 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
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
// smallest 25% of values
|
|
1703
|
-
let idx_q = Math.ceil(len*0.25);
|
|
1704
|
-
let dims_min = dims.slice(0, idx_q);
|
|
1705
|
-
|
|
1706
|
-
// average smallest values with mid value
|
|
1707
|
-
let dim_min = ((dims_min.reduce((a, b) => a + b, 0) / idx_q) + dim_mid) * 0.5;
|
|
1708
|
-
|
|
1709
|
-
let threshold = 75;
|
|
1710
|
-
let decimalsAuto = dim_min > threshold * 1.5 ? 0 : Math.floor(threshold / dim_min).toString().length;
|
|
1711
|
-
|
|
1712
|
-
// clamp
|
|
1713
|
-
return Math.min(Math.max(0, decimalsAuto), 8)
|
|
1714
|
-
|
|
1715
|
-
/*
|
|
1716
|
-
let dim_min = dims.sort()
|
|
1717
|
-
|
|
1718
|
-
let dim_mid = dim_min[Math.floor(dim_min.length*0.5)]
|
|
1692
|
+
dims = dims.sort((a,b)=>a-b);
|
|
1693
|
+
let len = dims.length;
|
|
1694
|
+
let dim_mid = dims[Math.floor(len * 0.5)];
|
|
1719
1695
|
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
let
|
|
1696
|
+
// smallest 25% of values
|
|
1697
|
+
let idx_q = Math.ceil(len * 0.25);
|
|
1698
|
+
let dims_min = dims.slice(0, idx_q);
|
|
1723
1699
|
|
|
1724
|
-
// average with mid value
|
|
1725
|
-
|
|
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;
|
|
1726
1702
|
|
|
1727
|
-
let threshold = 75
|
|
1728
|
-
let decimalsAuto =
|
|
1703
|
+
let threshold = 75;
|
|
1704
|
+
let decimalsAuto = dim_min > threshold * 1.5 ? 0 : Math.floor(threshold / dim_min).toString().length;
|
|
1729
1705
|
|
|
1730
1706
|
// clamp
|
|
1731
1707
|
return Math.min(Math.max(0, decimalsAuto), 8)
|
|
1732
|
-
*/
|
|
1733
1708
|
|
|
1734
1709
|
}
|
|
1735
1710
|
|
|
@@ -1790,117 +1765,6 @@ function getPolyBBox(vertices, decimals = -1) {
|
|
|
1790
1765
|
return bb;
|
|
1791
1766
|
}
|
|
1792
1767
|
|
|
1793
|
-
function getSubPathBBoxes(subPaths) {
|
|
1794
|
-
let bboxArr = [];
|
|
1795
|
-
subPaths.forEach((pathData) => {
|
|
1796
|
-
|
|
1797
|
-
let bb = getPathDataBBox_sloppy(pathData);
|
|
1798
|
-
bboxArr.push(bb);
|
|
1799
|
-
});
|
|
1800
|
-
|
|
1801
|
-
return bboxArr;
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
function checkBBoxIntersections(bb, bb1) {
|
|
1805
|
-
let [x, y, width, height, right, bottom] = [
|
|
1806
|
-
bb.x,
|
|
1807
|
-
bb.y,
|
|
1808
|
-
bb.width,
|
|
1809
|
-
bb.height,
|
|
1810
|
-
bb.x + bb.width,
|
|
1811
|
-
bb.y + bb.height
|
|
1812
|
-
];
|
|
1813
|
-
let [x1, y1, width1, height1, right1, bottom1] = [
|
|
1814
|
-
bb1.x,
|
|
1815
|
-
bb1.y,
|
|
1816
|
-
bb1.width,
|
|
1817
|
-
bb1.height,
|
|
1818
|
-
bb1.x + bb1.width,
|
|
1819
|
-
bb1.y + bb1.height
|
|
1820
|
-
];
|
|
1821
|
-
let intersects = false;
|
|
1822
|
-
if (width * height != width1 * height1) {
|
|
1823
|
-
if (width * height > width1 * height1) {
|
|
1824
|
-
if (x < x1 && right > right1 && y < y1 && bottom > bottom1) {
|
|
1825
|
-
intersects = true;
|
|
1826
|
-
}
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
return intersects;
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
|
-
/**
|
|
1833
|
-
* sloppy path bbox aaproximation
|
|
1834
|
-
*/
|
|
1835
|
-
|
|
1836
|
-
function getPathDataBBox_sloppy(pathData) {
|
|
1837
|
-
let pts = getPathDataPoly(pathData);
|
|
1838
|
-
let bb = getPolyBBox(pts);
|
|
1839
|
-
return bb;
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
|
-
/**
|
|
1843
|
-
* get path data poly
|
|
1844
|
-
* including command points
|
|
1845
|
-
* handy for faster/sloppy bbox approximations
|
|
1846
|
-
*/
|
|
1847
|
-
|
|
1848
|
-
function getPathDataPoly(pathData) {
|
|
1849
|
-
|
|
1850
|
-
let poly = [];
|
|
1851
|
-
for (let i = 0; i < pathData.length; i++) {
|
|
1852
|
-
let com = pathData[i];
|
|
1853
|
-
let prev = i > 0 ? pathData[i - 1] : pathData[i];
|
|
1854
|
-
let { type, values } = com;
|
|
1855
|
-
let p0 = { x: prev.values[prev.values.length - 2], y: prev.values[prev.values.length - 1] };
|
|
1856
|
-
let p = values.length ? { x: values[values.length - 2], y: values[values.length - 1] } : '';
|
|
1857
|
-
let cp1 = values.length ? { x: values[0], y: values[1] } : '';
|
|
1858
|
-
|
|
1859
|
-
switch (type) {
|
|
1860
|
-
|
|
1861
|
-
// convert to cubic to get polygon
|
|
1862
|
-
case 'A':
|
|
1863
|
-
|
|
1864
|
-
if (typeof arcToBezier !== 'function') {
|
|
1865
|
-
|
|
1866
|
-
// get real radii
|
|
1867
|
-
let rx = getDistance(p0, p) / 2;
|
|
1868
|
-
let ptMid = interpolate(p0, p, 0.5);
|
|
1869
|
-
|
|
1870
|
-
let pt1 = getPointOnEllipse(ptMid.x, ptMid.y, rx, rx, 0);
|
|
1871
|
-
let pt2 = getPointOnEllipse(ptMid.x, ptMid.y, rx, rx, Math.PI);
|
|
1872
|
-
poly.push(pt1, pt2, p);
|
|
1873
|
-
|
|
1874
|
-
break;
|
|
1875
|
-
}
|
|
1876
|
-
let cubic = arcToBezier(p0, values);
|
|
1877
|
-
cubic.forEach(com => {
|
|
1878
|
-
let vals = com.values;
|
|
1879
|
-
let cp1 = { x: vals[0], y: vals[1] };
|
|
1880
|
-
let cp2 = { x: vals[2], y: vals[3] };
|
|
1881
|
-
let p = { x: vals[4], y: vals[5] };
|
|
1882
|
-
poly.push(cp1, cp2, p);
|
|
1883
|
-
});
|
|
1884
|
-
break;
|
|
1885
|
-
|
|
1886
|
-
case 'C':
|
|
1887
|
-
let cp2 = { x: values[2], y: values[3] };
|
|
1888
|
-
poly.push(cp1, cp2);
|
|
1889
|
-
break;
|
|
1890
|
-
case 'Q':
|
|
1891
|
-
poly.push(cp1);
|
|
1892
|
-
break;
|
|
1893
|
-
}
|
|
1894
|
-
|
|
1895
|
-
// M and L commands
|
|
1896
|
-
if (type.toLowerCase() !== 'z') {
|
|
1897
|
-
poly.push(p);
|
|
1898
|
-
}
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
|
-
return poly;
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
1768
|
/**
|
|
1905
1769
|
* get exact path BBox
|
|
1906
1770
|
* calculating extremes for all command types
|
|
@@ -1964,166 +1828,10 @@ function getPathDataBBox(pathData) {
|
|
|
1964
1828
|
}
|
|
1965
1829
|
}
|
|
1966
1830
|
|
|
1967
|
-
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 };
|
|
1968
1832
|
return bbox
|
|
1969
1833
|
}
|
|
1970
1834
|
|
|
1971
|
-
/**
|
|
1972
|
-
* get pathdata area
|
|
1973
|
-
*/
|
|
1974
|
-
|
|
1975
|
-
function getPathArea(pathData, decimals = 9) {
|
|
1976
|
-
let totalArea = 0;
|
|
1977
|
-
let polyPoints = [];
|
|
1978
|
-
|
|
1979
|
-
let subPathsData = splitSubpaths(pathData);
|
|
1980
|
-
let isCompoundPath = subPathsData.length > 1 ? true : false;
|
|
1981
|
-
let counterShapes = [];
|
|
1982
|
-
|
|
1983
|
-
// check intersections for compund paths
|
|
1984
|
-
if (isCompoundPath) {
|
|
1985
|
-
let bboxArr = getSubPathBBoxes(subPathsData);
|
|
1986
|
-
|
|
1987
|
-
bboxArr.forEach(function (bb, b) {
|
|
1988
|
-
|
|
1989
|
-
for (let i = 0; i < bboxArr.length; i++) {
|
|
1990
|
-
let bb2 = bboxArr[i];
|
|
1991
|
-
if (bb != bb2) {
|
|
1992
|
-
let intersects = checkBBoxIntersections(bb, bb2);
|
|
1993
|
-
if (intersects) {
|
|
1994
|
-
counterShapes.push(i);
|
|
1995
|
-
}
|
|
1996
|
-
}
|
|
1997
|
-
}
|
|
1998
|
-
});
|
|
1999
|
-
}
|
|
2000
|
-
|
|
2001
|
-
subPathsData.forEach((pathData, d) => {
|
|
2002
|
-
|
|
2003
|
-
polyPoints = [];
|
|
2004
|
-
let comArea = 0;
|
|
2005
|
-
let pathArea = 0;
|
|
2006
|
-
let multiplier = 1;
|
|
2007
|
-
let pts = [];
|
|
2008
|
-
|
|
2009
|
-
pathData.forEach(function (com, i) {
|
|
2010
|
-
let [type, values] = [com.type, com.values];
|
|
2011
|
-
let valuesL = values.length;
|
|
2012
|
-
|
|
2013
|
-
if (values.length) {
|
|
2014
|
-
let prevC = i > 0 ? pathData[i - 1] : pathData[0];
|
|
2015
|
-
let prevCVals = prevC.values;
|
|
2016
|
-
let prevCValsL = prevCVals.length;
|
|
2017
|
-
let p0 = { x: prevCVals[prevCValsL - 2], y: prevCVals[prevCValsL - 1] };
|
|
2018
|
-
let p = { x: values[valuesL - 2], y: values[valuesL - 1] };
|
|
2019
|
-
|
|
2020
|
-
// C commands
|
|
2021
|
-
if (type === 'C' || type === 'Q') {
|
|
2022
|
-
let cp1 = { x: values[0], y: values[1] };
|
|
2023
|
-
pts = type === 'C' ? [p0, cp1, { x: values[2], y: values[3] }, p] : [p0, cp1, p];
|
|
2024
|
-
let areaBez = Math.abs(getBezierArea(pts));
|
|
2025
|
-
comArea += areaBez;
|
|
2026
|
-
|
|
2027
|
-
polyPoints.push(p0, p);
|
|
2028
|
-
}
|
|
2029
|
-
|
|
2030
|
-
// A commands
|
|
2031
|
-
else if (type === 'A') {
|
|
2032
|
-
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);
|
|
2033
|
-
let { cx, cy, rx, ry, startAngle, endAngle, deltaAngle } = arcData;
|
|
2034
|
-
|
|
2035
|
-
let arcArea = Math.abs(getEllipseArea(rx, ry, startAngle, endAngle));
|
|
2036
|
-
|
|
2037
|
-
// subtract remaining polygon between p0, center and p
|
|
2038
|
-
let polyArea = Math.abs(getPolygonArea([p0, { x: cx, y: cy }, p]));
|
|
2039
|
-
arcArea -= polyArea;
|
|
2040
|
-
|
|
2041
|
-
polyPoints.push(p0, p);
|
|
2042
|
-
comArea += arcArea;
|
|
2043
|
-
}
|
|
2044
|
-
|
|
2045
|
-
// L commands
|
|
2046
|
-
else {
|
|
2047
|
-
polyPoints.push(p0, p);
|
|
2048
|
-
}
|
|
2049
|
-
}
|
|
2050
|
-
});
|
|
2051
|
-
|
|
2052
|
-
let areaPoly = getPolygonArea(polyPoints);
|
|
2053
|
-
|
|
2054
|
-
if (counterShapes.indexOf(d) !== -1) {
|
|
2055
|
-
multiplier = -1;
|
|
2056
|
-
}
|
|
2057
|
-
|
|
2058
|
-
if (
|
|
2059
|
-
(areaPoly < 0 && comArea < 0)
|
|
2060
|
-
) {
|
|
2061
|
-
// are negative
|
|
2062
|
-
pathArea = (Math.abs(comArea) - Math.abs(areaPoly)) * multiplier;
|
|
2063
|
-
|
|
2064
|
-
} else {
|
|
2065
|
-
pathArea = (Math.abs(comArea) + Math.abs(areaPoly)) * multiplier;
|
|
2066
|
-
}
|
|
2067
|
-
|
|
2068
|
-
totalArea += pathArea;
|
|
2069
|
-
});
|
|
2070
|
-
|
|
2071
|
-
return totalArea;
|
|
2072
|
-
}
|
|
2073
|
-
|
|
2074
|
-
/**
|
|
2075
|
-
* get ellipse area
|
|
2076
|
-
* skips to circle calculation if rx===ry
|
|
2077
|
-
*/
|
|
2078
|
-
|
|
2079
|
-
function getEllipseArea(rx, ry, startAngle, endAngle) {
|
|
2080
|
-
const totalArea = Math.PI * rx * ry;
|
|
2081
|
-
let angleDiff = (endAngle - startAngle + 2 * Math.PI) % (2 * Math.PI);
|
|
2082
|
-
// If circle, use simple circular formula
|
|
2083
|
-
if (rx === ry) return totalArea * (angleDiff / (2 * Math.PI));
|
|
2084
|
-
|
|
2085
|
-
// Convert absolute angles to parametric angles
|
|
2086
|
-
const absoluteToParametric = (phi)=>{
|
|
2087
|
-
return Math.atan2(rx * Math.sin(phi), ry * Math.cos(phi));
|
|
2088
|
-
};
|
|
2089
|
-
startAngle = absoluteToParametric(startAngle);
|
|
2090
|
-
endAngle = absoluteToParametric(endAngle);
|
|
2091
|
-
angleDiff = (endAngle - startAngle + 2 * Math.PI) % (2 * Math.PI);
|
|
2092
|
-
return totalArea * (angleDiff / (2 * Math.PI));
|
|
2093
|
-
}
|
|
2094
|
-
|
|
2095
|
-
/**
|
|
2096
|
-
* get bezier area
|
|
2097
|
-
*/
|
|
2098
|
-
function getBezierArea(pts, absolute=false) {
|
|
2099
|
-
|
|
2100
|
-
let [p0, cp1, cp2, p] = [pts[0], pts[1], pts[2], pts[pts.length - 1]];
|
|
2101
|
-
let area;
|
|
2102
|
-
|
|
2103
|
-
if (pts.length < 3) return 0;
|
|
2104
|
-
|
|
2105
|
-
// quadratic beziers
|
|
2106
|
-
if (pts.length === 3) {
|
|
2107
|
-
cp1 = {
|
|
2108
|
-
x: pts[0].x * 1 / 3 + pts[1].x * 2 / 3,
|
|
2109
|
-
y: pts[0].y * 1 / 3 + pts[1].y * 2 / 3
|
|
2110
|
-
};
|
|
2111
|
-
|
|
2112
|
-
cp2 = {
|
|
2113
|
-
x: pts[2].x * 1 / 3 + pts[1].x * 2 / 3,
|
|
2114
|
-
y: pts[2].y * 1 / 3 + pts[1].y * 2 / 3
|
|
2115
|
-
};
|
|
2116
|
-
}
|
|
2117
|
-
|
|
2118
|
-
area = ((p0.x * (-2 * cp1.y - cp2.y + 3 * p.y) +
|
|
2119
|
-
cp1.x * (2 * p0.y - cp2.y - p.y) +
|
|
2120
|
-
cp2.x * (p0.y + cp1.y - 2 * p.y) +
|
|
2121
|
-
p.x * (-3 * p0.y + cp1.y + 2 * cp2.y)) *
|
|
2122
|
-
3) / 20;
|
|
2123
|
-
|
|
2124
|
-
return absolute ? Math.abs(area) : area;
|
|
2125
|
-
}
|
|
2126
|
-
|
|
2127
1835
|
function getPolygonArea(points, absolute=false) {
|
|
2128
1836
|
let area = 0;
|
|
2129
1837
|
let l = points.length;
|
|
@@ -2143,7 +1851,7 @@ function getPolygonArea(points, absolute=false) {
|
|
|
2143
1851
|
* d attribute string
|
|
2144
1852
|
*/
|
|
2145
1853
|
|
|
2146
|
-
function pathDataToD(pathData, mode = 0) {
|
|
1854
|
+
function pathDataToD(pathData = [], mode = 0) {
|
|
2147
1855
|
|
|
2148
1856
|
mode = parseFloat(mode);
|
|
2149
1857
|
/*
|
|
@@ -2152,80 +1860,99 @@ function pathDataToD(pathData, mode = 0) {
|
|
|
2152
1860
|
1 = verbose
|
|
2153
1861
|
2 = beautify
|
|
2154
1862
|
*/
|
|
1863
|
+
|
|
2155
1864
|
let len = pathData.length;
|
|
1865
|
+
let d = '';
|
|
2156
1866
|
|
|
2157
|
-
|
|
2158
|
-
let
|
|
2159
|
-
|
|
2160
|
-
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';
|
|
2161
1870
|
|
|
2162
|
-
|
|
2163
|
-
|
|
1871
|
+
if (mode < 1) {
|
|
1872
|
+
pathDataGrouped = [pathData[0]];
|
|
2164
1873
|
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
let
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
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;
|
|
2178
1892
|
}
|
|
1893
|
+
}
|
|
2179
1894
|
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
? " "
|
|
2185
|
-
: com.type);
|
|
1895
|
+
// stringify grouped
|
|
1896
|
+
len = pathDataGrouped.length;
|
|
1897
|
+
let separator_type = mode < 1 ? '' : ' ';
|
|
1898
|
+
let separator_command = mode < 1 ? '' : (mode === 1 ? ' ' : `\n`);
|
|
2186
1899
|
|
|
2187
|
-
|
|
2188
|
-
if (!mode) {
|
|
1900
|
+
typePrev = 'M';
|
|
2189
1901
|
|
|
2190
|
-
|
|
1902
|
+
for (let i = 0; i < len; i++) {
|
|
1903
|
+
let com = pathDataGrouped[i];
|
|
1904
|
+
let { type, values } = com;
|
|
2191
1905
|
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
let valStr = val.toString();
|
|
2195
|
-
let isFloat = valStr.includes('.');
|
|
2196
|
-
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'));
|
|
2197
1908
|
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
valStr = valStr.replace(/^0\./, '.');
|
|
2201
|
-
}
|
|
1909
|
+
// add type
|
|
1910
|
+
if (!omitType) d += type + separator_type;
|
|
2202
1911
|
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
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 ? '' : ' ');
|
|
2207
1936
|
|
|
2208
|
-
valsString += valStr;
|
|
2209
|
-
prevWasFloat = isSmallFloat;
|
|
2210
1937
|
}
|
|
2211
1938
|
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
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
|
+
|
|
2216
1948
|
}
|
|
2217
1949
|
|
|
2218
|
-
|
|
2219
|
-
d +=
|
|
2220
|
-
|
|
1950
|
+
// add command separator
|
|
1951
|
+
if (mode) d += separator_command;
|
|
1952
|
+
|
|
1953
|
+
// update previous type
|
|
1954
|
+
typePrev = type;
|
|
2221
1955
|
|
|
2222
|
-
if (mode < 1) {
|
|
2223
|
-
d = d
|
|
2224
|
-
.replace(/[A-Za-z]0(?=\.)/g, m => m[0])
|
|
2225
|
-
.replace(/ 0\./g, " .") // Space before small decimals
|
|
2226
|
-
.replace(/ -/g, "-") // Remove space before negatives
|
|
2227
|
-
.replace(/-0\./g, "-.") // Remove leading zero from negative decimals
|
|
2228
|
-
.replace(/Z/g, "z"); // Convert uppercase 'Z' to lowercase
|
|
2229
1956
|
}
|
|
2230
1957
|
|
|
2231
1958
|
return d;
|
|
@@ -2233,39 +1960,26 @@ function pathDataToD(pathData, mode = 0) {
|
|
|
2233
1960
|
|
|
2234
1961
|
function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, debug = false) {
|
|
2235
1962
|
|
|
2236
|
-
// cubic Bézier derivative
|
|
2237
|
-
const cubicDerivative = (p0, p1, p2, p3, t) => {
|
|
2238
|
-
let mt = 1 - t;
|
|
2239
|
-
|
|
2240
|
-
return {
|
|
2241
|
-
x:
|
|
2242
|
-
3 * mt * mt * (p1.x - p0.x) +
|
|
2243
|
-
6 * mt * t * (p2.x - p1.x) +
|
|
2244
|
-
3 * t * t * (p3.x - p2.x),
|
|
2245
|
-
y:
|
|
2246
|
-
3 * mt * mt * (p1.y - p0.y) +
|
|
2247
|
-
6 * mt * t * (p2.y - p1.y) +
|
|
2248
|
-
3 * t * t * (p3.y - p2.y)
|
|
2249
|
-
};
|
|
2250
|
-
};
|
|
2251
|
-
|
|
2252
1963
|
// if combining fails return original commands
|
|
2253
1964
|
let commands = [com1, com2];
|
|
2254
1965
|
|
|
2255
1966
|
// detect dominant
|
|
2256
|
-
let dist1 =
|
|
2257
|
-
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;
|
|
2258
1970
|
|
|
2259
|
-
|
|
1971
|
+
// take longer command
|
|
1972
|
+
let reverse = dist1 < dist2;
|
|
2260
1973
|
|
|
2261
1974
|
// backup original commands
|
|
2262
1975
|
let com1_o = JSON.parse(JSON.stringify(com1));
|
|
2263
1976
|
let com2_o = JSON.parse(JSON.stringify(com2));
|
|
2264
1977
|
|
|
2265
|
-
|
|
1978
|
+
// intersection of control tangents
|
|
1979
|
+
let ptI = checkLineIntersection(com1_o.p0, com1_o.cp1, com2_o.p, com2_o.cp2, false, true);
|
|
2266
1980
|
|
|
1981
|
+
// no intersection - we can't combine
|
|
2267
1982
|
if (!ptI) {
|
|
2268
|
-
|
|
2269
1983
|
return commands
|
|
2270
1984
|
}
|
|
2271
1985
|
|
|
@@ -2288,153 +2002,70 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, debug = f
|
|
|
2288
2002
|
com2 = com2_R;
|
|
2289
2003
|
}
|
|
2290
2004
|
|
|
2291
|
-
|
|
2292
|
-
let
|
|
2293
|
-
let
|
|
2294
|
-
let dot = (a, b) => a.x * b.x + a.y * b.y;
|
|
2295
|
-
|
|
2296
|
-
// estimate extrapolation parameter t0
|
|
2297
|
-
|
|
2298
|
-
let B0 = com2.p0;
|
|
2299
|
-
let D0 = cubicDerivative(
|
|
2300
|
-
com2.p0,
|
|
2301
|
-
com2.cp1,
|
|
2302
|
-
com2.cp2,
|
|
2303
|
-
com2.p,
|
|
2304
|
-
0
|
|
2305
|
-
);
|
|
2306
|
-
|
|
2307
|
-
let v = sub(com1.p0, B0);
|
|
2308
|
-
|
|
2309
|
-
// first-order projection onto tangent
|
|
2310
|
-
let t0 = dot(v, D0) / dot(D0, D0);
|
|
2311
|
-
|
|
2312
|
-
// refine with one Newton iteration (optional but cheap)
|
|
2313
|
-
let P = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], t0);
|
|
2314
|
-
let dP = cubicDerivative(com2.p0, com2.cp1, com2.cp2, com2.p, t0);
|
|
2315
|
-
let r = sub(P, com1.p0);
|
|
2316
|
-
|
|
2317
|
-
t0 -= dot(r, dP) / dot(dP, dP);
|
|
2318
|
-
|
|
2319
|
-
// construct merged cubic over [t0, 1]
|
|
2320
|
-
let Q0 = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], t0);
|
|
2321
|
-
let Q3 = com2.p;
|
|
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);
|
|
2322
2008
|
|
|
2323
|
-
let
|
|
2324
|
-
let
|
|
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;
|
|
2325
2012
|
|
|
2326
|
-
|
|
2013
|
+
// extrapolate
|
|
2014
|
+
let segs = pointAtT([com1.p0, com1.cp1, com1.cp2, com1.p], t, false, true).segments;
|
|
2327
2015
|
|
|
2328
|
-
let
|
|
2329
|
-
let Q2 = sub(Q3, mul(d1, scale / 3));
|
|
2016
|
+
let seg = segs[0];
|
|
2330
2017
|
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
cp1: Q1,
|
|
2334
|
-
cp2: Q2,
|
|
2335
|
-
p: Q3,
|
|
2336
|
-
t0
|
|
2337
|
-
};
|
|
2018
|
+
// if it worked - points should be nearby
|
|
2019
|
+
let dist = getDistManhattan(seg.p, com2.p);
|
|
2338
2020
|
|
|
2339
|
-
if
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
cp2: Q1,
|
|
2344
|
-
p: Q0,
|
|
2345
|
-
t0
|
|
2346
|
-
};
|
|
2347
|
-
}
|
|
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);
|
|
2348
2025
|
|
|
2349
|
-
|
|
2026
|
+
let angleDiff = (angle2 - angle);
|
|
2350
2027
|
|
|
2351
|
-
|
|
2352
|
-
|
|
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);
|
|
2353
2031
|
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
let cp1_2 = interpolate(result.p0, ptI_1, 1.333 );
|
|
2358
|
-
let cp2_2 = interpolate(result.p, ptI_2, 1.333 );
|
|
2359
|
-
|
|
2360
|
-
// test self intersections and exit
|
|
2361
|
-
let cp_intersection = checkLineIntersection(com1_o.p0, cp1_2, com2_o.p, cp2_2, true);
|
|
2362
|
-
if (cp_intersection) {
|
|
2363
|
-
|
|
2364
|
-
return commands;
|
|
2365
|
-
}
|
|
2032
|
+
// copy original final point coordinates
|
|
2033
|
+
seg.p = com2.p;
|
|
2366
2034
|
|
|
2367
|
-
|
|
2368
|
-
|
|
2035
|
+
// after rotation
|
|
2036
|
+
let dist2 = getDistManhattan(seg.p, seg.cp2);
|
|
2037
|
+
let scale = dist2 / dist1;
|
|
2369
2038
|
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
let dist4 = getDistAv(com2_o.p, result.p);
|
|
2373
|
-
let dist5 = (dist3 + dist4);
|
|
2039
|
+
// adjust tangent length
|
|
2040
|
+
seg.cp2 = interpolate(seg.p, seg.cp2, scale);
|
|
2374
2041
|
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
result.values = [result.cp1.x, result.cp1.y, result.cp2.x, result.cp2.y, result.p.x, result.p.y];
|
|
2384
|
-
|
|
2385
|
-
// extrapolated starting point is not completely off
|
|
2386
|
-
if (dist5 < maxDist) {
|
|
2387
|
-
|
|
2388
|
-
/*
|
|
2389
|
-
let tTotal = 1 + Math.abs(t0);
|
|
2390
|
-
let tSplit = reverse ? 1 + t0 : Math.abs(t0);
|
|
2391
|
-
|
|
2392
|
-
let pO = pointAtT([com2_o.p0, com2_o.cp1, com2_o.cp2, com2_o.p], t0);
|
|
2393
|
-
*/
|
|
2394
|
-
|
|
2395
|
-
// split t to meet original mid segment start point
|
|
2396
|
-
let tSplit = reverse ? 1 + t0 : Math.abs(t0);
|
|
2397
|
-
|
|
2398
|
-
let tTotal = 1 + Math.abs(t0);
|
|
2399
|
-
tSplit = reverse ? 1 + t0 : Math.abs(t0) / tTotal;
|
|
2400
|
-
|
|
2401
|
-
let ptSplit = pointAtT([result.p0, result.cp1, result.cp2, result.p], tSplit);
|
|
2402
|
-
let distSplit = getDistAv(ptSplit, com1.p);
|
|
2403
|
-
|
|
2404
|
-
// not close enough - exit
|
|
2405
|
-
if (distSplit > maxDist * tolerance ) {
|
|
2406
|
-
|
|
2407
|
-
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
|
+
};
|
|
2408
2050
|
}
|
|
2409
2051
|
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
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
|
|
2416
2065
|
|
|
2417
|
-
|
|
2418
|
-
let pathDataN = [
|
|
2419
|
-
{ type: 'M', values: [result.p0.x, result.p0.y] },
|
|
2420
|
-
{ type: 'C', values: [result.cp1.x, result.cp1.y, result.cp2.x, result.cp2.y, result.p.x, result.p.y] },
|
|
2066
|
+
}
|
|
2421
2067
|
];
|
|
2422
2068
|
|
|
2423
|
-
let areaN = getPathArea(pathDataN);
|
|
2424
|
-
let areaDiff = Math.abs(areaN / area0 - 1);
|
|
2425
|
-
|
|
2426
|
-
result.error = areaDiff * 5 * tolerance;
|
|
2427
|
-
|
|
2428
|
-
if (debug) {
|
|
2429
|
-
pathDataToD(pathDataN);
|
|
2430
|
-
|
|
2431
|
-
}
|
|
2432
|
-
|
|
2433
|
-
// success!!!
|
|
2434
|
-
if (areaDiff < 0.05 * tolerance) {
|
|
2435
|
-
commands = [result];
|
|
2436
|
-
|
|
2437
|
-
}
|
|
2438
2069
|
}
|
|
2439
2070
|
|
|
2440
2071
|
return commands
|
|
@@ -2557,11 +2188,12 @@ function combineCubicPairs(com1, com2, {
|
|
|
2557
2188
|
// quit if t is start
|
|
2558
2189
|
if (!t) return commands;
|
|
2559
2190
|
|
|
2191
|
+
// get averaged threshold
|
|
2560
2192
|
let distAv1 = getDistManhattan(com1.p0, com1.p);
|
|
2561
2193
|
let distAv2 = getDistManhattan(com2.p0, com2.p);
|
|
2562
2194
|
let distMin = Math.max(0, Math.min(distAv1, distAv2));
|
|
2563
2195
|
|
|
2564
|
-
let distScale = 0.
|
|
2196
|
+
let distScale = 0.075;
|
|
2565
2197
|
let maxDist = distMin * distScale * tolerance;
|
|
2566
2198
|
|
|
2567
2199
|
// get hypothetical combined command
|
|
@@ -2609,16 +2241,6 @@ function combineCubicPairs(com1, com2, {
|
|
|
2609
2241
|
|
|
2610
2242
|
if (error < maxDist) success = true;
|
|
2611
2243
|
|
|
2612
|
-
/*
|
|
2613
|
-
renderPoint(markers, ptM_seg1, 'cyan')
|
|
2614
|
-
renderPoint(markers, pt, 'orange', '1.5%', '1')
|
|
2615
|
-
renderPoint(markers, ptM_seg2, 'orange')
|
|
2616
|
-
|
|
2617
|
-
renderPoint(markers, com1.p, 'green')
|
|
2618
|
-
|
|
2619
|
-
renderPoint(markers, ptI_seg1, 'purple')
|
|
2620
|
-
*/
|
|
2621
|
-
|
|
2622
2244
|
}
|
|
2623
2245
|
|
|
2624
2246
|
} // end 1st try
|
|
@@ -2637,14 +2259,6 @@ function combineCubicPairs(com1, com2, {
|
|
|
2637
2259
|
comS.directionChange = com2.directionChange;
|
|
2638
2260
|
comS.corner = com2.corner;
|
|
2639
2261
|
|
|
2640
|
-
if (comS.extreme || comS.corner) ;
|
|
2641
|
-
|
|
2642
|
-
/*
|
|
2643
|
-
comS.extreme = com1.extreme;
|
|
2644
|
-
comS.directionChange = com1.directionChange;
|
|
2645
|
-
comS.corner = com1.corner;
|
|
2646
|
-
*/
|
|
2647
|
-
|
|
2648
2262
|
comS.values = [comS.cp1.x, comS.cp1.y, comS.cp2.x, comS.cp2.y, comS.p.x, comS.p.y];
|
|
2649
2263
|
|
|
2650
2264
|
// relative error
|
|
@@ -2703,7 +2317,9 @@ function findSplitT(com1, com2) {
|
|
|
2703
2317
|
}
|
|
2704
2318
|
*/
|
|
2705
2319
|
|
|
2706
|
-
|
|
2320
|
+
let t = l1 / l3;
|
|
2321
|
+
|
|
2322
|
+
return t;
|
|
2707
2323
|
}
|
|
2708
2324
|
|
|
2709
2325
|
function commandIsFlat(points, {
|
|
@@ -2756,7 +2372,7 @@ function analyzePathData(pathData = [], {
|
|
|
2756
2372
|
detectDirection = true,
|
|
2757
2373
|
detectSemiExtremes = false,
|
|
2758
2374
|
debug = false,
|
|
2759
|
-
addSquareLength =
|
|
2375
|
+
addSquareLength = false,
|
|
2760
2376
|
addArea = true,
|
|
2761
2377
|
|
|
2762
2378
|
} = {}) {
|
|
@@ -2805,8 +2421,8 @@ function analyzePathData(pathData = [], {
|
|
|
2805
2421
|
let commandPts = (type === 'C' || type === 'Q') ?
|
|
2806
2422
|
(type === 'C' ? [p0, cp1, cp2, p] : [p0, cp1, p]) :
|
|
2807
2423
|
([p0, p]);
|
|
2808
|
-
|
|
2809
|
-
let threshold =
|
|
2424
|
+
|
|
2425
|
+
let threshold = dimA * 0.005;
|
|
2810
2426
|
|
|
2811
2427
|
// bezier types
|
|
2812
2428
|
let isBezier = type === 'Q' || type === 'C';
|
|
@@ -2819,7 +2435,7 @@ function analyzePathData(pathData = [], {
|
|
|
2819
2435
|
*/
|
|
2820
2436
|
let hasExtremes = false;
|
|
2821
2437
|
|
|
2822
|
-
if (isBezier) {
|
|
2438
|
+
if (detectExtremes && isBezier) {
|
|
2823
2439
|
|
|
2824
2440
|
let dx = type === 'C' ? Math.abs(com.cp2.x - com.p.x) : Math.abs(com.cp1.x - com.p.x);
|
|
2825
2441
|
let dy = type === 'C' ? Math.abs(com.cp2.y - com.p.y) : Math.abs(com.cp1.y - com.p.y);
|
|
@@ -2858,7 +2474,7 @@ function analyzePathData(pathData = [], {
|
|
|
2858
2474
|
}
|
|
2859
2475
|
|
|
2860
2476
|
// check extremes introduce by small arcs
|
|
2861
|
-
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')) ){
|
|
2862
2478
|
let distN = comN ? comN.dimA : 0;
|
|
2863
2479
|
let isShort = com.dimA < (comPrev.dimA + distN) * 0.1;
|
|
2864
2480
|
let smallRadius = com.values[0] === com.values[1] && (com.values[0] < 1);
|
|
@@ -2876,28 +2492,7 @@ function analyzePathData(pathData = [], {
|
|
|
2876
2492
|
if (hasExtremes) com.extreme = true;
|
|
2877
2493
|
|
|
2878
2494
|
// Corners and semi extremes
|
|
2879
|
-
if (isBezier && isBezierN) {
|
|
2880
|
-
|
|
2881
|
-
// semi extremes
|
|
2882
|
-
if (detectSemiExtremes && !com.extreme) {
|
|
2883
|
-
|
|
2884
|
-
let dx1 = Math.abs(p.x - cp2.x);
|
|
2885
|
-
let dy1 = Math.abs(p.y - cp2.y);
|
|
2886
|
-
let hasSemiExtreme = false;
|
|
2887
|
-
|
|
2888
|
-
// exclude extremes or small deltas
|
|
2889
|
-
if (dx1 && dy1 && dx1 > thresholdLength || dy1 > thresholdLength) {
|
|
2890
|
-
let ang1 = getAngle(cp2, p);
|
|
2891
|
-
let ang2 = getAngle(p, comN.cp1);
|
|
2892
|
-
|
|
2893
|
-
let ang3 = Math.abs(ang1 + ang2) / 2;
|
|
2894
|
-
hasSemiExtreme = isMultipleOf45(ang3);
|
|
2895
|
-
}
|
|
2896
|
-
|
|
2897
|
-
if (hasSemiExtreme) {
|
|
2898
|
-
com.semiExtreme = true;
|
|
2899
|
-
}
|
|
2900
|
-
}
|
|
2495
|
+
if (detectCorners && isBezier && isBezierN) {
|
|
2901
2496
|
|
|
2902
2497
|
/**
|
|
2903
2498
|
* Detect direction change points
|
|
@@ -3473,7 +3068,12 @@ function convertPathData(pathData, {
|
|
|
3473
3068
|
if (hasShorthands && toLonghands) pathData = pathDataToLonghands(pathData);
|
|
3474
3069
|
|
|
3475
3070
|
// minify semicircle radii
|
|
3476
|
-
if (optimizeArcs)
|
|
3071
|
+
if (optimizeArcs) {
|
|
3072
|
+
pathData = optimizeArcPathData(pathData);
|
|
3073
|
+
} else {
|
|
3074
|
+
// get true absolute radii
|
|
3075
|
+
pathData = pathDataToTrueArcValues(pathData);
|
|
3076
|
+
}
|
|
3477
3077
|
|
|
3478
3078
|
if (toShorthands) pathData = pathDataToShorthands(pathData);
|
|
3479
3079
|
|
|
@@ -3563,9 +3163,42 @@ function parsePathDataNormalized(d,
|
|
|
3563
3163
|
}
|
|
3564
3164
|
|
|
3565
3165
|
/**
|
|
3566
|
-
*
|
|
3567
|
-
*
|
|
3568
|
-
|
|
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
|
|
3569
3202
|
*/
|
|
3570
3203
|
|
|
3571
3204
|
function optimizeArcPathData(pathData = []) {
|
|
@@ -3583,6 +3216,11 @@ function optimizeArcPathData(pathData = []) {
|
|
|
3583
3216
|
|
|
3584
3217
|
let [rx, ry, largeArc, x, y] = [values[0], values[1], values[3], values[5], values[6]];
|
|
3585
3218
|
let comPrev = pathData[i - 1];
|
|
3219
|
+
|
|
3220
|
+
// force absolute
|
|
3221
|
+
rx = Math.abs(rx);
|
|
3222
|
+
ry = Math.abs(ry);
|
|
3223
|
+
|
|
3586
3224
|
let [x0, y0] = [comPrev.values[comPrev.values.length - 2], comPrev.values[comPrev.values.length - 1]];
|
|
3587
3225
|
let M = { x: x0, y: y0 };
|
|
3588
3226
|
let p = { x, y };
|
|
@@ -3622,7 +3260,7 @@ function optimizeArcPathData(pathData = []) {
|
|
|
3622
3260
|
if (isHorizontal || isVertical) {
|
|
3623
3261
|
|
|
3624
3262
|
// check if semi circle
|
|
3625
|
-
let needsTrueR = isHorizontal ? rx*1.9 > diffX : ry*1.9 > diffY;
|
|
3263
|
+
let needsTrueR = isHorizontal ? rx * 1.9 > diffX : ry * 1.9 > diffY;
|
|
3626
3264
|
|
|
3627
3265
|
// is semicircle we can simplify rx
|
|
3628
3266
|
if (!needsTrueR) {
|
|
@@ -3727,7 +3365,7 @@ function pathDataArcsToCubics(pathData, {
|
|
|
3727
3365
|
|
|
3728
3366
|
if (com.type === 'A') {
|
|
3729
3367
|
// add all C commands instead of Arc
|
|
3730
|
-
let cubicArcs = arcToBezier
|
|
3368
|
+
let cubicArcs = arcToBezier(p0, com.values, arcAccuracy);
|
|
3731
3369
|
cubicArcs.forEach((cubicArc) => {
|
|
3732
3370
|
pathDataCubic.push(cubicArc);
|
|
3733
3371
|
});
|
|
@@ -4228,7 +3866,7 @@ function arcToBezierResolved({
|
|
|
4228
3866
|
* returns pathData array
|
|
4229
3867
|
*/
|
|
4230
3868
|
|
|
4231
|
-
function arcToBezier
|
|
3869
|
+
function arcToBezier(p0, values, splitSegments = 1) {
|
|
4232
3870
|
const TAU = Math.PI * 2;
|
|
4233
3871
|
let [rx, ry, rotation, largeArcFlag, sweepFlag, x, y] = values;
|
|
4234
3872
|
|
|
@@ -4239,8 +3877,8 @@ function arcToBezier$1(p0, values, splitSegments = 1) {
|
|
|
4239
3877
|
let phi = rotation ? rotation * TAU / 360 : 0;
|
|
4240
3878
|
let sinphi = phi ? Math.sin(phi) : 0;
|
|
4241
3879
|
let cosphi = phi ? Math.cos(phi) : 1;
|
|
4242
|
-
let pxp = cosphi * (p0.x - x)
|
|
4243
|
-
let pyp = -sinphi * (p0.x - x)
|
|
3880
|
+
let pxp = cosphi * (p0.x - x) *0.5 + sinphi * (p0.y - y) *0.5;
|
|
3881
|
+
let pyp = -sinphi * (p0.x - x) *0.5 + cosphi * (p0.y - y) *0.5;
|
|
4244
3882
|
|
|
4245
3883
|
if (pxp === 0 && pyp === 0) {
|
|
4246
3884
|
return []
|
|
@@ -4276,8 +3914,8 @@ function arcToBezier$1(p0, values, splitSegments = 1) {
|
|
|
4276
3914
|
|
|
4277
3915
|
let centerxp = radicant ? radicant * rx / ry * pyp : 0;
|
|
4278
3916
|
let centeryp = radicant ? radicant * -ry / rx * pxp : 0;
|
|
4279
|
-
let centerx = cosphi * centerxp - sinphi * centeryp + (p0.x + x)
|
|
4280
|
-
let centery = sinphi * centerxp + cosphi * centeryp + (p0.y + y)
|
|
3917
|
+
let centerx = cosphi * centerxp - sinphi * centeryp + (p0.x + x) * 0.5;
|
|
3918
|
+
let centery = sinphi * centerxp + cosphi * centeryp + (p0.y + y) * 0.5;
|
|
4281
3919
|
|
|
4282
3920
|
let vx1 = (pxp - centerxp) / rx;
|
|
4283
3921
|
let vy1 = (pyp - centeryp) / ry;
|
|
@@ -4299,15 +3937,17 @@ function arcToBezier$1(p0, values, splitSegments = 1) {
|
|
|
4299
3937
|
ang2 = vectorAngle(vx1, vy1, vx2, vy2);
|
|
4300
3938
|
|
|
4301
3939
|
if (sweepFlag === 0 && ang2 > 0) {
|
|
4302
|
-
|
|
3940
|
+
|
|
3941
|
+
ang2 -= TAU;
|
|
4303
3942
|
}
|
|
4304
3943
|
else if (sweepFlag === 1 && ang2 < 0) {
|
|
4305
|
-
|
|
3944
|
+
|
|
3945
|
+
ang2 += TAU;
|
|
4306
3946
|
}
|
|
4307
3947
|
|
|
4308
|
-
let ratio = +(Math.abs(ang2) / (TAU
|
|
3948
|
+
let ratio = +(Math.abs(ang2) / (TAU * 0.25)).toFixed(0) || 1;
|
|
4309
3949
|
|
|
4310
|
-
// increase segments for more
|
|
3950
|
+
// increase segments for more accurate length calculations
|
|
4311
3951
|
let segments = ratio * splitSegments;
|
|
4312
3952
|
ang2 /= segments;
|
|
4313
3953
|
let pathDataArc = [];
|
|
@@ -4319,7 +3959,8 @@ function arcToBezier$1(p0, values, splitSegments = 1) {
|
|
|
4319
3959
|
const k = 0.551785;
|
|
4320
3960
|
let a = ang2 === angle90 ? k :
|
|
4321
3961
|
(
|
|
4322
|
-
|
|
3962
|
+
|
|
3963
|
+
ang2 === -angle90 ? -k : 1.33333 * Math.tan(ang2 * 0.25)
|
|
4323
3964
|
);
|
|
4324
3965
|
|
|
4325
3966
|
let cos2 = ang2 ? Math.cos(ang2) : 1;
|
|
@@ -4369,59 +4010,50 @@ function pathDataRemoveColinear(pathData, {
|
|
|
4369
4010
|
pathData[pathData.length - 1].type.toLowerCase() === 'z';
|
|
4370
4011
|
|
|
4371
4012
|
for (let c = 1, l = pathData.length; c < l; c++) {
|
|
4372
|
-
|
|
4373
4013
|
let com = pathData[c];
|
|
4014
|
+
let { type, values } = com;
|
|
4374
4015
|
let comN = pathData[c + 1] || pathData[l - 1];
|
|
4375
|
-
|
|
4016
|
+
let valuesN = comN.values;
|
|
4376
4017
|
let p1 = comN.type.toLowerCase() === 'z' ? M : { x: comN.values[comN.values.length - 2], y: comN.values[comN.values.length - 1] };
|
|
4377
4018
|
|
|
4378
|
-
let { type, values } = com;
|
|
4379
4019
|
let valsL = values.slice(-2);
|
|
4380
4020
|
p = type !== 'Z' ? { x: valsL[0], y: valsL[1] } : M;
|
|
4381
4021
|
|
|
4022
|
+
let nextBezier = type!==comN.type && (comN.type==='C' || comN.type==='Q');
|
|
4023
|
+
|
|
4382
4024
|
let area = p1 ? getPolygonArea([p0, p, p1], true) : Infinity;
|
|
4383
4025
|
let distSquare = getSquareDistance(p0, p1);
|
|
4384
4026
|
let distMax = distSquare ? distSquare / 333 * tolerance : 0;
|
|
4385
4027
|
|
|
4386
4028
|
let isFlat = area < distMax;
|
|
4387
|
-
|
|
4388
4029
|
let isFlatBez = false;
|
|
4030
|
+
let cpts = [];
|
|
4389
4031
|
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
let dx2 = Math.abs(p1.x - p.x)
|
|
4399
|
-
let dy2 = Math.abs(p1.y - p.y)
|
|
4400
|
-
|
|
4401
|
-
// zero length segments are flat
|
|
4402
|
-
let isZeroLength = (!dy1 && !dx1) || (!dy2 && !dx2)
|
|
4403
|
-
if (isZeroLength) isFlat = true;
|
|
4404
|
-
|
|
4405
|
-
// check cross products for colinearity
|
|
4406
|
-
if (!isFlat) {
|
|
4407
|
-
|
|
4408
|
-
let cross0 = Math.abs(dx0 * dy1 - dy0 * dx1);
|
|
4032
|
+
/**
|
|
4033
|
+
* type change
|
|
4034
|
+
* check flatness
|
|
4035
|
+
*/
|
|
4036
|
+
if (nextBezier) {
|
|
4037
|
+
cpts = comN.type === 'C' ?
|
|
4038
|
+
[{ x: valuesN[0], y: valuesN[1] }, { x: valuesN[2], y: valuesN[3] }] :
|
|
4039
|
+
(comN.type === 'Q' ? [{ x: valuesN[0], y: valuesN[1] }] : []);
|
|
4409
4040
|
|
|
4410
|
-
|
|
4041
|
+
isFlat = commandIsFlat([p0, ...cpts, p], { tolerance });
|
|
4411
4042
|
|
|
4412
|
-
|
|
4043
|
+
/*
|
|
4413
4044
|
|
|
4414
|
-
|
|
4045
|
+
if(!isFlatBez){
|
|
4046
|
+
pathDataN.push(com)
|
|
4047
|
+
continue
|
|
4415
4048
|
}
|
|
4416
|
-
|
|
4417
|
-
*/
|
|
4049
|
+
*/
|
|
4418
4050
|
|
|
4419
|
-
|
|
4051
|
+
}
|
|
4420
4052
|
|
|
4421
4053
|
// convert flat beziers to linetos
|
|
4422
4054
|
if (flatBezierToLinetos && (type === 'C' || type === 'Q')) {
|
|
4423
4055
|
|
|
4424
|
-
|
|
4056
|
+
cpts = type === 'C' ?
|
|
4425
4057
|
[{ x: values[0], y: values[1] }, { x: values[2], y: values[3] }] :
|
|
4426
4058
|
(type === 'Q' ? [{ x: values[0], y: values[1] }] : []);
|
|
4427
4059
|
|
|
@@ -4435,10 +4067,13 @@ function pathDataRemoveColinear(pathData, {
|
|
|
4435
4067
|
}
|
|
4436
4068
|
}
|
|
4437
4069
|
|
|
4438
|
-
|
|
4070
|
+
/**
|
|
4071
|
+
* colinear = simplification success
|
|
4072
|
+
* exclude arcs (as always =)
|
|
4073
|
+
* as semicircles won't have an area
|
|
4074
|
+
*/
|
|
4439
4075
|
|
|
4440
4076
|
if (isFlat && c < l - 1 && comN.type !== 'A' && (type === 'L' || (flatBezierToLinetos && isFlatBez))) {
|
|
4441
|
-
|
|
4442
4077
|
continue;
|
|
4443
4078
|
}
|
|
4444
4079
|
|
|
@@ -4803,9 +4438,7 @@ function refineAdjacentExtremes(pathData, {
|
|
|
4803
4438
|
if (comEx.length === 1) {
|
|
4804
4439
|
|
|
4805
4440
|
comEx = comEx[0];
|
|
4806
|
-
|
|
4807
4441
|
pathData[i + 1] = null;
|
|
4808
|
-
|
|
4809
4442
|
pathData[i + 2].values = [comEx.cp1.x, comEx.cp1.y, comEx.cp2.x, comEx.cp2.y, comEx.p.x, comEx.p.y];
|
|
4810
4443
|
pathData[i + 2].cp1 = comEx.cp1;
|
|
4811
4444
|
pathData[i + 2].cp2 = comEx.cp2;
|