Implement correct macros for liminf and limsup, fixes #111 (#887)

* Implement correct macros for liminf and limsup, fixes #111

* fix nits
This commit is contained in:
Kevin Barabash
2017-12-26 17:11:10 -07:00
committed by GitHub
parent c883b3d54d
commit 7229f02d8f
12 changed files with 159 additions and 23 deletions

View File

@@ -198,9 +198,19 @@ groupTypes.supsub = function(group, options) {
if (isBrace) {
nodeType = (isOver ? "mover" : "munder");
} else if (!group.value.sub) {
nodeType = "msup";
const base = group.value.base;
if (base && base.value.limits && options.style === Style.DISPLAY) {
nodeType = "mover";
} else {
nodeType = "msup";
}
} else if (!group.value.sup) {
nodeType = "msub";
const base = group.value.base;
if (base && base.value.limits && options.style === Style.DISPLAY) {
nodeType = "munder";
} else {
nodeType = "msub";
}
} else {
const base = group.value.base;
if (base && base.value.limits && options.style === Style.DISPLAY) {

View File

@@ -181,8 +181,7 @@ defineFunction([
// Limits, not symbols
defineFunction([
"\\det", "\\gcd", "\\inf", "\\lim", "\\liminf", "\\limsup", "\\max",
"\\min", "\\Pr", "\\sup",
"\\det", "\\gcd", "\\inf", "\\lim", "\\max", "\\min", "\\Pr", "\\sup",
], {
numArgs: 0,
}, function(context) {

View File

@@ -30,22 +30,27 @@ defineFunction({
let mode = "";
// Consolidate Greek letter function names into symbol characters.
const temp = html.buildExpression(group.value.value, options, true);
const temp = html.buildExpression(
group.value.value, options.withFontFamily("mathrm"), true);
// All we want from temp are the letters. With them, we'll
// create a text operator similar to \tan or \cos.
for (let i = 0; i < temp.length; i++) {
letter = temp[i].value;
for (const child of temp) {
if (child instanceof domTree.symbolNode) {
letter = child.value;
// In the amsopn package, \newmcodes@ changes four
// characters, *-/:, from math operators back into text.
// Given what is in temp, we have to address two of them.
letter = letter.replace(/\u2212/, "-"); // minus => hyphen
letter = letter.replace(/\u2217/, "*");
// In the amsopn package, \newmcodes@ changes four
// characters, *-/:, from math operators back into text.
// Given what is in temp, we have to address two of them.
letter = letter.replace(/\u2212/, "-"); // minus => hyphen
letter = letter.replace(/\u2217/, "*");
// Use math mode for Greek letters
mode = (/[\u0391-\u03D7]/.test(letter) ? "math" : "text");
output.push(buildCommon.mathsym(letter, mode));
// Use math mode for Greek letters
mode = (/[\u0391-\u03D7]/.test(letter) ? "math" : "text");
output.push(buildCommon.mathsym(letter, mode));
} else {
output.push(child);
}
}
}
return buildCommon.makeSpan(["mop"], output, options);
@@ -55,12 +60,11 @@ defineFunction({
// The steps taken here are similar to the html version.
let output = [];
if (group.value.value.length > 0) {
const temp = mml.buildExpression(group.value.value, options);
const temp = mml.buildExpression(
group.value.value, options.withFontFamily("mathrm"));
let word = temp.map(node => node.toText()).join("");
let word = "";
for (let i = 0; i < temp.length; i++) {
word += temp[i].children[0].text;
}
word = word.replace(/\u2212/g, "-");
word = word.replace(/\u2217/g, "*");
output = [new mathMLTree.TextNode(word)];

View File

@@ -399,3 +399,5 @@ defineMacro("\\approxcoloncolon",
// macro turned into a propper defineSymbol in symbols.js. That way, the
// MathML result will be much cleaner.
defineMacro("\\notni", "\\not\\ni");
defineMacro("\\limsup", "\\DOTSB\\mathop{\\operatorname{lim\\,sup}}\\limits");
defineMacro("\\liminf", "\\DOTSB\\mathop{\\operatorname{lim\\,inf}}\\limits");

View File

@@ -93,6 +93,22 @@ class MathNode {
return markup;
}
/**
* Converts the math node into a string, similar to innerText.
*/
toText(): string {
if (this.type === "mspace") {
if (this.attributes.width === "0.16667em") {
return "\u2006";
} else {
// TODO: Use other space characters for different widths.
// https://github.com/Khan/KaTeX/issues/1036
return " ";
}
}
return this.children.map(child => child.toText()).join("");
}
}
/**
@@ -118,6 +134,13 @@ class TextNode {
toMarkup(): string {
return utils.escape(this.text);
}
/**
* Converts the text node into a string (which is just the text iteself).
*/
toText(): string {
return this.text;
}
}
export default {

View File

@@ -21,7 +21,9 @@ function init() {
}
var macros = {};
var options = {};
// TODO: Add toggle for displayMode.
// https://github.com/Khan/KaTeX/issues/1035
var options = {displayMode: true};
var macroRegex = /(?:^\?|&)(?:\\|%5[Cc])([A-Za-z]+)=([^&]*)/g;
var macroString = "";
while ((match = macroRegex.exec(window.location.search)) !== null) {

View File

@@ -77,6 +77,76 @@ exports[`A MathML builder should make prime operators into <mo> nodes 1`] = `
`;
exports[`A MathML builder should output \\limsup_{x \\rightarrow \\infty} correctly in \\textstyle 1`] = `
<math>
<semantics>
<mrow>
<msub>
<mo>
<mi mathvariant="normal">
limsup
</mi>
<mo>
</mo>
</mo>
<mrow>
<mi>
x
</mi>
<mo>
</mo>
<mi mathvariant="normal">
</mi>
</mrow>
</msub>
</mrow>
<annotation encoding="application/x-tex">
\\limsup_{x \\rightarrow \\infty}
</annotation>
</semantics>
</math>
`;
exports[`A MathML builder should output \\limsup_{x \\rightarrow \\infty} in displaymode correctly 1`] = `
<math>
<semantics>
<mrow>
<munder>
<mo>
<mi mathvariant="normal">
limsup
</mi>
<mo>
</mo>
</mo>
<mrow>
<mi>
x
</mi>
<mo>
</mo>
<mi mathvariant="normal">
</mi>
</mrow>
</munder>
</mrow>
<annotation encoding="application/x-tex">
\\limsup_{x \\rightarrow \\infty}
</annotation>
</semantics>
</math>
`;
exports[`A MathML builder should render boldsymbol with the correct mathvariants 1`] = `
<math>

View File

@@ -2733,6 +2733,16 @@ describe("A macro expander", function() {
expect("\\hspace{1em}").toParseLike("\\kern1em");
expect("\\hspace*{1em}").toParseLike("\\kern1em");
});
it("should expand \\limsup as expected", () => {
expect("\\limsup")
.toParseLike("\\mathop{\\operatorname{lim\\,sup}}\\limits");
});
it("should expand \\liminf as expected", () => {
expect("\\liminf")
.toParseLike("\\mathop{\\operatorname{lim\\,inf}}\\limits");
});
});
describe("A parser taking String objects", function() {

View File

@@ -51,6 +51,19 @@ describe("A MathML builder", function() {
expect(getMathML("\\textstyle\\sum_a^b")).toMatchSnapshot();
});
it("should output \\limsup_{x \\rightarrow \\infty} correctly in " +
"\\textstyle", () => {
const mathml = getMathML("\\limsup_{x \\rightarrow \\infty}");
expect(mathml).toMatchSnapshot();
});
it("should output \\limsup_{x \\rightarrow \\infty} in " +
"displaymode correctly", () => {
const settings = new Settings({displayMode: true});
const mathml = getMathML("\\limsup_{x \\rightarrow \\infty}", settings);
expect(mathml).toMatchSnapshot();
});
it('should use <mpadded> for raisebox', () => {
expect(getMathML("\\raisebox{0.25em}{b}")).toMatchSnapshot();
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -195,8 +195,11 @@ OperatorName: |
\operatorname{Gam ma}(z) + \operatorname{\Gamma}(z) + \operatorname{}x
\end{matrix}
OpLimits: |
{\sin_2^2 \lim_2^2 \int_2^2 \sum_2^2}
{\displaystyle \lim_2^2 \int_2^2 \intop_2^2 \sum_2^2}
\begin{matrix}
{\sin_2^2 \lim_2^2 \int_2^2 \sum_2^2}
{\displaystyle \lim_2^2 \int_2^2 \intop_2^2 \sum_2^2} \\
\limsup_{x \rightarrow \infty} x \stackrel{?}= \liminf_{x \rightarrow \infty} x
\end{matrix}
OverUnderline: x\underline{x}\underline{\underline{x}}\underline{x_{x_{x_x}}}\underline{x^{x^{x^x}}}\overline{x}\overline{x}\overline{x^{x^{x^x}}} \blue{\overline{\underline{x}}\underline{\overline{x}}}
OverUnderset: |
\begin{array}{l}