Spaces:
Running
on
T4
Running
on
T4
| /* eslint no-console:0 */ | |
| /** | |
| * This module contains general functions that can be used for building | |
| * different kinds of domTree nodes in a consistent manner. | |
| */ | |
| var domTree = require("./domTree"); | |
| var fontMetrics = require("./fontMetrics"); | |
| var symbols = require("./symbols"); | |
| var utils = require("./utils"); | |
| var greekCapitals = [ | |
| "\\Gamma", | |
| "\\Delta", | |
| "\\Theta", | |
| "\\Lambda", | |
| "\\Xi", | |
| "\\Pi", | |
| "\\Sigma", | |
| "\\Upsilon", | |
| "\\Phi", | |
| "\\Psi", | |
| "\\Omega", | |
| ]; | |
| var dotlessLetters = [ | |
| "\u0131", // dotless i, \imath | |
| "\u0237", // dotless j, \jmath | |
| ]; | |
| /** | |
| * Makes a symbolNode after translation via the list of symbols in symbols.js. | |
| * Correctly pulls out metrics for the character, and optionally takes a list of | |
| * classes to be attached to the node. | |
| */ | |
| var makeSymbol = function(value, style, mode, color, classes) { | |
| // Replace the value with its replaced value from symbol.js | |
| if (symbols[mode][value] && symbols[mode][value].replace) { | |
| value = symbols[mode][value].replace; | |
| } | |
| var metrics = fontMetrics.getCharacterMetrics(value, style); | |
| var symbolNode; | |
| if (metrics) { | |
| symbolNode = new domTree.symbolNode( | |
| value, metrics.height, metrics.depth, metrics.italic, metrics.skew, | |
| classes); | |
| } else { | |
| // TODO(emily): Figure out a good way to only print this in development | |
| typeof console !== "undefined" && console.warn( | |
| "No character metrics for '" + value + "' in style '" + | |
| style + "'"); | |
| symbolNode = new domTree.symbolNode(value, 0, 0, 0, 0, classes); | |
| } | |
| if (color) { | |
| symbolNode.style.color = color; | |
| } | |
| return symbolNode; | |
| }; | |
| /** | |
| * Makes a symbol in Main-Regular or AMS-Regular. | |
| * Used for rel, bin, open, close, inner, and punct. | |
| */ | |
| var mathsym = function(value, mode, color, classes) { | |
| // Decide what font to render the symbol in by its entry in the symbols | |
| // table. | |
| // Have a special case for when the value = \ because the \ is used as a | |
| // textord in unsupported command errors but cannot be parsed as a regular | |
| // text ordinal and is therefore not present as a symbol in the symbols | |
| // table for text | |
| if (value === "\\" || symbols[mode][value].font === "main") { | |
| return makeSymbol(value, "Main-Regular", mode, color, classes); | |
| } else { | |
| return makeSymbol( | |
| value, "AMS-Regular", mode, color, classes.concat(["amsrm"])); | |
| } | |
| }; | |
| /** | |
| * Makes a symbol in the default font for mathords and textords. | |
| */ | |
| var mathDefault = function(value, mode, color, classes, type) { | |
| if (type === "mathord") { | |
| return mathit(value, mode, color, classes); | |
| } else if (type === "textord") { | |
| return makeSymbol( | |
| value, "Main-Regular", mode, color, classes.concat(["mathrm"])); | |
| } else { | |
| throw new Error("unexpected type: " + type + " in mathDefault"); | |
| } | |
| }; | |
| /** | |
| * Makes a symbol in the italic math font. | |
| */ | |
| var mathit = function(value, mode, color, classes) { | |
| if (/[0-9]/.test(value.charAt(0)) || | |
| // glyphs for \imath and \jmath do not exist in Math-Italic so we | |
| // need to use Main-Italic instead | |
| utils.contains(dotlessLetters, value) || | |
| utils.contains(greekCapitals, value)) { | |
| return makeSymbol( | |
| value, "Main-Italic", mode, color, classes.concat(["mainit"])); | |
| } else { | |
| return makeSymbol( | |
| value, "Math-Italic", mode, color, classes.concat(["mathit"])); | |
| } | |
| }; | |
| /** | |
| * Makes either a mathord or textord in the correct font and color. | |
| */ | |
| var makeOrd = function(group, options, type) { | |
| var mode = group.mode; | |
| var value = group.value; | |
| if (symbols[mode][value] && symbols[mode][value].replace) { | |
| value = symbols[mode][value].replace; | |
| } | |
| var classes = ["mord"]; | |
| var color = options.getColor(); | |
| var font = options.font; | |
| if (font) { | |
| if (font === "mathit" || utils.contains(dotlessLetters, value)) { | |
| return mathit(value, mode, color, classes); | |
| } else { | |
| var fontName = fontMap[font].fontName; | |
| if (fontMetrics.getCharacterMetrics(value, fontName)) { | |
| return makeSymbol( | |
| value, fontName, mode, color, classes.concat([font])); | |
| } else { | |
| return mathDefault(value, mode, color, classes, type); | |
| } | |
| } | |
| } else { | |
| return mathDefault(value, mode, color, classes, type); | |
| } | |
| }; | |
| /** | |
| * Calculate the height, depth, and maxFontSize of an element based on its | |
| * children. | |
| */ | |
| var sizeElementFromChildren = function(elem) { | |
| var height = 0; | |
| var depth = 0; | |
| var maxFontSize = 0; | |
| if (elem.children) { | |
| for (var i = 0; i < elem.children.length; i++) { | |
| if (elem.children[i].height > height) { | |
| height = elem.children[i].height; | |
| } | |
| if (elem.children[i].depth > depth) { | |
| depth = elem.children[i].depth; | |
| } | |
| if (elem.children[i].maxFontSize > maxFontSize) { | |
| maxFontSize = elem.children[i].maxFontSize; | |
| } | |
| } | |
| } | |
| elem.height = height; | |
| elem.depth = depth; | |
| elem.maxFontSize = maxFontSize; | |
| }; | |
| /** | |
| * Makes a span with the given list of classes, list of children, and color. | |
| */ | |
| var makeSpan = function(classes, children, color) { | |
| var span = new domTree.span(classes, children); | |
| sizeElementFromChildren(span); | |
| if (color) { | |
| span.style.color = color; | |
| } | |
| return span; | |
| }; | |
| /** | |
| * Makes a document fragment with the given list of children. | |
| */ | |
| var makeFragment = function(children) { | |
| var fragment = new domTree.documentFragment(children); | |
| sizeElementFromChildren(fragment); | |
| return fragment; | |
| }; | |
| /** | |
| * Makes an element placed in each of the vlist elements to ensure that each | |
| * element has the same max font size. To do this, we create a zero-width space | |
| * with the correct font size. | |
| */ | |
| var makeFontSizer = function(options, fontSize) { | |
| var fontSizeInner = makeSpan([], [new domTree.symbolNode("\u200b")]); | |
| fontSizeInner.style.fontSize = | |
| (fontSize / options.style.sizeMultiplier) + "em"; | |
| var fontSizer = makeSpan( | |
| ["fontsize-ensurer", "reset-" + options.size, "size5"], | |
| [fontSizeInner]); | |
| return fontSizer; | |
| }; | |
| /** | |
| * Makes a vertical list by stacking elements and kerns on top of each other. | |
| * Allows for many different ways of specifying the positioning method. | |
| * | |
| * Arguments: | |
| * - children: A list of child or kern nodes to be stacked on top of each other | |
| * (i.e. the first element will be at the bottom, and the last at | |
| * the top). Element nodes are specified as | |
| * {type: "elem", elem: node} | |
| * while kern nodes are specified as | |
| * {type: "kern", size: size} | |
| * - positionType: The method by which the vlist should be positioned. Valid | |
| * values are: | |
| * - "individualShift": The children list only contains elem | |
| * nodes, and each node contains an extra | |
| * "shift" value of how much it should be | |
| * shifted (note that shifting is always | |
| * moving downwards). positionData is | |
| * ignored. | |
| * - "top": The positionData specifies the topmost point of | |
| * the vlist (note this is expected to be a height, | |
| * so positive values move up) | |
| * - "bottom": The positionData specifies the bottommost point | |
| * of the vlist (note this is expected to be a | |
| * depth, so positive values move down | |
| * - "shift": The vlist will be positioned such that its | |
| * baseline is positionData away from the baseline | |
| * of the first child. Positive values move | |
| * downwards. | |
| * - "firstBaseline": The vlist will be positioned such that | |
| * its baseline is aligned with the | |
| * baseline of the first child. | |
| * positionData is ignored. (this is | |
| * equivalent to "shift" with | |
| * positionData=0) | |
| * - positionData: Data used in different ways depending on positionType | |
| * - options: An Options object | |
| * | |
| */ | |
| var makeVList = function(children, positionType, positionData, options) { | |
| var depth; | |
| var currPos; | |
| var i; | |
| if (positionType === "individualShift") { | |
| var oldChildren = children; | |
| children = [oldChildren[0]]; | |
| // Add in kerns to the list of children to get each element to be | |
| // shifted to the correct specified shift | |
| depth = -oldChildren[0].shift - oldChildren[0].elem.depth; | |
| currPos = depth; | |
| for (i = 1; i < oldChildren.length; i++) { | |
| var diff = -oldChildren[i].shift - currPos - | |
| oldChildren[i].elem.depth; | |
| var size = diff - | |
| (oldChildren[i - 1].elem.height + | |
| oldChildren[i - 1].elem.depth); | |
| currPos = currPos + diff; | |
| children.push({type: "kern", size: size}); | |
| children.push(oldChildren[i]); | |
| } | |
| } else if (positionType === "top") { | |
| // We always start at the bottom, so calculate the bottom by adding up | |
| // all the sizes | |
| var bottom = positionData; | |
| for (i = 0; i < children.length; i++) { | |
| if (children[i].type === "kern") { | |
| bottom -= children[i].size; | |
| } else { | |
| bottom -= children[i].elem.height + children[i].elem.depth; | |
| } | |
| } | |
| depth = bottom; | |
| } else if (positionType === "bottom") { | |
| depth = -positionData; | |
| } else if (positionType === "shift") { | |
| depth = -children[0].elem.depth - positionData; | |
| } else if (positionType === "firstBaseline") { | |
| depth = -children[0].elem.depth; | |
| } else { | |
| depth = 0; | |
| } | |
| // Make the fontSizer | |
| var maxFontSize = 0; | |
| for (i = 0; i < children.length; i++) { | |
| if (children[i].type === "elem") { | |
| maxFontSize = Math.max(maxFontSize, children[i].elem.maxFontSize); | |
| } | |
| } | |
| var fontSizer = makeFontSizer(options, maxFontSize); | |
| // Create a new list of actual children at the correct offsets | |
| var realChildren = []; | |
| currPos = depth; | |
| for (i = 0; i < children.length; i++) { | |
| if (children[i].type === "kern") { | |
| currPos += children[i].size; | |
| } else { | |
| var child = children[i].elem; | |
| var shift = -child.depth - currPos; | |
| currPos += child.height + child.depth; | |
| var childWrap = makeSpan([], [fontSizer, child]); | |
| childWrap.height -= shift; | |
| childWrap.depth += shift; | |
| childWrap.style.top = shift + "em"; | |
| realChildren.push(childWrap); | |
| } | |
| } | |
| // Add in an element at the end with no offset to fix the calculation of | |
| // baselines in some browsers (namely IE, sometimes safari) | |
| var baselineFix = makeSpan( | |
| ["baseline-fix"], [fontSizer, new domTree.symbolNode("\u200b")]); | |
| realChildren.push(baselineFix); | |
| var vlist = makeSpan(["vlist"], realChildren); | |
| // Fix the final height and depth, in case there were kerns at the ends | |
| // since the makeSpan calculation won't take that in to account. | |
| vlist.height = Math.max(currPos, vlist.height); | |
| vlist.depth = Math.max(-depth, vlist.depth); | |
| return vlist; | |
| }; | |
| // A table of size -> font size for the different sizing functions | |
| var sizingMultiplier = { | |
| size1: 0.5, | |
| size2: 0.7, | |
| size3: 0.8, | |
| size4: 0.9, | |
| size5: 1.0, | |
| size6: 1.2, | |
| size7: 1.44, | |
| size8: 1.73, | |
| size9: 2.07, | |
| size10: 2.49, | |
| }; | |
| // A map of spacing functions to their attributes, like size and corresponding | |
| // CSS class | |
| var spacingFunctions = { | |
| "\\qquad": { | |
| size: "2em", | |
| className: "qquad", | |
| }, | |
| "\\quad": { | |
| size: "1em", | |
| className: "quad", | |
| }, | |
| "\\enspace": { | |
| size: "0.5em", | |
| className: "enspace", | |
| }, | |
| "\\;": { | |
| size: "0.277778em", | |
| className: "thickspace", | |
| }, | |
| "\\:": { | |
| size: "0.22222em", | |
| className: "mediumspace", | |
| }, | |
| "\\,": { | |
| size: "0.16667em", | |
| className: "thinspace", | |
| }, | |
| "\\!": { | |
| size: "-0.16667em", | |
| className: "negativethinspace", | |
| }, | |
| }; | |
| /** | |
| * Maps TeX font commands to objects containing: | |
| * - variant: string used for "mathvariant" attribute in buildMathML.js | |
| * - fontName: the "style" parameter to fontMetrics.getCharacterMetrics | |
| */ | |
| // A map between tex font commands an MathML mathvariant attribute values | |
| var fontMap = { | |
| // styles | |
| "mathbf": { | |
| variant: "bold", | |
| fontName: "Main-Bold", | |
| }, | |
| "mathrm": { | |
| variant: "normal", | |
| fontName: "Main-Regular", | |
| }, | |
| // "mathit" is missing because it requires the use of two fonts: Main-Italic | |
| // and Math-Italic. This is handled by a special case in makeOrd which ends | |
| // up calling mathit. | |
| // families | |
| "mathbb": { | |
| variant: "double-struck", | |
| fontName: "AMS-Regular", | |
| }, | |
| "mathcal": { | |
| variant: "script", | |
| fontName: "Caligraphic-Regular", | |
| }, | |
| "mathfrak": { | |
| variant: "fraktur", | |
| fontName: "Fraktur-Regular", | |
| }, | |
| "mathscr": { | |
| variant: "script", | |
| fontName: "Script-Regular", | |
| }, | |
| "mathsf": { | |
| variant: "sans-serif", | |
| fontName: "SansSerif-Regular", | |
| }, | |
| "mathtt": { | |
| variant: "monospace", | |
| fontName: "Typewriter-Regular", | |
| }, | |
| }; | |
| module.exports = { | |
| fontMap: fontMap, | |
| makeSymbol: makeSymbol, | |
| mathsym: mathsym, | |
| makeSpan: makeSpan, | |
| makeFragment: makeFragment, | |
| makeVList: makeVList, | |
| makeOrd: makeOrd, | |
| sizingMultiplier: sizingMultiplier, | |
| spacingFunctions: spacingFunctions, | |
| }; | |