svg-path-commander 2.1.1 → 2.1.2

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.
Files changed (56) hide show
  1. package/README.md +2 -2
  2. package/dist/svg-path-commander.cjs +1 -1
  3. package/dist/svg-path-commander.cjs.map +1 -1
  4. package/dist/svg-path-commander.d.ts +207 -19
  5. package/dist/svg-path-commander.js +1 -1
  6. package/dist/svg-path-commander.js.map +1 -1
  7. package/dist/svg-path-commander.mjs +1052 -969
  8. package/dist/svg-path-commander.mjs.map +1 -1
  9. package/package.json +7 -7
  10. package/src/convert/pathToAbsolute.ts +2 -31
  11. package/src/convert/pathToCurve.ts +13 -27
  12. package/src/convert/pathToRelative.ts +2 -47
  13. package/src/convert/pathToString.ts +40 -7
  14. package/src/index.ts +132 -39
  15. package/src/interface.ts +2 -1
  16. package/src/math/arcTools.ts +53 -51
  17. package/src/math/bezier.ts +41 -33
  18. package/src/math/cubicTools.ts +10 -8
  19. package/src/math/distanceSquareRoot.ts +1 -1
  20. package/src/math/lineTools.ts +6 -4
  21. package/src/math/polygonTools.ts +48 -0
  22. package/src/math/quadTools.ts +8 -6
  23. package/src/math/rotateVector.ts +3 -2
  24. package/src/math/roundTo.ts +7 -0
  25. package/src/parser/finalizeSegment.ts +11 -7
  26. package/src/parser/parsePathString.ts +2 -3
  27. package/src/parser/pathParser.ts +1 -1
  28. package/src/process/absolutizeSegment.ts +35 -30
  29. package/src/process/arcToCubic.ts +2 -2
  30. package/src/process/iterate.ts +41 -16
  31. package/src/process/lineToCubic.ts +1 -1
  32. package/src/process/normalizePath.ts +14 -31
  33. package/src/process/normalizeSegment.ts +53 -15
  34. package/src/process/optimizePath.ts +40 -60
  35. package/src/process/projection2d.ts +2 -2
  36. package/src/process/relativizeSegment.ts +33 -35
  37. package/src/process/reverseCurve.ts +8 -5
  38. package/src/process/reversePath.ts +87 -74
  39. package/src/process/roundPath.ts +14 -8
  40. package/src/process/roundSegment.ts +9 -0
  41. package/src/process/segmentToCubic.ts +9 -7
  42. package/src/process/shortenSegment.ts +24 -32
  43. package/src/process/splitCubic.ts +2 -2
  44. package/src/process/transformPath.ts +35 -40
  45. package/src/types.ts +7 -11
  46. package/src/util/getPathArea.ts +2 -2
  47. package/src/util/getPathBBox.ts +25 -42
  48. package/src/util/getPointAtLength.ts +51 -49
  49. package/src/util/getPropertiesAtLength.ts +4 -5
  50. package/src/util/getPropertiesAtPoint.ts +5 -5
  51. package/src/util/getTotalLength.ts +23 -38
  52. package/test/class.test.ts +2 -0
  53. package/test/fixtures/shapes.js +5 -5
  54. package/test/static.test.ts +18 -12
  55. package/src/math/polygonArea.ts +0 -29
  56. package/src/math/polygonLength.ts +0 -22
@@ -1,5 +1,5 @@
1
1
  import pathToCurve from '../convert/pathToCurve';
2
- import type { PointTuple, PathArray, QuadCoordinates } from '../types';
2
+ import type { PointTuple, PathArray } from '../types';
3
3
 
4
4
  /**
5
5
  * Returns the area of a single cubic-bezier segment.
@@ -60,7 +60,7 @@ const getPathArea = (path: PathArray) => {
60
60
  [, x, y] = seg;
61
61
  return 0;
62
62
  default:
63
- len = getCubicSegArea(x, y, ...(seg.slice(1) as QuadCoordinates));
63
+ len = getCubicSegArea(x, y, seg[1], seg[2], seg[3], seg[4], seg[5], seg[6]);
64
64
  [x, y] = seg.slice(-2) as PointTuple;
65
65
  return len;
66
66
  }
@@ -1,23 +1,13 @@
1
1
  import iterate from '../process/iterate';
2
2
  import { PathBBox } from '../interface';
3
- import {
4
- ArcCoordinates,
5
- CubicCoordinates,
6
- LineCoordinates,
7
- MSegment,
8
- PathArray,
9
- Point,
10
- PointTuple,
11
- QuadCoordinates,
12
- } from '../types';
13
- // import pathFactory from './pathFactory';
14
- import absolutizeSegment from '../process/absolutizeSegment';
15
- import normalizeSegment from '../process/normalizeSegment';
16
- import parsePathString from '../parser/parsePathString';
3
+ import { MSegment, PathArray, Point } from '../types';
17
4
  import { getLineBBox } from '../math/lineTools';
18
5
  import { getArcBBox } from '../math/arcTools';
19
6
  import { getCubicBBox } from '../math/cubicTools';
20
7
  import { getQuadBBox } from '../math/quadTools';
8
+ import parsePathString from '../parser/parsePathString';
9
+ import paramsParser from '../parser/paramsParser';
10
+ import normalizeSegment from '../process/normalizeSegment';
21
11
 
22
12
  const getPathBBox = (pathInput: PathArray | string) => {
23
13
  if (!pathInput) {
@@ -37,58 +27,51 @@ const getPathBBox = (pathInput: PathArray | string) => {
37
27
  const path = parsePathString(pathInput);
38
28
  let data = [] as number[];
39
29
  let pathCommand = 'M';
40
- let x = 0;
41
- let y = 0;
30
+ const x = 0;
31
+ const y = 0;
42
32
  let mx = 0;
43
33
  let my = 0;
44
34
  const MIN = [] as Point[];
45
35
  const MAX = [] as Point[];
46
36
  let min = { x, y };
47
37
  let max = { x, y };
38
+ const params = { ...paramsParser };
48
39
 
49
- iterate(path, (seg, params) => {
50
- const absoluteSegment = absolutizeSegment(seg, params);
51
- const normalSegment = normalizeSegment(absoluteSegment, params);
52
- [pathCommand] = normalSegment;
53
- data = [x, y, ...normalSegment.slice(1)] as number[];
40
+ iterate(path, (seg, _, lastX, lastY) => {
41
+ params.x = lastX;
42
+ params.y = lastY;
43
+ const result = normalizeSegment(seg, params);
44
+ [pathCommand] = result;
45
+ data = [lastX, lastY].concat(result.slice(1) as number[]);
54
46
 
55
47
  // this segment is always ZERO
56
48
  /* istanbul ignore else @preserve */
