@glissade/scene 0.19.0-pre.0 → 0.19.0-pre.2
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/describe.js +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/layout.d.ts +2 -1
- package/dist/layoutEngine.d.ts +1 -668
- package/dist/nodes.d.ts +722 -0
- package/dist/nodes.js +76 -2
- package/dist/type.d.ts +63 -0
- package/dist/type.js +120 -0
- package/package.json +6 -2
package/dist/nodes.d.ts
ADDED
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
import { C as Mat2x3, a as FilterSpec, d as Paint, f as PathSeg, g as ShaderRef, r as DisplayListBuilder, s as FontSpec, t as BlendMode } from "./displayList.js";
|
|
2
|
+
import { BindableSignal, PathValue, ReadonlySignal, Rng, Track, ValueTypeId, Vec2, Vec2Signal } from "@glissade/core";
|
|
3
|
+
|
|
4
|
+
//#region src/text.d.ts
|
|
5
|
+
|
|
6
|
+
interface TextMetricsLite {
|
|
7
|
+
width: number;
|
|
8
|
+
ascent: number;
|
|
9
|
+
descent: number;
|
|
10
|
+
}
|
|
11
|
+
interface TextMeasurer {
|
|
12
|
+
measureText(text: string, font: FontSpec): TextMetricsLite;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* §3.6 measurement quantum (px). Scene-owned pre-measure quantizes every
|
|
16
|
+
* layout-feeding advance to this grid ONCE, then hands Yoga frozen integers —
|
|
17
|
+
* so sub-pixel measureText drift between Skia/HarfBuzz versions cannot move a
|
|
18
|
+
* whole layout. The single source of truth for the grid; `quantize` rounds to
|
|
19
|
+
* it. (Yoga's `setMeasureFunc` was considered and rejected — see DESIGN.md §3.6.)
|
|
20
|
+
*/
|
|
21
|
+
declare const MEASURE_QUANTUM_PX = 0.5;
|
|
22
|
+
/** §3.6 measurement quantum — round to the MEASURE_QUANTUM_PX grid. */
|
|
23
|
+
declare function quantize(v: number): number;
|
|
24
|
+
/**
|
|
25
|
+
* Process-wide fallback measurer for FACTORY-TIME measurement — component
|
|
26
|
+
* factories run before any scene exists, so Text pulls (measuredSize,
|
|
27
|
+
* lineBoxes, wordBoxes) and createScene fall back here before the estimator.
|
|
28
|
+
* Node consumers: `setDefaultMeasurer(createMeasurer({ fonts }))` from
|
|
29
|
+
* @glissade/backend-skia gives factory code the rasterizer's real metrics.
|
|
30
|
+
* Scene-injected measurers (mount/CLI/golden harness) always win.
|
|
31
|
+
*/
|
|
32
|
+
declare function setDefaultMeasurer(m: TextMeasurer | null): void;
|
|
33
|
+
/** The default-or-estimating chain end; internal fallback for measurer pulls. */
|
|
34
|
+
|
|
35
|
+
declare const estimatingMeasurer: TextMeasurer;
|
|
36
|
+
/**
|
|
37
|
+
* The draw-path word segmentation (Intl.Segmenter boundaries, punctuation
|
|
38
|
+
* glued to its predecessor) — exported so Text.wordBoxes() boxes EXACTLY the
|
|
39
|
+
* units the breaker flows.
|
|
40
|
+
*/
|
|
41
|
+
declare function segmentWords(text: string): string[];
|
|
42
|
+
/**
|
|
43
|
+
* Split text into graphemes (user-perceived characters). Exported so Text.draw
|
|
44
|
+
* (reveal masking), Text.graphemes() (authoring), and revealSchedule() (the SFX
|
|
45
|
+
* keystroke contract) all count the SAME units.
|
|
46
|
+
*/
|
|
47
|
+
declare function segmentGraphemes(text: string): string[];
|
|
48
|
+
/**
|
|
49
|
+
* Greedy line breaking: explicit '\n' always breaks; otherwise word segments
|
|
50
|
+
* flow until maxWidth is exceeded (Intl.Segmenter boundaries, so CJK wraps
|
|
51
|
+
* without spaces). A segment wider than maxWidth gets its own line (no
|
|
52
|
+
* intra-word breaking in v1).
|
|
53
|
+
*/
|
|
54
|
+
declare function breakLines(text: string, font: FontSpec, maxWidth: number | undefined, measurer: TextMeasurer): string[];
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/node.d.ts
|
|
57
|
+
/**
|
|
58
|
+
* Where `position` pins to on the node's intrinsic box, as fractions of its
|
|
59
|
+
* size — and the rotation/scale pivot (the Lottie anchor model). Default
|
|
60
|
+
* 'center' preserves every pre-anchor scene byte-for-byte. With a non-center
|
|
61
|
+
* anchor, grow direction falls out: anchor 'left' + a width track sweeps
|
|
62
|
+
* rightward, anchor [0, 1] grows a bar upward.
|
|
63
|
+
*/
|
|
64
|
+
type AnchorSpec = 'center' | 'top-left' | 'top' | 'top-right' | 'left' | 'right' | 'bottom-left' | 'bottom' | 'bottom-right' | readonly [number, number];
|
|
65
|
+
declare function resolveAnchor(spec: AnchorSpec): Vec2;
|
|
66
|
+
interface EvalContext {
|
|
67
|
+
/** The playhead value at evaluate() entry — the only time channel (§3.1). */
|
|
68
|
+
readonly time: number;
|
|
69
|
+
/** Derived: round(time * fps) when the timeline carries an fps advisory; -1 otherwise. */
|
|
70
|
+
readonly frame: number;
|
|
71
|
+
/** Injected by mount()/CLI/exporters (§3.2): the active backend's measurer. */
|
|
72
|
+
readonly measurer: TextMeasurer;
|
|
73
|
+
}
|
|
74
|
+
/** A property initializer: a value, or a computed source (§2.1). */
|
|
75
|
+
type PropInit<T> = T | (() => T);
|
|
76
|
+
interface NodeProps {
|
|
77
|
+
id?: string;
|
|
78
|
+
position?: PropInit<Vec2>;
|
|
79
|
+
rotation?: PropInit<number>;
|
|
80
|
+
scale?: PropInit<Vec2>;
|
|
81
|
+
opacity?: PropInit<number>;
|
|
82
|
+
blend?: PropInit<BlendMode>;
|
|
83
|
+
zIndex?: PropInit<number>;
|
|
84
|
+
/** Group filters (§3.4): the subtree composites as a unit through them. */
|
|
85
|
+
filters?: PropInit<FilterSpec[]>;
|
|
86
|
+
/** Placement point + transform pivot on the intrinsic box; default 'center'. */
|
|
87
|
+
anchor?: AnchorSpec;
|
|
88
|
+
/**
|
|
89
|
+
* §3.5 cross-frame raster cache: FORCE this subtree into a group and stamp a
|
|
90
|
+
* cacheKey on its pushGroup, so a backend with the bitmap LRU re-blits an
|
|
91
|
+
* unchanged subtree under a moving parent instead of re-rasterizing it. A
|
|
92
|
+
* pure performance hint — semantics are byte-identical with the cache off
|
|
93
|
+
* (the cache key folds in the inherited device transform, so a stale CTM can
|
|
94
|
+
* never blit). OFF by default: a scene that never sets it emits ZERO extra
|
|
95
|
+
* groups and is byte-identical to before. Best for expensive STATIC subtrees.
|
|
96
|
+
*
|
|
97
|
+
* CAVEAT (when it does NOT help): the key folds in the inherited device
|
|
98
|
+
* transform, so a subtree that itself DRIFTS — e.g. animated on sub-pixel
|
|
99
|
+
* float positions — misses the cache every frame; cache a static subtree
|
|
100
|
+
* under a *moving parent*, not a subtree that moves itself. And a `filter`
|
|
101
|
+
* is a LIVE composite parameter applied on the blit, never baked into the
|
|
102
|
+
* cached bitmap, so `cache:true` on a filter-declaring (e.g. blurred) group
|
|
103
|
+
* does not cache the filter cost. For per-frame-cheap drift, prefer
|
|
104
|
+
* eliminating the work (a cheaper Paint/effect) over caching it.
|
|
105
|
+
*/
|
|
106
|
+
cache?: boolean;
|
|
107
|
+
}
|
|
108
|
+
interface BindablePropTarget {
|
|
109
|
+
bindSource(fn: () => unknown): void;
|
|
110
|
+
unbindSource(): void;
|
|
111
|
+
/**
|
|
112
|
+
* The value type(s) this prop accepts — bindTimeline hard-throws a mismatched
|
|
113
|
+
* track (§2.2). An array for a GENUINELY polymorphic prop (a Shape `fill` is
|
|
114
|
+
* color|paint — distinct reprs). A plain `vec2` prop tags just `'vec2'`: the
|
|
115
|
+
* 0.15 repr-compat guard binds a `vec2-arc` track (repr 'vec2') to it without
|
|
116
|
+
* an array tag. UNDEFINED for an untagged target (the 2-arg registerTarget
|
|
117
|
+
* form): bindTimeline skips the guard (0.13 back-compat seam).
|
|
118
|
+
*/
|
|
119
|
+
readonly expects: ValueTypeId | readonly ValueTypeId[] | undefined;
|
|
120
|
+
}
|
|
121
|
+
/** Node-local hit-shape override (v2 §C.3) — fat targets for thin strokes. */
|
|
122
|
+
type HitArea = {
|
|
123
|
+
kind: 'rect';
|
|
124
|
+
x: number;
|
|
125
|
+
y: number;
|
|
126
|
+
w: number;
|
|
127
|
+
h: number;
|
|
128
|
+
} | {
|
|
129
|
+
kind: 'circle';
|
|
130
|
+
x: number;
|
|
131
|
+
y: number;
|
|
132
|
+
r: number;
|
|
133
|
+
};
|
|
134
|
+
declare abstract class Node {
|
|
135
|
+
#private;
|
|
136
|
+
readonly id: string | undefined;
|
|
137
|
+
readonly position: Vec2Signal;
|
|
138
|
+
readonly rotation: BindableSignal<number>;
|
|
139
|
+
readonly scale: Vec2Signal;
|
|
140
|
+
readonly opacity: BindableSignal<number>;
|
|
141
|
+
readonly blend: BindableSignal<BlendMode>;
|
|
142
|
+
readonly zIndex: BindableSignal<number>;
|
|
143
|
+
readonly filters: BindableSignal<FilterSpec[]>;
|
|
144
|
+
/** Resolved anchor fraction over the intrinsic box; [0.5, 0.5] = center. */
|
|
145
|
+
readonly anchor: Vec2;
|
|
146
|
+
/** True only when the author SET an anchor — unset keeps the legacy origin. */
|
|
147
|
+
readonly hasAnchor: boolean;
|
|
148
|
+
/** §3.5: opt-in cross-frame raster cache. Forces a group + a stamped cacheKey. */
|
|
149
|
+
readonly cache: boolean;
|
|
150
|
+
parent: Node | null;
|
|
151
|
+
/** v2 §C.3: participates in hit testing; set implicitly by attaching a listener. */
|
|
152
|
+
interactive: boolean;
|
|
153
|
+
/** v2 §C.3: false prunes this subtree from hit testing (PixiJS's flag). */
|
|
154
|
+
interactiveChildren: boolean;
|
|
155
|
+
/** v2 §C.3: explicit hit-shape override in node-local coordinates. */
|
|
156
|
+
hitArea: HitArea | undefined;
|
|
157
|
+
/**
|
|
158
|
+
* Injected by createScene: the scene's CURRENT TextMeasurer (§3.2), so
|
|
159
|
+
* derived-size bindings (e.g. a background tracking Layout.computedSize)
|
|
160
|
+
* measure with the same rasterizer the flow uses.
|
|
161
|
+
*/
|
|
162
|
+
measurerSource: (() => TextMeasurer) | null;
|
|
163
|
+
readonly localMatrix: ReadonlySignal<Mat2x3>;
|
|
164
|
+
readonly worldMatrix: ReadonlySignal<Mat2x3>;
|
|
165
|
+
/** Track-target paths → bindable signals; subclasses register their own props. */
|
|
166
|
+
protected readonly targets: Map<string, BindablePropTarget>;
|
|
167
|
+
constructor(props?: NodeProps);
|
|
168
|
+
/**
|
|
169
|
+
* Register a track-target path → bindable signal, stamping the value type the
|
|
170
|
+
* signal accepts (§2.2). The stamp is what bindTimeline's bind-time guard
|
|
171
|
+
* reads to reject a mismatched track (a scalar on a vec2, a number on a paint
|
|
172
|
+
* prop, …) instead of silently sampling to NaN/undefined.
|
|
173
|
+
*
|
|
174
|
+
* `expects` is OPTIONAL: omitting it (the 2-arg form) leaves the target
|
|
175
|
+
* UNtagged — bindTimeline then skips the type guard for it, which is the
|
|
176
|
+
* back-compat seam for external `Custom`/`Node` subclasses (DESIGN.md §329)
|
|
177
|
+
* and prebuilt 0.13 nodes that called the 2-arg form (0.13 had no guard). A
|
|
178
|
+
* built-in node opts INTO the guard by tagging.
|
|
179
|
+
*/
|
|
180
|
+
protected registerTarget(path: string, sig: {
|
|
181
|
+
bindSource(fn: () => unknown): void;
|
|
182
|
+
unbindSource(): void;
|
|
183
|
+
}, expects?: ValueTypeId | readonly ValueTypeId[]): void;
|
|
184
|
+
resolveTarget(path: string): BindablePropTarget | undefined;
|
|
185
|
+
/**
|
|
186
|
+
* Enumerate this node's registered track-target paths and the value type each
|
|
187
|
+
* accepts — the introspection seam `describe()` reads to build the API
|
|
188
|
+
* manifest from the REAL `registerTarget` calls (so it can't drift). Returns
|
|
189
|
+
* `[path, expects]` pairs in registration order; `expects` is the §2.2 type
|
|
190
|
+
* stamp (a `ValueTypeId`, an array for a polymorphic prop like `fill`, or
|
|
191
|
+
* `undefined` for an untagged target).
|
|
192
|
+
*/
|
|
193
|
+
listTargets(): {
|
|
194
|
+
path: string;
|
|
195
|
+
expects: ValueTypeId | readonly ValueTypeId[] | undefined;
|
|
196
|
+
}[];
|
|
197
|
+
/** Subclass drawing: emit own commands (and children for containers). */
|
|
198
|
+
protected abstract draw(out: DisplayListBuilder, ctx: EvalContext): void;
|
|
199
|
+
/**
|
|
200
|
+
* Natural size for flex flow (§3.2); null = not flowable (a Layout parent
|
|
201
|
+
* emits such children absolutely, untouched).
|
|
202
|
+
*/
|
|
203
|
+
intrinsicSize(measurer: TextMeasurer): {
|
|
204
|
+
w: number;
|
|
205
|
+
h: number;
|
|
206
|
+
} | null;
|
|
207
|
+
/**
|
|
208
|
+
* Vector from the DRAW origin to the intrinsic box's top-left, in the
|
|
209
|
+
* geometry space draw() emits into (anchor-independent — the anchor shift
|
|
210
|
+
* lives in localMatrix). Hit testing boxes nodes with this. Default:
|
|
211
|
+
* center-anchored geometry (every shape). Text overrides — it draws from a
|
|
212
|
+
* left/center/right baseline origin; Path from author-positioned bounds.
|
|
213
|
+
*/
|
|
214
|
+
drawOffset(measurer?: TextMeasurer): {
|
|
215
|
+
x: number;
|
|
216
|
+
y: number;
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* Vector from the node ORIGIN (the point `position` places) to the box's
|
|
220
|
+
* top-left, so Layout can place any node. With an anchor this is exactly
|
|
221
|
+
* (−ax·w, −ay·h); the center default reproduces (−w/2, −h/2).
|
|
222
|
+
*/
|
|
223
|
+
flowOffset(measurer?: TextMeasurer): {
|
|
224
|
+
x: number;
|
|
225
|
+
y: number;
|
|
226
|
+
};
|
|
227
|
+
/**
|
|
228
|
+
* Translation composed after TRS in localMatrix: moves the drawn box so the
|
|
229
|
+
* anchor point lands on the origin. shift = −(drawOffset + anchor·size).
|
|
230
|
+
* No anchor set → zero shift, the legacy origin (shape center / Text
|
|
231
|
+
* baseline / Path author coords) — every pre-anchor scene is byte-stable.
|
|
232
|
+
* An EXPLICIT anchor pins position to that fraction of the box, even
|
|
233
|
+
* 'center' (which differs from the legacy origin only for Text and Path).
|
|
234
|
+
* Nodes without an intrinsic box (Group) warn once and ignore it.
|
|
235
|
+
*/
|
|
236
|
+
protected anchorShift(measurer?: TextMeasurer): Vec2;
|
|
237
|
+
/** §3.5 predicate: composite-as-a-unit when opacity/blend/filters demand it. */
|
|
238
|
+
protected requiresGroup(): boolean;
|
|
239
|
+
/** §3.7: a subtree-level shader pass; ShaderEffect overrides. */
|
|
240
|
+
protected groupShader(): ShaderRef | undefined;
|
|
241
|
+
emit(out: DisplayListBuilder, ctx: EvalContext): void;
|
|
242
|
+
}
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region src/sketch.d.ts
|
|
245
|
+
/** The closed set of hand-drawn looks. Mirrors FilterSpec's discipline. */
|
|
246
|
+
type SketchStyle = {
|
|
247
|
+
kind: 'marker';
|
|
248
|
+
width?: number;
|
|
249
|
+
roughness?: number;
|
|
250
|
+
} | {
|
|
251
|
+
kind: 'crayon';
|
|
252
|
+
width?: number;
|
|
253
|
+
roughness?: number;
|
|
254
|
+
passes?: number;
|
|
255
|
+
} | {
|
|
256
|
+
kind: 'pencil';
|
|
257
|
+
width?: number;
|
|
258
|
+
roughness?: number;
|
|
259
|
+
passes?: number;
|
|
260
|
+
} | {
|
|
261
|
+
kind: 'ink';
|
|
262
|
+
width?: number;
|
|
263
|
+
roughness?: number;
|
|
264
|
+
} | {
|
|
265
|
+
kind: 'chalk';
|
|
266
|
+
width?: number;
|
|
267
|
+
roughness?: number;
|
|
268
|
+
dash?: number[];
|
|
269
|
+
};
|
|
270
|
+
declare class SketchValidationError extends Error {
|
|
271
|
+
constructor(message: string);
|
|
272
|
+
}
|
|
273
|
+
/** Reject unknown kinds / out-of-range params at construction (like validateFilters). */
|
|
274
|
+
declare function validateSketch(s: SketchStyle): void;
|
|
275
|
+
interface ResolvedSketch {
|
|
276
|
+
width: number;
|
|
277
|
+
roughness: number;
|
|
278
|
+
passes: number;
|
|
279
|
+
dash?: number[];
|
|
280
|
+
}
|
|
281
|
+
/** Per-kind defaults — the character of each look. */
|
|
282
|
+
declare function resolveSketch(s: SketchStyle): ResolvedSketch;
|
|
283
|
+
interface Polyline {
|
|
284
|
+
points: [number, number][];
|
|
285
|
+
closed: boolean;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Flatten a path to polylines — de Casteljau for C/Q, arc sampling for E
|
|
289
|
+
* (Circle and rounded-rect corners are 'E' segments, so this MUST handle them
|
|
290
|
+
* or those shapes roughen wrong). `steps` is the samples per curved segment.
|
|
291
|
+
*/
|
|
292
|
+
declare function flatten(segs: readonly PathSeg[], steps?: number): Polyline[];
|
|
293
|
+
/** Total length of a flattened polyline (for draw-on dashing). */
|
|
294
|
+
declare function arcLength(poly: Polyline): number;
|
|
295
|
+
/** A sketchy fill: parallel hatch lines (clipped to the shape by the caller). */
|
|
296
|
+
interface HachureSpec {
|
|
297
|
+
/** hatch line angle, radians */
|
|
298
|
+
angleRad: number;
|
|
299
|
+
/** spacing between lines, px */
|
|
300
|
+
gap: number;
|
|
301
|
+
/** jitter amplitude, px; default 1 */
|
|
302
|
+
roughness?: number;
|
|
303
|
+
}
|
|
304
|
+
declare function validateHachure(h: HachureSpec): void;
|
|
305
|
+
/**
|
|
306
|
+
* Parallel hatch lines covering a path's bounding box at `angleRad`, spaced
|
|
307
|
+
* `gap`, lightly jittered. Returned as `M/L` segments to be stroked INSIDE a
|
|
308
|
+
* clip of the shape (the caller emits the clip). Pure; `rng` reseeded per draw.
|
|
309
|
+
*/
|
|
310
|
+
declare function hachureLines(segs: readonly PathSeg[], spec: HachureSpec, rng: Rng): PathSeg[];
|
|
311
|
+
/**
|
|
312
|
+
* Roughen a path into hand-drawn stroke passes. Each segment becomes a bowed,
|
|
313
|
+
* jittered quadratic; `passes` overlay slightly different jitters for the
|
|
314
|
+
* built-up look. `rng` must be a freshly seeded generator (the caller reseeds
|
|
315
|
+
* per draw from a stable seed, so evaluate() stays pure).
|
|
316
|
+
*/
|
|
317
|
+
declare function roughen(segs: readonly PathSeg[], style: SketchStyle, rng: Rng): {
|
|
318
|
+
strokes: PathSeg[][];
|
|
319
|
+
resolved: ResolvedSketch;
|
|
320
|
+
};
|
|
321
|
+
/** FNV-1a 32-bit — a stable per-shape sketch seed from its id. */
|
|
322
|
+
|
|
323
|
+
/** Convenience: the rough stroke passes for a path at a given seed. */
|
|
324
|
+
declare function sketchStrokes(segs: readonly PathSeg[], style: SketchStyle, seed: number): PathSeg[][];
|
|
325
|
+
//#endregion
|
|
326
|
+
//#region src/nodes.d.ts
|
|
327
|
+
/**
|
|
328
|
+
* The NAMED extension point of the closed §3.1 taxonomy: the documented base
|
|
329
|
+
* an author subclasses to emit IR commands (never canvas calls). It adds
|
|
330
|
+
* nothing to `Node` — it exists so "custom-via-subclassing" is a real,
|
|
331
|
+
* exported surface (the ninth taxonomy member) rather than an unnamed
|
|
332
|
+
* convention. Subclasses implement the abstract `draw()` from `Node`.
|
|
333
|
+
*/
|
|
334
|
+
declare abstract class Custom extends Node {}
|
|
335
|
+
/** Rounded-rect path segments — Rect's outline, shared with Highlight. */
|
|
336
|
+
declare function roundedRectSegs(x: number, y: number, w: number, h: number, r: number): PathSeg[];
|
|
337
|
+
declare class Group extends Node {
|
|
338
|
+
#private;
|
|
339
|
+
readonly children: Node[];
|
|
340
|
+
constructor(props?: NodeProps & {
|
|
341
|
+
children?: Node[];
|
|
342
|
+
});
|
|
343
|
+
/** Record the structural version as a dependency — call inside a computed
|
|
344
|
+
* that walks `children` so add()/remove() invalidate it. */
|
|
345
|
+
protected trackStructure(): void;
|
|
346
|
+
add(child: Node): this;
|
|
347
|
+
/** Remove a child (the reactive counterpart to add()); no-op if absent. */
|
|
348
|
+
remove(child: Node): this;
|
|
349
|
+
protected draw(out: DisplayListBuilder, ctx: EvalContext): void;
|
|
350
|
+
}
|
|
351
|
+
/** A color string is sugar for a solid `color` Paint; a Paint passes through. */
|
|
352
|
+
|
|
353
|
+
interface ShapeProps extends NodeProps {
|
|
354
|
+
/** A CSS color string, or a `Paint` (e.g. a `radial` gradient — soft-light
|
|
355
|
+
* fills with no blur filter; center/radius default to the shape bounds). */
|
|
356
|
+
fill?: PropInit<string | Paint>;
|
|
357
|
+
stroke?: PropInit<string>;
|
|
358
|
+
strokeWidth?: PropInit<number>;
|
|
359
|
+
/** hand-drawn look: the outline is geometrically roughened (see sketch.ts) */
|
|
360
|
+
sketch?: SketchStyle;
|
|
361
|
+
/** seed for the roughening; default a stable hash of the node id */
|
|
362
|
+
sketchSeed?: number;
|
|
363
|
+
/** draw-on for a sketched shape: 0..1 of the outline drawn (default 1 = whole).
|
|
364
|
+
* Track `<id>/reveal`. Precise for single-contour shapes; multi-contour ones
|
|
365
|
+
* reveal each contour in parallel. */
|
|
366
|
+
reveal?: PropInit<number>;
|
|
367
|
+
/** sketchy hatch fill clipped to the shape (the pencil/crayon filled look);
|
|
368
|
+
* requires `sketch`. */
|
|
369
|
+
sketchFill?: HachureSpec;
|
|
370
|
+
}
|
|
371
|
+
declare abstract class Shape extends Node {
|
|
372
|
+
readonly fill: BindableSignal<string | Paint>;
|
|
373
|
+
readonly stroke: BindableSignal<string>;
|
|
374
|
+
readonly strokeWidth: BindableSignal<number>;
|
|
375
|
+
readonly sketch: SketchStyle | undefined;
|
|
376
|
+
readonly sketchFill: HachureSpec | undefined;
|
|
377
|
+
readonly sketchSeed: number;
|
|
378
|
+
readonly reveal: BindableSignal<number>;
|
|
379
|
+
constructor(props?: ShapeProps);
|
|
380
|
+
protected abstract pathSegs(): PathSeg[];
|
|
381
|
+
protected draw(out: DisplayListBuilder): void;
|
|
382
|
+
/** Hand-drawn render: solid fill (if any) under roughened, multi-pass strokes.
|
|
383
|
+
* The seed is consumed fresh each draw, so re-evaluation is byte-identical. */
|
|
384
|
+
private drawSketch;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Coerce a `Path.data` init to a `PathValue`: an array of contour objects
|
|
388
|
+
* passes through; anything else (a string, a number, …) is a construction-time
|
|
389
|
+
* error. SVG `d` strings are NOT parsed here — that parser lives on the
|
|
390
|
+
* tree-shakeable `@glissade/scene/path` subpath (kept off the base embed), so a
|
|
391
|
+
* string `data` throws a clear error pointing at `pathFromSvg(d)`. Returns `[]`
|
|
392
|
+
* for `undefined` (the empty-path default).
|
|
393
|
+
*/
|
|
394
|
+
declare function coercePathData(data: unknown): PathValue;
|
|
395
|
+
/**
|
|
396
|
+
* `PathSeg[]` → `PathValue` (Lottie vertex contours) — the inverse of
|
|
397
|
+
* `Path.pathSegs`, so geometry from `roundedRectSegs` / `sketchStrokes` /
|
|
398
|
+
* `flatten` can be placed on a `Path` node (to morph, motion-path, or draw-on
|
|
399
|
+
* it). C/Q become an anchor + relative in/out tangents; L is a zero-tangent
|
|
400
|
+
* vertex; E samples to vertices; Z closes the contour, folding the closing
|
|
401
|
+
* tangent back onto the first vertex. Round-trips C-contours exactly.
|
|
402
|
+
*/
|
|
403
|
+
declare function pathFromSegs(segs: readonly PathSeg[]): PathValue;
|
|
404
|
+
declare class Rect extends Shape {
|
|
405
|
+
readonly width: BindableSignal<number>;
|
|
406
|
+
readonly height: BindableSignal<number>;
|
|
407
|
+
/** Corner radius; clamped to half the smaller dimension. radius = h/2 makes a pill. */
|
|
408
|
+
readonly cornerRadius: BindableSignal<number>;
|
|
409
|
+
constructor(props?: ShapeProps & {
|
|
410
|
+
width?: PropInit<number>;
|
|
411
|
+
height?: PropInit<number>;
|
|
412
|
+
cornerRadius?: PropInit<number>;
|
|
413
|
+
});
|
|
414
|
+
intrinsicSize(): {
|
|
415
|
+
w: number;
|
|
416
|
+
h: number;
|
|
417
|
+
};
|
|
418
|
+
protected pathSegs(): PathSeg[];
|
|
419
|
+
}
|
|
420
|
+
declare class Circle extends Shape {
|
|
421
|
+
readonly radius: BindableSignal<number>;
|
|
422
|
+
constructor(props?: ShapeProps & {
|
|
423
|
+
radius?: PropInit<number>;
|
|
424
|
+
});
|
|
425
|
+
intrinsicSize(): {
|
|
426
|
+
w: number;
|
|
427
|
+
h: number;
|
|
428
|
+
};
|
|
429
|
+
protected pathSegs(): PathSeg[];
|
|
430
|
+
}
|
|
431
|
+
interface PathProps extends ShapeProps {
|
|
432
|
+
/**
|
|
433
|
+
* The geometry (§2.2 'path' value): bezier contours in vertex form,
|
|
434
|
+
* animatable via a track on '<id>/d'. Accepts a `PathValue` directly or a
|
|
435
|
+
* computed `() => PathValue`. For an SVG `d` STRING, parse it first with
|
|
436
|
+
* `pathFromSvg(d)` from the tree-shakeable `@glissade/scene/path` subpath
|
|
437
|
+
* (off the base embed) — passing a bare string throws a clear construction
|
|
438
|
+
* error rather than dragging the parser onto every embed.
|
|
439
|
+
*/
|
|
440
|
+
data?: PropInit<PathValue> | string;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Arbitrary bezier geometry — the Lottie-import landing spot and the target
|
|
444
|
+
* of native path morphs. Coordinates are node-local (the node origin is
|
|
445
|
+
* wherever the author put 0,0); flow placement uses the control-point bounds.
|
|
446
|
+
*/
|
|
447
|
+
declare class Path extends Shape {
|
|
448
|
+
readonly data: BindableSignal<PathValue>;
|
|
449
|
+
constructor(props?: PathProps);
|
|
450
|
+
/** Control-point bounding box (conservative: contains the true curve). */
|
|
451
|
+
bounds(): {
|
|
452
|
+
minX: number;
|
|
453
|
+
minY: number;
|
|
454
|
+
maxX: number;
|
|
455
|
+
maxY: number;
|
|
456
|
+
};
|
|
457
|
+
intrinsicSize(): {
|
|
458
|
+
w: number;
|
|
459
|
+
h: number;
|
|
460
|
+
};
|
|
461
|
+
/** Geometry is node-local, not center-anchored: offset to the box's actual top-left. */
|
|
462
|
+
drawOffset(): {
|
|
463
|
+
x: number;
|
|
464
|
+
y: number;
|
|
465
|
+
};
|
|
466
|
+
protected pathSegs(): PathSeg[];
|
|
467
|
+
}
|
|
468
|
+
interface ImageProps extends NodeProps {
|
|
469
|
+
/** Asset id from the Timeline manifest (§2.3). */
|
|
470
|
+
assetId: string;
|
|
471
|
+
width?: PropInit<number>;
|
|
472
|
+
height?: PropInit<number>;
|
|
473
|
+
}
|
|
474
|
+
declare class ImageNode extends Node {
|
|
475
|
+
/** Marks this node as referencing a kind 'image' timeline asset (§2.3). */
|
|
476
|
+
static readonly assetKind: "image";
|
|
477
|
+
readonly assetId: string;
|
|
478
|
+
readonly width: BindableSignal<number>;
|
|
479
|
+
readonly height: BindableSignal<number>;
|
|
480
|
+
constructor(props: ImageProps);
|
|
481
|
+
intrinsicSize(): {
|
|
482
|
+
w: number;
|
|
483
|
+
h: number;
|
|
484
|
+
};
|
|
485
|
+
protected draw(out: DisplayListBuilder): void;
|
|
486
|
+
}
|
|
487
|
+
interface VideoProps extends NodeProps {
|
|
488
|
+
/** Asset id from the Timeline manifest (kind 'video'). */
|
|
489
|
+
assetId: string;
|
|
490
|
+
/** Timeline second at which the clip starts (§3.8). */
|
|
491
|
+
at?: number;
|
|
492
|
+
/** Seconds into the source where playback begins. */
|
|
493
|
+
trimStart?: number;
|
|
494
|
+
playbackRate?: number;
|
|
495
|
+
/** Clip length on the timeline (seconds); defaults to rest-of-source. */
|
|
496
|
+
clipDuration?: number;
|
|
497
|
+
/**
|
|
498
|
+
* Source frame rate; when set, mediaT is quantized to the source grid in
|
|
499
|
+
* the IR itself (§3.8) so equal-frame times emit identical DisplayLists.
|
|
500
|
+
* Unset: backends quantize at resolve time (pixels identical, IR not).
|
|
501
|
+
*/
|
|
502
|
+
sourceFps?: number;
|
|
503
|
+
width?: PropInit<number>;
|
|
504
|
+
height?: PropInit<number>;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Pure given a warmed VideoFrameSource (§3.8): emit() does only the
|
|
508
|
+
* frame-indexed media-time arithmetic — mediaT = trimStart + (t - at) * rate —
|
|
509
|
+
* and references the exact source-grid frame; backends resolve it.
|
|
510
|
+
*/
|
|
511
|
+
declare class Video extends Node {
|
|
512
|
+
/** Marks this node as referencing a kind 'video' timeline asset (§3.8). */
|
|
513
|
+
static readonly assetKind: "video";
|
|
514
|
+
readonly assetId: string;
|
|
515
|
+
readonly at: number;
|
|
516
|
+
readonly trimStart: number;
|
|
517
|
+
readonly playbackRate: number;
|
|
518
|
+
readonly clipDuration: number | undefined;
|
|
519
|
+
readonly sourceFps: number | undefined;
|
|
520
|
+
readonly width: BindableSignal<number>;
|
|
521
|
+
readonly height: BindableSignal<number>;
|
|
522
|
+
constructor(props: VideoProps);
|
|
523
|
+
/** Frame-indexed media time for timeline time t; null when outside the clip. */
|
|
524
|
+
mediaTime(t: number): number | null;
|
|
525
|
+
protected draw(out: DisplayListBuilder, ctx: EvalContext): void;
|
|
526
|
+
}
|
|
527
|
+
/** One laid-out line's ink box, in the Text node's draw space. */
|
|
528
|
+
interface LineBox {
|
|
529
|
+
text: string;
|
|
530
|
+
x: number;
|
|
531
|
+
y: number;
|
|
532
|
+
w: number;
|
|
533
|
+
h: number;
|
|
534
|
+
}
|
|
535
|
+
/** One word's ink box within a laid-out line, in the Text node's draw space. */
|
|
536
|
+
interface WordBox {
|
|
537
|
+
text: string;
|
|
538
|
+
/** laid-out line index (blank lines keep their slot in the numbering) */
|
|
539
|
+
line: number;
|
|
540
|
+
x: number;
|
|
541
|
+
y: number;
|
|
542
|
+
w: number;
|
|
543
|
+
h: number;
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* One grapheme's ink box within a laid-out line, in the Text node's draw space
|
|
547
|
+
* — the per-grapheme analogue of {@link WordBox}, boxing the SAME grapheme
|
|
548
|
+
* units `reveal`/`graphemes()` count. Whitespace graphemes advance but have no
|
|
549
|
+
* box (dropped), exactly as `wordBoxes()` trims whitespace advance.
|
|
550
|
+
*/
|
|
551
|
+
interface GraphemeBox {
|
|
552
|
+
text: string;
|
|
553
|
+
/** laid-out line index (blank lines keep their slot in the numbering) */
|
|
554
|
+
line: number;
|
|
555
|
+
x: number;
|
|
556
|
+
y: number;
|
|
557
|
+
w: number;
|
|
558
|
+
h: number;
|
|
559
|
+
}
|
|
560
|
+
interface TextProps extends NodeProps {
|
|
561
|
+
text?: PropInit<string>;
|
|
562
|
+
fill?: PropInit<string>;
|
|
563
|
+
fontFamily?: string;
|
|
564
|
+
fontSize?: PropInit<number>;
|
|
565
|
+
fontWeight?: number;
|
|
566
|
+
/** Font style; default 'normal'. Threaded into FontSpec.style (§3.6). */
|
|
567
|
+
fontStyle?: 'normal' | 'italic';
|
|
568
|
+
/** Horizontal alignment about the node position; default 'left'. */
|
|
569
|
+
align?: 'left' | 'center' | 'right';
|
|
570
|
+
/** Wrap width in px; unset = no wrapping (explicit \n still breaks). */
|
|
571
|
+
width?: PropInit<number>;
|
|
572
|
+
/** Line height as a multiple of fontSize; default 1.25. */
|
|
573
|
+
lineHeight?: number;
|
|
574
|
+
/**
|
|
575
|
+
* Typewriter reveal: how many graphemes of the laid-out text are shown,
|
|
576
|
+
* left-to-right. Default Infinity = fully shown (byte-identical to no
|
|
577
|
+
* reveal, so existing goldens never shift). Track target '<id>/reveal';
|
|
578
|
+
* author a per-keystroke staircase off graphemes() — see revealSchedule().
|
|
579
|
+
*/
|
|
580
|
+
reveal?: PropInit<number>;
|
|
581
|
+
/**
|
|
582
|
+
* Typewriter reveal expressed as a FRACTION of the grapheme stream, in
|
|
583
|
+
* [0, 1] — pure count-rounding sugar over {@link reveal}: it resolves against
|
|
584
|
+
* the SAME laid-out grapheme stream to `count = round(fraction * graphemes)`
|
|
585
|
+
* and feeds the identical masked-emit path. `1` = fully shown, `0` = hidden,
|
|
586
|
+
* `0.5` on a 10-grapheme string == `reveal: 5`. When set (the signal is not
|
|
587
|
+
* NaN) it OVERRIDES `reveal`; left unset (the default) the node is
|
|
588
|
+
* byte-identical to one without it. Animatable — track target
|
|
589
|
+
* '<id>/revealFraction'. The sub-grapheme clip-wipe is intentionally out of
|
|
590
|
+
* scope (the unit stays whole graphemes; no partial-grapheme softness).
|
|
591
|
+
*/
|
|
592
|
+
revealFraction?: PropInit<number>;
|
|
593
|
+
}
|
|
594
|
+
declare class Text extends Node {
|
|
595
|
+
readonly text: BindableSignal<string>;
|
|
596
|
+
readonly fill: BindableSignal<string>;
|
|
597
|
+
readonly fontSize: BindableSignal<number>;
|
|
598
|
+
readonly fontFamily: string;
|
|
599
|
+
readonly fontWeight: number;
|
|
600
|
+
readonly fontStyle: 'normal' | 'italic';
|
|
601
|
+
readonly align: 'left' | 'center' | 'right';
|
|
602
|
+
readonly width: BindableSignal<number>;
|
|
603
|
+
readonly lineHeight: number;
|
|
604
|
+
readonly reveal: BindableSignal<number>;
|
|
605
|
+
/**
|
|
606
|
+
* Reveal fraction in [0, 1]; NaN (the default) means "unset" so plain `reveal`
|
|
607
|
+
* is authoritative and the node stays byte-identical to one without it. When
|
|
608
|
+
* not-NaN it overrides `reveal` via {@link effectiveReveal}.
|
|
609
|
+
*/
|
|
610
|
+
readonly revealFraction: BindableSignal<number>;
|
|
611
|
+
constructor(props?: TextProps);
|
|
612
|
+
/**
|
|
613
|
+
* The grapheme COUNT to reveal this frame — the single source the draw mask,
|
|
614
|
+
* {@link revealHead}, and the masked emit path all read. When `revealFraction`
|
|
615
|
+
* is set (not NaN) it wins: `round(clamp(fraction, 0, 1) * graphemeCount)`,
|
|
616
|
+
* resolved against the SAME laid-out grapheme stream `reveal` counts. Unset
|
|
617
|
+
* (NaN) it falls straight through to `reveal()`, so a node without
|
|
618
|
+
* `revealFraction` is byte-identical to before this prop existed.
|
|
619
|
+
*/
|
|
620
|
+
private effectiveReveal;
|
|
621
|
+
intrinsicSize(measurer: TextMeasurer): {
|
|
622
|
+
w: number;
|
|
623
|
+
h: number;
|
|
624
|
+
};
|
|
625
|
+
/** Text draws from a baseline origin at its align edge, not a center (§3.6). */
|
|
626
|
+
drawOffset(measurer?: TextMeasurer): {
|
|
627
|
+
x: number;
|
|
628
|
+
y: number;
|
|
629
|
+
};
|
|
630
|
+
/**
|
|
631
|
+
* The wrapped box {w, h}, measured with the scene's active measurer — the
|
|
632
|
+
* same numbers Layout flows with, public so bindings never hand-calculate
|
|
633
|
+
* text dimensions (e.g. underline width = () => title.measuredSize().w).
|
|
634
|
+
*/
|
|
635
|
+
measuredSize(measurer?: TextMeasurer): {
|
|
636
|
+
w: number;
|
|
637
|
+
h: number;
|
|
638
|
+
};
|
|
639
|
+
/**
|
|
640
|
+
* Per-line ink boxes in this node's DRAW space (origin = first baseline at
|
|
641
|
+
* the align edge), from the same breakLines pass that draws. Pull-based:
|
|
642
|
+
* re-measures when text/font/width animate. Blank lines (from '\n\n')
|
|
643
|
+
* produce no box. The substrate for highlights, underlines, per-line
|
|
644
|
+
* reveals, selections.
|
|
645
|
+
*/
|
|
646
|
+
lineBoxes(measurer?: TextMeasurer): LineBox[];
|
|
647
|
+
/**
|
|
648
|
+
* Per-word ink boxes within each laid-out line — the SAME segmentation the
|
|
649
|
+
* breaker flows (Intl.Segmenter boundaries, punctuation glued), positioned
|
|
650
|
+
* by cumulative prefix advances so cross-word kerning is exact and word
|
|
651
|
+
* widths sum to the line's width. Whitespace contributes advance but no
|
|
652
|
+
* box. Pair index-wise with a narration manifest's word timestamps for
|
|
653
|
+
* karaoke; draw your own rects for sub-line multi-color token work.
|
|
654
|
+
*/
|
|
655
|
+
wordBoxes(measurer?: TextMeasurer): WordBox[];
|
|
656
|
+
/**
|
|
657
|
+
* Per-grapheme ink boxes within each laid-out line — the per-grapheme analogue
|
|
658
|
+
* of {@link wordBoxes}, boxing the SAME grapheme units `reveal`/`graphemes()`
|
|
659
|
+
* count (`Intl.Segmenter` boundaries via `segmentGraphemes`, so emoji/ZWJ
|
|
660
|
+
* sequences stay whole). Positioned by cumulative prefix advances so
|
|
661
|
+
* cross-grapheme kerning is exact and the boxes' advances sum to the line
|
|
662
|
+
* width — the boundaries MATCH the draw path, so splitText goldens don't
|
|
663
|
+
* drift. Whitespace graphemes advance but produce no box (dropped), exactly
|
|
664
|
+
* as `wordBoxes()` trims whitespace advance. The substrate `splitText({ by:
|
|
665
|
+
* 'grapheme' })` snapshots.
|
|
666
|
+
*/
|
|
667
|
+
graphemeBoxes(measurer?: TextMeasurer): GraphemeBox[];
|
|
668
|
+
/**
|
|
669
|
+
* The laid-out grapheme stream the typewriter reveal advances over — every
|
|
670
|
+
* grapheme of every wrapped line, in reading order (soft-wrap whitespace is
|
|
671
|
+
* dropped by the breaker, exactly as drawn, so draw/revealHead/revealSchedule
|
|
672
|
+
* all agree). Pull-based; its length is the `reveal` count that shows
|
|
673
|
+
* everything. Author a per-keystroke staircase straight off it:
|
|
674
|
+
*
|
|
675
|
+
* const g = title.graphemes();
|
|
676
|
+
* track('title/reveal', 'number',
|
|
677
|
+
* g.map((_, i) => key(t0 + i * 0.05, i + 1, { interp: 'hold' })));
|
|
678
|
+
*/
|
|
679
|
+
graphemes(measurer?: TextMeasurer): string[];
|
|
680
|
+
/**
|
|
681
|
+
* Draw-space position of the reveal head — the caret point just after the
|
|
682
|
+
* last revealed grapheme, for the current `reveal` value. Drives TextCursor;
|
|
683
|
+
* honours align and wrap exactly like wordBoxes(). At reveal 0 it sits at the
|
|
684
|
+
* start of the first line; fully revealed, at the end of the last line.
|
|
685
|
+
*/
|
|
686
|
+
revealHead(measurer?: TextMeasurer): {
|
|
687
|
+
x: number;
|
|
688
|
+
y: number;
|
|
689
|
+
h: number;
|
|
690
|
+
line: number;
|
|
691
|
+
index: number;
|
|
692
|
+
};
|
|
693
|
+
protected draw(out: DisplayListBuilder, ctx: EvalContext): void;
|
|
694
|
+
}
|
|
695
|
+
/** One revealed grapheme's timing + draw-space position — the keystroke sync
|
|
696
|
+
* contract, the direct analogue of narrate's TimedWord[]. SFX maps each mark to
|
|
697
|
+
* one AudioClip at `at: time`; visuals can place per-key effects at (x, y). */
|
|
698
|
+
interface RevealMark {
|
|
699
|
+
/** index into the laid-out grapheme stream (Text.graphemes()) */
|
|
700
|
+
charIndex: number;
|
|
701
|
+
/** the revealed grapheme (raw — char-class policy is the consumer's) */
|
|
702
|
+
grapheme: string;
|
|
703
|
+
/** time the grapheme first becomes visible, from the reveal track */
|
|
704
|
+
time: number;
|
|
705
|
+
/** caret x just after this grapheme, in the Text's draw space */
|
|
706
|
+
x: number;
|
|
707
|
+
/** top of the grapheme's line box, in the Text's draw space */
|
|
708
|
+
y: number;
|
|
709
|
+
/** laid-out line index */
|
|
710
|
+
line: number;
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Pure per-grapheme schedule from a Text and its reveal track — geometry from
|
|
714
|
+
* the text, timing from the track. A grapheme's time is the first key whose
|
|
715
|
+
* value reveals it (value >= index + 1); graphemes the track never reaches are
|
|
716
|
+
* omitted. The single source SFX keystroke-sync consumes (keystrokeClips()):
|
|
717
|
+
* one click per mark at `at: mark.time`, char-class policy (skip space/newline,
|
|
718
|
+
* pick a sample) decided downstream from `mark.grapheme`.
|
|
719
|
+
*/
|
|
720
|
+
declare function revealSchedule(text: Text, reveal: Track<number>, measurer?: TextMeasurer): RevealMark[];
|
|
721
|
+
//#endregion
|
|
722
|
+
export { resolveSketch as A, NodeProps as B, Polyline as C, arcLength as D, SketchValidationError as E, AnchorSpec as F, TextMetricsLite as G, resolveAnchor as H, BindablePropTarget as I, quantize as J, breakLines as K, EvalContext as L, sketchStrokes as M, validateHachure as N, flatten as O, validateSketch as P, HitArea as R, HachureSpec as S, SketchStyle as T, MEASURE_QUANTUM_PX as U, PropInit as V, TextMeasurer as W, segmentWords as X, segmentGraphemes as Y, setDefaultMeasurer as Z, WordBox as _, ImageNode as a, revealSchedule as b, Path as c, RevealMark as d, ShapeProps as f, VideoProps as g, Video as h, Group as i, roughen as j, hachureLines as k, PathProps as l, TextProps as m, Custom as n, ImageProps as o, Text as p, estimatingMeasurer as q, GraphemeBox as r, LineBox as s, Circle as t, Rect as u, coercePathData as v, ResolvedSketch as w, roundedRectSegs as x, pathFromSegs as y, Node as z };
|