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
@@ -1,5 +1,6 @@
1
1
  import type { PathArray } from '../types';
2
2
  import getPropertiesAtPoint from './getPropertiesAtPoint';
3
+ import DISTANCE_EPSILON from './distanceEpsilon';
3
4
 
4
5
  /**
5
6
  * Checks if a given point is in the stroke of a path.
@@ -10,6 +11,6 @@ import getPropertiesAtPoint from './getPropertiesAtPoint';
10
11
  */
11
12
  const isPointInStroke = (pathInput: string | PathArray, point: { x: number; y: number }) => {
12
13
  const { distance } = getPropertiesAtPoint(pathInput, point);
13
- return Math.abs(distance) < 0.001; // 0.01 might be more permissive
14
+ return Math.abs(distance) < DISTANCE_EPSILON; // 0.01 might be more permissive
14
15
  };
15
16
  export default isPointInStroke;
@@ -97,9 +97,9 @@ export const getRectanglePath = (attr: RectAttr): PathArray => {
97
97
  // rx = !rx ? ry : rx;
98
98
  // ry = !ry ? rx : ry;
99
99
 
100
- /* istanbul ignore else */
100
+ /* istanbul ignore else @preserve */
101
101
  if (rx * 2 > w) rx -= (rx * 2 - w) / 2;
102
- /* istanbul ignore else */
102
+ /* istanbul ignore else @preserve */
103
103
  if (ry * 2 > h) ry -= (ry * 2 - h) / 2;
104
104
 
105
105
  return [
@@ -134,7 +134,7 @@ export const getRectanglePath = (attr: RectAttr): PathArray => {
134
134
  * @param ownerDocument document for create element
135
135
  * @return the newly created `<path>` element
136
136
  */
137
- const shapeToPathArray = (element: ShapeTypes | ShapeOps, ownerDocument?: Document): PathArray | false => {
137
+ const shapeToPathArray = (element: ShapeTypes | ShapeOps, ownerDocument?: Document) => {
138
138
  const doc = ownerDocument || document;
139
139
  const win = doc.defaultView || /* istanbul ignore next */ window;
140
140
  const supportedShapes = Object.keys(shapeParams) as (keyof ShapeParams)[];
@@ -167,7 +167,11 @@ const shapeToPathArray = (element: ShapeTypes | ShapeOps, ownerDocument?: Docume
167
167
  else if (type === 'rect') pathArray = getRectanglePath(config as unknown as RectAttr);
168
168
  else if (type === 'line') pathArray = getLinePath(config as unknown as LineAttr);
169
169
  else if (['glyph', 'path'].includes(type)) {
170
- pathArray = parsePathString(targetIsElement ? element.getAttribute('d') || '' : (element as GlyphAttr).d || '');
170
+ pathArray = parsePathString(
171
+ targetIsElement
172
+ ? element.getAttribute('d') || /* istanbul ignore next @preserve */ ''
173
+ : (element as GlyphAttr).d || '',
174
+ );
171
175
  }
172
176
 
173
177
  // replace target element
@@ -0,0 +1,502 @@
1
+ import { expect, it, describe, beforeEach, vi } from 'vitest';
2
+ import SVGPathCommander from '~/index';
3
+ import invalidPathValue from '../src/parser/invalidPathValue';
4
+ import error from '../src/parser/error';
5
+
6
+ import getMarkup from './fixtures/getMarkup';
7
+ import shapes from './fixtures/shapes';
8
+ import simpleShapes from './fixtures/simpleShapes';
9
+
10
+ import "../docs/assets/style.css";
11
+
12
+ describe('SVGPathCommander Class Test', () => {
13
+ const wrapper = document.createElement('div');
14
+ document.body.append(wrapper);
15
+
16
+ beforeEach(async () => {
17
+ wrapper.innerHTML = '';
18
+ });
19
+
20
+ it('Test init with no parameter / empty throws error', () => {
21
+ try {
22
+ // @ts-expect-error
23
+ new SVGPathCommander();
24
+ } catch (er) {
25
+ expect(er).to.be.instanceOf(TypeError);
26
+ expect(er).to.have.property('message', `${error}: "pathValue" is undefined`);
27
+ }
28
+ try {
29
+ new SVGPathCommander('');
30
+ } catch (er) {
31
+ expect(er).to.be.instanceOf(TypeError);
32
+ expect(er).to.have.property('message', `${error}: "pathValue" is empty`);
33
+ }
34
+ });
35
+
36
+ it('Test init with invalid path value throws error', () => {
37
+ try {
38
+ new SVGPathCommander('M04 36.9a23.5 23');
39
+ } catch (er) {
40
+ expect(er).to.be.instanceOf(TypeError);
41
+ expect(er).to.have.property('message', `${error}: ${invalidPathValue} at index 1, "0" illegal number`);
42
+ }
43
+
44
+ try {
45
+ new SVGPathCommander('M4 36.9efa23.5 23');
46
+ } catch (er) {
47
+ expect(er).to.be.instanceOf(TypeError);
48
+ expect(er).to.have.property('message', `${error}: ${invalidPathValue} at index 8, "f" invalid integer exponent`);
49
+ }
50
+
51
+ try {
52
+ new SVGPathCommander('M4 .ea23.5 23');
53
+ } catch (er) {
54
+ expect(er).to.be.instanceOf(TypeError);
55
+ expect(er).to.have.property('message', `${error}: ${invalidPathValue} at index 4, "e" invalid float exponent`);
56
+ }
57
+
58
+ try {
59
+ new SVGPathCommander('M4 36.9a23.5 23');
60
+ } catch (er) {
61
+ expect(er).to.be.instanceOf(TypeError);
62
+ expect(er).to.have.property('message', `${error}: ${invalidPathValue} at index 15, "pathValue" is missing param`);
63
+ }
64
+
65
+ try {
66
+ new SVGPathCommander('M2 0a2 2 0 00-2 2 12');
67
+ } catch (er) {
68
+ expect(er).to.be.instanceOf(TypeError);
69
+ expect(er).to.have.property('message', `${error}: ${invalidPathValue} at index 20, "pathValue" is missing param`);
70
+ }
71
+
72
+ try {
73
+ new SVGPathCommander('M2 0aa 2 0 00-2 2 12');
74
+ } catch (er) {
75
+ expect(er).to.be.instanceOf(TypeError);
76
+ expect(er).to.have.property('message', `${error}: ${invalidPathValue} at index 5, "a" is not a number`);
77
+ }
78
+
79
+ try {
80
+ new SVGPathCommander('M2 0a2 2 0 21-2 2 12');
81
+ } catch (er) {
82
+ expect(er).to.be.instanceOf(TypeError);
83
+ expect(er).to.have.property('message', `${error}: invalid Arc flag "2", expecting 0 or 1 at index 11`);
84
+ }
85
+
86
+ try {
87
+ new SVGPathCommander('M2 0a2 2 0 03-2 2 12');
88
+ } catch (er) {
89
+ expect(er).to.be.instanceOf(TypeError);
90
+ expect(er).to.have.property('message', `${error}: invalid Arc flag "3", expecting 0 or 1 at index 12`);
91
+ }
92
+
93
+ try {
94
+ new SVGPathCommander('2 0a2 2 0 00-2 2');
95
+ } catch (er) {
96
+ expect(er).to.be.instanceOf(TypeError);
97
+ expect(er).to.have.property('message', `${error}: ${invalidPathValue} "2" is not a path command at index 0`);
98
+ }
99
+
100
+ try {
101
+ new SVGPathCommander(`M13.158 9.208a1.63 1.63 0 0 0 -0.906 0.274a1.63 1.63 0 0 0 -0.601 0.73a1.63 1.63 0 0 0 -0.094 0.942a1.63 1.63 0 0 0 3.229 -0.314a1.6 1.6 0 0 0 -0.12 -0.627a1.6 1.6 0 0 0 -0.353 -0.533a1.6 1.6 0 0 0 -0.53 -0.356a1.6 1.6 0 0 0 -0.625 -0.125z
102
+ a1.63 1.63 0 0 0 -0.906 0.274a1.63 1.63 0 0 0 -0.601 0.73a1.63 1.63 0 0 0 -0.094 0.942a1.63 1.63 0 0 0 3.229 -0.314a1.6 1.6 0 0 0 -0.12 -0.627a1.6 1.6 0 0 0 -0.353 -0.533a1.6 1.6 0 0 0 -0.53 -0.356a1.6 1.6 0 0 0 -0.625 -0.125za1.63 1.63 0 0 0 -0.906 0.274a1.63 1.63 0 0 0 -0.601 0.73a1.63 1.63 0 0 0 -0.094 0.942a1.63 1.63 0 0 0 3.229 -0.314a1.6 1.6 0 0 0 -0.12 -0.627a1.6 1.6 0 0 0 -0.353 -0.533a1.6 1.6 0 0 0 -0.53 -0.356a1.6 1.6 0 0 0 -0.625 -0.125z`);
103
+ } catch (er) {
104
+ expect(er).to.be.instanceOf(TypeError);
105
+ expect(er).to.have.property('message', `${error}: ${invalidPathValue} "a" is not a MoveTo path command at index 240`);
106
+ }
107
+ });
108
+
109
+ it('Test init with valid path value works', async () => {
110
+ const container = getMarkup();
111
+ wrapper.append(container);
112
+ await vi.waitFor(() => container.querySelector('svg'), { timeout: 200 });
113
+ const path = await vi.waitFor(() => container.querySelector('path') as SVGPathElement, { timeout: 200 });
114
+ const rect = new SVGPathCommander(
115
+ `M 2 0,
116
+ a2,2 0 00-2 2
117
+ v12
118
+ a2 2 0 002 2
119
+ h12
120
+ a2 2 0 0 0 2-2
121
+ V2
122
+ a2 2 0 0 0-2-2
123
+ H2
124
+ z`,
125
+ { origin: [8,8,24] }
126
+ );
127
+ expect(rect.segments).to.have.length(10);
128
+ expect(rect.origin).to.deep.equal([8,8,24]);
129
+ expect(rect.round).to.equal(4);
130
+ path.setAttribute('d', rect.toString());
131
+ expect(path.getAttribute('d')).to.equal(rect.toString())
132
+ });
133
+
134
+ it('Test overloaded moveTo', async () => {
135
+ const container = getMarkup();
136
+ wrapper.append(container);
137
+ await vi.waitFor(() => container.querySelector('svg'), { timeout: 200 });
138
+ const path = await vi.waitFor(() => container.querySelector('path') as SVGPathElement, { timeout: 200 });
139
+
140
+ const star = new SVGPathCommander(
141
+ `M12.774 14.5111 8.0167 12.292 3.4918 14.9529 4.1321 9.7428 0.2031 6.2615 5.3562 5.2604 7.4528 0.4479 9.9972 5.0393 15.222 5.5463 11.6414 9.3851Z`,
142
+ {round: 2}
143
+ );
144
+ const star1 = new SVGPathCommander(
145
+ `m12.774 14.5111 -4.7573 -2.2191 -4.5249 2.6609 0.6403 -5.2101 -3.929 -3.4813 5.1531 -1.0011 2.0966 -4.8125 2.5444 4.5914 5.2248 0.507 -3.5806 3.8388z`,
146
+ {round: 2}
147
+ );
148
+
149
+ expect(star.segments).to.have.length(11);
150
+ expect(star1.segments).to.have.length(11);
151
+
152
+ path.setAttribute('d', star.toString());
153
+ expect(path.getAttribute('d')).to.equal(star.toString());
154
+
155
+ path.setAttribute('d', star1.toString());
156
+ expect(path.getAttribute('d')).to.equal(star1.toString());
157
+ });
158
+
159
+ it('Test rounding `off`, and [0-5]', async () => {
160
+ const container = getMarkup();
161
+ wrapper.append(container);
162
+ await vi.waitFor(() => container.querySelector('svg'), { timeout: 200 });
163
+ const path = await vi.waitFor(() => container.querySelector('path') as SVGPathElement, { timeout: 200 });
164
+
165
+ const rect = new SVGPathCommander(
166
+ 'M2 0C0.8954304997175604 -8.780183295920349e-10 -1.3527075029566811e-16 0.8954304997175604 0 2C0 2 0 9.875 0 14C1.3527075029566811e-16 15.10456950028244 0.8954304997175604 16.000000000878018 2 16C8 16 10.25 16 14 16C15.104569499040734 15.999999999121982 16 15.104569499040734 16 14C16 8 16 5.75 16 2C16 0.8954305009592662 15.104569499040734 8.780185991465076e-10 14 0C8 0 5.75 0 2 0',
167
+ { round: 2 }
168
+ );
169
+ expect(rect.round).to.equal(2);
170
+ path.setAttribute('d', rect.toString());
171
+ expect(path.getAttribute('d')).to.equal('M2 0C0.9 0 0 0.9 0 2C0 2 0 9.88 0 14C0 15.1 0.9 16 2 16C8 16 10.25 16 14 16C15.1 16 16 15.1 16 14C16 8 16 5.75 16 2C16 0.9 15.1 0 14 0C8 0 5.75 0 2 0')
172
+
173
+ const rect1 = new SVGPathCommander(
174
+ 'M7.94 7.92C7.928954 7.92 7.92 7.928954 7.92 7.94C7.92 7.94 7.92 8.01875 7.92 8.06C7.92 8.071046 7.928954 8.08 7.94 8.08C8 8.08 8.0225 8.08 8.06 8.08C8.071046 8.08 8.08 8.071046 8.08 8.06C8.08 8 8.08 7.9775 8.08 7.94C8.08 7.928954 8.071046 7.92 8.06 7.92C8 7.92 7.9775 7.92 7.94 7.92',
175
+ { round: 'off' }
176
+ );
177
+ expect(rect1.round).to.equal('off');
178
+ path.setAttribute('d', rect1.toString());
179
+ expect(path.getAttribute('d')).to.equal('M7.94 7.92C7.928954 7.92 7.92 7.928954 7.92 7.94C7.92 7.94 7.92 8.01875 7.92 8.06C7.92 8.071046 7.928954 8.08 7.94 8.08C8 8.08 8.0225 8.08 8.06 8.08C8.071046 8.08 8.08 8.071046 8.08 8.06C8.08 8 8.08 7.9775 8.08 7.94C8.08 7.928954 8.071046 7.92 8.06 7.92C8 7.92 7.9775 7.92 7.94 7.92')
180
+
181
+ const rect2 = new SVGPathCommander(
182
+ 'M895.02 5.12C963.38 5.12 1018.88 60.62 1018.88 128.98V895.03C1018.88 963.39 963.38 1018.89 895.02 1018.89H128.98C60.62 1018.88 5.12 963.38 5.12 895.02V128.98C5.12 60.62 60.62 5.12 128.98 5.12H895.03Z',
183
+ { round: 0 }
184
+ );
185
+ expect(rect2.round).to.equal(0);
186
+ path.setAttribute('d', rect2.toString());
187
+ expect(path.getAttribute('d')).to.equal('M895 5C963 5 1019 61 1019 129V895C1019 963 963 1019 895 1019H129C61 1019 5 963 5 895V129C5 61 61 5 129 5H895Z')
188
+
189
+ const roundSample = 'M2 0C0.8954304997175604 -8.780183295920349e-10 -1.3527075029566811e-16 0.8954304997175604 0 2C0 2 0 9.875 0 14C1.3527075029566811e-16 15.10456950028244 0.8954304997175604 16.000000000878018 2 16C8 16 10.25 16 14 16C15.104569499040734 15.999999999121982 16 15.104569499040734 16 14C16 8 16 5.75 16 2C16 0.8954305009592662 15.104569499040734 8.780185991465076e-10 14 0C8 0 5.75 0 2 0';
190
+ const rect3 = new SVGPathCommander(roundSample, {round: 'off'});
191
+ expect(rect3.round).to.equal('off');
192
+ path.setAttribute('d', rect3.toString());
193
+ expect(path.getAttribute('d')).to.equal(roundSample);
194
+
195
+ const rect4 = new SVGPathCommander(roundSample, {round: 0});
196
+ expect(rect4.round).to.equal(0);
197
+ path.setAttribute('d', rect4.toString());
198
+ expect(path.getAttribute('d')).to.equal('M2 0C1 0 0 1 0 2C0 2 0 10 0 14C0 15 1 16 2 16C8 16 10 16 14 16C15 16 16 15 16 14C16 8 16 6 16 2C16 1 15 0 14 0C8 0 6 0 2 0');
199
+
200
+ const rect5 = new SVGPathCommander(roundSample, {round: 5});
201
+ expect(rect5.round).to.equal(5);
202
+ path.setAttribute('d', rect5.toString());
203
+ expect(path.getAttribute('d')).to.equal('M2 0C0.89543 0 0 0.89543 0 2C0 2 0 9.875 0 14C0 15.10457 0.89543 16 2 16C8 16 10.25 16 14 16C15.10457 16 16 15.10457 16 14C16 8 16 5.75 16 2C16 0.89543 15.10457 0 14 0C8 0 5.75 0 2 0')
204
+ });
205
+
206
+ it('Test getBBox', async () => {
207
+ const rect = new SVGPathCommander('M2 0a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V2a2 2 0 00-2-2H2z').getBBox();
208
+ expect(rect, 'Using the SVGPathCommander').to.deep.equal({cx: 8, cy: 8, cz: 24, height: 16, width: 16, x: 0, x2: 16, y: 0, y2: 16});
209
+ expect(SVGPathCommander.getPathBBox(''), 'Using the static method').to.deep.equal({cx: 0, cy: 0, cz: 0, height: 0, width: 0, x: 0, x2: 0, y: 0, y2: 0});
210
+ });
211
+
212
+ it('Test getTotalLength', () => {
213
+ const len = new SVGPathCommander('M2 0a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V2a2 2 0 00-2-2H2z').getTotalLength();
214
+ expect(Math.round(len)).to.equal(61);
215
+ });
216
+
217
+ it('Test getPointAtLength', () => {
218
+ const pt = new SVGPathCommander('M2 0a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V2a2 2 0 00-2-2H2z').getPointAtLength(25);
219
+ expect(pt).to.deep.equal({ x: 8.716814692820414, y: 16 });
220
+
221
+ const pt1 = new SVGPathCommander('M2 0A2 2 0 00 2 0').getPointAtLength(0);
222
+ expect(pt1).to.deep.equal({ x: 2, y: 0 });
223
+
224
+ const pt2 = new SVGPathCommander('M2 0A0 2 0 00 0 2').getPointAtLength(0.5);
225
+ expect(pt2).to.deep.equal({x: 0, y: 2 });
226
+
227
+ const pt3 = new SVGPathCommander('M2 0A3 2 0 00 0 2').getPointAtLength(5);
228
+ expect(pt3).to.deep.equal({x: 0, y: 2});
229
+ });
230
+
231
+ it('Test toAbsolute', async () => {
232
+ const container = getMarkup();
233
+ wrapper.append(container);
234
+ await vi.waitFor(() => container.querySelector('svg'), { timeout: 200 });
235
+ const path = await vi.waitFor(() => container.querySelector('path') as SVGPathElement, { timeout: 200 });
236
+
237
+ const rect = new SVGPathCommander('M2 0a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V2a2 2 0 00-2-2H2z').toAbsolute();
238
+ path.setAttribute('d', rect.toString());
239
+ expect(path.getAttribute('d')).to.equal('M2 0A2 2 0 0 0 0 2V14A2 2 0 0 0 2 16H14A2 2 0 0 0 16 14V2A2 2 0 0 0 14 0H2Z')
240
+ });
241
+
242
+ it('Test toRelative', async () => {
243
+ const container = getMarkup();
244
+ wrapper.append(container);
245
+ await vi.waitFor(() => container.querySelector('svg'), { timeout: 200 });
246
+ const path = await vi.waitFor(() => container.querySelector('path') as SVGPathElement, { timeout: 200 });
247
+
248
+ const rect = new SVGPathCommander('M2 0A2 2 0 0 0 0 2V14A2 2 0 0 0 2 16H14A2 2 0 0 0 16 14V2A2 2 0 0 0 14 0H2Z').toRelative();
249
+ path.setAttribute('d', rect.toString());
250
+ expect(path.getAttribute('d')).to.equal('M2 0a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2h-12z')
251
+ });
252
+
253
+ it('Test toCurve', async () => {
254
+ const container = getMarkup();
255
+ wrapper.append(container);
256
+ await vi.waitFor(() => container.querySelector('svg'), { timeout: 200 });
257
+ const path = await vi.waitFor(() => container.querySelector('path') as SVGPathElement, { timeout: 200 });
258
+
259
+ const rect = new SVGPathCommander('M2 0a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V2a2 2 0 00-2-2H2z', { round: 'off' }).toCurve();
260
+ path.setAttribute('d', rect.toString());
261
+ expect(rect.round).to.equal('off')
262
+ expect(path.getAttribute('d')).to.equal('M2 0C0.8954305003384135 2.0290612532945332e-16 -1.3527075021963556e-16 0.8954305003384133 0 2C0 6 0 10 0 14C1.3527075021963556e-16 15.104569499661586 0.8954305003384133 16 2 16C6 16 10 16 14 16C15.104569499661586 16 16 15.104569499661586 16 14C16 10 16 6 16 2C16 0.8954305003384135 15.104569499661586 6.763537510981778e-17 14 0C10 0 6 0 2 0C2 0 2 0 2 0')
263
+ });
264
+
265
+ it('Test normalize', async () => {
266
+ const container = getMarkup();
267
+ wrapper.append(container);
268
+ await vi.waitFor(() => container.querySelector('svg'), { timeout: 200 });
269
+ const path = await vi.waitFor(() => container.querySelector('path') as SVGPathElement, { timeout: 200 });
270
+
271
+ const rect = new SVGPathCommander('M2 0A2 2 0 0 0 0 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2V2A2 2 0 0 0 14 0H2z').normalize();
272
+ path.setAttribute('d', rect.toString());
273
+ expect(path.getAttribute('d')).to.equal('M2 0A2 2 0 0 0 0 2L0 14A2 2 0 0 0 2 16L14 16A2 2 0 0 0 16 14L16 2A2 2 0 0 0 14 0L2 0Z')
274
+ });
275
+
276
+ it('Test optimize', async () => {
277
+ const container = getMarkup();
278
+ wrapper.append(container);
279
+ await vi.waitFor(() => container.querySelector('svg'), { timeout: 200 });
280
+ const path = await vi.waitFor(() => container.querySelector('path') as SVGPathElement, { timeout: 200 });
281
+
282
+ const rect = new SVGPathCommander('M2 0A2 2 0 0 0 0 2L0 14A2 2 0 0 0 2 16L14 16A2 2 0 0 0 16 14L16 2A2 2 0 0 0 14 0L2 0Z').optimize();
283
+
284
+ path.setAttribute('d', rect.toString());
285
+ expect(path.getAttribute('d')).to.equal('M2 0A2 2 0 0 0 0 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2V2A2 2 0 0 0 14 0H2z');
286
+ });
287
+
288
+ it('Test reverse single path', async () => {
289
+ const container = getMarkup();
290
+ wrapper.append(container);
291
+ await vi.waitFor(() => container.querySelector('svg'), { timeout: 200 });
292
+ const path = await vi.waitFor(() => container.querySelector('path') as SVGPathElement, { timeout: 200 });
293
+
294
+ const rect = new SVGPathCommander('M2 0A2 2 0 0 0 0 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2V2A2 2 0 0 0 14 0H2z').reverse();
295
+ path.setAttribute('d', rect.toString());
296
+ expect(path.getAttribute('d')).to.equal('M2 0H14A2 2 0 0 1 16 2V14A2 2 0 0 1 14 16H2A2 2 0 0 1 0 14V2A2 2 0 0 1 2 0Z')
297
+
298
+ const rect1 = new SVGPathCommander('M2 0A2 2 0 0 0 0 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2V2A2 2 0 0 0 14 0H2z').reverse(true);
299
+
300
+ path.setAttribute('d', rect1.toString());
301
+ expect(path.getAttribute('d')).to.equal('M2 0A2 2 0 0 0 0 2V14A2 2 0 0 0 2 16H14A2 2 0 0 0 16 14V2A2 2 0 0 0 14 0H2Z')
302
+ });
303
+
304
+ it('Test reverse composite', async () => {
305
+ const container = getMarkup();
306
+ wrapper.append(container);
307
+ await vi.waitFor(() => container.querySelector('svg'), { timeout: 200 });
308
+ const path = await vi.waitFor(() => container.querySelector('path') as SVGPathElement, { timeout: 200 });
309
+
310
+ const rect = new SVGPathCommander('M2 0A2 2 0 0 0 0 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2V2A2 2 0 0 0 14 0H2zM4 4h8l-4 8z').reverse();
311
+ path.setAttribute('d', rect.toString());
312
+ expect(path.getAttribute('d')).to.equal('M2 0H14A2 2 0 0 1 16 2V14A2 2 0 0 1 14 16H2A2 2 0 0 1 0 14V2A2 2 0 0 1 2 0ZM8 12L12 4H4Z')
313
+ });
314
+
315
+ it('Test reverse composite path with `onlySubpath`', async () => {
316
+ const container = getMarkup();
317
+ wrapper.append(container);
318
+ await vi.waitFor(() => container.querySelector('svg'), { timeout: 200 });
319
+ const path = await vi.waitFor(() => container.querySelector('path') as SVGPathElement, { timeout: 200 });
320
+
321
+ const rect = new SVGPathCommander('M2 0A2 2 0 0 0 0 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2V2A2 2 0 0 0 14 0H2zM4 4h8l-4 8z').reverse(true);
322
+ path.setAttribute('d', rect.toString());
323
+ expect(path.getAttribute('d')).to.equal('M2 0A2 2 0 0 0 0 2V14A2 2 0 0 0 2 16H14A2 2 0 0 0 16 14V2A2 2 0 0 0 14 0H2ZM8 12L12 4H4Z')
324
+ });
325
+
326
+ it('Test flipX', () => {
327
+ const triangle = new SVGPathCommander('M0 0L16 0 L8 16');
328
+ expect(triangle.flipX().toString()).to.equal('M16 0H0L8 16');
329
+ });
330
+
331
+ it('Test flipY', () => {
332
+ const triangle = new SVGPathCommander('M0 0L16 0 L8 16');
333
+ expect(triangle.flipY().toString()).to.equal('M0 16H16L8 0');
334
+ });
335
+
336
+ it('Test empty transform', () => {
337
+ const transform = {}
338
+ const rect = new SVGPathCommander('M2 0a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V2a2 2 0 00-2-2H2z');
339
+ const transformed = rect.transform(transform);
340
+ const transformed1 = rect.transform();
341
+ // @ts-expect-error
342
+ const transformed2 = rect.transform('');
343
+
344
+ expect(rect.segments).to.deep.equal(transformed.segments);
345
+ expect(rect.segments).to.deep.equal(transformed1.segments);
346
+ expect(rect.segments).to.deep.equal(transformed2.segments);
347
+ });
348
+
349
+ it('Test transform', async () => {
350
+ const container = getMarkup();
351
+ wrapper.append(container);
352
+ await vi.waitFor(() => container.querySelector('svg'), { timeout: 200 });
353
+ const path = await vi.waitFor(() => container.querySelector('path') as SVGPathElement, { timeout: 200 });
354
+
355
+ const transform = {
356
+ rotate: [15,15],
357
+ skew: [-15,15],
358
+ }
359
+ const rect = new SVGPathCommander('M2 0a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V2a2 2 0 00-2-2H2z').transform(transform);
360
+ path.setAttribute('d', rect.toString());
361
+ expect(path.getAttribute('d')).to.equal('M1.829 0.5176C0.8189 0.2318 -0.1718 0.8649 -0.3837 1.9319L-2.6856 13.523C-2.8975 14.5899 -2.2504 15.6866 -1.2403 15.9725L9.734 19.0783C10.7442 19.3642 11.7349 18.731 11.9468 17.6641L14.2487 6.073C14.4606 5.006 13.8135 3.9094 12.8033 3.6235L1.829 0.5176Z')
362
+ });
363
+
364
+ it('Test transform with custom [x,y] origin', async () => {
365
+ const container = getMarkup();
366
+ wrapper.append(container);
367
+ await vi.waitFor(() => container.querySelector('svg'), { timeout: 200 });
368
+ const path = await vi.waitFor(() => container.querySelector('path') as SVGPathElement, { timeout: 200 });
369
+
370
+ const transform = {
371
+ rotate: [15,15],
372
+ skew: [-15,15],
373
+ origin: [0,0]
374
+ }
375
+ const rect = new SVGPathCommander('M2 0a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V2a2 2 0 00-2-2H2z').transform(transform);
376
+ path.setAttribute('d', rect.toString());
377
+ expect(path.getAttribute('d')).to.equal('M1.829 0.5176C0.8189 0.2318 -0.1718 0.8649 -0.3837 1.9319L-2.6856 13.523C-2.8975 14.5899 -2.2504 15.6866 -1.2403 15.9725L9.734 19.0783C10.7442 19.3642 11.7349 18.731 11.9468 17.6641L14.2487 6.073C14.4606 5.006 13.8135 3.9094 12.8033 3.6235L1.829 0.5176Z')
378
+ });
379
+
380
+ it('Test transform with custom [x,y,z] origin option', async () => {
381
+ const container = getMarkup();
382
+ wrapper.append(container);
383
+ await vi.waitFor(() => container.querySelector('svg'), { timeout: 200 });
384
+ const path = await vi.waitFor(() => container.querySelector('path') as SVGPathElement, { timeout: 200 });
385
+
386
+ const transform = {
387
+ rotate: [15,15],
388
+ skew: [-15,15]
389
+ }
390
+ const rect = new SVGPathCommander('M2 0a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V2a2 2 0 00-2-2H2z', {origin: [8,0,24]}).transform(transform);
391
+ path.setAttribute('d', rect.toString());
392
+ expect(path.getAttribute('d')).to.equal('M2.2644 -1.6232C1.1515 -1.9382 -0.0487 -1.2959 -0.4093 -0.1515L-5.2306 15.1494C-5.7788 16.8892 -5.0014 18.5047 -3.5039 18.6907L10.9116 20.4811C12.0888 20.6273 13.1574 19.4458 13.3135 17.8935L14.7232 3.8815C14.8313 2.8068 14.1803 1.7491 13.2594 1.4884L2.2644 -1.6232Z');
393
+ });
394
+
395
+ it('Test transform with invalid origin value/option, should use [0,0,0]', async () => {
396
+ const container = getMarkup();
397
+ wrapper.append(container);
398
+ await vi.waitFor(() => container.querySelector('svg'), { timeout: 200 });
399
+ const path = await vi.waitFor(() => container.querySelector('path') as SVGPathElement, { timeout: 200 });
400
+
401
+ const transform = {
402
+ rotate: [15,15],
403
+ skew: [-15,15],
404
+ scale: 1.2,
405
+ }
406
+ // @ts-expect-error
407
+ const rect = new SVGPathCommander('M2 0a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V2a2 2 0 00-2-2H2z', { origin: ['a','f','-8f'] }).transform(transform);
408
+ path.setAttribute('d', rect.toString());
409
+ expect(path.getAttribute('d')).to.equal('M2.1949 0.6212C0.9827 0.2781 -0.2061 1.0379 -0.4604 2.3182L-3.2228 16.2276C-3.477 17.5079 -2.7005 18.8239 -1.4883 19.1669L11.6808 22.8939C12.893 23.237 14.0818 22.4772 14.3361 21.1969L17.0985 7.2875C17.3527 6.0072 16.5762 4.6912 15.364 4.3482L2.1949 0.6212Z');
410
+
411
+ const transform1 = {
412
+ rotate: [15,15],
413
+ skew: [-15,15],
414
+ scale: 1.2,
415
+ origin: ['aa5','3f','-8f']
416
+ }
417
+ // @ts-expect-error
418
+ const rect1 = new SVGPathCommander('M2 0a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V2a2 2 0 00-2-2H2z').transform(transform1);
419
+ path.setAttribute('d', rect1.toString());
420
+ expect(path.getAttribute('d')).to.equal('M2.1949 0.6212C0.9827 0.2781 -0.2061 1.0379 -0.4604 2.3182L-3.2228 16.2276C-3.477 17.5079 -2.7005 18.8239 -1.4883 19.1669L11.6808 22.8939C12.893 23.237 14.0818 22.4772 14.3361 21.1969L17.0985 7.2875C17.3527 6.0072 16.5762 4.6912 15.364 4.3482L2.1949 0.6212Z');
421
+ });
422
+
423
+ simpleShapes.initial.forEach((shape, i) => {
424
+ it(`Test simple shapes #${i}`, async () => {
425
+ const container = getMarkup();
426
+ wrapper.append(container);
427
+ await vi.waitFor(() => container.querySelector('svg'), { timeout: 200 });
428
+ const path = await vi.waitFor(() => container.querySelector('path') as SVGPathElement, { timeout: 200 });
429
+
430
+ // 'Shape samples from [MDN docs](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#path_commands)'
431
+ path.ownerSVGElement?.setAttribute('viewBox', '0 0 200 100');
432
+
433
+ path.setAttribute('d', new SVGPathCommander(shape, { round: 2 }).normalize().toString());
434
+ expect(path.getAttribute('d')).to.equal(simpleShapes.normalized[i]);
435
+
436
+ path.setAttribute('d', new SVGPathCommander(shape, { round: 2 }).transform({translate: 15, rotate: 15, scale: 0.5}).toString());
437
+ expect(path.getAttribute('d')).to.equal(simpleShapes.transformed[i]);
438
+
439
+ path.setAttribute('d', new SVGPathCommander(shape, { round: 2 }).transform({scale: [0.55,0.6,0.65]}).toString());
440
+ expect(path.getAttribute('d')).to.equal(simpleShapes.scaled3d[i]);
441
+
442
+ path.setAttribute('d', new SVGPathCommander(shape, { round: 2 }).transform({skew: 45}).toString());
443
+ expect(path.getAttribute('d')).to.equal(simpleShapes.skewedX[i]);
444
+
445
+ path.setAttribute('d', new SVGPathCommander(shape, { round: 2 }).transform({skew: [45,0]}).toString());
446
+ expect(path.getAttribute('d')).to.equal(simpleShapes.skewedX[i]);
447
+
448
+ path.setAttribute('d', new SVGPathCommander(shape, { round: 2 }).transform({skew: [0,45]}).toString());
449
+ expect(path.getAttribute('d')).to.equal(simpleShapes.skewedY[i]);
450
+
451
+ path.setAttribute('d', new SVGPathCommander(shape, { round: 2 }).reverse().toString());
452
+ expect(path.getAttribute('d')).to.equal(simpleShapes.reversed[i]);
453
+
454
+ expect(new SVGPathCommander(shape).getTotalLength()).to.equal(simpleShapes.length[i]);
455
+ expect(new SVGPathCommander(shape).getPointAtLength(0)).to.deep.equal(simpleShapes.pointAt0[i]);
456
+ expect(new SVGPathCommander(shape).getPointAtLength(50)).to.deep.equal(simpleShapes.pointAt50[i]);
457
+ expect(new SVGPathCommander(shape).getPointAtLength(400)).to.deep.equal(simpleShapes.pointAt400[i]);
458
+ });
459
+ });
460
+
461
+ shapes.initial.forEach((shape, i) => {
462
+ it(`Test composite shapes #${i}`, async () => {
463
+ const container = getMarkup();
464
+ wrapper.append(container);
465
+ await vi.waitFor(() => container.querySelector('svg'), { timeout: 200 });
466
+ const path = await vi.waitFor(() => container.querySelector('path') as SVGPathElement, { timeout: 200 });
467
+
468
+ const normalized = new SVGPathCommander(shape, { round: 2 }).normalize().toString();
469
+ path.setAttribute('d', normalized);
470
+ expect(path.getAttribute('d')).to.equal(shapes.normalized[i]);
471
+ expect(new SVGPathCommander(normalized, { round: 2}).normalize().toString()).to.equal(shapes.normalized[i]); // test path already normalized
472
+
473
+ const optimized = new SVGPathCommander(shape, { round: 2 }).optimize().toString();
474
+ path.setAttribute('d', optimized);
475
+ expect(path.getAttribute('d')).to.equal(shapes.optimized[i]);
476
+ // skip checking for already optimized
477
+
478
+ const relative = new SVGPathCommander(shape, { round: 2 }).toRelative().toString();
479
+ path.setAttribute('d', relative);
480
+ expect(path.getAttribute('d')).to.equal(shapes.relative[i]);
481
+ expect(new SVGPathCommander(relative, { round: 2}).toRelative().toString()).to.equal(shapes.relative[i]);
482
+
483
+ const absolute = new SVGPathCommander(shape, { round: 2 }).toAbsolute().toString();
484
+ path.setAttribute('d', absolute);
485
+ expect(path.getAttribute('d')).to.equal(shapes.absolute[i]);
486
+ expect(new SVGPathCommander(absolute, { round: 2}).toAbsolute().toString()).to.equal(shapes.absolute[i]);
487
+
488
+ const curve = new SVGPathCommander(shape, { round: 2 }).toCurve().toString();
489
+ path.setAttribute('d', curve);
490
+ expect(path.getAttribute('d')).to.equal(shapes.curve[i]);
491
+ expect(new SVGPathCommander(curve, { round: 2}).toCurve().toString()).to.equal(shapes.curve[i]);
492
+
493
+ path.setAttribute('d', new SVGPathCommander(shape, { round: 2 }).transform({scale: 0.9}).toString());
494
+ expect(path.getAttribute('d')).to.equal(shapes.scaled[i]);
495
+
496
+ path.setAttribute('d', new SVGPathCommander(shape, { round: 2 }).transform({translate: [1,1,0]}).toString());
497
+ expect(path.getAttribute('d')).to.equal(shapes.translated[i]);
498
+ expect(new SVGPathCommander(shape).getTotalLength()).to.equal(shapes.length[i]);
499
+ expect(new SVGPathCommander(shape).getPointAtLength(50)).to.deep.equal(shapes.pointAt50[i]);
500
+ });
501
+ })
502
+ });
@@ -0,0 +1,17 @@
1
+ export default function getMarkup() {
2
+ const markup = `<div class="row row-lg">
3
+ <div class="col col-md-4 mx-auto">
4
+ <div class="text-center">
5
+ <svg id="test-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
6
+ <path d="M0 0L0 0" fill="rgba(255,0,255,0.75)"></path>
7
+ </svg>
8
+ </div>
9
+ </div>
10
+ </div>`;
11
+
12
+ const domParser = new DOMParser();
13
+ let tempDocument = domParser.parseFromString(markup, 'text/html');
14
+ const container = tempDocument.querySelector('div')!;
15
+
16
+ return container;
17
+ }