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
@@ -11,11 +11,11 @@ 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;
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): MSegment |
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 type { ParserParams } from 'src/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';
1
+ import defaultOptions from '../options/options';
2
+ import type { ParserParams } from '../interface';
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).map(n => +n);
32
- const normalValues = normalSegment.slice(1).map(n => +n);
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);
22
+ const { round: defaultRound } = defaultOptions;
23
+ const round = typeof defaultRound === 'number' ? defaultRound : /* istanbul ignore next */ 4;
24
+ const normalValues = normalSegment.slice(1) as number[];
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;
@@ -1,18 +1,19 @@
1
1
  import midPoint from '../math/midPoint';
2
- import type { CubicSegment } from '../types';
2
+ import type { CubicSegment, PointTuple } from '../types';
3
3
 
4
4
  /**
5
5
  * Split a cubic-bezier segment into two.
6
6
  *
7
7
  * @param pts the cubic-bezier parameters
8
+ * @param ratio the cubic-bezier parameters
8
9
  * @return two new cubic-bezier segments
9
10
  */
10
- const splitCubic = (pts: number[] /* , ratio */): [CubicSegment, CubicSegment] => {
11
- const t = /* ratio || */ 0.5;
12
- const p0 = pts.slice(0, 2) as [number, number];
13
- const p1 = pts.slice(2, 4) as [number, number];
14
- const p2 = pts.slice(4, 6) as [number, number];
15
- const p3 = pts.slice(6, 8) as [number, number];
11
+ const splitCubic = (pts: number[], ratio = 0.5): [CubicSegment, CubicSegment] => {
12
+ const t = ratio;
13
+ const p0 = pts.slice(0, 2) as PointTuple;
14
+ const p1 = pts.slice(2, 4) as PointTuple;
15
+ const p2 = pts.slice(4, 6) as PointTuple;
16
+ const p3 = pts.slice(6, 8) as PointTuple;
16
17
  const p4 = midPoint(p0, p1, t);
17
18
  const p5 = midPoint(p1, p2, t);
18
19
  const p6 = midPoint(p2, p3, t);
@@ -21,8 +22,8 @@ const splitCubic = (pts: number[] /* , ratio */): [CubicSegment, CubicSegment] =
21
22
  const p9 = midPoint(p7, p8, t);
22
23
 
23
24
  return [
24
- ['C', ...p4, ...p7, ...p9],
25
- ['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]],
26
27
  ];
27
28
  };
28
29
  export default splitCubic;
@@ -1,4 +1,5 @@
1
- import type { PathArray } from '../types';
1
+ import paramsParser from '../parser/paramsParser';
2
+ import type { AbsoluteCommand, HSegment, MSegment, PathArray, PointTuple, RelativeCommand, VSegment } from '../types';
2
3
 
3
4
  /**
4
5
  * Split a path into an `Array` of sub-path strings.
@@ -7,20 +8,53 @@ import type { PathArray } from '../types';
7
8
  * for visual consistency.
8
9
  *
9
10
  * @param pathInput the source `pathArray`
10
- * @return {SVGPath.pathArray[]} an array with all sub-path strings
11
+ * @return an array with all sub-path strings
11
12
  */
12
13
  const splitPath = (pathInput: PathArray): PathArray[] => {
13
14
  const composite = [] as PathArray[];
14
15
  let path: PathArray;
15
16
  let pi = -1;
17
+ let x = 0;
18
+ let y = 0;
19
+ let mx = 0;
20
+ let my = 0;
21
+ const params = { ...paramsParser };
16
22
 
17
23
  pathInput.forEach(seg => {
18
- if (seg[0] === 'M') {
19
- path = [seg];
24
+ const [pathCommand] = seg;
25
+ const absCommand = pathCommand.toUpperCase() as AbsoluteCommand;
26
+ const relCommand = pathCommand.toLowerCase() as RelativeCommand;
27
+ const isRelative = pathCommand === relCommand;
28
+ const values = seg.slice(1) as number[];
29
+
30
+ if (absCommand === 'M') {
20
31
  pi += 1;
32
+ [x, y] = values as PointTuple;
33
+ x += isRelative ? params.x : 0;
34
+ y += isRelative ? params.y : 0;
35
+ mx = x;
36
+ my = y;
37
+ path = [(isRelative ? [absCommand, mx, my] : seg) as MSegment];
21
38
  } else {
39
+ if (absCommand === 'Z') {
40
+ x = mx;
41
+ y = my;
42
+ } else if (absCommand === 'H') {
43
+ [, x] = seg as HSegment;
44
+ x += isRelative ? params.x : /* istanbul ignore next @preserve */ 0;
45
+ } else if (absCommand === 'V') {
46
+ [, y] = seg as VSegment;
47
+ y += isRelative ? params.y : /* istanbul ignore next @preserve */ 0;
48
+ } else {
49
+ [x, y] = seg.slice(-2) as PointTuple;
50
+ x += isRelative ? params.x : 0;
51
+ y += isRelative ? params.y : 0;
52
+ }
22
53
  path.push(seg);
23
54
  }
55
+
56
+ params.x = x;
57
+ params.y = y;
24
58
  composite[pi] = path;
25
59
  });
26
60
 
@@ -1,14 +1,14 @@
1
- import normalizePath from './normalizePath';
2
- // import pathToAbsolute from '../convert/pathToAbsolute';
3
- // import segmentToCubic from './segmentToCubic';
4
- // import fixArc from './fixArc';
5
1
  import getSVGMatrix from './getSVGMatrix';
6
2
  import projection2d from './projection2d';
7
- import paramsParser from '../parser/paramsParser';
8
- import replaceArc from './replaceArc';
9
3
  import defaultOptions from '../options/options';
10
- import type { AbsoluteArray, PathArray, TransformObjectValues } from '../types';
11
- import type { PathTransform, TransformObject } from '../interface';
4
+ import type { AbsoluteArray, AbsoluteSegment, CSegment, LSegment, PathArray, TransformObjectValues } from '../types';
5
+ import type { TransformObject } from '../interface';
6
+ import iterate from './iterate';
7
+ import parsePathString from '../parser/parsePathString';
8
+ import segmentToCubic from './segmentToCubic';
9
+ import normalizeSegment from './normalizeSegment';
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.
@@ -20,89 +20,91 @@ import type { PathTransform, TransformObject } from '../interface';
20
20
  * @param transform the transform functions `Object`
21
21
  * @returns the resulted `pathArray`
22
22
  */
23
- const transformPath = (path: string | PathArray, transform?: Partial<TransformObject>): PathArray => {
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 i;
27
- let j;
28
- let ii;
29
- let jj;
30
- let lx;
31
- let ly;
32
- // REPLACE Arc path commands with Cubic Beziers
33
- // we don't have any scripting know-how on 3d ellipse transformation
34
- // Arc segments don't work with 3D transformations or skews
35
- const absolutePath = replaceArc(path);
27
+ // new x and y transformed
28
+ let lx = 0;
29
+ let ly = 0;
30
+ // segment params iteration index and length
31
+ let j = 0;
32
+ let jj = 0;
33
+ let pathCommand = 'M';
34
+ // transform uses it's own set of params
35
+ const transformParams = { ...paramsParser };
36
+ const path = parsePathString(pathInput);
36
37
  const transformProps = transform && Object.keys(transform);
37
38
 
38
39
  // when used as a static method, invalidate somehow
39
- if (!transform || (transformProps && !transformProps.length)) return absolutePath.slice(0) as PathArray;
40
+ if (!transform || (transformProps && !transformProps.length)) return path.slice(0) as typeof path;
40
41
 
41
- const normalizedPath = normalizePath(absolutePath);
42
42
  // transform origin is extremely important
43
43
  if (!transform.origin) {
44
- const { origin: defaultOrigin } = defaultOptions;
45
- Object.assign(transform, { origin: defaultOrigin });
44
+ Object.assign(transform, { origin: defaultOptions.origin });
46
45
  }
46
+ const origin = transform.origin as [number, number, number];
47
47
  const matrixInstance = getSVGMatrix(transform as TransformObjectValues);
48
- const { origin } = transform;
49
- const params = { ...paramsParser };
50
- let segment = [];
51
- let seglen = 0;
52
- let pathCommand = '';
53
- const transformedPath = [] as PathTransform[];
54
48
 
55
- if (!matrixInstance.isIdentity) {
56
- for (i = 0, ii = absolutePath.length; i < ii; i += 1) {
57
- segment = normalizedPath[i];
58
- seglen = segment.length;
49
+ if (matrixInstance.isIdentity) return path.slice(0) as typeof path;
59
50
 
60
- params.x1 = +segment[seglen - 2];
61
- params.y1 = +segment[seglen - 1];
62
- params.x2 = +segment[seglen - 4] || params.x1;
63
- params.y2 = +segment[seglen - 3] || params.y1;
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);
64
60
 
65
- const result = {
66
- s: absolutePath[i],
67
- c: absolutePath[i][0],
68
- x: params.x1,
69
- y: params.y1,
70
- };
61
+ let result =
62
+ absCommand === 'A'
63
+ ? segmentToCubic(absoluteSegment, transformParams)
64
+ : ['V', 'H'].includes(absCommand)
65
+ ? normalizeSegment(absoluteSegment, transformParams)
66
+ : absoluteSegment;
71
67
 
72
- transformedPath.push(result);
73
- }
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;
74
72
 
75
- return transformedPath.map(seg => {
76
- pathCommand = seg.c;
77
- segment = seg.s;
78
- if (pathCommand === 'L' || pathCommand === 'H' || pathCommand === 'V') {
79
- [lx, ly] = projection2d(matrixInstance, [seg.x, seg.y], origin as [number, number, number]);
73
+ if (isLongArc) {
74
+ path.splice(index + 1, 0, ['C' as typeof pathCommand | number].concat(result.slice(7)) as CSegment);
75
+ result = tempSegment as CSegment;
76
+ }
80
77
 
81
- /* istanbul ignore else @preserve */
82
- if (x !== lx && y !== ly) {
83
- segment = ['L', lx, ly];
84
- } else if (y === ly) {
85
- segment = ['H', lx];
86
- } else if (x === lx) {
87
- segment = ['V', ly];
88
- }
78
+ if (pathCommand === 'L') {
79
+ [lx, ly] = projection2d(matrixInstance, [(result as LSegment)[1], (result as LSegment)[2]], origin);
89
80
 
90
- // now update x and y
91
- x = lx;
92
- y = ly;
81
+ /* istanbul ignore else @preserve */
82
+ if (x !== lx && y !== ly) {
83
+ result = ['L', lx, ly];
84
+ } else if (y === ly) {
85
+ result = ['H', lx];
86
+ } else if (x === lx) {
87
+ result = ['V', ly];
88
+ }
89
+ } else {
90
+ for (j = 1, jj = result.length; j < jj; j += 2) {
91
+ [lx, ly] = projection2d(matrixInstance, [+result[j], +result[j + 1]], origin);
92
+ result[j] = lx;
93
+ result[j + 1] = ly;
94
+ }
95
+ }
96
+ // now update x and y
97
+ x = lx;
98
+ y = ly;
93
99
 
94
- return segment;
95
- } else {
96
- for (j = 1, jj = segment.length; j < jj; j += 2) {
97
- [x, y] = projection2d(matrixInstance, [+segment[j], +segment[j + 1]], origin as [number, number, number]);
98
- segment[j] = x;
99
- segment[j + 1] = y;
100
- }
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;
101
105
 
102
- return segment;
103
- }
104
- }) as PathArray;
105
- }
106
- return absolutePath.slice(0) as AbsoluteArray;
106
+ return result;
107
+ });
107
108
  };
109
+
108
110
  export default transformPath;
package/src/types.ts CHANGED
@@ -196,3 +196,33 @@ export type Point = {
196
196
  x: number;
197
197
  y: number;
198
198
  };
199
+
200
+ export type PointTuple = [number, number];
201
+
202
+ export type DerivedPoint = Point & { t: number };
203
+ export type QuadPoints = [Point, Point, Point, Point, Point, Point];
204
+ export type CubicPoints = [Point, Point, Point, Point, Point, Point, Point, Point];
205
+ export type DerivedQuadPoints = [DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint];
206
+ export type DerivedCubicPoints = [
207
+ DerivedPoint,
208
+ DerivedPoint,
209
+ DerivedPoint,
210
+ DerivedPoint,
211
+ DerivedPoint,
212
+ DerivedPoint,
213
+ DerivedPoint,
214
+ DerivedPoint,
215
+ ];
216
+ export type QuadCoordinates = [number, number, number, number, number, number];
217
+ export type CubicCoordinates = [number, number, number, number, number, number, number, number];
218
+ export type ArcCoordinates = [number, number, number, number, number, number, number, number, number];
219
+ export type LineCoordinates = [number, number, number, number];
220
+
221
+ export type DeriveCallback = (t: number) => Point;
222
+
223
+ export type IteratorCallback = (
224
+ segment: PathSegment,
225
+ index: number,
226
+ lastX: number,
227
+ lastY: number,
228
+ ) => PathSegment | false | void | undefined;
@@ -1,5 +1,5 @@
1
1
  import pathToCurve from '../convert/pathToCurve';
2
- import type { PathArray } 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,8 +60,8 @@ 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 [number, number, number, number, number, number]));
64
- [x, y] = seg.slice(-2) as [number, number];
63
+ len = getCubicSegArea(x, y, seg[1], seg[2], seg[3], seg[4], seg[5], seg[6]);
64
+ [x, y] = seg.slice(-2) as PointTuple;
65
65
  return len;
