Implement $...$ within \text via styling node (#637)

This commit is contained in:
Erik Demaine
2017-06-10 15:03:22 -05:00
committed by Kevin Barabash
parent 9913242245
commit 7fdb1eed81
6 changed files with 85 additions and 17 deletions

View File

@@ -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;
}

View File

@@ -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 <mn> 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
// <mtext> outputs into a single <mtext> tag. In this way, we don't
// nest non-text items (e.g., $nested-math$) within an <mtext>.
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 <mtext>),
// just return it. Otherwise, wrap them in an <mrow>.
if (inner.length === 1) {
return inner[0];
} else {
return new mathMLTree.MathNode("mrow", inner);
}
};
groupTypes.color = function(group, options) {

View File

@@ -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("<mi mathvariant=\"double-struck\">A</mi>");
expect(markup).toContain("<mi>x</mi>");
expect(markup).toContain("<mn mathvariant=\"normal\">2</mn>");
expect(markup).toContain("<mn>2</mn>");
expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>"); // \Omega
expect(markup).toContain("<mi>\u0131</mi>"); // \imath
@@ -1552,7 +1557,7 @@ describe("A MathML font tree-builder", function() {
const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
expect(markup).toContain("<mi mathvariant=\"normal\">A</mi>");
expect(markup).toContain("<mi mathvariant=\"normal\">x</mi>");
expect(markup).toContain("<mn mathvariant=\"normal\">2</mn>");
expect(markup).toContain("<mn>2</mn>");
expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>"); // \Omega
expect(markup).toContain("<mi>\u0131</mi>"); // \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("<mi mathvariant=\"italic\">A</mi>");
expect(markup).toContain("<mi mathvariant=\"italic\">x</mi>");
expect(markup).toContain("<mi>A</mi>");
expect(markup).toContain("<mi>x</mi>");
expect(markup).toContain("<mn mathvariant=\"italic\">2</mn>");
expect(markup).toContain("<mi mathvariant=\"italic\">\u03c9</mi>"); // \omega
expect(markup).toContain("<mi mathvariant=\"italic\">\u03A9</mi>"); // \Omega
expect(markup).toContain("<mi mathvariant=\"italic\">\u0131</mi>"); // \imath
expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
expect(markup).toContain("<mi>\u03A9</mi>"); // \Omega
expect(markup).toContain("<mi>\u0131</mi>"); // \imath
expect(markup).toContain("<mo>+</mo>");
});
@@ -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("<mi>x</mi>");
expect(markup).toContain("<mn mathvariant=\"normal\">2</mn>");
expect(markup).toContain("<mn>2</mn>");
expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>"); // \Omega
expect(markup).toContain("<mi>\u0131</mi>"); // \imath
@@ -1661,6 +1666,22 @@ describe("A MathML font tree-builder", function() {
"</mstyle>";
expect(markup).toContain(node);
});
it("should render text as <mtext>", function() {
const tex = "\\text{for }";
const tree = getParsed(tex);
const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
expect(markup).toContain("<mtext>for\u00a0</mtext>");
});
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("<mrow><mtext>graph:\u00a0</mtext>");
expect(markup).toContain(
"<mi>y</mi><mo>=</mo><mi>m</mi><mi>x</mi><mo>+</mo><mi>b</mi>");
});
});
describe("A bin builder", function() {

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@@ -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