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