svg-path-simplify 0.0.7 → 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/dist/svg-path-simplify.esm.js +775 -169
- package/dist/svg-path-simplify.esm.min.js +1 -1
- package/dist/svg-path-simplify.js +4674 -4068
- package/dist/svg-path-simplify.min.js +1 -1
- package/dist/svg-path-simplify.node.js +775 -169
- package/dist/svg-path-simplify.node.min.js +1 -1
- package/index.html +11 -8
- package/package.json +5 -3
- package/src/dom-polyfill.js +29 -0
- package/src/dom-polyfill_back.js +22 -0
- package/src/index.js +7 -1
- package/src/pathData_simplify_cubic.js +1 -37
- package/src/pathData_simplify_cubic_extrapolate.js +43 -28
- package/src/pathSimplify-main.js +50 -9
- 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_parse_els.js +239 -0
- package/src/svgii/pathData_remove_collinear.js +3 -1
- package/src/svgii/pathData_remove_orphaned.js +20 -0
- package/src/svgii/pathData_remove_zerolength.js +12 -2
- package/src/svgii/pathData_reorder.js +3 -1
- package/src/svgii/pathData_split.js +1 -0
- package/src/svgii/rounding.js +25 -19
- package/src/svgii/simplify_refineExtremes.js +173 -0
- package/src/svgii/svg_cleanup.js +4 -1
- package/testSVG.js +39 -0
|
@@ -1,3 +1,42 @@
|
|
|
1
|
+
function renderPoint(
|
|
2
|
+
svg,
|
|
3
|
+
coords,
|
|
4
|
+
fill = "red",
|
|
5
|
+
r = "1%",
|
|
6
|
+
opacity = "1",
|
|
7
|
+
title = '',
|
|
8
|
+
render = true,
|
|
9
|
+
id = "",
|
|
10
|
+
className = ""
|
|
11
|
+
) {
|
|
12
|
+
if (Array.isArray(coords)) {
|
|
13
|
+
coords = {
|
|
14
|
+
x: coords[0],
|
|
15
|
+
y: coords[1]
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
let marker = `<circle class="${className}" opacity="${opacity}" id="${id}" cx="${coords.x}" cy="${coords.y}" r="${r}" fill="${fill}">
|
|
19
|
+
<title>${title}</title></circle>`;
|
|
20
|
+
|
|
21
|
+
if (render) {
|
|
22
|
+
svg.insertAdjacentHTML("beforeend", marker);
|
|
23
|
+
} else {
|
|
24
|
+
return marker;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function renderPath(svg, d = '', stroke = 'green', strokeWidth = '1%', render = true) {
|
|
29
|
+
|
|
30
|
+
let path = `<path d="${d}" fill="none" stroke="${stroke}" stroke-width="${strokeWidth}" /> `;
|
|
31
|
+
|
|
32
|
+
if (render) {
|
|
33
|
+
svg.insertAdjacentHTML("beforeend", path);
|
|
34
|
+
} else {
|
|
35
|
+
return path;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
|
|
1
40
|
function detectInputType(input) {
|
|
2
41
|
let type = 'string';
|
|
3
42
|
/*
|
|
@@ -65,19 +104,24 @@ function getAngle(p1, p2, normalize = false) {
|
|
|
65
104
|
* http://jsfiddle.net/justin_c_rounds/Gd2S2/light/
|
|
66
105
|
*/
|
|
67
106
|
|
|
68
|
-
function checkLineIntersection(p1, p2, p3, p4, exact = true) {
|
|
107
|
+
function checkLineIntersection(p1=null, p2=null, p3=null, p4=null, exact = true, debug=false) {
|
|
69
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
|
|
70
109
|
let denominator, a, b, numerator1, numerator2;
|
|
71
110
|
let intersectionPoint = {};
|
|
72
111
|
|
|
112
|
+
if(!p1 || !p2 || !p3 || !p4){
|
|
113
|
+
if(debug) console.warn('points missing');
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
116
|
+
|
|
73
117
|
try {
|
|
74
118
|
denominator = ((p4.y - p3.y) * (p2.x - p1.x)) - ((p4.x - p3.x) * (p2.y - p1.y));
|
|
75
119
|
if (denominator == 0) {
|
|
76
120
|
return false;
|
|
77
121
|
}
|
|
78
|
-
|
|
79
122
|
} catch {
|
|
80
|
-
console.
|
|
123
|
+
if(debug) console.warn('!catch', p1, p2, 'p3:', p3, 'p4:', p4);
|
|
124
|
+
return false
|
|
81
125
|
}
|
|
82
126
|
|
|
83
127
|
a = p1.y - p3.y;
|
|
@@ -94,8 +138,6 @@ function checkLineIntersection(p1, p2, p3, p4, exact = true) {
|
|
|
94
138
|
y: p1.y + (a * (p2.y - p1.y))
|
|
95
139
|
};
|
|
96
140
|
|
|
97
|
-
// console.log('intersectionPoint', intersectionPoint, p1, p2);
|
|
98
|
-
|
|
99
141
|
let intersection = false;
|
|
100
142
|
// if line1 is a segment and line2 is infinite, they intersect if:
|
|
101
143
|
if ((a > 0 && a < 1) && (b > 0 && b < 1)) {
|
|
@@ -495,6 +537,110 @@ function getBezierExtremeT(pts) {
|
|
|
495
537
|
return tArr;
|
|
496
538
|
}
|
|
497
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
|
+
|
|
498
644
|
// cubic bezier.
|
|
499
645
|
function cubicBezierExtremeT(p0, cp1, cp2, p) {
|
|
500
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];
|
|
@@ -603,90 +749,6 @@ function quadraticBezierExtremeT(p0, cp1, p) {
|
|
|
603
749
|
return extemeT
|
|
604
750
|
}
|
|
605
751
|
|
|
606
|
-
function commandIsFlat(points, tolerance = 0.025) {
|
|
607
|
-
|
|
608
|
-
let p0 = points[0];
|
|
609
|
-
let p = points[points.length - 1];
|
|
610
|
-
|
|
611
|
-
let xArr = points.map(pt => { return pt.x });
|
|
612
|
-
let yArr = points.map(pt => { return pt.y });
|
|
613
|
-
|
|
614
|
-
let xMin = Math.min(...xArr);
|
|
615
|
-
let xMax = Math.max(...xArr);
|
|
616
|
-
let yMin = Math.min(...yArr);
|
|
617
|
-
let yMax = Math.max(...yArr);
|
|
618
|
-
let w = xMax - xMin;
|
|
619
|
-
let h = yMax - yMin;
|
|
620
|
-
|
|
621
|
-
if (points.length < 3 || (w === 0 || h === 0)) {
|
|
622
|
-
return { area: 0, flat: true, thresh: 0.0001, ratio: 0 };
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
let squareDist = getSquareDistance(p0, p);
|
|
626
|
-
let squareDist1 = getSquareDistance(p0, points[0]);
|
|
627
|
-
let squareDist2 = points.length > 3 ? getSquareDistance(p, points[1]) : squareDist1;
|
|
628
|
-
let squareDistAvg = (squareDist1 + squareDist2) / 2;
|
|
629
|
-
|
|
630
|
-
tolerance = 0.5;
|
|
631
|
-
let thresh = (w + h) * 0.5 * tolerance;
|
|
632
|
-
|
|
633
|
-
let area = 0;
|
|
634
|
-
for (let i = 0, l = points.length; i < l; i++) {
|
|
635
|
-
let addX = points[i].x;
|
|
636
|
-
let addY = points[i === points.length - 1 ? 0 : i + 1].y;
|
|
637
|
-
let subX = points[i === points.length - 1 ? 0 : i + 1].x;
|
|
638
|
-
let subY = points[i].y;
|
|
639
|
-
area += addX * addY * 0.5 - subX * subY * 0.5;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
area = +Math.abs(area).toFixed(9);
|
|
643
|
-
let areaThresh = 1000;
|
|
644
|
-
|
|
645
|
-
let ratio = area / (squareDistAvg);
|
|
646
|
-
|
|
647
|
-
let isFlat = area === 0 ? true : area < squareDistAvg / areaThresh;
|
|
648
|
-
|
|
649
|
-
return { area: area, flat: isFlat, thresh: thresh, ratio: ratio, squareDist: squareDist, areaThresh: squareDist / areaThresh };
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
function checkBezierFlatness(p0, cpts, p) {
|
|
653
|
-
|
|
654
|
-
let isFlat = false;
|
|
655
|
-
|
|
656
|
-
let isCubic = cpts.length===2;
|
|
657
|
-
|
|
658
|
-
let cp1 = cpts[0];
|
|
659
|
-
let cp2 = isCubic ? cpts[1] : cp1;
|
|
660
|
-
|
|
661
|
-
if(p0.x===cp1.x && p0.y===cp1.y && p.x===cp2.x && p.y===cp2.y) return true;
|
|
662
|
-
|
|
663
|
-
let dx1 = cp1.x - p0.x;
|
|
664
|
-
let dy1 = cp1.y - p0.y;
|
|
665
|
-
|
|
666
|
-
let dx2 = p.x - cp2.x;
|
|
667
|
-
let dy2 = p.y - cp2.y;
|
|
668
|
-
|
|
669
|
-
let cross1 = Math.abs(dx1 * dy2 - dy1 * dx2);
|
|
670
|
-
|
|
671
|
-
if(!cross1) return true
|
|
672
|
-
|
|
673
|
-
let dx0 = p.x - p0.x;
|
|
674
|
-
let dy0 = p.y - p0.y;
|
|
675
|
-
let cross0 = Math.abs(dx0 * dy1 - dy0 * dx1);
|
|
676
|
-
|
|
677
|
-
if(!cross0) return true
|
|
678
|
-
|
|
679
|
-
let rat = (cross0/cross1);
|
|
680
|
-
|
|
681
|
-
if (rat<1.1 ) {
|
|
682
|
-
|
|
683
|
-
isFlat = true;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
return isFlat;
|
|
687
|
-
|
|
688
|
-
}
|
|
689
|
-
|
|
690
752
|
/**
|
|
691
753
|
* sloppy distance calculation
|
|
692
754
|
* based on x/y differences
|
|
@@ -1154,6 +1216,73 @@ function getPathDataPoly(pathData) {
|
|
|
1154
1216
|
return poly;
|
|
1155
1217
|
}
|
|
1156
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
|
+
|
|
1157
1286
|
/**
|
|
1158
1287
|
* get pathdata area
|
|
1159
1288
|
*/
|
|
@@ -1429,7 +1558,7 @@ function pathDataToD(pathData, optimize = 0) {
|
|
|
1429
1558
|
return d;
|
|
1430
1559
|
}
|
|
1431
1560
|
|
|
1432
|
-
function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1) {
|
|
1561
|
+
function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, debug = false) {
|
|
1433
1562
|
|
|
1434
1563
|
// cubic Bézier derivative
|
|
1435
1564
|
const cubicDerivative = (p0, p1, p2, p3, t) => {
|
|
@@ -1451,8 +1580,9 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1) {
|
|
|
1451
1580
|
let commands = [com1, com2];
|
|
1452
1581
|
|
|
1453
1582
|
// detect dominant
|
|
1454
|
-
let dist1 =
|
|
1455
|
-
let dist2 =
|
|
1583
|
+
let dist1 = getDistAv(com1.p0, com1.p);
|
|
1584
|
+
let dist2 = getDistAv(com2.p0, com2.p);
|
|
1585
|
+
|
|
1456
1586
|
let reverse = dist1 > dist2;
|
|
1457
1587
|
|
|
1458
1588
|
// backup original commands
|
|
@@ -1511,11 +1641,9 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1) {
|
|
|
1511
1641
|
let dP = cubicDerivative(com2.p0, com2.cp1, com2.cp2, com2.p, t0);
|
|
1512
1642
|
let r = sub(P, com1.p0);
|
|
1513
1643
|
|
|
1514
|
-
|
|
1515
1644
|
t0 -= dot(r, dP) / dot(dP, dP);
|
|
1516
1645
|
|
|
1517
1646
|
// construct merged cubic over [t0, 1]
|
|
1518
|
-
|
|
1519
1647
|
let Q0 = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], t0);
|
|
1520
1648
|
let Q3 = com2.p;
|
|
1521
1649
|
|
|
@@ -1543,7 +1671,7 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1) {
|
|
|
1543
1671
|
};
|
|
1544
1672
|
}
|
|
1545
1673
|
|
|
1546
|
-
let tMid = (1 - t0)*0.5
|
|
1674
|
+
let tMid = (1 - t0) * 0.5;
|
|
1547
1675
|
|
|
1548
1676
|
let ptM = pointAtT([result.p0, result.cp1, result.cp2, result.p], tMid, false, true);
|
|
1549
1677
|
let seg1_cp2 = ptM.cpts[2];
|
|
@@ -1555,17 +1683,18 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1) {
|
|
|
1555
1683
|
let cp2_2 = interpolate(result.p, ptI_2, 1.333);
|
|
1556
1684
|
|
|
1557
1685
|
// test self intersections and exit
|
|
1558
|
-
let cp_intersection = checkLineIntersection(com1_o.p0, cp1_2, com2_o.p, cp2_2, true
|
|
1559
|
-
if(cp_intersection){
|
|
1686
|
+
let cp_intersection = checkLineIntersection(com1_o.p0, cp1_2, com2_o.p, cp2_2, true);
|
|
1687
|
+
if (cp_intersection) {
|
|
1560
1688
|
|
|
1561
1689
|
return commands;
|
|
1562
1690
|
}
|
|
1563
1691
|
|
|
1692
|
+
if (debug) renderPoint(markers, ptM, 'purple');
|
|
1693
|
+
|
|
1564
1694
|
result.cp1 = cp1_2;
|
|
1565
1695
|
result.cp2 = cp2_2;
|
|
1566
1696
|
|
|
1567
|
-
// check distances
|
|
1568
|
-
|
|
1697
|
+
// check distances between original starting point and extrapolated
|
|
1569
1698
|
let dist3 = getDistAv(com1_o.p0, result.p0);
|
|
1570
1699
|
let dist4 = getDistAv(com2_o.p, result.p);
|
|
1571
1700
|
let dist5 = (dist3 + dist4);
|
|
@@ -1577,11 +1706,24 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1) {
|
|
|
1577
1706
|
result.corner = com2_o.corner;
|
|
1578
1707
|
result.dimA = com2_o.dimA;
|
|
1579
1708
|
result.directionChange = com2_o.directionChange;
|
|
1709
|
+
result.type = 'C';
|
|
1580
1710
|
result.values = [result.cp1.x, result.cp1.y, result.cp2.x, result.cp2.y, result.p.x, result.p.y];
|
|
1581
1711
|
|
|
1582
|
-
//
|
|
1712
|
+
// extrapolated starting point is not completely off
|
|
1583
1713
|
if (dist5 < maxDist) {
|
|
1584
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
|
+
|
|
1585
1727
|
// compare combined with original area
|
|
1586
1728
|
let pathData0 = [
|
|
1587
1729
|
{ type: 'M', values: [com1_o.p0.x, com1_o.p0.y] },
|
|
@@ -1598,16 +1740,18 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1) {
|
|
|
1598
1740
|
let areaN = getPathArea(pathDataN);
|
|
1599
1741
|
let areaDiff = Math.abs(areaN / area0 - 1);
|
|
1600
1742
|
|
|
1601
|
-
result.error = areaDiff *
|
|
1743
|
+
result.error = areaDiff * 5 * tolerance;
|
|
1602
1744
|
|
|
1603
|
-
|
|
1745
|
+
if (debug) {
|
|
1746
|
+
let d = pathDataToD(pathDataN);
|
|
1747
|
+
renderPath(markers, d, 'orange');
|
|
1748
|
+
}
|
|
1604
1749
|
|
|
1605
|
-
// success
|
|
1606
|
-
if (areaDiff < 0.
|
|
1750
|
+
// success!!!
|
|
1751
|
+
if (areaDiff < 0.05 * tolerance) {
|
|
1607
1752
|
commands = [result];
|
|
1608
1753
|
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1754
|
+
}
|
|
1611
1755
|
}
|
|
1612
1756
|
|
|
1613
1757
|
return commands
|
|
@@ -1691,14 +1835,6 @@ function combineCubicPairs(com1, com2, extrapolateDominant = false, tolerance =
|
|
|
1691
1835
|
} // end 1st try
|
|
1692
1836
|
|
|
1693
1837
|
|
|
1694
|
-
/*
|
|
1695
|
-
if (extrapolateDominant && com2.extreme) {
|
|
1696
|
-
renderPoint(markers, com2.p)
|
|
1697
|
-
|
|
1698
|
-
}
|
|
1699
|
-
*/
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
1838
|
|
|
1703
1839
|
// try extrapolated dominant curve
|
|
1704
1840
|
|
|
@@ -1767,25 +1903,59 @@ function getExtrapolatedCommand(com1, com2, t1 = 0, t2 = 0) {
|
|
|
1767
1903
|
|
|
1768
1904
|
function findSplitT(com1, com2) {
|
|
1769
1905
|
|
|
1770
|
-
|
|
1771
|
-
let
|
|
1906
|
+
let len3 = getDistance(com1.cp2, com1.p);
|
|
1907
|
+
let len4 = getDistance(com1.cp2, com2.cp1);
|
|
1772
1908
|
|
|
1773
|
-
|
|
1774
|
-
let ptI = checkLineIntersection(pt1, com2.p, com2.p0, com2.cp1, false);
|
|
1909
|
+
let t = Math.min(len3) / len4;
|
|
1775
1910
|
|
|
1776
|
-
|
|
1777
|
-
|
|
1911
|
+
return t
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
function checkBezierFlatness(p0, cpts, p) {
|
|
1778
1915
|
|
|
1779
|
-
let
|
|
1916
|
+
let isFlat = false;
|
|
1780
1917
|
|
|
1781
|
-
|
|
1918
|
+
let isCubic = cpts.length === 2;
|
|
1782
1919
|
|
|
1783
|
-
let
|
|
1784
|
-
let
|
|
1920
|
+
let cp1 = cpts[0];
|
|
1921
|
+
let cp2 = isCubic ? cpts[1] : cp1;
|
|
1785
1922
|
|
|
1786
|
-
|
|
1923
|
+
if (p0.x === cp1.x && p0.y === cp1.y && p.x === cp2.x && p.y === cp2.y) return true;
|
|
1787
1924
|
|
|
1788
|
-
|
|
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;
|
|
1789
1959
|
|
|
1790
1960
|
}
|
|
1791
1961
|
|
|
@@ -1831,7 +2001,6 @@ function analyzePathData(pathData = []) {
|
|
|
1831
2001
|
* this way we can skip certain tests
|
|
1832
2002
|
*/
|
|
1833
2003
|
let commandPts = [p0];
|
|
1834
|
-
let isFlat = false;
|
|
1835
2004
|
|
|
1836
2005
|
// init properties
|
|
1837
2006
|
com.idx = c - 1;
|
|
@@ -1862,7 +2031,7 @@ function analyzePathData(pathData = []) {
|
|
|
1862
2031
|
com.p0 = p0;
|
|
1863
2032
|
com.p = p;
|
|
1864
2033
|
|
|
1865
|
-
let cp1, cp2, cp1N,
|
|
2034
|
+
let cp1, cp2, cp1N, pN, typeN, area1;
|
|
1866
2035
|
|
|
1867
2036
|
let dimA = getDistAv(p0, p);
|
|
1868
2037
|
com.dimA = dimA;
|
|
@@ -1902,6 +2071,7 @@ function analyzePathData(pathData = []) {
|
|
|
1902
2071
|
if (type === 'C') commandPts.push(cp2);
|
|
1903
2072
|
commandPts.push(p);
|
|
1904
2073
|
|
|
2074
|
+
/*
|
|
1905
2075
|
let commandFlatness = commandIsFlat(commandPts);
|
|
1906
2076
|
isFlat = commandFlatness.flat;
|
|
1907
2077
|
com.flat = isFlat;
|
|
@@ -1909,6 +2079,7 @@ function analyzePathData(pathData = []) {
|
|
|
1909
2079
|
if (isFlat) {
|
|
1910
2080
|
com.extreme = false;
|
|
1911
2081
|
}
|
|
2082
|
+
*/
|
|
1912
2083
|
}
|
|
1913
2084
|
|
|
1914
2085
|
/**
|
|
@@ -1917,7 +2088,7 @@ function analyzePathData(pathData = []) {
|
|
|
1917
2088
|
* so we interpret maximum x/y on-path points as well as extremes
|
|
1918
2089
|
* but we ignore linetos to allow chunk compilation
|
|
1919
2090
|
*/
|
|
1920
|
-
if (
|
|
2091
|
+
if (type !== 'L' && (p.x === left || p.y === top || p.x === right || p.y === bottom)) {
|
|
1921
2092
|
com.extreme = true;
|
|
1922
2093
|
}
|
|
1923
2094
|
|
|
@@ -1930,7 +2101,7 @@ function analyzePathData(pathData = []) {
|
|
|
1930
2101
|
pN = comN ? { x: comNValsL[0], y: comNValsL[1] } : null;
|
|
1931
2102
|
|
|
1932
2103
|
cp1N = { x: comN.values[0], y: comN.values[1] };
|
|
1933
|
-
|
|
2104
|
+
comN.type === 'C' ? { x: comN.values[2], y: comN.values[3] } : null;
|
|
1934
2105
|
}
|
|
1935
2106
|
|
|
1936
2107
|
/**
|
|
@@ -1960,19 +2131,15 @@ function analyzePathData(pathData = []) {
|
|
|
1960
2131
|
// check extremes
|
|
1961
2132
|
let cpts = commandPts.slice(1);
|
|
1962
2133
|
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
let thresh = (w + h) / 2 * 0.1;
|
|
1966
|
-
let pts1 = type === 'C' ? [p, cp1N, cp2N, pN] : [p, cp1N, pN];
|
|
1967
|
-
|
|
1968
|
-
let flatness2 = commandIsFlat(pts1, thresh);
|
|
1969
|
-
let isFlat2 = flatness2.flat;
|
|
2134
|
+
pN ? Math.abs(pN.x - p0.x) : 0;
|
|
2135
|
+
pN ? Math.abs(pN.y - p0.y) : 0;
|
|
1970
2136
|
|
|
1971
2137
|
/**
|
|
1972
2138
|
* if current and next cubic are flat
|
|
1973
2139
|
* we don't flag them as extremes to allow simplification
|
|
1974
2140
|
*/
|
|
1975
|
-
|
|
2141
|
+
|
|
2142
|
+
let hasExtremes = (!com.extreme ? bezierhasExtreme(p0, cpts, angleThreshold) : true);
|
|
1976
2143
|
|
|
1977
2144
|
if (hasExtremes) {
|
|
1978
2145
|
com.extreme = true;
|
|
@@ -2018,7 +2185,7 @@ function detectAccuracy(pathData) {
|
|
|
2018
2185
|
|
|
2019
2186
|
// Reference first MoveTo command (M)
|
|
2020
2187
|
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
2021
|
-
let p0 =
|
|
2188
|
+
let p0 = M;
|
|
2022
2189
|
let p = M;
|
|
2023
2190
|
pathData[0].decimals = 0;
|
|
2024
2191
|
|
|
@@ -2030,28 +2197,33 @@ function detectAccuracy(pathData) {
|
|
|
2030
2197
|
let { type, values } = com;
|
|
2031
2198
|
|
|
2032
2199
|
let lastVals = values.length ? values.slice(-2) : [M.x, M.y];
|
|
2033
|
-
p={x:lastVals[0], y:lastVals[1]};
|
|
2200
|
+
p = { x: lastVals[0], y: lastVals[1] };
|
|
2034
2201
|
|
|
2035
2202
|
// use existing averave dimension value or calculate
|
|
2036
|
-
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;
|
|
2037
2204
|
|
|
2038
|
-
if(dimA) dims.add(dimA);
|
|
2039
|
-
|
|
2205
|
+
if (dimA) dims.add(dimA);
|
|
2040
2206
|
|
|
2041
|
-
if(type==='M'){
|
|
2042
|
-
M=p;
|
|
2207
|
+
if (type === 'M') {
|
|
2208
|
+
M = p;
|
|
2043
2209
|
}
|
|
2044
2210
|
p0 = p;
|
|
2045
2211
|
}
|
|
2046
2212
|
|
|
2047
2213
|
let dim_min = Array.from(dims).sort();
|
|
2048
|
-
let sliceIdx = Math.ceil(dim_min.length/8);
|
|
2049
|
-
dim_min = dim_min.slice(0, sliceIdx );
|
|
2050
2214
|
|
|
2051
|
-
|
|
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;
|
|
2052
2224
|
|
|
2053
|
-
let threshold =
|
|
2054
|
-
let decimalsAuto =
|
|
2225
|
+
let threshold = 40;
|
|
2226
|
+
let decimalsAuto = minVal > threshold*1.5 ? 0 : Math.floor(threshold / minVal).toString().length;
|
|
2055
2227
|
|
|
2056
2228
|
// clamp
|
|
2057
2229
|
return Math.min(Math.max(0, decimalsAuto), 8)
|
|
@@ -2067,13 +2239,13 @@ function roundPathData(pathData, decimals = -1) {
|
|
|
2067
2239
|
// has recommended decimals
|
|
2068
2240
|
let hasDecimal = decimals == 'auto' && pathData[0].hasOwnProperty('decimals') ? true : false;
|
|
2069
2241
|
|
|
2070
|
-
for(let c=0, len=pathData.length; c<len; c++){
|
|
2071
|
-
let com=pathData[c];
|
|
2242
|
+
for (let c = 0, len = pathData.length; c < len; c++) {
|
|
2243
|
+
let com = pathData[c];
|
|
2072
2244
|
|
|
2073
|
-
if (decimals
|
|
2245
|
+
if (decimals > -1 || hasDecimal) {
|
|
2074
2246
|
decimals = hasDecimal ? com.decimals : decimals;
|
|
2075
2247
|
|
|
2076
|
-
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 });
|
|
2077
2249
|
|
|
2078
2250
|
}
|
|
2079
2251
|
} return pathData;
|
|
@@ -3463,6 +3635,242 @@ function parsePathDataString(d, debug = true) {
|
|
|
3463
3635
|
|
|
3464
3636
|
}
|
|
3465
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
|
+
|
|
3466
3874
|
function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLinetos = true) {
|
|
3467
3875
|
|
|
3468
3876
|
let pathDataN = [pathData[0]];
|
|
@@ -3502,6 +3910,7 @@ function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLinetos = t
|
|
|
3502
3910
|
(type === 'Q' ? [{ x: values[0], y: values[1] }] : []);
|
|
3503
3911
|
|
|
3504
3912
|
isFlatBez = checkBezierFlatness(p0, cpts, p);
|
|
3913
|
+
|
|
3505
3914
|
// console.log();
|
|
3506
3915
|
|
|
3507
3916
|
if (isFlatBez && c < l - 1 && comPrev.type !== 'C') {
|
|
@@ -3543,20 +3952,43 @@ function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLinetos = t
|
|
|
3543
3952
|
|
|
3544
3953
|
}
|
|
3545
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
|
+
/*
|
|
3546
3977
|
// remove zero-length segments introduced by rounding
|
|
3547
|
-
function removeZeroLengthLinetos_post(pathData) {
|
|
3548
|
-
let pathDataOpt = []
|
|
3978
|
+
export function removeZeroLengthLinetos_post(pathData) {
|
|
3979
|
+
let pathDataOpt = []
|
|
3549
3980
|
pathData.forEach((com, i) => {
|
|
3550
3981
|
let { type, values } = com;
|
|
3551
3982
|
if (type === 'l' || type === 'v' || type === 'h') {
|
|
3552
|
-
let hasLength = type === 'l' ? (values.join('') !== '00') : values[0] !== 0
|
|
3553
|
-
if (hasLength) pathDataOpt.push(com)
|
|
3983
|
+
let hasLength = type === 'l' ? (values.join('') !== '00') : values[0] !== 0
|
|
3984
|
+
if (hasLength) pathDataOpt.push(com)
|
|
3554
3985
|
} else {
|
|
3555
|
-
pathDataOpt.push(com)
|
|
3986
|
+
pathDataOpt.push(com)
|
|
3556
3987
|
}
|
|
3557
|
-
})
|
|
3988
|
+
})
|
|
3558
3989
|
return pathDataOpt
|
|
3559
3990
|
}
|
|
3991
|
+
*/
|
|
3560
3992
|
|
|
3561
3993
|
function removeZeroLengthLinetos(pathData) {
|
|
3562
3994
|
|
|
@@ -3570,14 +4002,21 @@ function removeZeroLengthLinetos(pathData) {
|
|
|
3570
4002
|
let com = pathData[c];
|
|
3571
4003
|
let { type, values } = com;
|
|
3572
4004
|
|
|
3573
|
-
let
|
|
3574
|
-
|
|
4005
|
+
let valsLen = values.length;
|
|
4006
|
+
|
|
4007
|
+
p = { x: values[valsLen-2], y: values[valsLen-1] };
|
|
3575
4008
|
|
|
3576
4009
|
// skip lineto
|
|
3577
4010
|
if (type === 'L' && p.x === p0.x && p.y === p0.y) {
|
|
3578
4011
|
continue
|
|
3579
4012
|
}
|
|
3580
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
|
+
|
|
3581
4020
|
pathDataN.push(com);
|
|
3582
4021
|
p0 = p;
|
|
3583
4022
|
}
|
|
@@ -3916,6 +4355,145 @@ function reversePathData(pathData, {
|
|
|
3916
4355
|
return pathDataNew;
|
|
3917
4356
|
}
|
|
3918
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
|
+
|
|
3919
4497
|
function removeEmptySVGEls(svg) {
|
|
3920
4498
|
let els = svg.querySelectorAll('g, defs');
|
|
3921
4499
|
els.forEach(el => {
|
|
@@ -3938,7 +4516,7 @@ function cleanUpSVG(svgMarkup, {
|
|
|
3938
4516
|
.querySelector("svg");
|
|
3939
4517
|
|
|
3940
4518
|
|
|
3941
|
-
let allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class'];
|
|
4519
|
+
let allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class', 'fill', 'stroke', 'stroke-width'];
|
|
3942
4520
|
removeExcludedAttribues(svg, allowed);
|
|
3943
4521
|
|
|
3944
4522
|
let removeEls = ['metadata', 'script'];
|
|
@@ -4031,11 +4609,13 @@ function svgPathSimplify(input = '', {
|
|
|
4031
4609
|
flatBezierToLinetos = true,
|
|
4032
4610
|
revertToQuadratics = true,
|
|
4033
4611
|
|
|
4612
|
+
refineExtremes = true,
|
|
4034
4613
|
keepExtremes = true,
|
|
4035
4614
|
keepCorners = true,
|
|
4036
4615
|
extrapolateDominant = true,
|
|
4037
4616
|
keepInflections = false,
|
|
4038
4617
|
addExtremes = false,
|
|
4618
|
+
removeOrphanSubpaths = false,
|
|
4039
4619
|
|
|
4040
4620
|
// svg path optimizations
|
|
4041
4621
|
decimals = 3,
|
|
@@ -4049,6 +4629,7 @@ function svgPathSimplify(input = '', {
|
|
|
4049
4629
|
mergePaths = false,
|
|
4050
4630
|
removeHidden = true,
|
|
4051
4631
|
removeUnused = true,
|
|
4632
|
+
shapesToPaths = true,
|
|
4052
4633
|
|
|
4053
4634
|
} = {}) {
|
|
4054
4635
|
|
|
@@ -4091,6 +4672,14 @@ function svgPathSimplify(input = '', {
|
|
|
4091
4672
|
svg = cleanUpSVG(input, { returnDom, removeHidden, removeUnused }
|
|
4092
4673
|
);
|
|
4093
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
|
+
|
|
4094
4683
|
// collect paths
|
|
4095
4684
|
let pathEls = svg.querySelectorAll('path');
|
|
4096
4685
|
pathEls.forEach(path => {
|
|
@@ -4117,12 +4706,15 @@ function svgPathSimplify(input = '', {
|
|
|
4117
4706
|
|
|
4118
4707
|
let pathDataO = parsePathDataNormalized(d, { quadraticToCubic, toAbsolute, arcToCubic });
|
|
4119
4708
|
|
|
4120
|
-
// create clone for fallback
|
|
4121
|
-
let pathData = JSON.parse(JSON.stringify(pathDataO));
|
|
4122
|
-
|
|
4123
4709
|
// count commands for evaluation
|
|
4124
4710
|
let comCount = pathDataO.length;
|
|
4125
4711
|
|
|
4712
|
+
// create clone for fallback
|
|
4713
|
+
|
|
4714
|
+
let pathData = pathDataO;
|
|
4715
|
+
|
|
4716
|
+
if(removeOrphanSubpaths) pathData = removeOrphanedM(pathData);
|
|
4717
|
+
|
|
4126
4718
|
/**
|
|
4127
4719
|
* get sub paths
|
|
4128
4720
|
*/
|
|
@@ -4160,6 +4752,12 @@ function svgPathSimplify(input = '', {
|
|
|
4160
4752
|
|
|
4161
4753
|
pathData = simplifyBezier ? simplifyPathDataCubic(pathData, { simplifyBezier, keepInflections, keepExtremes, keepCorners, extrapolateDominant, revertToQuadratics, tolerance, reverse }) : pathData;
|
|
4162
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
|
+
}
|
|
4760
|
+
|
|
4163
4761
|
// cubic to arcs
|
|
4164
4762
|
if (cubicToArc) {
|
|
4165
4763
|
|
|
@@ -4227,13 +4825,15 @@ function svgPathSimplify(input = '', {
|
|
|
4227
4825
|
*/
|
|
4228
4826
|
if (autoAccuracy) {
|
|
4229
4827
|
decimals = detectAccuracy(pathData);
|
|
4828
|
+
pathOptions.decimals = decimals;
|
|
4829
|
+
|
|
4230
4830
|
}
|
|
4231
4831
|
|
|
4232
4832
|
// optimize path data
|
|
4233
4833
|
pathData = convertPathData(pathData, pathOptions);
|
|
4234
4834
|
|
|
4235
4835
|
// remove zero-length segments introduced by rounding
|
|
4236
|
-
pathData =
|
|
4836
|
+
pathData = removeZeroLengthLinetos(pathData);
|
|
4237
4837
|
|
|
4238
4838
|
// compare command count
|
|
4239
4839
|
let comCountS = pathData.length;
|
|
@@ -4267,7 +4867,8 @@ function svgPathSimplify(input = '', {
|
|
|
4267
4867
|
let pathData = convertPathData(pathData_merged, pathOptions);
|
|
4268
4868
|
|
|
4269
4869
|
// remove zero-length segments introduced by rounding
|
|
4270
|
-
|
|
4870
|
+
|
|
4871
|
+
pathData = removeZeroLengthLinetos(pathData);
|
|
4271
4872
|
|
|
4272
4873
|
let dOpt = pathDataToD(pathData, minifyD);
|
|
4273
4874
|
|
|
@@ -4372,6 +4973,7 @@ function simplifyPathDataCubic(pathData, {
|
|
|
4372
4973
|
// add cumulative error to prevent distortions
|
|
4373
4974
|
|
|
4374
4975
|
error += combined[0].error * 0.5;
|
|
4976
|
+
|
|
4375
4977
|
offset++;
|
|
4376
4978
|
}
|
|
4377
4979
|
com = combined[0];
|
|
@@ -4408,7 +5010,11 @@ const {
|
|
|
4408
5010
|
log, hypot, max, min, pow, random, round, sin, sqrt, tan, PI
|
|
4409
5011
|
} = Math;
|
|
4410
5012
|
|
|
4411
|
-
|
|
5013
|
+
/*
|
|
5014
|
+
import {XMLSerializerPoly, DOMParserPoly} from './dom_polyfills';
|
|
5015
|
+
export {XMLSerializerPoly as XMLSerializerPoly};
|
|
5016
|
+
export {DOMParserPoly as DOMParserPoly};
|
|
5017
|
+
*/
|
|
4412
5018
|
|
|
4413
5019
|
// IIFE
|
|
4414
5020
|
if (typeof window !== 'undefined') {
|