mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-12 14:38:39 +00:00
feat: Support {CD} (#2396)
* Support {CD} * Edit screenshotter test to fit on one page * Update screenshots * Remove bogus Safari screenshot * Edit documentation to avoid tag conflicts and explain delimiters * Add type annotations * Add bogus safari screenshot * Update with real Safari screenshot * Set label vertical alignment * Revise call to parseExpression() per PR 2085 changes to macro parsing * Update Firefox screenshot * Pick up review comments * Add unit tests and snapshot. * Tighten up label collection. * Better loop index * remove extra space * Picked up comments. Added a parse check. Added a test. Co-authored-by: ylemkimon <y@ylem.kim> Co-authored-by: Kevin Barabash <kevinb@khanacademy.org> Co-authored-by: Kevin Barabash <kevinb7@gmail.com>
This commit is contained in:
@@ -647,6 +647,14 @@ const handleObject = (
|
|||||||
throw new Error("KaTeX-a11y: xArrow not implemented yet");
|
throw new Error("KaTeX-a11y: xArrow not implemented yet");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "cdlabel": {
|
||||||
|
throw new Error("KaTeX-a11y: cdlabel not implemented yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
case "cdlabelparent": {
|
||||||
|
throw new Error("KaTeX-a11y: cdlabelparent not implemented yet");
|
||||||
|
}
|
||||||
|
|
||||||
case "mclass": {
|
case "mclass": {
|
||||||
// \neq and \ne are macros so we let "htmlmathml" render the mathmal
|
// \neq and \ne are macros so we let "htmlmathml" render the mathmal
|
||||||
// side of things and extract the text from that.
|
// side of things and extract the text from that.
|
||||||
|
@@ -215,7 +215,7 @@ table td {
|
|||||||
|\cap|$\cap$||
|
|\cap|$\cap$||
|
||||||
|{cases}|$\begin{cases}a&\text{if }b\\c&\text{if }d\end{cases}$|`\begin{cases}`<br> `a &\text{if } b \\`<br> `c &\text{if } d`<br>`\end{cases}`|
|
|{cases}|$\begin{cases}a&\text{if }b\\c&\text{if }d\end{cases}$|`\begin{cases}`<br> `a &\text{if } b \\`<br> `c &\text{if } d`<br>`\end{cases}`|
|
||||||
|\cases|<span style="color:firebrick;">Not supported</span>|see `{cases}`|
|
|\cases|<span style="color:firebrick;">Not supported</span>|see `{cases}`|
|
||||||
|{CD}|<span style="color:firebrick;">Not supported</span>||
|
|{CD}|$$\begin{CD}A @>a>> B \\@VbVV @AAcA\\C @= D\end{CD}$$|`\begin{CD}`<br> `A @>a>> B \\`<br>`@VbVV @AAcA \\`<br> `C @= D`<br>`\end{CD}`|
|
||||||
|\cdot|$\cdot$||
|
|\cdot|$\cdot$||
|
||||||
|\cdotp|$\cdotp$||
|
|\cdotp|$\cdotp$||
|
||||||
|\cdots|$\cdots$||
|
|\cdots|$\cdots$||
|
||||||
|
@@ -83,17 +83,24 @@ $( \big( \Big( \bigg( \Bigg($ `( \big( \Big( \bigg( \Bigg(`
|
|||||||
|$\begin{pmatrix} a & b \\ c & d \end{pmatrix}$ |`\begin{pmatrix}`<br> `a & b \\`<br> `c & d`<br>`\end{pmatrix}` |$\begin{bmatrix} a & b \\ c & d \end{bmatrix}$ | `\begin{bmatrix}`<br> `a & b \\`<br> `c & d`<br>`\end{bmatrix}`
|
|$\begin{pmatrix} a & b \\ c & d \end{pmatrix}$ |`\begin{pmatrix}`<br> `a & b \\`<br> `c & d`<br>`\end{pmatrix}` |$\begin{bmatrix} a & b \\ c & d \end{bmatrix}$ | `\begin{bmatrix}`<br> `a & b \\`<br> `c & d`<br>`\end{bmatrix}`
|
||||||
|$\begin{vmatrix} a & b \\ c & d \end{vmatrix}$ |`\begin{vmatrix}`<br> `a & b \\`<br> `c & d`<br>`\end{vmatrix}` |$\begin{Vmatrix} a & b \\ c & d \end{Vmatrix}$ |`\begin{Vmatrix}`<br> `a & b \\`<br> `c & d`<br>`\end{Vmatrix}`
|
|$\begin{vmatrix} a & b \\ c & d \end{vmatrix}$ |`\begin{vmatrix}`<br> `a & b \\`<br> `c & d`<br>`\end{vmatrix}` |$\begin{Vmatrix} a & b \\ c & d \end{Vmatrix}$ |`\begin{Vmatrix}`<br> `a & b \\`<br> `c & d`<br>`\end{Vmatrix}`
|
||||||
|$\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{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{equation}\begin{split}a &=b+c\\&=e+f\end{split}\end{equation}$$ |`\begin{equation}`<br>`\begin{split}` `a &=b+c\\`<br> `&=e+f`<br>`\end{split}`<br>`\end{equation}` |$$\begin{equation*}\begin{split}a &=b+c\\&=e+f\end{split}\end{equation*}$$ |`\begin{equation*}`<br>`\begin{split}` `a &=b+c\\`<br> `&=e+f`<br>`\end{split}`<br>`\end{equation*}`
|
|
||||||
|$$\begin{align} a&=b+c \\ d+e&=f \end{align}$$ |`\begin{align}`<br> `a&=b+c \\`<br> `d+e&=f`<br>`\end{align}`|$$\begin{alignat}{2}10&x+&3&y=2\\3&x+&13&y=4\end{alignat}$$ |
|
|
||||||
|$$\begin{align*} a&=b+c \\ d+e&=f \end{align*}$$ |`\begin{align*}`<br> `a&=b+c \\`<br> `d+e&=f`<br>`\end{align*}`|$\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}`<br> `10&x+ &3&y = 2 \\`<br> ` 3&x+&13&y = 4`<br>`\end{alignedat}`|$\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{gather} a=b \\ e=b+c \end{gather}$$ |`\begin{gather}`<br> `a=b \\ `<br> `e=b+c`<br>`\end{gather}`|$\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{rcases} a &\text{if } b \\ c &\text{if } d \end{rcases}⇒…$ |`\begin{rcases}`<br> `a &\text{if } b \\`<br> `c &\text{if } d`<br>`\end{rcases}⇒…`|
|
|$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{rcases} a &\text{if } b \\ c &\text{if } d \end{rcases}⇒…$ |`\begin{rcases}`<br> `a &\text{if } b \\`<br> `c &\text{if } d`<br>`\end{rcases}⇒…`|
|
||||||
|$\begin{smallmatrix} a & b \\ c & d \end{smallmatrix}$ | `\begin{smallmatrix}`<br> `a & b \\`<br> `c & d`<br>`\end{smallmatrix}` |||
|
|$\begin{smallmatrix} a & b \\ c & d \end{smallmatrix}$ | `\begin{smallmatrix}`<br> `a & b \\`<br> `c & d`<br>`\end{smallmatrix}` |$$\begin{CD}A @>a>> B \\@VbVV @AAcA\\C @= D\end{CD}$$ |`\begin{CD}`<br> `A @>a>> B \\`<br>`@VbVV @AAcA \\`<br> `C @= D`<br>`\\end{CD}`|
|
||||||
|
|
||||||
|
|||||
|
||||||
|
|:---------------------|:---------------------|
|
||||||
|
|$$\begin{align} a&=b+c \\ d+e&=f \end{align}$$ |`\begin{align}`<br> `a&=b+c \\`<br> `d+e&=f`<br>`\end{align}`
|
||||||
|
|$$\begin{alignat}{2}10&x+&3&y=2\\3&x+&13&y=4\end{alignat}$$ |`\begin{alignedat}{2}`<br> `10&x+ &3&y = 2 \\`<br> ` 3&x+&13&y = 4`<br>`\end{alignedat}`
|
||||||
|
|`\begin{equation}`<br>`\begin{split}` `a &=b+c\\`<br> `&=e+f`<br>`\end{split}`<br>`\end{equation}`
|
||||||
|
|$$\begin{gather} a=b \\ e=b+c \end{gather}$$ |`\begin{gather}`<br> `a=b \\ `<br> `e=b+c`<br>`\end{gather}`
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
KaTeX also supports `darray`, `dcases`, and `drcases`, which apply `displaystyle`.
|
KaTeX also supports:
|
||||||
|
* `darray`, `dcases`, and `drcases`, which render in `displaystyle`
|
||||||
|
* `align*`, `alignat*`, `gather*`, and `equation*`, which omit equation numbers
|
||||||
|
* `aligned`, `alignedat`, and `gathered`, which LaTeX will render while in math mode
|
||||||
|
|
||||||
|
Environments `align`, `alignat`, `CD`, `equation`, and `gather` (and their starred versions) may be called from display mode. This usually means that they may be called from within `$$…$$` delimiters or `\[…\]` delimiters. They may not be called from within `$…$` or `\(…\)` delimiters. In the KaTeX auto-render extension, as in LaTeX, these environments are themselves are a way to change from text mode to math display mode, and `\[…\]` delimiters are not necessary.
|
||||||
|
|
||||||
And KaTeX supports `matrix*`, `pmatrix*`, `bmatrix*`, `Bmatrix*`, `vmatrix*`, and `Vmatrix*`, which take an optional argument to set column alignment, as in `\begin{matrix}[1]`, `\begin{matrix}[c]`, or `\begin{matrix}[r]`.
|
And KaTeX supports `matrix*`, `pmatrix*`, `bmatrix*`, `Bmatrix*`, `vmatrix*`, and `Vmatrix*`, which take an optional argument to set column alignment, as in `\begin{matrix}[1]`, `\begin{matrix}[c]`, or `\begin{matrix}[r]`.
|
||||||
|
|
||||||
@@ -103,6 +110,8 @@ The `{array}` environment supports `|` and `:` vertical separators.
|
|||||||
|
|
||||||
The `{array}` environment does not yet support `\cline` or `\multicolumn`.
|
The `{array}` environment does not yet support `\cline` or `\multicolumn`.
|
||||||
|
|
||||||
|
`\tag` can not yet be applied to individual environment rows.
|
||||||
|
|
||||||
<div class="katex-hopscotch">
|
<div class="katex-hopscotch">
|
||||||
|
|
||||||
## HTML
|
## HTML
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
import buildCommon from "../buildCommon";
|
import buildCommon from "../buildCommon";
|
||||||
import Style from "../Style";
|
import Style from "../Style";
|
||||||
import defineEnvironment from "../defineEnvironment";
|
import defineEnvironment from "../defineEnvironment";
|
||||||
|
import {parseCD} from "./cd";
|
||||||
import defineFunction from "../defineFunction";
|
import defineFunction from "../defineFunction";
|
||||||
import mathMLTree from "../mathMLTree";
|
import mathMLTree from "../mathMLTree";
|
||||||
import ParseError from "../ParseError";
|
import ParseError from "../ParseError";
|
||||||
@@ -27,7 +28,7 @@ export type AlignSpec = { type: "separator", separator: string } | {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Type to indicate column separation in MathML
|
// Type to indicate column separation in MathML
|
||||||
export type ColSeparationType = "align" | "alignat" | "gather" | "small";
|
export type ColSeparationType = "align" | "alignat" | "gather" | "small" | "CD";
|
||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
function getHLines(parser: Parser): boolean[] {
|
function getHLines(parser: Parser): boolean[] {
|
||||||
@@ -256,7 +257,9 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Vertical spacing
|
// Vertical spacing
|
||||||
const baselineskip = 12 * pt; // see size10.clo
|
const baselineskip = group.colSeparationType === "CD"
|
||||||
|
? calculateSize({number: 3, unit: "ex"}, options)
|
||||||
|
: 12 * pt; // see size10.clo
|
||||||
// Default \jot from ltmath.dtx
|
// Default \jot from ltmath.dtx
|
||||||
// TODO(edemaine): allow overriding \jot via \setlength (#687)
|
// TODO(edemaine): allow overriding \jot via \setlength (#687)
|
||||||
const jot = 3 * pt;
|
const jot = 3 * pt;
|
||||||
@@ -516,7 +519,7 @@ const mathmlBuilder: MathMLBuilder<"array"> = function(group, options) {
|
|||||||
const gap = (group.arraystretch === 0.5)
|
const gap = (group.arraystretch === 0.5)
|
||||||
? 0.1 // {smallmatrix}, {subarray}
|
? 0.1 // {smallmatrix}, {subarray}
|
||||||
: 0.16 + group.arraystretch - 1 + (group.addJot ? 0.09 : 0);
|
: 0.16 + group.arraystretch - 1 + (group.addJot ? 0.09 : 0);
|
||||||
table.setAttribute("rowspacing", gap + "em");
|
table.setAttribute("rowspacing", gap.toFixed(4) + "em");
|
||||||
|
|
||||||
// MathML table lines go only between cells.
|
// MathML table lines go only between cells.
|
||||||
// To place a line on an edge we'll use <menclose>, if necessary.
|
// To place a line on an edge we'll use <menclose>, if necessary.
|
||||||
@@ -580,6 +583,8 @@ const mathmlBuilder: MathMLBuilder<"array"> = function(group, options) {
|
|||||||
table.setAttribute("columnspacing", "0em");
|
table.setAttribute("columnspacing", "0em");
|
||||||
} else if (group.colSeparationType === "small") {
|
} else if (group.colSeparationType === "small") {
|
||||||
table.setAttribute("columnspacing", "0.2778em");
|
table.setAttribute("columnspacing", "0.2778em");
|
||||||
|
} else if (group.colSeparationType === "CD") {
|
||||||
|
table.setAttribute("columnspacing", "0.5em");
|
||||||
} else {
|
} else {
|
||||||
table.setAttribute("columnspacing", "1em");
|
table.setAttribute("columnspacing", "1em");
|
||||||
}
|
}
|
||||||
@@ -1016,6 +1021,20 @@ defineEnvironment({
|
|||||||
mathmlBuilder,
|
mathmlBuilder,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defineEnvironment({
|
||||||
|
type: "array",
|
||||||
|
names: ["CD"],
|
||||||
|
props: {
|
||||||
|
numArgs: 0,
|
||||||
|
},
|
||||||
|
handler(context) {
|
||||||
|
validateAmsEnvironmentContext(context);
|
||||||
|
return parseCD(context.parser);
|
||||||
|
},
|
||||||
|
htmlBuilder,
|
||||||
|
mathmlBuilder,
|
||||||
|
});
|
||||||
|
|
||||||
// Catch \hline outside array environment
|
// Catch \hline outside array environment
|
||||||
defineFunction({
|
defineFunction({
|
||||||
type: "text", // Doesn't matter what this is.
|
type: "text", // Doesn't matter what this is.
|
||||||
|
312
src/environments/cd.js
Normal file
312
src/environments/cd.js
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
// @flow
|
||||||
|
import buildCommon from "../buildCommon";
|
||||||
|
import defineFunction from "../defineFunction";
|
||||||
|
import mathMLTree from "../mathMLTree";
|
||||||
|
import * as html from "../buildHTML";
|
||||||
|
import * as mml from "../buildMathML";
|
||||||
|
import {assertSymbolNodeType} from "../parseNode";
|
||||||
|
import ParseError from "../ParseError";
|
||||||
|
|
||||||
|
import type Parser from "../Parser";
|
||||||
|
import type {ParseNode, AnyParseNode} from "../parseNode";
|
||||||
|
|
||||||
|
const cdArrowFunctionName = {
|
||||||
|
">": "\\\\cdrightarrow",
|
||||||
|
"<": "\\\\cdleftarrow",
|
||||||
|
"=": "\\\\cdlongequal",
|
||||||
|
"A": "\\uparrow",
|
||||||
|
"V": "\\downarrow",
|
||||||
|
"|": "\\Vert",
|
||||||
|
".": "no arrow",
|
||||||
|
};
|
||||||
|
|
||||||
|
const newCell = () => {
|
||||||
|
// Create an empty cell, to be filled below with parse nodes.
|
||||||
|
// The parseTree from this module must be constructed like the
|
||||||
|
// one created by parseArray(), so an empty CD cell must
|
||||||
|
// be a ParseNode<"styling">. And CD is always displaystyle.
|
||||||
|
// So these values are fixed and flow can do implicit typing.
|
||||||
|
return {type: "styling", body: [], mode: "math", style: "display"};
|
||||||
|
};
|
||||||
|
|
||||||
|
const isStartOfArrow = (node: AnyParseNode) => {
|
||||||
|
return (node.type === "textord" && node.text === "@");
|
||||||
|
};
|
||||||
|
|
||||||
|
const isLabelEnd = (node: AnyParseNode, endChar: string): boolean => {
|
||||||
|
return ((node.type === "mathord" || node.type === "atom") &&
|
||||||
|
node.text === endChar);
|
||||||
|
};
|
||||||
|
|
||||||
|
function cdArrow(
|
||||||
|
arrowChar: string,
|
||||||
|
labels: ParseNode<"ordgroup">[],
|
||||||
|
parser: Parser
|
||||||
|
): AnyParseNode {
|
||||||
|
// Return a parse tree of an arrow and its labels.
|
||||||
|
// This acts in a way similar to a macro expansion.
|
||||||
|
const funcName = cdArrowFunctionName[arrowChar];
|
||||||
|
switch (funcName) {
|
||||||
|
case "\\\\cdrightarrow":
|
||||||
|
case "\\\\cdleftarrow":
|
||||||
|
return parser.callFunction(
|
||||||
|
funcName, [labels[0]], [labels[1]]
|
||||||
|
);
|
||||||
|
case "\\uparrow":
|
||||||
|
case "\\downarrow": {
|
||||||
|
const leftLabel = parser.callFunction(
|
||||||
|
"\\\\cdleft", [labels[0]], []
|
||||||
|
);
|
||||||
|
const bareArrow = {
|
||||||
|
type: "atom",
|
||||||
|
text: funcName,
|
||||||
|
mode: "math",
|
||||||
|
family: "rel",
|
||||||
|
};
|
||||||
|
const sizedArrow = parser.callFunction("\\Big", [bareArrow], []);
|
||||||
|
const rightLabel = parser.callFunction(
|
||||||
|
"\\\\cdright", [labels[1]], []
|
||||||
|
);
|
||||||
|
const arrowGroup = {
|
||||||
|
type: "ordgroup",
|
||||||
|
mode: "math",
|
||||||
|
body: [leftLabel, sizedArrow, rightLabel],
|
||||||
|
};
|
||||||
|
return parser.callFunction("\\\\cdparent", [arrowGroup], []);
|
||||||
|
}
|
||||||
|
case "\\\\cdlongequal":
|
||||||
|
return parser.callFunction("\\\\cdlongequal", [], []);
|
||||||
|
case "\\Vert": {
|
||||||
|
const arrow = {type: "textord", text: "\\Vert", mode: "math"};
|
||||||
|
return parser.callFunction("\\Big", [arrow], []);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return {type: "textord", text: " ", mode: "math"};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseCD(parser: Parser): ParseNode<"array"> {
|
||||||
|
// Get the array's parse nodes with \\ temporarily mapped to \cr.
|
||||||
|
const parsedRows: AnyParseNode[][] = [];
|
||||||
|
parser.gullet.beginGroup();
|
||||||
|
parser.gullet.macros.set("\\cr", "\\\\\\relax");
|
||||||
|
parser.gullet.beginGroup();
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
// Get the parse nodes for the next row.
|
||||||
|
parsedRows.push(parser.parseExpression(false, "\\\\"));
|
||||||
|
parser.gullet.endGroup();
|
||||||
|
parser.gullet.beginGroup();
|
||||||
|
const next = parser.fetch().text;
|
||||||
|
if (next === "&" || next === "\\\\") {
|
||||||
|
parser.consume();
|
||||||
|
} else if (next === "\\end") {
|
||||||
|
if (parsedRows[parsedRows.length - 1].length === 0) {
|
||||||
|
parsedRows.pop(); // final row ended in \\
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
throw new ParseError("Expected \\\\ or \\cr or \\end",
|
||||||
|
parser.nextToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let row = [];
|
||||||
|
const body = [row];
|
||||||
|
|
||||||
|
// Loop thru the parse nodes. Collect them into cells and arrows.
|
||||||
|
for (let i = 0; i < parsedRows.length; i++) {
|
||||||
|
// Start a new row.
|
||||||
|
const rowNodes = parsedRows[i];
|
||||||
|
// Create the first cell.
|
||||||
|
let cell = newCell();
|
||||||
|
|
||||||
|
for (let j = 0; j < rowNodes.length; j++) {
|
||||||
|
if (!isStartOfArrow(rowNodes[j])) {
|
||||||
|
// If a parseNode is not an arrow, it goes into a cell.
|
||||||
|
cell.body.push(rowNodes[j]);
|
||||||
|
} else {
|
||||||
|
// Parse node j is an "@", the start of an arrow.
|
||||||
|
// Before starting on the arrow, push the cell into `row`.
|
||||||
|
row.push(cell);
|
||||||
|
|
||||||
|
// Now collect parseNodes into an arrow.
|
||||||
|
// The character after "@" defines the arrow type.
|
||||||
|
j += 1;
|
||||||
|
const arrowChar = assertSymbolNodeType(rowNodes[j]).text;
|
||||||
|
|
||||||
|
// Create two empty label nodes. We may or may not use them.
|
||||||
|
const labels: ParseNode<"ordgroup">[] = new Array(2);
|
||||||
|
labels[0] = {type: "ordgroup", mode: "math", body: []};
|
||||||
|
labels[1] = {type: "ordgroup", mode: "math", body: []};
|
||||||
|
|
||||||
|
// Process the arrow.
|
||||||
|
if ("=|.".indexOf(arrowChar) > -1) {
|
||||||
|
// Three "arrows", ``@=`, `@|`, and `@.`, do not take labels.
|
||||||
|
// Do nothing here.
|
||||||
|
} else if ("<>AV".indexOf(arrowChar) > -1) {
|
||||||
|
// Four arrows, `@>>>`, `@<<<`, `@AAA`, and `@VVV`, each take
|
||||||
|
// two optional labels. E.g. the right-point arrow syntax is
|
||||||
|
// really: @>{optional label}>{optional label}>
|
||||||
|
// Collect parseNodes into labels.
|
||||||
|
for (let labelNum = 0; labelNum < 2; labelNum++) {
|
||||||
|
let inLabel = true;
|
||||||
|
for (let k = j + 1; k < rowNodes.length; k++) {
|
||||||
|
if (isLabelEnd(rowNodes[k], arrowChar)) {
|
||||||
|
inLabel = false;
|
||||||
|
j = k;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (isStartOfArrow(rowNodes[k])) {
|
||||||
|
throw new ParseError("Missing a " + arrowChar +
|
||||||
|
" character to complete a CD arrow.", rowNodes[k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
labels[labelNum].body.push(rowNodes[k]);
|
||||||
|
}
|
||||||
|
if (inLabel) {
|
||||||
|
// isLabelEnd never returned a true.
|
||||||
|
throw new ParseError("Missing a " + arrowChar +
|
||||||
|
" character to complete a CD arrow.", rowNodes[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new ParseError(`Expected one of "<>AV=|." after @`,
|
||||||
|
rowNodes[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now join the arrow to its labels.
|
||||||
|
const arrow: AnyParseNode = cdArrow(arrowChar, labels, parser);
|
||||||
|
|
||||||
|
// Wrap the arrow in ParseNode<"styling">.
|
||||||
|
// This is done to match parseArray() behavior.
|
||||||
|
const wrappedArrow = {
|
||||||
|
type: "styling",
|
||||||
|
body: [arrow],
|
||||||
|
mode: "math",
|
||||||
|
style: "display", // CD is always displaystyle.
|
||||||
|
};
|
||||||
|
row.push(wrappedArrow);
|
||||||
|
// In CD's syntax, cells are implicit. That is, everything that
|
||||||
|
// is not an arrow gets collected into a cell. So create an empty
|
||||||
|
// cell now. It will collect upcoming parseNodes.
|
||||||
|
cell = newCell();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i % 2 === 0) {
|
||||||
|
// Even-numbered rows consist of: cell, arrow, cell, arrow, ... cell
|
||||||
|
// The last cell is not yet pushed into `row`, so:
|
||||||
|
row.push(cell);
|
||||||
|
} else {
|
||||||
|
// Odd-numbered rows consist of: vert arrow, empty cell, ... vert arrow
|
||||||
|
// Remove the empty cell that was placed at the beginning of `row`.
|
||||||
|
row.shift();
|
||||||
|
}
|
||||||
|
row = [];
|
||||||
|
body.push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
// End row group
|
||||||
|
parser.gullet.endGroup();
|
||||||
|
// End array group defining \\
|
||||||
|
parser.gullet.endGroup();
|
||||||
|
|
||||||
|
// define column separation.
|
||||||
|
const cols = new Array(body[0].length).fill({
|
||||||
|
type: "align",
|
||||||
|
align: "c",
|
||||||
|
pregap: 0.25, // CD package sets \enskip between columns.
|
||||||
|
postgap: 0.25, // So pre and post each get half an \enskip, i.e. 0.25em.
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "array",
|
||||||
|
mode: "math",
|
||||||
|
body,
|
||||||
|
arraystretch: 1,
|
||||||
|
addJot: true,
|
||||||
|
rowGaps: [null],
|
||||||
|
cols,
|
||||||
|
colSeparationType: "CD",
|
||||||
|
hLinesBeforeRow: new Array(body.length + 1).fill([]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// The functions below are not available for general use.
|
||||||
|
// They are here only for internal use by the {CD} environment in placing labels
|
||||||
|
// next to vertical arrows.
|
||||||
|
|
||||||
|
// We don't need any such functions for horizontal arrows because we can reuse
|
||||||
|
// the functionality that already exists for extensible arrows.
|
||||||
|
|
||||||
|
defineFunction({
|
||||||
|
type: "cdlabel",
|
||||||
|
names: ["\\\\cdleft", "\\\\cdright"],
|
||||||
|
props: {
|
||||||
|
numArgs: 1,
|
||||||
|
},
|
||||||
|
handler({parser, funcName}, args) {
|
||||||
|
return {
|
||||||
|
type: "cdlabel",
|
||||||
|
mode: parser.mode,
|
||||||
|
side: funcName.slice(4),
|
||||||
|
label: args[0],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
htmlBuilder(group, options) {
|
||||||
|
const newOptions = options.havingStyle(options.style.sup());
|
||||||
|
const label = buildCommon.wrapFragment(
|
||||||
|
html.buildGroup(group.label, newOptions, options), options);
|
||||||
|
label.classes.push("cd-label-" + group.side);
|
||||||
|
label.style.bottom = (0.8 - label.depth) + "em";
|
||||||
|
// Zero out label height & depth, so vertical align of arrow is set
|
||||||
|
// by the arrow height, not by the label.
|
||||||
|
label.height = 0;
|
||||||
|
label.depth = 0;
|
||||||
|
return label;
|
||||||
|
},
|
||||||
|
mathmlBuilder(group, options) {
|
||||||
|
let label = new mathMLTree.MathNode("mrow",
|
||||||
|
[mml.buildGroup(group.label, options)]);
|
||||||
|
label = new mathMLTree.MathNode("mpadded", [label]);
|
||||||
|
label.setAttribute("width", "0");
|
||||||
|
if (group.side === "left") {
|
||||||
|
label.setAttribute("lspace", "-1width");
|
||||||
|
}
|
||||||
|
// We have to guess at vertical alignment. We know the arrow is 1.8em tall,
|
||||||
|
// But we don't know the height or depth of the label.
|
||||||
|
label.setAttribute("voffset", "0.7em");
|
||||||
|
label = new mathMLTree.MathNode("mstyle", [label]);
|
||||||
|
label.setAttribute("displaystyle", "false");
|
||||||
|
label.setAttribute("scriptlevel", "1");
|
||||||
|
return label;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
defineFunction({
|
||||||
|
type: "cdlabelparent",
|
||||||
|
names: ["\\\\cdparent"],
|
||||||
|
props: {
|
||||||
|
numArgs: 1,
|
||||||
|
},
|
||||||
|
handler({parser}, args) {
|
||||||
|
return {
|
||||||
|
type: "cdlabelparent",
|
||||||
|
mode: parser.mode,
|
||||||
|
fragment: args[0],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
htmlBuilder(group, options) {
|
||||||
|
// Wrap the vertical arrow and its labels.
|
||||||
|
// The parent gets position: relative. The child gets position: absolute.
|
||||||
|
// So CSS can locate the label correctly.
|
||||||
|
const parent = buildCommon.wrapFragment(
|
||||||
|
html.buildGroup(group.fragment, options), options
|
||||||
|
);
|
||||||
|
parent.classes.push("cd-vert-arrow");
|
||||||
|
return parent;
|
||||||
|
},
|
||||||
|
mathmlBuilder(group, options) {
|
||||||
|
return new mathMLTree.MathNode("mrow",
|
||||||
|
[mml.buildGroup(group.fragment, options)]);
|
||||||
|
},
|
||||||
|
});
|
@@ -10,6 +10,7 @@ export default functions;
|
|||||||
import "./functions/accent";
|
import "./functions/accent";
|
||||||
import "./functions/accentunder";
|
import "./functions/accentunder";
|
||||||
import "./functions/arrow";
|
import "./functions/arrow";
|
||||||
|
import "./environments/cd";
|
||||||
import "./functions/char";
|
import "./functions/char";
|
||||||
import "./functions/color";
|
import "./functions/color";
|
||||||
import "./functions/cr";
|
import "./functions/cr";
|
||||||
|
@@ -30,6 +30,8 @@ defineFunction({
|
|||||||
// The next 3 functions are here to support the mhchem extension.
|
// The next 3 functions are here to support the mhchem extension.
|
||||||
// Direct use of these functions is discouraged and may break someday.
|
// Direct use of these functions is discouraged and may break someday.
|
||||||
"\\xrightleftarrows", "\\xrightequilibrium", "\\xleftequilibrium",
|
"\\xrightleftarrows", "\\xrightequilibrium", "\\xleftequilibrium",
|
||||||
|
// The next 3 functions are here only to support the {CD} environment.
|
||||||
|
"\\\\cdrightarrow", "\\\\cdleftarrow", "\\\\cdlongequal",
|
||||||
],
|
],
|
||||||
props: {
|
props: {
|
||||||
numArgs: 1,
|
numArgs: 1,
|
||||||
@@ -57,7 +59,8 @@ defineFunction({
|
|||||||
let newOptions = options.havingStyle(style.sup());
|
let newOptions = options.havingStyle(style.sup());
|
||||||
const upperGroup = buildCommon.wrapFragment(
|
const upperGroup = buildCommon.wrapFragment(
|
||||||
html.buildGroup(group.body, newOptions, options), options);
|
html.buildGroup(group.body, newOptions, options), options);
|
||||||
upperGroup.classes.push("x-arrow-pad");
|
const arrowPrefix = group.label.slice(0, 2) === "\\x" ? "x" : "cd";
|
||||||
|
upperGroup.classes.push(arrowPrefix + "-arrow-pad");
|
||||||
|
|
||||||
let lowerGroup;
|
let lowerGroup;
|
||||||
if (group.below) {
|
if (group.below) {
|
||||||
@@ -65,7 +68,7 @@ defineFunction({
|
|||||||
newOptions = options.havingStyle(style.sub());
|
newOptions = options.havingStyle(style.sub());
|
||||||
lowerGroup = buildCommon.wrapFragment(
|
lowerGroup = buildCommon.wrapFragment(
|
||||||
html.buildGroup(group.below, newOptions, options), options);
|
html.buildGroup(group.below, newOptions, options), options);
|
||||||
lowerGroup.classes.push("x-arrow-pad");
|
lowerGroup.classes.push(arrowPrefix + "-arrow-pad");
|
||||||
}
|
}
|
||||||
|
|
||||||
const arrowBody = stretchy.svgSpan(group, options);
|
const arrowBody = stretchy.svgSpan(group, options);
|
||||||
@@ -112,6 +115,9 @@ defineFunction({
|
|||||||
},
|
},
|
||||||
mathmlBuilder(group, options) {
|
mathmlBuilder(group, options) {
|
||||||
const arrowNode = stretchy.mathMLnode(group.label);
|
const arrowNode = stretchy.mathMLnode(group.label);
|
||||||
|
arrowNode.setAttribute(
|
||||||
|
"minsize", group.label.charAt(0) === "x" ? "1.75em" : "3.0em"
|
||||||
|
);
|
||||||
let node;
|
let node;
|
||||||
|
|
||||||
if (group.body) {
|
if (group.body) {
|
||||||
|
@@ -555,6 +555,10 @@
|
|||||||
padding: 0 0.5em;
|
padding: 0 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cd-arrow-pad {
|
||||||
|
padding: 0 0.55556em 0 0.27778em; // \;{#1}\;\;
|
||||||
|
}
|
||||||
|
|
||||||
.x-arrow,
|
.x-arrow,
|
||||||
.mover,
|
.mover,
|
||||||
.munder {
|
.munder {
|
||||||
@@ -610,6 +614,25 @@
|
|||||||
.mtr-glue {
|
.mtr-glue {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cd-vert-arrow {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cd-label-left {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
right: calc(50% + 0.3em);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cd-label-right {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
left: calc(50% + 0.3em);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.katex-display {
|
.katex-display {
|
||||||
|
@@ -41,6 +41,20 @@ type ParseNodeTypes = {
|
|||||||
hLinesBeforeRow: Array<boolean[]>,
|
hLinesBeforeRow: Array<boolean[]>,
|
||||||
addEqnNum?: boolean,
|
addEqnNum?: boolean,
|
||||||
leqno?: boolean,
|
leqno?: boolean,
|
||||||
|
isCD?: boolean,
|
||||||
|
|},
|
||||||
|
"cdlabel": {|
|
||||||
|
type: "cdlabel",
|
||||||
|
mode: Mode,
|
||||||
|
loc?: ?SourceLocation,
|
||||||
|
side: string,
|
||||||
|
label: AnyParseNode,
|
||||||
|
|},
|
||||||
|
"cdlabelparent": {|
|
||||||
|
type: "cdlabelparent",
|
||||||
|
mode: Mode,
|
||||||
|
loc?: ?SourceLocation,
|
||||||
|
fragment: AnyParseNode,
|
||||||
|},
|
|},
|
||||||
"color": {|
|
"color": {|
|
||||||
type: "color",
|
type: "color",
|
||||||
|
@@ -54,11 +54,14 @@ const stretchyCodePoint: {[string]: string} = {
|
|||||||
xrightleftarrows: "\u21c4",
|
xrightleftarrows: "\u21c4",
|
||||||
xrightequilibrium: "\u21cc", // Not a perfect match.
|
xrightequilibrium: "\u21cc", // Not a perfect match.
|
||||||
xleftequilibrium: "\u21cb", // None better available.
|
xleftequilibrium: "\u21cb", // None better available.
|
||||||
|
"\\\\cdrightarrow": "\u2192",
|
||||||
|
"\\\\cdleftarrow": "\u2190",
|
||||||
|
"\\\\cdlongequal": "=",
|
||||||
};
|
};
|
||||||
|
|
||||||
const mathMLnode = function(label: string): mathMLTree.MathNode {
|
const mathMLnode = function(label: string): mathMLTree.MathNode {
|
||||||
const node = new mathMLTree.MathNode(
|
const node = new mathMLTree.MathNode(
|
||||||
"mo", [new mathMLTree.TextNode(stretchyCodePoint[label.substr(1)])]);
|
"mo", [new mathMLTree.TextNode(stretchyCodePoint[label])]);
|
||||||
node.setAttribute("stretchy", "true");
|
node.setAttribute("stretchy", "true");
|
||||||
return node;
|
return node;
|
||||||
};
|
};
|
||||||
@@ -118,7 +121,9 @@ const katexImagesData: {
|
|||||||
underrightarrow: [["rightarrow"], 0.888, 522, "xMaxYMin"],
|
underrightarrow: [["rightarrow"], 0.888, 522, "xMaxYMin"],
|
||||||
underleftarrow: [["leftarrow"], 0.888, 522, "xMinYMin"],
|
underleftarrow: [["leftarrow"], 0.888, 522, "xMinYMin"],
|
||||||
xrightarrow: [["rightarrow"], 1.469, 522, "xMaxYMin"],
|
xrightarrow: [["rightarrow"], 1.469, 522, "xMaxYMin"],
|
||||||
|
"\\cdrightarrow": [["rightarrow"], 3.0, 522, "xMaxYMin"], // CD minwwidth2.5pc
|
||||||
xleftarrow: [["leftarrow"], 1.469, 522, "xMinYMin"],
|
xleftarrow: [["leftarrow"], 1.469, 522, "xMinYMin"],
|
||||||
|
"\\cdleftarrow": [["leftarrow"], 3.0, 522, "xMinYMin"],
|
||||||
Overrightarrow: [["doublerightarrow"], 0.888, 560, "xMaxYMin"],
|
Overrightarrow: [["doublerightarrow"], 0.888, 560, "xMaxYMin"],
|
||||||
xRightarrow: [["doublerightarrow"], 1.526, 560, "xMaxYMin"],
|
xRightarrow: [["doublerightarrow"], 1.526, 560, "xMaxYMin"],
|
||||||
xLeftarrow: [["doubleleftarrow"], 1.526, 560, "xMinYMin"],
|
xLeftarrow: [["doubleleftarrow"], 1.526, 560, "xMinYMin"],
|
||||||
@@ -129,6 +134,7 @@ const katexImagesData: {
|
|||||||
xrightharpoonup: [["rightharpoon"], 0.888, 522, "xMaxYMin"],
|
xrightharpoonup: [["rightharpoon"], 0.888, 522, "xMaxYMin"],
|
||||||
xrightharpoondown: [["rightharpoondown"], 0.888, 522, "xMaxYMin"],
|
xrightharpoondown: [["rightharpoondown"], 0.888, 522, "xMaxYMin"],
|
||||||
xlongequal: [["longequal"], 0.888, 334, "xMinYMin"],
|
xlongequal: [["longequal"], 0.888, 334, "xMinYMin"],
|
||||||
|
"\\cdlongequal": [["longequal"], 3.0, 334, "xMinYMin"],
|
||||||
xtwoheadleftarrow: [["twoheadleftarrow"], 0.888, 334, "xMinYMin"],
|
xtwoheadleftarrow: [["twoheadleftarrow"], 0.888, 334, "xMinYMin"],
|
||||||
xtwoheadrightarrow: [["twoheadrightarrow"], 0.888, 334, "xMaxYMin"],
|
xtwoheadrightarrow: [["twoheadrightarrow"], 0.888, 334, "xMaxYMin"],
|
||||||
|
|
||||||
|
@@ -204,6 +204,211 @@ exports[`A MathML builder normal spaces render normally 1`] = `
|
|||||||
</math>
|
</math>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`A MathML builder should build the CD environment properly 1`] = `
|
||||||
|
<math xmlns="http://www.w3.org/1998/Math/MathML"
|
||||||
|
display="block"
|
||||||
|
>
|
||||||
|
<semantics>
|
||||||
|
<mtable rowspacing="0.2500em"
|
||||||
|
columnalign="center center center"
|
||||||
|
columnspacing="0.5em"
|
||||||
|
>
|
||||||
|
<mtr>
|
||||||
|
<mtd>
|
||||||
|
<mstyle scriptlevel="0"
|
||||||
|
displaystyle="true"
|
||||||
|
>
|
||||||
|
<mi>
|
||||||
|
A
|
||||||
|
</mi>
|
||||||
|
</mstyle>
|
||||||
|
</mtd>
|
||||||
|
<mtd>
|
||||||
|
<mstyle scriptlevel="0"
|
||||||
|
displaystyle="true"
|
||||||
|
>
|
||||||
|
<munderover>
|
||||||
|
<mo stretchy="true"
|
||||||
|
minsize="3.0em"
|
||||||
|
>
|
||||||
|
→
|
||||||
|
</mo>
|
||||||
|
<mpadded width="+0.6em"
|
||||||
|
lspace="0.3em"
|
||||||
|
>
|
||||||
|
<mrow>
|
||||||
|
</mrow>
|
||||||
|
</mpadded>
|
||||||
|
<mpadded width="+0.6em"
|
||||||
|
lspace="0.3em"
|
||||||
|
>
|
||||||
|
<mi>
|
||||||
|
a
|
||||||
|
</mi>
|
||||||
|
</mpadded>
|
||||||
|
</munderover>
|
||||||
|
</mstyle>
|
||||||
|
</mtd>
|
||||||
|
<mtd>
|
||||||
|
<mstyle scriptlevel="0"
|
||||||
|
displaystyle="true"
|
||||||
|
>
|
||||||
|
<mi>
|
||||||
|
B
|
||||||
|
</mi>
|
||||||
|
</mstyle>
|
||||||
|
</mtd>
|
||||||
|
</mtr>
|
||||||
|
<mtr>
|
||||||
|
<mtd>
|
||||||
|
<mstyle scriptlevel="0"
|
||||||
|
displaystyle="true"
|
||||||
|
>
|
||||||
|
<mrow>
|
||||||
|
<mrow>
|
||||||
|
<mstyle displaystyle="false"
|
||||||
|
scriptlevel="1"
|
||||||
|
>
|
||||||
|
<mpadded width="0"
|
||||||
|
lspace="-1width"
|
||||||
|
voffset="0.7em"
|
||||||
|
>
|
||||||
|
<mrow>
|
||||||
|
<mrow>
|
||||||
|
</mrow>
|
||||||
|
</mrow>
|
||||||
|
</mpadded>
|
||||||
|
</mstyle>
|
||||||
|
<mo fence="false"
|
||||||
|
stretchy="true"
|
||||||
|
minsize="1.8em"
|
||||||
|
maxsize="1.8em"
|
||||||
|
>
|
||||||
|
↓
|
||||||
|
</mo>
|
||||||
|
<mstyle displaystyle="false"
|
||||||
|
scriptlevel="1"
|
||||||
|
>
|
||||||
|
<mpadded width="0"
|
||||||
|
voffset="0.7em"
|
||||||
|
>
|
||||||
|
<mrow>
|
||||||
|
<mi>
|
||||||
|
b
|
||||||
|
</mi>
|
||||||
|
</mrow>
|
||||||
|
</mpadded>
|
||||||
|
</mstyle>
|
||||||
|
</mrow>
|
||||||
|
</mrow>
|
||||||
|
</mstyle>
|
||||||
|
</mtd>
|
||||||
|
<mtd>
|
||||||
|
<mstyle scriptlevel="0"
|
||||||
|
displaystyle="true"
|
||||||
|
>
|
||||||
|
</mstyle>
|
||||||
|
</mtd>
|
||||||
|
<mtd>
|
||||||
|
<mstyle scriptlevel="0"
|
||||||
|
displaystyle="true"
|
||||||
|
>
|
||||||
|
<mrow>
|
||||||
|
<mrow>
|
||||||
|
<mstyle displaystyle="false"
|
||||||
|
scriptlevel="1"
|
||||||
|
>
|
||||||
|
<mpadded width="0"
|
||||||
|
lspace="-1width"
|
||||||
|
voffset="0.7em"
|
||||||
|
>
|
||||||
|
<mrow>
|
||||||
|
<mrow>
|
||||||
|
</mrow>
|
||||||
|
</mrow>
|
||||||
|
</mpadded>
|
||||||
|
</mstyle>
|
||||||
|
<mo fence="false"
|
||||||
|
stretchy="true"
|
||||||
|
minsize="1.8em"
|
||||||
|
maxsize="1.8em"
|
||||||
|
>
|
||||||
|
↓
|
||||||
|
</mo>
|
||||||
|
<mstyle displaystyle="false"
|
||||||
|
scriptlevel="1"
|
||||||
|
>
|
||||||
|
<mpadded width="0"
|
||||||
|
voffset="0.7em"
|
||||||
|
>
|
||||||
|
<mrow>
|
||||||
|
<mi>
|
||||||
|
c
|
||||||
|
</mi>
|
||||||
|
</mrow>
|
||||||
|
</mpadded>
|
||||||
|
</mstyle>
|
||||||
|
</mrow>
|
||||||
|
</mrow>
|
||||||
|
</mstyle>
|
||||||
|
</mtd>
|
||||||
|
</mtr>
|
||||||
|
<mtr>
|
||||||
|
<mtd>
|
||||||
|
<mstyle scriptlevel="0"
|
||||||
|
displaystyle="true"
|
||||||
|
>
|
||||||
|
<mi>
|
||||||
|
C
|
||||||
|
</mi>
|
||||||
|
</mstyle>
|
||||||
|
</mtd>
|
||||||
|
<mtd>
|
||||||
|
<mstyle scriptlevel="0"
|
||||||
|
displaystyle="true"
|
||||||
|
>
|
||||||
|
<munderover>
|
||||||
|
<mo stretchy="true"
|
||||||
|
minsize="3.0em"
|
||||||
|
>
|
||||||
|
→
|
||||||
|
</mo>
|
||||||
|
<mpadded width="+0.6em"
|
||||||
|
lspace="0.3em"
|
||||||
|
>
|
||||||
|
<mrow>
|
||||||
|
</mrow>
|
||||||
|
</mpadded>
|
||||||
|
<mpadded width="+0.6em"
|
||||||
|
lspace="0.3em"
|
||||||
|
>
|
||||||
|
<mi>
|
||||||
|
d
|
||||||
|
</mi>
|
||||||
|
</mpadded>
|
||||||
|
</munderover>
|
||||||
|
</mstyle>
|
||||||
|
</mtd>
|
||||||
|
<mtd>
|
||||||
|
<mstyle scriptlevel="0"
|
||||||
|
displaystyle="true"
|
||||||
|
>
|
||||||
|
<mi>
|
||||||
|
D
|
||||||
|
</mi>
|
||||||
|
</mstyle>
|
||||||
|
</mtd>
|
||||||
|
</mtr>
|
||||||
|
<mtr>
|
||||||
|
</mtr>
|
||||||
|
</mtable>
|
||||||
|
<annotation encoding="application/x-tex">
|
||||||
|
\\begin{CD} A @>a>> B\\\\ @VVbV @VVcV\\\\ C @>d>> D \\end{CD}
|
||||||
|
</annotation>
|
||||||
|
</semantics>
|
||||||
|
</math>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`A MathML builder should concatenate digits into single <mn> 1`] = `
|
exports[`A MathML builder should concatenate digits into single <mn> 1`] = `
|
||||||
<math xmlns="http://www.w3.org/1998/Math/MathML">
|
<math xmlns="http://www.w3.org/1998/Math/MathML">
|
||||||
<semantics>
|
<semantics>
|
||||||
|
@@ -2778,6 +2778,7 @@ describe("AMS environments", function() {
|
|||||||
expect`\begin{alignat*}{2}10&x+ &3&y = 2\\3&x+&13&y = 4\end{alignat*}`.not.toParse(nonstrictSettings);
|
expect`\begin{alignat*}{2}10&x+ &3&y = 2\\3&x+&13&y = 4\end{alignat*}`.not.toParse(nonstrictSettings);
|
||||||
expect`\begin{equation}a=b+c\end{equation}`.not.toParse(nonstrictSettings);
|
expect`\begin{equation}a=b+c\end{equation}`.not.toParse(nonstrictSettings);
|
||||||
expect`\begin{split}a &=b+c\\&=e+f\end{split}`.not.toParse(nonstrictSettings);
|
expect`\begin{split}a &=b+c\\&=e+f\end{split}`.not.toParse(nonstrictSettings);
|
||||||
|
expect`\begin{CD}A @>a>> B \\@VbVV @AAcA\\C @= D\end{CD}`.not.toParse(nonstrictSettings);
|
||||||
});
|
});
|
||||||
|
|
||||||
const nonStrictDisplay = new Settings({displayMode: true, strict: false});
|
const nonStrictDisplay = new Settings({displayMode: true, strict: false});
|
||||||
@@ -2791,6 +2792,7 @@ describe("AMS environments", function() {
|
|||||||
expect`\begin{equation}a=b+c\end{equation}`.toBuild(nonStrictDisplay);
|
expect`\begin{equation}a=b+c\end{equation}`.toBuild(nonStrictDisplay);
|
||||||
expect`\begin{equation}\begin{split}a &=b+c\\&=e+f\end{split}\end{equation}`.toBuild(nonStrictDisplay);
|
expect`\begin{equation}\begin{split}a &=b+c\\&=e+f\end{split}\end{equation}`.toBuild(nonStrictDisplay);
|
||||||
expect`\begin{split}a &=b+c\\&=e+f\end{split}`.toBuild(nonStrictDisplay);
|
expect`\begin{split}a &=b+c\\&=e+f\end{split}`.toBuild(nonStrictDisplay);
|
||||||
|
expect`\begin{CD}A @<a<< B @>>b> C @>>> D\\@. @| @AcAA @VVdV \\@. E @= F @>>> G\end{CD}`.toBuild(nonStrictDisplay);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("{equation} should fail if argument contains two rows.", () => {
|
it("{equation} should fail if argument contains two rows.", () => {
|
||||||
@@ -2807,6 +2809,29 @@ describe("AMS environments", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("The CD environment", function() {
|
||||||
|
it("should fail if not is display mode", function() {
|
||||||
|
expect(`\\begin{CD}A @<a<< B @>>b> C @>>> D\\\\@. @| @AcAA @VVdV \\\\@. E @= F @>>> G\\end{CD}`).not.toParse(
|
||||||
|
new Settings({displayMode: false, strict: false})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const displaySettings = new Settings({displayMode: true, strict: false});
|
||||||
|
it("should fail if the character after '@' is not in <>AV=|.", function() {
|
||||||
|
expect(`\\begin{CD}A @X<a<< B @>>b> C @>>> D\\\\@. @| @AcAA @VVdV \\\\@. E @= F @>>> G\\end{CD}`).not.toParse(displaySettings);
|
||||||
|
});
|
||||||
|
it("should fail if an arrow does not have its final character.", function() {
|
||||||
|
expect(`\\begin{CD}A @<a< B @>>b> C @>>> D\\\\@. @| @AcAA @VVdV \\\\@. E @= F @>>> G\\end{CD}`).not.toParse(displaySettings);
|
||||||
|
expect(`\\begin{CD}A @<a<< B @>>b C @>>> D\\\\@. @| @AcAA @VVdV \\\\@. E @= F @>>> G\\end{CD}`).not.toParse(displaySettings);
|
||||||
|
});
|
||||||
|
it("should fail without an \\\\end.", function() {
|
||||||
|
expect(`\\begin{CD}A @<a<< B @>>b> C @>>> D\\\\@. @| @AcAA @VVdV \\\\@. E @= F @>>> G`).not.toParse(displaySettings);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should succeed without the flaws noted above.", function() {
|
||||||
|
expect(`\\begin{CD}A @<a<< B @>>b> C @>>> D\\\\@. @| @AcAA @VVdV \\\\@. E @= F @>>> G\\end{CD}`).toBuild(displaySettings);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("operatorname support", function() {
|
describe("operatorname support", function() {
|
||||||
it("should not fail", function() {
|
it("should not fail", function() {
|
||||||
expect("\\operatorname{x*Π∑\\Pi\\sum\\frac a b}").toBuild();
|
expect("\\operatorname{x*Π∑\\Pi\\sum\\frac a b}").toBuild();
|
||||||
|
@@ -79,6 +79,13 @@ describe("A MathML builder", function() {
|
|||||||
expect(getMathML("\\colorbox{red}{b}")).toMatchSnapshot();
|
expect(getMathML("\\colorbox{red}{b}")).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should build the CD environment properly', () => {
|
||||||
|
const displaySettings = new Settings({displayMode: true, strict: false});
|
||||||
|
const mathml = getMathML("\\begin{CD} A @>a>> B\\\\ @VVbV @VVcV\\\\" +
|
||||||
|
" C @>d>> D \\end{CD}", displaySettings);
|
||||||
|
expect(mathml).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
it('should set href attribute for href appropriately', () => {
|
it('should set href attribute for href appropriately', () => {
|
||||||
expect(
|
expect(
|
||||||
getMathML("\\href{http://example.org}{\\alpha}", new Settings({trust: true})),
|
getMathML("\\href{http://example.org}{\\alpha}", new Settings({trust: true})),
|
||||||
|
BIN
test/screenshotter/images/CD-chrome.png
Normal file
BIN
test/screenshotter/images/CD-chrome.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
test/screenshotter/images/CD-firefox.png
Normal file
BIN
test/screenshotter/images/CD-firefox.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
test/screenshotter/images/CD-safari.png
Normal file
BIN
test/screenshotter/images/CD-safari.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
@@ -86,6 +86,9 @@ Cases: |
|
|||||||
a &\text{if } b \\
|
a &\text{if } b \\
|
||||||
c &\text{if } d
|
c &\text{if } d
|
||||||
\end{rcases}⇒…
|
\end{rcases}⇒…
|
||||||
|
CD:
|
||||||
|
tex: \begin{CD} A @<a<< B @>>b> C \\ @| @AcAA @VVdV \\ D @= E @>>> F \end{CD}
|
||||||
|
display: 1
|
||||||
Colors:
|
Colors:
|
||||||
tex: \blue{a}\textcolor{#0f0}{b}\textcolor{red}{c}
|
tex: \blue{a}\textcolor{#0f0}{b}\textcolor{red}{c}
|
||||||
nolatex: different syntax and different scope
|
nolatex: different syntax and different scope
|
||||||
|
Reference in New Issue
Block a user