@glissade/scene 0.5.0 → 0.6.0-pre.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
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";
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
2
  import { BindableSignal, BoundTimeline, CompiledTimeline, PathValue, Playhead, Timeline, Track, Vec2 } from "@glissade/core";
3
3
 
4
4
  //#region src/highlight.d.ts
@@ -417,4 +417,4 @@ declare function bindScene(scene: Scene, doc: Timeline): BindingCacheEntry;
417
417
  */
418
418
  declare function evaluate(scene: Scene, doc: Timeline, t: number): DisplayList;
419
419
  //#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 };
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, 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, 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,4 +1,4 @@
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";
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
2
  import { bindTimeline, compileTimeline, createPlayhead, emitDevWarning, evaluateAt, key, signal, track, vec2Signal } from "@glissade/core";
3
3
  //#region src/highlight.ts
4
4
  /**
@@ -1079,4 +1079,4 @@ function evaluate(scene, doc, t) {
1079
1079
  });
1080
1080
  }
1081
1081
  //#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 };
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, 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 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";
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 { T as fallbackMeasurer, i as setLayoutEngine, n as getLayoutEngine, o as Group, r as requireLayoutEngine, t as LayoutEngineMissingError } from "./layoutEngine.js";
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
  /**
@@ -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 { 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 };
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 };
@@ -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) out.push({
824
- op: "strokePath",
825
- path,
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
- stroke: { width }
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, random(this.sketchSeed >>> 0));
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
- 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
- }
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 { 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 };
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.5.0",
3
+ "version": "0.6.0-pre.0",
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"
23
+ "@glissade/core": "0.6.0-pre.0"
24
24
  },
25
25
  "repository": {
26
26
  "type": "git",