Adds MathML support for math font commands.

This is part 3 or 3.  The first two pull requests added font metrics, HTML rendering, and screenshot tests.
This commit is contained in:
Kevin Barabash
2015-07-04 15:32:31 -06:00
committed by Kevin Barabash
parent 1b5834d894
commit 64e63d7546
6 changed files with 295 additions and 74 deletions

View File

@@ -6,7 +6,7 @@
*/
/**
* This is the main options class. It contains the style, size, color and font
* This is the main options class. It contains the style, size, color, and font
* of the current parse level. It also contains the style and size of the parent
* parse level, so size changes can be handled efficiently.
*

View File

@@ -435,6 +435,7 @@ var fontMap = {
};
module.exports = {
fontMap: fontMap,
makeSymbol: makeSymbol,
mathsym: mathsym,
makeSpan: makeSpan,

View File

@@ -5,7 +5,6 @@
* called, to produce a final HTML tree.
*/
var Options = require("./Options");
var ParseError = require("./ParseError");
var Style = require("./Style");
@@ -1281,22 +1280,11 @@ var buildGroup = function(group, options, prev) {
* Take an entire parse tree, and build it into an appropriate set of HTML
* nodes.
*/
var buildHTML = function(tree, settings) {
var buildHTML = function(tree, options) {
// buildExpression is destructive, so we need to make a clone
// of the incoming tree so that it isn't accidentally changed
tree = JSON.parse(JSON.stringify(tree));
var startStyle = Style.TEXT;
if (settings.displayMode) {
startStyle = Style.DISPLAY;
}
// Setup the default options
var options = new Options({
style: startStyle,
size: "size5"
});
// Build the expression contained in the tree
var expression = buildExpression(tree, options);
var body = makeSpan(["base", options.style.cls()], expression);

View File

@@ -5,11 +5,14 @@
*/
var buildCommon = require("./buildCommon");
var fontMetrics = require("./fontMetrics");
var mathMLTree = require("./mathMLTree");
var ParseError = require("./ParseError");
var symbols = require("./symbols");
var utils = require("./utils");
var makeSpan = buildCommon.makeSpan;
var fontMap = buildCommon.fontMap;
/**
* Takes a symbol and converts it into a MathML text node after performing
@@ -23,28 +26,70 @@ var makeText = function(text, mode) {
return new mathMLTree.TextNode(text);
};
/**
* Returns the math variant as a string or null if none is required.
*/
var getVariant = function(group, options) {
var font = options.font;
if (!font) {
return null;
}
var mode = group.mode;
if (font === "mathit") {
return "italic";
}
var value = group.value;
if (utils.contains(["\\imath", "\\jmath"], value)) {
return null;
}
if (symbols[mode][value] && symbols[mode][value].replace) {
value = symbols[mode][value].replace;
}
var fontName = fontMap[font].fontName;
if (fontMetrics.getCharacterMetrics(value, fontName)) {
return fontMap[options.font].variant;
}
return null;
};
/**
* Functions for handling the different types of groups found in the parse
* tree. Each function should take a parse group and return a MathML node.
*/
var groupTypes = {
mathord: function(group) {
mathord: function(group, options) {
var node = new mathMLTree.MathNode(
"mi",
[makeText(group.value, group.mode)]);
var variant = getVariant(group, options);
if (variant) {
node.setAttribute("mathvariant", variant);
}
return node;
},
textord: function(group) {
textord: function(group, options) {
var text = makeText(group.value, group.mode);
var variant = getVariant(group, options) || "normal";
var node;
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]);
node.setAttribute("mathvariant", "normal");
node.setAttribute("mathvariant", variant);
}
return node;
@@ -94,24 +139,24 @@ var groupTypes = {
return node;
},
ordgroup: function(group) {
var inner = buildExpression(group.value);
ordgroup: function(group, options) {
var inner = buildExpression(group.value, options);
var node = new mathMLTree.MathNode("mrow", inner);
return node;
},
text: function(group) {
var inner = buildExpression(group.value.body);
text: function(group, options) {
var inner = buildExpression(group.value.body, options);
var node = new mathMLTree.MathNode("mtext", inner);
return node;
},
color: function(group) {
var inner = buildExpression(group.value.value);
color: function(group, options) {
var inner = buildExpression(group.value.value, options);
var node = new mathMLTree.MathNode("mstyle", inner);
@@ -120,15 +165,15 @@ var groupTypes = {
return node;
},
supsub: function(group) {
var children = [buildGroup(group.value.base)];
supsub: function(group, options) {
var children = [buildGroup(group.value.base, options)];
if (group.value.sub) {
children.push(buildGroup(group.value.sub));
children.push(buildGroup(group.value.sub, options));
}
if (group.value.sup) {
children.push(buildGroup(group.value.sup));
children.push(buildGroup(group.value.sup, options));
}
var nodeType;
@@ -145,11 +190,11 @@ var groupTypes = {
return node;
},
genfrac: function(group) {
genfrac: function(group, options) {
var node = new mathMLTree.MathNode(
"mfrac",
[buildGroup(group.value.numer),
buildGroup(group.value.denom)]);
[buildGroup(group.value.numer, options),
buildGroup(group.value.denom, options)]);
if (!group.value.hasBarLine) {
node.setAttribute("linethickness", "0px");
@@ -186,35 +231,35 @@ var groupTypes = {
return node;
},
array: function(group) {
array: function(group, options) {
return new mathMLTree.MathNode(
"mtable", group.value.body.map(function(row) {
return new mathMLTree.MathNode(
"mtr", row.map(function(cell) {
return new mathMLTree.MathNode(
"mtd", [buildGroup(cell)]);
"mtd", [buildGroup(cell, options)]);
}));
}));
},
sqrt: function(group) {
sqrt: function(group, options) {
var node;
if (group.value.index) {
node = new mathMLTree.MathNode(
"mroot", [
buildGroup(group.value.body),
buildGroup(group.value.index)
buildGroup(group.value.body, options),
buildGroup(group.value.index, options)
]);
} else {
node = new mathMLTree.MathNode(
"msqrt", [buildGroup(group.value.body)]);
"msqrt", [buildGroup(group.value.body, options)]);
}
return node;
},
leftright: function(group) {
var inner = buildExpression(group.value.body);
leftright: function(group, options) {
var inner = buildExpression(group.value.body, options);
if (group.value.left !== ".") {
var leftNode = new mathMLTree.MathNode(
@@ -239,24 +284,19 @@ var groupTypes = {
return outerNode;
},
accent: function(group) {
accent: function(group, options) {
var accentNode = new mathMLTree.MathNode(
"mo", [makeText(group.value.accent, group.mode)]);
var node = new mathMLTree.MathNode(
"mover",
[buildGroup(group.value.base),
[buildGroup(group.value.base, options),
accentNode]);
node.setAttribute("accent", "true");
return node;
},
font: function(group) {
// pass through so we can render something without throwing
return buildGroup(group.value.body);
},
spacing: function(group) {
var node;
@@ -303,6 +343,11 @@ var groupTypes = {
return node;
},
font: function(group, options) {
var font = group.value.font;
return buildGroup(group.value.body, options.withFont(font));
},
delimsizing: function(group) {
var children = [];
@@ -326,8 +371,8 @@ var groupTypes = {
return node;
},
styling: function(group) {
var inner = buildExpression(group.value.value, inner);
styling: function(group, options) {
var inner = buildExpression(group.value.value, options);
var node = new mathMLTree.MathNode("mstyle", inner);
@@ -346,28 +391,30 @@ var groupTypes = {
return node;
},
sizing: function(group) {
var inner = buildExpression(group.value.value);
sizing: function(group, options) {
var inner = buildExpression(group.value.value, options);
var node = new mathMLTree.MathNode("mstyle", inner);
// TODO(emily): This doesn't produce the correct size for nested size
// changes, because we don't keep state of what style we're currently
// in, so we can't reset the size to normal before changing it.
// in, so we can't reset the size to normal before changing it. Now
// that we're passing an options parameter we should be able to fix
// this.
node.setAttribute(
"mathsize", buildCommon.sizingMultiplier[group.value.size] + "em");
return node;
},
overline: function(group) {
overline: function(group, options) {
var operator = new mathMLTree.MathNode(
"mo", [new mathMLTree.TextNode("\u203e")]);
operator.setAttribute("stretchy", "true");
var node = new mathMLTree.MathNode(
"mover",
[buildGroup(group.value.body),
[buildGroup(group.value.body, options),
operator]);
node.setAttribute("accent", "true");
@@ -382,9 +429,9 @@ var groupTypes = {
return node;
},
llap: function(group) {
llap: function(group, options) {
var node = new mathMLTree.MathNode(
"mpadded", [buildGroup(group.value.body)]);
"mpadded", [buildGroup(group.value.body, options)]);
node.setAttribute("lspace", "-1width");
node.setAttribute("width", "0px");
@@ -392,9 +439,9 @@ var groupTypes = {
return node;
},
rlap: function(group) {
rlap: function(group, options) {
var node = new mathMLTree.MathNode(
"mpadded", [buildGroup(group.value.body)]);
"mpadded", [buildGroup(group.value.body, options)]);
node.setAttribute("width", "0px");
@@ -402,7 +449,7 @@ var groupTypes = {
},
phantom: function(group, options, prev) {
var inner = buildExpression(group.value.value);
var inner = buildExpression(group.value.value, options);
return new mathMLTree.MathNode("mphantom", inner);
}
};
@@ -412,11 +459,11 @@ var groupTypes = {
* MathML nodes. A little simpler than the HTML version because we don't do any
* previous-node handling.
*/
var buildExpression = function(expression) {
var buildExpression = function(expression, options) {
var groups = [];
for (var i = 0; i < expression.length; i++) {
var group = expression[i];
groups.push(buildGroup(group));
groups.push(buildGroup(group, options));
}
return groups;
};
@@ -425,14 +472,14 @@ var buildExpression = function(expression) {
* Takes a group from the parser and calls the appropriate groupTypes function
* on it to produce a MathML node.
*/
var buildGroup = function(group) {
var buildGroup = function(group, options) {
if (!group) {
return new mathMLTree.MathNode("mrow");
}
if (groupTypes[group.type]) {
// Call the groupTypes function
return groupTypes[group.type](group);
return groupTypes[group.type](group, options);
} else {
throw new ParseError(
"Got group of unknown type: '" + group.type + "'");
@@ -447,8 +494,8 @@ var buildGroup = function(group) {
* Note that we actually return a domTree element with a `<math>` inside it so
* we can do appropriate styling.
*/
var buildMathML = function(tree, texExpression, settings) {
var expression = buildExpression(tree);
var buildMathML = function(tree, texExpression, options) {
var expression = buildExpression(tree, options);
// Wrap up the expression in an mrow so it is presented in the semantics
// tag correctly.

View File

@@ -1,15 +1,30 @@
var buildHTML = require("./buildHTML");
var buildMathML = require("./buildMathML");
var buildCommon = require("./buildCommon");
var Options = require("./Options");
var Settings = require("./Settings");
var Style = require("./Style");
var makeSpan = buildCommon.makeSpan;
var buildTree = function(tree, expression, settings) {
settings = settings || new Settings({});
var startStyle = Style.TEXT;
if (settings.displayMode) {
startStyle = Style.DISPLAY;
}
// Setup the default options
var options = new Options({
style: startStyle,
size: "size5"
});
// `buildHTML` sometimes messes with the parse tree (like turning bins ->
// ords), so we build the MathML version first.
var mathMLNode = buildMathML(tree, expression, settings);
var htmlNode = buildHTML(tree, settings);
var mathMLNode = buildMathML(tree, expression, options);
var htmlNode = buildHTML(tree, options);
var katexNode = makeSpan(["katex"], [
mathMLNode, htmlNode