svg-path-simplify 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -5
- package/dist/svg-path-simplify.esm.js +1250 -562
- package/dist/svg-path-simplify.esm.min.js +1 -1
- package/dist/svg-path-simplify.js +4756 -4068
- package/dist/svg-path-simplify.min.js +1 -1
- package/dist/svg-path-simplify.node.js +1250 -562
- package/dist/svg-path-simplify.node.min.js +1 -1
- package/index.html +89 -29
- package/package.json +5 -3
- package/src/detect_input.js +17 -10
- package/src/dom-polyfill.js +29 -0
- package/src/dom-polyfill_back.js +22 -0
- package/src/index.js +10 -1
- package/src/pathData_simplify_cubic.js +114 -143
- package/src/pathData_simplify_cubic_extrapolate.js +64 -35
- package/src/pathSimplify-main.js +113 -165
- package/src/svgii/geometry.js +8 -155
- package/src/svgii/geometry_flatness.js +94 -0
- package/src/svgii/pathData_analyze.js +15 -596
- package/src/svgii/pathData_convert.js +26 -17
- package/src/svgii/pathData_interpolate.js +65 -0
- package/src/svgii/pathData_parse.js +25 -9
- package/src/svgii/pathData_parse_els.js +245 -0
- package/src/svgii/pathData_remove_collinear.js +33 -28
- package/src/svgii/pathData_remove_orphaned.js +21 -0
- package/src/svgii/pathData_remove_zerolength.js +17 -3
- package/src/svgii/pathData_reorder.js +9 -3
- package/src/svgii/pathData_simplify_refineCorners.js +160 -0
- package/src/svgii/pathData_simplify_refineExtremes.js +208 -0
- package/src/svgii/pathData_split.js +43 -15
- package/src/svgii/pathData_stringify.js +3 -12
- package/src/svgii/rounding.js +35 -27
- package/src/svgii/svg_cleanup.js +4 -1
- package/testSVG.js +39 -0
- package/src/pathData_simplify_cubic_arr.js +0 -50
- package/src/svgii/simplify.js +0 -248
- package/src/svgii/simplify_bezier.js +0 -470
- package/src/svgii/simplify_linetos.js +0 -93
|
@@ -1,3 +1,42 @@
|
|
|
1
|
+
function renderPoint(
|
|
2
|
+
svg,
|
|
3
|
+
coords,
|
|
4
|
+
fill = "red",
|
|
5
|
+
r = "1%",
|
|
6
|
+
opacity = "1",
|
|
7
|
+
title = '',
|
|
8
|
+
render = true,
|
|
9
|
+
id = "",
|
|
10
|
+
className = ""
|
|
11
|
+
) {
|
|
12
|
+
if (Array.isArray(coords)) {
|
|
13
|
+
coords = {
|
|
14
|
+
x: coords[0],
|
|
15
|
+
y: coords[1]
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
let marker = `<circle class="${className}" opacity="${opacity}" id="${id}" cx="${coords.x}" cy="${coords.y}" r="${r}" fill="${fill}">
|
|
19
|
+
<title>${title}</title></circle>`;
|
|
20
|
+
|
|
21
|
+
if (render) {
|
|
22
|
+
svg.insertAdjacentHTML("beforeend", marker);
|
|
23
|
+
} else {
|
|
24
|
+
return marker;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function renderPath(svg, d = '', stroke = 'green', strokeWidth = '1%', render = true) {
|
|
29
|
+
|
|
30
|
+
let path = `<path d="${d}" fill="none" stroke="${stroke}" stroke-width="${strokeWidth}" /> `;
|
|
31
|
+
|
|
32
|
+
if (render) {
|
|
33
|
+
svg.insertAdjacentHTML("beforeend", path);
|
|
34
|
+
} else {
|
|
35
|
+
return path;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
|
|
1
40
|
function detectInputType(input) {
|
|
2
41
|
let type = 'string';
|
|
3
42
|
/*
|
|
@@ -8,23 +47,29 @@ function detectInputType(input) {
|
|
|
8
47
|
if (input instanceof ArrayBuffer) return "buffer";
|
|
9
48
|
if (input instanceof Blob) return "blob";
|
|
10
49
|
*/
|
|
11
|
-
if (Array.isArray(input))
|
|
50
|
+
if (Array.isArray(input)) {
|
|
51
|
+
if (input[0]?.type && input[0]?.values
|
|
52
|
+
) {
|
|
53
|
+
return "pathData";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return "array";
|
|
57
|
+
}
|
|
12
58
|
|
|
13
59
|
if (typeof input === "string") {
|
|
14
60
|
input = input.trim();
|
|
15
61
|
let isSVG = input.includes('<svg') && input.includes('</svg');
|
|
16
62
|
let isPathData = input.startsWith('M') || input.startsWith('m');
|
|
17
|
-
let isPolyString = !isNaN(input.substring(0, 1)) && !isNaN(input.substring(input.length-1, input.length));
|
|
63
|
+
let isPolyString = !isNaN(input.substring(0, 1)) && !isNaN(input.substring(input.length - 1, input.length));
|
|
18
64
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
type='svgMarkup';
|
|
65
|
+
if (isSVG) {
|
|
66
|
+
type = 'svgMarkup';
|
|
22
67
|
}
|
|
23
|
-
else if(isPathData) {
|
|
24
|
-
type='pathDataString';
|
|
68
|
+
else if (isPathData) {
|
|
69
|
+
type = 'pathDataString';
|
|
25
70
|
}
|
|
26
|
-
else if(isPolyString) {
|
|
27
|
-
type='polyString';
|
|
71
|
+
else if (isPolyString) {
|
|
72
|
+
type = 'polyString';
|
|
28
73
|
}
|
|
29
74
|
|
|
30
75
|
else {
|
|
@@ -65,19 +110,24 @@ function getAngle(p1, p2, normalize = false) {
|
|
|
65
110
|
* http://jsfiddle.net/justin_c_rounds/Gd2S2/light/
|
|
66
111
|
*/
|
|
67
112
|
|
|
68
|
-
function checkLineIntersection(p1, p2, p3, p4, exact = true) {
|
|
113
|
+
function checkLineIntersection(p1=null, p2=null, p3=null, p4=null, exact = true, debug=false) {
|
|
69
114
|
// if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite) and booleans for whether line segment 1 or line segment 2 contain the point
|
|
70
115
|
let denominator, a, b, numerator1, numerator2;
|
|
71
116
|
let intersectionPoint = {};
|
|
72
117
|
|
|
118
|
+
if(!p1 || !p2 || !p3 || !p4){
|
|
119
|
+
if(debug) console.warn('points missing');
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
122
|
+
|
|
73
123
|
try {
|
|
74
124
|
denominator = ((p4.y - p3.y) * (p2.x - p1.x)) - ((p4.x - p3.x) * (p2.y - p1.y));
|
|
75
125
|
if (denominator == 0) {
|
|
76
126
|
return false;
|
|
77
127
|
}
|
|
78
|
-
|
|
79
128
|
} catch {
|
|
80
|
-
console.
|
|
129
|
+
if(debug) console.warn('!catch', p1, p2, 'p3:', p3, 'p4:', p4);
|
|
130
|
+
return false
|
|
81
131
|
}
|
|
82
132
|
|
|
83
133
|
a = p1.y - p3.y;
|
|
@@ -94,8 +144,6 @@ function checkLineIntersection(p1, p2, p3, p4, exact = true) {
|
|
|
94
144
|
y: p1.y + (a * (p2.y - p1.y))
|
|
95
145
|
};
|
|
96
146
|
|
|
97
|
-
// console.log('intersectionPoint', intersectionPoint, p1, p2);
|
|
98
|
-
|
|
99
147
|
let intersection = false;
|
|
100
148
|
// if line1 is a segment and line2 is infinite, they intersect if:
|
|
101
149
|
if ((a > 0 && a < 1) && (b > 0 && b < 1)) {
|
|
@@ -495,6 +543,110 @@ function getBezierExtremeT(pts) {
|
|
|
495
543
|
return tArr;
|
|
496
544
|
}
|
|
497
545
|
|
|
546
|
+
/**
|
|
547
|
+
* based on Nikos M.'s answer
|
|
548
|
+
* how-do-you-calculate-the-axis-aligned-bounding-box-of-an-ellipse
|
|
549
|
+
* https://stackoverflow.com/questions/87734/#75031511
|
|
550
|
+
* See also: https://github.com/foo123/Geometrize
|
|
551
|
+
*/
|
|
552
|
+
function getArcExtemes(p0, values) {
|
|
553
|
+
// compute point on ellipse from angle around ellipse (theta)
|
|
554
|
+
const arc = (theta, cx, cy, rx, ry, alpha) => {
|
|
555
|
+
// theta is angle in radians around arc
|
|
556
|
+
// alpha is angle of rotation of ellipse in radians
|
|
557
|
+
var cos = Math.cos(alpha),
|
|
558
|
+
sin = Math.sin(alpha),
|
|
559
|
+
x = rx * Math.cos(theta),
|
|
560
|
+
y = ry * Math.sin(theta);
|
|
561
|
+
|
|
562
|
+
return {
|
|
563
|
+
x: cx + cos * x - sin * y,
|
|
564
|
+
y: cy + sin * x + cos * y
|
|
565
|
+
};
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
let arcData = svgArcToCenterParam(p0.x, p0.y, values[0], values[1], values[2], values[3], values[4], values[5], values[6]);
|
|
569
|
+
let { rx, ry, cx, cy, endAngle, deltaAngle } = arcData;
|
|
570
|
+
|
|
571
|
+
// arc rotation
|
|
572
|
+
let deg = values[2];
|
|
573
|
+
|
|
574
|
+
// final on path point
|
|
575
|
+
let p = { x: values[5], y: values[6] };
|
|
576
|
+
|
|
577
|
+
// collect extreme points – add end point
|
|
578
|
+
let extremes = [p];
|
|
579
|
+
|
|
580
|
+
// rotation to radians
|
|
581
|
+
let alpha = deg * Math.PI / 180;
|
|
582
|
+
let tan = Math.tan(alpha),
|
|
583
|
+
p1, p2, p3, p4, theta;
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* find min/max from zeroes of directional derivative along x and y
|
|
587
|
+
* along x axis
|
|
588
|
+
*/
|
|
589
|
+
theta = Math.atan2(-ry * tan, rx);
|
|
590
|
+
|
|
591
|
+
let angle1 = theta;
|
|
592
|
+
let angle2 = theta + Math.PI;
|
|
593
|
+
let angle3 = Math.atan2(ry, rx * tan);
|
|
594
|
+
let angle4 = angle3 + Math.PI;
|
|
595
|
+
|
|
596
|
+
// inner bounding box
|
|
597
|
+
let xArr = [p0.x, p.x];
|
|
598
|
+
let yArr = [p0.y, p.y];
|
|
599
|
+
let xMin = Math.min(...xArr);
|
|
600
|
+
let xMax = Math.max(...xArr);
|
|
601
|
+
let yMin = Math.min(...yArr);
|
|
602
|
+
let yMax = Math.max(...yArr);
|
|
603
|
+
|
|
604
|
+
// on path point close after start
|
|
605
|
+
let angleAfterStart = endAngle - deltaAngle * 0.001;
|
|
606
|
+
let pP2 = arc(angleAfterStart, cx, cy, rx, ry, alpha);
|
|
607
|
+
|
|
608
|
+
// on path point close before end
|
|
609
|
+
let angleBeforeEnd = endAngle - deltaAngle * 0.999;
|
|
610
|
+
let pP3 = arc(angleBeforeEnd, cx, cy, rx, ry, alpha);
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* expected extremes
|
|
614
|
+
* if leaving inner bounding box
|
|
615
|
+
* (between segment start and end point)
|
|
616
|
+
* otherwise exclude elliptic extreme points
|
|
617
|
+
*/
|
|
618
|
+
|
|
619
|
+
// right
|
|
620
|
+
if (pP2.x > xMax || pP3.x > xMax) {
|
|
621
|
+
// get point for this theta
|
|
622
|
+
p1 = arc(angle1, cx, cy, rx, ry, alpha);
|
|
623
|
+
extremes.push(p1);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// left
|
|
627
|
+
if (pP2.x < xMin || pP3.x < xMin) {
|
|
628
|
+
// get anti-symmetric point
|
|
629
|
+
p2 = arc(angle2, cx, cy, rx, ry, alpha);
|
|
630
|
+
extremes.push(p2);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// top
|
|
634
|
+
if (pP2.y < yMin || pP3.y < yMin) {
|
|
635
|
+
// get anti-symmetric point
|
|
636
|
+
p4 = arc(angle4, cx, cy, rx, ry, alpha);
|
|
637
|
+
extremes.push(p4);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// bottom
|
|
641
|
+
if (pP2.y > yMax || pP3.y > yMax) {
|
|
642
|
+
// get point for this theta
|
|
643
|
+
p3 = arc(angle3, cx, cy, rx, ry, alpha);
|
|
644
|
+
extremes.push(p3);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return extremes;
|
|
648
|
+
}
|
|
649
|
+
|
|
498
650
|
// cubic bezier.
|
|
499
651
|
function cubicBezierExtremeT(p0, cp1, cp2, p) {
|
|
500
652
|
let [x0, y0, x1, y1, x2, y2, x3, y3] = [p0.x, p0.y, cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y];
|
|
@@ -603,90 +755,6 @@ function quadraticBezierExtremeT(p0, cp1, p) {
|
|
|
603
755
|
return extemeT
|
|
604
756
|
}
|
|
605
757
|
|
|
606
|
-
function commandIsFlat(points, tolerance = 0.025) {
|
|
607
|
-
|
|
608
|
-
let p0 = points[0];
|
|
609
|
-
let p = points[points.length - 1];
|
|
610
|
-
|
|
611
|
-
let xArr = points.map(pt => { return pt.x });
|
|
612
|
-
let yArr = points.map(pt => { return pt.y });
|
|
613
|
-
|
|
614
|
-
let xMin = Math.min(...xArr);
|
|
615
|
-
let xMax = Math.max(...xArr);
|
|
616
|
-
let yMin = Math.min(...yArr);
|
|
617
|
-
let yMax = Math.max(...yArr);
|
|
618
|
-
let w = xMax - xMin;
|
|
619
|
-
let h = yMax - yMin;
|
|
620
|
-
|
|
621
|
-
if (points.length < 3 || (w === 0 || h === 0)) {
|
|
622
|
-
return { area: 0, flat: true, thresh: 0.0001, ratio: 0 };
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
let squareDist = getSquareDistance(p0, p);
|
|
626
|
-
let squareDist1 = getSquareDistance(p0, points[0]);
|
|
627
|
-
let squareDist2 = points.length > 3 ? getSquareDistance(p, points[1]) : squareDist1;
|
|
628
|
-
let squareDistAvg = (squareDist1 + squareDist2) / 2;
|
|
629
|
-
|
|
630
|
-
tolerance = 0.5;
|
|
631
|
-
let thresh = (w + h) * 0.5 * tolerance;
|
|
632
|
-
|
|
633
|
-
let area = 0;
|
|
634
|
-
for (let i = 0, l = points.length; i < l; i++) {
|
|
635
|
-
let addX = points[i].x;
|
|
636
|
-
let addY = points[i === points.length - 1 ? 0 : i + 1].y;
|
|
637
|
-
let subX = points[i === points.length - 1 ? 0 : i + 1].x;
|
|
638
|
-
let subY = points[i].y;
|
|
639
|
-
area += addX * addY * 0.5 - subX * subY * 0.5;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
area = +Math.abs(area).toFixed(9);
|
|
643
|
-
let areaThresh = 1000;
|
|
644
|
-
|
|
645
|
-
let ratio = area / (squareDistAvg);
|
|
646
|
-
|
|
647
|
-
let isFlat = area === 0 ? true : area < squareDistAvg / areaThresh;
|
|
648
|
-
|
|
649
|
-
return { area: area, flat: isFlat, thresh: thresh, ratio: ratio, squareDist: squareDist, areaThresh: squareDist / areaThresh };
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
function checkBezierFlatness(p0, cpts, p) {
|
|
653
|
-
|
|
654
|
-
let isFlat = false;
|
|
655
|
-
|
|
656
|
-
let isCubic = cpts.length===2;
|
|
657
|
-
|
|
658
|
-
let cp1 = cpts[0];
|
|
659
|
-
let cp2 = isCubic ? cpts[1] : cp1;
|
|
660
|
-
|
|
661
|
-
if(p0.x===cp1.x && p0.y===cp1.y && p.x===cp2.x && p.y===cp2.y) return true;
|
|
662
|
-
|
|
663
|
-
let dx1 = cp1.x - p0.x;
|
|
664
|
-
let dy1 = cp1.y - p0.y;
|
|
665
|
-
|
|
666
|
-
let dx2 = p.x - cp2.x;
|
|
667
|
-
let dy2 = p.y - cp2.y;
|
|
668
|
-
|
|
669
|
-
let cross1 = Math.abs(dx1 * dy2 - dy1 * dx2);
|
|
670
|
-
|
|
671
|
-
if(!cross1) return true
|
|
672
|
-
|
|
673
|
-
let dx0 = p.x - p0.x;
|
|
674
|
-
let dy0 = p.y - p0.y;
|
|
675
|
-
let cross0 = Math.abs(dx0 * dy1 - dy0 * dx1);
|
|
676
|
-
|
|
677
|
-
if(!cross0) return true
|
|
678
|
-
|
|
679
|
-
let rat = (cross0/cross1);
|
|
680
|
-
|
|
681
|
-
if (rat<1.1 ) {
|
|
682
|
-
|
|
683
|
-
isFlat = true;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
return isFlat;
|
|
687
|
-
|
|
688
|
-
}
|
|
689
|
-
|
|
690
758
|
/**
|
|
691
759
|
* sloppy distance calculation
|
|
692
760
|
* based on x/y differences
|
|
@@ -710,27 +778,23 @@ function getDistAv(pt1, pt2) {
|
|
|
710
778
|
* split compound paths into
|
|
711
779
|
* sub path data array
|
|
712
780
|
*/
|
|
713
|
-
function splitSubpaths(pathData) {
|
|
714
781
|
|
|
782
|
+
function splitSubpaths(pathData) {
|
|
715
783
|
let subPathArr = [];
|
|
784
|
+
let current = [pathData[0]];
|
|
785
|
+
let l = pathData.length;
|
|
716
786
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
let subPathIndices = pathData.map((com, i) => (com.type.toLowerCase() === 'm' ? i : -1)).filter(i => i !== -1);
|
|
787
|
+
for (let i = 1; i < l; i++) {
|
|
788
|
+
let com = pathData[i];
|
|
720
789
|
|
|
721
|
-
|
|
722
|
-
|
|
790
|
+
if (com.type === 'M' || com.type === 'm') {
|
|
791
|
+
subPathArr.push(current);
|
|
792
|
+
current = [];
|
|
793
|
+
}
|
|
794
|
+
current.push(com);
|
|
723
795
|
}
|
|
724
796
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
// no compound path
|
|
728
|
-
if (subPathIndices.length === 1) {
|
|
729
|
-
return [pathData]
|
|
730
|
-
}
|
|
731
|
-
subPathIndices.forEach((index, i) => {
|
|
732
|
-
subPathArr.push(pathData.slice(index, subPathIndices[i + 1]));
|
|
733
|
-
});
|
|
797
|
+
if (current.length) subPathArr.push(current);
|
|
734
798
|
|
|
735
799
|
return subPathArr;
|
|
736
800
|
}
|
|
@@ -1154,6 +1218,73 @@ function getPathDataPoly(pathData) {
|
|
|
1154
1218
|
return poly;
|
|
1155
1219
|
}
|
|
1156
1220
|
|
|
1221
|
+
/**
|
|
1222
|
+
* get exact path BBox
|
|
1223
|
+
* calculating extremes for all command types
|
|
1224
|
+
*/
|
|
1225
|
+
|
|
1226
|
+
function getPathDataBBox(pathData) {
|
|
1227
|
+
|
|
1228
|
+
// save extreme values
|
|
1229
|
+
let xMin = Infinity;
|
|
1230
|
+
let xMax = -Infinity;
|
|
1231
|
+
let yMin = Infinity;
|
|
1232
|
+
let yMax = -Infinity;
|
|
1233
|
+
|
|
1234
|
+
const setXYmaxMin = (pt) => {
|
|
1235
|
+
if (pt.x < xMin) {
|
|
1236
|
+
xMin = pt.x;
|
|
1237
|
+
}
|
|
1238
|
+
if (pt.x > xMax) {
|
|
1239
|
+
xMax = pt.x;
|
|
1240
|
+
}
|
|
1241
|
+
if (pt.y < yMin) {
|
|
1242
|
+
yMin = pt.y;
|
|
1243
|
+
}
|
|
1244
|
+
if (pt.y > yMax) {
|
|
1245
|
+
yMax = pt.y;
|
|
1246
|
+
}
|
|
1247
|
+
};
|
|
1248
|
+
|
|
1249
|
+
for (let i = 0; i < pathData.length; i++) {
|
|
1250
|
+
let com = pathData[i];
|
|
1251
|
+
let { type, values } = com;
|
|
1252
|
+
let valuesL = values.length;
|
|
1253
|
+
let comPrev = pathData[i - 1] ? pathData[i - 1] : pathData[i];
|
|
1254
|
+
let valuesPrev = comPrev.values;
|
|
1255
|
+
let valuesPrevL = valuesPrev.length;
|
|
1256
|
+
|
|
1257
|
+
if (valuesL) {
|
|
1258
|
+
let p0 = { x: valuesPrev[valuesPrevL - 2], y: valuesPrev[valuesPrevL - 1] };
|
|
1259
|
+
let p = { x: values[valuesL - 2], y: values[valuesL - 1] };
|
|
1260
|
+
// add final on path point
|
|
1261
|
+
setXYmaxMin(p);
|
|
1262
|
+
|
|
1263
|
+
if (type === 'C' || type === 'Q') {
|
|
1264
|
+
let cp1 = { x: values[0], y: values[1] };
|
|
1265
|
+
let cp2 = type === 'C' ? { x: values[2], y: values[3] } : cp1;
|
|
1266
|
+
let pts = type === 'C' ? [p0, cp1, cp2, p] : [p0, cp1, p];
|
|
1267
|
+
|
|
1268
|
+
let bezierExtremesT = getBezierExtremeT(pts);
|
|
1269
|
+
bezierExtremesT.forEach(t => {
|
|
1270
|
+
let pt = pointAtT(pts, t);
|
|
1271
|
+
setXYmaxMin(pt);
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
else if (type === 'A') {
|
|
1276
|
+
let arcExtremes = getArcExtemes(p0, values);
|
|
1277
|
+
arcExtremes.forEach(pt => {
|
|
1278
|
+
setXYmaxMin(pt);
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
let bbox = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin };
|
|
1285
|
+
return bbox
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1157
1288
|
/**
|
|
1158
1289
|
* get pathdata area
|
|
1159
1290
|
*/
|
|
@@ -1347,13 +1478,6 @@ function pathDataToD(pathData, optimize = 0) {
|
|
|
1347
1478
|
let beautify = optimize > 1;
|
|
1348
1479
|
let minify = beautify || optimize ? false : true;
|
|
1349
1480
|
|
|
1350
|
-
// Convert first "M" to "m" if followed by "l" (when minified)
|
|
1351
|
-
/*
|
|
1352
|
-
if (pathData[1].type === "l" && minify) {
|
|
1353
|
-
pathData[0].type = "m";
|
|
1354
|
-
}
|
|
1355
|
-
*/
|
|
1356
|
-
|
|
1357
1481
|
let d = '';
|
|
1358
1482
|
let separator_command = beautify ? `\n` : (minify ? '' : ' ');
|
|
1359
1483
|
let separator_type = !minify ? ' ' : '';
|
|
@@ -1375,13 +1499,11 @@ function pathDataToD(pathData, optimize = 0) {
|
|
|
1375
1499
|
}
|
|
1376
1500
|
|
|
1377
1501
|
// Omit type for repeated commands
|
|
1378
|
-
type = (com0.type === com.type && com.type.toLowerCase() !== 'm'
|
|
1502
|
+
type = (minify && com0.type === com.type && com.type.toLowerCase() !== 'm' )
|
|
1379
1503
|
? " "
|
|
1380
|
-
: (
|
|
1381
|
-
(com0.type === "M" && com.type === "L")
|
|
1382
|
-
) && minify
|
|
1504
|
+
: (minify && com0.type === "M" && com.type === "L"
|
|
1383
1505
|
? " "
|
|
1384
|
-
: com.type;
|
|
1506
|
+
: com.type);
|
|
1385
1507
|
|
|
1386
1508
|
// concatenate subsequent floating point values
|
|
1387
1509
|
if (minify) {
|
|
@@ -1429,7 +1551,7 @@ function pathDataToD(pathData, optimize = 0) {
|
|
|
1429
1551
|
return d;
|
|
1430
1552
|
}
|
|
1431
1553
|
|
|
1432
|
-
function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1) {
|
|
1554
|
+
function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, debug = false) {
|
|
1433
1555
|
|
|
1434
1556
|
// cubic Bézier derivative
|
|
1435
1557
|
const cubicDerivative = (p0, p1, p2, p3, t) => {
|
|
@@ -1451,8 +1573,9 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1) {
|
|
|
1451
1573
|
let commands = [com1, com2];
|
|
1452
1574
|
|
|
1453
1575
|
// detect dominant
|
|
1454
|
-
let dist1 =
|
|
1455
|
-
let dist2 =
|
|
1576
|
+
let dist1 = getDistAv(com1.p0, com1.p);
|
|
1577
|
+
let dist2 = getDistAv(com2.p0, com2.p);
|
|
1578
|
+
|
|
1456
1579
|
let reverse = dist1 > dist2;
|
|
1457
1580
|
|
|
1458
1581
|
// backup original commands
|
|
@@ -1511,11 +1634,9 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1) {
|
|
|
1511
1634
|
let dP = cubicDerivative(com2.p0, com2.cp1, com2.cp2, com2.p, t0);
|
|
1512
1635
|
let r = sub(P, com1.p0);
|
|
1513
1636
|
|
|
1514
|
-
|
|
1515
1637
|
t0 -= dot(r, dP) / dot(dP, dP);
|
|
1516
1638
|
|
|
1517
1639
|
// construct merged cubic over [t0, 1]
|
|
1518
|
-
|
|
1519
1640
|
let Q0 = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], t0);
|
|
1520
1641
|
let Q3 = com2.p;
|
|
1521
1642
|
|
|
@@ -1532,6 +1653,7 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1) {
|
|
|
1532
1653
|
cp1: Q1,
|
|
1533
1654
|
cp2: Q2,
|
|
1534
1655
|
p: Q3,
|
|
1656
|
+
t0
|
|
1535
1657
|
};
|
|
1536
1658
|
|
|
1537
1659
|
if (reverse) {
|
|
@@ -1540,10 +1662,11 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1) {
|
|
|
1540
1662
|
cp1: Q2,
|
|
1541
1663
|
cp2: Q1,
|
|
1542
1664
|
p: Q0,
|
|
1665
|
+
t0
|
|
1543
1666
|
};
|
|
1544
1667
|
}
|
|
1545
1668
|
|
|
1546
|
-
let tMid = (1 - t0)*0.5
|
|
1669
|
+
let tMid = (1 - t0) * 0.5;
|
|
1547
1670
|
|
|
1548
1671
|
let ptM = pointAtT([result.p0, result.cp1, result.cp2, result.p], tMid, false, true);
|
|
1549
1672
|
let seg1_cp2 = ptM.cpts[2];
|
|
@@ -1551,21 +1674,22 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1) {
|
|
|
1551
1674
|
let ptI_1 = checkLineIntersection(ptM, seg1_cp2, result.p0, ptI, false);
|
|
1552
1675
|
let ptI_2 = checkLineIntersection(ptM, seg1_cp2, result.p, ptI, false);
|
|
1553
1676
|
|
|
1554
|
-
let cp1_2 = interpolate(result.p0, ptI_1, 1.333);
|
|
1555
|
-
let cp2_2 = interpolate(result.p, ptI_2, 1.333);
|
|
1677
|
+
let cp1_2 = interpolate(result.p0, ptI_1, 1.333 );
|
|
1678
|
+
let cp2_2 = interpolate(result.p, ptI_2, 1.333 );
|
|
1556
1679
|
|
|
1557
1680
|
// test self intersections and exit
|
|
1558
|
-
let cp_intersection = checkLineIntersection(com1_o.p0, cp1_2, com2_o.p, cp2_2, true
|
|
1559
|
-
if(cp_intersection){
|
|
1681
|
+
let cp_intersection = checkLineIntersection(com1_o.p0, cp1_2, com2_o.p, cp2_2, true);
|
|
1682
|
+
if (cp_intersection) {
|
|
1560
1683
|
|
|
1561
1684
|
return commands;
|
|
1562
1685
|
}
|
|
1563
1686
|
|
|
1687
|
+
if (debug) renderPoint(markers, ptM, 'purple');
|
|
1688
|
+
|
|
1564
1689
|
result.cp1 = cp1_2;
|
|
1565
1690
|
result.cp2 = cp2_2;
|
|
1566
1691
|
|
|
1567
|
-
// check distances
|
|
1568
|
-
|
|
1692
|
+
// check distances between original starting point and extrapolated
|
|
1569
1693
|
let dist3 = getDistAv(com1_o.p0, result.p0);
|
|
1570
1694
|
let dist4 = getDistAv(com2_o.p, result.p);
|
|
1571
1695
|
let dist5 = (dist3 + dist4);
|
|
@@ -1577,11 +1701,34 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1) {
|
|
|
1577
1701
|
result.corner = com2_o.corner;
|
|
1578
1702
|
result.dimA = com2_o.dimA;
|
|
1579
1703
|
result.directionChange = com2_o.directionChange;
|
|
1704
|
+
result.type = 'C';
|
|
1580
1705
|
result.values = [result.cp1.x, result.cp1.y, result.cp2.x, result.cp2.y, result.p.x, result.p.y];
|
|
1581
1706
|
|
|
1582
|
-
//
|
|
1707
|
+
// extrapolated starting point is not completely off
|
|
1583
1708
|
if (dist5 < maxDist) {
|
|
1584
1709
|
|
|
1710
|
+
/*
|
|
1711
|
+
let tTotal = 1 + Math.abs(t0);
|
|
1712
|
+
let tSplit = reverse ? 1 + t0 : Math.abs(t0);
|
|
1713
|
+
|
|
1714
|
+
let pO = pointAtT([com2_o.p0, com2_o.cp1, com2_o.cp2, com2_o.p], t0);
|
|
1715
|
+
*/
|
|
1716
|
+
|
|
1717
|
+
// split t to meet original mid segment start point
|
|
1718
|
+
let tSplit = reverse ? 1 + t0 : Math.abs(t0);
|
|
1719
|
+
|
|
1720
|
+
let tTotal = 1 + Math.abs(t0);
|
|
1721
|
+
tSplit = reverse ? 1 + t0 : Math.abs(t0) / tTotal;
|
|
1722
|
+
|
|
1723
|
+
let ptSplit = pointAtT([result.p0, result.cp1, result.cp2, result.p], tSplit);
|
|
1724
|
+
let distSplit = getDistAv(ptSplit, com1.p);
|
|
1725
|
+
|
|
1726
|
+
// not close enough - exit
|
|
1727
|
+
if (distSplit > maxDist * tolerance ) {
|
|
1728
|
+
|
|
1729
|
+
return commands;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1585
1732
|
// compare combined with original area
|
|
1586
1733
|
let pathData0 = [
|
|
1587
1734
|
{ type: 'M', values: [com1_o.p0.x, com1_o.p0.y] },
|
|
@@ -1598,129 +1745,198 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1) {
|
|
|
1598
1745
|
let areaN = getPathArea(pathDataN);
|
|
1599
1746
|
let areaDiff = Math.abs(areaN / area0 - 1);
|
|
1600
1747
|
|
|
1601
|
-
result.error = areaDiff *
|
|
1748
|
+
result.error = areaDiff * 5 * tolerance;
|
|
1602
1749
|
|
|
1603
|
-
|
|
1750
|
+
if (debug) {
|
|
1751
|
+
let d = pathDataToD(pathDataN);
|
|
1752
|
+
renderPath(markers, d, 'orange');
|
|
1753
|
+
}
|
|
1604
1754
|
|
|
1605
|
-
// success
|
|
1606
|
-
if (areaDiff < 0.
|
|
1755
|
+
// success!!!
|
|
1756
|
+
if (areaDiff < 0.05 * tolerance) {
|
|
1607
1757
|
commands = [result];
|
|
1608
1758
|
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1759
|
+
}
|
|
1611
1760
|
}
|
|
1612
1761
|
|
|
1613
1762
|
return commands
|
|
1614
1763
|
|
|
1615
1764
|
}
|
|
1616
1765
|
|
|
1617
|
-
function
|
|
1766
|
+
function simplifyPathDataCubic(pathData, {
|
|
1767
|
+
keepExtremes = true,
|
|
1768
|
+
keepInflections = true,
|
|
1769
|
+
keepCorners = true,
|
|
1770
|
+
extrapolateDominant = true,
|
|
1771
|
+
tolerance = 1,
|
|
1772
|
+
} = {}) {
|
|
1618
1773
|
|
|
1619
|
-
let
|
|
1620
|
-
let
|
|
1774
|
+
let pathDataN = [pathData[0]];
|
|
1775
|
+
let l = pathData.length;
|
|
1621
1776
|
|
|
1622
|
-
let
|
|
1623
|
-
|
|
1624
|
-
|
|
1777
|
+
for (let i = 2; l && i <= l; i++) {
|
|
1778
|
+
let com = pathData[i - 1];
|
|
1779
|
+
let comN = i < l ? pathData[i] : null;
|
|
1780
|
+
let typeN = comN?.type || null;
|
|
1625
1781
|
|
|
1626
|
-
|
|
1627
|
-
|
|
1782
|
+
let isDirChange = com?.directionChange || null;
|
|
1783
|
+
let isDirChangeN = comN?.directionChange || null;
|
|
1628
1784
|
|
|
1629
|
-
|
|
1785
|
+
let { type, values, p0, p, cp1 = null, cp2 = null, extreme = false, corner = false, dimA = 0 } = com;
|
|
1630
1786
|
|
|
1631
|
-
|
|
1632
|
-
|
|
1787
|
+
// next is also cubic
|
|
1788
|
+
if (type === 'C' && typeN === 'C') {
|
|
1633
1789
|
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1790
|
+
// cannot be combined as crossing extremes or corners
|
|
1791
|
+
if (
|
|
1792
|
+
(keepInflections && isDirChangeN) ||
|
|
1793
|
+
(keepCorners && corner) ||
|
|
1794
|
+
(!isDirChange && keepExtremes && extreme)
|
|
1795
|
+
) {
|
|
1638
1796
|
|
|
1639
|
-
|
|
1640
|
-
|
|
1797
|
+
pathDataN.push(com);
|
|
1798
|
+
}
|
|
1641
1799
|
|
|
1642
|
-
|
|
1643
|
-
|
|
1800
|
+
// try simplification
|
|
1801
|
+
else {
|
|
1644
1802
|
|
|
1645
|
-
|
|
1646
|
-
|
|
1803
|
+
let combined = combineCubicPairs(com, comN, {tolerance});
|
|
1804
|
+
let error = 0;
|
|
1647
1805
|
|
|
1648
|
-
|
|
1806
|
+
// combining successful! try next segment
|
|
1807
|
+
if (combined.length === 1) {
|
|
1808
|
+
com = combined[0];
|
|
1809
|
+
let offset = 1;
|
|
1649
1810
|
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
* to prevent distortions
|
|
1653
|
-
*/
|
|
1811
|
+
// add cumulative error to prevent distortions
|
|
1812
|
+
error += com.error;
|
|
1654
1813
|
|
|
1655
|
-
|
|
1656
|
-
let pt_2 = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], 0.5);
|
|
1814
|
+
// find next candidates
|
|
1657
1815
|
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1816
|
+
for (let n = i + 1; error < tolerance && n < l; n++) {
|
|
1817
|
+
let comN = pathData[n];
|
|
1818
|
+
if (comN.type !== 'C' ||
|
|
1819
|
+
(
|
|
1820
|
+
(keepInflections && comN.directionChange) ||
|
|
1821
|
+
(keepCorners && com.corner) ||
|
|
1822
|
+
(keepExtremes && com.extreme)
|
|
1823
|
+
)
|
|
1824
|
+
) {
|
|
1825
|
+
break
|
|
1826
|
+
}
|
|
1662
1827
|
|
|
1663
|
-
|
|
1828
|
+
let combined = combineCubicPairs(com, comN, {tolerance});
|
|
1829
|
+
if (combined.length === 1) {
|
|
1830
|
+
// add cumulative error to prevent distortions
|
|
1664
1831
|
|
|
1665
|
-
|
|
1832
|
+
error += combined[0].error * 0.5;
|
|
1666
1833
|
|
|
1667
|
-
|
|
1834
|
+
offset++;
|
|
1835
|
+
}
|
|
1836
|
+
com = combined[0];
|
|
1837
|
+
}
|
|
1668
1838
|
|
|
1669
|
-
|
|
1670
|
-
let pt_1 = pointAtT([com1.p0, com1.cp1, com1.cp2, com1.p], 0.5);
|
|
1839
|
+
pathDataN.push(com);
|
|
1671
1840
|
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1841
|
+
if (i < l) {
|
|
1842
|
+
i += offset;
|
|
1843
|
+
}
|
|
1675
1844
|
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
renderPoint(markers, ptS_1, 'orange', '0.5%')
|
|
1845
|
+
} else {
|
|
1846
|
+
pathDataN.push(com);
|
|
1847
|
+
}
|
|
1680
1848
|
}
|
|
1681
|
-
*/
|
|
1682
|
-
|
|
1683
|
-
// quit - paths not congruent
|
|
1684
|
-
if (dist1 + dist2 < maxDist) success = true;
|
|
1685
1849
|
|
|
1686
|
-
|
|
1687
|
-
error += dist2;
|
|
1850
|
+
} // end of bezier command
|
|
1688
1851
|
|
|
1852
|
+
// other commands
|
|
1853
|
+
else {
|
|
1854
|
+
pathDataN.push(com);
|
|
1689
1855
|
}
|
|
1690
1856
|
|
|
1691
|
-
} // end
|
|
1857
|
+
} // end command loop
|
|
1692
1858
|
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
if (extrapolateDominant && com2.extreme) {
|
|
1696
|
-
renderPoint(markers, com2.p)
|
|
1859
|
+
return pathDataN
|
|
1860
|
+
}
|
|
1697
1861
|
|
|
1698
|
-
|
|
1699
|
-
|
|
1862
|
+
function combineCubicPairs(com1, com2, {
|
|
1863
|
+
tolerance = 1
|
|
1864
|
+
} = {}) {
|
|
1865
|
+
|
|
1866
|
+
let commands = [com1, com2];
|
|
1867
|
+
|
|
1868
|
+
// assume 2 segments are result of a segment split
|
|
1869
|
+
let t = findSplitT(com1, com2);
|
|
1870
|
+
|
|
1871
|
+
let distAv1 = getDistAv(com1.p0, com1.p);
|
|
1872
|
+
let distAv2 = getDistAv(com2.p0, com2.p);
|
|
1873
|
+
let distMin = Math.max(0, Math.min(distAv1, distAv2));
|
|
1874
|
+
|
|
1875
|
+
let distScale = 0.06;
|
|
1876
|
+
let maxDist = distMin * distScale * tolerance;
|
|
1877
|
+
|
|
1878
|
+
// get hypothetical combined command
|
|
1879
|
+
let comS = getExtrapolatedCommand(com1, com2, t);
|
|
1880
|
+
|
|
1881
|
+
// test new point-at-t against original mid segment starting point
|
|
1882
|
+
let pt = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t);
|
|
1883
|
+
|
|
1884
|
+
let dist0 = getDistAv(com1.p, pt);
|
|
1885
|
+
let dist1 = 0, dist2 = 0;
|
|
1886
|
+
let close = dist0 < maxDist;
|
|
1887
|
+
let success = false;
|
|
1888
|
+
|
|
1889
|
+
// collect error data
|
|
1890
|
+
let error = dist0;
|
|
1891
|
+
|
|
1892
|
+
if (close) {
|
|
1893
|
+
|
|
1894
|
+
/**
|
|
1895
|
+
* check additional points
|
|
1896
|
+
* to prevent distortions
|
|
1897
|
+
*/
|
|
1898
|
+
|
|
1899
|
+
// 2nd segment mid
|
|
1900
|
+
let pt_2 = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], 0.5);
|
|
1901
|
+
|
|
1902
|
+
// simplified path
|
|
1903
|
+
let t3 = (1 + t) * 0.5;
|
|
1904
|
+
let ptS_2 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t3);
|
|
1905
|
+
dist1 = getDistAv(pt_2, ptS_2);
|
|
1906
|
+
|
|
1907
|
+
error += dist1;
|
|
1908
|
+
|
|
1909
|
+
// quit - paths not congruent
|
|
1910
|
+
|
|
1911
|
+
if (dist1 < maxDist) {
|
|
1700
1912
|
|
|
1701
|
-
|
|
1913
|
+
// 1st segment mid
|
|
1914
|
+
let pt_1 = pointAtT([com1.p0, com1.cp1, com1.cp2, com1.p], 0.5);
|
|
1702
1915
|
|
|
1703
|
-
|
|
1916
|
+
let t2 = t * 0.5;
|
|
1917
|
+
let ptS_1 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t2);
|
|
1918
|
+
dist2 = getDistAv(pt_1, ptS_1);
|
|
1704
1919
|
|
|
1705
|
-
|
|
1706
|
-
|
|
1920
|
+
/*
|
|
1921
|
+
if(dist1>tolerance){
|
|
1922
|
+
renderPoint(markers, pt_1, 'blue')
|
|
1923
|
+
renderPoint(markers, ptS_1, 'orange', '0.5%')
|
|
1924
|
+
}
|
|
1925
|
+
*/
|
|
1707
1926
|
|
|
1708
|
-
|
|
1927
|
+
// quit - paths not congruent
|
|
1928
|
+
if (dist1 + dist2 < maxDist) success = true;
|
|
1709
1929
|
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
comS = combinedEx[0];
|
|
1713
|
-
error = comS.error;
|
|
1930
|
+
// collect error data
|
|
1931
|
+
error += dist2;
|
|
1714
1932
|
|
|
1715
1933
|
}
|
|
1716
1934
|
|
|
1717
|
-
|
|
1718
|
-
}
|
|
1935
|
+
} // end 1st try
|
|
1719
1936
|
|
|
1720
1937
|
// add meta
|
|
1721
1938
|
if (success) {
|
|
1722
1939
|
|
|
1723
|
-
|
|
1724
1940
|
// correct to exact start and end points
|
|
1725
1941
|
comS.p0 = com1.p0;
|
|
1726
1942
|
comS.p = com2.p;
|
|
@@ -1743,50 +1959,69 @@ function combineCubicPairs(com1, com2, extrapolateDominant = false, tolerance =
|
|
|
1743
1959
|
return commands;
|
|
1744
1960
|
}
|
|
1745
1961
|
|
|
1746
|
-
function getExtrapolatedCommand(com1, com2,
|
|
1962
|
+
function getExtrapolatedCommand(com1, com2, t = 0) {
|
|
1747
1963
|
|
|
1748
1964
|
let { p0, cp1 } = com1;
|
|
1749
1965
|
let { p, cp2 } = com2;
|
|
1750
1966
|
|
|
1751
1967
|
// extrapolate control points
|
|
1752
|
-
|
|
1753
|
-
x: (cp1.x - (1 -
|
|
1754
|
-
y: (cp1.y - (1 -
|
|
1968
|
+
cp1 = {
|
|
1969
|
+
x: (cp1.x - (1 - t) * p0.x) / t,
|
|
1970
|
+
y: (cp1.y - (1 - t) * p0.y) / t
|
|
1755
1971
|
};
|
|
1756
1972
|
|
|
1757
|
-
|
|
1758
|
-
x: (cp2.x -
|
|
1759
|
-
y: (cp2.y -
|
|
1973
|
+
cp2 = {
|
|
1974
|
+
x: (cp2.x - t * p.x) / (1 - t),
|
|
1975
|
+
y: (cp2.y - t * p.y) / (1 - t)
|
|
1760
1976
|
};
|
|
1761
1977
|
|
|
1762
|
-
|
|
1978
|
+
return { p0, cp1, cp2, p };
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
function findSplitT(com1, com2) {
|
|
1982
|
+
|
|
1983
|
+
let len3 = getDistance(com1.cp2, com1.p);
|
|
1984
|
+
let len4 = getDistance(com1.cp2, com2.cp1);
|
|
1763
1985
|
|
|
1764
|
-
|
|
1986
|
+
let t = Math.min(len3) / len4;
|
|
1765
1987
|
|
|
1988
|
+
return t
|
|
1766
1989
|
}
|
|
1767
1990
|
|
|
1768
|
-
function
|
|
1991
|
+
function commandIsFlat(points, {
|
|
1992
|
+
tolerance = 1,
|
|
1993
|
+
debug=false
|
|
1994
|
+
} = {}) {
|
|
1769
1995
|
|
|
1770
|
-
|
|
1771
|
-
let
|
|
1996
|
+
let isFlat=false;
|
|
1997
|
+
let report = {
|
|
1998
|
+
flat:true,
|
|
1999
|
+
steepness:0
|
|
2000
|
+
};
|
|
1772
2001
|
|
|
1773
|
-
|
|
1774
|
-
let
|
|
2002
|
+
let p0 = points[0];
|
|
2003
|
+
let p = points[points.length - 1];
|
|
1775
2004
|
|
|
1776
|
-
let
|
|
1777
|
-
let
|
|
2005
|
+
let xSet = new Set([...points.map(pt => +pt.x.toFixed(8))]);
|
|
2006
|
+
let ySet = new Set([...points.map(pt => +pt.y.toFixed(8))]);
|
|
1778
2007
|
|
|
1779
|
-
|
|
2008
|
+
// must be flat
|
|
2009
|
+
if(xSet.size===1 || ySet.size===1) return !debug ? true : report;
|
|
1780
2010
|
|
|
1781
|
-
|
|
2011
|
+
let squareDist = getSquareDistance(p0, p);
|
|
2012
|
+
let threshold = squareDist / 1000 * tolerance;
|
|
2013
|
+
let area = getPolygonArea(points, true);
|
|
1782
2014
|
|
|
1783
|
-
|
|
1784
|
-
|
|
2015
|
+
// flat enough
|
|
2016
|
+
if(area < threshold) isFlat = true;
|
|
1785
2017
|
|
|
1786
|
-
|
|
2018
|
+
if(debug){
|
|
2019
|
+
report.flat = isFlat;
|
|
1787
2020
|
|
|
1788
|
-
|
|
2021
|
+
report.steepness = area/squareDist*10;
|
|
2022
|
+
}
|
|
1789
2023
|
|
|
2024
|
+
return !debug ? isFlat : report;
|
|
1790
2025
|
}
|
|
1791
2026
|
|
|
1792
2027
|
function analyzePathData(pathData = []) {
|
|
@@ -1831,7 +2066,6 @@ function analyzePathData(pathData = []) {
|
|
|
1831
2066
|
* this way we can skip certain tests
|
|
1832
2067
|
*/
|
|
1833
2068
|
let commandPts = [p0];
|
|
1834
|
-
let isFlat = false;
|
|
1835
2069
|
|
|
1836
2070
|
// init properties
|
|
1837
2071
|
com.idx = c - 1;
|
|
@@ -1862,7 +2096,7 @@ function analyzePathData(pathData = []) {
|
|
|
1862
2096
|
com.p0 = p0;
|
|
1863
2097
|
com.p = p;
|
|
1864
2098
|
|
|
1865
|
-
let cp1, cp2, cp1N,
|
|
2099
|
+
let cp1, cp2, cp1N, pN, typeN, area1;
|
|
1866
2100
|
|
|
1867
2101
|
let dimA = getDistAv(p0, p);
|
|
1868
2102
|
com.dimA = dimA;
|
|
@@ -1902,13 +2136,18 @@ function analyzePathData(pathData = []) {
|
|
|
1902
2136
|
if (type === 'C') commandPts.push(cp2);
|
|
1903
2137
|
commandPts.push(p);
|
|
1904
2138
|
|
|
2139
|
+
/*
|
|
2140
|
+
|
|
1905
2141
|
let commandFlatness = commandIsFlat(commandPts);
|
|
1906
2142
|
isFlat = commandFlatness.flat;
|
|
1907
2143
|
com.flat = isFlat;
|
|
1908
2144
|
|
|
1909
2145
|
if (isFlat) {
|
|
1910
2146
|
com.extreme = false;
|
|
2147
|
+
|
|
1911
2148
|
}
|
|
2149
|
+
*/
|
|
2150
|
+
|
|
1912
2151
|
}
|
|
1913
2152
|
|
|
1914
2153
|
/**
|
|
@@ -1917,7 +2156,7 @@ function analyzePathData(pathData = []) {
|
|
|
1917
2156
|
* so we interpret maximum x/y on-path points as well as extremes
|
|
1918
2157
|
* but we ignore linetos to allow chunk compilation
|
|
1919
2158
|
*/
|
|
1920
|
-
if (
|
|
2159
|
+
if (type !== 'L' && (p.x === left || p.y === top || p.x === right || p.y === bottom)) {
|
|
1921
2160
|
com.extreme = true;
|
|
1922
2161
|
}
|
|
1923
2162
|
|
|
@@ -1930,7 +2169,7 @@ function analyzePathData(pathData = []) {
|
|
|
1930
2169
|
pN = comN ? { x: comNValsL[0], y: comNValsL[1] } : null;
|
|
1931
2170
|
|
|
1932
2171
|
cp1N = { x: comN.values[0], y: comN.values[1] };
|
|
1933
|
-
|
|
2172
|
+
comN.type === 'C' ? { x: comN.values[2], y: comN.values[3] } : null;
|
|
1934
2173
|
}
|
|
1935
2174
|
|
|
1936
2175
|
/**
|
|
@@ -1960,19 +2199,15 @@ function analyzePathData(pathData = []) {
|
|
|
1960
2199
|
// check extremes
|
|
1961
2200
|
let cpts = commandPts.slice(1);
|
|
1962
2201
|
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
let thresh = (w + h) / 2 * 0.1;
|
|
1966
|
-
let pts1 = type === 'C' ? [p, cp1N, cp2N, pN] : [p, cp1N, pN];
|
|
1967
|
-
|
|
1968
|
-
let flatness2 = commandIsFlat(pts1, thresh);
|
|
1969
|
-
let isFlat2 = flatness2.flat;
|
|
2202
|
+
pN ? Math.abs(pN.x - p0.x) : 0;
|
|
2203
|
+
pN ? Math.abs(pN.y - p0.y) : 0;
|
|
1970
2204
|
|
|
1971
2205
|
/**
|
|
1972
2206
|
* if current and next cubic are flat
|
|
1973
2207
|
* we don't flag them as extremes to allow simplification
|
|
1974
2208
|
*/
|
|
1975
|
-
|
|
2209
|
+
|
|
2210
|
+
let hasExtremes = (!com.extreme ? bezierhasExtreme(p0, cpts, angleThreshold) : true);
|
|
1976
2211
|
|
|
1977
2212
|
if (hasExtremes) {
|
|
1978
2213
|
com.extreme = true;
|
|
@@ -2018,11 +2253,11 @@ function detectAccuracy(pathData) {
|
|
|
2018
2253
|
|
|
2019
2254
|
// Reference first MoveTo command (M)
|
|
2020
2255
|
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
2021
|
-
let p0 =
|
|
2256
|
+
let p0 = M;
|
|
2022
2257
|
let p = M;
|
|
2023
2258
|
pathData[0].decimals = 0;
|
|
2024
2259
|
|
|
2025
|
-
let dims =
|
|
2260
|
+
let dims = [];
|
|
2026
2261
|
|
|
2027
2262
|
// add average distances
|
|
2028
2263
|
for (let i = 0, len = pathData.length; i < len; i++) {
|
|
@@ -2030,28 +2265,33 @@ function detectAccuracy(pathData) {
|
|
|
2030
2265
|
let { type, values } = com;
|
|
2031
2266
|
|
|
2032
2267
|
let lastVals = values.length ? values.slice(-2) : [M.x, M.y];
|
|
2033
|
-
p={x:lastVals[0], y:lastVals[1]};
|
|
2268
|
+
p = { x: lastVals[0], y: lastVals[1] };
|
|
2034
2269
|
|
|
2035
2270
|
// use existing averave dimension value or calculate
|
|
2036
|
-
let dimA = com.dimA ? +com.dimA.toFixed(8) : type!=='M' ? +getDistAv(p0, p).toFixed(8) : 0;
|
|
2271
|
+
let dimA = com.dimA ? +com.dimA.toFixed(8) : type !== 'M' ? +getDistAv(p0, p).toFixed(8) : 0;
|
|
2037
2272
|
|
|
2038
|
-
if(dimA) dims.
|
|
2039
|
-
|
|
2273
|
+
if (dimA) dims.push(dimA);
|
|
2040
2274
|
|
|
2041
|
-
if(type==='M'){
|
|
2042
|
-
M=p;
|
|
2275
|
+
if (type === 'M') {
|
|
2276
|
+
M = p;
|
|
2043
2277
|
}
|
|
2044
2278
|
p0 = p;
|
|
2045
2279
|
}
|
|
2046
2280
|
|
|
2047
|
-
let dim_min =
|
|
2048
|
-
let sliceIdx = Math.ceil(dim_min.length/8);
|
|
2049
|
-
dim_min = dim_min.slice(0, sliceIdx );
|
|
2281
|
+
let dim_min = dims.sort();
|
|
2050
2282
|
|
|
2051
|
-
|
|
2283
|
+
/*
|
|
2284
|
+
let minVal = dim_min.length > 15 ?
|
|
2285
|
+
(dim_min[0] + dim_min[2]) / 2 :
|
|
2286
|
+
dim_min[0];
|
|
2287
|
+
*/
|
|
2288
|
+
|
|
2289
|
+
let sliceIdx = Math.ceil(dim_min.length / 10);
|
|
2290
|
+
dim_min = dim_min.slice(0, sliceIdx);
|
|
2291
|
+
let minVal = dim_min.reduce((a, b) => a + b, 0) / sliceIdx;
|
|
2052
2292
|
|
|
2053
|
-
let threshold =
|
|
2054
|
-
let decimalsAuto =
|
|
2293
|
+
let threshold = 40;
|
|
2294
|
+
let decimalsAuto = minVal > threshold*1.5 ? 0 : Math.floor(threshold / minVal).toString().length;
|
|
2055
2295
|
|
|
2056
2296
|
// clamp
|
|
2057
2297
|
return Math.min(Math.max(0, decimalsAuto), 8)
|
|
@@ -2064,19 +2304,24 @@ function detectAccuracy(pathData) {
|
|
|
2064
2304
|
* based on suggested accuracy in path data
|
|
2065
2305
|
*/
|
|
2066
2306
|
function roundPathData(pathData, decimals = -1) {
|
|
2067
|
-
// has recommended decimals
|
|
2068
|
-
let hasDecimal = decimals == 'auto' && pathData[0].hasOwnProperty('decimals') ? true : false;
|
|
2069
2307
|
|
|
2070
|
-
|
|
2071
|
-
|
|
2308
|
+
let len = pathData.length;
|
|
2309
|
+
|
|
2310
|
+
for (let c = 0; c < len; c++) {
|
|
2311
|
+
|
|
2312
|
+
let values = pathData[c].values;
|
|
2313
|
+
let valLen = values.length;
|
|
2072
2314
|
|
|
2073
|
-
if (decimals
|
|
2074
|
-
decimals = hasDecimal ? com.decimals : decimals;
|
|
2315
|
+
if (valLen && (decimals > -1) ) {
|
|
2075
2316
|
|
|
2076
|
-
|
|
2317
|
+
for(let v=0; v<valLen; v++){
|
|
2077
2318
|
|
|
2319
|
+
pathData[c].values[v] = +values[v].toFixed(decimals);
|
|
2320
|
+
}
|
|
2078
2321
|
}
|
|
2079
|
-
}
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
return pathData;
|
|
2080
2325
|
}
|
|
2081
2326
|
|
|
2082
2327
|
function revertCubicQuadratic(p0 = {}, cp1 = {}, cp2 = {}, p = {}) {
|
|
@@ -2094,7 +2339,7 @@ function revertCubicQuadratic(p0 = {}, cp1 = {}, cp2 = {}, p = {}) {
|
|
|
2094
2339
|
let values = [cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y];
|
|
2095
2340
|
let comN = {type, values};
|
|
2096
2341
|
|
|
2097
|
-
if (dist1 < threshold) {
|
|
2342
|
+
if (dist1 && threshold && dist1 < threshold) {
|
|
2098
2343
|
cp1_Q = checkLineIntersection(p0, cp1, p, cp2, false);
|
|
2099
2344
|
if (cp1_Q) {
|
|
2100
2345
|
|
|
@@ -2119,9 +2364,10 @@ function convertPathData(pathData, {
|
|
|
2119
2364
|
if (toShorthands) pathData = pathDataToShorthands(pathData);
|
|
2120
2365
|
|
|
2121
2366
|
// pre round - before relative conversion to minimize distortions
|
|
2122
|
-
pathData = roundPathData(pathData, decimals);
|
|
2367
|
+
if(decimals>-1 && toRelative) pathData = roundPathData(pathData, decimals);
|
|
2123
2368
|
if (toRelative) pathData = pathDataToRelative(pathData);
|
|
2124
2369
|
if (decimals > -1) pathData = roundPathData(pathData, decimals);
|
|
2370
|
+
|
|
2125
2371
|
return pathData
|
|
2126
2372
|
}
|
|
2127
2373
|
|
|
@@ -2423,7 +2669,7 @@ function pathDataToLonghands(pathData, decimals = -1, test = true) {
|
|
|
2423
2669
|
* L, L, C, Q => H, V, S, T
|
|
2424
2670
|
* reversed method: pathDataToLonghands()
|
|
2425
2671
|
*/
|
|
2426
|
-
function pathDataToShorthands(pathData, decimals = -1, test =
|
|
2672
|
+
function pathDataToShorthands(pathData, decimals = -1, test = false) {
|
|
2427
2673
|
|
|
2428
2674
|
/**
|
|
2429
2675
|
* analyze pathdata – if you're sure your data is already absolute skip it via test=false
|
|
@@ -2434,29 +2680,28 @@ function pathDataToShorthands(pathData, decimals = -1, test = true) {
|
|
|
2434
2680
|
hasRel = /[astvqmhlc]/g.test(commandTokens);
|
|
2435
2681
|
}
|
|
2436
2682
|
|
|
2437
|
-
pathData = test && hasRel ?
|
|
2683
|
+
pathData = test && hasRel ? pathDataToAbsoluteOrRelative(pathData) : pathData;
|
|
2684
|
+
|
|
2685
|
+
let len = pathData.length;
|
|
2686
|
+
let pathDataShorts = new Array(len);
|
|
2438
2687
|
|
|
2439
2688
|
let comShort = {
|
|
2440
2689
|
type: "M",
|
|
2441
2690
|
values: pathData[0].values
|
|
2442
2691
|
};
|
|
2443
2692
|
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
comShort.decimals = pathData[0].decimals;
|
|
2447
|
-
}
|
|
2448
|
-
|
|
2449
|
-
let pathDataShorts = [comShort];
|
|
2693
|
+
pathDataShorts[0] = comShort;
|
|
2450
2694
|
|
|
2451
2695
|
let p0 = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
2452
2696
|
let p;
|
|
2453
2697
|
let tolerance = 0.01;
|
|
2454
2698
|
|
|
2455
|
-
for (let i = 1
|
|
2699
|
+
for (let i = 1; i < len; i++) {
|
|
2456
2700
|
|
|
2457
2701
|
let com = pathData[i];
|
|
2458
2702
|
let { type, values } = com;
|
|
2459
|
-
let
|
|
2703
|
+
let valuesLen = values.length;
|
|
2704
|
+
let valuesLast = [values[valuesLen-2], values[valuesLen-1]];
|
|
2460
2705
|
|
|
2461
2706
|
// previoius command
|
|
2462
2707
|
let comPrev = pathData[i - 1];
|
|
@@ -2504,7 +2749,8 @@ function pathDataToShorthands(pathData, decimals = -1, test = true) {
|
|
|
2504
2749
|
if (typePrev !== 'Q') {
|
|
2505
2750
|
|
|
2506
2751
|
p0 = { x: valuesLast[0], y: valuesLast[1] };
|
|
2507
|
-
|
|
2752
|
+
|
|
2753
|
+
pathDataShorts[i] = com;
|
|
2508
2754
|
continue;
|
|
2509
2755
|
}
|
|
2510
2756
|
|
|
@@ -2533,7 +2779,8 @@ function pathDataToShorthands(pathData, decimals = -1, test = true) {
|
|
|
2533
2779
|
|
|
2534
2780
|
if (typePrev !== 'C') {
|
|
2535
2781
|
|
|
2536
|
-
pathDataShorts
|
|
2782
|
+
pathDataShorts[i] = com;
|
|
2783
|
+
|
|
2537
2784
|
p0 = { x: valuesLast[0], y: valuesLast[1] };
|
|
2538
2785
|
continue;
|
|
2539
2786
|
}
|
|
@@ -2575,8 +2822,10 @@ function pathDataToShorthands(pathData, decimals = -1, test = true) {
|
|
|
2575
2822
|
}
|
|
2576
2823
|
|
|
2577
2824
|
p0 = { x: valuesLast[0], y: valuesLast[1] };
|
|
2578
|
-
pathDataShorts
|
|
2825
|
+
pathDataShorts[i] = comShort;
|
|
2826
|
+
|
|
2579
2827
|
}
|
|
2828
|
+
|
|
2580
2829
|
return pathDataShorts;
|
|
2581
2830
|
}
|
|
2582
2831
|
|
|
@@ -3021,10 +3270,10 @@ function normalizePathData(pathData = [],
|
|
|
3021
3270
|
quadraticToCubic = false,
|
|
3022
3271
|
arcToCubic = false,
|
|
3023
3272
|
arcAccuracy = 2,
|
|
3024
|
-
} = {},
|
|
3025
3273
|
|
|
3026
|
-
|
|
3274
|
+
// assume we need full normalization
|
|
3027
3275
|
hasRelatives = true, hasShorthands = true, hasQuadratics = true, hasArcs = true, testTypes = false
|
|
3276
|
+
|
|
3028
3277
|
} = {}
|
|
3029
3278
|
) {
|
|
3030
3279
|
|
|
@@ -3075,15 +3324,28 @@ function parsePathDataNormalized(d,
|
|
|
3075
3324
|
} = {}
|
|
3076
3325
|
) {
|
|
3077
3326
|
|
|
3078
|
-
|
|
3079
|
-
let
|
|
3080
|
-
|
|
3327
|
+
// is already array
|
|
3328
|
+
let isArray = Array.isArray(d);
|
|
3329
|
+
|
|
3330
|
+
// normalize native pathData to regular array
|
|
3331
|
+
let hasConstructor = isArray && typeof d[0] === 'object' && typeof d[0].constructor === 'function';
|
|
3332
|
+
/*
|
|
3333
|
+
if (hasConstructor) {
|
|
3334
|
+
d = d.map(com => { return { type: com.type, values: com.values } })
|
|
3335
|
+
console.log('hasConstructor', hasConstructor, (typeof d[0].constructor), d);
|
|
3336
|
+
}
|
|
3337
|
+
*/
|
|
3338
|
+
|
|
3339
|
+
let pathDataObj = isArray ? d : parsePathDataString(d);
|
|
3340
|
+
|
|
3341
|
+
let { hasRelatives = true, hasShorthands = true, hasQuadratics = true, hasArcs = true } = pathDataObj;
|
|
3342
|
+
let pathData = hasConstructor ? pathDataObj : pathDataObj.pathData;
|
|
3081
3343
|
|
|
3082
3344
|
// normalize
|
|
3083
3345
|
pathData = normalizePathData(pathData,
|
|
3084
|
-
{ toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy
|
|
3085
|
-
|
|
3086
|
-
|
|
3346
|
+
{ toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy,
|
|
3347
|
+
hasRelatives, hasShorthands, hasQuadratics, hasArcs
|
|
3348
|
+
},
|
|
3087
3349
|
);
|
|
3088
3350
|
|
|
3089
3351
|
return pathData;
|
|
@@ -3435,7 +3697,8 @@ function parsePathDataString(d, debug = true) {
|
|
|
3435
3697
|
if (debug === 'log') {
|
|
3436
3698
|
console.log(feedback);
|
|
3437
3699
|
} else {
|
|
3438
|
-
|
|
3700
|
+
|
|
3701
|
+
console.warn(feedback);
|
|
3439
3702
|
}
|
|
3440
3703
|
}
|
|
3441
3704
|
|
|
@@ -3463,18 +3726,266 @@ function parsePathDataString(d, debug = true) {
|
|
|
3463
3726
|
|
|
3464
3727
|
}
|
|
3465
3728
|
|
|
3466
|
-
function
|
|
3729
|
+
function stringifyPathData(pathData) {
|
|
3730
|
+
return pathData.map(com => { return `${com.type} ${com.values.join(' ')}` }).join(' ');
|
|
3731
|
+
}
|
|
3732
|
+
|
|
3733
|
+
function shapeElToPath(el) {
|
|
3734
|
+
|
|
3735
|
+
let nodeName = el.nodeName.toLowerCase();
|
|
3736
|
+
if (nodeName === 'path') return el;
|
|
3737
|
+
|
|
3738
|
+
let pathData = getPathDataFromEl(el);
|
|
3739
|
+
let d = pathData.map(com => { return `${com.type} ${com.values} ` }).join(' ');
|
|
3740
|
+
let attributes = [...el.attributes].map(att => att.name);
|
|
3741
|
+
|
|
3742
|
+
let pathN = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
3743
|
+
pathN.setAttribute('d', d);
|
|
3744
|
+
|
|
3745
|
+
let exclude = ['x', 'y', 'cx', 'cy', 'dx', 'dy', 'r', 'rx', 'ry', 'width', 'height', 'points'];
|
|
3746
|
+
|
|
3747
|
+
attributes.forEach(att => {
|
|
3748
|
+
if (!exclude.includes(att)) {
|
|
3749
|
+
let val = el.getAttribute(att);
|
|
3750
|
+
pathN.setAttribute(att, val);
|
|
3751
|
+
}
|
|
3752
|
+
});
|
|
3753
|
+
|
|
3754
|
+
return pathN
|
|
3755
|
+
|
|
3756
|
+
}
|
|
3757
|
+
|
|
3758
|
+
// retrieve pathdata from svg geometry elements
|
|
3759
|
+
function getPathDataFromEl(el, stringify = false) {
|
|
3760
|
+
|
|
3761
|
+
let pathData = [];
|
|
3762
|
+
let type = el.nodeName;
|
|
3763
|
+
let atts, attNames, d, x, y, width, height, r, rx, ry, cx, cy, x1, x2, y1, y2;
|
|
3764
|
+
|
|
3765
|
+
// convert relative or absolute units
|
|
3766
|
+
const svgElUnitsToPixel = (el, decimals = 9) => {
|
|
3767
|
+
|
|
3768
|
+
const svg = el.nodeName !== "svg" ? el.closest("svg") : el;
|
|
3769
|
+
|
|
3770
|
+
// convert real life units to pixels
|
|
3771
|
+
const translateUnitToPixel = (value) => {
|
|
3772
|
+
|
|
3773
|
+
if (value === null) {
|
|
3774
|
+
return 0
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3777
|
+
let dpi = 96;
|
|
3778
|
+
let unit = value.match(/([a-z]+)/gi);
|
|
3779
|
+
unit = unit ? unit[0] : "";
|
|
3780
|
+
let val = parseFloat(value);
|
|
3781
|
+
let rat;
|
|
3782
|
+
|
|
3783
|
+
// no unit - already pixes/user unit
|
|
3784
|
+
if (!unit) {
|
|
3785
|
+
return val;
|
|
3786
|
+
}
|
|
3787
|
+
|
|
3788
|
+
switch (unit) {
|
|
3789
|
+
case "in":
|
|
3790
|
+
rat = dpi;
|
|
3791
|
+
break;
|
|
3792
|
+
case "pt":
|
|
3793
|
+
rat = (1 / 72) * 96;
|
|
3794
|
+
break;
|
|
3795
|
+
case "cm":
|
|
3796
|
+
rat = (1 / 2.54) * 96;
|
|
3797
|
+
break;
|
|
3798
|
+
case "mm":
|
|
3799
|
+
rat = ((1 / 2.54) * 96) / 10;
|
|
3800
|
+
break;
|
|
3801
|
+
// just a default approximation
|
|
3802
|
+
case "em":
|
|
3803
|
+
case "rem":
|
|
3804
|
+
rat = 16;
|
|
3805
|
+
break;
|
|
3806
|
+
default:
|
|
3807
|
+
rat = 1;
|
|
3808
|
+
}
|
|
3809
|
+
let valuePx = val * rat;
|
|
3810
|
+
return +valuePx.toFixed(decimals);
|
|
3811
|
+
};
|
|
3812
|
+
|
|
3813
|
+
// svg width and height attributes
|
|
3814
|
+
let width = svg.getAttribute("width");
|
|
3815
|
+
width = width ? translateUnitToPixel(width) : 300;
|
|
3816
|
+
let height = svg.getAttribute("height");
|
|
3817
|
+
height = width ? translateUnitToPixel(height) : 150;
|
|
3818
|
+
|
|
3819
|
+
let vB = svg.getAttribute("viewBox");
|
|
3820
|
+
vB = vB
|
|
3821
|
+
? vB
|
|
3822
|
+
.replace(/,/g, " ")
|
|
3823
|
+
.split(" ")
|
|
3824
|
+
.filter(Boolean)
|
|
3825
|
+
.map((val) => {
|
|
3826
|
+
return +val;
|
|
3827
|
+
})
|
|
3828
|
+
: [];
|
|
3829
|
+
|
|
3830
|
+
let w = vB.length ? vB[2] : width;
|
|
3831
|
+
let h = vB.length ? vB[3] : height;
|
|
3832
|
+
let scaleX = w / 100;
|
|
3833
|
+
let scaleY = h / 100;
|
|
3834
|
+
let scalRoot = Math.sqrt((Math.pow(scaleX, 2) + Math.pow(scaleY, 2)) / 2);
|
|
3835
|
+
|
|
3836
|
+
let attsH = ["x", "width", "x1", "x2", "rx", "cx", "r"];
|
|
3837
|
+
let attsV = ["y", "height", "y1", "y2", "ry", "cy"];
|
|
3838
|
+
|
|
3839
|
+
let atts = el.getAttributeNames();
|
|
3840
|
+
atts.forEach((att) => {
|
|
3841
|
+
let val = el.getAttribute(att);
|
|
3842
|
+
let valAbs = val;
|
|
3843
|
+
if (attsH.includes(att) || attsV.includes(att)) {
|
|
3844
|
+
let scale = attsH.includes(att) ? scaleX : scaleY;
|
|
3845
|
+
scale = att === "r" && w != h ? scalRoot : scale;
|
|
3846
|
+
let unit = val.match(/([a-z|%]+)/gi);
|
|
3847
|
+
unit = unit ? unit[0] : "";
|
|
3848
|
+
if (val.includes("%")) {
|
|
3849
|
+
valAbs = parseFloat(val) * scale;
|
|
3850
|
+
}
|
|
3851
|
+
|
|
3852
|
+
else {
|
|
3853
|
+
valAbs = translateUnitToPixel(val);
|
|
3854
|
+
}
|
|
3855
|
+
el.setAttribute(att, +valAbs);
|
|
3856
|
+
}
|
|
3857
|
+
});
|
|
3858
|
+
};
|
|
3859
|
+
|
|
3860
|
+
svgElUnitsToPixel(el);
|
|
3861
|
+
|
|
3862
|
+
const getAtts = (attNames) => {
|
|
3863
|
+
atts = {};
|
|
3864
|
+
attNames.forEach(att => {
|
|
3865
|
+
atts[att] = +el.getAttribute(att);
|
|
3866
|
+
});
|
|
3867
|
+
return atts
|
|
3868
|
+
};
|
|
3869
|
+
|
|
3870
|
+
switch (type) {
|
|
3871
|
+
case 'path':
|
|
3872
|
+
d = el.getAttribute("d");
|
|
3873
|
+
pathData = parsePathDataNormalized(d);
|
|
3874
|
+
break;
|
|
3875
|
+
|
|
3876
|
+
case 'rect':
|
|
3877
|
+
attNames = ['x', 'y', 'width', 'height', 'rx', 'ry'];
|
|
3878
|
+
({ x, y, width, height, rx, ry } = getAtts(attNames));
|
|
3879
|
+
|
|
3880
|
+
if (!rx && !ry) {
|
|
3881
|
+
pathData = [
|
|
3882
|
+
{ type: "M", values: [x, y] },
|
|
3883
|
+
{ type: "L", values: [x + width, y] },
|
|
3884
|
+
{ type: "L", values: [x + width, y + height] },
|
|
3885
|
+
{ type: "L", values: [x, y + height] },
|
|
3886
|
+
{ type: "Z", values: [] }
|
|
3887
|
+
];
|
|
3888
|
+
} else {
|
|
3889
|
+
|
|
3890
|
+
if (rx > width / 2) {
|
|
3891
|
+
rx = width / 2;
|
|
3892
|
+
}
|
|
3893
|
+
if (ry > height / 2) {
|
|
3894
|
+
ry = height / 2;
|
|
3895
|
+
}
|
|
3896
|
+
pathData = [
|
|
3897
|
+
{ type: "M", values: [x + rx, y] },
|
|
3898
|
+
{ type: "L", values: [x + width - rx, y] },
|
|
3899
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x + width, y + ry] },
|
|
3900
|
+
{ type: "L", values: [x + width, y + height - ry] },
|
|
3901
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x + width - rx, y + height] },
|
|
3902
|
+
{ type: "L", values: [x + rx, y + height] },
|
|
3903
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x, y + height - ry] },
|
|
3904
|
+
{ type: "L", values: [x, y + ry] },
|
|
3905
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x + rx, y] },
|
|
3906
|
+
{ type: "Z", values: [] }
|
|
3907
|
+
];
|
|
3908
|
+
}
|
|
3909
|
+
break;
|
|
3910
|
+
|
|
3911
|
+
case 'circle':
|
|
3912
|
+
case 'ellipse':
|
|
3913
|
+
|
|
3914
|
+
attNames = ['cx', 'cy', 'rx', 'ry', 'r'];
|
|
3915
|
+
({ cx, cy, r, rx, ry } = getAtts(attNames));
|
|
3916
|
+
|
|
3917
|
+
let isCircle = type === 'circle';
|
|
3918
|
+
|
|
3919
|
+
if (isCircle) {
|
|
3920
|
+
r = r;
|
|
3921
|
+
rx = r;
|
|
3922
|
+
ry = r;
|
|
3923
|
+
} else {
|
|
3924
|
+
rx = rx ? rx : r;
|
|
3925
|
+
ry = ry ? ry : r;
|
|
3926
|
+
}
|
|
3927
|
+
|
|
3928
|
+
// simplified radii for cirecles
|
|
3929
|
+
let rxS = isCircle && r>=1 ? 1 : rx;
|
|
3930
|
+
let ryS = isCircle && r>=1 ? 1 : rx;
|
|
3931
|
+
|
|
3932
|
+
pathData = [
|
|
3933
|
+
{ type: "M", values: [cx + rx, cy] },
|
|
3934
|
+
{ type: "A", values: [rxS, ryS, 0, 1, 1, cx - rx, cy] },
|
|
3935
|
+
{ type: "A", values: [rxS, ryS, 0, 1, 1, cx + rx, cy] },
|
|
3936
|
+
];
|
|
3937
|
+
|
|
3938
|
+
break;
|
|
3939
|
+
case 'line':
|
|
3940
|
+
attNames = ['x1', 'y1', 'x2', 'y2'];
|
|
3941
|
+
({ x1, y1, x2, y2 } = getAtts(attNames));
|
|
3942
|
+
pathData = [
|
|
3943
|
+
{ type: "M", values: [x1, y1] },
|
|
3944
|
+
{ type: "L", values: [x2, y2] }
|
|
3945
|
+
];
|
|
3946
|
+
break;
|
|
3947
|
+
case 'polygon':
|
|
3948
|
+
case 'polyline':
|
|
3949
|
+
|
|
3950
|
+
let points = el.getAttribute('points').replaceAll(',', ' ').split(' ').filter(Boolean);
|
|
3951
|
+
|
|
3952
|
+
for (let i = 0; i < points.length; i += 2) {
|
|
3953
|
+
pathData.push({
|
|
3954
|
+
type: (i === 0 ? "M" : "L"),
|
|
3955
|
+
values: [+points[i], +points[i + 1]]
|
|
3956
|
+
});
|
|
3957
|
+
}
|
|
3958
|
+
if (type === 'polygon') {
|
|
3959
|
+
pathData.push({
|
|
3960
|
+
type: "Z",
|
|
3961
|
+
values: []
|
|
3962
|
+
});
|
|
3963
|
+
}
|
|
3964
|
+
break;
|
|
3965
|
+
}
|
|
3966
|
+
|
|
3967
|
+
return stringify ? stringifyPathData(pathData) : pathData;
|
|
3968
|
+
|
|
3969
|
+
}
|
|
3970
|
+
|
|
3971
|
+
function pathDataRemoveColinear(pathData, {
|
|
3972
|
+
tolerance = 1,
|
|
3973
|
+
|
|
3974
|
+
flatBezierToLinetos = true
|
|
3975
|
+
}={}) {
|
|
3467
3976
|
|
|
3468
3977
|
let pathDataN = [pathData[0]];
|
|
3978
|
+
|
|
3469
3979
|
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
3470
3980
|
let p0 = M;
|
|
3471
3981
|
let p = M;
|
|
3472
3982
|
pathData[pathData.length - 1].type.toLowerCase() === 'z';
|
|
3473
3983
|
|
|
3474
3984
|
for (let c = 1, l = pathData.length; c < l; c++) {
|
|
3475
|
-
|
|
3985
|
+
|
|
3476
3986
|
let com = pathData[c];
|
|
3477
3987
|
let comN = pathData[c + 1] || pathData[l - 1];
|
|
3988
|
+
|
|
3478
3989
|
let p1 = comN.type.toLowerCase() === 'z' ? M : { x: comN.values[comN.values.length - 2], y: comN.values[comN.values.length - 1] };
|
|
3479
3990
|
|
|
3480
3991
|
let { type, values } = com;
|
|
@@ -3483,11 +3994,9 @@ function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLinetos = t
|
|
|
3483
3994
|
|
|
3484
3995
|
let area = getPolygonArea([p0, p, p1], true);
|
|
3485
3996
|
|
|
3486
|
-
getSquareDistance(p0, p);
|
|
3487
|
-
getSquareDistance(p, p1);
|
|
3488
3997
|
let distSquare = getSquareDistance(p0, p1);
|
|
3489
3998
|
|
|
3490
|
-
let distMax = distSquare /
|
|
3999
|
+
let distMax = distSquare ? distSquare / 333 * tolerance : 0;
|
|
3491
4000
|
|
|
3492
4001
|
let isFlat = area < distMax;
|
|
3493
4002
|
let isFlatBez = false;
|
|
@@ -3501,30 +4010,38 @@ function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLinetos = t
|
|
|
3501
4010
|
[{ x: values[0], y: values[1] }, { x: values[2], y: values[3] }] :
|
|
3502
4011
|
(type === 'Q' ? [{ x: values[0], y: values[1] }] : []);
|
|
3503
4012
|
|
|
3504
|
-
isFlatBez =
|
|
3505
|
-
// console.log();
|
|
4013
|
+
isFlatBez = commandIsFlat([p0, ...cpts, p],{tolerance});
|
|
3506
4014
|
|
|
3507
|
-
if (isFlatBez && c < l - 1
|
|
4015
|
+
if (isFlatBez && c < l - 1 ) {
|
|
3508
4016
|
type = "L";
|
|
3509
4017
|
com.type = "L";
|
|
3510
4018
|
com.values = valsL;
|
|
3511
4019
|
|
|
3512
4020
|
}
|
|
3513
|
-
|
|
3514
4021
|
}
|
|
3515
4022
|
|
|
3516
|
-
// update end point
|
|
3517
|
-
p0 = p;
|
|
3518
|
-
|
|
3519
4023
|
// colinear – exclude arcs (as always =) as semicircles won't have an area
|
|
3520
4024
|
|
|
3521
4025
|
if ( isFlat && c < l - 1 && (type === 'L' || (flatBezierToLinetos && isFlatBez)) ) {
|
|
3522
|
-
|
|
3523
4026
|
|
|
4027
|
+
/*
|
|
4028
|
+
console.log(area, distMax );
|
|
4029
|
+
|
|
4030
|
+
if(p0.x === p.x && p0.y === p.y){
|
|
4031
|
+
|
|
4032
|
+
}
|
|
4033
|
+
|
|
4034
|
+
renderPoint(markers, p0, 'blue', '1.5%', '1')
|
|
4035
|
+
renderPoint(markers, p, 'red', '1%', '1')
|
|
4036
|
+
renderPoint(markers, p1, 'cyan', '0.5%', '1')
|
|
4037
|
+
*/
|
|
3524
4038
|
|
|
3525
4039
|
continue;
|
|
3526
4040
|
}
|
|
3527
4041
|
|
|
4042
|
+
// update end point
|
|
4043
|
+
p0 = p;
|
|
4044
|
+
|
|
3528
4045
|
if (type === 'M') {
|
|
3529
4046
|
M = p;
|
|
3530
4047
|
p0 = M;
|
|
@@ -3543,20 +4060,44 @@ function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLinetos = t
|
|
|
3543
4060
|
|
|
3544
4061
|
}
|
|
3545
4062
|
|
|
4063
|
+
function removeOrphanedM(pathData) {
|
|
4064
|
+
|
|
4065
|
+
let pathDataN = [];
|
|
4066
|
+
for (let i = 0, l = pathData.length; i < l; i++) {
|
|
4067
|
+
let com = pathData[i];
|
|
4068
|
+
if (!com) continue;
|
|
4069
|
+
let { type = null, values = [] } = com;
|
|
4070
|
+
let comN = pathData[i + 1] ? pathData[i + 1] : null;
|
|
4071
|
+
if ((type === 'M' || type === 'm')) {
|
|
4072
|
+
|
|
4073
|
+
if (!comN || (comN && (comN.type === 'Z' || comN.type === 'z'))) {
|
|
4074
|
+
if(comN) i++;
|
|
4075
|
+
continue
|
|
4076
|
+
}
|
|
4077
|
+
}
|
|
4078
|
+
pathDataN.push(com);
|
|
4079
|
+
}
|
|
4080
|
+
|
|
4081
|
+
return pathDataN;
|
|
4082
|
+
|
|
4083
|
+
}
|
|
4084
|
+
|
|
4085
|
+
/*
|
|
3546
4086
|
// remove zero-length segments introduced by rounding
|
|
3547
|
-
function removeZeroLengthLinetos_post(pathData) {
|
|
3548
|
-
let pathDataOpt = []
|
|
4087
|
+
export function removeZeroLengthLinetos_post(pathData) {
|
|
4088
|
+
let pathDataOpt = []
|
|
3549
4089
|
pathData.forEach((com, i) => {
|
|
3550
4090
|
let { type, values } = com;
|
|
3551
4091
|
if (type === 'l' || type === 'v' || type === 'h') {
|
|
3552
|
-
let hasLength = type === 'l' ? (values.join('') !== '00') : values[0] !== 0
|
|
3553
|
-
if (hasLength) pathDataOpt.push(com)
|
|
4092
|
+
let hasLength = type === 'l' ? (values.join('') !== '00') : values[0] !== 0
|
|
4093
|
+
if (hasLength) pathDataOpt.push(com)
|
|
3554
4094
|
} else {
|
|
3555
|
-
pathDataOpt.push(com)
|
|
4095
|
+
pathDataOpt.push(com)
|
|
3556
4096
|
}
|
|
3557
|
-
})
|
|
4097
|
+
})
|
|
3558
4098
|
return pathDataOpt
|
|
3559
4099
|
}
|
|
4100
|
+
*/
|
|
3560
4101
|
|
|
3561
4102
|
function removeZeroLengthLinetos(pathData) {
|
|
3562
4103
|
|
|
@@ -3568,16 +4109,27 @@ function removeZeroLengthLinetos(pathData) {
|
|
|
3568
4109
|
|
|
3569
4110
|
for (let c = 1, l = pathData.length; c < l; c++) {
|
|
3570
4111
|
let com = pathData[c];
|
|
4112
|
+
let comPrev = pathData[c-1];
|
|
4113
|
+
let comNext = pathData[c+1] || null;
|
|
3571
4114
|
let { type, values } = com;
|
|
3572
4115
|
|
|
3573
|
-
|
|
3574
|
-
|
|
4116
|
+
// zero length segments are simetimes used in icons for dots
|
|
4117
|
+
let isDot = comPrev.type.toLowerCase() ==='m' && !comNext;
|
|
4118
|
+
|
|
4119
|
+
let valsLen = values.length;
|
|
4120
|
+
p = { x: values[valsLen-2], y: values[valsLen-1] };
|
|
3575
4121
|
|
|
3576
4122
|
// skip lineto
|
|
3577
|
-
if (type === 'L' && p.x === p0.x && p.y === p0.y) {
|
|
4123
|
+
if (!isDot && type === 'L' && p.x === p0.x && p.y === p0.y) {
|
|
3578
4124
|
continue
|
|
3579
4125
|
}
|
|
3580
4126
|
|
|
4127
|
+
// skip minified zero length
|
|
4128
|
+
if (!isDot && (type === 'l' || type === 'v' || type === 'h')) {
|
|
4129
|
+
let noLength = type === 'l' ? (values.join('') === '00') : values[0] === 0;
|
|
4130
|
+
if(noLength) continue
|
|
4131
|
+
}
|
|
4132
|
+
|
|
3581
4133
|
pathDataN.push(com);
|
|
3582
4134
|
p0 = p;
|
|
3583
4135
|
}
|
|
@@ -3675,7 +4227,7 @@ function optimizeClosePath(pathData, removeFinalLineto = true, reorder = true) {
|
|
|
3675
4227
|
}
|
|
3676
4228
|
// use top most command
|
|
3677
4229
|
else {
|
|
3678
|
-
indices = indices.sort((a, b) => +a.y.toFixed(
|
|
4230
|
+
indices = indices.sort((a, b) => +a.y.toFixed(8) - +b.y.toFixed(8) || a.x - b.x);
|
|
3679
4231
|
newIndex = indices[0].index;
|
|
3680
4232
|
}
|
|
3681
4233
|
|
|
@@ -3683,7 +4235,7 @@ function optimizeClosePath(pathData, removeFinalLineto = true, reorder = true) {
|
|
|
3683
4235
|
pathData = newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
|
|
3684
4236
|
}
|
|
3685
4237
|
|
|
3686
|
-
M = { x: +pathData[0].values[0].toFixed(8), y: +pathData[0].values[1].toFixed(
|
|
4238
|
+
M = { x: +pathData[0].values[0].toFixed(8), y: +pathData[0].values[1].toFixed(8) };
|
|
3687
4239
|
|
|
3688
4240
|
len = pathData.length;
|
|
3689
4241
|
|
|
@@ -3797,123 +4349,161 @@ function addClosePathLineto(pathData) {
|
|
|
3797
4349
|
return pathData;
|
|
3798
4350
|
}
|
|
3799
4351
|
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
* make sure all command coordinates are absolute and
|
|
3803
|
-
* shorthands are converted to long notation
|
|
3804
|
-
*/
|
|
3805
|
-
function reversePathData(pathData, {
|
|
3806
|
-
arcToCubic = false,
|
|
3807
|
-
quadraticToCubic = false,
|
|
3808
|
-
toClockwise = false,
|
|
3809
|
-
returnD = false
|
|
4352
|
+
function refineAdjacentExtremes(pathData, {
|
|
4353
|
+
threshold = null, tolerance = 1
|
|
3810
4354
|
} = {}) {
|
|
3811
4355
|
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
let
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
4356
|
+
if (!threshold) {
|
|
4357
|
+
let bb = getPathDataBBox(pathData);
|
|
4358
|
+
threshold = (bb.width + bb.height) / 2 * 0.05;
|
|
4359
|
+
|
|
4360
|
+
}
|
|
4361
|
+
|
|
4362
|
+
let l = pathData.length;
|
|
4363
|
+
|
|
4364
|
+
for (let i = 0; i < l; i++) {
|
|
4365
|
+
let com = pathData[i];
|
|
4366
|
+
let { type, values, extreme, corner = false, dimA, p0, p } = com;
|
|
4367
|
+
let comN = pathData[i + 1] ? pathData[i + 1] : null;
|
|
4368
|
+
let comN2 = pathData[i + 2] ? pathData[i + 2] : null;
|
|
4369
|
+
|
|
4370
|
+
// check dist
|
|
4371
|
+
let diff = comN ? getDistAv(p, comN.p) : Infinity;
|
|
4372
|
+
let isCose = diff < threshold;
|
|
4373
|
+
|
|
4374
|
+
let diff2 = comN2 ? getDistAv(comN2.p, comN.p) : Infinity;
|
|
4375
|
+
let isCose2 = diff2 < threshold;
|
|
4376
|
+
|
|
4377
|
+
// next is extreme
|
|
4378
|
+
if (comN && type === 'C' && comN.type === 'C' && extreme && comN2 && comN2.extreme) {
|
|
4379
|
+
|
|
4380
|
+
if (isCose2 || isCose) {
|
|
4381
|
+
|
|
4382
|
+
// extrapolate
|
|
4383
|
+
let comEx = getCombinedByDominant(comN, comN2, threshold, tolerance, false);
|
|
3839
4384
|
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
4385
|
+
if (comEx.length === 1) {
|
|
4386
|
+
|
|
4387
|
+
pathData[i + 1] = null;
|
|
4388
|
+
comEx = comEx[0];
|
|
4389
|
+
|
|
4390
|
+
pathData[i + 2].values = [comEx.cp1.x, comEx.cp1.y, comEx.cp2.x, comEx.cp2.y, comEx.p.x, comEx.p.y];
|
|
4391
|
+
pathData[i + 2].cp1 = comEx.cp1;
|
|
4392
|
+
pathData[i + 2].cp2 = comEx.cp2;
|
|
4393
|
+
pathData[i + 2].p0 = comEx.p0;
|
|
4394
|
+
pathData[i + 2].p = comEx.p;
|
|
4395
|
+
pathData[i + 2].extreme = comEx.extreme;
|
|
4396
|
+
|
|
4397
|
+
i++;
|
|
4398
|
+
continue
|
|
4399
|
+
}
|
|
3847
4400
|
}
|
|
3848
|
-
|
|
3849
|
-
controlPoints.reverse();
|
|
4401
|
+
|
|
3850
4402
|
}
|
|
3851
|
-
// is arc
|
|
3852
|
-
else {
|
|
3853
4403
|
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
4404
|
+
// short after extreme
|
|
4405
|
+
|
|
4406
|
+
if (comN && type === 'C' && comN.type === 'C' && extreme ) {
|
|
4407
|
+
|
|
4408
|
+
if (isCose) {
|
|
4409
|
+
|
|
4410
|
+
let dx1 = (com.cp1.x - comN.p0.x);
|
|
4411
|
+
let dy1 = (com.cp1.y - comN.p0.y);
|
|
4412
|
+
|
|
4413
|
+
let horizontal = Math.abs(dy1) < Math.abs(dx1);
|
|
4414
|
+
|
|
4415
|
+
let pN = comN.p;
|
|
4416
|
+
let ptI;
|
|
4417
|
+
let t = 1;
|
|
4418
|
+
|
|
4419
|
+
let area0 = getPolygonArea([com.p0, com.p , comN.p]);
|
|
4420
|
+
// cpts area
|
|
4421
|
+
let area1 = getPolygonArea([com.p0, com.cp1, com.cp2, com.p]);
|
|
4422
|
+
|
|
4423
|
+
// sign change: is corner => skip
|
|
4424
|
+
if ( (area0<0 && area1>0) || (area0>0 && area1<0)) {
|
|
4425
|
+
|
|
4426
|
+
continue;
|
|
4427
|
+
}
|
|
4428
|
+
|
|
4429
|
+
|
|
4430
|
+
if (comN.extreme) {
|
|
4431
|
+
|
|
4432
|
+
// extend cp2
|
|
4433
|
+
if (horizontal) {
|
|
4434
|
+
t = Math.abs(Math.abs(comN.cp2.x - comN.p.x) / Math.abs(com.cp2.x - com.p.x));
|
|
4435
|
+
t = Math.min(1, t);
|
|
4436
|
+
|
|
4437
|
+
ptI = interpolate(comN.p, com.cp2, 1 + t);
|
|
4438
|
+
com.cp2.x = ptI.x;
|
|
4439
|
+
|
|
4440
|
+
}
|
|
4441
|
+
else {
|
|
4442
|
+
|
|
4443
|
+
t = Math.abs(Math.abs(comN.cp2.y - comN.p.y) / Math.abs(com.cp2.y - com.p.y));
|
|
4444
|
+
t = Math.min(1, t);
|
|
4445
|
+
|
|
4446
|
+
ptI = interpolate(comN.p, com.cp2, 1 + t);
|
|
4447
|
+
com.cp2.y = ptI.y;
|
|
4448
|
+
}
|
|
4449
|
+
|
|
4450
|
+
pathData[i + 1].values = [com.cp1.x, com.cp1.y, com.cp2.x, com.cp2.y, pN.x, pN.y];
|
|
4451
|
+
pathData[i + 1].cp1 = com.cp1;
|
|
4452
|
+
pathData[i + 1].cp2 = com.cp2;
|
|
4453
|
+
pathData[i + 1].p0 = com.p0;
|
|
4454
|
+
pathData[i + 1].p = pN;
|
|
4455
|
+
pathData[i + 1].extreme = true;
|
|
4456
|
+
|
|
4457
|
+
// nullify 1st
|
|
4458
|
+
pathData[i] = null;
|
|
4459
|
+
continue
|
|
4460
|
+
|
|
4461
|
+
}
|
|
4462
|
+
|
|
4463
|
+
}
|
|
3857
4464
|
}
|
|
3858
|
-
return { controlPoints, endPoints };
|
|
3859
|
-
};
|
|
3860
4465
|
|
|
3861
|
-
|
|
3862
|
-
|
|
4466
|
+
/*
|
|
4467
|
+
*/
|
|
3863
4468
|
|
|
3864
|
-
let closed =
|
|
3865
|
-
pathData[pathData.length - 1].type.toLowerCase() === "z" ? true : false;
|
|
3866
|
-
if (closed) {
|
|
3867
|
-
// add lineto closing space between Z and M
|
|
3868
|
-
pathData = addClosePathLineto(pathData);
|
|
3869
|
-
// remove Z closepath
|
|
3870
|
-
pathData.pop();
|
|
3871
4469
|
}
|
|
3872
4470
|
|
|
3873
|
-
//
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
let M = closed
|
|
3877
|
-
? pathData[0]
|
|
3878
|
-
: {
|
|
3879
|
-
type: "M",
|
|
3880
|
-
values: [valuesLast[valuesLastL - 2], valuesLast[valuesLastL - 1]]
|
|
3881
|
-
};
|
|
3882
|
-
// starting M stays the same – unless the path is not closed
|
|
3883
|
-
pathDataNew.push(M);
|
|
4471
|
+
// remove commands
|
|
4472
|
+
pathData = pathData.filter(Boolean);
|
|
4473
|
+
l = pathData.length;
|
|
3884
4474
|
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
let com = pathData[i];
|
|
3889
|
-
let type = com.type;
|
|
3890
|
-
let values = com.values;
|
|
3891
|
-
let comPrev = pathData[i - 1];
|
|
3892
|
-
let typePrev = comPrev.type;
|
|
3893
|
-
let valuesPrev = comPrev.values;
|
|
4475
|
+
/**
|
|
4476
|
+
* refine closing commands
|
|
4477
|
+
*/
|
|
3894
4478
|
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
4479
|
+
let closed = pathData[l - 1].type.toLowerCase() === 'z';
|
|
4480
|
+
let lastIdx = closed ? l - 2 : l - 1;
|
|
4481
|
+
let lastCom = pathData[lastIdx];
|
|
4482
|
+
let penultimateCom = pathData[lastIdx - 1] || null;
|
|
4483
|
+
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
3898
4484
|
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
4485
|
+
let dec = 8;
|
|
4486
|
+
let lastVals = lastCom.values.slice(-2);
|
|
4487
|
+
let isClosingTo = +lastVals[0].toFixed(dec) === +M.x.toFixed(dec) && +lastVals[1].toFixed(dec) === +M.y.toFixed(dec);
|
|
4488
|
+
let fistExt = pathData[1].type === 'C' && pathData[1].extreme ? pathData[1] : null;
|
|
4489
|
+
|
|
4490
|
+
let diff = getDistAv(lastCom.p0, lastCom.p);
|
|
4491
|
+
let isCose = diff < threshold;
|
|
4492
|
+
|
|
4493
|
+
if (penultimateCom && penultimateCom.type === 'C' && isCose && isClosingTo && fistExt) {
|
|
4494
|
+
|
|
4495
|
+
let comEx = getCombinedByDominant(penultimateCom, lastCom, threshold, tolerance, false);
|
|
4496
|
+
|
|
4497
|
+
if (comEx.length === 1) {
|
|
4498
|
+
pathData[lastIdx - 1] = comEx[0];
|
|
4499
|
+
pathData[lastIdx] = null;
|
|
4500
|
+
pathData = pathData.filter(Boolean);
|
|
4501
|
+
}
|
|
3907
4502
|
|
|
3908
|
-
// add previously removed Z close path
|
|
3909
|
-
if (closed) {
|
|
3910
|
-
pathDataNew.push({
|
|
3911
|
-
type: "z",
|
|
3912
|
-
values: []
|
|
3913
|
-
});
|
|
3914
4503
|
}
|
|
3915
4504
|
|
|
3916
|
-
return
|
|
4505
|
+
return pathData
|
|
4506
|
+
|
|
3917
4507
|
}
|
|
3918
4508
|
|
|
3919
4509
|
function removeEmptySVGEls(svg) {
|
|
@@ -3938,7 +4528,7 @@ function cleanUpSVG(svgMarkup, {
|
|
|
3938
4528
|
.querySelector("svg");
|
|
3939
4529
|
|
|
3940
4530
|
|
|
3941
|
-
let allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class'];
|
|
4531
|
+
let allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin'];
|
|
3942
4532
|
removeExcludedAttribues(svg, allowed);
|
|
3943
4533
|
|
|
3944
4534
|
let removeEls = ['metadata', 'script'];
|
|
@@ -4009,6 +4599,156 @@ function stringifySVG(svg){
|
|
|
4009
4599
|
return markup
|
|
4010
4600
|
}
|
|
4011
4601
|
|
|
4602
|
+
function refineRoundedCorners(pathData, {
|
|
4603
|
+
threshold = 0,
|
|
4604
|
+
tolerance = 1
|
|
4605
|
+
} = {}) {
|
|
4606
|
+
|
|
4607
|
+
let l = pathData.length;
|
|
4608
|
+
|
|
4609
|
+
// add fist command
|
|
4610
|
+
let pathDataN = [pathData[0]];
|
|
4611
|
+
|
|
4612
|
+
let isClosed = pathData[l - 1].type.toLowerCase() === 'z';
|
|
4613
|
+
let lastOff = isClosed ? 2 : 1;
|
|
4614
|
+
|
|
4615
|
+
let comLast = pathData[l - lastOff];
|
|
4616
|
+
let lastIsLine = comLast.type === 'L';
|
|
4617
|
+
let lastIsBez = comLast.type === 'C';
|
|
4618
|
+
let firstIsLine = pathData[1].type === 'L';
|
|
4619
|
+
let firstIsBez = pathData[1].type === 'C';
|
|
4620
|
+
|
|
4621
|
+
let normalizeClose = isClosed && firstIsBez;
|
|
4622
|
+
|
|
4623
|
+
// normalize closepath to lineto
|
|
4624
|
+
if (normalizeClose) {
|
|
4625
|
+
pathData[l - 1].values = pathData[0].values;
|
|
4626
|
+
pathData[l - 1].type = 'L';
|
|
4627
|
+
lastIsLine = true;
|
|
4628
|
+
}
|
|
4629
|
+
|
|
4630
|
+
for (let i = 1; i < l; i++) {
|
|
4631
|
+
let com = pathData[i];
|
|
4632
|
+
let { type } = com;
|
|
4633
|
+
let comN = pathData[i + 1] ? pathData[i + 1] : null;
|
|
4634
|
+
|
|
4635
|
+
// search small cubic segments enclosed by linetos
|
|
4636
|
+
if ((type === 'L' && comN && comN.type === 'C') ||
|
|
4637
|
+
(type === 'C' && comN && comN.type === 'L')
|
|
4638
|
+
|
|
4639
|
+
) {
|
|
4640
|
+
let comL0 = com;
|
|
4641
|
+
let comL1 = null;
|
|
4642
|
+
let comBez = [];
|
|
4643
|
+
let offset = 0;
|
|
4644
|
+
|
|
4645
|
+
// start to end
|
|
4646
|
+
if (i === 1 && firstIsBez && lastIsLine) {
|
|
4647
|
+
comBez = [pathData[1]];
|
|
4648
|
+
comL0 = pathData[l - 1];
|
|
4649
|
+
comL1 = comN;
|
|
4650
|
+
|
|
4651
|
+
}
|
|
4652
|
+
|
|
4653
|
+
// closing corner to start
|
|
4654
|
+
if (isClosed && lastIsBez && firstIsLine && i === l - lastOff - 1) {
|
|
4655
|
+
comL1 = pathData[1];
|
|
4656
|
+
comBez = [pathData[l - lastOff]];
|
|
4657
|
+
|
|
4658
|
+
}
|
|
4659
|
+
|
|
4660
|
+
for (let j = i + 1; j < l; j++) {
|
|
4661
|
+
let comN = pathData[j] ? pathData[j] : null;
|
|
4662
|
+
let comPrev = pathData[j - 1];
|
|
4663
|
+
|
|
4664
|
+
if (comPrev.type === 'C') {
|
|
4665
|
+
comBez.push(comPrev);
|
|
4666
|
+
}
|
|
4667
|
+
|
|
4668
|
+
if (comN.type === 'L' && comPrev.type === 'C') {
|
|
4669
|
+
comL1 = comN;
|
|
4670
|
+
break;
|
|
4671
|
+
}
|
|
4672
|
+
offset++;
|
|
4673
|
+
}
|
|
4674
|
+
|
|
4675
|
+
if (comL1) {
|
|
4676
|
+
|
|
4677
|
+
// linetos
|
|
4678
|
+
let len1 = getDistAv(comL0.p0, comL0.p);
|
|
4679
|
+
let len2 = getDistAv(comL1.p0, comL1.p);
|
|
4680
|
+
|
|
4681
|
+
// bezier
|
|
4682
|
+
|
|
4683
|
+
let comBezLen = comBez.length;
|
|
4684
|
+
let len3 = getDistAv(comBez[0].p0, comBez[comBezLen - 1].p);
|
|
4685
|
+
|
|
4686
|
+
// check concaveness by area sign change
|
|
4687
|
+
let area1 = getPolygonArea([comL0.p0, comL0.p, comL1.p0, comL1.p], false);
|
|
4688
|
+
let area2 = getPolygonArea([comBez[0].p0, comBez[0].cp1, comBez[0].cp2, comBez[0].p], false);
|
|
4689
|
+
|
|
4690
|
+
let signChange = (area1 < 0 && area2 > 0) || (area1 > 0 && area2 < 0);
|
|
4691
|
+
|
|
4692
|
+
if (comBez && !signChange && len3 < threshold && len1 > len3 && len2 > len3) {
|
|
4693
|
+
|
|
4694
|
+
let ptQ = checkLineIntersection(comL0.p0, comL0.p, comL1.p0, comL1.p, false);
|
|
4695
|
+
if (ptQ) {
|
|
4696
|
+
|
|
4697
|
+
/*
|
|
4698
|
+
let dist1 = getDistAv(ptQ, comL0.p)
|
|
4699
|
+
let dist2 = getDistAv(ptQ, comL1.p0)
|
|
4700
|
+
let diff = Math.abs(dist1-dist2)
|
|
4701
|
+
let rat = diff/Math.max(dist1, dist2)
|
|
4702
|
+
console.log('rat', rat);
|
|
4703
|
+
*/
|
|
4704
|
+
|
|
4705
|
+
/*
|
|
4706
|
+
// adjust curve start and end to meet original
|
|
4707
|
+
let t = 1
|
|
4708
|
+
|
|
4709
|
+
let p0_2 = pointAtT([ptQ, comL0.p], t)
|
|
4710
|
+
|
|
4711
|
+
comL0.p = p0_2
|
|
4712
|
+
comL0.values = [p0_2.x, p0_2.y]
|
|
4713
|
+
|
|
4714
|
+
let p_2 = pointAtT([ptQ, comL1.p0], t)
|
|
4715
|
+
|
|
4716
|
+
comL1.p0 = p_2
|
|
4717
|
+
|
|
4718
|
+
*/
|
|
4719
|
+
|
|
4720
|
+
let comQ = { type: 'Q', values: [ptQ.x, ptQ.y, comL1.p0.x, comL1.p0.y] };
|
|
4721
|
+
comQ.p0 = comL0.p;
|
|
4722
|
+
comQ.cp1 = ptQ;
|
|
4723
|
+
comQ.p = comL1.p0;
|
|
4724
|
+
|
|
4725
|
+
// add quadratic command
|
|
4726
|
+
pathDataN.push(comL0, comQ);
|
|
4727
|
+
i += offset;
|
|
4728
|
+
continue;
|
|
4729
|
+
}
|
|
4730
|
+
}
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
|
|
4734
|
+
// skip last lineto
|
|
4735
|
+
if (normalizeClose && i === l - 1 && type === 'L') {
|
|
4736
|
+
continue
|
|
4737
|
+
}
|
|
4738
|
+
|
|
4739
|
+
pathDataN.push(com);
|
|
4740
|
+
|
|
4741
|
+
}
|
|
4742
|
+
|
|
4743
|
+
// revert close path normalization
|
|
4744
|
+
if (normalizeClose) {
|
|
4745
|
+
pathDataN.push({ type: 'Z', values: [] });
|
|
4746
|
+
}
|
|
4747
|
+
|
|
4748
|
+
return pathDataN;
|
|
4749
|
+
|
|
4750
|
+
}
|
|
4751
|
+
|
|
4012
4752
|
function svgPathSimplify(input = '', {
|
|
4013
4753
|
|
|
4014
4754
|
// return svg markup or object
|
|
@@ -4027,15 +4767,20 @@ function svgPathSimplify(input = '', {
|
|
|
4027
4767
|
|
|
4028
4768
|
simplifyBezier = true,
|
|
4029
4769
|
optimizeOrder = true,
|
|
4770
|
+
removeZeroLength = true,
|
|
4030
4771
|
removeColinear = true,
|
|
4031
4772
|
flatBezierToLinetos = true,
|
|
4032
4773
|
revertToQuadratics = true,
|
|
4033
4774
|
|
|
4775
|
+
refineExtremes = true,
|
|
4776
|
+
refineCorners = false,
|
|
4777
|
+
|
|
4034
4778
|
keepExtremes = true,
|
|
4035
4779
|
keepCorners = true,
|
|
4036
4780
|
extrapolateDominant = true,
|
|
4037
4781
|
keepInflections = false,
|
|
4038
4782
|
addExtremes = false,
|
|
4783
|
+
removeOrphanSubpaths = false,
|
|
4039
4784
|
|
|
4040
4785
|
// svg path optimizations
|
|
4041
4786
|
decimals = 3,
|
|
@@ -4049,6 +4794,7 @@ function svgPathSimplify(input = '', {
|
|
|
4049
4794
|
mergePaths = false,
|
|
4050
4795
|
removeHidden = true,
|
|
4051
4796
|
removeUnused = true,
|
|
4797
|
+
shapesToPaths = true,
|
|
4052
4798
|
|
|
4053
4799
|
} = {}) {
|
|
4054
4800
|
|
|
@@ -4073,24 +4819,43 @@ function svgPathSimplify(input = '', {
|
|
|
4073
4819
|
*/
|
|
4074
4820
|
|
|
4075
4821
|
// original size
|
|
4076
|
-
svgSize = new Blob([input]).size;
|
|
4077
4822
|
|
|
4078
|
-
|
|
4823
|
+
svgSize = input.length;
|
|
4824
|
+
|
|
4825
|
+
// mode:0 – single path
|
|
4079
4826
|
if (!mode) {
|
|
4080
4827
|
if (inputType === 'pathDataString') {
|
|
4081
4828
|
d = input;
|
|
4082
4829
|
} else if (inputType === 'polyString') {
|
|
4083
4830
|
d = 'M' + input;
|
|
4084
4831
|
}
|
|
4832
|
+
else if (inputType === 'pathData') {
|
|
4833
|
+
d = input;
|
|
4834
|
+
|
|
4835
|
+
// stringify to compare lengths
|
|
4836
|
+
|
|
4837
|
+
let dStr = d.map(com=>{return `${com.type} ${com.values.join(' ')}`}).join(' ') ;
|
|
4838
|
+
svgSize = dStr.length;
|
|
4839
|
+
|
|
4840
|
+
}
|
|
4841
|
+
|
|
4085
4842
|
paths.push({ d, el: null });
|
|
4086
4843
|
}
|
|
4087
|
-
// process svg
|
|
4844
|
+
// mode:1 – process complete svg DOM
|
|
4088
4845
|
else {
|
|
4089
4846
|
|
|
4090
4847
|
let returnDom = true;
|
|
4091
4848
|
svg = cleanUpSVG(input, { returnDom, removeHidden, removeUnused }
|
|
4092
4849
|
);
|
|
4093
4850
|
|
|
4851
|
+
if (shapesToPaths) {
|
|
4852
|
+
let shapes = svg.querySelectorAll('polygon, polyline, line, rect, circle, ellipse');
|
|
4853
|
+
shapes.forEach(shape => {
|
|
4854
|
+
let path = shapeElToPath(shape);
|
|
4855
|
+
shape.replaceWith(path);
|
|
4856
|
+
});
|
|
4857
|
+
}
|
|
4858
|
+
|
|
4094
4859
|
// collect paths
|
|
4095
4860
|
let pathEls = svg.querySelectorAll('path');
|
|
4096
4861
|
pathEls.forEach(path => {
|
|
@@ -4100,6 +4865,7 @@ function svgPathSimplify(input = '', {
|
|
|
4100
4865
|
|
|
4101
4866
|
/**
|
|
4102
4867
|
* process all paths
|
|
4868
|
+
* try simplifications and removals
|
|
4103
4869
|
*/
|
|
4104
4870
|
|
|
4105
4871
|
// SVG optimization options
|
|
@@ -4112,34 +4878,35 @@ function svgPathSimplify(input = '', {
|
|
|
4112
4878
|
// combinded path data for SVGs with mergePaths enabled
|
|
4113
4879
|
let pathData_merged = [];
|
|
4114
4880
|
|
|
4115
|
-
paths.
|
|
4116
|
-
let { d, el } = path;
|
|
4881
|
+
for (let i = 0, l = paths.length; l && i < l; i++) {
|
|
4117
4882
|
|
|
4118
|
-
let
|
|
4883
|
+
let path = paths[i];
|
|
4884
|
+
let { d, el } = path;
|
|
4119
4885
|
|
|
4120
|
-
|
|
4121
|
-
let pathData = JSON.parse(JSON.stringify(pathDataO));
|
|
4886
|
+
let pathData = parsePathDataNormalized(d, { quadraticToCubic, toAbsolute, arcToCubic });
|
|
4122
4887
|
|
|
4123
4888
|
// count commands for evaluation
|
|
4124
|
-
let comCount =
|
|
4889
|
+
let comCount = pathData.length;
|
|
4890
|
+
|
|
4891
|
+
if (removeOrphanSubpaths) pathData = removeOrphanedM(pathData);
|
|
4125
4892
|
|
|
4126
4893
|
/**
|
|
4127
4894
|
* get sub paths
|
|
4128
4895
|
*/
|
|
4129
4896
|
let subPathArr = splitSubpaths(pathData);
|
|
4897
|
+
let lenSub = subPathArr.length;
|
|
4130
4898
|
|
|
4131
4899
|
// cleaned up pathData
|
|
4132
|
-
let pathDataArrN = [];
|
|
4133
4900
|
|
|
4134
|
-
|
|
4901
|
+
// reset array
|
|
4902
|
+
let pathDataFlat = [];
|
|
4135
4903
|
|
|
4136
|
-
|
|
4904
|
+
for (let i = 0; i < lenSub; i++) {
|
|
4137
4905
|
|
|
4138
|
-
|
|
4139
|
-
if (reverse) pathDataSub = reversePathData(pathDataSub);
|
|
4906
|
+
let pathDataSub = subPathArr[i];
|
|
4140
4907
|
|
|
4141
4908
|
// remove zero length linetos
|
|
4142
|
-
if (removeColinear) pathDataSub = removeZeroLengthLinetos(pathDataSub);
|
|
4909
|
+
if (removeColinear || removeZeroLength) pathDataSub = removeZeroLengthLinetos(pathDataSub);
|
|
4143
4910
|
|
|
4144
4911
|
// add extremes
|
|
4145
4912
|
|
|
@@ -4149,50 +4916,63 @@ function svgPathSimplify(input = '', {
|
|
|
4149
4916
|
// sort to top left
|
|
4150
4917
|
if (optimizeOrder) pathDataSub = pathDataToTopLeft(pathDataSub);
|
|
4151
4918
|
|
|
4152
|
-
// remove colinear
|
|
4153
|
-
if (removeColinear) pathDataSub = pathDataRemoveColinear(pathDataSub, tolerance, flatBezierToLinetos);
|
|
4919
|
+
// Preprocessing: remove colinear - ignore flat beziers (removed later)
|
|
4920
|
+
if (removeColinear) pathDataSub = pathDataRemoveColinear(pathDataSub, { tolerance, flatBezierToLinetos: false });
|
|
4154
4921
|
|
|
4155
4922
|
// analyze pathdata to add info about signicant properties such as extremes, corners
|
|
4156
4923
|
let pathDataPlus = analyzePathData(pathDataSub);
|
|
4157
4924
|
|
|
4158
4925
|
// simplify beziers
|
|
4159
4926
|
let { pathData, bb, dimA } = pathDataPlus;
|
|
4160
|
-
|
|
4161
4927
|
pathData = simplifyBezier ? simplifyPathDataCubic(pathData, { simplifyBezier, keepInflections, keepExtremes, keepCorners, extrapolateDominant, revertToQuadratics, tolerance, reverse }) : pathData;
|
|
4162
4928
|
|
|
4929
|
+
// refine extremes
|
|
4930
|
+
if (refineExtremes) {
|
|
4931
|
+
let thresholdEx = (bb.width + bb.height) / 2 * 0.05;
|
|
4932
|
+
pathData = refineAdjacentExtremes(pathData, { threshold: thresholdEx, tolerance });
|
|
4933
|
+
}
|
|
4934
|
+
|
|
4163
4935
|
// cubic to arcs
|
|
4164
4936
|
if (cubicToArc) {
|
|
4165
4937
|
|
|
4166
|
-
let thresh =
|
|
4938
|
+
let thresh = 1;
|
|
4167
4939
|
|
|
4168
|
-
|
|
4940
|
+
for(let c=0, l=pathData.length; c<l; c++){
|
|
4941
|
+
let com = pathData[c];
|
|
4169
4942
|
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
4170
4943
|
if (type === 'C') {
|
|
4171
4944
|
|
|
4172
4945
|
let comA = cubicCommandToArc(p0, cp1, cp2, p, thresh);
|
|
4173
4946
|
if (comA.isArc) pathData[c] = comA.com;
|
|
4174
|
-
|
|
4175
4947
|
}
|
|
4176
|
-
}
|
|
4948
|
+
}
|
|
4177
4949
|
|
|
4178
4950
|
// combine adjacent cubics
|
|
4179
4951
|
pathData = combineArcs(pathData);
|
|
4180
4952
|
|
|
4181
4953
|
}
|
|
4182
4954
|
|
|
4955
|
+
// post processing: remove flat beziers
|
|
4956
|
+
if (removeColinear && flatBezierToLinetos) {
|
|
4957
|
+
pathData = pathDataRemoveColinear(pathData, { tolerance, flatBezierToLinetos });
|
|
4958
|
+
}
|
|
4959
|
+
|
|
4960
|
+
// refine corners
|
|
4961
|
+
if(refineCorners){
|
|
4962
|
+
let threshold = (bb.width + bb.height) / 2 * 0.1;
|
|
4963
|
+
pathData = refineRoundedCorners(pathData, { threshold, tolerance });
|
|
4964
|
+
|
|
4965
|
+
}
|
|
4966
|
+
|
|
4183
4967
|
// simplify to quadratics
|
|
4184
4968
|
if (revertToQuadratics) {
|
|
4185
|
-
|
|
4969
|
+
for(let c=0, l=pathData.length; c<l; c++){
|
|
4970
|
+
let com = pathData[c];
|
|
4186
4971
|
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
4187
4972
|
if (type === 'C') {
|
|
4188
4973
|
|
|
4189
4974
|
let comQ = revertCubicQuadratic(p0, cp1, cp2, p);
|
|
4190
4975
|
if (comQ.type === 'Q') {
|
|
4191
|
-
/*
|
|
4192
|
-
comQ.p0 = com.p0
|
|
4193
|
-
comQ.cp1 = {x:comQ.values[0], y:comQ.values[1]}
|
|
4194
|
-
comQ.p = com.p
|
|
4195
|
-
*/
|
|
4196
4976
|
comQ.extreme = com.extreme;
|
|
4197
4977
|
comQ.corner = com.corner;
|
|
4198
4978
|
comQ.dimA = com.dimA;
|
|
@@ -4200,20 +4980,25 @@ function svgPathSimplify(input = '', {
|
|
|
4200
4980
|
pathData[c] = comQ;
|
|
4201
4981
|
}
|
|
4202
4982
|
}
|
|
4203
|
-
}
|
|
4983
|
+
}
|
|
4204
4984
|
}
|
|
4205
4985
|
|
|
4206
4986
|
// optimize close path
|
|
4207
4987
|
if (optimizeOrder) pathData = optimizeClosePath(pathData);
|
|
4208
4988
|
|
|
4209
|
-
// poly
|
|
4210
|
-
|
|
4211
4989
|
// update
|
|
4212
|
-
|
|
4990
|
+
pathDataFlat.push(...pathData);
|
|
4991
|
+
|
|
4213
4992
|
}
|
|
4214
4993
|
|
|
4215
4994
|
// flatten compound paths
|
|
4216
|
-
pathData =
|
|
4995
|
+
pathData = pathDataFlat;
|
|
4996
|
+
|
|
4997
|
+
if (autoAccuracy) {
|
|
4998
|
+
decimals = detectAccuracy(pathData);
|
|
4999
|
+
pathOptions.decimals = decimals;
|
|
5000
|
+
|
|
5001
|
+
}
|
|
4217
5002
|
|
|
4218
5003
|
// collect for merged svg paths
|
|
4219
5004
|
if (el && mergePaths) {
|
|
@@ -4222,24 +5007,19 @@ function svgPathSimplify(input = '', {
|
|
|
4222
5007
|
// single output
|
|
4223
5008
|
else {
|
|
4224
5009
|
|
|
4225
|
-
/**
|
|
4226
|
-
* detect accuracy
|
|
4227
|
-
*/
|
|
4228
|
-
if (autoAccuracy) {
|
|
4229
|
-
decimals = detectAccuracy(pathData);
|
|
4230
|
-
}
|
|
4231
|
-
|
|
4232
5010
|
// optimize path data
|
|
4233
5011
|
pathData = convertPathData(pathData, pathOptions);
|
|
4234
5012
|
|
|
4235
5013
|
// remove zero-length segments introduced by rounding
|
|
4236
|
-
pathData =
|
|
5014
|
+
pathData = removeZeroLengthLinetos(pathData);
|
|
4237
5015
|
|
|
4238
5016
|
// compare command count
|
|
4239
5017
|
let comCountS = pathData.length;
|
|
4240
5018
|
|
|
4241
5019
|
let dOpt = pathDataToD(pathData, minifyD);
|
|
4242
|
-
|
|
5020
|
+
|
|
5021
|
+
svgSizeOpt = dOpt.length;
|
|
5022
|
+
|
|
4243
5023
|
compression = +(100 / svgSize * (svgSizeOpt)).toFixed(2);
|
|
4244
5024
|
|
|
4245
5025
|
path.d = dOpt;
|
|
@@ -4255,19 +5035,19 @@ function svgPathSimplify(input = '', {
|
|
|
4255
5035
|
// apply new path for svgs
|
|
4256
5036
|
if (el) el.setAttribute('d', dOpt);
|
|
4257
5037
|
}
|
|
4258
|
-
}
|
|
4259
|
-
|
|
5038
|
+
}
|
|
4260
5039
|
/**
|
|
4261
5040
|
* stringify new SVG
|
|
4262
5041
|
*/
|
|
4263
5042
|
if (mode) {
|
|
4264
5043
|
|
|
4265
5044
|
if (pathData_merged.length) {
|
|
5045
|
+
|
|
4266
5046
|
// optimize path data
|
|
4267
5047
|
let pathData = convertPathData(pathData_merged, pathOptions);
|
|
4268
5048
|
|
|
4269
5049
|
// remove zero-length segments introduced by rounding
|
|
4270
|
-
pathData =
|
|
5050
|
+
pathData = removeZeroLengthLinetos(pathData);
|
|
4271
5051
|
|
|
4272
5052
|
let dOpt = pathDataToD(pathData, minifyD);
|
|
4273
5053
|
|
|
@@ -4285,7 +5065,8 @@ function svgPathSimplify(input = '', {
|
|
|
4285
5065
|
}
|
|
4286
5066
|
|
|
4287
5067
|
svg = stringifySVG(svg);
|
|
4288
|
-
|
|
5068
|
+
|
|
5069
|
+
svgSizeOpt = svg.length;
|
|
4289
5070
|
|
|
4290
5071
|
compression = +(100 / svgSize * (svgSizeOpt)).toFixed(2);
|
|
4291
5072
|
|
|
@@ -4306,109 +5087,16 @@ function svgPathSimplify(input = '', {
|
|
|
4306
5087
|
|
|
4307
5088
|
}
|
|
4308
5089
|
|
|
4309
|
-
function simplifyPathDataCubic(pathData, {
|
|
4310
|
-
keepExtremes = true,
|
|
4311
|
-
keepInflections = true,
|
|
4312
|
-
keepCorners = true,
|
|
4313
|
-
extrapolateDominant = true,
|
|
4314
|
-
tolerance = 1,
|
|
4315
|
-
reverse = false
|
|
4316
|
-
} = {}) {
|
|
4317
|
-
|
|
4318
|
-
let pathDataN = [pathData[0]];
|
|
4319
|
-
|
|
4320
|
-
for (let i = 2, l = pathData.length; l && i <= l; i++) {
|
|
4321
|
-
let com = pathData[i - 1];
|
|
4322
|
-
let comN = i < l ? pathData[i] : null;
|
|
4323
|
-
let typeN = comN?.type || null;
|
|
4324
|
-
|
|
4325
|
-
let isDirChange = com?.directionChange || null;
|
|
4326
|
-
let isDirChangeN = comN?.directionChange || null;
|
|
4327
|
-
|
|
4328
|
-
let { type, values, p0, p, cp1 = null, cp2 = null, extreme = false, corner = false, dimA = 0 } = com;
|
|
4329
|
-
|
|
4330
|
-
// next is also cubic
|
|
4331
|
-
if (type === 'C' && typeN === 'C') {
|
|
4332
|
-
|
|
4333
|
-
// cannot be combined as crossing extremes or corners
|
|
4334
|
-
if (
|
|
4335
|
-
(keepInflections && isDirChangeN) ||
|
|
4336
|
-
(keepCorners && corner) ||
|
|
4337
|
-
(!isDirChange && keepExtremes && extreme)
|
|
4338
|
-
) {
|
|
4339
|
-
|
|
4340
|
-
pathDataN.push(com);
|
|
4341
|
-
}
|
|
4342
|
-
|
|
4343
|
-
// try simplification
|
|
4344
|
-
else {
|
|
4345
|
-
|
|
4346
|
-
let combined = combineCubicPairs(com, comN, extrapolateDominant, tolerance);
|
|
4347
|
-
let error = 0;
|
|
4348
|
-
|
|
4349
|
-
// combining successful! try next segment
|
|
4350
|
-
if (combined.length === 1) {
|
|
4351
|
-
com = combined[0];
|
|
4352
|
-
let offset = 1;
|
|
4353
|
-
|
|
4354
|
-
// add cumulative error to prevent distortions
|
|
4355
|
-
error += com.error;
|
|
4356
|
-
|
|
4357
|
-
// find next candidates
|
|
4358
|
-
for (let n = i + 1; error < tolerance && n < l; n++) {
|
|
4359
|
-
let comN = pathData[n];
|
|
4360
|
-
if (comN.type !== 'C' ||
|
|
4361
|
-
(
|
|
4362
|
-
(keepInflections && comN.directionChange) ||
|
|
4363
|
-
(keepCorners && com.corner) ||
|
|
4364
|
-
(keepExtremes && com.extreme)
|
|
4365
|
-
)
|
|
4366
|
-
) {
|
|
4367
|
-
break
|
|
4368
|
-
}
|
|
4369
|
-
|
|
4370
|
-
let combined = combineCubicPairs(com, comN, extrapolateDominant, tolerance);
|
|
4371
|
-
if (combined.length === 1) {
|
|
4372
|
-
// add cumulative error to prevent distortions
|
|
4373
|
-
|
|
4374
|
-
error += combined[0].error * 0.5;
|
|
4375
|
-
offset++;
|
|
4376
|
-
}
|
|
4377
|
-
com = combined[0];
|
|
4378
|
-
}
|
|
4379
|
-
|
|
4380
|
-
pathDataN.push(com);
|
|
4381
|
-
|
|
4382
|
-
if (i < l) {
|
|
4383
|
-
i += offset;
|
|
4384
|
-
}
|
|
4385
|
-
|
|
4386
|
-
} else {
|
|
4387
|
-
pathDataN.push(com);
|
|
4388
|
-
}
|
|
4389
|
-
}
|
|
4390
|
-
|
|
4391
|
-
} // end of bezier command
|
|
4392
|
-
|
|
4393
|
-
// other commands
|
|
4394
|
-
else {
|
|
4395
|
-
pathDataN.push(com);
|
|
4396
|
-
}
|
|
4397
|
-
|
|
4398
|
-
} // end command loop
|
|
4399
|
-
|
|
4400
|
-
// reverse back
|
|
4401
|
-
if (reverse) pathDataN = reversePathData(pathDataN);
|
|
4402
|
-
|
|
4403
|
-
return pathDataN
|
|
4404
|
-
}
|
|
4405
|
-
|
|
4406
5090
|
const {
|
|
4407
5091
|
abs, acos, asin, atan, atan2, ceil, cos, exp, floor,
|
|
4408
5092
|
log, hypot, max, min, pow, random, round, sin, sqrt, tan, PI
|
|
4409
5093
|
} = Math;
|
|
4410
5094
|
|
|
4411
|
-
|
|
5095
|
+
/*
|
|
5096
|
+
import {XMLSerializerPoly, DOMParserPoly} from './dom_polyfills';
|
|
5097
|
+
export {XMLSerializerPoly as XMLSerializerPoly};
|
|
5098
|
+
export {DOMParserPoly as DOMParserPoly};
|
|
5099
|
+
*/
|
|
4412
5100
|
|
|
4413
5101
|
// IIFE
|
|
4414
5102
|
if (typeof window !== 'undefined') {
|