@motionscript/latex 0.0.0-stage → 0.1.0-alpha.3

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.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from './node.js';
2
+ export * from './engine.js';
3
+ export { NODES } from "./nodes.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC","sourcesContent":["export * from './node';\nexport * from './engine';\nexport { NODES } from \"./nodes\";\n"]}
package/dist/node.d.ts ADDED
@@ -0,0 +1,151 @@
1
+ import { RenderContext2D, EasingFunction, NodeConfig, ShapeNode, ShapeProps, InsetsProps, Size2D, SizeConstraints, InsetsResolved, type Command, type CommandArgs } from "@motionscript/sdk";
2
+ import { type LatexMorphStrategy } from "./engine.js";
3
+ export interface LatexProps extends ShapeProps {
4
+ latex: string;
5
+ fontSize: number;
6
+ padding: InsetsProps;
7
+ }
8
+ /**
9
+ * Construction-time-only options: pluggable engine behavior, set once when
10
+ * the node is created. Deliberately *not* part of {@link LatexProps} — every
11
+ * `@property` field accepts either a value or a zero-arg reactive binding
12
+ * (`value | (() => value)`, see `PropInputs`), and the node's own reactive
13
+ * write path (`_writeProp`) treats any function value as the latter. A
14
+ * strategy *is* a function, so it can't be told apart from a binding that
15
+ * computes one — it has to stay outside the reactive prop system entirely.
16
+ */
17
+ export interface LatexStrategies {
18
+ /** How the node morphs between two formulas. Defaults to `defaultLatexMorph`. */
19
+ morph?: LatexMorphStrategy;
20
+ }
21
+ export declare class Latex extends ShapeNode<LatexProps> {
22
+ readonly latex: string;
23
+ readonly fontSize: number;
24
+ readonly padding: InsetsResolved;
25
+ /**
26
+ * How this node morphs between two formulas. Construction-time-only (see
27
+ * {@link LatexStrategies}) — resolved once here rather than left as
28
+ * `?? defaultLatexMorph` at every call site.
29
+ */
30
+ private readonly morph;
31
+ private _intrinsicWidth;
32
+ private _intrinsicHeight;
33
+ /**
34
+ * Shared center frame for all current tokens, [minX, minY, maxX, maxY] in
35
+ * token space. Passed to every per-token path so glyphs keep their relative
36
+ * layout instead of each centering on its own bbox.
37
+ */
38
+ private _bounds;
39
+ /** Current tokens to render. Each has its own opacity for tween transitions. */
40
+ private _tokens;
41
+ /** Suppresses retokenization while a custom to() is driving frames. */
42
+ private _animating;
43
+ /** The `fontSize|latex` the live tokens were built from. See {@link _ensureTokens}. */
44
+ private _tokenizedFrom;
45
+ constructor(props: NodeConfig<Latex, LatexProps> & LatexStrategies);
46
+ /**
47
+ * Rebuild the token list when the formula or size has moved since it last
48
+ * was built.
49
+ *
50
+ * Pulled from every reader rather than pushed from a prop write, because
51
+ * `latex` can be a binding — and a binding has no write to hang a
52
+ * subscription on. The key is what the current tokens were built from;
53
+ * `_animating` hands ownership to the morph command for its open interval.
54
+ */
55
+ private _ensureTokens;
56
+ private _updateTokens;
57
+ measure(constraints: SizeConstraints): Partial<Size2D>;
58
+ /**
59
+ * A LaTeX morph — one {@link Command}, like every other `to()`.
60
+ *
61
+ * Sequence two of them with `sequence(...)` rather than chaining: the chain
62
+ * builder this used to return existed only so `latexRef().to(a, 1).to(b, 1)`
63
+ * would read as two steps, and it could not compose with anything that was
64
+ * not another Latex morph.
65
+ */
66
+ to<D extends Partial<LatexProps> = Partial<LatexProps>>(args: CommandArgs<D> & {
67
+ duration: number;
68
+ }): Command<LatexProps>;
69
+ /**
70
+ * Morph this formula into another — a document-facing name for {@link to},
71
+ * which already routes through {@link _buildMorph}.
72
+ */
73
+ morphTo(args: CommandArgs<{
74
+ latex: string;
75
+ }> & {
76
+ duration: number;
77
+ }): Command<LatexProps>;
78
+ /**
79
+ * Build a single LaTeX morph as a {@link Command}: every ordinary
80
+ * `ShapeProps` field (via the inherited `_prepareStep`), plus the
81
+ * intrinsic size, shared center frame and per-glyph token list — state a
82
+ * plain `set()` can't reach — driven together by one eased `t`, matching
83
+ * the concurrent `parallel` the generator version ran.
84
+ *
85
+ * The "from" snapshot (`fromTokens`, `fromLatex`, …) is captured
86
+ * **lazily, on the command's first evaluation**, not when this is called.
87
+ * That is what lets two morphs be sequenced: `commandSequence` settles each
88
+ * prior step before evaluating the next, so a second morph's "from" reads
89
+ * the first one's end state rather than whatever the node held before either
90
+ * of them ran.
91
+ *
92
+ * `_animating` suppresses {@link _ensureTokens} for the open interval, and
93
+ * both ends commit: `t === 1`
94
+ * lands on the target formula, `t === 0` restores the source one — the same
95
+ * "membership for the open interval, commit at either end" shape
96
+ * `packages/components/code`'s `runTransition` uses, so seeking into either
97
+ * side of the morph (not just running it forward) shows the right thing.
98
+ *
99
+ * `@internal` — the entry point for authors is {@link to}.
100
+ */
101
+ _buildMorph(to: Partial<LatexProps>, duration: number, easing?: EasingFunction): Command<LatexProps>;
102
+ /**
103
+ * The shadow op comes **first**, as it does on every other painted node.
104
+ *
105
+ * The renderer does not draw a shadow when it sees one: `.shadow()` stores
106
+ * it pending and the *next* fill or stroke op casts it (`_shadow` →
107
+ * `storePendingShadows`, `_fill`/`_stroke` → `takePendingShadows`). Appended
108
+ * last, it was stored by every token and then thrown away by the reset at
109
+ * the end of the draw — a shadow set on a LaTeX node simply never appeared,
110
+ * with no error to say why. Same failure {@link PaintedNode.renderStroke}
111
+ * documents for a fill-less stroked shape, and the same one-op fix.
112
+ *
113
+ * Order also carries the fill-less case for free: an empty fill returns
114
+ * before it takes the pending shadow, so the stroke below casts it from the
115
+ * glyph outlines instead — which is what `castsShadowFromStroke` arranges on
116
+ * a plain shape.
117
+ */
118
+ protected renderSelf(ctx: RenderContext2D): void;
119
+ /**
120
+ * The overlay, painted *through* the glyphs.
121
+ *
122
+ * `ShapeNode`'s inherited overlay pass fills whatever {@link
123
+ * ShapeNode.shapeGraphics} describes, and this node describes nothing there:
124
+ * a formula has no single fillable silhouette — its silhouette *is* the list
125
+ * of token paths, which is why `renderSelf` is overridden rather than a
126
+ * `shapeGraphics` supplied. So the generic pass drew nothing and an overlay
127
+ * set on a LaTeX node simply never appeared, with no error to say why.
128
+ *
129
+ * Painted the same way the fill is: one `Graphics2D` per token, all sharing the
130
+ * centre frame, each scaled by its token's animated opacity — so a morph
131
+ * carries the overlay along with the glyph it is laid over instead of leaving
132
+ * a wash hanging over glyphs that have faded out.
133
+ *
134
+ * The stroke is *not* re-drawn here. `renderSelf` already strokes each token
135
+ * (there is no silhouette for the deferred {@link ShapeNode.renderStroke} to
136
+ * outline), so it is painted under the overlay rather than over it — the one
137
+ * place this node's draw order differs from a plain shape's, and the price of
138
+ * a stroke that follows glyphs rather than a box.
139
+ */
140
+ protected renderOverlay(ctx: RenderContext2D): void;
141
+ /**
142
+ * Draws every visible token's path once, handing each one to `paint` to have
143
+ * its paint ops appended.
144
+ *
145
+ * A fresh `Graphics2D` per token per pass, because `.fill()`/`.stroke()` push
146
+ * onto one op list and return `this` — a silhouette shared between the fill
147
+ * pass and the overlay pass would accumulate both.
148
+ */
149
+ private eachToken;
150
+ }
151
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAc,cAAc,EAA6B,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,eAAe,EAAgB,cAAc,EAAwE,KAAK,OAAO,EAAE,KAAK,WAAW,EAAqB,MAAM,mBAAmB,CAAC;AAC3U,OAAO,EAAyD,KAAK,kBAAkB,EAAmB,MAAM,UAAU,CAAC;AAE3H,MAAM,WAAW,UAAW,SAAQ,UAAU;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC5B,iFAAiF;IACjF,KAAK,CAAC,EAAE,kBAAkB,CAAC;CAC9B;AAED,qBA0Ba,KAAM,SAAQ,SAAS,CAAC,UAAU,CAAC;IAC5C,SAA4C,KAAK,EAAE,MAAM,CAAC;IAC1D,SAA4C,QAAQ,EAAE,MAAM,CAAC;IAC7D,SAAmC,OAAO,EAAE,cAAc,CAAC;IAE3D;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAE3C,OAAO,CAAC,eAAe,CAAa;IACpC,OAAO,CAAC,gBAAgB,CAAa;IAErC;;;;OAIG;IACH,OAAO,CAAC,OAAO,CAAkD;IAEjE,gFAAgF;IAChF,OAAO,CAAC,OAAO,CAAuB;IAEtC,uEAAuE;IACvE,OAAO,CAAC,UAAU,CAAkB;IAEpC,uFAAuF;IACvF,OAAO,CAAC,cAAc,CAAuB;gBAEjC,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,UAAU,CAAC,GAAG,eAAe;IASlE;;;;;;;;OAQG;IACH,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,aAAa;IAqBZ,OAAO,CAAC,WAAW,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC;IAqB/D;;;;;;;OAOG;IACM,EAAE,CAAC,CAAC,SAAS,OAAO,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,CAAC;IAIlI;;;OAGG;IAEH,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,CAAC;IAIzF;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,WAAW,CAAC,EAAE,EAAE,OAAO,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC;IA0EpG;;;;;;;;;;;;;;;OAeG;IACH,SAAS,CAAC,UAAU,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI;IAOhD;;;;;;;;;;;;;;;;;;;;OAoBG;cACgB,aAAa,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI;IAM5D;;;;;;;OAOG;IACH,OAAO,CAAC,SAAS;CAyBpB"}
package/dist/node.js ADDED
@@ -0,0 +1,359 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { Graphics2D, command, node, lerpNumber, ShapeNode, toPathString, property, insetsProperty, driveCommand } from "@motionscript/sdk";
8
+ import { buildLatexPath, defaultLatexMorph } from "./engine.js";
9
+ let Latex = class Latex extends ShapeNode {
10
+ /**
11
+ * How this node morphs between two formulas. Construction-time-only (see
12
+ * {@link LatexStrategies}) — resolved once here rather than left as
13
+ * `?? defaultLatexMorph` at every call site.
14
+ */
15
+ morph;
16
+ _intrinsicWidth = 0;
17
+ _intrinsicHeight = 0;
18
+ /**
19
+ * Shared center frame for all current tokens, [minX, minY, maxX, maxY] in
20
+ * token space. Passed to every per-token path so glyphs keep their relative
21
+ * layout instead of each centering on its own bbox.
22
+ */
23
+ _bounds = [0, 0, 0, 0];
24
+ /** Current tokens to render. Each has its own opacity for tween transitions. */
25
+ _tokens = [];
26
+ /** Suppresses retokenization while a custom to() is driving frames. */
27
+ _animating = false;
28
+ /** The `fontSize|latex` the live tokens were built from. See {@link _ensureTokens}. */
29
+ _tokenizedFrom = null;
30
+ constructor(props) {
31
+ super(props);
32
+ this.morph = props.morph ?? defaultLatexMorph;
33
+ this.applyProp("width", props.width ?? "hug");
34
+ this.applyProp("height", props.height ?? "hug");
35
+ this._ensureTokens();
36
+ }
37
+ /**
38
+ * Rebuild the token list when the formula or size has moved since it last
39
+ * was built.
40
+ *
41
+ * Pulled from every reader rather than pushed from a prop write, because
42
+ * `latex` can be a binding — and a binding has no write to hang a
43
+ * subscription on. The key is what the current tokens were built from;
44
+ * `_animating` hands ownership to the morph command for its open interval.
45
+ */
46
+ _ensureTokens() {
47
+ if (this._animating)
48
+ return;
49
+ const key = `${this.fontSize}|${this.latex}`;
50
+ if (key === this._tokenizedFrom)
51
+ return;
52
+ this._tokenizedFrom = key;
53
+ this._updateTokens();
54
+ }
55
+ _updateTokens() {
56
+ if (this.latex) {
57
+ const result = buildLatexPath(this.latex, this.fontSize);
58
+ this._intrinsicWidth = result.width;
59
+ this._intrinsicHeight = result.height;
60
+ this._bounds = result.bounds;
61
+ this._tokens = result.tokens.map(t => ({
62
+ token: t.token,
63
+ path: t.path,
64
+ opacity: 1,
65
+ x: 0,
66
+ y: 0,
67
+ }));
68
+ }
69
+ else {
70
+ this._tokens = [];
71
+ this._intrinsicWidth = 0;
72
+ this._intrinsicHeight = 0;
73
+ this._bounds = [0, 0, 0, 0];
74
+ }
75
+ }
76
+ measure(constraints) {
77
+ this._ensureTokens();
78
+ const pad = this.padding;
79
+ const wm = this.width;
80
+ const hm = this.height;
81
+ const resolvedW = typeof wm === "number"
82
+ ? wm
83
+ : wm === "hug"
84
+ ? this._intrinsicWidth + pad.left + pad.right
85
+ : constraints.maxWidth ?? 0;
86
+ const resolvedH = typeof hm === "number"
87
+ ? hm
88
+ : hm === "hug"
89
+ ? this._intrinsicHeight + pad.top + pad.bottom
90
+ : constraints.maxHeight ?? 0;
91
+ return { width: resolvedW, height: resolvedH };
92
+ }
93
+ /**
94
+ * A LaTeX morph — one {@link Command}, like every other `to()`.
95
+ *
96
+ * Sequence two of them with `sequence(...)` rather than chaining: the chain
97
+ * builder this used to return existed only so `latexRef().to(a, 1).to(b, 1)`
98
+ * would read as two steps, and it could not compose with anything that was
99
+ * not another Latex morph.
100
+ */
101
+ to(args) {
102
+ return this._buildMorph(args.data ?? {}, args.duration, args.easing);
103
+ }
104
+ /**
105
+ * Morph this formula into another — a document-facing name for {@link to},
106
+ * which already routes through {@link _buildMorph}.
107
+ */
108
+ morphTo(args) {
109
+ return this.to({ data: { latex: args.data?.latex }, duration: args.duration, easing: args.easing });
110
+ }
111
+ /**
112
+ * Build a single LaTeX morph as a {@link Command}: every ordinary
113
+ * `ShapeProps` field (via the inherited `_prepareStep`), plus the
114
+ * intrinsic size, shared center frame and per-glyph token list — state a
115
+ * plain `set()` can't reach — driven together by one eased `t`, matching
116
+ * the concurrent `parallel` the generator version ran.
117
+ *
118
+ * The "from" snapshot (`fromTokens`, `fromLatex`, …) is captured
119
+ * **lazily, on the command's first evaluation**, not when this is called.
120
+ * That is what lets two morphs be sequenced: `commandSequence` settles each
121
+ * prior step before evaluating the next, so a second morph's "from" reads
122
+ * the first one's end state rather than whatever the node held before either
123
+ * of them ran.
124
+ *
125
+ * `_animating` suppresses {@link _ensureTokens} for the open interval, and
126
+ * both ends commit: `t === 1`
127
+ * lands on the target formula, `t === 0` restores the source one — the same
128
+ * "membership for the open interval, commit at either end" shape
129
+ * `packages/components/code`'s `runTransition` uses, so seeking into either
130
+ * side of the morph (not just running it forward) shows the right thing.
131
+ *
132
+ * `@internal` — the entry point for authors is {@link to}.
133
+ */
134
+ _buildMorph(to, duration, easing) {
135
+ let setupDone = false;
136
+ let fromAnimTokens = [];
137
+ let fromLatex = "";
138
+ let fromFontSize = 0;
139
+ let fromWidth = 0;
140
+ let fromHeight = 0;
141
+ let fromBounds = [0, 0, 0, 0];
142
+ let toFontSize = 0;
143
+ let toLatex = "";
144
+ let toResult;
145
+ let propStep;
146
+ let latexFrame;
147
+ const setup = () => {
148
+ setupDone = true;
149
+ this._ensureTokens();
150
+ const fromTokens = this._tokens.map(t => ({ token: t.token, path: t.path }));
151
+ fromAnimTokens = this._tokens.map(t => ({ ...t }));
152
+ fromLatex = this.latex;
153
+ fromFontSize = this.fontSize;
154
+ fromWidth = this._intrinsicWidth;
155
+ fromHeight = this._intrinsicHeight;
156
+ fromBounds = this._bounds;
157
+ toFontSize = to.fontSize !== undefined ? to.fontSize : this.fontSize;
158
+ toLatex = to.latex !== undefined ? to.latex : this.latex;
159
+ toResult = buildLatexPath(toLatex, toFontSize);
160
+ propStep = this._prepareStep(to, duration);
161
+ latexFrame = this.morph(fromTokens, toResult.tokens);
162
+ };
163
+ return driveCommand(duration, (t) => {
164
+ if (!setupDone)
165
+ setup();
166
+ const toBounds = toResult.bounds;
167
+ this._animating = true;
168
+ propStep.seek(t * duration);
169
+ this.set({ fontSize: lerpNumber(fromFontSize, toFontSize, t) });
170
+ // Track the measured size so a hugging box grows/shrinks smoothly
171
+ // across the morph rather than jumping when the end commits.
172
+ this._intrinsicWidth = lerpNumber(fromWidth, toResult.width, t);
173
+ this._intrinsicHeight = lerpNumber(fromHeight, toResult.height, t);
174
+ // Interpolate the shared center frame in lockstep so glyphs stay
175
+ // centered within the resizing box.
176
+ this._bounds = [
177
+ lerpNumber(fromBounds[0], toBounds[0], t),
178
+ lerpNumber(fromBounds[1], toBounds[1], t),
179
+ lerpNumber(fromBounds[2], toBounds[2], t),
180
+ lerpNumber(fromBounds[3], toBounds[3], t),
181
+ ];
182
+ this._tokens = latexFrame(t);
183
+ if (t >= 1) {
184
+ this.set({ latex: toLatex, fontSize: toFontSize });
185
+ this._tokens = toResult.tokens.map(tok => ({ token: tok.token, path: tok.path, opacity: 1, x: 0, y: 0 }));
186
+ this._intrinsicWidth = toResult.width;
187
+ this._intrinsicHeight = toResult.height;
188
+ this._bounds = toResult.bounds;
189
+ this._animating = false;
190
+ this._tokenizedFrom = `${toFontSize}|${toLatex}`;
191
+ }
192
+ else if (t <= 0) {
193
+ this.set({ latex: fromLatex, fontSize: fromFontSize });
194
+ this._tokens = fromAnimTokens;
195
+ this._intrinsicWidth = fromWidth;
196
+ this._intrinsicHeight = fromHeight;
197
+ this._bounds = fromBounds;
198
+ this._animating = false;
199
+ this._tokenizedFrom = `${fromFontSize}|${fromLatex}`;
200
+ }
201
+ }, easing);
202
+ }
203
+ /**
204
+ * The shadow op comes **first**, as it does on every other painted node.
205
+ *
206
+ * The renderer does not draw a shadow when it sees one: `.shadow()` stores
207
+ * it pending and the *next* fill or stroke op casts it (`_shadow` →
208
+ * `storePendingShadows`, `_fill`/`_stroke` → `takePendingShadows`). Appended
209
+ * last, it was stored by every token and then thrown away by the reset at
210
+ * the end of the draw — a shadow set on a LaTeX node simply never appeared,
211
+ * with no error to say why. Same failure {@link PaintedNode.renderStroke}
212
+ * documents for a fill-less stroked shape, and the same one-op fix.
213
+ *
214
+ * Order also carries the fill-less case for free: an empty fill returns
215
+ * before it takes the pending shadow, so the stroke below casts it from the
216
+ * glyph outlines instead — which is what `castsShadowFromStroke` arranges on
217
+ * a plain shape.
218
+ */
219
+ renderSelf(ctx) {
220
+ this.eachToken(ctx, (graphics, opacity) => graphics
221
+ .shadow(scaleShadowOpacity(this.shadow, opacity))
222
+ .fill(scaleFillopacity(this.fill, opacity))
223
+ .stroke(this.stroke));
224
+ }
225
+ /**
226
+ * The overlay, painted *through* the glyphs.
227
+ *
228
+ * `ShapeNode`'s inherited overlay pass fills whatever {@link
229
+ * ShapeNode.shapeGraphics} describes, and this node describes nothing there:
230
+ * a formula has no single fillable silhouette — its silhouette *is* the list
231
+ * of token paths, which is why `renderSelf` is overridden rather than a
232
+ * `shapeGraphics` supplied. So the generic pass drew nothing and an overlay
233
+ * set on a LaTeX node simply never appeared, with no error to say why.
234
+ *
235
+ * Painted the same way the fill is: one `Graphics2D` per token, all sharing the
236
+ * centre frame, each scaled by its token's animated opacity — so a morph
237
+ * carries the overlay along with the glyph it is laid over instead of leaving
238
+ * a wash hanging over glyphs that have faded out.
239
+ *
240
+ * The stroke is *not* re-drawn here. `renderSelf` already strokes each token
241
+ * (there is no silhouette for the deferred {@link ShapeNode.renderStroke} to
242
+ * outline), so it is painted under the overlay rather than over it — the one
243
+ * place this node's draw order differs from a plain shape's, and the price of
244
+ * a stroke that follows glyphs rather than a box.
245
+ */
246
+ renderOverlay(ctx) {
247
+ const overlay = this.overlay;
248
+ if (overlay.length === 0)
249
+ return;
250
+ this.eachToken(ctx, (graphics, opacity) => graphics.fill(scaleFillopacity(overlay, opacity)));
251
+ }
252
+ /**
253
+ * Draws every visible token's path once, handing each one to `paint` to have
254
+ * its paint ops appended.
255
+ *
256
+ * A fresh `Graphics2D` per token per pass, because `.fill()`/`.stroke()` push
257
+ * onto one op list and return `this` — a silhouette shared between the fill
258
+ * pass and the overlay pass would accumulate both.
259
+ */
260
+ eachToken(ctx, paint) {
261
+ // The one funnel every paint pass goes through, so the tokens a frame
262
+ // draws cannot be older than the formula it holds.
263
+ this._ensureTokens();
264
+ for (const token of this._tokens) {
265
+ if (token.opacity <= 0)
266
+ continue;
267
+ // Translate the path by token's interpolated position offset
268
+ const pathStr = token.x !== 0 || token.y !== 0
269
+ ? toPathString(offsetPath(token.path, token.x, token.y))
270
+ : toPathString(token.path);
271
+ ctx.draw(paint(new Graphics2D()
272
+ .path({
273
+ data: pathStr,
274
+ start: this.start,
275
+ end: this.end,
276
+ // All tokens share one center frame so they keep their relative
277
+ // layout — without this each glyph centers on its own bbox and
278
+ // they all stack on the origin.
279
+ centerBounds: this._bounds,
280
+ }), token.opacity));
281
+ }
282
+ }
283
+ };
284
+ __decorate([
285
+ property({ default: "" })
286
+ ], Latex.prototype, "latex", void 0);
287
+ __decorate([
288
+ property({ default: 16 })
289
+ ], Latex.prototype, "fontSize", void 0);
290
+ __decorate([
291
+ insetsProperty()
292
+ ], Latex.prototype, "padding", void 0);
293
+ __decorate([
294
+ command({ args: [{ key: "latex", kind: "text", multiline: true }] })
295
+ ], Latex.prototype, "morphTo", null);
296
+ Latex = __decorate([
297
+ node({
298
+ key: "latex",
299
+ parentKey: "shape",
300
+ forkable: true,
301
+ layout: {
302
+ children: "freeform",
303
+ defaultWidthMode: "hug",
304
+ defaultHeightMode: "hug",
305
+ acceptsChildren: true,
306
+ },
307
+ seed: {
308
+ fill: [
309
+ {
310
+ type: "solid",
311
+ color: [
312
+ 0.412,
313
+ 0.565,
314
+ 0.867,
315
+ 1,
316
+ ],
317
+ opacity: 1,
318
+ enabled: true,
319
+ },
320
+ ],
321
+ },
322
+ })
323
+ ], Latex);
324
+ export { Latex };
325
+ function scaleFillopacity(fills, opacity) {
326
+ if (opacity >= 1)
327
+ return fills;
328
+ return fills.map(f => ({ ...f, opacity: (f.opacity ?? 1) * opacity }));
329
+ }
330
+ /**
331
+ * A morphing token fades, and its shadow has to fade with it — the paints are
332
+ * scaled the same way the fill's are, so a half-faded glyph casts a half-strength
333
+ * shadow rather than sitting over a shadow at full weight.
334
+ */
335
+ function scaleShadowOpacity(shadows, opacity) {
336
+ if (opacity >= 1)
337
+ return shadows;
338
+ return shadows.map(s => ({ ...s, fill: scaleFillopacity(s.fill, opacity) }));
339
+ }
340
+ function offsetPath(path, dx, dy) {
341
+ return path.map(cmd => {
342
+ const c = cmd;
343
+ const shifted = { ...c };
344
+ if ("x" in c)
345
+ shifted.x = c.x + dx;
346
+ if ("y" in c)
347
+ shifted.y = c.y + dy;
348
+ if ("x1" in c) {
349
+ shifted.x1 = c.x1 + dx;
350
+ shifted.y1 = c.y1 + dy;
351
+ }
352
+ if ("x2" in c) {
353
+ shifted.x2 = c.x2 + dx;
354
+ shifted.y2 = c.y2 + dy;
355
+ }
356
+ return shifted;
357
+ });
358
+ }
359
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAmB,UAAU,EAAkB,OAAO,EAAE,IAAI,EAAE,UAAU,EAAc,SAAS,EAAoD,YAAY,EAAkB,QAAQ,EAAE,cAAc,EAAgC,YAAY,EAAqD,MAAM,mBAAmB,CAAC;AAC3U,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAgE,MAAM,UAAU,CAAC;AAgDpH,IAAM,KAAK,GAAX,MAAM,KAAM,SAAQ,SAAqB;IAK5C;;;;OAIG;IACc,KAAK,CAAqB;IAEnC,eAAe,GAAW,CAAC,CAAC;IAC5B,gBAAgB,GAAW,CAAC,CAAC;IAErC;;;;OAIG;IACK,OAAO,GAAqC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAEjE,gFAAgF;IACxE,OAAO,GAAoB,EAAE,CAAC;IAEtC,uEAAuE;IAC/D,UAAU,GAAY,KAAK,CAAC;IAEpC,uFAAuF;IAC/E,cAAc,GAAkB,IAAI,CAAC;IAE7C,YAAY,KAAsD;QAC9D,KAAK,CAAC,KAAK,CAAC,CAAC;QACb,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,iBAAiB,CAAC;QAC9C,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,CAAC;QAC9C,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC;QAEhD,IAAI,CAAC,aAAa,EAAE,CAAC;IACzB,CAAC;IAED;;;;;;;;OAQG;IACK,aAAa;QACjB,IAAI,IAAI,CAAC,UAAU;YAAE,OAAO;QAC5B,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAC7C,IAAI,GAAG,KAAK,IAAI,CAAC,cAAc;YAAE,OAAO;QACxC,IAAI,CAAC,cAAc,GAAG,GAAG,CAAC;QAC1B,IAAI,CAAC,aAAa,EAAE,CAAC;IACzB,CAAC;IAEO,aAAa;QACjB,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;YACzD,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC,KAAK,CAAC;YACpC,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC;YACtC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC;YAC7B,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBACnC,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,OAAO,EAAE,CAAC;gBACV,CAAC,EAAE,CAAC;gBACJ,CAAC,EAAE,CAAC;aACP,CAAC,CAAC,CAAC;QACR,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;YAClB,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC;YACzB,IAAI,CAAC,gBAAgB,GAAG,CAAC,CAAC;YAC1B,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAChC,CAAC;IACL,CAAC;IAEQ,OAAO,CAAC,WAA4B;QACzC,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC;QACzB,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;QACtB,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC;QAEvB,MAAM,SAAS,GAAG,OAAO,EAAE,KAAK,QAAQ;YACpC,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,KAAK,KAAK;gBACV,CAAC,CAAC,IAAI,CAAC,eAAe,GAAG,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,KAAK;gBAC7C,CAAC,CAAC,WAAW,CAAC,QAAQ,IAAI,CAAC,CAAC;QAEpC,MAAM,SAAS,GAAG,OAAO,EAAE,KAAK,QAAQ;YACpC,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,KAAK,KAAK;gBACV,CAAC,CAAC,IAAI,CAAC,gBAAgB,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,MAAM;gBAC9C,CAAC,CAAC,WAAW,CAAC,SAAS,IAAI,CAAC,CAAC;QAErC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IACnD,CAAC;IAED;;;;;;;OAOG;IACM,EAAE,CAAsD,IAA2C;QACxG,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACzE,CAAC;IAED;;;OAGG;IAEH,OAAO,CAAC,IAA2D;QAC/D,OAAO,IAAI,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAyB,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IAC/H,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,WAAW,CAAC,EAAuB,EAAE,QAAgB,EAAE,MAAuB;QAC1E,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,IAAI,cAAc,GAAoB,EAAE,CAAC;QACzC,IAAI,SAAS,GAAG,EAAE,CAAC;QACnB,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,IAAI,UAAU,GAAqC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAChE,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,IAAI,OAAO,GAAG,EAAE,CAAC;QACjB,IAAI,QAA2C,CAAC;QAChD,IAAI,QAAsB,CAAC;QAC3B,IAAI,UAA0C,CAAC;QAE/C,MAAM,KAAK,GAAG,GAAS,EAAE;YACrB,SAAS,GAAG,IAAI,CAAC;YACjB,IAAI,CAAC,aAAa,EAAE,CAAC;YACrB,MAAM,UAAU,GAAiB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YAC3F,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;YACnD,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC;YACvB,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC;YAC7B,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC;YACjC,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC;YACnC,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC;YAE1B,UAAU,GAAG,EAAE,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC;YACrE,OAAO,GAAG,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;YACzD,QAAQ,GAAG,cAAc,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;YAE/C,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;YAC3C,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QACzD,CAAC,CAAC;QAEF,OAAO,YAAY,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;YAChC,IAAI,CAAC,SAAS;gBAAE,KAAK,EAAE,CAAC;YACxB,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC;YAEjC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;YACvB,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC;YAC5B,IAAI,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,UAAU,CAAC,YAAY,EAAE,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;YAChE,kEAAkE;YAClE,6DAA6D;YAC7D,IAAI,CAAC,eAAe,GAAG,UAAU,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YAChE,IAAI,CAAC,gBAAgB,GAAG,UAAU,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YACnE,iEAAiE;YACjE,oCAAoC;YACpC,IAAI,CAAC,OAAO,GAAG;gBACX,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBACzC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBACzC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBACzC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;aAC5C,CAAC;YACF,IAAI,CAAC,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YAE7B,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACT,IAAI,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAC;gBACnD,IAAI,CAAC,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC1G,IAAI,CAAC,eAAe,GAAG,QAAQ,CAAC,KAAK,CAAC;gBACtC,IAAI,CAAC,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC;gBACxC,IAAI,CAAC,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC;gBAC/B,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;gBACxB,IAAI,CAAC,cAAc,GAAG,GAAG,UAAU,IAAI,OAAO,EAAE,CAAC;YACrD,CAAC;iBAAM,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAChB,IAAI,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC;gBACvD,IAAI,CAAC,OAAO,GAAG,cAAc,CAAC;gBAC9B,IAAI,CAAC,eAAe,GAAG,SAAS,CAAC;gBACjC,IAAI,CAAC,gBAAgB,GAAG,UAAU,CAAC;gBACnC,IAAI,CAAC,OAAO,GAAG,UAAU,CAAC;gBAC1B,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;gBACxB,IAAI,CAAC,cAAc,GAAG,GAAG,YAAY,IAAI,SAAS,EAAE,CAAC;YACzD,CAAC;QACL,CAAC,EAAE,MAAM,CAAwB,CAAC;IACtC,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACO,UAAU,CAAC,GAAoB;QACrC,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC,QAAQ;aAC9C,MAAM,CAAC,kBAAkB,CAAC,IAAI,CAAC,MAA0B,EAAE,OAAO,CAAC,CAAC;aACpE,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAsB,EAAE,OAAO,CAAC,CAAC;aAC5D,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9B,CAAC;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACgB,aAAa,CAAC,GAAoB;QACjD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAyB,CAAC;QAC/C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QACjC,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAClG,CAAC;IAED;;;;;;;OAOG;IACK,SAAS,CAAC,GAAoB,EAAE,KAA4D;QAChG,sEAAsE;QACtE,mDAAmD;QACnD,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC/B,IAAI,KAAK,CAAC,OAAO,IAAI,CAAC;gBAAE,SAAS;YAEjC,6DAA6D;YAC7D,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK,CAAC;gBAC1C,CAAC,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;gBACxD,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAE/B,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,UAAU,EAAE;iBAC1B,IAAI,CAAC;gBACF,IAAI,EAAE,OAAO;gBACb,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,gEAAgE;gBAChE,+DAA+D;gBAC/D,gCAAgC;gBAChC,YAAY,EAAE,IAAI,CAAC,OAAO;aAC7B,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAC5B,CAAC;IACL,CAAC;CAEJ,CAAA;AA3S+C;IAA3C,QAAQ,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;oCAAgC;AACd;IAA3C,QAAQ,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;uCAAmC;AAC1B;IAAlC,cAAc,EAAE;sCAA0C;AAiH3D;IADC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;oCAGpE;AAtHQ,KAAK;IA1BjB,IAAI,CAAC;QACJ,GAAG,EAAE,OAAO;QACZ,SAAS,EAAE,OAAO;QAClB,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE;YACN,QAAQ,EAAE,UAAU;YACpB,gBAAgB,EAAE,KAAK;YACvB,iBAAiB,EAAE,KAAK;YACxB,eAAe,EAAE,IAAI;SACtB;QACD,IAAI,EAAE;YACJ,IAAI,EAAE;gBACJ;oBACE,IAAI,EAAE,OAAO;oBACb,KAAK,EAAE;wBACL,KAAK;wBACL,KAAK;wBACL,KAAK;wBACL,CAAC;qBACF;oBACD,OAAO,EAAE,CAAC;oBACV,OAAO,EAAE,IAAI;iBACd;aACF;SACF;KACF,CAAC;GACW,KAAK,CA4SjB;;AAED,SAAS,gBAAgB,CAAC,KAAqB,EAAE,OAAe;IAC5D,IAAI,OAAO,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/B,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED;;;;GAIG;AACH,SAAS,kBAAkB,CAAC,OAAyB,EAAE,OAAe;IAClE,IAAI,OAAO,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC;IACjC,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;AACjF,CAAC;AAED,SAAS,UAAU,CACf,IAAwB,EACxB,EAAU,EACV,EAAU;IAEV,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;QAClB,MAAM,CAAC,GAAG,GAAU,CAAC;QACrB,MAAM,OAAO,GAAQ,EAAE,GAAG,CAAC,EAAE,CAAC;QAC9B,IAAI,GAAG,IAAI,CAAC;YAAE,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;QACnC,IAAI,GAAG,IAAI,CAAC;YAAE,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;QACnC,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;YAAC,OAAO,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC;YAAC,OAAO,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC;QAAC,CAAC;QAClE,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;YAAC,OAAO,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC;YAAC,OAAO,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC;QAAC,CAAC;QAClE,OAAO,OAAO,CAAC;IACnB,CAAC,CAAC,CAAC;AACP,CAAC","sourcesContent":["import { RenderContext2D, Graphics2D, EasingFunction, command, node, lerpNumber, NodeConfig, ShapeNode, ShapeProps, InsetsProps, Size2D, SizeConstraints, toPathString, InsetsResolved, property, insetsProperty, FillResolved, ShadowResolved, driveCommand, type Command, type CommandArgs, type TweenStepper } from \"@motionscript/sdk\";\nimport { buildLatexPath, defaultLatexMorph, type AnimatedToken, type LatexMorphStrategy, type LatexToken } from \"./engine\";\n\nexport interface LatexProps extends ShapeProps {\n latex: string;\n fontSize: number;\n padding: InsetsProps;\n}\n\n/**\n * Construction-time-only options: pluggable engine behavior, set once when\n * the node is created. Deliberately *not* part of {@link LatexProps} — every\n * `@property` field accepts either a value or a zero-arg reactive binding\n * (`value | (() => value)`, see `PropInputs`), and the node's own reactive\n * write path (`_writeProp`) treats any function value as the latter. A\n * strategy *is* a function, so it can't be told apart from a binding that\n * computes one — it has to stay outside the reactive prop system entirely.\n */\nexport interface LatexStrategies {\n /** How the node morphs between two formulas. Defaults to `defaultLatexMorph`. */\n morph?: LatexMorphStrategy;\n}\n\n@node({\n key: \"latex\",\n parentKey: \"shape\",\n forkable: true,\n layout: {\n children: \"freeform\",\n defaultWidthMode: \"hug\",\n defaultHeightMode: \"hug\",\n acceptsChildren: true,\n },\n seed: {\n fill: [\n {\n type: \"solid\",\n color: [\n 0.412,\n 0.565,\n 0.867,\n 1,\n ],\n opacity: 1,\n enabled: true,\n },\n ],\n },\n})\nexport class Latex extends ShapeNode<LatexProps> {\n @property({ default: \"\" }) declare readonly latex: string;\n @property({ default: 16 }) declare readonly fontSize: number;\n @insetsProperty() declare readonly padding: InsetsResolved;\n\n /**\n * How this node morphs between two formulas. Construction-time-only (see\n * {@link LatexStrategies}) — resolved once here rather than left as\n * `?? defaultLatexMorph` at every call site.\n */\n private readonly morph: LatexMorphStrategy;\n\n private _intrinsicWidth: number = 0;\n private _intrinsicHeight: number = 0;\n\n /**\n * Shared center frame for all current tokens, [minX, minY, maxX, maxY] in\n * token space. Passed to every per-token path so glyphs keep their relative\n * layout instead of each centering on its own bbox.\n */\n private _bounds: [number, number, number, number] = [0, 0, 0, 0];\n\n /** Current tokens to render. Each has its own opacity for tween transitions. */\n private _tokens: AnimatedToken[] = [];\n\n /** Suppresses retokenization while a custom to() is driving frames. */\n private _animating: boolean = false;\n\n /** The `fontSize|latex` the live tokens were built from. See {@link _ensureTokens}. */\n private _tokenizedFrom: string | null = null;\n\n constructor(props: NodeConfig<Latex, LatexProps> & LatexStrategies) {\n super(props);\n this.morph = props.morph ?? defaultLatexMorph;\n this.applyProp(\"width\", props.width ?? \"hug\");\n this.applyProp(\"height\", props.height ?? \"hug\");\n\n this._ensureTokens();\n }\n\n /**\n * Rebuild the token list when the formula or size has moved since it last\n * was built.\n *\n * Pulled from every reader rather than pushed from a prop write, because\n * `latex` can be a binding — and a binding has no write to hang a\n * subscription on. The key is what the current tokens were built from;\n * `_animating` hands ownership to the morph command for its open interval.\n */\n private _ensureTokens(): void {\n if (this._animating) return;\n const key = `${this.fontSize}|${this.latex}`;\n if (key === this._tokenizedFrom) return;\n this._tokenizedFrom = key;\n this._updateTokens();\n }\n\n private _updateTokens() {\n if (this.latex) {\n const result = buildLatexPath(this.latex, this.fontSize);\n this._intrinsicWidth = result.width;\n this._intrinsicHeight = result.height;\n this._bounds = result.bounds;\n this._tokens = result.tokens.map(t => ({\n token: t.token,\n path: t.path,\n opacity: 1,\n x: 0,\n y: 0,\n }));\n } else {\n this._tokens = [];\n this._intrinsicWidth = 0;\n this._intrinsicHeight = 0;\n this._bounds = [0, 0, 0, 0];\n }\n }\n\n override measure(constraints: SizeConstraints): Partial<Size2D> {\n this._ensureTokens();\n const pad = this.padding;\n const wm = this.width;\n const hm = this.height;\n\n const resolvedW = typeof wm === \"number\"\n ? wm\n : wm === \"hug\"\n ? this._intrinsicWidth + pad.left + pad.right\n : constraints.maxWidth ?? 0;\n\n const resolvedH = typeof hm === \"number\"\n ? hm\n : hm === \"hug\"\n ? this._intrinsicHeight + pad.top + pad.bottom\n : constraints.maxHeight ?? 0;\n\n return { width: resolvedW, height: resolvedH };\n }\n\n /**\n * A LaTeX morph — one {@link Command}, like every other `to()`.\n *\n * Sequence two of them with `sequence(...)` rather than chaining: the chain\n * builder this used to return existed only so `latexRef().to(a, 1).to(b, 1)`\n * would read as two steps, and it could not compose with anything that was\n * not another Latex morph.\n */\n override to<D extends Partial<LatexProps> = Partial<LatexProps>>(args: CommandArgs<D> & { duration: number }): Command<LatexProps> {\n return this._buildMorph(args.data ?? {}, args.duration, args.easing);\n }\n\n /**\n * Morph this formula into another — a document-facing name for {@link to},\n * which already routes through {@link _buildMorph}.\n */\n @command({ args: [{ key: \"latex\", kind: \"text\", multiline: true }] })\n morphTo(args: CommandArgs<{ latex: string }> & { duration: number }): Command<LatexProps> {\n return this.to({ data: { latex: args.data?.latex } as Partial<LatexProps>, duration: args.duration, easing: args.easing });\n }\n\n /**\n * Build a single LaTeX morph as a {@link Command}: every ordinary\n * `ShapeProps` field (via the inherited `_prepareStep`), plus the\n * intrinsic size, shared center frame and per-glyph token list — state a\n * plain `set()` can't reach — driven together by one eased `t`, matching\n * the concurrent `parallel` the generator version ran.\n *\n * The \"from\" snapshot (`fromTokens`, `fromLatex`, …) is captured\n * **lazily, on the command's first evaluation**, not when this is called.\n * That is what lets two morphs be sequenced: `commandSequence` settles each\n * prior step before evaluating the next, so a second morph's \"from\" reads\n * the first one's end state rather than whatever the node held before either\n * of them ran.\n *\n * `_animating` suppresses {@link _ensureTokens} for the open interval, and\n * both ends commit: `t === 1`\n * lands on the target formula, `t === 0` restores the source one — the same\n * \"membership for the open interval, commit at either end\" shape\n * `packages/components/code`'s `runTransition` uses, so seeking into either\n * side of the morph (not just running it forward) shows the right thing.\n *\n * `@internal` — the entry point for authors is {@link to}.\n */\n _buildMorph(to: Partial<LatexProps>, duration: number, easing?: EasingFunction): Command<LatexProps> {\n let setupDone = false;\n let fromAnimTokens: AnimatedToken[] = [];\n let fromLatex = \"\";\n let fromFontSize = 0;\n let fromWidth = 0;\n let fromHeight = 0;\n let fromBounds: [number, number, number, number] = [0, 0, 0, 0];\n let toFontSize = 0;\n let toLatex = \"\";\n let toResult: ReturnType<typeof buildLatexPath>;\n let propStep: TweenStepper;\n let latexFrame: (t: number) => AnimatedToken[];\n\n const setup = (): void => {\n setupDone = true;\n this._ensureTokens();\n const fromTokens: LatexToken[] = this._tokens.map(t => ({ token: t.token, path: t.path }));\n fromAnimTokens = this._tokens.map(t => ({ ...t }));\n fromLatex = this.latex;\n fromFontSize = this.fontSize;\n fromWidth = this._intrinsicWidth;\n fromHeight = this._intrinsicHeight;\n fromBounds = this._bounds;\n\n toFontSize = to.fontSize !== undefined ? to.fontSize : this.fontSize;\n toLatex = to.latex !== undefined ? to.latex : this.latex;\n toResult = buildLatexPath(toLatex, toFontSize);\n\n propStep = this._prepareStep(to, duration);\n latexFrame = this.morph(fromTokens, toResult.tokens);\n };\n\n return driveCommand(duration, (t) => {\n if (!setupDone) setup();\n const toBounds = toResult.bounds;\n\n this._animating = true;\n propStep.seek(t * duration);\n this.set({ fontSize: lerpNumber(fromFontSize, toFontSize, t) });\n // Track the measured size so a hugging box grows/shrinks smoothly\n // across the morph rather than jumping when the end commits.\n this._intrinsicWidth = lerpNumber(fromWidth, toResult.width, t);\n this._intrinsicHeight = lerpNumber(fromHeight, toResult.height, t);\n // Interpolate the shared center frame in lockstep so glyphs stay\n // centered within the resizing box.\n this._bounds = [\n lerpNumber(fromBounds[0], toBounds[0], t),\n lerpNumber(fromBounds[1], toBounds[1], t),\n lerpNumber(fromBounds[2], toBounds[2], t),\n lerpNumber(fromBounds[3], toBounds[3], t),\n ];\n this._tokens = latexFrame(t);\n\n if (t >= 1) {\n this.set({ latex: toLatex, fontSize: toFontSize });\n this._tokens = toResult.tokens.map(tok => ({ token: tok.token, path: tok.path, opacity: 1, x: 0, y: 0 }));\n this._intrinsicWidth = toResult.width;\n this._intrinsicHeight = toResult.height;\n this._bounds = toResult.bounds;\n this._animating = false;\n this._tokenizedFrom = `${toFontSize}|${toLatex}`;\n } else if (t <= 0) {\n this.set({ latex: fromLatex, fontSize: fromFontSize });\n this._tokens = fromAnimTokens;\n this._intrinsicWidth = fromWidth;\n this._intrinsicHeight = fromHeight;\n this._bounds = fromBounds;\n this._animating = false;\n this._tokenizedFrom = `${fromFontSize}|${fromLatex}`;\n }\n }, easing) as Command<LatexProps>;\n }\n\n /**\n * The shadow op comes **first**, as it does on every other painted node.\n *\n * The renderer does not draw a shadow when it sees one: `.shadow()` stores\n * it pending and the *next* fill or stroke op casts it (`_shadow` →\n * `storePendingShadows`, `_fill`/`_stroke` → `takePendingShadows`). Appended\n * last, it was stored by every token and then thrown away by the reset at\n * the end of the draw — a shadow set on a LaTeX node simply never appeared,\n * with no error to say why. Same failure {@link PaintedNode.renderStroke}\n * documents for a fill-less stroked shape, and the same one-op fix.\n *\n * Order also carries the fill-less case for free: an empty fill returns\n * before it takes the pending shadow, so the stroke below casts it from the\n * glyph outlines instead — which is what `castsShadowFromStroke` arranges on\n * a plain shape.\n */\n protected renderSelf(ctx: RenderContext2D): void {\n this.eachToken(ctx, (graphics, opacity) => graphics\n .shadow(scaleShadowOpacity(this.shadow as ShadowResolved[], opacity))\n .fill(scaleFillopacity(this.fill as FillResolved[], opacity))\n .stroke(this.stroke));\n }\n\n /**\n * The overlay, painted *through* the glyphs.\n *\n * `ShapeNode`'s inherited overlay pass fills whatever {@link\n * ShapeNode.shapeGraphics} describes, and this node describes nothing there:\n * a formula has no single fillable silhouette — its silhouette *is* the list\n * of token paths, which is why `renderSelf` is overridden rather than a\n * `shapeGraphics` supplied. So the generic pass drew nothing and an overlay\n * set on a LaTeX node simply never appeared, with no error to say why.\n *\n * Painted the same way the fill is: one `Graphics2D` per token, all sharing the\n * centre frame, each scaled by its token's animated opacity — so a morph\n * carries the overlay along with the glyph it is laid over instead of leaving\n * a wash hanging over glyphs that have faded out.\n *\n * The stroke is *not* re-drawn here. `renderSelf` already strokes each token\n * (there is no silhouette for the deferred {@link ShapeNode.renderStroke} to\n * outline), so it is painted under the overlay rather than over it — the one\n * place this node's draw order differs from a plain shape's, and the price of\n * a stroke that follows glyphs rather than a box.\n */\n protected override renderOverlay(ctx: RenderContext2D): void {\n const overlay = this.overlay as FillResolved[];\n if (overlay.length === 0) return;\n this.eachToken(ctx, (graphics, opacity) => graphics.fill(scaleFillopacity(overlay, opacity)));\n }\n\n /**\n * Draws every visible token's path once, handing each one to `paint` to have\n * its paint ops appended.\n *\n * A fresh `Graphics2D` per token per pass, because `.fill()`/`.stroke()` push\n * onto one op list and return `this` — a silhouette shared between the fill\n * pass and the overlay pass would accumulate both.\n */\n private eachToken(ctx: RenderContext2D, paint: (graphics: Graphics2D, opacity: number) => Graphics2D): void {\n // The one funnel every paint pass goes through, so the tokens a frame\n // draws cannot be older than the formula it holds.\n this._ensureTokens();\n for (const token of this._tokens) {\n if (token.opacity <= 0) continue;\n\n // Translate the path by token's interpolated position offset\n const pathStr = token.x !== 0 || token.y !== 0\n ? toPathString(offsetPath(token.path, token.x, token.y))\n : toPathString(token.path);\n\n ctx.draw(paint(new Graphics2D()\n .path({\n data: pathStr,\n start: this.start,\n end: this.end,\n // All tokens share one center frame so they keep their relative\n // layout — without this each glyph centers on its own bbox and\n // they all stack on the origin.\n centerBounds: this._bounds,\n }), token.opacity));\n }\n }\n\n}\n\nfunction scaleFillopacity(fills: FillResolved[], opacity: number): FillResolved[] {\n if (opacity >= 1) return fills;\n return fills.map(f => ({ ...f, opacity: (f.opacity ?? 1) * opacity }));\n}\n\n/**\n * A morphing token fades, and its shadow has to fade with it — the paints are\n * scaled the same way the fill's are, so a half-faded glyph casts a half-strength\n * shadow rather than sitting over a shadow at full weight.\n */\nfunction scaleShadowOpacity(shadows: ShadowResolved[], opacity: number): ShadowResolved[] {\n if (opacity >= 1) return shadows;\n return shadows.map(s => ({ ...s, fill: scaleFillopacity(s.fill, opacity) }));\n}\n\nfunction offsetPath(\n path: LatexToken[\"path\"],\n dx: number,\n dy: number,\n): LatexToken[\"path\"] {\n return path.map(cmd => {\n const c = cmd as any;\n const shifted: any = { ...c };\n if (\"x\" in c) shifted.x = c.x + dx;\n if (\"y\" in c) shifted.y = c.y + dy;\n if (\"x1\" in c) { shifted.x1 = c.x1 + dx; shifted.y1 = c.y1 + dy; }\n if (\"x2\" in c) { shifted.x2 = c.x2 + dx; shifted.y2 = c.y2 + dy; }\n return shifted;\n });\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import { Latex } from "./node.js";
2
+ /**
3
+ * Every node type this package publishes.
4
+ *
5
+ * A host registers these by handing the array to an engine —
6
+ * `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`
7
+ * key. The decorator only *declares* that key: nothing is registered by the mere
8
+ * act of importing a module, so a registry holds exactly what its owner asked
9
+ * for and two engines in one process can differ about it.
10
+ *
11
+ * Listing class **values** is also what survives bundling. This package declares
12
+ * `sideEffects: false`, which licenses a bundler to drop a module nothing
13
+ * imports a value from, and a document names a node type by string — so
14
+ * `import "./node.js"` for its side effect is exactly what gets shaken out, where an
15
+ * array of bindings is a data dependency that cannot be.
16
+ */
17
+ export declare const NODES: (typeof Latex)[];
18
+ //# sourceMappingURL=nodes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodes.d.ts","sourceRoot":"","sources":["../src/nodes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAE/B;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,KAAK,kBAAU,CAAC"}
package/dist/nodes.js ADDED
@@ -0,0 +1,18 @@
1
+ import { Latex } from "./node.js";
2
+ /**
3
+ * Every node type this package publishes.
4
+ *
5
+ * A host registers these by handing the array to an engine —
6
+ * `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`
7
+ * key. The decorator only *declares* that key: nothing is registered by the mere
8
+ * act of importing a module, so a registry holds exactly what its owner asked
9
+ * for and two engines in one process can differ about it.
10
+ *
11
+ * Listing class **values** is also what survives bundling. This package declares
12
+ * `sideEffects: false`, which licenses a bundler to drop a module nothing
13
+ * imports a value from, and a document names a node type by string — so
14
+ * `import "./node.js"` for its side effect is exactly what gets shaken out, where an
15
+ * array of bindings is a data dependency that cannot be.
16
+ */
17
+ export const NODES = [Latex];
18
+ //# sourceMappingURL=nodes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodes.js","sourceRoot":"","sources":["../src/nodes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAE/B;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,CAAC","sourcesContent":["import { Latex } from \"./node\";\n\n/**\n * Every node type this package publishes.\n *\n * A host registers these by handing the array to an engine —\n * `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`\n * key. The decorator only *declares* that key: nothing is registered by the mere\n * act of importing a module, so a registry holds exactly what its owner asked\n * for and two engines in one process can differ about it.\n *\n * Listing class **values** is also what survives bundling. This package declares\n * `sideEffects: false`, which licenses a bundler to drop a module nothing\n * imports a value from, and a document names a node type by string — so\n * `import \"./node\"` for its side effect is exactly what gets shaken out, where an\n * array of bindings is a data dependency that cannot be.\n */\nexport const NODES = [Latex];\n"]}