svg-path-simplify 0.0.5 → 0.0.8
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 +193 -11
- package/dist/svg-path-simplify.esm.js +890 -263
- package/dist/svg-path-simplify.esm.min.js +1 -1
- package/dist/svg-path-simplify.js +889 -263
- package/dist/svg-path-simplify.min.js +1 -1
- package/dist/svg-path-simplify.node.js +889 -263
- package/dist/svg-path-simplify.node.min.js +1 -1
- package/index.html +16 -8
- package/package.json +6 -3
- package/src/detect_input.js +2 -0
- package/src/dom-polyfill.js +29 -0
- package/src/dom-polyfill_back.js +22 -0
- package/src/index.js +11 -5
- package/src/pathData_simplify_cubic.js +1 -37
- package/src/pathData_simplify_cubic_extrapolate.js +45 -27
- package/src/pathSimplify-main.js +163 -68
- package/src/svgii/geometry.js +8 -155
- package/src/svgii/geometry_flatness.js +101 -0
- package/src/svgii/pathData_analyze.js +11 -596
- package/src/svgii/pathData_convert.js +7 -3
- package/src/svgii/pathData_parse_els.js +239 -0
- package/src/svgii/pathData_remove_collinear.js +13 -3
- package/src/svgii/pathData_remove_orphaned.js +20 -0
- package/src/svgii/pathData_remove_zerolength.js +28 -2
- package/src/svgii/pathData_reorder.js +3 -1
- package/src/svgii/pathData_split.js +5 -0
- package/src/svgii/pathData_toPolygon.js +98 -47
- package/src/svgii/poly_analyze.js +20 -16
- package/src/svgii/rounding.js +25 -19
- package/src/svgii/simplify_refineExtremes.js +173 -0
- package/src/svgii/svg_cleanup.js +14 -2
- package/test.js +14 -0
- package/testSVG.js +39 -0
- /package/src/svgii/{simplify_polygon.js → polygon_unite.js} +0 -0
|
@@ -28,14 +28,28 @@
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
function renderPath(svg, d = '', stroke = 'green', strokeWidth = '1%', render = true) {
|
|
32
|
+
|
|
33
|
+
let path = `<path d="${d}" fill="none" stroke="${stroke}" stroke-width="${strokeWidth}" /> `;
|
|
34
|
+
|
|
35
|
+
if (render) {
|
|
36
|
+
svg.insertAdjacentHTML("beforeend", path);
|
|
37
|
+
} else {
|
|
38
|
+
return path;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
31
43
|
function detectInputType(input) {
|
|
32
44
|
let type = 'string';
|
|
45
|
+
/*
|
|
33
46
|
if (input instanceof HTMLImageElement) return "img";
|
|
34
47
|
if (input instanceof SVGElement) return "svg";
|
|
35
48
|
if (input instanceof HTMLCanvasElement) return "canvas";
|
|
36
49
|
if (input instanceof File) return "file";
|
|
37
50
|
if (input instanceof ArrayBuffer) return "buffer";
|
|
38
51
|
if (input instanceof Blob) return "blob";
|
|
52
|
+
*/
|
|
39
53
|
if (Array.isArray(input)) return "array";
|
|
40
54
|
|
|
41
55
|
if (typeof input === "string") {
|
|
@@ -93,19 +107,24 @@
|
|
|
93
107
|
* http://jsfiddle.net/justin_c_rounds/Gd2S2/light/
|
|
94
108
|
*/
|
|
95
109
|
|
|
96
|
-
function checkLineIntersection(p1, p2, p3, p4, exact = true) {
|
|
110
|
+
function checkLineIntersection(p1=null, p2=null, p3=null, p4=null, exact = true, debug=false) {
|
|
97
111
|
// 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
|
|
98
112
|
let denominator, a, b, numerator1, numerator2;
|
|
99
113
|
let intersectionPoint = {};
|
|
100
114
|
|
|
115
|
+
if(!p1 || !p2 || !p3 || !p4){
|
|
116
|
+
if(debug) console.warn('points missing');
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
|
|
101
120
|
try {
|
|
102
121
|
denominator = ((p4.y - p3.y) * (p2.x - p1.x)) - ((p4.x - p3.x) * (p2.y - p1.y));
|
|
103
122
|
if (denominator == 0) {
|
|
104
123
|
return false;
|
|
105
124
|
}
|
|
106
|
-
|
|
107
125
|
} catch {
|
|
108
|
-
console.
|
|
126
|
+
if(debug) console.warn('!catch', p1, p2, 'p3:', p3, 'p4:', p4);
|
|
127
|
+
return false
|
|
109
128
|
}
|
|
110
129
|
|
|
111
130
|
a = p1.y - p3.y;
|
|
@@ -122,8 +141,6 @@
|
|
|
122
141
|
y: p1.y + (a * (p2.y - p1.y))
|
|
123
142
|
};
|
|
124
143
|
|
|
125
|
-
// console.log('intersectionPoint', intersectionPoint, p1, p2);
|
|
126
|
-
|
|
127
144
|
let intersection = false;
|
|
128
145
|
// if line1 is a segment and line2 is infinite, they intersect if:
|
|
129
146
|
if ((a > 0 && a < 1) && (b > 0 && b < 1)) {
|
|
@@ -523,6 +540,110 @@
|
|
|
523
540
|
return tArr;
|
|
524
541
|
}
|
|
525
542
|
|
|
543
|
+
/**
|
|
544
|
+
* based on Nikos M.'s answer
|
|
545
|
+
* how-do-you-calculate-the-axis-aligned-bounding-box-of-an-ellipse
|
|
546
|
+
* https://stackoverflow.com/questions/87734/#75031511
|
|
547
|
+
* See also: https://github.com/foo123/Geometrize
|
|
548
|
+
*/
|
|
549
|
+
function getArcExtemes(p0, values) {
|
|
550
|
+
// compute point on ellipse from angle around ellipse (theta)
|
|
551
|
+
const arc = (theta, cx, cy, rx, ry, alpha) => {
|
|
552
|
+
// theta is angle in radians around arc
|
|
553
|
+
// alpha is angle of rotation of ellipse in radians
|
|
554
|
+
var cos = Math.cos(alpha),
|
|
555
|
+
sin = Math.sin(alpha),
|
|
556
|
+
x = rx * Math.cos(theta),
|
|
557
|
+
y = ry * Math.sin(theta);
|
|
558
|
+
|
|
559
|
+
return {
|
|
560
|
+
x: cx + cos * x - sin * y,
|
|
561
|
+
y: cy + sin * x + cos * y
|
|
562
|
+
};
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
let arcData = svgArcToCenterParam(p0.x, p0.y, values[0], values[1], values[2], values[3], values[4], values[5], values[6]);
|
|
566
|
+
let { rx, ry, cx, cy, endAngle, deltaAngle } = arcData;
|
|
567
|
+
|
|
568
|
+
// arc rotation
|
|
569
|
+
let deg = values[2];
|
|
570
|
+
|
|
571
|
+
// final on path point
|
|
572
|
+
let p = { x: values[5], y: values[6] };
|
|
573
|
+
|
|
574
|
+
// collect extreme points – add end point
|
|
575
|
+
let extremes = [p];
|
|
576
|
+
|
|
577
|
+
// rotation to radians
|
|
578
|
+
let alpha = deg * Math.PI / 180;
|
|
579
|
+
let tan = Math.tan(alpha),
|
|
580
|
+
p1, p2, p3, p4, theta;
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* find min/max from zeroes of directional derivative along x and y
|
|
584
|
+
* along x axis
|
|
585
|
+
*/
|
|
586
|
+
theta = Math.atan2(-ry * tan, rx);
|
|
587
|
+
|
|
588
|
+
let angle1 = theta;
|
|
589
|
+
let angle2 = theta + Math.PI;
|
|
590
|
+
let angle3 = Math.atan2(ry, rx * tan);
|
|
591
|
+
let angle4 = angle3 + Math.PI;
|
|
592
|
+
|
|
593
|
+
// inner bounding box
|
|
594
|
+
let xArr = [p0.x, p.x];
|
|
595
|
+
let yArr = [p0.y, p.y];
|
|
596
|
+
let xMin = Math.min(...xArr);
|
|
597
|
+
let xMax = Math.max(...xArr);
|
|
598
|
+
let yMin = Math.min(...yArr);
|
|
599
|
+
let yMax = Math.max(...yArr);
|
|
600
|
+
|
|
601
|
+
// on path point close after start
|
|
602
|
+
let angleAfterStart = endAngle - deltaAngle * 0.001;
|
|
603
|
+
let pP2 = arc(angleAfterStart, cx, cy, rx, ry, alpha);
|
|
604
|
+
|
|
605
|
+
// on path point close before end
|
|
606
|
+
let angleBeforeEnd = endAngle - deltaAngle * 0.999;
|
|
607
|
+
let pP3 = arc(angleBeforeEnd, cx, cy, rx, ry, alpha);
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* expected extremes
|
|
611
|
+
* if leaving inner bounding box
|
|
612
|
+
* (between segment start and end point)
|
|
613
|
+
* otherwise exclude elliptic extreme points
|
|
614
|
+
*/
|
|
615
|
+
|
|
616
|
+
// right
|
|
617
|
+
if (pP2.x > xMax || pP3.x > xMax) {
|
|
618
|
+
// get point for this theta
|
|
619
|
+
p1 = arc(angle1, cx, cy, rx, ry, alpha);
|
|
620
|
+
extremes.push(p1);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// left
|
|
624
|
+
if (pP2.x < xMin || pP3.x < xMin) {
|
|
625
|
+
// get anti-symmetric point
|
|
626
|
+
p2 = arc(angle2, cx, cy, rx, ry, alpha);
|
|
627
|
+
extremes.push(p2);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// top
|
|
631
|
+
if (pP2.y < yMin || pP3.y < yMin) {
|
|
632
|
+
// get anti-symmetric point
|
|
633
|
+
p4 = arc(angle4, cx, cy, rx, ry, alpha);
|
|
634
|
+
extremes.push(p4);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// bottom
|
|
638
|
+
if (pP2.y > yMax || pP3.y > yMax) {
|
|
639
|
+
// get point for this theta
|
|
640
|
+
p3 = arc(angle3, cx, cy, rx, ry, alpha);
|
|
641
|
+
extremes.push(p3);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return extremes;
|
|
645
|
+
}
|
|
646
|
+
|
|
526
647
|
// cubic bezier.
|
|
527
648
|
function cubicBezierExtremeT(p0, cp1, cp2, p) {
|
|
528
649
|
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];
|
|
@@ -631,90 +752,6 @@
|
|
|
631
752
|
return extemeT
|
|
632
753
|
}
|
|
633
754
|
|
|
634
|
-
function commandIsFlat(points, tolerance = 0.025) {
|
|
635
|
-
|
|
636
|
-
let p0 = points[0];
|
|
637
|
-
let p = points[points.length - 1];
|
|
638
|
-
|
|
639
|
-
let xArr = points.map(pt => { return pt.x });
|
|
640
|
-
let yArr = points.map(pt => { return pt.y });
|
|
641
|
-
|
|
642
|
-
let xMin = Math.min(...xArr);
|
|
643
|
-
let xMax = Math.max(...xArr);
|
|
644
|
-
let yMin = Math.min(...yArr);
|
|
645
|
-
let yMax = Math.max(...yArr);
|
|
646
|
-
let w = xMax - xMin;
|
|
647
|
-
let h = yMax - yMin;
|
|
648
|
-
|
|
649
|
-
if (points.length < 3 || (w === 0 || h === 0)) {
|
|
650
|
-
return { area: 0, flat: true, thresh: 0.0001, ratio: 0 };
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
let squareDist = getSquareDistance(p0, p);
|
|
654
|
-
let squareDist1 = getSquareDistance(p0, points[0]);
|
|
655
|
-
let squareDist2 = points.length > 3 ? getSquareDistance(p, points[1]) : squareDist1;
|
|
656
|
-
let squareDistAvg = (squareDist1 + squareDist2) / 2;
|
|
657
|
-
|
|
658
|
-
tolerance = 0.5;
|
|
659
|
-
let thresh = (w + h) * 0.5 * tolerance;
|
|
660
|
-
|
|
661
|
-
let area = 0;
|
|
662
|
-
for (let i = 0, l = points.length; i < l; i++) {
|
|
663
|
-
let addX = points[i].x;
|
|
664
|
-
let addY = points[i === points.length - 1 ? 0 : i + 1].y;
|
|
665
|
-
let subX = points[i === points.length - 1 ? 0 : i + 1].x;
|
|
666
|
-
let subY = points[i].y;
|
|
667
|
-
area += addX * addY * 0.5 - subX * subY * 0.5;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
area = +Math.abs(area).toFixed(9);
|
|
671
|
-
let areaThresh = 1000;
|
|
672
|
-
|
|
673
|
-
let ratio = area / (squareDistAvg);
|
|
674
|
-
|
|
675
|
-
let isFlat = area === 0 ? true : area < squareDistAvg / areaThresh;
|
|
676
|
-
|
|
677
|
-
return { area: area, flat: isFlat, thresh: thresh, ratio: ratio, squareDist: squareDist, areaThresh: squareDist / areaThresh };
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
function checkBezierFlatness(p0, cpts, p) {
|
|
681
|
-
|
|
682
|
-
let isFlat = false;
|
|
683
|
-
|
|
684
|
-
let isCubic = cpts.length===2;
|
|
685
|
-
|
|
686
|
-
let cp1 = cpts[0];
|
|
687
|
-
let cp2 = isCubic ? cpts[1] : cp1;
|
|
688
|
-
|
|
689
|
-
if(p0.x===cp1.x && p0.y===cp1.y && p.x===cp2.x && p.y===cp2.y) return true;
|
|
690
|
-
|
|
691
|
-
let dx1 = cp1.x - p0.x;
|
|
692
|
-
let dy1 = cp1.y - p0.y;
|
|
693
|
-
|
|
694
|
-
let dx2 = p.x - cp2.x;
|
|
695
|
-
let dy2 = p.y - cp2.y;
|
|
696
|
-
|
|
697
|
-
let cross1 = Math.abs(dx1 * dy2 - dy1 * dx2);
|
|
698
|
-
|
|
699
|
-
if(!cross1) return true
|
|
700
|
-
|
|
701
|
-
let dx0 = p.x - p0.x;
|
|
702
|
-
let dy0 = p.y - p0.y;
|
|
703
|
-
let cross0 = Math.abs(dx0 * dy1 - dy0 * dx1);
|
|
704
|
-
|
|
705
|
-
if(!cross0) return true
|
|
706
|
-
|
|
707
|
-
let rat = (cross0/cross1);
|
|
708
|
-
|
|
709
|
-
if (rat<1.1 ) {
|
|
710
|
-
|
|
711
|
-
isFlat = true;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
return isFlat;
|
|
715
|
-
|
|
716
|
-
}
|
|
717
|
-
|
|
718
755
|
/**
|
|
719
756
|
* sloppy distance calculation
|
|
720
757
|
* based on x/y differences
|
|
@@ -885,6 +922,7 @@
|
|
|
885
922
|
|
|
886
923
|
if(tArr.length){
|
|
887
924
|
let commandsSplit = splitCommandAtTValues(p0, values, tArr);
|
|
925
|
+
|
|
888
926
|
pathDataNew.push(...commandsSplit);
|
|
889
927
|
extremeCount += commandsSplit.length;
|
|
890
928
|
}else {
|
|
@@ -1181,6 +1219,73 @@
|
|
|
1181
1219
|
return poly;
|
|
1182
1220
|
}
|
|
1183
1221
|
|
|
1222
|
+
/**
|
|
1223
|
+
* get exact path BBox
|
|
1224
|
+
* calculating extremes for all command types
|
|
1225
|
+
*/
|
|
1226
|
+
|
|
1227
|
+
function getPathDataBBox(pathData) {
|
|
1228
|
+
|
|
1229
|
+
// save extreme values
|
|
1230
|
+
let xMin = Infinity;
|
|
1231
|
+
let xMax = -Infinity;
|
|
1232
|
+
let yMin = Infinity;
|
|
1233
|
+
let yMax = -Infinity;
|
|
1234
|
+
|
|
1235
|
+
const setXYmaxMin = (pt) => {
|
|
1236
|
+
if (pt.x < xMin) {
|
|
1237
|
+
xMin = pt.x;
|
|
1238
|
+
}
|
|
1239
|
+
if (pt.x > xMax) {
|
|
1240
|
+
xMax = pt.x;
|
|
1241
|
+
}
|
|
1242
|
+
if (pt.y < yMin) {
|
|
1243
|
+
yMin = pt.y;
|
|
1244
|
+
}
|
|
1245
|
+
if (pt.y > yMax) {
|
|
1246
|
+
yMax = pt.y;
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
|
|
1250
|
+
for (let i = 0; i < pathData.length; i++) {
|
|
1251
|
+
let com = pathData[i];
|
|
1252
|
+
let { type, values } = com;
|
|
1253
|
+
let valuesL = values.length;
|
|
1254
|
+
let comPrev = pathData[i - 1] ? pathData[i - 1] : pathData[i];
|
|
1255
|
+
let valuesPrev = comPrev.values;
|
|
1256
|
+
let valuesPrevL = valuesPrev.length;
|
|
1257
|
+
|
|
1258
|
+
if (valuesL) {
|
|
1259
|
+
let p0 = { x: valuesPrev[valuesPrevL - 2], y: valuesPrev[valuesPrevL - 1] };
|
|
1260
|
+
let p = { x: values[valuesL - 2], y: values[valuesL - 1] };
|
|
1261
|
+
// add final on path point
|
|
1262
|
+
setXYmaxMin(p);
|
|
1263
|
+
|
|
1264
|
+
if (type === 'C' || type === 'Q') {
|
|
1265
|
+
let cp1 = { x: values[0], y: values[1] };
|
|
1266
|
+
let cp2 = type === 'C' ? { x: values[2], y: values[3] } : cp1;
|
|
1267
|
+
let pts = type === 'C' ? [p0, cp1, cp2, p] : [p0, cp1, p];
|
|
1268
|
+
|
|
1269
|
+
let bezierExtremesT = getBezierExtremeT(pts);
|
|
1270
|
+
bezierExtremesT.forEach(t => {
|
|
1271
|
+
let pt = pointAtT(pts, t);
|
|
1272
|
+
setXYmaxMin(pt);
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
else if (type === 'A') {
|
|
1277
|
+
let arcExtremes = getArcExtemes(p0, values);
|
|
1278
|
+
arcExtremes.forEach(pt => {
|
|
1279
|
+
setXYmaxMin(pt);
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
let bbox = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin };
|
|
1286
|
+
return bbox
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1184
1289
|
/**
|
|
1185
1290
|
* get pathdata area
|
|
1186
1291
|
*/
|
|
@@ -1456,7 +1561,7 @@
|
|
|
1456
1561
|
return d;
|
|
1457
1562
|
}
|
|
1458
1563
|
|
|
1459
|
-
function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1) {
|
|
1564
|
+
function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, debug = false) {
|
|
1460
1565
|
|
|
1461
1566
|
// cubic Bézier derivative
|
|
1462
1567
|
const cubicDerivative = (p0, p1, p2, p3, t) => {
|
|
@@ -1478,8 +1583,9 @@
|
|
|
1478
1583
|
let commands = [com1, com2];
|
|
1479
1584
|
|
|
1480
1585
|
// detect dominant
|
|
1481
|
-
let dist1 =
|
|
1482
|
-
let dist2 =
|
|
1586
|
+
let dist1 = getDistAv(com1.p0, com1.p);
|
|
1587
|
+
let dist2 = getDistAv(com2.p0, com2.p);
|
|
1588
|
+
|
|
1483
1589
|
let reverse = dist1 > dist2;
|
|
1484
1590
|
|
|
1485
1591
|
// backup original commands
|
|
@@ -1541,7 +1647,6 @@
|
|
|
1541
1647
|
t0 -= dot(r, dP) / dot(dP, dP);
|
|
1542
1648
|
|
|
1543
1649
|
// construct merged cubic over [t0, 1]
|
|
1544
|
-
|
|
1545
1650
|
let Q0 = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], t0);
|
|
1546
1651
|
let Q3 = com2.p;
|
|
1547
1652
|
|
|
@@ -1569,7 +1674,9 @@
|
|
|
1569
1674
|
};
|
|
1570
1675
|
}
|
|
1571
1676
|
|
|
1572
|
-
let
|
|
1677
|
+
let tMid = (1 - t0) * 0.5;
|
|
1678
|
+
|
|
1679
|
+
let ptM = pointAtT([result.p0, result.cp1, result.cp2, result.p], tMid, false, true);
|
|
1573
1680
|
let seg1_cp2 = ptM.cpts[2];
|
|
1574
1681
|
|
|
1575
1682
|
let ptI_1 = checkLineIntersection(ptM, seg1_cp2, result.p0, ptI, false);
|
|
@@ -1579,17 +1686,18 @@
|
|
|
1579
1686
|
let cp2_2 = interpolate(result.p, ptI_2, 1.333);
|
|
1580
1687
|
|
|
1581
1688
|
// test self intersections and exit
|
|
1582
|
-
let cp_intersection = checkLineIntersection(com1_o.p0, cp1_2, com2_o.p, cp2_2, true
|
|
1583
|
-
if(cp_intersection){
|
|
1689
|
+
let cp_intersection = checkLineIntersection(com1_o.p0, cp1_2, com2_o.p, cp2_2, true);
|
|
1690
|
+
if (cp_intersection) {
|
|
1584
1691
|
|
|
1585
1692
|
return commands;
|
|
1586
1693
|
}
|
|
1587
1694
|
|
|
1695
|
+
if (debug) renderPoint(markers, ptM, 'purple');
|
|
1696
|
+
|
|
1588
1697
|
result.cp1 = cp1_2;
|
|
1589
1698
|
result.cp2 = cp2_2;
|
|
1590
1699
|
|
|
1591
|
-
// check distances
|
|
1592
|
-
|
|
1700
|
+
// check distances between original starting point and extrapolated
|
|
1593
1701
|
let dist3 = getDistAv(com1_o.p0, result.p0);
|
|
1594
1702
|
let dist4 = getDistAv(com2_o.p, result.p);
|
|
1595
1703
|
let dist5 = (dist3 + dist4);
|
|
@@ -1601,11 +1709,24 @@
|
|
|
1601
1709
|
result.corner = com2_o.corner;
|
|
1602
1710
|
result.dimA = com2_o.dimA;
|
|
1603
1711
|
result.directionChange = com2_o.directionChange;
|
|
1712
|
+
result.type = 'C';
|
|
1604
1713
|
result.values = [result.cp1.x, result.cp1.y, result.cp2.x, result.cp2.y, result.p.x, result.p.y];
|
|
1605
1714
|
|
|
1606
|
-
//
|
|
1715
|
+
// extrapolated starting point is not completely off
|
|
1607
1716
|
if (dist5 < maxDist) {
|
|
1608
1717
|
|
|
1718
|
+
// split t to meet original mid segment start point
|
|
1719
|
+
let tSplit = reverse ? 1 + t0 : Math.abs(t0);
|
|
1720
|
+
|
|
1721
|
+
let ptSplit = pointAtT([result.p0, result.cp1, result.cp2, result.p], tSplit);
|
|
1722
|
+
let distSplit = getDistAv(ptSplit, com1.p);
|
|
1723
|
+
|
|
1724
|
+
// not close enough - exit
|
|
1725
|
+
if (distSplit > maxDist * tolerance) {
|
|
1726
|
+
|
|
1727
|
+
return commands;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1609
1730
|
// compare combined with original area
|
|
1610
1731
|
let pathData0 = [
|
|
1611
1732
|
{ type: 'M', values: [com1_o.p0.x, com1_o.p0.y] },
|
|
@@ -1622,16 +1743,18 @@
|
|
|
1622
1743
|
let areaN = getPathArea(pathDataN);
|
|
1623
1744
|
let areaDiff = Math.abs(areaN / area0 - 1);
|
|
1624
1745
|
|
|
1625
|
-
result.error = areaDiff *
|
|
1746
|
+
result.error = areaDiff * 5 * tolerance;
|
|
1626
1747
|
|
|
1627
|
-
|
|
1748
|
+
if (debug) {
|
|
1749
|
+
let d = pathDataToD(pathDataN);
|
|
1750
|
+
renderPath(markers, d, 'orange');
|
|
1751
|
+
}
|
|
1628
1752
|
|
|
1629
|
-
// success
|
|
1630
|
-
if (areaDiff < 0.
|
|
1753
|
+
// success!!!
|
|
1754
|
+
if (areaDiff < 0.05 * tolerance) {
|
|
1631
1755
|
commands = [result];
|
|
1632
1756
|
|
|
1633
|
-
}
|
|
1634
|
-
|
|
1757
|
+
}
|
|
1635
1758
|
}
|
|
1636
1759
|
|
|
1637
1760
|
return commands
|
|
@@ -1715,14 +1838,6 @@
|
|
|
1715
1838
|
} // end 1st try
|
|
1716
1839
|
|
|
1717
1840
|
|
|
1718
|
-
/*
|
|
1719
|
-
if (extrapolateDominant && com2.extreme) {
|
|
1720
|
-
renderPoint(markers, com2.p)
|
|
1721
|
-
|
|
1722
|
-
}
|
|
1723
|
-
*/
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
1841
|
|
|
1727
1842
|
// try extrapolated dominant curve
|
|
1728
1843
|
|
|
@@ -1791,25 +1906,59 @@
|
|
|
1791
1906
|
|
|
1792
1907
|
function findSplitT(com1, com2) {
|
|
1793
1908
|
|
|
1794
|
-
|
|
1795
|
-
let
|
|
1909
|
+
let len3 = getDistance(com1.cp2, com1.p);
|
|
1910
|
+
let len4 = getDistance(com1.cp2, com2.cp1);
|
|
1796
1911
|
|
|
1797
|
-
|
|
1798
|
-
let ptI = checkLineIntersection(pt1, com2.p, com2.p0, com2.cp1, false);
|
|
1912
|
+
let t = Math.min(len3) / len4;
|
|
1799
1913
|
|
|
1800
|
-
|
|
1801
|
-
|
|
1914
|
+
return t
|
|
1915
|
+
}
|
|
1802
1916
|
|
|
1803
|
-
|
|
1917
|
+
function checkBezierFlatness(p0, cpts, p) {
|
|
1804
1918
|
|
|
1805
|
-
|
|
1919
|
+
let isFlat = false;
|
|
1806
1920
|
|
|
1807
|
-
let
|
|
1808
|
-
let len4 = getDistance(com1.cp2, com2.cp1);
|
|
1921
|
+
let isCubic = cpts.length === 2;
|
|
1809
1922
|
|
|
1810
|
-
|
|
1923
|
+
let cp1 = cpts[0];
|
|
1924
|
+
let cp2 = isCubic ? cpts[1] : cp1;
|
|
1811
1925
|
|
|
1812
|
-
return
|
|
1926
|
+
if (p0.x === cp1.x && p0.y === cp1.y && p.x === cp2.x && p.y === cp2.y) return true;
|
|
1927
|
+
|
|
1928
|
+
let dx1 = cp1.x - p0.x;
|
|
1929
|
+
let dy1 = cp1.y - p0.y;
|
|
1930
|
+
|
|
1931
|
+
let dx2 = p.x - cp2.x;
|
|
1932
|
+
let dy2 = p.y - cp2.y;
|
|
1933
|
+
|
|
1934
|
+
let cross1 = Math.abs(dx1 * dy2 - dy1 * dx2);
|
|
1935
|
+
|
|
1936
|
+
if (!cross1) return true
|
|
1937
|
+
|
|
1938
|
+
let dx0 = p.x - p0.x;
|
|
1939
|
+
let dy0 = p.y - p0.y;
|
|
1940
|
+
let cross0 = Math.abs(dx0 * dy1 - dy0 * dx1);
|
|
1941
|
+
|
|
1942
|
+
if (!cross0) return true
|
|
1943
|
+
|
|
1944
|
+
let area = getPolygonArea([p0,...cpts, p], true);
|
|
1945
|
+
let dist1 = getSquareDistance(p0, p);
|
|
1946
|
+
let thresh = dist1/200;
|
|
1947
|
+
|
|
1948
|
+
// if(area<thresh) return true;
|
|
1949
|
+
isFlat = area<thresh;
|
|
1950
|
+
|
|
1951
|
+
/*
|
|
1952
|
+
|
|
1953
|
+
let rat = (cross0 / cross1)
|
|
1954
|
+
|
|
1955
|
+
if (rat < 1.1) {
|
|
1956
|
+
console.log('cross', cross0, cross1, 'rat', rat );
|
|
1957
|
+
isFlat = true;
|
|
1958
|
+
}
|
|
1959
|
+
*/
|
|
1960
|
+
|
|
1961
|
+
return isFlat;
|
|
1813
1962
|
|
|
1814
1963
|
}
|
|
1815
1964
|
|
|
@@ -1855,7 +2004,6 @@
|
|
|
1855
2004
|
* this way we can skip certain tests
|
|
1856
2005
|
*/
|
|
1857
2006
|
let commandPts = [p0];
|
|
1858
|
-
let isFlat = false;
|
|
1859
2007
|
|
|
1860
2008
|
// init properties
|
|
1861
2009
|
com.idx = c - 1;
|
|
@@ -1886,7 +2034,7 @@
|
|
|
1886
2034
|
com.p0 = p0;
|
|
1887
2035
|
com.p = p;
|
|
1888
2036
|
|
|
1889
|
-
let cp1, cp2, cp1N,
|
|
2037
|
+
let cp1, cp2, cp1N, pN, typeN, area1;
|
|
1890
2038
|
|
|
1891
2039
|
let dimA = getDistAv(p0, p);
|
|
1892
2040
|
com.dimA = dimA;
|
|
@@ -1926,6 +2074,7 @@
|
|
|
1926
2074
|
if (type === 'C') commandPts.push(cp2);
|
|
1927
2075
|
commandPts.push(p);
|
|
1928
2076
|
|
|
2077
|
+
/*
|
|
1929
2078
|
let commandFlatness = commandIsFlat(commandPts);
|
|
1930
2079
|
isFlat = commandFlatness.flat;
|
|
1931
2080
|
com.flat = isFlat;
|
|
@@ -1933,6 +2082,7 @@
|
|
|
1933
2082
|
if (isFlat) {
|
|
1934
2083
|
com.extreme = false;
|
|
1935
2084
|
}
|
|
2085
|
+
*/
|
|
1936
2086
|
}
|
|
1937
2087
|
|
|
1938
2088
|
/**
|
|
@@ -1941,7 +2091,7 @@
|
|
|
1941
2091
|
* so we interpret maximum x/y on-path points as well as extremes
|
|
1942
2092
|
* but we ignore linetos to allow chunk compilation
|
|
1943
2093
|
*/
|
|
1944
|
-
if (
|
|
2094
|
+
if (type !== 'L' && (p.x === left || p.y === top || p.x === right || p.y === bottom)) {
|
|
1945
2095
|
com.extreme = true;
|
|
1946
2096
|
}
|
|
1947
2097
|
|
|
@@ -1954,7 +2104,7 @@
|
|
|
1954
2104
|
pN = comN ? { x: comNValsL[0], y: comNValsL[1] } : null;
|
|
1955
2105
|
|
|
1956
2106
|
cp1N = { x: comN.values[0], y: comN.values[1] };
|
|
1957
|
-
|
|
2107
|
+
comN.type === 'C' ? { x: comN.values[2], y: comN.values[3] } : null;
|
|
1958
2108
|
}
|
|
1959
2109
|
|
|
1960
2110
|
/**
|
|
@@ -1984,19 +2134,15 @@
|
|
|
1984
2134
|
// check extremes
|
|
1985
2135
|
let cpts = commandPts.slice(1);
|
|
1986
2136
|
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
let thresh = (w + h) / 2 * 0.1;
|
|
1990
|
-
let pts1 = type === 'C' ? [p, cp1N, cp2N, pN] : [p, cp1N, pN];
|
|
1991
|
-
|
|
1992
|
-
let flatness2 = commandIsFlat(pts1, thresh);
|
|
1993
|
-
let isFlat2 = flatness2.flat;
|
|
2137
|
+
pN ? Math.abs(pN.x - p0.x) : 0;
|
|
2138
|
+
pN ? Math.abs(pN.y - p0.y) : 0;
|
|
1994
2139
|
|
|
1995
2140
|
/**
|
|
1996
2141
|
* if current and next cubic are flat
|
|
1997
2142
|
* we don't flag them as extremes to allow simplification
|
|
1998
2143
|
*/
|
|
1999
|
-
|
|
2144
|
+
|
|
2145
|
+
let hasExtremes = (!com.extreme ? bezierhasExtreme(p0, cpts, angleThreshold) : true);
|
|
2000
2146
|
|
|
2001
2147
|
if (hasExtremes) {
|
|
2002
2148
|
com.extreme = true;
|
|
@@ -2042,7 +2188,7 @@
|
|
|
2042
2188
|
|
|
2043
2189
|
// Reference first MoveTo command (M)
|
|
2044
2190
|
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
2045
|
-
let p0 =
|
|
2191
|
+
let p0 = M;
|
|
2046
2192
|
let p = M;
|
|
2047
2193
|
pathData[0].decimals = 0;
|
|
2048
2194
|
|
|
@@ -2054,28 +2200,33 @@
|
|
|
2054
2200
|
let { type, values } = com;
|
|
2055
2201
|
|
|
2056
2202
|
let lastVals = values.length ? values.slice(-2) : [M.x, M.y];
|
|
2057
|
-
p={x:lastVals[0], y:lastVals[1]};
|
|
2203
|
+
p = { x: lastVals[0], y: lastVals[1] };
|
|
2058
2204
|
|
|
2059
2205
|
// use existing averave dimension value or calculate
|
|
2060
|
-
let dimA = com.dimA ? +com.dimA.toFixed(8) : type!=='M' ? +getDistAv(p0, p).toFixed(8) : 0;
|
|
2206
|
+
let dimA = com.dimA ? +com.dimA.toFixed(8) : type !== 'M' ? +getDistAv(p0, p).toFixed(8) : 0;
|
|
2061
2207
|
|
|
2062
|
-
if(dimA) dims.add(dimA);
|
|
2063
|
-
|
|
2208
|
+
if (dimA) dims.add(dimA);
|
|
2064
2209
|
|
|
2065
|
-
if(type==='M'){
|
|
2066
|
-
M=p;
|
|
2210
|
+
if (type === 'M') {
|
|
2211
|
+
M = p;
|
|
2067
2212
|
}
|
|
2068
2213
|
p0 = p;
|
|
2069
2214
|
}
|
|
2070
2215
|
|
|
2071
2216
|
let dim_min = Array.from(dims).sort();
|
|
2072
|
-
let sliceIdx = Math.ceil(dim_min.length/8);
|
|
2073
|
-
dim_min = dim_min.slice(0, sliceIdx );
|
|
2074
2217
|
|
|
2075
|
-
|
|
2218
|
+
/*
|
|
2219
|
+
let minVal = dim_min.length > 15 ?
|
|
2220
|
+
(dim_min[0] + dim_min[2]) / 2 :
|
|
2221
|
+
dim_min[0];
|
|
2222
|
+
*/
|
|
2223
|
+
|
|
2224
|
+
let sliceIdx = Math.ceil(dim_min.length / 10);
|
|
2225
|
+
dim_min = dim_min.slice(0, sliceIdx);
|
|
2226
|
+
let minVal = dim_min.reduce((a, b) => a + b, 0) / sliceIdx;
|
|
2076
2227
|
|
|
2077
|
-
let threshold =
|
|
2078
|
-
let decimalsAuto =
|
|
2228
|
+
let threshold = 40;
|
|
2229
|
+
let decimalsAuto = minVal > threshold*1.5 ? 0 : Math.floor(threshold / minVal).toString().length;
|
|
2079
2230
|
|
|
2080
2231
|
// clamp
|
|
2081
2232
|
return Math.min(Math.max(0, decimalsAuto), 8)
|
|
@@ -2091,13 +2242,13 @@
|
|
|
2091
2242
|
// has recommended decimals
|
|
2092
2243
|
let hasDecimal = decimals == 'auto' && pathData[0].hasOwnProperty('decimals') ? true : false;
|
|
2093
2244
|
|
|
2094
|
-
for(let c=0, len=pathData.length; c<len; c++){
|
|
2095
|
-
let com=pathData[c];
|
|
2245
|
+
for (let c = 0, len = pathData.length; c < len; c++) {
|
|
2246
|
+
let com = pathData[c];
|
|
2096
2247
|
|
|
2097
|
-
if (decimals
|
|
2248
|
+
if (decimals > -1 || hasDecimal) {
|
|
2098
2249
|
decimals = hasDecimal ? com.decimals : decimals;
|
|
2099
2250
|
|
|
2100
|
-
pathData[c].values = com.values.map(val=>{return val ? +val.toFixed(decimals) : val });
|
|
2251
|
+
pathData[c].values = com.values.map(val => { return val ? +val.toFixed(decimals) : val });
|
|
2101
2252
|
|
|
2102
2253
|
}
|
|
2103
2254
|
} return pathData;
|
|
@@ -2116,17 +2267,21 @@
|
|
|
2116
2267
|
let cp1_Q = null;
|
|
2117
2268
|
let type = 'C';
|
|
2118
2269
|
let values = [cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y];
|
|
2270
|
+
let comN = {type, values};
|
|
2119
2271
|
|
|
2120
2272
|
if (dist1 < threshold) {
|
|
2121
2273
|
cp1_Q = checkLineIntersection(p0, cp1, p, cp2, false);
|
|
2122
2274
|
if (cp1_Q) {
|
|
2123
2275
|
|
|
2124
|
-
type = 'Q';
|
|
2125
|
-
values = [cp1_Q.x, cp1_Q.y, p.x, p.y];
|
|
2276
|
+
comN.type = 'Q';
|
|
2277
|
+
comN.values = [cp1_Q.x, cp1_Q.y, p.x, p.y];
|
|
2278
|
+
comN.p0 = p0;
|
|
2279
|
+
comN.cp1 = cp1_Q;
|
|
2280
|
+
comN.p = p;
|
|
2126
2281
|
}
|
|
2127
2282
|
}
|
|
2128
2283
|
|
|
2129
|
-
return
|
|
2284
|
+
return comN
|
|
2130
2285
|
|
|
2131
2286
|
}
|
|
2132
2287
|
|
|
@@ -3483,6 +3638,242 @@
|
|
|
3483
3638
|
|
|
3484
3639
|
}
|
|
3485
3640
|
|
|
3641
|
+
function stringifyPathData(pathData) {
|
|
3642
|
+
return pathData.map(com => { return `${com.type} ${com.values.join(' ')}` }).join(' ');
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3645
|
+
function shapeElToPath(el){
|
|
3646
|
+
|
|
3647
|
+
let nodeName = el.nodeName.toLowerCase();
|
|
3648
|
+
if(nodeName==='path')return el;
|
|
3649
|
+
|
|
3650
|
+
let pathData = getPathDataFromEl(el);
|
|
3651
|
+
let d = pathData.map(com=>{return `${com.type} ${com.values} `}).join(' ');
|
|
3652
|
+
let attributes = [...el.attributes].map(att=>att.name);
|
|
3653
|
+
|
|
3654
|
+
let pathN = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
3655
|
+
pathN.setAttribute('d', d );
|
|
3656
|
+
|
|
3657
|
+
let exclude = ['x', 'y', 'cx', 'cy', 'dx', 'dy', 'r', 'rx', 'ry', 'width', 'height', 'points'];
|
|
3658
|
+
|
|
3659
|
+
attributes.forEach(att=>{
|
|
3660
|
+
if(!exclude.includes(att)){
|
|
3661
|
+
let val = el.getAttribute(att);
|
|
3662
|
+
pathN.setAttribute(att, val);
|
|
3663
|
+
}
|
|
3664
|
+
});
|
|
3665
|
+
|
|
3666
|
+
return pathN
|
|
3667
|
+
|
|
3668
|
+
}
|
|
3669
|
+
|
|
3670
|
+
// retrieve pathdata from svg geometry elements
|
|
3671
|
+
function getPathDataFromEl(el, stringify=false) {
|
|
3672
|
+
|
|
3673
|
+
let pathData = [];
|
|
3674
|
+
let type = el.nodeName;
|
|
3675
|
+
let atts, attNames, d, x, y, width, height, r, rx, ry, cx, cy, x1, x2, y1, y2;
|
|
3676
|
+
|
|
3677
|
+
// convert relative or absolute units
|
|
3678
|
+
const svgElUnitsToPixel = (el, decimals = 9) => {
|
|
3679
|
+
|
|
3680
|
+
const svg = el.nodeName !== "svg" ? el.closest("svg") : el;
|
|
3681
|
+
|
|
3682
|
+
// convert real life units to pixels
|
|
3683
|
+
const translateUnitToPixel = (value) => {
|
|
3684
|
+
|
|
3685
|
+
if (value === null) {
|
|
3686
|
+
return 0
|
|
3687
|
+
}
|
|
3688
|
+
|
|
3689
|
+
let dpi = 96;
|
|
3690
|
+
let unit = value.match(/([a-z]+)/gi);
|
|
3691
|
+
unit = unit ? unit[0] : "";
|
|
3692
|
+
let val = parseFloat(value);
|
|
3693
|
+
let rat;
|
|
3694
|
+
|
|
3695
|
+
// no unit - already pixes/user unit
|
|
3696
|
+
if (!unit) {
|
|
3697
|
+
return val;
|
|
3698
|
+
}
|
|
3699
|
+
|
|
3700
|
+
switch (unit) {
|
|
3701
|
+
case "in":
|
|
3702
|
+
rat = dpi;
|
|
3703
|
+
break;
|
|
3704
|
+
case "pt":
|
|
3705
|
+
rat = (1 / 72) * 96;
|
|
3706
|
+
break;
|
|
3707
|
+
case "cm":
|
|
3708
|
+
rat = (1 / 2.54) * 96;
|
|
3709
|
+
break;
|
|
3710
|
+
case "mm":
|
|
3711
|
+
rat = ((1 / 2.54) * 96) / 10;
|
|
3712
|
+
break;
|
|
3713
|
+
// just a default approximation
|
|
3714
|
+
case "em":
|
|
3715
|
+
case "rem":
|
|
3716
|
+
rat = 16;
|
|
3717
|
+
break;
|
|
3718
|
+
default:
|
|
3719
|
+
rat = 1;
|
|
3720
|
+
}
|
|
3721
|
+
let valuePx = val * rat;
|
|
3722
|
+
return +valuePx.toFixed(decimals);
|
|
3723
|
+
};
|
|
3724
|
+
|
|
3725
|
+
// svg width and height attributes
|
|
3726
|
+
let width = svg.getAttribute("width");
|
|
3727
|
+
width = width ? translateUnitToPixel(width) : 300;
|
|
3728
|
+
let height = svg.getAttribute("height");
|
|
3729
|
+
height = width ? translateUnitToPixel(height) : 150;
|
|
3730
|
+
|
|
3731
|
+
let vB = svg.getAttribute("viewBox");
|
|
3732
|
+
vB = vB
|
|
3733
|
+
? vB
|
|
3734
|
+
.replace(/,/g, " ")
|
|
3735
|
+
.split(" ")
|
|
3736
|
+
.filter(Boolean)
|
|
3737
|
+
.map((val) => {
|
|
3738
|
+
return +val;
|
|
3739
|
+
})
|
|
3740
|
+
: [];
|
|
3741
|
+
|
|
3742
|
+
let w = vB.length ? vB[2] : width;
|
|
3743
|
+
let h = vB.length ? vB[3] : height;
|
|
3744
|
+
let scaleX = w / 100;
|
|
3745
|
+
let scaleY = h / 100;
|
|
3746
|
+
let scalRoot = Math.sqrt((Math.pow(scaleX, 2) + Math.pow(scaleY, 2)) / 2);
|
|
3747
|
+
|
|
3748
|
+
let attsH = ["x", "width", "x1", "x2", "rx", "cx", "r"];
|
|
3749
|
+
let attsV = ["y", "height", "y1", "y2", "ry", "cy"];
|
|
3750
|
+
|
|
3751
|
+
let atts = el.getAttributeNames();
|
|
3752
|
+
atts.forEach((att) => {
|
|
3753
|
+
let val = el.getAttribute(att);
|
|
3754
|
+
let valAbs = val;
|
|
3755
|
+
if (attsH.includes(att) || attsV.includes(att)) {
|
|
3756
|
+
let scale = attsH.includes(att) ? scaleX : scaleY;
|
|
3757
|
+
scale = att === "r" && w != h ? scalRoot : scale;
|
|
3758
|
+
let unit = val.match(/([a-z|%]+)/gi);
|
|
3759
|
+
unit = unit ? unit[0] : "";
|
|
3760
|
+
if (val.includes("%")) {
|
|
3761
|
+
valAbs = parseFloat(val) * scale;
|
|
3762
|
+
}
|
|
3763
|
+
|
|
3764
|
+
else {
|
|
3765
|
+
valAbs = translateUnitToPixel(val);
|
|
3766
|
+
}
|
|
3767
|
+
el.setAttribute(att, +valAbs);
|
|
3768
|
+
}
|
|
3769
|
+
});
|
|
3770
|
+
};
|
|
3771
|
+
|
|
3772
|
+
svgElUnitsToPixel(el);
|
|
3773
|
+
|
|
3774
|
+
const getAtts = (attNames) => {
|
|
3775
|
+
atts = {};
|
|
3776
|
+
attNames.forEach(att => {
|
|
3777
|
+
atts[att] = +el.getAttribute(att);
|
|
3778
|
+
});
|
|
3779
|
+
return atts
|
|
3780
|
+
};
|
|
3781
|
+
|
|
3782
|
+
switch (type) {
|
|
3783
|
+
case 'path':
|
|
3784
|
+
d = el.getAttribute("d");
|
|
3785
|
+
pathData = parsePathDataNormalized(d);
|
|
3786
|
+
break;
|
|
3787
|
+
|
|
3788
|
+
case 'rect':
|
|
3789
|
+
attNames = ['x', 'y', 'width', 'height', 'rx', 'ry'];
|
|
3790
|
+
({ x, y, width, height, rx, ry } = getAtts(attNames));
|
|
3791
|
+
|
|
3792
|
+
if (!rx && !ry) {
|
|
3793
|
+
pathData = [
|
|
3794
|
+
{ type: "M", values: [x, y] },
|
|
3795
|
+
{ type: "L", values: [x + width, y] },
|
|
3796
|
+
{ type: "L", values: [x + width, y + height] },
|
|
3797
|
+
{ type: "L", values: [x, y + height] },
|
|
3798
|
+
{ type: "Z", values: [] }
|
|
3799
|
+
];
|
|
3800
|
+
} else {
|
|
3801
|
+
|
|
3802
|
+
if (rx > width / 2) {
|
|
3803
|
+
rx = width / 2;
|
|
3804
|
+
}
|
|
3805
|
+
if (ry > height / 2) {
|
|
3806
|
+
ry = height / 2;
|
|
3807
|
+
}
|
|
3808
|
+
pathData = [
|
|
3809
|
+
{ type: "M", values: [x + rx, y] },
|
|
3810
|
+
{ type: "L", values: [x + width - rx, y] },
|
|
3811
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x + width, y + ry] },
|
|
3812
|
+
{ type: "L", values: [x + width, y + height - ry] },
|
|
3813
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x + width - rx, y + height] },
|
|
3814
|
+
{ type: "L", values: [x + rx, y + height] },
|
|
3815
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x, y + height - ry] },
|
|
3816
|
+
{ type: "L", values: [x, y + ry] },
|
|
3817
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x + rx, y] },
|
|
3818
|
+
{ type: "Z", values: [] }
|
|
3819
|
+
];
|
|
3820
|
+
}
|
|
3821
|
+
break;
|
|
3822
|
+
|
|
3823
|
+
case 'circle':
|
|
3824
|
+
case 'ellipse':
|
|
3825
|
+
|
|
3826
|
+
attNames = ['cx', 'cy', 'rx', 'ry', 'r'];
|
|
3827
|
+
({ cx, cy, r, rx, ry } = getAtts(attNames));
|
|
3828
|
+
|
|
3829
|
+
if (type === 'circle') {
|
|
3830
|
+
r = r;
|
|
3831
|
+
rx = r;
|
|
3832
|
+
ry = r;
|
|
3833
|
+
} else {
|
|
3834
|
+
rx = rx ? rx : r;
|
|
3835
|
+
ry = ry ? ry : r;
|
|
3836
|
+
}
|
|
3837
|
+
|
|
3838
|
+
pathData = [
|
|
3839
|
+
{ type: "M", values: [cx + rx, cy] },
|
|
3840
|
+
{ type: "A", values: [rx, ry, 0, 1, 1, cx - rx, cy] },
|
|
3841
|
+
{ type: "A", values: [rx, ry, 0, 1, 1, cx + rx, cy] },
|
|
3842
|
+
];
|
|
3843
|
+
|
|
3844
|
+
break;
|
|
3845
|
+
case 'line':
|
|
3846
|
+
attNames = ['x1', 'y1', 'x2', 'y2'];
|
|
3847
|
+
({ x1, y1, x2, y2 } = getAtts(attNames));
|
|
3848
|
+
pathData = [
|
|
3849
|
+
{ type: "M", values: [x1, y1] },
|
|
3850
|
+
{ type: "L", values: [x2, y2] }
|
|
3851
|
+
];
|
|
3852
|
+
break;
|
|
3853
|
+
case 'polygon':
|
|
3854
|
+
case 'polyline':
|
|
3855
|
+
|
|
3856
|
+
let points = el.getAttribute('points').replaceAll(',', ' ').split(' ').filter(Boolean);
|
|
3857
|
+
|
|
3858
|
+
for (let i = 0; i < points.length; i += 2) {
|
|
3859
|
+
pathData.push({
|
|
3860
|
+
type: (i === 0 ? "M" : "L"),
|
|
3861
|
+
values: [+points[i], +points[i + 1]]
|
|
3862
|
+
});
|
|
3863
|
+
}
|
|
3864
|
+
if (type === 'polygon') {
|
|
3865
|
+
pathData.push({
|
|
3866
|
+
type: "Z",
|
|
3867
|
+
values: []
|
|
3868
|
+
});
|
|
3869
|
+
}
|
|
3870
|
+
break;
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3873
|
+
return stringify ? stringifyPathData(pathData): pathData;
|
|
3874
|
+
|
|
3875
|
+
}
|
|
3876
|
+
|
|
3486
3877
|
function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLinetos = true) {
|
|
3487
3878
|
|
|
3488
3879
|
let pathDataN = [pathData[0]];
|
|
@@ -3503,8 +3894,11 @@
|
|
|
3503
3894
|
|
|
3504
3895
|
let area = getPolygonArea([p0, p, p1], true);
|
|
3505
3896
|
|
|
3897
|
+
getSquareDistance(p0, p);
|
|
3898
|
+
getSquareDistance(p, p1);
|
|
3506
3899
|
let distSquare = getSquareDistance(p0, p1);
|
|
3507
|
-
|
|
3900
|
+
|
|
3901
|
+
let distMax = distSquare / 200 * tolerance;
|
|
3508
3902
|
|
|
3509
3903
|
let isFlat = area < distMax;
|
|
3510
3904
|
let isFlatBez = false;
|
|
@@ -3519,6 +3913,7 @@
|
|
|
3519
3913
|
(type === 'Q' ? [{ x: values[0], y: values[1] }] : []);
|
|
3520
3914
|
|
|
3521
3915
|
isFlatBez = checkBezierFlatness(p0, cpts, p);
|
|
3916
|
+
|
|
3522
3917
|
// console.log();
|
|
3523
3918
|
|
|
3524
3919
|
if (isFlatBez && c < l - 1 && comPrev.type !== 'C') {
|
|
@@ -3534,7 +3929,10 @@
|
|
|
3534
3929
|
p0 = p;
|
|
3535
3930
|
|
|
3536
3931
|
// colinear – exclude arcs (as always =) as semicircles won't have an area
|
|
3537
|
-
|
|
3932
|
+
|
|
3933
|
+
if ( isFlat && c < l - 1 && (type === 'L' || (flatBezierToLinetos && isFlatBez)) ) {
|
|
3934
|
+
|
|
3935
|
+
|
|
3538
3936
|
|
|
3539
3937
|
continue;
|
|
3540
3938
|
}
|
|
@@ -3557,6 +3955,44 @@
|
|
|
3557
3955
|
|
|
3558
3956
|
}
|
|
3559
3957
|
|
|
3958
|
+
function removeOrphanedM(pathData) {
|
|
3959
|
+
|
|
3960
|
+
for (let i = 0, l = pathData.length; i < l; i++) {
|
|
3961
|
+
let com = pathData[i];
|
|
3962
|
+
if (!com) continue;
|
|
3963
|
+
let { type = null, values = [] } = com;
|
|
3964
|
+
let comN = pathData[i + 1] ? pathData[i + 1] : null;
|
|
3965
|
+
if ((type === 'M' || type === 'm')) {
|
|
3966
|
+
|
|
3967
|
+
if (!comN || (comN && (comN.type === 'Z' || comN.type === 'z'))) {
|
|
3968
|
+
pathData[i] = null;
|
|
3969
|
+
pathData[i + 1] = null;
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
}
|
|
3973
|
+
|
|
3974
|
+
pathData = pathData.filter(Boolean);
|
|
3975
|
+
return pathData;
|
|
3976
|
+
|
|
3977
|
+
}
|
|
3978
|
+
|
|
3979
|
+
/*
|
|
3980
|
+
// remove zero-length segments introduced by rounding
|
|
3981
|
+
export function removeZeroLengthLinetos_post(pathData) {
|
|
3982
|
+
let pathDataOpt = []
|
|
3983
|
+
pathData.forEach((com, i) => {
|
|
3984
|
+
let { type, values } = com;
|
|
3985
|
+
if (type === 'l' || type === 'v' || type === 'h') {
|
|
3986
|
+
let hasLength = type === 'l' ? (values.join('') !== '00') : values[0] !== 0
|
|
3987
|
+
if (hasLength) pathDataOpt.push(com)
|
|
3988
|
+
} else {
|
|
3989
|
+
pathDataOpt.push(com)
|
|
3990
|
+
}
|
|
3991
|
+
})
|
|
3992
|
+
return pathDataOpt
|
|
3993
|
+
}
|
|
3994
|
+
*/
|
|
3995
|
+
|
|
3560
3996
|
function removeZeroLengthLinetos(pathData) {
|
|
3561
3997
|
|
|
3562
3998
|
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
@@ -3569,14 +4005,21 @@
|
|
|
3569
4005
|
let com = pathData[c];
|
|
3570
4006
|
let { type, values } = com;
|
|
3571
4007
|
|
|
3572
|
-
let
|
|
3573
|
-
|
|
4008
|
+
let valsLen = values.length;
|
|
4009
|
+
|
|
4010
|
+
p = { x: values[valsLen-2], y: values[valsLen-1] };
|
|
3574
4011
|
|
|
3575
4012
|
// skip lineto
|
|
3576
4013
|
if (type === 'L' && p.x === p0.x && p.y === p0.y) {
|
|
3577
4014
|
continue
|
|
3578
4015
|
}
|
|
3579
4016
|
|
|
4017
|
+
// skip minified zero length
|
|
4018
|
+
if (type === 'l' || type === 'v' || type === 'h') {
|
|
4019
|
+
let noLength = type === 'l' ? (values.join('') === '00') : values[0] === 0;
|
|
4020
|
+
if(noLength) continue
|
|
4021
|
+
}
|
|
4022
|
+
|
|
3580
4023
|
pathDataN.push(com);
|
|
3581
4024
|
p0 = p;
|
|
3582
4025
|
}
|
|
@@ -3915,6 +4358,152 @@
|
|
|
3915
4358
|
return pathDataNew;
|
|
3916
4359
|
}
|
|
3917
4360
|
|
|
4361
|
+
function refineAdjacentExtremes(pathData, {
|
|
4362
|
+
threshold = null, tolerance = 1
|
|
4363
|
+
} = {}) {
|
|
4364
|
+
|
|
4365
|
+
if (!threshold) {
|
|
4366
|
+
let bb = getPathDataBBox(pathData);
|
|
4367
|
+
threshold = (bb.width + bb.height) / 2 * 0.05;
|
|
4368
|
+
|
|
4369
|
+
}
|
|
4370
|
+
|
|
4371
|
+
let l = pathData.length;
|
|
4372
|
+
|
|
4373
|
+
for (let i = 0; i < l; i++) {
|
|
4374
|
+
let com = pathData[i];
|
|
4375
|
+
let { type, values, extreme, corner=false, dimA, p0, p } = com;
|
|
4376
|
+
let comN = pathData[i + 1] ? pathData[i + 1] : null;
|
|
4377
|
+
|
|
4378
|
+
// adjacent
|
|
4379
|
+
|
|
4380
|
+
if (comN && type === 'C' && comN.type === 'C' && extreme && !corner) {
|
|
4381
|
+
|
|
4382
|
+
// check dist
|
|
4383
|
+
let diff = getDistAv(p, comN.p);
|
|
4384
|
+
let isCose = diff < threshold;
|
|
4385
|
+
|
|
4386
|
+
if (isCose) {
|
|
4387
|
+
|
|
4388
|
+
let dx1 = (com.cp1.x - comN.p0.x);
|
|
4389
|
+
let dy1 = (com.cp1.y - comN.p0.y);
|
|
4390
|
+
|
|
4391
|
+
let horizontal = Math.abs(dy1) < Math.abs(dx1);
|
|
4392
|
+
|
|
4393
|
+
let pN = comN.p;
|
|
4394
|
+
let ptI;
|
|
4395
|
+
let t = 1;
|
|
4396
|
+
|
|
4397
|
+
if (comN.extreme) {
|
|
4398
|
+
|
|
4399
|
+
// extend cp2
|
|
4400
|
+
if (horizontal) {
|
|
4401
|
+
t = Math.abs(Math.abs(comN.cp2.x - comN.p.x) / Math.abs(com.cp2.x - com.p.x));
|
|
4402
|
+
|
|
4403
|
+
ptI = interpolate(comN.p, com.cp2, 1 + t);
|
|
4404
|
+
com.cp2.x = ptI.x;
|
|
4405
|
+
|
|
4406
|
+
}
|
|
4407
|
+
else {
|
|
4408
|
+
|
|
4409
|
+
t = Math.abs(Math.abs(comN.cp2.y - comN.p.y) / Math.abs(com.cp2.y - com.p.y));
|
|
4410
|
+
ptI = interpolate(comN.p, com.cp2, 1 + t);
|
|
4411
|
+
com.cp2.y = ptI.y;
|
|
4412
|
+
}
|
|
4413
|
+
|
|
4414
|
+
pathData[i + 1].values = [com.cp1.x, com.cp1.y, com.cp2.x, com.cp2.y, pN.x, pN.y];
|
|
4415
|
+
pathData[i + 1].cp1 = com.cp1;
|
|
4416
|
+
pathData[i + 1].cp2 = com.cp2;
|
|
4417
|
+
pathData[i + 1].p0 = com.p0;
|
|
4418
|
+
pathData[i + 1].p = pN;
|
|
4419
|
+
pathData[i + 1].extreme = true;
|
|
4420
|
+
|
|
4421
|
+
// nullify 1st
|
|
4422
|
+
pathData[i] = null;
|
|
4423
|
+
continue
|
|
4424
|
+
|
|
4425
|
+
}
|
|
4426
|
+
|
|
4427
|
+
// extend fist command
|
|
4428
|
+
else {
|
|
4429
|
+
|
|
4430
|
+
let comN2 = pathData[i + 2] ? pathData[i + 2] : null;
|
|
4431
|
+
if (!comN2 && comN2.type !== 'C') continue
|
|
4432
|
+
|
|
4433
|
+
// extrapolate
|
|
4434
|
+
let comEx = getCombinedByDominant(comN, comN2, threshold, tolerance, false);
|
|
4435
|
+
|
|
4436
|
+
if (comEx.length === 1) {
|
|
4437
|
+
pathData[i + 1] = null;
|
|
4438
|
+
|
|
4439
|
+
comEx = comEx[0];
|
|
4440
|
+
|
|
4441
|
+
pathData[i + 2].values = [comEx.cp1.x, comEx.cp1.y, comEx.cp2.x, comEx.cp2.y, comEx.p.x, comEx.p.y];
|
|
4442
|
+
pathData[i + 2].cp1 = comEx.cp1;
|
|
4443
|
+
pathData[i + 2].cp2 = comEx.cp2;
|
|
4444
|
+
pathData[i + 2].p0 = comEx.p0;
|
|
4445
|
+
pathData[i + 2].p = comEx.p;
|
|
4446
|
+
pathData[i + 2].extreme = comEx.extreme;
|
|
4447
|
+
|
|
4448
|
+
i++;
|
|
4449
|
+
continue
|
|
4450
|
+
}
|
|
4451
|
+
|
|
4452
|
+
}
|
|
4453
|
+
|
|
4454
|
+
}
|
|
4455
|
+
}
|
|
4456
|
+
}
|
|
4457
|
+
|
|
4458
|
+
// remove commands
|
|
4459
|
+
pathData = pathData.filter(Boolean);
|
|
4460
|
+
l = pathData.length;
|
|
4461
|
+
|
|
4462
|
+
/**
|
|
4463
|
+
* refine closing commands
|
|
4464
|
+
*/
|
|
4465
|
+
|
|
4466
|
+
let closed = pathData[l - 1].type.toLowerCase() === 'z';
|
|
4467
|
+
let lastIdx = closed ? l - 2 : l - 1;
|
|
4468
|
+
let lastCom = pathData[lastIdx];
|
|
4469
|
+
let penultimateCom = pathData[lastIdx - 1] || null;
|
|
4470
|
+
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
4471
|
+
|
|
4472
|
+
let dec = 8;
|
|
4473
|
+
let lastVals = lastCom.values.slice(-2);
|
|
4474
|
+
let isClosingTo = +lastVals[0].toFixed(dec) === +M.x.toFixed(dec) && +lastVals[1].toFixed(dec) === +M.y.toFixed(dec);
|
|
4475
|
+
let fistExt = pathData[1].type === 'C' && pathData[1].extreme ? pathData[1] : null;
|
|
4476
|
+
|
|
4477
|
+
let diff = getDistAv(lastCom.p0, lastCom.p);
|
|
4478
|
+
let isCose = diff < threshold;
|
|
4479
|
+
|
|
4480
|
+
if (penultimateCom && penultimateCom.type === 'C' && isCose && isClosingTo && fistExt) {
|
|
4481
|
+
|
|
4482
|
+
Math.abs(fistExt.cp1.x - M.x);
|
|
4483
|
+
Math.abs(fistExt.cp1.y - M.y);
|
|
4484
|
+
|
|
4485
|
+
let comEx = getCombinedByDominant(penultimateCom, lastCom, threshold, tolerance, false);
|
|
4486
|
+
console.log('comEx', comEx);
|
|
4487
|
+
|
|
4488
|
+
if (comEx.length === 1) {
|
|
4489
|
+
pathData[lastIdx - 1] = comEx[0];
|
|
4490
|
+
pathData[lastIdx] = null;
|
|
4491
|
+
pathData = pathData.filter(Boolean);
|
|
4492
|
+
}
|
|
4493
|
+
|
|
4494
|
+
}
|
|
4495
|
+
|
|
4496
|
+
return pathData
|
|
4497
|
+
|
|
4498
|
+
}
|
|
4499
|
+
|
|
4500
|
+
function removeEmptySVGEls(svg) {
|
|
4501
|
+
let els = svg.querySelectorAll('g, defs');
|
|
4502
|
+
els.forEach(el => {
|
|
4503
|
+
if (!el.children.length) el.remove();
|
|
4504
|
+
});
|
|
4505
|
+
}
|
|
4506
|
+
|
|
3918
4507
|
function cleanUpSVG(svgMarkup, {
|
|
3919
4508
|
returnDom=false,
|
|
3920
4509
|
removeHidden=true,
|
|
@@ -3930,7 +4519,7 @@
|
|
|
3930
4519
|
.querySelector("svg");
|
|
3931
4520
|
|
|
3932
4521
|
|
|
3933
|
-
let allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class'];
|
|
4522
|
+
let allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class', 'fill', 'stroke', 'stroke-width'];
|
|
3934
4523
|
removeExcludedAttribues(svg, allowed);
|
|
3935
4524
|
|
|
3936
4525
|
let removeEls = ['metadata', 'script'];
|
|
@@ -4002,10 +4591,13 @@
|
|
|
4002
4591
|
}
|
|
4003
4592
|
|
|
4004
4593
|
function svgPathSimplify(input = '', {
|
|
4594
|
+
|
|
4595
|
+
// return svg markup or object
|
|
4596
|
+
getObject = false,
|
|
4597
|
+
|
|
4005
4598
|
toAbsolute = true,
|
|
4006
4599
|
toRelative = true,
|
|
4007
4600
|
toShorthands = true,
|
|
4008
|
-
decimals = 3,
|
|
4009
4601
|
|
|
4010
4602
|
// not necessary unless you need cubics only
|
|
4011
4603
|
quadraticToCubic = true,
|
|
@@ -4014,29 +4606,33 @@
|
|
|
4014
4606
|
arcToCubic = false,
|
|
4015
4607
|
cubicToArc = false,
|
|
4016
4608
|
|
|
4017
|
-
|
|
4018
|
-
arcAccuracy = 4,
|
|
4019
|
-
keepExtremes = true,
|
|
4020
|
-
keepCorners = true,
|
|
4021
|
-
keepInflections = true,
|
|
4022
|
-
extrapolateDominant = false,
|
|
4023
|
-
addExtremes = false,
|
|
4609
|
+
simplifyBezier = true,
|
|
4024
4610
|
optimizeOrder = true,
|
|
4025
4611
|
removeColinear = true,
|
|
4026
|
-
simplifyBezier = true,
|
|
4027
|
-
autoAccuracy = true,
|
|
4028
4612
|
flatBezierToLinetos = true,
|
|
4029
4613
|
revertToQuadratics = true,
|
|
4614
|
+
|
|
4615
|
+
refineExtremes = true,
|
|
4616
|
+
keepExtremes = true,
|
|
4617
|
+
keepCorners = true,
|
|
4618
|
+
extrapolateDominant = true,
|
|
4619
|
+
keepInflections = false,
|
|
4620
|
+
addExtremes = false,
|
|
4621
|
+
removeOrphanSubpaths = false,
|
|
4622
|
+
|
|
4623
|
+
// svg path optimizations
|
|
4624
|
+
decimals = 3,
|
|
4625
|
+
autoAccuracy = true,
|
|
4626
|
+
|
|
4030
4627
|
minifyD = 0,
|
|
4031
4628
|
tolerance = 1,
|
|
4032
4629
|
reverse = false,
|
|
4033
4630
|
|
|
4034
4631
|
// svg cleanup options
|
|
4632
|
+
mergePaths = false,
|
|
4035
4633
|
removeHidden = true,
|
|
4036
4634
|
removeUnused = true,
|
|
4037
|
-
|
|
4038
|
-
// return svg markup or object
|
|
4039
|
-
getObject = false
|
|
4635
|
+
shapesToPaths = true,
|
|
4040
4636
|
|
|
4041
4637
|
} = {}) {
|
|
4042
4638
|
|
|
@@ -4079,6 +4675,14 @@
|
|
|
4079
4675
|
svg = cleanUpSVG(input, { returnDom, removeHidden, removeUnused }
|
|
4080
4676
|
);
|
|
4081
4677
|
|
|
4678
|
+
if(shapesToPaths){
|
|
4679
|
+
let shapes = svg.querySelectorAll('polygon, polyline, line, rect, circle, ellipse');
|
|
4680
|
+
shapes.forEach(shape=>{
|
|
4681
|
+
let path = shapeElToPath(shape);
|
|
4682
|
+
shape.replaceWith(path);
|
|
4683
|
+
});
|
|
4684
|
+
}
|
|
4685
|
+
|
|
4082
4686
|
// collect paths
|
|
4083
4687
|
let pathEls = svg.querySelectorAll('path');
|
|
4084
4688
|
pathEls.forEach(path => {
|
|
@@ -4089,17 +4693,31 @@
|
|
|
4089
4693
|
/**
|
|
4090
4694
|
* process all paths
|
|
4091
4695
|
*/
|
|
4696
|
+
|
|
4697
|
+
// SVG optimization options
|
|
4698
|
+
let pathOptions = {
|
|
4699
|
+
toRelative,
|
|
4700
|
+
toShorthands,
|
|
4701
|
+
decimals,
|
|
4702
|
+
};
|
|
4703
|
+
|
|
4704
|
+
// combinded path data for SVGs with mergePaths enabled
|
|
4705
|
+
let pathData_merged = [];
|
|
4706
|
+
|
|
4092
4707
|
paths.forEach(path => {
|
|
4093
4708
|
let { d, el } = path;
|
|
4094
4709
|
|
|
4095
4710
|
let pathDataO = parsePathDataNormalized(d, { quadraticToCubic, toAbsolute, arcToCubic });
|
|
4096
4711
|
|
|
4097
|
-
// create clone for fallback
|
|
4098
|
-
let pathData = JSON.parse(JSON.stringify(pathDataO));
|
|
4099
|
-
|
|
4100
4712
|
// count commands for evaluation
|
|
4101
4713
|
let comCount = pathDataO.length;
|
|
4102
4714
|
|
|
4715
|
+
// create clone for fallback
|
|
4716
|
+
|
|
4717
|
+
let pathData = pathDataO;
|
|
4718
|
+
|
|
4719
|
+
if(removeOrphanSubpaths) pathData = removeOrphanedM(pathData);
|
|
4720
|
+
|
|
4103
4721
|
/**
|
|
4104
4722
|
* get sub paths
|
|
4105
4723
|
*/
|
|
@@ -4135,7 +4753,13 @@
|
|
|
4135
4753
|
// simplify beziers
|
|
4136
4754
|
let { pathData, bb, dimA } = pathDataPlus;
|
|
4137
4755
|
|
|
4138
|
-
pathData = simplifyBezier ?
|
|
4756
|
+
pathData = simplifyBezier ? simplifyPathDataCubic(pathData, { simplifyBezier, keepInflections, keepExtremes, keepCorners, extrapolateDominant, revertToQuadratics, tolerance, reverse }) : pathData;
|
|
4757
|
+
|
|
4758
|
+
// refine extremes
|
|
4759
|
+
if(refineExtremes){
|
|
4760
|
+
let thresholdEx = (bb.width + bb.height) / 2 * 0.05;
|
|
4761
|
+
pathData = refineAdjacentExtremes(pathData, {threshold:thresholdEx, tolerance});
|
|
4762
|
+
}
|
|
4139
4763
|
|
|
4140
4764
|
// cubic to arcs
|
|
4141
4765
|
if (cubicToArc) {
|
|
@@ -4164,80 +4788,107 @@
|
|
|
4164
4788
|
if (type === 'C') {
|
|
4165
4789
|
|
|
4166
4790
|
let comQ = revertCubicQuadratic(p0, cp1, cp2, p);
|
|
4167
|
-
if (comQ.type === 'Q')
|
|
4791
|
+
if (comQ.type === 'Q') {
|
|
4792
|
+
/*
|
|
4793
|
+
comQ.p0 = com.p0
|
|
4794
|
+
comQ.cp1 = {x:comQ.values[0], y:comQ.values[1]}
|
|
4795
|
+
comQ.p = com.p
|
|
4796
|
+
*/
|
|
4797
|
+
comQ.extreme = com.extreme;
|
|
4798
|
+
comQ.corner = com.corner;
|
|
4799
|
+
comQ.dimA = com.dimA;
|
|
4800
|
+
|
|
4801
|
+
pathData[c] = comQ;
|
|
4802
|
+
}
|
|
4168
4803
|
}
|
|
4169
4804
|
});
|
|
4170
4805
|
}
|
|
4171
4806
|
|
|
4172
4807
|
// optimize close path
|
|
4173
|
-
if(optimizeOrder) pathData=optimizeClosePath(pathData);
|
|
4808
|
+
if (optimizeOrder) pathData = optimizeClosePath(pathData);
|
|
4809
|
+
|
|
4810
|
+
// poly
|
|
4174
4811
|
|
|
4175
4812
|
// update
|
|
4176
4813
|
pathDataArrN.push(pathData);
|
|
4177
4814
|
}
|
|
4178
4815
|
|
|
4179
|
-
|
|
4180
4816
|
// flatten compound paths
|
|
4181
4817
|
pathData = pathDataArrN.flat();
|
|
4182
4818
|
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
if (autoAccuracy) {
|
|
4187
|
-
decimals = detectAccuracy(pathData);
|
|
4819
|
+
// collect for merged svg paths
|
|
4820
|
+
if (el && mergePaths) {
|
|
4821
|
+
pathData_merged.push(...pathData);
|
|
4188
4822
|
}
|
|
4823
|
+
// single output
|
|
4824
|
+
else {
|
|
4189
4825
|
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
// optimize path data
|
|
4198
|
-
pathData = convertPathData(pathData, pathOptions);
|
|
4199
|
-
|
|
4200
|
-
// remove zero-length segments introduced by rounding
|
|
4201
|
-
let pathDataOpt = [];
|
|
4826
|
+
/**
|
|
4827
|
+
* detect accuracy
|
|
4828
|
+
*/
|
|
4829
|
+
if (autoAccuracy) {
|
|
4830
|
+
decimals = detectAccuracy(pathData);
|
|
4831
|
+
pathOptions.decimals = decimals;
|
|
4202
4832
|
|
|
4203
|
-
pathData.forEach((com, i) => {
|
|
4204
|
-
let { type, values } = com;
|
|
4205
|
-
if (type === 'l' || type === 'v' || type === 'h') {
|
|
4206
|
-
let hasLength = type === 'l' ? (values.join('') !== '00') : values[0] !== 0;
|
|
4207
|
-
if (hasLength) pathDataOpt.push(com);
|
|
4208
|
-
} else {
|
|
4209
|
-
pathDataOpt.push(com);
|
|
4210
4833
|
}
|
|
4211
|
-
});
|
|
4212
|
-
|
|
4213
|
-
pathData = pathDataOpt;
|
|
4214
4834
|
|
|
4215
|
-
|
|
4216
|
-
|
|
4835
|
+
// optimize path data
|
|
4836
|
+
pathData = convertPathData(pathData, pathOptions);
|
|
4217
4837
|
|
|
4218
|
-
|
|
4219
|
-
|
|
4838
|
+
// remove zero-length segments introduced by rounding
|
|
4839
|
+
pathData = removeZeroLengthLinetos(pathData);
|
|
4220
4840
|
|
|
4221
|
-
|
|
4841
|
+
// compare command count
|
|
4842
|
+
let comCountS = pathData.length;
|
|
4222
4843
|
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
new: comCountS,
|
|
4227
|
-
saved: comCount - comCountS,
|
|
4228
|
-
compression,
|
|
4229
|
-
decimals,
|
|
4844
|
+
let dOpt = pathDataToD(pathData, minifyD);
|
|
4845
|
+
svgSizeOpt = new Blob([dOpt]).size;
|
|
4846
|
+
compression = +(100 / svgSize * (svgSizeOpt)).toFixed(2);
|
|
4230
4847
|
|
|
4231
|
-
|
|
4848
|
+
path.d = dOpt;
|
|
4849
|
+
path.report = {
|
|
4850
|
+
original: comCount,
|
|
4851
|
+
new: comCountS,
|
|
4852
|
+
saved: comCount - comCountS,
|
|
4853
|
+
compression,
|
|
4854
|
+
decimals,
|
|
4232
4855
|
|
|
4233
|
-
|
|
4234
|
-
if (el) el.setAttribute('d', dOpt);
|
|
4856
|
+
};
|
|
4235
4857
|
|
|
4858
|
+
// apply new path for svgs
|
|
4859
|
+
if (el) el.setAttribute('d', dOpt);
|
|
4860
|
+
}
|
|
4236
4861
|
});
|
|
4237
4862
|
|
|
4238
|
-
|
|
4863
|
+
/**
|
|
4864
|
+
* stringify new SVG
|
|
4865
|
+
*/
|
|
4239
4866
|
if (mode) {
|
|
4240
|
-
|
|
4867
|
+
|
|
4868
|
+
if (pathData_merged.length) {
|
|
4869
|
+
// optimize path data
|
|
4870
|
+
let pathData = convertPathData(pathData_merged, pathOptions);
|
|
4871
|
+
|
|
4872
|
+
// remove zero-length segments introduced by rounding
|
|
4873
|
+
|
|
4874
|
+
pathData = removeZeroLengthLinetos(pathData);
|
|
4875
|
+
|
|
4876
|
+
let dOpt = pathDataToD(pathData, minifyD);
|
|
4877
|
+
|
|
4878
|
+
// apply new path for svgs
|
|
4879
|
+
paths[0].el.setAttribute('d', dOpt);
|
|
4880
|
+
|
|
4881
|
+
// remove other paths
|
|
4882
|
+
for (let i = 1; i < paths.length; i++) {
|
|
4883
|
+
let pathEl = paths[i].el;
|
|
4884
|
+
if (pathEl) pathEl.remove();
|
|
4885
|
+
}
|
|
4886
|
+
|
|
4887
|
+
// remove empty groups e.g groups
|
|
4888
|
+
removeEmptySVGEls(svg);
|
|
4889
|
+
}
|
|
4890
|
+
|
|
4891
|
+
svg = stringifySVG(svg);
|
|
4241
4892
|
svgSizeOpt = new Blob([svg]).size;
|
|
4242
4893
|
|
|
4243
4894
|
compression = +(100 / svgSize * (svgSizeOpt)).toFixed(2);
|
|
@@ -4259,7 +4910,7 @@
|
|
|
4259
4910
|
|
|
4260
4911
|
}
|
|
4261
4912
|
|
|
4262
|
-
function
|
|
4913
|
+
function simplifyPathDataCubic(pathData, {
|
|
4263
4914
|
keepExtremes = true,
|
|
4264
4915
|
keepInflections = true,
|
|
4265
4916
|
keepCorners = true,
|
|
@@ -4303,6 +4954,8 @@
|
|
|
4303
4954
|
if (combined.length === 1) {
|
|
4304
4955
|
com = combined[0];
|
|
4305
4956
|
let offset = 1;
|
|
4957
|
+
|
|
4958
|
+
// add cumulative error to prevent distortions
|
|
4306
4959
|
error += com.error;
|
|
4307
4960
|
|
|
4308
4961
|
// find next candidates
|
|
@@ -4320,6 +4973,10 @@
|
|
|
4320
4973
|
|
|
4321
4974
|
let combined = combineCubicPairs(com, comN, extrapolateDominant, tolerance);
|
|
4322
4975
|
if (combined.length === 1) {
|
|
4976
|
+
// add cumulative error to prevent distortions
|
|
4977
|
+
|
|
4978
|
+
error += combined[0].error * 0.5;
|
|
4979
|
+
|
|
4323
4980
|
offset++;
|
|
4324
4981
|
}
|
|
4325
4982
|
com = combined[0];
|
|
@@ -4351,51 +5008,21 @@
|
|
|
4351
5008
|
return pathDataN
|
|
4352
5009
|
}
|
|
4353
5010
|
|
|
4354
|
-
/**
|
|
4355
|
-
* get viewBox
|
|
4356
|
-
* either from explicit attribute or
|
|
4357
|
-
* width and height attributes
|
|
4358
|
-
*/
|
|
4359
|
-
|
|
4360
|
-
function getViewBox(svg = null, round = false) {
|
|
4361
|
-
|
|
4362
|
-
// browser default
|
|
4363
|
-
if (!svg) return { x: 0, y: 0, width: 300, height: 150 }
|
|
4364
|
-
|
|
4365
|
-
let style = window.getComputedStyle(svg);
|
|
4366
|
-
|
|
4367
|
-
// the baseVal API method also converts physical units to pixels/user-units
|
|
4368
|
-
let w = svg.hasAttribute('width') ? svg.width.baseVal.value : parseFloat(style.width) || 300;
|
|
4369
|
-
let h = svg.hasAttribute('height') ? svg.height.baseVal.value : parseFloat(style.height) || 150;
|
|
4370
|
-
|
|
4371
|
-
let viewBox = svg.getAttribute('viewBox') ? svg.viewBox.baseVal : { x: 0, y: 0, width: w, height: h };
|
|
4372
|
-
|
|
4373
|
-
// remove SVG constructor
|
|
4374
|
-
let { x, y, width, height } = viewBox;
|
|
4375
|
-
viewBox = { x, y, width, height };
|
|
4376
|
-
|
|
4377
|
-
// round to integers
|
|
4378
|
-
if (round) {
|
|
4379
|
-
for (let prop in viewBox) {
|
|
4380
|
-
viewBox[prop] = Math.ceil(viewBox[prop]);
|
|
4381
|
-
}
|
|
4382
|
-
}
|
|
4383
|
-
|
|
4384
|
-
return viewBox
|
|
4385
|
-
}
|
|
4386
|
-
|
|
4387
5011
|
const {
|
|
4388
5012
|
abs, acos, asin, atan, atan2, ceil, cos, exp, floor,
|
|
4389
5013
|
log, hypot, max, min, pow, random, round, sin, sqrt, tan, PI
|
|
4390
5014
|
} = Math;
|
|
4391
5015
|
|
|
4392
|
-
|
|
5016
|
+
/*
|
|
5017
|
+
import {XMLSerializerPoly, DOMParserPoly} from './dom_polyfills';
|
|
5018
|
+
export {XMLSerializerPoly as XMLSerializerPoly};
|
|
5019
|
+
export {DOMParserPoly as DOMParserPoly};
|
|
5020
|
+
*/
|
|
4393
5021
|
|
|
4394
5022
|
// IIFE
|
|
4395
5023
|
if (typeof window !== 'undefined') {
|
|
4396
5024
|
window.svgPathSimplify = svgPathSimplify;
|
|
4397
|
-
|
|
4398
|
-
window.renderPoint = renderPoint;
|
|
5025
|
+
|
|
4399
5026
|
}
|
|
4400
5027
|
|
|
4401
5028
|
exports.PI = PI;
|
|
@@ -4408,7 +5035,6 @@
|
|
|
4408
5035
|
exports.cos = cos;
|
|
4409
5036
|
exports.exp = exp;
|
|
4410
5037
|
exports.floor = floor;
|
|
4411
|
-
exports.getViewBox = getViewBox;
|
|
4412
5038
|
exports.hypot = hypot;
|
|
4413
5039
|
exports.log = log;
|
|
4414
5040
|
exports.max = max;
|