svg-path-commander 2.1.0 → 2.1.1

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 (51) hide show
  1. package/README.md +61 -5
  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 +119 -33
  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 +798 -828
  8. package/dist/svg-path-commander.mjs.map +1 -1
  9. package/package.json +3 -3
  10. package/src/convert/pathToAbsolute.ts +16 -70
  11. package/src/convert/pathToCurve.ts +36 -28
  12. package/src/convert/pathToRelative.ts +33 -62
  13. package/src/index.ts +13 -19
  14. package/src/interface.ts +1 -1
  15. package/src/math/arcTools.ts +248 -71
  16. package/src/math/bezier.ts +19 -27
  17. package/src/math/cubicTools.ts +67 -26
  18. package/src/math/distanceSquareRoot.ts +3 -1
  19. package/src/math/lineTools.ts +41 -26
  20. package/src/math/midPoint.ts +3 -1
  21. package/src/math/polygonArea.ts +3 -1
  22. package/src/math/polygonLength.ts +2 -1
  23. package/src/math/quadTools.ts +45 -26
  24. package/src/parser/parsePathString.ts +4 -4
  25. package/src/process/absolutizeSegment.ts +58 -0
  26. package/src/process/iterate.ts +33 -0
  27. package/src/process/normalizePath.ts +34 -28
  28. package/src/process/normalizeSegment.ts +8 -9
  29. package/src/process/projection2d.ts +2 -1
  30. package/src/process/relativizeSegment.ts +61 -0
  31. package/src/process/reversePath.ts +1 -1
  32. package/src/process/roundPath.ts +8 -10
  33. package/src/process/segmentToCubic.ts +1 -1
  34. package/src/process/shortenSegment.ts +3 -3
  35. package/src/process/splitCubic.ts +8 -7
  36. package/src/process/splitPath.ts +38 -4
  37. package/src/process/transformPath.ts +80 -73
  38. package/src/types.ts +35 -1
  39. package/src/util/getPathArea.ts +3 -3
  40. package/src/util/getPathBBox.ts +86 -19
  41. package/src/util/getPointAtLength.ts +98 -4
  42. package/src/util/getPropertiesAtLength.ts +2 -2
  43. package/src/util/getTotalLength.ts +71 -4
  44. package/test/class.test.ts +15 -14
  45. package/test/fixtures/shapes.js +27 -27
  46. package/test/fixtures/simpleShapes.js +18 -18
  47. package/test/static.test.ts +37 -17
  48. package/cypress.config.ts +0 -29
  49. package/src/process/fixArc.ts +0 -23
  50. package/src/process/replaceArc.ts +0 -52
  51. package/src/util/pathFactory.ts +0 -130
@@ -2,51 +2,66 @@ import midPoint from './midPoint';
2
2
  import distanceSquareRoot from './distanceSquareRoot';
3
3
 
4
4
  /**
5
- * Returns properties for line segments (MoveTo, LineTo).
5
+ * Returns length for line segments (MoveTo, LineTo).
6
6
  *
7
7
  * @param x1 the starting point X
8
8
  * @param y1 the starting point Y
9
9
  * @param x2 the ending point X
10
10
  * @param y2 the ending point Y
11
- * @param distance the distance to point
12
- * @returns the segment length, point at length and the bounding box
11
+ * @returns the line segment length
13
12
  */
