diff --git a/src/buildMathML.js b/src/buildMathML.js index debfdc43..4631de95 100644 --- a/src/buildMathML.js +++ b/src/buildMathML.js @@ -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) { diff --git a/src/functions.js b/src/functions.js index eaa9e509..057195f9 100644 --- a/src/functions.js +++ b/src/functions.js @@ -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) { diff --git a/src/functions/operatorname.js b/src/functions/operatorname.js index 1a15b272..c26894e4 100644 --- a/src/functions/operatorname.js +++ b/src/functions/operatorname.js @@ -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)]; diff --git a/src/macros.js b/src/macros.js index d22d095f..34efa138 100644 --- a/src/macros.js +++ b/src/macros.js @@ -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"); diff --git a/src/mathMLTree.js b/src/mathMLTree.js index fd6375b3..b483cdff 100644 --- a/src/mathMLTree.js +++ b/src/mathMLTree.js @@ -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 { diff --git a/static/main.js b/static/main.js index 08514f9a..45a07e2a 100644 --- a/static/main.js +++ b/static/main.js @@ -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) { diff --git a/test/__snapshots__/mathml-spec.js.snap b/test/__snapshots__/mathml-spec.js.snap index f45c319f..227b3f0f 100644 --- a/test/__snapshots__/mathml-spec.js.snap +++ b/test/__snapshots__/mathml-spec.js.snap @@ -77,6 +77,76 @@ exports[`A MathML builder should make prime operators into nodes 1`] = ` `; +exports[`A MathML builder should output \\limsup_{x \\rightarrow \\infty} correctly in \\textstyle 1`] = ` + + + + + + + + lim sup + + + ⁡ + + + + + x + + + → + + + ∞ + + + + + + \\limsup_{x \\rightarrow \\infty} + + + + +`; + +exports[`A MathML builder should output \\limsup_{x \\rightarrow \\infty} in displaymode correctly 1`] = ` + + + + + + + + lim sup + + + ⁡ + + + + + x + + + → + + + ∞ + + + + + + \\limsup_{x \\rightarrow \\infty} + + + + +`; + exports[`A MathML builder should render boldsymbol with the correct mathvariants 1`] = ` diff --git a/test/katex-spec.js b/test/katex-spec.js index bd2a2641..71a245cf 100644 --- a/test/katex-spec.js +++ b/test/katex-spec.js @@ -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() { diff --git a/test/mathml-spec.js b/test/mathml-spec.js index a931d41f..ed926078 100644 --- a/test/mathml-spec.js +++ b/test/mathml-spec.js @@ -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 for raisebox', () => { expect(getMathML("\\raisebox{0.25em}{b}")).toMatchSnapshot(); }); diff --git a/test/screenshotter/images/OpLimits-chrome.png b/test/screenshotter/images/OpLimits-chrome.png index b426349e..9db85a20 100644 Binary files a/test/screenshotter/images/OpLimits-chrome.png and b/test/screenshotter/images/OpLimits-chrome.png differ diff --git a/test/screenshotter/images/OpLimits-firefox.png b/test/screenshotter/images/OpLimits-firefox.png index d7e94e2a..82afd80e 100644 Binary files a/test/screenshotter/images/OpLimits-firefox.png and b/test/screenshotter/images/OpLimits-firefox.png differ diff --git a/test/screenshotter/ss_data.yaml b/test/screenshotter/ss_data.yaml index 690ba0d4..b0a3a44c 100644 --- a/test/screenshotter/ss_data.yaml +++ b/test/screenshotter/ss_data.yaml @@ -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}