@jupyterlab/mathjax-extension 4.7.0-alpha.1 → 4.7.0-alpha.2
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/lib/index.d.ts +35 -0
- package/lib/index.js +211 -52
- package/lib/index.js.map +1 -1
- package/package.json +5 -6
- package/src/index.ts +278 -73
package/lib/index.d.ts
CHANGED
|
@@ -4,11 +4,36 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { JupyterFrontEndPlugin } from '@jupyterlab/application';
|
|
6
6
|
import { ILatexTypesetter } from '@jupyterlab/rendermime';
|
|
7
|
+
import type { IRenderMime } from '@jupyterlab/rendermime';
|
|
7
8
|
import type { MathDocument } from 'mathjax-full/js/core/MathDocument';
|
|
8
9
|
/**
|
|
9
10
|
* The MathJax Typesetter.
|
|
10
11
|
*/
|
|
11
12
|
export declare class MathJaxTypesetter implements ILatexTypesetter {
|
|
13
|
+
/**
|
|
14
|
+
* Construct a new MathJax typesetter.
|
|
15
|
+
*
|
|
16
|
+
* @param options - Options describing how math is recognized in the source
|
|
17
|
+
* text (see {@link MathJaxTypesetter.IOptions}).
|
|
18
|
+
*/
|
|
19
|
+
constructor(options?: MathJaxTypesetter.IOptions);
|
|
20
|
+
/**
|
|
21
|
+
* The options describing how math is recognized in the source text.
|
|
22
|
+
*
|
|
23
|
+
* Exposed so that the Markdown pre-processor (`removeMath`) and this
|
|
24
|
+
* typesetter agree on whether a single `$` introduces inline math.
|
|
25
|
+
*/
|
|
26
|
+
readonly mathParseOptions: IRenderMime.ILatexTypesetter.IMathParseOptions;
|
|
27
|
+
/**
|
|
28
|
+
* Create a new MathJax typesetter with the given parse options.
|
|
29
|
+
*
|
|
30
|
+
* Options which are not given are inherited from this typesetter; this
|
|
31
|
+
* typesetter is left unchanged. The underlying MathDocument is shared with
|
|
32
|
+
* any other typesetter using the same options, so this is cheap to call.
|
|
33
|
+
*
|
|
34
|
+
* @param options - The parse options to apply.
|
|
35
|
+
*/
|
|
36
|
+
withParseOptions(options: IRenderMime.ILatexTypesetter.IMathParseOptions): MathJaxTypesetter;
|
|
12
37
|
protected _ensureInitialized(): Promise<void>;
|
|
13
38
|
/**
|
|
14
39
|
* Get an instance of the MathDocument object.
|
|
@@ -21,6 +46,16 @@ export declare class MathJaxTypesetter implements ILatexTypesetter {
|
|
|
21
46
|
protected _initialized: boolean;
|
|
22
47
|
protected _mathDocument: MathDocument<any, any, any>;
|
|
23
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* A namespace for `MathJaxTypesetter` statics.
|
|
51
|
+
*/
|
|
52
|
+
export declare namespace MathJaxTypesetter {
|
|
53
|
+
/**
|
|
54
|
+
* Options for constructing a {@link MathJaxTypesetter}.
|
|
55
|
+
*/
|
|
56
|
+
interface IOptions extends IRenderMime.ILatexTypesetter.IMathParseOptions {
|
|
57
|
+
}
|
|
58
|
+
}
|
|
24
59
|
/**
|
|
25
60
|
* The MathJax extension.
|
|
26
61
|
*/
|
package/lib/index.js
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* @packageDocumentation
|
|
6
6
|
* @module mathjax-extension
|
|
7
7
|
*/
|
|
8
|
-
import { PromiseDelegate } from '@lumino/coreutils';
|
|
9
8
|
import { ILatexTypesetter } from '@jupyterlab/rendermime';
|
|
10
9
|
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
|
11
10
|
var CommandIDs;
|
|
@@ -23,12 +22,36 @@ var CommandIDs;
|
|
|
23
22
|
* The MathJax Typesetter.
|
|
24
23
|
*/
|
|
25
24
|
export class MathJaxTypesetter {
|
|
26
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Construct a new MathJax typesetter.
|
|
27
|
+
*
|
|
28
|
+
* @param options - Options describing how math is recognized in the source
|
|
29
|
+
* text (see {@link MathJaxTypesetter.IOptions}).
|
|
30
|
+
*/
|
|
31
|
+
constructor(options = {}) {
|
|
32
|
+
var _a;
|
|
27
33
|
this._initialized = false;
|
|
34
|
+
this.mathParseOptions = {
|
|
35
|
+
dollarInlineMath: (_a = options.dollarInlineMath) !== null && _a !== void 0 ? _a : true
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Create a new MathJax typesetter with the given parse options.
|
|
40
|
+
*
|
|
41
|
+
* Options which are not given are inherited from this typesetter; this
|
|
42
|
+
* typesetter is left unchanged. The underlying MathDocument is shared with
|
|
43
|
+
* any other typesetter using the same options, so this is cheap to call.
|
|
44
|
+
*
|
|
45
|
+
* @param options - The parse options to apply.
|
|
46
|
+
*/
|
|
47
|
+
withParseOptions(options) {
|
|
48
|
+
return new MathJaxTypesetter(Private.mergeParseOptions(this.mathParseOptions, options));
|
|
28
49
|
}
|
|
29
50
|
async _ensureInitialized() {
|
|
30
51
|
if (!this._initialized) {
|
|
31
|
-
this._mathDocument = await Private.ensureMathDocument(
|
|
52
|
+
this._mathDocument = await Private.ensureMathDocument(this.mathParseOptions.dollarInlineMath === false
|
|
53
|
+
? Private.INLINE_MATH_WITHOUT_DOLLAR
|
|
54
|
+
: Private.DEFAULT_INLINE_MATH);
|
|
32
55
|
this._initialized = true;
|
|
33
56
|
}
|
|
34
57
|
}
|
|
@@ -51,8 +74,15 @@ export class MathJaxTypesetter {
|
|
|
51
74
|
return;
|
|
52
75
|
}
|
|
53
76
|
this._mathDocument.options.elements = [node];
|
|
77
|
+
// `clear()` empties the document's math list before rendering, so that
|
|
78
|
+
// memory retained per math expression (most notably the compiled internal
|
|
79
|
+
// MathML tree) is bounded by the last typeset call rather than growing
|
|
80
|
+
// with the total amount of math on the page.
|
|
81
|
+
// This is also why we need to store the sources manually -
|
|
82
|
+
// we cannot extract them from MathJax state after `clear()`.
|
|
54
83
|
this._mathDocument.clear().render();
|
|
55
84
|
delete this._mathDocument.options.elements;
|
|
85
|
+
Private.recordTexSources(this._mathDocument);
|
|
56
86
|
Private.hardenAnchorLinks(node);
|
|
57
87
|
}
|
|
58
88
|
}
|
|
@@ -69,6 +99,16 @@ const mathJaxPlugin = {
|
|
|
69
99
|
const typesetter = new MathJaxTypesetter();
|
|
70
100
|
app.commands.addCommand(CommandIDs.copy, {
|
|
71
101
|
execute: async () => {
|
|
102
|
+
// Find the math expression the context menu was opened on; this works
|
|
103
|
+
// across all typesetter instances (e.g. ones configured with
|
|
104
|
+
// different delimiters), unlike the fallback below which reflects
|
|
105
|
+
// whichever expression the default typesetter processed last.
|
|
106
|
+
const root = app.contextMenuHitTest(node => node.classList.contains('MathJax'));
|
|
107
|
+
const tex = root ? Private.getTexSource(root) : undefined;
|
|
108
|
+
if (tex !== undefined) {
|
|
109
|
+
await navigator.clipboard.writeText(tex);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
72
112
|
const md = await typesetter.mathDocument();
|
|
73
113
|
const oJax = md.outputJax;
|
|
74
114
|
await navigator.clipboard.writeText(oJax.math.math);
|
|
@@ -83,15 +123,20 @@ const mathJaxPlugin = {
|
|
|
83
123
|
});
|
|
84
124
|
app.commands.addCommand(CommandIDs.scale, {
|
|
85
125
|
execute: async (args) => {
|
|
86
|
-
const md = await typesetter.mathDocument();
|
|
87
126
|
const scale = args['scale'] || 1.0;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
127
|
+
// Scale all documents so that every typesetter instance (e.g. ones
|
|
128
|
+
// configured with different delimiters) is affected alike; remember
|
|
129
|
+
// the scale so that documents created later inherit it too.
|
|
130
|
+
Private.setScale(scale);
|
|
131
|
+
for (const md of await Private.getMathDocuments()) {
|
|
132
|
+
md.outputJax.options.scale = scale;
|
|
133
|
+
md.rerender();
|
|
134
|
+
// Harden only the re-rendered anchors
|
|
135
|
+
for (const math of md.math) {
|
|
136
|
+
const root = math.typesetRoot;
|
|
137
|
+
if (root) {
|
|
138
|
+
Private.hardenAnchorLinks(root);
|
|
139
|
+
}
|
|
95
140
|
}
|
|
96
141
|
}
|
|
97
142
|
},
|
|
@@ -119,52 +164,166 @@ export default mathJaxPlugin;
|
|
|
119
164
|
*/
|
|
120
165
|
var Private;
|
|
121
166
|
(function (Private) {
|
|
167
|
+
/**
|
|
168
|
+
* The default delimiters for inline math.
|
|
169
|
+
*/
|
|
170
|
+
Private.DEFAULT_INLINE_MATH = [
|
|
171
|
+
['$', '$'],
|
|
172
|
+
['\\(', '\\)']
|
|
173
|
+
];
|
|
174
|
+
/**
|
|
175
|
+
* Merge parse options, where an `undefined` value in `overrides` means
|
|
176
|
+
* "keep the current value" rather than "reset to the default".
|
|
177
|
+
*/
|
|
178
|
+
function mergeParseOptions(base, overrides) {
|
|
179
|
+
const merged = { ...base };
|
|
180
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
181
|
+
if (value !== undefined) {
|
|
182
|
+
merged[key] = value;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return merged;
|
|
186
|
+
}
|
|
187
|
+
Private.mergeParseOptions = mergeParseOptions;
|
|
188
|
+
/**
|
|
189
|
+
* The inline math delimiters with the single `$` pair removed, so that `$`
|
|
190
|
+
* is rendered literally.
|
|
191
|
+
*/
|
|
192
|
+
Private.INLINE_MATH_WITHOUT_DOLLAR = [
|
|
193
|
+
['\\(', '\\)']
|
|
194
|
+
];
|
|
195
|
+
/**
|
|
196
|
+
* Load the MathJax modules and register the document handler.
|
|
197
|
+
*
|
|
198
|
+
* The heavy dynamic imports and the (global, one-time) handler registration
|
|
199
|
+
* are shared across all typesetters; only the per-instance document is built
|
|
200
|
+
* separately in {@link createMathDocument}. The returned bundle's type is
|
|
201
|
+
* inferred so no MathJax types need to be imported explicitly.
|
|
202
|
+
*/
|
|
203
|
+
async function loadModules() {
|
|
204
|
+
void import('mathjax-full/js/input/tex/require/RequireConfiguration');
|
|
205
|
+
const [{ mathjax }, { CHTML }, { TeX }, { TeXFont }, { AllPackages }, { SafeHandler }, { HTMLHandler }, { browserAdaptor }, { AssistiveMmlHandler }] = await Promise.all([
|
|
206
|
+
import('mathjax-full/js/mathjax'),
|
|
207
|
+
import('mathjax-full/js/output/chtml'),
|
|
208
|
+
import('mathjax-full/js/input/tex'),
|
|
209
|
+
import('mathjax-full/js/output/chtml/fonts/tex'),
|
|
210
|
+
import('mathjax-full/js/input/tex/AllPackages'),
|
|
211
|
+
import('mathjax-full/js/ui/safe/SafeHandler'),
|
|
212
|
+
import('mathjax-full/js/handlers/html/HTMLHandler'),
|
|
213
|
+
import('mathjax-full/js/adaptors/browserAdaptor'),
|
|
214
|
+
import('mathjax-full/js/a11y/assistive-mml')
|
|
215
|
+
]);
|
|
216
|
+
mathjax.handlers.register(AssistiveMmlHandler(SafeHandler(new HTMLHandler(browserAdaptor()))));
|
|
217
|
+
return { mathjax, CHTML, TeX, TeXFont, AllPackages };
|
|
218
|
+
}
|
|
122
219
|
let _loading = null;
|
|
123
|
-
|
|
220
|
+
/**
|
|
221
|
+
* Ensure the MathJax modules are loaded exactly once per page.
|
|
222
|
+
*/
|
|
223
|
+
function ensureMathModules() {
|
|
124
224
|
if (!_loading) {
|
|
125
|
-
_loading =
|
|
126
|
-
void import('mathjax-full/js/input/tex/require/RequireConfiguration');
|
|
127
|
-
const [{ mathjax }, { CHTML }, { TeX }, { TeXFont }, { AllPackages }, { SafeHandler }, { HTMLHandler }, { browserAdaptor }, { AssistiveMmlHandler }] = await Promise.all([
|
|
128
|
-
import('mathjax-full/js/mathjax'),
|
|
129
|
-
import('mathjax-full/js/output/chtml'),
|
|
130
|
-
import('mathjax-full/js/input/tex'),
|
|
131
|
-
import('mathjax-full/js/output/chtml/fonts/tex'),
|
|
132
|
-
import('mathjax-full/js/input/tex/AllPackages'),
|
|
133
|
-
import('mathjax-full/js/ui/safe/SafeHandler'),
|
|
134
|
-
import('mathjax-full/js/handlers/html/HTMLHandler'),
|
|
135
|
-
import('mathjax-full/js/adaptors/browserAdaptor'),
|
|
136
|
-
import('mathjax-full/js/a11y/assistive-mml')
|
|
137
|
-
]);
|
|
138
|
-
mathjax.handlers.register(AssistiveMmlHandler(SafeHandler(new HTMLHandler(browserAdaptor()))));
|
|
139
|
-
class EmptyFont extends TeXFont {
|
|
140
|
-
}
|
|
141
|
-
EmptyFont.defaultFonts = {};
|
|
142
|
-
const chtml = new CHTML({
|
|
143
|
-
// Override dynamically generated fonts in favor of our font css
|
|
144
|
-
font: new EmptyFont()
|
|
145
|
-
});
|
|
146
|
-
const tex = new TeX({
|
|
147
|
-
packages: AllPackages.concat('require'),
|
|
148
|
-
inlineMath: [
|
|
149
|
-
['$', '$'],
|
|
150
|
-
['\\(', '\\)']
|
|
151
|
-
],
|
|
152
|
-
displayMath: [
|
|
153
|
-
['$$', '$$'],
|
|
154
|
-
['\\[', '\\]']
|
|
155
|
-
],
|
|
156
|
-
processEscapes: true,
|
|
157
|
-
processEnvironments: true
|
|
158
|
-
});
|
|
159
|
-
const mathDocument = mathjax.document(window.document, {
|
|
160
|
-
InputJax: tex,
|
|
161
|
-
OutputJax: chtml
|
|
162
|
-
});
|
|
163
|
-
_loading.resolve(mathDocument);
|
|
225
|
+
_loading = loadModules();
|
|
164
226
|
}
|
|
165
|
-
return _loading
|
|
227
|
+
return _loading;
|
|
228
|
+
}
|
|
229
|
+
Private.ensureMathModules = ensureMathModules;
|
|
230
|
+
const _documents = new Map();
|
|
231
|
+
/**
|
|
232
|
+
* Get (or lazily create) the MathDocument for the given inline delimiters.
|
|
233
|
+
*
|
|
234
|
+
* Building a MathDocument costs on the order of ~5 ms and retains memory for
|
|
235
|
+
* the lifetime of the page. Caching one document per distinct delimiter
|
|
236
|
+
* configuration keeps that bounded, so constructing many typesetters with
|
|
237
|
+
* the same configuration (for example one per markdown cell across a large
|
|
238
|
+
* notebook) reuses a single document instead of multiplying the time and
|
|
239
|
+
* memory cost.
|
|
240
|
+
*/
|
|
241
|
+
function ensureMathDocument(inlineMath) {
|
|
242
|
+
const key = JSON.stringify(inlineMath);
|
|
243
|
+
let document = _documents.get(key);
|
|
244
|
+
if (!document) {
|
|
245
|
+
document = createMathDocument(inlineMath);
|
|
246
|
+
_documents.set(key, document);
|
|
247
|
+
}
|
|
248
|
+
return document;
|
|
166
249
|
}
|
|
167
250
|
Private.ensureMathDocument = ensureMathDocument;
|
|
251
|
+
/**
|
|
252
|
+
* Get all MathDocuments created so far, so that document-wide commands
|
|
253
|
+
* (e.g. scaling) can operate across every delimiter configuration in use.
|
|
254
|
+
*/
|
|
255
|
+
function getMathDocuments() {
|
|
256
|
+
return Promise.all(_documents.values());
|
|
257
|
+
}
|
|
258
|
+
Private.getMathDocuments = getMathDocuments;
|
|
259
|
+
let _scale = null;
|
|
260
|
+
/**
|
|
261
|
+
* Remember the requested scale factor, so that MathDocuments created later
|
|
262
|
+
* (e.g. on first use of a typesetter with a different delimiter
|
|
263
|
+
* configuration) start at the user's chosen scale rather than the default.
|
|
264
|
+
*/
|
|
265
|
+
function setScale(scale) {
|
|
266
|
+
_scale = scale;
|
|
267
|
+
}
|
|
268
|
+
Private.setScale = setScale;
|
|
269
|
+
const _texSourceByRoot = new WeakMap();
|
|
270
|
+
/**
|
|
271
|
+
* Record the TeX source of each math expression typeset by `document`,
|
|
272
|
+
* keyed by its rendered container element.
|
|
273
|
+
*
|
|
274
|
+
* A MathDocument only retains the math items from its most recent
|
|
275
|
+
* `render()` call (each `typeset()` starts with `clear()`), so the source
|
|
276
|
+
* of earlier expressions must be captured here for the copy command to
|
|
277
|
+
* find the expression that was actually clicked. Unlike retaining the math
|
|
278
|
+
* items themselves, this only keeps the source string, and the `WeakMap`
|
|
279
|
+
* lets it be reclaimed as soon as the rendered node is garbage-collected.
|
|
280
|
+
*/
|
|
281
|
+
function recordTexSources(document) {
|
|
282
|
+
for (const item of document.math) {
|
|
283
|
+
if (item.typesetRoot) {
|
|
284
|
+
_texSourceByRoot.set(item.typesetRoot, item.math);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
Private.recordTexSources = recordTexSources;
|
|
289
|
+
/**
|
|
290
|
+
* Get the TeX source of the math expression rendered in `root`
|
|
291
|
+
* (a `mjx-container` element), if known.
|
|
292
|
+
*/
|
|
293
|
+
function getTexSource(root) {
|
|
294
|
+
return _texSourceByRoot.get(root);
|
|
295
|
+
}
|
|
296
|
+
Private.getTexSource = getTexSource;
|
|
297
|
+
/**
|
|
298
|
+
* Build a MathDocument configured with the given inline delimiters.
|
|
299
|
+
*/
|
|
300
|
+
async function createMathDocument(inlineMath) {
|
|
301
|
+
const { mathjax, CHTML, TeX, TeXFont, AllPackages } = await ensureMathModules();
|
|
302
|
+
class EmptyFont extends TeXFont {
|
|
303
|
+
}
|
|
304
|
+
EmptyFont.defaultFonts = {};
|
|
305
|
+
const chtml = new CHTML({
|
|
306
|
+
// Override dynamically generated fonts in favor of our font css
|
|
307
|
+
font: new EmptyFont()
|
|
308
|
+
});
|
|
309
|
+
if (_scale !== null) {
|
|
310
|
+
chtml.options.scale = _scale;
|
|
311
|
+
}
|
|
312
|
+
const tex = new TeX({
|
|
313
|
+
packages: AllPackages.concat('require'),
|
|
314
|
+
inlineMath,
|
|
315
|
+
displayMath: [
|
|
316
|
+
['$$', '$$'],
|
|
317
|
+
['\\[', '\\]']
|
|
318
|
+
],
|
|
319
|
+
processEscapes: true,
|
|
320
|
+
processEnvironments: true
|
|
321
|
+
});
|
|
322
|
+
return mathjax.document(window.document, {
|
|
323
|
+
InputJax: tex,
|
|
324
|
+
OutputJax: chtml
|
|
325
|
+
});
|
|
326
|
+
}
|
|
168
327
|
/**
|
|
169
328
|
* Utility function to harden anchor links in a given element
|
|
170
329
|
*/
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,2DAA2D;AAC3D,uDAAuD;AACvD;;;GAGG;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,2DAA2D;AAC3D,uDAAuD;AACvD;;;GAGG;AAOH,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAI1D,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAItE,IAAU,UAAU,CASnB;AATD,WAAU,UAAU;IAClB;;OAEG;IACU,eAAI,GAAG,mBAAmB,CAAC;IACxC;;OAEG;IACU,gBAAK,GAAG,eAAe,CAAC;AACvC,CAAC,EATS,UAAU,KAAV,UAAU,QASnB;AAQD;;GAEG;AACH,MAAM,OAAO,iBAAiB;IAC5B;;;;;OAKG;IACH,YAAY,UAAsC,EAAE;;QA0E1C,iBAAY,GAAY,KAAK,CAAC;QAzEtC,IAAI,CAAC,gBAAgB,GAAG;YACtB,gBAAgB,EAAE,MAAA,OAAO,CAAC,gBAAgB,mCAAI,IAAI;SACnD,CAAC;IACJ,CAAC;IAUD;;;;;;;;OAQG;IACH,gBAAgB,CACd,OAAuD;QAEvD,OAAO,IAAI,iBAAiB,CAC1B,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAC1D,CAAC;IACJ,CAAC;IAES,KAAK,CAAC,kBAAkB;QAChC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,IAAI,CAAC,aAAa,GAAG,MAAM,OAAO,CAAC,kBAAkB,CACnD,IAAI,CAAC,gBAAgB,CAAC,gBAAgB,KAAK,KAAK;gBAC9C,CAAC,CAAC,OAAO,CAAC,0BAA0B;gBACpC,CAAC,CAAC,OAAO,CAAC,mBAAmB,CAChC,CAAC;YACF,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QAC3B,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,YAAY;QAChB,MAAM,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAChC,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO,CAAC,IAAiB;QAC7B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAClC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACjB,OAAO;QACT,CAAC;QAED,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,QAAQ,GAAG,CAAC,IAAI,CAAC,CAAC;QAC7C,uEAAuE;QACvE,0EAA0E;QAC1E,uEAAuE;QACvE,6CAA6C;QAC7C,2DAA2D;QAC3D,6DAA6D;QAC7D,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,CAAC;QACpC,OAAO,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC;QAC3C,OAAO,CAAC,gBAAgB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC7C,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;CAIF;AAeD;;GAEG;AACH,MAAM,aAAa,GAA4C;IAC7D,EAAE,EAAE,sCAAsC;IAC1C,WAAW,EAAE,yDAAyD;IACtE,QAAQ,EAAE,gBAAgB;IAC1B,QAAQ,EAAE,CAAC,WAAW,CAAC;IACvB,QAAQ,EAAE,CAAC,GAAoB,EAAE,UAA8B,EAAE,EAAE;QACjE,MAAM,KAAK,GAAG,CAAC,UAAU,aAAV,UAAU,cAAV,UAAU,GAAI,cAAc,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAChE,MAAM,UAAU,GAAG,IAAI,iBAAiB,EAAE,CAAC;QAE3C,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,EAAE;YACvC,OAAO,EAAE,KAAK,IAAI,EAAE;gBAClB,sEAAsE;gBACtE,6DAA6D;gBAC7D,kEAAkE;gBAClE,8DAA8D;gBAC9D,MAAM,IAAI,GAAG,GAAG,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,CACzC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,CACnC,CAAC;gBACF,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;gBAC1D,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;oBACtB,MAAM,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;oBACzC,OAAO;gBACT,CAAC;gBACD,MAAM,EAAE,GAAG,MAAM,UAAU,CAAC,YAAY,EAAE,CAAC;gBAC3C,MAAM,IAAI,GAAQ,EAAE,CAAC,SAAS,CAAC;gBAC/B,MAAM,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtD,CAAC;YACD,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAAC;YACrC,WAAW,EAAE;gBACX,IAAI,EAAE;oBACJ,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE,EAAE;iBACf;aACF;SACF,CAAC,CAAC;QAEH,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,KAAK,EAAE;YACxC,OAAO,EAAE,KAAK,EAAE,IAAuB,EAAE,EAAE;gBACzC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC;gBACnC,mEAAmE;gBACnE,oEAAoE;gBACpE,4DAA4D;gBAC5D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;gBACxB,KAAK,MAAM,EAAE,IAAI,MAAM,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC;oBAClD,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC;oBACnC,EAAE,CAAC,QAAQ,EAAE,CAAC;oBAEd,sCAAsC;oBACtC,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;wBAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,WAAiC,CAAC;wBACpD,IAAI,IAAI,EAAE,CAAC;4BACT,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;wBAClC,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YACD,KAAK,EAAE,IAAI,CAAC,EAAE,CACZ,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAAC;gBAC1B,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;YAC3D,WAAW,EAAE;gBACX,IAAI,EAAE;oBACJ,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACV,KAAK,EAAE;4BACL,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,wCAAwC,CAAC;yBAChE;qBACF;iBACF;aACF;SACF,CAAC,CAAC;QAEH,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,SAAS,EAAE,IAAI;CAChB,CAAC;AAEF,eAAe,aAAa,CAAC;AAE7B;;GAEG;AACH,IAAU,OAAO,CA4NhB;AA5ND,WAAU,OAAO;IACf;;OAEG;IACU,2BAAmB,GAAuB;QACrD,CAAC,GAAG,EAAE,GAAG,CAAC;QACV,CAAC,KAAK,EAAE,KAAK,CAAC;KACf,CAAC;IAEF;;;OAGG;IACH,SAAgB,iBAAiB,CAC/B,IAAoD,EACpD,SAAyD;QAEzD,MAAM,MAAM,GAA4B,EAAE,GAAG,IAAI,EAAE,CAAC;QACpD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;YACrD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YACtB,CAAC;QACH,CAAC;QACD,OAAO,MAAwD,CAAC;IAClE,CAAC;IAXe,yBAAiB,oBAWhC,CAAA;IAED;;;OAGG;IACU,kCAA0B,GAAuB;QAC5D,CAAC,KAAK,EAAE,KAAK,CAAC;KACf,CAAC;IAEF;;;;;;;OAOG;IACH,KAAK,UAAU,WAAW;QACxB,KAAK,MAAM,CAAC,wDAAwD,CAAC,CAAC;QAEtE,MAAM,CACJ,EAAE,OAAO,EAAE,EACX,EAAE,KAAK,EAAE,EACT,EAAE,GAAG,EAAE,EACP,EAAE,OAAO,EAAE,EACX,EAAE,WAAW,EAAE,EACf,EAAE,WAAW,EAAE,EACf,EAAE,WAAW,EAAE,EACf,EAAE,cAAc,EAAE,EAClB,EAAE,mBAAmB,EAAE,CACxB,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACpB,MAAM,CAAC,yBAAyB,CAAC;YACjC,MAAM,CAAC,8BAA8B,CAAC;YACtC,MAAM,CAAC,2BAA2B,CAAC;YACnC,MAAM,CAAC,wCAAwC,CAAC;YAChD,MAAM,CAAC,uCAAuC,CAAC;YAC/C,MAAM,CAAC,qCAAqC,CAAC;YAC7C,MAAM,CAAC,2CAA2C,CAAC;YACnD,MAAM,CAAC,yCAAyC,CAAC;YACjD,MAAM,CAAC,oCAAoC,CAAC;SAC7C,CAAC,CAAC;QAEH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CACvB,mBAAmB,CAAC,WAAW,CAAC,IAAI,WAAW,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,CACpE,CAAC;QAEF,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IACvD,CAAC;IAED,IAAI,QAAQ,GAA0C,IAAI,CAAC;IAE3D;;OAEG;IACH,SAAgB,iBAAiB;QAC/B,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,QAAQ,GAAG,WAAW,EAAE,CAAC;QAC3B,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IALe,yBAAiB,oBAKhC,CAAA;IAED,MAAM,UAAU,GAAG,IAAI,GAAG,EAAgD,CAAC;IAE3E;;;;;;;;;OASG;IACH,SAAgB,kBAAkB,CAChC,UAA8B;QAE9B,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QACvC,IAAI,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,QAAQ,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC;YAC1C,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAVe,0BAAkB,qBAUjC,CAAA;IAED;;;OAGG;IACH,SAAgB,gBAAgB;QAC9B,OAAO,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1C,CAAC;IAFe,wBAAgB,mBAE/B,CAAA;IAED,IAAI,MAAM,GAAkB,IAAI,CAAC;IAEjC;;;;OAIG;IACH,SAAgB,QAAQ,CAAC,KAAa;QACpC,MAAM,GAAG,KAAK,CAAC;IACjB,CAAC;IAFe,gBAAQ,WAEvB,CAAA;IAED,MAAM,gBAAgB,GAAG,IAAI,OAAO,EAAmB,CAAC;IAExD;;;;;;;;;;OAUG;IACH,SAAgB,gBAAgB,CAC9B,QAAqC;QAErC,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;YACjC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;gBACrB,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YACpD,CAAC;QACH,CAAC;IACH,CAAC;IARe,wBAAgB,mBAQ/B,CAAA;IAED;;;OAGG;IACH,SAAgB,YAAY,CAAC,IAAa;QACxC,OAAO,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAFe,oBAAY,eAE3B,CAAA;IAED;;OAEG;IACH,KAAK,UAAU,kBAAkB,CAC/B,UAA8B;QAE9B,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,GACjD,MAAM,iBAAiB,EAAE,CAAC;QAE5B,MAAM,SAAU,SAAQ,OAAO;;QACZ,sBAAY,GAAG,EAAS,CAAC;QAG5C,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC;YACtB,gEAAgE;YAChE,IAAI,EAAE,IAAI,SAAS,EAAE;SACtB,CAAC,CAAC;QACH,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,KAAK,CAAC,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC;QAC/B,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC;YAClB,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC;YACvC,UAAU;YACV,WAAW,EAAE;gBACX,CAAC,IAAI,EAAE,IAAI,CAAC;gBACZ,CAAC,KAAK,EAAE,KAAK,CAAC;aACf;YACD,cAAc,EAAE,IAAI;YACpB,mBAAmB,EAAE,IAAI;SAC1B,CAAC,CAAC;QAEH,OAAO,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE;YACvC,QAAQ,EAAE,GAAG;YACb,SAAS,EAAE,KAAK;SACjB,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,SAAgB,iBAAiB,CAAC,OAAoB;QACpD,MAAM,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAoB,YAAY,CAAC,CAAC;QAC1E,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;YACvB,4CAA4C;YAC5C,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;YACrC,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YAErE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;gBACpC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC7B,CAAC;YAED,MAAM,CAAC,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAEjC,6CAA6C;YAC7C,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;gBAC/B,MAAM,CAAC,MAAM,GAAG,QAAQ,CAAC;YAC3B,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAlBe,yBAAiB,oBAkBhC,CAAA;AACH,CAAC,EA5NS,OAAO,KAAP,OAAO,QA4NhB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupyterlab/mathjax-extension",
|
|
3
|
-
"version": "4.7.0-alpha.
|
|
3
|
+
"version": "4.7.0-alpha.2",
|
|
4
4
|
"description": "A JupyterLab extension providing MathJax Typesetting",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -48,14 +48,13 @@
|
|
|
48
48
|
"watch": "tsc -b --watch"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@jupyterlab/application": "^4.7.0-alpha.
|
|
52
|
-
"@jupyterlab/rendermime": "^4.7.0-alpha.
|
|
53
|
-
"@jupyterlab/translation": "^4.7.0-alpha.
|
|
54
|
-
"@lumino/coreutils": "^2.2.3",
|
|
51
|
+
"@jupyterlab/application": "^4.7.0-alpha.2",
|
|
52
|
+
"@jupyterlab/rendermime": "^4.7.0-alpha.2",
|
|
53
|
+
"@jupyterlab/translation": "^4.7.0-alpha.2",
|
|
55
54
|
"mathjax-full": "^3.2.2"
|
|
56
55
|
},
|
|
57
56
|
"devDependencies": {
|
|
58
|
-
"@jupyterlab/testing": "^4.7.0-alpha.
|
|
57
|
+
"@jupyterlab/testing": "^4.7.0-alpha.2",
|
|
59
58
|
"@types/jest": "^29.2.0",
|
|
60
59
|
"jest": "^29.2.0",
|
|
61
60
|
"rimraf": "~5.0.5",
|
package/src/index.ts
CHANGED
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
* @module mathjax-extension
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { PromiseDelegate } from '@lumino/coreutils';
|
|
10
|
-
|
|
11
9
|
import type {
|
|
12
10
|
JupyterFrontEnd,
|
|
13
11
|
JupyterFrontEndPlugin
|
|
@@ -15,6 +13,8 @@ import type {
|
|
|
15
13
|
|
|
16
14
|
import { ILatexTypesetter } from '@jupyterlab/rendermime';
|
|
17
15
|
|
|
16
|
+
import type { IRenderMime } from '@jupyterlab/rendermime';
|
|
17
|
+
|
|
18
18
|
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
|
19
19
|
|
|
20
20
|
import type { MathDocument } from 'mathjax-full/js/core/MathDocument';
|
|
@@ -40,9 +40,50 @@ namespace CommandArgs {
|
|
|
40
40
|
* The MathJax Typesetter.
|
|
41
41
|
*/
|
|
42
42
|
export class MathJaxTypesetter implements ILatexTypesetter {
|
|
43
|
+
/**
|
|
44
|
+
* Construct a new MathJax typesetter.
|
|
45
|
+
*
|
|
46
|
+
* @param options - Options describing how math is recognized in the source
|
|
47
|
+
* text (see {@link MathJaxTypesetter.IOptions}).
|
|
48
|
+
*/
|
|
49
|
+
constructor(options: MathJaxTypesetter.IOptions = {}) {
|
|
50
|
+
this.mathParseOptions = {
|
|
51
|
+
dollarInlineMath: options.dollarInlineMath ?? true
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The options describing how math is recognized in the source text.
|
|
57
|
+
*
|
|
58
|
+
* Exposed so that the Markdown pre-processor (`removeMath`) and this
|
|
59
|
+
* typesetter agree on whether a single `$` introduces inline math.
|
|
60
|
+
*/
|
|
61
|
+
readonly mathParseOptions: IRenderMime.ILatexTypesetter.IMathParseOptions;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Create a new MathJax typesetter with the given parse options.
|
|
65
|
+
*
|
|
66
|
+
* Options which are not given are inherited from this typesetter; this
|
|
67
|
+
* typesetter is left unchanged. The underlying MathDocument is shared with
|
|
68
|
+
* any other typesetter using the same options, so this is cheap to call.
|
|
69
|
+
*
|
|
70
|
+
* @param options - The parse options to apply.
|
|
71
|
+
*/
|
|
72
|
+
withParseOptions(
|
|
73
|
+
options: IRenderMime.ILatexTypesetter.IMathParseOptions
|
|
74
|
+
): MathJaxTypesetter {
|
|
75
|
+
return new MathJaxTypesetter(
|
|
76
|
+
Private.mergeParseOptions(this.mathParseOptions, options)
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
43
80
|
protected async _ensureInitialized() {
|
|
44
81
|
if (!this._initialized) {
|
|
45
|
-
this._mathDocument = await Private.ensureMathDocument(
|
|
82
|
+
this._mathDocument = await Private.ensureMathDocument(
|
|
83
|
+
this.mathParseOptions.dollarInlineMath === false
|
|
84
|
+
? Private.INLINE_MATH_WITHOUT_DOLLAR
|
|
85
|
+
: Private.DEFAULT_INLINE_MATH
|
|
86
|
+
);
|
|
46
87
|
this._initialized = true;
|
|
47
88
|
}
|
|
48
89
|
}
|
|
@@ -67,8 +108,15 @@ export class MathJaxTypesetter implements ILatexTypesetter {
|
|
|
67
108
|
}
|
|
68
109
|
|
|
69
110
|
this._mathDocument.options.elements = [node];
|
|
111
|
+
// `clear()` empties the document's math list before rendering, so that
|
|
112
|
+
// memory retained per math expression (most notably the compiled internal
|
|
113
|
+
// MathML tree) is bounded by the last typeset call rather than growing
|
|
114
|
+
// with the total amount of math on the page.
|
|
115
|
+
// This is also why we need to store the sources manually -
|
|
116
|
+
// we cannot extract them from MathJax state after `clear()`.
|
|
70
117
|
this._mathDocument.clear().render();
|
|
71
118
|
delete this._mathDocument.options.elements;
|
|
119
|
+
Private.recordTexSources(this._mathDocument);
|
|
72
120
|
Private.hardenAnchorLinks(node);
|
|
73
121
|
}
|
|
74
122
|
|
|
@@ -76,6 +124,19 @@ export class MathJaxTypesetter implements ILatexTypesetter {
|
|
|
76
124
|
protected _mathDocument: MathDocument<any, any, any>;
|
|
77
125
|
}
|
|
78
126
|
|
|
127
|
+
/**
|
|
128
|
+
* A namespace for `MathJaxTypesetter` statics.
|
|
129
|
+
*/
|
|
130
|
+
export namespace MathJaxTypesetter {
|
|
131
|
+
// Extends the provider-agnostic math-parsing options; MathJax-specific
|
|
132
|
+
// options can be added here later in backward-compatible fashion.
|
|
133
|
+
/**
|
|
134
|
+
* Options for constructing a {@link MathJaxTypesetter}.
|
|
135
|
+
*/
|
|
136
|
+
export interface IOptions
|
|
137
|
+
extends IRenderMime.ILatexTypesetter.IMathParseOptions {}
|
|
138
|
+
}
|
|
139
|
+
|
|
79
140
|
/**
|
|
80
141
|
* The MathJax extension.
|
|
81
142
|
*/
|
|
@@ -90,6 +151,18 @@ const mathJaxPlugin: JupyterFrontEndPlugin<ILatexTypesetter> = {
|
|
|
90
151
|
|
|
91
152
|
app.commands.addCommand(CommandIDs.copy, {
|
|
92
153
|
execute: async () => {
|
|
154
|
+
// Find the math expression the context menu was opened on; this works
|
|
155
|
+
// across all typesetter instances (e.g. ones configured with
|
|
156
|
+
// different delimiters), unlike the fallback below which reflects
|
|
157
|
+
// whichever expression the default typesetter processed last.
|
|
158
|
+
const root = app.contextMenuHitTest(node =>
|
|
159
|
+
node.classList.contains('MathJax')
|
|
160
|
+
);
|
|
161
|
+
const tex = root ? Private.getTexSource(root) : undefined;
|
|
162
|
+
if (tex !== undefined) {
|
|
163
|
+
await navigator.clipboard.writeText(tex);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
93
166
|
const md = await typesetter.mathDocument();
|
|
94
167
|
const oJax: any = md.outputJax;
|
|
95
168
|
await navigator.clipboard.writeText(oJax.math.math);
|
|
@@ -105,16 +178,21 @@ const mathJaxPlugin: JupyterFrontEndPlugin<ILatexTypesetter> = {
|
|
|
105
178
|
|
|
106
179
|
app.commands.addCommand(CommandIDs.scale, {
|
|
107
180
|
execute: async (args: CommandArgs.scale) => {
|
|
108
|
-
const md = await typesetter.mathDocument();
|
|
109
181
|
const scale = args['scale'] || 1.0;
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
for (const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
182
|
+
// Scale all documents so that every typesetter instance (e.g. ones
|
|
183
|
+
// configured with different delimiters) is affected alike; remember
|
|
184
|
+
// the scale so that documents created later inherit it too.
|
|
185
|
+
Private.setScale(scale);
|
|
186
|
+
for (const md of await Private.getMathDocuments()) {
|
|
187
|
+
md.outputJax.options.scale = scale;
|
|
188
|
+
md.rerender();
|
|
189
|
+
|
|
190
|
+
// Harden only the re-rendered anchors
|
|
191
|
+
for (const math of md.math) {
|
|
192
|
+
const root = math.typesetRoot as HTMLElement | null;
|
|
193
|
+
if (root) {
|
|
194
|
+
Private.hardenAnchorLinks(root);
|
|
195
|
+
}
|
|
118
196
|
}
|
|
119
197
|
}
|
|
120
198
|
},
|
|
@@ -145,74 +223,201 @@ export default mathJaxPlugin;
|
|
|
145
223
|
* A namespace for module-private functionality.
|
|
146
224
|
*/
|
|
147
225
|
namespace Private {
|
|
148
|
-
|
|
226
|
+
/**
|
|
227
|
+
* The default delimiters for inline math.
|
|
228
|
+
*/
|
|
229
|
+
export const DEFAULT_INLINE_MATH: [string, string][] = [
|
|
230
|
+
['$', '$'],
|
|
231
|
+
['\\(', '\\)']
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Merge parse options, where an `undefined` value in `overrides` means
|
|
236
|
+
* "keep the current value" rather than "reset to the default".
|
|
237
|
+
*/
|
|
238
|
+
export function mergeParseOptions(
|
|
239
|
+
base: IRenderMime.ILatexTypesetter.IMathParseOptions,
|
|
240
|
+
overrides: IRenderMime.ILatexTypesetter.IMathParseOptions
|
|
241
|
+
): IRenderMime.ILatexTypesetter.IMathParseOptions {
|
|
242
|
+
const merged: Record<string, unknown> = { ...base };
|
|
243
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
244
|
+
if (value !== undefined) {
|
|
245
|
+
merged[key] = value;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return merged as IRenderMime.ILatexTypesetter.IMathParseOptions;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* The inline math delimiters with the single `$` pair removed, so that `$`
|
|
253
|
+
* is rendered literally.
|
|
254
|
+
*/
|
|
255
|
+
export const INLINE_MATH_WITHOUT_DOLLAR: [string, string][] = [
|
|
256
|
+
['\\(', '\\)']
|
|
257
|
+
];
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Load the MathJax modules and register the document handler.
|
|
261
|
+
*
|
|
262
|
+
* The heavy dynamic imports and the (global, one-time) handler registration
|
|
263
|
+
* are shared across all typesetters; only the per-instance document is built
|
|
264
|
+
* separately in {@link createMathDocument}. The returned bundle's type is
|
|
265
|
+
* inferred so no MathJax types need to be imported explicitly.
|
|
266
|
+
*/
|
|
267
|
+
async function loadModules() {
|
|
268
|
+
void import('mathjax-full/js/input/tex/require/RequireConfiguration');
|
|
269
|
+
|
|
270
|
+
const [
|
|
271
|
+
{ mathjax },
|
|
272
|
+
{ CHTML },
|
|
273
|
+
{ TeX },
|
|
274
|
+
{ TeXFont },
|
|
275
|
+
{ AllPackages },
|
|
276
|
+
{ SafeHandler },
|
|
277
|
+
{ HTMLHandler },
|
|
278
|
+
{ browserAdaptor },
|
|
279
|
+
{ AssistiveMmlHandler }
|
|
280
|
+
] = await Promise.all([
|
|
281
|
+
import('mathjax-full/js/mathjax'),
|
|
282
|
+
import('mathjax-full/js/output/chtml'),
|
|
283
|
+
import('mathjax-full/js/input/tex'),
|
|
284
|
+
import('mathjax-full/js/output/chtml/fonts/tex'),
|
|
285
|
+
import('mathjax-full/js/input/tex/AllPackages'),
|
|
286
|
+
import('mathjax-full/js/ui/safe/SafeHandler'),
|
|
287
|
+
import('mathjax-full/js/handlers/html/HTMLHandler'),
|
|
288
|
+
import('mathjax-full/js/adaptors/browserAdaptor'),
|
|
289
|
+
import('mathjax-full/js/a11y/assistive-mml')
|
|
290
|
+
]);
|
|
291
|
+
|
|
292
|
+
mathjax.handlers.register(
|
|
293
|
+
AssistiveMmlHandler(SafeHandler(new HTMLHandler(browserAdaptor())))
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
return { mathjax, CHTML, TeX, TeXFont, AllPackages };
|
|
297
|
+
}
|
|
149
298
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
299
|
+
let _loading: ReturnType<typeof loadModules> | null = null;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Ensure the MathJax modules are loaded exactly once per page.
|
|
303
|
+
*/
|
|
304
|
+
export function ensureMathModules(): ReturnType<typeof loadModules> {
|
|
153
305
|
if (!_loading) {
|
|
154
|
-
_loading =
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
306
|
+
_loading = loadModules();
|
|
307
|
+
}
|
|
308
|
+
return _loading;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const _documents = new Map<string, Promise<MathDocument<any, any, any>>>();
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Get (or lazily create) the MathDocument for the given inline delimiters.
|
|
315
|
+
*
|
|
316
|
+
* Building a MathDocument costs on the order of ~5 ms and retains memory for
|
|
317
|
+
* the lifetime of the page. Caching one document per distinct delimiter
|
|
318
|
+
* configuration keeps that bounded, so constructing many typesetters with
|
|
319
|
+
* the same configuration (for example one per markdown cell across a large
|
|
320
|
+
* notebook) reuses a single document instead of multiplying the time and
|
|
321
|
+
* memory cost.
|
|
322
|
+
*/
|
|
323
|
+
export function ensureMathDocument(
|
|
324
|
+
inlineMath: [string, string][]
|
|
325
|
+
): Promise<MathDocument<any, any, any>> {
|
|
326
|
+
const key = JSON.stringify(inlineMath);
|
|
327
|
+
let document = _documents.get(key);
|
|
328
|
+
if (!document) {
|
|
329
|
+
document = createMathDocument(inlineMath);
|
|
330
|
+
_documents.set(key, document);
|
|
331
|
+
}
|
|
332
|
+
return document;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Get all MathDocuments created so far, so that document-wide commands
|
|
337
|
+
* (e.g. scaling) can operate across every delimiter configuration in use.
|
|
338
|
+
*/
|
|
339
|
+
export function getMathDocuments(): Promise<MathDocument<any, any, any>[]> {
|
|
340
|
+
return Promise.all(_documents.values());
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
let _scale: number | null = null;
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Remember the requested scale factor, so that MathDocuments created later
|
|
347
|
+
* (e.g. on first use of a typesetter with a different delimiter
|
|
348
|
+
* configuration) start at the user's chosen scale rather than the default.
|
|
349
|
+
*/
|
|
350
|
+
export function setScale(scale: number): void {
|
|
351
|
+
_scale = scale;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const _texSourceByRoot = new WeakMap<Element, string>();
|
|
183
355
|
|
|
184
|
-
|
|
185
|
-
|
|
356
|
+
/**
|
|
357
|
+
* Record the TeX source of each math expression typeset by `document`,
|
|
358
|
+
* keyed by its rendered container element.
|
|
359
|
+
*
|
|
360
|
+
* A MathDocument only retains the math items from its most recent
|
|
361
|
+
* `render()` call (each `typeset()` starts with `clear()`), so the source
|
|
362
|
+
* of earlier expressions must be captured here for the copy command to
|
|
363
|
+
* find the expression that was actually clicked. Unlike retaining the math
|
|
364
|
+
* items themselves, this only keeps the source string, and the `WeakMap`
|
|
365
|
+
* lets it be reclaimed as soon as the rendered node is garbage-collected.
|
|
366
|
+
*/
|
|
367
|
+
export function recordTexSources(
|
|
368
|
+
document: MathDocument<any, any, any>
|
|
369
|
+
): void {
|
|
370
|
+
for (const item of document.math) {
|
|
371
|
+
if (item.typesetRoot) {
|
|
372
|
+
_texSourceByRoot.set(item.typesetRoot, item.math);
|
|
186
373
|
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
187
376
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const mathDocument = mathjax.document(window.document, {
|
|
208
|
-
InputJax: tex,
|
|
209
|
-
OutputJax: chtml
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
_loading.resolve(mathDocument);
|
|
377
|
+
/**
|
|
378
|
+
* Get the TeX source of the math expression rendered in `root`
|
|
379
|
+
* (a `mjx-container` element), if known.
|
|
380
|
+
*/
|
|
381
|
+
export function getTexSource(root: Element): string | undefined {
|
|
382
|
+
return _texSourceByRoot.get(root);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Build a MathDocument configured with the given inline delimiters.
|
|
387
|
+
*/
|
|
388
|
+
async function createMathDocument(
|
|
389
|
+
inlineMath: [string, string][]
|
|
390
|
+
): Promise<MathDocument<any, any, any>> {
|
|
391
|
+
const { mathjax, CHTML, TeX, TeXFont, AllPackages } =
|
|
392
|
+
await ensureMathModules();
|
|
393
|
+
|
|
394
|
+
class EmptyFont extends TeXFont {
|
|
395
|
+
protected static defaultFonts = {} as any;
|
|
213
396
|
}
|
|
214
397
|
|
|
215
|
-
|
|
398
|
+
const chtml = new CHTML({
|
|
399
|
+
// Override dynamically generated fonts in favor of our font css
|
|
400
|
+
font: new EmptyFont()
|
|
401
|
+
});
|
|
402
|
+
if (_scale !== null) {
|
|
403
|
+
chtml.options.scale = _scale;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const tex = new TeX({
|
|
407
|
+
packages: AllPackages.concat('require'),
|
|
408
|
+
inlineMath,
|
|
409
|
+
displayMath: [
|
|
410
|
+
['$$', '$$'],
|
|
411
|
+
['\\[', '\\]']
|
|
412
|
+
],
|
|
413
|
+
processEscapes: true,
|
|
414
|
+
processEnvironments: true
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
return mathjax.document(window.document, {
|
|
418
|
+
InputJax: tex,
|
|
419
|
+
OutputJax: chtml
|
|
420
|
+
});
|
|
216
421
|
}
|
|
217
422
|
|
|
218
423
|
/**
|