svg-path-simplify 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2684,22 +2684,22 @@ function getPathDataVerbose(pathData, {
2684
2684
  return pathDataVerbose;
2685
2685
  }
2686
2686
 
2687
- function revertCubicQuadratic(p0 = {}, cp1 = {}, cp2 = {}, p = {}) {
2687
+ function revertCubicQuadratic(p0 = {}, cp1 = {}, cp2 = {}, p = {}, tolerance=1) {
2688
2688
 
2689
2689
  // test if cubic can be simplified to quadratic
2690
2690
  let cp1X = interpolate(p0, cp1, 1.5);
2691
2691
  let cp2X = interpolate(p, cp2, 1.5);
2692
2692
 
2693
- let dist0 = getDistAv(p0, p);
2694
- let threshold = dist0 * 0.03;
2695
- let dist1 = getDistAv(cp1X, cp2X);
2693
+ let dist0 = getDistManhattan(p0, p);
2694
+ let threshold = dist0 * 0.01 * tolerance;
2695
+ let dist1 = getDistManhattan(cp1X, cp2X);
2696
2696
 
2697
2697
  let cp1_Q = null;
2698
2698
  let type = 'C';
2699
2699
  let values = [cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y];
2700
2700
  let comN = { type, values };
2701
2701
 
2702
- if (dist1 && threshold && dist1 < threshold) {
2702
+ if (dist1 < threshold ) {
2703
2703
  cp1_Q = checkLineIntersection(p0, cp1, p, cp2, false);
2704
2704
  if (cp1_Q) {
2705
2705
 
@@ -3597,9 +3597,11 @@ const sanitizeArc = (val='', valueIndex=0) => {
3597
3597
 
3598
3598
  };
3599
3599
 
3600
- function parsePathDataString(d, debug = true) {
3600
+ function parsePathDataString(d, debug = true, limit=0) {
3601
3601
  d = d.trim();
3602
3602
 
3603
+ if(limit) console.log('!!!limit', limit);
3604
+
3603
3605
  let pathDataObj = {
3604
3606
  pathData: [],
3605
3607
  hasRelatives: false,
@@ -5994,6 +5996,648 @@ function refineAdjacentExtremes(pathData, {
5994
5996
 
5995
5997
  }
5996
5998
 
5999
+ /**
6000
+ * parse CSS string to
6001
+ * transform property object
6002
+ */
6003
+
6004
+ function parseCSSTransform(transformString, transformOrigin={x:0, y:0}) {
6005
+ let transformOptions = {
6006
+ transforms: [],
6007
+ transformOrigin,
6008
+ };
6009
+
6010
+ let regex = /(\w+)\(([^)]+)\)/g;
6011
+ let match;
6012
+
6013
+ function convertToDegrees(value) {
6014
+ if (typeof value === 'string') {
6015
+ if (value.includes('rad')) {
6016
+ return parseFloat(value) * (180 / Math.PI);
6017
+ } else if (value.includes('turn')) {
6018
+ return parseFloat(value) * 360;
6019
+ }
6020
+ }
6021
+ return parseFloat(value);
6022
+ }
6023
+
6024
+ while ((match = regex.exec(transformString)) !== null) {
6025
+ let name = match[1];
6026
+ let values = match[2].split(/,\s*/).map(v => convertToDegrees(v));
6027
+
6028
+ switch (name) {
6029
+
6030
+ case 'translate':
6031
+ transformOptions.transforms.push({ translate: [values[0] || 0, values[1] || 0] });
6032
+ break;
6033
+ case 'translateX':
6034
+ transformOptions.transforms.push({ translate: [values[0] || 0, 0, 0] });
6035
+ break;
6036
+
6037
+ case 'translateY':
6038
+ transformOptions.transforms.push({ translate: [0, values[0] || 0, 0] });
6039
+ break;
6040
+ case 'scale':
6041
+ transformOptions.transforms.push({ scale: [values[0] || 0, values[1] || 0] });
6042
+ break;
6043
+ case 'skew':
6044
+ transformOptions.transforms.push({ skew: [values[0] || 0, values[1] || 0] });
6045
+ break;
6046
+
6047
+ case 'skewX':
6048
+ transformOptions.transforms.push({ skew: [values[0] || 0, 0] });
6049
+ break;
6050
+
6051
+ case 'skewY':
6052
+ transformOptions.transforms.push({ skew: [0, values[0] || 0] });
6053
+ break;
6054
+ case 'rotate':
6055
+ transformOptions.transforms.push({ rotate: [0, 0, values[0] || 0] });
6056
+ break;
6057
+ case 'matrix':
6058
+ transformOptions.transforms.push({ matrix: values });
6059
+ break;
6060
+ }
6061
+ }
6062
+
6063
+ // Extract transform-origin, perspective-origin, and perspective if included as separate properties
6064
+ let styleProperties = transformString.split(/;\s*/);
6065
+ styleProperties.forEach(prop => {
6066
+ let [key, value] = prop.split(':').map(s => s.trim());
6067
+ if (key === 'transform-origin' || key === 'perspective-origin') {
6068
+ let [x, y] = value.split(/\s+/).map(parseFloat);
6069
+ if (key === 'transform-origin') {
6070
+ transformOptions.transformOrigin = { x: x || 0, y: y || 0 };
6071
+ }
6072
+ }
6073
+ });
6074
+
6075
+ return transformOptions;
6076
+ }
6077
+
6078
+ /**
6079
+ * wrapper function to switch between
6080
+ * 2D or 3D matrix
6081
+ */
6082
+ function getMatrix({
6083
+ transforms = [],
6084
+ transformOrigin = { x: 0, y: 0 },
6085
+ } = {}) {
6086
+
6087
+ let matrix = getMatrix2D(transforms, transformOrigin);
6088
+
6089
+ return matrix
6090
+ }
6091
+
6092
+ function getMatrix2D(transformations = [], origin = { x: 0, y: 0 }) {
6093
+
6094
+ // Helper function to multiply two 2D matrices
6095
+ const multiply = (m1, m2) => ({
6096
+ a: m1.a * m2.a + m1.c * m2.b,
6097
+ b: m1.b * m2.a + m1.d * m2.b,
6098
+ c: m1.a * m2.c + m1.c * m2.d,
6099
+ d: m1.b * m2.c + m1.d * m2.d,
6100
+ e: m1.a * m2.e + m1.c * m2.f + m1.e,
6101
+ f: m1.b * m2.e + m1.d * m2.f + m1.f
6102
+ });
6103
+
6104
+ // Helper function to create a translation matrix
6105
+ const translationMatrix = (x, y) => ({
6106
+ a: 1, b: 0, c: 0, d: 1, e: x, f: y
6107
+ });
6108
+
6109
+ // Helper function to create a scaling matrix
6110
+ const scalingMatrix = (x, y) => ({
6111
+ a: x, b: 0, c: 0, d: y, e: 0, f: 0
6112
+ });
6113
+
6114
+ // get skew or rotation axis matrix
6115
+ const angleMatrix = (angles, type) => {
6116
+ const toRad = (angle) => angle * Math.PI / 180;
6117
+ let [angleX, angleY] = angles.map(ang => { return toRad(ang) });
6118
+ let m = {};
6119
+
6120
+ if (type === 'rot') {
6121
+ let cos = Math.cos(angleX), sin = Math.sin(angleX);
6122
+ m = { a: cos, b: sin, c: -sin, d: cos, e: 0, f: 0 };
6123
+ } else if (type === 'skew') {
6124
+ let tanX = Math.tan(angleX), tanY = Math.tan(angleY);
6125
+ m = {
6126
+ a: 1, b: tanY, c: tanX, d: 1, e: 0, f: 0
6127
+ };
6128
+ }
6129
+ return m
6130
+ };
6131
+
6132
+ // Start with an identity matrix
6133
+ let matrix = { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 };
6134
+
6135
+ // Apply transform origin: translate to origin, apply transformations, translate back
6136
+ if (origin.x !== 0 || origin.y !== 0) {
6137
+ matrix = multiply(matrix, translationMatrix(origin.x, origin.y));
6138
+ }
6139
+
6140
+ // Default values for transformations
6141
+ const defaults = {
6142
+ translate: [0, 0],
6143
+ scale: [1, 1],
6144
+ skew: [0, 0],
6145
+ rotate: [0],
6146
+ matrix: [1, 0, 0, 1, 0, 0]
6147
+ };
6148
+
6149
+ // Process transformations in the provided order (right-to-left)
6150
+ for (const transform of transformations) {
6151
+ const type = Object.keys(transform)[0]; // Get the transformation type (e.g., "translate")
6152
+ const values = transform[type] || defaults[type]; // Use default values if none provided
6153
+
6154
+ // Destructure values with fallbacks
6155
+ let [x, y = defaults[type][1]] = values;
6156
+
6157
+ // Z-rotate as 2d rotation
6158
+ if (type === 'rotate' && values.length === 3) {
6159
+ x = values[2];
6160
+ }
6161
+
6162
+ switch (type) {
6163
+ case "matrix":
6164
+ let keys = ['a', 'b', 'c', 'd', 'e', 'f'];
6165
+ let obj = Object.fromEntries(keys.map((key, i) => [key, values[i]]));
6166
+ matrix = multiply(matrix, obj);
6167
+ break;
6168
+ case "translate":
6169
+ if (x || y) matrix = multiply(matrix, translationMatrix(x, y));
6170
+ break;
6171
+ case "skew":
6172
+ if (x || y) matrix = multiply(matrix, angleMatrix([x, y], 'skew'));
6173
+ break;
6174
+ case "rotate":
6175
+ if (x) matrix = multiply(matrix, angleMatrix([x], 'rot'));
6176
+ break;
6177
+ case "scale":
6178
+ if (x !== 1 || y !== 1) matrix = multiply(matrix, scalingMatrix(x, y));
6179
+ break;
6180
+
6181
+ default:
6182
+ throw new Error(`Unknown transformation type: ${type}`);
6183
+ }
6184
+ }
6185
+
6186
+ // Revert transform origin
6187
+ if (origin.x !== 0 || origin.y !== 0) {
6188
+ matrix = multiply(matrix, translationMatrix(-origin.x, -origin.y));
6189
+ }
6190
+
6191
+ return matrix;
6192
+ }
6193
+
6194
+ /**
6195
+ * all SVG attributes
6196
+ * mapped to elements
6197
+ * used to remove unnecessary attribution
6198
+ */
6199
+
6200
+ const shapeEls = [
6201
+ "polygon",
6202
+ "polyline",
6203
+ "line",
6204
+ "rect",
6205
+ "circle",
6206
+ "ellipse",
6207
+ ];
6208
+
6209
+ const geometryEls = [
6210
+ "path",
6211
+ ...shapeEls
6212
+ ];
6213
+
6214
+ const textEls = [
6215
+ "textPath",
6216
+ "text",
6217
+ "tspan",
6218
+ ];
6219
+
6220
+ const attLookup = {
6221
+
6222
+ atts: {
6223
+
6224
+ // wildcard
6225
+ id:'*',
6226
+ class:'*',
6227
+
6228
+ // svg
6229
+ viewBox: ["symbol", "svg"],
6230
+ preserveAspectRatio: ["symbol", "svg"],
6231
+ width: ["svg", "rect", "use", "image"],
6232
+ height: ["svg", "rect", "use", "image"],
6233
+
6234
+ // geometry
6235
+ d: ["path"],
6236
+ points: ["polygon", "polyline"],
6237
+
6238
+ x: ["image", "rect", "text", "textPath", "tspan", "use", "mask"],
6239
+ y: ["image", "rect", "text", "textPath", "tspan", "use", "mask"],
6240
+ x1: ["line", "linearGradient"],
6241
+ x2: ["line", "linearGradient"],
6242
+ y1: ["line", "linearGradient"],
6243
+ y2: ["line", "linearGradient"],
6244
+
6245
+ r: ["circle", "radialGradient"],
6246
+ rx: ["rect", "ellipse"],
6247
+ ry: ["rect", "ellipse"],
6248
+
6249
+ cx: ["circle", "ellipse", "radialGradient"],
6250
+ cy: ["circle", "ellipse", "radialGradient"],
6251
+
6252
+ refX: ["symbol", "markers"],
6253
+ refY: ["symbol", "markers"],
6254
+
6255
+ // transforms
6256
+ transform: [
6257
+ "svg",
6258
+ "g",
6259
+ "use",
6260
+ ...geometryEls,
6261
+ ...textEls,
6262
+ ],
6263
+
6264
+ "transform-origin": [
6265
+ "svg",
6266
+ "g",
6267
+ "use",
6268
+ ...geometryEls,
6269
+ ...textEls,
6270
+ ],
6271
+
6272
+ fill: [
6273
+ "svg",
6274
+ "g",
6275
+ "use",
6276
+ ...geometryEls,
6277
+ ...textEls,
6278
+ "animate",
6279
+ "animateMotion"
6280
+ ],
6281
+
6282
+ "fill-opacity": [
6283
+ "svg",
6284
+ "g",
6285
+ "use",
6286
+ ...geometryEls,
6287
+ ...textEls,
6288
+ ],
6289
+
6290
+ opacity: [
6291
+ "svg",
6292
+ "g",
6293
+ "use",
6294
+ ...geometryEls,
6295
+ ...textEls,
6296
+ ],
6297
+
6298
+ stroke: [
6299
+ "svg",
6300
+ "g",
6301
+ "use",
6302
+ ...geometryEls,
6303
+ ...textEls,
6304
+ ],
6305
+
6306
+ "stroke-width": [
6307
+ "svg",
6308
+ "g",
6309
+ "use",
6310
+ ...geometryEls,
6311
+ ...textEls,
6312
+ "mask",
6313
+ ],
6314
+
6315
+ "stroke-opacity": [
6316
+ "svg",
6317
+ "g",
6318
+ "use",
6319
+ ...geometryEls,
6320
+ ...textEls,
6321
+ "mask",
6322
+ ],
6323
+
6324
+ "stroke-miterlimit": [
6325
+ "svg",
6326
+ "g",
6327
+ "use",
6328
+ ...geometryEls,
6329
+ ...textEls,
6330
+ "mask",
6331
+ ],
6332
+
6333
+ "stroke-linejoin": [
6334
+ "svg",
6335
+ "g",
6336
+ "use",
6337
+ ...geometryEls,
6338
+ ...textEls,
6339
+ "mask",
6340
+ ],
6341
+
6342
+ "stroke-linecap": [
6343
+ "svg",
6344
+ "g",
6345
+ "use",
6346
+ ...geometryEls,
6347
+ ...textEls,
6348
+ "mask",
6349
+ ],
6350
+
6351
+ "stroke-dashoffset": [
6352
+ "svg",
6353
+ "g",
6354
+ "use",
6355
+ ...geometryEls,
6356
+ ...textEls,
6357
+ "mask",
6358
+ ],
6359
+
6360
+ "stroke-dasharray": [
6361
+ "svg",
6362
+ "g",
6363
+ "use",
6364
+ ...geometryEls,
6365
+ ...textEls,
6366
+ "mask",
6367
+ ],
6368
+
6369
+ "clip-path": [
6370
+ "svg",
6371
+ "g",
6372
+ "use",
6373
+ ...geometryEls,
6374
+ ...textEls,
6375
+ ],
6376
+
6377
+ "clip-rule": [
6378
+ "path",
6379
+ "polygon",
6380
+ ],
6381
+
6382
+ clipPathUnits: ["clipPath"],
6383
+
6384
+ mask: [
6385
+ "svg",
6386
+ "g",
6387
+ "use",
6388
+ ...geometryEls,
6389
+ ...textEls,
6390
+ ],
6391
+ maskContentUnits: ["mask"],
6392
+ maskUnits: ["mask"],
6393
+
6394
+ // text els
6395
+ "font-family": ["svg", "g", ...textEls],
6396
+ "font-size": ["svg", "g", ...textEls],
6397
+ "font-style": ["svg", "g", ...textEls],
6398
+ "font-weight": ["svg", "g", ...textEls],
6399
+ "font-stretch": ["svg", "g", ...textEls],
6400
+ "dominant-baseline": [...textEls],
6401
+ lengthAdjust: [...textEls],
6402
+ "text-anchor": ["text"],
6403
+ textLength: ["text", "textPath", "tspan"],
6404
+ dx: ["text", "tspan"],
6405
+ dy: ["text", "tspan"],
6406
+ method: ["textPath"],
6407
+
6408
+ spacing: ["textPath"],
6409
+ startOffset: ["textPath"],
6410
+ rotate: ["text", "tspan", "animateMotion"],
6411
+ side: ["textPath"],
6412
+ "white-space": ["svg", "g", ...textEls],
6413
+
6414
+ // actually nonsense but might be used for currentColor
6415
+ "color": ["svg", "g", ...textEls],
6416
+
6417
+ // animate
6418
+ playbackorder: ["svg"],
6419
+ timelinebegin: ["svg"],
6420
+
6421
+ dur: ["animate", "animateTransform", "animateMotion"],
6422
+ end: ["animate", "animateTransform", "animateMotion"],
6423
+ from: ["animate", "animateTransform", "animateMotion"],
6424
+ to: ["animate", "animateTransform", "animateMotion"],
6425
+ type: ["animateTransform"],
6426
+ values: ["animate", "animateTransform", "animateMotion"],
6427
+ accumulate: ["animate", "animateTransform", "animateMotion"],
6428
+ additive: ["animate", "animateTransform", "animateMotion"],
6429
+ attributeName: ["animate", "animateTransform"],
6430
+ begin: ["animate", "animateTransform", "animateMotion"],
6431
+ by: ["animate", "animateTransform", "animateMotion"],
6432
+ calcMode: ["animate", "animateTransform", "animateMotion"],
6433
+ keyPoints: ["animateMotion"],
6434
+ keySplines: ["animate", "animateTransform", "animateMotion"],
6435
+ keyTimes: ["animate", "animateTransform", "animateMotion"],
6436
+ max: ["animate", "animateTransform", "animateMotion"],
6437
+ min: ["animate", "animateTransform", "animateMotion"],
6438
+ origin: ["animateMotion"],
6439
+ repeatCount: ["animate", "animateTransform", "animateMotion"],
6440
+ repeatDur: ["animate", "animateTransform", "animateMotion"],
6441
+ restart: ["animate", "animateTransform", "animateMotion"],
6442
+
6443
+ // gradients
6444
+ gradientUnits: ["linearGradient", "radialGradient"],
6445
+ gradientTransform: ["linearGradient", "radialGradient"],
6446
+ fr: ["radialGradient"],
6447
+ fx: ["radialGradient"],
6448
+ fy: ["radialGradient"],
6449
+ offset: ["stop"],
6450
+ "stop-color": ["stop"],
6451
+ "stop-opacity": ["stop"],
6452
+ spreadMethod: ["linearGradient", "radialGradient"],
6453
+
6454
+ // object references
6455
+ href: [
6456
+ "pattern",
6457
+ "textPath",
6458
+ "linearGradient",
6459
+ "radialGradient",
6460
+ "use",
6461
+ "animate",
6462
+ "animateTransform",
6463
+ "animateMotion",
6464
+ "image"
6465
+ ],
6466
+
6467
+ pathLength: [
6468
+ ...geometryEls
6469
+ ],
6470
+
6471
+ },
6472
+
6473
+ defaults: {
6474
+
6475
+ transform: ["none", "matrix(1, 0, 0, 1, 0, 0)"],
6476
+ "transform-origin": ["0px, 0px", "0 0"],
6477
+ rx: ["0", "0px"],
6478
+ ry: ["0", "0px"],
6479
+ x: ["0", "0px"],
6480
+ y: ["0", "0px"],
6481
+
6482
+ fill: ["black", "rgb(0, 0, 0)", "rgba(0, 0, 0, 0)", "#000", "#000000"],
6483
+ "color": ["black", "rgb(0, 0, 0)", "rgba(0, 0, 0, 0)", "#000", "#000000"],
6484
+
6485
+ stroke: ["none"],
6486
+ "stroke-width": ["1", "1px"],
6487
+ opacity: ["1"],
6488
+ "fill-opacity": ["1"],
6489
+ "stroke-opacity": ["1"],
6490
+ "stroke-linecap": ["butt"],
6491
+ "stroke-miterlimit": ["4"],
6492
+ "stroke-linejoin": ["miter"],
6493
+ "stroke-dasharray": ["none"],
6494
+ "stroke-dashoffset": ["0", "0px", "none"],
6495
+ "pathLength": ["none"],
6496
+
6497
+ // text
6498
+ "font-family": ["serif"],
6499
+ "font-weight": ["normal", "400"],
6500
+ "font-stretch": ["normal"],
6501
+ "font-width": ["normal"],
6502
+ "letter-spacing": ["auto", "normal", "0"],
6503
+ "lengthAdjust": ["spacing"],
6504
+ "text-anchor": ["start"],
6505
+ "dominant-baseline": ["auto"],
6506
+ spacing: ["auto"],
6507
+ "white-space": ["normal"],
6508
+
6509
+ // gradients
6510
+ "stop-opacity": ["1"],
6511
+
6512
+ gradientUnits: ["objectBoundingBox"],
6513
+ patternUnits: ["objectBoundingBox"],
6514
+
6515
+ // clips and masks
6516
+ "clip-path": ["none"],
6517
+ "clip-rule": ["nonzero"],
6518
+ "fill-rule": ["nonzero"],
6519
+ clipPathUnits: ["userSpaceOnUse"],
6520
+
6521
+ mask: ["none"],
6522
+ maskUnits: ["objectBoundingBox"],
6523
+
6524
+ }
6525
+ };
6526
+
6527
+ function svgStylesToAttributes(el, {
6528
+ removeNameSpaced = true,
6529
+ decimals = -1
6530
+ } = {}) {
6531
+
6532
+ let nodeName = el.nodeName.toLowerCase();
6533
+ let attProps = getElAttributes(el);
6534
+ let cssProps = getElStyleProps(el);
6535
+
6536
+ // merge properties
6537
+ let props = {
6538
+ ...attProps,
6539
+ ...cssProps
6540
+ };
6541
+
6542
+ // filter out obsolete properties
6543
+ let propsFiltered = {};
6544
+
6545
+ // parse CSS transforms
6546
+ let cssTrans = cssProps['transform'];
6547
+
6548
+ if (cssTrans) {
6549
+ let transStr = `${cssTrans}`;
6550
+ let transformObj = parseCSSTransform(transStr);
6551
+ let matrix = getMatrix(transformObj);
6552
+
6553
+ // apply as SVG matrix transform
6554
+ props['transform'] = `matrix(${Object.values(matrix).join(',')})`;
6555
+ }
6556
+
6557
+ // can't be replaced with attributes
6558
+ let cssOnlyProps = ['inline-size'];
6559
+ let styleProps = [];
6560
+
6561
+ for (let prop in props) {
6562
+
6563
+ let value = props[prop];
6564
+
6565
+ // CSS variable
6566
+ if (value && prop.startsWith('--') || cssOnlyProps.includes(prop) ||
6567
+ (!removeNameSpaced && prop.startsWith('-'))) {
6568
+ styleProps.push(`${prop}:${value}`);
6569
+ continue
6570
+ }
6571
+
6572
+ // check if property is valid
6573
+ if (value && attLookup.atts[prop] &&
6574
+ (attLookup.atts[prop] === '*' ||
6575
+ attLookup.atts[prop].includes(nodeName) ||
6576
+ !removeNameSpaced && (prop.includes(':'))
6577
+ )
6578
+ ) {
6579
+ propsFiltered[prop] = value;
6580
+ }
6581
+
6582
+ // remove property
6583
+ el.removeAttribute(prop);
6584
+
6585
+ }
6586
+
6587
+ // apply filtered attributes
6588
+ for (let prop in propsFiltered) {
6589
+ let value = propsFiltered[prop];
6590
+ el.setAttribute(prop, value);
6591
+ }
6592
+
6593
+ if (styleProps.length) {
6594
+ el.setAttribute('style', styleProps.join(';'));
6595
+ }
6596
+
6597
+ return propsFiltered;
6598
+
6599
+ }
6600
+
6601
+ function parseInlineStyle(styleAtt = '') {
6602
+
6603
+ let props = {};
6604
+ if (!styleAtt) return props;
6605
+
6606
+ let styleArr = styleAtt.split(';').filter(Boolean).map(prop => prop.trim());
6607
+ let l = styleArr.length;
6608
+ if (!l) return props;
6609
+
6610
+ for (let i = 0; l && i < l; i++) {
6611
+ let style = styleArr[i];
6612
+ let [prop, value] = style.split(':').filter(Boolean);
6613
+ props[prop] = value;
6614
+
6615
+ }
6616
+
6617
+ return props
6618
+ }
6619
+
6620
+ function getElStyleProps(el) {
6621
+ let styleAtt = el.getAttribute('style');
6622
+ let props = styleAtt ? parseInlineStyle(styleAtt) : {};
6623
+ return props
6624
+ }
6625
+
6626
+ function getElAttributes(el) {
6627
+ let props = {};
6628
+ let atts = [...el.attributes].map((att) => att.name);
6629
+ let l = atts.length;
6630
+ if (!l) return props;
6631
+
6632
+ for (let i = 0; i < l; i++) {
6633
+ let att = atts[i];
6634
+ let value = el.getAttribute(att);
6635
+ props[att] = value;
6636
+ }
6637
+
6638
+ return props;
6639
+ }
6640
+
5997
6641
  function removeEmptySVGEls(svg) {
5998
6642
  let els = svg.querySelectorAll('g, defs');
5999
6643
  els.forEach(el => {
@@ -6005,50 +6649,167 @@ function cleanUpSVG(svgMarkup, {
6005
6649
  returnDom = false,
6006
6650
  removeHidden = true,
6007
6651
  removeUnused = true,
6652
+ stylesToAttributes = true,
6653
+ removePrologue = true,
6654
+ fixHref = true,
6655
+ mergePaths = false,
6656
+ cleanupSVGAtts = true,
6657
+ removeNameSpaced = true,
6658
+ attributesToGroup = true,
6659
+ decimals = -1,
6660
+ excludedEls = [],
6008
6661
  } = {}) {
6009
6662
 
6010
6663
  svgMarkup = cleanSvgPrologue(svgMarkup);
6011
6664
 
6012
6665
  // replace namespaced refs
6013
- svgMarkup = svgMarkup.replaceAll("xlink:href=", "href=");
6666
+ if (fixHref) svgMarkup = svgMarkup.replaceAll("xlink:href=", "href=");
6014
6667
 
6015
6668
  let svg = new DOMParser()
6016
6669
 
6017
- .parseFromString(svgMarkup, "text/html")
6018
- .querySelector("svg");
6670
+ .parseFromString(svgMarkup, "text/html")
6671
+ .querySelector("svg");
6672
+
6673
+ if (cleanupSVGAtts) {
6674
+ let allowed = ['viewBox', 'xmlns', 'width', 'height', 'id', 'class', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin'];
6675
+ removeExcludedAttribues(svg, allowed);
6019
6676
 
6020
- let allowed = ['viewBox', 'xmlns', 'width', 'height', 'id', 'class', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin'];
6021
- removeExcludedAttribues(svg, allowed);
6677
+ }
6022
6678
 
6023
- let removeEls = ['metadata', 'script'];
6679
+ // always remove scripts
6680
+ let removeEls = ['metadata', 'script', ...excludedEls];
6024
6681
 
6025
6682
  let els = svg.querySelectorAll('*');
6683
+ let elProps = [];
6684
+
6685
+ for (let i = 0; i < els.length; i++) {
6686
+ let el = els[i];
6687
+
6688
+ let name = el.nodeName.toLowerCase();
6026
6689
 
6027
- let textEls = svg.querySelectorAll('text');
6028
- let remove = !textEls.length ? ['font-family', 'font-weight', 'font-style', 'font-size'] : [];
6029
-
6030
- els.forEach(el => {
6031
- let name = el.nodeName;
6032
6690
  // remove hidden elements
6033
6691
  let style = el.getAttribute('style') || '';
6034
6692
  let isHiddenByStyle = style ? style.trim().includes('display:none') : false;
6035
6693
  let isHidden = (el.getAttribute('display') && el.getAttribute('display') === 'none') || isHiddenByStyle;
6036
6694
  if (name.includes(':') || removeEls.includes(name) || (removeHidden && isHidden)) {
6037
6695
  el.remove();
6038
- } else {
6039
- // remove BS elements
6040
- removeNameSpaceAtts(el);
6041
- removeAtts(el,remove);
6696
+ continue;
6042
6697
  }
6043
- });
6044
6698
 
6045
- if (returnDom) return svg
6699
+ // styles to attributes
6700
+ if (stylesToAttributes || attributesToGroup || mergePaths) {
6701
+ let propsFiltered = svgStylesToAttributes(el, { removeNameSpaced, decimals });
6702
+ if (name === 'path') {
6703
+ elProps.push({ el, name, idx: i, propsFiltered });
6704
+ }
6705
+ }
6706
+ }
6707
+
6708
+ // group styles
6046
6709
 
6710
+ if (attributesToGroup || mergePaths) {
6711
+ moveAttributesToGroup(elProps, mergePaths);
6712
+ }
6713
+
6714
+ if (returnDom) return svg
6047
6715
  let markup = stringifySVG(svg);
6048
6716
 
6049
6717
  return markup;
6050
6718
  }
6051
6719
 
6720
+ function moveAttributesToGroup(elProps = [], mergePaths = true) {
6721
+
6722
+ let combine = [[elProps[0]]];
6723
+ let idx = 0;
6724
+ let lastProps = '';
6725
+ for (let i = 0; i < elProps.length; i++) {
6726
+ let item = elProps[i];
6727
+ let props = item.propsFiltered;
6728
+ let propstr = [];
6729
+
6730
+ for (let prop in props) {
6731
+ if (prop !== 'd' && prop !== 'id') {
6732
+ propstr.push(`${prop}:${props[prop]}`);
6733
+ }
6734
+ }
6735
+ propstr = propstr.join('_');
6736
+ item.propstr = propstr;
6737
+
6738
+ if (propstr === lastProps) {
6739
+ combine[idx].push(item);
6740
+ } else {
6741
+ if (combine[idx].length) {
6742
+ combine.push([]);
6743
+ idx++;
6744
+ }
6745
+ }
6746
+ lastProps = propstr;
6747
+
6748
+ }
6749
+
6750
+ // add att groups
6751
+ for (let i = 0; i < combine.length; i++) {
6752
+ let group = combine[i];
6753
+
6754
+ if (group.length > 1) {
6755
+ // 1st el
6756
+ let el0 = group[0].el;
6757
+ let props = group[0].propsFiltered;
6758
+ let g = el0.parentNode.closest('g') ? el0.parentNode.closest('g') : null;
6759
+
6760
+ // wrap in group if not existent
6761
+ if (!g) {
6762
+ g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
6763
+ el0.parentNode.insertBefore(g, el0);
6764
+ group.forEach(item => {
6765
+ g.append(item.el);
6766
+ });
6767
+ }
6768
+
6769
+ let children = [...g.children];
6770
+ for (let prop in props) {
6771
+ if (prop !== 'd' && prop !== 'id') {
6772
+ let value = props[prop];
6773
+ // apply to parent group
6774
+ g.setAttribute(prop, value);
6775
+
6776
+ // remove from children
6777
+ children.forEach(el => {
6778
+ if (el.getAttribute(prop) === value) {
6779
+ el.removeAttribute(prop);
6780
+ }
6781
+ });
6782
+ }
6783
+
6784
+ if (mergePaths) {
6785
+ let path0 = group[0].el;
6786
+ let dCombined = group[0].propsFiltered.d;
6787
+
6788
+ for (let i = 1; i < group.length; i++) {
6789
+ let item = group[i];
6790
+ let path = item.el;
6791
+ let d = item.propsFiltered.d;
6792
+ let isAbs = d.startsWith('M');
6793
+
6794
+ let dAbs = isAbs ? d : parsePathDataString(d).pathData.map(com => `${com.type} ${com.values.join(' ')}`).join(' ');
6795
+
6796
+ // concat pathdata string
6797
+ dCombined += dAbs;
6798
+
6799
+ // delete path el
6800
+ path.remove();
6801
+ }
6802
+
6803
+ path0.setAttribute('d', dCombined);
6804
+
6805
+ }
6806
+
6807
+ }
6808
+ }
6809
+ }
6810
+
6811
+ }
6812
+
6052
6813
  function cleanSvgPrologue(svgString) {
6053
6814
  return (
6054
6815
  svgString
@@ -6072,24 +6833,6 @@ function removeExcludedAttribues(el, allowed = ['viewBox', 'xmlns', 'width', 'he
6072
6833
  });
6073
6834
  }
6074
6835
 
6075
- function removeAtts(el, remove=[]) {
6076
- let atts = [...el.attributes].map((att) => att.name);
6077
- atts.forEach((att) => {
6078
- if (remove.includes(att)) {
6079
- el.removeAttribute(att);
6080
- }
6081
- });
6082
- }
6083
-
6084
- function removeNameSpaceAtts(el) {
6085
- let atts = [...el.attributes].map((att) => att.name);
6086
- atts.forEach((att) => {
6087
- if (att.includes(":")) {
6088
- el.removeAttribute(att);
6089
- }
6090
- });
6091
- }
6092
-
6093
6836
  function stringifySVG(svg) {
6094
6837
  let markup = new XMLSerializer().serializeToString(svg);
6095
6838
  markup = markup
@@ -7618,6 +8361,12 @@ function svgPathSimplify(input = '', {
7618
8361
  reverse = false,
7619
8362
 
7620
8363
  // svg cleanup options
8364
+ cleanupSVGAtts=true,
8365
+ removePrologue = true,
8366
+ stylesToAttributes = true,
8367
+ fixHref = true,
8368
+ removeNameSpaced=true,
8369
+ attributesToGroup=false,
7621
8370
  mergePaths = false,
7622
8371
  removeHidden = true,
7623
8372
  removeUnused = true,
@@ -7686,7 +8435,7 @@ function svgPathSimplify(input = '', {
7686
8435
  else {
7687
8436
 
7688
8437
  let returnDom = true;
7689
- svg = cleanUpSVG(input, { returnDom, removeHidden, removeUnused }
8438
+ svg = cleanUpSVG(input, { cleanupSVGAtts, returnDom, removeHidden, removeUnused, removeNameSpaced, attributesToGroup, stylesToAttributes, removePrologue, fixHref ,mergePaths }
7690
8439
  );
7691
8440
 
7692
8441
  if (shapesToPaths) {
@@ -7973,6 +8722,7 @@ function svgPathSimplify(input = '', {
7973
8722
  }
7974
8723
 
7975
8724
  // collect for merged svg paths
8725
+ mergePaths= false;
7976
8726
  if (el && mergePaths) {
7977
8727
  pathData_merged.push(...pathData);
7978
8728
  }