summaryrefslogtreecommitdiffstats
path: root/webapp/non_npm_dependencies/katex/src/buildMathML.js
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/non_npm_dependencies/katex/src/buildMathML.js')
-rw-r--r--webapp/non_npm_dependencies/katex/src/buildMathML.js519
1 files changed, 519 insertions, 0 deletions
diff --git a/webapp/non_npm_dependencies/katex/src/buildMathML.js b/webapp/non_npm_dependencies/katex/src/buildMathML.js
new file mode 100644
index 000000000..f3c7c5e1c
--- /dev/null
+++ b/webapp/non_npm_dependencies/katex/src/buildMathML.js
@@ -0,0 +1,519 @@
+/**
+ * This file converts a parse tree into a cooresponding MathML tree. The main
+ * entry point is the `buildMathML` function, which takes a parse tree from the
+ * parser.
+ */
+
+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
+ * optional replacement from symbols.js.
+ */
+var makeText = function(text, mode) {
+ if (symbols[mode][text] && symbols[mode][text].replace) {
+ text = symbols[mode][text].replace;
+ }
+
+ 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 = {};
+
+groupTypes.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;
+};
+
+groupTypes.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", variant);
+ }
+
+ return node;
+};
+
+groupTypes.bin = function(group) {
+ var node = new mathMLTree.MathNode(
+ "mo", [makeText(group.value, group.mode)]);
+
+ return node;
+};
+
+groupTypes.rel = function(group) {
+ var node = new mathMLTree.MathNode(
+ "mo", [makeText(group.value, group.mode)]);
+
+ return node;
+};
+
+groupTypes.open = function(group) {
+ var node = new mathMLTree.MathNode(
+ "mo", [makeText(group.value, group.mode)]);
+
+ return node;
+};
+
+groupTypes.close = function(group) {
+ var node = new mathMLTree.MathNode(
+ "mo", [makeText(group.value, group.mode)]);
+
+ return node;
+};
+
+groupTypes.inner = function(group) {
+ var node = new mathMLTree.MathNode(
+ "mo", [makeText(group.value, group.mode)]);
+
+ return node;
+};
+
+groupTypes.punct = function(group) {
+ var node = new mathMLTree.MathNode(
+ "mo", [makeText(group.value, group.mode)]);
+
+ node.setAttribute("separator", "true");
+
+ return node;
+};
+
+groupTypes.ordgroup = function(group, options) {
+ var inner = buildExpression(group.value, options);
+
+ var node = new mathMLTree.MathNode("mrow", inner);
+
+ return node;
+};
+
+groupTypes.text = function(group, options) {
+ var inner = buildExpression(group.value.body, options);
+
+ var node = new mathMLTree.MathNode("mtext", inner);
+
+ return node;
+};
+
+groupTypes.color = function(group, options) {
+ var inner = buildExpression(group.value.value, options);
+
+ var node = new mathMLTree.MathNode("mstyle", inner);
+
+ node.setAttribute("mathcolor", group.value.color);
+
+ return node;
+};
+
+groupTypes.supsub = function(group, options) {
+ var children = [buildGroup(group.value.base, options)];
+
+ if (group.value.sub) {
+ children.push(buildGroup(group.value.sub, options));
+ }
+
+ if (group.value.sup) {
+ children.push(buildGroup(group.value.sup, options));
+ }
+
+ var nodeType;
+ if (!group.value.sub) {
+ nodeType = "msup";
+ } else if (!group.value.sup) {
+ nodeType = "msub";
+ } else {
+ nodeType = "msubsup";
+ }
+
+ var node = new mathMLTree.MathNode(nodeType, children);
+
+ return node;
+};
+
+groupTypes.genfrac = function(group, options) {
+ var node = new mathMLTree.MathNode(
+ "mfrac",
+ [buildGroup(group.value.numer, options),
+ buildGroup(group.value.denom, options)]);
+
+ if (!group.value.hasBarLine) {
+ node.setAttribute("linethickness", "0px");
+ }
+
+ if (group.value.leftDelim != null || group.value.rightDelim != null) {
+ var withDelims = [];
+
+ if (group.value.leftDelim != null) {
+ var leftOp = new mathMLTree.MathNode(
+ "mo", [new mathMLTree.TextNode(group.value.leftDelim)]);
+
+ leftOp.setAttribute("fence", "true");
+
+ withDelims.push(leftOp);
+ }
+
+ withDelims.push(node);
+
+ if (group.value.rightDelim != null) {
+ var rightOp = new mathMLTree.MathNode(
+ "mo", [new mathMLTree.TextNode(group.value.rightDelim)]);
+
+ rightOp.setAttribute("fence", "true");
+
+ withDelims.push(rightOp);
+ }
+
+ var outerNode = new mathMLTree.MathNode("mrow", withDelims);
+
+ return outerNode;
+ }
+
+ return node;
+};
+
+groupTypes.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, options)]);
+ }));
+ }));
+};
+
+groupTypes.sqrt = function(group, options) {
+ var node;
+ if (group.value.index) {
+ node = new mathMLTree.MathNode(
+ "mroot", [
+ buildGroup(group.value.body, options),
+ buildGroup(group.value.index, options),
+ ]);
+ } else {
+ node = new mathMLTree.MathNode(
+ "msqrt", [buildGroup(group.value.body, options)]);
+ }
+
+ return node;
+};
+
+groupTypes.leftright = function(group, options) {
+ var inner = buildExpression(group.value.body, options);
+
+ if (group.value.left !== ".") {
+ var leftNode = new mathMLTree.MathNode(
+ "mo", [makeText(group.value.left, group.mode)]);
+
+ leftNode.setAttribute("fence", "true");
+
+ inner.unshift(leftNode);
+ }
+
+ if (group.value.right !== ".") {
+ var rightNode = new mathMLTree.MathNode(
+ "mo", [makeText(group.value.right, group.mode)]);
+
+ rightNode.setAttribute("fence", "true");
+
+ inner.push(rightNode);
+ }
+
+ var outerNode = new mathMLTree.MathNode("mrow", inner);
+
+ return outerNode;
+};
+
+groupTypes.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, options),
+ accentNode]);
+
+ node.setAttribute("accent", "true");
+
+ return node;
+};
+
+groupTypes.spacing = function(group) {
+ var node;
+
+ if (group.value === "\\ " || group.value === "\\space" ||
+ group.value === " " || group.value === "~") {
+ node = new mathMLTree.MathNode(
+ "mtext", [new mathMLTree.TextNode("\u00a0")]);
+ } else {
+ node = new mathMLTree.MathNode("mspace");
+
+ node.setAttribute(
+ "width", buildCommon.spacingFunctions[group.value].size);
+ }
+
+ return node;
+};
+
+groupTypes.op = function(group) {
+ var node;
+
+ // TODO(emily): handle big operators using the `largeop` attribute
+
+ if (group.value.symbol) {
+ // This is a symbol. Just add the symbol.
+ node = new mathMLTree.MathNode(
+ "mo", [makeText(group.value.body, group.mode)]);
+ } else {
+ // This is a text operator. Add all of the characters from the
+ // operator's name.
+ // TODO(emily): Add a space in the middle of some of these
+ // operators, like \limsup.
+ node = new mathMLTree.MathNode(
+ "mi", [new mathMLTree.TextNode(group.value.body.slice(1))]);
+ }
+
+ return node;
+};
+
+groupTypes.katex = function(group) {
+ var node = new mathMLTree.MathNode(
+ "mtext", [new mathMLTree.TextNode("KaTeX")]);
+
+ return node;
+};
+
+groupTypes.font = function(group, options) {
+ var font = group.value.font;
+ return buildGroup(group.value.body, options.withFont(font));
+};
+
+groupTypes.delimsizing = function(group) {
+ var children = [];
+
+ if (group.value.value !== ".") {
+ children.push(makeText(group.value.value, group.mode));
+ }
+
+ var node = new mathMLTree.MathNode("mo", children);
+
+ if (group.value.delimType === "open" ||
+ group.value.delimType === "close") {
+ // Only some of the delimsizing functions act as fences, and they
+ // return "open" or "close" delimTypes.
+ node.setAttribute("fence", "true");
+ } else {
+ // Explicitly disable fencing if it's not a fence, to override the
+ // defaults.
+ node.setAttribute("fence", "false");
+ }
+
+ return node;
+};
+
+groupTypes.styling = function(group, options) {
+ var inner = buildExpression(group.value.value, options);
+
+ var node = new mathMLTree.MathNode("mstyle", inner);
+
+ var styleAttributes = {
+ "display": ["0", "true"],
+ "text": ["0", "false"],
+ "script": ["1", "false"],
+ "scriptscript": ["2", "false"],
+ };
+
+ var attr = styleAttributes[group.value.style];
+
+ node.setAttribute("scriptlevel", attr[0]);
+ node.setAttribute("displaystyle", attr[1]);
+
+ return node;
+};
+
+groupTypes.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. 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;
+};
+
+groupTypes.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, options),
+ operator]);
+ node.setAttribute("accent", "true");
+
+ return node;
+};
+
+groupTypes.rule = function(group) {
+ // TODO(emily): Figure out if there's an actual way to draw black boxes
+ // in MathML.
+ var node = new mathMLTree.MathNode("mrow");
+
+ return node;
+};
+
+groupTypes.llap = function(group, options) {
+ var node = new mathMLTree.MathNode(
+ "mpadded", [buildGroup(group.value.body, options)]);
+
+ node.setAttribute("lspace", "-1width");
+ node.setAttribute("width", "0px");
+
+ return node;
+};
+
+groupTypes.rlap = function(group, options) {
+ var node = new mathMLTree.MathNode(
+ "mpadded", [buildGroup(group.value.body, options)]);
+
+ node.setAttribute("width", "0px");
+
+ return node;
+};
+
+groupTypes.phantom = function(group, options, prev) {
+ var inner = buildExpression(group.value.value, options);
+ return new mathMLTree.MathNode("mphantom", inner);
+};
+
+/**
+ * Takes a list of nodes, builds them, and returns a list of the generated
+ * MathML nodes. A little simpler than the HTML version because we don't do any
+ * previous-node handling.
+ */
+var buildExpression = function(expression, options) {
+ var groups = [];
+ for (var i = 0; i < expression.length; i++) {
+ var group = expression[i];
+ groups.push(buildGroup(group, options));
+ }
+ return groups;
+};
+
+/**
+ * Takes a group from the parser and calls the appropriate groupTypes function
+ * on it to produce a MathML node.
+ */
+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, options);
+ } else {
+ throw new ParseError(
+ "Got group of unknown type: '" + group.type + "'");
+ }
+};
+
+/**
+ * Takes a full parse tree and settings and builds a MathML representation of
+ * it. In particular, we put the elements from building the parse tree into a
+ * <semantics> tag so we can also include that TeX source as an annotation.
+ *
+ * Note that we actually return a domTree element with a `<math>` inside it so
+ * we can do appropriate styling.
+ */
+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.
+ var wrapper = new mathMLTree.MathNode("mrow", expression);
+
+ // Build a TeX annotation of the source
+ var annotation = new mathMLTree.MathNode(
+ "annotation", [new mathMLTree.TextNode(texExpression)]);
+
+ annotation.setAttribute("encoding", "application/x-tex");
+
+ var semantics = new mathMLTree.MathNode(
+ "semantics", [wrapper, annotation]);
+
+ var math = new mathMLTree.MathNode("math", [semantics]);
+
+ // You can't style <math> nodes, so we wrap the node in a span.
+ return makeSpan(["katex-mathml"], [math]);
+};
+
+module.exports = buildMathML;