@glissade/scene 0.31.0 → 0.32.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/chart.d.ts +106 -0
- package/dist/chart.js +157 -0
- package/dist/describe.js +32 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/layoutCtors.js +1 -1
- package/dist/nodes.js +372 -372
- package/dist/orient.js +1 -1
- package/dist/scene.js +1 -1
- package/dist/tokens.js +1 -1
- package/dist/type.js +1 -1
- package/package.json +6 -2
package/dist/chart.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { V as NodeProps, i as Group, u as Rect } from "./nodes.js";
|
|
2
|
+
|
|
3
|
+
//#region src/chart.d.ts
|
|
4
|
+
|
|
5
|
+
declare class ChartError extends Error {
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
/** One data row: a plain record. `xKey`/`yKey` name the label + numeric columns. */
|
|
9
|
+
type DataRow = Readonly<Record<string, unknown>>;
|
|
10
|
+
/**
|
|
11
|
+
* A continuous scale: a pure, serializable map from a numeric DOMAIN onto a pixel
|
|
12
|
+
* (or unit) RANGE. `linearScale`/`logScale` produce these. Serializable by shape
|
|
13
|
+
* (`{ id, domain, range }`) so an agent/tool can round-trip it, matching the
|
|
14
|
+
* data-motion card's "scales are value-types" pitch.
|
|
15
|
+
*/
|
|
16
|
+
interface Scale {
|
|
17
|
+
readonly id: 'linear' | 'log';
|
|
18
|
+
readonly domain: readonly [number, number];
|
|
19
|
+
readonly range: readonly [number, number];
|
|
20
|
+
/** Map a domain value onto the range (clamped-free — extrapolates linearly). */
|
|
21
|
+
map(v: number): number;
|
|
22
|
+
}
|
|
23
|
+
/** A categorical BAND scale: N equal bands across a range, each with a `bandwidth`. */
|
|
24
|
+
interface BandScale {
|
|
25
|
+
readonly id: 'band';
|
|
26
|
+
readonly count: number;
|
|
27
|
+
readonly range: readonly [number, number];
|
|
28
|
+
/** The width of one band's drawable area (after padding). */
|
|
29
|
+
readonly bandwidth: number;
|
|
30
|
+
/** The CENTER of band `i` (0-based) within the range. */
|
|
31
|
+
map(i: number): number;
|
|
32
|
+
}
|
|
33
|
+
/** A colour-ramp scale: a numeric domain → an interpolated hex colour STRING. */
|
|
34
|
+
interface ColorScale {
|
|
35
|
+
readonly id: 'color-ramp';
|
|
36
|
+
readonly stops: readonly string[];
|
|
37
|
+
readonly domain: readonly [number, number];
|
|
38
|
+
/** Map a domain value onto its interpolated colour (`#rrggbb`). */
|
|
39
|
+
map(v: number): string;
|
|
40
|
+
}
|
|
41
|
+
/** A linear scale mapping `domain` → `range` (the workhorse for a value axis). */
|
|
42
|
+
declare function linearScale(domain: readonly [number, number], range: readonly [number, number]): Scale;
|
|
43
|
+
/**
|
|
44
|
+
* A base-10 logarithmic scale. Requires a strictly-positive domain (log of a
|
|
45
|
+
* non-positive number is undefined) — throws otherwise, fail-loud.
|
|
46
|
+
*/
|
|
47
|
+
declare function logScale(domain: readonly [number, number], range: readonly [number, number]): Scale;
|
|
48
|
+
/**
|
|
49
|
+
* A categorical band scale: `count` equal bands across `range`, separated by a
|
|
50
|
+
* `padding` fraction (0..1) of each step. `map(i)` is band `i`'s center;
|
|
51
|
+
* `bandwidth` is its drawable width.
|
|
52
|
+
*/
|
|
53
|
+
declare function bandScale(count: number, range: readonly [number, number], padding?: number): BandScale;
|
|
54
|
+
/**
|
|
55
|
+
* A colour ramp over `domain` (default `[0, 1]`): at least two hex stops,
|
|
56
|
+
* interpolated in sRGB byte space. `map(v)` returns `#rrggbb`. Deterministic and
|
|
57
|
+
* pure — the same v yields the same bytes, so a fill driven by a ramp is golden-
|
|
58
|
+
* stable.
|
|
59
|
+
*/
|
|
60
|
+
declare function colorRamp(stops: readonly string[], domain?: readonly [number, number]): ColorScale;
|
|
61
|
+
interface ChartSpec extends NodeProps {
|
|
62
|
+
/** Stable id — REQUIRED; bars bind tracks against `${id}/bars/${i}`. */
|
|
63
|
+
id: string;
|
|
64
|
+
/** The rows to plot (each a plain record). */
|
|
65
|
+
data: readonly DataRow[];
|
|
66
|
+
/** The column naming each bar's category label (used for ordering / count). */
|
|
67
|
+
xKey: string;
|
|
68
|
+
/** The column naming each bar's numeric value. */
|
|
69
|
+
yKey: string;
|
|
70
|
+
/** Total chart width (px) the bands divide. */
|
|
71
|
+
width: number;
|
|
72
|
+
/** Total chart height (px) — a full-value bar fills it. */
|
|
73
|
+
height: number;
|
|
74
|
+
/**
|
|
75
|
+
* The value → height scale. Defaults to `linearScale([0, max(y)], [0, height])`
|
|
76
|
+
* (turnkey: bars are proportional to their value, tallest fills the box).
|
|
77
|
+
*/
|
|
78
|
+
yScale?: Scale;
|
|
79
|
+
/** Gap fraction between bars (0..1) passed to the internal band scale. Default 0.2. */
|
|
80
|
+
bandPadding?: number;
|
|
81
|
+
/**
|
|
82
|
+
* Bar fill: a solid colour string, or a `ColorScale` evaluated at each bar's
|
|
83
|
+
* VALUE (a colour ramp over the data). Default `'#4f8cff'`.
|
|
84
|
+
*/
|
|
85
|
+
fill?: string | ColorScale;
|
|
86
|
+
}
|
|
87
|
+
interface ChartResult {
|
|
88
|
+
/** The wrapping group (center-anchored on its content box, like Grid). */
|
|
89
|
+
readonly node: Group;
|
|
90
|
+
/** The bar nodes in row order — `bars[i]` plots `data[i]`. */
|
|
91
|
+
readonly bars: readonly Rect[];
|
|
92
|
+
/** Ready-to-bind target ids in row order: `${id}/bars/${i}/${prop}`. */
|
|
93
|
+
targets(prop: string): string[];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Build a bar chart from `data` and return a `Group` of bars, each pinned to the
|
|
97
|
+
* axis at its base and sized to its value. Center-anchored (the group origin is
|
|
98
|
+
* the center of the content box), matching every other glissade node's convention.
|
|
99
|
+
*
|
|
100
|
+
* The bars are freshly constructed nodes (never cloned from the caller); their
|
|
101
|
+
* `height`/`fill` are plain signals, so a later track bind against
|
|
102
|
+
* `chart.targets(prop)` wins and drives the reveal.
|
|
103
|
+
*/
|
|
104
|
+
declare function Chart(spec: ChartSpec): ChartResult;
|
|
105
|
+
//#endregion
|
|
106
|
+
export { BandScale, Chart, ChartError, ChartResult, ChartSpec, ColorScale, DataRow, Scale, bandScale, colorRamp, linearScale, logScale };
|
package/dist/chart.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { o as Rect, r as Group } from "./nodes.js";
|
|
2
|
+
//#region src/chart.ts
|
|
3
|
+
var ChartError = class extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "ChartError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
/** A linear scale mapping `domain` → `range` (the workhorse for a value axis). */
|
|
10
|
+
function linearScale(domain, range) {
|
|
11
|
+
const [d0, d1] = domain;
|
|
12
|
+
const [r0, r1] = range;
|
|
13
|
+
const span = d1 - d0;
|
|
14
|
+
return {
|
|
15
|
+
id: "linear",
|
|
16
|
+
domain,
|
|
17
|
+
range,
|
|
18
|
+
map: (v) => span === 0 ? r0 : r0 + (v - d0) / span * (r1 - r0)
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A base-10 logarithmic scale. Requires a strictly-positive domain (log of a
|
|
23
|
+
* non-positive number is undefined) — throws otherwise, fail-loud.
|
|
24
|
+
*/
|
|
25
|
+
function logScale(domain, range) {
|
|
26
|
+
const [d0, d1] = domain;
|
|
27
|
+
const [r0, r1] = range;
|
|
28
|
+
if (d0 <= 0 || d1 <= 0) throw new ChartError(`logScale needs a strictly-positive domain (got [${d0}, ${d1}])`);
|
|
29
|
+
const l0 = Math.log10(d0);
|
|
30
|
+
const lspan = Math.log10(d1) - l0;
|
|
31
|
+
return {
|
|
32
|
+
id: "log",
|
|
33
|
+
domain,
|
|
34
|
+
range,
|
|
35
|
+
map: (v) => {
|
|
36
|
+
if (v <= 0) throw new ChartError(`logScale cannot map a non-positive value (${v})`);
|
|
37
|
+
return lspan === 0 ? r0 : r0 + (Math.log10(v) - l0) / lspan * (r1 - r0);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A categorical band scale: `count` equal bands across `range`, separated by a
|
|
43
|
+
* `padding` fraction (0..1) of each step. `map(i)` is band `i`'s center;
|
|
44
|
+
* `bandwidth` is its drawable width.
|
|
45
|
+
*/
|
|
46
|
+
function bandScale(count, range, padding = .2) {
|
|
47
|
+
if (!Number.isInteger(count) || count < 1) throw new ChartError(`bandScale needs a positive integer count (got ${count})`);
|
|
48
|
+
if (padding < 0 || padding >= 1) throw new ChartError(`bandScale padding must be in [0, 1) (got ${padding})`);
|
|
49
|
+
const [r0, r1] = range;
|
|
50
|
+
const step = (r1 - r0) / count;
|
|
51
|
+
return {
|
|
52
|
+
id: "band",
|
|
53
|
+
count,
|
|
54
|
+
range,
|
|
55
|
+
bandwidth: step * (1 - padding),
|
|
56
|
+
map: (i) => r0 + step * i + step / 2
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** Parse a `#rgb`/`#rrggbb` hex string to `[r, g, b]` bytes; throws on a bad format. */
|
|
60
|
+
function parseHex(hex) {
|
|
61
|
+
const h = hex.trim().replace(/^#/, "");
|
|
62
|
+
const full = h.length === 3 ? h.replace(/(.)/g, "$1$1") : h;
|
|
63
|
+
if (!/^[0-9a-fA-F]{6}$/.test(full)) throw new ChartError(`colorRamp stop '${hex}' is not a #rgb/#rrggbb hex colour`);
|
|
64
|
+
return [
|
|
65
|
+
parseInt(full.slice(0, 2), 16),
|
|
66
|
+
parseInt(full.slice(2, 4), 16),
|
|
67
|
+
parseInt(full.slice(4, 6), 16)
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
function toHex(n) {
|
|
71
|
+
return Math.round(Math.max(0, Math.min(255, n))).toString(16).padStart(2, "0");
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* A colour ramp over `domain` (default `[0, 1]`): at least two hex stops,
|
|
75
|
+
* interpolated in sRGB byte space. `map(v)` returns `#rrggbb`. Deterministic and
|
|
76
|
+
* pure — the same v yields the same bytes, so a fill driven by a ramp is golden-
|
|
77
|
+
* stable.
|
|
78
|
+
*/
|
|
79
|
+
function colorRamp(stops, domain = [0, 1]) {
|
|
80
|
+
if (stops.length < 2) throw new ChartError("colorRamp needs at least two colour stops");
|
|
81
|
+
const rgb = stops.map(parseHex);
|
|
82
|
+
const [d0, d1] = domain;
|
|
83
|
+
const span = d1 - d0;
|
|
84
|
+
return {
|
|
85
|
+
id: "color-ramp",
|
|
86
|
+
stops,
|
|
87
|
+
domain,
|
|
88
|
+
map: (v) => {
|
|
89
|
+
const seg = (span === 0 ? 0 : Math.max(0, Math.min(1, (v - d0) / span))) * (stops.length - 1);
|
|
90
|
+
const i = Math.min(stops.length - 2, Math.floor(seg));
|
|
91
|
+
const f = seg - i;
|
|
92
|
+
const a = rgb[i];
|
|
93
|
+
const b = rgb[i + 1];
|
|
94
|
+
return `#${toHex(a[0] + (b[0] - a[0]) * f)}${toHex(a[1] + (b[1] - a[1]) * f)}${toHex(a[2] + (b[2] - a[2]) * f)}`;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/** Coerce a row's numeric column, failing loud on a non-finite value. */
|
|
99
|
+
function numAt(row, key, i) {
|
|
100
|
+
const raw = row[key];
|
|
101
|
+
const n = typeof raw === "number" ? raw : Number(raw);
|
|
102
|
+
if (!Number.isFinite(n)) throw new ChartError(`Chart row ${i}: ${key}=${JSON.stringify(raw)} is not a finite number`);
|
|
103
|
+
return n;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Build a bar chart from `data` and return a `Group` of bars, each pinned to the
|
|
107
|
+
* axis at its base and sized to its value. Center-anchored (the group origin is
|
|
108
|
+
* the center of the content box), matching every other glissade node's convention.
|
|
109
|
+
*
|
|
110
|
+
* The bars are freshly constructed nodes (never cloned from the caller); their
|
|
111
|
+
* `height`/`fill` are plain signals, so a later track bind against
|
|
112
|
+
* `chart.targets(prop)` wins and drives the reveal.
|
|
113
|
+
*/
|
|
114
|
+
function Chart(spec) {
|
|
115
|
+
const { id, data, xKey, yKey, width, height, bandPadding = .2, fill = "#4f8cff" } = spec;
|
|
116
|
+
if (data.length === 0) throw new ChartError("Chart needs at least one data row");
|
|
117
|
+
if (!(width > 0) || !(height > 0)) throw new ChartError(`Chart needs positive width/height (got ${width}×${height})`);
|
|
118
|
+
const values = data.map((row, i) => numAt(row, yKey, i));
|
|
119
|
+
data.forEach((row, i) => {
|
|
120
|
+
if (row[xKey] === void 0) throw new ChartError(`Chart row ${i}: missing xKey '${xKey}'`);
|
|
121
|
+
});
|
|
122
|
+
const yMax = Math.max(...values, 0);
|
|
123
|
+
const yScale = spec.yScale ?? linearScale([0, yMax === 0 ? 1 : yMax], [0, height]);
|
|
124
|
+
const bands = bandScale(data.length, [0, width], bandPadding);
|
|
125
|
+
const ox = -width / 2;
|
|
126
|
+
const baseline = height / 2;
|
|
127
|
+
const bars = data.map((row, i) => {
|
|
128
|
+
const h = yScale.map(values[i]);
|
|
129
|
+
const barFill = typeof fill === "string" ? fill : fill.map(values[i]);
|
|
130
|
+
return new Rect({
|
|
131
|
+
id: `${id}/bars/${i}`,
|
|
132
|
+
anchor: "bottom",
|
|
133
|
+
position: [ox + bands.map(i), baseline],
|
|
134
|
+
width: bands.bandwidth,
|
|
135
|
+
height: h,
|
|
136
|
+
fill: barFill
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
const node = new Group({
|
|
140
|
+
id,
|
|
141
|
+
children: bars,
|
|
142
|
+
...stripChartOnly(spec)
|
|
143
|
+
});
|
|
144
|
+
const targets = (prop) => bars.map((_, i) => `${id}/bars/${i}/${prop}`);
|
|
145
|
+
return {
|
|
146
|
+
node,
|
|
147
|
+
bars,
|
|
148
|
+
targets
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/** Strip Chart's own props so the rest (position/opacity/anchor/…) pass to the Group. */
|
|
152
|
+
function stripChartOnly(spec) {
|
|
153
|
+
const { id, data, xKey, yKey, width, height, yScale, bandPadding, fill, children, ...nodeProps } = spec;
|
|
154
|
+
return nodeProps;
|
|
155
|
+
}
|
|
156
|
+
//#endregion
|
|
157
|
+
export { Chart, ChartError, bandScale, colorRamp, linearScale, logScale };
|
package/dist/describe.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { D as NODE_CONSTRUCTION_PROP_NAMES, E as BASE_CONSTRUCTION_PROP_NAMES, a as Path, c as Video, i as ImageNode, o as Rect, r as Group, s as Text, t as Circle } from "./nodes.js";
|
|
2
2
|
import { easings, listValueTypes } from "@glissade/core";
|
|
3
3
|
//#region src/describe.ts
|
|
4
4
|
/**
|
|
@@ -22,7 +22,7 @@ import { easings, listValueTypes } from "@glissade/core";
|
|
|
22
22
|
* never pulled onto the base embed path — a scene that never calls `describe()`
|
|
23
23
|
* pays zero bytes for it.
|
|
24
24
|
*/
|
|
25
|
-
const RAW_VERSION = "0.
|
|
25
|
+
const RAW_VERSION = "0.32.0-pre.0";
|
|
26
26
|
const PACKAGE_VERSION = RAW_VERSION.includes("GLISSADE_".concat("VERSION")) ? "0.0.0-dev" : RAW_VERSION;
|
|
27
27
|
/** Arity of a value type's numeric repr: vec2/vec2-arc → 2, number → 1; others (color/paint/path/string/boolean) carry no scalar arity. */
|
|
28
28
|
function arityOf(type) {
|
|
@@ -393,6 +393,36 @@ const HELPERS = [
|
|
|
393
393
|
import: "@glissade/scene/grid",
|
|
394
394
|
usage: "Grid({ columns: number | (number | { fr })[], children: Node[], gap?, columnGap?, rowGap?, cellHeight?, width? }): Group — child[i] → row floor(i/cols), col i%cols"
|
|
395
395
|
},
|
|
396
|
+
{
|
|
397
|
+
name: "Chart",
|
|
398
|
+
summary: "Build-time bar chart: bind a table (rows) → positioned+sized Rect bars, each pinned to the axis and grown from its base, returning a Group. Pure fan-out (like Grid) — animate a reveal with tl.stagger(chart.targets(\"height\"), …) or a colour sweep on \"fill\". Tree-shaken off the base scene index.",
|
|
399
|
+
import: "@glissade/scene/chart",
|
|
400
|
+
usage: "Chart({ id, data: Row[], xKey, yKey, width, height, yScale?, bandPadding?, fill?: string | ColorScale }): { node: Group, bars: Rect[], targets(prop): string[] }"
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
name: "linearScale",
|
|
404
|
+
summary: "A serializable linear scale (value axis): maps a numeric domain onto a pixel/unit range. Pair with Chart({ yScale }). On the @glissade/scene/chart subpath.",
|
|
405
|
+
import: "@glissade/scene/chart",
|
|
406
|
+
usage: "linearScale(domain: [number, number], range: [number, number]): Scale"
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
name: "logScale",
|
|
410
|
+
summary: "A serializable base-10 log scale (strictly-positive domain; throws otherwise) for a value axis. Pair with Chart({ yScale }). On the @glissade/scene/chart subpath.",
|
|
411
|
+
import: "@glissade/scene/chart",
|
|
412
|
+
usage: "logScale(domain: [number, number], range: [number, number]): Scale"
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
name: "bandScale",
|
|
416
|
+
summary: "A categorical band scale: N equal bands across a range with a padding gap, each with a bandwidth. Chart uses this internally for the x axis; exposed for custom layouts. On the @glissade/scene/chart subpath.",
|
|
417
|
+
import: "@glissade/scene/chart",
|
|
418
|
+
usage: "bandScale(count: number, range: [number, number], padding?: number): BandScale"
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
name: "colorRamp",
|
|
422
|
+
summary: "A serializable colour ramp (>=2 hex stops, sRGB-interpolated) over a numeric domain → a #rrggbb string. Pass as Chart({ fill }) to colour bars by value. On the @glissade/scene/chart subpath.",
|
|
423
|
+
import: "@glissade/scene/chart",
|
|
424
|
+
usage: "colorRamp(stops: string[], domain?: [number, number]): ColorScale"
|
|
425
|
+
},
|
|
396
426
|
{
|
|
397
427
|
name: "Stack",
|
|
398
428
|
summary: "Yoga-flexbox layout factory (column by default) — a Layout subclass that stacks children with gap/padding/justify/align. Needs loadYogaLayoutEngine() before mount/render. On the @glissade/scene/layout subpath.",
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { C as Mat2x3, D as matEquals, E as invert, O as multiply, S as IDENTITY, T as fromTRS, _ as StrokeStyle, a as FilterSpec, b as glow, c as MeshInterpolation, d as Paint, f as PathSeg, g as ShaderRef, h as ResourceId, i as DrawCommand, l as MeshPaint, m as Resource, n as DisplayList, o as FilterValidationError, p as Rect$1, r as DisplayListBuilder, s as FontSpec, t as BlendMode, u as MeshPoint, v as createDisplayListBuilder, w as applyToPoint, x as validateFilters, y as filtersToCanvasFilter } from "./displayList.js";
|
|
2
|
-
import { t as collapseReplacer } from "./collapseReplacer.js";
|
|
3
2
|
import { $ as measureWrappedText, A as resolveSketch, B as NodeConstructionError, C as Polyline, D as arcLength, E as SketchValidationError, F as AnchorSpec, G as TextMeasurer, H as PropInit, I as BindablePropTarget, J as __resetEstimateWarnings, K as TextMetricsLite, L as EvalContext, M as sketchStrokes, N as validateHachure, O as flatten, P as validateSketch, Q as isEstimatingMeasurer, R as HitArea, S as HachureSpec, T as SketchStyle, U as resolveAnchor, V as NodeProps, W as MEASURE_QUANTUM_PX, X as breakLines, Y as assertFiniteFontSize, Z as estimatingMeasurer, _ as WordBox, a as ImageNode, b as revealSchedule, c as Path, d as RevealMark, et as quantize, f as ShapeProps, g as VideoProps, h as Video, i as Group, j as roughen, k as hachureLines, l as PathProps, m as TextProps, n as Custom, nt as segmentWords, o as ImageProps, p as Text, q as WrappedTextMetrics, r as GraphemeBox, rt as setDefaultMeasurer, s as LineBox, t as Circle, tt as segmentGraphemes, u as Rect, v as coercePathData, w as ResolvedSketch, x as roundedRectSegs, y as pathFromSegs, z as Node } from "./nodes.js";
|
|
3
|
+
import { t as collapseReplacer } from "./collapseReplacer.js";
|
|
4
4
|
import { a as SceneModule, c as evaluate, i as SceneInit, n as ReservedNodeIdError, o as bindScene, r as Scene, s as createScene, t as DuplicateNodeIdError } from "./scene.js";
|
|
5
5
|
import { a as LayoutEngineMissingError, c as requireLayoutEngine, i as LayoutEngine, l as setLayoutEngine, n as LayoutChildSpec, r as LayoutContainerSpec, s as getLayoutEngine, t as LayoutBox } from "./layoutEngine.js";
|
|
6
6
|
import { BindableSignal, CoverageReport, EaseSpec, FontMode, FontUsage, MeshPaint as MeshPaint$1, Rng, Timeline, Track } from "@glissade/core";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as multiply, A as
|
|
1
|
+
import { $ as multiply, A as __resetEstimateWarnings, B as setDefaultMeasurer, C as Node, F as isEstimatingMeasurer, G as glow, H as FilterValidationError, I as measureWrappedText, J as IDENTITY, K as validateFilters, L as quantize, M as breakLines, N as estimatingMeasurer, Q as matEquals, R as segmentGraphemes, S as validateSketch, T as resolveAnchor, U as createDisplayListBuilder, W as filtersToCanvasFilter, X as fromTRS, Y as applyToPoint, Z as invert, _ as hashStr, a as Path, b as sketchStrokes, c as Video, d as revealSchedule, f as roundedRectSegs, g as hachureLines, h as flatten, i as ImageNode, j as assertFiniteFontSize, k as MEASURE_QUANTUM_PX, l as coercePathData, m as arcLength, n as Custom, o as Rect, p as SketchValidationError, q as collapseReplacer, r as Group, s as Text, t as Circle, u as pathFromSegs, v as resolveSketch, w as NodeConstructionError, x as validateHachure, y as roughen, z as segmentWords } from "./nodes.js";
|
|
2
2
|
import { a as evaluate, i as createScene, n as ReservedNodeIdError, r as bindScene, t as DuplicateNodeIdError } from "./scene.js";
|
|
3
3
|
import { i as setLayoutEngine, n as getLayoutEngine, r as requireLayoutEngine, t as LayoutEngineMissingError } from "./layoutEngine.js";
|
|
4
4
|
import { i as echo, n as motionBlur, r as Echo, t as MotionBlur } from "./motionBlur.js";
|
package/dist/layoutCtors.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { P as fallbackMeasurer, r as Group } from "./nodes.js";
|
|
2
2
|
import { r as requireLayoutEngine } from "./layoutEngine.js";
|
|
3
3
|
import { computed, signal } from "@glissade/core";
|
|
4
4
|
//#region src/layoutCtors.ts
|
package/dist/nodes.js
CHANGED
|
@@ -252,388 +252,101 @@ function createDisplayListBuilder(size) {
|
|
|
252
252
|
};
|
|
253
253
|
}
|
|
254
254
|
//#endregion
|
|
255
|
-
//#region src/
|
|
255
|
+
//#region src/text.ts
|
|
256
256
|
/**
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
* each draw, never as a shared stateful stream).
|
|
257
|
+
* Text measurement + line breaking (DESIGN.md §3.6). Shaping is delegated to
|
|
258
|
+
* the canvas implementation; line breaking is OURS, driven by the injected
|
|
259
|
+
* TextMeasurer so the breaker always measures with the rasterizer that will
|
|
260
|
+
* draw. Layout-feeding measurements are quantized (0.5px) so sub-pixel
|
|
261
|
+
* advance drift between Skia/HarfBuzz versions cannot move whole layouts.
|
|
263
262
|
*/
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
};
|
|
277
|
-
/** Reject unknown kinds / out-of-range params at construction (like validateFilters). */
|
|
278
|
-
function validateSketch(s) {
|
|
279
|
-
if (!KINDS.includes(s.kind)) throw new SketchValidationError(`unknown sketch kind '${String(s.kind)}' (have: ${KINDS.join(", ")})`);
|
|
280
|
-
if (s.width !== void 0 && !(s.width > 0)) throw new SketchValidationError(`sketch width must be > 0, got ${String(s.width)}`);
|
|
281
|
-
if (s.roughness !== void 0 && !(s.roughness >= 0)) throw new SketchValidationError(`sketch roughness must be ≥ 0, got ${String(s.roughness)}`);
|
|
282
|
-
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)}`);
|
|
283
|
-
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");
|
|
263
|
+
/**
|
|
264
|
+
* §3.6 measurement quantum (px). Scene-owned pre-measure quantizes every
|
|
265
|
+
* layout-feeding advance to this grid ONCE, then hands Yoga frozen integers —
|
|
266
|
+
* so sub-pixel measureText drift between Skia/HarfBuzz versions cannot move a
|
|
267
|
+
* whole layout. The single source of truth for the grid; `quantize` rounds to
|
|
268
|
+
* it. (Yoga's `setMeasureFunc` was considered and rejected — see DESIGN.md §3.6.)
|
|
269
|
+
*/
|
|
270
|
+
const MEASURE_QUANTUM_PX = .5;
|
|
271
|
+
/** §3.6 measurement quantum — round to the MEASURE_QUANTUM_PX grid. */
|
|
272
|
+
function quantize(v) {
|
|
273
|
+
return Math.round(v / MEASURE_QUANTUM_PX) * MEASURE_QUANTUM_PX;
|
|
284
274
|
}
|
|
285
|
-
/**
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
};
|
|
298
|
-
case "pencil": return {
|
|
299
|
-
width: s.width ?? 1.5,
|
|
300
|
-
roughness: s.roughness ?? 1,
|
|
301
|
-
passes: s.passes ?? 2
|
|
302
|
-
};
|
|
303
|
-
case "ink": return {
|
|
304
|
-
width: s.width ?? 2.5,
|
|
305
|
-
roughness: s.roughness ?? .8,
|
|
306
|
-
passes: 1
|
|
307
|
-
};
|
|
308
|
-
case "chalk": return {
|
|
309
|
-
width: s.width ?? 3,
|
|
310
|
-
roughness: s.roughness ?? 1.6,
|
|
311
|
-
passes: 1,
|
|
312
|
-
dash: s.dash ?? [6, 5]
|
|
313
|
-
};
|
|
314
|
-
}
|
|
275
|
+
/**
|
|
276
|
+
* FAIL LOUD on a non-measurable FontSpec (§0.24 fail-loud sweep). A `size` that
|
|
277
|
+
* isn't a finite positive number silently yields NaN/0 metrics in the estimating
|
|
278
|
+
* measurers (and a wrong-font fallback in the real backends) → zero-height layout
|
|
279
|
+
* boxes, broken wrapping/reveal, all with NO error — the silent-wrong-result class
|
|
280
|
+
* an agent can't glance-test. The common cause is the field name: the FontSpec
|
|
281
|
+
* field is `size`, NOT `fontSize` (that is the Text node prop). The single guard
|
|
282
|
+
* every measurement entry point (breakLines, measureWrappedText, the backend
|
|
283
|
+
* `measureText`s) routes through, so the contract is enforced uniformly.
|
|
284
|
+
*/
|
|
285
|
+
function assertFiniteFontSize(font, where) {
|
|
286
|
+
if (typeof font.size !== "number" || !Number.isFinite(font.size) || font.size <= 0) throw new Error(`${where}: font.size must be a positive number (got ${JSON.stringify(font.size)}). The FontSpec field is \`size\`, not \`fontSize\` (that is the Text node prop) — pass \`{ family, size }\`.`);
|
|
315
287
|
}
|
|
316
|
-
const cubic = (p0, c1, c2, p1, t) => {
|
|
317
|
-
const mt = 1 - t;
|
|
318
|
-
const a = mt * mt * mt;
|
|
319
|
-
const b = 3 * mt * mt * t;
|
|
320
|
-
const c = 3 * mt * t * t;
|
|
321
|
-
const d = t * t * t;
|
|
322
|
-
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]];
|
|
323
|
-
};
|
|
324
|
-
const quad = (p0, c, p1, t) => {
|
|
325
|
-
const mt = 1 - t;
|
|
326
|
-
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]];
|
|
327
|
-
};
|
|
328
|
-
const ellipse = (cx, cy, rx, ry, rot, ang) => {
|
|
329
|
-
const ex = rx * Math.cos(ang);
|
|
330
|
-
const ey = ry * Math.sin(ang);
|
|
331
|
-
const cos = Math.cos(rot);
|
|
332
|
-
const sin = Math.sin(rot);
|
|
333
|
-
return [cx + ex * cos - ey * sin, cy + ex * sin + ey * cos];
|
|
334
|
-
};
|
|
335
288
|
/**
|
|
336
|
-
*
|
|
337
|
-
* (
|
|
338
|
-
*
|
|
289
|
+
* Estimating fallback measurer — used only when no backend has been injected
|
|
290
|
+
* (e.g. evaluating for IR-level tests). Deterministic but not metrically
|
|
291
|
+
* faithful; mount(), the CLI, and exporters always inject the real one.
|
|
339
292
|
*/
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
293
|
+
let defaultMeasurer = null;
|
|
294
|
+
/**
|
|
295
|
+
* Process-wide fallback measurer for FACTORY-TIME measurement — component
|
|
296
|
+
* factories run before any scene exists, so Text pulls (measuredSize,
|
|
297
|
+
* lineBoxes, wordBoxes) and createScene fall back here before the estimator.
|
|
298
|
+
* Node consumers: `setDefaultMeasurer(createMeasurer({ fonts }))` from
|
|
299
|
+
* @glissade/backend-skia gives factory code the rasterizer's real metrics.
|
|
300
|
+
* Scene-injected measurers (mount/CLI/golden harness) always win.
|
|
301
|
+
*/
|
|
302
|
+
function setDefaultMeasurer(m) {
|
|
303
|
+
defaultMeasurer = m;
|
|
304
|
+
}
|
|
305
|
+
/** The default-or-estimating chain end; internal fallback for measurer pulls. */
|
|
306
|
+
function fallbackMeasurer() {
|
|
307
|
+
return defaultMeasurer ?? estimatingMeasurer;
|
|
308
|
+
}
|
|
309
|
+
const estimatingMeasurer = { measureText(text, font) {
|
|
310
|
+
return {
|
|
311
|
+
width: text.length * font.size * .52,
|
|
312
|
+
ascent: font.size * .8,
|
|
313
|
+
descent: font.size * .2
|
|
358
314
|
};
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
break;
|
|
369
|
-
case "L":
|
|
370
|
-
ensure(px, py).points.push([s[1], s[2]]);
|
|
371
|
-
px = s[1];
|
|
372
|
-
py = s[2];
|
|
373
|
-
break;
|
|
374
|
-
case "C": {
|
|
375
|
-
const c = ensure(px, py);
|
|
376
|
-
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));
|
|
377
|
-
px = s[5];
|
|
378
|
-
py = s[6];
|
|
379
|
-
break;
|
|
380
|
-
}
|
|
381
|
-
case "Q": {
|
|
382
|
-
const c = ensure(px, py);
|
|
383
|
-
for (let k = 1; k <= steps; k++) c.points.push(quad([px, py], [s[1], s[2]], [s[3], s[4]], k / steps));
|
|
384
|
-
px = s[3];
|
|
385
|
-
py = s[4];
|
|
386
|
-
break;
|
|
387
|
-
}
|
|
388
|
-
case "E": {
|
|
389
|
-
const [, cx, cy, rx, ry, rot, a0, a1] = s;
|
|
390
|
-
const begin = ellipse(cx, cy, rx, ry, rot, a0);
|
|
391
|
-
const c = ensure(begin[0], begin[1]);
|
|
392
|
-
for (let k = 1; k <= steps; k++) c.points.push(ellipse(cx, cy, rx, ry, rot, a0 + (a1 - a0) * (k / steps)));
|
|
393
|
-
const end = ellipse(cx, cy, rx, ry, rot, a1);
|
|
394
|
-
px = end[0];
|
|
395
|
-
py = end[1];
|
|
396
|
-
break;
|
|
397
|
-
}
|
|
398
|
-
case "Z":
|
|
399
|
-
if (cur) {
|
|
400
|
-
cur.points.push([sx, sy]);
|
|
401
|
-
cur.closed = true;
|
|
402
|
-
px = sx;
|
|
403
|
-
py = sy;
|
|
404
|
-
}
|
|
405
|
-
break;
|
|
406
|
-
}
|
|
407
|
-
return polys.filter((p) => p.points.length > 0);
|
|
315
|
+
} };
|
|
316
|
+
/**
|
|
317
|
+
* True when `m` is the per-character ESTIMATING fallback (the module singleton)
|
|
318
|
+
* — i.e. no real backend measurer and no registered `defaultMeasurer` was
|
|
319
|
+
* available. Identity-compare so a real backend or a `setDefaultMeasurer`-
|
|
320
|
+
* registered measurer never trips it.
|
|
321
|
+
*/
|
|
322
|
+
function isEstimatingMeasurer(m) {
|
|
323
|
+
return m === estimatingMeasurer;
|
|
408
324
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
325
|
+
const warnedEstimate = /* @__PURE__ */ new Set();
|
|
326
|
+
/**
|
|
327
|
+
* One-shot dev-warning when a build-time geometry getter resolved its measurer
|
|
328
|
+
* to the rough per-character estimate (no backend, no `setDefaultMeasurer`).
|
|
329
|
+
* `site` keys the de-dupe so each distinct caller warns at most once. Silent
|
|
330
|
+
* when a real measurer is in play — the estimate is the only footgun here.
|
|
331
|
+
*/
|
|
332
|
+
function warnIfEstimating(m, site) {
|
|
333
|
+
if (!isEstimatingMeasurer(m)) return;
|
|
334
|
+
if (warnedEstimate.has(site)) return;
|
|
335
|
+
warnedEstimate.add(site);
|
|
336
|
+
emitDevWarning(`${site}: no text measurer available — using a rough per-character estimate; pass { measurer } or call after setTextMeasurer()/setDefaultMeasurer() for exact layout.`);
|
|
414
337
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
338
|
+
/**
|
|
339
|
+
* Test-only: clear the one-shot de-dupe so the estimate warning can re-assert.
|
|
340
|
+
* @internal
|
|
341
|
+
*/
|
|
342
|
+
function __resetEstimateWarnings() {
|
|
343
|
+
warnedEstimate.clear();
|
|
418
344
|
}
|
|
345
|
+
let wordSegmenter;
|
|
419
346
|
/**
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
*/
|
|
424
|
-
function hachureLines(segs, spec, rng) {
|
|
425
|
-
const polys = flatten(segs);
|
|
426
|
-
let minX = Infinity;
|
|
427
|
-
let minY = Infinity;
|
|
428
|
-
let maxX = -Infinity;
|
|
429
|
-
let maxY = -Infinity;
|
|
430
|
-
for (const p of polys) for (const [x, y] of p.points) {
|
|
431
|
-
if (x < minX) minX = x;
|
|
432
|
-
if (y < minY) minY = y;
|
|
433
|
-
if (x > maxX) maxX = x;
|
|
434
|
-
if (y > maxY) maxY = y;
|
|
435
|
-
}
|
|
436
|
-
if (!Number.isFinite(minX)) return [];
|
|
437
|
-
const cx = (minX + maxX) / 2;
|
|
438
|
-
const cy = (minY + maxY) / 2;
|
|
439
|
-
const ca = Math.cos(spec.angleRad);
|
|
440
|
-
const sa = Math.sin(spec.angleRad);
|
|
441
|
-
const toRot = (x, y) => [(x - cx) * ca + (y - cy) * sa, -(x - cx) * sa + (y - cy) * ca];
|
|
442
|
-
const fromRot = (x, y) => [cx + x * ca - y * sa, cy + x * sa + y * ca];
|
|
443
|
-
let rMinX = Infinity;
|
|
444
|
-
let rMinY = Infinity;
|
|
445
|
-
let rMaxX = -Infinity;
|
|
446
|
-
let rMaxY = -Infinity;
|
|
447
|
-
const corners = [
|
|
448
|
-
[minX, minY],
|
|
449
|
-
[maxX, minY],
|
|
450
|
-
[maxX, maxY],
|
|
451
|
-
[minX, maxY]
|
|
452
|
-
];
|
|
453
|
-
for (const [x, y] of corners) {
|
|
454
|
-
const [rx, ry] = toRot(x, y);
|
|
455
|
-
if (rx < rMinX) rMinX = rx;
|
|
456
|
-
if (ry < rMinY) rMinY = ry;
|
|
457
|
-
if (rx > rMaxX) rMaxX = rx;
|
|
458
|
-
if (ry > rMaxY) rMaxY = ry;
|
|
459
|
-
}
|
|
460
|
-
const rough = spec.roughness ?? 1;
|
|
461
|
-
const jit = () => (rng() * 2 - 1) * rough;
|
|
462
|
-
const out = [];
|
|
463
|
-
for (let y = rMinY + spec.gap / 2; y < rMaxY; y += spec.gap) {
|
|
464
|
-
const a = fromRot(rMinX, y + jit());
|
|
465
|
-
const b = fromRot(rMaxX, y + jit());
|
|
466
|
-
out.push([
|
|
467
|
-
"M",
|
|
468
|
-
a[0],
|
|
469
|
-
a[1]
|
|
470
|
-
], [
|
|
471
|
-
"L",
|
|
472
|
-
b[0],
|
|
473
|
-
b[1]
|
|
474
|
-
]);
|
|
475
|
-
}
|
|
476
|
-
return out;
|
|
477
|
-
}
|
|
478
|
-
/**
|
|
479
|
-
* Roughen a path into hand-drawn stroke passes. Each segment becomes a bowed,
|
|
480
|
-
* jittered quadratic; `passes` overlay slightly different jitters for the
|
|
481
|
-
* built-up look. `rng` must be a freshly seeded generator (the caller reseeds
|
|
482
|
-
* per draw from a stable seed, so evaluate() stays pure).
|
|
483
|
-
*/
|
|
484
|
-
function roughen(segs, style, rng) {
|
|
485
|
-
const resolved = resolveSketch(style);
|
|
486
|
-
const polys = flatten(segs);
|
|
487
|
-
const jit = () => (rng() * 2 - 1) * resolved.roughness;
|
|
488
|
-
const strokes = [];
|
|
489
|
-
for (let pass = 0; pass < resolved.passes; pass++) {
|
|
490
|
-
const out = [];
|
|
491
|
-
for (const poly of polys) {
|
|
492
|
-
const pts = poly.points;
|
|
493
|
-
if (pts.length < 2) continue;
|
|
494
|
-
let ax = pts[0][0] + jit();
|
|
495
|
-
let ay = pts[0][1] + jit();
|
|
496
|
-
out.push([
|
|
497
|
-
"M",
|
|
498
|
-
ax,
|
|
499
|
-
ay
|
|
500
|
-
]);
|
|
501
|
-
for (let i = 1; i < pts.length; i++) {
|
|
502
|
-
const bx = pts[i][0] + jit();
|
|
503
|
-
const by = pts[i][1] + jit();
|
|
504
|
-
const dx = bx - ax;
|
|
505
|
-
const dy = by - ay;
|
|
506
|
-
const len = Math.hypot(dx, dy) || 1;
|
|
507
|
-
const bow = jit() * .5;
|
|
508
|
-
const mx = (ax + bx) / 2 + -dy / len * bow;
|
|
509
|
-
const my = (ay + by) / 2 + dx / len * bow;
|
|
510
|
-
out.push([
|
|
511
|
-
"Q",
|
|
512
|
-
mx,
|
|
513
|
-
my,
|
|
514
|
-
bx,
|
|
515
|
-
by
|
|
516
|
-
]);
|
|
517
|
-
ax = bx;
|
|
518
|
-
ay = by;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
strokes.push(out);
|
|
522
|
-
}
|
|
523
|
-
return {
|
|
524
|
-
strokes,
|
|
525
|
-
resolved
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
/** FNV-1a 32-bit — a stable per-shape sketch seed from its id. */
|
|
529
|
-
function hashStr(s) {
|
|
530
|
-
let h = 2166136261;
|
|
531
|
-
for (let i = 0; i < s.length; i++) {
|
|
532
|
-
h ^= s.charCodeAt(i);
|
|
533
|
-
h = Math.imul(h, 16777619);
|
|
534
|
-
}
|
|
535
|
-
return h >>> 0;
|
|
536
|
-
}
|
|
537
|
-
/** Convenience: the rough stroke passes for a path at a given seed. */
|
|
538
|
-
function sketchStrokes(segs, style, seed) {
|
|
539
|
-
return roughen(segs, style, random(seed >>> 0)).strokes;
|
|
540
|
-
}
|
|
541
|
-
//#endregion
|
|
542
|
-
//#region src/text.ts
|
|
543
|
-
/**
|
|
544
|
-
* Text measurement + line breaking (DESIGN.md §3.6). Shaping is delegated to
|
|
545
|
-
* the canvas implementation; line breaking is OURS, driven by the injected
|
|
546
|
-
* TextMeasurer so the breaker always measures with the rasterizer that will
|
|
547
|
-
* draw. Layout-feeding measurements are quantized (0.5px) so sub-pixel
|
|
548
|
-
* advance drift between Skia/HarfBuzz versions cannot move whole layouts.
|
|
549
|
-
*/
|
|
550
|
-
/**
|
|
551
|
-
* §3.6 measurement quantum (px). Scene-owned pre-measure quantizes every
|
|
552
|
-
* layout-feeding advance to this grid ONCE, then hands Yoga frozen integers —
|
|
553
|
-
* so sub-pixel measureText drift between Skia/HarfBuzz versions cannot move a
|
|
554
|
-
* whole layout. The single source of truth for the grid; `quantize` rounds to
|
|
555
|
-
* it. (Yoga's `setMeasureFunc` was considered and rejected — see DESIGN.md §3.6.)
|
|
556
|
-
*/
|
|
557
|
-
const MEASURE_QUANTUM_PX = .5;
|
|
558
|
-
/** §3.6 measurement quantum — round to the MEASURE_QUANTUM_PX grid. */
|
|
559
|
-
function quantize(v) {
|
|
560
|
-
return Math.round(v / MEASURE_QUANTUM_PX) * MEASURE_QUANTUM_PX;
|
|
561
|
-
}
|
|
562
|
-
/**
|
|
563
|
-
* FAIL LOUD on a non-measurable FontSpec (§0.24 fail-loud sweep). A `size` that
|
|
564
|
-
* isn't a finite positive number silently yields NaN/0 metrics in the estimating
|
|
565
|
-
* measurers (and a wrong-font fallback in the real backends) → zero-height layout
|
|
566
|
-
* boxes, broken wrapping/reveal, all with NO error — the silent-wrong-result class
|
|
567
|
-
* an agent can't glance-test. The common cause is the field name: the FontSpec
|
|
568
|
-
* field is `size`, NOT `fontSize` (that is the Text node prop). The single guard
|
|
569
|
-
* every measurement entry point (breakLines, measureWrappedText, the backend
|
|
570
|
-
* `measureText`s) routes through, so the contract is enforced uniformly.
|
|
571
|
-
*/
|
|
572
|
-
function assertFiniteFontSize(font, where) {
|
|
573
|
-
if (typeof font.size !== "number" || !Number.isFinite(font.size) || font.size <= 0) throw new Error(`${where}: font.size must be a positive number (got ${JSON.stringify(font.size)}). The FontSpec field is \`size\`, not \`fontSize\` (that is the Text node prop) — pass \`{ family, size }\`.`);
|
|
574
|
-
}
|
|
575
|
-
/**
|
|
576
|
-
* Estimating fallback measurer — used only when no backend has been injected
|
|
577
|
-
* (e.g. evaluating for IR-level tests). Deterministic but not metrically
|
|
578
|
-
* faithful; mount(), the CLI, and exporters always inject the real one.
|
|
579
|
-
*/
|
|
580
|
-
let defaultMeasurer = null;
|
|
581
|
-
/**
|
|
582
|
-
* Process-wide fallback measurer for FACTORY-TIME measurement — component
|
|
583
|
-
* factories run before any scene exists, so Text pulls (measuredSize,
|
|
584
|
-
* lineBoxes, wordBoxes) and createScene fall back here before the estimator.
|
|
585
|
-
* Node consumers: `setDefaultMeasurer(createMeasurer({ fonts }))` from
|
|
586
|
-
* @glissade/backend-skia gives factory code the rasterizer's real metrics.
|
|
587
|
-
* Scene-injected measurers (mount/CLI/golden harness) always win.
|
|
588
|
-
*/
|
|
589
|
-
function setDefaultMeasurer(m) {
|
|
590
|
-
defaultMeasurer = m;
|
|
591
|
-
}
|
|
592
|
-
/** The default-or-estimating chain end; internal fallback for measurer pulls. */
|
|
593
|
-
function fallbackMeasurer() {
|
|
594
|
-
return defaultMeasurer ?? estimatingMeasurer;
|
|
595
|
-
}
|
|
596
|
-
const estimatingMeasurer = { measureText(text, font) {
|
|
597
|
-
return {
|
|
598
|
-
width: text.length * font.size * .52,
|
|
599
|
-
ascent: font.size * .8,
|
|
600
|
-
descent: font.size * .2
|
|
601
|
-
};
|
|
602
|
-
} };
|
|
603
|
-
/**
|
|
604
|
-
* True when `m` is the per-character ESTIMATING fallback (the module singleton)
|
|
605
|
-
* — i.e. no real backend measurer and no registered `defaultMeasurer` was
|
|
606
|
-
* available. Identity-compare so a real backend or a `setDefaultMeasurer`-
|
|
607
|
-
* registered measurer never trips it.
|
|
608
|
-
*/
|
|
609
|
-
function isEstimatingMeasurer(m) {
|
|
610
|
-
return m === estimatingMeasurer;
|
|
611
|
-
}
|
|
612
|
-
const warnedEstimate = /* @__PURE__ */ new Set();
|
|
613
|
-
/**
|
|
614
|
-
* One-shot dev-warning when a build-time geometry getter resolved its measurer
|
|
615
|
-
* to the rough per-character estimate (no backend, no `setDefaultMeasurer`).
|
|
616
|
-
* `site` keys the de-dupe so each distinct caller warns at most once. Silent
|
|
617
|
-
* when a real measurer is in play — the estimate is the only footgun here.
|
|
618
|
-
*/
|
|
619
|
-
function warnIfEstimating(m, site) {
|
|
620
|
-
if (!isEstimatingMeasurer(m)) return;
|
|
621
|
-
if (warnedEstimate.has(site)) return;
|
|
622
|
-
warnedEstimate.add(site);
|
|
623
|
-
emitDevWarning(`${site}: no text measurer available — using a rough per-character estimate; pass { measurer } or call after setTextMeasurer()/setDefaultMeasurer() for exact layout.`);
|
|
624
|
-
}
|
|
625
|
-
/**
|
|
626
|
-
* Test-only: clear the one-shot de-dupe so the estimate warning can re-assert.
|
|
627
|
-
* @internal
|
|
628
|
-
*/
|
|
629
|
-
function __resetEstimateWarnings() {
|
|
630
|
-
warnedEstimate.clear();
|
|
631
|
-
}
|
|
632
|
-
let wordSegmenter;
|
|
633
|
-
/**
|
|
634
|
-
* The draw-path word segmentation (Intl.Segmenter boundaries, punctuation
|
|
635
|
-
* glued to its predecessor) — exported so Text.wordBoxes() boxes EXACTLY the
|
|
636
|
-
* units the breaker flows.
|
|
347
|
+
* The draw-path word segmentation (Intl.Segmenter boundaries, punctuation
|
|
348
|
+
* glued to its predecessor) — exported so Text.wordBoxes() boxes EXACTLY the
|
|
349
|
+
* units the breaker flows.
|
|
637
350
|
*/
|
|
638
351
|
function segmentWords(text) {
|
|
639
352
|
if (wordSegmenter === void 0) wordSegmenter = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter(void 0, { granularity: "word" }) : null;
|
|
@@ -1128,6 +841,293 @@ var Node = class {
|
|
|
1128
841
|
}
|
|
1129
842
|
};
|
|
1130
843
|
//#endregion
|
|
844
|
+
//#region src/sketch.ts
|
|
845
|
+
/**
|
|
846
|
+
* Hand-drawn stroke styles via GEOMETRIC roughening — not raster textures. A
|
|
847
|
+
* shape's outline is flattened to polylines, then each segment is redrawn as a
|
|
848
|
+
* slightly jittered, bowed stroke, overlaid in a few passes. Because it's pure
|
|
849
|
+
* path math seeded by a stable per-shape seed, the result is byte-identical on
|
|
850
|
+
* both backends and re-evaluates deterministically (the seed is consumed fresh
|
|
851
|
+
* each draw, never as a shared stateful stream).
|
|
852
|
+
*/
|
|
853
|
+
const KINDS = [
|
|
854
|
+
"marker",
|
|
855
|
+
"crayon",
|
|
856
|
+
"pencil",
|
|
857
|
+
"ink",
|
|
858
|
+
"chalk"
|
|
859
|
+
];
|
|
860
|
+
var SketchValidationError = class extends Error {
|
|
861
|
+
constructor(message) {
|
|
862
|
+
super(message);
|
|
863
|
+
this.name = "SketchValidationError";
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
/** Reject unknown kinds / out-of-range params at construction (like validateFilters). */
|
|
867
|
+
function validateSketch(s) {
|
|
868
|
+
if (!KINDS.includes(s.kind)) throw new SketchValidationError(`unknown sketch kind '${String(s.kind)}' (have: ${KINDS.join(", ")})`);
|
|
869
|
+
if (s.width !== void 0 && !(s.width > 0)) throw new SketchValidationError(`sketch width must be > 0, got ${String(s.width)}`);
|
|
870
|
+
if (s.roughness !== void 0 && !(s.roughness >= 0)) throw new SketchValidationError(`sketch roughness must be ≥ 0, got ${String(s.roughness)}`);
|
|
871
|
+
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)}`);
|
|
872
|
+
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");
|
|
873
|
+
}
|
|
874
|
+
/** Per-kind defaults — the character of each look. */
|
|
875
|
+
function resolveSketch(s) {
|
|
876
|
+
switch (s.kind) {
|
|
877
|
+
case "marker": return {
|
|
878
|
+
width: s.width ?? 8,
|
|
879
|
+
roughness: s.roughness ?? 1.2,
|
|
880
|
+
passes: 2
|
|
881
|
+
};
|
|
882
|
+
case "crayon": return {
|
|
883
|
+
width: s.width ?? 4,
|
|
884
|
+
roughness: s.roughness ?? 2.4,
|
|
885
|
+
passes: s.passes ?? 3
|
|
886
|
+
};
|
|
887
|
+
case "pencil": return {
|
|
888
|
+
width: s.width ?? 1.5,
|
|
889
|
+
roughness: s.roughness ?? 1,
|
|
890
|
+
passes: s.passes ?? 2
|
|
891
|
+
};
|
|
892
|
+
case "ink": return {
|
|
893
|
+
width: s.width ?? 2.5,
|
|
894
|
+
roughness: s.roughness ?? .8,
|
|
895
|
+
passes: 1
|
|
896
|
+
};
|
|
897
|
+
case "chalk": return {
|
|
898
|
+
width: s.width ?? 3,
|
|
899
|
+
roughness: s.roughness ?? 1.6,
|
|
900
|
+
passes: 1,
|
|
901
|
+
dash: s.dash ?? [6, 5]
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
const cubic = (p0, c1, c2, p1, t) => {
|
|
906
|
+
const mt = 1 - t;
|
|
907
|
+
const a = mt * mt * mt;
|
|
908
|
+
const b = 3 * mt * mt * t;
|
|
909
|
+
const c = 3 * mt * t * t;
|
|
910
|
+
const d = t * t * t;
|
|
911
|
+
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]];
|
|
912
|
+
};
|
|
913
|
+
const quad = (p0, c, p1, t) => {
|
|
914
|
+
const mt = 1 - t;
|
|
915
|
+
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]];
|
|
916
|
+
};
|
|
917
|
+
const ellipse = (cx, cy, rx, ry, rot, ang) => {
|
|
918
|
+
const ex = rx * Math.cos(ang);
|
|
919
|
+
const ey = ry * Math.sin(ang);
|
|
920
|
+
const cos = Math.cos(rot);
|
|
921
|
+
const sin = Math.sin(rot);
|
|
922
|
+
return [cx + ex * cos - ey * sin, cy + ex * sin + ey * cos];
|
|
923
|
+
};
|
|
924
|
+
/**
|
|
925
|
+
* Flatten a path to polylines — de Casteljau for C/Q, arc sampling for E
|
|
926
|
+
* (Circle and rounded-rect corners are 'E' segments, so this MUST handle them
|
|
927
|
+
* or those shapes roughen wrong). `steps` is the samples per curved segment.
|
|
928
|
+
*/
|
|
929
|
+
function flatten(segs, steps = 16) {
|
|
930
|
+
const polys = [];
|
|
931
|
+
let cur = null;
|
|
932
|
+
let px = 0;
|
|
933
|
+
let py = 0;
|
|
934
|
+
let sx = 0;
|
|
935
|
+
let sy = 0;
|
|
936
|
+
const ensure = (x, y) => {
|
|
937
|
+
if (!cur) {
|
|
938
|
+
cur = {
|
|
939
|
+
points: [[x, y]],
|
|
940
|
+
closed: false
|
|
941
|
+
};
|
|
942
|
+
polys.push(cur);
|
|
943
|
+
sx = x;
|
|
944
|
+
sy = y;
|
|
945
|
+
}
|
|
946
|
+
return cur;
|
|
947
|
+
};
|
|
948
|
+
for (const s of segs) switch (s[0]) {
|
|
949
|
+
case "M":
|
|
950
|
+
cur = {
|
|
951
|
+
points: [[s[1], s[2]]],
|
|
952
|
+
closed: false
|
|
953
|
+
};
|
|
954
|
+
polys.push(cur);
|
|
955
|
+
px = sx = s[1];
|
|
956
|
+
py = sy = s[2];
|
|
957
|
+
break;
|
|
958
|
+
case "L":
|
|
959
|
+
ensure(px, py).points.push([s[1], s[2]]);
|
|
960
|
+
px = s[1];
|
|
961
|
+
py = s[2];
|
|
962
|
+
break;
|
|
963
|
+
case "C": {
|
|
964
|
+
const c = ensure(px, py);
|
|
965
|
+
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));
|
|
966
|
+
px = s[5];
|
|
967
|
+
py = s[6];
|
|
968
|
+
break;
|
|
969
|
+
}
|
|
970
|
+
case "Q": {
|
|
971
|
+
const c = ensure(px, py);
|
|
972
|
+
for (let k = 1; k <= steps; k++) c.points.push(quad([px, py], [s[1], s[2]], [s[3], s[4]], k / steps));
|
|
973
|
+
px = s[3];
|
|
974
|
+
py = s[4];
|
|
975
|
+
break;
|
|
976
|
+
}
|
|
977
|
+
case "E": {
|
|
978
|
+
const [, cx, cy, rx, ry, rot, a0, a1] = s;
|
|
979
|
+
const begin = ellipse(cx, cy, rx, ry, rot, a0);
|
|
980
|
+
const c = ensure(begin[0], begin[1]);
|
|
981
|
+
for (let k = 1; k <= steps; k++) c.points.push(ellipse(cx, cy, rx, ry, rot, a0 + (a1 - a0) * (k / steps)));
|
|
982
|
+
const end = ellipse(cx, cy, rx, ry, rot, a1);
|
|
983
|
+
px = end[0];
|
|
984
|
+
py = end[1];
|
|
985
|
+
break;
|
|
986
|
+
}
|
|
987
|
+
case "Z":
|
|
988
|
+
if (cur) {
|
|
989
|
+
cur.points.push([sx, sy]);
|
|
990
|
+
cur.closed = true;
|
|
991
|
+
px = sx;
|
|
992
|
+
py = sy;
|
|
993
|
+
}
|
|
994
|
+
break;
|
|
995
|
+
}
|
|
996
|
+
return polys.filter((p) => p.points.length > 0);
|
|
997
|
+
}
|
|
998
|
+
/** Total length of a flattened polyline (for draw-on dashing). */
|
|
999
|
+
function arcLength(poly) {
|
|
1000
|
+
let len = 0;
|
|
1001
|
+
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]);
|
|
1002
|
+
return len;
|
|
1003
|
+
}
|
|
1004
|
+
function validateHachure(h) {
|
|
1005
|
+
if (!(h.gap > 0)) throw new SketchValidationError(`hachure gap must be > 0, got ${String(h.gap)}`);
|
|
1006
|
+
if (h.roughness !== void 0 && !(h.roughness >= 0)) throw new SketchValidationError(`hachure roughness must be ≥ 0, got ${String(h.roughness)}`);
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Parallel hatch lines covering a path's bounding box at `angleRad`, spaced
|
|
1010
|
+
* `gap`, lightly jittered. Returned as `M/L` segments to be stroked INSIDE a
|
|
1011
|
+
* clip of the shape (the caller emits the clip). Pure; `rng` reseeded per draw.
|
|
1012
|
+
*/
|
|
1013
|
+
function hachureLines(segs, spec, rng) {
|
|
1014
|
+
const polys = flatten(segs);
|
|
1015
|
+
let minX = Infinity;
|
|
1016
|
+
let minY = Infinity;
|
|
1017
|
+
let maxX = -Infinity;
|
|
1018
|
+
let maxY = -Infinity;
|
|
1019
|
+
for (const p of polys) for (const [x, y] of p.points) {
|
|
1020
|
+
if (x < minX) minX = x;
|
|
1021
|
+
if (y < minY) minY = y;
|
|
1022
|
+
if (x > maxX) maxX = x;
|
|
1023
|
+
if (y > maxY) maxY = y;
|
|
1024
|
+
}
|
|
1025
|
+
if (!Number.isFinite(minX)) return [];
|
|
1026
|
+
const cx = (minX + maxX) / 2;
|
|
1027
|
+
const cy = (minY + maxY) / 2;
|
|
1028
|
+
const ca = Math.cos(spec.angleRad);
|
|
1029
|
+
const sa = Math.sin(spec.angleRad);
|
|
1030
|
+
const toRot = (x, y) => [(x - cx) * ca + (y - cy) * sa, -(x - cx) * sa + (y - cy) * ca];
|
|
1031
|
+
const fromRot = (x, y) => [cx + x * ca - y * sa, cy + x * sa + y * ca];
|
|
1032
|
+
let rMinX = Infinity;
|
|
1033
|
+
let rMinY = Infinity;
|
|
1034
|
+
let rMaxX = -Infinity;
|
|
1035
|
+
let rMaxY = -Infinity;
|
|
1036
|
+
const corners = [
|
|
1037
|
+
[minX, minY],
|
|
1038
|
+
[maxX, minY],
|
|
1039
|
+
[maxX, maxY],
|
|
1040
|
+
[minX, maxY]
|
|
1041
|
+
];
|
|
1042
|
+
for (const [x, y] of corners) {
|
|
1043
|
+
const [rx, ry] = toRot(x, y);
|
|
1044
|
+
if (rx < rMinX) rMinX = rx;
|
|
1045
|
+
if (ry < rMinY) rMinY = ry;
|
|
1046
|
+
if (rx > rMaxX) rMaxX = rx;
|
|
1047
|
+
if (ry > rMaxY) rMaxY = ry;
|
|
1048
|
+
}
|
|
1049
|
+
const rough = spec.roughness ?? 1;
|
|
1050
|
+
const jit = () => (rng() * 2 - 1) * rough;
|
|
1051
|
+
const out = [];
|
|
1052
|
+
for (let y = rMinY + spec.gap / 2; y < rMaxY; y += spec.gap) {
|
|
1053
|
+
const a = fromRot(rMinX, y + jit());
|
|
1054
|
+
const b = fromRot(rMaxX, y + jit());
|
|
1055
|
+
out.push([
|
|
1056
|
+
"M",
|
|
1057
|
+
a[0],
|
|
1058
|
+
a[1]
|
|
1059
|
+
], [
|
|
1060
|
+
"L",
|
|
1061
|
+
b[0],
|
|
1062
|
+
b[1]
|
|
1063
|
+
]);
|
|
1064
|
+
}
|
|
1065
|
+
return out;
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Roughen a path into hand-drawn stroke passes. Each segment becomes a bowed,
|
|
1069
|
+
* jittered quadratic; `passes` overlay slightly different jitters for the
|
|
1070
|
+
* built-up look. `rng` must be a freshly seeded generator (the caller reseeds
|
|
1071
|
+
* per draw from a stable seed, so evaluate() stays pure).
|
|
1072
|
+
*/
|
|
1073
|
+
function roughen(segs, style, rng) {
|
|
1074
|
+
const resolved = resolveSketch(style);
|
|
1075
|
+
const polys = flatten(segs);
|
|
1076
|
+
const jit = () => (rng() * 2 - 1) * resolved.roughness;
|
|
1077
|
+
const strokes = [];
|
|
1078
|
+
for (let pass = 0; pass < resolved.passes; pass++) {
|
|
1079
|
+
const out = [];
|
|
1080
|
+
for (const poly of polys) {
|
|
1081
|
+
const pts = poly.points;
|
|
1082
|
+
if (pts.length < 2) continue;
|
|
1083
|
+
let ax = pts[0][0] + jit();
|
|
1084
|
+
let ay = pts[0][1] + jit();
|
|
1085
|
+
out.push([
|
|
1086
|
+
"M",
|
|
1087
|
+
ax,
|
|
1088
|
+
ay
|
|
1089
|
+
]);
|
|
1090
|
+
for (let i = 1; i < pts.length; i++) {
|
|
1091
|
+
const bx = pts[i][0] + jit();
|
|
1092
|
+
const by = pts[i][1] + jit();
|
|
1093
|
+
const dx = bx - ax;
|
|
1094
|
+
const dy = by - ay;
|
|
1095
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
1096
|
+
const bow = jit() * .5;
|
|
1097
|
+
const mx = (ax + bx) / 2 + -dy / len * bow;
|
|
1098
|
+
const my = (ay + by) / 2 + dx / len * bow;
|
|
1099
|
+
out.push([
|
|
1100
|
+
"Q",
|
|
1101
|
+
mx,
|
|
1102
|
+
my,
|
|
1103
|
+
bx,
|
|
1104
|
+
by
|
|
1105
|
+
]);
|
|
1106
|
+
ax = bx;
|
|
1107
|
+
ay = by;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
strokes.push(out);
|
|
1111
|
+
}
|
|
1112
|
+
return {
|
|
1113
|
+
strokes,
|
|
1114
|
+
resolved
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
/** FNV-1a 32-bit — a stable per-shape sketch seed from its id. */
|
|
1118
|
+
function hashStr(s) {
|
|
1119
|
+
let h = 2166136261;
|
|
1120
|
+
for (let i = 0; i < s.length; i++) {
|
|
1121
|
+
h ^= s.charCodeAt(i);
|
|
1122
|
+
h = Math.imul(h, 16777619);
|
|
1123
|
+
}
|
|
1124
|
+
return h >>> 0;
|
|
1125
|
+
}
|
|
1126
|
+
/** Convenience: the rough stroke passes for a path at a given seed. */
|
|
1127
|
+
function sketchStrokes(segs, style, seed) {
|
|
1128
|
+
return roughen(segs, style, random(seed >>> 0)).strokes;
|
|
1129
|
+
}
|
|
1130
|
+
//#endregion
|
|
1131
1131
|
//#region src/nodes.ts
|
|
1132
1132
|
/**
|
|
1133
1133
|
* Built-in nodes for M1 (DESIGN.md §3.1): Group, Rect, Circle, Text.
|
|
@@ -2302,4 +2302,4 @@ function revealSchedule(text, reveal, measurer) {
|
|
|
2302
2302
|
return marks;
|
|
2303
2303
|
}
|
|
2304
2304
|
//#endregion
|
|
2305
|
-
export { multiply as $,
|
|
2305
|
+
export { multiply as $, __resetEstimateWarnings as A, setDefaultMeasurer as B, Node as C, NODE_CONSTRUCTION_PROP_NAMES as D, BASE_CONSTRUCTION_PROP_NAMES as E, isEstimatingMeasurer as F, glow as G, FilterValidationError as H, measureWrappedText as I, IDENTITY as J, validateFilters as K, quantize as L, breakLines as M, estimatingMeasurer as N, isConstructionProp as O, fallbackMeasurer as P, matEquals as Q, segmentGraphemes as R, validateSketch as S, resolveAnchor as T, createDisplayListBuilder as U, warnIfEstimating as V, filtersToCanvasFilter as W, fromTRS as X, applyToPoint as Y, invert as Z, hashStr as _, Path as a, sketchStrokes as b, Video as c, revealSchedule as d, roundedRectSegs as f, hachureLines as g, flatten as h, ImageNode as i, assertFiniteFontSize as j, MEASURE_QUANTUM_PX as k, coercePathData as l, arcLength as m, Custom as n, Rect as o, SketchValidationError as p, collapseReplacer as q, Group as r, Text as s, Circle as t, pathFromSegs as u, resolveSketch as v, NodeConstructionError as w, validateHachure as x, roughen as y, segmentWords as z };
|
package/dist/orient.js
CHANGED
package/dist/scene.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { I as measureWrappedText, O as isConstructionProp, P as fallbackMeasurer, U as createDisplayListBuilder, r as Group } from "./nodes.js";
|
|
2
2
|
import { bindTimeline, compileTimeline, createPlayhead, evaluateAt, signal } from "@glissade/core";
|
|
3
3
|
//#region src/scene.ts
|
|
4
4
|
/**
|
package/dist/tokens.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { J as IDENTITY, Q as matEquals, f as roundedRectSegs
|
|
1
|
+
import { C as Node, J as IDENTITY, Q as matEquals, f as roundedRectSegs } from "./nodes.js";
|
|
2
2
|
import { signal, vec2Signal } from "@glissade/core";
|
|
3
3
|
//#region src/tokenHighlight.ts
|
|
4
4
|
/**
|
package/dist/type.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { L as quantize, P as fallbackMeasurer, V as warnIfEstimating, r as Group, s as Text } from "./nodes.js";
|
|
2
2
|
//#region src/type.ts
|
|
3
3
|
/**
|
|
4
4
|
* `@glissade/scene/type` — `splitText()`: build-time split-text sub-targets
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/scene",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.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
|
"engines": {
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
"types": "./dist/grid.d.ts",
|
|
28
28
|
"default": "./dist/grid.js"
|
|
29
29
|
},
|
|
30
|
+
"./chart": {
|
|
31
|
+
"types": "./dist/chart.d.ts",
|
|
32
|
+
"default": "./dist/chart.js"
|
|
33
|
+
},
|
|
30
34
|
"./path": {
|
|
31
35
|
"types": "./dist/path.d.ts",
|
|
32
36
|
"default": "./dist/path.js"
|
|
@@ -65,7 +69,7 @@
|
|
|
65
69
|
],
|
|
66
70
|
"dependencies": {
|
|
67
71
|
"yoga-layout": "^3.2.1",
|
|
68
|
-
"@glissade/core": "0.
|
|
72
|
+
"@glissade/core": "0.32.0-pre.0"
|
|
69
73
|
},
|
|
70
74
|
"repository": {
|
|
71
75
|
"type": "git",
|