svg-path-commander 1.0.5 → 2.0.0-alpha2

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 (131) hide show
  1. package/README.md +26 -4
  2. package/dist/svg-path-commander.cjs +2 -0
  3. package/dist/svg-path-commander.cjs.map +1 -0
  4. package/dist/svg-path-commander.d.ts +454 -0
  5. package/dist/svg-path-commander.js +2 -3762
  6. package/dist/svg-path-commander.js.map +1 -0
  7. package/dist/svg-path-commander.mjs +1194 -0
  8. package/dist/svg-path-commander.mjs.map +1 -0
  9. package/package.json +39 -42
  10. package/src/convert/pathToAbsolute.ts +101 -0
  11. package/src/convert/{pathToCurve.js → pathToCurve.ts} +12 -14
  12. package/src/convert/pathToRelative.ts +92 -0
  13. package/src/convert/pathToString.ts +17 -0
  14. package/src/{svg-path-commander.js → index.ts} +143 -66
  15. package/src/interface.ts +129 -0
  16. package/src/math/distanceSquareRoot.ts +13 -0
  17. package/src/math/midPoint.ts +16 -0
  18. package/src/math/{polygonArea.js → polygonArea.ts} +6 -4
  19. package/src/math/{polygonLength.js → polygonLength.ts} +5 -3
  20. package/src/math/rotateVector.ts +16 -0
  21. package/src/options/options.ts +9 -0
  22. package/src/parser/error.ts +2 -0
  23. package/src/parser/finalizeSegment.ts +31 -0
  24. package/src/parser/{invalidPathValue.js → invalidPathValue.ts} +0 -0
  25. package/src/parser/{isArcCommand.js → isArcCommand.ts} +5 -4
  26. package/src/parser/isDigit.ts +12 -0
  27. package/src/parser/isDigitStart.ts +14 -0
  28. package/src/parser/isPathCommand.ts +28 -0
  29. package/src/parser/isSpace.ts +23 -0
  30. package/src/parser/paramsCount.ts +16 -0
  31. package/src/parser/paramsParser.ts +14 -0
  32. package/src/parser/{parsePathString.js → parsePathString.ts} +13 -7
  33. package/src/parser/pathParser.ts +29 -0
  34. package/src/parser/{scanFlag.js → scanFlag.ts} +8 -5
  35. package/src/parser/{scanParam.js → scanParam.ts} +12 -11
  36. package/src/parser/{scanSegment.js → scanSegment.ts} +9 -5
  37. package/src/parser/{skipSpaces.js → skipSpaces.ts} +5 -3
  38. package/src/process/{arcToCubic.js → arcToCubic.ts} +42 -28
  39. package/src/process/fixArc.ts +23 -0
  40. package/src/process/getSVGMatrix.ts +70 -0
  41. package/src/process/lineToCubic.ts +17 -0
  42. package/src/process/{normalizePath.js → normalizePath.ts} +10 -10
  43. package/src/process/normalizeSegment.ts +47 -0
  44. package/src/process/{optimizePath.js → optimizePath.ts} +18 -21
  45. package/src/process/{projection2d.js → projection2d.ts} +16 -15
  46. package/src/process/quadToCubic.ts +31 -0
  47. package/src/process/reverseCurve.ts +21 -0
  48. package/src/process/reversePath.ts +101 -0
  49. package/src/process/roundPath.ts +29 -0
  50. package/src/process/segmentToCubic.ts +46 -0
  51. package/src/process/shortenSegment.ts +79 -0
  52. package/src/process/splitCubic.ts +28 -0
  53. package/src/process/{splitPath.js → splitPath.ts} +9 -8
  54. package/src/process/{transformPath.js → transformPath.ts} +55 -48
  55. package/src/types.ts +193 -0
  56. package/src/util/getClosestPoint.ts +15 -0
  57. package/src/util/{getDrawDirection.js → getDrawDirection.ts} +7 -4
  58. package/src/util/getPathArea.ts +70 -0
  59. package/src/util/{getPathBBox.js → getPathBBox.ts} +16 -5
  60. package/src/util/getPointAtLength.ts +14 -0
  61. package/src/util/{getPropertiesAtLength.js → getPropertiesAtLength.ts} +28 -19
  62. package/src/util/{getPropertiesAtPoint.js → getPropertiesAtPoint.ts} +16 -12
  63. package/src/util/getSegmentAtLength.ts +15 -0
  64. package/src/util/getSegmentOfPoint.ts +18 -0
  65. package/src/util/{getTotalLength.js → getTotalLength.ts} +6 -4
  66. package/src/util/isAbsoluteArray.ts +18 -0
  67. package/src/util/{isCurveArray.js → isCurveArray.ts} +6 -4
  68. package/src/util/{isNormalizedArray.js → isNormalizedArray.ts} +4 -2
  69. package/src/util/isPathArray.ts +19 -0
  70. package/src/util/isPointInStroke.ts +15 -0
  71. package/src/util/isRelativeArray.ts +18 -0
  72. package/src/util/{isValidPath.js → isValidPath.ts} +5 -4
  73. package/src/util/{pathLengthFactory.js → pathLengthFactory.ts} +38 -31
  74. package/src/util/{segmentArcFactory.js → segmentArcFactory.ts} +71 -55
  75. package/src/util/segmentCubicFactory.ts +114 -0
  76. package/src/util/{segmentLineFactory.js → segmentLineFactory.ts} +10 -8
  77. package/src/util/{segmentQuadFactory.js → segmentQuadFactory.ts} +47 -32
  78. package/src/util/shapeToPath.ts +214 -0
  79. package/dist/svg-path-commander.es5.js +0 -3875
  80. package/dist/svg-path-commander.es5.min.js +0 -2
  81. package/dist/svg-path-commander.esm.js +0 -3754
  82. package/dist/svg-path-commander.esm.min.js +0 -2
  83. package/dist/svg-path-commander.min.js +0 -2
  84. package/src/convert/pathToAbsolute.js +0 -86
  85. package/src/convert/pathToRelative.js +0 -84
  86. package/src/convert/pathToString.js +0 -14
  87. package/src/index.js +0 -10
  88. package/src/math/distanceSquareRoot.js +0 -14
  89. package/src/math/epsilon.js +0 -8
  90. package/src/math/midPoint.js +0 -13
  91. package/src/math/rotateVector.js +0 -14
  92. package/src/options/options.js +0 -10
  93. package/src/parser/error.js +0 -2
  94. package/src/parser/finalizeSegment.js +0 -28
  95. package/src/parser/isDigit.js +0 -9
  96. package/src/parser/isDigitStart.js +0 -13
  97. package/src/parser/isPathCommand.js +0 -25
  98. package/src/parser/isSpace.js +0 -16
  99. package/src/parser/paramsCount.js +0 -9
  100. package/src/parser/paramsParser.js +0 -8
  101. package/src/parser/pathParser.js +0 -24
  102. package/src/process/clonePath.js +0 -9
  103. package/src/process/fixArc.js +0 -21
  104. package/src/process/fixPath.js +0 -31
  105. package/src/process/getSVGMatrix.js +0 -61
  106. package/src/process/lineToCubic.js +0 -30
  107. package/src/process/normalizeSegment.js +0 -45
  108. package/src/process/quadToCubic.js +0 -22
  109. package/src/process/reverseCurve.js +0 -18
  110. package/src/process/reversePath.js +0 -89
  111. package/src/process/roundPath.js +0 -26
  112. package/src/process/segmentToCubic.js +0 -46
  113. package/src/process/shortenSegment.js +0 -58
  114. package/src/process/splitCubic.js +0 -26
  115. package/src/util/getClosestPoint.js +0 -12
  116. package/src/util/getPathArea.js +0 -47
  117. package/src/util/getPointAtLength.js +0 -12
  118. package/src/util/getSegmentAtLength.js +0 -11
  119. package/src/util/getSegmentOfPoint.js +0 -12
  120. package/src/util/isAbsoluteArray.js +0 -14
  121. package/src/util/isPathArray.js +0 -14
  122. package/src/util/isPointInStroke.js +0 -13
  123. package/src/util/isRelativeArray.js +0 -14
  124. package/src/util/segmentCubicFactory.js +0 -97
  125. package/src/util/shapeToPath.js +0 -204
  126. package/src/util/util.js +0 -82
  127. package/src/version.js +0 -8
  128. package/types/index.d.ts +0 -120
  129. package/types/more/modules.ts +0 -82
  130. package/types/more/svg.d.ts +0 -211
  131. package/types/svg-path-commander.d.ts +0 -1089
