/* eslint no-constant-condition:0 */ var fontMetrics = require("./fontMetrics"); var parseData = require("./parseData"); var ParseError = require("./ParseError"); var ParseNode = parseData.ParseNode; /** * Parse the body of the environment, with rows delimited by \\ and * columns delimited by &, and create a nested list in row-major order * with one group per cell. */ function parseArray(parser, result) { var row = []; var body = [row]; var rowGaps = []; while (true) { var cell = parser.parseExpression(false, null); row.push(new ParseNode("ordgroup", cell, parser.mode)); var next = parser.nextToken.text; if (next === "&") { parser.consume(); } else if (next === "\\end") { break; } else if (next === "\\\\" || next === "\\cr") { var cr = parser.parseFunction(); rowGaps.push(cr.value.size); row = []; body.push(row); } else { // TODO: Clean up the following hack once #385 got merged var pos = Math.min(parser.pos + 1, parser.lexer._input.length); throw new ParseError("Expected & or \\\\ or \\end", parser.lexer, pos); } } result.body = body; result.rowGaps = rowGaps; return new ParseNode(result.type, result, parser.mode); } /* * An environment definition is very similar to a function definition: * it is declared with a name or a list of names, a set of properties * and a handler containing the actual implementation. * * The properties include: * - numArgs: The number of arguments after the \begin{name} function. * - argTypes: (optional) Just like for a function * - allowedInText: (optional) Whether or not the environment is allowed inside * text mode (default false) (not enforced yet) * - numOptionalArgs: (optional) Just like for a function * A bare number instead of that object indicates the numArgs value. * * The handler function will receive two arguments * - context: information and references provided by the parser * - args: an array of arguments passed to \begin{name} * The context contains the following properties: * - envName: the name of the environment, one of the listed names. * - parser: the parser object * - lexer: the lexer object * - positions: the positions associated with these arguments from args. * The handler must return a ParseResult. */ function defineEnvironment(names, props, handler) { if (typeof names === "string") { names = [names]; } if (typeof props === "number") { props = { numArgs: props }; } // Set default values of environments var data = { numArgs: props.numArgs || 0, argTypes: props.argTypes, greediness: 1, allowedInText: !!props.allowedInText, numOptionalArgs: props.numOptionalArgs || 0, handler: handler, }; for (var i = 0; i < names.length; ++i) { module.exports[names[i]] = data; } } // Arrays are part of LaTeX, defined in lttab.dtx so its documentation // is part of the source2e.pdf file of LaTeX2e source documentation. defineEnvironment("array", { numArgs: 1, }, function(context, args) { var colalign = args[0]; colalign = colalign.value.map ? colalign.value : [colalign]; var cols = colalign.map(function(node) { var ca = node.value; if ("lcr".indexOf(ca) !== -1) { return { type: "align", align: ca, }; } else if (ca === "|") { return { type: "separator", separator: "|", }; } throw new ParseError( "Unknown column alignment: " + node.value, context.lexer, context.positions[1]); }); var res = { type: "array", cols: cols, hskipBeforeAndAfter: true, // \@preamble in lttab.dtx }; res = parseArray(context.parser, res); return res; }); // The matrix environments of amsmath builds on the array environment // of LaTeX, which is discussed above. defineEnvironment([ "matrix", "pmatrix", "bmatrix", "Bmatrix", "vmatrix", "Vmatrix", ], { }, function(context) { var delimiters = { "matrix": null, "pmatrix": ["(", ")"], "bmatrix": ["[", "]"], "Bmatrix": ["\\{", "\\}"], "vmatrix": ["|", "|"], "Vmatrix": ["\\Vert", "\\Vert"], }[context.envName]; var res = { type: "array", hskipBeforeAndAfter: false, // \hskip -\arraycolsep in amsmath }; res = parseArray(context.parser, res); if (delimiters) { res = new ParseNode("leftright", { body: [res], left: delimiters[0], right: delimiters[1], }, context.mode); } return res; }); // A cases environment (in amsmath.sty) is almost equivalent to // \def\arraystretch{1.2}% // \left\{\begin{array}{@{}l@{\quad}l@{}} … \end{array}\right. defineEnvironment("cases", { }, function(context) { var res = { type: "array", arraystretch: 1.2, cols: [{ type: "align", align: "l", pregap: 0, postgap: fontMetrics.metrics.quad, }, { type: "align", align: "l", pregap: 0, postgap: 0, }], }; res = parseArray(context.parser, res); res = new ParseNode("leftright", { body: [res], left: "\\{", right: ".", }, context.mode); return res; }); // An aligned environment is like the align* environment // except it operates within math mode. // Note that we assume \nomallineskiplimit to be zero, // so that \strut@ is the same as \strut. defineEnvironment("aligned", { }, function(context) { var res = { type: "array", cols: [], }; res = parseArray(context.parser, res); var emptyGroup = new ParseNode("ordgroup", [], context.mode); var numCols = 0; res.value.body.forEach(function(row) { var i; for (i = 1; i < row.length; i += 2) { row[i].value.unshift(emptyGroup); } if (numCols < row.length) { numCols = row.length; } }); for (var i = 0; i < numCols; ++i) { var align = "r"; var pregap = 0; if (i % 2 === 1) { align = "l"; } else if (i > 0) { pregap = 2; // one \qquad between columns } res.value.cols[i] = { type: "align", align: align, pregap: pregap, postgap: 0, }; } return res; });