From 7fdb1eed81fe3d1ab244ab2716a3f0873406cfe4 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Sat, 10 Jun 2017 15:03:22 -0500 Subject: [PATCH] Implement $...$ within \text via styling node (#637) --- src/Parser.js | 18 +++++++ src/buildMathML.js | 46 ++++++++++++++---- test/katex-spec.js | 37 +++++++++++--- .../images/TextWithMath-chrome.png | Bin 0 -> 9055 bytes .../images/TextWithMath-firefox.png | Bin 0 -> 8754 bytes test/screenshotter/ss_data.yaml | 1 + 6 files changed, 85 insertions(+), 17 deletions(-) create mode 100644 test/screenshotter/images/TextWithMath-chrome.png create mode 100644 test/screenshotter/images/TextWithMath-firefox.png diff --git a/src/Parser.js b/src/Parser.js index 0cf911ce..4f56b911 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -507,6 +507,20 @@ Parser.prototype.parseImplicitGroup = function() { body: new ParseNode("ordgroup", body, this.mode), }, this.mode); } + } else if (func === "$") { + if (this.mode === "math") { + throw new ParseError("$ within math mode"); + } + this.consume(); + const outerMode = this.mode; + this.switchMode("math"); + const body = this.parseExpression(false, "$"); + this.expect("$", true); + this.switchMode(outerMode); + return new ParseNode("styling", { + style: "text", + value: body, + }, "math"); } else { // Defer to parseFunction if it's not a function we handle return this.parseFunction(start); @@ -878,6 +892,10 @@ Parser.prototype.parseSymbol = function() { return new ParseFuncOrArgument( new ParseNode("textord", nucleus.text, this.mode, nucleus), false, nucleus); + } else if (nucleus.text === "$") { + return new ParseFuncOrArgument( + nucleus.text, + false, nucleus); } else { return null; } diff --git a/src/buildMathML.js b/src/buildMathML.js index 400324d3..7f106d2d 100644 --- a/src/buildMathML.js +++ b/src/buildMathML.js @@ -63,13 +63,19 @@ const getVariant = function(group, options) { */ const groupTypes = {}; +const defaultVariant = { + "mi": "italic", + "mn": "normal", + "mtext": "normal", +}; + groupTypes.mathord = function(group, options) { const node = new mathMLTree.MathNode( "mi", [makeText(group.value, group.mode)]); - const variant = getVariant(group, options); - if (variant) { + const variant = getVariant(group, options) || "italic"; + if (variant !== defaultVariant[node.type]) { node.setAttribute("mathvariant", variant); } return node; @@ -81,15 +87,16 @@ groupTypes.textord = function(group, options) { const variant = getVariant(group, options) || "normal"; let node; - if (/[0-9]/.test(group.value)) { + if (group.mode === 'text') { + node = new mathMLTree.MathNode("mtext", [text]); + } else if (/[0-9]/.test(group.value)) { // TODO(kevinb) merge adjacent nodes // do it as a post processing step node = new mathMLTree.MathNode("mn", [text]); - if (options.font) { - node.setAttribute("mathvariant", variant); - } } else { node = new mathMLTree.MathNode("mi", [text]); + } + if (variant !== defaultVariant[node.type]) { node.setAttribute("mathvariant", variant); } @@ -149,11 +156,32 @@ groupTypes.ordgroup = function(group, options) { }; groupTypes.text = function(group, options) { - const inner = buildExpression(group.value.body, options); + const body = group.value.body; - const node = new mathMLTree.MathNode("mtext", inner); + // Convert each element of the body into MathML, and combine consecutive + // outputs into a single tag. In this way, we don't + // nest non-text items (e.g., $nested-math$) within an . + const inner = []; + let currentText = null; + for (let i = 0; i < body.length; i++) { + const group = buildGroup(body[i], options); + if (group.type === 'mtext' && currentText != null) { + Array.prototype.push.apply(currentText.children, group.children); + } else { + inner.push(group); + if (group.type === 'mtext') { + currentText = group; + } + } + } - return node; + // If there is a single tag in the end (presumably ), + // just return it. Otherwise, wrap them in an . + if (inner.length === 1) { + return inner[0]; + } else { + return new mathMLTree.MathNode("mrow", inner); + } }; groupTypes.color = function(group, options) { diff --git a/test/katex-spec.js b/test/katex-spec.js index caefcc0a..33c2f5fc 100644 --- a/test/katex-spec.js +++ b/test/katex-spec.js @@ -732,6 +732,7 @@ describe("A text parser", function() { const badTextExpression = "\\text{a b%}"; const badFunctionExpression = "\\text{\\sqrt{x}}"; const mathTokenAfterText = "\\text{sin}^2"; + const textWithEmbeddedMath = "\\text{graph: $y = mx + b$}"; it("should not fail", function() { expect(textExpression).toParse(); @@ -789,6 +790,10 @@ describe("A text parser", function() { parse.value.body.map(function(n) { return n.value; }).join("") ).toBe("moo"); }); + + it("should parse math within text group", function() { + expect(textWithEmbeddedMath).toParse(); + }); }); describe("A color parser", function() { @@ -1539,7 +1544,7 @@ describe("A MathML font tree-builder", function() { const markup = buildMathML(tree, tex, defaultOptions).toMarkup(); expect(markup).toContain("A"); expect(markup).toContain("x"); - expect(markup).toContain("2"); + expect(markup).toContain("2"); expect(markup).toContain("\u03c9"); // \omega expect(markup).toContain("\u03A9"); // \Omega expect(markup).toContain("\u0131"); // \imath @@ -1552,7 +1557,7 @@ describe("A MathML font tree-builder", function() { const markup = buildMathML(tree, tex, defaultOptions).toMarkup(); expect(markup).toContain("A"); expect(markup).toContain("x"); - expect(markup).toContain("2"); + expect(markup).toContain("2"); expect(markup).toContain("\u03c9"); // \omega expect(markup).toContain("\u03A9"); // \Omega expect(markup).toContain("\u0131"); // \imath @@ -1563,12 +1568,12 @@ describe("A MathML font tree-builder", function() { const tex = "\\mathit{" + contents + "}"; const tree = getParsed(tex); const markup = buildMathML(tree, tex, defaultOptions).toMarkup(); - expect(markup).toContain("A"); - expect(markup).toContain("x"); + expect(markup).toContain("A"); + expect(markup).toContain("x"); expect(markup).toContain("2"); - expect(markup).toContain("\u03c9"); // \omega - expect(markup).toContain("\u03A9"); // \Omega - expect(markup).toContain("\u0131"); // \imath + expect(markup).toContain("\u03c9"); // \omega + expect(markup).toContain("\u03A9"); // \Omega + expect(markup).toContain("\u0131"); // \imath expect(markup).toContain("+"); }); @@ -1623,7 +1628,7 @@ describe("A MathML font tree-builder", function() { // MathJax marks everything below as "script" except \omega // We don't have these glyphs in "script" and neither does MathJax expect(markup).toContain("x"); - expect(markup).toContain("2"); + expect(markup).toContain("2"); expect(markup).toContain("\u03c9"); // \omega expect(markup).toContain("\u03A9"); // \Omega expect(markup).toContain("\u0131"); // \imath @@ -1661,6 +1666,22 @@ describe("A MathML font tree-builder", function() { ""; expect(markup).toContain(node); }); + + it("should render text as ", function() { + const tex = "\\text{for }"; + const tree = getParsed(tex); + const markup = buildMathML(tree, tex, defaultOptions).toMarkup(); + expect(markup).toContain("for\u00a0"); + }); + + it("should render math within text as side-by-side children", function() { + const tex = "\\text{graph: $y = mx + b$}"; + const tree = getParsed(tex); + const markup = buildMathML(tree, tex, defaultOptions).toMarkup(); + expect(markup).toContain("graph:\u00a0"); + expect(markup).toContain( + "y=mx+b"); + }); }); describe("A bin builder", function() { diff --git a/test/screenshotter/images/TextWithMath-chrome.png b/test/screenshotter/images/TextWithMath-chrome.png new file mode 100644 index 0000000000000000000000000000000000000000..c33d83c72f4c549e9d2d6aa714c62c1ecdf1ffe5 GIT binary patch literal 9055 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 literal 0 HcmV?d00001 diff --git a/test/screenshotter/images/TextWithMath-firefox.png b/test/screenshotter/images/TextWithMath-firefox.png new file mode 100644 index 0000000000000000000000000000000000000000..8f16314937948fd774fd18108f8ef5ebc8a1e275 GIT binary patch literal 8754 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 literal 0 HcmV?d00001 diff --git a/test/screenshotter/ss_data.yaml b/test/screenshotter/ss_data.yaml index b7962466..dc1acc9d 100644 --- a/test/screenshotter/ss_data.yaml +++ b/test/screenshotter/ss_data.yaml @@ -170,6 +170,7 @@ Symbols1: | \maltese\degree\pounds\$ \text{\maltese\degree\pounds\textdollar} Text: \frac{a}{b}\text{c~ {ab} \ e}+fg +TextWithMath: \text{for $a < b$ and $ c < d $}. Unicode: \begin{matrix}\text{ÀàÇçÉéÏïÖöÛû} \\ \text{БГДЖЗЙЛФЦШЫЮЯ} \\ \text{여보세요} \\ \text{私はバナナです} \end{matrix} UnsupportedCmds: tex: \err\,\frac\fracerr3\,2^\superr_\suberr\,\sqrt\sqrterr