svg-path-commander 2.0.10 → 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 (75) hide show
  1. package/.eslintrc.cjs +1 -0
  2. package/README.md +64 -8
  3. package/dist/svg-path-commander.cjs +1 -1
  4. package/dist/svg-path-commander.cjs.map +1 -1
  5. package/dist/svg-path-commander.d.ts +236 -43
  6. package/dist/svg-path-commander.js +1 -1
  7. package/dist/svg-path-commander.js.map +1 -1
  8. package/dist/svg-path-commander.mjs +790 -650
  9. package/dist/svg-path-commander.mjs.map +1 -1
  10. package/package.json +20 -22
  11. package/src/convert/pathToAbsolute.ts +16 -70
  12. package/src/convert/pathToCurve.ts +36 -28
  13. package/src/convert/pathToRelative.ts +33 -62
  14. package/src/index.ts +37 -39
  15. package/src/interface.ts +33 -33
  16. package/src/math/arcTools.ts +394 -0
  17. package/src/math/bezier.ts +253 -0
  18. package/src/math/cubicTools.ts +122 -0
  19. package/src/math/distanceSquareRoot.ts +3 -1
  20. package/src/math/lineTools.ts +67 -0
  21. package/src/math/midPoint.ts +3 -1
  22. package/src/math/polygonArea.ts +3 -1
  23. package/src/math/polygonLength.ts +2 -1
  24. package/src/math/quadTools.ts +98 -0
  25. package/src/parser/isMoveCommand.ts +17 -0
  26. package/src/parser/parsePathString.ts +5 -5
  27. package/src/parser/scanSegment.ts +12 -3
  28. package/src/process/absolutizeSegment.ts +58 -0
  29. package/src/process/iterate.ts +33 -0
  30. package/src/process/normalizePath.ts +34 -28
  31. package/src/process/normalizeSegment.ts +8 -9
  32. package/src/process/projection2d.ts +2 -1
  33. package/src/process/relativizeSegment.ts +61 -0
  34. package/src/process/reversePath.ts +1 -1
  35. package/src/process/roundPath.ts +8 -10
  36. package/src/process/segmentToCubic.ts +1 -1
  37. package/src/process/shortenSegment.ts +3 -3
  38. package/src/process/splitCubic.ts +8 -7
  39. package/src/process/splitPath.ts +39 -5
  40. package/src/process/transformPath.ts +81 -94
  41. package/src/types.ts +40 -1
  42. package/src/util/distanceEpsilon.ts +3 -0
  43. package/src/util/getClosestPoint.ts +1 -1
  44. package/src/util/getPathArea.ts +3 -3
  45. package/src/util/getPathBBox.ts +86 -18
  46. package/src/util/getPointAtLength.ts +98 -4
  47. package/src/util/getPropertiesAtLength.ts +4 -3
  48. package/src/util/getPropertiesAtPoint.ts +4 -1
  49. package/src/util/getTotalLength.ts +71 -4
  50. package/src/util/isPointInStroke.ts +2 -1
  51. package/src/util/shapeToPathArray.ts +8 -4
  52. package/test/class.test.ts +502 -0
  53. package/test/fixtures/getMarkup.ts +17 -0
  54. package/{cypress → test}/fixtures/shapes.js +39 -39
  55. package/test/fixtures/simpleShapes.js +75 -0
  56. package/test/static.test.ts +324 -0
  57. package/tsconfig.json +9 -4
  58. package/{vite.config.ts → vite.config.mts} +10 -1
  59. package/vitest.config-ui.mts +26 -0
  60. package/vitest.config.mts +26 -0
  61. package/cypress/e2e/svg-path-commander.spec.ts +0 -868
  62. package/cypress/fixtures/simpleShapes.js +0 -75
  63. package/cypress/plugins/esbuild-istanbul.ts +0 -50
  64. package/cypress/plugins/tsCompile.ts +0 -34
  65. package/cypress/support/commands.ts +0 -37
  66. package/cypress/support/e2e.ts +0 -21
  67. package/cypress/test.html +0 -36
  68. package/cypress.config.ts +0 -29
  69. package/src/process/fixArc.ts +0 -23
  70. package/src/util/pathLengthFactory.ts +0 -114
  71. package/src/util/segmentArcFactory.ts +0 -219
  72. package/src/util/segmentCubicFactory.ts +0 -114
  73. package/src/util/segmentLineFactory.ts +0 -45
  74. package/src/util/segmentQuadFactory.ts +0 -109
  75. /package/{cypress/fixtures/shapeObjects.js → test/fixtures/shapeObjects.ts} +0 -0
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "svg-path-commander",
3
3
  "author": "thednp",
