|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
|
|
|
var makeText = function(text, mode) { |
|
if (symbols[mode][text] && symbols[mode][text].replace) { |
|
text = symbols[mode][text].replace; |
|
} |
|
|
|
return new mathMLTree.TextNode(text); |
|
}; |
|
|
|
|
|
|
|
|
|
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; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
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)) { |
|
|
|
|
|
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; |
|
|
|
|
|
|
|
if (group.value.symbol) { |
|
|
|
node = new mathMLTree.MathNode( |
|
"mo", [makeText(group.value.body, group.mode)]); |
|
} else { |
|
|
|
|
|
|
|
|
|
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") { |
|
|
|
|
|
node.setAttribute("fence", "true"); |
|
} else { |
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
|
|
|
|
|
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.underline = function(group, options) { |
|
var operator = new mathMLTree.MathNode( |
|
"mo", [new mathMLTree.TextNode("\u203e")]); |
|
operator.setAttribute("stretchy", "true"); |
|
|
|
var node = new mathMLTree.MathNode( |
|
"munder", |
|
[buildGroup(group.value.body, options), |
|
operator]); |
|
node.setAttribute("accentunder", "true"); |
|
|
|
return node; |
|
}; |
|
|
|
groupTypes.rule = function(group) { |
|
|
|
|
|
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); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
var buildGroup = function(group, options) { |
|
if (!group) { |
|
return new mathMLTree.MathNode("mrow"); |
|
} |
|
|
|
if (groupTypes[group.type]) { |
|
|
|
return groupTypes[group.type](group, options); |
|
} else { |
|
throw new ParseError( |
|
"Got group of unknown type: '" + group.type + "'"); |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var buildMathML = function(tree, texExpression, options) { |
|
var expression = buildExpression(tree, options); |
|
|
|
|
|
|
|
var wrapper = new mathMLTree.MathNode("mrow", expression); |
|
|
|
|
|
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]); |
|
|
|
|
|
return makeSpan(["katex-mathml"], [math]); |
|
}; |
|
|
|
module.exports = buildMathML; |
|
|