@@ -6,27 +6,42 @@ import rotateVector from '../math/rotateVector';
6
6
  * For more information of where this math came from visit:
7
7
  * http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
8
8
  *
9
- * @param {number} X1 the starting x position
10
- * @param {number} Y1 the starting y position
11
- * @param {number} RX x-radius of the arc
12
- * @param {number} RY y-radius of the arc
13
- * @param {number} angle x-axis-rotation of the arc
14
- * @param {number} LAF large-arc-flag of the arc
15
- * @param {number} SF sweep-flag of the arc
16
- * @param {number} X2 the ending x position
17
- * @param {number} Y2 the ending y position
18
- * @param {number[]=} recursive the parameters needed to split arc into 2 segments
19
- * @return {number[]} the resulting cubic-bezier segment(s)
9
+ * @param X1 the starting x position
10
+ * @param Y1 the starting y position
11
+ * @param RX x-radius of the arc
12
+ * @param RY y-radius of the arc
13
+ * @param angle x-axis-rotation of the arc
14
+ * @param LAF large-arc-flag of the arc
15
+ * @param SF sweep-flag of the arc
16
+ * @param X2 the ending x position
17
+ * @param Y2 the ending y position
18
+ * @param recursive the parameters needed to split arc into 2 segments
19
+ * @return the resulting cubic-bezier segment(s)
20
20
  */
