@glissade/scene 0.5.0-pre.5 → 0.5.0-pre.6
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 +4 -2
- package/dist/index.js +12 -4
- package/dist/layout.d.ts +1 -1
- package/dist/layout.js +1 -1
- package/dist/layoutEngine.d.ts +82 -2
- package/dist/layoutEngine.js +314 -2
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as BlendMode, A as SketchValidationError, B as HitArea, C as VideoProps, Ct as multiply, D as Polyline, E as roundedRectSegs, F as sketchStrokes, G as TextMeasurer, H as NodeProps, I as validateSketch, J as estimatingMeasurer, K as TextMetricsLite, L as AnchorSpec, M as flatten, N as resolveSketch, O as ResolvedSketch, P as roughen, Q as setDefaultMeasurer, R as BindablePropTarget, S as Video, St as matEquals, T as revealSchedule, U as PropInit, V as Node, W as resolveAnchor, X as segmentGraphemes, Y as quantize, Z as segmentWords, _ as Rect, _t as IDENTITY, a as LayoutEngineMissingError, at as FontSpec, b as Text, bt as fromTRS, c as requireLayoutEngine, ct as Rect$1, d as Group, dt as ShaderRef, et as DisplayList, f as ImageNode, ft as StrokeStyle, g as PathProps, gt as validateFilters, h as Path, ht as glow, i as LayoutEngine, it as FilterValidationError, j as arcLength, k as SketchStyle, l as setLayoutEngine, lt as Resource, m as LineBox, mt as filtersToCanvasFilter, n as LayoutChildSpec, nt as DrawCommand, ot as Paint, p as ImageProps, pt as createDisplayListBuilder, q as breakLines, r as LayoutContainerSpec, rt as FilterSpec, s as getLayoutEngine, st as PathSeg, t as LayoutBox, tt as DisplayListBuilder, u as Circle, ut as ResourceId, v as RevealMark, vt as Mat2x3, w as WordBox, x as TextProps, xt as invert, y as ShapeProps, yt as applyToPoint, z as EvalContext } from "./layoutEngine.js";
|
|
2
2
|
import { BindableSignal, BoundTimeline, CompiledTimeline, PathValue, Playhead, Timeline, Track, Vec2 } from "@glissade/core";
|
|
3
3
|
|
|
4
4
|
//#region src/highlight.d.ts
|
|
@@ -116,6 +116,7 @@ interface TypewriterResult {
|
|
|
116
116
|
declare function typewriter(target: string, edits: readonly TypeEdit[], opts?: {
|
|
117
117
|
start?: number;
|
|
118
118
|
perChar?: number;
|
|
119
|
+
gap?: number;
|
|
119
120
|
}): TypewriterResult;
|
|
120
121
|
//#endregion
|
|
121
122
|
//#region src/motionPath.d.ts
|
|
@@ -306,6 +307,7 @@ interface Ctx2DLike<TPath, TDrawable> {
|
|
|
306
307
|
drawImage(image: TDrawable, x: number, y: number, w?: number, h?: number): void;
|
|
307
308
|
drawImage(image: TDrawable, sx: number, sy: number, sw: number, sh: number, x: number, y: number, w: number, h: number): void;
|
|
308
309
|
setLineDash(segments: number[]): void;
|
|
310
|
+
lineDashOffset: number;
|
|
309
311
|
fillStyle: unknown;
|
|
310
312
|
strokeStyle: unknown;
|
|
311
313
|
lineWidth: number;
|
|
@@ -415,4 +417,4 @@ declare function bindScene(scene: Scene, doc: Timeline): BindingCacheEntry;
|
|
|
415
417
|
*/
|
|
416
418
|
declare function evaluate(scene: Scene, doc: Timeline, t: number): DisplayList;
|
|
417
419
|
//#endregion
|
|
418
|
-
export { type AnchorSpec, type BindablePropTarget, type BlendMode, type CanvasLike, Circle, ColdAssetError, type Ctx2DLike, type DisplayList, type DisplayListBuilder, type DrawCommand, DuplicateNodeIdError, type EditMark, type EvalContext, type FilterSpec, FilterValidationError, FollowPath, type FollowPathProps, type FontSpec, Group, Highlight, type HighlightProps, type HitArea, IDENTITY, type ImageHandle, ImageNode, type ImageProps, type LayoutBox, type LayoutChildSpec, type LayoutContainerSpec, type LayoutEngine, LayoutEngineMissingError, type LineBox, type Mat2x3, Node, type NodeProps, type Paint, Path, type PathLike, type PathProps, type PathSampler, type PathSeg, type PropInit, Raster2D, type Raster2DHost, Rect, type Rect$1 as RectShape, type Resource, type ResourceId, type RevealMark, type Scene, type SceneInit, type SceneModule, type ShaderCaps, ShaderEffect, type ShaderEffectProps, type ShaderRef, type ShapeProps, type StepMark, type StrokeStyle, Text, TextCursor, type TextCursorProps, type TextMeasurer, type TextMetricsLite, type TextProps, TokenHighlight, type TokenHighlightProps, TokenMatchError, type TokenRange, type TypeEdit, type TypewriterResult, Video, type VideoFrameSource, type VideoProps, type WordBox, applyToPoint, bindScene, breakLines, createDisplayListBuilder, createScene, estimatingMeasurer, evaluate, filtersToCanvasFilter, followPath, fontString, fromTRS, getLayoutEngine, glow, highlight, invert, matEquals, matchTokenRun, motionPath, multiply, pathLength, pointAtLength, quantize, requireLayoutEngine, resolveAnchor, revealSchedule, roundedRectSegs, segmentGraphemes, segmentWords, setDefaultMeasurer, setLayoutEngine, textCursor, tokenHighlight, typewriter, validateFilters };
|
|
420
|
+
export { type AnchorSpec, type BindablePropTarget, type BlendMode, type CanvasLike, Circle, ColdAssetError, type Ctx2DLike, type DisplayList, type DisplayListBuilder, type DrawCommand, DuplicateNodeIdError, type EditMark, type EvalContext, type FilterSpec, FilterValidationError, FollowPath, type FollowPathProps, type FontSpec, Group, Highlight, type HighlightProps, type HitArea, IDENTITY, type ImageHandle, ImageNode, type ImageProps, type LayoutBox, type LayoutChildSpec, type LayoutContainerSpec, type LayoutEngine, LayoutEngineMissingError, type LineBox, type Mat2x3, Node, type NodeProps, type Paint, Path, type PathLike, type PathProps, type PathSampler, type PathSeg, type Polyline, type PropInit, Raster2D, type Raster2DHost, Rect, type Rect$1 as RectShape, 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, TokenHighlight, type TokenHighlightProps, TokenMatchError, type TokenRange, type TypeEdit, type TypewriterResult, Video, type VideoFrameSource, type VideoProps, type WordBox, applyToPoint, arcLength, bindScene, breakLines, createDisplayListBuilder, createScene, estimatingMeasurer, evaluate, filtersToCanvasFilter, flatten, followPath, fontString, fromTRS, getLayoutEngine, glow, highlight, invert, matEquals, matchTokenRun, motionPath, multiply, pathLength, pointAtLength, quantize, requireLayoutEngine, resolveAnchor, resolveSketch, revealSchedule, roughen, roundedRectSegs, segmentGraphemes, segmentWords, setDefaultMeasurer, setLayoutEngine, sketchStrokes, textCursor, tokenHighlight, typewriter, validateFilters, validateSketch };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as FilterValidationError, B as multiply, C as breakLines, D as segmentGraphemes, E as quantize, F as IDENTITY, I as applyToPoint, L as fromTRS, M as filtersToCanvasFilter, N as glow, O as segmentWords, P as validateFilters, R as invert, S as resolveAnchor, T as fallbackMeasurer, _ as resolveSketch, a as Circle, b as validateSketch, c as Path, d as Video, f as revealSchedule, g as flatten, h as arcLength, i as setLayoutEngine, j as createDisplayListBuilder, k as setDefaultMeasurer, l as Rect, m as SketchValidationError, n as getLayoutEngine, o as Group, p as roundedRectSegs, r as requireLayoutEngine, s as ImageNode, t as LayoutEngineMissingError, u as Text, v as roughen, w as estimatingMeasurer, x as Node, y as sketchStrokes, z as matEquals } from "./layoutEngine.js";
|
|
2
2
|
import { bindTimeline, compileTimeline, createPlayhead, emitDevWarning, evaluateAt, key, signal, track, vec2Signal } from "@glissade/core";
|
|
3
3
|
//#region src/highlight.ts
|
|
4
4
|
/**
|
|
@@ -181,6 +181,7 @@ const DEFAULT_PER_CHAR = .06;
|
|
|
181
181
|
function typewriter(target, edits, opts = {}) {
|
|
182
182
|
const start = opts.start ?? 0;
|
|
183
183
|
const globalPer = opts.perChar ?? DEFAULT_PER_CHAR;
|
|
184
|
+
const gap = opts.gap ?? 0;
|
|
184
185
|
let t = start;
|
|
185
186
|
const shown = [];
|
|
186
187
|
const keys = [key(start, "", { interp: "hold" })];
|
|
@@ -221,6 +222,7 @@ function typewriter(target, edits, opts = {}) {
|
|
|
221
222
|
end: t,
|
|
222
223
|
value: shown.join("")
|
|
223
224
|
});
|
|
225
|
+
if (gap > 0 && ei < edits.length - 1) t += gap;
|
|
224
226
|
}
|
|
225
227
|
return {
|
|
226
228
|
track: track(target, "string", keys),
|
|
@@ -859,9 +861,15 @@ var Raster2D = class {
|
|
|
859
861
|
ctx.lineWidth = cmd.stroke.width;
|
|
860
862
|
ctx.lineCap = cmd.stroke.cap ?? "butt";
|
|
861
863
|
ctx.lineJoin = cmd.stroke.join ?? "miter";
|
|
862
|
-
if (cmd.stroke.dash)
|
|
864
|
+
if (cmd.stroke.dash) {
|
|
865
|
+
ctx.setLineDash(cmd.stroke.dash);
|
|
866
|
+
ctx.lineDashOffset = cmd.stroke.dashOffset ?? 0;
|
|
867
|
+
}
|
|
863
868
|
ctx.stroke(this.path(list.resources, cmd.path));
|
|
864
|
-
if (cmd.stroke.dash)
|
|
869
|
+
if (cmd.stroke.dash) {
|
|
870
|
+
ctx.setLineDash([]);
|
|
871
|
+
ctx.lineDashOffset = 0;
|
|
872
|
+
}
|
|
865
873
|
const b = this.pathBounds(list.resources, cmd.path);
|
|
866
874
|
if (b) {
|
|
867
875
|
const o = cmd.stroke.width * ((cmd.stroke.join ?? "miter") === "miter" ? 5 : 1);
|
|
@@ -1071,4 +1079,4 @@ function evaluate(scene, doc, t) {
|
|
|
1071
1079
|
});
|
|
1072
1080
|
}
|
|
1073
1081
|
//#endregion
|
|
1074
|
-
export { Circle, ColdAssetError, DuplicateNodeIdError, FilterValidationError, FollowPath, Group, Highlight, IDENTITY, ImageNode, LayoutEngineMissingError, Node, Path, Raster2D, Rect, ShaderEffect, Text, TextCursor, TokenHighlight, TokenMatchError, Video, applyToPoint, bindScene, breakLines, createDisplayListBuilder, createScene, estimatingMeasurer, evaluate, filtersToCanvasFilter, followPath, fontString, fromTRS, getLayoutEngine, glow, highlight, invert, matEquals, matchTokenRun, motionPath, multiply, pathLength, pointAtLength, quantize, requireLayoutEngine, resolveAnchor, revealSchedule, roundedRectSegs, segmentGraphemes, segmentWords, setDefaultMeasurer, setLayoutEngine, textCursor, tokenHighlight, typewriter, validateFilters };
|
|
1082
|
+
export { Circle, ColdAssetError, DuplicateNodeIdError, FilterValidationError, FollowPath, Group, Highlight, IDENTITY, ImageNode, LayoutEngineMissingError, Node, Path, Raster2D, Rect, ShaderEffect, SketchValidationError, Text, TextCursor, TokenHighlight, TokenMatchError, Video, applyToPoint, arcLength, bindScene, breakLines, createDisplayListBuilder, createScene, estimatingMeasurer, evaluate, filtersToCanvasFilter, flatten, followPath, fontString, fromTRS, getLayoutEngine, glow, highlight, invert, matEquals, matchTokenRun, motionPath, multiply, pathLength, pointAtLength, quantize, requireLayoutEngine, resolveAnchor, resolveSketch, revealSchedule, roughen, roundedRectSegs, segmentGraphemes, segmentWords, setDefaultMeasurer, setLayoutEngine, sketchStrokes, textCursor, tokenHighlight, typewriter, validateFilters, validateSketch };
|
package/dist/layout.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { G as TextMeasurer, H as NodeProps, U as PropInit, V as Node, a as LayoutEngineMissingError, d as Group, i as LayoutEngine, l as setLayoutEngine, n as LayoutChildSpec, o as LayoutResult, r as LayoutContainerSpec, s as getLayoutEngine, t as LayoutBox, tt as DisplayListBuilder, z as EvalContext } from "./layoutEngine.js";
|
|
2
2
|
import { BindableSignal } from "@glissade/core";
|
|
3
3
|
|
|
4
4
|
//#region src/layout.d.ts
|
package/dist/layout.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as setLayoutEngine, n as getLayoutEngine, o as Group, r as requireLayoutEngine, t as LayoutEngineMissingError
|
|
1
|
+
import { T as fallbackMeasurer, i as setLayoutEngine, n as getLayoutEngine, o as Group, r as requireLayoutEngine, t as LayoutEngineMissingError } from "./layoutEngine.js";
|
|
2
2
|
import { signal } from "@glissade/core";
|
|
3
3
|
//#region src/layout.ts
|
|
4
4
|
/**
|
package/dist/layoutEngine.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BindableSignal, PathValue, ReadonlySignal, Track, Vec2, Vec2Signal } from "@glissade/core";
|
|
1
|
+
import { BindableSignal, PathValue, ReadonlySignal, Rng, Track, Vec2, Vec2Signal } from "@glissade/core";
|
|
2
2
|
|
|
3
3
|
//#region src/matrix.d.ts
|
|
4
4
|
|
|
@@ -351,6 +351,72 @@ declare abstract class Node {
|
|
|
351
351
|
emit(out: DisplayListBuilder, ctx: EvalContext): void;
|
|
352
352
|
}
|
|
353
353
|
//#endregion
|
|
354
|
+
//#region src/sketch.d.ts
|
|
355
|
+
/** The closed set of hand-drawn looks. Mirrors FilterSpec's discipline. */
|
|
356
|
+
type SketchStyle = {
|
|
357
|
+
kind: 'marker';
|
|
358
|
+
width?: number;
|
|
359
|
+
roughness?: number;
|
|
360
|
+
} | {
|
|
361
|
+
kind: 'crayon';
|
|
362
|
+
width?: number;
|
|
363
|
+
roughness?: number;
|
|
364
|
+
passes?: number;
|
|
365
|
+
} | {
|
|
366
|
+
kind: 'pencil';
|
|
367
|
+
width?: number;
|
|
368
|
+
roughness?: number;
|
|
369
|
+
passes?: number;
|
|
370
|
+
} | {
|
|
371
|
+
kind: 'ink';
|
|
372
|
+
width?: number;
|
|
373
|
+
roughness?: number;
|
|
374
|
+
} | {
|
|
375
|
+
kind: 'chalk';
|
|
376
|
+
width?: number;
|
|
377
|
+
roughness?: number;
|
|
378
|
+
dash?: number[];
|
|
379
|
+
};
|
|
380
|
+
declare class SketchValidationError extends Error {
|
|
381
|
+
constructor(message: string);
|
|
382
|
+
}
|
|
383
|
+
/** Reject unknown kinds / out-of-range params at construction (like validateFilters). */
|
|
384
|
+
declare function validateSketch(s: SketchStyle): void;
|
|
385
|
+
interface ResolvedSketch {
|
|
386
|
+
width: number;
|
|
387
|
+
roughness: number;
|
|
388
|
+
passes: number;
|
|
389
|
+
dash?: number[];
|
|
390
|
+
}
|
|
391
|
+
/** Per-kind defaults — the character of each look. */
|
|
392
|
+
declare function resolveSketch(s: SketchStyle): ResolvedSketch;
|
|
393
|
+
interface Polyline {
|
|
394
|
+
points: [number, number][];
|
|
395
|
+
closed: boolean;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Flatten a path to polylines — de Casteljau for C/Q, arc sampling for E
|
|
399
|
+
* (Circle and rounded-rect corners are 'E' segments, so this MUST handle them
|
|
400
|
+
* or those shapes roughen wrong). `steps` is the samples per curved segment.
|
|
401
|
+
*/
|
|
402
|
+
declare function flatten(segs: readonly PathSeg[], steps?: number): Polyline[];
|
|
403
|
+
/** Total length of a flattened polyline (for draw-on dashing). */
|
|
404
|
+
declare function arcLength(poly: Polyline): number;
|
|
405
|
+
/**
|
|
406
|
+
* Roughen a path into hand-drawn stroke passes. Each segment becomes a bowed,
|
|
407
|
+
* jittered quadratic; `passes` overlay slightly different jitters for the
|
|
408
|
+
* built-up look. `rng` must be a freshly seeded generator (the caller reseeds
|
|
409
|
+
* per draw from a stable seed, so evaluate() stays pure).
|
|
410
|
+
*/
|
|
411
|
+
declare function roughen(segs: readonly PathSeg[], style: SketchStyle, rng: Rng): {
|
|
412
|
+
strokes: PathSeg[][];
|
|
413
|
+
resolved: ResolvedSketch;
|
|
414
|
+
};
|
|
415
|
+
/** FNV-1a 32-bit — a stable per-shape sketch seed from its id. */
|
|
416
|
+
|
|
417
|
+
/** Convenience: the rough stroke passes for a path at a given seed. */
|
|
418
|
+
declare function sketchStrokes(segs: readonly PathSeg[], style: SketchStyle, seed: number): PathSeg[][];
|
|
419
|
+
//#endregion
|
|
354
420
|
//#region src/nodes.d.ts
|
|
355
421
|
/** Rounded-rect path segments — Rect's outline, shared with Highlight. */
|
|
356
422
|
declare function roundedRectSegs(x: number, y: number, w: number, h: number, r: number): PathSeg[];
|
|
@@ -366,14 +432,28 @@ interface ShapeProps extends NodeProps {
|
|
|
366
432
|
fill?: PropInit<string>;
|
|
367
433
|
stroke?: PropInit<string>;
|
|
368
434
|
strokeWidth?: PropInit<number>;
|
|
435
|
+
/** hand-drawn look: the outline is geometrically roughened (see sketch.ts) */
|
|
436
|
+
sketch?: SketchStyle;
|
|
437
|
+
/** seed for the roughening; default a stable hash of the node id */
|
|
438
|
+
sketchSeed?: number;
|
|
439
|
+
/** draw-on for a sketched shape: 0..1 of the outline drawn (default 1 = whole).
|
|
440
|
+
* Track `<id>/reveal`. Precise for single-contour shapes; multi-contour ones
|
|
441
|
+
* reveal each contour in parallel. */
|
|
442
|
+
reveal?: PropInit<number>;
|
|
369
443
|
}
|
|
370
444
|
declare abstract class Shape extends Node {
|
|
371
445
|
readonly fill: BindableSignal<string>;
|
|
372
446
|
readonly stroke: BindableSignal<string>;
|
|
373
447
|
readonly strokeWidth: BindableSignal<number>;
|
|
448
|
+
readonly sketch: SketchStyle | undefined;
|
|
449
|
+
readonly sketchSeed: number;
|
|
450
|
+
readonly reveal: BindableSignal<number>;
|
|
374
451
|
constructor(props?: ShapeProps);
|
|
375
452
|
protected abstract pathSegs(): PathSeg[];
|
|
376
453
|
protected draw(out: DisplayListBuilder): void;
|
|
454
|
+
/** Hand-drawn render: solid fill (if any) under roughened, multi-pass strokes.
|
|
455
|
+
* The seed is consumed fresh each draw, so re-evaluation is byte-identical. */
|
|
456
|
+
private drawSketch;
|
|
377
457
|
}
|
|
378
458
|
declare class Rect extends Shape {
|
|
379
459
|
readonly width: BindableSignal<number>;
|
|
@@ -673,4 +753,4 @@ declare function setLayoutEngine(e: LayoutEngine): void;
|
|
|
673
753
|
declare function getLayoutEngine(): LayoutEngine | null;
|
|
674
754
|
declare function requireLayoutEngine(): LayoutEngine;
|
|
675
755
|
//#endregion
|
|
676
|
-
export {
|
|
756
|
+
export { BlendMode as $, SketchValidationError as A, HitArea as B, VideoProps as C, multiply as Ct, Polyline as D, roundedRectSegs as E, sketchStrokes as F, TextMeasurer as G, NodeProps as H, validateSketch as I, estimatingMeasurer as J, TextMetricsLite as K, AnchorSpec as L, flatten as M, resolveSketch as N, ResolvedSketch as O, roughen as P, setDefaultMeasurer as Q, BindablePropTarget as R, Video as S, matEquals as St, revealSchedule as T, PropInit as U, Node as V, resolveAnchor as W, segmentGraphemes as X, quantize as Y, segmentWords as Z, Rect as _, IDENTITY as _t, LayoutEngineMissingError as a, FontSpec as at, Text as b, fromTRS as bt, requireLayoutEngine as c, Rect$1 as ct, Group as d, ShaderRef as dt, DisplayList as et, ImageNode as f, StrokeStyle as ft, PathProps as g, validateFilters as gt, Path as h, glow as ht, LayoutEngine as i, FilterValidationError as it, arcLength as j, SketchStyle as k, setLayoutEngine as l, Resource as lt, LineBox as m, filtersToCanvasFilter as mt, LayoutChildSpec as n, DrawCommand as nt, LayoutResult as o, Paint as ot, ImageProps as p, createDisplayListBuilder as pt, breakLines as q, LayoutContainerSpec as r, FilterSpec as rt, getLayoutEngine as s, PathSeg as st, LayoutBox as t, DisplayListBuilder as tt, Circle as u, ResourceId as ut, RevealMark as v, Mat2x3 as vt, WordBox as w, TextProps as x, invert as xt, ShapeProps as y, applyToPoint as yt, EvalContext as z };
|
package/dist/layoutEngine.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TARGET_PATH, computed, emitDevWarning, signal, vec2Signal } from "@glissade/core";
|
|
1
|
+
import { TARGET_PATH, computed, emitDevWarning, random, signal, vec2Signal } from "@glissade/core";
|
|
2
2
|
//#region src/matrix.ts
|
|
3
3
|
const IDENTITY = [
|
|
4
4
|
1,
|
|
@@ -437,6 +437,230 @@ var Node = class {
|
|
|
437
437
|
}
|
|
438
438
|
};
|
|
439
439
|
//#endregion
|
|
440
|
+
//#region src/sketch.ts
|
|
441
|
+
/**
|
|
442
|
+
* Hand-drawn stroke styles via GEOMETRIC roughening — not raster textures. A
|
|
443
|
+
* shape's outline is flattened to polylines, then each segment is redrawn as a
|
|
444
|
+
* slightly jittered, bowed stroke, overlaid in a few passes. Because it's pure
|
|
445
|
+
* path math seeded by a stable per-shape seed, the result is byte-identical on
|
|
446
|
+
* both backends and re-evaluates deterministically (the seed is consumed fresh
|
|
447
|
+
* each draw, never as a shared stateful stream).
|
|
448
|
+
*/
|
|
449
|
+
const KINDS = [
|
|
450
|
+
"marker",
|
|
451
|
+
"crayon",
|
|
452
|
+
"pencil",
|
|
453
|
+
"ink",
|
|
454
|
+
"chalk"
|
|
455
|
+
];
|
|
456
|
+
var SketchValidationError = class extends Error {
|
|
457
|
+
constructor(message) {
|
|
458
|
+
super(message);
|
|
459
|
+
this.name = "SketchValidationError";
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
/** Reject unknown kinds / out-of-range params at construction (like validateFilters). */
|
|
463
|
+
function validateSketch(s) {
|
|
464
|
+
if (!KINDS.includes(s.kind)) throw new SketchValidationError(`unknown sketch kind '${String(s.kind)}' (have: ${KINDS.join(", ")})`);
|
|
465
|
+
if (s.width !== void 0 && !(s.width > 0)) throw new SketchValidationError(`sketch width must be > 0, got ${String(s.width)}`);
|
|
466
|
+
if (s.roughness !== void 0 && !(s.roughness >= 0)) throw new SketchValidationError(`sketch roughness must be ≥ 0, got ${String(s.roughness)}`);
|
|
467
|
+
if ((s.kind === "crayon" || s.kind === "pencil") && s.passes !== void 0 && !(s.passes >= 1)) throw new SketchValidationError(`sketch passes must be ≥ 1, got ${String(s.passes)}`);
|
|
468
|
+
if (s.kind === "chalk" && s.dash !== void 0 && (!Array.isArray(s.dash) || s.dash.some((d) => !(d >= 0)))) throw new SketchValidationError("sketch chalk dash must be an array of non-negative numbers");
|
|
469
|
+
}
|
|
470
|
+
/** Per-kind defaults — the character of each look. */
|
|
471
|
+
function resolveSketch(s) {
|
|
472
|
+
switch (s.kind) {
|
|
473
|
+
case "marker": return {
|
|
474
|
+
width: s.width ?? 8,
|
|
475
|
+
roughness: s.roughness ?? 1.2,
|
|
476
|
+
passes: 2
|
|
477
|
+
};
|
|
478
|
+
case "crayon": return {
|
|
479
|
+
width: s.width ?? 4,
|
|
480
|
+
roughness: s.roughness ?? 2.4,
|
|
481
|
+
passes: s.passes ?? 3
|
|
482
|
+
};
|
|
483
|
+
case "pencil": return {
|
|
484
|
+
width: s.width ?? 1.5,
|
|
485
|
+
roughness: s.roughness ?? 1,
|
|
486
|
+
passes: s.passes ?? 2
|
|
487
|
+
};
|
|
488
|
+
case "ink": return {
|
|
489
|
+
width: s.width ?? 2.5,
|
|
490
|
+
roughness: s.roughness ?? .8,
|
|
491
|
+
passes: 1
|
|
492
|
+
};
|
|
493
|
+
case "chalk": return {
|
|
494
|
+
width: s.width ?? 3,
|
|
495
|
+
roughness: s.roughness ?? 1.6,
|
|
496
|
+
passes: 1,
|
|
497
|
+
dash: s.dash ?? [6, 5]
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
const cubic = (p0, c1, c2, p1, t) => {
|
|
502
|
+
const mt = 1 - t;
|
|
503
|
+
const a = mt * mt * mt;
|
|
504
|
+
const b = 3 * mt * mt * t;
|
|
505
|
+
const c = 3 * mt * t * t;
|
|
506
|
+
const d = t * t * t;
|
|
507
|
+
return [a * p0[0] + b * c1[0] + c * c2[0] + d * p1[0], a * p0[1] + b * c1[1] + c * c2[1] + d * p1[1]];
|
|
508
|
+
};
|
|
509
|
+
const quad = (p0, c, p1, t) => {
|
|
510
|
+
const mt = 1 - t;
|
|
511
|
+
return [mt * mt * p0[0] + 2 * mt * t * c[0] + t * t * p1[0], mt * mt * p0[1] + 2 * mt * t * c[1] + t * t * p1[1]];
|
|
512
|
+
};
|
|
513
|
+
const ellipse = (cx, cy, rx, ry, rot, ang) => {
|
|
514
|
+
const ex = rx * Math.cos(ang);
|
|
515
|
+
const ey = ry * Math.sin(ang);
|
|
516
|
+
const cos = Math.cos(rot);
|
|
517
|
+
const sin = Math.sin(rot);
|
|
518
|
+
return [cx + ex * cos - ey * sin, cy + ex * sin + ey * cos];
|
|
519
|
+
};
|
|
520
|
+
/**
|
|
521
|
+
* Flatten a path to polylines — de Casteljau for C/Q, arc sampling for E
|
|
522
|
+
* (Circle and rounded-rect corners are 'E' segments, so this MUST handle them
|
|
523
|
+
* or those shapes roughen wrong). `steps` is the samples per curved segment.
|
|
524
|
+
*/
|
|
525
|
+
function flatten(segs, steps = 16) {
|
|
526
|
+
const polys = [];
|
|
527
|
+
let cur = null;
|
|
528
|
+
let px = 0;
|
|
529
|
+
let py = 0;
|
|
530
|
+
let sx = 0;
|
|
531
|
+
let sy = 0;
|
|
532
|
+
const ensure = (x, y) => {
|
|
533
|
+
if (!cur) {
|
|
534
|
+
cur = {
|
|
535
|
+
points: [[x, y]],
|
|
536
|
+
closed: false
|
|
537
|
+
};
|
|
538
|
+
polys.push(cur);
|
|
539
|
+
sx = x;
|
|
540
|
+
sy = y;
|
|
541
|
+
}
|
|
542
|
+
return cur;
|
|
543
|
+
};
|
|
544
|
+
for (const s of segs) switch (s[0]) {
|
|
545
|
+
case "M":
|
|
546
|
+
cur = {
|
|
547
|
+
points: [[s[1], s[2]]],
|
|
548
|
+
closed: false
|
|
549
|
+
};
|
|
550
|
+
polys.push(cur);
|
|
551
|
+
px = sx = s[1];
|
|
552
|
+
py = sy = s[2];
|
|
553
|
+
break;
|
|
554
|
+
case "L":
|
|
555
|
+
ensure(px, py).points.push([s[1], s[2]]);
|
|
556
|
+
px = s[1];
|
|
557
|
+
py = s[2];
|
|
558
|
+
break;
|
|
559
|
+
case "C": {
|
|
560
|
+
const c = ensure(px, py);
|
|
561
|
+
for (let k = 1; k <= steps; k++) c.points.push(cubic([px, py], [s[1], s[2]], [s[3], s[4]], [s[5], s[6]], k / steps));
|
|
562
|
+
px = s[5];
|
|
563
|
+
py = s[6];
|
|
564
|
+
break;
|
|
565
|
+
}
|
|
566
|
+
case "Q": {
|
|
567
|
+
const c = ensure(px, py);
|
|
568
|
+
for (let k = 1; k <= steps; k++) c.points.push(quad([px, py], [s[1], s[2]], [s[3], s[4]], k / steps));
|
|
569
|
+
px = s[3];
|
|
570
|
+
py = s[4];
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
case "E": {
|
|
574
|
+
const [, cx, cy, rx, ry, rot, a0, a1] = s;
|
|
575
|
+
const begin = ellipse(cx, cy, rx, ry, rot, a0);
|
|
576
|
+
const c = ensure(begin[0], begin[1]);
|
|
577
|
+
for (let k = 1; k <= steps; k++) c.points.push(ellipse(cx, cy, rx, ry, rot, a0 + (a1 - a0) * (k / steps)));
|
|
578
|
+
const end = ellipse(cx, cy, rx, ry, rot, a1);
|
|
579
|
+
px = end[0];
|
|
580
|
+
py = end[1];
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
case "Z":
|
|
584
|
+
if (cur) {
|
|
585
|
+
cur.points.push([sx, sy]);
|
|
586
|
+
cur.closed = true;
|
|
587
|
+
px = sx;
|
|
588
|
+
py = sy;
|
|
589
|
+
}
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
return polys.filter((p) => p.points.length > 0);
|
|
593
|
+
}
|
|
594
|
+
/** Total length of a flattened polyline (for draw-on dashing). */
|
|
595
|
+
function arcLength(poly) {
|
|
596
|
+
let len = 0;
|
|
597
|
+
for (let i = 1; i < poly.points.length; i++) len += Math.hypot(poly.points[i][0] - poly.points[i - 1][0], poly.points[i][1] - poly.points[i - 1][1]);
|
|
598
|
+
return len;
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Roughen a path into hand-drawn stroke passes. Each segment becomes a bowed,
|
|
602
|
+
* jittered quadratic; `passes` overlay slightly different jitters for the
|
|
603
|
+
* built-up look. `rng` must be a freshly seeded generator (the caller reseeds
|
|
604
|
+
* per draw from a stable seed, so evaluate() stays pure).
|
|
605
|
+
*/
|
|
606
|
+
function roughen(segs, style, rng) {
|
|
607
|
+
const resolved = resolveSketch(style);
|
|
608
|
+
const polys = flatten(segs);
|
|
609
|
+
const jit = () => (rng() * 2 - 1) * resolved.roughness;
|
|
610
|
+
const strokes = [];
|
|
611
|
+
for (let pass = 0; pass < resolved.passes; pass++) {
|
|
612
|
+
const out = [];
|
|
613
|
+
for (const poly of polys) {
|
|
614
|
+
const pts = poly.points;
|
|
615
|
+
if (pts.length < 2) continue;
|
|
616
|
+
let ax = pts[0][0] + jit();
|
|
617
|
+
let ay = pts[0][1] + jit();
|
|
618
|
+
out.push([
|
|
619
|
+
"M",
|
|
620
|
+
ax,
|
|
621
|
+
ay
|
|
622
|
+
]);
|
|
623
|
+
for (let i = 1; i < pts.length; i++) {
|
|
624
|
+
const bx = pts[i][0] + jit();
|
|
625
|
+
const by = pts[i][1] + jit();
|
|
626
|
+
const dx = bx - ax;
|
|
627
|
+
const dy = by - ay;
|
|
628
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
629
|
+
const bow = jit() * .5;
|
|
630
|
+
const mx = (ax + bx) / 2 + -dy / len * bow;
|
|
631
|
+
const my = (ay + by) / 2 + dx / len * bow;
|
|
632
|
+
out.push([
|
|
633
|
+
"Q",
|
|
634
|
+
mx,
|
|
635
|
+
my,
|
|
636
|
+
bx,
|
|
637
|
+
by
|
|
638
|
+
]);
|
|
639
|
+
ax = bx;
|
|
640
|
+
ay = by;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
strokes.push(out);
|
|
644
|
+
}
|
|
645
|
+
return {
|
|
646
|
+
strokes,
|
|
647
|
+
resolved
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
/** FNV-1a 32-bit — a stable per-shape sketch seed from its id. */
|
|
651
|
+
function hashStr(s) {
|
|
652
|
+
let h = 2166136261;
|
|
653
|
+
for (let i = 0; i < s.length; i++) {
|
|
654
|
+
h ^= s.charCodeAt(i);
|
|
655
|
+
h = Math.imul(h, 16777619);
|
|
656
|
+
}
|
|
657
|
+
return h >>> 0;
|
|
658
|
+
}
|
|
659
|
+
/** Convenience: the rough stroke passes for a path at a given seed. */
|
|
660
|
+
function sketchStrokes(segs, style, seed) {
|
|
661
|
+
return roughen(segs, style, random(seed >>> 0)).strokes;
|
|
662
|
+
}
|
|
663
|
+
//#endregion
|
|
440
664
|
//#region src/nodes.ts
|
|
441
665
|
/**
|
|
442
666
|
* Built-in nodes for M1 (DESIGN.md §3.1): Group, Rect, Circle, Text.
|
|
@@ -561,17 +785,26 @@ var Shape = class extends Node {
|
|
|
561
785
|
fill;
|
|
562
786
|
stroke;
|
|
563
787
|
strokeWidth;
|
|
788
|
+
sketch;
|
|
789
|
+
sketchSeed;
|
|
790
|
+
reveal;
|
|
564
791
|
constructor(props = {}) {
|
|
565
792
|
super(props);
|
|
566
793
|
this.fill = initProp(signal(""), props.fill);
|
|
567
794
|
this.stroke = initProp(signal(""), props.stroke);
|
|
568
795
|
this.strokeWidth = initProp(signal(0), props.strokeWidth);
|
|
796
|
+
this.reveal = initProp(signal(1), props.reveal);
|
|
569
797
|
this.registerTarget("fill", this.fill);
|
|
570
798
|
this.registerTarget("stroke", this.stroke);
|
|
571
799
|
this.registerTarget("strokeWidth", this.strokeWidth);
|
|
800
|
+
this.registerTarget("reveal", this.reveal);
|
|
801
|
+
if (props.sketch) validateSketch(props.sketch);
|
|
802
|
+
this.sketch = props.sketch;
|
|
803
|
+
this.sketchSeed = props.sketchSeed ?? (this.id !== void 0 ? hashStr(this.id) : 0);
|
|
572
804
|
}
|
|
573
805
|
draw(out) {
|
|
574
806
|
const segs = this.pathSegs();
|
|
807
|
+
if (this.sketch) return this.drawSketch(out, segs);
|
|
575
808
|
const path = out.resource({
|
|
576
809
|
kind: "path",
|
|
577
810
|
segs
|
|
@@ -597,12 +830,91 @@ var Shape = class extends Node {
|
|
|
597
830
|
stroke: { width }
|
|
598
831
|
});
|
|
599
832
|
}
|
|
833
|
+
/** Hand-drawn render: solid fill (if any) under roughened, multi-pass strokes.
|
|
834
|
+
* The seed is consumed fresh each draw, so re-evaluation is byte-identical. */
|
|
835
|
+
drawSketch(out, segs) {
|
|
836
|
+
const fill = this.fill();
|
|
837
|
+
if (fill) {
|
|
838
|
+
const path = out.resource({
|
|
839
|
+
kind: "path",
|
|
840
|
+
segs
|
|
841
|
+
});
|
|
842
|
+
out.push({
|
|
843
|
+
op: "fillPath",
|
|
844
|
+
path,
|
|
845
|
+
paint: {
|
|
846
|
+
kind: "color",
|
|
847
|
+
color: fill
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
const { strokes, resolved } = roughen(segs, this.sketch, random(this.sketchSeed >>> 0));
|
|
852
|
+
const ink = this.stroke() || fill || "#000000";
|
|
853
|
+
const reveal = this.reveal();
|
|
854
|
+
const drawOn = reveal < 1;
|
|
855
|
+
for (const passSegs of strokes) {
|
|
856
|
+
if (passSegs.length === 0) continue;
|
|
857
|
+
if (drawOn) {
|
|
858
|
+
for (const contour of splitContours(passSegs)) {
|
|
859
|
+
const len = flatten(contour).reduce((s, p) => s + arcLength(p), 0);
|
|
860
|
+
const path = out.resource({
|
|
861
|
+
kind: "path",
|
|
862
|
+
segs: contour
|
|
863
|
+
});
|
|
864
|
+
out.push({
|
|
865
|
+
op: "strokePath",
|
|
866
|
+
path,
|
|
867
|
+
paint: {
|
|
868
|
+
kind: "color",
|
|
869
|
+
color: ink
|
|
870
|
+
},
|
|
871
|
+
stroke: {
|
|
872
|
+
width: resolved.width,
|
|
873
|
+
cap: "round",
|
|
874
|
+
join: "round",
|
|
875
|
+
dash: [len, len],
|
|
876
|
+
dashOffset: len * (1 - reveal)
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
const path = out.resource({
|
|
883
|
+
kind: "path",
|
|
884
|
+
segs: passSegs
|
|
885
|
+
});
|
|
886
|
+
out.push({
|
|
887
|
+
op: "strokePath",
|
|
888
|
+
path,
|
|
889
|
+
paint: {
|
|
890
|
+
kind: "color",
|
|
891
|
+
color: ink
|
|
892
|
+
},
|
|
893
|
+
stroke: {
|
|
894
|
+
width: resolved.width,
|
|
895
|
+
cap: "round",
|
|
896
|
+
join: "round",
|
|
897
|
+
...resolved.dash ? { dash: resolved.dash } : {}
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
}
|
|
600
902
|
};
|
|
601
903
|
function initProp(sig, init) {
|
|
602
904
|
if (typeof init === "function") sig.bindSource(init);
|
|
603
905
|
else if (init !== void 0) sig.set(init);
|
|
604
906
|
return sig;
|
|
605
907
|
}
|
|
908
|
+
/** Split a stroke path into its subpaths (each starting at an 'M'). */
|
|
909
|
+
function splitContours(segs) {
|
|
910
|
+
const out = [];
|
|
911
|
+
let cur = null;
|
|
912
|
+
for (const s of segs) if (s[0] === "M") {
|
|
913
|
+
cur = [s];
|
|
914
|
+
out.push(cur);
|
|
915
|
+
} else if (cur) cur.push(s);
|
|
916
|
+
return out;
|
|
917
|
+
}
|
|
606
918
|
var Rect = class extends Shape {
|
|
607
919
|
width;
|
|
608
920
|
height;
|
|
@@ -1224,4 +1536,4 @@ function requireLayoutEngine() {
|
|
|
1224
1536
|
return engine;
|
|
1225
1537
|
}
|
|
1226
1538
|
//#endregion
|
|
1227
|
-
export {
|
|
1539
|
+
export { FilterValidationError as A, multiply as B, breakLines as C, segmentGraphemes as D, quantize as E, IDENTITY as F, applyToPoint as I, fromTRS as L, filtersToCanvasFilter as M, glow as N, segmentWords as O, validateFilters as P, invert as R, resolveAnchor as S, fallbackMeasurer as T, resolveSketch as _, Circle as a, validateSketch as b, Path as c, Video as d, revealSchedule as f, flatten as g, arcLength as h, setLayoutEngine as i, createDisplayListBuilder as j, setDefaultMeasurer as k, Rect as l, SketchValidationError as m, getLayoutEngine as n, Group as o, roundedRectSegs as p, requireLayoutEngine as r, ImageNode as s, LayoutEngineMissingError as t, Text as u, roughen as v, estimatingMeasurer as w, Node as x, sketchStrokes as y, matEquals as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/scene",
|
|
3
|
-
"version": "0.5.0-pre.
|
|
3
|
+
"version": "0.5.0-pre.6",
|
|
4
4
|
"description": "glissade scene graph: nodes, transforms, DisplayList emission. Renderer-agnostic; zero DOM/Node dependencies.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"yoga-layout": "^3.2.1",
|
|
23
|
-
"@glissade/core": "0.5.0-pre.
|
|
23
|
+
"@glissade/core": "0.5.0-pre.6"
|
|
24
24
|
},
|
|
25
25
|
"repository": {
|
|
26
26
|
"type": "git",
|