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