"9123": [0.64502, 1.155, 0, 0, 0.66667], "9124": [0.64502, 1.155, 0, 0, 0.66667], "9125": [-0.00099, 0.601, 0, 0, 0.66667], "9126": [0.64502, 1.155, 0, 0, 0.66667], "9127": [1e-05, 0.9, 0, 0, 0.88889], "9128": [0.65002, 1.15, 0, 0, 0.88889], "9129": [0.90001, 0, 0, 0, 0.88889], "9130": [0, 0.3, 0, 0, 0.88889], "9131": [1e-05, 0.9, 0, 0, 0.88889], "9132": [0.65002, 1.15, 0, 0, 0.88889], "9133": [0.90001, 0, 0, 0, 0.88889], "9143": [0.88502, 0.915, 0, 0, 1.05556], "10216": [1.25003, 1.75, 0, 0, 0.80556], "10217": [1.25003, 1.75, 0, 0, 0.80556], "57344": [-0.00499, 0.605, 0, 0, 1.05556], "57345": [-0.00499, 0.605, 0, 0, 1.05556], "57680": [0, 0.12, 0, 0, 0.45], "57681": [0, 0.12, 0, 0, 0.45], "57682": [0, 0.12, 0, 0, 0.45], "57683": [0, 0.12, 0, 0, 0.45] }, "Typewriter-Regular": { "32": [0, 0, 0, 0, 0.525], "33": [0, 0.61111, 0, 0, 0.525], "34": [0, 0.61111, 0, 0, 0.525], "35": [0, 0.61111, 0, 0, 0.525], "36": [0.08333, 0.69444, 0, 0, 0.525], "37": [0.08333, 0.69444, 0, 0, 0.525], "38": [0, 0.61111, 0, 0, 0.525], "39": [0, 0.61111, 0, 0, 0.525], "40": [0.08333, 0.69444, 0, 0, 0.525], "41": [0.08333, 0.69444, 0, 0, 0.525], "42": [0, 0.52083, 0, 0, 0.525], "43": [-0.08056, 0.53055, 0, 0, 0.525], "44": [0.13889, 0.125, 0, 0, 0.525], "45": [-0.08056, 0.53055, 0, 0, 0.525], "46": [0, 0.125, 0, 0, 0.525], "47": [0.08333, 0.69444, 0, 0, 0.525], "48": [0, 0.61111, 0, 0, 0.525], "49": [0, 0.61111, 0, 0, 0.525], "50": [0, 0.61111, 0, 0, 0.525], "51": [0, 0.61111, 0, 0, 0.525], "52": [0, 0.61111, 0, 0, 0.525], "53": [0, 0.61111, 0, 0, 0.525], "54": [0, 0.61111, 0, 0, 0.525], "55": [0, 0.61111, 0, 0, 0.525], "56": [0, 0.61111, 0, 0, 0.525], "57": [0, 0.61111, 0, 0, 0.525], "58": [0, 0.43056, 0, 0, 0.525], "59": [0.13889, 0.43056, 0, 0, 0.525], "60": [-0.05556, 0.55556, 0, 0, 0.525], "61": [-0.19549, 0.41562, 0, 0, 0.525], "62": [-0.05556, 0.55556, 0, 0, 0.525], "63": [0, 0.61111, 0, 0, 0.525], "64": [0, 0.61111, 0, 0, 0.525], "65": [0, 0.61111, 0, 0, 0.525], "66": [0, 0.61111, 0, 0, 0.525], "67": [0, 0.61111, 0, 0, 0.525], "68": [0, 0.61111, 0, 0, 0.525], "69": [0, 0.61111, 0, 0, 0.525], "70": [0, 0.61111, 0, 0, 0.525], "71": [0, 0.61111, 0, 0, 0.525], "72": [0, 0.61111, 0, 0, 0.525], "73": [0, 0.61111, 0, 0, 0.525], "74": [0, 0.61111, 0, 0, 0.525], "75": [0, 0.61111, 0, 0, 0.525], "76": [0, 0.61111, 0, 0, 0.525], "77": [0, 0.61111, 0, 0, 0.525], "78": [0, 0.61111, 0, 0, 0.525], "79": [0, 0.61111, 0, 0, 0.525], "80": [0, 0.61111, 0, 0, 0.525], "81": [0.13889, 0.61111, 0, 0, 0.525], "82": [0, 0.61111, 0, 0, 0.525], "83": [0, 0.61111, 0, 0, 0.525], "84": [0, 0.61111, 0, 0, 0.525], "85": [0, 0.61111, 0, 0, 0.525], "86": [0, 0.61111, 0, 0, 0.525], "87": [0, 0.61111, 0, 0, 0.525], "88": [0, 0.61111, 0, 0, 0.525], "89": [0, 0.61111, 0, 0, 0.525], "90": [0, 0.61111, 0, 0, 0.525], "91": [0.08333, 0.69444, 0, 0, 0.525], "92": [0.08333, 0.69444, 0, 0, 0.525], "93": [0.08333, 0.69444, 0, 0, 0.525], "94": [0, 0.61111, 0, 0, 0.525], "95": [0.09514, 0, 0, 0, 0.525], "96": [0, 0.61111, 0, 0, 0.525], "97": [0, 0.43056, 0, 0, 0.525], "98": [0, 0.61111, 0, 0, 0.525], "99": [0, 0.43056, 0, 0, 0.525], "100": [0, 0.61111, 0, 0, 0.525], "101": [0, 0.43056, 0, 0, 0.525], "102": [0, 0.61111, 0, 0, 0.525], "103": [0.22222, 0.43056, 0, 0, 0.525], "104": [0, 0.61111, 0, 0, 0.525], "105": [0, 0.61111, 0, 0, 0.525], "106": [0.22222, 0.61111, 0, 0, 0.525], "107": [0, 0.61111, 0, 0, 0.525], "108": [0, 0.61111, 0, 0, 0.525], "109": [0, 0.43056, 0, 0, 0.525], "110": [0, 0.43056, 0, 0, 0.525], "111": [0, 0.43056, 0, 0, 0.525], "112": [0.22222, 0.43056, 0, 0, 0.525], "113": [0.22222, 0.43056, 0, 0, 0.525], "114": [0, 0.43056, 0, 0, 0.525], "115": [0, 0.43056, 0, 0, 0.525], "116": [0, 0.55358, 0, 0, 0.525], "117": [0, 0.43056, 0, 0, 0.525], "118": [0, 0.43056, 0, 0, 0.525], "119": [0, 0.43056, 0, 0, 0.525], "120": [0, 0.43056, 0, 0, 0.525], "121": [0.22222, 0.43056, 0, 0, 0.525], "122": [0, 0.43056, 0, 0, 0.525], "123": [0.08333, 0.69444, 0, 0, 0.525], "124": [0.08333, 0.69444, 0, 0, 0.525], "125": [0.08333, 0.69444, 0, 0, 0.525], "126": [0, 0.61111, 0, 0, 0.525], "127": [0, 0.61111, 0, 0, 0.525], "160": [0, 0, 0, 0, 0.525], "176": [0, 0.61111, 0, 0, 0.525], "184": [0.19445, 0, 0, 0, 0.525], "305": [0, 0.43056, 0, 0, 0.525], "567": [0.22222, 0.43056, 0, 0, 0.525], "711": [0, 0.56597, 0, 0, 0.525], "713": [0, 0.56555, 0, 0, 0.525], "714": [0, 0.61111, 0, 0, 0.525], "715": [0, 0.61111, 0, 0, 0.525], "728": [0, 0.61111, 0, 0, 0.525], "730": [0, 0.61111, 0, 0, 0.525], "770": [0, 0.61111, 0, 0, 0.525], "771": [0, 0.61111, 0, 0, 0.525], "776": [0, 0.61111, 0, 0, 0.525], "915": [0, 0.61111, 0, 0, 0.525], "916": [0, 0.61111, 0, 0, 0.525], "920": [0, 0.61111, 0, 0, 0.525], "923": [0, 0.61111, 0, 0, 0.525], "926": [0, 0.61111, 0, 0, 0.525], "928": [0, 0.61111, 0, 0, 0.525], "931": [0, 0.61111, 0, 0, 0.525], "933": [0, 0.61111, 0, 0, 0.525], "934": [0, 0.61111, 0, 0, 0.525], "936": [0, 0.61111, 0, 0, 0.525], "937": [0, 0.61111, 0, 0, 0.525], "8216": [0, 0.61111, 0, 0, 0.525], "8217": [0, 0.61111, 0, 0, 0.525], "8242": [0, 0.61111, 0, 0, 0.525], "9251": [0.11111, 0.21944, 0, 0, 0.525] } }; /** * This file contains metrics regarding fonts and individual symbols. The sigma * and xi variables, as well as the metricMap map contain data extracted from * TeX, TeX font metrics, and the TTF files. These data are then exposed via the * `metrics` variable and the getCharacterMetrics function. */ // In TeX, there are actually three sets of dimensions, one for each of // textstyle (size index 5 and higher: >=9pt), scriptstyle (size index 3 and 4: // 7-8pt), and scriptscriptstyle (size index 1 and 2: 5-6pt). These are // provided in the arrays below, in that order. // // The font metrics are stored in fonts cmsy10, cmsy7, and cmsy5 respectively. // This was determined by running the following script: // // latex -interaction=nonstopmode \ // '\documentclass{article}\usepackage{amsmath}\begin{document}' \ // '$a$ \expandafter\show\the\textfont2' \ // '\expandafter\show\the\scriptfont2' \ // '\expandafter\show\the\scriptscriptfont2' \ // '\stop' // // The metrics themselves were retrieved using the following commands: // // tftopl cmsy10 // tftopl cmsy7 // tftopl cmsy5 // // The output of each of these commands is quite lengthy. The only part we // care about is the FONTDIMEN section. Each value is measured in EMs. var sigmasAndXis = { slant: [0.250, 0.250, 0.250], // sigma1 space: [0.000, 0.000, 0.000], // sigma2 stretch: [0.000, 0.000, 0.000], // sigma3 shrink: [0.000, 0.000, 0.000], // sigma4 xHeight: [0.431, 0.431, 0.431], // sigma5 quad: [1.000, 1.171, 1.472], // sigma6 extraSpace: [0.000, 0.000, 0.000], // sigma7 num1: [0.677, 0.732, 0.925], // sigma8 num2: [0.394, 0.384, 0.387], // sigma9 num3: [0.444, 0.471, 0.504], // sigma10 denom1: [0.686, 0.752, 1.025], // sigma11 denom2: [0.345, 0.344, 0.532], // sigma12 sup1: [0.413, 0.503, 0.504], // sigma13 sup2: [0.363, 0.431, 0.404], // sigma14 sup3: [0.289, 0.286, 0.294], // sigma15 sub1: [0.150, 0.143, 0.200], // sigma16 sub2: [0.247, 0.286, 0.400], // sigma17 supDrop: [0.386, 0.353, 0.494], // sigma18 subDrop: [0.050, 0.071, 0.100], // sigma19 delim1: [2.390, 1.700, 1.980], // sigma20 delim2: [1.010, 1.157, 1.420], // sigma21 axisHeight: [0.250, 0.250, 0.250], // sigma22 // These font metrics are extracted from TeX by using tftopl on cmex10.tfm; // they correspond to the font parameters of the extension fonts (family 3). // See the TeXbook, page 441. In AMSTeX, the extension fonts scale; to // match cmex7, we'd use cmex7.tfm values for script and scriptscript // values. defaultRuleThickness: [0.04, 0.049, 0.049], // xi8; cmex7: 0.049 bigOpSpacing1: [0.111, 0.111, 0.111], // xi9 bigOpSpacing2: [0.166, 0.166, 0.166], // xi10 bigOpSpacing3: [0.2, 0.2, 0.2], // xi11 bigOpSpacing4: [0.6, 0.611, 0.611], // xi12; cmex7: 0.611 bigOpSpacing5: [0.1, 0.143, 0.143], // xi13; cmex7: 0.143 // The \sqrt rule width is taken from the height of the surd character. // Since we use the same font at all sizes, this thickness doesn't scale. sqrtRuleThickness: [0.04, 0.04, 0.04], // This value determines how large a pt is, for metrics which are defined // in terms of pts. // This value is also used in katex.scss; if you change it make sure the // values match. ptPerEm: [10.0, 10.0, 10.0], // The space between adjacent `|` columns in an array definition. From // `\showthe\doublerulesep` in LaTeX. Equals 2.0 / ptPerEm. doubleRuleSep: [0.2, 0.2, 0.2], // The width of separator lines in {array} environments. From // `\showthe\arrayrulewidth` in LaTeX. Equals 0.4 / ptPerEm. arrayRuleWidth: [0.04, 0.04, 0.04], // Two values from LaTeX source2e: fboxsep: [0.3, 0.3, 0.3], // 3 pt / ptPerEm fboxrule: [0.04, 0.04, 0.04] // 0.4 pt / ptPerEm }; // This map contains a mapping from font name and character code to character // should have Latin-1 and Cyrillic characters, but may not depending on the // operating system. The metrics do not account for extra height from the // accents. In the case of Cyrillic characters which have both ascenders and // descenders we prefer approximations with ascenders, primarily to prevent // the fraction bar or root line from intersecting the glyph. // TODO(kevinb) allow union of multiple glyph metrics for better accuracy. var extraCharacterMap = { // Latin-1 'Å': 'A', 'Ð': 'D', 'Þ': 'o', 'å': 'a', 'ð': 'd', 'þ': 'o', // Cyrillic 'А': 'A', 'Б': 'B', 'В': 'B', 'Г': 'F', 'Д': 'A', 'Е': 'E', 'Ж': 'K', 'З': '3', 'И': 'N', 'Й': 'N', 'К': 'K', 'Л': 'N', 'М': 'M', 'Н': 'H', 'О': 'O', 'П': 'N', 'Р': 'P', 'С': 'C', 'Т': 'T', 'У': 'y', 'Ф': 'O', 'Х': 'X', 'Ц': 'U', 'Ч': 'h', 'Ш': 'W', 'Щ': 'W', 'Ъ': 'B', 'Ы': 'X', 'Ь': 'B', 'Э': '3', 'Ю': 'X', 'Я': 'R', 'а': 'a', 'б': 'b', 'в': 'a', 'г': 'r', 'д': 'y', 'е': 'e', 'ж': 'm', 'з': 'e', 'и': 'n', 'й': 'n', 'к': 'n', 'л': 'n', 'м': 'm', 'н': 'n', 'о': 'o', 'п': 'n', 'р': 'p', 'с': 'c', 'т': 'o', 'у': 'y', 'ф': 'b', 'х': 'x', 'ц': 'n', 'ч': 'n', 'ш': 'w', 'щ': 'w', 'ъ': 'a', 'ы': 'm', 'ь': 'a', 'э': 'e', 'ю': 'm', 'я': 'r' }; /** * This function adds new font metrics to default metricMap * It can also override existing metrics */ function setFontMetrics(fontName, metrics) { fontMetricsData[fontName] = metrics; } /** * This function is a convenience function for looking up information in the * metricMap table. It takes a character as a string, and a font. * * Note: the `width` property may be undefined if fontMetricsData.js wasn't * built using `Make extended_metrics`. */ function getCharacterMetrics(character, font, mode) { if (!fontMetricsData[font]) { throw new Error("Font metrics not found for font: " + font + "."); } var ch = character.charCodeAt(0); var metrics = fontMetricsData[font][ch]; if (!metrics && character[0] in extraCharacterMap) { ch = extraCharacterMap[character[0]].charCodeAt(0); metrics = fontMetricsData[font][ch]; } if (!metrics && mode === 'text') { // We don't typically have font metrics for Asian scripts. // But since we support them in text mode, we need to return // some sort of metrics. // So if the character is in a script we support but we // don't have metrics for it, just use the metrics for // the Latin capital letter M. This is close enough because // we (currently) only care about the height of the glyph // not its width. if (supportedCodepoint(ch)) { metrics = fontMetricsData[font][77]; // 77 is the charcode for 'M' } } if (metrics) { return { depth: metrics[0], height: metrics[1], italic: metrics[2], skew: metrics[3], width: metrics[4] }; } } var fontMetricsBySizeIndex = {}; /** * Get the font metrics for a given size. */ function getGlobalMetrics(size) { var sizeIndex; if (size >= 5) { sizeIndex = 0; } else if (size >= 3) { sizeIndex = 1; } else { sizeIndex = 2; } if (!fontMetricsBySizeIndex[sizeIndex]) { var metrics = fontMetricsBySizeIndex[sizeIndex] = { cssEmPerMu: sigmasAndXis.quad[sizeIndex] / 18 }; for (var key in sigmasAndXis) { if (sigmasAndXis.hasOwnProperty(key)) { metrics[key] = sigmasAndXis[key][sizeIndex]; } } } return fontMetricsBySizeIndex[sizeIndex]; } /** * This file contains information about the options that the Parser carries * around with it while parsing. Data is held in an `Options` object, and when * recursing, a new `Options` object can be created with the `.with*` and * `.reset` functions. */ var sizeStyleMap = [// Each element contains [textsize, scriptsize, scriptscriptsize]. // The size mappings are taken from TeX with \normalsize=10pt. [1, 1, 1], // size1: [5, 5, 5] \tiny [2, 1, 1], // size2: [6, 5, 5] [3, 1, 1], // size3: [7, 5, 5] \scriptsize [4, 2, 1], // size4: [8, 6, 5] \footnotesize [5, 2, 1], // size5: [9, 6, 5] \small [6, 3, 1], // size6: [10, 7, 5] \normalsize [7, 4, 2], // size7: [12, 8, 6] \large [8, 6, 3], // size8: [14.4, 10, 7] \Large [9, 7, 6], // size9: [17.28, 12, 10] \LARGE [10, 8, 7], // size10: [20.74, 14.4, 12] \huge [11, 10, 9] // size11: [24.88, 20.74, 17.28] \HUGE ]; var sizeMultipliers = [// fontMetrics.js:getGlobalMetrics also uses size indexes, so if // you change size indexes, change that function. 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.2, 1.44, 1.728, 2.074, 2.488]; var sizeAtStyle = function sizeAtStyle(size, style) { return style.size < 2 ? size : sizeStyleMap[size - 1][style.size - 1]; }; // In these types, "" (empty string) means "no change". /** * This is the main options class. It contains the current style, size, color, * and font. * * Options objects should not be modified. To create a new Options with * different properties, call a `.having*` method. */ class Options { // A font family applies to a group of fonts (i.e. SansSerif), while a font // represents a specific font (i.e. SansSerif Bold). // See: https://tex.stackexchange.com/questions/22350/difference-between-textrm-and-mathrm /** * The base size index. */ constructor(data) { this.style = void 0; this.color = void 0; this.size = void 0; this.textSize = void 0; this.phantom = void 0; this.font = void 0; this.fontFamily = void 0; this.fontWeight = void 0; this.fontShape = void 0; this.sizeMultiplier = void 0; this.maxSize = void 0; this.minRuleThickness = void 0; this._fontMetrics = void 0; this.style = data.style; this.color = data.color; this.size = data.size || Options.BASESIZE; this.textSize = data.textSize || this.size; this.phantom = !!data.phantom; this.font = data.font || ""; this.fontFamily = data.fontFamily || ""; this.fontWeight = data.fontWeight || ''; this.fontShape = data.fontShape || ''; this.sizeMultiplier = sizeMultipliers[this.size - 1]; this.maxSize = data.maxSize; this.minRuleThickness = data.minRuleThickness; this._fontMetrics = undefined; } /** * Returns a new options object with the same properties as "this". Properties * from "extension" will be copied to the new options object. */ extend(extension) { var data = { style: this.style, size: this.size, textSize: this.textSize, color: this.color, phantom: this.phantom, font: this.font, fontFamily: this.fontFamily, fontWeight: this.fontWeight, fontShape: this.fontShape, maxSize: this.maxSize, minRuleThickness: this.minRuleThickness }; for (var key in extension) { if (extension.hasOwnProperty(key)) { data[key] = extension[key]; } } return new Options(data); } /** * Return an options object with the given style. If `this.style === style`, * returns `this`. */ havingStyle(style) { if (this.style === style) { return this; } else { return this.extend({ style: style, size: sizeAtStyle(this.textSize, style) }); } } /** * Return an options object with a cramped version of the current style. If * the current style is cramped, returns `this`. */ havingCrampedStyle() { return this.havingStyle(this.style.cramp()); } /** * Return an options object with the given size and in at least `\textstyle`. * Returns `this` if appropriate. */ havingSize(size) { if (this.size === size && this.textSize === size) { return this; } else { return this.extend({ style: this.style.text(), size: size, textSize: size, sizeMultiplier: sizeMultipliers[size - 1] }); } } /** * Like `this.havingSize(BASESIZE).havingStyle(style)`. If `style` is omitted, * changes to at least `\textstyle`. */ havingBaseStyle(style) { style = style || this.style.text(); var wantSize = sizeAtStyle(Options.BASESIZE, style); if (this.size === wantSize && this.textSize === Options.BASESIZE && this.style === style) { return this; } else { return this.extend({ style: style, size: wantSize }); } } /** * Remove the effect of sizing changes such as \Huge. * Keep the effect of the current style, such as \scriptstyle. */ havingBaseSizing() { var size; switch (this.style.id) { case 4: case 5: size = 3; // normalsize in scriptstyle break; case 6: case 7: size = 1; // normalsize in scriptscriptstyle break; default: size = 6; // normalsize in textstyle or displaystyle } return this.extend({ style: this.style.text(), size: size }); } /** * Create a new options object with the given color. */ withColor(color) { return this.extend({ color: color }); } /** * Create a new options object with "phantom" set to true. */ withPhantom() { return this.extend({ phantom: true }); } /** * Creates a new options object with the given math font or old text font. * @type {[type]} */ withFont(font) { return this.extend({ font }); } /** * Create a new options objects with the given fontFamily. */ withTextFontFamily(fontFamily) { return this.extend({ fontFamily, font: "" }); } /** * Creates a new options object with the given font weight */ withTextFontWeight(fontWeight) { return this.extend({ fontWeight, font: "" }); } /** * Creates a new options object with the given font weight */ withTextFontShape(fontShape) { return this.extend({ fontShape, font: "" }); } /** * Return the CSS sizing classes required to switch from enclosing options * `oldOptions` to `this`. Returns an array of classes. */ sizingClasses(oldOptions) { if (oldOptions.size !== this.size) { return ["sizing", "reset-size" + oldOptions.size, "size" + this.size]; } else { return []; } } /** * Return the CSS sizing classes required to switch to the base size. Like * `this.havingSize(BASESIZE).sizingClasses(this)`. */ baseSizingClasses() { if (this.size !== Options.BASESIZE) { return ["sizing", "reset-size" + this.size, "size" + Options.BASESIZE]; } else { return []; } } /** * Return the font metrics for this size. */ fontMetrics() { if (!this._fontMetrics) { this._fontMetrics = getGlobalMetrics(this.size); } return this._fontMetrics; } /** * Gets the CSS color of the current options object */ getColor() { if (this.phantom) { return "transparent"; } else { return this.color; } } } Options.BASESIZE = 6; /** * This file does conversion between units. In particular, it provides * calculateSize to convert other units into ems. */ // Thus, multiplying a length by this number converts the length from units // into pts. Dividing the result by ptPerEm gives the number of ems // *assuming* a font size of ptPerEm (normal size, normal style). var ptPerUnit = { // https://en.wikibooks.org/wiki/LaTeX/Lengths and // https://tex.stackexchange.com/a/8263 "pt": 1, // TeX point "mm": 7227 / 2540, // millimeter "cm": 7227 / 254, // centimeter "in": 72.27, // inch "bp": 803 / 800, // big (PostScript) points "pc": 12, // pica "dd": 1238 / 1157, // didot "cc": 14856 / 1157, // cicero (12 didot) "nd": 685 / 642, // new didot "nc": 1370 / 107, // new cicero (12 new didot) "sp": 1 / 65536, // scaled point (TeX's internal smallest unit) // https://tex.stackexchange.com/a/41371 "px": 803 / 800 // \pdfpxdimen defaults to 1 bp in pdfTeX and LuaTeX }; // Dictionary of relative units, for fast validity testing. var relativeUnit = { "ex": true, "em": true, "mu": true }; /** * Determine whether the specified unit (either a string defining the unit * or a "size" parse node containing a unit field) is valid. */ var validUnit = function validUnit(unit) { if (typeof unit !== "string") { unit = unit.unit; } return unit in ptPerUnit || unit in relativeUnit || unit === "ex"; }; /* * Convert a "size" parse node (with numeric "number" and string "unit" fields, * as parsed by functions.js argType "size") into a CSS em value for the * current style/scale. `options` gives the current options. */ var calculateSize = function calculateSize(sizeValue, options) { var scale; if (sizeValue.unit in ptPerUnit) { // Absolute units scale = ptPerUnit[sizeValue.unit] // Convert unit to pt / options.fontMetrics().ptPerEm // Convert pt to CSS em / options.sizeMultiplier; // Unscale to make absolute units } else if (sizeValue.unit === "mu") { // `mu` units scale with scriptstyle/scriptscriptstyle. scale = options.fontMetrics().cssEmPerMu; } else { // Other relative units always refer to the *textstyle* font // in the current size. var unitOptions; if (options.style.isTight()) { // isTight() means current style is script/scriptscript. unitOptions = options.havingStyle(options.style.text()); } else { unitOptions = options; } // TODO: In TeX these units are relative to the quad of the current // *text* font, e.g. cmr10. KaTeX instead uses values from the // comparably-sized *Computer Modern symbol* font. At 10pt, these // match. At 7pt and 5pt, they differ: cmr7=1.138894, cmsy7=1.170641; // cmr5=1.361133, cmsy5=1.472241. Consider $\scriptsize a\kern1emb$. // TeX \showlists shows a kern of 1.13889 * fontsize; // KaTeX shows a kern of 1.171 * fontsize. if (sizeValue.unit === "ex") { scale = unitOptions.fontMetrics().xHeight; } else if (sizeValue.unit === "em") { scale = unitOptions.fontMetrics().quad; } else { throw new ParseError("Invalid unit: '" + sizeValue.unit + "'"); } if (unitOptions !== options) { scale *= unitOptions.sizeMultiplier / options.sizeMultiplier; } } return Math.min(sizeValue.number * scale, options.maxSize); }; /** * Round `n` to 4 decimal places, or to the nearest 1/10,000th em. See * https://github.com/KaTeX/KaTeX/pull/2460. */ var makeEm = function makeEm(n) { return +n.toFixed(4) + "em"; }; /** * These objects store the data about the DOM nodes we create, as well as some * extra data. They can then be transformed into real DOM nodes with the * `toNode` function or HTML markup using `toMarkup`. They are useful for both * storing extra properties on the nodes, as well as providing a way to easily * work with the DOM. * * Similar functions for working with MathML nodes exist in mathMLTree.js. * * TODO: refactor `span` and `anchor` into common superclass when * target environments support class inheritance */ /** * Create an HTML className based on a list of classes. In addition to joining * with spaces, we also remove empty classes. */ var createClass = function createClass(classes) { return classes.filter(cls => cls).join(" "); }; var initNode = function initNode(classes, options, style) { this.classes = classes || []; this.attributes = {}; this.height = 0; this.depth = 0; this.maxFontSize = 0; this.style = style || {}; if (options) { if (options.style.isTight()) { this.classes.push("mtight"); } var color = options.getColor(); if (color) { this.style.color = color; } } }; /** * Convert into an HTML node */ var toNode = function toNode(tagName) { var node = document.createElement(tagName); // Apply the class node.className = createClass(this.classes); // Apply inline styles for (var style in this.style) { if (this.style.hasOwnProperty(style)) { // $FlowFixMe Flow doesn't seem to understand span.style's type. node.style[style] = this.style[style]; } } // Apply attributes for (var attr in this.attributes) { if (this.attributes.hasOwnProperty(attr)) { node.setAttribute(attr, this.attributes[attr]); } } // Append the children, also as HTML nodes for (var i = 0; i < this.children.length; i++) { node.appendChild(this.children[i].toNode()); } return node; }; /** * https://w3c.github.io/html-reference/syntax.html#syntax-attributes * * > Attribute Names must consist of one or more characters * other than the space characters, U+0000 NULL, * '"', "'", ">", "/", "=", the control characters, * and any characters that are not defined by Unicode. */ var invalidAttributeNameRegex = /[\s"'>/=\x00-\x1f]/; /** * Convert into an HTML markup string */ var toMarkup = function toMarkup(tagName) { var markup = "<" + tagName; // Add the class if (this.classes.length) { markup += " class=\"" + utils.escape(createClass(this.classes)) + "\""; } var styles = ""; // Add the styles, after hyphenation for (var style in this.style) { if (this.style.hasOwnProperty(style)) { styles += utils.hyphenate(style) + ":" + this.style[style] + ";"; } } if (styles) { markup += " style=\"" + utils.escape(styles) + "\""; } // Add the attributes for (var attr in this.attributes) { if (this.attributes.hasOwnProperty(attr)) { if (invalidAttributeNameRegex.test(attr)) { throw new ParseError("Invalid attribute name '" + attr + "'"); } markup += " " + attr + "=\"" + utils.escape(this.attributes[attr]) + "\""; } } markup += ">"; // Add the markup of the children, also as markup for (var i = 0; i < this.children.length; i++) { markup += this.children[i].toMarkup(); } markup += ""; return markup; }; // Making the type below exact with all optional fields doesn't work due to // - https://github.com/facebook/flow/issues/4582 // - https://github.com/facebook/flow/issues/5688 // However, since *all* fields are optional, $Shape<> works as suggested in 5688 // above. // This type does not include all CSS properties. Additional properties should // be added as needed. /** * This node represents a span node, with a className, a list of children, and * an inline style. It also contains information about its height, depth, and * maxFontSize. * * Represents two types with different uses: SvgSpan to wrap an SVG and DomSpan * otherwise. This typesafety is important when HTML builders access a span's * children. */ class Span { constructor(classes, children, options, style) { this.children = void 0; this.attributes = void 0; this.classes = void 0; this.height = void 0; this.depth = void 0; this.width = void 0; this.maxFontSize = void 0; this.style = void 0; initNode.call(this, classes, options, style); this.children = children || []; } /** * Sets an arbitrary attribute on the span. Warning: use this wisely. Not * all browsers support attributes the same, and having too many custom * attributes is probably bad. */ setAttribute(attribute, value) { this.attributes[attribute] = value; } hasClass(className) { return utils.contains(this.classes, className); } toNode() { return toNode.call(this, "span"); } toMarkup() { return toMarkup.call(this, "span"); } } /** * This node represents an anchor () element with a hyperlink. See `span` * for further details. */ class Anchor { constructor(href, classes, children, options) { this.children = void 0; this.attributes = void 0; this.classes = void 0; this.height = void 0; this.depth = void 0; this.maxFontSize = void 0; this.style = void 0; initNode.call(this, classes, options); this.children = children || []; this.setAttribute('href', href); } setAttribute(attribute, value) { this.attributes[attribute] = value; } hasClass(className) { return utils.contains(this.classes, className); } toNode() { return toNode.call(this, "a"); } toMarkup() { return toMarkup.call(this, "a"); } } /** * This node represents an image embed () element. */ class Img { constructor(src, alt, style) { this.src = void 0; this.alt = void 0; this.classes = void 0; this.height = void 0; this.depth = void 0; this.maxFontSize = void 0; this.style = void 0; this.alt = alt; this.src = src; this.classes = ["mord"]; this.style = style; } hasClass(className) { return utils.contains(this.classes, className); } toNode() { var node = document.createElement("img"); node.src = this.src; node.alt = this.alt; node.className = "mord"; // Apply inline styles for (var style in this.style) { if (this.style.hasOwnProperty(style)) { // $FlowFixMe node.style[style] = this.style[style]; } } return node; } toMarkup() { var markup = "\"" 0) { span = document.createElement("span"); span.style.marginRight = makeEm(this.italic); } if (this.classes.length > 0) { span = span || document.createElement("span"); span.className = createClass(this.classes); } for (var style in this.style) { if (this.style.hasOwnProperty(style)) { span = span || document.createElement("span"); // $FlowFixMe Flow doesn't seem to understand span.style's type. span.style[style] = this.style[style]; } } if (span) { span.appendChild(node); return span; } else { return node; } } /** * Creates markup for a symbol node. */ toMarkup() { // TODO(alpert): More duplication than I'd like from // span.prototype.toMarkup and symbolNode.prototype.toNode... var needsSpan = false; var markup = " 0) { styles += "margin-right:" + this.italic + "em;"; } for (var style in this.style) { if (this.style.hasOwnProperty(style)) { styles += utils.hyphenate(style) + ":" + this.style[style] + ";"; } } if (styles) { needsSpan = true; markup += " style=\"" + utils.escape(styles) + "\""; } var escaped = utils.escape(this.text); if (needsSpan) { markup += ">"; markup += escaped; markup += ""; return markup; } else { return escaped; } } } /** * SVG nodes are used to render stretchy wide elements. */ class SvgNode { constructor(children, attributes) { this.children = void 0; this.attributes = void 0; this.children = children || []; this.attributes = attributes || {}; } toNode() { var svgNS = "http://www.w3.org/2000/svg"; var node = document.createElementNS(svgNS, "svg"); // Apply attributes for (var attr in this.attributes) { if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) { node.setAttribute(attr, this.attributes[attr]); } } for (var i = 0; i < this.children.length; i++) { node.appendChild(this.children[i].toNode()); } return node; } toMarkup() { var markup = ""; } else { return ""; } } } class LineNode { constructor(attributes) { this.attributes = void 0; this.attributes = attributes || {}; } toNode() { var svgNS = "http://www.w3.org/2000/svg"; var node = document.createElementNS(svgNS, "line"); // Apply attributes for (var attr in this.attributes) { if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) { node.setAttribute(attr, this.attributes[attr]); } } return node; } toMarkup() { var markup = " but got " + String(group) + "."); } } /** * This file holds a list of all no-argument functions and single-character * symbols (like 'a' or ';'). * * For each of the symbols, there are three properties they can have: * - font (required): the font to be used for this symbol. Either "main" (the normal font), or "ams" (the ams fonts). * - group (required): the ParseNode group type the symbol should have (i.e. "textord", "mathord", etc). See https://github.com/KaTeX/KaTeX/wiki/Examining-TeX#group-types * - replace: the character that this symbol or function should be * replaced with (i.e. "\phi" has a replace value of "\u03d5", the phi * character in the main font). * * The outermost map in the table indicates what mode the symbols should be * accepted in (e.g. "math" or "text"). */ // Some of these have a "-token" suffix since these are also used as `ParseNode` // types for raw text tokens, and we want to avoid conflicts with higher-level // `ParseNode` types. These `ParseNode`s are constructed within `Parser` by // looking up the `symbols` map. var ATOMS = { "bin": 1, "close": 1, "inner": 1, "open": 1, "punct": 1, "rel": 1 }; var NON_ATOMS = { "accent-token": 1, "mathord": 1, "op-token": 1, "spacing": 1, "textord": 1 }; var symbols = { "math": {}, "text": {} }; /** `acceptUnicodeChar = true` is only applicable if `replace` is set. */ function defineSymbol(mode, font, group, replace, name, acceptUnicodeChar) { symbols[mode][name] = { font, group, replace }; if (acceptUnicodeChar && replace) { symbols[mode][replace] = symbols[mode][name]; } } // Some abbreviations for commonly used strings. // This helps minify the code, and also spotting typos using jshint. // modes: var math = "math"; var text = "text"; // fonts: var main = "main"; var ams = "ams"; // groups: var accent = "accent-token"; var bin = "bin"; var close = "close"; var inner = "inner"; var mathord = "mathord"; var op = "op-token"; var open = "open"; var punct = "punct"; var rel = "rel"; var spacing = "spacing"; var textord = "textord"; // Now comes the symbol table // Relation Symbols defineSymbol(math, main, rel, "\u2261", "\\equiv", true); defineSymbol(math, main, rel, "\u227a", "\\prec", true); defineSymbol(math, main, rel, "\u227b", "\\succ", true); defineSymbol(math, main, rel, "\u223c", "\\sim", true); defineSymbol(math, main, rel, "\u22a5", "\\perp"); defineSymbol(math, main, rel, "\u2aaf", "\\preceq", true); defineSymbol(math, main, rel, "\u2ab0", "\\succeq", true); defineSymbol(math, main, rel, "\u2243", "\\simeq", true); defineSymbol(math, main, rel, "\u2223", "\\mid", true); defineSymbol(math, main, rel, "\u226a", "\\ll", true); defineSymbol(math, main, rel, "\u226b", "\\gg", true); defineSymbol(math, main, rel, "\u224d", "\\asymp", true); defineSymbol(math, main, rel, "\u2225", "\\parallel"); defineSymbol(math, main, rel, "\u22c8", "\\bowtie", true); defineSymbol(math, main, rel, "\u2323", "\\smile", true); defineSymbol(math, main, rel, "\u2291", "\\sqsubseteq", true); defineSymbol(math, main, rel, "\u2292", "\\sqsupseteq", true); defineSymbol(math, main, rel, "\u2250", "\\doteq", true); defineSymbol(math, main, rel, "\u2322", "\\frown", true); defineSymbol(math, main, rel, "\u220b", "\\ni", true); defineSymbol(math, main, rel, "\u221d", "\\propto", true); defineSymbol(math, main, rel, "\u22a2", "\\vdash", true); defineSymbol(math, main, rel, "\u22a3", "\\dashv", true); defineSymbol(math, main, rel, "\u220b", "\\owns"); // Punctuation defineSymbol(math, main, punct, "\u002e", "\\ldotp"); defineSymbol(math, main, punct, "\u22c5", "\\cdotp"); // Misc Symbols defineSymbol(math, main, textord, "\u0023", "\\#"); defineSymbol(text, main, textord, "\u0023", "\\#"); defineSymbol(math, main, textord, "\u0026", "\\&"); defineSymbol(text, main, textord, "\u0026", "\\&"); defineSymbol(math, main, textord, "\u2135", "\\aleph", true); defineSymbol(math, main, textord, "\u2200", "\\forall", true); defineSymbol(math, main, textord, "\u210f", "\\hbar", true); defineSymbol(math, main, textord, "\u2203", "\\exists", true); defineSymbol(math, main, textord, "\u2207", "\\nabla", true); defineSymbol(math, main, textord, "\u266d", "\\flat", true); defineSymbol(math, main, textord, "\u2113", "\\ell", true); defineSymbol(math, main, textord, "\u266e", "\\natural", true); defineSymbol(math, main, textord, "\u2663", "\\clubsuit", true); defineSymbol(math, main, textord, "\u2118", "\\wp", true); defineSymbol(math, main, textord, "\u266f", "\\sharp", true); defineSymbol(math, main, textord, "\u2662", "\\diamondsuit", true); defineSymbol(math, main, textord, "\u211c", "\\Re", true); defineSymbol(math, main, textord, "\u2661", "\\heartsuit", true); defineSymbol(math, main, textord, "\u2111", "\\Im", true); defineSymbol(math, main, textord, "\u2660", "\\spadesuit", true); defineSymbol(math, main, textord, "\u00a7", "\\S", true); defineSymbol(text, main, textord, "\u00a7", "\\S"); defineSymbol(math, main, textord, "\u00b6", "\\P", true); defineSymbol(text, main, textord, "\u00b6", "\\P"); // Math and Text defineSymbol(math, main, textord, "\u2020", "\\dag"); defineSymbol(text, main, textord, "\u2020", "\\dag"); defineSymbol(text, main, textord, "\u2020", "\\textdagger"); defineSymbol(math, main, textord, "\u2021", "\\ddag"); defineSymbol(text, main, textord, "\u2021", "\\ddag"); defineSymbol(text, main, textord, "\u2021", "\\textdaggerdbl"); // Large Delimiters defineSymbol(math, main, close, "\u23b1", "\\rmoustache", true); defineSymbol(math, main, open, "\u23b0", "\\lmoustache", true); defineSymbol(math, main, close, "\u27ef", "\\rgroup", true); defineSymbol(math, main, open, "\u27ee", "\\lgroup", true); // Binary Operators defineSymbol(math, main, bin, "\u2213", "\\mp", true); defineSymbol(math, main, bin, "\u2296", "\\ominus", true); defineSymbol(math, main, bin, "\u228e", "\\uplus", true); defineSymbol(math, main, bin, "\u2293", "\\sqcap", true); defineSymbol(math, main, bin, "\u2217", "\\ast"); defineSymbol(math, main, bin, "\u2294", "\\sqcup", true); defineSymbol(math, main, bin, "\u25ef", "\\bigcirc", true); defineSymbol(math, main, bin, "\u2219", "\\bullet", true); defineSymbol(math, main, bin, "\u2021", "\\ddagger"); defineSymbol(math, main, bin, "\u2240", "\\wr", true); defineSymbol(math, main, bin, "\u2a3f", "\\amalg"); defineSymbol(math, main, bin, "\u0026", "\\And"); // from amsmath // Arrow Symbols defineSymbol(math, main, rel, "\u27f5", "\\longleftarrow", true); defineSymbol(math, main, rel, "\u21d0", "\\Leftarrow", true); defineSymbol(math, main, rel, "\u27f8", "\\Longleftarrow", true); defineSymbol(math, main, rel, "\u27f6", "\\longrightarrow", true); defineSymbol(math, main, rel, "\u21d2", "\\Rightarrow", true); defineSymbol(math, main, rel, "\u27f9", "\\Longrightarrow", true); defineSymbol(math, main, rel, "\u2194", "\\leftrightarrow", true); defineSymbol(math, main, rel, "\u27f7", "\\longleftrightarrow", true); defineSymbol(math, main, rel, "\u21d4", "\\Leftrightarrow", true); defineSymbol(math, main, rel, "\u27fa", "\\Longleftrightarrow", true); defineSymbol(math, main, rel, "\u21a6", "\\mapsto", true); defineSymbol(math, main, rel, "\u27fc", "\\longmapsto", true); defineSymbol(math, main, rel, "\u2197", "\\nearrow", true); defineSymbol(math, main, rel, "\u21a9", "\\hookleftarrow", true); defineSymbol(math, main, rel, "\u21aa", "\\hookrightarrow", true); defineSymbol(math, main, rel, "\u2198", "\\searrow", true); defineSymbol(math, main, rel, "\u21bc", "\\leftharpoonup", true); defineSymbol(math, main, rel, "\u21c0", "\\rightharpoonup", true); defineSymbol(math, main, rel, "\u2199", "\\swarrow", true); defineSymbol(math, main, rel, "\u21bd", "\\leftharpoondown", true); defineSymbol(math, main, rel, "\u21c1", "\\rightharpoondown", true); defineSymbol(math, main, rel, "\u2196", "\\nwarrow", true); defineSymbol(math, main, rel, "\u21cc", "\\rightleftharpoons", true); // AMS Negated Binary Relations defineSymbol(math, ams, rel, "\u226e", "\\nless", true); // Symbol names preceded by "@" each have a corresponding macro. defineSymbol(math, ams, rel, "\ue010", "\\@nleqslant"); defineSymbol(math, ams, rel, "\ue011", "\\@nleqq"); defineSymbol(math, ams, rel, "\u2a87", "\\lneq", true); defineSymbol(math, ams, rel, "\u2268", "\\lneqq", true); defineSymbol(math, ams, rel, "\ue00c", "\\@lvertneqq"); defineSymbol(math, ams, rel, "\u22e6", "\\lnsim", true); defineSymbol(math, ams, rel, "\u2a89", "\\lnapprox", true); defineSymbol(math, ams, rel, "\u2280", "\\nprec", true); // unicode-math maps \u22e0 to \npreccurlyeq. We'll use the AMS synonym. defineSymbol(math, ams, rel, "\u22e0", "\\npreceq", true); defineSymbol(math, ams, rel, "\u22e8", "\\precnsim", true); defineSymbol(math, ams, rel, "\u2ab9", "\\precnapprox", true); defineSymbol(math, ams, rel, "\u2241", "\\nsim", true); defineSymbol(math, ams, rel, "\ue006", "\\@nshortmid"); defineSymbol(math, ams, rel, "\u2224", "\\nmid", true); defineSymbol(math, ams, rel, "\u22ac", "\\nvdash", true); defineSymbol(math, ams, rel, "\u22ad", "\\nvDash", true); defineSymbol(math, ams, rel, "\u22ea", "\\ntriangleleft"); defineSymbol(math, ams, rel, "\u22ec", "\\ntrianglelefteq", true); defineSymbol(math, ams, rel, "\u228a", "\\subsetneq", true); defineSymbol(math, ams, rel, "\ue01a", "\\@varsubsetneq"); defineSymbol(math, ams, rel, "\u2acb", "\\subsetneqq", true); defineSymbol(math, ams, rel, "\ue017", "\\@varsubsetneqq"); defineSymbol(math, ams, rel, "\u226f", "\\ngtr", true); defineSymbol(math, ams, rel, "\ue00f", "\\@ngeqslant"); defineSymbol(math, ams, rel, "\ue00e", "\\@ngeqq"); defineSymbol(math, ams, rel, "\u2a88", "\\gneq", true); defineSymbol(math, ams, rel, "\u2269", "\\gneqq", true); defineSymbol(math, ams, rel, "\ue00d", "\\@gvertneqq"); defineSymbol(math, ams, rel, "\u22e7", "\\gnsim", true); defineSymbol(math, ams, rel, "\u2a8a", "\\gnapprox", true); defineSymbol(math, ams, rel, "\u2281", "\\nsucc", true); // unicode-math maps \u22e1 to \nsucccurlyeq. We'll use the AMS synonym. defineSymbol(math, ams, rel, "\u22e1", "\\nsucceq", true); defineSymbol(math, ams, rel, "\u22e9", "\\succnsim", true); defineSymbol(math, ams, rel, "\u2aba", "\\succnapprox", true); // unicode-math maps \u2246 to \simneqq. We'll use the AMS synonym. defineSymbol(math, ams, rel, "\u2246", "\\ncong", true); defineSymbol(math, ams, rel, "\ue007", "\\@nshortparallel"); defineSymbol(math, ams, rel, "\u2226", "\\nparallel", true); defineSymbol(math, ams, rel, "\u22af", "\\nVDash", true); defineSymbol(math, ams, rel, "\u22eb", "\\ntriangleright"); defineSymbol(math, ams, rel, "\u22ed", "\\ntrianglerighteq", true); defineSymbol(math, ams, rel, "\ue018", "\\@nsupseteqq"); defineSymbol(math, ams, rel, "\u228b", "\\supsetneq", true); defineSymbol(math, ams, rel, "\ue01b", "\\@varsupsetneq"); defineSymbol(math, ams, rel, "\u2acc", "\\supsetneqq", true); defineSymbol(math, ams, rel, "\ue019", "\\@varsupsetneqq"); defineSymbol(math, ams, rel, "\u22ae", "\\nVdash", true); defineSymbol(math, ams, rel, "\u2ab5", "\\precneqq", true); defineSymbol(math, ams, rel, "\u2ab6", "\\succneqq", true); defineSymbol(math, ams, rel, "\ue016", "\\@nsubseteqq"); defineSymbol(math, ams, bin, "\u22b4", "\\unlhd"); defineSymbol(math, ams, bin, "\u22b5", "\\unrhd"); // AMS Negated Arrows defineSymbol(math, ams, rel, "\u219a", "\\nleftarrow", true); defineSymbol(math, ams, rel, "\u219b", "\\nrightarrow", true); defineSymbol(math, ams, rel, "\u21cd", "\\nLeftarrow", true); defineSymbol(math, ams, rel, "\u21cf", "\\nRightarrow", true); defineSymbol(math, ams, rel, "\u21ae", "\\nleftrightarrow", true); defineSymbol(math, ams, rel, "\u21ce", "\\nLeftrightarrow", true); // AMS Misc defineSymbol(math, ams, rel, "\u25b3", "\\vartriangle"); defineSymbol(math, ams, textord, "\u210f", "\\hslash"); defineSymbol(math, ams, textord, "\u25bd", "\\triangledown"); defineSymbol(math, ams, textord, "\u25ca", "\\lozenge"); defineSymbol(math, ams, textord, "\u24c8", "\\circledS"); defineSymbol(math, ams, textord, "\u00ae", "\\circledR"); defineSymbol(text, ams, textord, "\u00ae", "\\circledR"); defineSymbol(math, ams, textord, "\u2221", "\\measuredangle", true); defineSymbol(math, ams, textord, "\u2204", "\\nexists"); defineSymbol(math, ams, textord, "\u2127", "\\mho"); defineSymbol(math, ams, textord, "\u2132", "\\Finv", true); defineSymbol(math, ams, textord, "\u2141", "\\Game", true); defineSymbol(math, ams, textord, "\u2035", "\\backprime"); defineSymbol(math, ams, textord, "\u25b2", "\\blacktriangle"); defineSymbol(math, ams, textord, "\u25bc", "\\blacktriangledown"); defineSymbol(math, ams, textord, "\u25a0", "\\blacksquare"); defineSymbol(math, ams, textord, "\u29eb", "\\blacklozenge"); defineSymbol(math, ams, textord, "\u2605", "\\bigstar"); defineSymbol(math, ams, textord, "\u2222", "\\sphericalangle", true); defineSymbol(math, ams, textord, "\u2201", "\\complement", true); // unicode-math maps U+F0 to \matheth. We map to AMS function \eth defineSymbol(math, ams, textord, "\u00f0", "\\eth", true); defineSymbol(text, main, textord, "\u00f0", "\u00f0"); defineSymbol(math, ams, textord, "\u2571", "\\diagup"); defineSymbol(math, ams, textord, "\u2572", "\\diagdown"); defineSymbol(math, ams, textord, "\u25a1", "\\square"); defineSymbol(math, ams, textord, "\u25a1", "\\Box"); defineSymbol(math, ams, textord, "\u25ca", "\\Diamond"); // unicode-math maps U+A5 to \mathyen. We map to AMS function \yen defineSymbol(math, ams, textord, "\u00a5", "\\yen", true); defineSymbol(text, ams, textord, "\u00a5", "\\yen", true); defineSymbol(math, ams, textord, "\u2713", "\\checkmark", true); defineSymbol(text, ams, textord, "\u2713", "\\checkmark"); // AMS Hebrew defineSymbol(math, ams, textord, "\u2136", "\\beth", true); defineSymbol(math, ams, textord, "\u2138", "\\daleth", true); defineSymbol(math, ams, textord, "\u2137", "\\gimel", true); // AMS Greek defineSymbol(math, ams, textord, "\u03dd", "\\digamma", true); defineSymbol(math, ams, textord, "\u03f0", "\\varkappa"); // AMS Delimiters defineSymbol(math, ams, open, "\u250c", "\\@ulcorner", true); defineSymbol(math, ams, close, "\u2510", "\\@urcorner", true); defineSymbol(math, ams, open, "\u2514", "\\@llcorner", true); defineSymbol(math, ams, close, "\u2518", "\\@lrcorner", true); // AMS Binary Relations defineSymbol(math, ams, rel, "\u2266", "\\leqq", true); defineSymbol(math, ams, rel, "\u2a7d", "\\leqslant", true); defineSymbol(math, ams, rel, "\u2a95", "\\eqslantless", true); defineSymbol(math, ams, rel, "\u2272", "\\lesssim", true); defineSymbol(math, ams, rel, "\u2a85", "\\lessapprox", true); defineSymbol(math, ams, rel, "\u224a", "\\approxeq", true); defineSymbol(math, ams, bin, "\u22d6", "\\lessdot"); defineSymbol(math, ams, rel, "\u22d8", "\\lll", true); defineSymbol(math, ams, rel, "\u2276", "\\lessgtr", true); defineSymbol(math, ams, rel, "\u22da", "\\lesseqgtr", true); defineSymbol(math, ams, rel, "\u2a8b", "\\lesseqqgtr", true); defineSymbol(math, ams, rel, "\u2251", "\\doteqdot"); defineSymbol(math, ams, rel, "\u2253", "\\risingdotseq", true); defineSymbol(math, ams, rel, "\u2252", "\\fallingdotseq", true); defineSymbol(math, ams, rel, "\u223d", "\\backsim", true); defineSymbol(math, ams, rel, "\u22cd", "\\backsimeq", true); defineSymbol(math, ams, rel, "\u2ac5", "\\subseteqq", true); defineSymbol(math, ams, rel, "\u22d0", "\\Subset", true); defineSymbol(math, ams, rel, "\u228f", "\\sqsubset", true); defineSymbol(math, ams, rel, "\u227c", "\\preccurlyeq", true); defineSymbol(math, ams, rel, "\u22de", "\\curlyeqprec", true); defineSymbol(math, ams, rel, "\u227e", "\\precsim", true); defineSymbol(math, ams, rel, "\u2ab7", "\\precapprox", true); defineSymbol(math, ams, rel, "\u22b2", "\\vartriangleleft"); defineSymbol(math, ams, rel, "\u22b4", "\\trianglelefteq"); defineSymbol(math, ams, rel, "\u22a8", "\\vDash", true); defineSymbol(math, ams, rel, "\u22aa", "\\Vvdash", true); defineSymbol(math, ams, rel, "\u2323", "\\smallsmile"); defineSymbol(math, ams, rel, "\u2322", "\\smallfrown"); defineSymbol(math, ams, rel, "\u224f", "\\bumpeq", true); defineSymbol(math, ams, rel, "\u224e", "\\Bumpeq", true); defineSymbol(math, ams, rel, "\u2267", "\\geqq", true); defineSymbol(math, ams, rel, "\u2a7e", "\\geqslant", true); defineSymbol(math, ams, rel, "\u2a96", "\\eqslantgtr", true); defineSymbol(math, ams, rel, "\u2273", "\\gtrsim", true); defineSymbol(math, ams, rel, "\u2a86", "\\gtrapprox", true); defineSymbol(math, ams, bin, "\u22d7", "\\gtrdot"); defineSymbol(math, ams, rel, "\u22d9", "\\ggg", true); defineSymbol(math, ams, rel, "\u2277", "\\gtrless", true); defineSymbol(math, ams, rel, "\u22db", "\\gtreqless", true); defineSymbol(math, ams, rel, "\u2a8c", "\\gtreqqless", true); defineSymbol(math, ams, rel, "\u2256", "\\eqcirc", true); defineSymbol(math, ams, rel, "\u2257", "\\circeq", true); defineSymbol(math, ams, rel, "\u225c", "\\triangleq", true); defineSymbol(math, ams, rel, "\u223c", "\\thicksim"); defineSymbol(math, ams, rel, "\u2248", "\\thickapprox"); defineSymbol(math, ams, rel, "\u2ac6", "\\supseteqq", true); defineSymbol(math, ams, rel, "\u22d1", "\\Supset", true); defineSymbol(math, ams, rel, "\u2290", "\\sqsupset", true); defineSymbol(math, ams, rel, "\u227d", "\\succcurlyeq", true); defineSymbol(math, ams, rel, "\u22df", "\\curlyeqsucc", true); defineSymbol(math, ams, rel, "\u227f", "\\succsim", true); defineSymbol(math, ams, rel, "\u2ab8", "\\succapprox", true); defineSymbol(math, ams, rel, "\u22b3", "\\vartriangleright"); defineSymbol(math, ams, rel, "\u22b5", "\\trianglerighteq"); defineSymbol(math, ams, rel, "\u22a9", "\\Vdash", true); defineSymbol(math, ams, rel, "\u2223", "\\shortmid"); defineSymbol(math, ams, rel, "\u2225", "\\shortparallel"); defineSymbol(math, ams, rel, "\u226c", "\\between", true); defineSymbol(math, ams, rel, "\u22d4", "\\pitchfork", true); defineSymbol(math, ams, rel, "\u221d", "\\varpropto"); defineSymbol(math, ams, rel, "\u25c0", "\\blacktriangleleft"); // unicode-math says that \therefore is a mathord atom. // We kept the amssymb atom type, which is rel. defineSymbol(math, ams, rel, "\u2234", "\\therefore", true); defineSymbol(math, ams, rel, "\u220d", "\\backepsilon"); defineSymbol(math, ams, rel, "\u25b6", "\\blacktriangleright"); // unicode-math says that \because is a mathord atom. // We kept the amssymb atom type, which is rel. defineSymbol(math, ams, rel, "\u2235", "\\because", true); defineSymbol(math, ams, rel, "\u22d8", "\\llless"); defineSymbol(math, ams, rel, "\u22d9", "\\gggtr"); defineSymbol(math, ams, bin, "\u22b2", "\\lhd"); defineSymbol(math, ams, bin, "\u22b3", "\\rhd"); defineSymbol(math, ams, rel, "\u2242", "\\eqsim", true); defineSymbol(math, main, rel, "\u22c8", "\\Join"); defineSymbol(math, ams, rel, "\u2251", "\\Doteq", true); // AMS Binary Operators defineSymbol(math, ams, bin, "\u2214", "\\dotplus", true); defineSymbol(math, ams, bin, "\u2216", "\\smallsetminus"); defineSymbol(math, ams, bin, "\u22d2", "\\Cap", true); defineSymbol(math, ams, bin, "\u22d3", "\\Cup", true); defineSymbol(math, ams, bin, "\u2a5e", "\\doublebarwedge", true); defineSymbol(math, ams, bin, "\u229f", "\\boxminus", true); defineSymbol(math, ams, bin, "\u229e", "\\boxplus", true); defineSymbol(math, ams, bin, "\u22c7", "\\divideontimes", true); defineSymbol(math, ams, bin, "\u22c9", "\\ltimes", true); defineSymbol(math, ams, bin, "\u22ca", "\\rtimes", true); defineSymbol(math, ams, bin, "\u22cb", "\\leftthreetimes", true); defineSymbol(math, ams, bin, "\u22cc", "\\rightthreetimes", true); defineSymbol(math, ams, bin, "\u22cf", "\\curlywedge", true); defineSymbol(math, ams, bin, "\u22ce", "\\curlyvee", true); defineSymbol(math, ams, bin, "\u229d", "\\circleddash", true); defineSymbol(math, ams, bin, "\u229b", "\\circledast", true); defineSymbol(math, ams, bin, "\u22c5", "\\centerdot"); defineSymbol(math, ams, bin, "\u22ba", "\\intercal", true); defineSymbol(math, ams, bin, "\u22d2", "\\doublecap"); defineSymbol(math, ams, bin, "\u22d3", "\\doublecup"); defineSymbol(math, ams, bin, "\u22a0", "\\boxtimes", true); // AMS Arrows // Note: unicode-math maps \u21e2 to their own function \rightdasharrow. // We'll map it to AMS function \dashrightarrow. It produces the same atom. defineSymbol(math, ams, rel, "\u21e2", "\\dashrightarrow", true); // unicode-math maps \u21e0 to \leftdasharrow. We'll use the AMS synonym. defineSymbol(math, ams, rel, "\u21e0", "\\dashleftarrow", true); defineSymbol(math, ams, rel, "\u21c7", "\\leftleftarrows", true); defineSymbol(math, ams, rel, "\u21c6", "\\leftrightarrows", true); defineSymbol(math, ams, rel, "\u21da", "\\Lleftarrow", true); defineSymbol(math, ams, rel, "\u219e", "\\twoheadleftarrow", true); defineSymbol(math, ams, rel, "\u21a2", "\\leftarrowtail", true); defineSymbol(math, ams, rel, "\u21ab", "\\looparrowleft", true); defineSymbol(math, ams, rel, "\u21cb", "\\leftrightharpoons", true); defineSymbol(math, ams, rel, "\u21b6", "\\curvearrowleft", true); // unicode-math maps \u21ba to \acwopencirclearrow. We'll use the AMS synonym. defineSymbol(math, ams, rel, "\u21ba", "\\circlearrowleft", true); defineSymbol(math, ams, rel, "\u21b0", "\\Lsh", true); defineSymbol(math, ams, rel, "\u21c8", "\\upuparrows", true); defineSymbol(math, ams, rel, "\u21bf", "\\upharpoonleft", true); defineSymbol(math, ams, rel, "\u21c3", "\\downharpoonleft", true); defineSymbol(math, main, rel, "\u22b6", "\\origof", true); // not in font defineSymbol(math, main, rel, "\u22b7", "\\imageof", true); // not in font defineSymbol(math, ams, rel, "\u22b8", "\\multimap", true); defineSymbol(math, ams, rel, "\u21ad", "\\leftrightsquigarrow", true); defineSymbol(math, ams, rel, "\u21c9", "\\rightrightarrows", true); defineSymbol(math, ams, rel, "\u21c4", "\\rightleftarrows", true); defineSymbol(math, ams, rel, "\u21a0", "\\twoheadrightarrow", true); defineSymbol(math, ams, rel, "\u21a3", "\\rightarrowtail", true); defineSymbol(math, ams, rel, "\u21ac", "\\looparrowright", true); defineSymbol(math, ams, rel, "\u21b7", "\\curvearrowright", true); // unicode-math maps \u21bb to \cwopencirclearrow. We'll use the AMS synonym. defineSymbol(math, ams, rel, "\u21bb", "\\circlearrowright", true); defineSymbol(math, ams, rel, "\u21b1", "\\Rsh", true); defineSymbol(math, ams, rel, "\u21ca", "\\downdownarrows", true); defineSymbol(math, ams, rel, "\u21be", "\\upharpoonright", true); defineSymbol(math, ams, rel, "\u21c2", "\\downharpoonright", true); defineSymbol(math, ams, rel, "\u21dd", "\\rightsquigarrow", true); defineSymbol(math, ams, rel, "\u21dd", "\\leadsto"); defineSymbol(math, ams, rel, "\u21db", "\\Rrightarrow", true); defineSymbol(math, ams, rel, "\u21be", "\\restriction"); defineSymbol(math, main, textord, "\u2018", "`"); defineSymbol(math, main, textord, "$", "\\$"); defineSymbol(text, main, textord, "$", "\\$"); defineSymbol(text, main, textord, "$", "\\textdollar"); defineSymbol(math, main, textord, "%", "\\%"); defineSymbol(text, main, textord, "%", "\\%"); defineSymbol(math, main, textord, "_", "\\_"); defineSymbol(text, main, textord, "_", "\\_"); defineSymbol(text, main, textord, "_", "\\textunderscore"); defineSymbol(math, main, textord, "\u2220", "\\angle", true); defineSymbol(math, main, textord, "\u221e", "\\infty", true); defineSymbol(math, main, textord, "\u2032", "\\prime"); defineSymbol(math, main, textord, "\u25b3", "\\triangle"); defineSymbol(math, main, textord, "\u0393", "\\Gamma", true); defineSymbol(math, main, textord, "\u0394", "\\Delta", true); defineSymbol(math, main, textord, "\u0398", "\\Theta", true); defineSymbol(math, main, textord, "\u039b", "\\Lambda", true); defineSymbol(math, main, textord, "\u039e", "\\Xi", true); defineSymbol(math, main, textord, "\u03a0", "\\Pi", true); defineSymbol(math, main, textord, "\u03a3", "\\Sigma", true); defineSymbol(math, main, textord, "\u03a5", "\\Upsilon", true); defineSymbol(math, main, textord, "\u03a6", "\\Phi", true); defineSymbol(math, main, textord, "\u03a8", "\\Psi", true); defineSymbol(math, main, textord, "\u03a9", "\\Omega", true); defineSymbol(math, main, textord, "A", "\u0391"); defineSymbol(math, main, textord, "B", "\u0392"); defineSymbol(math, main, textord, "E", "\u0395"); defineSymbol(math, main, textord, "Z", "\u0396"); defineSymbol(math, main, textord, "H", "\u0397"); defineSymbol(math, main, textord, "I", "\u0399"); defineSymbol(math, main, textord, "K", "\u039A"); defineSymbol(math, main, textord, "M", "\u039C"); defineSymbol(math, main, textord, "N", "\u039D"); defineSymbol(math, main, textord, "O", "\u039F"); defineSymbol(math, main, textord, "P", "\u03A1"); defineSymbol(math, main, textord, "T", "\u03A4"); defineSymbol(math, main, textord, "X", "\u03A7"); defineSymbol(math, main, textord, "\u00ac", "\\neg", true); defineSymbol(math, main, textord, "\u00ac", "\\lnot"); defineSymbol(math, main, textord, "\u22a4", "\\top"); defineSymbol(math, main, textord, "\u22a5", "\\bot"); defineSymbol(math, main, textord, "\u2205", "\\emptyset"); defineSymbol(math, ams, textord, "\u2205", "\\varnothing"); defineSymbol(math, main, mathord, "\u03b1", "\\alpha", true); defineSymbol(math, main, mathord, "\u03b2", "\\beta", true); defineSymbol(math, main, mathord, "\u03b3", "\\gamma", true); defineSymbol(math, main, mathord, "\u03b4", "\\delta", true); defineSymbol(math, main, mathord, "\u03f5", "\\epsilon", true); defineSymbol(math, main, mathord, "\u03b6", "\\zeta", true); defineSymbol(math, main, mathord, "\u03b7", "\\eta", true); defineSymbol(math, main, mathord, "\u03b8", "\\theta", true); defineSymbol(math, main, mathord, "\u03b9", "\\iota", true); defineSymbol(math, main, mathord, "\u03ba", "\\kappa", true); defineSymbol(math, main, mathord, "\u03bb", "\\lambda", true); defineSymbol(math, main, mathord, "\u03bc", "\\mu", true); defineSymbol(math, main, mathord, "\u03bd", "\\nu", true); defineSymbol(math, main, mathord, "\u03be", "\\xi", true); defineSymbol(math, main, mathord, "\u03bf", "\\omicron", true); defineSymbol(math, main, mathord, "\u03c0", "\\pi", true); defineSymbol(math, main, mathord, "\u03c1", "\\rho", true); defineSymbol(math, main, mathord, "\u03c3", "\\sigma", true); defineSymbol(math, main, mathord, "\u03c4", "\\tau", true); defineSymbol(math, main, mathord, "\u03c5", "\\upsilon", true); defineSymbol(math, main, mathord, "\u03d5", "\\phi", true); defineSymbol(math, main, mathord, "\u03c7", "\\chi", true); defineSymbol(math, main, mathord, "\u03c8", "\\psi", true); defineSymbol(math, main, mathord, "\u03c9", "\\omega", true); defineSymbol(math, main, mathord, "\u03b5", "\\varepsilon", true); defineSymbol(math, main, mathord, "\u03d1", "\\vartheta", true); defineSymbol(math, main, mathord, "\u03d6", "\\varpi", true); defineSymbol(math, main, mathord, "\u03f1", "\\varrho", true); defineSymbol(math, main, mathord, "\u03c2", "\\varsigma", true); defineSymbol(math, main, mathord, "\u03c6", "\\varphi", true); defineSymbol(math, main, bin, "\u2217", "*", true); defineSymbol(math, main, bin, "+", "+"); defineSymbol(math, main, bin, "\u2212", "-", true); defineSymbol(math, main, bin, "\u22c5", "\\cdot", true); defineSymbol(math, main, bin, "\u2218", "\\circ", true); defineSymbol(math, main, bin, "\u00f7", "\\div", true); defineSymbol(math, main, bin, "\u00b1", "\\pm", true); defineSymbol(math, main, bin, "\u00d7", "\\times", true); defineSymbol(math, main, bin, "\u2229", "\\cap", true); defineSymbol(math, main, bin, "\u222a", "\\cup", true); defineSymbol(math, main, bin, "\u2216", "\\setminus", true); defineSymbol(math, main, bin, "\u2227", "\\land"); defineSymbol(math, main, bin, "\u2228", "\\lor"); defineSymbol(math, main, bin, "\u2227", "\\wedge", true); defineSymbol(math, main, bin, "\u2228", "\\vee", true); defineSymbol(math, main, textord, "\u221a", "\\surd"); defineSymbol(math, main, open, "\u27e8", "\\langle", true); defineSymbol(math, main, open, "\u2223", "\\lvert"); defineSymbol(math, main, open, "\u2225", "\\lVert"); defineSymbol(math, main, close, "?", "?"); defineSymbol(math, main, close, "!", "!"); defineSymbol(math, main, close, "\u27e9", "\\rangle", true); defineSymbol(math, main, close, "\u2223", "\\rvert"); defineSymbol(math, main, close, "\u2225", "\\rVert"); defineSymbol(math, main, rel, "=", "="); defineSymbol(math, main, rel, ":", ":"); defineSymbol(math, main, rel, "\u2248", "\\approx", true); defineSymbol(math, main, rel, "\u2245", "\\cong", true); defineSymbol(math, main, rel, "\u2265", "\\ge"); defineSymbol(math, main, rel, "\u2265", "\\geq", true); defineSymbol(math, main, rel, "\u2190", "\\gets"); defineSymbol(math, main, rel, ">", "\\gt", true); defineSymbol(math, main, rel, "\u2208", "\\in", true); defineSymbol(math, main, rel, "\ue020", "\\@not"); defineSymbol(math, main, rel, "\u2282", "\\subset", true); defineSymbol(math, main, rel, "\u2283", "\\supset", true); defineSymbol(math, main, rel, "\u2286", "\\subseteq", true); defineSymbol(math, main, rel, "\u2287", "\\supseteq", true); defineSymbol(math, ams, rel, "\u2288", "\\nsubseteq", true); defineSymbol(math, ams, rel, "\u2289", "\\nsupseteq", true); defineSymbol(math, main, rel, "\u22a8", "\\models"); defineSymbol(math, main, rel, "\u2190", "\\leftarrow", true); defineSymbol(math, main, rel, "\u2264", "\\le"); defineSymbol(math, main, rel, "\u2264", "\\leq", true); defineSymbol(math, main, rel, "<", "\\lt", true); defineSymbol(math, main, rel, "\u2192", "\\rightarrow", true); defineSymbol(math, main, rel, "\u2192", "\\to"); defineSymbol(math, ams, rel, "\u2271", "\\ngeq", true); defineSymbol(math, ams, rel, "\u2270", "\\nleq", true); defineSymbol(math, main, spacing, "\u00a0", "\\ "); defineSymbol(math, main, spacing, "\u00a0", "\\space"); // Ref: LaTeX Source 2e: \DeclareRobustCommand{\nobreakspace}{% defineSymbol(math, main, spacing, "\u00a0", "\\nobreakspace"); defineSymbol(text, main, spacing, "\u00a0", "\\ "); defineSymbol(text, main, spacing, "\u00a0", " "); defineSymbol(text, main, spacing, "\u00a0", "\\space"); defineSymbol(text, main, spacing, "\u00a0", "\\nobreakspace"); defineSymbol(math, main, spacing, null, "\\nobreak"); defineSymbol(math, main, spacing, null, "\\allowbreak"); defineSymbol(math, main, punct, ",", ","); defineSymbol(math, main, punct, ";", ";"); defineSymbol(math, ams, bin, "\u22bc", "\\barwedge", true); defineSymbol(math, ams, bin, "\u22bb", "\\veebar", true); defineSymbol(math, main, bin, "\u2299", "\\odot", true); defineSymbol(math, main, bin, "\u2295", "\\oplus", true); defineSymbol(math, main, bin, "\u2297", "\\otimes", true); defineSymbol(math, main, textord, "\u2202", "\\partial", true); defineSymbol(math, main, bin, "\u2298", "\\oslash", true); defineSymbol(math, ams, bin, "\u229a", "\\circledcirc", true); defineSymbol(math, ams, bin, "\u22a1", "\\boxdot", true); defineSymbol(math, main, bin, "\u25b3", "\\bigtriangleup"); defineSymbol(math, main, bin, "\u25bd", "\\bigtriangledown"); defineSymbol(math, main, bin, "\u2020", "\\dagger"); defineSymbol(math, main, bin, "\u22c4", "\\diamond"); defineSymbol(math, main, bin, "\u22c6", "\\star"); defineSymbol(math, main, bin, "\u25c3", "\\triangleleft"); defineSymbol(math, main, bin, "\u25b9", "\\triangleright"); defineSymbol(math, main, open, "{", "\\{"); defineSymbol(text, main, textord, "{", "\\{"); defineSymbol(text, main, textord, "{", "\\textbraceleft"); defineSymbol(math, main, close, "}", "\\}"); defineSymbol(text, main, textord, "}", "\\}"); defineSymbol(text, main, textord, "}", "\\textbraceright"); defineSymbol(math, main, open, "{", "\\lbrace"); defineSymbol(math, main, close, "}", "\\rbrace"); defineSymbol(math, main, open, "[", "\\lbrack", true); defineSymbol(text, main, textord, "[", "\\lbrack", true); defineSymbol(math, main, close, "]", "\\rbrack", true); defineSymbol(text, main, textord, "]", "\\rbrack", true); defineSymbol(math, main, open, "(", "\\lparen", true); defineSymbol(math, main, close, ")", "\\rparen", true); defineSymbol(text, main, textord, "<", "\\textless", true); // in T1 fontenc defineSymbol(text, main, textord, ">", "\\textgreater", true); // in T1 fontenc defineSymbol(math, main, open, "\u230a", "\\lfloor", true); defineSymbol(math, main, close, "\u230b", "\\rfloor", true); defineSymbol(math, main, open, "\u2308", "\\lceil", true); defineSymbol(math, main, close, "\u2309", "\\rceil", true); defineSymbol(math, main, textord, "\\", "\\backslash"); defineSymbol(math, main, textord, "\u2223", "|"); defineSymbol(math, main, textord, "\u2223", "\\vert"); defineSymbol(text, main, textord, "|", "\\textbar", true); // in T1 fontenc defineSymbol(math, main, textord, "\u2225", "\\|"); defineSymbol(math, main, textord, "\u2225", "\\Vert"); defineSymbol(text, main, textord, "\u2225", "\\textbardbl"); defineSymbol(text, main, textord, "~", "\\textasciitilde"); defineSymbol(text, main, textord, "\\", "\\textbackslash"); defineSymbol(text, main, textord, "^", "\\textasciicircum"); defineSymbol(math, main, rel, "\u2191", "\\uparrow", true); defineSymbol(math, main, rel, "\u21d1", "\\Uparrow", true); defineSymbol(math, main, rel, "\u2193", "\\downarrow", true); defineSymbol(math, main, rel, "\u21d3", "\\Downarrow", true); defineSymbol(math, main, rel, "\u2195", "\\updownarrow", true); defineSymbol(math, main, rel, "\u21d5", "\\Updownarrow", true); defineSymbol(math, main, op, "\u2210", "\\coprod"); defineSymbol(math, main, op, "\u22c1", "\\bigvee"); defineSymbol(math, main, op, "\u22c0", "\\bigwedge"); defineSymbol(math, main, op, "\u2a04", "\\biguplus"); defineSymbol(math, main, op, "\u22c2", "\\bigcap"); defineSymbol(math, main, op, "\u22c3", "\\bigcup"); defineSymbol(math, main, op, "\u222b", "\\int"); defineSymbol(math, main, op, "\u222b", "\\intop"); defineSymbol(math, main, op, "\u222c", "\\iint"); defineSymbol(math, main, op, "\u222d", "\\iiint"); defineSymbol(math, main, op, "\u220f", "\\prod"); defineSymbol(math, main, op, "\u2211", "\\sum"); defineSymbol(math, main, op, "\u2a02", "\\bigotimes"); defineSymbol(math, main, op, "\u2a01", "\\bigoplus"); defineSymbol(math, main, op, "\u2a00", "\\bigodot"); defineSymbol(math, main, op, "\u222e", "\\oint"); defineSymbol(math, main, op, "\u222f", "\\oiint"); defineSymbol(math, main, op, "\u2230", "\\oiiint"); defineSymbol(math, main, op, "\u2a06", "\\bigsqcup"); defineSymbol(math, main, op, "\u222b", "\\smallint"); defineSymbol(text, main, inner, "\u2026", "\\textellipsis"); defineSymbol(math, main, inner, "\u2026", "\\mathellipsis"); defineSymbol(text, main, inner, "\u2026", "\\ldots", true); defineSymbol(math, main, inner, "\u2026", "\\ldots", true); defineSymbol(math, main, inner, "\u22ef", "\\@cdots", true); defineSymbol(math, main, inner, "\u22f1", "\\ddots", true); // \vdots is a macro that uses one of these two symbols (with made-up names): defineSymbol(math, main, textord, "\u22ee", "\\varvdots"); defineSymbol(text, main, textord, "\u22ee", "\\varvdots"); defineSymbol(math, main, accent, "\u02ca", "\\acute"); defineSymbol(math, main, accent, "\u02cb", "\\grave"); defineSymbol(math, main, accent, "\u00a8", "\\ddot"); defineSymbol(math, main, accent, "\u007e", "\\tilde"); defineSymbol(math, main, accent, "\u02c9", "\\bar"); defineSymbol(math, main, accent, "\u02d8", "\\breve"); defineSymbol(math, main, accent, "\u02c7", "\\check"); defineSymbol(math, main, accent, "\u005e", "\\hat"); defineSymbol(math, main, accent, "\u20d7", "\\vec"); defineSymbol(math, main, accent, "\u02d9", "\\dot"); defineSymbol(math, main, accent, "\u02da", "\\mathring"); // \imath and \jmath should be invariant to \mathrm, \mathbf, etc., so use PUA defineSymbol(math, main, mathord, "\ue131", "\\@imath"); defineSymbol(math, main, mathord, "\ue237", "\\@jmath"); defineSymbol(math, main, textord, "\u0131", "\u0131"); defineSymbol(math, main, textord, "\u0237", "\u0237"); defineSymbol(text, main, textord, "\u0131", "\\i", true); defineSymbol(text, main, textord, "\u0237", "\\j", true); defineSymbol(text, main, textord, "\u00df", "\\ss", true); defineSymbol(text, main, textord, "\u00e6", "\\ae", true); defineSymbol(text, main, textord, "\u0153", "\\oe", true); defineSymbol(text, main, textord, "\u00f8", "\\o", true); defineSymbol(text, main, textord, "\u00c6", "\\AE", true); defineSymbol(text, main, textord, "\u0152", "\\OE", true); defineSymbol(text, main, textord, "\u00d8", "\\O", true); defineSymbol(text, main, accent, "\u02ca", "\\'"); // acute defineSymbol(text, main, accent, "\u02cb", "\\`"); // grave defineSymbol(text, main, accent, "\u02c6", "\\^"); // circumflex defineSymbol(text, main, accent, "\u02dc", "\\~"); // tilde defineSymbol(text, main, accent, "\u02c9", "\\="); // macron defineSymbol(text, main, accent, "\u02d8", "\\u"); // breve defineSymbol(text, main, accent, "\u02d9", "\\."); // dot above defineSymbol(text, main, accent, "\u00b8", "\\c"); // cedilla defineSymbol(text, main, accent, "\u02da", "\\r"); // ring above defineSymbol(text, main, accent, "\u02c7", "\\v"); // caron defineSymbol(text, main, accent, "\u00a8", '\\"'); // diaeresis defineSymbol(text, main, accent, "\u02dd", "\\H"); // double acute defineSymbol(text, main, accent, "\u25ef", "\\textcircled"); // \bigcirc glyph // These ligatures are detected and created in Parser.js's `formLigatures`. var ligatures = { "--": true, "---": true, "``": true, "''": true }; defineSymbol(text, main, textord, "\u2013", "--", true); defineSymbol(text, main, textord, "\u2013", "\\textendash"); defineSymbol(text, main, textord, "\u2014", "---", true); defineSymbol(text, main, textord, "\u2014", "\\textemdash"); defineSymbol(text, main, textord, "\u2018", "`", true); defineSymbol(text, main, textord, "\u2018", "\\textquoteleft"); defineSymbol(text, main, textord, "\u2019", "'", true); defineSymbol(text, main, textord, "\u2019", "\\textquoteright"); defineSymbol(text, main, textord, "\u201c", "``", true); defineSymbol(text, main, textord, "\u201c", "\\textquotedblleft"); defineSymbol(text, main, textord, "\u201d", "''", true); defineSymbol(text, main, textord, "\u201d", "\\textquotedblright"); // \degree from gensymb package defineSymbol(math, main, textord, "\u00b0", "\\degree", true); defineSymbol(text, main, textord, "\u00b0", "\\degree"); // \textdegree from inputenc package defineSymbol(text, main, textord, "\u00b0", "\\textdegree", true); // TODO: In LaTeX, \pounds can generate a different character in text and math // mode, but among our fonts, only Main-Regular defines this character "163". defineSymbol(math, main, textord, "\u00a3", "\\pounds"); defineSymbol(math, main, textord, "\u00a3", "\\mathsterling", true); defineSymbol(text, main, textord, "\u00a3", "\\pounds"); defineSymbol(text, main, textord, "\u00a3", "\\textsterling", true); defineSymbol(math, ams, textord, "\u2720", "\\maltese"); defineSymbol(text, ams, textord, "\u2720", "\\maltese"); // There are lots of symbols which are the same, so we add them in afterwards. // All of these are textords in math mode var mathTextSymbols = "0123456789/@.\""; for (var i = 0; i < mathTextSymbols.length; i++) { var ch = mathTextSymbols.charAt(i); defineSymbol(math, main, textord, ch, ch); } // All of these are textords in text mode var textSymbols = "0123456789!@*()-=+\";:?/.,"; for (var _i = 0; _i < textSymbols.length; _i++) { var _ch = textSymbols.charAt(_i); defineSymbol(text, main, textord, _ch, _ch); } // All of these are textords in text mode, and mathords in math mode var letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; for (var _i2 = 0; _i2 < letters.length; _i2++) { var _ch2 = letters.charAt(_i2); defineSymbol(math, main, mathord, _ch2, _ch2); defineSymbol(text, main, textord, _ch2, _ch2); } // Blackboard bold and script letters in Unicode range defineSymbol(math, ams, textord, "C", "\u2102"); // blackboard bold defineSymbol(text, ams, textord, "C", "\u2102"); defineSymbol(math, ams, textord, "H", "\u210D"); defineSymbol(text, ams, textord, "H", "\u210D"); defineSymbol(math, ams, textord, "N", "\u2115"); defineSymbol(text, ams, textord, "N", "\u2115"); defineSymbol(math, ams, textord, "P", "\u2119"); defineSymbol(text, ams, textord, "P", "\u2119"); defineSymbol(math, ams, textord, "Q", "\u211A"); defineSymbol(text, ams, textord, "Q", "\u211A"); defineSymbol(math, ams, textord, "R", "\u211D"); defineSymbol(text, ams, textord, "R", "\u211D"); defineSymbol(math, ams, textord, "Z", "\u2124"); defineSymbol(text, ams, textord, "Z", "\u2124"); defineSymbol(math, main, mathord, "h", "\u210E"); // italic h, Planck constant defineSymbol(text, main, mathord, "h", "\u210E"); // The next loop loads wide (surrogate pair) characters. // We support some letters in the Unicode range U+1D400 to U+1D7FF, // Mathematical Alphanumeric Symbols. // Some editors do not deal well with wide characters. So don't write the // string into this file. Instead, create the string from the surrogate pair. var wideChar = ""; for (var _i3 = 0; _i3 < letters.length; _i3++) { var _ch3 = letters.charAt(_i3); // The hex numbers in the next line are a surrogate pair. // 0xD835 is the high surrogate for all letters in the range we support. // 0xDC00 is the low surrogate for bold A. wideChar = String.fromCharCode(0xD835, 0xDC00 + _i3); // A-Z a-z bold defineSymbol(math, main, mathord, _ch3, wideChar); defineSymbol(text, main, textord, _ch3, wideChar); wideChar = String.fromCharCode(0xD835, 0xDC34 + _i3); // A-Z a-z italic defineSymbol(math, main, mathord, _ch3, wideChar); defineSymbol(text, main, textord, _ch3, wideChar); wideChar = String.fromCharCode(0xD835, 0xDC68 + _i3); // A-Z a-z bold italic defineSymbol(math, main, mathord, _ch3, wideChar); defineSymbol(text, main, textord, _ch3, wideChar); wideChar = String.fromCharCode(0xD835, 0xDD04 + _i3); // A-Z a-z Fraktur defineSymbol(math, main, mathord, _ch3, wideChar); defineSymbol(text, main, textord, _ch3, wideChar); wideChar = String.fromCharCode(0xD835, 0xDD6C + _i3); // A-Z a-z bold Fraktur defineSymbol(math, main, mathord, _ch3, wideChar); defineSymbol(text, main, textord, _ch3, wideChar); wideChar = String.fromCharCode(0xD835, 0xDDA0 + _i3); // A-Z a-z sans-serif defineSymbol(math, main, mathord, _ch3, wideChar); defineSymbol(text, main, textord, _ch3, wideChar); wideChar = String.fromCharCode(0xD835, 0xDDD4 + _i3); // A-Z a-z sans bold defineSymbol(math, main, mathord, _ch3, wideChar); defineSymbol(text, main, textord, _ch3, wideChar); wideChar = String.fromCharCode(0xD835, 0xDE08 + _i3); // A-Z a-z sans italic defineSymbol(math, main, mathord, _ch3, wideChar); defineSymbol(text, main, textord, _ch3, wideChar); wideChar = String.fromCharCode(0xD835, 0xDE70 + _i3); // A-Z a-z monospace defineSymbol(math, main, mathord, _ch3, wideChar); defineSymbol(text, main, textord, _ch3, wideChar); if (_i3 < 26) { // KaTeX fonts have only capital letters for blackboard bold and script. // See exception for k below. wideChar = String.fromCharCode(0xD835, 0xDD38 + _i3); // A-Z double struck defineSymbol(math, main, mathord, _ch3, wideChar); defineSymbol(text, main, textord, _ch3, wideChar); wideChar = String.fromCharCode(0xD835, 0xDC9C + _i3); // A-Z script defineSymbol(math, main, mathord, _ch3, wideChar); defineSymbol(text, main, textord, _ch3, wideChar); } // TODO: Add bold script when it is supported by a KaTeX font. } // "k" is the only double struck lower case letter in the KaTeX fonts. wideChar = String.fromCharCode(0xD835, 0xDD5C); // k double struck defineSymbol(math, main, mathord, "k", wideChar); defineSymbol(text, main, textord, "k", wideChar); // Next, some wide character numerals for (var _i4 = 0; _i4 < 10; _i4++) { var _ch4 = _i4.toString(); wideChar = String.fromCharCode(0xD835, 0xDFCE + _i4); // 0-9 bold defineSymbol(math, main, mathord, _ch4, wideChar); defineSymbol(text, main, textord, _ch4, wideChar); wideChar = String.fromCharCode(0xD835, 0xDFE2 + _i4); // 0-9 sans serif defineSymbol(math, main, mathord, _ch4, wideChar); defineSymbol(text, main, textord, _ch4, wideChar); wideChar = String.fromCharCode(0xD835, 0xDFEC + _i4); // 0-9 bold sans defineSymbol(math, main, mathord, _ch4, wideChar); defineSymbol(text, main, textord, _ch4, wideChar); wideChar = String.fromCharCode(0xD835, 0xDFF6 + _i4); // 0-9 monospace defineSymbol(math, main, mathord, _ch4, wideChar); defineSymbol(text, main, textord, _ch4, wideChar); } // We add these Latin-1 letters as symbols for backwards-compatibility, // but they are not actually in the font, nor are they supported by the // Unicode accent mechanism, so they fall back to Times font and look ugly. // TODO(edemaine): Fix this. var extraLatin = "\u00d0\u00de\u00fe"; for (var _i5 = 0; _i5 < extraLatin.length; _i5++) { var _ch5 = extraLatin.charAt(_i5); defineSymbol(math, main, mathord, _ch5, _ch5); defineSymbol(text, main, textord, _ch5, _ch5); } /** * This file provides support for Unicode range U+1D400 to U+1D7FF, * Mathematical Alphanumeric Symbols. * * Function wideCharacterFont takes a wide character as input and returns * the font information necessary to render it properly. */ /** * Data below is from https://www.unicode.org/charts/PDF/U1D400.pdf * That document sorts characters into groups by font type, say bold or italic. * * In the arrays below, each subarray consists three elements: * * The CSS class of that group when in math mode. * * The CSS class of that group when in text mode. * * The font name, so that KaTeX can get font metrics. */ var wideLatinLetterData = [["mathbf", "textbf", "Main-Bold"], // A-Z bold upright ["mathbf", "textbf", "Main-Bold"], // a-z bold upright ["mathnormal", "textit", "Math-Italic"], // A-Z italic ["mathnormal", "textit", "Math-Italic"], // a-z italic ["boldsymbol", "boldsymbol", "Main-BoldItalic"], // A-Z bold italic ["boldsymbol", "boldsymbol", "Main-BoldItalic"], // a-z bold italic // Map fancy A-Z letters to script, not calligraphic. // This aligns with unicode-math and math fonts (except Cambria Math). ["mathscr", "textscr", "Script-Regular"], // A-Z script ["", "", ""], // a-z script. No font ["", "", ""], // A-Z bold script. No font ["", "", ""], // a-z bold script. No font ["mathfrak", "textfrak", "Fraktur-Regular"], // A-Z Fraktur ["mathfrak", "textfrak", "Fraktur-Regular"], // a-z Fraktur ["mathbb", "textbb", "AMS-Regular"], // A-Z double-struck ["mathbb", "textbb", "AMS-Regular"], // k double-struck // Note that we are using a bold font, but font metrics for regular Fraktur. ["mathboldfrak", "textboldfrak", "Fraktur-Regular"], // A-Z bold Fraktur ["mathboldfrak", "textboldfrak", "Fraktur-Regular"], // a-z bold Fraktur ["mathsf", "textsf", "SansSerif-Regular"], // A-Z sans-serif ["mathsf", "textsf", "SansSerif-Regular"], // a-z sans-serif ["mathboldsf", "textboldsf", "SansSerif-Bold"], // A-Z bold sans-serif ["mathboldsf", "textboldsf", "SansSerif-Bold"], // a-z bold sans-serif ["mathitsf", "textitsf", "SansSerif-Italic"], // A-Z italic sans-serif ["mathitsf", "textitsf", "SansSerif-Italic"], // a-z italic sans-serif ["", "", ""], // A-Z bold italic sans. No font ["", "", ""], // a-z bold italic sans. No font ["mathtt", "texttt", "Typewriter-Regular"], // A-Z monospace ["mathtt", "texttt", "Typewriter-Regular"] // a-z monospace ]; var wideNumeralData = [["mathbf", "textbf", "Main-Bold"], // 0-9 bold ["", "", ""], // 0-9 double-struck. No KaTeX font. ["mathsf", "textsf", "SansSerif-Regular"], // 0-9 sans-serif ["mathboldsf", "textboldsf", "SansSerif-Bold"], // 0-9 bold sans-serif ["mathtt", "texttt", "Typewriter-Regular"] // 0-9 monospace ]; var wideCharacterFont = function wideCharacterFont(wideChar, mode) { // IE doesn't support codePointAt(). So work with the surrogate pair. var H = wideChar.charCodeAt(0); // high surrogate var L = wideChar.charCodeAt(1); // low surrogate var codePoint = (H - 0xD800) * 0x400 + (L - 0xDC00) + 0x10000; var j = mode === "math" ? 0 : 1; // column index for CSS class. if (0x1D400 <= codePoint && codePoint < 0x1D6A4) { // wideLatinLetterData contains exactly 26 chars on each row. // So we can calculate the relevant row. No traverse necessary. var i = Math.floor((codePoint - 0x1D400) / 26); return [wideLatinLetterData[i][2], wideLatinLetterData[i][j]]; } else if (0x1D7CE <= codePoint && codePoint <= 0x1D7FF) { // Numerals, ten per row. var _i = Math.floor((codePoint - 0x1D7CE) / 10); return [wideNumeralData[_i][2], wideNumeralData[_i][j]]; } else if (codePoint === 0x1D6A5 || codePoint === 0x1D6A6) { // dotless i or j return [wideLatinLetterData[0][2], wideLatinLetterData[0][j]]; } else if (0x1D6A6 < codePoint && codePoint < 0x1D7CE) { // Greek letters. Not supported, yet. return ["", ""]; } else { // We don't support any wide characters outside 1D400–1D7FF. throw new ParseError("Unsupported character: " + wideChar); } }; /* eslint no-console:0 */ /** * Looks up the given symbol in fontMetrics, after applying any symbol * replacements defined in symbol.js */ var lookupSymbol = function lookupSymbol(value, // TODO(#963): Use a union type for this. fontName, mode) { // Replace the value with its replaced value from symbol.js if (symbols[mode][value] && symbols[mode][value].replace) { value = symbols[mode][value].replace; } return { value: value, metrics: getCharacterMetrics(value, fontName, mode) }; }; /** * 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. * * TODO: make argument order closer to makeSpan * TODO: add a separate argument for math class (e.g. `mop`, `mbin`), which * should if present come first in `classes`. * TODO(#953): Make `options` mandatory and always pass it in. */ var makeSymbol = function makeSymbol(value, fontName, mode, options, classes) { var lookup = lookupSymbol(value, fontName, mode); var metrics = lookup.metrics; value = lookup.value; var symbolNode; if (metrics) { var italic = metrics.italic; if (mode === "text" || options && options.font === "mathit") { italic = 0; } symbolNode = new SymbolNode(value, metrics.height, metrics.depth, italic, metrics.skew, metrics.width, 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 '" + fontName + "' and mode '" + mode + "'")); symbolNode = new SymbolNode(value, 0, 0, 0, 0, 0, classes); } if (options) { symbolNode.maxFontSize = options.sizeMultiplier; if (options.style.isTight()) { symbolNode.classes.push("mtight"); } var color = options.getColor(); 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 mathsym(value, mode, options, classes) { if (classes === void 0) { 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, as well as a special case for boldsymbol because it // can be used for bold + and - if (options.font === "boldsymbol" && lookupSymbol(value, "Main-Bold", mode).metrics) { return makeSymbol(value, "Main-Bold", mode, options, classes.concat(["mathbf"])); } else if (value === "\\" || symbols[mode][value].font === "main") { return makeSymbol(value, "Main-Regular", mode, options, classes); } else { return makeSymbol(value, "AMS-Regular", mode, options, classes.concat(["amsrm"])); } }; /** * Determines which of the two font names (Main-Bold and Math-BoldItalic) and * corresponding style tags (mathbf or boldsymbol) to use for font "boldsymbol", * depending on the symbol. Use this function instead of fontMap for font * "boldsymbol". */ var boldsymbol = function boldsymbol(value, mode, options, classes, type) { if (type !== "textord" && lookupSymbol(value, "Math-BoldItalic", mode).metrics) { return { fontName: "Math-BoldItalic", fontClass: "boldsymbol" }; } else { // Some glyphs do not exist in Math-BoldItalic so we need to use // Main-Bold instead. return { fontName: "Main-Bold", fontClass: "mathbf" }; } }; /** * Makes either a mathord or textord in the correct font and color. */ var makeOrd = function makeOrd(group, options, type) { var mode = group.mode; var text = group.text; var classes = ["mord"]; // Math mode or Old font (i.e. \rm) var isFont = mode === "math" || mode === "text" && options.font; var fontOrFamily = isFont ? options.font : options.fontFamily; var wideFontName = ""; var wideFontClass = ""; if (text.charCodeAt(0) === 0xD835) { [wideFontName, wideFontClass] = wideCharacterFont(text, mode); } if (wideFontName.length > 0) { // surrogate pairs get special treatment return makeSymbol(text, wideFontName, mode, options, classes.concat(wideFontClass)); } else if (fontOrFamily) { var fontName; var fontClasses; if (fontOrFamily === "boldsymbol") { var fontData = boldsymbol(text, mode, options, classes, type); fontName = fontData.fontName; fontClasses = [fontData.fontClass]; } else if (isFont) { fontName = fontMap[fontOrFamily].fontName; fontClasses = [fontOrFamily]; } else { fontName = retrieveTextFontName(fontOrFamily, options.fontWeight, options.fontShape); fontClasses = [fontOrFamily, options.fontWeight, options.fontShape]; } if (lookupSymbol(text, fontName, mode).metrics) { return makeSymbol(text, fontName, mode, options, classes.concat(fontClasses)); } else if (ligatures.hasOwnProperty(text) && fontName.slice(0, 10) === "Typewriter") { // Deconstruct ligatures in monospace fonts (\texttt, \tt). var parts = []; for (var i = 0; i < text.length; i++) { parts.push(makeSymbol(text[i], fontName, mode, options, classes.concat(fontClasses))); } return makeFragment(parts); } } // Makes a symbol in the default font for mathords and textords. if (type === "mathord") { return makeSymbol(text, "Math-Italic", mode, options, classes.concat(["mathnormal"])); } else if (type === "textord") { var font = symbols[mode][text] && symbols[mode][text].font; if (font === "ams") { var _fontName = retrieveTextFontName("amsrm", options.fontWeight, options.fontShape); return makeSymbol(text, _fontName, mode, options, classes.concat("amsrm", options.fontWeight, options.fontShape)); } else if (font === "main" || !font) { var _fontName2 = retrieveTextFontName("textrm", options.fontWeight, options.fontShape); return makeSymbol(text, _fontName2, mode, options, classes.concat(options.fontWeight, options.fontShape)); } else { // fonts added by plugins var _fontName3 = retrieveTextFontName(font, options.fontWeight, options.fontShape); // We add font name as a css class return makeSymbol(text, _fontName3, mode, options, classes.concat(_fontName3, options.fontWeight, options.fontShape)); } } else { throw new Error("unexpected type: " + type + " in makeOrd"); } }; /** * Returns true if subsequent symbolNodes have the same classes, skew, maxFont, * and styles. */ var canCombine = (prev, next) => { if (createClass(prev.classes) !== createClass(next.classes) || prev.skew !== next.skew || prev.maxFontSize !== next.maxFontSize) { return false; } // If prev and next both are just "mbin"s or "mord"s we don't combine them // so that the proper spacing can be preserved. if (prev.classes.length === 1) { var cls = prev.classes[0]; if (cls === "mbin" || cls === "mord") { return false; } } for (var style in prev.style) { if (prev.style.hasOwnProperty(style) && prev.style[style] !== next.style[style]) { return false; } } for (var _style in next.style) { if (next.style.hasOwnProperty(_style) && prev.style[_style] !== next.style[_style]) { return false; } } return true; }; /** * Combine consecutive domTree.symbolNodes into a single symbolNode. * Note: this function mutates the argument. */ var tryCombineChars = chars => { for (var i = 0; i < chars.length - 1; i++) { var prev = chars[i]; var next = chars[i + 1]; if (prev instanceof SymbolNode && next instanceof SymbolNode && canCombine(prev, next)) { prev.text += next.text; prev.height = Math.max(prev.height, next.height); prev.depth = Math.max(prev.depth, next.depth); // Use the last character's italic correction since we use // it to add padding to the right of the span created from // the combined characters. prev.italic = next.italic; chars.splice(i + 1, 1); i--; } } return chars; }; /** * Calculate the height, depth, and maxFontSize of an element based on its * children. */ var sizeElementFromChildren = function sizeElementFromChildren(elem) { var height = 0; var depth = 0; var maxFontSize = 0; for (var i = 0; i < elem.children.length; i++) { var child = elem.children[i]; if (child.height > height) { height = child.height; } if (child.depth > depth) { depth = child.depth; } if (child.maxFontSize > maxFontSize) { maxFontSize = child.maxFontSize; } } elem.height = height; elem.depth = depth; elem.maxFontSize = maxFontSize; }; /** * Makes a span with the given list of classes, list of children, and options. * * TODO(#953): Ensure that `options` is always provided (currently some call * sites don't pass it) and make the type below mandatory. * TODO: add a separate argument for math class (e.g. `mop`, `mbin`), which * should if present come first in `classes`. */ var makeSpan$2 = function makeSpan(classes, children, options, style) { var span = new Span(classes, children, options, style); sizeElementFromChildren(span); return span; }; // SVG one is simpler -- doesn't require height, depth, max-font setting. // This is also a separate method for typesafety. var makeSvgSpan = (classes, children, options, style) => new Span(classes, children, options, style); var makeLineSpan = function makeLineSpan(className, options, thickness) { var line = makeSpan$2([className], [], options); line.height = Math.max(thickness || options.fontMetrics().defaultRuleThickness, options.minRuleThickness); line.style.borderBottomWidth = makeEm(line.height); line.maxFontSize = 1.0; return line; }; /** * Makes an anchor with the given href, list of classes, list of children, * and options. */ var makeAnchor = function makeAnchor(href, classes, children, options) { var anchor = new Anchor(href, classes, children, options); sizeElementFromChildren(anchor); return anchor; }; /** * Makes a document fragment with the given list of children. */ var makeFragment = function makeFragment(children) { var fragment = new DocumentFragment(children); sizeElementFromChildren(fragment); return fragment; }; /** * Wraps group in a span if it's a document fragment, allowing to apply classes * and styles */ var wrapFragment = function wrapFragment(group, options) { if (group instanceof DocumentFragment) { return makeSpan$2([], [group], options); } return group; }; // These are exact object types to catch typos in the names of the optional fields. // Computes the updated `children` list and the overall depth. // // This helper function for makeVList makes it easier to enforce type safety by // allowing early exits (returns) in the logic. var getVListChildrenAndDepth = function getVListChildrenAndDepth(params) { if (params.positionType === "individualShift") { var oldChildren = params.children; var children = [oldChildren[0]]; // Add in kerns to the list of params.children to get each element to be // shifted to the correct specified shift var _depth = -oldChildren[0].shift - oldChildren[0].elem.depth; var currPos = _depth; for (var 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 }); children.push(oldChildren[i]); } return { children, depth: _depth }; } var depth; if (params.positionType === "top") { // We always start at the bottom, so calculate the bottom by adding up // all the sizes var bottom = params.positionData; for (var _i = 0; _i < params.children.length; _i++) { var child = params.children[_i]; bottom -= child.type === "kern" ? child.size : child.elem.height + child.elem.depth; } depth = bottom; } else if (params.positionType === "bottom") { depth = -params.positionData; } else { var firstChild = params.children[0]; if (firstChild.type !== "elem") { throw new Error('First child must have type "elem".'); } if (params.positionType === "shift") { depth = -firstChild.elem.depth - params.positionData; } else if (params.positionType === "firstBaseline") { depth = -firstChild.elem.depth; } else { throw new Error("Invalid positionType " + params.positionType + "."); } } return { children: params.children, depth }; }; /** * Makes a vertical list by stacking elements and kerns on top of each other. * Allows for many different ways of specifying the positioning method. * * See VListParam documentation above. */ var makeVList = function makeVList(params, options) { var { children, depth } = getVListChildrenAndDepth(params); // Create a strut that is taller than any list item. The strut is added to // each item, where it will determine the item's baseline. Since it has // `overflow:hidden`, the strut's top edge will sit on the item's line box's // top edge and the strut's bottom edge will sit on the item's baseline, // with no additional line-height spacing. This allows the item baseline to // be positioned precisely without worrying about font ascent and // line-height. var pstrutSize = 0; for (var i = 0; i < children.length; i++) { var child = children[i]; if (child.type === "elem") { var elem = child.elem; pstrutSize = Math.max(pstrutSize, elem.maxFontSize, elem.height); } } pstrutSize += 2; var pstrut = makeSpan$2(["pstrut"], []); pstrut.style.height = makeEm(pstrutSize); // Create a new list of actual children at the correct offsets var realChildren = []; var minPos = depth; var maxPos = depth; var currPos = depth; for (var _i2 = 0; _i2 < children.length; _i2++) { var _child = children[_i2]; if (_child.type === "kern") { currPos += _child.size; } else { var _elem = _child.elem; var classes = _child.wrapperClasses || []; var style = _child.wrapperStyle || {}; var childWrap = makeSpan$2(classes, [pstrut, _elem], undefined, style); childWrap.style.top = makeEm(-pstrutSize - currPos - _elem.depth); if (_child.marginLeft) { childWrap.style.marginLeft = _child.marginLeft; } if (_child.marginRight) { childWrap.style.marginRight = _child.marginRight; } realChildren.push(childWrap); currPos += _elem.height + _elem.depth; } minPos = Math.min(minPos, currPos); maxPos = Math.max(maxPos, currPos); } // The vlist contents go in a table-cell with `vertical-align:bottom`. // This cell's bottom edge will determine the containing table's baseline // without overly expanding the containing line-box. var vlist = makeSpan$2(["vlist"], realChildren); vlist.style.height = makeEm(maxPos); // A second row is used if necessary to represent the vlist's depth. var rows; if (minPos < 0) { // We will define depth in an empty span with display: table-cell. // It should render with the height that we define. But Chrome, in // contenteditable mode only, treats that span as if it contains some // text content. And that min-height over-rides our desired height. // So we put another empty span inside the depth strut span. var emptySpan = makeSpan$2([], []); var depthStrut = makeSpan$2(["vlist"], [emptySpan]); depthStrut.style.height = makeEm(-minPos); // Safari wants the first row to have inline content; otherwise it // puts the bottom of the *second* row on the baseline. var topStrut = makeSpan$2(["vlist-s"], [new SymbolNode("\u200b")]); rows = [makeSpan$2(["vlist-r"], [vlist, topStrut]), makeSpan$2(["vlist-r"], [depthStrut])]; } else { rows = [makeSpan$2(["vlist-r"], [vlist])]; } var vtable = makeSpan$2(["vlist-t"], rows); if (rows.length === 2) { vtable.classes.push("vlist-t2"); } vtable.height = maxPos; vtable.depth = -minPos; return vtable; }; // Glue is a concept from TeX which is a flexible space between elements in // either a vertical or horizontal list. In KaTeX, at least for now, it's // static space between elements in a horizontal layout. var makeGlue = (measurement, options) => { // Make an empty span for the space var rule = makeSpan$2(["mspace"], [], options); var size = calculateSize(measurement, options); rule.style.marginRight = makeEm(size); return rule; }; // Takes font options, and returns the appropriate fontLookup name var retrieveTextFontName = function retrieveTextFontName(fontFamily, fontWeight, fontShape) { var baseFontName = ""; switch (fontFamily) { case "amsrm": baseFontName = "AMS"; break; case "textrm": baseFontName = "Main"; break; case "textsf": baseFontName = "SansSerif"; break; case "texttt": baseFontName = "Typewriter"; break; default: baseFontName = fontFamily; // use fonts added by a plugin } var fontStylesName; if (fontWeight === "textbf" && fontShape === "textit") { fontStylesName = "BoldItalic"; } else if (fontWeight === "textbf") { fontStylesName = "Bold"; } else if (fontWeight === "textit") { fontStylesName = "Italic"; } else { fontStylesName = "Regular"; } return baseFontName + "-" + fontStylesName; }; /** * 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" }, "textit": { variant: "italic", fontName: "Main-Italic" }, "mathit": { variant: "italic", fontName: "Main-Italic" }, "mathnormal": { variant: "italic", fontName: "Math-Italic" }, "mathsfit": { variant: "sans-serif-italic", fontName: "SansSerif-Italic" }, // "boldsymbol" is missing because they require the use of multiple fonts: // Math-BoldItalic and Main-Bold. This is handled by a special case in // makeOrd which ends up calling boldsymbol. // 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" } }; var svgData = { // path, width, height vec: ["vec", 0.471, 0.714], // values from the font glyph oiintSize1: ["oiintSize1", 0.957, 0.499], // oval to overlay the integrand oiintSize2: ["oiintSize2", 1.472, 0.659], oiiintSize1: ["oiiintSize1", 1.304, 0.499], oiiintSize2: ["oiiintSize2", 1.98, 0.659] }; var staticSvg = function staticSvg(value, options) { // Create a span with inline SVG for the element. var [pathName, width, height] = svgData[value]; var path = new PathNode(pathName); var svgNode = new SvgNode([path], { "width": makeEm(width), "height": makeEm(height), // Override CSS rule `.katex svg { width: 100% }` "style": "width:" + makeEm(width), "viewBox": "0 0 " + 1000 * width + " " + 1000 * height, "preserveAspectRatio": "xMinYMin" }); var span = makeSvgSpan(["overlay"], [svgNode], options); span.height = height; span.style.height = makeEm(height); span.style.width = makeEm(width); return span; }; var buildCommon = { fontMap, makeSymbol, mathsym, makeSpan: makeSpan$2, makeSvgSpan, makeLineSpan, makeAnchor, makeFragment, wrapFragment, makeVList, makeOrd, makeGlue, staticSvg, svgData, tryCombineChars }; /** * Describes spaces between different classes of atoms. */ var thinspace = { number: 3, unit: "mu" }; var mediumspace = { number: 4, unit: "mu" }; var thickspace = { number: 5, unit: "mu" }; // Making the type below exact with all optional fields doesn't work due to // - https://github.com/facebook/flow/issues/4582 // - https://github.com/facebook/flow/issues/5688 // However, since *all* fields are optional, $Shape<> works as suggested in 5688 // above. // Spacing relationships for display and text styles var spacings = { mord: { mop: thinspace, mbin: mediumspace, mrel: thickspace, minner: thinspace }, mop: { mord: thinspace, mop: thinspace, mrel: thickspace, minner: thinspace }, mbin: { mord: mediumspace, mop: mediumspace, mopen: mediumspace, minner: mediumspace }, mrel: { mord: thickspace, mop: thickspace, mopen: thickspace, minner: thickspace }, mopen: {}, mclose: { mop: thinspace, mbin: mediumspace, mrel: thickspace, minner: thinspace }, mpunct: { mord: thinspace, mop: thinspace, mrel: thickspace, mopen: thinspace, mclose: thinspace, mpunct: thinspace, minner: thinspace }, minner: { mord: thinspace, mop: thinspace, mbin: mediumspace, mrel: thickspace, mopen: thinspace, mpunct: thinspace, minner: thinspace } }; // Spacing relationships for script and scriptscript styles var tightSpacings = { mord: { mop: thinspace }, mop: { mord: thinspace, mop: thinspace }, mbin: {}, mrel: {}, mopen: {}, mclose: { mop: thinspace }, mpunct: {}, minner: { mop: thinspace } }; /** Context provided to function handlers for error messages. */ // Note: reverse the order of the return type union will cause a flow error. // See https://github.com/facebook/flow/issues/3663. // More general version of `HtmlBuilder` for nodes (e.g. \sum, accent types) // whose presence impacts super/subscripting. In this case, ParseNode<"supsub"> // delegates its HTML building to the HtmlBuilder corresponding to these nodes. /** * Final function spec for use at parse time. * This is almost identical to `FunctionPropSpec`, except it * 1. includes the function handler, and * 2. requires all arguments except argTypes. * It is generated by `defineFunction()` below. */ /** * All registered functions. * `functions.js` just exports this same dictionary again and makes it public. * `Parser.js` requires this dictionary. */ var _functions = {}; /** * All HTML builders. Should be only used in the `define*` and the `build*ML` * functions. */ var _htmlGroupBuilders = {}; /** * All MathML builders. Should be only used in the `define*` and the `build*ML` * functions. */ var _mathmlGroupBuilders = {}; function defineFunction(_ref) { var { type, names, props, handler, htmlBuilder, mathmlBuilder } = _ref; // Set default values of functions var data = { type, numArgs: props.numArgs, argTypes: props.argTypes, allowedInArgument: !!props.allowedInArgument, allowedInText: !!props.allowedInText, allowedInMath: props.allowedInMath === undefined ? true : props.allowedInMath, numOptionalArgs: props.numOptionalArgs || 0, infix: !!props.infix, primitive: !!props.primitive, handler: handler }; for (var i = 0; i < names.length; ++i) { _functions[names[i]] = data; } if (type) { if (htmlBuilder) { _htmlGroupBuilders[type] = htmlBuilder; } if (mathmlBuilder) { _mathmlGroupBuilders[type] = mathmlBuilder; } } } /** * Use this to register only the HTML and MathML builders for a function (e.g. * if the function's ParseNode is generated in Parser.js rather than via a * stand-alone handler provided to `defineFunction`). */ function defineFunctionBuilders(_ref2) { var { type, htmlBuilder, mathmlBuilder } = _ref2; defineFunction({ type, names: [], props: { numArgs: 0 }, handler() { throw new Error('Should never be called.'); }, htmlBuilder, mathmlBuilder }); } var normalizeArgument = function normalizeArgument(arg) { return arg.type === "ordgroup" && arg.body.length === 1 ? arg.body[0] : arg; }; // Since the corresponding buildHTML/buildMathML function expects a // list of elements, we normalize for different kinds of arguments var ordargument = function ordargument(arg) { return arg.type === "ordgroup" ? arg.body : [arg]; }; /** * This file does the main work of building a domTree structure from a parse * tree. The entry point is the `buildHTML` function, which takes a parse tree. * Then, the buildExpression, buildGroup, and various groupBuilders functions * are called, to produce a final HTML tree. */ var makeSpan$1 = buildCommon.makeSpan; // Binary atoms (first class `mbin`) change into ordinary atoms (`mord`) // depending on their surroundings. See TeXbook pg. 442-446, Rules 5 and 6, // and the text before Rule 19. var binLeftCanceller = ["leftmost", "mbin", "mopen", "mrel", "mop", "mpunct"]; var binRightCanceller = ["rightmost", "mrel", "mclose", "mpunct"]; var styleMap$1 = { "display": Style$1.DISPLAY, "text": Style$1.TEXT, "script": Style$1.SCRIPT, "scriptscript": Style$1.SCRIPTSCRIPT }; var DomEnum = { mord: "mord", mop: "mop", mbin: "mbin", mrel: "mrel", mopen: "mopen", mclose: "mclose", mpunct: "mpunct", minner: "minner" }; /** * Take a list of nodes, build them in order, and return a list of the built * nodes. documentFragments are flattened into their contents, so the * returned list contains no fragments. `isRealGroup` is true if `expression` * is a real group (no atoms will be added on either side), as opposed to * a partial group (e.g. one created by \color). `surrounding` is an array * consisting type of nodes that will be added to the left and right. */ var buildExpression$1 = function buildExpression(expression, options, isRealGroup, surrounding) { if (surrounding === void 0) { surrounding = [null, null]; } // Parse expressions into `groups`. var groups = []; for (var i = 0; i < expression.length; i++) { var output = buildGroup$1(expression[i], options); if (output instanceof DocumentFragment) { var children = output.children; groups.push(...children); } else { groups.push(output); } } // Combine consecutive domTree.symbolNodes into a single symbolNode. buildCommon.tryCombineChars(groups); // If `expression` is a partial group, let the parent handle spacings // to avoid processing groups multiple times. if (!isRealGroup) { return groups; } var glueOptions = options; if (expression.length === 1) { var node = expression[0]; if (node.type === "sizing") { glueOptions = options.havingSize(node.size); } else if (node.type === "styling") { glueOptions = options.havingStyle(styleMap$1[node.style]); } } // Dummy spans for determining spacings between surrounding atoms. // If `expression` has no atoms on the left or right, class "leftmost" // or "rightmost", respectively, is used to indicate it. var dummyPrev = makeSpan$1([surrounding[0] || "leftmost"], [], options); var dummyNext = makeSpan$1([surrounding[1] || "rightmost"], [], options); // TODO: These code assumes that a node's math class is the first element // of its `classes` array. A later cleanup should ensure this, for // instance by changing the signature of `makeSpan`. // Before determining what spaces to insert, perform bin cancellation. // Binary operators change to ordinary symbols in some contexts. var isRoot = isRealGroup === "root"; traverseNonSpaceNodes(groups, (node, prev) => { var prevType = prev.classes[0]; var type = node.classes[0]; if (prevType === "mbin" && utils.contains(binRightCanceller, type)) { prev.classes[0] = "mord"; } else if (type === "mbin" && utils.contains(binLeftCanceller, prevType)) { node.classes[0] = "mord"; } }, { node: dummyPrev }, dummyNext, isRoot); traverseNonSpaceNodes(groups, (node, prev) => { var prevType = getTypeOfDomTree(prev); var type = getTypeOfDomTree(node); // 'mtight' indicates that the node is script or scriptscript style. var space = prevType && type ? node.hasClass("mtight") ? tightSpacings[prevType][type] : spacings[prevType][type] : null; if (space) { // Insert glue (spacing) after the `prev`. return buildCommon.makeGlue(space, glueOptions); } }, { node: dummyPrev }, dummyNext, isRoot); return groups; }; // Depth-first traverse non-space `nodes`, calling `callback` with the current and // previous node as arguments, optionally returning a node to insert after the // previous node. `prev` is an object with the previous node and `insertAfter` // function to insert after it. `next` is a node that will be added to the right. // Used for bin cancellation and inserting spacings. var traverseNonSpaceNodes = function traverseNonSpaceNodes(nodes, callback, prev, next, isRoot) { if (next) { // temporarily append the right node, if exists nodes.push(next); } var i = 0; for (; i < nodes.length; i++) { var node = nodes[i]; var partialGroup = checkPartialGroup(node); if (partialGroup) { // Recursive DFS // $FlowFixMe: make nodes a $ReadOnlyArray by returning a new array traverseNonSpaceNodes(partialGroup.children, callback, prev, null, isRoot); continue; } // Ignore explicit spaces (e.g., \;, \,) when determining what implicit // spacing should go between atoms of different classes var nonspace = !node.hasClass("mspace"); if (nonspace) { var result = callback(node, prev.node); if (result) { if (prev.insertAfter) { prev.insertAfter(result); } else { // insert at front nodes.unshift(result); i++; } } } if (nonspace) { prev.node = node; } else if (isRoot && node.hasClass("newline")) { prev.node = makeSpan$1(["leftmost"]); // treat like beginning of line } prev.insertAfter = (index => n => { nodes.splice(index + 1, 0, n); i++; })(i); } if (next) { nodes.pop(); } }; // Check if given node is a partial group, i.e., does not affect spacing around. var checkPartialGroup = function checkPartialGroup(node) { if (node instanceof DocumentFragment || node instanceof Anchor || node instanceof Span && node.hasClass("enclosing")) { return node; } return null; }; // Return the outermost node of a domTree. var getOutermostNode = function getOutermostNode(node, side) { var partialGroup = checkPartialGroup(node); if (partialGroup) { var children = partialGroup.children; if (children.length) { if (side === "right") { return getOutermostNode(children[children.length - 1], "right"); } else if (side === "left") { return getOutermostNode(children[0], "left"); } } } return node; }; // Return math atom class (mclass) of a domTree. // If `side` is given, it will get the type of the outermost node at given side. var getTypeOfDomTree = function getTypeOfDomTree(node, side) { if (!node) { return null; } if (side) { node = getOutermostNode(node, side); } // This makes a lot of assumptions as to where the type of atom // appears. We should do a better job of enforcing this. return DomEnum[node.classes[0]] || null; }; var makeNullDelimiter = function makeNullDelimiter(options, classes) { var moreClasses = ["nulldelimiter"].concat(options.baseSizingClasses()); return makeSpan$1(classes.concat(moreClasses)); }; /** * buildGroup is the function that takes a group and calls the correct groupType * function for it. It also handles the interaction of size and style changes * between parents and children. */ var buildGroup$1 = function buildGroup(group, options, baseOptions) { if (!group) { return makeSpan$1(); } if (_htmlGroupBuilders[group.type]) { // Call the groupBuilders function // $FlowFixMe var groupNode = _htmlGroupBuilders[group.type](group, options); // If the size changed between the parent and the current group, account // for that size difference. if (baseOptions && options.size !== baseOptions.size) { groupNode = makeSpan$1(options.sizingClasses(baseOptions), [groupNode], options); var multiplier = options.sizeMultiplier / baseOptions.sizeMultiplier; groupNode.height *= multiplier; groupNode.depth *= multiplier; } return groupNode; } else { throw new ParseError("Got group of unknown type: '" + group.type + "'"); } }; /** * Combine an array of HTML DOM nodes (e.g., the output of `buildExpression`) * into an unbreakable HTML node of class .base, with proper struts to * guarantee correct vertical extent. `buildHTML` calls this repeatedly to * make up the entire expression as a sequence of unbreakable units. */ function buildHTMLUnbreakable(children, options) { // Compute height and depth of this chunk. var body = makeSpan$1(["base"], children, options); // Add strut, which ensures that the top of the HTML element falls at // the height of the expression, and the bottom of the HTML element // falls at the depth of the expression. var strut = makeSpan$1(["strut"]); strut.style.height = makeEm(body.height + body.depth); if (body.depth) { strut.style.verticalAlign = makeEm(-body.depth); } body.children.unshift(strut); return body; } /** * Take an entire parse tree, and build it into an appropriate set of HTML * nodes. */ function buildHTML(tree, options) { // Strip off outer tag wrapper for processing below. var tag = null; if (tree.length === 1 && tree[0].type === "tag") { tag = tree[0].tag; tree = tree[0].body; } // Build the expression contained in the tree var expression = buildExpression$1(tree, options, "root"); var eqnNum; if (expression.length === 2 && expression[1].hasClass("tag")) { // An environment with automatic equation numbers, e.g. {gather}. eqnNum = expression.pop(); } var children = []; // Create one base node for each chunk between potential line breaks. // The TeXBook [p.173] says "A formula will be broken only after a // relation symbol like $=$ or $<$ or $\rightarrow$, or after a binary // operation symbol like $+$ or $-$ or $\times$, where the relation or // binary operation is on the ``outer level'' of the formula (i.e., not // enclosed in {...} and not part of an \over construction)." var parts = []; for (var i = 0; i < expression.length; i++) { parts.push(expression[i]); if (expression[i].hasClass("mbin") || expression[i].hasClass("mrel") || expression[i].hasClass("allowbreak")) { // Put any post-operator glue on same line as operator. // Watch for \nobreak along the way, and stop at \newline. var nobreak = false; while (i < expression.length - 1 && expression[i + 1].hasClass("mspace") && !expression[i + 1].hasClass("newline")) { i++; parts.push(expression[i]); if (expression[i].hasClass("nobreak")) { nobreak = true; } } // Don't allow break if \nobreak among the post-operator glue. if (!nobreak) { children.push(buildHTMLUnbreakable(parts, options)); parts = []; } } else if (expression[i].hasClass("newline")) { // Write the line except the newline parts.pop(); if (parts.length > 0) { children.push(buildHTMLUnbreakable(parts, options)); parts = []; } // Put the newline at the top level children.push(expression[i]); } } if (parts.length > 0) { children.push(buildHTMLUnbreakable(parts, options)); } // Now, if there was a tag, build it too and append it as a final child. var tagChild; if (tag) { tagChild = buildHTMLUnbreakable(buildExpression$1(tag, options, true)); tagChild.classes = ["tag"]; children.push(tagChild); } else if (eqnNum) { children.push(eqnNum); } var htmlNode = makeSpan$1(["katex-html"], children); htmlNode.setAttribute("aria-hidden", "true"); // Adjust the strut of the tag to be the maximum height of all children // (the height of the enclosing htmlNode) for proper vertical alignment. if (tagChild) { var strut = tagChild.children[0]; strut.style.height = makeEm(htmlNode.height + htmlNode.depth); if (htmlNode.depth) { strut.style.verticalAlign = makeEm(-htmlNode.depth); } } return htmlNode; } /** * These objects store data about MathML nodes. This is the MathML equivalent * of the types in domTree.js. Since MathML handles its own rendering, and * since we're mainly using MathML to improve accessibility, we don't manage * any of the styling state that the plain DOM nodes do. * * The `toNode` and `toMarkup` functions work similarly to how they do in * domTree.js, creating namespaced DOM nodes and HTML text markup respectively. */ function newDocumentFragment(children) { return new DocumentFragment(children); } /** * This node represents a general purpose MathML node of any type. The * constructor requires the type of node to create (for example, `"mo"` or * `"mspace"`, corresponding to `` and `` tags). */ class MathNode { constructor(type, children, classes) { this.type = void 0; this.attributes = void 0; this.children = void 0; this.classes = void 0; this.type = type; this.attributes = {}; this.children = children || []; this.classes = classes || []; } /** * Sets an attribute on a MathML node. MathML depends on attributes to convey a * semantic content, so this is used heavily. */ setAttribute(name, value) { this.attributes[name] = value; } /** * Gets an attribute on a MathML node. */ getAttribute(name) { return this.attributes[name]; } /** * Converts the math node into a MathML-namespaced DOM element. */ toNode() { var node = document.createElementNS("http://www.w3.org/1998/Math/MathML", this.type); for (var attr in this.attributes) { if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) { node.setAttribute(attr, this.attributes[attr]); } } if (this.classes.length > 0) { node.className = createClass(this.classes); } for (var i = 0; i < this.children.length; i++) { // Combine multiple TextNodes into one TextNode, to prevent // screen readers from reading each as a separate word [#3995] if (this.children[i] instanceof TextNode && this.children[i + 1] instanceof TextNode) { var text = this.children[i].toText() + this.children[++i].toText(); while (this.children[i + 1] instanceof TextNode) { text += this.children[++i].toText(); } node.appendChild(new TextNode(text).toNode()); } else { node.appendChild(this.children[i].toNode()); } } return node; } /** * Converts the math node into an HTML markup string. */ toMarkup() { var markup = "<" + this.type; // Add the attributes for (var attr in this.attributes) { if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) { markup += " " + attr + "=\""; markup += utils.escape(this.attributes[attr]); markup += "\""; } } if (this.classes.length > 0) { markup += " class =\"" + utils.escape(createClass(this.classes)) + "\""; } markup += ">"; for (var i = 0; i < this.children.length; i++) { markup += this.children[i].toMarkup(); } markup += ""; return markup; } /** * Converts the math node into a string, similar to innerText, but escaped. */ toText() { return this.children.map(child => child.toText()).join(""); } } /** * This node represents a piece of text. */ class TextNode { constructor(text) { this.text = void 0; this.text = text; } /** * Converts the text node into a DOM text node. */ toNode() { return document.createTextNode(this.text); } /** * Converts the text node into escaped HTML markup * (representing the text itself). */ toMarkup() { return utils.escape(this.toText()); } /** * Converts the text node into a string * (representing the text itself). */ toText() { return this.text; } } /** * This node represents a space, but may render as or as text, * depending on the width. */ class SpaceNode { /** * Create a Space node with width given in CSS ems. */ constructor(width) { this.width = void 0; this.character = void 0; this.width = width; // See https://www.w3.org/TR/2000/WD-MathML2-20000328/chapter6.html // for a table of space-like characters. We use Unicode // representations instead of &LongNames; as it's not clear how to // make the latter via document.createTextNode. if (width >= 0.05555 && width <= 0.05556) { this.character = "\u200a"; //   } else if (width >= 0.1666 && width <= 0.1667) { this.character = "\u2009"; //   } else if (width >= 0.2222 && width <= 0.2223) { this.character = "\u2005"; //   } else if (width >= 0.2777 && width <= 0.2778) { this.character = "\u2005\u200a"; //    } else if (width >= -0.05556 && width <= -0.05555) { this.character = "\u200a\u2063"; // ​ } else if (width >= -0.1667 && width <= -0.1666) { this.character = "\u2009\u2063"; // ​ } else if (width >= -0.2223 && width <= -0.2222) { this.character = "\u205f\u2063"; // ​ } else if (width >= -0.2778 && width <= -0.2777) { this.character = "\u2005\u2063"; // ​ } else { this.character = null; } } /** * Converts the math node into a MathML-namespaced DOM element. */ toNode() { if (this.character) { return document.createTextNode(this.character); } else { var node = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mspace"); node.setAttribute("width", makeEm(this.width)); return node; } } /** * Converts the math node into an HTML markup string. */ toMarkup() { if (this.character) { return "" + this.character + ""; } else { return ""; } } /** * Converts the math node into a string, similar to innerText. */ toText() { if (this.character) { return this.character; } else { return " "; } } } var mathMLTree = { MathNode, TextNode, SpaceNode, newDocumentFragment }; /** * This file converts a parse tree into a corresponding MathML tree. The main * entry point is the `buildMathML` function, which takes a parse tree from the * parser. */ /** * Takes a symbol and converts it into a MathML text node after performing * optional replacement from symbols.js. */ var makeText = function makeText(text, mode, options) { if (symbols[mode][text] && symbols[mode][text].replace && text.charCodeAt(0) !== 0xD835 && !(ligatures.hasOwnProperty(text) && options && (options.fontFamily && options.fontFamily.slice(4, 6) === "tt" || options.font && options.font.slice(4, 6) === "tt"))) { text = symbols[mode][text].replace; } return new mathMLTree.TextNode(text); }; /** * Wrap the given array of nodes in an node if needed, i.e., * unless the array has length 1. Always returns a single node. */ var makeRow = function makeRow(body) { if (body.length === 1) { return body[0]; } else { return new mathMLTree.MathNode("mrow", body); } }; /** * Returns the math variant as a string or null if none is required. */ var getVariant = function getVariant(group, options) { // Handle \text... font specifiers as best we can. // MathML has a limited list of allowable mathvariant specifiers; see // https://www.w3.org/TR/MathML3/chapter3.html#presm.commatt if (options.fontFamily === "texttt") { return "monospace"; } else if (options.fontFamily === "textsf") { if (options.fontShape === "textit" && options.fontWeight === "textbf") { return "sans-serif-bold-italic"; } else if (options.fontShape === "textit") { return "sans-serif-italic"; } else if (options.fontWeight === "textbf") { return "bold-sans-serif"; } else { return "sans-serif"; } } else if (options.fontShape === "textit" && options.fontWeight === "textbf") { return "bold-italic"; } else if (options.fontShape === "textit") { return "italic"; } else if (options.fontWeight === "textbf") { return "bold"; } var font = options.font; if (!font || font === "mathnormal") { return null; } var mode = group.mode; if (font === "mathit") { return "italic"; } else if (font === "boldsymbol") { return group.type === "textord" ? "bold" : "bold-italic"; } else if (font === "mathbf") { return "bold"; } else if (font === "mathbb") { return "double-struck"; } else if (font === "mathsfit") { return "sans-serif-italic"; } else if (font === "mathfrak") { return "fraktur"; } else if (font === "mathscr" || font === "mathcal") { // MathML makes no distinction between script and calligraphic return "script"; } else if (font === "mathsf") { return "sans-serif"; } else if (font === "mathtt") { return "monospace"; } var text = group.text; if (utils.contains(["\\imath", "\\jmath"], text)) { return null; } if (symbols[mode][text] && symbols[mode][text].replace) { text = symbols[mode][text].replace; } var fontName = buildCommon.fontMap[font].fontName; if (getCharacterMetrics(text, fontName, mode)) { return buildCommon.fontMap[font].variant; } return null; }; /** * Check for . which is how a dot renders in MathML, * or , * which is how a braced comma {,} renders in MathML */ function isNumberPunctuation(group) { if (!group) { return false; } if (group.type === 'mi' && group.children.length === 1) { var child = group.children[0]; return child instanceof TextNode && child.text === '.'; } else if (group.type === 'mo' && group.children.length === 1 && group.getAttribute('separator') === 'true' && group.getAttribute('lspace') === '0em' && group.getAttribute('rspace') === '0em') { var _child = group.children[0]; return _child instanceof TextNode && _child.text === ','; } else { return false; } } /** * Takes a list of nodes, builds them, and returns a list of the generated * MathML nodes. Also combine consecutive outputs into a single * tag. */ var buildExpression = function buildExpression(expression, options, isOrdgroup) { if (expression.length === 1) { var group = buildGroup(expression[0], options); if (isOrdgroup && group instanceof MathNode && group.type === "mo") { // When TeX writers want to suppress spacing on an operator, // they often put the operator by itself inside braces. group.setAttribute("lspace", "0em"); group.setAttribute("rspace", "0em"); } return [group]; } var groups = []; var lastGroup; for (var i = 0; i < expression.length; i++) { var _group = buildGroup(expression[i], options); if (_group instanceof MathNode && lastGroup instanceof MathNode) { // Concatenate adjacent s if (_group.type === 'mtext' && lastGroup.type === 'mtext' && _group.getAttribute('mathvariant') === lastGroup.getAttribute('mathvariant')) { lastGroup.children.push(..._group.children); continue; // Concatenate adjacent s } else if (_group.type === 'mn' && lastGroup.type === 'mn') { lastGroup.children.push(..._group.children); continue; // Concatenate ... followed by . } else if (isNumberPunctuation(_group) && lastGroup.type === 'mn') { lastGroup.children.push(..._group.children); continue; // Concatenate . followed by ... } else if (_group.type === 'mn' && isNumberPunctuation(lastGroup)) { _group.children = [...lastGroup.children, ..._group.children]; groups.pop(); // Put preceding ... or . inside base of // ...base......exponent... (or ) } else if ((_group.type === 'msup' || _group.type === 'msub') && _group.children.length >= 1 && (lastGroup.type === 'mn' || isNumberPunctuation(lastGroup))) { var base = _group.children[0]; if (base instanceof MathNode && base.type === 'mn') { base.children = [...lastGroup.children, ...base.children]; groups.pop(); } // \not } else if (lastGroup.type === 'mi' && lastGroup.children.length === 1) { var lastChild = lastGroup.children[0]; if (lastChild instanceof TextNode && lastChild.text === '\u0338' && (_group.type === 'mo' || _group.type === 'mi' || _group.type === 'mn')) { var child = _group.children[0]; if (child instanceof TextNode && child.text.length > 0) { // Overlay with combining character long solidus child.text = child.text.slice(0, 1) + "\u0338" + child.text.slice(1); groups.pop(); } } } } groups.push(_group); lastGroup = _group; } return groups; }; /** * Equivalent to buildExpression, but wraps the elements in an * if there's more than one. Returns a single node instead of an array. */ var buildExpressionRow = function buildExpressionRow(expression, options, isOrdgroup) { return makeRow(buildExpression(expression, options, isOrdgroup)); }; /** * Takes a group from the parser and calls the appropriate groupBuilders function * on it to produce a MathML node. */ var buildGroup = function buildGroup(group, options) { if (!group) { return new mathMLTree.MathNode("mrow"); } if (_mathmlGroupBuilders[group.type]) { // Call the groupBuilders function // $FlowFixMe var result = _mathmlGroupBuilders[group.type](group, options); // $FlowFixMe return result; } 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 * tag so we can also include that TeX source as an annotation. * * Note that we actually return a domTree element with a `` inside it so * we can do appropriate styling. */ function buildMathML(tree, texExpression, options, isDisplayMode, forMathmlOnly) { var expression = buildExpression(tree, options); // TODO: Make a pass thru the MathML similar to buildHTML.traverseNonSpaceNodes // and add spacing nodes. This is necessary only adjacent to math operators // like \sin or \lim or to subsup elements that contain math operators. // MathML takes care of the other spacing issues. // Wrap up the expression in an mrow so it is presented in the semantics // tag correctly, unless it's a single or . var wrapper; if (expression.length === 1 && expression[0] instanceof MathNode && utils.contains(["mrow", "mtable"], expression[0].type)) { wrapper = expression[0]; } else { 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]); math.setAttribute("xmlns", "http://www.w3.org/1998/Math/MathML"); if (isDisplayMode) { math.setAttribute("display", "block"); } // You can't style nodes, so we wrap the node in a span. // NOTE: The span class is not typed to have nodes as children, and // we don't want to make the children type more generic since the children // of span are expected to have more fields in `buildHtml` contexts. var wrapperClass = forMathmlOnly ? "katex" : "katex-mathml"; // $FlowFixMe return buildCommon.makeSpan([wrapperClass], [math]); } var optionsFromSettings = function optionsFromSettings(settings) { return new Options({ style: settings.displayMode ? Style$1.DISPLAY : Style$1.TEXT, maxSize: settings.maxSize, minRuleThickness: settings.minRuleThickness }); }; var displayWrap = function displayWrap(node, settings) { if (settings.displayMode) { var classes = ["katex-display"]; if (settings.leqno) { classes.push("leqno"); } if (settings.fleqn) { classes.push("fleqn"); } node = buildCommon.makeSpan(classes, [node]); } return node; }; var buildTree = function buildTree(tree, expression, settings) { var options = optionsFromSettings(settings); var katexNode; if (settings.output === "mathml") { return buildMathML(tree, expression, options, settings.displayMode, true); } else if (settings.output === "html") { var htmlNode = buildHTML(tree, options); katexNode = buildCommon.makeSpan(["katex"], [htmlNode]); } else { var mathMLNode = buildMathML(tree, expression, options, settings.displayMode, false); var _htmlNode = buildHTML(tree, options); katexNode = buildCommon.makeSpan(["katex"], [mathMLNode, _htmlNode]); } return displayWrap(katexNode, settings); }; var buildHTMLTree = function buildHTMLTree(tree, expression, settings) { var options = optionsFromSettings(settings); var htmlNode = buildHTML(tree, options); var katexNode = buildCommon.makeSpan(["katex"], [htmlNode]); return displayWrap(katexNode, settings); }; /** * This file provides support to buildMathML.js and buildHTML.js * for stretchy wide elements rendered from SVG files * and other CSS trickery. */ var stretchyCodePoint = { widehat: "^", widecheck: "ˇ", widetilde: "~", utilde: "~", overleftarrow: "\u2190", underleftarrow: "\u2190", xleftarrow: "\u2190", overrightarrow: "\u2192", underrightarrow: "\u2192", xrightarrow: "\u2192", underbrace: "\u23df", overbrace: "\u23de", overgroup: "\u23e0", undergroup: "\u23e1", overleftrightarrow: "\u2194", underleftrightarrow: "\u2194", xleftrightarrow: "\u2194", Overrightarrow: "\u21d2", xRightarrow: "\u21d2", overleftharpoon: "\u21bc", xleftharpoonup: "\u21bc", overrightharpoon: "\u21c0", xrightharpoonup: "\u21c0", xLeftarrow: "\u21d0", xLeftrightarrow: "\u21d4", xhookleftarrow: "\u21a9", xhookrightarrow: "\u21aa", xmapsto: "\u21a6", xrightharpoondown: "\u21c1", xleftharpoondown: "\u21bd", xrightleftharpoons: "\u21cc", xleftrightharpoons: "\u21cb", xtwoheadleftarrow: "\u219e", xtwoheadrightarrow: "\u21a0", xlongequal: "=", xtofrom: "\u21c4", xrightleftarrows: "\u21c4", xrightequilibrium: "\u21cc", // Not a perfect match. xleftequilibrium: "\u21cb", // None better available. "\\cdrightarrow": "\u2192", "\\cdleftarrow": "\u2190", "\\cdlongequal": "=" }; var mathMLnode = function mathMLnode(label) { var node = new mathMLTree.MathNode("mo", [new mathMLTree.TextNode(stretchyCodePoint[label.replace(/^\\/, '')])]); node.setAttribute("stretchy", "true"); return node; }; // Many of the KaTeX SVG images have been adapted from glyphs in KaTeX fonts. // Copyright (c) 2009-2010, Design Science, Inc. () // Copyright (c) 2014-2017 Khan Academy () // Licensed under the SIL Open Font License, Version 1.1. // See \nhttp://scripts.sil.org/OFL // Very Long SVGs // Many of the KaTeX stretchy wide elements use a long SVG image and an // overflow: hidden tactic to achieve a stretchy image while avoiding // distortion of arrowheads or brace corners. // The SVG typically contains a very long (400 em) arrow. // The SVG is in a container span that has overflow: hidden, so the span // acts like a window that exposes only part of the SVG. // The SVG always has a longer, thinner aspect ratio than the container span. // After the SVG fills 100% of the height of the container span, // there is a long arrow shaft left over. That left-over shaft is not shown. // Instead, it is sliced off because the span's CSS has overflow: hidden. // Thus, the reader sees an arrow that matches the subject matter width // without distortion. // Some functions, such as \cancel, need to vary their aspect ratio. These // functions do not get the overflow SVG treatment. // Second Brush Stroke // Low resolution monitors struggle to display images in fine detail. // So browsers apply anti-aliasing. A long straight arrow shaft therefore // will sometimes appear as if it has a blurred edge. // To mitigate this, these SVG files contain a second "brush-stroke" on the // arrow shafts. That is, a second long thin rectangular SVG path has been // written directly on top of each arrow shaft. This reinforcement causes // some of the screen pixels to display as black instead of the anti-aliased // gray pixel that a single path would generate. So we get arrow shafts // whose edges appear to be sharper. // In the katexImagesData object just below, the dimensions all // correspond to path geometry inside the relevant SVG. // For example, \overrightarrow uses the same arrowhead as glyph U+2192 // from the KaTeX Main font. The scaling factor is 1000. // That is, inside the font, that arrowhead is 522 units tall, which // corresponds to 0.522 em inside the document. var katexImagesData = { // path(s), minWidth, height, align overrightarrow: [["rightarrow"], 0.888, 522, "xMaxYMin"], overleftarrow: [["leftarrow"], 0.888, 522, "xMinYMin"], underrightarrow: [["rightarrow"], 0.888, 522, "xMaxYMin"], underleftarrow: [["leftarrow"], 0.888, 522, "xMinYMin"], xrightarrow: [["rightarrow"], 1.469, 522, "xMaxYMin"], "\\cdrightarrow": [["rightarrow"], 3.0, 522, "xMaxYMin"], // CD minwwidth2.5pc xleftarrow: [["leftarrow"], 1.469, 522, "xMinYMin"], "\\cdleftarrow": [["leftarrow"], 3.0, 522, "xMinYMin"], Overrightarrow: [["doublerightarrow"], 0.888, 560, "xMaxYMin"], xRightarrow: [["doublerightarrow"], 1.526, 560, "xMaxYMin"], xLeftarrow: [["doubleleftarrow"], 1.526, 560, "xMinYMin"], overleftharpoon: [["leftharpoon"], 0.888, 522, "xMinYMin"], xleftharpoonup: [["leftharpoon"], 0.888, 522, "xMinYMin"], xleftharpoondown: [["leftharpoondown"], 0.888, 522, "xMinYMin"], overrightharpoon: [["rightharpoon"], 0.888, 522, "xMaxYMin"], xrightharpoonup: [["rightharpoon"], 0.888, 522, "xMaxYMin"], xrightharpoondown: [["rightharpoondown"], 0.888, 522, "xMaxYMin"], xlongequal: [["longequal"], 0.888, 334, "xMinYMin"], "\\cdlongequal": [["longequal"], 3.0, 334, "xMinYMin"], xtwoheadleftarrow: [["twoheadleftarrow"], 0.888, 334, "xMinYMin"], xtwoheadrightarrow: [["twoheadrightarrow"], 0.888, 334, "xMaxYMin"], overleftrightarrow: [["leftarrow", "rightarrow"], 0.888, 522], overbrace: [["leftbrace", "midbrace", "rightbrace"], 1.6, 548], underbrace: [["leftbraceunder", "midbraceunder", "rightbraceunder"], 1.6, 548], underleftrightarrow: [["leftarrow", "rightarrow"], 0.888, 522], xleftrightarrow: [["leftarrow", "rightarrow"], 1.75, 522], xLeftrightarrow: [["doubleleftarrow", "doublerightarrow"], 1.75, 560], xrightleftharpoons: [["leftharpoondownplus", "rightharpoonplus"], 1.75, 716], xleftrightharpoons: [["leftharpoonplus", "rightharpoondownplus"], 1.75, 716], xhookleftarrow: [["leftarrow", "righthook"], 1.08, 522], xhookrightarrow: [["lefthook", "rightarrow"], 1.08, 522], overlinesegment: [["leftlinesegment", "rightlinesegment"], 0.888, 522], underlinesegment: [["leftlinesegment", "rightlinesegment"], 0.888, 522], overgroup: [["leftgroup", "rightgroup"], 0.888, 342], undergroup: [["leftgroupunder", "rightgroupunder"], 0.888, 342], xmapsto: [["leftmapsto", "rightarrow"], 1.5, 522], xtofrom: [["leftToFrom", "rightToFrom"], 1.75, 528], // The next three arrows are from the mhchem package. // In mhchem.sty, min-length is 2.0em. But these arrows might appear in the // document as \xrightarrow or \xrightleftharpoons. Those have // min-length = 1.75em, so we set min-length on these next three to match. xrightleftarrows: [["baraboveleftarrow", "rightarrowabovebar"], 1.75, 901], xrightequilibrium: [["baraboveshortleftharpoon", "rightharpoonaboveshortbar"], 1.75, 716], xleftequilibrium: [["shortbaraboveleftharpoon", "shortrightharpoonabovebar"], 1.75, 716] }; var groupLength = function groupLength(arg) { if (arg.type === "ordgroup") { return arg.body.length; } else { return 1; } }; var svgSpan = function svgSpan(group, options) { // Create a span with inline SVG for the element. function buildSvgSpan_() { var viewBoxWidth = 400000; // default var label = group.label.slice(1); if (utils.contains(["widehat", "widecheck", "widetilde", "utilde"], label)) { // Each type in the `if` statement corresponds to one of the ParseNode // types below. This narrowing is required to access `grp.base`. // $FlowFixMe var grp = group; // There are four SVG images available for each function. // Choose a taller image when there are more characters. var numChars = groupLength(grp.base); var viewBoxHeight; var pathName; var _height; if (numChars > 5) { if (label === "widehat" || label === "widecheck") { viewBoxHeight = 420; viewBoxWidth = 2364; _height = 0.42; pathName = label + "4"; } else { viewBoxHeight = 312; viewBoxWidth = 2340; _height = 0.34; pathName = "tilde4"; } } else { var imgIndex = [1, 1, 2, 2, 3, 3][numChars]; if (label === "widehat" || label === "widecheck") { viewBoxWidth = [0, 1062, 2364, 2364, 2364][imgIndex]; viewBoxHeight = [0, 239, 300, 360, 420][imgIndex]; _height = [0, 0.24, 0.3, 0.3, 0.36, 0.42][imgIndex]; pathName = label + imgIndex; } else { viewBoxWidth = [0, 600, 1033, 2339, 2340][imgIndex]; viewBoxHeight = [0, 260, 286, 306, 312][imgIndex]; _height = [0, 0.26, 0.286, 0.3, 0.306, 0.34][imgIndex]; pathName = "tilde" + imgIndex; } } var path = new PathNode(pathName); var svgNode = new SvgNode([path], { "width": "100%", "height": makeEm(_height), "viewBox": "0 0 " + viewBoxWidth + " " + viewBoxHeight, "preserveAspectRatio": "none" }); return { span: buildCommon.makeSvgSpan([], [svgNode], options), minWidth: 0, height: _height }; } else { var spans = []; var data = katexImagesData[label]; var [paths, _minWidth, _viewBoxHeight] = data; var _height2 = _viewBoxHeight / 1000; var numSvgChildren = paths.length; var widthClasses; var aligns; if (numSvgChildren === 1) { // $FlowFixMe: All these cases must be of the 4-tuple type. var align1 = data[3]; widthClasses = ["hide-tail"]; aligns = [align1]; } else if (numSvgChildren === 2) { widthClasses = ["halfarrow-left", "halfarrow-right"]; aligns = ["xMinYMin", "xMaxYMin"]; } else if (numSvgChildren === 3) { widthClasses = ["brace-left", "brace-center", "brace-right"]; aligns = ["xMinYMin", "xMidYMin", "xMaxYMin"]; } else { throw new Error("Correct katexImagesData or update code here to support\n " + numSvgChildren + " children."); } for (var i = 0; i < numSvgChildren; i++) { var _path = new PathNode(paths[i]); var _svgNode = new SvgNode([_path], { "width": "400em", "height": makeEm(_height2), "viewBox": "0 0 " + viewBoxWidth + " " + _viewBoxHeight, "preserveAspectRatio": aligns[i] + " slice" }); var _span = buildCommon.makeSvgSpan([widthClasses[i]], [_svgNode], options); if (numSvgChildren === 1) { return { span: _span, minWidth: _minWidth, height: _height2 }; } else { _span.style.height = makeEm(_height2); spans.push(_span); } } return { span: buildCommon.makeSpan(["stretchy"], spans, options), minWidth: _minWidth, height: _height2 }; } } // buildSvgSpan_() var { span, minWidth, height } = buildSvgSpan_(); // Note that we are returning span.depth = 0. // Any adjustments relative to the baseline must be done in buildHTML. span.height = height; span.style.height = makeEm(height); if (minWidth > 0) { span.style.minWidth = makeEm(minWidth); } return span; }; var encloseSpan = function encloseSpan(inner, label, topPad, bottomPad, options) { // Return an image span for \cancel, \bcancel, \xcancel, \fbox, or \angl var img; var totalHeight = inner.height + inner.depth + topPad + bottomPad; if (/fbox|color|angl/.test(label)) { img = buildCommon.makeSpan(["stretchy", label], [], options); if (label === "fbox") { var color = options.color && options.getColor(); if (color) { img.style.borderColor = color; } } } else { // \cancel, \bcancel, or \xcancel // Since \cancel's SVG is inline and it omits the viewBox attribute, // its stroke-width will not vary with span area. var lines = []; if (/^[bx]cancel$/.test(label)) { lines.push(new LineNode({ "x1": "0", "y1": "0", "x2": "100%", "y2": "100%", "stroke-width": "0.046em" })); } if (/^x?cancel$/.test(label)) { lines.push(new LineNode({ "x1": "0", "y1": "100%", "x2": "100%", "y2": "0", "stroke-width": "0.046em" })); } var svgNode = new SvgNode(lines, { "width": "100%", "height": makeEm(totalHeight) }); img = buildCommon.makeSvgSpan([], [svgNode], options); } img.height = totalHeight; img.style.height = makeEm(totalHeight); return img; }; var stretchy = { encloseSpan, mathMLnode, svgSpan }; /** * Asserts that the node is of the given type and returns it with stricter * typing. Throws if the node's type does not match. */ function assertNodeType(node, type) { if (!node || node.type !== type) { throw new Error("Expected node of type " + type + ", but got " + (node ? "node of type " + node.type : String(node))); } // $FlowFixMe, >=0.125 return node; } /** * Returns the node more strictly typed iff it is of the given type. Otherwise, * returns null. */ function assertSymbolNodeType(node) { var typedNode = checkSymbolNodeType(node); if (!typedNode) { throw new Error("Expected node of symbol group type, but got " + (node ? "node of type " + node.type : String(node))); } return typedNode; } /** * Returns the node more strictly typed iff it is of the given type. Otherwise, * returns null. */ function checkSymbolNodeType(node) { if (node && (node.type === "atom" || NON_ATOMS.hasOwnProperty(node.type))) { // $FlowFixMe return node; } return null; } // NOTE: Unlike most `htmlBuilder`s, this one handles not only "accent", but // also "supsub" since an accent can affect super/subscripting. var htmlBuilder$a = (grp, options) => { // Accents are handled in the TeXbook pg. 443, rule 12. var base; var group; var supSubGroup; if (grp && grp.type === "supsub") { // If our base is a character box, and we have superscripts and // subscripts, the supsub will defer to us. In particular, we want // to attach the superscripts and subscripts to the inner body (so // that the position of the superscripts and subscripts won't be // affected by the height of the accent). We accomplish this by // sticking the base of the accent into the base of the supsub, and // rendering that, while keeping track of where the accent is. // The real accent group is the base of the supsub group group = assertNodeType(grp.base, "accent"); // The character box is the base of the accent group base = group.base; // Stick the character box into the base of the supsub group grp.base = base; // Rerender the supsub group with its new base, and store that // result. supSubGroup = assertSpan(buildGroup$1(grp, options)); // reset original base grp.base = group; } else { group = assertNodeType(grp, "accent"); base = group.base; } // Build the base group var body = buildGroup$1(base, options.havingCrampedStyle()); // Does the accent need to shift for the skew of a character? var mustShift = group.isShifty && utils.isCharacterBox(base); // Calculate the skew of the accent. This is based on the line "If the // nucleus is not a single character, let s = 0; otherwise set s to the // kern amount for the nucleus followed by the \skewchar of its font." // Note that our skew metrics are just the kern between each character // and the skewchar. var skew = 0; if (mustShift) { // If the base is a character box, then we want the skew of the // innermost character. To do that, we find the innermost character: var baseChar = utils.getBaseElem(base); // Then, we render its group to get the symbol inside it var baseGroup = buildGroup$1(baseChar, options.havingCrampedStyle()); // Finally, we pull the skew off of the symbol. skew = assertSymbolDomNode(baseGroup).skew; // Note that we now throw away baseGroup, because the layers we // removed with getBaseElem might contain things like \color which // we can't get rid of. // TODO(emily): Find a better way to get the skew } var accentBelow = group.label === "\\c"; // calculate the amount of space between the body and the accent var clearance = accentBelow ? body.height + body.depth : Math.min(body.height, options.fontMetrics().xHeight); // Build the accent var accentBody; if (!group.isStretchy) { var accent; var width; if (group.label === "\\vec") { // Before version 0.9, \vec used the combining font glyph U+20D7. // But browsers, especially Safari, are not consistent in how they // render combining characters when not preceded by a character. // So now we use an SVG. // If Safari reforms, we should consider reverting to the glyph. accent = buildCommon.staticSvg("vec", options); width = buildCommon.svgData.vec[1]; } else { accent = buildCommon.makeOrd({ mode: group.mode, text: group.label }, options, "textord"); accent = assertSymbolDomNode(accent); // Remove the italic correction of the accent, because it only serves to // shift the accent over to a place we don't want. accent.italic = 0; width = accent.width; if (accentBelow) { clearance += accent.depth; } } accentBody = buildCommon.makeSpan(["accent-body"], [accent]); // "Full" accents expand the width of the resulting symbol to be // at least the width of the accent, and overlap directly onto the // character without any vertical offset. var accentFull = group.label === "\\textcircled"; if (accentFull) { accentBody.classes.push('accent-full'); clearance = body.height; } // Shift the accent over by the skew. var left = skew; // CSS defines `.katex .accent .accent-body:not(.accent-full) { width: 0 }` // so that the accent doesn't contribute to the bounding box. // We need to shift the character by its width (effectively half // its width) to compensate. if (!accentFull) { left -= width / 2; } accentBody.style.left = makeEm(left); // \textcircled uses the \bigcirc glyph, so it needs some // vertical adjustment to match LaTeX. if (group.label === "\\textcircled") { accentBody.style.top = ".2em"; } accentBody = buildCommon.makeVList({ positionType: "firstBaseline", children: [{ type: "elem", elem: body }, { type: "kern", size: -clearance }, { type: "elem", elem: accentBody }] }, options); } else { accentBody = stretchy.svgSpan(group, options); accentBody = buildCommon.makeVList({ positionType: "firstBaseline", children: [{ type: "elem", elem: body }, { type: "elem", elem: accentBody, wrapperClasses: ["svg-align"], wrapperStyle: skew > 0 ? { width: "calc(100% - " + makeEm(2 * skew) + ")", marginLeft: makeEm(2 * skew) } : undefined }] }, options); } var accentWrap = buildCommon.makeSpan(["mord", "accent"], [accentBody], options); if (supSubGroup) { // Here, we replace the "base" child of the supsub with our newly // generated accent. supSubGroup.children[0] = accentWrap; // Since we don't rerun the height calculation after replacing the // accent, we manually recalculate height. supSubGroup.height = Math.max(accentWrap.height, supSubGroup.height); // Accents should always be ords, even when their innards are not. supSubGroup.classes[0] = "mord"; return supSubGroup; } else { return accentWrap; } }; var mathmlBuilder$9 = (group, options) => { var accentNode = group.isStretchy ? stretchy.mathMLnode(group.label) : new mathMLTree.MathNode("mo", [makeText(group.label, group.mode)]); var node = new mathMLTree.MathNode("mover", [buildGroup(group.base, options), accentNode]); node.setAttribute("accent", "true"); return node; }; var NON_STRETCHY_ACCENT_REGEX = new RegExp(["\\acute", "\\grave", "\\ddot", "\\tilde", "\\bar", "\\breve", "\\check", "\\hat", "\\vec", "\\dot", "\\mathring"].map(accent => "\\" + accent).join("|")); // Accents defineFunction({ type: "accent", names: ["\\acute", "\\grave", "\\ddot", "\\tilde", "\\bar", "\\breve", "\\check", "\\hat", "\\vec", "\\dot", "\\mathring", "\\widecheck", "\\widehat", "\\widetilde", "\\overrightarrow", "\\overleftarrow", "\\Overrightarrow", "\\overleftrightarrow", "\\overgroup", "\\overlinesegment", "\\overleftharpoon", "\\overrightharpoon"], props: { numArgs: 1 }, handler: (context, args) => { var base = normalizeArgument(args[0]); var isStretchy = !NON_STRETCHY_ACCENT_REGEX.test(context.funcName); var isShifty = !isStretchy || context.funcName === "\\widehat" || context.funcName === "\\widetilde" || context.funcName === "\\widecheck"; return { type: "accent", mode: context.parser.mode, label: context.funcName, isStretchy: isStretchy, isShifty: isShifty, base: base }; }, htmlBuilder: htmlBuilder$a, mathmlBuilder: mathmlBuilder$9 }); // Text-mode accents defineFunction({ type: "accent", names: ["\\'", "\\`", "\\^", "\\~", "\\=", "\\u", "\\.", '\\"', "\\c", "\\r", "\\H", "\\v", "\\textcircled"], props: { numArgs: 1, allowedInText: true, allowedInMath: true, // unless in strict mode argTypes: ["primitive"] }, handler: (context, args) => { var base = args[0]; var mode = context.parser.mode; if (mode === "math") { context.parser.settings.reportNonstrict("mathVsTextAccents", "LaTeX's accent " + context.funcName + " works only in text mode"); mode = "text"; } return { type: "accent", mode: mode, label: context.funcName, isStretchy: false, isShifty: true, base: base }; }, htmlBuilder: htmlBuilder$a, mathmlBuilder: mathmlBuilder$9 }); // Horizontal overlap functions defineFunction({ type: "accentUnder", names: ["\\underleftarrow", "\\underrightarrow", "\\underleftrightarrow", "\\undergroup", "\\underlinesegment", "\\utilde"], props: { numArgs: 1 }, handler: (_ref, args) => { var { parser, funcName } = _ref; var base = args[0]; return { type: "accentUnder", mode: parser.mode, label: funcName, base: base }; }, htmlBuilder: (group, options) => { // Treat under accents much like underlines. var innerGroup = buildGroup$1(group.base, options); var accentBody = stretchy.svgSpan(group, options); var kern = group.label === "\\utilde" ? 0.12 : 0; // Generate the vlist, with the appropriate kerns var vlist = buildCommon.makeVList({ positionType: "top", positionData: innerGroup.height, children: [{ type: "elem", elem: accentBody, wrapperClasses: ["svg-align"] }, { type: "kern", size: kern }, { type: "elem", elem: innerGroup }] }, options); return buildCommon.makeSpan(["mord", "accentunder"], [vlist], options); }, mathmlBuilder: (group, options) => { var accentNode = stretchy.mathMLnode(group.label); var node = new mathMLTree.MathNode("munder", [buildGroup(group.base, options), accentNode]); node.setAttribute("accentunder", "true"); return node; } }); // Helper function var paddedNode = group => { var node = new mathMLTree.MathNode("mpadded", group ? [group] : []); node.setAttribute("width", "+0.6em"); node.setAttribute("lspace", "0.3em"); return node; }; // Stretchy arrows with an optional argument defineFunction({ type: "xArrow", names: ["\\xleftarrow", "\\xrightarrow", "\\xLeftarrow", "\\xRightarrow", "\\xleftrightarrow", "\\xLeftrightarrow", "\\xhookleftarrow", "\\xhookrightarrow", "\\xmapsto", "\\xrightharpoondown", "\\xrightharpoonup", "\\xleftharpoondown", "\\xleftharpoonup", "\\xrightleftharpoons", "\\xleftrightharpoons", "\\xlongequal", "\\xtwoheadrightarrow", "\\xtwoheadleftarrow", "\\xtofrom", // The next 3 functions are here to support the mhchem extension. // Direct use of these functions is discouraged and may break someday. "\\xrightleftarrows", "\\xrightequilibrium", "\\xleftequilibrium", // The next 3 functions are here only to support the {CD} environment. "\\\\cdrightarrow", "\\\\cdleftarrow", "\\\\cdlongequal"], props: { numArgs: 1, numOptionalArgs: 1 }, handler(_ref, args, optArgs) { var { parser, funcName } = _ref; return { type: "xArrow", mode: parser.mode, label: funcName, body: args[0], below: optArgs[0] }; }, // Flow is unable to correctly infer the type of `group`, even though it's // unambiguously determined from the passed-in `type` above. htmlBuilder(group, options) { var style = options.style; // Build the argument groups in the appropriate style. // Ref: amsmath.dtx: \hbox{$\scriptstyle\mkern#3mu{#6}\mkern#4mu$}% // Some groups can return document fragments. Handle those by wrapping // them in a span. var newOptions = options.havingStyle(style.sup()); var upperGroup = buildCommon.wrapFragment(buildGroup$1(group.body, newOptions, options), options); var arrowPrefix = group.label.slice(0, 2) === "\\x" ? "x" : "cd"; upperGroup.classes.push(arrowPrefix + "-arrow-pad"); var lowerGroup; if (group.below) { // Build the lower group newOptions = options.havingStyle(style.sub()); lowerGroup = buildCommon.wrapFragment(buildGroup$1(group.below, newOptions, options), options); lowerGroup.classes.push(arrowPrefix + "-arrow-pad"); } var arrowBody = stretchy.svgSpan(group, options); // Re shift: Note that stretchy.svgSpan returned arrowBody.depth = 0. // The point we want on the math axis is at 0.5 * arrowBody.height. var arrowShift = -options.fontMetrics().axisHeight + 0.5 * arrowBody.height; // 2 mu kern. Ref: amsmath.dtx: #7\if0#2\else\mkern#2mu\fi var upperShift = -options.fontMetrics().axisHeight - 0.5 * arrowBody.height - 0.111; // 0.111 em = 2 mu if (upperGroup.depth > 0.25 || group.label === "\\xleftequilibrium") { upperShift -= upperGroup.depth; // shift up if depth encroaches } // Generate the vlist var vlist; if (lowerGroup) { var lowerShift = -options.fontMetrics().axisHeight + lowerGroup.height + 0.5 * arrowBody.height + 0.111; vlist = buildCommon.makeVList({ positionType: "individualShift", children: [{ type: "elem", elem: upperGroup, shift: upperShift }, { type: "elem", elem: arrowBody, shift: arrowShift }, { type: "elem", elem: lowerGroup, shift: lowerShift }] }, options); } else { vlist = buildCommon.makeVList({ positionType: "individualShift", children: [{ type: "elem", elem: upperGroup, shift: upperShift }, { type: "elem", elem: arrowBody, shift: arrowShift }] }, options); } // $FlowFixMe: Replace this with passing "svg-align" into makeVList. vlist.children[0].children[0].children[1].classes.push("svg-align"); return buildCommon.makeSpan(["mrel", "x-arrow"], [vlist], options); }, mathmlBuilder(group, options) { var arrowNode = stretchy.mathMLnode(group.label); arrowNode.setAttribute("minsize", group.label.charAt(0) === "x" ? "1.75em" : "3.0em"); var node; if (group.body) { var upperNode = paddedNode(buildGroup(group.body, options)); if (group.below) { var lowerNode = paddedNode(buildGroup(group.below, options)); node = new mathMLTree.MathNode("munderover", [arrowNode, lowerNode, upperNode]); } else { node = new mathMLTree.MathNode("mover", [arrowNode, upperNode]); } } else if (group.below) { var _lowerNode = paddedNode(buildGroup(group.below, options)); node = new mathMLTree.MathNode("munder", [arrowNode, _lowerNode]); } else { // This should never happen. // Parser.js throws an error if there is no argument. node = paddedNode(); node = new mathMLTree.MathNode("mover", [arrowNode, node]); } return node; } }); var makeSpan = buildCommon.makeSpan; function htmlBuilder$9(group, options) { var elements = buildExpression$1(group.body, options, true); return makeSpan([group.mclass], elements, options); } function mathmlBuilder$8(group, options) { var node; var inner = buildExpression(group.body, options); if (group.mclass === "minner") { node = new mathMLTree.MathNode("mpadded", inner); } else if (group.mclass === "mord") { if (group.isCharacterBox) { node = inner[0]; node.type = "mi"; } else { node = new mathMLTree.MathNode("mi", inner); } } else { if (group.isCharacterBox) { node = inner[0]; node.type = "mo"; } else { node = new mathMLTree.MathNode("mo", inner); } // Set spacing based on what is the most likely adjacent atom type. // See TeXbook p170. if (group.mclass === "mbin") { node.attributes.lspace = "0.22em"; // medium space node.attributes.rspace = "0.22em"; } else if (group.mclass === "mpunct") { node.attributes.lspace = "0em"; node.attributes.rspace = "0.17em"; // thinspace } else if (group.mclass === "mopen" || group.mclass === "mclose") { node.attributes.lspace = "0em"; node.attributes.rspace = "0em"; } else if (group.mclass === "minner") { node.attributes.lspace = "0.0556em"; // 1 mu is the most likely option node.attributes.width = "+0.1111em"; } // MathML default space is 5/18 em, so needs no action. // Ref: https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mo } return node; } // Math class commands except \mathop defineFunction({ type: "mclass", names: ["\\mathord", "\\mathbin", "\\mathrel", "\\mathopen", "\\mathclose", "\\mathpunct", "\\mathinner"], props: { numArgs: 1, primitive: true }, handler(_ref, args) { var { parser, funcName } = _ref; var body = args[0]; return { type: "mclass", mode: parser.mode, mclass: "m" + funcName.slice(5), // TODO(kevinb): don't prefix with 'm' body: ordargument(body), isCharacterBox: utils.isCharacterBox(body) }; }, htmlBuilder: htmlBuilder$9, mathmlBuilder: mathmlBuilder$8 }); var binrelClass = arg => { // \binrel@ spacing varies with (bin|rel|ord) of the atom in the argument. // (by rendering separately and with {}s before and after, and measuring // the change in spacing). We'll do roughly the same by detecting the // atom type directly. var atom = arg.type === "ordgroup" && arg.body.length ? arg.body[0] : arg; if (atom.type === "atom" && (atom.family === "bin" || atom.family === "rel")) { return "m" + atom.family; } else { return "mord"; } }; // \@binrel{x}{y} renders like y but as mbin/mrel/mord if x is mbin/mrel/mord. // This is equivalent to \binrel@{x}\binrel@@{y} in AMSTeX. defineFunction({ type: "mclass", names: ["\\@binrel"], props: { numArgs: 2 }, handler(_ref2, args) { var { parser } = _ref2; return { type: "mclass", mode: parser.mode, mclass: binrelClass(args[0]), body: ordargument(args[1]), isCharacterBox: utils.isCharacterBox(args[1]) }; } }); // Build a relation or stacked op by placing one symbol on top of another defineFunction({ type: "mclass", names: ["\\stackrel", "\\overset", "\\underset"], props: { numArgs: 2 }, handler(_ref3, args) { var { parser, funcName } = _ref3; var baseArg = args[1]; var shiftedArg = args[0]; var mclass; if (funcName !== "\\stackrel") { // LaTeX applies \binrel spacing to \overset and \underset. mclass = binrelClass(baseArg); } else { mclass = "mrel"; // for \stackrel } var baseOp = { type: "op", mode: baseArg.mode, limits: true, alwaysHandleSupSub: true, parentIsSupSub: false, symbol: false, suppressBaseShift: funcName !== "\\stackrel", body: ordargument(baseArg) }; var supsub = { type: "supsub", mode: shiftedArg.mode, base: baseOp, sup: funcName === "\\underset" ? null : shiftedArg, sub: funcName === "\\underset" ? shiftedArg : null }; return { type: "mclass", mode: parser.mode, mclass, body: [supsub], isCharacterBox: utils.isCharacterBox(supsub) }; }, htmlBuilder: htmlBuilder$9, mathmlBuilder: mathmlBuilder$8 }); // \pmb is a simulation of bold font. // The version of \pmb in ambsy.sty works by typesetting three copies // with small offsets. We use CSS text-shadow. // It's a hack. Not as good as a real bold font. Better than nothing. defineFunction({ type: "pmb", names: ["\\pmb"], props: { numArgs: 1, allowedInText: true }, handler(_ref, args) { var { parser } = _ref; return { type: "pmb", mode: parser.mode, mclass: binrelClass(args[0]), body: ordargument(args[0]) }; }, htmlBuilder(group, options) { var elements = buildExpression$1(group.body, options, true); var node = buildCommon.makeSpan([group.mclass], elements, options); node.style.textShadow = "0.02em 0.01em 0.04px"; return node; }, mathmlBuilder(group, style) { var inner = buildExpression(group.body, style); // Wrap with an element. var node = new mathMLTree.MathNode("mstyle", inner); node.setAttribute("style", "text-shadow: 0.02em 0.01em 0.04px"); return node; } }); var cdArrowFunctionName = { ">": "\\\\cdrightarrow", "<": "\\\\cdleftarrow", "=": "\\\\cdlongequal", "A": "\\uparrow", "V": "\\downarrow", "|": "\\Vert", ".": "no arrow" }; var newCell = () => { // Create an empty cell, to be filled below with parse nodes. // The parseTree from this module must be constructed like the // one created by parseArray(), so an empty CD cell must // be a ParseNode<"styling">. And CD is always displaystyle. // So these values are fixed and flow can do implicit typing. return { type: "styling", body: [], mode: "math", style: "display" }; }; var isStartOfArrow = node => { return node.type === "textord" && node.text === "@"; }; var isLabelEnd = (node, endChar) => { return (node.type === "mathord" || node.type === "atom") && node.text === endChar; }; function cdArrow(arrowChar, labels, parser) { // Return a parse tree of an arrow and its labels. // This acts in a way similar to a macro expansion. var funcName = cdArrowFunctionName[arrowChar]; switch (funcName) { case "\\\\cdrightarrow": case "\\\\cdleftarrow": return parser.callFunction(funcName, [labels[0]], [labels[1]]); case "\\uparrow": case "\\downarrow": { var leftLabel = parser.callFunction("\\\\cdleft", [labels[0]], []); var bareArrow = { type: "atom", text: funcName, mode: "math", family: "rel" }; var sizedArrow = parser.callFunction("\\Big", [bareArrow], []); var rightLabel = parser.callFunction("\\\\cdright", [labels[1]], []); var arrowGroup = { type: "ordgroup", mode: "math", body: [leftLabel, sizedArrow, rightLabel] }; return parser.callFunction("\\\\cdparent", [arrowGroup], []); } case "\\\\cdlongequal": return parser.callFunction("\\\\cdlongequal", [], []); case "\\Vert": { var arrow = { type: "textord", text: "\\Vert", mode: "math" }; return parser.callFunction("\\Big", [arrow], []); } default: return { type: "textord", text: " ", mode: "math" }; } } function parseCD(parser) { // Get the array's parse nodes with \\ temporarily mapped to \cr. var parsedRows = []; parser.gullet.beginGroup(); parser.gullet.macros.set("\\cr", "\\\\\\relax"); parser.gullet.beginGroup(); while (true) { // eslint-disable-line no-constant-condition // Get the parse nodes for the next row. parsedRows.push(parser.parseExpression(false, "\\\\")); parser.gullet.endGroup(); parser.gullet.beginGroup(); var next = parser.fetch().text; if (next === "&" || next === "\\\\") { parser.consume(); } else if (next === "\\end") { if (parsedRows[parsedRows.length - 1].length === 0) { parsedRows.pop(); // final row ended in \\ } break; } else { throw new ParseError("Expected \\\\ or \\cr or \\end", parser.nextToken); } } var row = []; var body = [row]; // Loop thru the parse nodes. Collect them into cells and arrows. for (var i = 0; i < parsedRows.length; i++) { // Start a new row. var rowNodes = parsedRows[i]; // Create the first cell. var cell = newCell(); for (var j = 0; j < rowNodes.length; j++) { if (!isStartOfArrow(rowNodes[j])) { // If a parseNode is not an arrow, it goes into a cell. cell.body.push(rowNodes[j]); } else { // Parse node j is an "@", the start of an arrow. // Before starting on the arrow, push the cell into `row`. row.push(cell); // Now collect parseNodes into an arrow. // The character after "@" defines the arrow type. j += 1; var arrowChar = assertSymbolNodeType(rowNodes[j]).text; // Create two empty label nodes. We may or may not use them. var labels = new Array(2); labels[0] = { type: "ordgroup", mode: "math", body: [] }; labels[1] = { type: "ordgroup", mode: "math", body: [] }; // Process the arrow. if ("=|.".indexOf(arrowChar) > -1) ; else if ("<>AV".indexOf(arrowChar) > -1) { // Four arrows, `@>>>`, `@<<<`, `@AAA`, and `@VVV`, each take // two optional labels. E.g. the right-point arrow syntax is // really: @>{optional label}>{optional label}> // Collect parseNodes into labels. for (var labelNum = 0; labelNum < 2; labelNum++) { var inLabel = true; for (var k = j + 1; k < rowNodes.length; k++) { if (isLabelEnd(rowNodes[k], arrowChar)) { inLabel = false; j = k; break; } if (isStartOfArrow(rowNodes[k])) { throw new ParseError("Missing a " + arrowChar + " character to complete a CD arrow.", rowNodes[k]); } labels[labelNum].body.push(rowNodes[k]); } if (inLabel) { // isLabelEnd never returned a true. throw new ParseError("Missing a " + arrowChar + " character to complete a CD arrow.", rowNodes[j]); } } } else { throw new ParseError("Expected one of \"<>AV=|.\" after @", rowNodes[j]); } // Now join the arrow to its labels. var arrow = cdArrow(arrowChar, labels, parser); // Wrap the arrow in ParseNode<"styling">. // This is done to match parseArray() behavior. var wrappedArrow = { type: "styling", body: [arrow], mode: "math", style: "display" // CD is always displaystyle. }; row.push(wrappedArrow); // In CD's syntax, cells are implicit. That is, everything that // is not an arrow gets collected into a cell. So create an empty // cell now. It will collect upcoming parseNodes. cell = newCell(); } } if (i % 2 === 0) { // Even-numbered rows consist of: cell, arrow, cell, arrow, ... cell // The last cell is not yet pushed into `row`, so: row.push(cell); } else { // Odd-numbered rows consist of: vert arrow, empty cell, ... vert arrow // Remove the empty cell that was placed at the beginning of `row`. row.shift(); } row = []; body.push(row); } // End row group parser.gullet.endGroup(); // End array group defining \\ parser.gullet.endGroup(); // define column separation. var cols = new Array(body[0].length).fill({ type: "align", align: "c", pregap: 0.25, // CD package sets \enskip between columns. postgap: 0.25 // So pre and post each get half an \enskip, i.e. 0.25em. }); return { type: "array", mode: "math", body, arraystretch: 1, addJot: true, rowGaps: [null], cols, colSeparationType: "CD", hLinesBeforeRow: new Array(body.length + 1).fill([]) }; } // The functions below are not available for general use. // They are here only for internal use by the {CD} environment in placing labels // next to vertical arrows. // We don't need any such functions for horizontal arrows because we can reuse // the functionality that already exists for extensible arrows. defineFunction({ type: "cdlabel", names: ["\\\\cdleft", "\\\\cdright"], props: { numArgs: 1 }, handler(_ref, args) { var { parser, funcName } = _ref; return { type: "cdlabel", mode: parser.mode, side: funcName.slice(4), label: args[0] }; }, htmlBuilder(group, options) { var newOptions = options.havingStyle(options.style.sup()); var label = buildCommon.wrapFragment(buildGroup$1(group.label, newOptions, options), options); label.classes.push("cd-label-" + group.side); label.style.bottom = makeEm(0.8 - label.depth); // Zero out label height & depth, so vertical align of arrow is set // by the arrow height, not by the label. label.height = 0; label.depth = 0; return label; }, mathmlBuilder(group, options) { var label = new mathMLTree.MathNode("mrow", [buildGroup(group.label, options)]); label = new mathMLTree.MathNode("mpadded", [label]); label.setAttribute("width", "0"); if (group.side === "left") { label.setAttribute("lspace", "-1width"); } // We have to guess at vertical alignment. We know the arrow is 1.8em tall, // But we don't know the height or depth of the label. label.setAttribute("voffset", "0.7em"); label = new mathMLTree.MathNode("mstyle", [label]); label.setAttribute("displaystyle", "false"); label.setAttribute("scriptlevel", "1"); return label; } }); defineFunction({ type: "cdlabelparent", names: ["\\\\cdparent"], props: { numArgs: 1 }, handler(_ref2, args) { var { parser } = _ref2; return { type: "cdlabelparent", mode: parser.mode, fragment: args[0] }; }, htmlBuilder(group, options) { // Wrap the vertical arrow and its labels. // The parent gets position: relative. The child gets position: absolute. // So CSS can locate the label correctly. var parent = buildCommon.wrapFragment(buildGroup$1(group.fragment, options), options); parent.classes.push("cd-vert-arrow"); return parent; }, mathmlBuilder(group, options) { return new mathMLTree.MathNode("mrow", [buildGroup(group.fragment, options)]); } }); // {123} and converts into symbol with code 123. It is used by the *macro* // \char defined in macros.js. defineFunction({ type: "textord", names: ["\\@char"], props: { numArgs: 1, allowedInText: true }, handler(_ref, args) { var { parser } = _ref; var arg = assertNodeType(args[0], "ordgroup"); var group = arg.body; var number = ""; for (var i = 0; i < group.length; i++) { var node = assertNodeType(group[i], "textord"); number += node.text; } var code = parseInt(number); var text; if (isNaN(code)) { throw new ParseError("\\@char has non-numeric argument " + number); // If we drop IE support, the following code could be replaced with // text = String.fromCodePoint(code) } else if (code < 0 || code >= 0x10ffff) { throw new ParseError("\\@char with invalid code point " + number); } else if (code <= 0xffff) { text = String.fromCharCode(code); } else { // Astral code point; split into surrogate halves code -= 0x10000; text = String.fromCharCode((code >> 10) + 0xd800, (code & 0x3ff) + 0xdc00); } return { type: "textord", mode: parser.mode, text: text }; } }); var htmlBuilder$8 = (group, options) => { var elements = buildExpression$1(group.body, options.withColor(group.color), false); // \color isn't supposed to affect the type of the elements it contains. // To accomplish this, we wrap the results in a fragment, so the inner // elements will be able to directly interact with their neighbors. For // example, `\color{red}{2 +} 3` has the same spacing as `2 + 3` return buildCommon.makeFragment(elements); }; var mathmlBuilder$7 = (group, options) => { var inner = buildExpression(group.body, options.withColor(group.color)); var node = new mathMLTree.MathNode("mstyle", inner); node.setAttribute("mathcolor", group.color); return node; }; defineFunction({ type: "color", names: ["\\textcolor"], props: { numArgs: 2, allowedInText: true, argTypes: ["color", "original"] }, handler(_ref, args) { var { parser } = _ref; var color = assertNodeType(args[0], "color-token").color; var body = args[1]; return { type: "color", mode: parser.mode, color, body: ordargument(body) }; }, htmlBuilder: htmlBuilder$8, mathmlBuilder: mathmlBuilder$7 }); defineFunction({ type: "color", names: ["\\color"], props: { numArgs: 1, allowedInText: true, argTypes: ["color"] }, handler(_ref2, args) { var { parser, breakOnTokenText } = _ref2; var color = assertNodeType(args[0], "color-token").color; // Set macro \current@color in current namespace to store the current // color, mimicking the behavior of color.sty. // This is currently used just to correctly color a \right // that follows a \color command. parser.gullet.macros.set("\\current@color", color); // Parse out the implicit body that should be colored. var body = parser.parseExpression(true, breakOnTokenText); return { type: "color", mode: parser.mode, color, body }; }, htmlBuilder: htmlBuilder$8, mathmlBuilder: mathmlBuilder$7 }); // Row breaks within tabular environments, and line breaks at top level defineFunction({ type: "cr", names: ["\\\\"], props: { numArgs: 0, numOptionalArgs: 0, allowedInText: true }, handler(_ref, args, optArgs) { var { parser } = _ref; var size = parser.gullet.future().text === "[" ? parser.parseSizeGroup(true) : null; var newLine = !parser.settings.displayMode || !parser.settings.useStrictBehavior("newLineInDisplayMode", "In LaTeX, \\\\ or \\newline " + "does nothing in display mode"); return { type: "cr", mode: parser.mode, newLine, size: size && assertNodeType(size, "size").value }; }, // The following builders are called only at the top level, // not within tabular/array environments. htmlBuilder(group, options) { var span = buildCommon.makeSpan(["mspace"], [], options); if (group.newLine) { span.classes.push("newline"); if (group.size) { span.style.marginTop = makeEm(calculateSize(group.size, options)); } } return span; }, mathmlBuilder(group, options) { var node = new mathMLTree.MathNode("mspace"); if (group.newLine) { node.setAttribute("linebreak", "newline"); if (group.size) { node.setAttribute("height", makeEm(calculateSize(group.size, options))); } } return node; } }); var globalMap = { "\\global": "\\global", "\\long": "\\\\globallong", "\\\\globallong": "\\\\globallong", "\\def": "\\gdef", "\\gdef": "\\gdef", "\\edef": "\\xdef", "\\xdef": "\\xdef", "\\let": "\\\\globallet", "\\futurelet": "\\\\globalfuture" }; var checkControlSequence = tok => { var name = tok.text; if (/^(?:[\\{}$&#^_]|EOF)$/.test(name)) { throw new ParseError("Expected a control sequence", tok); } return name; }; var getRHS = parser => { var tok = parser.gullet.popToken(); if (tok.text === "=") { // consume optional equals tok = parser.gullet.popToken(); if (tok.text === " ") { // consume one optional space tok = parser.gullet.popToken(); } } return tok; }; var letCommand = (parser, name, tok, global) => { var macro = parser.gullet.macros.get(tok.text); if (macro == null) { // don't expand it later even if a macro with the same name is defined // e.g., \let\foo=\frac \def\frac{\relax} \frac12 tok.noexpand = true; macro = { tokens: [tok], numArgs: 0, // reproduce the same behavior in expansion unexpandable: !parser.gullet.isExpandable(tok.text) }; } parser.gullet.macros.set(name, macro, global); }; // -> | // -> |\global // -> | // -> \global|\long|\outer defineFunction({ type: "internal", names: ["\\global", "\\long", "\\\\globallong" // can’t be entered directly ], props: { numArgs: 0, allowedInText: true }, handler(_ref) { var { parser, funcName } = _ref; parser.consumeSpaces(); var token = parser.fetch(); if (globalMap[token.text]) { // KaTeX doesn't have \par, so ignore \long if (funcName === "\\global" || funcName === "\\\\globallong") { token.text = globalMap[token.text]; } return assertNodeType(parser.parseFunction(), "internal"); } throw new ParseError("Invalid token after macro prefix", token); } }); // Basic support for macro definitions: \def, \gdef, \edef, \xdef // -> // -> \def|\gdef|\edef|\xdef // -> defineFunction({ type: "internal", names: ["\\def", "\\gdef", "\\edef", "\\xdef"], props: { numArgs: 0, allowedInText: true, primitive: true }, handler(_ref2) { var { parser, funcName } = _ref2; var tok = parser.gullet.popToken(); var name = tok.text; if (/^(?:[\\{}$&#^_]|EOF)$/.test(name)) { throw new ParseError("Expected a control sequence", tok); } var numArgs = 0; var insert; var delimiters = [[]]; // contains no braces while (parser.gullet.future().text !== "{") { tok = parser.gullet.popToken(); if (tok.text === "#") { // If the very last character of the is #, so that // this # is immediately followed by {, TeX will behave as if the { // had been inserted at the right end of both the parameter text // and the replacement text. if (parser.gullet.future().text === "{") { insert = parser.gullet.future(); delimiters[numArgs].push("{"); break; } // A parameter, the first appearance of # must be followed by 1, // the next by 2, and so on; up to nine #’s are allowed tok = parser.gullet.popToken(); if (!/^[1-9]$/.test(tok.text)) { throw new ParseError("Invalid argument number \"" + tok.text + "\""); } if (parseInt(tok.text) !== numArgs + 1) { throw new ParseError("Argument number \"" + tok.text + "\" out of order"); } numArgs++; delimiters.push([]); } else if (tok.text === "EOF") { throw new ParseError("Expected a macro definition"); } else { delimiters[numArgs].push(tok.text); } } // replacement text, enclosed in '{' and '}' and properly nested var { tokens } = parser.gullet.consumeArg(); if (insert) { tokens.unshift(insert); } if (funcName === "\\edef" || funcName === "\\xdef") { tokens = parser.gullet.expandTokens(tokens); tokens.reverse(); // to fit in with stack order } // Final arg is the expansion of the macro parser.gullet.macros.set(name, { tokens, numArgs, delimiters }, funcName === globalMap[funcName]); return { type: "internal", mode: parser.mode }; } }); // -> // -> \futurelet // | \let // -> |= defineFunction({ type: "internal", names: ["\\let", "\\\\globallet" // can’t be entered directly ], props: { numArgs: 0, allowedInText: true, primitive: true }, handler(_ref3) { var { parser, funcName } = _ref3; var name = checkControlSequence(parser.gullet.popToken()); parser.gullet.consumeSpaces(); var tok = getRHS(parser); letCommand(parser, name, tok, funcName === "\\\\globallet"); return { type: "internal", mode: parser.mode }; } }); // ref: https://www.tug.org/TUGboat/tb09-3/tb22bechtolsheim.pdf defineFunction({ type: "internal", names: ["\\futurelet", "\\\\globalfuture" // can’t be entered directly ], props: { numArgs: 0, allowedInText: true, primitive: true }, handler(_ref4) { var { parser, funcName } = _ref4; var name = checkControlSequence(parser.gullet.popToken()); var middle = parser.gullet.popToken(); var tok = parser.gullet.popToken(); letCommand(parser, name, tok, funcName === "\\\\globalfuture"); parser.gullet.pushToken(tok); parser.gullet.pushToken(middle); return { type: "internal", mode: parser.mode }; } }); /** * This file deals with creating delimiters of various sizes. The TeXbook * discusses these routines on page 441-442, in the "Another subroutine sets box * x to a specified variable delimiter" paragraph. * * There are three main routines here. `makeSmallDelim` makes a delimiter in the * normal font, but in either text, script, or scriptscript style. * `makeLargeDelim` makes a delimiter in textstyle, but in one of the Size1, * Size2, Size3, or Size4 fonts. `makeStackedDelim` makes a delimiter out of * smaller pieces that are stacked on top of one another. * * The functions take a parameter `center`, which determines if the delimiter * should be centered around the axis. * * Then, there are three exposed functions. `sizedDelim` makes a delimiter in * one of the given sizes. This is used for things like `\bigl`. * `customSizedDelim` makes a delimiter with a given total height+depth. It is * called in places like `\sqrt`. `leftRightDelim` makes an appropriate * delimiter which surrounds an expression of a given height an depth. It is * used in `\left` and `\right`. */ /** * Get the metrics for a given symbol and font, after transformation (i.e. * after following replacement from symbols.js) */ var getMetrics = function getMetrics(symbol, font, mode) { var replace = symbols.math[symbol] && symbols.math[symbol].replace; var metrics = getCharacterMetrics(replace || symbol, font, mode); if (!metrics) { throw new Error("Unsupported symbol " + symbol + " and font size " + font + "."); } return metrics; }; /** * Puts a delimiter span in a given style, and adds appropriate height, depth, * and maxFontSizes. */ var styleWrap = function styleWrap(delim, toStyle, options, classes) { var newOptions = options.havingBaseStyle(toStyle); var span = buildCommon.makeSpan(classes.concat(newOptions.sizingClasses(options)), [delim], options); var delimSizeMultiplier = newOptions.sizeMultiplier / options.sizeMultiplier; span.height *= delimSizeMultiplier; span.depth *= delimSizeMultiplier; span.maxFontSize = newOptions.sizeMultiplier; return span; }; var centerSpan = function centerSpan(span, options, style) { var newOptions = options.havingBaseStyle(style); var shift = (1 - options.sizeMultiplier / newOptions.sizeMultiplier) * options.fontMetrics().axisHeight; span.classes.push("delimcenter"); span.style.top = makeEm(shift); span.height -= shift; span.depth += shift; }; /** * Makes a small delimiter. This is a delimiter that comes in the Main-Regular * font, but is restyled to either be in textstyle, scriptstyle, or * scriptscriptstyle. */ var makeSmallDelim = function makeSmallDelim(delim, style, center, options, mode, classes) { var text = buildCommon.makeSymbol(delim, "Main-Regular", mode, options); var span = styleWrap(text, style, options, classes); if (center) { centerSpan(span, options, style); } return span; }; /** * Builds a symbol in the given font size (note size is an integer) */ var mathrmSize = function mathrmSize(value, size, mode, options) { return buildCommon.makeSymbol(value, "Size" + size + "-Regular", mode, options); }; /** * Makes a large delimiter. This is a delimiter that comes in the Size1, Size2, * Size3, or Size4 fonts. It is always rendered in textstyle. */ var makeLargeDelim = function makeLargeDelim(delim, size, center, options, mode, classes) { var inner = mathrmSize(delim, size, mode, options); var span = styleWrap(buildCommon.makeSpan(["delimsizing", "size" + size], [inner], options), Style$1.TEXT, options, classes); if (center) { centerSpan(span, options, Style$1.TEXT); } return span; }; /** * Make a span from a font glyph with the given offset and in the given font. * This is used in makeStackedDelim to make the stacking pieces for the delimiter. */ var makeGlyphSpan = function makeGlyphSpan(symbol, font, mode) { var sizeClass; // Apply the correct CSS class to choose the right font. if (font === "Size1-Regular") { sizeClass = "delim-size1"; } else /* if (font === "Size4-Regular") */ { sizeClass = "delim-size4"; } var corner = buildCommon.makeSpan(["delimsizinginner", sizeClass], [buildCommon.makeSpan([], [buildCommon.makeSymbol(symbol, font, mode)])]); // Since this will be passed into `makeVList` in the end, wrap the element // in the appropriate tag that VList uses. return { type: "elem", elem: corner }; }; var makeInner = function makeInner(ch, height, options) { // Create a span with inline SVG for the inner part of a tall stacked delimiter. var width = fontMetricsData['Size4-Regular'][ch.charCodeAt(0)] ? fontMetricsData['Size4-Regular'][ch.charCodeAt(0)][4] : fontMetricsData['Size1-Regular'][ch.charCodeAt(0)][4]; var path = new PathNode("inner", innerPath(ch, Math.round(1000 * height))); var svgNode = new SvgNode([path], { "width": makeEm(width), "height": makeEm(height), // Override CSS rule `.katex svg { width: 100% }` "style": "width:" + makeEm(width), "viewBox": "0 0 " + 1000 * width + " " + Math.round(1000 * height), "preserveAspectRatio": "xMinYMin" }); var span = buildCommon.makeSvgSpan([], [svgNode], options); span.height = height; span.style.height = makeEm(height); span.style.width = makeEm(width); return { type: "elem", elem: span }; }; // Helpers for makeStackedDelim var lapInEms = 0.008; var lap = { type: "kern", size: -1 * lapInEms }; var verts = ["|", "\\lvert", "\\rvert", "\\vert"]; var doubleVerts = ["\\|", "\\lVert", "\\rVert", "\\Vert"]; /** * Make a stacked delimiter out of a given delimiter, with the total height at * least `heightTotal`. This routine is mentioned on page 442 of the TeXbook. */ var makeStackedDelim = function makeStackedDelim(delim, heightTotal, center, options, mode, classes) { // There are four parts, the top, an optional middle, a repeated part, and a // bottom. var top; var middle; var repeat; var bottom; var svgLabel = ""; var viewBoxWidth = 0; top = repeat = bottom = delim; middle = null; // Also keep track of what font the delimiters are in var font = "Size1-Regular"; // We set the parts and font based on the symbol. Note that we use // '\u23d0' instead of '|' and '\u2016' instead of '\\|' for the // repeats of the arrows if (delim === "\\uparrow") { repeat = bottom = "\u23d0"; } else if (delim === "\\Uparrow") { repeat = bottom = "\u2016"; } else if (delim === "\\downarrow") { top = repeat = "\u23d0"; } else if (delim === "\\Downarrow") { top = repeat = "\u2016"; } else if (delim === "\\updownarrow") { top = "\\uparrow"; repeat = "\u23d0"; bottom = "\\downarrow"; } else if (delim === "\\Updownarrow") { top = "\\Uparrow"; repeat = "\u2016"; bottom = "\\Downarrow"; } else if (utils.contains(verts, delim)) { repeat = "\u2223"; svgLabel = "vert"; viewBoxWidth = 333; } else if (utils.contains(doubleVerts, delim)) { repeat = "\u2225"; svgLabel = "doublevert"; viewBoxWidth = 556; } else if (delim === "[" || delim === "\\lbrack") { top = "\u23a1"; repeat = "\u23a2"; bottom = "\u23a3"; font = "Size4-Regular"; svgLabel = "lbrack"; viewBoxWidth = 667; } else if (delim === "]" || delim === "\\rbrack") { top = "\u23a4"; repeat = "\u23a5"; bottom = "\u23a6"; font = "Size4-Regular"; svgLabel = "rbrack"; viewBoxWidth = 667; } else if (delim === "\\lfloor" || delim === "\u230a") { repeat = top = "\u23a2"; bottom = "\u23a3"; font = "Size4-Regular"; svgLabel = "lfloor"; viewBoxWidth = 667; } else if (delim === "\\lceil" || delim === "\u2308") { top = "\u23a1"; repeat = bottom = "\u23a2"; font = "Size4-Regular"; svgLabel = "lceil"; viewBoxWidth = 667; } else if (delim === "\\rfloor" || delim === "\u230b") { repeat = top = "\u23a5"; bottom = "\u23a6"; font = "Size4-Regular"; svgLabel = "rfloor"; viewBoxWidth = 667; } else if (delim === "\\rceil" || delim === "\u2309") { top = "\u23a4"; repeat = bottom = "\u23a5"; font = "Size4-Regular"; svgLabel = "rceil"; viewBoxWidth = 667; } else if (delim === "(" || delim === "\\lparen") { top = "\u239b"; repeat = "\u239c"; bottom = "\u239d"; font = "Size4-Regular"; svgLabel = "lparen"; viewBoxWidth = 875; } else if (delim === ")" || delim === "\\rparen") { top = "\u239e"; repeat = "\u239f"; bottom = "\u23a0"; font = "Size4-Regular"; svgLabel = "rparen"; viewBoxWidth = 875; } else if (delim === "\\{" || delim === "\\lbrace") { top = "\u23a7"; middle = "\u23a8"; bottom = "\u23a9"; repeat = "\u23aa"; font = "Size4-Regular"; } else if (delim === "\\}" || delim === "\\rbrace") { top = "\u23ab"; middle = "\u23ac"; bottom = "\u23ad"; repeat = "\u23aa"; font = "Size4-Regular"; } else if (delim === "\\lgroup" || delim === "\u27ee") { top = "\u23a7"; bottom = "\u23a9"; repeat = "\u23aa"; font = "Size4-Regular"; } else if (delim === "\\rgroup" || delim === "\u27ef") { top = "\u23ab"; bottom = "\u23ad"; repeat = "\u23aa"; font = "Size4-Regular"; } else if (delim === "\\lmoustache" || delim === "\u23b0") { top = "\u23a7"; bottom = "\u23ad"; repeat = "\u23aa"; font = "Size4-Regular"; } else if (delim === "\\rmoustache" || delim === "\u23b1") { top = "\u23ab"; bottom = "\u23a9"; repeat = "\u23aa"; font = "Size4-Regular"; } // Get the metrics of the four sections var topMetrics = getMetrics(top, font, mode); var topHeightTotal = topMetrics.height + topMetrics.depth; var repeatMetrics = getMetrics(repeat, font, mode); var repeatHeightTotal = repeatMetrics.height + repeatMetrics.depth; var bottomMetrics = getMetrics(bottom, font, mode); var bottomHeightTotal = bottomMetrics.height + bottomMetrics.depth; var middleHeightTotal = 0; var middleFactor = 1; if (middle !== null) { var middleMetrics = getMetrics(middle, font, mode); middleHeightTotal = middleMetrics.height + middleMetrics.depth; middleFactor = 2; // repeat symmetrically above and below middle } // Calculate the minimal height that the delimiter can have. // It is at least the size of the top, bottom, and optional middle combined. var minHeight = topHeightTotal + bottomHeightTotal + middleHeightTotal; // Compute the number of copies of the repeat symbol we will need var repeatCount = Math.max(0, Math.ceil((heightTotal - minHeight) / (middleFactor * repeatHeightTotal))); // Compute the total height of the delimiter including all the symbols var realHeightTotal = minHeight + repeatCount * middleFactor * repeatHeightTotal; // The center of the delimiter is placed at the center of the axis. Note // that in this context, "center" means that the delimiter should be // centered around the axis in the current style, while normally it is // centered around the axis in textstyle. var axisHeight = options.fontMetrics().axisHeight; if (center) { axisHeight *= options.sizeMultiplier; } // Calculate the depth var depth = realHeightTotal / 2 - axisHeight; // Now, we start building the pieces that will go into the vlist // Keep a list of the pieces of the stacked delimiter var stack = []; if (svgLabel.length > 0) { // Instead of stacking glyphs, create a single SVG. // This evades browser problems with imprecise positioning of spans. var midHeight = realHeightTotal - topHeightTotal - bottomHeightTotal; var viewBoxHeight = Math.round(realHeightTotal * 1000); var pathStr = tallDelim(svgLabel, Math.round(midHeight * 1000)); var path = new PathNode(svgLabel, pathStr); var width = (viewBoxWidth / 1000).toFixed(3) + "em"; var height = (viewBoxHeight / 1000).toFixed(3) + "em"; var svg = new SvgNode([path], { "width": width, "height": height, "viewBox": "0 0 " + viewBoxWidth + " " + viewBoxHeight }); var wrapper = buildCommon.makeSvgSpan([], [svg], options); wrapper.height = viewBoxHeight / 1000; wrapper.style.width = width; wrapper.style.height = height; stack.push({ type: "elem", elem: wrapper }); } else { // Stack glyphs // Start by adding the bottom symbol stack.push(makeGlyphSpan(bottom, font, mode)); stack.push(lap); // overlap if (middle === null) { // The middle section will be an SVG. Make it an extra 0.016em tall. // We'll overlap by 0.008em at top and bottom. var innerHeight = realHeightTotal - topHeightTotal - bottomHeightTotal + 2 * lapInEms; stack.push(makeInner(repeat, innerHeight, options)); } else { // When there is a middle bit, we need the middle part and two repeated // sections var _innerHeight = (realHeightTotal - topHeightTotal - bottomHeightTotal - middleHeightTotal) / 2 + 2 * lapInEms; stack.push(makeInner(repeat, _innerHeight, options)); // Now insert the middle of the brace. stack.push(lap); stack.push(makeGlyphSpan(middle, font, mode)); stack.push(lap); stack.push(makeInner(repeat, _innerHeight, options)); } // Add the top symbol stack.push(lap); stack.push(makeGlyphSpan(top, font, mode)); } // Finally, build the vlist var newOptions = options.havingBaseStyle(Style$1.TEXT); var inner = buildCommon.makeVList({ positionType: "bottom", positionData: depth, children: stack }, newOptions); return styleWrap(buildCommon.makeSpan(["delimsizing", "mult"], [inner], newOptions), Style$1.TEXT, options, classes); }; // All surds have 0.08em padding above the vinculum inside the SVG. // That keeps browser span height rounding error from pinching the line. var vbPad = 80; // padding above the surd, measured inside the viewBox. var emPad = 0.08; // padding, in ems, measured in the document. var sqrtSvg = function sqrtSvg(sqrtName, height, viewBoxHeight, extraVinculum, options) { var path = sqrtPath(sqrtName, extraVinculum, viewBoxHeight); var pathNode = new PathNode(sqrtName, path); var svg = new SvgNode([pathNode], { // Note: 1000:1 ratio of viewBox to document em width. "width": "400em", "height": makeEm(height), "viewBox": "0 0 400000 " + viewBoxHeight, "preserveAspectRatio": "xMinYMin slice" }); return buildCommon.makeSvgSpan(["hide-tail"], [svg], options); }; /** * Make a sqrt image of the given height, */ var makeSqrtImage = function makeSqrtImage(height, options) { // Define a newOptions that removes the effect of size changes such as \Huge. // We don't pick different a height surd for \Huge. For it, we scale up. var newOptions = options.havingBaseSizing(); // Pick the desired surd glyph from a sequence of surds. var delim = traverseSequence("\\surd", height * newOptions.sizeMultiplier, stackLargeDelimiterSequence, newOptions); var sizeMultiplier = newOptions.sizeMultiplier; // default // The standard sqrt SVGs each have a 0.04em thick vinculum. // If Settings.minRuleThickness is larger than that, we add extraVinculum. var extraVinculum = Math.max(0, options.minRuleThickness - options.fontMetrics().sqrtRuleThickness); // Create a span containing an SVG image of a sqrt symbol. var span; var spanHeight = 0; var texHeight = 0; var viewBoxHeight = 0; var advanceWidth; // We create viewBoxes with 80 units of "padding" above each surd. // Then browser rounding error on the parent span height will not // encroach on the ink of the vinculum. But that padding is not // included in the TeX-like `height` used for calculation of // vertical alignment. So texHeight = span.height < span.style.height. if (delim.type === "small") { // Get an SVG that is derived from glyph U+221A in font KaTeX-Main. // 1000 unit normal glyph height. viewBoxHeight = 1000 + 1000 * extraVinculum + vbPad; if (height < 1.0) { sizeMultiplier = 1.0; // mimic a \textfont radical } else if (height < 1.4) { sizeMultiplier = 0.7; // mimic a \scriptfont radical } spanHeight = (1.0 + extraVinculum + emPad) / sizeMultiplier; texHeight = (1.00 + extraVinculum) / sizeMultiplier; span = sqrtSvg("sqrtMain", spanHeight, viewBoxHeight, extraVinculum, options); span.style.minWidth = "0.853em"; advanceWidth = 0.833 / sizeMultiplier; // from the font. } else if (delim.type === "large") { // These SVGs come from fonts: KaTeX_Size1, _Size2, etc. viewBoxHeight = (1000 + vbPad) * sizeToMaxHeight[delim.size]; texHeight = (sizeToMaxHeight[delim.size] + extraVinculum) / sizeMultiplier; spanHeight = (sizeToMaxHeight[delim.size] + extraVinculum + emPad) / sizeMultiplier; span = sqrtSvg("sqrtSize" + delim.size, spanHeight, viewBoxHeight, extraVinculum, options); span.style.minWidth = "1.02em"; advanceWidth = 1.0 / sizeMultiplier; // 1.0 from the font. } else { // Tall sqrt. In TeX, this would be stacked using multiple glyphs. // We'll use a single SVG to accomplish the same thing. spanHeight = height + extraVinculum + emPad; texHeight = height + extraVinculum; viewBoxHeight = Math.floor(1000 * height + extraVinculum) + vbPad; span = sqrtSvg("sqrtTall", spanHeight, viewBoxHeight, extraVinculum, options); span.style.minWidth = "0.742em"; advanceWidth = 1.056; } span.height = texHeight; span.style.height = makeEm(spanHeight); return { span, advanceWidth, // Calculate the actual line width. // This actually should depend on the chosen font -- e.g. \boldmath // should use the thicker surd symbols from e.g. KaTeX_Main-Bold, and // have thicker rules. ruleWidth: (options.fontMetrics().sqrtRuleThickness + extraVinculum) * sizeMultiplier }; }; // There are three kinds of delimiters, delimiters that stack when they become // too large var stackLargeDelimiters = ["(", "\\lparen", ")", "\\rparen", "[", "\\lbrack", "]", "\\rbrack", "\\{", "\\lbrace", "\\}", "\\rbrace", "\\lfloor", "\\rfloor", "\u230a", "\u230b", "\\lceil", "\\rceil", "\u2308", "\u2309", "\\surd"]; // delimiters that always stack var stackAlwaysDelimiters = ["\\uparrow", "\\downarrow", "\\updownarrow", "\\Uparrow", "\\Downarrow", "\\Updownarrow", "|", "\\|", "\\vert", "\\Vert", "\\lvert", "\\rvert", "\\lVert", "\\rVert", "\\lgroup", "\\rgroup", "\u27ee", "\u27ef", "\\lmoustache", "\\rmoustache", "\u23b0", "\u23b1"]; // and delimiters that never stack var stackNeverDelimiters = ["<", ">", "\\langle", "\\rangle", "/", "\\backslash", "\\lt", "\\gt"]; // Metrics of the different sizes. Found by looking at TeX's output of // $\bigl| // \Bigl| \biggl| \Biggl| \showlists$ // Used to create stacked delimiters of appropriate sizes in makeSizedDelim. var sizeToMaxHeight = [0, 1.2, 1.8, 2.4, 3.0]; /** * Used to create a delimiter of a specific size, where `size` is 1, 2, 3, or 4. */ var makeSizedDelim = function makeSizedDelim(delim, size, options, mode, classes) { // < and > turn into \langle and \rangle in delimiters if (delim === "<" || delim === "\\lt" || delim === "\u27e8") { delim = "\\langle"; } else if (delim === ">" || delim === "\\gt" || delim === "\u27e9") { delim = "\\rangle"; } // Sized delimiters are never centered. if (utils.contains(stackLargeDelimiters, delim) || utils.contains(stackNeverDelimiters, delim)) { return makeLargeDelim(delim, size, false, options, mode, classes); } else if (utils.contains(stackAlwaysDelimiters, delim)) { return makeStackedDelim(delim, sizeToMaxHeight[size], false, options, mode, classes); } else { throw new ParseError("Illegal delimiter: '" + delim + "'"); } }; /** * There are three different sequences of delimiter sizes that the delimiters * follow depending on the kind of delimiter. This is used when creating custom * sized delimiters to decide whether to create a small, large, or stacked * delimiter. * * In real TeX, these sequences aren't explicitly defined, but are instead * defined inside the font metrics. Since there are only three sequences that * are possible for the delimiters that TeX defines, it is easier to just encode * them explicitly here. */ // Delimiters that never stack try small delimiters and large delimiters only var stackNeverDelimiterSequence = [{ type: "small", style: Style$1.SCRIPTSCRIPT }, { type: "small", style: Style$1.SCRIPT }, { type: "small", style: Style$1.TEXT }, { type: "large", size: 1 }, { type: "large", size: 2 }, { type: "large", size: 3 }, { type: "large", size: 4 }]; // Delimiters that always stack try the small delimiters first, then stack var stackAlwaysDelimiterSequence = [{ type: "small", style: Style$1.SCRIPTSCRIPT }, { type: "small", style: Style$1.SCRIPT }, { type: "small", style: Style$1.TEXT }, { type: "stack" }]; // Delimiters that stack when large try the small and then large delimiters, and // stack afterwards var stackLargeDelimiterSequence = [{ type: "small", style: Style$1.SCRIPTSCRIPT }, { type: "small", style: Style$1.SCRIPT }, { type: "small", style: Style$1.TEXT }, { type: "large", size: 1 }, { type: "large", size: 2 }, { type: "large", size: 3 }, { type: "large", size: 4 }, { type: "stack" }]; /** * Get the font used in a delimiter based on what kind of delimiter it is. * TODO(#963) Use more specific font family return type once that is introduced. */ var delimTypeToFont = function delimTypeToFont(type) { if (type.type === "small") { return "Main-Regular"; } else if (type.type === "large") { return "Size" + type.size + "-Regular"; } else if (type.type === "stack") { return "Size4-Regular"; } else { throw new Error("Add support for delim type '" + type.type + "' here."); } }; /** * Traverse a sequence of types of delimiters to decide what kind of delimiter * should be used to create a delimiter of the given height+depth. */ var traverseSequence = function traverseSequence(delim, height, sequence, options) { // Here, we choose the index we should start at in the sequences. In smaller // sizes (which correspond to larger numbers in style.size) we start earlier // in the sequence. Thus, scriptscript starts at index 3-3=0, script starts // at index 3-2=1, text starts at 3-1=2, and display starts at min(2,3-0)=2 var start = Math.min(2, 3 - options.style.size); for (var i = start; i < sequence.length; i++) { if (sequence[i].type === "stack") { // This is always the last delimiter, so we just break the loop now. break; } var metrics = getMetrics(delim, delimTypeToFont(sequence[i]), "math"); var heightDepth = metrics.height + metrics.depth; // Small delimiters are scaled down versions of the same font, so we // account for the style change size. if (sequence[i].type === "small") { var newOptions = options.havingBaseStyle(sequence[i].style); heightDepth *= newOptions.sizeMultiplier; } // Check if the delimiter at this size works for the given height. if (heightDepth > height) { return sequence[i]; } } // If we reached the end of the sequence, return the last sequence element. return sequence[sequence.length - 1]; }; /** * Make a delimiter of a given height+depth, with optional centering. Here, we * traverse the sequences, and create a delimiter that the sequence tells us to. */ var makeCustomSizedDelim = function makeCustomSizedDelim(delim, height, center, options, mode, classes) { if (delim === "<" || delim === "\\lt" || delim === "\u27e8") { delim = "\\langle"; } else if (delim === ">" || delim === "\\gt" || delim === "\u27e9") { delim = "\\rangle"; } // Decide what sequence to use var sequence; if (utils.contains(stackNeverDelimiters, delim)) { sequence = stackNeverDelimiterSequence; } else if (utils.contains(stackLargeDelimiters, delim)) { sequence = stackLargeDelimiterSequence; } else { sequence = stackAlwaysDelimiterSequence; } // Look through the sequence var delimType = traverseSequence(delim, height, sequence, options); // Get the delimiter from font glyphs. // Depending on the sequence element we decided on, call the // appropriate function. if (delimType.type === "small") { return makeSmallDelim(delim, delimType.style, center, options, mode, classes); } else if (delimType.type === "large") { return makeLargeDelim(delim, delimType.size, center, options, mode, classes); } else /* if (delimType.type === "stack") */ { return makeStackedDelim(delim, height, center, options, mode, classes); } }; /** * Make a delimiter for use with `\left` and `\right`, given a height and depth * of an expression that the delimiters surround. */ var makeLeftRightDelim = function makeLeftRightDelim(delim, height, depth, options, mode, classes) { // We always center \left/\right delimiters, so the axis is always shifted var axisHeight = options.fontMetrics().axisHeight * options.sizeMultiplier; // Taken from TeX source, tex.web, function make_left_right var delimiterFactor = 901; var delimiterExtend = 5.0 / options.fontMetrics().ptPerEm; var maxDistFromAxis = Math.max(height - axisHeight, depth + axisHeight); var totalHeight = Math.max( // In real TeX, calculations are done using integral values which are // 65536 per pt, or 655360 per em. So, the division here truncates in // TeX but doesn't here, producing different results. If we wanted to // exactly match TeX's calculation, we could do // Math.floor(655360 * maxDistFromAxis / 500) * // delimiterFactor / 655360 // (To see the difference, compare // x^{x^{\left(\rule{0.1em}{0.68em}\right)}} // in TeX and KaTeX) maxDistFromAxis / 500 * delimiterFactor, 2 * maxDistFromAxis - delimiterExtend); // Finally, we defer to `makeCustomSizedDelim` with our calculated total // height return makeCustomSizedDelim(delim, totalHeight, true, options, mode, classes); }; var delimiter = { sqrtImage: makeSqrtImage, sizedDelim: makeSizedDelim, sizeToMaxHeight: sizeToMaxHeight, customSizedDelim: makeCustomSizedDelim, leftRightDelim: makeLeftRightDelim }; // Extra data needed for the delimiter handler down below var delimiterSizes = { "\\bigl": { mclass: "mopen", size: 1 }, "\\Bigl": { mclass: "mopen", size: 2 }, "\\biggl": { mclass: "mopen", size: 3 }, "\\Biggl": { mclass: "mopen", size: 4 }, "\\bigr": { mclass: "mclose", size: 1 }, "\\Bigr": { mclass: "mclose", size: 2 }, "\\biggr": { mclass: "mclose", size: 3 }, "\\Biggr": { mclass: "mclose", size: 4 }, "\\bigm": { mclass: "mrel", size: 1 }, "\\Bigm": { mclass: "mrel", size: 2 }, "\\biggm": { mclass: "mrel", size: 3 }, "\\Biggm": { mclass: "mrel", size: 4 }, "\\big": { mclass: "mord", size: 1 }, "\\Big": { mclass: "mord", size: 2 }, "\\bigg": { mclass: "mord", size: 3 }, "\\Bigg": { mclass: "mord", size: 4 } }; var delimiters = ["(", "\\lparen", ")", "\\rparen", "[", "\\lbrack", "]", "\\rbrack", "\\{", "\\lbrace", "\\}", "\\rbrace", "\\lfloor", "\\rfloor", "\u230a", "\u230b", "\\lceil", "\\rceil", "\u2308", "\u2309", "<", ">", "\\langle", "\u27e8", "\\rangle", "\u27e9", "\\lt", "\\gt", "\\lvert", "\\rvert", "\\lVert", "\\rVert", "\\lgroup", "\\rgroup", "\u27ee", "\u27ef", "\\lmoustache", "\\rmoustache", "\u23b0", "\u23b1", "/", "\\backslash", "|", "\\vert", "\\|", "\\Vert", "\\uparrow", "\\Uparrow", "\\downarrow", "\\Downarrow", "\\updownarrow", "\\Updownarrow", "."]; // Delimiter functions function checkDelimiter(delim, context) { var symDelim = checkSymbolNodeType(delim); if (symDelim && utils.contains(delimiters, symDelim.text)) { return symDelim; } else if (symDelim) { throw new ParseError("Invalid delimiter '" + symDelim.text + "' after '" + context.funcName + "'", delim); } else { throw new ParseError("Invalid delimiter type '" + delim.type + "'", delim); } } defineFunction({ type: "delimsizing", names: ["\\bigl", "\\Bigl", "\\biggl", "\\Biggl", "\\bigr", "\\Bigr", "\\biggr", "\\Biggr", "\\bigm", "\\Bigm", "\\biggm", "\\Biggm", "\\big", "\\Big", "\\bigg", "\\Bigg"], props: { numArgs: 1, argTypes: ["primitive"] }, handler: (context, args) => { var delim = checkDelimiter(args[0], context); return { type: "delimsizing", mode: context.parser.mode, size: delimiterSizes[context.funcName].size, mclass: delimiterSizes[context.funcName].mclass, delim: delim.text }; }, htmlBuilder: (group, options) => { if (group.delim === ".") { // Empty delimiters still count as elements, even though they don't // show anything. return buildCommon.makeSpan([group.mclass]); } // Use delimiter.sizedDelim to generate the delimiter. return delimiter.sizedDelim(group.delim, group.size, options, group.mode, [group.mclass]); }, mathmlBuilder: group => { var children = []; if (group.delim !== ".") { children.push(makeText(group.delim, group.mode)); } var node = new mathMLTree.MathNode("mo", children); if (group.mclass === "mopen" || group.mclass === "mclose") { // Only some of the delimsizing functions act as fences, and they // return "mopen" or "mclose" mclass. node.setAttribute("fence", "true"); } else { // Explicitly disable fencing if it's not a fence, to override the // defaults. node.setAttribute("fence", "false"); } node.setAttribute("stretchy", "true"); var size = makeEm(delimiter.sizeToMaxHeight[group.size]); node.setAttribute("minsize", size); node.setAttribute("maxsize", size); return node; } }); function assertParsed(group) { if (!group.body) { throw new Error("Bug: The leftright ParseNode wasn't fully parsed."); } } defineFunction({ type: "leftright-right", names: ["\\right"], props: { numArgs: 1, primitive: true }, handler: (context, args) => { // \left case below triggers parsing of \right in // `const right = parser.parseFunction();` // uses this return value. var color = context.parser.gullet.macros.get("\\current@color"); if (color && typeof color !== "string") { throw new ParseError("\\current@color set to non-string in \\right"); } return { type: "leftright-right", mode: context.parser.mode, delim: checkDelimiter(args[0], context).text, color // undefined if not set via \color }; } }); defineFunction({ type: "leftright", names: ["\\left"], props: { numArgs: 1, primitive: true }, handler: (context, args) => { var delim = checkDelimiter(args[0], context); var parser = context.parser; // Parse out the implicit body ++parser.leftrightDepth; // parseExpression stops before '\\right' var body = parser.parseExpression(false); --parser.leftrightDepth; // Check the next token parser.expect("\\right", false); var right = assertNodeType(parser.parseFunction(), "leftright-right"); return { type: "leftright", mode: parser.mode, body, left: delim.text, right: right.delim, rightColor: right.color }; }, htmlBuilder: (group, options) => { assertParsed(group); // Build the inner expression var inner = buildExpression$1(group.body, options, true, ["mopen", "mclose"]); var innerHeight = 0; var innerDepth = 0; var hadMiddle = false; // Calculate its height and depth for (var i = 0; i < inner.length; i++) { // Property `isMiddle` not defined on `span`. See comment in // "middle"'s htmlBuilder. // $FlowFixMe if (inner[i].isMiddle) { hadMiddle = true; } else { innerHeight = Math.max(inner[i].height, innerHeight); innerDepth = Math.max(inner[i].depth, innerDepth); } } // The size of delimiters is the same, regardless of what style we are // in. Thus, to correctly calculate the size of delimiter we need around // a group, we scale down the inner size based on the size. innerHeight *= options.sizeMultiplier; innerDepth *= options.sizeMultiplier; var leftDelim; if (group.left === ".") { // Empty delimiters in \left and \right make null delimiter spaces. leftDelim = makeNullDelimiter(options, ["mopen"]); } else { // Otherwise, use leftRightDelim to generate the correct sized // delimiter. leftDelim = delimiter.leftRightDelim(group.left, innerHeight, innerDepth, options, group.mode, ["mopen"]); } // Add it to the beginning of the expression inner.unshift(leftDelim); // Handle middle delimiters if (hadMiddle) { for (var _i = 1; _i < inner.length; _i++) { var middleDelim = inner[_i]; // Property `isMiddle` not defined on `span`. See comment in // "middle"'s htmlBuilder. // $FlowFixMe var isMiddle = middleDelim.isMiddle; if (isMiddle) { // Apply the options that were active when \middle was called inner[_i] = delimiter.leftRightDelim(isMiddle.delim, innerHeight, innerDepth, isMiddle.options, group.mode, []); } } } var rightDelim; // Same for the right delimiter, but using color specified by \color if (group.right === ".") { rightDelim = makeNullDelimiter(options, ["mclose"]); } else { var colorOptions = group.rightColor ? options.withColor(group.rightColor) : options; rightDelim = delimiter.leftRightDelim(group.right, innerHeight, innerDepth, colorOptions, group.mode, ["mclose"]); } // Add it to the end of the expression. inner.push(rightDelim); return buildCommon.makeSpan(["minner"], inner, options); }, mathmlBuilder: (group, options) => { assertParsed(group); var inner = buildExpression(group.body, options); if (group.left !== ".") { var leftNode = new mathMLTree.MathNode("mo", [makeText(group.left, group.mode)]); leftNode.setAttribute("fence", "true"); inner.unshift(leftNode); } if (group.right !== ".") { var rightNode = new mathMLTree.MathNode("mo", [makeText(group.right, group.mode)]); rightNode.setAttribute("fence", "true"); if (group.rightColor) { rightNode.setAttribute("mathcolor", group.rightColor); } inner.push(rightNode); } return makeRow(inner); } }); defineFunction({ type: "middle", names: ["\\middle"], props: { numArgs: 1, primitive: true }, handler: (context, args) => { var delim = checkDelimiter(args[0], context); if (!context.parser.leftrightDepth) { throw new ParseError("\\middle without preceding \\left", delim); } return { type: "middle", mode: context.parser.mode, delim: delim.text }; }, htmlBuilder: (group, options) => { var middleDelim; if (group.delim === ".") { middleDelim = makeNullDelimiter(options, []); } else { middleDelim = delimiter.sizedDelim(group.delim, 1, options, group.mode, []); var isMiddle = { delim: group.delim, options }; // Property `isMiddle` not defined on `span`. It is only used in // this file above. // TODO: Fix this violation of the `span` type and possibly rename // things since `isMiddle` sounds like a boolean, but is a struct. // $FlowFixMe middleDelim.isMiddle = isMiddle; } return middleDelim; }, mathmlBuilder: (group, options) => { // A Firefox \middle will stretch a character vertically only if it // is in the fence part of the operator dictionary at: // https://www.w3.org/TR/MathML3/appendixc.html. // So we need to avoid U+2223 and use plain "|" instead. var textNode = group.delim === "\\vert" || group.delim === "|" ? makeText("|", "text") : makeText(group.delim, group.mode); var middleNode = new mathMLTree.MathNode("mo", [textNode]); middleNode.setAttribute("fence", "true"); // MathML gives 5/18em spacing to each element. // \middle should get delimiter spacing instead. middleNode.setAttribute("lspace", "0.05em"); middleNode.setAttribute("rspace", "0.05em"); return middleNode; } }); var htmlBuilder$7 = (group, options) => { // \cancel, \bcancel, \xcancel, \sout, \fbox, \colorbox, \fcolorbox, \phase // Some groups can return document fragments. Handle those by wrapping // them in a span. var inner = buildCommon.wrapFragment(buildGroup$1(group.body, options), options); var label = group.label.slice(1); var scale = options.sizeMultiplier; var img; var imgShift = 0; // In the LaTeX cancel package, line geometry is slightly different // depending on whether the subject is wider than it is tall, or vice versa. // We don't know the width of a group, so as a proxy, we test if // the subject is a single character. This captures most of the // subjects that should get the "tall" treatment. var isSingleChar = utils.isCharacterBox(group.body); if (label === "sout") { img = buildCommon.makeSpan(["stretchy", "sout"]); img.height = options.fontMetrics().defaultRuleThickness / scale; imgShift = -0.5 * options.fontMetrics().xHeight; } else if (label === "phase") { // Set a couple of dimensions from the steinmetz package. var lineWeight = calculateSize({ number: 0.6, unit: "pt" }, options); var clearance = calculateSize({ number: 0.35, unit: "ex" }, options); // Prevent size changes like \Huge from affecting line thickness var newOptions = options.havingBaseSizing(); scale = scale / newOptions.sizeMultiplier; var angleHeight = inner.height + inner.depth + lineWeight + clearance; // Reserve a left pad for the angle. inner.style.paddingLeft = makeEm(angleHeight / 2 + lineWeight); // Create an SVG var viewBoxHeight = Math.floor(1000 * angleHeight * scale); var path = phasePath(viewBoxHeight); var svgNode = new SvgNode([new PathNode("phase", path)], { "width": "400em", "height": makeEm(viewBoxHeight / 1000), "viewBox": "0 0 400000 " + viewBoxHeight, "preserveAspectRatio": "xMinYMin slice" }); // Wrap it in a span with overflow: hidden. img = buildCommon.makeSvgSpan(["hide-tail"], [svgNode], options); img.style.height = makeEm(angleHeight); imgShift = inner.depth + lineWeight + clearance; } else { // Add horizontal padding if (/cancel/.test(label)) { if (!isSingleChar) { inner.classes.push("cancel-pad"); } } else if (label === "angl") { inner.classes.push("anglpad"); } else { inner.classes.push("boxpad"); } // Add vertical padding var topPad = 0; var bottomPad = 0; var ruleThickness = 0; // ref: cancel package: \advance\totalheight2\p@ % "+2" if (/box/.test(label)) { ruleThickness = Math.max(options.fontMetrics().fboxrule, // default options.minRuleThickness // User override. ); topPad = options.fontMetrics().fboxsep + (label === "colorbox" ? 0 : ruleThickness); bottomPad = topPad; } else if (label === "angl") { ruleThickness = Math.max(options.fontMetrics().defaultRuleThickness, options.minRuleThickness); topPad = 4 * ruleThickness; // gap = 3 × line, plus the line itself. bottomPad = Math.max(0, 0.25 - inner.depth); } else { topPad = isSingleChar ? 0.2 : 0; bottomPad = topPad; } img = stretchy.encloseSpan(inner, label, topPad, bottomPad, options); if (/fbox|boxed|fcolorbox/.test(label)) { img.style.borderStyle = "solid"; img.style.borderWidth = makeEm(ruleThickness); } else if (label === "angl" && ruleThickness !== 0.049) { img.style.borderTopWidth = makeEm(ruleThickness); img.style.borderRightWidth = makeEm(ruleThickness); } imgShift = inner.depth + bottomPad; if (group.backgroundColor) { img.style.backgroundColor = group.backgroundColor; if (group.borderColor) { img.style.borderColor = group.borderColor; } } } var vlist; if (group.backgroundColor) { vlist = buildCommon.makeVList({ positionType: "individualShift", children: [// Put the color background behind inner; { type: "elem", elem: img, shift: imgShift }, { type: "elem", elem: inner, shift: 0 }] }, options); } else { var classes = /cancel|phase/.test(label) ? ["svg-align"] : []; vlist = buildCommon.makeVList({ positionType: "individualShift", children: [// Write the \cancel stroke on top of inner. { type: "elem", elem: inner, shift: 0 }, { type: "elem", elem: img, shift: imgShift, wrapperClasses: classes }] }, options); } if (/cancel/.test(label)) { // The cancel package documentation says that cancel lines add their height // to the expression, but tests show that isn't how it actually works. vlist.height = inner.height; vlist.depth = inner.depth; } if (/cancel/.test(label) && !isSingleChar) { // cancel does not create horiz space for its line extension. return buildCommon.makeSpan(["mord", "cancel-lap"], [vlist], options); } else { return buildCommon.makeSpan(["mord"], [vlist], options); } }; var mathmlBuilder$6 = (group, options) => { var fboxsep = 0; var node = new mathMLTree.MathNode(group.label.indexOf("colorbox") > -1 ? "mpadded" : "menclose", [buildGroup(group.body, options)]); switch (group.label) { case "\\cancel": node.setAttribute("notation", "updiagonalstrike"); break; case "\\bcancel": node.setAttribute("notation", "downdiagonalstrike"); break; case "\\phase": node.setAttribute("notation", "phasorangle"); break; case "\\sout": node.setAttribute("notation", "horizontalstrike"); break; case "\\fbox": node.setAttribute("notation", "box"); break; case "\\angl": node.setAttribute("notation", "actuarial"); break; case "\\fcolorbox": case "\\colorbox": // doesn't have a good notation option. So use // instead. Set some attributes that come included with . fboxsep = options.fontMetrics().fboxsep * options.fontMetrics().ptPerEm; node.setAttribute("width", "+" + 2 * fboxsep + "pt"); node.setAttribute("height", "+" + 2 * fboxsep + "pt"); node.setAttribute("lspace", fboxsep + "pt"); // node.setAttribute("voffset", fboxsep + "pt"); if (group.label === "\\fcolorbox") { var thk = Math.max(options.fontMetrics().fboxrule, // default options.minRuleThickness // user override ); node.setAttribute("style", "border: " + thk + "em solid " + String(group.borderColor)); } break; case "\\xcancel": node.setAttribute("notation", "updiagonalstrike downdiagonalstrike"); break; } if (group.backgroundColor) { node.setAttribute("mathbackground", group.backgroundColor); } return node; }; defineFunction({ type: "enclose", names: ["\\colorbox"], props: { numArgs: 2, allowedInText: true, argTypes: ["color", "text"] }, handler(_ref, args, optArgs) { var { parser, funcName } = _ref; var color = assertNodeType(args[0], "color-token").color; var body = args[1]; return { type: "enclose", mode: parser.mode, label: funcName, backgroundColor: color, body }; }, htmlBuilder: htmlBuilder$7, mathmlBuilder: mathmlBuilder$6 }); defineFunction({ type: "enclose", names: ["\\fcolorbox"], props: { numArgs: 3, allowedInText: true, argTypes: ["color", "color", "text"] }, handler(_ref2, args, optArgs) { var { parser, funcName } = _ref2; var borderColor = assertNodeType(args[0], "color-token").color; var backgroundColor = assertNodeType(args[1], "color-token").color; var body = args[2]; return { type: "enclose", mode: parser.mode, label: funcName, backgroundColor, borderColor, body }; }, htmlBuilder: htmlBuilder$7, mathmlBuilder: mathmlBuilder$6 }); defineFunction({ type: "enclose", names: ["\\fbox"], props: { numArgs: 1, argTypes: ["hbox"], allowedInText: true }, handler(_ref3, args) { var { parser } = _ref3; return { type: "enclose", mode: parser.mode, label: "\\fbox", body: args[0] }; } }); defineFunction({ type: "enclose", names: ["\\cancel", "\\bcancel", "\\xcancel", "\\sout", "\\phase"], props: { numArgs: 1 }, handler(_ref4, args) { var { parser, funcName } = _ref4; var body = args[0]; return { type: "enclose", mode: parser.mode, label: funcName, body }; }, htmlBuilder: htmlBuilder$7, mathmlBuilder: mathmlBuilder$6 }); defineFunction({ type: "enclose", names: ["\\angl"], props: { numArgs: 1, argTypes: ["hbox"], allowedInText: false }, handler(_ref5, args) { var { parser } = _ref5; return { type: "enclose", mode: parser.mode, label: "\\angl", body: args[0] }; } }); /** * All registered environments. * `environments.js` exports this same dictionary again and makes it public. * `Parser.js` requires this dictionary via `environments.js`. */ var _environments = {}; function defineEnvironment(_ref) { var { type, names, props, handler, htmlBuilder, mathmlBuilder } = _ref; // Set default values of environments. var data = { type, numArgs: props.numArgs || 0, allowedInText: false, numOptionalArgs: 0, handler }; for (var i = 0; i < names.length; ++i) { // TODO: The value type of _environments should be a type union of all // possible `EnvSpec<>` possibilities instead of `EnvSpec<*>`, which is // an existential type. _environments[names[i]] = data; } if (htmlBuilder) { _htmlGroupBuilders[type] = htmlBuilder; } if (mathmlBuilder) { _mathmlGroupBuilders[type] = mathmlBuilder; } } /** * All registered global/built-in macros. * `macros.js` exports this same dictionary again and makes it public. * `Parser.js` requires this dictionary via `macros.js`. */ var _macros = {}; // This function might one day accept an additional argument and do more things. function defineMacro(name, body) { _macros[name] = body; } // Helper functions function getHLines(parser) { // Return an array. The array length = number of hlines. // Each element in the array tells if the line is dashed. var hlineInfo = []; parser.consumeSpaces(); var nxt = parser.fetch().text; if (nxt === "\\relax") { // \relax is an artifact of the \cr macro below parser.consume(); parser.consumeSpaces(); nxt = parser.fetch().text; } while (nxt === "\\hline" || nxt === "\\hdashline") { parser.consume(); hlineInfo.push(nxt === "\\hdashline"); parser.consumeSpaces(); nxt = parser.fetch().text; } return hlineInfo; } var validateAmsEnvironmentContext = context => { var settings = context.parser.settings; if (!settings.displayMode) { throw new ParseError("{" + context.envName + "} can be used only in" + " display mode."); } }; // autoTag (an argument to parseArray) can be one of three values: // * undefined: Regular (not-top-level) array; no tags on each row // * true: Automatic equation numbering, overridable by \tag // * false: Tags allowed on each row, but no automatic numbering // This function *doesn't* work with the "split" environment name. function getAutoTag(name) { if (name.indexOf("ed") === -1) { return name.indexOf("*") === -1; } // return undefined; } /** * Parse the body of the environment, with rows delimited by \\ and * columns delimited by &, and create a nested list in row-major order * with one group per cell. If given an optional argument style * ("text", "display", etc.), then each cell is cast into that style. */ function parseArray(parser, _ref, style) { var { hskipBeforeAndAfter, addJot, cols, arraystretch, colSeparationType, autoTag, singleRow, emptySingleRow, maxNumCols, leqno } = _ref; parser.gullet.beginGroup(); if (!singleRow) { // \cr is equivalent to \\ without the optional size argument (see below) // TODO: provide helpful error when \cr is used outside array environment parser.gullet.macros.set("\\cr", "\\\\\\relax"); } // Get current arraystretch if it's not set by the environment if (!arraystretch) { var stretch = parser.gullet.expandMacroAsText("\\arraystretch"); if (stretch == null) { // Default \arraystretch from lttab.dtx arraystretch = 1; } else { arraystretch = parseFloat(stretch); if (!arraystretch || arraystretch < 0) { throw new ParseError("Invalid \\arraystretch: " + stretch); } } } // Start group for first cell parser.gullet.beginGroup(); var row = []; var body = [row]; var rowGaps = []; var hLinesBeforeRow = []; var tags = autoTag != null ? [] : undefined; // amsmath uses \global\@eqnswtrue and \global\@eqnswfalse to represent // whether this row should have an equation number. Simulate this with // a \@eqnsw macro set to 1 or 0. function beginRow() { if (autoTag) { parser.gullet.macros.set("\\@eqnsw", "1", true); } } function endRow() { if (tags) { if (parser.gullet.macros.get("\\df@tag")) { tags.push(parser.subparse([new Token("\\df@tag")])); parser.gullet.macros.set("\\df@tag", undefined, true); } else { tags.push(Boolean(autoTag) && parser.gullet.macros.get("\\@eqnsw") === "1"); } } } beginRow(); // Test for \hline at the top of the array. hLinesBeforeRow.push(getHLines(parser)); while (true) { // eslint-disable-line no-constant-condition // Parse each cell in its own group (namespace) var cell = parser.parseExpression(false, singleRow ? "\\end" : "\\\\"); parser.gullet.endGroup(); parser.gullet.beginGroup(); cell = { type: "ordgroup", mode: parser.mode, body: cell }; if (style) { cell = { type: "styling", mode: parser.mode, style, body: [cell] }; } row.push(cell); var next = parser.fetch().text; if (next === "&") { if (maxNumCols && row.length === maxNumCols) { if (singleRow || colSeparationType) { // {equation} or {split} throw new ParseError("Too many tab characters: &", parser.nextToken); } else { // {array} environment parser.settings.reportNonstrict("textEnv", "Too few columns " + "specified in the {array} column argument."); } } parser.consume(); } else if (next === "\\end") { endRow(); // Arrays terminate newlines with `\crcr` which consumes a `\cr` if // the last line is empty. However, AMS environments keep the // empty row if it's the only one. // NOTE: Currently, `cell` is the last item added into `row`. if (row.length === 1 && cell.type === "styling" && cell.body[0].body.length === 0 && (body.length > 1 || !emptySingleRow)) { body.pop(); } if (hLinesBeforeRow.length < body.length + 1) { hLinesBeforeRow.push([]); } break; } else if (next === "\\\\") { parser.consume(); var size = void 0; // \def\Let@{\let\\\math@cr} // \def\math@cr{...\math@cr@} // \def\math@cr@{\new@ifnextchar[\math@cr@@{\math@cr@@[\z@]}} // \def\math@cr@@[#1]{...\math@cr@@@...} // \def\math@cr@@@{\cr} if (parser.gullet.future().text !== " ") { size = parser.parseSizeGroup(true); } rowGaps.push(size ? size.value : null); endRow(); // check for \hline(s) following the row separator hLinesBeforeRow.push(getHLines(parser)); row = []; body.push(row); beginRow(); } else { throw new ParseError("Expected & or \\\\ or \\cr or \\end", parser.nextToken); } } // End cell group parser.gullet.endGroup(); // End array group defining \cr parser.gullet.endGroup(); return { type: "array", mode: parser.mode, addJot, arraystretch, body, cols, rowGaps, hskipBeforeAndAfter, hLinesBeforeRow, colSeparationType, tags, leqno }; } // Decides on a style for cells in an array according to whether the given // environment name starts with the letter 'd'. function dCellStyle(envName) { if (envName.slice(0, 1) === "d") { return "display"; } else { return "text"; } } var htmlBuilder$6 = function htmlBuilder(group, options) { var r; var c; var nr = group.body.length; var hLinesBeforeRow = group.hLinesBeforeRow; var nc = 0; var body = new Array(nr); var hlines = []; var ruleThickness = Math.max( // From LaTeX \showthe\arrayrulewidth. Equals 0.04 em. options.fontMetrics().arrayRuleWidth, options.minRuleThickness // User override. ); // Horizontal spacing var pt = 1 / options.fontMetrics().ptPerEm; var arraycolsep = 5 * pt; // default value, i.e. \arraycolsep in article.cls if (group.colSeparationType && group.colSeparationType === "small") { // We're in a {smallmatrix}. Default column space is \thickspace, // i.e. 5/18em = 0.2778em, per amsmath.dtx for {smallmatrix}. // But that needs adjustment because LaTeX applies \scriptstyle to the // entire array, including the colspace, but this function applies // \scriptstyle only inside each element. var localMultiplier = options.havingStyle(Style$1.SCRIPT).sizeMultiplier; arraycolsep = 0.2778 * (localMultiplier / options.sizeMultiplier); } // Vertical spacing var baselineskip = group.colSeparationType === "CD" ? calculateSize({ number: 3, unit: "ex" }, options) : 12 * pt; // see size10.clo // Default \jot from ltmath.dtx // TODO(edemaine): allow overriding \jot via \setlength (#687) var jot = 3 * pt; var arrayskip = group.arraystretch * baselineskip; var arstrutHeight = 0.7 * arrayskip; // \strutbox in ltfsstrc.dtx and var arstrutDepth = 0.3 * arrayskip; // \@arstrutbox in lttab.dtx var totalHeight = 0; // Set a position for \hline(s) at the top of the array, if any. function setHLinePos(hlinesInGap) { for (var i = 0; i < hlinesInGap.length; ++i) { if (i > 0) { totalHeight += 0.25; } hlines.push({ pos: totalHeight, isDashed: hlinesInGap[i] }); } } setHLinePos(hLinesBeforeRow[0]); for (r = 0; r < group.body.length; ++r) { var inrow = group.body[r]; var height = arstrutHeight; // \@array adds an \@arstrut var depth = arstrutDepth; // to each tow (via the template) if (nc < inrow.length) { nc = inrow.length; } var outrow = new Array(inrow.length); for (c = 0; c < inrow.length; ++c) { var elt = buildGroup$1(inrow[c], options); if (depth < elt.depth) { depth = elt.depth; } if (height < elt.height) { height = elt.height; } outrow[c] = elt; } var rowGap = group.rowGaps[r]; var gap = 0; if (rowGap) { gap = calculateSize(rowGap, options); if (gap > 0) { // \@argarraycr gap += arstrutDepth; if (depth < gap) { depth = gap; // \@xargarraycr } gap = 0; } } // In AMS multiline environments such as aligned and gathered, rows // correspond to lines that have additional \jot added to the // \baselineskip via \openup. if (group.addJot) { depth += jot; } outrow.height = height; outrow.depth = depth; totalHeight += height; outrow.pos = totalHeight; totalHeight += depth + gap; // \@yargarraycr body[r] = outrow; // Set a position for \hline(s), if any. setHLinePos(hLinesBeforeRow[r + 1]); } var offset = totalHeight / 2 + options.fontMetrics().axisHeight; var colDescriptions = group.cols || []; var cols = []; var colSep; var colDescrNum; var tagSpans = []; if (group.tags && group.tags.some(tag => tag)) { // An environment with manual tags and/or automatic equation numbers. // Create node(s), the latter of which trigger CSS counter increment. for (r = 0; r < nr; ++r) { var rw = body[r]; var shift = rw.pos - offset; var tag = group.tags[r]; var tagSpan = void 0; if (tag === true) { // automatic numbering tagSpan = buildCommon.makeSpan(["eqn-num"], [], options); } else if (tag === false) { // \nonumber/\notag or starred environment tagSpan = buildCommon.makeSpan([], [], options); } else { // manual \tag tagSpan = buildCommon.makeSpan([], buildExpression$1(tag, options, true), options); } tagSpan.depth = rw.depth; tagSpan.height = rw.height; tagSpans.push({ type: "elem", elem: tagSpan, shift }); } } for (c = 0, colDescrNum = 0; // Continue while either there are more columns or more column // descriptions, so trailing separators don't get lost. c < nc || colDescrNum < colDescriptions.length; ++c, ++colDescrNum) { var colDescr = colDescriptions[colDescrNum] || {}; var firstSeparator = true; while (colDescr.type === "separator") { // If there is more than one separator in a row, add a space // between them. if (!firstSeparator) { colSep = buildCommon.makeSpan(["arraycolsep"], []); colSep.style.width = makeEm(options.fontMetrics().doubleRuleSep); cols.push(colSep); } if (colDescr.separator === "|" || colDescr.separator === ":") { var lineType = colDescr.separator === "|" ? "solid" : "dashed"; var separator = buildCommon.makeSpan(["vertical-separator"], [], options); separator.style.height = makeEm(totalHeight); separator.style.borderRightWidth = makeEm(ruleThickness); separator.style.borderRightStyle = lineType; separator.style.margin = "0 " + makeEm(-ruleThickness / 2); var _shift = totalHeight - offset; if (_shift) { separator.style.verticalAlign = makeEm(-_shift); } cols.push(separator); } else { throw new ParseError("Invalid separator type: " + colDescr.separator); } colDescrNum++; colDescr = colDescriptions[colDescrNum] || {}; firstSeparator = false; } if (c >= nc) { continue; } var sepwidth = void 0; if (c > 0 || group.hskipBeforeAndAfter) { sepwidth = utils.deflt(colDescr.pregap, arraycolsep); if (sepwidth !== 0) { colSep = buildCommon.makeSpan(["arraycolsep"], []); colSep.style.width = makeEm(sepwidth); cols.push(colSep); } } var col = []; for (r = 0; r < nr; ++r) { var row = body[r]; var elem = row[c]; if (!elem) { continue; } var _shift2 = row.pos - offset; elem.depth = row.depth; elem.height = row.height; col.push({ type: "elem", elem: elem, shift: _shift2 }); } col = buildCommon.makeVList({ positionType: "individualShift", children: col }, options); col = buildCommon.makeSpan(["col-align-" + (colDescr.align || "c")], [col]); cols.push(col); if (c < nc - 1 || group.hskipBeforeAndAfter) { sepwidth = utils.deflt(colDescr.postgap, arraycolsep); if (sepwidth !== 0) { colSep = buildCommon.makeSpan(["arraycolsep"], []); colSep.style.width = makeEm(sepwidth); cols.push(colSep); } } } body = buildCommon.makeSpan(["mtable"], cols); // Add \hline(s), if any. if (hlines.length > 0) { var line = buildCommon.makeLineSpan("hline", options, ruleThickness); var dashes = buildCommon.makeLineSpan("hdashline", options, ruleThickness); var vListElems = [{ type: "elem", elem: body, shift: 0 }]; while (hlines.length > 0) { var hline = hlines.pop(); var lineShift = hline.pos - offset; if (hline.isDashed) { vListElems.push({ type: "elem", elem: dashes, shift: lineShift }); } else { vListElems.push({ type: "elem", elem: line, shift: lineShift }); } } body = buildCommon.makeVList({ positionType: "individualShift", children: vListElems }, options); } if (tagSpans.length === 0) { return buildCommon.makeSpan(["mord"], [body], options); } else { var eqnNumCol = buildCommon.makeVList({ positionType: "individualShift", children: tagSpans }, options); eqnNumCol = buildCommon.makeSpan(["tag"], [eqnNumCol], options); return buildCommon.makeFragment([body, eqnNumCol]); } }; var alignMap = { c: "center ", l: "left ", r: "right " }; var mathmlBuilder$5 = function mathmlBuilder(group, options) { var tbl = []; var glue = new mathMLTree.MathNode("mtd", [], ["mtr-glue"]); var tag = new mathMLTree.MathNode("mtd", [], ["mml-eqn-num"]); for (var i = 0; i < group.body.length; i++) { var rw = group.body[i]; var row = []; for (var j = 0; j < rw.length; j++) { row.push(new mathMLTree.MathNode("mtd", [buildGroup(rw[j], options)])); } if (group.tags && group.tags[i]) { row.unshift(glue); row.push(glue); if (group.leqno) { row.unshift(tag); } else { row.push(tag); } } tbl.push(new mathMLTree.MathNode("mtr", row)); } var table = new mathMLTree.MathNode("mtable", tbl); // Set column alignment, row spacing, column spacing, and // array lines by setting attributes on the table element. // Set the row spacing. In MathML, we specify a gap distance. // We do not use rowGap[] because MathML automatically increases // cell height with the height/depth of the element content. // LaTeX \arraystretch multiplies the row baseline-to-baseline distance. // We simulate this by adding (arraystretch - 1)em to the gap. This // does a reasonable job of adjusting arrays containing 1 em tall content. // The 0.16 and 0.09 values are found empirically. They produce an array // similar to LaTeX and in which content does not interfere with \hlines. var gap = group.arraystretch === 0.5 ? 0.1 // {smallmatrix}, {subarray} : 0.16 + group.arraystretch - 1 + (group.addJot ? 0.09 : 0); table.setAttribute("rowspacing", makeEm(gap)); // MathML table lines go only between cells. // To place a line on an edge we'll use , if necessary. var menclose = ""; var align = ""; if (group.cols && group.cols.length > 0) { // Find column alignment, column spacing, and vertical lines. var cols = group.cols; var columnLines = ""; var prevTypeWasAlign = false; var iStart = 0; var iEnd = cols.length; if (cols[0].type === "separator") { menclose += "top "; iStart = 1; } if (cols[cols.length - 1].type === "separator") { menclose += "bottom "; iEnd -= 1; } for (var _i = iStart; _i < iEnd; _i++) { if (cols[_i].type === "align") { align += alignMap[cols[_i].align]; if (prevTypeWasAlign) { columnLines += "none "; } prevTypeWasAlign = true; } else if (cols[_i].type === "separator") { // MathML accepts only single lines between cells. // So we read only the first of consecutive separators. if (prevTypeWasAlign) { columnLines += cols[_i].separator === "|" ? "solid " : "dashed "; prevTypeWasAlign = false; } } } table.setAttribute("columnalign", align.trim()); if (/[sd]/.test(columnLines)) { table.setAttribute("columnlines", columnLines.trim()); } } // Set column spacing. if (group.colSeparationType === "align") { var _cols = group.cols || []; var spacing = ""; for (var _i2 = 1; _i2 < _cols.length; _i2++) { spacing += _i2 % 2 ? "0em " : "1em "; } table.setAttribute("columnspacing", spacing.trim()); } else if (group.colSeparationType === "alignat" || group.colSeparationType === "gather") { table.setAttribute("columnspacing", "0em"); } else if (group.colSeparationType === "small") { table.setAttribute("columnspacing", "0.2778em"); } else if (group.colSeparationType === "CD") { table.setAttribute("columnspacing", "0.5em"); } else { table.setAttribute("columnspacing", "1em"); } // Address \hline and \hdashline var rowLines = ""; var hlines = group.hLinesBeforeRow; menclose += hlines[0].length > 0 ? "left " : ""; menclose += hlines[hlines.length - 1].length > 0 ? "right " : ""; for (var _i3 = 1; _i3 < hlines.length - 1; _i3++) { rowLines += hlines[_i3].length === 0 ? "none " // MathML accepts only a single line between rows. Read one element. : hlines[_i3][0] ? "dashed " : "solid "; } if (/[sd]/.test(rowLines)) { table.setAttribute("rowlines", rowLines.trim()); } if (menclose !== "") { table = new mathMLTree.MathNode("menclose", [table]); table.setAttribute("notation", menclose.trim()); } if (group.arraystretch && group.arraystretch < 1) { // A small array. Wrap in scriptstyle so row gap is not too large. table = new mathMLTree.MathNode("mstyle", [table]); table.setAttribute("scriptlevel", "1"); } return table; }; // Convenience function for align, align*, aligned, alignat, alignat*, alignedat. var alignedHandler = function alignedHandler(context, args) { if (context.envName.indexOf("ed") === -1) { validateAmsEnvironmentContext(context); } var cols = []; var separationType = context.envName.indexOf("at") > -1 ? "alignat" : "align"; var isSplit = context.envName === "split"; var res = parseArray(context.parser, { cols, addJot: true, autoTag: isSplit ? undefined : getAutoTag(context.envName), emptySingleRow: true, colSeparationType: separationType, maxNumCols: isSplit ? 2 : undefined, leqno: context.parser.settings.leqno }, "display"); // Determining number of columns. // 1. If the first argument is given, we use it as a number of columns, // and makes sure that each row doesn't exceed that number. // 2. Otherwise, just count number of columns = maximum number // of cells in each row ("aligned" mode -- isAligned will be true). // // At the same time, prepend empty group {} at beginning of every second // cell in each row (starting with second cell) so that operators become // binary. This behavior is implemented in amsmath's \start@aligned. var numMaths; var numCols = 0; var emptyGroup = { type: "ordgroup", mode: context.mode, body: [] }; if (args[0] && args[0].type === "ordgroup") { var arg0 = ""; for (var i = 0; i < args[0].body.length; i++) { var textord = assertNodeType(args[0].body[i], "textord"); arg0 += textord.text; } numMaths = Number(arg0); numCols = numMaths * 2; } var isAligned = !numCols; res.body.forEach(function (row) { for (var _i4 = 1; _i4 < row.length; _i4 += 2) { // Modify ordgroup node within styling node var styling = assertNodeType(row[_i4], "styling"); var ordgroup = assertNodeType(styling.body[0], "ordgroup"); ordgroup.body.unshift(emptyGroup); } if (!isAligned) { // Case 1 var curMaths = row.length / 2; if (numMaths < curMaths) { throw new ParseError("Too many math in a row: " + ("expected " + numMaths + ", but got " + curMaths), row[0]); } } else if (numCols < row.length) { // Case 2 numCols = row.length; } }); // Adjusting alignment. // In aligned mode, we add one \qquad between columns; // otherwise we add nothing. for (var _i5 = 0; _i5 < numCols; ++_i5) { var align = "r"; var pregap = 0; if (_i5 % 2 === 1) { align = "l"; } else if (_i5 > 0 && isAligned) { // "aligned" mode. pregap = 1; // add one \quad } cols[_i5] = { type: "align", align: align, pregap: pregap, postgap: 0 }; } res.colSeparationType = isAligned ? "align" : "alignat"; return res; }; // Arrays are part of LaTeX, defined in lttab.dtx so its documentation // is part of the source2e.pdf file of LaTeX2e source documentation. // {darray} is an {array} environment where cells are set in \displaystyle, // as defined in nccmath.sty. defineEnvironment({ type: "array", names: ["array", "darray"], props: { numArgs: 1 }, handler(context, args) { // Since no types are specified above, the two possibilities are // - The argument is wrapped in {} or [], in which case Parser's // parseGroup() returns an "ordgroup" wrapping some symbol node. // - The argument is a bare symbol node. var symNode = checkSymbolNodeType(args[0]); var colalign = symNode ? [args[0]] : assertNodeType(args[0], "ordgroup").body; var cols = colalign.map(function (nde) { var node = assertSymbolNodeType(nde); var ca = node.text; if ("lcr".indexOf(ca) !== -1) { return { type: "align", align: ca }; } else if (ca === "|") { return { type: "separator", separator: "|" }; } else if (ca === ":") { return { type: "separator", separator: ":" }; } throw new ParseError("Unknown column alignment: " + ca, nde); }); var res = { cols, hskipBeforeAndAfter: true, // \@preamble in lttab.dtx maxNumCols: cols.length }; return parseArray(context.parser, res, dCellStyle(context.envName)); }, htmlBuilder: htmlBuilder$6, mathmlBuilder: mathmlBuilder$5 }); // The matrix environments of amsmath builds on the array environment // of LaTeX, which is discussed above. // The mathtools package adds starred versions of the same environments. // These have an optional argument to choose left|center|right justification. defineEnvironment({ type: "array", names: ["matrix", "pmatrix", "bmatrix", "Bmatrix", "vmatrix", "Vmatrix", "matrix*", "pmatrix*", "bmatrix*", "Bmatrix*", "vmatrix*", "Vmatrix*"], props: { numArgs: 0 }, handler(context) { var delimiters = { "matrix": null, "pmatrix": ["(", ")"], "bmatrix": ["[", "]"], "Bmatrix": ["\\{", "\\}"], "vmatrix": ["|", "|"], "Vmatrix": ["\\Vert", "\\Vert"] }[context.envName.replace("*", "")]; // \hskip -\arraycolsep in amsmath var colAlign = "c"; var payload = { hskipBeforeAndAfter: false, cols: [{ type: "align", align: colAlign }] }; if (context.envName.charAt(context.envName.length - 1) === "*") { // It's one of the mathtools starred functions. // Parse the optional alignment argument. var parser = context.parser; parser.consumeSpaces(); if (parser.fetch().text === "[") { parser.consume(); parser.consumeSpaces(); colAlign = parser.fetch().text; if ("lcr".indexOf(colAlign) === -1) { throw new ParseError("Expected l or c or r", parser.nextToken); } parser.consume(); parser.consumeSpaces(); parser.expect("]"); parser.consume(); payload.cols = [{ type: "align", align: colAlign }]; } } var res = parseArray(context.parser, payload, dCellStyle(context.envName)); // Populate cols with the correct number of column alignment specs. var numCols = Math.max(0, ...res.body.map(row => row.length)); res.cols = new Array(numCols).fill({ type: "align", align: colAlign }); return delimiters ? { type: "leftright", mode: context.mode, body: [res], left: delimiters[0], right: delimiters[1], rightColor: undefined // \right uninfluenced by \color in array } : res; }, htmlBuilder: htmlBuilder$6, mathmlBuilder: mathmlBuilder$5 }); defineEnvironment({ type: "array", names: ["smallmatrix"], props: { numArgs: 0 }, handler(context) { var payload = { arraystretch: 0.5 }; var res = parseArray(context.parser, payload, "script"); res.colSeparationType = "small"; return res; }, htmlBuilder: htmlBuilder$6, mathmlBuilder: mathmlBuilder$5 }); defineEnvironment({ type: "array", names: ["subarray"], props: { numArgs: 1 }, handler(context, args) { // Parsing of {subarray} is similar to {array} var symNode = checkSymbolNodeType(args[0]); var colalign = symNode ? [args[0]] : assertNodeType(args[0], "ordgroup").body; var cols = colalign.map(function (nde) { var node = assertSymbolNodeType(nde); var ca = node.text; // {subarray} only recognizes "l" & "c" if ("lc".indexOf(ca) !== -1) { return { type: "align", align: ca }; } throw new ParseError("Unknown column alignment: " + ca, nde); }); if (cols.length > 1) { throw new ParseError("{subarray} can contain only one column"); } var res = { cols, hskipBeforeAndAfter: false, arraystretch: 0.5 }; res = parseArray(context.parser, res, "script"); if (res.body.length > 0 && res.body[0].length > 1) { throw new ParseError("{subarray} can contain only one column"); } return res; }, htmlBuilder: htmlBuilder$6, mathmlBuilder: mathmlBuilder$5 }); // A cases environment (in amsmath.sty) is almost equivalent to // \def\arraystretch{1.2}% // \left\{\begin{array}{@{}l@{\quad}l@{}} … \end{array}\right. // {dcases} is a {cases} environment where cells are set in \displaystyle, // as defined in mathtools.sty. // {rcases} is another mathtools environment. It's brace is on the right side. defineEnvironment({ type: "array", names: ["cases", "dcases", "rcases", "drcases"], props: { numArgs: 0 }, handler(context) { var payload = { arraystretch: 1.2, cols: [{ type: "align", align: "l", pregap: 0, // TODO(kevinb) get the current style. // For now we use the metrics for TEXT style which is what we were // doing before. Before attempting to get the current style we // should look at TeX's behavior especially for \over and matrices. postgap: 1.0 /* 1em quad */ }, { type: "align", align: "l", pregap: 0, postgap: 0 }] }; var res = parseArray(context.parser, payload, dCellStyle(context.envName)); return { type: "leftright", mode: context.mode, body: [res], left: context.envName.indexOf("r") > -1 ? "." : "\\{", right: context.envName.indexOf("r") > -1 ? "\\}" : ".", rightColor: undefined }; }, htmlBuilder: htmlBuilder$6, mathmlBuilder: mathmlBuilder$5 }); // In the align environment, one uses ampersands, &, to specify number of // columns in each row, and to locate spacing between each column. // align gets automatic numbering. align* and aligned do not. // The alignedat environment can be used in math mode. // Note that we assume \nomallineskiplimit to be zero, // so that \strut@ is the same as \strut. defineEnvironment({ type: "array", names: ["align", "align*", "aligned", "split"], props: { numArgs: 0 }, handler: alignedHandler, htmlBuilder: htmlBuilder$6, mathmlBuilder: mathmlBuilder$5 }); // A gathered environment is like an array environment with one centered // column, but where rows are considered lines so get \jot line spacing // and contents are set in \displaystyle. defineEnvironment({ type: "array", names: ["gathered", "gather", "gather*"], props: { numArgs: 0 }, handler(context) { if (utils.contains(["gather", "gather*"], context.envName)) { validateAmsEnvironmentContext(context); } var res = { cols: [{ type: "align", align: "c" }], addJot: true, colSeparationType: "gather", autoTag: getAutoTag(context.envName), emptySingleRow: true, leqno: context.parser.settings.leqno }; return parseArray(context.parser, res, "display"); }, htmlBuilder: htmlBuilder$6, mathmlBuilder: mathmlBuilder$5 }); // alignat environment is like an align environment, but one must explicitly // specify maximum number of columns in each row, and can adjust spacing between // each columns. defineEnvironment({ type: "array", names: ["alignat", "alignat*", "alignedat"], props: { numArgs: 1 }, handler: alignedHandler, htmlBuilder: htmlBuilder$6, mathmlBuilder: mathmlBuilder$5 }); defineEnvironment({ type: "array", names: ["equation", "equation*"], props: { numArgs: 0 }, handler(context) { validateAmsEnvironmentContext(context); var res = { autoTag: getAutoTag(context.envName), emptySingleRow: true, singleRow: true, maxNumCols: 1, leqno: context.parser.settings.leqno }; return parseArray(context.parser, res, "display"); }, htmlBuilder: htmlBuilder$6, mathmlBuilder: mathmlBuilder$5 }); defineEnvironment({ type: "array", names: ["CD"], props: { numArgs: 0 }, handler(context) { validateAmsEnvironmentContext(context); return parseCD(context.parser); }, htmlBuilder: htmlBuilder$6, mathmlBuilder: mathmlBuilder$5 }); defineMacro("\\nonumber", "\\gdef\\@eqnsw{0}"); defineMacro("\\notag", "\\nonumber"); // Catch \hline outside array environment defineFunction({ type: "text", // Doesn't matter what this is. names: ["\\hline", "\\hdashline"], props: { numArgs: 0, allowedInText: true, allowedInMath: true }, handler(context, args) { throw new ParseError(context.funcName + " valid only within array environment"); } }); var environments = _environments; // defineEnvironment definitions. defineFunction({ type: "environment", names: ["\\begin", "\\end"], props: { numArgs: 1, argTypes: ["text"] }, handler(_ref, args) { var { parser, funcName } = _ref; var nameGroup = args[0]; if (nameGroup.type !== "ordgroup") { throw new ParseError("Invalid environment name", nameGroup); } var envName = ""; for (var i = 0; i < nameGroup.body.length; ++i) { envName += assertNodeType(nameGroup.body[i], "textord").text; } if (funcName === "\\begin") { // begin...end is similar to left...right if (!environments.hasOwnProperty(envName)) { throw new ParseError("No such environment: " + envName, nameGroup); } // Build the environment object. Arguments and other information will // be made available to the begin and end methods using properties. var env = environments[envName]; var { args: _args, optArgs } = parser.parseArguments("\\begin{" + envName + "}", env); var context = { mode: parser.mode, envName, parser }; var result = env.handler(context, _args, optArgs); parser.expect("\\end", false); var endNameToken = parser.nextToken; var end = assertNodeType(parser.parseFunction(), "environment"); if (end.name !== envName) { throw new ParseError("Mismatch: \\begin{" + envName + "} matched by \\end{" + end.name + "}", endNameToken); } // $FlowFixMe, "environment" handler returns an environment ParseNode return result; } return { type: "environment", mode: parser.mode, name: envName, nameGroup }; } }); // TODO(kevinb): implement \\sl and \\sc var htmlBuilder$5 = (group, options) => { var font = group.font; var newOptions = options.withFont(font); return buildGroup$1(group.body, newOptions); }; var mathmlBuilder$4 = (group, options) => { var font = group.font; var newOptions = options.withFont(font); return buildGroup(group.body, newOptions); }; var fontAliases = { "\\Bbb": "\\mathbb", "\\bold": "\\mathbf", "\\frak": "\\mathfrak", "\\bm": "\\boldsymbol" }; defineFunction({ type: "font", names: [// styles, except \boldsymbol defined below "\\mathrm", "\\mathit", "\\mathbf", "\\mathnormal", "\\mathsfit", // families "\\mathbb", "\\mathcal", "\\mathfrak", "\\mathscr", "\\mathsf", "\\mathtt", // aliases, except \bm defined below "\\Bbb", "\\bold", "\\frak"], props: { numArgs: 1, allowedInArgument: true }, handler: (_ref, args) => { var { parser, funcName } = _ref; var body = normalizeArgument(args[0]); var func = funcName; if (func in fontAliases) { func = fontAliases[func]; } return { type: "font", mode: parser.mode, font: func.slice(1), body }; }, htmlBuilder: htmlBuilder$5, mathmlBuilder: mathmlBuilder$4 }); defineFunction({ type: "mclass", names: ["\\boldsymbol", "\\bm"], props: { numArgs: 1 }, handler: (_ref2, args) => { var { parser } = _ref2; var body = args[0]; var isCharacterBox = utils.isCharacterBox(body); // amsbsy.sty's \boldsymbol uses \binrel spacing to inherit the // argument's bin|rel|ord status return { type: "mclass", mode: parser.mode, mclass: binrelClass(body), body: [{ type: "font", mode: parser.mode, font: "boldsymbol", body }], isCharacterBox: isCharacterBox }; } }); // Old font changing functions defineFunction({ type: "font", names: ["\\rm", "\\sf", "\\tt", "\\bf", "\\it", "\\cal"], props: { numArgs: 0, allowedInText: true }, handler: (_ref3, args) => { var { parser, funcName, breakOnTokenText } = _ref3; var { mode } = parser; var body = parser.parseExpression(true, breakOnTokenText); var style = "math" + funcName.slice(1); return { type: "font", mode: mode, font: style, body: { type: "ordgroup", mode: parser.mode, body } }; }, htmlBuilder: htmlBuilder$5, mathmlBuilder: mathmlBuilder$4 }); var adjustStyle = (size, originalStyle) => { // Figure out what style this fraction should be in based on the // function used var style = originalStyle; if (size === "display") { // Get display style as a default. // If incoming style is sub/sup, use style.text() to get correct size. style = style.id >= Style$1.SCRIPT.id ? style.text() : Style$1.DISPLAY; } else if (size === "text" && style.size === Style$1.DISPLAY.size) { // We're in a \tfrac but incoming style is displaystyle, so: style = Style$1.TEXT; } else if (size === "script") { style = Style$1.SCRIPT; } else if (size === "scriptscript") { style = Style$1.SCRIPTSCRIPT; } return style; }; var htmlBuilder$4 = (group, options) => { // Fractions are handled in the TeXbook on pages 444-445, rules 15(a-e). var style = adjustStyle(group.size, options.style); var nstyle = style.fracNum(); var dstyle = style.fracDen(); var newOptions; newOptions = options.havingStyle(nstyle); var numerm = buildGroup$1(group.numer, newOptions, options); if (group.continued) { // \cfrac inserts a \strut into the numerator. // Get \strut dimensions from TeXbook page 353. var hStrut = 8.5 / options.fontMetrics().ptPerEm; var dStrut = 3.5 / options.fontMetrics().ptPerEm; numerm.height = numerm.height < hStrut ? hStrut : numerm.height; numerm.depth = numerm.depth < dStrut ? dStrut : numerm.depth; } newOptions = options.havingStyle(dstyle); var denomm = buildGroup$1(group.denom, newOptions, options); var rule; var ruleWidth; var ruleSpacing; if (group.hasBarLine) { if (group.barSize) { ruleWidth = calculateSize(group.barSize, options); rule = buildCommon.makeLineSpan("frac-line", options, ruleWidth); } else { rule = buildCommon.makeLineSpan("frac-line", options); } ruleWidth = rule.height; ruleSpacing = rule.height; } else { rule = null; ruleWidth = 0; ruleSpacing = options.fontMetrics().defaultRuleThickness; } // Rule 15b var numShift; var clearance; var denomShift; if (style.size === Style$1.DISPLAY.size || group.size === "display") { numShift = options.fontMetrics().num1; if (ruleWidth > 0) { clearance = 3 * ruleSpacing; } else { clearance = 7 * ruleSpacing; } denomShift = options.fontMetrics().denom1; } else { if (ruleWidth > 0) { numShift = options.fontMetrics().num2; clearance = ruleSpacing; } else { numShift = options.fontMetrics().num3; clearance = 3 * ruleSpacing; } denomShift = options.fontMetrics().denom2; } var frac; if (!rule) { // Rule 15c var candidateClearance = numShift - numerm.depth - (denomm.height - denomShift); if (candidateClearance < clearance) { numShift += 0.5 * (clearance - candidateClearance); denomShift += 0.5 * (clearance - candidateClearance); } frac = buildCommon.makeVList({ positionType: "individualShift", children: [{ type: "elem", elem: denomm, shift: denomShift }, { type: "elem", elem: numerm, shift: -numShift }] }, options); } else { // Rule 15d var axisHeight = options.fontMetrics().axisHeight; if (numShift - numerm.depth - (axisHeight + 0.5 * ruleWidth) < clearance) { numShift += clearance - (numShift - numerm.depth - (axisHeight + 0.5 * ruleWidth)); } if (axisHeight - 0.5 * ruleWidth - (denomm.height - denomShift) < clearance) { denomShift += clearance - (axisHeight - 0.5 * ruleWidth - (denomm.height - denomShift)); } var midShift = -(axisHeight - 0.5 * ruleWidth); frac = buildCommon.makeVList({ positionType: "individualShift", children: [{ type: "elem", elem: denomm, shift: denomShift }, { type: "elem", elem: rule, shift: midShift }, { type: "elem", elem: numerm, shift: -numShift }] }, options); } // Since we manually change the style sometimes (with \dfrac or \tfrac), // account for the possible size change here. newOptions = options.havingStyle(style); frac.height *= newOptions.sizeMultiplier / options.sizeMultiplier; frac.depth *= newOptions.sizeMultiplier / options.sizeMultiplier; // Rule 15e var delimSize; if (style.size === Style$1.DISPLAY.size) { delimSize = options.fontMetrics().delim1; } else if (style.size === Style$1.SCRIPTSCRIPT.size) { delimSize = options.havingStyle(Style$1.SCRIPT).fontMetrics().delim2; } else { delimSize = options.fontMetrics().delim2; } var leftDelim; var rightDelim; if (group.leftDelim == null) { leftDelim = makeNullDelimiter(options, ["mopen"]); } else { leftDelim = delimiter.customSizedDelim(group.leftDelim, delimSize, true, options.havingStyle(style), group.mode, ["mopen"]); } if (group.continued) { rightDelim = buildCommon.makeSpan([]); // zero width for \cfrac } else if (group.rightDelim == null) { rightDelim = makeNullDelimiter(options, ["mclose"]); } else { rightDelim = delimiter.customSizedDelim(group.rightDelim, delimSize, true, options.havingStyle(style), group.mode, ["mclose"]); } return buildCommon.makeSpan(["mord"].concat(newOptions.sizingClasses(options)), [leftDelim, buildCommon.makeSpan(["mfrac"], [frac]), rightDelim], options); }; var mathmlBuilder$3 = (group, options) => { var node = new mathMLTree.MathNode("mfrac", [buildGroup(group.numer, options), buildGroup(group.denom, options)]); if (!group.hasBarLine) { node.setAttribute("linethickness", "0px"); } else if (group.barSize) { var ruleWidth = calculateSize(group.barSize, options); node.setAttribute("linethickness", makeEm(ruleWidth)); } var style = adjustStyle(group.size, options.style); if (style.size !== options.style.size) { node = new mathMLTree.MathNode("mstyle", [node]); var isDisplay = style.size === Style$1.DISPLAY.size ? "true" : "false"; node.setAttribute("displaystyle", isDisplay); node.setAttribute("scriptlevel", "0"); } if (group.leftDelim != null || group.rightDelim != null) { var withDelims = []; if (group.leftDelim != null) { var leftOp = new mathMLTree.MathNode("mo", [new mathMLTree.TextNode(group.leftDelim.replace("\\", ""))]); leftOp.setAttribute("fence", "true"); withDelims.push(leftOp); } withDelims.push(node); if (group.rightDelim != null) { var rightOp = new mathMLTree.MathNode("mo", [new mathMLTree.TextNode(group.rightDelim.replace("\\", ""))]); rightOp.setAttribute("fence", "true"); withDelims.push(rightOp); } return makeRow(withDelims); } return node; }; defineFunction({ type: "genfrac", names: ["\\dfrac", "\\frac", "\\tfrac", "\\dbinom", "\\binom", "\\tbinom", "\\\\atopfrac", // can’t be entered directly "\\\\bracefrac", "\\\\brackfrac" // ditto ], props: { numArgs: 2, allowedInArgument: true }, handler: (_ref, args) => { var { parser, funcName } = _ref; var numer = args[0]; var denom = args[1]; var hasBarLine; var leftDelim = null; var rightDelim = null; var size = "auto"; switch (funcName) { case "\\dfrac": case "\\frac": case "\\tfrac": hasBarLine = true; break; case "\\\\atopfrac": hasBarLine = false; break; case "\\dbinom": case "\\binom": case "\\tbinom": hasBarLine = false; leftDelim = "("; rightDelim = ")"; break; case "\\\\bracefrac": hasBarLine = false; leftDelim = "\\{"; rightDelim = "\\}"; break; case "\\\\brackfrac": hasBarLine = false; leftDelim = "["; rightDelim = "]"; break; default: throw new Error("Unrecognized genfrac command"); } switch (funcName) { case "\\dfrac": case "\\dbinom": size = "display"; break; case "\\tfrac": case "\\tbinom": size = "text"; break; } return { type: "genfrac", mode: parser.mode, continued: false, numer, denom, hasBarLine, leftDelim, rightDelim, size, barSize: null }; }, htmlBuilder: htmlBuilder$4, mathmlBuilder: mathmlBuilder$3 }); defineFunction({ type: "genfrac", names: ["\\cfrac"], props: { numArgs: 2 }, handler: (_ref2, args) => { var { parser, funcName } = _ref2; var numer = args[0]; var denom = args[1]; return { type: "genfrac", mode: parser.mode, continued: true, numer, denom, hasBarLine: true, leftDelim: null, rightDelim: null, size: "display", barSize: null }; } }); // Infix generalized fractions -- these are not rendered directly, but replaced // immediately by one of the variants above. defineFunction({ type: "infix", names: ["\\over", "\\choose", "\\atop", "\\brace", "\\brack"], props: { numArgs: 0, infix: true }, handler(_ref3) { var { parser, funcName, token } = _ref3; var replaceWith; switch (funcName) { case "\\over": replaceWith = "\\frac"; break; case "\\choose": replaceWith = "\\binom"; break; case "\\atop": replaceWith = "\\\\atopfrac"; break; case "\\brace": replaceWith = "\\\\bracefrac"; break; case "\\brack": replaceWith = "\\\\brackfrac"; break; default: throw new Error("Unrecognized infix genfrac command"); } return { type: "infix", mode: parser.mode, replaceWith, token }; } }); var stylArray = ["display", "text", "script", "scriptscript"]; var delimFromValue = function delimFromValue(delimString) { var delim = null; if (delimString.length > 0) { delim = delimString; delim = delim === "." ? null : delim; } return delim; }; defineFunction({ type: "genfrac", names: ["\\genfrac"], props: { numArgs: 6, allowedInArgument: true, argTypes: ["math", "math", "size", "text", "math", "math"] }, handler(_ref4, args) { var { parser } = _ref4; var numer = args[4]; var denom = args[5]; // Look into the parse nodes to get the desired delimiters. var leftNode = normalizeArgument(args[0]); var leftDelim = leftNode.type === "atom" && leftNode.family === "open" ? delimFromValue(leftNode.text) : null; var rightNode = normalizeArgument(args[1]); var rightDelim = rightNode.type === "atom" && rightNode.family === "close" ? delimFromValue(rightNode.text) : null; var barNode = assertNodeType(args[2], "size"); var hasBarLine; var barSize = null; if (barNode.isBlank) { // \genfrac acts differently than \above. // \genfrac treats an empty size group as a signal to use a // standard bar size. \above would see size = 0 and omit the bar. hasBarLine = true; } else { barSize = barNode.value; hasBarLine = barSize.number > 0; } // Find out if we want displaystyle, textstyle, etc. var size = "auto"; var styl = args[3]; if (styl.type === "ordgroup") { if (styl.body.length > 0) { var textOrd = assertNodeType(styl.body[0], "textord"); size = stylArray[Number(textOrd.text)]; } } else { styl = assertNodeType(styl, "textord"); size = stylArray[Number(styl.text)]; } return { type: "genfrac", mode: parser.mode, numer, denom, continued: false, hasBarLine, barSize, leftDelim, rightDelim, size }; }, htmlBuilder: htmlBuilder$4, mathmlBuilder: mathmlBuilder$3 }); // \above is an infix fraction that also defines a fraction bar size. defineFunction({ type: "infix", names: ["\\above"], props: { numArgs: 1, argTypes: ["size"], infix: true }, handler(_ref5, args) { var { parser, funcName, token } = _ref5; return { type: "infix", mode: parser.mode, replaceWith: "\\\\abovefrac", size: assertNodeType(args[0], "size").value, token }; } }); defineFunction({ type: "genfrac", names: ["\\\\abovefrac"], props: { numArgs: 3, argTypes: ["math", "size", "math"] }, handler: (_ref6, args) => { var { parser, funcName } = _ref6; var numer = args[0]; var barSize = assert(assertNodeType(args[1], "infix").size); var denom = args[2]; var hasBarLine = barSize.number > 0; return { type: "genfrac", mode: parser.mode, numer, denom, continued: false, hasBarLine, barSize, leftDelim: null, rightDelim: null, size: "auto" }; }, htmlBuilder: htmlBuilder$4, mathmlBuilder: mathmlBuilder$3 }); // NOTE: Unlike most `htmlBuilder`s, this one handles not only "horizBrace", but // also "supsub" since an over/underbrace can affect super/subscripting. var htmlBuilder$3 = (grp, options) => { var style = options.style; // Pull out the `ParseNode<"horizBrace">` if `grp` is a "supsub" node. var supSubGroup; var group; if (grp.type === "supsub") { // Ref: LaTeX source2e: }}}}\limits} // i.e. LaTeX treats the brace similar to an op and passes it // with \limits, so we need to assign supsub style. supSubGroup = grp.sup ? buildGroup$1(grp.sup, options.havingStyle(style.sup()), options) : buildGroup$1(grp.sub, options.havingStyle(style.sub()), options); group = assertNodeType(grp.base, "horizBrace"); } else { group = assertNodeType(grp, "horizBrace"); } // Build the base group var body = buildGroup$1(group.base, options.havingBaseStyle(Style$1.DISPLAY)); // Create the stretchy element var braceBody = stretchy.svgSpan(group, options); // Generate the vlist, with the appropriate kerns ┏━━━━━━━━┓ // This first vlist contains the content and the brace: equation var vlist; if (group.isOver) { vlist = buildCommon.makeVList({ positionType: "firstBaseline", children: [{ type: "elem", elem: body }, { type: "kern", size: 0.1 }, { type: "elem", elem: braceBody }] }, options); // $FlowFixMe: Replace this with passing "svg-align" into makeVList. vlist.children[0].children[0].children[1].classes.push("svg-align"); } else { vlist = buildCommon.makeVList({ positionType: "bottom", positionData: body.depth + 0.1 + braceBody.height, children: [{ type: "elem", elem: braceBody }, { type: "kern", size: 0.1 }, { type: "elem", elem: body }] }, options); // $FlowFixMe: Replace this with passing "svg-align" into makeVList. vlist.children[0].children[0].children[0].classes.push("svg-align"); } if (supSubGroup) { // To write the supsub, wrap the first vlist in another vlist: // They can't all go in the same vlist, because the note might be // wider than the equation. We want the equation to control the // brace width. // note long note long note // ┏━━━━━━━━┓ or ┏━━━┓ not ┏━━━━━━━━━┓ // equation eqn eqn var vSpan = buildCommon.makeSpan(["mord", group.isOver ? "mover" : "munder"], [vlist], options); if (group.isOver) { vlist = buildCommon.makeVList({ positionType: "firstBaseline", children: [{ type: "elem", elem: vSpan }, { type: "kern", size: 0.2 }, { type: "elem", elem: supSubGroup }] }, options); } else { vlist = buildCommon.makeVList({ positionType: "bottom", positionData: vSpan.depth + 0.2 + supSubGroup.height + supSubGroup.depth, children: [{ type: "elem", elem: supSubGroup }, { type: "kern", size: 0.2 }, { type: "elem", elem: vSpan }] }, options); } } return buildCommon.makeSpan(["mord", group.isOver ? "mover" : "munder"], [vlist], options); }; var mathmlBuilder$2 = (group, options) => { var accentNode = stretchy.mathMLnode(group.label); return new mathMLTree.MathNode(group.isOver ? "mover" : "munder", [buildGroup(group.base, options), accentNode]); }; // Horizontal stretchy braces defineFunction({ type: "horizBrace", names: ["\\overbrace", "\\underbrace"], props: { numArgs: 1 }, handler(_ref, args) { var { parser, funcName } = _ref; return { type: "horizBrace", mode: parser.mode, label: funcName, isOver: /^\\over/.test(funcName), base: args[0] }; }, htmlBuilder: htmlBuilder$3, mathmlBuilder: mathmlBuilder$2 }); defineFunction({ type: "href", names: ["\\href"], props: { numArgs: 2, argTypes: ["url", "original"], allowedInText: true }, handler: (_ref, args) => { var { parser } = _ref; var body = args[1]; var href = assertNodeType(args[0], "url").url; if (!parser.settings.isTrusted({ command: "\\href", url: href })) { return parser.formatUnsupportedCmd("\\href"); } return { type: "href", mode: parser.mode, href, body: ordargument(body) }; }, htmlBuilder: (group, options) => { var elements = buildExpression$1(group.body, options, false); return buildCommon.makeAnchor(group.href, [], elements, options); }, mathmlBuilder: (group, options) => { var math = buildExpressionRow(group.body, options); if (!(math instanceof MathNode)) { math = new MathNode("mrow", [math]); } math.setAttribute("href", group.href); return math; } }); defineFunction({ type: "href", names: ["\\url"], props: { numArgs: 1, argTypes: ["url"], allowedInText: true }, handler: (_ref2, args) => { var { parser } = _ref2; var href = assertNodeType(args[0], "url").url; if (!parser.settings.isTrusted({ command: "\\url", url: href })) { return parser.formatUnsupportedCmd("\\url"); } var chars = []; for (var i = 0; i < href.length; i++) { var c = href[i]; if (c === "~") { c = "\\textasciitilde"; } chars.push({ type: "textord", mode: "text", text: c }); } var body = { type: "text", mode: parser.mode, font: "\\texttt", body: chars }; return { type: "href", mode: parser.mode, href, body: ordargument(body) }; } }); // In LaTeX, \vcenter can act only on a box, as in // \vcenter{\hbox{$\frac{a+b}{\dfrac{c}{d}}$}} // This function by itself doesn't do anything but prevent a soft line break. defineFunction({ type: "hbox", names: ["\\hbox"], props: { numArgs: 1, argTypes: ["text"], allowedInText: true, primitive: true }, handler(_ref, args) { var { parser } = _ref; return { type: "hbox", mode: parser.mode, body: ordargument(args[0]) }; }, htmlBuilder(group, options) { var elements = buildExpression$1(group.body, options, false); return buildCommon.makeFragment(elements); }, mathmlBuilder(group, options) { return new mathMLTree.MathNode("mrow", buildExpression(group.body, options)); } }); defineFunction({ type: "html", names: ["\\htmlClass", "\\htmlId", "\\htmlStyle", "\\htmlData"], props: { numArgs: 2, argTypes: ["raw", "original"], allowedInText: true }, handler: (_ref, args) => { var { parser, funcName, token } = _ref; var value = assertNodeType(args[0], "raw").string; var body = args[1]; if (parser.settings.strict) { parser.settings.reportNonstrict("htmlExtension", "HTML extension is disabled on strict mode"); } var trustContext; var attributes = {}; switch (funcName) { case "\\htmlClass": attributes.class = value; trustContext = { command: "\\htmlClass", class: value }; break; case "\\htmlId": attributes.id = value; trustContext = { command: "\\htmlId", id: value }; break; case "\\htmlStyle": attributes.style = value; trustContext = { command: "\\htmlStyle", style: value }; break; case "\\htmlData": { var data = value.split(","); for (var i = 0; i < data.length; i++) { var keyVal = data[i].split("="); if (keyVal.length !== 2) { throw new ParseError("Error parsing key-value for \\htmlData"); } attributes["data-" + keyVal[0].trim()] = keyVal[1].trim(); } trustContext = { command: "\\htmlData", attributes }; break; } default: throw new Error("Unrecognized html command"); } if (!parser.settings.isTrusted(trustContext)) { return parser.formatUnsupportedCmd(funcName); } return { type: "html", mode: parser.mode, attributes, body: ordargument(body) }; }, htmlBuilder: (group, options) => { var elements = buildExpression$1(group.body, options, false); var classes = ["enclosing"]; if (group.attributes.class) { classes.push(...group.attributes.class.trim().split(/\s+/)); } var span = buildCommon.makeSpan(classes, elements, options); for (var attr in group.attributes) { if (attr !== "class" && group.attributes.hasOwnProperty(attr)) { span.setAttribute(attr, group.attributes[attr]); } } return span; }, mathmlBuilder: (group, options) => { return buildExpressionRow(group.body, options); } }); defineFunction({ type: "htmlmathml", names: ["\\html@mathml"], props: { numArgs: 2, allowedInText: true }, handler: (_ref, args) => { var { parser } = _ref; return { type: "htmlmathml", mode: parser.mode, html: ordargument(args[0]), mathml: ordargument(args[1]) }; }, htmlBuilder: (group, options) => { var elements = buildExpression$1(group.html, options, false); return buildCommon.makeFragment(elements); }, mathmlBuilder: (group, options) => { return buildExpressionRow(group.mathml, options); } }); var sizeData = function sizeData(str) { if (/^[-+]? *(\d+(\.\d*)?|\.\d+)$/.test(str)) { // str is a number with no unit specified. // default unit is bp, per graphix package. return { number: +str, unit: "bp" }; } else { var match = /([-+]?) *(\d+(?:\.\d*)?|\.\d+) *([a-z]{2})/.exec(str); if (!match) { throw new ParseError("Invalid size: '" + str + "' in \\includegraphics"); } var data = { number: +(match[1] + match[2]), // sign + magnitude, cast to number unit: match[3] }; if (!validUnit(data)) { throw new ParseError("Invalid unit: '" + data.unit + "' in \\includegraphics."); } return data; } }; defineFunction({ type: "includegraphics", names: ["\\includegraphics"], props: { numArgs: 1, numOptionalArgs: 1, argTypes: ["raw", "url"], allowedInText: false }, handler: (_ref, args, optArgs) => { var { parser } = _ref; var width = { number: 0, unit: "em" }; var height = { number: 0.9, unit: "em" }; // sorta character sized. var totalheight = { number: 0, unit: "em" }; var alt = ""; if (optArgs[0]) { var attributeStr = assertNodeType(optArgs[0], "raw").string; // Parser.js does not parse key/value pairs. We get a string. var attributes = attributeStr.split(","); for (var i = 0; i < attributes.length; i++) { var keyVal = attributes[i].split("="); if (keyVal.length === 2) { var str = keyVal[1].trim(); switch (keyVal[0].trim()) { case "alt": alt = str; break; case "width": width = sizeData(str); break; case "height": height = sizeData(str); break; case "totalheight": totalheight = sizeData(str); break; default: throw new ParseError("Invalid key: '" + keyVal[0] + "' in \\includegraphics."); } } } } var src = assertNodeType(args[0], "url").url; if (alt === "") { // No alt given. Use the file name. Strip away the path. alt = src; alt = alt.replace(/^.*[\\/]/, ''); alt = alt.substring(0, alt.lastIndexOf('.')); } if (!parser.settings.isTrusted({ command: "\\includegraphics", url: src })) { return parser.formatUnsupportedCmd("\\includegraphics"); } return { type: "includegraphics", mode: parser.mode, alt: alt, width: width, height: height, totalheight: totalheight, src: src }; }, htmlBuilder: (group, options) => { var height = calculateSize(group.height, options); var depth = 0; if (group.totalheight.number > 0) { depth = calculateSize(group.totalheight, options) - height; } var width = 0; if (group.width.number > 0) { width = calculateSize(group.width, options); } var style = { height: makeEm(height + depth) }; if (width > 0) { style.width = makeEm(width); } if (depth > 0) { style.verticalAlign = makeEm(-depth); } var node = new Img(group.src, group.alt, style); node.height = height; node.depth = depth; return node; }, mathmlBuilder: (group, options) => { var node = new mathMLTree.MathNode("mglyph", []); node.setAttribute("alt", group.alt); var height = calculateSize(group.height, options); var depth = 0; if (group.totalheight.number > 0) { depth = calculateSize(group.totalheight, options) - height; node.setAttribute("valign", makeEm(-depth)); } node.setAttribute("height", makeEm(height + depth)); if (group.width.number > 0) { var width = calculateSize(group.width, options); node.setAttribute("width", makeEm(width)); } node.setAttribute("src", group.src); return node; } }); // Horizontal spacing commands defineFunction({ type: "kern", names: ["\\kern", "\\mkern", "\\hskip", "\\mskip"], props: { numArgs: 1, argTypes: ["size"], primitive: true, allowedInText: true }, handler(_ref, args) { var { parser, funcName } = _ref; var size = assertNodeType(args[0], "size"); if (parser.settings.strict) { var mathFunction = funcName[1] === 'm'; // \mkern, \mskip var muUnit = size.value.unit === 'mu'; if (mathFunction) { if (!muUnit) { parser.settings.reportNonstrict("mathVsTextUnits", "LaTeX's " + funcName + " supports only mu units, " + ("not " + size.value.unit + " units")); } if (parser.mode !== "math") { parser.settings.reportNonstrict("mathVsTextUnits", "LaTeX's " + funcName + " works only in math mode"); } } else { // !mathFunction if (muUnit) { parser.settings.reportNonstrict("mathVsTextUnits", "LaTeX's " + funcName + " doesn't support mu units"); } } } return { type: "kern", mode: parser.mode, dimension: size.value }; }, htmlBuilder(group, options) { return buildCommon.makeGlue(group.dimension, options); }, mathmlBuilder(group, options) { var dimension = calculateSize(group.dimension, options); return new mathMLTree.SpaceNode(dimension); } }); // Horizontal overlap functions defineFunction({ type: "lap", names: ["\\mathllap", "\\mathrlap", "\\mathclap"], props: { numArgs: 1, allowedInText: true }, handler: (_ref, args) => { var { parser, funcName } = _ref; var body = args[0]; return { type: "lap", mode: parser.mode, alignment: funcName.slice(5), body }; }, htmlBuilder: (group, options) => { // mathllap, mathrlap, mathclap var inner; if (group.alignment === "clap") { // ref: https://www.math.lsu.edu/~aperlis/publications/mathclap/ inner = buildCommon.makeSpan([], [buildGroup$1(group.body, options)]); // wrap, since CSS will center a .clap > .inner > span inner = buildCommon.makeSpan(["inner"], [inner], options); } else { inner = buildCommon.makeSpan(["inner"], [buildGroup$1(group.body, options)]); } var fix = buildCommon.makeSpan(["fix"], []); var node = buildCommon.makeSpan([group.alignment], [inner, fix], options); // At this point, we have correctly set horizontal alignment of the // two items involved in the lap. // Next, use a strut to set the height of the HTML bounding box. // Otherwise, a tall argument may be misplaced. // This code resolved issue #1153 var strut = buildCommon.makeSpan(["strut"]); strut.style.height = makeEm(node.height + node.depth); if (node.depth) { strut.style.verticalAlign = makeEm(-node.depth); } node.children.unshift(strut); // Next, prevent vertical misplacement when next to something tall. // This code resolves issue #1234 node = buildCommon.makeSpan(["thinbox"], [node], options); return buildCommon.makeSpan(["mord", "vbox"], [node], options); }, mathmlBuilder: (group, options) => { // mathllap, mathrlap, mathclap var node = new mathMLTree.MathNode("mpadded", [buildGroup(group.body, options)]); if (group.alignment !== "rlap") { var offset = group.alignment === "llap" ? "-1" : "-0.5"; node.setAttribute("lspace", offset + "width"); } node.setAttribute("width", "0px"); return node; } }); defineFunction({ type: "styling", names: ["\\(", "$"], props: { numArgs: 0, allowedInText: true, allowedInMath: false }, handler(_ref, args) { var { funcName, parser } = _ref; var outerMode = parser.mode; parser.switchMode("math"); var close = funcName === "\\(" ? "\\)" : "$"; var body = parser.parseExpression(false, close); parser.expect(close); parser.switchMode(outerMode); return { type: "styling", mode: parser.mode, style: "text", body }; } }); // Check for extra closing math delimiters defineFunction({ type: "text", // Doesn't matter what this is. names: ["\\)", "\\]"], props: { numArgs: 0, allowedInText: true, allowedInMath: false }, handler(context, args) { throw new ParseError("Mismatched " + context.funcName); } }); var chooseMathStyle = (group, options) => { switch (options.style.size) { case Style$1.DISPLAY.size: return group.display; case Style$1.TEXT.size: return group.text; case Style$1.SCRIPT.size: return group.script; case Style$1.SCRIPTSCRIPT.size: return group.scriptscript; default: return group.text; } }; defineFunction({ type: "mathchoice", names: ["\\mathchoice"], props: { numArgs: 4, primitive: true }, handler: (_ref, args) => { var { parser } = _ref; return { type: "mathchoice", mode: parser.mode, display: ordargument(args[0]), text: ordargument(args[1]), script: ordargument(args[2]), scriptscript: ordargument(args[3]) }; }, htmlBuilder: (group, options) => { var body = chooseMathStyle(group, options); var elements = buildExpression$1(body, options, false); return buildCommon.makeFragment(elements); }, mathmlBuilder: (group, options) => { var body = chooseMathStyle(group, options); return buildExpressionRow(body, options); } }); var assembleSupSub = (base, supGroup, subGroup, options, style, slant, baseShift) => { base = buildCommon.makeSpan([], [base]); var subIsSingleCharacter = subGroup && utils.isCharacterBox(subGroup); var sub; var sup; // We manually have to handle the superscripts and subscripts. This, // aside from the kern calculations, is copied from supsub. if (supGroup) { var elem = buildGroup$1(supGroup, options.havingStyle(style.sup()), options); sup = { elem, kern: Math.max(options.fontMetrics().bigOpSpacing1, options.fontMetrics().bigOpSpacing3 - elem.depth) }; } if (subGroup) { var _elem = buildGroup$1(subGroup, options.havingStyle(style.sub()), options); sub = { elem: _elem, kern: Math.max(options.fontMetrics().bigOpSpacing2, options.fontMetrics().bigOpSpacing4 - _elem.height) }; } // Build the final group as a vlist of the possible subscript, base, // and possible superscript. var finalGroup; if (sup && sub) { var bottom = options.fontMetrics().bigOpSpacing5 + sub.elem.height + sub.elem.depth + sub.kern + base.depth + baseShift; finalGroup = buildCommon.makeVList({ positionType: "bottom", positionData: bottom, children: [{ type: "kern", size: options.fontMetrics().bigOpSpacing5 }, { type: "elem", elem: sub.elem, marginLeft: makeEm(-slant) }, { type: "kern", size: sub.kern }, { type: "elem", elem: base }, { type: "kern", size: sup.kern }, { type: "elem", elem: sup.elem, marginLeft: makeEm(slant) }, { type: "kern", size: options.fontMetrics().bigOpSpacing5 }] }, options); } else if (sub) { var top = base.height - baseShift; // Shift the limits by the slant of the symbol. Note // that we are supposed to shift the limits by 1/2 of the slant, // but since we are centering the limits adding a full slant of // margin will shift by 1/2 that. finalGroup = buildCommon.makeVList({ positionType: "top", positionData: top, children: [{ type: "kern", size: options.fontMetrics().bigOpSpacing5 }, { type: "elem", elem: sub.elem, marginLeft: makeEm(-slant) }, { type: "kern", size: sub.kern }, { type: "elem", elem: base }] }, options); } else if (sup) { var _bottom = base.depth + baseShift; finalGroup = buildCommon.makeVList({ positionType: "bottom", positionData: _bottom, children: [{ type: "elem", elem: base }, { type: "kern", size: sup.kern }, { type: "elem", elem: sup.elem, marginLeft: makeEm(slant) }, { type: "kern", size: options.fontMetrics().bigOpSpacing5 }] }, options); } else { // This case probably shouldn't occur (this would mean the // supsub was sending us a group with no superscript or // subscript) but be safe. return base; } var parts = [finalGroup]; if (sub && slant !== 0 && !subIsSingleCharacter) { // A negative margin-left was applied to the lower limit. // Avoid an overlap by placing a spacer on the left on the group. var spacer = buildCommon.makeSpan(["mspace"], [], options); spacer.style.marginRight = makeEm(slant); parts.unshift(spacer); } return buildCommon.makeSpan(["mop", "op-limits"], parts, options); }; // Limits, symbols // Most operators have a large successor symbol, but these don't. var noSuccessor = ["\\smallint"]; // NOTE: Unlike most `htmlBuilder`s, this one handles not only "op", but also // "supsub" since some of them (like \int) can affect super/subscripting. var htmlBuilder$2 = (grp, options) => { // Operators are handled in the TeXbook pg. 443-444, rule 13(a). var supGroup; var subGroup; var hasLimits = false; var group; if (grp.type === "supsub") { // If we have limits, supsub will pass us its group to handle. Pull // out the superscript and subscript and set the group to the op in // its base. supGroup = grp.sup; subGroup = grp.sub; group = assertNodeType(grp.base, "op"); hasLimits = true; } else { group = assertNodeType(grp, "op"); } var style = options.style; var large = false; if (style.size === Style$1.DISPLAY.size && group.symbol && !utils.contains(noSuccessor, group.name)) { // Most symbol operators get larger in displaystyle (rule 13) large = true; } var base; if (group.symbol) { // If this is a symbol, create the symbol. var fontName = large ? "Size2-Regular" : "Size1-Regular"; var stash = ""; if (group.name === "\\oiint" || group.name === "\\oiiint") { // No font glyphs yet, so use a glyph w/o the oval. // TODO: When font glyphs are available, delete this code. stash = group.name.slice(1); group.name = stash === "oiint" ? "\\iint" : "\\iiint"; } base = buildCommon.makeSymbol(group.name, fontName, "math", options, ["mop", "op-symbol", large ? "large-op" : "small-op"]); if (stash.length > 0) { // We're in \oiint or \oiiint. Overlay the oval. // TODO: When font glyphs are available, delete this code. var italic = base.italic; var oval = buildCommon.staticSvg(stash + "Size" + (large ? "2" : "1"), options); base = buildCommon.makeVList({ positionType: "individualShift", children: [{ type: "elem", elem: base, shift: 0 }, { type: "elem", elem: oval, shift: large ? 0.08 : 0 }] }, options); group.name = "\\" + stash; base.classes.unshift("mop"); // $FlowFixMe base.italic = italic; } } else if (group.body) { // If this is a list, compose that list. var inner = buildExpression$1(group.body, options, true); if (inner.length === 1 && inner[0] instanceof SymbolNode) { base = inner[0]; base.classes[0] = "mop"; // replace old mclass } else { base = buildCommon.makeSpan(["mop"], inner, options); } } else { // Otherwise, this is a text operator. Build the text from the // operator's name. var output = []; for (var i = 1; i < group.name.length; i++) { output.push(buildCommon.mathsym(group.name[i], group.mode, options)); } base = buildCommon.makeSpan(["mop"], output, options); } // If content of op is a single symbol, shift it vertically. var baseShift = 0; var slant = 0; if ((base instanceof SymbolNode || group.name === "\\oiint" || group.name === "\\oiiint") && !group.suppressBaseShift) { // We suppress the shift of the base of \overset and \underset. Otherwise, // shift the symbol so its center lies on the axis (rule 13). It // appears that our fonts have the centers of the symbols already // almost on the axis, so these numbers are very small. Note we // don't actually apply this here, but instead it is used either in // the vlist creation or separately when there are no limits. baseShift = (base.height - base.depth) / 2 - options.fontMetrics().axisHeight; // The slant of the symbol is just its italic correction. // $FlowFixMe slant = base.italic; } if (hasLimits) { return assembleSupSub(base, supGroup, subGroup, options, style, slant, baseShift); } else { if (baseShift) { base.style.position = "relative"; base.style.top = makeEm(baseShift); } return base; } }; var mathmlBuilder$1 = (group, options) => { var node; if (group.symbol) { // This is a symbol. Just add the symbol. node = new MathNode("mo", [makeText(group.name, group.mode)]); if (utils.contains(noSuccessor, group.name)) { node.setAttribute("largeop", "false"); } } else if (group.body) { // This is an operator with children. Add them. node = new MathNode("mo", buildExpression(group.body, options)); } else { // This is a text operator. Add all of the characters from the // operator's name. node = new MathNode("mi", [new TextNode(group.name.slice(1))]); // Append an . // ref: https://www.w3.org/TR/REC-MathML/chap3_2.html#sec3.2.4 var operator = new MathNode("mo", [makeText("\u2061", "text")]); if (group.parentIsSupSub) { node = new MathNode("mrow", [node, operator]); } else { node = newDocumentFragment([node, operator]); } } return node; }; var singleCharBigOps = { "\u220F": "\\prod", "\u2210": "\\coprod", "\u2211": "\\sum", "\u22c0": "\\bigwedge", "\u22c1": "\\bigvee", "\u22c2": "\\bigcap", "\u22c3": "\\bigcup", "\u2a00": "\\bigodot", "\u2a01": "\\bigoplus", "\u2a02": "\\bigotimes", "\u2a04": "\\biguplus", "\u2a06": "\\bigsqcup" }; defineFunction({ type: "op", names: ["\\coprod", "\\bigvee", "\\bigwedge", "\\biguplus", "\\bigcap", "\\bigcup", "\\intop", "\\prod", "\\sum", "\\bigotimes", "\\bigoplus", "\\bigodot", "\\bigsqcup", "\\smallint", "\u220F", "\u2210", "\u2211", "\u22c0", "\u22c1", "\u22c2", "\u22c3", "\u2a00", "\u2a01", "\u2a02", "\u2a04", "\u2a06"], props: { numArgs: 0 }, handler: (_ref, args) => { var { parser, funcName } = _ref; var fName = funcName; if (fName.length === 1) { fName = singleCharBigOps[fName]; } return { type: "op", mode: parser.mode, limits: true, parentIsSupSub: false, symbol: true, name: fName }; }, htmlBuilder: htmlBuilder$2, mathmlBuilder: mathmlBuilder$1 }); // Note: calling defineFunction with a type that's already been defined only // works because the same htmlBuilder and mathmlBuilder are being used. defineFunction({ type: "op", names: ["\\mathop"], props: { numArgs: 1, primitive: true }, handler: (_ref2, args) => { var { parser } = _ref2; var body = args[0]; return { type: "op", mode: parser.mode, limits: false, parentIsSupSub: false, symbol: false, body: ordargument(body) }; }, htmlBuilder: htmlBuilder$2, mathmlBuilder: mathmlBuilder$1 }); // There are 2 flags for operators; whether they produce limits in // displaystyle, and whether they are symbols and should grow in // displaystyle. These four groups cover the four possible choices. var singleCharIntegrals = { "\u222b": "\\int", "\u222c": "\\iint", "\u222d": "\\iiint", "\u222e": "\\oint", "\u222f": "\\oiint", "\u2230": "\\oiiint" }; // No limits, not symbols defineFunction({ type: "op", names: ["\\arcsin", "\\arccos", "\\arctan", "\\arctg", "\\arcctg", "\\arg", "\\ch", "\\cos", "\\cosec", "\\cosh", "\\cot", "\\cotg", "\\coth", "\\csc", "\\ctg", "\\cth", "\\deg", "\\dim", "\\exp", "\\hom", "\\ker", "\\lg", "\\ln", "\\log", "\\sec", "\\sin", "\\sinh", "\\sh", "\\tan", "\\tanh", "\\tg", "\\th"], props: { numArgs: 0 }, handler(_ref3) { var { parser, funcName } = _ref3; return { type: "op", mode: parser.mode, limits: false, parentIsSupSub: false, symbol: false, name: funcName }; }, htmlBuilder: htmlBuilder$2, mathmlBuilder: mathmlBuilder$1 }); // Limits, not symbols defineFunction({ type: "op", names: ["\\det", "\\gcd", "\\inf", "\\lim", "\\max", "\\min", "\\Pr", "\\sup"], props: { numArgs: 0 }, handler(_ref4) { var { parser, funcName } = _ref4; return { type: "op", mode: parser.mode, limits: true, parentIsSupSub: false, symbol: false, name: funcName }; }, htmlBuilder: htmlBuilder$2, mathmlBuilder: mathmlBuilder$1 }); // No limits, symbols defineFunction({ type: "op", names: ["\\int", "\\iint", "\\iiint", "\\oint", "\\oiint", "\\oiiint", "\u222b", "\u222c", "\u222d", "\u222e", "\u222f", "\u2230"], props: { numArgs: 0 }, handler(_ref5) { var { parser, funcName } = _ref5; var fName = funcName; if (fName.length === 1) { fName = singleCharIntegrals[fName]; } return { type: "op", mode: parser.mode, limits: false, parentIsSupSub: false, symbol: true, name: fName }; }, htmlBuilder: htmlBuilder$2, mathmlBuilder: mathmlBuilder$1 }); // NOTE: Unlike most `htmlBuilder`s, this one handles not only // "operatorname", but also "supsub" since \operatorname* can // affect super/subscripting. var htmlBuilder$1 = (grp, options) => { // Operators are handled in the TeXbook pg. 443-444, rule 13(a). var supGroup; var subGroup; var hasLimits = false; var group; if (grp.type === "supsub") { // If we have limits, supsub will pass us its group to handle. Pull // out the superscript and subscript and set the group to the op in // its base. supGroup = grp.sup; subGroup = grp.sub; group = assertNodeType(grp.base, "operatorname"); hasLimits = true; } else { group = assertNodeType(grp, "operatorname"); } var base; if (group.body.length > 0) { var body = group.body.map(child => { // $FlowFixMe: Check if the node has a string `text` property. var childText = child.text; if (typeof childText === "string") { return { type: "textord", mode: child.mode, text: childText }; } else { return child; } }); // Consolidate function names into symbol characters. var expression = buildExpression$1(body, options.withFont("mathrm"), true); for (var i = 0; i < expression.length; i++) { var child = expression[i]; if (child instanceof SymbolNode) { // Per amsopn package, // change minus to hyphen and \ast to asterisk child.text = child.text.replace(/\u2212/, "-").replace(/\u2217/, "*"); } } base = buildCommon.makeSpan(["mop"], expression, options); } else { base = buildCommon.makeSpan(["mop"], [], options); } if (hasLimits) { return assembleSupSub(base, supGroup, subGroup, options, options.style, 0, 0); } else { return base; } }; var mathmlBuilder = (group, options) => { // The steps taken here are similar to the html version. var expression = buildExpression(group.body, options.withFont("mathrm")); // Is expression a string or has it something like a fraction? var isAllString = true; // default for (var i = 0; i < expression.length; i++) { var node = expression[i]; if (node instanceof mathMLTree.SpaceNode) ; else if (node instanceof mathMLTree.MathNode) { switch (node.type) { case "mi": case "mn": case "ms": case "mspace": case "mtext": break; // Do nothing yet. case "mo": { var child = node.children[0]; if (node.children.length === 1 && child instanceof mathMLTree.TextNode) { child.text = child.text.replace(/\u2212/, "-").replace(/\u2217/, "*"); } else { isAllString = false; } break; } default: isAllString = false; } } else { isAllString = false; } } if (isAllString) { // Write a single TextNode instead of multiple nested tags. var word = expression.map(node => node.toText()).join(""); expression = [new mathMLTree.TextNode(word)]; } var identifier = new mathMLTree.MathNode("mi", expression); identifier.setAttribute("mathvariant", "normal"); // \u2061 is the same as ⁡ // ref: https://www.w3schools.com/charsets/ref_html_entities_a.asp var operator = new mathMLTree.MathNode("mo", [makeText("\u2061", "text")]); if (group.parentIsSupSub) { return new mathMLTree.MathNode("mrow", [identifier, operator]); } else { return mathMLTree.newDocumentFragment([identifier, operator]); } }; // \operatorname // amsopn.dtx: \mathop{#1\kern\z@\operator@font#3}\newmcodes@ defineFunction({ type: "operatorname", names: ["\\operatorname@", "\\operatornamewithlimits"], props: { numArgs: 1 }, handler: (_ref, args) => { var { parser, funcName } = _ref; var body = args[0]; return { type: "operatorname", mode: parser.mode, body: ordargument(body), alwaysHandleSupSub: funcName === "\\operatornamewithlimits", limits: false, parentIsSupSub: false }; }, htmlBuilder: htmlBuilder$1, mathmlBuilder }); defineMacro("\\operatorname", "\\@ifstar\\operatornamewithlimits\\operatorname@"); defineFunctionBuilders({ type: "ordgroup", htmlBuilder(group, options) { if (group.semisimple) { return buildCommon.makeFragment(buildExpression$1(group.body, options, false)); } return buildCommon.makeSpan(["mord"], buildExpression$1(group.body, options, true), options); }, mathmlBuilder(group, options) { return buildExpressionRow(group.body, options, true); } }); defineFunction({ type: "overline", names: ["\\overline"], props: { numArgs: 1 }, handler(_ref, args) { var { parser } = _ref; var body = args[0]; return { type: "overline", mode: parser.mode, body }; }, htmlBuilder(group, options) { // Overlines are handled in the TeXbook pg 443, Rule 9. // Build the inner group in the cramped style. var innerGroup = buildGroup$1(group.body, options.havingCrampedStyle()); // Create the line above the body var line = buildCommon.makeLineSpan("overline-line", options); // Generate the vlist, with the appropriate kerns var defaultRuleThickness = options.fontMetrics().defaultRuleThickness; var vlist = buildCommon.makeVList({ positionType: "firstBaseline", children: [{ type: "elem", elem: innerGroup }, { type: "kern", size: 3 * defaultRuleThickness }, { type: "elem", elem: line }, { type: "kern", size: defaultRuleThickness }] }, options); return buildCommon.makeSpan(["mord", "overline"], [vlist], options); }, mathmlBuilder(group, options) { var operator = new mathMLTree.MathNode("mo", [new mathMLTree.TextNode("\u203e")]); operator.setAttribute("stretchy", "true"); var node = new mathMLTree.MathNode("mover", [buildGroup(group.body, options), operator]); node.setAttribute("accent", "true"); return node; } }); defineFunction({ type: "phantom", names: ["\\phantom"], props: { numArgs: 1, allowedInText: true }, handler: (_ref, args) => { var { parser } = _ref; var body = args[0]; return { type: "phantom", mode: parser.mode, body: ordargument(body) }; }, htmlBuilder: (group, options) => { var elements = buildExpression$1(group.body, options.withPhantom(), false); // \phantom isn't supposed to affect the elements it contains. // See "color" for more details. return buildCommon.makeFragment(elements); }, mathmlBuilder: (group, options) => { var inner = buildExpression(group.body, options); return new mathMLTree.MathNode("mphantom", inner); } }); defineFunction({ type: "hphantom", names: ["\\hphantom"], props: { numArgs: 1, allowedInText: true }, handler: (_ref2, args) => { var { parser } = _ref2; var body = args[0]; return { type: "hphantom", mode: parser.mode, body }; }, htmlBuilder: (group, options) => { var node = buildCommon.makeSpan([], [buildGroup$1(group.body, options.withPhantom())]); node.height = 0; node.depth = 0; if (node.children) { for (var i = 0; i < node.children.length; i++) { node.children[i].height = 0; node.children[i].depth = 0; } } // See smash for comment re: use of makeVList node = buildCommon.makeVList({ positionType: "firstBaseline", children: [{ type: "elem", elem: node }] }, options); // For spacing, TeX treats \smash as a math group (same spacing as ord). return buildCommon.makeSpan(["mord"], [node], options); }, mathmlBuilder: (group, options) => { var inner = buildExpression(ordargument(group.body), options); var phantom = new mathMLTree.MathNode("mphantom", inner); var node = new mathMLTree.MathNode("mpadded", [phantom]); node.setAttribute("height", "0px"); node.setAttribute("depth", "0px"); return node; } }); defineFunction({ type: "vphantom", names: ["\\vphantom"], props: { numArgs: 1, allowedInText: true }, handler: (_ref3, args) => { var { parser } = _ref3; var body = args[0]; return { type: "vphantom", mode: parser.mode, body }; }, htmlBuilder: (group, options) => { var inner = buildCommon.makeSpan(["inner"], [buildGroup$1(group.body, options.withPhantom())]); var fix = buildCommon.makeSpan(["fix"], []); return buildCommon.makeSpan(["mord", "rlap"], [inner, fix], options); }, mathmlBuilder: (group, options) => { var inner = buildExpression(ordargument(group.body), options); var phantom = new mathMLTree.MathNode("mphantom", inner); var node = new mathMLTree.MathNode("mpadded", [phantom]); node.setAttribute("width", "0px"); return node; } }); defineFunction({ type: "raisebox", names: ["\\raisebox"], props: { numArgs: 2, argTypes: ["size", "hbox"], allowedInText: true }, handler(_ref, args) { var { parser } = _ref; var amount = assertNodeType(args[0], "size").value; var body = args[1]; return { type: "raisebox", mode: parser.mode, dy: amount, body }; }, htmlBuilder(group, options) { var body = buildGroup$1(group.body, options); var dy = calculateSize(group.dy, options); return buildCommon.makeVList({ positionType: "shift", positionData: -dy, children: [{ type: "elem", elem: body }] }, options); }, mathmlBuilder(group, options) { var node = new mathMLTree.MathNode("mpadded", [buildGroup(group.body, options)]); var dy = group.dy.number + group.dy.unit; node.setAttribute("voffset", dy); return node; } }); defineFunction({ type: "internal", names: ["\\relax"], props: { numArgs: 0, allowedInText: true, allowedInArgument: true }, handler(_ref) { var { parser } = _ref; return { type: "internal", mode: parser.mode }; } }); defineFunction({ type: "rule", names: ["\\rule"], props: { numArgs: 2, numOptionalArgs: 1, allowedInText: true, allowedInMath: true, argTypes: ["size", "size", "size"] }, handler(_ref, args, optArgs) { var { parser } = _ref; var shift = optArgs[0]; var width = assertNodeType(args[0], "size"); var height = assertNodeType(args[1], "size"); return { type: "rule", mode: parser.mode, shift: shift && assertNodeType(shift, "size").value, width: width.value, height: height.value }; }, htmlBuilder(group, options) { // Make an empty span for the rule var rule = buildCommon.makeSpan(["mord", "rule"], [], options); // Calculate the shift, width, and height of the rule, and account for units var width = calculateSize(group.width, options); var height = calculateSize(group.height, options); var shift = group.shift ? calculateSize(group.shift, options) : 0; // Style the rule to the right size rule.style.borderRightWidth = makeEm(width); rule.style.borderTopWidth = makeEm(height); rule.style.bottom = makeEm(shift); // Record the height and width rule.width = width; rule.height = height + shift; rule.depth = -shift; // Font size is the number large enough that the browser will // reserve at least `absHeight` space above the baseline. // The 1.125 factor was empirically determined rule.maxFontSize = height * 1.125 * options.sizeMultiplier; return rule; }, mathmlBuilder(group, options) { var width = calculateSize(group.width, options); var height = calculateSize(group.height, options); var shift = group.shift ? calculateSize(group.shift, options) : 0; var color = options.color && options.getColor() || "black"; var rule = new mathMLTree.MathNode("mspace"); rule.setAttribute("mathbackground", color); rule.setAttribute("width", makeEm(width)); rule.setAttribute("height", makeEm(height)); var wrapper = new mathMLTree.MathNode("mpadded", [rule]); if (shift >= 0) { wrapper.setAttribute("height", makeEm(shift)); } else { wrapper.setAttribute("height", makeEm(shift)); wrapper.setAttribute("depth", makeEm(-shift)); } wrapper.setAttribute("voffset", makeEm(shift)); return wrapper; } }); function sizingGroup(value, options, baseOptions) { var inner = buildExpression$1(value, options, false); var multiplier = options.sizeMultiplier / baseOptions.sizeMultiplier; // Add size-resetting classes to the inner list and set maxFontSize // manually. Handle nested size changes. for (var i = 0; i < inner.length; i++) { var pos = inner[i].classes.indexOf("sizing"); if (pos < 0) { Array.prototype.push.apply(inner[i].classes, options.sizingClasses(baseOptions)); } else if (inner[i].classes[pos + 1] === "reset-size" + options.size) { // This is a nested size change: e.g., inner[i] is the "b" in // `\Huge a \small b`. Override the old size (the `reset-` class) // but not the new size. inner[i].classes[pos + 1] = "reset-size" + baseOptions.size; } inner[i].height *= multiplier; inner[i].depth *= multiplier; } return buildCommon.makeFragment(inner); } var sizeFuncs = ["\\tiny", "\\sixptsize", "\\scriptsize", "\\footnotesize", "\\small", "\\normalsize", "\\large", "\\Large", "\\LARGE", "\\huge", "\\Huge"]; var htmlBuilder = (group, options) => { // Handle sizing operators like \Huge. Real TeX doesn't actually allow // these functions inside of math expressions, so we do some special // handling. var newOptions = options.havingSize(group.size); return sizingGroup(group.body, newOptions, options); }; defineFunction({ type: "sizing", names: sizeFuncs, props: { numArgs: 0, allowedInText: true }, handler: (_ref, args) => { var { breakOnTokenText, funcName, parser } = _ref; var body = parser.parseExpression(false, breakOnTokenText); return { type: "sizing", mode: parser.mode, // Figure out what size to use based on the list of functions above size: sizeFuncs.indexOf(funcName) + 1, body }; }, htmlBuilder, mathmlBuilder: (group, options) => { var newOptions = options.havingSize(group.size); var inner = buildExpression(group.body, newOptions); 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", makeEm(newOptions.sizeMultiplier)); return node; } }); // smash, with optional [tb], as in AMS defineFunction({ type: "smash", names: ["\\smash"], props: { numArgs: 1, numOptionalArgs: 1, allowedInText: true }, handler: (_ref, args, optArgs) => { var { parser } = _ref; var smashHeight = false; var smashDepth = false; var tbArg = optArgs[0] && assertNodeType(optArgs[0], "ordgroup"); if (tbArg) { // Optional [tb] argument is engaged. // ref: amsmath: \renewcommand{\smash}[1][tb]{% // def\mb@t{\ht}\def\mb@b{\dp}\def\mb@tb{\ht\z@\z@\dp}% var letter = ""; for (var i = 0; i < tbArg.body.length; ++i) { var node = tbArg.body[i]; // $FlowFixMe: Not every node type has a `text` property. letter = node.text; if (letter === "t") { smashHeight = true; } else if (letter === "b") { smashDepth = true; } else { smashHeight = false; smashDepth = false; break; } } } else { smashHeight = true; smashDepth = true; } var body = args[0]; return { type: "smash", mode: parser.mode, body, smashHeight, smashDepth }; }, htmlBuilder: (group, options) => { var node = buildCommon.makeSpan([], [buildGroup$1(group.body, options)]); if (!group.smashHeight && !group.smashDepth) { return node; } if (group.smashHeight) { node.height = 0; // In order to influence makeVList, we have to reset the children. if (node.children) { for (var i = 0; i < node.children.length; i++) { node.children[i].height = 0; } } } if (group.smashDepth) { node.depth = 0; if (node.children) { for (var _i = 0; _i < node.children.length; _i++) { node.children[_i].depth = 0; } } } // At this point, we've reset the TeX-like height and depth values. // But the span still has an HTML line height. // makeVList applies "display: table-cell", which prevents the browser // from acting on that line height. So we'll call makeVList now. var smashedNode = buildCommon.makeVList({ positionType: "firstBaseline", children: [{ type: "elem", elem: node }] }, options); // For spacing, TeX treats \hphantom as a math group (same spacing as ord). return buildCommon.makeSpan(["mord"], [smashedNode], options); }, mathmlBuilder: (group, options) => { var node = new mathMLTree.MathNode("mpadded", [buildGroup(group.body, options)]); if (group.smashHeight) { node.setAttribute("height", "0px"); } if (group.smashDepth) { node.setAttribute("depth", "0px"); } return node; } }); defineFunction({ type: "sqrt", names: ["\\sqrt"], props: { numArgs: 1, numOptionalArgs: 1 }, handler(_ref, args, optArgs) { var { parser } = _ref; var index = optArgs[0]; var body = args[0]; return { type: "sqrt", mode: parser.mode, body, index }; }, htmlBuilder(group, options) { // Square roots are handled in the TeXbook pg. 443, Rule 11. // First, we do the same steps as in overline to build the inner group // and line var inner = buildGroup$1(group.body, options.havingCrampedStyle()); if (inner.height === 0) { // Render a small surd. inner.height = options.fontMetrics().xHeight; } // Some groups can return document fragments. Handle those by wrapping // them in a span. inner = buildCommon.wrapFragment(inner, options); // Calculate the minimum size for the \surd delimiter var metrics = options.fontMetrics(); var theta = metrics.defaultRuleThickness; var phi = theta; if (options.style.id < Style$1.TEXT.id) { phi = options.fontMetrics().xHeight; } // Calculate the clearance between the body and line var lineClearance = theta + phi / 4; var minDelimiterHeight = inner.height + inner.depth + lineClearance + theta; // Create a sqrt SVG of the required minimum size var { span: img, ruleWidth, advanceWidth } = delimiter.sqrtImage(minDelimiterHeight, options); var delimDepth = img.height - ruleWidth; // Adjust the clearance based on the delimiter size if (delimDepth > inner.height + inner.depth + lineClearance) { lineClearance = (lineClearance + delimDepth - inner.height - inner.depth) / 2; } // Shift the sqrt image var imgShift = img.height - inner.height - lineClearance - ruleWidth; inner.style.paddingLeft = makeEm(advanceWidth); // Overlay the image and the argument. var body = buildCommon.makeVList({ positionType: "firstBaseline", children: [{ type: "elem", elem: inner, wrapperClasses: ["svg-align"] }, { type: "kern", size: -(inner.height + imgShift) }, { type: "elem", elem: img }, { type: "kern", size: ruleWidth }] }, options); if (!group.index) { return buildCommon.makeSpan(["mord", "sqrt"], [body], options); } else { // Handle the optional root index // The index is always in scriptscript style var newOptions = options.havingStyle(Style$1.SCRIPTSCRIPT); var rootm = buildGroup$1(group.index, newOptions, options); // The amount the index is shifted by. This is taken from the TeX // source, in the definition of `\r@@t`. var toShift = 0.6 * (body.height - body.depth); // Build a VList with the superscript shifted up correctly var rootVList = buildCommon.makeVList({ positionType: "shift", positionData: -toShift, children: [{ type: "elem", elem: rootm }] }, options); // Add a class surrounding it so we can add on the appropriate // kerning var rootVListWrap = buildCommon.makeSpan(["root"], [rootVList]); return buildCommon.makeSpan(["mord", "sqrt"], [rootVListWrap, body], options); } }, mathmlBuilder(group, options) { var { body, index } = group; return index ? new mathMLTree.MathNode("mroot", [buildGroup(body, options), buildGroup(index, options)]) : new mathMLTree.MathNode("msqrt", [buildGroup(body, options)]); } }); var styleMap = { "display": Style$1.DISPLAY, "text": Style$1.TEXT, "script": Style$1.SCRIPT, "scriptscript": Style$1.SCRIPTSCRIPT }; defineFunction({ type: "styling", names: ["\\displaystyle", "\\textstyle", "\\scriptstyle", "\\scriptscriptstyle"], props: { numArgs: 0, allowedInText: true, primitive: true }, handler(_ref, args) { var { breakOnTokenText, funcName, parser } = _ref; // parse out the implicit body var body = parser.parseExpression(true, breakOnTokenText); // TODO: Refactor to avoid duplicating styleMap in multiple places (e.g. // here and in buildHTML and de-dupe the enumeration of all the styles). // $FlowFixMe: The names above exactly match the styles. var style = funcName.slice(1, funcName.length - 5); return { type: "styling", mode: parser.mode, // Figure out what style to use by pulling out the style from // the function name style, body }; }, htmlBuilder(group, options) { // Style changes are handled in the TeXbook on pg. 442, Rule 3. var newStyle = styleMap[group.style]; var newOptions = options.havingStyle(newStyle).withFont(''); return sizingGroup(group.body, newOptions, options); }, mathmlBuilder(group, options) { // Figure out what style we're changing to. var newStyle = styleMap[group.style]; var newOptions = options.havingStyle(newStyle); var inner = buildExpression(group.body, newOptions); 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.style]; node.setAttribute("scriptlevel", attr[0]); node.setAttribute("displaystyle", attr[1]); return node; } }); /** * Sometimes, groups perform special rules when they have superscripts or * subscripts attached to them. This function lets the `supsub` group know that * Sometimes, groups perform special rules when they have superscripts or * its inner element should handle the superscripts and subscripts instead of * handling them itself. */ var htmlBuilderDelegate = function htmlBuilderDelegate(group, options) { var base = group.base; if (!base) { return null; } else if (base.type === "op") { // Operators handle supsubs differently when they have limits // (e.g. `\displaystyle\sum_2^3`) var delegate = base.limits && (options.style.size === Style$1.DISPLAY.size || base.alwaysHandleSupSub); return delegate ? htmlBuilder$2 : null; } else if (base.type === "operatorname") { var _delegate = base.alwaysHandleSupSub && (options.style.size === Style$1.DISPLAY.size || base.limits); return _delegate ? htmlBuilder$1 : null; } else if (base.type === "accent") { return utils.isCharacterBox(base.base) ? htmlBuilder$a : null; } else if (base.type === "horizBrace") { var isSup = !group.sub; return isSup === base.isOver ? htmlBuilder$3 : null; } else { return null; } }; // Super scripts and subscripts, whose precise placement can depend on other // functions that precede them. defineFunctionBuilders({ type: "supsub", htmlBuilder(group, options) { // Superscript and subscripts are handled in the TeXbook on page // 445-446, rules 18(a-f). // Here is where we defer to the inner group if it should handle // superscripts and subscripts itself. var builderDelegate = htmlBuilderDelegate(group, options); if (builderDelegate) { return builderDelegate(group, options); } var { base: valueBase, sup: valueSup, sub: valueSub } = group; var base = buildGroup$1(valueBase, options); var supm; var subm; var metrics = options.fontMetrics(); // Rule 18a var supShift = 0; var subShift = 0; var isCharacterBox = valueBase && utils.isCharacterBox(valueBase); if (valueSup) { var newOptions = options.havingStyle(options.style.sup()); supm = buildGroup$1(valueSup, newOptions, options); if (!isCharacterBox) { supShift = base.height - newOptions.fontMetrics().supDrop * newOptions.sizeMultiplier / options.sizeMultiplier; } } if (valueSub) { var _newOptions = options.havingStyle(options.style.sub()); subm = buildGroup$1(valueSub, _newOptions, options); if (!isCharacterBox) { subShift = base.depth + _newOptions.fontMetrics().subDrop * _newOptions.sizeMultiplier / options.sizeMultiplier; } } // Rule 18c var minSupShift; if (options.style === Style$1.DISPLAY) { minSupShift = metrics.sup1; } else if (options.style.cramped) { minSupShift = metrics.sup3; } else { minSupShift = metrics.sup2; } // scriptspace is a font-size-independent size, so scale it // appropriately for use as the marginRight. var multiplier = options.sizeMultiplier; var marginRight = makeEm(0.5 / metrics.ptPerEm / multiplier); var marginLeft = null; if (subm) { // Subscripts shouldn't be shifted by the base's italic correction. // Account for that by shifting the subscript back the appropriate // amount. Note we only do this when the base is a single symbol. var isOiint = group.base && group.base.type === "op" && group.base.name && (group.base.name === "\\oiint" || group.base.name === "\\oiiint"); if (base instanceof SymbolNode || isOiint) { // $FlowFixMe marginLeft = makeEm(-base.italic); } } var supsub; if (supm && subm) { supShift = Math.max(supShift, minSupShift, supm.depth + 0.25 * metrics.xHeight); subShift = Math.max(subShift, metrics.sub2); var ruleWidth = metrics.defaultRuleThickness; // Rule 18e var maxWidth = 4 * ruleWidth; if (supShift - supm.depth - (subm.height - subShift) < maxWidth) { subShift = maxWidth - (supShift - supm.depth) + subm.height; var psi = 0.8 * metrics.xHeight - (supShift - supm.depth); if (psi > 0) { supShift += psi; subShift -= psi; } } var vlistElem = [{ type: "elem", elem: subm, shift: subShift, marginRight, marginLeft }, { type: "elem", elem: supm, shift: -supShift, marginRight }]; supsub = buildCommon.makeVList({ positionType: "individualShift", children: vlistElem }, options); } else if (subm) { // Rule 18b subShift = Math.max(subShift, metrics.sub1, subm.height - 0.8 * metrics.xHeight); var _vlistElem = [{ type: "elem", elem: subm, marginLeft, marginRight }]; supsub = buildCommon.makeVList({ positionType: "shift", positionData: subShift, children: _vlistElem }, options); } else if (supm) { // Rule 18c, d supShift = Math.max(supShift, minSupShift, supm.depth + 0.25 * metrics.xHeight); supsub = buildCommon.makeVList({ positionType: "shift", positionData: -supShift, children: [{ type: "elem", elem: supm, marginRight }] }, options); } else { throw new Error("supsub must have either sup or sub."); } // Wrap the supsub vlist in a span.msupsub to reset text-align. var mclass = getTypeOfDomTree(base, "right") || "mord"; return buildCommon.makeSpan([mclass], [base, buildCommon.makeSpan(["msupsub"], [supsub])], options); }, mathmlBuilder(group, options) { // Is the inner group a relevant horizontal brace? var isBrace = false; var isOver; var isSup; if (group.base && group.base.type === "horizBrace") { isSup = !!group.sup; if (isSup === group.base.isOver) { isBrace = true; isOver = group.base.isOver; } } if (group.base && (group.base.type === "op" || group.base.type === "operatorname")) { group.base.parentIsSupSub = true; } var children = [buildGroup(group.base, options)]; if (group.sub) { children.push(buildGroup(group.sub, options)); } if (group.sup) { children.push(buildGroup(group.sup, options)); } var nodeType; if (isBrace) { nodeType = isOver ? "mover" : "munder"; } else if (!group.sub) { var base = group.base; if (base && base.type === "op" && base.limits && (options.style === Style$1.DISPLAY || base.alwaysHandleSupSub)) { nodeType = "mover"; } else if (base && base.type === "operatorname" && base.alwaysHandleSupSub && (base.limits || options.style === Style$1.DISPLAY)) { nodeType = "mover"; } else { nodeType = "msup"; } } else if (!group.sup) { var _base = group.base; if (_base && _base.type === "op" && _base.limits && (options.style === Style$1.DISPLAY || _base.alwaysHandleSupSub)) { nodeType = "munder"; } else if (_base && _base.type === "operatorname" && _base.alwaysHandleSupSub && (_base.limits || options.style === Style$1.DISPLAY)) { nodeType = "munder"; } else { nodeType = "msub"; } } else { var _base2 = group.base; if (_base2 && _base2.type === "op" && _base2.limits && options.style === Style$1.DISPLAY) { nodeType = "munderover"; } else if (_base2 && _base2.type === "operatorname" && _base2.alwaysHandleSupSub && (options.style === Style$1.DISPLAY || _base2.limits)) { nodeType = "munderover"; } else { nodeType = "msubsup"; } } return new mathMLTree.MathNode(nodeType, children); } }); defineFunctionBuilders({ type: "atom", htmlBuilder(group, options) { return buildCommon.mathsym(group.text, group.mode, options, ["m" + group.family]); }, mathmlBuilder(group, options) { var node = new mathMLTree.MathNode("mo", [makeText(group.text, group.mode)]); if (group.family === "bin") { var variant = getVariant(group, options); if (variant === "bold-italic") { node.setAttribute("mathvariant", variant); } } else if (group.family === "punct") { node.setAttribute("separator", "true"); } else if (group.family === "open" || group.family === "close") { // Delims built here should not stretch vertically. // See delimsizing.js for stretchy delims. node.setAttribute("stretchy", "false"); } return node; } }); // "mathord" and "textord" ParseNodes created in Parser.js from symbol Groups in // src/symbols.js. var defaultVariant = { "mi": "italic", "mn": "normal", "mtext": "normal" }; defineFunctionBuilders({ type: "mathord", htmlBuilder(group, options) { return buildCommon.makeOrd(group, options, "mathord"); }, mathmlBuilder(group, options) { var node = new mathMLTree.MathNode("mi", [makeText(group.text, group.mode, options)]); var variant = getVariant(group, options) || "italic"; if (variant !== defaultVariant[node.type]) { node.setAttribute("mathvariant", variant); } return node; } }); defineFunctionBuilders({ type: "textord", htmlBuilder(group, options) { return buildCommon.makeOrd(group, options, "textord"); }, mathmlBuilder(group, options) { var text = makeText(group.text, group.mode, options); var variant = getVariant(group, options) || "normal"; var node; if (group.mode === 'text') { node = new mathMLTree.MathNode("mtext", [text]); } else if (/[0-9]/.test(group.text)) { node = new mathMLTree.MathNode("mn", [text]); } else if (group.text === "\\prime") { node = new mathMLTree.MathNode("mo", [text]); } else { node = new mathMLTree.MathNode("mi", [text]); } if (variant !== defaultVariant[node.type]) { node.setAttribute("mathvariant", variant); } return node; } }); var cssSpace = { "\\nobreak": "nobreak", "\\allowbreak": "allowbreak" }; // A lookup table to determine whether a spacing function/symbol should be // treated like a regular space character. If a symbol or command is a key // in this table, then it should be a regular space character. Furthermore, // the associated value may have a `className` specifying an extra CSS class // to add to the created `span`. var regularSpace = { " ": {}, "\\ ": {}, "~": { className: "nobreak" }, "\\space": {}, "\\nobreakspace": { className: "nobreak" } }; // ParseNode<"spacing"> created in Parser.js from the "spacing" symbol Groups in // src/symbols.js. defineFunctionBuilders({ type: "spacing", htmlBuilder(group, options) { if (regularSpace.hasOwnProperty(group.text)) { var className = regularSpace[group.text].className || ""; // Spaces are generated by adding an actual space. Each of these // things has an entry in the symbols table, so these will be turned // into appropriate outputs. if (group.mode === "text") { var ord = buildCommon.makeOrd(group, options, "textord"); ord.classes.push(className); return ord; } else { return buildCommon.makeSpan(["mspace", className], [buildCommon.mathsym(group.text, group.mode, options)], options); } } else if (cssSpace.hasOwnProperty(group.text)) { // Spaces based on just a CSS class. return buildCommon.makeSpan(["mspace", cssSpace[group.text]], [], options); } else { throw new ParseError("Unknown type of space \"" + group.text + "\""); } }, mathmlBuilder(group, options) { var node; if (regularSpace.hasOwnProperty(group.text)) { node = new mathMLTree.MathNode("mtext", [new mathMLTree.TextNode("\u00a0")]); } else if (cssSpace.hasOwnProperty(group.text)) { // CSS-based MathML spaces (\nobreak, \allowbreak) are ignored return new mathMLTree.MathNode("mspace"); } else { throw new ParseError("Unknown type of space \"" + group.text + "\""); } return node; } }); var pad = () => { var padNode = new mathMLTree.MathNode("mtd", []); padNode.setAttribute("width", "50%"); return padNode; }; defineFunctionBuilders({ type: "tag", mathmlBuilder(group, options) { var table = new mathMLTree.MathNode("mtable", [new mathMLTree.MathNode("mtr", [pad(), new mathMLTree.MathNode("mtd", [buildExpressionRow(group.body, options)]), pad(), new mathMLTree.MathNode("mtd", [buildExpressionRow(group.tag, options)])])]); table.setAttribute("width", "100%"); return table; // TODO: Left-aligned tags. // Currently, the group and options passed here do not contain // enough info to set tag alignment. `leqno` is in Settings but it is // not passed to Options. On the HTML side, leqno is // set by a CSS class applied in buildTree.js. That would have worked // in MathML if browsers supported . Since they don't, we // need to rewrite the way this function is called. } }); var textFontFamilies = { "\\text": undefined, "\\textrm": "textrm", "\\textsf": "textsf", "\\texttt": "texttt", "\\textnormal": "textrm" }; var textFontWeights = { "\\textbf": "textbf", "\\textmd": "textmd" }; var textFontShapes = { "\\textit": "textit", "\\textup": "textup" }; var optionsWithFont = (group, options) => { var font = group.font; // Checks if the argument is a font family or a font style. if (!font) { return options; } else if (textFontFamilies[font]) { return options.withTextFontFamily(textFontFamilies[font]); } else if (textFontWeights[font]) { return options.withTextFontWeight(textFontWeights[font]); } else if (font === "\\emph") { return options.fontShape === "textit" ? options.withTextFontShape("textup") : options.withTextFontShape("textit"); } return options.withTextFontShape(textFontShapes[font]); }; defineFunction({ type: "text", names: [// Font families "\\text", "\\textrm", "\\textsf", "\\texttt", "\\textnormal", // Font weights "\\textbf", "\\textmd", // Font Shapes "\\textit", "\\textup", "\\emph"], props: { numArgs: 1, argTypes: ["text"], allowedInArgument: true, allowedInText: true }, handler(_ref, args) { var { parser, funcName } = _ref; var body = args[0]; return { type: "text", mode: parser.mode, body: ordargument(body), font: funcName }; }, htmlBuilder(group, options) { var newOptions = optionsWithFont(group, options); var inner = buildExpression$1(group.body, newOptions, true); return buildCommon.makeSpan(["mord", "text"], inner, newOptions); }, mathmlBuilder(group, options) { var newOptions = optionsWithFont(group, options); return buildExpressionRow(group.body, newOptions); } }); defineFunction({ type: "underline", names: ["\\underline"], props: { numArgs: 1, allowedInText: true }, handler(_ref, args) { var { parser } = _ref; return { type: "underline", mode: parser.mode, body: args[0] }; }, htmlBuilder(group, options) { // Underlines are handled in the TeXbook pg 443, Rule 10. // Build the inner group. var innerGroup = buildGroup$1(group.body, options); // Create the line to go below the body var line = buildCommon.makeLineSpan("underline-line", options); // Generate the vlist, with the appropriate kerns var defaultRuleThickness = options.fontMetrics().defaultRuleThickness; var vlist = buildCommon.makeVList({ positionType: "top", positionData: innerGroup.height, children: [{ type: "kern", size: defaultRuleThickness }, { type: "elem", elem: line }, { type: "kern", size: 3 * defaultRuleThickness }, { type: "elem", elem: innerGroup }] }, options); return buildCommon.makeSpan(["mord", "underline"], [vlist], options); }, mathmlBuilder(group, options) { var operator = new mathMLTree.MathNode("mo", [new mathMLTree.TextNode("\u203e")]); operator.setAttribute("stretchy", "true"); var node = new mathMLTree.MathNode("munder", [buildGroup(group.body, options), operator]); node.setAttribute("accentunder", "true"); return node; } }); defineFunction({ type: "vcenter", names: ["\\vcenter"], props: { numArgs: 1, argTypes: ["original"], // In LaTeX, \vcenter can act only on a box. allowedInText: false }, handler(_ref, args) { var { parser } = _ref; return { type: "vcenter", mode: parser.mode, body: args[0] }; }, htmlBuilder(group, options) { var body = buildGroup$1(group.body, options); var axisHeight = options.fontMetrics().axisHeight; var dy = 0.5 * (body.height - axisHeight - (body.depth + axisHeight)); return buildCommon.makeVList({ positionType: "shift", positionData: dy, children: [{ type: "elem", elem: body }] }, options); }, mathmlBuilder(group, options) { // There is no way to do this in MathML. // Write a class as a breadcrumb in case some post-processor wants // to perform a vcenter adjustment. return new mathMLTree.MathNode("mpadded", [buildGroup(group.body, options)], ["vcenter"]); } }); defineFunction({ type: "verb", names: ["\\verb"], props: { numArgs: 0, allowedInText: true }, handler(context, args, optArgs) { // \verb and \verb* are dealt with directly in Parser.js. // If we end up here, it's because of a failure to match the two delimiters // in the regex in Lexer.js. LaTeX raises the following error when \verb is // terminated by end of line (or file). throw new ParseError("\\verb ended by end of line instead of matching delimiter"); }, htmlBuilder(group, options) { var text = makeVerb(group); var body = []; // \verb enters text mode and therefore is sized like \textstyle var newOptions = options.havingStyle(options.style.text()); for (var i = 0; i < text.length; i++) { var c = text[i]; if (c === '~') { c = '\\textasciitilde'; } body.push(buildCommon.makeSymbol(c, "Typewriter-Regular", group.mode, newOptions, ["mord", "texttt"])); } return buildCommon.makeSpan(["mord", "text"].concat(newOptions.sizingClasses(options)), buildCommon.tryCombineChars(body), newOptions); }, mathmlBuilder(group, options) { var text = new mathMLTree.TextNode(makeVerb(group)); var node = new mathMLTree.MathNode("mtext", [text]); node.setAttribute("mathvariant", "monospace"); return node; } }); /** * Converts verb group into body string. * * \verb* replaces each space with an open box \u2423 * \verb replaces each space with a no-break space \xA0 */ var makeVerb = group => group.body.replace(/ /g, group.star ? '\u2423' : '\xA0'); /** Include this to ensure that all functions are defined. */ var functions = _functions; /** * The Lexer class handles tokenizing the input in various ways. Since our * parser expects us to be able to backtrack, the lexer allows lexing from any * given starting point. * * Its main exposed function is the `lex` function, which takes a position to * lex from and a type of token to lex. It defers to the appropriate `_innerLex` * function. * * The various `_innerLex` functions perform the actual lexing of different * kinds. */ /* The following tokenRegex * - matches typical whitespace (but not NBSP etc.) using its first group * - does not match any control character \x00-\x1f except whitespace * - does not match a bare backslash * - matches any ASCII character except those just mentioned * - does not match the BMP private use area \uE000-\uF8FF * - does not match bare surrogate code units * - matches any BMP character except for those just described * - matches any valid Unicode surrogate pair * - matches a backslash followed by one or more whitespace characters * - matches a backslash followed by one or more letters then whitespace * - matches a backslash followed by any BMP character * Capturing groups: * [1] regular whitespace * [2] backslash followed by whitespace * [3] anything else, which may include: * [4] left character of \verb* * [5] left character of \verb * [6] backslash followed by word, excluding any trailing whitespace * Just because the Lexer matches something doesn't mean it's valid input: * If there is no matching function or symbol definition, the Parser will * still reject the input. */ var spaceRegexString = "[ \r\n\t]"; var controlWordRegexString = "\\\\[a-zA-Z@]+"; var controlSymbolRegexString = "\\\\[^\uD800-\uDFFF]"; var controlWordWhitespaceRegexString = "(" + controlWordRegexString + ")" + spaceRegexString + "*"; var controlSpaceRegexString = "\\\\(\n|[ \r\t]+\n?)[ \r\t]*"; var combiningDiacriticalMarkString = "[\u0300-\u036f]"; var combiningDiacriticalMarksEndRegex = new RegExp(combiningDiacriticalMarkString + "+$"); var tokenRegexString = "(" + spaceRegexString + "+)|" + ( // whitespace controlSpaceRegexString + "|") + // \whitespace "([!-\\[\\]-\u2027\u202A-\uD7FF\uF900-\uFFFF]" + ( // single codepoint combiningDiacriticalMarkString + "*") + // ...plus accents "|[\uD800-\uDBFF][\uDC00-\uDFFF]" + ( // surrogate pair combiningDiacriticalMarkString + "*") + // ...plus accents "|\\\\verb\\*([^]).*?\\4" + // \verb* "|\\\\verb([^*a-zA-Z]).*?\\5" + ( // \verb unstarred "|" + controlWordWhitespaceRegexString) + ( // \macroName + spaces "|" + controlSymbolRegexString + ")"); // \\, \', etc. /** Main Lexer class */ class Lexer { // Category codes. The lexer only supports comment characters (14) for now. // MacroExpander additionally distinguishes active (13). constructor(input, settings) { this.input = void 0; this.settings = void 0; this.tokenRegex = void 0; this.catcodes = void 0; // Separate accents from characters this.input = input; this.settings = settings; this.tokenRegex = new RegExp(tokenRegexString, 'g'); this.catcodes = { "%": 14, // comment character "~": 13 // active character }; } setCatcode(char, code) { this.catcodes[char] = code; } /** * This function lexes a single token. */ lex() { var input = this.input; var pos = this.tokenRegex.lastIndex; if (pos === input.length) { return new Token("EOF", new SourceLocation(this, pos, pos)); } var match = this.tokenRegex.exec(input); if (match === null || match.index !== pos) { throw new ParseError("Unexpected character: '" + input[pos] + "'", new Token(input[pos], new SourceLocation(this, pos, pos + 1))); } var text = match[6] || match[3] || (match[2] ? "\\ " : " "); if (this.catcodes[text] === 14) { // comment character var nlIndex = input.indexOf('\n', this.tokenRegex.lastIndex); if (nlIndex === -1) { this.tokenRegex.lastIndex = input.length; // EOF this.settings.reportNonstrict("commentAtEnd", "% comment has no terminating newline; LaTeX would " + "fail because of commenting the end of math mode (e.g. $)"); } else { this.tokenRegex.lastIndex = nlIndex + 1; } return this.lex(); } return new Token(text, new SourceLocation(this, pos, this.tokenRegex.lastIndex)); } } /** * A `Namespace` refers to a space of nameable things like macros or lengths, * which can be `set` either globally or local to a nested group, using an * undo stack similar to how TeX implements this functionality. * Performance-wise, `get` and local `set` take constant time, while global * `set` takes time proportional to the depth of group nesting. */ class Namespace { /** * Both arguments are optional. The first argument is an object of * built-in mappings which never change. The second argument is an object * of initial (global-level) mappings, which will constantly change * according to any global/top-level `set`s done. */ constructor(builtins, globalMacros) { if (builtins === void 0) { builtins = {}; } if (globalMacros === void 0) { globalMacros = {}; } this.current = void 0; this.builtins = void 0; this.undefStack = void 0; this.current = globalMacros; this.builtins = builtins; this.undefStack = []; } /** * Start a new nested group, affecting future local `set`s. */ beginGroup() { this.undefStack.push({}); } /** * End current nested group, restoring values before the group began. */ endGroup() { if (this.undefStack.length === 0) { throw new ParseError("Unbalanced namespace destruction: attempt " + "to pop global namespace; please report this as a bug"); } var undefs = this.undefStack.pop(); for (var undef in undefs) { if (undefs.hasOwnProperty(undef)) { if (undefs[undef] == null) { delete this.current[undef]; } else { this.current[undef] = undefs[undef]; } } } } /** * Ends all currently nested groups (if any), restoring values before the * groups began. Useful in case of an error in the middle of parsing. */ endGroups() { while (this.undefStack.length > 0) { this.endGroup(); } } /** * Detect whether `name` has a definition. Equivalent to * `get(name) != null`. */ has(name) { return this.current.hasOwnProperty(name) || this.builtins.hasOwnProperty(name); } /** * Get the current value of a name, or `undefined` if there is no value. * * Note: Do not use `if (namespace.get(...))` to detect whether a macro * is defined, as the definition may be the empty string which evaluates * to `false` in JavaScript. Use `if (namespace.get(...) != null)` or * `if (namespace.has(...))`. */ get(name) { if (this.current.hasOwnProperty(name)) { return this.current[name]; } else { return this.builtins[name]; } } /** * Set the current value of a name, and optionally set it globally too. * Local set() sets the current value and (when appropriate) adds an undo * operation to the undo stack. Global set() may change the undo * operation at every level, so takes time linear in their number. * A value of undefined means to delete existing definitions. */ set(name, value, global) { if (global === void 0) { global = false; } if (global) { // Global set is equivalent to setting in all groups. Simulate this // by destroying any undos currently scheduled for this name, // and adding an undo with the *new* value (in case it later gets // locally reset within this environment). for (var i = 0; i < this.undefStack.length; i++) { delete this.undefStack[i][name]; } if (this.undefStack.length > 0) { this.undefStack[this.undefStack.length - 1][name] = value; } } else { // Undo this set at end of this group (possibly to `undefined`), // unless an undo is already in place, in which case that older // value is the correct one. var top = this.undefStack[this.undefStack.length - 1]; if (top && !top.hasOwnProperty(name)) { top[name] = this.current[name]; } } if (value == null) { delete this.current[name]; } else { this.current[name] = value; } } } /** * Predefined macros for KaTeX. * This can be used to define some commands in terms of others. */ var macros = _macros; // macro tools defineMacro("\\noexpand", function (context) { // The expansion is the token itself; but that token is interpreted // as if its meaning were ‘\relax’ if it is a control sequence that // would ordinarily be expanded by TeX’s expansion rules. var t = context.popToken(); if (context.isExpandable(t.text)) { t.noexpand = true; t.treatAsRelax = true; } return { tokens: [t], numArgs: 0 }; }); defineMacro("\\expandafter", function (context) { // TeX first reads the token that comes immediately after \expandafter, // without expanding it; let’s call this token t. Then TeX reads the // token that comes after t (and possibly more tokens, if that token // has an argument), replacing it by its expansion. Finally TeX puts // t back in front of that expansion. var t = context.popToken(); context.expandOnce(true); // expand only an expandable token return { tokens: [t], numArgs: 0 }; }); // LaTeX's \@firstoftwo{#1}{#2} expands to #1, skipping #2 // TeX source: \long\def\@firstoftwo#1#2{#1} defineMacro("\\@firstoftwo", function (context) { var args = context.consumeArgs(2); return { tokens: args[0], numArgs: 0 }; }); // LaTeX's \@secondoftwo{#1}{#2} expands to #2, skipping #1 // TeX source: \long\def\@secondoftwo#1#2{#2} defineMacro("\\@secondoftwo", function (context) { var args = context.consumeArgs(2); return { tokens: args[1], numArgs: 0 }; }); // LaTeX's \@ifnextchar{#1}{#2}{#3} looks ahead to the next (unexpanded) // symbol that isn't a space, consuming any spaces but not consuming the // first nonspace character. If that nonspace character matches #1, then // the macro expands to #2; otherwise, it expands to #3. defineMacro("\\@ifnextchar", function (context) { var args = context.consumeArgs(3); // symbol, if, else context.consumeSpaces(); var nextToken = context.future(); if (args[0].length === 1 && args[0][0].text === nextToken.text) { return { tokens: args[1], numArgs: 0 }; } else { return { tokens: args[2], numArgs: 0 }; } }); // LaTeX's \@ifstar{#1}{#2} looks ahead to the next (unexpanded) symbol. // If it is `*`, then it consumes the symbol, and the macro expands to #1; // otherwise, the macro expands to #2 (without consuming the symbol). // TeX source: \def\@ifstar#1{\@ifnextchar *{\@firstoftwo{#1}}} defineMacro("\\@ifstar", "\\@ifnextchar *{\\@firstoftwo{#1}}"); // LaTeX's \TextOrMath{#1}{#2} expands to #1 in text mode, #2 in math mode defineMacro("\\TextOrMath", function (context) { var args = context.consumeArgs(2); if (context.mode === 'text') { return { tokens: args[0], numArgs: 0 }; } else { return { tokens: args[1], numArgs: 0 }; } }); // Lookup table for parsing numbers in base 8 through 16 var digitToNumber = { "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "a": 10, "A": 10, "b": 11, "B": 11, "c": 12, "C": 12, "d": 13, "D": 13, "e": 14, "E": 14, "f": 15, "F": 15 }; // TeX \char makes a literal character (catcode 12) using the following forms: // (see The TeXBook, p. 43) // \char123 -- decimal // \char'123 -- octal // \char"123 -- hex // \char`x -- character that can be written (i.e. isn't active) // \char`\x -- character that cannot be written (e.g. %) // These all refer to characters from the font, so we turn them into special // calls to a function \@char dealt with in the Parser. defineMacro("\\char", function (context) { var token = context.popToken(); var base; var number = ''; if (token.text === "'") { base = 8; token = context.popToken(); } else if (token.text === '"') { base = 16; token = context.popToken(); } else if (token.text === "`") { token = context.popToken(); if (token.text[0] === "\\") { number = token.text.charCodeAt(1); } else if (token.text === "EOF") { throw new ParseError("\\char` missing argument"); } else { number = token.text.charCodeAt(0); } } else { base = 10; } if (base) { // Parse a number in the given base, starting with first `token`. number = digitToNumber[token.text]; if (number == null || number >= base) { throw new ParseError("Invalid base-" + base + " digit " + token.text); } var digit; while ((digit = digitToNumber[context.future().text]) != null && digit < base) { number *= base; number += digit; context.popToken(); } } return "\\@char{" + number + "}"; }); // \newcommand{\macro}[args]{definition} // \renewcommand{\macro}[args]{definition} // TODO: Optional arguments: \newcommand{\macro}[args][default]{definition} var newcommand = (context, existsOK, nonexistsOK, skipIfExists) => { var arg = context.consumeArg().tokens; if (arg.length !== 1) { throw new ParseError("\\newcommand's first argument must be a macro name"); } var name = arg[0].text; var exists = context.isDefined(name); if (exists && !existsOK) { throw new ParseError("\\newcommand{" + name + "} attempting to redefine " + (name + "; use \\renewcommand")); } if (!exists && !nonexistsOK) { throw new ParseError("\\renewcommand{" + name + "} when command " + name + " " + "does not yet exist; use \\newcommand"); } var numArgs = 0; arg = context.consumeArg().tokens; if (arg.length === 1 && arg[0].text === "[") { var argText = ''; var token = context.expandNextToken(); while (token.text !== "]" && token.text !== "EOF") { // TODO: Should properly expand arg, e.g., ignore {}s argText += token.text; token = context.expandNextToken(); } if (!argText.match(/^\s*[0-9]+\s*$/)) { throw new ParseError("Invalid number of arguments: " + argText); } numArgs = parseInt(argText); arg = context.consumeArg().tokens; } if (!(exists && skipIfExists)) { // Final arg is the expansion of the macro context.macros.set(name, { tokens: arg, numArgs }); } return ''; }; defineMacro("\\newcommand", context => newcommand(context, false, true, false)); defineMacro("\\renewcommand", context => newcommand(context, true, false, false)); defineMacro("\\providecommand", context => newcommand(context, true, true, true)); // terminal (console) tools defineMacro("\\message", context => { var arg = context.consumeArgs(1)[0]; // eslint-disable-next-line no-console console.log(arg.reverse().map(token => token.text).join("")); return ''; }); defineMacro("\\errmessage", context => { var arg = context.consumeArgs(1)[0]; // eslint-disable-next-line no-console console.error(arg.reverse().map(token => token.text).join("")); return ''; }); defineMacro("\\show", context => { var tok = context.popToken(); var name = tok.text; // eslint-disable-next-line no-console console.log(tok, context.macros.get(name), functions[name], symbols.math[name], symbols.text[name]); return ''; }); ////////////////////////////////////////////////////////////////////// // Grouping // \let\bgroup={ \let\egroup=} defineMacro("\\bgroup", "{"); defineMacro("\\egroup", "}"); // Symbols from latex.ltx: // \def~{\nobreakspace{}} // \def\lq{`} // \def\rq{'} // \def \aa {\r a} // \def \AA {\r A} defineMacro("~", "\\nobreakspace"); defineMacro("\\lq", "`"); defineMacro("\\rq", "'"); defineMacro("\\aa", "\\r a"); defineMacro("\\AA", "\\r A"); // Copyright (C) and registered (R) symbols. Use raw symbol in MathML. // \DeclareTextCommandDefault{\textcopyright}{\textcircled{c}} // \DeclareTextCommandDefault{\textregistered}{\textcircled{% // \check@mathfonts\fontsize\sf@size\z@\math@fontsfalse\selectfont R}} // \DeclareRobustCommand{\copyright}{% // \ifmmode{\nfss@text{\textcopyright}}\else\textcopyright\fi} defineMacro("\\textcopyright", "\\html@mathml{\\textcircled{c}}{\\char`©}"); defineMacro("\\copyright", "\\TextOrMath{\\textcopyright}{\\text{\\textcopyright}}"); defineMacro("\\textregistered", "\\html@mathml{\\textcircled{\\scriptsize R}}{\\char`®}"); // Characters omitted from Unicode range 1D400–1D7FF defineMacro("\u212C", "\\mathscr{B}"); // script defineMacro("\u2130", "\\mathscr{E}"); defineMacro("\u2131", "\\mathscr{F}"); defineMacro("\u210B", "\\mathscr{H}"); defineMacro("\u2110", "\\mathscr{I}"); defineMacro("\u2112", "\\mathscr{L}"); defineMacro("\u2133", "\\mathscr{M}"); defineMacro("\u211B", "\\mathscr{R}"); defineMacro("\u212D", "\\mathfrak{C}"); // Fraktur defineMacro("\u210C", "\\mathfrak{H}"); defineMacro("\u2128", "\\mathfrak{Z}"); // Define \Bbbk with a macro that works in both HTML and MathML. defineMacro("\\Bbbk", "\\Bbb{k}"); // Unicode middle dot // The KaTeX fonts do not contain U+00B7. Instead, \cdotp displays // the dot at U+22C5 and gives it punct spacing. defineMacro("\u00b7", "\\cdotp"); // \llap and \rlap render their contents in text mode defineMacro("\\llap", "\\mathllap{\\textrm{#1}}"); defineMacro("\\rlap", "\\mathrlap{\\textrm{#1}}"); defineMacro("\\clap", "\\mathclap{\\textrm{#1}}"); // \mathstrut from the TeXbook, p 360 defineMacro("\\mathstrut", "\\vphantom{(}"); // \underbar from TeXbook p 353 defineMacro("\\underbar", "\\underline{\\text{#1}}"); // \not is defined by base/fontmath.ltx via // \DeclareMathSymbol{\not}{\mathrel}{symbols}{"36} // It's thus treated like a \mathrel, but defined by a symbol that has zero // width but extends to the right. We use \rlap to get that spacing. // For MathML we write U+0338 here. buildMathML.js will then do the overlay. defineMacro("\\not", '\\html@mathml{\\mathrel{\\mathrlap\\@not}}{\\char"338}'); // Negated symbols from base/fontmath.ltx: // \def\neq{\not=} \let\ne=\neq // \DeclareRobustCommand // \notin{\mathrel{\m@th\mathpalette\c@ncel\in}} // \def\c@ncel#1#2{\m@th\ooalign{$\hfil#1\mkern1mu/\hfil$\crcr$#1#2$}} defineMacro("\\neq", "\\html@mathml{\\mathrel{\\not=}}{\\mathrel{\\char`≠}}"); defineMacro("\\ne", "\\neq"); defineMacro("\u2260", "\\neq"); defineMacro("\\notin", "\\html@mathml{\\mathrel{{\\in}\\mathllap{/\\mskip1mu}}}" + "{\\mathrel{\\char`∉}}"); defineMacro("\u2209", "\\notin"); // Unicode stacked relations defineMacro("\u2258", "\\html@mathml{" + "\\mathrel{=\\kern{-1em}\\raisebox{0.4em}{$\\scriptsize\\frown$}}" + "}{\\mathrel{\\char`\u2258}}"); defineMacro("\u2259", "\\html@mathml{\\stackrel{\\tiny\\wedge}{=}}{\\mathrel{\\char`\u2258}}"); defineMacro("\u225A", "\\html@mathml{\\stackrel{\\tiny\\vee}{=}}{\\mathrel{\\char`\u225A}}"); defineMacro("\u225B", "\\html@mathml{\\stackrel{\\scriptsize\\star}{=}}" + "{\\mathrel{\\char`\u225B}}"); defineMacro("\u225D", "\\html@mathml{\\stackrel{\\tiny\\mathrm{def}}{=}}" + "{\\mathrel{\\char`\u225D}}"); defineMacro("\u225E", "\\html@mathml{\\stackrel{\\tiny\\mathrm{m}}{=}}" + "{\\mathrel{\\char`\u225E}}"); defineMacro("\u225F", "\\html@mathml{\\stackrel{\\tiny?}{=}}{\\mathrel{\\char`\u225F}}"); // Misc Unicode defineMacro("\u27C2", "\\perp"); defineMacro("\u203C", "\\mathclose{!\\mkern-0.8mu!}"); defineMacro("\u220C", "\\notni"); defineMacro("\u231C", "\\ulcorner"); defineMacro("\u231D", "\\urcorner"); defineMacro("\u231E", "\\llcorner"); defineMacro("\u231F", "\\lrcorner"); defineMacro("\u00A9", "\\copyright"); defineMacro("\u00AE", "\\textregistered"); defineMacro("\uFE0F", "\\textregistered"); // The KaTeX fonts have corners at codepoints that don't match Unicode. // For MathML purposes, use the Unicode code point. defineMacro("\\ulcorner", "\\html@mathml{\\@ulcorner}{\\mathop{\\char\"231c}}"); defineMacro("\\urcorner", "\\html@mathml{\\@urcorner}{\\mathop{\\char\"231d}}"); defineMacro("\\llcorner", "\\html@mathml{\\@llcorner}{\\mathop{\\char\"231e}}"); defineMacro("\\lrcorner", "\\html@mathml{\\@lrcorner}{\\mathop{\\char\"231f}}"); ////////////////////////////////////////////////////////////////////// // LaTeX_2ε // \vdots{\vbox{\baselineskip4\p@ \lineskiplimit\z@ // \kern6\p@\hbox{.}\hbox{.}\hbox{.}}} // We'll call \varvdots, which gets a glyph from symbols.js. // The zero-width rule gets us an equivalent to the vertical 6pt kern. defineMacro("\\vdots", "{\\varvdots\\rule{0pt}{15pt}}"); defineMacro("\u22ee", "\\vdots"); ////////////////////////////////////////////////////////////////////// // amsmath.sty // http://mirrors.concertpass.com/tex-archive/macros/latex/required/amsmath/amsmath.pdf // Italic Greek capital letters. AMS defines these with \DeclareMathSymbol, // but they are equivalent to \mathit{\Letter}. defineMacro("\\varGamma", "\\mathit{\\Gamma}"); defineMacro("\\varDelta", "\\mathit{\\Delta}"); defineMacro("\\varTheta", "\\mathit{\\Theta}"); defineMacro("\\varLambda", "\\mathit{\\Lambda}"); defineMacro("\\varXi", "\\mathit{\\Xi}"); defineMacro("\\varPi", "\\mathit{\\Pi}"); defineMacro("\\varSigma", "\\mathit{\\Sigma}"); defineMacro("\\varUpsilon", "\\mathit{\\Upsilon}"); defineMacro("\\varPhi", "\\mathit{\\Phi}"); defineMacro("\\varPsi", "\\mathit{\\Psi}"); defineMacro("\\varOmega", "\\mathit{\\Omega}"); //\newcommand{\substack}[1]{\subarray{c}#1\endsubarray} defineMacro("\\substack", "\\begin{subarray}{c}#1\\end{subarray}"); // \renewcommand{\colon}{\nobreak\mskip2mu\mathpunct{}\nonscript // \mkern-\thinmuskip{:}\mskip6muplus1mu\relax} defineMacro("\\colon", "\\nobreak\\mskip2mu\\mathpunct{}" + "\\mathchoice{\\mkern-3mu}{\\mkern-3mu}{}{}{:}\\mskip6mu\\relax"); // \newcommand{\boxed}[1]{\fbox{\m@th$\displaystyle#1$}} defineMacro("\\boxed", "\\fbox{$\\displaystyle{#1}$}"); // \def\iff{\DOTSB\;\Longleftrightarrow\;} // \def\implies{\DOTSB\;\Longrightarrow\;} // \def\impliedby{\DOTSB\;\Longleftarrow\;} defineMacro("\\iff", "\\DOTSB\\;\\Longleftrightarrow\\;"); defineMacro("\\implies", "\\DOTSB\\;\\Longrightarrow\\;"); defineMacro("\\impliedby", "\\DOTSB\\;\\Longleftarrow\\;"); // \def\dddot#1{{\mathop{#1}\limits^{\vbox to-1.4\ex@{\kern-\tw@\ex@ // \hbox{\normalfont ...}\vss}}}} // We use \overset which avoids the vertical shift of \mathop. defineMacro("\\dddot", "{\\overset{\\raisebox{-0.1ex}{\\normalsize ...}}{#1}}"); defineMacro("\\ddddot", "{\\overset{\\raisebox{-0.1ex}{\\normalsize ....}}{#1}}"); // AMSMath's automatic \dots, based on \mdots@@ macro. var dotsByToken = { ',': '\\dotsc', '\\not': '\\dotsb', // \keybin@ checks for the following: '+': '\\dotsb', '=': '\\dotsb', '<': '\\dotsb', '>': '\\dotsb', '-': '\\dotsb', '*': '\\dotsb', ':': '\\dotsb', // Symbols whose definition starts with \DOTSB: '\\DOTSB': '\\dotsb', '\\coprod': '\\dotsb', '\\bigvee': '\\dotsb', '\\bigwedge': '\\dotsb', '\\biguplus': '\\dotsb', '\\bigcap': '\\dotsb', '\\bigcup': '\\dotsb', '\\prod': '\\dotsb', '\\sum': '\\dotsb', '\\bigotimes': '\\dotsb', '\\bigoplus': '\\dotsb', '\\bigodot': '\\dotsb', '\\bigsqcup': '\\dotsb', '\\And': '\\dotsb', '\\longrightarrow': '\\dotsb', '\\Longrightarrow': '\\dotsb', '\\longleftarrow': '\\dotsb', '\\Longleftarrow': '\\dotsb', '\\longleftrightarrow': '\\dotsb', '\\Longleftrightarrow': '\\dotsb', '\\mapsto': '\\dotsb', '\\longmapsto': '\\dotsb', '\\hookrightarrow': '\\dotsb', '\\doteq': '\\dotsb', // Symbols whose definition starts with \mathbin: '\\mathbin': '\\dotsb', // Symbols whose definition starts with \mathrel: '\\mathrel': '\\dotsb', '\\relbar': '\\dotsb', '\\Relbar': '\\dotsb', '\\xrightarrow': '\\dotsb', '\\xleftarrow': '\\dotsb', // Symbols whose definition starts with \DOTSI: '\\DOTSI': '\\dotsi', '\\int': '\\dotsi', '\\oint': '\\dotsi', '\\iint': '\\dotsi', '\\iiint': '\\dotsi', '\\iiiint': '\\dotsi', '\\idotsint': '\\dotsi', // Symbols whose definition starts with \DOTSX: '\\DOTSX': '\\dotsx' }; defineMacro("\\dots", function (context) { // TODO: If used in text mode, should expand to \textellipsis. // However, in KaTeX, \textellipsis and \ldots behave the same // (in text mode), and it's unlikely we'd see any of the math commands // that affect the behavior of \dots when in text mode. So fine for now // (until we support \ifmmode ... \else ... \fi). var thedots = '\\dotso'; var next = context.expandAfterFuture().text; if (next in dotsByToken) { thedots = dotsByToken[next]; } else if (next.slice(0, 4) === '\\not') { thedots = '\\dotsb'; } else if (next in symbols.math) { if (utils.contains(['bin', 'rel'], symbols.math[next].group)) { thedots = '\\dotsb'; } } return thedots; }); var spaceAfterDots = { // \rightdelim@ checks for the following: ')': true, ']': true, '\\rbrack': true, '\\}': true, '\\rbrace': true, '\\rangle': true, '\\rceil': true, '\\rfloor': true, '\\rgroup': true, '\\rmoustache': true, '\\right': true, '\\bigr': true, '\\biggr': true, '\\Bigr': true, '\\Biggr': true, // \extra@ also tests for the following: '$': true, // \extrap@ checks for the following: ';': true, '.': true, ',': true }; defineMacro("\\dotso", function (context) { var next = context.future().text; if (next in spaceAfterDots) { return "\\ldots\\,"; } else { return "\\ldots"; } }); defineMacro("\\dotsc", function (context) { var next = context.future().text; // \dotsc uses \extra@ but not \extrap@, instead specially checking for // ';' and '.', but doesn't check for ','. if (next in spaceAfterDots && next !== ',') { return "\\ldots\\,"; } else { return "\\ldots"; } }); defineMacro("\\cdots", function (context) { var next = context.future().text; if (next in spaceAfterDots) { return "\\@cdots\\,"; } else { return "\\@cdots"; } }); defineMacro("\\dotsb", "\\cdots"); defineMacro("\\dotsm", "\\cdots"); defineMacro("\\dotsi", "\\!\\cdots"); // amsmath doesn't actually define \dotsx, but \dots followed by a macro // starting with \DOTSX implies \dotso, and then \extra@ detects this case // and forces the added `\,`. defineMacro("\\dotsx", "\\ldots\\,"); // \let\DOTSI\relax // \let\DOTSB\relax // \let\DOTSX\relax defineMacro("\\DOTSI", "\\relax"); defineMacro("\\DOTSB", "\\relax"); defineMacro("\\DOTSX", "\\relax"); // Spacing, based on amsmath.sty's override of LaTeX defaults // \DeclareRobustCommand{\tmspace}[3]{% // \ifmmode\mskip#1#2\else\kern#1#3\fi\relax} defineMacro("\\tmspace", "\\TextOrMath{\\kern#1#3}{\\mskip#1#2}\\relax"); // \renewcommand{\,}{\tmspace+\thinmuskip{.1667em}} // TODO: math mode should use \thinmuskip defineMacro("\\,", "\\tmspace+{3mu}{.1667em}"); // \let\thinspace\, defineMacro("\\thinspace", "\\,"); // \def\>{\mskip\medmuskip} // \renewcommand{\:}{\tmspace+\medmuskip{.2222em}} // TODO: \> and math mode of \: should use \medmuskip = 4mu plus 2mu minus 4mu defineMacro("\\>", "\\mskip{4mu}"); defineMacro("\\:", "\\tmspace+{4mu}{.2222em}"); // \let\medspace\: defineMacro("\\medspace", "\\:"); // \renewcommand{\;}{\tmspace+\thickmuskip{.2777em}} // TODO: math mode should use \thickmuskip = 5mu plus 5mu defineMacro("\\;", "\\tmspace+{5mu}{.2777em}"); // \let\thickspace\; defineMacro("\\thickspace", "\\;"); // \renewcommand{\!}{\tmspace-\thinmuskip{.1667em}} // TODO: math mode should use \thinmuskip defineMacro("\\!", "\\tmspace-{3mu}{.1667em}"); // \let\negthinspace\! defineMacro("\\negthinspace", "\\!"); // \newcommand{\negmedspace}{\tmspace-\medmuskip{.2222em}} // TODO: math mode should use \medmuskip defineMacro("\\negmedspace", "\\tmspace-{4mu}{.2222em}"); // \newcommand{\negthickspace}{\tmspace-\thickmuskip{.2777em}} // TODO: math mode should use \thickmuskip defineMacro("\\negthickspace", "\\tmspace-{5mu}{.277em}"); // \def\enspace{\kern.5em } defineMacro("\\enspace", "\\kern.5em "); // \def\enskip{\hskip.5em\relax} defineMacro("\\enskip", "\\hskip.5em\\relax"); // \def\quad{\hskip1em\relax} defineMacro("\\quad", "\\hskip1em\\relax"); // \def\qquad{\hskip2em\relax} defineMacro("\\qquad", "\\hskip2em\\relax"); // \tag@in@display form of \tag defineMacro("\\tag", "\\@ifstar\\tag@literal\\tag@paren"); defineMacro("\\tag@paren", "\\tag@literal{({#1})}"); defineMacro("\\tag@literal", context => { if (context.macros.get("\\df@tag")) { throw new ParseError("Multiple \\tag"); } return "\\gdef\\df@tag{\\text{#1}}"; }); // \renewcommand{\bmod}{\nonscript\mskip-\medmuskip\mkern5mu\mathbin // {\operator@font mod}\penalty900 // \mkern5mu\nonscript\mskip-\medmuskip} // \newcommand{\pod}[1]{\allowbreak // \if@display\mkern18mu\else\mkern8mu\fi(#1)} // \renewcommand{\pmod}[1]{\pod{{\operator@font mod}\mkern6mu#1}} // \newcommand{\mod}[1]{\allowbreak\if@display\mkern18mu // \else\mkern12mu\fi{\operator@font mod}\,\,#1} // TODO: math mode should use \medmuskip = 4mu plus 2mu minus 4mu defineMacro("\\bmod", "\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}" + "\\mathbin{\\rm mod}" + "\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}"); defineMacro("\\pod", "\\allowbreak" + "\\mathchoice{\\mkern18mu}{\\mkern8mu}{\\mkern8mu}{\\mkern8mu}(#1)"); defineMacro("\\pmod", "\\pod{{\\rm mod}\\mkern6mu#1}"); defineMacro("\\mod", "\\allowbreak" + "\\mathchoice{\\mkern18mu}{\\mkern12mu}{\\mkern12mu}{\\mkern12mu}" + "{\\rm mod}\\,\\,#1"); ////////////////////////////////////////////////////////////////////// // LaTeX source2e // \expandafter\let\expandafter\@normalcr // \csname\expandafter\@gobble\string\\ \endcsname // \DeclareRobustCommand\newline{\@normalcr\relax} defineMacro("\\newline", "\\\\\\relax"); // \def\TeX{T\kern-.1667em\lower.5ex\hbox{E}\kern-.125emX\@} // TODO: Doesn't normally work in math mode because \@ fails. KaTeX doesn't // support \@ yet, so that's omitted, and we add \text so that the result // doesn't look funny in math mode. defineMacro("\\TeX", "\\textrm{\\html@mathml{" + "T\\kern-.1667em\\raisebox{-.5ex}{E}\\kern-.125emX" + "}{TeX}}"); // \DeclareRobustCommand{\LaTeX}{L\kern-.36em% // {\sbox\z@ T% // \vbox to\ht\z@{\hbox{\check@mathfonts // \fontsize\sf@size\z@ // \math@fontsfalse\selectfont // A}% // \vss}% // }% // \kern-.15em% // \TeX} // This code aligns the top of the A with the T (from the perspective of TeX's // boxes, though visually the A appears to extend above slightly). // We compute the corresponding \raisebox when A is rendered in \normalsize // \scriptstyle, which has a scale factor of 0.7 (see Options.js). var latexRaiseA = makeEm(fontMetricsData['Main-Regular']["T".charCodeAt(0)][1] - 0.7 * fontMetricsData['Main-Regular']["A".charCodeAt(0)][1]); defineMacro("\\LaTeX", "\\textrm{\\html@mathml{" + ("L\\kern-.36em\\raisebox{" + latexRaiseA + "}{\\scriptstyle A}") + "\\kern-.15em\\TeX}{LaTeX}}"); // New KaTeX logo based on tweaking LaTeX logo defineMacro("\\KaTeX", "\\textrm{\\html@mathml{" + ("K\\kern-.17em\\raisebox{" + latexRaiseA + "}{\\scriptstyle A}") + "\\kern-.15em\\TeX}{KaTeX}}"); // \DeclareRobustCommand\hspace{\@ifstar\@hspacer\@hspace} // \def\@hspace#1{\hskip #1\relax} // \def\@hspacer#1{\vrule \@width\z@\nobreak // \hskip #1\hskip \z@skip} defineMacro("\\hspace", "\\@ifstar\\@hspacer\\@hspace"); defineMacro("\\@hspace", "\\hskip #1\\relax"); defineMacro("\\@hspacer", "\\rule{0pt}{0pt}\\hskip #1\\relax"); ////////////////////////////////////////////////////////////////////// // mathtools.sty //\providecommand\ordinarycolon{:} defineMacro("\\ordinarycolon", ":"); //\def\vcentcolon{\mathrel{\mathop\ordinarycolon}} //TODO(edemaine): Not yet centered. Fix via \raisebox or #726 defineMacro("\\vcentcolon", "\\mathrel{\\mathop\\ordinarycolon}"); // \providecommand*\dblcolon{\vcentcolon\mathrel{\mkern-.9mu}\vcentcolon} defineMacro("\\dblcolon", "\\html@mathml{" + "\\mathrel{\\vcentcolon\\mathrel{\\mkern-.9mu}\\vcentcolon}}" + "{\\mathop{\\char\"2237}}"); // \providecommand*\coloneqq{\vcentcolon\mathrel{\mkern-1.2mu}=} defineMacro("\\coloneqq", "\\html@mathml{" + "\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}=}}" + "{\\mathop{\\char\"2254}}"); // ≔ // \providecommand*\Coloneqq{\dblcolon\mathrel{\mkern-1.2mu}=} defineMacro("\\Coloneqq", "\\html@mathml{" + "\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}=}}" + "{\\mathop{\\char\"2237\\char\"3d}}"); // \providecommand*\coloneq{\vcentcolon\mathrel{\mkern-1.2mu}\mathrel{-}} defineMacro("\\coloneq", "\\html@mathml{" + "\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}" + "{\\mathop{\\char\"3a\\char\"2212}}"); // \providecommand*\Coloneq{\dblcolon\mathrel{\mkern-1.2mu}\mathrel{-}} defineMacro("\\Coloneq", "\\html@mathml{" + "\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}" + "{\\mathop{\\char\"2237\\char\"2212}}"); // \providecommand*\eqqcolon{=\mathrel{\mkern-1.2mu}\vcentcolon} defineMacro("\\eqqcolon", "\\html@mathml{" + "\\mathrel{=\\mathrel{\\mkern-1.2mu}\\vcentcolon}}" + "{\\mathop{\\char\"2255}}"); // ≕ // \providecommand*\Eqqcolon{=\mathrel{\mkern-1.2mu}\dblcolon} defineMacro("\\Eqqcolon", "\\html@mathml{" + "\\mathrel{=\\mathrel{\\mkern-1.2mu}\\dblcolon}}" + "{\\mathop{\\char\"3d\\char\"2237}}"); // \providecommand*\eqcolon{\mathrel{-}\mathrel{\mkern-1.2mu}\vcentcolon} defineMacro("\\eqcolon", "\\html@mathml{" + "\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\vcentcolon}}" + "{\\mathop{\\char\"2239}}"); // \providecommand*\Eqcolon{\mathrel{-}\mathrel{\mkern-1.2mu}\dblcolon} defineMacro("\\Eqcolon", "\\html@mathml{" + "\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\dblcolon}}" + "{\\mathop{\\char\"2212\\char\"2237}}"); // \providecommand*\colonapprox{\vcentcolon\mathrel{\mkern-1.2mu}\approx} defineMacro("\\colonapprox", "\\html@mathml{" + "\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\approx}}" + "{\\mathop{\\char\"3a\\char\"2248}}"); // \providecommand*\Colonapprox{\dblcolon\mathrel{\mkern-1.2mu}\approx} defineMacro("\\Colonapprox", "\\html@mathml{" + "\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\approx}}" + "{\\mathop{\\char\"2237\\char\"2248}}"); // \providecommand*\colonsim{\vcentcolon\mathrel{\mkern-1.2mu}\sim} defineMacro("\\colonsim", "\\html@mathml{" + "\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\sim}}" + "{\\mathop{\\char\"3a\\char\"223c}}"); // \providecommand*\Colonsim{\dblcolon\mathrel{\mkern-1.2mu}\sim} defineMacro("\\Colonsim", "\\html@mathml{" + "\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\sim}}" + "{\\mathop{\\char\"2237\\char\"223c}}"); // Some Unicode characters are implemented with macros to mathtools functions. defineMacro("\u2237", "\\dblcolon"); // :: defineMacro("\u2239", "\\eqcolon"); // -: defineMacro("\u2254", "\\coloneqq"); // := defineMacro("\u2255", "\\eqqcolon"); // =: defineMacro("\u2A74", "\\Coloneqq"); // ::= ////////////////////////////////////////////////////////////////////// // colonequals.sty // Alternate names for mathtools's macros: defineMacro("\\ratio", "\\vcentcolon"); defineMacro("\\coloncolon", "\\dblcolon"); defineMacro("\\colonequals", "\\coloneqq"); defineMacro("\\coloncolonequals", "\\Coloneqq"); defineMacro("\\equalscolon", "\\eqqcolon"); defineMacro("\\equalscoloncolon", "\\Eqqcolon"); defineMacro("\\colonminus", "\\coloneq"); defineMacro("\\coloncolonminus", "\\Coloneq"); defineMacro("\\minuscolon", "\\eqcolon"); defineMacro("\\minuscoloncolon", "\\Eqcolon"); // \colonapprox name is same in mathtools and colonequals. defineMacro("\\coloncolonapprox", "\\Colonapprox"); // \colonsim name is same in mathtools and colonequals. defineMacro("\\coloncolonsim", "\\Colonsim"); // Additional macros, implemented by analogy with mathtools definitions: defineMacro("\\simcolon", "\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\vcentcolon}"); defineMacro("\\simcoloncolon", "\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\dblcolon}"); defineMacro("\\approxcolon", "\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\vcentcolon}"); defineMacro("\\approxcoloncolon", "\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\dblcolon}"); // Present in newtxmath, pxfonts and txfonts defineMacro("\\notni", "\\html@mathml{\\not\\ni}{\\mathrel{\\char`\u220C}}"); defineMacro("\\limsup", "\\DOTSB\\operatorname*{lim\\,sup}"); defineMacro("\\liminf", "\\DOTSB\\operatorname*{lim\\,inf}"); ////////////////////////////////////////////////////////////////////// // From amsopn.sty defineMacro("\\injlim", "\\DOTSB\\operatorname*{inj\\,lim}"); defineMacro("\\projlim", "\\DOTSB\\operatorname*{proj\\,lim}"); defineMacro("\\varlimsup", "\\DOTSB\\operatorname*{\\overline{lim}}"); defineMacro("\\varliminf", "\\DOTSB\\operatorname*{\\underline{lim}}"); defineMacro("\\varinjlim", "\\DOTSB\\operatorname*{\\underrightarrow{lim}}"); defineMacro("\\varprojlim", "\\DOTSB\\operatorname*{\\underleftarrow{lim}}"); ////////////////////////////////////////////////////////////////////// // MathML alternates for KaTeX glyphs in the Unicode private area defineMacro("\\gvertneqq", "\\html@mathml{\\@gvertneqq}{\u2269}"); defineMacro("\\lvertneqq", "\\html@mathml{\\@lvertneqq}{\u2268}"); defineMacro("\\ngeqq", "\\html@mathml{\\@ngeqq}{\u2271}"); defineMacro("\\ngeqslant", "\\html@mathml{\\@ngeqslant}{\u2271}"); defineMacro("\\nleqq", "\\html@mathml{\\@nleqq}{\u2270}"); defineMacro("\\nleqslant", "\\html@mathml{\\@nleqslant}{\u2270}"); defineMacro("\\nshortmid", "\\html@mathml{\\@nshortmid}{∤}"); defineMacro("\\nshortparallel", "\\html@mathml{\\@nshortparallel}{∦}"); defineMacro("\\nsubseteqq", "\\html@mathml{\\@nsubseteqq}{\u2288}"); defineMacro("\\nsupseteqq", "\\html@mathml{\\@nsupseteqq}{\u2289}"); defineMacro("\\varsubsetneq", "\\html@mathml{\\@varsubsetneq}{⊊}"); defineMacro("\\varsubsetneqq", "\\html@mathml{\\@varsubsetneqq}{⫋}"); defineMacro("\\varsupsetneq", "\\html@mathml{\\@varsupsetneq}{⊋}"); defineMacro("\\varsupsetneqq", "\\html@mathml{\\@varsupsetneqq}{⫌}"); defineMacro("\\imath", "\\html@mathml{\\@imath}{\u0131}"); defineMacro("\\jmath", "\\html@mathml{\\@jmath}{\u0237}"); ////////////////////////////////////////////////////////////////////// // stmaryrd and semantic // The stmaryrd and semantic packages render the next four items by calling a // glyph. Those glyphs do not exist in the KaTeX fonts. Hence the macros. defineMacro("\\llbracket", "\\html@mathml{" + "\\mathopen{[\\mkern-3.2mu[}}" + "{\\mathopen{\\char`\u27e6}}"); defineMacro("\\rrbracket", "\\html@mathml{" + "\\mathclose{]\\mkern-3.2mu]}}" + "{\\mathclose{\\char`\u27e7}}"); defineMacro("\u27e6", "\\llbracket"); // blackboard bold [ defineMacro("\u27e7", "\\rrbracket"); // blackboard bold ] defineMacro("\\lBrace", "\\html@mathml{" + "\\mathopen{\\{\\mkern-3.2mu[}}" + "{\\mathopen{\\char`\u2983}}"); defineMacro("\\rBrace", "\\html@mathml{" + "\\mathclose{]\\mkern-3.2mu\\}}}" + "{\\mathclose{\\char`\u2984}}"); defineMacro("\u2983", "\\lBrace"); // blackboard bold { defineMacro("\u2984", "\\rBrace"); // blackboard bold } // TODO: Create variable sized versions of the last two items. I believe that // will require new font glyphs. // The stmaryrd function `\minuso` provides a "Plimsoll" symbol that // superimposes the characters \circ and \mathminus. Used in chemistry. defineMacro("\\minuso", "\\mathbin{\\html@mathml{" + "{\\mathrlap{\\mathchoice{\\kern{0.145em}}{\\kern{0.145em}}" + "{\\kern{0.1015em}}{\\kern{0.0725em}}\\circ}{-}}}" + "{\\char`⦵}}"); defineMacro("⦵", "\\minuso"); ////////////////////////////////////////////////////////////////////// // texvc.sty // The texvc package contains macros available in mediawiki pages. // We omit the functions deprecated at // https://en.wikipedia.org/wiki/Help:Displaying_a_formula#Deprecated_syntax // We also omit texvc's \O, which conflicts with \text{\O} defineMacro("\\darr", "\\downarrow"); defineMacro("\\dArr", "\\Downarrow"); defineMacro("\\Darr", "\\Downarrow"); defineMacro("\\lang", "\\langle"); defineMacro("\\rang", "\\rangle"); defineMacro("\\uarr", "\\uparrow"); defineMacro("\\uArr", "\\Uparrow"); defineMacro("\\Uarr", "\\Uparrow"); defineMacro("\\N", "\\mathbb{N}"); defineMacro("\\R", "\\mathbb{R}"); defineMacro("\\Z", "\\mathbb{Z}"); defineMacro("\\alef", "\\aleph"); defineMacro("\\alefsym", "\\aleph"); defineMacro("\\Alpha", "\\mathrm{A}"); defineMacro("\\Beta", "\\mathrm{B}"); defineMacro("\\bull", "\\bullet"); defineMacro("\\Chi", "\\mathrm{X}"); defineMacro("\\clubs", "\\clubsuit"); defineMacro("\\cnums", "\\mathbb{C}"); defineMacro("\\Complex", "\\mathbb{C}"); defineMacro("\\Dagger", "\\ddagger"); defineMacro("\\diamonds", "\\diamondsuit"); defineMacro("\\empty", "\\emptyset"); defineMacro("\\Epsilon", "\\mathrm{E}"); defineMacro("\\Eta", "\\mathrm{H}"); defineMacro("\\exist", "\\exists"); defineMacro("\\harr", "\\leftrightarrow"); defineMacro("\\hArr", "\\Leftrightarrow"); defineMacro("\\Harr", "\\Leftrightarrow"); defineMacro("\\hearts", "\\heartsuit"); defineMacro("\\image", "\\Im"); defineMacro("\\infin", "\\infty"); defineMacro("\\Iota", "\\mathrm{I}"); defineMacro("\\isin", "\\in"); defineMacro("\\Kappa", "\\mathrm{K}"); defineMacro("\\larr", "\\leftarrow"); defineMacro("\\lArr", "\\Leftarrow"); defineMacro("\\Larr", "\\Leftarrow"); defineMacro("\\lrarr", "\\leftrightarrow"); defineMacro("\\lrArr", "\\Leftrightarrow"); defineMacro("\\Lrarr", "\\Leftrightarrow"); defineMacro("\\Mu", "\\mathrm{M}"); defineMacro("\\natnums", "\\mathbb{N}"); defineMacro("\\Nu", "\\mathrm{N}"); defineMacro("\\Omicron", "\\mathrm{O}"); defineMacro("\\plusmn", "\\pm"); defineMacro("\\rarr", "\\rightarrow"); defineMacro("\\rArr", "\\Rightarrow"); defineMacro("\\Rarr", "\\Rightarrow"); defineMacro("\\real", "\\Re"); defineMacro("\\reals", "\\mathbb{R}"); defineMacro("\\Reals", "\\mathbb{R}"); defineMacro("\\Rho", "\\mathrm{P}"); defineMacro("\\sdot", "\\cdot"); defineMacro("\\sect", "\\S"); defineMacro("\\spades", "\\spadesuit"); defineMacro("\\sub", "\\subset"); defineMacro("\\sube", "\\subseteq"); defineMacro("\\supe", "\\supseteq"); defineMacro("\\Tau", "\\mathrm{T}"); defineMacro("\\thetasym", "\\vartheta"); // TODO: defineMacro("\\varcoppa", "\\\mbox{\\coppa}"); defineMacro("\\weierp", "\\wp"); defineMacro("\\Zeta", "\\mathrm{Z}"); ////////////////////////////////////////////////////////////////////// // statmath.sty // https://ctan.math.illinois.edu/macros/latex/contrib/statmath/statmath.pdf defineMacro("\\argmin", "\\DOTSB\\operatorname*{arg\\,min}"); defineMacro("\\argmax", "\\DOTSB\\operatorname*{arg\\,max}"); defineMacro("\\plim", "\\DOTSB\\mathop{\\operatorname{plim}}\\limits"); ////////////////////////////////////////////////////////////////////// // braket.sty // http://ctan.math.washington.edu/tex-archive/macros/latex/contrib/braket/braket.pdf defineMacro("\\bra", "\\mathinner{\\langle{#1}|}"); defineMacro("\\ket", "\\mathinner{|{#1}\\rangle}"); defineMacro("\\braket", "\\mathinner{\\langle{#1}\\rangle}"); defineMacro("\\Bra", "\\left\\langle#1\\right|"); defineMacro("\\Ket", "\\left|#1\\right\\rangle"); var braketHelper = one => context => { var left = context.consumeArg().tokens; var middle = context.consumeArg().tokens; var middleDouble = context.consumeArg().tokens; var right = context.consumeArg().tokens; var oldMiddle = context.macros.get("|"); var oldMiddleDouble = context.macros.get("\\|"); context.macros.beginGroup(); var midMacro = double => context => { if (one) { // Only modify the first instance of | or \| context.macros.set("|", oldMiddle); if (middleDouble.length) { context.macros.set("\\|", oldMiddleDouble); } } var doubled = double; if (!double && middleDouble.length) { // Mimic \@ifnextchar var nextToken = context.future(); if (nextToken.text === "|") { context.popToken(); doubled = true; } } return { tokens: doubled ? middleDouble : middle, numArgs: 0 }; }; context.macros.set("|", midMacro(false)); if (middleDouble.length) { context.macros.set("\\|", midMacro(true)); } var arg = context.consumeArg().tokens; var expanded = context.expandTokens([...right, ...arg, ...left // reversed ]); context.macros.endGroup(); return { tokens: expanded.reverse(), numArgs: 0 }; }; defineMacro("\\bra@ket", braketHelper(false)); defineMacro("\\bra@set", braketHelper(true)); defineMacro("\\Braket", "\\bra@ket{\\left\\langle}" + "{\\,\\middle\\vert\\,}{\\,\\middle\\vert\\,}{\\right\\rangle}"); defineMacro("\\Set", "\\bra@set{\\left\\{\\:}" + "{\\;\\middle\\vert\\;}{\\;\\middle\\Vert\\;}{\\:\\right\\}}"); defineMacro("\\set", "\\bra@set{\\{\\,}{\\mid}{}{\\,\\}}"); // has no support for special || or \| ////////////////////////////////////////////////////////////////////// // actuarialangle.dtx defineMacro("\\angln", "{\\angl n}"); // Custom Khan Academy colors, should be moved to an optional package defineMacro("\\blue", "\\textcolor{##6495ed}{#1}"); defineMacro("\\orange", "\\textcolor{##ffa500}{#1}"); defineMacro("\\pink", "\\textcolor{##ff00af}{#1}"); defineMacro("\\red", "\\textcolor{##df0030}{#1}"); defineMacro("\\green", "\\textcolor{##28ae7b}{#1}"); defineMacro("\\gray", "\\textcolor{gray}{#1}"); defineMacro("\\purple", "\\textcolor{##9d38bd}{#1}"); defineMacro("\\blueA", "\\textcolor{##ccfaff}{#1}"); defineMacro("\\blueB", "\\textcolor{##80f6ff}{#1}"); defineMacro("\\blueC", "\\textcolor{##63d9ea}{#1}"); defineMacro("\\blueD", "\\textcolor{##11accd}{#1}"); defineMacro("\\blueE", "\\textcolor{##0c7f99}{#1}"); defineMacro("\\tealA", "\\textcolor{##94fff5}{#1}"); defineMacro("\\tealB", "\\textcolor{##26edd5}{#1}"); defineMacro("\\tealC", "\\textcolor{##01d1c1}{#1}"); defineMacro("\\tealD", "\\textcolor{##01a995}{#1}"); defineMacro("\\tealE", "\\textcolor{##208170}{#1}"); defineMacro("\\greenA", "\\textcolor{##b6ffb0}{#1}"); defineMacro("\\greenB", "\\textcolor{##8af281}{#1}"); defineMacro("\\greenC", "\\textcolor{##74cf70}{#1}"); defineMacro("\\greenD", "\\textcolor{##1fab54}{#1}"); defineMacro("\\greenE", "\\textcolor{##0d923f}{#1}"); defineMacro("\\goldA", "\\textcolor{##ffd0a9}{#1}"); defineMacro("\\goldB", "\\textcolor{##ffbb71}{#1}"); defineMacro("\\goldC", "\\textcolor{##ff9c39}{#1}"); defineMacro("\\goldD", "\\textcolor{##e07d10}{#1}"); defineMacro("\\goldE", "\\textcolor{##a75a05}{#1}"); defineMacro("\\redA", "\\textcolor{##fca9a9}{#1}"); defineMacro("\\redB", "\\textcolor{##ff8482}{#1}"); defineMacro("\\redC", "\\textcolor{##f9685d}{#1}"); defineMacro("\\redD", "\\textcolor{##e84d39}{#1}"); defineMacro("\\redE", "\\textcolor{##bc2612}{#1}"); defineMacro("\\maroonA", "\\textcolor{##ffbde0}{#1}"); defineMacro("\\maroonB", "\\textcolor{##ff92c6}{#1}"); defineMacro("\\maroonC", "\\textcolor{##ed5fa6}{#1}"); defineMacro("\\maroonD", "\\textcolor{##ca337c}{#1}"); defineMacro("\\maroonE", "\\textcolor{##9e034e}{#1}"); defineMacro("\\purpleA", "\\textcolor{##ddd7ff}{#1}"); defineMacro("\\purpleB", "\\textcolor{##c6b9fc}{#1}"); defineMacro("\\purpleC", "\\textcolor{##aa87ff}{#1}"); defineMacro("\\purpleD", "\\textcolor{##7854ab}{#1}"); defineMacro("\\purpleE", "\\textcolor{##543b78}{#1}"); defineMacro("\\mintA", "\\textcolor{##f5f9e8}{#1}"); defineMacro("\\mintB", "\\textcolor{##edf2df}{#1}"); defineMacro("\\mintC", "\\textcolor{##e0e5cc}{#1}"); defineMacro("\\grayA", "\\textcolor{##f6f7f7}{#1}"); defineMacro("\\grayB", "\\textcolor{##f0f1f2}{#1}"); defineMacro("\\grayC", "\\textcolor{##e3e5e6}{#1}"); defineMacro("\\grayD", "\\textcolor{##d6d8da}{#1}"); defineMacro("\\grayE", "\\textcolor{##babec2}{#1}"); defineMacro("\\grayF", "\\textcolor{##888d93}{#1}"); defineMacro("\\grayG", "\\textcolor{##626569}{#1}"); defineMacro("\\grayH", "\\textcolor{##3b3e40}{#1}"); defineMacro("\\grayI", "\\textcolor{##21242c}{#1}"); defineMacro("\\kaBlue", "\\textcolor{##314453}{#1}"); defineMacro("\\kaGreen", "\\textcolor{##71B307}{#1}"); /** * This file contains the “gullet” where macros are expanded * until only non-macro tokens remain. */ // List of commands that act like macros but aren't defined as a macro, // function, or symbol. Used in `isDefined`. var implicitCommands = { "^": true, // Parser.js "_": true, // Parser.js "\\limits": true, // Parser.js "\\nolimits": true // Parser.js }; class MacroExpander { constructor(input, settings, mode) { this.settings = void 0; this.expansionCount = void 0; this.lexer = void 0; this.macros = void 0; this.stack = void 0; this.mode = void 0; this.settings = settings; this.expansionCount = 0; this.feed(input); // Make new global namespace this.macros = new Namespace(macros, settings.macros); this.mode = mode; this.stack = []; // contains tokens in REVERSE order } /** * Feed a new input string to the same MacroExpander * (with existing macros etc.). */ feed(input) { this.lexer = new Lexer(input, this.settings); } /** * Switches between "text" and "math" modes. */ switchMode(newMode) { this.mode = newMode; } /** * Start a new group nesting within all namespaces. */ beginGroup() { this.macros.beginGroup(); } /** * End current group nesting within all namespaces. */ endGroup() { this.macros.endGroup(); } /** * Ends all currently nested groups (if any), restoring values before the * groups began. Useful in case of an error in the middle of parsing. */ endGroups() { this.macros.endGroups(); } /** * Returns the topmost token on the stack, without expanding it. * Similar in behavior to TeX's `\futurelet`. */ future() { if (this.stack.length === 0) { this.pushToken(this.lexer.lex()); } return this.stack[this.stack.length - 1]; } /** * Remove and return the next unexpanded token. */ popToken() { this.future(); // ensure non-empty stack return this.stack.pop(); } /** * Add a given token to the token stack. In particular, this get be used * to put back a token returned from one of the other methods. */ pushToken(token) { this.stack.push(token); } /** * Append an array of tokens to the token stack. */ pushTokens(tokens) { this.stack.push(...tokens); } /** * Find an macro argument without expanding tokens and append the array of * tokens to the token stack. Uses Token as a container for the result. */ scanArgument(isOptional) { var start; var end; var tokens; if (isOptional) { this.consumeSpaces(); // \@ifnextchar gobbles any space following it if (this.future().text !== "[") { return null; } start = this.popToken(); // don't include [ in tokens ({ tokens, end } = this.consumeArg(["]"])); } else { ({ tokens, start, end } = this.consumeArg()); } // indicate the end of an argument this.pushToken(new Token("EOF", end.loc)); this.pushTokens(tokens); return start.range(end, ""); } /** * Consume all following space tokens, without expansion. */ consumeSpaces() { for (;;) { var token = this.future(); if (token.text === " ") { this.stack.pop(); } else { break; } } } /** * Consume an argument from the token stream, and return the resulting array * of tokens and start/end token. */ consumeArg(delims) { // The argument for a delimited parameter is the shortest (possibly // empty) sequence of tokens with properly nested {...} groups that is // followed ... by this particular list of non-parameter tokens. // The argument for an undelimited parameter is the next nonblank // token, unless that token is ‘{’, when the argument will be the // entire {...} group that follows. var tokens = []; var isDelimited = delims && delims.length > 0; if (!isDelimited) { // Ignore spaces between arguments. As the TeXbook says: // "After you have said ‘\def\row#1#2{...}’, you are allowed to // put spaces between the arguments (e.g., ‘\row x n’), because // TeX doesn’t use single spaces as undelimited arguments." this.consumeSpaces(); } var start = this.future(); var tok; var depth = 0; var match = 0; do { tok = this.popToken(); tokens.push(tok); if (tok.text === "{") { ++depth; } else if (tok.text === "}") { --depth; if (depth === -1) { throw new ParseError("Extra }", tok); } } else if (tok.text === "EOF") { throw new ParseError("Unexpected end of input in a macro argument" + ", expected '" + (delims && isDelimited ? delims[match] : "}") + "'", tok); } if (delims && isDelimited) { if ((depth === 0 || depth === 1 && delims[match] === "{") && tok.text === delims[match]) { ++match; if (match === delims.length) { // don't include delims in tokens tokens.splice(-match, match); break; } } else { match = 0; } } } while (depth !== 0 || isDelimited); // If the argument found ... has the form ‘{}’, // ... the outermost braces enclosing the argument are removed if (start.text === "{" && tokens[tokens.length - 1].text === "}") { tokens.pop(); tokens.shift(); } tokens.reverse(); // to fit in with stack order return { tokens, start, end: tok }; } /** * Consume the specified number of (delimited) arguments from the token * stream and return the resulting array of arguments. */ consumeArgs(numArgs, delimiters) { if (delimiters) { if (delimiters.length !== numArgs + 1) { throw new ParseError("The length of delimiters doesn't match the number of args!"); } var delims = delimiters[0]; for (var i = 0; i < delims.length; i++) { var tok = this.popToken(); if (delims[i] !== tok.text) { throw new ParseError("Use of the macro doesn't match its definition", tok); } } } var args = []; for (var _i = 0; _i < numArgs; _i++) { args.push(this.consumeArg(delimiters && delimiters[_i + 1]).tokens); } return args; } /** * Increment `expansionCount` by the specified amount. * Throw an error if it exceeds `maxExpand`. */ countExpansion(amount) { this.expansionCount += amount; if (this.expansionCount > this.settings.maxExpand) { throw new ParseError("Too many expansions: infinite loop or " + "need to increase maxExpand setting"); } } /** * Expand the next token only once if possible. * * If the token is expanded, the resulting tokens will be pushed onto * the stack in reverse order, and the number of such tokens will be * returned. This number might be zero or positive. * * If not, the return value is `false`, and the next token remains at the * top of the stack. * * In either case, the next token will be on the top of the stack, * or the stack will be empty (in case of empty expansion * and no other tokens). * * Used to implement `expandAfterFuture` and `expandNextToken`. * * If expandableOnly, only expandable tokens are expanded and * an undefined control sequence results in an error. */ expandOnce(expandableOnly) { var topToken = this.popToken(); var name = topToken.text; var expansion = !topToken.noexpand ? this._getExpansion(name) : null; if (expansion == null || expandableOnly && expansion.unexpandable) { if (expandableOnly && expansion == null && name[0] === "\\" && !this.isDefined(name)) { throw new ParseError("Undefined control sequence: " + name); } this.pushToken(topToken); return false; } this.countExpansion(1); var tokens = expansion.tokens; var args = this.consumeArgs(expansion.numArgs, expansion.delimiters); if (expansion.numArgs) { // paste arguments in place of the placeholders tokens = tokens.slice(); // make a shallow copy for (var i = tokens.length - 1; i >= 0; --i) { var tok = tokens[i]; if (tok.text === "#") { if (i === 0) { throw new ParseError("Incomplete placeholder at end of macro body", tok); } tok = tokens[--i]; // next token on stack if (tok.text === "#") { // ## → # tokens.splice(i + 1, 1); // drop first # } else if (/^[1-9]$/.test(tok.text)) { // replace the placeholder with the indicated argument tokens.splice(i, 2, ...args[+tok.text - 1]); } else { throw new ParseError("Not a valid argument number", tok); } } } } // Concatenate expansion onto top of stack. this.pushTokens(tokens); return tokens.length; } /** * Expand the next token only once (if possible), and return the resulting * top token on the stack (without removing anything from the stack). * Similar in behavior to TeX's `\expandafter\futurelet`. * Equivalent to expandOnce() followed by future(). */ expandAfterFuture() { this.expandOnce(); return this.future(); } /** * Recursively expand first token, then return first non-expandable token. */ expandNextToken() { for (;;) { if (this.expandOnce() === false) { // fully expanded var token = this.stack.pop(); // the token after \noexpand is interpreted as if its meaning // were ‘\relax’ if (token.treatAsRelax) { token.text = "\\relax"; } return token; } } // Flow unable to figure out that this pathway is impossible. // https://github.com/facebook/flow/issues/4808 throw new Error(); // eslint-disable-line no-unreachable } /** * Fully expand the given macro name and return the resulting list of * tokens, or return `undefined` if no such macro is defined. */ expandMacro(name) { return this.macros.has(name) ? this.expandTokens([new Token(name)]) : undefined; } /** * Fully expand the given token stream and return the resulting list of * tokens. Note that the input tokens are in reverse order, but the * output tokens are in forward order. */ expandTokens(tokens) { var output = []; var oldStackLength = this.stack.length; this.pushTokens(tokens); while (this.stack.length > oldStackLength) { // Expand only expandable tokens if (this.expandOnce(true) === false) { // fully expanded var token = this.stack.pop(); if (token.treatAsRelax) { // the expansion of \noexpand is the token itself token.noexpand = false; token.treatAsRelax = false; } output.push(token); } } // Count all of these tokens as additional expansions, to prevent // exponential blowup from linearly many \edef's. this.countExpansion(output.length); return output; } /** * Fully expand the given macro name and return the result as a string, * or return `undefined` if no such macro is defined. */ expandMacroAsText(name) { var tokens = this.expandMacro(name); if (tokens) { return tokens.map(token => token.text).join(""); } else { return tokens; } } /** * Returns the expanded macro as a reversed array of tokens and a macro * argument count. Or returns `null` if no such macro. */ _getExpansion(name) { var definition = this.macros.get(name); if (definition == null) { // mainly checking for undefined here return definition; } // If a single character has an associated catcode other than 13 // (active character), then don't expand it. if (name.length === 1) { var catcode = this.lexer.catcodes[name]; if (catcode != null && catcode !== 13) { return; } } var expansion = typeof definition === "function" ? definition(this) : definition; if (typeof expansion === "string") { var numArgs = 0; if (expansion.indexOf("#") !== -1) { var stripped = expansion.replace(/##/g, ""); while (stripped.indexOf("#" + (numArgs + 1)) !== -1) { ++numArgs; } } var bodyLexer = new Lexer(expansion, this.settings); var tokens = []; var tok = bodyLexer.lex(); while (tok.text !== "EOF") { tokens.push(tok); tok = bodyLexer.lex(); } tokens.reverse(); // to fit in with stack using push and pop var expanded = { tokens, numArgs }; return expanded; } return expansion; } /** * Determine whether a command is currently "defined" (has some * functionality), meaning that it's a macro (in the current group), * a function, a symbol, or one of the special commands listed in * `implicitCommands`. */ isDefined(name) { return this.macros.has(name) || functions.hasOwnProperty(name) || symbols.math.hasOwnProperty(name) || symbols.text.hasOwnProperty(name) || implicitCommands.hasOwnProperty(name); } /** * Determine whether a command is expandable. */ isExpandable(name) { var macro = this.macros.get(name); return macro != null ? typeof macro === "string" || typeof macro === "function" || !macro.unexpandable : functions.hasOwnProperty(name) && !functions[name].primitive; } } // Helpers for Parser.js handling of Unicode (sub|super)script characters. var unicodeSubRegEx = /^[₊₋₌₍₎₀₁₂₃₄₅₆₇₈₉ₐₑₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓᵦᵧᵨᵩᵪ]/; var uSubsAndSups = Object.freeze({ '₊': '+', '₋': '-', '₌': '=', '₍': '(', '₎': ')', '₀': '0', '₁': '1', '₂': '2', '₃': '3', '₄': '4', '₅': '5', '₆': '6', '₇': '7', '₈': '8', '₉': '9', '\u2090': 'a', '\u2091': 'e', '\u2095': 'h', '\u1D62': 'i', '\u2C7C': 'j', '\u2096': 'k', '\u2097': 'l', '\u2098': 'm', '\u2099': 'n', '\u2092': 'o', '\u209A': 'p', '\u1D63': 'r', '\u209B': 's', '\u209C': 't', '\u1D64': 'u', '\u1D65': 'v', '\u2093': 'x', '\u1D66': 'β', '\u1D67': 'γ', '\u1D68': 'ρ', '\u1D69': '\u03d5', '\u1D6A': 'χ', '⁺': '+', '⁻': '-', '⁼': '=', '⁽': '(', '⁾': ')', '⁰': '0', '¹': '1', '²': '2', '³': '3', '⁴': '4', '⁵': '5', '⁶': '6', '⁷': '7', '⁸': '8', '⁹': '9', '\u1D2C': 'A', '\u1D2E': 'B', '\u1D30': 'D', '\u1D31': 'E', '\u1D33': 'G', '\u1D34': 'H', '\u1D35': 'I', '\u1D36': 'J', '\u1D37': 'K', '\u1D38': 'L', '\u1D39': 'M', '\u1D3A': 'N', '\u1D3C': 'O', '\u1D3E': 'P', '\u1D3F': 'R', '\u1D40': 'T', '\u1D41': 'U', '\u2C7D': 'V', '\u1D42': 'W', '\u1D43': 'a', '\u1D47': 'b', '\u1D9C': 'c', '\u1D48': 'd', '\u1D49': 'e', '\u1DA0': 'f', '\u1D4D': 'g', '\u02B0': 'h', '\u2071': 'i', '\u02B2': 'j', '\u1D4F': 'k', '\u02E1': 'l', '\u1D50': 'm', '\u207F': 'n', '\u1D52': 'o', '\u1D56': 'p', '\u02B3': 'r', '\u02E2': 's', '\u1D57': 't', '\u1D58': 'u', '\u1D5B': 'v', '\u02B7': 'w', '\u02E3': 'x', '\u02B8': 'y', '\u1DBB': 'z', '\u1D5D': 'β', '\u1D5E': 'γ', '\u1D5F': 'δ', '\u1D60': '\u03d5', '\u1D61': 'χ', '\u1DBF': 'θ' }); /* eslint no-constant-condition:0 */ var unicodeAccents = { "́": { "text": "\\'", "math": "\\acute" }, "̀": { "text": "\\`", "math": "\\grave" }, "̈": { "text": "\\\"", "math": "\\ddot" }, "̃": { "text": "\\~", "math": "\\tilde" }, "̄": { "text": "\\=", "math": "\\bar" }, "̆": { "text": "\\u", "math": "\\breve" }, "̌": { "text": "\\v", "math": "\\check" }, "̂": { "text": "\\^", "math": "\\hat" }, "̇": { "text": "\\.", "math": "\\dot" }, "̊": { "text": "\\r", "math": "\\mathring" }, "̋": { "text": "\\H" }, "̧": { "text": "\\c" } }; var unicodeSymbols = { "á": "á", "à": "à", "ä": "ä", "ǟ": "ǟ", "ã": "ã", "ā": "ā", "ă": "ă", "ắ": "ắ", "ằ": "ằ", "ẵ": "ẵ", "ǎ": "ǎ", "â": "â", "ấ": "ấ", "ầ": "ầ", "ẫ": "ẫ", "ȧ": "ȧ", "ǡ": "ǡ", "å": "å", "ǻ": "ǻ", "ḃ": "ḃ", "ć": "ć", "ḉ": "ḉ", "č": "č", "ĉ": "ĉ", "ċ": "ċ", "ç": "ç", "ď": "ď", "ḋ": "ḋ", "ḑ": "ḑ", "é": "é", "è": "è", "ë": "ë", "ẽ": "ẽ", "ē": "ē", "ḗ": "ḗ", "ḕ": "ḕ", "ĕ": "ĕ", "ḝ": "ḝ", "ě": "ě", "ê": "ê", "ế": "ế", "ề": "ề", "ễ": "ễ", "ė": "ė", "ȩ": "ȩ", "ḟ": "ḟ", "ǵ": "ǵ", "ḡ": "ḡ", "ğ": "ğ", "ǧ": "ǧ", "ĝ": "ĝ", "ġ": "ġ", "ģ": "ģ", "ḧ": "ḧ", "ȟ": "ȟ", "ĥ": "ĥ", "ḣ": "ḣ", "ḩ": "ḩ", "í": "í", "ì": "ì", "ï": "ï", "ḯ": "ḯ", "ĩ": "ĩ", "ī": "ī", "ĭ": "ĭ", "ǐ": "ǐ", "î": "î", "ǰ": "ǰ", "ĵ": "ĵ", "ḱ": "ḱ", "ǩ": "ǩ", "ķ": "ķ", "ĺ": "ĺ", "ľ": "ľ", "ļ": "ļ", "ḿ": "ḿ", "ṁ": "ṁ", "ń": "ń", "ǹ": "ǹ", "ñ": "ñ", "ň": "ň", "ṅ": "ṅ", "ņ": "ņ", "ó": "ó", "ò": "ò", "ö": "ö", "ȫ": "ȫ", "õ": "õ", "ṍ": "ṍ", "ṏ": "ṏ", "ȭ": "ȭ", "ō": "ō", "ṓ": "ṓ", "ṑ": "ṑ", "ŏ": "ŏ", "ǒ": "ǒ", "ô": "ô", "ố": "ố", "ồ": "ồ", "ỗ": "ỗ", "ȯ": "ȯ", "ȱ": "ȱ", "ő": "ő", "ṕ": "ṕ", "ṗ": "ṗ", "ŕ": "ŕ", "ř": "ř", "ṙ": "ṙ", "ŗ": "ŗ", "ś": "ś", "ṥ": "ṥ", "š": "š", "ṧ": "ṧ", "ŝ": "ŝ", "ṡ": "ṡ", "ş": "ş", "ẗ": "ẗ", "ť": "ť", "ṫ": "ṫ", "ţ": "ţ", "ú": "ú", "ù": "ù", "ü": "ü", "ǘ": "ǘ", "ǜ": "ǜ", "ǖ": "ǖ", "ǚ": "ǚ", "ũ": "ũ", "ṹ": "ṹ", "ū": "ū", "ṻ": "ṻ", "ŭ": "ŭ", "ǔ": "ǔ", "û": "û", "ů": "ů", "ű": "ű", "ṽ": "ṽ", "ẃ": "ẃ", "ẁ": "ẁ", "ẅ": "ẅ", "ŵ": "ŵ", "ẇ": "ẇ", "ẘ": "ẘ", "ẍ": "ẍ", "ẋ": "ẋ", "ý": "ý", "ỳ": "ỳ", "ÿ": "ÿ", "ỹ": "ỹ", "ȳ": "ȳ", "ŷ": "ŷ", "ẏ": "ẏ", "ẙ": "ẙ", "ź": "ź", "ž": "ž", "ẑ": "ẑ", "ż": "ż", "Á": "Á", "À": "À", "Ä": "Ä", "Ǟ": "Ǟ", "Ã": "Ã", "Ā": "Ā", "Ă": "Ă", "Ắ": "Ắ", "Ằ": "Ằ", "Ẵ": "Ẵ", "Ǎ": "Ǎ", "Â": "Â", "Ấ": "Ấ", "Ầ": "Ầ", "Ẫ": "Ẫ", "Ȧ": "Ȧ", "Ǡ": "Ǡ", "Å": "Å", "Ǻ": "Ǻ", "Ḃ": "Ḃ", "Ć": "Ć", "Ḉ": "Ḉ", "Č": "Č", "Ĉ": "Ĉ", "Ċ": "Ċ", "Ç": "Ç", "Ď": "Ď", "Ḋ": "Ḋ", "Ḑ": "Ḑ", "É": "É", "È": "È", "Ë": "Ë", "Ẽ": "Ẽ", "Ē": "Ē", "Ḗ": "Ḗ", "Ḕ": "Ḕ", "Ĕ": "Ĕ", "Ḝ": "Ḝ", "Ě": "Ě", "Ê": "Ê", "Ế": "Ế", "Ề": "Ề", "Ễ": "Ễ", "Ė": "Ė", "Ȩ": "Ȩ", "Ḟ": "Ḟ", "Ǵ": "Ǵ", "Ḡ": "Ḡ", "Ğ": "Ğ", "Ǧ": "Ǧ", "Ĝ": "Ĝ", "Ġ": "Ġ", "Ģ": "Ģ", "Ḧ": "Ḧ", "Ȟ": "Ȟ", "Ĥ": "Ĥ", "Ḣ": "Ḣ", "Ḩ": "Ḩ", "Í": "Í", "Ì": "Ì", "Ï": "Ï", "Ḯ": "Ḯ", "Ĩ": "Ĩ", "Ī": "Ī", "Ĭ": "Ĭ", "Ǐ": "Ǐ", "Î": "Î", "İ": "İ", "Ĵ": "Ĵ", "Ḱ": "Ḱ", "Ǩ": "Ǩ", "Ķ": "Ķ", "Ĺ": "Ĺ", "Ľ": "Ľ", "Ļ": "Ļ", "Ḿ": "Ḿ", "Ṁ": "Ṁ", "Ń": "Ń", "Ǹ": "Ǹ", "Ñ": "Ñ", "Ň": "Ň", "Ṅ": "Ṅ", "Ņ": "Ņ", "Ó": "Ó", "Ò": "Ò", "Ö": "Ö", "Ȫ": "Ȫ", "Õ": "Õ", "Ṍ": "Ṍ", "Ṏ": "Ṏ", "Ȭ": "Ȭ", "Ō": "Ō", "Ṓ": "Ṓ", "Ṑ": "Ṑ", "Ŏ": "Ŏ", "Ǒ": "Ǒ", "Ô": "Ô", "Ố": "Ố", "Ồ": "Ồ", "Ỗ": "Ỗ", "Ȯ": "Ȯ", "Ȱ": "Ȱ", "Ő": "Ő", "Ṕ": "Ṕ", "Ṗ": "Ṗ", "Ŕ": "Ŕ", "Ř": "Ř", "Ṙ": "Ṙ", "Ŗ": "Ŗ", "Ś": "Ś", "Ṥ": "Ṥ", "Š": "Š", "Ṧ": "Ṧ", "Ŝ": "Ŝ", "Ṡ": "Ṡ", "Ş": "Ş", "Ť": "Ť", "Ṫ": "Ṫ", "Ţ": "Ţ", "Ú": "Ú", "Ù": "Ù", "Ü": "Ü", "Ǘ": "Ǘ", "Ǜ": "Ǜ", "Ǖ": "Ǖ", "Ǚ": "Ǚ", "Ũ": "Ũ", "Ṹ": "Ṹ", "Ū": "Ū", "Ṻ": "Ṻ", "Ŭ": "Ŭ", "Ǔ": "Ǔ", "Û": "Û", "Ů": "Ů", "Ű": "Ű", "Ṽ": "Ṽ", "Ẃ": "Ẃ", "Ẁ": "Ẁ", "Ẅ": "Ẅ", "Ŵ": "Ŵ", "Ẇ": "Ẇ", "Ẍ": "Ẍ", "Ẋ": "Ẋ", "Ý": "Ý", "Ỳ": "Ỳ", "Ÿ": "Ÿ", "Ỹ": "Ỹ", "Ȳ": "Ȳ", "Ŷ": "Ŷ", "Ẏ": "Ẏ", "Ź": "Ź", "Ž": "Ž", "Ẑ": "Ẑ", "Ż": "Ż", "ά": "ά", "ὰ": "ὰ", "ᾱ": "ᾱ", "ᾰ": "ᾰ", "έ": "έ", "ὲ": "ὲ", "ή": "ή", "ὴ": "ὴ", "ί": "ί", "ὶ": "ὶ", "ϊ": "ϊ", "ΐ": "ΐ", "ῒ": "ῒ", "ῑ": "ῑ", "ῐ": "ῐ", "ό": "ό", "ὸ": "ὸ", "ύ": "ύ", "ὺ": "ὺ", "ϋ": "ϋ", "ΰ": "ΰ", "ῢ": "ῢ", "ῡ": "ῡ", "ῠ": "ῠ", "ώ": "ώ", "ὼ": "ὼ", "Ύ": "Ύ", "Ὺ": "Ὺ", "Ϋ": "Ϋ", "Ῡ": "Ῡ", "Ῠ": "Ῠ", "Ώ": "Ώ", "Ὼ": "Ὼ" }; /** * This file contains the parser used to parse out a TeX expression from the * input. Since TeX isn't context-free, standard parsers don't work particularly * well. * * The strategy of this parser is as such: * * The main functions (the `.parse...` ones) take a position in the current * parse string to parse tokens from. The lexer (found in Lexer.js, stored at * this.gullet.lexer) also supports pulling out tokens at arbitrary places. When * individual tokens are needed at a position, the lexer is called to pull out a * token, which is then used. * * The parser has a property called "mode" indicating the mode that * the parser is currently in. Currently it has to be one of "math" or * "text", which denotes whether the current environment is a math-y * one or a text-y one (e.g. inside \text). Currently, this serves to * limit the functions which can be used in text mode. * * The main functions then return an object which contains the useful data that * was parsed at its given point, and a new position at the end of the parsed * data. The main functions can call each other and continue the parsing by * using the returned position as a new starting point. * * There are also extra `.handle...` functions, which pull out some reused * functionality into self-contained functions. * * The functions return ParseNodes. */ class Parser { constructor(input, settings) { this.mode = void 0; this.gullet = void 0; this.settings = void 0; this.leftrightDepth = void 0; this.nextToken = void 0; // Start in math mode this.mode = "math"; // Create a new macro expander (gullet) and (indirectly via that) also a // new lexer (mouth) for this parser (stomach, in the language of TeX) this.gullet = new MacroExpander(input, settings, this.mode); // Store the settings for use in parsing this.settings = settings; // Count leftright depth (for \middle errors) this.leftrightDepth = 0; } /** * Checks a result to make sure it has the right type, and throws an * appropriate error otherwise. */ expect(text, consume) { if (consume === void 0) { consume = true; } if (this.fetch().text !== text) { throw new ParseError("Expected '" + text + "', got '" + this.fetch().text + "'", this.fetch()); } if (consume) { this.consume(); } } /** * Discards the current lookahead token, considering it consumed. */ consume() { this.nextToken = null; } /** * Return the current lookahead token, or if there isn't one (at the * beginning, or if the previous lookahead token was consume()d), * fetch the next token as the new lookahead token and return it. */ fetch() { if (this.nextToken == null) { this.nextToken = this.gullet.expandNextToken(); } return this.nextToken; } /** * Switches between "text" and "math" modes. */ switchMode(newMode) { this.mode = newMode; this.gullet.switchMode(newMode); } /** * Main parsing function, which parses an entire input. */ parse() { if (!this.settings.globalGroup) { // Create a group namespace for the math expression. // (LaTeX creates a new group for every $...$, $$...$$, \[...\].) this.gullet.beginGroup(); } // Use old \color behavior (same as LaTeX's \textcolor) if requested. // We do this within the group for the math expression, so it doesn't // pollute settings.macros. if (this.settings.colorIsTextColor) { this.gullet.macros.set("\\color", "\\textcolor"); } try { // Try to parse the input var parse = this.parseExpression(false); // If we succeeded, make sure there's an EOF at the end this.expect("EOF"); // End the group namespace for the expression if (!this.settings.globalGroup) { this.gullet.endGroup(); } return parse; // Close any leftover groups in case of a parse error. } finally { this.gullet.endGroups(); } } /** * Fully parse a separate sequence of tokens as a separate job. * Tokens should be specified in reverse order, as in a MacroDefinition. */ subparse(tokens) { // Save the next token from the current job. var oldToken = this.nextToken; this.consume(); // Run the new job, terminating it with an excess '}' this.gullet.pushToken(new Token("}")); this.gullet.pushTokens(tokens); var parse = this.parseExpression(false); this.expect("}"); // Restore the next token from the current job. this.nextToken = oldToken; return parse; } /** * Parses an "expression", which is a list of atoms. * * `breakOnInfix`: Should the parsing stop when we hit infix nodes? This * happens when functions have higher precedence han infix * nodes in implicit parses. * * `breakOnTokenText`: The text of the token that the expression should end * with, or `null` if something else should end the * expression. */ parseExpression(breakOnInfix, breakOnTokenText) { var body = []; // Keep adding atoms to the body until we can't parse any more atoms (either // we reached the end, a }, or a \right) while (true) { // Ignore spaces in math mode if (this.mode === "math") { this.consumeSpaces(); } var lex = this.fetch(); if (Parser.endOfExpression.indexOf(lex.text) !== -1) { break; } if (breakOnTokenText && lex.text === breakOnTokenText) { break; } if (breakOnInfix && functions[lex.text] && functions[lex.text].infix) { break; } var atom = this.parseAtom(breakOnTokenText); if (!atom) { break; } else if (atom.type === "internal") { // Internal nodes do not appear in parse tree continue; } body.push(atom); } if (this.mode === "text") { this.formLigatures(body); } return this.handleInfixNodes(body); } /** * Rewrites infix operators such as \over with corresponding commands such * as \frac. * * There can only be one infix operator per group. If there's more than one * then the expression is ambiguous. This can be resolved by adding {}. */ handleInfixNodes(body) { var overIndex = -1; var funcName; for (var i = 0; i < body.length; i++) { if (body[i].type === "infix") { if (overIndex !== -1) { throw new ParseError("only one infix operator per group", body[i].token); } overIndex = i; funcName = body[i].replaceWith; } } if (overIndex !== -1 && funcName) { var numerNode; var denomNode; var numerBody = body.slice(0, overIndex); var denomBody = body.slice(overIndex + 1); if (numerBody.length === 1 && numerBody[0].type === "ordgroup") { numerNode = numerBody[0]; } else { numerNode = { type: "ordgroup", mode: this.mode, body: numerBody }; } if (denomBody.length === 1 && denomBody[0].type === "ordgroup") { denomNode = denomBody[0]; } else { denomNode = { type: "ordgroup", mode: this.mode, body: denomBody }; } var node; if (funcName === "\\\\abovefrac") { node = this.callFunction(funcName, [numerNode, body[overIndex], denomNode], []); } else { node = this.callFunction(funcName, [numerNode, denomNode], []); } return [node]; } else { return body; } } /** * Handle a subscript or superscript with nice errors. */ handleSupSubscript(name // For error reporting. ) { var symbolToken = this.fetch(); var symbol = symbolToken.text; this.consume(); this.consumeSpaces(); // ignore spaces before sup/subscript argument // Skip over allowed internal nodes such as \relax var group; do { var _group; group = this.parseGroup(name); } while (((_group = group) == null ? void 0 : _group.type) === "internal"); if (!group) { throw new ParseError("Expected group after '" + symbol + "'", symbolToken); } return group; } /** * Converts the textual input of an unsupported command into a text node * contained within a color node whose color is determined by errorColor */ formatUnsupportedCmd(text) { var textordArray = []; for (var i = 0; i < text.length; i++) { textordArray.push({ type: "textord", mode: "text", text: text[i] }); } var textNode = { type: "text", mode: this.mode, body: textordArray }; var colorNode = { type: "color", mode: this.mode, color: this.settings.errorColor, body: [textNode] }; return colorNode; } /** * Parses a group with optional super/subscripts. */ parseAtom(breakOnTokenText) { // The body of an atom is an implicit group, so that things like // \left(x\right)^2 work correctly. var base = this.parseGroup("atom", breakOnTokenText); // Internal nodes (e.g. \relax) cannot support super/subscripts. // Instead we will pick up super/subscripts with blank base next round. if ((base == null ? void 0 : base.type) === "internal") { return base; } // In text mode, we don't have superscripts or subscripts if (this.mode === "text") { return base; } // Note that base may be empty (i.e. null) at this point. var superscript; var subscript; while (true) { // Guaranteed in math mode, so eat any spaces first. this.consumeSpaces(); // Lex the first token var lex = this.fetch(); if (lex.text === "\\limits" || lex.text === "\\nolimits") { // We got a limit control if (base && base.type === "op") { var limits = lex.text === "\\limits"; base.limits = limits; base.alwaysHandleSupSub = true; } else if (base && base.type === "operatorname") { if (base.alwaysHandleSupSub) { base.limits = lex.text === "\\limits"; } } else { throw new ParseError("Limit controls must follow a math operator", lex); } this.consume(); } else if (lex.text === "^") { // We got a superscript start if (superscript) { throw new ParseError("Double superscript", lex); } superscript = this.handleSupSubscript("superscript"); } else if (lex.text === "_") { // We got a subscript start if (subscript) { throw new ParseError("Double subscript", lex); } subscript = this.handleSupSubscript("subscript"); } else if (lex.text === "'") { // We got a prime if (superscript) { throw new ParseError("Double superscript", lex); } var prime = { type: "textord", mode: this.mode, text: "\\prime" }; // Many primes can be grouped together, so we handle this here var primes = [prime]; this.consume(); // Keep lexing tokens until we get something that's not a prime while (this.fetch().text === "'") { // For each one, add another prime to the list primes.push(prime); this.consume(); } // If there's a superscript following the primes, combine that // superscript in with the primes. if (this.fetch().text === "^") { primes.push(this.handleSupSubscript("superscript")); } // Put everything into an ordgroup as the superscript superscript = { type: "ordgroup", mode: this.mode, body: primes }; } else if (uSubsAndSups[lex.text]) { // A Unicode subscript or superscript character. // We treat these similarly to the unicode-math package. // So we render a string of Unicode (sub|super)scripts the // same as a (sub|super)script of regular characters. var isSub = unicodeSubRegEx.test(lex.text); var subsupTokens = []; subsupTokens.push(new Token(uSubsAndSups[lex.text])); this.consume(); // Continue fetching tokens to fill out the string. while (true) { var token = this.fetch().text; if (!uSubsAndSups[token]) { break; } if (unicodeSubRegEx.test(token) !== isSub) { break; } subsupTokens.unshift(new Token(uSubsAndSups[token])); this.consume(); } // Now create a (sub|super)script. var body = this.subparse(subsupTokens); if (isSub) { subscript = { type: "ordgroup", mode: "math", body }; } else { superscript = { type: "ordgroup", mode: "math", body }; } } else { // If it wasn't ^, _, or ', stop parsing super/subscripts break; } } // Base must be set if superscript or subscript are set per logic above, // but need to check here for type check to pass. if (superscript || subscript) { // If we got either a superscript or subscript, create a supsub return { type: "supsub", mode: this.mode, base: base, sup: superscript, sub: subscript }; } else { // Otherwise return the original body return base; } } /** * Parses an entire function, including its base and all of its arguments. */ parseFunction(breakOnTokenText, name // For determining its context ) { var token = this.fetch(); var func = token.text; var funcData = functions[func]; if (!funcData) { return null; } this.consume(); // consume command token if (name && name !== "atom" && !funcData.allowedInArgument) { throw new ParseError("Got function '" + func + "' with no arguments" + (name ? " as " + name : ""), token); } else if (this.mode === "text" && !funcData.allowedInText) { throw new ParseError("Can't use function '" + func + "' in text mode", token); } else if (this.mode === "math" && funcData.allowedInMath === false) { throw new ParseError("Can't use function '" + func + "' in math mode", token); } var { args, optArgs } = this.parseArguments(func, funcData); return this.callFunction(func, args, optArgs, token, breakOnTokenText); } /** * Call a function handler with a suitable context and arguments. */ callFunction(name, args, optArgs, token, breakOnTokenText) { var context = { funcName: name, parser: this, token, breakOnTokenText }; var func = functions[name]; if (func && func.handler) { return func.handler(context, args, optArgs); } else { throw new ParseError("No function handler for " + name); } } /** * Parses the arguments of a function or environment */ parseArguments(func, // Should look like "\name" or "\begin{name}". funcData) { var totalArgs = funcData.numArgs + funcData.numOptionalArgs; if (totalArgs === 0) { return { args: [], optArgs: [] }; } var args = []; var optArgs = []; for (var i = 0; i < totalArgs; i++) { var argType = funcData.argTypes && funcData.argTypes[i]; var isOptional = i < funcData.numOptionalArgs; if (funcData.primitive && argType == null || // \sqrt expands into primitive if optional argument doesn't exist funcData.type === "sqrt" && i === 1 && optArgs[0] == null) { argType = "primitive"; } var arg = this.parseGroupOfType("argument to '" + func + "'", argType, isOptional); if (isOptional) { optArgs.push(arg); } else if (arg != null) { args.push(arg); } else { // should be unreachable throw new ParseError("Null argument, please report this as a bug"); } } return { args, optArgs }; } /** * Parses a group when the mode is changing. */ parseGroupOfType(name, type, optional) { switch (type) { case "color": return this.parseColorGroup(optional); case "size": return this.parseSizeGroup(optional); case "url": return this.parseUrlGroup(optional); case "math": case "text": return this.parseArgumentGroup(optional, type); case "hbox": { // hbox argument type wraps the argument in the equivalent of // \hbox, which is like \text but switching to \textstyle size. var group = this.parseArgumentGroup(optional, "text"); return group != null ? { type: "styling", mode: group.mode, body: [group], style: "text" // simulate \textstyle } : null; } case "raw": { var token = this.parseStringGroup("raw", optional); return token != null ? { type: "raw", mode: "text", string: token.text } : null; } case "primitive": { if (optional) { throw new ParseError("A primitive argument cannot be optional"); } var _group2 = this.parseGroup(name); if (_group2 == null) { throw new ParseError("Expected group as " + name, this.fetch()); } return _group2; } case "original": case null: case undefined: return this.parseArgumentGroup(optional); default: throw new ParseError("Unknown group type as " + name, this.fetch()); } } /** * Discard any space tokens, fetching the next non-space token. */ consumeSpaces() { while (this.fetch().text === " ") { this.consume(); } } /** * Parses a group, essentially returning the string formed by the * brace-enclosed tokens plus some position information. */ parseStringGroup(modeName, // Used to describe the mode in error messages. optional) { var argToken = this.gullet.scanArgument(optional); if (argToken == null) { return null; } var str = ""; var nextToken; while ((nextToken = this.fetch()).text !== "EOF") { str += nextToken.text; this.consume(); } this.consume(); // consume the end of the argument argToken.text = str; return argToken; } /** * Parses a regex-delimited group: the largest sequence of tokens * whose concatenated strings match `regex`. Returns the string * formed by the tokens plus some position information. */ parseRegexGroup(regex, modeName // Used to describe the mode in error messages. ) { var firstToken = this.fetch(); var lastToken = firstToken; var str = ""; var nextToken; while ((nextToken = this.fetch()).text !== "EOF" && regex.test(str + nextToken.text)) { lastToken = nextToken; str += lastToken.text; this.consume(); } if (str === "") { throw new ParseError("Invalid " + modeName + ": '" + firstToken.text + "'", firstToken); } return firstToken.range(lastToken, str); } /** * Parses a color description. */ parseColorGroup(optional) { var res = this.parseStringGroup("color", optional); if (res == null) { return null; } var match = /^(#[a-f0-9]{3}|#?[a-f0-9]{6}|[a-z]+)$/i.exec(res.text); if (!match) { throw new ParseError("Invalid color: '" + res.text + "'", res); } var color = match[0]; if (/^[0-9a-f]{6}$/i.test(color)) { // We allow a 6-digit HTML color spec without a leading "#". // This follows the xcolor package's HTML color model. // Predefined color names are all missed by this RegEx pattern. color = "#" + color; } return { type: "color-token", mode: this.mode, color }; } /** * Parses a size specification, consisting of magnitude and unit. */ parseSizeGroup(optional) { var res; var isBlank = false; // don't expand before parseStringGroup this.gullet.consumeSpaces(); if (!optional && this.gullet.future().text !== "{") { res = this.parseRegexGroup(/^[-+]? *(?:$|\d+|\d+\.\d*|\.\d*) *[a-z]{0,2} *$/, "size"); } else { res = this.parseStringGroup("size", optional); } if (!res) { return null; } if (!optional && res.text.length === 0) { // Because we've tested for what is !optional, this block won't // affect \kern, \hspace, etc. It will capture the mandatory arguments // to \genfrac and \above. res.text = "0pt"; // Enable \above{} isBlank = true; // This is here specifically for \genfrac } var match = /([-+]?) *(\d+(?:\.\d*)?|\.\d+) *([a-z]{2})/.exec(res.text); if (!match) { throw new ParseError("Invalid size: '" + res.text + "'", res); } var data = { number: +(match[1] + match[2]), // sign + magnitude, cast to number unit: match[3] }; if (!validUnit(data)) { throw new ParseError("Invalid unit: '" + data.unit + "'", res); } return { type: "size", mode: this.mode, value: data, isBlank }; } /** * Parses an URL, checking escaped letters and allowed protocols, * and setting the catcode of % as an active character (as in \hyperref). */ parseUrlGroup(optional) { this.gullet.lexer.setCatcode("%", 13); // active character this.gullet.lexer.setCatcode("~", 12); // other character var res = this.parseStringGroup("url", optional); this.gullet.lexer.setCatcode("%", 14); // comment character this.gullet.lexer.setCatcode("~", 13); // active character if (res == null) { return null; } // hyperref package allows backslashes alone in href, but doesn't // generate valid links in such cases; we interpret this as // "undefined" behaviour, and keep them as-is. Some browser will // replace backslashes with forward slashes. var url = res.text.replace(/\\([#$%&~_^{}])/g, '$1'); return { type: "url", mode: this.mode, url }; } /** * Parses an argument with the mode specified. */ parseArgumentGroup(optional, mode) { var argToken = this.gullet.scanArgument(optional); if (argToken == null) { return null; } var outerMode = this.mode; if (mode) { // Switch to specified mode this.switchMode(mode); } this.gullet.beginGroup(); var expression = this.parseExpression(false, "EOF"); // TODO: find an alternative way to denote the end this.expect("EOF"); // expect the end of the argument this.gullet.endGroup(); var result = { type: "ordgroup", mode: this.mode, loc: argToken.loc, body: expression }; if (mode) { // Switch mode back this.switchMode(outerMode); } return result; } /** * Parses an ordinary group, which is either a single nucleus (like "x") * or an expression in braces (like "{x+y}") or an implicit group, a group * that starts at the current position, and ends right before a higher explicit * group ends, or at EOF. */ parseGroup(name, // For error reporting. breakOnTokenText) { var firstToken = this.fetch(); var text = firstToken.text; var result; // Try to parse an open brace or \begingroup if (text === "{" || text === "\\begingroup") { this.consume(); var groupEnd = text === "{" ? "}" : "\\endgroup"; this.gullet.beginGroup(); // If we get a brace, parse an expression var expression = this.parseExpression(false, groupEnd); var lastToken = this.fetch(); this.expect(groupEnd); // Check that we got a matching closing brace this.gullet.endGroup(); result = { type: "ordgroup", mode: this.mode, loc: SourceLocation.range(firstToken, lastToken), body: expression, // A group formed by \begingroup...\endgroup is a semi-simple group // which doesn't affect spacing in math mode, i.e., is transparent. // https://tex.stackexchange.com/questions/1930/when-should-one- // use-begingroup-instead-of-bgroup semisimple: text === "\\begingroup" || undefined }; } else { // If there exists a function with this name, parse the function. // Otherwise, just return a nucleus result = this.parseFunction(breakOnTokenText, name) || this.parseSymbol(); if (result == null && text[0] === "\\" && !implicitCommands.hasOwnProperty(text)) { if (this.settings.throwOnError) { throw new ParseError("Undefined control sequence: " + text, firstToken); } result = this.formatUnsupportedCmd(text); this.consume(); } } return result; } /** * Form ligature-like combinations of characters for text mode. * This includes inputs like "--", "---", "``" and "''". * The result will simply replace multiple textord nodes with a single * character in each value by a single textord node having multiple * characters in its value. The representation is still ASCII source. * The group will be modified in place. */ formLigatures(group) { var n = group.length - 1; for (var i = 0; i < n; ++i) { var a = group[i]; // $FlowFixMe: Not every node type has a `text` property. var v = a.text; if (v === "-" && group[i + 1].text === "-") { if (i + 1 < n && group[i + 2].text === "-") { group.splice(i, 3, { typtype: snap-revision authority-id: canonical snap-sha3-384: jdL_nMZP8jyXrtncwTIOh3GBcde7nn18bnJNpJH7wZjqVyCs4vXq8LpcIYZYv7XT developer-id: canonical provenance: global-upload snap-id: J60k4JY0HppjwOjW8dZdYc8obXKxujRu snap-revision: 24061 snap-size: 96292864 timestamp: 2022-12-02T16:20:30.476463Z sign-key-sha3-384: BWDEoaqyr25nF5SNCvEv2v7QnM9QsfCc0PBMYD_i2NGSQ32EF2d4D0hqUel3m8ul AcLBUgQAAQoABgUCY4olzgAADrMQAA1qFMUniTrLCJAaoY1eLleW0T5VeD6x4lnWxmoSvVDPlu+i ZGCyHLNSrGG9oM3IITSKlH8UxlThtB6qfdPQEi8doAO7KS6StwxS40lvGqkYPK0R6iwUGtynwbi0 M/rMHEo/lADMOr8TMckW1wuUKkryDQT2I34e/AHNe+X/Hq6D27DMcPxWUCpWzCbcb7t9fN3I88GA Epn8iJLB5LzUwVTMi1VUOwnNOF6WKn5KVM87aC7YH7osfPSxcwrcAvRcwyKZa50T40C7sVNoYXch sHH/EceiFhmxRLX5HVpmPNB9qKHtXRUubaK33DPrqCBFEjGnne9GO9z9v/5vryBq9wyIiaggBxS0 RGnN4vp08VfTEX/QhlaBTA9fIdZwx1FBsnTnSLkMkhVi/XaRhqsrMtyAeCIZu53MvbIH/3wREtrJ y0O3jkT62AZ4sAxEUmL3H2dN+tOZCujs5CYDcjeVbeC4u5Fu0bqyZHC1k5t+jevS+HIc4zDpITMr noZXml+YwpXJxOz4dro1R0gm6OLhgDMBtNfMBnM5hhpLp6RT0CPUBsyVEuemIlRQZAK1QPJfMzNo dzHrBnrn1GbvGDt8W2IqW1palehcqqJ4jehdCzAQhJdyYrTLJ/SQ9QatmvY9J2HJS50TYWVdsRtN JrB7qR9D4XfVkdcawlVnqB0svJTe