4
4
  "license": "MIT",
5
- "version": "2.0.10",
5
+ "version": "2.1.1",
6
6
  "description": "Modern TypeScript tools for SVG",
7
7
  "source": "./src/index.ts",
8
8
  "main": "./dist/svg-path-commander.js",
@@ -38,28 +38,24 @@
38
38
  },
39
39
  "homepage": "http://thednp.github.io/svg-path-commander",
40
40
  "devDependencies": {
41
- "@bahmutov/cypress-esbuild-preprocessor": "^2.2.2",
42
- "@cypress/code-coverage": "^3.12.44",
43
- "@types/istanbul-lib-instrument": "^1.7.7",
44
41
  "@typescript-eslint/eslint-plugin": "^5.62.0",
45
42
  "@typescript-eslint/parser": "^5.62.0",
46
- "cypress": "^13.13.2",
43
+ "@vitest/browser": "^2.1.2",
44
+ "@vitest/coverage-istanbul": "^2.1.2",
45
+ "@vitest/ui": "^2.1.2",
47
46
  "dts-bundle-generator": "^9.5.1",
48
- "eslint": "^8.57.0",
47
+ "eslint": "^8.57.1",
49
48
  "eslint-plugin-jsdoc": "^46.10.1",
50
49
  "eslint-plugin-prefer-arrow": "^1.2.3",
51
50
  "eslint-plugin-prettier": "^4.2.1",
52
- "istanbul-lib-coverage": "^3.2.2",
53
- "istanbul-lib-instrument": "^5.2.1",
54
- "ncp": "^2.0.0",
55
- "nyc": "^17.0.0",
51
+ "playwright": "^1.48.0",
56
52
  "prettier": "^2.8.8",
57
- "rimraf": "^6.0.1",
58
- "typescript": "^5.5.4",
59
- "vite": "^5.4.0"
53
+ "typescript": "^5.6.3",
54
+ "vite": "^5.4.8",
55
+ "vitest": "^2.1.2"
60
56
  },
61
57
  "dependencies": {
62
- "@thednp/dommatrix": "^2.0.7"
58
+ "@thednp/dommatrix": "^2.0.8"
63
59
  },
64
60
  "engines": {
65
61
  "node": ">=16",
@@ -67,17 +63,19 @@
67
63
  },
68
64
  "scripts": {
69
65
  "pre-test": "pnpm clean-coverage",
70
- "badges": "npx -p dependency-version-badge update-badge typescript cypress eslint prettier vite",
71
- "test": "pnpm pre-test && cypress run",
72
- "cypress": "pnpm pre-test && npx cypress open",
73
- "clean-coverage": "rimraf coverage .nyc_output",
74
- "coverage:report": "nyc report --reporter=lcov --reporter=json --reporter=text --reporter=json-summary",
66
+ "badges": "npx -p dependency-version-badge update-badge typescript eslint prettier vitest vite",
67
+ "dev": "vite serve docs --port 3000",
68
+ "test": "pnpm pre-test && vitest --config vitest.config.mts",
69
+ "test-ui": "pnpm pre-test && vitest --config vitest.config-ui.mts --browser=chrome",
70
+ "clean-coverage": "rm -rf coverage .nyc_output",
75
71
  "format": "prettier --write \"src/**/*.ts\"",
72
+ "lint": "pnpm lint:ts && pnpm check:ts",
76
73
  "fix:ts": "eslint src --config .eslintrc.cjs --fix",
77
74
  "lint:ts": "eslint src --config .eslintrc.cjs",
78
- "build": "pnpm lint:ts && pnpm build-vite && pnpm build-ts",
79
- "build-vite": "vite build && pnpm build-docs",
75
+ "check:ts": "tsc --noEmit",
76
+ "build": "pnpm lint && pnpm build-vite && pnpm build-ts",
77
+ "build-vite": "vite build && pnpm copy-docs",
80
78
  "build-ts": "dts-bundle-generator --config ./dts.config.ts",
81
- "build-docs": "ncp dist/svg-path-commander.js docs/svg-path-commander.js && ncp dist/svg-path-commander.js.map docs/svg-path-commander.js.map"
79
+ "copy-docs": "cp dist/svg-path-commander.js docs/svg-path-commander.js && cp dist/svg-path-commander.js.map docs/svg-path-commander.js.map"
82
80
  }
