svg-path-simplify 0.0.5 → 0.0.7

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.
@@ -1,40 +1,15 @@
1
1
  'use strict';
2
2
 
3
- function renderPoint(
4
- svg,
5
- coords,
6
- fill = "red",
7
- r = "1%",
8
- opacity = "1",
9
- title = '',
10
- render = true,
11
- id = "",
12
- className = ""
13
- ) {
14
- if (Array.isArray(coords)) {
15
- coords = {
16
- x: coords[0],
17
- y: coords[1]
18
- };
19
- }
20
- let marker = `<circle class="${className}" opacity="${opacity}" id="${id}" cx="${coords.x}" cy="${coords.y}" r="${r}" fill="${fill}">
21
- <title>${title}</title></circle>`;
22
-
23
- if (render) {
24
- svg.insertAdjacentHTML("beforeend", marker);
25
- } else {
26
- return marker;
27
- }
28
- }
29
-
30
3
  function detectInputType(input) {
31
4
  let type = 'string';
5
+ /*
32
6
  if (input instanceof HTMLImageElement) return "img";
33
7
  if (input instanceof SVGElement) return "svg";
34
8
  if (input instanceof HTMLCanvasElement) return "canvas";
35
9
  if (input instanceof File) return "file";
36
10
  if (input instanceof ArrayBuffer) return "buffer";
37
11
  if (input instanceof Blob) return "blob";
12
+ */
38
13
  if (Array.isArray(input)) return "array";
39
14
 
40
15
  if (typeof input === "string") {
@@ -884,6 +859,7 @@ function addExtemesToCommand(p0, values, tMin=0, tMax=1) {
884
859
 
885
860
  if(tArr.length){
886
861
  let commandsSplit = splitCommandAtTValues(p0, values, tArr);
862
+
887
863
  pathDataNew.push(...commandsSplit);
888
864
  extremeCount += commandsSplit.length;
889
865
  }else {
@@ -1537,6 +1513,7 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1) {
1537
1513
  let dP = cubicDerivative(com2.p0, com2.cp1, com2.cp2, com2.p, t0);
1538
1514
  let r = sub(P, com1.p0);
1539
1515
 
1516
+
1540
1517
  t0 -= dot(r, dP) / dot(dP, dP);
1541
1518
 
1542
1519
  // construct merged cubic over [t0, 1]
@@ -1568,7 +1545,9 @@ function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1) {
1568
1545
  };
1569
1546
  }
1570
1547
 
1571
- let ptM = pointAtT([result.p0, result.cp1, result.cp2, result.p], 0.5, false, true);
1548
+ let tMid = (1 - t0)*0.5 ;
1549
+
1550
+ let ptM = pointAtT([result.p0, result.cp1, result.cp2, result.p], tMid, false, true);
1572
1551
  let seg1_cp2 = ptM.cpts[2];
1573
1552
 
1574
1553
  let ptI_1 = checkLineIntersection(ptM, seg1_cp2, result.p0, ptI, false);
@@ -2115,17 +2094,21 @@ function revertCubicQuadratic(p0 = {}, cp1 = {}, cp2 = {}, p = {}) {
2115
2094
  let cp1_Q = null;
2116
2095
  let type = 'C';
2117
2096
  let values = [cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y];
2097
+ let comN = {type, values};
2118
2098
 
2119
2099
  if (dist1 < threshold) {
2120
2100
  cp1_Q = checkLineIntersection(p0, cp1, p, cp2, false);
2121
2101
  if (cp1_Q) {
2122
2102
 
2123
- type = 'Q';
2124
- values = [cp1_Q.x, cp1_Q.y, p.x, p.y];
2103
+ comN.type = 'Q';
2104
+ comN.values = [cp1_Q.x, cp1_Q.y, p.x, p.y];
2105
+ comN.p0 = p0;
2106
+ comN.cp1 = cp1_Q;
2107
+ comN.p = p;
2125
2108
  }
2126
2109
  }
2127
2110
 
2128
- return { type, values }
2111
+ return comN
2129
2112
 
2130
2113
  }
2131
2114
 
@@ -3502,8 +3485,11 @@ function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLinetos = t
3502
3485
 
3503
3486
  let area = getPolygonArea([p0, p, p1], true);
3504
3487
 
3488
+ getSquareDistance(p0, p);
3489
+ getSquareDistance(p, p1);
3505
3490
  let distSquare = getSquareDistance(p0, p1);
3506
- let distMax = distSquare / 100 * tolerance;
3491
+
3492
+ let distMax = distSquare / 200 * tolerance;
3507
3493
 
3508
3494
  let isFlat = area < distMax;
3509
3495
  let isFlatBez = false;
@@ -3533,7 +3519,10 @@ function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLinetos = t
3533
3519
  p0 = p;
3534
3520
 
3535
3521
  // colinear – exclude arcs (as always =) as semicircles won't have an area
3536
- if ( isFlat && c < l - 1 && (type === 'L' || (flatBezierToLinetos && isFlatBez))) {
3522
+
3523
+ if ( isFlat && c < l - 1 && (type === 'L' || (flatBezierToLinetos && isFlatBez)) ) {
3524
+
3525
+
3537
3526
 
3538
3527
  continue;
3539
3528
  }
@@ -3556,6 +3545,21 @@ function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLinetos = t
3556
3545
 
3557
3546
  }
3558
3547
 
3548
+ // remove zero-length segments introduced by rounding
3549
+ function removeZeroLengthLinetos_post(pathData) {
3550
+ let pathDataOpt = [];
3551
+ pathData.forEach((com, i) => {
3552
+ let { type, values } = com;
3553
+ if (type === 'l' || type === 'v' || type === 'h') {
3554
+ let hasLength = type === 'l' ? (values.join('') !== '00') : values[0] !== 0;
3555
+ if (hasLength) pathDataOpt.push(com);
3556
+ } else {
3557
+ pathDataOpt.push(com);
3558
+ }
3559
+ });
3560
+ return pathDataOpt
3561
+ }
3562
+
3559
3563
  function removeZeroLengthLinetos(pathData) {
3560
3564
 
3561
3565
  let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
@@ -3914,6 +3918,13 @@ function reversePathData(pathData, {
3914
3918
  return pathDataNew;
3915
3919
  }
3916
3920
 
3921
+ function removeEmptySVGEls(svg) {
3922
+ let els = svg.querySelectorAll('g, defs');
3923
+ els.forEach(el => {
3924
+ if (!el.children.length) el.remove();
3925
+ });
3926
+ }
3927
+
3917
3928
  function cleanUpSVG(svgMarkup, {
3918
3929
  returnDom=false,
3919
3930
  removeHidden=true,
@@ -4001,10 +4012,13 @@ function stringifySVG(svg){
4001
4012
  }
4002
4013
 
4003
4014
  function svgPathSimplify(input = '', {
4015
+
4016
+ // return svg markup or object
4017
+ getObject = false,
4018
+
4004
4019
  toAbsolute = true,
4005
4020
  toRelative = true,
4006
4021
  toShorthands = true,
4007
- decimals = 3,
4008
4022
 
4009
4023
  // not necessary unless you need cubics only
4010
4024
  quadraticToCubic = true,
@@ -4013,30 +4027,31 @@ function svgPathSimplify(input = '', {
4013
4027
  arcToCubic = false,
4014
4028
  cubicToArc = false,
4015
4029
 
4016
- // arc to cubic precision - adds more segments for better precision
4017
- arcAccuracy = 4,
4018
- keepExtremes = true,
4019
- keepCorners = true,
4020
- keepInflections = true,
4021
- extrapolateDominant = false,
4022
- addExtremes = false,
4030
+ simplifyBezier = true,
4023
4031
  optimizeOrder = true,
4024
4032
  removeColinear = true,
4025
- simplifyBezier = true,
4026
- autoAccuracy = true,
4027
4033
  flatBezierToLinetos = true,
4028
4034
  revertToQuadratics = true,
4035
+
4036
+ keepExtremes = true,
4037
+ keepCorners = true,
4038
+ extrapolateDominant = true,
4039
+ keepInflections = false,
4040
+ addExtremes = false,
4041
+
4042
+ // svg path optimizations
4043
+ decimals = 3,
4044
+ autoAccuracy = true,
4045
+
4029
4046
  minifyD = 0,
4030
4047
  tolerance = 1,
4031
4048
  reverse = false,
4032
4049
 
4033
4050
  // svg cleanup options
4051
+ mergePaths = false,
4034
4052
  removeHidden = true,
4035
4053
  removeUnused = true,
4036
4054
 
4037
- // return svg markup or object
4038
- getObject = false
4039
-
4040
4055
  } = {}) {
4041
4056
 
4042
4057
  // clamp tolerance
@@ -4088,6 +4103,17 @@ function svgPathSimplify(input = '', {
4088
4103
  /**
4089
4104
  * process all paths
4090
4105
  */
4106
+
4107
+ // SVG optimization options
4108
+ let pathOptions = {
4109
+ toRelative,
4110
+ toShorthands,
4111
+ decimals,
4112
+ };
4113
+
4114
+ // combinded path data for SVGs with mergePaths enabled
4115
+ let pathData_merged = [];
4116
+
4091
4117
  paths.forEach(path => {
4092
4118
  let { d, el } = path;
4093
4119
 
@@ -4134,7 +4160,7 @@ function svgPathSimplify(input = '', {
4134
4160
  // simplify beziers
4135
4161
  let { pathData, bb, dimA } = pathDataPlus;
4136
4162
 
4137
- pathData = simplifyBezier ? simplifyPathData(pathData, { simplifyBezier, keepInflections, keepExtremes, keepCorners, extrapolateDominant, revertToQuadratics, tolerance, reverse }) : pathData;
4163
+ pathData = simplifyBezier ? simplifyPathDataCubic(pathData, { simplifyBezier, keepInflections, keepExtremes, keepCorners, extrapolateDominant, revertToQuadratics, tolerance, reverse }) : pathData;
4138
4164
 
4139
4165
  // cubic to arcs
4140
4166
  if (cubicToArc) {
@@ -4163,80 +4189,104 @@ function svgPathSimplify(input = '', {
4163
4189
  if (type === 'C') {
4164
4190
 
4165
4191
  let comQ = revertCubicQuadratic(p0, cp1, cp2, p);
4166
- if (comQ.type === 'Q') pathData[c] = comQ;
4192
+ if (comQ.type === 'Q') {
4193
+ /*
4194
+ comQ.p0 = com.p0
4195
+ comQ.cp1 = {x:comQ.values[0], y:comQ.values[1]}
4196
+ comQ.p = com.p
4197
+ */
4198
+ comQ.extreme = com.extreme;
4199
+ comQ.corner = com.corner;
4200
+ comQ.dimA = com.dimA;
4201
+
4202
+ pathData[c] = comQ;
4203
+ }
4167
4204
  }
4168
4205
  });
4169
4206
  }
4170
4207
 
4171
4208
  // optimize close path
4172
- if(optimizeOrder) pathData=optimizeClosePath(pathData);
4209
+ if (optimizeOrder) pathData = optimizeClosePath(pathData);
4210
+
4211
+ // poly
4173
4212
 
4174
4213
  // update
4175
4214
  pathDataArrN.push(pathData);
4176
4215
  }
4177
4216
 
4178
-
4179
4217
  // flatten compound paths
4180
4218
  pathData = pathDataArrN.flat();
4181
4219
 
4182
- /**
4183
- * detect accuracy
4184
- */
4185
- if (autoAccuracy) {
4186
- decimals = detectAccuracy(pathData);
4220
+ // collect for merged svg paths
4221
+ if (el && mergePaths) {
4222
+ pathData_merged.push(...pathData);
4187
4223
  }
4224
+ // single output
4225
+ else {
4188
4226
 
4189
- // optimize
4190
- let pathOptions = {
4191
- toRelative,
4192
- toShorthands,
4193
- decimals,
4194
- };
4227
+ /**
4228
+ * detect accuracy
4229
+ */
4230
+ if (autoAccuracy) {
4231
+ decimals = detectAccuracy(pathData);
4232
+ }
4195
4233
 
4196
- // optimize path data
4197
- pathData = convertPathData(pathData, pathOptions);
4234
+ // optimize path data
4235
+ pathData = convertPathData(pathData, pathOptions);
4198
4236
 
4199
- // remove zero-length segments introduced by rounding
4200
- let pathDataOpt = [];
4237
+ // remove zero-length segments introduced by rounding
4238
+ pathData = removeZeroLengthLinetos_post(pathData);
4201
4239
 
4202
- pathData.forEach((com, i) => {
4203
- let { type, values } = com;
4204
- if (type === 'l' || type === 'v' || type === 'h') {
4205
- let hasLength = type === 'l' ? (values.join('') !== '00') : values[0] !== 0;
4206
- if (hasLength) pathDataOpt.push(com);
4207
- } else {
4208
- pathDataOpt.push(com);
4209
- }
4210
- });
4240
+ // compare command count
4241
+ let comCountS = pathData.length;
4211
4242
 
4212
- pathData = pathDataOpt;
4243
+ let dOpt = pathDataToD(pathData, minifyD);
4244
+ svgSizeOpt = new Blob([dOpt]).size;
4245
+ compression = +(100 / svgSize * (svgSizeOpt)).toFixed(2);
4213
4246
 
4214
- // compare command count
4215
- let comCountS = pathData.length;
4247
+ path.d = dOpt;
4248
+ path.report = {
4249
+ original: comCount,
4250
+ new: comCountS,
4251
+ saved: comCount - comCountS,
4252
+ compression,
4253
+ decimals,
4216
4254
 
4217
- let dOpt = pathDataToD(pathData, minifyD);
4218
- svgSizeOpt = new Blob([dOpt]).size;
4255
+ };
4219
4256
 
4220
- compression = +(100 / svgSize * (svgSizeOpt)).toFixed(2);
4257
+ // apply new path for svgs
4258
+ if (el) el.setAttribute('d', dOpt);
4259
+ }
4260
+ });
4221
4261
 
4222
- path.d = dOpt;
4223
- path.report = {
4224
- original: comCount,
4225
- new: comCountS,
4226
- saved: comCount - comCountS,
4227
- compression,
4228
- decimals,
4262
+ /**
4263
+ * stringify new SVG
4264
+ */
4265
+ if (mode) {
4229
4266
 
4230
- };
4267
+ if (pathData_merged.length) {
4268
+ // optimize path data
4269
+ let pathData = convertPathData(pathData_merged, pathOptions);
4231
4270
 
4232
- // apply new path for svgs
4233
- if (el) el.setAttribute('d', dOpt);
4271
+ // remove zero-length segments introduced by rounding
4272
+ pathData = removeZeroLengthLinetos_post(pathData);
4234
4273
 
4235
- });
4274
+ let dOpt = pathDataToD(pathData, minifyD);
4236
4275
 
4237
- // stringify new SVG
4238
- if (mode) {
4239
- svg = new XMLSerializer().serializeToString(svg);
4276
+ // apply new path for svgs
4277
+ paths[0].el.setAttribute('d', dOpt);
4278
+
4279
+ // remove other paths
4280
+ for (let i = 1; i < paths.length; i++) {
4281
+ let pathEl = paths[i].el;
4282
+ if (pathEl) pathEl.remove();
4283
+ }
4284
+
4285
+ // remove empty groups e.g groups
4286
+ removeEmptySVGEls(svg);
4287
+ }
4288
+
4289
+ svg = stringifySVG(svg);
4240
4290
  svgSizeOpt = new Blob([svg]).size;
4241
4291
 
4242
4292
  compression = +(100 / svgSize * (svgSizeOpt)).toFixed(2);
@@ -4258,7 +4308,7 @@ function svgPathSimplify(input = '', {
4258
4308
 
4259
4309
  }
4260
4310
 
4261
- function simplifyPathData(pathData, {
4311
+ function simplifyPathDataCubic(pathData, {
4262
4312
  keepExtremes = true,
4263
4313
  keepInflections = true,
4264
4314
  keepCorners = true,
@@ -4302,6 +4352,8 @@ function simplifyPathData(pathData, {
4302
4352
  if (combined.length === 1) {
4303
4353
  com = combined[0];
4304
4354
  let offset = 1;
4355
+
4356
+ // add cumulative error to prevent distortions
4305
4357
  error += com.error;
4306
4358
 
4307
4359
  // find next candidates
@@ -4319,6 +4371,9 @@ function simplifyPathData(pathData, {
4319
4371
 
4320
4372
  let combined = combineCubicPairs(com, comN, extrapolateDominant, tolerance);
4321
4373
  if (combined.length === 1) {
4374
+ // add cumulative error to prevent distortions
4375
+
4376
+ error += combined[0].error * 0.5;
4322
4377
  offset++;
4323
4378
  }
4324
4379
  com = combined[0];
@@ -4350,39 +4405,6 @@ function simplifyPathData(pathData, {
4350
4405
  return pathDataN
4351
4406
  }
4352
4407
 
4353
- /**
4354
- * get viewBox
4355
- * either from explicit attribute or
4356
- * width and height attributes
4357
- */
4358
-
4359
- function getViewBox(svg = null, round = false) {
4360
-
4361
- // browser default
4362
- if (!svg) return { x: 0, y: 0, width: 300, height: 150 }
4363
-
4364
- let style = window.getComputedStyle(svg);
4365
-
4366
- // the baseVal API method also converts physical units to pixels/user-units
4367
- let w = svg.hasAttribute('width') ? svg.width.baseVal.value : parseFloat(style.width) || 300;
4368
- let h = svg.hasAttribute('height') ? svg.height.baseVal.value : parseFloat(style.height) || 150;
4369
-
4370
- let viewBox = svg.getAttribute('viewBox') ? svg.viewBox.baseVal : { x: 0, y: 0, width: w, height: h };
4371
-
4372
- // remove SVG constructor
4373
- let { x, y, width, height } = viewBox;
4374
- viewBox = { x, y, width, height };
4375
-
4376
- // round to integers
4377
- if (round) {
4378
- for (let prop in viewBox) {
4379
- viewBox[prop] = Math.ceil(viewBox[prop]);
4380
- }
4381
- }
4382
-
4383
- return viewBox
4384
- }
4385
-
4386
4408
  const {
4387
4409
  abs, acos, asin, atan, atan2, ceil, cos, exp, floor,
4388
4410
  log, hypot, max, min, pow, random, round, sin, sqrt, tan, PI
@@ -4393,8 +4415,7 @@ const {
4393
4415
  // IIFE
4394
4416
  if (typeof window !== 'undefined') {
4395
4417
  window.svgPathSimplify = svgPathSimplify;
4396
- window.getViewBox = getViewBox;
4397
- window.renderPoint = renderPoint;
4418
+
4398
4419
  }
4399
4420
 
4400
4421
  exports.PI = PI;
@@ -4407,7 +4428,6 @@ exports.ceil = ceil;
4407
4428
  exports.cos = cos;
4408
4429
  exports.exp = exp;
4409
4430
  exports.floor = floor;
4410
- exports.getViewBox = getViewBox;
4411
4431
  exports.hypot = hypot;
4412
4432
  exports.log = log;
4413
4433
  exports.max = max;