MathtoolsMethods.ts 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  1. /*************************************************************
  2. * Copyright (c) 2020-2022 MathJax Consortium
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. /**
  17. * @fileoverview Macro and environment implementations for the mathtools package.
  18. *
  19. * @author v.sorge@mathjax.org (Volker Sorge)
  20. * @author dpvc@mathjax.org (Davide P. Cervone)
  21. */
  22. import {ArrayItem, EqnArrayItem} from '../base/BaseItems.js';
  23. import {StackItem} from '../StackItem.js';
  24. import ParseUtil from '../ParseUtil.js';
  25. import {ParseMethod, ParseResult} from '../Types.js';
  26. import {AmsMethods} from '../ams/AmsMethods.js';
  27. import BaseMethods from '../base/BaseMethods.js';
  28. import TexParser from '../TexParser.js';
  29. import TexError from '../TexError.js';
  30. import NodeUtil from '../NodeUtil.js';
  31. import {TEXCLASS} from '../../../core/MmlTree/MmlNode.js';
  32. import {length2em, em} from '../../../util/lengths.js';
  33. import {lookup} from '../../../util/Options.js';
  34. import NewcommandUtil from '../newcommand/NewcommandUtil.js';
  35. import NewcommandMethods from '../newcommand/NewcommandMethods.js';
  36. import {MathtoolsTags} from './MathtoolsTags.js';
  37. import {MathtoolsUtil} from './MathtoolsUtil.js';
  38. /**
  39. * The implementations for the macros and environments for the mathtools package.
  40. */
  41. export const MathtoolsMethods: Record<string, ParseMethod> = {
  42. /**
  43. * Handle a mathtools matrix environment, with optional alignment.
  44. *
  45. * @param {TexParser} parser The active tex parser.
  46. * @param {StackItem} begin The BeginItem for the environment.
  47. * @param {string} open The open delimiter for the matrix.
  48. * @param {string} close The close delimiter for the matrix.
  49. * @return {ParserResult} The ArrayItem for the matrix.
  50. */
  51. MtMatrix(parser: TexParser, begin: StackItem, open: string, close: string): ParseResult {
  52. const align = parser.GetBrackets(`\\begin{${begin.getName()}}`, 'c');
  53. return MathtoolsMethods.Array(parser, begin, open, close, align);
  54. },
  55. /**
  56. * Create a smallmatrix with given delimiters, and with optional alignment (and settable default)
  57. *
  58. * @param {TexParser} parser The active tex parser.
  59. * @param {StackItem} begin The BeginItem for the environment.
  60. * @param {string} open The open delimiter for the matrix.
  61. * @param {string} close The close delimiter for the matrix.
  62. * @param {string} align The (optional) alignment. If not given, use a bracket argument for it.
  63. * @return {ParseResult} The ArrayItem for the matrix.
  64. */
  65. MtSmallMatrix(parser: TexParser, begin: StackItem, open: string, close: string, align?: string): ParseResult {
  66. if (!align) {
  67. align = parser.GetBrackets(`\\begin{${begin.getName()}}`, parser.options.mathtools['smallmatrix-align']);
  68. }
  69. return MathtoolsMethods.Array(
  70. parser, begin, open, close, align, ParseUtil.Em(1 / 3), '.2em', 'S', 1
  71. );
  72. },
  73. /**
  74. * Create the multlined StackItem.
  75. *
  76. * @param {TexParser} parser The active tex parser.
  77. * @param {StackItem} begin The BeginItem for the environment.
  78. * @return {ParseResult} The MultlinedItem.
  79. */
  80. MtMultlined(parser: TexParser, begin: StackItem): ParseResult {
  81. const name = `\\begin{${begin.getName()}}`;
  82. let pos = parser.GetBrackets(name, parser.options.mathtools['multlined-pos'] || 'c');
  83. let width = pos ? parser.GetBrackets(name, '') : '';
  84. if (pos && !pos.match(/^[cbt]$/)) {
  85. [width, pos] = [pos, width];
  86. }
  87. parser.Push(begin);
  88. const item = parser.itemFactory.create('multlined', parser, begin) as ArrayItem;
  89. item.arraydef = {
  90. displaystyle: true,
  91. rowspacing: '.5em',
  92. width: width || 'auto',
  93. columnwidth: '100%',
  94. };
  95. return ParseUtil.setArrayAlign(item as ArrayItem, pos || 'c');
  96. },
  97. /**
  98. * Replacement for the AMS HandleShove that includes optional spacing values
  99. *
  100. * @param {TexParser} parser The active tex parser.
  101. * @param {string} name The name of the calling macro.
  102. * @param {string} shove Which way to shove the result.
  103. */
  104. HandleShove(parser: TexParser, name: string, shove: string) {
  105. let top = parser.stack.Top();
  106. if (top.kind !== 'multline' && top.kind !== 'multlined') {
  107. throw new TexError(
  108. 'CommandInMultlined',
  109. '%1 can only appear within the multline or multlined environments',
  110. name);
  111. }
  112. if (top.Size()) {
  113. throw new TexError(
  114. 'CommandAtTheBeginingOfLine',
  115. '%1 must come at the beginning of the line',
  116. name);
  117. }
  118. top.setProperty('shove', shove);
  119. let shift = parser.GetBrackets(name);
  120. let mml = parser.ParseArg(name);
  121. if (shift) {
  122. let mrow = parser.create('node', 'mrow', []);
  123. let mspace = parser.create('node', 'mspace', [], {width: shift});
  124. if (shove === 'left') {
  125. mrow.appendChild(mspace);
  126. mrow.appendChild(mml);
  127. } else {
  128. mrow.appendChild(mml);
  129. mrow.appendChild(mspace);
  130. }
  131. mml = mrow;
  132. }
  133. parser.Push(mml);
  134. },
  135. /**
  136. * Handle the spreadlines environment.
  137. *
  138. * @param {TexParser} parser The active tex parser.
  139. * @param {StackItem} begin The BeginItem for the environment.
  140. */
  141. SpreadLines(parser: TexParser, begin: StackItem) {
  142. if (parser.stack.env.closing === begin.getName()) {
  143. //
  144. // When the environment ends, look through the contents and
  145. // adjust the spacing in any tables, then push the results.
  146. //
  147. delete parser.stack.env.closing;
  148. const top = parser.stack.Pop();
  149. const mml = top.toMml();
  150. const spread = top.getProperty('spread') as string;
  151. if (mml.isInferred) {
  152. for (const child of NodeUtil.getChildren(mml)) {
  153. MathtoolsUtil.spreadLines(child, spread);
  154. }
  155. } else {
  156. MathtoolsUtil.spreadLines(mml, spread);
  157. }
  158. parser.Push(mml);
  159. } else {
  160. //
  161. // Read the spread dimension and save it, then begin the environment.
  162. //
  163. const spread = parser.GetDimen(`\\begin{${begin.getName()}}`);
  164. begin.setProperty('spread', spread);
  165. parser.Push(begin);
  166. }
  167. },
  168. /**
  169. * Implements the various cases environments.
  170. *
  171. * @param {TexParser} parser The calling parser.
  172. * @param {StackItem} begin The BeginItem for the environment.
  173. * @param {string} open The open delimiter for the matrix.
  174. * @param {string} close The close delimiter for the matrix.
  175. * @param {string} style The style (D, T, S, SS) for the contents of the array
  176. * @return {ArrayItem} The ArrayItem for the environment
  177. */
  178. Cases(parser: TexParser, begin: StackItem, open: string, close: string, style: string): ArrayItem {
  179. const array = parser.itemFactory.create('array').setProperty('casesEnv', begin.getName()) as ArrayItem;
  180. array.arraydef = {
  181. rowspacing: '.2em',
  182. columnspacing: '1em',
  183. columnalign: 'left'
  184. };
  185. if (style === 'D') {
  186. array.arraydef.displaystyle = true;
  187. }
  188. array.setProperties({open, close});
  189. parser.Push(begin);
  190. return array;
  191. },
  192. /**
  193. * Handle \mathrlap, \mathllap, \mathclap, and their cramped versions.
  194. *
  195. * @param {TexParser} parser The calling parser.
  196. * @param {string} name The macro name.
  197. * @param {string} pos The position (l, c, r) of the lapped content
  198. * @param {boolean} cramped True if the style should be cramped
  199. */
  200. MathLap(parser: TexParser, name: string, pos: string, cramped: boolean) {
  201. const style = parser.GetBrackets(name, '').trim();
  202. let mml = parser.create('node', 'mstyle', [
  203. parser.create('node', 'mpadded', [parser.ParseArg(name)], {
  204. width: 0, ...(pos === 'r' ? {} : {lspace: (pos === 'l' ? '-1width' : '-.5width')})
  205. })
  206. ], {'data-cramped': cramped});
  207. MathtoolsUtil.setDisplayLevel(mml, style);
  208. parser.Push(parser.create('node', 'TeXAtom', [mml]));
  209. },
  210. /**
  211. * Implements \cramped.
  212. *
  213. * @param {TexParser} parser The calling parser.
  214. * @param {string} name The macro name.
  215. */
  216. Cramped(parser: TexParser, name: string) {
  217. const style = parser.GetBrackets(name, '').trim();
  218. const arg = parser.ParseArg(name);
  219. const mml = parser.create('node', 'mstyle', [arg], {'data-cramped': true});
  220. MathtoolsUtil.setDisplayLevel(mml, style);
  221. parser.Push(mml);
  222. },
  223. /**
  224. * Implements \clap (and could do \llap and \rlap, where the contents are text mode).
  225. *
  226. * @param {TexParser} parser The calling parser.
  227. * @param {string} name The macro name.
  228. * @param {string} pos The position (l, c, r) of the lapped content
  229. */
  230. MtLap(parser: TexParser, name: string, pos: string) {
  231. const content = ParseUtil.internalMath(parser, parser.GetArgument(name), 0);
  232. let mml = parser.create('node', 'mpadded', content, {width: 0});
  233. if (pos !== 'r') {
  234. NodeUtil.setAttribute(mml, 'lspace', pos === 'l' ? '-1width' : '-.5width');
  235. }
  236. parser.Push(mml);
  237. },
  238. /**
  239. * Implements \mathmakebox.
  240. *
  241. * @param {TexParser} parser The calling parser.
  242. * @param {string} name The macro name.
  243. */
  244. MathMakeBox(parser: TexParser, name: string) {
  245. const width = parser.GetBrackets(name);
  246. const pos = parser.GetBrackets(name, 'c');
  247. const mml = parser.create('node', 'mpadded', [parser.ParseArg(name)]);
  248. if (width) {
  249. NodeUtil.setAttribute(mml, 'width', width);
  250. }
  251. const align = lookup(pos, {c: 'center', r: 'right'}, '');
  252. if (align) {
  253. NodeUtil.setAttribute(mml, 'data-align', align);
  254. }
  255. parser.Push(mml);
  256. },
  257. /**
  258. * Implements \mathmbox.
  259. *
  260. * @param {TexParser} parser The calling parser.
  261. * @param {string} name The macro name.
  262. */
  263. MathMBox(parser: TexParser, name: string) {
  264. parser.Push(parser.create('node', 'mrow', [parser.ParseArg(name)]));
  265. },
  266. /**
  267. * Implements \underbacket and \overbracket.
  268. *
  269. * @param {TexParser} parser The calling parser.
  270. * @param {string} name The macro name.
  271. */
  272. UnderOverBracket(parser: TexParser, name: string) {
  273. const thickness = length2em(parser.GetBrackets(name, '.1em'), .1);
  274. const height = parser.GetBrackets(name, '.2em');
  275. const arg = parser.GetArgument(name);
  276. const [pos, accent, border] = (
  277. name.charAt(1) === 'o' ?
  278. ['over', 'accent', 'bottom'] :
  279. ['under', 'accentunder', 'top']
  280. );
  281. const t = em(thickness);
  282. const base = new TexParser(arg, parser.stack.env, parser.configuration).mml();
  283. const copy = new TexParser(arg, parser.stack.env, parser.configuration).mml();
  284. const script = parser.create('node', 'mpadded', [
  285. parser.create('node', 'mphantom', [copy])
  286. ], {
  287. style: `border: ${t} solid; border-${border}: none`,
  288. height: height,
  289. depth: 0
  290. });
  291. const node = ParseUtil.underOver(parser, base, script, pos, true);
  292. const munderover = NodeUtil.getChildAt(NodeUtil.getChildAt(node, 0), 0); // TeXAtom.inferredMrow child 0
  293. NodeUtil.setAttribute(munderover, accent, true);
  294. parser.Push(node);
  295. },
  296. /**
  297. * Implements \Aboxed.
  298. *
  299. * @param {TexParser} parser The calling parser.
  300. * @param {string} name The macro name.
  301. */
  302. Aboxed(parser: TexParser, name: string) {
  303. //
  304. // Check that the top item is an alignment, and that we are on an even number of cells
  305. // (othewise add one to make it even).
  306. //
  307. const top = MathtoolsUtil.checkAlignment(parser, name);
  308. if (top.row.length % 2 === 1) {
  309. top.row.push(parser.create('node', 'mtd', []));
  310. }
  311. //
  312. // Get the argument and the rest of the TeX string.
  313. //
  314. const arg = parser.GetArgument(name);
  315. const rest = parser.string.substr(parser.i);
  316. //
  317. // Put the argument back, followed by "&&", and a marker that we look for below.
  318. //
  319. parser.string = arg + '&&\\endAboxed';
  320. parser.i = 0;
  321. //
  322. // Get the two parts separated by ampersands, and ignore the rest.
  323. //
  324. const left = parser.GetUpTo(name, '&');
  325. const right = parser.GetUpTo(name, '&');
  326. parser.GetUpTo(name, '\\endAboxed');
  327. //
  328. // Insert the TeX needed for the boxed content
  329. //
  330. const tex = ParseUtil.substituteArgs(
  331. parser, [left, right], '\\rlap{\\boxed{#1{}#2}}\\kern.267em\\phantom{#1}&\\phantom{{}#2}\\kern.267em'
  332. );
  333. parser.string = tex + rest;
  334. parser.i = 0;
  335. },
  336. /**
  337. * Implements \ArrowBetweenLines.
  338. *
  339. * @param {TexParser} parser The calling parser.
  340. * @param {string} name The macro name.
  341. */
  342. ArrowBetweenLines(parser: TexParser, name: string) {
  343. const top = MathtoolsUtil.checkAlignment(parser, name);
  344. if (top.Size() || top.row.length) {
  345. throw new TexError('BetweenLines', '%1 must be on a row by itself', name);
  346. }
  347. const star = parser.GetStar();
  348. const symbol = parser.GetBrackets(name, '\\Updownarrow');
  349. if (star) {
  350. top.EndEntry();
  351. top.EndEntry();
  352. }
  353. const tex = (star ? '\\quad' + symbol : symbol + '\\quad');
  354. const mml = new TexParser(tex, parser.stack.env, parser.configuration).mml();
  355. parser.Push(mml);
  356. top.EndEntry();
  357. top.EndRow();
  358. },
  359. /**
  360. * Implements \vdotswithin.
  361. *
  362. * @param {TexParser} parser The calling parser.
  363. * @param {string} name The macro name.
  364. */
  365. VDotsWithin(parser: TexParser, name: string) {
  366. const top = parser.stack.Top() as EqnArrayItem;
  367. const isFlush = (top.getProperty('flushspaceabove') === top.table.length);
  368. const arg = '\\mmlToken{mi}{}' + parser.GetArgument(name) + '\\mmlToken{mi}{}';
  369. const base = new TexParser(arg, parser.stack.env, parser.configuration).mml();
  370. let mml = parser.create('node', 'mpadded', [
  371. parser.create('node', 'mpadded', [
  372. parser.create('node', 'mo', [
  373. parser.create('text', '\u22EE')
  374. ])
  375. ], {
  376. width: 0,
  377. lspace: '-.5width', ...(isFlush ? {height: '-.6em', voffset: '-.18em'} : {})
  378. }),
  379. parser.create('node', 'mphantom', [base])
  380. ], {
  381. lspace: '.5width'
  382. });
  383. parser.Push(mml);
  384. },
  385. /**
  386. * Implements \shortvdotswithin.
  387. *
  388. * @param {TexParser} parser The calling parser.
  389. * @param {string} name The macro name.
  390. */
  391. ShortVDotsWithin(parser: TexParser, _name: string) {
  392. const top = parser.stack.Top() as EqnArrayItem;
  393. const star = parser.GetStar();
  394. MathtoolsMethods.FlushSpaceAbove(parser, '\\MTFlushSpaceAbove');
  395. !star && top.EndEntry();
  396. MathtoolsMethods.VDotsWithin(parser, '\\vdotswithin');
  397. star && top.EndEntry();
  398. MathtoolsMethods.FlushSpaceBelow(parser, '\\MTFlushSpaceBelow');
  399. },
  400. /**
  401. * Implements \MTFlushSpaceAbove.
  402. *
  403. * @param {TexParser} parser The calling parser.
  404. * @param {string} name The macro name.
  405. */
  406. FlushSpaceAbove(parser: TexParser, name: string) {
  407. const top = MathtoolsUtil.checkAlignment(parser, name);
  408. top.setProperty('flushspaceabove', top.table.length); // marker so \vdotswithin can shorten its height
  409. top.addRowSpacing('-' + parser.options.mathtools['shortvdotsadjustabove']);
  410. },
  411. /**
  412. * Implements \MTFlushSpaceBelow.
  413. *
  414. * @param {TexParser} parser The calling parser.
  415. * @param {string} name The macro name.
  416. */
  417. FlushSpaceBelow(parser: TexParser, name: string) {
  418. const top = MathtoolsUtil.checkAlignment(parser, name);
  419. top.Size() && top.EndEntry();
  420. top.EndRow();
  421. top.addRowSpacing('-' + parser.options.mathtools['shortvdotsadjustbelow']);
  422. },
  423. /**
  424. * Implements a paired delimiter (e.g., from \DeclarePairedDelimiter).
  425. *
  426. * @param {TexParser} parser The calling parser.
  427. * @param {string} name The macro name.
  428. * @param {string} open The open delimiter.
  429. * @param {string} close The close delimiter.
  430. * @param {string?} body The body betweeen the delimiters.
  431. * @param {number?} n The number of arguments to use for the body.
  432. * @param {string?} pre The TeX to go before the open delimiter.
  433. * @param {string?} post The TeX to go after the close delimiter.
  434. */
  435. PairedDelimiters(parser: TexParser, name: string,
  436. open: string, close: string,
  437. body: string = '#1', n: number = 1,
  438. pre: string = '', post: string = '') {
  439. const star = parser.GetStar();
  440. const size = (star ? '' : parser.GetBrackets(name));
  441. const [left, right] = (star ? ['\\left', '\\right'] : size ? [size + 'l' , size + 'r'] : ['', '']);
  442. const delim = (star ? '\\middle' : size || '');
  443. if (n) {
  444. const args: string[] = [];
  445. for (let i = args.length; i < n; i++) {
  446. args.push(parser.GetArgument(name));
  447. }
  448. pre = ParseUtil.substituteArgs(parser, args, pre);
  449. body = ParseUtil.substituteArgs(parser, args, body);
  450. post = ParseUtil.substituteArgs(parser, args, post);
  451. }
  452. body = body.replace(/\\delimsize/g, delim);
  453. parser.string = [pre, left, open, body, right, close, post, parser.string.substr(parser.i)]
  454. .reduce((s, part) => ParseUtil.addArgs(parser, s, part), '');
  455. parser.i = 0;
  456. ParseUtil.checkMaxMacros(parser);
  457. },
  458. /**
  459. * Implements \DeclarePairedDelimiter.
  460. *
  461. * @param {TexParser} parser The calling parser.
  462. * @param {string} name The macro name.
  463. */
  464. DeclarePairedDelimiter(parser: TexParser, name: string) {
  465. const cs = NewcommandUtil.GetCsNameArgument(parser, name);
  466. const open = parser.GetArgument(name);
  467. const close = parser.GetArgument(name);
  468. MathtoolsUtil.addPairedDelims(parser.configuration, cs, [open, close]);
  469. },
  470. /**
  471. * Implements \DeclarePairedDelimiterX.
  472. *
  473. * @param {TexParser} parser The calling parser.
  474. * @param {string} name The macro name.
  475. */
  476. DeclarePairedDelimiterX(parser: TexParser, name: string) {
  477. const cs = NewcommandUtil.GetCsNameArgument(parser, name);
  478. const n = NewcommandUtil.GetArgCount(parser, name);
  479. const open = parser.GetArgument(name);
  480. const close = parser.GetArgument(name);
  481. const body = parser.GetArgument(name);
  482. MathtoolsUtil.addPairedDelims(parser.configuration, cs, [open, close, body, n]);
  483. },
  484. /**
  485. * Implements \DeclarePairedDelimiterXPP.
  486. *
  487. * @param {TexParser} parser The calling parser.
  488. * @param {string} name The macro name.
  489. */
  490. DeclarePairedDelimiterXPP(parser: TexParser, name: string) {
  491. const cs = NewcommandUtil.GetCsNameArgument(parser, name);
  492. const n = NewcommandUtil.GetArgCount(parser, name);
  493. const pre = parser.GetArgument(name);
  494. const open = parser.GetArgument(name);
  495. const close = parser.GetArgument(name);
  496. const post = parser.GetArgument(name);
  497. const body = parser.GetArgument(name);
  498. MathtoolsUtil.addPairedDelims(parser.configuration, cs, [open, close, body, n, pre, post]);
  499. },
  500. /**
  501. * Implements \centeredcolon, \ordinarycolon, \MTThinColon.
  502. *
  503. * @param {TexParser} parser The calling parser.
  504. * @param {string} name The macro name.
  505. * @param {boolean} center True if colon should be centered
  506. * @param {boolean} force True menas always center (don't use centercolon option).
  507. * @param {boolean} thin True if this is a thin color (for \coloneqq, etc).
  508. */
  509. CenterColon(parser: TexParser, _name: string, center: boolean, force: boolean = false, thin: boolean = false) {
  510. const options = parser.options.mathtools;
  511. let mml = parser.create('token', 'mo', {}, ':');
  512. if (center && (options['centercolon'] || force)) {
  513. const dy = options['centercolon-offset'];
  514. mml = parser.create('node', 'mpadded', [mml], {
  515. voffset: dy, height: `+${dy}`, depth: `-${dy}`,
  516. ...(thin ? {width: options['thincolon-dw'], lspace: options['thincolon-dx']} : {})
  517. });
  518. }
  519. parser.Push(mml);
  520. },
  521. /**
  522. * Implements \coloneqq and related macros.
  523. *
  524. * @param {TexParser} parser The calling parser.
  525. * @param {string} name The macro name.
  526. * @param {string} tex The tex string to use (if not using unicode versions or if there isn't one).
  527. * @param {string} unicode The unicode character (if there is one).
  528. */
  529. Relation(parser: TexParser, _name: string, tex: string, unicode?: string) {
  530. const options = parser.options.mathtools;
  531. if (options['use-unicode'] && unicode) {
  532. parser.Push(parser.create('token', 'mo', {texClass: TEXCLASS.REL}, unicode));
  533. } else {
  534. tex = '\\mathrel{' + tex.replace(/:/g, '\\MTThinColon').replace(/-/g, '\\mathrel{-}') + '}';
  535. parser.string = ParseUtil.addArgs(parser, tex, parser.string.substr(parser.i));
  536. parser.i = 0;
  537. }
  538. },
  539. /**
  540. * Implements \ndownarrow and \nuparrow via a terrible hack (visual only, no chance of this working with SRE).
  541. *
  542. * @param {TexParser} parser The calling parser.
  543. * @param {string} name The macro name.
  544. * @param {string} c The base arrow for the slashed version
  545. * @param {string} dy A vertical offset for the slash
  546. */
  547. NArrow(parser: TexParser, _name: string, c: string, dy: string) {
  548. parser.Push(
  549. parser.create('node', 'TeXAtom', [
  550. parser.create('token', 'mtext', {}, c),
  551. parser.create('node', 'mpadded', [
  552. parser.create('node', 'mpadded', [
  553. parser.create('node', 'menclose', [
  554. parser.create('node', 'mspace', [], {height: '.2em', depth: 0, width: '.4em'})
  555. ], {notation: 'updiagonalstrike', 'data-thickness': '.05em', 'data-padding': 0})
  556. ], {width: 0, lspace: '-.5width', voffset: dy}),
  557. parser.create('node', 'mphantom', [
  558. parser.create('token', 'mtext', {}, c)
  559. ])
  560. ], {width: 0, lspace: '-.5width'})
  561. ], {texClass: TEXCLASS.REL})
  562. );
  563. },
  564. /**
  565. * Implements \splitfrac and \splitdfrac.
  566. *
  567. * @param {TexParser} parser The calling parser.
  568. * @param {string} name The macro name.
  569. * @param {boolean} display True if \splitdfrac.
  570. */
  571. SplitFrac(parser: TexParser, name: string, display: boolean) {
  572. const num = parser.ParseArg(name);
  573. const den = parser.ParseArg(name);
  574. parser.Push(
  575. parser.create('node', 'mstyle', [
  576. parser.create('node', 'mfrac', [
  577. parser.create('node', 'mstyle', [
  578. num,
  579. parser.create('token', 'mi'),
  580. parser.create('token', 'mspace', {width: '1em'}) // no parameter for this in mathtools. Should we add one?
  581. ], {scriptlevel: 0}),
  582. parser.create('node', 'mstyle', [
  583. parser.create('token', 'mspace', {width: '1em'}),
  584. parser.create('token', 'mi'),
  585. den
  586. ], {scriptlevel: 0})
  587. ], {linethickness: 0, numalign: 'left', denomalign: 'right'})
  588. ], {displaystyle: display, scriptlevel: 0})
  589. );
  590. },
  591. /**
  592. * Implements \xmathstrut.
  593. *
  594. * @param {TexParser} parser The calling parser.
  595. * @param {string} name The macro name.
  596. */
  597. XMathStrut(parser: TexParser, name: string) {
  598. let dd = parser.GetBrackets(name);
  599. let dh = parser.GetArgument(name);
  600. dh = MathtoolsUtil.plusOrMinus(name, dh);
  601. dd = MathtoolsUtil.plusOrMinus(name, dd || dh);
  602. parser.Push(
  603. parser.create('node', 'TeXAtom', [
  604. parser.create('node', 'mpadded', [
  605. parser.create('node', 'mphantom', [
  606. parser.create('token', 'mo', {stretchy: false}, '(')
  607. ])
  608. ], {width: 0, height: dh + 'height', depth: dd + 'depth'})
  609. ], {texClass: TEXCLASS.ORD})
  610. );
  611. },
  612. /**
  613. * Implements \prescript.
  614. *
  615. * @param {TexParser} parser The calling parser.
  616. * @param {string} name The macro name.
  617. */
  618. Prescript(parser: TexParser, name: string) {
  619. const sup = MathtoolsUtil.getScript(parser, name, 'sup');
  620. const sub = MathtoolsUtil.getScript(parser, name, 'sub');
  621. const base = MathtoolsUtil.getScript(parser, name, 'arg');
  622. if (NodeUtil.isType(sup, 'none') && NodeUtil.isType(sub, 'none')) {
  623. parser.Push(base);
  624. return;
  625. }
  626. const mml = parser.create('node', 'mmultiscripts', [base]);
  627. NodeUtil.getChildren(mml).push(null, null);
  628. NodeUtil.appendChildren(mml, [parser.create('node', 'mprescripts'), sub, sup]);
  629. mml.setProperty('fixPrescript', true);
  630. parser.Push(mml);
  631. },
  632. /**
  633. * Implements \newtagform and \renewtagform.
  634. *
  635. * @param {TexParser} parser The calling parser.
  636. * @param {string} name The macro name.
  637. * @param {boolean=} renew True if \renewtagform.
  638. */
  639. NewTagForm(parser: TexParser, name: string, renew: boolean = false) {
  640. const tags = parser.tags as MathtoolsTags;
  641. if (!('mtFormats' in tags)) {
  642. throw new TexError('TagsNotMT', '%1 can only be used with ams or mathtools tags', name);
  643. }
  644. const id = parser.GetArgument(name).trim();
  645. if (!id) {
  646. throw new TexError('InvalidTagFormID', 'Tag form name can\'t be empty');
  647. }
  648. const format = parser.GetBrackets(name, '');
  649. const left = parser.GetArgument(name);
  650. const right = parser.GetArgument(name);
  651. if (!renew && tags.mtFormats.has(id)) {
  652. throw new TexError('DuplicateTagForm', 'Duplicate tag form: %1', id);
  653. }
  654. tags.mtFormats.set(id, [left, right, format]);
  655. },
  656. /**
  657. * Implements \usetagform.
  658. *
  659. * @param {TexParser} parser The calling parser.
  660. * @param {string} name The macro name.
  661. */
  662. UseTagForm(parser: TexParser, name: string) {
  663. const tags = parser.tags as MathtoolsTags;
  664. if (!('mtFormats' in tags)) {
  665. throw new TexError('TagsNotMT', '%1 can only be used with ams or mathtools tags', name);
  666. }
  667. const id = parser.GetArgument(name).trim();
  668. if (!id) {
  669. tags.mtCurrent = null;
  670. return;
  671. }
  672. if (!tags.mtFormats.has(id)) {
  673. throw new TexError('UndefinedTagForm', 'Undefined tag form: %1', id);
  674. }
  675. tags.mtCurrent = tags.mtFormats.get(id);
  676. },
  677. /**
  678. * Implements \mathtoolsset.
  679. *
  680. * @param {TexParser} parser The calling parser.
  681. * @param {string} name The macro name.
  682. */
  683. SetOptions(parser: TexParser, name: string) {
  684. const options = parser.options.mathtools;
  685. if (!options['allow-mathtoolsset']) {
  686. throw new TexError('ForbiddenMathtoolsSet', '%1 is disabled', name);
  687. }
  688. const allowed = {} as {[id: string]: number};
  689. Object.keys(options).forEach(id => {
  690. if (id !== 'pariedDelimiters' && id !== 'tagforms' && id !== 'allow-mathtoolsset') {
  691. allowed[id] = 1;
  692. }
  693. });
  694. const args = parser.GetArgument(name);
  695. const keys = ParseUtil.keyvalOptions(args, allowed, true);
  696. for (const id of Object.keys(keys)) {
  697. options[id] = keys[id];
  698. }
  699. },
  700. /**
  701. * Use the Base or AMS methods for these
  702. */
  703. Array: BaseMethods.Array,
  704. Macro: BaseMethods.Macro,
  705. xArrow: AmsMethods.xArrow,
  706. HandleRef: AmsMethods.HandleRef,
  707. AmsEqnArray: AmsMethods.AmsEqnArray,
  708. MacroWithTemplate: NewcommandMethods.MacroWithTemplate,
  709. };