svg-path-commander 2.1.0 → 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 (63) hide show
  1. package/README.md +63 -7
  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 +314 -40
  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 +1085 -1032
  8. package/dist/svg-path-commander.mjs.map +1 -1
  9. package/package.json +8 -8
  10. package/src/convert/pathToAbsolute.ts +5 -88
  11. package/src/convert/pathToCurve.ts +22 -28
  12. package/src/convert/pathToRelative.ts +4 -78
  13. package/src/convert/pathToString.ts +40 -7
  14. package/src/index.ts +145 -58
  15. package/src/interface.ts +3 -2
  16. package/src/math/arcTools.ts +259 -80
  17. package/src/math/bezier.ts +58 -58
  18. package/src/math/cubicTools.ts +68 -25
  19. package/src/math/distanceSquareRoot.ts +3 -1
  20. package/src/math/lineTools.ts +42 -25
  21. package/src/math/midPoint.ts +3 -1
  22. package/src/math/polygonTools.ts +48 -0
  23. package/src/math/quadTools.ts +46 -25
  24. package/src/math/rotateVector.ts +3 -2
  25. package/src/math/roundTo.ts +7 -0
  26. package/src/parser/finalizeSegment.ts +11 -7
  27. package/src/parser/parsePathString.ts +4 -5
  28. package/src/parser/pathParser.ts +1 -1
  29. package/src/process/absolutizeSegment.ts +63 -0
  30. package/src/process/arcToCubic.ts +2 -2
  31. package/src/process/iterate.ts +58 -0
  32. package/src/process/lineToCubic.ts +1 -1
  33. package/src/process/normalizePath.ts +17 -28
  34. package/src/process/normalizeSegment.ts +55 -18
  35. package/src/process/optimizePath.ts +40 -60
  36. package/src/process/projection2d.ts +4 -3
  37. package/src/process/relativizeSegment.ts +59 -0
  38. package/src/process/reverseCurve.ts +8 -5
  39. package/src/process/reversePath.ts +88 -75
  40. package/src/process/roundPath.ts +18 -14
  41. package/src/process/roundSegment.ts +9 -0
  42. package/src/process/segmentToCubic.ts +10 -8
  43. package/src/process/shortenSegment.ts +26 -34
  44. package/src/process/splitCubic.ts +10 -9
  45. package/src/process/splitPath.ts +38 -4
  46. package/src/process/transformPath.ts +75 -73
  47. package/src/types.ts +30 -0
  48. package/src/util/getPathArea.ts +3 -3
  49. package/src/util/getPathBBox.ts +69 -19
  50. package/src/util/getPointAtLength.ts +100 -4
  51. package/src/util/getPropertiesAtLength.ts +3 -4
  52. package/src/util/getPropertiesAtPoint.ts +5 -5
  53. package/src/util/getTotalLength.ts +56 -4
  54. package/test/class.test.ts +17 -14
  55. package/test/fixtures/shapes.js +26 -26
  56. package/test/fixtures/simpleShapes.js +18 -18
  57. package/test/static.test.ts +54 -28
  58. package/cypress.config.ts +0 -29
  59. package/src/math/polygonArea.ts +0 -27
  60. package/src/math/polygonLength.ts +0 -21
  61. package/src/process/fixArc.ts +0 -23
  62. package/src/process/replaceArc.ts +0 -52
  63. package/src/util/pathFactory.ts +0 -130
@@ -1,44 +1,33 @@
1
- import pathToAbsolute from '../convert/pathToAbsolute';
2
1
  import normalizeSegment from './normalizeSegment';
3
- import isNormalizedArray from '../util/isNormalizedArray';
4
- import paramsParser from '../parser/paramsParser';
5
2
  import type { NormalArray, PathArray } from '../types';
3
+ import iterate from './iterate';
4
+ import parsePathString from '../parser/parsePathString';
5
+ import paramsParser from '../parser/paramsParser';
6
6
 
7
7
  /**
8
- * Normalizes a `path` object for further processing:
8
+ * Normalizes a `pathArray` object for further processing:
9
9
  * * convert segments to absolute values
10
10
  * * convert shorthand path commands to their non-shorthand notation
11
11
  *
12
12
  * @param pathInput the string to be parsed or 'pathArray'
13
13
  * @returns the normalized `pathArray`
14
14
  */
