@flighthq/path-formats 0.1.0
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.
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/svgPathData.d.ts +34 -0
- package/dist/svgPathData.d.ts.map +1 -0
- package/dist/svgPathData.js +330 -0
- package/dist/svgPathData.js.map +1 -0
- package/package.json +38 -0
- package/src/svgPathData.test.ts +237 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Path } from '@flighthq/types';
|
|
2
|
+
/**
|
|
3
|
+
* Parses an SVG path `d` string into the end of an existing `path`, appending its contours via the
|
|
4
|
+
* `@flighthq/path` builders. Supports the full SVG path grammar: absolute (uppercase) and relative
|
|
5
|
+
* (lowercase) `M L H V C S Q T A Z`, implicit repeated commands, and the smooth-curve shorthands
|
|
6
|
+
* `S`/`T` (reflecting the previous cubic/quadratic control point). Returns `false` without further
|
|
7
|
+
* mutation guarantee on structurally malformed input (a leading command that is not moveto, an
|
|
8
|
+
* unknown command letter, or a missing/short coordinate run); returns `true` when the whole string
|
|
9
|
+
* parses. An empty or whitespace-only string is well-formed and appends nothing.
|
|
10
|
+
*
|
|
11
|
+
* Elliptic arcs (`A`/`a`) are appended through `appendPathArcTo`, which approximates them as cubic
|
|
12
|
+
* bezier segments — a subsequent `forEachPathSegment` walk (and `formatSvgPathData`) therefore sees
|
|
13
|
+
* cubics, not an arc verb. The geometry round-trips; the arc command does not.
|
|
14
|
+
*/
|
|
15
|
+
export declare function appendSvgPathData(path: Path, d: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Serializes a `Path` to an SVG path `d` string, emitting absolute commands. Each `PathSegment`
|
|
18
|
+
* maps to its SVG verb: `moveTo`→`M`, `lineTo`→`L`, `curveTo` (quadratic)→`Q`, `cubicCurveTo`→`C`,
|
|
19
|
+
* `close`→`Z`. Arcs are not a distinct segment kind — the path stores them as cubics — so no `A`
|
|
20
|
+
* command is produced.
|
|
21
|
+
*
|
|
22
|
+
* `options.precision`, when given, rounds every coordinate to that many decimal places and drops
|
|
23
|
+
* trailing zeros; the default emits full-precision numbers (also trailing-zero-free).
|
|
24
|
+
*/
|
|
25
|
+
export declare function formatSvgPathData(path: Readonly<Path>, options?: Readonly<{
|
|
26
|
+
precision?: number;
|
|
27
|
+
}>): string;
|
|
28
|
+
/**
|
|
29
|
+
* Parses an SVG path `d` string into a freshly allocated `Path`, or returns `null` on structurally
|
|
30
|
+
* malformed input (see `appendSvgPathData` for the malformed cases). An empty or whitespace-only
|
|
31
|
+
* string yields an empty path, not `null`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function parseSvgPathData(d: string): Path | null;
|
|
34
|
+
//# sourceMappingURL=svgPathData.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svgPathData.d.ts","sourceRoot":"","sources":["../src/svgPathData.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAE5C;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAqPhE;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,MAAM,CAwB1G;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAIvD"}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { appendPathArcTo, appendPathClose, appendPathCubicCurveTo, appendPathCurveTo, appendPathLineTo, appendPathMoveTo, createPath, forEachPathSegment, } from '@flighthq/path';
|
|
2
|
+
/**
|
|
3
|
+
* Parses an SVG path `d` string into the end of an existing `path`, appending its contours via the
|
|
4
|
+
* `@flighthq/path` builders. Supports the full SVG path grammar: absolute (uppercase) and relative
|
|
5
|
+
* (lowercase) `M L H V C S Q T A Z`, implicit repeated commands, and the smooth-curve shorthands
|
|
6
|
+
* `S`/`T` (reflecting the previous cubic/quadratic control point). Returns `false` without further
|
|
7
|
+
* mutation guarantee on structurally malformed input (a leading command that is not moveto, an
|
|
8
|
+
* unknown command letter, or a missing/short coordinate run); returns `true` when the whole string
|
|
9
|
+
* parses. An empty or whitespace-only string is well-formed and appends nothing.
|
|
10
|
+
*
|
|
11
|
+
* Elliptic arcs (`A`/`a`) are appended through `appendPathArcTo`, which approximates them as cubic
|
|
12
|
+
* bezier segments — a subsequent `forEachPathSegment` walk (and `formatSvgPathData`) therefore sees
|
|
13
|
+
* cubics, not an arc verb. The geometry round-trips; the arc command does not.
|
|
14
|
+
*/
|
|
15
|
+
export function appendSvgPathData(path, d) {
|
|
16
|
+
const length = d.length;
|
|
17
|
+
let pos = 0;
|
|
18
|
+
// Current point, subpath start (for Z), and the last emitted control points in absolute space.
|
|
19
|
+
// `lastControl2` feeds `S` reflection (valid only after C/S); `lastQuadControl` feeds `T`
|
|
20
|
+
// reflection (valid only after Q/T). `lastKind` records the previous group's canonical letter so
|
|
21
|
+
// a shorthand can decide whether reflection applies.
|
|
22
|
+
let currentX = 0;
|
|
23
|
+
let currentY = 0;
|
|
24
|
+
let startX = 0;
|
|
25
|
+
let startY = 0;
|
|
26
|
+
let lastControl2X = 0;
|
|
27
|
+
let lastControl2Y = 0;
|
|
28
|
+
let lastQuadControlX = 0;
|
|
29
|
+
let lastQuadControlY = 0;
|
|
30
|
+
let lastKind = '';
|
|
31
|
+
function skipSeparators() {
|
|
32
|
+
while (pos < length) {
|
|
33
|
+
const c = d.charCodeAt(pos);
|
|
34
|
+
// space, tab, LF, CR, FF, or comma
|
|
35
|
+
if (c === 32 || c === 9 || c === 10 || c === 13 || c === 12 || c === 44)
|
|
36
|
+
pos++;
|
|
37
|
+
else
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function readNumber() {
|
|
42
|
+
skipSeparators();
|
|
43
|
+
const start = pos;
|
|
44
|
+
if (pos < length && (d[pos] === '+' || d[pos] === '-'))
|
|
45
|
+
pos++;
|
|
46
|
+
let sawDigit = false;
|
|
47
|
+
while (pos < length && d[pos] >= '0' && d[pos] <= '9') {
|
|
48
|
+
pos++;
|
|
49
|
+
sawDigit = true;
|
|
50
|
+
}
|
|
51
|
+
if (pos < length && d[pos] === '.') {
|
|
52
|
+
pos++;
|
|
53
|
+
while (pos < length && d[pos] >= '0' && d[pos] <= '9') {
|
|
54
|
+
pos++;
|
|
55
|
+
sawDigit = true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!sawDigit) {
|
|
59
|
+
pos = start;
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
if (pos < length && (d[pos] === 'e' || d[pos] === 'E')) {
|
|
63
|
+
const expStart = pos;
|
|
64
|
+
pos++;
|
|
65
|
+
if (pos < length && (d[pos] === '+' || d[pos] === '-'))
|
|
66
|
+
pos++;
|
|
67
|
+
let expDigit = false;
|
|
68
|
+
while (pos < length && d[pos] >= '0' && d[pos] <= '9') {
|
|
69
|
+
pos++;
|
|
70
|
+
expDigit = true;
|
|
71
|
+
}
|
|
72
|
+
if (!expDigit)
|
|
73
|
+
pos = expStart;
|
|
74
|
+
}
|
|
75
|
+
return Number.parseFloat(d.slice(start, pos));
|
|
76
|
+
}
|
|
77
|
+
// An arc flag is a single '0' or '1' character, which may be packed against neighbours with no
|
|
78
|
+
// separator (`0110` = four flags), so it is read one char at a time rather than as a number.
|
|
79
|
+
function readFlag() {
|
|
80
|
+
skipSeparators();
|
|
81
|
+
if (pos < length && d[pos] === '0') {
|
|
82
|
+
pos++;
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
if (pos < length && d[pos] === '1') {
|
|
86
|
+
pos++;
|
|
87
|
+
return 1;
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
while (true) {
|
|
92
|
+
skipSeparators();
|
|
93
|
+
if (pos >= length)
|
|
94
|
+
break;
|
|
95
|
+
const commandLetter = d[pos];
|
|
96
|
+
if (!isSvgCommandLetter(commandLetter))
|
|
97
|
+
return false;
|
|
98
|
+
pos++;
|
|
99
|
+
// Path data must open with a moveto.
|
|
100
|
+
if (lastKind === '' && commandLetter !== 'M' && commandLetter !== 'm')
|
|
101
|
+
return false;
|
|
102
|
+
if (commandLetter === 'Z' || commandLetter === 'z') {
|
|
103
|
+
appendPathClose(path);
|
|
104
|
+
currentX = startX;
|
|
105
|
+
currentY = startY;
|
|
106
|
+
lastKind = 'Z';
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
// Execute one or more operand groups. After the first `M`/`m` group, implicit repeats are
|
|
110
|
+
// lineto groups (SVG spec), so the active letter shifts from M→L (or m→l).
|
|
111
|
+
let active = commandLetter;
|
|
112
|
+
let first = true;
|
|
113
|
+
while (true) {
|
|
114
|
+
if (!first) {
|
|
115
|
+
skipSeparators();
|
|
116
|
+
if (pos >= length)
|
|
117
|
+
break;
|
|
118
|
+
if (isSvgCommandLetter(d[pos]))
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
const relative = active >= 'a';
|
|
122
|
+
const upper = relative ? active.toUpperCase() : active;
|
|
123
|
+
if (upper === 'M') {
|
|
124
|
+
const nx = readNumber();
|
|
125
|
+
const ny = readNumber();
|
|
126
|
+
if (nx === null || ny === null)
|
|
127
|
+
return false;
|
|
128
|
+
currentX = relative ? currentX + nx : nx;
|
|
129
|
+
currentY = relative ? currentY + ny : ny;
|
|
130
|
+
startX = currentX;
|
|
131
|
+
startY = currentY;
|
|
132
|
+
appendPathMoveTo(path, currentX, currentY);
|
|
133
|
+
lastKind = 'M';
|
|
134
|
+
}
|
|
135
|
+
else if (upper === 'L') {
|
|
136
|
+
const nx = readNumber();
|
|
137
|
+
const ny = readNumber();
|
|
138
|
+
if (nx === null || ny === null)
|
|
139
|
+
return false;
|
|
140
|
+
currentX = relative ? currentX + nx : nx;
|
|
141
|
+
currentY = relative ? currentY + ny : ny;
|
|
142
|
+
appendPathLineTo(path, currentX, currentY);
|
|
143
|
+
lastKind = 'L';
|
|
144
|
+
}
|
|
145
|
+
else if (upper === 'H') {
|
|
146
|
+
const nx = readNumber();
|
|
147
|
+
if (nx === null)
|
|
148
|
+
return false;
|
|
149
|
+
currentX = relative ? currentX + nx : nx;
|
|
150
|
+
appendPathLineTo(path, currentX, currentY);
|
|
151
|
+
lastKind = 'L';
|
|
152
|
+
}
|
|
153
|
+
else if (upper === 'V') {
|
|
154
|
+
const ny = readNumber();
|
|
155
|
+
if (ny === null)
|
|
156
|
+
return false;
|
|
157
|
+
currentY = relative ? currentY + ny : ny;
|
|
158
|
+
appendPathLineTo(path, currentX, currentY);
|
|
159
|
+
lastKind = 'L';
|
|
160
|
+
}
|
|
161
|
+
else if (upper === 'C') {
|
|
162
|
+
const x1 = readNumber();
|
|
163
|
+
const y1 = readNumber();
|
|
164
|
+
const x2 = readNumber();
|
|
165
|
+
const y2 = readNumber();
|
|
166
|
+
const x = readNumber();
|
|
167
|
+
const y = readNumber();
|
|
168
|
+
if (x1 === null || y1 === null || x2 === null || y2 === null || x === null || y === null)
|
|
169
|
+
return false;
|
|
170
|
+
const c1x = relative ? currentX + x1 : x1;
|
|
171
|
+
const c1y = relative ? currentY + y1 : y1;
|
|
172
|
+
const c2x = relative ? currentX + x2 : x2;
|
|
173
|
+
const c2y = relative ? currentY + y2 : y2;
|
|
174
|
+
const ax = relative ? currentX + x : x;
|
|
175
|
+
const ay = relative ? currentY + y : y;
|
|
176
|
+
appendPathCubicCurveTo(path, c1x, c1y, c2x, c2y, ax, ay);
|
|
177
|
+
lastControl2X = c2x;
|
|
178
|
+
lastControl2Y = c2y;
|
|
179
|
+
currentX = ax;
|
|
180
|
+
currentY = ay;
|
|
181
|
+
lastKind = 'C';
|
|
182
|
+
}
|
|
183
|
+
else if (upper === 'S') {
|
|
184
|
+
const x2 = readNumber();
|
|
185
|
+
const y2 = readNumber();
|
|
186
|
+
const x = readNumber();
|
|
187
|
+
const y = readNumber();
|
|
188
|
+
if (x2 === null || y2 === null || x === null || y === null)
|
|
189
|
+
return false;
|
|
190
|
+
const reflect = lastKind === 'C' || lastKind === 'S';
|
|
191
|
+
const c1x = reflect ? 2 * currentX - lastControl2X : currentX;
|
|
192
|
+
const c1y = reflect ? 2 * currentY - lastControl2Y : currentY;
|
|
193
|
+
const c2x = relative ? currentX + x2 : x2;
|
|
194
|
+
const c2y = relative ? currentY + y2 : y2;
|
|
195
|
+
const ax = relative ? currentX + x : x;
|
|
196
|
+
const ay = relative ? currentY + y : y;
|
|
197
|
+
appendPathCubicCurveTo(path, c1x, c1y, c2x, c2y, ax, ay);
|
|
198
|
+
lastControl2X = c2x;
|
|
199
|
+
lastControl2Y = c2y;
|
|
200
|
+
currentX = ax;
|
|
201
|
+
currentY = ay;
|
|
202
|
+
lastKind = 'S';
|
|
203
|
+
}
|
|
204
|
+
else if (upper === 'Q') {
|
|
205
|
+
const x1 = readNumber();
|
|
206
|
+
const y1 = readNumber();
|
|
207
|
+
const x = readNumber();
|
|
208
|
+
const y = readNumber();
|
|
209
|
+
if (x1 === null || y1 === null || x === null || y === null)
|
|
210
|
+
return false;
|
|
211
|
+
const cx = relative ? currentX + x1 : x1;
|
|
212
|
+
const cy = relative ? currentY + y1 : y1;
|
|
213
|
+
const ax = relative ? currentX + x : x;
|
|
214
|
+
const ay = relative ? currentY + y : y;
|
|
215
|
+
appendPathCurveTo(path, cx, cy, ax, ay);
|
|
216
|
+
lastQuadControlX = cx;
|
|
217
|
+
lastQuadControlY = cy;
|
|
218
|
+
currentX = ax;
|
|
219
|
+
currentY = ay;
|
|
220
|
+
lastKind = 'Q';
|
|
221
|
+
}
|
|
222
|
+
else if (upper === 'T') {
|
|
223
|
+
const x = readNumber();
|
|
224
|
+
const y = readNumber();
|
|
225
|
+
if (x === null || y === null)
|
|
226
|
+
return false;
|
|
227
|
+
const reflect = lastKind === 'Q' || lastKind === 'T';
|
|
228
|
+
const cx = reflect ? 2 * currentX - lastQuadControlX : currentX;
|
|
229
|
+
const cy = reflect ? 2 * currentY - lastQuadControlY : currentY;
|
|
230
|
+
const ax = relative ? currentX + x : x;
|
|
231
|
+
const ay = relative ? currentY + y : y;
|
|
232
|
+
appendPathCurveTo(path, cx, cy, ax, ay);
|
|
233
|
+
lastQuadControlX = cx;
|
|
234
|
+
lastQuadControlY = cy;
|
|
235
|
+
currentX = ax;
|
|
236
|
+
currentY = ay;
|
|
237
|
+
lastKind = 'T';
|
|
238
|
+
}
|
|
239
|
+
else if (upper === 'A') {
|
|
240
|
+
const rx = readNumber();
|
|
241
|
+
const ry = readNumber();
|
|
242
|
+
const rotationDegrees = readNumber();
|
|
243
|
+
const largeArc = readFlag();
|
|
244
|
+
const sweep = readFlag();
|
|
245
|
+
const x = readNumber();
|
|
246
|
+
const y = readNumber();
|
|
247
|
+
if (rx === null ||
|
|
248
|
+
ry === null ||
|
|
249
|
+
rotationDegrees === null ||
|
|
250
|
+
largeArc === null ||
|
|
251
|
+
sweep === null ||
|
|
252
|
+
x === null ||
|
|
253
|
+
y === null) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
const ax = relative ? currentX + x : x;
|
|
257
|
+
const ay = relative ? currentY + y : y;
|
|
258
|
+
appendPathArcTo(path, rx, ry, (rotationDegrees * Math.PI) / 180, largeArc === 1, sweep === 1, ax, ay);
|
|
259
|
+
currentX = ax;
|
|
260
|
+
currentY = ay;
|
|
261
|
+
lastKind = 'A';
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
first = false;
|
|
267
|
+
if (active === 'M')
|
|
268
|
+
active = 'L';
|
|
269
|
+
else if (active === 'm')
|
|
270
|
+
active = 'l';
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Serializes a `Path` to an SVG path `d` string, emitting absolute commands. Each `PathSegment`
|
|
277
|
+
* maps to its SVG verb: `moveTo`→`M`, `lineTo`→`L`, `curveTo` (quadratic)→`Q`, `cubicCurveTo`→`C`,
|
|
278
|
+
* `close`→`Z`. Arcs are not a distinct segment kind — the path stores them as cubics — so no `A`
|
|
279
|
+
* command is produced.
|
|
280
|
+
*
|
|
281
|
+
* `options.precision`, when given, rounds every coordinate to that many decimal places and drops
|
|
282
|
+
* trailing zeros; the default emits full-precision numbers (also trailing-zero-free).
|
|
283
|
+
*/
|
|
284
|
+
export function formatSvgPathData(path, options) {
|
|
285
|
+
const precision = options?.precision;
|
|
286
|
+
const parts = [];
|
|
287
|
+
forEachPathSegment(path, (segment) => {
|
|
288
|
+
if (segment.kind === 'moveTo') {
|
|
289
|
+
parts.push(`M${formatSvgNumber(segment.x, precision)} ${formatSvgNumber(segment.y, precision)}`);
|
|
290
|
+
}
|
|
291
|
+
else if (segment.kind === 'lineTo') {
|
|
292
|
+
parts.push(`L${formatSvgNumber(segment.x, precision)} ${formatSvgNumber(segment.y, precision)}`);
|
|
293
|
+
}
|
|
294
|
+
else if (segment.kind === 'curveTo') {
|
|
295
|
+
parts.push(`Q${formatSvgNumber(segment.controlX, precision)} ${formatSvgNumber(segment.controlY, precision)} ` +
|
|
296
|
+
`${formatSvgNumber(segment.x, precision)} ${formatSvgNumber(segment.y, precision)}`);
|
|
297
|
+
}
|
|
298
|
+
else if (segment.kind === 'cubicCurveTo') {
|
|
299
|
+
parts.push(`C${formatSvgNumber(segment.control1X, precision)} ${formatSvgNumber(segment.control1Y, precision)} ` +
|
|
300
|
+
`${formatSvgNumber(segment.control2X, precision)} ${formatSvgNumber(segment.control2Y, precision)} ` +
|
|
301
|
+
`${formatSvgNumber(segment.x, precision)} ${formatSvgNumber(segment.y, precision)}`);
|
|
302
|
+
}
|
|
303
|
+
else if (segment.kind === 'close') {
|
|
304
|
+
parts.push('Z');
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
return parts.join('');
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Parses an SVG path `d` string into a freshly allocated `Path`, or returns `null` on structurally
|
|
311
|
+
* malformed input (see `appendSvgPathData` for the malformed cases). An empty or whitespace-only
|
|
312
|
+
* string yields an empty path, not `null`.
|
|
313
|
+
*/
|
|
314
|
+
export function parseSvgPathData(d) {
|
|
315
|
+
const path = createPath();
|
|
316
|
+
if (!appendSvgPathData(path, d))
|
|
317
|
+
return null;
|
|
318
|
+
return path;
|
|
319
|
+
}
|
|
320
|
+
function formatSvgNumber(value, precision) {
|
|
321
|
+
if (precision === undefined)
|
|
322
|
+
return String(value);
|
|
323
|
+
const factor = 10 ** precision;
|
|
324
|
+
// String() of the rounded number drops trailing zeros and normalizes -0 to "0".
|
|
325
|
+
return String(Math.round(value * factor) / factor);
|
|
326
|
+
}
|
|
327
|
+
function isSvgCommandLetter(c) {
|
|
328
|
+
return 'MmLlHhVvCcSsQqTtAaZz'.indexOf(c) !== -1;
|
|
329
|
+
}
|
|
330
|
+
//# sourceMappingURL=svgPathData.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svgPathData.js","sourceRoot":"","sources":["../src/svgPathData.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flighthq/path-formats",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src/**/*.test.ts",
|
|
16
|
+
"!dist/**/*.test.js",
|
|
17
|
+
"!dist/**/*.test.d.ts",
|
|
18
|
+
"!dist/**/*.test.js.map",
|
|
19
|
+
"!dist/**/*.test.d.ts.map"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -b",
|
|
23
|
+
"clean": "tsc -b --clean",
|
|
24
|
+
"test": "vitest run --config vitest.config.ts",
|
|
25
|
+
"test:watch": "vitest --watch --config vitest.config.ts",
|
|
26
|
+
"prepack": "npm run clean && npm run clean:dist && npm run build",
|
|
27
|
+
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@flighthq/path": "0.1.0",
|
|
31
|
+
"@flighthq/types": "0.1.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"typescript": "^5.3.0"
|
|
35
|
+
},
|
|
36
|
+
"description": "Tree-shakable path serialization codecs for @flighthq/path (SVG path data)",
|
|
37
|
+
"sideEffects": false
|
|
38
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { createPath, forEachPathSegment } from '@flighthq/path';
|
|
2
|
+
import type { Path, PathSegment } from '@flighthq/types';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { appendSvgPathData, formatSvgPathData, parseSvgPathData } from './svgPathData';
|
|
6
|
+
|
|
7
|
+
function collectSegments(path: Readonly<Path>): PathSegment[] {
|
|
8
|
+
const segments: PathSegment[] = [];
|
|
9
|
+
forEachPathSegment(path, (segment) => segments.push(segment));
|
|
10
|
+
return segments;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe('appendSvgPathData', () => {
|
|
14
|
+
it('appends into an existing path without clearing it', () => {
|
|
15
|
+
const path = createPath();
|
|
16
|
+
appendSvgPathData(path, 'M0 0 L10 0');
|
|
17
|
+
const ok = appendSvgPathData(path, 'M20 20 L30 20');
|
|
18
|
+
expect(ok).toBe(true);
|
|
19
|
+
const segments = collectSegments(path);
|
|
20
|
+
expect(segments.map((s) => s.kind)).toEqual(['moveTo', 'lineTo', 'moveTo', 'lineTo']);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('returns false on malformed input', () => {
|
|
24
|
+
const path = createPath();
|
|
25
|
+
expect(appendSvgPathData(path, 'L10 10')).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('formatSvgPathData', () => {
|
|
30
|
+
it('emits absolute M/L/C/Q/Z commands per segment kind', () => {
|
|
31
|
+
const path = createPath();
|
|
32
|
+
appendSvgPathData(path, 'M0 0 L10 0 Q15 5 20 0 C22 2 24 2 26 0 Z');
|
|
33
|
+
expect(formatSvgPathData(path)).toBe('M0 0L10 0Q15 5 20 0C22 2 24 2 26 0Z');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('rounds coordinates and trims trailing zeros with a precision option', () => {
|
|
37
|
+
const path = createPath();
|
|
38
|
+
appendSvgPathData(path, 'M0.123456 0.5 L10.987654 20');
|
|
39
|
+
expect(formatSvgPathData(path, { precision: 2 })).toBe('M0.12 0.5L10.99 20');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('emits full precision by default', () => {
|
|
43
|
+
const path = createPath();
|
|
44
|
+
appendSvgPathData(path, 'M0.5 0.25 L1.5 2.75');
|
|
45
|
+
expect(formatSvgPathData(path)).toBe('M0.5 0.25L1.5 2.75');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('emits no A command — arcs are stored as cubics', () => {
|
|
49
|
+
const path = createPath();
|
|
50
|
+
appendSvgPathData(path, 'M0 0 A5 5 0 0 1 10 0');
|
|
51
|
+
const d = formatSvgPathData(path);
|
|
52
|
+
expect(d).not.toContain('A');
|
|
53
|
+
expect(d.startsWith('M0 0C')).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('parseSvgPathData', () => {
|
|
58
|
+
it('parses absolute moveto and lineto', () => {
|
|
59
|
+
const path = parseSvgPathData('M0 0 L10 10');
|
|
60
|
+
expect(path).not.toBeNull();
|
|
61
|
+
expect(collectSegments(path as Path)).toEqual([
|
|
62
|
+
{ kind: 'moveTo', x: 0, y: 0 },
|
|
63
|
+
{ kind: 'lineTo', x: 10, y: 10 },
|
|
64
|
+
]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('treats repeated moveto operands as implicit lineto', () => {
|
|
68
|
+
const path = parseSvgPathData('M0 0 1 1 2 2');
|
|
69
|
+
expect(collectSegments(path as Path)).toEqual([
|
|
70
|
+
{ kind: 'moveTo', x: 0, y: 0 },
|
|
71
|
+
{ kind: 'lineTo', x: 1, y: 1 },
|
|
72
|
+
{ kind: 'lineTo', x: 2, y: 2 },
|
|
73
|
+
]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('resolves relative commands against the current point', () => {
|
|
77
|
+
const path = parseSvgPathData('m10 10 l5 5 l-5 0');
|
|
78
|
+
expect(collectSegments(path as Path)).toEqual([
|
|
79
|
+
{ kind: 'moveTo', x: 10, y: 10 },
|
|
80
|
+
{ kind: 'lineTo', x: 15, y: 15 },
|
|
81
|
+
{ kind: 'lineTo', x: 10, y: 15 },
|
|
82
|
+
]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('maps horizontal and vertical lineto (absolute and relative)', () => {
|
|
86
|
+
const path = parseSvgPathData('M0 0 H10 V20 h5 v-5');
|
|
87
|
+
expect(collectSegments(path as Path)).toEqual([
|
|
88
|
+
{ kind: 'moveTo', x: 0, y: 0 },
|
|
89
|
+
{ kind: 'lineTo', x: 10, y: 0 },
|
|
90
|
+
{ kind: 'lineTo', x: 10, y: 20 },
|
|
91
|
+
{ kind: 'lineTo', x: 15, y: 20 },
|
|
92
|
+
{ kind: 'lineTo', x: 15, y: 15 },
|
|
93
|
+
]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('parses cubic curves', () => {
|
|
97
|
+
const path = parseSvgPathData('M0 0 C1 2 3 4 5 6');
|
|
98
|
+
expect(collectSegments(path as Path)).toEqual([
|
|
99
|
+
{ kind: 'moveTo', x: 0, y: 0 },
|
|
100
|
+
{ kind: 'cubicCurveTo', control1X: 1, control1Y: 2, control2X: 3, control2Y: 4, x: 5, y: 6 },
|
|
101
|
+
]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('reflects the previous cubic control for the S shorthand', () => {
|
|
105
|
+
const path = parseSvgPathData('M0 0 C1 2 3 4 5 6 S3 3 4 4');
|
|
106
|
+
const segments = collectSegments(path as Path);
|
|
107
|
+
expect(segments[2]).toEqual({
|
|
108
|
+
kind: 'cubicCurveTo',
|
|
109
|
+
control1X: 7,
|
|
110
|
+
control1Y: 8,
|
|
111
|
+
control2X: 3,
|
|
112
|
+
control2Y: 3,
|
|
113
|
+
x: 4,
|
|
114
|
+
y: 4,
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('uses the current point as the S control when the previous command was not a cubic', () => {
|
|
119
|
+
const path = parseSvgPathData('M0 0 L2 2 S3 3 4 4');
|
|
120
|
+
const segments = collectSegments(path as Path);
|
|
121
|
+
expect(segments[2]).toEqual({
|
|
122
|
+
kind: 'cubicCurveTo',
|
|
123
|
+
control1X: 2,
|
|
124
|
+
control1Y: 2,
|
|
125
|
+
control2X: 3,
|
|
126
|
+
control2Y: 3,
|
|
127
|
+
x: 4,
|
|
128
|
+
y: 4,
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('parses quadratic curves', () => {
|
|
133
|
+
const path = parseSvgPathData('M0 0 Q1 1 2 2');
|
|
134
|
+
expect(collectSegments(path as Path)).toEqual([
|
|
135
|
+
{ kind: 'moveTo', x: 0, y: 0 },
|
|
136
|
+
{ kind: 'curveTo', controlX: 1, controlY: 1, x: 2, y: 2 },
|
|
137
|
+
]);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('reflects the previous quadratic control for the T shorthand', () => {
|
|
141
|
+
const path = parseSvgPathData('M0 0 Q1 1 2 2 T4 4');
|
|
142
|
+
const segments = collectSegments(path as Path);
|
|
143
|
+
expect(segments[2]).toEqual({ kind: 'curveTo', controlX: 3, controlY: 3, x: 4, y: 4 });
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('appends an arc as cubic segments ending at the arc endpoint', () => {
|
|
147
|
+
const path = parseSvgPathData('M0 0 A5 5 0 0 1 10 0');
|
|
148
|
+
expect(path).not.toBeNull();
|
|
149
|
+
const segments = collectSegments(path as Path);
|
|
150
|
+
expect(segments[0].kind).toBe('moveTo');
|
|
151
|
+
expect(segments[1].kind).toBe('cubicCurveTo');
|
|
152
|
+
const last = segments[segments.length - 1] as Extract<PathSegment, { kind: 'cubicCurveTo' }>;
|
|
153
|
+
expect(last.x).toBeCloseTo(10, 6);
|
|
154
|
+
expect(last.y).toBeCloseTo(0, 6);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('closes contours and returns the current point to the subpath start', () => {
|
|
158
|
+
const path = parseSvgPathData('M0 0 L10 0 L10 10 Z L5 5');
|
|
159
|
+
const segments = collectSegments(path as Path);
|
|
160
|
+
expect(segments.map((s) => s.kind)).toEqual(['moveTo', 'lineTo', 'lineTo', 'close', 'lineTo']);
|
|
161
|
+
// The lineto after Z starts from the subpath origin (0,0) and goes to (5,5).
|
|
162
|
+
expect(segments[4]).toEqual({ kind: 'lineTo', x: 5, y: 5 });
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('tokenizes numbers with no space before a negative sign', () => {
|
|
166
|
+
const path = parseSvgPathData('M0 0L10-5');
|
|
167
|
+
expect(collectSegments(path as Path)).toEqual([
|
|
168
|
+
{ kind: 'moveTo', x: 0, y: 0 },
|
|
169
|
+
{ kind: 'lineTo', x: 10, y: -5 },
|
|
170
|
+
]);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('tokenizes scientific notation and comma separators', () => {
|
|
174
|
+
const path = parseSvgPathData('M1e1,1e1 L2E1,-1.5e1');
|
|
175
|
+
expect(collectSegments(path as Path)).toEqual([
|
|
176
|
+
{ kind: 'moveTo', x: 10, y: 10 },
|
|
177
|
+
{ kind: 'lineTo', x: 20, y: -15 },
|
|
178
|
+
]);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('parses packed arc flags with no separators', () => {
|
|
182
|
+
const path = parseSvgPathData('M0 0A5 5 0 0110 0');
|
|
183
|
+
expect(path).not.toBeNull();
|
|
184
|
+
const segments = collectSegments(path as Path);
|
|
185
|
+
const last = segments[segments.length - 1] as Extract<PathSegment, { kind: 'cubicCurveTo' }>;
|
|
186
|
+
expect(last.x).toBeCloseTo(10, 6);
|
|
187
|
+
expect(last.y).toBeCloseTo(0, 6);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('returns an empty path for empty or whitespace-only input', () => {
|
|
191
|
+
expect(collectSegments(parseSvgPathData('') as Path)).toEqual([]);
|
|
192
|
+
expect(collectSegments(parseSvgPathData(' \n ') as Path)).toEqual([]);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('returns null when the path does not begin with a moveto', () => {
|
|
196
|
+
expect(parseSvgPathData('L10 10')).toBeNull();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('returns null on an unknown command letter', () => {
|
|
200
|
+
expect(parseSvgPathData('M0 0 X10 10')).toBeNull();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('returns null on a short coordinate run', () => {
|
|
204
|
+
expect(parseSvgPathData('M0 0 L10')).toBeNull();
|
|
205
|
+
expect(parseSvgPathData('M')).toBeNull();
|
|
206
|
+
expect(parseSvgPathData('M0 0 C1 2 3 4 5')).toBeNull();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('round-trips representative path data through format and back', () => {
|
|
210
|
+
const cases = [
|
|
211
|
+
'M0 0 L10 0 L10 10 Z',
|
|
212
|
+
'M0 0 C1 2 3 4 5 6 S7 8 9 10',
|
|
213
|
+
'M0 0 Q1 1 2 2 T4 4 T6 0',
|
|
214
|
+
'm10 10 h20 v20 h-20 z',
|
|
215
|
+
'M0 0 A5 5 0 0 1 10 0 A5 5 0 0 1 20 0',
|
|
216
|
+
];
|
|
217
|
+
for (const d of cases) {
|
|
218
|
+
const first = parseSvgPathData(d);
|
|
219
|
+
expect(first).not.toBeNull();
|
|
220
|
+
const reserialized = formatSvgPathData(first as Path);
|
|
221
|
+
const second = parseSvgPathData(reserialized);
|
|
222
|
+
expect(second).not.toBeNull();
|
|
223
|
+
const a = collectSegments(first as Path);
|
|
224
|
+
const b = collectSegments(second as Path);
|
|
225
|
+
expect(b.length).toBe(a.length);
|
|
226
|
+
for (let i = 0; i < a.length; i++) {
|
|
227
|
+
expect(b[i].kind).toBe(a[i].kind);
|
|
228
|
+
const av = a[i] as unknown as Record<string, number>;
|
|
229
|
+
const bv = b[i] as unknown as Record<string, number>;
|
|
230
|
+
for (const key of Object.keys(av)) {
|
|
231
|
+
if (key === 'kind') continue;
|
|
232
|
+
expect(bv[key]).toBeCloseTo(av[key], 6);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
});
|