123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579 |
- /*************************************************************
- *
- * Copyright (c) 2018-2022 The MathJax Consortium
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- /**
- * @fileoverview Implements a startup module that allows dynamically
- * loaded components to register themselves, and then
- * creates MathJax methods for typesetting and converting
- * math based on the registered components.
- *
- * @author dpvc@mathjax.org (Davide Cervone)
- */
- import {MathJax as MJGlobal, MathJaxObject as MJObject,
- MathJaxConfig as MJConfig, combineWithMathJax, combineDefaults} from './global.js';
- import {MathDocument} from '../core/MathDocument.js';
- import {MmlNode} from '../core/MmlTree/MmlNode.js';
- import {Handler} from '../core/Handler.js';
- import {InputJax} from '../core/InputJax.js';
- import {OutputJax} from '../core/OutputJax.js';
- import {CommonOutputJax} from '../output/common/OutputJax.js';
- import {DOMAdaptor} from '../core/DOMAdaptor.js';
- import {PrioritizedList} from '../util/PrioritizedList.js';
- import {OptionList, OPTIONS} from '../util/Options.js';
- import {TeX} from '../input/tex.js';
- /**
- * Update the configuration structure to include the startup configuration
- */
- export interface MathJaxConfig extends MJConfig {
- startup?: {
- input?: string[]; // The names of the input jax to use
- output?: string; // The name for the output jax to use
- handler?: string; // The handler to register
- adaptor?: string; // The name for the DOM adaptor to use
- document?: any; // The document (or fragment or string) to work in
- elements?: any[]; // The elements to typeset (default is document body)
- typeset?: boolean; // Perform initial typeset?
- ready?: () => void; // Function to perform when components are ready
- pageReady?: () => void; // Function to perform when page is ready
- invalidOption?: 'fatal' | 'warn'; // Do invalid options produce a warning, or throw an error?
- optionError?: (message: string, key: string) => void, // Function to report invalid options
- [name: string]: any; // Other configuration blocks
- };
- }
- /**
- * Generic types for the standard MathJax objects
- */
- export type MATHDOCUMENT = MathDocument<any, any, any>;
- export type HANDLER = Handler<any, any, any>;
- export type DOMADAPTOR = DOMAdaptor<any, any, any>;
- export type INPUTJAX = InputJax<any, any, any>;
- export type OUTPUTJAX = OutputJax<any, any, any>;
- export type COMMONJAX = CommonOutputJax<any, any, any, any, any, any, any>;
- export type TEX = TeX<any, any, any>;
- /**
- * A function to extend a handler class
- */
- export type HandlerExtension = (handler: HANDLER) => HANDLER;
- /**
- * Update the MathJax object to inclide the startup information
- */
- export interface MathJaxObject extends MJObject {
- config: MathJaxConfig;
- startup: {
- constructors: {[name: string]: any};
- input: INPUTJAX[];
- output: OUTPUTJAX;
- handler: HANDLER;
- adaptor: DOMADAPTOR;
- elements: any[];
- document: MATHDOCUMENT;
- promise: Promise<void>;
- /* tslint:disable:jsdoc-require */
- registerConstructor(name: string, constructor: any): void;
- useHandler(name: string, force?: boolean): void;
- useAdaptor(name: string, force?: boolean): void;
- useOutput(name: string, force?: boolean): void;
- useInput(name: string, force?: boolean): void;
- extendHandler(extend: HandlerExtension): void;
- toMML(node: MmlNode): string;
- defaultReady(): void;
- defaultPageReady(): Promise<void>;
- getComponents(): void;
- makeMethods(): void;
- makeTypesetMethods(): void;
- makeOutputMethods(iname: string, oname: string, input: INPUTJAX): void;
- makeMmlMethods(name: string, input: INPUTJAX): void;
- makeResetMethod(name: string, input: INPUTJAX): void;
- getInputJax(): INPUTJAX[];
- getOutputJax(): OUTPUTJAX;
- getAdaptor(): DOMADAPTOR;
- getHandler(): HANDLER;
- /* tslint:enable */
- };
- [name: string]: any; // Needed for the methods created by the startup module
- }
- /*
- * Access to the browser document
- */
- declare var global: {document: Document};
- /**
- * The implementation of the startup module
- */
- export namespace Startup {
- /**
- * The array of handler extensions
- */
- const extensions = new PrioritizedList<HandlerExtension>();
- let visitor: any; // the visitor for toMML();
- let mathjax: any; // the mathjax variable from mathjax.js
- /**
- * The constructors (or other data) registered by the loaded packages
- */
- export const constructors: {[name: string]: any} = {};
- /**
- * The array of InputJax instances (created after everything is loaded)
- */
- export let input: INPUTJAX[] = [];
- /**
- * The OutputJax instance (created after everything is loaded)
- */
- export let output: OUTPUTJAX = null;
- /**
- * The Handler instance (created after everything is loaded)
- */
- export let handler: HANDLER = null;
- /**
- * The DOMAdaptor instance (created after everything is loaded)
- */
- export let adaptor: DOMADAPTOR = null;
- /**
- * The elements to process (set when typeset or conversion method is called)
- */
- export let elements: any[] = null;
- /**
- * The MathDocument instance being used (based on the browser DOM or configuration value)
- */
- export let document: MATHDOCUMENT = null;
- /**
- * The function that resolves the first promise defined below
- * (called in the defaultReady() function when MathJax is finished with
- * its initial typesetting)
- */
- export let promiseResolve: () => void;
- /**
- * The function that rejects the first promise defined below
- * (called in the defaultReady() function when MathJax's initial
- * typesetting fails)
- */
- export let promiseReject: (reason: any) => void;
- /**
- * The promise for the startup process (the initial typesetting).
- * It is resolves or rejected in the ready() function.
- */
- export let promise = new Promise<void>((resolve, reject) => {
- promiseResolve = resolve;
- promiseReject = reject;
- });
- /**
- * A promise that is resolved when the page contents are available
- * for processing.
- */
- export let pagePromise = new Promise<void>((resolve, _reject) => {
- const doc = global.document;
- if (!doc || !doc.readyState || doc.readyState === 'complete' || doc.readyState === 'interactive') {
- resolve();
- } else {
- const listener = () => resolve();
- doc.defaultView.addEventListener('load', listener, true);
- doc.defaultView.addEventListener('DOMContentLoaded', listener, true);
- }
- });
- /**
- * @param {MmlNode} node The root of the tree to convert to serialized MathML
- * @return {string} The serialized MathML from the tree
- */
- export function toMML(node: MmlNode): string {
- return visitor.visitTree(node, document);
- }
- /**
- * @param {string} name The identifier for the constructor
- * @param {any} constructor The constructor function for the named object
- */
- export function registerConstructor(name: string, constructor: any) {
- constructors[name] = constructor;
- }
- /**
- * @param {string} name The identifier for the Handler to use
- * @param {boolean} force True to force the Handler to be used even if one is already registered
- */
- export function useHandler(name: string, force: boolean = false) {
- if (!CONFIG.handler || force) {
- CONFIG.handler = name;
- }
- }
- /**
- * @param {string} name The identifier for the DOMAdaptor to use
- * @param {boolean} force True to force the DOMAdaptor to be used even if one is already registered
- */
- export function useAdaptor(name: string, force: boolean = false) {
- if (!CONFIG.adaptor || force) {
- CONFIG.adaptor = name;
- }
- }
- /**
- * @param {string} name The identifier for the InputJax to use
- * @param {boolean} force True to force the InputJax to be used even if the configuration already
- * included an array of input jax
- */
- export function useInput(name: string, force: boolean = false) {
- if (!inputSpecified || force) {
- CONFIG.input.push(name);
- }
- }
- /**
- * @param {string} name The identifier for the OutputJax to use
- * @param {boolean} force True to force the OutputJax to be used even if one is already registered
- */
- export function useOutput(name: string, force: boolean = false) {
- if (!CONFIG.output || force) {
- CONFIG.output = name;
- }
- }
- /**
- * @param {HandlerExtension} extend A function to extend the handler class
- * @param {number} priority The priority of the extension
- */
- export function extendHandler(extend: HandlerExtension, priority: number = 10) {
- extensions.add(extend, priority);
- }
- /**
- * The default ready() function called when all the packages have been loaded,
- * which creates the various objects needed by MathJax, creates the methods
- * based on the loaded components, and does the initial typesetting.
- *
- * Setting MathJax.startup.ready in the configuration will
- * override this, but you can call MathJax.startup.defaultReady()
- * within your own ready function if needed, or can use the
- * individual methods below to perform portions of the default
- * startup actions.
- */
- export function defaultReady() {
- getComponents();
- makeMethods();
- pagePromise
- .then(() => CONFIG.pageReady()) // usually the initial typesetting call
- .then(() => promiseResolve())
- .catch((err) => promiseReject(err));
- }
- /**
- * The default pageReady() function called when the page is ready to be processed,
- * which returns the function that performs the initial typesetting, if needed.
- *
- * Setting Mathjax.startup.pageReady in the configuration will override this.
- */
- export function defaultPageReady() {
- return (CONFIG.typeset && MathJax.typesetPromise ?
- MathJax.typesetPromise(CONFIG.elements) as Promise<void> :
- Promise.resolve());
- }
- /**
- * Create the instances of the registered components
- */
- export function getComponents() {
- visitor = new MathJax._.core.MmlTree.SerializedMmlVisitor.SerializedMmlVisitor();
- mathjax = MathJax._.mathjax.mathjax;
- input = getInputJax();
- output = getOutputJax();
- adaptor = getAdaptor();
- if (handler) {
- mathjax.handlers.unregister(handler);
- }
- handler = getHandler();
- if (handler) {
- mathjax.handlers.register(handler);
- document = getDocument();
- }
- }
- /**
- * Make the typeset and conversion methods based on the registered components
- *
- * If there are both input and output jax,
- * Make Typeset() and TypesetPromise() methods using the given jax,
- * and TypesetClear() to clear the existing math items
- * For each input jax
- * Make input2mml() and input2mmlPromise() conversion methods and inputReset() method
- * If there is a registered output jax
- * Make input2output() and input2outputPromise conversion methods and outputStylesheet() method
- */
- export function makeMethods() {
- if (input && output) {
- makeTypesetMethods();
- }
- const oname = (output ? output.name.toLowerCase() : '');
- for (const jax of input) {
- const iname = jax.name.toLowerCase();
- makeMmlMethods(iname, jax);
- makeResetMethod(iname, jax);
- if (output) {
- makeOutputMethods(iname, oname, jax);
- }
- }
- }
- /**
- * Create the Typeset(elements?), TypesetPromise(elements?), and TypesetClear() methods.
- *
- * The first two call the document's render() function, the latter
- * wrapped in handleRetriesFor() and returning the resulting promise.
- *
- * TypeseClear() clears all the MathItems from the document.
- */
- export function makeTypesetMethods() {
- MathJax.typeset = (elements: any[] = null) => {
- document.options.elements = elements;
- document.reset();
- document.render();
- };
- MathJax.typesetPromise = (elements: any[] = null) => {
- document.options.elements = elements;
- document.reset();
- return mathjax.handleRetriesFor(() => {
- document.render();
- });
- };
- MathJax.typesetClear = (elements: any[] = null) => {
- if (elements) {
- document.clearMathItemsWithin(elements);
- } else {
- document.clear();
- }
- };
- }
- /**
- * Make the input2output(math, options?) and input2outputPromise(math, options?) methods,
- * and outputStylesheet() method, where "input" and "output" are replaced by the
- * jax names (e.g., tex2chtml() and chtmlStyleSheet()).
- *
- * The first two perform the document's convert() call, with the Promise version wrapped in
- * handlerRetriesFor() and returning the resulting promise. The return value is the
- * DOM object for the converted math. Use MathJax.startup.adaptor.outerHTML(result)
- * to get the serialized string version of the output.
- *
- * The outputStylesheet() method returns the styleSheet object for the output.
- * Use MathJax.startup.adaptor.innerHTML(MathJax.outputStylesheet()) to get the serialized
- * version of the stylesheet.
- * The getMetricsFor(node, display) method returns the metric data for the given node
- *
- * @param {string} iname The name of the input jax
- * @param {string} oname The name of the output jax
- * @param {INPUTJAX} input The input jax instance
- */
- export function makeOutputMethods(iname: string, oname: string, input: INPUTJAX) {
- const name = iname + '2' + oname;
- MathJax[name] =
- (math: string, options: OptionList = {}) => {
- options.format = input.name;
- return document.convert(math, options);
- };
- MathJax[name + 'Promise'] =
- (math: string, options: OptionList = {}) => {
- options.format = input.name;
- return mathjax.handleRetriesFor(() => document.convert(math, options));
- };
- MathJax[oname + 'Stylesheet'] = () => output.styleSheet(document);
- if ('getMetricsFor' in output) {
- MathJax.getMetricsFor = (node: any, display: boolean) => {
- return (output as COMMONJAX).getMetricsFor(node, display);
- };
- }
- }
- /**
- * Make the input2mml(math, options?) and input2mmlPromise(math, options?) methods,
- * where "input" is replaced by the name of the input jax (e.g., "tex2mml").
- *
- * These convert the math to its serialized MathML representation.
- * The second wraps the conversion in handleRetriesFor() and
- * returns the resulting promise.
- *
- * @param {string} name The name of the input jax
- * @param {INPUTJAX} input The input jax itself
- */
- export function makeMmlMethods(name: string, input: INPUTJAX) {
- const STATE = MathJax._.core.MathItem.STATE;
- MathJax[name + '2mml'] =
- (math: string, options: OptionList = {}) => {
- options.end = STATE.CONVERT;
- options.format = input.name;
- return toMML(document.convert(math, options));
- };
- MathJax[name + '2mmlPromise'] =
- (math: string, options: OptionList = {}) => {
- options.end = STATE.CONVERT;
- options.format = input.name;
- return mathjax.handleRetriesFor(() => toMML(document.convert(math, options)));
- };
- }
- /**
- * Creates the inputReset() method, where "input" is replaced by the input jax name (e.g., "texReset()).
- *
- * The texReset() method clears the equation numbers and labels
- *
- * @param {string} name The name of the input jax
- * @param {INPUTJAX} input The input jax itself
- */
- export function makeResetMethod(name: string, input: INPUTJAX) {
- MathJax[name + 'Reset'] = (...args: any[]) => input.reset(...args);
- }
- /**
- * @return {INPUTJAX[]} The array of instances of the registered input jax
- */
- export function getInputJax(): INPUTJAX[] {
- const jax = [] as INPUTJAX[];
- for (const name of CONFIG.input) {
- const inputClass = constructors[name];
- if (inputClass) {
- jax.push(new inputClass(MathJax.config[name]));
- } else {
- throw Error('Input Jax "' + name + '" is not defined (has it been loaded?)');
- }
- }
- return jax;
- }
- /**
- * @return {OUTPUTJAX} The instance of the registered output jax
- */
- export function getOutputJax(): OUTPUTJAX {
- const name = CONFIG.output;
- if (!name) return null;
- const outputClass = constructors[name];
- if (!outputClass) {
- throw Error('Output Jax "' + name + '" is not defined (has it been loaded?)');
- }
- return new outputClass(MathJax.config[name]);
- }
- /**
- * @return {DOMADAPTOR} The instance of the registered DOMAdator (the registered constructor
- * in this case is a function that creates the adaptor, not a class)
- */
- export function getAdaptor(): DOMADAPTOR {
- const name = CONFIG.adaptor;
- if (!name || name === 'none') return null;
- const adaptor = constructors[name];
- if (!adaptor) {
- throw Error('DOMAdaptor "' + name + '" is not defined (has it been loaded?)');
- }
- return adaptor(MathJax.config[name]);
- }
- /**
- * @return {HANDLER} The instance of the registered Handler, extended by the registered extensions
- */
- export function getHandler(): HANDLER {
- const name = CONFIG.handler;
- if (!name || name === 'none' || !adaptor) return null;
- const handlerClass = constructors[name];
- if (!handlerClass) {
- throw Error('Handler "' + name + '" is not defined (has it been loaded?)');
- }
- let handler = new handlerClass(adaptor, 5);
- for (const extend of extensions) {
- handler = extend.item(handler);
- }
- return handler;
- }
- /**
- * Create the document with the given input and output jax
- *
- * @param {any=} root The Document to use as the root document (or null to use the configured document)
- * @returns {MathDocument} The MathDocument with the configured input and output jax
- */
- export function getDocument(root: any = null): MathDocument<any, any, any> {
- return mathjax.document(root || CONFIG.document, {
- ...MathJax.config.options,
- InputJax: input,
- OutputJax: output
- });
- }
- }
- /**
- * Export the global MathJax object for convenience
- */
- export const MathJax = MJGlobal as MathJaxObject;
- /*
- * If the startup module hasn't been added to the MathJax variable,
- * Add the startup configuration and data objects, and
- * set the method for handling invalid options, if provided.
- */
- if (typeof MathJax._.startup === 'undefined') {
- combineDefaults(MathJax.config, 'startup', {
- input: [],
- output: '',
- handler: null,
- adaptor: null,
- document: (typeof document === 'undefined' ? '' : document),
- elements: null,
- typeset: true,
- ready: Startup.defaultReady.bind(Startup),
- pageReady: Startup.defaultPageReady.bind(Startup)
- });
- combineWithMathJax({
- startup: Startup,
- options: {}
- });
- if (MathJax.config.startup.invalidOption) {
- OPTIONS.invalidOption = MathJax.config.startup.invalidOption;
- }
- if (MathJax.config.startup.optionError) {
- OPTIONS.optionError = MathJax.config.startup.optionError;
- }
- }
- /**
- * Export the startup configuration for convenience
- */
- export const CONFIG = MathJax.config.startup;
- /*
- * Tells if the user configuration included input jax or not
- */
- const inputSpecified = CONFIG.input.length !== 0;
|