57
49
  if (pathCommand === 'M') {
58
50
  // remember mx, my for Z
59
- [, mx, my] = normalSegment as MSegment;
51
+ [, mx, my] = result as MSegment;
60
52
  min = { x: mx, y: my };
61
53
  max = { x: mx, y: my };
62
54
  } else if (pathCommand === 'L') {
63
- ({ min, max } = getLineBBox(...(data as LineCoordinates)));
55
+ ({ min, max } = getLineBBox(data[0], data[1], data[2], data[3]));
64
56
  } else if (pathCommand === 'A') {
65
- ({ min, max } = getArcBBox(...(data as ArcCoordinates)));
57
+ ({ min, max } = getArcBBox(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8]));
66
58
  } else if (pathCommand === 'C') {
67
- ({ min, max } = getCubicBBox(...(data as CubicCoordinates)));
59
+ ({ min, max } = getCubicBBox(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]));
68
60
  } else if (pathCommand === 'Q') {
69
- ({ min, max } = getQuadBBox(...(data as QuadCoordinates)));
61
+ ({ min, max } = getQuadBBox(data[0], data[1], data[2], data[3], data[4], data[5]));
70
62
  } else if (pathCommand === 'Z') {
71
- data = [x, y, mx, my];
72
- ({ min, max } = getLineBBox(...(data as LineCoordinates)));
63
+ data = [lastX, lastY, mx, my];
64
+ ({ min, max } = getLineBBox(data[0], data[1], data[2], data[3]));
73
65
  }
74
66
 
75
67
  MIN.push(min);
76
68
  MAX.push(max);
77
69
 
78
- if (pathCommand === 'Z') {
79
- x = mx;
80
- y = my;
81
- } else {
82
- [x, y] = normalSegment.slice(-2) as PointTuple;
83
-
84
- if (pathCommand === 'M') {
85
- mx = x;
86
- my = y;
87
- }
88
- }
89
- params.x = x;
90
- params.y = y;
91
- return normalSegment;
70
+ const seglen = result.length;
71
+ params.x1 = +result[seglen - 2];
72
+ params.y1 = +result[seglen - 1];
73
+ params.x2 = +result[seglen - 4] || params.x1;
74
+ params.y2 = +result[seglen - 3] || params.y1;
92
75
  });
93
76
 
94
77
  const xMin = Math.min(...MIN.map(n => n.x));
@@ -1,21 +1,11 @@
1
1
  import DISTANCE_EPSILON from './distanceEpsilon';
2
- import parsePathString from '../parser/parsePathString';
3
- import type {
4
- ArcCoordinates,
5
- CubicCoordinates,
6
- LineCoordinates,
7
- MSegment,
8
- PathArray,
9
- PointTuple,
10
- QuadCoordinates,
11
- } from '../types';
2
+ import type { MSegment, PathArray, PointTuple } from '../types';
12
3
  import iterate from '../process/iterate';
13
- import absolutizeSegment from '../process/absolutizeSegment';
14
- import normalizeSegment from '../process/normalizeSegment';
15
4
  import { getLineLength, getPointAtLineLength } from '../math/lineTools';
16
5
  import { getArcLength, getPointAtArcLength } from '../math/arcTools';
17
6
  import { getCubicLength, getPointAtCubicLength } from '../math/cubicTools';
18
7
  import { getQuadLength, getPointAtQuadLength } from '../math/quadTools';
8
+ import normalizePath from '../process/normalizePath';
19
9
 
20
10
  /**
21
11
  * Returns [x,y] coordinates of a point at a given length of a shape.
@@ -25,7 +15,7 @@ import { getQuadLength, getPointAtQuadLength } from '../math/quadTools';
25
15
  * @returns the requested {x, y} point coordinates
26
16
  */