21
- export default function arcToCubic(X1, Y1, RX, RY, angle, LAF, SF, X2, Y2, recursive) {
22
- let x1 = X1; let y1 = Y1; let rx = RX; let ry = RY; let x2 = X2; let y2 = Y2;
21
+ const arcToCubic = (
22
+ X1: number,
23
+ Y1: number,
24
+ RX: number,
25
+ RY: number,
26
+ angle: number,
27
+ LAF: number,
28
+ SF: number,
29
+ X2: number,
30
+ Y2: number,
31
+ recursive?: [number, number, number, number],
32
+ ): number[] => {
33
+ let x1 = X1;
34
+ let y1 = Y1;
35
+ let rx = RX;
36
+ let ry = RY;
37
+ let x2 = X2;
38
+ let y2 = Y2;
23
39
  // for more information of where this Math came from visit:
24
40
  // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
25
41
  const d120 = (Math.PI * 120) / 180;
26
42
 
27
43
  const rad = (Math.PI / 180) * (+angle || 0);
28
- /** @type {number[]} */
29
- let res = [];
44
+ let res = [] as number[];
30
45
  let xy;
31
46
  let f1;
32
47
  let f2;
@@ -52,21 +67,21 @@ export default function arcToCubic(X1, Y1, RX, RY, angle, LAF, SF, X2, Y2, recur
52
67
  const rx2 = rx * rx;
53
68
  const ry2 = ry * ry;
54
69
 
55
- const k = (LAF === SF ? -1 : 1)
56
- * Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x)
57
- / (rx2 * y * y + ry2 * x * x)));
70
+ const k =
71
+ (LAF === SF ? -1 : 1) *
72
+ Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
58
73
 
59
- cx = ((k * rx * y) / ry) + ((x1 + x2) / 2);
60
- cy = ((k * -ry * x) / rx) + ((y1 + y2) / 2);
74
+ cx = (k * rx * y) / ry + (x1 + x2) / 2;
75
+ cy = (k * -ry * x) / rx + (y1 + y2) / 2;
61
76
  // eslint-disable-next-line no-bitwise -- Impossible to satisfy no-bitwise
62
- f1 = Math.asin((((y1 - cy) / ry) * (10 ** 9) >> 0) / (10 ** 9));
77
+ f1 = Math.asin(((((y1 - cy) / ry) * 10 ** 9) >> 0) / 10 ** 9);
63
78
  // eslint-disable-next-line no-bitwise -- Impossible to satisfy no-bitwise
64
- f2 = Math.asin((((y2 - cy) / ry) * (10 ** 9) >> 0) / (10 ** 9));
79
+ f2 = Math.asin(((((y2 - cy) / ry) * 10 ** 9) >> 0) / 10 ** 9);
65
80
 
66
81
  f1 = x1 < cx ? Math.PI - f1 : f1;
67
82
  f2 = x2 < cx ? Math.PI - f2 : f2;
68
- if (f1 < 0) (f1 = Math.PI * 2 + f1);
69
- if (f2 < 0) (f2 = Math.PI * 2 + f2);
83
+ if (f1 < 0) f1 = Math.PI * 2 + f1;
84
+ if (f2 < 0) f2 = Math.PI * 2 + f2;
70
85
  if (SF && f1 > f2) {
71
86
  f1 -= Math.PI * 2;
72
87
  }
