svg-path-simplify 0.4.4 → 0.4.5

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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.5] - 2026-04-02
4
+ ### Fixed
5
+ - stroke-dash conversion must disable reordering of commands
6
+ - stroke-dashoffset missing in attribute scaling
7
+ - removeOffCanvas - fixed bbox calculation
8
+ - rounding bug in command reordering
9
+ ### Added
10
+ - better auto accuracy approximation
11
+
12
+
3
13
  ## [0.4.4] - 2026-04-01
4
14
  ### Fixed
5
15
  - shape attribute retrieval in node.js
package/README.md CHANGED
@@ -15,7 +15,7 @@ While this library reduces SVG markup sizes significantly by removing commands i
15
15
  Unlike most existing approaches (e.g in graphic applications), it checks where simplifications are suitable and stops simplification at the right »point« (literally).
16
16
 
17
17
  ![simplification](./demo/img/splash.png)
18
- *Fira Sans (based on truetype/glyph quadratic commands) converted to cubic Béziers. Right:Original; Left:optimized*
18
+ *Fira Sans (based on truetype/glyph quadratic commands) converted to cubic Béziers. Left:Original; Right:optimized*
19
19
 
20
20
  ## Features
21
21
  ### Path simplification
@@ -1852,7 +1852,7 @@ function roundPathData(pathData, decimalsGlobal = -1) {
1852
1852
 
1853
1853
  let len = pathData.length;
1854
1854
  let decimals = decimalsGlobal;
1855
- let decimalsArc = decimals < 3 ? decimals+2 : decimals;
1855
+ let decimalsArc = decimals < 3 ? decimals + 2 : decimals;
1856
1856
 
1857
1857
  for (let c = 0; c < len; c++) {
1858
1858
  let com = pathData[c];
@@ -1912,23 +1912,47 @@ function detectAccuracy(pathData) {
1912
1912
 
1913
1913
  dimA = dimA ? dimA : getDistManhattan(p0, p);
1914
1914
 
1915
- if (dimA) dims.push(dimA);
1915
+ if (dimA) dims.push(+dimA.toFixed(8));
1916
1916
 
1917
1917
  }
1918
1918
 
1919
1919
  }
1920
1920
 
1921
- let dim_min = dims.sort();
1921
+ dims = dims.sort();
1922
+ let len = dims.length;
1923
+ let dim_mid = dims[Math.floor(len*0.5)];
1924
+
1925
+ // smallest 25% of values
1926
+ let idx_q = Math.ceil(len*0.25);
1927
+ let dims_min = dims.slice(0, idx_q);
1928
+
1929
+ // average smallest values with mid value
1930
+ let dim_min = ((dims_min.reduce((a, b) => a + b, 0) / idx_q) + dim_mid) * 0.5;
1931
+
1932
+ let threshold = 75;
1933
+ let decimalsAuto = dim_min > threshold * 1.5 ? 0 : Math.floor(threshold / dim_min).toString().length;
1934
+
1935
+ // clamp
1936
+ return Math.min(Math.max(0, decimalsAuto), 8)
1937
+
1938
+ /*
1939
+ let dim_min = dims.sort()
1922
1940
 
1923
- let sliceIdx = Math.ceil(dim_min.length / 6);
1941
+ let dim_mid = dim_min[Math.floor(dim_min.length*0.5)]
1942
+
1943
+ let sliceIdx = Math.ceil(dim_min.length / 4);
1924
1944
  dim_min = dim_min.slice(0, sliceIdx);
1925
1945
  let minVal = dim_min.reduce((a, b) => a + b, 0) / sliceIdx;
1926
1946
 
1927
- let threshold = 75;
1928
- let decimalsAuto = minVal > threshold * 1.5 ? 0 : Math.floor(threshold / minVal).toString().length;
1947
+ // average with mid value
1948
+ minVal = (minVal+dim_mid)*0.5
1949
+
1950
+ let threshold = 75
1951
+ let decimalsAuto = minVal > threshold * 1.5 ? 0 : Math.floor(threshold / minVal).toString().length
1929
1952
 
1930
1953
  // clamp
1931
1954
  return Math.min(Math.max(0, decimalsAuto), 8)
1955
+ */
1932
1956
 
1933
1957
  }
1934
1958
 
