@glissade/scene 0.56.0-pre.0 → 0.57.0-pre.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/describe.js +25 -1
- package/dist/diagnostics.d.ts +41 -2
- package/dist/diagnostics.js +81 -2
- package/dist/each.d.ts +125 -0
- package/dist/each.js +168 -0
- package/dist/index.d.ts +3 -161
- package/dist/index.js +4 -246
- package/dist/motion.d.ts +207 -2
- package/dist/motion.js +396 -3
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -3,9 +3,9 @@ import { $ as isEstimatingMeasurer, A as hachureLines, B as Node, C as HachureSp
|
|
|
3
3
|
import { t as collapseReplacer } from "./collapseReplacer.js";
|
|
4
4
|
import { a as SceneModule, c as evaluate, i as SceneInit, n as ReservedNodeIdError, o as bindScene, r as Scene, s as createScene, t as DuplicateNodeIdError } from "./scene.js";
|
|
5
5
|
import { a as typewriter, c as textCursor, i as TypewriterResult, n as StepMark, o as TextCursor, r as TypeEdit, s as TextCursorProps, t as EditMark } from "./typewriter.js";
|
|
6
|
+
import { a as EachLayout, c as EachResult, i as EachError, l as Place, n as EachContext, o as EachMotion, r as EachDistribute, s as EachOpts, t as EachBox, u as each } from "./each.js";
|
|
6
7
|
import { a as LayoutEngineMissingError, c as requireLayoutEngine, i as LayoutEngine, l as setLayoutEngine, n as LayoutChildSpec, r as LayoutContainerSpec, s as getLayoutEngine, t as LayoutBox } from "./layoutEngine.js";
|
|
7
|
-
import { BindableSignal,
|
|
8
|
-
import { ChannelOverride, Clip } from "@glissade/core/clips";
|
|
8
|
+
import { BindableSignal, EaseSpec, MeshPaint as MeshPaint$1, Track } from "@glissade/core";
|
|
9
9
|
|
|
10
10
|
//#region src/taxonomy.d.ts
|
|
11
11
|
|
|
@@ -51,125 +51,6 @@ declare class Highlight extends Node {
|
|
|
51
51
|
/** `children: [highlight(title, { color: '#ffe066' }), title]` — marker behind the text. */
|
|
52
52
|
declare function highlight(text: Text, props?: Omit<HighlightProps, 'text'>): Highlight;
|
|
53
53
|
//#endregion
|
|
54
|
-
//#region src/each.d.ts
|
|
55
|
-
/** An aspect-fraction placement: [fx, fy], each conventionally in [0, 1]. */
|
|
56
|
-
type Place = readonly [number, number];
|
|
57
|
-
/**
|
|
58
|
-
* Built-in layouts (a discriminated union — NOT factory fns) plus the escape
|
|
59
|
-
* hatch `(i, n) => [fx, fy]`. Every built-in is PURE arithmetic in aspect
|
|
60
|
-
* fractions; mapping to px happens only when `box` is given (see `places`).
|
|
61
|
-
*/
|
|
62
|
-
type EachLayout = {
|
|
63
|
-
kind: 'row';
|
|
64
|
-
gap?: number;
|
|
65
|
-
align?: number;
|
|
66
|
-
} | {
|
|
67
|
-
kind: 'column';
|
|
68
|
-
gap?: number;
|
|
69
|
-
align?: number;
|
|
70
|
-
} | {
|
|
71
|
-
kind: 'grid';
|
|
72
|
-
cols: number;
|
|
73
|
-
rows?: number;
|
|
74
|
-
gapX?: number;
|
|
75
|
-
gapY?: number;
|
|
76
|
-
order?: 'row' | 'column';
|
|
77
|
-
} | {
|
|
78
|
-
kind: 'ring';
|
|
79
|
-
radius?: number;
|
|
80
|
-
center?: Place;
|
|
81
|
-
startAngle?: number;
|
|
82
|
-
sweep?: number;
|
|
83
|
-
} | ((i: number, n: number) => Place);
|
|
84
|
-
/** How a `stagger` delay distributes across the clones. */
|
|
85
|
-
type EachDistribute = 'delay' | 'from-center' | 'from-edges';
|
|
86
|
-
/** Per-index motion: a clip fanned across the clones with stagger + jitter. */
|
|
87
|
-
interface EachMotion {
|
|
88
|
-
/** The motion clip applied to every clone (TYPE: `Clip` from core/clips). */
|
|
89
|
-
clip: Clip;
|
|
90
|
-
/** Wall-clock start second of the first clone. Default 0. */
|
|
91
|
-
startSec?: number;
|
|
92
|
-
/** Per-index delay (seconds) or a function of the index. Default 0. */
|
|
93
|
-
stagger?: number | ((i: number) => number);
|
|
94
|
-
/**
|
|
95
|
-
* Shape a numeric `stagger` gap into a distribution. `from-center` ramps the
|
|
96
|
-
* delay outward from the middle clone, `from-edges` inward toward it; `delay`
|
|
97
|
-
* (the default) is the plain `i * gap` ramp. Ignored when `stagger` is a fn.
|
|
98
|
-
*/
|
|
99
|
-
distribute?: EachDistribute;
|
|
100
|
-
/** Per-index clip overrides, seeded — `(i, rng, n) => overrides`. */
|
|
101
|
-
jitter?: (i: number, rng: Rng, n: number) => Record<string, ChannelOverride>;
|
|
102
|
-
/** Clip speed (passed straight to `clip.apply`). */
|
|
103
|
-
speed?: number;
|
|
104
|
-
}
|
|
105
|
-
/** Pixel box for mapping aspect-fraction places to a concrete coordinate frame. */
|
|
106
|
-
interface EachBox {
|
|
107
|
-
w: number;
|
|
108
|
-
h: number;
|
|
109
|
-
/** Top-left of the box in scene coords; default [0, 0]. */
|
|
110
|
-
origin?: Place;
|
|
111
|
-
}
|
|
112
|
-
interface EachOpts {
|
|
113
|
-
/** Stable id prefix; clones are `${id}/${i}`, the wrapping group is `${id}`. */
|
|
114
|
-
id: string;
|
|
115
|
-
layout: EachLayout;
|
|
116
|
-
motion?: EachMotion;
|
|
117
|
-
/** Seed for per-clone RNG; defaults to a stable hash of `id`. */
|
|
118
|
-
seed?: number;
|
|
119
|
-
/** When given, `places` also carries the px-mapped points (see EachResult). */
|
|
120
|
-
box?: EachBox;
|
|
121
|
-
}
|
|
122
|
-
/** The per-clone authoring context handed to the factory. */
|
|
123
|
-
interface EachContext {
|
|
124
|
-
/** Clone index, 0..n-1. */
|
|
125
|
-
i: number;
|
|
126
|
-
/** Total clone count. */
|
|
127
|
-
n: number;
|
|
128
|
-
/** This clone's id (`${opts.id}/${i}`). */
|
|
129
|
-
id: string;
|
|
130
|
-
/** Aspect-fraction placement [fx, fy] — ALWAYS a fraction (px is separate). */
|
|
131
|
-
place: Place;
|
|
132
|
-
/** Seeded generator for this clone: `random(mix(seed, i))`. */
|
|
133
|
-
rng: Rng;
|
|
134
|
-
/** The resolved base seed (`opts.seed ?? hash(id)`). */
|
|
135
|
-
seed: number;
|
|
136
|
-
}
|
|
137
|
-
interface EachResult {
|
|
138
|
-
/** The wrapping group (`id: opts.id`) holding every generated child. */
|
|
139
|
-
node: Group;
|
|
140
|
-
/** The generated children, in index order. */
|
|
141
|
-
children: Node[];
|
|
142
|
-
/** The compiled motion tracks (empty when no `motion`). */
|
|
143
|
-
tracks: Track[];
|
|
144
|
-
/** Max child clip end (== startSec when no motion). */
|
|
145
|
-
end: number;
|
|
146
|
-
/**
|
|
147
|
-
* Per-clone placement. `frac` is the aspect-fraction [fx, fy] every layout
|
|
148
|
-
* produces; `px` is present only when `opts.box` was given (frac mapped into
|
|
149
|
-
* the box). Authoring a `box` once here is the single place fraction→px lives.
|
|
150
|
-
*/
|
|
151
|
-
places: {
|
|
152
|
-
frac: Place;
|
|
153
|
-
px?: Place;
|
|
154
|
-
}[];
|
|
155
|
-
}
|
|
156
|
-
declare class EachError extends Error {
|
|
157
|
-
constructor(message: string);
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Generate `n` clones from `factory`, lay them out, and (optionally) stagger a
|
|
161
|
-
* motion clip across them.
|
|
162
|
-
*
|
|
163
|
-
* const grid = each(9, (i) => new Rect({ width: 40, height: 40, fill: '#9ef0c0' }), {
|
|
164
|
-
* id: 'card',
|
|
165
|
-
* layout: { kind: 'grid', cols: 3 },
|
|
166
|
-
* box: { w: 600, h: 360 },
|
|
167
|
-
* motion: { clip: popIn(), stagger: 0.08, distribute: 'from-center' },
|
|
168
|
-
* });
|
|
169
|
-
* // scene children: [grid.node]; timeline tracks: [...grid.tracks]
|
|
170
|
-
*/
|
|
171
|
-
declare function each(n: number, factory: (i: number, ctx: EachContext) => Node, opts: EachOpts): EachResult;
|
|
172
|
-
//#endregion
|
|
173
54
|
//#region src/drawOn.d.ts
|
|
174
55
|
interface DrawOnOptions {
|
|
175
56
|
/** when the stroke-on starts, seconds; default 0 */
|
|
@@ -277,45 +158,6 @@ interface RenderBackend extends TextMeasurer {
|
|
|
277
158
|
dispose(): void;
|
|
278
159
|
}
|
|
279
160
|
//#endregion
|
|
280
|
-
//#region src/fontUsage.d.ts
|
|
281
|
-
/** Walk `scene` for Text nodes; one usage per node carrying its full text. */
|
|
282
|
-
declare function collectTextUsages(scene: Scene): FontUsage[];
|
|
283
|
-
/**
|
|
284
|
-
* Collect font usages from the POST-localize document's STRING tracks (FIX 3,
|
|
285
|
-
* 0.14 canary). For every `'string'` track whose target node is a Text node,
|
|
286
|
-
* emit one usage per distinct localized KEY VALUE under that node's fontFamily —
|
|
287
|
-
* so a localized CJK message bound to a Latin-only font surfaces as an uncovered
|
|
288
|
-
* glyph. `collectTextUsages` only sees the authored BASE `node.text()`, which is
|
|
289
|
-
* resolved BEFORE the localized string tracks bind, so it misses this.
|
|
290
|
-
*/
|
|
291
|
-
declare function collectLocalizedTextUsages(scene: Scene, doc: Timeline): FontUsage[];
|
|
292
|
-
/**
|
|
293
|
-
* Caller-supplied I/O: fetch the raw bytes for a font face URL (the export
|
|
294
|
-
* paths read a file / fetch a URL; this keeps core pure). Returning undefined
|
|
295
|
-
* means "could not load" — that family contributes no coverage, surfacing as
|
|
296
|
-
* missing glyphs (strict) / a dev warning, never a hang.
|
|
297
|
-
*/
|
|
298
|
-
type FontByteLoader = (url: string) => Promise<ArrayBuffer | undefined>;
|
|
299
|
-
interface ValidateSceneFontsOptions {
|
|
300
|
-
mode?: FontMode;
|
|
301
|
-
/** OS-installed families to treat as registered (case-insensitive). */
|
|
302
|
-
osFamilies?: ReadonlySet<string> | undefined;
|
|
303
|
-
/**
|
|
304
|
-
* Additional usages to validate alongside the scene's authored Text (FIX 3):
|
|
305
|
-
* the POST-localize document's localized string-track values, which the
|
|
306
|
-
* scene-walk can't see (they bind AFTER `node.text()` is read). Build them
|
|
307
|
-
* with `collectLocalizedTextUsages(scene, localizedDoc)`.
|
|
308
|
-
*/
|
|
309
|
-
extraUsages?: readonly FontUsage[] | undefined;
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Run §3.6 font validation for a scene + its timeline document. Builds the
|
|
313
|
-
* registry from `doc.assets`, loads each registered face's cmap via `loadBytes`
|
|
314
|
-
* (once per family — the first face's URL is enough for coverage), and runs the
|
|
315
|
-
* pure `validateFonts`. Strict mode throws FontValidationError; dev warns.
|
|
316
|
-
*/
|
|
317
|
-
declare function validateSceneFonts(scene: Scene, doc: Timeline, loadBytes: FontByteLoader, options?: ValidateSceneFontsOptions): Promise<CoverageReport>;
|
|
318
|
-
//#endregion
|
|
319
161
|
//#region src/shaderEffect.d.ts
|
|
320
162
|
interface ShaderEffectProps extends NodeProps {
|
|
321
163
|
children?: Node[];
|
|
@@ -651,4 +493,4 @@ declare function meshRasterSize(bw: number, bh: number): {
|
|
|
651
493
|
h: number;
|
|
652
494
|
};
|
|
653
495
|
//#endregion
|
|
654
|
-
export { ALL_FILTER_KINDS, type AnchorSpec, type BackendCaps, type BindablePropTarget, type BlendMode, type Bounds, type CanvasLike, Circle, type ClipRegion, ColdAssetError, type Ctx2DLike, Custom, DeterminismViolationError, type DisplayList, type DisplayListBuilder, type DrawCommand, type DrawOnEachOptions, type DrawOnOptions, DuplicateNodeIdError, type EachBox, type EachContext, type EachDistribute, EachError, type EachLayout, type EachMotion, type EachOpts, type EachResult, Echo, type EchoProps, type EditMark, type EvalContext, type FilterKind, type FilterSpec, FilterValidationError, type
|
|
496
|
+
export { ALL_FILTER_KINDS, type AnchorSpec, type BackendCaps, type BindablePropTarget, type BlendMode, type Bounds, type CanvasLike, Circle, type ClipRegion, ColdAssetError, type Ctx2DLike, Custom, DeterminismViolationError, type DisplayList, type DisplayListBuilder, type DrawCommand, type DrawOnEachOptions, type DrawOnOptions, DuplicateNodeIdError, type EachBox, type EachContext, type EachDistribute, EachError, type EachLayout, type EachMotion, type EachOpts, type EachResult, Echo, type EchoProps, type EditMark, type EvalContext, type FilterKind, type FilterSpec, FilterValidationError, type FontSpec, type GraphemeBox, Group, type GuardMode, type HachureSpec, Highlight, type HighlightProps, type HitArea, IDENTITY, ImageNode as Image, ImageNode, type ImageDataLike, type ImageHandle, type ImageProps, type LayerCacheEntry, type LayerStore, type LayoutBox, type LayoutChildSpec, type LayoutContainerSpec, type LayoutEngine, LayoutEngineMissingError, type LineBox, MEASURE_QUANTUM_PX, MESH_DOWNSCALE, MESH_SHEPARD_POWER, MESH_SIGMA, type Mat2x3, type MeshInterpolation, type MeshPaint, type MeshPoint, MotionBlur, type MotionBlurProps, NODE_TAXONOMY, Node, NodeConstructionError, type NodeProps, type NodeTypeName, type Paint, Path, type PathLike, type PathProps, type PathSeg, type Place, type Polyline, type PropInit, Raster2D, type Raster2DHost, Rect, type Rect$1 as RectShape, type RenderBackend, ReservedNodeIdError, type ResolvedSketch, type Resource, type ResourceId, type RevealMark, type Scene, type SceneInit, type SceneModule, type ShaderCaps, ShaderEffect, type ShaderEffectProps, type ShaderRef, type ShapeProps, type SketchStyle, SketchValidationError, type StepMark, type StrokeStyle, Text, TextCursor, type TextCursorProps, type TextMeasurer, type TextMetricsLite, type TextProps, TrackMatte, type TrackMatteProps, type TypeEdit, type TypewriterResult, Video, type VideoFrameSource, type VideoProps, type WordBox, type WrappedTextMetrics, __resetEstimateWarnings, applyToPoint, arcLength, assertFiniteFontSize, bindScene, breakLines, coercePathData, collapseReplacer, createDisplayListBuilder, createScene, drawOn, drawOnEach, each, echo, estimatingMeasurer, evaluate, filtersToCanvasFilter, flatten, fontString, fromTRS, getLayoutEngine, glow, hachureLines, highlight, invert, isEstimatingMeasurer, matEquals, measureWrappedText, meshRasterSize, motionBlur, multiply, pathFromSegs, quantize, rasterizeMesh, requireLayoutEngine, resolveAnchor, resolveSketch, revealSchedule, roughen, roundedRectSegs, segmentGraphemes, segmentWords, setDefaultMeasurer, setLayoutEngine, sketchStrokes, textCursor, trackMatte, typewriter, validateFilters, validateHachure, validateSketch, withDeterminismGuards };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { $ as multiply, A as __resetEstimateWarnings, B as setDefaultMeasurer, C as Node, F as isEstimatingMeasurer, G as glow, H as FilterValidationError, I as measureWrappedText, J as IDENTITY, K as validateFilters, L as quantize, M as breakLines, N as estimatingMeasurer, Q as matEquals, R as segmentGraphemes, S as validateSketch, T as resolveAnchor, U as createDisplayListBuilder, W as filtersToCanvasFilter, X as fromTRS, Y as applyToPoint, Z as invert,
|
|
1
|
+
import { $ as multiply, A as __resetEstimateWarnings, B as setDefaultMeasurer, C as Node, F as isEstimatingMeasurer, G as glow, H as FilterValidationError, I as measureWrappedText, J as IDENTITY, K as validateFilters, L as quantize, M as breakLines, N as estimatingMeasurer, Q as matEquals, R as segmentGraphemes, S as validateSketch, T as resolveAnchor, U as createDisplayListBuilder, W as filtersToCanvasFilter, X as fromTRS, Y as applyToPoint, Z as invert, a as Path, b as sketchStrokes, c as Video, d as revealSchedule, f as roundedRectSegs, g as hachureLines, h as flatten, i as ImageNode, j as assertFiniteFontSize, k as MEASURE_QUANTUM_PX, l as coercePathData, m as arcLength, n as Custom, o as Rect, p as SketchValidationError, q as collapseReplacer, r as Group, s as Text, t as Circle, u as pathFromSegs, v as resolveSketch, w as NodeConstructionError, x as validateHachure, y as roughen, z as segmentWords } from "./nodes.js";
|
|
2
2
|
import { a as evaluate, i as createScene, n as ReservedNodeIdError, r as bindScene, t as DuplicateNodeIdError } from "./scene.js";
|
|
3
3
|
import { i as setLayoutEngine, n as getLayoutEngine, r as requireLayoutEngine, t as LayoutEngineMissingError } from "./layoutEngine.js";
|
|
4
4
|
import { n as TextCursor, r as textCursor, t as typewriter } from "./typewriter.js";
|
|
5
5
|
import { i as echo, n as motionBlur, r as Echo, t as MotionBlur } from "./motionBlur.js";
|
|
6
|
-
import {
|
|
6
|
+
import { n as each, t as EachError } from "./each.js";
|
|
7
|
+
import { emitDevWarning, key, lerpColor, oklabToRgba, parseColor, rgbaToOklab, signal, stagger, track } from "@glissade/core";
|
|
7
8
|
//#region src/taxonomy.ts
|
|
8
9
|
/**
|
|
9
10
|
* The CLOSED node taxonomy (DESIGN.md §3.1): exactly nine built-in node TYPES.
|
|
@@ -108,171 +109,6 @@ function init(sig, v) {
|
|
|
108
109
|
return sig;
|
|
109
110
|
}
|
|
110
111
|
//#endregion
|
|
111
|
-
//#region src/each.ts
|
|
112
|
-
/**
|
|
113
|
-
* `each()` — deterministic parametric instancing (0.13 clip-tier sugar). Pure
|
|
114
|
-
* BUILD-TIME fan-out: it generates N scene nodes from a factory, lays them out
|
|
115
|
-
* in aspect-fraction space, and (optionally) staggers a motion `clip` across
|
|
116
|
-
* them — compiling to ordinary keyed `Track[]` plus a `Group` of children with
|
|
117
|
-
* stable `${id}/${i}` ids. Nothing executes at play time; the emitted tracks are
|
|
118
|
-
* byte-indistinguishable from hand-authored ones, so goldens hold by
|
|
119
|
-
* construction and every `--workers` export shard reconstructs the same id set.
|
|
120
|
-
*
|
|
121
|
-
* The clip runtime is imported TYPE-ONLY (the `Clip` instance the author passes
|
|
122
|
-
* carries its own `apply`), so `each` adds no clip bytes to the embed: the
|
|
123
|
-
* `@glissade/core/clips` runtime lands in the consumer's bundle, never scene's.
|
|
124
|
-
*/
|
|
125
|
-
var EachError = class extends Error {
|
|
126
|
-
constructor(message) {
|
|
127
|
-
super(message);
|
|
128
|
-
this.name = "EachError";
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
/**
|
|
132
|
-
* Fold a base seed and an index into a fresh per-clone seed. splitmix-style
|
|
133
|
-
* avalanche so adjacent indices decorrelate (a bare `seed + i` would hand
|
|
134
|
-
* near-identical streams to neighbours).
|
|
135
|
-
*/
|
|
136
|
-
function mix(seed, i) {
|
|
137
|
-
let h = (seed ^ Math.imul(i + 1, 2654435769)) >>> 0;
|
|
138
|
-
h = Math.imul(h ^ h >>> 16, 569420461);
|
|
139
|
-
h = Math.imul(h ^ h >>> 15, 1935289751);
|
|
140
|
-
return (h ^ h >>> 15) >>> 0;
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Salt folded into the motion-jitter seed so the per-index jitter rng
|
|
144
|
-
* decorrelates from `ctx.rng` (the factory rng). Both axes derive from
|
|
145
|
-
* `mix(baseSeed, i)`; without a distinct salt they would be the SAME stream,
|
|
146
|
-
* so a factory that draws from `ctx.rng` and a `jitter` callback would see
|
|
147
|
-
* correlated "independent" randomness. An arbitrary fixed odd constant.
|
|
148
|
-
*/
|
|
149
|
-
const JITTER_SALT = 1779033703;
|
|
150
|
-
/** Resolve a built-in layout (or call the escape-hatch fn) to a fraction. */
|
|
151
|
-
function placeAt(layout, i, n) {
|
|
152
|
-
if (typeof layout === "function") return layout(i, n);
|
|
153
|
-
switch (layout.kind) {
|
|
154
|
-
case "row": {
|
|
155
|
-
const gap = layout.gap ?? (n > 1 ? 1 / (n - 1) : 0);
|
|
156
|
-
const align = layout.align ?? .5;
|
|
157
|
-
const x0 = .5 - gap * (n - 1) / 2;
|
|
158
|
-
return [n === 1 ? .5 : x0 + gap * i, align];
|
|
159
|
-
}
|
|
160
|
-
case "column": {
|
|
161
|
-
const gap = layout.gap ?? (n > 1 ? 1 / (n - 1) : 0);
|
|
162
|
-
const align = layout.align ?? .5;
|
|
163
|
-
const y0 = .5 - gap * (n - 1) / 2;
|
|
164
|
-
return [align, n === 1 ? .5 : y0 + gap * i];
|
|
165
|
-
}
|
|
166
|
-
case "grid": {
|
|
167
|
-
const cols = layout.cols;
|
|
168
|
-
if (!(cols >= 1)) throw new EachError(`grid layout needs cols >= 1 (got ${cols})`);
|
|
169
|
-
const rows = layout.rows ?? Math.ceil(n / cols);
|
|
170
|
-
const order = layout.order ?? "row";
|
|
171
|
-
const col = order === "row" ? i % cols : Math.floor(i / rows);
|
|
172
|
-
const row = order === "row" ? Math.floor(i / cols) : i % rows;
|
|
173
|
-
const gapX = layout.gapX ?? (cols > 1 ? 1 / (cols - 1) : 0);
|
|
174
|
-
const gapY = layout.gapY ?? (rows > 1 ? 1 / (rows - 1) : 0);
|
|
175
|
-
const spanX = gapX * (cols - 1);
|
|
176
|
-
const spanY = gapY * (rows - 1);
|
|
177
|
-
return [cols === 1 ? .5 : .5 - spanX / 2 + gapX * col, rows === 1 ? .5 : .5 - spanY / 2 + gapY * row];
|
|
178
|
-
}
|
|
179
|
-
case "ring": {
|
|
180
|
-
const radius = layout.radius ?? .5;
|
|
181
|
-
const [cx, cy] = layout.center ?? [.5, .5];
|
|
182
|
-
const theta = (layout.startAngle ?? -Math.PI / 2) + (layout.sweep ?? Math.PI * 2) * (n === 0 ? 0 : i / n);
|
|
183
|
-
return [cx + radius * Math.cos(theta), cy + radius * Math.sin(theta)];
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
/** Compile a `distribute` mode + numeric gap into a stagger delay fn. */
|
|
188
|
-
function distributeFn(distribute, gap, n) {
|
|
189
|
-
const mid = (n - 1) / 2;
|
|
190
|
-
switch (distribute) {
|
|
191
|
-
case "from-center": return (i) => Math.abs(i - mid) * gap;
|
|
192
|
-
case "from-edges": return (i) => (mid - Math.abs(i - mid)) * gap;
|
|
193
|
-
case "delay": return (i) => i * gap;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
/** Resolve the motion's per-index delay into a plain `(i) => seconds` fn. */
|
|
197
|
-
function staggerFn(motion, n) {
|
|
198
|
-
const s = motion.stagger ?? 0;
|
|
199
|
-
if (typeof s === "function") return s;
|
|
200
|
-
return distributeFn(motion.distribute ?? "delay", s, n);
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Generate `n` clones from `factory`, lay them out, and (optionally) stagger a
|
|
204
|
-
* motion clip across them.
|
|
205
|
-
*
|
|
206
|
-
* const grid = each(9, (i) => new Rect({ width: 40, height: 40, fill: '#9ef0c0' }), {
|
|
207
|
-
* id: 'card',
|
|
208
|
-
* layout: { kind: 'grid', cols: 3 },
|
|
209
|
-
* box: { w: 600, h: 360 },
|
|
210
|
-
* motion: { clip: popIn(), stagger: 0.08, distribute: 'from-center' },
|
|
211
|
-
* });
|
|
212
|
-
* // scene children: [grid.node]; timeline tracks: [...grid.tracks]
|
|
213
|
-
*/
|
|
214
|
-
function each(n, factory, opts) {
|
|
215
|
-
if (!Number.isInteger(n) || n < 0) throw new EachError(`each() count must be a non-negative integer (got ${n})`);
|
|
216
|
-
const baseSeed = (opts.seed ?? hashStr(opts.id)) >>> 0;
|
|
217
|
-
const box = opts.box;
|
|
218
|
-
const [ox, oy] = box?.origin ?? [0, 0];
|
|
219
|
-
const children = [];
|
|
220
|
-
const places = [];
|
|
221
|
-
const seen = /* @__PURE__ */ new Set();
|
|
222
|
-
for (let i = 0; i < n; i++) {
|
|
223
|
-
const id = `${opts.id}/${i}`;
|
|
224
|
-
const frac = placeAt(opts.layout, i, n);
|
|
225
|
-
const ctx = {
|
|
226
|
-
i,
|
|
227
|
-
n,
|
|
228
|
-
id,
|
|
229
|
-
place: frac,
|
|
230
|
-
rng: random(mix(baseSeed, i)),
|
|
231
|
-
seed: baseSeed
|
|
232
|
-
};
|
|
233
|
-
const child = factory(i, ctx);
|
|
234
|
-
if (!(child instanceof Node)) throw new EachError(`each() factory must return a Node for index ${i} (got ${typeof child})`);
|
|
235
|
-
if (seen.has(child)) throw new EachError(`each() factory returned the same Node instance for index ${i} — the factory must construct a new node per index (it is called once per clone)`);
|
|
236
|
-
seen.add(child);
|
|
237
|
-
if (child.id === void 0) child.id = id;
|
|
238
|
-
else if (child.id !== id) throw new EachError(`each() factory set id '${child.id}' on index ${i}, but each owns the id namespace — leave it unset so it becomes '${id}'`);
|
|
239
|
-
children.push(child);
|
|
240
|
-
places.push(box ? {
|
|
241
|
-
frac,
|
|
242
|
-
px: [ox + frac[0] * box.w, oy + frac[1] * box.h]
|
|
243
|
-
} : { frac });
|
|
244
|
-
}
|
|
245
|
-
const node = new Group({
|
|
246
|
-
id: opts.id,
|
|
247
|
-
children
|
|
248
|
-
});
|
|
249
|
-
const tracks = [];
|
|
250
|
-
let end = opts.motion?.startSec ?? 0;
|
|
251
|
-
if (opts.motion) {
|
|
252
|
-
const m = opts.motion;
|
|
253
|
-
const start = m.startSec ?? 0;
|
|
254
|
-
const at = staggerFn(m, n);
|
|
255
|
-
for (let i = 0; i < n; i++) {
|
|
256
|
-
const rngI = random(mix(mix(baseSeed, i), JITTER_SALT));
|
|
257
|
-
const overrides = m.jitter?.(i, rngI, n);
|
|
258
|
-
const applyOpts = {
|
|
259
|
-
...overrides !== void 0 ? { overrides } : {},
|
|
260
|
-
...m.speed !== void 0 ? { speed: m.speed } : {}
|
|
261
|
-
};
|
|
262
|
-
const r = m.clip.apply(`${opts.id}/${i}`, start + at(i), applyOpts);
|
|
263
|
-
tracks.push(...r.tracks);
|
|
264
|
-
if (r.end > end) end = r.end;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
return {
|
|
268
|
-
node,
|
|
269
|
-
children,
|
|
270
|
-
tracks,
|
|
271
|
-
end,
|
|
272
|
-
places
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
//#endregion
|
|
276
112
|
//#region src/drawOn.ts
|
|
277
113
|
/**
|
|
278
114
|
* Whiteboard kit: one-call "draw this shape on" tracks. A stroked or sketched
|
|
@@ -388,84 +224,6 @@ const ALL_FILTER_KINDS = new Set([
|
|
|
388
224
|
"saturate"
|
|
389
225
|
]);
|
|
390
226
|
//#endregion
|
|
391
|
-
//#region src/fontUsage.ts
|
|
392
|
-
/**
|
|
393
|
-
* Scene → font-validation bridge (DESIGN.md §3.6). Core owns the AssetRef,
|
|
394
|
-
* FontRegistry, cmap reader, and the pure validation; this module owns the
|
|
395
|
-
* node-walk (which only `scene` can do) and the I/O seam that loads a font
|
|
396
|
-
* face's bytes so core stays DOM/Node-free.
|
|
397
|
-
*
|
|
398
|
-
* `collectTextUsages` walks every Text node and reads the FULL `.text()` (not
|
|
399
|
-
* the reveal-masked prefix) — coverage is a property of the authored content,
|
|
400
|
-
* independent of the playhead, so it stays out of the pure evaluate() path.
|
|
401
|
-
*/
|
|
402
|
-
/** Walk `scene` for Text nodes; one usage per node carrying its full text. */
|
|
403
|
-
function collectTextUsages(scene) {
|
|
404
|
-
const out = [];
|
|
405
|
-
const visit = (node) => {
|
|
406
|
-
if (node instanceof Text) {
|
|
407
|
-
const text = node.text();
|
|
408
|
-
if (text) out.push({
|
|
409
|
-
family: node.fontFamily,
|
|
410
|
-
text
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
if (node instanceof Group) for (const child of node.children) visit(child);
|
|
414
|
-
};
|
|
415
|
-
visit(scene.root);
|
|
416
|
-
return out;
|
|
417
|
-
}
|
|
418
|
-
/** The node-id of a track target ('<nodeId>/<prop.path>' → '<nodeId>'). */
|
|
419
|
-
function nodeIdOf(target) {
|
|
420
|
-
const slash = target.indexOf("/");
|
|
421
|
-
return slash >= 0 ? target.slice(0, slash) : target;
|
|
422
|
-
}
|
|
423
|
-
/**
|
|
424
|
-
* Collect font usages from the POST-localize document's STRING tracks (FIX 3,
|
|
425
|
-
* 0.14 canary). For every `'string'` track whose target node is a Text node,
|
|
426
|
-
* emit one usage per distinct localized KEY VALUE under that node's fontFamily —
|
|
427
|
-
* so a localized CJK message bound to a Latin-only font surfaces as an uncovered
|
|
428
|
-
* glyph. `collectTextUsages` only sees the authored BASE `node.text()`, which is
|
|
429
|
-
* resolved BEFORE the localized string tracks bind, so it misses this.
|
|
430
|
-
*/
|
|
431
|
-
function collectLocalizedTextUsages(scene, doc) {
|
|
432
|
-
const out = [];
|
|
433
|
-
for (const tr of doc.tracks) {
|
|
434
|
-
if (tr.type !== "string") continue;
|
|
435
|
-
const node = scene.nodes.get(nodeIdOf(tr.target));
|
|
436
|
-
if (!(node instanceof Text)) continue;
|
|
437
|
-
for (const k of tr.keys) {
|
|
438
|
-
const value = k.value;
|
|
439
|
-
if (typeof value === "string" && value) out.push({
|
|
440
|
-
family: node.fontFamily,
|
|
441
|
-
text: value
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
return out;
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* Run §3.6 font validation for a scene + its timeline document. Builds the
|
|
449
|
-
* registry from `doc.assets`, loads each registered face's cmap via `loadBytes`
|
|
450
|
-
* (once per family — the first face's URL is enough for coverage), and runs the
|
|
451
|
-
* pure `validateFonts`. Strict mode throws FontValidationError; dev warns.
|
|
452
|
-
*/
|
|
453
|
-
async function validateSceneFonts(scene, doc, loadBytes, options = {}) {
|
|
454
|
-
const mode = options.mode ?? "dev";
|
|
455
|
-
const registry = buildFontRegistry(doc.assets);
|
|
456
|
-
const usages = [...collectTextUsages(scene), ...options.extraUsages ?? []];
|
|
457
|
-
const wanted = /* @__PURE__ */ new Set();
|
|
458
|
-
for (const u of usages) if (registry.has(u.family)) for (const f of registry.fallbackChain(u.family)) wanted.add(f);
|
|
459
|
-
const cmaps = /* @__PURE__ */ new Map();
|
|
460
|
-
for (const family of wanted) {
|
|
461
|
-
const face = registry.resolveFace(family);
|
|
462
|
-
if (!face) continue;
|
|
463
|
-
const bytes = await loadBytes(face.url);
|
|
464
|
-
if (bytes) cmaps.set(family, parseCmap(bytes));
|
|
465
|
-
}
|
|
466
|
-
return validateFonts(usages, registry, cmaps, mode, { ...options.osFamilies !== void 0 ? { osFamilies: options.osFamilies } : {} });
|
|
467
|
-
}
|
|
468
|
-
//#endregion
|
|
469
227
|
//#region src/assets.ts
|
|
470
228
|
var ColdAssetError = class extends Error {
|
|
471
229
|
assetId;
|
|
@@ -1310,4 +1068,4 @@ var Raster2D = class {
|
|
|
1310
1068
|
}
|
|
1311
1069
|
};
|
|
1312
1070
|
//#endregion
|
|
1313
|
-
export { ALL_FILTER_KINDS, Circle, ColdAssetError, Custom, DeterminismViolationError, DuplicateNodeIdError, EachError, Echo, FilterValidationError, Group, Highlight, IDENTITY, ImageNode as Image, ImageNode, LayoutEngineMissingError, MEASURE_QUANTUM_PX, MESH_DOWNSCALE, MESH_SHEPARD_POWER, MESH_SIGMA, MotionBlur, NODE_TAXONOMY, Node, NodeConstructionError, Path, Raster2D, Rect, ReservedNodeIdError, ShaderEffect, SketchValidationError, Text, TextCursor, TrackMatte, Video, __resetEstimateWarnings, applyToPoint, arcLength, assertFiniteFontSize, bindScene, breakLines, coercePathData, collapseReplacer,
|
|
1071
|
+
export { ALL_FILTER_KINDS, Circle, ColdAssetError, Custom, DeterminismViolationError, DuplicateNodeIdError, EachError, Echo, FilterValidationError, Group, Highlight, IDENTITY, ImageNode as Image, ImageNode, LayoutEngineMissingError, MEASURE_QUANTUM_PX, MESH_DOWNSCALE, MESH_SHEPARD_POWER, MESH_SIGMA, MotionBlur, NODE_TAXONOMY, Node, NodeConstructionError, Path, Raster2D, Rect, ReservedNodeIdError, ShaderEffect, SketchValidationError, Text, TextCursor, TrackMatte, Video, __resetEstimateWarnings, applyToPoint, arcLength, assertFiniteFontSize, bindScene, breakLines, coercePathData, collapseReplacer, createDisplayListBuilder, createScene, drawOn, drawOnEach, each, echo, estimatingMeasurer, evaluate, filtersToCanvasFilter, flatten, fontString, fromTRS, getLayoutEngine, glow, hachureLines, highlight, invert, isEstimatingMeasurer, matEquals, measureWrappedText, meshRasterSize, motionBlur, multiply, pathFromSegs, quantize, rasterizeMesh, requireLayoutEngine, resolveAnchor, resolveSketch, revealSchedule, roughen, roundedRectSegs, segmentGraphemes, segmentWords, setDefaultMeasurer, setLayoutEngine, sketchStrokes, textCursor, trackMatte, typewriter, validateFilters, validateHachure, validateSketch, withDeterminismGuards };
|