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