feat: Support \vcenter and \hbox (#2452)
* Support \vcenter, \raise, \lower, and \hbox * Update screenshots * Edit docs for strict and hbox to * Fix typo for \hbox to * Update Safari screenshot * Augment docs for \vcentcolon * Edit vcenter MathML comment. * Remove pointless class from vcenter MathML * Withdraw \raise and \lower * Updatae Chrome and Firefox screenshots * Update Safari screenshot * Delete allowedInArgument setting Co-authored-by: ylemkimon <y@ylem.kim> * Update Chrome and Firefox screenshots * Update Chrome and Firefox screenshots take 2 * Update screenshot Co-authored-by: ylemkimon <y@ylem.kim>
@@ -327,6 +327,11 @@ const handleObject = (
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "hbox": {
|
||||||
|
buildA11yStrings(tree.body, a11yStrings, atomType);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "kern": {
|
case "kern": {
|
||||||
// No op: we don't attempt to present kerning information
|
// No op: we don't attempt to present kerning information
|
||||||
// to the screen reader.
|
// to the screen reader.
|
||||||
@@ -546,6 +551,11 @@ const handleObject = (
|
|||||||
`KaTeX-a11y: enclose node with ${tree.label} not supported yet`);
|
`KaTeX-a11y: enclose node with ${tree.label} not supported yet`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "vcenter": {
|
||||||
|
buildA11yStrings(tree.body, a11yStrings, atomType);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "vphantom": {
|
case "vphantom": {
|
||||||
throw new Error("KaTeX-a11y: vphantom not implemented yet");
|
throw new Error("KaTeX-a11y: vphantom not implemented yet");
|
||||||
}
|
}
|
||||||
|
@@ -272,6 +272,13 @@ describe("renderA11yString", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("hbox", () => {
|
||||||
|
test("\\hbox", () => {
|
||||||
|
const result = renderA11yString("x+\\hbox{y}");
|
||||||
|
expect(result).toMatchInlineSnapshot(`"x, plus, y"`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("inner", () => {
|
describe("inner", () => {
|
||||||
test("\\ldots", () => {
|
test("\\ldots", () => {
|
||||||
const result = renderA11yString("\\ldots");
|
const result = renderA11yString("\\ldots");
|
||||||
@@ -524,6 +531,13 @@ describe("renderA11yString", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("vcenter", () => {
|
||||||
|
test("\\vcenter", () => {
|
||||||
|
const result = renderA11yString("x+\\vcenter{y}");
|
||||||
|
expect(result).toMatchInlineSnapshot(`"x, plus, y"`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("verb", () => {
|
describe("verb", () => {
|
||||||
test("\\verb", () => {
|
test("\\verb", () => {
|
||||||
const result = renderA11yString("\\verb|hello|");
|
const result = renderA11yString("\\verb|hello|");
|
||||||
|
@@ -456,7 +456,8 @@ use `\ce` instead|
|
|||||||
|\harr|$\harr$||
|
|\harr|$\harr$||
|
||||||
|\hat|$\hat{\theta}$|`\hat{\theta}`|
|
|\hat|$\hat{\theta}$|`\hat{\theta}`|
|
||||||
|\hbar|$\hbar$||
|
|\hbar|$\hbar$||
|
||||||
|\hbox|<span style="color:firebrick;">Not supported</span>||
|
|\hbox|$\hbox{$x^2$}$|`\hbox{$x^2$}`|
|
||||||
|
|\hbox to <dimen>| <span style="color:firebrick;">Not supported</span> ||
|
||||||
|\hdashline|$\begin{matrix}a&b\\ \hdashline c &d\end{matrix}$|`\begin{matrix}`<br> `a & b \\`<br> `\hdashline`<br> `c & d`<br>`\end{matrix}`|
|
|\hdashline|$\begin{matrix}a&b\\ \hdashline c &d\end{matrix}$|`\begin{matrix}`<br> `a & b \\`<br> `\hdashline`<br> `c & d`<br>`\end{matrix}`|
|
||||||
|\hearts|$\hearts$||
|
|\hearts|$\hearts$||
|
||||||
|\heartsuit|$\heartsuit$||
|
|\heartsuit|$\heartsuit$||
|
||||||
@@ -865,7 +866,7 @@ use `\ce` instead|
|
|||||||
|\R|$\R$||
|
|\R|$\R$||
|
||||||
|\r|$\text{\r{a}}$|`\text{\r{a}}`|
|
|\r|$\text{\r{a}}$|`\text{\r{a}}`|
|
||||||
|\raise|<span style="color:firebrick;">Not supported</span>|see `\raisebox`|
|
|\raise|<span style="color:firebrick;">Not supported</span>|see `\raisebox`|
|
||||||
|\raisebox|$h\raisebox{2pt}{ighe}r$|`h\raisebox{2pt}{ighe}r`|
|
|\raisebox|$h\raisebox{2pt}{ighe}r$|`h\raisebox{2pt}{$ighe$}r`|
|
||||||
|\rang|$\langle A\rang$|`\langle A\rang`|
|
|\rang|$\langle A\rang$|`\langle A\rang`|
|
||||||
|\rangle|$\langle A\rangle$|`\langle A\rangle`|
|
|\rangle|$\langle A\rangle$|`\langle A\rangle`|
|
||||||
|\Rarr|$\Rarr$||
|
|\Rarr|$\Rarr$||
|
||||||
@@ -1176,8 +1177,9 @@ use `\ce` instead|
|
|||||||
|\vartriangleright|$\vartriangleright$||
|
|\vartriangleright|$\vartriangleright$||
|
||||||
|\varUpsilon|$\varUpsilon$||
|
|\varUpsilon|$\varUpsilon$||
|
||||||
|\varXi|$\varXi$||
|
|\varXi|$\varXi$||
|
||||||
|\vcentcolon|$\vcentcolon$||
|
|\vcentcolon|$\mathrel{\vcentcolon =}$|`\mathrel{\vcentcolon =}`|
|
||||||
|\vcenter|<span style="color:firebrick;">Not supported</span>||
|
|\vcenter|$a+\left(\vcenter{\frac{\frac a b}c}\right)$|`a+\left(\vcenter{\hbox{$\frac{\frac a b}c$}}\right)`<br>TeX (strict) syntax|
|
||||||
|
|\vcenter|$a+\left(\vcenter{\frac{\frac a b}c}\right)$|`a+\left(\vcenter{\frac{\frac a b}c}\right)`<br>non-strict syntax|
|
||||||
|\Vdash|$\Vdash$||
|
|\Vdash|$\Vdash$||
|
||||||
|\vDash|$\vDash$||
|
|\vDash|$\vDash$||
|
||||||
|\vdash|$\vdash$||
|
|\vdash|$\vdash$||
|
||||||
|
@@ -236,11 +236,14 @@ 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`
|
|$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`
|
|$e^x$ `e^x` |$\overset{!}{=}$ `\overset{!}{=}` | $a\raisebox{0.25em}{$b$}c$ `a\raisebox{0.25em}{$b$}c`
|
||||||
|$_u^o $ `_u^o `|$\underset{!}{=}$ `\underset{!}{=}` | $$\sum_{\substack{0<i<m\\0<j<n}}$$ `\sum_{\substack{0<i<m\\0<j<n}}`
|
|$_u^o $ `_u^o `| $\underset{!}{=}$ `\underset{!}{=}` | $a+\left(\vcenter{\frac{\frac a b}c}\right)$ `a+\left(\vcenter{\hbox{$\frac{\frac a b}c$}}\right)`
|
||||||
|
||| $$\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$}`
|
`\raisebox` and `\hbox` put their argument into text mode. To raise math, nest `$…$` delimiters inside the argument as shown above.
|
||||||
|
|
||||||
|
`\vcenter` can be written without an `\hbox` if the `strict` rendering option is *false*. In that case, omit the nested `$…$` delimiters.
|
||||||
|
|
||||||
### Overlap and Spacing
|
### Overlap and Spacing
|
||||||
|
|
||||||
|
@@ -21,6 +21,7 @@ import "./functions/font";
|
|||||||
import "./functions/genfrac";
|
import "./functions/genfrac";
|
||||||
import "./functions/horizBrace";
|
import "./functions/horizBrace";
|
||||||
import "./functions/href";
|
import "./functions/href";
|
||||||
|
import "./functions/hbox";
|
||||||
import "./functions/html";
|
import "./functions/html";
|
||||||
import "./functions/htmlmathml";
|
import "./functions/htmlmathml";
|
||||||
import "./functions/includegraphics";
|
import "./functions/includegraphics";
|
||||||
@@ -47,4 +48,5 @@ import "./functions/symbolsSpacing";
|
|||||||
import "./functions/tag";
|
import "./functions/tag";
|
||||||
import "./functions/text";
|
import "./functions/text";
|
||||||
import "./functions/underline";
|
import "./functions/underline";
|
||||||
|
import "./functions/vcenter";
|
||||||
import "./functions/verb";
|
import "./functions/verb";
|
||||||
|
39
src/functions/hbox.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// @flow
|
||||||
|
import defineFunction, {ordargument} from "../defineFunction";
|
||||||
|
import buildCommon from "../buildCommon";
|
||||||
|
import mathMLTree from "../mathMLTree";
|
||||||
|
|
||||||
|
import * as html from "../buildHTML";
|
||||||
|
import * as mml from "../buildMathML";
|
||||||
|
|
||||||
|
// \hbox is provided for compatibility with LaTeX \vcenter.
|
||||||
|
// In LaTeX, \vcenter can act only on a box, as in
|
||||||
|
// \vcenter{\hbox{$\frac{a+b}{\dfrac{c}{d}}$}}
|
||||||
|
// This function by itself doesn't do anything but prevent a soft line break.
|
||||||
|
|
||||||
|
defineFunction({
|
||||||
|
type: "hbox",
|
||||||
|
names: ["\\hbox"],
|
||||||
|
props: {
|
||||||
|
numArgs: 1,
|
||||||
|
argTypes: ["text"],
|
||||||
|
allowedInText: true,
|
||||||
|
primitive: true,
|
||||||
|
},
|
||||||
|
handler({parser}, args) {
|
||||||
|
return {
|
||||||
|
type: "hbox",
|
||||||
|
mode: parser.mode,
|
||||||
|
body: ordargument(args[0]),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
htmlBuilder(group, options) {
|
||||||
|
const elements = html.buildExpression(group.body, options, false);
|
||||||
|
return buildCommon.makeFragment(elements);
|
||||||
|
},
|
||||||
|
mathmlBuilder(group, options) {
|
||||||
|
return new mathMLTree.MathNode(
|
||||||
|
"mrow", mml.buildExpression(group.body, options)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
@@ -44,4 +44,3 @@ defineFunction({
|
|||||||
return node;
|
return node;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
44
src/functions/vcenter.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// @flow
|
||||||
|
import defineFunction from "../defineFunction";
|
||||||
|
import buildCommon from "../buildCommon";
|
||||||
|
import mathMLTree from "../mathMLTree";
|
||||||
|
|
||||||
|
import * as html from "../buildHTML";
|
||||||
|
import * as mml from "../buildMathML";
|
||||||
|
|
||||||
|
// \vcenter: Vertically center the argument group on the math axis.
|
||||||
|
|
||||||
|
defineFunction({
|
||||||
|
type: "vcenter",
|
||||||
|
names: ["\\vcenter"],
|
||||||
|
props: {
|
||||||
|
numArgs: 1,
|
||||||
|
argTypes: ["original"], // In LaTeX, \vcenter can act only on a box.
|
||||||
|
allowedInText: false,
|
||||||
|
},
|
||||||
|
handler({parser}, args) {
|
||||||
|
return {
|
||||||
|
type: "vcenter",
|
||||||
|
mode: parser.mode,
|
||||||
|
body: args[0],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
htmlBuilder(group, options) {
|
||||||
|
const body = html.buildGroup(group.body, options);
|
||||||
|
const axisHeight = options.fontMetrics().axisHeight;
|
||||||
|
const dy = 0.5 * ((body.height - axisHeight) - (body.depth + axisHeight));
|
||||||
|
return buildCommon.makeVList({
|
||||||
|
positionType: "shift",
|
||||||
|
positionData: dy,
|
||||||
|
children: [{type: "elem", elem: body}],
|
||||||
|
}, options);
|
||||||
|
},
|
||||||
|
mathmlBuilder(group, options) {
|
||||||
|
// There is no way to do this in MathML.
|
||||||
|
// Write a class as a breadcrumb in case some post-processor wants
|
||||||
|
// to perform a vcenter adjustment.
|
||||||
|
return new mathMLTree.MathNode(
|
||||||
|
"mpadded", [mml.buildGroup(group.body, options)], ["vcenter"]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@@ -256,6 +256,12 @@ type ParseNodeTypes = {
|
|||||||
size: StyleStr | "auto",
|
size: StyleStr | "auto",
|
||||||
barSize: Measurement | null,
|
barSize: Measurement | null,
|
||||||
|},
|
|},
|
||||||
|
"hbox": {|
|
||||||
|
type: "hbox",
|
||||||
|
mode: Mode,
|
||||||
|
loc?: ?SourceLocation,
|
||||||
|
body: AnyParseNode[],
|
||||||
|
|},
|
||||||
"horizBrace": {|
|
"horizBrace": {|
|
||||||
type: "horizBrace",
|
type: "horizBrace",
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
@@ -436,6 +442,12 @@ type ParseNodeTypes = {
|
|||||||
loc?: ?SourceLocation,
|
loc?: ?SourceLocation,
|
||||||
body: AnyParseNode,
|
body: AnyParseNode,
|
||||||
|},
|
|},
|
||||||
|
"vcenter": {|
|
||||||
|
type: "vcenter",
|
||||||
|
mode: Mode,
|
||||||
|
loc?: ?SourceLocation,
|
||||||
|
body: AnyParseNode,
|
||||||
|
|},
|
||||||
"xArrow": {|
|
"xArrow": {|
|
||||||
type: "xArrow",
|
type: "xArrow",
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
|
@@ -1642,6 +1642,36 @@ describe("A \\pmb builder", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("A raise parser", function() {
|
||||||
|
it("should parse and build text in \\raisebox", function() {
|
||||||
|
expect("\\raisebox{5pt}{text}").toBuild(strictSettings);
|
||||||
|
expect("\\raisebox{-5pt}{text}").toBuild(strictSettings);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should parse and build math in non-strict \\vcenter", function() {
|
||||||
|
expect("\\vcenter{\\frac a b}").toBuild(nonstrictSettings);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to parse math in \\raisebox", function() {
|
||||||
|
expect("\\raisebox{5pt}{\\frac a b}").not.toParse(nonstrictSettings);
|
||||||
|
expect("\\raisebox{-5pt}{\\frac a b}").not.toParse(nonstrictSettings);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to parse math in an \\hbox", function() {
|
||||||
|
expect("\\hbox{\\frac a b}").not.toParse(nonstrictSettings);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to build, given an unbraced length", function() {
|
||||||
|
expect("\\raisebox5pt{text}").not.toBuild(strictSettings);
|
||||||
|
expect("\\raisebox-5pt{text}").not.toBuild(strictSettings);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("should build math in an hbox when math mode is set", function() {
|
||||||
|
expect("a + \\vcenter{\\hbox{$\\frac{\\frac a b}c$}}").toBuild(strictSettings);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("A comment parser", function() {
|
describe("A comment parser", function() {
|
||||||
it("should parse comments at the end of a line", () => {
|
it("should parse comments at the end of a line", () => {
|
||||||
expect("a^2 + b^2 = c^2 % Pythagoras' Theorem\n").toParse();
|
expect("a^2 + b^2 = c^2 % Pythagoras' Theorem\n").toParse();
|
||||||
|
BIN
test/screenshotter/images/CD-chrome.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
test/screenshotter/images/CD-firefox.png
Normal file
After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 16 KiB |
@@ -323,7 +323,11 @@ Phase: 120\text{V}\phase{-78.2^\circ}\;\Large\phase{78.2^\circ}
|
|||||||
Pmb: \mu\pmb{\mu}\pmb{=}\mu\pmb{+}\mu
|
Pmb: \mu\pmb{\mu}\pmb{=}\mu\pmb{+}\mu
|
||||||
PrimeSpacing: f'+f_2'+f^{f'}
|
PrimeSpacing: f'+f_2'+f^{f'}
|
||||||
PrimeSuper: x'^2+x'''^2+x'^2_3+x_3'^2
|
PrimeSuper: x'^2+x'''^2+x'^2_3+x_3'^2
|
||||||
Raisebox: \frac{a}{a\raisebox{0.5em}{b}} \cdot \frac{a\raisebox{-0.5em}{b}}{a} \cdot \sqrt{a\raisebox{0.5em}{b}} \cdot \sqrt{a\raisebox{-0.5em}{b}} \cdot \sqrt{a\raisebox{0.5em}{b}\raisebox{-0.5em}{b}}
|
Raisebox:
|
||||||
|
\begin{matrix}
|
||||||
|
\frac{a}{a\raisebox{0.5em}{b}} \cdot \frac{a\raisebox{-0.5em}{b}}{a} \cdot \sqrt{a\raisebox{0.5em}{b}} \cdot \sqrt{a\raisebox{-0.5em}{b}} \cdot \sqrt{a\raisebox{0.5em}{b}\raisebox{-0.5em}{b}} \\[2em]
|
||||||
|
a + \left(\vcenter{\hbox{$\frac{a+b}{\dfrac{c}{d}}$}}\right)
|
||||||
|
\end {matrix}
|
||||||
ReactionArrows: |
|
ReactionArrows: |
|
||||||
\begin{matrix}
|
\begin{matrix}
|
||||||
A \xrightleftarrows{} B \xrightequilibrium{} C \xleftequilibrium{} D \\
|
A \xrightleftarrows{} B \xrightequilibrium{} C \xleftequilibrium{} D \\
|
||||||
|