import { MeshInterpolation, MeshPaint as MeshPaint$1, MeshPoint, Paint, Vec2 } from "@glissade/core"; //#region src/matrix.d.ts type Mat2x3 = readonly [number, number, number, number, number, number]; declare const IDENTITY: Mat2x3; declare function multiply(m1: Mat2x3, m2: Mat2x3): Mat2x3; /** Compose translate × rotate × scale (rotation in degrees). */ declare function fromTRS(position: Vec2, rotationDeg: number, scale: Vec2): Mat2x3; /** Inverse affine: [A | t]⁻¹ = [A⁻¹ | −A⁻¹t]; null when degenerate (det 0). */ declare function invert(m: Mat2x3): Mat2x3 | null; declare function applyToPoint(m: Mat2x3, p: Vec2): Vec2; declare function matEquals(a: Mat2x3, b: Mat2x3): boolean; //#endregion //#region src/displayList.d.ts type ResourceId = number; /** * Path data as plain segments (JSON-serializable; backends build Path2D/SkPath): * M/L: point; C: cubic; Q: quadratic; Z: close * E: ellipse arc — cx, cy, rx, ry, rotationRad, startAngleRad, endAngleRad */ type PathSeg = ['M', number, number] | ['L', number, number] | ['C', number, number, number, number, number, number] | ['Q', number, number, number, number] | ['E', number, number, number, number, number, number, number] | ['Z']; type Resource = { kind: 'path'; segs: PathSeg[]; } | { kind: 'image'; assetId: string; } /** One source-grid video frame: backends resolve via their VideoFrameSource registry (§3.8). */ | { kind: 'videoFrame'; assetId: string; mediaT: number; }; type BlendMode = 'source-over' | 'multiply' | 'screen' | 'overlay' | 'darken' | 'lighten'; interface StrokeStyle { width: number; cap?: 'butt' | 'round' | 'square'; join?: 'miter' | 'round' | 'bevel'; miterLimit?: number; dash?: number[]; dashOffset?: number; } interface FontSpec { family: string; size: number; weight?: number; style?: 'normal' | 'italic'; /** * Variable-font axis settings in CSS `font-variation-settings` form * (e.g. `'"wght" 700, "opsz" 14'`). 0.20 STATIC passthrough: applied on the * Skia/export path (`@napi-rs/canvas` exposes `ctx.fontVariationSettings`), * best-effort in the browser (the DOM 2D context has no such property — a * guarded no-op there). OMITTED for default Text, so a node without axes * emits a byte-identical FontSpec (the golden corpus depends on this). * Animatable axes (a `wght` track) are deferred to 1.0 — the string isn't * lerp-able, and a track targeting `/fontVariationSettings` already * hard-throws `UnboundTargetError` (no signal resolves to it). */ fontVariationSettings?: string; /** * Letter-spacing (tracking) in **px**, applied between glyphs. Maps 1:1 to * `ctx.letterSpacing` on the canvas/Skia path (both `@napi-rs/canvas` and the * modern browser 2D context honor it — and it affects `measureText`, so * wrapping stays correct) and to CSS `letter-spacing` on the DOM backend. * OMITTED for default Text, so a node without tracking emits a byte-identical * FontSpec (the golden corpus depends on this). For em-relative tracking pass * `em * fontSize` (px is the engine's unit everywhere else). */ letterSpacing?: number; } /** * Group filters (§3.4): a CLOSED union — validated data, never a CSS * passthrough string — limited to effects both rasterizers implement * faithfully. Cross-backend parity is perceptual (SSIM), not byte-exact: * filters are where rasterizers diverge most. Per-backend output stays * deterministic on the pinned toolchain (golden-tested on Skia). */ type FilterSpec = { kind: 'blur'; /** Gaussian stdDeviation, px; ≥ 0. */ radius: number; } | { kind: 'drop-shadow'; dx: number; dy: number; /** ≥ 0 */ blur: number; color: string; } | { kind: 'brightness'; /** 1 = identity; ≥ 0. */ amount: number; } | { kind: 'contrast'; /** 1 = identity; ≥ 0. */ amount: number; } | { kind: 'saturate'; /** 1 = identity; ≥ 0. */ amount: number; }; declare class FilterValidationError extends Error { constructor(message: string); } /** Document-layer validation: reject unknown kinds and out-of-range params loudly. */ declare function validateFilters(filters: readonly FilterSpec[]): void; /** * Compile the validated union to the canvas 2D `ctx.filter` syntax — both * backends speak it (browser canvas and @napi-rs/canvas/Skia). This is the * ONLY place the CSS-like syntax appears; documents never carry it. */ declare function filtersToCanvasFilter(filters: readonly FilterSpec[]): string; /** * Outer glow as stacked zero-offset drop-shadows — the classic recipe, fully * deterministic on both backends (it is just filters). intensity stacks more * layers; pair with a signal binding to follow an animated fill. */ declare function glow(color: string, radius?: number, intensity?: number): FilterSpec[]; interface Rect { x: number; y: number; w: number; h: number; } /** * Shader effect pass (§3.7): runs over the group's rasterized texture. * EXPLICITLY outside the determinism guarantee — GPU/driver per-pixel * variance breaks distributed reproducibility; export with shaders is * best-effort, single machine. Uniform VALUES are resolved at emit time * (they ride on signals), so the IR stays a plain serializable snapshot. */ interface ShaderRef { /** WGSL fragment module: declare `struct Uniforms` + `@fragment fn effect(@location(0) uv: vec2f) -> @location(0) vec4f`. */ wgsl: string; /** Scalar uniforms, packed as f32 in SORTED KEY ORDER into the Uniforms struct. */ uniforms: Record; /** Named texture inputs: binding name → image/video asset id (the source canvas is binding 0). Reserved for multi-input passes. */ textures?: Record; } type DrawCommand = { op: 'save'; } | { op: 'restore'; } | { op: 'transform'; m: Mat2x3; } | { op: 'clip'; path: ResourceId; rule?: 'nonzero' | 'evenodd'; } | { op: 'fillPath'; path: ResourceId; paint: Paint; } | { op: 'strokePath'; path: ResourceId; paint: Paint; stroke: StrokeStyle; } | { op: 'fillText'; text: string; font: FontSpec; paint: Paint; x: number; y: number; align?: 'left' | 'center' | 'right'; } | { op: 'drawImage'; image: ResourceId; src?: Rect; dst: Rect; smoothing?: boolean; } | { op: 'pushGroup'; opacity: number; blend: BlendMode; filters: FilterSpec[]; shader?: ShaderRef; cacheKey?: string; /** * 0.34 track-matte: this layer is a MATTE for the layer it composites * onto — 'alpha' keeps destination pixels where this layer is opaque * (native destination-in, byte-exact); 'luma' first converts this * layer's luminance to alpha via the shared straight-alpha CPU kernel * (the mesh-kernel discipline), then applies destination-in. Emitted * by trackMatte() inside its isolated outer group; an optional field * on the shader?/cacheKey? extension precedent — BlendMode stays a * closed union. */ matte?: 'alpha' | 'luma'; } | { op: 'popGroup'; }; interface DisplayList { commands: DrawCommand[]; resources: Resource[]; size: { w: number; h: number; }; } interface DisplayListBuilder { push(cmd: DrawCommand): void; resource(res: Resource): ResourceId; /** * §3.5 cacheKey seam — OPTIONAL so non-cache emits and lightweight mock * builders never need them. `createDisplayListBuilder` supplies all three; * `Node.emit` calls them only for `cache:true` nodes when present. */ /** Count of commands emitted so far — a markpoint for cacheKey ranges. */ mark?(): number; /** * A stable hash of the command slice [start, end) plus the FULL content of * every resource those commands reference (not just ids — interned ids are a * per-list detail). Pure function of the slice; identical slices at two times * hash equal, so a static subtree caches. Opaque buffers collapse to a length * marker (mirrors cacheColdAudit's serializer). Undefined for an empty slice. */ cacheKey?(start: number, end: number): string | undefined; /** Stamp a cacheKey onto the pushGroup already emitted at index `i`. */ patchCacheKey?(i: number, key: string): void; /** * OUT-OF-BAND node-identity seam (S1, the DOM-backend readiness prerequisite — * see docs/design/dom-backend.md "Seam 1"). `Node.emit` announces the node it * is about to emit (`enterNode(this.id)`) and announces completion * (`exitNode()`) — a strictly LIFO pair around the whole save…restore slice, * so the instrumented builder can attribute each `push` to the emitting node * and produce a positional `NodeIdStream` ALONGSIDE the DisplayList, never * inside it. Both are OPTIONAL: the default `createDisplayListBuilder` does NOT * implement them, so `Node.emit`'s guarded `out.enterNode?.()` / * `out.exitNode?.()` calls are no-ops on the normal evaluate/render path and * every DrawCommand stays byte-identical (the 262-golden contract). Only the * opt-in `emitWithIds` builder (`@glissade/scene/identity`) supplies them. */ enterNode?(id: string | undefined): void; exitNode?(): void; } declare function createDisplayListBuilder(size: { w: number; h: number; }): DisplayListBuilder & { finish(): DisplayList; }; //#endregion export { Mat2x3 as C, matEquals as D, invert as E, multiply as O, IDENTITY as S, fromTRS as T, StrokeStyle as _, FilterSpec as a, glow as b, MeshInterpolation as c, Paint as d, PathSeg as f, ShaderRef as g, ResourceId as h, DrawCommand as i, MeshPaint$1 as l, Resource as m, DisplayList as n, FilterValidationError as o, Rect as p, DisplayListBuilder as r, FontSpec as s, BlendMode as t, MeshPoint as u, createDisplayListBuilder as v, applyToPoint as w, validateFilters as x, filtersToCanvasFilter as y };