svg-path-simplify 0.0.2 → 0.0.5
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/LICENSE +339 -0
- package/README.md +33 -1
- package/dist/svg-path-simplify.esm.js +489 -131
- package/dist/svg-path-simplify.esm.min.js +1 -1
- package/dist/svg-path-simplify.js +489 -130
- package/dist/svg-path-simplify.min.js +1 -1
- package/dist/svg-path-simplify.node.js +489 -130
- package/dist/svg-path-simplify.node.min.js +1 -1
- package/index.html +17 -9
- package/package.json +4 -5
- package/src/detect_input.js +42 -0
- package/src/index.js +3 -0
- package/src/pathSimplify-main.js +198 -90
- package/src/svg_getViewbox.js +32 -0
- package/src/svgii/geometry.js +51 -4
- package/src/svgii/pathData_remove_collinear.js +31 -18
- package/src/svgii/pathData_reorder.js +50 -11
- package/src/svgii/pathData_stringify.js +11 -12
- package/src/svgii/rounding.js +14 -6
- package/src/svgii/svg_cleanup.js +7 -1
|
@@ -27,6 +27,48 @@ function renderPoint(
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function detectInputType(input) {
|
|
31
|
+
let type = 'string';
|
|
32
|
+
if (input instanceof HTMLImageElement) return "img";
|
|
33
|
+
if (input instanceof SVGElement) return "svg";
|
|
34
|
+
if (input instanceof HTMLCanvasElement) return "canvas";
|
|
35
|
+
if (input instanceof File) return "file";
|
|
36
|
+
if (input instanceof ArrayBuffer) return "buffer";
|
|
37
|
+
if (input instanceof Blob) return "blob";
|
|
38
|
+
if (Array.isArray(input)) return "array";
|
|
39
|
+
|
|
40
|
+
if (typeof input === "string") {
|
|
41
|
+
input = input.trim();
|
|
42
|
+
let isSVG = input.includes('<svg') && input.includes('</svg');
|
|
43
|
+
let isPathData = input.startsWith('M') || input.startsWith('m');
|
|
44
|
+
let isPolyString = !isNaN(input.substring(0, 1)) && !isNaN(input.substring(input.length-1, input.length));
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if(isSVG) {
|
|
48
|
+
type='svgMarkup';
|
|
49
|
+
}
|
|
50
|
+
else if(isPathData) {
|
|
51
|
+
type='pathDataString';
|
|
52
|
+
}
|
|
53
|
+
else if(isPolyString) {
|
|
54
|
+
type='polyString';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
else {
|
|
58
|
+
let url = /^(file:|https?:\/\/|\/|\.\/|\.\.\/)/.test(input);
|
|
59
|
+
let dataUrl = input.startsWith('data:image');
|
|
60
|
+
type = url || dataUrl ? "url" : "string";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return type
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
type = typeof input;
|
|
67
|
+
let constructor = input.constructor.name;
|
|
68
|
+
|
|
69
|
+
return (constructor || type).toLowerCase();
|
|
70
|
+
}
|
|
71
|
+
|
|
30
72
|
/*
|
|
31
73
|
import {abs, acos, asin, atan, atan2, ceil, cos, exp, floor,
|
|
32
74
|
log, max, min, pow, random, round, sin, sqrt, tan, PI} from '/.constants.js';
|
|
@@ -79,7 +121,7 @@ function checkLineIntersection(p1, p2, p3, p4, exact = true) {
|
|
|
79
121
|
y: p1.y + (a * (p2.y - p1.y))
|
|
80
122
|
};
|
|
81
123
|
|
|
82
|
-
|
|
124
|
+
// console.log('intersectionPoint', intersectionPoint, p1, p2);
|
|
83
125
|
|
|
84
126
|
let intersection = false;
|
|
85
127
|
// if line1 is a segment and line2 is infinite, they intersect if:
|
|
@@ -634,14 +676,60 @@ function commandIsFlat(points, tolerance = 0.025) {
|
|
|
634
676
|
return { area: area, flat: isFlat, thresh: thresh, ratio: ratio, squareDist: squareDist, areaThresh: squareDist / areaThresh };
|
|
635
677
|
}
|
|
636
678
|
|
|
679
|
+
function checkBezierFlatness(p0, cpts, p) {
|
|
680
|
+
|
|
681
|
+
let isFlat = false;
|
|
682
|
+
|
|
683
|
+
let isCubic = cpts.length===2;
|
|
684
|
+
|
|
685
|
+
let cp1 = cpts[0];
|
|
686
|
+
let cp2 = isCubic ? cpts[1] : cp1;
|
|
687
|
+
|
|
688
|
+
if(p0.x===cp1.x && p0.y===cp1.y && p.x===cp2.x && p.y===cp2.y) return true;
|
|
689
|
+
|
|
690
|
+
let dx1 = cp1.x - p0.x;
|
|
691
|
+
let dy1 = cp1.y - p0.y;
|
|
692
|
+
|
|
693
|
+
let dx2 = p.x - cp2.x;
|
|
694
|
+
let dy2 = p.y - cp2.y;
|
|
695
|
+
|
|
696
|
+
let cross1 = Math.abs(dx1 * dy2 - dy1 * dx2);
|
|
697
|
+
|
|
698
|
+
if(!cross1) return true
|
|
699
|
+
|
|
700
|
+
let dx0 = p.x - p0.x;
|
|
701
|
+
let dy0 = p.y - p0.y;
|
|
702
|
+
let cross0 = Math.abs(dx0 * dy1 - dy0 * dx1);
|
|
703
|
+
|
|
704
|
+
if(!cross0) return true
|
|
705
|
+
|
|
706
|
+
let rat = (cross0/cross1);
|
|
707
|
+
|
|
708
|
+
if (rat<1.1 ) {
|
|
709
|
+
|
|
710
|
+
isFlat = true;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
return isFlat;
|
|
714
|
+
|
|
715
|
+
}
|
|
716
|
+
|
|
637
717
|
/**
|
|
638
718
|
* sloppy distance calculation
|
|
639
719
|
* based on x/y differences
|
|
640
720
|
*/
|
|
641
721
|
function getDistAv(pt1, pt2) {
|
|
642
|
-
|
|
643
|
-
let
|
|
722
|
+
|
|
723
|
+
let diffX = Math.abs(pt2.x - pt1.x);
|
|
724
|
+
let diffY = Math.abs(pt2.y - pt1.y);
|
|
644
725
|
let diff = (diffX + diffY) / 2;
|
|
726
|
+
|
|
727
|
+
/*
|
|
728
|
+
let diffX = pt2.x - pt1.x;
|
|
729
|
+
let diffY = pt2.y - pt1.y;
|
|
730
|
+
let diff = Math.abs(diffX + diffY) / 2;
|
|
731
|
+
*/
|
|
732
|
+
|
|
645
733
|
return diff;
|
|
646
734
|
}
|
|
647
735
|
|
|
@@ -1281,24 +1369,24 @@ function pathDataToD(pathData, optimize = 0) {
|
|
|
1281
1369
|
|
|
1282
1370
|
optimize = parseFloat(optimize);
|
|
1283
1371
|
|
|
1372
|
+
let len = pathData.length;
|
|
1284
1373
|
let beautify = optimize > 1;
|
|
1285
1374
|
let minify = beautify || optimize ? false : true;
|
|
1286
1375
|
|
|
1287
1376
|
// Convert first "M" to "m" if followed by "l" (when minified)
|
|
1377
|
+
/*
|
|
1288
1378
|
if (pathData[1].type === "l" && minify) {
|
|
1289
1379
|
pathData[0].type = "m";
|
|
1290
1380
|
}
|
|
1381
|
+
*/
|
|
1291
1382
|
|
|
1292
1383
|
let d = '';
|
|
1293
|
-
let
|
|
1384
|
+
let separator_command = beautify ? `\n` : (minify ? '' : ' ');
|
|
1385
|
+
let separator_type = !minify ? ' ' : '';
|
|
1294
1386
|
|
|
1295
|
-
|
|
1296
|
-
d = `${pathData[0].type} ${pathData[0].values.join(" ")}`;
|
|
1297
|
-
} else {
|
|
1298
|
-
d = `${pathData[0].type} ${pathData[0].values.join(" ")}${suff}`;
|
|
1299
|
-
}
|
|
1387
|
+
d = `${pathData[0].type}${separator_type}${pathData[0].values.join(" ")}${separator_command}`;
|
|
1300
1388
|
|
|
1301
|
-
for (let i = 1
|
|
1389
|
+
for (let i = 1; i < len; i++) {
|
|
1302
1390
|
let com0 = pathData[i - 1];
|
|
1303
1391
|
let com = pathData[i];
|
|
1304
1392
|
let { type, values } = com;
|
|
@@ -1316,8 +1404,6 @@ function pathDataToD(pathData, optimize = 0) {
|
|
|
1316
1404
|
type = (com0.type === com.type && com.type.toLowerCase() !== 'm' && minify)
|
|
1317
1405
|
? " "
|
|
1318
1406
|
: (
|
|
1319
|
-
(com0.type === "m" && com.type === "l") ||
|
|
1320
|
-
(com0.type === "M" && com.type === "l") ||
|
|
1321
1407
|
(com0.type === "M" && com.type === "L")
|
|
1322
1408
|
) && minify
|
|
1323
1409
|
? " "
|
|
@@ -1346,16 +1432,15 @@ function pathDataToD(pathData, optimize = 0) {
|
|
|
1346
1432
|
}
|
|
1347
1433
|
|
|
1348
1434
|
valsString += valStr;
|
|
1349
|
-
|
|
1350
1435
|
prevWasFloat = isSmallFloat;
|
|
1351
1436
|
}
|
|
1352
1437
|
|
|
1353
|
-
d += `${type}${valsString}`;
|
|
1438
|
+
d += `${type}${separator_type}${valsString}${separator_command}`;
|
|
1354
1439
|
|
|
1355
1440
|
}
|
|
1356
1441
|
// regular non-minified output
|
|
1357
1442
|
else {
|
|
1358
|
-
d += `${type}
|
|
1443
|
+
d += `${type}${separator_type}${values.join(' ')}${separator_command}`;
|
|
1359
1444
|
}
|
|
1360
1445
|
}
|
|
1361
1446
|
|
|
@@ -1959,7 +2044,8 @@ function detectAccuracy(pathData) {
|
|
|
1959
2044
|
let p0 = M;
|
|
1960
2045
|
let p = M;
|
|
1961
2046
|
pathData[0].decimals = 0;
|
|
1962
|
-
|
|
2047
|
+
|
|
2048
|
+
let dims = new Set();
|
|
1963
2049
|
|
|
1964
2050
|
// add average distances
|
|
1965
2051
|
for (let i = 0, len = pathData.length; i < len; i++) {
|
|
@@ -1972,8 +2058,7 @@ function detectAccuracy(pathData) {
|
|
|
1972
2058
|
// use existing averave dimension value or calculate
|
|
1973
2059
|
let dimA = com.dimA ? +com.dimA.toFixed(8) : type!=='M' ? +getDistAv(p0, p).toFixed(8) : 0;
|
|
1974
2060
|
|
|
1975
|
-
if(dimA
|
|
1976
|
-
|
|
2061
|
+
if(dimA) dims.add(dimA);
|
|
1977
2062
|
|
|
1978
2063
|
|
|
1979
2064
|
if(type==='M'){
|
|
@@ -1982,7 +2067,14 @@ function detectAccuracy(pathData) {
|
|
|
1982
2067
|
p0 = p;
|
|
1983
2068
|
}
|
|
1984
2069
|
|
|
1985
|
-
let
|
|
2070
|
+
let dim_min = Array.from(dims).sort();
|
|
2071
|
+
let sliceIdx = Math.ceil(dim_min.length/8);
|
|
2072
|
+
dim_min = dim_min.slice(0, sliceIdx );
|
|
2073
|
+
|
|
2074
|
+
let dimVal = dim_min.reduce((a,b)=>a+b, 0) / sliceIdx;
|
|
2075
|
+
|
|
2076
|
+
let threshold = 50;
|
|
2077
|
+
let decimalsAuto = dimVal > threshold ? 0 : Math.floor(threshold / dimVal).toString().length;
|
|
1986
2078
|
|
|
1987
2079
|
// clamp
|
|
1988
2080
|
return Math.min(Math.max(0, decimalsAuto), 8)
|
|
@@ -3402,33 +3494,37 @@ function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLinetos = t
|
|
|
3402
3494
|
let comPrev = pathData[c - 1];
|
|
3403
3495
|
let com = pathData[c];
|
|
3404
3496
|
let comN = pathData[c + 1] || pathData[l - 1];
|
|
3405
|
-
let p1 = comN.type === '
|
|
3497
|
+
let p1 = comN.type.toLowerCase() === 'z' ? M : { x: comN.values[comN.values.length - 2], y: comN.values[comN.values.length - 1] };
|
|
3406
3498
|
|
|
3407
3499
|
let { type, values } = com;
|
|
3408
3500
|
let valsL = values.slice(-2);
|
|
3409
3501
|
p = type !== 'Z' ? { x: valsL[0], y: valsL[1] } : M;
|
|
3410
3502
|
|
|
3411
|
-
let
|
|
3412
|
-
[{ x: values[0], y: values[1] }, { x: values[2], y: values[3] }] :
|
|
3413
|
-
(type === 'Q' ? [{ x: values[0], y: values[1] }] : []);
|
|
3503
|
+
let area = getPolygonArea([p0, p, p1], true);
|
|
3414
3504
|
|
|
3415
|
-
let
|
|
3416
|
-
let
|
|
3417
|
-
let distMax = distSquare / 500 * tolerance;
|
|
3505
|
+
let distSquare = getSquareDistance(p0, p1);
|
|
3506
|
+
let distMax = distSquare / 100 * tolerance;
|
|
3418
3507
|
|
|
3419
|
-
let isFlat = area < distMax;
|
|
3420
|
-
|
|
3421
|
-
|
|
3508
|
+
let isFlat = area < distMax;
|
|
3509
|
+
let isFlatBez = false;
|
|
3510
|
+
|
|
3511
|
+
if (!flatBezierToLinetos && type === 'C') isFlat = false;
|
|
3422
3512
|
|
|
3423
3513
|
// convert flat beziers to linetos
|
|
3424
|
-
if (flatBezierToLinetos && type === 'C') {
|
|
3514
|
+
if (flatBezierToLinetos && (type === 'C' || type === 'Q')) {
|
|
3515
|
+
|
|
3516
|
+
let cpts = type === 'C' ?
|
|
3517
|
+
[{ x: values[0], y: values[1] }, { x: values[2], y: values[3] }] :
|
|
3518
|
+
(type === 'Q' ? [{ x: values[0], y: values[1] }] : []);
|
|
3425
3519
|
|
|
3426
|
-
|
|
3427
|
-
|
|
3520
|
+
isFlatBez = checkBezierFlatness(p0, cpts, p);
|
|
3521
|
+
// console.log();
|
|
3428
3522
|
|
|
3429
|
-
if (isFlatBez && comPrev.type !== 'C') {
|
|
3523
|
+
if (isFlatBez && c < l - 1 && comPrev.type !== 'C') {
|
|
3524
|
+
type = "L";
|
|
3430
3525
|
com.type = "L";
|
|
3431
3526
|
com.values = valsL;
|
|
3527
|
+
|
|
3432
3528
|
}
|
|
3433
3529
|
|
|
3434
3530
|
}
|
|
@@ -3437,7 +3533,8 @@ function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLinetos = t
|
|
|
3437
3533
|
p0 = p;
|
|
3438
3534
|
|
|
3439
3535
|
// colinear – exclude arcs (as always =) as semicircles won't have an area
|
|
3440
|
-
if (
|
|
3536
|
+
if ( isFlat && c < l - 1 && (type === 'L' || (flatBezierToLinetos && isFlatBez))) {
|
|
3537
|
+
|
|
3441
3538
|
continue;
|
|
3442
3539
|
}
|
|
3443
3540
|
|
|
@@ -3487,11 +3584,43 @@ function removeZeroLengthLinetos(pathData) {
|
|
|
3487
3584
|
|
|
3488
3585
|
}
|
|
3489
3586
|
|
|
3490
|
-
function pathDataToTopLeft(pathData
|
|
3587
|
+
function pathDataToTopLeft(pathData) {
|
|
3588
|
+
|
|
3589
|
+
let len = pathData.length;
|
|
3590
|
+
let isClosed = pathData[len - 1].type.toLowerCase() === 'z';
|
|
3591
|
+
|
|
3592
|
+
// we can't change starting point for non closed paths
|
|
3593
|
+
if (!isClosed) {
|
|
3594
|
+
return pathData
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
let newIndex = 0;
|
|
3598
|
+
|
|
3599
|
+
let indices = [];
|
|
3600
|
+
for (let i = 0; i < len; i++) {
|
|
3601
|
+
let com = pathData[i];
|
|
3602
|
+
let { type, values } = com;
|
|
3603
|
+
let valsLen = values.length;
|
|
3604
|
+
if (valsLen) {
|
|
3605
|
+
let p = { type: type, x: values[valsLen-2], y: values[valsLen-1], index: 0};
|
|
3606
|
+
p.index = i;
|
|
3607
|
+
indices.push(p);
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3610
|
+
|
|
3611
|
+
// reorder to top left most
|
|
3612
|
+
|
|
3613
|
+
indices = indices.sort((a, b) => +a.y.toFixed(3) - +b.y.toFixed(3) );
|
|
3614
|
+
newIndex = indices[0].index;
|
|
3615
|
+
|
|
3616
|
+
return newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
function optimizeClosePath(pathData, removeFinalLineto = true, reorder = true) {
|
|
3491
3620
|
|
|
3492
3621
|
let pathDataNew = [];
|
|
3493
3622
|
let len = pathData.length;
|
|
3494
|
-
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
3623
|
+
let M = { x: +pathData[0].values[0].toFixed(8), y: +pathData[0].values[1].toFixed(8) };
|
|
3495
3624
|
let isClosed = pathData[len - 1].type.toLowerCase() === 'z';
|
|
3496
3625
|
|
|
3497
3626
|
let linetos = pathData.filter(com => com.type === 'L');
|
|
@@ -3499,17 +3628,17 @@ function pathDataToTopLeft(pathData, removeFinalLineto = false, reorder = true)
|
|
|
3499
3628
|
// check if order is ideal
|
|
3500
3629
|
let penultimateCom = pathData[len - 2];
|
|
3501
3630
|
let penultimateType = penultimateCom.type;
|
|
3502
|
-
let penultimateComCoords = penultimateCom.values.slice(-2).map(val
|
|
3631
|
+
let penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8));
|
|
3503
3632
|
|
|
3504
3633
|
// last L command ends at M
|
|
3505
|
-
let isClosingCommand =
|
|
3634
|
+
let isClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y;
|
|
3506
3635
|
|
|
3507
3636
|
// if last segment is not closing or a lineto
|
|
3508
|
-
let skipReorder = pathData[1].type!=='L' && (!isClosingCommand || penultimateType==='L'
|
|
3509
|
-
skipReorder=false;
|
|
3637
|
+
let skipReorder = pathData[1].type !== 'L' && (!isClosingCommand || penultimateType === 'L');
|
|
3638
|
+
skipReorder = false;
|
|
3510
3639
|
|
|
3511
3640
|
// we can't change starting point for non closed paths
|
|
3512
|
-
if (!isClosed
|
|
3641
|
+
if (!isClosed) {
|
|
3513
3642
|
return pathData
|
|
3514
3643
|
}
|
|
3515
3644
|
|
|
@@ -3536,15 +3665,15 @@ function pathDataToTopLeft(pathData, removeFinalLineto = false, reorder = true)
|
|
|
3536
3665
|
// find top most lineto
|
|
3537
3666
|
|
|
3538
3667
|
if (linetos.length) {
|
|
3539
|
-
let curveAfterLine = indices.filter(com => (com.type !== 'L' && com.type !== 'M') && com.prevCom &&
|
|
3540
|
-
|
|
3668
|
+
let curveAfterLine = indices.filter(com => (com.type !== 'L' && com.type !== 'M') && com.prevCom &&
|
|
3669
|
+
com.prevCom === 'L' || com.prevCom === 'M' && penultimateType === 'L').sort((a, b) => a.y - b.y || a.x - b.x)[0];
|
|
3541
3670
|
|
|
3542
3671
|
newIndex = curveAfterLine ? curveAfterLine.index - 1 : 0;
|
|
3543
3672
|
|
|
3544
3673
|
}
|
|
3545
3674
|
// use top most command
|
|
3546
3675
|
else {
|
|
3547
|
-
indices = indices.sort((a, b) => +a.y.toFixed(1) - +b.y.toFixed(1) || a.x - b.x
|
|
3676
|
+
indices = indices.sort((a, b) => +a.y.toFixed(1) - +b.y.toFixed(1) || a.x - b.x);
|
|
3548
3677
|
newIndex = indices[0].index;
|
|
3549
3678
|
}
|
|
3550
3679
|
|
|
@@ -3552,12 +3681,14 @@ function pathDataToTopLeft(pathData, removeFinalLineto = false, reorder = true)
|
|
|
3552
3681
|
pathData = newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
|
|
3553
3682
|
}
|
|
3554
3683
|
|
|
3684
|
+
M = { x: +pathData[0].values[0].toFixed(8), y: +pathData[0].values[1].toFixed(7) };
|
|
3685
|
+
|
|
3555
3686
|
len = pathData.length;
|
|
3556
3687
|
|
|
3557
3688
|
// remove last lineto
|
|
3558
3689
|
penultimateCom = pathData[len - 2];
|
|
3559
3690
|
penultimateType = penultimateCom.type;
|
|
3560
|
-
penultimateComCoords = penultimateCom.values.slice(-2);
|
|
3691
|
+
penultimateComCoords = penultimateCom.values.slice(-2).map(val=>+val.toFixed(8));
|
|
3561
3692
|
|
|
3562
3693
|
isClosingCommand = penultimateType === 'L' && penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y;
|
|
3563
3694
|
|
|
@@ -3783,7 +3914,93 @@ function reversePathData(pathData, {
|
|
|
3783
3914
|
return pathDataNew;
|
|
3784
3915
|
}
|
|
3785
3916
|
|
|
3786
|
-
function
|
|
3917
|
+
function cleanUpSVG(svgMarkup, {
|
|
3918
|
+
returnDom=false,
|
|
3919
|
+
removeHidden=true,
|
|
3920
|
+
removeUnused=true,
|
|
3921
|
+
}={}) {
|
|
3922
|
+
svgMarkup = cleanSvgPrologue(svgMarkup);
|
|
3923
|
+
|
|
3924
|
+
// replace namespaced refs
|
|
3925
|
+
svgMarkup = svgMarkup.replaceAll("xlink:href=", "href=");
|
|
3926
|
+
|
|
3927
|
+
let svg = new DOMParser()
|
|
3928
|
+
.parseFromString(svgMarkup, "text/html")
|
|
3929
|
+
.querySelector("svg");
|
|
3930
|
+
|
|
3931
|
+
|
|
3932
|
+
let allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class'];
|
|
3933
|
+
removeExcludedAttribues(svg, allowed);
|
|
3934
|
+
|
|
3935
|
+
let removeEls = ['metadata', 'script'];
|
|
3936
|
+
|
|
3937
|
+
let els = svg.querySelectorAll('*');
|
|
3938
|
+
els.forEach(el=>{
|
|
3939
|
+
let name = el.nodeName;
|
|
3940
|
+
// remove hidden elements
|
|
3941
|
+
let style = el.getAttribute('style') || '';
|
|
3942
|
+
let isHiddenByStyle = style ? style.trim().includes('display:none') : false;
|
|
3943
|
+
let isHidden = (el.getAttribute('display') && el.getAttribute('display') === 'none') || isHiddenByStyle;
|
|
3944
|
+
if(name.includes(':') || removeEls.includes(name) || (removeHidden && isHidden )) {
|
|
3945
|
+
el.remove();
|
|
3946
|
+
}else {
|
|
3947
|
+
// remove BS elements
|
|
3948
|
+
removeNameSpaceAtts(el);
|
|
3949
|
+
}
|
|
3950
|
+
});
|
|
3951
|
+
|
|
3952
|
+
if(returnDom) return svg
|
|
3953
|
+
|
|
3954
|
+
let markup = stringifySVG(svg);
|
|
3955
|
+
console.log(markup);
|
|
3956
|
+
|
|
3957
|
+
return markup;
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3960
|
+
function cleanSvgPrologue(svgString) {
|
|
3961
|
+
return (
|
|
3962
|
+
svgString
|
|
3963
|
+
// Remove XML prologues like <?xml ... ?>
|
|
3964
|
+
.replace(/<\?xml[\s\S]*?\?>/gi, "")
|
|
3965
|
+
// Remove DOCTYPE declarations
|
|
3966
|
+
.replace(/<!DOCTYPE[\s\S]*?>/gi, "")
|
|
3967
|
+
// Remove comments <!-- ... -->
|
|
3968
|
+
.replace(/<!--[\s\S]*?-->/g, "")
|
|
3969
|
+
// Trim extra whitespace
|
|
3970
|
+
.trim()
|
|
3971
|
+
);
|
|
3972
|
+
}
|
|
3973
|
+
|
|
3974
|
+
function removeExcludedAttribues(el, allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class']){
|
|
3975
|
+
let atts = [...el.attributes].map((att) => att.name);
|
|
3976
|
+
atts.forEach((att) => {
|
|
3977
|
+
if (!allowed.includes(att)) {
|
|
3978
|
+
el.removeAttribute(att);
|
|
3979
|
+
}
|
|
3980
|
+
});
|
|
3981
|
+
}
|
|
3982
|
+
|
|
3983
|
+
function removeNameSpaceAtts(el) {
|
|
3984
|
+
let atts = [...el.attributes].map((att) => att.name);
|
|
3985
|
+
atts.forEach((att) => {
|
|
3986
|
+
if (att.includes(":")) {
|
|
3987
|
+
el.removeAttribute(att);
|
|
3988
|
+
}
|
|
3989
|
+
});
|
|
3990
|
+
}
|
|
3991
|
+
|
|
3992
|
+
function stringifySVG(svg){
|
|
3993
|
+
let markup = new XMLSerializer().serializeToString(svg);
|
|
3994
|
+
markup = markup
|
|
3995
|
+
.replace(/\t/g, "")
|
|
3996
|
+
.replace(/[\n\r|]/g, "\n")
|
|
3997
|
+
.replace(/\n\s*\n/g, '\n')
|
|
3998
|
+
.replace(/ +/g, ' ');
|
|
3999
|
+
|
|
4000
|
+
return markup
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
function svgPathSimplify(input = '', {
|
|
3787
4004
|
toAbsolute = true,
|
|
3788
4005
|
toRelative = true,
|
|
3789
4006
|
toShorthands = true,
|
|
@@ -3811,126 +4028,233 @@ function svgPathSimplify(d = '', {
|
|
|
3811
4028
|
revertToQuadratics = true,
|
|
3812
4029
|
minifyD = 0,
|
|
3813
4030
|
tolerance = 1,
|
|
3814
|
-
reverse = false
|
|
4031
|
+
reverse = false,
|
|
4032
|
+
|
|
4033
|
+
// svg cleanup options
|
|
4034
|
+
removeHidden = true,
|
|
4035
|
+
removeUnused = true,
|
|
4036
|
+
|
|
4037
|
+
// return svg markup or object
|
|
4038
|
+
getObject = false
|
|
4039
|
+
|
|
3815
4040
|
} = {}) {
|
|
3816
4041
|
|
|
3817
|
-
|
|
4042
|
+
// clamp tolerance
|
|
4043
|
+
tolerance = Math.max(0.1, tolerance);
|
|
4044
|
+
|
|
4045
|
+
let inputType = detectInputType(input);
|
|
3818
4046
|
|
|
3819
|
-
|
|
3820
|
-
let
|
|
4047
|
+
let svg = '';
|
|
4048
|
+
let svgSize = 0;
|
|
4049
|
+
let svgSizeOpt = 0;
|
|
4050
|
+
let compression = 0;
|
|
4051
|
+
let report = {};
|
|
4052
|
+
let d = '';
|
|
4053
|
+
let mode = inputType === 'svgMarkup' ? 1 : 0;
|
|
3821
4054
|
|
|
3822
|
-
|
|
3823
|
-
let comCount = pathDataO.length;
|
|
4055
|
+
let paths = [];
|
|
3824
4056
|
|
|
3825
4057
|
/**
|
|
3826
|
-
*
|
|
4058
|
+
* normalize input
|
|
4059
|
+
* switch mode
|
|
3827
4060
|
*/
|
|
3828
|
-
let subPathArr = splitSubpaths(pathData);
|
|
3829
4061
|
|
|
3830
|
-
//
|
|
3831
|
-
|
|
4062
|
+
// original size
|
|
4063
|
+
svgSize = new Blob([input]).size;
|
|
3832
4064
|
|
|
3833
|
-
|
|
4065
|
+
// single path
|
|
4066
|
+
if (!mode) {
|
|
4067
|
+
if (inputType === 'pathDataString') {
|
|
4068
|
+
d = input;
|
|
4069
|
+
} else if (inputType === 'polyString') {
|
|
4070
|
+
d = 'M' + input;
|
|
4071
|
+
}
|
|
4072
|
+
paths.push({ d, el: null });
|
|
4073
|
+
}
|
|
4074
|
+
// process svg
|
|
4075
|
+
else {
|
|
3834
4076
|
|
|
3835
|
-
let
|
|
4077
|
+
let returnDom = true;
|
|
4078
|
+
svg = cleanUpSVG(input, { returnDom, removeHidden, removeUnused }
|
|
4079
|
+
);
|
|
3836
4080
|
|
|
3837
|
-
//
|
|
3838
|
-
|
|
4081
|
+
// collect paths
|
|
4082
|
+
let pathEls = svg.querySelectorAll('path');
|
|
4083
|
+
pathEls.forEach(path => {
|
|
4084
|
+
paths.push({ d: path.getAttribute('d'), el: path });
|
|
4085
|
+
});
|
|
4086
|
+
}
|
|
3839
4087
|
|
|
3840
|
-
|
|
3841
|
-
|
|
4088
|
+
/**
|
|
4089
|
+
* process all paths
|
|
4090
|
+
*/
|
|
4091
|
+
paths.forEach(path => {
|
|
4092
|
+
let { d, el } = path;
|
|
3842
4093
|
|
|
3843
|
-
|
|
4094
|
+
let pathDataO = parsePathDataNormalized(d, { quadraticToCubic, toAbsolute, arcToCubic });
|
|
3844
4095
|
|
|
3845
|
-
|
|
3846
|
-
|
|
4096
|
+
// create clone for fallback
|
|
4097
|
+
let pathData = JSON.parse(JSON.stringify(pathDataO));
|
|
3847
4098
|
|
|
3848
|
-
//
|
|
3849
|
-
|
|
4099
|
+
// count commands for evaluation
|
|
4100
|
+
let comCount = pathDataO.length;
|
|
3850
4101
|
|
|
3851
|
-
|
|
3852
|
-
|
|
4102
|
+
/**
|
|
4103
|
+
* get sub paths
|
|
4104
|
+
*/
|
|
4105
|
+
let subPathArr = splitSubpaths(pathData);
|
|
3853
4106
|
|
|
3854
|
-
//
|
|
3855
|
-
let
|
|
4107
|
+
// cleaned up pathData
|
|
4108
|
+
let pathDataArrN = [];
|
|
3856
4109
|
|
|
3857
|
-
|
|
3858
|
-
let { pathData, bb, dimA } = pathDataPlus;
|
|
4110
|
+
for (let i = 0, l = subPathArr.length; i < l; i++) {
|
|
3859
4111
|
|
|
3860
|
-
|
|
4112
|
+
let pathDataSub = subPathArr[i];
|
|
3861
4113
|
|
|
3862
|
-
|
|
3863
|
-
|
|
4114
|
+
// try simplification in reversed order
|
|
4115
|
+
if (reverse) pathDataSub = reversePathData(pathDataSub);
|
|
3864
4116
|
|
|
3865
|
-
|
|
3866
|
-
|
|
4117
|
+
// remove zero length linetos
|
|
4118
|
+
if (removeColinear) pathDataSub = removeZeroLengthLinetos(pathDataSub);
|
|
3867
4119
|
|
|
3868
|
-
|
|
4120
|
+
// add extremes
|
|
3869
4121
|
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
if (type === 'C') {
|
|
4122
|
+
let tMin = 0, tMax = 1;
|
|
4123
|
+
if (addExtremes) pathDataSub = addExtremePoints(pathDataSub, tMin, tMax);
|
|
3873
4124
|
|
|
3874
|
-
|
|
3875
|
-
|
|
4125
|
+
// sort to top left
|
|
4126
|
+
if (optimizeOrder) pathDataSub = pathDataToTopLeft(pathDataSub);
|
|
3876
4127
|
|
|
3877
|
-
|
|
3878
|
-
|
|
4128
|
+
// remove colinear/flat
|
|
4129
|
+
if (removeColinear) pathDataSub = pathDataRemoveColinear(pathDataSub, tolerance, flatBezierToLinetos);
|
|
4130
|
+
|
|
4131
|
+
// analyze pathdata to add info about signicant properties such as extremes, corners
|
|
4132
|
+
let pathDataPlus = analyzePathData(pathDataSub);
|
|
4133
|
+
|
|
4134
|
+
// simplify beziers
|
|
4135
|
+
let { pathData, bb, dimA } = pathDataPlus;
|
|
4136
|
+
|
|
4137
|
+
pathData = simplifyBezier ? simplifyPathData(pathData, { simplifyBezier, keepInflections, keepExtremes, keepCorners, extrapolateDominant, revertToQuadratics, tolerance, reverse }) : pathData;
|
|
4138
|
+
|
|
4139
|
+
// cubic to arcs
|
|
4140
|
+
if (cubicToArc) {
|
|
4141
|
+
|
|
4142
|
+
let thresh = 3;
|
|
4143
|
+
|
|
4144
|
+
pathData.forEach((com, c) => {
|
|
4145
|
+
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
4146
|
+
if (type === 'C') {
|
|
4147
|
+
|
|
4148
|
+
let comA = cubicCommandToArc(p0, cp1, cp2, p, thresh);
|
|
4149
|
+
if (comA.isArc) pathData[c] = comA.com;
|
|
4150
|
+
|
|
4151
|
+
}
|
|
4152
|
+
});
|
|
4153
|
+
|
|
4154
|
+
// combine adjacent cubics
|
|
4155
|
+
pathData = combineArcs(pathData);
|
|
3879
4156
|
|
|
3880
|
-
|
|
3881
|
-
|
|
4157
|
+
}
|
|
4158
|
+
|
|
4159
|
+
// simplify to quadratics
|
|
4160
|
+
if (revertToQuadratics) {
|
|
4161
|
+
pathData.forEach((com, c) => {
|
|
4162
|
+
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
4163
|
+
if (type === 'C') {
|
|
3882
4164
|
|
|
4165
|
+
let comQ = revertCubicQuadratic(p0, cp1, cp2, p);
|
|
4166
|
+
if (comQ.type === 'Q') pathData[c] = comQ;
|
|
4167
|
+
}
|
|
4168
|
+
});
|
|
4169
|
+
}
|
|
4170
|
+
|
|
4171
|
+
// optimize close path
|
|
4172
|
+
if(optimizeOrder) pathData=optimizeClosePath(pathData);
|
|
4173
|
+
|
|
4174
|
+
// update
|
|
4175
|
+
pathDataArrN.push(pathData);
|
|
3883
4176
|
}
|
|
3884
4177
|
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
3889
|
-
if (type === 'C') {
|
|
4178
|
+
|
|
4179
|
+
// flatten compound paths
|
|
4180
|
+
pathData = pathDataArrN.flat();
|
|
3890
4181
|
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
4182
|
+
/**
|
|
4183
|
+
* detect accuracy
|
|
4184
|
+
*/
|
|
4185
|
+
if (autoAccuracy) {
|
|
4186
|
+
decimals = detectAccuracy(pathData);
|
|
3895
4187
|
}
|
|
3896
4188
|
|
|
3897
|
-
//
|
|
3898
|
-
|
|
3899
|
-
|
|
4189
|
+
// optimize
|
|
4190
|
+
let pathOptions = {
|
|
4191
|
+
toRelative,
|
|
4192
|
+
toShorthands,
|
|
4193
|
+
decimals,
|
|
4194
|
+
};
|
|
3900
4195
|
|
|
3901
|
-
|
|
3902
|
-
|
|
4196
|
+
// optimize path data
|
|
4197
|
+
pathData = convertPathData(pathData, pathOptions);
|
|
3903
4198
|
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
4199
|
+
// remove zero-length segments introduced by rounding
|
|
4200
|
+
let pathDataOpt = [];
|
|
4201
|
+
|
|
4202
|
+
pathData.forEach((com, i) => {
|
|
4203
|
+
let { type, values } = com;
|
|
4204
|
+
if (type === 'l' || type === 'v' || type === 'h') {
|
|
4205
|
+
let hasLength = type === 'l' ? (values.join('') !== '00') : values[0] !== 0;
|
|
4206
|
+
if (hasLength) pathDataOpt.push(com);
|
|
4207
|
+
} else {
|
|
4208
|
+
pathDataOpt.push(com);
|
|
4209
|
+
}
|
|
4210
|
+
});
|
|
3910
4211
|
|
|
3911
|
-
|
|
3912
|
-
let comCountS = pathDataFlat.length;
|
|
4212
|
+
pathData = pathDataOpt;
|
|
3913
4213
|
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
toRelative,
|
|
3917
|
-
toShorthands,
|
|
3918
|
-
decimals,
|
|
3919
|
-
};
|
|
4214
|
+
// compare command count
|
|
4215
|
+
let comCountS = pathData.length;
|
|
3920
4216
|
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
let dOpt = pathDataToD(pathData, minifyD);
|
|
4217
|
+
let dOpt = pathDataToD(pathData, minifyD);
|
|
4218
|
+
svgSizeOpt = new Blob([dOpt]).size;
|
|
3924
4219
|
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
4220
|
+
compression = +(100 / svgSize * (svgSizeOpt)).toFixed(2);
|
|
4221
|
+
|
|
4222
|
+
path.d = dOpt;
|
|
4223
|
+
path.report = {
|
|
4224
|
+
original: comCount,
|
|
4225
|
+
new: comCountS,
|
|
4226
|
+
saved: comCount - comCountS,
|
|
4227
|
+
compression,
|
|
4228
|
+
decimals,
|
|
4229
|
+
|
|
4230
|
+
};
|
|
4231
|
+
|
|
4232
|
+
// apply new path for svgs
|
|
4233
|
+
if (el) el.setAttribute('d', dOpt);
|
|
4234
|
+
|
|
4235
|
+
});
|
|
3932
4236
|
|
|
3933
|
-
|
|
4237
|
+
// stringify new SVG
|
|
4238
|
+
if (mode) {
|
|
4239
|
+
svg = new XMLSerializer().serializeToString(svg);
|
|
4240
|
+
svgSizeOpt = new Blob([svg]).size;
|
|
4241
|
+
|
|
4242
|
+
compression = +(100 / svgSize * (svgSizeOpt)).toFixed(2);
|
|
4243
|
+
|
|
4244
|
+
svgSize = +(svgSize / 1024).toFixed(3);
|
|
4245
|
+
svgSizeOpt = +(svgSizeOpt / 1024).toFixed(3);
|
|
4246
|
+
|
|
4247
|
+
report = {
|
|
4248
|
+
svgSize,
|
|
4249
|
+
svgSizeOpt,
|
|
4250
|
+
compression
|
|
4251
|
+
};
|
|
4252
|
+
|
|
4253
|
+
} else {
|
|
4254
|
+
({ d, report } = paths[0]);
|
|
4255
|
+
}
|
|
4256
|
+
|
|
4257
|
+
return !getObject ? (d ? d : svg) : { svg, d, report, inputType, mode };
|
|
3934
4258
|
|
|
3935
4259
|
}
|
|
3936
4260
|
|
|
@@ -3960,9 +4284,9 @@ function simplifyPathData(pathData, {
|
|
|
3960
4284
|
|
|
3961
4285
|
// cannot be combined as crossing extremes or corners
|
|
3962
4286
|
if (
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
4287
|
+
(keepInflections && isDirChangeN) ||
|
|
4288
|
+
(keepCorners && corner) ||
|
|
4289
|
+
(!isDirChange && keepExtremes && extreme)
|
|
3966
4290
|
) {
|
|
3967
4291
|
|
|
3968
4292
|
pathDataN.push(com);
|
|
@@ -4026,6 +4350,39 @@ function simplifyPathData(pathData, {
|
|
|
4026
4350
|
return pathDataN
|
|
4027
4351
|
}
|
|
4028
4352
|
|
|
4353
|
+
/**
|
|
4354
|
+
* get viewBox
|
|
4355
|
+
* either from explicit attribute or
|
|
4356
|
+
* width and height attributes
|
|
4357
|
+
*/
|
|
4358
|
+
|
|
4359
|
+
function getViewBox(svg = null, round = false) {
|
|
4360
|
+
|
|
4361
|
+
// browser default
|
|
4362
|
+
if (!svg) return { x: 0, y: 0, width: 300, height: 150 }
|
|
4363
|
+
|
|
4364
|
+
let style = window.getComputedStyle(svg);
|
|
4365
|
+
|
|
4366
|
+
// the baseVal API method also converts physical units to pixels/user-units
|
|
4367
|
+
let w = svg.hasAttribute('width') ? svg.width.baseVal.value : parseFloat(style.width) || 300;
|
|
4368
|
+
let h = svg.hasAttribute('height') ? svg.height.baseVal.value : parseFloat(style.height) || 150;
|
|
4369
|
+
|
|
4370
|
+
let viewBox = svg.getAttribute('viewBox') ? svg.viewBox.baseVal : { x: 0, y: 0, width: w, height: h };
|
|
4371
|
+
|
|
4372
|
+
// remove SVG constructor
|
|
4373
|
+
let { x, y, width, height } = viewBox;
|
|
4374
|
+
viewBox = { x, y, width, height };
|
|
4375
|
+
|
|
4376
|
+
// round to integers
|
|
4377
|
+
if (round) {
|
|
4378
|
+
for (let prop in viewBox) {
|
|
4379
|
+
viewBox[prop] = Math.ceil(viewBox[prop]);
|
|
4380
|
+
}
|
|
4381
|
+
}
|
|
4382
|
+
|
|
4383
|
+
return viewBox
|
|
4384
|
+
}
|
|
4385
|
+
|
|
4029
4386
|
const {
|
|
4030
4387
|
abs, acos, asin, atan, atan2, ceil, cos, exp, floor,
|
|
4031
4388
|
log, hypot, max, min, pow, random, round, sin, sqrt, tan, PI
|
|
@@ -4036,6 +4393,7 @@ const {
|
|
|
4036
4393
|
// IIFE
|
|
4037
4394
|
if (typeof window !== 'undefined') {
|
|
4038
4395
|
window.svgPathSimplify = svgPathSimplify;
|
|
4396
|
+
window.getViewBox = getViewBox;
|
|
4039
4397
|
window.renderPoint = renderPoint;
|
|
4040
4398
|
}
|
|
4041
4399
|
|
|
@@ -4049,6 +4407,7 @@ exports.ceil = ceil;
|
|
|
4049
4407
|
exports.cos = cos;
|
|
4050
4408
|
exports.exp = exp;
|
|
4051
4409
|
exports.floor = floor;
|
|
4410
|
+
exports.getViewBox = getViewBox;
|
|
4052
4411
|
exports.hypot = hypot;
|
|
4053
4412
|
exports.log = log;
|
|
4054
4413
|
exports.max = max;
|