@@ -106,9 +121,8 @@ export default function arcToCubic(X1, Y1, RX, RY, angle, LAF, SF, X2, Y2, recur
106
121
  res = [...m2, ...m3, ...m4, ...res];
107
122
  const newres = [];
108
123
  for (let i = 0, ii = res.length; i < ii; i += 1) {
109
- newres[i] = i % 2
110
- ? rotateVector(res[i - 1], res[i], rad).y
111
- : rotateVector(res[i], res[i + 1], rad).x;
124
+ newres[i] = i % 2 ? rotateVector(res[i - 1], res[i], rad).y : rotateVector(res[i], res[i + 1], rad).x;
112
125
  }
113
126
  return newres;
114
- }
127
+ };
128
+ export default arcToCubic;
@@ -0,0 +1,23 @@
1
+ import { CSegment, PathArray, PathCommand } from '../types';
2
+
3
+ /**
4
+ * Splits an extended A (arc-to) segment into two cubic-bezier segments.
5
+ *
6
+ * @param path the `pathArray` this segment belongs to
7
+ * @param allPathCommands all previous path commands
8
+ * @param i the segment index
9
+ */
10
+ const fixArc = (path: PathArray, allPathCommands: PathCommand[], i: number) => {
11
+ if (path[i].length > 7) {
12
+ path[i].shift();
13
+ const segment = path[i];
14
+ let ni = i; // ESLint
15
+ while (segment.length) {
16
+ // if created multiple C:s, their original seg is saved
17
+ allPathCommands[i] = 'A';
18
+ path.splice((ni += 1), 0, ['C', ...segment.splice(0, 6)] as CSegment);
19
+ }
20
+ path.splice(i, 1);
21
+ }
22
+ };
23
+ export default fixArc;
@@ -0,0 +1,70 @@
1
+ import CSSMatrix from '@thednp/dommatrix';
2
+ // import type { TransformObject } from '../interface';
3
+ import type { TransformObjectValues } from '../types';
4
+
5
+ /**
6
+ * Returns a transformation matrix to apply to `<path>` elements.
7
+ *
8
+ * @see TransformObjectValues
9
+ *
10
+ * @param transform the `transformObject`
11
+ * @returns a new transformation matrix
12
+ */
13
+ const getSVGMatrix = (transform: TransformObjectValues): CSSMatrix => {
14
+ let matrix = new CSSMatrix();
15
+ const { origin } = transform;
16
+ const [originX, originY] = origin as [number, number, number];
17
+ const { translate } = transform;
18
+ const { rotate } = transform;
19
+ const { skew } = transform;
20
+ const { scale } = transform;
21
+
22
+ // set translate
23
+ if (
24
+ Array.isArray(translate) &&
25
+ translate.length >= 2 &&
26
+ translate.every(x => !Number.isNaN(+x)) &&
27
+ translate.some(x => x !== 0)
28
+ ) {
29
+ matrix = matrix.translate(...(translate as [number, number, number?]));
30
+ } else if (typeof translate === 'number' && !Number.isNaN(translate)) {
31
+ matrix = matrix.translate(translate);
32
+ }
33
+
34
+ if (rotate || skew || scale) {
35
+ // set SVG transform-origin, always defined
36
+ matrix = matrix.translate(originX, originY);
37
+
38
+ // set rotation
39
+ if (
40
+ Array.isArray(rotate) &&
41
+ rotate.length >= 2 &&
42
+ rotate.every(x => !Number.isNaN(+x)) &&
43
+ rotate.some(x => x !== 0)
44
+ ) {
45
+ matrix = matrix.rotate(...(rotate as [number, number, number?]));
46
+ } else if (typeof rotate === 'number' && !Number.isNaN(rotate)) {
47
+ matrix = matrix.rotate(rotate);
48
+ }
49
+
50
+ // set skew(s)
51
+ if (Array.isArray(skew) && skew.length === 2 && skew.every(x => !Number.isNaN(+x)) && skew.some(x => x !== 0)) {
52
+ matrix = skew[0] ? matrix.skewX(skew[0]) : matrix;
53
+ matrix = skew[1] ? matrix.skewY(skew[1]) : matrix;
54
+ } else if (typeof skew === 'number' && !Number.isNaN(skew)) {
55
+ matrix = matrix.skewX(skew);
56
+ }
57
+
58
+ // set scale
59
+ if (Array.isArray(scale) && scale.length >= 2 && scale.every(x => !Number.isNaN(+x)) && scale.some(x => x !== 1)) {
60
+ matrix = matrix.scale(...(scale as [number, number, number?]));
61
+ } else if (typeof scale === 'number' && !Number.isNaN(scale)) {
62
+ matrix = matrix.scale(scale);
63
+ }
64
+ // set SVG transform-origin
65
+ matrix = matrix.translate(-originX, -originY);
66
+ }
67
+
68
+ return matrix;
69
+ };
70
+ export default getSVGMatrix;
@@ -0,0 +1,17 @@
1
+ import midPoint from '../math/midPoint';
2
+
3
+ /**
4
+ * Converts an L (line-to) segment to C (cubic-bezier).
5
+ *
6
+ * @param x1 line start x
7
+ * @param y1 line start y
8
+ * @param x2 line end x
9
+ * @param y2 line end y
10
+ * @returns the cubic-bezier segment
11
+ */
12
+ const lineToCubic = (x1: number, y1: number, x2: number, y2: number) => {
13
+ const t = 0.5;
14
+ const mid = midPoint([x1, y1], [x2, y2], t);
15
+ return [...mid, x2, y2, x2, y2];
16
+ };
17
+ export default lineToCubic;
@@ -1,23 +1,22 @@
1
1
  import pathToAbsolute from '../convert/pathToAbsolute';
