@octane-xplat/smooth-corners 0.14.0 → 0.15.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.
@@ -1,453 +0,0 @@
1
- import { a as st, i as ft, p as ot } from "../../../node_modules/.pnpm/@lisse_core@0.7.2/node_modules/@lisse/core/dist/chunk-BL6ZAFLB.js";
2
- //#region src/path.ts
3
- /** Shared smooth-corner path generation — pure TS, no DOM. Emits one
4
- * `PathCommand[]` (absolute coords, arcs already center-parameterized) that
5
- * web stringifies to `d`, iOS feeds `CGPathAdd*`, Android feeds
6
- * `android.graphics.Path`, and macOS feeds `NSBezierPath`.
7
- *
8
- * Lisse curves (`squircle`/`superellipse`/`clothoid`/`arc`) delegate to
9
- * `@lisse/core/path`; `continuous` is our own builder — the Rosenfeld
10
- * `UIBezierPath` constants reproducing Apple's fixed continuous corner
11
- * (~0.25 px from SwiftUI `.continuous`; Lisse issue #103 /
12
- * tools/apple-continuous-export). */
13
- function resolveOne(c) {
14
- const cfg = typeof c === "number" ? { radius: c } : c ?? { radius: 0 };
15
- return {
16
- radius: cfg.radius,
17
- curve: cfg.curve ?? "continuous",
18
- smoothing: cfg.smoothing ?? .65,
19
- exponent: cfg.exponent ?? 4,
20
- preserveSmoothing: cfg.preserveSmoothing ?? true
21
- };
22
- }
23
- function resolveOptions(options) {
24
- if (typeof options === "number") {
25
- const c = resolveOne(options);
26
- return {
27
- topLeft: c,
28
- topRight: c,
29
- bottomRight: c,
30
- bottomLeft: c
31
- };
32
- }
33
- if ("radius" in options) {
34
- const c = resolveOne(options);
35
- return {
36
- topLeft: c,
37
- topRight: c,
38
- bottomRight: c,
39
- bottomLeft: c
40
- };
41
- }
42
- const p = options;
43
- return {
44
- topLeft: resolveOne(p.topLeft),
45
- topRight: resolveOne(p.topRight),
46
- bottomRight: resolveOne(p.bottomRight),
47
- bottomLeft: resolveOne(p.bottomLeft)
48
- };
49
- }
50
- /** Shoulder extent along each adjacent edge, as a multiple of R. */
51
- var CONT_P = 1.528665;
52
- var CONT_CURVE = [
53
- [1.08849296, 0],
54
- [.86840694, 0],
55
- [.63149379, .07491139],
56
- [.37282383, .16905956],
57
- [.16905956, .37282383],
58
- [.07491139, .63149379],
59
- [0, .86840694],
60
- [0, 1.08849296],
61
- [0, 1.52866498]
62
- ];
63
- var CONT_CURVE_YX = CONT_CURVE.map(([x, y]) => [y, x]);
64
- /** Absolute `C` commands for one continuous corner. `p` returned separately —
65
- * the tangent distance from the vertex along the incoming edge. */
66
- function continuousCorner(w, h, radius, budget, orient) {
67
- const r = Math.min(radius, budget / CONT_P);
68
- const p = CONT_P * r;
69
- if (r <= 0) return {
70
- p: 0,
71
- cmds: []
72
- };
73
- let fx;
74
- let fy;
75
- let seq = CONT_CURVE;
76
- switch (orient) {
77
- case "TR":
78
- fx = (x, _y) => w - x * r;
79
- fy = (_x, y) => y * r;
80
- break;
81
- case "BR":
82
- fx = (x, _y) => w - x * r;
83
- fy = (_x, y) => h - y * r;
84
- seq = CONT_CURVE_YX;
85
- break;
86
- case "BL":
87
- fx = (x, _y) => x * r;
88
- fy = (_x, y) => h - y * r;
89
- break;
90
- case "TL":
91
- fx = (x, _y) => x * r;
92
- fy = (_x, y) => 0 + y * r;
93
- seq = CONT_CURVE_YX;
94
- }
95
- const cmds = [];
96
- for (let i = 0; i < seq.length; i += 3) {
97
- const [c1, c2, e] = [
98
- seq[i],
99
- seq[i + 1],
100
- seq[i + 2]
101
- ];
102
- cmds.push({
103
- c: "C",
104
- x1: fx(c1[0], c1[1]),
105
- y1: fy(c1[0], c1[1]),
106
- x2: fx(c2[0], c2[1]),
107
- y2: fy(c2[0], c2[1]),
108
- x: fx(e[0], e[1]),
109
- y: fy(e[0], e[1])
110
- });
111
- }
112
- return {
113
- p,
114
- cmds
115
- };
116
- }
117
- /** Parse a curve builder's per-orient relative segment starting at `pen`.
118
- * Returns absolute commands. Lisse segments only emit `c`/`a`/`l`. */
119
- function segmentCommands(seg, pen) {
120
- return parsePath(seg, pen.x, pen.y).filter((c) => c.c !== "M");
121
- }
122
- var TOKEN = /[a-zA-Z]|[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
123
- var ARITY = {
124
- M: 2,
125
- L: 2,
126
- H: 1,
127
- V: 1,
128
- C: 6,
129
- S: 4,
130
- Q: 4,
131
- T: 2,
132
- A: 7,
133
- Z: 0
134
- };
135
- /** Endpoint→center for a circular SVG arc (φ=0 — every Lisse emitter).
136
- * Non-circular or rotated arcs throw: unsupported is loud, not silent. */
137
- function arcToCenter(x0, y0, rx, ry, rot, largeArc, sweep, x1, y1) {
138
- if (x0 === x1 && y0 === y1) return null;
139
- if (rx <= 0 || ry <= 0) return null;
140
- if (rx !== ry || rot !== 0) throw new Error(`smooth-corners: only circular unrotated arcs are supported (got a ${rx} ${ry} ${rot})`);
141
- const dx = (x0 - x1) / 2;
142
- const dy = (y0 - y1) / 2;
143
- const d2 = dx * dx + dy * dy;
144
- let r = rx;
145
- const lambda = d2 / (r * r);
146
- if (lambda > 1) r *= Math.sqrt(lambda);
147
- const h = Math.sqrt(Math.max(0, (r * r - d2) / d2));
148
- const sign = largeArc !== sweep ? 1 : -1;
149
- const cx = (x0 + x1) / 2 + sign * h * dy;
150
- const cy = (y0 + y1) / 2 - sign * h * dx;
151
- const deg = (v) => v * 180 / Math.PI;
152
- const start = deg(Math.atan2(y0 - cy, x0 - cx));
153
- let sweepDeg = deg(Math.atan2(y1 - cy, x1 - cx)) - start;
154
- if (sweep && sweepDeg <= 0) sweepDeg += 360;
155
- if (!sweep && sweepDeg >= 0) sweepDeg -= 360;
156
- return {
157
- c: "A",
158
- cx,
159
- cy,
160
- r,
161
- startDeg: start,
162
- sweepDeg,
163
- x: x1,
164
- y: y1
165
- };
166
- }
167
- /** Parse an SVG `d` string (or relative corner segment) into absolute
168
- * `PathCommand[]`. Supports M L H V C S Q T A Z; `pen` seeds relative
169
- * commands when parsing a mid-path segment. */
170
- function parsePath(d, penX = 0, penY = 0) {
171
- const tokens = d.match(TOKEN) ?? [];
172
- const out = [];
173
- let i = 0;
174
- let cmd = "";
175
- let px = penX;
176
- let py = penY;
177
- let sx = penX;
178
- let sy = penY;
179
- const num = () => {
180
- const t = tokens[i++];
181
- const n = Number(t);
182
- if (!Number.isFinite(n)) throw new Error(`smooth-corners: bad path token '${t}' in '${d}'`);
183
- return n;
184
- };
185
- while (i < tokens.length) {
186
- const t = tokens[i];
187
- if (/[a-zA-Z]/.test(t)) {
188
- cmd = t;
189
- i++;
190
- if (cmd === "Z" || cmd === "z") {
191
- out.push({ c: "Z" });
192
- px = sx;
193
- py = sy;
194
- continue;
195
- }
196
- }
197
- if (!cmd) throw new Error(`smooth-corners: path data missing command: '${d}'`);
198
- const rel = cmd === cmd.toLowerCase();
199
- const upper = cmd.toUpperCase();
200
- if (ARITY[upper] === void 0) throw new Error(`smooth-corners: unsupported path command '${cmd}'`);
201
- if (upper === "S" || upper === "Q" || upper === "T") throw new Error(`smooth-corners: '${upper}' commands are not supported`);
202
- switch (upper) {
203
- case "M": {
204
- const x = num() + (rel ? px : 0);
205
- const y = num() + (rel ? py : 0);
206
- out.push({
207
- c: "M",
208
- x,
209
- y
210
- });
211
- px = sx = x;
212
- py = sy = y;
213
- cmd = rel ? "l" : "L";
214
- break;
215
- }
216
- case "L": {
217
- const x = num() + (rel ? px : 0);
218
- const y = num() + (rel ? py : 0);
219
- out.push({
220
- c: "L",
221
- x,
222
- y
223
- });
224
- px = x;
225
- py = y;
226
- break;
227
- }
228
- case "H": {
229
- const x = num() + (rel ? px : 0);
230
- out.push({
231
- c: "L",
232
- x,
233
- y: py
234
- });
235
- px = x;
236
- break;
237
- }
238
- case "V": {
239
- const y = num() + (rel ? py : 0);
240
- out.push({
241
- c: "L",
242
- x: px,
243
- y
244
- });
245
- py = y;
246
- break;
247
- }
248
- case "C": {
249
- const x1 = num() + (rel ? px : 0);
250
- const y1 = num() + (rel ? py : 0);
251
- const x2 = num() + (rel ? px : 0);
252
- const y2 = num() + (rel ? py : 0);
253
- const x = num() + (rel ? px : 0);
254
- const y = num() + (rel ? py : 0);
255
- out.push({
256
- c: "C",
257
- x1,
258
- y1,
259
- x2,
260
- y2,
261
- x,
262
- y
263
- });
264
- px = x;
265
- py = y;
266
- break;
267
- }
268
- case "A": {
269
- const rx = num();
270
- const ry = num();
271
- const rot = num();
272
- const fa = num();
273
- const fs = num();
274
- const x = num() + (rel ? px : 0);
275
- const y = num() + (rel ? py : 0);
276
- const arc = arcToCenter(px, py, rx, ry, rot, fa, fs, x, y);
277
- if (arc) out.push(arc);
278
- else out.push({
279
- c: "L",
280
- x,
281
- y
282
- });
283
- px = x;
284
- py = y;
285
- break;
286
- }
287
- }
288
- }
289
- return out;
290
- }
291
- function lisseCornerSeg(corner, radius, budget, orient, pen) {
292
- const out = ft(corner.curve)({
293
- cornerRadius: radius,
294
- smoothing: corner.smoothing,
295
- exponent: corner.exponent,
296
- preserveSmoothing: corner.preserveSmoothing,
297
- roundingAndSmoothingBudget: budget
298
- });
299
- return {
300
- p: out.p,
301
- cmds: segmentCommands(out.pathSegment(orient), pen)
302
- };
303
- }
304
- function cornerCmds(corner, normalized, orient, pen, w, h) {
305
- if (corner.curve === "continuous") return continuousCorner(w, h, normalized.radius, normalized.roundingAndSmoothingBudget, orient);
306
- return lisseCornerSeg(corner, normalized.radius, normalized.roundingAndSmoothingBudget, orient, pen);
307
- }
308
- var RECT = [
309
- {
310
- c: "M",
311
- x: 0,
312
- y: 0
313
- },
314
- {
315
- c: "L",
316
- x: 0,
317
- y: 0
318
- },
319
- {
320
- c: "L",
321
- x: 0,
322
- y: 0
323
- },
324
- {
325
- c: "L",
326
- x: 0,
327
- y: 0
328
- },
329
- { c: "Z" }
330
- ];
331
- /** Absolute command list for the smooth-cornered rect. When no corner uses
332
- * `continuous`, delegates to `@lisse/core` `generatePath` (keeping its
333
- * capsule/blend fast paths) and parses the result; otherwise stitches
334
- * per-corner segments itself (per-corner mixing with `continuous`). */
335
- function generateCommands(width, height, options) {
336
- if (width <= 0 || height <= 0) return RECT;
337
- const corners = resolveOptions(options);
338
- if (!(corners.topLeft.curve === "continuous" || corners.topRight.curve === "continuous" || corners.bottomRight.curve === "continuous" || corners.bottomLeft.curve === "continuous")) return parsePath(ot(width, height, lisseOptions(corners)));
339
- const normalized = st({
340
- topLeftCornerRadius: corners.topLeft.radius,
341
- topRightCornerRadius: corners.topRight.radius,
342
- bottomRightCornerRadius: corners.bottomRight.radius,
343
- bottomLeftCornerRadius: corners.bottomLeft.radius,
344
- width,
345
- height
346
- });
347
- const emit = (corner, n, orient, entryX, entryY) => cornerCmds(corner, n, orient, {
348
- x: entryX,
349
- y: entryY
350
- }, width, height);
351
- const pTL = previewP(corners.topLeft, normalized.topLeft, "TL", width, height);
352
- const pTR = previewP(corners.topRight, normalized.topRight, "TR", width, height);
353
- const pBR = previewP(corners.bottomRight, normalized.bottomRight, "BR", width, height);
354
- const pBL = previewP(corners.bottomLeft, normalized.bottomLeft, "BL", width, height);
355
- const out = [{
356
- c: "M",
357
- x: pTL,
358
- y: 0
359
- }];
360
- out.push({
361
- c: "L",
362
- x: width - pTR,
363
- y: 0
364
- });
365
- const tr = emit(corners.topRight, normalized.topRight, "TR", width - pTR, 0);
366
- out.push(...tr.cmds);
367
- out.push({
368
- c: "L",
369
- x: width,
370
- y: height - pBR
371
- });
372
- const br = emit(corners.bottomRight, normalized.bottomRight, "BR", width, height - pBR);
373
- out.push(...br.cmds);
374
- out.push({
375
- c: "L",
376
- x: pBL,
377
- y: height
378
- });
379
- const bl = emit(corners.bottomLeft, normalized.bottomLeft, "BL", pBL, height);
380
- out.push(...bl.cmds);
381
- out.push({
382
- c: "L",
383
- x: 0,
384
- y: pTL
385
- });
386
- const tl = emit(corners.topLeft, normalized.topLeft, "TL", 0, pTL);
387
- out.push(...tl.cmds);
388
- out.push({ c: "Z" });
389
- return out;
390
- }
391
- /** A corner's tangent distance without emitting commands (cheap for
392
- * continuous; re-runs the lisse builder for other curves — called once per
393
- * frame per corner, acceptable). */
394
- function previewP(corner, n, orient, w, h) {
395
- return cornerCmds(corner, n, orient, {
396
- x: 0,
397
- y: 0
398
- }, w, h).p;
399
- }
400
- function lisseOptions(corners) {
401
- const { topLeft: a, topRight: b, bottomRight: c, bottomLeft: d } = corners;
402
- if (a.radius === b.radius && a.radius === c.radius && a.radius === d.radius && a.curve === b.curve && a.curve === c.curve && a.curve === d.curve && a.smoothing === b.smoothing && a.smoothing === c.smoothing && a.smoothing === d.smoothing && a.exponent === b.exponent && a.exponent === c.exponent && a.exponent === d.exponent && a.preserveSmoothing === b.preserveSmoothing && a.preserveSmoothing === c.preserveSmoothing && a.preserveSmoothing === d.preserveSmoothing) return {
403
- radius: a.radius,
404
- curve: a.curve,
405
- smoothing: a.smoothing,
406
- exponent: a.exponent,
407
- preserveSmoothing: a.preserveSmoothing
408
- };
409
- const per = (c) => ({
410
- radius: c.radius,
411
- curve: c.curve,
412
- smoothing: c.smoothing,
413
- exponent: c.exponent,
414
- preserveSmoothing: c.preserveSmoothing
415
- });
416
- return {
417
- topLeft: per(a),
418
- topRight: per(b),
419
- bottomRight: per(c),
420
- bottomLeft: per(d)
421
- };
422
- }
423
- var f = (n) => Math.round(n * 1e4) / 1e4;
424
- /** Serialize commands back to an SVG `d` (web `clip-path`, debug/tests). */
425
- function commandsToD(cmds) {
426
- const parts = [];
427
- for (const c of cmds) switch (c.c) {
428
- case "M":
429
- parts.push(`M ${f(c.x)} ${f(c.y)}`);
430
- break;
431
- case "L":
432
- parts.push(`L ${f(c.x)} ${f(c.y)}`);
433
- break;
434
- case "C":
435
- parts.push(`C ${f(c.x1)} ${f(c.y1)} ${f(c.x2)} ${f(c.y2)} ${f(c.x)} ${f(c.y)}`);
436
- break;
437
- case "A":
438
- parts.push(`A ${f(c.r)} ${f(c.r)} 0 ${Math.abs(c.sweepDeg) > 180 ? 1 : 0} ${c.sweepDeg >= 0 ? 1 : 0} ${f(c.x)} ${f(c.y)}`);
439
- break;
440
- case "Z": parts.push("Z");
441
- }
442
- return parts.join(" ");
443
- }
444
- /** `d` for the smooth-cornered rect — Lisse's own string when no
445
- * `continuous` corner is involved (byte-identical to upstream, incl.
446
- * capsule/blend fast paths), our serialization otherwise. */
447
- function smoothPathD(width, height, options) {
448
- const corners = resolveOptions(options);
449
- if (!(corners.topLeft.curve === "continuous" || corners.topRight.curve === "continuous" || corners.bottomRight.curve === "continuous" || corners.bottomLeft.curve === "continuous") && width > 0 && height > 0) return ot(width, height, lisseOptions(corners));
450
- return commandsToD(generateCommands(width, height, options));
451
- }
452
- //#endregion
453
- export { commandsToD, generateCommands, parsePath, smoothPathD };