mirror of
https://github.com/motion-canvas/motion-canvas.git
synced 2026-01-12 15:28:03 -05:00
feat(2d): add LaTeX component (#228)
This commit is contained in:
committed by
GitHub
parent
b8708732af
commit
4c26d2aaf0
122
package-lock.json
generated
122
package-lock.json
generated
@@ -10032,6 +10032,14 @@
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esm": {
|
||||
"version": "3.2.25",
|
||||
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
|
||||
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/espree": {
|
||||
"version": "9.3.3",
|
||||
"devOptional": true,
|
||||
@@ -13952,6 +13960,17 @@
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/mathjax-full": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.2.tgz",
|
||||
"integrity": "sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==",
|
||||
"dependencies": {
|
||||
"esm": "^3.2.25",
|
||||
"mhchemparser": "^4.1.0",
|
||||
"mj-context-menu": "^0.6.1",
|
||||
"speech-rule-engine": "^4.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-squeeze-paragraphs": {
|
||||
"version": "4.0.0",
|
||||
"license": "MIT",
|
||||
@@ -14228,6 +14247,11 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mhchemparser": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.1.1.tgz",
|
||||
"integrity": "sha512-R75CUN6O6e1t8bgailrF1qPq+HhVeFTM3XQ0uzI+mXTybmphy3b6h4NbLOYhemViQ3lUs+6CKRkC3Ws1TlYREA=="
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.5",
|
||||
"license": "MIT",
|
||||
@@ -14457,6 +14481,11 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/mj-context-menu": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz",
|
||||
"integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA=="
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"dev": true,
|
||||
@@ -18577,6 +18606,27 @@
|
||||
"wbuf": "^1.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/speech-rule-engine": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz",
|
||||
"integrity": "sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==",
|
||||
"dependencies": {
|
||||
"commander": "9.2.0",
|
||||
"wicked-good-xpath": "1.3.0",
|
||||
"xmldom-sre": "0.1.31"
|
||||
},
|
||||
"bin": {
|
||||
"sre": "bin/sre"
|
||||
}
|
||||
},
|
||||
"node_modules/speech-rule-engine/node_modules/commander": {
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz",
|
||||
"integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/split": {
|
||||
"version": "1.0.1",
|
||||
"dev": true,
|
||||
@@ -20486,6 +20536,11 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/wicked-good-xpath": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz",
|
||||
"integrity": "sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw=="
|
||||
},
|
||||
"node_modules/wide-align": {
|
||||
"version": "1.1.5",
|
||||
"dev": true,
|
||||
@@ -20777,6 +20832,14 @@
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/xmldom-sre": {
|
||||
"version": "0.1.31",
|
||||
"resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz",
|
||||
"integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==",
|
||||
"engines": {
|
||||
"node": ">=0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"license": "MIT",
|
||||
@@ -20873,7 +20936,8 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@motion-canvas/core": "^2.2.0",
|
||||
"code-fns": "^0.8.2"
|
||||
"code-fns": "^0.8.2",
|
||||
"mathjax-full": "^3.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@motion-canvas/internal": "0.0.0"
|
||||
@@ -24117,7 +24181,8 @@
|
||||
"requires": {
|
||||
"@motion-canvas/core": "^2.2.0",
|
||||
"@motion-canvas/internal": "0.0.0",
|
||||
"code-fns": "^0.8.2"
|
||||
"code-fns": "^0.8.2",
|
||||
"mathjax-full": "*"
|
||||
}
|
||||
},
|
||||
"@motion-canvas/core": {
|
||||
@@ -27597,6 +27662,11 @@
|
||||
"version": "3.3.0",
|
||||
"devOptional": true
|
||||
},
|
||||
"esm": {
|
||||
"version": "3.2.25",
|
||||
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
|
||||
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="
|
||||
},
|
||||
"espree": {
|
||||
"version": "9.3.3",
|
||||
"devOptional": true,
|
||||
@@ -30059,6 +30129,17 @@
|
||||
"version": "4.2.12",
|
||||
"dev": true
|
||||
},
|
||||
"mathjax-full": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.2.tgz",
|
||||
"integrity": "sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==",
|
||||
"requires": {
|
||||
"esm": "^3.2.25",
|
||||
"mhchemparser": "^4.1.0",
|
||||
"mj-context-menu": "^0.6.1",
|
||||
"speech-rule-engine": "^4.0.6"
|
||||
}
|
||||
},
|
||||
"mdast-squeeze-paragraphs": {
|
||||
"version": "4.0.0",
|
||||
"requires": {
|
||||
@@ -30239,6 +30320,11 @@
|
||||
"methods": {
|
||||
"version": "1.1.2"
|
||||
},
|
||||
"mhchemparser": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.1.1.tgz",
|
||||
"integrity": "sha512-R75CUN6O6e1t8bgailrF1qPq+HhVeFTM3XQ0uzI+mXTybmphy3b6h4NbLOYhemViQ3lUs+6CKRkC3Ws1TlYREA=="
|
||||
},
|
||||
"micromatch": {
|
||||
"version": "4.0.5",
|
||||
"requires": {
|
||||
@@ -30374,6 +30460,11 @@
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"mj-context-menu": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz",
|
||||
"integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA=="
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"dev": true
|
||||
@@ -32929,6 +33020,23 @@
|
||||
"wbuf": "^1.7.3"
|
||||
}
|
||||
},
|
||||
"speech-rule-engine": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz",
|
||||
"integrity": "sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==",
|
||||
"requires": {
|
||||
"commander": "9.2.0",
|
||||
"wicked-good-xpath": "1.3.0",
|
||||
"xmldom-sre": "0.1.31"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz",
|
||||
"integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"version": "1.0.1",
|
||||
"dev": true,
|
||||
@@ -34014,6 +34122,11 @@
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"wicked-good-xpath": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz",
|
||||
"integrity": "sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw=="
|
||||
},
|
||||
"wide-align": {
|
||||
"version": "1.1.5",
|
||||
"dev": true,
|
||||
@@ -34190,6 +34303,11 @@
|
||||
"version": "2.2.0",
|
||||
"devOptional": true
|
||||
},
|
||||
"xmldom-sre": {
|
||||
"version": "0.1.31",
|
||||
"resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz",
|
||||
"integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw=="
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.2"
|
||||
},
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@motion-canvas/core": "^2.2.0",
|
||||
"code-fns": "^0.8.2"
|
||||
"code-fns": "^0.8.2",
|
||||
"mathjax-full": "^3.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
84
packages/2d/src/components/Latex.ts
Normal file
84
packages/2d/src/components/Latex.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import {mathjax} from 'mathjax-full/js/mathjax';
|
||||
import {TeX} from 'mathjax-full/js/input/tex';
|
||||
import {SVG} from 'mathjax-full/js/output/svg';
|
||||
import {AllPackages} from 'mathjax-full/js/input/tex/AllPackages';
|
||||
import {liteAdaptor} from 'mathjax-full/js/adaptors/liteAdaptor';
|
||||
import {RegisterHTMLHandler} from 'mathjax-full/js/handlers/html';
|
||||
import {initial, signal} from '../decorators';
|
||||
import {Image, ImageProps} from './Image';
|
||||
import {
|
||||
DependencyContext,
|
||||
SignalValue,
|
||||
SimpleSignal,
|
||||
} from '@motion-canvas/core/lib/signals';
|
||||
import {OptionList} from 'mathjax-full/js/util/Options';
|
||||
import {useLogger} from '@motion-canvas/core/lib/utils';
|
||||
|
||||
const adaptor = liteAdaptor();
|
||||
RegisterHTMLHandler(adaptor);
|
||||
|
||||
const jaxDocument = mathjax.document('', {
|
||||
InputJax: new TeX({packages: AllPackages}),
|
||||
OutputJax: new SVG({fontCache: 'local'}),
|
||||
});
|
||||
|
||||
export interface LatexProps extends ImageProps {
|
||||
tex?: SignalValue<string>;
|
||||
renderProps?: SignalValue<OptionList>;
|
||||
}
|
||||
|
||||
export class Latex extends Image {
|
||||
private static svgContentsPool: Record<string, string> = {};
|
||||
|
||||
private readonly imageElement = document.createElement('img');
|
||||
|
||||
@initial({})
|
||||
@signal()
|
||||
public declare readonly options: SimpleSignal<OptionList, this>;
|
||||
|
||||
@signal()
|
||||
public declare readonly tex: SimpleSignal<string, this>;
|
||||
|
||||
public constructor(props: LatexProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected override image(): HTMLImageElement {
|
||||
// Render props may change the look of the TeX, so we need to cache both
|
||||
// source and render props together.
|
||||
const src = `${this.tex()}::${JSON.stringify(this.options())}`;
|
||||
if (Latex.svgContentsPool[src]) {
|
||||
this.imageElement.src = Latex.svgContentsPool[src];
|
||||
return this.imageElement;
|
||||
}
|
||||
|
||||
// Convert to TeX, look for any errors
|
||||
const tex = this.tex();
|
||||
const svg = adaptor.innerHTML(jaxDocument.convert(tex, this.options()));
|
||||
if (svg.includes('data-mjx-error')) {
|
||||
const errors = svg.match(/data-mjx-error="(.*?)"/);
|
||||
if (errors && errors.length > 0) {
|
||||
useLogger().error(`Invalid MathJax: ${errors[1]}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Encode to raw base64 image format
|
||||
const text = `data:image/svg+xml;base64,${btoa(
|
||||
`<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n${svg}`,
|
||||
)}`;
|
||||
Latex.svgContentsPool[src] = text;
|
||||
const image = document.createElement('img');
|
||||
image.src = text;
|
||||
image.src = text;
|
||||
if (!image.complete) {
|
||||
DependencyContext.collectPromise(
|
||||
new Promise((resolve, reject) => {
|
||||
image.addEventListener('load', resolve);
|
||||
image.addEventListener('error', reject);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './Circle';
|
||||
export * from './Image';
|
||||
export * from './Grid';
|
||||
export * from './Latex';
|
||||
export * from './Layout';
|
||||
export * from './Line';
|
||||
export * from './Node';
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"experimentalDecorators": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "@motion-canvas/2d/lib",
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"@motion-canvas/2d/lib/jsx-runtime": ["jsx-runtime.ts"]
|
||||
},
|
||||
|
||||
24
packages/docs/docs/getting-started/nodes.mdx
Normal file
24
packages/docs/docs/getting-started/nodes.mdx
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
sidebar_position: 10
|
||||
slug: /nodes
|
||||
---
|
||||
import AnimationPlayer from '@site/src/components/AnimationPlayer';
|
||||
import CodeBlock from '@theme/CodeBlock';
|
||||
import texSource from '!!raw-loader!@motion-canvas/examples/src/scenes/tex';
|
||||
|
||||
# Nodes
|
||||
|
||||
## LaTeX
|
||||
|
||||
<CodeBlock language="tsx">{texSource}</CodeBlock>
|
||||
|
||||
<AnimationPlayer small name="tex" />
|
||||
|
||||
### A note on tweening LaTeX
|
||||
Because we use a canvas renderer, we're rendering the LaTeX to an image instead
|
||||
using a full graphics backend to render the TeX. This means that for all
|
||||
intents and purposes, LaTeX should be treated as if it were an image rather
|
||||
than more flexible TeX rendering like you may see in other software.
|
||||
|
||||
In other words, opacity, position, size, etc. should be fine to tween, but we
|
||||
do not recommend tweening the `tex` signal.
|
||||
3
packages/examples/src/scenes/tex.meta
Normal file
3
packages/examples/src/scenes/tex.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"version": 0
|
||||
}
|
||||
21
packages/examples/src/scenes/tex.tsx
Normal file
21
packages/examples/src/scenes/tex.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import {Latex} from '@motion-canvas/2d/lib/components';
|
||||
import {makeScene2D} from '@motion-canvas/2d/lib/scenes';
|
||||
import {waitFor} from '@motion-canvas/core/lib/flow';
|
||||
import {createRef} from '@motion-canvas/core/lib/utils';
|
||||
|
||||
export default makeScene2D(function* (view) {
|
||||
const tex = createRef<Latex>();
|
||||
view.add(
|
||||
<Latex
|
||||
ref={tex}
|
||||
tex="{\color{white} x = \sin \left( \frac{\pi}{2} \right)}"
|
||||
y={0}
|
||||
width={400} // height and width can calculate based on each other
|
||||
/>,
|
||||
);
|
||||
|
||||
yield* waitFor(2);
|
||||
yield* tex().opacity(0, 1);
|
||||
yield* waitFor(2);
|
||||
yield* tex().opacity(1, 1);
|
||||
});
|
||||
3
packages/examples/src/tex.meta
Normal file
3
packages/examples/src/tex.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"version": 0
|
||||
}
|
||||
9
packages/examples/src/tex.ts
Normal file
9
packages/examples/src/tex.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import {makeProject} from '@motion-canvas/core';
|
||||
|
||||
import scene from './scenes/tex?scene';
|
||||
import {Vector2} from '@motion-canvas/core/lib/types';
|
||||
|
||||
export default makeProject({
|
||||
scenes: [scene],
|
||||
size: new Vector2(960, 540),
|
||||
});
|
||||
@@ -6,6 +6,7 @@ export default defineConfig({
|
||||
motionCanvas({
|
||||
project: [
|
||||
'./src/quickstart.ts',
|
||||
'./src/tex.ts',
|
||||
'./src/tweening-linear.ts',
|
||||
'./src/tweening-cubic.ts',
|
||||
'./src/tweening-color.ts',
|
||||
|
||||
Reference in New Issue
Block a user