feat: support {equation}, {equation*}, and {split} (#2369)

* Support {equation}, {equation*}, and {split}

* Update screenshots

* Allow {split} at top level

* Move equation column number check to to ParseArray

* Add token to ParseError

* Sharpen parameters passed to parseArray

* Add token information

* Update an {array} spec in screenshotter

* Adjust {array} screenshotter spec

* Make a non-strict error call when {array} argument specifies too few columns.

* Move context checks to a helper function.
This commit is contained in:
Ron Kok
2020-08-03 09:35:41 -07:00
committed by GitHub
parent 1263681563
commit ab5936a6e7
7 changed files with 86 additions and 22 deletions

View File

@@ -368,7 +368,8 @@ use `\ce` instead|
|\eqcirc|$\eqcirc$||
|\Eqcolon|$\Eqcolon$||
|\eqcolon|$\eqcolon$||
|{equation}|<span style="color:firebrick;">Not supported</span>|[Issue #445](https://github.com/KaTeX/KaTeX/issues/445)|
|{equation}|$$\begin{equation}a = b + c\end{equation}$$|`\begin{equation}`<br>&nbsp;&nbsp;&nbsp;`a = b + c`<br>`\end{equation}`|
|{equation*}|$$\begin{equation*}a = b + c\end{equation*}$$|`\begin{equation*}`<br>&nbsp;&nbsp;&nbsp;`a = b + c`<br>`\end{equation*}`|
|{eqnarray}|<span style="color:firebrick;">Not supported</span>||
|\Eqqcolon|$\Eqqcolon$||
|\eqqcolon|$\eqqcolon$||
@@ -965,7 +966,7 @@ use `\ce` instead|
|\spades|$\spades$||
|\spadesuit|$\spadesuit$||
|\sphericalangle|$\sphericalangle$||
|{split}|<span style="color:firebrick;">Not supported</span>|[Issue #1345](https://github.com/KaTeX/KaTeX/issues/1345)|
|{split}|$$\begin{equation}\begin{split}a &=b+c\\&=e+f\end{split}\end{equation}$$|`\begin{equation}`<br>`\begin{split}`<br>&nbsp;&nbsp;&nbsp;`a &=b+c\\`<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`&=e+f`<br>`\end{split}`<br>`\end{equation}`|
|\sqcap|$\sqcap$||
|\sqcup|$\sqcup$||
|\square|$\square$||

View File

@@ -83,6 +83,7 @@ $( \big( \Big( \bigg( \Bigg($ `( \big( \Big( \bigg( \Bigg(`
|$\begin{pmatrix} a & b \\ c & d \end{pmatrix}$ |`\begin{pmatrix}`<br>&nbsp;&nbsp;&nbsp;`a & b \\`<br>&nbsp;&nbsp;&nbsp;`c & d`<br>`\end{pmatrix}` |$\begin{bmatrix} a & b \\ c & d \end{bmatrix}$ | `\begin{bmatrix}`<br>&nbsp;&nbsp;&nbsp;`a & b \\`<br>&nbsp;&nbsp;&nbsp;`c & d`<br>`\end{bmatrix}`
|$\begin{vmatrix} a & b \\ c & d \end{vmatrix}$ |`\begin{vmatrix}`<br>&nbsp;&nbsp;&nbsp;`a & b \\`<br>&nbsp;&nbsp;&nbsp;`c & d`<br>`\end{vmatrix}` |$\begin{Vmatrix} a & b \\ c & d \end{Vmatrix}$ |`\begin{Vmatrix}`<br>&nbsp;&nbsp;&nbsp;`a & b \\`<br>&nbsp;&nbsp;&nbsp;`c & d`<br>`\end{Vmatrix}`
|$\begin{Bmatrix} a & b \\ c & d \end{Bmatrix}$ |`\begin{Bmatrix}`<br>&nbsp;&nbsp;&nbsp;`a & b \\`<br>&nbsp;&nbsp;&nbsp;`c & d`<br>`\end{Bmatrix}`|$\def\arraystretch{1.5}\begin{array}{c:c:c} a & b & c \\ \hline d & e & f \\ \hdashline g & h & i \end{array}$|`\def\arraystretch{1.5}`<br>&nbsp;&nbsp;&nbsp;`\begin{array}{c:c:c}`<br>&nbsp;&nbsp;&nbsp;`a & b & c \\ \hline`<br>&nbsp;&nbsp;&nbsp;`d & e & f \\`<br>&nbsp;&nbsp;&nbsp;`\hdashline`<br>&nbsp;&nbsp;&nbsp;`g & h & i`<br>`\end{array}`
|$$\begin{equation}\begin{split}a &=b+c\\&=e+f\end{split}\end{equation}$$ |`\begin{equation}`<br>`\begin{split}`&nbsp;&nbsp;&nbsp;`a &=b+c\\`<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`&=e+f`<br>`\end{split}`<br>`\end{equation}` |$$\begin{equation*}\begin{split}a &=b+c\\&=e+f\end{split}\end{equation*}$$ |`\begin{equation*}`<br>`\begin{split}`&nbsp;&nbsp;&nbsp;`a &=b+c\\`<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`&=e+f`<br>`\end{split}`<br>`\end{equation*}`
|$$\begin{align} a&=b+c \\ d+e&=f \end{align}$$ |`\begin{align}`<br>&nbsp;&nbsp;&nbsp;`a&=b+c \\`<br>&nbsp;&nbsp;&nbsp;`d+e&=f`<br>`\end{align}`|$$\begin{alignat}{2}10&x+&3&y=2\\3&x+&13&y=4\end{alignat}$$ |
|$$\begin{align*} a&=b+c \\ d+e&=f \end{align*}$$ |`\begin{align*}`<br>&nbsp;&nbsp;&nbsp;`a&=b+c \\`<br>&nbsp;&nbsp;&nbsp;`d+e&=f`<br>`\end{align*}`|$\begin{aligned} a&=b+c \\ d+e&=f \end{aligned}$ |`\begin{aligned}`<br>&nbsp;&nbsp;&nbsp;`a&=b+c \\`<br>&nbsp;&nbsp;&nbsp;`d+e&=f`<br>`\end{aligned}`|
`\begin{alignedat}{2}`<br>&nbsp;&nbsp;&nbsp;`10&x+ &3&y = 2 \\`<br>&nbsp;&nbsp;&nbsp;` 3&x+&13&y = 4`<br>`\end{alignedat}`|$\begin{alignedat}{2}10&x+&3&y=2\\3&x+&13&y=4\end{alignedat}$ |`\begin{alignedat}{2}`<br>&nbsp;&nbsp;&nbsp;`10&x+ &3&y = 2 \\`<br>&nbsp;&nbsp;&nbsp;` 3&x+&13&y = 4`<br>`\end{alignedat}`

View File

@@ -29,6 +29,7 @@ export type AlignSpec = { type: "separator", separator: string } | {
// Type to indicate column separation in MathML
export type ColSeparationType = "align" | "alignat" | "gather" | "small";
// Helper functions
function getHLines(parser: Parser): boolean[] {
// Return an array. The array length = number of hlines.
// Each element in the array tells if the line is dashed.
@@ -44,6 +45,16 @@ function getHLines(parser: Parser): boolean[] {
return hlineInfo;
}
const validateAmsEnvironmentContext = context => {
const settings = context.parser.settings;
if (!settings.displayMode) {
throw new ParseError(`{${context.envName}} cannot be used inline.`);
} else if (settings.strict && !settings.topEnv) {
settings.reportNonstrict("textEnv",
`{${context.envName}} called from math mode.`);
}
};
/**
* Parse the body of the environment, with rows delimited by \\ and
* columns delimited by &, and create a nested list in row-major order
@@ -59,6 +70,8 @@ function parseArray(
arraystretch,
colSeparationType,
addEqnNum,
singleRow,
maxNumCols,
leqno,
}: {|
hskipBeforeAndAfter?: boolean,
@@ -67,13 +80,19 @@ function parseArray(
arraystretch?: number,
colSeparationType?: ColSeparationType,
addEqnNum?: boolean,
singleRow?: boolean,
maxNumCols?: number,
leqno?: boolean,
|},
style: StyleStr,
): ParseNode<"array"> {
// Parse body of array with \\ temporarily mapped to \cr
parser.gullet.beginGroup();
parser.gullet.macros.set("\\\\", "\\cr");
if (singleRow) {
parser.gullet.macros.set("\\\\", ""); // {equation} acts this way.
} else {
parser.gullet.macros.set("\\\\", "\\cr");
}
// Get current arraystretch if it's not set by the environment
if (!arraystretch) {
@@ -122,6 +141,17 @@ function parseArray(
row.push(cell);
const next = parser.fetch().text;
if (next === "&") {
if (maxNumCols && row.length === maxNumCols) {
if (singleRow || colSeparationType) {
// {equation} or {split}
throw new ParseError("Too many tab characters: &",
parser.nextToken);
} else {
// {array} environment
parser.settings.reportNonstrict("textEnv", "Too few columns " +
"specified in the {array} column argument.");
}
}
parser.consume();
} else if (next === "\\end") {
// Arrays terminate newlines with `\crcr` which consumes a `\cr` if
@@ -136,6 +166,9 @@ function parseArray(
}
break;
} else if (next === "\\cr") {
if (singleRow) {
throw new ParseError("Misplaced \\cr.", parser.nextToken);
}
const cr = assertNodeType(parser.parseFunction(), "cr");
rowGaps.push(cr.size);
@@ -580,14 +613,7 @@ const mathmlBuilder: MathMLBuilder<"array"> = function(group, options) {
// Convenience function for align, align*, aligned, alignat, alignat*, alignedat.
const alignedHandler = function(context, args) {
if (context.envName.indexOf("ed") === -1) {
// Check if this environment call is allowed.
const settings = context.parser.settings;
if (!settings.displayMode) {
throw new ParseError(`{${context.envName}} cannot be used inline.`);
} else if (settings.strict && !settings.topEnv) {
settings.reportNonstrict("textEnv",
`{${context.envName}} called from math mode.`);
}
validateAmsEnvironmentContext(context);
}
const cols = [];
const separationType = context.envName.indexOf("at") > -1 ? "alignat" : "align";
@@ -597,6 +623,7 @@ const alignedHandler = function(context, args) {
addJot: true,
addEqnNum: context.envName === "align" || context.envName === "alignat",
colSeparationType: separationType,
maxNumCols: context.envName === "split" ? 2 : undefined,
leqno: context.parser.settings.leqno,
},
"display"
@@ -712,6 +739,7 @@ defineEnvironment({
const res = {
cols,
hskipBeforeAndAfter: true, // \@preamble in lttab.dtx
maxNumCols: cols.length,
};
return parseArray(context.parser, res, dCellStyle(context.envName));
},
@@ -876,7 +904,7 @@ defineEnvironment({
// so that \strut@ is the same as \strut.
defineEnvironment({
type: "array",
names: ["align", "align*", "aligned"],
names: ["align", "align*", "aligned", "split"],
props: {
numArgs: 0,
},
@@ -896,13 +924,7 @@ defineEnvironment({
},
handler(context) {
if (utils.contains(["gather", "gather*"], context.envName)) {
const settings = context.parser.settings;
if (!settings.displayMode) {
throw new ParseError(`{${context.envName}} cannot be used inline.`);
} else if (settings.strict && !settings.topEnv) {
settings.reportNonstrict("textEnv",
`{${context.envName}} called from math mode.`);
}
validateAmsEnvironmentContext(context);
}
const res = {
cols: [{
@@ -934,6 +956,26 @@ defineEnvironment({
mathmlBuilder,
});
defineEnvironment({
type: "array",
names: ["equation", "equation*"],
props: {
numArgs: 0,
},
handler(context) {
validateAmsEnvironmentContext(context);
const res = {
addEqnNum: context.envName === "equation",
singleRow: true,
maxNumCols: 1,
leqno: context.parser.settings.leqno,
};
return parseArray(context.parser, res, "display");
},
htmlBuilder,
mathmlBuilder,
});
// Catch \hline outside array environment
defineFunction({
type: "text", // Doesn't matter what this is.

View File

@@ -2727,8 +2727,6 @@ describe("An aligned environment", function() {
});
describe("AMS environments", function() {
const nonStrictDisplay = new Settings({displayMode: true, strict: false});
it("should fail outside display mode", () => {
expect`\begin{gather}a+b\\c+d\end{gather}`.not.toParse(nonstrictSettings);
expect`\begin{gather*}a+b\\c+d\end{gather*}`.not.toParse(nonstrictSettings);
@@ -2736,8 +2734,11 @@ describe("AMS environments", function() {
expect`\begin{align*}a&=b+c\\d+e&=f\end{align*}`.not.toParse(nonstrictSettings);
expect`\begin{alignat}{2}10&x+ &3&y = 2\\3&x+&13&y = 4\end{alignat}`.not.toParse(nonstrictSettings);
expect`\begin{alignat*}{2}10&x+ &3&y = 2\\3&x+&13&y = 4\end{alignat*}`.not.toParse(nonstrictSettings);
expect`\begin{equation}a=b+c\end{equation}`.not.toParse(nonstrictSettings);
expect`\begin{split}a &=b+c\\&=e+f\end{split}`.not.toParse(nonstrictSettings);
});
const nonStrictDisplay = new Settings({displayMode: true, strict: false});
it("should build if in non-strict display mode", () => {
expect`\begin{gather}a+b\\c+d\end{gather}`.toBuild(nonStrictDisplay);
expect`\begin{gather*}a+b\\c+d\end{gather*}`.toBuild(nonStrictDisplay);
@@ -2745,6 +2746,22 @@ describe("AMS environments", function() {
expect`\begin{align*}a&=b+c\\d+e&=f\end{align*}`.toBuild(nonStrictDisplay);
expect`\begin{alignat}{2}10&x+ &3&y = 2\\3&x+&13&y = 4\end{alignat}`.toBuild(nonStrictDisplay);
expect`\begin{alignat*}{2}10&x+ &3&y = 2\\3&x+&13&y = 4\end{alignat*}`.toBuild(nonStrictDisplay);
expect`\begin{equation}a=b+c\end{equation}`.toBuild(nonStrictDisplay);
expect`\begin{equation}\begin{split}a &=b+c\\&=e+f\end{split}\end{equation}`.toBuild(nonStrictDisplay);
expect`\begin{split}a &=b+c\\&=e+f\end{split}`.toBuild(nonStrictDisplay);
});
it("{equation} should fail if argument contains two rows.", () => {
expect`\begin{equation}a=\cr b+c\end{equation}`.not.toParse(nonStrictDisplay);
});
it("{equation} should fail if argument contains two columns.", () => {
expect`\begin{equation}a &=b+c\end{equation}`.not.toBuild(nonStrictDisplay);
});
it("{split} should fail if argument contains three columns.", () => {
expect`\begin{equation}\begin{split}a &=b &+c\\&=e &+f\end{split}\end{equation}`.not.toBuild(nonStrictDisplay);
});
it("{array} should fail if body contains more columns than specification.", () => {
expect`\begin{array}{2}a & b & c\\d & e f\end{array}`.not.toBuild(nonStrictDisplay);
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -15,7 +15,7 @@
Accents: \vec{A}\vec{x}\vec x^2\vec{x}_2^2\vec{A}^2\vec{xA}^2
AccentsText: |
\begin{array}{l}
\begin{array}{lccccc}
\text{\'\i} & \text{\.\i} & \text{\`\i} & \text{\"\i} & \text{\H\i} & \text{\r\i} \\
\text{\'\j} & \text{\.\j} & \text{\`\j} & \text{\"\j} & \text{\H\j} & \text{\r\j} \\
\text{\'a} & \text{\.a} & \text{\`a} & \text{\"a} & \text{\H{a}} & \text{\r{a}} \\
@@ -129,6 +129,9 @@ Dots: |
\cdots;\dots+\dots\int\dots,\dots \\
\cdots{};\ldots+\ldots\int\ldots,\ldots
\end{array}
Equation:
tex: \begin{equation}\begin{split}a& =b+c-d \\ & \quad +e-f \\ & =g+h \\ & =i \end{split}\end{equation}
display: 1
Exponents: a^{a^a_a}_{a^a_a}
ExtensibleArrows: |
\begin{array}{l}