/************************************************************* * Copyright (c) 2020-2025 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 Macro and environment implementations for the mathtools package. * * @author v.sorge@mathjax.org (Volker Sorge) * @author dpvc@mathjax.org (Davide P. Cervone) */ import { ArrayItem, EqnArrayItem } from '../base/BaseItems.js'; import { StackItem } from '../StackItem.js'; import { ParseUtil } from '../ParseUtil.js'; import { UnitUtil } from '../UnitUtil.js'; import { ParseMethod, ParseResult } from '../Types.js'; import { AmsMethods } from '../ams/AmsMethods.js'; import BaseMethods from '../base/BaseMethods.js'; import TexParser from '../TexParser.js'; import TexError from '../TexError.js'; import NodeUtil from '../NodeUtil.js'; import { TEXCLASS } from '../../../core/MmlTree/MmlNode.js'; import { length2em, em } from '../../../util/lengths.js'; import { lookup } from '../../../util/Options.js'; import { HandlerType } from '../HandlerTypes.js'; import { Macro } from '../Token.js'; import { CommandMap } from '../TokenMap.js'; import { NewcommandUtil, NewcommandTables, } from '../newcommand/NewcommandUtil.js'; import NewcommandMethods from '../newcommand/NewcommandMethods.js'; import { PrioritizedList } from '../../../util/PrioritizedList.js'; import { MathtoolsTags } from './MathtoolsTags.js'; import { MathtoolsUtil } from './MathtoolsUtil.js'; export const LEGACYCONFIG = { [HandlerType.MACRO]: ['mathtools-legacycolonsymbols'], }; export const LEGACYPRIORITY = PrioritizedList.DEFAULTPRIORITY - 1; /** * The implementations for the macros and environments for the mathtools package. */ export const MathtoolsMethods: { [key: string]: ParseMethod } = { /** * Handle a mathtools matrix environment, with optional alignment. * * @param {TexParser} parser The active tex parser. * @param {StackItem} begin The BeginItem for the environment. * @param {string} open The open delimiter for the matrix. * @param {string} close The close delimiter for the matrix. * @returns {ParseResult} The ArrayItem for the matrix. */ MtMatrix( parser: TexParser, begin: StackItem, open: string, close: string ): ParseResult { const align = parser.GetBrackets(`\\begin{${begin.getName()}}`, 'c'); return MathtoolsMethods.Array(parser, begin, open, close, align); }, /** * Create a smallmatrix with given delimiters, and with optional alignment (and settable default) * * @param {TexParser} parser The active tex parser. * @param {StackItem} begin The BeginItem for the environment. * @param {string} open The open delimiter for the matrix. * @param {string} close The close delimiter for the matrix. * @param {string} align The (optional) alignment. If not given, use a bracket argument for it. * @returns {ParseResult} The ArrayItem for the matrix. */ MtSmallMatrix( parser: TexParser, begin: StackItem, open: string, close: string, align?: string ): ParseResult { if (!align) { align = parser.GetBrackets( `\\begin{${begin.getName()}}`, parser.options.mathtools['smallmatrix-align'] ); } return MathtoolsMethods.Array( parser, begin, open, close, align, UnitUtil.em(1 / 3), '.2em', 'S', 1 ); }, /** * Create the multlined StackItem. * * @param {TexParser} parser The active tex parser. * @param {StackItem} begin The BeginItem for the environment. * @returns {ParseResult} The MultlinedItem. */ MtMultlined(parser: TexParser, begin: StackItem): ParseResult { const name = `\\begin{${begin.getName()}}`; let pos = parser.options.mathtools['multlined-pos'] || 'c'; let width = parser.options.mathtools['multlined-width'] || ''; // // Only process bracket arguments if they follow immediately with no space. // Note that if [pos] is missing, [width] can still be provided. // if (!parser.nextIsSpace()) { const arg = parser.GetBrackets(name, pos); if (arg.match(/^[ctb]$/)) { pos = arg; width = !parser.nextIsSpace() ? parser.GetBrackets(name, '') : ''; } else { width = arg; } if (width && !UnitUtil.matchDimen(width)[0]) { throw new TexError( 'BadWidth', 'Width for %1 must be a dimension', name ); } } parser.Push(begin); const item = parser.itemFactory.create( 'multlined', parser, begin ) as ArrayItem; item.arraydef = { displaystyle: true, rowspacing: '.5em', width: width || 'auto', columnwidth: '100%', }; return ParseUtil.setArrayAlign(item as ArrayItem, pos); }, /** * Replacement for the AMS HandleShove that includes optional spacing values * * @param {TexParser} parser The active tex parser. * @param {string} name The name of the calling macro. * @param {string} shove Which way to shove the result. */ HandleShove(parser: TexParser, name: string, shove: string) { const top = parser.stack.Top(); if (top.kind !== 'multline' && top.kind !== 'multlined') { throw new TexError( 'CommandInMultlined', '%1 can only appear within the multline or multlined environments', name ); } if (top.Size()) { throw new TexError( 'CommandAtTheBeginingOfLine', '%1 must come at the beginning of the line', name ); } top.setProperty('shove', shove); const shift = parser.GetBrackets(name); let mml = parser.ParseArg(name); if (shift) { const mrow = parser.create('node', 'mrow', []); const mspace = parser.create('node', 'mspace', [], { width: shift }); if (shove === 'left') { mrow.appendChild(mspace); mrow.appendChild(mml); } else { mrow.appendChild(mml); mrow.appendChild(mspace); } mml = mrow; } parser.Push(mml); }, /** * Handle the spreadlines environment. * * @param {TexParser} parser The active tex parser. * @param {StackItem} begin The BeginItem for the environment. */ SpreadLines(parser: TexParser, begin: StackItem) { if (parser.stack.env.closing === begin.getName()) { // // When the environment ends, look through the contents and // adjust the spacing in any tables, then push the results. // delete parser.stack.env.closing; const top = parser.stack.Pop(); const mml = top.toMml(); const spread = top.getProperty('spread') as string; if (mml.isInferred) { for (const child of NodeUtil.getChildren(mml)) { MathtoolsUtil.spreadLines(child, spread); } } else { MathtoolsUtil.spreadLines(mml, spread); } parser.Push(mml); } else { // // Read the spread dimension and save it, then begin the environment. // const spread = parser.GetDimen(`\\begin{${begin.getName()}}`); begin.setProperty('spread', spread); begin.setProperty('nestStart', true); ParseUtil.checkEqnEnv(parser); parser.Push(begin); } }, /** * Implements the various cases environments. * * @param {TexParser} parser The calling parser. * @param {StackItem} begin The BeginItem for the environment. * @param {string} open The open delimiter for the matrix. * @param {string} close The close delimiter for the matrix. * @param {string} style The style (D, T, S, SS) for the contents of the array * @returns {ArrayItem} The ArrayItem for the environment */ Cases( parser: TexParser, begin: StackItem, open: string, close: string, style: string ): ArrayItem { const array = parser.itemFactory .create('array') .setProperty('casesEnv', begin.getName()) as ArrayItem; array.arraydef = { rowspacing: '.2em', columnspacing: '1em', columnalign: 'left', }; if (style === 'D') { array.arraydef.displaystyle = true; } array.setProperties({ open, close }); parser.Push(begin); return array; }, /** * Handle \mathrlap, \mathllap, \mathclap, and their cramped versions. * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. * @param {string} pos The position (l, c, r) of the lapped content * @param {boolean} cramped True if the style should be cramped */ MathLap(parser: TexParser, name: string, pos: string, cramped: boolean) { const style = parser.GetBrackets(name, '').trim(); const mml = parser.create( 'node', 'mstyle', [ parser.create('node', 'mpadded', [parser.ParseArg(name)], { width: 0, ...(pos === 'r' ? {} : { lspace: pos === 'l' ? '-1width' : '-.5width' }), }), ], { 'data-cramped': cramped } ); MathtoolsUtil.setDisplayLevel(mml, style); parser.Push(parser.create('node', 'TeXAtom', [mml])); }, /** * Implements \cramped. * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. */ Cramped(parser: TexParser, name: string) { const style = parser.GetBrackets(name, '').trim(); const arg = parser.ParseArg(name); const mml = parser.create('node', 'mstyle', [arg], { 'data-cramped': true, }); MathtoolsUtil.setDisplayLevel(mml, style); parser.Push(mml); }, /** * Implements \clap (and could do \llap and \rlap, where the contents are text mode). * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. * @param {string} pos The position (l, c, r) of the lapped content */ MtLap(parser: TexParser, name: string, pos: string) { const content = ParseUtil.internalMath(parser, parser.GetArgument(name), 0); const mml = parser.create('node', 'mpadded', content, { width: 0 }); if (pos !== 'r') { NodeUtil.setAttribute( mml, 'lspace', pos === 'l' ? '-1width' : '-.5width' ); } parser.Push(mml); }, /** * Implements \mathmakebox. * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. */ MathMakeBox(parser: TexParser, name: string) { const width = parser.GetBrackets(name); const pos = parser.GetBrackets(name, 'c'); const mml = parser.create('node', 'mpadded', [parser.ParseArg(name)]); if (width) { NodeUtil.setAttribute(mml, 'width', width); } const align = lookup(pos.toLowerCase(), { c: 'center', r: 'right' }, ''); if (align) { NodeUtil.setAttribute(mml, 'data-align', align); } if (pos.toLowerCase() !== pos) { NodeUtil.setAttribute(mml, 'data-overflow', 'linebreak'); } parser.Push(mml); }, /** * Implements \mathmbox. * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. */ MathMBox(parser: TexParser, name: string) { parser.Push(parser.create('node', 'mrow', [parser.ParseArg(name)])); }, /** * Implements \underbacket and \overbracket. * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. */ UnderOverBracket(parser: TexParser, name: string) { const thickness = length2em(parser.GetBrackets(name, '.1em'), 0.1); const height = parser.GetBrackets(name, '.2em'); const arg = parser.GetArgument(name); const [pos, accent, border] = name.charAt(1) === 'o' ? ['over', 'accent', 'bottom'] : ['under', 'accentunder', 'top']; const t = em(thickness); const base = new TexParser( arg, parser.stack.env, parser.configuration ).mml(); const copy = new TexParser( arg, parser.stack.env, parser.configuration ).mml(); const script = parser.create( 'node', 'mpadded', [parser.create('node', 'mphantom', [copy])], { style: `border: ${t} solid; border-${border}: none`, height: height, depth: 0, } ); const node = ParseUtil.underOver(parser, base, script, pos, true); const munderover = NodeUtil.getChildAt(NodeUtil.getChildAt(node, 0), 0); // TeXAtom.inferredMrow child 0 NodeUtil.setAttribute(munderover, accent, true); parser.Push(node); }, /** * Implements \Aboxed. * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. * @param {string} box The box command to use * @param {boolean} math True when the box command uses math mode */ Aboxed( parser: TexParser, name: string, box: string = 'boxed', math: boolean = true ) { // // Check that the top item is an alignment, and that we are on an even number of cells // (othewise add one to make it even). // const top = MathtoolsUtil.checkAlignment(parser, name); if (top.row.length % 2 === 1) { top.row.push(parser.create('node', 'mtd', [])); } // // Get the argument and the rest of the TeX string. // const arg = parser.GetArgument(name); const rest = parser.string.substring(parser.i); // // Put the argument back, followed by "&&", and a marker that we look for below. // parser.string = arg + '&&\\endAboxed'; parser.i = 0; // // Get the two parts separated by ampersands, and ignore the rest. // const left = parser.GetUpTo(name, '&'); const right = parser.GetUpTo(name, '&'); parser.GetUpTo(name, '\\endAboxed'); // // Insert the TeX needed for the boxed content // const [bmath, emath] = math ? ['', ''] : ['$\\displaystyle{', '}$']; const tex = ParseUtil.substituteArgs( parser, [left, right], `\\rlap{\\${box}{${bmath}#1{}#2${emath}}}` + '\\kern.267em\\phantom{#1}&\\phantom{{}#2}\\kern.267em' ); parser.string = tex + rest; parser.i = 0; }, /** * Implements \MakeAboxedCommand. * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. */ MakeAboxedCommand(parser: TexParser, name: string) { const star = parser.GetStar(); const cs = NewcommandUtil.GetCSname(parser, name); const box = NewcommandUtil.GetCSname(parser, name + '\\' + cs); const handlers = parser.configuration.handlers; if (handlers.get(HandlerType.MACRO).lookup(cs)) { throw new TexError('AlreadyDefined', '%1 is already defined', '\\' + cs); } const handler = handlers.retrieve( NewcommandTables.NEW_COMMAND ) as CommandMap; handler.add(cs, new Macro(cs, MathtoolsMethods.Aboxed, [box, star])); parser.Push(parser.itemFactory.create('null')); }, /** * Implements \ArrowBetweenLines. * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. */ ArrowBetweenLines(parser: TexParser, name: string) { const top = MathtoolsUtil.checkAlignment(parser, name); if (top.Size() || top.row.length) { throw new TexError('BetweenLines', '%1 must be on a row by itself', name); } const star = parser.GetStar(); const symbol = parser.GetBrackets(name, '\\Updownarrow'); if (star) { top.EndEntry(); top.EndEntry(); } const tex = star ? '\\quad' + symbol : symbol + '\\quad'; const mml = new TexParser( tex, parser.stack.env, parser.configuration ).mml(); parser.Push(mml); top.EndEntry(); top.EndRow(); }, /** * Implements \vdotswithin. * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. */ VDotsWithin(parser: TexParser, name: string) { const arg = '\\mmlToken{mi}{}' + parser.GetArgument(name) + '\\mmlToken{mi}{}'; const base = new TexParser( arg, parser.stack.env, parser.configuration ).mml(); const mml = parser.create( 'node', 'mpadded', [ parser.create( 'node', 'mpadded', [parser.create('node', 'mo', [parser.create('text', '\u22EE')])], { width: 0, lspace: '-.5width' } ), parser.create('node', 'mphantom', [base]), ], { lspace: '.5width', } ); parser.Push(mml); }, /** * Implements \shortvdotswithin. * * @param {TexParser} parser The calling parser. * @param {string} _name The macro name. */ ShortVDotsWithin(parser: TexParser, _name: string) { const top = parser.stack.Top() as EqnArrayItem; const star = parser.GetStar(); if (top.EndEntry) { MathtoolsMethods.FlushSpaceAbove(parser, '\\MTFlushSpaceAbove'); if (!star) { top.EndEntry(); } } MathtoolsMethods.VDotsWithin(parser, '\\vdotswithin'); if (top.EndEntry) { if (star) { top.EndEntry(); } MathtoolsMethods.FlushSpaceBelow(parser, '\\MTFlushSpaceBelow'); } }, /** * Implements \MTFlushSpaceAbove. * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. */ FlushSpaceAbove(parser: TexParser, name: string) { const top = MathtoolsUtil.checkAlignment(parser, name); if (top.table) { top.setProperty('flushspaceabove', top.table.length); // marker so \vdotswithin can shorten its height top.addRowSpacing( '-' + parser.options.mathtools['shortvdotsadjustabove'] ); } }, /** * Implements \MTFlushSpaceBelow. * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. */ FlushSpaceBelow(parser: TexParser, name: string) { const top = MathtoolsUtil.checkAlignment(parser, name); if (top.table) { if (top.Size()) { top.EndEntry(); } top.EndRow(); top.addRowSpacing( '-' + parser.options.mathtools['shortvdotsadjustbelow'] ); } }, /** * Implements a paired delimiter (e.g., from \DeclarePairedDelimiter). * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. * @param {string} open The open delimiter. * @param {string} close The close delimiter. * @param {string?} body The body betweeen the delimiters. * @param {number?} n The number of arguments to use for the body. * @param {string?} pre The TeX to go before the open delimiter. * @param {string?} post The TeX to go after the close delimiter. */ PairedDelimiters( parser: TexParser, name: string, open: string, close: string, body: string = '#1', n: number = 1, pre: string = '', post: string = '' ) { const star = parser.GetStar(); const size = star ? '' : parser.GetBrackets(name); const [left, right, after] = star ? ['\\mathopen{\\left', '\\right', '}\\mathclose{}'] : size ? [size + 'l', size + 'r', ''] : ['', '', '']; const delim = star ? '\\middle' : size || ''; if (n) { const args: string[] = []; for (let i = args.length; i < n; i++) { args.push(parser.GetArgument(name)); } pre = ParseUtil.substituteArgs(parser, args, pre); body = ParseUtil.substituteArgs(parser, args, body); post = ParseUtil.substituteArgs(parser, args, post); } body = body.replace(/\\delimsize/g, delim); parser.string = [ pre, left, open, body, right, close, after, post, parser.string.substring(parser.i), ].reduce((s, part) => ParseUtil.addArgs(parser, s, part), ''); parser.i = 0; ParseUtil.checkMaxMacros(parser); }, /** * Implements \DeclarePairedDelimiter. * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. */ DeclarePairedDelimiter(parser: TexParser, name: string) { const cs = NewcommandUtil.GetCsNameArgument(parser, name); const open = parser.GetArgument(name); const close = parser.GetArgument(name); MathtoolsUtil.addPairedDelims(parser, cs, [open, close]); parser.Push(parser.itemFactory.create('null')); }, /** * Implements \DeclarePairedDelimiterX. * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. */ DeclarePairedDelimiterX(parser: TexParser, name: string) { const cs = NewcommandUtil.GetCsNameArgument(parser, name); const n = NewcommandUtil.GetArgCount(parser, name); const open = parser.GetArgument(name); const close = parser.GetArgument(name); const body = parser.GetArgument(name); MathtoolsUtil.addPairedDelims(parser, cs, [open, close, body, n]); parser.Push(parser.itemFactory.create('null')); }, /** * Implements \DeclarePairedDelimiterXPP. * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. */ DeclarePairedDelimiterXPP(parser: TexParser, name: string) { const cs = NewcommandUtil.GetCsNameArgument(parser, name); const n = NewcommandUtil.GetArgCount(parser, name); const pre = parser.GetArgument(name); const open = parser.GetArgument(name); const close = parser.GetArgument(name); const post = parser.GetArgument(name); const body = parser.GetArgument(name); MathtoolsUtil.addPairedDelims(parser, cs, [ open, close, body, n, pre, post, ]); parser.Push(parser.itemFactory.create('null')); }, /** * Implements \centercolon, \ordinarycolon, \MTThinColon. * * @param {TexParser} parser The calling parser. * @param {string} _name The macro name. * @param {boolean} center True if colon should be centered * @param {boolean} force True menas always center (don't use centercolon option). * @param {boolean} thin True if this is a thin color (for \coloneqq, etc). */ CenterColon( parser: TexParser, _name: string, center: boolean, force: boolean = false, thin: boolean = false ) { const options = parser.options.mathtools; let mml = parser.create('token', 'mo', {}, ':'); if (center && (options['centercolon'] || force)) { const dy = options['centercolon-offset']; mml = parser.create('node', 'mpadded', [mml], { voffset: dy, height: `+${dy}`, depth: `-${dy}`, ...(thin ? { width: options['thincolon-dw'], lspace: options['thincolon-dx'] } : {}), }); } parser.Push(mml); }, /** * Implements \coloneqq and related macros. * * @param {TexParser} parser The calling parser. * @param {string} _name The macro name. * @param {string} tex The tex string to use (if not using unicode versions or if there isn't one). * @param {string} unicode The unicode character (if there is one). */ Relation(parser: TexParser, _name: string, tex: string, unicode?: string) { const options = parser.options.mathtools; if (options['use-unicode'] && unicode) { parser.Push( parser.create('token', 'mo', { texClass: TEXCLASS.REL }, unicode) ); } else { tex = '\\mathrel{' + tex.replace(/:/g, '\\MTThinColon').replace(/-/g, '\\mathrel{-}') + '}'; parser.string = ParseUtil.addArgs( parser, tex, parser.string.substring(parser.i) ); parser.i = 0; } }, /** * Implements \ndownarrow and \nuparrow via a terrible hack (visual only, no chance of this working with SRE). * * @param {TexParser} parser The calling parser. * @param {string} _name The macro name. * @param {string} c The base arrow for the slashed version * @param {string} dy A vertical offset for the slash */ NArrow(parser: TexParser, _name: string, c: string, dy: string) { parser.Push( parser.create( 'node', 'TeXAtom', [ parser.create('token', 'mtext', {}, c), parser.create( 'node', 'mpadded', [ parser.create( 'node', 'mpadded', [ parser.create( 'node', 'menclose', [ parser.create('node', 'mspace', [], { height: '.2em', depth: 0, width: '.4em', }), ], { notation: 'updiagonalstrike', 'data-thickness': '.05em', 'data-padding': 0, } ), ], { width: 0, lspace: '-.5width', voffset: dy } ), parser.create('node', 'mphantom', [ parser.create('token', 'mtext', {}, c), ]), ], { width: 0, lspace: '-.5width' } ), ], { texClass: TEXCLASS.REL } ) ); }, /** * Implements \splitfrac and \splitdfrac. * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. * @param {boolean} display True if \splitdfrac. */ SplitFrac(parser: TexParser, name: string, display: boolean) { const num = parser.ParseArg(name); const den = parser.ParseArg(name); parser.Push( parser.create( 'node', 'mstyle', [ parser.create( 'node', 'mfrac', [ parser.create( 'node', 'mstyle', [ num, parser.create('token', 'mi'), parser.create('token', 'mspace', { width: '1em' }), // no parameter for this in mathtools. Should we add one? ], { scriptlevel: 0 } ), parser.create( 'node', 'mstyle', [ parser.create('token', 'mspace', { width: '1em' }), parser.create('token', 'mi'), den, ], { scriptlevel: 0 } ), ], { linethickness: 0, numalign: 'left', denomalign: 'right' } ), ], { displaystyle: display, scriptlevel: 0 } ) ); }, /** * Implements \xmathstrut. * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. */ XMathStrut(parser: TexParser, name: string) { let dd = parser.GetBrackets(name); let dh = parser.GetArgument(name); dh = MathtoolsUtil.plusOrMinus(name, dh); dd = MathtoolsUtil.plusOrMinus(name, dd || dh); parser.Push( parser.create( 'node', 'TeXAtom', [ parser.create( 'node', 'mpadded', [ parser.create('node', 'mphantom', [ parser.create('token', 'mo', { stretchy: false }, '('), ]), ], { width: 0, height: dh + 'height', depth: dd + 'depth' } ), ], { texClass: TEXCLASS.ORD } ) ); }, /** * Implements \prescript. * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. */ Prescript(parser: TexParser, name: string) { const sup = MathtoolsUtil.getScript(parser, name, 'sup'); const sub = MathtoolsUtil.getScript(parser, name, 'sub'); const base = MathtoolsUtil.getScript(parser, name, 'arg'); if (NodeUtil.isType(sup, 'none') && NodeUtil.isType(sub, 'none')) { parser.Push(base); return; } const mml = parser.create('node', 'mmultiscripts', [base]); NodeUtil.getChildren(mml).push(null, null); NodeUtil.appendChildren(mml, [ parser.create('node', 'mprescripts'), sub, sup, ]); mml.setProperty('fixPrescript', true); parser.Push(mml); }, /** * Implements \newtagform and \renewtagform. * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. * @param {boolean=} renew True if \renewtagform. */ NewTagForm(parser: TexParser, name: string, renew: boolean = false) { const tags = parser.tags as MathtoolsTags; if (!('mtFormats' in tags)) { throw new TexError( 'TagsNotMT', '%1 can only be used with ams or mathtools tags', name ); } const id = parser.GetArgument(name).trim(); if (!id) { throw new TexError('InvalidTagFormID', "Tag form name can't be empty"); } const format = parser.GetBrackets(name, ''); const left = parser.GetArgument(name); const right = parser.GetArgument(name); if (!renew && tags.mtFormats.has(id)) { throw new TexError('DuplicateTagForm', 'Duplicate tag form: %1', id); } tags.mtFormats.set(id, [left, right, format]); parser.Push(parser.itemFactory.create('null')); }, /** * Implements \usetagform. * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. */ UseTagForm(parser: TexParser, name: string) { const tags = parser.tags as MathtoolsTags; if (!('mtFormats' in tags)) { throw new TexError( 'TagsNotMT', '%1 can only be used with ams or mathtools tags', name ); } const id = parser.GetArgument(name).trim(); if (!id) { tags.mtCurrent = null; parser.Push(parser.itemFactory.create('null')); return; } if (!tags.mtFormats.has(id)) { throw new TexError('UndefinedTagForm', 'Undefined tag form: %1', id); } tags.mtCurrent = tags.mtFormats.get(id); parser.Push(parser.itemFactory.create('null')); }, /** * Implements \mathtoolsset. * * @param {TexParser} parser The calling parser. * @param {string} name The macro name. */ SetOptions(parser: TexParser, name: string) { const options = parser.options.mathtools; if (!options['allow-mathtoolsset']) { throw new TexError('ForbiddenMathtoolsSet', '%1 is disabled', name); } const allowed = {} as { [id: string]: number }; Object.keys(options).forEach((id) => { if ( id !== 'pariedDelimiters' && id !== 'tagforms' && id !== 'allow-mathtoolsset' ) { allowed[id] = 1; } }); const args = parser.GetArgument(name); const keys = ParseUtil.keyvalOptions(args, allowed, true); for (const id of Object.keys(keys)) { if (id === 'legacycolonsymbols' && options[id] !== keys[id]) { if (options[id]) { parser.configuration.handlers.remove(LEGACYCONFIG, {}); } else { parser.configuration.handlers.add(LEGACYCONFIG, {}, LEGACYPRIORITY); } } options[id] = keys[id]; } parser.Push(parser.itemFactory.create('null')); }, /** * Use the Base or AMS methods for these */ Array: BaseMethods.Array, Macro: BaseMethods.Macro, xArrow: AmsMethods.xArrow, HandleRef: AmsMethods.HandleRef, AmsEqnArray: AmsMethods.AmsEqnArray, MacroWithTemplate: NewcommandMethods.MacroWithTemplate, };