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
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.1.0",
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",
@@ -48,9 +48,9 @@
48
48
  "eslint-plugin-jsdoc": "^46.10.1",
49
49
  "eslint-plugin-prefer-arrow": "^1.2.3",
50
50
  "eslint-plugin-prettier": "^4.2.1",
51
- "playwright": "^1.47.2",
51
+ "playwright": "^1.48.0",
52
52
  "prettier": "^2.8.8",
53
- "typescript": "^5.6.2",
53
+ "typescript": "^5.6.3",
54
54
  "vite": "^5.4.8",
55
55
  "vitest": "^2.1.2"
56
56
  },
@@ -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.slice(0) as AbsoluteArray;
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.slice(0) as CurveArray;
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.slice(0) as RelativeArray;
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,4 +1,5 @@
1
- import { PathArray, TransformObjectValues } from './types';
1
+ 'use strict';
2
+ import { PointTuple, PathArray, TransformObjectValues } from './types';
2
3
  import type { Options, TransformEntries, TransformObject } from './interface';
3
4
  export * from './types';
4
5
  export * from './interface';
@@ -16,7 +17,6 @@ import getPathArea from './util/getPathArea';
16
17
  import getTotalLength from './util/getTotalLength';
17
18
  import getDrawDirection from './util/getDrawDirection';
18
19
  import getPointAtLength from './util/getPointAtLength';
19
- import pathFactory from './util/pathFactory';
20
20
 
21
21
  import getPropertiesAtLength from './util/getPropertiesAtLength';
22
22
  import getPropertiesAtPoint from './util/getPropertiesAtPoint';
@@ -43,7 +43,6 @@ import reversePath from './process/reversePath';
43
43
  import normalizePath from './process/normalizePath';
44
44
  import transformPath from './process/transformPath';
45
45
  import splitCubic from './process/splitCubic';
46
- import replaceArc from './process/replaceArc';
47
46
 
48
47
  import pathToAbsolute from './convert/pathToAbsolute';
49
48
  import pathToRelative from './convert/pathToRelative';
@@ -61,7 +60,6 @@ import pathToString from './convert/pathToString';
61
60
  * @returns a new SVGPathCommander instance
62
61
  */
63
62
  class SVGPathCommander {
64
- // bring main utilities to front
65
63
  public static CSSMatrix = CSSMatrix;
66
64
  public static getSVGMatrix = getSVGMatrix;
67
65
  public static getPathBBox = getPathBBox;
@@ -69,7 +67,6 @@ class SVGPathCommander {
69
67
  public static getTotalLength = getTotalLength;
70
68
  public static getDrawDirection = getDrawDirection;
71
69
  public static getPointAtLength = getPointAtLength;
72
- public static pathFactory = pathFactory;
73
70
  public static getPropertiesAtLength = getPropertiesAtLength;
74
71
  public static getPropertiesAtPoint = getPropertiesAtPoint;
75
72
  public static polygonLength = polygonLength;
@@ -90,7 +87,6 @@ class SVGPathCommander {
90
87
  public static roundPath = roundPath;
91
88
  public static splitPath = splitPath;
92
89
  public static splitCubic = splitCubic;
93
- public static replaceArc = replaceArc;
94
90
  public static optimizePath = optimizePath;
95
91
  public static reverseCurve = reverseCurve;
96
92
  public static reversePath = reversePath;
@@ -120,16 +116,12 @@ class SVGPathCommander {
120
116
 
121
117
  const segments = parsePathString(pathValue);
122
118
  this.segments = segments;
123
- const { width, height, cx, cy, cz } = this.bbox;
124
119
 
125
- // set instance options.round
120
+ // // set instance options.round
126
121
  const { round: roundOption, origin: originOption } = instanceOptions;
127
122
  let round: number | 'off';
128
123
 
129
- if (roundOption === 'auto') {
130
- const pathScale = `${Math.floor(Math.max(width, height))}`.length;
131
- round = pathScale >= 4 ? 0 : 4 - pathScale;
132
- } else if (Number.isInteger(roundOption) || roundOption === 'off') {
124
+ if (Number.isInteger(roundOption) || roundOption === 'off') {
133
125
  round = roundOption as number | 'off';
134
126
  } else {
135
127
  round = defaultOptions.round as number;
@@ -137,14 +129,14 @@ class SVGPathCommander {
137
129
 
138
130
  // set instance options.origin
139
131
  // the SVGPathCommander class will always override the default origin
140
- let origin = [cx, cy, cz] as [number, number, number];
132
+ let origin = defaultOptions.origin as [number, number, number];
141
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
141
  }
150
142
 
@@ -311,7 +303,7 @@ class SVGPathCommander {
311
303
  for (const [k, v] of Object.entries(source) as TransformEntries) {
312
304
  // istanbul ignore else @preserve
313
305
  if (k === 'skew' && Array.isArray(v)) {
314
- transform[k] = v.map(Number) as [number, number];
306
+ transform[k] = v.map(Number) as PointTuple;
315
307
  } else if ((k === 'rotate' || k === 'translate' || k === 'origin' || k === 'scale') && Array.isArray(v)) {
316
308
  transform[k] = v.map(Number) as [number, number, number];
317
309
  } else if (k !== 'origin' && typeof Number(v) === 'number') transform[k] = Number(v);
@@ -338,7 +330,8 @@ class SVGPathCommander {
338
330
  * @public
339
331
  */
340
332
  flipX() {
341
- this.transform({ rotate: [0, 180, 0] });
333
+ const { cx, cy } = this.bbox;
334
+ this.transform({ rotate: [0, 180, 0], origin: [cx, cy, 0] });
342
335
  return this;
343
336
  }
344
337
 
@@ -348,7 +341,8 @@ class SVGPathCommander {
348
341
  * @public
349
342
  */
350
343
  flipY() {
351
- this.transform({ rotate: [180, 0, 0] });
344
+ const { cx, cy } = this.bbox;
345
+ this.transform({ rotate: [180, 0, 0], origin: [cx, cy, 0] });
352
346
  return this;
353
347
  }
354
348
 
package/src/interface.ts CHANGED
@@ -106,7 +106,7 @@ export type LengthFactory = {
106
106
  };
107
107
 
108
108
  export type Options = {
109
- round: 'auto' | 'off' | number;
109
+ round: 'off' | number;
110
110
  origin: number[];
111
111
  };
112
112