@@ -2494,7 +2518,7 @@ function getElementAtts(el, {x=0, y=0, width=0, height=0}={}){
2494
2518
  attributes.forEach(att=>{
2495
2519
 
2496
2520
  let value = normalizeUnits(el.getAttribute(att), {x, y, width, height});
2497
- atts[att.name] = value;
2521
+ atts[att] = value;
2498
2522
  });
2499
2523
 
2500
2524
  return atts
@@ -3239,7 +3263,7 @@ function simplifyPathDataCubic(pathData, {
3239
3263
  error += com.error;
3240
3264
 
3241
3265
  // find next candidates
3242
- for (let n = i + 1; error < tolerance && n < l; n++) {
3266
+ for (let n = i + offset; error < tolerance && n < l; n++) {
3243
3267
  let comN = pathData[n];
3244
3268
 
3245
3269
  if (comN.type !== 'C' ||
@@ -3249,6 +3273,7 @@ function simplifyPathDataCubic(pathData, {
3249
3273
  (keepExtremes && com.extreme)
3250
3274
  )
3251
3275
  ) {
3276
+
3252
3277
  break
3253
3278
  }
3254
3279
 
@@ -3256,6 +3281,7 @@ function simplifyPathDataCubic(pathData, {
3256
3281
 
3257
3282
  // failure - could not be combined - exit loop
3258
3283
  if (combined.length > 1) {
3284
+
3259
3285
  break
3260
3286
  }
3261
3287
 
@@ -3269,6 +3295,7 @@ function simplifyPathDataCubic(pathData, {
3269
3295
 
3270
3296
  // return combined
3271
3297
  com = combined[0];
3298
+
3272
3299
  }
3273
3300
 
3274
3301
  pathDataN.push(com);
@@ -3382,11 +3409,19 @@ function combineCubicPairs(com1, com2, {
3382
3409
 
3383
3410
  comS.dimA = getDistManhattan(comS.p0, comS.p);
3384
3411
  comS.type = 'C';
3412
+
3385
3413
  comS.extreme = com2.extreme;
3386
3414
  comS.directionChange = com2.directionChange;
3387
-
3388
3415
  comS.corner = com2.corner;
3389
3416
 
3417
+ if (comS.extreme || comS.corner) ;
3418
+
3419
+ /*
3420
+ comS.extreme = com1.extreme;
3421
+ comS.directionChange = com1.directionChange;
3422
+ comS.corner = com1.corner;
3423
+ */
3424
+
3390
3425
  comS.values = [comS.cp1.x, comS.cp1.y, comS.cp2.x, comS.cp2.y, comS.p.x, comS.p.y];
3391
3426
 
3392
3427
  // relative error
@@ -6555,7 +6590,8 @@ function pathDataToTopLeft(pathData) {
6555
6590
  let { type, values } = com;
6556
6591
  let valsLen = values.length;
6557
6592
  if (valsLen) {
6558
- let p = { type: type, x: values[valsLen - 2], y: values[valsLen - 1], index: 0 };
6593
+ // we need rounding otherwise sorting may crash due to e notation
6594
+ let p = { type: type, x: +values[valsLen - 2].toFixed(8), y: +values[valsLen - 1].toFixed(8), index: 0 };
6559
6595
  p.index = i;
6560
6596
  indices.push(p);
6561
6597
  }
@@ -6563,7 +6599,7 @@ function pathDataToTopLeft(pathData) {
6563
6599
 
6564
6600
  // reorder to top left most
6565
6601
 
6566
- indices = indices.sort((a, b) => +a.y.toFixed(8) - +b.y.toFixed(8) || a.x - b.x);
6602
+ indices = indices.sort((a, b) => a.y - b.y || a.x - b.x);
6567
6603
  newIndex = indices[0].index;
6568
6604
 
6569
6605
  return newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
@@ -7099,7 +7135,7 @@ function normalizePoly(pts, {
7099
7135
  } = {}) {
7100
7136
 
7101
7137
  // is stringified flat point attribute
7102
- if(typeof pts === 'string' && !isNaN(pts[0])){
7138
+ if (typeof pts === 'string' && !isNaN(pts[0])) {
7103
7139
  pts = toPointArray(pts.split(/,| /).filter(Boolean).map(Number));
7104
7140
  return pts
7105
7141
  }
@@ -7109,8 +7145,9 @@ function normalizePoly(pts, {
7109
7145
  return poly
7110
7146
  }
7111
7147
 
7112
- function polyArrayToObject(pts) {
7148
+ function polyArrayToObject(pts = []) {
7113
7149
 
7150
+ if (!pts.length) return [];
7114
7151
  // is point object array
7115
7152
  if (pts[0].x !== undefined && pts[0].y !== undefined) return pts
7116
7153
 
@@ -7128,7 +7165,7 @@ function polyArrayToObject(pts) {
7128
7165
  return poly
7129
7166
  }
7130
7167
 
7131
- else if(pts.length>3){
7168
+ else if (pts.length > 3) {
7132
7169
  pts = toPointArray(pts);
7133
7170
  return pts
7134
7171
  }
@@ -7157,13 +7194,13 @@ function polyPtsToArray(pts) {
7157
7194
  function toPointArray(pts) {
7158
7195
  let ptArr = [];
7159
7196
 
7160
- if(pts[0].length===2){
7161
- for (let i = 0, l = pts.length; i < l; i ++) {
7197
+ if (pts[0].length === 2) {
7198
+ for (let i = 0, l = pts.length; i < l; i++) {
7162
7199
  let pt = pts[i];
7163
- ptArr.push({ x: pt[0], y:pt[1] });
7200
+ ptArr.push({ x: pt[0], y: pt[1] });
7164
7201
  }
7165
7202
 
7166
- }else {
7203
+ } else {
7167
7204
  for (let i = 1, l = pts.length; i < l; i += 2) {
7168
7205
  ptArr.push({ x: pts[i - 1], y: pts[i] });
7169
7206
  }
@@ -8005,7 +8042,6 @@ function getLength(pts, {
8005
8042
 
8006
8043
  // LG weight/abscissae generator
8007
8044
  function getLegendreGaussValues(n, x1 = -1, x2 = 1) {
8008
- console.log('add new LG', n);
8009
8045
 
8010
8046
  let waArr = [];
8011
8047
  let z1, z, xm, xl, pp, p3, p2, p1;
@@ -8482,7 +8518,7 @@ function scaleProps(styleProps = {}, { props = [], scale = 1 } = {}, round = tru
8482
8518
  let prop = props[i];
8483
8519
 
8484
8520
  if (styleProps[prop] !== undefined) {
8485
- styleProps[prop] = styleProps[prop].map(val => round ? roundTo(val * scale, 2) : val * scale);
8521
+ styleProps[prop] = styleProps[prop].map(val => round ? roundTo(val * scale, 3) : val * scale);
8486
8522
  }
8487
8523
  }
8488
8524
  return styleProps
@@ -8503,6 +8539,7 @@ function convertPathLengthAtt(el, {
8503
8539
  });
8504
8540
 
8505
8541
  let scale = elLength / pathLength;
8542
+
8506
8543
  styleProps = scaleProps(styleProps, { props: ['stroke-dasharray', 'stroke-dashoffset'], scale });
8507
8544
 
8508
8545
  // set absolute
@@ -9031,9 +9068,13 @@ function cleanUpSVG(svgMarkup, {
9031
9068
  let stylePropsFiltered = {};
9032
9069
 
9033
9070
  // convert pathLength before transforming
9034
- if (convertPathLength) {
9071
+ if(convertTransforms || attributesToGroup) convertPathLength=true;
9072
+
9073
+ if (convertPathLength ) {
9074
+
9035
9075
  styleProps = convertPathLengthAtt(el, { styleProps });
9036
9076
  remove = [...new Set([...remove, ...styleProps.remove])];
9077
+
9037
9078
  }
9038
9079
 
9039
9080
  // get parent styles
@@ -9211,7 +9252,7 @@ function cleanUpSVG(svgMarkup, {
9211
9252
 
9212
9253
  // scale props like stroke width or dash-array before conversion
9213
9254
  if (matrix && transComponents) {
9214
- ['stroke-width', 'stroke-dasharray'].forEach(att => {
9255
+ ['stroke-width', 'stroke-dasharray', 'stroke-dashoffset'].forEach(att => {
9215
9256
  let attVal = el.getAttribute(att);
9216
9257
  let vals = attVal ? attVal.split(' ').filter(Boolean).map(Number).map(val => val * transComponents.scaleX) : [];
9217
9258
  if (vals.length) el.setAttribute(att, vals.join(' '));
@@ -12342,7 +12383,6 @@ const presetSettings = {
12342
12383
  addViewBox: true,
12343
12384
  removeDimensions: true,
12344
12385
  removeOffCanvas: true,
12345
-
12346
12386
  /*
12347
12387
  */
12348
12388
  }
@@ -12529,11 +12569,11 @@ function svgPathSimplify(input = '', settings = {}) {
12529
12569
  original: 0,
12530
12570
  new: 0,
12531
12571
  saved: 0,
12532
- svgSize:0,
12533
- svgSizeOpt:0,
12534
- compression:0,
12535
- decimals:0,
12536
- invalid:true
12572
+ svgSize: 0,
12573
+ svgSizeOpt: 0,
12574
+ compression: 0,
12575
+ decimals: 0,
12576
+ invalid: true
12537
12577
  };
12538
12578
 
12539
12579
  return { svg: dummySVG, d: '', polys: [], report, pathDataPlusArr: [], pathDataPlusArr_global: [], inputType: 'invalid', dOriginal: '' };
@@ -12703,6 +12743,12 @@ function svgPathSimplify(input = '', settings = {}) {
12703
12743
  let { d, el } = path;
12704
12744
  let isPoly = false;
12705
12745
 
12746
+ // disable reordering for elements with stroke dash-array
12747
+ if (el && (el.hasAttribute('stroke-dasharray') || el.hasAttribute('stroke-dashoffset'))) {
12748
+ optimizeOrder = false;
12749
+
12750
+ }
12751
+
12706
12752
  // if polygon we already heave absolute coordinates
12707
12753
 
12708
12754
  let pathData = parsePathDataNormalized(d, { quadraticToCubic, arcToCubic });