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
|
@@ -377,7 +377,14 @@ const {
|
|
|
377
377
|
|
|
378
378
|
// get angle helper
|
|
379
379
|
function getAngle(p1, p2, normalize = false) {
|
|
380
|
-
let angle = atan2(p2.y - p1.y, p2.x - p1.x);
|
|
380
|
+
let angle = Math.atan2(p2.y - p1.y, p2.x - p1.x);
|
|
381
|
+
// normalize negative angles
|
|
382
|
+
if (normalize && angle < 0) angle += Math.PI * 2;
|
|
383
|
+
return angle
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function getAngleFromDelta(dx, dy, normalize = false) {
|
|
387
|
+
let angle = Math.atan2(dy, dx);
|
|
381
388
|
// normalize negative angles
|
|
382
389
|
if (normalize && angle < 0) angle += Math.PI * 2;
|
|
383
390
|
return angle
|
|
@@ -473,7 +480,9 @@ function checkLineIntersection(p1 = null, p2 = null, p3 = null, p4 = null, exact
|
|
|
473
480
|
// if we cast these lines infinitely in both directions, they intersect here:
|
|
474
481
|
intersectionPoint = {
|
|
475
482
|
x: p1.x + (a * (p2.x - p1.x)),
|
|
476
|
-
y: p1.y + (a * (p2.y - p1.y))
|
|
483
|
+
y: p1.y + (a * (p2.y - p1.y)),
|
|
484
|
+
t1: a,
|
|
485
|
+
t2: b
|
|
477
486
|
};
|
|
478
487
|
|
|
479
488
|
let intersection = false;
|
|
@@ -575,13 +584,13 @@ function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, returnArray
|
|
|
575
584
|
if (t === 0 && !shortCp1) {
|
|
576
585
|
pt.x = p0.x;
|
|
577
586
|
pt.y = p0.y;
|
|
578
|
-
pt.angle = getAngle(p0, cp1);
|
|
587
|
+
if (getTangent) pt.angle = getAngle(p0, cp1);
|
|
579
588
|
}
|
|
580
589
|
|
|
581
590
|
else if (t === 1 && !shortCp2) {
|
|
582
591
|
pt.x = p.x;
|
|
583
592
|
pt.y = p.y;
|
|
584
|
-
pt.angle = getAngle(cp2, p);
|
|
593
|
+
if (getTangent) pt.angle = getAngle(cp2, p);
|
|
585
594
|
}
|
|
586
595
|
|
|
587
596
|
else {
|
|
@@ -598,18 +607,38 @@ function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, returnArray
|
|
|
598
607
|
pt = interpolate(m3, m4, t);
|
|
599
608
|
|
|
600
609
|
// add angles
|
|
601
|
-
pt.angle = getAngle(m3, m4);
|
|
610
|
+
if (getTangent) pt.angle = getAngle(m3, m4);
|
|
611
|
+
|
|
612
|
+
/*
|
|
613
|
+
renderPoint(markers, m0, 'cyan')
|
|
614
|
+
renderPoint(markers, m3, 'magenta')
|
|
615
|
+
renderPoint(markers, pt, 'green')
|
|
616
|
+
renderPoint(markers, m4, 'cyan')
|
|
617
|
+
renderPoint(markers, m2, 'magenta')
|
|
618
|
+
*/
|
|
602
619
|
|
|
603
620
|
// add control points
|
|
604
|
-
if (getCpts)
|
|
621
|
+
if (getCpts) {
|
|
622
|
+
pt.cpts = [m1, m2, m3, m4];
|
|
623
|
+
pt.segments = [
|
|
624
|
+
{ p0, cp1: m0, cp2: m3, p: pt },
|
|
625
|
+
{ p0: pt, cp1: m4, cp2: m2, p }
|
|
626
|
+
];
|
|
627
|
+
}
|
|
605
628
|
} else {
|
|
606
629
|
m1 = interpolate(p0, cp1, t);
|
|
607
630
|
m2 = interpolate(cp1, p, t);
|
|
608
631
|
pt = interpolate(m1, m2, t);
|
|
609
|
-
pt.angle = getAngle(m1, m2);
|
|
632
|
+
if (getTangent) pt.angle = getAngle(m1, m2);
|
|
610
633
|
|
|
611
634
|
// add control points
|
|
612
|
-
if (getCpts)
|
|
635
|
+
if (getCpts) {
|
|
636
|
+
pt.cpts = [m1, m2];
|
|
637
|
+
pt.segments = [
|
|
638
|
+
{ p0, cp1: m1, p: pt },
|
|
639
|
+
{ p0: p, cp1: m2, p }
|
|
640
|
+
];
|
|
641
|
+
}
|
|
613
642
|
}
|
|
614
643
|
}
|
|
615
644
|
|
|
@@ -693,7 +722,7 @@ function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, returnArray
|
|
|
693
722
|
* get vertices from path command final on-path points
|
|
694
723
|
*/
|
|
695
724
|
|
|
696
|
-
function getPathDataVertices(pathData=[], includeCpts = false, decimals = -1) {
|
|
725
|
+
function getPathDataVertices(pathData = [], includeCpts = false, decimals = -1) {
|
|
697
726
|
let polyPoints = [];
|
|
698
727
|
|
|
699
728
|
pathData.forEach((com) => {
|
|
@@ -780,7 +809,8 @@ function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2,
|
|
|
780
809
|
|
|
781
810
|
if (rx == 0 || ry == 0) {
|
|
782
811
|
// invalid arguments
|
|
783
|
-
|
|
812
|
+
console.warn("rx and ry can not be 0");
|
|
813
|
+
return arcData
|
|
784
814
|
}
|
|
785
815
|
|
|
786
816
|
/**
|
|
@@ -793,10 +823,10 @@ function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2,
|
|
|
793
823
|
let s_phi = !phi ? 0 : Math.sin(phi);
|
|
794
824
|
let c_phi = !phi ? 1 : Math.cos(phi);
|
|
795
825
|
|
|
796
|
-
let hd_x = (x1 - x2)
|
|
797
|
-
let hd_y = (y1 - y2)
|
|
798
|
-
let hs_x = (x1 + x2)
|
|
799
|
-
let hs_y = (y1 + y2)
|
|
826
|
+
let hd_x = (x1 - x2) * 0.5;
|
|
827
|
+
let hd_y = (y1 - y2) * 0.5;
|
|
828
|
+
let hs_x = (x1 + x2) * 0.5;
|
|
829
|
+
let hs_y = (y1 + y2) * 0.5;
|
|
800
830
|
|
|
801
831
|
// F6.5.1
|
|
802
832
|
let x1_ = !phi ? hd_x : c_phi * hd_x + s_phi * hd_y;
|
|
@@ -818,10 +848,11 @@ function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2,
|
|
|
818
848
|
let rxry = rx * ry;
|
|
819
849
|
let rxy1_ = rx * y1_;
|
|
820
850
|
let ryx1_ = ry * x1_;
|
|
821
|
-
let sum_of_sq = rxy1_
|
|
851
|
+
let sum_of_sq = rxy1_ * rxy1_ + ryx1_ * ryx1_; // sum of square
|
|
822
852
|
if (!sum_of_sq) {
|
|
853
|
+
console.warn("start point can not be same as end point");
|
|
854
|
+
return arcData
|
|
823
855
|
|
|
824
|
-
throw Error("start point can not be same as end point");
|
|
825
856
|
}
|
|
826
857
|
let coe = Math.sqrt(Math.abs((rxry * rxry - sum_of_sq) / sum_of_sq));
|
|
827
858
|
if (largeArc === sweep) {
|
|
@@ -970,7 +1001,11 @@ function bezierhasExtreme(p0 = null, cpts = []) {
|
|
|
970
1001
|
|
|
971
1002
|
function getBezierExtremeT(pts, { addExtremes = true, addSemiExtremes = false } = {}) {
|
|
972
1003
|
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 });
|
|
973
|
-
|
|
1004
|
+
if (tArr.length) {
|
|
1005
|
+
tArr = tArr.map(t => +t.toFixed(9)).sort();
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
return tArr
|
|
974
1009
|
}
|
|
975
1010
|
|
|
976
1011
|
function getArcExtemes(p0, values) {
|
|
@@ -1300,13 +1335,6 @@ function cubicBezierExtremeT(p0, cp1, cp2, p,
|
|
|
1300
1335
|
return tArr;
|
|
1301
1336
|
}
|
|
1302
1337
|
|
|
1303
|
-
function isMultipleOf45(angleRad, epsilon = 0.001) {
|
|
1304
|
-
let rad90 = Math.PI / 2;
|
|
1305
|
-
let rad45 = rad90 / 2;
|
|
1306
|
-
let isRightAngle = Math.abs(angleRad / rad90 - Math.round(angleRad / rad90)) < epsilon;
|
|
1307
|
-
return !isRightAngle ? Math.abs(angleRad / rad45 - Math.round(angleRad / rad45)) < epsilon : false;
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
1338
|
function quadraticBezierExtremeT(p0, cp1, p, { addExtremes = true, addSemiExtremes = false } = {}) {
|
|
1311
1339
|
|
|
1312
1340
|
// rotate cpts for semi extremes
|
|
@@ -1370,6 +1398,11 @@ function getDistance(p1, p2, isArray = false) {
|
|
|
1370
1398
|
let dx = isArray ? p2[0] - p1[0] : (p2.x - p1.x);
|
|
1371
1399
|
let dy = isArray ? p2[1] - p1[1] : (p2.y - p1.y);
|
|
1372
1400
|
|
|
1401
|
+
/*
|
|
1402
|
+
let sqrt2 = 1.4142135623730951
|
|
1403
|
+
return dx===dy ? Math.abs(dx) * sqrt2 : Math.sqrt(dx * dx + dy * dy);
|
|
1404
|
+
*/
|
|
1405
|
+
|
|
1373
1406
|
return Math.sqrt(dx * dx + dy * dy);
|
|
1374
1407
|
}
|
|
1375
1408
|
|
|
@@ -1385,6 +1418,7 @@ function getSquareDistance(p1, p2) {
|
|
|
1385
1418
|
* sloppy but fast
|
|
1386
1419
|
*/
|
|
1387
1420
|
function getDistManhattan(pt1, pt2) {
|
|
1421
|
+
|
|
1388
1422
|
let dx = Math.abs(pt2.x - pt1.x);
|
|
1389
1423
|
let dy = Math.abs(pt2.y - pt1.y);
|
|
1390
1424
|
return dx + dy;
|
|
@@ -1852,7 +1886,7 @@ function roundPathData(pathData, decimalsGlobal = -1) {
|
|
|
1852
1886
|
|
|
1853
1887
|
let len = pathData.length;
|
|
1854
1888
|
let decimals = decimalsGlobal;
|
|
1855
|
-
let decimalsArc = decimals < 3 ? decimals+
|
|
1889
|
+
let decimalsArc = decimals < 3 ? decimals + 1 : decimals;
|
|
1856
1890
|
|
|
1857
1891
|
for (let c = 0; c < len; c++) {
|
|
1858
1892
|
let com = pathData[c];
|
|
@@ -1887,7 +1921,7 @@ function detectAccuracyPoly(pts) {
|
|
|
1887
1921
|
}
|
|
1888
1922
|
}
|
|
1889
1923
|
|
|
1890
|
-
let dim_min = dims.sort();
|
|
1924
|
+
let dim_min = dims.sort((a,b)=>a-b);
|
|
1891
1925
|
let sliceIdx = Math.ceil(dim_min.length / 8);
|
|
1892
1926
|
dim_min = dim_min.slice(0, sliceIdx);
|
|
1893
1927
|
let minVal = dim_min.reduce((a, b) => a + b, 0) / sliceIdx;
|
|
@@ -1907,31 +1941,50 @@ function detectAccuracy(pathData) {
|
|
|
1907
1941
|
let com = pathData[i];
|
|
1908
1942
|
let { type, values, p0 = null, p = null, dimA = 0 } = com;
|
|
1909
1943
|
|
|
1910
|
-
// use existing
|
|
1944
|
+
// use existing average dimension value or calculate
|
|
1911
1945
|
if (values.length && p && p0) {
|
|
1912
|
-
|
|
1913
1946
|
dimA = dimA ? dimA : getDistManhattan(p0, p);
|
|
1914
1947
|
|
|
1915
|
-
if (
|
|
1948
|
+
if (type === 'A') dimA *= 0.5;
|
|
1916
1949
|
|
|
1950
|
+
if (dimA) dims.push(+dimA.toFixed(8));
|
|
1917
1951
|
}
|
|
1918
1952
|
|
|
1919
1953
|
}
|
|
1920
1954
|
|
|
1921
|
-
|
|
1955
|
+
dims = dims.sort((a,b)=>a-b);
|
|
1956
|
+
let len = dims.length;
|
|
1957
|
+
let dim_mid = dims[Math.floor(len * 0.5)];
|
|
1922
1958
|
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
let
|
|
1959
|
+
// smallest 25% of values
|
|
1960
|
+
let idx_q = Math.ceil(len * 0.25);
|
|
1961
|
+
let dims_min = dims.slice(0, idx_q);
|
|
1962
|
+
|
|
1963
|
+
// average smallest values with mid value
|
|
1964
|
+
let dim_min = ((dims_min.reduce((a, b) => a + b, 0) / idx_q) + dim_mid) * 0.5;
|
|
1926
1965
|
|
|
1927
1966
|
let threshold = 75;
|
|
1928
|
-
let decimalsAuto =
|
|
1967
|
+
let decimalsAuto = dim_min > threshold * 1.5 ? 0 : Math.floor(threshold / dim_min).toString().length;
|
|
1929
1968
|
|
|
1930
1969
|
// clamp
|
|
1931
1970
|
return Math.min(Math.max(0, decimalsAuto), 8)
|
|
1932
1971
|
|
|
1933
1972
|
}
|
|
1934
1973
|
|
|
1974
|
+
function roundPoly(poly = [], decimals = 3) {
|
|
1975
|
+
if (!poly.length || decimals < 0) return poly;
|
|
1976
|
+
poly = poly.map(pt => roundPoint(pt, decimals));
|
|
1977
|
+
return poly
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
function roundPoint(pt = {}, decimals = 3) {
|
|
1981
|
+
if (pt.x === undefined || pt.y === undefined || decimals < 0) return pt;
|
|
1982
|
+
pt.x=roundTo(pt.x, decimals);
|
|
1983
|
+
pt.y=roundTo(pt.y, decimals);
|
|
1984
|
+
return pt
|
|
1985
|
+
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1935
1988
|
/**
|
|
1936
1989
|
* rounding helper
|
|
1937
1990
|
* allows for quantized rounding
|
|
@@ -2494,7 +2547,7 @@ function getElementAtts(el, {x=0, y=0, width=0, height=0}={}){
|
|
|
2494
2547
|
attributes.forEach(att=>{
|
|
2495
2548
|
|
|
2496
2549
|
let value = normalizeUnits(el.getAttribute(att), {x, y, width, height});
|
|
2497
|
-
atts[att
|
|
2550
|
+
atts[att] = value;
|
|
2498
2551
|
});
|
|
2499
2552
|
|
|
2500
2553
|
return atts
|
|
@@ -2706,7 +2759,7 @@ function getPathDataBBox(pathData) {
|
|
|
2706
2759
|
}
|
|
2707
2760
|
}
|
|
2708
2761
|
|
|
2709
|
-
let bbox = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin };
|
|
2762
|
+
let bbox = { x: xMin, y: yMin, right: xMax, width: xMax - xMin, bottom: yMax, height: yMax - yMin };
|
|
2710
2763
|
return bbox
|
|
2711
2764
|
}
|
|
2712
2765
|
|
|
@@ -2896,7 +2949,7 @@ function getPolygonArea(points, absolute=false) {
|
|
|
2896
2949
|
* d attribute string
|
|
2897
2950
|
*/
|
|
2898
2951
|
|
|
2899
|
-
function pathDataToD(pathData, mode = 0) {
|
|
2952
|
+
function pathDataToD(pathData = [], mode = 0) {
|
|
2900
2953
|
|
|
2901
2954
|
mode = parseFloat(mode);
|
|
2902
2955
|
/*
|
|
@@ -2905,80 +2958,99 @@ function pathDataToD(pathData, mode = 0) {
|
|
|
2905
2958
|
1 = verbose
|
|
2906
2959
|
2 = beautify
|
|
2907
2960
|
*/
|
|
2961
|
+
|
|
2908
2962
|
let len = pathData.length;
|
|
2963
|
+
let d = '';
|
|
2909
2964
|
|
|
2910
|
-
|
|
2911
|
-
let
|
|
2912
|
-
|
|
2913
|
-
let separator_type = mode > 0.5 ? ' ' : '';
|
|
2965
|
+
// group same types
|
|
2966
|
+
let pathDataGrouped = mode >0.5 ? JSON.parse(JSON.stringify(pathData)) : [];
|
|
2967
|
+
let typePrev = 'M';
|
|
2914
2968
|
|
|
2915
|
-
|
|
2916
|
-
|
|
2969
|
+
if (mode < 1) {
|
|
2970
|
+
pathDataGrouped = [pathData[0]];
|
|
2917
2971
|
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
let
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2972
|
+
let idx = 0;
|
|
2973
|
+
|
|
2974
|
+
for (let i = 1; i < len; i++) {
|
|
2975
|
+
let com = pathData[i];
|
|
2976
|
+
let { type } = com;
|
|
2977
|
+
// decouple from object
|
|
2978
|
+
let values = [...com.values];
|
|
2979
|
+
|
|
2980
|
+
// new type
|
|
2981
|
+
if (type !== typePrev) {
|
|
2982
|
+
pathDataGrouped.push({type, values});
|
|
2983
|
+
idx++;
|
|
2984
|
+
} else {
|
|
2985
|
+
pathDataGrouped[idx].values.push(...values);
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
// update type
|
|
2989
|
+
typePrev = type;
|
|
2931
2990
|
}
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
// stringify grouped
|
|
2994
|
+
len = pathDataGrouped.length;
|
|
2995
|
+
let separator_type = mode < 1 ? '' : ' ';
|
|
2996
|
+
let separator_command = mode < 1 ? '' : (mode === 1 ? ' ' : `\n`);
|
|
2932
2997
|
|
|
2933
|
-
|
|
2934
|
-
type = ((mode < 1) && com0.type === com.type && com.type.toLowerCase() !== 'm')
|
|
2935
|
-
? " "
|
|
2936
|
-
: ((mode < 1) && com0.type === "M" && com.type === "L"
|
|
2937
|
-
? " "
|
|
2938
|
-
: com.type);
|
|
2998
|
+
typePrev = 'M';
|
|
2939
2999
|
|
|
2940
|
-
|
|
2941
|
-
|
|
3000
|
+
for (let i = 0; i < len; i++) {
|
|
3001
|
+
let com = pathDataGrouped[i];
|
|
3002
|
+
let { type, values } = com;
|
|
2942
3003
|
|
|
2943
|
-
|
|
3004
|
+
// we're always starting a path with absolute M!
|
|
3005
|
+
let omitType = mode < 1 && ((typePrev === 'M' && type === 'L') || (typePrev === 'm' && type === 'l'));
|
|
2944
3006
|
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
let valStr = val.toString();
|
|
2948
|
-
let isFloat = valStr.includes('.');
|
|
2949
|
-
let isSmallFloat = isFloat && Math.abs(val) < 1;
|
|
3007
|
+
// add type
|
|
3008
|
+
if (!omitType) d += type + separator_type;
|
|
2950
3009
|
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
}
|
|
3010
|
+
// add values
|
|
3011
|
+
let wasSmallFloat = false;
|
|
3012
|
+
let separatorVal = ' ';
|
|
2955
3013
|
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
3014
|
+
for (let v = 0, vlen = values.length; vlen && v < vlen; v++) {
|
|
3015
|
+
let val = values[v];
|
|
3016
|
+
let valAbs = Math.abs(val);
|
|
3017
|
+
let valStr = val.toString();
|
|
3018
|
+
let isNegative = val < 0;
|
|
3019
|
+
let sign = isNegative ? '-' : '';
|
|
3020
|
+
let isSmallFloat = mode > 0.5 ? false : (val && valAbs < 1);
|
|
3021
|
+
let idxSub = isSmallFloat ? (isNegative ? 2 : 1) : 0;
|
|
3022
|
+
|
|
3023
|
+
// we don't need whitespace for first value
|
|
3024
|
+
separatorVal = v === 0 || isNegative ? '' : ' ';
|
|
3025
|
+
|
|
3026
|
+
if (mode < 1) {
|
|
3027
|
+
// omit leading zero
|
|
3028
|
+
if (isSmallFloat) valStr = sign + valStr.substring(idxSub);
|
|
3029
|
+
|
|
3030
|
+
// omit whitespace for subsequent small floats
|
|
3031
|
+
separatorVal = (v === 0 && !omitType) || (wasSmallFloat && isSmallFloat) ?
|
|
3032
|
+
(!mode ? '' : (isNegative ? '' : ' '))
|
|
3033
|
+
: (isNegative ? '' : ' ');
|
|
2960
3034
|
|
|
2961
|
-
valsString += valStr;
|
|
2962
|
-
prevWasFloat = isSmallFloat;
|
|
2963
3035
|
}
|
|
2964
3036
|
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
3037
|
+
// omit separator between large Arc sweep and final x in minify mode
|
|
3038
|
+
if (!mode && (type === 'a' || type === 'A')) {
|
|
3039
|
+
let pos = (v % 7);
|
|
3040
|
+
if (pos > 3 && pos < 6) separatorVal = '';
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
d += `${separatorVal}${valStr}`;
|
|
3044
|
+
wasSmallFloat = isSmallFloat;
|
|
3045
|
+
|
|
2969
3046
|
}
|
|
2970
3047
|
|
|
2971
|
-
|
|
2972
|
-
d +=
|
|
2973
|
-
|
|
3048
|
+
// add command separator
|
|
3049
|
+
if (mode) d += separator_command;
|
|
3050
|
+
|
|
3051
|
+
// update previous type
|
|
3052
|
+
typePrev = type;
|
|
2974
3053
|
|
|
2975
|
-
if (mode < 1) {
|
|
2976
|
-
d = d
|
|
2977
|
-
.replace(/[A-Za-z]0(?=\.)/g, m => m[0])
|
|
2978
|
-
.replace(/ 0\./g, " .") // Space before small decimals
|
|
2979
|
-
.replace(/ -/g, "-") // Remove space before negatives
|
|
2980
|
-
.replace(/-0\./g, "-.") // Remove leading zero from negative decimals
|
|
2981
|
-
.replace(/Z/g, "z"); // Convert uppercase 'Z' to lowercase
|
|
2982
3054
|
}
|
|
2983
3055
|
|
|
2984
3056
|
return d;
|
|
@@ -2986,39 +3058,26 @@ function pathDataToD(pathData, mode = 0) {
|
|
|
2986
3058
|
|
|
2987
3059
|
function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, debug = false) {
|
|
2988
3060
|
|
|
2989
|
-
// cubic Bézier derivative
|
|
2990
|
-
const cubicDerivative = (p0, p1, p2, p3, t) => {
|
|
2991
|
-
let mt = 1 - t;
|
|
2992
|
-
|
|
2993
|
-
return {
|
|
2994
|
-
x:
|
|
2995
|
-
3 * mt * mt * (p1.x - p0.x) +
|
|
2996
|
-
6 * mt * t * (p2.x - p1.x) +
|
|
2997
|
-
3 * t * t * (p3.x - p2.x),
|
|
2998
|
-
y:
|
|
2999
|
-
3 * mt * mt * (p1.y - p0.y) +
|
|
3000
|
-
6 * mt * t * (p2.y - p1.y) +
|
|
3001
|
-
3 * t * t * (p3.y - p2.y)
|
|
3002
|
-
};
|
|
3003
|
-
};
|
|
3004
|
-
|
|
3005
3061
|
// if combining fails return original commands
|
|
3006
3062
|
let commands = [com1, com2];
|
|
3007
3063
|
|
|
3008
3064
|
// detect dominant
|
|
3009
|
-
let dist1 =
|
|
3010
|
-
let dist2 =
|
|
3065
|
+
let dist1 = getDistManhattan(com1.p0, com1.p);
|
|
3066
|
+
let dist2 = getDistManhattan(com2.p0, com2.p);
|
|
3067
|
+
let thresh = (dist1 + dist2) * 0.5 * 0.075 * tolerance;
|
|
3011
3068
|
|
|
3012
|
-
|
|
3069
|
+
// take longer command
|
|
3070
|
+
let reverse = dist1 < dist2;
|
|
3013
3071
|
|
|
3014
3072
|
// backup original commands
|
|
3015
3073
|
let com1_o = JSON.parse(JSON.stringify(com1));
|
|
3016
3074
|
let com2_o = JSON.parse(JSON.stringify(com2));
|
|
3017
3075
|
|
|
3018
|
-
|
|
3076
|
+
// intersection of control tangents
|
|
3077
|
+
let ptI = checkLineIntersection(com1_o.p0, com1_o.cp1, com2_o.p, com2_o.cp2, false, true);
|
|
3019
3078
|
|
|
3079
|
+
// no intersection - we can't combine
|
|
3020
3080
|
if (!ptI) {
|
|
3021
|
-
|
|
3022
3081
|
return commands
|
|
3023
3082
|
}
|
|
3024
3083
|
|
|
@@ -3041,153 +3100,70 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, debug = f
|
|
|
3041
3100
|
com2 = com2_R;
|
|
3042
3101
|
}
|
|
3043
3102
|
|
|
3044
|
-
|
|
3045
|
-
let
|
|
3046
|
-
let
|
|
3047
|
-
let dot = (a, b) => a.x * b.x + a.y * b.y;
|
|
3048
|
-
|
|
3049
|
-
// estimate extrapolation parameter t0
|
|
3050
|
-
|
|
3051
|
-
let B0 = com2.p0;
|
|
3052
|
-
let D0 = cubicDerivative(
|
|
3053
|
-
com2.p0,
|
|
3054
|
-
com2.cp1,
|
|
3055
|
-
com2.cp2,
|
|
3056
|
-
com2.p,
|
|
3057
|
-
0
|
|
3058
|
-
);
|
|
3059
|
-
|
|
3060
|
-
let v = sub(com1.p0, B0);
|
|
3061
|
-
|
|
3062
|
-
// first-order projection onto tangent
|
|
3063
|
-
let t0 = dot(v, D0) / dot(D0, D0);
|
|
3064
|
-
|
|
3065
|
-
// refine with one Newton iteration (optional but cheap)
|
|
3066
|
-
let P = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], t0);
|
|
3067
|
-
let dP = cubicDerivative(com2.p0, com2.cp1, com2.cp2, com2.p, t0);
|
|
3068
|
-
let r = sub(P, com1.p0);
|
|
3069
|
-
|
|
3070
|
-
t0 -= dot(r, dP) / dot(dP, dP);
|
|
3071
|
-
|
|
3072
|
-
// construct merged cubic over [t0, 1]
|
|
3073
|
-
let Q0 = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], t0);
|
|
3074
|
-
let Q3 = com2.p;
|
|
3075
|
-
|
|
3076
|
-
let d0 = cubicDerivative(com2.p0, com2.cp1, com2.cp2, com2.p, t0);
|
|
3077
|
-
let d1 = cubicDerivative(com2.p0, com2.cp1, com2.cp2, com2.p, 1);
|
|
3078
|
-
|
|
3079
|
-
let scale = 1 - t0;
|
|
3080
|
-
|
|
3081
|
-
let Q1 = add(Q0, mul(d0, scale / 3));
|
|
3082
|
-
let Q2 = sub(Q3, mul(d1, scale / 3));
|
|
3083
|
-
|
|
3084
|
-
let result = {
|
|
3085
|
-
p0: Q0,
|
|
3086
|
-
cp1: Q1,
|
|
3087
|
-
cp2: Q2,
|
|
3088
|
-
p: Q3,
|
|
3089
|
-
t0
|
|
3090
|
-
};
|
|
3091
|
-
|
|
3092
|
-
if (reverse) {
|
|
3093
|
-
result = {
|
|
3094
|
-
p0: Q3,
|
|
3095
|
-
cp1: Q2,
|
|
3096
|
-
cp2: Q1,
|
|
3097
|
-
p: Q0,
|
|
3098
|
-
t0
|
|
3099
|
-
};
|
|
3100
|
-
}
|
|
3101
|
-
|
|
3102
|
-
let tMid = (1 - t0) * 0.5;
|
|
3103
|
+
// etsimate t for extrapolation
|
|
3104
|
+
let PtI = checkLineIntersection(com1.cp2, com1.p, com2.p, com2.cp2, false, true);
|
|
3105
|
+
let cp1_I = interpolate(com1.p, PtI, 0.666);
|
|
3103
3106
|
|
|
3104
|
-
let
|
|
3105
|
-
let
|
|
3107
|
+
let dist1_2 = getDistManhattan(com1.cp2, com1.p);
|
|
3108
|
+
let dist2_2 = getDistManhattan(com1.cp2, cp1_I);
|
|
3109
|
+
let t = dist2_2 / dist1_2;
|
|
3106
3110
|
|
|
3107
|
-
|
|
3108
|
-
let
|
|
3111
|
+
// extrapolate
|
|
3112
|
+
let segs = pointAtT([com1.p0, com1.cp1, com1.cp2, com1.p], t, false, true).segments;
|
|
3109
3113
|
|
|
3110
|
-
let
|
|
3111
|
-
let cp2_2 = interpolate(result.p, ptI_2, 1.333 );
|
|
3114
|
+
let seg = segs[0];
|
|
3112
3115
|
|
|
3113
|
-
//
|
|
3114
|
-
let
|
|
3115
|
-
if (cp_intersection) {
|
|
3116
|
+
// if it worked - points should be nearby
|
|
3117
|
+
let dist = getDistManhattan(seg.p, com2.p);
|
|
3116
3118
|
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
result.cp2 = cp2_2;
|
|
3122
|
-
|
|
3123
|
-
// check distances between original starting point and extrapolated
|
|
3124
|
-
let dist3 = getDistAv(com1_o.p0, result.p0);
|
|
3125
|
-
let dist4 = getDistAv(com2_o.p, result.p);
|
|
3126
|
-
let dist5 = (dist3 + dist4);
|
|
3127
|
-
|
|
3128
|
-
// use original points
|
|
3129
|
-
result.p0 = com1_o.p0;
|
|
3130
|
-
result.p = com2_o.p;
|
|
3131
|
-
result.extreme = com2_o.extreme;
|
|
3132
|
-
result.corner = com2_o.corner;
|
|
3133
|
-
result.dimA = com2_o.dimA;
|
|
3134
|
-
result.directionChange = com2_o.directionChange;
|
|
3135
|
-
result.type = 'C';
|
|
3136
|
-
result.values = [result.cp1.x, result.cp1.y, result.cp2.x, result.cp2.y, result.p.x, result.p.y];
|
|
3119
|
+
// if close enough adjust
|
|
3120
|
+
if (dist < thresh) {
|
|
3121
|
+
let angle = getAngle(seg.p, seg.cp2);
|
|
3122
|
+
let angle2 = getAngle(com2.p, com2.cp2);
|
|
3137
3123
|
|
|
3138
|
-
|
|
3139
|
-
if (dist5 < maxDist) {
|
|
3140
|
-
|
|
3141
|
-
/*
|
|
3142
|
-
let tTotal = 1 + Math.abs(t0);
|
|
3143
|
-
let tSplit = reverse ? 1 + t0 : Math.abs(t0);
|
|
3144
|
-
|
|
3145
|
-
let pO = pointAtT([com2_o.p0, com2_o.cp1, com2_o.cp2, com2_o.p], t0);
|
|
3146
|
-
*/
|
|
3124
|
+
let angleDiff = (angle2 - angle);
|
|
3147
3125
|
|
|
3148
|
-
//
|
|
3149
|
-
|
|
3126
|
+
// adjust cp angle
|
|
3127
|
+
seg.cp2 = rotatePoint(seg.cp2, seg.p.x, seg.p.y, angleDiff);
|
|
3128
|
+
let dist1 = getDistManhattan(seg.p, seg.cp2);
|
|
3150
3129
|
|
|
3151
|
-
|
|
3152
|
-
|
|
3130
|
+
// copy original final point coordinates
|
|
3131
|
+
seg.p = com2.p;
|
|
3153
3132
|
|
|
3154
|
-
|
|
3155
|
-
let
|
|
3133
|
+
// after rotation
|
|
3134
|
+
let dist2 = getDistManhattan(seg.p, seg.cp2);
|
|
3135
|
+
let scale = dist2 / dist1;
|
|
3156
3136
|
|
|
3157
|
-
//
|
|
3158
|
-
|
|
3137
|
+
// adjust tangent length
|
|
3138
|
+
seg.cp2 = interpolate(seg.p, seg.cp2, scale);
|
|
3159
3139
|
|
|
3160
|
-
|
|
3140
|
+
// reverse back
|
|
3141
|
+
if (reverse) {
|
|
3142
|
+
seg = {
|
|
3143
|
+
p0: seg.p,
|
|
3144
|
+
p: seg.p0,
|
|
3145
|
+
cp1: seg.cp2,
|
|
3146
|
+
cp2: seg.cp1,
|
|
3147
|
+
};
|
|
3161
3148
|
}
|
|
3162
3149
|
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3150
|
+
commands = [
|
|
3151
|
+
{
|
|
3152
|
+
type: 'C',
|
|
3153
|
+
values: [seg.cp1.x, seg.cp1.y, seg.cp2.x, seg.cp2.y, seg.p.x, seg.p.y],
|
|
3154
|
+
p0: seg.p0,
|
|
3155
|
+
cp1: seg.cp1,
|
|
3156
|
+
cp2: seg.cp2,
|
|
3157
|
+
p: seg.p,
|
|
3158
|
+
extreme: com2_o.extreme,
|
|
3159
|
+
corner: com2_o.corner,
|
|
3160
|
+
directionChange: com2_o.directionChange,
|
|
3161
|
+
dimA: getDistManhattan(seg.p0, seg.p),
|
|
3162
|
+
error: dist
|
|
3169
3163
|
|
|
3170
|
-
|
|
3171
|
-
let pathDataN = [
|
|
3172
|
-
{ type: 'M', values: [result.p0.x, result.p0.y] },
|
|
3173
|
-
{ type: 'C', values: [result.cp1.x, result.cp1.y, result.cp2.x, result.cp2.y, result.p.x, result.p.y] },
|
|
3164
|
+
}
|
|
3174
3165
|
];
|
|
3175
3166
|
|
|
3176
|
-
let areaN = getPathArea(pathDataN);
|
|
3177
|
-
let areaDiff = Math.abs(areaN / area0 - 1);
|
|
3178
|
-
|
|
3179
|
-
result.error = areaDiff * 5 * tolerance;
|
|
3180
|
-
|
|
3181
|
-
if (debug) {
|
|
3182
|
-
pathDataToD(pathDataN);
|
|
3183
|
-
|
|
3184
|
-
}
|
|
3185
|
-
|
|
3186
|
-
// success!!!
|
|
3187
|
-
if (areaDiff < 0.05 * tolerance) {
|
|
3188
|
-
commands = [result];
|
|
3189
|
-
|
|
3190
|
-
}
|
|
3191
3167
|
}
|
|
3192
3168
|
|
|
3193
3169
|
return commands
|
|
@@ -3239,7 +3215,7 @@ function simplifyPathDataCubic(pathData, {
|
|
|
3239
3215
|
error += com.error;
|
|
3240
3216
|
|
|
3241
3217
|
// find next candidates
|
|
3242
|
-
for (let n = i +
|
|
3218
|
+
for (let n = i + offset; error < tolerance && n < l; n++) {
|
|
3243
3219
|
let comN = pathData[n];
|
|
3244
3220
|
|
|
3245
3221
|
if (comN.type !== 'C' ||
|
|
@@ -3249,6 +3225,7 @@ function simplifyPathDataCubic(pathData, {
|
|
|
3249
3225
|
(keepExtremes && com.extreme)
|
|
3250
3226
|
)
|
|
3251
3227
|
) {
|
|
3228
|
+
|
|
3252
3229
|
break
|
|
3253
3230
|
}
|
|
3254
3231
|
|
|
@@ -3256,6 +3233,7 @@ function simplifyPathDataCubic(pathData, {
|
|
|
3256
3233
|
|
|
3257
3234
|
// failure - could not be combined - exit loop
|
|
3258
3235
|
if (combined.length > 1) {
|
|
3236
|
+
|
|
3259
3237
|
break
|
|
3260
3238
|
}
|
|
3261
3239
|
|
|
@@ -3269,6 +3247,7 @@ function simplifyPathDataCubic(pathData, {
|
|
|
3269
3247
|
|
|
3270
3248
|
// return combined
|
|
3271
3249
|
com = combined[0];
|
|
3250
|
+
|
|
3272
3251
|
}
|
|
3273
3252
|
|
|
3274
3253
|
pathDataN.push(com);
|
|
@@ -3307,11 +3286,12 @@ function combineCubicPairs(com1, com2, {
|
|
|
3307
3286
|
// quit if t is start
|
|
3308
3287
|
if (!t) return commands;
|
|
3309
3288
|
|
|
3289
|
+
// get averaged threshold
|
|
3310
3290
|
let distAv1 = getDistManhattan(com1.p0, com1.p);
|
|
3311
3291
|
let distAv2 = getDistManhattan(com2.p0, com2.p);
|
|
3312
3292
|
let distMin = Math.max(0, Math.min(distAv1, distAv2));
|
|
3313
3293
|
|
|
3314
|
-
let distScale = 0.
|
|
3294
|
+
let distScale = 0.075;
|
|
3315
3295
|
let maxDist = distMin * distScale * tolerance;
|
|
3316
3296
|
|
|
3317
3297
|
// get hypothetical combined command
|
|
@@ -3359,16 +3339,6 @@ function combineCubicPairs(com1, com2, {
|
|
|
3359
3339
|
|
|
3360
3340
|
if (error < maxDist) success = true;
|
|
3361
3341
|
|
|
3362
|
-
/*
|
|
3363
|
-
renderPoint(markers, ptM_seg1, 'cyan')
|
|
3364
|
-
renderPoint(markers, pt, 'orange', '1.5%', '1')
|
|
3365
|
-
renderPoint(markers, ptM_seg2, 'orange')
|
|
3366
|
-
|
|
3367
|
-
renderPoint(markers, com1.p, 'green')
|
|
3368
|
-
|
|
3369
|
-
renderPoint(markers, ptI_seg1, 'purple')
|
|
3370
|
-
*/
|
|
3371
|
-
|
|
3372
3342
|
}
|
|
3373
3343
|
|
|
3374
3344
|
} // end 1st try
|
|
@@ -3382,9 +3352,9 @@ function combineCubicPairs(com1, com2, {
|
|
|
3382
3352
|
|
|
3383
3353
|
comS.dimA = getDistManhattan(comS.p0, comS.p);
|
|
3384
3354
|
comS.type = 'C';
|
|
3355
|
+
|
|
3385
3356
|
comS.extreme = com2.extreme;
|
|
3386
3357
|
comS.directionChange = com2.directionChange;
|
|
3387
|
-
|
|
3388
3358
|
comS.corner = com2.corner;
|
|
3389
3359
|
|
|
3390
3360
|
comS.values = [comS.cp1.x, comS.cp1.y, comS.cp2.x, comS.cp2.y, comS.p.x, comS.p.y];
|
|
@@ -3445,7 +3415,9 @@ function findSplitT(com1, com2) {
|
|
|
3445
3415
|
}
|
|
3446
3416
|
*/
|
|
3447
3417
|
|
|
3448
|
-
|
|
3418
|
+
let t = l1 / l3;
|
|
3419
|
+
|
|
3420
|
+
return t;
|
|
3449
3421
|
}
|
|
3450
3422
|
|
|
3451
3423
|
function commandIsFlat(points, {
|
|
@@ -3498,7 +3470,7 @@ function analyzePathData(pathData = [], {
|
|
|
3498
3470
|
detectDirection = true,
|
|
3499
3471
|
detectSemiExtremes = false,
|
|
3500
3472
|
debug = false,
|
|
3501
|
-
addSquareLength =
|
|
3473
|
+
addSquareLength = false,
|
|
3502
3474
|
addArea = true,
|
|
3503
3475
|
|
|
3504
3476
|
} = {}) {
|
|
@@ -3547,8 +3519,8 @@ function analyzePathData(pathData = [], {
|
|
|
3547
3519
|
let commandPts = (type === 'C' || type === 'Q') ?
|
|
3548
3520
|
(type === 'C' ? [p0, cp1, cp2, p] : [p0, cp1, p]) :
|
|
3549
3521
|
([p0, p]);
|
|
3550
|
-
|
|
3551
|
-
let threshold =
|
|
3522
|
+
|
|
3523
|
+
let threshold = dimA * 0.005;
|
|
3552
3524
|
|
|
3553
3525
|
// bezier types
|
|
3554
3526
|
let isBezier = type === 'Q' || type === 'C';
|
|
@@ -3561,7 +3533,7 @@ function analyzePathData(pathData = [], {
|
|
|
3561
3533
|
*/
|
|
3562
3534
|
let hasExtremes = false;
|
|
3563
3535
|
|
|
3564
|
-
if (isBezier) {
|
|
3536
|
+
if (detectExtremes && isBezier) {
|
|
3565
3537
|
|
|
3566
3538
|
let dx = type === 'C' ? Math.abs(com.cp2.x - com.p.x) : Math.abs(com.cp1.x - com.p.x);
|
|
3567
3539
|
let dy = type === 'C' ? Math.abs(com.cp2.y - com.p.y) : Math.abs(com.cp1.y - com.p.y);
|
|
@@ -3600,7 +3572,7 @@ function analyzePathData(pathData = [], {
|
|
|
3600
3572
|
}
|
|
3601
3573
|
|
|
3602
3574
|
// check extremes introduce by small arcs
|
|
3603
|
-
else if(isArc && comN && ((comPrev.type==='C' || comPrev.type==='Q') || (comN.type==='C' || comN.type==='Q')) ){
|
|
3575
|
+
else if(detectExtremes && isArc && comN && ((comPrev.type==='C' || comPrev.type==='Q') || (comN.type==='C' || comN.type==='Q')) ){
|
|
3604
3576
|
let distN = comN ? comN.dimA : 0;
|
|
3605
3577
|
let isShort = com.dimA < (comPrev.dimA + distN) * 0.1;
|
|
3606
3578
|
let smallRadius = com.values[0] === com.values[1] && (com.values[0] < 1);
|
|
@@ -3618,28 +3590,7 @@ function analyzePathData(pathData = [], {
|
|
|
3618
3590
|
if (hasExtremes) com.extreme = true;
|
|
3619
3591
|
|
|
3620
3592
|
// Corners and semi extremes
|
|
3621
|
-
if (isBezier && isBezierN) {
|
|
3622
|
-
|
|
3623
|
-
// semi extremes
|
|
3624
|
-
if (detectSemiExtremes && !com.extreme) {
|
|
3625
|
-
|
|
3626
|
-
let dx1 = Math.abs(p.x - cp2.x);
|
|
3627
|
-
let dy1 = Math.abs(p.y - cp2.y);
|
|
3628
|
-
let hasSemiExtreme = false;
|
|
3629
|
-
|
|
3630
|
-
// exclude extremes or small deltas
|
|
3631
|
-
if (dx1 && dy1 && dx1 > thresholdLength || dy1 > thresholdLength) {
|
|
3632
|
-
let ang1 = getAngle(cp2, p);
|
|
3633
|
-
let ang2 = getAngle(p, comN.cp1);
|
|
3634
|
-
|
|
3635
|
-
let ang3 = Math.abs(ang1 + ang2) / 2;
|
|
3636
|
-
hasSemiExtreme = isMultipleOf45(ang3);
|
|
3637
|
-
}
|
|
3638
|
-
|
|
3639
|
-
if (hasSemiExtreme) {
|
|
3640
|
-
com.semiExtreme = true;
|
|
3641
|
-
}
|
|
3642
|
-
}
|
|
3593
|
+
if (detectCorners && isBezier && isBezierN) {
|
|
3643
3594
|
|
|
3644
3595
|
/**
|
|
3645
3596
|
* Detect direction change points
|
|
@@ -4219,7 +4170,12 @@ function convertPathData(pathData, {
|
|
|
4219
4170
|
if (hasShorthands && toLonghands) pathData = pathDataToLonghands(pathData);
|
|
4220
4171
|
|
|
4221
4172
|
// minify semicircle radii
|
|
4222
|
-
if (optimizeArcs)
|
|
4173
|
+
if (optimizeArcs) {
|
|
4174
|
+
pathData = optimizeArcPathData(pathData);
|
|
4175
|
+
} else {
|
|
4176
|
+
// get true absolute radii
|
|
4177
|
+
pathData = pathDataToTrueArcValues(pathData);
|
|
4178
|
+
}
|
|
4223
4179
|
|
|
4224
4180
|
if (toShorthands) pathData = pathDataToShorthands(pathData);
|
|
4225
4181
|
|
|
@@ -4309,9 +4265,42 @@ function parsePathDataNormalized(d,
|
|
|
4309
4265
|
}
|
|
4310
4266
|
|
|
4311
4267
|
/**
|
|
4312
|
-
*
|
|
4313
|
-
*
|
|
4314
|
-
|
|
4268
|
+
* Converts minified arc
|
|
4269
|
+
* values to true rx and ry values
|
|
4270
|
+
*/
|
|
4271
|
+
function pathDataToTrueArcValues(pathData = []) {
|
|
4272
|
+
let l = pathData.length;
|
|
4273
|
+
let pathDataN = [pathData[0]];
|
|
4274
|
+
|
|
4275
|
+
for (let i = 1; i < l; i++) {
|
|
4276
|
+
let com = pathData[i];
|
|
4277
|
+
let comPrev = pathData[i-1];
|
|
4278
|
+
let { type, values } = com;
|
|
4279
|
+
|
|
4280
|
+
if (type === 'A') {
|
|
4281
|
+
|
|
4282
|
+
// previous commands final on-path point
|
|
4283
|
+
let [x1, y1] = comPrev.values.slice(-2);
|
|
4284
|
+
let [rx, ry, xAxisRotation, largeArc, sweep, x2, y2] = values;
|
|
4285
|
+
let arcData = svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2, y2);
|
|
4286
|
+
|
|
4287
|
+
// set true arc values
|
|
4288
|
+
com.values[0] = arcData.rx;
|
|
4289
|
+
com.values[1] = arcData.ry;
|
|
4290
|
+
|
|
4291
|
+
}
|
|
4292
|
+
|
|
4293
|
+
pathDataN.push(com);
|
|
4294
|
+
|
|
4295
|
+
}
|
|
4296
|
+
return pathDataN;
|
|
4297
|
+
}
|
|
4298
|
+
|
|
4299
|
+
/**
|
|
4300
|
+
* Minify arc radii
|
|
4301
|
+
* for semi circles
|
|
4302
|
+
* returns smaller rx and ry
|
|
4303
|
+
* values
|
|
4315
4304
|
*/
|
|
4316
4305
|
|
|
4317
4306
|
function optimizeArcPathData(pathData = []) {
|
|
@@ -4368,7 +4357,7 @@ function optimizeArcPathData(pathData = []) {
|
|
|
4368
4357
|
if (isHorizontal || isVertical) {
|
|
4369
4358
|
|
|
4370
4359
|
// check if semi circle
|
|
4371
|
-
let needsTrueR = isHorizontal ? rx*1.9 > diffX : ry*1.9 > diffY;
|
|
4360
|
+
let needsTrueR = isHorizontal ? rx * 1.9 > diffX : ry * 1.9 > diffY;
|
|
4372
4361
|
|
|
4373
4362
|
// is semicircle we can simplify rx
|
|
4374
4363
|
if (!needsTrueR) {
|
|
@@ -5064,8 +5053,8 @@ function arcToBezier$1(p0, values, splitSegments = 1) {
|
|
|
5064
5053
|
let phi = rotation ? rotation * TAU / 360 : 0;
|
|
5065
5054
|
let sinphi = phi ? Math.sin(phi) : 0;
|
|
5066
5055
|
let cosphi = phi ? Math.cos(phi) : 1;
|
|
5067
|
-
let pxp = cosphi * (p0.x - x)
|
|
5068
|
-
let pyp = -sinphi * (p0.x - x)
|
|
5056
|
+
let pxp = cosphi * (p0.x - x) *0.5 + sinphi * (p0.y - y) *0.5;
|
|
5057
|
+
let pyp = -sinphi * (p0.x - x) *0.5 + cosphi * (p0.y - y) *0.5;
|
|
5069
5058
|
|
|
5070
5059
|
if (pxp === 0 && pyp === 0) {
|
|
5071
5060
|
return []
|
|
@@ -5101,8 +5090,8 @@ function arcToBezier$1(p0, values, splitSegments = 1) {
|
|
|
5101
5090
|
|
|
5102
5091
|
let centerxp = radicant ? radicant * rx / ry * pyp : 0;
|
|
5103
5092
|
let centeryp = radicant ? radicant * -ry / rx * pxp : 0;
|
|
5104
|
-
let centerx = cosphi * centerxp - sinphi * centeryp + (p0.x + x)
|
|
5105
|
-
let centery = sinphi * centerxp + cosphi * centeryp + (p0.y + y)
|
|
5093
|
+
let centerx = cosphi * centerxp - sinphi * centeryp + (p0.x + x) * 0.5;
|
|
5094
|
+
let centery = sinphi * centerxp + cosphi * centeryp + (p0.y + y) * 0.5;
|
|
5106
5095
|
|
|
5107
5096
|
let vx1 = (pxp - centerxp) / rx;
|
|
5108
5097
|
let vy1 = (pyp - centeryp) / ry;
|
|
@@ -5124,15 +5113,17 @@ function arcToBezier$1(p0, values, splitSegments = 1) {
|
|
|
5124
5113
|
ang2 = vectorAngle(vx1, vy1, vx2, vy2);
|
|
5125
5114
|
|
|
5126
5115
|
if (sweepFlag === 0 && ang2 > 0) {
|
|
5127
|
-
|
|
5116
|
+
|
|
5117
|
+
ang2 -= TAU;
|
|
5128
5118
|
}
|
|
5129
5119
|
else if (sweepFlag === 1 && ang2 < 0) {
|
|
5130
|
-
|
|
5120
|
+
|
|
5121
|
+
ang2 += TAU;
|
|
5131
5122
|
}
|
|
5132
5123
|
|
|
5133
|
-
let ratio = +(Math.abs(ang2) / (TAU
|
|
5124
|
+
let ratio = +(Math.abs(ang2) / (TAU * 0.25)).toFixed(0) || 1;
|
|
5134
5125
|
|
|
5135
|
-
// increase segments for more
|
|
5126
|
+
// increase segments for more accurate length calculations
|
|
5136
5127
|
let segments = ratio * splitSegments;
|
|
5137
5128
|
ang2 /= segments;
|
|
5138
5129
|
let pathDataArc = [];
|
|
@@ -5144,7 +5135,8 @@ function arcToBezier$1(p0, values, splitSegments = 1) {
|
|
|
5144
5135
|
const k = 0.551785;
|
|
5145
5136
|
let a = ang2 === angle90 ? k :
|
|
5146
5137
|
(
|
|
5147
|
-
|
|
5138
|
+
|
|
5139
|
+
ang2 === -angle90 ? -k : 1.33333 * Math.tan(ang2 * 0.25)
|
|
5148
5140
|
);
|
|
5149
5141
|
|
|
5150
5142
|
let cos2 = ang2 ? Math.cos(ang2) : 1;
|
|
@@ -5191,7 +5183,7 @@ function pathDataCubicsToArc(pathData, { areaThreshold = 2.5 } = {}) {
|
|
|
5191
5183
|
let comA = cubicCommandToArc(p0, cp1, cp2, p, areaThreshold);
|
|
5192
5184
|
let comAN = cubicCommandToArc(comN.p0, comN.cp1, comN.cp2, comN.p, areaThreshold);
|
|
5193
5185
|
|
|
5194
|
-
if (comA.isArc
|
|
5186
|
+
if (comA.isArc && comAN.isArc) {
|
|
5195
5187
|
|
|
5196
5188
|
let dist = getDistManhattan(p0, comN.p);
|
|
5197
5189
|
let maxDist = dist * 0.01;
|
|
@@ -5206,8 +5198,18 @@ function pathDataCubicsToArc(pathData, { areaThreshold = 2.5 } = {}) {
|
|
|
5206
5198
|
let sweep = area < 0 ? 0 : 1;
|
|
5207
5199
|
|
|
5208
5200
|
if (vertical || horizontal) {
|
|
5201
|
+
|
|
5209
5202
|
rx = Math.min(rx, comAN.rx);
|
|
5210
5203
|
ry = Math.min(ry, comAN.ry);
|
|
5204
|
+
|
|
5205
|
+
let diffR = Math.abs(rx - ry) / rx;
|
|
5206
|
+
let isSemiCircle = diffR < 0.025;
|
|
5207
|
+
|
|
5208
|
+
if (isSemiCircle) {
|
|
5209
|
+
rx = rx > 1 ? 1 : Math.min(rx, ry);
|
|
5210
|
+
ry = rx;
|
|
5211
|
+
}
|
|
5212
|
+
|
|
5211
5213
|
pathData[c] = null;
|
|
5212
5214
|
pathData[c + 1].type = 'A';
|
|
5213
5215
|
pathData[c + 1].values = [rx, ry, 0, 0, sweep, comN.p.x, comN.p.y];
|
|
@@ -5230,33 +5232,61 @@ function cubicCommandToArc(p0, cp1, cp2, p, tolerance = 7.5) {
|
|
|
5230
5232
|
let arcSegArea = 0, isArc = false;
|
|
5231
5233
|
|
|
5232
5234
|
// check angles
|
|
5233
|
-
let
|
|
5234
|
-
let
|
|
5235
|
+
let dx1 = (cp1.x - p0.x);
|
|
5236
|
+
let dy1 = (cp1.y - p0.y);
|
|
5237
|
+
let dx2 = (cp2.x - p.x);
|
|
5238
|
+
let dy2 = (cp2.y - p.y);
|
|
5239
|
+
|
|
5240
|
+
let thresh = getDistManhattan(p0, p) * 0.001;
|
|
5241
|
+
|
|
5242
|
+
let isVertical1 = dx1 < thresh;
|
|
5243
|
+
let isVertical2 = dx2 < thresh;
|
|
5244
|
+
|
|
5245
|
+
let isHorizontal1 = dy1 < thresh;
|
|
5246
|
+
let isHorizontal2 = dy2 < thresh;
|
|
5247
|
+
|
|
5248
|
+
let isRightAngle = (isVertical1 || isVertical2) && (isHorizontal1 || isHorizontal2);
|
|
5249
|
+
|
|
5250
|
+
/*
|
|
5251
|
+
let angle1 = getAngleFromDelta(dx1, dy1, true);
|
|
5252
|
+
let angle2 = getAngleFromDelta(dx2, dy2, true);
|
|
5235
5253
|
let deltaAngle = Math.abs(angle1 - angle2) * 180 / Math.PI;
|
|
5236
5254
|
|
|
5237
5255
|
let angleDiff = Math.abs((deltaAngle % 180) - 90);
|
|
5238
5256
|
let isRightAngle = angleDiff < 3;
|
|
5257
|
+
*/
|
|
5239
5258
|
|
|
5240
5259
|
let rx = 0;
|
|
5241
5260
|
let ry = 0;
|
|
5242
|
-
let ptC;
|
|
5261
|
+
let ptC = p0;
|
|
5262
|
+
let r1 = 0, r2 = 0;
|
|
5243
5263
|
|
|
5244
5264
|
if (isRightAngle) {
|
|
5245
|
-
// point between cps
|
|
5246
5265
|
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5266
|
+
if (isHorizontal1 && isVertical2) {
|
|
5267
|
+
ptC = { x: p0.x, y: p.y };
|
|
5268
|
+
r1 = Math.abs(p.x-p0.x);
|
|
5269
|
+
r2 = Math.abs(p.y-p0.y);
|
|
5270
|
+
}
|
|
5271
|
+
else if (isHorizontal2 && isVertical1) {
|
|
5272
|
+
ptC = { x: p.x, y: p0.y };
|
|
5273
|
+
r2 = Math.abs(p0.x-p.x);
|
|
5274
|
+
r1 = Math.abs(p0.y-p.y);
|
|
5275
|
+
}
|
|
5276
|
+
|
|
5277
|
+
/*
|
|
5278
|
+
if (r1 && r2) {
|
|
5250
5279
|
|
|
5251
|
-
|
|
5252
|
-
ptC = checkLineIntersection(p0, cp1_r, p, cp2_r, false);
|
|
5280
|
+
}
|
|
5253
5281
|
|
|
5254
|
-
|
|
5282
|
+
*/
|
|
5255
5283
|
|
|
5256
|
-
if (
|
|
5284
|
+
if (r1 && r2) {
|
|
5257
5285
|
|
|
5286
|
+
/*
|
|
5258
5287
|
let r1 = getDistance(p0, pI);
|
|
5259
5288
|
let r2 = getDistance(p, pI);
|
|
5289
|
+
*/
|
|
5260
5290
|
|
|
5261
5291
|
let rMax = +Math.max(r1, r2).toFixed(8);
|
|
5262
5292
|
let rMin = +Math.min(r1, r2).toFixed(8);
|
|
@@ -5274,7 +5304,6 @@ function cubicCommandToArc(p0, cp1, cp2, p, tolerance = 7.5) {
|
|
|
5274
5304
|
let circular = (100 / rx * Math.abs(rx - ry)) < 5;
|
|
5275
5305
|
|
|
5276
5306
|
if (circular) {
|
|
5277
|
-
|
|
5278
5307
|
rx = rMax;
|
|
5279
5308
|
ry = rx;
|
|
5280
5309
|
}
|
|
@@ -5300,7 +5329,7 @@ function cubicCommandToArc(p0, cp1, cp2, p, tolerance = 7.5) {
|
|
|
5300
5329
|
arcSegArea = (Math.PI * (rx * ry)) / 4;
|
|
5301
5330
|
|
|
5302
5331
|
// subtract polygon between start, end and center point
|
|
5303
|
-
arcSegArea -= Math.abs(getPolygonArea([p0, p,
|
|
5332
|
+
arcSegArea -= Math.abs(getPolygonArea([p0, p, ptC]));
|
|
5304
5333
|
|
|
5305
5334
|
let areaDiff = getRelativeAreaDiff(comArea, arcSegArea);
|
|
5306
5335
|
|
|
@@ -6370,59 +6399,50 @@ function pathDataRemoveColinear(pathData, {
|
|
|
6370
6399
|
pathData[pathData.length - 1].type.toLowerCase() === 'z';
|
|
6371
6400
|
|
|
6372
6401
|
for (let c = 1, l = pathData.length; c < l; c++) {
|
|
6373
|
-
|
|
6374
6402
|
let com = pathData[c];
|
|
6403
|
+
let { type, values } = com;
|
|
6375
6404
|
let comN = pathData[c + 1] || pathData[l - 1];
|
|
6376
|
-
|
|
6405
|
+
let valuesN = comN.values;
|
|
6377
6406
|
let p1 = comN.type.toLowerCase() === 'z' ? M : { x: comN.values[comN.values.length - 2], y: comN.values[comN.values.length - 1] };
|
|
6378
6407
|
|
|
6379
|
-
let { type, values } = com;
|
|
6380
6408
|
let valsL = values.slice(-2);
|
|
6381
6409
|
p = type !== 'Z' ? { x: valsL[0], y: valsL[1] } : M;
|
|
6382
6410
|
|
|
6411
|
+
let nextBezier = type!==comN.type && (comN.type==='C' || comN.type==='Q');
|
|
6412
|
+
|
|
6383
6413
|
let area = p1 ? getPolygonArea([p0, p, p1], true) : Infinity;
|
|
6384
6414
|
let distSquare = getSquareDistance(p0, p1);
|
|
6385
6415
|
let distMax = distSquare ? distSquare / 333 * tolerance : 0;
|
|
6386
6416
|
|
|
6387
6417
|
let isFlat = area < distMax;
|
|
6388
|
-
|
|
6389
6418
|
let isFlatBez = false;
|
|
6419
|
+
let cpts = [];
|
|
6390
6420
|
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
let dx2 = Math.abs(p1.x - p.x)
|
|
6400
|
-
let dy2 = Math.abs(p1.y - p.y)
|
|
6401
|
-
|
|
6402
|
-
// zero length segments are flat
|
|
6403
|
-
let isZeroLength = (!dy1 && !dx1) || (!dy2 && !dx2)
|
|
6404
|
-
if (isZeroLength) isFlat = true;
|
|
6405
|
-
|
|
6406
|
-
// check cross products for colinearity
|
|
6407
|
-
if (!isFlat) {
|
|
6408
|
-
|
|
6409
|
-
let cross0 = Math.abs(dx0 * dy1 - dy0 * dx1);
|
|
6421
|
+
/**
|
|
6422
|
+
* type change
|
|
6423
|
+
* check flatness
|
|
6424
|
+
*/
|
|
6425
|
+
if (nextBezier) {
|
|
6426
|
+
cpts = comN.type === 'C' ?
|
|
6427
|
+
[{ x: valuesN[0], y: valuesN[1] }, { x: valuesN[2], y: valuesN[3] }] :
|
|
6428
|
+
(comN.type === 'Q' ? [{ x: valuesN[0], y: valuesN[1] }] : []);
|
|
6410
6429
|
|
|
6411
|
-
|
|
6430
|
+
isFlat = commandIsFlat([p0, ...cpts, p], { tolerance });
|
|
6412
6431
|
|
|
6413
|
-
|
|
6432
|
+
/*
|
|
6414
6433
|
|
|
6415
|
-
|
|
6434
|
+
if(!isFlatBez){
|
|
6435
|
+
pathDataN.push(com)
|
|
6436
|
+
continue
|
|
6416
6437
|
}
|
|
6417
|
-
|
|
6418
|
-
*/
|
|
6438
|
+
*/
|
|
6419
6439
|
|
|
6420
|
-
|
|
6440
|
+
}
|
|
6421
6441
|
|
|
6422
6442
|
// convert flat beziers to linetos
|
|
6423
6443
|
if (flatBezierToLinetos && (type === 'C' || type === 'Q')) {
|
|
6424
6444
|
|
|
6425
|
-
|
|
6445
|
+
cpts = type === 'C' ?
|
|
6426
6446
|
[{ x: values[0], y: values[1] }, { x: values[2], y: values[3] }] :
|
|
6427
6447
|
(type === 'Q' ? [{ x: values[0], y: values[1] }] : []);
|
|
6428
6448
|
|
|
@@ -6436,10 +6456,13 @@ function pathDataRemoveColinear(pathData, {
|
|
|
6436
6456
|
}
|
|
6437
6457
|
}
|
|
6438
6458
|
|
|
6439
|
-
|
|
6459
|
+
/**
|
|
6460
|
+
* colinear = simplification success
|
|
6461
|
+
* exclude arcs (as always =)
|
|
6462
|
+
* as semicircles won't have an area
|
|
6463
|
+
*/
|
|
6440
6464
|
|
|
6441
6465
|
if (isFlat && c < l - 1 && comN.type !== 'A' && (type === 'L' || (flatBezierToLinetos && isFlatBez))) {
|
|
6442
|
-
|
|
6443
6466
|
continue;
|
|
6444
6467
|
}
|
|
6445
6468
|
|
|
@@ -6555,7 +6578,8 @@ function pathDataToTopLeft(pathData) {
|
|
|
6555
6578
|
let { type, values } = com;
|
|
6556
6579
|
let valsLen = values.length;
|
|
6557
6580
|
if (valsLen) {
|
|
6558
|
-
|
|
6581
|
+
// we need rounding otherwise sorting may crash due to e notation
|
|
6582
|
+
let p = { type: type, x: +values[valsLen - 2].toFixed(8), y: +values[valsLen - 1].toFixed(8), index: 0 };
|
|
6559
6583
|
p.index = i;
|
|
6560
6584
|
indices.push(p);
|
|
6561
6585
|
}
|
|
@@ -6563,7 +6587,7 @@ function pathDataToTopLeft(pathData) {
|
|
|
6563
6587
|
|
|
6564
6588
|
// reorder to top left most
|
|
6565
6589
|
|
|
6566
|
-
indices = indices.sort((a, b) =>
|
|
6590
|
+
indices = indices.sort((a, b) => a.y - b.y || a.x - b.x);
|
|
6567
6591
|
newIndex = indices[0].index;
|
|
6568
6592
|
|
|
6569
6593
|
return newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
|
|
@@ -6922,9 +6946,7 @@ function refineAdjacentExtremes(pathData, {
|
|
|
6922
6946
|
if (comEx.length === 1) {
|
|
6923
6947
|
|
|
6924
6948
|
comEx = comEx[0];
|
|
6925
|
-
|
|
6926
6949
|
pathData[i + 1] = null;
|
|
6927
|
-
|
|
6928
6950
|
pathData[i + 2].values = [comEx.cp1.x, comEx.cp1.y, comEx.cp2.x, comEx.cp2.y, comEx.p.x, comEx.p.y];
|
|
6929
6951
|
pathData[i + 2].cp1 = comEx.cp1;
|
|
6930
6952
|
pathData[i + 2].cp2 = comEx.cp2;
|
|
@@ -7099,18 +7121,28 @@ function normalizePoly(pts, {
|
|
|
7099
7121
|
} = {}) {
|
|
7100
7122
|
|
|
7101
7123
|
// is stringified flat point attribute
|
|
7102
|
-
if(typeof pts === 'string' && !isNaN(pts[0])){
|
|
7124
|
+
if (typeof pts === 'string' && !isNaN(pts[0])) {
|
|
7103
7125
|
pts = toPointArray(pts.split(/,| /).filter(Boolean).map(Number));
|
|
7104
7126
|
return pts
|
|
7105
7127
|
}
|
|
7106
7128
|
|
|
7129
|
+
if (pts.length && typeof pts[0] === 'string') {
|
|
7130
|
+
pts = pts.map(pt => {
|
|
7131
|
+
return toPointArray(pt.split(/,| /).filter(Boolean).map(Number))
|
|
7132
|
+
});
|
|
7133
|
+
pts = pts.flat(2);
|
|
7134
|
+
|
|
7135
|
+
}
|
|
7136
|
+
|
|
7107
7137
|
if (flatten) pts = pts.flat(2);
|
|
7138
|
+
|
|
7108
7139
|
let poly = toArray ? polyPtsToArray(pts) : polyArrayToObject(pts);
|
|
7109
7140
|
return poly
|
|
7110
7141
|
}
|
|
7111
7142
|
|
|
7112
|
-
function polyArrayToObject(pts) {
|
|
7143
|
+
function polyArrayToObject(pts = []) {
|
|
7113
7144
|
|
|
7145
|
+
if (!pts.length) return [];
|
|
7114
7146
|
// is point object array
|
|
7115
7147
|
if (pts[0].x !== undefined && pts[0].y !== undefined) return pts
|
|
7116
7148
|
|
|
@@ -7128,7 +7160,7 @@ function polyArrayToObject(pts) {
|
|
|
7128
7160
|
return poly
|
|
7129
7161
|
}
|
|
7130
7162
|
|
|
7131
|
-
else if(pts.length>3){
|
|
7163
|
+
else if (pts.length > 3) {
|
|
7132
7164
|
pts = toPointArray(pts);
|
|
7133
7165
|
return pts
|
|
7134
7166
|
}
|
|
@@ -7157,13 +7189,13 @@ function polyPtsToArray(pts) {
|
|
|
7157
7189
|
function toPointArray(pts) {
|
|
7158
7190
|
let ptArr = [];
|
|
7159
7191
|
|
|
7160
|
-
if(pts[0].length===2){
|
|
7161
|
-
for (let i = 0, l = pts.length; i < l; i
|
|
7192
|
+
if (pts[0].length === 2) {
|
|
7193
|
+
for (let i = 0, l = pts.length; i < l; i++) {
|
|
7162
7194
|
let pt = pts[i];
|
|
7163
|
-
ptArr.push({ x: pt[0], y:pt[1] });
|
|
7195
|
+
ptArr.push({ x: pt[0], y: pt[1] });
|
|
7164
7196
|
}
|
|
7165
7197
|
|
|
7166
|
-
}else {
|
|
7198
|
+
} else {
|
|
7167
7199
|
for (let i = 1, l = pts.length; i < l; i += 2) {
|
|
7168
7200
|
ptArr.push({ x: pts[i - 1], y: pts[i] });
|
|
7169
7201
|
}
|
|
@@ -8005,7 +8037,6 @@ function getLength(pts, {
|
|
|
8005
8037
|
|
|
8006
8038
|
// LG weight/abscissae generator
|
|
8007
8039
|
function getLegendreGaussValues(n, x1 = -1, x2 = 1) {
|
|
8008
|
-
console.log('add new LG', n);
|
|
8009
8040
|
|
|
8010
8041
|
let waArr = [];
|
|
8011
8042
|
let z1, z, xm, xl, pp, p3, p2, p1;
|
|
@@ -8464,7 +8495,7 @@ function setNormalizedTransformsToEl(el, {
|
|
|
8464
8495
|
styleProps.remove.push('transform');
|
|
8465
8496
|
|
|
8466
8497
|
// scale props like stroke width or dash-array
|
|
8467
|
-
styleProps = scaleProps(styleProps, { props: ['stroke-width', 'stroke-dasharray'], scale: scaleX });
|
|
8498
|
+
styleProps = scaleProps(styleProps, { props: ['stroke-width', 'stroke-dasharray', 'stroke-dashoffset'], scale: scaleX });
|
|
8468
8499
|
|
|
8469
8500
|
} else {
|
|
8470
8501
|
el.setAttribute('transform', transComponents.matrixAtt);
|
|
@@ -8482,7 +8513,7 @@ function scaleProps(styleProps = {}, { props = [], scale = 1 } = {}, round = tru
|
|
|
8482
8513
|
let prop = props[i];
|
|
8483
8514
|
|
|
8484
8515
|
if (styleProps[prop] !== undefined) {
|
|
8485
|
-
styleProps[prop] = styleProps[prop].map(val => round ? roundTo(val * scale,
|
|
8516
|
+
styleProps[prop] = styleProps[prop].map(val => round ? roundTo(val * scale, 3) : val * scale);
|
|
8486
8517
|
}
|
|
8487
8518
|
}
|
|
8488
8519
|
return styleProps
|
|
@@ -8503,6 +8534,7 @@ function convertPathLengthAtt(el, {
|
|
|
8503
8534
|
});
|
|
8504
8535
|
|
|
8505
8536
|
let scale = elLength / pathLength;
|
|
8537
|
+
|
|
8506
8538
|
styleProps = scaleProps(styleProps, { props: ['stroke-dasharray', 'stroke-dashoffset'], scale });
|
|
8507
8539
|
|
|
8508
8540
|
// set absolute
|
|
@@ -9010,6 +9042,38 @@ function cleanUpSVG(svgMarkup, {
|
|
|
9010
9042
|
});
|
|
9011
9043
|
});
|
|
9012
9044
|
|
|
9045
|
+
/**
|
|
9046
|
+
* remove els and attributes
|
|
9047
|
+
*/
|
|
9048
|
+
|
|
9049
|
+
// remove meta
|
|
9050
|
+
if (!allowMeta) removeElements.push('meta', 'metadata', 'desc', 'title');
|
|
9051
|
+
|
|
9052
|
+
if (removeClassNames) {
|
|
9053
|
+
removeSVGAttributes.push('class');
|
|
9054
|
+
removeElAttributes.push('class');
|
|
9055
|
+
}
|
|
9056
|
+
|
|
9057
|
+
if (removeIds) {
|
|
9058
|
+
removeSVGAttributes.push('id');
|
|
9059
|
+
removeElAttributes.push('id');
|
|
9060
|
+
}
|
|
9061
|
+
|
|
9062
|
+
// remove hidden elements
|
|
9063
|
+
removeHiddenSvgEls(svg);
|
|
9064
|
+
|
|
9065
|
+
// remove SVG elements
|
|
9066
|
+
removeSvgEls(svg, { removeElements, removeNameSpaced });
|
|
9067
|
+
|
|
9068
|
+
// remove SVG attributes
|
|
9069
|
+
removeSvgAtts(svg, removeSVGAttributes);
|
|
9070
|
+
|
|
9071
|
+
// remove SVG child element attributes
|
|
9072
|
+
removeSvgChildAtts(svg, removeElAttributes);
|
|
9073
|
+
|
|
9074
|
+
// general cleanup
|
|
9075
|
+
if (cleanupSVGAtts) cleanupSVGAttributes(svg, { removeIds, removeClassNames, removeDimensions, stylesToAttributes, allowMeta, allowAriaAtts, allowDataAtts });
|
|
9076
|
+
|
|
9013
9077
|
// collect all elements' properties
|
|
9014
9078
|
let svgElProps = [];
|
|
9015
9079
|
let els = svg.querySelectorAll(`${renderedEls.join(', ')}`);
|
|
@@ -9030,7 +9094,12 @@ function cleanUpSVG(svgMarkup, {
|
|
|
9030
9094
|
let styleProps = parseStylesProperties(el, propOptions);
|
|
9031
9095
|
let stylePropsFiltered = {};
|
|
9032
9096
|
|
|
9097
|
+
// reset remove array
|
|
9098
|
+
remove = [];
|
|
9099
|
+
|
|
9033
9100
|
// convert pathLength before transforming
|
|
9101
|
+
if (convertTransforms || attributesToGroup) convertPathLength = true;
|
|
9102
|
+
|
|
9034
9103
|
if (convertPathLength) {
|
|
9035
9104
|
styleProps = convertPathLengthAtt(el, { styleProps });
|
|
9036
9105
|
remove = [...new Set([...remove, ...styleProps.remove])];
|
|
@@ -9083,41 +9152,11 @@ function cleanUpSVG(svgMarkup, {
|
|
|
9083
9152
|
|
|
9084
9153
|
remove = [...new Set([...remove, ...styleProps.remove])];
|
|
9085
9154
|
|
|
9086
|
-
|
|
9087
|
-
* remove els and attributes
|
|
9088
|
-
*/
|
|
9089
|
-
|
|
9090
|
-
// remove meta
|
|
9091
|
-
if (!allowMeta) removeElements.push('meta', 'metadata', 'desc', 'title');
|
|
9155
|
+
|
|
9092
9156
|
|
|
9093
|
-
|
|
9094
|
-
|
|
9095
|
-
|
|
9096
|
-
}
|
|
9097
|
-
|
|
9098
|
-
if (removeIds) {
|
|
9099
|
-
removeSVGAttributes.push('id');
|
|
9100
|
-
removeElAttributes.push('id');
|
|
9101
|
-
}
|
|
9102
|
-
|
|
9103
|
-
// remove hidden elements
|
|
9104
|
-
removeHiddenSvgEls(svg);
|
|
9105
|
-
|
|
9106
|
-
// remove SVG elements
|
|
9107
|
-
removeSvgEls(svg, { removeElements, removeNameSpaced });
|
|
9108
|
-
|
|
9109
|
-
// remove SVG attributes
|
|
9110
|
-
removeSvgAtts(svg, removeSVGAttributes);
|
|
9111
|
-
|
|
9112
|
-
// remove SVG child element attributes
|
|
9113
|
-
removeSvgChildAtts(svg, removeElAttributes);
|
|
9114
|
-
|
|
9115
|
-
// general cleanup
|
|
9116
|
-
if (cleanupSVGAtts) cleanupSVGAttributes(svg, { removeIds, removeClassNames, removeDimensions, stylesToAttributes, allowMeta, allowAriaAtts, allowDataAtts });
|
|
9117
|
-
|
|
9118
|
-
// all relative units to absolute
|
|
9119
|
-
if (toAbsoluteUnits) {
|
|
9120
|
-
normalizeTransforms = true;
|
|
9157
|
+
// all relative units to absolute
|
|
9158
|
+
if (toAbsoluteUnits) {
|
|
9159
|
+
normalizeTransforms = true;
|
|
9121
9160
|
|
|
9122
9161
|
/**
|
|
9123
9162
|
* apply consolidated
|
|
@@ -9158,7 +9197,6 @@ function cleanUpSVG(svgMarkup, {
|
|
|
9158
9197
|
styleProps = setNormalizedTransformsToEl(el, { styleProps });
|
|
9159
9198
|
|
|
9160
9199
|
remove = [...new Set([...remove, ...styleProps.remove])];
|
|
9161
|
-
|
|
9162
9200
|
}
|
|
9163
9201
|
|
|
9164
9202
|
/**
|
|
@@ -9183,13 +9221,6 @@ function cleanUpSVG(svgMarkup, {
|
|
|
9183
9221
|
*/
|
|
9184
9222
|
removeAtts(el, remove);
|
|
9185
9223
|
|
|
9186
|
-
/*
|
|
9187
|
-
for (let i = 0; i < remove.length; i++) {
|
|
9188
|
-
let att = remove[i];
|
|
9189
|
-
el.removeAttribute(att)
|
|
9190
|
-
}
|
|
9191
|
-
*/
|
|
9192
|
-
|
|
9193
9224
|
} // endof style processing
|
|
9194
9225
|
|
|
9195
9226
|
/**
|
|
@@ -9211,7 +9242,7 @@ function cleanUpSVG(svgMarkup, {
|
|
|
9211
9242
|
|
|
9212
9243
|
// scale props like stroke width or dash-array before conversion
|
|
9213
9244
|
if (matrix && transComponents) {
|
|
9214
|
-
['stroke-width', 'stroke-dasharray'].forEach(att => {
|
|
9245
|
+
['stroke-width', 'stroke-dasharray', 'stroke-dashoffset'].forEach(att => {
|
|
9215
9246
|
let attVal = el.getAttribute(att);
|
|
9216
9247
|
let vals = attVal ? attVal.split(' ').filter(Boolean).map(Number).map(val => val * transComponents.scaleX) : [];
|
|
9217
9248
|
if (vals.length) el.setAttribute(att, vals.join(' '));
|
|
@@ -10480,71 +10511,6 @@ function pathDataRevertCubicToQuadratic(pathData, tolerance=1) {
|
|
|
10480
10511
|
return pathData
|
|
10481
10512
|
}
|
|
10482
10513
|
|
|
10483
|
-
function fixIntersectingCpts(pathData = []) {
|
|
10484
|
-
|
|
10485
|
-
let l = pathData.length;
|
|
10486
|
-
let p0 = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
10487
|
-
let p = p0;
|
|
10488
|
-
|
|
10489
|
-
for (let i = 1; i < l; i++) {
|
|
10490
|
-
let com = pathData[i];
|
|
10491
|
-
let comPrev = pathData[i - 1] || null;
|
|
10492
|
-
let { type, values } = com;
|
|
10493
|
-
pathData[i + 1] ? pathData[i + 1] : null;
|
|
10494
|
-
|
|
10495
|
-
if (type === 'C') {
|
|
10496
|
-
let tx = 1.2;
|
|
10497
|
-
let cp1 = { x: values[0], y: values[1] };
|
|
10498
|
-
p = { x: values[4], y: values[5] };
|
|
10499
|
-
let cp2 = { x: values[2], y: values[3] };
|
|
10500
|
-
|
|
10501
|
-
interpolate(p0, cp1, tx );
|
|
10502
|
-
let cp2_ex = interpolate( p, cp2, tx);
|
|
10503
|
-
|
|
10504
|
-
comPrev.values.slice(-2);
|
|
10505
|
-
|
|
10506
|
-
// extend tangents
|
|
10507
|
-
|
|
10508
|
-
let ptI = checkLineIntersection(p0, cp1, p, cp2_ex, true, true);
|
|
10509
|
-
|
|
10510
|
-
|
|
10511
|
-
/*
|
|
10512
|
-
renderPoly(markers, [p0, cp1], 'orange', '0.75')
|
|
10513
|
-
renderPoly(markers, [p, cp2], 'blue', '0.75')
|
|
10514
|
-
renderPoly(markers, [p, cp2_ex], 'magenta', '0.75')
|
|
10515
|
-
|
|
10516
|
-
renderPoint(markers, p0, 'orange', '0.75')
|
|
10517
|
-
renderPoint(markers, cp1, 'magenta', '0.75')
|
|
10518
|
-
renderPoint(markers, cp1_ex, 'blue', '0.5')
|
|
10519
|
-
|
|
10520
|
-
renderPoint(markers, cp2, 'cyan', '0.75')
|
|
10521
|
-
*/
|
|
10522
|
-
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
/*
|
|
10526
|
-
renderPoint(markers, p0, 'orange')
|
|
10527
|
-
renderPoint(markers, p, 'magenta', '0.5')
|
|
10528
|
-
*/
|
|
10529
|
-
|
|
10530
|
-
if (ptI) {
|
|
10531
|
-
let t = 0.666;
|
|
10532
|
-
cp1 = interpolate(p0, ptI, t);
|
|
10533
|
-
cp2 = interpolate(p, ptI, t);
|
|
10534
|
-
com.values = [cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y];
|
|
10535
|
-
|
|
10536
|
-
}
|
|
10537
|
-
|
|
10538
|
-
}
|
|
10539
|
-
|
|
10540
|
-
if (values.length) {
|
|
10541
|
-
p0 = p;
|
|
10542
|
-
}
|
|
10543
|
-
}
|
|
10544
|
-
|
|
10545
|
-
return pathData;
|
|
10546
|
-
}
|
|
10547
|
-
|
|
10548
10514
|
function simplifyPolyRDP(pts, {quality = 0.9, width = 0, height = 0}={}) {
|
|
10549
10515
|
|
|
10550
10516
|
/**
|
|
@@ -10739,860 +10705,817 @@ function pathDataFromPoly(pts, closed = true) {
|
|
|
10739
10705
|
|
|
10740
10706
|
}
|
|
10741
10707
|
|
|
10742
|
-
function
|
|
10708
|
+
function refineAdjacentPolyExtremes(pts = []) {
|
|
10743
10709
|
|
|
10744
10710
|
let l = pts.length;
|
|
10745
|
-
let x = 0, y = 0;
|
|
10746
|
-
for (let i = 0; l && i < l; i++) {
|
|
10747
|
-
let pt = pts[i];
|
|
10748
|
-
x += pt.x;
|
|
10749
|
-
y += pt.y;
|
|
10750
|
-
}
|
|
10751
|
-
|
|
10752
|
-
let centroid = {x: x/l, y:y/l};
|
|
10753
|
-
return centroid
|
|
10754
|
-
|
|
10755
|
-
}
|
|
10756
|
-
|
|
10757
|
-
function detectRegularPolygon(pts, centroid={x:0, y:0}) {
|
|
10758
|
-
let rSq = getSquareDistance(pts[0], centroid);
|
|
10759
|
-
let isRegular = true;
|
|
10760
|
-
|
|
10761
|
-
for (let i = 1, l = pts.length; i < l; i++) {
|
|
10762
|
-
let pt1 = pts[i];
|
|
10763
|
-
let dist = getSquareDistance(pt1, centroid);
|
|
10764
10711
|
|
|
10765
|
-
|
|
10766
|
-
|
|
10767
|
-
|
|
10768
|
-
if (diffRel > 0.05) {
|
|
10769
|
-
return false;
|
|
10770
|
-
}
|
|
10712
|
+
let { x, y, width, height, top, bottom, left, right } = getPolyBBox(pts);
|
|
10713
|
+
let threshShort = (width + height) * 0.05;
|
|
10714
|
+
let thresh = (width + height) * 0.001;
|
|
10771
10715
|
|
|
10772
|
-
|
|
10773
|
-
|
|
10774
|
-
}
|
|
10716
|
+
let pt0 = pts[0];
|
|
10717
|
+
let ptLast = pts[l - 1];
|
|
10775
10718
|
|
|
10776
|
-
|
|
10777
|
-
|
|
10778
|
-
|
|
10779
|
-
|
|
10780
|
-
|
|
10781
|
-
|
|
10782
|
-
} = {}) {
|
|
10719
|
+
/**
|
|
10720
|
+
* cleanup close path - almost vertical or horizontal
|
|
10721
|
+
* average start and end extremes
|
|
10722
|
+
*/
|
|
10723
|
+
let dx = Math.abs(ptLast.x - pt0.x);
|
|
10724
|
+
let dy = Math.abs(ptLast.y - pt0.y);
|
|
10783
10725
|
|
|
10784
|
-
|
|
10726
|
+
if (dy < threshShort || dx < threshShort) {
|
|
10785
10727
|
|
|
10786
|
-
|
|
10787
|
-
let bb0 = getPolyBBox(pts);
|
|
10728
|
+
if (pt0.isExtreme && !pt0.isCorner) {
|
|
10788
10729
|
|
|
10789
|
-
|
|
10790
|
-
|
|
10791
|
-
}
|
|
10730
|
+
let xAv = (pt0.x + ptLast.x) * 0.5;
|
|
10731
|
+
let yAv = (pt0.y + ptLast.y) * 0.5;
|
|
10792
10732
|
|
|
10793
|
-
|
|
10733
|
+
pt0.x = xAv;
|
|
10734
|
+
pt0.y = yAv;
|
|
10794
10735
|
|
|
10795
|
-
|
|
10796
|
-
|
|
10797
|
-
|
|
10798
|
-
let p1 = pts[i];
|
|
10799
|
-
let p2 = i < l - 1 ? pts[i + 1] : pts[l - 1];
|
|
10736
|
+
ptLast.x = xAv;
|
|
10737
|
+
ptLast.y = yAv;
|
|
10738
|
+
ptLast.isExtreme = true;
|
|
10800
10739
|
|
|
10801
|
-
|
|
10802
|
-
|
|
10803
|
-
|
|
10804
|
-
p1.idx = i;
|
|
10740
|
+
if (dy < thresh) {
|
|
10741
|
+
ptLast.tangentR.y = pt0.y;
|
|
10742
|
+
ptLast.tangentL.y = pt0.y;
|
|
10805
10743
|
|
|
10744
|
+
}
|
|
10745
|
+
if (dx < thresh) {
|
|
10746
|
+
ptLast.tangentR.x = pt0.x;
|
|
10747
|
+
ptLast.tangentL.x = pt0.x;
|
|
10748
|
+
}
|
|
10749
|
+
}
|
|
10806
10750
|
}
|
|
10807
10751
|
|
|
10808
|
-
for (let i =
|
|
10809
|
-
i >
|
|
10810
|
-
let p0 = i > 0 ? pts[i - 1] : pts[l - 1];
|
|
10752
|
+
for (let i = 1; i < l; i++) {
|
|
10753
|
+
i > 0 ? pts[i - 1] : pts[l - 1];
|
|
10811
10754
|
let p1 = pts[i];
|
|
10812
10755
|
let p2 = i < l - 1 ? pts[i + 1] : pts[l - 1];
|
|
10756
|
+
let dist = getDistManhattan(p1, p2);
|
|
10813
10757
|
|
|
10814
|
-
|
|
10815
|
-
|
|
10816
|
-
Math.abs(p0.area);
|
|
10817
|
-
let area1 = Math.abs(p1.area);
|
|
10818
|
-
Math.abs(p2.area);
|
|
10819
|
-
let isCorner = false;
|
|
10820
|
-
|
|
10821
|
-
let flat = !p1.area || area1 < thresh;
|
|
10822
|
-
|
|
10823
|
-
getDistManhattan(p1, p0);
|
|
10824
|
-
|
|
10825
|
-
/**
|
|
10826
|
-
* check extremes
|
|
10827
|
-
*/
|
|
10828
|
-
|
|
10829
|
-
let isExtreme = false;
|
|
10758
|
+
let { isHorizontal, isVertical, isCorner, isLong, isExtreme, isSemiExtreme, isDirChange } = p1;
|
|
10830
10759
|
|
|
10831
|
-
|
|
10832
|
-
if ((p1.x === bb0.left || p1.x === bb0.right || p1.y === bb0.top || p1.y === bb0.bottom)) {
|
|
10833
|
-
isExtreme = true;
|
|
10834
|
-
}
|
|
10760
|
+
let extremes = [];
|
|
10835
10761
|
|
|
10836
|
-
|
|
10837
|
-
|
|
10838
|
-
|
|
10762
|
+
/*
|
|
10763
|
+
if(isExtreme && p0.isCorner && !isLong && !isCorner){
|
|
10764
|
+
isExtreme= false
|
|
10765
|
+
p1.isExtreme = false
|
|
10766
|
+
p1.isHorizontal = false
|
|
10767
|
+
p1.isVertical = false
|
|
10839
10768
|
|
|
10840
|
-
|
|
10841
|
-
p0.isExtreme = true;
|
|
10842
|
-
isExtreme = true;
|
|
10769
|
+
continue;
|
|
10843
10770
|
}
|
|
10771
|
+
*/
|
|
10844
10772
|
|
|
10845
|
-
|
|
10846
|
-
|
|
10847
|
-
|
|
10848
|
-
|
|
10849
|
-
|
|
10850
|
-
|
|
10851
|
-
isExtreme = true;
|
|
10773
|
+
/*
|
|
10774
|
+
if(isExtreme && p2.isCorner && !isLong && !isCorner && dist<threshShort*0.5){
|
|
10775
|
+
isExtreme= false
|
|
10776
|
+
p1.isExtreme = false
|
|
10777
|
+
p1.isHorizontal = false
|
|
10778
|
+
p1.isVertical = false
|
|
10852
10779
|
|
|
10780
|
+
if(isVertical){
|
|
10781
|
+
p2.tangentL.x = p2.x
|
|
10782
|
+
}
|
|
10783
|
+
if(isHorizontal){
|
|
10784
|
+
p2.tangentL.y = p2.y
|
|
10785
|
+
}
|
|
10786
|
+
continue;
|
|
10853
10787
|
}
|
|
10788
|
+
*/
|
|
10854
10789
|
|
|
10855
|
-
|
|
10856
|
-
|
|
10857
|
-
*/
|
|
10858
|
-
let signChange = (p0.area < 0 && p1.area > 0) || (p0.area > 0 && p1.area < 0);
|
|
10859
|
-
let isDirChange = signChange && !flat && !p0.isDirChange;
|
|
10860
|
-
|
|
10861
|
-
/**
|
|
10862
|
-
* 3. corners
|
|
10863
|
-
*/
|
|
10864
|
-
|
|
10865
|
-
if (isExtreme) {
|
|
10866
|
-
|
|
10867
|
-
let delta = getDeltaAngle(p1, p2, p0);
|
|
10868
|
-
let { deltaAngleDeg } = delta;
|
|
10869
|
-
deltaAngleDeg = Math.abs(deltaAngleDeg);
|
|
10790
|
+
if (isExtreme && !isCorner && p2.isExtreme) {
|
|
10791
|
+
extremes.push(p1);
|
|
10870
10792
|
|
|
10871
|
-
let
|
|
10872
|
-
|
|
10793
|
+
for (let j = i + 1; j < l; j++) {
|
|
10794
|
+
let p2 = pts[j];
|
|
10795
|
+
dist = getDistManhattan(p1, p2);
|
|
10873
10796
|
|
|
10874
|
-
isCorner
|
|
10797
|
+
if (dist * 0.75 >= threshShort || p2.isCorner || p2.isDirChange) {
|
|
10798
|
+
break
|
|
10799
|
+
}
|
|
10800
|
+
if (p2.isExtreme && !p2.isDirChange && !p2.isCorner) {
|
|
10801
|
+
extremes.push(p2);
|
|
10802
|
+
}
|
|
10875
10803
|
}
|
|
10876
10804
|
|
|
10877
|
-
|
|
10805
|
+
if (extremes.length > 1) {
|
|
10878
10806
|
|
|
10879
|
-
|
|
10880
|
-
|
|
10881
|
-
if (debug) {
|
|
10807
|
+
// find best extreme according to angle
|
|
10808
|
+
let angleDiffMin = Infinity;
|
|
10882
10809
|
|
|
10883
|
-
|
|
10884
|
-
renderPoint(markers, p1, 'blue', '2%', '0.5')
|
|
10885
|
-
renderPoint(markers, p0, 'blue', '2%', '0.5')
|
|
10886
|
-
}
|
|
10810
|
+
let bestMatch = extremes[0];
|
|
10887
10811
|
|
|
10888
|
-
|
|
10889
|
-
|
|
10890
|
-
}
|
|
10812
|
+
|
|
10813
|
+
extremes.forEach(pt => {
|
|
10891
10814
|
|
|
10892
|
-
|
|
10893
|
-
|
|
10894
|
-
|
|
10815
|
+
let angle = Math.abs(getAngleFromDelta(pt.dx2, pt.dy2, false)) * rad2Deg;
|
|
10816
|
+
let angleDiff = angle > 160 ? Math.abs(180 - angle) : (angle > 60 ? Math.abs(90 - angle) : angle);
|
|
10817
|
+
pt.angle = angle;
|
|
10818
|
+
pt.angleDiff = angleDiff;
|
|
10895
10819
|
|
|
10896
|
-
|
|
10897
|
-
|
|
10898
|
-
|
|
10899
|
-
}
|
|
10900
|
-
*/
|
|
10820
|
+
if (angleDiff < angleDiffMin) {
|
|
10821
|
+
bestMatch = pt;
|
|
10822
|
+
angleDiffMin = angleDiff;
|
|
10901
10823
|
|
|
10902
|
-
|
|
10903
|
-
|
|
10904
|
-
p1.isHorizontal = isHorizontal;
|
|
10905
|
-
p1.isVertical = isVertical;
|
|
10906
|
-
p1.isDirChange = isDirChange;
|
|
10824
|
+
}
|
|
10825
|
+
});
|
|
10907
10826
|
|
|
10908
|
-
|
|
10827
|
+
let extremes2 = [];
|
|
10909
10828
|
|
|
10910
|
-
|
|
10911
|
-
let pts1 = [];
|
|
10912
|
-
let exclude = [];
|
|
10829
|
+
extremes.forEach((pt, i) => {
|
|
10913
10830
|
|
|
10914
|
-
|
|
10915
|
-
for (let i = 0; i < pts.length; i++) {
|
|
10916
|
-
let p = pts[i];
|
|
10917
|
-
let p1 = pts[i + 1] || null;
|
|
10918
|
-
let p2 = pts[i + 2] || null;
|
|
10831
|
+
let isBestMatch = pt === bestMatch;
|
|
10919
10832
|
|
|
10920
|
-
|
|
10833
|
+
if (isBestMatch) {
|
|
10921
10834
|
|
|
10922
|
-
|
|
10923
|
-
|
|
10924
|
-
|
|
10835
|
+
if (pt.isHorizontal) {
|
|
10836
|
+
pt.tangentL.y = pt.y;
|
|
10837
|
+
pt.tangentR.y = pt.y;
|
|
10838
|
+
}
|
|
10839
|
+
if (pt.isVertical) {
|
|
10840
|
+
pt.tangentL.x = pt.x;
|
|
10841
|
+
pt.tangentR.x = pt.x;
|
|
10842
|
+
}
|
|
10925
10843
|
|
|
10926
|
-
|
|
10927
|
-
extremes.push(p, p1);
|
|
10844
|
+
// renderPoint(markers, pt, 'green', '3%', '0.5')
|
|
10928
10845
|
|
|
10929
|
-
|
|
10930
|
-
|
|
10931
|
-
/*
|
|
10932
|
-
renderPoint(markers, p, 'green', '1%', '0.5')
|
|
10933
|
-
renderPoint(markers, p1, 'red', '1%', '0.5')
|
|
10934
|
-
renderPoint(markers, p2, 'blue', '1%', '0.5')
|
|
10935
|
-
*/
|
|
10936
|
-
}
|
|
10846
|
+
}
|
|
10847
|
+
else {
|
|
10937
10848
|
|
|
10938
|
-
|
|
10939
|
-
// average extreme
|
|
10849
|
+
if (bestMatch) {
|
|
10940
10850
|
|
|
10941
|
-
|
|
10942
|
-
|
|
10851
|
+
if (!isBestMatch && (pt.x === bestMatch.x || pt.y === bestMatch.y)) {
|
|
10852
|
+
extremes2.push(pt);
|
|
10853
|
+
}
|
|
10854
|
+
pt.isExtreme = false;
|
|
10855
|
+
pt.isHorizontal = false;
|
|
10856
|
+
pt.isVertical = false;
|
|
10857
|
+
}
|
|
10943
10858
|
|
|
10944
|
-
|
|
10945
|
-
p.y = y;
|
|
10859
|
+
}
|
|
10946
10860
|
|
|
10947
|
-
|
|
10948
|
-
}
|
|
10949
|
-
}
|
|
10861
|
+
});
|
|
10950
10862
|
|
|
10951
|
-
|
|
10952
|
-
|
|
10953
|
-
|
|
10863
|
+
// average coordinates
|
|
10864
|
+
if (extremes2.length) {
|
|
10865
|
+
bestMatch.x = (extremes2[0].x + bestMatch.x) * 0.5;
|
|
10866
|
+
bestMatch.y = (extremes2[0].y + bestMatch.y) * 0.5;
|
|
10954
10867
|
|
|
10955
|
-
|
|
10868
|
+
}
|
|
10956
10869
|
|
|
10957
|
-
|
|
10958
|
-
|
|
10959
|
-
let p0 = pts1[0];
|
|
10960
|
-
let pL = pts1[l2 - 1];
|
|
10961
|
-
let near0 = getDistManhattan(p0, pL) < thresh * 2;
|
|
10962
|
-
if (p0.isExtreme && pL.isExtreme && near0) {
|
|
10963
|
-
pL.x = p0.x;
|
|
10964
|
-
pL.y = p0.y;
|
|
10870
|
+
i += extremes.length;
|
|
10871
|
+
continue;
|
|
10965
10872
|
}
|
|
10966
10873
|
}
|
|
10967
10874
|
|
|
10968
|
-
pts = pts1;
|
|
10969
10875
|
}
|
|
10970
10876
|
|
|
10971
|
-
return pts
|
|
10972
10877
|
}
|
|
10973
10878
|
|
|
10974
|
-
function
|
|
10975
|
-
{ closed = true,
|
|
10976
|
-
keepCorners = true,
|
|
10977
|
-
keepExtremes = true,
|
|
10978
|
-
keepInflections = false
|
|
10979
|
-
} = {}
|
|
10980
|
-
) {
|
|
10981
|
-
let chunks = [[pts[0]]];
|
|
10982
|
-
|
|
10983
|
-
let idx = 0;
|
|
10984
|
-
let lastChunk = chunks[idx];
|
|
10879
|
+
function cleanupPolyKeypoints(pts = []) {
|
|
10985
10880
|
|
|
10986
10881
|
let l = pts.length;
|
|
10987
10882
|
|
|
10988
|
-
|
|
10883
|
+
getPolyBBox(pts);
|
|
10884
|
+
|
|
10885
|
+
pts[0];
|
|
10886
|
+
|
|
10989
10887
|
for (let i = 1; i < l; i++) {
|
|
10990
|
-
i > 0 ? pts[i] : pts[l - 1];
|
|
10888
|
+
i > 0 ? pts[i - 1] : pts[l - 1];
|
|
10991
10889
|
let p1 = pts[i];
|
|
10992
10890
|
i < l - 1 ? pts[i + 1] : pts[l - 1];
|
|
10993
10891
|
|
|
10994
|
-
|
|
10995
|
-
|
|
10996
|
-
if ((keepExtremes && p1.isExtreme || keepCorners && p1.isCorner )) {
|
|
10997
|
-
idx++;
|
|
10998
|
-
chunks.push([]);
|
|
10999
|
-
}
|
|
10892
|
+
let { isHorizontal, isVertical, isCorner, isLong, isExtreme, isSemiExtreme, isDirChange } = p1;
|
|
10893
|
+
let offset = 0;
|
|
11000
10894
|
|
|
11001
|
-
|
|
11002
|
-
lastChunk.push(p1);
|
|
11003
|
-
}
|
|
10895
|
+
if (!isSemiExtreme){
|
|
11004
10896
|
|
|
11005
|
-
|
|
10897
|
+
continue
|
|
10898
|
+
}
|
|
11006
10899
|
|
|
11007
|
-
|
|
11008
|
-
|
|
10900
|
+
if (isSemiExtreme || isExtreme) {
|
|
10901
|
+
let semiExtremes = isSemiExtreme ? [p1] : [];
|
|
11009
10902
|
|
|
11010
|
-
|
|
11011
|
-
|
|
11012
|
-
*
|
|
11013
|
-
*/
|
|
11014
|
-
function fitCurveSchneider(pts, {
|
|
11015
|
-
maxError = 0,
|
|
11016
|
-
adjustCpts = true,
|
|
11017
|
-
harmonize = true,
|
|
11018
|
-
keepCorners = true
|
|
11019
|
-
} = {}) {
|
|
10903
|
+
for (let j = i + 1; j < l; j++) {
|
|
10904
|
+
let p2 = pts[j];
|
|
11020
10905
|
|
|
11021
|
-
|
|
11022
|
-
|
|
11023
|
-
|
|
10906
|
+
if (!p2.isSemiExtreme || p2.isExtreme || p2.isCorner){
|
|
10907
|
+
break
|
|
10908
|
+
}
|
|
10909
|
+
semiExtremes.push(p2);
|
|
10910
|
+
}
|
|
11024
10911
|
|
|
11025
|
-
|
|
11026
|
-
if (pts.length === 2) {
|
|
11027
|
-
return [
|
|
11028
|
-
{ type: 'L', values: [pts[0].x, pts[0].y] },
|
|
11029
|
-
{ type: 'L', values: [pts[1].x, pts[1].y] }
|
|
11030
|
-
]
|
|
11031
|
-
}
|
|
10912
|
+
if (semiExtremes.length > 1) {
|
|
11032
10913
|
|
|
11033
|
-
|
|
10914
|
+
let semiExtremeMid = semiExtremes[Math.floor(semiExtremes.length*0.5)];
|
|
10915
|
+
let p1_1 = semiExtremes[0];
|
|
10916
|
+
let p2_1 = semiExtremes[semiExtremes.length - 1];
|
|
10917
|
+
let ptI = checkLineIntersection(p1_1, p1_1.tangentR, p2_1, p2_1.tangentL, false, true);
|
|
11034
10918
|
|
|
11035
|
-
|
|
11036
|
-
|
|
11037
|
-
|
|
11038
|
-
|
|
10919
|
+
semiExtremes.forEach(pt=>{
|
|
10920
|
+
pt.isSemiExtreme=false;
|
|
10921
|
+
});
|
|
10922
|
+
semiExtremeMid.isSemiExtreme=true;
|
|
11039
10923
|
|
|
11040
|
-
|
|
11041
|
-
|
|
11042
|
-
|
|
10924
|
+
// interpolate mid point
|
|
10925
|
+
if (ptI) {
|
|
10926
|
+
let pI_1 = interpolate(p1_1, ptI, 0.5);
|
|
10927
|
+
let pI_2 = interpolate(p2_1, ptI, 0.5);
|
|
10928
|
+
let pI_3 = interpolate(pI_2, pI_1, 0.5);
|
|
11043
10929
|
|
|
11044
|
-
|
|
10930
|
+
semiExtremeMid.x = pI_3.x;
|
|
10931
|
+
semiExtremeMid.y = pI_3.y;
|
|
10932
|
+
semiExtremeMid.tangentL = pI_1;
|
|
10933
|
+
semiExtremeMid.tangentR = pI_2;
|
|
11045
10934
|
|
|
11046
|
-
|
|
10935
|
+
i += offset;
|
|
10936
|
+
continue
|
|
10937
|
+
}
|
|
11047
10938
|
|
|
11048
|
-
|
|
10939
|
+
}
|
|
10940
|
+
}
|
|
10941
|
+
// find significant of same type
|
|
11049
10942
|
|
|
11050
|
-
|
|
11051
|
-
let com1 = pathData[0];
|
|
10943
|
+
}
|
|
11052
10944
|
|
|
11053
|
-
|
|
11054
|
-
|
|
10945
|
+
/*
|
|
10946
|
+
// update index
|
|
10947
|
+
ptsClean.forEach((pt, i) => {
|
|
10948
|
+
pt.idx = i
|
|
10949
|
+
})
|
|
10950
|
+
*/
|
|
11055
10951
|
|
|
11056
|
-
|
|
11057
|
-
let p1 = { x: pts[1].x, y: pts[1].y };
|
|
11058
|
-
let p2 = pts[2] ? { x: pts[2].x, y: pts[2].y } : null;
|
|
10952
|
+
return pts;
|
|
11059
10953
|
|
|
11060
|
-
|
|
11061
|
-
cp1 = { x: com1.values[0], y: com1.values[1] };
|
|
11062
|
-
cp1 = adjustTangentAngle(cp1, p0, p1, p2);
|
|
11063
|
-
com1.values[0] = cp1.x;
|
|
11064
|
-
com1.values[1] = cp1.y;
|
|
11065
|
-
}
|
|
10954
|
+
}
|
|
11066
10955
|
|
|
11067
|
-
|
|
11068
|
-
|
|
11069
|
-
|
|
10956
|
+
function adjustTangentAngle(cp, p0, p1, p2) {
|
|
10957
|
+
let ang1 = getAngle(p0, p1);
|
|
10958
|
+
let ang2 = getAngle(p0, p2);
|
|
10959
|
+
let angDiff = (ang2 - ang1);
|
|
11070
10960
|
|
|
11071
|
-
|
|
11072
|
-
|
|
11073
|
-
cp2 = adjustTangentAngle(cp2, pL, pL1, pL2);
|
|
11074
|
-
com2.values[2] = cp2.x;
|
|
11075
|
-
com2.values[3] = cp2.y;
|
|
11076
|
-
}
|
|
10961
|
+
let f = 0.666;
|
|
10962
|
+
f = 1;
|
|
11077
10963
|
|
|
11078
|
-
|
|
11079
|
-
|
|
11080
|
-
|
|
11081
|
-
harmonize = false;
|
|
11082
|
-
if (harmonize) {
|
|
11083
|
-
pathData = harmonizeCubicCptsThird([{ type: 'M', values: [pts[0].x, pts[0].y] },
|
|
11084
|
-
...pathData])
|
|
11085
|
-
pathData.shift()
|
|
11086
|
-
}
|
|
11087
|
-
*/
|
|
10964
|
+
cp = rotatePoint(cp, p0.x, p0.y, -angDiff * f);
|
|
10965
|
+
return cp
|
|
10966
|
+
}
|
|
11088
10967
|
|
|
11089
|
-
|
|
10968
|
+
function getTangents(pts = [], {
|
|
10969
|
+
x = 0,
|
|
10970
|
+
y = 0,
|
|
10971
|
+
width = 0,
|
|
10972
|
+
height = 0,
|
|
10973
|
+
debug = false,
|
|
10974
|
+
closed=false,
|
|
10975
|
+
} = {}) {
|
|
11090
10976
|
|
|
11091
|
-
|
|
11092
|
-
}
|
|
10977
|
+
let l = pts.length;
|
|
11093
10978
|
|
|
11094
|
-
|
|
11095
|
-
|
|
11096
|
-
|
|
11097
|
-
|
|
11098
|
-
*/
|
|
11099
|
-
function fitCubic(pts, leftTangent, rightTangent, error, keepCorners = false) {
|
|
10979
|
+
// bounding box of this sub poly
|
|
10980
|
+
if (!width || !height) {
|
|
10981
|
+
({ x, y, width, height } = getPolyBBox(pts));
|
|
10982
|
+
}
|
|
11100
10983
|
|
|
11101
|
-
|
|
11102
|
-
let bezCurve;
|
|
10984
|
+
// threshold for horizontal or vertical detection
|
|
11103
10985
|
|
|
11104
|
-
|
|
10986
|
+
for (let i = 0; i < l; i++) {
|
|
10987
|
+
let p0 = i > 0 ? pts[i - 1] : pts[l - 1];
|
|
10988
|
+
let p1 = pts[i];
|
|
10989
|
+
let p2 = i < l - 1 ? pts[i + 1] : pts[l - 1];
|
|
10990
|
+
let p3 = i < l - 1 ? pts[i + 2] : pts[l - 1];
|
|
11105
10991
|
|
|
11106
|
-
|
|
11107
|
-
let dist = getDistance(pts[0], pts[1], false) * 0.333;
|
|
11108
|
-
bezCurve = [pts[0], addArrays(pts[0], mulItems(leftTangent, dist)), addArrays(pts[1], mulItems(rightTangent, dist)), pts[1]];
|
|
11109
|
-
return [bezCurve];
|
|
11110
|
-
}
|
|
11111
|
-
*/
|
|
10992
|
+
let { isHorizontal, isVertical, isCorner, isLong, isExtreme, isSemiExtreme, isDirChange } = p1;
|
|
11112
10993
|
|
|
11113
|
-
|
|
11114
|
-
|
|
10994
|
+
// default
|
|
10995
|
+
let tangentL = { x: p1.x - p1.dx2 * 0.5, y: p1.y - p1.dy2 * 0.5 };
|
|
10996
|
+
let tangentR = { x: p1.x + p1.dx2 * 0.5, y: p1.y + p1.dy2 * 0.5 };
|
|
11115
10997
|
|
|
11116
|
-
|
|
10998
|
+
// average first tangent
|
|
10999
|
+
if(i===0){
|
|
11000
|
+
tangentR = adjustTangentAngle(p2, p1, p2, p3);
|
|
11001
|
+
}
|
|
11117
11002
|
|
|
11118
|
-
|
|
11119
|
-
|
|
11003
|
+
/**
|
|
11004
|
+
* add left and right tangents
|
|
11005
|
+
* for later curve fitting
|
|
11006
|
+
*/
|
|
11120
11007
|
|
|
11121
|
-
|
|
11122
|
-
|
|
11123
|
-
|
|
11124
|
-
|
|
11125
|
-
if (
|
|
11126
|
-
|
|
11127
|
-
|
|
11008
|
+
if (isHorizontal && !isCorner) {
|
|
11009
|
+
tangentL = { x: p1.x - p1.dx2*0.5, y: p1.y };
|
|
11010
|
+
tangentR = { x: p1.x + p1.dx2*0.5, y: p1.y };
|
|
11011
|
+
}
|
|
11012
|
+
else if (isVertical) {
|
|
11013
|
+
tangentL = { x: p1.x , y: p1.y - p1.dy2*0.5 };
|
|
11014
|
+
tangentR = { x: p1.x , y: p1.y + p1.dy2*0.5 };
|
|
11128
11015
|
}
|
|
11129
|
-
}
|
|
11130
11016
|
|
|
11131
|
-
|
|
11132
|
-
|
|
11133
|
-
|
|
11017
|
+
if (!isExtreme && p1.isLong) {
|
|
11018
|
+
tangentL = { x: p1.x - p1.dx*0.5, y: p1.y - p1.dy*0.5 };
|
|
11019
|
+
tangentR = { x: p1.x + p1.dx*0.5, y: p1.y + p1.dy*0.5 };
|
|
11020
|
+
}
|
|
11134
11021
|
|
|
11135
|
-
|
|
11022
|
+
/*
|
|
11136
11023
|
|
|
11137
|
-
|
|
11138
|
-
|
|
11139
|
-
|
|
11024
|
+
if (isDirChange && !isCorner && !isExtreme) {
|
|
11025
|
+
p1.tangentL = { x: p1.x-p1.dx2*0.5, y: p1.y-p1.dy2*0.5 }
|
|
11026
|
+
p1.tangentR = { x: p1.x+p1.dx2*0.5, y: p1.y+p1.dy2*0.5 }
|
|
11027
|
+
}
|
|
11028
|
+
*/
|
|
11140
11029
|
|
|
11141
|
-
|
|
11030
|
+
if (isCorner) {
|
|
11142
11031
|
|
|
11143
|
-
|
|
11032
|
+
tangentL = {x:p0.x, y:p0.y};
|
|
11033
|
+
tangentR = {x:p2.x, y:p2.y};
|
|
11144
11034
|
|
|
11145
|
-
let
|
|
11035
|
+
let p0_1 = pts[i - 2] ? pts[i - 2] : pts[l - 1];
|
|
11146
11036
|
|
|
11147
|
-
|
|
11148
|
-
maxError = _generateAndReport2[1];
|
|
11149
|
-
splitPoint = _generateAndReport2[2];
|
|
11037
|
+
let p2_1 = pts[i + 2] ? pts[i + 2] : pts[1];
|
|
11150
11038
|
|
|
11151
|
-
|
|
11152
|
-
|
|
11039
|
+
// adjust angle
|
|
11040
|
+
if (!p0.isCorner) {
|
|
11041
|
+
tangentL = adjustTangentAngle(p0, p1, p0, p0_1);
|
|
11153
11042
|
}
|
|
11154
11043
|
|
|
11155
|
-
|
|
11156
|
-
|
|
11157
|
-
if (errChange > .9999 && errChange < 1.0001) {
|
|
11158
|
-
break;
|
|
11159
|
-
}
|
|
11044
|
+
if (!p2.isCorner) {
|
|
11045
|
+
tangentR = adjustTangentAngle(tangentR, p1, p2, p2_1);
|
|
11160
11046
|
}
|
|
11161
11047
|
|
|
11162
|
-
|
|
11163
|
-
|
|
11048
|
+
/*
|
|
11049
|
+
renderPoint(markers, p0, 'darkblue', '0.75%', '0.5')
|
|
11050
|
+
// renderPoint(markers, p0_1, 'blue', '0.5%')
|
|
11051
|
+
renderPoint(markers, tangentL, 'blue', '0.5%', '0.5')
|
|
11052
|
+
renderPoint(markers, tangentR, 'blue', '0.5%', '0.5')
|
|
11053
|
+
*/
|
|
11054
|
+
|
|
11164
11055
|
}
|
|
11165
|
-
}
|
|
11166
11056
|
|
|
11167
|
-
|
|
11168
|
-
|
|
11057
|
+
p1.tangentL = tangentL;
|
|
11058
|
+
p1.tangentR = tangentR;
|
|
11059
|
+
|
|
11060
|
+
/*
|
|
11061
|
+
debug = true
|
|
11062
|
+
if(debug){
|
|
11063
|
+
if (isCorner || isSemiExtreme || isDirChange || isExtreme) {
|
|
11064
|
+
renderPoint(markers, p1.tangentL, 'darkred', '0.5%')
|
|
11065
|
+
renderPoint(markers, p1.tangentR, 'darkblue', '0.5%')
|
|
11066
|
+
}
|
|
11067
|
+
}
|
|
11068
|
+
*/
|
|
11169
11069
|
|
|
11170
|
-
if (centerVector.x === 0 && centerVector.y === 0) {
|
|
11171
|
-
centerVector = subtract(pts[splitPoint - 1], pts[splitPoint]);
|
|
11172
|
-
let _ref = { x: -centerVector.y, y: centerVector.x };
|
|
11173
|
-
centerVector.x = _ref.x;
|
|
11174
|
-
centerVector.y = _ref.y;
|
|
11175
11070
|
}
|
|
11176
11071
|
|
|
11177
|
-
|
|
11072
|
+
}
|
|
11178
11073
|
|
|
11179
|
-
|
|
11074
|
+
function getPolyCentroid(pts) {
|
|
11180
11075
|
|
|
11181
|
-
|
|
11076
|
+
let l = pts.length;
|
|
11077
|
+
let x = 0, y = 0;
|
|
11078
|
+
for (let i = 0; l && i < l; i++) {
|
|
11079
|
+
let pt = pts[i];
|
|
11080
|
+
x += pt.x;
|
|
11081
|
+
y += pt.y;
|
|
11082
|
+
}
|
|
11182
11083
|
|
|
11183
|
-
|
|
11184
|
-
|
|
11185
|
-
...fitCubic(pts.slice(splitPoint), fromCenterTangent, rightTangent, error, keepCorners)
|
|
11186
|
-
);
|
|
11084
|
+
let centroid = { x: x / l, y: y / l };
|
|
11085
|
+
return centroid
|
|
11187
11086
|
|
|
11188
|
-
return beziers;
|
|
11189
11087
|
}
|
|
11190
11088
|
|
|
11191
|
-
|
|
11192
|
-
|
|
11193
|
-
|
|
11194
|
-
function generateBezier(pts, parameters, leftTangent, rightTangent) {
|
|
11089
|
+
function detectRegularPolygon(pts, centroid = { x: 0, y: 0 }) {
|
|
11090
|
+
let rSq = getSquareDistance(pts[0], centroid);
|
|
11091
|
+
let isRegular = true;
|
|
11195
11092
|
|
|
11196
|
-
let
|
|
11093
|
+
for (let i = 1, l = pts.length; i < l; i++) {
|
|
11094
|
+
let pt1 = pts[i];
|
|
11095
|
+
let dist = getSquareDistance(pt1, centroid);
|
|
11197
11096
|
|
|
11198
|
-
|
|
11199
|
-
|
|
11200
|
-
let len = parameters.length;
|
|
11097
|
+
let diff = Math.abs(rSq - dist);
|
|
11098
|
+
let diffRel = diff / rSq;
|
|
11201
11099
|
|
|
11202
|
-
|
|
11203
|
-
|
|
11204
|
-
|
|
11205
|
-
a = A[i];
|
|
11100
|
+
if (diffRel > 0.05) {
|
|
11101
|
+
return false;
|
|
11102
|
+
}
|
|
11206
11103
|
|
|
11207
|
-
a[0] = mulItems(leftTangent, 3 * u * (ux * ux));
|
|
11208
|
-
a[1] = mulItems(rightTangent, 3 * ux * (u * u));
|
|
11209
11104
|
}
|
|
11105
|
+
return isRegular;
|
|
11106
|
+
}
|
|
11107
|
+
|
|
11108
|
+
function analyzePoly(pts, {
|
|
11109
|
+
x = 0,
|
|
11110
|
+
y = 0,
|
|
11111
|
+
width = 0,
|
|
11112
|
+
height = 0,
|
|
11113
|
+
debug = false
|
|
11114
|
+
} = {}) {
|
|
11210
11115
|
|
|
11211
|
-
let C = [[0, 0], [0, 0]];
|
|
11212
|
-
let X = [0, 0];
|
|
11213
11116
|
let l = pts.length;
|
|
11117
|
+
let left = x;
|
|
11118
|
+
let top = y;
|
|
11119
|
+
let right = x + width;
|
|
11120
|
+
let bottom = y + height;
|
|
11214
11121
|
|
|
11215
|
-
|
|
11216
|
-
|
|
11217
|
-
|
|
11122
|
+
if (!width || !height) {
|
|
11123
|
+
({ x, y, width, height, top, bottom, left, right } = getPolyBBox(pts));
|
|
11124
|
+
}
|
|
11125
|
+
|
|
11126
|
+
// round
|
|
11127
|
+
[x, y, width, height, top, bottom, left, right] = [x, y, width, height, top, bottom, left, right].map(val => +val.toFixed(8));
|
|
11218
11128
|
|
|
11219
|
-
|
|
11220
|
-
|
|
11221
|
-
C[1][0] += dot(a[0], a[1]);
|
|
11222
|
-
C[1][1] += dot(a[1], a[1]);
|
|
11129
|
+
// bounding box of this sub poly
|
|
11130
|
+
let bb0 = { x, y, top, left, width, height, right, bottom };
|
|
11223
11131
|
|
|
11224
|
-
|
|
11132
|
+
let thresh = (width + height) * 0.01;
|
|
11225
11133
|
|
|
11226
|
-
|
|
11227
|
-
|
|
11228
|
-
}
|
|
11134
|
+
// threshold for horizontal or vertical detection
|
|
11135
|
+
let thresh2 = thresh * 0.75;
|
|
11229
11136
|
|
|
11230
|
-
let
|
|
11231
|
-
let det_C0_X = C[0][0] * X[1] - C[1][0] * X[0];
|
|
11232
|
-
let det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
|
|
11137
|
+
let dims = [];
|
|
11233
11138
|
|
|
11234
|
-
|
|
11235
|
-
|
|
11236
|
-
|
|
11237
|
-
|
|
11139
|
+
/*
|
|
11140
|
+
pts.forEach(pt=>{
|
|
11141
|
+
renderPoint(markers, pt, 'red', '2.5%')
|
|
11142
|
+
})
|
|
11143
|
+
*/
|
|
11238
11144
|
|
|
11239
|
-
|
|
11145
|
+
/**
|
|
11146
|
+
* 1st run:
|
|
11147
|
+
* collect more details
|
|
11148
|
+
* area for sign change detection
|
|
11149
|
+
* deltas and distances
|
|
11150
|
+
*/
|
|
11151
|
+
for (let i = 0; i < l; i++) {
|
|
11152
|
+
let p0 = i > 0 ? pts[i - 1] : pts[l - 1];
|
|
11153
|
+
let p1 = pts[i];
|
|
11154
|
+
let p2 = i < l - 1 ? pts[i + 1] : pts[l - 1];
|
|
11155
|
+
|
|
11156
|
+
let area = getPolygonArea([p0, p1, p2], false);
|
|
11157
|
+
let dx = i > 0 ? +(p1.x - p0.x).toFixed(7) : 0;
|
|
11158
|
+
let dy = i > 0 ? +(p1.y - p0.y).toFixed(7) : 0;
|
|
11159
|
+
|
|
11160
|
+
let dx2 = +(p2.x - p0.x).toFixed(7);
|
|
11161
|
+
let dy2 = +(p2.y - p0.y).toFixed(7);
|
|
11162
|
+
|
|
11163
|
+
p1.area = area;
|
|
11164
|
+
p1.dist = i > 0 ? getDistManhattan(p0, p1) : 0;
|
|
11165
|
+
// add dist for long/short segment detection
|
|
11166
|
+
dims.push(p1.dist);
|
|
11167
|
+
p1.idx = i;
|
|
11168
|
+
p1.dx = dx;
|
|
11169
|
+
p1.dy = dy;
|
|
11170
|
+
p1.dx2 = dx2;
|
|
11171
|
+
p1.dy2 = dy2;
|
|
11240
11172
|
|
|
11241
|
-
bezCurve[1] = addArrays(firstPoint, mulItems(leftTangent, segLength * 0.333));
|
|
11242
|
-
bezCurve[2] = addArrays(lastPoint, mulItems(rightTangent, segLength * 0.333));
|
|
11243
|
-
} else {
|
|
11244
|
-
// First and last control pts of the Bezier curve
|
|
11245
|
-
bezCurve[1] = addArrays(firstPoint, mulItems(leftTangent, alpha_l));
|
|
11246
|
-
bezCurve[2] = addArrays(lastPoint, mulItems(rightTangent, alpha_r));
|
|
11247
11173
|
}
|
|
11248
11174
|
|
|
11249
|
-
|
|
11250
|
-
|
|
11175
|
+
/**
|
|
11176
|
+
* find average segment length
|
|
11177
|
+
* for long/short segment detection
|
|
11178
|
+
*/
|
|
11179
|
+
dims = dims.filter(Boolean).sort((a, b) => a - b);
|
|
11180
|
+
let lenD = dims.length;
|
|
11181
|
+
let dimMin = dims[0];
|
|
11182
|
+
dims[lenD - 1];
|
|
11251
11183
|
|
|
11252
|
-
|
|
11253
|
-
let
|
|
11184
|
+
let dimAv = dims.reduce((a, b) => a + b, 0) / lenD;
|
|
11185
|
+
let dimShort = (dimMin + dimAv) * 0.5;
|
|
11186
|
+
let dimLong = dimAv * 2;
|
|
11254
11187
|
|
|
11255
|
-
|
|
11256
|
-
|
|
11188
|
+
/*
|
|
11189
|
+
// round to adjust for minor deviations
|
|
11190
|
+
let idx_q = Math.ceil(lenD * 0.25);
|
|
11191
|
+
let dim_mid = dims[Math.floor(lenD * 0.5)]
|
|
11192
|
+
let dims_min = dims.slice(0, Math.floor(lenD * 0.25));
|
|
11193
|
+
let dim_min = ((dims_min.reduce((a, b) => a + b, 0) / idx_q) + dim_mid) * 0.5;
|
|
11257
11194
|
|
|
11258
|
-
|
|
11259
|
-
|
|
11195
|
+
let threshold = 75
|
|
11196
|
+
let decimalsAuto = dim_min > threshold * 1.5 ? 0 : Math.floor(threshold / dim_min).toString().length
|
|
11260
11197
|
|
|
11261
|
-
|
|
11262
|
-
|
|
11198
|
+
// clamp
|
|
11199
|
+
decimalsAuto = Math.min(Math.max(0, decimalsAuto), 8)
|
|
11263
11200
|
|
|
11264
|
-
|
|
11265
|
-
|
|
11266
|
-
|
|
11267
|
-
function reparameterize(bezier, pts, parameters) {
|
|
11268
|
-
return parameters.map((p, i) => {
|
|
11269
|
-
return newtonRaphsonRootFind(bezier, pts[i], p);
|
|
11270
|
-
});
|
|
11271
|
-
}
|
|
11272
|
-
/**
|
|
11273
|
-
* Use Newton-Raphson iteration to find better root.
|
|
11274
|
-
*/
|
|
11201
|
+
pts = roundPoly(pts, 2)
|
|
11202
|
+
console.log(pts);
|
|
11203
|
+
*/
|
|
11275
11204
|
|
|
11276
|
-
|
|
11277
|
-
|
|
11278
|
-
|
|
11279
|
-
|
|
11205
|
+
/**
|
|
11206
|
+
* analyze topology:
|
|
11207
|
+
* find significant commands:
|
|
11208
|
+
* extremes, inflections etc.
|
|
11209
|
+
*/
|
|
11210
|
+
for (let i = 0; i < l; i++) {
|
|
11280
11211
|
|
|
11281
|
-
|
|
11212
|
+
let p0 = i > 0 ? pts[i - 1] : pts[l - 1];
|
|
11213
|
+
let p1 = pts[i];
|
|
11214
|
+
let p2 = i < l - 1 ? pts[i + 1] : pts[l - 1];
|
|
11282
11215
|
|
|
11283
|
-
|
|
11216
|
+
// convert area to absolute for flatness checks
|
|
11217
|
+
let area1 = Math.abs(p1.area);
|
|
11218
|
+
let isCorner = false;
|
|
11219
|
+
let isSemiExtreme = false;
|
|
11220
|
+
let isShort = false;
|
|
11221
|
+
let isLong = false;
|
|
11284
11222
|
|
|
11285
|
-
|
|
11286
|
-
|
|
11223
|
+
/**
|
|
11224
|
+
* detect short or long
|
|
11225
|
+
*/
|
|
11226
|
+
if (p1.dist > dimLong) {
|
|
11227
|
+
isLong = true;
|
|
11228
|
+
}
|
|
11287
11229
|
|
|
11288
|
-
|
|
11289
|
-
|
|
11230
|
+
if (p1.dist < dimShort) {
|
|
11231
|
+
isShort = true;
|
|
11232
|
+
}
|
|
11290
11233
|
|
|
11291
|
-
|
|
11292
|
-
// This represents how much the error aligns with the tangent
|
|
11293
|
-
let numerator = dx * qp[0] + dy * qp[1];
|
|
11234
|
+
let flat = !p1.area || area1 < thresh;
|
|
11294
11235
|
|
|
11295
|
-
|
|
11296
|
-
|
|
11297
|
-
|
|
11236
|
+
/**
|
|
11237
|
+
* check extremes
|
|
11238
|
+
*/
|
|
11239
|
+
let isExtreme = false;
|
|
11298
11240
|
|
|
11299
|
-
|
|
11300
|
-
|
|
11241
|
+
// 1. total extreme
|
|
11242
|
+
let isTop = p1.y === bb0.top;
|
|
11243
|
+
let isBottom = p1.y === bb0.bottom;
|
|
11244
|
+
let isLeft = p1.x === bb0.left;
|
|
11245
|
+
let isRight = p1.x === bb0.right;
|
|
11301
11246
|
|
|
11302
|
-
|
|
11303
|
-
|
|
11247
|
+
if (isTop || isBottom || isLeft || isRight) {
|
|
11248
|
+
isExtreme = true;
|
|
11304
11249
|
|
|
11305
|
-
|
|
11250
|
+
}
|
|
11306
11251
|
|
|
11307
|
-
|
|
11308
|
-
|
|
11309
|
-
|
|
11252
|
+
// 1.2 horizontal or vertical
|
|
11253
|
+
/*
|
|
11254
|
+
let isHorizontal = isTop || isBottom || (p1.y === p0.y && p1.x !== p0.x);
|
|
11255
|
+
let isVertical = isLeft || isRight || (p1.x === p0.x && p1.y !== p0.y)
|
|
11310
11256
|
|
|
11311
|
-
|
|
11312
|
-
return u - numerator / denominator;
|
|
11313
|
-
}
|
|
11257
|
+
if ((isHorizontal || isVertical)) {
|
|
11314
11258
|
|
|
11315
|
-
|
|
11316
|
-
|
|
11317
|
-
*/
|
|
11318
|
-
function chordLengthParameterize(pts) {
|
|
11319
|
-
let u = [];
|
|
11320
|
-
let l = pts.length;
|
|
11321
|
-
let p0 = pts[0];
|
|
11322
|
-
let p = pts[1];
|
|
11323
|
-
let currU = 0;
|
|
11324
|
-
let prevU = 0;
|
|
11259
|
+
let diffX = Math.abs(p0.x - p1.x)
|
|
11260
|
+
let diffY = Math.abs(p0.y - p1.y)
|
|
11325
11261
|
|
|
11326
|
-
|
|
11327
|
-
|
|
11262
|
+
if (isLong) {
|
|
11263
|
+
}
|
|
11328
11264
|
|
|
11329
|
-
|
|
11330
|
-
|
|
11331
|
-
|
|
11265
|
+
if (isLong && (diffY < thresh2) && diffX > thresh) {
|
|
11266
|
+
p0.isExtreme = true;
|
|
11267
|
+
p0.isHorizontal = true;
|
|
11268
|
+
}
|
|
11269
|
+
else if (isLong && (diffX < thresh2) && diffY > thresh) {
|
|
11270
|
+
p0.isExtreme = true;
|
|
11271
|
+
p0.isVertical = true;
|
|
11272
|
+
}
|
|
11332
11273
|
|
|
11333
|
-
|
|
11334
|
-
|
|
11274
|
+
isExtreme = true
|
|
11275
|
+
}
|
|
11276
|
+
*/
|
|
11335
11277
|
|
|
11336
|
-
|
|
11337
|
-
|
|
11338
|
-
});
|
|
11278
|
+
let dx = Math.abs(p0.x - p1.x);
|
|
11279
|
+
let dy = Math.abs(p0.y - p1.y);
|
|
11339
11280
|
|
|
11340
|
-
|
|
11341
|
-
|
|
11342
|
-
|
|
11343
|
-
|
|
11344
|
-
*/
|
|
11345
|
-
function computeMaxError(pts, bez, parameters) {
|
|
11346
|
-
let dist,
|
|
11347
|
-
maxDist,
|
|
11348
|
-
splitPoint,
|
|
11349
|
-
i, point, t;
|
|
11281
|
+
let vh_thresh = thresh * 0.05;
|
|
11282
|
+
// vh_thresh = thresh * 0.25
|
|
11283
|
+
let isHorizontal = isTop || isBottom || (p1.y === p0.y && p1.x !== p0.x) || (dy <= vh_thresh);
|
|
11284
|
+
let isVertical = (isLeft || isRight || (p1.x === p0.x && p1.y !== p0.y) || (dx <= vh_thresh));
|
|
11350
11285
|
|
|
11351
|
-
|
|
11352
|
-
splitPoint = Math.floor(pts.length * 0.5);
|
|
11286
|
+
// renderPoint(markers, p1, 'red', '0.5%')
|
|
11353
11287
|
|
|
11354
|
-
|
|
11355
|
-
let l = pts.length;
|
|
11356
|
-
let ptOnPath = null;
|
|
11288
|
+
if (p1.y === p0.y) ;
|
|
11357
11289
|
|
|
11358
|
-
|
|
11359
|
-
point = pts[i];
|
|
11290
|
+
if ((isHorizontal || isVertical)) {
|
|
11360
11291
|
|
|
11361
|
-
|
|
11292
|
+
if (isLong && isHorizontal) {
|
|
11293
|
+
p0.isExtreme = true;
|
|
11294
|
+
p0.isHorizontal = true;
|
|
11362
11295
|
|
|
11363
|
-
|
|
11364
|
-
|
|
11296
|
+
}
|
|
11297
|
+
else if (isLong && isVertical) {
|
|
11298
|
+
p0.isExtreme = true;
|
|
11299
|
+
p0.isVertical = true;
|
|
11300
|
+
}
|
|
11365
11301
|
|
|
11366
|
-
|
|
11367
|
-
|
|
11368
|
-
v = subtract(pointAtT(bez, t), point);
|
|
11369
|
-
dist = v.x * v.x + v.y * v.y;
|
|
11370
|
-
*/
|
|
11302
|
+
isExtreme = true;
|
|
11303
|
+
}
|
|
11371
11304
|
|
|
11372
|
-
|
|
11305
|
+
// 1.3 is local or absolute extreme
|
|
11306
|
+
let bb = getPolyBBox([p0, p2]); // local bb
|
|
11307
|
+
let { left, right, top, bottom } = bb;
|
|
11308
|
+
|
|
11309
|
+
let extremeLocal = (p1.x < left || p1.x > right || p1.y < top || p1.y > bottom);
|
|
11310
|
+
if (!isExtreme && extremeLocal) {
|
|
11311
|
+
isExtreme = true;
|
|
11373
11312
|
|
|
11374
|
-
maxDist = dist;
|
|
11375
|
-
splitPoint = i;
|
|
11376
11313
|
}
|
|
11377
|
-
}
|
|
11378
11314
|
|
|
11379
|
-
|
|
11380
|
-
|
|
11315
|
+
/**
|
|
11316
|
+
* 2. sign changes
|
|
11317
|
+
*/
|
|
11318
|
+
let signChange = (p0.area < 0 && p1.area > 0) || (p0.area > 0 && p1.area < 0);
|
|
11319
|
+
let isDirChange = signChange && !flat && !p0.isDirChange && isLong;
|
|
11381
11320
|
|
|
11382
|
-
|
|
11383
|
-
|
|
11384
|
-
|
|
11385
|
-
let B_t_prev = bez[0];
|
|
11386
|
-
let sumLen = 0;
|
|
11321
|
+
/**
|
|
11322
|
+
* 3. corners
|
|
11323
|
+
*/
|
|
11387
11324
|
|
|
11388
|
-
|
|
11389
|
-
B_t_curr = pointAtT(bez, i / B_parts);
|
|
11390
|
-
sumLen += getDistance(B_t_curr, B_t_prev);
|
|
11391
|
-
B_t_dist.push(sumLen);
|
|
11392
|
-
B_t_prev = B_t_curr;
|
|
11393
|
-
}
|
|
11325
|
+
if (isExtreme) {
|
|
11394
11326
|
|
|
11395
|
-
|
|
11396
|
-
|
|
11397
|
-
|
|
11398
|
-
return B_t_dist;
|
|
11399
|
-
}
|
|
11400
|
-
function find_t(param, t_distMap, B_parts) {
|
|
11327
|
+
let delta = getDeltaAngle(p1, p2, p0);
|
|
11328
|
+
let { deltaAngleDeg } = delta;
|
|
11329
|
+
deltaAngleDeg = Math.abs(deltaAngleDeg);
|
|
11401
11330
|
|
|
11402
|
-
|
|
11403
|
-
|
|
11404
|
-
}
|
|
11405
|
-
if (param > 1) {
|
|
11406
|
-
return 1;
|
|
11407
|
-
}
|
|
11331
|
+
let isCornerDelta = deltaAngleDeg > 10 && deltaAngleDeg < 160;
|
|
11332
|
+
if (isCornerDelta) {
|
|
11408
11333
|
|
|
11409
|
-
|
|
11334
|
+
isCorner = true;
|
|
11410
11335
|
|
|
11411
|
-
|
|
11336
|
+
}
|
|
11412
11337
|
|
|
11413
|
-
if (param <= t_distMap[i]) {
|
|
11414
|
-
tMin = (i - 1) / B_parts;
|
|
11415
|
-
tMax = i / B_parts;
|
|
11416
|
-
lenMin = t_distMap[i - 1];
|
|
11417
|
-
lenMax = t_distMap[i];
|
|
11418
|
-
t = (param - lenMin) / (lenMax - lenMin) * (tMax - tMin) + tMin;
|
|
11419
|
-
break;
|
|
11420
11338
|
}
|
|
11421
|
-
}
|
|
11422
|
-
return t;
|
|
11423
|
-
}
|
|
11424
11339
|
|
|
11425
|
-
|
|
11340
|
+
if (isExtreme && !isCorner) {
|
|
11426
11341
|
|
|
11427
|
-
|
|
11428
|
-
|
|
11429
|
-
|
|
11342
|
+
if ((Math.abs(p1.dy2) < thresh2) && Math.abs(p1.dx2) > thresh) {
|
|
11343
|
+
isHorizontal = true;
|
|
11344
|
+
}
|
|
11345
|
+
else if (Math.abs(p1.dx2) < thresh2 && Math.abs(p1.dy2) > thresh) {
|
|
11346
|
+
isVertical = true;
|
|
11347
|
+
}
|
|
11348
|
+
}
|
|
11430
11349
|
|
|
11431
|
-
|
|
11432
|
-
|
|
11433
|
-
|
|
11350
|
+
/**
|
|
11351
|
+
* semi extremes
|
|
11352
|
+
* ~ 45deg tangent
|
|
11353
|
+
*/
|
|
11354
|
+
let diffX = Math.abs(p1.dx2);
|
|
11355
|
+
let diffY = Math.abs(p1.dy2);
|
|
11434
11356
|
|
|
11435
|
-
|
|
11436
|
-
let t = step * i;
|
|
11437
|
-
pt = pointAtT(bezCurve, t);
|
|
11438
|
-
poly.push(pt);
|
|
11439
|
-
}
|
|
11357
|
+
let ratDelta = (diffX / diffY);
|
|
11440
11358
|
|
|
11441
|
-
|
|
11359
|
+
if (ratDelta > 0.8 && ratDelta <= 1.2) {
|
|
11360
|
+
isSemiExtreme = true;
|
|
11361
|
+
}
|
|
11442
11362
|
|
|
11443
|
-
|
|
11444
|
-
|
|
11363
|
+
p1.isCorner = isCorner;
|
|
11364
|
+
p1.isExtreme = isExtreme;
|
|
11365
|
+
p1.isSemiExtreme = isSemiExtreme;
|
|
11366
|
+
p1.isLong = isLong;
|
|
11367
|
+
p1.isShort = isShort;
|
|
11368
|
+
|
|
11369
|
+
p1.isHorizontal = isHorizontal;
|
|
11370
|
+
p1.isVertical = isVertical;
|
|
11371
|
+
p1.isDirChange = isDirChange;
|
|
11445
11372
|
|
|
11446
|
-
// flat line
|
|
11447
|
-
if (!polyArea && pts.length === 2) {
|
|
11448
|
-
polyArea = getSquareDistance(pts[0], pts[1]) * 0.01;
|
|
11449
11373
|
}
|
|
11450
11374
|
|
|
11451
|
-
|
|
11452
|
-
|
|
11375
|
+
// add tangents
|
|
11376
|
+
getTangents(pts, { x, y, width, height });
|
|
11453
11377
|
|
|
11454
|
-
|
|
11378
|
+
refineAdjacentPolyExtremes(pts);
|
|
11455
11379
|
|
|
11456
|
-
//
|
|
11457
|
-
|
|
11458
|
-
if (isBulged) {
|
|
11459
|
-
let ptMid = pts[Math.floor(l * 0.5)];
|
|
11460
|
-
let p = pts[l - 1];
|
|
11461
|
-
/*
|
|
11462
|
-
let cp1 = pointAtT([pts[0], ptMid], 0.666);
|
|
11463
|
-
let cp2 = pointAtT([p, ptMid], 0.666);
|
|
11380
|
+
// filter adjacent significant points
|
|
11381
|
+
cleanupPolyKeypoints(pts);
|
|
11464
11382
|
|
|
11465
|
-
|
|
11383
|
+
renderPolyTopology(pts);
|
|
11466
11384
|
|
|
11467
|
-
|
|
11385
|
+
return pts
|
|
11386
|
+
}
|
|
11468
11387
|
|
|
11469
|
-
|
|
11470
|
-
cp1 = pointAtT([bezCurve[0], bezCurve[3]], 0.333);
|
|
11471
|
-
cp2 = pointAtT([bezCurve[0], bezCurve[3]], 0.666);
|
|
11472
|
-
bezCurve = [bezCurve[0], cp1, cp2, bezCurve[3]]
|
|
11473
|
-
*/
|
|
11388
|
+
/*
|
|
11474
11389
|
|
|
11475
|
-
|
|
11476
|
-
bezierNew = bezCurve;
|
|
11477
|
-
}
|
|
11390
|
+
*/
|
|
11478
11391
|
|
|
11479
|
-
|
|
11480
|
-
|
|
11392
|
+
// just for visualization
|
|
11393
|
+
function renderPolyTopology(pts, showTangents = true) {
|
|
11481
11394
|
|
|
11482
|
-
|
|
11483
|
-
* Creates a vector of length 1 which shows the direction from B to A
|
|
11484
|
-
*/
|
|
11485
|
-
function createTangent(p1, p2) {
|
|
11486
|
-
// Returns unit vector pointing from B to A
|
|
11487
|
-
let dx = p1.x - p2.x;
|
|
11488
|
-
let dy = p1.y - p2.y;
|
|
11489
|
-
let length = Math.sqrt(dx * dx + dy * dy);
|
|
11395
|
+
let l = pts.length;
|
|
11490
11396
|
|
|
11491
|
-
|
|
11492
|
-
|
|
11493
|
-
|
|
11397
|
+
// render
|
|
11398
|
+
for (let i = 0; i < l; i++) {
|
|
11399
|
+
i > 0 ? pts[i - 1] : pts[l - 1];
|
|
11400
|
+
let p1 = pts[i];
|
|
11401
|
+
i < l - 1 ? pts[i + 1] : pts[l - 1];
|
|
11494
11402
|
|
|
11495
|
-
|
|
11496
|
-
|
|
11497
|
-
|
|
11403
|
+
if (p1.isDirChange) {
|
|
11404
|
+
renderPoint(markers, p1, 'orange', '1%', '0.75');
|
|
11405
|
+
}
|
|
11498
11406
|
|
|
11499
|
-
|
|
11500
|
-
|
|
11501
|
-
|
|
11502
|
-
}
|
|
11407
|
+
if (p1.isSemiExtreme) {
|
|
11408
|
+
renderPoint(markers, p1, 'red', '1%', '0.5');
|
|
11409
|
+
}
|
|
11503
11410
|
|
|
11504
|
-
|
|
11505
|
-
|
|
11506
|
-
|
|
11411
|
+
/*
|
|
11412
|
+
if (p1.isLong && (p1.isDirChange || p1.isExtreme || p1.isCorner || p1.isSemiExtreme)) {
|
|
11413
|
+
renderPoint(markers, p1, 'green', '1.5%', '0.5')
|
|
11414
|
+
}
|
|
11415
|
+
*/
|
|
11507
11416
|
|
|
11508
|
-
|
|
11509
|
-
|
|
11510
|
-
}
|
|
11417
|
+
if (p1.isDirChange) {
|
|
11418
|
+
renderPoint(markers, p1, 'green', '1.5%', '0.5');
|
|
11419
|
+
}
|
|
11511
11420
|
|
|
11512
|
-
|
|
11513
|
-
|
|
11514
|
-
|
|
11515
|
-
}
|
|
11421
|
+
if (p1.isExtreme) {
|
|
11422
|
+
renderPoint(markers, p1, 'cyan', '1%', '0.5');
|
|
11423
|
+
}
|
|
11516
11424
|
|
|
11517
|
-
|
|
11518
|
-
|
|
11519
|
-
}
|
|
11425
|
+
if (p1.isHorizontal) {
|
|
11426
|
+
renderPoint(markers, p1, 'blue', '1.5%', '0.25');
|
|
11427
|
+
}
|
|
11520
11428
|
|
|
11521
|
-
|
|
11522
|
-
|
|
11523
|
-
|
|
11524
|
-
zs.push([0, 0]);
|
|
11525
|
-
}
|
|
11526
|
-
return zs;
|
|
11527
|
-
}
|
|
11429
|
+
if (p1.isVertical) {
|
|
11430
|
+
renderPoint(markers, p1, 'purple', '1.5%', '0.25');
|
|
11431
|
+
}
|
|
11528
11432
|
|
|
11529
|
-
|
|
11530
|
-
|
|
11531
|
-
|
|
11532
|
-
let t = u;
|
|
11533
|
-
let mt = 1 - t;
|
|
11534
|
-
let mt2 = mt * mt;
|
|
11535
|
-
let t2 = t * t;
|
|
11536
|
-
let dx, dy;
|
|
11433
|
+
if (p1.isCorner) {
|
|
11434
|
+
renderPoint(markers, p1, 'magenta', '1%', '1');
|
|
11435
|
+
}
|
|
11537
11436
|
|
|
11538
|
-
|
|
11539
|
-
|
|
11540
|
-
|
|
11437
|
+
if (showTangents && (p1.isCorner || p1.isSemiExtreme || p1.isDirChange || p1.isExtreme)) {
|
|
11438
|
+
renderPoint(markers, p1.tangentL, 'darkred', '0.5%');
|
|
11439
|
+
renderPoint(markers, p1.tangentR, 'darkblue', '0.5%');
|
|
11541
11440
|
|
|
11542
|
-
|
|
11543
|
-
|
|
11441
|
+
/*
|
|
11442
|
+
if (p1.isDirChange) {
|
|
11443
|
+
renderPoint(markers, p1.tangentL, 'darkred', '1.5%')
|
|
11444
|
+
renderPoint(markers, p1.tangentR, 'darkblue', '1.5%')
|
|
11445
|
+
}
|
|
11446
|
+
*/
|
|
11544
11447
|
|
|
11545
|
-
|
|
11546
|
-
dx = 3 * mt2 * (cp1.x - p0.x) +
|
|
11547
|
-
6 * mt * t * (cp2.x - cp1.x) +
|
|
11548
|
-
3 * t2 * (p1.x - cp2.x);
|
|
11448
|
+
}
|
|
11549
11449
|
|
|
11550
|
-
dy = 3 * mt2 * (cp1.y - p0.y) +
|
|
11551
|
-
6 * mt * t * (cp2.y - cp1.y) +
|
|
11552
|
-
3 * t2 * (p1.y - cp2.y);
|
|
11553
11450
|
}
|
|
11554
11451
|
|
|
11555
|
-
return [dx, dy];
|
|
11556
11452
|
}
|
|
11557
11453
|
|
|
11558
|
-
function
|
|
11559
|
-
|
|
11560
|
-
|
|
11561
|
-
|
|
11562
|
-
|
|
11563
|
-
|
|
11564
|
-
|
|
11454
|
+
function getPolyChunks(pts,
|
|
11455
|
+
{ closed = true,
|
|
11456
|
+
keepCorners = true,
|
|
11457
|
+
keepExtremes = true,
|
|
11458
|
+
keepInflections = false
|
|
11459
|
+
} = {}
|
|
11460
|
+
) {
|
|
11461
|
+
let chunks = [[pts[0]]];
|
|
11565
11462
|
|
|
11566
|
-
|
|
11567
|
-
|
|
11568
|
-
let pathData = [];
|
|
11569
|
-
beziers.forEach(bez => {
|
|
11463
|
+
let idx = 0;
|
|
11464
|
+
let lastChunk = chunks[idx];
|
|
11570
11465
|
|
|
11571
|
-
|
|
11466
|
+
let l = pts.length;
|
|
11572
11467
|
|
|
11573
|
-
|
|
11574
|
-
|
|
11575
|
-
|
|
11468
|
+
// render
|
|
11469
|
+
for (let i = 1; i < l; i++) {
|
|
11470
|
+
i > 0 ? pts[i] : pts[l - 1];
|
|
11471
|
+
let p1 = pts[i];
|
|
11472
|
+
i < l - 1 ? pts[i + 1] : pts[l - 1];
|
|
11576
11473
|
|
|
11577
|
-
|
|
11578
|
-
|
|
11579
|
-
|
|
11580
|
-
|
|
11581
|
-
|
|
11582
|
-
|
|
11474
|
+
// start new chunk
|
|
11475
|
+
// keepInflections && p1.isDirChange
|
|
11476
|
+
if ((keepExtremes && p1.isExtreme || keepCorners && p1.isCorner ||
|
|
11477
|
+
(keepInflections && p1.isDirChange && !p1.isExtreme && !p1.isCorner )
|
|
11478
|
+
)) {
|
|
11479
|
+
idx++;
|
|
11480
|
+
chunks.push([]);
|
|
11481
|
+
}
|
|
11583
11482
|
|
|
11584
|
-
|
|
11585
|
-
|
|
11586
|
-
}
|
|
11483
|
+
lastChunk = chunks[idx];
|
|
11484
|
+
lastChunk.push(p1);
|
|
11485
|
+
}
|
|
11587
11486
|
|
|
11588
|
-
|
|
11487
|
+
// test render
|
|
11488
|
+
|
|
11489
|
+
return chunks;
|
|
11589
11490
|
}
|
|
11590
11491
|
|
|
11591
|
-
function
|
|
11492
|
+
function removeCoincidingVertices(pts = []) {
|
|
11493
|
+
let l = pts.length;
|
|
11494
|
+
if (!l) return pts;
|
|
11495
|
+
|
|
11496
|
+
let ptsN = [pts[0]];
|
|
11497
|
+
let pt1, pt2;
|
|
11498
|
+
|
|
11499
|
+
for (let i = 1; i < l; i++) {
|
|
11500
|
+
pt1 = pts[i - 1];
|
|
11501
|
+
pt2 = pts[i];
|
|
11502
|
+
|
|
11503
|
+
/**
|
|
11504
|
+
* 1. Skip zero-length segments
|
|
11505
|
+
*/
|
|
11506
|
+
if (pt1.x === pt2.x && pt1.y === pt2.y) {
|
|
11507
|
+
continue;
|
|
11508
|
+
}
|
|
11509
|
+
ptsN.push(pt2);
|
|
11510
|
+
}
|
|
11511
|
+
return ptsN
|
|
11512
|
+
|
|
11513
|
+
}
|
|
11592
11514
|
|
|
11593
|
-
|
|
11515
|
+
function simplifyRC(pts = [], quality = 1, shiftStart = true) {
|
|
11594
11516
|
|
|
11595
11517
|
let l = pts.length;
|
|
11518
|
+
if (l < 4) return pts;
|
|
11596
11519
|
|
|
11597
11520
|
// starting point
|
|
11598
11521
|
let M = pts[0];
|
|
@@ -11657,7 +11580,7 @@ function simplifyRC(pts, quality = 1, shiftStart = true) {
|
|
|
11657
11580
|
let thresh = getSquareDistance(pt0, pt2) * 0.005;
|
|
11658
11581
|
|
|
11659
11582
|
// flat
|
|
11660
|
-
if (
|
|
11583
|
+
if (area <= thresh && i < l - 1) {
|
|
11661
11584
|
|
|
11662
11585
|
pt0 = pt1;
|
|
11663
11586
|
continue
|
|
@@ -11678,10 +11601,10 @@ function simplifyRC(pts, quality = 1, shiftStart = true) {
|
|
|
11678
11601
|
}
|
|
11679
11602
|
|
|
11680
11603
|
// 1st and last are colinear
|
|
11681
|
-
let area0 = getPolygonArea([ptsSmp[1], M, ptsSmp[ptsSmp.length-1]], true);
|
|
11682
|
-
let thresh0 = getSquareDistance
|
|
11604
|
+
let area0 = getPolygonArea([ptsSmp[1], M, ptsSmp[ptsSmp.length - 1]], true);
|
|
11605
|
+
let thresh0 = getSquareDistance(ptsSmp[1], ptsSmp[ptsSmp.length - 1]) * 0.005;
|
|
11683
11606
|
// remove first point
|
|
11684
|
-
if(area0 < thresh0) ptsSmp.shift();
|
|
11607
|
+
if (area0 < thresh0) ptsSmp.shift();
|
|
11685
11608
|
|
|
11686
11609
|
return ptsSmp;
|
|
11687
11610
|
}
|
|
@@ -11700,6 +11623,7 @@ function simplifyPolygonToPathData(pts, {
|
|
|
11700
11623
|
tolerance = 1,
|
|
11701
11624
|
simplifyRD = 1,
|
|
11702
11625
|
simplifyRDP = 1,
|
|
11626
|
+
isClosed = true,
|
|
11703
11627
|
} = {}) {
|
|
11704
11628
|
|
|
11705
11629
|
let polyPath = [];
|
|
@@ -11777,6 +11701,25 @@ function simplifyPolygonToPathData(pts, {
|
|
|
11777
11701
|
|
|
11778
11702
|
// remove colinear
|
|
11779
11703
|
|
|
11704
|
+
keepExtremes = false;
|
|
11705
|
+
keepCorners = false;
|
|
11706
|
+
|
|
11707
|
+
keepExtremes = true;
|
|
11708
|
+
keepCorners = true;
|
|
11709
|
+
|
|
11710
|
+
// check if closed
|
|
11711
|
+
/*
|
|
11712
|
+
let bb = getPolyBBox(pts)
|
|
11713
|
+
let thresh = (bb.width+bb.height)*0.25
|
|
11714
|
+
let dist0 = getDistManhattan(pts[0], pts[pts.length-1])
|
|
11715
|
+
|
|
11716
|
+
*/
|
|
11717
|
+
|
|
11718
|
+
// copy 1st first to end
|
|
11719
|
+
if (isClosed) {
|
|
11720
|
+
pts.push(pts[0]);
|
|
11721
|
+
}
|
|
11722
|
+
|
|
11780
11723
|
// get topology of poly
|
|
11781
11724
|
let polyAnalyzed = !keepExtremes && !keepCorners ? pts : analyzePoly(pts, {
|
|
11782
11725
|
debug: false
|
|
@@ -11784,69 +11727,369 @@ function simplifyPolygonToPathData(pts, {
|
|
|
11784
11727
|
});
|
|
11785
11728
|
|
|
11786
11729
|
// split into segment chunks
|
|
11787
|
-
|
|
11730
|
+
|
|
11731
|
+
let chunks = getPolyChunks(polyAnalyzed, { keepCorners, keepExtremes, keepInflections: true });
|
|
11788
11732
|
|
|
11789
11733
|
// Schneider curve fit
|
|
11790
11734
|
let threshold = width && height ? (width + height) / 2 * 0.004 * tolerance : 2.5;
|
|
11735
|
+
threshold = width && height ? (width + height) / 2 * 0.004 * tolerance : 2.5;
|
|
11791
11736
|
|
|
11792
|
-
|
|
11793
|
-
closed,
|
|
11794
|
-
tolerance: threshold,
|
|
11795
|
-
keepCorners,
|
|
11796
|
-
keepExtremes: true,
|
|
11797
|
-
});
|
|
11737
|
+
{
|
|
11798
11738
|
|
|
11799
|
-
|
|
11739
|
+
polyPath = simplifyPolyChunksTopology(chunks, {
|
|
11740
|
+
closed,
|
|
11741
|
+
tolerance: threshold,
|
|
11742
|
+
keepCorners,
|
|
11743
|
+
keepExtremes: true,
|
|
11744
|
+
});
|
|
11745
|
+
}
|
|
11800
11746
|
|
|
11801
11747
|
return polyPath;
|
|
11802
11748
|
}
|
|
11803
11749
|
|
|
11804
11750
|
/**
|
|
11805
|
-
*
|
|
11806
|
-
* to cubic beziers
|
|
11751
|
+
* topology based curve fit
|
|
11807
11752
|
*/
|
|
11808
|
-
|
|
11809
|
-
function simplifyPolyChunks(chunks = [], {
|
|
11753
|
+
function simplifyPolyChunksTopology(chunks = [], {
|
|
11810
11754
|
closed = true,
|
|
11811
11755
|
keepCorners = true,
|
|
11812
11756
|
tolerance = 1,
|
|
11813
11757
|
} = {}) {
|
|
11814
11758
|
|
|
11759
|
+
console.log(chunks);
|
|
11760
|
+
|
|
11815
11761
|
let l = chunks.length;
|
|
11816
11762
|
|
|
11817
11763
|
// new pathData
|
|
11764
|
+
|
|
11818
11765
|
let pathData = [{ type: 'M', values: [chunks[0][0].x, chunks[0][0].y] }];
|
|
11819
11766
|
|
|
11767
|
+
// loop chunks
|
|
11820
11768
|
for (let i = 0; i < l; i++) {
|
|
11821
11769
|
|
|
11770
|
+
let chunkPrev = i > 0 ? chunks[i - 1] : (closed ? chunks[l - 1] : null);
|
|
11822
11771
|
let chunk = chunks[i];
|
|
11823
|
-
let chunkN = chunks[i + 1] ? chunks[i + 1] : null;
|
|
11772
|
+
let chunkN = chunks[i + 1] ? chunks[i + 1] : (closed ? chunks[0] : null);
|
|
11824
11773
|
let segments = [];
|
|
11825
|
-
let chunklen = chunk.length;
|
|
11826
|
-
chunk[chunk.length - 1];
|
|
11827
11774
|
|
|
11828
11775
|
// add from next command
|
|
11829
11776
|
if (chunkN) {
|
|
11830
11777
|
chunk.push(chunkN[0]);
|
|
11831
11778
|
}
|
|
11832
11779
|
|
|
11833
|
-
|
|
11834
|
-
|
|
11835
|
-
|
|
11836
|
-
|
|
11780
|
+
let chunklen = chunk.length;
|
|
11781
|
+
let hasInflection = false;
|
|
11782
|
+
let segments_1 = [], segments_2 = [], segments_3 = [];
|
|
11783
|
+
let segsRequired = 3;
|
|
11784
|
+
|
|
11785
|
+
// 1st point
|
|
11786
|
+
let p1 = chunk[0];
|
|
11787
|
+
// last point in chunk
|
|
11788
|
+
let p2 = chunk[chunklen - 1];
|
|
11789
|
+
|
|
11790
|
+
// nothing to simplify - lineto
|
|
11791
|
+
|
|
11792
|
+
// if (chunklen < 2 || (chunklen === 2 && (chunk[1].isExtreme || i===l-1 && !closed) )) {
|
|
11793
|
+
|
|
11794
|
+
if (chunklen < 2 || (chunklen === 2 && (chunk[1].isExtreme))) {
|
|
11795
|
+
|
|
11796
|
+
if (chunklen === 2) {
|
|
11797
|
+
segsRequired = 2;
|
|
11798
|
+
segments_2 = [
|
|
11799
|
+
{
|
|
11800
|
+
type: 'L',
|
|
11801
|
+
values: [p1.x, p1.y],
|
|
11802
|
+
p0: chunkPrev,
|
|
11803
|
+
p: p1,
|
|
11804
|
+
},
|
|
11805
|
+
{
|
|
11806
|
+
type: 'L',
|
|
11807
|
+
values: [chunk[1].x, chunk[1].y],
|
|
11808
|
+
p0: p1,
|
|
11809
|
+
p: p2,
|
|
11810
|
+
}
|
|
11811
|
+
];
|
|
11812
|
+
} else {
|
|
11813
|
+
segsRequired = 1;
|
|
11814
|
+
segments_1 = [
|
|
11815
|
+
{
|
|
11816
|
+
type: 'L',
|
|
11817
|
+
values: [p1.x, p1.y],
|
|
11818
|
+
p0: chunkPrev,
|
|
11819
|
+
p: p2,
|
|
11820
|
+
},
|
|
11821
|
+
];
|
|
11822
|
+
}
|
|
11837
11823
|
|
|
11838
11824
|
} else {
|
|
11839
|
-
|
|
11840
|
-
|
|
11841
|
-
|
|
11825
|
+
|
|
11826
|
+
// point before inflection
|
|
11827
|
+
let p3 = chunk[chunklen - 2];
|
|
11828
|
+
|
|
11829
|
+
chunk.filter(pt => pt.isExtreme);
|
|
11830
|
+
let semiExtremes = chunk.filter(pt => pt.isSemiExtreme);
|
|
11831
|
+
chunk.filter(pt => pt.isCorner);
|
|
11832
|
+
let inflections = chunk.filter(pt => pt.isDirChange && !pt.isCorner && !pt.isExtreme);
|
|
11833
|
+
hasInflection = inflections.length && inflections[0] !== p1;
|
|
11834
|
+
|
|
11835
|
+
let idxMid = Math.floor(chunklen * 0.5);
|
|
11836
|
+
let pMid = semiExtremes.length ? semiExtremes[Math.floor(semiExtremes.length * 0.5)] : chunk[idxMid];
|
|
11837
|
+
|
|
11838
|
+
let dist0 = getDistManhattan(p1, p3);
|
|
11839
|
+
let dist1 = getDistManhattan(pMid, p3);
|
|
11840
|
+
let dist2 = getDistManhattan(pMid, p1);
|
|
11841
|
+
let thresh = dist0 * 0.25;
|
|
11842
|
+
let shortMidSegment = dist1 < thresh || dist2 < thresh;
|
|
11843
|
+
|
|
11844
|
+
/**
|
|
11845
|
+
* we have 3 modes
|
|
11846
|
+
* 1 segment: only 1 segment between extremes/corners
|
|
11847
|
+
* 2 segments: semiextreme/mid in between
|
|
11848
|
+
* 3 segments: inflection
|
|
11849
|
+
*/
|
|
11850
|
+
|
|
11851
|
+
segsRequired = shortMidSegment ? (!hasInflection ? 1 : 2) : (hasInflection ? 3 : 2);
|
|
11852
|
+
|
|
11853
|
+
let cp1_1 = p1.tangentR;
|
|
11854
|
+
let cp2_1 = pMid.tangentL;
|
|
11855
|
+
let p_1 = pMid;
|
|
11856
|
+
|
|
11857
|
+
// renderPoint(markers, pMid, 'orange', '2%')
|
|
11858
|
+
|
|
11859
|
+
let cp2_2 = p2.tangentL;
|
|
11860
|
+
let cp1_2 = pMid.tangentR;
|
|
11861
|
+
let p_2 = p2;
|
|
11862
|
+
|
|
11863
|
+
let cp1_3 = null;
|
|
11864
|
+
let cp2_3 = null;
|
|
11865
|
+
let p_3 = null;
|
|
11866
|
+
|
|
11867
|
+
// general extrapolation
|
|
11868
|
+
let t = 0.666;
|
|
11869
|
+
let ptI_1 = null, ptI_2 = null, ptI_3 = null;
|
|
11870
|
+
|
|
11871
|
+
// 1 segment
|
|
11872
|
+
ptI_1 = checkLineIntersection(p1, p1.tangentR, p2, p2.tangentL, false, true);
|
|
11873
|
+
if (ptI_1) {
|
|
11874
|
+
cp1_1 = interpolate(p1, ptI_1, t);
|
|
11875
|
+
cp2_1 = interpolate(p2, ptI_1, t);
|
|
11876
|
+
p_1 = p2;
|
|
11877
|
+
|
|
11878
|
+
segments_1 = [
|
|
11879
|
+
|
|
11880
|
+
{
|
|
11881
|
+
type: 'C',
|
|
11882
|
+
values: [cp1_1.x, cp1_1.y, cp2_1.x, cp2_1.y, p_1.x, p_1.y],
|
|
11883
|
+
p0: p1,
|
|
11884
|
+
cp1: cp1_1,
|
|
11885
|
+
cp2: cp2_1,
|
|
11886
|
+
p: p_1,
|
|
11887
|
+
}
|
|
11888
|
+
];
|
|
11889
|
+
|
|
11890
|
+
}
|
|
11891
|
+
|
|
11892
|
+
// 2 segments
|
|
11893
|
+
ptI_1 = checkLineIntersection(p1, p1.tangentR, pMid, pMid.tangentL, false, true);
|
|
11894
|
+
ptI_2 = checkLineIntersection(p2, p2.tangentL, pMid, pMid.tangentR, false, true);
|
|
11895
|
+
|
|
11896
|
+
if (ptI_1 && ptI_2) {
|
|
11897
|
+
cp1_1 = interpolate(p1, ptI_1, t);
|
|
11898
|
+
cp2_1 = interpolate(pMid, ptI_1, t);
|
|
11899
|
+
p_1 = pMid;
|
|
11900
|
+
|
|
11901
|
+
cp1_2 = interpolate(pMid, ptI_2, t);
|
|
11902
|
+
cp2_2 = interpolate(p2, ptI_2, t);
|
|
11903
|
+
p_2 = p2;
|
|
11904
|
+
|
|
11905
|
+
segments_2 = [
|
|
11906
|
+
|
|
11907
|
+
{
|
|
11908
|
+
type: 'C',
|
|
11909
|
+
values: [cp1_1.x, cp1_1.y, cp2_1.x, cp2_1.y, p_1.x, p_1.y],
|
|
11910
|
+
p0: p1,
|
|
11911
|
+
cp1: cp1_1,
|
|
11912
|
+
cp2: cp2_1,
|
|
11913
|
+
p: p_1,
|
|
11914
|
+
isExtreme: p_1.isExtreme
|
|
11915
|
+
},
|
|
11916
|
+
{
|
|
11917
|
+
type: 'C',
|
|
11918
|
+
values: [cp1_2.x, cp1_2.y, cp2_2.x, cp2_2.y, p_2.x, p_2.y],
|
|
11919
|
+
p0: p_1,
|
|
11920
|
+
cp1: cp1_2,
|
|
11921
|
+
cp2: cp2_2,
|
|
11922
|
+
p: p_2,
|
|
11923
|
+
isExtreme: p_2.isExtreme
|
|
11924
|
+
|
|
11925
|
+
},
|
|
11926
|
+
];
|
|
11927
|
+
|
|
11928
|
+
}
|
|
11929
|
+
|
|
11930
|
+
// 3 segments
|
|
11931
|
+
|
|
11932
|
+
if (hasInflection) {
|
|
11933
|
+
|
|
11934
|
+
// get pt between dir change and mid
|
|
11935
|
+
let idx_3_4 = Math.floor(chunklen * 0.75);
|
|
11936
|
+
p3 = chunk[idx_3_4];
|
|
11937
|
+
ptI_3 = checkLineIntersection(p3, p3.tangentR, p2, p2.tangentL, false, false);
|
|
11938
|
+
|
|
11939
|
+
if (ptI_3) {
|
|
11940
|
+
let tangentR_beforeDirChange = interpolate(p3, ptI_3, t);
|
|
11941
|
+
|
|
11942
|
+
// extend right tangent
|
|
11943
|
+
p3.tangentR.x = tangentR_beforeDirChange.x;
|
|
11944
|
+
p3.tangentR.y = tangentR_beforeDirChange.y;
|
|
11945
|
+
|
|
11946
|
+
// extend dir change tangent
|
|
11947
|
+
let tangentL_dirChange = interpolate(p2, ptI_3, t);
|
|
11948
|
+
p2.tangentL.x = tangentL_dirChange.x;
|
|
11949
|
+
p2.tangentL.y = tangentL_dirChange.y;
|
|
11950
|
+
} else {
|
|
11951
|
+
|
|
11952
|
+
if (p3 === p2) {
|
|
11953
|
+
|
|
11954
|
+
idx_3_4 = Math.floor(chunklen * 0.3);
|
|
11955
|
+
p3 = chunk[idx_3_4];
|
|
11956
|
+
|
|
11957
|
+
}
|
|
11958
|
+
checkLineIntersection(p3, p3.tangentR, p2, p2.tangentL, false, false);
|
|
11959
|
+
|
|
11960
|
+
cp1_1 = interpolate(p1, p1.tangentR, 1.333);
|
|
11961
|
+
cp2_1 = interpolate(p2, p2.tangentL, 1.333);
|
|
11962
|
+
|
|
11963
|
+
segments_3 = [
|
|
11964
|
+
{
|
|
11965
|
+
type: 'C',
|
|
11966
|
+
values: [cp1_1.x, cp1_1.y, cp2_1.x, cp2_1.y, p2.x, p2.y],
|
|
11967
|
+
p0: p1,
|
|
11968
|
+
cp1: cp1_1,
|
|
11969
|
+
cp2: cp2_1,
|
|
11970
|
+
p: p2,
|
|
11971
|
+
isExtreme: p2.isExtreme
|
|
11972
|
+
|
|
11973
|
+
},
|
|
11974
|
+
];
|
|
11975
|
+
|
|
11976
|
+
/*
|
|
11977
|
+
let tangentR_beforeDirChange = interpolate(p3, ptI_3, t)
|
|
11978
|
+
|
|
11979
|
+
// extend right tangent
|
|
11980
|
+
p3.tangentR.x = tangentR_beforeDirChange.x
|
|
11981
|
+
p3.tangentR.y = tangentR_beforeDirChange.y
|
|
11982
|
+
|
|
11983
|
+
let tangentL_dirChange = interpolate(p2, ptI_3, t)
|
|
11984
|
+
p2.tangentL.x = tangentL_dirChange.x
|
|
11985
|
+
p2.tangentL.y = tangentL_dirChange.y
|
|
11986
|
+
*/
|
|
11987
|
+
|
|
11988
|
+
pathDataToD([{ type: 'M', values: [p1.x, p1.y] }, ...segments_3]);
|
|
11989
|
+
|
|
11990
|
+
}
|
|
11991
|
+
|
|
11992
|
+
cp1_3 = p3.tangentR;
|
|
11993
|
+
cp2_3 = p2.tangentL;
|
|
11994
|
+
p_3 = p2;
|
|
11995
|
+
|
|
11996
|
+
ptI_1 = checkLineIntersection(p1, p1.tangentR, pMid, pMid.tangentL, false, true);
|
|
11997
|
+
ptI_2 = checkLineIntersection(pMid, pMid.tangentR, p3, p3.tangentL, false, true);
|
|
11998
|
+
|
|
11999
|
+
if (ptI_1 && ptI_2 && ptI_3) {
|
|
12000
|
+
|
|
12001
|
+
cp1_1 = interpolate(p1, ptI_1, t);
|
|
12002
|
+
cp2_1 = interpolate(pMid, ptI_1, t);
|
|
12003
|
+
p_1 = pMid;
|
|
12004
|
+
|
|
12005
|
+
cp1_2 = interpolate(pMid, ptI_2, t);
|
|
12006
|
+
cp2_2 = interpolate(p3, ptI_2, t);
|
|
12007
|
+
p_2 = p3;
|
|
12008
|
+
|
|
12009
|
+
segments_3 = [
|
|
12010
|
+
{
|
|
12011
|
+
type: 'C',
|
|
12012
|
+
values: [cp1_1.x, cp1_1.y, cp2_1.x, cp2_1.y, p_1.x, p_1.y],
|
|
12013
|
+
p0: p1,
|
|
12014
|
+
cp1: cp1_1,
|
|
12015
|
+
cp2: cp2_1,
|
|
12016
|
+
p: p_1,
|
|
12017
|
+
isExtreme: p_1.isExtreme
|
|
12018
|
+
|
|
12019
|
+
},
|
|
12020
|
+
{
|
|
12021
|
+
type: 'C',
|
|
12022
|
+
values: [cp1_2.x, cp1_2.y, cp2_2.x, cp2_2.y, p_2.x, p_2.y],
|
|
12023
|
+
p0: p_1,
|
|
12024
|
+
cp1: cp1_2,
|
|
12025
|
+
cp2: cp2_2,
|
|
12026
|
+
p: p_2,
|
|
12027
|
+
},
|
|
12028
|
+
{
|
|
12029
|
+
type: 'C',
|
|
12030
|
+
values: [cp1_3.x, cp1_3.y, cp2_3.x, cp2_3.y, p_3.x, p_3.y],
|
|
12031
|
+
p0: p_2,
|
|
12032
|
+
cp1: cp1_3,
|
|
12033
|
+
cp2: cp2_3,
|
|
12034
|
+
p: p_3,
|
|
12035
|
+
isExtreme: p_3.isExtreme
|
|
12036
|
+
|
|
12037
|
+
}
|
|
12038
|
+
];
|
|
12039
|
+
|
|
12040
|
+
pathDataToD([{ type: 'M', values: [p1.x, p1.y] }, ...segments_3]);
|
|
12041
|
+
|
|
12042
|
+
}
|
|
12043
|
+
|
|
12044
|
+
}
|
|
12045
|
+
|
|
12046
|
+
}
|
|
12047
|
+
|
|
12048
|
+
if (segsRequired === 1) {
|
|
12049
|
+
segments = segments_1;
|
|
12050
|
+
} else if (segsRequired === 2 && segments_2.length) {
|
|
12051
|
+
segments = segments_2;
|
|
12052
|
+
}
|
|
12053
|
+
else ;
|
|
12054
|
+
|
|
12055
|
+
segments = segments_3.length ? segments_3 : segments_2;
|
|
12056
|
+
/*
|
|
12057
|
+
if (simplify && !isLinetoSeg && segments.length > 1) {
|
|
12058
|
+
|
|
12059
|
+
let com1 = segments[0]
|
|
12060
|
+
let com2 = segments[1]
|
|
12061
|
+
|
|
12062
|
+
tolerance = 1.1
|
|
12063
|
+
let combined = combineCubicPairs(com1, com2, { tolerance })
|
|
12064
|
+
|
|
12065
|
+
let error = 0;
|
|
12066
|
+
let comsSimp =[]
|
|
12067
|
+
|
|
12068
|
+
console.log('!!!combined', segments.length, combined);
|
|
12069
|
+
|
|
12070
|
+
// success
|
|
12071
|
+
if (combined.length === 1) {
|
|
12072
|
+
|
|
12073
|
+
if(segments.length === 2){
|
|
12074
|
+
segments = combined
|
|
12075
|
+
}
|
|
12076
|
+
|
|
12077
|
+
let com = combined[0]
|
|
12078
|
+
}
|
|
12079
|
+
|
|
11842
12080
|
}
|
|
12081
|
+
*/
|
|
11843
12082
|
|
|
11844
12083
|
// remove first segment to connect to last segment
|
|
11845
12084
|
pathData.push(...segments);
|
|
11846
12085
|
|
|
11847
12086
|
}
|
|
11848
12087
|
|
|
11849
|
-
if (closed)
|
|
12088
|
+
if (closed) {
|
|
12089
|
+
pathData.push({ type: 'Z', values: [] });
|
|
12090
|
+
}
|
|
12091
|
+
|
|
12092
|
+
// refine extremes
|
|
11850
12093
|
return pathData
|
|
11851
12094
|
|
|
11852
12095
|
}
|
|
@@ -11857,13 +12100,18 @@ function simplifyPolyChunks(chunks = [], {
|
|
|
11857
12100
|
*/
|
|
11858
12101
|
function pathDataToPolygonOpt(pathData, {
|
|
11859
12102
|
precisionPoly = 1,
|
|
11860
|
-
autoAccuracy=false,
|
|
11861
|
-
polyFormat='
|
|
11862
|
-
decimals
|
|
11863
|
-
simplifyRD=1,
|
|
11864
|
-
simplifyRDP=1,
|
|
12103
|
+
autoAccuracy = false,
|
|
12104
|
+
polyFormat = 'object',
|
|
12105
|
+
decimals = -1,
|
|
12106
|
+
simplifyRD = 1,
|
|
12107
|
+
simplifyRDP = 1,
|
|
11865
12108
|
} = {}) {
|
|
11866
12109
|
|
|
12110
|
+
pathData = convertPathData(pathData, {toAbsolute:true, toLonghands:true, arcToCubic:true});
|
|
12111
|
+
pathData = addExtremePoints(pathData);
|
|
12112
|
+
|
|
12113
|
+
pathData = getPathDataVerbose(pathData);
|
|
12114
|
+
|
|
11867
12115
|
let l = pathData.length;
|
|
11868
12116
|
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
11869
12117
|
let p0 = M;
|
|
@@ -11890,7 +12138,7 @@ simplifyRDP=1,
|
|
|
11890
12138
|
let pts2 = [pts[0]];
|
|
11891
12139
|
|
|
11892
12140
|
// adjustments for very small or large paths
|
|
11893
|
-
dims = dims.filter(Boolean).sort();
|
|
12141
|
+
dims = dims.filter(Boolean).sort((a,b)=>a-b);
|
|
11894
12142
|
let dimMax = dims[dims.length - 1];
|
|
11895
12143
|
|
|
11896
12144
|
let scale = dimMax > 2 && dimMax < 25 ? 1 : (20 / dimMax);
|
|
@@ -11929,31 +12177,33 @@ simplifyRDP=1,
|
|
|
11929
12177
|
}
|
|
11930
12178
|
|
|
11931
12179
|
// simplify polygon
|
|
11932
|
-
if(simplifyRD>0){
|
|
11933
|
-
pts2 = simplifyPolyRD(pts2, {quality:simplifyRD});
|
|
12180
|
+
if (simplifyRD > 0) {
|
|
12181
|
+
pts2 = simplifyPolyRD(pts2, { quality: simplifyRD });
|
|
11934
12182
|
}
|
|
11935
12183
|
|
|
11936
|
-
if(simplifyRDP>0){
|
|
11937
|
-
pts2 = simplifyPolyRDP(pts2, {quality:simplifyRDP});
|
|
12184
|
+
if (simplifyRDP > 0) {
|
|
12185
|
+
pts2 = simplifyPolyRDP(pts2, { quality: simplifyRDP });
|
|
11938
12186
|
}
|
|
11939
12187
|
|
|
11940
|
-
|
|
11941
|
-
pathData = pathDataPoly;
|
|
11942
|
-
|
|
11943
|
-
if(autoAccuracy){
|
|
12188
|
+
if (autoAccuracy) {
|
|
11944
12189
|
decimals = detectAccuracyPoly(pts);
|
|
11945
12190
|
}
|
|
11946
12191
|
|
|
11947
|
-
let poly = decimals
|
|
12192
|
+
let poly = decimals > -1 ? pts2.map(pt => { return { x: roundTo(pt.x, decimals), y: roundTo(pt.y, decimals) } }) : pts2.map(pt => { return { x: pt.x, y: pt.y } });
|
|
11948
12193
|
|
|
11949
|
-
|
|
12194
|
+
pathDataPoly = pathDataFromPoly(poly);
|
|
12195
|
+
pathData = pathDataPoly;
|
|
12196
|
+
|
|
12197
|
+
if (polyFormat === 'array') {
|
|
11950
12198
|
poly = poly.map(pt => { return [pt.x, pt.y] });
|
|
11951
12199
|
}
|
|
11952
|
-
else if(polyFormat==='string'){
|
|
12200
|
+
else if (polyFormat === 'string') {
|
|
11953
12201
|
poly = poly.map(pt => { return [pt.x, pt.y].join(',') }).flat().join(' ');
|
|
11954
12202
|
}
|
|
11955
12203
|
|
|
11956
|
-
|
|
12204
|
+
let d= pathDataToD(pathData);
|
|
12205
|
+
|
|
12206
|
+
return { pathData, poly, d }
|
|
11957
12207
|
|
|
11958
12208
|
}
|
|
11959
12209
|
|
|
@@ -12194,6 +12444,7 @@ let settingsDefaults = {
|
|
|
12194
12444
|
// polygon
|
|
12195
12445
|
toPolygon: false,
|
|
12196
12446
|
smoothPoly: false,
|
|
12447
|
+
isClosed:true,
|
|
12197
12448
|
polyFormat: 'object',
|
|
12198
12449
|
precisionPoly: 1,
|
|
12199
12450
|
simplifyRD: 0,
|
|
@@ -12342,7 +12593,6 @@ const presetSettings = {
|
|
|
12342
12593
|
addViewBox: true,
|
|
12343
12594
|
removeDimensions: true,
|
|
12344
12595
|
removeOffCanvas: true,
|
|
12345
|
-
|
|
12346
12596
|
/*
|
|
12347
12597
|
*/
|
|
12348
12598
|
}
|
|
@@ -12491,6 +12741,122 @@ function checkBBoxIntersections2(bb, bb1) {
|
|
|
12491
12741
|
}
|
|
12492
12742
|
*/
|
|
12493
12743
|
|
|
12744
|
+
function SlickVGObj(props = {}) {
|
|
12745
|
+
|
|
12746
|
+
Object.assign(this, props);
|
|
12747
|
+
}
|
|
12748
|
+
|
|
12749
|
+
SlickVGObj.prototype.getD = function () {
|
|
12750
|
+
let d = this.d;
|
|
12751
|
+
return d;
|
|
12752
|
+
};
|
|
12753
|
+
|
|
12754
|
+
SlickVGObj.prototype.getSvg = function () {
|
|
12755
|
+
let svg = this.svg;
|
|
12756
|
+
|
|
12757
|
+
if (!svg) {
|
|
12758
|
+
let xArr = [];
|
|
12759
|
+
let yArr = [];
|
|
12760
|
+
let d = this.d;
|
|
12761
|
+
let pathDataPlusArr = this.pathDataPlusArr || [];
|
|
12762
|
+
pathDataPlusArr.forEach(path => {
|
|
12763
|
+
path.forEach(sub => {
|
|
12764
|
+
let { pathData } = sub;
|
|
12765
|
+
let bb = getPathDataBBox(pathData);
|
|
12766
|
+
let { x, y, right, bottom } = bb;
|
|
12767
|
+
xArr.push(x, right);
|
|
12768
|
+
yArr.push(y, bottom);
|
|
12769
|
+
});
|
|
12770
|
+
});
|
|
12771
|
+
|
|
12772
|
+
let x = Math.min(...xArr);
|
|
12773
|
+
let right = Math.max(...xArr);
|
|
12774
|
+
let y = Math.min(...yArr);
|
|
12775
|
+
let bottom = Math.max(...yArr);
|
|
12776
|
+
let width = right - x;
|
|
12777
|
+
let height = bottom - y;
|
|
12778
|
+
|
|
12779
|
+
svg = `<svg xmlns="${svgNs}" viewBox="${[x, y, width, height].join(' ')}"><path d="${d}"/></svg>`;
|
|
12780
|
+
|
|
12781
|
+
}
|
|
12782
|
+
return svg;
|
|
12783
|
+
};
|
|
12784
|
+
|
|
12785
|
+
/**
|
|
12786
|
+
* retrieve poly
|
|
12787
|
+
* formats: points, array, string, pathData, d,
|
|
12788
|
+
*/
|
|
12789
|
+
SlickVGObj.prototype.getPoly = function (options = {}
|
|
12790
|
+
) {
|
|
12791
|
+
|
|
12792
|
+
options = {
|
|
12793
|
+
...{
|
|
12794
|
+
precisionPoly: 1,
|
|
12795
|
+
simplifyRDP: 0,
|
|
12796
|
+
simplifyRD: 0,
|
|
12797
|
+
autoAccuracy: true,
|
|
12798
|
+
decimals: 3,
|
|
12799
|
+
format: 'object'
|
|
12800
|
+
},
|
|
12801
|
+
...options
|
|
12802
|
+
};
|
|
12803
|
+
|
|
12804
|
+
let { precisionPoly, simplifyRDP, simplifyRD, autoAccuracy, decimals, format } = options;
|
|
12805
|
+
|
|
12806
|
+
let polyFormat = format;
|
|
12807
|
+
|
|
12808
|
+
let polys = this.polys;
|
|
12809
|
+
if (!polys.length) {
|
|
12810
|
+
let pathDataPlusArr = this.pathDataPlusArr || [];
|
|
12811
|
+
let poly = [];
|
|
12812
|
+
let polyPaths = [];
|
|
12813
|
+
let dPoly = '';
|
|
12814
|
+
pathDataPlusArr.forEach(path => {
|
|
12815
|
+
path.forEach(sub => {
|
|
12816
|
+
let { pathData } = sub;
|
|
12817
|
+
|
|
12818
|
+
let polyData = pathDataToPolygonOpt(pathData, {
|
|
12819
|
+
precisionPoly,
|
|
12820
|
+
autoAccuracy,
|
|
12821
|
+
decimals,
|
|
12822
|
+
simplifyRD,
|
|
12823
|
+
simplifyRDP,
|
|
12824
|
+
polyFormat
|
|
12825
|
+
});
|
|
12826
|
+
|
|
12827
|
+
dPoly += polyData.d;
|
|
12828
|
+
poly.push(polyData.poly);
|
|
12829
|
+
polyPaths.push(polyData.pathData);
|
|
12830
|
+
|
|
12831
|
+
});
|
|
12832
|
+
});
|
|
12833
|
+
|
|
12834
|
+
if (polyFormat === 'object' || polyFormat === 'array' || polyFormat === 'string') {
|
|
12835
|
+
polys = poly;
|
|
12836
|
+
}
|
|
12837
|
+
else if (polyFormat === 'pathData') {
|
|
12838
|
+
polys = polyPaths.flat();
|
|
12839
|
+
}
|
|
12840
|
+
|
|
12841
|
+
else if (polyFormat === 'd') {
|
|
12842
|
+
polys = dPoly;
|
|
12843
|
+
}
|
|
12844
|
+
|
|
12845
|
+
}
|
|
12846
|
+
return polys;
|
|
12847
|
+
};
|
|
12848
|
+
|
|
12849
|
+
/*
|
|
12850
|
+
export function PathLengthObject(props = {}) {
|
|
12851
|
+
Object.assign(this, props);
|
|
12852
|
+
}
|
|
12853
|
+
*/
|
|
12854
|
+
|
|
12855
|
+
function SlickVG(input = '', settings = {}) {
|
|
12856
|
+
settings.getObject = true;
|
|
12857
|
+
return svgPathSimplify(input, settings)
|
|
12858
|
+
}
|
|
12859
|
+
|
|
12494
12860
|
function svgPathSimplify(input = '', settings = {}) {
|
|
12495
12861
|
|
|
12496
12862
|
let preset = settings['preset'] !== undefined && settings['preset'] ? settings['preset'] : null;
|
|
@@ -12502,7 +12868,7 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
12502
12868
|
...settings
|
|
12503
12869
|
};
|
|
12504
12870
|
|
|
12505
|
-
let { getObject = false, removeComments, removeOffCanvas, unGroup, mergePaths, removeElements, removeDimensions, removeIds, removeClassNames, omitNamespace, cleanUpStrokes, addViewBox, addDimensions, removePrologue, removeHidden, removeUnused, cleanupDefs, cleanupClip, cleanupSVGAtts, removeNameSpaced, removeNameSpacedAtts, attributesToGroup, minifyRgbColors, stylesToAttributes, fixHref, legacyHref, allowMeta, allowDataAtts, allowAriaAtts, removeSVGAttributes, removeElAttributes, shapesToPaths, shapeConvert, convertShapes, simplifyBezier, optimizeOrder, autoClose, removeZeroLength, refineClosing, removeColinear, flatBezierToLinetos, revertToQuadratics, refineExtremes, simplifyCorners, fixDirections, keepExtremes, keepCorners, keepInflections, addExtremes, reversePath, toAbsolute, toRelative, toMixed, toShorthands, toLonghands, quadraticToCubic, arcToCubic, cubicToArc, lineToCubic, decimals, autoAccuracy, minifyD, tolerance, toPolygon, smoothPoly, polyFormat, precisionPoly, simplifyRD, simplifyRDP, harmonizeCpts, removeOrphanSubpaths, simplifyRound, simplifyQuadraticCorners, scale, scaleTo, crop, alignToOrigin, convertTransforms, keepSmaller, splitCompound, convertPathLength, toAbsoluteUnits } = settings;
|
|
12871
|
+
let { getObject = false, removeComments, removeOffCanvas, unGroup, mergePaths, removeElements, removeDimensions, removeIds, removeClassNames, omitNamespace, cleanUpStrokes, addViewBox, addDimensions, removePrologue, removeHidden, removeUnused, cleanupDefs, cleanupClip, cleanupSVGAtts, removeNameSpaced, removeNameSpacedAtts, attributesToGroup, minifyRgbColors, stylesToAttributes, fixHref, legacyHref, allowMeta, allowDataAtts, allowAriaAtts, removeSVGAttributes, removeElAttributes, shapesToPaths, shapeConvert, convertShapes, simplifyBezier, optimizeOrder, autoClose, removeZeroLength, refineClosing, removeColinear, flatBezierToLinetos, revertToQuadratics, refineExtremes, simplifyCorners, fixDirections, keepExtremes, keepCorners, keepInflections, addExtremes, reversePath, toAbsolute, toRelative, toMixed, toShorthands, toLonghands, quadraticToCubic, arcToCubic, cubicToArc, lineToCubic, decimals, autoAccuracy, minifyD, tolerance, toPolygon, smoothPoly, polyFormat, isClosed, precisionPoly, simplifyRD, simplifyRDP, harmonizeCpts, removeOrphanSubpaths, simplifyRound, simplifyQuadraticCorners, scale, scaleTo, crop, alignToOrigin, convertTransforms, keepSmaller, splitCompound, convertPathLength, toAbsoluteUnits } = settings;
|
|
12506
12872
|
|
|
12507
12873
|
// clamp tolerance and scale
|
|
12508
12874
|
tolerance = Math.max(0.1, tolerance);
|
|
@@ -12513,6 +12879,10 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
12513
12879
|
settings.convertTransforms = true;
|
|
12514
12880
|
}
|
|
12515
12881
|
|
|
12882
|
+
if (shapeConvert === 'toShapes' || shapeConvert === 'shapesToPaths') {
|
|
12883
|
+
keepSmaller = false;
|
|
12884
|
+
}
|
|
12885
|
+
|
|
12516
12886
|
/**
|
|
12517
12887
|
* intercept
|
|
12518
12888
|
* invalid inputs
|
|
@@ -12529,11 +12899,11 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
12529
12899
|
original: 0,
|
|
12530
12900
|
new: 0,
|
|
12531
12901
|
saved: 0,
|
|
12532
|
-
svgSize:0,
|
|
12533
|
-
svgSizeOpt:0,
|
|
12534
|
-
compression:0,
|
|
12535
|
-
decimals:0,
|
|
12536
|
-
invalid:true
|
|
12902
|
+
svgSize: 0,
|
|
12903
|
+
svgSizeOpt: 0,
|
|
12904
|
+
compression: 0,
|
|
12905
|
+
decimals: 0,
|
|
12906
|
+
invalid: true
|
|
12537
12907
|
};
|
|
12538
12908
|
|
|
12539
12909
|
return { svg: dummySVG, d: '', polys: [], report, pathDataPlusArr: [], pathDataPlusArr_global: [], inputType: 'invalid', dOriginal: '' };
|
|
@@ -12690,6 +13060,8 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
12690
13060
|
toRelative,
|
|
12691
13061
|
toMixed,
|
|
12692
13062
|
toShorthands,
|
|
13063
|
+
// return true arc radii or minified/parametrized
|
|
13064
|
+
optimizeArcs: minifyD < 1,
|
|
12693
13065
|
decimals,
|
|
12694
13066
|
};
|
|
12695
13067
|
|
|
@@ -12701,7 +13073,12 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
12701
13073
|
let pathDataPlusArr = [];
|
|
12702
13074
|
let path = paths[i];
|
|
12703
13075
|
let { d, el } = path;
|
|
12704
|
-
|
|
13076
|
+
|
|
13077
|
+
// disable reordering for elements with stroke dash-array
|
|
13078
|
+
if (el && (el.hasAttribute('stroke-dasharray') || el.hasAttribute('stroke-dashoffset'))) {
|
|
13079
|
+
optimizeOrder = false;
|
|
13080
|
+
|
|
13081
|
+
}
|
|
12705
13082
|
|
|
12706
13083
|
// if polygon we already heave absolute coordinates
|
|
12707
13084
|
|
|
@@ -12742,7 +13119,7 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
12742
13119
|
// count commands for evaluation
|
|
12743
13120
|
comCount += pathData.length;
|
|
12744
13121
|
|
|
12745
|
-
if (
|
|
13122
|
+
if (removeOrphanSubpaths) pathData = removeOrphanedM(pathData);
|
|
12746
13123
|
|
|
12747
13124
|
/**
|
|
12748
13125
|
* get sub paths
|
|
@@ -12756,8 +13133,9 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
12756
13133
|
let pathDataSub = subPathArr[i];
|
|
12757
13134
|
let poly = [];
|
|
12758
13135
|
let coms = Array.from(new Set(pathDataSub.map(com => com.type))).join('');
|
|
12759
|
-
isPoly = !(/[acqts]/gi).test(coms);
|
|
12760
|
-
|
|
13136
|
+
let isPoly = !(/[acqts]/gi).test(coms);
|
|
13137
|
+
|
|
13138
|
+
let closed = (/z/gi).test(coms);
|
|
12761
13139
|
|
|
12762
13140
|
if (isPoly && !mode) {
|
|
12763
13141
|
|
|
@@ -12774,7 +13152,6 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
12774
13152
|
|
|
12775
13153
|
}
|
|
12776
13154
|
|
|
12777
|
-
toPolygon = false;
|
|
12778
13155
|
pathDataSub = pathDataFromPoly(poly, closed);
|
|
12779
13156
|
|
|
12780
13157
|
}
|
|
@@ -12783,26 +13160,56 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
12783
13160
|
* convert curves to polygon
|
|
12784
13161
|
* flattening
|
|
12785
13162
|
*/
|
|
12786
|
-
|
|
13163
|
+
|
|
13164
|
+
if (toPolygon) {
|
|
12787
13165
|
simplifyBezier = false;
|
|
12788
13166
|
smoothPoly = false;
|
|
12789
13167
|
harmonizeCpts = false;
|
|
12790
13168
|
|
|
12791
|
-
|
|
13169
|
+
/**
|
|
13170
|
+
* if pathdata is already polygon- pass through
|
|
13171
|
+
* otherwise create precise polygon by curve splitting
|
|
13172
|
+
* */
|
|
12792
13173
|
|
|
12793
|
-
|
|
12794
|
-
|
|
12795
|
-
|
|
13174
|
+
if (!isPoly) {
|
|
13175
|
+
pathDataSub = getPathDataVerbose(pathDataSub);
|
|
13176
|
+
let polyData = pathDataToPolygonOpt(pathDataSub, {
|
|
13177
|
+
precisionPoly,
|
|
13178
|
+
autoAccuracy,
|
|
13179
|
+
polyFormat,
|
|
12796
13180
|
|
|
12797
|
-
|
|
12798
|
-
|
|
12799
|
-
|
|
13181
|
+
simplifyRD,
|
|
13182
|
+
simplifyRDP
|
|
13183
|
+
});
|
|
13184
|
+
|
|
13185
|
+
poly = polyData.poly;
|
|
13186
|
+
pathDataSub = polyData.pathData;
|
|
13187
|
+
isPoly = true;
|
|
13188
|
+
|
|
13189
|
+
}
|
|
13190
|
+
|
|
13191
|
+
polys.push(poly);
|
|
13192
|
+
|
|
13193
|
+
}
|
|
13194
|
+
|
|
13195
|
+
// harmonize cpts
|
|
13196
|
+
// if (harmonizeCpts) pathDataSub = harmonizeCubicCpts(pathDataSub)
|
|
12800
13197
|
|
|
12801
|
-
|
|
12802
|
-
isPoly = true;
|
|
13198
|
+
if (smoothPoly) {
|
|
12803
13199
|
|
|
13200
|
+
removeZeroLength=true;
|
|
13201
|
+
optimizeOrder=true;
|
|
12804
13202
|
}
|
|
12805
13203
|
|
|
13204
|
+
// remove zero length linetos
|
|
13205
|
+
if (removeColinear || removeZeroLength) pathDataSub = removeZeroLengthLinetos(pathDataSub);
|
|
13206
|
+
|
|
13207
|
+
// sort to top left
|
|
13208
|
+
if (optimizeOrder) pathDataSub = pathDataToTopLeft(pathDataSub);
|
|
13209
|
+
|
|
13210
|
+
// Preprocessing: remove colinear - ignore flat beziers (removed later)
|
|
13211
|
+
if (removeColinear) pathDataSub = pathDataRemoveColinear(pathDataSub, { tolerance, flatBezierToLinetos: false });
|
|
13212
|
+
|
|
12806
13213
|
/**
|
|
12807
13214
|
* poly to beziers via
|
|
12808
13215
|
* Philip J. Schneider's
|
|
@@ -12811,12 +13218,18 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
12811
13218
|
if (smoothPoly) {
|
|
12812
13219
|
|
|
12813
13220
|
if (isPoly) {
|
|
12814
|
-
|
|
13221
|
+
/*
|
|
13222
|
+
pathDataSub = pathDataToTopLeft(pathDataSub)
|
|
13223
|
+
pathDataSub = removeZeroLengthLinetos(pathDataSub)
|
|
13224
|
+
pathDataSub = pathDataRemoveColinear(pathDataSub, { tolerance, flatBezierToLinetos: true });
|
|
13225
|
+
*/
|
|
13226
|
+
|
|
12815
13227
|
let poly = getPathDataVertices(pathDataSub);
|
|
12816
13228
|
|
|
12817
13229
|
// options for poly simplification
|
|
12818
13230
|
let optionsPoly = {
|
|
12819
|
-
|
|
13231
|
+
|
|
13232
|
+
denoise: 0,
|
|
12820
13233
|
tolerance,
|
|
12821
13234
|
width: bb_poly.width,
|
|
12822
13235
|
height: bb_poly.height,
|
|
@@ -12828,26 +13241,15 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
12828
13241
|
closed,
|
|
12829
13242
|
simplifyRD,
|
|
12830
13243
|
simplifyRDP,
|
|
13244
|
+
isClosed,
|
|
12831
13245
|
};
|
|
12832
13246
|
|
|
12833
13247
|
pathDataSub = simplifyPolygonToPathData(poly, optionsPoly);
|
|
12834
13248
|
// flag as non poly as we're smoothing to curves
|
|
12835
|
-
|
|
13249
|
+
isPoly = false;
|
|
12836
13250
|
}
|
|
12837
13251
|
}
|
|
12838
13252
|
|
|
12839
|
-
// harmonize cpts
|
|
12840
|
-
// if (harmonizeCpts) pathDataSub = harmonizeCubicCpts(pathDataSub)
|
|
12841
|
-
|
|
12842
|
-
// remove zero length linetos
|
|
12843
|
-
if (removeColinear || removeZeroLength) pathDataSub = removeZeroLengthLinetos(pathDataSub);
|
|
12844
|
-
|
|
12845
|
-
// sort to top left
|
|
12846
|
-
if (optimizeOrder) pathDataSub = pathDataToTopLeft(pathDataSub);
|
|
12847
|
-
|
|
12848
|
-
// Preprocessing: remove colinear - ignore flat beziers (removed later)
|
|
12849
|
-
if (removeColinear) pathDataSub = pathDataRemoveColinear(pathDataSub, { tolerance, flatBezierToLinetos: false });
|
|
12850
|
-
|
|
12851
13253
|
let tMin = 0, tMax = 1;
|
|
12852
13254
|
if (addExtremes) pathDataSub = addExtremePoints(pathDataSub,
|
|
12853
13255
|
{ tMin, tMax, addExtremes, angles: [30] });
|
|
@@ -12870,11 +13272,13 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
12870
13272
|
pathDataPlus.bb = getPolyBBox(getPathDataVertices(pathDataCubic));
|
|
12871
13273
|
}
|
|
12872
13274
|
pathDataPlus.dimA = pathDataPlus.bb.width + pathDataPlus.bb.height;
|
|
13275
|
+
|
|
12873
13276
|
pathDataPlus.pathData = getPathDataVerbose(pathDataSub, {
|
|
12874
13277
|
addSquareLength: false,
|
|
12875
13278
|
addArea: false,
|
|
12876
13279
|
addAverageDim: false
|
|
12877
13280
|
});
|
|
13281
|
+
|
|
12878
13282
|
}
|
|
12879
13283
|
|
|
12880
13284
|
// simplify beziers
|
|
@@ -12885,6 +13289,12 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
12885
13289
|
|
|
12886
13290
|
if (refineClosing) pathData = refineClosingCommand(pathData, { threshold: dimA * 0.001 });
|
|
12887
13291
|
|
|
13292
|
+
// refine round segment sequences
|
|
13293
|
+
if (simplifyRound) {
|
|
13294
|
+
pathData = refineRoundSegments(pathData);
|
|
13295
|
+
pathData = simplifyAdjacentRound(pathData);
|
|
13296
|
+
}
|
|
13297
|
+
|
|
12888
13298
|
pathData = simplifyBezier ? simplifyPathDataCubic(pathData, { simplifyBezier, keepInflections, keepExtremes, keepCorners, revertToQuadratics, tolerance }) : pathData;
|
|
12889
13299
|
|
|
12890
13300
|
// refine extremes
|
|
@@ -12908,12 +13318,6 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
12908
13318
|
pathData = refineRoundedCorners(pathData, { threshold, tolerance, simplifyQuadraticCorners });
|
|
12909
13319
|
}
|
|
12910
13320
|
|
|
12911
|
-
// refine round segment sequences
|
|
12912
|
-
if (simplifyRound) {
|
|
12913
|
-
pathData = refineRoundSegments(pathData);
|
|
12914
|
-
pathData = simplifyAdjacentRound(pathData);
|
|
12915
|
-
}
|
|
12916
|
-
|
|
12917
13321
|
// simplify to quadratics
|
|
12918
13322
|
if (revertToQuadratics) pathData = pathDataRevertCubicToQuadratic(pathData, tolerance);
|
|
12919
13323
|
|
|
@@ -12930,6 +13334,8 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
12930
13334
|
|
|
12931
13335
|
}
|
|
12932
13336
|
|
|
13337
|
+
// offset path
|
|
13338
|
+
|
|
12933
13339
|
// update
|
|
12934
13340
|
pathDataPlusArr.push({ pathData, bb });
|
|
12935
13341
|
|
|
@@ -12989,17 +13395,18 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
12989
13395
|
}
|
|
12990
13396
|
|
|
12991
13397
|
// add simplified poly - if not populated by toPoly conversion
|
|
13398
|
+
/*
|
|
12992
13399
|
if (isPoly) {
|
|
12993
13400
|
|
|
12994
13401
|
pathDataPlusArr.forEach(sub => {
|
|
12995
|
-
let poly = getPathDataVertices(sub.pathData, false, decimals)
|
|
13402
|
+
let poly = getPathDataVertices(sub.pathData, false, decimals)
|
|
12996
13403
|
if (polyFormat === 'array') {
|
|
12997
|
-
poly = polyPtsToArray(poly)
|
|
13404
|
+
poly = polyPtsToArray(poly)
|
|
12998
13405
|
}
|
|
12999
|
-
polys.push(poly)
|
|
13000
|
-
})
|
|
13001
|
-
|
|
13406
|
+
polys.push(poly)
|
|
13407
|
+
})
|
|
13002
13408
|
}
|
|
13409
|
+
*/
|
|
13003
13410
|
|
|
13004
13411
|
// split into sub paths - returns svg with multiple paths
|
|
13005
13412
|
if (splitCompound && !mode && pathDataPlusArr.length > 1) {
|
|
@@ -13098,7 +13505,6 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
13098
13505
|
svg = stringifySVG(svg, { omitNamespace, removeComments, format: minifyD });
|
|
13099
13506
|
|
|
13100
13507
|
svgSizeOpt = svg.length;
|
|
13101
|
-
|
|
13102
13508
|
compression = +(100 / svgSize * (svgSizeOpt)).toFixed(2);
|
|
13103
13509
|
|
|
13104
13510
|
svgSize = +(svgSize / 1024).toFixed(3);
|
|
@@ -13124,15 +13530,52 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
13124
13530
|
({ d, report } = paths[0]);
|
|
13125
13531
|
}
|
|
13126
13532
|
|
|
13127
|
-
|
|
13128
|
-
|
|
13129
|
-
|
|
13533
|
+
// sanitize poly output
|
|
13534
|
+
if (polys.length) {
|
|
13535
|
+
|
|
13536
|
+
// round point data
|
|
13537
|
+
polys.forEach((poly, i) => {
|
|
13538
|
+
|
|
13539
|
+
if (polyFormat === 'string') poly = normalizePoly(poly);
|
|
13540
|
+
|
|
13541
|
+
poly = roundPoly(poly, decimals);
|
|
13542
|
+
// remove coinciding points
|
|
13543
|
+
polys[i] = removeCoincidingVertices(poly);
|
|
13544
|
+
});
|
|
13545
|
+
|
|
13546
|
+
if (polys.length === 1) {
|
|
13547
|
+
polys = polys[0];
|
|
13548
|
+
}
|
|
13549
|
+
|
|
13550
|
+
if (polyFormat === 'string') {
|
|
13551
|
+
|
|
13552
|
+
polys = normalizePoly(polys, { toArray: true, flatten: true });
|
|
13553
|
+
|
|
13554
|
+
polys = polys.flat().join(' ');
|
|
13555
|
+
}
|
|
13130
13556
|
|
|
13131
|
-
if (polyFormat === 'string' && polys.length) {
|
|
13132
|
-
polys = polys.flat().map(pt => `${pt.x},${pt.y}`).join(' ');
|
|
13133
13557
|
}
|
|
13134
13558
|
|
|
13135
|
-
|
|
13559
|
+
// create object
|
|
13560
|
+
let svgObj = new SlickVGObj({ svg, d, polys, report, pathDataPlusArr: pathDataPlusArr_global, inputType, dOriginal });
|
|
13561
|
+
|
|
13562
|
+
/*
|
|
13563
|
+
let d2 = svgObj.getD()
|
|
13564
|
+
console.log('d2', d2);
|
|
13565
|
+
|
|
13566
|
+
let svg2 = svgObj.getSvg()
|
|
13567
|
+
console.log('svg2', svg2);
|
|
13568
|
+
|
|
13569
|
+
let format = 'd'
|
|
13570
|
+
format = 'pathData'
|
|
13571
|
+
format = 'd'
|
|
13572
|
+
format = 'points'
|
|
13573
|
+
format = 'array'
|
|
13574
|
+
let poly2 = svgObj.getPoly({ format })
|
|
13575
|
+
console.log('poly2', 'format', format, poly2);
|
|
13576
|
+
*/
|
|
13577
|
+
|
|
13578
|
+
return !getObject ? (d ? d : svg) : svgObj;
|
|
13136
13579
|
|
|
13137
13580
|
}
|
|
13138
13581
|
|
|
@@ -13141,6 +13584,7 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
13141
13584
|
// IIFE
|
|
13142
13585
|
if (typeof window !== 'undefined') {
|
|
13143
13586
|
window.svgPathSimplify = svgPathSimplify;
|
|
13587
|
+
window.SlickVG = SlickVG;
|
|
13144
13588
|
window.getElementTransform = getElementTransform;
|
|
13145
13589
|
window.validateSVG = validateSVG;
|
|
13146
13590
|
window.detectInputType = detectInputType;
|
|
@@ -13149,4 +13593,4 @@ if (typeof window !== 'undefined') {
|
|
|
13149
13593
|
|
|
13150
13594
|
}
|
|
13151
13595
|
|
|
13152
|
-
export { PI$1 as PI, abs$1 as abs, acos$1 as acos, asin$1 as asin, atan$1 as atan, atan2$1 as atan2, ceil$1 as ceil, cos$1 as cos, detectInputType, exp$1 as exp, floor$1 as floor, getElementTransform, getViewBox, hypot, log$1 as log, max$1 as max, min$1 as min, pow$1 as pow, random$1 as random, round$1 as round, sin$1 as sin, sqrt$1 as sqrt, svgPathSimplify, tan$1 as tan, validateSVG };
|
|
13596
|
+
export { PI$1 as PI, SlickVG, abs$1 as abs, acos$1 as acos, asin$1 as asin, atan$1 as atan, atan2$1 as atan2, ceil$1 as ceil, cos$1 as cos, detectInputType, exp$1 as exp, floor$1 as floor, getElementTransform, getViewBox, hypot, log$1 as log, max$1 as max, min$1 as min, pow$1 as pow, random$1 as random, round$1 as round, sin$1 as sin, sqrt$1 as sqrt, svgPathSimplify, tan$1 as tan, validateSVG };
|