2
2
  import normalizeSegment from './normalizeSegment';
3
- import clonePath from './clonePath';
4
3
  import isNormalizedArray from '../util/isNormalizedArray';
5
4
  import paramsParser from '../parser/paramsParser';
5
+ import type { NormalArray, PathArray } from '../types';
6
6
 
7
7
  /**
8
8
  * Normalizes a `path` object for further processing:
9
9
  * * convert segments to absolute values
10
10
  * * convert shorthand path commands to their non-shorthand notation
11
11
  *
12
- * @param {string | SVGPath.pathArray} pathInput the string to be parsed or 'pathArray'
13
- * @returns {SVGPath.normalArray} the normalized `pathArray`
12
+ * @param pathInput the string to be parsed or 'pathArray'
13
+ * @returns the normalized `pathArray`
14
14
  */
15
- export default function normalizePath(pathInput) {
15
+ const normalizePath = (pathInput: string | PathArray): NormalArray => {
16
16
  if (isNormalizedArray(pathInput)) {
17
- return clonePath(pathInput);
17
+ return [...pathInput];
18
18
  }
19
19
 
20
- /** @type {SVGPath.normalArray} */
21
20
  const path = pathToAbsolute(pathInput);
22
21
  const params = { ...paramsParser };
23
22
  const allPathCommands = [];
@@ -36,9 +35,10 @@ export default function normalizePath(pathInput) {
36
35
 
37
36
  params.x1 = +segment[seglen - 2];
38
37
  params.y1 = +segment[seglen - 1];
39
- params.x2 = +(segment[seglen - 4]) || params.x1;
40
- params.y2 = +(segment[seglen - 3]) || params.y1;
38
+ params.x2 = +segment[seglen - 4] || params.x1;
39
+ params.y2 = +segment[seglen - 3] || params.y1;
41
40
  }
42
41
 
43
- return path;
44
- }
42
+ return path as NormalArray;
43
+ };
44
+ export default normalizePath;
@@ -0,0 +1,47 @@
1
+ import type { ParserParams } from '../interface';
2
+ import type { NormalSegment, PathSegment } from '../types';
3
+
4
+ /**
5
+ * Normalizes a single segment of a `pathArray` object.
6
+ *
7
+ * @param segment the segment object
8
+ * @param params the coordinates of the previous segment
9
+ * @returns the normalized segment
10
+ */
11
+ const normalizeSegment = (segment: PathSegment, params: ParserParams): NormalSegment => {
12
+ const [pathCommand] = segment;
13
+ const { x1: px1, y1: py1, x2: px2, y2: py2 } = params;
14
+ const values = segment.slice(1).map(Number);
15
+ let result = segment;
16
+
17
+ if (!'TQ'.includes(pathCommand)) {
18
+ // optional but good to be cautious
19
+ params.qx = null;
20
+ params.qy = null;
21
+ }
22
+
23
+ if (pathCommand === 'H') {
24
+ result = ['L', segment[1], py1];
25
+ } else if (pathCommand === 'V') {
26
+ result = ['L', px1, segment[1]];
27
+ } else if (pathCommand === 'S') {
28
+ const x1 = px1 * 2 - px2;
29
+ const y1 = py1 * 2 - py2;
30
+ params.x1 = x1;
31
+ params.y1 = y1;
32
+ result = ['C', x1, y1, ...(values as [number, number, number, number])];
33
+ } else if (pathCommand === 'T') {
34
+ const qx = px1 * 2 - (params.qx ? params.qx : /* istanbul ignore next */ 0);
35
+ const qy = py1 * 2 - (params.qy ? params.qy : /* istanbul ignore next */ 0);
36
+ params.qx = qx;
37
+ params.qy = qy;
38
+ result = ['Q', qx, qy, ...(values as [number, number])];
39
+ } else if (pathCommand === 'Q') {
40
+ const [nqx, nqy] = values as [number, number];
41
+ params.qx = nqx;
42
+ params.qy = nqy;
43
+ }
44
+
45
+ return result as NormalSegment;
46
+ };
47
+ export default normalizeSegment;
@@ -4,28 +4,25 @@ import pathToRelative from '../convert/pathToRelative';
4
4
  import shortenSegment from './shortenSegment';
