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,4 +1,3 @@
1
- import type { ParserParams } from '../interface';
2
1
  import type {
3
2
  RelativeSegment,
4
3
  RelativeCommand,
@@ -10,52 +9,51 @@ import type {
10
9
  tSegment,
11
10
  sSegment,
12
11
  cSegment,
12
+ MSegment,
13
+ lSegment,
13
14
  } from '../types';
14
15
 
15
16
  /**
16
17
  * Returns a relative segment of a `PathArray` object.
17
18
  *
18
19
  * @param segment the segment object
19
- * @param params the coordinates of the previous segment
20
20
  * @param index the segment index
21
- * @returns the absolute segment
21
+ * @param lastX the last known X value
22
+ * @param lastY the last known Y value
23
+ * @returns the relative segment
22
24
  */
23
- const relativizeSegment = (segment: PathSegment, params: ParserParams, index: number) => {
25
+ const relativizeSegment = (segment: PathSegment, index: number, lastX: number, lastY: number) => {
24
26
  const [pathCommand] = segment;
25
- const { x, y } = params;
26
- const values = segment.slice(1).map(Number);
27
27
  const relCommand = pathCommand.toLowerCase() as RelativeCommand;
28
-
29
- if (index === 0 && pathCommand === 'M') {
30
- return segment;
31
- }
28
+ const isRelative = pathCommand === relCommand;
32
29
 
33
30
  /* 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
- }
31
+ if (index === 0 || isRelative) return segment as MSegment | RelativeSegment;
58
32
 
59
- return segment as RelativeSegment;
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
+ }
60
57
  };
58
+
61
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,6 +5,7 @@ import type {
5
5
  MSegment,
6
6
  PathArray,
7
7
  PathSegment,
8
+ PointTuple,
8
9
  QSegment,
9
10
  SSegment,
10
11
  TSegment,
@@ -12,90 +13,102 @@ import type {
12
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,6 +1,7 @@
1
- import type { PathArray, PathSegment } from '../types';
1
+ import type { PathArray } from '../types';
2
2
  import defaultOptions from '../options/options';
3
3
  import iterate from './iterate';
4
+ import roundSegment from './roundSegment';
4
5
 
5
6
  /**
6
7
  * Rounds the values of a `pathArray` instance to
@@ -12,16 +13,21 @@ import iterate from './iterate';
12
13
  */
13
14
  const roundPath = (path: PathArray, roundOption?: number | 'off') => {
14
15
  let { round } = defaultOptions;
15
- if (roundOption === 'off' || round === 'off') return path.slice(0) as PathArray;
16
16
  // allow for ZERO decimals
17
- round = typeof roundOption === 'number' && roundOption >= 0 ? roundOption : round;
18
- // to round values to the power
19
- // the `round` value must be integer
20
- 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';
25
+
26
+ /* istanbul ignore else @preserve */
27
+ if (round === 'off') return path.slice(0) as PathArray;
21
28
 
22
29
  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;
30
+ return roundSegment(segment, round);
25
31
  });
26
32
  };
27
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;
@@ -15,7 +15,7 @@ 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;
18
- let args;
18
+ // let args;
19
19
  const { x1: px1, y1: py1, x: px, y: py } = params;
20
20
 
21
21
  if (!'TQ'.includes(pathCommand)) {
@@ -28,17 +28,19 @@ const segmentToCubic = (segment: PathSegment, params: ParserParams) => {
28
28
  params.y = y;
29
29
  return segment;
30
30
  } else if (pathCommand === 'A') {
31
- args = [px1, py1, ...values] as [number, number, number, number, number, number, number, number, number];
32
- return ['C', ...arcToCubic(...args)] as CSegment;
31
+ return ['C' as string | number].concat(
32
+ arcToCubic(px1, py1, values[0], values[1], values[2], values[3], values[4], values[5], values[6]),
33
+ ) as CSegment;
33
34
  } else if (pathCommand === 'Q') {
34
35
  params.qx = x;
35
36
  params.qy = y;
36
- args = [px1, py1, ...values] as [number, number, number, number, number, number];
37
- return ['C', ...quadToCubic(...args)] as CSegment;
37
+ return ['C' as string | number].concat(
38
+ quadToCubic(px1, py1, values[0], values[1], values[2], values[3]),
39
+ ) as CSegment;
38
40
  } else if (pathCommand === 'L') {
39
- return ['C', ...lineToCubic(px1, py1, x, y)] as CSegment;
41
+ return ['C' as string | number].concat(lineToCubic(px1, py1, x, y)) as CSegment;
40
42
  } else if (pathCommand === 'Z') {
41
- return ['C', ...lineToCubic(px1, py1, px, py)] as CSegment;
43
+ return ['C' as string | number].concat(lineToCubic(px1, py1, px, py)) as CSegment;
42
44
  }
43
45
 
44
46
  return segment as MSegment | CSegment;
@@ -1,15 +1,7 @@
1
+ import defaultOptions from '../options/options';
1
2
  import type { ParserParams } from '../interface';
2
- import type {
3
- AbsoluteSegment,
4
- HSegment,
5
- NormalSegment,
6
- PathCommand,
7
- ShortSegment,
8
- SSegment,
9
- TSegment,
10
- VSegment,
11
- ZSegment,
12
- } from '../types';
3
+ import roundTo from '../math/roundTo';
4
+ import type { AbsoluteSegment, NormalSegment, PathCommand, ShortSegment, SSegment, TSegment } from '../types';
13
5
 
14
6
  /**
15
7
  * Shorten a single segment of a `pathArray` object.
@@ -27,12 +19,12 @@ const shortenSegment = (
27
19
  prevCommand: PathCommand,
28
20
  ): ShortSegment => {
29
21
  const [pathCommand] = segment;
30
- const round4 = (n: number) => Math.round(n * 10 ** 4) / 10 ** 4;
31
- const segmentValues = segment.slice(1) as number[];
22
+ const { round: defaultRound } = defaultOptions;
23
+ const round = typeof defaultRound === 'number' ? defaultRound : /* istanbul ignore next */ 4;
32
24
  const normalValues = normalSegment.slice(1) as number[];
33
- const { x1: px1, y1: py1, x2: px2, y2: py2, x: px, y: py } = params;
34
- let result = segment;
35
- const [x, y] = normalValues.slice(-2);
25
+ const { x1, y1, x2, y2, x, y } = params;
26
+ const [nx, ny] = normalValues.slice(-2);
27
+ const result = segment;
36
28
 
37
29
  if (!'TQ'.includes(pathCommand)) {
38
30
  // optional but good to be cautious
@@ -40,26 +32,24 @@ const shortenSegment = (
40
32
  params.qy = null;
41
33
  }
42
34
 
43
- if (['V', 'H', 'S', 'T', 'Z'].includes(pathCommand)) {
44
- result = [pathCommand, ...segmentValues] as VSegment | HSegment | SSegment | TSegment | ZSegment;
45
- } else if (pathCommand === 'L') {
46
- if (round4(px) === round4(x)) {
47
- result = ['V', y];
48
- } else if (round4(py) === round4(y)) {
49
- result = ['H', x];
35
+ if (pathCommand === 'L') {
36
+ if (roundTo(x, round) === roundTo(nx, round)) {
37
+ return ['V', ny];
38
+ } else if (roundTo(y, round) === roundTo(ny, round)) {
39
+ return ['H', nx];
50
40
  }
51
41
  } else if (pathCommand === 'C') {
52
- const [x1, y1] = normalValues;
42
+ const [nx1, ny1] = normalValues;
43
+ params.x1 = nx1;
44
+ params.y1 = ny1;
53
45
 
54
46
  if (
55
47
  'CS'.includes(prevCommand) &&
56
- ((round4(x1) === round4(px1 * 2 - px2) && round4(y1) === round4(py1 * 2 - py2)) ||
57
- (round4(px1) === round4(px2 * 2 - px) && round4(py1) === round4(py2 * 2 - py)))
48
+ ((roundTo(nx1, round) === roundTo(x1 * 2 - x2, round) && roundTo(ny1, round) === roundTo(y1 * 2 - y2, round)) ||
49
+ (roundTo(x1, round) === roundTo(x2 * 2 - x, round) && roundTo(y1, round) === roundTo(y2 * 2 - y, round)))
58
50
  ) {
59
- result = ['S', ...normalValues.slice(-4)] as SSegment;
51
+ return ['S', normalValues[2], normalValues[3], normalValues[4], normalValues[5]] as SSegment;
60
52
  }
61
- params.x1 = x1;
62
- params.y1 = y1;
63
53
  } else if (pathCommand === 'Q') {
64
54
  const [qx, qy] = normalValues;
65
55
  params.qx = qx;
@@ -67,13 +57,15 @@ const shortenSegment = (
67
57
 
68
58
  if (
69
59
  'QT'.includes(prevCommand) &&
70
- ((round4(qx) === round4(px1 * 2 - px2) && round4(qy) === round4(py1 * 2 - py2)) ||
71
- (round4(px1) === round4(px2 * 2 - px) && round4(py1) === round4(py2 * 2 - py)))
60
+ roundTo(qx, round) === roundTo(x1 * 2 - x2, round) &&
61
+ roundTo(qy, round) === roundTo(y1 * 2 - y2, round)
72
62
  ) {
73
- result = ['T', ...normalValues.slice(-2)] as TSegment;
63
+ return ['T', normalValues[2], normalValues[3]] as TSegment;
74
64
  }
75
65
  }
76
66
 
67
+ // ['V', 'H', 'S', 'T', 'Z'].includes(pathCommand)
77
68
  return result as ShortSegment;
78
69
  };
70
+
79
71
  export default shortenSegment;
@@ -22,8 +22,8 @@ const splitCubic = (pts: number[], ratio = 0.5): [CubicSegment, CubicSegment] =>
22
22
  const p9 = midPoint(p7, p8, t);
23
23
 
24
24
  return [
25
- ['C', ...p4, ...p7, ...p9],
26
- ['C', ...p8, ...p6, ...p3],
25
+ ['C', p4[0], p4[1], p7[0], p7[1], p9[0], p9[1]],
26
+ ['C', p8[0], p8[1], p6[0], p6[1], p3[0], p3[1]],
27
27
  ];
28
28
  };
29
29
  export default splitCubic;
@@ -1,14 +1,14 @@
1
1
  import getSVGMatrix from './getSVGMatrix';
2
2
  import projection2d from './projection2d';
3
3
  import defaultOptions from '../options/options';
4
- import type { AbsoluteArray, CSegment, PathArray, PointTuple, TransformObjectValues } from '../types';
4
+ import type { AbsoluteArray, AbsoluteSegment, CSegment, LSegment, PathArray, TransformObjectValues } from '../types';
5
5
  import type { TransformObject } from '../interface';
6
6
  import iterate from './iterate';
7
7
  import parsePathString from '../parser/parsePathString';
8
- import absolutizeSegment from './absolutizeSegment';
9
8
  import segmentToCubic from './segmentToCubic';
10
9
  import normalizeSegment from './normalizeSegment';
11
10
  import paramsParser from '../parser/paramsParser';
11
+ import absolutizeSegment from './absolutizeSegment';
12
12
 
13
13
  /**
14
14
  * Apply a 2D / 3D transformation to a `pathArray` instance.
@@ -21,16 +21,15 @@ import paramsParser from '../parser/paramsParser';
21
21
  * @returns the resulted `pathArray`
22
22
  */
23
23
  const transformPath = (pathInput: PathArray | string, transform?: Partial<TransformObject>) => {
24
+ // last x and y transformed values
24
25
  let x = 0;
25
26
  let y = 0;
26
- let mx = 0;
27
- let my = 0;
27
+ // new x and y transformed
28
28
  let lx = 0;
29
29
  let ly = 0;
30
+ // segment params iteration index and length
30
31
  let j = 0;
31
32
  let jj = 0;
32
- let nx = 0;
33
- let ny = 0;
34
33
  let pathCommand = 'M';
35
34
  // transform uses it's own set of params
36
35
  const transformParams = { ...paramsParser };
@@ -38,7 +37,7 @@ const transformPath = (pathInput: PathArray | string, transform?: Partial<Transf
38
37
  const transformProps = transform && Object.keys(transform);
39
38
 
40
39
  // when used as a static method, invalidate somehow
41
- if (!transform || (transformProps && !transformProps.length)) return path;
40
+ if (!transform || (transformProps && !transformProps.length)) return path.slice(0) as typeof path;
42
41
 
43
42
  // transform origin is extremely important
44
43
  if (!transform.origin) {
@@ -47,29 +46,37 @@ const transformPath = (pathInput: PathArray | string, transform?: Partial<Transf
47
46
  const origin = transform.origin as [number, number, number];
48
47
  const matrixInstance = getSVGMatrix(transform as TransformObjectValues);
49
48
 
50
- if (matrixInstance.isIdentity) return path;
49
+ if (matrixInstance.isIdentity) return path.slice(0) as typeof path;
51
50
 
52
- return iterate<AbsoluteArray>(path, (seg, _, i) => {
53
- const absSegment = absolutizeSegment(seg, transformParams);
54
- [pathCommand] = absSegment;
51
+ return iterate<AbsoluteArray>(path, (seg, index, lastX, lastY) => {
52
+ transformParams.x = lastX;
53
+ transformParams.y = lastY;
54
+ [pathCommand] = seg;
55
+ const absCommand = pathCommand.toUpperCase();
56
+ const isRelative = absCommand !== pathCommand;
57
+ const absoluteSegment = isRelative
58
+ ? absolutizeSegment(seg, index, lastX, lastY)
59
+ : (seg.slice(0) as AbsoluteSegment);
55
60
 
56
61
  let result =
57
- pathCommand === 'A'
58
- ? segmentToCubic(absSegment, transformParams)
59
- : ['V', 'H'].includes(pathCommand)
60
- ? normalizeSegment(absSegment, transformParams)
61
- : absSegment;
62
- const isLongArc = result[0] === 'C' && result.length > 7;
63
- const normalizedSegment = (isLongArc ? result.slice(0, 7) : result.slice(0)) as typeof result;
62
+ absCommand === 'A'
63
+ ? segmentToCubic(absoluteSegment, transformParams)
64
+ : ['V', 'H'].includes(absCommand)
65
+ ? normalizeSegment(absoluteSegment, transformParams)
66
+ : absoluteSegment;
67
+
68
+ // update pathCommand
69
+ pathCommand = result[0];
70
+ const isLongArc = pathCommand === 'C' && result.length > 7;
71
+ const tempSegment = (isLongArc ? result.slice(0, 7) : result.slice(0)) as AbsoluteSegment;
64
72
 
65
73
  if (isLongArc) {
66
- path.splice(i + 1, 0, ['C', ...result.slice(7)] as CSegment);
67
- result = result.slice(0, 7) as CSegment;
74
+ path.splice(index + 1, 0, ['C' as typeof pathCommand | number].concat(result.slice(7)) as CSegment);
75
+ result = tempSegment as CSegment;
68
76
  }
69
77
 
70
- if (result[0] === 'L') {
71
- const values = result.slice(-2) as PointTuple;
72
- [lx, ly] = projection2d(matrixInstance, values, origin);
78
+ if (pathCommand === 'L') {
79
+ [lx, ly] = projection2d(matrixInstance, [(result as LSegment)[1], (result as LSegment)[2]], origin);
73
80
 
74
81
  /* istanbul ignore else @preserve */
75
82
  if (x !== lx && y !== ly) {
@@ -90,24 +97,12 @@ const transformPath = (pathInput: PathArray | string, transform?: Partial<Transf
90
97
  x = lx;
91
98
  y = ly;
92
99
 
93
- if (pathCommand === 'Z') {
94
- nx = mx;
95
- ny = my;
96
- } else {
97
- [nx, ny] = normalizedSegment.slice(-2) as PointTuple;
98
- if (pathCommand === 'M') {
99
- mx = nx;
100
- my = ny;
101
- }
102
- }
100
+ const seglen = tempSegment.length;
101
+ transformParams.x1 = +tempSegment[seglen - 2];
102
+ transformParams.y1 = +tempSegment[seglen - 1];
103
+ transformParams.x2 = +tempSegment[seglen - 4] || transformParams.x1;
104
+ transformParams.y2 = +tempSegment[seglen - 3] || transformParams.y1;
103
105
 
104
- const seglen = normalizedSegment.length;
105
- transformParams.x1 = +normalizedSegment[seglen - 2];
106
- transformParams.y1 = +normalizedSegment[seglen - 1];
107
- transformParams.x2 = +normalizedSegment[seglen - 4] || transformParams.x1;
108
- transformParams.y2 = +normalizedSegment[seglen - 3] || transformParams.y1;
109
- transformParams.x = nx;
110
- transformParams.y = ny;
111
106
  return result;
112
107
  });
113
108
  };
package/src/types.ts CHANGED
@@ -1,13 +1,4 @@
1
- import type {
2
- LineAttr,
3
- CircleAttr,
4
- PolyAttr,
5
- RectAttr,
6
- EllipseAttr,
7
- GlyphAttr,
8
- TransformObject,
9
- ParserParams,
10
- } from './interface';
1
+ import type { LineAttr, CircleAttr, PolyAttr, RectAttr, EllipseAttr, GlyphAttr, TransformObject } from './interface';
11
2
 
12
3
  export type SpaceNumber =
13
4
  | 0x1680
@@ -229,4 +220,9 @@ export type LineCoordinates = [number, number, number, number];
229
220
 
230
221
  export type DeriveCallback = (t: number) => Point;
231
222
 
232
- export type IteratorCallback = (segment: PathSegment, params: ParserParams, index: number) => PathSegment;
223
+ export type IteratorCallback = (
224
+ segment: PathSegment,
225
+ index: number,
226
+ lastX: number,
227
+ lastY: number,
228
+ ) => PathSegment | false | void | undefined;