1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618 |
- /*************************************************************
- *
- * Copyright (c) 2017-2022 The MathJax Consortium
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- /**
- * @fileoverview The Basic Parse methods.
- *
- * @author v.sorge@mathjax.org (Volker Sorge)
- */
- import * as sitem from './BaseItems.js';
- import {StackItem, EnvList} from '../StackItem.js';
- import {Macro} from '../Symbol.js';
- import {ParseMethod} from '../Types.js';
- import NodeUtil from '../NodeUtil.js';
- import TexError from '../TexError.js';
- import TexParser from '../TexParser.js';
- import {TexConstant} from '../TexConstants.js';
- import ParseUtil from '../ParseUtil.js';
- import {MmlNode, TEXCLASS} from '../../../core/MmlTree/MmlNode.js';
- import {MmlMsubsup} from '../../../core/MmlTree/MmlNodes/msubsup.js';
- import {MmlMunderover} from '../../../core/MmlTree/MmlNodes/munderover.js';
- import {Label} from '../Tags.js';
- import {em} from '../../../util/lengths.js';
- import {entities} from '../../../util/Entities.js';
- import {lookup} from '../../../util/Options.js';
- // Namespace
- let BaseMethods: Record<string, ParseMethod> = {};
- const P_HEIGHT = 1.2 / .85; // cmex10 height plus depth over .85
- const MmlTokenAllow: {[key: string]: number} = {
- fontfamily: 1, fontsize: 1, fontweight: 1, fontstyle: 1,
- color: 1, background: 1,
- id: 1, 'class': 1, href: 1, style: 1
- };
- /**
- * Handle LaTeX tokens.
- */
- /**
- * Handle {
- * @param {TexParser} parser The calling parser.
- * @param {string} c The parsed character.
- */
- BaseMethods.Open = function(parser: TexParser, _c: string) {
- // @test Identifier Font, Prime, Prime with subscript
- parser.Push(parser.itemFactory.create('open'));
- };
- /**
- * Handle }
- * @param {TexParser} parser The calling parser.
- * @param {string} c The parsed character.
- */
- BaseMethods.Close = function(parser: TexParser, _c: string) {
- // @test Identifier Font, Prime, Prime with subscript
- parser.Push(parser.itemFactory.create('close'));
- };
- /**
- * Handle tilde and spaces.
- * @param {TexParser} parser The calling parser.
- * @param {string} c The parsed character.
- */
- BaseMethods.Tilde = function(parser: TexParser, _c: string) {
- // @test Tilde, Tilde2
- parser.Push(parser.create('token', 'mtext', {}, entities.nbsp));
- };
- /**
- * Handling space, by doing nothing.
- * @param {TexParser} parser The calling parser.
- * @param {string} c The parsed character.
- */
- BaseMethods.Space = function(_parser: TexParser, _c: string) {};
- /**
- * Handle ^
- * @param {TexParser} parser The calling parser.
- * @param {string} c The parsed character.
- */
- BaseMethods.Superscript = function(parser: TexParser, _c: string) {
- if (parser.GetNext().match(/\d/)) {
- // don't treat numbers as a unit
- parser.string = parser.string.substr(0, parser.i + 1) +
- ' ' + parser.string.substr(parser.i + 1);
- }
- let primes: MmlNode;
- let base: MmlNode | void;
- const top = parser.stack.Top();
- if (top.isKind('prime')) {
- // @test Prime on Prime
- [base, primes] = top.Peek(2);
- parser.stack.Pop();
- } else {
- // @test Empty base2, Square, Cube
- base = parser.stack.Prev();
- if (!base) {
- // @test Empty base
- base = parser.create('token', 'mi', {}, '');
- }
- }
- const movesupsub = NodeUtil.getProperty(base, 'movesupsub');
- let position = NodeUtil.isType(base, 'msubsup') ? (base as MmlMsubsup).sup :
- (base as MmlMunderover).over;
- if ((NodeUtil.isType(base, 'msubsup') && !NodeUtil.isType(base, 'msup') &&
- NodeUtil.getChildAt(base, (base as MmlMsubsup).sup)) ||
- (NodeUtil.isType(base, 'munderover') && !NodeUtil.isType(base, 'mover') &&
- NodeUtil.getChildAt(base, (base as MmlMunderover).over) &&
- !NodeUtil.getProperty(base, 'subsupOK'))) {
- // @test Double-super-error, Double-over-error
- throw new TexError('DoubleExponent', 'Double exponent: use braces to clarify');
- }
- if (!NodeUtil.isType(base, 'msubsup') || NodeUtil.isType(base, 'msup')) {
- if (movesupsub) {
- // @test Move Superscript, Large Operator
- if (!NodeUtil.isType(base, 'munderover') || NodeUtil.isType(base, 'mover') ||
- NodeUtil.getChildAt(base, (base as MmlMunderover).over)) {
- // @test Large Operator
- base = parser.create('node', 'munderover', [base], {movesupsub: true});
- }
- position = (base as MmlMunderover).over;
- } else {
- // @test Empty base, Empty base2, Square, Cube
- base = parser.create('node', 'msubsup', [base]);
- position = (base as MmlMsubsup).sup;
- }
- }
- parser.Push(
- parser.itemFactory.create('subsup', base).setProperties({
- position: position, primes: primes, movesupsub: movesupsub
- }) );
- };
- /**
- * Handle _
- * @param {TexParser} parser The calling parser.
- * @param {string} c The parsed character.
- */
- BaseMethods.Subscript = function(parser: TexParser, _c: string) {
- if (parser.GetNext().match(/\d/)) {
- // don't treat numbers as a unit
- parser.string =
- parser.string.substr(0, parser.i + 1) + ' ' +
- parser.string.substr(parser.i + 1);
- }
- let primes, base;
- const top = parser.stack.Top();
- if (top.isKind('prime')) {
- // @test Prime on Sub
- [base, primes] = top.Peek(2);
- parser.stack.Pop();
- } else {
- base = parser.stack.Prev();
- if (!base) {
- // @test Empty Base Index
- base = parser.create('token', 'mi', {}, '');
- }
- }
- const movesupsub = NodeUtil.getProperty(base, 'movesupsub');
- let position = NodeUtil.isType(base, 'msubsup') ?
- (base as MmlMsubsup).sub : (base as MmlMunderover).under;
- if ((NodeUtil.isType(base, 'msubsup') && !NodeUtil.isType(base, 'msup') &&
- NodeUtil.getChildAt(base, (base as MmlMsubsup).sub)) ||
- (NodeUtil.isType(base, 'munderover') && !NodeUtil.isType(base, 'mover') &&
- NodeUtil.getChildAt(base, (base as MmlMunderover).under) &&
- !NodeUtil.getProperty(base, 'subsupOK'))) {
- // @test Double-sub-error, Double-under-error
- throw new TexError('DoubleSubscripts', 'Double subscripts: use braces to clarify');
- }
- if (!NodeUtil.isType(base, 'msubsup') || NodeUtil.isType(base, 'msup')) {
- if (movesupsub) {
- // @test Large Operator, Move Superscript
- if (!NodeUtil.isType(base, 'munderover') || NodeUtil.isType(base, 'mover') ||
- NodeUtil.getChildAt(base, (base as MmlMunderover).under)) {
- // @test Move Superscript
- base = parser.create('node', 'munderover', [base], {movesupsub: true});
- }
- position = (base as MmlMunderover).under;
- } else {
- // @test Empty Base Index, Empty Base Index2, Index
- base = parser.create('node', 'msubsup', [base]);
- position = (base as MmlMsubsup).sub;
- }
- }
- parser.Push(
- parser.itemFactory.create('subsup', base).setProperties({
- position: position, primes: primes, movesupsub: movesupsub
- }) );
- };
- /**
- * Handle '
- * @param {TexParser} parser The calling parser.
- * @param {string} c The parsed character.
- */
- BaseMethods.Prime = function(parser: TexParser, c: string) {
- // @test Prime
- let base = parser.stack.Prev();
- if (!base) {
- // @test PrimeSup, PrePrime, Prime on Sup
- base = parser.create('node', 'mi');
- }
- if (NodeUtil.isType(base, 'msubsup') && !NodeUtil.isType(base, 'msup') &&
- NodeUtil.getChildAt(base, (base as MmlMsubsup).sup)) {
- // @test Double Prime Error
- throw new TexError('DoubleExponentPrime',
- 'Prime causes double exponent: use braces to clarify');
- }
- let sup = '';
- parser.i--;
- do {
- // @test Prime, PrimeSup, Double Prime, PrePrime
- sup += entities.prime; parser.i++, c = parser.GetNext();
- } while (c === '\'' || c === entities.rsquo);
- sup = ['', '\u2032', '\u2033', '\u2034', '\u2057'][sup.length] || sup;
- const node = parser.create('token', 'mo', {variantForm: true}, sup);
- parser.Push(
- parser.itemFactory.create('prime', base, node) );
- };
- /**
- * Handle comments
- * @param {TexParser} parser The calling parser.
- * @param {string} c The parsed character.
- */
- BaseMethods.Comment = function(parser: TexParser, _c: string) {
- while (parser.i < parser.string.length && parser.string.charAt(parser.i) !== '\n') {
- parser.i++;
- }
- };
- /**
- * Handle hash marks outside of definitions
- * @param {TexParser} parser The calling parser.
- * @param {string} c The parsed character.
- */
- BaseMethods.Hash = function(_parser: TexParser, _c: string) {
- // @test Hash Error
- throw new TexError('CantUseHash1',
- 'You can\'t use \'macro parameter character #\' in math mode');
- };
- /**
- *
- * Handle LaTeX Macros
- *
- */
- /**
- * Handle \mathrm, \mathbf, etc, allowing for multi-letter runs to be one <mi>.
- */
- BaseMethods.MathFont = function(parser: TexParser, name: string, variant: string) {
- const text = parser.GetArgument(name);
- let mml = new TexParser(text, {
- ...parser.stack.env,
- font: variant,
- multiLetterIdentifiers: /^[a-zA-Z]+/ as any,
- noAutoOP: true
- }, parser.configuration).mml();
- parser.Push(parser.create('node', 'TeXAtom', [mml]));
- };
- /**
- * Setting font, e.g., via \\rm, \\bf etc.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- * @param {string} font The font name.
- */
- BaseMethods.SetFont = function(parser: TexParser, _name: string, font: string) {
- parser.stack.env['font'] = font;
- };
- /**
- * Setting style, e.g., via \\displaystyle, \\textstyle, etc.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- * @param {string} texStyle The tex style name: D, T, S, SS
- * @param {boolean} style True if we are in displaystyle.
- * @param {string} level The nesting level for scripts.
- */
- BaseMethods.SetStyle = function(parser: TexParser, _name: string,
- texStyle: string, style: boolean,
- level: string) {
- parser.stack.env['style'] = texStyle;
- parser.stack.env['level'] = level;
- parser.Push(
- parser.itemFactory.create('style').setProperty(
- 'styles', {displaystyle: style, scriptlevel: level}));
- };
- /**
- * Setting size of an expression, e.g., \\small, \\huge.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- * @param {number} size The size value.
- */
- BaseMethods.SetSize = function(parser: TexParser, _name: string, size: number) {
- parser.stack.env['size'] = size;
- parser.Push(
- parser.itemFactory.create('style').setProperty('styles', {mathsize: em(size)}));
- };
- /**
- * Setting explicit spaces, e.g., via commata or colons.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- * @param {string} space The space value.
- */
- BaseMethods.Spacer = function(parser: TexParser, _name: string, space: number) {
- // @test Positive Spacing, Negative Spacing
- const node = parser.create('node', 'mspace', [], {width: em(space)});
- const style = parser.create('node', 'mstyle', [node], {scriptlevel: 0});
- parser.Push(style);
- };
- /**
- * Parses left/right fenced expressions.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.LeftRight = function(parser: TexParser, name: string) {
- // @test Fenced, Fenced3
- const first = name.substr(1);
- parser.Push(parser.itemFactory.create(first, parser.GetDelimiter(name), parser.stack.env.color));
- };
- /**
- * Handle a named math function, e.g., \\sin, \\cos
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- * @param {string} id Alternative string representation of the function.
- */
- BaseMethods.NamedFn = function(parser: TexParser, name: string, id: string) {
- // @test Named Function
- if (!id) {
- id = name.substr(1);
- }
- const mml = parser.create('token', 'mi', {texClass: TEXCLASS.OP}, id);
- parser.Push(parser.itemFactory.create('fn', mml));
- };
- /**
- * Handle a named math operator, e.g., \\min, \\lim
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- * @param {string} id Alternative string representation of the operator.
- */
- BaseMethods.NamedOp = function(parser: TexParser, name: string, id: string) {
- // @test Limit
- if (!id) {
- id = name.substr(1);
- }
- id = id.replace(/ /, '\u2006');
- const mml = parser.create('token', 'mo', {
- movablelimits: true,
- movesupsub: true,
- form: TexConstant.Form.PREFIX,
- texClass: TEXCLASS.OP
- }, id);
- parser.Push(mml);
- };
- /**
- * Handle a limits command for math operators.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- * @param {string} limits The limits arguments.
- */
- BaseMethods.Limits = function(parser: TexParser, _name: string, limits: string) {
- // @test Limits
- let op = parser.stack.Prev(true);
- // Get the texclass for the core operator.
- if (!op || (NodeUtil.getTexClass(NodeUtil.getCoreMO(op)) !== TEXCLASS.OP &&
- NodeUtil.getProperty(op, 'movesupsub') == null)) {
- // @test Limits Error
- throw new TexError('MisplacedLimits', '%1 is allowed only on operators', parser.currentCS);
- }
- const top = parser.stack.Top();
- let node;
- if (NodeUtil.isType(op, 'munderover') && !limits) {
- // @test Limits UnderOver
- node = parser.create('node', 'msubsup');
- NodeUtil.copyChildren(op, node);
- op = top.Last = node;
- } else if (NodeUtil.isType(op, 'msubsup') && limits) {
- // @test Limits SubSup
- // node = parser.create('node', 'munderover', NodeUtil.getChildren(op), {});
- // Needs to be copied, otherwise we get an error in MmlNode.appendChild!
- node = parser.create('node', 'munderover');
- NodeUtil.copyChildren(op, node);
- op = top.Last = node;
- }
- NodeUtil.setProperty(op, 'movesupsub', limits ? true : false);
- NodeUtil.setProperties(NodeUtil.getCoreMO(op), {'movablelimits': false});
- if (NodeUtil.getAttribute(op, 'movablelimits') ||
- NodeUtil.getProperty(op, 'movablelimits')) {
- NodeUtil.setProperties(op, {'movablelimits': false});
- }
- };
- /**
- * Handle over commands.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- * @param {string} open The open delimiter in case of a "withdelim" version.
- * @param {string} close The close delimiter.
- */
- BaseMethods.Over = function(parser: TexParser, name: string, open: string, close: string) {
- // @test Over
- const mml = parser.itemFactory.create('over').setProperty('name', parser.currentCS) ;
- if (open || close) {
- // @test Choose
- mml.setProperty('open', open);
- mml.setProperty('close', close);
- } else if (name.match(/withdelims$/)) {
- // @test Over With Delims, Above With Delims
- mml.setProperty('open', parser.GetDelimiter(name));
- mml.setProperty('close', parser.GetDelimiter(name));
- }
- if (name.match(/^\\above/)) {
- // @test Above, Above With Delims
- mml.setProperty('thickness', parser.GetDimen(name));
- }
- else if (name.match(/^\\atop/) || open || close) {
- // @test Choose
- mml.setProperty('thickness', 0);
- }
- parser.Push(mml);
- };
- /**
- * Parses a fraction.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.Frac = function(parser: TexParser, name: string) {
- // @test Frac
- const num = parser.ParseArg(name);
- const den = parser.ParseArg(name);
- const node = parser.create('node', 'mfrac', [num, den]);
- parser.Push(node);
- };
- /**
- * Parses a square root element.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.Sqrt = function(parser: TexParser, name: string) {
- const n = parser.GetBrackets(name);
- let arg = parser.GetArgument(name);
- if (arg === '\\frac') {
- arg += '{' + parser.GetArgument(arg) + '}{' + parser.GetArgument(arg) + '}';
- }
- let mml = new TexParser(arg, parser.stack.env, parser.configuration).mml();
- if (!n) {
- // @test Square Root
- mml = parser.create('node', 'msqrt', [mml]);
- } else {
- // @test General Root
- mml = parser.create('node', 'mroot', [mml, parseRoot(parser, n)]);
- }
- parser.Push(mml);
- };
- // Utility
- /**
- * Parse a general root.
- * @param {TexParser} parser The calling parser.
- * @param {string} n The index of the root.
- */
- function parseRoot(parser: TexParser, n: string) {
- // @test General Root, Explicit Root
- const env = parser.stack.env;
- const inRoot = env['inRoot'];
- env['inRoot'] = true;
- const newParser = new TexParser(n, env, parser.configuration);
- let node = newParser.mml();
- const global = newParser.stack.global;
- if (global['leftRoot'] || global['upRoot']) {
- // @test Tweaked Root
- const def: EnvList = {};
- if (global['leftRoot']) {
- def['width'] = global['leftRoot'];
- }
- if (global['upRoot']) {
- def['voffset'] = global['upRoot'];
- def['height'] = global['upRoot'];
- }
- node = parser.create('node', 'mpadded', [node], def);
- }
- env['inRoot'] = inRoot;
- return node;
- }
- /**
- * Parse a general root.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.Root = function(parser: TexParser, name: string) {
- const n = parser.GetUpTo(name, '\\of');
- const arg = parser.ParseArg(name);
- const node = parser.create('node', 'mroot', [arg, parseRoot(parser, n)]);
- parser.Push(node);
- };
- /**
- * Parses a movable index element in a root, e.g. \\uproot, \\leftroot
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- * @param {string} id Argument which should be a string representation of an integer.
- */
- BaseMethods.MoveRoot = function(parser: TexParser, name: string, id: string) {
- // @test Tweaked Root
- if (!parser.stack.env['inRoot']) {
- // @test Misplaced Move Root
- throw new TexError('MisplacedMoveRoot', '%1 can appear only within a root', parser.currentCS);
- }
- if (parser.stack.global[id]) {
- // @test Multiple Move Root
- throw new TexError('MultipleMoveRoot', 'Multiple use of %1', parser.currentCS);
- }
- let n = parser.GetArgument(name);
- if (!n.match(/-?[0-9]+/)) {
- // @test Incorrect Move Root
- throw new TexError('IntegerArg', 'The argument to %1 must be an integer', parser.currentCS);
- }
- n = (parseInt(n, 10) / 15) + 'em';
- if (n.substr(0, 1) !== '-') {
- n = '+' + n;
- }
- parser.stack.global[id] = n;
- };
- /**
- * Handle accents.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- * @param {string} accent The accent.
- * @param {boolean} stretchy True if accent is stretchy.
- */
- BaseMethods.Accent = function(parser: TexParser, name: string, accent: string, stretchy: boolean) {
- // @test Vector
- const c = parser.ParseArg(name);
- // @test Vector Font
- const def = {...ParseUtil.getFontDef(parser), accent: true, mathaccent: true};
- const entity = NodeUtil.createEntity(accent);
- const moNode = parser.create('token', 'mo', def, entity);
- const mml = moNode;
- NodeUtil.setAttribute(mml, 'stretchy', stretchy ? true : false);
- // @test Vector Op, Vector
- const mo = (NodeUtil.isEmbellished(c) ? NodeUtil.getCoreMO(c) : c);
- if (NodeUtil.isType(mo, 'mo') || NodeUtil.getProperty(mo, 'movablelimits')) {
- // @test Vector Op
- NodeUtil.setProperties(mo, {'movablelimits': false});
- }
- const muoNode = parser.create('node', 'munderover');
- // This is necessary to get the empty element into the children.
- NodeUtil.setChild(muoNode, 0, c);
- NodeUtil.setChild(muoNode, 1, null);
- NodeUtil.setChild(muoNode, 2, mml);
- let texAtom = parser.create('node', 'TeXAtom', [muoNode]);
- parser.Push(texAtom);
- };
- /**
- * Handles stacked elements.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- * @param {string} c Character to stack.
- * @param {boolean} stack True if stacked operator.
- */
- BaseMethods.UnderOver = function(parser: TexParser, name: string, c: string, stack: boolean) {
- const entity = NodeUtil.createEntity(c);
- const mo = parser.create('token', 'mo', {stretchy: true, accent: true}, entity);
- const pos = (name.charAt(1) === 'o' ? 'over' : 'under');
- const base = parser.ParseArg(name);
- parser.Push(ParseUtil.underOver(parser, base, mo, pos, stack));
- };
- /**
- * Handles overset.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.Overset = function(parser: TexParser, name: string) {
- // @test Overset
- const top = parser.ParseArg(name);
- const base = parser.ParseArg(name);
- ParseUtil.checkMovableLimits(base);
- if (top.isKind('mo')) {
- NodeUtil.setAttribute(top, 'accent', false);
- }
- const node = parser.create('node', 'mover', [base, top]);
- parser.Push(node);
- };
- /**
- * Handles underset.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.Underset = function(parser: TexParser, name: string) {
- // @test Underset
- const bot = parser.ParseArg(name);
- const base = parser.ParseArg(name);
- ParseUtil.checkMovableLimits(base);
- if (bot.isKind('mo')) {
- NodeUtil.setAttribute(bot, 'accent', false);
- }
- const node = parser.create('node', 'munder', [base, bot], {accentunder: false});
- parser.Push(node);
- };
- /**
- * Handles overunderset.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.Overunderset = function(parser: TexParser, name: string) {
- const top = parser.ParseArg(name);
- const bot = parser.ParseArg(name);
- const base = parser.ParseArg(name);
- ParseUtil.checkMovableLimits(base);
- if (top.isKind('mo')) {
- NodeUtil.setAttribute(top, 'accent', false);
- }
- if (bot.isKind('mo')) {
- NodeUtil.setAttribute(bot, 'accent', false);
- }
- const node = parser.create('node', 'munderover', [base, bot, top], {accent: false, accentunder: false});
- parser.Push(node);
- };
- /**
- * Creates TeXAtom, when class of element is changed explicitly.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- * @param {number} mclass The new TeX class.
- */
- BaseMethods.TeXAtom = function(parser: TexParser, name: string, mclass: number) {
- let def: EnvList = {texClass: mclass};
- let mml: StackItem | MmlNode;
- let node: MmlNode;
- let parsed: MmlNode;
- if (mclass === TEXCLASS.OP) {
- def['movesupsub'] = def['movablelimits'] = true;
- const arg = parser.GetArgument(name);
- const match = arg.match(/^\s*\\rm\s+([a-zA-Z0-9 ]+)$/);
- if (match) {
- // @test Mathop
- def['mathvariant'] = TexConstant.Variant.NORMAL;
- node = parser.create('token', 'mi', def, match[1]);
- } else {
- // @test Mathop Cal
- parsed = new TexParser(arg, parser.stack.env, parser.configuration).mml();
- node = parser.create('node', 'TeXAtom', [parsed], def);
- }
- mml = parser.itemFactory.create('fn', node);
- } else {
- // @test Mathrel
- parsed = parser.ParseArg(name);
- mml = parser.create('node', 'TeXAtom', [parsed], def);
- }
- parser.Push(mml);
- };
- /**
- * Creates mmltoken elements. Used in Macro substitutions.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.MmlToken = function(parser: TexParser, name: string) {
- // @test Modulo
- const kind = parser.GetArgument(name);
- let attr = parser.GetBrackets(name, '').replace(/^\s+/, '');
- const text = parser.GetArgument(name);
- const def: EnvList = {};
- const keep: string[] = [];
- let node: MmlNode;
- try {
- node = parser.create('node', kind);
- } catch (e) {
- node = null;
- }
- if (!node || !node.isToken) {
- // @test Token Illegal Type, Token Wrong Type
- throw new TexError('NotMathMLToken', '%1 is not a token element', kind);
- }
- while (attr !== '') {
- const match = attr.match(/^([a-z]+)\s*=\s*('[^']*'|"[^"]*"|[^ ,]*)\s*,?\s*/i);
- if (!match) {
- // @test Token Invalid Attribute
- throw new TexError('InvalidMathMLAttr', 'Invalid MathML attribute: %1', attr);
- }
- if (!node.attributes.hasDefault(match[1]) && !MmlTokenAllow[match[1]]) {
- // @test Token Unknown Attribute, Token Wrong Attribute
- throw new TexError('UnknownAttrForElement',
- '%1 is not a recognized attribute for %2',
- match[1], kind);
- }
- let value: string | boolean = ParseUtil.MmlFilterAttribute(
- parser, match[1], match[2].replace(/^(['"])(.*)\1$/, '$2'));
- if (value) {
- if (value.toLowerCase() === 'true') {
- value = true;
- }
- else if (value.toLowerCase() === 'false') {
- value = false;
- }
- def[match[1]] = value;
- keep.push(match[1]);
- }
- attr = attr.substr(match[0].length);
- }
- if (keep.length) {
- def['mjx-keep-attrs'] = keep.join(' ');
- }
- const textNode = parser.create('text', text);
- node.appendChild(textNode);
- NodeUtil.setProperties(node, def);
- parser.Push(node);
- };
- /**
- * Handle strut.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.Strut = function(parser: TexParser, _name: string) {
- // @test Strut
- const row = parser.create('node', 'mrow');
- const padded = parser.create('node', 'mpadded', [row],
- {height: '8.6pt', depth: '3pt', width: 0});
- parser.Push(padded);
- };
- /**
- * Handle phantom commands.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- * @param {string} v Vertical size.
- * @param {string} h Horizontal size.
- */
- BaseMethods.Phantom = function(parser: TexParser, name: string, v: string, h: string) {
- // @test Phantom
- let box = parser.create('node', 'mphantom', [parser.ParseArg(name)]);
- if (v || h) {
- // TEMP: Changes here
- box = parser.create('node', 'mpadded', [box]);
- if (h) {
- // @test Horizontal Phantom
- NodeUtil.setAttribute(box, 'height', 0);
- NodeUtil.setAttribute(box, 'depth', 0);
- }
- if (v) {
- // @test Vertical Phantom
- NodeUtil.setAttribute(box, 'width', 0);
- }
- }
- const atom = parser.create('node', 'TeXAtom', [box]);
- parser.Push(atom);
- };
- /**
- * Handle smash.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.Smash = function(parser: TexParser, name: string) {
- // @test Smash, Smash Top, Smash Bottom
- const bt = ParseUtil.trimSpaces(parser.GetBrackets(name, ''));
- const smash = parser.create('node', 'mpadded', [parser.ParseArg(name)]);
- // TEMP: Changes here:
- switch (bt) {
- case 'b': NodeUtil.setAttribute(smash, 'depth', 0); break;
- case 't': NodeUtil.setAttribute(smash, 'height', 0); break;
- default:
- NodeUtil.setAttribute(smash, 'height', 0);
- NodeUtil.setAttribute(smash, 'depth', 0);
- }
- const atom = parser.create('node', 'TeXAtom', [smash]);
- parser.Push(atom);
- };
- /**
- * Handle rlap and llap commands.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.Lap = function(parser: TexParser, name: string) {
- // @test Llap, Rlap
- const mml = parser.create('node', 'mpadded', [parser.ParseArg(name)], {width: 0});
- if (name === '\\llap') {
- // @test Llap
- NodeUtil.setAttribute(mml, 'lspace', '-1width');
- }
- const atom = parser.create('node', 'TeXAtom', [mml]);
- parser.Push(atom);
- };
- /**
- * Handle raise and lower commands.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.RaiseLower = function(parser: TexParser, name: string) {
- // @test Raise, Lower, Raise Negative, Lower Negative
- let h = parser.GetDimen(name);
- let item =
- parser.itemFactory.create('position').setProperties({name: parser.currentCS, move: 'vertical'}) ;
- // TEMP: Changes here:
- if (h.charAt(0) === '-') {
- // @test Raise Negative, Lower Negative
- h = h.slice(1);
- name = name.substr(1) === 'raise' ? '\\lower' : '\\raise';
- }
- if (name === '\\lower') {
- // @test Raise, Raise Negative
- item.setProperty('dh', '-' + h);
- item.setProperty('dd', '+' + h);
- } else {
- // @test Lower, Lower Negative
- item.setProperty('dh', '+' + h);
- item.setProperty('dd', '-' + h);
- }
- parser.Push(item);
- };
- /**
- * Handle moveleft, moveright commands
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.MoveLeftRight = function(parser: TexParser, name: string) {
- // @test Move Left, Move Right, Move Left Negative, Move Right Negative
- let h = parser.GetDimen(name);
- let nh = (h.charAt(0) === '-' ? h.slice(1) : '-' + h);
- if (name === '\\moveleft') {
- let tmp = h;
- h = nh;
- nh = tmp;
- }
- parser.Push(
- parser.itemFactory.create('position').setProperties({
- name: parser.currentCS, move: 'horizontal',
- left: parser.create('node', 'mspace', [], {width: h}),
- right: parser.create('node', 'mspace', [], {width: nh})}) );
- };
- /**
- * Handle horizontal spacing commands.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.Hskip = function(parser: TexParser, name: string) {
- // @test Modulo
- const node = parser.create('node', 'mspace', [],
- {width: parser.GetDimen(name)});
- parser.Push(node);
- };
- /**
- * Handle removal of spaces in script modes
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.Nonscript = function(parser: TexParser, _name: string) {
- parser.Push(parser.itemFactory.create('nonscript'));
- };
- /**
- * Handle Rule and Space command
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- * @param {string} style The style of the rule spacer.
- */
- BaseMethods.Rule = function(parser: TexParser, name: string, style: string) {
- // @test Rule 3D, Space 3D
- const w = parser.GetDimen(name),
- h = parser.GetDimen(name),
- d = parser.GetDimen(name);
- let def: EnvList = {width: w, height: h, depth: d};
- if (style !== 'blank') {
- def['mathbackground'] = (parser.stack.env['color'] || 'black');
- }
- const node = parser.create('node', 'mspace', [], def);
- parser.Push(node);
- };
- /**
- * Handle rule command.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.rule = function(parser: TexParser, name: string) {
- // @test Rule 2D
- const v = parser.GetBrackets(name),
- w = parser.GetDimen(name),
- h = parser.GetDimen(name);
- let mml = parser.create('node', 'mspace', [], {
- width: w, height: h,
- mathbackground: (parser.stack.env['color'] || 'black') });
- if (v) {
- mml = parser.create('node', 'mpadded', [mml], {voffset: v});
- if (v.match(/^\-/)) {
- NodeUtil.setAttribute(mml, 'height', v);
- NodeUtil.setAttribute(mml, 'depth', '+' + v.substr(1));
- } else {
- NodeUtil.setAttribute(mml, 'height', '+' + v);
- }
- }
- parser.Push(mml);
- };
- /**
- * Handle big command sequences, e.g., \\big, \\Bigg.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- * @param {number} mclass The TeX class of the element.
- * @param {number} size The em size.
- */
- BaseMethods.MakeBig = function(parser: TexParser, name: string, mclass: number, size: number) {
- // @test Choose, Over With Delims, Above With Delims
- size *= P_HEIGHT;
- let sizeStr = String(size).replace(/(\.\d\d\d).+/, '$1') + 'em';
- const delim = parser.GetDelimiter(name, true);
- const mo = parser.create('token', 'mo', {
- minsize: sizeStr, maxsize: sizeStr,
- fence: true, stretchy: true, symmetric: true
- }, delim);
- const node = parser.create('node', 'TeXAtom', [mo], {texClass: mclass});
- parser.Push(node);
- };
- /**
- * Handle buildrel command.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.BuildRel = function(parser: TexParser, name: string) {
- // @test BuildRel, BuildRel Expression
- const top = parser.ParseUpTo(name, '\\over');
- const bot = parser.ParseArg(name);
- const node = parser.create('node', 'munderover');
- // This is necessary to get the empty element into the children.
- NodeUtil.setChild(node, 0, bot);
- NodeUtil.setChild(node, 1, null);
- NodeUtil.setChild(node, 2, top);
- const atom = parser.create('node', 'TeXAtom', [node], {texClass: TEXCLASS.REL});
- parser.Push(atom);
- };
- /**
- * Handle horizontal boxes.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- * @param {string} style Box style.
- * @param {string} font The mathvariant to use
- */
- BaseMethods.HBox = function(parser: TexParser, name: string, style: string, font?: string) {
- // @test Hbox
- parser.PushAll(ParseUtil.internalMath(parser, parser.GetArgument(name), style, font));
- };
- /**
- * Handle framed boxes.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.FBox = function(parser: TexParser, name: string) {
- // @test Fbox
- const internal = ParseUtil.internalMath(parser, parser.GetArgument(name));
- const node = parser.create('node', 'menclose', internal, {notation: 'box'});
- parser.Push(node);
- };
- /**
- * Handle framed boxes with options.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.FrameBox = function(parser: TexParser, name: string) {
- const width = parser.GetBrackets(name);
- const pos = parser.GetBrackets(name) || 'c';
- let mml = ParseUtil.internalMath(parser, parser.GetArgument(name));
- if (width) {
- mml = [parser.create('node', 'mpadded', mml, {
- width,
- 'data-align': lookup(pos, {l: 'left', r: 'right'}, 'center')
- })];
- }
- const node = parser.create('node', 'TeXAtom',
- [parser.create('node', 'menclose', mml, {notation: 'box'})],
- {texClass: TEXCLASS.ORD});
- parser.Push(node);
- };
- /**
- * Handle \\not.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.Not = function(parser: TexParser, _name: string) {
- // @test Negation Simple, Negation Complex, Negation Explicit,
- // Negation Large
- parser.Push(parser.itemFactory.create('not'));
- };
- /**
- * Handle dots.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.Dots = function(parser: TexParser, _name: string) {
- // @test Operator Dots
- const ldotsEntity = NodeUtil.createEntity('2026');
- const cdotsEntity = NodeUtil.createEntity('22EF');
- const ldots = parser.create('token', 'mo', {stretchy: false}, ldotsEntity);
- const cdots = parser.create('token', 'mo', {stretchy: false}, cdotsEntity);
- parser.Push(
- parser.itemFactory.create('dots').setProperties({
- ldots: ldots,
- cdots: cdots
- }) );
- };
- /**
- * Handle small matrix environments.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- * @param {string} open Opening fence.
- * @param {string} close Closing fence.
- * @param {string} align Column alignment.
- * @param {string} spacing Column spacing.
- * @param {string} vspacing Row spacing.
- * @param {string} style Display or text style.
- * @param {boolean} cases Is it a cases environment.
- * @param {boolean} numbered Is it a numbered environment.
- */
- BaseMethods.Matrix = function(parser: TexParser, _name: string,
- open: string, close: string, align: string,
- spacing: string, vspacing: string, style: string,
- cases: boolean, numbered: boolean) {
- const c = parser.GetNext();
- if (c === '') {
- // @test Matrix Error
- throw new TexError('MissingArgFor', 'Missing argument for %1', parser.currentCS);
- }
- if (c === '{') {
- // @test Matrix Braces, Matrix Columns, Matrix Rows.
- parser.i++;
- } else {
- // @test Matrix Arg
- parser.string = c + '}' + parser.string.slice(parser.i + 1);
- parser.i = 0;
- }
- // @test Matrix Braces, Matrix Columns, Matrix Rows.
- const array = parser.itemFactory.create('array').setProperty('requireClose', true) as sitem.ArrayItem;
- array.arraydef = {
- rowspacing: (vspacing || '4pt'),
- columnspacing: (spacing || '1em')
- };
- if (cases) {
- // @test Matrix Cases
- array.setProperty('isCases', true);
- }
- if (numbered) {
- // @test Matrix Numbered
- array.setProperty('isNumbered', true);
- array.arraydef.side = numbered;
- }
- if (open || close) {
- // @test Matrix Parens, Matrix Parens Subscript, Matrix Cases
- array.setProperty('open', open);
- array.setProperty('close', close);
- }
- if (style === 'D') {
- // @test Matrix Numbered
- array.arraydef.displaystyle = true;
- }
- if (align != null) {
- // @test Matrix Cases, Matrix Numbered
- array.arraydef.columnalign = align;
- }
- parser.Push(array);
- };
- /**
- * Handle array entry.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.Entry = function(parser: TexParser, name: string) {
- // @test Label, Array, Cross Product Formula
- parser.Push(parser.itemFactory.create('cell').setProperties({isEntry: true, name: name}));
- const top = parser.stack.Top();
- const env = top.getProperty('casesEnv') as string;
- const cases = top.getProperty('isCases');
- if (!cases && !env) return;
- //
- // Make second column be in \text{...} (unless it is already
- // in a \text{...}, for backward compatibility).
- //
- const str = parser.string;
- let braces = 0, close = -1, i = parser.i, m = str.length;
- const end = (env ? new RegExp(`^\\\\end\\s*\\{${env.replace(/\*/, '\\*')}\\}`) : null);
- //
- // Look through the string character by character...
- //
- while (i < m) {
- const c = str.charAt(i);
- if (c === '{') {
- //
- // Increase the nested brace count and go on
- //
- braces++;
- i++;
- } else if (c === '}') {
- //
- // If there are too many close braces, just end (we will get an
- // error message later when the rest of the string is parsed)
- // Otherwise
- // decrease the nested brace count,
- // if it is now zero and we haven't already marked the end of the
- // first brace group, record the position (use to check for \text{} later)
- // go on to the next character.
- //
- if (braces === 0) {
- m = 0;
- } else {
- braces--;
- if (braces === 0 && close < 0) {
- close = i - parser.i;
- }
- i++;
- }
- } else if (c === '&' && braces === 0) {
- //
- // Extra alignment tabs are not allowed in cases
- //
- // @test ExtraAlignTab
- throw new TexError('ExtraAlignTab', 'Extra alignment tab in \\cases text');
- } else if (c === '\\') {
- //
- // If the macro is \cr or \\, end the search, otherwise skip the macro
- // (multi-letter names don't matter, as we will skip the rest of the
- // characters in the main loop)
- //
- const rest = str.substr(i);
- if (rest.match(/^((\\cr)[^a-zA-Z]|\\\\)/) || (end && rest.match(end))) {
- m = 0;
- } else {
- i += 2;
- }
- } else {
- //
- // Go on to the next character
- //
- i++;
- }
- }
- //
- // Check if the second column text is already in \text{};
- // If not, process the second column as text and continue parsing from there,
- // (otherwise process the second column as normal, since it is in \text{}
- //
- const text = str.substr(parser.i, i - parser.i);
- if (!text.match(/^\s*\\text[^a-zA-Z]/) || close !== text.replace(/\s+$/, '').length - 1) {
- const internal = ParseUtil.internalMath(parser, ParseUtil.trimSpaces(text), 0);
- parser.PushAll(internal);
- parser.i = i;
- }
- };
- /**
- * Handle newline in array.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.Cr = function(parser: TexParser, name: string) {
- // @test Cr Linebreak, Misplaced Cr
- parser.Push(
- parser.itemFactory.create('cell').setProperties({isCR: true, name: name}));
- };
- /**
- * Handle newline outside array.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- * @param {boolean} nobrackets Flag indicating if newline is followed by
- * brackets.
- */
- BaseMethods.CrLaTeX = function(parser: TexParser, name: string, nobrackets: boolean = false) {
- let n: string;
- if (!nobrackets) {
- // TODO: spaces before * and [ are not allowed in AMS environments like align, but
- // should be allowed in array and eqnarray. This distinction should be honored here.
- if (parser.string.charAt(parser.i) === '*') { // The * controls page breaking, so ignore it
- parser.i++;
- }
- if (parser.string.charAt(parser.i) === '[') {
- let dim = parser.GetBrackets(name, '');
- let [value, unit, ] = ParseUtil.matchDimen(dim);
- // @test Custom Linebreak
- if (dim && !value) {
- // @test Dimension Error
- throw new TexError('BracketMustBeDimension',
- 'Bracket argument to %1 must be a dimension', parser.currentCS);
- }
- n = value + unit;
- }
- }
- parser.Push(
- parser.itemFactory.create('cell').setProperties({isCR: true, name: name, linebreak: true})
- );
- const top = parser.stack.Top();
- let node: MmlNode;
- if (top instanceof sitem.ArrayItem) {
- // @test Array
- if (n) {
- top.addRowSpacing(n);
- }
- } else {
- if (n) {
- // @test Custom Linebreak
- node = parser.create('node', 'mspace', [], {depth: n});
- parser.Push(node);
- }
- // @test Linebreak
- node = parser.create('node', 'mspace', [], {linebreak: TexConstant.LineBreak.NEWLINE});
- parser.Push(node);
- }
- };
- /**
- * Handle horizontal lines in arrays.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- * @param {string} style Style of the line. E.g., dashed.
- */
- BaseMethods.HLine = function(parser: TexParser, _name: string, style: string) {
- if (style == null) {
- style = 'solid';
- }
- const top = parser.stack.Top();
- if (!(top instanceof sitem.ArrayItem) || top.Size()) {
- // @test Misplaced hline
- throw new TexError('Misplaced', 'Misplaced %1', parser.currentCS);
- }
- if (!top.table.length) {
- // @test Enclosed top, Enclosed top bottom
- top.frame.push('top');
- } else {
- // @test Enclosed bottom, Enclosed top bottom
- const lines = (top.arraydef['rowlines'] ? (top.arraydef['rowlines'] as string).split(/ /) : []);
- while (lines.length < top.table.length) {
- lines.push('none');
- }
- lines[top.table.length - 1] = style;
- top.arraydef['rowlines'] = lines.join(' ');
- }
- };
- /**
- * Handle hfill commands.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.HFill = function(parser: TexParser, _name: string) {
- const top = parser.stack.Top();
- if (top instanceof sitem.ArrayItem) {
- // @test Hfill
- top.hfill.push(top.Size());
- } else {
- // @test UnsupportedHFill
- throw new TexError('UnsupportedHFill', 'Unsupported use of %1', parser.currentCS);
- }
- };
- /**
- * LaTeX environments
- */
- /**
- * Handle begin and end environments. This is a macro method.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.BeginEnd = function(parser: TexParser, name: string) {
- // @test Array1, Array2, Array Test
- let env = parser.GetArgument(name);
- if (env.match(/\\/i)) {
- // @test InvalidEnv
- throw new TexError('InvalidEnv', 'Invalid environment name \'%1\'', env);
- }
- let macro = parser.configuration.handlers.get('environment').lookup(env) as Macro;
- if (macro && name === '\\end') {
- // If the first argument is true, we have some sort of user defined
- // environment. Otherwise we have a standard LaTeX environment that is
- // handled with begin and end items.
- if (!macro.args[0]) {
- const mml = parser.itemFactory.create('end').setProperty('name', env);
- parser.Push(mml);
- return;
- }
- // Remember the user defined environment we are closing.
- parser.stack.env['closing'] = env;
- }
- ParseUtil.checkMaxMacros(parser, false);
- parser.parse('environment', [parser, env]);
- };
- /**
- * Handle array environment.
- * @param {TexParser} parser The calling parser.
- * @param {StackItem} begin The opening stackitem.
- * @param {string} open Opening fence.
- * @param {string} close Closing fence.
- * @param {string} align Column alignment.
- * @param {string} spacing Column spacing.
- * @param {string} vspacing Row spacing.
- * @param {string} style Display or text style.
- * @param {boolean} raggedHeight Does the height need to be adjusted?
- */
- BaseMethods.Array = function(parser: TexParser, begin: StackItem,
- open: string, close: string, align: string,
- spacing: string, vspacing: string, style: string,
- raggedHeight: boolean) {
- if (!align) {
- // @test Array Single
- align = parser.GetArgument('\\begin{' + begin.getName() + '}');
- }
- let lines = ('c' + align).replace(/[^clr|:]/g, '').replace(/[^|:]([|:])+/g, '$1');
- align = align.replace(/[^clr]/g, '').split('').join(' ');
- align = align.replace(/l/g, 'left').replace(/r/g, 'right').replace(/c/g, 'center');
- const array = parser.itemFactory.create('array') as sitem.ArrayItem;
- array.arraydef = {
- columnalign: align,
- columnspacing: (spacing || '1em'),
- rowspacing: (vspacing || '4pt')
- };
- if (lines.match(/[|:]/)) {
- // @test Enclosed left right
- if (lines.charAt(0).match(/[|:]/)) {
- // @test Enclosed left right, Enclosed left
- array.frame.push('left');
- array.dashed = lines.charAt(0) === ':';
- }
- if (lines.charAt(lines.length - 1).match(/[|:]/)) {
- // @test Enclosed left right, Enclosed right
- array.frame.push('right');
- }
- // @test Enclosed left right
- lines = lines.substr(1, lines.length - 2);
- array.arraydef.columnlines =
- lines.split('').join(' ').replace(/[^|: ]/g, 'none').replace(/\|/g, 'solid').replace(/:/g, 'dashed');
- }
- if (open) {
- // @test Cross Product
- array.setProperty('open', parser.convertDelimiter(open));
- }
- if (close) {
- // @test Cross Product
- array.setProperty('close', parser.convertDelimiter(close));
- }
- if ((style || '').charAt(1) === '\'') {
- array.arraydef['data-cramped'] = true;
- style = style.charAt(0);
- }
- if (style === 'D') {
- // TODO: This case never seems to occur! No test.
- array.arraydef['displaystyle'] = true;
- }
- else if (style) {
- // @test Subarray, Small Matrix
- array.arraydef['displaystyle'] = false;
- }
- if (style === 'S') {
- // @test Subarray, Small Matrix
- array.arraydef['scriptlevel'] = 1;
- }
- if (raggedHeight) {
- // @test Subarray, Small Matrix
- array.arraydef['useHeight'] = false;
- }
- parser.Push(begin);
- return array;
- };
- /**
- * Handle aligned arrays.
- * @param {TexParser} parser The calling parser.
- * @param {StackItem} begin The opening stackitem.
- */
- BaseMethods.AlignedArray = function(parser: TexParser, begin: StackItem) {
- // @test Array1, Array2, Array Test
- const align = parser.GetBrackets('\\begin{' + begin.getName() + '}');
- let item = BaseMethods.Array(parser, begin);
- return ParseUtil.setArrayAlign(item as sitem.ArrayItem, align);
- };
- /**
- * Handle equation environment.
- * @param {TexParser} parser The calling parser.
- * @param {StackItem} begin The opening stackitem.
- * @param {boolean} numbered True if environment is numbered.
- */
- BaseMethods.Equation = function (parser: TexParser, begin: StackItem, numbered: boolean) {
- parser.Push(begin);
- ParseUtil.checkEqnEnv(parser);
- return parser.itemFactory.create('equation', numbered).
- setProperty('name', begin.getName());
- };
- /**
- * Handle eqnarray.
- * @param {TexParser} parser The calling parser.
- * @param {StackItem} begin The opening stackitem.
- * @param {boolean} numbered True if environment is numbered.
- * @param {boolean} taggable True if taggable.
- * @param {string} align Alignment string.
- * @param {string} spacing Spacing between columns.
- */
- BaseMethods.EqnArray = function(parser: TexParser, begin: StackItem,
- numbered: boolean, taggable: boolean,
- align: string, spacing: string) {
- // @test The Lorenz Equations, Maxwell's Equations, Cubic Binomial
- parser.Push(begin);
- if (taggable) {
- ParseUtil.checkEqnEnv(parser);
- }
- align = align.replace(/[^clr]/g, '').split('').join(' ');
- align = align.replace(/l/g, 'left').replace(/r/g, 'right').replace(/c/g, 'center');
- let newItem = parser.itemFactory.create('eqnarray', begin.getName(),
- numbered, taggable, parser.stack.global) as sitem.ArrayItem;
- newItem.arraydef = {
- displaystyle: true,
- columnalign: align,
- columnspacing: (spacing || '1em'),
- rowspacing: '3pt',
- side: parser.options['tagSide'],
- minlabelspacing: parser.options['tagIndent']
- };
- return newItem;
- };
- /**
- * Handles no tag commands.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.HandleNoTag = function(parser: TexParser, _name: string) {
- parser.tags.notag();
- };
- /**
- * Record a label name for a tag
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.HandleLabel = function(parser: TexParser, name: string) {
- // @test Label, Label Empty
- let label = parser.GetArgument(name);
- if (label === '') {
- // @test Label Empty
- return;
- }
- if (!parser.tags.refUpdate) {
- // @test Label, Ref, Ref Unknown
- if (parser.tags.label) {
- // @test Double Label Error
- throw new TexError('MultipleCommand', 'Multiple %1', parser.currentCS);
- }
- parser.tags.label = label;
- if ((parser.tags.allLabels[label] || parser.tags.labels[label]) && !parser.options['ignoreDuplicateLabels']) {
- // @ Duplicate Label Error
- throw new TexError('MultipleLabel', 'Label \'%1\' multiply defined', label);
- }
- // TODO: This should be set in the tags structure!
- parser.tags.labels[label] = new Label(); // will be replaced by tag value later
- }
- };
- /**
- * Handle a label reference.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- * @param {boolean} eqref True if formatted as eqref.
- */
- BaseMethods.HandleRef = function(parser: TexParser, name: string, eqref: boolean) {
- // @test Ref, Ref Unknown, Eqref, Ref Default, Ref Named
- let label = parser.GetArgument(name);
- let ref = parser.tags.allLabels[label] || parser.tags.labels[label];
- if (!ref) {
- // @test Ref Unknown
- if (!parser.tags.refUpdate) {
- parser.tags.redo = true;
- }
- ref = new Label();
- }
- let tag = ref.tag;
- if (eqref) {
- // @test Eqref
- tag = parser.tags.formatTag(tag);
- }
- let node = parser.create('node', 'mrow', ParseUtil.internalMath(parser, tag), {
- href: parser.tags.formatUrl(ref.id, parser.options.baseURL), 'class': 'MathJax_ref'
- });
- parser.Push(node);
- };
- /**
- * Macros
- */
- BaseMethods.Macro = function(parser: TexParser, name: string,
- macro: string, argcount: number,
- def?: string) {
- if (argcount) {
- const args: string[] = [];
- if (def != null) {
- const optional = parser.GetBrackets(name);
- args.push(optional == null ? def : optional);
- }
- for (let i = args.length; i < argcount; i++) {
- args.push(parser.GetArgument(name));
- }
- macro = ParseUtil.substituteArgs(parser, args, macro);
- }
- parser.string = ParseUtil.addArgs(parser, macro, parser.string.slice(parser.i));
- parser.i = 0;
- ParseUtil.checkMaxMacros(parser);
- };
- /**
- * Handle MathChoice for elements whose exact size/style properties can only be
- * determined after the expression has been parsed.
- * @param {TexParser} parser The calling parser.
- * @param {string} name The macro name.
- */
- BaseMethods.MathChoice = function(parser: TexParser, name: string) {
- const D = parser.ParseArg(name);
- const T = parser.ParseArg(name);
- const S = parser.ParseArg(name);
- const SS = parser.ParseArg(name);
- parser.Push(parser.create('node', 'MathChoice', [D, T, S, SS]));
- };
- export default BaseMethods;
|