svg-path-simplify 0.0.2 → 0.0.4

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.
@@ -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
- // console.log('intersectionPoint', intersectionPoint, p1, p2);
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
- let diffX = Math.abs(pt1.x - pt2.x);
641
- let diffY = Math.abs(pt1.y - pt2.y);
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 suff = beautify ? `\n` : ' ';
1382
+ let separator_command = beautify ? `\n` : (minify ? '' : ' ');
1383
+ let separator_type = !minify ? ' ' : '';
1292
1384
 
1293
- if (minify) {
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, len = pathData.length; i < len; i++) {
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} ${values.join(' ')}${suff}`;
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
- let minDim = Infinity;
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 && dimA<minDim) minDim = 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 decimalsAuto = Math.floor(50 / minDim).toString().length;
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 === 'Z' ? M : { x: comN.values[comN.values.length - 2], y: comN.values[comN.values.length - 1] };
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 cpts = type === 'C' ?
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 area = getPolygonArea([p0, ...cpts, p, p1], true);
3414
- let distSquare = getSquareDistance(p0, p);
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
- if(!flatBezierToLinetos && type==='C') isFlat = false;
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
- let areaBez = getPolygonArea([p0, ...cpts, p], true);
3425
- let isFlatBez = areaBez < distSquare / 1000;
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 (type !== 'A' && isFlat && c < l - 1) {
3534
+ if ( isFlat && c < l - 1 && (type === 'L' || (flatBezierToLinetos && isFlatBez))) {
3535
+
3439
3536
  continue;
3440
3537
  }
3441
3538
 
@@ -3485,87 +3582,35 @@ function removeZeroLengthLinetos(pathData) {
3485
3582
 
3486
3583
  }
3487
3584
 
3488
- function pathDataToTopLeft(pathData, removeFinalLineto = false, reorder = true) {
3585
+ function pathDataToTopLeft(pathData) {
3489
3586
 
3490
- let pathDataNew = [];
3491
3587
  let len = pathData.length;
3492
- let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
3493
3588
  let isClosed = pathData[len - 1].type.toLowerCase() === 'z';
3494
3589
 
3495
- let linetos = pathData.filter(com => com.type === 'L');
3496
-
3497
- // check if order is ideal
3498
- let penultimateCom = pathData[len - 2];
3499
- let penultimateType = penultimateCom.type;
3500
- let penultimateComCoords = penultimateCom.values.slice(-2).map(val=>+val.toFixed(8));
3501
-
3502
- // last L command ends at M
3503
- let isClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y;
3504
-
3505
- // if last segment is not closing or a lineto
3506
- let skipReorder = pathData[1].type!=='L' && (!isClosingCommand || penultimateType==='L' );
3507
- skipReorder=false;
3508
-
3509
3590
  // we can't change starting point for non closed paths
3510
- if (!isClosed ) {
3591
+ if (!isClosed) {
3511
3592
  return pathData
3512
3593
  }
3513
3594
 
3514
3595
  let newIndex = 0;
3515
3596
 
3516
- if (!skipReorder) {
3517
-
3518
- let indices = [];
3519
- for (let i = 0, len = pathData.length; i < len; i++) {
3520
- let com = pathData[i];
3521
- let { type, values } = com;
3522
- if (values.length) {
3523
- let valsL = values.slice(-2);
3524
- let prevL = pathData[i - 1] && pathData[i - 1].type === 'L';
3525
- let nextL = pathData[i + 1] && pathData[i + 1].type === 'L';
3526
- let prevCom = pathData[i - 1] ? pathData[i - 1].type.toUpperCase() : null;
3527
- let nextCom = pathData[i + 1] ? pathData[i + 1].type.toUpperCase() : null;
3528
- let p = { type: type, x: valsL[0], y: valsL[1], dist: 0, index: 0, prevL, nextL, prevCom, nextCom };
3529
- p.index = i;
3530
- indices.push(p);
3531
- }
3532
- }
3533
-
3534
- // find top most lineto
3535
-
3536
- if (linetos.length) {
3537
- let curveAfterLine = indices.filter(com => (com.type !== 'L' && com.type !== 'M') && com.prevCom &&
3538
- com.prevCom === 'L' || com.prevCom==='M' && penultimateType==='L' ).sort((a, b) => a.y - b.y || a.x-b.x)[0];
3539
-
3540
- newIndex = curveAfterLine ? curveAfterLine.index - 1 : 0;
3541
-
3542
- }
3543
- // use top most command
3544
- else {
3545
- indices = indices.sort((a, b) => +a.y.toFixed(1) - +b.y.toFixed(1) || a.x - b.x );
3546
- newIndex = indices[0].index;
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);
3547
3606
  }
3548
-
3549
- // reorder
3550
- pathData = newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
3551
- }
3552
-
3553
- len = pathData.length;
3554
-
3555
- // remove last lineto
3556
- penultimateCom = pathData[len - 2];
3557
- penultimateType = penultimateCom.type;
3558
- penultimateComCoords = penultimateCom.values.slice(-2);
3559
-
3560
- isClosingCommand = penultimateType === 'L' && penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y;
3561
-
3562
- if (removeFinalLineto && isClosingCommand) {
3563
- pathData.splice(len - 2, 1);
3564
3607
  }
3565
3608
 
3566
- pathDataNew.push(...pathData);
3609
+ // reorder to top left most
3610
+ indices = indices.sort((a, b) => +a.y.toFixed(3) - +b.y.toFixed(3) || a.x - b.x);
3611
+ newIndex = indices[0].index;
3567
3612
 
3568
- return pathDataNew
3613
+ return newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
3569
3614
  }
3570
3615
 
3571
3616
  /**
@@ -3781,7 +3826,93 @@ function reversePathData(pathData, {
3781
3826
  return pathDataNew;
3782
3827
  }
3783
3828
 
3784
- function svgPathSimplify(d = '', {
3829
+ function cleanUpSVG(svgMarkup, {
3830
+ returnDom=false,
3831
+ removeHidden=true,
3832
+ removeUnused=true,
3833
+ }={}) {
3834
+ svgMarkup = cleanSvgPrologue(svgMarkup);
3835
+
3836
+ // replace namespaced refs
3837
+ svgMarkup = svgMarkup.replaceAll("xlink:href=", "href=");
3838
+
3839
+ let svg = new DOMParser()
3840
+ .parseFromString(svgMarkup, "text/html")
3841
+ .querySelector("svg");
3842
+
3843
+
3844
+ let allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class'];
3845
+ removeExcludedAttribues(svg, allowed);
3846
+
3847
+ let removeEls = ['metadata', 'script'];
3848
+
3849
+ let els = svg.querySelectorAll('*');
3850
+ els.forEach(el=>{
3851
+ let name = el.nodeName;
3852
+ // remove hidden elements
3853
+ let style = el.getAttribute('style') || '';
3854
+ let isHiddenByStyle = style ? style.trim().includes('display:none') : false;
3855
+ let isHidden = (el.getAttribute('display') && el.getAttribute('display') === 'none') || isHiddenByStyle;
3856
+ if(name.includes(':') || removeEls.includes(name) || (removeHidden && isHidden )) {
3857
+ el.remove();
3858
+ }else {
3859
+ // remove BS elements
3860
+ removeNameSpaceAtts(el);
3861
+ }
3862
+ });
3863
+
3864
+ if(returnDom) return svg
3865
+
3866
+ let markup = stringifySVG(svg);
3867
+ console.log(markup);
3868
+
3869
+ return markup;
3870
+ }
3871
+
3872
+ function cleanSvgPrologue(svgString) {
3873
+ return (
3874
+ svgString
3875
+ // Remove XML prologues like <?xml ... ?>
3876
+ .replace(/<\?xml[\s\S]*?\?>/gi, "")
3877
+ // Remove DOCTYPE declarations
3878
+ .replace(/<!DOCTYPE[\s\S]*?>/gi, "")
3879
+ // Remove comments <!-- ... -->
3880
+ .replace(/<!--[\s\S]*?-->/g, "")
3881
+ // Trim extra whitespace
3882
+ .trim()
3883
+ );
3884
+ }
3885
+
3886
+ function removeExcludedAttribues(el, allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class']){
3887
+ let atts = [...el.attributes].map((att) => att.name);
3888
+ atts.forEach((att) => {
3889
+ if (!allowed.includes(att)) {
3890
+ el.removeAttribute(att);
3891
+ }
3892
+ });
3893
+ }
3894
+
3895
+ function removeNameSpaceAtts(el) {
3896
+ let atts = [...el.attributes].map((att) => att.name);
3897
+ atts.forEach((att) => {
3898
+ if (att.includes(":")) {
3899
+ el.removeAttribute(att);
3900
+ }
3901
+ });
3902
+ }
3903
+
3904
+ function stringifySVG(svg){
3905
+ let markup = new XMLSerializer().serializeToString(svg);
3906
+ markup = markup
3907
+ .replace(/\t/g, "")
3908
+ .replace(/[\n\r|]/g, "\n")
3909
+ .replace(/\n\s*\n/g, '\n')
3910
+ .replace(/ +/g, ' ');
3911
+
3912
+ return markup
3913
+ }
3914
+
3915
+ function svgPathSimplify(input = '', {
3785
3916
  toAbsolute = true,
3786
3917
  toRelative = true,
3787
3918
  toShorthands = true,
@@ -3809,126 +3940,229 @@ function svgPathSimplify(d = '', {
3809
3940
  revertToQuadratics = true,
3810
3941
  minifyD = 0,
3811
3942
  tolerance = 1,
3812
- reverse = false
3943
+ reverse = false,
3944
+
3945
+ // svg cleanup options
3946
+ removeHidden = true,
3947
+ removeUnused = true,
3948
+
3949
+ // return svg markup or object
3950
+ getObject = false
3951
+
3813
3952
  } = {}) {
3814
3953
 
3815
- let pathDataO = parsePathDataNormalized(d, { quadraticToCubic, toAbsolute, arcToCubic });
3954
+ // clamp tolerance
3955
+ tolerance = Math.max(0.1, tolerance);
3956
+
3957
+ let inputType = detectInputType(input);
3816
3958
 
3817
- // create clone for fallback
3818
- let pathData = JSON.parse(JSON.stringify(pathDataO));
3959
+ let svg = '';
3960
+ let svgSize = 0;
3961
+ let svgSizeOpt = 0;
3962
+ let compression = 0;
3963
+ let report = {};
3964
+ let d = '';
3965
+ let mode = inputType === 'svgMarkup' ? 1 : 0;
3819
3966
 
3820
- // count commands for evaluation
3821
- let comCount = pathDataO.length;
3967
+ let paths = [];
3822
3968
 
3823
3969
  /**
3824
- * get sub paths
3970
+ * normalize input
3971
+ * switch mode
3825
3972
  */
3826
- let subPathArr = splitSubpaths(pathData);
3827
3973
 
3828
- // cleaned up pathData
3829
- let pathDataArrN = [];
3974
+ // original size
3975
+ svgSize = new Blob([input]).size;
3830
3976
 
3831
- for (let i = 0, l = subPathArr.length; i < l; i++) {
3977
+ // single path
3978
+ if (!mode) {
3979
+ if (inputType === 'pathDataString') {
3980
+ d = input;
3981
+ } else if (inputType === 'polyString') {
3982
+ d = 'M' + input;
3983
+ }
3984
+ paths.push({ d, el: null });
3985
+ }
3986
+ // process svg
3987
+ else {
3832
3988
 
3833
- let pathDataSub = subPathArr[i];
3989
+ let returnDom = true;
3990
+ svg = cleanUpSVG(input, { returnDom, removeHidden, removeUnused }
3991
+ );
3834
3992
 
3835
- // try simplification in reversed order
3836
- if (reverse) pathDataSub = reversePathData(pathDataSub);
3993
+ // collect paths
3994
+ let pathEls = svg.querySelectorAll('path');
3995
+ pathEls.forEach(path => {
3996
+ paths.push({ d: path.getAttribute('d'), el: path });
3997
+ });
3998
+ }
3837
3999
 
3838
- // remove zero length linetos
3839
- if (removeColinear) pathDataSub = removeZeroLengthLinetos(pathDataSub);
4000
+ /**
4001
+ * process all paths
4002
+ */
4003
+ paths.forEach(path => {
4004
+ let { d, el } = path;
3840
4005
 
3841
- // add extremes
4006
+ let pathDataO = parsePathDataNormalized(d, { quadraticToCubic, toAbsolute, arcToCubic });
3842
4007
 
3843
- let tMin = 0, tMax = 1;
3844
- if (addExtremes) pathDataSub = addExtremePoints(pathDataSub, tMin, tMax);
4008
+ // create clone for fallback
4009
+ let pathData = JSON.parse(JSON.stringify(pathDataO));
3845
4010
 
3846
- // sort to top left
3847
- if (optimizeOrder) pathDataSub = pathDataToTopLeft(pathDataSub);
4011
+ // count commands for evaluation
4012
+ let comCount = pathDataO.length;
3848
4013
 
3849
- // remove colinear/flat
3850
- if (removeColinear) pathDataSub = pathDataRemoveColinear(pathDataSub, tolerance, flatBezierToLinetos);
4014
+ /**
4015
+ * get sub paths
4016
+ */
4017
+ let subPathArr = splitSubpaths(pathData);
3851
4018
 
3852
- // analyze pathdata to add info about signicant properties such as extremes, corners
3853
- let pathDataPlus = analyzePathData(pathDataSub);
4019
+ // cleaned up pathData
4020
+ let pathDataArrN = [];
3854
4021
 
3855
- // simplify beziers
3856
- let { pathData, bb, dimA } = pathDataPlus;
4022
+ for (let i = 0, l = subPathArr.length; i < l; i++) {
3857
4023
 
3858
- let pathDataN = pathData;
4024
+ let pathDataSub = subPathArr[i];
3859
4025
 
3860
-
3861
- pathDataN = simplifyBezier ? simplifyPathData(pathDataN, { simplifyBezier, keepInflections, keepExtremes, keepCorners, extrapolateDominant, revertToQuadratics, tolerance, reverse }) : pathDataN;
4026
+ // try simplification in reversed order
4027
+ if (reverse) pathDataSub = reversePathData(pathDataSub);
3862
4028
 
3863
- // cubic to arcs
3864
- if(cubicToArc){
4029
+ // remove zero length linetos
4030
+ if (removeColinear) pathDataSub = removeZeroLengthLinetos(pathDataSub);
3865
4031
 
3866
- let thresh = 3;
4032
+ // add extremes
3867
4033
 
3868
- pathDataN.forEach((com, c) => {
3869
- let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
3870
- if (type === 'C') {
4034
+ let tMin = 0, tMax = 1;
4035
+ if (addExtremes) pathDataSub = addExtremePoints(pathDataSub, tMin, tMax);
3871
4036
 
3872
- let comA = cubicCommandToArc(p0, cp1, cp2, p, thresh);
3873
- if(comA.isArc) pathDataN[c] = comA.com;
4037
+ // sort to top left
4038
+ if (optimizeOrder) pathDataSub = pathDataToTopLeft(pathDataSub);
3874
4039
 
3875
- }
3876
- });
4040
+ // remove colinear/flat
4041
+ if (removeColinear) pathDataSub = pathDataRemoveColinear(pathDataSub, tolerance, flatBezierToLinetos);
4042
+
4043
+ // analyze pathdata to add info about signicant properties such as extremes, corners
4044
+ let pathDataPlus = analyzePathData(pathDataSub);
4045
+
4046
+ // simplify beziers
4047
+ let { pathData, bb, dimA } = pathDataPlus;
4048
+
4049
+ pathData = simplifyBezier ? simplifyPathData(pathData, { simplifyBezier, keepInflections, keepExtremes, keepCorners, extrapolateDominant, revertToQuadratics, tolerance, reverse }) : pathData;
4050
+
4051
+ // cubic to arcs
4052
+ if (cubicToArc) {
4053
+
4054
+ let thresh = 3;
3877
4055
 
3878
- // combine adjacent cubics
3879
- pathDataN = combineArcs(pathDataN);
4056
+ pathData.forEach((com, c) => {
4057
+ let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
4058
+ if (type === 'C') {
4059
+
4060
+ let comA = cubicCommandToArc(p0, cp1, cp2, p, thresh);
4061
+ if (comA.isArc) pathData[c] = comA.com;
4062
+
4063
+ }
4064
+ });
4065
+
4066
+ // combine adjacent cubics
4067
+ pathData = combineArcs(pathData);
4068
+
4069
+ }
4070
+
4071
+ // simplify to quadratics
4072
+ if (revertToQuadratics) {
4073
+ pathData.forEach((com, c) => {
4074
+ let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
4075
+ if (type === 'C') {
4076
+
4077
+ let comQ = revertCubicQuadratic(p0, cp1, cp2, p);
4078
+ if (comQ.type === 'Q') pathData[c] = comQ;
4079
+ }
4080
+ });
4081
+ }
3880
4082
 
4083
+ // update
4084
+ pathDataArrN.push(pathData);
3881
4085
  }
3882
4086
 
3883
- // simplify to quadratics
3884
- if (revertToQuadratics) {
3885
- pathDataN.forEach((com, c) => {
3886
- let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
3887
- if (type === 'C') {
4087
+ // flatten compound paths
4088
+ pathData = pathDataArrN.flat();
3888
4089
 
3889
- let comQ = revertCubicQuadratic(p0, cp1, cp2, p);
3890
- if (comQ.type === 'Q') pathDataN[c] = comQ;
3891
- }
3892
- });
4090
+ /**
4091
+ * detect accuracy
4092
+ */
4093
+ if (autoAccuracy) {
4094
+ decimals = detectAccuracy(pathData);
3893
4095
  }
3894
4096
 
3895
- // update
3896
- pathDataArrN.push(pathDataN);
3897
- }
4097
+ // optimize
4098
+ let pathOptions = {
4099
+ toRelative,
4100
+ toShorthands,
4101
+ decimals,
4102
+ };
3898
4103
 
3899
- // merge pathdata
3900
- let pathDataFlat = pathDataArrN.flat();
4104
+ // optimize path data
4105
+ pathData = convertPathData(pathData, pathOptions);
3901
4106
 
3902
- /**
3903
- * detect accuracy
3904
- */
3905
- if (autoAccuracy) {
3906
- decimals = detectAccuracy(pathDataFlat);
3907
- }
4107
+ // remove zero-length segments introduced by rounding
4108
+ let pathDataOpt = [];
3908
4109
 
3909
- // compare command count
3910
- let comCountS = pathDataFlat.length;
4110
+ pathData.forEach((com, i) => {
4111
+ let { type, values } = com;
4112
+ if (type === 'l' || type === 'v' || type === 'h') {
4113
+ let hasLength = type === 'l' ? (values.join('') !== '00') : values[0] !== 0;
4114
+ if (hasLength) pathDataOpt.push(com);
4115
+ } else {
4116
+ pathDataOpt.push(com);
4117
+ }
4118
+ });
3911
4119
 
3912
- // optimize
3913
- let pathOptions = {
3914
- toRelative,
3915
- toShorthands,
3916
- decimals,
3917
- };
4120
+ pathData = pathDataOpt;
3918
4121
 
3919
- // optimize path data
3920
- pathData = convertPathData(pathDataFlat, pathOptions);
3921
- let dOpt = pathDataToD(pathData, minifyD);
4122
+ // compare command count
4123
+ let comCountS = pathData.length;
3922
4124
 
3923
- let report = {
3924
- original: comCount,
3925
- new: comCountS,
3926
- saved: comCount - comCountS,
3927
- decimals,
3928
- success: comCountS < comCount
3929
- };
4125
+ let dOpt = pathDataToD(pathData, minifyD);
4126
+ svgSizeOpt = new Blob([dOpt]).size;
4127
+
4128
+ compression = +(100 / svgSize * (svgSizeOpt)).toFixed(2);
4129
+
4130
+ path.d = dOpt;
4131
+ path.report = {
4132
+ original: comCount,
4133
+ new: comCountS,
4134
+ saved: comCount - comCountS,
4135
+ compression,
4136
+ decimals,
4137
+
4138
+ };
4139
+
4140
+ // apply new path for svgs
4141
+ if (el) el.setAttribute('d', dOpt);
4142
+
4143
+ });
3930
4144
 
3931
- return { pathData, d: dOpt, report };
4145
+ // stringify new SVG
4146
+ if (mode) {
4147
+ svg = new XMLSerializer().serializeToString(svg);
4148
+ svgSizeOpt = new Blob([svg]).size;
4149
+
4150
+ compression = +(100 / svgSize * (svgSizeOpt)).toFixed(2);
4151
+
4152
+ svgSize = +(svgSize / 1024).toFixed(3);
4153
+ svgSizeOpt = +(svgSizeOpt / 1024).toFixed(3);
4154
+
4155
+ report = {
4156
+ svgSize,
4157
+ svgSizeOpt,
4158
+ compression
4159
+ };
4160
+
4161
+ } else {
4162
+ ({ d, report } = paths[0]);
4163
+ }
4164
+
4165
+ return !getObject ? (d ? d : svg) : { svg, d, report, inputType, mode };
3932
4166
 
3933
4167
  }
3934
4168
 
@@ -3958,9 +4192,9 @@ function simplifyPathData(pathData, {
3958
4192
 
3959
4193
  // cannot be combined as crossing extremes or corners
3960
4194
  if (
3961
- (keepInflections && isDirChangeN) ||
3962
- (keepCorners && corner) ||
3963
- (!isDirChange && keepExtremes && extreme)
4195
+ (keepInflections && isDirChangeN) ||
4196
+ (keepCorners && corner) ||
4197
+ (!isDirChange && keepExtremes && extreme)
3964
4198
  ) {
3965
4199
 
3966
4200
  pathDataN.push(com);
@@ -4024,6 +4258,39 @@ function simplifyPathData(pathData, {
4024
4258
  return pathDataN
4025
4259
  }
4026
4260
 
4261
+ /**
4262
+ * get viewBox
4263
+ * either from explicit attribute or
4264
+ * width and height attributes
4265
+ */
4266
+
4267
+ function getViewBox(svg = null, round = false) {
4268
+
4269
+ // browser default
4270
+ if (!svg) return { x: 0, y: 0, width: 300, height: 150 }
4271
+
4272
+ let style = window.getComputedStyle(svg);
4273
+
4274
+ // the baseVal API method also converts physical units to pixels/user-units
4275
+ let w = svg.hasAttribute('width') ? svg.width.baseVal.value : parseFloat(style.width) || 300;
4276
+ let h = svg.hasAttribute('height') ? svg.height.baseVal.value : parseFloat(style.height) || 150;
4277
+
4278
+ let viewBox = svg.getAttribute('viewBox') ? svg.viewBox.baseVal : { x: 0, y: 0, width: w, height: h };
4279
+
4280
+ // remove SVG constructor
4281
+ let { x, y, width, height } = viewBox;
4282
+ viewBox = { x, y, width, height };
4283
+
4284
+ // round to integers
4285
+ if (round) {
4286
+ for (let prop in viewBox) {
4287
+ viewBox[prop] = Math.ceil(viewBox[prop]);
4288
+ }
4289
+ }
4290
+
4291
+ return viewBox
4292
+ }
4293
+
4027
4294
  const {
4028
4295
  abs, acos, asin, atan, atan2, ceil, cos, exp, floor,
4029
4296
  log, hypot, max, min, pow, random, round, sin, sqrt, tan, PI
@@ -4034,7 +4301,8 @@ const {
4034
4301
  // IIFE
4035
4302
  if (typeof window !== 'undefined') {
4036
4303
  window.svgPathSimplify = svgPathSimplify;
4304
+ window.getViewBox = getViewBox;
4037
4305
  window.renderPoint = renderPoint;
4038
4306
  }
4039
4307
 
4040
- export { PI, abs, acos, asin, atan, atan2, ceil, cos, exp, floor, hypot, log, max, min, pow, random, round, sin, sqrt, svgPathSimplify, tan };
4308
+ export { PI, abs, acos, asin, atan, atan2, ceil, cos, exp, floor, getViewBox, hypot, log, max, min, pow, random, round, sin, sqrt, svgPathSimplify, tan };