Support {gather} and {gather*} (#2183)

* Support `gather` and `gather*`

* Add bogus screenshots

* Add correct screenshots

* Fix typo

* Update screenshots

* Improve error handling

* Scope styles inside .katex{}

* Revert console.log to console.error
This commit is contained in:
Ron Kok
2020-07-27 06:56:39 -07:00
committed by GitHub
parent 0f0e40d226
commit 80b0e3dc20
12 changed files with 153 additions and 17 deletions

View File

@@ -67,6 +67,7 @@ export type SettingsOptions = {
maxSize?: number;
maxExpand?: number;
globalGroup?: boolean;
topEnv?: boolean;
};
/**
@@ -94,6 +95,7 @@ export default class Settings {
maxSize: number;
maxExpand: number;
globalGroup: boolean;
topEnv: boolean;
constructor(options: SettingsOptions) {
// allow null options
@@ -115,6 +117,7 @@ export default class Settings {
this.maxSize = Math.max(0, utils.deflt(options.maxSize, Infinity));
this.maxExpand = Math.max(0, utils.deflt(options.maxExpand, 1000));
this.globalGroup = utils.deflt(options.globalGroup, false);
this.topEnv = utils.deflt(options.topEnv, false);
}
/**

View File

@@ -318,6 +318,12 @@ export default function buildHTML(tree: AnyParseNode[], options: Options): DomSp
// Build the expression contained in the tree
const expression = buildExpression(tree, options, "root");
let eqnNum;
if (expression.length === 2 && expression[1].hasClass("tag")) {
// An environment with automatic equation numbers, e.g. {gather}.
eqnNum = expression.pop();
}
const children = [];
// Create one base node for each chunk between potential line breaks.
@@ -373,6 +379,8 @@ export default function buildHTML(tree: AnyParseNode[], options: Options): DomSp
);
tagChild.classes = ["tag"];
children.push(tagChild);
} else if (eqnNum) {
children.push(eqnNum);
}
const htmlNode = makeSpan(["katex-html"], children);

View File

@@ -27,7 +27,7 @@ export type AlignSpec = { type: "separator", separator: string } | {
};
// Type to indicate column separation in MathML
export type ColSeparationType = "align" | "alignat" | "small";
export type ColSeparationType = "align" | "alignat" | "gather" | "small";
function getHLines(parser: Parser): boolean[] {
// Return an array. The array length = number of hlines.
@@ -52,12 +52,22 @@ function getHLines(parser: Parser): boolean[] {
*/
function parseArray(
parser: Parser,
{hskipBeforeAndAfter, addJot, cols, arraystretch, colSeparationType}: {|
{
hskipBeforeAndAfter,
addJot,
cols,
arraystretch,
colSeparationType,
addEqnNum,
leqno,
}: {|
hskipBeforeAndAfter?: boolean,
addJot?: boolean,
cols?: AlignSpec[],
arraystretch?: number,
colSeparationType?: ColSeparationType,
addEqnNum?: boolean,
leqno?: boolean,
|},
style: StyleStr,
): ParseNode<"array"> {
@@ -156,6 +166,8 @@ function parseArray(
hskipBeforeAndAfter,
hLinesBeforeRow,
colSeparationType,
addEqnNum,
leqno,
};
}
@@ -283,6 +295,21 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
const cols = [];
let colSep;
let colDescrNum;
const eqnNumSpans = [];
if (group.addEqnNum) {
// An environment with automatic equation numbers.
// Create node(s) that will trigger CSS counter increment.
for (r = 0; r < nr; ++r) {
const rw = body[r];
const shift = rw.pos - offset;
const eqnTag = buildCommon.makeSpan(["eqn-num"], [], options);
eqnTag.depth = rw.depth;
eqnTag.height = rw.height;
eqnNumSpans.push({type: "elem", elem: eqnTag, shift});
}
}
for (c = 0, colDescrNum = 0;
// Continue while either there are more columns or more column
// descriptions, so trailing separators don't get lost.
@@ -393,7 +420,16 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
}, options);
}
return buildCommon.makeSpan(["mord"], [body], options);
if (!group.addEqnNum) {
return buildCommon.makeSpan(["mord"], [body], options);
} else {
let eqnNumCol = buildCommon.makeVList({
positionType: "individualShift",
children: eqnNumSpans,
}, options);
eqnNumCol = buildCommon.makeSpan(["tag"], [eqnNumCol], options);
return buildCommon.makeFragment([body, eqnNumCol]);
}
};
const alignMap = {
@@ -403,14 +439,28 @@ const alignMap = {
};
const mathmlBuilder: MathMLBuilder<"array"> = function(group, options) {
let table = new mathMLTree.MathNode(
"mtable", group.body.map(function(row) {
return new mathMLTree.MathNode(
"mtr", row.map(function(cell) {
return new mathMLTree.MathNode(
"mtd", [mml.buildGroup(cell, options)]);
}));
}));
const tbl = [];
const glue = new mathMLTree.MathNode("mtd", [], ["mtr-glue"]);
const tag = new mathMLTree.MathNode("mtd", [], ["mml-eqn-num"]);
for (let i = 0; i < group.body.length; i++) {
const rw = group.body[i];
const row = [];
for (let j = 0; j < rw.length; j++) {
row.push(new mathMLTree.MathNode("mtd",
[mml.buildGroup(rw[j], options)]));
}
if (group.addEqnNum) {
row.unshift(glue);
row.push(glue);
if (group.leqno) {
row.unshift(tag);
} else {
row.push(tag);
}
}
tbl.push(new mathMLTree.MathNode("mtr", row));
}
let table = new mathMLTree.MathNode("mtable", tbl);
// Set column alignment, row spacing, column spacing, and
// array lines by setting attributes on the table element.
@@ -487,7 +537,8 @@ const mathmlBuilder: MathMLBuilder<"array"> = function(group, options) {
spacing += i % 2 ? "0em " : "1em ";
}
table.setAttribute("columnspacing", spacing.trim());
} else if (group.colSeparationType === "alignat") {
} else if (group.colSeparationType === "alignat" ||
group.colSeparationType === "gather") {
table.setAttribute("columnspacing", "0em");
} else if (group.colSeparationType === "small") {
table.setAttribute("columnspacing", "0.2778em");
@@ -817,17 +868,29 @@ defineEnvironment({
// and contents are set in \displaystyle.
defineEnvironment({
type: "array",
names: ["gathered"],
names: ["gathered", "gather", "gather*"],
props: {
numArgs: 0,
},
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.`);
}
}
const res = {
cols: [{
type: "align",
align: "c",
}],
addJot: true,
colSeparationType: "gather",
addEqnNum: context.envName === "gather",
leqno: context.parser.settings.leqno,
};
return parseArray(context.parser, res, "display");
},

View File

@@ -600,6 +600,20 @@
.anglpad {
padding: 0 0.03889em 0 0.03889em; // pad 1mu left and right (in scriptstyle)
}
.eqn-num::before {
counter-increment: katexEqnNo;
content: "(" counter(katexEqnNo) ")";
}
.mml-eqn-num::before {
counter-increment: mmlEqnNo;
content: "(" counter(mmlEqnNo) ")";
}
.mtr-glue {
width: 50%;
}
}
.katex-display {
@@ -635,3 +649,9 @@
text-align: left;
padding-left: 2em;
}
// Automatic equation numbers for some environments.
// Use parallel counters for HTML and MathML.
body {
counter-reset: katexEqnNo mmlEqnNo;
}

View File

@@ -11,6 +11,7 @@
import utils from "./utils";
import {DocumentFragment} from "./tree";
import {createClass} from "./domTree";
import type {VirtualNode} from "./tree";
@@ -47,11 +48,17 @@ export class MathNode implements MathDomNode {
type: MathNodeType;
attributes: {[string]: string};
children: $ReadOnlyArray<MathDomNode>;
classes: string[];
constructor(type: MathNodeType, children?: $ReadOnlyArray<MathDomNode>) {
constructor(
type: MathNodeType,
children?: $ReadOnlyArray<MathDomNode>,
classes?: string[]
) {
this.type = type;
this.attributes = {};
this.children = children || [];
this.classes = classes || [];
}
/**
@@ -82,6 +89,10 @@ export class MathNode implements MathDomNode {
}
}
if (this.classes.length > 0) {
node.className = createClass(this.classes);
}
for (let i = 0; i < this.children.length; i++) {
node.appendChild(this.children[i].toNode());
}
@@ -104,6 +115,10 @@ export class MathNode implements MathDomNode {
}
}
if (this.classes.length > 0) {
markup += ` class ="${utils.escape(createClass(this.classes))}"`;
}
markup += ">";
for (let i = 0; i < this.children.length; i++) {

View File

@@ -39,6 +39,8 @@ type ParseNodeTypes = {
body: AnyParseNode[][], // List of rows in the (2D) array.
rowGaps: (?Measurement)[],
hLinesBeforeRow: Array<boolean[]>,
addEqnNum?: boolean,
leqno?: boolean,
|},
"color": {|
type: "color",