BaseMethods.ts 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618
  1. /*************************************************************
  2. *
  3. * Copyright (c) 2017-2022 The MathJax Consortium
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /**
  18. * @fileoverview The Basic Parse methods.
  19. *
  20. * @author v.sorge@mathjax.org (Volker Sorge)
  21. */
  22. import * as sitem from './BaseItems.js';
  23. import {StackItem, EnvList} from '../StackItem.js';
  24. import {Macro} from '../Symbol.js';
  25. import {ParseMethod} from '../Types.js';
  26. import NodeUtil from '../NodeUtil.js';
  27. import TexError from '../TexError.js';
  28. import TexParser from '../TexParser.js';
  29. import {TexConstant} from '../TexConstants.js';
  30. import ParseUtil from '../ParseUtil.js';
  31. import {MmlNode, TEXCLASS} from '../../../core/MmlTree/MmlNode.js';
  32. import {MmlMsubsup} from '../../../core/MmlTree/MmlNodes/msubsup.js';
  33. import {MmlMunderover} from '../../../core/MmlTree/MmlNodes/munderover.js';
  34. import {Label} from '../Tags.js';
  35. import {em} from '../../../util/lengths.js';
  36. import {entities} from '../../../util/Entities.js';
  37. import {lookup} from '../../../util/Options.js';
  38. // Namespace
  39. let BaseMethods: Record<string, ParseMethod> = {};
  40. const P_HEIGHT = 1.2 / .85; // cmex10 height plus depth over .85
  41. const MmlTokenAllow: {[key: string]: number} = {
  42. fontfamily: 1, fontsize: 1, fontweight: 1, fontstyle: 1,
  43. color: 1, background: 1,
  44. id: 1, 'class': 1, href: 1, style: 1
  45. };
  46. /**
  47. * Handle LaTeX tokens.
  48. */
  49. /**
  50. * Handle {
  51. * @param {TexParser} parser The calling parser.
  52. * @param {string} c The parsed character.
  53. */
  54. BaseMethods.Open = function(parser: TexParser, _c: string) {
  55. // @test Identifier Font, Prime, Prime with subscript
  56. parser.Push(parser.itemFactory.create('open'));
  57. };
  58. /**
  59. * Handle }
  60. * @param {TexParser} parser The calling parser.
  61. * @param {string} c The parsed character.
  62. */
  63. BaseMethods.Close = function(parser: TexParser, _c: string) {
  64. // @test Identifier Font, Prime, Prime with subscript
  65. parser.Push(parser.itemFactory.create('close'));
  66. };
  67. /**
  68. * Handle tilde and spaces.
  69. * @param {TexParser} parser The calling parser.
  70. * @param {string} c The parsed character.
  71. */
  72. BaseMethods.Tilde = function(parser: TexParser, _c: string) {
  73. // @test Tilde, Tilde2
  74. parser.Push(parser.create('token', 'mtext', {}, entities.nbsp));
  75. };
  76. /**
  77. * Handling space, by doing nothing.
  78. * @param {TexParser} parser The calling parser.
  79. * @param {string} c The parsed character.
  80. */
  81. BaseMethods.Space = function(_parser: TexParser, _c: string) {};
  82. /**
  83. * Handle ^
  84. * @param {TexParser} parser The calling parser.
  85. * @param {string} c The parsed character.
  86. */
  87. BaseMethods.Superscript = function(parser: TexParser, _c: string) {
  88. if (parser.GetNext().match(/\d/)) {
  89. // don't treat numbers as a unit
  90. parser.string = parser.string.substr(0, parser.i + 1) +
  91. ' ' + parser.string.substr(parser.i + 1);
  92. }
  93. let primes: MmlNode;
  94. let base: MmlNode | void;
  95. const top = parser.stack.Top();
  96. if (top.isKind('prime')) {
  97. // @test Prime on Prime
  98. [base, primes] = top.Peek(2);
  99. parser.stack.Pop();
  100. } else {
  101. // @test Empty base2, Square, Cube
  102. base = parser.stack.Prev();
  103. if (!base) {
  104. // @test Empty base
  105. base = parser.create('token', 'mi', {}, '');
  106. }
  107. }
  108. const movesupsub = NodeUtil.getProperty(base, 'movesupsub');
  109. let position = NodeUtil.isType(base, 'msubsup') ? (base as MmlMsubsup).sup :
  110. (base as MmlMunderover).over;
  111. if ((NodeUtil.isType(base, 'msubsup') && !NodeUtil.isType(base, 'msup') &&
  112. NodeUtil.getChildAt(base, (base as MmlMsubsup).sup)) ||
  113. (NodeUtil.isType(base, 'munderover') && !NodeUtil.isType(base, 'mover') &&
  114. NodeUtil.getChildAt(base, (base as MmlMunderover).over) &&
  115. !NodeUtil.getProperty(base, 'subsupOK'))) {
  116. // @test Double-super-error, Double-over-error
  117. throw new TexError('DoubleExponent', 'Double exponent: use braces to clarify');
  118. }
  119. if (!NodeUtil.isType(base, 'msubsup') || NodeUtil.isType(base, 'msup')) {
  120. if (movesupsub) {
  121. // @test Move Superscript, Large Operator
  122. if (!NodeUtil.isType(base, 'munderover') || NodeUtil.isType(base, 'mover') ||
  123. NodeUtil.getChildAt(base, (base as MmlMunderover).over)) {
  124. // @test Large Operator
  125. base = parser.create('node', 'munderover', [base], {movesupsub: true});
  126. }
  127. position = (base as MmlMunderover).over;
  128. } else {
  129. // @test Empty base, Empty base2, Square, Cube
  130. base = parser.create('node', 'msubsup', [base]);
  131. position = (base as MmlMsubsup).sup;
  132. }
  133. }
  134. parser.Push(
  135. parser.itemFactory.create('subsup', base).setProperties({
  136. position: position, primes: primes, movesupsub: movesupsub
  137. }) );
  138. };
  139. /**
  140. * Handle _
  141. * @param {TexParser} parser The calling parser.
  142. * @param {string} c The parsed character.
  143. */
  144. BaseMethods.Subscript = function(parser: TexParser, _c: string) {
  145. if (parser.GetNext().match(/\d/)) {
  146. // don't treat numbers as a unit
  147. parser.string =
  148. parser.string.substr(0, parser.i + 1) + ' ' +
  149. parser.string.substr(parser.i + 1);
  150. }
  151. let primes, base;
  152. const top = parser.stack.Top();
  153. if (top.isKind('prime')) {
  154. // @test Prime on Sub
  155. [base, primes] = top.Peek(2);
  156. parser.stack.Pop();
  157. } else {
  158. base = parser.stack.Prev();
  159. if (!base) {
  160. // @test Empty Base Index
  161. base = parser.create('token', 'mi', {}, '');
  162. }
  163. }
  164. const movesupsub = NodeUtil.getProperty(base, 'movesupsub');
  165. let position = NodeUtil.isType(base, 'msubsup') ?
  166. (base as MmlMsubsup).sub : (base as MmlMunderover).under;
  167. if ((NodeUtil.isType(base, 'msubsup') && !NodeUtil.isType(base, 'msup') &&
  168. NodeUtil.getChildAt(base, (base as MmlMsubsup).sub)) ||
  169. (NodeUtil.isType(base, 'munderover') && !NodeUtil.isType(base, 'mover') &&
  170. NodeUtil.getChildAt(base, (base as MmlMunderover).under) &&
  171. !NodeUtil.getProperty(base, 'subsupOK'))) {
  172. // @test Double-sub-error, Double-under-error
  173. throw new TexError('DoubleSubscripts', 'Double subscripts: use braces to clarify');
  174. }
  175. if (!NodeUtil.isType(base, 'msubsup') || NodeUtil.isType(base, 'msup')) {
  176. if (movesupsub) {
  177. // @test Large Operator, Move Superscript
  178. if (!NodeUtil.isType(base, 'munderover') || NodeUtil.isType(base, 'mover') ||
  179. NodeUtil.getChildAt(base, (base as MmlMunderover).under)) {
  180. // @test Move Superscript
  181. base = parser.create('node', 'munderover', [base], {movesupsub: true});
  182. }
  183. position = (base as MmlMunderover).under;
  184. } else {
  185. // @test Empty Base Index, Empty Base Index2, Index
  186. base = parser.create('node', 'msubsup', [base]);
  187. position = (base as MmlMsubsup).sub;
  188. }
  189. }
  190. parser.Push(
  191. parser.itemFactory.create('subsup', base).setProperties({
  192. position: position, primes: primes, movesupsub: movesupsub
  193. }) );
  194. };
  195. /**
  196. * Handle '
  197. * @param {TexParser} parser The calling parser.
  198. * @param {string} c The parsed character.
  199. */
  200. BaseMethods.Prime = function(parser: TexParser, c: string) {
  201. // @test Prime
  202. let base = parser.stack.Prev();
  203. if (!base) {
  204. // @test PrimeSup, PrePrime, Prime on Sup
  205. base = parser.create('node', 'mi');
  206. }
  207. if (NodeUtil.isType(base, 'msubsup') && !NodeUtil.isType(base, 'msup') &&
  208. NodeUtil.getChildAt(base, (base as MmlMsubsup).sup)) {
  209. // @test Double Prime Error
  210. throw new TexError('DoubleExponentPrime',
  211. 'Prime causes double exponent: use braces to clarify');
  212. }
  213. let sup = '';
  214. parser.i--;
  215. do {
  216. // @test Prime, PrimeSup, Double Prime, PrePrime
  217. sup += entities.prime; parser.i++, c = parser.GetNext();
  218. } while (c === '\'' || c === entities.rsquo);
  219. sup = ['', '\u2032', '\u2033', '\u2034', '\u2057'][sup.length] || sup;
  220. const node = parser.create('token', 'mo', {variantForm: true}, sup);
  221. parser.Push(
  222. parser.itemFactory.create('prime', base, node) );
  223. };
  224. /**
  225. * Handle comments
  226. * @param {TexParser} parser The calling parser.
  227. * @param {string} c The parsed character.
  228. */
  229. BaseMethods.Comment = function(parser: TexParser, _c: string) {
  230. while (parser.i < parser.string.length && parser.string.charAt(parser.i) !== '\n') {
  231. parser.i++;
  232. }
  233. };
  234. /**
  235. * Handle hash marks outside of definitions
  236. * @param {TexParser} parser The calling parser.
  237. * @param {string} c The parsed character.
  238. */
  239. BaseMethods.Hash = function(_parser: TexParser, _c: string) {
  240. // @test Hash Error
  241. throw new TexError('CantUseHash1',
  242. 'You can\'t use \'macro parameter character #\' in math mode');
  243. };
  244. /**
  245. *
  246. * Handle LaTeX Macros
  247. *
  248. */
  249. /**
  250. * Handle \mathrm, \mathbf, etc, allowing for multi-letter runs to be one <mi>.
  251. */
  252. BaseMethods.MathFont = function(parser: TexParser, name: string, variant: string) {
  253. const text = parser.GetArgument(name);
  254. let mml = new TexParser(text, {
  255. ...parser.stack.env,
  256. font: variant,
  257. multiLetterIdentifiers: /^[a-zA-Z]+/ as any,
  258. noAutoOP: true
  259. }, parser.configuration).mml();
  260. parser.Push(parser.create('node', 'TeXAtom', [mml]));
  261. };
  262. /**
  263. * Setting font, e.g., via \\rm, \\bf etc.
  264. * @param {TexParser} parser The calling parser.
  265. * @param {string} name The macro name.
  266. * @param {string} font The font name.
  267. */
  268. BaseMethods.SetFont = function(parser: TexParser, _name: string, font: string) {
  269. parser.stack.env['font'] = font;
  270. };
  271. /**
  272. * Setting style, e.g., via \\displaystyle, \\textstyle, etc.
  273. * @param {TexParser} parser The calling parser.
  274. * @param {string} name The macro name.
  275. * @param {string} texStyle The tex style name: D, T, S, SS
  276. * @param {boolean} style True if we are in displaystyle.
  277. * @param {string} level The nesting level for scripts.
  278. */
  279. BaseMethods.SetStyle = function(parser: TexParser, _name: string,
  280. texStyle: string, style: boolean,
  281. level: string) {
  282. parser.stack.env['style'] = texStyle;
  283. parser.stack.env['level'] = level;
  284. parser.Push(
  285. parser.itemFactory.create('style').setProperty(
  286. 'styles', {displaystyle: style, scriptlevel: level}));
  287. };
  288. /**
  289. * Setting size of an expression, e.g., \\small, \\huge.
  290. * @param {TexParser} parser The calling parser.
  291. * @param {string} name The macro name.
  292. * @param {number} size The size value.
  293. */
  294. BaseMethods.SetSize = function(parser: TexParser, _name: string, size: number) {
  295. parser.stack.env['size'] = size;
  296. parser.Push(
  297. parser.itemFactory.create('style').setProperty('styles', {mathsize: em(size)}));
  298. };
  299. /**
  300. * Setting explicit spaces, e.g., via commata or colons.
  301. * @param {TexParser} parser The calling parser.
  302. * @param {string} name The macro name.
  303. * @param {string} space The space value.
  304. */
  305. BaseMethods.Spacer = function(parser: TexParser, _name: string, space: number) {
  306. // @test Positive Spacing, Negative Spacing
  307. const node = parser.create('node', 'mspace', [], {width: em(space)});
  308. const style = parser.create('node', 'mstyle', [node], {scriptlevel: 0});
  309. parser.Push(style);
  310. };
  311. /**
  312. * Parses left/right fenced expressions.
  313. * @param {TexParser} parser The calling parser.
  314. * @param {string} name The macro name.
  315. */
  316. BaseMethods.LeftRight = function(parser: TexParser, name: string) {
  317. // @test Fenced, Fenced3
  318. const first = name.substr(1);
  319. parser.Push(parser.itemFactory.create(first, parser.GetDelimiter(name), parser.stack.env.color));
  320. };
  321. /**
  322. * Handle a named math function, e.g., \\sin, \\cos
  323. * @param {TexParser} parser The calling parser.
  324. * @param {string} name The macro name.
  325. * @param {string} id Alternative string representation of the function.
  326. */
  327. BaseMethods.NamedFn = function(parser: TexParser, name: string, id: string) {
  328. // @test Named Function
  329. if (!id) {
  330. id = name.substr(1);
  331. }
  332. const mml = parser.create('token', 'mi', {texClass: TEXCLASS.OP}, id);
  333. parser.Push(parser.itemFactory.create('fn', mml));
  334. };
  335. /**
  336. * Handle a named math operator, e.g., \\min, \\lim
  337. * @param {TexParser} parser The calling parser.
  338. * @param {string} name The macro name.
  339. * @param {string} id Alternative string representation of the operator.
  340. */
  341. BaseMethods.NamedOp = function(parser: TexParser, name: string, id: string) {
  342. // @test Limit
  343. if (!id) {
  344. id = name.substr(1);
  345. }
  346. id = id.replace(/&thinsp;/, '\u2006');
  347. const mml = parser.create('token', 'mo', {
  348. movablelimits: true,
  349. movesupsub: true,
  350. form: TexConstant.Form.PREFIX,
  351. texClass: TEXCLASS.OP
  352. }, id);
  353. parser.Push(mml);
  354. };
  355. /**
  356. * Handle a limits command for math operators.
  357. * @param {TexParser} parser The calling parser.
  358. * @param {string} name The macro name.
  359. * @param {string} limits The limits arguments.
  360. */
  361. BaseMethods.Limits = function(parser: TexParser, _name: string, limits: string) {
  362. // @test Limits
  363. let op = parser.stack.Prev(true);
  364. // Get the texclass for the core operator.
  365. if (!op || (NodeUtil.getTexClass(NodeUtil.getCoreMO(op)) !== TEXCLASS.OP &&
  366. NodeUtil.getProperty(op, 'movesupsub') == null)) {
  367. // @test Limits Error
  368. throw new TexError('MisplacedLimits', '%1 is allowed only on operators', parser.currentCS);
  369. }
  370. const top = parser.stack.Top();
  371. let node;
  372. if (NodeUtil.isType(op, 'munderover') && !limits) {
  373. // @test Limits UnderOver
  374. node = parser.create('node', 'msubsup');
  375. NodeUtil.copyChildren(op, node);
  376. op = top.Last = node;
  377. } else if (NodeUtil.isType(op, 'msubsup') && limits) {
  378. // @test Limits SubSup
  379. // node = parser.create('node', 'munderover', NodeUtil.getChildren(op), {});
  380. // Needs to be copied, otherwise we get an error in MmlNode.appendChild!
  381. node = parser.create('node', 'munderover');
  382. NodeUtil.copyChildren(op, node);
  383. op = top.Last = node;
  384. }
  385. NodeUtil.setProperty(op, 'movesupsub', limits ? true : false);
  386. NodeUtil.setProperties(NodeUtil.getCoreMO(op), {'movablelimits': false});
  387. if (NodeUtil.getAttribute(op, 'movablelimits') ||
  388. NodeUtil.getProperty(op, 'movablelimits')) {
  389. NodeUtil.setProperties(op, {'movablelimits': false});
  390. }
  391. };
  392. /**
  393. * Handle over commands.
  394. * @param {TexParser} parser The calling parser.
  395. * @param {string} name The macro name.
  396. * @param {string} open The open delimiter in case of a "withdelim" version.
  397. * @param {string} close The close delimiter.
  398. */
  399. BaseMethods.Over = function(parser: TexParser, name: string, open: string, close: string) {
  400. // @test Over
  401. const mml = parser.itemFactory.create('over').setProperty('name', parser.currentCS) ;
  402. if (open || close) {
  403. // @test Choose
  404. mml.setProperty('open', open);
  405. mml.setProperty('close', close);
  406. } else if (name.match(/withdelims$/)) {
  407. // @test Over With Delims, Above With Delims
  408. mml.setProperty('open', parser.GetDelimiter(name));
  409. mml.setProperty('close', parser.GetDelimiter(name));
  410. }
  411. if (name.match(/^\\above/)) {
  412. // @test Above, Above With Delims
  413. mml.setProperty('thickness', parser.GetDimen(name));
  414. }
  415. else if (name.match(/^\\atop/) || open || close) {
  416. // @test Choose
  417. mml.setProperty('thickness', 0);
  418. }
  419. parser.Push(mml);
  420. };
  421. /**
  422. * Parses a fraction.
  423. * @param {TexParser} parser The calling parser.
  424. * @param {string} name The macro name.
  425. */
  426. BaseMethods.Frac = function(parser: TexParser, name: string) {
  427. // @test Frac
  428. const num = parser.ParseArg(name);
  429. const den = parser.ParseArg(name);
  430. const node = parser.create('node', 'mfrac', [num, den]);
  431. parser.Push(node);
  432. };
  433. /**
  434. * Parses a square root element.
  435. * @param {TexParser} parser The calling parser.
  436. * @param {string} name The macro name.
  437. */
  438. BaseMethods.Sqrt = function(parser: TexParser, name: string) {
  439. const n = parser.GetBrackets(name);
  440. let arg = parser.GetArgument(name);
  441. if (arg === '\\frac') {
  442. arg += '{' + parser.GetArgument(arg) + '}{' + parser.GetArgument(arg) + '}';
  443. }
  444. let mml = new TexParser(arg, parser.stack.env, parser.configuration).mml();
  445. if (!n) {
  446. // @test Square Root
  447. mml = parser.create('node', 'msqrt', [mml]);
  448. } else {
  449. // @test General Root
  450. mml = parser.create('node', 'mroot', [mml, parseRoot(parser, n)]);
  451. }
  452. parser.Push(mml);
  453. };
  454. // Utility
  455. /**
  456. * Parse a general root.
  457. * @param {TexParser} parser The calling parser.
  458. * @param {string} n The index of the root.
  459. */
  460. function parseRoot(parser: TexParser, n: string) {
  461. // @test General Root, Explicit Root
  462. const env = parser.stack.env;
  463. const inRoot = env['inRoot'];
  464. env['inRoot'] = true;
  465. const newParser = new TexParser(n, env, parser.configuration);
  466. let node = newParser.mml();
  467. const global = newParser.stack.global;
  468. if (global['leftRoot'] || global['upRoot']) {
  469. // @test Tweaked Root
  470. const def: EnvList = {};
  471. if (global['leftRoot']) {
  472. def['width'] = global['leftRoot'];
  473. }
  474. if (global['upRoot']) {
  475. def['voffset'] = global['upRoot'];
  476. def['height'] = global['upRoot'];
  477. }
  478. node = parser.create('node', 'mpadded', [node], def);
  479. }
  480. env['inRoot'] = inRoot;
  481. return node;
  482. }
  483. /**
  484. * Parse a general root.
  485. * @param {TexParser} parser The calling parser.
  486. * @param {string} name The macro name.
  487. */
  488. BaseMethods.Root = function(parser: TexParser, name: string) {
  489. const n = parser.GetUpTo(name, '\\of');
  490. const arg = parser.ParseArg(name);
  491. const node = parser.create('node', 'mroot', [arg, parseRoot(parser, n)]);
  492. parser.Push(node);
  493. };
  494. /**
  495. * Parses a movable index element in a root, e.g. \\uproot, \\leftroot
  496. * @param {TexParser} parser The calling parser.
  497. * @param {string} name The macro name.
  498. * @param {string} id Argument which should be a string representation of an integer.
  499. */
  500. BaseMethods.MoveRoot = function(parser: TexParser, name: string, id: string) {
  501. // @test Tweaked Root
  502. if (!parser.stack.env['inRoot']) {
  503. // @test Misplaced Move Root
  504. throw new TexError('MisplacedMoveRoot', '%1 can appear only within a root', parser.currentCS);
  505. }
  506. if (parser.stack.global[id]) {
  507. // @test Multiple Move Root
  508. throw new TexError('MultipleMoveRoot', 'Multiple use of %1', parser.currentCS);
  509. }
  510. let n = parser.GetArgument(name);
  511. if (!n.match(/-?[0-9]+/)) {
  512. // @test Incorrect Move Root
  513. throw new TexError('IntegerArg', 'The argument to %1 must be an integer', parser.currentCS);
  514. }
  515. n = (parseInt(n, 10) / 15) + 'em';
  516. if (n.substr(0, 1) !== '-') {
  517. n = '+' + n;
  518. }
  519. parser.stack.global[id] = n;
  520. };
  521. /**
  522. * Handle accents.
  523. * @param {TexParser} parser The calling parser.
  524. * @param {string} name The macro name.
  525. * @param {string} accent The accent.
  526. * @param {boolean} stretchy True if accent is stretchy.
  527. */
  528. BaseMethods.Accent = function(parser: TexParser, name: string, accent: string, stretchy: boolean) {
  529. // @test Vector
  530. const c = parser.ParseArg(name);
  531. // @test Vector Font
  532. const def = {...ParseUtil.getFontDef(parser), accent: true, mathaccent: true};
  533. const entity = NodeUtil.createEntity(accent);
  534. const moNode = parser.create('token', 'mo', def, entity);
  535. const mml = moNode;
  536. NodeUtil.setAttribute(mml, 'stretchy', stretchy ? true : false);
  537. // @test Vector Op, Vector
  538. const mo = (NodeUtil.isEmbellished(c) ? NodeUtil.getCoreMO(c) : c);
  539. if (NodeUtil.isType(mo, 'mo') || NodeUtil.getProperty(mo, 'movablelimits')) {
  540. // @test Vector Op
  541. NodeUtil.setProperties(mo, {'movablelimits': false});
  542. }
  543. const muoNode = parser.create('node', 'munderover');
  544. // This is necessary to get the empty element into the children.
  545. NodeUtil.setChild(muoNode, 0, c);
  546. NodeUtil.setChild(muoNode, 1, null);
  547. NodeUtil.setChild(muoNode, 2, mml);
  548. let texAtom = parser.create('node', 'TeXAtom', [muoNode]);
  549. parser.Push(texAtom);
  550. };
  551. /**
  552. * Handles stacked elements.
  553. * @param {TexParser} parser The calling parser.
  554. * @param {string} name The macro name.
  555. * @param {string} c Character to stack.
  556. * @param {boolean} stack True if stacked operator.
  557. */
  558. BaseMethods.UnderOver = function(parser: TexParser, name: string, c: string, stack: boolean) {
  559. const entity = NodeUtil.createEntity(c);
  560. const mo = parser.create('token', 'mo', {stretchy: true, accent: true}, entity);
  561. const pos = (name.charAt(1) === 'o' ? 'over' : 'under');
  562. const base = parser.ParseArg(name);
  563. parser.Push(ParseUtil.underOver(parser, base, mo, pos, stack));
  564. };
  565. /**
  566. * Handles overset.
  567. * @param {TexParser} parser The calling parser.
  568. * @param {string} name The macro name.
  569. */
  570. BaseMethods.Overset = function(parser: TexParser, name: string) {
  571. // @test Overset
  572. const top = parser.ParseArg(name);
  573. const base = parser.ParseArg(name);
  574. ParseUtil.checkMovableLimits(base);
  575. if (top.isKind('mo')) {
  576. NodeUtil.setAttribute(top, 'accent', false);
  577. }
  578. const node = parser.create('node', 'mover', [base, top]);
  579. parser.Push(node);
  580. };
  581. /**
  582. * Handles underset.
  583. * @param {TexParser} parser The calling parser.
  584. * @param {string} name The macro name.
  585. */
  586. BaseMethods.Underset = function(parser: TexParser, name: string) {
  587. // @test Underset
  588. const bot = parser.ParseArg(name);
  589. const base = parser.ParseArg(name);
  590. ParseUtil.checkMovableLimits(base);
  591. if (bot.isKind('mo')) {
  592. NodeUtil.setAttribute(bot, 'accent', false);
  593. }
  594. const node = parser.create('node', 'munder', [base, bot], {accentunder: false});
  595. parser.Push(node);
  596. };
  597. /**
  598. * Handles overunderset.
  599. * @param {TexParser} parser The calling parser.
  600. * @param {string} name The macro name.
  601. */
  602. BaseMethods.Overunderset = function(parser: TexParser, name: string) {
  603. const top = parser.ParseArg(name);
  604. const bot = parser.ParseArg(name);
  605. const base = parser.ParseArg(name);
  606. ParseUtil.checkMovableLimits(base);
  607. if (top.isKind('mo')) {
  608. NodeUtil.setAttribute(top, 'accent', false);
  609. }
  610. if (bot.isKind('mo')) {
  611. NodeUtil.setAttribute(bot, 'accent', false);
  612. }
  613. const node = parser.create('node', 'munderover', [base, bot, top], {accent: false, accentunder: false});
  614. parser.Push(node);
  615. };
  616. /**
  617. * Creates TeXAtom, when class of element is changed explicitly.
  618. * @param {TexParser} parser The calling parser.
  619. * @param {string} name The macro name.
  620. * @param {number} mclass The new TeX class.
  621. */
  622. BaseMethods.TeXAtom = function(parser: TexParser, name: string, mclass: number) {
  623. let def: EnvList = {texClass: mclass};
  624. let mml: StackItem | MmlNode;
  625. let node: MmlNode;
  626. let parsed: MmlNode;
  627. if (mclass === TEXCLASS.OP) {
  628. def['movesupsub'] = def['movablelimits'] = true;
  629. const arg = parser.GetArgument(name);
  630. const match = arg.match(/^\s*\\rm\s+([a-zA-Z0-9 ]+)$/);
  631. if (match) {
  632. // @test Mathop
  633. def['mathvariant'] = TexConstant.Variant.NORMAL;
  634. node = parser.create('token', 'mi', def, match[1]);
  635. } else {
  636. // @test Mathop Cal
  637. parsed = new TexParser(arg, parser.stack.env, parser.configuration).mml();
  638. node = parser.create('node', 'TeXAtom', [parsed], def);
  639. }
  640. mml = parser.itemFactory.create('fn', node);
  641. } else {
  642. // @test Mathrel
  643. parsed = parser.ParseArg(name);
  644. mml = parser.create('node', 'TeXAtom', [parsed], def);
  645. }
  646. parser.Push(mml);
  647. };
  648. /**
  649. * Creates mmltoken elements. Used in Macro substitutions.
  650. * @param {TexParser} parser The calling parser.
  651. * @param {string} name The macro name.
  652. */
  653. BaseMethods.MmlToken = function(parser: TexParser, name: string) {
  654. // @test Modulo
  655. const kind = parser.GetArgument(name);
  656. let attr = parser.GetBrackets(name, '').replace(/^\s+/, '');
  657. const text = parser.GetArgument(name);
  658. const def: EnvList = {};
  659. const keep: string[] = [];
  660. let node: MmlNode;
  661. try {
  662. node = parser.create('node', kind);
  663. } catch (e) {
  664. node = null;
  665. }
  666. if (!node || !node.isToken) {
  667. // @test Token Illegal Type, Token Wrong Type
  668. throw new TexError('NotMathMLToken', '%1 is not a token element', kind);
  669. }
  670. while (attr !== '') {
  671. const match = attr.match(/^([a-z]+)\s*=\s*('[^']*'|"[^"]*"|[^ ,]*)\s*,?\s*/i);
  672. if (!match) {
  673. // @test Token Invalid Attribute
  674. throw new TexError('InvalidMathMLAttr', 'Invalid MathML attribute: %1', attr);
  675. }
  676. if (!node.attributes.hasDefault(match[1]) && !MmlTokenAllow[match[1]]) {
  677. // @test Token Unknown Attribute, Token Wrong Attribute
  678. throw new TexError('UnknownAttrForElement',
  679. '%1 is not a recognized attribute for %2',
  680. match[1], kind);
  681. }
  682. let value: string | boolean = ParseUtil.MmlFilterAttribute(
  683. parser, match[1], match[2].replace(/^(['"])(.*)\1$/, '$2'));
  684. if (value) {
  685. if (value.toLowerCase() === 'true') {
  686. value = true;
  687. }
  688. else if (value.toLowerCase() === 'false') {
  689. value = false;
  690. }
  691. def[match[1]] = value;
  692. keep.push(match[1]);
  693. }
  694. attr = attr.substr(match[0].length);
  695. }
  696. if (keep.length) {
  697. def['mjx-keep-attrs'] = keep.join(' ');
  698. }
  699. const textNode = parser.create('text', text);
  700. node.appendChild(textNode);
  701. NodeUtil.setProperties(node, def);
  702. parser.Push(node);
  703. };
  704. /**
  705. * Handle strut.
  706. * @param {TexParser} parser The calling parser.
  707. * @param {string} name The macro name.
  708. */
  709. BaseMethods.Strut = function(parser: TexParser, _name: string) {
  710. // @test Strut
  711. const row = parser.create('node', 'mrow');
  712. const padded = parser.create('node', 'mpadded', [row],
  713. {height: '8.6pt', depth: '3pt', width: 0});
  714. parser.Push(padded);
  715. };
  716. /**
  717. * Handle phantom commands.
  718. * @param {TexParser} parser The calling parser.
  719. * @param {string} name The macro name.
  720. * @param {string} v Vertical size.
  721. * @param {string} h Horizontal size.
  722. */
  723. BaseMethods.Phantom = function(parser: TexParser, name: string, v: string, h: string) {
  724. // @test Phantom
  725. let box = parser.create('node', 'mphantom', [parser.ParseArg(name)]);
  726. if (v || h) {
  727. // TEMP: Changes here
  728. box = parser.create('node', 'mpadded', [box]);
  729. if (h) {
  730. // @test Horizontal Phantom
  731. NodeUtil.setAttribute(box, 'height', 0);
  732. NodeUtil.setAttribute(box, 'depth', 0);
  733. }
  734. if (v) {
  735. // @test Vertical Phantom
  736. NodeUtil.setAttribute(box, 'width', 0);
  737. }
  738. }
  739. const atom = parser.create('node', 'TeXAtom', [box]);
  740. parser.Push(atom);
  741. };
  742. /**
  743. * Handle smash.
  744. * @param {TexParser} parser The calling parser.
  745. * @param {string} name The macro name.
  746. */
  747. BaseMethods.Smash = function(parser: TexParser, name: string) {
  748. // @test Smash, Smash Top, Smash Bottom
  749. const bt = ParseUtil.trimSpaces(parser.GetBrackets(name, ''));
  750. const smash = parser.create('node', 'mpadded', [parser.ParseArg(name)]);
  751. // TEMP: Changes here:
  752. switch (bt) {
  753. case 'b': NodeUtil.setAttribute(smash, 'depth', 0); break;
  754. case 't': NodeUtil.setAttribute(smash, 'height', 0); break;
  755. default:
  756. NodeUtil.setAttribute(smash, 'height', 0);
  757. NodeUtil.setAttribute(smash, 'depth', 0);
  758. }
  759. const atom = parser.create('node', 'TeXAtom', [smash]);
  760. parser.Push(atom);
  761. };
  762. /**
  763. * Handle rlap and llap commands.
  764. * @param {TexParser} parser The calling parser.
  765. * @param {string} name The macro name.
  766. */
  767. BaseMethods.Lap = function(parser: TexParser, name: string) {
  768. // @test Llap, Rlap
  769. const mml = parser.create('node', 'mpadded', [parser.ParseArg(name)], {width: 0});
  770. if (name === '\\llap') {
  771. // @test Llap
  772. NodeUtil.setAttribute(mml, 'lspace', '-1width');
  773. }
  774. const atom = parser.create('node', 'TeXAtom', [mml]);
  775. parser.Push(atom);
  776. };
  777. /**
  778. * Handle raise and lower commands.
  779. * @param {TexParser} parser The calling parser.
  780. * @param {string} name The macro name.
  781. */
  782. BaseMethods.RaiseLower = function(parser: TexParser, name: string) {
  783. // @test Raise, Lower, Raise Negative, Lower Negative
  784. let h = parser.GetDimen(name);
  785. let item =
  786. parser.itemFactory.create('position').setProperties({name: parser.currentCS, move: 'vertical'}) ;
  787. // TEMP: Changes here:
  788. if (h.charAt(0) === '-') {
  789. // @test Raise Negative, Lower Negative
  790. h = h.slice(1);
  791. name = name.substr(1) === 'raise' ? '\\lower' : '\\raise';
  792. }
  793. if (name === '\\lower') {
  794. // @test Raise, Raise Negative
  795. item.setProperty('dh', '-' + h);
  796. item.setProperty('dd', '+' + h);
  797. } else {
  798. // @test Lower, Lower Negative
  799. item.setProperty('dh', '+' + h);
  800. item.setProperty('dd', '-' + h);
  801. }
  802. parser.Push(item);
  803. };
  804. /**
  805. * Handle moveleft, moveright commands
  806. * @param {TexParser} parser The calling parser.
  807. * @param {string} name The macro name.
  808. */
  809. BaseMethods.MoveLeftRight = function(parser: TexParser, name: string) {
  810. // @test Move Left, Move Right, Move Left Negative, Move Right Negative
  811. let h = parser.GetDimen(name);
  812. let nh = (h.charAt(0) === '-' ? h.slice(1) : '-' + h);
  813. if (name === '\\moveleft') {
  814. let tmp = h;
  815. h = nh;
  816. nh = tmp;
  817. }
  818. parser.Push(
  819. parser.itemFactory.create('position').setProperties({
  820. name: parser.currentCS, move: 'horizontal',
  821. left: parser.create('node', 'mspace', [], {width: h}),
  822. right: parser.create('node', 'mspace', [], {width: nh})}) );
  823. };
  824. /**
  825. * Handle horizontal spacing commands.
  826. * @param {TexParser} parser The calling parser.
  827. * @param {string} name The macro name.
  828. */
  829. BaseMethods.Hskip = function(parser: TexParser, name: string) {
  830. // @test Modulo
  831. const node = parser.create('node', 'mspace', [],
  832. {width: parser.GetDimen(name)});
  833. parser.Push(node);
  834. };
  835. /**
  836. * Handle removal of spaces in script modes
  837. * @param {TexParser} parser The calling parser.
  838. * @param {string} name The macro name.
  839. */
  840. BaseMethods.Nonscript = function(parser: TexParser, _name: string) {
  841. parser.Push(parser.itemFactory.create('nonscript'));
  842. };
  843. /**
  844. * Handle Rule and Space command
  845. * @param {TexParser} parser The calling parser.
  846. * @param {string} name The macro name.
  847. * @param {string} style The style of the rule spacer.
  848. */
  849. BaseMethods.Rule = function(parser: TexParser, name: string, style: string) {
  850. // @test Rule 3D, Space 3D
  851. const w = parser.GetDimen(name),
  852. h = parser.GetDimen(name),
  853. d = parser.GetDimen(name);
  854. let def: EnvList = {width: w, height: h, depth: d};
  855. if (style !== 'blank') {
  856. def['mathbackground'] = (parser.stack.env['color'] || 'black');
  857. }
  858. const node = parser.create('node', 'mspace', [], def);
  859. parser.Push(node);
  860. };
  861. /**
  862. * Handle rule command.
  863. * @param {TexParser} parser The calling parser.
  864. * @param {string} name The macro name.
  865. */
  866. BaseMethods.rule = function(parser: TexParser, name: string) {
  867. // @test Rule 2D
  868. const v = parser.GetBrackets(name),
  869. w = parser.GetDimen(name),
  870. h = parser.GetDimen(name);
  871. let mml = parser.create('node', 'mspace', [], {
  872. width: w, height: h,
  873. mathbackground: (parser.stack.env['color'] || 'black') });
  874. if (v) {
  875. mml = parser.create('node', 'mpadded', [mml], {voffset: v});
  876. if (v.match(/^\-/)) {
  877. NodeUtil.setAttribute(mml, 'height', v);
  878. NodeUtil.setAttribute(mml, 'depth', '+' + v.substr(1));
  879. } else {
  880. NodeUtil.setAttribute(mml, 'height', '+' + v);
  881. }
  882. }
  883. parser.Push(mml);
  884. };
  885. /**
  886. * Handle big command sequences, e.g., \\big, \\Bigg.
  887. * @param {TexParser} parser The calling parser.
  888. * @param {string} name The macro name.
  889. * @param {number} mclass The TeX class of the element.
  890. * @param {number} size The em size.
  891. */
  892. BaseMethods.MakeBig = function(parser: TexParser, name: string, mclass: number, size: number) {
  893. // @test Choose, Over With Delims, Above With Delims
  894. size *= P_HEIGHT;
  895. let sizeStr = String(size).replace(/(\.\d\d\d).+/, '$1') + 'em';
  896. const delim = parser.GetDelimiter(name, true);
  897. const mo = parser.create('token', 'mo', {
  898. minsize: sizeStr, maxsize: sizeStr,
  899. fence: true, stretchy: true, symmetric: true
  900. }, delim);
  901. const node = parser.create('node', 'TeXAtom', [mo], {texClass: mclass});
  902. parser.Push(node);
  903. };
  904. /**
  905. * Handle buildrel command.
  906. * @param {TexParser} parser The calling parser.
  907. * @param {string} name The macro name.
  908. */
  909. BaseMethods.BuildRel = function(parser: TexParser, name: string) {
  910. // @test BuildRel, BuildRel Expression
  911. const top = parser.ParseUpTo(name, '\\over');
  912. const bot = parser.ParseArg(name);
  913. const node = parser.create('node', 'munderover');
  914. // This is necessary to get the empty element into the children.
  915. NodeUtil.setChild(node, 0, bot);
  916. NodeUtil.setChild(node, 1, null);
  917. NodeUtil.setChild(node, 2, top);
  918. const atom = parser.create('node', 'TeXAtom', [node], {texClass: TEXCLASS.REL});
  919. parser.Push(atom);
  920. };
  921. /**
  922. * Handle horizontal boxes.
  923. * @param {TexParser} parser The calling parser.
  924. * @param {string} name The macro name.
  925. * @param {string} style Box style.
  926. * @param {string} font The mathvariant to use
  927. */
  928. BaseMethods.HBox = function(parser: TexParser, name: string, style: string, font?: string) {
  929. // @test Hbox
  930. parser.PushAll(ParseUtil.internalMath(parser, parser.GetArgument(name), style, font));
  931. };
  932. /**
  933. * Handle framed boxes.
  934. * @param {TexParser} parser The calling parser.
  935. * @param {string} name The macro name.
  936. */
  937. BaseMethods.FBox = function(parser: TexParser, name: string) {
  938. // @test Fbox
  939. const internal = ParseUtil.internalMath(parser, parser.GetArgument(name));
  940. const node = parser.create('node', 'menclose', internal, {notation: 'box'});
  941. parser.Push(node);
  942. };
  943. /**
  944. * Handle framed boxes with options.
  945. * @param {TexParser} parser The calling parser.
  946. * @param {string} name The macro name.
  947. */
  948. BaseMethods.FrameBox = function(parser: TexParser, name: string) {
  949. const width = parser.GetBrackets(name);
  950. const pos = parser.GetBrackets(name) || 'c';
  951. let mml = ParseUtil.internalMath(parser, parser.GetArgument(name));
  952. if (width) {
  953. mml = [parser.create('node', 'mpadded', mml, {
  954. width,
  955. 'data-align': lookup(pos, {l: 'left', r: 'right'}, 'center')
  956. })];
  957. }
  958. const node = parser.create('node', 'TeXAtom',
  959. [parser.create('node', 'menclose', mml, {notation: 'box'})],
  960. {texClass: TEXCLASS.ORD});
  961. parser.Push(node);
  962. };
  963. /**
  964. * Handle \\not.
  965. * @param {TexParser} parser The calling parser.
  966. * @param {string} name The macro name.
  967. */
  968. BaseMethods.Not = function(parser: TexParser, _name: string) {
  969. // @test Negation Simple, Negation Complex, Negation Explicit,
  970. // Negation Large
  971. parser.Push(parser.itemFactory.create('not'));
  972. };
  973. /**
  974. * Handle dots.
  975. * @param {TexParser} parser The calling parser.
  976. * @param {string} name The macro name.
  977. */
  978. BaseMethods.Dots = function(parser: TexParser, _name: string) {
  979. // @test Operator Dots
  980. const ldotsEntity = NodeUtil.createEntity('2026');
  981. const cdotsEntity = NodeUtil.createEntity('22EF');
  982. const ldots = parser.create('token', 'mo', {stretchy: false}, ldotsEntity);
  983. const cdots = parser.create('token', 'mo', {stretchy: false}, cdotsEntity);
  984. parser.Push(
  985. parser.itemFactory.create('dots').setProperties({
  986. ldots: ldots,
  987. cdots: cdots
  988. }) );
  989. };
  990. /**
  991. * Handle small matrix environments.
  992. * @param {TexParser} parser The calling parser.
  993. * @param {string} name The macro name.
  994. * @param {string} open Opening fence.
  995. * @param {string} close Closing fence.
  996. * @param {string} align Column alignment.
  997. * @param {string} spacing Column spacing.
  998. * @param {string} vspacing Row spacing.
  999. * @param {string} style Display or text style.
  1000. * @param {boolean} cases Is it a cases environment.
  1001. * @param {boolean} numbered Is it a numbered environment.
  1002. */
  1003. BaseMethods.Matrix = function(parser: TexParser, _name: string,
  1004. open: string, close: string, align: string,
  1005. spacing: string, vspacing: string, style: string,
  1006. cases: boolean, numbered: boolean) {
  1007. const c = parser.GetNext();
  1008. if (c === '') {
  1009. // @test Matrix Error
  1010. throw new TexError('MissingArgFor', 'Missing argument for %1', parser.currentCS);
  1011. }
  1012. if (c === '{') {
  1013. // @test Matrix Braces, Matrix Columns, Matrix Rows.
  1014. parser.i++;
  1015. } else {
  1016. // @test Matrix Arg
  1017. parser.string = c + '}' + parser.string.slice(parser.i + 1);
  1018. parser.i = 0;
  1019. }
  1020. // @test Matrix Braces, Matrix Columns, Matrix Rows.
  1021. const array = parser.itemFactory.create('array').setProperty('requireClose', true) as sitem.ArrayItem;
  1022. array.arraydef = {
  1023. rowspacing: (vspacing || '4pt'),
  1024. columnspacing: (spacing || '1em')
  1025. };
  1026. if (cases) {
  1027. // @test Matrix Cases
  1028. array.setProperty('isCases', true);
  1029. }
  1030. if (numbered) {
  1031. // @test Matrix Numbered
  1032. array.setProperty('isNumbered', true);
  1033. array.arraydef.side = numbered;
  1034. }
  1035. if (open || close) {
  1036. // @test Matrix Parens, Matrix Parens Subscript, Matrix Cases
  1037. array.setProperty('open', open);
  1038. array.setProperty('close', close);
  1039. }
  1040. if (style === 'D') {
  1041. // @test Matrix Numbered
  1042. array.arraydef.displaystyle = true;
  1043. }
  1044. if (align != null) {
  1045. // @test Matrix Cases, Matrix Numbered
  1046. array.arraydef.columnalign = align;
  1047. }
  1048. parser.Push(array);
  1049. };
  1050. /**
  1051. * Handle array entry.
  1052. * @param {TexParser} parser The calling parser.
  1053. * @param {string} name The macro name.
  1054. */
  1055. BaseMethods.Entry = function(parser: TexParser, name: string) {
  1056. // @test Label, Array, Cross Product Formula
  1057. parser.Push(parser.itemFactory.create('cell').setProperties({isEntry: true, name: name}));
  1058. const top = parser.stack.Top();
  1059. const env = top.getProperty('casesEnv') as string;
  1060. const cases = top.getProperty('isCases');
  1061. if (!cases && !env) return;
  1062. //
  1063. // Make second column be in \text{...} (unless it is already
  1064. // in a \text{...}, for backward compatibility).
  1065. //
  1066. const str = parser.string;
  1067. let braces = 0, close = -1, i = parser.i, m = str.length;
  1068. const end = (env ? new RegExp(`^\\\\end\\s*\\{${env.replace(/\*/, '\\*')}\\}`) : null);
  1069. //
  1070. // Look through the string character by character...
  1071. //
  1072. while (i < m) {
  1073. const c = str.charAt(i);
  1074. if (c === '{') {
  1075. //
  1076. // Increase the nested brace count and go on
  1077. //
  1078. braces++;
  1079. i++;
  1080. } else if (c === '}') {
  1081. //
  1082. // If there are too many close braces, just end (we will get an
  1083. // error message later when the rest of the string is parsed)
  1084. // Otherwise
  1085. // decrease the nested brace count,
  1086. // if it is now zero and we haven't already marked the end of the
  1087. // first brace group, record the position (use to check for \text{} later)
  1088. // go on to the next character.
  1089. //
  1090. if (braces === 0) {
  1091. m = 0;
  1092. } else {
  1093. braces--;
  1094. if (braces === 0 && close < 0) {
  1095. close = i - parser.i;
  1096. }
  1097. i++;
  1098. }
  1099. } else if (c === '&' && braces === 0) {
  1100. //
  1101. // Extra alignment tabs are not allowed in cases
  1102. //
  1103. // @test ExtraAlignTab
  1104. throw new TexError('ExtraAlignTab', 'Extra alignment tab in \\cases text');
  1105. } else if (c === '\\') {
  1106. //
  1107. // If the macro is \cr or \\, end the search, otherwise skip the macro
  1108. // (multi-letter names don't matter, as we will skip the rest of the
  1109. // characters in the main loop)
  1110. //
  1111. const rest = str.substr(i);
  1112. if (rest.match(/^((\\cr)[^a-zA-Z]|\\\\)/) || (end && rest.match(end))) {
  1113. m = 0;
  1114. } else {
  1115. i += 2;
  1116. }
  1117. } else {
  1118. //
  1119. // Go on to the next character
  1120. //
  1121. i++;
  1122. }
  1123. }
  1124. //
  1125. // Check if the second column text is already in \text{};
  1126. // If not, process the second column as text and continue parsing from there,
  1127. // (otherwise process the second column as normal, since it is in \text{}
  1128. //
  1129. const text = str.substr(parser.i, i - parser.i);
  1130. if (!text.match(/^\s*\\text[^a-zA-Z]/) || close !== text.replace(/\s+$/, '').length - 1) {
  1131. const internal = ParseUtil.internalMath(parser, ParseUtil.trimSpaces(text), 0);
  1132. parser.PushAll(internal);
  1133. parser.i = i;
  1134. }
  1135. };
  1136. /**
  1137. * Handle newline in array.
  1138. * @param {TexParser} parser The calling parser.
  1139. * @param {string} name The macro name.
  1140. */
  1141. BaseMethods.Cr = function(parser: TexParser, name: string) {
  1142. // @test Cr Linebreak, Misplaced Cr
  1143. parser.Push(
  1144. parser.itemFactory.create('cell').setProperties({isCR: true, name: name}));
  1145. };
  1146. /**
  1147. * Handle newline outside array.
  1148. * @param {TexParser} parser The calling parser.
  1149. * @param {string} name The macro name.
  1150. * @param {boolean} nobrackets Flag indicating if newline is followed by
  1151. * brackets.
  1152. */
  1153. BaseMethods.CrLaTeX = function(parser: TexParser, name: string, nobrackets: boolean = false) {
  1154. let n: string;
  1155. if (!nobrackets) {
  1156. // TODO: spaces before * and [ are not allowed in AMS environments like align, but
  1157. // should be allowed in array and eqnarray. This distinction should be honored here.
  1158. if (parser.string.charAt(parser.i) === '*') { // The * controls page breaking, so ignore it
  1159. parser.i++;
  1160. }
  1161. if (parser.string.charAt(parser.i) === '[') {
  1162. let dim = parser.GetBrackets(name, '');
  1163. let [value, unit, ] = ParseUtil.matchDimen(dim);
  1164. // @test Custom Linebreak
  1165. if (dim && !value) {
  1166. // @test Dimension Error
  1167. throw new TexError('BracketMustBeDimension',
  1168. 'Bracket argument to %1 must be a dimension', parser.currentCS);
  1169. }
  1170. n = value + unit;
  1171. }
  1172. }
  1173. parser.Push(
  1174. parser.itemFactory.create('cell').setProperties({isCR: true, name: name, linebreak: true})
  1175. );
  1176. const top = parser.stack.Top();
  1177. let node: MmlNode;
  1178. if (top instanceof sitem.ArrayItem) {
  1179. // @test Array
  1180. if (n) {
  1181. top.addRowSpacing(n);
  1182. }
  1183. } else {
  1184. if (n) {
  1185. // @test Custom Linebreak
  1186. node = parser.create('node', 'mspace', [], {depth: n});
  1187. parser.Push(node);
  1188. }
  1189. // @test Linebreak
  1190. node = parser.create('node', 'mspace', [], {linebreak: TexConstant.LineBreak.NEWLINE});
  1191. parser.Push(node);
  1192. }
  1193. };
  1194. /**
  1195. * Handle horizontal lines in arrays.
  1196. * @param {TexParser} parser The calling parser.
  1197. * @param {string} name The macro name.
  1198. * @param {string} style Style of the line. E.g., dashed.
  1199. */
  1200. BaseMethods.HLine = function(parser: TexParser, _name: string, style: string) {
  1201. if (style == null) {
  1202. style = 'solid';
  1203. }
  1204. const top = parser.stack.Top();
  1205. if (!(top instanceof sitem.ArrayItem) || top.Size()) {
  1206. // @test Misplaced hline
  1207. throw new TexError('Misplaced', 'Misplaced %1', parser.currentCS);
  1208. }
  1209. if (!top.table.length) {
  1210. // @test Enclosed top, Enclosed top bottom
  1211. top.frame.push('top');
  1212. } else {
  1213. // @test Enclosed bottom, Enclosed top bottom
  1214. const lines = (top.arraydef['rowlines'] ? (top.arraydef['rowlines'] as string).split(/ /) : []);
  1215. while (lines.length < top.table.length) {
  1216. lines.push('none');
  1217. }
  1218. lines[top.table.length - 1] = style;
  1219. top.arraydef['rowlines'] = lines.join(' ');
  1220. }
  1221. };
  1222. /**
  1223. * Handle hfill commands.
  1224. * @param {TexParser} parser The calling parser.
  1225. * @param {string} name The macro name.
  1226. */
  1227. BaseMethods.HFill = function(parser: TexParser, _name: string) {
  1228. const top = parser.stack.Top();
  1229. if (top instanceof sitem.ArrayItem) {
  1230. // @test Hfill
  1231. top.hfill.push(top.Size());
  1232. } else {
  1233. // @test UnsupportedHFill
  1234. throw new TexError('UnsupportedHFill', 'Unsupported use of %1', parser.currentCS);
  1235. }
  1236. };
  1237. /**
  1238. * LaTeX environments
  1239. */
  1240. /**
  1241. * Handle begin and end environments. This is a macro method.
  1242. * @param {TexParser} parser The calling parser.
  1243. * @param {string} name The macro name.
  1244. */
  1245. BaseMethods.BeginEnd = function(parser: TexParser, name: string) {
  1246. // @test Array1, Array2, Array Test
  1247. let env = parser.GetArgument(name);
  1248. if (env.match(/\\/i)) {
  1249. // @test InvalidEnv
  1250. throw new TexError('InvalidEnv', 'Invalid environment name \'%1\'', env);
  1251. }
  1252. let macro = parser.configuration.handlers.get('environment').lookup(env) as Macro;
  1253. if (macro && name === '\\end') {
  1254. // If the first argument is true, we have some sort of user defined
  1255. // environment. Otherwise we have a standard LaTeX environment that is
  1256. // handled with begin and end items.
  1257. if (!macro.args[0]) {
  1258. const mml = parser.itemFactory.create('end').setProperty('name', env);
  1259. parser.Push(mml);
  1260. return;
  1261. }
  1262. // Remember the user defined environment we are closing.
  1263. parser.stack.env['closing'] = env;
  1264. }
  1265. ParseUtil.checkMaxMacros(parser, false);
  1266. parser.parse('environment', [parser, env]);
  1267. };
  1268. /**
  1269. * Handle array environment.
  1270. * @param {TexParser} parser The calling parser.
  1271. * @param {StackItem} begin The opening stackitem.
  1272. * @param {string} open Opening fence.
  1273. * @param {string} close Closing fence.
  1274. * @param {string} align Column alignment.
  1275. * @param {string} spacing Column spacing.
  1276. * @param {string} vspacing Row spacing.
  1277. * @param {string} style Display or text style.
  1278. * @param {boolean} raggedHeight Does the height need to be adjusted?
  1279. */
  1280. BaseMethods.Array = function(parser: TexParser, begin: StackItem,
  1281. open: string, close: string, align: string,
  1282. spacing: string, vspacing: string, style: string,
  1283. raggedHeight: boolean) {
  1284. if (!align) {
  1285. // @test Array Single
  1286. align = parser.GetArgument('\\begin{' + begin.getName() + '}');
  1287. }
  1288. let lines = ('c' + align).replace(/[^clr|:]/g, '').replace(/[^|:]([|:])+/g, '$1');
  1289. align = align.replace(/[^clr]/g, '').split('').join(' ');
  1290. align = align.replace(/l/g, 'left').replace(/r/g, 'right').replace(/c/g, 'center');
  1291. const array = parser.itemFactory.create('array') as sitem.ArrayItem;
  1292. array.arraydef = {
  1293. columnalign: align,
  1294. columnspacing: (spacing || '1em'),
  1295. rowspacing: (vspacing || '4pt')
  1296. };
  1297. if (lines.match(/[|:]/)) {
  1298. // @test Enclosed left right
  1299. if (lines.charAt(0).match(/[|:]/)) {
  1300. // @test Enclosed left right, Enclosed left
  1301. array.frame.push('left');
  1302. array.dashed = lines.charAt(0) === ':';
  1303. }
  1304. if (lines.charAt(lines.length - 1).match(/[|:]/)) {
  1305. // @test Enclosed left right, Enclosed right
  1306. array.frame.push('right');
  1307. }
  1308. // @test Enclosed left right
  1309. lines = lines.substr(1, lines.length - 2);
  1310. array.arraydef.columnlines =
  1311. lines.split('').join(' ').replace(/[^|: ]/g, 'none').replace(/\|/g, 'solid').replace(/:/g, 'dashed');
  1312. }
  1313. if (open) {
  1314. // @test Cross Product
  1315. array.setProperty('open', parser.convertDelimiter(open));
  1316. }
  1317. if (close) {
  1318. // @test Cross Product
  1319. array.setProperty('close', parser.convertDelimiter(close));
  1320. }
  1321. if ((style || '').charAt(1) === '\'') {
  1322. array.arraydef['data-cramped'] = true;
  1323. style = style.charAt(0);
  1324. }
  1325. if (style === 'D') {
  1326. // TODO: This case never seems to occur! No test.
  1327. array.arraydef['displaystyle'] = true;
  1328. }
  1329. else if (style) {
  1330. // @test Subarray, Small Matrix
  1331. array.arraydef['displaystyle'] = false;
  1332. }
  1333. if (style === 'S') {
  1334. // @test Subarray, Small Matrix
  1335. array.arraydef['scriptlevel'] = 1;
  1336. }
  1337. if (raggedHeight) {
  1338. // @test Subarray, Small Matrix
  1339. array.arraydef['useHeight'] = false;
  1340. }
  1341. parser.Push(begin);
  1342. return array;
  1343. };
  1344. /**
  1345. * Handle aligned arrays.
  1346. * @param {TexParser} parser The calling parser.
  1347. * @param {StackItem} begin The opening stackitem.
  1348. */
  1349. BaseMethods.AlignedArray = function(parser: TexParser, begin: StackItem) {
  1350. // @test Array1, Array2, Array Test
  1351. const align = parser.GetBrackets('\\begin{' + begin.getName() + '}');
  1352. let item = BaseMethods.Array(parser, begin);
  1353. return ParseUtil.setArrayAlign(item as sitem.ArrayItem, align);
  1354. };
  1355. /**
  1356. * Handle equation environment.
  1357. * @param {TexParser} parser The calling parser.
  1358. * @param {StackItem} begin The opening stackitem.
  1359. * @param {boolean} numbered True if environment is numbered.
  1360. */
  1361. BaseMethods.Equation = function (parser: TexParser, begin: StackItem, numbered: boolean) {
  1362. parser.Push(begin);
  1363. ParseUtil.checkEqnEnv(parser);
  1364. return parser.itemFactory.create('equation', numbered).
  1365. setProperty('name', begin.getName());
  1366. };
  1367. /**
  1368. * Handle eqnarray.
  1369. * @param {TexParser} parser The calling parser.
  1370. * @param {StackItem} begin The opening stackitem.
  1371. * @param {boolean} numbered True if environment is numbered.
  1372. * @param {boolean} taggable True if taggable.
  1373. * @param {string} align Alignment string.
  1374. * @param {string} spacing Spacing between columns.
  1375. */
  1376. BaseMethods.EqnArray = function(parser: TexParser, begin: StackItem,
  1377. numbered: boolean, taggable: boolean,
  1378. align: string, spacing: string) {
  1379. // @test The Lorenz Equations, Maxwell's Equations, Cubic Binomial
  1380. parser.Push(begin);
  1381. if (taggable) {
  1382. ParseUtil.checkEqnEnv(parser);
  1383. }
  1384. align = align.replace(/[^clr]/g, '').split('').join(' ');
  1385. align = align.replace(/l/g, 'left').replace(/r/g, 'right').replace(/c/g, 'center');
  1386. let newItem = parser.itemFactory.create('eqnarray', begin.getName(),
  1387. numbered, taggable, parser.stack.global) as sitem.ArrayItem;
  1388. newItem.arraydef = {
  1389. displaystyle: true,
  1390. columnalign: align,
  1391. columnspacing: (spacing || '1em'),
  1392. rowspacing: '3pt',
  1393. side: parser.options['tagSide'],
  1394. minlabelspacing: parser.options['tagIndent']
  1395. };
  1396. return newItem;
  1397. };
  1398. /**
  1399. * Handles no tag commands.
  1400. * @param {TexParser} parser The calling parser.
  1401. * @param {string} name The macro name.
  1402. */
  1403. BaseMethods.HandleNoTag = function(parser: TexParser, _name: string) {
  1404. parser.tags.notag();
  1405. };
  1406. /**
  1407. * Record a label name for a tag
  1408. * @param {TexParser} parser The calling parser.
  1409. * @param {string} name The macro name.
  1410. */
  1411. BaseMethods.HandleLabel = function(parser: TexParser, name: string) {
  1412. // @test Label, Label Empty
  1413. let label = parser.GetArgument(name);
  1414. if (label === '') {
  1415. // @test Label Empty
  1416. return;
  1417. }
  1418. if (!parser.tags.refUpdate) {
  1419. // @test Label, Ref, Ref Unknown
  1420. if (parser.tags.label) {
  1421. // @test Double Label Error
  1422. throw new TexError('MultipleCommand', 'Multiple %1', parser.currentCS);
  1423. }
  1424. parser.tags.label = label;
  1425. if ((parser.tags.allLabels[label] || parser.tags.labels[label]) && !parser.options['ignoreDuplicateLabels']) {
  1426. // @ Duplicate Label Error
  1427. throw new TexError('MultipleLabel', 'Label \'%1\' multiply defined', label);
  1428. }
  1429. // TODO: This should be set in the tags structure!
  1430. parser.tags.labels[label] = new Label(); // will be replaced by tag value later
  1431. }
  1432. };
  1433. /**
  1434. * Handle a label reference.
  1435. * @param {TexParser} parser The calling parser.
  1436. * @param {string} name The macro name.
  1437. * @param {boolean} eqref True if formatted as eqref.
  1438. */
  1439. BaseMethods.HandleRef = function(parser: TexParser, name: string, eqref: boolean) {
  1440. // @test Ref, Ref Unknown, Eqref, Ref Default, Ref Named
  1441. let label = parser.GetArgument(name);
  1442. let ref = parser.tags.allLabels[label] || parser.tags.labels[label];
  1443. if (!ref) {
  1444. // @test Ref Unknown
  1445. if (!parser.tags.refUpdate) {
  1446. parser.tags.redo = true;
  1447. }
  1448. ref = new Label();
  1449. }
  1450. let tag = ref.tag;
  1451. if (eqref) {
  1452. // @test Eqref
  1453. tag = parser.tags.formatTag(tag);
  1454. }
  1455. let node = parser.create('node', 'mrow', ParseUtil.internalMath(parser, tag), {
  1456. href: parser.tags.formatUrl(ref.id, parser.options.baseURL), 'class': 'MathJax_ref'
  1457. });
  1458. parser.Push(node);
  1459. };
  1460. /**
  1461. * Macros
  1462. */
  1463. BaseMethods.Macro = function(parser: TexParser, name: string,
  1464. macro: string, argcount: number,
  1465. def?: string) {
  1466. if (argcount) {
  1467. const args: string[] = [];
  1468. if (def != null) {
  1469. const optional = parser.GetBrackets(name);
  1470. args.push(optional == null ? def : optional);
  1471. }
  1472. for (let i = args.length; i < argcount; i++) {
  1473. args.push(parser.GetArgument(name));
  1474. }
  1475. macro = ParseUtil.substituteArgs(parser, args, macro);
  1476. }
  1477. parser.string = ParseUtil.addArgs(parser, macro, parser.string.slice(parser.i));
  1478. parser.i = 0;
  1479. ParseUtil.checkMaxMacros(parser);
  1480. };
  1481. /**
  1482. * Handle MathChoice for elements whose exact size/style properties can only be
  1483. * determined after the expression has been parsed.
  1484. * @param {TexParser} parser The calling parser.
  1485. * @param {string} name The macro name.
  1486. */
  1487. BaseMethods.MathChoice = function(parser: TexParser, name: string) {
  1488. const D = parser.ParseArg(name);
  1489. const T = parser.ParseArg(name);
  1490. const S = parser.ParseArg(name);
  1491. const SS = parser.ParseArg(name);
  1492. parser.Push(parser.create('node', 'MathChoice', [D, T, S, SS]));
  1493. };
  1494. export default BaseMethods;