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