Support {smallmatrix}, {subarray}, and \substack (#1969)

* Support {smallmatrix}, {subarray}, and \substack

* Fix lint errors

* Update docs. Screenshots step 1.

* Screenshots step 2

* Screenshots step 3

* Pick up review comments

* Fixed arraycolsep for {smallmatrix}

* Fixed lint error

* Updated screenshots
This commit is contained in:
Ron Kok
2019-05-20 20:34:49 -07:00
committed by Kevin Barabash
parent 6b0f06df21
commit 3b60aee16c
10 changed files with 135 additions and 15 deletions

View File

@@ -928,7 +928,7 @@ table td {
|\small|$\small small$|`\small small`|
|\smallfrown|$\smallfrown$||
|\smallint|$\smallint$||
|{smallmatrix}|<span style="color:firebrick;">Not supported</span>||
|{smallmatrix}|$\begin{smallmatrix} a & b \\ c & d \end{smallmatrix}$|`\begin{smallmatrix}`<br>&nbsp;&nbsp;&nbsp;`a & b \\`<br>&nbsp;&nbsp;&nbsp;`c & d`<br>`\end{smallmatrix}`|
|\smallsetminus|$\smallsetminus$||
|\smallsmile|$\smallsmile$||
|\smash|$\left(x^{\smash{2}}\right)$|`\left(x^{\smash{2}}\right)`|
@@ -965,7 +965,7 @@ table td {
|\subseteqq|$\subseteqq$||
|\subsetneq|$\subsetneq$||
|\subsetneqq|$\subsetneqq$||
|\substack|<span style="color:firebrick;">Not supported</span>||
|\substack|$$\sum_{\substack{0<i<m\\0<j<n}}$$|`\sum_{\substack{0<i<m\\0<j<n}}`|
|\succ|$\succ$||
|\succapprox|$\succapprox$||
|\succcurlyeq|$\succcurlyeq$||

View File

@@ -85,6 +85,7 @@ $( \big( \Big( \bigg( \Bigg($ `( \big( \Big( \bigg( \Bigg(`
|$\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{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}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}`
|$\begin{gathered} a=b \\ e=b+c \end{gathered}$ |`\begin{gathered}`<br>&nbsp;&nbsp;&nbsp;`a=b \\ `<br>&nbsp;&nbsp;&nbsp;`e=b+c`<br>`\end{gathered}`|$x = \begin{cases} a &\text{if } b \\ c &\text{if } d \end{cases}$ |`x = \begin{cases}`<br>&nbsp;&nbsp;&nbsp;`a &\text{if } b \\`<br>&nbsp;&nbsp;&nbsp;`c &\text{if } d`<br>`\end{cases}`
|$\begin{smallmatrix} a & b \\ c & d \end{smallmatrix}$ | `\begin{smallmatrix}`<br>&nbsp;&nbsp;&nbsp;`a & b \\`<br>&nbsp;&nbsp;&nbsp;`c & d`<br>`\end{smallmatrix}` | | |
</div>
@@ -205,7 +206,7 @@ In display math, KaTeX does not insert automatic line breaks. It ignores display
|:--------------|:----------------------------------------|:-----
|$x_n$ `x_n` |$\stackrel{!}{=}$ `\stackrel{!}{=}` |$a \atop b$ `a \atop b`
|$e^x$ `e^x` |$\overset{!}{=}$ `\overset{!}{=}` |$a\raisebox{0.25em}{b}c$ `a\raisebox{0.25em}{b}c`
|$_u^o $ `_u^o `|$\underset{!}{=}$ `\underset{!}{=}`
|$_u^o $ `_u^o `|$\underset{!}{=}$ `\underset{!}{=}` | $$\sum_{\substack{0<i<m\\0<j<n}}$$ `\sum_{\substack{0<i<m\\0<j<n}}`
The second argument of `\raisebox` can contain math if it is nested within `$…$` delimiters, as in `\raisebox{0.25em}{$\frac a b$}`

View File

@@ -1,5 +1,6 @@
// @flow
import buildCommon from "../buildCommon";
import Style from "../Style";
import defineEnvironment from "../defineEnvironment";
import defineFunction from "../defineFunction";
import mathMLTree from "../mathMLTree";
@@ -26,7 +27,7 @@ export type AlignSpec = { type: "separator", separator: string } | {
};
// Type to indicate column separation in MathML
export type ColSeparationType = "align" | "alignat";
export type ColSeparationType = "align" | "alignat" | "small";
function getHLines(parser: Parser): boolean[] {
// Return an array. The array length = number of hlines.
@@ -175,7 +176,16 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
// Horizontal spacing
const pt = 1 / options.fontMetrics().ptPerEm;
const arraycolsep = 5 * pt; // \arraycolsep in article.cls
let arraycolsep = 5 * pt; // default value, i.e. \arraycolsep in article.cls
if (group.colSeparationType && group.colSeparationType === "small") {
// We're in a {smallmatrix}. Default column space is \thickspace,
// i.e. 5/18em = 0.2778em, per amsmath.dtx for {smallmatrix}.
// But that needs adjustment because LaTeX applies \scriptstyle to the
// entire array, including the colspace, but this function applies
// \scriptstyle only inside each element.
const localMultiplier = options.havingStyle(Style.SCRIPT).sizeMultiplier;
arraycolsep = 0.2778 * (localMultiplier / options.sizeMultiplier);
}
// Vertical spacing
const baselineskip = 12 * pt; // see size10.clo
@@ -379,7 +389,7 @@ const alignMap = {
};
const mathmlBuilder: MathMLBuilder<"array"> = function(group, options) {
const table = new mathMLTree.MathNode(
let table = new mathMLTree.MathNode(
"mtable", group.body.map(function(row) {
return new mathMLTree.MathNode(
"mtr", row.map(function(cell) {
@@ -401,7 +411,9 @@ const mathmlBuilder: MathMLBuilder<"array"> = function(group, options) {
// The 0.16 and 0.09 values are found emprically. They produce an array
// similar to LaTeX and in which content does not interfere with \hines.
const gap = 0.16 + group.arraystretch - 1 + (group.addJot ? 0.09 : 0);
const gap = (group.arraystretch === 0.5)
? 0.1 // {smallmatrix}, {subarray}
: 0.16 + group.arraystretch - 1 + (group.addJot ? 0.09 : 0);
table.setAttribute("rowspacing", gap + "em");
// MathML table lines go only between cells.
@@ -463,6 +475,8 @@ const mathmlBuilder: MathMLBuilder<"array"> = function(group, options) {
table.setAttribute("columnspacing", spacing.trim());
} else if (group.colSeparationType === "alignat") {
table.setAttribute("columnspacing", "0em");
} else if (group.colSeparationType === "small") {
table.setAttribute("columnspacing", "0.2778em");
} else {
table.setAttribute("columnspacing", "1em");
}
@@ -484,13 +498,18 @@ const mathmlBuilder: MathMLBuilder<"array"> = function(group, options) {
table.setAttribute("rowlines", rowLines.trim());
}
if (menclose === "") {
return table;
} else {
const wrapper = new mathMLTree.MathNode("menclose", [table]);
wrapper.setAttribute("notation", menclose.trim());
return wrapper;
if (menclose !== "") {
table = new mathMLTree.MathNode("menclose", [table]);
table.setAttribute("notation", menclose.trim());
}
if (group.arraystretch && group.arraystretch < 1) {
// A small array. Wrap in scriptstyle so row gap is not too large.
table = new mathMLTree.MathNode("mstyle", [table]);
table.setAttribute("scriptlevel", "1");
}
return table;
};
// Convenience function for aligned and alignedat environments.
@@ -656,6 +675,63 @@ defineEnvironment({
mathmlBuilder,
});
defineEnvironment({
type: "array",
names: ["smallmatrix"],
props: {
numArgs: 0,
},
handler(context) {
const payload = {arraystretch: 0.5};
const res = parseArray(context.parser, payload, "script");
res.colSeparationType = "small";
return res;
},
htmlBuilder,
mathmlBuilder,
});
defineEnvironment({
type: "array",
names: ["subarray"],
props: {
numArgs: 1,
},
handler(context, args) {
// Parsing of {subarray} is similar to {array}
const symNode = checkSymbolNodeType(args[0]);
const colalign: AnyParseNode[] =
symNode ? [args[0]] : assertNodeType(args[0], "ordgroup").body;
const cols = colalign.map(function(nde) {
const node = assertSymbolNodeType(nde);
const ca = node.text;
// {subarray} only recognizes "l" & "c"
if ("lc".indexOf(ca) !== -1) {
return {
type: "align",
align: ca,
};
}
throw new ParseError("Unknown column alignment: " + ca, nde);
});
if (cols.length > 1) {
throw new ParseError("{subarray} can contain only one column");
}
let res = {
cols,
hskipBeforeAndAfter: false,
arraystretch: 0.5,
};
res = parseArray(context.parser, res, "script");
if (res.body[0].length > 1) {
throw new ParseError("{subarray} can contain only one column");
}
return res;
},
htmlBuilder,
mathmlBuilder,
});
// A cases environment (in amsmath.sty) is almost equivalent to
// \def\arraystretch{1.2}%
// \left\{\begin{array}{@{}l@{\quad}l@{}} … \end{array}\right.

View File

@@ -426,6 +426,9 @@ defineMacro("\\varPhi", "\\mathit{\\Phi}");
defineMacro("\\varPsi", "\\mathit{\\Psi}");
defineMacro("\\varOmega", "\\mathit{\\Omega}");
//\newcommand{\substack}[1]{\subarray{c}#1\endsubarray}
defineMacro("\\substack", "\\begin{subarray}{c}#1\\end{subarray}");
// \renewcommand{\colon}{\nobreak\mskip2mu\mathpunct{}\nonscript
// \mkern-\thinmuskip{:}\mskip6muplus1mu\relax}
defineMacro("\\colon", "\\nobreak\\mskip2mu\\mathpunct{}" +

View File

@@ -2582,6 +2582,43 @@ describe("An array environment", function() {
});
describe("A subarray environment", function() {
it("should accept only a single alignment character", function() {
const parse = getParsed`\begin{subarray}{c}a \\ b\end{subarray}`;
expect(parse[0].type).toBe("array");
expect(parse[0].cols).toEqual([
{type: "align", align: "c"},
]);
expect`\begin{subarray}{cc}a \\ b\end{subarray}`.not.toParse();
expect`\begin{subarray}{c}a & b \\ c & d\end{subarray}`.not.toParse();
expect`\begin{subarray}{c}a \\ b\end{subarray}`.toBuild();
});
});
describe("A substack function", function() {
it("should build", function() {
expect`\sum_{\substack{ 0<i<m \\ 0<j<n }} P(i,j)`.toBuild();
});
it("should accommodate spaces in the argument", function() {
expect`\sum_{\substack{ 0<i<m \\ 0<j<n }} P(i,j)`.toBuild();
});
it("should accommodate macros in the argument", function() {
expect`\sum_{\substack{ 0<i<\varPi \\ 0<j<\pi }} P(i,j)`.toBuild();
});
});
describe("A smallmatrix environment", function() {
it("should build", function() {
expect`\begin{smallmatrix} a & b \\ c & d \end{smallmatrix}`.toBuild();
});
});
describe("A cases environment", function() {
it("should parse its input", function() {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -37,7 +37,9 @@ Arrays: |
1&2&3\\ \hline
1+1&2+1&3+1\cr1\over2&\scriptstyle 1/2&\frac12\\[1ex] \hline \hdashline
\begin{pmatrix}x\\y\end{pmatrix}&0&\begin{vmatrix}a&b\\c&d\end{vmatrix}
\end{array}\right]
\end{array}\right] \\
\begin{smallmatrix} a & b \\ c & d \end{smallmatrix} \;\;
\begin{subarray}{c}a \\ b\end{subarray}
ArrayMode:
tex: |
\begin{matrix}
@@ -270,7 +272,8 @@ OpLimits: |
\begin{matrix}
{\sin_2^2 \lim_2^2 \int_2^2 \sum_2^2}
{\displaystyle \lim_2^2 \int_2^2 \intop_2^2 \sum_2^2} \\
\limsup_{x \rightarrow \infty} x \stackrel{?}= \liminf_{x \rightarrow \infty} x
\limsup_{x \rightarrow \infty} x \stackrel{?}= \liminf_{x \rightarrow \infty} x \\
\displaystyle \sum_{\substack{0<i<m\\0<j<n}}
\end{matrix}
OverUnderline: x\underline{x}\underline{\underline{x}}\underline{x_{x_{x_x}}}\underline{x^{x^{x^x}}}\overline{x}\overline{x}\overline{x^{x^{x^x}}} \blue{\overline{\underline{x}}\underline{\overline{x}}}
OverUnderset: |