66
66
  }
67
67
  })
@@ -1,15 +1,16 @@
1
- import { PathBBox } from 'src/interface';
2
- import { PathArray } from 'src/types';
3
- import pathFactory from './pathFactory';
4
-
5
- /**
6
- * Returns the bounding box of a shape.
7
- *
8
- * @param path the shape `pathArray`
9
- * @returns the length of the cubic-bezier segment
10
- */
11
- const getPathBBox = (path: PathArray | string): PathBBox => {
12
- if (!path) {
1
+ import iterate from '../process/iterate';
2
+ import { PathBBox } from '../interface';
3
+ import { MSegment, PathArray, Point } from '../types';
4
+ import { getLineBBox } from '../math/lineTools';
5
+ import { getArcBBox } from '../math/arcTools';
6
+ import { getCubicBBox } from '../math/cubicTools';
7
+ import { getQuadBBox } from '../math/quadTools';
8
+ import parsePathString from '../parser/parsePathString';
9
+ import paramsParser from '../parser/paramsParser';
10
+ import normalizeSegment from '../process/normalizeSegment';
11
+
12
+ const getPathBBox = (pathInput: PathArray | string) => {
13
+ if (!pathInput) {
13
14
  return {
14
15
  x: 0,
15
16
  y: 0,
@@ -23,12 +24,60 @@ const getPathBBox = (path: PathArray | string): PathBBox => {
23
24
  };
24
25
  }
25
26
 
26
- const props = pathFactory(path);
27
- const {
28
- min: { x: xMin, y: yMin },
29
- max: { x: xMax, y: yMax },
30
- } = props.bbox;
27
+ const path = parsePathString(pathInput);
28
+ let data = [] as number[];
29
+ let pathCommand = 'M';
30
+ const x = 0;
31
+ const y = 0;
32
+ let mx = 0;
33
+ let my = 0;
34
+ const MIN = [] as Point[];
35
+ const MAX = [] as Point[];
36
+ let min = { x, y };
37
+ let max = { x, y };
38
+ const params = { ...paramsParser };
39
+
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[]);
46
+
47
+ // this segment is always ZERO
48
+ /* istanbul ignore else @preserve */
49
+ if (pathCommand === 'M') {
50
+ // remember mx, my for Z
51
+ [, mx, my] = result as MSegment;
52
+ min = { x: mx, y: my };
53
+ max = { x: mx, y: my };
54
+ } else if (pathCommand === 'L') {
55
+ ({ min, max } = getLineBBox(data[0], data[1], data[2], data[3]));
56
+ } else if (pathCommand === 'A') {
57
+ ({ min, max } = getArcBBox(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8]));
58
+ } else if (pathCommand === 'C') {
59
+ ({ min, max } = getCubicBBox(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]));
60
+ } else if (pathCommand === 'Q') {
61
+ ({ min, max } = getQuadBBox(data[0], data[1], data[2], data[3], data[4], data[5]));
62
+ } else if (pathCommand === 'Z') {
63
+ data = [lastX, lastY, mx, my];
64
+ ({ min, max } = getLineBBox(data[0], data[1], data[2], data[3]));
65
+ }
31
66
 
67
+ MIN.push(min);
68
+ MAX.push(max);
69
+
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;
75
+ });
76
+
77
+ const xMin = Math.min(...MIN.map(n => n.x));
78
+ const xMax = Math.max(...MAX.map(n => n.x));
79
+ const yMin = Math.min(...MIN.map(n => n.y));
80
+ const yMax = Math.max(...MAX.map(n => n.y));
32
81
  const width = xMax - xMin;
33
82
  const height = yMax - yMin;
34
83
 
@@ -41,8 +90,9 @@ const getPathBBox = (path: PathArray | string): PathBBox => {
41
90
  y2: yMax,
42
91
  cx: xMin + width / 2,
43
92
  cy: yMin + height / 2,
44
- // an estimted guess
93
+ // an estimated guess
45
94
  cz: Math.max(width, height) + Math.min(width, height) / 2,
46
- };
95
+ } satisfies PathBBox;
47
96
  };
97
+
48
98
  export default getPathBBox;