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.
@@ -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,
@@ -4148,27 +4150,27 @@ function pathDataRemoveColinear(pathData, {
4148
4150
  let valsL = values.slice(-2);
4149
4151
  p = type !== 'Z' ? { x: valsL[0], y: valsL[1] } : M;
4150
4152
 
4151
- /*
4152
- let area = p1 ? getPolygonArea([p0, p, p1], true) : Infinity
4153
- let distSquare = getSquareDistance(p0, p1)
4154
- let distMax = distSquare ? distSquare / 333 * tolerance : 0
4155
- */
4153
+ let area = p1 ? getPolygonArea([p0, p, p1], true) : Infinity;
4154
+ let distSquare = getSquareDistance(p0, p1);
4155
+ let distMax = distSquare ? distSquare / 333 * tolerance : 0;
4156
+
4157
+ let isFlat = area < distMax;
4156
4158
 
4157
- let isFlat = false;
4158
4159
  let isFlatBez = false;
4159
4160
 
4161
+ /*
4160
4162
  // flatness by cross product
4161
- let dx0 = Math.abs(p1.x - p0.x);
4162
- let dy0 = Math.abs(p1.y - p0.y);
4163
+ let dx0 = Math.abs(p1.x - p0.x)
4164
+ let dy0 = Math.abs(p1.y - p0.y)
4163
4165
 
4164
- let dx1 = Math.abs(p.x - p0.x);
4165
- let dy1 = Math.abs(p.y - p0.y);
4166
+ let dx1 = Math.abs(p.x - p0.x)
4167
+ let dy1 = Math.abs(p.y - p0.y)
4166
4168
 
4167
- let dx2 = Math.abs(p1.x - p.x);
4168
- let dy2 = Math.abs(p1.y - p.y);
4169
+ let dx2 = Math.abs(p1.x - p.x)
4170
+ let dy2 = Math.abs(p1.y - p.y)
4169
4171
 
4170
4172
  // zero length segments are flat
4171
- let isZeroLength = (!dy1 && !dx1) || (!dy2 && !dx2);
4173
+ let isZeroLength = (!dy1 && !dx1) || (!dy2 && !dx2)
4172
4174
  if (isZeroLength) isFlat = true;
4173
4175
 
4174
4176
  // check cross products for colinearity
@@ -4176,13 +4178,14 @@ function pathDataRemoveColinear(pathData, {
4176
4178
 
4177
4179
  let cross0 = Math.abs(dx0 * dy1 - dy0 * dx1);
4178
4180
 
4179
- let thresh = (dx0 + dy0) * 0.1;
4181
+ let thresh = (dx0 + dy0) * 0.1
4180
4182
 
4181
4183
  if ( cross0 < thresh) {
4182
4184
 
4183
- isFlat = true;
4185
+ isFlat = true
4184
4186
  }
4185
4187
  }
4188
+ */
4186
4189
 
4187
4190
  if (!flatBezierToLinetos && type === 'C') isFlat = false;
4188
4191
 
@@ -5993,6 +5996,648 @@ function refineAdjacentExtremes(pathData, {
5993
5996
 
5994
5997
  }
5995
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
+
5996
6641
  function removeEmptySVGEls(svg) {
5997
6642
  let els = svg.querySelectorAll('g, defs');
5998
6643
  els.forEach(el => {
@@ -6004,46 +6649,167 @@ function cleanUpSVG(svgMarkup, {
6004
6649
  returnDom = false,
6005
6650
  removeHidden = true,
6006
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 = [],
6007
6661
  } = {}) {
6008
6662
 
6009
6663
  svgMarkup = cleanSvgPrologue(svgMarkup);
6010
6664
 
6011
6665
  // replace namespaced refs
6012
- svgMarkup = svgMarkup.replaceAll("xlink:href=", "href=");
6666
+ if (fixHref) svgMarkup = svgMarkup.replaceAll("xlink:href=", "href=");
6013
6667
 
6014
6668
  let svg = new DOMParser()
6015
6669
 
6016
- .parseFromString(svgMarkup, "text/html")
6017
- .querySelector("svg");
6670
+ .parseFromString(svgMarkup, "text/html")
6671
+ .querySelector("svg");
6018
6672
 
6019
- let allowed = ['viewBox', 'xmlns', 'width', 'height', 'id', 'class', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin'];
6020
- removeExcludedAttribues(svg, allowed);
6673
+ if (cleanupSVGAtts) {
6674
+ let allowed = ['viewBox', 'xmlns', 'width', 'height', 'id', 'class', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin'];
6675
+ removeExcludedAttribues(svg, allowed);
6021
6676
 
6022
- let removeEls = ['metadata', 'script'];
6677
+ }
6678
+
6679
+ // always remove scripts
6680
+ let removeEls = ['metadata', 'script', ...excludedEls];
6023
6681
 
6024
6682
  let els = svg.querySelectorAll('*');
6025
-
6026
- els.forEach(el => {
6027
- let name = el.nodeName;
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();
6689
+
6028
6690
  // remove hidden elements
6029
6691
  let style = el.getAttribute('style') || '';
6030
6692
  let isHiddenByStyle = style ? style.trim().includes('display:none') : false;
6031
6693
  let isHidden = (el.getAttribute('display') && el.getAttribute('display') === 'none') || isHiddenByStyle;
6032
6694
  if (name.includes(':') || removeEls.includes(name) || (removeHidden && isHidden)) {
6033
6695
  el.remove();
6034
- } else {
6035
- // remove BS elements
6036
- removeNameSpaceAtts(el);
6696
+ continue;
6037
6697
  }
6038
- });
6039
6698
 
6040
- 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
6709
+
6710
+ if (attributesToGroup || mergePaths) {
6711
+ moveAttributesToGroup(elProps, mergePaths);
6712
+ }
6041
6713
 
6714
+ if (returnDom) return svg
6042
6715
  let markup = stringifySVG(svg);
6043
6716
 
6044
6717
  return markup;
6045
6718
  }
6046
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
+
6047
6813
  function cleanSvgPrologue(svgString) {
6048
6814
  return (
6049
6815
  svgString
@@ -6067,15 +6833,6 @@ function removeExcludedAttribues(el, allowed = ['viewBox', 'xmlns', 'width', 'he
6067
6833
  });
6068
6834
  }
6069
6835
 
6070
- function removeNameSpaceAtts(el) {
6071
- let atts = [...el.attributes].map((att) => att.name);
6072
- atts.forEach((att) => {
6073
- if (att.includes(":")) {
6074
- el.removeAttribute(att);
6075
- }
6076
- });
6077
- }
6078
-
6079
6836
  function stringifySVG(svg) {
6080
6837
  let markup = new XMLSerializer().serializeToString(svg);
6081
6838
  markup = markup
@@ -7604,6 +8361,12 @@ function svgPathSimplify(input = '', {
7604
8361
  reverse = false,
7605
8362
 
7606
8363
  // svg cleanup options
8364
+ cleanupSVGAtts=true,
8365
+ removePrologue = true,
8366
+ stylesToAttributes = true,
8367
+ fixHref = true,
8368
+ removeNameSpaced=true,
8369
+ attributesToGroup=false,
7607
8370
  mergePaths = false,
7608
8371
  removeHidden = true,
7609
8372
  removeUnused = true,
@@ -7672,7 +8435,7 @@ function svgPathSimplify(input = '', {
7672
8435
  else {
7673
8436
 
7674
8437
  let returnDom = true;
7675
- svg = cleanUpSVG(input, { returnDom, removeHidden, removeUnused }
8438
+ svg = cleanUpSVG(input, { cleanupSVGAtts, returnDom, removeHidden, removeUnused, removeNameSpaced, attributesToGroup, stylesToAttributes, removePrologue, fixHref ,mergePaths }
7676
8439
  );
7677
8440
 
7678
8441
  if (shapesToPaths) {
@@ -7959,6 +8722,7 @@ function svgPathSimplify(input = '', {
7959
8722
  }
7960
8723
 
7961
8724
  // collect for merged svg paths
8725
+ mergePaths= false;
7962
8726
  if (el && mergePaths) {
7963
8727
  pathData_merged.push(...pathData);
7964
8728
  }