@glissade/scene 0.56.0 → 0.57.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 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, CoverageReport, EaseSpec, FontMode, FontUsage, MeshPaint as MeshPaint$1, Rng, Timeline, Track } from "@glissade/core";
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 FontByteLoader, 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, type ValidateSceneFontsOptions, Video, type VideoFrameSource, type VideoProps, type WordBox, type WrappedTextMetrics, __resetEstimateWarnings, applyToPoint, arcLength, assertFiniteFontSize, bindScene, breakLines, coercePathData, collapseReplacer, collectLocalizedTextUsages, collectTextUsages, 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, validateSceneFonts, validateSketch, withDeterminismGuards };
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, _ as hashStr, 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";
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 { buildFontRegistry, emitDevWarning, key, lerpColor, oklabToRgba, parseCmap, parseColor, random, rgbaToOklab, signal, stagger, track, validateFonts } from "@glissade/core";
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, collectLocalizedTextUsages, collectTextUsages, 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, validateSceneFonts, validateSketch, withDeterminismGuards };
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 };