site/node_modules/mathjax-full/ts/core/MathItem.ts
2024-10-14 08:09:33 +02:00

459 lines
13 KiB
TypeScript

/*************************************************************
*
* Copyright (c) 2017-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 the interface and abstract class for MathItem objects
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {MathDocument} from './MathDocument.js';
import {InputJax} from './InputJax.js';
import {OptionList} from '../util/Options.js';
import {MmlNode} from './MmlTree/MmlNode.js';
/*****************************************************************/
/**
* The Location gives a location of a position in a document
* (either a node and character position within it, or
* an index into a string array, the character position within
* the string, and the delimiter at that location).
*
* @template N The HTMLElement node class
* @template T The Text node class
*/
export type Location<N, T> = {
i?: number;
n?: number;
delim?: string;
node?: N | T;
};
/*****************************************************************/
/**
* The Metrics object includes the data needed to typeset
* a MathItem.
*/
export type Metrics = {
em: number;
ex: number;
containerWidth: number;
lineWidth: number;
scale: number;
};
/*****************************************************************/
/**
* The MathItem interface
*
* The MathItem is the object that holds the information about a
* particular expression on the page, including pointers to
* where it is in the document, its compiled version (in the
* internal format), its typeset version, its bounding box,
* and so on.
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export interface MathItem<N, T, D> {
/**
* The string representing the expression to be processed
*/
math: string;
/**
* The input jax used to process the math
*/
inputJax: InputJax<N, T, D>;
/**
* Whether the math is in display mode or inline mode
*/
display: boolean;
/**
* Whether this item is an escaped character or not
*/
isEscaped: boolean;
/**
* The start and ending locations in the document of
* this expression
*/
start: Location<N, T>;
end: Location<N, T>;
/**
* The internal format for this expression (once compiled)
*/
root: MmlNode;
/**
* The typeset version of the expression (once typeset)
*/
typesetRoot: N;
/**
* The metric information at the location of the math
* (the em-size, scaling factor, etc.)
*/
metrics: Metrics;
/**
* Extra data needed by the input or output jax, as needed
*/
inputData: OptionList;
outputData: OptionList;
/**
* Perform the renderActions on the document
*
* @param {MathDocument} document The MathDocument in which the math resides
*/
render(document: MathDocument<N, T, D>): void;
/**
* Rerenders an already rendered item and inserts it into the document
*
* @param {MathDocument} document The MathDocument in which the math resides
* @param {number=} start The state to start rerendering at (default = RERENDER)
*/
rerender(document: MathDocument<N, T, D>, start?: number): void;
/**
* Converts the expression by calling the render actions until the state matches the end state
*
* @param {MathDocument} document The MathDocument in which the math resides
* @param {number=} end The state to end rerendering at (default = LAST)
*/
convert(document: MathDocument<N, T, D>, end?: number): void;
/**
* Converts the expression into the internal format by calling the input jax
*
* @param {MathDocument} document The MathDocument in which the math resides
*/
compile(document: MathDocument<N, T, D>): void;
/**
* Converts the internal format to the typeset version by calling the output jax
*
* @param {MathDocument} document The MathDocument in which the math resides
*/
typeset(document: MathDocument<N, T, D>): void;
/**
* Inserts the typeset version in place of the original form in the document
*
* @param {MathDocument} document The MathDocument in which the math resides
*/
updateDocument(document: MathDocument<N, T, D>): void;
/**
* Removes the typeset version from the document, optionally replacing the original
* form of the expression and its delimiters.
*
* @param {boolean} restore True if the original version is to be restored
*/
removeFromDocument(restore: boolean): void;
/**
* Sets the metric information for this expression
*
* @param {number} em The size of 1 em in pixels
* @param {number} ex The size of 1 ex in pixels
* @param {number} cwidth The container width in pixels
* @param {number} lwidth The line breaking width in pixels
* @param {number} scale The scaling factor (unitless)
*/
setMetrics(em: number, ex: number, cwidth: number, lwidth: number, scale: number): void;
/**
* Set or return the current processing state of this expression,
* optionally restoring the document if rolling back an expression
* that has been added to the document.
*
* @param {number} state The state to set for the expression
* @param {number} restore True if the original form should be restored
* to the document when rolling back a typeset version
* @returns {number} The current state
*/
state(state?: number, restore?: boolean): number;
/**
* Reset the item to its unprocessed state
*
* @param {number} restore True if the original form should be restored
* to the document when rolling back a typeset version
*/
reset(restore?: boolean): void;
}
/*****************************************************************/
/**
* The ProtoItem interface
*
* This is what is returned by the FindMath class, giving the location
* of math within the document, and is used to produce the full
* MathItem later (e.g., when the position within a string array
* is translated back into the actual node location in the DOM).
*
* @template N The HTMLElement node class
* @template T The Text node class
*/
export type ProtoItem<N, T> = {
math: string; // The math expression itself
start: Location<N, T>; // The starting location of the math
end: Location<N, T>; // The ending location of the math
open?: string; // The opening delimiter
close?: string; // The closing delimiter
n?: number; // The index of the string in which this math is found
display: boolean; // True means display mode, false is inline mode
};
/**
* Produce a proto math item that can be turned into a MathItem
*
* @template N The HTMLElement node class
* @template T The Text node class
*/
export function protoItem<N, T>(open: string, math: string, close: string, n: number,
start: number, end: number, display: boolean = null) {
let item: ProtoItem<N, T> = {open: open, math: math, close: close,
n: n, start: {n: start}, end: {n: end}, display: display};
return item;
}
/*****************************************************************/
/**
* Implements the MathItem class
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export abstract class AbstractMathItem<N, T, D> implements MathItem<N, T, D> {
/**
* The source text for the math (e.g., TeX string)
*/
public math: string;
/**
* The input jax associated with this item
*/
public inputJax: InputJax<N, T, D>;
/**
* True when this math is in display mode
*/
public display: boolean;
/**
* Reference to the beginning of the math in the document
*/
public start: Location<N, T>;
/**
* Reference to the end of the math in the document
*/
public end: Location<N, T>;
/**
* The compiled internal MathML (result of InputJax)
*/
public root: MmlNode = null;
/**
* The typeset result (result of OutputJax)
*/
public typesetRoot: N = null;
/**
* The metric information about the surrounding environment
*/
public metrics: Metrics = {} as Metrics;
/**
* Data private to the input jax
*/
public inputData: OptionList = {};
/**
* Data private to the output jax
*/
public outputData: OptionList = {};
/**
* The current state of the item (how far in the render actions it has been processed)
*/
protected _state: number = STATE.UNPROCESSED;
/**
* @return {boolean} True when this item is an escaped delimiter
*/
public get isEscaped(): boolean {
return this.display === null;
}
/**
* @param {string} math The math expression for this item
* @param {Inputjax} jax The input jax to use for this item
* @param {boolean} display True if display mode, false if inline
* @param {Location} start The starting position of the math in the document
* @param {Location} end The ending position of the math in the document
* @constructor
*/
constructor (math: string, jax: InputJax<N, T, D>, display: boolean = true,
start: Location<N, T> = {i: 0, n: 0, delim: ''},
end: Location<N, T> = {i: 0, n: 0, delim: ''}) {
this.math = math;
this.inputJax = jax;
this.display = display;
this.start = start;
this.end = end;
this.root = null;
this.typesetRoot = null;
this.metrics = {} as Metrics;
this.inputData = {};
this.outputData = {};
}
/**
* @override
*/
public render(document: MathDocument<N, T, D>) {
document.renderActions.renderMath(this, document);
}
/**
* @override
*/
public rerender(document: MathDocument<N, T, D>, start: number = STATE.RERENDER) {
if (this.state() >= start) {
this.state(start - 1);
}
document.renderActions.renderMath(this, document, start);
}
/**
* @override
*/
public convert(document: MathDocument<N, T, D>, end: number = STATE.LAST) {
document.renderActions.renderConvert(this, document, end);
}
/**
* @override
*/
public compile(document: MathDocument<N, T, D>) {
if (this.state() < STATE.COMPILED) {
this.root = this.inputJax.compile(this, document);
this.state(STATE.COMPILED);
}
}
/**
* @override
*/
public typeset(document: MathDocument<N, T, D>) {
if (this.state() < STATE.TYPESET) {
this.typesetRoot = document.outputJax[this.isEscaped ? 'escaped' : 'typeset'](this, document);
this.state(STATE.TYPESET);
}
}
/**
* @override
*/
public updateDocument(_document: MathDocument<N, T, D>) {}
/**
* @override
*/
public removeFromDocument(_restore: boolean = false) {}
/**
* @override
*/
public setMetrics(em: number, ex: number, cwidth: number, lwidth: number, scale: number) {
this.metrics = {
em: em, ex: ex,
containerWidth: cwidth,
lineWidth: lwidth,
scale: scale
};
}
/**
* @override
*/
public state(state: number = null, restore: boolean = false) {
if (state != null) {
if (state < STATE.INSERTED && this._state >= STATE.INSERTED) {
this.removeFromDocument(restore);
}
if (state < STATE.TYPESET && this._state >= STATE.TYPESET) {
this.outputData = {};
}
if (state < STATE.COMPILED && this._state >= STATE.COMPILED) {
this.inputData = {};
}
this._state = state;
}
return this._state;
}
/**
* @override
*/
public reset(restore: boolean = false) {
this.state(STATE.UNPROCESSED, restore);
}
}
/*****************************************************************/
/**
* The various states that a MathItem (or MathDocument) can be in
* (open-ended so that extensions can add to it)
*/
export const STATE: {[state: string]: number} = {
UNPROCESSED: 0,
FINDMATH: 10,
COMPILED: 20,
CONVERT: 100,
METRICS: 110,
RERENDER: 125,
TYPESET: 150,
INSERTED: 200,
LAST: 10000
};
/**
* Allocate a new named state
*
* @param {string} name The name of the new state
* @param {number} state The value for the new state
*/
export function newState(name: string, state: number) {
if (name in STATE) {
throw Error('State ' + name + ' already exists');
}
STATE[name] = state;
}