14
- const getSegmentProperties = (x1: number, y1: number, x2: number, y2: number, distance?: number) => {
15
- const { min, max } = Math;
16
- let point = { x: 0, y: 0 };
17
- const length = () => distanceSquareRoot([x1, y1], [x2, y2]);
13
+ export const getLineLength = (x1: number, y1: number, x2: number, y2: number) => {
14
+ return distanceSquareRoot([x1, y1], [x2, y2]);
15
+ };
16
+
17
+ /**
18
+ * Returns a point along the line segments (MoveTo, LineTo).
19
+ *
20
+ * @param x1 the starting point X
21
+ * @param y1 the starting point Y
22
+ * @param x2 the ending point X
23
+ * @param y2 the ending point Y
24
+ * @param distance the distance to point in [0-1] range
25
+ * @returns the point at length
26
+ */
27
+ export const getPointAtLineLength = (x1: number, y1: number, x2: number, y2: number, distance?: number) => {
28
+ const length = distanceSquareRoot([x1, y1], [x2, y2]);
29
+ let point = { x: x1, y: y1 };
18
30
 
19
31
  /* istanbul ignore else @preserve */
20
32
  if (typeof distance === 'number') {
21
- const currentLength = length();
22
33
  if (distance <= 0) {
23
34
  point = { x: x1, y: y1 };
24
- } else if (distance >= currentLength) {
35
+ } else if (distance >= length) {
25
36
  point = { x: x2, y: y2 };
26
37
  } else {
27
- const [x, y] = midPoint([x1, y1], [x2, y2], distance / currentLength);
38
+ const [x, y] = midPoint([x1, y1], [x2, y2], distance / length);
28
39
  point = { x, y };
29
40
  }
30
41
  }
42
+ return point;
43
+ };
31
44
 
45
+ /**
46
+ * Returns bounding box for line segments (MoveTo, LineTo).
47
+ *
48
+ * @param x1 the starting point X
49
+ * @param y1 the starting point Y
50
+ * @param x2 the ending point X
51
+ * @param y2 the ending point Y
52
+ * @param distance the distance to point in [0-1] range
53
+ * @returns the extrema for line segments
54
+ */
55
+ export const getLineBBox = (x1: number, y1: number, x2: number, y2: number) => {
56
+ const { min, max } = Math;
32
57
  return {
33
- point,
34
- get length() {
35
- return length();
58
+ min: {
59
+ x: min(x1, x2),
60
+ y: min(y1, y2),
36
61
  },
37
- get bbox() {
38
- return {
39
- min: {
40
- x: min(x1, x2),
41
- y: min(y1, y2),
42
- },
43
- max: {
44
- x: max(x1, x2),
45
- y: max(y1, y2),
46
- },
47
- };
62
+ max: {
63
+ x: max(x1, x2),
64
+ y: max(y1, y2),
48
65
  },
49
66
  };
50
67
  };
51
-
52
- export default getSegmentProperties;
@@ -1,3 +1,5 @@
1
+ import { PointTuple } from '../types';
2
+
1
3
  /**
2
4
  * Returns the coordinates of a specified distance
3
5
  * ratio between two points.
@@ -7,7 +9,7 @@
7
9
  * @param t the ratio
8
10
  * @returns the midpoint coordinates
9
11
  */
10
- const midPoint = (a: [number, number], b: [number, number], t: number): [number, number] => {
12
+ const midPoint = (a: PointTuple, b: PointTuple, t: number): PointTuple => {
11
13
  const [ax, ay] = a;
12
14
  const [bx, by] = b;
13
15
  return [ax + (bx - ax) * t, ay + (by - ay) * t];
@@ -1,3 +1,5 @@
1
+ import { type PointTuple } from '../types';
2
+
1
3
  /**
2
4
  * d3-polygon-area
3
5
  * https://github.com/d3/d3-polygon
@@ -7,7 +9,7 @@
7
9
  * @param polygon an array of coordinates
8
10
  * @returns the polygon area
9
11
  */
10
- const polygonArea = (polygon: [number, number][]): number => {
12
+ const polygonArea = (polygon: PointTuple[]): number => {
11
13
  const n = polygon.length;
12
14
  let i = -1;
13
15
  let a;
@@ -1,3 +1,4 @@
1
+ import { type PointTuple } from '../types';
1
2
  import distanceSquareRoot from './distanceSquareRoot';
2
3
 
3
4
  /**
@@ -9,7 +10,7 @@ import distanceSquareRoot from './distanceSquareRoot';
9
10
  * @param polygon an array of coordinates
10
11
  * @returns {number} the polygon length
11
12
  */
12
- const polygonLength = (polygon: [number, number][]): number => {
13
+ const polygonLength = (polygon: PointTuple[]): number => {
13
14
  return polygon.reduce((length, point, i) => {
14
15
  if (i) {
15
16
  return length + distanceSquareRoot(polygon[i - 1], point);
@@ -1,4 +1,5 @@
1
- import { length, minmaxQ, type QuadCoordinates } from './bezier';
1
+ import { length, minmaxQ } from './bezier';
2
+ import { type QuadCoordinates } from '../types';
2
3
 
3
4
  /**
4
5
  * Returns the {x,y} coordinates of a point at a
@@ -24,7 +25,7 @@ const getPointAtQuadSegmentLength = ([x1, y1, cx, cy, x2, y2]: QuadCoordinates,
24
25
  };
25
26
 
26
27
  /**
27
- * Returns properties of a QuadraticBezier segment.
28
+ * Returns the length of a QuadraticBezier segment.
28
29
  *
29
30
  * @param x1 the starting point X
30
31
  * @param y1 the starting point Y
@@ -32,48 +33,66 @@ const getPointAtQuadSegmentLength = ([x1, y1, cx, cy, x2, y2]: QuadCoordinates,
32
33
  * @param cy the control point Y
33
34
  * @param x2 the ending point X
34
35
  * @param y2 the ending point Y
35
- * @param distance the point distance
36
- * @returns the segment length, point at length and the bounding box
36
+ * @returns the QuadraticBezier segment length
37
37
  */
38
- const getSegmentProperties = (
38
+ export const getQuadLength = (x1: number, y1: number, cx: number, cy: number, x2: number, y2: number) => {
39
+ return length([x1, y1, cx, cy, x2, y2]);
40
+ };
41
+
42
+ /**
43
+ * Returns the point along a QuadraticBezier segment at a given distance.
44
+ *
45
+ * @param x1 the starting point X
46
+ * @param y1 the starting point Y
47
+ * @param cx the control point X
48
+ * @param cy the control point Y
49
+ * @param x2 the ending point X
50
+ * @param y2 the ending point Y
51
+ * @param distance the distance to look at
52
+ * @returns the point at QuadraticBezier length
53
+ */
54
+ export const getPointAtQuadLength = (
39
55
  x1: number,
40
56
  y1: number,
41
57
  cx: number,
42
58
  cy: number,
43
59
  x2: number,
44
60
  y2: number,
45
- distance: number | undefined,
61
+ distance?: number,
46
62
  ) => {
47
63
  const distanceIsNumber = typeof distance === 'number';
48
- let POINT = { x: x1, y: y1 };
49
- const getLength = () => length([x1, y1, cx, cy, x2, y2]);
64
+ let point = { x: x1, y: y1 };
50
65
 
66
+ /* istanbul ignore else @preserve */
51
67
  if (distanceIsNumber) {
52
- const currentLength = getLength();
53
-
68
+ const currentLength = length([x1, y1, cx, cy, x2, y2]);
54
69
  if (distance <= 0) {
55
70
  // first point already defined
56
71
  } else if (distance >= currentLength) {
57
- POINT = { x: x2, y: y2 };
72
+ point = { x: x2, y: y2 };
58
73
  } else {
59
- POINT = getPointAtQuadSegmentLength([x1, y1, cx, cy, x2, y2], distance / currentLength);
74
+ point = getPointAtQuadSegmentLength([x1, y1, cx, cy, x2, y2], distance / currentLength);
60
75
  }
61
76
  }
77
+ return point;
78
+ };
62
79
 
80
+ /**
81
+ * Returns the boundig box of a QuadraticBezier segment.
82
+ *
83
+ * @param x1 the starting point X
84
+ * @param y1 the starting point Y
85
+ * @param cx the control point X
86
+ * @param cy the control point Y
87
+ * @param x2 the ending point X
88
+ * @param y2 the ending point Y
89
+ * @returns the point at CubicBezier length
90
+ */
91
+ export const getQuadBBox = (x1: number, y1: number, cx: number, cy: number, x2: number, y2: number) => {
92
+ const cxMinMax = minmaxQ([x1, cx, x2]);
93
+ const cyMinMax = minmaxQ([y1, cy, y2]);
63
94
  return {
64
- point: POINT,
65
- get length() {
66
- return getLength();
67
- },
68
- get bbox() {
69
- const cxMinMax = minmaxQ([x1, cx, x2]);
70
- const cyMinMax = minmaxQ([y1, cy, y2]);
71
- return {
72
- min: { x: cxMinMax[0], y: cyMinMax[0] },
73
- max: { x: cxMinMax[1], y: cyMinMax[1] },
74
- };
75
- },
95
+ min: { x: cxMinMax[0], y: cyMinMax[0] },
96
+ max: { x: cxMinMax[1], y: cyMinMax[1] },
76
97
  };
77
98
  };
78
-
79
- export default getSegmentProperties;
@@ -1,7 +1,6 @@
1
1
  import scanSegment from './scanSegment';
2
2
  import skipSpaces from './skipSpaces';
3
3
  import PathParser from './pathParser';
4
- import isPathArray from '../util/isPathArray';
5
4
  import type { PathArray } from '../types';
6
5
 
7
6
  /**
@@ -11,9 +10,10 @@ import type { PathArray } from '../types';
11
10
  * @param pathInput the string to be parsed
12
11
  * @returns the resulted `pathArray` or error string
13
12
  */
14
- const parsePathString = (pathInput: string | PathArray): PathArray => {
15
- if (isPathArray(pathInput)) {
13
+ const parsePathString = (pathInput: string | PathArray) => {
14
+ if (typeof pathInput !== 'string') {
16
15
  return pathInput.slice(0) as PathArray;
16
+ // return pathInput;
17
17
  }
18
18
 
19
19
  const path = new PathParser(pathInput);
@@ -24,7 +24,7 @@ const parsePathString = (pathInput: string | PathArray): PathArray => {
24
24
  scanSegment(path);
25
25
  }
26
26
 
27
- if (path.err && path.err.length) {
27
+ if (path?.err.length) {
28
28
  throw TypeError(path.err);
29
29
  }
30
30
 
@@ -0,0 +1,58 @@
1
+ import type { ParserParams } from '../interface';
2
+ import type {
3
+ AbsoluteSegment,
4
+ AbsoluteCommand,
5
+ ASegment,
6
+ VSegment,
7
+ HSegment,
8
+ QSegment,
9
+ SSegment,
10
+ TSegment,
11
+ CSegment,
12
+ PathSegment,
13
+ MSegment,
14
+ } from '../types';
15
+
16
+ /**
17
+ * Returns an absolute segment of a `PathArray` object.
18
+ *
19
+ * @param segment the segment object
20
+ * @param params the coordinates of the previous segment
21
+ * @returns the absolute segment
22
+ */
23
+ const absolutizeSegment = (segment: PathSegment, params: ParserParams) => {
24
+ const [pathCommand] = segment;
25
+ const { x, y } = params;
26
+ const values = segment.slice(1).map(Number);
27
+ const absCommand = pathCommand.toUpperCase() as AbsoluteCommand;
28
+ const isAbsolute = absCommand === pathCommand;
29
+
30
+ /* istanbul ignore else @preserve */
31
+ if (!isAbsolute) {
32
+ if (absCommand === 'A') {
33
+ return [
34
+ absCommand,
35
+ values[0],
36
+ values[1],
37
+ values[2],
38
+ values[3],
39
+ values[4],
40
+ values[5] + x,
41
+ values[6] + y,
42
+ ] as ASegment;
43
+ } else if (absCommand === 'V') {
44
+ return [absCommand, values[0] + y] as VSegment;
45
+ } else if (absCommand === 'H') {
46
+ return [absCommand, values[0] + x] as HSegment;
47
+ } else {
48
+ // use brakets for `eslint: no-case-declaration`
49
+ // https://stackoverflow.com/a/50753272/803358
50
+ const absValues = values.map((n, j) => n + (j % 2 ? y : x));
51
+ // for n, l, c, s, q, t
52
+ return [absCommand, ...absValues] as MSegment | QSegment | TSegment | SSegment | CSegment;
53
+ }
54
+ }
55
+
56
+ return segment as AbsoluteSegment;
57
+ };
58
+ export default absolutizeSegment;
@@ -0,0 +1,33 @@
1
+ import paramsParser from '../parser/paramsParser';
2
+ import type { PathArray, PathCommand, PathSegment, IteratorCallback } from '../types';
3
+
4
+ const iterate = <T extends PathArray>(path: PathArray, iterator: IteratorCallback) => {
5
+ const allPathCommands = [] as PathCommand[];
6
+ const params = { ...paramsParser };
7
+ let pathLen = path.length;
8
+ let segment: PathSegment;
9
+ let pathCommand = 'M' as PathCommand;
10
+
11
+ for (let i = 0; i < pathLen; i += 1) {
12
+ segment = path[i];
13
+ [pathCommand] = segment;
14
+ allPathCommands[i] = pathCommand;
15
+ const iteratorResult = iterator(segment, params, i);
16
+ path[i] = iteratorResult;
17
+
18
+ if (iteratorResult[0] === 'C') {
19
+ pathLen = path.length;
20
+ }
21
+
22
+ segment = path[i];
23
+ const seglen = segment.length;
24
+ params.x1 = +segment[seglen - 2];
25
+ params.y1 = +segment[seglen - 1];
26
+ params.x2 = +segment[seglen - 4] || params.x1;
27
+ params.y2 = +segment[seglen - 3] || params.y1;
28
+ }
29
+ // console.log('iteration: ', ...path)
30
+ return path as T;
31
+ };
32
+
33
+ export default iterate;
@@ -1,8 +1,8 @@
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
- import type { NormalArray, PathArray } from '../types';
2
+ import type { AbsoluteCommand, NormalArray, PathArray, PointTuple } from '../types';
3
+ import iterate from './iterate';
4
+ import parsePathString from '../parser/parsePathString';
5
+ import absolutizeSegment from './absolutizeSegment';
6
6
 
7
7
  /**
8
8
  * Normalizes a `path` object for further processing:
@@ -12,33 +12,39 @@ import type { NormalArray, PathArray } from '../types';
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
- }
15
+ const normalizePath = (pathInput: string | PathArray) => {
16
+ let x = 0;
17
+ let y = 0;
18
+ let mx = 0;
19
+ let my = 0;
20
+ let pathCommand = 'M';
19
21
 
20
- const path = pathToAbsolute(pathInput);
21
- const params = { ...paramsParser };
22
- const allPathCommands = [];
23
- const ii = path.length;
24
- let pathCommand = '';
22
+ return iterate<NormalArray>(parsePathString(pathInput), (seg, params) => {
23
+ const absoluteSegment = absolutizeSegment(seg, params);
24
+ const result = normalizeSegment(absoluteSegment, params);
25
+ [pathCommand] = result;
26
+ const absCommand = pathCommand.toUpperCase() as AbsoluteCommand;
25
27
 
26
- for (let i = 0; i < ii; i += 1) {
27
- [pathCommand] = path[i];
28
+ if (absCommand === 'Z') {
29
+ x = mx;
30
+ y = my;
31
+ } else {
32
+ [x, y] = result.slice(-2) as PointTuple;
28
33
 
29
- // Save current path command
30
- allPathCommands[i] = pathCommand;
31
- path[i] = normalizeSegment(path[i], params);
34
+ if (absCommand === 'M') {
35
+ mx = x;
36
+ my = y;
37
+ }
38
+ }
32
39
 
33
- const segment = path[i];
34
- const seglen = segment.length;
35
-
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
- }
41
-
42
- return path as NormalArray;
40
+ // const seglen = result.length;
41
+ // params.x1 = +result[seglen - 2];
42
+ // params.y1 = +result[seglen - 1];
43
+ // params.x2 = +result[seglen - 4] || params.x1;
44
+ // params.y2 = +result[seglen - 3] || params.y1;
45
+ params.x = x;
46
+ params.y = y;
47
+ return result;
48
+ });
43
49
  };
44
50
  export default normalizePath;
@@ -1,5 +1,5 @@
1
1
  import type { ParserParams } from '../interface';
2
- import type { NormalSegment, PathSegment } from '../types';
2
+ import type { NormalSegment, PointTuple, PathSegment, QSegment, CSegment, LSegment } from '../types';
3
3
 
4
4
  /**
5
5
  * Normalizes a single segment of a `pathArray` object.
@@ -8,11 +8,10 @@ import type { NormalSegment, PathSegment } from '../types';
8
8
  * @param params the coordinates of the previous segment
9
9
  * @returns the normalized segment
10
10
  */
11
- const normalizeSegment = (segment: PathSegment, params: ParserParams): NormalSegment => {
11
+ const normalizeSegment = (segment: PathSegment, params: ParserParams) => {
12
12
  const [pathCommand] = segment;
13
13
  const { x1: px1, y1: py1, x2: px2, y2: py2 } = params;
14
14
  const values = segment.slice(1).map(Number);
15
- let result = segment;
16
15
 
17
16
  if (!'TQ'.includes(pathCommand)) {
18
17
  // optional but good to be cautious
@@ -21,27 +20,27 @@ const normalizeSegment = (segment: PathSegment, params: ParserParams): NormalSeg
21
20
  }
22
21
 
23
22
  if (pathCommand === 'H') {
24
- result = ['L', segment[1], py1];
23
+ return ['L', segment[1], py1] as LSegment;
25
24
  } else if (pathCommand === 'V') {
26
- result = ['L', px1, segment[1]];
25
+ return ['L', px1, segment[1]] as LSegment;
27
26
  } else if (pathCommand === 'S') {
28
27
  const x1 = px1 * 2 - px2;
29
28
  const y1 = py1 * 2 - py2;
30
29
  params.x1 = x1;
31
30
  params.y1 = y1;
32
- result = ['C', x1, y1, ...(values as [number, number, number, number])];
31
+ return ['C', x1, y1, ...values] as CSegment;
33
32
  } else if (pathCommand === 'T') {
34
33
  const qx = px1 * 2 - (params.qx ? params.qx : /* istanbul ignore next */ 0);
35
34
  const qy = py1 * 2 - (params.qy ? params.qy : /* istanbul ignore next */ 0);
36
35
  params.qx = qx;
37
36
  params.qy = qy;
38
- result = ['Q', qx, qy, ...(values as [number, number])];
37
+ return ['Q', qx, qy, ...values] as QSegment;
39
38
  } else if (pathCommand === 'Q') {
40
- const [nqx, nqy] = values as [number, number];
39
+ const [nqx, nqy] = values as PointTuple;
41
40
  params.qx = nqx;
42
41
  params.qy = nqy;
43
42
  }
44
43
 
45
- return result as NormalSegment;
44
+ return segment as NormalSegment;
46
45
  };
47
46
  export default normalizeSegment;
@@ -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
@@ -34,7 +35,7 @@ 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
40
  const [x, y, z] = translatePoint(m, [...point2D, 0, 1]);
40
41
 
@@ -0,0 +1,61 @@
1
+ import type { ParserParams } from '../interface';
2
+ import type {
3
+ RelativeSegment,
4
+ RelativeCommand,
5
+ PathSegment,
6
+ aSegment,
7
+ vSegment,
8
+ hSegment,
9
+ qSegment,
10
+ tSegment,
11
+ sSegment,
12
+ cSegment,
13
+ } from '../types';
14
+
15
+ /**
16
+ * Returns a relative segment of a `PathArray` object.
17
+ *
18
+ * @param segment the segment object
19
+ * @param params the coordinates of the previous segment
20
+ * @param index the segment index
21
+ * @returns the absolute segment
22
+ */
23
+ const relativizeSegment = (segment: PathSegment, params: ParserParams, index: number) => {
24
+ const [pathCommand] = segment;
25
+ const { x, y } = params;
26
+ const values = segment.slice(1).map(Number);
27
+ const relCommand = pathCommand.toLowerCase() as RelativeCommand;
28
+
29
+ if (index === 0 && pathCommand === 'M') {
30
+ return segment;
31
+ }
32
+
33
+ /* istanbul ignore else @preserve */
34
+ if (pathCommand !== relCommand) {
35
+ if (relCommand === 'a') {
36
+ return [
37
+ relCommand,
38
+ values[0],
39
+ values[1],
40
+ values[2],
41
+ values[3],
42
+ values[4],
43
+ values[5] - x,
44
+ values[6] - y,
45
+ ] as aSegment;
46
+ } else if (relCommand === 'v') {
47
+ return [relCommand, values[0] - y] as vSegment;
48
+ } else if (relCommand === 'h') {
49
+ return [relCommand, values[0] - x] as hSegment;
50
+ } else {
51
+ // use brakets for `eslint: no-case-declaration`
52
+ // https://stackoverflow.com/a/50753272/803358
53
+ const relValues = values.map((n, j) => n - (j % 2 ? y : x));
54
+ // for n, l, c, s, q, t
55
+ return [relCommand, ...relValues] as qSegment | tSegment | sSegment | cSegment;
56
+ }
57
+ }
58
+
59
+ return segment as RelativeSegment;
60
+ };
61
+ export default relativizeSegment;
@@ -9,7 +9,7 @@ import type {
9
9
  SSegment,
10
10
  TSegment,
11
11
  VSegment,
12
- } from 'src/types';
12
+ } from '../types';
13
13
  import pathToAbsolute from '../convert/pathToAbsolute';
14
14
  import normalizePath from './normalizePath';
15
15
 
@@ -1,5 +1,6 @@
1
- import type { PathArray } from 'src/types';
1
+ import type { PathArray, PathSegment } from '../types';
2
2
  import defaultOptions from '../options/options';
3
+ import iterate from './iterate';
3
4
 
4
5
  /**
5
6
  * Rounds the values of a `pathArray` instance to
@@ -9,21 +10,18 @@ import defaultOptions from '../options/options';
9
10
  * @param roundOption the amount of decimals to round numbers to
10
11
  * @returns the resulted `pathArray` with rounded values
11
12
  */
12
- const roundPath = (path: PathArray, roundOption?: number | 'off'): PathArray => {
13
+ const roundPath = (path: PathArray, roundOption?: number | 'off') => {
13
14
  let { round } = defaultOptions;
14
- if (roundOption === 'off' || round === 'off') return [...path];
15
+ if (roundOption === 'off' || round === 'off') return path.slice(0) as PathArray;
15
16
  // allow for ZERO decimals
16
17
  round = typeof roundOption === 'number' && roundOption >= 0 ? roundOption : round;
17
18
  // to round values to the power
18
19
  // the `round` value must be integer
19
20
  const pow = typeof round === 'number' && round >= 1 ? 10 ** round : 1;
20
21
 
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;
22
+ return iterate<typeof path>(path, segment => {
23
+ const values = (segment.slice(1) as number[]).map(n => (round ? Math.round(n * pow) / pow : Math.round(n)));
24
+ return [segment[0], ...values] as PathSegment;
25
+ });
28
26
  };
29
27
  export default roundPath;
@@ -11,7 +11,7 @@ import type { ParserParams } from '../interface';
11
11
  * @param params the source segment parameters
12
12
  * @returns the cubic-bezier segment
13
13
  */
14
- const segmentToCubic = (segment: PathSegment, params: ParserParams): MSegment | CSegment => {
14
+ const segmentToCubic = (segment: PathSegment, params: ParserParams) => {
15
15
  const [pathCommand] = segment;
16
16
  const values = segment.slice(1).map(Number);
17
17
  const [x, y] = values;
@@ -1,4 +1,4 @@
1
- import type { ParserParams } from 'src/interface';
1
+ import type { ParserParams } from '../interface';
2
2
  import type {
3
3
  AbsoluteSegment,
4
4
  HSegment,
@@ -28,8 +28,8 @@ const shortenSegment = (
28
28
  ): ShortSegment => {
29
29
  const [pathCommand] = segment;
30
30
  const round4 = (n: number) => Math.round(n * 10 ** 4) / 10 ** 4;
31
- const segmentValues = segment.slice(1).map(n => +n);
32
- const normalValues = normalSegment.slice(1).map(n => +n);
31
+ const segmentValues = segment.slice(1) as number[];
32
+ const normalValues = normalSegment.slice(1) as number[];
33
33
  const { x1: px1, y1: py1, x2: px2, y2: py2, x: px, y: py } = params;
34
34
  let result = segment;
35
35
  const [x, y] = normalValues.slice(-2);