@glissade/scene 0.5.0 → 0.6.0-pre.1
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 +23 -3
- package/dist/index.js +25 -3
- package/dist/layout.d.ts +1 -1
- package/dist/layout.js +1 -1
- package/dist/layoutEngine.d.ts +30 -1
- package/dist/layoutEngine.js +224 -31
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
import { BindableSignal, BoundTimeline, CompiledTimeline, PathValue, Playhead, Timeline, Track, Vec2 } from "@glissade/core";
|
|
1
|
+
import { $ as quantize, A as ResolvedSketch, B as validateSketch, C as VideoProps, Ct as applyToPoint, D as roundedRectSegs, Dt as multiply, E as revealSchedule, Et as matEquals, F as hachureLines, G as Node, H as BindablePropTarget, I as resolveSketch, J as resolveAnchor, K as NodeProps, L as roughen, M as SketchValidationError, N as arcLength, O as HachureSpec, P as flatten, Q as estimatingMeasurer, R as sketchStrokes, S as Video, St as Mat2x3, T as pathFromSegs, Tt as invert, U as EvalContext, V as AnchorSpec, W as HitArea, X as TextMetricsLite, Y as TextMeasurer, Z as breakLines, _ as Rect, _t as createDisplayListBuilder, a as LayoutEngineMissingError, at as DisplayListBuilder, b as Text, bt as validateFilters, c as requireLayoutEngine, ct as FilterValidationError, d as Group, dt as PathSeg, et as segmentGraphemes, f as ImageNode, ft as Rect$1, g as PathProps, gt as StrokeStyle, h as Path, ht as ShaderRef, i as LayoutEngine, it as DisplayList, j as SketchStyle, k as Polyline, l as setLayoutEngine, lt as FontSpec, m as LineBox, mt as ResourceId, n as LayoutChildSpec, nt as setDefaultMeasurer, ot as DrawCommand, p as ImageProps, pt as Resource, q as PropInit, r as LayoutContainerSpec, rt as BlendMode, s as getLayoutEngine, st as FilterSpec, t as LayoutBox, tt as segmentWords, u as Circle, ut as Paint, v as RevealMark, vt as filtersToCanvasFilter, w as WordBox, wt as fromTRS, x as TextProps, xt as IDENTITY, y as ShapeProps, yt as glow, z as validateHachure } from "./layoutEngine.js";
|
|
2
|
+
import { BindableSignal, BoundTimeline, CompiledTimeline, EaseSpec, PathValue, Playhead, Timeline, Track, Vec2 } from "@glissade/core";
|
|
3
3
|
|
|
4
4
|
//#region src/highlight.d.ts
|
|
5
5
|
|
|
@@ -119,6 +119,26 @@ declare function typewriter(target: string, edits: readonly TypeEdit[], opts?: {
|
|
|
119
119
|
gap?: number;
|
|
120
120
|
}): TypewriterResult;
|
|
121
121
|
//#endregion
|
|
122
|
+
//#region src/drawOn.d.ts
|
|
123
|
+
interface DrawOnOptions {
|
|
124
|
+
/** when the stroke-on starts, seconds; default 0 */
|
|
125
|
+
start?: number;
|
|
126
|
+
/** how long it takes, seconds; default 1 */
|
|
127
|
+
duration?: number;
|
|
128
|
+
/** the ease arriving at fully drawn; default 'easeInOutCubic' */
|
|
129
|
+
ease?: EaseSpec;
|
|
130
|
+
}
|
|
131
|
+
/** A `<id>/reveal` track running 0→1 — point a stroked/sketched shape at it to
|
|
132
|
+
* hand-draw itself on. `target` is the node id. */
|
|
133
|
+
declare function drawOn(target: string, opts?: DrawOnOptions): Track<number>;
|
|
134
|
+
interface DrawOnEachOptions extends DrawOnOptions {
|
|
135
|
+
/** gap between each shape starting, seconds; default 0.6 × duration */
|
|
136
|
+
delay?: number;
|
|
137
|
+
}
|
|
138
|
+
/** Cascade several shapes drawing themselves on, one after another — the
|
|
139
|
+
* whiteboard sequence. Returns one reveal track per id, staggered by `delay`. */
|
|
140
|
+
declare function drawOnEach(targets: readonly string[], opts?: DrawOnEachOptions): Track<number>[];
|
|
141
|
+
//#endregion
|
|
122
142
|
//#region src/motionPath.d.ts
|
|
123
143
|
/** An arc-length-parameterized sampler over a path. */
|
|
124
144
|
interface PathSampler {
|
|
@@ -417,4 +437,4 @@ declare function bindScene(scene: Scene, doc: Timeline): BindingCacheEntry;
|
|
|
417
437
|
*/
|
|
418
438
|
declare function evaluate(scene: Scene, doc: Timeline, t: number): DisplayList;
|
|
419
439
|
//#endregion
|
|
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 };
|
|
440
|
+
export { type AnchorSpec, type BindablePropTarget, type BlendMode, type CanvasLike, Circle, ColdAssetError, type Ctx2DLike, type DisplayList, type DisplayListBuilder, type DrawCommand, type DrawOnEachOptions, type DrawOnOptions, DuplicateNodeIdError, type EditMark, type EvalContext, type FilterSpec, FilterValidationError, FollowPath, type FollowPathProps, type FontSpec, Group, type HachureSpec, 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, drawOn, drawOnEach, estimatingMeasurer, evaluate, filtersToCanvasFilter, flatten, followPath, fontString, fromTRS, getLayoutEngine, glow, hachureLines, highlight, invert, matEquals, matchTokenRun, motionPath, multiply, pathFromSegs, pathLength, pointAtLength, quantize, requireLayoutEngine, resolveAnchor, resolveSketch, revealSchedule, roughen, roundedRectSegs, segmentGraphemes, segmentWords, setDefaultMeasurer, setLayoutEngine, sketchStrokes, textCursor, tokenHighlight, typewriter, validateFilters, validateHachure, validateSketch };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { bindTimeline, compileTimeline, createPlayhead, emitDevWarning, evaluateAt, key, signal, track, vec2Signal } from "@glissade/core";
|
|
1
|
+
import { A as segmentGraphemes, B as fromTRS, C as validateSketch, D as estimatingMeasurer, E as breakLines, F as filtersToCanvasFilter, H as matEquals, I as glow, L as validateFilters, M as setDefaultMeasurer, N as FilterValidationError, O as fallbackMeasurer, P as createDisplayListBuilder, R as IDENTITY, S as validateHachure, T as resolveAnchor, U as multiply, V as invert, _ as flatten, a as Circle, b as roughen, c as Path, d as Video, f as pathFromSegs, g as arcLength, h as SketchValidationError, i as setLayoutEngine, j as segmentWords, k as quantize, l as Rect, m as roundedRectSegs, n as getLayoutEngine, o as Group, p as revealSchedule, r as requireLayoutEngine, s as ImageNode, t as LayoutEngineMissingError, u as Text, v as hachureLines, w as Node, x as sketchStrokes, y as resolveSketch, z as applyToPoint } from "./layoutEngine.js";
|
|
2
|
+
import { bindTimeline, compileTimeline, createPlayhead, emitDevWarning, evaluateAt, key, signal, stagger, track, vec2Signal } from "@glissade/core";
|
|
3
3
|
//#region src/highlight.ts
|
|
4
4
|
/**
|
|
5
5
|
* Marker-style text highlight: per-line rounded rects behind a Text node's
|
|
@@ -232,6 +232,28 @@ function typewriter(target, edits, opts = {}) {
|
|
|
232
232
|
};
|
|
233
233
|
}
|
|
234
234
|
//#endregion
|
|
235
|
+
//#region src/drawOn.ts
|
|
236
|
+
/**
|
|
237
|
+
* Whiteboard kit: one-call "draw this shape on" tracks. A stroked or sketched
|
|
238
|
+
* shape's `reveal` (0..1) animates it stroking itself on; `drawOn` builds that
|
|
239
|
+
* track, and `drawOnEach` cascades a list of shapes one after another (the
|
|
240
|
+
* classic whiteboard sequence) via the core `stagger` helper.
|
|
241
|
+
*/
|
|
242
|
+
/** A `<id>/reveal` track running 0→1 — point a stroked/sketched shape at it to
|
|
243
|
+
* hand-draw itself on. `target` is the node id. */
|
|
244
|
+
function drawOn(target, opts = {}) {
|
|
245
|
+
const start = opts.start ?? 0;
|
|
246
|
+
const duration = opts.duration ?? 1;
|
|
247
|
+
const ease = opts.ease ?? "easeInOutCubic";
|
|
248
|
+
return track(`${target}/reveal`, "number", [key(start, 0), key(start + duration, 1, ease)]);
|
|
249
|
+
}
|
|
250
|
+
/** Cascade several shapes drawing themselves on, one after another — the
|
|
251
|
+
* whiteboard sequence. Returns one reveal track per id, staggered by `delay`. */
|
|
252
|
+
function drawOnEach(targets, opts = {}) {
|
|
253
|
+
const delay = opts.delay ?? (opts.duration ?? 1) * .6;
|
|
254
|
+
return stagger(targets.map((t) => drawOn(t, opts)), delay);
|
|
255
|
+
}
|
|
256
|
+
//#endregion
|
|
235
257
|
//#region src/motionPath.ts
|
|
236
258
|
/**
|
|
237
259
|
* Motion along a path: sample a point (and tangent) at an arc-length position on
|
|
@@ -1079,4 +1101,4 @@ function evaluate(scene, doc, t) {
|
|
|
1079
1101
|
});
|
|
1080
1102
|
}
|
|
1081
1103
|
//#endregion
|
|
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 };
|
|
1104
|
+
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, drawOn, drawOnEach, estimatingMeasurer, evaluate, filtersToCanvasFilter, flatten, followPath, fontString, fromTRS, getLayoutEngine, glow, hachureLines, highlight, invert, matEquals, matchTokenRun, motionPath, multiply, pathFromSegs, pathLength, pointAtLength, quantize, requireLayoutEngine, resolveAnchor, resolveSketch, revealSchedule, roughen, roundedRectSegs, segmentGraphemes, segmentWords, setDefaultMeasurer, setLayoutEngine, sketchStrokes, textCursor, tokenHighlight, typewriter, validateFilters, validateHachure, validateSketch };
|
package/dist/layout.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { G as
|
|
1
|
+
import { G as Node, K as NodeProps, U as EvalContext, Y as TextMeasurer, a as LayoutEngineMissingError, at as DisplayListBuilder, d as Group, i as LayoutEngine, l as setLayoutEngine, n as LayoutChildSpec, o as LayoutResult, q as PropInit, r as LayoutContainerSpec, s as getLayoutEngine, t as LayoutBox } 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 {
|
|
1
|
+
import { O 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
|
@@ -402,6 +402,22 @@ interface Polyline {
|
|
|
402
402
|
declare function flatten(segs: readonly PathSeg[], steps?: number): Polyline[];
|
|
403
403
|
/** Total length of a flattened polyline (for draw-on dashing). */
|
|
404
404
|
declare function arcLength(poly: Polyline): number;
|
|
405
|
+
/** A sketchy fill: parallel hatch lines (clipped to the shape by the caller). */
|
|
406
|
+
interface HachureSpec {
|
|
407
|
+
/** hatch line angle, radians */
|
|
408
|
+
angleRad: number;
|
|
409
|
+
/** spacing between lines, px */
|
|
410
|
+
gap: number;
|
|
411
|
+
/** jitter amplitude, px; default 1 */
|
|
412
|
+
roughness?: number;
|
|
413
|
+
}
|
|
414
|
+
declare function validateHachure(h: HachureSpec): void;
|
|
415
|
+
/**
|
|
416
|
+
* Parallel hatch lines covering a path's bounding box at `angleRad`, spaced
|
|
417
|
+
* `gap`, lightly jittered. Returned as `M/L` segments to be stroked INSIDE a
|
|
418
|
+
* clip of the shape (the caller emits the clip). Pure; `rng` reseeded per draw.
|
|
419
|
+
*/
|
|
420
|
+
declare function hachureLines(segs: readonly PathSeg[], spec: HachureSpec, rng: Rng): PathSeg[];
|
|
405
421
|
/**
|
|
406
422
|
* Roughen a path into hand-drawn stroke passes. Each segment becomes a bowed,
|
|
407
423
|
* jittered quadratic; `passes` overlay slightly different jitters for the
|
|
@@ -440,12 +456,16 @@ interface ShapeProps extends NodeProps {
|
|
|
440
456
|
* Track `<id>/reveal`. Precise for single-contour shapes; multi-contour ones
|
|
441
457
|
* reveal each contour in parallel. */
|
|
442
458
|
reveal?: PropInit<number>;
|
|
459
|
+
/** sketchy hatch fill clipped to the shape (the pencil/crayon filled look);
|
|
460
|
+
* requires `sketch`. */
|
|
461
|
+
sketchFill?: HachureSpec;
|
|
443
462
|
}
|
|
444
463
|
declare abstract class Shape extends Node {
|
|
445
464
|
readonly fill: BindableSignal<string>;
|
|
446
465
|
readonly stroke: BindableSignal<string>;
|
|
447
466
|
readonly strokeWidth: BindableSignal<number>;
|
|
448
467
|
readonly sketch: SketchStyle | undefined;
|
|
468
|
+
readonly sketchFill: HachureSpec | undefined;
|
|
449
469
|
readonly sketchSeed: number;
|
|
450
470
|
readonly reveal: BindableSignal<number>;
|
|
451
471
|
constructor(props?: ShapeProps);
|
|
@@ -455,6 +475,15 @@ declare abstract class Shape extends Node {
|
|
|
455
475
|
* The seed is consumed fresh each draw, so re-evaluation is byte-identical. */
|
|
456
476
|
private drawSketch;
|
|
457
477
|
}
|
|
478
|
+
/**
|
|
479
|
+
* `PathSeg[]` → `PathValue` (Lottie vertex contours) — the inverse of
|
|
480
|
+
* `Path.pathSegs`, so geometry from `roundedRectSegs` / `sketchStrokes` /
|
|
481
|
+
* `flatten` can be placed on a `Path` node (to morph, motion-path, or draw-on
|
|
482
|
+
* it). C/Q become an anchor + relative in/out tangents; L is a zero-tangent
|
|
483
|
+
* vertex; E samples to vertices; Z closes the contour, folding the closing
|
|
484
|
+
* tangent back onto the first vertex. Round-trips C-contours exactly.
|
|
485
|
+
*/
|
|
486
|
+
declare function pathFromSegs(segs: readonly PathSeg[]): PathValue;
|
|
458
487
|
declare class Rect extends Shape {
|
|
459
488
|
readonly width: BindableSignal<number>;
|
|
460
489
|
readonly height: BindableSignal<number>;
|
|
@@ -753,4 +782,4 @@ declare function setLayoutEngine(e: LayoutEngine): void;
|
|
|
753
782
|
declare function getLayoutEngine(): LayoutEngine | null;
|
|
754
783
|
declare function requireLayoutEngine(): LayoutEngine;
|
|
755
784
|
//#endregion
|
|
756
|
-
export {
|
|
785
|
+
export { quantize as $, ResolvedSketch as A, validateSketch as B, VideoProps as C, applyToPoint as Ct, roundedRectSegs as D, multiply as Dt, revealSchedule as E, matEquals as Et, hachureLines as F, Node as G, BindablePropTarget as H, resolveSketch as I, resolveAnchor as J, NodeProps as K, roughen as L, SketchValidationError as M, arcLength as N, HachureSpec as O, flatten as P, estimatingMeasurer as Q, sketchStrokes as R, Video as S, Mat2x3 as St, pathFromSegs as T, invert as Tt, EvalContext as U, AnchorSpec as V, HitArea as W, TextMetricsLite as X, TextMeasurer as Y, breakLines as Z, Rect as _, createDisplayListBuilder as _t, LayoutEngineMissingError as a, DisplayListBuilder as at, Text as b, validateFilters as bt, requireLayoutEngine as c, FilterValidationError as ct, Group as d, PathSeg as dt, segmentGraphemes as et, ImageNode as f, Rect$1 as ft, PathProps as g, StrokeStyle as gt, Path as h, ShaderRef as ht, LayoutEngine as i, DisplayList as it, SketchStyle as j, Polyline as k, setLayoutEngine as l, FontSpec as lt, LineBox as m, ResourceId as mt, LayoutChildSpec as n, setDefaultMeasurer as nt, LayoutResult as o, DrawCommand as ot, ImageProps as p, Resource as pt, PropInit as q, LayoutContainerSpec as r, BlendMode as rt, getLayoutEngine as s, FilterSpec as st, LayoutBox as t, segmentWords as tt, Circle as u, Paint as ut, RevealMark as v, filtersToCanvasFilter as vt, WordBox as w, fromTRS as wt, TextProps as x, IDENTITY as xt, ShapeProps as y, glow as yt, validateHachure as z };
|
package/dist/layoutEngine.js
CHANGED
|
@@ -597,6 +597,69 @@ function arcLength(poly) {
|
|
|
597
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
598
|
return len;
|
|
599
599
|
}
|
|
600
|
+
function validateHachure(h) {
|
|
601
|
+
if (!(h.gap > 0)) throw new SketchValidationError(`hachure gap must be > 0, got ${String(h.gap)}`);
|
|
602
|
+
if (h.roughness !== void 0 && !(h.roughness >= 0)) throw new SketchValidationError(`hachure roughness must be ≥ 0, got ${String(h.roughness)}`);
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Parallel hatch lines covering a path's bounding box at `angleRad`, spaced
|
|
606
|
+
* `gap`, lightly jittered. Returned as `M/L` segments to be stroked INSIDE a
|
|
607
|
+
* clip of the shape (the caller emits the clip). Pure; `rng` reseeded per draw.
|
|
608
|
+
*/
|
|
609
|
+
function hachureLines(segs, spec, rng) {
|
|
610
|
+
const polys = flatten(segs);
|
|
611
|
+
let minX = Infinity;
|
|
612
|
+
let minY = Infinity;
|
|
613
|
+
let maxX = -Infinity;
|
|
614
|
+
let maxY = -Infinity;
|
|
615
|
+
for (const p of polys) for (const [x, y] of p.points) {
|
|
616
|
+
if (x < minX) minX = x;
|
|
617
|
+
if (y < minY) minY = y;
|
|
618
|
+
if (x > maxX) maxX = x;
|
|
619
|
+
if (y > maxY) maxY = y;
|
|
620
|
+
}
|
|
621
|
+
if (!Number.isFinite(minX)) return [];
|
|
622
|
+
const cx = (minX + maxX) / 2;
|
|
623
|
+
const cy = (minY + maxY) / 2;
|
|
624
|
+
const ca = Math.cos(spec.angleRad);
|
|
625
|
+
const sa = Math.sin(spec.angleRad);
|
|
626
|
+
const toRot = (x, y) => [(x - cx) * ca + (y - cy) * sa, -(x - cx) * sa + (y - cy) * ca];
|
|
627
|
+
const fromRot = (x, y) => [cx + x * ca - y * sa, cy + x * sa + y * ca];
|
|
628
|
+
let rMinX = Infinity;
|
|
629
|
+
let rMinY = Infinity;
|
|
630
|
+
let rMaxX = -Infinity;
|
|
631
|
+
let rMaxY = -Infinity;
|
|
632
|
+
const corners = [
|
|
633
|
+
[minX, minY],
|
|
634
|
+
[maxX, minY],
|
|
635
|
+
[maxX, maxY],
|
|
636
|
+
[minX, maxY]
|
|
637
|
+
];
|
|
638
|
+
for (const [x, y] of corners) {
|
|
639
|
+
const [rx, ry] = toRot(x, y);
|
|
640
|
+
if (rx < rMinX) rMinX = rx;
|
|
641
|
+
if (ry < rMinY) rMinY = ry;
|
|
642
|
+
if (rx > rMaxX) rMaxX = rx;
|
|
643
|
+
if (ry > rMaxY) rMaxY = ry;
|
|
644
|
+
}
|
|
645
|
+
const rough = spec.roughness ?? 1;
|
|
646
|
+
const jit = () => (rng() * 2 - 1) * rough;
|
|
647
|
+
const out = [];
|
|
648
|
+
for (let y = rMinY + spec.gap / 2; y < rMaxY; y += spec.gap) {
|
|
649
|
+
const a = fromRot(rMinX, y + jit());
|
|
650
|
+
const b = fromRot(rMaxX, y + jit());
|
|
651
|
+
out.push([
|
|
652
|
+
"M",
|
|
653
|
+
a[0],
|
|
654
|
+
a[1]
|
|
655
|
+
], [
|
|
656
|
+
"L",
|
|
657
|
+
b[0],
|
|
658
|
+
b[1]
|
|
659
|
+
]);
|
|
660
|
+
}
|
|
661
|
+
return out;
|
|
662
|
+
}
|
|
600
663
|
/**
|
|
601
664
|
* Roughen a path into hand-drawn stroke passes. Each segment becomes a bowed,
|
|
602
665
|
* jittered quadratic; `passes` overlay slightly different jitters for the
|
|
@@ -786,6 +849,7 @@ var Shape = class extends Node {
|
|
|
786
849
|
stroke;
|
|
787
850
|
strokeWidth;
|
|
788
851
|
sketch;
|
|
852
|
+
sketchFill;
|
|
789
853
|
sketchSeed;
|
|
790
854
|
reveal;
|
|
791
855
|
constructor(props = {}) {
|
|
@@ -799,7 +863,9 @@ var Shape = class extends Node {
|
|
|
799
863
|
this.registerTarget("strokeWidth", this.strokeWidth);
|
|
800
864
|
this.registerTarget("reveal", this.reveal);
|
|
801
865
|
if (props.sketch) validateSketch(props.sketch);
|
|
866
|
+
if (props.sketchFill) validateHachure(props.sketchFill);
|
|
802
867
|
this.sketch = props.sketch;
|
|
868
|
+
this.sketchFill = props.sketchFill;
|
|
803
869
|
this.sketchSeed = props.sketchSeed ?? (this.id !== void 0 ? hashStr(this.id) : 0);
|
|
804
870
|
}
|
|
805
871
|
draw(out) {
|
|
@@ -820,19 +886,27 @@ var Shape = class extends Node {
|
|
|
820
886
|
});
|
|
821
887
|
const stroke = this.stroke();
|
|
822
888
|
const width = this.strokeWidth();
|
|
823
|
-
if (stroke && width > 0)
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
paint: {
|
|
889
|
+
if (stroke && width > 0) {
|
|
890
|
+
const reveal = this.reveal();
|
|
891
|
+
if (reveal < 1) emitDrawOnStroke(out, segs, {
|
|
827
892
|
kind: "color",
|
|
828
893
|
color: stroke
|
|
829
|
-
},
|
|
830
|
-
|
|
831
|
-
|
|
894
|
+
}, { width }, reveal);
|
|
895
|
+
else out.push({
|
|
896
|
+
op: "strokePath",
|
|
897
|
+
path,
|
|
898
|
+
paint: {
|
|
899
|
+
kind: "color",
|
|
900
|
+
color: stroke
|
|
901
|
+
},
|
|
902
|
+
stroke: { width }
|
|
903
|
+
});
|
|
904
|
+
}
|
|
832
905
|
}
|
|
833
906
|
/** Hand-drawn render: solid fill (if any) under roughened, multi-pass strokes.
|
|
834
907
|
* The seed is consumed fresh each draw, so re-evaluation is byte-identical. */
|
|
835
908
|
drawSketch(out, segs) {
|
|
909
|
+
const rng = random(this.sketchSeed >>> 0);
|
|
836
910
|
const fill = this.fill();
|
|
837
911
|
if (fill) {
|
|
838
912
|
const path = out.resource({
|
|
@@ -848,35 +922,53 @@ var Shape = class extends Node {
|
|
|
848
922
|
}
|
|
849
923
|
});
|
|
850
924
|
}
|
|
851
|
-
const { strokes, resolved } = roughen(segs, this.sketch,
|
|
925
|
+
const { strokes, resolved } = roughen(segs, this.sketch, rng);
|
|
852
926
|
const ink = this.stroke() || fill || "#000000";
|
|
927
|
+
if (this.sketchFill) {
|
|
928
|
+
const clipPath = out.resource({
|
|
929
|
+
kind: "path",
|
|
930
|
+
segs
|
|
931
|
+
});
|
|
932
|
+
out.push({ op: "save" });
|
|
933
|
+
out.push({
|
|
934
|
+
op: "clip",
|
|
935
|
+
path: clipPath,
|
|
936
|
+
rule: "nonzero"
|
|
937
|
+
});
|
|
938
|
+
const hatch = hachureLines(segs, this.sketchFill, rng);
|
|
939
|
+
if (hatch.length > 0) {
|
|
940
|
+
const hp = out.resource({
|
|
941
|
+
kind: "path",
|
|
942
|
+
segs: hatch
|
|
943
|
+
});
|
|
944
|
+
out.push({
|
|
945
|
+
op: "strokePath",
|
|
946
|
+
path: hp,
|
|
947
|
+
paint: {
|
|
948
|
+
kind: "color",
|
|
949
|
+
color: ink
|
|
950
|
+
},
|
|
951
|
+
stroke: {
|
|
952
|
+
width: Math.max(1, resolved.width * .5),
|
|
953
|
+
cap: "round"
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
out.push({ op: "restore" });
|
|
958
|
+
}
|
|
853
959
|
const reveal = this.reveal();
|
|
854
960
|
const drawOn = reveal < 1;
|
|
855
961
|
for (const passSegs of strokes) {
|
|
856
962
|
if (passSegs.length === 0) continue;
|
|
857
963
|
if (drawOn) {
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
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
|
-
}
|
|
964
|
+
emitDrawOnStroke(out, passSegs, {
|
|
965
|
+
kind: "color",
|
|
966
|
+
color: ink
|
|
967
|
+
}, {
|
|
968
|
+
width: resolved.width,
|
|
969
|
+
cap: "round",
|
|
970
|
+
join: "round"
|
|
971
|
+
}, reveal);
|
|
880
972
|
continue;
|
|
881
973
|
}
|
|
882
974
|
const path = out.resource({
|
|
@@ -915,6 +1007,107 @@ function splitContours(segs) {
|
|
|
915
1007
|
} else if (cur) cur.push(s);
|
|
916
1008
|
return out;
|
|
917
1009
|
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Emit a stroke that "draws on" by arc length — a retreating dash PER CONTOUR
|
|
1012
|
+
* (canvas restarts the dash phase at each subpath move, so each contour reveals
|
|
1013
|
+
* from its own start). Shared by sketched and plain stroked shapes.
|
|
1014
|
+
*/
|
|
1015
|
+
function emitDrawOnStroke(out, segs, paint, baseStroke, reveal) {
|
|
1016
|
+
for (const contour of splitContours(segs)) {
|
|
1017
|
+
const len = flatten(contour).reduce((s, p) => s + arcLength(p), 0);
|
|
1018
|
+
const path = out.resource({
|
|
1019
|
+
kind: "path",
|
|
1020
|
+
segs: contour
|
|
1021
|
+
});
|
|
1022
|
+
out.push({
|
|
1023
|
+
op: "strokePath",
|
|
1024
|
+
path,
|
|
1025
|
+
paint,
|
|
1026
|
+
stroke: {
|
|
1027
|
+
...baseStroke,
|
|
1028
|
+
dash: [len, len],
|
|
1029
|
+
dashOffset: len * (1 - reveal)
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* `PathSeg[]` → `PathValue` (Lottie vertex contours) — the inverse of
|
|
1036
|
+
* `Path.pathSegs`, so geometry from `roundedRectSegs` / `sketchStrokes` /
|
|
1037
|
+
* `flatten` can be placed on a `Path` node (to morph, motion-path, or draw-on
|
|
1038
|
+
* it). C/Q become an anchor + relative in/out tangents; L is a zero-tangent
|
|
1039
|
+
* vertex; E samples to vertices; Z closes the contour, folding the closing
|
|
1040
|
+
* tangent back onto the first vertex. Round-trips C-contours exactly.
|
|
1041
|
+
*/
|
|
1042
|
+
function pathFromSegs(segs) {
|
|
1043
|
+
const contours = [];
|
|
1044
|
+
let c = null;
|
|
1045
|
+
const push = (v, inT = [0, 0], outT = [0, 0]) => {
|
|
1046
|
+
c.v.push(v);
|
|
1047
|
+
c.in.push(inT);
|
|
1048
|
+
c.out.push(outT);
|
|
1049
|
+
};
|
|
1050
|
+
const last = () => c.v[c.v.length - 1];
|
|
1051
|
+
for (const s of segs) switch (s[0]) {
|
|
1052
|
+
case "M":
|
|
1053
|
+
c = {
|
|
1054
|
+
closed: false,
|
|
1055
|
+
v: [[s[1], s[2]]],
|
|
1056
|
+
in: [[0, 0]],
|
|
1057
|
+
out: [[0, 0]]
|
|
1058
|
+
};
|
|
1059
|
+
contours.push(c);
|
|
1060
|
+
break;
|
|
1061
|
+
case "L":
|
|
1062
|
+
if (c) push([s[1], s[2]]);
|
|
1063
|
+
break;
|
|
1064
|
+
case "C":
|
|
1065
|
+
if (c) {
|
|
1066
|
+
const p0 = last();
|
|
1067
|
+
c.out[c.out.length - 1] = [s[1] - p0[0], s[2] - p0[1]];
|
|
1068
|
+
push([s[5], s[6]], [s[3] - s[5], s[4] - s[6]]);
|
|
1069
|
+
}
|
|
1070
|
+
break;
|
|
1071
|
+
case "Q":
|
|
1072
|
+
if (c) {
|
|
1073
|
+
const p0 = last();
|
|
1074
|
+
const qx = s[1];
|
|
1075
|
+
const qy = s[2];
|
|
1076
|
+
const px = s[3];
|
|
1077
|
+
const py = s[4];
|
|
1078
|
+
c.out[c.out.length - 1] = [2 / 3 * (qx - p0[0]), 2 / 3 * (qy - p0[1])];
|
|
1079
|
+
push([px, py], [2 / 3 * (qx - px), 2 / 3 * (qy - py)]);
|
|
1080
|
+
}
|
|
1081
|
+
break;
|
|
1082
|
+
case "E":
|
|
1083
|
+
if (c) {
|
|
1084
|
+
const [, cx, cy, rx, ry, rot, a0, a1] = s;
|
|
1085
|
+
const cos = Math.cos(rot);
|
|
1086
|
+
const sin = Math.sin(rot);
|
|
1087
|
+
for (let k = 1; k <= 16; k++) {
|
|
1088
|
+
const ang = a0 + (a1 - a0) * (k / 16);
|
|
1089
|
+
const ex = rx * Math.cos(ang);
|
|
1090
|
+
const ey = ry * Math.sin(ang);
|
|
1091
|
+
push([cx + ex * cos - ey * sin, cy + ex * sin + ey * cos]);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
break;
|
|
1095
|
+
case "Z":
|
|
1096
|
+
if (c) {
|
|
1097
|
+
c.closed = true;
|
|
1098
|
+
const f = c.v[0];
|
|
1099
|
+
const l = last();
|
|
1100
|
+
if (c.v.length > 1 && Math.abs(f[0] - l[0]) < 1e-9 && Math.abs(f[1] - l[1]) < 1e-9) {
|
|
1101
|
+
c.in[0] = c.in[c.in.length - 1];
|
|
1102
|
+
c.v.pop();
|
|
1103
|
+
c.in.pop();
|
|
1104
|
+
c.out.pop();
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
break;
|
|
1108
|
+
}
|
|
1109
|
+
return contours;
|
|
1110
|
+
}
|
|
918
1111
|
var Rect = class extends Shape {
|
|
919
1112
|
width;
|
|
920
1113
|
height;
|
|
@@ -1536,4 +1729,4 @@ function requireLayoutEngine() {
|
|
|
1536
1729
|
return engine;
|
|
1537
1730
|
}
|
|
1538
1731
|
//#endregion
|
|
1539
|
-
export {
|
|
1732
|
+
export { segmentGraphemes as A, fromTRS as B, validateSketch as C, estimatingMeasurer as D, breakLines as E, filtersToCanvasFilter as F, matEquals as H, glow as I, validateFilters as L, setDefaultMeasurer as M, FilterValidationError as N, fallbackMeasurer as O, createDisplayListBuilder as P, IDENTITY as R, validateHachure as S, resolveAnchor as T, multiply as U, invert as V, flatten as _, Circle as a, roughen as b, Path as c, Video as d, pathFromSegs as f, arcLength as g, SketchValidationError as h, setLayoutEngine as i, segmentWords as j, quantize as k, Rect as l, roundedRectSegs as m, getLayoutEngine as n, Group as o, revealSchedule as p, requireLayoutEngine as r, ImageNode as s, LayoutEngineMissingError as t, Text as u, hachureLines as v, Node as w, sketchStrokes as x, resolveSketch as y, applyToPoint as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/scene",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0-pre.1",
|
|
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.
|
|
23
|
+
"@glissade/core": "0.6.0-pre.1"
|
|
24
24
|
},
|
|
25
25
|
"repository": {
|
|
26
26
|
"type": "git",
|