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

@@ -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{}" +