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
@@ -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> `a & b \\`<br> `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$||
|
||||
|
@@ -85,6 +85,7 @@ $( \big( \Big( \bigg( \Bigg($ `( \big( \Big( \bigg( \Bigg(`
|
||||
|$\begin{Bmatrix} a & b \\ c & d \end{Bmatrix}$ |`\begin{Bmatrix}`<br> `a & b \\`<br> `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> `\begin{array}{c:c:c}`<br> `a & b & c \\ \hline`<br> `d & e & f \\`<br> `\hdashline`<br> `g & h & i`<br>`\end{array}`
|
||||
|$\begin{aligned} a&=b+c \\ d+e&=f \end{aligned}$ |`\begin{aligned}`<br> `a&=b+c \\`<br> `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> `10&x+ &3&y = 2 \\`<br> ` 3&x+&13&y = 4`<br>`\end{alignedat}`
|
||||
|$\begin{gathered} a=b \\ e=b+c \end{gathered}$ |`\begin{gathered}`<br> `a=b \\ `<br> `e=b+c`<br>`\end{gathered}`|$x = \begin{cases} a &\text{if } b \\ c &\text{if } d \end{cases}$ |`x = \begin{cases}`<br> `a &\text{if } b \\`<br> `c &\text{if } d`<br>`\end{cases}`
|
||||
|$\begin{smallmatrix} a & b \\ c & d \end{smallmatrix}$ | `\begin{smallmatrix}`<br> `a & b \\`<br> `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$}`
|
||||
|
||||
|
@@ -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.
|
||||
|
@@ -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{}" +
|
||||
|
@@ -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() {
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 24 KiB |
@@ -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: |
|
||||
|