@glissade/scene 0.8.1-pre.1 → 0.9.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,5 +1,5 @@
1
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";
2
+ import { BindableSignal, BoundTimeline, CompiledTimeline, CoverageReport, EaseSpec, FontMode, FontUsage, PathValue, Playhead, Timeline, Track, Vec2 } from "@glissade/core";
3
3
 
4
4
  //#region src/highlight.d.ts
5
5
 
@@ -396,6 +396,29 @@ declare class TokenHighlight extends Node {
396
396
  */
397
397
  declare function tokenHighlight(text: Text, props: Omit<TokenHighlightProps, 'text'>): TokenHighlight;
398
398
  //#endregion
399
+ //#region src/fontUsage.d.ts
400
+ /** Walk `scene` for Text nodes; one usage per node carrying its full text. */
401
+ declare function collectTextUsages(scene: Scene): FontUsage[];
402
+ /**
403
+ * Caller-supplied I/O: fetch the raw bytes for a font face URL (the export
404
+ * paths read a file / fetch a URL; this keeps core pure). Returning undefined
405
+ * means "could not load" — that family contributes no coverage, surfacing as
406
+ * missing glyphs (strict) / a dev warning, never a hang.
407
+ */
408
+ type FontByteLoader = (url: string) => Promise<ArrayBuffer | undefined>;
409
+ interface ValidateSceneFontsOptions {
410
+ mode?: FontMode;
411
+ /** OS-installed families to treat as registered (case-insensitive). */
412
+ osFamilies?: ReadonlySet<string> | undefined;
413
+ }
414
+ /**
415
+ * Run §3.6 font validation for a scene + its timeline document. Builds the
416
+ * registry from `doc.assets`, loads each registered face's cmap via `loadBytes`
417
+ * (once per family — the first face's URL is enough for coverage), and runs the
418
+ * pure `validateFonts`. Strict mode throws FontValidationError; dev warns.
419
+ */
420
+ declare function validateSceneFonts(scene: Scene, doc: Timeline, loadBytes: FontByteLoader, options?: ValidateSceneFontsOptions): Promise<CoverageReport>;
421
+ //#endregion
399
422
  //#region src/shaderEffect.d.ts
400
423
  interface ShaderEffectProps extends NodeProps {
401
424
  children?: Node[];
@@ -503,4 +526,4 @@ declare class Raster2D<TCanvas extends CanvasLike, TPath extends PathLike, TDraw
503
526
  render(target: TCanvas, list: DisplayList): void;
504
527
  }
505
528
  //#endregion
506
- export { ALL_FILTER_KINDS, type AnchorSpec, type BackendCaps, type BindablePropTarget, type BlendMode, type CacheColdResult, type CanvasLike, Circle, ColdAssetError, type Ctx2DLike, DeterminismViolationError, type DisplayList, type DisplayListBuilder, type DrawCommand, type DrawOnEachOptions, type DrawOnOptions, DuplicateNodeIdError, type EditMark, type EvalContext, type FilterKind, type FilterSpec, FilterValidationError, FollowPath, type FollowPathProps, type FontSpec, Group, type GuardMode, 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 RenderBackend, 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, auditCacheCold, 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, withDeterminismGuards };
529
+ export { ALL_FILTER_KINDS, type AnchorSpec, type BackendCaps, type BindablePropTarget, type BlendMode, type CacheColdResult, type CanvasLike, Circle, ColdAssetError, type Ctx2DLike, DeterminismViolationError, type DisplayList, type DisplayListBuilder, type DrawCommand, type DrawOnEachOptions, type DrawOnOptions, DuplicateNodeIdError, type EditMark, type EvalContext, type FilterKind, type FilterSpec, FilterValidationError, FollowPath, type FollowPathProps, type FontByteLoader, type FontSpec, Group, type GuardMode, 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 RenderBackend, 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, type ValidateSceneFontsOptions, Video, type VideoFrameSource, type VideoProps, type WordBox, applyToPoint, arcLength, auditCacheCold, bindScene, breakLines, collectTextUsages, 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, validateSceneFonts, validateSketch, withDeterminismGuards };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
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";
2
+ import { bindTimeline, buildFontRegistry, compileTimeline, createPlayhead, emitDevWarning, evaluateAt, key, parseCmap, signal, stagger, track, validateFonts, 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
@@ -849,6 +849,55 @@ function initVec(sig, v) {
849
849
  return sig;
850
850
  }
851
851
  //#endregion
852
+ //#region src/fontUsage.ts
853
+ /**
854
+ * Scene → font-validation bridge (DESIGN.md §3.6). Core owns the AssetRef,
855
+ * FontRegistry, cmap reader, and the pure validation; this module owns the
856
+ * node-walk (which only `scene` can do) and the I/O seam that loads a font
857
+ * face's bytes so core stays DOM/Node-free.
858
+ *
859
+ * `collectTextUsages` walks every Text node and reads the FULL `.text()` (not
860
+ * the reveal-masked prefix) — coverage is a property of the authored content,
861
+ * independent of the playhead, so it stays out of the pure evaluate() path.
862
+ */
863
+ /** Walk `scene` for Text nodes; one usage per node carrying its full text. */
864
+ function collectTextUsages(scene) {
865
+ const out = [];
866
+ const visit = (node) => {
867
+ if (node instanceof Text) {
868
+ const text = node.text();
869
+ if (text) out.push({
870
+ family: node.fontFamily,
871
+ text
872
+ });
873
+ }
874
+ if (node instanceof Group) for (const child of node.children) visit(child);
875
+ };
876
+ visit(scene.root);
877
+ return out;
878
+ }
879
+ /**
880
+ * Run §3.6 font validation for a scene + its timeline document. Builds the
881
+ * registry from `doc.assets`, loads each registered face's cmap via `loadBytes`
882
+ * (once per family — the first face's URL is enough for coverage), and runs the
883
+ * pure `validateFonts`. Strict mode throws FontValidationError; dev warns.
884
+ */
885
+ async function validateSceneFonts(scene, doc, loadBytes, options = {}) {
886
+ const mode = options.mode ?? "dev";
887
+ const registry = buildFontRegistry(doc.assets);
888
+ const usages = collectTextUsages(scene);
889
+ const wanted = /* @__PURE__ */ new Set();
890
+ for (const u of usages) if (registry.has(u.family)) for (const f of registry.fallbackChain(u.family)) wanted.add(f);
891
+ const cmaps = /* @__PURE__ */ new Map();
892
+ for (const family of wanted) {
893
+ const face = registry.resolveFace(family);
894
+ if (!face) continue;
895
+ const bytes = await loadBytes(face.url);
896
+ if (bytes) cmaps.set(family, parseCmap(bytes));
897
+ }
898
+ return validateFonts(usages, registry, cmaps, mode, { ...options.osFamilies !== void 0 ? { osFamilies: options.osFamilies } : {} });
899
+ }
900
+ //#endregion
852
901
  //#region src/assets.ts
853
902
  var ColdAssetError = class extends Error {
854
903
  assetId;
@@ -1254,4 +1303,4 @@ var Raster2D = class {
1254
1303
  }
1255
1304
  };
1256
1305
  //#endregion
1257
- export { ALL_FILTER_KINDS, Circle, ColdAssetError, DeterminismViolationError, DuplicateNodeIdError, FilterValidationError, FollowPath, Group, Highlight, IDENTITY, ImageNode, LayoutEngineMissingError, Node, Path, Raster2D, Rect, ShaderEffect, SketchValidationError, Text, TextCursor, TokenHighlight, TokenMatchError, Video, applyToPoint, arcLength, auditCacheCold, 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, withDeterminismGuards };
1306
+ export { ALL_FILTER_KINDS, Circle, ColdAssetError, DeterminismViolationError, DuplicateNodeIdError, FilterValidationError, FollowPath, Group, Highlight, IDENTITY, ImageNode, LayoutEngineMissingError, Node, Path, Raster2D, Rect, ShaderEffect, SketchValidationError, Text, TextCursor, TokenHighlight, TokenMatchError, Video, applyToPoint, arcLength, auditCacheCold, bindScene, breakLines, collectTextUsages, 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, validateSceneFonts, validateSketch, withDeterminismGuards };
@@ -622,6 +622,8 @@ interface TextProps extends NodeProps {
622
622
  fontFamily?: string;
623
623
  fontSize?: PropInit<number>;
624
624
  fontWeight?: number;
625
+ /** Font style; default 'normal'. Threaded into FontSpec.style (§3.6). */
626
+ fontStyle?: 'normal' | 'italic';
625
627
  /** Horizontal alignment about the node position; default 'left'. */
626
628
  align?: 'left' | 'center' | 'right';
627
629
  /** Wrap width in px; unset = no wrapping (explicit \n still breaks). */
@@ -642,6 +644,7 @@ declare class Text extends Node {
642
644
  readonly fontSize: BindableSignal<number>;
643
645
  readonly fontFamily: string;
644
646
  readonly fontWeight: number;
647
+ readonly fontStyle: 'normal' | 'italic';
645
648
  readonly align: 'left' | 'center' | 'right';
646
649
  readonly width: BindableSignal<number>;
647
650
  readonly lineHeight: number;
@@ -1363,6 +1363,7 @@ var Text = class extends Node {
1363
1363
  fontSize;
1364
1364
  fontFamily;
1365
1365
  fontWeight;
1366
+ fontStyle;
1366
1367
  align;
1367
1368
  width;
1368
1369
  lineHeight;
@@ -1374,6 +1375,7 @@ var Text = class extends Node {
1374
1375
  this.fontSize = initProp(signal(16), props.fontSize);
1375
1376
  this.fontFamily = props.fontFamily ?? "sans-serif";
1376
1377
  this.fontWeight = props.fontWeight ?? 400;
1378
+ this.fontStyle = props.fontStyle ?? "normal";
1377
1379
  this.align = props.align ?? "left";
1378
1380
  this.width = initProp(signal(0), props.width);
1379
1381
  this.lineHeight = props.lineHeight ?? 1.25;
@@ -1393,7 +1395,8 @@ var Text = class extends Node {
1393
1395
  const font = {
1394
1396
  family: this.fontFamily,
1395
1397
  size: this.fontSize(),
1396
- weight: this.fontWeight
1398
+ weight: this.fontWeight,
1399
+ ...this.fontStyle === "italic" ? { style: "italic" } : {}
1397
1400
  };
1398
1401
  const maxWidth = this.width();
1399
1402
  const lines = breakLines(text, font, maxWidth > 0 ? maxWidth : void 0, measurer);
@@ -1410,7 +1413,8 @@ var Text = class extends Node {
1410
1413
  const font = {
1411
1414
  family: this.fontFamily,
1412
1415
  size: this.fontSize(),
1413
- weight: this.fontWeight
1416
+ weight: this.fontWeight,
1417
+ ...this.fontStyle === "italic" ? { style: "italic" } : {}
1414
1418
  };
1415
1419
  const firstLine = breakLines(this.text(), font, this.width() > 0 ? this.width() : void 0, m)[0] ?? "";
1416
1420
  const ascent = m.measureText(firstLine, font).ascent;
@@ -1441,7 +1445,8 @@ var Text = class extends Node {
1441
1445
  const font = {
1442
1446
  family: this.fontFamily,
1443
1447
  size: this.fontSize(),
1444
- weight: this.fontWeight
1448
+ weight: this.fontWeight,
1449
+ ...this.fontStyle === "italic" ? { style: "italic" } : {}
1445
1450
  };
1446
1451
  const maxWidth = this.width();
1447
1452
  const lines = breakLines(text, font, maxWidth > 0 ? maxWidth : void 0, m);
@@ -1478,7 +1483,8 @@ var Text = class extends Node {
1478
1483
  const font = {
1479
1484
  family: this.fontFamily,
1480
1485
  size: this.fontSize(),
1481
- weight: this.fontWeight
1486
+ weight: this.fontWeight,
1487
+ ...this.fontStyle === "italic" ? { style: "italic" } : {}
1482
1488
  };
1483
1489
  const maxWidth = this.width();
1484
1490
  const lines = breakLines(text, font, maxWidth > 0 ? maxWidth : void 0, m);
@@ -1531,7 +1537,8 @@ var Text = class extends Node {
1531
1537
  const font = {
1532
1538
  family: this.fontFamily,
1533
1539
  size: this.fontSize(),
1534
- weight: this.fontWeight
1540
+ weight: this.fontWeight,
1541
+ ...this.fontStyle === "italic" ? { style: "italic" } : {}
1535
1542
  };
1536
1543
  const maxWidth = this.width();
1537
1544
  const lines = breakLines(text, font, maxWidth > 0 ? maxWidth : void 0, m);
@@ -1550,7 +1557,8 @@ var Text = class extends Node {
1550
1557
  const font = {
1551
1558
  family: this.fontFamily,
1552
1559
  size: this.fontSize(),
1553
- weight: this.fontWeight
1560
+ weight: this.fontWeight,
1561
+ ...this.fontStyle === "italic" ? { style: "italic" } : {}
1554
1562
  };
1555
1563
  const maxWidth = this.width();
1556
1564
  const lines = breakLines(this.text(), font, maxWidth > 0 ? maxWidth : void 0, m);
@@ -1598,7 +1606,8 @@ var Text = class extends Node {
1598
1606
  const font = {
1599
1607
  family: this.fontFamily,
1600
1608
  size: this.fontSize(),
1601
- weight: this.fontWeight
1609
+ weight: this.fontWeight,
1610
+ ...this.fontStyle === "italic" ? { style: "italic" } : {}
1602
1611
  };
1603
1612
  const maxWidth = this.width();
1604
1613
  const lines = breakLines(text, font, maxWidth > 0 ? maxWidth : void 0, ctx.measurer);
@@ -1673,7 +1682,8 @@ function revealSchedule(text, reveal, measurer) {
1673
1682
  const font = {
1674
1683
  family: text.fontFamily,
1675
1684
  size: text.fontSize(),
1676
- weight: text.fontWeight
1685
+ weight: text.fontWeight,
1686
+ ...text.fontStyle === "italic" ? { style: "italic" } : {}
1677
1687
  };
1678
1688
  const maxWidth = text.width();
1679
1689
  const lines = breakLines(src, font, maxWidth > 0 ? maxWidth : void 0, m);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glissade/scene",
3
- "version": "0.8.1-pre.1",
3
+ "version": "0.9.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.8.1-pre.1"
23
+ "@glissade/core": "0.9.0-pre.0"
24
24
  },
25
25
  "repository": {
26
26
  "type": "git",