5
5
  import paramsParser from '../parser/paramsParser';
6
6
  import normalizePath from './normalizePath';
7
+ import type { PathSegment, HSegment, PathArray, VSegment, PathCommand, AbsoluteSegment } from '../types';
7
8
 
8
9
  /**
9
10
  * Optimizes a `pathArray` object:
10
11
  * * convert segments to shorthand if possible
11
12
  * * select shortest segments from absolute and relative `pathArray`s
12
13
  *
13
- * TO DO
14
- * * implement `auto` for rounding values based on pathBBox
15
- * * also revers path check if it's smaller string, maybe?
16
- *
17
- * @param {SVGPath.pathArray} pathInput a string or `pathArray`
18
- * @param {number | 'off'} round the amount of decimals to round values to
19
- * @returns {SVGPath.pathArray} the optimized `pathArray`
14
+ * @param pathInput a string or `pathArray`
15
+ * @param round the amount of decimals to round values to
16
+ * @returns the optimized `pathArray`
20
17
  */
21
- export default function optimizePath(pathInput, round) {
18
+ const optimizePath = (pathInput: PathArray, round: 'off' | number): PathArray => {
22
19
  const path = pathToAbsolute(pathInput);
23
20
  const normalPath = normalizePath(path);
24
21
  const params = { ...paramsParser };
25
- const allPathCommands = [];
22
+ const allPathCommands = [] as PathCommand[];
26
23
  const ii = path.length;
27
- let pathCommand = '';
28
- let prevCommand = '';
24
+ let pathCommand = '' as PathCommand;
25
+ let prevCommand = '' as PathCommand;
29
26
  let x = 0;
30
27
  let y = 0;
31
28
  let mx = 0;
@@ -38,7 +35,7 @@ export default function optimizePath(pathInput, round) {
38
35
  allPathCommands[i] = pathCommand;
39
36
  // Get previous path command for `shortenSegment`
40
37
  if (i) prevCommand = allPathCommands[i - 1];
41
- path[i] = shortenSegment(path[i], normalPath[i], params, prevCommand);
38
+ path[i] = shortenSegment(path[i], normalPath[i], params, prevCommand) as AbsoluteSegment;
42
39
 
43
40
  const segment = path[i];
44
41
  const seglen = segment.length;
@@ -46,8 +43,8 @@ export default function optimizePath(pathInput, round) {
46
43
  // update C, S, Q, T specific params
47
44
  params.x1 = +segment[seglen - 2];
48
45
  params.y1 = +segment[seglen - 1];
49
- params.x2 = +(segment[seglen - 4]) || params.x1;
50
- params.y2 = +(segment[seglen - 3]) || params.y1;
46
+ params.x2 = +segment[seglen - 4] || params.x1;
47
+ params.y2 = +segment[seglen - 3] || params.y1;
51
48
 
52
49
  // update x, y params
53
50
  switch (pathCommand) {
@@ -56,10 +53,10 @@ export default function optimizePath(pathInput, round) {
56
53
  y = my;
57
54
  break;
58
55
  case 'H':
59
- [, x] = segment;
56
+ [, x] = segment as HSegment;
60
57
  break;
61
58
  case 'V':
62
- [, y] = segment;
59
+ [, y] = segment as VSegment;
63
60
  break;
64
61
  default:
65
62
  [x, y] = segment.slice(-2).map(Number);
@@ -76,11 +73,11 @@ export default function optimizePath(pathInput, round) {
76
73
  const absolutePath = roundPath(path, round);
77
74
  const relativePath = roundPath(pathToRelative(path), round);
78
75
 
79
- return absolutePath.map((a, i) => {
76
+ return absolutePath.map((a: PathSegment, i: number) => {
80
77
  if (i) {
81
- return a.join('').length < relativePath[i].join('').length
82
- ? a : relativePath[i];
78
+ return a.join('').length < relativePath[i].join('').length ? a : relativePath[i];
83
79
  }
84
80
  return a;
85
- });
86
- }
81
+ }) as PathArray;
82
+ };
83
+ export default optimizePath;
@@ -1,4 +1,4 @@
1
- import { Translate } from 'dommatrix/src/dommatrix';
1
+ import CSSMatrix from '@thednp/dommatrix';
2
2
 
3
3
  /**
4
4
  * Transforms a specified point using a matrix, returning a new
@@ -7,18 +7,18 @@ import { Translate } from 'dommatrix/src/dommatrix';
7
7
  *
8
8
  * @copyright thednp © 2021
9
9
  *
10
- * @param {SVGPath.CSSMatrix} M CSSMatrix instance
11
- * @param {[number, number, number, number]} v Tuple
12
- * @return {[number, number, number, number]} the resulting Tuple
10
+ * @param cssm CSSMatrix instance
11
+ * @param v Tuple
12
+ * @return the resulting Tuple
13
13
  */
14
- function translatePoint(M, v) {
15
- let m = Translate(...v);
14
+ const translatePoint = (cssm: CSSMatrix, v: [number, number, number, number]): [number, number, number, number] => {
15
+ let m = CSSMatrix.Translate(...(v.slice(0, -1) as [number, number, number]));
16
16
 
17
- [,,, m.m44] = v;
18
- m = M.multiply(m);
17
+ [, , , m.m44] = v;
18
+ m = cssm.multiply(m);
19
19
 
20
20
  return [m.m41, m.m42, m.m43, m.m44];
21
- }
21
+ };
22
22
 
23
23
  /**
24
24
  * Returns the [x,y] projected coordinates for a given an [x,y] point
@@ -29,12 +29,12 @@ function translatePoint(M, v) {
29
29
  * Details =>
30
30
  * https://stackoverflow.com/questions/23792505/predicted-rendering-of-css-3d-transformed-pixel
31
31
  *
32
- * @param {SVGPath.CSSMatrix} m the transformation matrix
33
- * @param {[number, number]} point2D the initial [x,y] coordinates
34
- * @param {number[]} origin the [x,y,z] transform origin
35
- * @returns {[number, number]} the projected [x,y] coordinates
32
+ * @param m the transformation matrix
33
+ * @param point2D the initial [x,y] coordinates
34
+ * @param origin the [x,y,z] transform origin
35
+ * @returns the projected [x,y] coordinates
36
36
  */
37
- export default function projection2d(m, point2D, origin) {
37
+ const projection2d = (m: CSSMatrix, point2D: [number, number], origin: [number, number, number]): [number, number] => {
38
38
  const [originX, originY, originZ] = origin;
39
39
  const [x, y, z] = translatePoint(m, [...point2D, 0, 1]);
40
40
 
@@ -47,4 +47,5 @@ export default function projection2d(m, point2D, origin) {
47
47
  relativePositionX * (Math.abs(originZ) / Math.abs(relativePositionZ) || 1) + originX,
48
48
  relativePositionY * (Math.abs(originZ) / Math.abs(relativePositionZ) || 1) + originY,
49
49
  ];
50
- }
50
+ };
51
+ export default projection2d;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Converts a Q (quadratic-bezier) segment to C (cubic-bezier).
3
+ *
4
+ * @param x1 curve start x
5
+ * @param y1 curve start y
6
+ * @param qx control point x
7
+ * @param qy control point y
8
+ * @param x2 curve end x
9
+ * @param y2 curve end y
10
+ * @returns the cubic-bezier segment
11
+ */
12
+ const quadToCubic = (
13
+ x1: number,
14
+ y1: number,
15
+ qx: number,
16
+ qy: number,
17
+ x2: number,
18
+ y2: number,
19
+ ): [number, number, number, number, number, number] => {
20
+ const r13 = 1 / 3;
21
+ const r23 = 2 / 3;
22
+ return [
23
+ r13 * x1 + r23 * qx, // cpx1
24
+ r13 * y1 + r23 * qy, // cpy1
25
+ r13 * x2 + r23 * qx, // cpx2
26
+ r13 * y2 + r23 * qy, // cpy2
27
+ x2,
28
+ y2, // x,y
29
+ ];
30
+ };
31
+ export default quadToCubic;
@@ -0,0 +1,21 @@
1
+ import type { CurveArray } from '../types';
2
+
3
+ /**
4
+ * Reverses all segments of a `pathArray`
5
+ * which consists of only C (cubic-bezier) path commands.
6
+ *
7
+ * @param path the source `pathArray`
8
+ * @returns the reversed `pathArray`
9
+ */
10
+ const reverseCurve = (path: CurveArray): CurveArray => {
11
+ const rotatedCurve = path
12
+ .slice(1)
13
+ .map((x, i, curveOnly) =>
14
+ !i ? [...path[0].slice(1), ...x.slice(1)] : [...curveOnly[i - 1].slice(-2), ...x.slice(1)],
15
+ )
16
+ .map(x => x.map((_, i) => x[x.length - i - 2 * (1 - (i % 2))]))
17
+ .reverse();
18
+
19
+ return [['M', ...rotatedCurve[0].slice(0, 2)], ...rotatedCurve.map(x => ['C', ...x.slice(2)])] as CurveArray;
20
+ };
21
+ export default reverseCurve;
@@ -0,0 +1,101 @@
1
+ import type {
2
+ ASegment,
3
+ CSegment,
4
+ HSegment,
5
+ MSegment,
6
+ PathArray,
7
+ PathSegment,
8
+ QSegment,
9
+ SSegment,
10
+ TSegment,
11
+ VSegment,
12
+ } from 'src/types';
13
+ import pathToAbsolute from '../convert/pathToAbsolute';
14
+ import normalizePath from './normalizePath';
15
+
16
+ /**
17
+ * Reverses all segments of a `pathArray` and returns a new `pathArray` instance.
18
+ *
19
+ * @param pathInput the source `pathArray`
20
+ * @returns the reversed `pathArray`
21
+ */
22
+ const reversePath = (pathInput: PathArray): PathArray => {
23
+ const absolutePath = pathToAbsolute(pathInput);
24
+ const isClosed = absolutePath.slice(-1)[0][0] === 'Z';
25
+
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 = [];
47
+
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
+ }
95
+
96
+ return result;
97
+ });
98
+
99
+ return (isClosed ? reversedPath.reverse() : [reversedPath[0], ...reversedPath.slice(1).reverse()]) as PathArray;
100
+ };
101
+ export default reversePath;
@@ -0,0 +1,29 @@
1
+ import type { PathArray } from 'src/types';
2
+ import defaultOptions from '../options/options';
3
+
4
+ /**
5
+ * Rounds the values of a `pathArray` instance to
6
+ * a specified amount of decimals and returns it.
7
+ *
8
+ * @param path the source `pathArray`
9
+ * @param roundOption the amount of decimals to round numbers to
10
+ * @returns the resulted `pathArray` with rounded values
11
+ */
12
+ const roundPath = (path: PathArray, roundOption?: number | 'off'): PathArray => {
13
+ let { round } = defaultOptions;
14
+ if (roundOption === 'off' || round === 'off') return [...path];
15
+ // allow for ZERO decimals
16
+ round = typeof roundOption === 'number' && roundOption >= 0 ? roundOption : round;
17
+ // to round values to the power
18
+ // the `round` value must be integer
19
+ const pow = typeof round === 'number' && round >= 1 ? 10 ** round : 1;
20
+
21
+ return path.map(pi => {
22
+ const values = pi
23
+ .slice(1)
24
+ .map(Number)
25
+ .map(n => (round ? Math.round(n * pow) / pow : Math.round(n)));
26
+ return [pi[0], ...values];
27
+ }) as PathArray;
28
+ };
29
+ export default roundPath;