83
81
  }
@@ -1,19 +1,7 @@
1
1
  import parsePathString from '../parser/parsePathString';
2
- import isAbsoluteArray from '../util/isAbsoluteArray';
3
- import type {
4
- PathArray,
5
- AbsoluteArray,
6
- AbsoluteCommand,
7
- AbsoluteSegment,
8
- VSegment,
9
- HSegment,
10
- QSegment,
11
- TSegment,
12
- ASegment,
13
- SSegment,
14
- CSegment,
15
- MSegment,
16
- } from '../types';
2
+ import absolutizeSegment from '../process/absolutizeSegment';
3
+ import type { AbsoluteArray, AbsoluteCommand, HSegment, PathArray, PointTuple, VSegment } from '../types';
4
+ import iterate from '../process/iterate';
17
5
 
18
6
  /**
19
7
  * Parses a path string value or object and returns an array
@@ -22,72 +10,28 @@ import type {
22
10
  * @param pathInput the path string | object
23
11
  * @returns the resulted `pathArray` with absolute values
24
12
  */
25
- const pathToAbsolute = (pathInput: string | PathArray): AbsoluteArray => {
26
- /* istanbul ignore else */
27
- if (isAbsoluteArray(pathInput)) {
28
- return [...pathInput];
29
- }
30
-
31
- const path = parsePathString(pathInput);
13
+ const pathToAbsolute = (pathInput: string | PathArray) => {
32
14
  let x = 0;
33
15
  let y = 0;
34
16
  let mx = 0;
35
17
  let my = 0;
18
+ let pathCommand = 'M';
19
+ const path = parsePathString(pathInput);
36
20
 
37
- // the `absoluteSegment[]` is for sure an `absolutePath`
38
- return path.map(segment => {
39
- const values = segment.slice(1).map(Number);
40
- const [pathCommand] = segment;
21
+ return iterate<AbsoluteArray>(path, (seg, params) => {
22
+ [pathCommand] = seg;
23
+ const result = absolutizeSegment(seg, params);
41
24
  const absCommand = pathCommand.toUpperCase() as AbsoluteCommand;
42
25
 
43
- if (pathCommand === 'M') {
44
- [x, y] = values;
45
- mx = x;
46
- my = y;
47
- return ['M', x, y] as MSegment;
48
- }
49
-
50
- let absoluteSegment = [] as unknown as AbsoluteSegment;
51
-
52
- if (pathCommand !== absCommand) {
53
- if (absCommand === 'A') {
54
- absoluteSegment = [
55
- absCommand,
56
- values[0],
57
- values[1],
58
- values[2],
59
- values[3],
60
- values[4],
61
- values[5] + x,
62
- values[6] + y,
63
- ] as ASegment;
64
- } else if (absCommand === 'V') {
65
- absoluteSegment = [absCommand, values[0] + y] as VSegment;
66
- } else if (absCommand === 'H') {
67
- absoluteSegment = [absCommand, values[0] + x] as HSegment;
68
- } else {
69
- // use brakets for `eslint: no-case-declaration`
70
- // https://stackoverflow.com/a/50753272/803358
71
- const absValues = values.map((n, j) => n + (j % 2 ? y : x));
72
- // for n, l, c, s, q, t
73
- absoluteSegment = [absCommand, ...absValues] as QSegment | TSegment | SSegment | CSegment;
74
- }
75
- } else {
76
- absoluteSegment = [absCommand, ...values] as typeof segment;
77
- }
78
-
79
- // const segLength = absoluteSegment.length;
80
26
  if (absCommand === 'Z') {
81
27
  x = mx;
82
28
  y = my;
83
29
  } else if (absCommand === 'H') {
84
- [, x] = absoluteSegment as HSegment;
30
+ [, x] = result as HSegment;
85
31
  } else if (absCommand === 'V') {
86
- [, y] = absoluteSegment as VSegment;
32
+ [, y] = result as VSegment;
87
33
  } else {
88
- // x = absoluteSegment[segLength - 2];
89
- // y = absoluteSegment[segLength - 1];
90
- [x, y] = absoluteSegment.slice(-2) as [number, number];
34
+ [x, y] = result.slice(-2) as PointTuple;
91
35
 
92
36
  if (absCommand === 'M') {
93
37
  mx = x;
@@ -95,7 +39,9 @@ const pathToAbsolute = (pathInput: string | PathArray): AbsoluteArray => {
95
39
  }
96
40
  }
97
41
 
98
- return absoluteSegment;
99
- }) as AbsoluteArray;
42
+ params.x = x;
43
+ params.y = y;
44
+ return result;
45
+ });
100
46
  };
101
47
  export default pathToAbsolute;
@@ -1,9 +1,9 @@
1
- import fixArc from '../process/fixArc';
2
- import isCurveArray from '../util/isCurveArray';
3
- import normalizePath from '../process/normalizePath';
4
1
  import segmentToCubic from '../process/segmentToCubic';
5
- import paramsParser from '../parser/paramsParser';
6
- import { CurveArray, PathArray, PathCommand } from '../types';
2
+ import { AbsoluteCommand, CSegment, CurveArray, PathArray, PointTuple } from '../types';
3
+ import iterate from '../process/iterate';
4
+ import parsePathString from '../parser/parsePathString';
5
+ import normalizeSegment from '../process/normalizeSegment';
6
+ import absolutizeSegment from '../process/absolutizeSegment';
7
7
 
8
8
  /**
9
9
  * Parses a path string value or 'pathArray' and returns a new one
@@ -16,34 +16,42 @@ import { CurveArray, PathArray, PathCommand } from '../types';
16
16
  * @returns the resulted `pathArray` converted to cubic-bezier
17
17
  */
18
18
  const pathToCurve = (pathInput: string | PathArray): CurveArray => {
19
- /* istanbul ignore else */
20
- if (isCurveArray(pathInput)) {
21
- return [...pathInput];
22
- }
19
+ let x = 0;
20
+ let y = 0;
21
+ let mx = 0;
22
+ let my = 0;
23
+ let pathCommand = 'M';
23
24
 
24
- const path = normalizePath(pathInput);
25
- const params = { ...paramsParser };
26
- const allPathCommands = [] as PathCommand[];
27
- let pathCommand = ''; // ts-lint
28
- let ii = path.length;
25
+ const path = parsePathString(pathInput);
26
+ return iterate<CurveArray>(path, (seg, params, i) => {
27
+ const absSegment = absolutizeSegment(seg, params);
28
+ [pathCommand] = absSegment;
29
29
 
30
- for (let i = 0; i < ii; i += 1) {
31
- [pathCommand] = path[i];
32
- allPathCommands[i] = pathCommand as PathCommand;
30
+ const absCommand = pathCommand.toUpperCase() as AbsoluteCommand;
31
+ const normalSegment = normalizeSegment(absSegment, params);
32
+ let result = segmentToCubic(normalSegment, params);
33
+ const isLongArc = result[0] === 'C' && result.length > 7;
33
34
 
34
- path[i] = segmentToCubic(path[i], params);
35
+ if (isLongArc) {
36
+ path.splice(i + 1, 0, ['C', ...result.slice(7)] as CSegment);
37
+ result = result.slice(0, 7) as CSegment;
38
+ }
35
39
 
36
- fixArc(path, allPathCommands, i);
37
- ii = path.length;
40
+ if (absCommand === 'Z') {
41
+ x = mx;
42
+ y = my;
43
+ } else {
44
+ [x, y] = result.slice(-2) as PointTuple;
38
45
 
39
- const segment = path[i];
40
- const seglen = segment.length;
41
- params.x1 = +segment[seglen - 2];
42
- params.y1 = +segment[seglen - 1];
43
- params.x2 = +segment[seglen - 4] || params.x1;
44
- params.y2 = +segment[seglen - 3] || params.y1;
45
- }
46
+ if (absCommand === 'M') {
47
+ mx = x;
48
+ my = y;
49
+ }
50
+ }
46
51
 
47
- return path as CurveArray;
52
+ params.x = x;
53
+ params.y = y;
54
+ return result;
55
+ });
48
56
  };
49
57
  export default pathToCurve;
@@ -1,14 +1,15 @@
1
1
  import type {
2
- aSegment,
2
+ AbsoluteCommand,
3
3
  hSegment,
4
4
  PathArray,
5
+ PointTuple,
5
6
  RelativeArray,
6
7
  RelativeCommand,
7
- RelativeSegment,
8
8
  vSegment,
9
9
  } from '../types';
10
10
  import parsePathString from '../parser/parsePathString';
11
- import isRelativeArray from '../util/isRelativeArray';
11
+ import iterate from '../process/iterate';
12
+ import relativizeSegment from '../process/relativizeSegment';
12
13
 
13
14
  /**
14
15
  * Parses a path string value or object and returns an array
@@ -18,75 +19,45 @@ import isRelativeArray from '../util/isRelativeArray';
18
19
  * @returns the resulted `pathArray` with relative values
19
20
  */
20
21
  const pathToRelative = (pathInput: string | PathArray): RelativeArray => {
21
- /* istanbul ignore else */
22
- if (isRelativeArray(pathInput)) {
23
- return [...pathInput];
24
- }
25
-
26
- const path = parsePathString(pathInput);
27
22
  let x = 0;
28
23
  let y = 0;
29
24
  let mx = 0;
30
25
  let my = 0;
26
+ let pathCommand = 'M';
27
+ const path = parsePathString(pathInput);
31
28
 
32
- return path.map(segment => {
33
- const values = segment.slice(1).map(Number);
34
- const [pathCommand] = segment;
35
- const relativeCommand = pathCommand.toLowerCase() as RelativeCommand;
36
-
37
- if (pathCommand === 'M') {
38
- [x, y] = values;
39
- mx = x;
40
- my = y;
41
- return ['M', x, y];
42
- }
43
-
44
- let relativeSegment = [];
45
-
46
- if (pathCommand !== relativeCommand) {
47
- if (relativeCommand === 'a') {
48
- relativeSegment = [
49
- relativeCommand,
50
- values[0],
51
- values[1],
52
- values[2],
53
- values[3],
54
- values[4],
55
- values[5] - x,
56
- values[6] - y,
57
- ] as aSegment;
58
- } else if (relativeCommand === 'v') {
59
- relativeSegment = [relativeCommand, values[0] - y] as vSegment;
60
- } else if (relativeCommand === 'h') {
61
- relativeSegment = [relativeCommand, values[0] - x] as hSegment;
62
- } else {
63
- // use brakets for `eslint: no-case-declaration`
64
- // https://stackoverflow.com/a/50753272/803358
65
- const relValues = values.map((n, j) => n - (j % 2 ? y : x));
66
- relativeSegment = [relativeCommand, ...relValues] as RelativeSegment;
67
- }
68
- } else {
69
- if (pathCommand === 'm') {
70
- mx = values[0] + x;
71
- my = values[1] + y;
72
- }
73
- relativeSegment = [relativeCommand, ...values] as RelativeSegment;
74
- }
29
+ return iterate<RelativeArray>(path, (seg, params, i) => {
30
+ [pathCommand] = seg;
31
+ const result = relativizeSegment(seg, params, i);
32
+ const [resultedCommand] = result;
33
+ const absCommand = pathCommand.toUpperCase() as AbsoluteCommand;
34
+ const relCommand = pathCommand.toLowerCase() as RelativeCommand;
35
+ const isRelative = resultedCommand === relCommand;
75
36
 
76
- const segLength = relativeSegment.length;
77
- if (relativeCommand === 'z') {
37
+ if (absCommand === 'Z') {
78
38
  x = mx;
79
39
  y = my;
80
- } else if (relativeCommand === 'h') {
81
- x += relativeSegment[1] as number;
82
- } else if (relativeCommand === 'v') {
83
- y += relativeSegment[1] as number;
40
+ } else if (absCommand === 'H') {
41
+ [, x] = result as hSegment;
42
+ x += isRelative ? params.x : /* istanbul ignore next @preserve */ 0;
43
+ } else if (absCommand === 'V') {
44
+ [, y] = result as vSegment;
45
+ y += isRelative ? params.y : /* istanbul ignore next @preserve */ 0;
84
46
  } else {
85
- x += relativeSegment[segLength - 2] as number;
86
- y += relativeSegment[segLength - 1] as number;
47
+ [x, y] = result.slice(-2) as PointTuple;
48
+ x += isRelative ? params.x : 0;
49
+ y += isRelative ? params.y : 0;
50
+
51
+ if (absCommand === 'M') {
52
+ mx = x;
53
+ my = y;
54
+ }
87
55
  }
88
56
 
89
- return relativeSegment as typeof segment;
90
- }) as RelativeArray;
57
+ params.x = x;
58
+ params.y = y;
59
+
60
+ return result;
61
+ });
91
62
  };
92
63
  export default pathToRelative;
package/src/index.ts CHANGED
@@ -1,5 +1,8 @@
1
- import { PathArray, TransformObjectValues } from './types';
2
- import { Options, PathBBox, TransformEntries, TransformObject } from './interface';
1
+ 'use strict';
2
+ import { PointTuple, PathArray, TransformObjectValues } from './types';
3
+ import type { Options, TransformEntries, TransformObject } from './interface';
4
+ export * from './types';
5
+ export * from './interface';
3
6
  import defaultOptions from './options/options';
4
7
 
5
8
  import error from './parser/error';
@@ -14,7 +17,6 @@ import getPathArea from './util/getPathArea';
14
17
  import getTotalLength from './util/getTotalLength';
15
18
  import getDrawDirection from './util/getDrawDirection';
16
19
  import getPointAtLength from './util/getPointAtLength';
17
- import pathLengthFactory from './util/pathLengthFactory';
18
20
 
19
21
  import getPropertiesAtLength from './util/getPropertiesAtLength';
20
22
  import getPropertiesAtPoint from './util/getPropertiesAtPoint';
@@ -55,10 +57,9 @@ import pathToString from './convert/pathToString';
55
57
  *
56
58
  * @class
57
59
  * @author thednp <https://github.com/thednp/svg-path-commander>
58
- * @returns {SVGPathCommander} a new SVGPathCommander instance
60
+ * @returns a new SVGPathCommander instance
59
61
  */
60
62
  class SVGPathCommander {
61
- // bring main utilities to front
62
63
  public static CSSMatrix = CSSMatrix;
63
64
  public static getSVGMatrix = getSVGMatrix;
64
65
  public static getPathBBox = getPathBBox;
@@ -66,7 +67,6 @@ class SVGPathCommander {
66
67
  public static getTotalLength = getTotalLength;
67
68
  public static getDrawDirection = getDrawDirection;
68
69
  public static getPointAtLength = getPointAtLength;
69
- public static pathLengthFactory = pathLengthFactory;
70
70
  public static getPropertiesAtLength = getPropertiesAtLength;
71
71
  public static getPropertiesAtPoint = getPropertiesAtPoint;
72
72
  public static polygonLength = polygonLength;
@@ -103,8 +103,8 @@ class SVGPathCommander {
103
103
 
104
104
  /**
105
105
  * @constructor
106
- * @param {string} pathValue the path string
107
- * @param {any} config instance options
106
+ * @param pathValue the path string
107
+ * @param config instance options
108
108
  */
109
109
  constructor(pathValue: string, config?: Partial<Options>) {
110
110
  const instanceOptions = config || {};
@@ -115,22 +115,13 @@ class SVGPathCommander {
115
115
  }
116
116
 
117
117
  const segments = parsePathString(pathValue);
118
- // if (typeof segments === 'string') {
119
- // throw TypeError(segments);
120
- // }
121
-
122
118
  this.segments = segments;
123
119
 
124
- const { width, height, cx, cy, cz } = this.getBBox();
125
-
126
- // set instance options.round
120
+ // // set instance options.round
127
121
  const { round: roundOption, origin: originOption } = instanceOptions;
128
122
  let round: number | 'off';
129
123
 
130
- if (roundOption === 'auto') {
131
- const pathScale = `${Math.floor(Math.max(width, height))}`.length;
132
- round = pathScale >= 4 ? 0 : 4 - pathScale;
133
- } else if (Number.isInteger(roundOption) || roundOption === 'off') {
124
+ if (Number.isInteger(roundOption) || roundOption === 'off') {
134
125
  round = roundOption as number | 'off';
135
126
  } else {
136
127
  round = defaultOptions.round as number;
@@ -138,16 +129,15 @@ class SVGPathCommander {
138
129
 
139
130
  // set instance options.origin
140
131
  // the SVGPathCommander class will always override the default origin
141
- let origin: [number, number, number];
132
+ let origin = defaultOptions.origin as [number, number, number];
133
+ /* istanbul ignore else @preserve */
142
134
  if (Array.isArray(originOption) && originOption.length >= 2) {
143
135
  const [originX, originY, originZ] = originOption.map(Number);
144
136
  origin = [
145
- !Number.isNaN(originX) ? originX : cx,
146
- !Number.isNaN(originY) ? originY : cy,
147
- !Number.isNaN(originZ) ? originZ : cz,
137
+ !Number.isNaN(originX) ? originX : 0,
138
+ !Number.isNaN(originY) ? originY : 0,
139
+ !Number.isNaN(originZ) ? originZ : 0,
148
140
  ];
149
- } else {
150
- origin = [cx, cy, cz];
151
141
  }
152
142
 
153
143
  this.round = round;
@@ -155,6 +145,12 @@ class SVGPathCommander {
155
145
 
156
146
  return this;
157
147
  }
148
+ get bbox() {
149
+ return getPathBBox(this.segments);
150
+ }
151
+ get length() {
152
+ return getTotalLength(this.segments);
153
+ }
158
154
 
159
155
  /**
160
156
  * Returns the path bounding box, equivalent to native `path.getBBox()`.
@@ -162,8 +158,8 @@ class SVGPathCommander {
162
158
  * @public
163
159
  * @returns the pathBBox
164
160
  */
165
- getBBox(): PathBBox {
166
- return getPathBBox(this.segments);
161
+ getBBox() {
162
+ return this.bbox;
167
163
  }
168
164
 
169
165
  /**
@@ -173,7 +169,7 @@ class SVGPathCommander {
173
169
  * @returns the path total length
174
170
  */
175
171
  getTotalLength() {
176
- return getTotalLength(this.segments);
172
+ return this.length;
177
173
  }
178
174
 
179
175
  /**
@@ -184,7 +180,7 @@ class SVGPathCommander {
184
180
  * @param length the length
185
181
  * @returns the requested point
186
182
  */
187
- getPointAtLength(length: number): { x: number; y: number } {
183
+ getPointAtLength(length: number) {
188
184
  return getPointAtLength(this.segments, length);
189
185
  }
190
186
 
@@ -236,23 +232,22 @@ class SVGPathCommander {
236
232
  const subPath = split.length > 1 ? split : false;
237
233
 
238
234
  const absoluteMultiPath = subPath
239
- ? [...subPath].map((x, i) => {
235
+ ? subPath.map((x, i) => {
240
236
  if (onlySubpath) {
241
- // return i ? reversePath(x) : parsePathString(x);
242
- return i ? reversePath(x) : [...x];
237
+ return i ? reversePath(x) : x.slice(0);
243
238
  }
244
239
  return reversePath(x);
245
240
  })
246
- : [...segments];
241
+ : segments.slice(0);
247
242
 
248
- let path = [];
243
+ let path = [] as unknown as PathArray;
249
244
  if (subPath) {
250
- path = absoluteMultiPath.flat(1);
245
+ path = absoluteMultiPath.flat(1) as PathArray;
251
246
  } else {
252
247
  path = onlySubpath ? segments : reversePath(segments);
253
248
  }
254
249
 
255
- this.segments = [...path] as PathArray;
250
+ this.segments = path.slice(0) as PathArray;
256
251
  return this;
257
252
  }
258
253
 
@@ -306,8 +301,9 @@ class SVGPathCommander {
306
301
  } = this;
307
302
  const transform = {} as TransformObjectValues;
308
303
  for (const [k, v] of Object.entries(source) as TransformEntries) {
304
+ // istanbul ignore else @preserve
309
305
  if (k === 'skew' && Array.isArray(v)) {
310
- transform[k] = v.map(Number) as [number, number];
306
+ transform[k] = v.map(Number) as PointTuple;
311
307
  } else if ((k === 'rotate' || k === 'translate' || k === 'origin' || k === 'scale') && Array.isArray(v)) {
312
308
  transform[k] = v.map(Number) as [number, number, number];
313
309
  } else if (k !== 'origin' && typeof Number(v) === 'number') transform[k] = Number(v);
@@ -334,7 +330,8 @@ class SVGPathCommander {
334
330
  * @public
335
331
  */
336
332
  flipX() {
337
- this.transform({ rotate: [0, 180, 0] });
333
+ const { cx, cy } = this.bbox;
334
+ this.transform({ rotate: [0, 180, 0], origin: [cx, cy, 0] });
338
335
  return this;
339
336
  }
340
337
 
@@ -344,7 +341,8 @@ class SVGPathCommander {
344
341
  * @public
345
342
  */
346
343
  flipY() {
347
- this.transform({ rotate: [180, 0, 0] });
344
+ const { cx, cy } = this.bbox;
345
+ this.transform({ rotate: [180, 0, 0], origin: [cx, cy, 0] });
348
346
  return this;
349
347
  }
350
348