add initial marp implementation with sample content and build configuration
This commit is contained in:
+295
@@ -0,0 +1,295 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* Copyright (c) 2019-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 Mixin that adds hidden MathML to the output
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {Handler} from '../core/Handler.js';
|
||||
import {MathDocument, AbstractMathDocument, MathDocumentConstructor} from '../core/MathDocument.js';
|
||||
import {MathItem, AbstractMathItem, STATE, newState} from '../core/MathItem.js';
|
||||
import {MmlNode} from '../core/MmlTree/MmlNode.js';
|
||||
import {SerializedMmlVisitor} from '../core/MmlTree/SerializedMmlVisitor.js';
|
||||
import {OptionList, expandable} from '../util/Options.js';
|
||||
import {StyleList} from '../util/StyleList.js';
|
||||
|
||||
/*==========================================================================*/
|
||||
|
||||
export class LimitedMmlVisitor extends SerializedMmlVisitor {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
protected getAttributes(node: MmlNode): string {
|
||||
/**
|
||||
* Remove id from attribute output
|
||||
*/
|
||||
return super.getAttributes(node).replace(/ ?id=".*?"/, '');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic constructor for Mixins
|
||||
*/
|
||||
export type Constructor<T> = new(...args: any[]) => T;
|
||||
|
||||
/*==========================================================================*/
|
||||
|
||||
/**
|
||||
* Add STATE value for having assistive MathML (after TYPESETTING)
|
||||
*/
|
||||
newState('ASSISTIVEMML', 153);
|
||||
|
||||
/**
|
||||
* The functions added to MathItem for assistive MathML
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export interface AssistiveMmlMathItem<N, T, D> extends MathItem<N, T, D> {
|
||||
/**
|
||||
* @param {MathDocument} document The document where assistive MathML is being added
|
||||
* @param {boolean} force True to force assistive MathML even if enableAssistiveMml is false
|
||||
*/
|
||||
assistiveMml(document: MathDocument<N, T, D>, force?: boolean): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The mixin for adding assistive MathML to MathItems
|
||||
*
|
||||
* @param {B} BaseMathItem The MathItem class to be extended
|
||||
* @return {AssistiveMathItem} The augmented MathItem class
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
* @template B The MathItem class to extend
|
||||
*/
|
||||
export function AssistiveMmlMathItemMixin<N, T, D, B extends Constructor<AbstractMathItem<N, T, D>>>(
|
||||
BaseMathItem: B
|
||||
): Constructor<AssistiveMmlMathItem<N, T, D>> & B {
|
||||
|
||||
return class extends BaseMathItem {
|
||||
|
||||
/**
|
||||
* @param {MathDocument} document The MathDocument for the MathItem
|
||||
* @param {boolean} force True to force assistive MathML evenif enableAssistiveMml is false
|
||||
*/
|
||||
public assistiveMml(document: AssistiveMmlMathDocument<N, T, D>, force: boolean = false) {
|
||||
if (this.state() >= STATE.ASSISTIVEMML) return;
|
||||
if (!this.isEscaped && (document.options.enableAssistiveMml || force)) {
|
||||
const adaptor = document.adaptor;
|
||||
//
|
||||
// Get the serialized MathML
|
||||
//
|
||||
const mml = document.toMML(this.root).replace(/\n */g, '').replace(/<!--.*?-->/g, '');
|
||||
//
|
||||
// Parse is as HTML and retrieve the <math> element
|
||||
//
|
||||
const mmlNodes = adaptor.firstChild(adaptor.body(adaptor.parse(mml, 'text/html')));
|
||||
//
|
||||
// Create a container for the hidden MathML
|
||||
//
|
||||
const node = adaptor.node('mjx-assistive-mml', {
|
||||
unselectable: 'on', display: (this.display ? 'block' : 'inline')
|
||||
}, [mmlNodes]);
|
||||
//
|
||||
// Hide the typeset math from assistive technology and append the MathML that is visually
|
||||
// hidden from other users
|
||||
//
|
||||
adaptor.setAttribute(adaptor.firstChild(this.typesetRoot) as N, 'aria-hidden', 'true');
|
||||
adaptor.setStyle(this.typesetRoot, 'position', 'relative');
|
||||
adaptor.append(this.typesetRoot, node);
|
||||
}
|
||||
this.state(STATE.ASSISTIVEMML);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/*==========================================================================*/
|
||||
|
||||
/**
|
||||
* The functions added to MathDocument for assistive MathML
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export interface AssistiveMmlMathDocument<N, T, D> extends AbstractMathDocument<N, T, D> {
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node to be serializes
|
||||
* @return {string} The serialization of the node
|
||||
*/
|
||||
toMML: (node: MmlNode) => string;
|
||||
|
||||
/**
|
||||
* Add assistive MathML to the MathItems in the MathDocument
|
||||
*
|
||||
* @return {AssistiveMmlMathDocument} The MathDocument (so calls can be chained)
|
||||
*/
|
||||
assistiveMml(): AssistiveMmlMathDocument<N, T, D>;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The mixin for adding assistive MathML to MathDocuments
|
||||
*
|
||||
* @param {B} BaseDocument The MathDocument class to be extended
|
||||
* @return {AssistiveMmlMathDocument} The Assistive MathML MathDocument class
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
* @template B The MathDocument class to extend
|
||||
*/
|
||||
export function AssistiveMmlMathDocumentMixin<N, T, D,
|
||||
B extends MathDocumentConstructor<AbstractMathDocument<N, T, D>>>(
|
||||
BaseDocument: B
|
||||
): MathDocumentConstructor<AssistiveMmlMathDocument<N, T, D>> & B {
|
||||
|
||||
return class BaseClass extends BaseDocument {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static OPTIONS: OptionList = {
|
||||
...BaseDocument.OPTIONS,
|
||||
enableAssistiveMml: true,
|
||||
renderActions: expandable({
|
||||
...BaseDocument.OPTIONS.renderActions,
|
||||
assistiveMml: [STATE.ASSISTIVEMML]
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
* styles needed for the hidden MathML
|
||||
*/
|
||||
public static assistiveStyles: StyleList = {
|
||||
'mjx-assistive-mml': {
|
||||
position: 'absolute !important',
|
||||
top: '0px', left: '0px',
|
||||
clip: 'rect(1px, 1px, 1px, 1px)',
|
||||
padding: '1px 0px 0px 0px !important',
|
||||
border: '0px !important',
|
||||
display: 'block !important',
|
||||
width: 'auto !important',
|
||||
overflow: 'hidden !important',
|
||||
/*
|
||||
* Don't allow the assistive MathML to become part of the selection
|
||||
*/
|
||||
'-webkit-touch-callout': 'none',
|
||||
'-webkit-user-select': 'none',
|
||||
'-khtml-user-select': 'none',
|
||||
'-moz-user-select': 'none',
|
||||
'-ms-user-select': 'none',
|
||||
'user-select': 'none'
|
||||
},
|
||||
'mjx-assistive-mml[display="block"]': {
|
||||
width: '100% !important'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Visitor used for serializing internal MathML nodes
|
||||
*/
|
||||
protected visitor: LimitedMmlVisitor;
|
||||
|
||||
/**
|
||||
* Augment the MathItem class used for this MathDocument, and create the serialization visitor.
|
||||
*
|
||||
* @override
|
||||
* @constructor
|
||||
*/
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
const CLASS = (this.constructor as typeof BaseClass);
|
||||
const ProcessBits = CLASS.ProcessBits;
|
||||
if (!ProcessBits.has('assistive-mml')) {
|
||||
ProcessBits.allocate('assistive-mml');
|
||||
}
|
||||
this.visitor = new LimitedMmlVisitor(this.mmlFactory);
|
||||
this.options.MathItem =
|
||||
AssistiveMmlMathItemMixin<N, T, D, Constructor<AbstractMathItem<N, T, D>>>(
|
||||
this.options.MathItem
|
||||
);
|
||||
if ('addStyles' in this) {
|
||||
(this as any).addStyles(CLASS.assistiveStyles);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node to be serializes
|
||||
* @return {string} The serialization of the node
|
||||
*/
|
||||
public toMML(node: MmlNode): string {
|
||||
return this.visitor.visitTree(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add assistive MathML to the MathItems in this MathDocument
|
||||
*/
|
||||
public assistiveMml() {
|
||||
if (!this.processed.isSet('assistive-mml')) {
|
||||
for (const math of this.math) {
|
||||
(math as AssistiveMmlMathItem<N, T, D>).assistiveMml(this);
|
||||
}
|
||||
this.processed.set('assistive-mml');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public state(state: number, restore: boolean = false) {
|
||||
super.state(state, restore);
|
||||
if (state < STATE.ASSISTIVEMML) {
|
||||
this.processed.clear('assistive-mml');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/*==========================================================================*/
|
||||
|
||||
/**
|
||||
* Add assitive MathML support a Handler instance
|
||||
*
|
||||
* @param {Handler} handler The Handler instance to enhance
|
||||
* @return {Handler} The handler that was modified (for purposes of chainging extensions)
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export function AssistiveMmlHandler<N, T, D>(handler: Handler<N, T, D>): Handler<N, T, D> {
|
||||
handler.documentClass =
|
||||
AssistiveMmlMathDocumentMixin<N, T, D, MathDocumentConstructor<AbstractMathDocument<N, T, D>>>(
|
||||
handler.documentClass
|
||||
);
|
||||
return handler;
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 Mixin that computes complexity of the internal MathML
|
||||
* and optionally marks collapsible items
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {Handler} from '../core/Handler.js';
|
||||
import {MathDocumentConstructor} from '../core/MathDocument.js';
|
||||
import {STATE, newState} from '../core/MathItem.js';
|
||||
import {MathML} from '../input/mathml.js';
|
||||
import {MmlNode} from '../core/MmlTree/MmlNode.js';
|
||||
import {EnrichHandler, EnrichedMathItem, EnrichedMathDocument} from './semantic-enrich.js';
|
||||
import {ComplexityVisitor} from './complexity/visitor.js';
|
||||
import {OptionList, selectOptionsFromKeys, expandable} from '../util/Options.js';
|
||||
|
||||
/**
|
||||
* Generic constructor for Mixins
|
||||
*/
|
||||
export type Constructor<T> = new(...args: any[]) => T;
|
||||
|
||||
/**
|
||||
* Shorthands for constructors
|
||||
*/
|
||||
export type EMItemC<N, T, D> = Constructor<EnrichedMathItem<N, T, D>>;
|
||||
export type CMItemC<N, T, D> = Constructor<ComplexityMathItem<N, T, D>>;
|
||||
export type EMDocC<N, T, D> = MathDocumentConstructor<EnrichedMathDocument<N, T, D>>;
|
||||
export type CMDocC<N, T, D> = Constructor<ComplexityMathDocument<N, T, D>>;
|
||||
|
||||
/*==========================================================================*/
|
||||
|
||||
/**
|
||||
* Add STATE value for having complexity added (after ENRICHED and before TYPESET)
|
||||
*/
|
||||
newState('COMPLEXITY', 40);
|
||||
|
||||
/**
|
||||
* The functions added to MathItem for complexity
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export interface ComplexityMathItem<N, T, D> extends EnrichedMathItem<N, T, D> {
|
||||
|
||||
/**
|
||||
* @param {ComplexityMathDocument} document The MathDocument for the MathItem
|
||||
* @param {boolean} force True to force the computation even if enableComplexity is false
|
||||
*/
|
||||
complexity(document: ComplexityMathDocument<N, T, D>, force?: boolean): void;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The mixin for adding complexity to MathItems
|
||||
*
|
||||
* @param {B} BaseMathItem The MathItem class to be extended
|
||||
* @param {function(MmlNode): void} computeComplexity Method of complexity computation.
|
||||
* @return {ComplexityMathItem} The complexity MathItem class
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
* @template B The MathItem class to extend
|
||||
*/
|
||||
export function ComplexityMathItemMixin<N, T, D, B extends
|
||||
EMItemC<N, T, D>>(BaseMathItem: B, computeComplexity: (node: MmlNode) => void): CMItemC<N, T, D> & B {
|
||||
|
||||
return class extends BaseMathItem {
|
||||
|
||||
/**
|
||||
* @param {ComplexityMathDocument} document The MathDocument for the MathItem
|
||||
* @param {boolean} force True to force the computation even if enableComplexity is false
|
||||
*/
|
||||
public complexity(document: ComplexityMathDocument<N, T, D>, force: boolean = false) {
|
||||
if (this.state() >= STATE.COMPLEXITY) return;
|
||||
if (!this.isEscaped && (document.options.enableComplexity || force)) {
|
||||
this.enrich(document, true);
|
||||
computeComplexity(this.root);
|
||||
}
|
||||
this.state(STATE.COMPLEXITY);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/*==========================================================================*/
|
||||
|
||||
/**
|
||||
* The functions added to MathDocument for complexity
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export interface ComplexityMathDocument<N, T, D> extends EnrichedMathDocument<N, T, D> {
|
||||
/**
|
||||
* Perform complexity computations on the MathItems in the MathDocument
|
||||
*
|
||||
* @return {ComplexityMathDocument} The MathDocument (so calls can be chained)
|
||||
*/
|
||||
complexity(): ComplexityMathDocument<N, T, D>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The mixin for adding complexity to MathDocuments
|
||||
*
|
||||
* @param {B} BaseDocument The MathDocument class to be extended
|
||||
* @return {EnrichedMathDocument} The enriched MathDocument class
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
* @template B The MathDocument class to extend
|
||||
*/
|
||||
export function ComplexityMathDocumentMixin<N, T, D, B extends
|
||||
EMDocC<N, T, D>>(BaseDocument: B): CMDocC<N, T, D> & B {
|
||||
|
||||
return class extends BaseDocument {
|
||||
|
||||
/**
|
||||
* The options for this type of document
|
||||
*/
|
||||
public static OPTIONS: OptionList = {
|
||||
...BaseDocument.OPTIONS,
|
||||
...ComplexityVisitor.OPTIONS,
|
||||
enableComplexity: true,
|
||||
ComplexityVisitor: ComplexityVisitor,
|
||||
renderActions: expandable({
|
||||
...BaseDocument.OPTIONS.renderActions,
|
||||
complexity: [STATE.COMPLEXITY]
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
* The visitor that computes complexities
|
||||
*/
|
||||
protected complexityVisitor: ComplexityVisitor;
|
||||
|
||||
/**
|
||||
* Extend the MathItem class used for this MathDocument
|
||||
*
|
||||
* @override
|
||||
* @constructor
|
||||
*/
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
const ProcessBits = (this.constructor as typeof BaseDocument).ProcessBits;
|
||||
if (!ProcessBits.has('complexity')) {
|
||||
ProcessBits.allocate('complexity');
|
||||
}
|
||||
const visitorOptions = selectOptionsFromKeys(this.options, this.options.ComplexityVisitor.OPTIONS);
|
||||
this.complexityVisitor = new this.options.ComplexityVisitor(this.mmlFactory, visitorOptions);
|
||||
const computeComplexity = ((node: MmlNode) => this.complexityVisitor.visitTree(node));
|
||||
this.options.MathItem =
|
||||
ComplexityMathItemMixin<N, T, D, EMItemC<N, T, D>>(
|
||||
this.options.MathItem, computeComplexity
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the complexity the MathItems in this MathDocument
|
||||
*/
|
||||
public complexity() {
|
||||
if (!this.processed.isSet('complexity')) {
|
||||
if (this.options.enableComplexity) {
|
||||
for (const math of this.math) {
|
||||
(math as ComplexityMathItem<N, T, D>).complexity(this);
|
||||
}
|
||||
}
|
||||
this.processed.set('complexity');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public state(state: number, restore: boolean = false) {
|
||||
super.state(state, restore);
|
||||
if (state < STATE.COMPLEXITY) {
|
||||
this.processed.clear('complexity');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/*==========================================================================*/
|
||||
|
||||
/**
|
||||
* Add complexity computations a Handler instance
|
||||
*
|
||||
* @param {Handler} handler The Handler instance to enhance
|
||||
* @param {MathML} MmlJax The MathML input jax to use for reading the enriched MathML
|
||||
* @return {Handler} The handler that was modified (for purposes of chainging extensions)
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export function ComplexityHandler<N, T, D>(
|
||||
handler: Handler<N, T, D>,
|
||||
MmlJax: MathML<N, T, D> = null
|
||||
): Handler<N, T, D> {
|
||||
if (!handler.documentClass.prototype.enrich && MmlJax) {
|
||||
handler = EnrichHandler(handler, MmlJax);
|
||||
}
|
||||
handler.documentClass = ComplexityMathDocumentMixin<N, T, D, EMDocC<N, T, D>>(handler.documentClass as any);
|
||||
return handler;
|
||||
}
|
||||
+519
@@ -0,0 +1,519 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 class that marks complex items for collapsing
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {MmlNode, AbstractMmlTokenNode, TextNode} from '../../core/MmlTree/MmlNode.js';
|
||||
import {ComplexityVisitor} from './visitor.js';
|
||||
|
||||
/*==========================================================================*/
|
||||
|
||||
/**
|
||||
* Function for checking if a node should be collapsible
|
||||
*/
|
||||
export type CollapseFunction = (node: MmlNode, complexity: number) => number;
|
||||
|
||||
/**
|
||||
* Map of types to collase functions
|
||||
*/
|
||||
export type CollapseFunctionMap = Map<string, CollapseFunction>;
|
||||
|
||||
/**
|
||||
* A list of values indexed by semantic-type, possibly sub-indexed by semantic-role
|
||||
*
|
||||
* @template T The type of the indexed item
|
||||
*/
|
||||
export type TypeRole<T> = {[type: string]: T | {[role: string]: T}};
|
||||
|
||||
/**
|
||||
* The class for determining of a subtree can be collapsed
|
||||
*/
|
||||
export class Collapse {
|
||||
|
||||
/**
|
||||
* A constant to use to indicate no collapsing
|
||||
*/
|
||||
public static NOCOLLAPSE: number = 10000000; // really big complexity
|
||||
|
||||
/**
|
||||
* The complexity object containing this one
|
||||
*/
|
||||
public complexity: ComplexityVisitor;
|
||||
|
||||
/**
|
||||
* The cutt-off complexity values for when a structure
|
||||
* of the given type should collapse
|
||||
*/
|
||||
public cutoff: TypeRole<number> = {
|
||||
identifier: 3,
|
||||
number: 3,
|
||||
text: 10,
|
||||
infixop: 15,
|
||||
relseq: 15,
|
||||
multirel: 15,
|
||||
fenced: 18,
|
||||
bigop: 20,
|
||||
integral: 20,
|
||||
fraction: 12,
|
||||
sqrt: 9,
|
||||
root: 12,
|
||||
vector: 15,
|
||||
matrix: 15,
|
||||
cases: 15,
|
||||
superscript: 9,
|
||||
subscript: 9,
|
||||
subsup: 9,
|
||||
punctuated: {
|
||||
endpunct: Collapse.NOCOLLAPSE,
|
||||
startpunct: Collapse.NOCOLLAPSE,
|
||||
value: 12
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* These are the characters to use for the various collapsed elements
|
||||
* (if an object, then semantic-role is used to get the character
|
||||
* from the object)
|
||||
*/
|
||||
public marker: TypeRole<string> = {
|
||||
identifier: 'x',
|
||||
number: '#',
|
||||
text: '...',
|
||||
appl: {
|
||||
'limit function': 'lim',
|
||||
value: 'f()'
|
||||
},
|
||||
fraction: '/',
|
||||
sqrt: '\u221A',
|
||||
root: '\u221A',
|
||||
superscript: '\u25FD\u02D9',
|
||||
subscript: '\u25FD.',
|
||||
subsup: '\u25FD:',
|
||||
vector: {
|
||||
binomial: '(:)',
|
||||
determinant: '|:|',
|
||||
value: '\u27E8:\u27E9'
|
||||
},
|
||||
matrix: {
|
||||
squarematrix: '[::]',
|
||||
rowvector: '\u27E8\u22EF\u27E9',
|
||||
columnvector: '\u27E8\u22EE\u27E9',
|
||||
determinant: '|::|',
|
||||
value: '(::)'
|
||||
},
|
||||
cases: '{:',
|
||||
infixop: {
|
||||
addition: '+',
|
||||
subtraction: '\u2212',
|
||||
multiplication: '\u22C5',
|
||||
implicit: '\u22C5',
|
||||
value: '+'
|
||||
},
|
||||
punctuated: {
|
||||
text: '...',
|
||||
value: ','
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The type-to-function mapping for semantic types
|
||||
*/
|
||||
public collapse: CollapseFunctionMap = new Map([
|
||||
|
||||
//
|
||||
// For fenced elements, if the contents are collapsed,
|
||||
// collapse the fence instead.
|
||||
//
|
||||
['fenced', (node, complexity) => {
|
||||
complexity = this.uncollapseChild(complexity, node, 1);
|
||||
if (complexity > this.cutoff.fenced && node.attributes.get('data-semantic-role') === 'leftright') {
|
||||
complexity = this.recordCollapse(
|
||||
node, complexity,
|
||||
this.getText(node.childNodes[0] as MmlNode) +
|
||||
this.getText(node.childNodes[node.childNodes.length - 1] as MmlNode)
|
||||
);
|
||||
}
|
||||
return complexity;
|
||||
}],
|
||||
|
||||
//
|
||||
// Collapse function applications if the argument is collapsed
|
||||
// (FIXME: Handle role="limit function" a bit better?)
|
||||
//
|
||||
['appl', (node, complexity) => {
|
||||
if (this.canUncollapse(node, 2, 2)) {
|
||||
complexity = this.complexity.visitNode(node, false);
|
||||
const marker = this.marker.appl as {[name: string]: string};
|
||||
const text = marker[node.attributes.get('data-semantic-role') as string] || marker.value;
|
||||
complexity = this.recordCollapse(node, complexity, text);
|
||||
}
|
||||
return complexity;
|
||||
}],
|
||||
|
||||
//
|
||||
// For sqrt elements, if the contents are collapsed,
|
||||
// collapse the sqrt instead.
|
||||
//
|
||||
['sqrt', (node, complexity) => {
|
||||
complexity = this.uncollapseChild(complexity, node, 0);
|
||||
if (complexity > this.cutoff.sqrt) {
|
||||
complexity = this.recordCollapse(node, complexity, this.marker.sqrt as string);
|
||||
}
|
||||
return complexity;
|
||||
}],
|
||||
['root', (node, complexity) => {
|
||||
complexity = this.uncollapseChild(complexity, node, 0, 2);
|
||||
if (complexity > this.cutoff.sqrt) {
|
||||
complexity = this.recordCollapse(node, complexity, this.marker.sqrt as string);
|
||||
}
|
||||
return complexity;
|
||||
}],
|
||||
|
||||
//
|
||||
// For enclose, include enclosure in collapse
|
||||
//
|
||||
['enclose', (node, complexity) => {
|
||||
if (this.splitAttribute(node, 'children').length === 1) {
|
||||
const child = this.canUncollapse(node, 1);
|
||||
if (child) {
|
||||
const marker = child.getProperty('collapse-marker') as string;
|
||||
this.unrecordCollapse(child);
|
||||
complexity = this.recordCollapse(node, this.complexity.visitNode(node, false), marker);
|
||||
}
|
||||
}
|
||||
return complexity;
|
||||
}],
|
||||
|
||||
//
|
||||
// For bigops, get the character to use from the largeop at its core.
|
||||
//
|
||||
['bigop', (node, complexity) => {
|
||||
if (complexity > this.cutoff.bigop || !node.isKind('mo')) {
|
||||
const id = this.splitAttribute(node, 'content').pop();
|
||||
const op = this.findChildText(node, id);
|
||||
complexity = this.recordCollapse(node, complexity, op);
|
||||
}
|
||||
return complexity;
|
||||
}],
|
||||
['integral', (node, complexity) => {
|
||||
if (complexity > this.cutoff.integral || !node.isKind('mo')) {
|
||||
const id = this.splitAttribute(node, 'content').pop();
|
||||
const op = this.findChildText(node, id);
|
||||
complexity = this.recordCollapse(node, complexity, op);
|
||||
}
|
||||
return complexity;
|
||||
}],
|
||||
|
||||
//
|
||||
// For relseq and multirel, use proper symbol
|
||||
//
|
||||
['relseq', (node, complexity) => {
|
||||
if (complexity > this.cutoff.relseq) {
|
||||
const id = this.splitAttribute(node, 'content')[0];
|
||||
const text = this.findChildText(node, id);
|
||||
complexity = this.recordCollapse(node, complexity, text);
|
||||
}
|
||||
return complexity;
|
||||
}],
|
||||
['multirel', (node, complexity) => {
|
||||
if (complexity > this.cutoff.relseq) {
|
||||
const id = this.splitAttribute(node, 'content')[0];
|
||||
const text = this.findChildText(node, id) + '\u22EF';
|
||||
complexity = this.recordCollapse(node, complexity, text);
|
||||
}
|
||||
return complexity;
|
||||
}],
|
||||
|
||||
//
|
||||
// Include super- and subscripts into a collapsed base
|
||||
//
|
||||
['superscript', (node, complexity) => {
|
||||
complexity = this.uncollapseChild(complexity, node, 0, 2);
|
||||
if (complexity > this.cutoff.superscript) {
|
||||
complexity = this.recordCollapse(node, complexity, this.marker.superscript as string);
|
||||
}
|
||||
return complexity;
|
||||
}],
|
||||
['subscript', (node, complexity) => {
|
||||
complexity = this.uncollapseChild(complexity, node, 0, 2);
|
||||
if (complexity > this.cutoff.subscript) {
|
||||
complexity = this.recordCollapse(node, complexity, this.marker.subscript as string);
|
||||
}
|
||||
return complexity;
|
||||
}],
|
||||
['subsup', (node, complexity) => {
|
||||
complexity = this.uncollapseChild(complexity, node, 0, 3);
|
||||
if (complexity > this.cutoff.subsup) {
|
||||
complexity = this.recordCollapse(node, complexity, this.marker.subsup as string);
|
||||
}
|
||||
return complexity;
|
||||
}]
|
||||
|
||||
] as [string, CollapseFunction][]);
|
||||
|
||||
/**
|
||||
* The highest id number used for mactions so far
|
||||
*/
|
||||
private idCount = 0;
|
||||
|
||||
/**
|
||||
* @param {ComplexityVisitor} visitor The visitor for computing complexities
|
||||
*/
|
||||
constructor(visitor: ComplexityVisitor) {
|
||||
this.complexity = visitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a node should be collapsible and insert the
|
||||
* maction node to handle that. Return the updated
|
||||
* complexity.
|
||||
*
|
||||
* @param {MmlNode} node The node to check
|
||||
* @param {number} complexity The current complexity of the node
|
||||
* @return {number} The revised complexity
|
||||
*/
|
||||
public check(node: MmlNode, complexity: number): number {
|
||||
const type = node.attributes.get('data-semantic-type') as string;
|
||||
if (this.collapse.has(type)) {
|
||||
return this.collapse.get(type).call(this, node, complexity);
|
||||
}
|
||||
if (this.cutoff.hasOwnProperty(type)) {
|
||||
return this.defaultCheck(node, complexity, type);
|
||||
}
|
||||
return complexity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the complexity exceeds the cutoff value for the type
|
||||
*
|
||||
* @param {MmlNode} node The node to check
|
||||
* @param {number} complexity The current complexity of the node
|
||||
* @param {string} type The semantic type of the node
|
||||
* @return {number} The revised complexity
|
||||
*/
|
||||
protected defaultCheck(node: MmlNode, complexity: number, type: string): number {
|
||||
const role = node.attributes.get('data-semantic-role') as string;
|
||||
const check = this.cutoff[type];
|
||||
const cutoff = (typeof check === 'number' ? check : check[role] || check.value);
|
||||
if (complexity > cutoff) {
|
||||
const marker = this.marker[type] || '??';
|
||||
const text = (typeof marker === 'string' ? marker : marker[role] || marker.value);
|
||||
complexity = this.recordCollapse(node, complexity, text);
|
||||
}
|
||||
return complexity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node to check
|
||||
* @param {number} complexity The current complexity of the node
|
||||
* @param {string} text The text to use for the collapsed node
|
||||
* @return {number} The revised complexity for the collapsed node
|
||||
*/
|
||||
protected recordCollapse(node: MmlNode, complexity: number, text: string): number {
|
||||
text = '\u25C2' + text + '\u25B8';
|
||||
node.setProperty('collapse-marker', text);
|
||||
node.setProperty('collapse-complexity', complexity);
|
||||
return text.length * this.complexity.complexity.text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove collapse markers (to move them to a parent node)
|
||||
*
|
||||
* @param {MmlNode} node The node to uncollapse
|
||||
*/
|
||||
protected unrecordCollapse(node: MmlNode) {
|
||||
const complexity = node.getProperty('collapse-complexity');
|
||||
if (complexity != null) {
|
||||
node.attributes.set('data-semantic-complexity', complexity);
|
||||
node.removeProperty('collapse-complexity');
|
||||
node.removeProperty('collapse-marker');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node to check if its child is collapsible
|
||||
* @param {number} n The position of the child node to check
|
||||
* @param {number=} m The number of children node must have
|
||||
* @return {MmlNode|null} The child node that was collapsed (or null)
|
||||
*/
|
||||
protected canUncollapse(node: MmlNode, n: number, m: number = 1): MmlNode | null {
|
||||
if (this.splitAttribute(node, 'children').length === m) {
|
||||
const mml = (node.childNodes.length === 1 &&
|
||||
(node.childNodes[0] as MmlNode).isInferred ? node.childNodes[0] as MmlNode : node);
|
||||
if (mml && mml.childNodes[n]) {
|
||||
const child = mml.childNodes[n] as MmlNode;
|
||||
if (child.getProperty('collapse-marker')) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} complexity The current complexity
|
||||
* @param {MmlNode} node The node to check
|
||||
* @param {number} n The position of the child node to check
|
||||
* @param {number=} m The number of children the node must have
|
||||
* @return {number} The updated complexity
|
||||
*/
|
||||
protected uncollapseChild(complexity: number, node: MmlNode, n: number, m: number = 1): number {
|
||||
const child = this.canUncollapse(node, n, m);
|
||||
if (child) {
|
||||
this.unrecordCollapse(child);
|
||||
if (child.parent !== node) {
|
||||
child.parent.attributes.set('data-semantic-complexity', undefined);
|
||||
}
|
||||
complexity = this.complexity.visitNode(node, false) as number;
|
||||
}
|
||||
return complexity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node whose attribute is to be split
|
||||
* @param {string} id The name of the data-semantic attribute to split
|
||||
* @return {string[]} Array of ids in the attribute split at commas
|
||||
*/
|
||||
protected splitAttribute(node: MmlNode, id: string): string[] {
|
||||
return (node.attributes.get('data-semantic-' + id) as string || '').split(/,/);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node whose text content is needed
|
||||
* @return{string} The text of the node (and its children), combined
|
||||
*/
|
||||
protected getText(node: MmlNode): string {
|
||||
if (node.isToken) return (node as AbstractMmlTokenNode).getText();
|
||||
return node.childNodes.map((n: MmlNode) => this.getText(n)).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node whose child text is needed
|
||||
* @param {string} id The (semantic) id of the child needed
|
||||
* @return {string} The text of the specified child node
|
||||
*/
|
||||
protected findChildText(node: MmlNode, id: string): string {
|
||||
const child = this.findChild(node, id);
|
||||
return this.getText(child.coreMO() || child);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node whose child is to be located
|
||||
* @param {string} id The (semantic) id of the child to be found
|
||||
* @return {MmlNode|null} The child node (or null if not found)
|
||||
*/
|
||||
protected findChild(node: MmlNode, id: string): MmlNode | null {
|
||||
if (!node || node.attributes.get('data-semantic-id') === id) return node;
|
||||
if (!node.isToken) {
|
||||
for (const mml of node.childNodes) {
|
||||
const child = this.findChild(mml as MmlNode, id);
|
||||
if (child) return child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add maction nodes to the nodes in the tree that can collapse
|
||||
*
|
||||
* @paramn {MmlNode} node The root of the tree to check
|
||||
*/
|
||||
public makeCollapse(node: MmlNode) {
|
||||
const nodes: MmlNode[] = [];
|
||||
node.walkTree((child: MmlNode) => {
|
||||
if (child.getProperty('collapse-marker')) {
|
||||
nodes.push(child);
|
||||
}
|
||||
});
|
||||
this.makeActions(nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode[]} nodes The list of nodes to replace by maction nodes
|
||||
*/
|
||||
public makeActions(nodes: MmlNode[]) {
|
||||
for (const node of nodes) {
|
||||
this.makeAction(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string} A unique id string.
|
||||
*/
|
||||
private makeId(): string {
|
||||
return 'mjx-collapse-' + this.idCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node to make collapsible by replacing with an maction
|
||||
*/
|
||||
public makeAction(node: MmlNode) {
|
||||
if (node.isKind('math')) {
|
||||
node = this.addMrow(node);
|
||||
}
|
||||
const factory = this.complexity.factory;
|
||||
const marker = node.getProperty('collapse-marker') as string;
|
||||
const parent = node.parent;
|
||||
let maction = factory.create('maction', {
|
||||
actiontype: 'toggle',
|
||||
selection: 2,
|
||||
'data-collapsible': true,
|
||||
id: this.makeId(),
|
||||
'data-semantic-complexity': node.attributes.get('data-semantic-complexity')
|
||||
}, [
|
||||
factory.create('mtext', {mathcolor: 'blue'}, [
|
||||
(factory.create('text') as TextNode).setText(marker)
|
||||
])
|
||||
]);
|
||||
maction.inheritAttributesFrom(node);
|
||||
node.attributes.set('data-semantic-complexity', node.getProperty('collapse-complexity'));
|
||||
node.removeProperty('collapse-marker');
|
||||
node.removeProperty('collapse-complexity');
|
||||
parent.replaceChild(maction, node);
|
||||
maction.appendChild(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the <math> node is to be collapsible, add an mrow to it instead so that we can wrap it
|
||||
* in an maction (can't put one around the <math> node).
|
||||
*
|
||||
* @param {MmlNode} node The math node to create an mrow for
|
||||
* @return {MmlNode} The newly created mrow
|
||||
*/
|
||||
public addMrow(node: MmlNode): MmlNode {
|
||||
const mrow = this.complexity.factory.create('mrow', null, node.childNodes[0].childNodes as MmlNode[]);
|
||||
node.childNodes[0].setChildren([mrow]);
|
||||
|
||||
const attributes = node.attributes.getAllAttributes();
|
||||
for (const name of Object.keys(attributes)) {
|
||||
if (name.substr(0, 14) === 'data-semantic-') {
|
||||
mrow.attributes.set(name, attributes[name]);
|
||||
delete attributes[name];
|
||||
}
|
||||
}
|
||||
|
||||
mrow.setProperty('collapse-marker', node.getProperty('collapse-marker'));
|
||||
mrow.setProperty('collapse-complexity', node.getProperty('collapse-complexity'));
|
||||
node.removeProperty('collapse-marker');
|
||||
node.removeProperty('collapse-complexity');
|
||||
return mrow;
|
||||
}
|
||||
}
|
||||
+381
@@ -0,0 +1,381 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 class that computes complexities for enriched math
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {MmlNode, AbstractMmlTokenNode} from '../../core/MmlTree/MmlNode.js';
|
||||
import {MmlMroot} from '../../core/MmlTree/MmlNodes/mroot.js';
|
||||
import {MmlMaction} from '../../core/MmlTree/MmlNodes/maction.js';
|
||||
import {MmlMsubsup, MmlMsub, MmlMsup} from '../../core/MmlTree/MmlNodes/msubsup.js';
|
||||
import {MmlMunderover, MmlMunder, MmlMover} from '../../core/MmlTree/MmlNodes/munderover.js';
|
||||
import {MmlVisitor} from '../../core/MmlTree/MmlVisitor.js';
|
||||
import {MmlFactory} from '../../core/MmlTree/MmlFactory.js';
|
||||
import {Collapse} from './collapse.js';
|
||||
import {OptionList, userOptions, defaultOptions} from '../../util/Options.js';
|
||||
|
||||
/*==========================================================================*/
|
||||
|
||||
/**
|
||||
* A visitor pattern that computes complexities within the MathML tree
|
||||
*/
|
||||
export class ComplexityVisitor extends MmlVisitor {
|
||||
|
||||
/**
|
||||
* The options for handling collapsing
|
||||
*/
|
||||
public static OPTIONS: OptionList = {
|
||||
identifyCollapsible: true, // mark elements that should be collapsed
|
||||
makeCollapsible: true, // insert maction to allow collapsing
|
||||
Collapse: Collapse // the Collapse class to use
|
||||
};
|
||||
|
||||
/**
|
||||
* Values used to compute complexities
|
||||
*/
|
||||
public complexity: {[name: string]: number} = {
|
||||
text: .5, // each character of a token element adds this to complexity
|
||||
token: .5, // each token element gets this additional complexity
|
||||
child: 1, // child nodes add this to their parent node's complexity
|
||||
|
||||
script: .8, // script elements reduce their complexity by this factor
|
||||
sqrt: 2, // sqrt adds this extra complexity
|
||||
subsup: 2, // sub-sup adds this extra complexity
|
||||
underover: 2, // under-over adds this extra complexity
|
||||
fraction: 2, // fractions add this extra complexity
|
||||
enclose: 2, // menclose adds this extra complexity
|
||||
action: 2, // maction adds this extra complexity
|
||||
phantom: 0, // mphantom makes complexity 0?
|
||||
xml: 2, // Can't really measure complexity of annotation-xml, so punt
|
||||
glyph: 2 // Can't really measure complexity of mglyph, to punt
|
||||
};
|
||||
|
||||
/**
|
||||
* The object used to handle collapsable content
|
||||
*/
|
||||
public collapse: Collapse;
|
||||
|
||||
/**
|
||||
* The MmlFactory for this visitor
|
||||
*/
|
||||
public factory: MmlFactory;
|
||||
|
||||
/**
|
||||
* The options for this visitor
|
||||
*/
|
||||
public options: OptionList;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
constructor(factory: MmlFactory, options: OptionList) {
|
||||
super(factory);
|
||||
let CLASS = this.constructor as typeof ComplexityVisitor;
|
||||
this.options = userOptions(defaultOptions({}, CLASS.OPTIONS), options);
|
||||
this.collapse = new this.options.Collapse(this);
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
/*==========================================================================*/
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public visitTree(node: MmlNode) {
|
||||
super.visitTree(node, true);
|
||||
if (this.options.makeCollapsible) {
|
||||
this.collapse.makeCollapse(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public visitNode(node: MmlNode, save: boolean) {
|
||||
if (node.attributes.get('data-semantic-complexity')) return;
|
||||
return super.visitNode(node, save);
|
||||
}
|
||||
|
||||
/**
|
||||
* For token nodes, use the content length, otherwise, add up the child complexities
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public visitDefault(node: MmlNode, save: boolean) {
|
||||
let complexity;
|
||||
if (node.isToken) {
|
||||
const text = (node as AbstractMmlTokenNode).getText();
|
||||
complexity = this.complexity.text * text.length + this.complexity.token;
|
||||
} else {
|
||||
complexity = this.childrenComplexity(node);
|
||||
}
|
||||
return this.setComplexity(node, complexity, save);
|
||||
}
|
||||
|
||||
/**
|
||||
* For a fraction, add the complexities of the children and scale by script factor, then
|
||||
* add the fraction amount
|
||||
*
|
||||
* @param {MmlNode} node The node whose complixity is being computed
|
||||
* @param {boolean} save True if the complexity is to be saved or just returned
|
||||
*/
|
||||
protected visitMfracNode(node: MmlNode, save: boolean) {
|
||||
const complexity = this.childrenComplexity(node) * this.complexity.script + this.complexity.fraction;
|
||||
return this.setComplexity(node, complexity, save);
|
||||
}
|
||||
|
||||
/**
|
||||
* For square roots, use the child complexity plus the sqrt complexity
|
||||
*
|
||||
* @param {MmlNode} node The node whose complixity is being computed
|
||||
* @param {boolean} save True if the complexity is to be saved or just returned
|
||||
*/
|
||||
protected visitMsqrtNode(node: MmlNode, save: boolean) {
|
||||
const complexity = this.childrenComplexity(node) + this.complexity.sqrt;
|
||||
return this.setComplexity(node, complexity, save);
|
||||
}
|
||||
|
||||
/**
|
||||
* For roots, do the sqrt root computation and remove a bit for the root
|
||||
* (since it is counted in the children sum but is smaller)
|
||||
*
|
||||
* @param {MmlMroot} node The node whose complixity is being computed
|
||||
* @param {boolean} save True if the complexity is to be saved or just returned
|
||||
*/
|
||||
protected visitMrootNode(node: MmlMroot, save: boolean) {
|
||||
const complexity = this.childrenComplexity(node) + this.complexity.sqrt
|
||||
- (1 - this.complexity.script) * this.getComplexity(node.childNodes[1]);
|
||||
return this.setComplexity(node, complexity, save);
|
||||
}
|
||||
|
||||
/**
|
||||
* Phantom complexity is 0
|
||||
*
|
||||
* @param {MmlNode} node The node whose complixity is being computed
|
||||
* @param {boolean} save True if the complexity is to be saved or just returned
|
||||
*/
|
||||
protected visitMphantomNode(node: MmlNode, save: boolean) {
|
||||
return this.setComplexity(node, this.complexity.phantom, save);
|
||||
}
|
||||
|
||||
/**
|
||||
* For ms, add the complexity of the quotes to that of the content, and use the
|
||||
* length of that times the text factor as the complexity
|
||||
*
|
||||
* @param {MmlNode} node The node whose complixity is being computed
|
||||
* @param {boolean} save True if the complexity is to be saved or just returned
|
||||
*/
|
||||
protected visitMsNode(node: MmlNode, save: boolean) {
|
||||
const text = node.attributes.get('lquote')
|
||||
+ (node as AbstractMmlTokenNode).getText()
|
||||
+ node.attributes.get('rquote');
|
||||
const complexity = text.length * this.complexity.text;
|
||||
return this.setComplexity(node, complexity, save);
|
||||
}
|
||||
|
||||
/**
|
||||
* For supscripts and superscripts use the maximum of the script complexities,
|
||||
* multiply by the script factor, and add the base complexity. Add the child
|
||||
* complexity for each child, and the subsup complexity.
|
||||
*
|
||||
* @param {MmlMsubsup} node The node whose complixity is being computed
|
||||
* @param {boolean} save True if the complexity is to be saved or just returned
|
||||
*/
|
||||
protected visitMsubsupNode(node: MmlMsubsup, save: boolean) {
|
||||
super.visitDefault(node, true);
|
||||
const sub = node.childNodes[node.sub];
|
||||
const sup = node.childNodes[node.sup];
|
||||
const base = node.childNodes[node.base];
|
||||
let complexity = Math.max(
|
||||
sub ? this.getComplexity(sub) : 0,
|
||||
sup ? this.getComplexity(sup) : 0
|
||||
) * this.complexity.script;
|
||||
complexity += this.complexity.child * ((sub ? 1 : 0) + (sup ? 1 : 0));
|
||||
complexity += (base ? this.getComplexity(base) + this.complexity.child : 0);
|
||||
complexity += this.complexity.subsup;
|
||||
return this.setComplexity(node, complexity, save);
|
||||
}
|
||||
/**
|
||||
* @param {MmlMsub} node The node whose complixity is being computed
|
||||
* @param {boolean} save True if the complexity is to be saved or just returned
|
||||
*/
|
||||
protected visitMsubNode(node: MmlMsub, save: boolean) {
|
||||
return this.visitMsubsupNode(node, save);
|
||||
}
|
||||
/**
|
||||
* @param {MmlMsup} node The node whose complixity is being computed
|
||||
* @param {boolean} save True if the complexity is to be saved or just returned
|
||||
*/
|
||||
protected visitMsupNode(node: MmlMsup, save: boolean) {
|
||||
return this.visitMsubsupNode(node, save);
|
||||
}
|
||||
|
||||
/**
|
||||
* For under/over, get the maximum of the complexities of the under and over
|
||||
* elements times the script factor, and that the maximum of that with the
|
||||
* base complexity. Add child complexity for all children, and add the
|
||||
* underover amount.
|
||||
*
|
||||
* @param {MmlMunderover} node The node whose complixity is being computed
|
||||
* @param {boolean} save True if the complexity is to be saved or just returned
|
||||
*/
|
||||
protected visitMunderoverNode(node: MmlMunderover, save: boolean) {
|
||||
super.visitDefault(node, true);
|
||||
const under = node.childNodes[node.under];
|
||||
const over = node.childNodes[node.over];
|
||||
const base = node.childNodes[node.base];
|
||||
let complexity = Math.max(
|
||||
under ? this.getComplexity(under) : 0,
|
||||
over ? this.getComplexity(over) : 0,
|
||||
) * this.complexity.script;
|
||||
if (base) {
|
||||
complexity = Math.max(this.getComplexity(base), complexity);
|
||||
}
|
||||
complexity += this.complexity.child * ((under ? 1 : 0) + (over ? 1 : 0) + (base ? 1 : 0));
|
||||
complexity += this.complexity.underover;
|
||||
return this.setComplexity(node, complexity, save);
|
||||
}
|
||||
/**
|
||||
* @param {MmlMunder} node The node whose complixity is being computed
|
||||
* @param {boolean} save True if the complexity is to be saved or just returned
|
||||
*/
|
||||
protected visitMunderNode(node: MmlMunder, save: boolean) {
|
||||
return this.visitMunderoverNode(node, save);
|
||||
}
|
||||
/**
|
||||
* @param {MmlMover} node The node whose complixity is being computed
|
||||
* @param {boolean} save True if the complexity is to be saved or just returned
|
||||
*/
|
||||
protected visitMoverNode(node: MmlMover, save: boolean) {
|
||||
return this.visitMunderoverNode(node, save);
|
||||
}
|
||||
|
||||
/**
|
||||
* For enclose, use sum of child complexities plus some for the enclose
|
||||
*
|
||||
* @param {MmlNode} node The node whose complixity is being computed
|
||||
* @param {boolean} save True if the complexity is to be saved or just returned
|
||||
*/
|
||||
protected visitMencloseNode(node: MmlNode, save: boolean) {
|
||||
const complexity = this.childrenComplexity(node) + this.complexity.enclose;
|
||||
return this.setComplexity(node, complexity, save);
|
||||
}
|
||||
|
||||
/**
|
||||
* For actions, use the complexity of the selected child
|
||||
*
|
||||
* @param {MmlMaction} node The node whose complixity is being computed
|
||||
* @param {boolean} save True if the complexity is to be saved or just returned
|
||||
*/
|
||||
protected visitMactionNode(node: MmlMaction, save: boolean) {
|
||||
this.childrenComplexity(node);
|
||||
const complexity = this.getComplexity(node.selected);
|
||||
return this.setComplexity(node, complexity, save);
|
||||
}
|
||||
|
||||
/**
|
||||
* For semantics, get the complexity from the first child
|
||||
*
|
||||
* @param {MmlNode} node The node whose complixity is being computed
|
||||
* @param {boolean} save True if the complexity is to be saved or just returned
|
||||
*/
|
||||
protected visitMsemanticsNode(node: MmlNode, save: boolean) {
|
||||
const child = node.childNodes[0] as MmlNode;
|
||||
let complexity = 0;
|
||||
if (child) {
|
||||
this.visitNode(child, true);
|
||||
complexity = this.getComplexity(child);
|
||||
}
|
||||
return this.setComplexity(node, complexity, save);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can't really measure annotations, so just use a specific value
|
||||
*
|
||||
* @param {MmlNode} node The node whose complixity is being computed
|
||||
* @param {boolean} save True if the complexity is to be saved or just returned
|
||||
*/
|
||||
protected visitAnnotationNode(node: MmlNode, save: boolean) {
|
||||
return this.setComplexity(node, this.complexity.xml, save);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can't really measure annotations, so just use a specific value
|
||||
*
|
||||
* @param {MmlNode} node The node whose complixity is being computed
|
||||
* @param {boolean} save True if the complexity is to be saved or just returned
|
||||
*/
|
||||
protected visitAnnotation_xmlNode(node: MmlNode, save: boolean) {
|
||||
return this.setComplexity(node, this.complexity.xml, save);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can't really measure mglyph complexity, so just use a specific value
|
||||
*
|
||||
* @param {MmlNode} node The node whose complixity is being computed
|
||||
* @param {boolean} save True if the complexity is to be saved or just returned
|
||||
*/
|
||||
protected visitMglyphNode(node: MmlNode, save: boolean) {
|
||||
return this.setComplexity(node, this.complexity.glyph, save);
|
||||
}
|
||||
|
||||
/*==========================================================================*/
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node whose complixity is needed
|
||||
* @return {number} The complexity fof the node (if collapsable, then the collapsed complexity)
|
||||
*/
|
||||
public getComplexity(node: MmlNode): number {
|
||||
const collapsed = node.getProperty('collapsedComplexity');
|
||||
return (collapsed != null ? collapsed : node.attributes.get('data-semantic-complexity')) as number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node whose complixity is being set
|
||||
* @param {complexity} number The complexity for the node
|
||||
* @param {boolean} save True if complexity is to be set or just reported
|
||||
*/
|
||||
protected setComplexity(node: MmlNode, complexity: number, save: boolean) {
|
||||
if (save) {
|
||||
if (this.options.identifyCollapsible) {
|
||||
complexity = this.collapse.check(node, complexity);
|
||||
}
|
||||
node.attributes.set('data-semantic-complexity', complexity);
|
||||
}
|
||||
return complexity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node whose children complexities are to be added
|
||||
* @return {number} The sum of the complexities, plus child complexity for each one
|
||||
*/
|
||||
protected childrenComplexity(node: MmlNode): number {
|
||||
super.visitDefault(node, true);
|
||||
let complexity = 0;
|
||||
for (const child of node.childNodes) {
|
||||
complexity += this.getComplexity(child as MmlNode);
|
||||
}
|
||||
if (node.childNodes.length > 1) {
|
||||
complexity += node.childNodes.length * this.complexity.child;
|
||||
}
|
||||
return complexity;
|
||||
}
|
||||
|
||||
}
|
||||
+650
@@ -0,0 +1,650 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 Mixin that implements the Explorer
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {Handler} from '../core/Handler.js';
|
||||
import {MmlNode} from '../core/MmlTree/MmlNode.js';
|
||||
import {MathML} from '../input/mathml.js';
|
||||
import {STATE, newState} from '../core/MathItem.js';
|
||||
import {EnrichedMathItem, EnrichedMathDocument, EnrichHandler} from './semantic-enrich.js';
|
||||
import {MathDocumentConstructor} from '../core/MathDocument.js';
|
||||
import {OptionList, expandable} from '../util/Options.js';
|
||||
import {SerializedMmlVisitor} from '../core/MmlTree/SerializedMmlVisitor.js';
|
||||
import {MJContextMenu} from '../ui/menu/MJContextMenu.js';
|
||||
|
||||
import {Explorer} from './explorer/Explorer.js';
|
||||
import * as ke from './explorer/KeyExplorer.js';
|
||||
import * as me from './explorer/MouseExplorer.js';
|
||||
import {TreeColorer, FlameColorer} from './explorer/TreeExplorer.js';
|
||||
import {LiveRegion, ToolTip, HoverRegion} from './explorer/Region.js';
|
||||
|
||||
import {Submenu} from 'mj-context-menu/js/item_submenu.js';
|
||||
|
||||
import Sre from './sre.js';
|
||||
|
||||
/**
|
||||
* Generic constructor for Mixins
|
||||
*/
|
||||
export type Constructor<T> = new(...args: any[]) => T;
|
||||
|
||||
/**
|
||||
* Shorthands for types with HTMLElement, Text, and Document instead of generics
|
||||
*/
|
||||
export type HANDLER = Handler<HTMLElement, Text, Document>;
|
||||
export type HTMLDOCUMENT = EnrichedMathDocument<HTMLElement, Text, Document>;
|
||||
export type HTMLMATHITEM = EnrichedMathItem<HTMLElement, Text, Document>;
|
||||
export type MATHML = MathML<HTMLElement, Text, Document>;
|
||||
|
||||
/*==========================================================================*/
|
||||
|
||||
/**
|
||||
* Add STATE value for having the Explorer added (after TYPESET and before INSERTED or CONTEXT_MENU)
|
||||
*/
|
||||
newState('EXPLORER', 160);
|
||||
|
||||
/**
|
||||
* The properties added to MathItem for the Explorer
|
||||
*/
|
||||
export interface ExplorerMathItem extends HTMLMATHITEM {
|
||||
|
||||
/**
|
||||
* @param {HTMLDocument} document The document where the Explorer is being added
|
||||
* @param {boolean} force True to force the explorer even if enableExplorer is false
|
||||
*/
|
||||
explorable(document: HTMLDOCUMENT, force?: boolean): void;
|
||||
|
||||
/**
|
||||
* @param {HTMLDocument} document The document where the Explorer is being added
|
||||
*/
|
||||
attachExplorers(document: HTMLDOCUMENT): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The mixin for adding the Explorer to MathItems
|
||||
*
|
||||
* @param {B} BaseMathItem The MathItem class to be extended
|
||||
* @param {Function} toMathML The function to serialize the internal MathML
|
||||
* @returns {ExplorerMathItem} The Explorer MathItem class
|
||||
*
|
||||
* @template B The MathItem class to extend
|
||||
*/
|
||||
export function ExplorerMathItemMixin<B extends Constructor<HTMLMATHITEM>>(
|
||||
BaseMathItem: B,
|
||||
toMathML: (node: MmlNode) => string
|
||||
): Constructor<ExplorerMathItem> & B {
|
||||
|
||||
return class extends BaseMathItem {
|
||||
|
||||
/**
|
||||
* The Explorer objects for this math item
|
||||
*/
|
||||
protected explorers: {[key: string]: Explorer} = {};
|
||||
|
||||
/**
|
||||
* The currently attached explorers
|
||||
*/
|
||||
protected attached: string[] = [];
|
||||
|
||||
/**
|
||||
* True when a rerendered element should restart these explorers
|
||||
*/
|
||||
protected restart: string[] = [];
|
||||
|
||||
/**
|
||||
* True when a rerendered element should regain the focus
|
||||
*/
|
||||
protected refocus: boolean = false;
|
||||
|
||||
/**
|
||||
* Save explorer id during rerendering.
|
||||
*/
|
||||
protected savedId: string = null;
|
||||
|
||||
/**
|
||||
* Add the explorer to the output for this math item
|
||||
*
|
||||
* @param {HTMLDocument} document The MathDocument for the MathItem
|
||||
* @param {boolean} force True to force the explorer even if enableExplorer is false
|
||||
*/
|
||||
public explorable(document: ExplorerMathDocument, force: boolean = false) {
|
||||
if (this.state() >= STATE.EXPLORER) return;
|
||||
if (!this.isEscaped && (document.options.enableExplorer || force)) {
|
||||
const node = this.typesetRoot;
|
||||
const mml = toMathML(this.root);
|
||||
if (this.savedId) {
|
||||
this.typesetRoot.setAttribute('sre-explorer-id', this.savedId);
|
||||
this.savedId = null;
|
||||
}
|
||||
// Init explorers:
|
||||
this.explorers = initExplorers(document, node, mml);
|
||||
this.attachExplorers(document);
|
||||
}
|
||||
this.state(STATE.EXPLORER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches the explorers that are currently meant to be active given
|
||||
* the document options. Detaches all others.
|
||||
* @param {ExplorerMathDocument} document The current document.
|
||||
*/
|
||||
public attachExplorers(document: ExplorerMathDocument) {
|
||||
this.attached = [];
|
||||
let keyExplorers = [];
|
||||
for (let key of Object.keys(this.explorers)) {
|
||||
let explorer = this.explorers[key];
|
||||
if (explorer instanceof ke.AbstractKeyExplorer) {
|
||||
explorer.AddEvents();
|
||||
explorer.stoppable = false;
|
||||
keyExplorers.unshift(explorer);
|
||||
}
|
||||
if (document.options.a11y[key]) {
|
||||
explorer.Attach();
|
||||
this.attached.push(key);
|
||||
} else {
|
||||
explorer.Detach();
|
||||
}
|
||||
}
|
||||
// Ensure that the last currently attached key explorer stops propagating
|
||||
// key events.
|
||||
for (let explorer of keyExplorers) {
|
||||
if (explorer.attached) {
|
||||
explorer.stoppable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public rerender(document: ExplorerMathDocument, start: number = STATE.RERENDER) {
|
||||
this.savedId = this.typesetRoot.getAttribute('sre-explorer-id');
|
||||
this.refocus = (window.document.activeElement === this.typesetRoot);
|
||||
for (let key of this.attached) {
|
||||
let explorer = this.explorers[key];
|
||||
if (explorer.active) {
|
||||
this.restart.push(key);
|
||||
explorer.Stop();
|
||||
}
|
||||
}
|
||||
super.rerender(document, start);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public updateDocument(document: ExplorerMathDocument) {
|
||||
super.updateDocument(document);
|
||||
this.refocus && this.typesetRoot.focus();
|
||||
this.restart.forEach(x => this.explorers[x].Start());
|
||||
this.restart = [];
|
||||
this.refocus = false;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The functions added to MathDocument for the Explorer
|
||||
*/
|
||||
export interface ExplorerMathDocument extends HTMLDOCUMENT {
|
||||
|
||||
/**
|
||||
* The objects needed for the explorer
|
||||
*/
|
||||
explorerRegions: ExplorerRegions;
|
||||
|
||||
/**
|
||||
* Add the Explorer to the MathItems in the MathDocument
|
||||
*
|
||||
* @returns {MathDocument} The MathDocument (so calls can be chained)
|
||||
*/
|
||||
explorable(): HTMLDOCUMENT;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The mixin for adding the Explorer to MathDocuments
|
||||
*
|
||||
* @param {B} BaseDocument The MathDocument class to be extended
|
||||
* @returns {ExplorerMathDocument} The extended MathDocument class
|
||||
*/
|
||||
export function ExplorerMathDocumentMixin<B extends MathDocumentConstructor<HTMLDOCUMENT>>(
|
||||
BaseDocument: B
|
||||
): MathDocumentConstructor<ExplorerMathDocument> & B {
|
||||
|
||||
return class extends BaseDocument {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static OPTIONS: OptionList = {
|
||||
...BaseDocument.OPTIONS,
|
||||
enableExplorer: true,
|
||||
renderActions: expandable({
|
||||
...BaseDocument.OPTIONS.renderActions,
|
||||
explorable: [STATE.EXPLORER]
|
||||
}),
|
||||
sre: expandable({
|
||||
...BaseDocument.OPTIONS.sre,
|
||||
speech: 'shallow', // overrides option in EnrichedMathDocument
|
||||
}),
|
||||
a11y: {
|
||||
align: 'top', // placement of magnified expression
|
||||
backgroundColor: 'Blue', // color for background of selected sub-expression
|
||||
backgroundOpacity: 20, // opacity for background of selected sub-expression
|
||||
braille: false, // switch on Braille output
|
||||
flame: false, // color collapsible sub-expressions
|
||||
foregroundColor: 'Black', // color to use for text of selected sub-expression
|
||||
foregroundOpacity: 100, // opacity for text of selected sub-expression
|
||||
highlight: 'None', // type of highlighting for collapsible sub-expressions
|
||||
hover: false, // show collapsible sub-expression on mouse hovering
|
||||
infoPrefix: false, // show speech prefixes on mouse hovering
|
||||
infoRole: false, // show semantic role on mouse hovering
|
||||
infoType: false, // show semantic type on mouse hovering
|
||||
keyMagnifier: false, // switch on magnification via key exploration
|
||||
magnification: 'None', // type of magnification
|
||||
magnify: '400%', // percentage of magnification of zoomed expressions
|
||||
mouseMagnifier: false, // switch on magnification via mouse hovering
|
||||
speech: true, // switch on speech output
|
||||
subtitles: true, // show speech as a subtitle
|
||||
treeColoring: false, // tree color expression
|
||||
viewBraille: false // display Braille output as subtitles
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The objects needed for the explorer
|
||||
*/
|
||||
public explorerRegions: ExplorerRegions;
|
||||
|
||||
/**
|
||||
* Extend the MathItem class used for this MathDocument
|
||||
* and create the visitor and explorer objects needed for the explorer
|
||||
*
|
||||
* @override
|
||||
* @constructor
|
||||
*/
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
const ProcessBits = (this.constructor as typeof BaseDocument).ProcessBits;
|
||||
if (!ProcessBits.has('explorer')) {
|
||||
ProcessBits.allocate('explorer');
|
||||
}
|
||||
const visitor = new SerializedMmlVisitor(this.mmlFactory);
|
||||
const toMathML = ((node: MmlNode) => visitor.visitTree(node));
|
||||
this.options.MathItem = ExplorerMathItemMixin(this.options.MathItem, toMathML);
|
||||
// TODO: set backward compatibility options here.
|
||||
this.explorerRegions = initExplorerRegions(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the Explorer to the MathItems in this MathDocument
|
||||
*
|
||||
* @return {ExplorerMathDocument} The MathDocument (so calls can be chained)
|
||||
*/
|
||||
public explorable(): ExplorerMathDocument {
|
||||
if (!this.processed.isSet('explorer')) {
|
||||
if (this.options.enableExplorer) {
|
||||
for (const math of this.math) {
|
||||
(math as ExplorerMathItem).explorable(this);
|
||||
}
|
||||
}
|
||||
this.processed.set('explorer');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public state(state: number, restore: boolean = false) {
|
||||
super.state(state, restore);
|
||||
if (state < STATE.EXPLORER) {
|
||||
this.processed.clear('explorer');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*==========================================================================*/
|
||||
|
||||
/**
|
||||
* Add Explorer functions to a Handler instance
|
||||
*
|
||||
* @param {Handler} handler The Handler instance to enhance
|
||||
* @param {MathML} MmlJax A MathML input jax to be used for the semantic enrichment
|
||||
* @returns {Handler} The handler that was modified (for purposes of chainging extensions)
|
||||
*/
|
||||
export function ExplorerHandler(handler: HANDLER, MmlJax: MATHML = null): HANDLER {
|
||||
if (!handler.documentClass.prototype.enrich && MmlJax) {
|
||||
handler = EnrichHandler(handler, MmlJax);
|
||||
}
|
||||
handler.documentClass = ExplorerMathDocumentMixin(handler.documentClass as any);
|
||||
return handler;
|
||||
}
|
||||
|
||||
|
||||
/*==========================================================================*/
|
||||
|
||||
/**
|
||||
* The regions objects needed for the explorers.
|
||||
*/
|
||||
export type ExplorerRegions = {
|
||||
speechRegion?: LiveRegion,
|
||||
brailleRegion?: LiveRegion,
|
||||
magnifier?: HoverRegion,
|
||||
tooltip1?: ToolTip,
|
||||
tooltip2?: ToolTip,
|
||||
tooltip3?: ToolTip
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the regions needed for a document.
|
||||
* @param {ExplorerMathDocument} document The current document.
|
||||
*/
|
||||
function initExplorerRegions(document: ExplorerMathDocument) {
|
||||
return {
|
||||
speechRegion: new LiveRegion(document),
|
||||
brailleRegion: new LiveRegion(document),
|
||||
magnifier: new HoverRegion(document),
|
||||
tooltip1: new ToolTip(document),
|
||||
tooltip2: new ToolTip(document),
|
||||
tooltip3: new ToolTip(document)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Type of explorer initialization methods.
|
||||
* @type {(ExplorerMathDocument, HTMLElement, any[]): Explorer}
|
||||
*/
|
||||
type ExplorerInit = (doc: ExplorerMathDocument,
|
||||
node: HTMLElement, ...rest: any[]) => Explorer;
|
||||
|
||||
/**
|
||||
* Generation methods for all MathJax explorers available via option settings.
|
||||
*/
|
||||
let allExplorers: {[options: string]: ExplorerInit} = {
|
||||
speech: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) => {
|
||||
let explorer = ke.SpeechExplorer.create(
|
||||
doc, doc.explorerRegions.speechRegion, node, ...rest) as ke.SpeechExplorer;
|
||||
explorer.speechGenerator.setOptions({
|
||||
locale: doc.options.sre.locale, domain: doc.options.sre.domain,
|
||||
style: doc.options.sre.style, modality: 'speech'});
|
||||
// This weeds out the case of providing a non-existent locale option.
|
||||
let locale = explorer.speechGenerator.getOptions().locale;
|
||||
if (locale !== Sre.engineSetup().locale) {
|
||||
doc.options.sre.locale = Sre.engineSetup().locale;
|
||||
explorer.speechGenerator.setOptions({locale: doc.options.sre.locale});
|
||||
}
|
||||
explorer.showRegion = 'subtitles';
|
||||
return explorer;
|
||||
},
|
||||
braille: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) => {
|
||||
let explorer = ke.SpeechExplorer.create(
|
||||
doc, doc.explorerRegions.brailleRegion, node, ...rest) as ke.SpeechExplorer;
|
||||
explorer.speechGenerator.setOptions({locale: 'nemeth', domain: 'default',
|
||||
style: 'default', modality: 'braille'});
|
||||
explorer.showRegion = 'viewBraille';
|
||||
return explorer;
|
||||
},
|
||||
keyMagnifier: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) =>
|
||||
ke.Magnifier.create(doc, doc.explorerRegions.magnifier, node, ...rest),
|
||||
mouseMagnifier: (doc: ExplorerMathDocument, node: HTMLElement, ..._rest: any[]) =>
|
||||
me.ContentHoverer.create(doc, doc.explorerRegions.magnifier, node,
|
||||
(x: HTMLElement) => x.hasAttribute('data-semantic-type'),
|
||||
(x: HTMLElement) => x),
|
||||
hover: (doc: ExplorerMathDocument, node: HTMLElement, ..._rest: any[]) =>
|
||||
me.FlameHoverer.create(doc, null, node),
|
||||
infoType: (doc: ExplorerMathDocument, node: HTMLElement, ..._rest: any[]) =>
|
||||
me.ValueHoverer.create(doc, doc.explorerRegions.tooltip1, node,
|
||||
(x: HTMLElement) => x.hasAttribute('data-semantic-type'),
|
||||
(x: HTMLElement) => x.getAttribute('data-semantic-type')),
|
||||
infoRole: (doc: ExplorerMathDocument, node: HTMLElement, ..._rest: any[]) =>
|
||||
me.ValueHoverer.create(doc, doc.explorerRegions.tooltip2, node,
|
||||
(x: HTMLElement) => x.hasAttribute('data-semantic-role'),
|
||||
(x: HTMLElement) => x.getAttribute('data-semantic-role')),
|
||||
infoPrefix: (doc: ExplorerMathDocument, node: HTMLElement, ..._rest: any[]) =>
|
||||
me.ValueHoverer.create(doc, doc.explorerRegions.tooltip3, node,
|
||||
(x: HTMLElement) => x.hasAttribute('data-semantic-prefix'),
|
||||
(x: HTMLElement) => x.getAttribute('data-semantic-prefix')),
|
||||
flame: (doc: ExplorerMathDocument, node: HTMLElement, ..._rest: any[]) =>
|
||||
FlameColorer.create(doc, null, node),
|
||||
treeColoring: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) =>
|
||||
TreeColorer.create(doc, null, node, ...rest)
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initialises explorers for a document.
|
||||
* @param {ExplorerMathDocument} document The target document.
|
||||
* @param {HTMLElement} node The node explorers will be attached to.
|
||||
* @param {string} mml The corresponding Mathml node as a string.
|
||||
* @return {Explorer[]} A list of initialised explorers.
|
||||
*/
|
||||
function initExplorers(document: ExplorerMathDocument, node: HTMLElement, mml: string): {[key: string]: Explorer} {
|
||||
let explorers: {[key: string]: Explorer} = {};
|
||||
for (let key of Object.keys(allExplorers)) {
|
||||
explorers[key] = allExplorers[key](document, node, mml);
|
||||
}
|
||||
return explorers;
|
||||
}
|
||||
|
||||
|
||||
/* Context Menu Interactions */
|
||||
|
||||
/**
|
||||
* Sets a list of a11y options for a given document.
|
||||
* @param {HTMLDOCUMENT} document The current document.
|
||||
* @param {{[key: string]: any}} options Association list for a11y option value pairs.
|
||||
*/
|
||||
export function setA11yOptions(document: HTMLDOCUMENT, options: {[key: string]: any}) {
|
||||
let sreOptions = Sre.engineSetup() as {[name: string]: string};
|
||||
for (let key in options) {
|
||||
if (document.options.a11y[key] !== undefined) {
|
||||
setA11yOption(document, key, options[key]);
|
||||
if (key === 'locale') {
|
||||
document.options.sre[key] = options[key];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (sreOptions[key] !== undefined) {
|
||||
document.options.sre[key] = options[key];
|
||||
}
|
||||
}
|
||||
// Reinit explorers
|
||||
for (let item of document.math) {
|
||||
(item as ExplorerMathItem).attachExplorers(document as ExplorerMathDocument);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets a single a11y option for a menu name.
|
||||
* @param {HTMLDOCUMENT} document The current document.
|
||||
* @param {string} option The option name in the menu.
|
||||
* @param {string|boolean} value The new value.
|
||||
*/
|
||||
export function setA11yOption(document: HTMLDOCUMENT, option: string, value: string | boolean) {
|
||||
switch (option) {
|
||||
case 'magnification':
|
||||
switch (value) {
|
||||
case 'None':
|
||||
document.options.a11y.magnification = value;
|
||||
document.options.a11y.keyMagnifier = false;
|
||||
document.options.a11y.mouseMagnifier = false;
|
||||
break;
|
||||
case 'Keyboard':
|
||||
document.options.a11y.magnification = value;
|
||||
document.options.a11y.keyMagnifier = true;
|
||||
document.options.a11y.mouseMagnifier = false;
|
||||
break;
|
||||
case 'Mouse':
|
||||
document.options.a11y.magnification = value;
|
||||
document.options.a11y.keyMagnifier = false;
|
||||
document.options.a11y.mouseMagnifier = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'highlight':
|
||||
switch (value) {
|
||||
case 'None':
|
||||
document.options.a11y.highlight = value;
|
||||
document.options.a11y.hover = false;
|
||||
document.options.a11y.flame = false;
|
||||
break;
|
||||
case 'Hover':
|
||||
document.options.a11y.highlight = value;
|
||||
document.options.a11y.hover = true;
|
||||
document.options.a11y.flame = false;
|
||||
break;
|
||||
case 'Flame':
|
||||
document.options.a11y.highlight = value;
|
||||
document.options.a11y.hover = false;
|
||||
document.options.a11y.flame = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
document.options.a11y[option] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Values for the ClearSpeak preference variables.
|
||||
*/
|
||||
let csPrefsSetting: {[pref: string]: string} = {};
|
||||
|
||||
/**
|
||||
* Generator of all variables for the Clearspeak Preference settings.
|
||||
* @param {MJContextMenu} menu The current context menu.
|
||||
* @param {string[]} prefs The preferences.
|
||||
*/
|
||||
let csPrefsVariables = function(menu: MJContextMenu, prefs: string[]) {
|
||||
let srVariable = menu.pool.lookup('speechRules');
|
||||
for (let pref of prefs) {
|
||||
if (csPrefsSetting[pref]) continue;
|
||||
menu.factory.get('variable')(menu.factory, {
|
||||
name: 'csprf_' + pref,
|
||||
setter: (value: string) => {
|
||||
csPrefsSetting[pref] = value;
|
||||
srVariable.setValue(
|
||||
'clearspeak-' +
|
||||
Sre.clearspeakPreferences.addPreference(
|
||||
Sre.clearspeakStyle(), pref, value)
|
||||
);
|
||||
},
|
||||
getter: () => { return csPrefsSetting[pref] || 'Auto'; }
|
||||
}, menu.pool);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate the selection box for the Clearspeak Preferences.
|
||||
* @param {MJContextMenu} menu The current context menu.
|
||||
* @param {string} locale The current locale.
|
||||
*/
|
||||
let csSelectionBox = function(menu: MJContextMenu, locale: string) {
|
||||
let prefs = Sre.clearspeakPreferences.getLocalePreferences();
|
||||
let props = prefs[locale];
|
||||
if (!props) {
|
||||
let csEntry = menu.findID('Accessibility', 'Speech', 'Clearspeak');
|
||||
if (csEntry) {
|
||||
csEntry.disable();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
csPrefsVariables(menu, Object.keys(props));
|
||||
let items = [];
|
||||
for (const prop of Object.getOwnPropertyNames(props)) {
|
||||
items.push({
|
||||
'title': prop,
|
||||
'values': props[prop].map(x => x.replace(RegExp('^' + prop + '_'), '')),
|
||||
'variable': 'csprf_' + prop
|
||||
});
|
||||
}
|
||||
let sb = menu.factory.get('selectionBox')(menu.factory, {
|
||||
'title': 'Clearspeak Preferences',
|
||||
'signature': '',
|
||||
'order': 'alphabetic',
|
||||
'grid': 'square',
|
||||
'selections': items
|
||||
}, menu);
|
||||
return {'type': 'command',
|
||||
'id': 'ClearspeakPreferences',
|
||||
'content': 'Select Preferences',
|
||||
'action': () => sb.post(0, 0)};
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates dynamic clearspeak menu.
|
||||
* @param {MJContextMenu} menu The context menu.
|
||||
* @param {Submenu} sub The submenu to attach.
|
||||
*/
|
||||
let csMenu = function(menu: MJContextMenu, sub: Submenu) {
|
||||
let locale = menu.pool.lookup('locale').getValue() as string;
|
||||
const box = csSelectionBox(menu, locale);
|
||||
let items: Object[] = [];
|
||||
try {
|
||||
items = Sre.clearspeakPreferences.smartPreferences(
|
||||
menu.mathItem, locale);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
if (box) {
|
||||
items.splice(2, 0, box);
|
||||
}
|
||||
return menu.factory.get('subMenu')(menu.factory, {
|
||||
items: items,
|
||||
id: 'Clearspeak'
|
||||
}, sub);
|
||||
};
|
||||
|
||||
MJContextMenu.DynamicSubmenus.set('Clearspeak', csMenu);
|
||||
|
||||
/**
|
||||
* Creates dynamic locale menu.
|
||||
* @param {MJContextMenu} menu The context menu.
|
||||
* @param {Submenu} sub The submenu to attach.
|
||||
*/
|
||||
let language = function(menu: MJContextMenu, sub: Submenu) {
|
||||
let radios: {type: string, id: string,
|
||||
content: string, variable: string}[] = [];
|
||||
for (let lang of Sre.locales.keys()) {
|
||||
if (lang === 'nemeth') continue;
|
||||
radios.push({type: 'radio', id: lang,
|
||||
content: Sre.locales.get(lang) || lang, variable: 'locale'});
|
||||
}
|
||||
radios.sort((x, y) => x.content.localeCompare(y.content, 'en'));
|
||||
return menu.factory.get('subMenu')(menu.factory, {
|
||||
items: radios, id: 'Language'}, sub);
|
||||
};
|
||||
|
||||
MJContextMenu.DynamicSubmenus.set('A11yLanguage', language);
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* Copyright (c) 2009-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 Explorers for A11Y purposes.
|
||||
*
|
||||
* @author v.sorge@mathjax.org (Volker Sorge)
|
||||
*/
|
||||
|
||||
|
||||
import {A11yDocument, Region} from './Region.js';
|
||||
import Sre from '../sre.js';
|
||||
|
||||
/**
|
||||
* A11y explorers.
|
||||
* @interface
|
||||
*/
|
||||
export interface Explorer {
|
||||
|
||||
/**
|
||||
* Flag indicating if the explorer is active.
|
||||
* @type {boolean}
|
||||
*/
|
||||
active: boolean;
|
||||
|
||||
/**
|
||||
* Flag indicating if event bubbling is stopped.
|
||||
* @type {boolean}
|
||||
*/
|
||||
stoppable: boolean;
|
||||
|
||||
/**
|
||||
* Attaches navigator and its event handlers to a node.
|
||||
*/
|
||||
Attach(): void;
|
||||
|
||||
/**
|
||||
* Detaches navigator and its event handlers to a node.
|
||||
*/
|
||||
Detach(): void;
|
||||
|
||||
/**
|
||||
* Starts the explorer.
|
||||
*/
|
||||
Start(): void;
|
||||
|
||||
/**
|
||||
* Stops the explorer.
|
||||
*/
|
||||
Stop(): void;
|
||||
|
||||
|
||||
/**
|
||||
* Adds the events of the explorer to the node's event listener.
|
||||
*/
|
||||
AddEvents(): void;
|
||||
|
||||
/**
|
||||
* Removes the events of the explorer from the node's event listener.
|
||||
*/
|
||||
RemoveEvents(): void;
|
||||
|
||||
/**
|
||||
* Update the explorer after state changes.
|
||||
* @param {boolean=} force Forces the update in any case. (E.g., even if
|
||||
* explorer is inactive.)
|
||||
*/
|
||||
Update(force?: boolean): void;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class implementing the very basic explorer functionality.
|
||||
*
|
||||
* Explorers use creator pattern to ensure they automatically attach themselves
|
||||
* to their node. This class provides the create method and is consequently not
|
||||
* declared abstract.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {Explorer}
|
||||
*
|
||||
* @template T The type that is consumed by the Region of this explorer.
|
||||
*/
|
||||
export class AbstractExplorer<T> implements Explorer {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public stoppable: boolean = true;
|
||||
|
||||
/**
|
||||
* Named events and their functions.
|
||||
* @type {[string, function(x: Event)][]}
|
||||
*/
|
||||
protected events: [string, (x: Event) => void][] = [];
|
||||
|
||||
/**
|
||||
* The Sre highlighter associated with the walker.
|
||||
* @type {Sre.highlighter}
|
||||
*/
|
||||
protected highlighter: Sre.highlighter = this.getHighlighter();
|
||||
|
||||
/**
|
||||
* Flag if explorer is active.
|
||||
* @type {boolean}
|
||||
*/
|
||||
private _active: boolean = false;
|
||||
|
||||
/**
|
||||
* Stops event bubbling.
|
||||
* @param {Event} event The event that is stopped.
|
||||
*/
|
||||
protected static stopEvent(event: Event) {
|
||||
if (event.preventDefault) {
|
||||
event.preventDefault();
|
||||
} else {
|
||||
event.returnValue = false;
|
||||
}
|
||||
if (event.stopImmediatePropagation) {
|
||||
event.stopImmediatePropagation();
|
||||
} else if (event.stopPropagation) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
event.cancelBubble = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creator pattern for explorers.
|
||||
* @param {A11yDocument} document The current document.
|
||||
* @param {Region<T>} region A region to display results.
|
||||
* @param {HTMLElement} node The node on which the explorer works.
|
||||
* @param {any[]} ...rest Remaining information.
|
||||
* @return {Explorer} An object of the particular explorer class.
|
||||
*
|
||||
* @template T
|
||||
*/
|
||||
public static create<T>(
|
||||
document: A11yDocument,
|
||||
region: Region<T>,
|
||||
node: HTMLElement, ...rest: any[]
|
||||
): Explorer {
|
||||
let explorer = new this(document, region, node, ...rest);
|
||||
return explorer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {A11yDocument} document The current document.
|
||||
* @param {Region<T>} region A region to display results.
|
||||
* @param {HTMLElement} node The node on which the explorer works.
|
||||
* @param {any[]} ...rest Remaining information.
|
||||
*/
|
||||
protected constructor(
|
||||
public document: A11yDocument,
|
||||
protected region: Region<T>,
|
||||
protected node: HTMLElement, ..._rest: any[]
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return {[string, (x: Event) => void][]} The events associated with this
|
||||
* explorer.
|
||||
*/
|
||||
protected Events(): [string, (x: Event) => void][] {
|
||||
return this.events;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get active(): boolean {
|
||||
return this._active;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public set active(flag: boolean) {
|
||||
this._active = flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Attach() {
|
||||
this.AddEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Detach() {
|
||||
this.RemoveEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Start() {
|
||||
this.highlighter = this.getHighlighter();
|
||||
this.active = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Stop() {
|
||||
if (this.active) {
|
||||
this.region.Clear();
|
||||
this.region.Hide();
|
||||
this.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public AddEvents() {
|
||||
for (let [eventkind, eventfunc] of this.events) {
|
||||
this.node.addEventListener(eventkind, eventfunc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public RemoveEvents() {
|
||||
for (let [eventkind, eventfunc] of this.events) {
|
||||
this.node.removeEventListener(eventkind, eventfunc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
// @ts-ignore: unused variable
|
||||
public Update(force: boolean = false): void {}
|
||||
|
||||
|
||||
/**
|
||||
* @return {Sre.Highlighter} A highlighter for the explorer.
|
||||
*/
|
||||
protected getHighlighter(): Sre.highlighter {
|
||||
let opts = this.document.options.a11y;
|
||||
let foreground = {color: opts.foregroundColor.toLowerCase(),
|
||||
alpha: opts.foregroundOpacity / 100};
|
||||
let background = {color: opts.backgroundColor.toLowerCase(),
|
||||
alpha: opts.backgroundOpacity / 100};
|
||||
return Sre.getHighlighter(
|
||||
background, foreground,
|
||||
{renderer: this.document.outputJax.name, browser: 'v3'});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the events of this explorer from bubbling.
|
||||
* @param {Event} event The event to stop.
|
||||
*/
|
||||
protected stopEvent(event: Event) {
|
||||
if (this.stoppable) {
|
||||
AbstractExplorer.stopEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+460
@@ -0,0 +1,460 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* Copyright (c) 2009-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 Explorers based on keyboard events.
|
||||
*
|
||||
* @author v.sorge@mathjax.org (Volker Sorge)
|
||||
*/
|
||||
|
||||
|
||||
import {A11yDocument, Region} from './Region.js';
|
||||
import {Explorer, AbstractExplorer} from './Explorer.js';
|
||||
import Sre from '../sre.js';
|
||||
|
||||
|
||||
/**
|
||||
* Interface for keyboard explorers. Adds the necessary keyboard events.
|
||||
* @interface
|
||||
* @extends {Explorer}
|
||||
*/
|
||||
export interface KeyExplorer extends Explorer {
|
||||
|
||||
/**
|
||||
* Function to be executed on key down.
|
||||
* @param {KeyboardEvent} event The keyboard event.
|
||||
*/
|
||||
KeyDown(event: KeyboardEvent): void;
|
||||
|
||||
/**
|
||||
* Function to be executed on focus in.
|
||||
* @param {KeyboardEvent} event The keyboard event.
|
||||
*/
|
||||
FocusIn(event: FocusEvent): void;
|
||||
|
||||
/**
|
||||
* Function to be executed on focus out.
|
||||
* @param {KeyboardEvent} event The keyboard event.
|
||||
*/
|
||||
FocusOut(event: FocusEvent): void;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @extends {AbstractExplorer}
|
||||
*
|
||||
* @template T The type that is consumed by the Region of this explorer.
|
||||
*/
|
||||
export abstract class AbstractKeyExplorer<T> extends AbstractExplorer<T> implements KeyExplorer {
|
||||
|
||||
/**
|
||||
* Flag indicating if the explorer is attached to an object.
|
||||
*/
|
||||
public attached: boolean = false;
|
||||
|
||||
/**
|
||||
* The attached Sre walker.
|
||||
* @type {Walker}
|
||||
*/
|
||||
protected walker: Sre.walker;
|
||||
|
||||
private eventsAttached: boolean = false;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
protected events: [string, (x: Event) => void][] =
|
||||
super.Events().concat(
|
||||
[['keydown', this.KeyDown.bind(this)],
|
||||
['focusin', this.FocusIn.bind(this)],
|
||||
['focusout', this.FocusOut.bind(this)]]);
|
||||
|
||||
/**
|
||||
* The original tabindex value before explorer was attached.
|
||||
* @type {boolean}
|
||||
*/
|
||||
private oldIndex: number = null;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract KeyDown(event: KeyboardEvent): void;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public FocusIn(_event: FocusEvent) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public FocusOut(_event: FocusEvent) {
|
||||
this.Stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Update(force: boolean = false) {
|
||||
if (!this.active && !force) return;
|
||||
this.highlighter.unhighlight();
|
||||
let nodes = this.walker.getFocus(true).getNodes();
|
||||
if (!nodes.length) {
|
||||
this.walker.refocus();
|
||||
nodes = this.walker.getFocus().getNodes();
|
||||
}
|
||||
this.highlighter.highlight(nodes as HTMLElement[]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Attach() {
|
||||
super.Attach();
|
||||
this.attached = true;
|
||||
this.oldIndex = this.node.tabIndex;
|
||||
this.node.tabIndex = 1;
|
||||
this.node.setAttribute('role', 'application');
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public AddEvents() {
|
||||
if (!this.eventsAttached) {
|
||||
super.AddEvents();
|
||||
this.eventsAttached = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Detach() {
|
||||
if (this.active) {
|
||||
this.node.tabIndex = this.oldIndex;
|
||||
this.oldIndex = null;
|
||||
this.node.removeAttribute('role');
|
||||
}
|
||||
this.attached = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Stop() {
|
||||
if (this.active) {
|
||||
this.highlighter.unhighlight();
|
||||
this.walker.deactivate();
|
||||
}
|
||||
super.Stop();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Explorer that pushes speech to live region.
|
||||
* @constructor
|
||||
* @extends {AbstractKeyExplorer}
|
||||
*/
|
||||
export class SpeechExplorer extends AbstractKeyExplorer<string> {
|
||||
|
||||
private static updatePromise = Promise.resolve();
|
||||
|
||||
/**
|
||||
* The Sre speech generator associated with the walker.
|
||||
* @type {SpeechGenerator}
|
||||
*/
|
||||
public speechGenerator: Sre.speechGenerator;
|
||||
|
||||
/**
|
||||
* The name of the option used to control when this is being shown
|
||||
* @type {string}
|
||||
*/
|
||||
public showRegion: string = 'subtitles';
|
||||
|
||||
private init: boolean = false;
|
||||
|
||||
/**
|
||||
* Flag in case the start method is triggered before the walker is fully
|
||||
* initialised. I.e., we have to wait for Sre. Then region is re-shown if
|
||||
* necessary, as otherwise it leads to incorrect stacking.
|
||||
* @type {boolean}
|
||||
*/
|
||||
private restarted: boolean = false;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @extends {AbstractKeyExplorer}
|
||||
*/
|
||||
constructor(public document: A11yDocument,
|
||||
protected region: Region<string>,
|
||||
protected node: HTMLElement,
|
||||
private mml: string) {
|
||||
super(document, region, node);
|
||||
this.initWalker();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Start() {
|
||||
if (!this.attached) return;
|
||||
let options = this.getOptions();
|
||||
if (!this.init) {
|
||||
this.init = true;
|
||||
SpeechExplorer.updatePromise = SpeechExplorer.updatePromise.then(async () => {
|
||||
return Sre.sreReady()
|
||||
.then(() => Sre.setupEngine({locale: options.locale}))
|
||||
.then(() => {
|
||||
// Important that both are in the same block so speech explorers
|
||||
// are restarted sequentially.
|
||||
this.Speech(this.walker);
|
||||
this.Start();
|
||||
});
|
||||
})
|
||||
.catch((error: Error) => console.log(error.message));
|
||||
return;
|
||||
}
|
||||
super.Start();
|
||||
this.speechGenerator = Sre.getSpeechGenerator('Direct');
|
||||
this.speechGenerator.setOptions(options);
|
||||
this.walker = Sre.getWalker(
|
||||
'table', this.node, this.speechGenerator, this.highlighter, this.mml);
|
||||
this.walker.activate();
|
||||
this.Update();
|
||||
if (this.document.options.a11y[this.showRegion]) {
|
||||
SpeechExplorer.updatePromise.then(
|
||||
() => this.region.Show(this.node, this.highlighter));
|
||||
}
|
||||
this.restarted = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Update(force: boolean = false) {
|
||||
super.Update(force);
|
||||
let options = this.speechGenerator.getOptions();
|
||||
// This is a necessary in case speech options have changed via keypress
|
||||
// during walking.
|
||||
if (options.modality === 'speech') {
|
||||
this.document.options.sre.domain = options.domain;
|
||||
this.document.options.sre.style = options.style;
|
||||
this.document.options.a11y.speechRules =
|
||||
options.domain + '-' + options.style;
|
||||
}
|
||||
SpeechExplorer.updatePromise = SpeechExplorer.updatePromise.then(async () => {
|
||||
return Sre.sreReady()
|
||||
.then(() => Sre.setupEngine({modality: options.modality,
|
||||
locale: options.locale}))
|
||||
.then(() => this.region.Update(this.walker.speech()));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Computes the speech for the current expression once Sre is ready.
|
||||
* @param {Walker} walker The sre walker.
|
||||
*/
|
||||
public Speech(walker: Sre.walker) {
|
||||
SpeechExplorer.updatePromise.then(() => {
|
||||
walker.speech();
|
||||
this.node.setAttribute('hasspeech', 'true');
|
||||
this.Update();
|
||||
if (this.restarted && this.document.options.a11y[this.showRegion]) {
|
||||
this.region.Show(this.node, this.highlighter);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public KeyDown(event: KeyboardEvent) {
|
||||
const code = event.keyCode;
|
||||
this.walker.modifier = event.shiftKey;
|
||||
if (code === 27) {
|
||||
this.Stop();
|
||||
this.stopEvent(event);
|
||||
return;
|
||||
}
|
||||
if (this.active) {
|
||||
this.Move(code);
|
||||
if (this.triggerLink(code)) return;
|
||||
this.stopEvent(event);
|
||||
return;
|
||||
}
|
||||
if (code === 32 && event.shiftKey || code === 13) {
|
||||
this.Start();
|
||||
this.stopEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Programmatically triggers a link if the focused node contains one.
|
||||
* @param {number} code The keycode of the last key pressed.
|
||||
*/
|
||||
protected triggerLink(code: number) {
|
||||
if (code !== 13) {
|
||||
return false;
|
||||
}
|
||||
let node = this.walker.getFocus().getNodes()?.[0];
|
||||
let focus = node?.
|
||||
getAttribute('data-semantic-postfix')?.
|
||||
match(/(^| )link($| )/);
|
||||
if (focus) {
|
||||
node.parentNode.dispatchEvent(new MouseEvent('click'));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Move(key: number) {
|
||||
this.walker.move(key);
|
||||
this.Update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises the Sre walker.
|
||||
*/
|
||||
private initWalker() {
|
||||
this.speechGenerator = Sre.getSpeechGenerator('Tree');
|
||||
let dummy = Sre.getWalker(
|
||||
'dummy', this.node, this.speechGenerator, this.highlighter, this.mml);
|
||||
this.walker = dummy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the speech options to sync with document options.
|
||||
* @return {{[key: string]: string}} The options settings for the speech
|
||||
* generator.
|
||||
*/
|
||||
private getOptions(): {[key: string]: string} {
|
||||
let options = this.speechGenerator.getOptions();
|
||||
let sreOptions = this.document.options.sre;
|
||||
if (options.modality === 'speech' &&
|
||||
(options.locale !== sreOptions.locale ||
|
||||
options.domain !== sreOptions.domain ||
|
||||
options.style !== sreOptions.style)) {
|
||||
options.domain = sreOptions.domain;
|
||||
options.style = sreOptions.style;
|
||||
options.locale = sreOptions.locale;
|
||||
this.walker.update(options);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Explorer that magnifies what is currently explored. Uses a hover region.
|
||||
* @constructor
|
||||
* @extends {AbstractKeyExplorer}
|
||||
*/
|
||||
export class Magnifier extends AbstractKeyExplorer<HTMLElement> {
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @extends {AbstractKeyExplorer}
|
||||
*/
|
||||
constructor(public document: A11yDocument,
|
||||
protected region: Region<HTMLElement>,
|
||||
protected node: HTMLElement,
|
||||
private mml: string) {
|
||||
super(document, region, node);
|
||||
this.walker = Sre.getWalker(
|
||||
'table', this.node, Sre.getSpeechGenerator('Dummy'),
|
||||
this.highlighter, this.mml);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Update(force: boolean = false) {
|
||||
super.Update(force);
|
||||
this.showFocus();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Start() {
|
||||
super.Start();
|
||||
if (!this.attached) return;
|
||||
this.region.Show(this.node, this.highlighter);
|
||||
this.walker.activate();
|
||||
this.Update();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shows the nodes that are currently focused.
|
||||
*/
|
||||
private showFocus() {
|
||||
let node = this.walker.getFocus().getNodes()[0] as HTMLElement;
|
||||
this.region.Show(node, this.highlighter);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Move(key: number) {
|
||||
let result = this.walker.move(key);
|
||||
if (result) {
|
||||
this.Update();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public KeyDown(event: KeyboardEvent) {
|
||||
const code = event.keyCode;
|
||||
this.walker.modifier = event.shiftKey;
|
||||
if (code === 27) {
|
||||
this.Stop();
|
||||
this.stopEvent(event);
|
||||
return;
|
||||
}
|
||||
if (this.active && code !== 13) {
|
||||
this.Move(code);
|
||||
this.stopEvent(event);
|
||||
return;
|
||||
}
|
||||
if (code === 32 && event.shiftKey || code === 13) {
|
||||
this.Start();
|
||||
this.stopEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* Copyright (c) 2009-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 Explorers based on mouse events.
|
||||
*
|
||||
* @author v.sorge@mathjax.org (Volker Sorge)
|
||||
*/
|
||||
|
||||
|
||||
import {A11yDocument, DummyRegion, Region} from './Region.js';
|
||||
import {Explorer, AbstractExplorer} from './Explorer.js';
|
||||
import '../sre.js';
|
||||
|
||||
|
||||
/**
|
||||
* Interface for mouse explorers. Adds the necessary mouse events.
|
||||
* @interface
|
||||
* @extends {Explorer}
|
||||
*/
|
||||
export interface MouseExplorer extends Explorer {
|
||||
|
||||
/**
|
||||
* Function to be executed on mouse over.
|
||||
* @param {MouseEvent} event The mouse event.
|
||||
*/
|
||||
MouseOver(event: MouseEvent): void;
|
||||
|
||||
/**
|
||||
* Function to be executed on mouse out.
|
||||
* @param {MouseEvent} event The mouse event.
|
||||
*/
|
||||
MouseOut(event: MouseEvent): void;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @extends {AbstractExplorer}
|
||||
*
|
||||
* @template T The type that is consumed by the Region of this explorer.
|
||||
*/
|
||||
export abstract class AbstractMouseExplorer<T> extends AbstractExplorer<T> implements MouseExplorer {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
protected events: [string, (x: Event) => void][] =
|
||||
super.Events().concat([
|
||||
['mouseover', this.MouseOver.bind(this)],
|
||||
['mouseout', this.MouseOut.bind(this)]
|
||||
]);
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public MouseOver(_event: MouseEvent) {
|
||||
this.Start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public MouseOut(_event: MouseEvent) {
|
||||
this.Stop();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Exploration via hovering.
|
||||
* @constructor
|
||||
* @extends {AbstractMouseExplorer}
|
||||
*/
|
||||
export abstract class Hoverer<T> extends AbstractMouseExplorer<T> {
|
||||
|
||||
/**
|
||||
* Remember the last position to avoid flickering.
|
||||
* @type {[number, number]}
|
||||
*/
|
||||
protected coord: [number, number];
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @extends {AbstractMouseExplorer<T>}
|
||||
*
|
||||
* @param {A11yDocument} document The current document.
|
||||
* @param {Region<T>} region A region to display results.
|
||||
* @param {HTMLElement} node The node on which the explorer works.
|
||||
* @param {(node: HTMLElement) => boolean} nodeQuery Predicate on nodes that
|
||||
* will fire the hoverer.
|
||||
* @param {(node: HTMLElement) => T} nodeAccess Accessor to extract node value
|
||||
* that is passed to the region.
|
||||
*
|
||||
* @template T
|
||||
*/
|
||||
protected constructor(public document: A11yDocument,
|
||||
protected region: Region<T>,
|
||||
protected node: HTMLElement,
|
||||
protected nodeQuery: (node: HTMLElement) => boolean,
|
||||
protected nodeAccess: (node: HTMLElement) => T) {
|
||||
super(document, region, node);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public MouseOut(event: MouseEvent) {
|
||||
if (event.clientX === this.coord[0] &&
|
||||
event.clientY === this.coord[1]) {
|
||||
return;
|
||||
}
|
||||
this.highlighter.unhighlight();
|
||||
this.region.Hide();
|
||||
super.MouseOut(event);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public MouseOver(event: MouseEvent) {
|
||||
super.MouseOver(event);
|
||||
let target = event.target as HTMLElement;
|
||||
this.coord = [event.clientX, event.clientY];
|
||||
let [node, kind] = this.getNode(target);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
this.highlighter.unhighlight();
|
||||
this.highlighter.highlight([node]);
|
||||
this.region.Update(kind);
|
||||
this.region.Show(node, this.highlighter);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the closest node on which the node query fires. Thereby closest
|
||||
* is defined as:
|
||||
* 1. The node or its ancestor on which the query is true.
|
||||
* 2. In case 1 does not exist the left-most child on which query is true.
|
||||
* 3. Otherwise fails.
|
||||
*
|
||||
* @param {HTMLElement} node The node on which the mouse event fired.
|
||||
* @return {[HTMLElement, T]} Node and output pair if successful.
|
||||
*/
|
||||
public getNode(node: HTMLElement): [HTMLElement, T] {
|
||||
let original = node;
|
||||
while (node && node !== this.node) {
|
||||
if (this.nodeQuery(node)) {
|
||||
return [node, this.nodeAccess(node)];
|
||||
}
|
||||
node = node.parentNode as HTMLElement;
|
||||
}
|
||||
node = original;
|
||||
while (node) {
|
||||
if (this.nodeQuery(node)) {
|
||||
return [node, this.nodeAccess(node)];
|
||||
}
|
||||
let child = node.childNodes[0] as HTMLElement;
|
||||
node = (child && child.tagName === 'defs') ? // This is for SVG.
|
||||
node.childNodes[1] as HTMLElement : child;
|
||||
}
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Hoverer that displays information on nodes (e.g., as tooltips).
|
||||
* @constructor
|
||||
* @extends {Hoverer}
|
||||
*/
|
||||
export class ValueHoverer extends Hoverer<string> { }
|
||||
|
||||
|
||||
/**
|
||||
* Hoverer that displays node content (e.g., for magnification).
|
||||
* @constructor
|
||||
* @extends {Hoverer}
|
||||
*/
|
||||
export class ContentHoverer extends Hoverer<HTMLElement> { }
|
||||
|
||||
|
||||
/**
|
||||
* Highlights maction nodes on hovering.
|
||||
* @constructor
|
||||
* @extends {Hoverer}
|
||||
*/
|
||||
export class FlameHoverer extends Hoverer<void> {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
protected constructor(
|
||||
public document: A11yDocument,
|
||||
_ignore: any,
|
||||
protected node: HTMLElement) {
|
||||
super(document, new DummyRegion(document), node,
|
||||
x => this.highlighter.isMactionNode(x),
|
||||
() => {});
|
||||
}
|
||||
|
||||
}
|
||||
+519
@@ -0,0 +1,519 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* Copyright (c) 2009-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 Regions for A11y purposes.
|
||||
*
|
||||
* @author v.sorge@mathjax.org (Volker Sorge)
|
||||
*/
|
||||
|
||||
|
||||
import {MathDocument} from '../../core/MathDocument.js';
|
||||
import {CssStyles} from '../../util/StyleList.js';
|
||||
import Sre from '../sre.js';
|
||||
|
||||
export type A11yDocument = MathDocument<HTMLElement, Text, Document>;
|
||||
|
||||
export interface Region<T> {
|
||||
|
||||
/**
|
||||
* Adds a style sheet for the live region to the document.
|
||||
*/
|
||||
AddStyles(): void;
|
||||
|
||||
/**
|
||||
* Adds the region element to the document.
|
||||
*/
|
||||
AddElement(): void;
|
||||
|
||||
/**
|
||||
* Shows the live region in the document.
|
||||
* @param {HTMLElement} node
|
||||
* @param {Sre.highlighter} highlighter
|
||||
*/
|
||||
Show(node: HTMLElement, highlighter: Sre.highlighter): void;
|
||||
|
||||
/**
|
||||
* Takes the element out of the document flow.
|
||||
*/
|
||||
Hide(): void;
|
||||
|
||||
/**
|
||||
* Clears the content of the region.
|
||||
*/
|
||||
Clear(): void;
|
||||
|
||||
/**
|
||||
* Updates the content of the region.
|
||||
* @template T
|
||||
*/
|
||||
Update(content: T): void;
|
||||
|
||||
}
|
||||
|
||||
export abstract class AbstractRegion<T> implements Region<T> {
|
||||
|
||||
/**
|
||||
* CSS Classname of the element.
|
||||
* @type {String}
|
||||
*/
|
||||
protected static className: string;
|
||||
|
||||
/**
|
||||
* True if the style has already been added to the document.
|
||||
* @type {boolean}
|
||||
*/
|
||||
protected static styleAdded: boolean = false;
|
||||
|
||||
/**
|
||||
* The CSS style that needs to be added for this type of region.
|
||||
* @type {CssStyles}
|
||||
*/
|
||||
protected static style: CssStyles;
|
||||
|
||||
/**
|
||||
* The outer div node.
|
||||
* @type {HTMLElement}
|
||||
*/
|
||||
protected div: HTMLElement;
|
||||
|
||||
/**
|
||||
* The inner node.
|
||||
* @type {HTMLElement}
|
||||
*/
|
||||
protected inner: HTMLElement;
|
||||
|
||||
/**
|
||||
* The actual class name to refer to static elements of a class.
|
||||
* @type {typeof AbstractRegion}
|
||||
*/
|
||||
protected CLASS: typeof AbstractRegion;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {A11yDocument} document The document the live region is added to.
|
||||
*/
|
||||
constructor(public document: A11yDocument) {
|
||||
this.CLASS = this.constructor as typeof AbstractRegion;
|
||||
this.AddStyles();
|
||||
this.AddElement();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public AddStyles() {
|
||||
if (this.CLASS.styleAdded) {
|
||||
return;
|
||||
}
|
||||
// TODO: should that be added to document.documentStyleSheet()?
|
||||
let node = this.document.adaptor.node('style');
|
||||
node.innerHTML = this.CLASS.style.cssText;
|
||||
this.document.adaptor.head(this.document.adaptor.document).
|
||||
appendChild(node);
|
||||
this.CLASS.styleAdded = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public AddElement() {
|
||||
let element = this.document.adaptor.node('div');
|
||||
element.classList.add(this.CLASS.className);
|
||||
element.style.backgroundColor = 'white';
|
||||
this.div = element;
|
||||
this.inner = this.document.adaptor.node('div');
|
||||
this.div.appendChild(this.inner);
|
||||
this.document.adaptor.
|
||||
body(this.document.adaptor.document).
|
||||
appendChild(this.div);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Show(node: HTMLElement, highlighter: Sre.highlighter) {
|
||||
this.position(node);
|
||||
this.highlight(highlighter);
|
||||
this.div.classList.add(this.CLASS.className + '_Show');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Computes the position where to place the element wrt. to the given node.
|
||||
* @param {HTMLElement} node The reference node.
|
||||
*/
|
||||
protected abstract position(node: HTMLElement): void;
|
||||
|
||||
|
||||
/**
|
||||
* Highlights the region.
|
||||
* @param {Sre.highlighter} highlighter The Sre highlighter.
|
||||
*/
|
||||
protected abstract highlight(highlighter: Sre.highlighter): void;
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Hide() {
|
||||
this.div.classList.remove(this.CLASS.className + '_Show');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract Clear(): void;
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract Update(content: T): void;
|
||||
|
||||
|
||||
/**
|
||||
* Auxiliary position method that stacks shown regions of the same type.
|
||||
* @param {HTMLElement} node The reference node.
|
||||
*/
|
||||
protected stackRegions(node: HTMLElement) {
|
||||
// TODO: This could be made more efficient by caching regions of a class.
|
||||
const rect = node.getBoundingClientRect();
|
||||
let baseBottom = 0;
|
||||
let baseLeft = Number.POSITIVE_INFINITY;
|
||||
let regions = this.document.adaptor.document.getElementsByClassName(
|
||||
this.CLASS.className + '_Show');
|
||||
// Get all the shown regions (one is this element!) and append at bottom.
|
||||
for (let i = 0, region; region = regions[i]; i++) {
|
||||
if (region !== this.div) {
|
||||
baseBottom = Math.max(region.getBoundingClientRect().bottom, baseBottom);
|
||||
baseLeft = Math.min(region.getBoundingClientRect().left, baseLeft);
|
||||
}
|
||||
}
|
||||
const bot = (baseBottom ? baseBottom : rect.bottom + 10) + window.pageYOffset;
|
||||
const left = (baseLeft < Number.POSITIVE_INFINITY ? baseLeft : rect.left) + window.pageXOffset;
|
||||
this.div.style.top = bot + 'px';
|
||||
this.div.style.left = left + 'px';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class DummyRegion extends AbstractRegion<void> {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Clear() {}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Update() {}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Hide() {}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Show() {}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public AddElement() {}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public AddStyles() {}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public position() {}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public highlight(_highlighter: Sre.highlighter) {}
|
||||
}
|
||||
|
||||
|
||||
export class StringRegion extends AbstractRegion<string> {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Clear(): void {
|
||||
this.Update('');
|
||||
this.inner.style.top = '';
|
||||
this.inner.style.backgroundColor = '';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Update(speech: string) {
|
||||
this.inner.textContent = '';
|
||||
this.inner.textContent = speech;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
protected position(node: HTMLElement) {
|
||||
this.stackRegions(node);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
protected highlight(highlighter: Sre.highlighter) {
|
||||
const color = highlighter.colorString();
|
||||
this.inner.style.backgroundColor = color.background;
|
||||
this.inner.style.color = color.foreground;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class ToolTip extends StringRegion {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
protected static className = 'MJX_ToolTip';
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
protected static style: CssStyles =
|
||||
new CssStyles({
|
||||
['.' + ToolTip.className]: {
|
||||
position: 'absolute', display: 'inline-block',
|
||||
height: '1px', width: '1px'
|
||||
},
|
||||
['.' + ToolTip.className + '_Show']: {
|
||||
width: 'auto', height: 'auto', opacity: 1, 'text-align': 'center',
|
||||
'border-radius': '6px', padding: '0px 0px',
|
||||
'border-bottom': '1px dotted black', position: 'absolute',
|
||||
'z-index': 202
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class LiveRegion extends StringRegion {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
protected static className = 'MJX_LiveRegion';
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
protected static style: CssStyles =
|
||||
new CssStyles({
|
||||
['.' + LiveRegion.className]: {
|
||||
position: 'absolute', top: '0', height: '1px', width: '1px',
|
||||
padding: '1px', overflow: 'hidden'
|
||||
},
|
||||
['.' + LiveRegion.className + '_Show']: {
|
||||
top: '0', position: 'absolute', width: 'auto', height: 'auto',
|
||||
padding: '0px 0px', opacity: 1, 'z-index': '202',
|
||||
left: 0, right: 0, 'margin': '0 auto',
|
||||
'background-color': 'rgba(0, 0, 255, 0.2)', 'box-shadow': '0px 10px 20px #888',
|
||||
border: '2px solid #CCCCCC'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {A11yDocument} document The document the live region is added to.
|
||||
*/
|
||||
constructor(public document: A11yDocument) {
|
||||
super(document);
|
||||
this.div.setAttribute('aria-live', 'assertive');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Region that overlays the current element.
|
||||
export class HoverRegion extends AbstractRegion<HTMLElement> {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
protected static className = 'MJX_HoverRegion';
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
protected static style: CssStyles =
|
||||
new CssStyles({
|
||||
['.' + HoverRegion.className]: {
|
||||
position: 'absolute', height: '1px', width: '1px',
|
||||
padding: '1px', overflow: 'hidden'
|
||||
},
|
||||
['.' + HoverRegion.className + '_Show']: {
|
||||
position: 'absolute', width: 'max-content', height: 'auto',
|
||||
padding: '0px 0px', opacity: 1, 'z-index': '202', 'margin': '0 auto',
|
||||
'background-color': 'rgba(0, 0, 255, 0.2)',
|
||||
'box-shadow': '0px 10px 20px #888', border: '2px solid #CCCCCC'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {A11yDocument} document The document the live region is added to.
|
||||
*/
|
||||
constructor(public document: A11yDocument) {
|
||||
super(document);
|
||||
this.inner.style.lineHeight = '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the position of the region with respect to align parameter. There are
|
||||
* three options: top, bottom and center. Center is the default.
|
||||
*
|
||||
* @param {HTMLElement} node The node that is displayed.
|
||||
*/
|
||||
protected position(node: HTMLElement) {
|
||||
const nodeRect = node.getBoundingClientRect();
|
||||
const divRect = this.div.getBoundingClientRect();
|
||||
const xCenter = nodeRect.left + (nodeRect.width / 2);
|
||||
let left = xCenter - (divRect.width / 2);
|
||||
left = (left < 0) ? 0 : left;
|
||||
left = left + window.pageXOffset;
|
||||
let top;
|
||||
switch (this.document.options.a11y.align) {
|
||||
case 'top':
|
||||
top = nodeRect.top - divRect.height - 10 ;
|
||||
break;
|
||||
case 'bottom':
|
||||
top = nodeRect.bottom + 10;
|
||||
break;
|
||||
case 'center':
|
||||
default:
|
||||
const yCenter = nodeRect.top + (nodeRect.height / 2);
|
||||
top = yCenter - (divRect.height / 2);
|
||||
}
|
||||
top = top + window.pageYOffset;
|
||||
top = (top < 0) ? 0 : top;
|
||||
this.div.style.top = top + 'px';
|
||||
this.div.style.left = left + 'px';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
protected highlight(highlighter: Sre.highlighter) {
|
||||
// TODO Do this with styles to avoid the interaction of SVG/CHTML.
|
||||
if (this.inner.firstChild &&
|
||||
!(this.inner.firstChild as HTMLElement).hasAttribute('sre-highlight')) {
|
||||
return;
|
||||
}
|
||||
const color = highlighter.colorString();
|
||||
this.inner.style.backgroundColor = color.background;
|
||||
this.inner.style.color = color.foreground;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Show(node: HTMLElement, highlighter: Sre.highlighter) {
|
||||
this.div.style.fontSize = this.document.options.a11y.magnify;
|
||||
this.Update(node);
|
||||
super.Show(node, highlighter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Clear() {
|
||||
this.inner.textContent = '';
|
||||
this.inner.style.top = '';
|
||||
this.inner.style.backgroundColor = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Update(node: HTMLElement) {
|
||||
this.Clear();
|
||||
let mjx = this.cloneNode(node);
|
||||
this.inner.appendChild(mjx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the node to put into the hover region.
|
||||
* @param {HTMLElement} node The original node.
|
||||
* @return {HTMLElement} The cloned node.
|
||||
*/
|
||||
private cloneNode(node: HTMLElement): HTMLElement {
|
||||
let mjx = node.cloneNode(true) as HTMLElement;
|
||||
if (mjx.nodeName !== 'MJX-CONTAINER') {
|
||||
// remove element spacing (could be done in CSS)
|
||||
if (mjx.nodeName !== 'g') {
|
||||
mjx.style.marginLeft = mjx.style.marginRight = '0';
|
||||
}
|
||||
let container = node;
|
||||
while (container && container.nodeName !== 'MJX-CONTAINER') {
|
||||
container = container.parentNode as HTMLElement;
|
||||
}
|
||||
if (mjx.nodeName !== 'MJX-MATH' && mjx.nodeName !== 'svg') {
|
||||
const child = container.firstChild;
|
||||
mjx = child.cloneNode(false).appendChild(mjx).parentNode as HTMLElement;
|
||||
//
|
||||
// SVG specific
|
||||
//
|
||||
if (mjx.nodeName === 'svg') {
|
||||
(mjx.firstChild as HTMLElement).setAttribute('transform', 'matrix(1 0 0 -1 0 0)');
|
||||
const W = parseFloat(mjx.getAttribute('viewBox').split(/ /)[2]);
|
||||
const w = parseFloat(mjx.getAttribute('width'));
|
||||
const {x, y, width, height} = (node as any).getBBox();
|
||||
mjx.setAttribute('viewBox', [x, -(y + height), width, height].join(' '));
|
||||
mjx.removeAttribute('style');
|
||||
mjx.setAttribute('width', (w / W * width) + 'ex');
|
||||
mjx.setAttribute('height', (w / W * height) + 'ex');
|
||||
container.setAttribute('sre-highlight', 'false');
|
||||
}
|
||||
}
|
||||
mjx = container.cloneNode(false).appendChild(mjx).parentNode as HTMLElement;
|
||||
// remove displayed math margins (could be done in CSS)
|
||||
mjx.style.margin = '0';
|
||||
}
|
||||
return mjx;
|
||||
}
|
||||
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* Copyright (c) 2009-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 Tree Explorers allow to switch on effects on the entire
|
||||
* expression tree.
|
||||
*
|
||||
* @author v.sorge@mathjax.org (Volker Sorge)
|
||||
*/
|
||||
|
||||
|
||||
import {A11yDocument, Region} from './Region.js';
|
||||
import {Explorer, AbstractExplorer} from './Explorer.js';
|
||||
import Sre from '../sre.js';
|
||||
|
||||
export interface TreeExplorer extends Explorer {
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class AbstractTreeExplorer extends AbstractExplorer<void> {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
protected constructor(public document: A11yDocument,
|
||||
protected region: Region<void>,
|
||||
protected node: HTMLElement,
|
||||
protected mml: HTMLElement) {
|
||||
super(document, null, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public readonly stoppable = false;
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Attach() {
|
||||
super.Attach();
|
||||
this.Start();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Detach() {
|
||||
this.Stop();
|
||||
super.Detach();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class FlameColorer extends AbstractTreeExplorer {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Start() {
|
||||
if (this.active) return;
|
||||
this.active = true;
|
||||
this.highlighter.highlightAll(this.node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Stop() {
|
||||
if (this.active) {
|
||||
this.highlighter.unhighlightAll();
|
||||
}
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class TreeColorer extends AbstractTreeExplorer {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Start() {
|
||||
if (this.active) return;
|
||||
this.active = true;
|
||||
let generator = Sre.getSpeechGenerator('Color');
|
||||
if (!this.node.hasAttribute('hasforegroundcolor')) {
|
||||
generator.generateSpeech(this.node, this.mml);
|
||||
this.node.setAttribute('hasforegroundcolor', 'true');
|
||||
}
|
||||
// TODO: Make this cleaner in Sre.
|
||||
(this.highlighter as any).colorizeAll(this.node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public Stop() {
|
||||
if (this.active) {
|
||||
(this.highlighter as any).uncolorizeAll(this.node);
|
||||
}
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 Base imports of sre locales.
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
* @author v.sorge@mathjax.org (Volker Sorge)
|
||||
*/
|
||||
|
||||
const MathMaps: Map<string, {[path: string]: any}> = new Map();
|
||||
|
||||
export default MathMaps;
|
||||
+361
@@ -0,0 +1,361 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 Mixin that adds semantic enrichment to internal MathML
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {mathjax} from '../mathjax.js';
|
||||
import {Handler} from '../core/Handler.js';
|
||||
import {MathDocument, AbstractMathDocument, MathDocumentConstructor} from '../core/MathDocument.js';
|
||||
import {MathItem, AbstractMathItem, STATE, newState} from '../core/MathItem.js';
|
||||
import {MmlNode} from '../core/MmlTree/MmlNode.js';
|
||||
import {MathML} from '../input/mathml.js';
|
||||
import {SerializedMmlVisitor} from '../core/MmlTree/SerializedMmlVisitor.js';
|
||||
import {OptionList, expandable} from '../util/Options.js';
|
||||
import Sre from './sre.js';
|
||||
|
||||
/*==========================================================================*/
|
||||
|
||||
/**
|
||||
* The current speech setting for Sre
|
||||
*/
|
||||
let currentSpeech = 'none';
|
||||
|
||||
/**
|
||||
* Generic constructor for Mixins
|
||||
*/
|
||||
export type Constructor<T> = new(...args: any[]) => T;
|
||||
|
||||
/*==========================================================================*/
|
||||
|
||||
/**
|
||||
* Add STATE value for being enriched (after COMPILED and before TYPESET)
|
||||
*/
|
||||
newState('ENRICHED', 30);
|
||||
|
||||
/**
|
||||
* Add STATE value for adding speech (after TYPESET)
|
||||
*/
|
||||
newState('ATTACHSPEECH', 155);
|
||||
|
||||
|
||||
/**
|
||||
* The functions added to MathItem for enrichment
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export interface EnrichedMathItem<N, T, D> extends MathItem<N, T, D> {
|
||||
|
||||
/**
|
||||
* @param {MathDocument} document The document where enrichment is occurring
|
||||
* @param {boolean} force True to force the enrichment even if not enabled
|
||||
*/
|
||||
enrich(document: MathDocument<N, T, D>, force?: boolean): void;
|
||||
|
||||
/**
|
||||
* @param {MathDocument} document The document where enrichment is occurring
|
||||
*/
|
||||
attachSpeech(document: MathDocument<N, T, D>): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The mixin for adding enrichment to MathItems
|
||||
*
|
||||
* @param {B} BaseMathItem The MathItem class to be extended
|
||||
* @param {MathML} MmlJax The MathML input jax used to convert the enriched MathML
|
||||
* @param {Function} toMathML The function to serialize the internal MathML
|
||||
* @return {EnrichedMathItem} The enriched MathItem class
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
* @template B The MathItem class to extend
|
||||
*/
|
||||
export function EnrichedMathItemMixin<N, T, D, B extends Constructor<AbstractMathItem<N, T, D>>>(
|
||||
BaseMathItem: B,
|
||||
MmlJax: MathML<N, T, D>,
|
||||
toMathML: (node: MmlNode) => string
|
||||
): Constructor<EnrichedMathItem<N, T, D>> & B {
|
||||
|
||||
return class extends BaseMathItem {
|
||||
|
||||
/**
|
||||
* @param {any} node The node to be serialized
|
||||
* @return {string} The serialized version of node
|
||||
*/
|
||||
protected serializeMml(node: any): string {
|
||||
if ('outerHTML' in node) {
|
||||
return node.outerHTML;
|
||||
}
|
||||
//
|
||||
// For IE11
|
||||
//
|
||||
if (typeof Element !== 'undefined' && typeof window !== 'undefined' && node instanceof Element) {
|
||||
const div = window.document.createElement('div');
|
||||
div.appendChild(node);
|
||||
return div.innerHTML;
|
||||
}
|
||||
//
|
||||
// For NodeJS version of Sre
|
||||
//
|
||||
return node.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MathDocument} document The MathDocument for the MathItem
|
||||
* @param {boolean} force True to force the enrichment even if not enabled
|
||||
*/
|
||||
public enrich(document: MathDocument<N, T, D>, force: boolean = false) {
|
||||
if (this.state() >= STATE.ENRICHED) return;
|
||||
if (!this.isEscaped && (document.options.enableEnrichment || force)) {
|
||||
if (document.options.sre.speech !== currentSpeech) {
|
||||
currentSpeech = document.options.sre.speech;
|
||||
mathjax.retryAfter(
|
||||
Sre.setupEngine(document.options.sre).then(
|
||||
() => Sre.sreReady()));
|
||||
}
|
||||
const math = new document.options.MathItem('', MmlJax);
|
||||
try {
|
||||
const mml = this.inputData.originalMml = toMathML(this.root);
|
||||
math.math = this.serializeMml(Sre.toEnriched(mml));
|
||||
math.display = this.display;
|
||||
math.compile(document);
|
||||
this.root = math.root;
|
||||
this.inputData.enrichedMml = math.math;
|
||||
} catch (err) {
|
||||
document.options.enrichError(document, this, err);
|
||||
}
|
||||
}
|
||||
this.state(STATE.ENRICHED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MathDocument} document The MathDocument for the MathItem
|
||||
*/
|
||||
public attachSpeech(document: MathDocument<N, T, D>) {
|
||||
if (this.state() >= STATE.ATTACHSPEECH) return;
|
||||
const attributes = this.root.attributes;
|
||||
const speech = (attributes.get('aria-label') ||
|
||||
this.getSpeech(this.root)) as string;
|
||||
if (speech) {
|
||||
const adaptor = document.adaptor;
|
||||
const node = this.typesetRoot;
|
||||
adaptor.setAttribute(node, 'aria-label', speech);
|
||||
for (const child of adaptor.childNodes(node) as N[]) {
|
||||
adaptor.setAttribute(child, 'aria-hidden', 'true');
|
||||
}
|
||||
}
|
||||
this.state(STATE.ATTACHSPEECH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the actual speech element that should be used as aria label.
|
||||
* @param {MmlNode} node The root node to search from.
|
||||
* @return {string} The speech content.
|
||||
*/
|
||||
private getSpeech(node: MmlNode): string {
|
||||
const attributes = node.attributes;
|
||||
if (!attributes) return '';
|
||||
const speech = attributes.getExplicit('data-semantic-speech') as string;
|
||||
if (!attributes.getExplicit('data-semantic-parent') && speech) {
|
||||
return speech;
|
||||
}
|
||||
for (let child of node.childNodes) {
|
||||
let value = this.getSpeech(child as MmlNode);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/*==========================================================================*/
|
||||
|
||||
/**
|
||||
* The functions added to MathDocument for enrichment
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export interface EnrichedMathDocument<N, T, D> extends AbstractMathDocument<N, T, D> {
|
||||
|
||||
/**
|
||||
* Perform enrichment on the MathItems in the MathDocument
|
||||
*
|
||||
* @return {EnrichedMathDocument} The MathDocument (so calls can be chained)
|
||||
*/
|
||||
enrich(): EnrichedMathDocument<N, T, D>;
|
||||
|
||||
/**
|
||||
* Attach speech to the MathItems in the MathDocument
|
||||
*
|
||||
* @return {EnrichedMathDocument} The MathDocument (so calls can be chained)
|
||||
*/
|
||||
attachSpeech(): EnrichedMathDocument<N, T, D>;
|
||||
|
||||
/**
|
||||
* @param {EnrichedMathDocument} doc The MathDocument for the error
|
||||
* @paarm {EnrichedMathItem} math The MathItem causing the error
|
||||
* @param {Error} err The error being processed
|
||||
*/
|
||||
enrichError(doc: EnrichedMathDocument<N, T, D>, math: EnrichedMathItem<N, T, D>, err: Error): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The mixin for adding enrichment to MathDocuments
|
||||
*
|
||||
* @param {B} BaseDocument The MathDocument class to be extended
|
||||
* @param {MathML} MmlJax The MathML input jax used to convert the enriched MathML
|
||||
* @return {EnrichedMathDocument} The enriched MathDocument class
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
* @template B The MathDocument class to extend
|
||||
*/
|
||||
export function EnrichedMathDocumentMixin<N, T, D, B extends MathDocumentConstructor<AbstractMathDocument<N, T, D>>>(
|
||||
BaseDocument: B,
|
||||
MmlJax: MathML<N, T, D>,
|
||||
): MathDocumentConstructor<EnrichedMathDocument<N, T, D>> & B {
|
||||
|
||||
return class extends BaseDocument {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static OPTIONS: OptionList = {
|
||||
...BaseDocument.OPTIONS,
|
||||
enableEnrichment: true,
|
||||
enrichError: (doc: EnrichedMathDocument<N, T, D>,
|
||||
math: EnrichedMathItem<N, T, D>,
|
||||
err: Error) => doc.enrichError(doc, math, err),
|
||||
renderActions: expandable({
|
||||
...BaseDocument.OPTIONS.renderActions,
|
||||
enrich: [STATE.ENRICHED],
|
||||
attachSpeech: [STATE.ATTACHSPEECH]
|
||||
}),
|
||||
sre: expandable({
|
||||
speech: 'none', // by default no speech is included
|
||||
domain: 'mathspeak', // speech rules domain
|
||||
style: 'default', // speech rules style
|
||||
locale: 'en' // switch the locale
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
* Enrich the MathItem class used for this MathDocument, and create the
|
||||
* temporary MathItem used for enrchment
|
||||
*
|
||||
* @override
|
||||
* @constructor
|
||||
*/
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
MmlJax.setMmlFactory(this.mmlFactory);
|
||||
const ProcessBits = (this.constructor as typeof AbstractMathDocument).ProcessBits;
|
||||
if (!ProcessBits.has('enriched')) {
|
||||
ProcessBits.allocate('enriched');
|
||||
ProcessBits.allocate('attach-speech');
|
||||
}
|
||||
const visitor = new SerializedMmlVisitor(this.mmlFactory);
|
||||
const toMathML = ((node: MmlNode) => visitor.visitTree(node));
|
||||
this.options.MathItem =
|
||||
EnrichedMathItemMixin<N, T, D, Constructor<AbstractMathItem<N, T, D>>>(
|
||||
this.options.MathItem, MmlJax, toMathML
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach speech from a MathItem to a node
|
||||
*/
|
||||
public attachSpeech() {
|
||||
if (!this.processed.isSet('attach-speech')) {
|
||||
for (const math of this.math) {
|
||||
(math as EnrichedMathItem<N, T, D>).attachSpeech(this);
|
||||
}
|
||||
this.processed.set('attach-speech');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enrich the MathItems in this MathDocument
|
||||
*/
|
||||
public enrich() {
|
||||
if (!this.processed.isSet('enriched')) {
|
||||
if (this.options.enableEnrichment) {
|
||||
for (const math of this.math) {
|
||||
(math as EnrichedMathItem<N, T, D>).enrich(this);
|
||||
}
|
||||
}
|
||||
this.processed.set('enriched');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public enrichError(_doc: EnrichedMathDocument<N, T, D>, _math: EnrichedMathItem<N, T, D>, err: Error) {
|
||||
console.warn('Enrichment error:', err);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public state(state: number, restore: boolean = false) {
|
||||
super.state(state, restore);
|
||||
if (state < STATE.ENRICHED) {
|
||||
this.processed.clear('enriched');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/*==========================================================================*/
|
||||
|
||||
/**
|
||||
* Add enrichment a Handler instance
|
||||
*
|
||||
* @param {Handler} handler The Handler instance to enhance
|
||||
* @param {MathML} MmlJax The MathML input jax to use for reading the enriched MathML
|
||||
* @return {Handler} The handler that was modified (for purposes of chainging extensions)
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export function EnrichHandler<N, T, D>(handler: Handler<N, T, D>, MmlJax: MathML<N, T, D>): Handler<N, T, D> {
|
||||
MmlJax.setAdaptor(handler.adaptor);
|
||||
handler.documentClass =
|
||||
EnrichedMathDocumentMixin<N, T, D, MathDocumentConstructor<AbstractMathDocument<N, T, D>>>(
|
||||
handler.documentClass, MmlJax
|
||||
);
|
||||
return handler;
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 Provides the interface functionality to SRE.
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
* @author v.sorge@mathjax.org (Volker Sorge)
|
||||
*/
|
||||
|
||||
import * as Api from 'speech-rule-engine/js/common/system.js';
|
||||
import {Walker} from 'speech-rule-engine/js/walker/walker.js';
|
||||
import * as WalkerFactory from 'speech-rule-engine/js/walker/walker_factory.js';
|
||||
import * as SpeechGeneratorFactory from 'speech-rule-engine/js/speech_generator/speech_generator_factory.js';
|
||||
import * as EngineConst from 'speech-rule-engine/js/common/engine_const.js';
|
||||
import Engine from 'speech-rule-engine/js/common/engine.js';
|
||||
import {ClearspeakPreferences} from 'speech-rule-engine/js/speech_rules/clearspeak_preferences.js';
|
||||
import {Highlighter} from 'speech-rule-engine/js/highlighter/highlighter.js';
|
||||
import * as HighlighterFactory from 'speech-rule-engine/js/highlighter/highlighter_factory.js';
|
||||
import {SpeechGenerator} from 'speech-rule-engine/js/speech_generator/speech_generator.js';
|
||||
import {Variables} from 'speech-rule-engine/js/common/variables.js';
|
||||
import MathMaps from './mathmaps.js';
|
||||
|
||||
export namespace Sre {
|
||||
|
||||
export type highlighter = Highlighter;
|
||||
|
||||
export type speechGenerator = SpeechGenerator;
|
||||
|
||||
export type walker = Walker;
|
||||
|
||||
|
||||
export const locales = Variables.LOCALES;
|
||||
|
||||
export const sreReady = Api.engineReady;
|
||||
|
||||
export const setupEngine = Api.setupEngine;
|
||||
|
||||
export const engineSetup = Api.engineSetup;
|
||||
|
||||
export const toEnriched = Api.toEnriched;
|
||||
|
||||
export const toSpeech = Api.toSpeech;
|
||||
|
||||
export const clearspeakPreferences = ClearspeakPreferences;
|
||||
|
||||
export const getHighlighter = HighlighterFactory.highlighter;
|
||||
|
||||
export const getSpeechGenerator = SpeechGeneratorFactory.generator;
|
||||
|
||||
export const getWalker = WalkerFactory.walker;
|
||||
|
||||
export const clearspeakStyle = () => {
|
||||
return EngineConst.DOMAIN_TO_STYLES['clearspeak'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads locales that are already included in the imported MathMaps. Defaults
|
||||
* to standard loading if a locale is not yet preloaded.
|
||||
*/
|
||||
export const preloadLocales = async function(locale: string) {
|
||||
const json = MathMaps.get(locale);
|
||||
return json ? new Promise((res, _rej) => res(JSON.stringify(json))) :
|
||||
Api.localeLoader()(locale);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A promise that resolves when SRE is loaded and ready, and rejects if
|
||||
* SRE can't be loaded, or does not become ready within the timout period.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
export const sreReady = Sre.sreReady;
|
||||
|
||||
// Setting delay stops SRE from setting itself up (and loading locales) when it
|
||||
// is not actually being used. As we are not yet sure in which environment we
|
||||
// are (browser, node) we can not use a configuration vector.
|
||||
Engine.getInstance().delay = true;
|
||||
|
||||
export default Sre;
|
||||
+563
@@ -0,0 +1,563 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 the HTML DOM adaptor
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {OptionList} from '../util/Options.js';
|
||||
import {AttributeData, AbstractDOMAdaptor, DOMAdaptor, PageBBox} from '../core/DOMAdaptor.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The minimum fields needed for a Document
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
*/
|
||||
export interface MinDocument<N, T> {
|
||||
documentElement: N;
|
||||
head: N;
|
||||
body: N;
|
||||
title: string;
|
||||
doctype: {name: string};
|
||||
/* tslint:disable:jsdoc-require */
|
||||
createElement(kind: string): N;
|
||||
createElementNS(ns: string, kind: string): N;
|
||||
createTextNode(text: string): T;
|
||||
querySelectorAll(selector: string): ArrayLike<N>;
|
||||
/* tslint:enable */
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The minimum fields needed for an HTML Element
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
*/
|
||||
export interface MinHTMLElement<N, T> {
|
||||
nodeType: number;
|
||||
nodeName: string;
|
||||
nodeValue: string;
|
||||
textContent: string;
|
||||
innerHTML: string;
|
||||
outerHTML: string;
|
||||
parentNode: N | Node;
|
||||
nextSibling: N | T | Node;
|
||||
previousSibling: N | T | Node;
|
||||
offsetWidth: number;
|
||||
offsetHeight: number;
|
||||
|
||||
attributes: AttributeData[] | NamedNodeMap;
|
||||
className: string;
|
||||
classList: DOMTokenList;
|
||||
style: OptionList;
|
||||
sheet?: {insertRule: (rule: string, index?: number) => void};
|
||||
|
||||
childNodes: (N | T)[] | NodeList;
|
||||
firstChild: N | T | Node;
|
||||
lastChild: N | T | Node;
|
||||
/* tslint:disable:jsdoc-require */
|
||||
getElementsByTagName(name: string): N[] | HTMLCollectionOf<Element>;
|
||||
getElementsByTagNameNS(ns: string, name: string): N[] | HTMLCollectionOf<Element>;
|
||||
contains(child: N | T): boolean;
|
||||
appendChild(child: N | T): N | T | Node;
|
||||
removeChild(child: N | T): N | T | Node;
|
||||
replaceChild(nnode: N | T, onode: N | T): N | T | Node;
|
||||
insertBefore(nchild: N | T, ochild: N | T): void;
|
||||
cloneNode(deep: boolean): N | Node;
|
||||
setAttribute(name: string, value: string): void;
|
||||
setAttributeNS(ns: string, name: string, value: string): void;
|
||||
getAttribute(name: string): string;
|
||||
removeAttribute(name: string): void;
|
||||
hasAttribute(name: string): boolean;
|
||||
getBoundingClientRect(): Object;
|
||||
getBBox?(): {x: number, y: number, width: number, height: number};
|
||||
/* tslint:endable */
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The minimum fields needed for a Text element
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
*/
|
||||
export interface MinText<N, T> {
|
||||
nodeType: number;
|
||||
nodeName: string;
|
||||
nodeValue: string;
|
||||
parentNode: N | Node;
|
||||
nextSibling: N | T | Node;
|
||||
previousSibling: N | T | Node;
|
||||
splitText(n: number): T;
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The minimum fields needed for a DOMParser
|
||||
*
|
||||
* @template D The Document class
|
||||
*/
|
||||
export interface MinDOMParser<D> {
|
||||
parseFromString(text: string, format?: string): D;
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The minimum fields needed for a DOMParser
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
*/
|
||||
export interface MinXMLSerializer<N> {
|
||||
serializeToString(node: N): string;
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The minimum fields needed for a Window
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export interface MinWindow<N, D> {
|
||||
document: D;
|
||||
DOMParser: {
|
||||
new(): MinDOMParser<D>
|
||||
};
|
||||
XMLSerializer: {
|
||||
new(): MinXMLSerializer<N>;
|
||||
};
|
||||
NodeList: any;
|
||||
HTMLCollection: any;
|
||||
HTMLElement: any;
|
||||
DocumentFragment: any;
|
||||
Document: any;
|
||||
getComputedStyle(node: N): any;
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The minimum needed for an HTML Adaptor
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export interface MinHTMLAdaptor<N, T, D> extends DOMAdaptor<N, T, D> {
|
||||
window: MinWindow<N, D>;
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Abstract HTMLAdaptor class for manipulating HTML elements
|
||||
* (subclass of AbstractDOMAdaptor)
|
||||
*
|
||||
* N = HTMLElement node class
|
||||
* T = Text node class
|
||||
* D = Document class
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export class HTMLAdaptor<N extends MinHTMLElement<N, T>, T extends MinText<N, T>, D extends MinDocument<N, T>> extends
|
||||
AbstractDOMAdaptor<N, T, D> implements MinHTMLAdaptor<N, T, D> {
|
||||
/**
|
||||
* The window object for this adaptor
|
||||
*/
|
||||
public window: MinWindow<N, D>;
|
||||
|
||||
/**
|
||||
* The DOMParser used to parse a string into a DOM tree
|
||||
*/
|
||||
public parser: MinDOMParser<D>;
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @constructor
|
||||
*/
|
||||
constructor(window: MinWindow<N, D>) {
|
||||
super(window.document);
|
||||
this.window = window;
|
||||
this.parser = new (window.DOMParser as any)();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public parse(text: string, format: string = 'text/html') {
|
||||
return this.parser.parseFromString(text, format);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
protected create(kind: string, ns?: string) {
|
||||
return (ns ?
|
||||
this.document.createElementNS(ns, kind) :
|
||||
this.document.createElement(kind));
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public text(text: string) {
|
||||
return this.document.createTextNode(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public head(doc: D) {
|
||||
return doc.head || (doc as any as N);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public body(doc: D) {
|
||||
return doc.body || (doc as any as N);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public root(doc: D) {
|
||||
return doc.documentElement || (doc as any as N);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public doctype(doc: D) {
|
||||
return (doc.doctype ? `<!DOCTYPE ${doc.doctype.name}>` : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public tags(node: N, name: string, ns: string = null) {
|
||||
let nodes = (ns ? node.getElementsByTagNameNS(ns, name) : node.getElementsByTagName(name));
|
||||
return Array.from(nodes as N[]) as N[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public getElements(nodes: (string | N | N[])[], _document: D) {
|
||||
let containers: N[] = [];
|
||||
for (const node of nodes) {
|
||||
if (typeof(node) === 'string') {
|
||||
containers = containers.concat(Array.from(this.document.querySelectorAll(node)));
|
||||
} else if (Array.isArray(node)) {
|
||||
containers = containers.concat(Array.from(node) as N[]);
|
||||
} else if (node instanceof this.window.NodeList || node instanceof this.window.HTMLCollection) {
|
||||
containers = containers.concat(Array.from(node as any as N[]));
|
||||
} else {
|
||||
containers.push(node);
|
||||
}
|
||||
}
|
||||
return containers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public contains(container: N, node: N | T) {
|
||||
return container.contains(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public parent(node: N | T) {
|
||||
return node.parentNode as N;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public append(node: N, child: N | T) {
|
||||
return node.appendChild(child) as N | T;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public insert(nchild: N | T, ochild: N | T) {
|
||||
return this.parent(ochild).insertBefore(nchild, ochild);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public remove(child: N | T) {
|
||||
return this.parent(child).removeChild(child) as N | T;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public replace(nnode: N | T, onode: N | T) {
|
||||
return this.parent(onode).replaceChild(nnode, onode) as N | T;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public clone(node: N) {
|
||||
return node.cloneNode(true) as N;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public split(node: T, n: number) {
|
||||
return node.splitText(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public next(node: N | T) {
|
||||
return node.nextSibling as N | T;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public previous(node: N | T) {
|
||||
return node.previousSibling as N | T;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public firstChild(node: N) {
|
||||
return node.firstChild as N | T;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public lastChild(node: N) {
|
||||
return node.lastChild as N | T;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public childNodes(node: N) {
|
||||
return Array.from(node.childNodes as (N | T)[]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public childNode(node: N, i: number) {
|
||||
return node.childNodes[i] as N | T;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public kind(node: N | T) {
|
||||
const n = node.nodeType;
|
||||
return (n === 1 || n === 3 || n === 8 ? node.nodeName.toLowerCase() : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public value(node: N | T) {
|
||||
return node.nodeValue || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public textContent(node: N) {
|
||||
return node.textContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public innerHTML(node: N) {
|
||||
return node.innerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public outerHTML(node: N) {
|
||||
return node.outerHTML;
|
||||
}
|
||||
|
||||
public serializeXML(node: N) {
|
||||
const serializer = new this.window.XMLSerializer();
|
||||
return serializer.serializeToString(node) as string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public setAttribute(node: N, name: string, value: string, ns: string = null) {
|
||||
if (!ns) {
|
||||
return node.setAttribute(name, value);
|
||||
}
|
||||
name = ns.replace(/.*\//, '') + ':' + name.replace(/^.*:/, '');
|
||||
return node.setAttributeNS(ns, name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public getAttribute(node: N, name: string) {
|
||||
return node.getAttribute(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public removeAttribute(node: N, name: string) {
|
||||
return node.removeAttribute(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public hasAttribute(node: N, name: string) {
|
||||
return node.hasAttribute(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public allAttributes(node: N) {
|
||||
return Array.from(node.attributes).map(
|
||||
(x: AttributeData) => {
|
||||
return {name: x.name, value: x.value} as AttributeData;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public addClass(node: N, name: string) {
|
||||
if (node.classList) {
|
||||
node.classList.add(name);
|
||||
} else {
|
||||
node.className = (node.className + ' ' + name).trim();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public removeClass(node: N, name: string) {
|
||||
if (node.classList) {
|
||||
node.classList.remove(name);
|
||||
} else {
|
||||
node.className = node.className.split(/ /).filter((c) => c !== name).join(' ');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public hasClass(node: N, name: string) {
|
||||
if (node.classList) {
|
||||
return node.classList.contains(name);
|
||||
}
|
||||
return node.className.split(/ /).indexOf(name) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public setStyle(node: N, name: string, value: string) {
|
||||
(node.style as OptionList)[name] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public getStyle(node: N, name: string) {
|
||||
return (node.style as OptionList)[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public allStyles(node: N) {
|
||||
return node.style.cssText;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public insertRules(node: N, rules: string[]) {
|
||||
for (const rule of rules.reverse()) {
|
||||
try {
|
||||
node.sheet.insertRule(rule, 0);
|
||||
} catch (e) {
|
||||
console.warn(`MathJax: can't insert css rule '${rule}': ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public fontSize(node: N) {
|
||||
const style = this.window.getComputedStyle(node);
|
||||
return parseFloat(style.fontSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public fontFamily(node: N) {
|
||||
const style = this.window.getComputedStyle(node);
|
||||
return style.fontFamily || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public nodeSize(node: N, em: number = 1, local: boolean = false) {
|
||||
if (local && node.getBBox) {
|
||||
let {width, height} = node.getBBox();
|
||||
return [width / em , height / em] as [number, number];
|
||||
}
|
||||
return [node.offsetWidth / em, node.offsetHeight / em] as [number, number];
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public nodeBBox(node: N) {
|
||||
const {left, right, top, bottom} = node.getBoundingClientRect() as PageBBox;
|
||||
return {left, right, top, bottom};
|
||||
}
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* Copyright (c) 2022-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 mixin for node-based adaptors that overrides
|
||||
* the methods that obtain DOM node sizes, when those aren't
|
||||
* available from the DOM itself.
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {DOMAdaptor} from '../core/DOMAdaptor.js';
|
||||
import {userOptions, defaultOptions, OptionList} from '../util/Options.js';
|
||||
|
||||
/**
|
||||
* A constructor for a given class
|
||||
*
|
||||
* @template T The class to construct
|
||||
*/
|
||||
export type Constructor<T> = (new(...args: any[]) => T);
|
||||
|
||||
/**
|
||||
* The type of an Adaptor class
|
||||
*/
|
||||
export type AdaptorConstructor<N, T, D> = Constructor<DOMAdaptor<N, T, D>>;
|
||||
|
||||
/**
|
||||
* The options to the NodeMixin
|
||||
*/
|
||||
export const NodeMixinOptions: OptionList = {
|
||||
badCSS: true, // getComputedStyles() is not implemented in the DOM
|
||||
badSizes: true, // element sizes (e.g., ClientWidth, etc.) are not implemented in the DOM
|
||||
};
|
||||
|
||||
/**
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export function NodeMixin<N, T, D, A extends AdaptorConstructor<N, T, D>>(
|
||||
Base: A,
|
||||
options: typeof NodeMixinOptions = {}
|
||||
): A {
|
||||
|
||||
options = userOptions(defaultOptions({}, NodeMixinOptions), options);
|
||||
|
||||
return class NodeAdaptor extends Base {
|
||||
|
||||
/**
|
||||
* The default options
|
||||
*/
|
||||
public static OPTIONS: OptionList = {
|
||||
...(options.badCSS ? {
|
||||
fontSize: 16, // We can't compute the font size, so always use this
|
||||
fontFamily: 'Times', // We can't compute the font family, so always use this
|
||||
} : {}),
|
||||
...(options.badSizes ? {
|
||||
cjkCharWidth: 1, // Width (in em units) of full width characters
|
||||
unknownCharWidth: .6, // Width (in em units) of unknown (non-full-width) characters
|
||||
unknownCharHeight: .8, // Height (in em units) of unknown characters
|
||||
} : {})
|
||||
};
|
||||
|
||||
/**
|
||||
* Pattern to identify CJK (i.e., full-width) characters
|
||||
*/
|
||||
public static cjkPattern = new RegExp([
|
||||
'[',
|
||||
'\u1100-\u115F', // Hangul Jamo
|
||||
'\u2329\u232A', // LEFT-POINTING ANGLE BRACKET, RIGHT-POINTING ANGLE BRACKET
|
||||
'\u2E80-\u303E', // CJK Radicals Supplement ... CJK Symbols and Punctuation
|
||||
'\u3040-\u3247', // Hiragana ... Enclosed CJK Letters and Months
|
||||
'\u3250-\u4DBF', // Enclosed CJK Letters and Months ... CJK Unified Ideographs Extension A
|
||||
'\u4E00-\uA4C6', // CJK Unified Ideographs ... Yi Radicals
|
||||
'\uA960-\uA97C', // Hangul Jamo Extended-A
|
||||
'\uAC00-\uD7A3', // Hangul Syllables
|
||||
'\uF900-\uFAFF', // CJK Compatibility Ideographs
|
||||
'\uFE10-\uFE19', // Vertical Forms
|
||||
'\uFE30-\uFE6B', // CJK Compatibility Forms ... Small Form Variants
|
||||
'\uFF01-\uFF60\uFFE0-\uFFE6', // Halfwidth and Fullwidth Forms
|
||||
'\u{1B000}-\u{1B001}', // Kana Supplement
|
||||
'\u{1F200}-\u{1F251}', // Enclosed Ideographic Supplement
|
||||
'\u{20000}-\u{3FFFD}', // CJK Unified Ideographs Extension B ... Tertiary Ideographic Plane
|
||||
']'
|
||||
].join(''), 'gu');
|
||||
|
||||
/**
|
||||
* The options for the instance
|
||||
*/
|
||||
public options: OptionList;
|
||||
|
||||
/**
|
||||
* @param {any} window The window to work with
|
||||
* @param {OptionList} options The options for the adaptor
|
||||
* @constructor
|
||||
*/
|
||||
constructor(...args: any[]) {
|
||||
super(args[0]);
|
||||
let CLASS = this.constructor as typeof NodeAdaptor;
|
||||
this.options = userOptions(defaultOptions({}, CLASS.OPTIONS), args[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* For DOMs that don't handle CSS well, use the font size from the options
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public fontSize(node: N) {
|
||||
return (options.badCSS ? this.options.fontSize : super.fontSize(node));
|
||||
}
|
||||
|
||||
/**
|
||||
* For DOMs that don't handle CSS well, use the font family from the options
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public fontFamily(node: N) {
|
||||
return (options.badCSS ? this.options.fontFamily : super.fontFamily(node));
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public nodeSize(node: N, em: number = 1, local: boolean = null) {
|
||||
if (!options.badSizes) {
|
||||
return super.nodeSize(node, em, local);
|
||||
}
|
||||
const text = this.textContent(node);
|
||||
const non = Array.from(text.replace(NodeAdaptor.cjkPattern, '')).length; // # of non-CJK chars
|
||||
const CJK = Array.from(text).length - non; // # of cjk chars
|
||||
return [
|
||||
CJK * this.options.cjkCharWidth + non * this.options.unknownCharWidth,
|
||||
this.options.unknownCharHeight
|
||||
] as [number, number];
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public nodeBBox(node: N) {
|
||||
return (options.badSizes ? {left: 0, right: 0, top: 0, bottom: 0} : super.nodeBBox(node));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 the browser DOM adaptor
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {HTMLAdaptor} from './HTMLAdaptor.js';
|
||||
|
||||
//
|
||||
// Let Typescript know about these
|
||||
//
|
||||
declare global {
|
||||
interface Window {
|
||||
Document: typeof Document;
|
||||
DOMParser: typeof DOMParser;
|
||||
XMLSerializer: typeof XMLSerializer;
|
||||
HTMLElement: typeof HTMLElement;
|
||||
HTMLCollection: typeof HTMLCollection;
|
||||
NodeList: typeof NodeList;
|
||||
DocumentFragment: typeof DocumentFragment;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to create an HTML adpator for browsers
|
||||
*
|
||||
* @return {HTMLAdaptor} The newly created adaptor
|
||||
*/
|
||||
export function browserAdaptor(): HTMLAdaptor<HTMLElement, Text, Document> {
|
||||
return new HTMLAdaptor<HTMLElement, Text, Document>(window);
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 Chooses between jdsom and browser DOM adaptors
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {liteAdaptor} from './liteAdaptor.js';
|
||||
import {browserAdaptor} from './browserAdaptor.js';
|
||||
|
||||
let choose;
|
||||
|
||||
try {
|
||||
document; // errors if not in browser
|
||||
choose = browserAdaptor;
|
||||
} catch (e) {
|
||||
choose = liteAdaptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to select which adaptor to use (depending on whether we are in a browser or node.js)
|
||||
*/
|
||||
export const chooseAdaptor = choose;
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 the jdsom DOM adaptor
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {HTMLAdaptor} from './HTMLAdaptor.js';
|
||||
import {NodeMixin, Constructor} from './NodeMixin.js';
|
||||
import {OptionList} from '../util/Options.js';
|
||||
|
||||
/**
|
||||
* The constructor for an HTMLAdaptor
|
||||
*/
|
||||
export type HTMLAdaptorConstructor = Constructor<HTMLAdaptor<HTMLElement, Text, Document>>;
|
||||
|
||||
/**
|
||||
* The JsdomAdaptor class
|
||||
*/
|
||||
export class JsdomAdaptor extends NodeMixin<HTMLElement, Text, Document, HTMLAdaptorConstructor>(HTMLAdaptor) {}
|
||||
|
||||
/**
|
||||
* Function for creating an HTML adaptor using jsdom
|
||||
*
|
||||
* @param {any} JSDOM The jsdom object to use for this adaptor
|
||||
* @return {HTMLAdaptor} The newly created adaptor
|
||||
*/
|
||||
export function jsdomAdaptor(JSDOM: any, options: OptionList = null): JsdomAdaptor {
|
||||
return new JsdomAdaptor(new JSDOM().window, options);
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* Copyright (c) 2022-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 linkedom DOM adaptor
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {HTMLAdaptor} from './HTMLAdaptor.js';
|
||||
import {NodeMixin, Constructor} from './NodeMixin.js';
|
||||
import {OptionList} from '../util/Options.js';
|
||||
|
||||
/**
|
||||
* The constructor for an HTMLAdaptor
|
||||
*/
|
||||
export type HTMLAdaptorConstructor = Constructor<HTMLAdaptor<HTMLElement, Text, Document>>;
|
||||
|
||||
/**
|
||||
* The LinkedomAdaptor class
|
||||
*/
|
||||
export class LinkedomAdaptor extends NodeMixin<HTMLElement, Text, Document, HTMLAdaptorConstructor>(HTMLAdaptor) {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public parse(text: string, format: string = 'text/html') {
|
||||
//
|
||||
// Make sure the text string has nodes (in particular, it can't be empty)
|
||||
//
|
||||
if (!text.match(/^(?:\s|\n)*</)) text = '<html>' + text + '</html>';
|
||||
return this.parser.parseFromString(text, format);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*
|
||||
* This will do an HTML serialization, which may be good enough, but
|
||||
* won't necessarily close some tags properly.
|
||||
*/
|
||||
public serializeXML(node: HTMLElement) {
|
||||
return this.outerHTML(node);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for creating an HTML adaptor using linkedom
|
||||
*
|
||||
* @param {any} parseHTML The linkedom HTML parser to use for this adaptor
|
||||
* @return {LinkeddomAdaptor} The newly created adaptor
|
||||
*/
|
||||
export function linkedomAdaptor(parseHTML: any, options: OptionList = null): LinkedomAdaptor {
|
||||
const window = parseHTML('<html></html>');
|
||||
window.constructor.prototype.HTMLCollection = class {}; // add fake class for missing HTMLCollecton
|
||||
return new LinkedomAdaptor(window, options);
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 lightweight DOM adaptor
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {LiteElement} from './Element.js';
|
||||
|
||||
/************************************************************/
|
||||
/**
|
||||
* Implements a lightweight Document replacement
|
||||
*/
|
||||
export class LiteDocument {
|
||||
/**
|
||||
* The document's <html> element
|
||||
*/
|
||||
public root: LiteElement;
|
||||
/**
|
||||
* The document's <head> element
|
||||
*/
|
||||
public head: LiteElement;
|
||||
/**
|
||||
* The document's <body> element
|
||||
*/
|
||||
public body: LiteElement;
|
||||
|
||||
/**
|
||||
* the DOCTYPE comment
|
||||
*/
|
||||
public type: string;
|
||||
|
||||
/**
|
||||
* The kind is always #document
|
||||
*/
|
||||
public get kind() {
|
||||
return '#document';
|
||||
}
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
constructor() {
|
||||
this.root = new LiteElement('html', {}, [
|
||||
this.head = new LiteElement('head'),
|
||||
this.body = new LiteElement('body')
|
||||
]);
|
||||
this.type = '';
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 lightweight HTML Element replacement
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {OptionList} from '../../util/Options.js';
|
||||
import {Styles} from '../../util/Styles.js';
|
||||
import {LiteText} from './Text.js';
|
||||
|
||||
/**
|
||||
* Type for attribute lists
|
||||
*/
|
||||
export type LiteAttributeList = OptionList;
|
||||
|
||||
/**
|
||||
* Type for generic nodes in LiteAdaptor
|
||||
*/
|
||||
export type LiteNode = LiteElement | LiteText;
|
||||
|
||||
|
||||
/************************************************************/
|
||||
/**
|
||||
* Implements a lightweight HTML element replacement
|
||||
*/
|
||||
export class LiteElement {
|
||||
/**
|
||||
* The type of element (tag name)
|
||||
*/
|
||||
public kind: string;
|
||||
|
||||
/**
|
||||
* The element's attribute list
|
||||
*/
|
||||
public attributes: LiteAttributeList;
|
||||
|
||||
/**
|
||||
* The element's children
|
||||
*/
|
||||
public children: LiteNode[];
|
||||
|
||||
/**
|
||||
* The element's parent
|
||||
*/
|
||||
public parent: LiteElement;
|
||||
|
||||
/**
|
||||
* The styles for the element
|
||||
*/
|
||||
public styles: Styles;
|
||||
|
||||
/**
|
||||
* @param {string} kind The type of node to create
|
||||
* @param {LiteAttributeList} attributes The list of attributes to set (if any)
|
||||
* @param {LiteNode[]} children The children for the node (if any)
|
||||
* @constructor
|
||||
*/
|
||||
constructor(kind: string, attributes: LiteAttributeList = {}, children: LiteNode[] = []) {
|
||||
this.kind = kind;
|
||||
this.attributes = {...attributes};
|
||||
this.children = [...children];
|
||||
for (const child of this.children) {
|
||||
child.parent = this;
|
||||
}
|
||||
this.styles = null;
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 lightweight DOM adaptor
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {LiteNode} from './Element.js';
|
||||
|
||||
/************************************************************/
|
||||
/**
|
||||
* Implements a lightweight DocumentFragment or NodeList replacement
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
*/
|
||||
export class LiteList<N> {
|
||||
/**
|
||||
* The nodes held in the fragment
|
||||
*/
|
||||
public nodes: N[] = [];
|
||||
|
||||
/**
|
||||
* @param {N[]} children The children for the fragment
|
||||
* @constructor
|
||||
*/
|
||||
constructor(children: N[]) {
|
||||
this.nodes = [...children];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {N} node A node to append to the fragment
|
||||
*/
|
||||
public append(node: N) {
|
||||
this.nodes.push(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make this class iterable (so it can be used with Array.from())
|
||||
*/
|
||||
public [Symbol.iterator](): Iterator<LiteNode> {
|
||||
let i = 0;
|
||||
return {
|
||||
/**
|
||||
* @return {IteratorResult<LiteNode>}
|
||||
*/
|
||||
next(): IteratorResult<LiteNode> {
|
||||
return (i === this.nodes.length ?
|
||||
{value: null, done: true} :
|
||||
{value: this.nodes[i++], done: false});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
+405
@@ -0,0 +1,405 @@
|
||||
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 lightweight DOM adaptor
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {AttributeData} from '../../core/DOMAdaptor.js';
|
||||
import {MinDOMParser} from '../HTMLAdaptor.js';
|
||||
import * as Entities from '../../util/Entities.js';
|
||||
import {LiteDocument} from './Document.js';
|
||||
import {LiteElement} from './Element.js';
|
||||
import {LiteText, LiteComment} from './Text.js';
|
||||
import {LiteAdaptor} from '../liteAdaptor.js';
|
||||
|
||||
/**
|
||||
* Patterns used in parsing serialized HTML
|
||||
*/
|
||||
export namespace PATTERNS {
|
||||
export const TAGNAME = '[a-z][^\\s\\n>]*';
|
||||
export const ATTNAME = '[a-z][^\\s\\n>=]*';
|
||||
export const VALUE = `(?:'[^']*'|"[^"]*"|[^\\s\\n]+)`;
|
||||
export const VALUESPLIT = `(?:'([^']*)'|"([^"]*)"|([^\\s\\n]+))`;
|
||||
export const SPACE = '(?:\\s|\\n)+';
|
||||
export const OPTIONALSPACE = '(?:\\s|\\n)*';
|
||||
export const ATTRIBUTE = ATTNAME + '(?:' + OPTIONALSPACE + '=' + OPTIONALSPACE + VALUE + ')?';
|
||||
export const ATTRIBUTESPLIT = '(' + ATTNAME + ')(?:' + OPTIONALSPACE + '=' + OPTIONALSPACE + VALUESPLIT + ')?';
|
||||
export const TAG = '(<(?:' + TAGNAME + '(?:' + SPACE + ATTRIBUTE + ')*'
|
||||
+ OPTIONALSPACE + '/?|/' + TAGNAME + '|!--[^]*?--|![^]*?)(?:>|$))';
|
||||
export const tag = new RegExp(TAG, 'i');
|
||||
export const attr = new RegExp(ATTRIBUTE, 'i');
|
||||
export const attrsplit = new RegExp(ATTRIBUTESPLIT, 'i');
|
||||
}
|
||||
|
||||
/************************************************************/
|
||||
/**
|
||||
* Implements a lightweight DOMParser replacement
|
||||
* (Not perfect, but handles most well-formed HTML)
|
||||
*/
|
||||
export class LiteParser implements MinDOMParser<LiteDocument> {
|
||||
|
||||
/**
|
||||
* The list of self-closing tags
|
||||
*/
|
||||
public static SELF_CLOSING: {[name: string]: boolean} = {
|
||||
area: true,
|
||||
base: true,
|
||||
br: true,
|
||||
col: true,
|
||||
command: true,
|
||||
embed: true,
|
||||
hr: true,
|
||||
img: true,
|
||||
input: true,
|
||||
keygen: true,
|
||||
link: true,
|
||||
menuitem: true,
|
||||
meta: true,
|
||||
param: true,
|
||||
source: true,
|
||||
track: true,
|
||||
wbr: true
|
||||
};
|
||||
|
||||
/**
|
||||
* The list of tags chose content is not parsed (PCDATA)
|
||||
*/
|
||||
public static PCDATA: {[name: string]: boolean} = {
|
||||
option: true,
|
||||
textarea: true,
|
||||
fieldset: true,
|
||||
title: true,
|
||||
style: true,
|
||||
script: true
|
||||
};
|
||||
|
||||
/**
|
||||
* The list of attributes that don't get entity translation
|
||||
*/
|
||||
public static CDATA_ATTR: {[name: string]: boolean} = {
|
||||
style: true,
|
||||
datafld: true,
|
||||
datasrc: true,
|
||||
href: true,
|
||||
src: true,
|
||||
longdesc: true,
|
||||
usemap: true,
|
||||
cite: true,
|
||||
datetime: true,
|
||||
action: true,
|
||||
axis: true,
|
||||
profile: true,
|
||||
content: true,
|
||||
scheme: true
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public parseFromString(text: string, _format: string = 'text/html', adaptor: LiteAdaptor = null) {
|
||||
const root = adaptor.createDocument();
|
||||
let node = adaptor.body(root);
|
||||
//
|
||||
// Split the HTML into an array of text, tag, text, tag, ...
|
||||
// Then loop through them and add text nodes and process tags.
|
||||
//
|
||||
let parts = text.replace(/<\?.*?\?>/g, '').split(PATTERNS.tag);
|
||||
while (parts.length) {
|
||||
const text = parts.shift();
|
||||
const tag = parts.shift();
|
||||
if (text) {
|
||||
this.addText(adaptor, node, text);
|
||||
}
|
||||
if (tag && tag.charAt(tag.length - 1) === '>') {
|
||||
if (tag.charAt(1) === '!') {
|
||||
this.addComment(adaptor, node, tag);
|
||||
} else if (tag.charAt(1) === '/') {
|
||||
node = this.closeTag(adaptor, node, tag);
|
||||
} else {
|
||||
node = this.openTag(adaptor, node, tag, parts);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.checkDocument(adaptor, root);
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {LiteAdaptor} adaptor The adaptor for managing nodes
|
||||
* @param {LiteElement} node The node to add a text element to
|
||||
* @param {string} text The text for the text node
|
||||
* @return {LiteText} The text element
|
||||
*/
|
||||
protected addText(adaptor: LiteAdaptor, node: LiteElement, text: string): LiteText {
|
||||
text = Entities.translate(text);
|
||||
return adaptor.append(node, adaptor.text(text)) as LiteText;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {LiteAdaptor} adaptor The adaptor for managing nodes
|
||||
* @param {LiteElement} node The node to add a comment to
|
||||
* @param {string} comment The text for the comment node
|
||||
* @return {LiteComment} The comment element
|
||||
*/
|
||||
protected addComment(adaptor: LiteAdaptor, node: LiteElement, comment: string): LiteComment {
|
||||
return adaptor.append(node, new LiteComment(comment)) as LiteComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {LiteAdaptor} adaptor The adaptor for managing nodes
|
||||
* @param {LiteElement} node The node to close
|
||||
* @param {string} tag The close tag being processed
|
||||
* @return {LiteElement} The first unclosed parent node
|
||||
*/
|
||||
protected closeTag(adaptor: LiteAdaptor, node: LiteElement, tag: string): LiteElement {
|
||||
const kind = tag.slice(2, tag.length - 1).toLowerCase();
|
||||
while (adaptor.parent(node) && adaptor.kind(node) !== kind) {
|
||||
node = adaptor.parent(node);
|
||||
}
|
||||
return adaptor.parent(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {LiteAdaptor} adaptor The adaptor for managing nodes
|
||||
* @param {LiteElement} node The parent node for the tag
|
||||
* @param {string} tag The tag being processed
|
||||
* @param {string[]} parts The rest of the text/tag array
|
||||
* @return {LiteElement} The node to which the next tag will be added
|
||||
*/
|
||||
protected openTag(adaptor: LiteAdaptor, node: LiteElement, tag: string, parts: string[]): LiteElement {
|
||||
const PCDATA = (this.constructor as typeof LiteParser).PCDATA;
|
||||
const SELF_CLOSING = (this.constructor as typeof LiteParser).SELF_CLOSING;
|
||||
//
|
||||
// Get the child to be added to the node
|
||||
//
|
||||
const kind = tag.match(/<(.*?)[\s\n>\/]/)[1].toLowerCase();
|
||||
const child = adaptor.node(kind) as LiteElement;
|
||||
//
|
||||
// Split out the tag attributes as an array of space, name, value1, value3, value3,
|
||||
// where value1, value2, and value3 are the value of the node (only one is defined)
|
||||
// that come from matching quoted strings with ' (value1), " (value2) or no quotes (value3).
|
||||
//
|
||||
const attributes = tag.replace(/^<.*?[\s\n>]/, '').split(PATTERNS.attrsplit);
|
||||
//
|
||||
// If the tag was complete (it ends with > or has no attributes)
|
||||
//
|
||||
if (attributes.pop().match(/>$/) || attributes.length < 5) {
|
||||
this.addAttributes(adaptor, child, attributes);
|
||||
adaptor.append(node, child);
|
||||
//
|
||||
// For non-self-closing tags,
|
||||
// For tags whose contents is PCDATA (like <script>), collect the
|
||||
// content up until the end tag, and continue adding nee tags
|
||||
// to the current parent node.
|
||||
// Otherwise, the child tag becames the parent node to which
|
||||
// new tags are added
|
||||
//
|
||||
if (!SELF_CLOSING[kind] && !tag.match(/\/>$/)) {
|
||||
if (PCDATA[kind]) {
|
||||
this.handlePCDATA(adaptor, child, kind, parts);
|
||||
} else {
|
||||
node = child;
|
||||
}
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {LiteAdaptor} adaptor The adaptor for managing nodes
|
||||
* @param {LiteElement} node The node getting the attributes
|
||||
* @param {string[]} attributes The array of space, name, value1, value2, value3
|
||||
* as described above.
|
||||
*/
|
||||
protected addAttributes(adaptor: LiteAdaptor, node: LiteElement, attributes: string[]) {
|
||||
const CDATA_ATTR = (this.constructor as typeof LiteParser).CDATA_ATTR;
|
||||
while (attributes.length) {
|
||||
let [ , name, v1, v2, v3] = attributes.splice(0, 5);
|
||||
let value = v1 || v2 || v3 || '';
|
||||
if (!CDATA_ATTR[name]) {
|
||||
value = Entities.translate(value);
|
||||
}
|
||||
adaptor.setAttribute(node, name, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {LiteAdaptor} adaptor The adaptor for managing nodes
|
||||
* @param {LiteElement} node The node whose PCDATA content is being collected
|
||||
* @param {string} kind The tag name being handled
|
||||
* @param {string[]} parts The array of text/tag data for the document
|
||||
*/
|
||||
protected handlePCDATA(adaptor: LiteAdaptor, node: LiteElement, kind: string, parts: string[]) {
|
||||
const pcdata = [] as string[];
|
||||
const etag = '</' + kind + '>';
|
||||
let ptag = '';
|
||||
//
|
||||
// Look through the parts until the end tag is found
|
||||
// Add the unmatched tag and the following text
|
||||
// and try the next tag until we find the end tag.
|
||||
//
|
||||
while (parts.length && ptag !== etag) {
|
||||
pcdata.push(ptag);
|
||||
pcdata.push(parts.shift());
|
||||
ptag = parts.shift();
|
||||
}
|
||||
//
|
||||
// Add the collected contents as a text node
|
||||
//
|
||||
adaptor.append(node, adaptor.text(pcdata.join('')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the contents of the parsed document and move html, head, and body
|
||||
* tags into the document structure. That way, you can parse fragments or
|
||||
* full documents and still get a valid document.
|
||||
*
|
||||
* @param {LiteAdaptor} adaptor The adaptor for managing nodes
|
||||
* @param {LiteDocument} root The document being checked
|
||||
*/
|
||||
protected checkDocument(adaptor: LiteAdaptor, root: LiteDocument) {
|
||||
let node = this.getOnlyChild(adaptor, adaptor.body(root));
|
||||
if (!node) return;
|
||||
for (const child of adaptor.childNodes(adaptor.body(root))) {
|
||||
if (child === node) {
|
||||
break;
|
||||
}
|
||||
if (child instanceof LiteComment && child.value.match(/^<!DOCTYPE/)) {
|
||||
root.type = child.value;
|
||||
}
|
||||
}
|
||||
switch (adaptor.kind(node)) {
|
||||
case 'html':
|
||||
//
|
||||
// Look through the children for the head and body
|
||||
//
|
||||
for (const child of node.children) {
|
||||
switch (adaptor.kind(child)) {
|
||||
case 'head':
|
||||
root.head = child as LiteElement;
|
||||
break;
|
||||
case 'body':
|
||||
root.body = child as LiteElement;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//
|
||||
// Make sure the elements are linked in properly
|
||||
//
|
||||
root.root = node;
|
||||
adaptor.remove(node);
|
||||
if (adaptor.parent(root.body) !== node) {
|
||||
adaptor.append(node, root.body);
|
||||
}
|
||||
if (adaptor.parent(root.head) !== node) {
|
||||
adaptor.insert(root.head, root.body);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'head':
|
||||
root.head = adaptor.replace(node, root.head) as LiteElement;
|
||||
break;
|
||||
|
||||
case 'body':
|
||||
root.body = adaptor.replace(node, root.body) as LiteElement;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the body has only one element child (as opposed to comments or text nodes)
|
||||
* and returns that sole element (or null if none or more than one)
|
||||
*
|
||||
* @param {LiteAdaptor} adaptor The adaptor for managing nodes
|
||||
* @param {LiteElement} body The body element being checked
|
||||
* @return {LiteElement} The sole LiteElement child of the body, or null if none or more than one
|
||||
*/
|
||||
protected getOnlyChild(adaptor: LiteAdaptor, body: LiteElement): LiteElement {
|
||||
let node: LiteElement = null;
|
||||
for (const child of adaptor.childNodes(body)) {
|
||||
if (child instanceof LiteElement) {
|
||||
if (node) return null;
|
||||
node = child;
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {LiteAdaptor} adaptor The adaptor for managing nodes
|
||||
* @param {LiteElement} node The node to serialize
|
||||
* @param {boolean} xml True when producing XML, false for HTML
|
||||
* @return {string} The serialized element (like outerHTML)
|
||||
*/
|
||||
public serialize(adaptor: LiteAdaptor, node: LiteElement, xml: boolean = false): string {
|
||||
const SELF_CLOSING = (this.constructor as typeof LiteParser).SELF_CLOSING;
|
||||
const CDATA = (this.constructor as typeof LiteParser).CDATA_ATTR;
|
||||
const tag = adaptor.kind(node);
|
||||
const attributes = adaptor.allAttributes(node).map(
|
||||
(x: AttributeData) => x.name + '="' + (CDATA[x.name] ? x.value : this.protectAttribute(x.value)) + '"'
|
||||
).join(' ');
|
||||
const content = this.serializeInner(adaptor, node, xml);
|
||||
const html =
|
||||
'<' + tag + (attributes ? ' ' + attributes : '')
|
||||
+ ((!xml || content) && !SELF_CLOSING[tag] ? `>${content}</${tag}>` : xml ? '/>' : '>');
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {LiteAdaptor} adaptor The adaptor for managing nodes
|
||||
* @param {LiteElement} node The node whose innerHTML is needed
|
||||
* @return {string} The serialized element (like innerHTML)
|
||||
*/
|
||||
public serializeInner(adaptor: LiteAdaptor, node: LiteElement, xml: boolean = false): string {
|
||||
const PCDATA = (this.constructor as typeof LiteParser).PCDATA;
|
||||
if (PCDATA.hasOwnProperty(node.kind)) {
|
||||
return adaptor.childNodes(node).map(x => adaptor.value(x)).join('');
|
||||
}
|
||||
return adaptor.childNodes(node).map(x => {
|
||||
const kind = adaptor.kind(x);
|
||||
return (kind === '#text' ? this.protectHTML(adaptor.value(x)) :
|
||||
kind === '#comment' ? (x as LiteComment).value :
|
||||
this.serialize(adaptor, x as LiteElement, xml));
|
||||
}).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text The attribute value to be HTML escaped
|
||||
* @return {string} The string with " replaced by entities
|
||||
*/
|
||||
public protectAttribute(text: string): string {
|
||||
if (typeof text !== 'string') {
|
||||
text = String(text);
|
||||
}
|
||||
return text.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text The text to be HTML escaped
|
||||
* @return {string} The string with &, <, and > replaced by entities
|
||||
*/
|
||||
public protectHTML(text: string): string {
|
||||
return text.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 lightweight Text element replacement
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {LiteElement} from './Element.js';
|
||||
|
||||
/************************************************************/
|
||||
/**
|
||||
* Implements a lightweight Text node replacement
|
||||
*/
|
||||
export class LiteText {
|
||||
/**
|
||||
* The text stored in the node
|
||||
*/
|
||||
public value: string;
|
||||
|
||||
/**
|
||||
* The parent holding this text
|
||||
*/
|
||||
public parent: LiteElement;
|
||||
|
||||
/**
|
||||
* The kind of node is #text
|
||||
*/
|
||||
public get kind() {
|
||||
return '#text';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text The text for the node
|
||||
* @constructor
|
||||
*/
|
||||
constructor(text: string = '') {
|
||||
this.value = text;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************/
|
||||
/**
|
||||
* Implements a lightweight Comment node replacement
|
||||
*/
|
||||
export class LiteComment extends LiteText {
|
||||
public get kind() {
|
||||
return '#comment';
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 lightweight DOM adaptor
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {LiteElement} from './Element.js';
|
||||
import {LiteDocument} from './Document.js';
|
||||
import {LiteList} from './List.js';
|
||||
import {LiteParser} from './Parser.js';
|
||||
|
||||
/************************************************************/
|
||||
/**
|
||||
* Implements a lightweight Window replacement
|
||||
*/
|
||||
export class LiteWindow {
|
||||
/**
|
||||
* The window's document instance
|
||||
*/
|
||||
public document: LiteDocument;
|
||||
/**
|
||||
* The DOMParser class
|
||||
*/
|
||||
public DOMParser: typeof LiteParser = LiteParser;
|
||||
/**
|
||||
* The NodeList class
|
||||
*/
|
||||
public NodeList: typeof LiteList = LiteList;
|
||||
/**
|
||||
* The HTMLCollection class
|
||||
*/
|
||||
public HTMLCollection: typeof LiteList = LiteList;
|
||||
/**
|
||||
* The HTMLElement class
|
||||
*/
|
||||
public HTMLElement: typeof LiteElement = LiteElement;
|
||||
/**
|
||||
* The DocumentFragment class
|
||||
*/
|
||||
public DocumentFragment: typeof LiteList = LiteList;
|
||||
/**
|
||||
* The Document class
|
||||
*/
|
||||
public Document: typeof LiteDocument = LiteDocument;
|
||||
|
||||
/**
|
||||
* Create the LiteWindow and its LiteDocument
|
||||
*/
|
||||
constructor() {
|
||||
this.document = new LiteDocument();
|
||||
}
|
||||
}
|
||||
+603
@@ -0,0 +1,603 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 lightweight DOM adaptor
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {AbstractDOMAdaptor} from '../core/DOMAdaptor.js';
|
||||
import {NodeMixin, Constructor} from './NodeMixin.js';
|
||||
import {LiteDocument} from './lite/Document.js';
|
||||
import {LiteElement, LiteNode} from './lite/Element.js';
|
||||
import {LiteText, LiteComment} from './lite/Text.js';
|
||||
import {LiteList} from './lite/List.js';
|
||||
import {LiteWindow} from './lite/Window.js';
|
||||
import {LiteParser} from './lite/Parser.js';
|
||||
import {Styles} from '../util/Styles.js';
|
||||
import {OptionList} from '../util/Options.js';
|
||||
|
||||
/************************************************************/
|
||||
|
||||
|
||||
/**
|
||||
* Implements a lightweight DOMAdaptor on liteweight HTML elements
|
||||
*/
|
||||
export class LiteBase extends AbstractDOMAdaptor<LiteElement, LiteText, LiteDocument> {
|
||||
/**
|
||||
* The document in which the HTML nodes will be created
|
||||
*/
|
||||
public document: LiteDocument;
|
||||
|
||||
/**
|
||||
* The window for the document
|
||||
*/
|
||||
public window: LiteWindow;
|
||||
|
||||
/**
|
||||
* The parser for serialized HTML
|
||||
*/
|
||||
public parser: LiteParser;
|
||||
|
||||
/**
|
||||
* @param {OptionList} options The options for the lite adaptor (e.g., fontSize)
|
||||
* @constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this.parser = new LiteParser();
|
||||
this.window = new LiteWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public parse(text: string, format?: string): LiteDocument {
|
||||
return this.parser.parseFromString(text, format, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
protected create(kind: string, _ns: string = null) {
|
||||
return new LiteElement(kind);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public text(text: string) {
|
||||
return new LiteText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text The text of the comment
|
||||
* @return {LiteComment} The comment node
|
||||
*/
|
||||
public comment(text: string): LiteComment {
|
||||
return new LiteComment(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {LiteDocument} A new document element
|
||||
*/
|
||||
public createDocument(): LiteDocument {
|
||||
return new LiteDocument();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public head(doc: LiteDocument) {
|
||||
return doc.head;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public body(doc: LiteDocument) {
|
||||
return doc.body;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public root(doc: LiteDocument) {
|
||||
return doc.root;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public doctype(doc: LiteDocument) {
|
||||
return doc.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public tags(node: LiteElement, name: string, ns: string = null) {
|
||||
let stack = [] as LiteNode[];
|
||||
let tags = [] as LiteElement[];
|
||||
if (ns) {
|
||||
return tags; // we don't have namespaces
|
||||
}
|
||||
let n: LiteNode = node;
|
||||
while (n) {
|
||||
let kind = n.kind;
|
||||
if (kind !== '#text' && kind !== '#comment') {
|
||||
n = n as LiteElement;
|
||||
if (kind === name) {
|
||||
tags.push(n);
|
||||
}
|
||||
if (n.children.length) {
|
||||
stack = n.children.concat(stack);
|
||||
}
|
||||
}
|
||||
n = stack.shift();
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {LiteElement} node The node to be searched
|
||||
* @param {string} id The id of the node to look for
|
||||
* @return {LiteElement} The child node having the given id
|
||||
*/
|
||||
public elementById(node: LiteElement, id: string): LiteElement {
|
||||
let stack = [] as LiteNode[];
|
||||
let n = node as LiteNode;
|
||||
while (n) {
|
||||
if (n.kind !== '#text' && n.kind !== '#comment') {
|
||||
n = n as LiteElement;
|
||||
if (n.attributes['id'] === id) {
|
||||
return n;
|
||||
}
|
||||
if (n.children.length) {
|
||||
stack = n.children.concat(stack);
|
||||
}
|
||||
}
|
||||
n = stack.shift();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {LiteElement} node The node to be searched
|
||||
* @param {string} name The name of the class to find
|
||||
* @return {LiteElement[]} The nodes with the given class
|
||||
*/
|
||||
public elementsByClass(node: LiteElement, name: string): LiteElement[] {
|
||||
let stack = [] as LiteNode[];
|
||||
let tags = [] as LiteElement[];
|
||||
let n: LiteNode = node;
|
||||
while (n) {
|
||||
if (n.kind !== '#text' && n.kind !== '#comment') {
|
||||
n = n as LiteElement;
|
||||
const classes = (n.attributes['class'] || '').trim().split(/ +/);
|
||||
if (classes.includes(name)) {
|
||||
tags.push(n);
|
||||
}
|
||||
if (n.children.length) {
|
||||
stack = n.children.concat(stack);
|
||||
}
|
||||
}
|
||||
n = stack.shift();
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public getElements(nodes: (string | LiteElement | LiteElement[])[], document: LiteDocument) {
|
||||
let containers = [] as LiteElement[];
|
||||
const body = this.body(document);
|
||||
for (const node of nodes) {
|
||||
if (typeof(node) === 'string') {
|
||||
if (node.charAt(0) === '#') {
|
||||
const n = this.elementById(body, node.slice(1));
|
||||
if (n) {
|
||||
containers.push(n);
|
||||
}
|
||||
} else if (node.charAt(0) === '.') {
|
||||
containers = containers.concat(this.elementsByClass(body, node.slice(1)));
|
||||
} else if (node.match(/^[-a-z][-a-z0-9]*$/i)) {
|
||||
containers = containers.concat(this.tags(body, node));
|
||||
}
|
||||
} else if (Array.isArray(node)) {
|
||||
containers = containers.concat(node);
|
||||
} else if (node instanceof this.window.NodeList || node instanceof this.window.HTMLCollection) {
|
||||
containers = containers.concat((node as LiteList<LiteElement>).nodes);
|
||||
} else {
|
||||
containers.push(node);
|
||||
}
|
||||
}
|
||||
return containers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public contains(container: LiteNode, node: LiteNode | LiteText) {
|
||||
while (node && node !== container) {
|
||||
node = this.parent(node);
|
||||
}
|
||||
return !!node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public parent(node: LiteNode) {
|
||||
return node.parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {LiteNode} node The node whose index is needed
|
||||
* @return {number} The index of the node it its parent's children array
|
||||
*/
|
||||
public childIndex(node: LiteNode): number {
|
||||
return (node.parent ? node.parent.children.findIndex(n => n === node) : -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public append(node: LiteElement, child: LiteNode) {
|
||||
if (child.parent) {
|
||||
this.remove(child);
|
||||
}
|
||||
node.children.push(child);
|
||||
child.parent = node;
|
||||
return child;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public insert(nchild: LiteNode, ochild: LiteNode) {
|
||||
if (nchild.parent) {
|
||||
this.remove(nchild);
|
||||
}
|
||||
if (ochild && ochild.parent) {
|
||||
const i = this.childIndex(ochild);
|
||||
ochild.parent.children.splice(i, 0, nchild);
|
||||
nchild.parent = ochild.parent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public remove(child: LiteNode) {
|
||||
const i = this.childIndex(child);
|
||||
if (i >= 0) {
|
||||
child.parent.children.splice(i, 1);
|
||||
}
|
||||
child.parent = null;
|
||||
return child;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public replace(nnode: LiteNode, onode: LiteNode) {
|
||||
const i = this.childIndex(onode);
|
||||
if (i >= 0) {
|
||||
onode.parent.children[i] = nnode;
|
||||
nnode.parent = onode.parent;
|
||||
onode.parent = null;
|
||||
}
|
||||
return onode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public clone(node: LiteElement) {
|
||||
const nnode = new LiteElement(node.kind);
|
||||
nnode.attributes = {...node.attributes};
|
||||
nnode.children = node.children.map(n => {
|
||||
if (n.kind === '#text') {
|
||||
return new LiteText((n as LiteText).value);
|
||||
} else if (n.kind === '#comment') {
|
||||
return new LiteComment((n as LiteComment).value);
|
||||
} else {
|
||||
const m = this.clone(n as LiteElement);
|
||||
m.parent = nnode;
|
||||
return m;
|
||||
}
|
||||
});
|
||||
return nnode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public split(node: LiteText, n: number) {
|
||||
const text = new LiteText(node.value.slice(n));
|
||||
node.value = node.value.slice(0, n);
|
||||
node.parent.children.splice(this.childIndex(node) + 1, 0, text);
|
||||
text.parent = node.parent;
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public next(node: LiteNode) {
|
||||
const parent = node.parent;
|
||||
if (!parent) return null;
|
||||
const i = this.childIndex(node) + 1;
|
||||
return (i >= 0 && i < parent.children.length ? parent.children[i] : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public previous(node: LiteNode) {
|
||||
const parent = node.parent;
|
||||
if (!parent) return null;
|
||||
const i = this.childIndex(node) - 1;
|
||||
return (i >= 0 ? parent.children[i] : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public firstChild(node: LiteElement) {
|
||||
return node.children[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public lastChild(node: LiteElement) {
|
||||
return node.children[node.children.length - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public childNodes(node: LiteElement) {
|
||||
return [...node.children];
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public childNode(node: LiteElement, i: number) {
|
||||
return node.children[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public kind(node: LiteNode) {
|
||||
return node.kind;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public value(node: LiteNode | LiteText) {
|
||||
return (node.kind === '#text' ? (node as LiteText).value :
|
||||
node.kind === '#comment' ? (node as LiteComment).value.replace(/^<!(--)?((?:.|\n)*)\1>$/, '$2') : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public textContent(node: LiteElement): string {
|
||||
return node.children.reduce((s: string, n: LiteNode) => {
|
||||
return s + (n.kind === '#text' ? (n as LiteText).value :
|
||||
n.kind === '#comment' ? '' : this.textContent(n as LiteElement));
|
||||
}, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public innerHTML(node: LiteElement): string {
|
||||
return this.parser.serializeInner(this, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public outerHTML(node: LiteElement): string {
|
||||
return this.parser.serialize(this, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public serializeXML(node: LiteElement): string {
|
||||
return this.parser.serialize(this, node, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public setAttribute(node: LiteElement, name: string, value: string | number, ns: string = null) {
|
||||
if (typeof value !== 'string') {
|
||||
value = String(value);
|
||||
}
|
||||
if (ns) {
|
||||
name = ns.replace(/.*\//, '') + ':' + name.replace(/^.*:/, '');
|
||||
}
|
||||
node.attributes[name] = value;
|
||||
if (name === 'style') {
|
||||
node.styles = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public getAttribute(node: LiteElement, name: string) {
|
||||
return node.attributes[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public removeAttribute(node: LiteElement, name: string) {
|
||||
delete node.attributes[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public hasAttribute(node: LiteElement, name: string) {
|
||||
return node.attributes.hasOwnProperty(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public allAttributes(node: LiteElement) {
|
||||
const attributes = node.attributes;
|
||||
const list = [];
|
||||
for (const name of Object.keys(attributes)) {
|
||||
list.push({name: name, value: attributes[name] as string});
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public addClass(node: LiteElement, name: string) {
|
||||
const classes = (node.attributes['class'] as string || '').split(/ /);
|
||||
if (!classes.find(n => n === name)) {
|
||||
classes.push(name);
|
||||
node.attributes['class'] = classes.join(' ');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public removeClass(node: LiteElement, name: string) {
|
||||
const classes = (node.attributes['class'] as string || '').split(/ /);
|
||||
const i = classes.findIndex(n => n === name);
|
||||
if (i >= 0) {
|
||||
classes.splice(i, 1);
|
||||
node.attributes['class'] = classes.join(' ');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public hasClass(node: LiteElement, name: string) {
|
||||
const classes = (node.attributes['class'] as string || '').split(/ /);
|
||||
return !!classes.find(n => n === name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public setStyle(node: LiteElement, name: string, value: string) {
|
||||
if (!node.styles) {
|
||||
node.styles = new Styles(this.getAttribute(node, 'style'));
|
||||
}
|
||||
node.styles.set(name, value);
|
||||
node.attributes['style'] = node.styles.cssText;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public getStyle(node: LiteElement, name: string) {
|
||||
if (!node.styles) {
|
||||
const style = this.getAttribute(node, 'style');
|
||||
if (!style) {
|
||||
return '';
|
||||
}
|
||||
node.styles = new Styles(style);
|
||||
}
|
||||
return node.styles.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public allStyles(node: LiteElement) {
|
||||
return this.getAttribute(node, 'style');
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public insertRules(node: LiteElement, rules: string[]) {
|
||||
node.children = [this.text(rules.join('\n\n') + '\n\n' + this.textContent(node))];
|
||||
}
|
||||
|
||||
/*******************************************************************/
|
||||
/*
|
||||
* The next four methods get overridden by the NodeMixin below
|
||||
*/
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public fontSize(_node: LiteElement) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public fontFamily(_node: LiteElement) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public nodeSize(_node: LiteElement, _em: number = 1, _local: boolean = null) {
|
||||
return [0, 0] as [number, number];
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public nodeBBox(_node: LiteElement) {
|
||||
return {left: 0, right: 0, top: 0, bottom: 0};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The LiteAdaptor class (add in the NodeMixin methods and options)
|
||||
*/
|
||||
export class LiteAdaptor extends NodeMixin<LiteElement, LiteText, LiteDocument, Constructor<LiteBase>>(LiteBase) {}
|
||||
|
||||
/************************************************************/
|
||||
/**
|
||||
* The function to call to obtain a LiteAdaptor
|
||||
*
|
||||
* @param {OptionList} options The options for the adaptor
|
||||
* @return {LiteAdaptor} The newly created adaptor
|
||||
*/
|
||||
export function liteAdaptor(options: OptionList = null): LiteAdaptor {
|
||||
return new LiteAdaptor(null, options);
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 Handles using MathJax global as config object, and to hold
|
||||
* methods and data to be available to the page author
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {VERSION} from './version.js';
|
||||
|
||||
/**
|
||||
* The MathJax variable as a configuration object
|
||||
*/
|
||||
export interface MathJaxConfig {
|
||||
[name: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* The object used to store class and other definitions
|
||||
* from the various MathJax modules so that they can be shared
|
||||
* among the various component webpack files
|
||||
*/
|
||||
export interface MathJaxLibrary {
|
||||
[name: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* The MathJax global object structure
|
||||
*/
|
||||
export interface MathJaxObject {
|
||||
version: string;
|
||||
_: MathJaxLibrary;
|
||||
config: MathJaxConfig;
|
||||
}
|
||||
|
||||
declare const global: {MathJax: MathJaxObject | MathJaxConfig};
|
||||
|
||||
/**
|
||||
* @param {any} x An item to test if it is an object
|
||||
* @return {boolean} True if the item is a non-null object
|
||||
*/
|
||||
export function isObject(x: any): boolean {
|
||||
return typeof x === 'object' && x !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine user-produced configuration with existing defaults. Values
|
||||
* from src will replace those in dst.
|
||||
*
|
||||
* @param {any} dst The destination config object (to be merged into)
|
||||
* @param {any} src The source configuration object (to replace defaul values in dst}
|
||||
* @return {any} The resulting (modified) config object
|
||||
*/
|
||||
export function combineConfig(dst: any, src: any): any {
|
||||
for (const id of Object.keys(src)) {
|
||||
if (id === '__esModule') continue;
|
||||
if (isObject(dst[id]) && isObject(src[id]) &&
|
||||
!(src[id] instanceof Promise) /* needed for IE polyfill */) {
|
||||
combineConfig(dst[id], src[id]);
|
||||
} else if (src[id] !== null && src[id] !== undefined) {
|
||||
dst[id] = src[id];
|
||||
}
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine defaults into a configuration that may already have
|
||||
* user-provided values. Values in src only go into dst if
|
||||
* there is not already a value for that key.
|
||||
*
|
||||
* @param {any} dst The destination config object (to be merged into)
|
||||
* @param {string} name The id of the configuration block to modify (created if doesn't exist)
|
||||
* @param {any} src The source configuration object (to replace defaul values in dst}
|
||||
* @return {any} The resulting (modified) config object
|
||||
*/
|
||||
export function combineDefaults(dst: any, name: string, src: any): any {
|
||||
if (!dst[name]) {
|
||||
dst[name] = {};
|
||||
}
|
||||
dst = dst[name];
|
||||
for (const id of Object.keys(src)) {
|
||||
if (isObject(dst[id]) && isObject(src[id])) {
|
||||
combineDefaults(dst, id, src[id]);
|
||||
} else if (dst[id] == null && src[id] != null) {
|
||||
dst[id] = src[id];
|
||||
}
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine configuration or data with the existing MathJax object
|
||||
*
|
||||
* @param {any} config The data to be merged into the MathJax object
|
||||
*/
|
||||
export function combineWithMathJax(config: any): MathJaxObject {
|
||||
return combineConfig(MathJax, config);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create the MathJax global, if it doesn't exist
|
||||
*/
|
||||
if (typeof global.MathJax === 'undefined') {
|
||||
global.MathJax = {} as MathJaxConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the global is currently a config object, convert it to the
|
||||
* MathJaxObject containing the version, class library, and user
|
||||
* configuration.
|
||||
*/
|
||||
if (!(global.MathJax as MathJaxObject).version) {
|
||||
global.MathJax = {
|
||||
version: VERSION,
|
||||
_: {},
|
||||
config: global.MathJax
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the global MathJax object for convenience
|
||||
*/
|
||||
export const MathJax = global.MathJax as MathJaxObject;
|
||||
+407
@@ -0,0 +1,407 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* Copyright (c) 2019-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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The data for a CDN
|
||||
*/
|
||||
type CdnData = {
|
||||
api: string, // URL for JSON containing version number
|
||||
key: string, // key for versionb string in JSON data
|
||||
base?: string // base URL for MathJax on the CDN (version is appended to get actual URL)
|
||||
};
|
||||
|
||||
/**
|
||||
* A map of server names to CDN data
|
||||
*/
|
||||
type CdnList = Map<string, CdnData>;
|
||||
|
||||
/**
|
||||
* The data from a script tag for latest.js
|
||||
*/
|
||||
type ScriptData = {
|
||||
tag: HTMLScriptElement, // the script DOM element
|
||||
src: string, // the script's (possibly modified) source attribute
|
||||
id: string, // the script's (possibly empty) id string
|
||||
version: string, // the MathJax version where latest.js was loaded
|
||||
dir: string, // the subdirectory where latest.js was loaded from (e.g., /es5)
|
||||
file: string, // the file to be loaded by latest.js
|
||||
cdn: CdnData // the CDN where latest.js was loaded
|
||||
} | null;
|
||||
|
||||
/**
|
||||
* Add XMLHttpRequest and ActiveXObject (for IE)
|
||||
*/
|
||||
declare const window: {
|
||||
XMLHttpRequest: XMLHttpRequest;
|
||||
ActiveXObject: any;
|
||||
};
|
||||
|
||||
/*=====================================================================*/
|
||||
|
||||
|
||||
/**
|
||||
* The various CDNs and their data for how to obtain versions
|
||||
*/
|
||||
const CDN: CdnList = new Map([
|
||||
['cdnjs.cloudflare.com', {
|
||||
api: 'https://api.cdnjs.com/libraries/mathjax?fields=version',
|
||||
key: 'version',
|
||||
base: 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/'
|
||||
}],
|
||||
|
||||
['rawcdn.githack.com', {
|
||||
api: 'https://api.github.com/repos/mathjax/mathjax/releases/latest',
|
||||
key: 'tag_name',
|
||||
base: 'https://rawcdn.githack.com/mathjax/MathJax/'
|
||||
}],
|
||||
|
||||
['gitcdn.xyz', {
|
||||
api: 'https://api.github.com/repos/mathjax/mathjax/releases/latest',
|
||||
key: 'tag_name',
|
||||
base: 'https://gitcdn.xyz/mathjax/MathJax/'
|
||||
}],
|
||||
|
||||
['cdn.statically.io', {
|
||||
api: 'https://api.github.com/repos/mathjax/mathjax/releases/latest',
|
||||
key: 'tag_name',
|
||||
base: 'https://cdn.statically.io/gh/mathjax/MathJax/'
|
||||
}],
|
||||
|
||||
['unpkg.com', {
|
||||
api: 'https://api.github.com/repos/mathjax/mathjax/releases/latest',
|
||||
key: 'tag_name',
|
||||
base: 'https://unpkg.com/mathjax@'
|
||||
}],
|
||||
|
||||
['cdn.jsdelivr.net', {
|
||||
api: 'https://api.github.com/repos/mathjax/mathjax/releases/latest',
|
||||
key: 'tag_name',
|
||||
base: 'https://cdn.jsdelivr.net/npm/mathjax@'
|
||||
}]
|
||||
]);
|
||||
|
||||
/**
|
||||
* The data for getting release versions from GitHub
|
||||
*/
|
||||
const GITHUB: CdnData = {
|
||||
api: 'https://api.github.com/repos/mathjax/mathjax/releases',
|
||||
key: 'tag_name'
|
||||
};
|
||||
|
||||
/**
|
||||
* The major version number for MathJax (we will load the highest version with this initial number)
|
||||
*/
|
||||
const MJX_VERSION = 3;
|
||||
|
||||
/**
|
||||
* The name to use for the version in localStorage
|
||||
*/
|
||||
const MJX_LATEST = 'mjx-latest-version';
|
||||
|
||||
/**
|
||||
* The amount of time a cached version number is valid
|
||||
*/
|
||||
const SAVE_TIME = 1000 * 60 * 60 * 24 * 7; // one week
|
||||
|
||||
/**
|
||||
* Data for the script that loaded latest.js
|
||||
*/
|
||||
let script: ScriptData = null;
|
||||
|
||||
/*=====================================================================*/
|
||||
|
||||
/**
|
||||
* Produce an error message on the console
|
||||
*
|
||||
* @param {string} message The error message to display
|
||||
*/
|
||||
function Error(message: string) {
|
||||
if (console && console.error) {
|
||||
console.error('MathJax(latest.js): ' + message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ScriptData object from the given script tag and CDN
|
||||
*
|
||||
* @param {HTMLScriptElement} script The script tag whose data is desired
|
||||
* @param {CdnData} cdn The CDN data already obtained for the script (or null)
|
||||
* @return {ScriptData} The data for the given script
|
||||
*/
|
||||
function scriptData(script: HTMLScriptElement, cdn: CdnData = null): ScriptData {
|
||||
script.parentNode.removeChild(script);
|
||||
let src = script.src;
|
||||
let file = src.replace(/.*?\/latest\.js(\?|$)/, '');
|
||||
if (file === '') {
|
||||
file = 'startup.js';
|
||||
src = src.replace(/\?$/, '') + '?' + file;
|
||||
}
|
||||
const version = (src.match(/(\d+\.\d+\.\d+)(\/es\d+)?\/latest.js\?/) || ['', ''])[1];
|
||||
const dir = (src.match(/(\/es\d+)\/latest.js\?/) || ['', ''])[1] || '';
|
||||
return {
|
||||
tag: script,
|
||||
src: src,
|
||||
id: script.id,
|
||||
version: version,
|
||||
dir: dir,
|
||||
file: file,
|
||||
cdn: cdn
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a script refers to MathJax on one of the CDNs
|
||||
*
|
||||
* @param {HTMLScriptElement} script The script tag to check
|
||||
* @return {ScriptData | null} Non-null if the script is from a MathJax CDN
|
||||
*/
|
||||
function checkScript(script: HTMLScriptElement): ScriptData | null {
|
||||
for (const server of CDN.keys()) {
|
||||
const cdn = CDN.get(server);
|
||||
const url = cdn.base;
|
||||
const src = script.src;
|
||||
if (src && src.substr(0, url.length) === url && src.match(/\/latest\.js(\?|$)/)) {
|
||||
return scriptData(script, cdn);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {ScriptData} The data for the script tag that loaded latest.js
|
||||
*/
|
||||
function getScript(): ScriptData {
|
||||
if (document.currentScript) {
|
||||
return scriptData(document.currentScript as HTMLScriptElement);
|
||||
}
|
||||
const script = document.getElementById('MathJax-script') as HTMLScriptElement;
|
||||
if (script && script.nodeName.toLowerCase() === 'script') {
|
||||
return checkScript(script);
|
||||
}
|
||||
const scripts = document.getElementsByTagName('script');
|
||||
for (const script of Array.from(scripts)) {
|
||||
const data = checkScript(script);
|
||||
if (data) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*=====================================================================*/
|
||||
|
||||
/**
|
||||
* Save the version and date information in localStorage so we don't
|
||||
* have to contact the CDN for every page that uses MathJax.
|
||||
*
|
||||
* @param {string} version The version to save
|
||||
*/
|
||||
function saveVersion(version: string) {
|
||||
try {
|
||||
const data = version + ' ' + Date.now();
|
||||
localStorage.setItem(MJX_LATEST, data);
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version from localStorage, and make sure it is fresh enough to use
|
||||
*
|
||||
* @return {string|null} The version string (if one has been saved) or null (if not)
|
||||
*/
|
||||
function getSavedVersion(): string | null {
|
||||
try {
|
||||
const [version, date] = localStorage.getItem(MJX_LATEST).split(/ /);
|
||||
if (date && Date.now() - parseInt(date) < SAVE_TIME) {
|
||||
return version;
|
||||
}
|
||||
} catch (err) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*=====================================================================*/
|
||||
|
||||
/**
|
||||
* Create a script tag that loads the given URL
|
||||
*
|
||||
* @param {string} url The URL of the javascript file to be loaded
|
||||
* @param {string} id The id to use for the script tag
|
||||
*/
|
||||
function loadMathJax(url: string, id: string) {
|
||||
const script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.async = true;
|
||||
script.src = url;
|
||||
if (id) {
|
||||
script.id = id;
|
||||
}
|
||||
const head = document.head || document.getElementsByTagName('head')[0] || document.body;
|
||||
if (head) {
|
||||
head.appendChild(script);
|
||||
} else {
|
||||
Error('Can\'t find the document <head> element');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When we can't find the current version, use the original URL but remove the "latest.js"
|
||||
*/
|
||||
function loadDefaultMathJax() {
|
||||
if (script) {
|
||||
loadMathJax(script.src.replace(/\/latest\.js\?/, '/'), script.id);
|
||||
} else {
|
||||
Error('Can\'t determine the URL for loading MathJax');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the given version using the base URL and file to load
|
||||
* (if the versions differ, run latest.js from the new version
|
||||
* in case there are important changes there)
|
||||
*
|
||||
* @param {string} version The version of MathJax to load from
|
||||
*/
|
||||
function loadVersion(version: string) {
|
||||
if (script.version && script.version !== version) {
|
||||
script.file = 'latest.js?' + script.file;
|
||||
}
|
||||
loadMathJax(script.cdn.base + version + script.dir + '/' + script.file, script.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given version is acceptable and load it if it is.
|
||||
*
|
||||
* @param {string} version The version to check if it is the latest (valid) one
|
||||
* @return {boolean} True if it is the latest version, false if not
|
||||
*/
|
||||
function checkVersion(version: string): boolean {
|
||||
const major = parseInt(version.split(/\./)[0]);
|
||||
if (major === MJX_VERSION && !version.match(/-(beta|rc)/)) {
|
||||
saveVersion(version);
|
||||
loadVersion(version);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*=====================================================================*/
|
||||
|
||||
/**
|
||||
* Create an XMLHttpRequest object, if possible
|
||||
*
|
||||
* @return {XMLHttpRequest} The XMLHttpRequest instance
|
||||
*/
|
||||
function getXMLHttpRequest(): XMLHttpRequest {
|
||||
if (window.XMLHttpRequest) {
|
||||
return new XMLHttpRequest();
|
||||
}
|
||||
if (window.ActiveXObject) {
|
||||
try { return new window.ActiveXObject('Msxml2.XMLHTTP'); } catch (err) {}
|
||||
try { return new window.ActiveXObject('Microsoft.XMLHTTP'); } catch (err) {}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request JSON data from a CDN. If it loads OK, call the action() function
|
||||
* on the data. If not, or if the action returns false, run the failure() function.
|
||||
*
|
||||
* @param {CdnData} cdn The CDN whose API will be used
|
||||
* @param {Function} action The function to perform when the data are received
|
||||
* @param {Function} failure The function to perform if data can't be obtained,
|
||||
* or if action() returns false
|
||||
*/
|
||||
function requestXML(cdn: CdnData, action: (json: JSON | JSON[]) => boolean, failure: () => void) {
|
||||
const request = getXMLHttpRequest();
|
||||
if (request) {
|
||||
// tslint:disable-next-line:jsdoc-require
|
||||
request.onreadystatechange = function () {
|
||||
if (request.readyState === 4) {
|
||||
if (request.status === 200) {
|
||||
!action(JSON.parse(request.responseText)) && failure();
|
||||
} else {
|
||||
Error('Problem acquiring MathJax version: status = ' + request.status);
|
||||
failure();
|
||||
}
|
||||
}
|
||||
};
|
||||
request.open('GET', cdn.api, true);
|
||||
request.send(null);
|
||||
} else {
|
||||
Error('Can\'t create XMLHttpRequest object');
|
||||
failure();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Look through the list of versions on GitHub and find the first one that
|
||||
* has the MJX_VERSION as its major version number, and load that. If none
|
||||
* is found, run the version from which latest.js was loaded.
|
||||
*/
|
||||
function loadLatestGitVersion() {
|
||||
requestXML(GITHUB, (json: JSON[]) => {
|
||||
if (!(json instanceof Array)) return false;
|
||||
for (const data of json) {
|
||||
if (checkVersion((data as any)[GITHUB.key])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, loadDefaultMathJax);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the CDN for its latest version, and load that, if it is an
|
||||
* acceptable version, otherwise, (e.g., the current version has a
|
||||
* higher major version that MJX_VERSION), find the highest version on
|
||||
* GitHub with the given major version and use that. If one can't be
|
||||
* found, use the version where latest.js was loaded.
|
||||
*/
|
||||
function loadLatestCdnVersion() {
|
||||
requestXML(script.cdn, function (json) {
|
||||
if (json instanceof Array) {
|
||||
json = json[0];
|
||||
}
|
||||
if (!checkVersion((json as any)[script.cdn.key])) {
|
||||
loadLatestGitVersion();
|
||||
}
|
||||
return true;
|
||||
}, loadDefaultMathJax);
|
||||
}
|
||||
|
||||
/*=====================================================================*/
|
||||
|
||||
|
||||
/**
|
||||
* Find the script that loaded latest.js
|
||||
* If the script is from a known CDN:
|
||||
* Retrieve the cached version (if any)
|
||||
* Load the given version of the file, if the version is cached,
|
||||
* Otherwise find the latest version and load that.
|
||||
* Otherwise,
|
||||
* Load using the version where latest.js was loaded.
|
||||
*/
|
||||
export function loadLatest() {
|
||||
script = getScript();
|
||||
if (script && script.cdn) {
|
||||
const version = getSavedVersion();
|
||||
version ?
|
||||
loadVersion(version) :
|
||||
loadLatestCdnVersion();
|
||||
} else {
|
||||
loadDefaultMathJax();
|
||||
}
|
||||
}
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 A dynamic loader for loading MathJax components based
|
||||
* on a user configuration, while handling timing of
|
||||
* dependencies properly
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {MathJax as MJGlobal, MathJaxObject as MJObject, MathJaxLibrary,
|
||||
MathJaxConfig as MJConfig, combineWithMathJax, combineDefaults} from './global.js';
|
||||
|
||||
import {Package, PackageError, PackageReady, PackageFailed} from './package.js';
|
||||
export {Package, PackageError, PackageReady, PackageFailed} from './package.js';
|
||||
export {MathJaxLibrary} from './global.js';
|
||||
|
||||
import {FunctionList} from '../util/FunctionList.js';
|
||||
|
||||
/*
|
||||
* The current directory (for webpack), and the browser document (if any)
|
||||
*/
|
||||
declare var __dirname: string;
|
||||
declare var document: Document;
|
||||
|
||||
/**
|
||||
* Function used to determine path to a given package.
|
||||
*/
|
||||
export type PathFilterFunction = (data: {name: string, original: string, addExtension: boolean}) => boolean;
|
||||
export type PathFilterList = (PathFilterFunction | [PathFilterFunction, number])[];
|
||||
|
||||
/**
|
||||
* Update the configuration structure to include the loader configuration
|
||||
*/
|
||||
export interface MathJaxConfig extends MJConfig {
|
||||
loader?: {
|
||||
paths?: {[name: string]: string}; // The path prefixes for use in locations
|
||||
source?: {[name: string]: string}; // The URLs for the extensions, e.g., tex: [mathjax]/input/tex.js
|
||||
dependencies?: {[name: string]: string[]}; // The dependencies for each package
|
||||
provides?: {[name: string]: string[]}; // The sub-packages provided by each package
|
||||
load?: string[]; // The packages to load (found in locations or [mathjax]/name])
|
||||
ready?: PackageReady; // A function to call when MathJax is ready
|
||||
failed?: PackageFailed; // A function to call when MathJax fails to load
|
||||
require?: (url: string) => any; // A function for loading URLs
|
||||
pathFilters?: PathFilterList; // List of path filters (and optional priorities) to add
|
||||
versionWarnings?: boolean; // True means warn when extension version doesn't match MJ version
|
||||
[name: string]: any; // Other configuration blocks
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the MathJax object to inclide the loader information
|
||||
*/
|
||||
export interface MathJaxObject extends MJObject {
|
||||
_: MathJaxLibrary;
|
||||
config: MathJaxConfig;
|
||||
loader: {
|
||||
ready: (...names: string[]) => Promise<string[]>; // Get a promise for when all the named packages are loaded
|
||||
load: (...names: string[]) => Promise<string>; // Load the packages and return a promise for when ready
|
||||
preLoad: (...names: string[]) => void; // Indicate that packages are already loaded by hand
|
||||
defaultReady: () => void; // The function performed when all packages are loaded
|
||||
getRoot: () => string; // Find the root URL for the MathJax files
|
||||
checkVersion: (name: string, version: string) => boolean; // Check the version of an extension
|
||||
pathFilters: FunctionList; // the filters to use for looking for package paths
|
||||
};
|
||||
startup?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Functions used to filter the path to a package
|
||||
*/
|
||||
export const PathFilters: {[name: string]: PathFilterFunction} = {
|
||||
/**
|
||||
* Look up the path in the configuration's source list
|
||||
*/
|
||||
source: (data) => {
|
||||
if (CONFIG.source.hasOwnProperty(data.name)) {
|
||||
data.name = CONFIG.source[data.name];
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add [mathjax] before any relative path, and add .js if needed
|
||||
*/
|
||||
normalize: (data) => {
|
||||
const name = data.name;
|
||||
if (!name.match(/^(?:[a-z]+:\/)?\/|[a-z]:\\|\[/i)) {
|
||||
data.name = '[mathjax]/' + name.replace(/^\.\//, '');
|
||||
}
|
||||
if (data.addExtension && !name.match(/\.[^\/]+$/)) {
|
||||
data.name += '.js';
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Recursively replace path prefixes (e.g., [mathjax], [tex], etc.)
|
||||
*/
|
||||
prefix: (data) => {
|
||||
let match;
|
||||
while ((match = data.name.match(/^\[([^\]]*)\]/))) {
|
||||
if (!CONFIG.paths.hasOwnProperty(match[1])) break;
|
||||
data.name = CONFIG.paths[match[1]] + data.name.substr(match[0].length);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The implementation of the dynamic loader
|
||||
*/
|
||||
export namespace Loader {
|
||||
|
||||
/**
|
||||
* The version of MathJax that is running.
|
||||
*/
|
||||
const VERSION = MJGlobal.version;
|
||||
|
||||
/**
|
||||
* The versions of all the loaded extensions.
|
||||
*/
|
||||
export const versions: Map<string, string> = new Map();
|
||||
|
||||
/**
|
||||
* Get a promise that is resolved when all the named packages have been loaded.
|
||||
*
|
||||
* @param {string[]} names The packages to wait for
|
||||
* @returns {Promise} A promise that resolves when all the named packages are ready
|
||||
*/
|
||||
export function ready(...names: string[]): Promise<string[]> {
|
||||
if (names.length === 0) {
|
||||
names = Array.from(Package.packages.keys());
|
||||
}
|
||||
const promises = [];
|
||||
for (const name of names) {
|
||||
const extension = Package.packages.get(name) || new Package(name, true);
|
||||
promises.push(extension.promise);
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the named packages and return a promise that is resolved when they are all loaded
|
||||
*
|
||||
* @param {string[]} names The packages to load
|
||||
* @returns {Promise} A promise that resolves when all the named packages are ready
|
||||
*/
|
||||
export function load(...names: string[]): Promise<void | string[]> {
|
||||
if (names.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const promises = [];
|
||||
for (const name of names) {
|
||||
let extension = Package.packages.get(name);
|
||||
if (!extension) {
|
||||
extension = new Package(name);
|
||||
extension.provides(CONFIG.provides[name]);
|
||||
}
|
||||
extension.checkNoLoad();
|
||||
promises.push(extension.promise.then(() => {
|
||||
if (!CONFIG.versionWarnings) return;
|
||||
if (extension.isLoaded && !versions.has(Package.resolvePath(name))) {
|
||||
console.warn(`No version information available for component ${name}`);
|
||||
}
|
||||
}) as Promise<null>);
|
||||
}
|
||||
Package.loadAll();
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the named packages are being loaded by hand (e.g., as part of a larger package).
|
||||
*
|
||||
* @param {string[]} names The packages to load
|
||||
*/
|
||||
export function preLoad(...names: string[]) {
|
||||
for (const name of names) {
|
||||
let extension = Package.packages.get(name);
|
||||
if (!extension) {
|
||||
extension = new Package(name, true);
|
||||
extension.provides(CONFIG.provides[name]);
|
||||
}
|
||||
extension.loaded();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The default function to perform when all the packages are loaded
|
||||
*/
|
||||
export function defaultReady() {
|
||||
if (typeof MathJax.startup !== 'undefined') {
|
||||
MathJax.config.startup.ready();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the root location for where the MathJax package files are found
|
||||
*
|
||||
* @returns {string} The root location (directory for node.js, URL for browser)
|
||||
*/
|
||||
export function getRoot(): string {
|
||||
let root = __dirname + '/../../es5';
|
||||
if (typeof document !== 'undefined') {
|
||||
const script = document.currentScript || document.getElementById('MathJax-script');
|
||||
if (script) {
|
||||
root = (script as HTMLScriptElement).src.replace(/\/[^\/]*$/, '');
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the version of an extension and report an error if not correct
|
||||
*
|
||||
* @param {string} name The name of the extension being checked
|
||||
* @param {string} version The version of the extension to check
|
||||
* @param {string} type The type of extension (future code may use this to check ranges of versions)
|
||||
* @return {boolean} True if there was a mismatch, false otherwise
|
||||
*/
|
||||
export function checkVersion(name: string, version: string, _type?: string): boolean {
|
||||
versions.set(Package.resolvePath(name), VERSION);
|
||||
if (CONFIG.versionWarnings && version !== VERSION) {
|
||||
console.warn(`Component ${name} uses ${version} of MathJax; version in use is ${VERSION}`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The filters to use to modify the paths used to obtain the packages
|
||||
*/
|
||||
export const pathFilters = new FunctionList();
|
||||
|
||||
/**
|
||||
* The default filters to use.
|
||||
*/
|
||||
pathFilters.add(PathFilters.source, 0);
|
||||
pathFilters.add(PathFilters.normalize, 10);
|
||||
pathFilters.add(PathFilters.prefix, 20);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the global MathJax object for convenience
|
||||
*/
|
||||
export const MathJax = MJGlobal as MathJaxObject;
|
||||
|
||||
/*
|
||||
* If the loader hasn't been added to the MathJax variable,
|
||||
* Add the loader configuration, library, and data objects.
|
||||
* Add any path filters from the configuration.
|
||||
*/
|
||||
if (typeof MathJax.loader === 'undefined') {
|
||||
|
||||
combineDefaults(MathJax.config, 'loader', {
|
||||
paths: {
|
||||
mathjax: Loader.getRoot()
|
||||
},
|
||||
source: {},
|
||||
dependencies: {},
|
||||
provides: {},
|
||||
load: [],
|
||||
ready: Loader.defaultReady.bind(Loader),
|
||||
failed: (error: PackageError) => console.log(`MathJax(${error.package || '?'}): ${error.message}`),
|
||||
require: null,
|
||||
pathFilters: [],
|
||||
versionWarnings: true
|
||||
});
|
||||
combineWithMathJax({
|
||||
loader: Loader
|
||||
});
|
||||
|
||||
//
|
||||
// Add any path filters from the configuration
|
||||
//
|
||||
for (const filter of MathJax.config.loader.pathFilters) {
|
||||
if (Array.isArray(filter)) {
|
||||
Loader.pathFilters.add(filter[0], filter[1]);
|
||||
} else {
|
||||
Loader.pathFilters.add(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the loader configuration for convenience
|
||||
*/
|
||||
export const CONFIG = MathJax.config.loader;
|
||||
+426
@@ -0,0 +1,426 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 component Package object for handling
|
||||
* dynamic loading of components.
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {CONFIG, Loader} from './loader.js';
|
||||
|
||||
/*
|
||||
* The browser document (for creating scripts to load components)
|
||||
*/
|
||||
declare var document: Document;
|
||||
|
||||
/**
|
||||
* A map of package names to Package instances
|
||||
*/
|
||||
export type PackageMap = Map<string, Package>;
|
||||
|
||||
/**
|
||||
* An error class that includes the package name
|
||||
*/
|
||||
export class PackageError extends Error {
|
||||
/* tslint:disable:jsdoc-require */
|
||||
public package: string;
|
||||
constructor(message: string, name: string) {
|
||||
super(message);
|
||||
this.package = name;
|
||||
}
|
||||
/* tslint:enable */
|
||||
}
|
||||
|
||||
/**
|
||||
* Types for ready() and failed() functions and for promises
|
||||
*/
|
||||
export type PackageReady = (name: string) => string | void;
|
||||
export type PackageFailed = (message: PackageError) => void;
|
||||
export type PackagePromise = (resolve: PackageReady, reject: PackageFailed) => void;
|
||||
|
||||
/**
|
||||
* The configuration data for a package
|
||||
*/
|
||||
export interface PackageConfig {
|
||||
ready?: PackageReady; // Function to call when package is loaded successfully
|
||||
failed?: PackageFailed; // Function to call when package fails to load
|
||||
checkReady?: () => Promise<void>; // Function called to see if package is fully loaded
|
||||
// (may cause additional packages to load, for example)
|
||||
}
|
||||
|
||||
/**
|
||||
* The Package class for handling individual components
|
||||
*/
|
||||
export class Package {
|
||||
/**
|
||||
* The set of packages being used
|
||||
*/
|
||||
public static packages: PackageMap = new Map();
|
||||
|
||||
/**
|
||||
* The package name
|
||||
*/
|
||||
public name: string;
|
||||
|
||||
/**
|
||||
* True when the package has been loaded successfully
|
||||
*/
|
||||
public isLoaded: boolean = false;
|
||||
|
||||
/**
|
||||
* A promise that resolves when the package is loaded successfully and rejects when it fails to load
|
||||
*/
|
||||
public promise: Promise<string>;
|
||||
|
||||
/**
|
||||
* True when the package is being loaded but hasn't yet finished loading
|
||||
*/
|
||||
protected isLoading: boolean = false;
|
||||
|
||||
/**
|
||||
* True if the package has failed to load
|
||||
*/
|
||||
protected hasFailed: boolean = false;
|
||||
|
||||
/**
|
||||
* True if this package should be loaded automatically (e.g., it was created in reference
|
||||
* to a MathJax.loader.ready() call when the package hasn't been requested to load)
|
||||
*/
|
||||
protected noLoad: boolean;
|
||||
|
||||
/**
|
||||
* The function that resolves the package's promise
|
||||
*/
|
||||
protected resolve: PackageReady;
|
||||
|
||||
/**
|
||||
* The function that rejects the package's promise
|
||||
*/
|
||||
protected reject: PackageFailed;
|
||||
|
||||
/**
|
||||
* The packages that require this one
|
||||
*/
|
||||
protected dependents: Package[] = [];
|
||||
|
||||
/**
|
||||
* The packages that this one depends on
|
||||
*/
|
||||
protected dependencies: Package[] = [];
|
||||
|
||||
/**
|
||||
* The number of dependencies that haven't yet been loaded
|
||||
*/
|
||||
protected dependencyCount: number = 0;
|
||||
|
||||
/**
|
||||
* The sub-packages that this one provides
|
||||
*/
|
||||
protected provided: Package[] = [];
|
||||
|
||||
/**
|
||||
* @return {boolean} True when the package can be loaded (i.e., its dependencies are all loaded,
|
||||
* it is allowed to be loaded, isn't already loading, and hasn't failed to load
|
||||
* in the past)
|
||||
*/
|
||||
get canLoad(): boolean {
|
||||
return this.dependencyCount === 0 && !this.noLoad && !this.isLoading && !this.hasFailed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the path for a package using the loader's path filters
|
||||
*
|
||||
* @param {string} name The name of the package to resolve
|
||||
* @param {boolean} addExtension True if .js should be added automatically
|
||||
* @return {string} The path (file or URL) for this package
|
||||
*/
|
||||
public static resolvePath(name: string, addExtension: boolean = true): string {
|
||||
const data = {name, original: name, addExtension};
|
||||
Loader.pathFilters.execute(data);
|
||||
return data.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to load all packages that are ready to be loaded
|
||||
* (i.e., that have no unloaded dependencies, and that haven't
|
||||
* already been loaded, and that aren't in process of being
|
||||
* loaded, and that aren't marked as noLoad).
|
||||
*/
|
||||
public static loadAll() {
|
||||
for (const extension of this.packages.values()) {
|
||||
if (extension.canLoad) {
|
||||
extension.load();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name The name of the package
|
||||
* @param {boolean} noLoad True when the package is just for reference, not loading
|
||||
*/
|
||||
constructor(name: string, noLoad: boolean = false) {
|
||||
this.name = name;
|
||||
this.noLoad = noLoad;
|
||||
Package.packages.set(name, this);
|
||||
this.promise = this.makePromise(this.makeDependencies());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Promise<string>[]} The array of promises that must be resolved before this package
|
||||
* can be loaded
|
||||
*/
|
||||
protected makeDependencies(): Promise<string>[] {
|
||||
const promises = [] as Promise<string>[];
|
||||
const map = Package.packages;
|
||||
const noLoad = this.noLoad;
|
||||
const name = this.name;
|
||||
//
|
||||
// Get the dependencies for this package
|
||||
//
|
||||
const dependencies = [] as string[];
|
||||
if (CONFIG.dependencies.hasOwnProperty(name)) {
|
||||
dependencies.push(...CONFIG.dependencies[name]);
|
||||
} else if (name !== 'core') {
|
||||
dependencies.push('core'); // Add 'core' dependency by default
|
||||
}
|
||||
//
|
||||
// Add all the dependencies (creating them, if needed)
|
||||
// and record the promises of unloaded ones
|
||||
//
|
||||
for (const dependent of dependencies) {
|
||||
const extension = map.get(dependent) || new Package(dependent, noLoad);
|
||||
if (this.dependencies.indexOf(extension) < 0) {
|
||||
extension.addDependent(this, noLoad);
|
||||
this.dependencies.push(extension);
|
||||
if (!extension.isLoaded) {
|
||||
this.dependencyCount++;
|
||||
promises.push(extension.promise);
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
// Return the collected promises
|
||||
//
|
||||
return promises;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Promise<string>[]} promises The array or promises that must be resolved before
|
||||
* this package can load
|
||||
*/
|
||||
protected makePromise(promises: Promise<string>[]) {
|
||||
//
|
||||
// Make a promise and save its resolve/reject functions
|
||||
//
|
||||
let promise = new Promise<string>(((resolve, reject) => {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
}) as PackagePromise);
|
||||
//
|
||||
// If there is a ready() function in the configuration for this package,
|
||||
// Add running that to the promise
|
||||
//
|
||||
const config = (CONFIG[this.name] || {}) as PackageConfig;
|
||||
if (config.ready) {
|
||||
promise = promise.then((_name: string) => config.ready(this.name)) as Promise<string>;
|
||||
}
|
||||
//
|
||||
// If there are promises for dependencies,
|
||||
// Add the one for loading this package and create a promise for all of them
|
||||
// (That way, if any of them fail to load, our promise will reject automatically)
|
||||
//
|
||||
if (promises.length) {
|
||||
promises.push(promise);
|
||||
promise = Promise.all(promises).then((names: string[]) => names.join(', '));
|
||||
}
|
||||
//
|
||||
// If there is a failed() function in the configuration for this package,
|
||||
// Add a catch to handle the error
|
||||
//
|
||||
if (config.failed) {
|
||||
promise.catch((message: string) => config.failed(new PackageError(message, this.name)));
|
||||
}
|
||||
//
|
||||
// Return the promise that represents when this file is loaded
|
||||
//
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to load this package
|
||||
*/
|
||||
public load() {
|
||||
if (!this.isLoaded && !this.isLoading && !this.noLoad) {
|
||||
this.isLoading = true;
|
||||
const url = Package.resolvePath(this.name);
|
||||
if (CONFIG.require) {
|
||||
this.loadCustom(url);
|
||||
} else {
|
||||
this.loadScript(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load using a custom require method (usually the one from node.js)
|
||||
*/
|
||||
protected loadCustom(url: string) {
|
||||
try {
|
||||
const result = CONFIG.require(url);
|
||||
if (result instanceof Promise) {
|
||||
result.then(() => this.checkLoad())
|
||||
.catch((err) => this.failed('Can\'t load "' + url + '"\n' + err.message.trim()));
|
||||
} else {
|
||||
this.checkLoad();
|
||||
}
|
||||
} catch (err) {
|
||||
this.failed(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load in a browser by inserting a script to load the proper URL
|
||||
*/
|
||||
protected loadScript(url: string) {
|
||||
const script = document.createElement('script');
|
||||
script.src = url;
|
||||
script.charset = 'UTF-8';
|
||||
script.onload = (_event) => this.checkLoad();
|
||||
script.onerror = (_event) => this.failed('Can\'t load "' + url + '"');
|
||||
// FIXME: Should there be a timeout failure as well?
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the package is loaded.
|
||||
*
|
||||
* Mark it as loaded, and tell its dependents that this package
|
||||
* has been loaded (may cause dependents to load themselves).
|
||||
* Mark any provided packages as loaded.
|
||||
* Resolve the promise that says this package is loaded.
|
||||
*/
|
||||
public loaded() {
|
||||
this.isLoaded = true;
|
||||
this.isLoading = false;
|
||||
for (const dependent of this.dependents) {
|
||||
dependent.requirementSatisfied();
|
||||
}
|
||||
for (const provided of this.provided) {
|
||||
provided.loaded();
|
||||
}
|
||||
this.resolve(this.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the package fails to load for some reason
|
||||
*
|
||||
* Mark it as failed to load
|
||||
* Reject the promise for this package with an error
|
||||
*
|
||||
* @param {string} message The error message for a load failure
|
||||
*/
|
||||
protected failed(message: string) {
|
||||
this.hasFailed = true;
|
||||
this.isLoading = false;
|
||||
this.reject(new PackageError(message, this.name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a package is really ready to be marked as loaded
|
||||
* (When it is loaded, it may set its own checkReady() function
|
||||
* as a means of loading additional packages. E.g., an output
|
||||
* jax may load a font package, dependent on its configuration.)
|
||||
*
|
||||
* The configuration's checkReady() function returns a promise
|
||||
* that allows the loader to wait for addition actions to finish
|
||||
* before marking the file as loaded (or failing to load).
|
||||
*/
|
||||
protected checkLoad() {
|
||||
const config = (CONFIG[this.name] || {}) as PackageConfig;
|
||||
const checkReady = config.checkReady || (() => Promise.resolve());
|
||||
checkReady().then(() => this.loaded())
|
||||
.catch((message) => this.failed(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called when a dependency loads.
|
||||
*
|
||||
* Decrease the dependency count, and try to load this package
|
||||
* when the dependencies are all loaded.
|
||||
*/
|
||||
public requirementSatisfied() {
|
||||
if (this.dependencyCount) {
|
||||
this.dependencyCount--;
|
||||
if (this.canLoad) {
|
||||
this.load();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} names The names of the packages that this package provides
|
||||
*/
|
||||
public provides(names: string[] = []) {
|
||||
for (const name of names) {
|
||||
let provided = Package.packages.get(name);
|
||||
if (!provided) {
|
||||
if (!CONFIG.dependencies[name]) {
|
||||
CONFIG.dependencies[name] = [];
|
||||
}
|
||||
CONFIG.dependencies[name].push(name);
|
||||
provided = new Package(name, true);
|
||||
provided.isLoading = true;
|
||||
}
|
||||
this.provided.push(provided);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a package as a dependent, and if it is not just for reference,
|
||||
* check if we need to change our noLoad status.
|
||||
*
|
||||
* @param {Package} extension The package to add as a dependent
|
||||
* @param {boolean} noLoad The noLoad status of the dependent
|
||||
*/
|
||||
public addDependent(extension: Package, noLoad: boolean) {
|
||||
this.dependents.push(extension);
|
||||
if (!noLoad) {
|
||||
this.checkNoLoad();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If this package is marked as noLoad, change that and check all
|
||||
* our dependencies to see if they need to change their noLoad
|
||||
* status as well.
|
||||
*
|
||||
* I.e., if there are dependencies that were set up for reference
|
||||
* and a leaf node needs to be loaded, make sure all parent nodes
|
||||
* are marked as needing to be loaded as well.
|
||||
*/
|
||||
public checkNoLoad() {
|
||||
if (this.noLoad) {
|
||||
this.noLoad = false;
|
||||
for (const dependency of this.dependencies) {
|
||||
dependency.checkNoLoad();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+579
@@ -0,0 +1,579 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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;
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* Copyright (c) 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 The version of MathJax (used to tell what version a component
|
||||
* was compiled against).
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
export const VERSION = '3.2.2';
|
||||
+668
@@ -0,0 +1,668 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 The DOMAdaptor interface and abstract class
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {OptionList} from '../util/Options.js';
|
||||
|
||||
/**
|
||||
* The data for an attribute
|
||||
*/
|
||||
export type AttributeData = {
|
||||
name: string,
|
||||
value: string
|
||||
};
|
||||
|
||||
/**
|
||||
* The data for an elements page-based bounding box
|
||||
*/
|
||||
export type PageBBox = {
|
||||
left: number,
|
||||
right: number,
|
||||
top: number,
|
||||
bottom: number
|
||||
};
|
||||
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The interface for the DOMAdaptor
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export interface DOMAdaptor<N, T, D> {
|
||||
/**
|
||||
* Document in which the nodes are to be created
|
||||
*/
|
||||
document: D;
|
||||
|
||||
/**
|
||||
* @param {string} text The serialized document to be parsed
|
||||
* @param {string} format The format (e.g., 'text/html' or 'text/xhtml')
|
||||
* @return {D} The parsed document
|
||||
*/
|
||||
parse(text: string, format?: string): D;
|
||||
|
||||
/**
|
||||
* @param {string} kind The tag name of the HTML node to be created
|
||||
* @param {OptionList} def The properties to set for the created node
|
||||
* @param {(N|T)[]} children The child nodes for the created HTML node
|
||||
* @param {string} ns The namespace in which to create the node
|
||||
* @return {N} The generated HTML tree
|
||||
*/
|
||||
node(kind: string, def?: OptionList, children?: (N | T)[], ns?: string): N;
|
||||
|
||||
/**
|
||||
* @param {string} text The text from which to create an HTML text node
|
||||
* @return {T} The generated text node with the given text
|
||||
*/
|
||||
text(text: string): T;
|
||||
|
||||
/**
|
||||
* @param {D} doc The document whose head is to be obtained
|
||||
* @return {N} The document.head element
|
||||
*/
|
||||
head(doc: D): N;
|
||||
|
||||
/**
|
||||
* @param {D} doc The document whose body is to be obtained
|
||||
* @return {N} The document.body element
|
||||
*/
|
||||
body(doc: D): N;
|
||||
|
||||
/**
|
||||
* @param {D} doc The document whose documentElement is to be obtained
|
||||
* @return {N} The documentElement
|
||||
*/
|
||||
root(doc: D): N;
|
||||
|
||||
/**
|
||||
* @param {D} doc The document whose doctype is to be obtained
|
||||
* @return {string} The DOCTYPE comment
|
||||
*/
|
||||
doctype(doc: D): string;
|
||||
|
||||
/**
|
||||
* @param {N} node The node to search for tags
|
||||
* @param {string} name The name of the tag to search for
|
||||
* @param {string} ns The namespace to search in (or null for no namespace)
|
||||
* @return {N[]} The list of tags found
|
||||
*/
|
||||
tags(node: N, name: string, ns?: string): N[];
|
||||
|
||||
/**
|
||||
* Get a list of containers (to be searched for math). These can be
|
||||
* specified by CSS selector, or as actual DOM elements or arrays of such.
|
||||
*
|
||||
* @param {(string | N | N[])[]} nodes The array of items to make into a container list
|
||||
* @param {D} document The document in which to search
|
||||
* @return {N[]} The array of containers to search
|
||||
*/
|
||||
getElements(nodes: (string | N | N[])[], document: D): N[];
|
||||
|
||||
/**
|
||||
* Determine if a container node contains a given node somewhere in its DOM tree
|
||||
*
|
||||
* @param {N} container The container to search
|
||||
* @param {N|T} node The node to look for
|
||||
* @return {boolean} True if the node is in the container's DOM tree
|
||||
*/
|
||||
contains(container: N, node: N | T): boolean;
|
||||
|
||||
/**
|
||||
* @param {N|T} node The HTML node whose parent is to be obtained
|
||||
* @return {N} The parent node of the given one
|
||||
*/
|
||||
parent(node: N | T): N;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node to be appended to
|
||||
* @param {N|T} child The node or text to be appended
|
||||
* @return {N|T} The appended node
|
||||
*/
|
||||
append(node: N, child: N | T): N | T;
|
||||
|
||||
/**
|
||||
* @param {N|T} nchild The node or text to be inserted
|
||||
* @param {N|T} ochild The node or text where the new child is to be added before it
|
||||
*/
|
||||
insert(nchild: N | T, ochild: N | T): void;
|
||||
|
||||
/**
|
||||
* @param {N|T} child The node or text to be removed from its parent
|
||||
* @return {N|T} The removed node
|
||||
*/
|
||||
remove(child: N | T): N | T;
|
||||
|
||||
/**
|
||||
* @param {N|T} nnode The node to replace with
|
||||
* @param {N|T} onode The child to be replaced
|
||||
* @return {N|T} The removed node
|
||||
*/
|
||||
replace(nnode: N | T, onode: N | T): N | T;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node to be cloned
|
||||
* @return {N} The copied node
|
||||
*/
|
||||
clone(node: N): N;
|
||||
|
||||
/**
|
||||
* @param {T} node The HTML text node to be split
|
||||
* @param {number} n The index of the character where the split will occur
|
||||
*/
|
||||
split(node: T, n: number): T;
|
||||
|
||||
/**
|
||||
* @param {N|T} node The HTML node whose sibling is to be obtained
|
||||
* @return {N|T} The node following the given one (or null)
|
||||
*/
|
||||
next(node: N | T): N | T;
|
||||
|
||||
/**
|
||||
* @param {N|T} node The HTML node whose sibling is to be obtained
|
||||
* @return {N|T} The node preceding the given one (or null)
|
||||
*/
|
||||
previous(node: N | T): N | T;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose child is to be obtained
|
||||
* @return {N|T} The first child of the given node (or null)
|
||||
*/
|
||||
firstChild(node: N): N | T;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose child is to be obtained
|
||||
* @return {N} The last child of the given node (or null)
|
||||
*/
|
||||
lastChild(node: N): N | T;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose children are to be obtained
|
||||
* @return {(N|T)[]} Array of children for the given node (not a live list)
|
||||
*/
|
||||
childNodes(node: N): (N | T)[];
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose child is to be obtained
|
||||
* @param {number} i The index of the child to return
|
||||
* @return {N|T} The i-th child node of the given node (or null)
|
||||
*/
|
||||
childNode(node: N, i: number): N | T;
|
||||
|
||||
/**
|
||||
* @param {N | T} node The HTML node whose tag or node name is to be obtained
|
||||
* @return {string} The tag or node name of the given node
|
||||
*/
|
||||
kind(node: N | T): string;
|
||||
|
||||
/**
|
||||
* @param {N|T} node The HTML node whose value is to be obtained
|
||||
* @return {string} The value of the given node
|
||||
*/
|
||||
value(node: N | T): string;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose text content is to be obtained
|
||||
* @return {string} The text content of the given node
|
||||
*/
|
||||
textContent(node: N): string;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose inner HTML string is to be obtained
|
||||
* @return {string} The serialized content of the node
|
||||
*/
|
||||
innerHTML(node: N): string;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose outer HTML string is to be obtained
|
||||
* @return {string} The serialized node and its content
|
||||
*/
|
||||
outerHTML(node: N): string;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose serialized string is to be obtained
|
||||
* @return {string} The serialized node and its content
|
||||
*/
|
||||
serializeXML(node: N): string;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose attribute is to be set
|
||||
* @param {string|number} name The name of the attribute to set
|
||||
* @param {string} value The new value of the attribute
|
||||
* @param {string=} ns The namespace to use for the attribute
|
||||
*/
|
||||
setAttribute(node: N, name: string, value: string | number, ns?: string): void;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML element whose attributes are to be set
|
||||
* @param {OptionList} def The attributes to set on that node
|
||||
*/
|
||||
setAttributes(node: N, def: OptionList): void;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose attribute is to be obtained
|
||||
* @param {string} name The name of the attribute to get
|
||||
* @return {string} The value of the given attribute of the given node
|
||||
*/
|
||||
getAttribute(node: N, name: string): string;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose attribute is to be removed
|
||||
* @param {string} name The name of the attribute to remove
|
||||
*/
|
||||
removeAttribute(node: N, name: string): void;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose attribute is to be tested
|
||||
* @param {string} name The name of the attribute to test
|
||||
* @return {boolean} True of the node has the given attribute defined
|
||||
*/
|
||||
hasAttribute(node: N, name: string): boolean;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose attributes are to be returned
|
||||
* @return {AttributeData[]} The list of attributes
|
||||
*/
|
||||
allAttributes(node: N): AttributeData[];
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose class is to be augmented
|
||||
* @param {string} name The class to be added
|
||||
*/
|
||||
addClass(node: N, name: string): void;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose class is to be changed
|
||||
* @param {string} name The class to be removed
|
||||
*/
|
||||
removeClass(node: N, name: string): void;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose class is to be tested
|
||||
* @param {string} name The class to test
|
||||
* @return {boolean} True if the node has the given class
|
||||
*/
|
||||
hasClass(node: N, name: string): boolean;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose class list is needed
|
||||
* @return {string[]} An array of the class names for this node
|
||||
*/
|
||||
allClasses(node: N): string[];
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose style is to be changed
|
||||
* @param {string} name The style to be set
|
||||
* @param {string} value The new value of the style
|
||||
*/
|
||||
setStyle(node: N, name: string, value: string): void;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose style is to be obtained
|
||||
* @param {string} name The style to be obtained
|
||||
* @return {string} The value of the style
|
||||
*/
|
||||
getStyle(node: N, name: string): string;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose styles are to be returned
|
||||
* @return {string} The cssText for the styles
|
||||
*/
|
||||
allStyles(node: N): string;
|
||||
|
||||
/**
|
||||
* @param {N} node The stylesheet node where the rule will be added
|
||||
* @param {string[]} rules The rule to add at the beginning of the stylesheet
|
||||
*/
|
||||
insertRules(node: N, rules: string[]): void;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose font size is to be determined
|
||||
* @return {number} The font size (in pixels) of the node
|
||||
*/
|
||||
fontSize(node: N): number;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose font family is to be determined
|
||||
* @return {string} The font family
|
||||
*/
|
||||
fontFamily(node: N): string;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose dimensions are to be determined
|
||||
* @param {number} em The number of pixels in an em
|
||||
* @param {boolean} local True if local coordinates are to be used in SVG elements
|
||||
* @return {[number, number]} The width and height (in ems) of the element
|
||||
*/
|
||||
nodeSize(node: N, em?: number, local?: boolean): [number, number];
|
||||
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML node whose BBox is to be determined
|
||||
* @return {PageBBox} BBox as {left, right, top, bottom} position on the page (in pixels)
|
||||
*/
|
||||
nodeBBox(node: N): PageBBox;
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Abstract DOMAdaptor class for creating HTML elements
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export abstract class AbstractDOMAdaptor<N, T, D> implements DOMAdaptor<N, T, D> {
|
||||
|
||||
/**
|
||||
* The document in which the HTML nodes will be created
|
||||
*/
|
||||
public document: D;
|
||||
|
||||
/**
|
||||
* @param {D} document The document in which the nodes will be created
|
||||
* @constructor
|
||||
*/
|
||||
constructor(document: D = null) {
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract parse(text: string, format?: string): D;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public node(kind: string, def: OptionList = {}, children: (N | T)[] = [], ns?: string) {
|
||||
const node = this.create(kind, ns);
|
||||
this.setAttributes(node, def);
|
||||
for (const child of children) {
|
||||
this.append(node, child);
|
||||
}
|
||||
return node as N;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} kind The type of the node to create
|
||||
* @param {string} ns The optional namespace in which to create the node
|
||||
* @return {N} The created node
|
||||
*/
|
||||
protected abstract create(kind: string, ns?: string): N;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract text(text: string): T;
|
||||
|
||||
/**
|
||||
* @param {N} node The HTML element whose attributes are to be set
|
||||
* @param {OptionList} def The attributes to set on that node
|
||||
*/
|
||||
public setAttributes(node: N, def: OptionList) {
|
||||
if (def.style && typeof(def.style) !== 'string') {
|
||||
for (let key of Object.keys(def.style)) {
|
||||
this.setStyle(node, key.replace(/-([a-z])/g, (_m, c) => c.toUpperCase()), def.style[key]);
|
||||
}
|
||||
}
|
||||
if (def.properties) {
|
||||
for (let key of Object.keys(def.properties)) {
|
||||
(node as OptionList)[key] = def.properties[key];
|
||||
}
|
||||
}
|
||||
for (let key of Object.keys(def)) {
|
||||
if ((key !== 'style' || typeof(def.style) === 'string') && key !== 'properties') {
|
||||
this.setAttribute(node, key, def[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract head(doc: D): N;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract body(doc: D): N;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract root(doc: D): N;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract doctype(doc: D): string;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract tags(node: N, name: string, ns?: string): N[];
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract getElements(nodes: (string | N | N[])[], document: D): N[];
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract contains(container: N, node: N | T): boolean;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract parent(node: N | T): N;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract append(node: N, child: N | T): N | T;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract insert(nchild: N | T, ochild: N | T): void;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract remove(child: N | T): N | T;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public replace(nnode: N | T, onode: N | T) {
|
||||
this.insert(nnode, onode);
|
||||
this.remove(onode);
|
||||
return onode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract clone(node: N): N;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract split(node: T, n: number): T;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract next(node: N | T): N | T;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract previous(node: N | T): N | T;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract firstChild(node: N): N | T;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract lastChild(node: N): N | T;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract childNodes(node: N): (N | T)[];
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public childNode(node: N, i: number) {
|
||||
return this.childNodes(node)[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract kind(node: N | T): string;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract value(node: N | T): string;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract textContent(node: N): string;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract innerHTML(node: N): string;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract outerHTML(node: N): string;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract serializeXML(node: N): string;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract setAttribute(node: N, name: string, value: string, ns?: string): void;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract getAttribute(node: N, name: string): string;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract removeAttribute(node: N, name: string): void;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract hasAttribute(node: N, name: string): boolean;
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract allAttributes(node: N): AttributeData[];
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract addClass(node: N, name: string): void;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract removeClass(node: N, name: string): void;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract hasClass(node: N, name: string): boolean;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public allClasses(node: N) {
|
||||
const classes = this.getAttribute(node, 'class');
|
||||
return (!classes ? [] as string[] :
|
||||
classes.replace(/ +/g, ' ').replace(/^ /, '').replace(/ $/, '').split(/ /));
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract setStyle(node: N, name: string, value: string): void;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract getStyle(node: N, name: string): string;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract allStyles(node: N): string;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract insertRules(node: N, rules: string[]): void;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract fontSize(node: N): number;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract fontFamily(node: N): string;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract nodeSize(node: N, em?: number, local?: boolean): [number, number];
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract nodeBBox(node: N): PageBBox;
|
||||
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 Interfaces and abstract classes for FindMath objects
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {userOptions, defaultOptions, OptionList} from '../util/Options.js';
|
||||
import {ProtoItem} from './MathItem.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The FindMath interface
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template _D The Document class
|
||||
*/
|
||||
export interface FindMath<N, T, _D> {
|
||||
/**
|
||||
* One of two possibilities: Look through a DOM element,
|
||||
* or look through an array of strings for delimited math.
|
||||
*
|
||||
* @param {N} node The node to search for math
|
||||
* @return {ProtoItem<N, T>[]}
|
||||
*/
|
||||
findMath(node: N): ProtoItem<N, T>[];
|
||||
/**
|
||||
*
|
||||
* @param {string[]} strings The strings to search for math
|
||||
* @return {ProtoItem<N, T>[]}
|
||||
*/
|
||||
findMath(strings: string[]): ProtoItem<N, T>[];
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The FindMath abstract class
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export abstract class AbstractFindMath<N, T, D> implements FindMath<N, T, D> {
|
||||
|
||||
/**
|
||||
* The default options for FindMath
|
||||
*/
|
||||
public static OPTIONS: OptionList = {};
|
||||
|
||||
/**
|
||||
* The actual options for this instance
|
||||
*/
|
||||
protected options: OptionList;
|
||||
|
||||
/**
|
||||
* @param {OptionList} options The user options for this instance
|
||||
*/
|
||||
constructor(options: OptionList) {
|
||||
let CLASS = this.constructor as typeof AbstractFindMath;
|
||||
this.options = userOptions(defaultOptions({}, CLASS.OPTIONS), options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate math in an Element or a string array;
|
||||
*
|
||||
* @param {Element | string[]} where The node or string array to search for math
|
||||
* @return {ProtoItem[]} The array of proto math items found
|
||||
*/
|
||||
public abstract findMath(where: N | string[]): ProtoItem<N, T>[];
|
||||
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 Interfaces and abstract classes for Handler objects
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {MathDocument, AbstractMathDocument, MathDocumentConstructor} from './MathDocument.js';
|
||||
import {OptionList} from '../util/Options.js';
|
||||
import {DOMAdaptor} from '../core/DOMAdaptor.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The Handler interface
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export interface Handler<N, T, D> {
|
||||
/**
|
||||
* The name of the handler class
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The DOM Adaptor to use for managing HTML elements
|
||||
*/
|
||||
adaptor: DOMAdaptor<N, T, D>;
|
||||
|
||||
/**
|
||||
* The priority for the handler when handlers are polled
|
||||
* to see which one can process a given document.
|
||||
*/
|
||||
priority: number;
|
||||
|
||||
/**
|
||||
* The class implementing the MathDocument for this handler
|
||||
* (so it can be subclassed by extensions as needed)
|
||||
*/
|
||||
documentClass: MathDocumentConstructor<AbstractMathDocument<N, T, D>>;
|
||||
|
||||
/**
|
||||
* Checks to see if the handler can process a given document
|
||||
*
|
||||
* @param {any} document The document to be processed (string, window, etc.)
|
||||
* @return {boolean} True if this handler can process the given document
|
||||
*/
|
||||
handlesDocument(document: any): boolean;
|
||||
|
||||
/**
|
||||
* Creates a MathDocument for the given handler
|
||||
*
|
||||
* @param {any} document The document to be handled
|
||||
* @param {OptionList} options The options for the handling of the document
|
||||
* @return {MathDocument} The MathDocument object that manages the processing
|
||||
*/
|
||||
create(document: any, options: OptionList): MathDocument<N, T, D>;
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The default MathDocument class (subclasses use their own)
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
class DefaultMathDocument<N, T, D> extends AbstractMathDocument<N, T, D> {}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The Handler interface
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export abstract class AbstractHandler<N, T, D> implements Handler<N, T, D> {
|
||||
|
||||
/**
|
||||
* The name of this class
|
||||
*/
|
||||
public static NAME: string = 'generic';
|
||||
|
||||
/**
|
||||
* The DOM Adaptor to use for managing HTML elements
|
||||
*/
|
||||
public adaptor: DOMAdaptor<N, T, D>;
|
||||
|
||||
/**
|
||||
* The priority for this handler
|
||||
*/
|
||||
public priority: number;
|
||||
|
||||
/**
|
||||
* The class implementing the MathDocument for this handler
|
||||
* (so it can be subclassed by extensions as needed)
|
||||
*/
|
||||
public documentClass: MathDocumentConstructor<AbstractMathDocument<N, T, D>> = DefaultMathDocument;
|
||||
|
||||
/**
|
||||
* @param {number} priority The priority to use for this handler
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
constructor(adaptor: DOMAdaptor<N, T, D>, priority: number = 5) {
|
||||
this.adaptor = adaptor;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string} The name of this handler class
|
||||
*/
|
||||
public get name(): string {
|
||||
return (this.constructor as typeof AbstractHandler).NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public handlesDocument(_document: any) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public create(document: any, options: OptionList) {
|
||||
return new this.documentClass(document, this.adaptor, options) as MathDocument<N, T, D>;
|
||||
}
|
||||
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 HandlerList objects
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PrioritizedList} from '../util/PrioritizedList.js';
|
||||
import {OptionList} from '../util/Options.js';
|
||||
import {Handler} from './Handler.js';
|
||||
import {MathDocument} from './MathDocument.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The HandlerList class (extends PrioritizedList of Handlers)
|
||||
*
|
||||
* This list is used to find the handler for a given document
|
||||
* by asking each handler to test if it can handle the document,
|
||||
* and when one can, it is asked to create its associated MathDocument.
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export class HandlerList<N, T, D> extends PrioritizedList<Handler<N, T, D>> {
|
||||
|
||||
/**
|
||||
* @param {Handler} handler The handler to register
|
||||
* @return {Handler} The list item created for the handler
|
||||
*/
|
||||
public register(handler: Handler<N, T, D>): Handler<N, T, D> {
|
||||
return this.add(handler, handler.priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Handler} Handler The handler to remove from the list
|
||||
*/
|
||||
public unregister(handler: Handler<N, T, D>) {
|
||||
this.remove(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} document The document (string, window, DOM element, etc) to be handled
|
||||
* @return {Handler} The handler from the list that can process the given document
|
||||
*/
|
||||
public handlesDocument(document: any): Handler<N, T, D> {
|
||||
for (const item of this) {
|
||||
let handler = item.item;
|
||||
if (handler.handlesDocument(document)) {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
throw new Error(`Can't find handler for document`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} document The document to be processed
|
||||
* @param {OptionList} options The options for the handler
|
||||
* @return {MathDocument} The MathDocument created by the handler for this document
|
||||
*/
|
||||
public document(document: any, options: OptionList = null): MathDocument<N, T, D> {
|
||||
return this.handlesDocument(document).create(document, options);
|
||||
}
|
||||
|
||||
}
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 the InputJax
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {MathDocument} from './MathDocument.js';
|
||||
import {MathItem, ProtoItem} from './MathItem.js';
|
||||
import {MmlNode} from './MmlTree/MmlNode.js';
|
||||
import {MmlFactory} from './MmlTree/MmlFactory.js';
|
||||
import {userOptions, defaultOptions, OptionList} from '../util/Options.js';
|
||||
import {FunctionList} from '../util/FunctionList.js';
|
||||
import {DOMAdaptor} from '../core/DOMAdaptor.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The InputJax interface
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export interface InputJax<N, T, D> {
|
||||
/**
|
||||
* The name of the input jax subclass (e.g,. 'TeX')
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Whether this input jax processes string arrays or DOM nodes
|
||||
* (TeX and AsciiMath process strings, MathML processes DOM nodes)
|
||||
*/
|
||||
processStrings: boolean;
|
||||
|
||||
/**
|
||||
* The options for this input jax instance
|
||||
*/
|
||||
options: OptionList;
|
||||
|
||||
/**
|
||||
* Lists of pre- and post-filters to call before and after processing the input
|
||||
*/
|
||||
preFilters: FunctionList;
|
||||
postFilters: FunctionList;
|
||||
|
||||
/**
|
||||
* The DOM adaptor for managing HTML elements
|
||||
*/
|
||||
adaptor: DOMAdaptor<N, T, D>;
|
||||
|
||||
/**
|
||||
* The MmlFactory for this input jax
|
||||
*/
|
||||
mmlFactory: MmlFactory;
|
||||
|
||||
/**
|
||||
* @param {DOMAdaptor} adaptor The adaptor to use in this jax
|
||||
*/
|
||||
setAdaptor(adaptor: DOMAdaptor<N, T, D>): void;
|
||||
|
||||
/**
|
||||
* @param {MmlFactory} mmlFactory The MmlFactory to use in this jax
|
||||
*/
|
||||
setMmlFactory(mmlFactory: MmlFactory): void;
|
||||
|
||||
/**
|
||||
* Do any initialization that depends on the document being set up
|
||||
*/
|
||||
initialize(): void;
|
||||
|
||||
/**
|
||||
* Reset any needed features of the input jax
|
||||
*
|
||||
* @param {any[]} args The arguments needed by the reset operation
|
||||
*/
|
||||
reset(...args: any[]): void;
|
||||
|
||||
/**
|
||||
* Finds the math within the DOM or the list of strings
|
||||
*
|
||||
* @param {N | string[]} which The element or array of strings to be searched for math
|
||||
* @param {OptionList} options The options for the search, if any
|
||||
* @return {ProtoItem[]} Array of proto math items found (further processed by the
|
||||
* handler to produce actual MathItem objects)
|
||||
*/
|
||||
findMath(which: N | string[], options?: OptionList): ProtoItem<N, T>[];
|
||||
|
||||
/**
|
||||
* Convert the math in a math item into the internal format
|
||||
*
|
||||
* @param {MathItem} math The MathItem whose math content is to processed
|
||||
* @param {MathDocument} document The MathDocument for this input jax.
|
||||
* @return {MmlNode} The resulting internal node tree for the math
|
||||
*/
|
||||
compile(math: MathItem<N, T, D>, document: MathDocument<N, T, D>): MmlNode;
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The abstract InputJax class
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export abstract class AbstractInputJax<N, T, D> implements InputJax<N, T, D> {
|
||||
|
||||
/**
|
||||
* The name of the input jax
|
||||
*/
|
||||
public static NAME: string = 'generic';
|
||||
|
||||
/**
|
||||
* The default options for the input jax
|
||||
*/
|
||||
public static OPTIONS: OptionList = {};
|
||||
|
||||
/**
|
||||
* The actual options supplied to the input jax
|
||||
*/
|
||||
public options: OptionList;
|
||||
|
||||
/**
|
||||
* Filters to run on the TeX string before it is processed
|
||||
*/
|
||||
public preFilters: FunctionList;
|
||||
|
||||
/**
|
||||
* Filters to run on the generated MathML after the TeX string is processed
|
||||
*/
|
||||
public postFilters: FunctionList;
|
||||
|
||||
/**
|
||||
* The DOMAdaptor for the MathDocument for this input jax
|
||||
*/
|
||||
public adaptor: DOMAdaptor<N, T, D> = null; // set by the handler
|
||||
/**
|
||||
* The MathML node factory
|
||||
*/
|
||||
public mmlFactory: MmlFactory = null; // set by the handler
|
||||
|
||||
/**
|
||||
* @param {OptionList} options The options to apply to this input jax
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
constructor(options: OptionList = {}) {
|
||||
let CLASS = this.constructor as typeof AbstractInputJax;
|
||||
this.options = userOptions(defaultOptions({}, CLASS.OPTIONS), options);
|
||||
this.preFilters = new FunctionList();
|
||||
this.postFilters = new FunctionList();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string} The name of this input jax class
|
||||
*/
|
||||
public get name(): string {
|
||||
return (this.constructor as typeof AbstractInputJax).NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public setAdaptor(adaptor: DOMAdaptor<N, T, D>) {
|
||||
this.adaptor = adaptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public setMmlFactory(mmlFactory: MmlFactory) {
|
||||
this.mmlFactory = mmlFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public initialize() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public reset(..._args: any[]) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} True means find math in string array, false means in DOM element
|
||||
*/
|
||||
public get processStrings(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public findMath(_node: N | string[], _options?: OptionList) {
|
||||
return [] as ProtoItem<N, T>[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract compile(math: MathItem<N, T, D>, document: MathDocument<N, T, D>): MmlNode;
|
||||
|
||||
/**
|
||||
* Execute a set of filters, passing them the MathItem and any needed data,
|
||||
* and return the (possibly modified) data
|
||||
*
|
||||
* @param {FunctionList} filters The list of functions to be performed
|
||||
* @param {MathItem} math The math item that is being processed
|
||||
* @param {MathDocument} document The math document containg the math item
|
||||
* @param {any} data Whatever other data is needed
|
||||
* @return {any} The (possibly modified) data
|
||||
*/
|
||||
protected executeFilters(
|
||||
filters: FunctionList, math: MathItem<N, T, D>,
|
||||
document: MathDocument<N, T, D>, data: any
|
||||
): any {
|
||||
let args = {math: math, document: document, data: data};
|
||||
filters.execute(args);
|
||||
return args.data;
|
||||
}
|
||||
|
||||
}
|
||||
+987
@@ -0,0 +1,987 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MathDocument objects
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {userOptions, defaultOptions, OptionList, expandable} from '../util/Options.js';
|
||||
import {InputJax, AbstractInputJax} from './InputJax.js';
|
||||
import {OutputJax, AbstractOutputJax} from './OutputJax.js';
|
||||
import {MathList, AbstractMathList} from './MathList.js';
|
||||
import {MathItem, AbstractMathItem, STATE} from './MathItem.js';
|
||||
import {MmlNode, TextNode} from './MmlTree/MmlNode.js';
|
||||
import {MmlFactory} from '../core/MmlTree/MmlFactory.js';
|
||||
import {DOMAdaptor} from '../core/DOMAdaptor.js';
|
||||
import {BitField, BitFieldClass} from '../util/BitField.js';
|
||||
|
||||
import {PrioritizedList} from '../util/PrioritizedList.js';
|
||||
|
||||
/*****************************************************************/
|
||||
|
||||
/**
|
||||
* A function to call while rendering a document (usually calls a MathDocument method)
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export type RenderDoc<N, T, D> = (document: MathDocument<N, T, D>) => boolean;
|
||||
|
||||
/**
|
||||
* A function to call while rendering a MathItem (usually calls one of its methods)
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export type RenderMath<N, T, D> = (math: MathItem<N, T, D>, document: MathDocument<N, T, D>) => boolean;
|
||||
|
||||
/**
|
||||
* The data for an action to perform during rendering or conversion
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export type RenderData<N, T, D> = {
|
||||
id: string, // The name for the action
|
||||
renderDoc: RenderDoc<N, T, D>, // The action to take during a render() call
|
||||
renderMath: RenderMath<N, T, D>, // The action to take during a rerender() or convert() call
|
||||
convert: boolean // Whether the action is to be used during convert()
|
||||
};
|
||||
|
||||
/**
|
||||
* The data used to define a render action in configurations and options objects
|
||||
* (the key is used as the id, the number in the data below is the priority, and
|
||||
* the remainind data is as described below; if no boolean is given, convert = true
|
||||
* by default)
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export type RenderAction<N, T, D> =
|
||||
[number] | // id (i.e., key) is method name to use
|
||||
[number, string] | // string is method to call
|
||||
[number, string, string] | // the strings are methods names for doc and math
|
||||
[number, RenderDoc<N, T, D>, RenderMath<N, T, D>] | // explicit functions for doc and math
|
||||
[number, boolean] | // same as first above, with boolean for convert
|
||||
[number, string, boolean] | // same as second above, with boolean for convert
|
||||
[number, string, string, boolean] | // same as third above, with boolean for convert
|
||||
[number, RenderDoc<N, T, D>, RenderMath<N, T, D>, boolean]; // same as forth above, with boolean for convert
|
||||
|
||||
/**
|
||||
* An object representing a collection of rendering actions (id's tied to priority-and-method data)
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export type RenderActions<N, T, D> = {[id: string]: RenderAction<N, T, D>};
|
||||
|
||||
/**
|
||||
* Implements a prioritized list of render actions. Extensions can add actions to the list
|
||||
* to make it easy to extend the normal typesetting and conversion operations.
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export class RenderList<N, T, D> extends PrioritizedList<RenderData<N, T, D>> {
|
||||
|
||||
/**
|
||||
* Creates a new RenderList from an initial list of rendering actions
|
||||
*
|
||||
* @param {RenderActions} actions The list of actions to take during render(), rerender(), and convert() calls
|
||||
* @returns {RenderList} The newly created prioritied list
|
||||
*/
|
||||
public static create<N, T, D>(actions: RenderActions<N, T, D>): RenderList<N, T, D> {
|
||||
const list = new this<N, T, D>();
|
||||
for (const id of Object.keys(actions)) {
|
||||
const [action, priority] = this.action<N, T, D>(id, actions[id]);
|
||||
if (priority) {
|
||||
list.add(action, priority);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a RenderAction to produce the correspinding RenderData item
|
||||
* (e.g., turn method names into actual functions that call the method)
|
||||
*
|
||||
* @param {string} id The id of the action
|
||||
* @param {RenderAction} action The RenderAction defining the action
|
||||
* @returns {[RenderData,number]} The corresponding RenderData definition for the action and its priority
|
||||
*/
|
||||
public static action<N, T, D>(id: string, action: RenderAction<N, T, D>): [RenderData<N, T, D>, number] {
|
||||
let renderDoc, renderMath;
|
||||
let convert = true;
|
||||
let priority = action[0];
|
||||
if (action.length === 1 || typeof action[1] === 'boolean') {
|
||||
action.length === 2 && (convert = action[1] as boolean);
|
||||
[renderDoc, renderMath] = this.methodActions(id);
|
||||
} else if (typeof action[1] === 'string') {
|
||||
if (typeof action[2] === 'string') {
|
||||
action.length === 4 && (convert = action[3] as boolean);
|
||||
const [method1, method2] = action.slice(1) as [string, string];
|
||||
[renderDoc, renderMath] = this.methodActions(method1, method2);
|
||||
} else {
|
||||
action.length === 3 && (convert = action[2] as boolean);
|
||||
[renderDoc, renderMath] = this.methodActions(action[1] as string);
|
||||
}
|
||||
} else {
|
||||
action.length === 4 && (convert = action[3] as boolean);
|
||||
[renderDoc, renderMath] = action.slice(1) as [RenderDoc<N, T, D>, RenderMath<N, T, D>];
|
||||
}
|
||||
return [{id, renderDoc, renderMath, convert} as RenderData<N, T, D>, priority];
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces the doc and math actions for the given method name(s)
|
||||
* (a blank name is a no-op)
|
||||
*
|
||||
* @param {string} method1 The method to use for the render() call
|
||||
* @param {string} method1 The method to use for the rerender() and convert() calls
|
||||
*/
|
||||
protected static methodActions(method1: string, method2: string = method1) {
|
||||
return [
|
||||
(document: any) => {method1 && document[method1](); return false; },
|
||||
(math: any, document: any) => {method2 && math[method2](document); return false; }
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the document-level rendering functions
|
||||
*
|
||||
* @param {MathDocument} document The MathDocument whose methods are to be called
|
||||
* @param {number=} start The state at which to start rendering (default is UNPROCESSED)
|
||||
*/
|
||||
public renderDoc(document: MathDocument<N, T, D>, start: number = STATE.UNPROCESSED) {
|
||||
for (const item of this.items) {
|
||||
if (item.priority >= start) {
|
||||
if (item.item.renderDoc(document)) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the MathItem-level rendering functions
|
||||
*
|
||||
* @param {MathItem} math The MathItem whose methods are to be called
|
||||
* @param {MathDocument} document The MathDocument to pass to the MathItem methods
|
||||
* @param {number=} start The state at which to start rendering (default is UNPROCESSED)
|
||||
*/
|
||||
public renderMath(math: MathItem<N, T, D>, document: MathDocument<N, T, D>, start: number = STATE.UNPROCESSED) {
|
||||
for (const item of this.items) {
|
||||
if (item.priority >= start) {
|
||||
if (item.item.renderMath(math, document)) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the MathItem-level conversion functions
|
||||
*
|
||||
* @param {MathItem} math The MathItem whose methods are to be called
|
||||
* @param {MathDocument} document The MathDocument to pass to the MathItem methods
|
||||
* @param {number=} end The state at which to end rendering (default is LAST)
|
||||
*/
|
||||
public renderConvert(math: MathItem<N, T, D>, document: MathDocument<N, T, D>, end: number = STATE.LAST) {
|
||||
for (const item of this.items) {
|
||||
if (item.priority > end) return;
|
||||
if (item.item.convert) {
|
||||
if (item.item.renderMath(math, document)) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an entry in the list with a given ID
|
||||
*
|
||||
* @param {string} id The id to search for
|
||||
* @returns {RenderData|null} The data for the given id, if found, or null
|
||||
*/
|
||||
public findID(id: string): RenderData<N, T, D> | null {
|
||||
for (const item of this.items) {
|
||||
if (item.item.id === id) {
|
||||
return item.item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
|
||||
/**
|
||||
* The ways of specifying a container (a selector string, an actual node,
|
||||
* or an array of those (e.g., the result of document.getElementsByTagName())
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
*/
|
||||
export type ContainerList<N> = string | N | (string | N | N[])[];
|
||||
|
||||
/**
|
||||
* The options allowed for the reset() method
|
||||
*/
|
||||
export type ResetList = {
|
||||
all?: boolean,
|
||||
processed?: boolean,
|
||||
inputJax?: any[],
|
||||
outputJax?: any[]
|
||||
};
|
||||
|
||||
/**
|
||||
* The default option list for the reset() method
|
||||
*/
|
||||
export const resetOptions: ResetList = {
|
||||
all: false,
|
||||
processed: false,
|
||||
inputJax: null,
|
||||
outputJax: null
|
||||
};
|
||||
|
||||
/**
|
||||
* The option list for when all options are to be reset
|
||||
*/
|
||||
export const resetAllOptions: ResetList = {
|
||||
all: true,
|
||||
processed: true,
|
||||
inputJax: [],
|
||||
outputJax: []
|
||||
};
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The MathDocument interface
|
||||
*
|
||||
* The MathDocument is created by MathJax.Document() and holds the
|
||||
* document, the math found in it, and so on. The methods of the
|
||||
* MathDocument all return the MathDocument itself, so you can
|
||||
* chain the method calls. E.g.,
|
||||
*
|
||||
* const html = MathJax.Document('<html>...</html>');
|
||||
* html.findMath()
|
||||
* .compile()
|
||||
* .getMetrics()
|
||||
* .typeset()
|
||||
* .updateDocument();
|
||||
*
|
||||
* The MathDocument is the main interface for page authors to
|
||||
* interact with MathJax.
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export interface MathDocument<N, T, D> {
|
||||
/**
|
||||
* The document being processed (e.g., DOM document, or Markdown string)
|
||||
*/
|
||||
document: D;
|
||||
|
||||
/**
|
||||
* The kind of MathDocument (e.g., "HTML")
|
||||
*/
|
||||
kind: string;
|
||||
|
||||
/**
|
||||
* The options for the document
|
||||
*/
|
||||
options: OptionList;
|
||||
|
||||
/**
|
||||
* The list of MathItems found in this page
|
||||
*/
|
||||
math: MathList<N, T, D>;
|
||||
|
||||
/**
|
||||
* The list of actions to take during a render() or convert() call
|
||||
*/
|
||||
renderActions: RenderList<N, T, D>;
|
||||
|
||||
/**
|
||||
* This object tracks what operations have been performed, so that (when
|
||||
* asynchronous operations are used), the ones that have already been
|
||||
* completed won't be performed again.
|
||||
*/
|
||||
processed: BitField;
|
||||
|
||||
/**
|
||||
* An array of input jax to run on the document
|
||||
*/
|
||||
inputJax: InputJax<N, T, D>[];
|
||||
|
||||
/**
|
||||
* The output jax to use for the document
|
||||
*/
|
||||
outputJax: OutputJax<N, T, D>;
|
||||
|
||||
/**
|
||||
* The DOM adaptor to use for input and output
|
||||
*/
|
||||
adaptor: DOMAdaptor<N, T, D>;
|
||||
|
||||
/**
|
||||
* The MmlFactory to be used for input jax and error processing
|
||||
*/
|
||||
mmlFactory: MmlFactory;
|
||||
|
||||
/**
|
||||
* @param {string} id The id of the action to add
|
||||
* @param {any[]} action The RenderAction to take
|
||||
*/
|
||||
addRenderAction(id: string, ...action: any[]): void;
|
||||
|
||||
/**
|
||||
* @param {string} id The id of the action to remove
|
||||
*/
|
||||
removeRenderAction(id: string): void;
|
||||
|
||||
/**
|
||||
* Perform the renderActions on the document
|
||||
*/
|
||||
render(): MathDocument<N, T, D>;
|
||||
|
||||
/**
|
||||
* Rerender the MathItems on the page
|
||||
*
|
||||
* @param {number=} start The state to start rerendering at
|
||||
* @return {MathDocument} The math document instance
|
||||
*/
|
||||
rerender(start?: number): MathDocument<N, T, D>;
|
||||
|
||||
/**
|
||||
* Convert a math string to the document's output format
|
||||
*
|
||||
* @param {string} math The math string to convert
|
||||
* @params {OptionList} options The options for the conversion (e.g., format, ex, em, etc.)
|
||||
* @return {MmlNode|N} The MmlNode or N node for the converted content
|
||||
*/
|
||||
convert(math: string, options?: OptionList): MmlNode | N;
|
||||
|
||||
/**
|
||||
* Locates the math in the document and constructs the MathList
|
||||
* for the document.
|
||||
*
|
||||
* @param {OptionList} options The options for locating the math
|
||||
* @return {MathDocument} The math document instance
|
||||
*/
|
||||
findMath(options?: OptionList): MathDocument<N, T, D>;
|
||||
|
||||
/**
|
||||
* Calls the input jax to process the MathItems in the MathList
|
||||
*
|
||||
* @return {MathDocument} The math document instance
|
||||
*/
|
||||
compile(): MathDocument<N, T, D>;
|
||||
|
||||
/**
|
||||
* Gets the metric information for the MathItems
|
||||
*
|
||||
* @return {MathDocument} The math document instance
|
||||
*/
|
||||
getMetrics(): MathDocument<N, T, D>;
|
||||
|
||||
/**
|
||||
* Calls the output jax to process the compiled math in the MathList
|
||||
*
|
||||
* @return {MathDocument} The math document instance
|
||||
*/
|
||||
typeset(): MathDocument<N, T, D>;
|
||||
|
||||
/**
|
||||
* Updates the document to include the typeset math
|
||||
*
|
||||
* @return {MathDocument} The math document instance
|
||||
*/
|
||||
updateDocument(): MathDocument<N, T, D>;
|
||||
|
||||
/**
|
||||
* Removes the typeset math from the document
|
||||
*
|
||||
* @param {boolean} restore True if the original math should be put
|
||||
* back into the document as well
|
||||
* @return {MathDocument} The math document instance
|
||||
*/
|
||||
removeFromDocument(restore?: boolean): MathDocument<N, T, D>;
|
||||
|
||||
/**
|
||||
* Set the state of the document (allowing you to roll back
|
||||
* the state to a previous one, if needed).
|
||||
*
|
||||
* @param {number} state The new state of the document
|
||||
* @param {boolean} restore True if the original math should be put
|
||||
* back into the document during the rollback
|
||||
* @return {MathDocument} The math document instance
|
||||
*/
|
||||
state(state: number, restore?: boolean): MathDocument<N, T, D>;
|
||||
|
||||
/**
|
||||
* Clear the processed values so that the document can be reprocessed
|
||||
*
|
||||
* @param {ResetList} options The things to be reset
|
||||
* @return {MathDocument} The math document instance
|
||||
*/
|
||||
reset(options?: ResetList): MathDocument<N, T, D>;
|
||||
|
||||
/**
|
||||
* Reset the processed values and clear the MathList (so that new math
|
||||
* can be processed in the document).
|
||||
*
|
||||
* @return {MathDocument} The math document instance
|
||||
*/
|
||||
clear(): MathDocument<N, T, D>;
|
||||
|
||||
/**
|
||||
* Merges a MathList into the list for this document.
|
||||
*
|
||||
* @param {MathList} list The MathList to be merged into this document's list
|
||||
* @return {MathDocument} The math document instance
|
||||
*/
|
||||
concat(list: MathList<N, T, D>): MathDocument<N, T, D>;
|
||||
|
||||
/**
|
||||
* Clear the typeset MathItems that are within the given container
|
||||
* from the document's MathList. (E.g., when the content of the
|
||||
* container has been updated and you want to remove the
|
||||
* associated MathItems)
|
||||
*
|
||||
* @param {ContainerList<N>} elements The container DOM elements whose math items are to be removed
|
||||
* @return {MathItem<N,T,D>[]} The removed MathItems
|
||||
*/
|
||||
clearMathItemsWithin(containers: ContainerList<N>): MathItem<N, T, D>[];
|
||||
|
||||
/**
|
||||
* Get the typeset MathItems that are within a given container.
|
||||
*
|
||||
* @param {ContainerList<N>} elements The container DOM elements whose math items are to be found
|
||||
* @return {MathItem<N,T,D>[]} The list of MathItems within that container
|
||||
*/
|
||||
getMathItemsWithin(elements: ContainerList<N>): MathItem<N, T, D>[];
|
||||
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
|
||||
/**
|
||||
* Defaults used when input jax isn't specified
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
class DefaultInputJax<N, T, D> extends AbstractInputJax<N, T, D> {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public compile(_math: MathItem<N, T, D>) {
|
||||
return null as MmlNode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults used when ouput jax isn't specified
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
class DefaultOutputJax<N, T, D> extends AbstractOutputJax<N, T, D> {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public typeset(_math: MathItem<N, T, D>, _document: MathDocument<N, T, D> = null) {
|
||||
return null as N;
|
||||
}
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public escaped(_math: MathItem<N, T, D>, _document?: MathDocument<N, T, D>) {
|
||||
return null as N;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default for the MathList when one isn't specified
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
class DefaultMathList<N, T, D> extends AbstractMathList<N, T, D> {}
|
||||
|
||||
/**
|
||||
* Default for the Mathitem when one isn't specified
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
class DefaultMathItem<N, T, D> extends AbstractMathItem<N, T, D> {}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the abstract MathDocument class
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export abstract class AbstractMathDocument<N, T, D> implements MathDocument<N, T, D> {
|
||||
|
||||
/**
|
||||
* The type of MathDocument
|
||||
*/
|
||||
public static KIND: string = 'MathDocument';
|
||||
|
||||
/**
|
||||
* The default options for the document
|
||||
*/
|
||||
public static OPTIONS: OptionList = {
|
||||
OutputJax: null, // instance of an OutputJax for the document
|
||||
InputJax: null, // instance of an InputJax or an array of them
|
||||
MmlFactory: null, // instance of a MmlFactory for this document
|
||||
MathList: DefaultMathList, // constructor for a MathList to use for the document
|
||||
MathItem: DefaultMathItem, // constructor for a MathItem to use for the MathList
|
||||
compileError: (doc: AbstractMathDocument<any, any, any>, math: MathItem<any, any, any>, err: Error) => {
|
||||
doc.compileError(math, err);
|
||||
},
|
||||
typesetError: (doc: AbstractMathDocument<any, any, any>, math: MathItem<any, any, any>, err: Error) => {
|
||||
doc.typesetError(math, err);
|
||||
},
|
||||
renderActions: expandable({
|
||||
find: [STATE.FINDMATH, 'findMath', '', false],
|
||||
compile: [STATE.COMPILED],
|
||||
metrics: [STATE.METRICS, 'getMetrics', '', false],
|
||||
typeset: [STATE.TYPESET],
|
||||
update: [STATE.INSERTED, 'updateDocument', false]
|
||||
}) as RenderActions<any, any, any>
|
||||
};
|
||||
|
||||
/**
|
||||
* A bit-field for the actions that have been processed
|
||||
*/
|
||||
public static ProcessBits = BitFieldClass('findMath', 'compile', 'getMetrics', 'typeset', 'updateDocument');
|
||||
|
||||
/**
|
||||
* The document managed by this MathDocument
|
||||
*/
|
||||
public document: D;
|
||||
/**
|
||||
* The actual options for this document (with user-supplied ones merged in)
|
||||
*/
|
||||
public options: OptionList;
|
||||
|
||||
/**
|
||||
* The list of MathItems for this document
|
||||
*/
|
||||
public math: MathList<N, T, D>;
|
||||
|
||||
/**
|
||||
* The list of render actions
|
||||
*/
|
||||
public renderActions: RenderList<N, T, D>;
|
||||
|
||||
/**
|
||||
* The bit-field used to tell what steps have been taken on the document (for retries)
|
||||
*/
|
||||
public processed: BitField;
|
||||
|
||||
/**
|
||||
* The list of input jax for the document
|
||||
*/
|
||||
public inputJax: InputJax<N, T, D>[];
|
||||
|
||||
/**
|
||||
* The output jax for the document
|
||||
*/
|
||||
public outputJax: OutputJax<N, T, D>;
|
||||
|
||||
/**
|
||||
* The DOM adaptor for the document
|
||||
*/
|
||||
public adaptor: DOMAdaptor<N, T, D>;
|
||||
|
||||
/**
|
||||
* The MathML node factory for the internal MathML representation
|
||||
*/
|
||||
public mmlFactory: MmlFactory;
|
||||
|
||||
|
||||
/**
|
||||
* @param {any} document The document (HTML string, parsed DOM, etc.) to be processed
|
||||
* @param {DOMAdaptor} adaptor The DOM adaptor for this document
|
||||
* @param {OptionList} options The options for this document
|
||||
* @constructor
|
||||
*/
|
||||
constructor (document: D, adaptor: DOMAdaptor<N, T, D>, options: OptionList) {
|
||||
let CLASS = this.constructor as typeof AbstractMathDocument;
|
||||
this.document = document;
|
||||
this.options = userOptions(defaultOptions({}, CLASS.OPTIONS), options);
|
||||
this.math = new (this.options['MathList'] || DefaultMathList)();
|
||||
this.renderActions = RenderList.create<N, T, D>(this.options['renderActions']);
|
||||
this.processed = new AbstractMathDocument.ProcessBits();
|
||||
this.outputJax = this.options['OutputJax'] || new DefaultOutputJax<N, T, D>();
|
||||
let inputJax = this.options['InputJax'] || [new DefaultInputJax<N, T, D>()];
|
||||
if (!Array.isArray(inputJax)) {
|
||||
inputJax = [inputJax];
|
||||
}
|
||||
this.inputJax = inputJax;
|
||||
//
|
||||
// Pass the DOM adaptor to the jax
|
||||
//
|
||||
this.adaptor = adaptor;
|
||||
this.outputJax.setAdaptor(adaptor);
|
||||
this.inputJax.map(jax => jax.setAdaptor(adaptor));
|
||||
//
|
||||
// Pass the MmlFactory to the jax
|
||||
//
|
||||
this.mmlFactory = this.options['MmlFactory'] || new MmlFactory();
|
||||
this.inputJax.map(jax => jax.setMmlFactory(this.mmlFactory));
|
||||
//
|
||||
// Do any initialization that requires adaptors or factories
|
||||
//
|
||||
this.outputJax.initialize();
|
||||
this.inputJax.map(jax => jax.initialize());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string} The kind of document
|
||||
*/
|
||||
public get kind(): string {
|
||||
return (this.constructor as typeof AbstractMathDocument).KIND;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public addRenderAction(id: string, ...action: any[]) {
|
||||
const [fn, p] = RenderList.action<N, T, D>(id, action as RenderAction<N, T, D>);
|
||||
this.renderActions.add(fn, p);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public removeRenderAction(id: string) {
|
||||
const action = this.renderActions.findID(id);
|
||||
if (action) {
|
||||
this.renderActions.remove(action);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public render() {
|
||||
this.renderActions.renderDoc(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public rerender(start: number = STATE.RERENDER) {
|
||||
this.state(start - 1);
|
||||
this.render();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public convert(math: string, options: OptionList = {}) {
|
||||
let {format, display, end, ex, em, containerWidth, lineWidth, scale, family} = userOptions({
|
||||
format: this.inputJax[0].name, display: true, end: STATE.LAST,
|
||||
em: 16, ex: 8, containerWidth: null, lineWidth: 1000000, scale: 1, family: ''
|
||||
}, options);
|
||||
if (containerWidth === null) {
|
||||
containerWidth = 80 * ex;
|
||||
}
|
||||
const jax = this.inputJax.reduce((jax, ijax) => (ijax.name === format ? ijax : jax), null);
|
||||
const mitem = new this.options.MathItem(math, jax, display);
|
||||
mitem.start.node = this.adaptor.body(this.document);
|
||||
mitem.setMetrics(em, ex, containerWidth, lineWidth, scale);
|
||||
if (this.outputJax.options.mtextInheritFont) {
|
||||
mitem.outputData.mtextFamily = family;
|
||||
}
|
||||
if (this.outputJax.options.merrorInheritFont) {
|
||||
mitem.outputData.merrorFamily = family;
|
||||
}
|
||||
mitem.convert(this, end);
|
||||
return (mitem.typesetRoot || mitem.root);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public findMath(_options: OptionList = null) {
|
||||
this.processed.set('findMath');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public compile() {
|
||||
if (!this.processed.isSet('compile')) {
|
||||
//
|
||||
// Compile all the math in the list
|
||||
//
|
||||
const recompile = [];
|
||||
for (const math of this.math) {
|
||||
this.compileMath(math);
|
||||
if (math.inputData.recompile !== undefined) {
|
||||
recompile.push(math);
|
||||
}
|
||||
}
|
||||
//
|
||||
// If any were added to the recompile list,
|
||||
// compile them again
|
||||
//
|
||||
for (const math of recompile) {
|
||||
const data = math.inputData.recompile;
|
||||
math.state(data.state);
|
||||
math.inputData.recompile = data;
|
||||
this.compileMath(math);
|
||||
}
|
||||
this.processed.set('compile');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MathItem} math The item to compile
|
||||
*/
|
||||
protected compileMath(math: MathItem<N, T, D>) {
|
||||
try {
|
||||
math.compile(this);
|
||||
} catch (err) {
|
||||
if (err.retry || err.restart) {
|
||||
throw err;
|
||||
}
|
||||
this.options['compileError'](this, math, err);
|
||||
math.inputData['error'] = err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce an error using MmlNodes
|
||||
*
|
||||
* @param {MathItem} math The MathItem producing the error
|
||||
* @param {Error} err The Error object for the error
|
||||
*/
|
||||
public compileError(math: MathItem<N, T, D>, err: Error) {
|
||||
math.root = this.mmlFactory.create('math', null, [
|
||||
this.mmlFactory.create('merror', {'data-mjx-error': err.message, title: err.message}, [
|
||||
this.mmlFactory.create('mtext', null, [
|
||||
(this.mmlFactory.create('text') as TextNode).setText('Math input error')
|
||||
])
|
||||
])
|
||||
]);
|
||||
if (math.display) {
|
||||
math.root.attributes.set('display', 'block');
|
||||
}
|
||||
math.inputData.error = err.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public typeset() {
|
||||
if (!this.processed.isSet('typeset')) {
|
||||
for (const math of this.math) {
|
||||
try {
|
||||
math.typeset(this);
|
||||
} catch (err) {
|
||||
if (err.retry || err.restart) {
|
||||
throw err;
|
||||
}
|
||||
this.options['typesetError'](this, math, err);
|
||||
math.outputData['error'] = err;
|
||||
}
|
||||
}
|
||||
this.processed.set('typeset');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce an error using HTML
|
||||
*
|
||||
* @param {MathItem} math The MathItem producing the error
|
||||
* @param {Error} err The Error object for the error
|
||||
*/
|
||||
public typesetError(math: MathItem<N, T, D>, err: Error) {
|
||||
math.typesetRoot = this.adaptor.node('mjx-container', {
|
||||
class: 'MathJax mjx-output-error',
|
||||
jax: this.outputJax.name,
|
||||
}, [
|
||||
this.adaptor.node('span', {
|
||||
'data-mjx-error': err.message,
|
||||
title: err.message,
|
||||
style: {
|
||||
color: 'red',
|
||||
'background-color': 'yellow',
|
||||
'line-height': 'normal'
|
||||
}
|
||||
}, [
|
||||
this.adaptor.text('Math output error')
|
||||
])
|
||||
]);
|
||||
if (math.display) {
|
||||
this.adaptor.setAttributes(math.typesetRoot, {
|
||||
style: {
|
||||
display: 'block',
|
||||
margin: '1em 0',
|
||||
'text-align': 'center'
|
||||
}
|
||||
});
|
||||
}
|
||||
math.outputData.error = err.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public getMetrics() {
|
||||
if (!this.processed.isSet('getMetrics')) {
|
||||
this.outputJax.getMetrics(this);
|
||||
this.processed.set('getMetrics');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public updateDocument() {
|
||||
if (!this.processed.isSet('updateDocument')) {
|
||||
for (const math of this.math.reversed()) {
|
||||
math.updateDocument(this);
|
||||
}
|
||||
this.processed.set('updateDocument');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public removeFromDocument(_restore: boolean = false) {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public state(state: number, restore: boolean = false) {
|
||||
for (const math of this.math) {
|
||||
math.state(state, restore);
|
||||
}
|
||||
if (state < STATE.INSERTED) {
|
||||
this.processed.clear('updateDocument');
|
||||
}
|
||||
if (state < STATE.TYPESET) {
|
||||
this.processed.clear('typeset');
|
||||
this.processed.clear('getMetrics');
|
||||
}
|
||||
if (state < STATE.COMPILED) {
|
||||
this.processed.clear('compile');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public reset(options: ResetList = {processed: true}) {
|
||||
options = userOptions(Object.assign({}, resetOptions), options);
|
||||
options.all && Object.assign(options, resetAllOptions);
|
||||
options.processed && this.processed.reset();
|
||||
options.inputJax && this.inputJax.forEach(jax => jax.reset(...options.inputJax));
|
||||
options.outputJax && this.outputJax.reset(...options.outputJax);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public clear() {
|
||||
this.reset();
|
||||
this.math.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public concat(list: MathList<N, T, D>) {
|
||||
this.math.merge(list);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public clearMathItemsWithin(containers: ContainerList<N>) {
|
||||
const items = this.getMathItemsWithin(containers);
|
||||
this.math.remove(...items);
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public getMathItemsWithin(elements: ContainerList<N>) {
|
||||
if (!Array.isArray(elements)) {
|
||||
elements = [elements];
|
||||
}
|
||||
const adaptor = this.adaptor;
|
||||
const items = [] as MathItem<N, T, D>[];
|
||||
const containers = adaptor.getElements(elements, this.document);
|
||||
ITEMS:
|
||||
for (const item of this.math) {
|
||||
for (const container of containers) {
|
||||
if (item.start.node && adaptor.contains(container, item.start.node)) {
|
||||
items.push(item);
|
||||
continue ITEMS;
|
||||
}
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The constructor type for a MathDocument
|
||||
*
|
||||
* @template D The MathDocument type this constructor is for
|
||||
*/
|
||||
export interface MathDocumentConstructor<D extends MathDocument<any, any, any>> {
|
||||
KIND: string;
|
||||
OPTIONS: OptionList;
|
||||
ProcessBits: typeof BitField;
|
||||
new (...args: any[]): D;
|
||||
}
|
||||
+458
@@ -0,0 +1,458 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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;
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MathList objects
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {LinkedList} from '../util/LinkedList.js';
|
||||
import {MathItem} from './MathItem.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The MathList interface (extends LinkedList<MathItem>)
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export interface MathList<N, T, D> extends LinkedList<MathItem<N, T, D>> {
|
||||
/**
|
||||
* Test if one math item is before the other in the document (a < b)
|
||||
*
|
||||
* @param {MathItem} a The first MathItem
|
||||
* @param {MathItem} b The second MathItem
|
||||
*/
|
||||
isBefore(a: MathItem<N, T, D>, b: MathItem<N, T, D>): boolean;
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The MathList abstract class (extends LinkedList<MathItem>)
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export abstract class AbstractMathList<N, T, D> extends
|
||||
LinkedList<MathItem<N, T, D>> implements MathList<N, T, D> {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public isBefore(a: MathItem<N, T, D>, b: MathItem<N, T, D>) {
|
||||
return (a.start.i < b.start.i || (a.start.i === b.start.i && a.start.n < b.start.n));
|
||||
}
|
||||
|
||||
}
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 Attribute class for MmlNodes
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList, Property} from '../Tree/Node.js';
|
||||
|
||||
/**
|
||||
* A constant for when a property should be inherited from the global defaults lists
|
||||
*/
|
||||
export const INHERIT = '_inherit_';
|
||||
|
||||
/******************************************************************/
|
||||
/**
|
||||
* Implements the Attributes class for MmlNodes
|
||||
* (These can be set explicitly, inherited from parent nodes,
|
||||
* taken from a default list of values, or taken from global
|
||||
* defaults.)
|
||||
*/
|
||||
|
||||
export class Attributes {
|
||||
/**
|
||||
* The attributes explicitly set on a node
|
||||
*/
|
||||
protected attributes: PropertyList;
|
||||
/**
|
||||
* The attributes inherited from parent nodes
|
||||
*/
|
||||
protected inherited: PropertyList;
|
||||
/**
|
||||
* The default attributes for the node type
|
||||
*/
|
||||
protected defaults: PropertyList;
|
||||
/**
|
||||
* Global attributes from the math node itself
|
||||
*/
|
||||
protected global: PropertyList;
|
||||
|
||||
/**
|
||||
* @param {PropertyList} defaults The defaults for this node type
|
||||
* @param {PropertyList} global The global properties (from the math node)
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
constructor(defaults: PropertyList, global: PropertyList) {
|
||||
this.global = global;
|
||||
this.defaults = Object.create(global);
|
||||
this.inherited = Object.create(this.defaults);
|
||||
this.attributes = Object.create(this.inherited);
|
||||
Object.assign(this.defaults, defaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name The name of the attribute to set
|
||||
* @param {Property} value The value to give the named attribute
|
||||
*/
|
||||
public set(name: string, value: Property) {
|
||||
this.attributes[name] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PropertyList} list An object containing the properties to set
|
||||
*/
|
||||
public setList(list: PropertyList) {
|
||||
Object.assign(this.attributes, list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name The name of the attribute whose value is to be returned
|
||||
* @return {Property} The value of the named attribute (including inheritance and defaults)
|
||||
*/
|
||||
public get(name: string): Property {
|
||||
let value = this.attributes[name];
|
||||
if (value === INHERIT) {
|
||||
value = this.global[name];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name The value of the attribute whose value is to be returned
|
||||
* @return {Property} The attribute whose name was given if it is explicit on the
|
||||
* node (not inherited or defaulted), null otherwise
|
||||
*/
|
||||
public getExplicit(name: string): Property {
|
||||
if (!this.attributes.hasOwnProperty(name)) {
|
||||
return undefined;
|
||||
}
|
||||
return this.attributes[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} names The names of attributes whose values are to be returned
|
||||
* @return {PropertyList} An object containing the attributes and their values
|
||||
*/
|
||||
public getList(...names: string[]): PropertyList {
|
||||
let values: PropertyList = {};
|
||||
for (const name of names) {
|
||||
values[name] = this.get(name);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name The name of an inherited attribute to be set
|
||||
* @param {Property} value The value to assign to the named attribute
|
||||
*/
|
||||
public setInherited(name: string, value: Property) {
|
||||
this.inherited[name] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name The name of an inherited attribute whose value is to be returned
|
||||
* @return {Property} The value of the named attribute if it is inherited, null otherwise
|
||||
*/
|
||||
public getInherited(name: string): Property {
|
||||
return this.inherited[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name The name of a default attribute whose value is to be returned
|
||||
* @return {Property} The value of the named attribute if a default exists for it, null otherwise
|
||||
*/
|
||||
public getDefault(name: string): Property {
|
||||
return this.defaults[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name The name of a attribute to check
|
||||
* @return {boolean} True if attribute is set explicitly or inherited
|
||||
* from an explicit mstyle or math attribute
|
||||
*/
|
||||
public isSet(name: string): boolean {
|
||||
return this.attributes.hasOwnProperty(name) || this.inherited.hasOwnProperty(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name The name of an attribute to test for the existence of a default
|
||||
* @return {boolean} True of there is a default for the named attribute, false otherwise
|
||||
*/
|
||||
public hasDefault(name: string): boolean {
|
||||
return (name in this.defaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string[]} The names of all the attributes explicitly set on the node
|
||||
*/
|
||||
public getExplicitNames(): string[] {
|
||||
return Object.keys(this.attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string[]} The names of all the inherited attributes for the node
|
||||
*/
|
||||
public getInheritedNames(): string[] {
|
||||
return Object.keys(this.inherited);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string[]} The names of all the default attributes for the node
|
||||
*/
|
||||
public getDefaultNames(): string[] {
|
||||
return Object.keys(this.defaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string[]} The names of all the global attributes
|
||||
*/
|
||||
public getGlobalNames(): string[] {
|
||||
return Object.keys(this.global);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {PropertyList} The attribute object
|
||||
*/
|
||||
public getAllAttributes(): PropertyList {
|
||||
return this.attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {PropertyList} The inherited object
|
||||
*/
|
||||
public getAllInherited(): PropertyList {
|
||||
return this.inherited;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {PropertyList} The defaults object
|
||||
*/
|
||||
public getAllDefaults(): PropertyList {
|
||||
return this.defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {PropertyList} The global object
|
||||
*/
|
||||
public getAllGlobals(): PropertyList {
|
||||
return this.global;
|
||||
}
|
||||
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 A visitor that produces a JSON version of an MmlNode tree
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../Tree/Node.js';
|
||||
import {MmlVisitor} from './MmlVisitor.js';
|
||||
import {MmlNode, TextNode, XMLNode} from './MmlNode.js';
|
||||
|
||||
export type MmlNodeJSON = {
|
||||
kind: string,
|
||||
texClass: number
|
||||
isEmbellished?: boolean,
|
||||
isSpacelike?: boolean,
|
||||
isInferred?: boolean,
|
||||
childNodes: MmlJSON[],
|
||||
attributes: PropertyList,
|
||||
inherited: PropertyList,
|
||||
properties: PropertyList
|
||||
};
|
||||
|
||||
export type MmlTextJSON = {
|
||||
kind: string,
|
||||
text: string
|
||||
};
|
||||
|
||||
export type MmlXmlJSON = {
|
||||
kind: string,
|
||||
xml: any
|
||||
};
|
||||
|
||||
export type MmlJSON = MmlNodeJSON | MmlTextJSON | MmlXmlJSON;
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the JsonMmlVisitor (subclass of MmlVisitor)
|
||||
*/
|
||||
|
||||
export class JsonMmlVisitor extends MmlVisitor {
|
||||
/**
|
||||
* Convert the tree rooted at a particular node into a JSON structure
|
||||
*
|
||||
* @param {MmlNode} node The node to use as the root of the tree to traverse
|
||||
* @return {MmlJSON} The JSON object representing the internal tree
|
||||
*/
|
||||
public visitTree(node: MmlNode): MmlJSON {
|
||||
return this.visitNode(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {TextNode} node The text node to visit
|
||||
* @return {MmlJSON} The JSON for the text element
|
||||
*/
|
||||
public visitTextNode(node: TextNode): MmlTextJSON {
|
||||
return {kind: node.kind, text: node.getText()};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {XMLNode} node The XML node to visit
|
||||
* @return {MmlJSON} The JSON for the XML node
|
||||
*/
|
||||
public visitXMLNode(node: XMLNode): MmlXmlJSON {
|
||||
return {kind: node.kind, xml: node.getXML()};
|
||||
}
|
||||
|
||||
/**
|
||||
* The generic visiting function:
|
||||
* Create a DOM node of the correct type.
|
||||
* Add its explicit attributes.
|
||||
* Append its children nodes.
|
||||
* Append the new node to the DOM parent.
|
||||
*
|
||||
* @param {MmlNode} node The node to visit
|
||||
* @return {MmlJSON} The JSON object representing it
|
||||
*/
|
||||
public visitDefault(node: MmlNode): MmlJSON {
|
||||
let json: MmlJSON = {
|
||||
kind: node.kind.replace(/inferredM/, 'm'),
|
||||
texClass: node.texClass,
|
||||
attributes: this.getAttributes(node),
|
||||
inherited: this.getInherited(node),
|
||||
properties: this.getProperties(node),
|
||||
childNodes: this.getChildren(node)
|
||||
};
|
||||
if (node.isInferred) {
|
||||
json.isInferred = true;
|
||||
}
|
||||
if (node.isEmbellished) {
|
||||
json.isEmbellished = true;
|
||||
}
|
||||
if (node.isSpacelike) {
|
||||
json.isSpacelike = true;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node whose children are to be copied
|
||||
* @return {MmlJSON[]} The array of child JSON objects
|
||||
*/
|
||||
public getChildren(node: MmlNode): MmlJSON[] {
|
||||
let children = [];
|
||||
for (const child of node.childNodes) {
|
||||
children.push(this.visitNode(child));
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node whose attributes are to be copied
|
||||
* @return {PropertyList} The object containing the attributes;
|
||||
*/
|
||||
public getAttributes(node: MmlNode): PropertyList {
|
||||
return Object.assign({}, node.attributes.getAllAttributes());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node whose inherited attributes are to be copied
|
||||
* @return {PropertyList} The object containing the inherited attributes;
|
||||
*/
|
||||
public getInherited(node: MmlNode): PropertyList {
|
||||
return Object.assign({}, node.attributes.getAllInherited());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node whose properties are to be copied
|
||||
* @return {PropertyList} The object containing the properties;
|
||||
*/
|
||||
public getProperties(node: MmlNode): PropertyList {
|
||||
return Object.assign({}, node.getAllProperties());
|
||||
}
|
||||
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 A visitor to convert the new to the old internal format.
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {MmlVisitor} from './MmlVisitor.js';
|
||||
import {MmlNode, TextNode, XMLNode} from './MmlNode.js';
|
||||
|
||||
/**
|
||||
* Get access to legacy MML Element Jax
|
||||
*/
|
||||
declare var MathJax: any;
|
||||
let MML = MathJax.ElementJax.mml;
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the LegacyMmlVisitor (subclass of MmlVisitor)
|
||||
*/
|
||||
|
||||
export class LegacyMmlVisitor extends MmlVisitor {
|
||||
|
||||
/**
|
||||
* Convert the tree rooted at a particular node into the old-style
|
||||
* internal format used by MathJax v2.
|
||||
*
|
||||
* @param {MmlNode} node The node to use as the root of the tree to traverse
|
||||
* @return {any} The old-style internal format equivalent of the tree
|
||||
*/
|
||||
public visitTree(node: MmlNode): any {
|
||||
let root = MML.mrow();
|
||||
this.visitNode(node, root);
|
||||
root = root.data[0];
|
||||
root.parent = null;
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {TextNode} node The text node to visit
|
||||
* @param {any} parent The old-style parent to which this node should be added
|
||||
*/
|
||||
public visitTextNode(node: TextNode, parent: any) {
|
||||
parent.Append(MML.chars(node.getText()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {XMLNode} node The XML node to visit
|
||||
* @param {any} parent The old-style parent to which this node should be added
|
||||
*/
|
||||
public visitXMLNode(node: XMLNode, parent: any) {
|
||||
parent.Append(MML.xml(node.getXML()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit an inferred mrow, but don't add the inferred row itself (the old-style
|
||||
* nodes will add one automatically).
|
||||
*
|
||||
* @param {MmlNode} node The inferred mrow to visit
|
||||
* @param {any} parent The old-style parent to which this node's children should be added
|
||||
*/
|
||||
public visitInferredMrowNode(node: MmlNode, parent: any) {
|
||||
for (const child of node.childNodes) {
|
||||
this.visitNode(child, parent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The generic visiting function:
|
||||
* Create a node of the correct type.
|
||||
* Add its explicit attributes.
|
||||
* Add its non-attribute properties.
|
||||
* Append its children nodes.
|
||||
* Append the new node to the old-style parent.
|
||||
*
|
||||
* @param {MmlNode} node The node to visit
|
||||
* @param {any} parent The old-style parent to which this node should be added
|
||||
*/
|
||||
public visitDefault(node: MmlNode, parent: any) {
|
||||
let mml = MML[node.kind]();
|
||||
this.addAttributes(node, mml);
|
||||
this.addProperties(node, mml);
|
||||
for (const child of node.childNodes) {
|
||||
this.visitNode(child, mml);
|
||||
}
|
||||
parent.Append(mml);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node who attributes are to be copied
|
||||
* @param {any} mml The old-style node to which attributes are being added
|
||||
*/
|
||||
public addAttributes(node: MmlNode, mml: any) {
|
||||
let attributes = node.attributes;
|
||||
let names = attributes.getExplicitNames();
|
||||
for (const name of names) {
|
||||
mml[name] = attributes.getExplicit(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node whose properties are to be copied
|
||||
* @param {any} mml The old-stype node to which the properties are being copied
|
||||
*/
|
||||
public addProperties(node: MmlNode, mml: any) {
|
||||
let names = node.getPropertyNames();
|
||||
for (const name of names) {
|
||||
mml[name] = node.getProperty(name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 An object listing all the MathML node types
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {MmlNodeClass, TextNode, XMLNode} from './MmlNode.js';
|
||||
|
||||
import {MmlMath} from './MmlNodes/math.js';
|
||||
|
||||
import {MmlMi} from './MmlNodes/mi.js';
|
||||
import {MmlMn} from './MmlNodes/mn.js';
|
||||
import {MmlMo} from './MmlNodes/mo.js';
|
||||
import {MmlMtext} from './MmlNodes/mtext.js';
|
||||
import {MmlMspace} from './MmlNodes/mspace.js';
|
||||
import {MmlMs} from './MmlNodes/ms.js';
|
||||
|
||||
import {MmlMrow, MmlInferredMrow} from './MmlNodes/mrow.js';
|
||||
import {MmlMfrac} from './MmlNodes/mfrac.js';
|
||||
import {MmlMsqrt} from './MmlNodes/msqrt.js';
|
||||
import {MmlMroot} from './MmlNodes/mroot.js';
|
||||
import {MmlMstyle} from './MmlNodes/mstyle.js';
|
||||
import {MmlMerror} from './MmlNodes/merror.js';
|
||||
import {MmlMpadded} from './MmlNodes/mpadded.js';
|
||||
import {MmlMphantom} from './MmlNodes/mphantom.js';
|
||||
import {MmlMfenced} from './MmlNodes/mfenced.js';
|
||||
import {MmlMenclose} from './MmlNodes/menclose.js';
|
||||
|
||||
import {MmlMaction} from './MmlNodes/maction.js';
|
||||
|
||||
import {MmlMsubsup, MmlMsub, MmlMsup} from './MmlNodes/msubsup.js';
|
||||
import {MmlMunderover, MmlMunder, MmlMover} from './MmlNodes/munderover.js';
|
||||
import {MmlMmultiscripts, MmlMprescripts, MmlNone} from './MmlNodes/mmultiscripts.js';
|
||||
|
||||
import {MmlMtable} from './MmlNodes/mtable.js';
|
||||
import {MmlMtr, MmlMlabeledtr} from './MmlNodes/mtr.js';
|
||||
import {MmlMtd} from './MmlNodes/mtd.js';
|
||||
import {MmlMaligngroup} from './MmlNodes/maligngroup.js';
|
||||
import {MmlMalignmark} from './MmlNodes/malignmark.js';
|
||||
|
||||
import {MmlMglyph} from './MmlNodes/mglyph.js';
|
||||
|
||||
import {MmlSemantics, MmlAnnotation, MmlAnnotationXML} from './MmlNodes/semantics.js';
|
||||
|
||||
import {TeXAtom} from './MmlNodes/TeXAtom.js';
|
||||
import {MathChoice} from './MmlNodes/mathchoice.js';
|
||||
|
||||
/************************************************************************/
|
||||
/**
|
||||
* This object collects all the MathML node types together so that
|
||||
* they can be used to seed an MmlNodeFactory. One could copy this
|
||||
* object to override existing classes with subclasses, or to add new
|
||||
* classes as necessary.
|
||||
*/
|
||||
|
||||
export let MML: {[kind: string]: MmlNodeClass} = {
|
||||
[MmlMath.prototype.kind]: MmlMath,
|
||||
|
||||
[MmlMi.prototype.kind]: MmlMi,
|
||||
[MmlMn.prototype.kind]: MmlMn,
|
||||
[MmlMo.prototype.kind]: MmlMo,
|
||||
[MmlMtext.prototype.kind]: MmlMtext,
|
||||
[MmlMspace.prototype.kind]: MmlMspace,
|
||||
[MmlMs.prototype.kind]: MmlMs,
|
||||
|
||||
[MmlMrow.prototype.kind]: MmlMrow,
|
||||
[MmlInferredMrow.prototype.kind]: MmlInferredMrow,
|
||||
[MmlMfrac.prototype.kind]: MmlMfrac,
|
||||
[MmlMsqrt.prototype.kind]: MmlMsqrt,
|
||||
[MmlMroot.prototype.kind]: MmlMroot,
|
||||
[MmlMstyle.prototype.kind]: MmlMstyle,
|
||||
[MmlMerror.prototype.kind]: MmlMerror,
|
||||
[MmlMpadded.prototype.kind]: MmlMpadded,
|
||||
[MmlMphantom.prototype.kind]: MmlMphantom,
|
||||
[MmlMfenced.prototype.kind]: MmlMfenced,
|
||||
[MmlMenclose.prototype.kind]: MmlMenclose,
|
||||
|
||||
[MmlMaction.prototype.kind]: MmlMaction,
|
||||
|
||||
[MmlMsub.prototype.kind]: MmlMsub,
|
||||
[MmlMsup.prototype.kind]: MmlMsup,
|
||||
[MmlMsubsup.prototype.kind]: MmlMsubsup,
|
||||
[MmlMunder.prototype.kind]: MmlMunder,
|
||||
[MmlMover.prototype.kind]: MmlMover,
|
||||
[MmlMunderover.prototype.kind]: MmlMunderover,
|
||||
[MmlMmultiscripts.prototype.kind]: MmlMmultiscripts,
|
||||
[MmlMprescripts.prototype.kind]: MmlMprescripts,
|
||||
[MmlNone.prototype.kind]: MmlNone,
|
||||
|
||||
[MmlMtable.prototype.kind]: MmlMtable,
|
||||
[MmlMlabeledtr.prototype.kind]: MmlMlabeledtr,
|
||||
[MmlMtr.prototype.kind]: MmlMtr,
|
||||
[MmlMtd.prototype.kind]: MmlMtd,
|
||||
[MmlMaligngroup.prototype.kind]: MmlMaligngroup,
|
||||
[MmlMalignmark.prototype.kind]: MmlMalignmark,
|
||||
|
||||
[MmlMglyph.prototype.kind]: MmlMglyph,
|
||||
|
||||
[MmlSemantics.prototype.kind]: MmlSemantics,
|
||||
[MmlAnnotation.prototype.kind]: MmlAnnotation,
|
||||
[MmlAnnotationXML.prototype.kind]: MmlAnnotationXML,
|
||||
|
||||
[TeXAtom.prototype.kind]: TeXAtom,
|
||||
[MathChoice.prototype.kind]: MathChoice,
|
||||
|
||||
[TextNode.prototype.kind]: TextNode,
|
||||
[XMLNode.prototype.kind]: XMLNode
|
||||
};
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 A visitor that produces MathML DOM nodes from the iternal nodes
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {MmlVisitor} from './MmlVisitor.js';
|
||||
import {MmlNode, TextNode, XMLNode} from './MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MathMLVisitor (subclass of MmlVisitor)
|
||||
*/
|
||||
|
||||
export class MathMLVisitor extends MmlVisitor {
|
||||
/**
|
||||
* The document in which the nodes are being made
|
||||
*/
|
||||
protected document: Document = null;
|
||||
|
||||
/**
|
||||
* Convert the tree rooted at a particular node into DOM nodes.
|
||||
*
|
||||
* @param {MmlNode} node The node to use as the root of the tree to traverse
|
||||
* @param {Document} document The document in which the nodes are created
|
||||
* @return {Node} The MathML DOM nodes representing the internal tree
|
||||
*/
|
||||
public visitTree(node: MmlNode, document: Document): Node {
|
||||
this.document = document;
|
||||
let root = document.createElement('top');
|
||||
this.visitNode(node, root);
|
||||
this.document = null;
|
||||
return root.firstChild;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {TextNode} node The text node to visit
|
||||
* @param {Element} parent The DOM parent to which this node should be added
|
||||
*/
|
||||
public visitTextNode(node: TextNode, parent: Element) {
|
||||
parent.appendChild(this.document.createTextNode(node.getText()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {XMLNode} node The XML node to visit
|
||||
* @param {Element} parent The DOM parent to which this node should be added
|
||||
*/
|
||||
public visitXMLNode(node: XMLNode, parent: Element) {
|
||||
parent.appendChild((node.getXML() as Element).cloneNode(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit an inferred mrow, but don't add the inferred row itself (since
|
||||
* it is supposed to be inferred).
|
||||
*
|
||||
* @param {MmlNode} node The inferred mrow to visit
|
||||
* @param {Element} parent The DOM parent to which this node's children should be added
|
||||
*/
|
||||
public visitInferredMrowNode(node: MmlNode, parent: Element) {
|
||||
for (const child of node.childNodes) {
|
||||
this.visitNode(child, parent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The generic visiting function:
|
||||
* Create a DOM node of the correct type.
|
||||
* Add its explicit attributes.
|
||||
* Append its children nodes.
|
||||
* Append the new node to the DOM parent.
|
||||
*
|
||||
* @param {MmlNode} node The node to visit
|
||||
* @param {Element} parent The DOM parent to which this node should be added
|
||||
*/
|
||||
public visitDefault(node: MmlNode, parent: Element) {
|
||||
let mml = this.document.createElement(node.kind);
|
||||
this.addAttributes(node, mml);
|
||||
for (const child of node.childNodes) {
|
||||
this.visitNode(child, mml);
|
||||
}
|
||||
parent.appendChild(mml);
|
||||
}
|
||||
/**
|
||||
* @param {MmlNode} node The node who attributes are to be copied
|
||||
* @param {Element} mml The MathML DOM node to which attributes are being added
|
||||
*/
|
||||
public addAttributes(node: MmlNode, mml: Element) {
|
||||
let attributes = node.attributes;
|
||||
let names = attributes.getExplicitNames();
|
||||
for (const name of names) {
|
||||
mml.setAttribute(name, attributes.getExplicit(name).toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlFactory to create Mml Nodes
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {AbstractNodeFactory} from '../Tree/NodeFactory.js';
|
||||
import {MmlNode, MmlNodeClass} from './MmlNode.js';
|
||||
import {MML} from './MML.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlFactory (subclass of NodeFactory)
|
||||
*/
|
||||
|
||||
export class MmlFactory extends AbstractNodeFactory<MmlNode, MmlNodeClass> {
|
||||
|
||||
/**
|
||||
* The default node-creation functions
|
||||
*/
|
||||
public static defaultNodes = MML;
|
||||
|
||||
/**
|
||||
* @return {Object} The list of node-creation functions (similar to the
|
||||
* MML object from MathJax v2).
|
||||
*/
|
||||
get MML(): Object {
|
||||
return this.node;
|
||||
}
|
||||
|
||||
}
|
||||
+1314
File diff suppressed because it is too large
Load Diff
+100
@@ -0,0 +1,100 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 TeXAtom node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {MmlFactory} from '../MmlFactory.js';
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {AbstractMmlBaseNode, MmlNode, TEXCLASS} from '../MmlNode.js';
|
||||
import {MmlMo} from './mo.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the TeXAtom node class (subclass of AbstractMmlBaseNode)
|
||||
*/
|
||||
|
||||
export class TeXAtom extends AbstractMmlBaseNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlBaseNode.defaults
|
||||
};
|
||||
|
||||
/**
|
||||
* TeX class is ORD
|
||||
*/
|
||||
protected texclass = TEXCLASS.ORD;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'TeXAtom';
|
||||
}
|
||||
|
||||
/**
|
||||
* Inferred mrow with any number of children
|
||||
* @override
|
||||
*/
|
||||
public get arity() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* This element is not considered a MathML container
|
||||
* @override
|
||||
*/
|
||||
public get notParent() {
|
||||
return this.childNodes[0] && this.childNodes[0].childNodes.length === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
constructor(factory: MmlFactory, attributes: PropertyList, children: MmlNode[]) {
|
||||
super(factory, attributes, children);
|
||||
this.setProperty('texClass', this.texClass); // needed for serialization to include the texClass
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public setTeXclass(prev: MmlNode) {
|
||||
this.childNodes[0].setTeXclass(null);
|
||||
return this.adjustTeXclass(prev);
|
||||
}
|
||||
|
||||
/**
|
||||
* (Replaced below by the version from the MmlMo node)
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public adjustTeXclass(prev: MmlNode) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Use the method from the MmlMo class
|
||||
*/
|
||||
TeXAtom.prototype.adjustTeXclass = MmlMo.prototype.adjustTeXclass;
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMaction node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {MmlNode, AbstractMmlNode} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMaction node class (subclass of AbstractMmlNode)
|
||||
*/
|
||||
|
||||
export class MmlMaction extends AbstractMmlNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlNode.defaults,
|
||||
actiontype: 'toggle',
|
||||
selection: 1
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'maction';
|
||||
}
|
||||
|
||||
/**
|
||||
* At least one child
|
||||
* @override
|
||||
*/
|
||||
public get arity() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {MmlNode} The selected child node (or an mrow if none selected)
|
||||
*/
|
||||
public get selected(): MmlNode {
|
||||
const selection = this.attributes.get('selection') as number;
|
||||
const i = Math.max(1, Math.min(this.childNodes.length, selection)) - 1;
|
||||
return this.childNodes[i] || this.factory.create('mrow');
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get isEmbellished() {
|
||||
return this.selected.isEmbellished;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get isSpacelike() {
|
||||
return this.selected.isSpacelike;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public core(): MmlNode {
|
||||
return this.selected.core();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public coreMO(): MmlNode {
|
||||
return this.selected.coreMO();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
protected verifyAttributes(options: PropertyList) {
|
||||
super.verifyAttributes(options);
|
||||
if (this.attributes.get('actiontype') !== 'toggle' &&
|
||||
this.attributes.getExplicit('selection') !== undefined) {
|
||||
const attributes = this.attributes.getAllAttributes();
|
||||
delete attributes.selection;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the TeX class from the selceted node
|
||||
* For tooltips, set TeX classes within the tip as a separate math list
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public setTeXclass(prev: MmlNode) {
|
||||
if (this.attributes.get('actiontype') === 'tooltip' && this.childNodes[1]) {
|
||||
this.childNodes[1].setTeXclass(null);
|
||||
}
|
||||
let selected = this.selected;
|
||||
prev = selected.setTeXclass(prev);
|
||||
this.updateTeXclass(selected);
|
||||
return prev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the next child for a toggle action
|
||||
*/
|
||||
public nextToggleSelection() {
|
||||
let selection = Math.max(1, (this.attributes.get('selection') as number) + 1);
|
||||
if (selection > this.childNodes.length) {
|
||||
selection = 1;
|
||||
}
|
||||
this.attributes.set('selection', selection);
|
||||
}
|
||||
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMaligngroup node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {AbstractMmlLayoutNode, AttributeList} from '../MmlNode.js';
|
||||
import {INHERIT} from '../Attributes.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMaligngroup node class (subclass of AbstractMmlNode)
|
||||
*/
|
||||
|
||||
export class MmlMaligngroup extends AbstractMmlLayoutNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlLayoutNode.defaults,
|
||||
groupalign: INHERIT
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'maligngroup';
|
||||
}
|
||||
|
||||
/**
|
||||
* <maligngroup> is space-like
|
||||
* @override
|
||||
*/
|
||||
public get isSpacelike() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Children can inherit from <maligngroup>
|
||||
* @override
|
||||
*/
|
||||
protected setChildInheritedAttributes(attributes: AttributeList, display: boolean, level: number, prime: boolean) {
|
||||
attributes = this.addInheritedAttributes(attributes, this.attributes.getAllAttributes());
|
||||
super.setChildInheritedAttributes(attributes, display, level, prime);
|
||||
}
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMalignmark node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {AbstractMmlNode} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMalignmark node class (subclass of AbstractMmlNode)
|
||||
*/
|
||||
|
||||
export class MmlMalignmark extends AbstractMmlNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlNode.defaults,
|
||||
edge: 'left'
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'malignmark';
|
||||
}
|
||||
|
||||
/**
|
||||
* No children allowed
|
||||
* @override
|
||||
*/
|
||||
public get arity() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* <malignmark> is space-like
|
||||
* @override
|
||||
*/
|
||||
public get isSpacelike() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMath node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {AbstractMmlLayoutNode, AttributeList} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMath node class (subclass of AbstractMmlLayoutNode)
|
||||
*/
|
||||
|
||||
export class MmlMath extends AbstractMmlLayoutNode {
|
||||
|
||||
/**
|
||||
* These are used as the defaults for any attributes marked INHERIT in other classes
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlLayoutNode.defaults,
|
||||
mathvariant: 'normal',
|
||||
mathsize: 'normal',
|
||||
mathcolor: '', // Should be 'black', but allow it to inherit from surrounding text
|
||||
mathbackground: 'transparent',
|
||||
dir: 'ltr',
|
||||
scriptlevel: 0,
|
||||
displaystyle: false,
|
||||
display: 'inline',
|
||||
maxwidth: '',
|
||||
overflow: 'linebreak',
|
||||
altimg: '',
|
||||
'altimg-width': '',
|
||||
'altimg-height': '',
|
||||
'altimg-valign': '',
|
||||
alttext: '',
|
||||
cdgroup: '',
|
||||
scriptsizemultiplier: 1 / Math.sqrt(2),
|
||||
scriptminsize: '8px', // Should be 8pt, but that's too big
|
||||
infixlinebreakstyle: 'before',
|
||||
lineleading: '1ex',
|
||||
linebreakmultchar: '\u2062', // Invisible times
|
||||
indentshift: 'auto', // Use user configuration
|
||||
indentalign: 'auto',
|
||||
indenttarget: '',
|
||||
indentalignfirst: 'indentalign',
|
||||
indentshiftfirst: 'indentshift',
|
||||
indentalignlast: 'indentalign',
|
||||
indentshiftlast: 'indentshift'
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'math';
|
||||
}
|
||||
|
||||
/**
|
||||
* Linebreaking can occur in math nodes
|
||||
* @override
|
||||
*/
|
||||
public get linebreakContainer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The attributes of math nodes are inherited, so add them into the list.
|
||||
* The displaystyle attribute comes from the display attribute if not given explicitly
|
||||
* The scriptlevel comes from the scriptlevel attribute or default
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
protected setChildInheritedAttributes(attributes: AttributeList, display: boolean, level: number, prime: boolean) {
|
||||
if (this.attributes.get('mode') === 'display') {
|
||||
this.attributes.setInherited('display', 'block');
|
||||
}
|
||||
attributes = this.addInheritedAttributes(attributes, this.attributes.getAllAttributes());
|
||||
display = (!!this.attributes.get('displaystyle') ||
|
||||
(!this.attributes.get('displaystyle') && this.attributes.get('display') === 'block'));
|
||||
this.attributes.setInherited('displaystyle', display);
|
||||
level = (this.attributes.get('scriptlevel') ||
|
||||
(this.constructor as typeof MmlMath).defaults['scriptlevel']) as number;
|
||||
super.setChildInheritedAttributes(attributes, display, level, prime);
|
||||
}
|
||||
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 the MathChoice node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {AbstractMmlBaseNode, AttributeList} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MathChoice node class (subclass of AbstractMmlBaseNode)
|
||||
*
|
||||
* This is used by TeX's \mathchoice macro, but removes itself
|
||||
* during the setInheritedAttributes process
|
||||
*/
|
||||
|
||||
export class MathChoice extends AbstractMmlBaseNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlBaseNode.defaults
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'MathChoice';
|
||||
}
|
||||
|
||||
/**
|
||||
* 4 children (display, text, script, and scriptscript styles)
|
||||
* @override
|
||||
*/
|
||||
public get arity() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* This element is not considered a MathML container
|
||||
* @override
|
||||
*/
|
||||
public get notParent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the MathChoice node with the selected on based on the displaystyle and scriptlevel settings
|
||||
* (so the MathChoice never ends up in a finished MmlNode tree)
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public setInheritedAttributes(attributes: AttributeList, display: boolean, level: number, prime: boolean) {
|
||||
const selection = (display ? 0 : Math.max(0, Math.min(level, 2)) + 1);
|
||||
const child = this.childNodes[selection] || this.factory.create('mrow');
|
||||
this.parent.replaceChild(child, this);
|
||||
child.setInheritedAttributes(attributes, display, level, prime);
|
||||
}
|
||||
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMenclose node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {MmlNode, AbstractMmlNode, TEXCLASS} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlEnclose node class (subclass of AbstractMmlNode)
|
||||
*/
|
||||
|
||||
export class MmlMenclose extends AbstractMmlNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlNode.defaults,
|
||||
notation: 'longdiv'
|
||||
};
|
||||
|
||||
/**
|
||||
* TeX class is ORD
|
||||
*/
|
||||
protected texclass = TEXCLASS.ORD;
|
||||
|
||||
/**
|
||||
* The menclose kind
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'menclose';
|
||||
}
|
||||
|
||||
/**
|
||||
* <menclose> has an inferred mrow
|
||||
* @override
|
||||
*/
|
||||
public get arity() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* <menclose> is a linebreak container
|
||||
* @override
|
||||
*/
|
||||
public get linebreakContininer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public setTeXclass(prev: MmlNode) {
|
||||
prev = this.childNodes[0].setTeXclass(prev);
|
||||
this.updateTeXclass(this.childNodes[0]);
|
||||
return prev;
|
||||
}
|
||||
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMerror node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {AbstractMmlNode, TEXCLASS} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMerror node class (subclass of AbstractMmlNode)
|
||||
*/
|
||||
|
||||
export class MmlMerror extends AbstractMmlNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlNode.defaults
|
||||
};
|
||||
|
||||
/**
|
||||
* TeX class is ORD
|
||||
*/
|
||||
protected texclass = TEXCLASS.ORD;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'merror';
|
||||
}
|
||||
|
||||
/**
|
||||
* <merror> gets an inferred mrow
|
||||
* @override
|
||||
*/
|
||||
public get arity() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* <merror> can contain line breaks
|
||||
* @override
|
||||
*/
|
||||
public get linebreakContainer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMfenced node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {MmlNode, TextNode, AbstractMmlNode, AttributeList, TEXCLASS} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMfenced node class (subclass of AbstractMmlNode)
|
||||
*/
|
||||
|
||||
export class MmlMfenced extends AbstractMmlNode {
|
||||
|
||||
/**
|
||||
* @overeride
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlNode.defaults,
|
||||
open: '(',
|
||||
close: ')',
|
||||
separators: ','
|
||||
};
|
||||
|
||||
/**
|
||||
* TeX class is INNER
|
||||
*/
|
||||
protected texclass = TEXCLASS.INNER;
|
||||
|
||||
/**
|
||||
* Storage for "fake" nodes for the separators
|
||||
*/
|
||||
public separators: MmlNode[] = [];
|
||||
/**
|
||||
* Storage for "fake" open node
|
||||
*/
|
||||
public open: MmlNode = null;
|
||||
/**
|
||||
* Storage for "fake" close node
|
||||
*/
|
||||
public close: MmlNode = null;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'mfenced';
|
||||
}
|
||||
|
||||
/**
|
||||
* Include the fake nodes in the process, since they will be used
|
||||
* to produce the output.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public setTeXclass(prev: MmlNode) {
|
||||
this.getPrevClass(prev);
|
||||
if (this.open) {
|
||||
prev = this.open.setTeXclass(prev);
|
||||
}
|
||||
if (this.childNodes[0]) {
|
||||
prev = this.childNodes[0].setTeXclass(prev);
|
||||
}
|
||||
for (let i = 1, m = this.childNodes.length; i < m; i++) {
|
||||
if (this.separators[i - 1]) {
|
||||
prev = this.separators[i - 1].setTeXclass(prev);
|
||||
}
|
||||
if (this.childNodes[i]) {
|
||||
prev = this.childNodes[i].setTeXclass(prev);
|
||||
}
|
||||
}
|
||||
if (this.close) {
|
||||
prev = this.close.setTeXclass(prev);
|
||||
}
|
||||
this.updateTeXclass(this.open);
|
||||
return prev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the fake nodes and do their inheritance
|
||||
* Then do inheridence of usual children
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
protected setChildInheritedAttributes(attributes: AttributeList, display: boolean, level: number, prime: boolean) {
|
||||
this.addFakeNodes();
|
||||
for (const child of [this.open, this.close].concat(this.separators)) {
|
||||
if (child) {
|
||||
child.setInheritedAttributes(attributes, display, level, prime);
|
||||
}
|
||||
}
|
||||
super.setChildInheritedAttributes(attributes, display, level, prime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create <mo> elements for the open and close delimiters, and for the separators (if any)
|
||||
*/
|
||||
protected addFakeNodes() {
|
||||
let {open, close, separators} = this.attributes.getList('open', 'close', 'separators') as
|
||||
{open: string, close: string, separators: string};
|
||||
open = open.replace(/[ \t\n\r]/g, '');
|
||||
close = close.replace(/[ \t\n\r]/g, '');
|
||||
separators = separators.replace(/[ \t\n\r]/g, '');
|
||||
//
|
||||
// Create open node
|
||||
//
|
||||
if (open) {
|
||||
this.open = this.fakeNode(open, {fence: true, form: 'prefix'}, TEXCLASS.OPEN);
|
||||
}
|
||||
//
|
||||
// Create nodes for the separators
|
||||
//
|
||||
if (separators) {
|
||||
while (separators.length < this.childNodes.length - 1) {
|
||||
separators += separators.charAt(separators.length - 1);
|
||||
}
|
||||
let i = 0;
|
||||
for (const child of this.childNodes.slice(1)) {
|
||||
if (child) {
|
||||
this.separators.push(this.fakeNode(separators.charAt(i++)));
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
// Create close node
|
||||
//
|
||||
if (close) {
|
||||
this.close = this.fakeNode(close, {fence: true, form: 'postfix'}, TEXCLASS.CLOSE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} c The character for the text of the node
|
||||
* @param {PropertyList} properties The attributes for the node
|
||||
* @param {number} texClass The TeX class for the node
|
||||
* @return {MmlNode} The generated <mo> node
|
||||
*/
|
||||
protected fakeNode(c: string, properties: PropertyList = {}, texClass: number = null): MmlNode {
|
||||
let text = (this.factory.create('text') as TextNode).setText(c);
|
||||
let node = this.factory.create('mo', properties, [text]);
|
||||
node.texClass = texClass;
|
||||
node.parent = this;
|
||||
return node;
|
||||
}
|
||||
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMfrac node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {MmlNode, AbstractMmlBaseNode, AttributeList} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMfrac node class (subclass of AbstractMmlBaseNode)
|
||||
*/
|
||||
|
||||
export class MmlMfrac extends AbstractMmlBaseNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlBaseNode.defaults,
|
||||
linethickness: 'medium',
|
||||
numalign: 'center',
|
||||
denomalign: 'center',
|
||||
bevelled: false
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'mfrac';
|
||||
}
|
||||
|
||||
/**
|
||||
* <mfrac> requires two children
|
||||
* @override
|
||||
*/
|
||||
public get arity() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* The children of <mfrac> can include line breaks
|
||||
* @override
|
||||
*/
|
||||
public get linebreakContainer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the children separately
|
||||
* @override
|
||||
*/
|
||||
public setTeXclass(prev: MmlNode) {
|
||||
this.getPrevClass(prev);
|
||||
for (const child of this.childNodes) {
|
||||
child.setTeXclass(null);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the display level, and use prime style in denominator
|
||||
* @override
|
||||
*/
|
||||
protected setChildInheritedAttributes(attributes: AttributeList, display: boolean, level: number, prime: boolean) {
|
||||
if (!display || level > 0) {
|
||||
level++;
|
||||
}
|
||||
this.childNodes[0].setInheritedAttributes(attributes, false, level, prime);
|
||||
this.childNodes[1].setInheritedAttributes(attributes, false, level, true);
|
||||
}
|
||||
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMglyph node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {AbstractMmlTokenNode, TEXCLASS} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMglyph node class (subclass of AbstractMmlTokenNode)
|
||||
*/
|
||||
|
||||
export class MmlMglyph extends AbstractMmlTokenNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlTokenNode.defaults,
|
||||
alt: '',
|
||||
src: '',
|
||||
index: '',
|
||||
width: 'auto',
|
||||
height: 'auto',
|
||||
valign: '0em'
|
||||
};
|
||||
|
||||
/**
|
||||
* TeX class is ORD
|
||||
*/
|
||||
protected texclass = TEXCLASS.ORD;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'mglyph';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public verifyAttributes(options: PropertyList) {
|
||||
const {src, fontfamily, index} = this.attributes.getList('src', 'fontfamily', 'index');
|
||||
if (src === '' && (fontfamily === '' || index === '')) {
|
||||
this.mError('mglyph must have either src or fontfamily and index attributes', options, true);
|
||||
} else {
|
||||
super.verifyAttributes(options);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMi node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {AbstractMmlTokenNode, AbstractMmlNode, AttributeList, TEXCLASS} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMi node class (subclass of AbstractMmlTokenNode)
|
||||
*/
|
||||
|
||||
export class MmlMi extends AbstractMmlTokenNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlTokenNode.defaults
|
||||
};
|
||||
|
||||
/**
|
||||
* Pattern for operator names
|
||||
*/
|
||||
public static operatorName: RegExp = /^[a-z][a-z0-9]*$/i;
|
||||
/**
|
||||
* Pattern for single-character texts
|
||||
*/
|
||||
public static singleCharacter: RegExp =
|
||||
/^[\uD800-\uDBFF]?.[\u0300-\u036F\u1AB0-\u1ABE\u1DC0-\u1DFF\u20D0-\u20EF]*$/;
|
||||
|
||||
/**
|
||||
* TeX class is ORD
|
||||
*/
|
||||
protected texclass = TEXCLASS.ORD;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'mi';
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the usual inheritance, then check the text length to see
|
||||
* if mathvariant should be normal or italic.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public setInheritedAttributes(attributes: AttributeList = {},
|
||||
display: boolean = false, level: number = 0, prime: boolean = false) {
|
||||
super.setInheritedAttributes(attributes, display, level, prime);
|
||||
let text = this.getText();
|
||||
if (text.match(MmlMi.singleCharacter) && !attributes.mathvariant) {
|
||||
this.attributes.setInherited('mathvariant', 'italic');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark multi-character texts as OP rather than ORD for spacing purposes
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public setTeXclass(prev: AbstractMmlNode) {
|
||||
this.getPrevClass(prev);
|
||||
let name = this.getText();
|
||||
if (name.length > 1 && name.match(MmlMi.operatorName) &&
|
||||
this.attributes.get('mathvariant') === 'normal' &&
|
||||
this.getProperty('autoOP') === undefined &&
|
||||
this.getProperty('texClass') === undefined) {
|
||||
this.texClass = TEXCLASS.OP;
|
||||
this.setProperty('autoOP', true);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMmultiscripts node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {AbstractMmlNode, AttributeList} from '../MmlNode.js';
|
||||
import {MmlMsubsup} from './msubsup.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMmultiscripts node class (subclass of MmlMsubsup)
|
||||
*/
|
||||
|
||||
export class MmlMmultiscripts extends MmlMsubsup {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...MmlMsubsup.defaults
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'mmultiscripts';
|
||||
}
|
||||
|
||||
/**
|
||||
* <mmultiscripts> requires at least one child (the base)
|
||||
* @override
|
||||
*/
|
||||
public get arity() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push the inherited values to the base
|
||||
* Make sure the number of pre- and post-scripts are even by adding mrows, if needed.
|
||||
* For the scripts, use displaystyle = false, scriptlevel + 1, and
|
||||
* set the primestyle in the subscripts.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
protected setChildInheritedAttributes(attributes: AttributeList, display: boolean, level: number, prime: boolean) {
|
||||
this.childNodes[0].setInheritedAttributes(attributes, display, level, prime);
|
||||
let prescripts = false;
|
||||
for (let i = 1, n = 0; i < this.childNodes.length; i++) {
|
||||
let child = this.childNodes[i];
|
||||
if (child.isKind('mprescripts')) {
|
||||
if (!prescripts) {
|
||||
prescripts = true;
|
||||
if (i % 2 === 0) {
|
||||
let mrow = this.factory.create('mrow');
|
||||
this.childNodes.splice(i, 0, mrow);
|
||||
mrow.parent = this;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let primestyle = prime || (n % 2 === 0);
|
||||
child.setInheritedAttributes(attributes, false, level + 1, primestyle);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
if (this.childNodes.length % 2 === (prescripts ? 1 : 0)) {
|
||||
this.appendChild(this.factory.create('mrow'));
|
||||
this.childNodes[this.childNodes.length - 1].setInheritedAttributes(attributes, false, level + 1, prime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that mprescripts only occurs once, and that the number of pre- and post-scripts are even.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
protected verifyChildren(options: PropertyList) {
|
||||
let prescripts = false;
|
||||
let fix = options['fixMmultiscripts'];
|
||||
for (let i = 0; i < this.childNodes.length; i++) {
|
||||
let child = this.childNodes[i];
|
||||
if (child.isKind('mprescripts')) {
|
||||
if (prescripts) {
|
||||
child.mError(child.kind + ' can only appear once in ' + this.kind, options, true);
|
||||
} else {
|
||||
prescripts = true;
|
||||
if (i % 2 === 0 && !fix) {
|
||||
this.mError('There must be an equal number of prescripts of each type', options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.childNodes.length % 2 === (prescripts ? 1 : 0) && !fix) {
|
||||
this.mError('There must be an equal number of scripts of each type', options);
|
||||
}
|
||||
super.verifyChildren(options);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMprescripts node class (subclass of AbstractMmlNode)
|
||||
*/
|
||||
|
||||
export class MmlMprescripts extends AbstractMmlNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlNode.defaults
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {string} The mprescripts kind
|
||||
*/
|
||||
public get kind(): string {
|
||||
return 'mprescripts';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {number} <mprescripts> can have no children
|
||||
*/
|
||||
public get arity(): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that parent is mmultiscripts
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public verifyTree(options: PropertyList) {
|
||||
super.verifyTree(options);
|
||||
if (this.parent && !this.parent.isKind('mmultiscripts')) {
|
||||
this.mError(this.kind + ' must be a child of mmultiscripts', options, true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlNone node class (subclass of AbstractMmlNode)
|
||||
*/
|
||||
|
||||
export class MmlNone extends AbstractMmlNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlNode.defaults
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {string} The none kind
|
||||
*/
|
||||
public get kind(): string {
|
||||
return 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {number} <none> can have no children
|
||||
*/
|
||||
public get arity(): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that parent is mmultiscripts
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public verifyTree(options: PropertyList) {
|
||||
super.verifyTree(options);
|
||||
if (this.parent && !this.parent.isKind('mmultiscripts')) {
|
||||
this.mError(this.kind + ' must be a child of mmultiscripts', options, true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMn node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {AbstractMmlTokenNode, TEXCLASS} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMn node class (subclass of AbstractMmlTokenNode)
|
||||
*/
|
||||
|
||||
export class MmlMn extends AbstractMmlTokenNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlTokenNode.defaults
|
||||
};
|
||||
|
||||
/**
|
||||
* TeX class is ORD
|
||||
*/
|
||||
protected texclass = TEXCLASS.ORD;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'mn';
|
||||
}
|
||||
|
||||
}
|
||||
+476
@@ -0,0 +1,476 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMo node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {AbstractMmlTokenNode, MmlNode, AttributeList, TEXCLASS} from '../MmlNode.js';
|
||||
import {MmlMrow} from './mrow.js';
|
||||
import {MmlMover, MmlMunder, MmlMunderover} from './munderover.js';
|
||||
import {OperatorList, OPTABLE, getRange, MMLSPACING} from '../OperatorDictionary.js';
|
||||
import {unicodeChars, unicodeString} from '../../../util/string.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMo node class (subclass of AbstractMmlTokenNode)
|
||||
*/
|
||||
|
||||
export class MmlMo extends AbstractMmlTokenNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlTokenNode.defaults,
|
||||
form: 'infix',
|
||||
fence: false,
|
||||
separator: false,
|
||||
lspace: 'thickmathspace',
|
||||
rspace: 'thickmathspace',
|
||||
stretchy: false,
|
||||
symmetric: false,
|
||||
maxsize: 'infinity',
|
||||
minsize: '0em', // MathML says '1em', but that is larger than some natural sizes
|
||||
largeop: false,
|
||||
movablelimits: false,
|
||||
accent: false,
|
||||
linebreak: 'auto',
|
||||
lineleading: '1ex',
|
||||
linebreakstyle: 'before',
|
||||
indentalign: 'auto',
|
||||
indentshift: '0',
|
||||
indenttarget: '',
|
||||
indentalignfirst: 'indentalign',
|
||||
indentshiftfirst: 'indentshift',
|
||||
indentalignlast: 'indentalign',
|
||||
indentshiftlast: 'indentshift'
|
||||
};
|
||||
|
||||
/**
|
||||
* The MathML spacing values for the TeX classes
|
||||
*/
|
||||
public static MMLSPACING = MMLSPACING;
|
||||
|
||||
/**
|
||||
* The Operator Dictionary
|
||||
*/
|
||||
public static OPTABLE: {[form: string]: OperatorList} = OPTABLE;
|
||||
|
||||
/**
|
||||
* Pattern for matching when the contents is one ore more pseudoscripts
|
||||
*/
|
||||
public static pseudoScripts = new RegExp([
|
||||
'^["\'*`',
|
||||
'\u00AA', // FEMININE ORDINAL INDICATOR
|
||||
'\u00B0', // DEGREE SIGN
|
||||
'\u00B2-\u00B4', // SUPERSCRIPT 2 and 3, ACUTE ACCENT
|
||||
'\u00B9', // SUPERSCRIPT ONE
|
||||
'\u00BA', // MASCULINE ORDINAL INDICATOR
|
||||
'\u2018-\u201F', // Various double and single quotation marks (up and down)
|
||||
'\u2032-\u2037\u2057', // Primes and reversed primes (forward and reversed)
|
||||
'\u2070\u2071', // SUPERSCRIPT 0 and i
|
||||
'\u2074-\u207F', // SUPERCRIPT 4 through 9, -, =, (, ), and n
|
||||
'\u2080-\u208E', // SUBSCRIPT 0 through 9, -, =, (, ).
|
||||
']+$'
|
||||
].join(''));
|
||||
|
||||
/**
|
||||
* Pattern for when contents is a collection of primes
|
||||
*/
|
||||
protected static primes = new RegExp([
|
||||
'^["\'`',
|
||||
'\u2018-\u201F', // Various double and single quotation marks (up and down)
|
||||
']+$'
|
||||
].join(''));
|
||||
|
||||
/**
|
||||
* Default map for remapping prime characters
|
||||
*/
|
||||
protected static remapPrimes: {[n: number]: number} = {
|
||||
0x0022: 0x2033, // double quotes
|
||||
0x0027: 0x2032, // single quote
|
||||
0x0060: 0x2035, // back quote
|
||||
0x2018: 0x2035, // open single quote
|
||||
0x2019: 0x2032, // close single quote
|
||||
0x201A: 0x2032, // low open single quote
|
||||
0x201B: 0x2035, // reversed open single quote
|
||||
0x201C: 0x2036, // open double quote
|
||||
0x201D: 0x2033, // close double quote
|
||||
0x201E: 0x2033, // low open double quote
|
||||
0x201F: 0x2036, // reversed open double quote
|
||||
};
|
||||
|
||||
/**
|
||||
* Regular expression matching characters that are marked as math accents
|
||||
*/
|
||||
protected static mathaccents = new RegExp([
|
||||
'^[',
|
||||
'\u00B4\u0301\u02CA', // acute
|
||||
'\u0060\u0300\u02CB', // grave
|
||||
'\u00A8\u0308', // ddot
|
||||
'\u007E\u0303\u02DC', // tilde
|
||||
'\u00AF\u0304\u02C9', // bar
|
||||
'\u02D8\u0306', // breve
|
||||
'\u02C7\u030C', // check
|
||||
'\u005E\u0302\u02C6', // hat
|
||||
'\u2192\u20D7', // vec
|
||||
'\u02D9\u0307', // dot
|
||||
'\u02DA\u030A', // mathring
|
||||
'\u20DB', // dddot
|
||||
'\u20DC', // ddddot
|
||||
']$'
|
||||
].join(''));
|
||||
|
||||
/**
|
||||
* The internal TeX class of the node (for use with getter/setter below)
|
||||
*/
|
||||
public _texClass: number = null;
|
||||
|
||||
/**
|
||||
* Use a getter to look up the TeX class from the operator table if it hasn't
|
||||
* been set yet (but don't save it in case the form changes when it is in its
|
||||
* location).
|
||||
*/
|
||||
public get texClass() {
|
||||
if (this._texClass === null) {
|
||||
let mo = this.getText();
|
||||
let [form1, form2, form3] = this.handleExplicitForm(this.getForms());
|
||||
let OPTABLE = (this.constructor as typeof MmlMo).OPTABLE;
|
||||
let def = OPTABLE[form1][mo] || OPTABLE[form2][mo] || OPTABLE[form3][mo];
|
||||
return def ? def[2] : TEXCLASS.REL;
|
||||
}
|
||||
return this._texClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a setter to store the actual value in _texClass;
|
||||
*/
|
||||
public set texClass(value: number) {
|
||||
this._texClass = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default MathML spacing on the left
|
||||
*/
|
||||
/* tslint:disable-next-line:whitespace */
|
||||
public lspace = 5/18;
|
||||
|
||||
/**
|
||||
* The default MathML spacing on the right
|
||||
*/
|
||||
/* tslint:disable-next-line:whitespace */
|
||||
public rspace = 5/18;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'mo';
|
||||
}
|
||||
|
||||
/**
|
||||
* All <mo> are considered embellished
|
||||
* @override
|
||||
*/
|
||||
public get isEmbellished() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} Is <mo> marked as an explicit linebreak?
|
||||
*/
|
||||
public get hasNewLine(): boolean {
|
||||
return this.attributes.get('linebreak') === 'newline';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {MmlNode} The node that is the outermost embellished operator
|
||||
* with this node as its core
|
||||
*/
|
||||
public coreParent(): MmlNode {
|
||||
let embellished = this as MmlNode;
|
||||
let parent = this as MmlNode;
|
||||
let math = this.factory.getNodeClass('math');
|
||||
while (parent && parent.isEmbellished && parent.coreMO() === this && !(parent instanceof math)) {
|
||||
embellished = parent;
|
||||
parent = (parent as MmlNode).parent;
|
||||
}
|
||||
return embellished;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} parent The node whose core text is to be obtained
|
||||
* @return {string} The text of the core MO of the given parent element
|
||||
*/
|
||||
public coreText(parent: MmlNode): string {
|
||||
if (!parent) {
|
||||
return '';
|
||||
}
|
||||
if (parent.isEmbellished) {
|
||||
return (parent.coreMO() as MmlMo).getText();
|
||||
}
|
||||
while ((((parent.isKind('mrow') ||
|
||||
(parent.isKind('TeXAtom') && parent.texClass !== TEXCLASS.VCENTER) ||
|
||||
parent.isKind('mstyle') ||
|
||||
parent.isKind('mphantom')) && parent.childNodes.length === 1) ||
|
||||
parent.isKind('munderover')) && parent.childNodes[0]) {
|
||||
parent = parent.childNodes[0] as MmlNode;
|
||||
}
|
||||
return (parent.isToken ? (parent as AbstractMmlTokenNode).getText() : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public hasSpacingAttributes() {
|
||||
return this.attributes.isSet('lspace') ||
|
||||
this.attributes.isSet('rspace');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} True is this mo is an accent in an munderover construction
|
||||
*/
|
||||
get isAccent(): boolean {
|
||||
let accent = false;
|
||||
const node = this.coreParent().parent;
|
||||
if (node) {
|
||||
const key = (node.isKind('mover') ?
|
||||
((node.childNodes[(node as MmlMover).over] as MmlNode).coreMO() ?
|
||||
'accent' : '') :
|
||||
node.isKind('munder') ?
|
||||
((node.childNodes[(node as MmlMunder).under] as MmlNode).coreMO() ?
|
||||
'accentunder' : '') :
|
||||
node.isKind('munderover') ?
|
||||
(this === (node.childNodes[(node as MmlMunderover).over] as MmlNode).coreMO() ?
|
||||
'accent' :
|
||||
this === (node.childNodes[(node as MmlMunderover).under] as MmlNode).coreMO() ?
|
||||
'accentunder' : '') :
|
||||
'');
|
||||
if (key) {
|
||||
const value = node.attributes.getExplicit(key);
|
||||
accent = (value !== undefined ? accent : this.attributes.get('accent')) as boolean;
|
||||
}
|
||||
}
|
||||
return accent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce the texClass based on the operator dictionary values
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public setTeXclass(prev: MmlNode): MmlNode {
|
||||
let {form, fence} = this.attributes.getList('form', 'fence') as {form: string, fence: string};
|
||||
if (this.getProperty('texClass') === undefined &&
|
||||
(this.attributes.isSet('lspace') || this.attributes.isSet('rspace'))) {
|
||||
return null;
|
||||
}
|
||||
if (fence && this.texClass === TEXCLASS.REL) {
|
||||
if (form === 'prefix') {
|
||||
this.texClass = TEXCLASS.OPEN;
|
||||
}
|
||||
if (form === 'postfix') {
|
||||
this.texClass = TEXCLASS.CLOSE;
|
||||
}
|
||||
}
|
||||
return this.adjustTeXclass(prev);
|
||||
}
|
||||
/**
|
||||
* Follow the TeXBook rules for adjusting the TeX class once its neighbors are known
|
||||
*
|
||||
* @param {MmlNode} prev The node appearing before this one in the output
|
||||
* @return {MmlNode} The last node displayed (this node)
|
||||
*/
|
||||
public adjustTeXclass(prev: MmlNode): MmlNode {
|
||||
let texClass = this.texClass;
|
||||
let prevClass = this.prevClass;
|
||||
if (texClass === TEXCLASS.NONE) {
|
||||
return prev;
|
||||
}
|
||||
if (prev) {
|
||||
if (prev.getProperty('autoOP') && (texClass === TEXCLASS.BIN || texClass === TEXCLASS.REL)) {
|
||||
prevClass = prev.texClass = TEXCLASS.ORD;
|
||||
}
|
||||
prevClass = this.prevClass = (prev.texClass || TEXCLASS.ORD);
|
||||
this.prevLevel = this.attributes.getInherited('scriptlevel') as number;
|
||||
} else {
|
||||
prevClass = this.prevClass = TEXCLASS.NONE;
|
||||
}
|
||||
if (texClass === TEXCLASS.BIN &&
|
||||
(prevClass === TEXCLASS.NONE || prevClass === TEXCLASS.BIN || prevClass === TEXCLASS.OP ||
|
||||
prevClass === TEXCLASS.REL || prevClass === TEXCLASS.OPEN || prevClass === TEXCLASS.PUNCT)) {
|
||||
this.texClass = TEXCLASS.ORD;
|
||||
} else if (prevClass === TEXCLASS.BIN &&
|
||||
(texClass === TEXCLASS.REL || texClass === TEXCLASS.CLOSE || texClass === TEXCLASS.PUNCT)) {
|
||||
prev.texClass = this.prevClass = TEXCLASS.ORD;
|
||||
} else if (texClass === TEXCLASS.BIN) {
|
||||
//
|
||||
// Check if node is the last one in its container since the rule
|
||||
// above only takes effect if there is a node that follows.
|
||||
//
|
||||
let child: MmlNode = this;
|
||||
let parent = this.parent;
|
||||
while (parent && parent.parent && parent.isEmbellished &&
|
||||
(parent.childNodes.length === 1 ||
|
||||
(!parent.isKind('mrow') && parent.core() === child))) {
|
||||
child = parent;
|
||||
parent = parent.parent;
|
||||
}
|
||||
if (parent.childNodes[parent.childNodes.length - 1] === child) {
|
||||
this.texClass = TEXCLASS.ORD;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the normal inheritance, then look up the attributes from the operator dictionary.
|
||||
* If there is no dictionary entry, get the TeX class from the Unicode range list.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public setInheritedAttributes(attributes: AttributeList = {},
|
||||
display: boolean = false, level: number = 0, prime: boolean = false) {
|
||||
super.setInheritedAttributes(attributes, display, level, prime);
|
||||
let mo = this.getText();
|
||||
this.checkOperatorTable(mo);
|
||||
this.checkPseudoScripts(mo);
|
||||
this.checkPrimes(mo);
|
||||
this.checkMathAccent(mo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the attributes from the operator table
|
||||
*
|
||||
* @param {string} mo The test of the mo element
|
||||
*/
|
||||
protected checkOperatorTable(mo: string) {
|
||||
let [form1, form2, form3] = this.handleExplicitForm(this.getForms());
|
||||
this.attributes.setInherited('form', form1);
|
||||
let OPTABLE = (this.constructor as typeof MmlMo).OPTABLE;
|
||||
let def = OPTABLE[form1][mo] || OPTABLE[form2][mo] || OPTABLE[form3][mo];
|
||||
if (def) {
|
||||
if (this.getProperty('texClass') === undefined) {
|
||||
this.texClass = def[2];
|
||||
}
|
||||
for (const name of Object.keys(def[3] || {})) {
|
||||
this.attributes.setInherited(name, def[3][name]);
|
||||
}
|
||||
this.lspace = (def[0] + 1) / 18;
|
||||
this.rspace = (def[1] + 1) / 18;
|
||||
} else {
|
||||
let range = getRange(mo);
|
||||
if (range) {
|
||||
if (this.getProperty('texClass') === undefined) {
|
||||
this.texClass = range[2];
|
||||
}
|
||||
const spacing = (this.constructor as typeof MmlMo).MMLSPACING[range[2]];
|
||||
this.lspace = (spacing[0] + 1) / 18;
|
||||
this.rspace = (spacing[1] + 1) / 18;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {[string, string, string]} The list of form attribute values in the
|
||||
* order they should be tested, based on the
|
||||
* position of the element in its parent.
|
||||
*/
|
||||
public getForms(): [string, string, string] {
|
||||
let core: MmlNode = this;
|
||||
let parent = this.parent;
|
||||
let Parent = this.Parent;
|
||||
while (Parent && Parent.isEmbellished) {
|
||||
core = parent;
|
||||
parent = Parent.parent;
|
||||
Parent = Parent.Parent;
|
||||
}
|
||||
if (parent && parent.isKind('mrow') && (parent as MmlMrow).nonSpaceLength() !== 1) {
|
||||
if ((parent as MmlMrow).firstNonSpace() === core) {
|
||||
return ['prefix', 'infix', 'postfix'];
|
||||
}
|
||||
if ((parent as MmlMrow).lastNonSpace() === core) {
|
||||
return ['postfix', 'infix', 'prefix'];
|
||||
}
|
||||
}
|
||||
return ['infix', 'prefix', 'postfix'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} forms The three forms in the default order they are to be tested
|
||||
* @return {string[]} The forms in the new order, if there is an explicit form attribute
|
||||
*/
|
||||
protected handleExplicitForm(forms: string[]): string[] {
|
||||
if (this.attributes.isSet('form')) {
|
||||
const form = this.attributes.get('form') as string;
|
||||
forms = [form].concat(forms.filter(name => (name !== form)));
|
||||
}
|
||||
return forms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the mo as a pseudoscript if it is one. True means it is,
|
||||
* false means it is a pseudo-script character, but in an msup (so needs a variant form)
|
||||
*
|
||||
* @param {string} mo The test of the mo element
|
||||
*/
|
||||
protected checkPseudoScripts(mo: string) {
|
||||
const PSEUDOSCRIPTS = (this.constructor as typeof MmlMo).pseudoScripts;
|
||||
if (!mo.match(PSEUDOSCRIPTS)) return;
|
||||
const parent = this.coreParent().Parent;
|
||||
const isPseudo = !parent || !(parent.isKind('msubsup') && !parent.isKind('msub'));
|
||||
this.setProperty('pseudoscript', isPseudo);
|
||||
if (isPseudo) {
|
||||
this.attributes.setInherited('lspace', 0);
|
||||
this.attributes.setInherited('rspace', 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the mo consists of primes, and remap them if so.
|
||||
*
|
||||
* @param {string} mo The test of the mo element
|
||||
*/
|
||||
protected checkPrimes(mo: string) {
|
||||
const PRIMES = (this.constructor as typeof MmlMo).primes;
|
||||
if (!mo.match(PRIMES)) return;
|
||||
const REMAP = (this.constructor as typeof MmlMo).remapPrimes;
|
||||
const primes = unicodeString(unicodeChars(mo).map(c => REMAP[c]));
|
||||
this.setProperty('primes', primes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the mo is a mathaccent character
|
||||
*
|
||||
* @param {string} mo The test of the mo element
|
||||
*/
|
||||
protected checkMathAccent(mo: string) {
|
||||
const parent = this.Parent;
|
||||
if (this.getProperty('mathaccent') !== undefined || !parent || !parent.isKind('munderover')) return;
|
||||
const base = parent.childNodes[0] as MmlNode;
|
||||
if (base.isEmbellished && base.coreMO() === this) return;
|
||||
const MATHACCENT = (this.constructor as typeof MmlMo).mathaccents;
|
||||
if (mo.match(MATHACCENT)) {
|
||||
this.setProperty('mathaccent', true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMpadded node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {AbstractMmlLayoutNode} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMpadded node class (subclass of AbstractMmlLayoutNode)
|
||||
*/
|
||||
|
||||
export class MmlMpadded extends AbstractMmlLayoutNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlLayoutNode.defaults,
|
||||
width: '',
|
||||
height: '',
|
||||
depth: '',
|
||||
lspace: 0,
|
||||
voffset: 0
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'mpadded';
|
||||
}
|
||||
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMphantom node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {AbstractMmlLayoutNode, TEXCLASS} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMphantom node class (subclass of AbstractMmlLayoutNode)
|
||||
*/
|
||||
|
||||
export class MmlMphantom extends AbstractMmlLayoutNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlLayoutNode.defaults
|
||||
};
|
||||
|
||||
/**
|
||||
* TeX class is ORD
|
||||
*/
|
||||
protected texclass = TEXCLASS.ORD;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'mphantom';
|
||||
}
|
||||
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMroot node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {MmlNode, AbstractMmlNode, AttributeList, TEXCLASS} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMroot node class (subclass of AbstractMmlNode)
|
||||
*/
|
||||
|
||||
export class MmlMroot extends AbstractMmlNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlNode.defaults
|
||||
};
|
||||
|
||||
/**
|
||||
* TeX class is ORD
|
||||
*/
|
||||
protected texclass = TEXCLASS.ORD;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'mroot';
|
||||
}
|
||||
|
||||
/**
|
||||
* <mroot> requires two children
|
||||
* @override
|
||||
*/
|
||||
public get arity() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the TeX class for the content of the root and the root separately.
|
||||
* Return ourself as the previous item.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public setTeXclass(prev: MmlNode) {
|
||||
this.getPrevClass(prev);
|
||||
this.childNodes[0].setTeXclass(null);
|
||||
this.childNodes[1].setTeXclass(null);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the children display/level/prime for the base and root.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
protected setChildInheritedAttributes(attributes: AttributeList, display: boolean, level: number, prime: boolean) {
|
||||
this.childNodes[0].setInheritedAttributes(attributes, display, level, true);
|
||||
this.childNodes[1].setInheritedAttributes(attributes, false, level + 2, prime);
|
||||
}
|
||||
|
||||
}
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMrow node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {MmlNode, AbstractMmlNode, TEXCLASS} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMrow node class (subclass of AbstractMmlNode)
|
||||
*/
|
||||
|
||||
export class MmlMrow extends AbstractMmlNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlNode.defaults
|
||||
};
|
||||
|
||||
/**
|
||||
* The index of the core child, when acting as an embellish mrow
|
||||
*/
|
||||
protected _core: number = null;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'mrow';
|
||||
}
|
||||
|
||||
/**
|
||||
* An mrow is space-like if all its children are.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public get isSpacelike() {
|
||||
for (const child of this.childNodes) {
|
||||
if (!child.isSpacelike) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* An mrow is embellished if it contains one embellished operator
|
||||
* and any number of space-like nodes
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public get isEmbellished() {
|
||||
let embellished = false;
|
||||
let i = 0;
|
||||
for (const child of this.childNodes) {
|
||||
if (child) {
|
||||
if (child.isEmbellished) {
|
||||
if (embellished) {
|
||||
return false;
|
||||
}
|
||||
embellished = true;
|
||||
this._core = i;
|
||||
} else if (!child.isSpacelike) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return embellished;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public core(): MmlNode {
|
||||
if (!this.isEmbellished || this._core == null) {
|
||||
return this;
|
||||
}
|
||||
return this.childNodes[this._core];
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public coreMO(): MmlNode {
|
||||
if (!this.isEmbellished || this._core == null) {
|
||||
return this;
|
||||
}
|
||||
return this.childNodes[this._core].coreMO();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {number} The number of non-spacelike child nodes
|
||||
*/
|
||||
public nonSpaceLength(): number {
|
||||
let n = 0;
|
||||
for (const child of this.childNodes) {
|
||||
if (child && !child.isSpacelike) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {MmlNode|null} The first non-space-like child node
|
||||
*/
|
||||
public firstNonSpace(): MmlNode | null {
|
||||
for (const child of this.childNodes) {
|
||||
if (child && !child.isSpacelike) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {MmlNode|null} The last non-space-like child node
|
||||
*/
|
||||
public lastNonSpace(): MmlNode | null {
|
||||
let i = this.childNodes.length;
|
||||
while (--i >= 0) {
|
||||
let child = this.childNodes[i];
|
||||
if (child && !child.isSpacelike) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public setTeXclass(prev: MmlNode) {
|
||||
if (this.getProperty('open') != null || this.getProperty('close') != null) {
|
||||
//
|
||||
// <mrow> looks like it came from \left...\right
|
||||
// so treat as subexpression (TeX class INNER).
|
||||
// Use prev = null for the initial element in the
|
||||
// delimiters, since there is nothing previous to
|
||||
// it in what would be the TeX math list.
|
||||
//
|
||||
this.getPrevClass(prev);
|
||||
prev = null;
|
||||
for (const child of this.childNodes) {
|
||||
prev = child.setTeXclass(prev);
|
||||
}
|
||||
if (this.texClass == null) {
|
||||
this.texClass = TEXCLASS.INNER;
|
||||
}
|
||||
} else {
|
||||
//
|
||||
// Normal <mrow>, so treat as though mrow is not there
|
||||
//
|
||||
for (const child of this.childNodes) {
|
||||
prev = child.setTeXclass(prev);
|
||||
}
|
||||
if (this.childNodes[0]) {
|
||||
this.updateTeXclass(this.childNodes[0]);
|
||||
}
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlInferredMrow node class (subclass of MmlMrow)
|
||||
*/
|
||||
|
||||
export class MmlInferredMrow extends MmlMrow {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = MmlMrow.defaults;
|
||||
|
||||
/**
|
||||
* @return {string} The inferred-mrow kind
|
||||
*/
|
||||
public get kind(): string {
|
||||
return 'inferredMrow';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} This is inferred
|
||||
*/
|
||||
public get isInferred(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get notParent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the child nodes in brackets
|
||||
*/
|
||||
public toString() {
|
||||
return '[' + this.childNodes.join(',') + ']';
|
||||
}
|
||||
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMs node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {AbstractMmlTokenNode, TEXCLASS} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMs node class (subclass of AbstractMmlTokenNode)
|
||||
*/
|
||||
|
||||
export class MmlMs extends AbstractMmlTokenNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlTokenNode.defaults,
|
||||
lquote: '"',
|
||||
rquote: '"'
|
||||
};
|
||||
|
||||
/**
|
||||
* TeX class is ORD
|
||||
*/
|
||||
protected texclass = TEXCLASS.ORD;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'ms';
|
||||
}
|
||||
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMspace node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {MmlNode, AbstractMmlTokenNode, TEXCLASS} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMspace node class (subclass of AbstractMmlTokenNode)
|
||||
*/
|
||||
|
||||
export class MmlMspace extends AbstractMmlTokenNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlTokenNode.defaults,
|
||||
width: '0em',
|
||||
height: '0ex',
|
||||
depth: '0ex',
|
||||
linebreak: 'auto'
|
||||
};
|
||||
|
||||
/**
|
||||
* TeX class is ORD
|
||||
*/
|
||||
protected texclass = TEXCLASS.NONE;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public setTeXclass(prev: MmlNode): MmlNode {
|
||||
return prev;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'mspace';
|
||||
}
|
||||
|
||||
/**
|
||||
* mspace can't have children
|
||||
* @override
|
||||
*/
|
||||
public get arity() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get isSpacelike() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only process linebreak if the space has no explicit dimensions (according to spec)
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public get hasNewline() {
|
||||
let attributes = this.attributes;
|
||||
return (attributes.getExplicit('width') == null && attributes.getExplicit('height') == null &&
|
||||
attributes.getExplicit('depth') == null && attributes.get('linebreak') === 'newline');
|
||||
}
|
||||
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMsqrt node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {MmlNode, AbstractMmlNode, AttributeList, TEXCLASS} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMsqrt node class (subclass of AbstractMmlNode)
|
||||
*/
|
||||
|
||||
export class MmlMsqrt extends AbstractMmlNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlNode.defaults
|
||||
};
|
||||
|
||||
/**
|
||||
* TeX class is ORD
|
||||
*/
|
||||
protected texclass = TEXCLASS.ORD;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'msqrt';
|
||||
}
|
||||
|
||||
/**
|
||||
* <msqrt> has an inferred mrow
|
||||
* @override
|
||||
*/
|
||||
public get arity() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* <msqrt> can contain line breaks
|
||||
* @override
|
||||
*/
|
||||
public get linebreakContainer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public setTeXclass(prev: MmlNode) {
|
||||
this.getPrevClass(prev);
|
||||
this.childNodes[0].setTeXclass(null);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The contents of sqrt are in TeX prime style.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
protected setChildInheritedAttributes(attributes: AttributeList, display: boolean, level: number, _prime: boolean) {
|
||||
this.childNodes[0].setInheritedAttributes(attributes, display, level, true);
|
||||
}
|
||||
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMstyle node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {AbstractMmlLayoutNode, AttributeList} from '../MmlNode.js';
|
||||
import {INHERIT} from '../Attributes.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMstyle node class (subclass of AbstractMmlLayoutNode)
|
||||
*/
|
||||
|
||||
export class MmlMstyle extends AbstractMmlLayoutNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlLayoutNode.defaults,
|
||||
scriptlevel: INHERIT,
|
||||
displaystyle: INHERIT,
|
||||
scriptsizemultiplier: 1 / Math.sqrt(2),
|
||||
scriptminsize: '8px', // should be 8pt, but that is too large
|
||||
mathbackground: INHERIT,
|
||||
mathcolor: INHERIT,
|
||||
dir: INHERIT,
|
||||
infixlinebreakstyle: 'before'
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'mstyle';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get notParent() {
|
||||
return this.childNodes[0] && this.childNodes[0].childNodes.length === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle scriptlevel changes, and add mstyle attributes to the ones being inherited.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
protected setChildInheritedAttributes(attributes: AttributeList, display: boolean, level: number, prime: boolean) {
|
||||
let scriptlevel = this.attributes.getExplicit('scriptlevel');
|
||||
if (scriptlevel != null) {
|
||||
scriptlevel = scriptlevel.toString();
|
||||
if (scriptlevel.match(/^\s*[-+]/)) {
|
||||
level += parseInt(scriptlevel);
|
||||
} else {
|
||||
level = parseInt(scriptlevel);
|
||||
}
|
||||
prime = false; // style change resets tex prime style
|
||||
}
|
||||
let displaystyle = this.attributes.getExplicit('displaystyle') as boolean;
|
||||
if (displaystyle != null) {
|
||||
display = (displaystyle === true);
|
||||
prime = false; // style change resets tex prime style
|
||||
}
|
||||
const cramped = this.attributes.getExplicit('data-cramped') as boolean; // manual control of tex prime style
|
||||
if (cramped != null) {
|
||||
prime = cramped;
|
||||
}
|
||||
attributes = this.addInheritedAttributes(attributes, this.attributes.getAllAttributes());
|
||||
this.childNodes[0].setInheritedAttributes(attributes, display, level, prime);
|
||||
}
|
||||
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMsubsup, MmlMsub, and MmlMsup nodes
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {AbstractMmlBaseNode, AttributeList} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMsubsup node class (subclass of AbstractMmlBaseNode)
|
||||
*/
|
||||
|
||||
export class MmlMsubsup extends AbstractMmlBaseNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlBaseNode.defaults,
|
||||
subscriptshift: '',
|
||||
superscriptshift: ''
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'msubsup';
|
||||
}
|
||||
|
||||
/**
|
||||
* <msubsup> requires three children
|
||||
* @override
|
||||
*/
|
||||
public get arity() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {number} The position of the base element
|
||||
*/
|
||||
public get base(): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {number} The position of the subscript (overridden in msup below)
|
||||
*/
|
||||
public get sub(): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {number} The position of the superscript (overridden in msup below)
|
||||
*/
|
||||
public get sup(): number {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Super- and subscripts are not in displaymode, have scriptlevel increased, and prime style in subscripts.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
protected setChildInheritedAttributes(attributes: AttributeList, display: boolean, level: number, prime: boolean) {
|
||||
let nodes = this.childNodes;
|
||||
nodes[0].setInheritedAttributes(attributes, display, level, prime);
|
||||
nodes[1].setInheritedAttributes(attributes, false, level + 1, prime || this.sub === 1);
|
||||
if (!nodes[2]) {
|
||||
return;
|
||||
}
|
||||
nodes[2].setInheritedAttributes(attributes, false, level + 1, prime || this.sub === 2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMsub node class (subclass of MmlMsubsup)
|
||||
*/
|
||||
|
||||
export class MmlMsub extends MmlMsubsup {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...MmlMsubsup.defaults
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'msub';
|
||||
}
|
||||
|
||||
/**
|
||||
* <msub> only gets two children
|
||||
* @override
|
||||
*/
|
||||
public get arity() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMsup node class (subclass of MmlMsubsup)
|
||||
*/
|
||||
|
||||
export class MmlMsup extends MmlMsubsup {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...MmlMsubsup.defaults
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'msup';
|
||||
}
|
||||
|
||||
/**
|
||||
* <msup> only gets two children
|
||||
* @override
|
||||
*/
|
||||
get arity() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* child 1 is superscript
|
||||
* @override
|
||||
*/
|
||||
get sup() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* child 2 is null (no subscript)
|
||||
* @override
|
||||
*/
|
||||
get sub() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMtable node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {MmlNode, AbstractMmlNode, AttributeList, TEXCLASS, indentAttributes} from '../MmlNode.js';
|
||||
import {split} from '../../../util/string.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMtable node class (subclass of AbstractMmlNode)
|
||||
*/
|
||||
|
||||
export class MmlMtable extends AbstractMmlNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlNode.defaults,
|
||||
align: 'axis',
|
||||
rowalign: 'baseline',
|
||||
columnalign: 'center',
|
||||
groupalign: '{left}',
|
||||
alignmentscope: true,
|
||||
columnwidth: 'auto',
|
||||
width: 'auto',
|
||||
rowspacing: '1ex',
|
||||
columnspacing: '.8em',
|
||||
rowlines: 'none',
|
||||
columnlines: 'none',
|
||||
frame: 'none',
|
||||
framespacing: '0.4em 0.5ex',
|
||||
equalrows: false,
|
||||
equalcolumns: false,
|
||||
displaystyle: false,
|
||||
side: 'right',
|
||||
minlabelspacing: '0.8em'
|
||||
};
|
||||
|
||||
/**
|
||||
* Extra properties for this node
|
||||
*/
|
||||
public properties = {
|
||||
useHeight: true
|
||||
};
|
||||
|
||||
/**
|
||||
* TeX class is ORD
|
||||
*/
|
||||
protected texclass = TEXCLASS.ORD;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'mtable';
|
||||
}
|
||||
|
||||
/**
|
||||
* Linebreaks are allowed in tables
|
||||
* @override
|
||||
*/
|
||||
public get linebreakContainer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public setInheritedAttributes(attributes: AttributeList, display: boolean, level: number, prime: boolean) {
|
||||
//
|
||||
// Force inheritance of shift and align values (since they are needed to output tables with labels)
|
||||
// but make sure they are not given explicitly on the <mtable> tag.
|
||||
//
|
||||
for (const name of indentAttributes) {
|
||||
if (attributes[name]) {
|
||||
this.attributes.setInherited(name, attributes[name][1]);
|
||||
}
|
||||
if (this.attributes.getExplicit(name) !== undefined) {
|
||||
delete (this.attributes.getAllAttributes())[name];
|
||||
}
|
||||
}
|
||||
super.setInheritedAttributes(attributes, display, level, prime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure all children are mtr or mlabeledtr nodes
|
||||
* Inherit the table attributes, and set the display attribute based on the table's displaystyle attribute
|
||||
* Reset the prime value to false
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
protected setChildInheritedAttributes(attributes: AttributeList, display: boolean, level: number, _prime: boolean) {
|
||||
for (const child of this.childNodes) {
|
||||
if (!child.isKind('mtr')) {
|
||||
this.replaceChild(this.factory.create('mtr'), child)
|
||||
.appendChild(child);
|
||||
}
|
||||
}
|
||||
level = this.getProperty('scriptlevel') as number || level;
|
||||
display = !!(this.attributes.getExplicit('displaystyle') || this.attributes.getDefault('displaystyle'));
|
||||
attributes = this.addInheritedAttributes(attributes, {
|
||||
columnalign: this.attributes.get('columnalign'),
|
||||
rowalign: 'center'
|
||||
});
|
||||
const cramped = this.attributes.getExplicit('data-cramped') as boolean;
|
||||
const ralign = split(this.attributes.get('rowalign') as string);
|
||||
for (const child of this.childNodes) {
|
||||
attributes.rowalign[1] = ralign.shift() || attributes.rowalign[1];
|
||||
child.setInheritedAttributes(attributes, display, level, !!cramped);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that children are mtr or mlabeledtr
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
protected verifyChildren(options: PropertyList) {
|
||||
let mtr: MmlNode = null; // all consecutive non-mtr elements are collected into one mtr
|
||||
const factory = this.factory;
|
||||
for (let i = 0; i < this.childNodes.length; i++) {
|
||||
const child = this.childNodes[i];
|
||||
if (child.isKind('mtr')) {
|
||||
mtr = null; // start a new row if there are non-mtr children
|
||||
} else {
|
||||
const isMtd = child.isKind('mtd');
|
||||
//
|
||||
// If there is already an mtr for previous children, just remove the child
|
||||
// otherwise replace the child with a new mtr
|
||||
//
|
||||
if (mtr) {
|
||||
this.removeChild(child);
|
||||
i--; // there is one fewer child now
|
||||
} else {
|
||||
mtr = this.replaceChild(factory.create('mtr'), child) as MmlNode;
|
||||
}
|
||||
mtr.appendChild(isMtd ? child : factory.create('mtd', {}, [child])); // Move the child into the mtr
|
||||
if (!options['fixMtables']) {
|
||||
child.parent.removeChild(child); // remove the child from its mtd or mtr
|
||||
child.parent = this; // ... and make it think it is a child of the table again
|
||||
isMtd && mtr.appendChild(factory.create('mtd')); // child will be replaced, so make sure there is an mtd
|
||||
const merror = child.mError('Children of ' + this.kind + ' must be mtr or mlabeledtr', options, isMtd);
|
||||
mtr.childNodes[mtr.childNodes.length - 1].appendChild(merror); // append the error to the mtd in the mtr
|
||||
}
|
||||
}
|
||||
}
|
||||
super.verifyChildren(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public setTeXclass(prev: MmlNode) {
|
||||
this.getPrevClass(prev);
|
||||
for (const child of this.childNodes) {
|
||||
child.setTeXclass(null);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMtd node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {AbstractMmlBaseNode, MmlNode} from '../MmlNode.js';
|
||||
import {INHERIT} from '../Attributes.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMtd node class (subclass of AbstractMmlBaseNode)
|
||||
*/
|
||||
|
||||
export class MmlMtd extends AbstractMmlBaseNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlBaseNode.defaults,
|
||||
rowspan: 1,
|
||||
columnspan: 1,
|
||||
rowalign: INHERIT,
|
||||
columnalign: INHERIT,
|
||||
groupalign: INHERIT
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'mtd';
|
||||
}
|
||||
|
||||
/**
|
||||
* <mtd> has an inferred mrow
|
||||
* @overrride
|
||||
*/
|
||||
public get arity() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* <mtd> can contain line breaks
|
||||
* @override
|
||||
*/
|
||||
public get linebreakContainer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that parent is mtr
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
protected verifyChildren(options: PropertyList) {
|
||||
if (this.parent && !this.parent.isKind('mtr')) {
|
||||
this.mError(this.kind + ' can only be a child of an mtr or mlabeledtr', options, true);
|
||||
return;
|
||||
}
|
||||
super.verifyChildren(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public setTeXclass(prev: MmlNode) {
|
||||
this.getPrevClass(prev);
|
||||
this.childNodes[0].setTeXclass(null);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMtext node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {AbstractMmlTokenNode, TEXCLASS} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMtext node class (subclass of AbstractMmlTokenNode)
|
||||
*/
|
||||
|
||||
export class MmlMtext extends AbstractMmlTokenNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlTokenNode.defaults
|
||||
};
|
||||
|
||||
/**
|
||||
* TeX class is ORD
|
||||
*/
|
||||
protected texclass = TEXCLASS.ORD;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'mtext';
|
||||
}
|
||||
|
||||
/**
|
||||
* <mtext> is always space-like according to the spec
|
||||
* @override
|
||||
*/
|
||||
public get isSpacelike() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMtr and MmlMlabeledtr nodes
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {MmlNode, AbstractMmlNode, AttributeList} from '../MmlNode.js';
|
||||
import {INHERIT} from '../Attributes.js';
|
||||
import {split} from '../../../util/string.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMtr node class (subclass of AbstractMmlNode)
|
||||
*/
|
||||
|
||||
export class MmlMtr extends AbstractMmlNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlNode.defaults,
|
||||
rowalign: INHERIT,
|
||||
columnalign: INHERIT,
|
||||
groupalign: INHERIT
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'mtr';
|
||||
}
|
||||
|
||||
/**
|
||||
* <mtr> can contain linebreaks
|
||||
* @override
|
||||
*/
|
||||
public get linebreakContainer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherit the mtr attributes
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
protected setChildInheritedAttributes(attributes: AttributeList, display: boolean, level: number, prime: boolean) {
|
||||
for (const child of this.childNodes) {
|
||||
if (!child.isKind('mtd')) {
|
||||
this.replaceChild(this.factory.create('mtd'), child)
|
||||
.appendChild(child);
|
||||
}
|
||||
}
|
||||
const calign = split(this.attributes.get('columnalign') as string);
|
||||
if (this.arity === 1) {
|
||||
calign.unshift(this.parent.attributes.get('side') as string);
|
||||
}
|
||||
attributes = this.addInheritedAttributes(attributes, {
|
||||
rowalign: this.attributes.get('rowalign'),
|
||||
columnalign: 'center'
|
||||
});
|
||||
for (const child of this.childNodes) {
|
||||
attributes.columnalign[1] = calign.shift() || attributes.columnalign[1];
|
||||
child.setInheritedAttributes(attributes, display, level, prime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that parent is mtable and children are mtd
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
protected verifyChildren(options: PropertyList) {
|
||||
if (this.parent && !this.parent.isKind('mtable')) {
|
||||
this.mError(this.kind + ' can only be a child of an mtable', options, true);
|
||||
return;
|
||||
}
|
||||
for (const child of this.childNodes) {
|
||||
if (!child.isKind('mtd')) {
|
||||
let mtd = this.replaceChild(this.factory.create('mtd'), child) as MmlNode;
|
||||
mtd.appendChild(child);
|
||||
if (!options['fixMtables']) {
|
||||
child.mError('Children of ' + this.kind + ' must be mtd', options);
|
||||
}
|
||||
}
|
||||
}
|
||||
super.verifyChildren(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public setTeXclass(prev: MmlNode) {
|
||||
this.getPrevClass(prev);
|
||||
for (const child of this.childNodes) {
|
||||
child.setTeXclass(null);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMlabeledtr node class (subclass of MmlMtr)
|
||||
*/
|
||||
|
||||
export class MmlMlabeledtr extends MmlMtr {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'mlabeledtr';
|
||||
}
|
||||
|
||||
/**
|
||||
* <mlabeledtr> requires at least one child (the label)
|
||||
* @override
|
||||
*/
|
||||
get arity() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlMunderover node
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {AbstractMmlBaseNode, AttributeList} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMunderover node class (subclass of AbstractMmlNode)
|
||||
*/
|
||||
|
||||
export class MmlMunderover extends AbstractMmlBaseNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlBaseNode.defaults,
|
||||
accent: false,
|
||||
accentunder: false,
|
||||
align: 'center'
|
||||
};
|
||||
|
||||
/**
|
||||
* The names of attributes controling accents for each child node (reversed for mover below)
|
||||
*/
|
||||
protected static ACCENTS = ['', 'accentunder', 'accent'];
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'munderover';
|
||||
}
|
||||
|
||||
/**
|
||||
* <munderover> requires three children
|
||||
* @override
|
||||
*/
|
||||
public get arity() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {number} The base is child 0
|
||||
*/
|
||||
public get base(): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {number} Child 1 goes under (overridden by mover below)
|
||||
*/
|
||||
public get under(): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {number} Child 2 goes over (overridden by mover below)
|
||||
*/
|
||||
public get over(): number {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* <munderover> can contain line breaks
|
||||
* @override
|
||||
*/
|
||||
public get linebreakContainer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base is in prime style if there is an over node
|
||||
* Force scriptlevel change if converted to sub-sup by movablelimits on the base in non-display mode
|
||||
* Adjust displaystyle, scriptlevel, and primestyle for the under/over nodes and check if accent
|
||||
* values have changed due to the inheritance (e.g., settings in operator dictionary)
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
protected setChildInheritedAttributes(attributes: AttributeList, display: boolean, level: number, prime: boolean) {
|
||||
let nodes = this.childNodes;
|
||||
nodes[0].setInheritedAttributes(attributes, display, level, prime || !!nodes[this.over]);
|
||||
let force = !!(!display && nodes[0].coreMO().attributes.get('movablelimits'));
|
||||
let ACCENTS = (this.constructor as typeof MmlMunderover).ACCENTS;
|
||||
nodes[1].setInheritedAttributes(attributes, false,
|
||||
this.getScriptlevel(ACCENTS[1], force, level),
|
||||
prime || this.under === 1);
|
||||
this.setInheritedAccent(1, ACCENTS[1], display, level, prime, force);
|
||||
if (!nodes[2]) {
|
||||
return;
|
||||
}
|
||||
nodes[2].setInheritedAttributes(attributes, false,
|
||||
this.getScriptlevel(ACCENTS[2], force, level),
|
||||
prime || this.under === 2);
|
||||
this.setInheritedAccent(2, ACCENTS[2], display, level, prime, force);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} accent The name of the accent attribute to check ("accent" or "accentunder")
|
||||
* @param {boolean} force True if the scriptlevel change is to be forced to occur
|
||||
* @param {number} level The current scriptlevel
|
||||
* @return {number} The new script level based on the accent attribute
|
||||
*/
|
||||
protected getScriptlevel(accent: string, force: boolean, level: number): number {
|
||||
if (force || !this.attributes.get(accent)) {
|
||||
level++;
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an under or over accent should cause the appropriate accent attribute to be inherited
|
||||
* on the munderover node, and if it is not the default, re-inherit the scriptlevel, since that
|
||||
* is affected by the accent attribute
|
||||
*
|
||||
* @param {number} n The index of the node to check
|
||||
* @param {string} accent The name of the accent attribute to check ("accent" or "accentunder")
|
||||
* @param {boolean} display The displaystyle
|
||||
* @param {number} level The scriptlevel
|
||||
* @param {number} prime The TeX prime style
|
||||
* @param {boolean} force Whether to force the scriptlevel change
|
||||
*/
|
||||
protected setInheritedAccent(n: number, accent: string, display: boolean, level: number,
|
||||
prime: boolean, force: boolean) {
|
||||
let node = this.childNodes[n];
|
||||
if (this.attributes.getExplicit(accent) == null && node.isEmbellished) {
|
||||
let value = node.coreMO().attributes.get('accent');
|
||||
this.attributes.setInherited(accent, value);
|
||||
if (value !== this.attributes.getDefault(accent)) {
|
||||
node.setInheritedAttributes({}, display, this.getScriptlevel(accent, force, level), prime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMunder node class (subclass of MmlMunderover)
|
||||
*/
|
||||
|
||||
export class MmlMunder extends MmlMunderover {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...MmlMunderover.defaults
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'munder';
|
||||
}
|
||||
|
||||
/**
|
||||
* <munder> has only two children
|
||||
* @override
|
||||
*/
|
||||
public get arity() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMover node class (subclass of MmlMunderover)
|
||||
*/
|
||||
|
||||
export class MmlMover extends MmlMunderover {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...MmlMunderover.defaults
|
||||
};
|
||||
/**
|
||||
* The first child is the over accent (second never occurs)
|
||||
*/
|
||||
protected static ACCENTS = ['', 'accent', 'accentunder'];
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'mover';
|
||||
}
|
||||
|
||||
/**
|
||||
* <mover> has only two children
|
||||
* @override
|
||||
*/
|
||||
get arity() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Child 1 is the over node
|
||||
* @override
|
||||
*/
|
||||
public get over() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Child 2 is the null (the under node)
|
||||
* @override
|
||||
*/
|
||||
public get under() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 MmlSemantics, MmlAnnotation, and MmlAnnotationXML nodes
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {PropertyList} from '../../Tree/Node.js';
|
||||
import {AbstractMmlNode, AbstractMmlBaseNode} from '../MmlNode.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMroot node class (subclass of AbstractMmlBaseNode)
|
||||
*/
|
||||
|
||||
export class MmlSemantics extends AbstractMmlBaseNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlBaseNode.defaults,
|
||||
definitionUrl: null,
|
||||
encoding: null
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'semantics';
|
||||
}
|
||||
|
||||
/**
|
||||
* <semantics> requires at least one node
|
||||
* @override
|
||||
*/
|
||||
public get arity() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore <semantics> when looking for partent node
|
||||
* @override
|
||||
*/
|
||||
public get notParent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMroot node class (subclass of AbstractMmlNode)
|
||||
*/
|
||||
|
||||
export class MmlAnnotationXML extends AbstractMmlNode {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults: PropertyList = {
|
||||
...AbstractMmlNode.defaults,
|
||||
definitionUrl: null,
|
||||
encoding: null,
|
||||
cd: 'mathmlkeys',
|
||||
name: '',
|
||||
src: null
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'annotation-xml';
|
||||
}
|
||||
|
||||
/**
|
||||
* Children are XMLNodes, so don't bother inheritting to them
|
||||
* @override
|
||||
*/
|
||||
protected setChildInheritedAttributes() {}
|
||||
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlMroot node class (subclass of MmlAnnotationXML)
|
||||
*/
|
||||
|
||||
export class MmlAnnotation extends MmlAnnotationXML {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static defaults = {
|
||||
...MmlAnnotationXML.defaults
|
||||
};
|
||||
|
||||
/**
|
||||
* Extra properties for this node
|
||||
*/
|
||||
public properties = {
|
||||
isChars: true
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'annotation';
|
||||
}
|
||||
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 A visitor class that visits MmlNode trees
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {TextNode, XMLNode} from './MmlNode.js';
|
||||
import {MmlFactory} from './MmlFactory.js';
|
||||
import {AbstractVisitor} from '../Tree/Visitor.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the MmlVisitor (subclass of Visitor, and base class
|
||||
* for visitors that accept MmlNode trees)
|
||||
*/
|
||||
|
||||
export class MmlVisitor extends AbstractVisitor {
|
||||
/**
|
||||
* @param {MmlFactory} factory The MmlNode factory (defaults to MmlFactory if not given)
|
||||
*
|
||||
* @constructor
|
||||
* @extends {AbstractVisitor}
|
||||
*/
|
||||
constructor(factory: MmlFactory = null) {
|
||||
if (!factory) {
|
||||
factory = new MmlFactory();
|
||||
}
|
||||
super(factory);
|
||||
}
|
||||
|
||||
/***********************************************/
|
||||
/**
|
||||
* Stubs for overriding in subclasses
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {TextNode} node The TextNode to visit
|
||||
* @param {any[]} args Any arguments needed by the visitor
|
||||
* @return {any} Any return value needed for the visitor
|
||||
*/
|
||||
public visitTextNode(_node: TextNode, ..._args: any[]): any {}
|
||||
|
||||
/**
|
||||
* @param {XMLNode} node The XMLNode to visit
|
||||
* @param {any[]} args Any arguments needed by the visitor
|
||||
* @return {any} Any return value needed for the visitor
|
||||
*/
|
||||
public visitXMLNode(_node: XMLNode, ..._args: any[]): any {}
|
||||
|
||||
}
|
||||
+1339
File diff suppressed because it is too large
Load Diff
+248
@@ -0,0 +1,248 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 A visitor that produces a serilaied MathML string
|
||||
* (replacement for toMathML() output from v2)
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {MmlVisitor} from './MmlVisitor.js';
|
||||
import {MmlNode, TextNode, XMLNode, TEXCLASS, TEXCLASSNAMES} from './MmlNode.js';
|
||||
import {MmlMi} from './MmlNodes/mi.js';
|
||||
|
||||
|
||||
export const DATAMJX = 'data-mjx-';
|
||||
|
||||
export const toEntity = (c: string) => '&#x' + c.codePointAt(0).toString(16).toUpperCase() + ';';
|
||||
|
||||
type PropertyList = {[name: string]: string};
|
||||
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the SerializedMmlVisitor (subclass of MmlVisitor)
|
||||
*/
|
||||
|
||||
export class SerializedMmlVisitor extends MmlVisitor {
|
||||
|
||||
/**
|
||||
* Translations for the internal mathvariants
|
||||
*/
|
||||
public static variants: PropertyList = {
|
||||
'-tex-calligraphic': 'script',
|
||||
'-tex-bold-calligraphic': 'bold-script',
|
||||
'-tex-oldstyle': 'normal',
|
||||
'-tex-bold-oldstyle': 'bold',
|
||||
'-tex-mathit': 'italic'
|
||||
};
|
||||
|
||||
/**
|
||||
* Attributes to include on every element of a given kind
|
||||
*/
|
||||
public static defaultAttributes: {[kind: string]: PropertyList} = {
|
||||
math: {
|
||||
xmlns: 'http://www.w3.org/1998/Math/MathML'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert the tree rooted at a particular node into a serialized MathML string
|
||||
*
|
||||
* @param {MmlNode} node The node to use as the root of the tree to traverse
|
||||
* @return {string} The MathML string representing the internal tree
|
||||
*/
|
||||
public visitTree(node: MmlNode): string {
|
||||
return this.visitNode(node, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {TextNode} node The text node to visit
|
||||
* @param {string} space The amount of indenting for this node
|
||||
* @return {string} The (HTML-quoted) text of the node
|
||||
*/
|
||||
public visitTextNode(node: TextNode, _space: string): string {
|
||||
return this.quoteHTML(node.getText());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {XMLNode} node The XML node to visit
|
||||
* @param {string} space The amount of indenting for this node
|
||||
* @return {string} The serialization of the XML node
|
||||
*/
|
||||
public visitXMLNode(node: XMLNode, space: string): string {
|
||||
return space + node.getSerializedXML();
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit an inferred mrow, but don't add the inferred row itself (since
|
||||
* it is supposed to be inferred).
|
||||
*
|
||||
* @param {MmlNode} node The inferred mrow to visit
|
||||
* @param {string} space The amount of indenting for this node
|
||||
* @return {string} The serialized contents of the mrow, properly indented
|
||||
*/
|
||||
public visitInferredMrowNode(node: MmlNode, space: string): string {
|
||||
let mml = [];
|
||||
for (const child of node.childNodes) {
|
||||
mml.push(this.visitNode(child, space));
|
||||
}
|
||||
return mml.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit a TeXAtom node. It is turned into a mrow with the appropriate TeX class
|
||||
* indicator.
|
||||
*
|
||||
* @param {MmlNode} node The TeXAtom to visit.
|
||||
* @param {string} space The amount of indenting for this node.
|
||||
* @return {string} The serialized contents of the mrow, properly indented.
|
||||
*/
|
||||
public visitTeXAtomNode(node: MmlNode, space: string): string {
|
||||
let children = this.childNodeMml(node, space + ' ', '\n');
|
||||
let mml = space + '<mrow' + this.getAttributes(node) + '>' +
|
||||
(children.match(/\S/) ? '\n' + children + space : '') + '</mrow>';
|
||||
return mml;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The annotation node to visit
|
||||
* @param {string} space The number of spaces to use for indentation
|
||||
* @return {string} The serializied annotation element
|
||||
*/
|
||||
public visitAnnotationNode(node: MmlNode, space: string): string {
|
||||
return space + '<annotation' + this.getAttributes(node) + '>'
|
||||
+ this.childNodeMml(node, '', '')
|
||||
+ '</annotation>';
|
||||
}
|
||||
|
||||
/**
|
||||
* The generic visiting function:
|
||||
* Make the string version of the open tag, properly indented, with it attributes
|
||||
* Increate the indentation level
|
||||
* Add the childnodes
|
||||
* Add the end tag with proper spacing (empty tags have the close tag following directly)
|
||||
*
|
||||
* @param {MmlNode} node The node to visit
|
||||
* @param {string} space The number of spaces to use for indentation
|
||||
* @return {string} The serialization of the given node
|
||||
*/
|
||||
public visitDefault(node: MmlNode, space: string): string {
|
||||
let kind = node.kind;
|
||||
let [nl, endspace] = (node.isToken || node.childNodes.length === 0 ? ['', ''] : ['\n', space]);
|
||||
const children = this.childNodeMml(node, space + ' ', nl);
|
||||
return space + '<' + kind + this.getAttributes(node) + '>'
|
||||
+ (children.match(/\S/) ? nl + children + endspace : '')
|
||||
+ '</' + kind + '>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node whose children are to be added
|
||||
* @param {string} space The spaces to use for indentation
|
||||
* @param {string} nl The newline character (or empty)
|
||||
* @return {string} The serializied children
|
||||
*/
|
||||
protected childNodeMml(node: MmlNode, space: string, nl: string): string {
|
||||
let mml = '';
|
||||
for (const child of node.childNodes) {
|
||||
mml += this.visitNode(child, space) + nl;
|
||||
}
|
||||
return mml;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node whose attributes are to be produced
|
||||
* @return {string} The attribute list as a string
|
||||
*/
|
||||
protected getAttributes(node: MmlNode): string {
|
||||
const attr = [];
|
||||
const defaults = (this.constructor as typeof SerializedMmlVisitor).defaultAttributes[node.kind] || {};
|
||||
const attributes = Object.assign({},
|
||||
defaults,
|
||||
this.getDataAttributes(node),
|
||||
node.attributes.getAllAttributes()
|
||||
);
|
||||
const variants = (this.constructor as typeof SerializedMmlVisitor).variants;
|
||||
if (attributes.hasOwnProperty('mathvariant') && variants.hasOwnProperty(attributes.mathvariant)) {
|
||||
attributes.mathvariant = variants[attributes.mathvariant];
|
||||
}
|
||||
for (const name of Object.keys(attributes)) {
|
||||
const value = String(attributes[name]);
|
||||
if (value === undefined) continue;
|
||||
attr.push(name + '="' + this.quoteHTML(value) + '"');
|
||||
}
|
||||
return attr.length ? ' ' + attr.join(' ') : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the list of data-mjx-* attributes
|
||||
*
|
||||
* @param {MmlNode} node The node whose data list is to be generated
|
||||
* @return {PropertyList} The final class attribute list
|
||||
*/
|
||||
protected getDataAttributes(node: MmlNode): PropertyList {
|
||||
const data = {} as PropertyList;
|
||||
const variant = node.attributes.getExplicit('mathvariant') as string;
|
||||
const variants = (this.constructor as typeof SerializedMmlVisitor).variants;
|
||||
variant && variants.hasOwnProperty(variant) && this.setDataAttribute(data, 'variant', variant);
|
||||
node.getProperty('variantForm') && this.setDataAttribute(data, 'alternate', '1');
|
||||
node.getProperty('pseudoscript') && this.setDataAttribute(data, 'pseudoscript', 'true');
|
||||
node.getProperty('autoOP') === false && this.setDataAttribute(data, 'auto-op', 'false');
|
||||
const scriptalign = node.getProperty('scriptalign') as string;
|
||||
scriptalign && this.setDataAttribute(data, 'script-align', scriptalign);
|
||||
const texclass = node.getProperty('texClass') as number;
|
||||
if (texclass !== undefined) {
|
||||
let setclass = true;
|
||||
if (texclass === TEXCLASS.OP && node.isKind('mi')) {
|
||||
const name = (node as MmlMi).getText();
|
||||
setclass = !(name.length > 1 && name.match(MmlMi.operatorName));
|
||||
}
|
||||
setclass && this.setDataAttribute(data, 'texclass', texclass < 0 ? 'NONE' : TEXCLASSNAMES[texclass]);
|
||||
}
|
||||
node.getProperty('scriptlevel') && node.getProperty('useHeight') === false &&
|
||||
this.setDataAttribute(data, 'smallmatrix', 'true');
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PropertyList} data The class attribute list
|
||||
* @param {string} name The name for the data-mjx-name attribute
|
||||
* @param {string} value The value of the attribute
|
||||
*/
|
||||
protected setDataAttribute(data: PropertyList, name: string, value: string) {
|
||||
data[DATAMJX + name] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert HTML special characters to entities (&, <, >, ")
|
||||
* Convert multi-character Unicode characters to entities
|
||||
* Convert non-ASCII characters to entities.
|
||||
*
|
||||
* @param {string} value The string to be made HTML escaped
|
||||
* @return {string} The string with escaping performed
|
||||
*/
|
||||
protected quoteHTML(value: string): string {
|
||||
return value
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/\"/g, '"')
|
||||
.replace(/[\uD800-\uDBFF]./g, toEntity)
|
||||
.replace(/[\u0080-\uD7FF\uE000-\uFFFF]/g, toEntity);
|
||||
}
|
||||
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 A visitor that produces a serilaied MathML string
|
||||
* that contains addition information about inherited
|
||||
* attributes and internal properties.
|
||||
* (For testing purposes only.)
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {SerializedMmlVisitor} from './SerializedMmlVisitor.js';
|
||||
import {MmlNode} from './MmlNode.js';
|
||||
import {PropertyList} from '../Tree/Node.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the TestMmlVisitor (subclass of SerializedMmlVisitor)
|
||||
*/
|
||||
|
||||
export class TestMmlVisitor extends SerializedMmlVisitor {
|
||||
|
||||
/**
|
||||
* The generic visiting function:
|
||||
* Make the string versino of the open tag with it attributes (explicit and
|
||||
* inherited) and properties
|
||||
* Increate the indentation level
|
||||
* Add the childnodes
|
||||
* Add the end tag with proper spacing (empty tags have the close tag following directly)
|
||||
*
|
||||
* @param {MmlNode} node The node to visit
|
||||
* @param {string} space The number of spaces to use for indentation
|
||||
*/
|
||||
public visitDefault(node: MmlNode, space: string) {
|
||||
let kind = node.kind;
|
||||
let [nl, endspace] = (node.isToken || node.childNodes.length === 0 ? ['', ''] : ['\n', space]);
|
||||
let mml = space + '<' + kind + this.getAttributes(node) +
|
||||
this.getInherited(node) + this.getProperties(node) + '\n' + space + ' ' +
|
||||
this.attributeString({
|
||||
isEmbellished: node.isEmbellished,
|
||||
isSpacelike: node.isSpacelike,
|
||||
texClass: node.texClass
|
||||
}, '{', '}') +
|
||||
'>' + nl;
|
||||
space += ' ';
|
||||
for (const child of node.childNodes) {
|
||||
mml += this.visitNode(child, space) + nl;
|
||||
}
|
||||
mml += endspace + '</' + kind + '>';
|
||||
return mml;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node whose attributes are to be produced
|
||||
* @return {string} The attribute list as a string
|
||||
*/
|
||||
protected getAttributes(node: MmlNode): string {
|
||||
return this.attributeString(node.attributes.getAllAttributes(), '', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node whose inherited attributes are to be produced
|
||||
* @return {string} The inhertited attribute list as a string (with each in [...])
|
||||
*/
|
||||
protected getInherited(node: MmlNode): string {
|
||||
return this.attributeString(node.attributes.getAllInherited(), '[', ']');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MmlNode} node The node whose properties are to be produced
|
||||
* @return {string} The property list as a string (with each in [[...]])
|
||||
*/
|
||||
protected getProperties(node: MmlNode): string {
|
||||
return this.attributeString(node.getAllProperties(), '[[', ']]');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PropertyList} attributes The attributes to be made into a list
|
||||
* @param {string} open The opening delimiter to add before each attribute
|
||||
* @param {string} close The closing delimiter to add after each attribute
|
||||
* @return {string} The attribute list as a string
|
||||
*/
|
||||
protected attributeString(attributes: PropertyList, open: string, close: string): string {
|
||||
let ATTR = '';
|
||||
for (const name of Object.keys(attributes)) {
|
||||
ATTR += ' ' + open + name + '="' + this.quoteHTML(String(attributes[name])) + '"' + close;
|
||||
}
|
||||
return ATTR;
|
||||
}
|
||||
|
||||
}
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 the OutputJax
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {userOptions, defaultOptions, OptionList} from '../util/Options.js';
|
||||
import {MathDocument} from './MathDocument.js';
|
||||
import {MathItem} from './MathItem.js';
|
||||
import {DOMAdaptor} from '../core/DOMAdaptor.js';
|
||||
import {FunctionList} from '../util/FunctionList.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The OutputJax interface
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export interface OutputJax<N, T, D> {
|
||||
/**
|
||||
* The name of this output jax class
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The options for the instance
|
||||
*/
|
||||
options: OptionList;
|
||||
|
||||
/**
|
||||
* Lists of post-filters to call after typesetting the math
|
||||
*/
|
||||
postFilters: FunctionList;
|
||||
|
||||
/**
|
||||
* The DOM adaptor for managing HTML elements
|
||||
*/
|
||||
adaptor: DOMAdaptor<N, T, D>;
|
||||
|
||||
/**
|
||||
* @param {DOMAdaptor} adaptor The adaptor to use in this jax
|
||||
*/
|
||||
setAdaptor(adaptor: DOMAdaptor<N, T, D>): void;
|
||||
|
||||
/**
|
||||
* Do any initialization that depends on the document being set up
|
||||
*/
|
||||
initialize(): void;
|
||||
|
||||
/**
|
||||
* Reset any needed features of the output jax
|
||||
*
|
||||
* @param {any[]} args The arguments needed by the reset operation
|
||||
*/
|
||||
reset(...args: any[]): void;
|
||||
|
||||
/**
|
||||
* Typset a given MathItem
|
||||
*
|
||||
* @param {MathItem} math The MathItem to be typeset
|
||||
* @param {MathDocument} document The MathDocument in which the typesetting should occur
|
||||
* @return {N} The DOM tree for the typeset math
|
||||
*/
|
||||
typeset(math: MathItem<N, T, D>, document?: MathDocument<N, T, D>): N;
|
||||
|
||||
/**
|
||||
* Handle an escaped character (e.g., \$ from the TeX input jax preventing it from being a delimiter)
|
||||
*
|
||||
* @param {MathItem} math The MathItem to be escaped
|
||||
* @param {MathDocument} document The MathDocument in which the math occurs
|
||||
* @return {N} The DOM tree for the escaped item
|
||||
*/
|
||||
escaped(math: MathItem<N, T, D>, document?: MathDocument<N, T, D>): N;
|
||||
|
||||
/**
|
||||
* Get the metric information for all math in the given document
|
||||
*
|
||||
* @param {MathDocument} document The MathDocument being processed
|
||||
*/
|
||||
getMetrics(document: MathDocument<N, T, D>): void;
|
||||
|
||||
/**
|
||||
* Produce the stylesheet needed for this output jax
|
||||
*
|
||||
* @param {MathDocument} document The MathDocument being processed
|
||||
*/
|
||||
styleSheet(document: MathDocument<N, T, D>): N;
|
||||
|
||||
/**
|
||||
* Produce any page-specific elements needed for this output jax
|
||||
*
|
||||
* @param {MathDocument} document The MathDocument being processed
|
||||
*/
|
||||
pageElements(document: MathDocument<N, T, D>): N;
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The OutputJax abstract class
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export abstract class AbstractOutputJax<N, T, D> implements OutputJax<N, T, D> {
|
||||
|
||||
/**
|
||||
* The name for the output jax
|
||||
*/
|
||||
public static NAME: string = 'generic';
|
||||
|
||||
/**
|
||||
* The default options for the output jax
|
||||
*/
|
||||
public static OPTIONS: OptionList = {};
|
||||
|
||||
/**
|
||||
* The actual options supplied to the output jax
|
||||
*/
|
||||
public options: OptionList;
|
||||
|
||||
/**
|
||||
* Filters to run after the output is processed
|
||||
*/
|
||||
public postFilters: FunctionList;
|
||||
|
||||
/**
|
||||
* The MathDocument's DOMAdaptor
|
||||
*/
|
||||
public adaptor: DOMAdaptor<N, T, D> = null; // set by the handler
|
||||
|
||||
/**
|
||||
* @param {OptionList} options The options for this instance
|
||||
*/
|
||||
constructor(options: OptionList = {}) {
|
||||
let CLASS = this.constructor as typeof AbstractOutputJax;
|
||||
this.options = userOptions(defaultOptions({}, CLASS.OPTIONS), options);
|
||||
this.postFilters = new FunctionList();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string} The name for this output jax class
|
||||
*/
|
||||
public get name(): string {
|
||||
return (this.constructor as typeof AbstractOutputJax).NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public setAdaptor(adaptor: DOMAdaptor<N, T, D>) {
|
||||
this.adaptor = adaptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public initialize() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public reset(..._args: any[]) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract typeset(math: MathItem<N, T, D>, document?: MathDocument<N, T, D>): N;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public abstract escaped(math: MathItem<N, T, D>, document?: MathDocument<N, T, D>): N;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public getMetrics(_document: MathDocument<N, T, D>) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public styleSheet(_document: MathDocument<N, T, D>) {
|
||||
return null as N;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public pageElements(_document: MathDocument<N, T, D>) {
|
||||
return null as N;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a set of filters, passing them the MathItem and any needed data,
|
||||
* and return the (possibly modified) data
|
||||
*
|
||||
* @param {FunctionList} filters The list of functions to be performed
|
||||
* @param {MathItem} math The math item that is being processed
|
||||
* @param {MathDocument} document The math document contaiing the math item
|
||||
* @param {any} data Whatever other data is needed
|
||||
* @return {any} The (possibly modified) data
|
||||
*/
|
||||
protected executeFilters(
|
||||
filters: FunctionList, math: MathItem<N, T, D>,
|
||||
document: MathDocument<N, T, D>, data: any
|
||||
): any {
|
||||
let args = {math, document, data};
|
||||
filters.execute(args);
|
||||
return args.data;
|
||||
}
|
||||
|
||||
}
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 The generic Factory class for creating arbitrary objects
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The Factory node interfaces (one for the node instance, one for the node class)
|
||||
*/
|
||||
|
||||
export interface FactoryNode {
|
||||
readonly kind: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template N The Node type being created by the factory
|
||||
*/
|
||||
export interface FactoryNodeClass<N extends FactoryNode> {
|
||||
/**
|
||||
* @param {Factory<N, FactoryNodeClass<N>>} factory The factory for creating more nodes
|
||||
* @param {any[]} args Any additional arguments needed by the node
|
||||
* @return {N} The newly created node
|
||||
*/
|
||||
new(factory: Factory<N, FactoryNodeClass<N>>, ...args: any[]): N;
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The Factory interface
|
||||
*
|
||||
* Factory<N, C> takes a node type N and a node class C, which give
|
||||
* the interfaces for the node instance and the node constructors. We
|
||||
* need both for two reasons: first, you can't use typeof N to get C,
|
||||
* since N is a type not an object, and if N has static members, we
|
||||
* may want to access them from the results of getNodeClass(kind)
|
||||
* (this is done in MmlNodes, for example).
|
||||
*
|
||||
* @template N The node type created by the factory
|
||||
* @template C The class of the node being constructed (for access to static properties)
|
||||
*/
|
||||
export interface Factory<N extends FactoryNode, C extends FactoryNodeClass<N>> {
|
||||
/**
|
||||
* @param {string} kind The kind of node to create
|
||||
* @return {N} The newly created node of the given kind
|
||||
*/
|
||||
create(kind: string): N;
|
||||
|
||||
/**
|
||||
* Defines a class for a given node kind
|
||||
*
|
||||
* @param {string} kind The kind whose class is being defined
|
||||
* @param {C} nodeClass The class for the given kind
|
||||
*/
|
||||
setNodeClass(kind: string, nodeClass: C): void;
|
||||
|
||||
/**
|
||||
* @param {string} kind The kind of node whose class is to be returned
|
||||
* @return {C} The class object for the given kind
|
||||
*/
|
||||
getNodeClass(kind: string): C;
|
||||
|
||||
/**
|
||||
* @param {string} kind The kind whose definition is to be deleted
|
||||
*/
|
||||
deleteNodeClass(kind: string): void;
|
||||
|
||||
/**
|
||||
* @param {N} node The node to test if it is of a given kind
|
||||
* @param {string} kind The kind to test for
|
||||
* @return {boolean} True if the node is of the given kind, false otherwise
|
||||
*/
|
||||
nodeIsKind(node: N, kind: string): boolean;
|
||||
|
||||
/**
|
||||
* @return {string[]} The names of all the available kinds of nodes
|
||||
*/
|
||||
getKinds(): string[];
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The generic AbstractFactoryClass interface
|
||||
* (needed for access to defaultNodes via the constructor)
|
||||
*
|
||||
* @template N The node type created by the factory
|
||||
* @template C The class of the node being constructed (for access to static properties)
|
||||
*/
|
||||
interface AbstractFactoryClass<N extends FactoryNode, C extends FactoryNodeClass<N>> extends Function {
|
||||
defaultNodes: {[kind: string]: C};
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The generic AbstractFactory class
|
||||
*
|
||||
* @template N The node type created by the factory
|
||||
* @template C The class of the node being constructed (for access to static properties)
|
||||
*/
|
||||
export abstract class AbstractFactory<N extends FactoryNode, C extends FactoryNodeClass<N>> implements Factory<N, C> {
|
||||
|
||||
/**
|
||||
* The default collection of objects to use for the node map
|
||||
*/
|
||||
public static defaultNodes = {};
|
||||
|
||||
/**
|
||||
* The default kind
|
||||
*/
|
||||
public defaultKind = 'unknown';
|
||||
|
||||
/**
|
||||
* The map of node kinds to node classes
|
||||
*/
|
||||
protected nodeMap: Map<string, C> = new Map();
|
||||
|
||||
/**
|
||||
* An object containing functions for creating the various node kinds
|
||||
*/
|
||||
protected node: {[kind: string]: (...args: any[]) => N} = {};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
constructor(nodes: {[kind: string]: C} = null) {
|
||||
if (nodes === null) {
|
||||
nodes = (this.constructor as AbstractFactoryClass<N, C>).defaultNodes;
|
||||
}
|
||||
for (const kind of Object.keys(nodes)) {
|
||||
this.setNodeClass(kind, nodes[kind]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public create(kind: string, ...args: any[]) {
|
||||
return (this.node[kind] || this.node[this.defaultKind])(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public setNodeClass(kind: string, nodeClass: C) {
|
||||
this.nodeMap.set(kind, nodeClass);
|
||||
let THIS = this;
|
||||
let KIND = this.nodeMap.get(kind);
|
||||
this.node[kind] = (...args: any[]) => {
|
||||
return new KIND(THIS, ...args);
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public getNodeClass(kind: string): C {
|
||||
return this.nodeMap.get(kind);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public deleteNodeClass(kind: string) {
|
||||
this.nodeMap.delete(kind);
|
||||
delete this.node[kind];
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public nodeIsKind(node: N, kind: string) {
|
||||
return (node instanceof this.getNodeClass(kind));
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public getKinds() {
|
||||
return Array.from(this.nodeMap.keys());
|
||||
}
|
||||
|
||||
}
|
||||
+395
@@ -0,0 +1,395 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 Generic Node classes for node trees
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {NodeFactory} from './NodeFactory.js';
|
||||
|
||||
/**
|
||||
* PropertyList and Property are for string data like
|
||||
* attributes and other properties
|
||||
*/
|
||||
export type Property = string | number | boolean;
|
||||
export type PropertyList = {[key: string]: Property};
|
||||
|
||||
/*********************************************************/
|
||||
/**
|
||||
* The generic Node interface
|
||||
*/
|
||||
|
||||
export interface Node {
|
||||
readonly kind: string;
|
||||
/**
|
||||
* The NodeFactory to use to create additional nodes, as needed
|
||||
*/
|
||||
readonly factory: NodeFactory<Node, NodeClass>;
|
||||
parent: Node;
|
||||
childNodes: Node[];
|
||||
|
||||
/**
|
||||
* @param {string} name The name of the property to set
|
||||
* @param {Property} value The value to which the property will be set
|
||||
*/
|
||||
setProperty(name: string, value: Property): void;
|
||||
|
||||
/**
|
||||
* @param {string} name The name of the property to get
|
||||
* @return {Property} The value of the named property
|
||||
*/
|
||||
getProperty(name: string): Property;
|
||||
|
||||
/**
|
||||
* @return {string[]} An array of the names of every property currently defined
|
||||
*/
|
||||
getPropertyNames(): string[];
|
||||
|
||||
/**
|
||||
* @return {PropertyList} The propery list containing all the properties of the node
|
||||
*/
|
||||
getAllProperties(): PropertyList;
|
||||
|
||||
/**
|
||||
* @param {string[]} names The names of the properties to be removed
|
||||
*/
|
||||
removeProperty(...names: string[]): void;
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} kind The type of node to test for
|
||||
* @return {boolean} True when the node is of the given type
|
||||
*/
|
||||
isKind(kind: string): boolean;
|
||||
|
||||
/**
|
||||
* @param {Node[]} children The child nodes to add to this node
|
||||
*/
|
||||
setChildren(children: Node[]): void;
|
||||
|
||||
/**
|
||||
* @param {Node} child A node to add to this node's children
|
||||
* @return {Node} The child node that was added
|
||||
*/
|
||||
appendChild(child: Node): Node;
|
||||
|
||||
/**
|
||||
* @param {Node} newChild A child node to be inserted
|
||||
* @param {Node} oldChild A child node to be replaced
|
||||
* @return {Node} The old child node that was removed
|
||||
*/
|
||||
replaceChild(newChild: Node, oldChild: Node): Node;
|
||||
|
||||
/**
|
||||
* @param {Node} child Child node to be removed
|
||||
* @return {Node} The old child node that was removed
|
||||
*/
|
||||
removeChild(child: Node): Node;
|
||||
|
||||
/**
|
||||
* @param {Node} child A child node whose index in childNodes is desired
|
||||
* @return {number} The index of the child in childNodes, or null if not found
|
||||
*/
|
||||
childIndex(child: Node): number;
|
||||
|
||||
/**
|
||||
* Make a deep copy of the node (but with no parent).
|
||||
*/
|
||||
copy(): Node;
|
||||
|
||||
/**
|
||||
* @param {string} kind The kind of nodes to be located in the tree
|
||||
* @return {Node[]} An array of nodes that are children (at any depth) of the given kind
|
||||
*/
|
||||
findNodes(kind: string): Node[];
|
||||
|
||||
/**
|
||||
* @param {Function} func A function to apply to each node in the tree rooted at this node
|
||||
* @param {any} data Data to pass to the function (as state information)
|
||||
*/
|
||||
walkTree(func: (node: Node, data?: any) => void, data?: any): void;
|
||||
}
|
||||
|
||||
/*********************************************************/
|
||||
/**
|
||||
* The generic Node class interface
|
||||
*/
|
||||
|
||||
export interface NodeClass {
|
||||
/**
|
||||
* @param {NodeFactory} factory The NodeFactory to use to create new nodes when needed
|
||||
* @param {PropertyList} properties Any properties to be added to the node, if any
|
||||
* @param {Node[]} children The initial child nodes, if any
|
||||
* @return {Node} The newly created node
|
||||
*/
|
||||
new (factory: NodeFactory<Node, NodeClass>, properties?: PropertyList, children?: Node[]): Node;
|
||||
}
|
||||
|
||||
/*********************************************************/
|
||||
/**
|
||||
* The abstract Node class
|
||||
*/
|
||||
|
||||
export abstract class AbstractNode implements Node {
|
||||
|
||||
/**
|
||||
* The parent node for this one
|
||||
*/
|
||||
public parent: Node = null;
|
||||
|
||||
/**
|
||||
* The properties for this node
|
||||
*/
|
||||
protected properties: PropertyList = {};
|
||||
|
||||
/**
|
||||
* The children for this node
|
||||
*/
|
||||
public childNodes: Node[] = [];
|
||||
|
||||
/**
|
||||
* @param {NodeFactory} factory The NodeFactory to use to create new nodes when needed
|
||||
* @param {PropertyList} properties Any properties to be added to the node, if any
|
||||
* @param {Node[]} children The initial child nodes, if any
|
||||
*
|
||||
* @constructor
|
||||
* @implements {Node}
|
||||
*/
|
||||
constructor(readonly factory: NodeFactory<Node, NodeClass>, properties: PropertyList = {}, children: Node[] = []) {
|
||||
for (const name of Object.keys(properties)) {
|
||||
this.setProperty(name, properties[name]);
|
||||
}
|
||||
if (children.length) {
|
||||
this.setChildren(children);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public get kind() {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public setProperty(name: string, value: Property) {
|
||||
this.properties[name] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public getProperty(name: string) {
|
||||
return this.properties[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public getPropertyNames() {
|
||||
return Object.keys(this.properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public getAllProperties() {
|
||||
return this.properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public removeProperty(...names: string[]) {
|
||||
for (const name of names) {
|
||||
delete this.properties[name];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public isKind(kind: string): boolean {
|
||||
return this.factory.nodeIsKind(this, kind);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public setChildren(children: Node[]) {
|
||||
this.childNodes = [];
|
||||
for (let child of children) {
|
||||
this.appendChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public appendChild(child: Node) {
|
||||
this.childNodes.push(child);
|
||||
child.parent = this;
|
||||
return child;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public replaceChild(newChild: Node, oldChild: Node) {
|
||||
let i = this.childIndex(oldChild);
|
||||
// If i === null should we error? return null? silently fail?
|
||||
if (i !== null) {
|
||||
this.childNodes[i] = newChild;
|
||||
newChild.parent = this;
|
||||
oldChild.parent = null;
|
||||
}
|
||||
return newChild;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public removeChild(child: Node) {
|
||||
const i = this.childIndex(child);
|
||||
if (i !== null) {
|
||||
this.childNodes.splice(i, 1);
|
||||
child.parent = null;
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public childIndex(node: Node) {
|
||||
let i = this.childNodes.indexOf(node);
|
||||
return (i === -1 ? null : i);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public copy() {
|
||||
const node = (this as AbstractNode).factory.create(this.kind) as AbstractNode;
|
||||
node.properties = {...this.properties};
|
||||
for (const child of this.childNodes || []) {
|
||||
if (child) {
|
||||
node.appendChild(child.copy());
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public findNodes(kind: string) {
|
||||
let nodes: Node[] = [];
|
||||
this.walkTree((node: Node) => {
|
||||
if (node.isKind(kind)) {
|
||||
nodes.push(node);
|
||||
}
|
||||
});
|
||||
return nodes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public walkTree(func: (node: Node, data?: any) => void, data?: any) {
|
||||
func(this, data);
|
||||
for (const child of this.childNodes) {
|
||||
if (child) {
|
||||
child.walkTree(func, data);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple string version for debugging, just to get the structure.
|
||||
*/
|
||||
public toString() {
|
||||
return this.kind + '(' + this.childNodes.join(',') + ')';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*********************************************************/
|
||||
/**
|
||||
* The abstract EmptyNode class
|
||||
*/
|
||||
|
||||
export abstract class AbstractEmptyNode extends AbstractNode {
|
||||
/**
|
||||
* We don't have children, so ignore these methods
|
||||
*/
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public setChildren(_children: Node[]) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public appendChild(child: Node) {
|
||||
return child;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public replaceChild(_newChild: Node, oldChild: Node) {
|
||||
return oldChild;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public childIndex(_node: Node) {
|
||||
return null as number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't step into children (there aren't any)
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public walkTree(func: (node: Node, data?: any) => void, data?: any) {
|
||||
func(this, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple string version for debugging, just to get the structure.
|
||||
*/
|
||||
public toString() {
|
||||
return this.kind;
|
||||
}
|
||||
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 The generic NodeFactory class for creating Node objects
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {Node, PropertyList} from './Node.js';
|
||||
import {Factory, FactoryNodeClass, AbstractFactory} from './Factory.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The NodeFactory interface
|
||||
*
|
||||
* @template N The node type created by the factory
|
||||
* @template C The class of the node being constructed (for access to static properties)
|
||||
*/
|
||||
export interface NodeFactory<N extends Node, C extends FactoryNodeClass<N>> extends Factory<N, C> {
|
||||
/**
|
||||
* @param {string} kind The kind of node to create
|
||||
* @param {PropertyList} properties The list of initial properties for the node (if any)
|
||||
* @param {N[]} children The array of initial child nodes (if any)
|
||||
* @return {N} The newly created node of the given kind
|
||||
*/
|
||||
create(kind: string, properties?: PropertyList, children?: N[]): N;
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The generic NodeFactory class
|
||||
*
|
||||
* @template N The node type created by the factory
|
||||
* @template C The class of the node being constructed (for access to static properties)
|
||||
*/
|
||||
export abstract class AbstractNodeFactory<N extends Node, C extends FactoryNodeClass<N>> extends AbstractFactory<N, C> {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public create(kind: string, properties: PropertyList = {}, children: N[] = []) {
|
||||
return this.node[kind](properties, children);
|
||||
}
|
||||
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 The generic visitor class for node trees
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {Node, NodeClass, AbstractNode} from './Node.js';
|
||||
import {NodeFactory} from './NodeFactory.js';
|
||||
|
||||
/**
|
||||
* The type for the functions associated with each node class
|
||||
*/
|
||||
export type VisitorFunction = (visitor: NodeFactory<Node, NodeClass>, node: Node, ...args: any[]) => any;
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the Visitor interface
|
||||
*/
|
||||
|
||||
export interface Visitor {
|
||||
|
||||
/**
|
||||
* Visit the tree rooted at the given node (passing along any needed parameters)
|
||||
*
|
||||
* @param {Node} tree The node that is the root of the tree
|
||||
* @param {any[]} args The arguments to pass to the visitNode functions
|
||||
* @return {any} Whatever the visitNode function returns for the root tree node
|
||||
*/
|
||||
visitTree(tree: Node, ...args: any[]): any;
|
||||
|
||||
/**
|
||||
* Visit a node by calling the visitor function for the given type of node
|
||||
* (passing along any needed parameters)
|
||||
*
|
||||
* @param {Node} node The node to visit
|
||||
* @param {any[]} args The arguments to pass to the visitor function for this node
|
||||
* @return {any} Whatever the visitor function returns for this node
|
||||
*/
|
||||
visitNode(node: Node, ...args: any[]): any;
|
||||
|
||||
/**
|
||||
* The default visitor function for when no node-specific function is defined
|
||||
*
|
||||
* @param {Node} node The node to visit
|
||||
* @param {any[]} args The arguments to pass to the visitor function for this node
|
||||
* @return {any} Whatever the visitor function returns for this node
|
||||
*/
|
||||
visitDefault(node: Node, ...args: any[]): any;
|
||||
|
||||
/**
|
||||
* Define a visitor function for a given node kind
|
||||
*
|
||||
* @param {string} kind The node kind for which the handler is being defined
|
||||
* @param {VisitorFunction} handler The function to call to handle nodes of this kind
|
||||
*/
|
||||
setNodeHandler(kind: string, handler: VisitorFunction): void;
|
||||
|
||||
/**
|
||||
* Remove the visitor function for a given node kind
|
||||
*
|
||||
* @param {string} kind The node kind whose visitor function is to be removed
|
||||
*/
|
||||
removeNodeHandler(kind: string): void;
|
||||
|
||||
/**
|
||||
* The various visitor functions implemented by the subclasses, and any data they need
|
||||
*/
|
||||
[property: string]: any;
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the generic Visitor object
|
||||
*/
|
||||
|
||||
export abstract class AbstractVisitor implements Visitor {
|
||||
/**
|
||||
* Holds the mapping from node kinds to visitor funcitons
|
||||
*/
|
||||
protected nodeHandlers: Map<string, VisitorFunction> = new Map();
|
||||
|
||||
/**
|
||||
* Visitor functions are named "visitKindNode" where "Kind" is replaced by
|
||||
* the node kind; e.g., visitTextNode for kind = text.
|
||||
*
|
||||
* @param {string} kind The node kind whose method name is needed
|
||||
* @return {string} The name of the visitor method for the given node kind
|
||||
*/
|
||||
protected static methodName(kind: string): string {
|
||||
return 'visit' + (kind.charAt(0).toUpperCase() + kind.substr(1)).replace(/[^a-z0-9_]/ig, '_') + 'Node';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the node handler map by looking for methods with the correct names
|
||||
* based on the node kinds available from the factory.
|
||||
*
|
||||
* @constructor
|
||||
* @param {NodeFactory} factory The node factory for the kinds of nodes this visitor handles
|
||||
*/
|
||||
constructor(factory: NodeFactory<Node, NodeClass>) {
|
||||
for (const kind of factory.getKinds()) {
|
||||
let method = (this as Visitor)[AbstractVisitor.methodName(kind)] as VisitorFunction;
|
||||
if (method) {
|
||||
this.nodeHandlers.set(kind, method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public visitTree(tree: Node, ...args: any[]) {
|
||||
return this.visitNode(tree, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public visitNode(node: Node, ...args: any[]) {
|
||||
let handler = this.nodeHandlers.get(node.kind) || this.visitDefault;
|
||||
return handler.call(this, node, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public visitDefault(node: Node, ...args: any[]) {
|
||||
if (node instanceof AbstractNode) {
|
||||
for (const child of node.childNodes) {
|
||||
this.visitNode(child, ...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public setNodeHandler(kind: string, handler: VisitorFunction) {
|
||||
this.nodeHandlers.set(kind, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public removeNodeHandler(kind: string) {
|
||||
this.nodeHandlers.delete(kind);
|
||||
}
|
||||
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 Generic Wrapper class for adding methods to a Node class for visitors
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {Node} from './Node.js';
|
||||
import {WrapperFactory} from './WrapperFactory.js';
|
||||
|
||||
/*********************************************************/
|
||||
/**
|
||||
* The Wrapper interface
|
||||
*
|
||||
* It points to a Node object. Subclasses add methods for the visitor to call.
|
||||
*
|
||||
* @template N The Node type being wrapped
|
||||
* @template W The Wrapper type being produced
|
||||
*/
|
||||
export interface Wrapper<N extends Node, W extends Wrapper<N, W>> {
|
||||
node: N;
|
||||
readonly kind: string;
|
||||
|
||||
/**
|
||||
* @param {Node} node A node to be wrapped
|
||||
* @param {any[]} args Any additional arguments needed when creating the wrapper
|
||||
* @return {Wrapper} The wrapped node
|
||||
*/
|
||||
wrap(node: N, ...args: any[]): W;
|
||||
}
|
||||
|
||||
/*********************************************************/
|
||||
/**
|
||||
* The Wrapper class interface
|
||||
*
|
||||
* @template N The Node type being wrapped
|
||||
* @template W The Wrapper type being produced
|
||||
*/
|
||||
export interface WrapperClass<N extends Node, W extends Wrapper<N, W>> {
|
||||
/**
|
||||
* @param {WrapperFactory} factory The factory used to create more wrappers
|
||||
* @param {N} node The node to be wrapped
|
||||
* @param {any[]} args Any additional arguments needed when creating the wrapper
|
||||
* @return {W} The wrapped node
|
||||
*/
|
||||
new(factory: WrapperFactory<N, W, WrapperClass<N, W>>, node: N, ...args: any[]): W;
|
||||
}
|
||||
|
||||
/*********************************************************/
|
||||
/**
|
||||
* The abstract Wrapper class
|
||||
*
|
||||
* @template N The Node type being created by the factory
|
||||
* @template W The Wrapper type being produced
|
||||
*/
|
||||
export class AbstractWrapper<N extends Node, W extends Wrapper<N, W>> implements Wrapper<N, W> {
|
||||
/**
|
||||
* The Node object associated with this instance
|
||||
*/
|
||||
public node: N;
|
||||
|
||||
/**
|
||||
* The WrapperFactory to use to wrap child nodes, as needed
|
||||
*/
|
||||
protected factory: WrapperFactory<N, W, WrapperClass<N, W>>;
|
||||
|
||||
/**
|
||||
* The kind of this wrapper
|
||||
*/
|
||||
get kind() {
|
||||
return this.node.kind;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {WrapperFactory} factory The WrapperFactory to use to wrap child nodes when needed
|
||||
* @param {Node} node The node to wrap
|
||||
*
|
||||
* @constructor
|
||||
* @implements {Wrapper}
|
||||
*/
|
||||
constructor(factory: WrapperFactory<N, W, WrapperClass<N, W>>, node: N) {
|
||||
this.factory = factory;
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public wrap(node: N) {
|
||||
return this.factory.wrap(node);
|
||||
}
|
||||
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 Generic WrapperFactory class for creating Wrapper objects
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {Node} from './Node.js';
|
||||
import {Wrapper, WrapperClass} from './Wrapper.js';
|
||||
import {Factory, AbstractFactory} from './Factory.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The generic WrapperFactory class
|
||||
*
|
||||
* @template N The Node type being created by the factory
|
||||
* @template W The Wrapper type being produced (instance type)
|
||||
* @template C The Wrapper class (for static values)
|
||||
*/
|
||||
export interface WrapperFactory<N extends Node, W extends Wrapper<N, W>, C extends WrapperClass<N, W>>
|
||||
extends Factory<W, C> {
|
||||
/**
|
||||
* @param {N} node The node to be wrapped
|
||||
* @param {any[]} args Any additional arguments needed when wrapping the node
|
||||
* @return {W} The newly wrapped node
|
||||
*/
|
||||
wrap(node: N, ...args: any[]): W;
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The generic WrapperFactory class
|
||||
*
|
||||
* @template N The Node type being created by the factory
|
||||
* @template W The Wrapper type being produced (instance type)
|
||||
* @template C The Wrapper class (for static values)
|
||||
*/
|
||||
export abstract class AbstractWrapperFactory<N extends Node, W extends Wrapper<N, W>, C extends WrapperClass<N, W>>
|
||||
extends AbstractFactory<W, C> implements WrapperFactory<N, W, C> {
|
||||
/**
|
||||
* @param {N} node The node to be wrapped
|
||||
* @param {any[]} args Any additional arguments needed when wrapping the node
|
||||
* @return {W} The newly wrapped node
|
||||
*/
|
||||
public wrap(node: N, ...args: any[]): W {
|
||||
return this.create(node.kind, node, ...args);
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 Registers the HTML document type
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {mathjax} from '../mathjax.js';
|
||||
import {HTMLHandler} from './html/HTMLHandler.js';
|
||||
import {DOMAdaptor} from '../core/DOMAdaptor.js';
|
||||
|
||||
/**
|
||||
* Create the HTML handler object and register it with MathJax.
|
||||
*
|
||||
* @param {DOMAdaptor<N,T,D>} adaptor The DOM adaptor to use with HTML
|
||||
* @return {HTMLHandler} The newly created handler
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export function RegisterHTMLHandler<N, T, D>(adaptor: DOMAdaptor<N, T, D>): HTMLHandler<N, T, D> {
|
||||
const handler = new HTMLHandler<N, T, D>(adaptor);
|
||||
mathjax.handlers.register(handler);
|
||||
return handler;
|
||||
}
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 HTMLDocument class
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {AbstractMathDocument} from '../../core/MathDocument.js';
|
||||
import {userOptions, separateOptions, OptionList, expandable} from '../../util/Options.js';
|
||||
import {HTMLMathItem} from './HTMLMathItem.js';
|
||||
import {HTMLMathList} from './HTMLMathList.js';
|
||||
import {HTMLDomStrings} from './HTMLDomStrings.js';
|
||||
import {DOMAdaptor} from '../../core/DOMAdaptor.js';
|
||||
import {InputJax} from '../../core/InputJax.js';
|
||||
import {STATE, ProtoItem, Location} from '../../core/MathItem.js';
|
||||
import {StyleList} from '../../util/StyleList.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* List of Lists of pairs consisting of a DOM node and its text length
|
||||
*
|
||||
* These represent the Text elements that make up a single
|
||||
* string in the list of strings to be searched for math
|
||||
* (multiple consecutive Text nodes can form a single string).
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
*/
|
||||
export type HTMLNodeArray<N, T> = [N | T, number][][];
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The HTMLDocument class (extends AbstractMathDocument)
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export class HTMLDocument<N, T, D> extends AbstractMathDocument<N, T, D> {
|
||||
|
||||
/**
|
||||
* The kind of document
|
||||
*/
|
||||
public static KIND: string = 'HTML';
|
||||
|
||||
/**
|
||||
* The default options for HTMLDocument
|
||||
*/
|
||||
public static OPTIONS: OptionList = {
|
||||
...AbstractMathDocument.OPTIONS,
|
||||
renderActions: expandable({
|
||||
...AbstractMathDocument.OPTIONS.renderActions,
|
||||
styles: [STATE.INSERTED + 1, '', 'updateStyleSheet', false] // update styles on a rerender() call
|
||||
}),
|
||||
MathList: HTMLMathList, // Use the HTMLMathList for MathLists
|
||||
MathItem: HTMLMathItem, // Use the HTMLMathItem for MathItem
|
||||
DomStrings: null // Use the default DomString parser
|
||||
};
|
||||
|
||||
/**
|
||||
* Extra styles to be included in the document's stylesheet (added by extensions)
|
||||
*/
|
||||
protected styles: StyleList[];
|
||||
|
||||
/**
|
||||
* The DomString parser for locating the text in DOM trees
|
||||
*/
|
||||
public domStrings: HTMLDomStrings<N, T, D>;
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @constructor
|
||||
* @extends {AbstractMathDocument}
|
||||
*/
|
||||
constructor(document: any, adaptor: DOMAdaptor<N, T, D>, options: OptionList) {
|
||||
let [html, dom] = separateOptions(options, HTMLDomStrings.OPTIONS);
|
||||
super(document, adaptor, html);
|
||||
this.domStrings = this.options['DomStrings'] || new HTMLDomStrings<N, T, D>(dom);
|
||||
this.domStrings.adaptor = adaptor;
|
||||
this.styles = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Location object for a delimiter at the position given by index in the N's string
|
||||
* of the array of strings searched for math, recovering the original DOM node where the delimiter
|
||||
* was found.
|
||||
*
|
||||
* @param {number} N The index of the string in the string array
|
||||
* @param {number} index The position within the N's string that needs to be found
|
||||
* @param {string} delim The delimiter for this position
|
||||
* @param {HTMLNodeArray} nodes The list of node lists representing the string array
|
||||
* @return {Location} The Location object for the position of the delimiter in the document
|
||||
*/
|
||||
protected findPosition(N: number, index: number, delim: string, nodes: HTMLNodeArray<N, T>): Location<N, T> {
|
||||
const adaptor = this.adaptor;
|
||||
for (const list of nodes[N]) {
|
||||
let [node, n] = list;
|
||||
if (index <= n && adaptor.kind(node) === '#text') {
|
||||
return {node: node, n: Math.max(index, 0), delim: delim};
|
||||
}
|
||||
index -= n;
|
||||
}
|
||||
return {node: null, n: 0, delim: delim};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a ProtoItem to a MathItem (i.e., determine the actual Location
|
||||
* objects for its start and end)
|
||||
*
|
||||
* @param {ProtoItem} item The proto math item to turn into an actual MathItem
|
||||
* @param {InputJax} jax The input jax to use for the MathItem
|
||||
* @param {HTMLNodeArray} nodes The array of node lists that produced the string array
|
||||
* @return {HTMLMathItem} The MathItem for the given proto item
|
||||
*/
|
||||
protected mathItem(item: ProtoItem<N, T>, jax: InputJax<N, T, D>,
|
||||
nodes: HTMLNodeArray<N, T>): HTMLMathItem<N, T, D> {
|
||||
let math = item.math;
|
||||
let start = this.findPosition(item.n, item.start.n, item.open, nodes);
|
||||
let end = this.findPosition(item.n, item.end.n, item.close, nodes);
|
||||
return new this.options.MathItem(math, jax, item.display, start, end) as HTMLMathItem<N, T, D>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find math within the document:
|
||||
* Get the list of containers (default is document.body), and for each:
|
||||
* For each input jax:
|
||||
* Make a new MathList to store the located math
|
||||
* If the input jax processes strings:
|
||||
* If we haven't already made the string array and corresponding node list, do so
|
||||
* Ask the jax to find the math in the string array, and
|
||||
* for each one, push it onto the math list
|
||||
* Otherwise (the jax processes DOM nodes):
|
||||
* Ask the jax to find the math in the container, and
|
||||
* for each one, make the result into a MathItem, and push it on the list
|
||||
* Merge the new math list into the document's math list
|
||||
* (we use merge to maintain a sorted list of MathItems)
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public findMath(options: OptionList) {
|
||||
if (!this.processed.isSet('findMath')) {
|
||||
this.adaptor.document = this.document;
|
||||
options = userOptions({elements: this.options.elements || [this.adaptor.body(this.document)]}, options);
|
||||
for (const container of this.adaptor.getElements(options['elements'], this.document)) {
|
||||
let [strings, nodes] = [null, null] as [string[], HTMLNodeArray<N, T>];
|
||||
for (const jax of this.inputJax) {
|
||||
let list = new (this.options['MathList'])();
|
||||
if (jax.processStrings) {
|
||||
if (strings === null) {
|
||||
[strings, nodes] = this.domStrings.find(container);
|
||||
}
|
||||
for (const math of jax.findMath(strings)) {
|
||||
list.push(this.mathItem(math, jax, nodes));
|
||||
}
|
||||
} else {
|
||||
for (const math of jax.findMath(container)) {
|
||||
let item: HTMLMathItem<N, T, D> =
|
||||
new this.options.MathItem(math.math, jax, math.display, math.start, math.end);
|
||||
list.push(item);
|
||||
}
|
||||
}
|
||||
this.math.merge(list);
|
||||
}
|
||||
}
|
||||
this.processed.set('findMath');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public updateDocument() {
|
||||
if (!this.processed.isSet('updateDocument')) {
|
||||
this.addPageElements();
|
||||
this.addStyleSheet();
|
||||
super.updateDocument();
|
||||
this.processed.set('updateDocument');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add any elements needed for the document
|
||||
*/
|
||||
protected addPageElements() {
|
||||
const body = this.adaptor.body(this.document);
|
||||
const node = this.documentPageElements();
|
||||
if (node) {
|
||||
this.adaptor.append(body, node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the stylesheet to the document
|
||||
*/
|
||||
public addStyleSheet() {
|
||||
const sheet = this.documentStyleSheet();
|
||||
const adaptor = this.adaptor;
|
||||
if (sheet && !adaptor.parent(sheet)) {
|
||||
const head = adaptor.head(this.document);
|
||||
let styles = this.findSheet(head, adaptor.getAttribute(sheet, 'id'));
|
||||
if (styles) {
|
||||
adaptor.replace(sheet, styles);
|
||||
} else {
|
||||
adaptor.append(head, sheet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {N} head The document <head>
|
||||
* @param {string} id The id of the stylesheet to find
|
||||
* @param {N|null} The stylesheet with the given ID
|
||||
*/
|
||||
protected findSheet(head: N, id: string) {
|
||||
if (id) {
|
||||
for (const sheet of this.adaptor.tags(head, 'style')) {
|
||||
if (this.adaptor.getAttribute(sheet, 'id') === id) {
|
||||
return sheet;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null as N;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public removeFromDocument(restore: boolean = false) {
|
||||
if (this.processed.isSet('updateDocument')) {
|
||||
for (const math of this.math) {
|
||||
if (math.state() >= STATE.INSERTED) {
|
||||
math.state(STATE.TYPESET, restore);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.processed.clear('updateDocument');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public documentStyleSheet() {
|
||||
return this.outputJax.styleSheet(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public documentPageElements() {
|
||||
return this.outputJax.pageElements(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add styles to be included in the document's stylesheet
|
||||
*
|
||||
* @param {StyleList} styles The styles to include
|
||||
*/
|
||||
public addStyles(styles: StyleList) {
|
||||
this.styles.push(styles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of document-specific styles
|
||||
*/
|
||||
public getStyles() {
|
||||
return this.styles;
|
||||
}
|
||||
|
||||
}
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 HTMLDomStrings class
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {userOptions, defaultOptions, OptionList, makeArray} from '../../util/Options.js';
|
||||
import {DOMAdaptor} from '../../core/DOMAdaptor.js';
|
||||
|
||||
/**
|
||||
* List of consecutive text nodes and their text lengths
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
*/
|
||||
export type HTMLNodeList<N, T> = [N | T, number][];
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* The HTMLDocument class (extends AbstractMathDocument)
|
||||
*
|
||||
* A class for extracting the text from DOM trees
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export class HTMLDomStrings<N, T, D> {
|
||||
|
||||
/**
|
||||
* The default options for string processing
|
||||
*/
|
||||
public static OPTIONS: OptionList = {
|
||||
skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code', 'annotation', 'annotation-xml'],
|
||||
// The names of the tags whose contents will not be
|
||||
// scanned for math delimiters
|
||||
|
||||
includeHtmlTags: {br: '\n', wbr: '', '#comment': ''},
|
||||
// tags to be included in the text (and what
|
||||
// text to replace them with)
|
||||
|
||||
ignoreHtmlClass: 'mathjax_ignore', // the class name of elements whose contents should
|
||||
// NOT be processed by tex2jax. Note that this
|
||||
// is a regular expression, so be sure to quote any
|
||||
// regexp special characters
|
||||
|
||||
processHtmlClass: 'mathjax_process' // the class name of elements whose contents SHOULD
|
||||
// be processed when they appear inside ones that
|
||||
// are ignored. Note that this is a regular expression,
|
||||
// so be sure to quote any regexp special characters
|
||||
};
|
||||
|
||||
/**
|
||||
* The options for this instance
|
||||
*/
|
||||
protected options: OptionList;
|
||||
|
||||
/**
|
||||
* The array of strings found in the DOM
|
||||
*/
|
||||
protected strings: string[];
|
||||
|
||||
/**
|
||||
* The string currently being constructed
|
||||
*/
|
||||
protected string: string;
|
||||
|
||||
/**
|
||||
* The list of nodes and lengths for the string being constructed
|
||||
*/
|
||||
protected snodes: HTMLNodeList<N, T>;
|
||||
|
||||
/**
|
||||
* The list of node lists corresponding to the strings in this.strings
|
||||
*/
|
||||
protected nodes: HTMLNodeList<N, T>[];
|
||||
|
||||
/**
|
||||
* The container nodes that are currently being traversed, and whether their
|
||||
* contents are being ignored or not
|
||||
*/
|
||||
protected stack: [N | T, boolean][];
|
||||
|
||||
/**
|
||||
* Regular expression for the tags to be skipped
|
||||
* processing of math
|
||||
*/
|
||||
protected skipHtmlTags: RegExp;
|
||||
/**
|
||||
* Regular expression for which classes should stop processing of math
|
||||
*/
|
||||
protected ignoreHtmlClass: RegExp;
|
||||
/**
|
||||
* Regular expression for which classes should start processing of math
|
||||
*/
|
||||
protected processHtmlClass: RegExp;
|
||||
|
||||
/**
|
||||
* The DOM Adaptor to managing HTML elements
|
||||
*/
|
||||
public adaptor: DOMAdaptor<N, T, D>;
|
||||
|
||||
/**
|
||||
* @param {OptionList} options The user-supplied options
|
||||
* @constructor
|
||||
*/
|
||||
constructor(options: OptionList = null) {
|
||||
let CLASS = this.constructor as typeof HTMLDomStrings;
|
||||
this.options = userOptions(defaultOptions({}, CLASS.OPTIONS), options);
|
||||
this.init();
|
||||
this.getPatterns();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the initial values of the main properties
|
||||
*/
|
||||
protected init() {
|
||||
this.strings = [];
|
||||
this.string = '';
|
||||
this.snodes = [];
|
||||
this.nodes = [];
|
||||
this.stack = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the search patterns for skipHtmlTags, ignoreHtmlClass, and processHtmlClass
|
||||
*/
|
||||
protected getPatterns() {
|
||||
let skip = makeArray(this.options['skipHtmlTags']);
|
||||
let ignore = makeArray(this.options['ignoreHtmlClass']);
|
||||
let process = makeArray(this.options['processHtmlClass']);
|
||||
this.skipHtmlTags = new RegExp('^(?:' + skip.join('|') + ')$', 'i');
|
||||
this.ignoreHtmlClass = new RegExp('(?:^| )(?:' + ignore.join('|') + ')(?: |$)');
|
||||
this.processHtmlClass = new RegExp('(?:^| )(?:' + process + ')(?: |$)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a string to the string array and record its node list
|
||||
*/
|
||||
protected pushString() {
|
||||
if (this.string.match(/\S/)) {
|
||||
this.strings.push(this.string);
|
||||
this.nodes.push(this.snodes);
|
||||
}
|
||||
this.string = '';
|
||||
this.snodes = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add more text to the current string, and record the
|
||||
* node and its position in the string.
|
||||
*
|
||||
* @param {N|T} node The node to be pushed
|
||||
* @param {string} text The text to be added (it may not be the actual text
|
||||
* of the node, if it is one of the nodes that gets
|
||||
* translated to text, like <br> to a newline).
|
||||
*/
|
||||
protected extendString(node: N | T, text: string) {
|
||||
this.snodes.push([node, text.length]);
|
||||
this.string += text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a #text node (add its text to the current string)
|
||||
*
|
||||
* @param {T} node The Text node to process
|
||||
* @param {boolean} ignore Whether we are currently ignoring content
|
||||
* @return {N | T} The next element to process
|
||||
*/
|
||||
protected handleText(node: T, ignore: boolean): N | T {
|
||||
if (!ignore) {
|
||||
this.extendString(node, this.adaptor.value(node));
|
||||
}
|
||||
return this.adaptor.next(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a BR, WBR, or #comment element (or others in the includeHtmlTags object).
|
||||
*
|
||||
* @param {N} node The node to process
|
||||
* @param {boolean} ignore Whether we are currently ignoring content
|
||||
* @return {N | T} The next element to process
|
||||
*/
|
||||
protected handleTag(node: N, ignore: boolean): N | T {
|
||||
if (!ignore) {
|
||||
let text = this.options['includeHtmlTags'][this.adaptor.kind(node)];
|
||||
this.extendString(node, text);
|
||||
}
|
||||
return this.adaptor.next(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an arbitrary DOM node:
|
||||
* Check the class to see if it matches the processHtmlClass regex
|
||||
* If the node has a child and is not marked as created by MathJax (data-MJX)
|
||||
* and either it is marked as restarting processing or is not a tag to be skipped, then
|
||||
* Save the next node (if there is one) and whether we are currently ignoring content
|
||||
* Move to the first child node
|
||||
* Update whether we are ignoring content
|
||||
* Otherwise
|
||||
* Move on to the next sibling
|
||||
* Return the next node to process and the ignore state
|
||||
*
|
||||
* @param {N} node The node to process
|
||||
* @param {boolean} ignore Whether we are currently ignoring content
|
||||
* @return {[N|T, boolean]} The next element to process and whether to ignore its content
|
||||
*/
|
||||
protected handleContainer(node: N, ignore: boolean): [N | T, boolean] {
|
||||
this.pushString();
|
||||
const cname = this.adaptor.getAttribute(node, 'class') || '';
|
||||
const tname = this.adaptor.kind(node) || '';
|
||||
const process = this.processHtmlClass.exec(cname);
|
||||
let next = node as N | T;
|
||||
if (this.adaptor.firstChild(node) && !this.adaptor.getAttribute(node, 'data-MJX') &&
|
||||
(process || !this.skipHtmlTags.exec(tname))) {
|
||||
if (this.adaptor.next(node)) {
|
||||
this.stack.push([this.adaptor.next(node), ignore]);
|
||||
}
|
||||
next = this.adaptor.firstChild(node);
|
||||
ignore = (ignore || this.ignoreHtmlClass.exec(cname)) && !process;
|
||||
} else {
|
||||
next = this.adaptor.next(node);
|
||||
}
|
||||
return [next, ignore];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an unknown node type (nodeType other than 1, 3, 8)
|
||||
*
|
||||
* @param {N} node The node to process
|
||||
* @param {boolean} ignore Whether we are currently ignoring content
|
||||
* @return {N|T} The next element to process
|
||||
*/
|
||||
protected handleOther(node: N, _ignore: boolean): N | T {
|
||||
this.pushString();
|
||||
return this.adaptor.next(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the strings for a given DOM element:
|
||||
* Initialize the state
|
||||
* Get the element where we stop processing
|
||||
* While we still have a node, and it is not the one where we are to stop:
|
||||
* If it is a text node, handle it and get the next node
|
||||
* Otherwise, if it is in the includeHtmlTags list, handle it and get the next node
|
||||
* Otherwise, handle it as a container and get the next node and ignore status
|
||||
* If there is no next node, and there are more nodes on the stack:
|
||||
* Save the current string, and pop the node and ignore status from the stack
|
||||
* Push the final string
|
||||
* Get the string array and array of associated DOM nodes
|
||||
* Clear the internal values (so the memory can be freed)
|
||||
* Return the strings and node lists
|
||||
*
|
||||
* @param {N} node The node to search
|
||||
* @return {[string[], HTMLNodeList[]]} The array of strings and their associated lists of nodes
|
||||
*/
|
||||
public find(node: N | T): [string[], HTMLNodeList<N, T>[]] {
|
||||
this.init();
|
||||
let stop = this.adaptor.next(node);
|
||||
let ignore = false;
|
||||
let include = this.options['includeHtmlTags'];
|
||||
|
||||
while (node && node !== stop) {
|
||||
const kind = this.adaptor.kind(node);
|
||||
if (kind === '#text') {
|
||||
node = this.handleText(node as T, ignore);
|
||||
} else if (include.hasOwnProperty(kind)) {
|
||||
node = this.handleTag(node as N, ignore);
|
||||
} else if (kind) {
|
||||
[node, ignore] = this.handleContainer(node as N, ignore);
|
||||
} else {
|
||||
node = this.handleOther(node as N, ignore);
|
||||
}
|
||||
if (!node && this.stack.length) {
|
||||
this.pushString();
|
||||
[node, ignore] = this.stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
this.pushString();
|
||||
let result = [this.strings, this.nodes] as [string[], HTMLNodeList<N, T>[]];
|
||||
this.init(); // free up memory
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 HTMLHandler class
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {AbstractHandler} from '../../core/Handler.js';
|
||||
import {MinHTMLAdaptor} from '../../adaptors/HTMLAdaptor.js';
|
||||
import {HTMLDocument} from './HTMLDocument.js';
|
||||
import {OptionList} from '../../util/Options.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the HTMLHandler class (extends AbstractHandler)
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export class HTMLHandler<N, T, D> extends AbstractHandler<N, T, D> {
|
||||
|
||||
/**
|
||||
* The DOMAdaptor for the document being handled
|
||||
*/
|
||||
public adaptor: MinHTMLAdaptor<N, T, D>; // declare a more specific adaptor type
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public documentClass = HTMLDocument;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public handlesDocument(document: any) {
|
||||
const adaptor = this.adaptor;
|
||||
if (typeof(document) === 'string') {
|
||||
try {
|
||||
document = adaptor.parse(document, 'text/html');
|
||||
} catch (err) {}
|
||||
}
|
||||
if (document instanceof adaptor.window.Document ||
|
||||
document instanceof adaptor.window.HTMLElement ||
|
||||
document instanceof adaptor.window.DocumentFragment) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the document isn't already a Document object, create one
|
||||
* using the given data
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public create(document: any, options: OptionList) {
|
||||
const adaptor = this.adaptor;
|
||||
if (typeof(document) === 'string') {
|
||||
document = adaptor.parse(document, 'text/html');
|
||||
} else if (document instanceof adaptor.window.HTMLElement ||
|
||||
document instanceof adaptor.window.DocumentFragment) {
|
||||
let child = document as N;
|
||||
document = adaptor.parse('', 'text/html');
|
||||
adaptor.append(adaptor.body(document), child);
|
||||
}
|
||||
return super.create(document, options) as HTMLDocument<N, T, D>;
|
||||
}
|
||||
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 HTMLMathItem class
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {AbstractMathItem, Location, STATE} from '../../core/MathItem.js';
|
||||
import {InputJax} from '../../core/InputJax.js';
|
||||
import {HTMLDocument} from './HTMLDocument.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the HTMLMathItem class (extends AbstractMathItem)
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export class HTMLMathItem<N, T, D> extends AbstractMathItem<N, T, D> {
|
||||
|
||||
/**
|
||||
* Easy access to DOM adaptor
|
||||
*/
|
||||
get adaptor() {
|
||||
return this.inputJax.adaptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
constructor(math: string, jax: InputJax<N, T, D>, display: boolean = true,
|
||||
start: Location<N, T> = {node: null, n: 0, delim: ''},
|
||||
end: Location<N, T> = {node: null, n: 0, delim: ''}) {
|
||||
super(math, jax, display, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the typeset MathItem into the document at the right location
|
||||
* If the starting and ending nodes are the same:
|
||||
* Split the text to isolate the math and its delimiters
|
||||
* Replace the math by the typeset version
|
||||
* Otherewise (spread over several nodes)
|
||||
* Split the start node, if needed
|
||||
* Remove nodes until we reach the end node
|
||||
* Insert the math before the end node
|
||||
* Split the end node, if needed
|
||||
* Remove the end node
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public updateDocument(_html: HTMLDocument<N, T, D>) {
|
||||
if (this.state() < STATE.INSERTED) {
|
||||
if (this.inputJax.processStrings) {
|
||||
let node = this.start.node as T;
|
||||
if (node === this.end.node) {
|
||||
if (this.end.n && this.end.n < this.adaptor.value(this.end.node).length) {
|
||||
this.adaptor.split(this.end.node, this.end.n);
|
||||
}
|
||||
if (this.start.n) {
|
||||
node = this.adaptor.split(this.start.node as T, this.start.n);
|
||||
}
|
||||
this.adaptor.replace(this.typesetRoot, node);
|
||||
} else {
|
||||
if (this.start.n) {
|
||||
node = this.adaptor.split(node, this.start.n);
|
||||
}
|
||||
while (node !== this.end.node) {
|
||||
let next = this.adaptor.next(node) as T;
|
||||
this.adaptor.remove(node);
|
||||
node = next;
|
||||
}
|
||||
this.adaptor.insert(this.typesetRoot, node);
|
||||
if (this.end.n < this.adaptor.value(node).length) {
|
||||
this.adaptor.split(node, this.end.n);
|
||||
}
|
||||
this.adaptor.remove(node);
|
||||
}
|
||||
} else {
|
||||
this.adaptor.replace(this.typesetRoot, this.start.node);
|
||||
}
|
||||
this.start.node = this.end.node = this.typesetRoot;
|
||||
this.start.n = this.end.n = 0;
|
||||
this.state(STATE.INSERTED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the style sheet for any changes due to rerendering
|
||||
*
|
||||
* @param {HTMLDocument} document The document whose styles are to be updated
|
||||
*/
|
||||
public updateStyleSheet(document: HTMLDocument<N, T, D>) {
|
||||
document.addStyleSheet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the typeset math from the document, and put back the original
|
||||
* expression and its delimiters, if requested.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public removeFromDocument(restore: boolean = false) {
|
||||
if (this.state() >= STATE.TYPESET) {
|
||||
const adaptor = this.adaptor;
|
||||
let node = this.start.node;
|
||||
let math: N | T = adaptor.text('');
|
||||
if (restore) {
|
||||
let text = this.start.delim + this.math + this.end.delim;
|
||||
if (this.inputJax.processStrings) {
|
||||
math = adaptor.text(text);
|
||||
} else {
|
||||
const doc = adaptor.parse(text, 'text/html');
|
||||
math = adaptor.firstChild(adaptor.body(doc));
|
||||
}
|
||||
}
|
||||
if (adaptor.parent(node)) {
|
||||
adaptor.replace(math, node);
|
||||
}
|
||||
this.start.node = this.end.node = math;
|
||||
this.start.n = this.end.n = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 HTMLMathList object
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {AbstractMathList} from '../../core/MathList.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implement the HTMLMathList class (extends AbstractMathList)
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export class HTMLMathList<N, T, D> extends AbstractMathList<N, T, D> {
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 AsciiMath InputJax object
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {AbstractInputJax} from '../core/InputJax.js';
|
||||
import {LegacyAsciiMath} from './asciimath/mathjax2/input/AsciiMath.js';
|
||||
import {separateOptions, OptionList} from '../util/Options.js';
|
||||
import {MathDocument} from '../core/MathDocument.js';
|
||||
import {MathItem} from '../core/MathItem.js';
|
||||
|
||||
import {FindAsciiMath} from './asciimath/FindAsciiMath.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the AsciiMath class (extends AbstractInputJax)
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export class AsciiMath<N, T, D> extends AbstractInputJax<N, T, D> {
|
||||
|
||||
/**
|
||||
* The name of the input jax
|
||||
*/
|
||||
public static NAME: string = 'AsciiMath';
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static OPTIONS: OptionList = {
|
||||
...AbstractInputJax.OPTIONS,
|
||||
FindAsciiMath: null
|
||||
};
|
||||
|
||||
/**
|
||||
* The FindMath object used to search for AsciiMath in the document
|
||||
*/
|
||||
protected findAsciiMath: FindAsciiMath<N, T, D>;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
constructor(options: OptionList) {
|
||||
let [ , find, am] = separateOptions(options, FindAsciiMath.OPTIONS, AsciiMath.OPTIONS);
|
||||
super(am);
|
||||
this.findAsciiMath = this.options['FindAsciiMath'] || new FindAsciiMath(find);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use legacy AsciiMath input jax for now
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public compile(math: MathItem<N, T, D>, _document: MathDocument<N, T, D>) {
|
||||
return LegacyAsciiMath.Compile(math.math, math.display);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public findMath(strings: string[]) {
|
||||
return this.findAsciiMath.findMath(strings);
|
||||
}
|
||||
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
/*************************************************************
|
||||
*
|
||||
* 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 AsciiMath version of the FindMath object
|
||||
*
|
||||
* @author dpvc@mathjax.org (Davide Cervone)
|
||||
*/
|
||||
|
||||
import {AbstractFindMath} from '../../core/FindMath.js';
|
||||
import {OptionList} from '../../util/Options.js';
|
||||
import {quotePattern} from '../../util/string.js';
|
||||
import {ProtoItem, protoItem} from '../../core/MathItem.js';
|
||||
|
||||
/**
|
||||
* Shorthand types for data about end delimiters and delimiter pairs
|
||||
*/
|
||||
export type EndItem = [string, boolean, RegExp];
|
||||
export type Delims = [string, string];
|
||||
|
||||
/*****************************************************************/
|
||||
/**
|
||||
* Implements the FindAsciiMath class (extends AbstractFindMath)
|
||||
*
|
||||
* Locates AsciiMath expressions within strings
|
||||
*
|
||||
* @template N The HTMLElement node class
|
||||
* @template T The Text node class
|
||||
* @template D The Document class
|
||||
*/
|
||||
export class FindAsciiMath<N, T, D> extends AbstractFindMath<N, T, D> {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public static OPTIONS: OptionList = {
|
||||
delimiters: [['`', '`']], // The start/end delimiter pairs for asciimath code
|
||||
};
|
||||
|
||||
/**
|
||||
* The regular expression for any starting delimiter
|
||||
*/
|
||||
protected start: RegExp;
|
||||
|
||||
/**
|
||||
* The end-delimiter data keyed to the opening delimiter string
|
||||
*/
|
||||
protected end: {[name: string]: EndItem};
|
||||
|
||||
/**
|
||||
* False if the configuration has no delimiters (so search can be skipped), true otherwise
|
||||
*/
|
||||
protected hasPatterns: boolean;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
constructor(options: OptionList) {
|
||||
super(options);
|
||||
this.getPatterns();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the patterns needed for searching the strings for AsciiMath
|
||||
* based on the configuration options
|
||||
*/
|
||||
protected getPatterns() {
|
||||
let options = this.options;
|
||||
let starts: string[] = [];
|
||||
this.end = {};
|
||||
options['delimiters'].forEach((delims: Delims) => this.addPattern(starts, delims, false));
|
||||
this.start = new RegExp(starts.join('|'), 'g');
|
||||
this.hasPatterns = (starts.length > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the needed patterns for a pair of delimiters
|
||||
*
|
||||
* @param {string[]} starts Array of starting delimiter strings
|
||||
* @param {Delims} delims Array of delimiter strings, as [start, end]
|
||||
* @param {boolean} display True if the delimiters are for display mode
|
||||
*/
|
||||
protected addPattern(starts: string[], delims: Delims, display: boolean) {
|
||||
let [open, close] = delims;
|
||||
starts.push(quotePattern(open));
|
||||
this.end[open] = [close, display, new RegExp(quotePattern(close), 'g')];
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the end delimiter given the start delimiter.
|
||||
*
|
||||
* @param {string} text The string being searched for the end delimiter
|
||||
* @param {number} n The index of the string being searched
|
||||
* @param {RegExpExecArray} start The result array from the start-delimiter search
|
||||
* @param {EndItem} end The end-delimiter data corresponding to the start delimiter
|
||||
* @return {ProtoItem} The proto math item for the math, if found
|
||||
*/
|
||||
protected findEnd(text: string, n: number, start: RegExpExecArray, end: EndItem): ProtoItem<N, T> {
|
||||
let [ , display, pattern] = end;
|
||||
let i = pattern.lastIndex = start.index + start[0].length;
|
||||
let match = pattern.exec(text);
|
||||
return (!match ? null : protoItem<N, T>(start[0], text.substr(i, match.index - i), match[0],
|
||||
n, start.index, match.index + match[0].length, display));
|
||||
}
|
||||
|
||||
/**
|
||||
* Search a string for math delimited by one of the delimiter pairs.
|
||||
*
|
||||
* @param {ProtoItem[]} math The array of proto math items located so far
|
||||
* @param {number} n The index of the string being searched
|
||||
* @param {string} text The string being searched
|
||||
*/
|
||||
protected findMathInString(math: ProtoItem<N, T>[], n: number, text: string) {
|
||||
let start, match;
|
||||
this.start.lastIndex = 0;
|
||||
while ((start = this.start.exec(text))) {
|
||||
match = this.findEnd(text, n, start, this.end[start[0]]);
|
||||
if (match) {
|
||||
math.push(match);
|
||||
this.start.lastIndex = match.end.n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for math in an array of strings and return an array of matches.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public findMath(strings: string[]) {
|
||||
let math: ProtoItem<N, T>[] = [];
|
||||
if (this.hasPatterns) {
|
||||
for (let i = 0, m = strings.length; i < m; i++) {
|
||||
this.findMathInString(math, i, strings[i]);
|
||||
}
|
||||
}
|
||||
return math;
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export var LegacyAsciiMath: any;
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
MathJax = Object.assign(global.MathJax || {}, require("../legacy/MathJax.js").MathJax);
|
||||
|
||||
//
|
||||
// Load component-based configuration, if any
|
||||
//
|
||||
if (MathJax.config && MathJax.config.asciimath) {
|
||||
MathJax.Hub.Config({AsciiMath: MathJax.config.asciimath});
|
||||
}
|
||||
|
||||
MathJax.Ajax.Preloading(
|
||||
"[MathJax]/jax/input/AsciiMath/config.js",
|
||||
"[MathJax]/jax/input/AsciiMath/jax.js",
|
||||
"[MathJax]/jax/element/mml/jax.js"
|
||||
);
|
||||
|
||||
require("../legacy/jax/element/mml/jax.js");
|
||||
require("../legacy/jax/input/AsciiMath/config.js");
|
||||
require("../legacy/jax/input/AsciiMath/jax.js");
|
||||
|
||||
require("../legacy/jax/element/MmlNode.js");
|
||||
|
||||
var MmlFactory = require("../../../../core/MmlTree/MmlFactory.js").MmlFactory;
|
||||
var factory = new MmlFactory();
|
||||
|
||||
exports.LegacyAsciiMath = {
|
||||
Compile: function (am,display) {
|
||||
var script = {
|
||||
type: "math/asciimath",
|
||||
innerText: am,
|
||||
MathJax: {}
|
||||
};
|
||||
var node = MathJax.InputJax.AsciiMath.Translate(script).root.toMmlNode(factory);
|
||||
node.setInheritedAttributes({}, display, 0, false);
|
||||
return node;
|
||||
},
|
||||
Translate: function (am,display) {
|
||||
return this.Compile(am,display);
|
||||
}
|
||||
};
|
||||
+1943
File diff suppressed because it is too large
Load Diff
+103
@@ -0,0 +1,103 @@
|
||||
(function () {
|
||||
var MML = MathJax.ElementJax.mml;
|
||||
|
||||
MML.mbase.Augment({
|
||||
toJSON: function () {
|
||||
var m = this.data.length;
|
||||
var json = {type: this.type};
|
||||
if (this.inferred) json.inferred = true;
|
||||
if (this.isToken) {
|
||||
json.text = this.data.join("");
|
||||
} else {
|
||||
json.children = new Array(m);
|
||||
for (var i = 0; i < m; i++) {
|
||||
var child = this.data[i];
|
||||
if (child) json.children[i] = child.toJSON();
|
||||
}
|
||||
}
|
||||
this.jsonAddAttributes(json);
|
||||
return json;
|
||||
},
|
||||
jsonAddAttributes: function (json) {
|
||||
var defaults = (this.type === "mstyle" ? MML.math.prototype.defaults : this.defaults);
|
||||
var names = (this.attrNames||MML.copyAttributeNames),
|
||||
skip = MML.skipAttributes,
|
||||
copy = MML.copyAttributes;
|
||||
var attr = {};
|
||||
|
||||
if (!this.attrNames) {
|
||||
for (var id in defaults) {
|
||||
if (!skip[id] && !copy[id] && defaults.hasOwnProperty(id)) {
|
||||
if (this[id] != null && this[id] !== defaults[id]) {
|
||||
if (this.Get(id,null,1) !== this[id]) attr[id] = this[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var i = 0, m = names.length; i < m; i++) {
|
||||
if (copy[names[i]] === 1 && !defaults.hasOwnProperty(names[i])) continue;
|
||||
value = (this.attr||{})[names[i]];
|
||||
if (value == null) value = this[names[i]];
|
||||
if (value != null) attr[names[i]] = value;
|
||||
}
|
||||
json.attributes = attr;
|
||||
}
|
||||
});
|
||||
|
||||
MML.chars.Augment({
|
||||
toJSON: function () {
|
||||
return this.data.join("");
|
||||
}
|
||||
});
|
||||
MML.entity.Augment({
|
||||
toJSON: function () {
|
||||
return this.data.join("");
|
||||
}
|
||||
});
|
||||
|
||||
MML.msubsup.Augment({
|
||||
toJSON: function () {
|
||||
var json = this.SUPER(arguments).toJSON.call(this);
|
||||
if (this.data[this.sub] == null) {
|
||||
json.type = "msup";
|
||||
json.children.splice(1,1);
|
||||
}
|
||||
if (this.data[this.sup] == null) {
|
||||
json.type = "msub";
|
||||
json.children.splice(2,1);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
});
|
||||
|
||||
MML.munderover.Augment({
|
||||
toJSON: function () {
|
||||
var json = this.SUPER(arguments).toJSON.call(this);
|
||||
if (this.data[this.munder] == null) {
|
||||
json.type = "mover";
|
||||
json.children.splice(1,1);
|
||||
}
|
||||
if (this.data[this.mover] == null) {
|
||||
json.type = "munder";
|
||||
json.children.splice(2,1);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
});
|
||||
|
||||
MML.TeXAtom.Augment({
|
||||
toJSON: function () {
|
||||
var json = this.SUPER(arguments).toJSON.call(this);
|
||||
json.type = "mrow";
|
||||
json.TeXAtom = MML.TEXCLASSNAMES[this.Get("texClass")];
|
||||
return json;
|
||||
}
|
||||
});
|
||||
|
||||
MML.xml.Augment({
|
||||
toJSON: function () {
|
||||
return {type:"xml", data: this.toString()};
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user