mathlive
Version:
Render and edit beautifully typeset math
844 lines (762 loc) • 30.5 kB
JavaScript
/**
*
* This modules exports the MathLive entry points.
*
* @module mathlive
* @example
* // To invoke the functions in this module, import the MathLive module.
*
* import MathLive from 'dist/mathlive.mjs';
*
* const markup = MathLive.latexToMarkup('e^{i\\pi}+1=0');
*
*/
import Lexer from './core/lexer.js';
import MathAtom from './core/mathAtom.js';
import ParserModule from './core/parser.js';
import Span from './core/span.js';
import Definitions from './core/definitions.js';
import MathField from './editor/editor-mathfield.js';
import AutoRender from './addons/auto-render.js';
import Maston from './addons/maston.js';
/**
* Convert a LaTeX string to a string of HTML markup.
*
* @param {string} text A string of valid LaTeX. It does not have to start
* with a mode token such as `$$` or `\(`.
*
* @param {string} mathstyle If `'displaystyle'` the "display" mode of TeX
* is used to typeset the formula, which is most appropriate for formulas that are
* displayed in a standalone block. If `'textstyle'` is used, the "text" mode
* of TeX is used, which is most appropriate when displaying math "inline"
* with other text (on the same line).
*
* @param {string} [format='html'] For debugging purposes, this function
* can also return a text representation of internal data structures
* used to construct the markup. Valid values include `'mathlist'` and `'span'`
*
* @return {string}
* @function module:mathlive#latexToMarkup
*/
function toMarkup(text, mathstyle, format, macros) {
mathstyle = mathstyle || 'displaystyle';
console.assert(/displaystyle|textstyle|scriptstyle|scriptscriptstyle/.test(mathstyle),
"Invalid style:", mathstyle);
//
// 1. Tokenize the text
//
const tokens = Lexer.tokenize(text);
//
// 2. Parse each token in the formula
// Turn the list of tokens in the formula into
// a tree of high-level MathAtom, e.g. 'genfrac'.
//
const mathlist = ParserModule.parseTokens(tokens, 'math', null, macros);
if (format === 'mathlist') return mathlist;
//
// 3. Transform the math atoms into elementary spans
// for example from genfrac to vlist.
//
let spans = MathAtom.decompose({mathstyle: mathstyle}, mathlist);
//
// 4. Simplify by coalescing adjacent nodes
// for example, from <span>1</span><span>2</span>
// to <span>12</span>
//
spans = Span.coalesce(spans);
if (format === 'span') return spans;
//
// 5. Wrap the expression with struts
//
const base = Span.makeSpan(spans, 'ML__base');
const topStrut = Span.makeSpan('', 'ML__strut')
topStrut.setStyle('height', base.height, 'em');
const struts = [topStrut];
if (base.depth !== 0) {
const bottomStrut = Span.makeSpan('', 'ML__strut--bottom');
bottomStrut.setStyle('height', base.height + base.depth, 'em');
bottomStrut.setStyle('vertical-align', -base.depth, 'em');
struts.push(bottomStrut);
}
struts.push(base);
const wrapper = Span.makeSpan(struts, 'ML__mathlive');
//
// 6. Generate markup
//
return wrapper.toMarkup();
}
/**
* Convert a DOM element into an editable math field.
*
* After the DOM element has been created, the value `element.mathfield` will
* return a reference to the mathfield object. This value is also returned
* by `makeMathField`
*
* @param {HTMLElement|string} element A DOM element, for example as obtained
* by `document.getElementById()`, or the ID of a DOM element as a string.
*
* @param {MathFieldConfig} [config={}] See {@tutorial CONFIG} for details.
*
*
* @return {MathField}
*
* Given the HTML markup:
* ```html
* <span id='equation'>$f(x)=sin(x)$</span>
* ```
* The following code will turn the span into an editable mathfield.
* ```
* import MathLive from 'dist/mathlive.mjs';
* MathLive.makeMathField('equation');
* ```
*
* @function module:mathlive#makeMathField
*/
function makeMathField(element, config) {
if (!MathField) {
throw Error('The MathField module is not loaded.');
}
config = config || {};
config.handleSpeak = config.handleSpeak || speak;
config.handleReadAloud = config.handleReadAloud || readAloud;
return new MathField.MathField(getElement(element), config);
}
/**
* Convert a LaTeX string to a string of MathML markup.
*
* @param {string} latex A string of valid LaTeX. It does not have to start
* with a mode token such as a `$$` or `\(`.
* @param {object} options
* @param {boolean} [options.generateID=false] - If true, add an `extid` attribute
* to the MathML nodes with a value matching the `atomID`.
* @return {string}
* @function module:mathlive#latexToMathML
*/
function toMathML(latex, options) {
if (!MathAtom.toMathML) {
console.warn('The MathML module is not loaded.');
return '';
}
options = options || {};
options.macros = options.macros || {};
Object.assign(options.macros, Definitions.MACROS);
const mathlist = ParserModule.parseTokens(Lexer.tokenize(latex),
'math', null, options.macros);
return MathAtom.toMathML(mathlist, options);
}
/**
* Convert a LaTeX string to an Abstract Syntax Tree
*
* **See:** {@tutorial MASTON}
*
* @param {string} latex A string of valid LaTeX. It does not have to start
* with a mode token such as a `$$` or `\(`.
* @param {Object.<string, any>} options
* @param {object} [options.macros] A dictionary of LaTeX macros
*
* @return {object} The Abstract Syntax Tree as a JavaScript object.
* @function module:mathlive#latexToAST
*/
function latexToAST(latex, options) {
if (!MathAtom.toAST) {
console.warn('The AST module is not loaded.');
return {};
}
options = options || {};
options.macros = options.macros || {};
Object.assign(options.macros, Definitions.MACROS);
const mathlist = ParserModule.parseTokens(Lexer.tokenize(latex),
'math', null, options.macros);
return MathAtom.toAST(mathlist, options);
}
/**
* Convert an Abstract Syntax Tree to a LaTeX string.
*
* **See:** {@tutorial MASTON}
*
* @param {object} ast - The Abstract Syntax Tree as a JavaScript object.
* @param {Object.<string, any>} options
* @param {number} [options.precision=14] Number of digits used in the representation of numbers
* @param {string} [options.decimalMarker='.'] Character used as the decimal marker
* @param {string} [options.groupSeparator='\\, '] Character used to separate group of numbers, typicall thousands
* @param {string} [options.product='\\cdot '] Character used to indicate product. Other option would be '\\times '
* @param {string} [options.exponentProduct='\\cdot '] Character used before an exponent indicator
* @param {string} [options.exponentMarker=''] Character used to indicate an exponent
* @param {string} [options.scientificNotation='auto'] Other possible values 'engineering' or 'on'
* @param {string} [options.beginRepeatingDigits='\\overline{']
* @param {string} [options.endRepeatingDigits='}']
*
* @return {string} The LaTeX representation of the Abstract Syntax Tree, if valid.
* @function module:mathlive#astToLatex
*/
function astToLatex(ast, options) {
return Maston.asLatex(ast, options);
}
/**
* Convert a LaTeX string to a textual representation ready to be spoken
*
* @param {string} latex A string of valid LaTeX. It does not have to start
* with a mode token such as a `$$` or `\(`.
*
* @param {Object.<string, any>} options -
*
* @param {string} [options.textToSpeechRules='mathlive'] Specify which
* set of text to speech rules to use.
*
* A value of `mathlive` indicates that
* the simple rules built into MathLive should be used. A value of `sre`
* indicates that the Speech Rule Engine from Volker Sorge should be used.
* Note that SRE is not included or loaded by MathLive and for this option to
* work SRE should be loaded separately.
*
* @param {string} [options.textToSpeechMarkup=''] The markup syntax to use
* for the output of conversion to spoken text.
*
* Possible values are `ssml` for
* the SSML markup or `mac` for the MacOS markup (e.g. `[[ltr]]`)
*
* @param {Object.<string, any>} [options.textToSpeechRulesOptions={}] A set of
* key/value pairs that can be used to configure the speech rule engine.
*
* Which options are available depends on the speech rule engine in use. There
* are no options available with MathLive's built-in engine. The options for
* the SRE engine are documented [here]{@link:https://github.com/zorkow/speech-rule-engine}
* @return {string} The spoken representation of the input LaTeX.
* @example
* console.log(MathLive.latexToSpeakableText('\\frac{1}{2}'));
* // ➡︎'half'
* @function module:mathlive#latexToSpeakableText
*/
function latexToSpeakableText(latex, options) {
if (!MathAtom.toSpeakableText) {
console.warn('The outputSpokenText module is not loaded.');
return "";
}
options = options || {};
options.macros = options.macros || {};
Object.assign(options.macros, Definitions.MACROS);
const mathlist = ParserModule.parseTokens(Lexer.tokenize(latex),
'math', null, options.macros);
return MathAtom.toSpeakableText(mathlist, options);
}
function removeHighlight(node) {
node.classList.remove('highlight');
if (node.children) {
Array.from(node.children).forEach(x => {
removeHighlight(x);
});
}
}
/**
* Highlight the span corresponding to the specified atomID
* This is used for TTS with synchronized highlighting (read aloud)
*
* @param {string} atomID
*
*/
function highlightAtomID(node, atomID) {
if (!atomID || node.dataset.atomId === atomID) {
node.classList.add('highlight');
if (node.children && node.children.length > 0) {
Array.from(node.children).forEach(x => {
highlightAtomID(x);
});
}
} else {
node.classList.remove('highlight');
if (node.children && node.children.length > 0) {
Array.from(node.children).forEach(x => {
highlightAtomID(x, atomID);
});
}
}
}
function speak(text, config) {
if (!config && window && window.mathlive) {
config = window.mathlive.config;
}
config = config || {};
if (!config.speechEngine || config.speechEngine === 'local') {
// On ChromeOS: chrome.accessibilityFeatures.spokenFeedback
// See also https://developer.chrome.com/apps/tts
const utterance = new SpeechSynthesisUtterance(text);
if (window) {
window.speechSynthesis.speak(utterance);
} else {
console.log('Speak: ', text);
}
} else if (config.speechEngine === 'amazon') {
if (!window || !window.AWS) {
console.warn('AWS SDK not loaded. See https://www.npmjs.com/package/aws-sdk');
} else {
const polly = new window.AWS.Polly({apiVersion: '2016-06-10'});
const params = {
OutputFormat: 'mp3',
VoiceId: config.speechEngineVoice || 'Joanna',
// SampleRate: '16000',
Text: text,
TextType: 'ssml',
// SpeechMarkTypes: ['ssml]'
};
polly.synthesizeSpeech(params, function(err, data) {
if (err) {
console.warn('polly.synthesizeSpeech() error:', err, err.stack);
} else {
if (data && data.AudioStream) {
const uInt8Array = new Uint8Array(data.AudioStream);
const blob = new Blob([uInt8Array.buffer], {type: 'audio/mpeg'});
const url = URL.createObjectURL(blob);
const audioElement = new Audio(url);
audioElement.play().catch(err => console.log(err));
} else {
console.log('polly.synthesizeSpeech():' + data);
}
}
});
// Can call AWS.Request() on the result of synthesizeSpeech()
}
} else if (config.speechEngine === 'google') {
console.warn('The Google speech engine is not supported yet. Please come again.');
// @todo: implement support for Google Text-to-Speech API,
// using config.speechEngineToken, config.speechEngineVoice and
// config.speechEngineAudioConfig
// curl -H "Authorization: Bearer "$(gcloud auth application-default print-access-token) \
// -H "Content-Type: application/json; charset=utf-8" \
// --data "{
// 'input':{
// 'text':'Android is a mobile operating system developed by Google,
// based on the Linux kernel and designed primarily for
// touchscreen mobile devices such as smartphones and tablets.'
// },
// 'voice':{
// 'languageCode':'en-gb',
// 'name':'en-GB-Standard-A',
// 'ssmlGender':'FEMALE'
// },
// 'audioConfig':{
// 'audioEncoding':'MP3'
// }
// }" "https://texttospeech.googleapis.com/v1beta1/text:synthesize" > synthesize-text.txt
}
}
/**
* "Read Aloud" is an asynchronous operation that reads the
* reading with synchronized highlighting
*
* @param {DOMElement} element - The DOM element to highlight
* @param {string} text - The text to speak
* @param {object} config
* @private
* @function module:mathlive#readAloud
*/
function readAloud(element, text, config) {
if (!window) {
return;
}
if (!config && window.mathlive) {
config = window.mathlive.config;
}
config = config || {};
if (config.speechEngine !== 'amazon') {
console.warn('Use Amazon TTS Engine for synchronized highlighting');
if (config.handleSpeak) config.handleSpeak(text);
return;
}
if (!window.AWS) {
console.warn('AWS SDK not loaded. See https://www.npmjs.com/package/aws-sdk');
return;
}
const polly = new window.AWS.Polly({apiVersion: '2016-06-10'});
const params = {
OutputFormat: 'json',
VoiceId: config.speechEngineVoice || 'Joanna',
Text: text,
TextType: 'ssml',
SpeechMarkTypes: ['ssml']
};
window.mathlive = window.mathlive || {};
window.mathlive.readAloudElement = element;
const status = config.onReadAloudStatus || window.mathlive.onReadAloudStatus;
// Request the mark points
polly.synthesizeSpeech(params, function(err, data) {
if (err) {
console.warn('polly.synthesizeSpeech() error:', err, err.stack);
} else {
if (data && data.AudioStream) {
const response = new TextDecoder('utf-8').decode(new Uint8Array(data.AudioStream));
window.mathlive.readAloudMarks = response.split('\n').map(x => x ? JSON.parse(x) : {});
window.mathlive.readAloudTokens = [];
for (const mark of window.mathlive.readAloudMarks) {
if (mark.value) {
window.mathlive.readAloudTokens.push(mark.value);
}
}
window.mathlive.readAloudCurrentMark = '';
// Request the audio
params.OutputFormat = 'mp3';
params.SpeechMarkTypes = [];
polly.synthesizeSpeech(params, function(err, data) {
if (err) {
console.warn('polly.synthesizeSpeech(', text , ') error:', err, err.stack);
} else {
if (data && data.AudioStream) {
const uInt8Array = new Uint8Array(data.AudioStream);
const blob = new Blob([uInt8Array.buffer], {type: 'audio/mpeg'});
const url = URL.createObjectURL(blob);
if (!window.mathlive.readAloudAudio) {
window.mathlive.readAloudAudio = new Audio();
window.mathlive.readAloudAudio.addEventListener('ended', () => {
if (status) status(window.mathlive.readAloudMathField, 'ended');
if (window.mathlive.readAloudMathField) {
window.mathlive.readAloudMathField._render();
window.mathlive.readAloudElement = null;
window.mathlive.readAloudMathField = null;
window.mathlive.readAloudTokens = [];
window.mathlive.readAloudMarks = [];
window.mathlive.readAloudCurrentMark = '';
} else {
removeHighlight(window.mathlive.readAloudElement);
}
});
window.mathlive.readAloudAudio.addEventListener('timeupdate', () => {
let value = '';
// The target, the atom we're looking for, is the one matching the current audio
// plus 100 ms. By anticipating it a little bit, it feels more natural, otherwise it
// feels like the highlighting is trailing the audio.
const target = window.mathlive.readAloudAudio.currentTime * 1000 + 100;
// Find the smallest element which is bigger than the target time
for (const mark of window.mathlive.readAloudMarks) {
if (mark.time < target) {
value = mark.value;
}
}
if (window.mathlive.readAloudCurrentMark !== value) {
window.mathlive.readAloudCurrentToken = value;
if (value && value === window.mathlive.readAloudFinalToken) {
window.mathlive.readAloudAudio.pause();
} else {
window.mathlive.readAloudCurrentMark = value;
highlightAtomID(window.mathlive.readAloudElement, window.mathlive.readAloudCurrentMark);
}
}
});
} else {
window.mathlive.readAloudAudio.pause();
}
window.mathlive.readAloudAudio.src = url;
if (status) {
status(window.mathlive.readAloudMathField, 'playing');
}
window.mathlive.readAloudAudio.play();
} else {
// console.log('polly.synthesizeSpeech():' + data);
}
}
});
} else {
console.log('polly.synthesizeSpeech():' + data);
}
}
});
}
/**
* Return the status of a Read Aloud operation (reading with synchronized
* highlighting).
*
* Possible values include:
* - `ready`
* - `playing`
* - `paused`
* - `unavailable`
*
* **See** {@linkcode module:editor-mathfield#speak speak}
* @return {string}
* @function module:mathlive#readAloudStatus
*/
function readAloudStatus() {
if (!window) return 'unavailable';
window.mathlive = window.mathlive || {};
if (!window.mathlive.readAloudAudio) return 'ready';
if (window.mathlive.readAloudAudio.paused) return 'paused';
if (!window.mathlive.readAloudAudio.ended) return 'playing';
return 'ready';
}
/**
* If a Read Aloud operation is in progress, stop it.
*
* **See** {@linkcode module:editor/mathfield#speak speak}
* @function module:mathlive#pauseReadAloud
*/
function pauseReadAloud() {
if (!window) return;
window.mathlive = window.mathlive || {};
if (window.mathlive.readAloudAudio) {
if (window.mathlive.onReadAloudStatus) {
window.mathlive.onReadAloudStatus(window.mathlive.readAloudMathField, 'paused');
}
window.mathlive.readAloudAudio.pause();
}
}
/**
* If a Read Aloud operation is paused, resume it
*
* **See** {@linkcode module:editor-mathfield#speak speak}
* @function module:mathlive#resumeReadAloud
*/
function resumeReadAloud() {
if (!window) return;
window.mathlive = window.mathlive || {};
if (window.mathlive.readAloudAudio) {
if (window.mathlive.onReadAloudStatus) {
window.mathlive.onReadAloudStatus(window.mathlive.readAloudMathField, 'playing');
}
window.mathlive.readAloudAudio.play();
}
}
/**
* If a Read Aloud operation is in progress, read from a specified token
*
* **See** {@linkcode module:editor-mathfield#speak speak}
*
* @param {string} token
* @param {number} [count]
* @function module:mathlive#playReadAloud
*/
function playReadAloud(token, count) {
if (!window) return;
window.mathlive = window.mathlive || {};
if (window.mathlive.readAloudAudio) {
let timeIndex = 0;
window.mathlive.readAloudFinalToken = null;
if (token) {
window.mathlive.readAloudMarks = window.mathlive.readAloudMarks || [];
for (const mark of window.mathlive.readAloudMarks) {
if (mark.value === token) {
timeIndex = mark.time / 1000;
}
}
let tokenIndex = window.mathlive.readAloudTokens.indexOf(token);
if (tokenIndex >= 0) {
tokenIndex += count;
if (tokenIndex < window.mathlive.readAloudTokens.length) {
window.mathlive.readAloudFinalToken = tokenIndex;
}
}
}
window.mathlive.readAloudAudio.currentTime = timeIndex;
if (window.mathlive.onReadAloudStatus) {
window.mathlive.onReadAloudStatus(window.mathlive.readAloudMathField, 'playing');
}
window.mathlive.readAloudAudio.play();
}
}
/**
* Transform all the elements in the document body that contain LaTeX code
* into typeset math.
*
* **Note:** This is a very expensive call, as it needs to parse the entire
* DOM tree to determine which elements need to be processed. In most cases
* this should only be called once per document, once the DOM has been loaded.
* To render a specific element, use {@linkcode module:mathlive#renderMathInElement renderMathInElement()}
*
* **See:** {@tutorial USAGE_GUIDE}
*
* @param {object<string, any>} [options={}] See {@linkcode module:mathlive#renderMathInElement renderMathInElement()}
* for details
* @example
* import MathLive from 'dist/mathlive.mjs';
* document.addEventListener("load", () => {
* MathLive.renderMathInDocument();
* });
*
*/
function renderMathInDocument(options) {
renderMathInElement(document.body, options);
}
function getElement(element) {
let result = element;
if (typeof element === 'string') {
result = document.getElementById(element);
if (!result) {
throw Error(`The element with ID "${element}" could not be found.`);
}
}
return result;
}
/**
* Transform all the children of `element`, recursively, that contain LaTeX code
* into typeset math.
*
* **See:** {@tutorial USAGE_GUIDE}
*
* @param {HTMLElement|string} element An HTML DOM element, or a string containing
* the ID of an element.
* @param {object} [options={}]
*
* @param {string} [options.namespace=''] - Namespace that is added to `data-`
* attributes to avoid collisions with other libraries.
*
* It is empty by default.
*
* The namespace should be a string of lowercase letters.
*
* @param {object[]} [options.macros={}] - Custom LaTeX macros
*
* @param {string[]} [options.skipTags=['noscript', 'style', 'textarea', 'pre', 'code', 'annotation', 'annotation-xml'] ]
* an array of tag names whose content will
* not be scanned for delimiters (unless their class matches the `processClass`
* pattern below.
*
* @param {string} [options.ignoreClass='tex2jax_ignore'] a string used as a
* regular expression of class names of elements whose content will not be
* scanned for delimiters
* @param {string} [options.processClass='tex2jax_process'] a string used as a
* regular expression of class names of elements whose content **will** be
* scanned for delimiters, even if their tag name or parent class name would
* have prevented them from doing so.
*
* @param {string} [options.processScriptType="math/tex"] `<script>` tags of the
* indicated type will be processed while others will be ignored.
*
* @param {string} [options.renderAccessibleContent='mathml'] The format(s) in
* which to render the math for screen readers:
* - `'mathml'` MathML
* - `'speakable-text'` Spoken representation
*
* You can pass an empty string to turn off the rendering of accessible content.
*
* You can pass multiple values separated by spaces, e.g `'mathml speakable-text'`
*
* @param {boolean} [options.preserveOriginalContent=true] if true, store the
* original textual content of the element in a `data-original-content`
* attribute. This value can be accessed for example to restore the element to
* its original value:
* ```javascript
* elem.innerHTML = elem.dataset.originalContent;
* ```
* @param {boolean} [options.readAloud=false] if true, generate markup that can
* be read aloud later using {@linkcode module:editor-mathfield#speak speak}
*
* @param {boolean} [options.TeX.processEnvironments=true] if false, math expression
* that start with `\begin{` will not automatically be rendered.
*
* @param {string[][]} [options.TeX.delimiters.inline=[['\\(','\\)']] ] arrays
* of delimiter pairs that will trigger a render of the content in 'textstyle'
*
* @param {string[][]} [options.TeX.delimiters.display=[['$$', '$$'], ['\\[', '\\]']] ] arrays
* of delimiter pairs that will trigger a render of the content in
* 'displaystyle'.
*
* @param {function} [renderToMarkup] a function that will convert any LaTeX found to
* HTML markup. This is only useful to override the default MathLive renderer
*
* @param {function} [renderToMathML] a function that will convert any LaTeX found to
* MathML markup.
*
* @param {function} [renderToSpeakableText] a function that will convert any LaTeX found to
* speakable text markup.
*
* @function module:mathlive#renderMathInElement
*/
function renderMathInElement(element, options) {
if (!AutoRender) {
console.warn('The AutoRender module is not loaded.');
return;
}
options = options || {};
options.renderToMarkup = options.renderToMarkup || toMarkup;
options.renderToMathML = options.renderToMathML || toMathML;
options.renderToSpeakableText = options.renderToSpeakableText || latexToSpeakableText;
options.macros = options.macros || Definitions.MACROS;
AutoRender.renderMathInElement(getElement(element), options);
}
function validateNamespace(options) {
if (options.namespace) {
if (!/^[a-z]+[-]?$/.test(options.namespace)) {
throw Error('options.namespace must be a string of lowercase characters only');
}
if (!/-$/.test(options.namespace)) {
options.namespace += '-';
}
}
}
/**
*
* @param {string|HTMLElement|MathField} element
* @param {Object.<string, any>} [options={}]
* @param {string} options.namespace The namespace used for the `data-`
* attributes. If you used a namespace with `renderMathInElement`, you must
* use the same namespace here.
* @function module:mathlive#revertToOriginalContent
*/
function revertToOriginalContent(element, options) {
element = getElement(element);
// element is a pair: accessible span, math -- set it to the math part
element = element.children[1];
if (element instanceof MathField.MathField) {
element.revertToOriginalContent();
} else {
options = options || {};
validateNamespace(options);
element.innerHTML = element.getAttribute('data-' +
(options.namespace || '') + 'original-content');
}
}
/**
* After calling {@linkcode module:mathlive#renderMathInElement renderMathInElement}
* or {@linkcode module:mathlive#makeMathField makeMathField} the original content
* can be retrived by calling this function.
*
* Given the following markup:
* ```html
* <span id='equation'>$$f(x)=sin(x)$$</span>
* ```
* The following code:
* ```javascript
* MathLive.renderMathInElement('equation');
* console.log(MathLive.getOriginalContent('equation'));
* ```
* will output:
* ```
* $$f(x)=sin(x)$$
* ```
* @param {string | HTMLElement | MathField} element - A DOM element ID, a DOM
* element or a MathField.
* @param {object} [options={}]
* @param {string} [options.namespace=""] The namespace used for the `data-`
* attributes.
* If you used a namespace with `renderMathInElement`, you must
* use the same namespace here.
* @return {string} the original content of the element.
* @function module:mathlive#getOriginalContent
*/
function getOriginalContent(element, options) {
element = getElement(element);
// element is a pair: accessible span, math -- set it to the math part
element = element.children[1];
if (element instanceof MathField.MathField) {
return element.originalContent;
}
options = options || {};
validateNamespace(options);
return element.getAttribute('data-' +
(options.namespace || '') + 'original-content');
}
const MathLive = {
latexToMarkup: toMarkup,
latexToMathML: toMathML,
latexToSpeakableText,
latexToAST,
astToLatex,
makeMathField,
renderMathInDocument,
renderMathInElement,
revertToOriginalContent,
getOriginalContent,
readAloud,
readAloudStatus,
pauseReadAloud,
resumeReadAloud,
playReadAloud
};
export default MathLive;