15
- const normalizePath = (pathInput: string | PathArray): NormalArray => {
16
- if (isNormalizedArray(pathInput)) {
17
- return pathInput.slice(0) as NormalArray;
18
- }
19
-
20
- const path = pathToAbsolute(pathInput);
15
+ const normalizePath = (pathInput: string | PathArray) => {
16
+ const path = parsePathString(pathInput);
21
17
  const params = { ...paramsParser };
22
- const allPathCommands = [];
23
- const ii = path.length;
24
- let pathCommand = '';
25
-
26
- for (let i = 0; i < ii; i += 1) {
27
- [pathCommand] = path[i];
28
-
29
- // Save current path command
30
- allPathCommands[i] = pathCommand;
31
- path[i] = normalizeSegment(path[i], params);
32
18
 
33
- const segment = path[i];
34
- const seglen = segment.length;
19
+ return iterate<NormalArray>(path, (seg, _, lastX, lastY) => {
20
+ params.x = lastX;
21
+ params.y = lastY;
22
+ const result = normalizeSegment(seg, params);
35
23
 
36
- params.x1 = +segment[seglen - 2];
37
- params.y1 = +segment[seglen - 1];
38
- params.x2 = +segment[seglen - 4] || params.x1;
39
- params.y2 = +segment[seglen - 3] || params.y1;
40
- }
24
+ const seglen = result.length;
25
+ params.x1 = +result[seglen - 2];
26
+ params.y1 = +result[seglen - 1];
27
+ params.x2 = +result[seglen - 4] || params.x1;
28
+ params.y2 = +result[seglen - 3] || params.y1;
41
29
 
42
- return path as NormalArray;
30
+ return result;
31
+ });
43
32
  };
44
33
  export default normalizePath;
@@ -1,47 +1,84 @@
1
1
  import type { ParserParams } from '../interface';
2
- import type { NormalSegment, PathSegment } from '../types';
2
+ import type {
3
+ NormalSegment,
4
+ PointTuple,
5
+ PathSegment,
6
+ QSegment,
7
+ CSegment,
8
+ LSegment,
9
+ MSegment,
10
+ HSegment,
11
+ VSegment,
12
+ ASegment,
13
+ PathCommand,
14
+ } from '../types';
3
15
 
4
16
  /**
5
17
  * Normalizes a single segment of a `pathArray` object.
6
18
  *
7
19
  * @param segment the segment object
8
- * @param params the coordinates of the previous segment
20
+ * @param params the normalization parameters
9
21
  * @returns the normalized segment
10
22
  */
11
- const normalizeSegment = (segment: PathSegment, params: ParserParams): NormalSegment => {
23
+ const normalizeSegment = (segment: PathSegment, params: ParserParams) => {
12
24
  const [pathCommand] = segment;
13
- const { x1: px1, y1: py1, x2: px2, y2: py2 } = params;
14
- const values = segment.slice(1).map(Number);
15
- let result = segment;
25
+ const absCommand = pathCommand.toUpperCase();
26
+ const isRelative = pathCommand !== absCommand;
27
+ const { x1: px1, y1: py1, x2: px2, y2: py2, x, y } = params;
28
+ const values = segment.slice(1) as number[];
29
+ let absValues = values.map((n, j) => n + (isRelative ? (j % 2 ? y : x) : 0));
16
30
 
17
- if (!'TQ'.includes(pathCommand)) {
31
+ if (!'TQ'.includes(absCommand)) {
18
32
  // optional but good to be cautious
19
33
  params.qx = null;
20
34
  params.qy = null;
21
35
  }
22
36
 
23
- if (pathCommand === 'H') {
24
- result = ['L', segment[1], py1];
25
- } else if (pathCommand === 'V') {
26
- result = ['L', px1, segment[1]];
27
- } else if (pathCommand === 'S') {
37
+ // istanbul ignore else @preserve
38
+ if (absCommand === 'A') {
39
+ absValues = values.slice(0, -2).concat(values[5] + (isRelative ? x : 0), values[6] + (isRelative ? y : 0));
40
+
41
+ return ['A' as PathCommand | number].concat(absValues) as ASegment;
42
+ } else if (absCommand === 'H') {
43
+ return ['L', (segment as HSegment)[1] + (isRelative ? x : 0), py1] as LSegment;
44
+ } else if (absCommand === 'V') {
45
+ return ['L', px1, (segment as VSegment)[1] + (isRelative ? y : 0)] as LSegment;
46
+ } else if (absCommand === 'L') {
47
+ return [
48
+ 'L',
49
+ (segment as LSegment)[1] + (isRelative ? x : 0),
50
+ (segment as LSegment)[2] + (isRelative ? y : 0),
51
+ ] as LSegment;
52
+ } else if (absCommand === 'M') {
53
+ return [
54
+ 'M',
55
+ (segment as MSegment)[1] + (isRelative ? x : 0),
56
+ (segment as MSegment)[2] + (isRelative ? y : 0),
57
+ ] as MSegment;
58
+ } else if (absCommand === 'C') {
59
+ return ['C' as PathCommand | number].concat(absValues) as CSegment;
60
+ } else if (absCommand === 'S') {
28
61
  const x1 = px1 * 2 - px2;
29
62
  const y1 = py1 * 2 - py2;
30
63
  params.x1 = x1;
31
64
  params.y1 = y1;
32
- result = ['C', x1, y1, ...(values as [number, number, number, number])];
33
- } else if (pathCommand === 'T') {
65
+ return ['C', x1, y1].concat(absValues) as CSegment;
66
+ } else if (absCommand === 'T') {
34
67
  const qx = px1 * 2 - (params.qx ? params.qx : /* istanbul ignore next */ 0);
35
68
  const qy = py1 * 2 - (params.qy ? params.qy : /* istanbul ignore next */ 0);
36
69
  params.qx = qx;
37
70
  params.qy = qy;
38
- result = ['Q', qx, qy, ...(values as [number, number])];
39
- } else if (pathCommand === 'Q') {
40
- const [nqx, nqy] = values as [number, number];
71
+ return ['Q', qx, qy].concat(absValues) as QSegment;
72
+ } else if (absCommand === 'Q') {
73
+ const [nqx, nqy] = absValues as PointTuple;
41
74
  params.qx = nqx;
42
75
  params.qy = nqy;
76
+ return ['Q' as PathCommand | number].concat(absValues) as QSegment;
77
+ } else if (absCommand === 'Z') {
78
+ return ['Z'] as NormalSegment;
43
79
  }
44
80
 
45
- return result as NormalSegment;
81
+ // istanbul ignore next @preserve
82
+ return segment as NormalSegment;
46
83
  };
47
84
  export default normalizeSegment;
@@ -1,10 +1,11 @@
1
- import roundPath from './roundPath';
2
1
  import pathToAbsolute from '../convert/pathToAbsolute';
3
- import pathToRelative from '../convert/pathToRelative';
4
2
  import shortenSegment from './shortenSegment';
5
3
  import paramsParser from '../parser/paramsParser';
6
- import normalizePath from './normalizePath';
7
- import type { PathSegment, HSegment, PathArray, VSegment, PathCommand, AbsoluteSegment } from '../types';
4
+ import type { AbsoluteSegment, PathArray, PathCommand } from '../types';
5
+ import iterate from './iterate';
6
+ import normalizeSegment from './normalizeSegment';
7
+ import relativizeSegment from './relativizeSegment';
8
+ import roundSegment from './roundSegment';
8
9
 
9
10
  /**
10
11
  * Optimizes a `pathArray` object:
@@ -12,72 +13,51 @@ import type { PathSegment, HSegment, PathArray, VSegment, PathCommand, AbsoluteS
12
13
  * * select shortest segments from absolute and relative `pathArray`s
13
14
  *
14
15
  * @param pathInput a string or `pathArray`
15
- * @param round the amount of decimals to round values to
16
+ * @param roundOption the amount of decimals to round values to
16
17
  * @returns the optimized `pathArray`
17
18
  */
18
- const optimizePath = (pathInput: PathArray, round: 'off' | number): PathArray => {
19
+ const optimizePath = (pathInput: PathArray, roundOption: number) => {
19
20
  const path = pathToAbsolute(pathInput);
20
- const normalPath = normalizePath(path);
21
- const params = { ...paramsParser };
21
+ // allow for ZERO decimals or use an aggressive value of 2
22
+ const round =
23
+ typeof roundOption === 'number' && roundOption >= 0 ? roundOption : /* istanbul ignore next @preserve */ 2;
24
+ // this utility overrides the iterator params
25
+ const optimParams = { ...paramsParser };
26
+
22
27
  const allPathCommands = [] as PathCommand[];
23
- const ii = path.length;
24
- let pathCommand = '' as PathCommand;
25
- let prevCommand = '' as PathCommand;
26
- let x = 0;
27
- let y = 0;
28
- let mx = 0;
29
- let my = 0;
28
+ let pathCommand = 'M' as PathCommand;
29
+ let prevCommand = 'Z' as PathCommand;
30
30
 
31
- for (let i = 0; i < ii; i += 1) {
32
- [pathCommand] = path[i];
31
+ return iterate(path, (seg, i, lastX, lastY) => {
32
+ optimParams.x = lastX;
33
+ optimParams.y = lastY;
34
+ // const absoluteSegment = absolutizeSegment(seg, optimParams);
35
+ const normalizedSegment = normalizeSegment(seg, optimParams);
36
+ let result = seg;
37
+ [pathCommand] = seg;
33
38
 
34
39
  // Save current path command
35
40
  allPathCommands[i] = pathCommand;
36
- // Get previous path command for `shortenSegment`
37
- if (i) prevCommand = allPathCommands[i - 1];
38
- path[i] = shortenSegment(path[i], normalPath[i], params, prevCommand) as AbsoluteSegment;
39
-
40
- const segment = path[i];
41
- const seglen = segment.length;
42
-
43
- // update C, S, Q, T specific params
44
- params.x1 = +segment[seglen - 2];
45
- params.y1 = +segment[seglen - 1];
46
- params.x2 = +segment[seglen - 4] || params.x1;
47
- params.y2 = +segment[seglen - 3] || params.y1;
48
-
49
- // update x, y params
50
- switch (pathCommand) {
51
- case 'Z':
52
- x = mx;
53
- y = my;
54
- break;
55
- case 'H':
56
- [, x] = segment as HSegment;
57
- break;
58
- case 'V':
59
- [, y] = segment as VSegment;
60
- break;
61
- default:
62
- [x, y] = segment.slice(-2).map(Number);
63
-
64
- if (pathCommand === 'M') {
65
- mx = x;
66
- my = y;
67
- }
41
+ if (i) {
42
+ // Get previous path command for `shortenSegment`
43
+ prevCommand = allPathCommands[i - 1];
44
+ const shortSegment = shortenSegment(seg as AbsoluteSegment, normalizedSegment, optimParams, prevCommand);
45
+ const absSegment = roundSegment(shortSegment, round);
46
+ const absString = absSegment.join('');
47
+ const relativeSegment = relativizeSegment(shortSegment, i, lastX, lastY);
48
+ const relSegment = roundSegment(relativeSegment, round);
49
+ const relString = relSegment.join('');
50
+ result = absString.length < relString.length ? absSegment : relSegment;
68
51
  }
69
- params.x = x;
70
- params.y = y;
71
- }
72
52
 
73
- const absolutePath = roundPath(path, round);
74
- const relativePath = roundPath(pathToRelative(path), round);
53
+ const seglen = normalizedSegment.length;
54
+ optimParams.x1 = +normalizedSegment[seglen - 2];
55
+ optimParams.y1 = +normalizedSegment[seglen - 1];
56
+ optimParams.x2 = +normalizedSegment[seglen - 4] || optimParams.x1;
57
+ optimParams.y2 = +normalizedSegment[seglen - 3] || optimParams.y1;
75
58
 
76
- return absolutePath.map((a: PathSegment, i: number) => {
77
- if (i) {
78
- return a.join('').length < relativePath[i].join('').length ? a : relativePath[i];
79
- }
80
- return a;
81
- }) as PathArray;
59
+ return result;
60
+ });
82
61
  };
62
+
83
63
  export default optimizePath;
@@ -1,4 +1,5 @@
1
1
  import CSSMatrix from '@thednp/dommatrix';
2
+ import { type PointTuple } from '../types';
2
3
 
3
4
  /**
4
5
  * Transforms a specified point using a matrix, returning a new
@@ -12,7 +13,7 @@ import CSSMatrix from '@thednp/dommatrix';
12
13
  * @return the resulting Tuple
13
14
  */
14
15
  const translatePoint = (cssm: CSSMatrix, v: [number, number, number, number]): [number, number, number, number] => {
15
- let m = CSSMatrix.Translate(...(v.slice(0, -1) as [number, number, number]));
16
+ let m = CSSMatrix.Translate(v[0], v[1], v[2]);
16
17
 
17
18
  [, , , m.m44] = v;
18
19
  m = cssm.multiply(m);
@@ -34,9 +35,9 @@ const translatePoint = (cssm: CSSMatrix, v: [number, number, number, number]): [
34
35
  * @param origin the [x,y,z] transform origin
35
36
  * @returns the projected [x,y] coordinates
36
37
  */
37
- const projection2d = (m: CSSMatrix, point2D: [number, number], origin: [number, number, number]): [number, number] => {
38
+ const projection2d = (m: CSSMatrix, point2D: PointTuple, origin: [number, number, number]): PointTuple => {
38
39
  const [originX, originY, originZ] = origin;
39
- const [x, y, z] = translatePoint(m, [...point2D, 0, 1]);
40
+ const [x, y, z] = translatePoint(m, [point2D[0], point2D[1], 0, 1]);
40
41
 
41
42
  const relativePositionX = x - originX;
42
43
  const relativePositionY = y - originY;
@@ -0,0 +1,59 @@
1
+ import type {
2
+ RelativeSegment,
3
+ RelativeCommand,
4
+ PathSegment,
5
+ aSegment,
6
+ vSegment,
7
+ hSegment,
8
+ qSegment,
9
+ tSegment,
10
+ sSegment,
11
+ cSegment,
12
+ MSegment,
13
+ lSegment,
14
+ } from '../types';
15
+
16
+ /**
17
+ * Returns a relative segment of a `PathArray` object.
18
+ *
19
+ * @param segment the segment object
20
+ * @param index the segment index
21
+ * @param lastX the last known X value
22
+ * @param lastY the last known Y value
23
+ * @returns the relative segment
24
+ */
25
+ const relativizeSegment = (segment: PathSegment, index: number, lastX: number, lastY: number) => {
26
+ const [pathCommand] = segment;
27
+ const relCommand = pathCommand.toLowerCase() as RelativeCommand;
28
+ const isRelative = pathCommand === relCommand;
29
+
30
+ /* istanbul ignore else @preserve */
31
+ if (index === 0 || isRelative) return segment as MSegment | RelativeSegment;
32
+
33
+ if (relCommand === 'a') {
34
+ return [
35
+ relCommand,
36
+ segment[1],
37
+ segment[2],
38
+ segment[3],
39
+ segment[4],
40
+ segment[5],
41
+ (segment as aSegment)[6] - lastX,
42
+ (segment as aSegment)[7] - lastY,
43
+ ] as aSegment;
44
+ } else if (relCommand === 'v') {
45
+ return [relCommand, (segment as vSegment)[1] - lastY] as vSegment;
46
+ } else if (relCommand === 'h') {
47
+ return [relCommand, (segment as hSegment)[1] - lastX] as hSegment;
48
+ } else if (relCommand === 'l') {
49
+ return [relCommand, (segment as lSegment)[1] - lastX, (segment as lSegment)[2] - lastY] as lSegment;
50
+ } else {
51
+ // use brakets for `eslint: no-case-declaration`
52
+ // https://stackoverflow.com/a/50753272/803358
53
+ const relValues = (segment.slice(1) as number[]).map((n, j) => n - (j % 2 ? lastY : lastX));
54
+ // for c, s, q, t
55
+ return [relCommand as RelativeCommand | number].concat(relValues) as qSegment | tSegment | sSegment | cSegment;
56
+ }
57
+ };
58
+
59
+ export default relativizeSegment;
@@ -1,4 +1,4 @@
1
- import type { CurveArray } from '../types';
1
+ import type { CSegment, CurveArray, MSegment, PathCommand } from '../types';
2
2
 
3
3
  /**
4
4
  * Reverses all segments of a `pathArray`
@@ -7,15 +7,18 @@ import type { CurveArray } from '../types';
7
7
  * @param path the source `pathArray`
8
8
  * @returns the reversed `pathArray`
9
9
  */
10
- const reverseCurve = (path: CurveArray): CurveArray => {
10
+ const reverseCurve = (path: CurveArray) => {
11
11
  const rotatedCurve = path
12
12
  .slice(1)
13
13
  .map((x, i, curveOnly) =>
14
- !i ? [...path[0].slice(1), ...x.slice(1)] : [...curveOnly[i - 1].slice(-2), ...x.slice(1)],
14
+ !i ? path[0].slice(1).concat(x.slice(1) as number[]) : curveOnly[i - 1].slice(-2).concat(x.slice(1)),
15
15
  )
16
16
  .map(x => x.map((_, i) => x[x.length - i - 2 * (1 - (i % 2))]))
17
- .reverse();
17
+ .reverse() as (MSegment | CSegment)[];
18
18
 
19
- return [['M', ...rotatedCurve[0].slice(0, 2)], ...rotatedCurve.map(x => ['C', ...x.slice(2)])] as CurveArray;
19
+ return [['M' as PathCommand | number].concat(rotatedCurve[0].slice(0, 2))].concat(
20
+ rotatedCurve.map(x => ['C' as PathCommand | number].concat(x.slice(2))),
21
+ ) as CurveArray;
20
22
  };
23
+
21
24
  export default reverseCurve;
@@ -5,97 +5,110 @@ import type {
5
5
  MSegment,
6
6
  PathArray,
7
7
  PathSegment,
8
+ PointTuple,
8
9
  QSegment,
9
10
  SSegment,
10
11
  TSegment,
11
12
  VSegment,
12
- } from 'src/types';
13
+ } from '../types';
13
14
  import pathToAbsolute from '../convert/pathToAbsolute';
14
15
  import normalizePath from './normalizePath';
16
+ import iterate from './iterate';
15
17
 
16
18
  /**
17
- * Reverses all segments of a `pathArray` and returns a new `pathArray` instance.
19
+ * Reverses all segments of a `pathArray` and returns a new `pathArray` instance
20
+ * with absolute values.
18
21
  *
19
22
  * @param pathInput the source `pathArray`
20
23
  * @returns the reversed `pathArray`
21
24
  */
22
- const reversePath = (pathInput: PathArray): PathArray => {
25
+ const reversePath = (pathInput: PathArray) => {
23
26
  const absolutePath = pathToAbsolute(pathInput);
24
- const isClosed = absolutePath.slice(-1)[0][0] === 'Z';
27
+ const normalizedPath = normalizePath(absolutePath);
28
+ const pLen = absolutePath.length;
29
+ const isClosed = absolutePath[pLen - 1][0] === 'Z';
25
30
 
26
- const reversedPath = normalizePath(absolutePath)
27
- .map((segment, i) => {
28
- const [x, y] = segment.slice(-2).map(Number);
29
- return {
30
- seg: absolutePath[i], // absolute
31
- n: segment, // normalized
32
- c: absolutePath[i][0], // pathCommand
33
- x, // x
34
- y, // y
35
- };
36
- })
37
- .map((seg, i, path) => {
38
- const segment = seg.seg;
39
- const data = seg.n;
40
- const prevSeg = i && path[i - 1];
41
- const nextSeg = path[i + 1];
42
- const pathCommand = seg.c;
43
- const pLen = path.length;
44
- const x = i ? path[i - 1].x : path[pLen - 1].x;
45
- const y = i ? path[i - 1].y : path[pLen - 1].y;
46
- let result = [];
31
+ const reversedPath = iterate(absolutePath, (segment, i) => {
32
+ const normalizedSegment = normalizedPath[i];
33
+ const prevSeg = i && absolutePath[i - 1];
34
+ const prevCommand = prevSeg && prevSeg[0];
35
+ const nextSeg = absolutePath[i + 1];
36
+ const nextCommand = nextSeg && nextSeg[0];
37
+ const [pathCommand] = segment;
38
+ const [x, y] = normalizedPath[i ? i - 1 : pLen - 1].slice(-2) as PointTuple;
39
+ let result = segment;
47
40
 
48
- switch (pathCommand) {
49
- case 'M':
50
- result = (isClosed ? ['Z'] : [pathCommand, x, y]) as PathSegment;
51
- break;
52
- case 'A':
53
- result = [pathCommand, ...segment.slice(1, -3), segment[5] === 1 ? 0 : 1, x, y] as ASegment;
54
- break;
55
- case 'C':
56
- if (nextSeg && nextSeg.c === 'S') {
57
- result = ['S', segment[1], segment[2], x, y] as SSegment;
58
- } else {
59
- result = [pathCommand, segment[3], segment[4], segment[1], segment[2], x, y] as CSegment;
60
- }
61
- break;
62
- case 'S':
63
- if (prevSeg && 'CS'.includes(prevSeg.c) && (!nextSeg || nextSeg.c !== 'S')) {
64
- result = ['C', data[3], data[4], data[1], data[2], x, y] as CSegment;
65
- } else {
66
- result = [pathCommand, data[1], data[2], x, y] as SSegment;
67
- }
68
- break;
69
- case 'Q':
70
- if (nextSeg && nextSeg.c === 'T') {
71
- result = ['T', x, y] as TSegment;
72
- } else {
73
- result = [pathCommand, ...segment.slice(1, -2), x, y] as QSegment;
74
- }
75
- break;
76
- case 'T':
77
- if (prevSeg && 'QT'.includes(prevSeg.c) && (!nextSeg || nextSeg.c !== 'T')) {
78
- result = ['Q', data[1], data[2], x, y] as QSegment;
79
- } else {
80
- result = [pathCommand, x, y] as TSegment;
81
- }
82
- break;
83
- case 'Z':
84
- result = ['M', x, y] as MSegment;
85
- break;
86
- case 'H':
87
- result = [pathCommand, x] as HSegment;
88
- break;
89
- case 'V':
90
- result = [pathCommand, y] as VSegment;
91
- break;
92
- default:
93
- result = [pathCommand, ...segment.slice(1, -2), x, y] as PathSegment;
94
- }
41
+ switch (pathCommand) {
42
+ case 'M':
43
+ result = (isClosed ? ['Z'] : [pathCommand, x, y]) as PathSegment;
44
+ break;
45
+ case 'A':
46
+ result = [
47
+ pathCommand,
48
+ segment[1],
49
+ segment[2],
50
+ segment[3],
51
+ segment[4],
52
+ segment[5] === 1 ? 0 : 1,
53
+ x,
54
+ y,
55
+ ] as ASegment;
56
+ break;
57
+ case 'C':
58
+ if (nextSeg && nextCommand === 'S') {
59
+ result = ['S', segment[1], segment[2], x, y] as SSegment;
60
+ } else {
61
+ result = [pathCommand, segment[3], segment[4], segment[1], segment[2], x, y] as CSegment;
62
+ }
63
+ break;
64
+ case 'S':
65
+ if (prevCommand && 'CS'.includes(prevCommand) && (!nextSeg || nextCommand !== 'S')) {
66
+ result = [
67
+ 'C',
68
+ normalizedSegment[3],
69
+ normalizedSegment[4],
70
+ normalizedSegment[1],
71
+ normalizedSegment[2],
72
+ x,
73
+ y,
74
+ ] as CSegment;
75
+ } else {
76
+ result = [pathCommand, normalizedSegment[1], normalizedSegment[2], x, y] as SSegment;
77
+ }
78
+ break;
79
+ case 'Q':
80
+ if (nextSeg && nextCommand === 'T') {
81
+ result = ['T', x, y] as TSegment;
82
+ } else {
83
+ result = [pathCommand, segment[1], segment[2], x, y] as QSegment;
84
+ }
85
+ break;
86
+ case 'T':
87
+ if (prevCommand && 'QT'.includes(prevCommand) && (!nextSeg || nextCommand !== 'T')) {
88
+ result = ['Q', normalizedSegment[1], normalizedSegment[2], x, y] as QSegment;
89
+ } else {
90
+ result = [pathCommand, x, y] as TSegment;
91
+ }
92
+ break;
93
+ case 'Z':
94
+ result = ['M', x, y] as MSegment;
95
+ break;
96
+ case 'H':
97
+ result = [pathCommand, x] as HSegment;
98
+ break;
99
+ case 'V':
100
+ result = [pathCommand, y] as VSegment;
101
+ break;
102
+ default:
103
+ result = [pathCommand as typeof pathCommand | number].concat(segment.slice(1, -2), x, y) as PathSegment;
104
+ }
95
105
 
96
- return result;
97
- });
106
+ return result;
107
+ });
98
108
 
99
- return (isClosed ? reversedPath.reverse() : [reversedPath[0], ...reversedPath.slice(1).reverse()]) as PathArray;
109
+ return (
110
+ isClosed ? reversedPath.reverse() : [reversedPath[0] as PathSegment].concat(reversedPath.slice(1).reverse())
111
+ ) as PathArray;
100
112
  };
113
+
101
114
  export default reversePath;
@@ -1,5 +1,7 @@
1
- import type { PathArray } from 'src/types';
1
+ import type { PathArray } from '../types';
2
2
  import defaultOptions from '../options/options';
3
+ import iterate from './iterate';
4
+ import roundSegment from './roundSegment';
3
5
 
4
6
  /**
5
7
  * Rounds the values of a `pathArray` instance to
@@ -9,21 +11,23 @@ import defaultOptions from '../options/options';
9
11
  * @param roundOption the amount of decimals to round numbers to
10
12
  * @returns the resulted `pathArray` with rounded values
11
13
  */
12
- const roundPath = (path: PathArray, roundOption?: number | 'off'): PathArray => {
14
+ const roundPath = (path: PathArray, roundOption?: number | 'off') => {
13
15
  let { round } = defaultOptions;
14
- if (roundOption === 'off' || round === 'off') return [...path];
15
16
  // allow for ZERO decimals
16
- round = typeof roundOption === 'number' && roundOption >= 0 ? roundOption : round;
17
- // to round values to the power
18
- // the `round` value must be integer
19
- const pow = typeof round === 'number' && round >= 1 ? 10 ** round : 1;
17
+ round =
18
+ roundOption === 'off'
19
+ ? roundOption
20
+ : typeof roundOption === 'number' && roundOption >= 0
21
+ ? roundOption
22
+ : typeof round === 'number' && round >= 0
23
+ ? round
24
+ : /* istanbul ignore next @preserve */ 'off';
20
25
 
21
- return path.map(pi => {
22
- const values = pi
23
- .slice(1)
24
- .map(Number)
25
- .map(n => (round ? Math.round(n * pow) / pow : Math.round(n)));
26
- return [pi[0], ...values];
27
- }) as PathArray;
26
+ /* istanbul ignore else @preserve */
27
+ if (round === 'off') return path.slice(0) as PathArray;
28
+
29
+ return iterate<typeof path>(path, segment => {
30
+ return roundSegment(segment, round);
31
+ });
28
32
  };
29
33
  export default roundPath;
@@ -0,0 +1,9 @@
1
+ import type { PathCommand, PathSegment } from '../types';
2
+ import roundTo from '../math/roundTo';
3
+
4
+ const roundSegment = <T extends PathSegment>(segment: T, roundOption: number) => {
5
+ const values = (segment.slice(1) as number[]).map(n => roundTo(n, roundOption));
6
+ return [segment[0] as PathCommand | number].concat(values) as T;
7
+ };
8
+
9
+ export default roundSegment;