mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-11 05:58:40 +00:00
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:
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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);
|
||||
|
@@ -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");
|
||||
},
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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++) {
|
||||
|
@@ -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",
|
||||
|
Reference in New Issue
Block a user