From fcb32f058baca3651b9597d136c388b4bde911e7 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Sun, 3 Jun 2018 18:19:23 -0400 Subject: [PATCH] Support \arraystretch as a macro definition (#1381) * Support \arraystretch as a macro definition Also add `expandMacro` and `expandMacroAsText` helpers to `MacroExpander`. * Remove excess defaulting * Add test --- src/MacroExpander.js | 34 ++++++ src/environments/array.js | 23 +++-- src/macros.js | 12 +++ test/__snapshots__/katex-spec.js.snap | 143 ++++++++++++++++++++++++++ test/katex-spec.js | 6 ++ 5 files changed, 212 insertions(+), 6 deletions(-) diff --git a/src/MacroExpander.js b/src/MacroExpander.js index 67a8dec8..01d703be 100644 --- a/src/MacroExpander.js +++ b/src/MacroExpander.js @@ -249,6 +249,40 @@ export default class MacroExpander implements MacroContextInterface { throw new Error(); // eslint-disable-line no-unreachable } + /** + * Fully expand the given macro name and return the resulting list of + * tokens, or return `undefined` if no such macro is defined. + */ + expandMacro(name: string): Token[] | void { + if (!this.macros.get(name)) { + return undefined; + } + const output = []; + const oldStackLength = this.stack.length; + this.pushToken(new Token(name)); + while (this.stack.length > oldStackLength) { + const expanded = this.expandOnce(); + // expandOnce returns Token if and only if it's fully expanded. + if (expanded instanceof Token) { + output.push(this.stack.pop()); + } + } + return output; + } + + /** + * Fully expand the given macro name and return the result as a string, + * or return `undefined` if no such macro is defined. + */ + expandMacroAsText(name: string): string | void { + const tokens = this.expandMacro(name); + if (tokens) { + return tokens.map((token) => token.text).join(""); + } else { + return tokens; + } + } + /** * Returns the expanded macro as a reversed array of tokens and a macro * argument count. Or returns `null` if no such macro. diff --git a/src/environments/array.js b/src/environments/array.js index 8cdf3a4c..13bedce7 100644 --- a/src/environments/array.js +++ b/src/environments/array.js @@ -25,7 +25,7 @@ type AlignSpec = { type: "separator", separator: string } | { export type ArrayEnvNodeData = {| type: "array", hskipBeforeAndAfter?: boolean, - arraystretch?: number, + arraystretch: number, addJot?: boolean, cols?: AlignSpec[], body: ParseNode<*>[][], // List of rows in the (2D) array. @@ -71,6 +71,20 @@ function parseArray( parser.gullet.beginGroup(); parser.gullet.macros.set("\\\\", "\\cr"); + // Get current arraystretch if it's not set by the environment + if (!result.arraystretch) { + const arraystretch = parser.gullet.expandMacroAsText("\\arraystretch"); + if (arraystretch == null) { + // Default \arraystretch from lttab.dtx + result.arraystretch = 1; + } else { + result.arraystretch = parseFloat(arraystretch); + if (!result.arraystretch || result.arraystretch < 0) { + throw new ParseError(`Invalid \\arraystretch: ${arraystretch}`); + } + } + } + let row = []; const body = [row]; const rowGaps = []; @@ -166,10 +180,7 @@ const htmlBuilder = function(group, options) { // Default \jot from ltmath.dtx // TODO(edemaine): allow overriding \jot via \setlength (#687) const jot = 3 * pt; - // Default \arraystretch from lttab.dtx - // TODO(gagern): may get redefined once we have user-defined macros - const arraystretch = utils.deflt(groupValue.arraystretch, 1); - const arrayskip = arraystretch * baselineskip; + const arrayskip = groupValue.arraystretch * baselineskip; const arstrutHeight = 0.7 * arrayskip; // \strutbox in ltfsstrc.dtx and const arstrutDepth = 0.3 * arrayskip; // \@arstrutbox in lttab.dtx @@ -358,7 +369,7 @@ const mathmlBuilder = function(group, options) { })); }; -// Convinient function for aligned and alignedat environments. +// Convenience function for aligned and alignedat environments. const alignedHandler = function(context, args) { const cols = []; let res = { diff --git a/src/macros.js b/src/macros.js index c331daad..38d506fb 100644 --- a/src/macros.js +++ b/src/macros.js @@ -38,6 +38,18 @@ export interface MacroContextInterface { */ expandAfterFuture(): Token; + /** + * Fully expand the given macro name and return the resulting list of + * tokens, or return `undefined` if no such macro is defined. + */ + expandMacro(name: string): Token[] | void; + + /** + * Fully expand the given macro name and return the result as a string, + * or return `undefined` if no such macro is defined. + */ + expandMacroAsText(name: string): string | void; + /** * Consume the specified number of arguments from the token stream, * and return the resulting array of arguments. diff --git a/test/__snapshots__/katex-spec.js.snap b/test/__snapshots__/katex-spec.js.snap index 930b7b17..627988d5 100644 --- a/test/__snapshots__/katex-spec.js.snap +++ b/test/__snapshots__/katex-spec.js.snap @@ -1,5 +1,148 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`A begin/end parser should grab \\arraystretch 1`] = ` +[ + { + "type": "array", + "mode": "math", + "value": { + "type": "array", + "arraystretch": 1.5, + "body": [ + [ + { + "type": "styling", + "mode": "math", + "value": { + "type": "styling", + "style": "text", + "value": [ + { + "type": "ordgroup", + "mode": "math", + "value": [ + { + "type": "mathord", + "loc": { + "end": 37, + "lexer": { + "input": "\\\\def\\\\arraystretch{1.5}\\\\begin{matrix}a&b\\\\\\\\c&d\\\\end{matrix}", + "pos": 56 + }, + "start": 36 + }, + "mode": "math", + "value": "a" + } + ] + } + ] + } + }, + { + "type": "styling", + "mode": "math", + "value": { + "type": "styling", + "style": "text", + "value": [ + { + "type": "ordgroup", + "mode": "math", + "value": [ + { + "type": "mathord", + "loc": { + "end": 39, + "lexer": { + "input": "\\\\def\\\\arraystretch{1.5}\\\\begin{matrix}a&b\\\\\\\\c&d\\\\end{matrix}", + "pos": 56 + }, + "start": 38 + }, + "mode": "math", + "value": "b" + } + ] + } + ] + } + } + ], + [ + { + "type": "styling", + "mode": "math", + "value": { + "type": "styling", + "style": "text", + "value": [ + { + "type": "ordgroup", + "mode": "math", + "value": [ + { + "type": "mathord", + "loc": { + "end": 42, + "lexer": { + "input": "\\\\def\\\\arraystretch{1.5}\\\\begin{matrix}a&b\\\\\\\\c&d\\\\end{matrix}", + "pos": 56 + }, + "start": 41 + }, + "mode": "math", + "value": "c" + } + ] + } + ] + } + }, + { + "type": "styling", + "mode": "math", + "value": { + "type": "styling", + "style": "text", + "value": [ + { + "type": "ordgroup", + "mode": "math", + "value": [ + { + "type": "mathord", + "loc": { + "end": 44, + "lexer": { + "input": "\\\\def\\\\arraystretch{1.5}\\\\begin{matrix}a&b\\\\\\\\c&d\\\\end{matrix}", + "pos": 56 + }, + "start": 43 + }, + "mode": "math", + "value": "d" + } + ] + } + ] + } + } + ] + ], + "hskipBeforeAndAfter": false, + "numHLinesBeforeRow": [ + 0, + 0 + ], + "rowGaps": [ + null + ] + } + } +] +`; + exports[`A parser that does not throw on unsupported commands should build katex-error span for other type of KaTeX error 1`] = ` { "attributes": { diff --git a/test/katex-spec.js b/test/katex-spec.js index a7136609..cfb92eb9 100644 --- a/test/katex-spec.js +++ b/test/katex-spec.js @@ -1173,6 +1173,12 @@ describe("A begin/end parser", function() { const m3 = getParsed("\\begin{matrix}a&b\\\\ c&d \\\\ \\end{matrix}")[0]; expect(m3.value.body.length).toBe(2); }); + + it("should grab \\arraystretch", function() { + const parse = getParsed("\\def\\arraystretch{1.5}" + + "\\begin{matrix}a&b\\\\c&d\\end{matrix}"); + expect(parse).toMatchSnapshot(); + }); }); describe("A sqrt parser", function() {