27
17
  const getPointAtLength = (pathInput: string | PathArray, distance?: number) => {
28
- const path = parsePathString(pathInput);
18
+ const path = normalizePath(pathInput);
29
19
  let isM = false;
30
20
  let data = [] as number[];
31
21
  let pathCommand = 'M';
@@ -38,18 +28,13 @@ const getPointAtLength = (pathInput: string | PathArray, distance?: number) => {
38
28
  let POINT = point;
39
29
  let totalLength = 0;
40
30
 
41
- if (!distanceIsNumber) return point;
31
+ if (!distanceIsNumber || distance < DISTANCE_EPSILON) return point;
42
32
 
43
- if (distance < DISTANCE_EPSILON) {
44
- POINT = point;
45
- }
46
-
47
- iterate(path, (seg, params) => {
48
- const absoluteSegment = absolutizeSegment(seg, params);
49
- const normalSegment = normalizeSegment(absoluteSegment, params);
50
- [pathCommand] = normalSegment;
33
+ // for (let i = 0; i < pathLen; i += 1) {
34
+ iterate(path, (seg, _, lastX, lastY) => {
35
+ [pathCommand] = seg;
51
36
  isM = pathCommand === 'M';
52
- data = !isM ? [x, y, ...(normalSegment.slice(1) as number[])] : data;
37
+ data = !isM ? [lastX, lastY].concat(seg.slice(1) as number[]) : data;
53
38
 
54
39
  // this segment is always ZERO
55
40
  /* istanbul ignore else @preserve */
@@ -59,50 +44,67 @@ const getPointAtLength = (pathInput: string | PathArray, distance?: number) => {
59
44
  point = { x: mx, y: my };
60
45
  length = 0;
61
46
  } else if (pathCommand === 'L') {
62
- point = getPointAtLineLength(...(data as LineCoordinates), distance - totalLength);
63
- length = getLineLength(...(data as LineCoordinates));
47
+ point = getPointAtLineLength(data[0], data[1], data[2], data[3], distance - totalLength);
48
+ length = getLineLength(data[0], data[1], data[2], data[3]);
64
49
  } else if (pathCommand === 'A') {
65
- point = getPointAtArcLength(...(data as ArcCoordinates), distance - totalLength);
66
- length = getArcLength(...(data as ArcCoordinates));
50
+ point = getPointAtArcLength(
51
+ data[0],
52
+ data[1],
53
+ data[2],
54
+ data[3],
55
+ data[4],
56
+ data[5],
57
+ data[6],
58
+ data[7],
59
+ data[8],
60
+ distance - totalLength,
61
+ );
62
+ length = getArcLength(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8]);
67
63
  } else if (pathCommand === 'C') {
68
- point = getPointAtCubicLength(...(data as CubicCoordinates), distance - totalLength);
69
- length = getCubicLength(...(data as CubicCoordinates));
64
+ point = getPointAtCubicLength(
65
+ data[0],
66
+ data[1],
67
+ data[2],
68
+ data[3],
69
+ data[4],
70
+ data[5],
71
+ data[6],
72
+ data[7],
73
+ distance - totalLength,
74
+ );
75
+ length = getCubicLength(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
70
76
  } else if (pathCommand === 'Q') {
71
- point = getPointAtQuadLength(...(data as QuadCoordinates), distance - totalLength);
72
- length = getQuadLength(...(data as QuadCoordinates));
77
+ point = getPointAtQuadLength(data[0], data[1], data[2], data[3], data[4], data[5], distance - totalLength);
78
+ length = getQuadLength(data[0], data[1], data[2], data[3], data[4], data[5]);
73
79
  } else if (pathCommand === 'Z') {
74
- data = [x, y, mx, my];
80
+ data = [lastX, lastY, mx, my];
75
81
  point = { x: mx, y: my };
76
- length = getLineLength(...(data as LineCoordinates));
82
+
83
+ length = getLineLength(data[0], data[1], data[2], data[3]);
77
84
  }
78
85
 
79
- if (totalLength < distance && totalLength + length >= distance) {
86
+ [x, y] = data.slice(-2);
87
+
88
+ if (totalLength < distance) {
80
89
  POINT = point;
90
+ } else {
91
+ // totalLength >= distance
92
+ // stop right here
93
+ // stop iterator now!
94
+ return false;
81
95
  }
82
96
 
83
97
  totalLength += length;
84
- if (pathCommand === 'Z') {
85
- x = mx;
86
- y = my;
87
- } else {
88
- [x, y] = normalSegment.slice(-2) as PointTuple;
89
-
90
- if (pathCommand === 'M') {
91
- mx = x;
92
- my = y;
93
- }
94
- }
95
- params.x = x;
96
- params.y = y;
97
- return normalSegment;
98
+ return;
98
99
  });
99
100
 
100
101
  // native `getPointAtLength` behavior when the given distance
101
102
  // is higher than total length
102
103
  if (distance > totalLength - DISTANCE_EPSILON) {
103
- POINT = { x, y };
104
+ return { x, y };
104
105
  }
105
106
 
106
107
  return POINT;
107
108
  };
109
+
108
110
  export default getPointAtLength;
@@ -1,4 +1,4 @@
1
- import type { PointTuple, PathArray, PathSegment } from '../types';
1
+ import type { PathArray, PathSegment } from '../types';
2
2
  import type { SegmentProperties } from '../interface';
3
3
  import parsePathString from '../parser/parsePathString';
4
4
  import getTotalLength from './getTotalLength';
@@ -20,8 +20,6 @@ const getPropertiesAtLength = (pathInput: string | PathArray, distance?: number)
20
20
  let lengthAtSegment = 0;
21
21
  let length = 0;
22
22
  let segment = pathArray[0] as PathSegment;
23
- const [x, y] = segment.slice(-2) as PointTuple;
24
- const point = { x, y };
25
23
 
26
24
  // If the path is empty, return 0.
27
25
  if (index <= 0 || !distance || !Number.isFinite(distance)) {
@@ -29,7 +27,6 @@ const getPropertiesAtLength = (pathInput: string | PathArray, distance?: number)
29
27
  segment,
30
28
  index: 0,
31
29
  length,
32
- point,
33
30
  lengthAtSegment,
34
31
  };
35
32
  }
@@ -38,8 +35,9 @@ const getPropertiesAtLength = (pathInput: string | PathArray, distance?: number)
38
35
  pathTemp = pathArray.slice(0, -1) as PathArray;
39
36
  lengthAtSegment = getTotalLength(pathTemp);
40
37
  length = pathLength - lengthAtSegment;
38
+ segment = pathArray[index];
41
39
  return {
42
- segment: pathArray[index],
40
+ segment,
43
41
  index,
44
42
  length,
45
43
  lengthAtSegment,
@@ -53,6 +51,7 @@ const getPropertiesAtLength = (pathInput: string | PathArray, distance?: number)
53
51
  lengthAtSegment = getTotalLength(pathTemp);
54
52
  length = pathLength - lengthAtSegment;
55
53
  pathLength = lengthAtSegment;
54
+
56
55
  segments.push({
57
56
  segment,
58
57
  index,
@@ -1,4 +1,4 @@
1
- import type { PathArray } from '../types';
1
+ import type { PathArray, Point } from '../types';
2
2
  import type { PointProperties } from '../interface';
3
3
  import getPointAtLength from './getPointAtLength';
4
4
  import getPropertiesAtLength from './getPropertiesAtLength';
@@ -16,17 +16,17 @@ import normalizePath from '../process/normalizePath';
16
16
  * @param point the given point
17
17
  * @returns the requested properties
18
18
  */
19
- const getPropertiesAtPoint = (pathInput: string | PathArray, point: { x: number; y: number }): PointProperties => {
19
+ const getPropertiesAtPoint = (pathInput: string | PathArray, point: Point): PointProperties => {
20
20
  const path = parsePathString(pathInput);
21
21
  const normalPath = normalizePath(path);
22
- const pathLength = getTotalLength(path);
23
- const distanceTo = (p: { x: number; y: number }) => {
22
+ const pathLength = getTotalLength(normalPath);
23
+ const distanceTo = (p: Point) => {
24
24
  const dx = p.x - point.x;
25
25
  const dy = p.y - point.y;
26
26
  return dx * dx + dy * dy;
27
27
  };
28
28
  let precision = 8;
29
- let scan: { x: number; y: number };
29
+ let scan: Point;
30
30
  let closest = { x: 0, y: 0 }; // make TS happy
31
31
  let scanDistance = 0;
32
32
  let bestLength = 0;
@@ -1,21 +1,13 @@
1
- import parsePathString from '../parser/parsePathString';
2
- import type {
3
- ArcCoordinates,
4
- CubicCoordinates,
5
- LineCoordinates,
6
- MSegment,
7
- PathArray,
8
- PointTuple,
9
- QuadCoordinates,
10
- } from '../types';
1
+ import type { MSegment, PathArray } from '../types';
11
2
  import { getLineLength } from '../math/lineTools';
12
3
  import { getArcLength } from '../math/arcTools';
13
4
  import { getCubicLength } from '../math/cubicTools';
14
5
  import { getQuadLength } from '../math/quadTools';
15
6
  import iterate from '../process/iterate';
16
- import absolutizeSegment from '../process/absolutizeSegment';
7
+ // import normalizePath from '../process/normalizePath';
8
+ import parsePathString from '../parser/parsePathString';
9
+ import paramsParser from '../parser/paramsParser';
17
10
  import normalizeSegment from '../process/normalizeSegment';
18
- // import pathFactory from './pathFactory';
19
11
 
20
12
  /**
21
13
  * Returns the shape total length, or the equivalent to `shape.getTotalLength()`.
@@ -28,53 +20,46 @@ import normalizeSegment from '../process/normalizeSegment';
28
20
  */
29
21
  const getTotalLength = (pathInput: string | PathArray) => {
30
22
  const path = parsePathString(pathInput);
23
+ const params = { ...paramsParser };
24
+
31
25
  let isM = false;
32
26
  let data = [] as number[];
33
27
  let pathCommand = 'M';
34
- let x = 0;
35
- let y = 0;
36
28
  let mx = 0;
37
29
  let my = 0;
38
30
  let totalLength = 0;
39
31
 
40
- iterate(path, (seg, params) => {
41
- const absoluteSegment = absolutizeSegment(seg, params);
42
- const normalSegment = normalizeSegment(absoluteSegment, params);
32
+ iterate(path, (seg, _, lastX, lastY) => {
33
+ params.x = lastX;
34
+ params.y = lastY;
35
+ const normalSegment = normalizeSegment(seg, params);
43
36
  [pathCommand] = normalSegment;
44
37
  isM = pathCommand === 'M';
45
- data = !isM ? [x, y, ...(normalSegment.slice(1) as number[])] : data;
38
+ data = !isM ? [lastX, lastY].concat(normalSegment.slice(1) as number[]) : data;
46
39
 
47
40
  // this segment is always ZERO
48
41
  /* istanbul ignore else @preserve */
49
42
  if (isM) {
50
43
  // remember mx, my for Z
51
- [, mx, my] = seg as MSegment;
44
+ [, mx, my] = normalSegment as MSegment;
52
45
  } else if (pathCommand === 'L') {
53
- totalLength += getLineLength(...(data as LineCoordinates));
46
+ totalLength += getLineLength(data[0], data[1], data[2], data[3]);
54
47
  } else if (pathCommand === 'A') {
55
- totalLength += getArcLength(...(data as ArcCoordinates));
48
+ totalLength += getArcLength(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8]);
56
49
  } else if (pathCommand === 'C') {
57
- totalLength += getCubicLength(...(data as CubicCoordinates));
50
+ totalLength += getCubicLength(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
58
51
  } else if (pathCommand === 'Q') {
59
- totalLength += getQuadLength(...(data as QuadCoordinates));
52
+ totalLength += getQuadLength(data[0], data[1], data[2], data[3], data[4], data[5]);
60
53
  } else if (pathCommand === 'Z') {
61
- data = [x, y, mx, my];
62
- totalLength += getLineLength(...(data as LineCoordinates));
54
+ data = [lastX, lastY, mx, my];
55
+ totalLength += getLineLength(data[0], data[1], data[2], data[3]);
63
56
  }
64
- if (pathCommand === 'Z') {
65
- x = mx;
66
- y = my;
67
- } else {
68
- [x, y] = normalSegment.slice(-2) as PointTuple;
69
57
 
70
- if (isM) {
71
- mx = x;
72
- my = y;
73
- }
74
- }
75
- params.x = x;
76
- params.y = y;
77
- return normalSegment;
58
+ const seglen = normalSegment.length;
59
+ params.x1 = +normalSegment[seglen - 2];
60
+ params.y1 = +normalSegment[seglen - 1];
61
+ params.x2 = +normalSegment[seglen - 4] || params.x1;
62
+ params.y2 = +normalSegment[seglen - 3] || params.y1;
78
63
  });
79
64
 
80
65
  return totalLength;
@@ -283,6 +283,8 @@ a1.63 1.63 0 0 0 -0.906 0.274a1.63 1.63 0 0 0 -0.601 0.73a1.63 1.63 0 0 0 -0.094
283
283
 
284
284
  path.setAttribute('d', rect.toString());
285
285
  expect(path.getAttribute('d')).to.equal('M2 0A2 2 0 0 0 0 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2V2A2 2 0 0 0 14 0H2z');
286
+ // add at least one round: 'off' test
287
+ expect(new SVGPathCommander(simpleShapes.normalized[2], { round: 'off' }).optimize().toString()).to.equal(simpleShapes.initial[2]);
286
288
  });
287
289
 
288
290
  it('Test reverse single path', async () => {
@@ -20,12 +20,12 @@ const shapes = {
20
20
  "M8.66 8.66L8.66 4.66L7.34 4.66L7.34 8.66L8.65 8.66ZM8.66 11.34L8.66 10L7.34 10L7.34 11.34L8.65 11.34ZM8 1.34Q10.75 1.34 12.7 3.29Q14.65 5.24 14.66 8Q14.67 10.76 12.71 12.7Q10.75 14.64 8 14.66Q5.25 14.68 3.3 12.7Q1.35 10.72 1.34 8Q1.33 5.28 3.3 3.3Q5.27 1.32 8 1.34Z"
21
21
  ],
22
22
  optimized: [
23
- "M16 8C16 4.13 12.42 1 8 1S0 4.13 0 8c0 1.76 0.74 3.37 1.97 4.6c-0.1 1.02 -0.42 2.13 -0.77 2.97c-0.08 0.18 0.07 0.39 0.27 0.36c2.26 -0.37 3.6 -0.94 4.18 -1.24A9.06 9.06 0 0 0 8 15c4.42 0 8 -3.13 8 -7zM7.19 6.77c0.09 0.12 0.17 0.26 0.23 0.4c0.43 0.94 0.39 2.37 -0.94 3.7a0.45 0.45 0 0 1 -0.61 0.01A0.41 0.41 0 0 1 5.86 10.29C6.27 9.88 6.53 9.46 6.67 9.07C6.4 9.24 6.08 9.33 5.74 9.33C4.78 9.33 4 8.59 4 7.67S4.78 6 5.73 6c0.28 0 0.53 0.06 0.76 0.17l0.01 0c0.17 0.07 0.32 0.18 0.47 0.32c0.08 0.09 0.16 0.18 0.22 0.28zM11 9.07c-0.27 0.17 -0.59 0.26 -0.93 0.26C9.11 9.33 8.34 8.59 8.34 7.67S9.11 6 10.07 6c0.27 0 0.53 0.06 0.75 0.17l0.01 0C11 6.24 11.16 6.35 11.3 6.49c0.09 0.09 0.16 0.18 0.23 0.27c0.08 0.13 0.16 0.26 0.22 0.4c0.43 0.95 0.4 2.38 -0.94 3.71a0.45 0.45 0 0 1 -0.61 0.01a0.41 0.41 0 0 1 -0.01 -0.59C10.61 9.87 10.86 9.46 11 9.07z",
24
- "M16 14V5H0v9a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2zM2.56 12.33H1.85L3.75 7h0.69l1.9 5.33H5.62l-0.54 -1.6H3.1l-0.54 1.6zM4.1 7.81H4.08l-0.8 2.37H4.9L4.1 7.81zm5.75 0.42v4.1H9.18v-0.54H9.16C9.01 12.12 8.62 12.4 7.97 12.4c-0.85 0 -1.46 -0.49 -1.46 -1.43V8.23h0.68v2.55c0 0.77 0.44 1.01 0.98 1.01c0.59 0 1 -0.37 1 -1.02V8.23h0.68zm1.27 4.41c0.08 0.33 0.43 0.63 0.99 0.63c0.65 0 1.07 -0.37 1.07 -1.02v-0.6h-0.02C13 12 12.55 12.29 11.99 12.29c-0.96 0 -1.64 -0.67 -1.64 -1.9v-0.34c0 -1.21 0.67 -1.89 1.64 -1.89c0.56 0 1 0.3 1.19 0.64h0.02V8.23h0.65v4.03c0 1.05 -0.82 1.58 -1.75 1.58c-1.04 0 -1.57 -0.52 -1.66 -1.2h0.68zm2.06 -2.54c0 -0.83 -0.42 -1.36 -1.06 -1.36c-0.7 0 -1.1 0.49 -1.1 1.36v0.26c0 0.85 0.4 1.36 1.1 1.36c0.67 0 1.06 -0.52 1.06 -1.36V10.1zM4 0.5a0.5 0.5 0 0 0 -1 0V1H2A2 2 0 0 0 0 3v1h16V3A2 2 0 0 0 14 1h-1V0.5a0.5 0.5 0 0 0 -1 0V1H4V0.5z",
25
- "M5.5 0.5A0.5 0.5 0 0 1 6 0h4a0.5 0.5 0 0 1 0 1H9v1.07a7 7 0 0 1 3.54 12.26l0.81 0.82a0.5 0.5 0 0 1 -0.7 0.7l-0.93 -0.92A6.97 6.97 0 0 1 8 16A6.97 6.97 0 0 1 4.28 14.93l-0.93 0.92a0.5 0.5 0 0 1 -0.7 -0.7l0.81 -0.82A7 7 0 0 1 7 2.07V1H6A0.5 0.5 0 0 1 5.5 0.5zM0.86 5.39A2.5 2.5 0 1 1 4.39 1.86A8.04 8.04 0 0 0 0.86 5.39zM13.5 1c-0.75 0 -1.43 0.33 -1.89 0.86a8.04 8.04 0 0 1 3.53 3.53A2.5 2.5 0 0 0 13.5 1zm-5 4a0.5 0.5 0 0 0 -1 0v3.88l-1.45 2.9a0.5 0.5 0 1 0 0.9 0.44l1.5 -3A0.5 0.5 0 0 0 8.5 9V5z",
26
- "M2 0h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2H2A2 2 0 0 1 0 14V2A2 2 0 0 1 2 0zm3.35 4.65a0.5 0.5 0 1 0 -0.7 0.7L7.29 8l-2.64 2.65a0.5 0.5 0 0 0 0.7 0.7L8 8.71l2.65 2.64a0.5 0.5 0 0 0 0.7 -0.7L8.71 8l2.64 -2.65a0.5 0.5 0 0 0 -0.7 -0.7L8 7.29L5.35 4.65z",
23
+ "M16 8C16 4.13 12.42 1 8 1S0 4.13 0 8c0 1.76 0.74 3.37 1.97 4.6c-0.1 1.02 -0.42 2.13 -0.77 2.97c-0.08 0.19 0.07 0.39 0.27 0.36c2.26 -0.37 3.6 -0.94 4.18 -1.23A9.06 9.06 0 0 0 8 15c4.42 0 8 -3.13 8 -7zM7.19 6.77c0.09 0.12 0.16 0.26 0.23 0.4c0.43 0.95 0.39 2.38 -0.94 3.71a0.45 0.45 0 0 1 -0.61 0.01A0.41 0.41 0 0 1 5.86 10.29C6.27 9.88 6.53 9.46 6.67 9.07C6.4 9.24 6.08 9.33 5.74 9.33C4.78 9.33 4 8.59 4 7.67S4.78 6 5.73 6c0.27 0 0.53 0.06 0.76 0.17l0.01 0c0.17 0.07 0.33 0.18 0.47 0.32c0.08 0.08 0.16 0.17 0.23 0.27zM11 9.07c-0.27 0.16 -0.59 0.26 -0.93 0.26C9.11 9.33 8.34 8.59 8.34 7.67S9.11 6 10.07 6c0.27 0 0.53 0.06 0.76 0.17l0.01 0C11 6.24 11.16 6.35 11.3 6.49c0.09 0.08 0.16 0.17 0.23 0.27c0.09 0.12 0.16 0.26 0.23 0.4c0.43 0.95 0.39 2.38 -0.94 3.71a0.45 0.45 0 0 1 -0.61 0.01a0.41 0.41 0 0 1 -0.01 -0.59C10.61 9.87 10.86 9.46 11 9.07z",
24
+ "M16 14V5H0v9a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2zM2.56 12.33H1.85L3.75 7h0.7l1.9 5.33H5.62l-0.54 -1.6H3.1l-0.54 1.6zM4.1 7.81H4.08l-0.8 2.38H4.9L4.1 7.81zm5.75 0.42v4.11H9.18v-0.54H9.16C9.01 12.12 8.62 12.4 7.97 12.4c-0.85 0 -1.45 -0.48 -1.45 -1.43V8.23h0.68v2.55c0 0.77 0.44 1.01 0.98 1.01c0.59 0 1 -0.37 1 -1.02V8.23h0.68zm1.27 4.41c0.07 0.33 0.42 0.64 0.98 0.64c0.65 0 1.07 -0.38 1.07 -1.02v-0.61h-0.02C13 12 12.55 12.29 11.99 12.29c-0.96 0 -1.64 -0.67 -1.64 -1.9v-0.34c0 -1.21 0.68 -1.89 1.64 -1.89c0.56 0 1 0.29 1.2 0.64h0.02V8.23h0.65v4.03c0 1.05 -0.82 1.58 -1.75 1.58c-1.04 0 -1.57 -0.52 -1.67 -1.2h0.69zm2.06 -2.53c0 -0.83 -0.41 -1.36 -1.06 -1.36c-0.69 0 -1.1 0.49 -1.1 1.36v0.25c0 0.85 0.41 1.36 1.1 1.36c0.67 0 1.06 -0.52 1.06 -1.36V10.1zM4 0.5a0.5 0.5 0 0 0 -1 0V1H2A2 2 0 0 0 0 3v1h16V3A2 2 0 0 0 14 1h-1V0.5a0.5 0.5 0 0 0 -1 0V1H4V0.5z",
25
+ "M5.5 0.5A0.5 0.5 0 0 1 6 0h4a0.5 0.5 0 0 1 0 1H9v1.07a7 7 0 0 1 3.54 12.26l0.82 0.82a0.5 0.5 0 0 1 -0.71 0.71l-0.92 -0.93A6.97 6.97 0 0 1 8 16A6.97 6.97 0 0 1 4.28 14.93l-0.92 0.92A0.5 0.5 0 0 1 2.65 15.15l0.82 -0.82A7 7 0 0 1 7 2.07V1H6A0.5 0.5 0 0 1 5.5 0.5zM0.86 5.39A2.5 2.5 0 1 1 4.39 1.86A8.04 8.04 0 0 0 0.86 5.39zM13.5 1c-0.75 0 -1.43 0.33 -1.89 0.86a8.04 8.04 0 0 1 3.53 3.53A2.5 2.5 0 0 0 13.5 1zm-5 4a0.5 0.5 0 0 0 -1 0v3.88l-1.45 2.89a0.5 0.5 0 1 0 0.89 0.45l1.5 -3A0.5 0.5 0 0 0 8.5 9V5z",
26
+ "M2 0h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2H2A2 2 0 0 1 0 14V2A2 2 0 0 1 2 0zm3.35 4.65A0.5 0.5 0 1 0 4.65 5.35L7.29 8l-2.65 2.65a0.5 0.5 0 0 0 0.71 0.71L8 8.71l2.65 2.65a0.5 0.5 0 0 0 0.71 -0.71L8.71 8l2.65 -2.65A0.5 0.5 0 0 0 10.65 4.65L8 7.29L5.35 4.65z",
27
27
  "M2 4A2 2 0 0 0 0 6v6a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2V6A2 2 0 0 0 14 4h-1.17A2 2 0 0 1 11.41 3.41L10.59 2.59A2 2 0 0 0 9.17 2H6.83A2 2 0 0 0 5.41 2.59L4.59 3.41A2 2 0 0 1 3.17 4H2zm8.5 4.5a2.5 2.5 0 0 0 -5 0a2.5 2.5 0 1 0 5 0zM2.5 6a0.5 0.5 0 0 1 0 -1a0.5 0.5 0 1 1 0 1zm9 2.5a3.5 3.5 0 1 1 -7 0a3.5 3.5 0 0 1 7 0z",
28
- "M2 0A2 2 0 0 0 0 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2V2A2 2 0 0 0 14 0H2zm7.5 11h-4A0.5 0.5 0 0 1 5 10.5v-4a0.5 0.5 0 0 1 1 0v2.79l4.15 -4.14a0.5 0.5 0 0 1 0.7 0.7L6.71 10H9.5a0.5 0.5 0 0 1 0 1z",
28
+ "M2 0A2 2 0 0 0 0 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2V2A2 2 0 0 0 14 0H2zm7.5 11h-4A0.5 0.5 0 0 1 5 10.5v-4a0.5 0.5 0 0 1 1 0v2.79l4.15 -4.15a0.5 0.5 0 0 1 0.71 0.71L6.71 10H9.5a0.5 0.5 0 0 1 0 1z",
29
29
  "M15.98 8.51c-0.38 6.15 -7.27 9.57 -12.4 6.17C3.27 14.47 2.98 14.25 2.71 14.01l5.5 -5.5h7.78zM2 13.3L7.5 7.8V0.03C1.35 0.41 -2.07 7.3 1.33 12.43c0.2 0.3 0.42 0.59 0.67 0.87zM8.5 0.03c4.02 0.25 7.23 3.46 7.49 7.49H8.5V0.03z",
30
30
  "M8.66 8.66v-4H7.34v4h1.31zm0 2.68V10H7.34v1.34h1.31zM8 1.34q2.75 0 4.7 1.95T14.66 8t-1.95 4.7T8 14.66T3.3 12.7T1.34 8T3.3 3.3T8 1.34z"
31
31
  ],
@@ -1,5 +1,5 @@
1
1
  import { expect, it, describe, beforeEach, vi } from 'vitest';
2
- import SVGPathCommander, { type CurveArray, type ShapeTypes } from '~/index';
2
+ import SVGPathCommander, { PathArray, type CurveArray, type ShapeTypes } from '~/index';
3
3
  import invalidPathValue from '../src/parser/invalidPathValue';
4
4
  import error from '../src/parser/error';
5
5
 
@@ -115,11 +115,12 @@ describe('SVGPathCommander Static Methods', () => {
115
115
  });
116
116
  });
117
117
 
118
- it(`Can revert back to default round option`, () => {
119
- const sample = [["M", 0, 0], ["L", 181.99955, 0], ["L", 91, 72], ["L", 0, 0], ["Z"]];
120
- const rounded = [["M", 0, 0], ["L", 181.9996, 0], ["L", 91, 72], ["L", 0, 0], ["Z"]];
118
+ it(`Can disable round, use a given decimal amount or revert back to default round option`, () => {
119
+ const sample = [["M", 0, 0], ["L", 181.99955, 0], ["L", 91, 72], ["L", 0, 0], ["Z"]] as PathArray;
120
+ const rounded = [["M", 0, 0], ["L", 181.9996, 0], ["L", 91, 72], ["L", 0, 0], ["Z"]] as PathArray;
121
121
 
122
- // @ts-expect-error
122
+ expect(SVGPathCommander.roundPath(sample, 4), `can use number setting`).to.deep.equal(rounded);
123
+ expect(SVGPathCommander.roundPath(sample, 'off'), `can use "off" setting`).to.deep.equal(sample);
123
124
  expect(SVGPathCommander.roundPath(sample, -1), `use 4 decimals when negative number is provided`).to.deep.equal(rounded);
124
125
  // @ts-expect-error
125
126
  expect(SVGPathCommander.roundPath(sample, 'wombat'), `use 4 decimals when string is provided`).to.deep.equal(rounded);
@@ -198,7 +199,7 @@ describe('SVGPathCommander Static Methods', () => {
198
199
  const propsPoint0 = getPropertiesAtPoint(simpleShapes.initial[1], { "x": 10, "y": 90 });
199
200
  expect(propsPoint0.closest).to.deep.equal({ x: 10, y: 90 });
200
201
  expect(propsPoint0.distance).to.equal(0);
201
- expect(propsPoint0.segment).to.deep.equal({ segment: ["M", 10, 90], index: 0, length: 0, point: { x: 10, y: 90 }, lengthAtSegment: 0 })
202
+ expect(propsPoint0.segment).to.deep.equal({ segment: ["M", 10, 90], index: 0, length: 0, lengthAtSegment: 0 })
202
203
 
203
204
  // getPropertiesAtPoint mid point
204
205
  const propsPoint50 = getPropertiesAtPoint(simpleShapes.initial[1], { x: 30.072453006153214, y: 41.42818552481854 });
@@ -224,7 +225,7 @@ describe('SVGPathCommander Static Methods', () => {
224
225
  it(`Can do getSegmentOfPoint`, () => {
225
226
  const { getSegmentOfPoint } = SVGPathCommander;
226
227
  // first point
227
- expect(getSegmentOfPoint(simpleShapes.initial[1], { x: 10, y: 90 })).to.deep.equal({ segment: ["M", 10, 90], index: 0, length: 0, point: { x: 10, y: 90 }, lengthAtSegment: 0 });
228
+ expect(getSegmentOfPoint(simpleShapes.initial[1], { x: 10, y: 90 })).to.deep.equal({ segment: ["M", 10, 90], index: 0, length: 0, lengthAtSegment: 0 });
228
229
  // mid point
229
230
  expect(getSegmentOfPoint(simpleShapes.initial[3], { x: 9, y: 9 })).to.deep.equal({ segment: ["a", 6, 4, 10, 0, 1, 8, 0], index: 5, length: 7.498916687913066,/* point: { x: 6, y: 10 },*/ lengthAtSegment: 48.11479095890485 });
230
231
  });
@@ -267,13 +268,13 @@ describe('SVGPathCommander Static Methods', () => {
267
268
  });
268
269
 
269
270
  it(`Can do polygonLength`, () => {
270
- const { polygonLength } = SVGPathCommander;
271
- expect(polygonLength([[100, 100], [150, 25], [150, 75], [200, 0]])).to.equal(230.27756377319946);
271
+ const { polygonTools } = SVGPathCommander;
272
+ expect(polygonTools.polygonLength([[100, 100], [150, 25], [150, 75], [200, 0]])).to.equal(230.27756377319946);
272
273
  });
273
274
 
274
275
  it(`Can do polygonArea`, () => {
275
- const { polygonArea } = SVGPathCommander;
276
- expect(polygonArea([[107.4, 13], [113.7, 28.8], [127.9, 31.3], [117.6, 43.5], [120.1, 60.8], [107.4, 52.6], [94.6, 60.8], [97.1, 43.5], [86.8, 31.3], [101, 28.8]])).to.equal(-836.69);
276
+ const { polygonTools } = SVGPathCommander;
277
+ expect(polygonTools.polygonArea([[107.4, 13], [113.7, 28.8], [127.9, 31.3], [117.6, 43.5], [120.1, 60.8], [107.4, 52.6], [94.6, 60.8], [97.1, 43.5], [86.8, 31.3], [101, 28.8]])).to.equal(-836.69);
277
278
  });
278
279
 
279
280
  it(`Can do transformPath with empty object`, () => {
@@ -304,7 +305,7 @@ describe('SVGPathCommander Static Methods', () => {
304
305
  });
305
306
 
306
307
  it(`Can cover all remaining branches`, () => {
307
- const { splitPath, parsePathString, getPathBBox, getPointAtLength, getTotalLength } = SVGPathCommander;
308
+ const { splitPath, pathToString, optimizePath, parsePathString, getPathBBox, getPointAtLength, getTotalLength } = SVGPathCommander;
308
309
  expect(getPointAtLength(simpleShapes.normalized[3], 24.057395479452424)).to.deep.equal({ x: 14, y: 10 });
309
310
  expect(getPointAtLength(simpleShapes.normalized[0], 0)).to.deep.equal({ x: 10, y: 10 });
310
311
  expect(getPointAtLength(simpleShapes.normalized[3], undefined)).to.deep.equal({ x: 6, y: 10 });
@@ -320,5 +321,10 @@ describe('SVGPathCommander Static Methods', () => {
320
321
  "x": 10, "y": 10, "x2": 170,"y2": 90,
321
322
  });
322
323
  expect(splitPath(parsePathString(shapes.relative[1])).length).to.equal(7);
324
+ expect(pathToString(optimizePath(parsePathString(
325
+ // 'M10 50q15 -25 30 0Q55 75 70 50Q85 25 100 50T130 50Q145 25 160 50t30 0'
326
+ simpleShapes.normalized[2]
327
+ ), 2))).to.equal(simpleShapes.initial[2]);
328
+ // M10 50q15 -25 30 0t30 0t30 0t30 0t30 0t30 0
323
329
  });
324
330
  });
@@ -1,29 +0,0 @@
1
- import { type PointTuple } from '../types';
2
-
3
- /**
4
- * d3-polygon-area
5
- * https://github.com/d3/d3-polygon
6
- *
7
- * Returns the area of a polygon.
8
- *
9
- * @param polygon an array of coordinates
10
- * @returns the polygon area
11
- */
12
- const polygonArea = (polygon: PointTuple[]): number => {
13
- const n = polygon.length;
14
- let i = -1;
15
- let a;
16
- let b = polygon[n - 1];
17
- let area = 0;
18
-
19
- /* eslint-disable-next-line */
20
- while (++i < n) {
21
- a = b;
22
- b = polygon[i];
23
- area += a[1] * b[0] - a[0] * b[1];
24
- }
25
-
26
- return area / 2;
27
- };
28
-
29
- export default polygonArea;
@@ -1,22 +0,0 @@
1
- import { type PointTuple } from '../types';
2
- import distanceSquareRoot from './distanceSquareRoot';
3
-
4
- /**
5
- * d3-polygon-length
6
- * https://github.com/d3/d3-polygon
7
- *
8
- * Returns the perimeter of a polygon.
9
- *
10
- * @param polygon an array of coordinates
11
- * @returns {number} the polygon length
12
- */
13
- const polygonLength = (polygon: PointTuple[]): number => {
14
- return polygon.reduce((length, point, i) => {
15
- if (i) {
16
- return length + distanceSquareRoot(polygon[i - 1], point);
17
- }
18
- return 0;
19
- }, 0);
20
- };
21
-
22
- export default polygonLength;