-
-
Notifications
You must be signed in to change notification settings - Fork 574
/
Latex.ts
112 lines (100 loc) · 3.44 KB
/
Latex.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import {
DependencyContext,
SignalValue,
SimpleSignal,
useLogger,
} from '@motion-canvas/core';
import {liteAdaptor} from 'mathjax-full/js/adaptors/liteAdaptor';
import {RegisterHTMLHandler} from 'mathjax-full/js/handlers/html';
import {TeX} from 'mathjax-full/js/input/tex';
import {AllPackages} from 'mathjax-full/js/input/tex/AllPackages';
import {mathjax} from 'mathjax-full/js/mathjax';
import {SVG} from 'mathjax-full/js/output/svg';
import {OptionList} from 'mathjax-full/js/util/Options';
import {initial, signal} from '../decorators';
import {Img, ImgProps} from './Img';
const Adaptor = liteAdaptor();
RegisterHTMLHandler(Adaptor);
const JaxDocument = mathjax.document('', {
// eslint-disable-next-line @typescript-eslint/naming-convention
InputJax: new TeX({packages: AllPackages}),
// eslint-disable-next-line @typescript-eslint/naming-convention
OutputJax: new SVG({fontCache: 'local'}),
});
export interface LatexProps extends ImgProps {
tex?: SignalValue<string>;
renderProps?: SignalValue<OptionList>;
}
/**
* A node for rendering equations with LaTeX.
*
* @preview
* ```tsx editor
* import {Latex, makeScene2D} from '@motion-canvas/2d';
*
* export default makeScene2D(function* (view) {
* view.add(
* <Latex
* // Note how this uses \color to set the color.
* tex="{\color{white} ax^2+bx+c=0 \implies x=\frac{-b \pm \sqrt{b^2-4ac}}{2a}}"
* width={600} // height and width can calculate based on each other
* />,
* );
* });
* ```
*/
export class Latex extends Img {
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, src: null});
}
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];
if (!this.imageElement.complete) {
DependencyContext.collectPromise(
new Promise((resolve, reject) => {
this.imageElement.addEventListener('load', resolve);
this.imageElement.addEventListener('error', reject);
}),
);
}
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;
}
}