svg-path-simplify 0.4.1 → 0.4.3
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/CHANGELOG.md +19 -0
- package/README.md +6 -4
- package/dist/svg-path-simplify.esm.js +2450 -888
- package/dist/svg-path-simplify.esm.min.js +2 -2
- package/dist/svg-path-simplify.js +2450 -888
- package/dist/svg-path-simplify.min.js +2 -2
- package/dist/svg-path-simplify.pathdata.esm.js +167 -85
- package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
- package/docs/privacy-webapp.md +24 -0
- package/index.html +333 -132
- package/package.json +5 -2
- package/src/css_parse.js +317 -0
- package/src/detect_input.js +34 -4
- package/src/pathData_simplify_harmonize_cpts.js +77 -1
- package/src/pathSimplify-main.js +246 -262
- package/src/pathSimplify-presets.js +243 -0
- package/src/poly-fit-curve-schneider.js +14 -7
- package/src/simplify_poly_RC.js +102 -0
- package/src/simplify_poly_RDP.js +109 -1
- package/src/simplify_poly_radial_distance.js +3 -3
- package/src/string_helpers.js +144 -0
- package/src/svgii/convert_units.js +8 -2
- package/src/svgii/geometry.js +182 -3
- package/src/svgii/geometry_length.js +237 -0
- package/src/svgii/pathData_convert.js +43 -1
- package/src/svgii/pathData_fix_directions.js +6 -0
- package/src/svgii/pathData_fromPoly.js +3 -3
- package/src/svgii/pathData_getLength.js +86 -0
- package/src/svgii/pathData_parse.js +2 -0
- package/src/svgii/pathData_parse_els.js +189 -189
- package/src/svgii/pathData_split_to_groups.js +168 -0
- package/src/svgii/pathData_stringify.js +26 -64
- package/src/svgii/pathData_toPolygon.js +3 -4
- package/src/svgii/poly_analyze.js +61 -0
- package/src/svgii/poly_normalize.js +11 -2
- package/src/svgii/poly_to_pathdata.js +85 -24
- package/src/svgii/rounding.js +8 -7
- package/src/svgii/svg-styles-to-attributes-const.js +1 -0
- package/src/svgii/svg_cleanup.js +467 -421
- package/src/svgii/svg_cleanup_convertPathLength.js +32 -0
- package/src/svgii/svg_cleanup_general_svg_atts.js +97 -0
- package/src/svgii/svg_cleanup_normalize_transforms.js +83 -0
- package/src/svgii/svg_cleanup_remove_els_and_atts.js +72 -0
- package/src/svgii/svg_cleanup_ungroup.js +36 -0
- package/src/svgii/svg_el_parse_style_props.js +76 -28
- package/src/svgii/svg_getElementLength.js +67 -0
- package/tests/testSVG_shape.js +59 -0
- package/tests/testSVG_transform.js +61 -0
|
@@ -52,12 +52,12 @@ function detectInputType(input) {
|
|
|
52
52
|
// nested array
|
|
53
53
|
if (Array.isArray(input[0])) {
|
|
54
54
|
|
|
55
|
-
if(input[0].length===2){
|
|
55
|
+
if (input[0].length === 2) {
|
|
56
56
|
|
|
57
57
|
return 'polyArray'
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
else if (Array.isArray(input[0][0]) && input[0][0].length === 2
|
|
60
|
+
else if (Array.isArray(input[0][0]) && input[0][0].length === 2) {
|
|
61
61
|
|
|
62
62
|
return 'polyComplexArray'
|
|
63
63
|
}
|
|
@@ -68,7 +68,7 @@ function detectInputType(input) {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
// is point array
|
|
71
|
-
else if (input[0].x!==undefined && input[0].y!==undefined) {
|
|
71
|
+
else if (input[0].x !== undefined && input[0].y !== undefined) {
|
|
72
72
|
|
|
73
73
|
return 'polyObjectArray'
|
|
74
74
|
}
|
|
@@ -86,12 +86,22 @@ function detectInputType(input) {
|
|
|
86
86
|
if (typeof input === "string") {
|
|
87
87
|
input = input.trim();
|
|
88
88
|
let isSVG = input.includes('<svg') && input.includes('</svg');
|
|
89
|
+
let isSymbol = input.startsWith('<symbol') && input.includes('</symbol');
|
|
89
90
|
let isPathData = input.startsWith('M') || input.startsWith('m');
|
|
90
91
|
let isPolyString = !isNaN(input.substring(0, 1)) && !isNaN(input.substring(input.length - 1, input.length));
|
|
92
|
+
let isJson = isNumberJson(input);
|
|
91
93
|
|
|
92
94
|
if (isSVG) {
|
|
93
95
|
type = 'svgMarkup';
|
|
94
96
|
}
|
|
97
|
+
|
|
98
|
+
else if (isJson) {
|
|
99
|
+
type = 'json';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
else if (isSymbol) {
|
|
103
|
+
type = 'symbol';
|
|
104
|
+
}
|
|
95
105
|
else if (isPathData) {
|
|
96
106
|
type = 'pathDataString';
|
|
97
107
|
}
|
|
@@ -114,6 +124,21 @@ function detectInputType(input) {
|
|
|
114
124
|
return (constructor || type).toLowerCase();
|
|
115
125
|
}
|
|
116
126
|
|
|
127
|
+
function isNumberJson(str) {
|
|
128
|
+
|
|
129
|
+
str = str.trim();
|
|
130
|
+
|
|
131
|
+
let hasNumber = /\d/.test(str);
|
|
132
|
+
let hasInvalid = /[abcdfghijklmnopqrstuvwz]/gi.test(str);
|
|
133
|
+
if (!hasNumber || hasInvalid) return false
|
|
134
|
+
|
|
135
|
+
// is JSON like
|
|
136
|
+
let isJson = str.startsWith('[') && str.endsWith(']');
|
|
137
|
+
|
|
138
|
+
return isJson
|
|
139
|
+
|
|
140
|
+
}
|
|
141
|
+
|
|
117
142
|
const {
|
|
118
143
|
abs: abs$1, acos: acos$1, asin: asin$1, atan: atan$1, atan2: atan2$1, ceil: ceil$1, cos: cos$1, exp: exp$1, floor: floor$1,
|
|
119
144
|
log: log$1, hypot, max: max$1, min: min$1, pow: pow$1, random: random$1, round: round$1, sin: sin$1, sqrt: sqrt$1, tan: tan$1, PI: PI$1
|
|
@@ -438,7 +463,36 @@ function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, returnArray
|
|
|
438
463
|
/**
|
|
439
464
|
* get vertices from path command final on-path points
|
|
440
465
|
*/
|
|
441
|
-
|
|
466
|
+
|
|
467
|
+
function getPathDataVertices(pathData=[], includeCpts = false, decimals = -1) {
|
|
468
|
+
let polyPoints = [];
|
|
469
|
+
|
|
470
|
+
pathData.forEach((com) => {
|
|
471
|
+
let { type, values } = com;
|
|
472
|
+
|
|
473
|
+
// get final on path point from last 2 values
|
|
474
|
+
if (values.length) {
|
|
475
|
+
|
|
476
|
+
// round
|
|
477
|
+
if (decimals > -1) values = values.map(val => +val.toFixed(decimals));
|
|
478
|
+
|
|
479
|
+
if (includeCpts) {
|
|
480
|
+
|
|
481
|
+
for (let i = 1; i < values.length; i += 2) {
|
|
482
|
+
polyPoints.push({ x: values[i - 1], y: values[i] });
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
} else {
|
|
486
|
+
polyPoints.push({ x: values[values.length - 2], y: values[values.length - 1] });
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
return polyPoints;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/*
|
|
495
|
+
export function getPathDataVertices(pathData) {
|
|
442
496
|
let polyPoints = [];
|
|
443
497
|
let p0 = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
444
498
|
|
|
@@ -453,22 +507,30 @@ function getPathDataVertices(pathData) {
|
|
|
453
507
|
}
|
|
454
508
|
});
|
|
455
509
|
return polyPoints;
|
|
456
|
-
}
|
|
510
|
+
};
|
|
511
|
+
*/
|
|
457
512
|
|
|
458
513
|
/**
|
|
459
514
|
* based on @cuixiping;
|
|
460
515
|
* https://stackoverflow.com/questions/9017100/calculate-center-of-svg-arc/12329083#12329083
|
|
461
516
|
*/
|
|
462
|
-
|
|
517
|
+
|
|
518
|
+
function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2, y2, normalize = true
|
|
519
|
+
) {
|
|
463
520
|
|
|
464
521
|
// helper for angle calculation
|
|
465
|
-
const getAngle = (cx, cy, x, y) => {
|
|
466
|
-
|
|
522
|
+
const getAngle = (cx, cy, x, y, normalize = true) => {
|
|
523
|
+
let angle = Math.atan2(y - cy, x - cx);
|
|
524
|
+
if (normalize && angle < 0) angle += Math.PI * 2;
|
|
525
|
+
return angle
|
|
467
526
|
};
|
|
468
527
|
|
|
469
528
|
// make sure rx, ry are positive
|
|
470
|
-
rx = abs(rx);
|
|
471
|
-
ry = abs(ry);
|
|
529
|
+
rx = Math.abs(rx);
|
|
530
|
+
ry = Math.abs(ry);
|
|
531
|
+
|
|
532
|
+
// normalize xAxis rotation
|
|
533
|
+
xAxisRotation = rx === ry ? 0 : (xAxisRotation < 0 && normalize ? xAxisRotation + 360 : xAxisRotation);
|
|
472
534
|
|
|
473
535
|
// create data object
|
|
474
536
|
let arcData = {
|
|
@@ -492,53 +554,11 @@ function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2,
|
|
|
492
554
|
throw Error("rx and ry can not be 0");
|
|
493
555
|
}
|
|
494
556
|
|
|
495
|
-
let shortcut = true;
|
|
496
|
-
|
|
497
|
-
if (rx === ry && shortcut) {
|
|
498
|
-
|
|
499
|
-
// test semicircles
|
|
500
|
-
let diffX = Math.abs(x2 - x1);
|
|
501
|
-
let diffY = Math.abs(y2 - y1);
|
|
502
|
-
let r = diffX;
|
|
503
|
-
|
|
504
|
-
let xMin = Math.min(x1, x2),
|
|
505
|
-
yMin = Math.min(y1, y2),
|
|
506
|
-
PIHalf = Math.PI * 0.5;
|
|
507
|
-
|
|
508
|
-
// semi circles
|
|
509
|
-
if (diffX === 0 && diffY || diffY === 0 && diffX) {
|
|
510
|
-
|
|
511
|
-
r = diffX === 0 && diffY ? diffY / 2 : diffX / 2;
|
|
512
|
-
arcData.rx = r;
|
|
513
|
-
arcData.ry = r;
|
|
514
|
-
|
|
515
|
-
// verical
|
|
516
|
-
if (diffX === 0 && diffY) {
|
|
517
|
-
arcData.cx = x1;
|
|
518
|
-
arcData.cy = yMin + diffY / 2;
|
|
519
|
-
arcData.startAngle = y1 > y2 ? PIHalf : -PIHalf;
|
|
520
|
-
arcData.endAngle = y1 > y2 ? -PIHalf : PIHalf;
|
|
521
|
-
arcData.deltaAngle = sweep ? Math.PI : -Math.PI;
|
|
522
|
-
|
|
523
|
-
}
|
|
524
|
-
// horizontal
|
|
525
|
-
else if (diffY === 0 && diffX) {
|
|
526
|
-
arcData.cx = xMin + diffX / 2;
|
|
527
|
-
arcData.cy = y1;
|
|
528
|
-
arcData.startAngle = x1 > x2 ? Math.PI : 0;
|
|
529
|
-
arcData.endAngle = x1 > x2 ? -Math.PI : Math.PI;
|
|
530
|
-
arcData.deltaAngle = sweep ? Math.PI : -Math.PI;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
return arcData;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
557
|
/**
|
|
538
558
|
* if rx===ry x-axis rotation is ignored
|
|
539
559
|
* otherwise convert degrees to radians
|
|
540
560
|
*/
|
|
541
|
-
let phi = rx === ry ? 0 :
|
|
561
|
+
let phi = rx === ry ? 0 : xAxisRotation * deg2rad;
|
|
542
562
|
let cx, cy;
|
|
543
563
|
|
|
544
564
|
let s_phi = !phi ? 0 : Math.sin(phi);
|
|
@@ -557,8 +577,9 @@ function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2,
|
|
|
557
577
|
// Step 3: Ensure radii are large enough
|
|
558
578
|
let lambda = (x1_ * x1_) / (rx * rx) + (y1_ * y1_) / (ry * ry);
|
|
559
579
|
if (lambda > 1) {
|
|
560
|
-
|
|
561
|
-
|
|
580
|
+
let lambdaRoot = Math.sqrt(lambda);
|
|
581
|
+
rx = rx * lambdaRoot;
|
|
582
|
+
ry = ry * lambdaRoot;
|
|
562
583
|
|
|
563
584
|
// save real rx/ry
|
|
564
585
|
arcData.rx = rx;
|
|
@@ -574,7 +595,7 @@ function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2,
|
|
|
574
595
|
throw Error("start point can not be same as end point");
|
|
575
596
|
}
|
|
576
597
|
let coe = Math.sqrt(Math.abs((rxry * rxry - sum_of_sq) / sum_of_sq));
|
|
577
|
-
if (largeArc
|
|
598
|
+
if (largeArc === sweep) {
|
|
578
599
|
coe = -coe;
|
|
579
600
|
}
|
|
580
601
|
|
|
@@ -594,24 +615,33 @@ function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2,
|
|
|
594
615
|
* calculate angles between center point and
|
|
595
616
|
* commands starting and final on path point
|
|
596
617
|
*/
|
|
597
|
-
let startAngle = getAngle(cx, cy, x1, y1);
|
|
598
|
-
let endAngle = getAngle(cx, cy, x2, y2);
|
|
618
|
+
let startAngle = getAngle(cx, cy, x1, y1, normalize);
|
|
619
|
+
let endAngle = getAngle(cx, cy, x2, y2, normalize);
|
|
599
620
|
|
|
600
621
|
// adjust end angle
|
|
601
|
-
if (!sweep && endAngle > startAngle) {
|
|
602
|
-
|
|
603
|
-
endAngle -= Math.PI * 2;
|
|
604
|
-
}
|
|
605
622
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
623
|
+
// Adjust angles based on sweep direction
|
|
624
|
+
if (sweep) {
|
|
625
|
+
// Clockwise
|
|
626
|
+
if (endAngle < startAngle) {
|
|
627
|
+
endAngle += Math.PI * 2;
|
|
628
|
+
}
|
|
629
|
+
} else {
|
|
630
|
+
// Counterclockwise
|
|
631
|
+
if (endAngle > startAngle) {
|
|
632
|
+
endAngle -= Math.PI * 2;
|
|
633
|
+
}
|
|
609
634
|
}
|
|
610
635
|
|
|
611
636
|
let deltaAngle = endAngle - startAngle;
|
|
637
|
+
|
|
638
|
+
// The rest of your code remains the same
|
|
612
639
|
arcData.startAngle = startAngle;
|
|
640
|
+
arcData.startAngle_deg = startAngle * rad2Deg;
|
|
613
641
|
arcData.endAngle = endAngle;
|
|
642
|
+
arcData.endAngle_deg = endAngle * rad2Deg;
|
|
614
643
|
arcData.deltaAngle = deltaAngle;
|
|
644
|
+
arcData.deltaAngle_deg = deltaAngle * rad2Deg;
|
|
615
645
|
|
|
616
646
|
return arcData;
|
|
617
647
|
}
|
|
@@ -1117,7 +1147,7 @@ function getDistance(p1, p2, isArray = false) {
|
|
|
1117
1147
|
let dx = isArray ? p2[0] - p1[0] : (p2.x - p1.x);
|
|
1118
1148
|
let dy = isArray ? p2[1] - p1[1] : (p2.y - p1.y);
|
|
1119
1149
|
|
|
1120
|
-
return sqrt(dx * dx + dy * dy);
|
|
1150
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
1121
1151
|
}
|
|
1122
1152
|
|
|
1123
1153
|
function getSquareDistance(p1, p2) {
|
|
@@ -1637,7 +1667,7 @@ function detectAccuracy(pathData) {
|
|
|
1637
1667
|
|
|
1638
1668
|
let dim_min = dims.sort();
|
|
1639
1669
|
|
|
1640
|
-
let sliceIdx = Math.ceil(dim_min.length /
|
|
1670
|
+
let sliceIdx = Math.ceil(dim_min.length / 6);
|
|
1641
1671
|
dim_min = dim_min.slice(0, sliceIdx);
|
|
1642
1672
|
let minVal = dim_min.reduce((a, b) => a + b, 0) / sliceIdx;
|
|
1643
1673
|
|
|
@@ -1650,6 +1680,7 @@ function detectAccuracy(pathData) {
|
|
|
1650
1680
|
}
|
|
1651
1681
|
|
|
1652
1682
|
function roundTo(num = 0, decimals = 3) {
|
|
1683
|
+
if(decimals<=-1) return num;
|
|
1653
1684
|
if (!decimals) return Math.round(num);
|
|
1654
1685
|
let factor = 10 ** decimals;
|
|
1655
1686
|
return Math.round(num * factor) / factor;
|
|
@@ -1660,19 +1691,20 @@ function roundTo(num = 0, decimals = 3) {
|
|
|
1660
1691
|
* floating point accuracy
|
|
1661
1692
|
* based on numeric value
|
|
1662
1693
|
*/
|
|
1663
|
-
function autoRound(val, integerThresh =
|
|
1694
|
+
function autoRound(val, integerThresh = 50){
|
|
1664
1695
|
let decimals=8;
|
|
1665
|
-
|
|
1666
1696
|
|
|
1667
|
-
if(val>integerThresh){
|
|
1697
|
+
if(val>integerThresh*2){
|
|
1668
1698
|
decimals=0;
|
|
1669
1699
|
}
|
|
1670
|
-
else if(val>
|
|
1700
|
+
else if(val>integerThresh){
|
|
1671
1701
|
decimals=1;
|
|
1672
1702
|
}else {
|
|
1673
|
-
decimals=Math.ceil(
|
|
1703
|
+
decimals=Math.ceil(500/val).toString().length;
|
|
1704
|
+
|
|
1674
1705
|
}
|
|
1675
1706
|
|
|
1707
|
+
|
|
1676
1708
|
let factor = 10 ** decimals;
|
|
1677
1709
|
return Math.round(val * factor) / factor;
|
|
1678
1710
|
}
|
|
@@ -1726,6 +1758,7 @@ const transHorizontal = ['scaleX', 'translateX', 'skewX'];
|
|
|
1726
1758
|
const transVertical = ['scaleY', 'translateY', 'skewY'];
|
|
1727
1759
|
|
|
1728
1760
|
const colorProps = ['fill', 'stroke', 'stop-color'];
|
|
1761
|
+
const geometryProps = ['d', 'points', 'cx', 'cy', 'x1', 'x2', 'y1', 'y2', 'width', 'height', 'r', 'rx', 'ry', 'x', 'y'];
|
|
1729
1762
|
|
|
1730
1763
|
const geometryEls = [
|
|
1731
1764
|
"path",
|
|
@@ -2067,7 +2100,14 @@ function svgElUnitsToPixel(el, {
|
|
|
2067
2100
|
|
|
2068
2101
|
let attributes = [...el.attributes];
|
|
2069
2102
|
let attNames = attributes.map(att => att.name);
|
|
2070
|
-
|
|
2103
|
+
|
|
2104
|
+
// doesn't work in node!
|
|
2105
|
+
|
|
2106
|
+
|
|
2107
|
+
let attValues = [];
|
|
2108
|
+
attNames.forEach(att=>{
|
|
2109
|
+
attValues.push(el.getAttribute(att));
|
|
2110
|
+
});
|
|
2071
2111
|
|
|
2072
2112
|
let isSquare = width === height;
|
|
2073
2113
|
|
|
@@ -2612,20 +2652,24 @@ function getPolygonArea(points, absolute=false) {
|
|
|
2612
2652
|
* d attribute string
|
|
2613
2653
|
*/
|
|
2614
2654
|
|
|
2615
|
-
function pathDataToD(pathData,
|
|
2616
|
-
|
|
2617
|
-
optimize = parseFloat(optimize);
|
|
2655
|
+
function pathDataToD(pathData, mode = 0) {
|
|
2618
2656
|
|
|
2657
|
+
mode = parseFloat(mode);
|
|
2658
|
+
/*
|
|
2659
|
+
0 = max minification
|
|
2660
|
+
0.5 = safe
|
|
2661
|
+
1 = verbose
|
|
2662
|
+
2 = beautify
|
|
2663
|
+
*/
|
|
2619
2664
|
let len = pathData.length;
|
|
2620
|
-
let beautify = optimize > 1;
|
|
2621
|
-
let minify = beautify || optimize ? false : true;
|
|
2622
2665
|
|
|
2623
|
-
let d = '';
|
|
2624
2666
|
let valsString = pathData[0].values.join(" ");
|
|
2625
|
-
let separator_command =
|
|
2626
|
-
|
|
2667
|
+
let separator_command = mode > 1 ? `\n` :
|
|
2668
|
+
((mode < 1) ? '' : ' ');
|
|
2669
|
+
let separator_type = mode > 0.5 ? ' ' : '';
|
|
2627
2670
|
|
|
2628
|
-
|
|
2671
|
+
// 1st command
|
|
2672
|
+
let d = `${pathData[0].type}${separator_type}${valsString}${separator_command}`;
|
|
2629
2673
|
|
|
2630
2674
|
for (let i = 1; i < len; i++) {
|
|
2631
2675
|
let com0 = pathData[i - 1];
|
|
@@ -2634,7 +2678,7 @@ function pathDataToD(pathData, optimize = 0) {
|
|
|
2634
2678
|
valsString = '';
|
|
2635
2679
|
|
|
2636
2680
|
// Minify Arc commands (A/a) – actually sucks!
|
|
2637
|
-
if (
|
|
2681
|
+
if (!mode && (type === 'A' || type === 'a')) {
|
|
2638
2682
|
values = [
|
|
2639
2683
|
values[0], values[1], values[2],
|
|
2640
2684
|
`${values[3]}${values[4]}${values[5]}`,
|
|
@@ -2643,14 +2687,14 @@ function pathDataToD(pathData, optimize = 0) {
|
|
|
2643
2687
|
}
|
|
2644
2688
|
|
|
2645
2689
|
// Omit type for repeated commands
|
|
2646
|
-
type = (
|
|
2690
|
+
type = ((mode < 1) && com0.type === com.type && com.type.toLowerCase() !== 'm')
|
|
2647
2691
|
? " "
|
|
2648
|
-
: (
|
|
2692
|
+
: ((mode < 1) && com0.type === "M" && com.type === "L"
|
|
2649
2693
|
? " "
|
|
2650
2694
|
: com.type);
|
|
2651
2695
|
|
|
2652
2696
|
// concatenate subsequent floating point values
|
|
2653
|
-
if (
|
|
2697
|
+
if (!mode) {
|
|
2654
2698
|
|
|
2655
2699
|
let prevWasFloat = false;
|
|
2656
2700
|
|
|
@@ -2674,22 +2718,23 @@ function pathDataToD(pathData, optimize = 0) {
|
|
|
2674
2718
|
prevWasFloat = isSmallFloat;
|
|
2675
2719
|
}
|
|
2676
2720
|
|
|
2677
|
-
d += `${type}${separator_type}${valsString}${separator_command}`;
|
|
2678
|
-
|
|
2679
2721
|
}
|
|
2680
2722
|
// regular non-minified output
|
|
2681
2723
|
else {
|
|
2682
|
-
|
|
2724
|
+
valsString = values.join(' ');
|
|
2683
2725
|
}
|
|
2726
|
+
|
|
2727
|
+
if(i===len-1) separator_command='';
|
|
2728
|
+
d += `${type}${separator_type}${valsString}${separator_command}`;
|
|
2684
2729
|
}
|
|
2685
2730
|
|
|
2686
|
-
if (
|
|
2731
|
+
if (mode < 1) {
|
|
2687
2732
|
d = d
|
|
2688
2733
|
.replace(/[A-Za-z]0(?=\.)/g, m => m[0])
|
|
2689
2734
|
.replace(/ 0\./g, " .") // Space before small decimals
|
|
2690
2735
|
.replace(/ -/g, "-") // Remove space before negatives
|
|
2691
2736
|
.replace(/-0\./g, "-.") // Remove leading zero from negative decimals
|
|
2692
|
-
.replace(/Z/g, "z");
|
|
2737
|
+
.replace(/Z/g, "z"); // Convert uppercase 'Z' to lowercase
|
|
2693
2738
|
}
|
|
2694
2739
|
|
|
2695
2740
|
return d;
|
|
@@ -3566,6 +3611,7 @@ const sanitizeArc = (val='', valueIndex=0) => {
|
|
|
3566
3611
|
};
|
|
3567
3612
|
|
|
3568
3613
|
function parsePathDataString(d, debug = true, limit=0) {
|
|
3614
|
+
if(!d) return []
|
|
3569
3615
|
d = d.trim();
|
|
3570
3616
|
|
|
3571
3617
|
if(limit) console.log('!!!limit', limit);
|
|
@@ -3901,6 +3947,7 @@ function convertPathData(pathData, {
|
|
|
3901
3947
|
toShorthands = true,
|
|
3902
3948
|
toLonghands = false,
|
|
3903
3949
|
toRelative = true,
|
|
3950
|
+
toMixed = false,
|
|
3904
3951
|
toAbsolute = false,
|
|
3905
3952
|
decimals = 3,
|
|
3906
3953
|
arcToCubic = false,
|
|
@@ -3911,11 +3958,14 @@ function convertPathData(pathData, {
|
|
|
3911
3958
|
hasShorthands = true,
|
|
3912
3959
|
hasQuadratics = true,
|
|
3913
3960
|
hasArcs = true,
|
|
3961
|
+
isPoly = false,
|
|
3914
3962
|
optimizeArcs = true,
|
|
3915
3963
|
testTypes = false
|
|
3916
3964
|
|
|
3917
3965
|
} = {}) {
|
|
3918
3966
|
|
|
3967
|
+
let pathDataAbs = [];
|
|
3968
|
+
|
|
3919
3969
|
// pathdata properties - test= true adds a manual test
|
|
3920
3970
|
if (testTypes) {
|
|
3921
3971
|
|
|
@@ -3929,6 +3979,7 @@ function convertPathData(pathData, {
|
|
|
3929
3979
|
|
|
3930
3980
|
// some params exclude each other
|
|
3931
3981
|
toRelative = toAbsolute ? false : toRelative;
|
|
3982
|
+
|
|
3932
3983
|
toShorthands = toLonghands ? false : toShorthands;
|
|
3933
3984
|
|
|
3934
3985
|
if (toAbsolute) pathData = pathDataToAbsolute(pathData);
|
|
@@ -3943,12 +3994,38 @@ function convertPathData(pathData, {
|
|
|
3943
3994
|
|
|
3944
3995
|
if (hasQuadratics && quadraticToCubic) pathData = pathDataQuadraticToCubic(pathData);
|
|
3945
3996
|
|
|
3997
|
+
if(toMixed) toRelative = true;
|
|
3998
|
+
|
|
3946
3999
|
// pre round - before relative conversion to minimize distortions
|
|
3947
4000
|
if (decimals > -1 && toRelative) pathData = roundPathData(pathData, decimals);
|
|
3948
4001
|
|
|
4002
|
+
// clone absolute pathdata
|
|
4003
|
+
if(toMixed){
|
|
4004
|
+
pathDataAbs = JSON.parse(JSON.stringify(pathData));
|
|
4005
|
+
}
|
|
4006
|
+
|
|
3949
4007
|
if (toRelative) pathData = pathDataToRelative(pathData);
|
|
3950
4008
|
if (decimals > -1) pathData = roundPathData(pathData, decimals);
|
|
3951
4009
|
|
|
4010
|
+
// choose most compact commands: relative or absolute
|
|
4011
|
+
if(toMixed){
|
|
4012
|
+
for(let i=0; i<pathData.length; i++){
|
|
4013
|
+
let com = pathData[i];
|
|
4014
|
+
let comA = pathDataAbs[i];
|
|
4015
|
+
// compare Lengths
|
|
4016
|
+
let comStr = [com.type, com.values.join(' ')].join('').replaceAll(' -', '-').replaceAll(' 0.', ' .');
|
|
4017
|
+
let comStrA = [comA.type, comA.values.join(' ')].join('').replaceAll(' -', '-').replaceAll(' 0.', ' .');
|
|
4018
|
+
|
|
4019
|
+
let lenR = comStr.length;
|
|
4020
|
+
let lenA = comStrA.length;
|
|
4021
|
+
|
|
4022
|
+
if(lenA<lenR){
|
|
4023
|
+
|
|
4024
|
+
pathData[i] = pathDataAbs[i];
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
|
|
3952
4029
|
return pathData
|
|
3953
4030
|
}
|
|
3954
4031
|
|
|
@@ -3959,6 +4036,9 @@ function convertPathData(pathData, {
|
|
|
3959
4036
|
*/
|
|
3960
4037
|
|
|
3961
4038
|
function optimizeArcPathData(pathData = []) {
|
|
4039
|
+
|
|
4040
|
+
let remove =[];
|
|
4041
|
+
|
|
3962
4042
|
pathData.forEach((com, i) => {
|
|
3963
4043
|
let { type, values } = com;
|
|
3964
4044
|
if (type === 'A') {
|
|
@@ -3968,6 +4048,12 @@ function optimizeArcPathData(pathData = []) {
|
|
|
3968
4048
|
let M = { x: x0, y: y0 };
|
|
3969
4049
|
let p = { x, y };
|
|
3970
4050
|
|
|
4051
|
+
if(rx===0 || ry===0){
|
|
4052
|
+
pathData[i]= null;
|
|
4053
|
+
remove.push(i);
|
|
4054
|
+
|
|
4055
|
+
}
|
|
4056
|
+
|
|
3971
4057
|
// rx and ry are large enough
|
|
3972
4058
|
if (rx >= 1 && (x === x0 || y === y0)) {
|
|
3973
4059
|
let diff = Math.abs(rx - ry) / rx;
|
|
@@ -3990,6 +4076,8 @@ function optimizeArcPathData(pathData = []) {
|
|
|
3990
4076
|
}
|
|
3991
4077
|
}
|
|
3992
4078
|
});
|
|
4079
|
+
|
|
4080
|
+
if(remove.length) pathData = pathData.filter(Boolean);
|
|
3993
4081
|
return pathData;
|
|
3994
4082
|
}
|
|
3995
4083
|
|
|
@@ -5615,153 +5703,20 @@ function qrDecomposeMatrix(matrix, precision = 4) {
|
|
|
5615
5703
|
return transObj;
|
|
5616
5704
|
}
|
|
5617
5705
|
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
convert_lines = false
|
|
5623
|
-
} = {}) {
|
|
5624
|
-
|
|
5625
|
-
let pathData = parsePathDataNormalized(el.getAttribute('d'));
|
|
5626
|
-
let coms = Array.from(new Set(pathData.map(com => com.type))).join('');
|
|
5627
|
-
|
|
5628
|
-
let hasArcs = (/[a]/gi).test(coms);
|
|
5629
|
-
let hasBeziers = (/[csqt]/gi).test(coms);
|
|
5630
|
-
let hasLines = (/[l]/gi).test(coms);
|
|
5631
|
-
let isPoly = !(/[acqts]/gi).test(coms);
|
|
5632
|
-
let closed = (/[z]/gi).test(coms);
|
|
5633
|
-
let shape = null;
|
|
5634
|
-
let type = null;
|
|
5635
|
-
|
|
5636
|
-
let attributes = getElementAtts(el);
|
|
5637
|
-
let attsNew = {};
|
|
5638
|
-
let decimals = 7;
|
|
5639
|
-
|
|
5640
|
-
if (isPoly) {
|
|
5641
|
-
|
|
5642
|
-
// is line
|
|
5643
|
-
if (pathData.length === 2 && convert_lines) {
|
|
5644
|
-
type = 'line';
|
|
5645
|
-
shape = document.createElementNS(svgNs, type);
|
|
5646
|
-
let [x1, y1, x2, y2] = [...pathData[0].values, ...pathData[1].values].map(val => roundTo(val, decimals));
|
|
5647
|
-
attsNew = { x1, y1, x2, y2 };
|
|
5648
|
-
}
|
|
5649
|
-
// polygon, polyline or rect
|
|
5650
|
-
else {
|
|
5651
|
-
|
|
5652
|
-
let vertices = getPathDataVertices(pathData);
|
|
5653
|
-
let bb = getPolyBBox(vertices);
|
|
5654
|
-
let areaPoly = getPolygonArea(vertices, true);
|
|
5655
|
-
let areaRect = bb.width * bb.height;
|
|
5656
|
-
let areaDiff = Math.abs(1 - areaRect / areaPoly);
|
|
5657
|
-
|
|
5658
|
-
// is rect
|
|
5659
|
-
if (convert_rects && areaDiff < 0.01) {
|
|
5660
|
-
type = 'rect';
|
|
5661
|
-
shape = document.createElementNS(svgNs, type);
|
|
5662
|
-
let { x, y, width, height } = bb;
|
|
5663
|
-
attsNew = { x, y, width, height };
|
|
5664
|
-
|
|
5665
|
-
}
|
|
5666
|
-
// polyline or polygon
|
|
5667
|
-
else if(convert_poly) {
|
|
5668
|
-
type = closed ? 'polygon' : 'polyline';
|
|
5669
|
-
shape = document.createElementNS(svgNs, type);
|
|
5670
|
-
let points = vertices.map(pt => { return [pt.x, pt.y] }).flat().map(val => roundTo(val, decimals)).join(' ');
|
|
5671
|
-
attsNew = { points };
|
|
5672
|
-
}
|
|
5673
|
-
}
|
|
5674
|
-
}
|
|
5675
|
-
// circles or ellipses
|
|
5676
|
-
else if (!hasLines && convert_ellipses) {
|
|
5677
|
-
|
|
5678
|
-
// try to convert cubics to arcs
|
|
5679
|
-
if (!hasArcs && hasBeziers) {
|
|
5680
|
-
pathData = pathDataCubicsToArc(pathData, { areaThreshold: 2.5 });
|
|
5681
|
-
hasArcs = pathData.filter(com => com.type === 'A').length;
|
|
5682
|
-
}
|
|
5683
|
-
|
|
5684
|
-
if (hasArcs) {
|
|
5685
|
-
let pathData2 = getPathDataVerbose(pathData, { addArcParams: true });
|
|
5686
|
-
let arcComs = pathData2.filter(com => com.type === 'A');
|
|
5687
|
-
|
|
5688
|
-
let cxVals = new Set();
|
|
5689
|
-
let cyVals = new Set();
|
|
5690
|
-
let rxVals = new Set();
|
|
5691
|
-
let ryVals = new Set();
|
|
5692
|
-
|
|
5693
|
-
if (arcComs.length > 1) {
|
|
5694
|
-
|
|
5695
|
-
pathData2.forEach(com => {
|
|
5696
|
-
if (com.type === 'A') {
|
|
5697
|
-
|
|
5698
|
-
cxVals.add(roundTo(com.cx, decimals));
|
|
5699
|
-
cyVals.add(roundTo(com.cy, decimals));
|
|
5700
|
-
rxVals.add(roundTo(com.rx, decimals));
|
|
5701
|
-
ryVals.add(roundTo(com.ry, decimals));
|
|
5702
|
-
}
|
|
5703
|
-
});
|
|
5704
|
-
}
|
|
5705
|
-
|
|
5706
|
-
cxVals = Array.from(cxVals);
|
|
5707
|
-
cyVals = Array.from(cyVals);
|
|
5708
|
-
rxVals = Array.from(rxVals);
|
|
5709
|
-
ryVals = Array.from(ryVals);
|
|
5710
|
-
|
|
5711
|
-
if(cxVals.length===1 && cyVals.length===1 && rxVals.length===1 && ryVals.length===1){
|
|
5712
|
-
let [rx, ry, cx, cy] = [rxVals[0], ryVals[0], cxVals[0], cyVals[0]];
|
|
5713
|
-
type = rx===ry ? 'circle' : 'ellipse';
|
|
5714
|
-
shape = document.createElementNS(svgNs, type);
|
|
5715
|
-
attsNew = type==='circle' ? { r:rx, cx, cy } : {rx, ry, cx, cy};
|
|
5716
|
-
}
|
|
5717
|
-
}
|
|
5718
|
-
}
|
|
5719
|
-
|
|
5720
|
-
// if el could be replaced
|
|
5721
|
-
if (shape) {
|
|
5722
|
-
let ignore = ['id', 'class'];
|
|
5723
|
-
|
|
5724
|
-
// set shape attributes
|
|
5725
|
-
for (let att in attsNew) {
|
|
5726
|
-
shape.setAttribute(att, attsNew[att]);
|
|
5727
|
-
}
|
|
5728
|
-
|
|
5729
|
-
// copy old attributes
|
|
5730
|
-
for (let att in attributes) {
|
|
5731
|
-
|
|
5732
|
-
if (attLookup.atts[att].includes(type) || ignore.includes(att) || att.startsWith('data-')) {
|
|
5733
|
-
shape.setAttribute(att, attributes[att]);
|
|
5734
|
-
}
|
|
5735
|
-
}
|
|
5736
|
-
|
|
5737
|
-
// replace
|
|
5738
|
-
el = shape;
|
|
5739
|
-
}
|
|
5740
|
-
|
|
5741
|
-
return el;
|
|
5742
|
-
|
|
5743
|
-
}
|
|
5744
|
-
|
|
5706
|
+
/**
|
|
5707
|
+
* Convert shapes to paths
|
|
5708
|
+
* converts also transforms
|
|
5709
|
+
*/
|
|
5745
5710
|
function shapeElToPath(el, { width = 0,
|
|
5746
5711
|
height = 0,
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
convert_poly = false,
|
|
5750
|
-
convert_lines = false,
|
|
5751
|
-
|
|
5752
|
-
matrix=null
|
|
5712
|
+
convertShapes = [],
|
|
5713
|
+
matrix = null
|
|
5753
5714
|
|
|
5754
5715
|
} = {}) {
|
|
5755
5716
|
|
|
5756
5717
|
let nodeName = el.nodeName.toLowerCase();
|
|
5757
5718
|
|
|
5758
|
-
if (
|
|
5759
|
-
nodeName === 'path' && !matrix ||
|
|
5760
|
-
nodeName === 'rect' && !convert_rects ||
|
|
5761
|
-
(nodeName === 'circle' || nodeName === 'ellipse') && !convert_ellipses ||
|
|
5762
|
-
(nodeName === 'polygon' || nodeName === 'polyline') && !convert_poly ||
|
|
5763
|
-
(nodeName === 'line') && !convert_lines
|
|
5764
|
-
) return el;
|
|
5719
|
+
if (!convertShapes.includes(nodeName)) return el;
|
|
5765
5720
|
|
|
5766
5721
|
let pathData = getPathDataFromEl(el, { width, height });
|
|
5767
5722
|
|
|
@@ -5769,8 +5724,9 @@ function shapeElToPath(el, { width = 0,
|
|
|
5769
5724
|
let exclude = ['d', 'x', 'y', 'x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'dx', 'dy', 'r', 'rx', 'ry', 'width', 'height', 'points'];
|
|
5770
5725
|
|
|
5771
5726
|
// transform pathData
|
|
5772
|
-
if(matrix && Object.values(matrix).join('')!=='100100'){
|
|
5727
|
+
if (matrix && Object.values(matrix).join('') !== '100100') {
|
|
5773
5728
|
pathData = transformPathData(pathData, matrix);
|
|
5729
|
+
|
|
5774
5730
|
exclude.push('transform', 'transform-origin');
|
|
5775
5731
|
}
|
|
5776
5732
|
|
|
@@ -5791,13 +5747,6 @@ function shapeElToPath(el, { width = 0,
|
|
|
5791
5747
|
return pathN
|
|
5792
5748
|
|
|
5793
5749
|
}
|
|
5794
|
-
/*
|
|
5795
|
-
export function copyAttributes(newEl, oldEl){
|
|
5796
|
-
|
|
5797
|
-
let attributes = [...oldEl.attributes].map(att => att.name);
|
|
5798
|
-
|
|
5799
|
-
}
|
|
5800
|
-
*/
|
|
5801
5750
|
|
|
5802
5751
|
// retrieve pathdata from svg geometry elements
|
|
5803
5752
|
function getPathDataFromEl(el, {
|
|
@@ -5828,39 +5777,7 @@ function getPathDataFromEl(el, {
|
|
|
5828
5777
|
|
|
5829
5778
|
case 'rect':
|
|
5830
5779
|
({ x=0, y=0, width=0, height=0, rx=0, ry=0 } = atts);
|
|
5831
|
-
|
|
5832
|
-
if (!rx && !ry) {
|
|
5833
|
-
pathData = [
|
|
5834
|
-
{ type: "M", values: [x, y] },
|
|
5835
|
-
{ type: "L", values: [x + width, y] },
|
|
5836
|
-
{ type: "L", values: [x + width, y + height] },
|
|
5837
|
-
{ type: "L", values: [x, y + height] },
|
|
5838
|
-
{ type: "Z", values: [] }
|
|
5839
|
-
];
|
|
5840
|
-
} else {
|
|
5841
|
-
|
|
5842
|
-
rx = rx ? rx : ry;
|
|
5843
|
-
ry = ry ? ry : rx;
|
|
5844
|
-
|
|
5845
|
-
if (rx > width / 2) {
|
|
5846
|
-
rx = width / 2;
|
|
5847
|
-
}
|
|
5848
|
-
if (ry > height / 2) {
|
|
5849
|
-
ry = height / 2;
|
|
5850
|
-
}
|
|
5851
|
-
pathData = [
|
|
5852
|
-
{ type: "M", values: [x + rx, y] },
|
|
5853
|
-
{ type: "L", values: [x + width - rx, y] },
|
|
5854
|
-
{ type: "A", values: [rx, ry, 0, 0, 1, x + width, y + ry] },
|
|
5855
|
-
{ type: "L", values: [x + width, y + height - ry] },
|
|
5856
|
-
{ type: "A", values: [rx, ry, 0, 0, 1, x + width - rx, y + height] },
|
|
5857
|
-
{ type: "L", values: [x + rx, y + height] },
|
|
5858
|
-
{ type: "A", values: [rx, ry, 0, 0, 1, x, y + height - ry] },
|
|
5859
|
-
{ type: "L", values: [x, y + ry] },
|
|
5860
|
-
{ type: "A", values: [rx, ry, 0, 0, 1, x + rx, y] },
|
|
5861
|
-
{ type: "Z", values: [] }
|
|
5862
|
-
];
|
|
5863
|
-
}
|
|
5780
|
+
pathData = rectToPathData(x, y, width, height, rx, ry);
|
|
5864
5781
|
break;
|
|
5865
5782
|
|
|
5866
5783
|
case 'circle':
|
|
@@ -5920,25 +5837,187 @@ function getPathDataFromEl(el, {
|
|
|
5920
5837
|
|
|
5921
5838
|
}
|
|
5922
5839
|
|
|
5923
|
-
function
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
flatBezierToLinetos = true
|
|
5927
|
-
} = {}) {
|
|
5840
|
+
function rectToPathData(x = 0, y = 0, width = 0, height = 0, rx = 0, ry = 0) {
|
|
5841
|
+
let pathData = [];
|
|
5928
5842
|
|
|
5929
|
-
|
|
5843
|
+
if (!rx && !ry) {
|
|
5844
|
+
pathData = [
|
|
5845
|
+
{ type: "M", values: [x, y] },
|
|
5846
|
+
{ type: "L", values: [x + width, y] },
|
|
5847
|
+
{ type: "L", values: [x + width, y + height] },
|
|
5848
|
+
{ type: "L", values: [x, y + height] },
|
|
5849
|
+
{ type: "Z", values: [] }
|
|
5850
|
+
];
|
|
5851
|
+
} else {
|
|
5930
5852
|
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
let p = M;
|
|
5934
|
-
pathData[pathData.length - 1].type.toLowerCase() === 'z';
|
|
5853
|
+
rx = rx ? rx : ry;
|
|
5854
|
+
ry = ry ? ry : rx;
|
|
5935
5855
|
|
|
5936
|
-
|
|
5856
|
+
if (rx > width / 2) {
|
|
5857
|
+
rx = width / 2;
|
|
5858
|
+
}
|
|
5859
|
+
if (ry > height / 2) {
|
|
5860
|
+
ry = height / 2;
|
|
5861
|
+
}
|
|
5862
|
+
pathData = [
|
|
5863
|
+
{ type: "M", values: [x + rx, y] },
|
|
5864
|
+
{ type: "L", values: [x + width - rx, y] },
|
|
5865
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x + width, y + ry] },
|
|
5866
|
+
{ type: "L", values: [x + width, y + height - ry] },
|
|
5867
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x + width - rx, y + height] },
|
|
5868
|
+
{ type: "L", values: [x + rx, y + height] },
|
|
5869
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x, y + height - ry] },
|
|
5870
|
+
{ type: "L", values: [x, y + ry] },
|
|
5871
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x + rx, y] },
|
|
5872
|
+
{ type: "Z", values: [] }
|
|
5873
|
+
];
|
|
5874
|
+
}
|
|
5937
5875
|
|
|
5938
|
-
|
|
5939
|
-
|
|
5876
|
+
return pathData
|
|
5877
|
+
}
|
|
5940
5878
|
|
|
5941
|
-
|
|
5879
|
+
function pathElToShape(el, {
|
|
5880
|
+
convertShapes = [],
|
|
5881
|
+
} = {}) {
|
|
5882
|
+
|
|
5883
|
+
let pathData = parsePathDataNormalized(el.getAttribute('d'));
|
|
5884
|
+
let coms = Array.from(new Set(pathData.map(com => com.type))).join('');
|
|
5885
|
+
|
|
5886
|
+
let hasArcs = (/[a]/gi).test(coms);
|
|
5887
|
+
let hasBeziers = (/[csqt]/gi).test(coms);
|
|
5888
|
+
let hasLines = (/[l]/gi).test(coms);
|
|
5889
|
+
let isPoly = !(/[acqts]/gi).test(coms);
|
|
5890
|
+
let closed = (/[z]/gi).test(coms);
|
|
5891
|
+
let shape = null;
|
|
5892
|
+
let type = null;
|
|
5893
|
+
|
|
5894
|
+
let attributes = getElementAtts(el);
|
|
5895
|
+
let attsNew = {};
|
|
5896
|
+
let decimals = 7;
|
|
5897
|
+
|
|
5898
|
+
if (isPoly) {
|
|
5899
|
+
|
|
5900
|
+
// is line
|
|
5901
|
+
if (pathData.length === 2 && convertShapes.includes('line')) {
|
|
5902
|
+
type = 'line';
|
|
5903
|
+
shape = document.createElementNS(svgNs, type);
|
|
5904
|
+
let [x1, y1, x2, y2] = [...pathData[0].values, ...pathData[1].values].map(val => roundTo(val, decimals));
|
|
5905
|
+
attsNew = { x1, y1, x2, y2 };
|
|
5906
|
+
}
|
|
5907
|
+
// polygon, polyline or rect
|
|
5908
|
+
else {
|
|
5909
|
+
|
|
5910
|
+
let vertices = getPathDataVertices(pathData);
|
|
5911
|
+
let bb = getPolyBBox(vertices);
|
|
5912
|
+
let areaPoly = getPolygonArea(vertices, true);
|
|
5913
|
+
let areaRect = bb.width * bb.height;
|
|
5914
|
+
let areaDiff = Math.abs(1 - areaRect / areaPoly);
|
|
5915
|
+
|
|
5916
|
+
// is rect
|
|
5917
|
+
if (convertShapes.includes('rect') && areaDiff < 0.01) {
|
|
5918
|
+
type = 'rect';
|
|
5919
|
+
shape = document.createElementNS(svgNs, type);
|
|
5920
|
+
let { x, y, width, height } = bb;
|
|
5921
|
+
attsNew = { x, y, width, height };
|
|
5922
|
+
|
|
5923
|
+
}
|
|
5924
|
+
// polyline or polygon
|
|
5925
|
+
else if (convertShapes.includes('polygon') || convertShapes.includes('polyline')) {
|
|
5926
|
+
type = closed ? 'polygon' : 'polyline';
|
|
5927
|
+
shape = document.createElementNS(svgNs, type);
|
|
5928
|
+
let points = vertices.map(pt => { return [pt.x, pt.y] }).flat().map(val => roundTo(val, decimals)).join(' ');
|
|
5929
|
+
attsNew = { points };
|
|
5930
|
+
}
|
|
5931
|
+
}
|
|
5932
|
+
}
|
|
5933
|
+
// circles or ellipses
|
|
5934
|
+
else if (!hasLines && (convertShapes.includes('circle') || convertShapes.includes('ellipse'))) {
|
|
5935
|
+
|
|
5936
|
+
// try to convert cubics to arcs
|
|
5937
|
+
if (!hasArcs && hasBeziers) {
|
|
5938
|
+
pathData = pathDataCubicsToArc(pathData, { areaThreshold: 2.5 });
|
|
5939
|
+
hasArcs = pathData.filter(com => com.type === 'A').length;
|
|
5940
|
+
}
|
|
5941
|
+
|
|
5942
|
+
if (hasArcs) {
|
|
5943
|
+
let pathData2 = getPathDataVerbose(pathData, { addArcParams: true });
|
|
5944
|
+
let arcComs = pathData2.filter(com => com.type === 'A');
|
|
5945
|
+
|
|
5946
|
+
let cxVals = new Set();
|
|
5947
|
+
let cyVals = new Set();
|
|
5948
|
+
let rxVals = new Set();
|
|
5949
|
+
let ryVals = new Set();
|
|
5950
|
+
|
|
5951
|
+
if (arcComs.length > 1) {
|
|
5952
|
+
|
|
5953
|
+
pathData2.forEach(com => {
|
|
5954
|
+
if (com.type === 'A') {
|
|
5955
|
+
|
|
5956
|
+
cxVals.add(roundTo(com.cx, decimals));
|
|
5957
|
+
cyVals.add(roundTo(com.cy, decimals));
|
|
5958
|
+
rxVals.add(roundTo(com.rx, decimals));
|
|
5959
|
+
ryVals.add(roundTo(com.ry, decimals));
|
|
5960
|
+
}
|
|
5961
|
+
});
|
|
5962
|
+
}
|
|
5963
|
+
|
|
5964
|
+
cxVals = Array.from(cxVals);
|
|
5965
|
+
cyVals = Array.from(cyVals);
|
|
5966
|
+
rxVals = Array.from(rxVals);
|
|
5967
|
+
ryVals = Array.from(ryVals);
|
|
5968
|
+
|
|
5969
|
+
if (cxVals.length === 1 && cyVals.length === 1 && rxVals.length === 1 && ryVals.length === 1) {
|
|
5970
|
+
let [rx, ry, cx, cy] = [rxVals[0], ryVals[0], cxVals[0], cyVals[0]];
|
|
5971
|
+
type = rx === ry ? 'circle' : 'ellipse';
|
|
5972
|
+
shape = document.createElementNS(svgNs, type);
|
|
5973
|
+
attsNew = type === 'circle' ? { r: rx, cx, cy } : { rx, ry, cx, cy };
|
|
5974
|
+
}
|
|
5975
|
+
}
|
|
5976
|
+
}
|
|
5977
|
+
|
|
5978
|
+
// if el could be replaced
|
|
5979
|
+
if (shape) {
|
|
5980
|
+
let ignore = ['id', 'class'];
|
|
5981
|
+
|
|
5982
|
+
// set shape attributes
|
|
5983
|
+
for (let att in attsNew) {
|
|
5984
|
+
shape.setAttribute(att, attsNew[att]);
|
|
5985
|
+
}
|
|
5986
|
+
|
|
5987
|
+
// copy old attributes
|
|
5988
|
+
for (let att in attributes) {
|
|
5989
|
+
|
|
5990
|
+
if (attLookup.atts[att].includes(type) || ignore.includes(att) || att.startsWith('data-')) {
|
|
5991
|
+
shape.setAttribute(att, attributes[att]);
|
|
5992
|
+
}
|
|
5993
|
+
}
|
|
5994
|
+
// replace
|
|
5995
|
+
el = shape;
|
|
5996
|
+
}
|
|
5997
|
+
|
|
5998
|
+
return el;
|
|
5999
|
+
|
|
6000
|
+
}
|
|
6001
|
+
|
|
6002
|
+
function pathDataRemoveColinear(pathData, {
|
|
6003
|
+
tolerance = 1,
|
|
6004
|
+
|
|
6005
|
+
flatBezierToLinetos = true
|
|
6006
|
+
} = {}) {
|
|
6007
|
+
|
|
6008
|
+
let pathDataN = [pathData[0]];
|
|
6009
|
+
|
|
6010
|
+
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
6011
|
+
let p0 = M;
|
|
6012
|
+
let p = M;
|
|
6013
|
+
pathData[pathData.length - 1].type.toLowerCase() === 'z';
|
|
6014
|
+
|
|
6015
|
+
for (let c = 1, l = pathData.length; c < l; c++) {
|
|
6016
|
+
|
|
6017
|
+
let com = pathData[c];
|
|
6018
|
+
let comN = pathData[c + 1] || pathData[l - 1];
|
|
6019
|
+
|
|
6020
|
+
let p1 = comN.type.toLowerCase() === 'z' ? M : { x: comN.values[comN.values.length - 2], y: comN.values[comN.values.length - 1] };
|
|
5942
6021
|
|
|
5943
6022
|
let { type, values } = com;
|
|
5944
6023
|
let valsL = values.slice(-2);
|
|
@@ -6722,8 +6801,17 @@ function polyPtsToArray(pts) {
|
|
|
6722
6801
|
// convert flat point value array to point object array
|
|
6723
6802
|
function toPointArray(pts) {
|
|
6724
6803
|
let ptArr = [];
|
|
6725
|
-
|
|
6726
|
-
|
|
6804
|
+
|
|
6805
|
+
if(pts[0].length===2){
|
|
6806
|
+
for (let i = 0, l = pts.length; i < l; i ++) {
|
|
6807
|
+
let pt = pts[i];
|
|
6808
|
+
ptArr.push({ x: pt[0], y:pt[1] });
|
|
6809
|
+
}
|
|
6810
|
+
|
|
6811
|
+
}else {
|
|
6812
|
+
for (let i = 1, l = pts.length; i < l; i += 2) {
|
|
6813
|
+
ptArr.push({ x: pts[i - 1], y: pts[i] });
|
|
6814
|
+
}
|
|
6727
6815
|
}
|
|
6728
6816
|
return ptArr;
|
|
6729
6817
|
}
|
|
@@ -6779,29 +6867,63 @@ function parseStylesProperties(el, {
|
|
|
6779
6867
|
autoRoundValues = false,
|
|
6780
6868
|
minifyRgbColors = false,
|
|
6781
6869
|
removeInvalid = true,
|
|
6870
|
+
allowDataAtts=true,
|
|
6871
|
+
allowAriaAtts=true,
|
|
6782
6872
|
removeDefaults = true,
|
|
6783
6873
|
cleanUpStrokes = true,
|
|
6784
6874
|
normalizeTransforms = true,
|
|
6875
|
+
removeIds = false,
|
|
6876
|
+
removeClassNames = false,
|
|
6877
|
+
|
|
6878
|
+
include = [],
|
|
6785
6879
|
exclude = [],
|
|
6786
6880
|
width = 0,
|
|
6787
6881
|
height = 0,
|
|
6882
|
+
stylesheetProps = {}
|
|
6788
6883
|
} = {}) {
|
|
6789
6884
|
|
|
6790
6885
|
let nodeName = el.nodeName.toLowerCase();
|
|
6791
6886
|
let attProps = getSvgPresentationAtts(el);
|
|
6792
|
-
let
|
|
6887
|
+
let inlineCssProps = getSvgCssProps(el);
|
|
6888
|
+
|
|
6889
|
+
// get CSS properties from SVG style element
|
|
6890
|
+
let cssRuleSelectors = el.cssRules || [];
|
|
6891
|
+
let cssProps = {};
|
|
6892
|
+
|
|
6893
|
+
cssRuleSelectors.forEach(selector => {
|
|
6894
|
+
for (let prop in stylesheetProps[selector]) {
|
|
6895
|
+
let val = stylesheetProps[selector][prop];
|
|
6896
|
+
|
|
6897
|
+
// check CSS vars
|
|
6898
|
+
if (val.startsWith('var(')) {
|
|
6899
|
+
let varName = val.replace(/[var\(|\)]/g, '').trim();
|
|
6900
|
+
|
|
6901
|
+
if (stylesheetProps[':root']) {
|
|
6902
|
+
val = `var(${varName}, ${stylesheetProps[':root'][varName]})`;
|
|
6903
|
+
}
|
|
6904
|
+
}
|
|
6905
|
+
cssProps[prop] = val;
|
|
6906
|
+
}
|
|
6907
|
+
});
|
|
6793
6908
|
|
|
6794
6909
|
/**
|
|
6795
|
-
* merge props
|
|
6796
|
-
*
|
|
6910
|
+
* merge props by specificity
|
|
6911
|
+
* 1. attributes
|
|
6912
|
+
* 2. CSS rules
|
|
6913
|
+
* 3. inline CSS
|
|
6797
6914
|
*/
|
|
6798
6915
|
let props = {
|
|
6799
6916
|
...attProps,
|
|
6800
6917
|
...cssProps,
|
|
6918
|
+
...inlineCssProps,
|
|
6801
6919
|
};
|
|
6802
6920
|
|
|
6921
|
+
// obsolete/not style relevant anymore
|
|
6803
6922
|
delete props['style'];
|
|
6804
|
-
|
|
6923
|
+
delete props['class'];
|
|
6924
|
+
delete props['id'];
|
|
6925
|
+
|
|
6926
|
+
exclude.push('style', 'class', 'id');
|
|
6805
6927
|
|
|
6806
6928
|
let remove = ['style'];
|
|
6807
6929
|
let transformsStandalone = ['scale', 'translate', 'rotate'];
|
|
@@ -6812,9 +6934,10 @@ function parseStylesProperties(el, {
|
|
|
6812
6934
|
*/
|
|
6813
6935
|
|
|
6814
6936
|
if (removeInvalid || removeDefaults || removeNameSpaced) {
|
|
6815
|
-
let propsFilteredObj = filterSvgElProps(nodeName, props, { removeDefaults, removeNameSpaced, exclude, cleanUpStrokes, include: transformsStandalone, cleanUpStrokes: false });
|
|
6937
|
+
let propsFilteredObj = filterSvgElProps(nodeName, props, {allowDataAtts, allowAriaAtts, removeIds, removeClassNames, removeDefaults, removeNameSpaced, exclude, cleanUpStrokes, include: [...transformsStandalone, ...include], cleanUpStrokes: false });
|
|
6816
6938
|
props = propsFilteredObj.propsFiltered;
|
|
6817
6939
|
remove.push(...propsFilteredObj.remove);
|
|
6940
|
+
|
|
6818
6941
|
}
|
|
6819
6942
|
|
|
6820
6943
|
// sanitized prop array
|
|
@@ -6833,11 +6956,14 @@ function parseStylesProperties(el, {
|
|
|
6833
6956
|
|
|
6834
6957
|
// minify rgb values
|
|
6835
6958
|
if (minifyRgbColors && colorProps.includes(prop)) {
|
|
6959
|
+
|
|
6836
6960
|
let color = parseColor(valueStr);
|
|
6961
|
+
|
|
6837
6962
|
if (color.mode === 'rgba' || color.mode === 'rgb') {
|
|
6838
6963
|
let hex = rgba2Hex(color);
|
|
6839
6964
|
valueStr = hex;
|
|
6840
6965
|
}
|
|
6966
|
+
|
|
6841
6967
|
}
|
|
6842
6968
|
|
|
6843
6969
|
if (prop === 'transform') {
|
|
@@ -6877,7 +7003,6 @@ function parseStylesProperties(el, {
|
|
|
6877
7003
|
else {
|
|
6878
7004
|
|
|
6879
7005
|
item.values = parseValue(valueStr);
|
|
6880
|
-
|
|
6881
7006
|
}
|
|
6882
7007
|
|
|
6883
7008
|
if (item.values.length) {
|
|
@@ -6952,6 +7077,7 @@ function parseStylesProperties(el, {
|
|
|
6952
7077
|
|
|
6953
7078
|
if (autoRoundValues && isNumeric) {
|
|
6954
7079
|
valAbs = autoRound(valAbs);
|
|
7080
|
+
|
|
6955
7081
|
}
|
|
6956
7082
|
|
|
6957
7083
|
}
|
|
@@ -7146,16 +7272,24 @@ function filterSvgElProps(elNodename = '', props = {}, {
|
|
|
7146
7272
|
removeInvalid = true,
|
|
7147
7273
|
removeDefaults = true,
|
|
7148
7274
|
allowDataAtts = true,
|
|
7275
|
+
allowMeta = false,
|
|
7276
|
+
allowAriaAtts = false,
|
|
7149
7277
|
cleanUpStrokes = true,
|
|
7150
|
-
|
|
7278
|
+
|
|
7279
|
+
include = [],
|
|
7280
|
+
removeIds = false,
|
|
7281
|
+
removeClassNames = false,
|
|
7151
7282
|
exclude = [],
|
|
7152
7283
|
} = {}) {
|
|
7153
7284
|
let propsFiltered = {};
|
|
7154
7285
|
let remove = [];
|
|
7155
7286
|
|
|
7287
|
+
if (!removeIds) include.push('id');
|
|
7288
|
+
if (!removeClassNames) include.push('class');
|
|
7289
|
+
|
|
7156
7290
|
// allow defaults for nested
|
|
7157
7291
|
|
|
7158
|
-
let noStrokeColor = cleanUpStrokes ? (props['stroke'] === undefined) : false;
|
|
7292
|
+
let noStrokeColor = cleanUpStrokes ? (props['stroke'] === undefined || props['stroke'][0] === 'none') : false;
|
|
7159
7293
|
|
|
7160
7294
|
for (let prop in props) {
|
|
7161
7295
|
let values = props[prop];
|
|
@@ -7167,43 +7301,41 @@ function filterSvgElProps(elNodename = '', props = {}, {
|
|
|
7167
7301
|
false;
|
|
7168
7302
|
|
|
7169
7303
|
// remove null transforms
|
|
7170
|
-
if(prop==='transform' && value==='matrix(1 0 0 1 0 0)') isValid = false;
|
|
7304
|
+
if (prop === 'transform' && value === 'matrix(1 0 0 1 0 0)') isValid = false;
|
|
7171
7305
|
|
|
7172
7306
|
// allow data attributes
|
|
7173
|
-
let isDataAtt =
|
|
7307
|
+
let isDataAtt = prop.startsWith('data-') ? true : false;
|
|
7308
|
+
let isMeta = prop === 'title';
|
|
7309
|
+
let isAria = prop.startsWith('aria-');
|
|
7310
|
+
|
|
7311
|
+
if( (allowDataAtts && isDataAtt) || (allowAriaAtts && isAria) || (allowMeta && isMeta ) ) continue
|
|
7174
7312
|
|
|
7175
7313
|
// filter out defaults
|
|
7176
7314
|
let isDefault = removeDefaults ?
|
|
7177
7315
|
(attLookup.defaults[prop] ? attLookup.defaults[prop] !== undefined && attLookup.defaults[prop].includes(value) : false) :
|
|
7178
7316
|
false;
|
|
7179
7317
|
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
if (
|
|
7183
|
-
if (
|
|
7318
|
+
let isFutileStroke = noStrokeColor && strokeAtts.includes(prop);
|
|
7319
|
+
|
|
7320
|
+
if (isDefault || isDataAtt || isMeta || isAria || isFutileStroke) isValid = false;
|
|
7321
|
+
if (include.includes(prop)) isValid = true;
|
|
7184
7322
|
|
|
7185
7323
|
if (isValid) {
|
|
7186
7324
|
propsFiltered[prop] = props[prop];
|
|
7187
7325
|
}
|
|
7188
7326
|
else {
|
|
7327
|
+
|
|
7189
7328
|
remove.push(prop);
|
|
7190
7329
|
}
|
|
7191
7330
|
}
|
|
7192
7331
|
|
|
7193
|
-
/*
|
|
7194
|
-
// set explicit stroke width when disabled by stroke color
|
|
7195
|
-
if (propsFiltered['stroke'] && propsFiltered['stroke'][0] === 'none') {
|
|
7196
|
-
propsFiltered['stroke-width'] = [1]
|
|
7197
|
-
remove.push('stroke', 'stroke-width')
|
|
7198
|
-
console.log('remove', remove);
|
|
7199
|
-
}
|
|
7200
|
-
*/
|
|
7201
|
-
|
|
7202
7332
|
return { propsFiltered, remove }
|
|
7203
7333
|
}
|
|
7204
7334
|
|
|
7205
7335
|
function parseValue(valStr = '') {
|
|
7206
|
-
|
|
7336
|
+
|
|
7337
|
+
valStr = valStr.replace('!important', '');
|
|
7338
|
+
let valArr = (valStr.includes("'") || valStr.includes('(') || valStr.includes(')')) ? [valStr] : valStr.split(/,| /).filter(Boolean);
|
|
7207
7339
|
|
|
7208
7340
|
for (let i = 0; i < valArr.length; i++) {
|
|
7209
7341
|
|
|
@@ -7284,110 +7416,787 @@ function parseInlineCss(styleAtt = '') {
|
|
|
7284
7416
|
return props
|
|
7285
7417
|
}
|
|
7286
7418
|
|
|
7287
|
-
function
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
|
|
7419
|
+
function toCamelCase(str) {
|
|
7420
|
+
return str
|
|
7421
|
+
.split(/[-| ]/)
|
|
7422
|
+
.map((e, i) => i
|
|
7423
|
+
? e.charAt(0).toUpperCase() + e.slice(1).toLowerCase()
|
|
7424
|
+
: e.toLowerCase()
|
|
7425
|
+
)
|
|
7426
|
+
.join('')
|
|
7292
7427
|
}
|
|
7293
7428
|
|
|
7294
|
-
function
|
|
7295
|
-
|
|
7429
|
+
function toShortStr(str) {
|
|
7430
|
+
if (isNumericValue(str)) return str
|
|
7431
|
+
let strShort = str.split('-').map(str => { return str.replace(/a|e|i|o|u/g, '') }).join('-');
|
|
7432
|
+
strShort = toCamelCase(strShort);
|
|
7433
|
+
return strShort
|
|
7434
|
+
}
|
|
7296
7435
|
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
7301
|
-
|
|
7302
|
-
fixHref = false,
|
|
7303
|
-
legacyHref = false,
|
|
7304
|
-
cleanupDefs = true,
|
|
7305
|
-
cleanupClip = true,
|
|
7306
|
-
addViewBox = false,
|
|
7307
|
-
addDimensions = false,
|
|
7308
|
-
minifyRgbColors = false,
|
|
7436
|
+
function stringifySVG(svg, {
|
|
7437
|
+
omitNamespace = false,
|
|
7438
|
+
removeComments = true,
|
|
7439
|
+
format = 0,
|
|
7440
|
+
} = {}) {
|
|
7309
7441
|
|
|
7310
|
-
|
|
7311
|
-
autoRoundValues = true,
|
|
7442
|
+
let markup = '';
|
|
7312
7443
|
|
|
7313
|
-
|
|
7444
|
+
if (format < 2) {
|
|
7445
|
+
markup = new XMLSerializer().serializeToString(svg);
|
|
7314
7446
|
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7447
|
+
markup = minifySVGMarkup(markup, { removeComments });
|
|
7448
|
+
|
|
7449
|
+
} else {
|
|
7450
|
+
markup = serializeSVGPretty(svg);
|
|
7451
|
+
}
|
|
7320
7452
|
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
convert_poly = false,
|
|
7325
|
-
convert_lines = false,
|
|
7453
|
+
if (omitNamespace) {
|
|
7454
|
+
markup = markup.replaceAll('xmlns="http://www.w3.org/2000/svg"', '');
|
|
7455
|
+
}
|
|
7326
7456
|
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
excludedEls = [],
|
|
7332
|
-
} = {}) {
|
|
7457
|
+
if (removeComments) {
|
|
7458
|
+
markup = markup
|
|
7459
|
+
.replace(/(<!--.*?-->)|(<!--[\S\s]+?-->)|(<!--[\S\s]*?$)/g, '');
|
|
7460
|
+
}
|
|
7333
7461
|
|
|
7334
|
-
|
|
7335
|
-
|
|
7462
|
+
/*
|
|
7463
|
+
markup = markup
|
|
7464
|
+
.replace(/\t/g, "")
|
|
7465
|
+
.replace(/[\n\r|]/g, "\n")
|
|
7466
|
+
.replace(/\n\s*\n/g, '\n')
|
|
7467
|
+
.replace(/ +/g, ' ')
|
|
7336
7468
|
|
|
7337
|
-
|
|
7338
|
-
.
|
|
7339
|
-
|
|
7469
|
+
.replace(/> </g, '><')
|
|
7470
|
+
.trim()
|
|
7471
|
+
// sanitize linebreaks within pathdata
|
|
7472
|
+
.replaceAll(' ', '\n');
|
|
7473
|
+
*/
|
|
7340
7474
|
|
|
7341
|
-
|
|
7342
|
-
|
|
7475
|
+
return markup
|
|
7476
|
+
}
|
|
7343
7477
|
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
height: height,
|
|
7348
|
-
normalizeTransforms,
|
|
7349
|
-
removeDefaults: false,
|
|
7350
|
-
cleanUpStrokes: false,
|
|
7351
|
-
autoRoundValues,
|
|
7352
|
-
minifyRgbColors,
|
|
7353
|
-
};
|
|
7354
|
-
let stylePropsSVG = parseStylesProperties(svg, propOptions);
|
|
7478
|
+
function minifySVGMarkup(svg, {
|
|
7479
|
+
removeComments = true,
|
|
7480
|
+
} = {}) {
|
|
7355
7481
|
|
|
7356
|
-
|
|
7357
|
-
|
|
7482
|
+
if (removeComments) {
|
|
7483
|
+
svg = svg.replace(/<!--[\s\S]*?-->/g, '');
|
|
7484
|
+
}
|
|
7358
7485
|
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7486
|
+
// Remove whitespace between tags
|
|
7487
|
+
svg = svg.replace(/>\s+</g, '><')
|
|
7488
|
+
// Trim leading/trailing whitespace
|
|
7489
|
+
.trim()
|
|
7490
|
+
// Remove extra whitespace within tags (attributes)
|
|
7491
|
+
.replace(/\s+([=])\s+/g, '$1')
|
|
7492
|
+
.replace(/\s+(?=[^<]*>)/g, ' ')
|
|
7493
|
+
// Collapse multiple spaces to single space
|
|
7494
|
+
.replace(/\s{2,}/g, ' ')
|
|
7495
|
+
// Remove spaces around = signs in attributes
|
|
7496
|
+
.replace(/\s*=\s*/g, '=');
|
|
7366
7497
|
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
groupProps.push(stylePropsG);
|
|
7370
|
-
let children = g.querySelectorAll(`${renderedEls.join(', ')}`);
|
|
7498
|
+
return svg
|
|
7499
|
+
}
|
|
7371
7500
|
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7501
|
+
function serializeSVGPretty(xmlDoc, {
|
|
7502
|
+
indentSize = 2 } = {}) {
|
|
7503
|
+
if (typeof xmlDoc === 'string') {
|
|
7504
|
+
xmlDoc = new DOMParser().parseFromString(xmlDoc, 'image/svg+xml').querySelector('svg');
|
|
7505
|
+
}
|
|
7506
|
+
return formatXMLNode(xmlDoc, 0, indentSize);
|
|
7507
|
+
}
|
|
7508
|
+
|
|
7509
|
+
function formatXMLNode(node, level, indentSize) {
|
|
7510
|
+
let indent = " ".repeat(level * indentSize);
|
|
7511
|
+
|
|
7512
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
7513
|
+
let text = node.textContent.trim();
|
|
7514
|
+
return text ? text : "";
|
|
7515
|
+
}
|
|
7516
|
+
|
|
7517
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
7518
|
+
let hasChildren = node.children.length > 0;
|
|
7519
|
+
let hasTextContent = node.textContent.trim().length > 0 && node.children.length === 0;
|
|
7520
|
+
|
|
7521
|
+
let result = `${indent}<${node.nodeName}`;
|
|
7522
|
+
|
|
7523
|
+
// Add attributes
|
|
7524
|
+
for (let i = 0; i < node.attributes.length; i++) {
|
|
7525
|
+
let att = node.attributes[i];
|
|
7526
|
+
result += ` ${att.name}="${att.value}"`;
|
|
7527
|
+
}
|
|
7528
|
+
|
|
7529
|
+
if (!hasChildren && !hasTextContent) {
|
|
7530
|
+
return result + " />\n";
|
|
7531
|
+
}
|
|
7532
|
+
|
|
7533
|
+
result += ">";
|
|
7534
|
+
|
|
7535
|
+
if (hasChildren) {
|
|
7536
|
+
result += "\n";
|
|
7537
|
+
for (let child of node.children) {
|
|
7538
|
+
result += formatXMLNode(child, level + 1, indentSize);
|
|
7376
7539
|
}
|
|
7377
|
-
|
|
7540
|
+
result += `${indent}</${node.nodeName}>\n`;
|
|
7541
|
+
} else if (hasTextContent) {
|
|
7542
|
+
result += node.textContent.trim();
|
|
7543
|
+
result += `</${node.nodeName}>\n`;
|
|
7544
|
+
} else {
|
|
7545
|
+
result += `</${node.nodeName}>\n`;
|
|
7546
|
+
}
|
|
7547
|
+
|
|
7548
|
+
return result;
|
|
7549
|
+
}
|
|
7550
|
+
|
|
7551
|
+
return "";
|
|
7552
|
+
}
|
|
7553
|
+
|
|
7554
|
+
function removeHiddenSvgEls(svg) {
|
|
7555
|
+
let els = svg.querySelectorAll('*');
|
|
7556
|
+
els.forEach(el => {
|
|
7557
|
+
el.nodeName.toLowerCase();
|
|
7558
|
+
let style = el.getAttribute('style') || '';
|
|
7559
|
+
let isHiddenByStyle = style ? style.trim().includes('display:none') : false;
|
|
7560
|
+
let isHidden = (el.getAttribute('display') && el.getAttribute('display') === 'none') || isHiddenByStyle;
|
|
7561
|
+
if (isHidden) el.remove();
|
|
7562
|
+
});
|
|
7563
|
+
|
|
7564
|
+
}
|
|
7565
|
+
|
|
7566
|
+
function removeSvgEls(svg, {
|
|
7567
|
+
removeElements = [],
|
|
7568
|
+
removeNameSpaced = true,
|
|
7569
|
+
} = {}) {
|
|
7570
|
+
|
|
7571
|
+
// always remove scripts
|
|
7572
|
+
removeElements.push('script');
|
|
7573
|
+
|
|
7574
|
+
let els = svg.querySelectorAll('*');
|
|
7575
|
+
let allowMeta = !removeElements.includes('metadata');
|
|
7576
|
+
|
|
7577
|
+
els.forEach(el => {
|
|
7578
|
+
let nodeName = el.nodeName;
|
|
7579
|
+
let isMeta = allowMeta && el.closest('metadata');
|
|
7580
|
+
if (
|
|
7581
|
+
!isMeta &&
|
|
7582
|
+
((removeNameSpaced && nodeName.includes(':')) ||
|
|
7583
|
+
removeElements.includes(nodeName))
|
|
7584
|
+
) {
|
|
7585
|
+
el.remove();
|
|
7586
|
+
}
|
|
7587
|
+
});
|
|
7588
|
+
}
|
|
7589
|
+
|
|
7590
|
+
/*export function removeSvgEls(svg, remove = []) {
|
|
7591
|
+
// remove elements
|
|
7592
|
+
if (remove.length) {
|
|
7593
|
+
let selector = remove.join(', ').replaceAll(':', '\\:');
|
|
7594
|
+
svg.querySelectorAll(selector).forEach(el => {
|
|
7595
|
+
el.remove()
|
|
7596
|
+
})
|
|
7597
|
+
}
|
|
7598
|
+
}
|
|
7599
|
+
*/
|
|
7600
|
+
|
|
7601
|
+
function removeSvgAtts(svg, remove = []) {
|
|
7602
|
+
remove.forEach(att => {
|
|
7603
|
+
svg.removeAttribute(att);
|
|
7604
|
+
});
|
|
7605
|
+
}
|
|
7606
|
+
|
|
7607
|
+
function removeSvgChildAtts(svg, remove = []) {
|
|
7608
|
+
if (remove.length) {
|
|
7609
|
+
let selector = remove.map(att => { return `[${att}]` }).join(', ')
|
|
7610
|
+
// escape name spaced
|
|
7611
|
+
.replaceAll(':', '\\:');
|
|
7612
|
+
|
|
7613
|
+
svg.querySelectorAll(selector).forEach(el => {
|
|
7614
|
+
remove.forEach(att => {
|
|
7615
|
+
el.removeAttribute(att);
|
|
7616
|
+
});
|
|
7378
7617
|
});
|
|
7618
|
+
}
|
|
7619
|
+
}
|
|
7620
|
+
|
|
7621
|
+
/**
|
|
7622
|
+
* general clean up to remove bullshit like
|
|
7623
|
+
* version or enable background
|
|
7624
|
+
*/
|
|
7625
|
+
|
|
7626
|
+
function cleanupSVGAttributes(svg, {
|
|
7627
|
+
removeIds = false,
|
|
7628
|
+
removeClassNames = false,
|
|
7629
|
+
removeDimensions = false,
|
|
7630
|
+
stylesToAttributes = false,
|
|
7631
|
+
allowMeta = false,
|
|
7632
|
+
allowAriaAtts = false,
|
|
7633
|
+
allowDataAtts = false,
|
|
7634
|
+
} = {}) {
|
|
7635
|
+
|
|
7636
|
+
let allowed = new Set(['viewBox', 'xmlns', 'width', 'height']);
|
|
7637
|
+
|
|
7638
|
+
if (!removeIds) allowed.add('id');
|
|
7639
|
+
if (!removeClassNames) allowed.add('class');
|
|
7640
|
+
if (removeDimensions) {
|
|
7641
|
+
allowed.delete('width');
|
|
7642
|
+
allowed.delete('height');
|
|
7643
|
+
}
|
|
7644
|
+
|
|
7645
|
+
allowed = Array.from(allowed);
|
|
7646
|
+
if (!stylesToAttributes) {
|
|
7647
|
+
allowed.push('fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'font-size', 'font-family', 'font-style', 'style');
|
|
7648
|
+
}
|
|
7649
|
+
|
|
7650
|
+
removeExcludedAttribues(svg, { allowed, allowMeta, allowAriaAtts, allowDataAtts });
|
|
7651
|
+
}
|
|
7652
|
+
|
|
7653
|
+
function removeExcludedAttribues(el, {
|
|
7654
|
+
allowed = ['viewBox', 'xmlns', 'width', 'height', 'id', 'class'],
|
|
7655
|
+
allowAriaAtts = true,
|
|
7656
|
+
allowDataAtts = true,
|
|
7657
|
+
allowMeta = false
|
|
7658
|
+
} = {}) {
|
|
7659
|
+
let atts = [...el.attributes].map((att) => att.name);
|
|
7660
|
+
atts.forEach((att) => {
|
|
7661
|
+
|
|
7662
|
+
let isMeta = allowMeta && (att === 'title');
|
|
7663
|
+
let isAria = allowAriaAtts && att.startsWith('aria-');
|
|
7664
|
+
let isData = allowDataAtts && att.startsWith('data-');
|
|
7665
|
+
|
|
7666
|
+
if (
|
|
7667
|
+
!allowed.includes(att) &&
|
|
7668
|
+
!isAria && !isData && !isMeta
|
|
7669
|
+
) {
|
|
7670
|
+
el.removeAttribute(att);
|
|
7671
|
+
}
|
|
7672
|
+
});
|
|
7673
|
+
}
|
|
7674
|
+
|
|
7675
|
+
function removeElAtts(el, exclude = [], include = []) {
|
|
7676
|
+
let atts = [...el.attributes].map((att) => att.name);
|
|
7677
|
+
atts.forEach((att) => {
|
|
7678
|
+
if (exclude.includes(att) && !include.includes(att)) {
|
|
7679
|
+
el.removeAttribute(att);
|
|
7680
|
+
}
|
|
7379
7681
|
});
|
|
7682
|
+
}
|
|
7683
|
+
|
|
7684
|
+
/*
|
|
7685
|
+
|
|
7686
|
+
function cleanSvgPrologue(svgString) {
|
|
7687
|
+
return (
|
|
7688
|
+
svgString
|
|
7689
|
+
// Remove XML prologues like <?xml ... ?>
|
|
7690
|
+
.replace(/<\?xml[\s\S]*?\?>/gi, "")
|
|
7691
|
+
// Remove DOCTYPE declarations
|
|
7692
|
+
.replace(/<!DOCTYPE[\s\S]*?>/gi, "")
|
|
7693
|
+
// Remove comments <!-- ... -->
|
|
7694
|
+
.replace(/<!--[\s\S]*?-->/g, "")
|
|
7695
|
+
// Trim extra whitespace
|
|
7696
|
+
.trim()
|
|
7697
|
+
);
|
|
7698
|
+
}
|
|
7699
|
+
*/
|
|
7700
|
+
|
|
7701
|
+
function convertPathLengthAtt(el, {
|
|
7702
|
+
styleProps = {}
|
|
7703
|
+
}={}) {
|
|
7704
|
+
|
|
7705
|
+
let pathLength = el.getAttribute('pathLength') ? +el.getAttribute('pathLength') : 0;
|
|
7706
|
+
|
|
7707
|
+
if (pathLength && (styleProps['stroke-dasharray'] || styleProps['stroke-dashoffset'])) {
|
|
7708
|
+
let elLength = getElementLength(el, {
|
|
7709
|
+
pathLength,
|
|
7710
|
+
props: styleProps
|
|
7711
|
+
});
|
|
7712
|
+
|
|
7713
|
+
let scale = elLength / pathLength;
|
|
7714
|
+
|
|
7715
|
+
styleProps = scaleProps(styleProps, { props: ['stroke-dasharray', 'stroke-dashoffset'], scale });
|
|
7716
|
+
[styleProps['stroke-dasharray'], styleProps['stroke-dashoffset']];
|
|
7380
7717
|
|
|
7381
|
-
|
|
7718
|
+
// tag for removal
|
|
7719
|
+
delete styleProps['pathLength'];
|
|
7720
|
+
styleProps.remove.push('pathLength');
|
|
7721
|
+
el.removeAttribute('pathLength');
|
|
7382
7722
|
|
|
7383
|
-
let allowed = ['viewBox', 'xmlns', 'width', 'height', 'id', 'class'];
|
|
7384
|
-
if (!stylesToAttributes) {
|
|
7385
|
-
allowed.push('fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'font-size', 'font-family', 'font-style', 'style');
|
|
7386
7723
|
}
|
|
7387
7724
|
|
|
7388
|
-
|
|
7725
|
+
return styleProps;
|
|
7726
|
+
|
|
7727
|
+
}
|
|
7728
|
+
|
|
7729
|
+
function ungroupElements(groups) {
|
|
7730
|
+
groups.forEach((g, i) => {
|
|
7731
|
+
let children = [...g.children];
|
|
7732
|
+
|
|
7733
|
+
children.forEach(child => {
|
|
7734
|
+
g.parentNode.insertBefore(child, g);
|
|
7735
|
+
});
|
|
7736
|
+
g.remove();
|
|
7737
|
+
});
|
|
7738
|
+
}
|
|
7739
|
+
|
|
7740
|
+
/**
|
|
7741
|
+
* Parse nested CSS text into a flat object structure
|
|
7742
|
+
* Supports arbitrary nesting depth and & parent selector reference
|
|
7743
|
+
* Respects !important modifiers and handles data URLs
|
|
7744
|
+
*/
|
|
7745
|
+
function parseSvgCss(css, {
|
|
7746
|
+
parent=null,
|
|
7747
|
+
removeUnused=true,
|
|
7748
|
+
flatten = true
|
|
7749
|
+
}={}) {
|
|
7750
|
+
|
|
7751
|
+
let type = typeof css;
|
|
7752
|
+
if(type==='string') removeUnused = false;
|
|
7753
|
+
|
|
7754
|
+
// get style element text content
|
|
7755
|
+
if(type!=='string' ){
|
|
7756
|
+
if(css.nodeName==='style'){
|
|
7757
|
+
css = css.innerHTML;
|
|
7758
|
+
}
|
|
7759
|
+
else if(css.nodeName==='svg'){
|
|
7760
|
+
let styleEl = css.querySelector('style');
|
|
7761
|
+
if(!styleEl) return {}
|
|
7762
|
+
parent = css;
|
|
7763
|
+
css = styleEl.innerHTML;
|
|
7764
|
+
}
|
|
7765
|
+
|
|
7766
|
+
else {
|
|
7767
|
+
console.warn('invalid CSS input');
|
|
7768
|
+
return {}
|
|
7769
|
+
}
|
|
7770
|
+
}
|
|
7771
|
+
|
|
7772
|
+
css = css.trim();
|
|
7773
|
+
if (!css) return {};
|
|
7774
|
+
|
|
7775
|
+
// Remove comments
|
|
7776
|
+
css = css.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
7777
|
+
|
|
7778
|
+
function parseBlock(text, parentSelector = "") {
|
|
7779
|
+
let i = 0;
|
|
7780
|
+
let rules = {};
|
|
7781
|
+
let l = text.length;
|
|
7782
|
+
|
|
7783
|
+
while (i < l) {
|
|
7784
|
+
// Skip whitespace
|
|
7785
|
+
while (/\s/.test(text[i])) i++;
|
|
7786
|
+
if (i >= l) break;
|
|
7787
|
+
|
|
7788
|
+
// Peek ahead to check if this is a selector or a declaration
|
|
7789
|
+
let peekIdx = i;
|
|
7790
|
+
let isSelector = false;
|
|
7791
|
+
|
|
7792
|
+
// Look for '{' before ';' to determine if it's a selector
|
|
7793
|
+
while (peekIdx < l && text[peekIdx] !== ";") {
|
|
7794
|
+
if (text[peekIdx] === "{") {
|
|
7795
|
+
isSelector = true;
|
|
7796
|
+
break;
|
|
7797
|
+
}
|
|
7798
|
+
peekIdx++;
|
|
7799
|
+
}
|
|
7800
|
+
|
|
7801
|
+
if (!isSelector) {
|
|
7802
|
+
// It's a declaration, skip it (will be handled below)
|
|
7803
|
+
i = peekIdx + 1;
|
|
7804
|
+
continue;
|
|
7805
|
+
}
|
|
7806
|
+
|
|
7807
|
+
// Read selector (up to '{')
|
|
7808
|
+
let selector = "";
|
|
7809
|
+
while (i < l && text[i] !== "{") {
|
|
7810
|
+
selector += text[i];
|
|
7811
|
+
i++;
|
|
7812
|
+
}
|
|
7813
|
+
|
|
7814
|
+
selector = selector.trim();
|
|
7815
|
+
if (!selector || text[i] !== "{") continue;
|
|
7816
|
+
|
|
7817
|
+
i++; // skip '{'
|
|
7818
|
+
|
|
7819
|
+
// Find matching closing brace
|
|
7820
|
+
let blockContent = "";
|
|
7821
|
+
let depth = 1;
|
|
7822
|
+
|
|
7823
|
+
while (i < l && depth > 0) {
|
|
7824
|
+
if (text[i] === "{") depth++;
|
|
7825
|
+
else if (text[i] === "}") depth--;
|
|
7826
|
+
|
|
7827
|
+
if (depth > 0) blockContent += text[i];
|
|
7828
|
+
i++;
|
|
7829
|
+
}
|
|
7830
|
+
|
|
7831
|
+
// Compose full selector
|
|
7832
|
+
let fullSelector = selector;
|
|
7833
|
+
if (parentSelector) {
|
|
7834
|
+
if (selector.includes("&")) {
|
|
7835
|
+
fullSelector = selector.replace(/&/g, parentSelector);
|
|
7836
|
+
} else {
|
|
7837
|
+
fullSelector = parentSelector + " " + selector;
|
|
7838
|
+
}
|
|
7839
|
+
}
|
|
7840
|
+
fullSelector = fullSelector.replace(/\s+/g, " ").trim();
|
|
7841
|
+
|
|
7842
|
+
// Separate declarations from nested rules
|
|
7843
|
+
let { declarations, hasNested } = extractDeclarations(blockContent);
|
|
7844
|
+
|
|
7845
|
+
// Add declarations for this selector (respect !important)
|
|
7846
|
+
if (Object.keys(declarations).length) {
|
|
7847
|
+
if (!rules[fullSelector]) {
|
|
7848
|
+
rules[fullSelector] = declarations;
|
|
7849
|
+
} else {
|
|
7850
|
+
// Merge declarations, preserving !important
|
|
7851
|
+
for (let prop in declarations) {
|
|
7852
|
+
let existingValue = rules[fullSelector][prop];
|
|
7853
|
+
let newValue = declarations[prop];
|
|
7854
|
+
|
|
7855
|
+
// Only override if existing doesn't have !important, or new has !important
|
|
7856
|
+
let existingHasImportant =
|
|
7857
|
+
existingValue && existingValue.includes("!important");
|
|
7858
|
+
let newHasImportant = newValue.includes("!important");
|
|
7859
|
+
|
|
7860
|
+
if (!existingHasImportant || newHasImportant) {
|
|
7861
|
+
rules[fullSelector][prop] = newValue;
|
|
7862
|
+
}
|
|
7863
|
+
}
|
|
7864
|
+
}
|
|
7865
|
+
}
|
|
7866
|
+
|
|
7867
|
+
// If block contains nested rules, parse them recursively
|
|
7868
|
+
if (hasNested) {
|
|
7869
|
+
parseBlock(blockContent, fullSelector);
|
|
7870
|
+
}
|
|
7871
|
+
}
|
|
7872
|
+
|
|
7873
|
+
return rules
|
|
7874
|
+
|
|
7875
|
+
}
|
|
7876
|
+
|
|
7877
|
+
function extractDeclarations(content) {
|
|
7878
|
+
let declarations = {};
|
|
7879
|
+
let i = 0;
|
|
7880
|
+
let l= content.length;
|
|
7881
|
+
let hasNested = false;
|
|
7882
|
+
|
|
7883
|
+
while (i < l) {
|
|
7884
|
+
// Skip whitespace
|
|
7885
|
+
while (i < l && /\s/.test(content[i])) i++;
|
|
7886
|
+
if (i >= l) break;
|
|
7887
|
+
|
|
7888
|
+
// Check if next thing is a nested selector or a declaration
|
|
7889
|
+
let checkIdx = i;
|
|
7890
|
+
let isNested = false;
|
|
7891
|
+
|
|
7892
|
+
// Scan until we hit ':' or '{' or ';'
|
|
7893
|
+
while (checkIdx < l) {
|
|
7894
|
+
if (content[checkIdx] === "{") {
|
|
7895
|
+
isNested = true;
|
|
7896
|
+
break;
|
|
7897
|
+
}
|
|
7898
|
+
if (content[checkIdx] === ":") {
|
|
7899
|
+
// It's a declaration
|
|
7900
|
+
break;
|
|
7901
|
+
}
|
|
7902
|
+
if (content[checkIdx] === ";") {
|
|
7903
|
+
// Empty or malformed
|
|
7904
|
+
break;
|
|
7905
|
+
}
|
|
7906
|
+
checkIdx++;
|
|
7907
|
+
}
|
|
7908
|
+
|
|
7909
|
+
if (isNested) {
|
|
7910
|
+
// Skip nested rule (will be handled by recursive call)
|
|
7911
|
+
hasNested = true;
|
|
7912
|
+
// Skip to closing brace of this nested rule
|
|
7913
|
+
let depth = 0;
|
|
7914
|
+
while (i < l) {
|
|
7915
|
+
if (content[i] === "{") depth++;
|
|
7916
|
+
if (content[i] === "}") depth--;
|
|
7917
|
+
i++;
|
|
7918
|
+
if (depth === 0) break;
|
|
7919
|
+
}
|
|
7920
|
+
} else {
|
|
7921
|
+
// It's a declaration, read until ';' (but respect url() and quotes)
|
|
7922
|
+
let decl = "";
|
|
7923
|
+
let inUrl = false;
|
|
7924
|
+
let inQuotes = false;
|
|
7925
|
+
let quoteChar = "";
|
|
7926
|
+
|
|
7927
|
+
while (i < l) {
|
|
7928
|
+
let char = content[i];
|
|
7929
|
+
let nextChar = content[i + 1];
|
|
7930
|
+
|
|
7931
|
+
// Track if we're inside url()
|
|
7932
|
+
if (
|
|
7933
|
+
char === "u" &&
|
|
7934
|
+
nextChar === "r" &&
|
|
7935
|
+
content.slice(i, i + 4) === "url("
|
|
7936
|
+
) {
|
|
7937
|
+
inUrl = true;
|
|
7938
|
+
}
|
|
7939
|
+
|
|
7940
|
+
// Track quotes
|
|
7941
|
+
if (
|
|
7942
|
+
(char === '"' || char === "'") &&
|
|
7943
|
+
(i === 0 || content[i - 1] !== "\\")
|
|
7944
|
+
) {
|
|
7945
|
+
if (!inQuotes) {
|
|
7946
|
+
inQuotes = true;
|
|
7947
|
+
quoteChar = char;
|
|
7948
|
+
} else if (char === quoteChar) {
|
|
7949
|
+
inQuotes = false;
|
|
7950
|
+
quoteChar = "";
|
|
7951
|
+
}
|
|
7952
|
+
}
|
|
7953
|
+
|
|
7954
|
+
// Check for end of url()
|
|
7955
|
+
if (inUrl && char === ")" && !inQuotes) {
|
|
7956
|
+
inUrl = false;
|
|
7957
|
+
}
|
|
7958
|
+
|
|
7959
|
+
// Only break on semicolon if we're not inside url() or quotes
|
|
7960
|
+
if (char === ";" && !inUrl && !inQuotes) {
|
|
7961
|
+
i++; // skip ';'
|
|
7962
|
+
break;
|
|
7963
|
+
}
|
|
7964
|
+
|
|
7965
|
+
decl += char;
|
|
7966
|
+
i++;
|
|
7967
|
+
}
|
|
7968
|
+
|
|
7969
|
+
decl = decl.trim();
|
|
7970
|
+
if (decl) {
|
|
7971
|
+
let colonIdx = decl.indexOf(":");
|
|
7972
|
+
if (colonIdx > -1) {
|
|
7973
|
+
let prop = decl.substring(0, colonIdx).trim();
|
|
7974
|
+
let value = decl.substring(colonIdx + 1).trim();
|
|
7975
|
+
if (prop && value) {
|
|
7976
|
+
|
|
7977
|
+
declarations[prop] = value;
|
|
7978
|
+
}
|
|
7979
|
+
}
|
|
7980
|
+
}
|
|
7981
|
+
}
|
|
7982
|
+
}
|
|
7983
|
+
|
|
7984
|
+
return { declarations, hasNested };
|
|
7985
|
+
}
|
|
7986
|
+
|
|
7987
|
+
let rules = parseBlock(css);
|
|
7988
|
+
if(parent && removeUnused) rules = removeUnusedSelectors(parent, rules);
|
|
7989
|
+
if(flatten) rules = flattenCssProps(rules);
|
|
7990
|
+
|
|
7991
|
+
// emulate specificity: prioritize ids and important
|
|
7992
|
+
let rulesID = {};
|
|
7993
|
+
let rulesImportant = {};
|
|
7994
|
+
for(let rule in rules){
|
|
7995
|
+
if(rule.startsWith('#')){
|
|
7996
|
+
rulesID[rule] = rules[rule];
|
|
7997
|
+
delete rules[rule];
|
|
7998
|
+
}
|
|
7999
|
+
|
|
8000
|
+
for(let prop in rules[rule]){
|
|
8001
|
+
let val = rules[rule][prop];
|
|
8002
|
+
if(val.includes('!important')){
|
|
8003
|
+
if(!rulesImportant[rule]) rulesImportant[rule]={};
|
|
8004
|
+
rulesImportant[rule][prop] = val;
|
|
8005
|
+
}
|
|
8006
|
+
}
|
|
8007
|
+
}
|
|
8008
|
+
|
|
8009
|
+
rules= {
|
|
8010
|
+
...rules,
|
|
8011
|
+
...rulesID,
|
|
8012
|
+
...rulesImportant
|
|
8013
|
+
};
|
|
8014
|
+
|
|
8015
|
+
return rules;
|
|
8016
|
+
}
|
|
8017
|
+
|
|
8018
|
+
function flattenCssProps(rules) {
|
|
8019
|
+
for (let selector in rules) {
|
|
8020
|
+
let targets = selector.split(/,/).map((sel) => sel.trim());
|
|
8021
|
+
rules[selector];
|
|
8022
|
+
if (targets.length > 1) {
|
|
8023
|
+
targets.forEach((target) => {
|
|
8024
|
+
let props = rules[target];
|
|
8025
|
+
for (let prop in props) {
|
|
8026
|
+
let value = props[prop];
|
|
8027
|
+
if (!value.includes("!important")) {
|
|
8028
|
+
rules[target][prop] = value;
|
|
8029
|
+
}
|
|
8030
|
+
}
|
|
8031
|
+
});
|
|
8032
|
+
delete rules[selector];
|
|
8033
|
+
}
|
|
8034
|
+
}
|
|
8035
|
+
return rules;
|
|
8036
|
+
}
|
|
8037
|
+
|
|
8038
|
+
function removeUnusedSelectors(parent=null, props={}){
|
|
8039
|
+
let selectors = Object.keys(props);
|
|
8040
|
+
selectors.forEach(selector=>{
|
|
8041
|
+
let el = parent.querySelector(selector);
|
|
8042
|
+
// remove
|
|
8043
|
+
if(!el && selector!==':root') {
|
|
8044
|
+
|
|
8045
|
+
delete props[selector];
|
|
8046
|
+
}
|
|
8047
|
+
});
|
|
8048
|
+
return props
|
|
8049
|
+
}
|
|
8050
|
+
|
|
8051
|
+
function setNormalizedTransformsToEl(el, {
|
|
8052
|
+
styleProps = {},
|
|
8053
|
+
} = {}) {
|
|
8054
|
+
let { remove, matrix, transComponents } = styleProps;
|
|
8055
|
+
let name = el.nodeName.toLowerCase();
|
|
8056
|
+
|
|
8057
|
+
if(!matrix) return styleProps;
|
|
8058
|
+
|
|
8059
|
+
let { rotate, scaleX, scaleY, skewX, translateX, translateY } = transComponents;
|
|
8060
|
+
|
|
8061
|
+
// scale attributes instead of transform
|
|
8062
|
+
let hasRot = rotate !== 0 || skewX !== 0;
|
|
8063
|
+
let unProportional = scaleX !== scaleY;
|
|
8064
|
+
let scalableByAtt = ['circle', 'ellipse', 'rect'];
|
|
8065
|
+
|
|
8066
|
+
let needsTrans = (hasRot) || unProportional;
|
|
8067
|
+
needsTrans = true;
|
|
8068
|
+
|
|
8069
|
+
if (!needsTrans && scalableByAtt.includes(name)) {
|
|
8070
|
+
|
|
8071
|
+
if (name === 'circle' || name === 'ellipse') {
|
|
8072
|
+
styleProps.cx[0] = [styleProps.cx[0] * scaleX + translateX];
|
|
8073
|
+
styleProps.cy[0] = [styleProps.cy[0] * scaleX + translateY];
|
|
8074
|
+
|
|
8075
|
+
if (styleProps.r) styleProps.r[0] = [styleProps.r[0] * scaleX];
|
|
8076
|
+
if (styleProps.rx) styleProps.rx[0] = [styleProps.rx[0] * scaleX];
|
|
8077
|
+
if (styleProps.ry) styleProps.ry[0] = [styleProps.ry[0] * scaleX];
|
|
8078
|
+
|
|
8079
|
+
}
|
|
8080
|
+
else if (name === 'rect') {
|
|
8081
|
+
let x = styleProps.x ? styleProps.x[0] + translateX : translateX;
|
|
8082
|
+
let y = styleProps.y ? styleProps.y[0] + translateY : translateY;
|
|
8083
|
+
|
|
8084
|
+
let rx = styleProps.rx ? styleProps.rx[0] * scaleX : 0;
|
|
8085
|
+
let ry = styleProps.ry ? styleProps.ry[0] * scaleY : 0;
|
|
8086
|
+
|
|
8087
|
+
styleProps.x = [x];
|
|
8088
|
+
styleProps.y = [y];
|
|
8089
|
+
|
|
8090
|
+
styleProps.rx = [rx];
|
|
8091
|
+
styleProps.ry = [ry];
|
|
8092
|
+
|
|
8093
|
+
styleProps.width = [styleProps.width[0] * scaleX];
|
|
8094
|
+
styleProps.height = [styleProps.height[0] * scaleX];
|
|
8095
|
+
}
|
|
8096
|
+
|
|
8097
|
+
// remove now obsolete transform properties
|
|
8098
|
+
delete styleProps.matrix;
|
|
8099
|
+
delete styleProps.transformArr;
|
|
8100
|
+
delete styleProps.transComponents;
|
|
8101
|
+
|
|
8102
|
+
// mark transform attribute for removal
|
|
8103
|
+
styleProps.remove.push('transform');
|
|
8104
|
+
|
|
8105
|
+
// scale props like stroke width or dash-array
|
|
8106
|
+
styleProps = scaleProps$1(styleProps, { props: ['stroke-width', 'stroke-dasharray'], scale: scaleX });
|
|
8107
|
+
|
|
8108
|
+
} else {
|
|
8109
|
+
el.setAttribute('transform', transComponents.matrixAtt);
|
|
8110
|
+
|
|
7389
8111
|
}
|
|
7390
8112
|
|
|
8113
|
+
return styleProps
|
|
8114
|
+
|
|
8115
|
+
}
|
|
8116
|
+
|
|
8117
|
+
function scaleProps$1(styleProps = {}, { props = [], scale = 1 } = {}, round = true) {
|
|
8118
|
+
if (scale === 1 || !props.length) return props;
|
|
8119
|
+
|
|
8120
|
+
for (let i = 0; i < props.length; i++) {
|
|
8121
|
+
let prop = props[i];
|
|
8122
|
+
|
|
8123
|
+
if (styleProps[prop] !== undefined) {
|
|
8124
|
+
styleProps[prop] = styleProps[prop].map(val => round ? roundTo(val * scale, 2) : val * scale);
|
|
8125
|
+
}
|
|
8126
|
+
}
|
|
8127
|
+
return styleProps
|
|
8128
|
+
}
|
|
8129
|
+
|
|
8130
|
+
function cleanUpSVG(svgMarkup, {
|
|
8131
|
+
removeHidden = true,
|
|
8132
|
+
|
|
8133
|
+
stylesToAttributes = true,
|
|
8134
|
+
attributesToGroup = false,
|
|
8135
|
+
|
|
8136
|
+
removePrologue = true,
|
|
8137
|
+
removeIds = false,
|
|
8138
|
+
removeClassNames = false,
|
|
8139
|
+
removeDimensions = false,
|
|
8140
|
+
fixHref = false,
|
|
8141
|
+
legacyHref = false,
|
|
8142
|
+
cleanupDefs = true,
|
|
8143
|
+
cleanupClip = true,
|
|
8144
|
+
addViewBox = false,
|
|
8145
|
+
addDimensions = false,
|
|
8146
|
+
minifyRgbColors = false,
|
|
8147
|
+
|
|
8148
|
+
normalizeTransforms = true,
|
|
8149
|
+
autoRoundValues = true,
|
|
8150
|
+
|
|
8151
|
+
unGroup = false,
|
|
8152
|
+
|
|
8153
|
+
mergePaths = false,
|
|
8154
|
+
removeOffCanvas = true,
|
|
8155
|
+
|
|
8156
|
+
cleanupSVGAtts = true,
|
|
8157
|
+
removeNameSpaced = true,
|
|
8158
|
+
removeNameSpacedAtts = true,
|
|
8159
|
+
convertPathLength = false,
|
|
8160
|
+
|
|
8161
|
+
// meta
|
|
8162
|
+
allowMeta = false,
|
|
8163
|
+
allowDataAtts = true,
|
|
8164
|
+
allowAriaAtts = true,
|
|
8165
|
+
|
|
8166
|
+
shapeConvert = false,
|
|
8167
|
+
convertShapes = [],
|
|
8168
|
+
|
|
8169
|
+
// remove elements
|
|
8170
|
+
removeElements = [],
|
|
8171
|
+
|
|
8172
|
+
// remove attributes
|
|
8173
|
+
removeSVGAttributes = [],
|
|
8174
|
+
removeElAttributes = [],
|
|
8175
|
+
|
|
8176
|
+
convertTransforms = false,
|
|
8177
|
+
removeDefaults = true,
|
|
8178
|
+
cleanUpStrokes = true,
|
|
8179
|
+
decimals = -1,
|
|
8180
|
+
excludedEls = [],
|
|
8181
|
+
} = {}) {
|
|
8182
|
+
|
|
8183
|
+
// resolve dependencies
|
|
8184
|
+
if (unGroup || convertTransforms || minifyRgbColors || attributesToGroup)
|
|
8185
|
+
stylesToAttributes = true;
|
|
8186
|
+
|
|
8187
|
+
if(stylesToAttributes) cleanUpStrokes = true;
|
|
8188
|
+
|
|
8189
|
+
// replace namespaced refs
|
|
8190
|
+
if (fixHref) svgMarkup = svgMarkup.replaceAll("xlink:href=", "href=");
|
|
8191
|
+
|
|
8192
|
+
let svg = new DOMParser()
|
|
8193
|
+
.parseFromString(svgMarkup, "text/html")
|
|
8194
|
+
.querySelector("svg");
|
|
8195
|
+
|
|
8196
|
+
let viewBox = getViewBox(svg);
|
|
8197
|
+
let { x, y, width, height } = viewBox;
|
|
8198
|
+
let remove = [];
|
|
8199
|
+
|
|
7391
8200
|
// add viewBox
|
|
7392
8201
|
if (addViewBox) addSvgViewBox(svg, { x, y, width, height });
|
|
7393
8202
|
if (addDimensions) {
|
|
@@ -7401,42 +8210,120 @@ function cleanUpSVG(svgMarkup, {
|
|
|
7401
8210
|
// remove off canvas
|
|
7402
8211
|
if (removeOffCanvas) removeOffCanvasEls(svg, { x, y, width, height });
|
|
7403
8212
|
|
|
7404
|
-
|
|
7405
|
-
|
|
8213
|
+
/**
|
|
8214
|
+
* collect svg styles
|
|
8215
|
+
* and properties
|
|
8216
|
+
*/
|
|
8217
|
+
let propOptions = {
|
|
8218
|
+
width,
|
|
8219
|
+
height,
|
|
8220
|
+
normalizeTransforms,
|
|
8221
|
+
removeDefaults: false,
|
|
8222
|
+
cleanUpStrokes: false,
|
|
8223
|
+
|
|
8224
|
+
allowMeta,
|
|
8225
|
+
allowDataAtts,
|
|
8226
|
+
allowAriaAtts,
|
|
8227
|
+
autoRoundValues,
|
|
8228
|
+
removeIds,
|
|
8229
|
+
removeClassNames,
|
|
8230
|
+
minifyRgbColors,
|
|
8231
|
+
stylesheetProps: {},
|
|
8232
|
+
exclude:[]
|
|
8233
|
+
};
|
|
8234
|
+
|
|
8235
|
+
// root svg inline style properties
|
|
8236
|
+
let stylePropsSVG = parseStylesProperties(svg, propOptions);
|
|
8237
|
+
|
|
8238
|
+
let styleEl = svg.querySelector('style');
|
|
8239
|
+
let cssStylePropsSVG = {};
|
|
8240
|
+
|
|
8241
|
+
if (styleEl) {
|
|
8242
|
+
cssStylePropsSVG = parseSvgCss(styleEl, { parent: svg });
|
|
8243
|
+
|
|
8244
|
+
for (let selector in cssStylePropsSVG) {
|
|
8245
|
+
let els = svg.querySelectorAll(`${selector}`);
|
|
8246
|
+
els.forEach(el => {
|
|
8247
|
+
if (!el['cssRules']) el['cssRules'] = [];
|
|
8248
|
+
el['cssRules'].push(selector);
|
|
8249
|
+
|
|
8250
|
+
// remove class names only used for styling
|
|
8251
|
+
if (stylesToAttributes) {
|
|
8252
|
+
let className = selector.substring(1);
|
|
8253
|
+
el.classList.remove(className);
|
|
8254
|
+
}
|
|
8255
|
+
});
|
|
8256
|
+
}
|
|
8257
|
+
|
|
8258
|
+
// remove style element from element
|
|
8259
|
+
if (stylesToAttributes) {
|
|
8260
|
+
styleEl.remove();
|
|
8261
|
+
}
|
|
8262
|
+
}
|
|
8263
|
+
// remove style element from root SVG
|
|
8264
|
+
if (stylesToAttributes) svg.removeAttribute('style');
|
|
7406
8265
|
|
|
7407
|
-
|
|
8266
|
+
// add stylesheet props
|
|
8267
|
+
propOptions.stylesheetProps = cssStylePropsSVG;
|
|
7408
8268
|
|
|
7409
|
-
//
|
|
8269
|
+
// add svg font size for scaling relative
|
|
8270
|
+
propOptions.fontSize = stylePropsSVG['font-size'] ? stylePropsSVG['font-size'][0] : 16;
|
|
8271
|
+
|
|
8272
|
+
/**
|
|
8273
|
+
* get group styles
|
|
8274
|
+
* especially transformations to
|
|
8275
|
+
* be inherited by children
|
|
8276
|
+
*/
|
|
8277
|
+
let groups = svg.querySelectorAll('g');
|
|
8278
|
+
|
|
8279
|
+
groups.forEach(g => {
|
|
8280
|
+
|
|
8281
|
+
let stylePropsG = parseStylesProperties(g, propOptions);
|
|
8282
|
+
let children = g.querySelectorAll(`${renderedEls.join(', ')}`);
|
|
8283
|
+
|
|
8284
|
+
// store parent styles to child property
|
|
8285
|
+
children.forEach(child => {
|
|
8286
|
+
if (child.parentStyleProps === undefined) {
|
|
8287
|
+
child.parentStyleProps = [];
|
|
8288
|
+
}
|
|
8289
|
+
child.parentStyleProps.push(stylePropsG);
|
|
8290
|
+
});
|
|
8291
|
+
});
|
|
8292
|
+
|
|
8293
|
+
// collect all elements' properties
|
|
7410
8294
|
let svgElProps = [];
|
|
7411
8295
|
let els = svg.querySelectorAll(`${renderedEls.join(', ')}`);
|
|
7412
8296
|
|
|
8297
|
+
/**
|
|
8298
|
+
* loop all geometry elements
|
|
8299
|
+
*/
|
|
7413
8300
|
for (let i = 0; i < els.length; i++) {
|
|
7414
8301
|
let el = els[i];
|
|
7415
8302
|
|
|
7416
8303
|
let name = el.nodeName.toLowerCase();
|
|
7417
8304
|
|
|
7418
|
-
// 1. remove hidden elements
|
|
7419
|
-
let style = el.getAttribute('style') || '';
|
|
7420
|
-
let isHiddenByStyle = style ? style.trim().includes('display:none') : false;
|
|
7421
|
-
let isHidden = (el.getAttribute('display') && el.getAttribute('display') === 'none') || isHiddenByStyle;
|
|
7422
|
-
if (name.includes(':') || removeEls.includes(name) || (removeHidden && isHidden)) {
|
|
7423
|
-
el.remove();
|
|
7424
|
-
continue;
|
|
7425
|
-
}
|
|
7426
|
-
|
|
7427
8305
|
/**
|
|
7428
|
-
* get all style properties
|
|
8306
|
+
* get all element style properties
|
|
7429
8307
|
* convert relative or physical units
|
|
7430
8308
|
* to user units
|
|
7431
8309
|
*/
|
|
7432
8310
|
let styleProps = parseStylesProperties(el, propOptions);
|
|
8311
|
+
let stylePropsFiltered = {};
|
|
8312
|
+
|
|
8313
|
+
// convert pathLength before transforming
|
|
8314
|
+
if (convertPathLength) {
|
|
8315
|
+
styleProps = convertPathLengthAtt(el, { styleProps });
|
|
8316
|
+
remove = [...new Set([...remove, ...styleProps.remove])];
|
|
8317
|
+
}
|
|
7433
8318
|
|
|
7434
8319
|
// get parent styles
|
|
7435
8320
|
let { parentStyleProps = [] } = el;
|
|
7436
8321
|
let inheritedProps = {};
|
|
7437
8322
|
let transFormInherited = [];
|
|
7438
8323
|
|
|
7439
|
-
/**
|
|
8324
|
+
/**
|
|
8325
|
+
* consolidate all properties:
|
|
8326
|
+
* merge with inherited transforms
|
|
7440
8327
|
* and styles from group
|
|
7441
8328
|
*/
|
|
7442
8329
|
parentStyleProps.forEach(props => {
|
|
@@ -7451,9 +8338,14 @@ function cleanUpSVG(svgMarkup, {
|
|
|
7451
8338
|
};
|
|
7452
8339
|
});
|
|
7453
8340
|
|
|
8341
|
+
// merge all transforms
|
|
7454
8342
|
transFormInherited = [...transFormInherited, ...styleProps.transformArr];
|
|
7455
8343
|
styleProps.transformArr = transFormInherited;
|
|
7456
8344
|
|
|
8345
|
+
// don't inherit class from SVG
|
|
8346
|
+
if (stylePropsSVG['class']) delete stylePropsSVG['class'];
|
|
8347
|
+
if (stylePropsSVG['id']) delete stylePropsSVG['id'];
|
|
8348
|
+
|
|
7457
8349
|
// merge with svg props
|
|
7458
8350
|
styleProps = {
|
|
7459
8351
|
...stylePropsSVG,
|
|
@@ -7464,195 +8356,378 @@ function cleanUpSVG(svgMarkup, {
|
|
|
7464
8356
|
// add combined transforms
|
|
7465
8357
|
addTransFormProps(styleProps, transFormInherited);
|
|
7466
8358
|
|
|
7467
|
-
|
|
8359
|
+
remove = [...new Set([...remove, ...styleProps.remove])];
|
|
8360
|
+
|
|
8361
|
+
/**
|
|
8362
|
+
* remove els and attributes
|
|
8363
|
+
*/
|
|
8364
|
+
|
|
8365
|
+
// remove meta
|
|
8366
|
+
if (!allowMeta) removeElements.push('meta', 'metadata', 'desc', 'title');
|
|
8367
|
+
|
|
8368
|
+
if (removeClassNames) {
|
|
8369
|
+
removeSVGAttributes.push('class');
|
|
8370
|
+
removeElAttributes.push('class');
|
|
8371
|
+
}
|
|
8372
|
+
|
|
8373
|
+
if (removeIds) {
|
|
8374
|
+
removeSVGAttributes.push('id');
|
|
8375
|
+
removeElAttributes.push('id');
|
|
8376
|
+
}
|
|
8377
|
+
|
|
8378
|
+
// remove hidden elements
|
|
8379
|
+
removeHiddenSvgEls(svg);
|
|
8380
|
+
|
|
8381
|
+
// remove SVG elements
|
|
8382
|
+
removeSvgEls(svg, { removeElements, removeNameSpaced });
|
|
8383
|
+
|
|
8384
|
+
// remove SVG attributes
|
|
8385
|
+
removeSvgAtts(svg, removeSVGAttributes);
|
|
8386
|
+
|
|
8387
|
+
// remove SVG child element attributes
|
|
8388
|
+
removeSvgChildAtts(svg, removeElAttributes);
|
|
8389
|
+
|
|
8390
|
+
// general cleanup
|
|
8391
|
+
if (cleanupSVGAtts) cleanupSVGAttributes(svg, { removeIds, removeClassNames, removeDimensions, stylesToAttributes, allowMeta, allowAriaAtts, allowDataAtts });
|
|
8392
|
+
|
|
8393
|
+
if (stylesToAttributes) {
|
|
8394
|
+
|
|
8395
|
+
/**
|
|
8396
|
+
* normalize transforms
|
|
8397
|
+
*/
|
|
8398
|
+
if (normalizeTransforms) {
|
|
8399
|
+
styleProps = setNormalizedTransformsToEl(el, { styleProps });
|
|
8400
|
+
|
|
8401
|
+
remove = [...new Set([...remove, ...styleProps.remove])];
|
|
8402
|
+
|
|
8403
|
+
}
|
|
8404
|
+
|
|
8405
|
+
/**
|
|
8406
|
+
* apply consolidated
|
|
8407
|
+
* element attributes
|
|
8408
|
+
* remove non-supported element props
|
|
8409
|
+
*/
|
|
8410
|
+
stylePropsFiltered = filterSvgElProps(name, styleProps,
|
|
8411
|
+
{ removeDefaults: true, cleanUpStrokes, allowMeta, allowAriaAtts, allowDataAtts, removeIds });
|
|
8412
|
+
|
|
8413
|
+
remove = [...new Set([...remove, ...stylePropsFiltered.remove])];
|
|
8414
|
+
|
|
8415
|
+
for (let prop in stylePropsFiltered.propsFiltered) {
|
|
8416
|
+
let values = styleProps[prop];
|
|
8417
|
+
let val = values.length ? values.join(' ') : values[0];
|
|
8418
|
+
el.setAttribute(prop, val);
|
|
8419
|
+
}
|
|
8420
|
+
|
|
8421
|
+
/**
|
|
8422
|
+
* remove obsolete
|
|
8423
|
+
* attributes
|
|
8424
|
+
*/
|
|
8425
|
+
|
|
8426
|
+
for (let i = 0; i < remove.length; i++) {
|
|
8427
|
+
let att = remove[i];
|
|
8428
|
+
|
|
8429
|
+
el.removeAttribute(att);
|
|
8430
|
+
}
|
|
8431
|
+
|
|
8432
|
+
} // endof style processing
|
|
8433
|
+
|
|
8434
|
+
/**
|
|
8435
|
+
* element conversions:
|
|
8436
|
+
* shapes to paths or
|
|
8437
|
+
* paths to shapes
|
|
8438
|
+
*/
|
|
8439
|
+
|
|
8440
|
+
// force shape conversion when transform conversion is enabled
|
|
8441
|
+
if (convertTransforms) {
|
|
8442
|
+
shapeConvert = 'toPaths';
|
|
8443
|
+
convertShapes = ['path', 'rect', 'ellipse', 'circle', 'line', 'polygon', 'polyline'];
|
|
8444
|
+
}
|
|
8445
|
+
|
|
8446
|
+
// convert shapes to paths
|
|
8447
|
+
if (shapeConvert === 'toPaths') {
|
|
8448
|
+
|
|
8449
|
+
let { matrix = null, transComponents = null } = styleProps;
|
|
8450
|
+
|
|
8451
|
+
// scale props like stroke width or dash-array before conversion
|
|
8452
|
+
if (matrix && transComponents) {
|
|
8453
|
+
['stroke-width', 'stroke-dasharray'].forEach(att => {
|
|
8454
|
+
let attVal = el.getAttribute(att);
|
|
8455
|
+
let vals = attVal ? attVal.split(' ').filter(Boolean).map(Number).map(val => val * transComponents.scaleX) : [];
|
|
8456
|
+
if (vals.length) el.setAttribute(att, vals.join(' '));
|
|
8457
|
+
});
|
|
8458
|
+
}
|
|
8459
|
+
|
|
8460
|
+
// convert paths only if a matrix transform is required
|
|
8461
|
+
if (matrix ? geometryEls.includes(name) : shapeEls.includes(name)) {
|
|
8462
|
+
|
|
8463
|
+
let path = shapeElToPath(el, { width, height, convertShapes, matrix });
|
|
8464
|
+
el.replaceWith(path);
|
|
8465
|
+
name = 'path';
|
|
8466
|
+
el = path; // required for node
|
|
8467
|
+
|
|
8468
|
+
}
|
|
8469
|
+
|
|
8470
|
+
}
|
|
8471
|
+
|
|
8472
|
+
/**
|
|
8473
|
+
* Reverse conversion:
|
|
8474
|
+
* paths to shapes
|
|
8475
|
+
*/
|
|
8476
|
+
else if (shapeConvert === 'toShapes') {
|
|
8477
|
+
let paths = svg.querySelectorAll('path');
|
|
8478
|
+
paths.forEach(path => {
|
|
8479
|
+
let shape = pathElToShape(path, { convertShapes });
|
|
8480
|
+
path.replaceWith(shape);
|
|
8481
|
+
path = shape;
|
|
8482
|
+
|
|
8483
|
+
});
|
|
8484
|
+
}
|
|
8485
|
+
|
|
8486
|
+
/**
|
|
8487
|
+
* combine styles
|
|
8488
|
+
* store in node property
|
|
8489
|
+
*/
|
|
8490
|
+
if (mergePaths || attributesToGroup) {
|
|
8491
|
+
|
|
8492
|
+
let options = { allowMeta, allowAriaAtts, removeIds, removeClassNames, allowDataAtts };
|
|
8493
|
+
|
|
8494
|
+
/**
|
|
8495
|
+
* exclude properties for
|
|
8496
|
+
* adjacent path merging
|
|
8497
|
+
* e.g ignore classnames or ids
|
|
8498
|
+
*/
|
|
8499
|
+
if (mergePaths) {
|
|
8500
|
+
options.removeIds = true;
|
|
8501
|
+
options.removeClassNames = true;
|
|
8502
|
+
options.allowAriaAtts = false;
|
|
8503
|
+
options.allowMeta = false;
|
|
8504
|
+
}
|
|
8505
|
+
|
|
8506
|
+
stylePropsFiltered = filterSvgElProps(name, styleProps, options).propsFiltered;
|
|
8507
|
+
|
|
8508
|
+
for (let prop in stylePropsFiltered) {
|
|
8509
|
+
|
|
8510
|
+
if (geometryProps.includes(prop)) continue;
|
|
8511
|
+
|
|
8512
|
+
let values = stylePropsFiltered[prop];
|
|
8513
|
+
let val = values.length ? values.join(' ') : values[0];
|
|
8514
|
+
|
|
8515
|
+
if(prop!=='class' && prop!=='id'){
|
|
8516
|
+
|
|
8517
|
+
let propShort = toShortStr(prop);
|
|
8518
|
+
let valShort = toShortStr(val);
|
|
8519
|
+
let propStr = `${propShort}-${valShort}`;
|
|
8520
|
+
|
|
8521
|
+
// store in node property
|
|
8522
|
+
if (!el.styleSet) el.styleSet = new Set();
|
|
8523
|
+
if(propStr) el.styleSet.add(propStr);
|
|
8524
|
+
}
|
|
8525
|
+
}
|
|
8526
|
+
|
|
8527
|
+
}
|
|
8528
|
+
|
|
8529
|
+
}//endof element loop
|
|
8530
|
+
|
|
8531
|
+
/**
|
|
8532
|
+
* remove group styles
|
|
8533
|
+
* copied to children
|
|
8534
|
+
* or remove nesting
|
|
8535
|
+
*/
|
|
8536
|
+
|
|
8537
|
+
if (unGroup) {
|
|
8538
|
+
ungroupElements(groups);
|
|
8539
|
+
} else {
|
|
7468
8540
|
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
styleProps.remove.push('width');
|
|
7474
|
-
styleProps.remove.push('height');
|
|
8541
|
+
if (stylesToAttributes) {
|
|
8542
|
+
groups.forEach(g => {
|
|
8543
|
+
removeElAtts(g, ['style', 'transform']);
|
|
8544
|
+
});
|
|
7475
8545
|
}
|
|
7476
8546
|
|
|
7477
|
-
|
|
7478
|
-
if (unGroup || convertTransforms || minifyRgbColors ) stylesToAttributes = true;
|
|
8547
|
+
}
|
|
7479
8548
|
|
|
7480
|
-
|
|
8549
|
+
// styles to group
|
|
8550
|
+
if (attributesToGroup) sharedAttributesToGroup(svg);
|
|
7481
8551
|
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
8552
|
+
/**
|
|
8553
|
+
* merge paths with same styles
|
|
8554
|
+
*/
|
|
8555
|
+
if (mergePaths) {
|
|
8556
|
+
mergePathsWithSameProps(svg);
|
|
8557
|
+
}
|
|
7487
8558
|
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
let unProportional = scaleX !== scaleY;
|
|
7491
|
-
let scalableByAtt = ['circle', 'ellipse', 'rect'];
|
|
7492
|
-
let needsTrans = convertTransforms || (name === 'g') || (hasRot) || unProportional;
|
|
8559
|
+
// remove futile clip-paths
|
|
8560
|
+
if (cleanupClip) removeFutileClipPaths(svg, { x, y, width, height });
|
|
7493
8561
|
|
|
7494
|
-
|
|
8562
|
+
// replace href attributes with namespace - required by many older applications
|
|
8563
|
+
if (legacyHref) hrefToXlink(svg);
|
|
7495
8564
|
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
8565
|
+
// remove empty class attributes
|
|
8566
|
+
removeEmptyClassAtts(svg);
|
|
8567
|
+
return { svg, svgElProps }
|
|
7499
8568
|
|
|
7500
|
-
|
|
8569
|
+
}
|
|
7501
8570
|
|
|
7502
|
-
|
|
7503
|
-
|
|
8571
|
+
function removeEmptyClassAtts(svg) {
|
|
8572
|
+
let emptyClassEls = svg.querySelectorAll('[class=""');
|
|
8573
|
+
emptyClassEls.forEach(el => {
|
|
8574
|
+
el.removeAttribute('class');
|
|
8575
|
+
});
|
|
8576
|
+
}
|
|
7504
8577
|
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
8578
|
+
/**
|
|
8579
|
+
* shared styles to group
|
|
8580
|
+
*/
|
|
8581
|
+
function sharedAttributesToGroup(svg) {
|
|
7509
8582
|
|
|
7510
|
-
|
|
7511
|
-
|
|
8583
|
+
let els = svg.querySelectorAll(renderedEls.join(', '));
|
|
8584
|
+
let len = els.length;
|
|
8585
|
+
if(len===1) return;
|
|
7512
8586
|
|
|
7513
|
-
|
|
7514
|
-
|
|
8587
|
+
let el0 = els[0] || null;
|
|
8588
|
+
let stylePrev = el0.styleSet !== undefined ? [...el0.styleSet].join('_') : '';
|
|
7515
8589
|
|
|
7516
|
-
|
|
7517
|
-
|
|
8590
|
+
// all props
|
|
8591
|
+
let allProps = {};
|
|
7518
8592
|
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
}
|
|
8593
|
+
// find attributes shared by all
|
|
8594
|
+
let globalAtts = [];
|
|
7522
8595
|
|
|
7523
|
-
|
|
8596
|
+
if (len) {
|
|
7524
8597
|
|
|
7525
|
-
|
|
7526
|
-
|
|
8598
|
+
let groups = [[el0]];
|
|
8599
|
+
let idx = 0;
|
|
8600
|
+
let elPrev = el0;
|
|
7527
8601
|
|
|
7528
|
-
|
|
7529
|
-
|
|
8602
|
+
for (let i = 0; i < len; i++) {
|
|
8603
|
+
let el = els[i];
|
|
8604
|
+
let atts = getElementAtts(el);
|
|
8605
|
+
for (let att in atts) {
|
|
8606
|
+
let att_str = `${att}_${atts[att]}`;
|
|
7530
8607
|
|
|
8608
|
+
if (!allProps[att_str]) {
|
|
8609
|
+
allProps[att_str] = [];
|
|
8610
|
+
}
|
|
8611
|
+
allProps[att_str].push(el);
|
|
8612
|
+
//
|
|
8613
|
+
if (allProps[att_str].length === len) {
|
|
8614
|
+
globalAtts.push(att);
|
|
7531
8615
|
}
|
|
7532
8616
|
}
|
|
8617
|
+
}
|
|
7533
8618
|
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
8619
|
+
// apply global to parent SVG
|
|
8620
|
+
if (globalAtts.length) {
|
|
8621
|
+
let atts0 = getElementAtts(el0);
|
|
8622
|
+
for (let att in atts0) {
|
|
8623
|
+
if (globalAtts.includes(att) && att !== 'transform') {
|
|
8624
|
+
svg.setAttribute(att, atts0[att]);
|
|
8625
|
+
}
|
|
8626
|
+
}
|
|
8627
|
+
}
|
|
7538
8628
|
|
|
7539
|
-
|
|
7540
|
-
|
|
8629
|
+
// detect groups
|
|
8630
|
+
for (let i = 1; i < len; i++) {
|
|
8631
|
+
let el = els[i];
|
|
8632
|
+
let styleArr = el.styleSet !== undefined ? [...el.styleSet] : [];
|
|
8633
|
+
let style = styleArr.length ? styleArr.join('_') : '';
|
|
7541
8634
|
|
|
7542
|
-
|
|
8635
|
+
// same style add to group
|
|
8636
|
+
if (style === stylePrev && elPrev.nextElementSibling === el) {
|
|
8637
|
+
groups[idx].push(el);
|
|
8638
|
+
}
|
|
8639
|
+
// start new group
|
|
8640
|
+
else {
|
|
8641
|
+
groups.push([el]);
|
|
8642
|
+
idx++;
|
|
8643
|
+
}
|
|
8644
|
+
// update style
|
|
8645
|
+
stylePrev = style;
|
|
8646
|
+
elPrev = el;
|
|
7543
8647
|
|
|
7544
|
-
|
|
7545
|
-
let values = styleProps[prop];
|
|
8648
|
+
}// endof el loop
|
|
7546
8649
|
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
8650
|
+
// create groups
|
|
8651
|
+
for (let i = 0; i < groups.length; i++) {
|
|
8652
|
+
let children = groups[i];
|
|
8653
|
+
let child0 = children[0];
|
|
8654
|
+
let atts = getElementAtts(child0);
|
|
8655
|
+
let groupEl = child0.parentNode.closest('g');
|
|
7550
8656
|
|
|
7551
|
-
//
|
|
7552
|
-
|
|
7553
|
-
let att = remove[i];
|
|
7554
|
-
if (!stylesToAttributes && att === 'style') continue
|
|
8657
|
+
// only 1 child - nothing to group
|
|
8658
|
+
if (children.length === 1) continue
|
|
7555
8659
|
|
|
7556
|
-
|
|
7557
|
-
|
|
8660
|
+
// create new group
|
|
8661
|
+
if (!groupEl || groups.length>1) {
|
|
7558
8662
|
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
*/
|
|
8663
|
+
groupEl = document.createElementNS(svgNs, 'g');
|
|
8664
|
+
child0.parentNode.insertBefore(groupEl, child0);
|
|
8665
|
+
groupEl.append(...children);
|
|
8666
|
+
}
|
|
7564
8667
|
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
8668
|
+
// move attributes to group
|
|
8669
|
+
for (let att in atts) {
|
|
8670
|
+
let val = atts[att];
|
|
7568
8671
|
|
|
8672
|
+
let excludeAtts = ['id', 'class'];
|
|
8673
|
+
if (!geometryProps.includes(att) && !excludeAtts.includes(att)) {
|
|
8674
|
+
if (!globalAtts.includes(att) || att === 'transform') {
|
|
8675
|
+
groupEl.setAttribute(att, val);
|
|
8676
|
+
}
|
|
7569
8677
|
children.forEach(child => {
|
|
7570
|
-
|
|
7571
|
-
});
|
|
7572
|
-
g.remove();
|
|
7573
|
-
});
|
|
7574
|
-
} else {
|
|
7575
|
-
groups.forEach((g, i) => {
|
|
7576
|
-
let atts = [...Object.keys(groupProps[i]), 'style', 'transform'];
|
|
7577
|
-
atts.forEach(att => {
|
|
7578
|
-
g.removeAttribute(att);
|
|
8678
|
+
child.removeAttribute(att);
|
|
7579
8679
|
});
|
|
7580
|
-
}
|
|
7581
|
-
|
|
8680
|
+
}
|
|
7582
8681
|
}
|
|
7583
8682
|
|
|
7584
|
-
} // endof
|
|
8683
|
+
} // endof groups
|
|
7585
8684
|
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
* shapes to paths or
|
|
7589
|
-
* paths to shapes
|
|
7590
|
-
*/
|
|
8685
|
+
}
|
|
8686
|
+
}
|
|
7591
8687
|
|
|
7592
|
-
|
|
7593
|
-
|
|
7594
|
-
|
|
7595
|
-
|
|
7596
|
-
convert_ellipses = true;
|
|
7597
|
-
convert_poly = true;
|
|
7598
|
-
convert_lines = true;
|
|
7599
|
-
}
|
|
8688
|
+
// merge adjacent paths
|
|
8689
|
+
function mergePathsWithSameProps(svg) {
|
|
8690
|
+
let paths = svg.querySelectorAll('path');
|
|
8691
|
+
let len = paths.length;
|
|
7600
8692
|
|
|
7601
|
-
|
|
7602
|
-
|
|
8693
|
+
if (len) {
|
|
8694
|
+
let path0 = paths[0];
|
|
8695
|
+
let d0 = path0.getAttribute('d');
|
|
8696
|
+
let stylePrev = path0.styleSet !== undefined ? [...path0.styleSet].join(' ') : '';
|
|
7603
8697
|
|
|
7604
|
-
|
|
8698
|
+
let remove = [];
|
|
7605
8699
|
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
|
|
7612
|
-
});
|
|
7613
|
-
}
|
|
8700
|
+
for (let i = 1; i < len; i++) {
|
|
8701
|
+
let path = paths[i];
|
|
8702
|
+
let style = path.styleSet !== undefined ? [...path.styleSet].join(' ') : '';
|
|
8703
|
+
let isSibling = path.previousElementSibling === path0;
|
|
8704
|
+
let d = path.getAttribute('d');
|
|
8705
|
+
let isAbs = d.startsWith('M');
|
|
7614
8706
|
|
|
7615
|
-
|
|
7616
|
-
|
|
8707
|
+
if (isSibling && style === stylePrev) {
|
|
8708
|
+
let dAbs = isAbs ? d : parsePathDataString(d).pathData.map(com => `${com.type} ${com.values.join(' ')}`).join(' ');
|
|
7617
8709
|
|
|
7618
|
-
|
|
7619
|
-
|
|
8710
|
+
d0 += dAbs;
|
|
8711
|
+
path0.setAttribute('d', d0);
|
|
7620
8712
|
|
|
7621
|
-
|
|
7622
|
-
el = path;
|
|
8713
|
+
remove.push(path);
|
|
7623
8714
|
|
|
7624
|
-
}
|
|
8715
|
+
} else {
|
|
8716
|
+
path0 = path;
|
|
7625
8717
|
|
|
7626
|
-
|
|
8718
|
+
d0 = isAbs ? d : parsePathDataString(d).pathData.map(com => `${com.type} ${com.values.join(' ')}`).join(' ');
|
|
7627
8719
|
|
|
7628
|
-
|
|
7629
|
-
else if (shapeConvert === 'toShapes') {
|
|
7630
|
-
let paths = svg.querySelectorAll('path');
|
|
7631
|
-
paths.forEach(path => {
|
|
7632
|
-
let shape = pathElToShape(path, { convert_rects, convert_ellipses, convert_poly, convert_lines });
|
|
7633
|
-
path.replaceWith(shape);
|
|
7634
|
-
path = shape;
|
|
7635
|
-
});
|
|
8720
|
+
}
|
|
7636
8721
|
|
|
8722
|
+
// update style
|
|
8723
|
+
stylePrev = style;
|
|
7637
8724
|
}
|
|
7638
8725
|
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
// remove futile clip-paths
|
|
7642
|
-
if (cleanupClip) removeFutileClipPaths(svg, { x, y, width, height });
|
|
7643
|
-
|
|
7644
|
-
// replace href attributes with namespace - required by many older applications
|
|
7645
|
-
if (legacyHref) {
|
|
7646
|
-
svg.setAttribute('xmlns:xlink', "http://www.w3.org/1999/xlink");
|
|
7647
|
-
let hrefs = svg.querySelectorAll('[href]');
|
|
7648
|
-
hrefs.forEach(el => {
|
|
7649
|
-
let href = el.getAttribute('href');
|
|
7650
|
-
el.setAttribute('xlink:href', href);
|
|
7651
|
-
el.removeAttribute('href');
|
|
8726
|
+
remove.forEach(el => {
|
|
8727
|
+
el.remove();
|
|
7652
8728
|
});
|
|
7653
|
-
}
|
|
7654
8729
|
|
|
7655
|
-
|
|
8730
|
+
}
|
|
7656
8731
|
|
|
7657
8732
|
}
|
|
7658
8733
|
|
|
@@ -7713,8 +8788,6 @@ function cleanupSvgDefs(svg, { x = 0, y = 0, width = 0, height = 0, cleanupClip
|
|
|
7713
8788
|
}
|
|
7714
8789
|
});
|
|
7715
8790
|
|
|
7716
|
-
// remove futile clip-paths
|
|
7717
|
-
|
|
7718
8791
|
}
|
|
7719
8792
|
|
|
7720
8793
|
function removeFutileClipPaths(svg, { x = 0, y = 0, width = 0, height = 0 } = {}) {
|
|
@@ -7766,72 +8839,16 @@ function removeFutileClipPaths(svg, { x = 0, y = 0, width = 0, height = 0 } = {}
|
|
|
7766
8839
|
|
|
7767
8840
|
}
|
|
7768
8841
|
|
|
7769
|
-
function
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
let
|
|
7774
|
-
|
|
7775
|
-
if (styleProps[prop] !== undefined) {
|
|
7776
|
-
styleProps[prop] = styleProps[prop].map(val => val * scale);
|
|
7777
|
-
}
|
|
7778
|
-
}
|
|
7779
|
-
return styleProps
|
|
7780
|
-
}
|
|
7781
|
-
|
|
7782
|
-
function removeSVGEls(svg, {
|
|
7783
|
-
remove = ['metadata', 'script'],
|
|
7784
|
-
removeNameSpaced = true,
|
|
7785
|
-
} = {}) {
|
|
7786
|
-
let els = svg.querySelectorAll('*');
|
|
7787
|
-
els.forEach(el => {
|
|
7788
|
-
let nodeName = el.nodeName;
|
|
7789
|
-
if ((removeNameSpaced && nodeName.includes(':')) ||
|
|
7790
|
-
remove.includes(nodeName)
|
|
7791
|
-
) {
|
|
7792
|
-
el.remove();
|
|
7793
|
-
}
|
|
7794
|
-
});
|
|
7795
|
-
}
|
|
8842
|
+
function hrefToXlink(svg) {
|
|
8843
|
+
svg.setAttribute('xmlns:xlink', "http://www.w3.org/1999/xlink");
|
|
8844
|
+
let hrefs = svg.querySelectorAll('[href]');
|
|
8845
|
+
hrefs.forEach(el => {
|
|
8846
|
+
let href = el.getAttribute('href');
|
|
8847
|
+
el.setAttribute('xlink:href', href);
|
|
7796
8848
|
|
|
7797
|
-
function removeExcludedAttribues(el, allowed = ['viewBox', 'xmlns', 'width', 'height', 'id', 'class']) {
|
|
7798
|
-
let atts = [...el.attributes].map((att) => att.name);
|
|
7799
|
-
atts.forEach((att) => {
|
|
7800
|
-
if (!allowed.includes(att)) {
|
|
7801
|
-
el.removeAttribute(att);
|
|
7802
|
-
}
|
|
7803
8849
|
});
|
|
7804
8850
|
}
|
|
7805
8851
|
|
|
7806
|
-
function stringifySVG(svg, {
|
|
7807
|
-
omitNamespace = false,
|
|
7808
|
-
removeComments = true,
|
|
7809
|
-
} = {}) {
|
|
7810
|
-
let markup = new XMLSerializer().serializeToString(svg);
|
|
7811
|
-
|
|
7812
|
-
if (omitNamespace) {
|
|
7813
|
-
markup = markup.replaceAll('xmlns="http://www.w3.org/2000/svg"', '');
|
|
7814
|
-
}
|
|
7815
|
-
|
|
7816
|
-
if (removeComments) {
|
|
7817
|
-
markup = markup
|
|
7818
|
-
.replace(/(<!--.*?-->)|(<!--[\S\s]+?-->)|(<!--[\S\s]*?$)/g, '');
|
|
7819
|
-
}
|
|
7820
|
-
|
|
7821
|
-
markup = markup
|
|
7822
|
-
.replace(/\t/g, "")
|
|
7823
|
-
.replace(/[\n\r|]/g, "\n")
|
|
7824
|
-
.replace(/\n\s*\n/g, '\n')
|
|
7825
|
-
.replace(/ +/g, ' ')
|
|
7826
|
-
|
|
7827
|
-
.replace(/> </g, '><')
|
|
7828
|
-
.trim()
|
|
7829
|
-
// sanitize linebreaks within pathdata
|
|
7830
|
-
.replaceAll(' ', '\n');
|
|
7831
|
-
|
|
7832
|
-
return markup
|
|
7833
|
-
}
|
|
7834
|
-
|
|
7835
8852
|
function refineRoundedCorners(pathData, {
|
|
7836
8853
|
threshold = 0,
|
|
7837
8854
|
tolerance = 1
|
|
@@ -8401,61 +9418,69 @@ function pathDataRevertCubicToQuadratic(pathData, tolerance=1) {
|
|
|
8401
9418
|
return pathData
|
|
8402
9419
|
}
|
|
8403
9420
|
|
|
8404
|
-
function
|
|
9421
|
+
function fixIntersectingCpts(pathData = []) {
|
|
8405
9422
|
|
|
8406
9423
|
let l = pathData.length;
|
|
8407
|
-
|
|
9424
|
+
let p0 = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
9425
|
+
let p = p0;
|
|
9426
|
+
|
|
9427
|
+
for (let i = 1; i < l; i++) {
|
|
8408
9428
|
let com = pathData[i];
|
|
8409
9429
|
let comPrev = pathData[i - 1] || null;
|
|
8410
9430
|
let { type, values } = com;
|
|
8411
9431
|
pathData[i + 1] ? pathData[i + 1] : null;
|
|
8412
|
-
let adjust = false;
|
|
8413
9432
|
|
|
8414
9433
|
if (type === 'C') {
|
|
9434
|
+
let tx = 1.2;
|
|
8415
9435
|
let cp1 = { x: values[0], y: values[1] };
|
|
9436
|
+
p = { x: values[4], y: values[5] };
|
|
8416
9437
|
let cp2 = { x: values[2], y: values[3] };
|
|
8417
|
-
let valuesL = comPrev.values.slice(-2);
|
|
8418
|
-
let p0 = { x: valuesL[0], y: valuesL[1] };
|
|
8419
|
-
let p = { x: values[4], y: values[5] };
|
|
8420
9438
|
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
getDistManhattan(p, cp2);
|
|
8424
|
-
let dist3 = getDistManhattan(cp1, cp2);
|
|
9439
|
+
interpolate(p0, cp1, tx );
|
|
9440
|
+
let cp2_ex = interpolate( p, cp2, tx);
|
|
8425
9441
|
|
|
8426
|
-
|
|
9442
|
+
comPrev.values.slice(-2);
|
|
8427
9443
|
|
|
8428
|
-
|
|
9444
|
+
// extend tangents
|
|
8429
9445
|
|
|
8430
|
-
|
|
8431
|
-
let ptI = ptIV ? checkLineIntersection(p0, cp1, p, cp2, true) : null;
|
|
9446
|
+
let ptI = checkLineIntersection(p0, cp1, p, cp2_ex, true, true);
|
|
8432
9447
|
|
|
8433
|
-
|
|
8434
|
-
|
|
8435
|
-
|
|
8436
|
-
|
|
9448
|
+
|
|
9449
|
+
/*
|
|
9450
|
+
renderPoly(markers, [p0, cp1], 'orange', '0.75')
|
|
9451
|
+
renderPoly(markers, [p, cp2], 'blue', '0.75')
|
|
9452
|
+
renderPoly(markers, [p, cp2_ex], 'magenta', '0.75')
|
|
8437
9453
|
|
|
8438
|
-
|
|
8439
|
-
|
|
8440
|
-
|
|
8441
|
-
|
|
9454
|
+
renderPoint(markers, p0, 'orange', '0.75')
|
|
9455
|
+
renderPoint(markers, cp1, 'magenta', '0.75')
|
|
9456
|
+
renderPoint(markers, cp1_ex, 'blue', '0.5')
|
|
9457
|
+
|
|
9458
|
+
renderPoint(markers, cp2, 'cyan', '0.75')
|
|
9459
|
+
*/
|
|
8442
9460
|
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
|
|
9461
|
+
|
|
9462
|
+
|
|
9463
|
+
/*
|
|
9464
|
+
renderPoint(markers, p0, 'orange')
|
|
9465
|
+
renderPoint(markers, p, 'magenta', '0.5')
|
|
9466
|
+
*/
|
|
9467
|
+
|
|
9468
|
+
if (ptI) {
|
|
9469
|
+
let t = 0.666;
|
|
9470
|
+
cp1 = interpolate(p0, ptI, t);
|
|
9471
|
+
cp2 = interpolate(p, ptI, t);
|
|
9472
|
+
com.values = [cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y];
|
|
8446
9473
|
|
|
8447
|
-
pathData[i].values[0] = cp1.x;
|
|
8448
|
-
pathData[i].values[1] = cp1.y;
|
|
8449
|
-
pathData[i].values[2] = cp2.x;
|
|
8450
|
-
pathData[i].values[3] = cp2.y;
|
|
8451
9474
|
}
|
|
8452
9475
|
|
|
8453
9476
|
}
|
|
8454
9477
|
|
|
9478
|
+
if (values.length) {
|
|
9479
|
+
p0 = p;
|
|
9480
|
+
}
|
|
8455
9481
|
}
|
|
8456
9482
|
|
|
8457
|
-
return pathData
|
|
8458
|
-
|
|
9483
|
+
return pathData;
|
|
8459
9484
|
}
|
|
8460
9485
|
|
|
8461
9486
|
function simplifyPolyRDP(pts, {quality = 0.9, width = 0, height = 0}={}) {
|
|
@@ -8471,7 +9496,7 @@ function simplifyPolyRDP(pts, {quality = 0.9, width = 0, height = 0}={}) {
|
|
|
8471
9496
|
quality = parseFloat(quality);
|
|
8472
9497
|
}
|
|
8473
9498
|
|
|
8474
|
-
if (pts.length < 4
|
|
9499
|
+
if (pts.length < 4 ) return pts;
|
|
8475
9500
|
|
|
8476
9501
|
// convert quality to squaredistance tolerance
|
|
8477
9502
|
let tolerance = quality;
|
|
@@ -8572,7 +9597,8 @@ function simplifyPolyRD(pts, {quality = 0.9, width = 0, height = 0}={}) {
|
|
|
8572
9597
|
}
|
|
8573
9598
|
|
|
8574
9599
|
// nothing to do - exit
|
|
8575
|
-
|
|
9600
|
+
|
|
9601
|
+
if (pts.length < 4 ) return pts;
|
|
8576
9602
|
|
|
8577
9603
|
let p0 = pts[0];
|
|
8578
9604
|
let pt;
|
|
@@ -8584,7 +9610,7 @@ function simplifyPolyRD(pts, {quality = 0.9, width = 0, height = 0}={}) {
|
|
|
8584
9610
|
if (!isAbsolute) {
|
|
8585
9611
|
|
|
8586
9612
|
// quality to tolerance
|
|
8587
|
-
tolerance =
|
|
9613
|
+
tolerance = quality;
|
|
8588
9614
|
|
|
8589
9615
|
/**
|
|
8590
9616
|
* approximate dimensions
|
|
@@ -8631,14 +9657,15 @@ function pathDataFromPoly(pts, closed = true) {
|
|
|
8631
9657
|
|
|
8632
9658
|
// complex polygon
|
|
8633
9659
|
if (Array.isArray(pts[0])) {
|
|
8634
|
-
pts.forEach(sub => {
|
|
9660
|
+
pts.forEach(sub => {
|
|
8635
9661
|
subPath = [
|
|
8636
9662
|
{ type: 'M', values: [sub[0].x, sub[0].y] },
|
|
8637
9663
|
...sub.slice(1).map(pt => { return { type: 'L', values: [pt.x, pt.y] } })
|
|
8638
9664
|
];
|
|
9665
|
+
if (closed) subPath.push({ type: 'Z', values: [] });
|
|
8639
9666
|
pathData.push(...subPath);
|
|
8640
9667
|
});
|
|
8641
|
-
}else {
|
|
9668
|
+
} else {
|
|
8642
9669
|
pathData = [
|
|
8643
9670
|
{ type: 'M', values: [pts[0].x, pts[0].y] },
|
|
8644
9671
|
...pts.slice(1).map(pt => { return { type: 'L', values: [pt.x, pt.y] } })
|
|
@@ -8650,6 +9677,40 @@ function pathDataFromPoly(pts, closed = true) {
|
|
|
8650
9677
|
|
|
8651
9678
|
}
|
|
8652
9679
|
|
|
9680
|
+
function getPolyCentroid(pts) {
|
|
9681
|
+
|
|
9682
|
+
let l = pts.length;
|
|
9683
|
+
let x = 0, y = 0;
|
|
9684
|
+
for (let i = 0; l && i < l; i++) {
|
|
9685
|
+
let pt = pts[i];
|
|
9686
|
+
x += pt.x;
|
|
9687
|
+
y += pt.y;
|
|
9688
|
+
}
|
|
9689
|
+
|
|
9690
|
+
let centroid = {x: x/l, y:y/l};
|
|
9691
|
+
return centroid
|
|
9692
|
+
|
|
9693
|
+
}
|
|
9694
|
+
|
|
9695
|
+
function detectRegularPolygon(pts, centroid={x:0, y:0}) {
|
|
9696
|
+
let rSq = getSquareDistance(pts[0], centroid);
|
|
9697
|
+
let isRegular = true;
|
|
9698
|
+
|
|
9699
|
+
for (let i = 1, l = pts.length; i < l; i++) {
|
|
9700
|
+
let pt1 = pts[i];
|
|
9701
|
+
let dist = getSquareDistance(pt1, centroid);
|
|
9702
|
+
|
|
9703
|
+
let diff = Math.abs(rSq-dist);
|
|
9704
|
+
let diffRel = diff/rSq;
|
|
9705
|
+
|
|
9706
|
+
if (diffRel > 0.05) {
|
|
9707
|
+
return false;
|
|
9708
|
+
}
|
|
9709
|
+
|
|
9710
|
+
}
|
|
9711
|
+
return isRegular;
|
|
9712
|
+
}
|
|
9713
|
+
|
|
8653
9714
|
function analyzePoly(pts, {
|
|
8654
9715
|
x = 0,
|
|
8655
9716
|
y = 0,
|
|
@@ -8916,15 +9977,13 @@ function fitCurveSchneider(pts, {
|
|
|
8916
9977
|
|
|
8917
9978
|
// create pathdata
|
|
8918
9979
|
let pathData = bezierPtsToPathData(beziers);
|
|
8919
|
-
|
|
8920
9980
|
let cp1, cp2;
|
|
8921
9981
|
|
|
8922
9982
|
adjustCpts = false;
|
|
8923
|
-
harmonize = false;
|
|
8924
9983
|
|
|
8925
|
-
|
|
9984
|
+
adjustCpts = true;
|
|
8926
9985
|
|
|
8927
|
-
|
|
9986
|
+
if (adjustCpts) {
|
|
8928
9987
|
|
|
8929
9988
|
let len2 = pathData.length;
|
|
8930
9989
|
let com1 = pathData[0];
|
|
@@ -8954,13 +10013,16 @@ function fitCurveSchneider(pts, {
|
|
|
8954
10013
|
com2.values[3] = cp2.y;
|
|
8955
10014
|
}
|
|
8956
10015
|
|
|
10016
|
+
/*
|
|
8957
10017
|
// harmonize too tight tangents
|
|
8958
|
-
|
|
10018
|
+
let harmonize = true;
|
|
10019
|
+
harmonize = false;
|
|
8959
10020
|
if (harmonize) {
|
|
8960
10021
|
pathData = harmonizeCubicCptsThird([{ type: 'M', values: [pts[0].x, pts[0].y] },
|
|
8961
|
-
...pathData])
|
|
8962
|
-
pathData.shift()
|
|
10022
|
+
...pathData])
|
|
10023
|
+
pathData.shift()
|
|
8963
10024
|
}
|
|
10025
|
+
*/
|
|
8964
10026
|
|
|
8965
10027
|
}
|
|
8966
10028
|
|
|
@@ -9439,29 +10501,127 @@ function adjustTangentAngle(cp, p0, p1, p2) {
|
|
|
9439
10501
|
return cp
|
|
9440
10502
|
}
|
|
9441
10503
|
|
|
9442
|
-
// convert to pathdata
|
|
9443
|
-
function bezierPtsToPathData(beziers = []) {
|
|
9444
|
-
let pathData = [];
|
|
9445
|
-
beziers.forEach(bez => {
|
|
10504
|
+
// convert to pathdata
|
|
10505
|
+
function bezierPtsToPathData(beziers = []) {
|
|
10506
|
+
let pathData = [];
|
|
10507
|
+
beziers.forEach(bez => {
|
|
10508
|
+
|
|
10509
|
+
let type = bez.length === 4 ? 'C' : (bez.length === 3 ? 'Q' : 'L');
|
|
10510
|
+
|
|
10511
|
+
let cp1 = type === 'C' || type === 'Q' ? bez[1] : null;
|
|
10512
|
+
let cp2 = type === 'C' ? bez[2] : null;
|
|
10513
|
+
let p = bez[bez.length - 1];
|
|
10514
|
+
|
|
10515
|
+
let values = type === 'C' ?
|
|
10516
|
+
[cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y] :
|
|
10517
|
+
(type === 'Q' ?
|
|
10518
|
+
[cp1.x, cp1.y, p.x, p.y] :
|
|
10519
|
+
[p.x, p.y]
|
|
10520
|
+
);
|
|
10521
|
+
|
|
10522
|
+
let com = { type, values };
|
|
10523
|
+
pathData.push(com);
|
|
10524
|
+
});
|
|
10525
|
+
|
|
10526
|
+
return pathData
|
|
10527
|
+
}
|
|
10528
|
+
|
|
10529
|
+
function simplifyRC(pts, quality = 1, shiftStart = true) {
|
|
10530
|
+
|
|
10531
|
+
if (pts.length < 4) return pts;
|
|
10532
|
+
|
|
10533
|
+
let l = pts.length;
|
|
10534
|
+
|
|
10535
|
+
// starting point
|
|
10536
|
+
let M = pts[0];
|
|
10537
|
+
|
|
10538
|
+
// last point
|
|
10539
|
+
let Z = pts[l - 1];
|
|
10540
|
+
|
|
10541
|
+
// remove unnecessary closing point
|
|
10542
|
+
if (M.x === Z.x && M.y === Z.y) {
|
|
10543
|
+
pts.pop();
|
|
10544
|
+
l--;
|
|
10545
|
+
Z = pts[l - 1];
|
|
10546
|
+
}
|
|
10547
|
+
|
|
10548
|
+
// init new point array
|
|
10549
|
+
let ptsSmp = [M];
|
|
10550
|
+
let pt0 = M;
|
|
10551
|
+
let pt1, pt2;
|
|
10552
|
+
|
|
10553
|
+
// loop through vertices by triangles
|
|
10554
|
+
for (let i = 2; i < l; i++) {
|
|
10555
|
+
pt1 = pts[i - 1];
|
|
10556
|
+
pt2 = pts[i];
|
|
10557
|
+
let isLast = i === l - 1;
|
|
10558
|
+
|
|
10559
|
+
/**
|
|
10560
|
+
* 1. Skip zero-length segments
|
|
10561
|
+
*/
|
|
10562
|
+
if ((pt1.x === pt0.x && pt1.y === pt0.y) || (pt1.x === pt2.x && pt1.y === pt2.y)) {
|
|
10563
|
+
continue;
|
|
10564
|
+
}
|
|
10565
|
+
|
|
10566
|
+
/**
|
|
10567
|
+
* 2. Check for perfectly flat
|
|
10568
|
+
* vertical/horizontal segments
|
|
10569
|
+
*/
|
|
10570
|
+
let isVertical = (pt0.x === pt1.x);
|
|
10571
|
+
let isHorizontal = (pt0.y === pt1.y);
|
|
10572
|
+
|
|
10573
|
+
if (isVertical || isHorizontal) {
|
|
10574
|
+
|
|
10575
|
+
let isVertical2 = (pt1.x === pt2.x);
|
|
10576
|
+
let isHorizontal2 = (pt1.y === pt2.y);
|
|
10577
|
+
|
|
10578
|
+
if (((isVertical && isVertical2) || (isHorizontal && isHorizontal2))) {
|
|
10579
|
+
|
|
10580
|
+
// perfectly flat segment - skip
|
|
10581
|
+
if (!isLast) continue;
|
|
10582
|
+
|
|
10583
|
+
// flat but last – add last and skip colinearity check
|
|
10584
|
+
if (isLast && M.x !== pt2.x && M.y !== pt2.y) {
|
|
10585
|
+
|
|
10586
|
+
ptsSmp.push(pt2);
|
|
10587
|
+
continue
|
|
10588
|
+
}
|
|
10589
|
+
|
|
10590
|
+
}
|
|
10591
|
+
}
|
|
9446
10592
|
|
|
9447
|
-
|
|
10593
|
+
// check area
|
|
10594
|
+
let area = getPolygonArea([pt0, pt1, pt2], true);
|
|
10595
|
+
let thresh = getSquareDistance(pt0, pt2) * 0.005;
|
|
9448
10596
|
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
let p = bez[bez.length - 1];
|
|
10597
|
+
// flat
|
|
10598
|
+
if ( area <= thresh && i<l-1) {
|
|
9452
10599
|
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
[cp1.x, cp1.y, p.x, p.y] :
|
|
9457
|
-
[p.x, p.y]
|
|
9458
|
-
);
|
|
10600
|
+
pt0 = pt1;
|
|
10601
|
+
continue
|
|
10602
|
+
}
|
|
9459
10603
|
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
});
|
|
10604
|
+
// no simplification - add mid pt
|
|
10605
|
+
ptsSmp.push(pt1);
|
|
9463
10606
|
|
|
9464
|
-
|
|
10607
|
+
// add last point if not first
|
|
10608
|
+
if (isLast && M.x !== pt2.x && M.y !== pt2.y) {
|
|
10609
|
+
// console.log('add last', M, pt2);
|
|
10610
|
+
ptsSmp.push(pt2);
|
|
10611
|
+
}
|
|
10612
|
+
|
|
10613
|
+
// update previous point
|
|
10614
|
+
pt0 = pt1;
|
|
10615
|
+
|
|
10616
|
+
}
|
|
10617
|
+
|
|
10618
|
+
// 1st and last are colinear
|
|
10619
|
+
let area0 = getPolygonArea([ptsSmp[1], M, ptsSmp[ptsSmp.length-1]], true);
|
|
10620
|
+
let thresh0 = getSquareDistance (ptsSmp[1], ptsSmp[ptsSmp.length-1]) * 0.005;
|
|
10621
|
+
// remove first point
|
|
10622
|
+
if(area0 < thresh0) ptsSmp.shift();
|
|
10623
|
+
|
|
10624
|
+
return ptsSmp;
|
|
9465
10625
|
}
|
|
9466
10626
|
|
|
9467
10627
|
function simplifyPolygonToPathData(pts, {
|
|
@@ -9480,29 +10640,80 @@ function simplifyPolygonToPathData(pts, {
|
|
|
9480
10640
|
simplifyRDP = 1,
|
|
9481
10641
|
} = {}) {
|
|
9482
10642
|
|
|
9483
|
-
|
|
9484
|
-
|
|
9485
|
-
|
|
9486
|
-
|
|
9487
|
-
width,
|
|
9488
|
-
height,
|
|
9489
|
-
quality: denoise,
|
|
9490
|
-
manhattan,
|
|
9491
|
-
absolute
|
|
9492
|
-
})
|
|
9493
|
-
}
|
|
9494
|
-
*/
|
|
10643
|
+
let polyPath = [];
|
|
10644
|
+
let l = pts.length;
|
|
10645
|
+
let M = pts[0];
|
|
10646
|
+
let Z = pts[l - 1];
|
|
9495
10647
|
|
|
9496
|
-
|
|
9497
|
-
|
|
9498
|
-
|
|
9499
|
-
|
|
10648
|
+
// triangle
|
|
10649
|
+
if (pts.length === 3) {
|
|
10650
|
+
|
|
10651
|
+
let pM1 = interpolate(M, pts[1], 0.5);
|
|
10652
|
+
let pM2 = interpolate(pts[1], Z, 0.5);
|
|
10653
|
+
let pM3 = interpolate(Z, pts[0], 0.5);
|
|
10654
|
+
|
|
10655
|
+
/*
|
|
10656
|
+
console.log('triangle');
|
|
10657
|
+
renderPoint(markers, M)
|
|
10658
|
+
renderPoint(markers, pM1)
|
|
10659
|
+
renderPoint(markers, pM2)
|
|
10660
|
+
renderPoint(markers, pM3)
|
|
10661
|
+
*/
|
|
10662
|
+
|
|
10663
|
+
if (closed) {
|
|
10664
|
+
let t = 0.6666;
|
|
10665
|
+
let cp1_1 = interpolate(pM1, pts[1], t);
|
|
10666
|
+
let cp2_1 = interpolate(pM2, pts[1], t);
|
|
10667
|
+
let cp1_2 = interpolate(pM2, Z, t);
|
|
10668
|
+
let cp2_2 = interpolate(pM3, Z, t);
|
|
10669
|
+
let cp1_3 = interpolate(pM3, M, t);
|
|
10670
|
+
let cp2_3 = interpolate(pM1, M, t);
|
|
10671
|
+
|
|
10672
|
+
polyPath = [
|
|
10673
|
+
{ type: 'M', values: [pM1.x, pM1.y] },
|
|
10674
|
+
{ type: 'C', values: [cp1_1.x, cp1_1.y, cp2_1.x, cp2_1.y, pM2.x, pM2.y] },
|
|
10675
|
+
{ type: 'C', values: [cp1_2.x, cp1_2.y, cp2_2.x, cp2_2.y, pM3.x, pM3.y] },
|
|
10676
|
+
{ type: 'C', values: [cp1_3.x, cp1_3.y, cp2_3.x, cp2_3.y, pM1.x, pM1.y] },
|
|
10677
|
+
{ type: 'Z', values: [] },
|
|
10678
|
+
];
|
|
10679
|
+
|
|
10680
|
+
} else {
|
|
10681
|
+
polyPath = [
|
|
10682
|
+
|
|
10683
|
+
{ type: 'M', values: [M.x, M.y] },
|
|
10684
|
+
{ type: 'C', values: [pts[1].x, pts[1].y, pts[1].x, pts[1].y, Z.x, Z.y] },
|
|
10685
|
+
];
|
|
10686
|
+
}
|
|
10687
|
+
return polyPath;
|
|
9500
10688
|
}
|
|
9501
10689
|
|
|
9502
|
-
|
|
9503
|
-
|
|
10690
|
+
// remove colinear
|
|
10691
|
+
|
|
10692
|
+
/**
|
|
10693
|
+
* detect regular polygon
|
|
10694
|
+
* curved path is a circle
|
|
10695
|
+
*/
|
|
10696
|
+
let centroid = getPolyCentroid(simplifyRC(pts));
|
|
10697
|
+
let isRegularPolygon = detectRegularPolygon(pts, centroid);
|
|
10698
|
+
|
|
10699
|
+
if (isRegularPolygon) {
|
|
10700
|
+
|
|
10701
|
+
let ptAd = rotatePoint(pts[0], centroid.x, centroid.y, Math.PI);
|
|
10702
|
+
let sweep = getPolygonArea(pts) > 0 ? 1 : 0;
|
|
10703
|
+
|
|
10704
|
+
polyPath = [
|
|
10705
|
+
{ type: 'M', values: [pts[0].x, pts[0].y] },
|
|
10706
|
+
{ type: 'A', values: [1, 1, 0, 0, sweep, ptAd.x, ptAd.y] },
|
|
10707
|
+
{ type: 'A', values: [1, 1, 0, 0, sweep, pts[0].x, pts[0].y] }
|
|
10708
|
+
];
|
|
10709
|
+
|
|
10710
|
+
if (closed) {
|
|
10711
|
+
polyPath.push({ type: 'Z', values: [] });
|
|
10712
|
+
}
|
|
10713
|
+
return polyPath;
|
|
9504
10714
|
}
|
|
9505
|
-
|
|
10715
|
+
|
|
10716
|
+
// remove colinear
|
|
9506
10717
|
|
|
9507
10718
|
// get topology of poly
|
|
9508
10719
|
let polyAnalyzed = !keepExtremes && !keepCorners ? pts : analyzePoly(pts, {
|
|
@@ -9516,12 +10727,15 @@ function simplifyPolygonToPathData(pts, {
|
|
|
9516
10727
|
// Schneider curve fit
|
|
9517
10728
|
let threshold = width && height ? (width + height) / 2 * 0.004 * tolerance : 2.5;
|
|
9518
10729
|
|
|
9519
|
-
|
|
10730
|
+
polyPath = simplifyPolyChunks(chunks, {
|
|
9520
10731
|
closed,
|
|
9521
10732
|
tolerance: threshold,
|
|
9522
|
-
keepCorners
|
|
10733
|
+
keepCorners,
|
|
10734
|
+
keepExtremes: true,
|
|
9523
10735
|
});
|
|
9524
10736
|
|
|
10737
|
+
polyPath = fixIntersectingCpts(polyPath);
|
|
10738
|
+
|
|
9525
10739
|
return polyPath;
|
|
9526
10740
|
}
|
|
9527
10741
|
|
|
@@ -9579,7 +10793,7 @@ function simplifyPolyChunks(chunks = [], {
|
|
|
9579
10793
|
* creates precise polygon approximation from pathdata
|
|
9580
10794
|
* converts arc to cubis
|
|
9581
10795
|
*/
|
|
9582
|
-
function
|
|
10796
|
+
function pathDataToPolygonOpt(pathData, {
|
|
9583
10797
|
precisionPoly = 1,
|
|
9584
10798
|
autoAccuracy=false,
|
|
9585
10799
|
polyFormat='points',
|
|
@@ -9654,11 +10868,11 @@ simplifyRDP=1,
|
|
|
9654
10868
|
|
|
9655
10869
|
// simplify polygon
|
|
9656
10870
|
if(simplifyRD>0){
|
|
9657
|
-
pts2 = simplifyPolyRD(pts2, {quality:simplifyRD
|
|
10871
|
+
pts2 = simplifyPolyRD(pts2, {quality:simplifyRD});
|
|
9658
10872
|
}
|
|
9659
10873
|
|
|
9660
10874
|
if(simplifyRDP>0){
|
|
9661
|
-
pts2 = simplifyPolyRDP(pts2, {quality:simplifyRDP
|
|
10875
|
+
pts2 = simplifyPolyRDP(pts2, {quality:simplifyRDP});
|
|
9662
10876
|
}
|
|
9663
10877
|
|
|
9664
10878
|
pathDataPoly = pathDataFromPoly(pts2);
|
|
@@ -9666,7 +10880,6 @@ simplifyRDP=1,
|
|
|
9666
10880
|
|
|
9667
10881
|
if(autoAccuracy){
|
|
9668
10882
|
decimals = detectAccuracyPoly(pts);
|
|
9669
|
-
|
|
9670
10883
|
}
|
|
9671
10884
|
|
|
9672
10885
|
let poly = decimals>-1 ? pts2.map(pt => { return { x: roundTo(pt.x, decimals), y: roundTo(pt.y, decimals) } }) : pts2.map(pt => { return { x: pt.x, y: pt.y } });
|
|
@@ -9812,9 +11025,11 @@ function fixPathDataDirections(pathDataArr = [], toClockwise = false) {
|
|
|
9812
11025
|
if (!includedIn.length && cw && !toClockwise
|
|
9813
11026
|
|| !includedIn.length && !cw && toClockwise
|
|
9814
11027
|
) {
|
|
11028
|
+
|
|
9815
11029
|
pathDataArr[i].pathData = reversePathData(pathDataArr[i].pathData);
|
|
9816
11030
|
polys[i].cw = polys[i].cw ? false : true;
|
|
9817
11031
|
cw = polys[i].cw;
|
|
11032
|
+
|
|
9818
11033
|
}
|
|
9819
11034
|
|
|
9820
11035
|
// reverse inner sub paths
|
|
@@ -9823,6 +11038,7 @@ function fixPathDataDirections(pathDataArr = [], toClockwise = false) {
|
|
|
9823
11038
|
let child = polys[ind];
|
|
9824
11039
|
|
|
9825
11040
|
if (child.cw === cw) {
|
|
11041
|
+
|
|
9826
11042
|
pathDataArr[ind].pathData = reversePathData(pathDataArr[ind].pathData);
|
|
9827
11043
|
polys[ind].cw = polys[ind].cw ? false : true;
|
|
9828
11044
|
}
|
|
@@ -9833,116 +11049,404 @@ function fixPathDataDirections(pathDataArr = [], toClockwise = false) {
|
|
|
9833
11049
|
|
|
9834
11050
|
}
|
|
9835
11051
|
|
|
9836
|
-
|
|
11052
|
+
let settingsDefaults = {
|
|
11053
|
+
|
|
11054
|
+
// SVG elements
|
|
11055
|
+
removeComments: true,
|
|
11056
|
+
removeOffCanvas: false,
|
|
11057
|
+
|
|
11058
|
+
// attributes
|
|
11059
|
+
removeDimensions: false,
|
|
11060
|
+
removeIds: false,
|
|
11061
|
+
removeClassNames: false,
|
|
11062
|
+
omitNamespace: false,
|
|
11063
|
+
cleanUpStrokes: true,
|
|
11064
|
+
addViewBox: true,
|
|
11065
|
+
addDimensions: false,
|
|
11066
|
+
removePrologue: true,
|
|
11067
|
+
removeHidden: true,
|
|
11068
|
+
removeUnused: true,
|
|
11069
|
+
cleanupDefs: true,
|
|
11070
|
+
cleanupClip: true,
|
|
11071
|
+
cleanupSVGAtts: true,
|
|
11072
|
+
removeNameSpaced: true,
|
|
11073
|
+
removeNameSpacedAtts: true,
|
|
11074
|
+
attributesToGroup: false,
|
|
11075
|
+
minifyRgbColors: true,
|
|
11076
|
+
stylesToAttributes: false,
|
|
11077
|
+
fixHref: false,
|
|
11078
|
+
legacyHref: false,
|
|
11079
|
+
allowMeta: false,
|
|
11080
|
+
allowDataAtts: true,
|
|
11081
|
+
allowAriaAtts: true,
|
|
11082
|
+
|
|
11083
|
+
convertPathLength: false,
|
|
11084
|
+
|
|
11085
|
+
// custom removal
|
|
11086
|
+
removeElements: [],
|
|
11087
|
+
removeSVGAttributes: [],
|
|
11088
|
+
removeElAttributes: [],
|
|
11089
|
+
|
|
11090
|
+
// merging/splitting
|
|
11091
|
+
unGroup: false,
|
|
11092
|
+
mergePaths: false,
|
|
11093
|
+
splitCompound: false,
|
|
9837
11094
|
|
|
9838
|
-
//
|
|
9839
|
-
|
|
11095
|
+
// shape conversions
|
|
11096
|
+
shapesToPaths: false,
|
|
11097
|
+
shapeConvert: 0,
|
|
11098
|
+
convertShapes: ['rect', 'ellipse', 'circle', 'line', 'polygon', 'polyline'],
|
|
11099
|
+
|
|
11100
|
+
// simplify
|
|
11101
|
+
keepSmaller: true,
|
|
11102
|
+
simplifyBezier: true,
|
|
11103
|
+
optimizeOrder: true,
|
|
11104
|
+
autoClose: false,
|
|
11105
|
+
removeZeroLength: true,
|
|
11106
|
+
refineClosing: true,
|
|
11107
|
+
removeColinear: true,
|
|
11108
|
+
flatBezierToLinetos: true,
|
|
11109
|
+
revertToQuadratics: true,
|
|
11110
|
+
refineExtremes: false,
|
|
11111
|
+
simplifyCorners: false,
|
|
11112
|
+
keepExtremes: true,
|
|
11113
|
+
keepCorners: true,
|
|
11114
|
+
keepInflections: false,
|
|
11115
|
+
addExtremes: false,
|
|
11116
|
+
|
|
11117
|
+
// draw direction
|
|
11118
|
+
fixDirections: false,
|
|
11119
|
+
reversePath: false,
|
|
11120
|
+
|
|
11121
|
+
// pathdata
|
|
11122
|
+
toAbsolute: false,
|
|
11123
|
+
toRelative: true,
|
|
11124
|
+
toMixed: false,
|
|
11125
|
+
toShorthands: true,
|
|
11126
|
+
toLonghands: false,
|
|
11127
|
+
quadraticToCubic: true,
|
|
11128
|
+
arcToCubic: false,
|
|
11129
|
+
cubicToArc: false,
|
|
11130
|
+
lineToCubic: false,
|
|
11131
|
+
|
|
11132
|
+
// minification
|
|
11133
|
+
decimals: 3,
|
|
11134
|
+
autoAccuracy: true,
|
|
11135
|
+
minifyD: 0,
|
|
11136
|
+
tolerance: 1,
|
|
11137
|
+
|
|
11138
|
+
// polygon
|
|
11139
|
+
toPolygon: false,
|
|
11140
|
+
smoothPoly: false,
|
|
11141
|
+
polyFormat: 'object',
|
|
11142
|
+
precisionPoly: 1,
|
|
11143
|
+
simplifyRD: 0,
|
|
11144
|
+
simplifyRDP: 0,
|
|
11145
|
+
harmonizeCpts: false,
|
|
11146
|
+
removeOrphanSubpaths: false,
|
|
11147
|
+
simplifyRound: false,
|
|
11148
|
+
|
|
11149
|
+
scale: 1,
|
|
11150
|
+
scaleTo: 0,
|
|
11151
|
+
crop: false,
|
|
11152
|
+
alignToOrigin: false,
|
|
9840
11153
|
|
|
9841
|
-
|
|
11154
|
+
// flatten transforms
|
|
11155
|
+
convertTransforms: false,
|
|
11156
|
+
|
|
11157
|
+
};
|
|
11158
|
+
|
|
11159
|
+
const settingsNull = {};
|
|
11160
|
+
|
|
11161
|
+
for (let prop in settingsDefaults) {
|
|
11162
|
+
let val = settingsDefaults[prop];
|
|
11163
|
+
let isBoolean = val === false || val === true;
|
|
11164
|
+
let isNum = !isNaN(val);
|
|
11165
|
+
let isArray = Array.isArray(val);
|
|
11166
|
+
|
|
11167
|
+
if (isBoolean) val = false;
|
|
11168
|
+
else if (!isArray && isNum) val = val===1 ? 1 : (prop==='decimals'? -1 : 0);
|
|
11169
|
+
else if (isArray) val = [];
|
|
11170
|
+
settingsNull[prop] = val;
|
|
11171
|
+
}
|
|
11172
|
+
|
|
11173
|
+
const presetSettings = {
|
|
11174
|
+
default: settingsDefaults,
|
|
11175
|
+
|
|
11176
|
+
education: {
|
|
11177
|
+
...settingsDefaults,
|
|
11178
|
+
...{
|
|
11179
|
+
keepSmaller: false,
|
|
11180
|
+
toRelative: false,
|
|
11181
|
+
toMixed: false,
|
|
11182
|
+
toShorthands: false,
|
|
11183
|
+
fixHref: true,
|
|
11184
|
+
legacyHref: false,
|
|
11185
|
+
addViewBox: true,
|
|
11186
|
+
addDimensions: true,
|
|
11187
|
+
removeComments: false,
|
|
11188
|
+
decimals: 3,
|
|
11189
|
+
minifyD: 2
|
|
11190
|
+
}
|
|
11191
|
+
},
|
|
11192
|
+
|
|
11193
|
+
null: settingsNull,
|
|
11194
|
+
|
|
11195
|
+
editor: {
|
|
11196
|
+
...settingsDefaults,
|
|
11197
|
+
...{
|
|
11198
|
+
keepSmaller: false,
|
|
11199
|
+
toRelative: true,
|
|
11200
|
+
toMixed: true,
|
|
11201
|
+
toShorthands: true,
|
|
11202
|
+
|
|
11203
|
+
legacyHref: true,
|
|
11204
|
+
addViewBox: true,
|
|
11205
|
+
addDimensions: true,
|
|
11206
|
+
removeComments: true,
|
|
11207
|
+
autoAccuracy: true,
|
|
11208
|
+
|
|
11209
|
+
minifyD: 0.5
|
|
11210
|
+
}
|
|
11211
|
+
},
|
|
11212
|
+
|
|
11213
|
+
noSimplification: {
|
|
11214
|
+
...settingsDefaults,
|
|
11215
|
+
...{
|
|
11216
|
+
simplifyBezier: false,
|
|
11217
|
+
quadraticToCubic: false,
|
|
11218
|
+
toRelative: true,
|
|
11219
|
+
toShorthands: true,
|
|
11220
|
+
fixHref: true,
|
|
11221
|
+
optimizeOrder: false,
|
|
11222
|
+
removeZeroLength: false,
|
|
11223
|
+
refineExtremes: false,
|
|
11224
|
+
refineClosing: false,
|
|
11225
|
+
removeColinear: false,
|
|
11226
|
+
flatBezierToLinetos: false,
|
|
11227
|
+
|
|
11228
|
+
addDimensions: false,
|
|
11229
|
+
removeComments: true,
|
|
11230
|
+
minifyD: 0
|
|
11231
|
+
}
|
|
11232
|
+
|
|
11233
|
+
},
|
|
11234
|
+
path: {
|
|
11235
|
+
...settingsDefaults,
|
|
11236
|
+
...{
|
|
11237
|
+
shapeConvert: 'toPaths',
|
|
11238
|
+
convertShapes: ['rect', 'ellipse', 'circle', 'line', 'polygon', 'polyline'],
|
|
11239
|
+
addViewBox: true,
|
|
11240
|
+
minifyD: 0.5
|
|
11241
|
+
}
|
|
11242
|
+
},
|
|
11243
|
+
|
|
11244
|
+
poly: {
|
|
11245
|
+
...settingsDefaults,
|
|
11246
|
+
...{
|
|
11247
|
+
toPolygon: true,
|
|
11248
|
+
}
|
|
11249
|
+
},
|
|
11250
|
+
|
|
11251
|
+
curvefit: {
|
|
11252
|
+
...settingsDefaults,
|
|
11253
|
+
...{
|
|
11254
|
+
smoothPoly: true,
|
|
11255
|
+
}
|
|
11256
|
+
},
|
|
11257
|
+
|
|
11258
|
+
detransform: {
|
|
11259
|
+
...settingsDefaults,
|
|
11260
|
+
...{
|
|
11261
|
+
convertTransforms: true,
|
|
11262
|
+
addViewBox: true,
|
|
11263
|
+
minifyD: 0.5
|
|
11264
|
+
}
|
|
11265
|
+
},
|
|
11266
|
+
|
|
11267
|
+
high: {
|
|
11268
|
+
...settingsDefaults,
|
|
11269
|
+
...{
|
|
11270
|
+
tolerance: 1.2,
|
|
11271
|
+
toMixed: true,
|
|
11272
|
+
refineExtremes: true,
|
|
11273
|
+
simplifyCorners: true,
|
|
11274
|
+
simplifyRound: true,
|
|
11275
|
+
removeClassNames: true,
|
|
11276
|
+
cubicToArc: true,
|
|
11277
|
+
removeComments: true,
|
|
11278
|
+
removeHidden: true,
|
|
11279
|
+
removeOffCanvas: true,
|
|
11280
|
+
addViewBox: true,
|
|
11281
|
+
removeDimensions: true,
|
|
11282
|
+
minifyD: 0
|
|
11283
|
+
}
|
|
11284
|
+
}
|
|
11285
|
+
|
|
11286
|
+
};
|
|
11287
|
+
|
|
11288
|
+
function splitCompundGroups(pathDataPlusArr = [], {
|
|
9842
11289
|
toRelative = true,
|
|
9843
11290
|
toShorthands = true,
|
|
9844
|
-
|
|
11291
|
+
minifyD = 0,
|
|
11292
|
+
decimals = 3,
|
|
11293
|
+
addDimensions = false
|
|
11294
|
+
} = {}) {
|
|
11295
|
+
pathDataPlusArr = JSON.parse(JSON.stringify(pathDataPlusArr));
|
|
11296
|
+
let len = pathDataPlusArr.length;
|
|
9845
11297
|
|
|
9846
|
-
|
|
9847
|
-
|
|
11298
|
+
let xArr = [];
|
|
11299
|
+
let yArr = [];
|
|
9848
11300
|
|
|
9849
|
-
//
|
|
9850
|
-
|
|
9851
|
-
|
|
9852
|
-
|
|
9853
|
-
simplifyBezier = true,
|
|
9854
|
-
optimizeOrder = true,
|
|
9855
|
-
autoClose = false,
|
|
9856
|
-
removeZeroLength = true,
|
|
9857
|
-
refineClosing = true,
|
|
9858
|
-
removeColinear = true,
|
|
9859
|
-
flatBezierToLinetos = true,
|
|
9860
|
-
revertToQuadratics = true,
|
|
9861
|
-
|
|
9862
|
-
refineExtremes = true,
|
|
9863
|
-
simplifyCorners = false,
|
|
9864
|
-
removeDimensions = false,
|
|
9865
|
-
removeIds = false,
|
|
9866
|
-
removeClassNames = false,
|
|
9867
|
-
omitNamespace = false,
|
|
11301
|
+
// refine bbox and add cpt polygon
|
|
11302
|
+
for (let i = 0; i < len; i++) {
|
|
11303
|
+
let sub = pathDataPlusArr[i];
|
|
11304
|
+
let { pathData, bb } = sub;
|
|
9868
11305
|
|
|
9869
|
-
|
|
11306
|
+
// console.log(bb);
|
|
11307
|
+
// include control points for better overlapping approximation
|
|
9870
11308
|
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
9875
|
-
addExtremes = false,
|
|
9876
|
-
addSemiExtremes = false,
|
|
11309
|
+
if (bb.width && bb.height) ; else {
|
|
11310
|
+
let poly = getPathDataVertices(pathData, true);
|
|
11311
|
+
bb = getPolyBBox(poly);
|
|
11312
|
+
pathDataPlusArr[i].bb = bb;
|
|
9877
11313
|
|
|
9878
|
-
|
|
9879
|
-
smoothPoly = false,
|
|
9880
|
-
polyFormat = 'points',
|
|
9881
|
-
precisionPoly = 1,
|
|
11314
|
+
}
|
|
9882
11315
|
|
|
9883
|
-
|
|
9884
|
-
|
|
11316
|
+
xArr.push(bb.left, bb.right);
|
|
11317
|
+
yArr.push(bb.top, bb.bottom);
|
|
11318
|
+
sub.includes = [];
|
|
11319
|
+
}
|
|
11320
|
+
|
|
11321
|
+
/**
|
|
11322
|
+
* check overlapping
|
|
11323
|
+
* sub paths
|
|
11324
|
+
*/
|
|
11325
|
+
for (let i = 0, l = pathDataPlusArr.length; i < l; i++) {
|
|
11326
|
+
let sub1 = pathDataPlusArr[i];
|
|
11327
|
+
let { bb, poly } = sub1;
|
|
9885
11328
|
|
|
9886
|
-
|
|
11329
|
+
for (let j = 0; j < l; j++) {
|
|
9887
11330
|
|
|
9888
|
-
|
|
9889
|
-
|
|
11331
|
+
let sub1 = pathDataPlusArr[j];
|
|
11332
|
+
if (i === j) continue;
|
|
9890
11333
|
|
|
9891
|
-
|
|
9892
|
-
scaleTo = 0,
|
|
9893
|
-
crop = false,
|
|
9894
|
-
alignToOrigin = false,
|
|
11334
|
+
let bb1 = sub1.bb;
|
|
9895
11335
|
|
|
9896
|
-
|
|
9897
|
-
|
|
11336
|
+
// test sample on-path points
|
|
11337
|
+
let ptM = { x: bb1.x + bb1.width * 0.5, y: bb1.y + bb1.height * 0.5 };
|
|
11338
|
+
if (ptM.x >= bb.x && ptM.y >= bb.y && ptM.x <= bb.right && ptM.y <= bb.bottom) {
|
|
11339
|
+
pathDataPlusArr[i].includes.push(j);
|
|
11340
|
+
}
|
|
9898
11341
|
|
|
9899
|
-
|
|
9900
|
-
|
|
11342
|
+
}
|
|
11343
|
+
}
|
|
9901
11344
|
|
|
9902
|
-
|
|
11345
|
+
/**
|
|
11346
|
+
* combine overlapping
|
|
11347
|
+
* compound paths
|
|
11348
|
+
*/
|
|
11349
|
+
for (let i = 0, l = pathDataPlusArr.length; i < l; i++) {
|
|
11350
|
+
let sub = pathDataPlusArr[i];
|
|
11351
|
+
let { includes } = sub;
|
|
9903
11352
|
|
|
9904
|
-
|
|
9905
|
-
|
|
9906
|
-
|
|
11353
|
+
includes.forEach(s => {
|
|
11354
|
+
let pathData = pathDataPlusArr[s].pathData;
|
|
11355
|
+
if (pathData.length) {
|
|
11356
|
+
pathDataPlusArr[i].pathData.push(...pathData);
|
|
11357
|
+
pathDataPlusArr[s].pathData = [];
|
|
11358
|
+
}
|
|
11359
|
+
});
|
|
11360
|
+
}
|
|
9907
11361
|
|
|
9908
|
-
|
|
9909
|
-
|
|
9910
|
-
removeHidden = true,
|
|
9911
|
-
removeUnused = true,
|
|
9912
|
-
cleanupDefs = true,
|
|
9913
|
-
cleanupClip = true,
|
|
9914
|
-
cleanupSVGAtts = true,
|
|
9915
|
-
|
|
9916
|
-
stylesToAttributes = false,
|
|
9917
|
-
fixHref = false,
|
|
9918
|
-
legacyHref = false,
|
|
9919
|
-
removeNameSpaced = true,
|
|
11362
|
+
// remove empty els due to grouping
|
|
11363
|
+
pathDataPlusArr = pathDataPlusArr.filter(sub => sub.pathData.length);
|
|
9920
11364
|
|
|
9921
|
-
|
|
9922
|
-
unGroup = false,
|
|
9923
|
-
mergePaths = false,
|
|
11365
|
+
// try to find row left to right order
|
|
9924
11366
|
|
|
9925
|
-
|
|
9926
|
-
shapesToPaths = false,
|
|
11367
|
+
pathDataPlusArr = pathDataPlusArr.sort((a, b) => ((a.bb.x ) - (b.bb.x)));
|
|
9927
11368
|
|
|
9928
|
-
|
|
9929
|
-
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
11369
|
+
// create SVG
|
|
11370
|
+
let x = Math.min(...xArr);
|
|
11371
|
+
let y = Math.min(...yArr);
|
|
11372
|
+
let right = Math.max(...xArr);
|
|
11373
|
+
let bottom = Math.max(...yArr);
|
|
11374
|
+
let width = right - x;
|
|
11375
|
+
let height = bottom - y;
|
|
9933
11376
|
|
|
9934
|
-
|
|
9935
|
-
cleanUpStrokes = true,
|
|
9936
|
-
addViewBox = false,
|
|
9937
|
-
addDimensions = false,
|
|
11377
|
+
[x, y, width, height] = [x, y, width, height].map(val => roundTo(val, decimals));
|
|
9938
11378
|
|
|
9939
|
-
|
|
11379
|
+
let dimensionAtts = addDimensions ? `width="${width}" height="${height}"` : '';
|
|
11380
|
+
let svgSplit = `<svg ${dimensionAtts} viewBox="${x} ${y} ${width} ${height}" xmlns="${svgNs}">`;
|
|
9940
11381
|
|
|
9941
|
-
|
|
11382
|
+
pathDataPlusArr.forEach(sub => {
|
|
11383
|
+
let { pathData } = sub;
|
|
11384
|
+
|
|
11385
|
+
pathData = convertPathData(pathData, { toRelative, toShorthands, decimals });
|
|
11386
|
+
let d = pathDataToD(pathData, minifyD);
|
|
11387
|
+
svgSplit += `<path d="${d}"/>`;
|
|
11388
|
+
|
|
11389
|
+
});
|
|
11390
|
+
|
|
11391
|
+
svgSplit += '</svg>';
|
|
11392
|
+
|
|
11393
|
+
let splitObj = { pathData: pathDataPlusArr, svg: svgSplit };
|
|
11394
|
+
|
|
11395
|
+
return splitObj
|
|
11396
|
+
|
|
11397
|
+
}
|
|
11398
|
+
|
|
11399
|
+
/*
|
|
11400
|
+
function checkBBoxIntersections2(bb, bb1) {
|
|
11401
|
+
let [x, y, width, height, right, bottom] = [
|
|
11402
|
+
bb.x,
|
|
11403
|
+
bb.y,
|
|
11404
|
+
bb.width,
|
|
11405
|
+
bb.height,
|
|
11406
|
+
bb.x + bb.width,
|
|
11407
|
+
bb.y + bb.height
|
|
11408
|
+
];
|
|
11409
|
+
let [x1, y1, width1, height1, right1, bottom1] = [
|
|
11410
|
+
bb1.x,
|
|
11411
|
+
bb1.y,
|
|
11412
|
+
bb1.width,
|
|
11413
|
+
bb1.height,
|
|
11414
|
+
bb1.x + bb1.width,
|
|
11415
|
+
bb1.y + bb1.height
|
|
11416
|
+
];
|
|
11417
|
+
let intersects = false;
|
|
11418
|
+
|
|
11419
|
+
if (x < x1 && right > right1 && y < y1 && bottom > bottom1) {
|
|
11420
|
+
intersects = true;
|
|
11421
|
+
}
|
|
11422
|
+
|
|
11423
|
+
console.log('???', intersects, 'dims', width, height, '2', width1, height1);
|
|
11424
|
+
|
|
11425
|
+
return intersects;
|
|
11426
|
+
}
|
|
11427
|
+
*/
|
|
11428
|
+
|
|
11429
|
+
function svgPathSimplify(input = '', settings = {}) {
|
|
11430
|
+
|
|
11431
|
+
let preset = settings['preset'] !== undefined && settings['preset'] ? settings['preset'] : null;
|
|
11432
|
+
let defaults = preset && presetSettings[preset] !== undefined ? presetSettings[preset] : presetSettings['default'];
|
|
11433
|
+
|
|
11434
|
+
// merge settings
|
|
11435
|
+
settings = {
|
|
11436
|
+
...defaults,
|
|
11437
|
+
...settings
|
|
11438
|
+
};
|
|
11439
|
+
|
|
11440
|
+
let { getObject = false, removeComments, removeOffCanvas, unGroup, mergePaths, removeElements, removeDimensions, removeIds, removeClassNames, omitNamespace, cleanUpStrokes, addViewBox, addDimensions, removePrologue, removeHidden, removeUnused, cleanupDefs, cleanupClip, cleanupSVGAtts, removeNameSpaced, removeNameSpacedAtts, attributesToGroup, minifyRgbColors, stylesToAttributes, fixHref, legacyHref, allowMeta, allowDataAtts, allowAriaAtts, convertPathLength, removeSVGAttributes, removeElAttributes, shapesToPaths, shapeConvert, convertShapes, simplifyBezier, optimizeOrder, autoClose, removeZeroLength, refineClosing, removeColinear, flatBezierToLinetos, revertToQuadratics, refineExtremes, simplifyCorners, fixDirections, keepExtremes, keepCorners, keepInflections, addExtremes, reversePath, toAbsolute, toRelative, toMixed, toShorthands, toLonghands, quadraticToCubic, arcToCubic, cubicToArc, lineToCubic, decimals, autoAccuracy, minifyD, tolerance, toPolygon, smoothPoly, polyFormat, precisionPoly, simplifyRD, simplifyRDP, harmonizeCpts, removeOrphanSubpaths, simplifyRound, scale, scaleTo, crop, alignToOrigin, convertTransforms, keepSmaller, splitCompound } = settings;
|
|
9942
11441
|
|
|
9943
11442
|
// clamp tolerance and scale
|
|
9944
11443
|
tolerance = Math.max(0.1, tolerance);
|
|
9945
11444
|
scale = Math.max(0.001, scale);
|
|
11445
|
+
if(fixDirections) keepSmaller = false;
|
|
11446
|
+
if (scale !== 1 || scaleTo || crop || alignToOrigin) {
|
|
11447
|
+
convertTransforms = true;
|
|
11448
|
+
settings.convertTransforms = true;
|
|
11449
|
+
}
|
|
9946
11450
|
|
|
9947
11451
|
let inputType = detectInputType(input);
|
|
9948
11452
|
let svg = '';
|
|
@@ -9957,7 +11461,9 @@ function svgPathSimplify(input = '', {
|
|
|
9957
11461
|
let pathDataPlusArr_global = [];
|
|
9958
11462
|
let paths = [];
|
|
9959
11463
|
let polys = [];
|
|
11464
|
+
let poly = [];
|
|
9960
11465
|
let dStr = '';
|
|
11466
|
+
let dOriginal = '';
|
|
9961
11467
|
|
|
9962
11468
|
/**
|
|
9963
11469
|
* normalize input
|
|
@@ -9981,25 +11487,55 @@ function svgPathSimplify(input = '', {
|
|
|
9981
11487
|
autoClose = false;
|
|
9982
11488
|
let accuracyArr = [];
|
|
9983
11489
|
|
|
11490
|
+
// validate point JSON
|
|
11491
|
+
if (inputType === 'json') {
|
|
11492
|
+
let pts = [];
|
|
11493
|
+
let needsQuotes = /([{,]\s*)(x|y)(\s*:)/.test(input);
|
|
11494
|
+
if (needsQuotes) input = input.replaceAll('x:', '"x":').replaceAll('y:', '"y":');
|
|
11495
|
+
|
|
11496
|
+
try {
|
|
11497
|
+
pts = JSON.parse(input);
|
|
11498
|
+
} catch {
|
|
11499
|
+
console.warn('No valid JSON');
|
|
11500
|
+
}
|
|
11501
|
+
if (pts.length) {
|
|
11502
|
+
inputType = 'polyArray';
|
|
11503
|
+
input = normalizePoly(pts);
|
|
11504
|
+
}
|
|
11505
|
+
}
|
|
11506
|
+
|
|
9984
11507
|
// single path or polys
|
|
9985
|
-
if (inputType !== 'svgMarkup') {
|
|
11508
|
+
if (inputType !== 'svgMarkup' && inputType !== 'symbol') {
|
|
9986
11509
|
if (inputType === 'pathDataString') {
|
|
9987
11510
|
d = input;
|
|
9988
11511
|
} else if (inputType === 'polyString') {
|
|
9989
|
-
|
|
11512
|
+
splitCompound = false;
|
|
11513
|
+
poly = normalizePoly(input);
|
|
11514
|
+
d = pathDataFromPoly(poly, closed);
|
|
11515
|
+
|
|
9990
11516
|
}
|
|
9991
11517
|
|
|
9992
11518
|
else if (inputType === 'polyArray' || inputType === 'polyObjectArray' || inputType === 'polyComplexArray' || inputType === 'polyComplexObjectArray') {
|
|
11519
|
+
splitCompound = false;
|
|
9993
11520
|
|
|
9994
11521
|
// normalize poly input to object array
|
|
9995
|
-
|
|
11522
|
+
poly = normalizePoly(input);
|
|
9996
11523
|
|
|
9997
11524
|
// convert to pathdata
|
|
9998
|
-
|
|
11525
|
+
let closed = true;
|
|
9999
11526
|
|
|
10000
11527
|
// calculate size
|
|
11528
|
+
d = pathDataFromPoly(poly, closed);
|
|
10001
11529
|
dStr = d.map(com => { return `${com.type} ${com.values.join(' ')}` }).join(' ');
|
|
11530
|
+
dOriginal = dStr;
|
|
10002
11531
|
svgSize = dStr.length;
|
|
11532
|
+
|
|
11533
|
+
/*
|
|
11534
|
+
d=''
|
|
11535
|
+
dOriginal = '';
|
|
11536
|
+
svgSize = input.length;
|
|
11537
|
+
*/
|
|
11538
|
+
|
|
10003
11539
|
}
|
|
10004
11540
|
|
|
10005
11541
|
else if (inputType === 'pathData') {
|
|
@@ -10008,6 +11544,11 @@ function svgPathSimplify(input = '', {
|
|
|
10008
11544
|
// stringify to compare lengths
|
|
10009
11545
|
dStr = Array.from(d).map(com => { return `${com.type} ${com.values.join(' ')}` }).join(' ');
|
|
10010
11546
|
svgSize = dStr.length;
|
|
11547
|
+
|
|
11548
|
+
}
|
|
11549
|
+
// not valid - set dummy path data
|
|
11550
|
+
else {
|
|
11551
|
+
d = 'M0 0 h0';
|
|
10011
11552
|
}
|
|
10012
11553
|
|
|
10013
11554
|
paths.push({ d, el: null });
|
|
@@ -10016,27 +11557,34 @@ function svgPathSimplify(input = '', {
|
|
|
10016
11557
|
// mode:1 – process complete svg DOM
|
|
10017
11558
|
else {
|
|
10018
11559
|
|
|
11560
|
+
// convert symbol temporarily to SVG
|
|
11561
|
+
if (inputType === 'symbol') {
|
|
11562
|
+
input = input.replaceAll('<symbol', '<svg').replaceAll('</symbol', '</svg');
|
|
11563
|
+
// ids are mandatory for symbols
|
|
11564
|
+
removeIds = false;
|
|
11565
|
+
removeDimensions = true;
|
|
11566
|
+
}
|
|
11567
|
+
|
|
10019
11568
|
// convert all shapes to paths
|
|
10020
11569
|
if (shapesToPaths) {
|
|
10021
|
-
shapeConvert =
|
|
11570
|
+
shapeConvert = 'toPaths';
|
|
10022
11571
|
convert_rects = true;
|
|
10023
11572
|
convert_ellipses = true;
|
|
10024
11573
|
convert_poly = true;
|
|
10025
11574
|
convert_lines = true;
|
|
10026
11575
|
}
|
|
10027
11576
|
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
shapeConvert, convert_rects, convert_ellipses, convert_poly, convert_lines, minifyRgbColors, unGroup, convertTransforms
|
|
10031
|
-
}
|
|
10032
|
-
);
|
|
11577
|
+
// sanitize SVG - clone/decouple settings
|
|
11578
|
+
let svgPropObject = cleanUpSVG(input, JSON.parse(JSON.stringify(settings)));
|
|
10033
11579
|
svg = svgPropObject.svg;
|
|
10034
11580
|
|
|
10035
11581
|
// collect paths
|
|
10036
11582
|
let pathEls = svg.querySelectorAll('path');
|
|
10037
11583
|
|
|
10038
11584
|
pathEls.forEach((path, i) => {
|
|
10039
|
-
|
|
11585
|
+
let d = path.getAttribute('d');
|
|
11586
|
+
|
|
11587
|
+
paths.push({ d, el: path, idx: i });
|
|
10040
11588
|
});
|
|
10041
11589
|
|
|
10042
11590
|
// get viewBox/dimensions
|
|
@@ -10052,20 +11600,19 @@ function svgPathSimplify(input = '', {
|
|
|
10052
11600
|
// SVG optimization options
|
|
10053
11601
|
let pathOptions = {
|
|
10054
11602
|
toRelative,
|
|
10055
|
-
|
|
10056
|
-
toLonghands,
|
|
11603
|
+
toMixed,
|
|
10057
11604
|
toShorthands,
|
|
10058
11605
|
decimals,
|
|
10059
11606
|
};
|
|
10060
11607
|
|
|
10061
|
-
// combinded path data for SVGs with mergePaths enabled
|
|
10062
|
-
let pathData_merged = [];
|
|
10063
|
-
|
|
10064
11608
|
for (let i = 0, l = paths.length; l && i < l; i++) {
|
|
10065
11609
|
|
|
10066
11610
|
let pathDataPlusArr = [];
|
|
10067
11611
|
let path = paths[i];
|
|
10068
11612
|
let { d, el } = path;
|
|
11613
|
+
let isPoly = false;
|
|
11614
|
+
|
|
11615
|
+
// if polygon we already heave absolute coordinates
|
|
10069
11616
|
|
|
10070
11617
|
let pathData = parsePathDataNormalized(d, { quadraticToCubic, arcToCubic });
|
|
10071
11618
|
|
|
@@ -10104,7 +11651,7 @@ function svgPathSimplify(input = '', {
|
|
|
10104
11651
|
// count commands for evaluation
|
|
10105
11652
|
let comCount = pathData.length;
|
|
10106
11653
|
|
|
10107
|
-
if (removeOrphanSubpaths) pathData = removeOrphanedM(pathData);
|
|
11654
|
+
if (!isPoly && removeOrphanSubpaths) pathData = removeOrphanedM(pathData);
|
|
10108
11655
|
|
|
10109
11656
|
/**
|
|
10110
11657
|
* get sub paths
|
|
@@ -10117,24 +11664,26 @@ function svgPathSimplify(input = '', {
|
|
|
10117
11664
|
|
|
10118
11665
|
let pathDataSub = subPathArr[i];
|
|
10119
11666
|
let poly = [];
|
|
10120
|
-
|
|
10121
11667
|
let coms = Array.from(new Set(pathDataSub.map(com => com.type))).join('');
|
|
10122
|
-
|
|
10123
|
-
let closed =
|
|
11668
|
+
isPoly = !(/[acqts]/gi).test(coms);
|
|
11669
|
+
let closed = isPoly ? true : false;
|
|
10124
11670
|
|
|
10125
|
-
if (isPoly) {
|
|
11671
|
+
if (isPoly && !mode) {
|
|
10126
11672
|
|
|
10127
11673
|
poly = getPathDataVertices(pathDataSub);
|
|
11674
|
+
let bb = getPolyBBox(reducePoints(poly, 64));
|
|
10128
11675
|
|
|
10129
11676
|
// simplify polygon
|
|
10130
11677
|
if (simplifyRD > 0) {
|
|
10131
|
-
poly = simplifyPolyRD(poly, { quality: simplifyRD
|
|
11678
|
+
poly = simplifyPolyRD(poly, { quality: simplifyRD, width: bb.width, height: bb.height });
|
|
10132
11679
|
}
|
|
10133
11680
|
|
|
10134
11681
|
if (simplifyRDP > 0) {
|
|
10135
|
-
poly = simplifyPolyRDP(poly, { quality: simplifyRDP
|
|
11682
|
+
poly = simplifyPolyRDP(poly, { quality: simplifyRDP, width: bb.width, height: bb.height });
|
|
11683
|
+
|
|
10136
11684
|
}
|
|
10137
11685
|
|
|
11686
|
+
toPolygon = false;
|
|
10138
11687
|
pathDataSub = pathDataFromPoly(poly, closed);
|
|
10139
11688
|
|
|
10140
11689
|
}
|
|
@@ -10148,21 +11697,18 @@ function svgPathSimplify(input = '', {
|
|
|
10148
11697
|
smoothPoly = false;
|
|
10149
11698
|
harmonizeCpts = false;
|
|
10150
11699
|
|
|
10151
|
-
|
|
10152
|
-
let { bb, pathData } = pathDataSubPlus;
|
|
10153
|
-
pathDataSub = pathData;
|
|
11700
|
+
pathDataSub = getPathDataVerbose(pathDataSub);
|
|
10154
11701
|
|
|
10155
|
-
let polyData =
|
|
11702
|
+
let polyData = pathDataToPolygonOpt(pathDataSub, {
|
|
10156
11703
|
precisionPoly,
|
|
10157
11704
|
autoAccuracy,
|
|
10158
|
-
|
|
10159
|
-
decimals,
|
|
11705
|
+
|
|
10160
11706
|
simplifyRD,
|
|
10161
11707
|
simplifyRDP
|
|
10162
11708
|
});
|
|
10163
11709
|
|
|
10164
|
-
polys.push(polyData.poly);
|
|
10165
11710
|
pathDataSub = polyData.pathData;
|
|
11711
|
+
isPoly = true;
|
|
10166
11712
|
|
|
10167
11713
|
}
|
|
10168
11714
|
|
|
@@ -10192,7 +11738,10 @@ function svgPathSimplify(input = '', {
|
|
|
10192
11738
|
simplifyRD,
|
|
10193
11739
|
simplifyRDP,
|
|
10194
11740
|
};
|
|
11741
|
+
|
|
10195
11742
|
pathDataSub = simplifyPolygonToPathData(poly, optionsPoly);
|
|
11743
|
+
// flag as non poly as we're smoothing to curves
|
|
11744
|
+
|
|
10196
11745
|
}
|
|
10197
11746
|
}
|
|
10198
11747
|
|
|
@@ -10209,27 +11758,43 @@ function svgPathSimplify(input = '', {
|
|
|
10209
11758
|
if (removeColinear) pathDataSub = pathDataRemoveColinear(pathDataSub, { tolerance, flatBezierToLinetos: false });
|
|
10210
11759
|
|
|
10211
11760
|
let tMin = 0, tMax = 1;
|
|
10212
|
-
if (addExtremes
|
|
10213
|
-
{ tMin, tMax, addExtremes,
|
|
11761
|
+
if (addExtremes) pathDataSub = addExtremePoints(pathDataSub,
|
|
11762
|
+
{ tMin, tMax, addExtremes, angles: [30] });
|
|
10214
11763
|
|
|
10215
11764
|
// reverse
|
|
10216
11765
|
if (reversePath) {
|
|
10217
11766
|
pathDataSub = reversePathData(pathDataSub);
|
|
10218
11767
|
}
|
|
10219
11768
|
|
|
10220
|
-
// analyze pathdata to add info about
|
|
10221
|
-
let pathDataPlus =
|
|
10222
|
-
|
|
10223
|
-
|
|
11769
|
+
// analyze pathdata to add info about significant properties such as extremes, corners
|
|
11770
|
+
let pathDataPlus = { bb: {}, dimA: 0, pathData: [] };
|
|
11771
|
+
|
|
11772
|
+
if (!isPoly) {
|
|
11773
|
+
pathDataPlus = analyzePathData(pathDataSub);
|
|
11774
|
+
}
|
|
11775
|
+
// we skip detailed analysis for native polygons
|
|
11776
|
+
else {
|
|
11777
|
+
if (!poly.length) {
|
|
11778
|
+
let pathDataCubic = convertPathData(JSON.parse(JSON.stringify(pathDataSub)), { toLonghands: true, toAbsolute: true, arcToCubic: true, testTypes: true });
|
|
11779
|
+
pathDataPlus.bb = getPolyBBox(getPathDataVertices(pathDataCubic));
|
|
11780
|
+
}
|
|
11781
|
+
pathDataPlus.dimA = pathDataPlus.bb.width + pathDataPlus.bb.height;
|
|
11782
|
+
pathDataPlus.pathData = getPathDataVerbose(pathDataSub, {
|
|
11783
|
+
addSquareLength: false,
|
|
11784
|
+
addArea: false,
|
|
11785
|
+
addAverageDim: false
|
|
11786
|
+
});
|
|
11787
|
+
}
|
|
10224
11788
|
|
|
10225
11789
|
// simplify beziers
|
|
10226
11790
|
let { pathData, bb, dimA } = pathDataPlus;
|
|
11791
|
+
|
|
10227
11792
|
xArr.push(bb.x, bb.x + bb.width);
|
|
10228
11793
|
yArr.push(bb.y, bb.y + bb.height);
|
|
10229
11794
|
|
|
10230
11795
|
if (refineClosing) pathData = refineClosingCommand(pathData, { threshold: dimA * 0.001 });
|
|
10231
11796
|
|
|
10232
|
-
pathData = simplifyBezier ? simplifyPathDataCubic(pathData, { simplifyBezier, keepInflections, keepExtremes, keepCorners,
|
|
11797
|
+
pathData = simplifyBezier ? simplifyPathDataCubic(pathData, { simplifyBezier, keepInflections, keepExtremes, keepCorners, revertToQuadratics, tolerance }) : pathData;
|
|
10233
11798
|
|
|
10234
11799
|
// refine extremes
|
|
10235
11800
|
if (refineExtremes) {
|
|
@@ -10269,8 +11834,6 @@ function svgPathSimplify(input = '', {
|
|
|
10269
11834
|
let decimalsSub = detectAccuracy(pathData);
|
|
10270
11835
|
accuracyArr.push(decimalsSub);
|
|
10271
11836
|
|
|
10272
|
-
// pre round sub path
|
|
10273
|
-
|
|
10274
11837
|
}
|
|
10275
11838
|
|
|
10276
11839
|
// update
|
|
@@ -10287,16 +11850,16 @@ function svgPathSimplify(input = '', {
|
|
|
10287
11850
|
bb_global = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin };
|
|
10288
11851
|
let isPortrait = bb_global.height > bb_global.width;
|
|
10289
11852
|
|
|
11853
|
+
// fix path directions - before reordering
|
|
11854
|
+
if (fixDirections) {
|
|
11855
|
+
pathDataPlusArr = fixPathDataDirections(pathDataPlusArr);
|
|
11856
|
+
}
|
|
11857
|
+
|
|
10290
11858
|
// prefer top to bottom priority for portrait aspect ratios
|
|
10291
11859
|
if (optimizeOrder) {
|
|
10292
11860
|
pathDataPlusArr = isPortrait ? pathDataPlusArr.sort((a, b) => a.bb.y - b.bb.y || a.bb.x - b.bb.x) : pathDataPlusArr.sort((a, b) => a.bb.x - b.bb.x || a.bb.y - b.bb.y);
|
|
10293
11861
|
}
|
|
10294
11862
|
|
|
10295
|
-
// fix path directions
|
|
10296
|
-
if (fixDirections) {
|
|
10297
|
-
pathDataPlusArr = fixPathDataDirections(pathDataPlusArr);
|
|
10298
|
-
}
|
|
10299
|
-
|
|
10300
11863
|
// flatten compound paths
|
|
10301
11864
|
pathData = [];
|
|
10302
11865
|
|
|
@@ -10315,57 +11878,69 @@ function svgPathSimplify(input = '', {
|
|
|
10315
11878
|
pathOptions.decimals = decimals;
|
|
10316
11879
|
}
|
|
10317
11880
|
|
|
10318
|
-
//
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
11881
|
+
// add simplified poly - if not populated by toPoly conversion
|
|
11882
|
+
if (isPoly) {
|
|
11883
|
+
|
|
11884
|
+
pathDataPlusArr.forEach(sub => {
|
|
11885
|
+
let poly = getPathDataVertices(sub.pathData, false, decimals);
|
|
11886
|
+
if (polyFormat === 'array') {
|
|
11887
|
+
poly = polyPtsToArray(poly);
|
|
11888
|
+
}
|
|
11889
|
+
polys.push(poly);
|
|
11890
|
+
});
|
|
11891
|
+
|
|
10322
11892
|
}
|
|
10323
|
-
// single output
|
|
10324
|
-
else {
|
|
10325
11893
|
|
|
10326
|
-
|
|
11894
|
+
// split into sub paths - returns svg with multiple paths
|
|
11895
|
+
if (splitCompound && !mode && pathDataPlusArr.length > 1) {
|
|
11896
|
+
let pathDataSplit = splitCompundGroups(pathDataPlusArr, { toRelative, toShorthands, decimals, addDimensions });
|
|
11897
|
+
svg = new DOMParser().parseFromString(pathDataSplit.svg, 'image/svg+xml').querySelector('svg');
|
|
11898
|
+
// switch output type
|
|
11899
|
+
mode = 1;
|
|
11900
|
+
inputType = 'splitPath';
|
|
11901
|
+
}
|
|
10327
11902
|
|
|
10328
|
-
|
|
11903
|
+
// clone pathdata
|
|
11904
|
+
pathData = JSON.parse(JSON.stringify(pathData));
|
|
10329
11905
|
|
|
10330
|
-
|
|
10331
|
-
|
|
11906
|
+
// optimize path data
|
|
11907
|
+
pathData = convertPathData(pathData, pathOptions);
|
|
10332
11908
|
|
|
10333
|
-
|
|
10334
|
-
|
|
11909
|
+
// remove zero-length segments introduced by rounding
|
|
11910
|
+
if (removeZeroLength) pathData = removeZeroLengthLinetos(pathData);
|
|
10335
11911
|
|
|
10336
|
-
|
|
10337
|
-
|
|
11912
|
+
// realign path to zero origin
|
|
11913
|
+
if (alignToOrigin) {
|
|
10338
11914
|
|
|
10339
|
-
|
|
10340
|
-
|
|
11915
|
+
pathData[0].values[0] = roundTo((pathData[0].values[0] - bb_global.x), decimals);
|
|
11916
|
+
pathData[0].values[1] = roundTo((pathData[0].values[1] - bb_global.y), decimals);
|
|
10341
11917
|
|
|
10342
|
-
|
|
10343
|
-
|
|
10344
|
-
|
|
11918
|
+
bb_global.x = 0;
|
|
11919
|
+
bb_global.y = 0;
|
|
11920
|
+
}
|
|
10345
11921
|
|
|
10346
|
-
|
|
10347
|
-
|
|
11922
|
+
// compare command count
|
|
11923
|
+
let comCountS = pathData.length;
|
|
10348
11924
|
|
|
10349
|
-
|
|
11925
|
+
let dOpt = pathDataToD(pathData, minifyD);
|
|
10350
11926
|
|
|
10351
|
-
|
|
11927
|
+
svgSizeOpt = dOpt.length;
|
|
10352
11928
|
|
|
10353
|
-
|
|
11929
|
+
compression = +(100 / svgSize * (svgSizeOpt)).toFixed(2);
|
|
10354
11930
|
|
|
10355
|
-
|
|
10356
|
-
|
|
10357
|
-
|
|
10358
|
-
|
|
10359
|
-
|
|
10360
|
-
|
|
10361
|
-
|
|
11931
|
+
path.d = dOpt;
|
|
11932
|
+
path.report = {
|
|
11933
|
+
original: comCount,
|
|
11934
|
+
new: comCountS,
|
|
11935
|
+
saved: comCount - comCountS,
|
|
11936
|
+
compression,
|
|
11937
|
+
decimals,
|
|
10362
11938
|
|
|
10363
|
-
|
|
11939
|
+
};
|
|
10364
11940
|
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
|
|
10368
|
-
}
|
|
11941
|
+
// apply new path for svgs
|
|
11942
|
+
if (el) {
|
|
11943
|
+
el.setAttribute('d', dOpt);
|
|
10369
11944
|
}
|
|
10370
11945
|
|
|
10371
11946
|
} // end path array
|
|
@@ -10373,36 +11948,11 @@ function svgPathSimplify(input = '', {
|
|
|
10373
11948
|
/**
|
|
10374
11949
|
* stringify new SVG
|
|
10375
11950
|
*/
|
|
10376
|
-
if (mode) {
|
|
10377
|
-
|
|
10378
|
-
if (pathData_merged.length) {
|
|
10379
|
-
|
|
10380
|
-
// optimize path data
|
|
10381
|
-
let pathData = convertPathData(pathData_merged, pathOptions);
|
|
10382
|
-
|
|
10383
|
-
// remove zero-length segments introduced by rounding
|
|
10384
|
-
pathData = removeZeroLengthLinetos(pathData);
|
|
10385
|
-
|
|
10386
|
-
let dOpt = pathDataToD(pathData, minifyD);
|
|
10387
|
-
|
|
10388
|
-
// apply new path for svgs
|
|
10389
|
-
paths[0].el.setAttribute('d', dOpt);
|
|
10390
|
-
|
|
10391
|
-
// remove other paths
|
|
10392
|
-
for (let i = 1; i < paths.length; i++) {
|
|
10393
|
-
let pathEl = paths[i].el;
|
|
10394
|
-
if (pathEl) pathEl.remove();
|
|
10395
|
-
}
|
|
10396
|
-
|
|
10397
|
-
// remove empty groups e.g groups
|
|
10398
|
-
removeEmptySVGEls(svg);
|
|
10399
|
-
}
|
|
11951
|
+
if (mode || inputType === 'symbol') {
|
|
10400
11952
|
|
|
10401
11953
|
// adjust viewBox and width for scale
|
|
10402
11954
|
if (scale) {
|
|
10403
|
-
|
|
10404
11955
|
let { x, y, width, height, w, h, hasViewBox, hasWidth, hasHeight, widthUnit, heightUnit } = viewBox;
|
|
10405
|
-
|
|
10406
11956
|
if (crop) {
|
|
10407
11957
|
x = bb_global.x;
|
|
10408
11958
|
y = bb_global.y;
|
|
@@ -10413,14 +11963,14 @@ function svgPathSimplify(input = '', {
|
|
|
10413
11963
|
}
|
|
10414
11964
|
|
|
10415
11965
|
if (hasViewBox) {
|
|
10416
|
-
svg.setAttribute('viewBox', [x, y, width, height].map(val =>
|
|
11966
|
+
svg.setAttribute('viewBox', [x, y, width, height].map(val => roundTo(val * scale, decimals)).join(' '));
|
|
10417
11967
|
}
|
|
10418
11968
|
if (hasWidth) {
|
|
10419
|
-
svg.setAttribute('width',
|
|
11969
|
+
svg.setAttribute('width', roundTo(w * scale, decimals) + widthUnit);
|
|
10420
11970
|
}
|
|
10421
11971
|
|
|
10422
11972
|
if (hasHeight) {
|
|
10423
|
-
svg.setAttribute('height',
|
|
11973
|
+
svg.setAttribute('height', roundTo(h * scale, decimals) + heightUnit);
|
|
10424
11974
|
}
|
|
10425
11975
|
}
|
|
10426
11976
|
|
|
@@ -10433,7 +11983,9 @@ function svgPathSimplify(input = '', {
|
|
|
10433
11983
|
});
|
|
10434
11984
|
}
|
|
10435
11985
|
|
|
10436
|
-
|
|
11986
|
+
if (removeSVGAttributes.includes('xmlns')) omitNamespace = true;
|
|
11987
|
+
|
|
11988
|
+
svg = stringifySVG(svg, { omitNamespace, removeComments, format: minifyD });
|
|
10437
11989
|
|
|
10438
11990
|
svgSizeOpt = svg.length;
|
|
10439
11991
|
|
|
@@ -10446,9 +11998,15 @@ function svgPathSimplify(input = '', {
|
|
|
10446
11998
|
svgSize,
|
|
10447
11999
|
svgSizeOpt,
|
|
10448
12000
|
compression,
|
|
10449
|
-
decimals
|
|
12001
|
+
decimals,
|
|
10450
12002
|
};
|
|
10451
12003
|
|
|
12004
|
+
if (keepSmaller && svgSize < svgSizeOpt && !splitCompound) {
|
|
12005
|
+
|
|
12006
|
+
svg = input;
|
|
12007
|
+
report.node = 'Original is smaller!';
|
|
12008
|
+
}
|
|
12009
|
+
|
|
10452
12010
|
} else {
|
|
10453
12011
|
({ d, report } = paths[0]);
|
|
10454
12012
|
}
|
|
@@ -10457,7 +12015,11 @@ function svgPathSimplify(input = '', {
|
|
|
10457
12015
|
polys = polys[0];
|
|
10458
12016
|
}
|
|
10459
12017
|
|
|
10460
|
-
|
|
12018
|
+
if (polyFormat === 'string' && polys.length) {
|
|
12019
|
+
polys = polys.flat().map(pt => `${pt.x},${pt.y}`).join(' ');
|
|
12020
|
+
}
|
|
12021
|
+
|
|
12022
|
+
return !getObject ? (d ? d : svg) : { svg, d, polys, report, pathDataPlusArr: pathDataPlusArr_global, inputType, dOriginal };
|
|
10461
12023
|
|
|
10462
12024
|
}
|
|
10463
12025
|
|