@canvas-commons/2d 0.2.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/LICENSE +22 -0
- package/editor/index.css +40 -0
- package/editor/index.js +525 -0
- package/editor/index.js.map +1 -0
- package/lib/index-OvpZ-GsA.d.ts +5673 -0
- package/lib/index-OvpZ-GsA.d.ts.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +10326 -0
- package/lib/index.js.map +1 -0
- package/lib/jsx-dev-runtime.d.ts +2 -0
- package/lib/jsx-dev-runtime.js +2 -0
- package/lib/jsx-runtime.d.ts +2 -0
- package/lib/jsx-runtime.js +28 -0
- package/lib/jsx-runtime.js.map +1 -0
- package/package.json +79 -0
- package/src/editor/NodeInspectorConfig.tsx +76 -0
- package/src/editor/PreviewOverlayConfig.tsx +65 -0
- package/src/editor/Provider.tsx +109 -0
- package/src/editor/SceneGraphTabConfig.tsx +87 -0
- package/src/editor/icons/CircleIcon.tsx +7 -0
- package/src/editor/icons/CodeIcon.tsx +8 -0
- package/src/editor/icons/CurveIcon.tsx +7 -0
- package/src/editor/icons/GridIcon.tsx +7 -0
- package/src/editor/icons/IconMap.ts +35 -0
- package/src/editor/icons/ImgIcon.tsx +8 -0
- package/src/editor/icons/LayoutIcon.tsx +9 -0
- package/src/editor/icons/LineIcon.tsx +7 -0
- package/src/editor/icons/NodeIcon.tsx +7 -0
- package/src/editor/icons/RayIcon.tsx +7 -0
- package/src/editor/icons/RectIcon.tsx +7 -0
- package/src/editor/icons/ShapeIcon.tsx +7 -0
- package/src/editor/icons/TxtIcon.tsx +8 -0
- package/src/editor/icons/VideoIcon.tsx +7 -0
- package/src/editor/icons/View2DIcon.tsx +10 -0
- package/src/editor/index.css +0 -0
- package/src/editor/index.ts +19 -0
- package/src/editor/shortcuts.ts +27 -0
- package/src/editor/tree/DetachedRoot.tsx +27 -0
- package/src/editor/tree/NodeElement.tsx +72 -0
- package/src/editor/tree/TreeElement.tsx +70 -0
- package/src/editor/tree/TreeRoot.tsx +10 -0
- package/src/editor/tree/ViewRoot.tsx +20 -0
- package/src/editor/tree/index.module.scss +45 -0
- package/src/editor/tree/index.ts +4 -0
- package/src/editor/tree/navigation.ts +145 -0
- package/src/editor/tsconfig.build.json +5 -0
- package/src/editor/tsconfig.json +12 -0
- package/src/editor/tsdoc.json +4 -0
- package/src/editor/utils/SignalSet.ts +37 -0
- package/src/editor/utils/index.ts +1 -0
- package/src/editor/vite-env.d.ts +1 -0
- package/src/lib/code/CodeCursor.ts +468 -0
- package/src/lib/code/CodeDiffer.ts +77 -0
- package/src/lib/code/CodeFragment.ts +96 -0
- package/src/lib/code/CodeHighlighter.ts +73 -0
- package/src/lib/code/CodeMetrics.ts +47 -0
- package/src/lib/code/CodeRange.test.ts +113 -0
- package/src/lib/code/CodeRange.ts +222 -0
- package/src/lib/code/CodeScope.ts +100 -0
- package/src/lib/code/CodeSelection.ts +28 -0
- package/src/lib/code/CodeSignal.ts +348 -0
- package/src/lib/code/CodeTokenizer.ts +54 -0
- package/src/lib/code/DefaultHighlightStyle.ts +98 -0
- package/src/lib/code/LezerHighlighter.ts +113 -0
- package/src/lib/code/diff.test.ts +311 -0
- package/src/lib/code/diff.ts +319 -0
- package/src/lib/code/extractRange.ts +125 -0
- package/src/lib/code/index.ts +13 -0
- package/src/lib/components/Bezier.ts +103 -0
- package/src/lib/components/Camera.ts +401 -0
- package/src/lib/components/Circle.ts +310 -0
- package/src/lib/components/Code.ts +532 -0
- package/src/lib/components/CubicBezier.ts +115 -0
- package/src/lib/components/Curve.ts +460 -0
- package/src/lib/components/Grid.ts +134 -0
- package/src/lib/components/Icon.ts +153 -0
- package/src/lib/components/Img.ts +328 -0
- package/src/lib/components/Knot.ts +156 -0
- package/src/lib/components/Latex.ts +537 -0
- package/src/lib/components/Layout.ts +1101 -0
- package/src/lib/components/Line.ts +394 -0
- package/src/lib/components/Node.ts +1941 -0
- package/src/lib/components/Path.ts +132 -0
- package/src/lib/components/Polygon.ts +266 -0
- package/src/lib/components/QuadBezier.ts +103 -0
- package/src/lib/components/Ray.ts +126 -0
- package/src/lib/components/Rect.ts +219 -0
- package/src/lib/components/SVG.ts +853 -0
- package/src/lib/components/Shape.ts +322 -0
- package/src/lib/components/Spline.ts +318 -0
- package/src/lib/components/Txt.test.tsx +81 -0
- package/src/lib/components/Txt.ts +204 -0
- package/src/lib/components/TxtLeaf.ts +210 -0
- package/src/lib/components/Video.ts +368 -0
- package/src/lib/components/View2D.ts +85 -0
- package/src/lib/components/__logs__/image-without-source.ts +18 -0
- package/src/lib/components/__logs__/line-without-points.ts +31 -0
- package/src/lib/components/__logs__/reactive-playback-rate.ts +22 -0
- package/src/lib/components/__logs__/spline-with-insufficient-knots.ts +25 -0
- package/src/lib/components/__tests__/Camera.test.tsx +73 -0
- package/src/lib/components/__tests__/children.test.tsx +142 -0
- package/src/lib/components/__tests__/clone.test.tsx +126 -0
- package/src/lib/components/__tests__/fontInheritance.test.tsx +102 -0
- package/src/lib/components/__tests__/generatorTest.ts +27 -0
- package/src/lib/components/__tests__/mockScene2D.ts +50 -0
- package/src/lib/components/__tests__/query.test.tsx +122 -0
- package/src/lib/components/__tests__/state.test.tsx +60 -0
- package/src/lib/components/index.ts +26 -0
- package/src/lib/components/types.ts +35 -0
- package/src/lib/curves/ArcSegment.ts +169 -0
- package/src/lib/curves/CircleSegment.ts +99 -0
- package/src/lib/curves/CubicBezierSegment.ts +80 -0
- package/src/lib/curves/CurveDrawingInfo.ts +11 -0
- package/src/lib/curves/CurvePoint.ts +15 -0
- package/src/lib/curves/CurveProfile.ts +28 -0
- package/src/lib/curves/KnotInfo.ts +10 -0
- package/src/lib/curves/LineSegment.ts +75 -0
- package/src/lib/curves/Polynomial.ts +355 -0
- package/src/lib/curves/Polynomial2D.ts +62 -0
- package/src/lib/curves/PolynomialSegment.ts +151 -0
- package/src/lib/curves/QuadBezierSegment.ts +66 -0
- package/src/lib/curves/Segment.ts +31 -0
- package/src/lib/curves/UniformPolynomialCurveSampler.ts +93 -0
- package/src/lib/curves/createCurveProfileLerp.ts +471 -0
- package/src/lib/curves/getBezierSplineProfile.ts +227 -0
- package/src/lib/curves/getCircleProfile.ts +86 -0
- package/src/lib/curves/getPathProfile.ts +177 -0
- package/src/lib/curves/getPointAtDistance.ts +21 -0
- package/src/lib/curves/getPolylineProfile.test.ts +21 -0
- package/src/lib/curves/getPolylineProfile.ts +88 -0
- package/src/lib/curves/getRectProfile.ts +138 -0
- package/src/lib/curves/index.ts +16 -0
- package/src/lib/decorators/canvasStyleSignal.ts +15 -0
- package/src/lib/decorators/colorSignal.ts +9 -0
- package/src/lib/decorators/compound.ts +85 -0
- package/src/lib/decorators/computed.ts +18 -0
- package/src/lib/decorators/defaultStyle.ts +15 -0
- package/src/lib/decorators/filtersSignal.ts +133 -0
- package/src/lib/decorators/index.ts +10 -0
- package/src/lib/decorators/initializers.ts +32 -0
- package/src/lib/decorators/nodeName.ts +13 -0
- package/src/lib/decorators/signal.test.ts +89 -0
- package/src/lib/decorators/signal.ts +348 -0
- package/src/lib/decorators/spacingSignal.ts +15 -0
- package/src/lib/decorators/transformSignals.test.ts +858 -0
- package/src/lib/decorators/transformSignals.ts +1633 -0
- package/src/lib/decorators/vector2Signal.ts +35 -0
- package/src/lib/globals.d.ts +2 -0
- package/src/lib/index.ts +9 -0
- package/src/lib/jsx-dev-runtime.ts +2 -0
- package/src/lib/jsx-runtime.ts +45 -0
- package/src/lib/morphers/PathMorpher.ts +8 -0
- package/src/lib/morphers/defaultMorpher.ts +43 -0
- package/src/lib/morphers/index.ts +4 -0
- package/src/lib/morphers/manimMorpher.ts +658 -0
- package/src/lib/parse-svg-path.d.ts +14 -0
- package/src/lib/partials/Filter.ts +185 -0
- package/src/lib/partials/Gradient.ts +103 -0
- package/src/lib/partials/Pattern.ts +35 -0
- package/src/lib/partials/RoughConfig.ts +180 -0
- package/src/lib/partials/ShaderConfig.ts +122 -0
- package/src/lib/partials/index.ts +5 -0
- package/src/lib/partials/types.ts +58 -0
- package/src/lib/scenes/Scene2D.ts +167 -0
- package/src/lib/scenes/index.ts +3 -0
- package/src/lib/scenes/makeScene2D.ts +19 -0
- package/src/lib/scenes/useScene2D.ts +6 -0
- package/src/lib/tsconfig.build.json +5 -0
- package/src/lib/tsconfig.json +11 -0
- package/src/lib/tsdoc.json +4 -0
- package/src/lib/utils/CanvasUtils.ts +304 -0
- package/src/lib/utils/PathDataBuilder.ts +320 -0
- package/src/lib/utils/diff.test.ts +453 -0
- package/src/lib/utils/diff.ts +148 -0
- package/src/lib/utils/index.ts +5 -0
- package/src/lib/utils/is.ts +11 -0
- package/src/lib/utils/makeSignalExtensions.ts +29 -0
- package/src/lib/utils/rough.ts +513 -0
- package/src/lib/utils/withDefaults.tsx +26 -0
- package/src/tsconfig.base.json +18 -0
- package/src/tsconfig.build.json +8 -0
- package/src/tsconfig.json +5 -0
- package/tsconfig.project.json +7 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SerializedVector2,
|
|
3
|
+
Signal,
|
|
4
|
+
SignalValue,
|
|
5
|
+
SimpleSignal,
|
|
6
|
+
ThreadGenerator,
|
|
7
|
+
TimingFunction,
|
|
8
|
+
Vector2,
|
|
9
|
+
all,
|
|
10
|
+
delay,
|
|
11
|
+
easeInOutCubic,
|
|
12
|
+
lazy,
|
|
13
|
+
threadable,
|
|
14
|
+
tween,
|
|
15
|
+
useLogger,
|
|
16
|
+
} from '@canvas-commons/core';
|
|
17
|
+
import {liteAdaptor} from 'mathjax-full/js/adaptors/liteAdaptor.js';
|
|
18
|
+
import {RegisterHTMLHandler} from 'mathjax-full/js/handlers/html.js';
|
|
19
|
+
import {TeX} from 'mathjax-full/js/input/tex.js';
|
|
20
|
+
import {AllPackages} from 'mathjax-full/js/input/tex/AllPackages.js';
|
|
21
|
+
import {mathjax} from 'mathjax-full/js/mathjax.js';
|
|
22
|
+
import {SVG} from 'mathjax-full/js/output/svg.js';
|
|
23
|
+
import {OptionList} from 'mathjax-full/js/util/Options.js';
|
|
24
|
+
import {computed, initial, parser, signal} from '../decorators';
|
|
25
|
+
import {Curve} from './Curve';
|
|
26
|
+
import {Node} from './Node';
|
|
27
|
+
import {Path} from './Path';
|
|
28
|
+
import {Rect} from './Rect';
|
|
29
|
+
import {
|
|
30
|
+
SVGDocument,
|
|
31
|
+
SVGDocumentData,
|
|
32
|
+
SVG as SVGNode,
|
|
33
|
+
SVGProps,
|
|
34
|
+
SVGShapeData,
|
|
35
|
+
} from './SVG';
|
|
36
|
+
|
|
37
|
+
const Adaptor = liteAdaptor();
|
|
38
|
+
RegisterHTMLHandler(Adaptor);
|
|
39
|
+
|
|
40
|
+
const JaxDocument = mathjax.document('', {
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
42
|
+
InputJax: new TeX({packages: AllPackages}),
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
44
|
+
OutputJax: new SVG({fontCache: 'local'}),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export interface LatexProps extends Omit<SVGProps, 'svg'> {
|
|
48
|
+
tex?: SignalValue<string[] | string>;
|
|
49
|
+
renderProps?: SignalValue<OptionList>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A node for animating equations with LaTeX.
|
|
54
|
+
*
|
|
55
|
+
* @preview
|
|
56
|
+
* ```tsx editor
|
|
57
|
+
* import {Latex, makeScene2D} from '@canvas-commons/2d';
|
|
58
|
+
* import {createRef, waitFor} from '@canvas-commons/core';
|
|
59
|
+
*
|
|
60
|
+
* export default makeScene2D(function* (view) {
|
|
61
|
+
* const tex = createRef<Latex>();
|
|
62
|
+
* view.add(<Latex ref={tex} tex="{{y=}}{{a}}{{x^2}}" fill="white" />);
|
|
63
|
+
*
|
|
64
|
+
* yield* waitFor(0.2);
|
|
65
|
+
* yield* tex().tex('{{y=}}{{a}}{{x^2}} + {{bx}}', 1);
|
|
66
|
+
* yield* waitFor(0.2);
|
|
67
|
+
* yield* tex().tex(
|
|
68
|
+
* '{{y=}}{{\\left(}}{{a}}{{x^2}} + {{bx}}{{\\over 1}}{{\\right)}}',
|
|
69
|
+
* 1,
|
|
70
|
+
* );
|
|
71
|
+
* yield* waitFor(0.2);
|
|
72
|
+
* yield* tex().tex('{{y=}}{{a}}{{x^2}}', 1);
|
|
73
|
+
* });
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export class Latex extends SVGNode {
|
|
77
|
+
@lazy(() => {
|
|
78
|
+
return parseFloat(
|
|
79
|
+
window.getComputedStyle(SVGNode.containerElement).fontSize,
|
|
80
|
+
);
|
|
81
|
+
})
|
|
82
|
+
private static containerFontSize: number;
|
|
83
|
+
private static svgContentsPool: Record<string, string> = {};
|
|
84
|
+
private static texNodesPool: Record<string, SVGDocumentData> = {};
|
|
85
|
+
private svgSubTexMap: Record<string, string[]> = {};
|
|
86
|
+
|
|
87
|
+
@initial({})
|
|
88
|
+
@signal()
|
|
89
|
+
declare public readonly options: SimpleSignal<OptionList, this>;
|
|
90
|
+
|
|
91
|
+
@initial('')
|
|
92
|
+
@parser(function (this: Latex, value: string[] | string): string[] {
|
|
93
|
+
const array = typeof value === 'string' ? [value] : value;
|
|
94
|
+
return array
|
|
95
|
+
.reduce<string[]>((prev, current) => {
|
|
96
|
+
prev.push(...current.split(/{{(.*?)}}/));
|
|
97
|
+
return prev;
|
|
98
|
+
}, [])
|
|
99
|
+
.filter(sub => sub.trim().length > 0);
|
|
100
|
+
})
|
|
101
|
+
@signal()
|
|
102
|
+
declare public readonly tex: Signal<string[] | string, string[], this>;
|
|
103
|
+
|
|
104
|
+
public constructor(props: LatexProps) {
|
|
105
|
+
super({
|
|
106
|
+
fontSize: 48,
|
|
107
|
+
...props,
|
|
108
|
+
svg: '',
|
|
109
|
+
});
|
|
110
|
+
this.svg(this.latexSVG);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
protected override calculateWrapperScale(
|
|
114
|
+
documentSize: Vector2,
|
|
115
|
+
parentSize: SerializedVector2<number | null>,
|
|
116
|
+
): Vector2 {
|
|
117
|
+
if (parentSize.x || parentSize.y) {
|
|
118
|
+
return super.calculateWrapperScale(documentSize, parentSize);
|
|
119
|
+
}
|
|
120
|
+
return new Vector2(this.fontSize() / Latex.containerFontSize);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@computed()
|
|
124
|
+
protected latexSVG() {
|
|
125
|
+
return this.texToSvg(this.tex());
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private getNodeCharacterId({id}: SVGShapeData) {
|
|
129
|
+
if (!id.includes('-')) return id;
|
|
130
|
+
return id.substring(id.lastIndexOf('-') + 1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
protected override parseSVG(svg: string): SVGDocument {
|
|
134
|
+
if (!this.svgSubTexMap[svg]) {
|
|
135
|
+
return super.parseSVG(svg);
|
|
136
|
+
}
|
|
137
|
+
const subTexs = this.svgSubTexMap[svg].map(sub => sub.trim());
|
|
138
|
+
const key = `[${subTexs.join(',')}]::${JSON.stringify(this.options())}`;
|
|
139
|
+
const cached = Latex.texNodesPool[key];
|
|
140
|
+
if (cached && (cached.size.x > 0 || cached.size.y > 0)) {
|
|
141
|
+
return this.buildDocument(Latex.texNodesPool[key]);
|
|
142
|
+
}
|
|
143
|
+
const oldSVG = SVGNode.parseSVGData(svg);
|
|
144
|
+
const oldNodes = [...oldSVG.nodes];
|
|
145
|
+
|
|
146
|
+
const newNodes: SVGShapeData[] = [];
|
|
147
|
+
const pendingFragments: {sub: string; nodeCount: number}[] = [];
|
|
148
|
+
for (const sub of subTexs) {
|
|
149
|
+
const subSvg = this.subTexToSVG(sub);
|
|
150
|
+
const subNodes = SVGNode.parseSVGData(subSvg).nodes;
|
|
151
|
+
|
|
152
|
+
if (subNodes.length === 0) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const firstId = this.getNodeCharacterId(subNodes[0]);
|
|
157
|
+
const spliceIndex = oldNodes.findIndex(
|
|
158
|
+
node => this.getNodeCharacterId(node) === firstId,
|
|
159
|
+
);
|
|
160
|
+
if (spliceIndex === -1) {
|
|
161
|
+
pendingFragments.push({sub, nodeCount: subNodes.length});
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const children = oldNodes.splice(spliceIndex, subNodes.length);
|
|
165
|
+
|
|
166
|
+
if (children.length === 1) {
|
|
167
|
+
newNodes.push({
|
|
168
|
+
...children[0],
|
|
169
|
+
id: sub,
|
|
170
|
+
});
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
newNodes.push({
|
|
175
|
+
id: sub,
|
|
176
|
+
type: Node,
|
|
177
|
+
props: {},
|
|
178
|
+
children,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
for (const pending of pendingFragments) {
|
|
182
|
+
const children = oldNodes.splice(0, pending.nodeCount);
|
|
183
|
+
if (children.length === 0) continue;
|
|
184
|
+
if (children.length === 1) {
|
|
185
|
+
newNodes.push({...children[0], id: pending.sub});
|
|
186
|
+
} else {
|
|
187
|
+
newNodes.push({
|
|
188
|
+
id: pending.sub,
|
|
189
|
+
type: Node,
|
|
190
|
+
props: {},
|
|
191
|
+
children,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (oldNodes.length > 0) {
|
|
196
|
+
newNodes.push({
|
|
197
|
+
id: '__structural__',
|
|
198
|
+
type: Node,
|
|
199
|
+
props: {},
|
|
200
|
+
children: [...oldNodes],
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const newSVG: SVGDocumentData = {
|
|
205
|
+
size: oldSVG.size,
|
|
206
|
+
nodes: newNodes,
|
|
207
|
+
};
|
|
208
|
+
Latex.texNodesPool[key] = newSVG;
|
|
209
|
+
return this.buildDocument(newSVG);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
protected texToSvg(subTexs: string[]) {
|
|
213
|
+
const singleTex = subTexs.join('');
|
|
214
|
+
const svg = this.singleTexToSVG(singleTex);
|
|
215
|
+
if (subTexs.length > 1) {
|
|
216
|
+
this.svgSubTexMap[svg] = subTexs;
|
|
217
|
+
}
|
|
218
|
+
return svg;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private subTexToSVG(subTex: string) {
|
|
222
|
+
let tex = subTex.trim();
|
|
223
|
+
if (
|
|
224
|
+
['\\overline', '\\sqrt', '\\sqrt{'].includes(tex) ||
|
|
225
|
+
tex.endsWith('_') ||
|
|
226
|
+
tex.endsWith('^') ||
|
|
227
|
+
tex.endsWith('dot')
|
|
228
|
+
) {
|
|
229
|
+
tex += '{\\quad}';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (tex === '\\substack') tex = '\\quad';
|
|
233
|
+
|
|
234
|
+
const numLeft = tex.match(/\\left[()[\]|.\\]/g)?.length ?? 0;
|
|
235
|
+
const numRight = tex.match(/\\right[()[\]|.\\]/g)?.length ?? 0;
|
|
236
|
+
if (numLeft !== numRight) {
|
|
237
|
+
tex = tex.replace(/\\left/g, '\\big').replace(/\\right/g, '\\big');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const bracesLeft = tex.match(/((?<!\\)|(?<=\\\\)){/g)?.length ?? 0;
|
|
241
|
+
const bracesRight = tex.match(/((?<!\\)|(?<=\\\\))}/g)?.length ?? 0;
|
|
242
|
+
|
|
243
|
+
if (bracesLeft < bracesRight) {
|
|
244
|
+
tex = '{'.repeat(bracesRight - bracesLeft) + tex;
|
|
245
|
+
} else if (bracesRight < bracesLeft) {
|
|
246
|
+
tex += '}'.repeat(bracesLeft - bracesRight);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const hasArrayBegin = tex.includes('\\begin{array}');
|
|
250
|
+
const hasArrayEnd = tex.includes('\\end{array}');
|
|
251
|
+
if (hasArrayBegin !== hasArrayEnd) tex = '';
|
|
252
|
+
|
|
253
|
+
return this.singleTexToSVG(tex);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private singleTexToSVG(tex: string): string {
|
|
257
|
+
const src = `${tex}::${JSON.stringify(this.options())}`;
|
|
258
|
+
if (Latex.svgContentsPool[src]) {
|
|
259
|
+
return Latex.svgContentsPool[src];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const svg = Adaptor.innerHTML(JaxDocument.convert(tex, this.options()));
|
|
263
|
+
if (svg.includes('data-mjx-error')) {
|
|
264
|
+
const errors = svg.match(/data-mjx-error="(.*?)"/);
|
|
265
|
+
if (errors && errors.length > 0) {
|
|
266
|
+
useLogger().error(`Invalid MathJax: ${errors[1]}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
Latex.svgContentsPool[src] = svg;
|
|
270
|
+
return svg;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private getShapes(): Curve[] {
|
|
274
|
+
return this.wrapper
|
|
275
|
+
.children()
|
|
276
|
+
.flatMap(child =>
|
|
277
|
+
child.children().length > 0 ? child.children() : [child],
|
|
278
|
+
)
|
|
279
|
+
.filter((c): c is Curve => c instanceof Path || c instanceof Rect);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private getFragmentShapes(): Curve[][] {
|
|
283
|
+
return this.wrapper.children().map(child => {
|
|
284
|
+
const children = child.children().length > 0 ? child.children() : [child];
|
|
285
|
+
return children.filter(
|
|
286
|
+
(c): c is Curve => c instanceof Path || c instanceof Rect,
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private getTargetFragmentShapes(doc: SVGDocument): Curve[][] {
|
|
292
|
+
return doc.nodes.map(node => {
|
|
293
|
+
const shape = node.shape;
|
|
294
|
+
if (shape.children().length > 0) {
|
|
295
|
+
return shape
|
|
296
|
+
.children()
|
|
297
|
+
.filter((c): c is Curve => c instanceof Path || c instanceof Rect);
|
|
298
|
+
}
|
|
299
|
+
return shape instanceof Path || shape instanceof Rect ? [shape] : [];
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private createFragmentMorphAnimations(
|
|
304
|
+
sourceShapes: Curve[],
|
|
305
|
+
targetShapes: Curve[],
|
|
306
|
+
time: number,
|
|
307
|
+
timingFunction: TimingFunction,
|
|
308
|
+
): ThreadGenerator[] {
|
|
309
|
+
const animations: ThreadGenerator[] = [];
|
|
310
|
+
const maxLen = Math.max(sourceShapes.length, targetShapes.length);
|
|
311
|
+
|
|
312
|
+
for (let i = 0; i < maxLen; i++) {
|
|
313
|
+
const from = sourceShapes[i];
|
|
314
|
+
const to = targetShapes[i];
|
|
315
|
+
|
|
316
|
+
if (from && to) {
|
|
317
|
+
if (from instanceof Path && to instanceof Path) {
|
|
318
|
+
const fromData = from.data();
|
|
319
|
+
const toData = to.data();
|
|
320
|
+
if (fromData && toData && fromData !== toData) {
|
|
321
|
+
const interpolator = this.morpher.createInterpolator(
|
|
322
|
+
fromData,
|
|
323
|
+
toData,
|
|
324
|
+
);
|
|
325
|
+
animations.push(
|
|
326
|
+
tween(time, t => {
|
|
327
|
+
const progress = timingFunction(t);
|
|
328
|
+
from.data.context.setter(interpolator(progress));
|
|
329
|
+
}),
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
animations.push(
|
|
333
|
+
from.position(to.position(), time, timingFunction),
|
|
334
|
+
from.scale(to.scale(), time, timingFunction),
|
|
335
|
+
);
|
|
336
|
+
} else if (from instanceof Rect && to instanceof Rect) {
|
|
337
|
+
animations.push(
|
|
338
|
+
from.position(to.position(), time, timingFunction),
|
|
339
|
+
from.scale(to.scale(), time, timingFunction),
|
|
340
|
+
from.size(to.size(), time, timingFunction),
|
|
341
|
+
);
|
|
342
|
+
} else {
|
|
343
|
+
animations.push(from.opacity(0, time * 0.3, timingFunction));
|
|
344
|
+
const clone = to.clone();
|
|
345
|
+
clone.opacity(0);
|
|
346
|
+
this.wrapper.add(clone);
|
|
347
|
+
animations.push(
|
|
348
|
+
delay(time * 0.7, clone.opacity(1, time * 0.3, timingFunction)),
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
} else if (from && !to) {
|
|
352
|
+
animations.push(from.opacity(0, time * 0.3, timingFunction));
|
|
353
|
+
} else if (!from && to) {
|
|
354
|
+
const clone = to.clone();
|
|
355
|
+
clone.opacity(0);
|
|
356
|
+
this.wrapper.add(clone);
|
|
357
|
+
animations.push(clone.opacity(1, time, timingFunction));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return animations;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
@threadable()
|
|
365
|
+
protected *tweenTex(
|
|
366
|
+
value: string[],
|
|
367
|
+
time: number,
|
|
368
|
+
timingFunction: TimingFunction,
|
|
369
|
+
) {
|
|
370
|
+
const parsedValue = this.tex.context.parse(value);
|
|
371
|
+
const newSVG = this.texToSvg(parsedValue);
|
|
372
|
+
const currentShapes = this.getShapes();
|
|
373
|
+
|
|
374
|
+
const targetDoc = this.parseSVG(newSVG);
|
|
375
|
+
const targetShapes = targetDoc.nodes.flatMap(node => {
|
|
376
|
+
const shape = node.shape;
|
|
377
|
+
if (shape.children().length > 0) {
|
|
378
|
+
return shape
|
|
379
|
+
.children()
|
|
380
|
+
.filter((c): c is Curve => c instanceof Path || c instanceof Rect);
|
|
381
|
+
}
|
|
382
|
+
return shape instanceof Path || shape instanceof Rect ? [shape] : [];
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const currentPaths = currentShapes.filter(
|
|
386
|
+
(s): s is Path => s instanceof Path,
|
|
387
|
+
);
|
|
388
|
+
const currentRects = currentShapes.filter(
|
|
389
|
+
(s): s is Rect => s instanceof Rect,
|
|
390
|
+
);
|
|
391
|
+
const targetPaths = targetShapes.filter(
|
|
392
|
+
(s): s is Path => s instanceof Path,
|
|
393
|
+
);
|
|
394
|
+
const targetRects = targetShapes.filter(
|
|
395
|
+
(s): s is Rect => s instanceof Rect,
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
yield* all(
|
|
399
|
+
...this.createFragmentMorphAnimations(
|
|
400
|
+
currentPaths,
|
|
401
|
+
targetPaths,
|
|
402
|
+
time,
|
|
403
|
+
timingFunction,
|
|
404
|
+
),
|
|
405
|
+
...this.createFragmentMorphAnimations(
|
|
406
|
+
currentRects,
|
|
407
|
+
targetRects,
|
|
408
|
+
time,
|
|
409
|
+
timingFunction,
|
|
410
|
+
),
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
this.svg.context.setter(newSVG);
|
|
414
|
+
this.tex.context.setter(parsedValue);
|
|
415
|
+
this.wrapper.children(this.documentNodes);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Animate from the current tex to a new value using a fragment-to-fragment
|
|
420
|
+
* mapping.
|
|
421
|
+
*
|
|
422
|
+
* @param value - The new tex value.
|
|
423
|
+
* @param mapping - A mapping from source fragment indices to target fragment
|
|
424
|
+
* indices. For example, `[[0], [1, 2]]` maps source fragment 0 to target
|
|
425
|
+
* fragment 0, and source fragment 1 to both target fragments 1 and 2.
|
|
426
|
+
* @param time - The duration of the animation.
|
|
427
|
+
* @param timingFunction - The timing function.
|
|
428
|
+
*/
|
|
429
|
+
@threadable()
|
|
430
|
+
public *map(
|
|
431
|
+
value: string[] | string,
|
|
432
|
+
mapping: number[][],
|
|
433
|
+
time: number,
|
|
434
|
+
timingFunction?: TimingFunction,
|
|
435
|
+
) {
|
|
436
|
+
const logger = useLogger();
|
|
437
|
+
const parsedValue = this.tex.context.parse(value);
|
|
438
|
+
const newSVG = this.texToSvg(parsedValue);
|
|
439
|
+
const targetDoc = this.parseSVG(newSVG);
|
|
440
|
+
|
|
441
|
+
const timing: TimingFunction = timingFunction ?? easeInOutCubic;
|
|
442
|
+
|
|
443
|
+
const sourceFragments = this.getFragmentShapes();
|
|
444
|
+
const targetFragments = this.getTargetFragmentShapes(targetDoc);
|
|
445
|
+
|
|
446
|
+
const mappedTargetIndices = new Set<number>();
|
|
447
|
+
const animations: ThreadGenerator[] = [];
|
|
448
|
+
|
|
449
|
+
for (let srcIdx = 0; srcIdx < mapping.length; srcIdx++) {
|
|
450
|
+
const targetIndices = mapping[srcIdx];
|
|
451
|
+
const srcShapes = sourceFragments[srcIdx];
|
|
452
|
+
|
|
453
|
+
if (!srcShapes || srcShapes.length === 0) {
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (!targetIndices || targetIndices.length === 0) {
|
|
458
|
+
for (const shape of srcShapes) {
|
|
459
|
+
animations.push(shape.opacity(0, time * 0.3, timing));
|
|
460
|
+
}
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
for (let t = 0; t < targetIndices.length; t++) {
|
|
465
|
+
const tgtIdx = targetIndices[t];
|
|
466
|
+
|
|
467
|
+
if (tgtIdx < 0 || tgtIdx >= targetFragments.length) {
|
|
468
|
+
logger.warn(
|
|
469
|
+
`texMap: target index ${tgtIdx} is out of bounds (0-${targetFragments.length - 1})`,
|
|
470
|
+
);
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
mappedTargetIndices.add(tgtIdx);
|
|
475
|
+
const tgtShapes = targetFragments[tgtIdx];
|
|
476
|
+
|
|
477
|
+
if (t === 0) {
|
|
478
|
+
animations.push(
|
|
479
|
+
...this.createFragmentMorphAnimations(
|
|
480
|
+
srcShapes,
|
|
481
|
+
tgtShapes,
|
|
482
|
+
time,
|
|
483
|
+
timing,
|
|
484
|
+
),
|
|
485
|
+
);
|
|
486
|
+
} else {
|
|
487
|
+
const clonedSrc = srcShapes.map(shape => {
|
|
488
|
+
const clone = shape.clone();
|
|
489
|
+
this.wrapper.add(clone);
|
|
490
|
+
return clone;
|
|
491
|
+
});
|
|
492
|
+
animations.push(
|
|
493
|
+
...this.createFragmentMorphAnimations(
|
|
494
|
+
clonedSrc,
|
|
495
|
+
tgtShapes,
|
|
496
|
+
time,
|
|
497
|
+
timing,
|
|
498
|
+
),
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
for (
|
|
505
|
+
let srcIdx = mapping.length;
|
|
506
|
+
srcIdx < sourceFragments.length;
|
|
507
|
+
srcIdx++
|
|
508
|
+
) {
|
|
509
|
+
const srcShapes = sourceFragments[srcIdx];
|
|
510
|
+
if (srcShapes) {
|
|
511
|
+
for (const shape of srcShapes) {
|
|
512
|
+
animations.push(shape.opacity(0, time * 0.3, timing));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
for (let tgtIdx = 0; tgtIdx < targetFragments.length; tgtIdx++) {
|
|
518
|
+
if (mappedTargetIndices.has(tgtIdx)) {
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const tgtShapes = targetFragments[tgtIdx];
|
|
523
|
+
for (const shape of tgtShapes) {
|
|
524
|
+
const clone = shape.clone();
|
|
525
|
+
clone.opacity(0);
|
|
526
|
+
this.wrapper.add(clone);
|
|
527
|
+
animations.push(clone.opacity(1, time, timing));
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
yield* all(...animations);
|
|
532
|
+
|
|
533
|
+
this.svg.context.setter(newSVG);
|
|
534
|
+
this.tex.context.setter(parsedValue);
|
|
535
|
+
this.wrapper.children(this.documentNodes);
|
|
536
|
+
}
|
|
537
|
+
}
|