/************************************************************* * * Copyright (c) 2017-2025 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. */ /** * @file Implements the TeX InputJax object * * @author dpvc@mathjax.org (Davide Cervone) */ import { AbstractInputJax } from '../core/InputJax.js'; import { userOptions, separateOptions, OptionList } from '../util/Options.js'; import { MathDocument } from '../core/MathDocument.js'; import { MathItem } from '../core/MathItem.js'; import { MmlNode } from '../core/MmlTree/MmlNode.js'; import { MmlFactory } from '../core/MmlTree/MmlFactory.js'; import { FindTeX } from './tex/FindTeX.js'; import FilterUtil from './tex/FilterUtil.js'; import NodeUtil from './tex/NodeUtil.js'; import TexParser from './tex/TexParser.js'; import TexError from './tex/TexError.js'; import ParseOptions from './tex/ParseOptions.js'; import { TagsFactory } from './tex/Tags.js'; import { ParserConfiguration } from './tex/Configuration.js'; import { TexConstant } from './tex/TexConstants.js'; // Import base as it is the default package loaded. import './tex/base/BaseConfiguration.js'; /*****************************************************************/ /* * Implements the TeX class (extends AbstractInputJax) */ /** * @template N The HTMLElement node class * @template T The Text node class * @template D The Document class */ export class TeX extends AbstractInputJax { /** * Name of input jax. * * @type {string} */ public static NAME: string = 'TeX'; /** * Default options for the jax. * * @type {OptionList} */ public static OPTIONS: OptionList = { ...AbstractInputJax.OPTIONS, FindTeX: null, packages: ['base'], // Maximum size of TeX string to process. maxBuffer: 5 * 1024, // Maximum number of array template substitutions (avoids infinite loop from @{\\} for example) maxTemplateSubtitutions: 10000, // math-style to use for Latin and Greek letters mathStyle: 'TeX', // one of TeX, ISO, French, or upright formatError: (jax: TeX, err: TexError) => jax.formatError(err), }; /** * The FindTeX instance used for locating TeX in strings */ protected findTeX: FindTeX; /** * The configuration of the TeX jax. * * @type {ParserConfiguration} */ protected configuration: ParserConfiguration; /** * The LaTeX code that is parsed. * * @type {string} */ protected latex: string; /** * The Math node that results from parsing. * * @type {MmlNode} */ protected mathNode: MmlNode; private _parseOptions: ParseOptions; /** * Initialises the configurations. * * @param {string[]} packages Names of packages. * @returns {ParserConfiguration} The configuration object. */ protected static configure( packages: (string | [string, number])[] ): ParserConfiguration { const configuration = new ParserConfiguration(packages, ['tex']); configuration.init(); return configuration; } /** * Initialises the Tags factory. Add tagging structures from packages and set * tagging to given default. * * @param {ParseOptions} options The parse options. * @param {ParserConfiguration} configuration The configuration. */ protected static tags( options: ParseOptions, configuration: ParserConfiguration ) { TagsFactory.addTags(configuration.tags); TagsFactory.setDefault(options.options.tags); options.tags = TagsFactory.getDefault(); options.tags.configuration = options; } /** * @override */ constructor(options: OptionList = {}) { const [rest, tex, find] = separateOptions( options, TeX.OPTIONS, FindTeX.OPTIONS ); super(tex); this.findTeX = this.options['FindTeX'] || new FindTeX(find); const packages = this.options.packages; const configuration = (this.configuration = TeX.configure(packages)); const parseOptions = (this._parseOptions = new ParseOptions(configuration, [ this.options, TagsFactory.OPTIONS, ])); userOptions(parseOptions.options, rest); configuration.config(this); TeX.tags(parseOptions, configuration); this.postFilters.addList([ [FilterUtil.cleanSubSup, -7], [FilterUtil.setInherited, -6], [FilterUtil.checkScriptlevel, -5], [FilterUtil.moveLimits, -4], [FilterUtil.cleanStretchy, -3], [FilterUtil.cleanAttributes, -2], [FilterUtil.combineRelations, -1], ]); } /** * @override */ public setMmlFactory(mmlFactory: MmlFactory) { super.setMmlFactory(mmlFactory); this._parseOptions.nodeFactory.setMmlFactory(mmlFactory); } /** * @returns {ParseOptions} The parse options that configure this JaX instance. */ public get parseOptions(): ParseOptions { return this._parseOptions; } /** * @override */ public reset(tag: number = 0) { this.parseOptions.tags.reset(tag); } /** * @override */ public compile( math: MathItem, document: MathDocument ): MmlNode { this.parseOptions.clear(); this.parseOptions.mathItem = math; this.executeFilters(this.preFilters, math, document, this.parseOptions); this.latex = math.math; let node: MmlNode; this.parseOptions.tags.startEquation(math); let parser; try { parser = new TexParser( this.latex, { display: math.display, isInner: false }, this.parseOptions ); node = parser.mml(); } catch (err) { if (!(err instanceof TexError)) { throw err; } this.parseOptions.error = true; node = this.options.formatError(this, err); } node = this.parseOptions.nodeFactory.create('node', 'math', [node]); node.attributes.set(TexConstant.Attr.LATEX, this.latex); if (math.display) { NodeUtil.setAttribute(node, 'display', 'block'); } this.parseOptions.tags.finishEquation(math); this.parseOptions.root = node; this.executeFilters(this.postFilters, math, document, this.parseOptions); // // Add these here to not lose overflow during filtering attributes in postFilters // if (parser && parser.stack.env.hsize) { NodeUtil.setAttribute(node, 'maxwidth', parser.stack.env.hsize); NodeUtil.setAttribute(node, 'overflow', 'linebreak'); } this.mathNode = this.parseOptions.root; return this.mathNode; } /** * @override */ public findMath(strings: string[]) { return this.findTeX.findMath(strings); } /** * Default formatter for error messages: * wrap an error into a node for output. * * @param {TexError} err The TexError. * @returns {Node} The merror node. */ public formatError(err: TexError): MmlNode { const message = err.message.replace(/\n.*/, ''); return this.parseOptions.nodeFactory.create( 'error', message, err.id, this.latex ); } }