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