/** * This is the ParseError class, which is the main error thrown by Temml * functions when something has gone wrong. This is used to distinguish internal * errors from errors in the expression that the user provided. * * If possible, a caller should provide a Token or ParseNode with information * about where in the source string the problem occurred. */ class ParseError { constructor( message, // The error message token // An object providing position information ) { let error = " " + message; let start; const loc = token && token.loc; if (loc && loc.start <= loc.end) { // If we have the input and a position, make the error a bit fancier // Get the input const input = loc.lexer.input; // Prepend some information start = loc.start; const end = loc.end; if (start === input.length) { error += " at end of input: "; } else { error += " at position " + (start + 1) + ": "; } // Underline token in question using combining underscores const underlined = input.slice(start, end).replace(/[^]/g, "$&\u0332"); // Extract some context from the input and add it to the error let left; if (start > 15) { left = "…" + input.slice(start - 15, start); } else { left = input.slice(0, start); } let right; if (end + 15 < input.length) { right = input.slice(end, end + 15) + "…"; } else { right = input.slice(end); } error += left + underlined + right; } // Some hackery to make ParseError a prototype of Error // See http://stackoverflow.com/a/8460753 const self = new Error(error); self.name = "ParseError"; self.__proto__ = ParseError.prototype; self.position = start; return self; } } ParseError.prototype.__proto__ = Error.prototype; // /** * This file contains a list of utility functions which are useful in other * files. */ /** * Provide a default value if a setting is undefined */ const deflt = function(setting, defaultIfUndefined) { return setting === undefined ? defaultIfUndefined : setting; }; // hyphenate and escape adapted from Facebook's React under Apache 2 license const uppercase = /([A-Z])/g; const hyphenate = function(str) { return str.replace(uppercase, "-$1").toLowerCase(); }; const ESCAPE_LOOKUP = { "&": "&", ">": ">", "<": "<", '"': """, "'": "'" }; const ESCAPE_REGEX = /[&><"']/g; /** * Escapes text to prevent scripting attacks. */ function escape(text) { return String(text).replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]); } /** * Sometimes we want to pull out the innermost element of a group. In most * cases, this will just be the group itself, but when ordgroups and colors have * a single element, we want to pull that out. */ const getBaseElem = function(group) { if (group.type === "ordgroup") { if (group.body.length === 1) { return getBaseElem(group.body[0]); } else { return group; } } else if (group.type === "color") { if (group.body.length === 1) { return getBaseElem(group.body[0]); } else { return group; } } else if (group.type === "font") { return getBaseElem(group.body); } else { return group; } }; /** * TeXbook algorithms often reference "character boxes", which are simply groups * with a single character in them. To decide if something is a character box, * we find its innermost group, and see if it is a single character. */ const isCharacterBox = function(group) { const baseElem = getBaseElem(group); // These are all the types of groups which hold single characters return baseElem.type === "mathord" || baseElem.type === "textord" || baseElem.type === "atom" }; const assert = function(value) { if (!value) { throw new Error("Expected non-null, but got " + String(value)); } return value; }; /** * Return the protocol of a URL, or "_relative" if the URL does not specify a * protocol (and thus is relative). */ const protocolFromUrl = function(url) { const protocol = /^\s*([^\\/#]*?)(?::|�*58|�*3a)/i.exec(url); return protocol != null ? protocol[1] : "_relative"; }; /** * Round `n` to 4 decimal places, or to the nearest 1/10,000th em. The TeXbook * gives an acceptable rounding error of 100sp (which would be the nearest * 1/6551.6em with our ptPerEm = 10): * http://www.ctex.org/documents/shredder/src/texbook.pdf#page=69 */ const round = function(n) { return +n.toFixed(4); }; var utils = { deflt, escape, hyphenate, getBaseElem, isCharacterBox, protocolFromUrl, round }; /** * This is a module for storing settings passed into Temml. It correctly handles * default settings. */ /** * The main Settings object */ class Settings { constructor(options) { // allow null options options = options || {}; this.displayMode = utils.deflt(options.displayMode, false); // boolean this.annotate = utils.deflt(options.annotate, false); // boolean this.leqno = utils.deflt(options.leqno, false); // boolean this.throwOnError = utils.deflt(options.throwOnError, false); // boolean this.errorColor = utils.deflt(options.errorColor, "#b22222"); // string this.macros = options.macros || {}; this.wrap = utils.deflt(options.wrap, "tex"); // "tex" | "=" this.xml = utils.deflt(options.xml, false); // boolean this.colorIsTextColor = utils.deflt(options.colorIsTextColor, false); // booelean this.strict = utils.deflt(options.strict, false); // boolean this.trust = utils.deflt(options.trust, false); // trust context. See html.js. this.maxSize = (options.maxSize === undefined ? [Infinity, Infinity] : Array.isArray(options.maxSize) ? options.maxSize : [Infinity, Infinity] ); this.maxExpand = Math.max(0, utils.deflt(options.maxExpand, 1000)); // number } /** * Check whether to test potentially dangerous input, and return * `true` (trusted) or `false` (untrusted). The sole argument `context` * should be an object with `command` field specifying the relevant LaTeX * command (as a string starting with `\`), and any other arguments, etc. * If `context` has a `url` field, a `protocol` field will automatically * get added by this function (changing the specified object). */ isTrusted(context) { if (context.url && !context.protocol) { context.protocol = utils.protocolFromUrl(context.url); } const trust = typeof this.trust === "function" ? this.trust(context) : this.trust; return Boolean(trust); } } /** * All registered functions. * `functions.js` just exports this same dictionary again and makes it public. * `Parser.js` requires this dictionary. */ const _functions = {}; /** * All MathML builders. Should be only used in the `define*` and the `build*ML` * functions. */ const _mathmlGroupBuilders = {}; function defineFunction({ type, names, props, handler, mathmlBuilder }) { // Set default values of functions const 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 (let i = 0; i < names.length; ++i) { _functions[names[i]] = data; } if (type) { if (mathmlBuilder) { _mathmlGroupBuilders[type] = mathmlBuilder; } } } /** * Use this to register only the MathML builder 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({ type, mathmlBuilder }) { defineFunction({ type, names: [], props: { numArgs: 0 }, handler() { throw new Error("Should never be called.") }, mathmlBuilder }); } const normalizeArgument = function(arg) { return arg.type === "ordgroup" && arg.body.length === 1 ? arg.body[0] : arg }; // Since the corresponding buildMathML function expects a // list of elements, we normalize for different kinds of arguments const ordargument = function(arg) { return arg.type === "ordgroup" ? arg.body : [arg] }; /** * This node represents a document fragment, which contains elements, but when * placed into the DOM doesn't have any representation itself. It only contains * children and doesn't have any DOM node properties. */ class DocumentFragment { constructor(children) { this.children = children; this.classes = []; this.style = {}; } hasClass(className) { return this.classes.includes(className); } /** Convert the fragment into a node. */ toNode() { const frag = document.createDocumentFragment(); for (let i = 0; i < this.children.length; i++) { frag.appendChild(this.children[i].toNode()); } return frag; } /** Convert the fragment into HTML markup. */ toMarkup() { let markup = ""; // Simply concatenate the markup for the children together. for (let i = 0; i < this.children.length; i++) { markup += this.children[i].toMarkup(); } return markup; } /** * Converts the math node into a string, similar to innerText. Applies to * MathDomNode's only. */ toText() { // To avoid this, we would subclass documentFragment separately for // MathML, but polyfills for subclassing is expensive per PR 1469. const toText = (child) => child.toText(); return this.children.map(toText).join(""); } } /** * 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. * */ /** * Create an HTML className based on a list of classes. In addition to joining * with spaces, we also remove empty classes. */ const createClass = function(classes) { return classes.filter((cls) => cls).join(" "); }; const initNode = function(classes, style) { this.classes = classes || []; this.attributes = {}; this.style = style || {}; }; /** * Convert into an HTML node */ const toNode = function(tagName) { const node = document.createElement(tagName); // Apply the class node.className = createClass(this.classes); // Apply inline styles for (const style in this.style) { if (Object.prototype.hasOwnProperty.call(this.style, style )) { node.style[style] = this.style[style]; } } // Apply attributes for (const attr in this.attributes) { if (Object.prototype.hasOwnProperty.call(this.attributes, attr )) { node.setAttribute(attr, this.attributes[attr]); } } // Append the children, also as HTML nodes for (let i = 0; i < this.children.length; i++) { node.appendChild(this.children[i].toNode()); } return node; }; /** * Convert into an HTML markup string */ const toMarkup = function(tagName) { let markup = `<${tagName}`; // Add the class if (this.classes.length) { markup += ` class="${utils.escape(createClass(this.classes))}"`; } let styles = ""; // Add the styles, after hyphenation for (const style in this.style) { if (Object.prototype.hasOwnProperty.call(this.style, style )) { styles += `${utils.hyphenate(style)}:${this.style[style]};`; } } if (styles) { markup += ` style="${styles}"`; } // Add the attributes for (const attr in this.attributes) { if (Object.prototype.hasOwnProperty.call(this.attributes, attr )) { markup += ` ${attr}="${utils.escape(this.attributes[attr])}"`; } } markup += ">"; // Add the markup of the children, also as markup for (let i = 0; i < this.children.length; i++) { markup += this.children[i].toMarkup(); } markup += ``; return markup; }; /** * This node represents a span node, with a className, a list of children, and * an inline style. * */ class Span { constructor(classes, children, style) { initNode.call(this, classes, style); this.children = children || []; } setAttribute(attribute, value) { this.attributes[attribute] = value; } toNode() { return toNode.call(this, "span"); } toMarkup() { return toMarkup.call(this, "span"); } } class TextNode$1 { constructor(text) { this.text = text; } toNode() { return document.createTextNode(this.text); } toMarkup() { return utils.escape(this.text); } } /* * This node represents an image embed () element. */ class Img { constructor(src, alt, style) { this.alt = alt; this.src = src; this.classes = ["mord"]; this.style = style; } hasClass(className) { return this.classes.includes(className); } toNode() { const node = document.createElement("img"); node.src = this.src; node.alt = this.alt; node.className = "mord"; // Apply inline styles for (const style in this.style) { if (Object.prototype.hasOwnProperty.call(this.style, style )) { node.style[style] = this.style[style]; } } return node; } toMarkup() { let markup = `${this.alt}` and * `` tags). */ class MathNode { constructor(type, children, classes, style) { this.type = type; this.attributes = {}; this.children = children || []; this.classes = classes || []; this.style = style || {}; // Used for elements } /** * 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() { const node = document.createElementNS("http://www.w3.org/1998/Math/MathML", this.type); for (const 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); } // Apply inline styles for (const style in this.style) { if (Object.prototype.hasOwnProperty.call(this.style, style )) { node.style[style] = this.style[style]; } } for (let i = 0; i < this.children.length; i++) { node.appendChild(this.children[i].toNode()); } return node; } /** * Converts the math node into an HTML markup string. */ toMarkup() { let markup = "<" + this.type; // Add the attributes for (const 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))}"`; } let styles = ""; // Add the styles, after hyphenation for (const style in this.style) { if (Object.prototype.hasOwnProperty.call(this.style, style )) { styles += `${utils.hyphenate(style)}:${this.style[style]};`; } } if (styles) { markup += ` style="${styles}"`; } markup += ">"; for (let 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 = 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; } } // Do not make an the only child of a . // An acts as its own implicit . const wrapWithMstyle = expression => { let node; if (expression.length === 1 && expression[0].type === "mrow") { node = expression.pop(); node.type = "mstyle"; } else { node = new MathNode("mstyle", expression); } return node }; var mathMLTree = { MathNode, TextNode, newDocumentFragment }; /** * This file provides support for building horizontal stretchy elements. */ // TODO: Remove when Chromium stretches \widetilde & \widehat const estimatedWidth = node => { let width = 0; if (node.body) { for (const item of node.body) { width += estimatedWidth(item); } } else if (node.type === "supsub") { width += estimatedWidth(node.base); if (node.sub) { width += 0.7 * estimatedWidth(node.sub); } if (node.sup) { width += 0.7 * estimatedWidth(node.sup); } } else if (node.type === "mathord" || node.type === "textord") { for (const ch of node.text.split('')) { const codePoint = ch.codePointAt(0); if ((0x60 < codePoint && codePoint < 0x7B) || (0x03B0 < codePoint && codePoint < 0x3CA)) { width += 0.56; // lower case latin or greek. Use advance width of letter n } else if (0x2F < codePoint && codePoint < 0x3A) { width += 0.50; // numerals. } else { width += 0.92; // advance width of letter M } } } else { width += 1.0; } return width }; const stretchyCodePoint = { widehat: "^", widecheck: "ˇ", widetilde: "~", wideparen: "⏜", // \u23dc utilde: "~", overleftarrow: "\u2190", underleftarrow: "\u2190", xleftarrow: "\u2190", overrightarrow: "\u2192", underrightarrow: "\u2192", xrightarrow: "\u2192", underbrace: "\u23df", overbrace: "\u23de", overgroup: "\u23e0", overparen: "⏜", undergroup: "\u23e1", underparen: "\u23dd", 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", xtwoheadleftarrow: "\u219e", xtwoheadrightarrow: "\u21a0", xlongequal: "=", xrightleftarrows: "\u21c4", yields: "\u2192", yieldsLeft: "\u2190", mesomerism: "\u2194", longrightharpoonup: "\u21c0", longleftharpoondown: "\u21bd", eqrightharpoonup: "\u21c0", eqleftharpoondown: "\u21bd", "\\cdrightarrow": "\u2192", "\\cdleftarrow": "\u2190", "\\cdlongequal": "=" }; const mathMLnode = function(label) { const child = new mathMLTree.TextNode(stretchyCodePoint[label.slice(1)]); const node = new mathMLTree.MathNode("mo", [child]); node.setAttribute("stretchy", "true"); return node }; const crookedWides = ["\\widetilde", "\\widehat", "\\widecheck", "\\utilde"]; // TODO: Remove when Chromium stretches \widetilde & \widehat const accentNode = (group) => { const mo = mathMLnode(group.label); if (crookedWides.includes(group.label)) { const width = estimatedWidth(group.base); if (1 < width && width < 1.6) { mo.classes.push("tml-crooked-2"); } else if (1.6 <= width && width < 2.5) { mo.classes.push("tml-crooked-3"); } else if (2.5 <= width) { mo.classes.push("tml-crooked-4"); } } return mo }; var stretchy = { mathMLnode, accentNode }; /** * This file holds a list of all no-argument functions and single-character * symbols (like 'a' or ';'). * * For each of the symbols, there are two properties they can have: * - group (required): the ParseNode group type the symbol should have (i.e. "textord", "mathord", etc). * - 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. const ATOMS = { bin: 1, close: 1, inner: 1, open: 1, punct: 1, rel: 1 }; const NON_ATOMS = { "accent-token": 1, mathord: 1, "op-token": 1, spacing: 1, textord: 1 }; const symbols = { math: {}, text: {} }; /** `acceptUnicodeChar = true` is only applicable if `replace` is set. */ function defineSymbol(mode, group, replace, name, acceptUnicodeChar) { symbols[mode][name] = { 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: const math = "math"; const text = "text"; // groups: const accent = "accent-token"; const bin = "bin"; const close = "close"; const inner = "inner"; const mathord = "mathord"; const op = "op-token"; const open = "open"; const punct = "punct"; const rel = "rel"; const spacing = "spacing"; const textord = "textord"; // Now comes the symbol table // Relation Symbols defineSymbol(math, rel, "\u2261", "\\equiv", true); defineSymbol(math, rel, "\u227a", "\\prec", true); defineSymbol(math, rel, "\u227b", "\\succ", true); defineSymbol(math, rel, "\u223c", "\\sim", true); defineSymbol(math, rel, "\u27c2", "\\perp", true); defineSymbol(math, rel, "\u2aaf", "\\preceq", true); defineSymbol(math, rel, "\u2ab0", "\\succeq", true); defineSymbol(math, rel, "\u2243", "\\simeq", true); defineSymbol(math, rel, "\u224c", "\\backcong", true); defineSymbol(math, rel, "|", "\\mid", true); defineSymbol(math, rel, "\u226a", "\\ll", true); defineSymbol(math, rel, "\u226b", "\\gg", true); defineSymbol(math, rel, "\u224d", "\\asymp", true); defineSymbol(math, rel, "\u2225", "\\parallel"); defineSymbol(math, rel, "\u2323", "\\smile", true); defineSymbol(math, rel, "\u2291", "\\sqsubseteq", true); defineSymbol(math, rel, "\u2292", "\\sqsupseteq", true); defineSymbol(math, rel, "\u2250", "\\doteq", true); defineSymbol(math, rel, "\u2322", "\\frown", true); defineSymbol(math, rel, "\u220b", "\\ni", true); defineSymbol(math, rel, "\u220c", "\\notni", true); defineSymbol(math, rel, "\u221d", "\\propto", true); defineSymbol(math, rel, "\u22a2", "\\vdash", true); defineSymbol(math, rel, "\u22a3", "\\dashv", true); defineSymbol(math, rel, "\u220b", "\\owns"); defineSymbol(math, rel, "\u2258", "\\arceq", true); defineSymbol(math, rel, "\u2259", "\\wedgeq", true); defineSymbol(math, rel, "\u225a", "\\veeeq", true); defineSymbol(math, rel, "\u225b", "\\stareq", true); defineSymbol(math, rel, "\u225d", "\\eqdef", true); defineSymbol(math, rel, "\u225e", "\\measeq", true); defineSymbol(math, rel, "\u225f", "\\questeq", true); defineSymbol(math, rel, "\u2260", "\\ne", true); defineSymbol(math, rel, "\u2260", "\\neq"); // unicodemath defineSymbol(math, rel, "\u2a75", "\\eqeq", true); defineSymbol(math, rel, "\u2a76", "\\eqeqeq", true); // mathtools.sty defineSymbol(math, rel, "\u2237", "\\dblcolon", true); defineSymbol(math, rel, "\u2254", "\\coloneqq", true); defineSymbol(math, rel, "\u2255", "\\eqqcolon", true); defineSymbol(math, rel, "\u2239", "\\eqcolon", true); defineSymbol(math, rel, "\u2A74", "\\Coloneqq", true); // Punctuation defineSymbol(math, punct, "\u002e", "\\ldotp"); defineSymbol(math, punct, "\u00b7", "\\cdotp"); // Misc Symbols defineSymbol(math, textord, "\u0023", "\\#"); defineSymbol(text, textord, "\u0023", "\\#"); defineSymbol(math, textord, "\u0026", "\\&"); defineSymbol(text, textord, "\u0026", "\\&"); defineSymbol(math, textord, "\u2135", "\\aleph", true); defineSymbol(math, textord, "\u2200", "\\forall", true); defineSymbol(math, textord, "\u210f", "\\hbar", true); defineSymbol(math, textord, "\u2203", "\\exists", true); // ∇ is actually a unary operator, not binary. But this works. defineSymbol(math, bin, "\u2207", "\\nabla", true); defineSymbol(math, textord, "\u266d", "\\flat", true); defineSymbol(math, textord, "\u2113", "\\ell", true); defineSymbol(math, textord, "\u266e", "\\natural", true); defineSymbol(math, textord, "Å", "\\Angstrom", true); defineSymbol(text, textord, "Å", "\\Angstrom", true); defineSymbol(math, textord, "\u2663", "\\clubsuit", true); defineSymbol(math, textord, "\u2667", "\\varclubsuit", true); defineSymbol(math, textord, "\u2118", "\\wp", true); defineSymbol(math, textord, "\u266f", "\\sharp", true); defineSymbol(math, textord, "\u2662", "\\diamondsuit", true); defineSymbol(math, textord, "\u2666", "\\vardiamondsuit", true); defineSymbol(math, textord, "\u211c", "\\Re", true); defineSymbol(math, textord, "\u2661", "\\heartsuit", true); defineSymbol(math, textord, "\u2665", "\\varheartsuit", true); defineSymbol(math, textord, "\u2111", "\\Im", true); defineSymbol(math, textord, "\u2660", "\\spadesuit", true); defineSymbol(math, textord, "\u2664", "\\varspadesuit", true); defineSymbol(math, textord, "\u2640", "\\female", true); defineSymbol(math, textord, "\u2642", "\\male", true); defineSymbol(math, textord, "\u00a7", "\\S", true); defineSymbol(text, textord, "\u00a7", "\\S"); defineSymbol(math, textord, "\u00b6", "\\P", true); defineSymbol(text, textord, "\u00b6", "\\P"); defineSymbol(text, textord, "\u263a", "\\smiley", true); defineSymbol(math, textord, "\u263a", "\\smiley", true); // Math and Text defineSymbol(math, textord, "\u2020", "\\dag"); defineSymbol(text, textord, "\u2020", "\\dag"); defineSymbol(text, textord, "\u2020", "\\textdagger"); defineSymbol(math, textord, "\u2021", "\\ddag"); defineSymbol(text, textord, "\u2021", "\\ddag"); defineSymbol(text, textord, "\u2021", "\\textdaggerdbl"); // Large Delimiters defineSymbol(math, close, "\u23b1", "\\rmoustache", true); defineSymbol(math, open, "\u23b0", "\\lmoustache", true); defineSymbol(math, close, "\u27ef", "\\rgroup", true); defineSymbol(math, open, "\u27ee", "\\lgroup", true); // Binary Operators defineSymbol(math, bin, "\u2213", "\\mp", true); defineSymbol(math, bin, "\u2296", "\\ominus", true); defineSymbol(math, bin, "\u228e", "\\uplus", true); defineSymbol(math, bin, "\u2293", "\\sqcap", true); defineSymbol(math, bin, "\u2217", "\\ast"); defineSymbol(math, bin, "\u2294", "\\sqcup", true); defineSymbol(math, bin, "\u25ef", "\\bigcirc", true); defineSymbol(math, bin, "\u2219", "\\bullet", true); defineSymbol(math, bin, "\u2021", "\\ddagger"); defineSymbol(math, bin, "\u2240", "\\wr", true); defineSymbol(math, bin, "\u2a3f", "\\amalg"); defineSymbol(math, bin, "\u0026", "\\And"); // from amsmath defineSymbol(math, bin, "\u2AFD", "\\sslash", true); // from stmaryrd // Arrow Symbols defineSymbol(math, rel, "\u27f5", "\\longleftarrow", true); defineSymbol(math, rel, "\u21d0", "\\Leftarrow", true); defineSymbol(math, rel, "\u27f8", "\\Longleftarrow", true); defineSymbol(math, rel, "\u27f6", "\\longrightarrow", true); defineSymbol(math, rel, "\u21d2", "\\Rightarrow", true); defineSymbol(math, rel, "\u27f9", "\\Longrightarrow", true); defineSymbol(math, rel, "\u2194", "\\leftrightarrow", true); defineSymbol(math, rel, "\u27f7", "\\longleftrightarrow", true); defineSymbol(math, rel, "\u21d4", "\\Leftrightarrow", true); defineSymbol(math, rel, "\u27fa", "\\Longleftrightarrow", true); defineSymbol(math, rel, "\u21a4", "\\mapsfrom", true); defineSymbol(math, rel, "\u21a6", "\\mapsto", true); defineSymbol(math, rel, "\u27fc", "\\longmapsto", true); defineSymbol(math, rel, "\u2197", "\\nearrow", true); defineSymbol(math, rel, "\u21a9", "\\hookleftarrow", true); defineSymbol(math, rel, "\u21aa", "\\hookrightarrow", true); defineSymbol(math, rel, "\u2198", "\\searrow", true); defineSymbol(math, rel, "\u21bc", "\\leftharpoonup", true); defineSymbol(math, rel, "\u21c0", "\\rightharpoonup", true); defineSymbol(math, rel, "\u2199", "\\swarrow", true); defineSymbol(math, rel, "\u21bd", "\\leftharpoondown", true); defineSymbol(math, rel, "\u21c1", "\\rightharpoondown", true); defineSymbol(math, rel, "\u2196", "\\nwarrow", true); defineSymbol(math, rel, "\u21cc", "\\rightleftharpoons", true); defineSymbol(math, mathord, "\u21af", "\\lightning", true); defineSymbol(math, mathord, "\u220E", "\\QED", true); defineSymbol(math, mathord, "\u2030", "\\permil", true); defineSymbol(text, textord, "\u2030", "\\permil"); defineSymbol(math, mathord, "\u2609", "\\astrosun", true); defineSymbol(math, mathord, "\u263c", "\\sun", true); defineSymbol(math, mathord, "\u263e", "\\leftmoon", true); defineSymbol(math, mathord, "\u263d", "\\rightmoon", true); // AMS Negated Binary Relations defineSymbol(math, rel, "\u226e", "\\nless", true); // Symbol names preceeded by "@" each have a corresponding macro. defineSymbol(math, rel, "\u2a87", "\\lneq", true); defineSymbol(math, rel, "\u2268", "\\lneqq", true); defineSymbol(math, rel, "\u2268\ufe00", "\\lvertneqq"); defineSymbol(math, rel, "\u22e6", "\\lnsim", true); defineSymbol(math, rel, "\u2a89", "\\lnapprox", true); defineSymbol(math, rel, "\u2280", "\\nprec", true); // unicode-math maps \u22e0 to \npreccurlyeq. We'll use the AMS synonym. defineSymbol(math, rel, "\u22e0", "\\npreceq", true); defineSymbol(math, rel, "\u22e8", "\\precnsim", true); defineSymbol(math, rel, "\u2ab9", "\\precnapprox", true); defineSymbol(math, rel, "\u2241", "\\nsim", true); defineSymbol(math, rel, "\u2224", "\\nmid", true); defineSymbol(math, rel, "\u2224", "\\nshortmid"); defineSymbol(math, rel, "\u22ac", "\\nvdash", true); defineSymbol(math, rel, "\u22ad", "\\nvDash", true); defineSymbol(math, rel, "\u22ea", "\\ntriangleleft"); defineSymbol(math, rel, "\u22ec", "\\ntrianglelefteq", true); defineSymbol(math, rel, "\u2284", "\\nsubset", true); defineSymbol(math, rel, "\u2285", "\\nsupset", true); defineSymbol(math, rel, "\u228a", "\\subsetneq", true); defineSymbol(math, rel, "\u228a\ufe00", "\\varsubsetneq"); defineSymbol(math, rel, "\u2acb", "\\subsetneqq", true); defineSymbol(math, rel, "\u2acb\ufe00", "\\varsubsetneqq"); defineSymbol(math, rel, "\u226f", "\\ngtr", true); defineSymbol(math, rel, "\u2a88", "\\gneq", true); defineSymbol(math, rel, "\u2269", "\\gneqq", true); defineSymbol(math, rel, "\u2269\ufe00", "\\gvertneqq"); defineSymbol(math, rel, "\u22e7", "\\gnsim", true); defineSymbol(math, rel, "\u2a8a", "\\gnapprox", true); defineSymbol(math, rel, "\u2281", "\\nsucc", true); // unicode-math maps \u22e1 to \nsucccurlyeq. We'll use the AMS synonym. defineSymbol(math, rel, "\u22e1", "\\nsucceq", true); defineSymbol(math, rel, "\u22e9", "\\succnsim", true); defineSymbol(math, rel, "\u2aba", "\\succnapprox", true); // unicode-math maps \u2246 to \simneqq. We'll use the AMS synonym. defineSymbol(math, rel, "\u2246", "\\ncong", true); defineSymbol(math, rel, "\u2226", "\\nparallel", true); defineSymbol(math, rel, "\u2226", "\\nshortparallel"); defineSymbol(math, rel, "\u22af", "\\nVDash", true); defineSymbol(math, rel, "\u22eb", "\\ntriangleright"); defineSymbol(math, rel, "\u22ed", "\\ntrianglerighteq", true); defineSymbol(math, rel, "\u228b", "\\supsetneq", true); defineSymbol(math, rel, "\u228b", "\\varsupsetneq"); defineSymbol(math, rel, "\u2acc", "\\supsetneqq", true); defineSymbol(math, rel, "\u2acc\ufe00", "\\varsupsetneqq"); defineSymbol(math, rel, "\u22ae", "\\nVdash", true); defineSymbol(math, rel, "\u2ab5", "\\precneqq", true); defineSymbol(math, rel, "\u2ab6", "\\succneqq", true); defineSymbol(math, bin, "\u22b4", "\\unlhd"); defineSymbol(math, bin, "\u22b5", "\\unrhd"); // AMS Negated Arrows defineSymbol(math, rel, "\u219a", "\\nleftarrow", true); defineSymbol(math, rel, "\u219b", "\\nrightarrow", true); defineSymbol(math, rel, "\u21cd", "\\nLeftarrow", true); defineSymbol(math, rel, "\u21cf", "\\nRightarrow", true); defineSymbol(math, rel, "\u21ae", "\\nleftrightarrow", true); defineSymbol(math, rel, "\u21ce", "\\nLeftrightarrow", true); // AMS Misc defineSymbol(math, rel, "\u25b3", "\\vartriangle"); defineSymbol(math, textord, "\u210f", "\\hslash"); defineSymbol(math, textord, "\u25bd", "\\triangledown"); defineSymbol(math, textord, "\u25ca", "\\lozenge"); defineSymbol(math, textord, "\u24c8", "\\circledS"); defineSymbol(math, textord, "\u00ae", "\\circledR", true); defineSymbol(text, textord, "\u00ae", "\\circledR"); defineSymbol(text, textord, "\u00ae", "\\textregistered"); defineSymbol(math, textord, "\u2221", "\\measuredangle", true); defineSymbol(math, textord, "\u2204", "\\nexists"); defineSymbol(math, textord, "\u2127", "\\mho"); defineSymbol(math, textord, "\u2132", "\\Finv", true); defineSymbol(math, textord, "\u2141", "\\Game", true); defineSymbol(math, textord, "\u2035", "\\backprime"); defineSymbol(math, textord, "\u2036", "\\backdprime"); defineSymbol(math, textord, "\u2037", "\\backtrprime"); defineSymbol(math, textord, "\u25b2", "\\blacktriangle"); defineSymbol(math, textord, "\u25bc", "\\blacktriangledown"); defineSymbol(math, textord, "\u25a0", "\\blacksquare"); defineSymbol(math, textord, "\u29eb", "\\blacklozenge"); defineSymbol(math, textord, "\u2605", "\\bigstar"); defineSymbol(math, textord, "\u2222", "\\sphericalangle", true); defineSymbol(math, textord, "\u2201", "\\complement", true); // unicode-math maps U+F0 to \matheth. We map to AMS function \eth defineSymbol(math, textord, "\u00f0", "\\eth", true); defineSymbol(text, textord, "\u00f0", "\u00f0"); defineSymbol(math, textord, "\u2571", "\\diagup"); defineSymbol(math, textord, "\u2572", "\\diagdown"); defineSymbol(math, textord, "\u25a1", "\\square"); defineSymbol(math, textord, "\u25a1", "\\Box"); defineSymbol(math, textord, "\u25ca", "\\Diamond"); // unicode-math maps U+A5 to \mathyen. We map to AMS function \yen defineSymbol(math, textord, "\u00a5", "\\yen", true); defineSymbol(text, textord, "\u00a5", "\\yen", true); defineSymbol(math, textord, "\u2713", "\\checkmark", true); defineSymbol(text, textord, "\u2713", "\\checkmark"); defineSymbol(math, textord, "\u2717", "\\ballotx", true); defineSymbol(text, textord, "\u2717", "\\ballotx"); defineSymbol(text, textord, "\u2022", "\\textbullet"); // AMS Hebrew defineSymbol(math, textord, "\u2136", "\\beth", true); defineSymbol(math, textord, "\u2138", "\\daleth", true); defineSymbol(math, textord, "\u2137", "\\gimel", true); // AMS Greek defineSymbol(math, textord, "\u03dd", "\\digamma", true); defineSymbol(math, textord, "\u03f0", "\\varkappa"); // AMS Delimiters defineSymbol(math, open, "\u231C", "\\ulcorner", true); defineSymbol(math, close, "\u231D", "\\urcorner", true); defineSymbol(math, open, "\u231E", "\\llcorner", true); defineSymbol(math, close, "\u231F", "\\lrcorner", true); // AMS Binary Relations defineSymbol(math, rel, "\u2266", "\\leqq", true); defineSymbol(math, rel, "\u2a7d", "\\leqslant", true); defineSymbol(math, rel, "\u2a95", "\\eqslantless", true); defineSymbol(math, rel, "\u2272", "\\lesssim", true); defineSymbol(math, rel, "\u2a85", "\\lessapprox", true); defineSymbol(math, rel, "\u224a", "\\approxeq", true); defineSymbol(math, bin, "\u22d6", "\\lessdot"); defineSymbol(math, rel, "\u22d8", "\\lll", true); defineSymbol(math, rel, "\u2276", "\\lessgtr", true); defineSymbol(math, rel, "\u22da", "\\lesseqgtr", true); defineSymbol(math, rel, "\u2a8b", "\\lesseqqgtr", true); defineSymbol(math, rel, "\u2251", "\\doteqdot"); defineSymbol(math, rel, "\u2253", "\\risingdotseq", true); defineSymbol(math, rel, "\u2252", "\\fallingdotseq", true); defineSymbol(math, rel, "\u223d", "\\backsim", true); defineSymbol(math, rel, "\u22cd", "\\backsimeq", true); defineSymbol(math, rel, "\u2ac5", "\\subseteqq", true); defineSymbol(math, rel, "\u22d0", "\\Subset", true); defineSymbol(math, rel, "\u228f", "\\sqsubset", true); defineSymbol(math, rel, "\u227c", "\\preccurlyeq", true); defineSymbol(math, rel, "\u22de", "\\curlyeqprec", true); defineSymbol(math, rel, "\u227e", "\\precsim", true); defineSymbol(math, rel, "\u2ab7", "\\precapprox", true); defineSymbol(math, rel, "\u22b2", "\\vartriangleleft"); defineSymbol(math, rel, "\u22b4", "\\trianglelefteq"); defineSymbol(math, rel, "\u22a8", "\\vDash", true); defineSymbol(math, rel, "\u22ab", "\\VDash", true); defineSymbol(math, rel, "\u22aa", "\\Vvdash", true); defineSymbol(math, rel, "\u2323", "\\smallsmile"); defineSymbol(math, rel, "\u2322", "\\smallfrown"); defineSymbol(math, rel, "\u224f", "\\bumpeq", true); defineSymbol(math, rel, "\u224e", "\\Bumpeq", true); defineSymbol(math, rel, "\u2267", "\\geqq", true); defineSymbol(math, rel, "\u2a7e", "\\geqslant", true); defineSymbol(math, rel, "\u2a96", "\\eqslantgtr", true); defineSymbol(math, rel, "\u2273", "\\gtrsim", true); defineSymbol(math, rel, "\u2a86", "\\gtrapprox", true); defineSymbol(math, bin, "\u22d7", "\\gtrdot"); defineSymbol(math, rel, "\u22d9", "\\ggg", true); defineSymbol(math, rel, "\u2277", "\\gtrless", true); defineSymbol(math, rel, "\u22db", "\\gtreqless", true); defineSymbol(math, rel, "\u2a8c", "\\gtreqqless", true); defineSymbol(math, rel, "\u2256", "\\eqcirc", true); defineSymbol(math, rel, "\u2257", "\\circeq", true); defineSymbol(math, rel, "\u225c", "\\triangleq", true); defineSymbol(math, rel, "\u223c", "\\thicksim"); defineSymbol(math, rel, "\u2248", "\\thickapprox"); defineSymbol(math, rel, "\u2ac6", "\\supseteqq", true); defineSymbol(math, rel, "\u22d1", "\\Supset", true); defineSymbol(math, rel, "\u2290", "\\sqsupset", true); defineSymbol(math, rel, "\u227d", "\\succcurlyeq", true); defineSymbol(math, rel, "\u22df", "\\curlyeqsucc", true); defineSymbol(math, rel, "\u227f", "\\succsim", true); defineSymbol(math, rel, "\u2ab8", "\\succapprox", true); defineSymbol(math, rel, "\u22b3", "\\vartriangleright"); defineSymbol(math, rel, "\u22b5", "\\trianglerighteq"); defineSymbol(math, rel, "\u22a9", "\\Vdash", true); defineSymbol(math, rel, "\u2223", "\\shortmid"); defineSymbol(math, rel, "\u2225", "\\shortparallel"); defineSymbol(math, rel, "\u226c", "\\between", true); defineSymbol(math, rel, "\u22d4", "\\pitchfork", true); defineSymbol(math, rel, "\u221d", "\\varpropto"); defineSymbol(math, rel, "\u25c0", "\\blacktriangleleft"); // unicode-math says that \therefore is a mathord atom. // We kept the amssymb atom type, which is rel. defineSymbol(math, rel, "\u2234", "\\therefore", true); defineSymbol(math, rel, "\u220d", "\\backepsilon"); defineSymbol(math, rel, "\u25b6", "\\blacktriangleright"); // unicode-math says that \because is a mathord atom. // We kept the amssymb atom type, which is rel. defineSymbol(math, rel, "\u2235", "\\because", true); defineSymbol(math, rel, "\u22d8", "\\llless"); defineSymbol(math, rel, "\u22d9", "\\gggtr"); defineSymbol(math, bin, "\u22b2", "\\lhd"); defineSymbol(math, bin, "\u22b3", "\\rhd"); defineSymbol(math, rel, "\u2242", "\\eqsim", true); defineSymbol(math, rel, "\u2251", "\\Doteq", true); defineSymbol(math, rel, "\u297d", "\\strictif", true); defineSymbol(math, rel, "\u297c", "\\strictfi", true); // AMS Binary Operators defineSymbol(math, bin, "\u2214", "\\dotplus", true); defineSymbol(math, bin, "\u2216", "\\smallsetminus"); defineSymbol(math, bin, "\u22d2", "\\Cap", true); defineSymbol(math, bin, "\u22d3", "\\Cup", true); defineSymbol(math, bin, "\u2a5e", "\\doublebarwedge", true); defineSymbol(math, bin, "\u229f", "\\boxminus", true); defineSymbol(math, bin, "\u229e", "\\boxplus", true); defineSymbol(math, bin, "\u22c7", "\\divideontimes", true); defineSymbol(math, bin, "\u22c9", "\\ltimes", true); defineSymbol(math, bin, "\u22ca", "\\rtimes", true); defineSymbol(math, bin, "\u22cb", "\\leftthreetimes", true); defineSymbol(math, bin, "\u22cc", "\\rightthreetimes", true); defineSymbol(math, bin, "\u22cf", "\\curlywedge", true); defineSymbol(math, bin, "\u22ce", "\\curlyvee", true); defineSymbol(math, bin, "\u229d", "\\circleddash", true); defineSymbol(math, bin, "\u229b", "\\circledast", true); defineSymbol(math, bin, "\u22ba", "\\intercal", true); defineSymbol(math, bin, "\u22d2", "\\doublecap"); defineSymbol(math, bin, "\u22d3", "\\doublecup"); defineSymbol(math, bin, "\u22a0", "\\boxtimes", true); defineSymbol(math, bin, "\u22c8", "\\bowtie", true); defineSymbol(math, bin, "\u22c8", "\\Join"); defineSymbol(math, bin, "\u27d5", "\\leftouterjoin", true); defineSymbol(math, bin, "\u27d6", "\\rightouterjoin", true); defineSymbol(math, bin, "\u27d7", "\\fullouterjoin", 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, rel, "\u21e2", "\\dashrightarrow", true); // unicode-math maps \u21e0 to \leftdasharrow. We'll use the AMS synonym. defineSymbol(math, rel, "\u21e0", "\\dashleftarrow", true); defineSymbol(math, rel, "\u21c7", "\\leftleftarrows", true); defineSymbol(math, rel, "\u21c6", "\\leftrightarrows", true); defineSymbol(math, rel, "\u21da", "\\Lleftarrow", true); defineSymbol(math, rel, "\u219e", "\\twoheadleftarrow", true); defineSymbol(math, rel, "\u21a2", "\\leftarrowtail", true); defineSymbol(math, rel, "\u21ab", "\\looparrowleft", true); defineSymbol(math, rel, "\u21cb", "\\leftrightharpoons", true); defineSymbol(math, rel, "\u21b6", "\\curvearrowleft", true); // unicode-math maps \u21ba to \acwopencirclearrow. We'll use the AMS synonym. defineSymbol(math, rel, "\u21ba", "\\circlearrowleft", true); defineSymbol(math, rel, "\u21b0", "\\Lsh", true); defineSymbol(math, rel, "\u21c8", "\\upuparrows", true); defineSymbol(math, rel, "\u21bf", "\\upharpoonleft", true); defineSymbol(math, rel, "\u21c3", "\\downharpoonleft", true); defineSymbol(math, rel, "\u22b6", "\\origof", true); defineSymbol(math, rel, "\u22b7", "\\imageof", true); defineSymbol(math, rel, "\u22b8", "\\multimap", true); defineSymbol(math, rel, "\u21ad", "\\leftrightsquigarrow", true); defineSymbol(math, rel, "\u21c9", "\\rightrightarrows", true); defineSymbol(math, rel, "\u21c4", "\\rightleftarrows", true); defineSymbol(math, rel, "\u21a0", "\\twoheadrightarrow", true); defineSymbol(math, rel, "\u21a3", "\\rightarrowtail", true); defineSymbol(math, rel, "\u21ac", "\\looparrowright", true); defineSymbol(math, rel, "\u21b7", "\\curvearrowright", true); // unicode-math maps \u21bb to \cwopencirclearrow. We'll use the AMS synonym. defineSymbol(math, rel, "\u21bb", "\\circlearrowright", true); defineSymbol(math, rel, "\u21b1", "\\Rsh", true); defineSymbol(math, rel, "\u21ca", "\\downdownarrows", true); defineSymbol(math, rel, "\u21be", "\\upharpoonright", true); defineSymbol(math, rel, "\u21c2", "\\downharpoonright", true); defineSymbol(math, rel, "\u21dd", "\\rightsquigarrow", true); defineSymbol(math, rel, "\u21dd", "\\leadsto"); defineSymbol(math, rel, "\u21db", "\\Rrightarrow", true); defineSymbol(math, rel, "\u21be", "\\restriction"); defineSymbol(math, textord, "\u2018", "`"); defineSymbol(math, textord, "$", "\\$"); defineSymbol(text, textord, "$", "\\$"); defineSymbol(text, textord, "$", "\\textdollar"); defineSymbol(math, textord, "¢", "\\cent"); defineSymbol(text, textord, "¢", "\\cent"); defineSymbol(math, textord, "%", "\\%"); defineSymbol(text, textord, "%", "\\%"); defineSymbol(math, textord, "_", "\\_"); defineSymbol(text, textord, "_", "\\_"); defineSymbol(text, textord, "_", "\\textunderscore"); defineSymbol(text, textord, "\u2423", "\\textvisiblespace", true); defineSymbol(math, textord, "\u2220", "\\angle", true); defineSymbol(math, textord, "\u221e", "\\infty", true); defineSymbol(math, textord, "\u2032", "\\prime"); defineSymbol(math, textord, "\u2033", "\\dprime"); defineSymbol(math, textord, "\u2034", "\\trprime"); defineSymbol(math, textord, "\u2057", "\\qprime"); defineSymbol(math, textord, "\u25b3", "\\triangle"); defineSymbol(text, textord, "\u0391", "\\Alpha", true); defineSymbol(text, textord, "\u0392", "\\Beta", true); defineSymbol(text, textord, "\u0393", "\\Gamma", true); defineSymbol(text, textord, "\u0394", "\\Delta", true); defineSymbol(text, textord, "\u0395", "\\Epsilon", true); defineSymbol(text, textord, "\u0396", "\\Zeta", true); defineSymbol(text, textord, "\u0397", "\\Eta", true); defineSymbol(text, textord, "\u0398", "\\Theta", true); defineSymbol(text, textord, "\u0399", "\\Iota", true); defineSymbol(text, textord, "\u039a", "\\Kappa", true); defineSymbol(text, textord, "\u039b", "\\Lambda", true); defineSymbol(text, textord, "\u039c", "\\Mu", true); defineSymbol(text, textord, "\u039d", "\\Nu", true); defineSymbol(text, textord, "\u039e", "\\Xi", true); defineSymbol(text, textord, "\u039f", "\\Omicron", true); defineSymbol(text, textord, "\u03a0", "\\Pi", true); defineSymbol(text, textord, "\u03a1", "\\Rho", true); defineSymbol(text, textord, "\u03a3", "\\Sigma", true); defineSymbol(text, textord, "\u03a4", "\\Tau", true); defineSymbol(text, textord, "\u03a5", "\\Upsilon", true); defineSymbol(text, textord, "\u03a6", "\\Phi", true); defineSymbol(text, textord, "\u03a7", "\\Chi", true); defineSymbol(text, textord, "\u03a8", "\\Psi", true); defineSymbol(text, textord, "\u03a9", "\\Omega", true); defineSymbol(math, mathord, "\u0391", "\\Alpha", true); defineSymbol(math, mathord, "\u0392", "\\Beta", true); defineSymbol(math, mathord, "\u0393", "\\Gamma", true); defineSymbol(math, mathord, "\u0394", "\\Delta", true); defineSymbol(math, mathord, "\u0395", "\\Epsilon", true); defineSymbol(math, mathord, "\u0396", "\\Zeta", true); defineSymbol(math, mathord, "\u0397", "\\Eta", true); defineSymbol(math, mathord, "\u0398", "\\Theta", true); defineSymbol(math, mathord, "\u0399", "\\Iota", true); defineSymbol(math, mathord, "\u039a", "\\Kappa", true); defineSymbol(math, mathord, "\u039b", "\\Lambda", true); defineSymbol(math, mathord, "\u039c", "\\Mu", true); defineSymbol(math, mathord, "\u039d", "\\Nu", true); defineSymbol(math, mathord, "\u039e", "\\Xi", true); defineSymbol(math, mathord, "\u039f", "\\Omicron", true); defineSymbol(math, mathord, "\u03a0", "\\Pi", true); defineSymbol(math, mathord, "\u03a1", "\\Rho", true); defineSymbol(math, mathord, "\u03a3", "\\Sigma", true); defineSymbol(math, mathord, "\u03a4", "\\Tau", true); defineSymbol(math, mathord, "\u03a5", "\\Upsilon", true); defineSymbol(math, mathord, "\u03a6", "\\Phi", true); defineSymbol(math, mathord, "\u03a7", "\\Chi", true); defineSymbol(math, mathord, "\u03a8", "\\Psi", true); defineSymbol(math, mathord, "\u03a9", "\\Omega", true); defineSymbol(math, open, "\u00ac", "\\neg", true); defineSymbol(math, open, "\u00ac", "\\lnot"); defineSymbol(math, textord, "\u22a4", "\\top"); defineSymbol(math, textord, "\u22a5", "\\bot"); defineSymbol(math, textord, "\u2205", "\\emptyset"); defineSymbol(math, textord, "\u2300", "\\varnothing"); defineSymbol(math, mathord, "\u03b1", "\\alpha", true); defineSymbol(math, mathord, "\u03b2", "\\beta", true); defineSymbol(math, mathord, "\u03b3", "\\gamma", true); defineSymbol(math, mathord, "\u03b4", "\\delta", true); defineSymbol(math, mathord, "\u03f5", "\\epsilon", true); defineSymbol(math, mathord, "\u03b6", "\\zeta", true); defineSymbol(math, mathord, "\u03b7", "\\eta", true); defineSymbol(math, mathord, "\u03b8", "\\theta", true); defineSymbol(math, mathord, "\u03b9", "\\iota", true); defineSymbol(math, mathord, "\u03ba", "\\kappa", true); defineSymbol(math, mathord, "\u03bb", "\\lambda", true); defineSymbol(math, mathord, "\u03bc", "\\mu", true); defineSymbol(math, mathord, "\u03bd", "\\nu", true); defineSymbol(math, mathord, "\u03be", "\\xi", true); defineSymbol(math, mathord, "\u03bf", "\\omicron", true); defineSymbol(math, mathord, "\u03c0", "\\pi", true); defineSymbol(math, mathord, "\u03c1", "\\rho", true); defineSymbol(math, mathord, "\u03c3", "\\sigma", true); defineSymbol(math, mathord, "\u03c4", "\\tau", true); defineSymbol(math, mathord, "\u03c5", "\\upsilon", true); defineSymbol(math, mathord, "\u03d5", "\\phi", true); defineSymbol(math, mathord, "\u03c7", "\\chi", true); defineSymbol(math, mathord, "\u03c8", "\\psi", true); defineSymbol(math, mathord, "\u03c9", "\\omega", true); defineSymbol(math, mathord, "\u03b5", "\\varepsilon", true); defineSymbol(math, mathord, "\u03d1", "\\vartheta", true); defineSymbol(math, mathord, "\u03d6", "\\varpi", true); defineSymbol(math, mathord, "\u03f1", "\\varrho", true); defineSymbol(math, mathord, "\u03c2", "\\varsigma", true); defineSymbol(math, mathord, "\u03c6", "\\varphi", true); defineSymbol(math, mathord, "\u03d8", "\\Coppa", true); defineSymbol(math, mathord, "\u03d9", "\\coppa", true); defineSymbol(math, mathord, "\u03d9", "\\varcoppa", true); defineSymbol(math, mathord, "\u03de", "\\Koppa", true); defineSymbol(math, mathord, "\u03df", "\\koppa", true); defineSymbol(math, mathord, "\u03e0", "\\Sampi", true); defineSymbol(math, mathord, "\u03e1", "\\sampi", true); defineSymbol(math, mathord, "\u03da", "\\Stigma", true); defineSymbol(math, mathord, "\u03db", "\\stigma", true); defineSymbol(math, mathord, "\u2aeb", "\\Bot"); defineSymbol(math, bin, "\u2217", "\u2217", true); defineSymbol(math, bin, "+", "+"); defineSymbol(math, bin, "*", "*"); defineSymbol(math, bin, "\u2044", "/", true); defineSymbol(math, bin, "\u2044", "\u2044"); defineSymbol(math, bin, "\u2212", "-", true); defineSymbol(math, bin, "\u22c5", "\\cdot", true); defineSymbol(math, bin, "\u2218", "\\circ", true); defineSymbol(math, bin, "\u00f7", "\\div", true); defineSymbol(math, bin, "\u00b1", "\\pm", true); defineSymbol(math, bin, "\u00d7", "\\times", true); defineSymbol(math, bin, "\u2229", "\\cap", true); defineSymbol(math, bin, "\u222a", "\\cup", true); defineSymbol(math, bin, "\u2216", "\\setminus", true); defineSymbol(math, bin, "\u2227", "\\land"); defineSymbol(math, bin, "\u2228", "\\lor"); defineSymbol(math, bin, "\u2227", "\\wedge", true); defineSymbol(math, bin, "\u2228", "\\vee", true); defineSymbol(math, open, "\u27e6", "\\llbracket", true); // stmaryrd/semantic packages defineSymbol(math, close, "\u27e7", "\\rrbracket", true); defineSymbol(math, open, "\u27e8", "\\langle", true); defineSymbol(math, open, "\u27ea", "\\lAngle", true); defineSymbol(math, open, "\u2989", "\\llangle", true); defineSymbol(math, open, "|", "\\lvert"); defineSymbol(math, open, "\u2016", "\\lVert"); defineSymbol(math, textord, "!", "\\oc"); // cmll package defineSymbol(math, textord, "?", "\\wn"); defineSymbol(math, textord, "\u2193", "\\shpos"); defineSymbol(math, textord, "\u2195", "\\shift"); defineSymbol(math, textord, "\u2191", "\\shneg"); defineSymbol(math, close, "?", "?"); defineSymbol(math, close, "!", "!"); defineSymbol(math, close, "‼", "‼"); defineSymbol(math, close, "\u27e9", "\\rangle", true); defineSymbol(math, close, "\u27eb", "\\rAngle", true); defineSymbol(math, close, "\u298a", "\\rrangle", true); defineSymbol(math, close, "|", "\\rvert"); defineSymbol(math, close, "\u2016", "\\rVert"); defineSymbol(math, open, "\u2983", "\\lBrace", true); // stmaryrd/semantic packages defineSymbol(math, close, "\u2984", "\\rBrace", true); defineSymbol(math, rel, "=", "\\equal", true); defineSymbol(math, rel, ":", ":"); defineSymbol(math, rel, "\u2248", "\\approx", true); defineSymbol(math, rel, "\u2245", "\\cong", true); defineSymbol(math, rel, "\u2265", "\\ge"); defineSymbol(math, rel, "\u2265", "\\geq", true); defineSymbol(math, rel, "\u2190", "\\gets"); defineSymbol(math, rel, ">", "\\gt", true); defineSymbol(math, rel, "\u2208", "\\in", true); defineSymbol(math, rel, "\u2209", "\\notin", true); defineSymbol(math, rel, "\ue020", "\\@not"); defineSymbol(math, rel, "\u2282", "\\subset", true); defineSymbol(math, rel, "\u2283", "\\supset", true); defineSymbol(math, rel, "\u2286", "\\subseteq", true); defineSymbol(math, rel, "\u2287", "\\supseteq", true); defineSymbol(math, rel, "\u2288", "\\nsubseteq", true); defineSymbol(math, rel, "\u2288", "\\nsubseteqq"); defineSymbol(math, rel, "\u2289", "\\nsupseteq", true); defineSymbol(math, rel, "\u2289", "\\nsupseteqq"); defineSymbol(math, rel, "\u22a8", "\\models"); defineSymbol(math, rel, "\u2190", "\\leftarrow", true); defineSymbol(math, rel, "\u2264", "\\le"); defineSymbol(math, rel, "\u2264", "\\leq", true); defineSymbol(math, rel, "<", "\\lt", true); defineSymbol(math, rel, "\u2192", "\\rightarrow", true); defineSymbol(math, rel, "\u2192", "\\to"); defineSymbol(math, rel, "\u2271", "\\ngeq", true); defineSymbol(math, rel, "\u2271", "\\ngeqq"); defineSymbol(math, rel, "\u2271", "\\ngeqslant"); defineSymbol(math, rel, "\u2270", "\\nleq", true); defineSymbol(math, rel, "\u2270", "\\nleqq"); defineSymbol(math, rel, "\u2270", "\\nleqslant"); defineSymbol(math, rel, "\u2aeb", "\\Perp", true); //cmll package defineSymbol(math, spacing, "\u00a0", "\\ "); defineSymbol(math, spacing, "\u00a0", "\\space"); // Ref: LaTeX Source 2e: \DeclareRobustCommand{\nobreakspace}{% defineSymbol(math, spacing, "\u00a0", "\\nobreakspace"); defineSymbol(text, spacing, "\u00a0", "\\ "); defineSymbol(text, spacing, "\u00a0", " "); defineSymbol(text, spacing, "\u00a0", "\\space"); defineSymbol(text, spacing, "\u00a0", "\\nobreakspace"); defineSymbol(math, spacing, null, "\\nobreak"); defineSymbol(math, spacing, null, "\\allowbreak"); defineSymbol(math, punct, ",", ","); defineSymbol(text, punct, ":", ":"); defineSymbol(math, punct, ";", ";"); defineSymbol(math, bin, "\u22bc", "\\barwedge", true); defineSymbol(math, bin, "\u22bb", "\\veebar", true); defineSymbol(math, bin, "\u2299", "\\odot", true); // Firefox turns ⊕ into an emoji. So append \uFE0E. Define Unicode character in macros, not here. defineSymbol(math, bin, "\u2295\uFE0E", "\\oplus"); defineSymbol(math, bin, "\u2297", "\\otimes", true); defineSymbol(math, textord, "\u2202", "\\partial", true); defineSymbol(math, bin, "\u2298", "\\oslash", true); defineSymbol(math, bin, "\u229a", "\\circledcirc", true); defineSymbol(math, bin, "\u22a1", "\\boxdot", true); defineSymbol(math, bin, "\u25b3", "\\bigtriangleup"); defineSymbol(math, bin, "\u25bd", "\\bigtriangledown"); defineSymbol(math, bin, "\u2020", "\\dagger"); defineSymbol(math, bin, "\u22c4", "\\diamond"); defineSymbol(math, bin, "\u22c6", "\\star"); defineSymbol(math, bin, "\u25c3", "\\triangleleft"); defineSymbol(math, bin, "\u25b9", "\\triangleright"); defineSymbol(math, open, "{", "\\{"); defineSymbol(text, textord, "{", "\\{"); defineSymbol(text, textord, "{", "\\textbraceleft"); defineSymbol(math, close, "}", "\\}"); defineSymbol(text, textord, "}", "\\}"); defineSymbol(text, textord, "}", "\\textbraceright"); defineSymbol(math, open, "{", "\\lbrace"); defineSymbol(math, close, "}", "\\rbrace"); defineSymbol(math, open, "[", "\\lbrack", true); defineSymbol(text, textord, "[", "\\lbrack", true); defineSymbol(math, close, "]", "\\rbrack", true); defineSymbol(text, textord, "]", "\\rbrack", true); defineSymbol(math, open, "(", "\\lparen", true); defineSymbol(math, close, ")", "\\rparen", true); defineSymbol(math, open, "⦇", "\\llparenthesis", true); defineSymbol(math, close, "⦈", "\\rrparenthesis", true); defineSymbol(text, textord, "<", "\\textless", true); // in T1 fontenc defineSymbol(text, textord, ">", "\\textgreater", true); // in T1 fontenc defineSymbol(math, open, "\u230a", "\\lfloor", true); defineSymbol(math, close, "\u230b", "\\rfloor", true); defineSymbol(math, open, "\u2308", "\\lceil", true); defineSymbol(math, close, "\u2309", "\\rceil", true); defineSymbol(math, textord, "\\", "\\backslash"); defineSymbol(math, textord, "|", "|"); defineSymbol(math, textord, "|", "\\vert"); defineSymbol(text, textord, "|", "\\textbar", true); // in T1 fontenc defineSymbol(math, textord, "\u2016", "\\|"); defineSymbol(math, textord, "\u2016", "\\Vert"); defineSymbol(text, textord, "\u2016", "\\textbardbl"); defineSymbol(text, textord, "~", "\\textasciitilde"); defineSymbol(text, textord, "\\", "\\textbackslash"); defineSymbol(text, textord, "^", "\\textasciicircum"); defineSymbol(math, rel, "\u2191", "\\uparrow", true); defineSymbol(math, rel, "\u21d1", "\\Uparrow", true); defineSymbol(math, rel, "\u2193", "\\downarrow", true); defineSymbol(math, rel, "\u21d3", "\\Downarrow", true); defineSymbol(math, rel, "\u2195", "\\updownarrow", true); defineSymbol(math, rel, "\u21d5", "\\Updownarrow", true); defineSymbol(math, op, "\u2210", "\\coprod"); defineSymbol(math, op, "\u22c1", "\\bigvee"); defineSymbol(math, op, "\u22c0", "\\bigwedge"); defineSymbol(math, op, "\u2a04", "\\biguplus"); defineSymbol(math, op, "\u22c2", "\\bigcap"); defineSymbol(math, op, "\u22c3", "\\bigcup"); defineSymbol(math, op, "\u222b", "\\int"); defineSymbol(math, op, "\u222b", "\\intop"); defineSymbol(math, op, "\u222c", "\\iint"); defineSymbol(math, op, "\u222d", "\\iiint"); defineSymbol(math, op, "\u220f", "\\prod"); defineSymbol(math, op, "\u2211", "\\sum"); defineSymbol(math, op, "\u2a02", "\\bigotimes"); defineSymbol(math, op, "\u2a01", "\\bigoplus"); defineSymbol(math, op, "\u2a00", "\\bigodot"); defineSymbol(math, op, "\u2a09", "\\bigtimes"); defineSymbol(math, op, "\u222e", "\\oint"); defineSymbol(math, op, "\u222f", "\\oiint"); defineSymbol(math, op, "\u2230", "\\oiiint"); defineSymbol(math, op, "\u2231", "\\intclockwise"); defineSymbol(math, op, "\u2232", "\\varointclockwise"); defineSymbol(math, op, "\u2a0c", "\\iiiint"); defineSymbol(math, op, "\u2a0d", "\\intbar"); defineSymbol(math, op, "\u2a0e", "\\intBar"); defineSymbol(math, op, "\u2a0f", "\\fint"); defineSymbol(math, op, "\u2a12", "\\rppolint"); defineSymbol(math, op, "\u2a13", "\\scpolint"); defineSymbol(math, op, "\u2a15", "\\pointint"); defineSymbol(math, op, "\u2a16", "\\sqint"); defineSymbol(math, op, "\u2a17", "\\intlarhk"); defineSymbol(math, op, "\u2a18", "\\intx"); defineSymbol(math, op, "\u2a19", "\\intcap"); defineSymbol(math, op, "\u2a1a", "\\intcup"); defineSymbol(math, op, "\u2a05", "\\bigsqcap"); defineSymbol(math, op, "\u2a06", "\\bigsqcup"); defineSymbol(math, op, "\u222b", "\\smallint"); defineSymbol(text, inner, "\u2026", "\\textellipsis"); defineSymbol(math, inner, "\u2026", "\\mathellipsis"); defineSymbol(text, inner, "\u2026", "\\ldots", true); defineSymbol(math, inner, "\u2026", "\\ldots", true); defineSymbol(math, inner, "\u22f0", "\\iddots", true); defineSymbol(math, inner, "\u22ef", "\\@cdots", true); defineSymbol(math, inner, "\u22f1", "\\ddots", true); defineSymbol(math, textord, "\u22ee", "\\varvdots"); // \vdots is a macro defineSymbol(math, accent, "\u02ca", "\\acute"); defineSymbol(math, accent, "\u0060", "\\grave"); defineSymbol(math, accent, "\u00a8", "\\ddot"); defineSymbol(math, accent, "\u2026", "\\dddot"); defineSymbol(math, accent, "\u2026\u002e", "\\ddddot"); defineSymbol(math, accent, "\u007e", "\\tilde"); defineSymbol(math, accent, "\u203e", "\\bar"); defineSymbol(math, accent, "\u02d8", "\\breve"); defineSymbol(math, accent, "\u02c7", "\\check"); defineSymbol(math, accent, "\u005e", "\\hat"); defineSymbol(math, accent, "\u2192", "\\vec"); defineSymbol(math, accent, "\u02d9", "\\dot"); defineSymbol(math, accent, "\u02da", "\\mathring"); defineSymbol(math, mathord, "\u0131", "\\imath", true); defineSymbol(math, mathord, "\u0237", "\\jmath", true); defineSymbol(math, textord, "\u0131", "\u0131"); defineSymbol(math, textord, "\u0237", "\u0237"); defineSymbol(text, textord, "\u0131", "\\i", true); defineSymbol(text, textord, "\u0237", "\\j", true); defineSymbol(text, textord, "\u00df", "\\ss", true); defineSymbol(text, textord, "\u00e6", "\\ae", true); defineSymbol(text, textord, "\u0153", "\\oe", true); defineSymbol(text, textord, "\u00f8", "\\o", true); defineSymbol(math, mathord, "\u00f8", "\\o", true); defineSymbol(text, textord, "\u00c6", "\\AE", true); defineSymbol(text, textord, "\u0152", "\\OE", true); defineSymbol(text, textord, "\u00d8", "\\O", true); defineSymbol(math, mathord, "\u00d8", "\\O", true); defineSymbol(text, accent, "\u02ca", "\\'"); // acute defineSymbol(text, accent, "\u02cb", "\\`"); // grave defineSymbol(text, accent, "\u02c6", "\\^"); // circumflex defineSymbol(text, accent, "\u02dc", "\\~"); // tilde defineSymbol(text, accent, "\u02c9", "\\="); // macron defineSymbol(text, accent, "\u02d8", "\\u"); // breve defineSymbol(text, accent, "\u02d9", "\\."); // dot above defineSymbol(text, accent, "\u00b8", "\\c"); // cedilla defineSymbol(text, accent, "\u02da", "\\r"); // ring above defineSymbol(text, accent, "\u02c7", "\\v"); // caron defineSymbol(text, accent, "\u00a8", '\\"'); // diaresis defineSymbol(text, accent, "\u02dd", "\\H"); // double acute defineSymbol(math, accent, "\u02ca", "\\'"); // acute defineSymbol(math, accent, "\u02cb", "\\`"); // grave defineSymbol(math, accent, "\u02c6", "\\^"); // circumflex defineSymbol(math, accent, "\u02dc", "\\~"); // tilde defineSymbol(math, accent, "\u02c9", "\\="); // macron defineSymbol(math, accent, "\u02d8", "\\u"); // breve defineSymbol(math, accent, "\u02d9", "\\."); // dot above defineSymbol(math, accent, "\u00b8", "\\c"); // cedilla defineSymbol(math, accent, "\u02da", "\\r"); // ring above defineSymbol(math, accent, "\u02c7", "\\v"); // caron defineSymbol(math, accent, "\u00a8", '\\"'); // diaresis defineSymbol(math, accent, "\u02dd", "\\H"); // double acute // These ligatures are detected and created in Parser.js's `formLigatures`. const ligatures = { "--": true, "---": true, "``": true, "''": true }; defineSymbol(text, textord, "\u2013", "--", true); defineSymbol(text, textord, "\u2013", "\\textendash"); defineSymbol(text, textord, "\u2014", "---", true); defineSymbol(text, textord, "\u2014", "\\textemdash"); defineSymbol(text, textord, "\u2018", "`", true); defineSymbol(text, textord, "\u2018", "\\textquoteleft"); defineSymbol(text, textord, "\u2019", "'", true); defineSymbol(text, textord, "\u2019", "\\textquoteright"); defineSymbol(text, textord, "\u201c", "``", true); defineSymbol(text, textord, "\u201c", "\\textquotedblleft"); defineSymbol(text, textord, "\u201d", "''", true); defineSymbol(text, textord, "\u201d", "\\textquotedblright"); // \degree from gensymb package defineSymbol(math, textord, "\u00b0", "\\degree", true); defineSymbol(text, textord, "\u00b0", "\\degree"); // \textdegree from inputenc package defineSymbol(text, 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, textord, "\u00a3", "\\pounds"); defineSymbol(math, textord, "\u00a3", "\\mathsterling", true); defineSymbol(text, textord, "\u00a3", "\\pounds"); defineSymbol(text, textord, "\u00a3", "\\textsterling", true); defineSymbol(math, textord, "\u2720", "\\maltese"); defineSymbol(text, textord, "\u2720", "\\maltese"); defineSymbol(math, textord, "\u20ac", "\\euro", true); defineSymbol(text, textord, "\u20ac", "\\euro", true); defineSymbol(text, textord, "\u20ac", "\\texteuro"); defineSymbol(math, textord, "\u00a9", "\\copyright", true); defineSymbol(text, textord, "\u00a9", "\\textcopyright"); defineSymbol(math, textord, "\u2300", "\\diameter", true); defineSymbol(text, textord, "\u2300", "\\diameter"); // Italic Greek defineSymbol(math, textord, "𝛤", "\\varGamma"); defineSymbol(math, textord, "𝛥", "\\varDelta"); defineSymbol(math, textord, "𝛩", "\\varTheta"); defineSymbol(math, textord, "𝛬", "\\varLambda"); defineSymbol(math, textord, "𝛯", "\\varXi"); defineSymbol(math, textord, "𝛱", "\\varPi"); defineSymbol(math, textord, "𝛴", "\\varSigma"); defineSymbol(math, textord, "𝛶", "\\varUpsilon"); defineSymbol(math, textord, "𝛷", "\\varPhi"); defineSymbol(math, textord, "𝛹", "\\varPsi"); defineSymbol(math, textord, "𝛺", "\\varOmega"); defineSymbol(text, textord, "𝛤", "\\varGamma"); defineSymbol(text, textord, "𝛥", "\\varDelta"); defineSymbol(text, textord, "𝛩", "\\varTheta"); defineSymbol(text, textord, "𝛬", "\\varLambda"); defineSymbol(text, textord, "𝛯", "\\varXi"); defineSymbol(text, textord, "𝛱", "\\varPi"); defineSymbol(text, textord, "𝛴", "\\varSigma"); defineSymbol(text, textord, "𝛶", "\\varUpsilon"); defineSymbol(text, textord, "𝛷", "\\varPhi"); defineSymbol(text, textord, "𝛹", "\\varPsi"); defineSymbol(text, textord, "𝛺", "\\varOmega"); // There are lots of symbols which are the same, so we add them in afterwards. // All of these are textords in math mode const mathTextSymbols = '0123456789/@."'; for (let i = 0; i < mathTextSymbols.length; i++) { const ch = mathTextSymbols.charAt(i); defineSymbol(math, textord, ch, ch); } // All of these are textords in text mode const textSymbols = '0123456789!@*()-=+";:?/.,'; for (let i = 0; i < textSymbols.length; i++) { const ch = textSymbols.charAt(i); defineSymbol(text, textord, ch, ch); } // All of these are textords in text mode, and mathords in math mode const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; for (let i = 0; i < letters.length; i++) { const ch = letters.charAt(i); defineSymbol(math, mathord, ch, ch); defineSymbol(text, textord, ch, ch); } // Some more letters in Unicode Basic Multilingual Plane. const narrow = "ÇÐÞçþℂℍℕℙℚℝℤℎℏℊℋℌℐℑℒℓ℘ℛℜℬℰℱℳℭℨ"; for (let i = 0; i < narrow.length; i++) { const ch = narrow.charAt(i); defineSymbol(math, mathord, ch, ch); defineSymbol(text, textord, ch, ch); } // The next loop loads wide (surrogate pair) characters. // We support some letters in the Unicode range U+1D400 to U+1D7FF, // Mathematical Alphanumeric Symbols. let wideChar = ""; for (let i = 0; i < letters.length; i++) { // 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 + i); // A-Z a-z bold defineSymbol(math, mathord, wideChar, wideChar); defineSymbol(text, textord, wideChar, wideChar); wideChar = String.fromCharCode(0xd835, 0xdc34 + i); // A-Z a-z italic defineSymbol(math, mathord, wideChar, wideChar); defineSymbol(text, textord, wideChar, wideChar); wideChar = String.fromCharCode(0xd835, 0xdc68 + i); // A-Z a-z bold italic defineSymbol(math, mathord, wideChar, wideChar); defineSymbol(text, textord, wideChar, wideChar); wideChar = String.fromCharCode(0xd835, 0xdd04 + i); // A-Z a-z Fractur defineSymbol(math, mathord, wideChar, wideChar); defineSymbol(text, textord, wideChar, wideChar); wideChar = String.fromCharCode(0xd835, 0xdda0 + i); // A-Z a-z sans-serif defineSymbol(math, mathord, wideChar, wideChar); defineSymbol(text, textord, wideChar, wideChar); wideChar = String.fromCharCode(0xd835, 0xddd4 + i); // A-Z a-z sans bold defineSymbol(math, mathord, wideChar, wideChar); defineSymbol(text, textord, wideChar, wideChar); wideChar = String.fromCharCode(0xd835, 0xde08 + i); // A-Z a-z sans italic defineSymbol(math, mathord, wideChar, wideChar); defineSymbol(text, textord, wideChar, wideChar); wideChar = String.fromCharCode(0xd835, 0xde70 + i); // A-Z a-z monospace defineSymbol(math, mathord, wideChar, wideChar); defineSymbol(text, textord, wideChar, wideChar); wideChar = String.fromCharCode(0xd835, 0xdd38 + i); // A-Z a-z double struck defineSymbol(math, mathord, wideChar, wideChar); defineSymbol(text, textord, wideChar, wideChar); const ch = letters.charAt(i); wideChar = String.fromCharCode(0xd835, 0xdc9c + i); // A-Z a-z calligraphic defineSymbol(math, mathord, ch, wideChar); defineSymbol(text, textord, ch, wideChar); } // Next, some wide character numerals for (let i = 0; i < 10; i++) { wideChar = String.fromCharCode(0xd835, 0xdfce + i); // 0-9 bold defineSymbol(math, mathord, wideChar, wideChar); defineSymbol(text, textord, wideChar, wideChar); wideChar = String.fromCharCode(0xd835, 0xdfe2 + i); // 0-9 sans serif defineSymbol(math, mathord, wideChar, wideChar); defineSymbol(text, textord, wideChar, wideChar); wideChar = String.fromCharCode(0xd835, 0xdfec + i); // 0-9 bold sans defineSymbol(math, mathord, wideChar, wideChar); defineSymbol(text, textord, wideChar, wideChar); wideChar = String.fromCharCode(0xd835, 0xdff6 + i); // 0-9 monospace defineSymbol(math, mathord, wideChar, wideChar); defineSymbol(text, textord, wideChar, wideChar); } /* * Neither Firefox nor Chrome support hard line breaks or soft line breaks. * (Despite https://www.w3.org/Math/draft-spec/mathml.html#chapter3_presm.lbattrs) * So Temml has work-arounds for both hard and soft breaks. * The work-arounds sadly do not work simultaneously. Any top-level hard * break makes soft line breaks impossible. * * Hard breaks are simulated by creating a and putting each line in its own . * * To create soft line breaks, Temml avoids using the and tags. * Then the top level of a element can be occupied by elements, and the browser * will break after a if the expression extends beyond the container limit. * * The default is for soft line breaks after each top-level binary or * relational operator, per TeXbook p. 173. So we gather the expression into s so that * each ends in a binary or relational operator. * * An option is for soft line breaks before an "=" sign. That changes the s. * * Soft line breaks will not work in Chromium and Safari, only Firefox. * * Hopefully browsers will someday do their own linebreaking and we will be able to delete * much of this module. */ const openDelims = "([{⌊⌈⟨⟮⎰⟦⦃"; const closeDelims = ")]}⌋⌉⟩⟯⎱⟦⦄"; function setLineBreaks(expression, wrapMode, isDisplayMode) { const mtrs = []; let mrows = []; let block = []; let numTopLevelEquals = 0; let i = 0; let level = 0; while (i < expression.length) { while (expression[i] instanceof DocumentFragment) { expression.splice(i, 1, ...expression[i].children); // Expand the fragment. } const node = expression[i]; if (node.attributes && node.attributes.linebreak && node.attributes.linebreak === "newline") { // A hard line break. Create a for the current block. if (block.length > 0) { mrows.push(new mathMLTree.MathNode("mrow", block)); } mrows.push(node); block = []; const mtd = new mathMLTree.MathNode("mtd", mrows); mtd.style.textAlign = "left"; mtrs.push(new mathMLTree.MathNode("mtr", [mtd])); mrows = []; i += 1; continue } block.push(node); if (node.type && node.type === "mo" && node.children.length === 1 && !Object.hasOwn(node.attributes, "movablelimits")) { const ch = node.children[0].text; if (openDelims.indexOf(ch) > -1) { level += 1; } else if (closeDelims.indexOf(ch) > -1) { level -= 1; } else if (level === 0 && wrapMode === "=" && ch === "=") { numTopLevelEquals += 1; if (numTopLevelEquals > 1) { block.pop(); // Start a new block. (Insert a soft linebreak.) const element = new mathMLTree.MathNode("mrow", block); mrows.push(element); block = [node]; } } else if (level === 0 && wrapMode === "tex" && ch !== "∇") { // Check if the following node is a \nobreak text node, e.g. "~"" const next = i < expression.length - 1 ? expression[i + 1] : null; let glueIsFreeOfNobreak = true; if ( !( next && next.type === "mtext" && next.attributes.linebreak && next.attributes.linebreak === "nobreak" ) ) { // We may need to start a new block. // First, put any post-operator glue on same line as operator. for (let j = i + 1; j < expression.length; j++) { const nd = expression[j]; if ( nd.type && nd.type === "mspace" && !(nd.attributes.linebreak && nd.attributes.linebreak === "newline") ) { block.push(nd); i += 1; if ( nd.attributes && nd.attributes.linebreak && nd.attributes.linebreak === "nobreak" ) { glueIsFreeOfNobreak = false; } } else { break; } } } if (glueIsFreeOfNobreak) { // Start a new block. (Insert a soft linebreak.) const element = new mathMLTree.MathNode("mrow", block); mrows.push(element); block = []; } } } i += 1; } if (block.length > 0) { const element = new mathMLTree.MathNode("mrow", block); mrows.push(element); } if (mtrs.length > 0) { const mtd = new mathMLTree.MathNode("mtd", mrows); mtd.style.textAlign = "left"; const mtr = new mathMLTree.MathNode("mtr", [mtd]); mtrs.push(mtr); const mtable = new mathMLTree.MathNode("mtable", mtrs); if (!isDisplayMode) { mtable.setAttribute("columnalign", "left"); mtable.setAttribute("rowspacing", "0em"); } return mtable } return mathMLTree.newDocumentFragment(mrows); } /** * 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. */ const makeText = function(text, mode, style) { if ( symbols[mode][text] && symbols[mode][text].replace && text.charCodeAt(0) !== 0xd835 && !( Object.prototype.hasOwnProperty.call(ligatures, text) && style && ((style.fontFamily && style.fontFamily.slice(4, 6) === "tt") || (style.font && style.font.slice(4, 6) === "tt")) ) ) { text = symbols[mode][text].replace; } return new mathMLTree.TextNode(text); }; const consolidateText = mrow => { // If possible, consolidate adjacent elements into a single element. if (mrow.type !== "mrow" && mrow.type !== "mstyle") { return mrow } if (mrow.children.length === 0) { return mrow } // empty group, e.g., \text{} if (!mrow.children[0].attributes || mrow.children[0].type !== "mtext") { return mrow } const variant = mrow.children[0].attributes.mathvariant || ""; const mtext = new mathMLTree.MathNode( "mtext", [new mathMLTree.TextNode(mrow.children[0].children[0].text)] ); for (let i = 1; i < mrow.children.length; i++) { // Check each child and, if possible, copy the character into child[0]. const localVariant = mrow.children[i].attributes.mathvariant || ""; if (mrow.children[i].type === "mrow") { const childRow = mrow.children[i]; for (let j = 0; j < childRow.children.length; j++) { // We'll also check the children of a mrow. One level only. No recursion. const childVariant = childRow.children[j].attributes.mathvariant || ""; if (childVariant !== variant || childRow.children[j].type !== "mtext") { return mrow // At least one element cannot be consolidated. Get out. } else { mtext.children[0].text += childRow.children[j].children[0].text; } } } else if (localVariant !== variant || mrow.children[i].type !== "mtext") { return mrow } else { mtext.children[0].text += mrow.children[i].children[0].text; } } // Firefox does not render a space at either end of an string. // To get proper rendering, we replace leading or trailing spaces with no-break spaces. if (mtext.children[0].text.charAt(0) === " ") { mtext.children[0].text = "\u00a0" + mtext.children[0].text.slice(1); } const L = mtext.children[0].text.length; if (L > 0 && mtext.children[0].text.charAt(L - 1) === " ") { mtext.children[0].text = mtext.children[0].text.slice(0, -1) + "\u00a0"; } for (const [key, value] of Object.entries(mrow.attributes)) { mtext.attributes[key] = value; } return mtext }; const numberRegEx$1 = /^[0-9]$/; const isCommaOrDot = node => { return (node.type === "atom" && node.text === ",") || (node.type === "textord" && node.text === ".") }; const consolidateNumbers = expression => { // Consolidate adjacent numbers. We want to return 1,506.3, // not 1,506.3 if (expression.length < 2) { return } const nums = []; let inNum = false; // Find adjacent numerals for (let i = 0; i < expression.length; i++) { const node = expression[i]; if (node.type === "textord" && numberRegEx$1.test(node.text)) { if (!inNum) { nums.push({ start: i }); } inNum = true; } else { if (inNum) { nums[nums.length - 1].end = i - 1; } inNum = false; } } if (inNum) { nums[nums.length - 1].end = expression.length - 1; } // Determine if numeral groups are separated by a comma or dot. for (let i = nums.length - 1; i > 0; i--) { if (nums[i - 1].end === nums[i].start - 2 && isCommaOrDot(expression[nums[i].start - 1])) { // Merge the two groups. nums[i - 1].end = nums[i].end; nums.splice(i, 1); } } // Consolidate the number nodes for (let i = nums.length - 1; i >= 0; i--) { for (let j = nums[i].start + 1; j <= nums[i].end; j++) { expression[nums[i].start].text += expression[j].text; } expression.splice(nums[i].start + 1, nums[i].end - nums[i].start); // Check if the is followed by a numeric base in a supsub, e.g. the "3" in 123^4 // If so, merge the first into the base. if (expression.length > nums[i].start + 1) { const nextTerm = expression[nums[i].start + 1]; if (nextTerm.type === "supsub" && nextTerm.base && nextTerm.base.type === "textord" && numberRegEx$1.test(nextTerm.base.text)) { nextTerm.base.text = expression[nums[i].start].text + nextTerm.base.text; expression.splice(nums[i].start, 1); } } } }; /** * Wrap the given array of nodes in an node if needed, i.e., * unless the array has length 1. Always returns a single node. */ const makeRow = function(body, semisimple = false) { if (body.length === 1 && !(body[0] instanceof DocumentFragment)) { return body[0]; } else if (!semisimple) { // Suppress spacing on nodes at both ends of the row. if (body[0] instanceof MathNode && body[0].type === "mo" && !body[0].attributes.fence) { body[0].attributes.lspace = "0em"; body[0].attributes.rspace = "0em"; } const end = body.length - 1; if (body[end] instanceof MathNode && body[end].type === "mo" && !body[end].attributes.fence) { body[end].attributes.lspace = "0em"; body[end].attributes.rspace = "0em"; } } return new mathMLTree.MathNode("mrow", body); }; const isRel = item => { return (item.type === "atom" && item.family === "rel") || (item.type === "mclass" && item.mclass === "mrel") }; /** * Takes a list of nodes, builds them, and returns a list of the generated * MathML nodes. Also do a couple chores along the way: * (1) Suppress spacing when an author wraps an operator w/braces, as in {=}. * (2) Suppress spacing between two adjacent relations. */ const buildExpression = function(expression, style, semisimple = false) { if (!semisimple && expression.length === 1) { const group = buildGroup$1(expression[0], style); if (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]; } consolidateNumbers(expression); const groups = []; for (let i = 0; i < expression.length; i++) { const group = buildGroup$1(expression[i], style); // Suppress spacing between adjacent relations if (i < expression.length - 1 && isRel(expression[i]) && isRel(expression[i + 1])) { group.setAttribute("rspace", "0em"); } if (i > 0 && isRel(expression[i]) && isRel(expression[i - 1])) { group.setAttribute("lspace", "0em"); } groups.push(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. */ const buildExpressionRow = function(expression, style, semisimple = false) { return makeRow(buildExpression(expression, style, semisimple), semisimple); }; /** * Takes a group from the parser and calls the appropriate groupBuilders function * on it to produce a MathML node. */ const buildGroup$1 = function(group, style) { if (!group) { return new mathMLTree.MathNode("mrow"); } if (_mathmlGroupBuilders[group.type]) { // Call the groupBuilders function const result = _mathmlGroupBuilders[group.type](group, style); return result; } else { throw new ParseError("Got group of unknown type: '" + group.type + "'"); } }; const glue$1 = _ => { return new mathMLTree.MathNode("mtd", [], [], { padding: "0", width: "50%" }) }; const taggedExpression = (expression, tag, style, leqno) => { tag = buildExpressionRow(tag[0].body, style); tag = consolidateText(tag); tag.classes.push("tml-tag"); expression = new mathMLTree.MathNode("mtd", [expression]); const rowArray = [glue$1(), expression, glue$1()]; rowArray[leqno ? 0 : 2].classes.push(leqno ? "tml-left" : "tml-right"); rowArray[leqno ? 0 : 2].children.push(tag); const mtr = new mathMLTree.MathNode("mtr", rowArray, ["tml-tageqn"]); const table = new mathMLTree.MathNode("mtable", [mtr]); table.style.width = "100%"; table.setAttribute("displaystyle", "true"); return table }; /** * Takes a full parse tree and settings and builds a MathML representation of * it. */ function buildMathML(tree, texExpression, style, settings) { // Strip off outer tag wrapper for processing below. let tag = null; if (tree.length === 1 && tree[0].type === "tag") { tag = tree[0].tag; tree = tree[0].body; } const expression = buildExpression(tree, style); const wrap = (settings.displayMode || settings.annotate) ? "none" : settings.wrap; const n1 = expression.length === 0 ? null : expression[0]; let wrapper = expression.length === 1 && tag === null && (n1 instanceof MathNode) ? expression[0] : setLineBreaks(expression, wrap, settings.displayMode); if (tag) { wrapper = taggedExpression(wrapper, tag, style, settings.leqno); } if (settings.annotate) { // Build a TeX annotation of the source const annotation = new mathMLTree.MathNode( "annotation", [new mathMLTree.TextNode(texExpression)]); annotation.setAttribute("encoding", "application/x-tex"); wrapper = new mathMLTree.MathNode("semantics", [wrapper, annotation]); } const math = new mathMLTree.MathNode("math", [wrapper]); if (settings.xml) { math.setAttribute("xmlns", "http://www.w3.org/1998/Math/MathML"); } if (settings.displayMode) { math.setAttribute("display", "block"); math.style.display = "block math"; // necessary in Chromium. // Firefox and Safari do not recognize display: "block math". // Set a class so that the CSS file can set display: block. math.classes = ["tml-display"]; } return math; } const smalls = "acegıȷmnopqrsuvwxyzαγεηικμνοπρςστυχωϕ𝐚𝐜𝐞𝐠𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐮𝐯𝐰𝐱𝐲𝐳"; const talls = "ABCDEFGHIJKLMNOPQRSTUVWXYZbdfhkltΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩβδλζφθψ" + "𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙𝐛𝐝𝐟𝐡𝐤𝐥𝐭"; const longSmalls = new Set(["\\alpha", "\\gamma", "\\delta", "\\epsilon", "\\eta", "\\iota", "\\kappa", "\\mu", "\\nu", "\\pi", "\\rho", "\\sigma", "\\tau", "\\upsilon", "\\chi", "\\psi", "\\omega", "\\imath", "\\jmath"]); const longTalls = new Set(["\\Gamma", "\\Delta", "\\Sigma", "\\Omega", "\\beta", "\\delta", "\\lambda", "\\theta", "\\psi"]); const mathmlBuilder$a = (group, style) => { const accentNode = group.isStretchy ? stretchy.accentNode(group) : new mathMLTree.MathNode("mo", [makeText(group.label, group.mode)]); if (group.label === "\\vec") { accentNode.style.transform = "scale(0.75) translate(10%, 30%)"; } else { accentNode.style.mathStyle = "normal"; accentNode.style.mathDepth = "0"; if (needWebkitShift.has(group.label) && utils.isCharacterBox(group.base)) { let shift = ""; const ch = group.base.text; if (smalls.indexOf(ch) > -1 || longSmalls.has(ch)) { shift = "tml-xshift"; } if (talls.indexOf(ch) > -1 || longTalls.has(ch)) { shift = "tml-capshift"; } if (shift) { accentNode.classes.push(shift); } } } if (!group.isStretchy) { accentNode.setAttribute("stretchy", "false"); } const node = new mathMLTree.MathNode((group.label === "\\c" ? "munder" : "mover"), [buildGroup$1(group.base, style), accentNode] ); return node; }; const nonStretchyAccents = new Set([ "\\acute", "\\grave", "\\ddot", "\\dddot", "\\ddddot", "\\tilde", "\\bar", "\\breve", "\\check", "\\hat", "\\vec", "\\dot", "\\mathring" ]); const needWebkitShift = new Set([ "\\acute", "\\bar", "\\breve", "\\check", "\\dot", "\\ddot", "\\grave", "\\hat", "\\mathring", "\\'", "\\^", "\\~", "\\=", "\\u", "\\.", '\\"', "\\r", "\\H", "\\v" ]); // Accents defineFunction({ type: "accent", names: [ "\\acute", "\\grave", "\\ddot", "\\dddot", "\\ddddot", "\\tilde", "\\bar", "\\breve", "\\check", "\\hat", "\\vec", "\\dot", "\\mathring", "\\overparen", "\\widecheck", "\\widehat", "\\wideparen", "\\widetilde", "\\overrightarrow", "\\overleftarrow", "\\Overrightarrow", "\\overleftrightarrow", "\\overgroup", "\\overleftharpoon", "\\overrightharpoon" ], props: { numArgs: 1 }, handler: (context, args) => { const base = normalizeArgument(args[0]); const isStretchy = !nonStretchyAccents.has(context.funcName); return { type: "accent", mode: context.parser.mode, label: context.funcName, isStretchy: isStretchy, base: base }; }, mathmlBuilder: mathmlBuilder$a }); // Text-mode accents defineFunction({ type: "accent", names: ["\\'", "\\`", "\\^", "\\~", "\\=", "\\c", "\\u", "\\.", '\\"', "\\r", "\\H", "\\v"], props: { numArgs: 1, allowedInText: true, allowedInMath: true, argTypes: ["primitive"] }, handler: (context, args) => { const base = normalizeArgument(args[0]); const mode = context.parser.mode; if (mode === "math" && context.parser.settings.strict) { // LaTeX only writes a warning. It doesn't stop. We'll issue the same warning. // eslint-disable-next-line no-console console.log(`Temml parse error: Command ${context.funcName} is invalid in math mode.`); } return { type: "accent", mode: mode, label: context.funcName, isStretchy: false, base: base }; }, mathmlBuilder: mathmlBuilder$a }); defineFunction({ type: "accentUnder", names: [ "\\underleftarrow", "\\underrightarrow", "\\underleftrightarrow", "\\undergroup", "\\underparen", "\\utilde" ], props: { numArgs: 1 }, handler: ({ parser, funcName }, args) => { const base = args[0]; return { type: "accentUnder", mode: parser.mode, label: funcName, base: base }; }, mathmlBuilder: (group, style) => { const accentNode = stretchy.accentNode(group); accentNode.style["math-depth"] = 0; const node = new mathMLTree.MathNode("munder", [ buildGroup$1(group.base, style), accentNode ]); return node; } }); /** * This file does conversion between units. In particular, it provides * calculateSize to convert other units into CSS units. */ const ptPerUnit = { // Convert to CSS (Postscipt) points, not TeX points // https://en.wikibooks.org/wiki/LaTeX/Lengths and // https://tex.stackexchange.com/a/8263 pt: 800 / 803, // convert TeX point to CSS (Postscript) point pc: (12 * 800) / 803, // pica dd: ((1238 / 1157) * 800) / 803, // didot cc: ((14856 / 1157) * 800) / 803, // cicero (12 didot) nd: ((685 / 642) * 800) / 803, // new didot nc: ((1370 / 107) * 800) / 803, // new cicero (12 new didot) sp: ((1 / 65536) * 800) / 803, // scaled point (TeX's internal smallest unit) mm: (25.4 / 72), cm: (2.54 / 72), in: (1 / 72), px: (96 / 72) }; /** * Determine whether the specified unit (either a string defining the unit * or a "size" parse node containing a unit field) is valid. */ const validUnits = [ "em", "ex", "mu", "pt", "mm", "cm", "in", "px", "bp", "pc", "dd", "cc", "nd", "nc", "sp" ]; const validUnit = function(unit) { if (typeof unit !== "string") { unit = unit.unit; } return validUnits.indexOf(unit) > -1 }; const emScale = styleLevel => { const scriptLevel = Math.max(styleLevel - 1, 0); return [1, 0.7, 0.5][scriptLevel] }; /* * Convert a "size" parse node (with numeric "number" and string "unit" fields, * as parsed by functions.js argType "size") into a CSS value. */ const calculateSize = function(sizeValue, style) { let number = sizeValue.number; if (style.maxSize[0] < 0 && number > 0) { return { number: 0, unit: "em" } } const unit = sizeValue.unit; switch (unit) { case "mm": case "cm": case "in": case "px": { const numInCssPts = number * ptPerUnit[unit]; if (numInCssPts > style.maxSize[1]) { return { number: style.maxSize[1], unit: "pt" } } return { number, unit }; // absolute CSS units. } case "em": case "ex": { // In TeX, em and ex do not change size in \scriptstyle. if (unit === "ex") { number *= 0.431; } number = Math.min(number / emScale(style.level), style.maxSize[0]); return { number: utils.round(number), unit: "em" }; } case "bp": { if (number > style.maxSize[1]) { number = style.maxSize[1]; } return { number, unit: "pt" }; // TeX bp is a CSS pt. (1/72 inch). } case "pt": case "pc": case "dd": case "cc": case "nd": case "nc": case "sp": { number = Math.min(number * ptPerUnit[unit], style.maxSize[1]); return { number: utils.round(number), unit: "pt" } } case "mu": { number = Math.min(number / 18, style.maxSize[0]); return { number: utils.round(number), unit: "em" } } default: throw new ParseError("Invalid unit: '" + unit + "'") } }; // Helper functions const padding$2 = width => { const node = new mathMLTree.MathNode("mspace"); node.setAttribute("width", width + "em"); return node }; const paddedNode = (group, lspace = 0.3, rspace = 0) => { if (group == null && rspace === 0) { return padding$2(lspace) } const row = group ? [group] : []; if (lspace !== 0) { row.unshift(padding$2(lspace)); } if (rspace > 0) { row.push(padding$2(rspace)); } return new mathMLTree.MathNode("mrow", row) }; const labelSize = (size, scriptLevel) => Number(size) / emScale(scriptLevel); const munderoverNode = (fName, body, below, style) => { const arrowNode = stretchy.mathMLnode(fName); // Is this the short part of a mhchem equilibrium arrow? const isEq = fName.slice(1, 3) === "eq"; const minWidth = fName.charAt(1) === "x" ? "1.75" // mathtools extensible arrows are ≥ 1.75em long : fName.slice(2, 4) === "cd" ? "3.0" // cd package arrows : isEq ? "1.0" // The shorter harpoon of a mhchem equilibrium arrow : "2.0"; // other mhchem arrows // TODO: When Firefox supports minsize, use the next line. //arrowNode.setAttribute("minsize", String(minWidth) + "em") arrowNode.setAttribute("lspace", "0"); arrowNode.setAttribute("rspace", (isEq ? "0.5em" : "0")); // upper and lower labels are set to scriptlevel by MathML // So we have to adjust our label dimensions accordingly. const labelStyle = style.withLevel(style.level < 2 ? 2 : 3); const minArrowWidth = labelSize(minWidth, labelStyle.level); // The dummyNode will be inside a inside a // So it will be at scriptlevel 3 const dummyWidth = labelSize(minWidth, 3); const emptyLabel = paddedNode(null, minArrowWidth.toFixed(4), 0); const dummyNode = paddedNode(null, dummyWidth.toFixed(4), 0); // The arrow is a little longer than the label. Set a spacer length. const space = labelSize((isEq ? 0 : 0.3), labelStyle.level).toFixed(4); let upperNode; let lowerNode; const gotUpper = (body && body.body && // \hphantom visible content (body.body.body || body.body.length > 0)); if (gotUpper) { let label = buildGroup$1(body, labelStyle); label = paddedNode(label, space, space); // Since Firefox does not support minsize, stack a invisible node // on top of the label. Its width will serve as a min-width. // TODO: Refactor this after Firefox supports minsize. upperNode = new mathMLTree.MathNode("mover", [label, dummyNode]); } const gotLower = (below && below.body && (below.body.body || below.body.length > 0)); if (gotLower) { let label = buildGroup$1(below, labelStyle); label = paddedNode(label, space, space); lowerNode = new mathMLTree.MathNode("munder", [label, dummyNode]); } let node; if (!gotUpper && !gotLower) { node = new mathMLTree.MathNode("mover", [arrowNode, emptyLabel]); } else if (gotUpper && gotLower) { node = new mathMLTree.MathNode("munderover", [arrowNode, lowerNode, upperNode]); } else if (gotUpper) { node = new mathMLTree.MathNode("mover", [arrowNode, upperNode]); } else { node = new mathMLTree.MathNode("munder", [arrowNode, lowerNode]); } if (minWidth === "3.0") { node.style.height = "1em"; } // CD environment node.setAttribute("accent", "false"); // Necessary for MS Word 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", "\\xlongequal", "\\xtwoheadrightarrow", "\\xtwoheadleftarrow", // The next 5 functions are here only to support mhchem "\\yields", "\\yieldsLeft", "\\mesomerism", "\\longrightharpoonup", "\\longleftharpoondown", // The next 3 functions are here only to support the {CD} environment. "\\\\cdrightarrow", "\\\\cdleftarrow", "\\\\cdlongequal" ], props: { numArgs: 1, numOptionalArgs: 1 }, handler({ parser, funcName }, args, optArgs) { return { type: "xArrow", mode: parser.mode, name: funcName, body: args[0], below: optArgs[0] }; }, mathmlBuilder(group, style) { // Build the arrow and its labels. const node = munderoverNode(group.name, group.body, group.below, style); // Create operator spacing for a relation. const row = [node]; row.unshift(padding$2(0.2778)); row.push(padding$2(0.2778)); return new mathMLTree.MathNode("mrow", row) } }); const arrowComponent = { "\\xtofrom": ["\\xrightarrow", "\\xleftarrow"], "\\xleftrightharpoons": ["\\xleftharpoonup", "\\xrightharpoondown"], "\\xrightleftharpoons": ["\\xrightharpoonup", "\\xleftharpoondown"], "\\yieldsLeftRight": ["\\yields", "\\yieldsLeft"], // The next three all get the same harpoon glyphs. Only the lengths and paddings differ. "\\equilibrium": ["\\longrightharpoonup", "\\longleftharpoondown"], "\\equilibriumRight": ["\\longrightharpoonup", "\\eqleftharpoondown"], "\\equilibriumLeft": ["\\eqrightharpoonup", "\\longleftharpoondown"] }; // Browsers are not good at stretching a glyph that contains a pair of stacked arrows such as ⇄. // So we stack a pair of single arrows. defineFunction({ type: "stackedArrow", names: [ "\\xtofrom", // expfeil "\\xleftrightharpoons", // mathtools "\\xrightleftharpoons", // mathtools "\\yieldsLeftRight", // mhchem "\\equilibrium", // mhchem "\\equilibriumRight", "\\equilibriumLeft" ], props: { numArgs: 1, numOptionalArgs: 1 }, handler({ parser, funcName }, args, optArgs) { const lowerArrowBody = args[0] ? { type: "hphantom", mode: parser.mode, body: args[0] } : null; const upperArrowBelow = optArgs[0] ? { type: "hphantom", mode: parser.mode, body: optArgs[0] } : null; return { type: "stackedArrow", mode: parser.mode, name: funcName, body: args[0], upperArrowBelow, lowerArrowBody, below: optArgs[0] }; }, mathmlBuilder(group, style) { const topLabel = arrowComponent[group.name][0]; const botLabel = arrowComponent[group.name][1]; const topArrow = munderoverNode(topLabel, group.body, group.upperArrowBelow, style); const botArrow = munderoverNode(botLabel, group.lowerArrowBody, group.below, style); let wrapper; const raiseNode = new mathMLTree.MathNode("mpadded", [topArrow]); raiseNode.setAttribute("voffset", "0.3em"); raiseNode.setAttribute("height", "+0.3em"); raiseNode.setAttribute("depth", "-0.3em"); // One of the arrows is given ~zero width. so the other has the same horzontal alignment. if (group.name === "\\equilibriumLeft") { const botNode = new mathMLTree.MathNode("mpadded", [botArrow]); botNode.setAttribute("width", "0.5em"); wrapper = new mathMLTree.MathNode( "mpadded", [padding$2(0.2778), botNode, raiseNode, padding$2(0.2778)] ); } else { raiseNode.setAttribute("width", (group.name === "\\equilibriumRight" ? "0.5em" : "0")); wrapper = new mathMLTree.MathNode( "mpadded", [padding$2(0.2778), raiseNode, botArrow, padding$2(0.2778)] ); } wrapper.setAttribute("voffset", "-0.18em"); wrapper.setAttribute("height", "-0.18em"); wrapper.setAttribute("depth", "+0.18em"); return wrapper } }); /** * 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)) ); } return node; } /** * Returns the node more strictly typed iff it is of the given type. Otherwise, * returns null. */ function assertSymbolNodeType(node) { const 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" || Object.prototype.hasOwnProperty.call(NON_ATOMS, node.type))) { return node; } return null; } const cdArrowFunctionName = { ">": "\\\\cdrightarrow", "<": "\\\\cdleftarrow", "=": "\\\\cdlongequal", A: "\\uparrow", V: "\\downarrow", "|": "\\Vert", ".": "no arrow" }; const newCell = () => { // Create an empty cell, to be filled below with parse nodes. return { type: "styling", body: [], mode: "math", scriptLevel: "display" }; }; const isStartOfArrow = (node) => { return node.type === "textord" && node.text === "@"; }; const 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. const funcName = cdArrowFunctionName[arrowChar]; switch (funcName) { case "\\\\cdrightarrow": case "\\\\cdleftarrow": return parser.callFunction(funcName, [labels[0]], [labels[1]]); case "\\uparrow": case "\\downarrow": { const leftLabel = parser.callFunction("\\\\cdleft", [labels[0]], []); const bareArrow = { type: "atom", text: funcName, mode: "math", family: "rel" }; const sizedArrow = parser.callFunction("\\Big", [bareArrow], []); const rightLabel = parser.callFunction("\\\\cdright", [labels[1]], []); const arrowGroup = { type: "ordgroup", mode: "math", body: [leftLabel, sizedArrow, rightLabel], semisimple: true }; return parser.callFunction("\\\\cdparent", [arrowGroup], []); } case "\\\\cdlongequal": return parser.callFunction("\\\\cdlongequal", [], []); case "\\Vert": { const 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. const 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(); const 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); } } let row = []; const body = [row]; // Loop thru the parse nodes. Collect them into cells and arrows. for (let i = 0; i < parsedRows.length; i++) { // Start a new row. const rowNodes = parsedRows[i]; // Create the first cell. let cell = newCell(); for (let 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; const arrowChar = assertSymbolNodeType(rowNodes[j]).text; // Create two empty label nodes. We may or may not use them. const 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 (let labelNum = 0; labelNum < 2; labelNum++) { let inLabel = true; for (let 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 @.`); } // Now join the arrow to its labels. const arrow = cdArrow(arrowChar, labels, parser); // Wrap the arrow in a styling node row.push(arrow); // 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); } body.pop(); // End row group parser.gullet.endGroup(); // End array group defining \\ parser.gullet.endGroup(); return { type: "array", mode: "math", body, envClasses: ["jot", "cd"], cols: [], 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({ parser, funcName }, args) { return { type: "cdlabel", mode: parser.mode, side: funcName.slice(4), label: args[0] }; }, mathmlBuilder(group, style) { let label = new mathMLTree.MathNode("mrow", [buildGroup$1(group.label, style)]); 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({ parser }, args) { return { type: "cdlabelparent", mode: parser.mode, fragment: args[0] }; }, mathmlBuilder(group, style) { return new mathMLTree.MathNode("mrow", [buildGroup$1(group.fragment, style)]); } }); // \@char is an internal function that takes a grouped decimal argument like // {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({ parser, token }, args) { const arg = assertNodeType(args[0], "ordgroup"); const group = arg.body; let number = ""; for (let i = 0; i < group.length; i++) { const node = assertNodeType(group[i], "textord"); number += node.text; } const code = parseInt(number); if (isNaN(code)) { throw new ParseError(`\\@char has non-numeric argument ${number}`, token) } return { type: "textord", mode: parser.mode, text: String.fromCodePoint(code) } } }); // Helpers const htmlRegEx = /^(#[a-f0-9]{3}|#?[a-f0-9]{6})$/i; const htmlOrNameRegEx = /^(#[a-f0-9]{3}|#?[a-f0-9]{6}|[a-z]+)$/i; const RGBregEx = /^ *\d{1,3} *(?:, *\d{1,3} *){2}$/; const rgbRegEx = /^ *[10](?:\.\d*)? *(?:, *[10](?:\.\d*)? *){2}$/; const xcolorHtmlRegEx = /^[a-f0-9]{6}$/i; const toHex = num => { let str = num.toString(16); if (str.length === 1) { str = "0" + str; } return str }; // Colors from Tables 4.1 and 4.2 of the xcolor package. // Table 4.1 (lower case) RGB values are taken from chroma and xcolor.dtx. // Table 4.2 (Capitalizzed) values were sampled, because Chroma contains a unreliable // conversion from cmyk to RGB. See https://tex.stackexchange.com/a/537274. const xcolors = JSON.parse(`{ "Apricot": "#ffb484", "Aquamarine": "#08b4bc", "Bittersweet": "#c84c14", "blue": "#0000FF", "Blue": "#303494", "BlueGreen": "#08b4bc", "BlueViolet": "#503c94", "BrickRed": "#b8341c", "brown": "#BF8040", "Brown": "#802404", "BurntOrange": "#f8941c", "CadetBlue": "#78749c", "CarnationPink": "#f884b4", "Cerulean": "#08a4e4", "CornflowerBlue": "#40ace4", "cyan": "#00FFFF", "Cyan": "#08acec", "Dandelion": "#ffbc44", "darkgray": "#404040", "DarkOrchid": "#a8548c", "Emerald": "#08ac9c", "ForestGreen": "#089c54", "Fuchsia": "#90348c", "Goldenrod": "#ffdc44", "gray": "#808080", "Gray": "#98949c", "green": "#00FF00", "Green": "#08a44c", "GreenYellow": "#e0e474", "JungleGreen": "#08ac9c", "Lavender": "#f89cc4", "lightgray": "#c0c0c0", "lime": "#BFFF00", "LimeGreen": "#90c43c", "magenta": "#FF00FF", "Magenta": "#f0048c", "Mahogany": "#b0341c", "Maroon": "#b03434", "Melon": "#f89c7c", "MidnightBlue": "#086494", "Mulberry": "#b03c94", "NavyBlue": "#086cbc", "olive": "#7F7F00", "OliveGreen": "#407c34", "orange": "#FF8000", "Orange": "#f8843c", "OrangeRed": "#f0145c", "Orchid": "#b074ac", "Peach": "#f8945c", "Periwinkle": "#8074bc", "PineGreen": "#088c74", "pink": "#ff7f7f", "Plum": "#98248c", "ProcessBlue": "#08b4ec", "purple": "#BF0040", "Purple": "#a0449c", "RawSienna": "#983c04", "red": "#ff0000", "Red": "#f01c24", "RedOrange": "#f86434", "RedViolet": "#a0246c", "Rhodamine": "#f0549c", "Royallue": "#0874bc", "RoyalPurple": "#683c9c", "RubineRed": "#f0047c", "Salmon": "#f8948c", "SeaGreen": "#30bc9c", "Sepia": "#701404", "SkyBlue": "#48c4dc", "SpringGreen": "#c8dc64", "Tan": "#e09c74", "teal": "#007F7F", "TealBlue": "#08acb4", "Thistle": "#d884b4", "Turquoise": "#08b4cc", "violet": "#800080", "Violet": "#60449c", "VioletRed": "#f054a4", "WildStrawberry": "#f0246c", "yellow": "#FFFF00", "Yellow": "#fff404", "YellowGreen": "#98cc6c", "YellowOrange": "#ffa41c" }`); const colorFromSpec = (model, spec) => { let color = ""; if (model === "HTML") { if (!htmlRegEx.test(spec)) { throw new ParseError("Invalid HTML input.") } color = spec; } else if (model === "RGB") { if (!RGBregEx.test(spec)) { throw new ParseError("Invalid RGB input.") } spec.split(",").map(e => { color += toHex(Number(e.trim())); }); } else { if (!rgbRegEx.test(spec)) { throw new ParseError("Invalid rbg input.") } spec.split(",").map(e => { const num = Number(e.trim()); if (num > 1) { throw new ParseError("Color rgb input must be < 1.") } color += toHex(Number((num * 255).toFixed(0))); }); } if (color.charAt(0) !== "#") { color = "#" + color; } return color }; const validateColor = (color, macros, token) => { const macroName = `\\\\color@${color}`; // from \defineColor. const match = htmlOrNameRegEx.exec(color); if (!match) { throw new ParseError("Invalid color: '" + color + "'", token) } // 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. if (xcolorHtmlRegEx.test(color)) { return "#" + color } else if (color.charAt(0) === "#") { return color } else if (macros.has(macroName)) { color = macros.get(macroName).tokens[0].text; } else if (xcolors[color]) { color = xcolors[color]; } return color }; const mathmlBuilder$9 = (group, style) => { // In LaTeX, color is not supposed to change the spacing of any node. // So instead of wrapping the group in an , we apply // the color individually to each node and return a document fragment. let expr = buildExpression(group.body, style.withColor(group.color)); expr = expr.map(e => { e.style.color = group.color; return e }); return mathMLTree.newDocumentFragment(expr) }; defineFunction({ type: "color", names: ["\\textcolor"], props: { numArgs: 2, numOptionalArgs: 1, allowedInText: true, argTypes: ["raw", "raw", "original"] }, handler({ parser, token }, args, optArgs) { const model = optArgs[0] && assertNodeType(optArgs[0], "raw").string; let color = ""; if (model) { const spec = assertNodeType(args[0], "raw").string; color = colorFromSpec(model, spec); } else { color = validateColor(assertNodeType(args[0], "raw").string, parser.gullet.macros, token); } const body = args[1]; return { type: "color", mode: parser.mode, color, body: ordargument(body) } }, mathmlBuilder: mathmlBuilder$9 }); defineFunction({ type: "color", names: ["\\color"], props: { numArgs: 1, numOptionalArgs: 1, allowedInText: true, argTypes: ["raw", "raw"] }, handler({ parser, breakOnTokenText, token }, args, optArgs) { const model = optArgs[0] && assertNodeType(optArgs[0], "raw").string; let color = ""; if (model) { const spec = assertNodeType(args[0], "raw").string; color = colorFromSpec(model, spec); } else { color = validateColor(assertNodeType(args[0], "raw").string, parser.gullet.macros, token); } // Parse out the implicit body that should be colored. const body = parser.parseExpression(true, breakOnTokenText, true); return { type: "color", mode: parser.mode, color, body } }, mathmlBuilder: mathmlBuilder$9 }); defineFunction({ type: "color", names: ["\\definecolor"], props: { numArgs: 3, allowedInText: true, argTypes: ["raw", "raw", "raw"] }, handler({ parser, funcName, token }, args) { const name = assertNodeType(args[0], "raw").string; if (!/^[A-Za-z]+$/.test(name)) { throw new ParseError("Color name must be latin letters.", token) } const model = assertNodeType(args[1], "raw").string; if (!["HTML", "RGB", "rgb"].includes(model)) { throw new ParseError("Color model must be HTML, RGB, or rgb.", token) } const spec = assertNodeType(args[2], "raw").string; const color = colorFromSpec(model, spec); parser.gullet.macros.set(`\\\\color@${name}`, { tokens: [{ text: color }], numArgs: 0 }); return { type: "internal", mode: parser.mode } } // No mathmlBuilder. The point of \definecolor is to set a macro. }); // Row breaks within tabular environments, and line breaks at top level // \DeclareRobustCommand\\{...\@xnewline} defineFunction({ type: "cr", names: ["\\\\"], props: { numArgs: 0, numOptionalArgs: 0, allowedInText: true }, handler({ parser }, args, optArgs) { const size = parser.gullet.future().text === "[" ? parser.parseSizeGroup(true) : null; const newLine = !parser.settings.displayMode; return { type: "cr", mode: parser.mode, newLine, size: size && assertNodeType(size, "size").value } }, // The following builder is called only at the top level, // not within tabular/array environments. mathmlBuilder(group, style) { // MathML 3.0 calls for newline to occur in an or an . // Ref: https://www.w3.org/TR/MathML3/chapter3.html#presm.linebreaking const node = new mathMLTree.MathNode("mo"); if (group.newLine) { node.setAttribute("linebreak", "newline"); if (group.size) { const size = calculateSize(group.size, style); node.setAttribute("height", size.number + size.unit); } } return node } }); const globalMap = { "\\global": "\\global", "\\long": "\\\\globallong", "\\\\globallong": "\\\\globallong", "\\def": "\\gdef", "\\gdef": "\\gdef", "\\edef": "\\xdef", "\\xdef": "\\xdef", "\\let": "\\\\globallet", "\\futurelet": "\\\\globalfuture" }; const checkControlSequence = (tok) => { const name = tok.text; if (/^(?:[\\{}$&#^_]|EOF)$/.test(name)) { throw new ParseError("Expected a control sequence", tok); } return name; }; const getRHS = (parser) => { let 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; }; const letCommand = (parser, name, tok, global) => { let 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({ parser, funcName }) { parser.consumeSpaces(); const token = parser.fetch(); if (globalMap[token.text]) { // Temml 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({ parser, funcName }) { let tok = parser.gullet.popToken(); const name = tok.text; if (/^(?:[\\{}$&#^_]|EOF)$/.test(name)) { throw new ParseError("Expected a control sequence", tok); } let numArgs = 0; let insert; const 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 let { 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({ parser, funcName }) { const name = checkControlSequence(parser.gullet.popToken()); parser.gullet.consumeSpaces(); const 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({ parser, funcName }) { const name = checkControlSequence(parser.gullet.popToken()); const middle = parser.gullet.popToken(); const tok = parser.gullet.popToken(); letCommand(parser, name, tok, funcName === "\\\\globalfuture"); parser.gullet.pushToken(tok); parser.gullet.pushToken(middle); return { type: "internal", mode: parser.mode }; } }); defineFunction({ type: "internal", names: ["\\newcommand", "\\renewcommand", "\\providecommand"], props: { numArgs: 0, allowedInText: true, primitive: true }, handler({ parser, funcName }) { let name = ""; const tok = parser.gullet.popToken(); if (tok.text === "{") { name = checkControlSequence(parser.gullet.popToken()); parser.gullet.popToken(); } else { name = checkControlSequence(tok); } const exists = parser.gullet.isDefined(name); if (exists && funcName === "\\newcommand") { throw new ParseError( `\\newcommand{${name}} attempting to redefine ${name}; use \\renewcommand` ); } if (!exists && funcName === "\\renewcommand") { throw new ParseError( `\\renewcommand{${name}} when command ${name} does not yet exist; use \\newcommand` ); } let numArgs = 0; if (parser.gullet.future().text === "[") { let tok = parser.gullet.popToken(); tok = parser.gullet.popToken(); if (!/^[0-9]$/.test(tok.text)) { throw new ParseError(`Invalid number of arguments: "${tok.text}"`); } numArgs = parseInt(tok.text); tok = parser.gullet.popToken(); if (tok.text !== "]") { throw new ParseError(`Invalid argument "${tok.text}"`); } } // replacement text, enclosed in '{' and '}' and properly nested const { tokens } = parser.gullet.consumeArg(); parser.gullet.macros.set( name, { tokens, numArgs }, !parser.settings.strict ); return { type: "internal", mode: parser.mode }; } }); // Extra data needed for the delimiter handler down below const 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 } }; const delimiters = [ "(", "\\lparen", ")", "\\rparen", "[", "\\lbrack", "]", "\\rbrack", "\\{", "\\lbrace", "\\}", "\\rbrace", "⦇", "\\llparenthesis", "⦈", "\\rrparenthesis", "\\lfloor", "\\rfloor", "\u230a", "\u230b", "\\lceil", "\\rceil", "\u2308", "\u2309", "<", ">", "\\langle", "\u27e8", "\\rangle", "\u27e9", "\\lAngle", "\u27ea", "\\rAngle", "\u27eb", "\\llangle", "⦉", "\\rrangle", "⦊", "\\lt", "\\gt", "\\lvert", "\\rvert", "\\lVert", "\\rVert", "\\lgroup", "\\rgroup", "\u27ee", "\u27ef", "\\lmoustache", "\\rmoustache", "\u23b0", "\u23b1", "\\llbracket", "\\rrbracket", "\u27e6", "\u27e6", "\\lBrace", "\\rBrace", "\u2983", "\u2984", "/", "\\backslash", "|", "\\vert", "\\|", "\\Vert", "\\uparrow", "\\Uparrow", "\\downarrow", "\\Downarrow", "\\updownarrow", "\\Updownarrow", "." ]; // Export isDelimiter for benefit of parser. const dels = ["}", "\\left", "\\middle", "\\right"]; const isDelimiter = str => str.length > 0 && (delimiters.includes(str) || delimiterSizes[str] || dels.includes(str)); // 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. const sizeToMaxHeight = [0, 1.2, 1.8, 2.4, 3.0]; // Delimiter functions function checkDelimiter(delim, context) { const symDelim = checkSymbolNodeType(delim); if (symDelim && delimiters.includes(symDelim.text)) { // If a character is not in the MathML operator dictionary, it will not stretch. // Replace such characters w/characters that will stretch. if (["/", "\u2044"].includes(symDelim.text)) { symDelim.text = "\u2215"; } if (["<", "\\lt"].includes(symDelim.text)) { symDelim.text = "⟨"; } if ([">", "\\gt"].includes(symDelim.text)) { symDelim.text = "⟩"; } if (symDelim.text === "\\backslash") { symDelim.text = "\u2216"; } 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) => { const 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 }; }, mathmlBuilder: (group) => { const children = []; if (group.delim === ".") { group.delim = ""; } children.push(makeText(group.delim, group.mode)); const 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"); } if (group.delim === "\u2216" || group.delim === "\\vert" || group.delim === "|" || group.delim.indexOf("arrow") > -1) { // We have to explicitly set stretchy to true. node.setAttribute("stretchy", "true"); } node.setAttribute("symmetric", "true"); // Needed for tall arrows in Firefox. node.setAttribute("minsize", sizeToMaxHeight[group.size] + "em"); node.setAttribute("maxsize", sizeToMaxHeight[group.size] + "em"); 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, argTypes: ["primitive"] }, handler: (context, args) => { return { type: "leftright-right", mode: context.parser.mode, delim: checkDelimiter(args[0], context).text }; } }); defineFunction({ type: "leftright", names: ["\\left"], props: { numArgs: 1, argTypes: ["primitive"] }, handler: (context, args) => { const delim = checkDelimiter(args[0], context); const parser = context.parser; // Parse out the implicit body ++parser.leftrightDepth; // parseExpression stops before '\\right' or `\\middle` let body = parser.parseExpression(false, null, true); let nextToken = parser.fetch(); while (nextToken.text === "\\middle") { // `\middle`, from the ε-TeX package, ends one group and starts another group. // We had to parse this expression with `breakOnMiddle` enabled in order // to get TeX-compliant parsing of \over. // But we do not want, at this point, to end on \middle, so continue // to parse until we fetch a `\right`. parser.consume(); const middle = parser.fetch().text; if (!symbols.math[middle]) { throw new ParseError(`Invalid delimiter '${middle}' after '\\middle'`); } checkDelimiter({ type: "atom", mode: "math", text: middle }, { funcName: "\\middle" }); body.push({ type: "middle", mode: "math", delim: middle }); parser.consume(); body = body.concat(parser.parseExpression(false, null, true)); nextToken = parser.fetch(); } --parser.leftrightDepth; // Check the next token parser.expect("\\right", false); const right = assertNodeType(parser.parseFunction(), "leftright-right"); return { type: "leftright", mode: parser.mode, body, left: delim.text, right: right.delim }; }, mathmlBuilder: (group, style) => { assertParsed(group); const inner = buildExpression(group.body, style); if (group.left === ".") { group.left = ""; } const leftNode = new mathMLTree.MathNode("mo", [makeText(group.left, group.mode)]); leftNode.setAttribute("fence", "true"); leftNode.setAttribute("form", "prefix"); if (group.left === "\u2216" || group.left.indexOf("arrow") > -1) { leftNode.setAttribute("stretchy", "true"); } inner.unshift(leftNode); if (group.right === ".") { group.right = ""; } const rightNode = new mathMLTree.MathNode("mo", [makeText(group.right, group.mode)]); rightNode.setAttribute("fence", "true"); rightNode.setAttribute("form", "postfix"); if (group.right === "\u2216" || group.right.indexOf("arrow") > -1) { rightNode.setAttribute("stretchy", "true"); } inner.push(rightNode); return makeRow(inner); } }); defineFunction({ type: "middle", names: ["\\middle"], props: { numArgs: 1, argTypes: ["primitive"] }, handler: (context, args) => { const 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 }; }, mathmlBuilder: (group, style) => { const textNode = makeText(group.delim, group.mode); const middleNode = new mathMLTree.MathNode("mo", [textNode]); middleNode.setAttribute("fence", "true"); if (group.delim.indexOf("arrow") > -1) { middleNode.setAttribute("stretchy", "true"); } // The next line is not semantically correct, but // Chromium fails to stretch if it is not there. middleNode.setAttribute("form", "prefix"); // 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; } }); const padding$1 = _ => { const node = new mathMLTree.MathNode("mspace"); node.setAttribute("width", "3pt"); return node }; const mathmlBuilder$8 = (group, style) => { let node; if (group.label.indexOf("colorbox") > -1 || group.label === "\\boxed") { // MathML core does not support +width attribute in . // Firefox does not reliably add side padding. // Insert node = new mathMLTree.MathNode("mrow", [ padding$1(), buildGroup$1(group.body, style), padding$1() ]); } else { node = new mathMLTree.MathNode("mrow", [buildGroup$1(group.body, style)]); } switch (group.label) { case "\\overline": node.style.padding = "0.1em 0 0 0"; node.style.borderTop = "0.065em solid"; break case "\\underline": node.style.padding = "0 0 0.1em 0"; node.style.borderBottom = "0.065em solid"; break case "\\cancel": // We can't use an inline background-gradient. It does not work client-side. // So set a class and put the rule in the external CSS file. node.classes.push("tml-cancel"); break case "\\bcancel": node.classes.push("tml-bcancel"); break /* case "\\longdiv": node.setAttribute("notation", "longdiv"); break case "\\phase": node.setAttribute("notation", "phasorangle"); break */ case "\\angl": node.style.padding = "0.03889em 0.03889em 0 0.03889em"; node.style.borderTop = "0.049em solid"; node.style.borderRight = "0.049em solid"; node.style.marginRight = "0.03889em"; break case "\\sout": node.style.backgroundImage = 'linear-gradient(black, black)'; node.style.backgroundRepeat = 'no-repeat'; node.style.backgroundSize = '100% 1.5px'; node.style.backgroundPosition = '0 center'; break case "\\boxed": // \newcommand{\boxed}[1]{\fbox{\m@th$\displaystyle#1$}} from amsmath.sty node.style = { padding: "3pt 0 3pt 0", border: "1px solid" }; node.setAttribute("scriptlevel", "0"); node.setAttribute("displaystyle", "true"); break case "\\fbox": node.style = { padding: "3pt", border: "1px solid" }; break case "\\fcolorbox": case "\\colorbox": { // doesn't have a good notation option for \colorbox. // So use instead. Set some attributes that come // included with . //const fboxsep = 3; // 3 pt from LaTeX source2e //node.setAttribute("height", `+${2 * fboxsep}pt`) //node.setAttribute("voffset", `${fboxsep}pt`) const style = { padding: "3pt 0 3pt 0" }; if (group.label === "\\fcolorbox") { style.border = "0.06em solid " + String(group.borderColor); } node.style = style; break } case "\\xcancel": node.classes.push("tml-xcancel"); break } if (group.backgroundColor) { node.setAttribute("mathbackground", group.backgroundColor); } return node; }; defineFunction({ type: "enclose", names: ["\\colorbox"], props: { numArgs: 2, numOptionalArgs: 1, allowedInText: true, argTypes: ["raw", "raw", "text"] }, handler({ parser, funcName }, args, optArgs) { const model = optArgs[0] && assertNodeType(optArgs[0], "raw").string; let color = ""; if (model) { const spec = assertNodeType(args[0], "raw").string; color = colorFromSpec(model, spec); } else { color = validateColor(assertNodeType(args[0], "raw").string, parser.gullet.macros); } const body = args[1]; return { type: "enclose", mode: parser.mode, label: funcName, backgroundColor: color, body }; }, mathmlBuilder: mathmlBuilder$8 }); defineFunction({ type: "enclose", names: ["\\fcolorbox"], props: { numArgs: 3, numOptionalArgs: 1, allowedInText: true, argTypes: ["raw", "raw", "raw", "text"] }, handler({ parser, funcName }, args, optArgs) { const model = optArgs[0] && assertNodeType(optArgs[0], "raw").string; let borderColor = ""; let backgroundColor; if (model) { const borderSpec = assertNodeType(args[0], "raw").string; const backgroundSpec = assertNodeType(args[0], "raw").string; borderColor = colorFromSpec(model, borderSpec); backgroundColor = colorFromSpec(model, backgroundSpec); } else { borderColor = validateColor(assertNodeType(args[0], "raw").string, parser.gullet.macros); backgroundColor = validateColor(assertNodeType(args[1], "raw").string, parser.gullet.macros); } const body = args[2]; return { type: "enclose", mode: parser.mode, label: funcName, backgroundColor, borderColor, body }; }, mathmlBuilder: mathmlBuilder$8 }); defineFunction({ type: "enclose", names: ["\\fbox"], props: { numArgs: 1, argTypes: ["hbox"], allowedInText: true }, handler({ parser }, args) { return { type: "enclose", mode: parser.mode, label: "\\fbox", body: args[0] }; } }); defineFunction({ type: "enclose", names: ["\\angl", "\\cancel", "\\bcancel", "\\xcancel", "\\sout", "\\overline", "\\boxed"], // , "\\phase", "\\longdiv" props: { numArgs: 1 }, handler({ parser, funcName }, args) { const body = args[0]; return { type: "enclose", mode: parser.mode, label: funcName, body }; }, mathmlBuilder: mathmlBuilder$8 }); defineFunction({ type: "enclose", names: ["\\underline"], props: { numArgs: 1, allowedInText: true }, handler({ parser, funcName }, args) { const body = args[0]; return { type: "enclose", mode: parser.mode, label: funcName, body }; }, mathmlBuilder: mathmlBuilder$8 }); /** * All registered environments. * `environments.js` exports this same dictionary again and makes it public. * `Parser.js` requires this dictionary via `environments.js`. */ const _environments = {}; function defineEnvironment({ type, names, props, handler, mathmlBuilder }) { // Set default values of environments. const data = { type, numArgs: props.numArgs || 0, allowedInText: false, numOptionalArgs: 0, handler }; for (let i = 0; i < names.length; ++i) { _environments[names[i]] = data; } if (mathmlBuilder) { _mathmlGroupBuilders[type] = mathmlBuilder; } } // In TeX, there are actually three sets of dimensions, one for each of // Math style is not quite the same thing as script level. const StyleLevel = { DISPLAY: 0, TEXT: 1, SCRIPT: 2, SCRIPTSCRIPT: 3 }; // 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. const hlineInfo = []; parser.consumeSpaces(); let nxt = parser.fetch().text; if (nxt === "\\relax") { 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; } const validateAmsEnvironmentContext = context => { const settings = context.parser.settings; if (!settings.displayMode) { throw new ParseError(`{${context.envName}} can be used only in display mode.`); } }; const getTag = (group, style, rowNum) => { let tag; const tagContents = group.tags.shift(); if (tagContents) { // The author has written a \tag or a \notag in this row. if (tagContents.body) { tag = buildExpressionRow(tagContents.body, style, true); tag.classes = ["tml-tag"]; } else { // \notag. Return an empty span. tag = new mathMLTree.MathNode("mtext", [], []); return tag } } else if (group.envClasses.includes("multline") && ((group.leqno && rowNum !== 0) || (!group.leqno && rowNum !== group.body.length - 1))) { // A multiline that does not receive a tag. Return an empty cell. tag = new mathMLTree.MathNode("mtext", [], []); return tag } else { // AMS automatcally numbered equaton. // Insert a class so the element can be populated by a CSS counter. // WebKit will display the CSS counter only inside a span. tag = new mathMLTree.MathNode("mtext", [new Span(["tml-eqn"])]); } return tag }; /** * 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 scriptLevel * ("text", "display", etc.), then each cell is cast into that scriptLevel. */ function parseArray( parser, { cols, // [{ type: string , align: l|c|r|null }] envClasses, // align(ed|at|edat) | array | cases | cd | small | multline addEqnNum, // boolean singleRow, // boolean emptySingleRow, // boolean maxNumCols, // number leqno // boolean }, scriptLevel ) { 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"); } if (addEqnNum) { parser.gullet.macros.set("\\tag", "\\@ifstar\\envtag@literal\\envtag@paren"); parser.gullet.macros.set("\\envtag@paren", "\\env@tag{{(\\text{#1})}}"); parser.gullet.macros.set("\\envtag@literal", "\\env@tag{\\text{#1}}"); parser.gullet.macros.set("\\notag", "\\env@notag"); parser.gullet.macros.set("\\nonumber", "\\env@notag"); } // Start group for first cell parser.gullet.beginGroup(); let row = []; const body = [row]; const rowGaps = []; const tags = []; let rowTag; const hLinesBeforeRow = []; // Test for \hline at the top of the array. hLinesBeforeRow.push(getHLines(parser)); // eslint-disable-next-line no-constant-condition while (true) { // Parse each cell in its own group (namespace) let cell = parser.parseExpression(false, singleRow ? "\\end" : "\\\\"); if (addEqnNum && !rowTag) { // Check if the author wrote a \tag{} inside this cell. for (let i = 0; i < cell.length; i++) { if (cell[i].type === "envTag" || cell[i].type === "noTag") { // Get the contents of the \text{} nested inside the \env@Tag{} rowTag = cell[i].type === "envTag" ? cell.splice(i, 1)[0].body.body[0] : { body: null }; break } } } parser.gullet.endGroup(); parser.gullet.beginGroup(); cell = { type: "ordgroup", mode: parser.mode, body: cell, semisimple: true }; row.push(cell); const next = parser.fetch().text; if (next === "&") { if (maxNumCols && row.length === maxNumCols) { if (envClasses.includes("array")) { if (parser.settings.strict) { throw new ParseError("Too few columns " + "specified in the {array} column argument.", parser.nextToken) } } else if (maxNumCols === 2) { throw new ParseError("The split environment accepts no more than two columns", parser.nextToken); } else { throw new ParseError("The equation environment accepts only one column", parser.nextToken) } } parser.consume(); } else if (next === "\\end") { // 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.body.length === 0 && (body.length > 1 || !emptySingleRow)) { body.pop(); } if (hLinesBeforeRow.length < body.length + 1) { hLinesBeforeRow.push([]); } break; } else if (next === "\\\\") { parser.consume(); let size; // \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); tags.push(rowTag); // check for \hline(s) following the row separator hLinesBeforeRow.push(getHLines(parser)); row = []; rowTag = null; body.push(row); } 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(); tags.push(rowTag); return { type: "array", mode: parser.mode, body, cols, rowGaps, hLinesBeforeRow, envClasses, addEqnNum, scriptLevel, tags, leqno }; } // Decides on a scriptLevel for cells in an array according to whether the given // environment name starts with the letter 'd'. function dCellStyle(envName) { return envName.slice(0, 1) === "d" ? "display" : "text" } const alignMap = { c: "center ", l: "left ", r: "right " }; const glue = group => { const glueNode = new mathMLTree.MathNode("mtd", []); glueNode.style = { padding: "0", width: "50%" }; if (group.envClasses.includes("multline")) { glueNode.style.width = "7.5%"; } return glueNode }; const mathmlBuilder$7 = function(group, style) { const tbl = []; const numRows = group.body.length; const hlines = group.hLinesBeforeRow; for (let i = 0; i < numRows; i++) { const rw = group.body[i]; const row = []; const cellLevel = group.scriptLevel === "text" ? StyleLevel.TEXT : group.scriptLevel === "script" ? StyleLevel.SCRIPT : StyleLevel.DISPLAY; for (let j = 0; j < rw.length; j++) { const mtd = new mathMLTree.MathNode( "mtd", [buildGroup$1(rw[j], style.withLevel(cellLevel))] ); if (group.envClasses.includes("multline")) { const align = i === 0 ? "left" : i === numRows - 1 ? "right" : "center"; mtd.setAttribute("columnalign", align); if (align !== "center") { mtd.classes.push("tml-" + align); } } row.push(mtd); } if (group.addEqnNum) { row.unshift(glue(group)); row.push(glue(group)); const tag = getTag(group, style.withLevel(cellLevel), i); if (group.leqno) { row[0].children.push(tag); row[0].classes.push("tml-left"); } else { row[row.length - 1].children.push(tag); row[row.length - 1].classes.push("tml-right"); } } const mtr = new mathMLTree.MathNode("mtr", row, []); // Write horizontal rules if (i === 0 && hlines[0].length > 0) { if (hlines[0].length === 2) { mtr.children.forEach(cell => { cell.style.borderTop = "0.15em double"; }); } else { mtr.children.forEach(cell => { cell.style.borderTop = hlines[0][0] ? "0.06em dashed" : "0.06em solid"; }); } } if (hlines[i + 1].length > 0) { if (hlines[i + 1].length === 2) { mtr.children.forEach(cell => { cell.style.borderBottom = "0.15em double"; }); } else { mtr.children.forEach(cell => { cell.style.borderBottom = hlines[i + 1][0] ? "0.06em dashed" : "0.06em solid"; }); } } tbl.push(mtr); } if (group.envClasses.length > 0) { const pad = group.envClasses.includes("jot") ? "0.7" // 0.5ex + 0.09em top & bot padding : group.envClasses.includes("small") ? "0.35" : "0.5"; // 0.5ex default top & bot padding const sidePadding = group.envClasses.includes("abut") ? "0" : group.envClasses.includes("cases") ? "0" : group.envClasses.includes("small") ? "0.1389" : group.envClasses.includes("cd") ? "0.25" : "0.4"; // default side padding const numCols = tbl.length === 0 ? 0 : tbl[0].children.length; const sidePad = (j, hand) => { if (j === 0 && hand === 0) { return "0" } if (j === numCols - 1 && hand === 1) { return "0" } if (group.envClasses[0] !== "align") { return sidePadding } if (hand === 1) { return "0" } if (group.addEqnNum) { return (j % 2) ? "1" : "0" } else { return (j % 2) ? "0" : "1" } }; // Padding for (let i = 0; i < tbl.length; i++) { for (let j = 0; j < tbl[i].children.length; j++) { tbl[i].children[j].style.padding = `${pad}ex ${sidePad(j, 1)}em ${pad}ex ${sidePad(j, 0)}em`; } } // Justification const align = group.envClasses.includes("align") || group.envClasses.includes("alignat"); for (let i = 0; i < tbl.length; i++) { const row = tbl[i]; if (align) { for (let j = 0; j < row.children.length; j++) { // Chromium does not recognize text-align: left. Use -webkit- // TODO: Remove -webkit- when Chromium no longer needs it. row.children[j].classes = ["tml-" + (j % 2 ? "left" : "right")]; } if (group.addEqnNum) { const k = group.leqno ? 0 : row.children.length - 1; row.children[k].classes = ["tml-" + (group.leqno ? "left" : "right")]; } } if (row.children.length > 1 && group.envClasses.includes("cases")) { row.children[1].style.padding = row.children[1].style.padding.replace(/0em$/, "1em"); } if (group.envClasses.includes("cases") || group.envClasses.includes("subarray")) { for (const cell of row.children) { cell.classes.push("tml-left"); } } } } else { // Set zero padding on side of the matrix for (let i = 0; i < tbl.length; i++) { tbl[i].children[0].style.paddingLeft = "0em"; if (tbl[i].children.length === tbl[0].children.length) { tbl[i].children[tbl[i].children.length - 1].style.paddingRight = "0em"; } } } let table = new mathMLTree.MathNode("mtable", tbl); if (group.scriptLevel === "display") { table.setAttribute("displaystyle", "true"); } if (group.addEqnNum || group.envClasses.includes("multline")) { table.style.width = "100%"; } // Column separator lines and column alignment let align = ""; if (group.cols && group.cols.length > 0) { const cols = group.cols; let prevTypeWasAlign = false; let iStart = 0; let iEnd = cols.length; while (cols[iStart].type === "separator") { iStart += 1; } while (cols[iEnd - 1].type === "separator") { iEnd -= 1; } if (cols[0].type === "separator") { const sep = cols[1].type === "separator" ? "0.15em double" : cols[0].separator === "|" ? "0.06em solid " : "0.06em dashed "; for (const row of table.children) { row.children[0].style.borderLeft = sep; } } let iCol = group.addEqnNum ? 0 : -1; for (let i = iStart; i < iEnd; i++) { if (cols[i].type === "align") { const colAlign = alignMap[cols[i].align]; align += colAlign; iCol += 1; for (const row of table.children) { if (colAlign.trim() !== "center" && iCol < row.children.length) { row.children[iCol].classes = ["tml-" + colAlign.trim()]; } } 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) { const sep = cols[i + 1].type === "separator" ? "0.15em double" : cols[i].separator === "|" ? "0.06em solid" : "0.06em dashed"; for (const row of table.children) { if (iCol < row.children.length) { row.children[iCol].style.borderRight = sep; } } } prevTypeWasAlign = false; } } if (cols[cols.length - 1].type === "separator") { const sep = cols[cols.length - 2].type === "separator" ? "0.15em double" : cols[cols.length - 1].separator === "|" ? "0.06em solid" : "0.06em dashed"; for (const row of table.children) { row.children[row.children.length - 1].style.borderRight = sep; row.children[row.children.length - 1].style.paddingRight = "0.4em"; } } } if (group.addEqnNum) { // allow for glue cells on each side align = "left " + (align.length > 0 ? align : "center ") + "right "; } if (align) { // Firefox reads this attribute, not the -webkit-left|right written above. // TODO: When Chrome no longer needs "-webkit-", use CSS and delete the next line. table.setAttribute("columnalign", align.trim()); } if (group.envClasses.includes("small")) { // A small array. Wrap in scriptstyle. table = new mathMLTree.MathNode("mstyle", [table]); table.setAttribute("scriptlevel", "1"); } return table }; // Convenience function for align, align*, aligned, alignat, alignat*, alignedat, split. const alignedHandler = function(context, args) { if (context.envName.indexOf("ed") === -1) { validateAmsEnvironmentContext(context); } const cols = []; const res = parseArray( context.parser, { cols, addEqnNum: context.envName === "align" || context.envName === "alignat", emptySingleRow: true, envClasses: ["abut", "jot"], // set row spacing & provisional column spacing maxNumCols: context.envName === "split" ? 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. let numMaths; let numCols = 0; const isAlignedAt = context.envName.indexOf("at") > -1; if (args[0] && isAlignedAt) { // alignat environment takes an argument w/ number of columns let arg0 = ""; for (let i = 0; i < args[0].body.length; i++) { const textord = assertNodeType(args[0].body[i], "textord"); arg0 += textord.text; } if (isNaN(arg0)) { throw new ParseError("The alignat enviroment requires a numeric first argument.") } numMaths = Number(arg0); numCols = numMaths * 2; } res.body.forEach(function(row) { if (isAlignedAt) { // Case 1 const 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 (let i = 0; i < numCols; ++i) { let align = "r"; if (i % 2 === 1) { align = "l"; } cols[i] = { type: "align", align: align }; } if (context.envName === "split") ; else if (isAlignedAt) { res.envClasses.push("alignat"); // Sets justification } else { res.envClasses[0] = "align"; // Sets column spacing & justification } 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. const symNode = checkSymbolNodeType(args[0]); const colalign = symNode ? [args[0]] : assertNodeType(args[0], "ordgroup").body; const cols = colalign.map(function(nde) { const node = assertSymbolNodeType(nde); const 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); }); const res = { cols, envClasses: ["array"], maxNumCols: cols.length }; return parseArray(context.parser, res, dCellStyle(context.envName)); }, mathmlBuilder: mathmlBuilder$7 }); // 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) { const delimiters = { matrix: null, pmatrix: ["(", ")"], bmatrix: ["[", "]"], Bmatrix: ["\\{", "\\}"], vmatrix: ["|", "|"], Vmatrix: ["\\Vert", "\\Vert"] }[context.envName.replace("*", "")]; // \hskip -\arraycolsep in amsmath let colAlign = "c"; const payload = { envClasses: [], cols: [] }; if (context.envName.charAt(context.envName.length - 1) === "*") { // It's one of the mathtools starred functions. // Parse the optional alignment argument. const 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 = []; } } const res = parseArray(context.parser, payload, "text"); res.cols = new Array(res.body[0].length).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; }, mathmlBuilder: mathmlBuilder$7 }); defineEnvironment({ type: "array", names: ["smallmatrix"], props: { numArgs: 0 }, handler(context) { const payload = { type: "small" }; const res = parseArray(context.parser, payload, "script"); res.envClasses = ["small"]; return res; }, mathmlBuilder: mathmlBuilder$7 }); defineEnvironment({ type: "array", names: ["subarray"], props: { numArgs: 1 }, handler(context, args) { // Parsing of {subarray} is similar to {array} const symNode = checkSymbolNodeType(args[0]); const colalign = symNode ? [args[0]] : assertNodeType(args[0], "ordgroup").body; const cols = colalign.map(function(nde) { const node = assertSymbolNodeType(nde); const 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"); } let res = { cols, envClasses: ["small"] }; 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; }, mathmlBuilder: mathmlBuilder$7 }); // A cases environment (in amsmath.sty) is almost equivalent to // \def // \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) { const payload = { cols: [], envClasses: ["cases"] }; const 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 }; }, mathmlBuilder: mathmlBuilder$7 }); // 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. defineEnvironment({ type: "array", names: ["align", "align*", "aligned", "split"], props: { numArgs: 0 }, handler: alignedHandler, mathmlBuilder: mathmlBuilder$7 }); // alignat environment is like an align environment, but one must explicitly // specify maximum number of columns in each row, and can adjust where spacing occurs. defineEnvironment({ type: "array", names: ["alignat", "alignat*", "alignedat"], props: { numArgs: 1 }, handler: alignedHandler, mathmlBuilder: mathmlBuilder$7 }); // 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 (context.envName !== "gathered") { validateAmsEnvironmentContext(context); } const res = { cols: [], envClasses: ["abut", "jot"], addEqnNum: context.envName === "gather", emptySingleRow: true, leqno: context.parser.settings.leqno }; return parseArray(context.parser, res, "display"); }, mathmlBuilder: mathmlBuilder$7 }); defineEnvironment({ type: "array", names: ["equation", "equation*"], props: { numArgs: 0 }, handler(context) { validateAmsEnvironmentContext(context); const res = { addEqnNum: context.envName === "equation", emptySingleRow: true, singleRow: true, maxNumCols: 1, envClasses: ["align"], leqno: context.parser.settings.leqno }; return parseArray(context.parser, res, "display"); }, mathmlBuilder: mathmlBuilder$7 }); defineEnvironment({ type: "array", names: ["multline", "multline*"], props: { numArgs: 0 }, handler(context) { validateAmsEnvironmentContext(context); const res = { addEqnNum: context.envName === "multline", maxNumCols: 1, envClasses: ["jot", "multline"], leqno: context.parser.settings.leqno }; return parseArray(context.parser, res, "display"); }, mathmlBuilder: mathmlBuilder$7 }); defineEnvironment({ type: "array", names: ["CD"], props: { numArgs: 0 }, handler(context) { validateAmsEnvironmentContext(context); return parseCD(context.parser); }, mathmlBuilder: mathmlBuilder$7 }); // 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`); } }); const environments = _environments; // Environment delimiters. HTML/MathML rendering is defined in the corresponding // defineEnvironment definitions. defineFunction({ type: "environment", names: ["\\begin", "\\end"], props: { numArgs: 1, argTypes: ["text"] }, handler({ parser, funcName }, args) { const nameGroup = args[0]; if (nameGroup.type !== "ordgroup") { throw new ParseError("Invalid environment name", nameGroup); } let envName = ""; for (let 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 (!Object.prototype.hasOwnProperty.call(environments, 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. const env = environments[envName]; const { args, optArgs } = parser.parseArguments("\\begin{" + envName + "}", env); const context = { mode: parser.mode, envName, parser }; const result = env.handler(context, args, optArgs); parser.expect("\\end", false); const endNameToken = parser.nextToken; const end = assertNodeType(parser.parseFunction(), "environment"); if (end.name !== envName) { throw new ParseError( `Mismatch: \\begin{${envName}} matched by \\end{${end.name}}`, endNameToken ); } return result; } return { type: "environment", mode: parser.mode, name: envName, nameGroup }; } }); defineFunction({ type: "envTag", names: ["\\env@tag"], props: { numArgs: 1, argTypes: ["math"] }, handler({ parser }, args) { return { type: "envTag", mode: parser.mode, body: args[0] }; }, mathmlBuilder(group, style) { return new mathMLTree.MathNode("mrow"); } }); defineFunction({ type: "noTag", names: ["\\env@notag"], props: { numArgs: 0 }, handler({ parser }) { return { type: "noTag", mode: parser.mode }; }, mathmlBuilder(group, style) { return new mathMLTree.MathNode("mrow"); } }); const mathmlBuilder$6 = (group, style) => { const font = group.font; const newStyle = style.withFont(font); const mathGroup = buildGroup$1(group.body, newStyle); if (mathGroup.children.length === 0) { return mathGroup } // empty group, e.g., \mathrm{} if (font === "boldsymbol" && ["mo", "mpadded", "mrow"].includes(mathGroup.type)) { mathGroup.style.fontWeight = "bold"; return mathGroup } // Check if it is possible to consolidate elements into a single element. let canConsolidate = mathGroup.children[0].type === "mo"; for (let i = 1; i < mathGroup.children.length; i++) { if (mathGroup.children[i].type === "mo" && font === "boldsymbol") { mathGroup.children[i].style.fontWeight = "bold"; } if (mathGroup.children[i].type !== "mi") { canConsolidate = false; } const localVariant = mathGroup.children[i].attributes && mathGroup.children[i].attributes.mathvariant || ""; if (localVariant !== "normal") { canConsolidate = false; } } if (!canConsolidate) { return mathGroup } // Consolidate the elements. const mi = mathGroup.children[0]; for (let i = 1; i < mathGroup.children.length; i++) { mi.children.push(mathGroup.children[i].children[0]); } if (mi.attributes.mathvariant && mi.attributes.mathvariant === "normal") { // Workaround for a Firefox bug that renders spurious space around // a // Ref: https://bugs.webkit.org/show_bug.cgi?id=129097 // We insert a text node that contains a zero-width space and wrap in an mrow. // TODO: Get rid of this workaround when the Firefox bug is fixed. const bogus = new mathMLTree.MathNode("mtext", new mathMLTree.TextNode("\u200b")); return new mathMLTree.MathNode("mrow", [bogus, mi]) } return mi }; const fontAliases = { "\\Bbb": "\\mathbb", "\\bold": "\\mathbf", "\\frak": "\\mathfrak", "\\bm": "\\boldsymbol" }; defineFunction({ type: "font", names: [ // styles "\\mathrm", "\\mathit", "\\mathbf", "\\mathnormal", "\\up@greek", "\\boldsymbol", // families "\\mathbb", "\\mathcal", "\\mathfrak", "\\mathscr", "\\mathsf", "\\mathtt", // aliases "\\Bbb", "\\bm", "\\bold", "\\frak" ], props: { numArgs: 1, allowedInArgument: true }, handler: ({ parser, funcName }, args) => { const body = normalizeArgument(args[0]); let func = funcName; if (func in fontAliases) { func = fontAliases[func]; } return { type: "font", mode: parser.mode, font: func.slice(1), body }; }, mathmlBuilder: mathmlBuilder$6 }); // Old font changing functions defineFunction({ type: "font", names: ["\\rm", "\\sf", "\\tt", "\\bf", "\\it", "\\cal"], props: { numArgs: 0, allowedInText: true }, handler: ({ parser, funcName, breakOnTokenText }, args) => { const { mode } = parser; const body = parser.parseExpression(true, breakOnTokenText, true); const fontStyle = `math${funcName.slice(1)}`; return { type: "font", mode: mode, font: fontStyle, body: { type: "ordgroup", mode: parser.mode, body } }; }, mathmlBuilder: mathmlBuilder$6 }); const stylArray = ["display", "text", "script", "scriptscript"]; const scriptLevel = { auto: -1, display: 0, text: 0, script: 1, scriptscript: 2 }; const mathmlBuilder$5 = (group, style) => { // Track the scriptLevel of the numerator and denominator. // We may need that info for \mathchoice or for adjusting em dimensions. const childOptions = group.scriptLevel === "auto" ? style.incrementLevel() : group.scriptLevel === "display" ? style.withLevel(StyleLevel.TEXT) : group.scriptLevel === "text" ? style.withLevel(StyleLevel.SCRIPT) : style.withLevel(StyleLevel.SCRIPTSCRIPT); let node = new mathMLTree.MathNode("mfrac", [ buildGroup$1(group.numer, childOptions), buildGroup$1(group.denom, childOptions) ]); if (!group.hasBarLine) { node.setAttribute("linethickness", "0px"); } else if (group.barSize) { const ruleWidth = calculateSize(group.barSize, style); node.setAttribute("linethickness", ruleWidth.number + ruleWidth.unit); } if (group.leftDelim != null || group.rightDelim != null) { const withDelims = []; if (group.leftDelim != null) { const 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) { const rightOp = new mathMLTree.MathNode("mo", [ new mathMLTree.TextNode(group.rightDelim.replace("\\", "")) ]); rightOp.setAttribute("fence", "true"); withDelims.push(rightOp); } node = makeRow(withDelims); } if (group.scriptLevel !== "auto") { node = new mathMLTree.MathNode("mstyle", [node]); node.setAttribute("displaystyle", String(group.scriptLevel === "display")); node.setAttribute("scriptlevel", scriptLevel[group.scriptLevel]); } 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: ({ parser, funcName }, args) => { const numer = args[0]; const denom = args[1]; let hasBarLine = false; let leftDelim = null; let rightDelim = null; let scriptLevel = "auto"; switch (funcName) { case "\\dfrac": case "\\frac": case "\\tfrac": hasBarLine = true; break; case "\\\\atopfrac": hasBarLine = false; break; case "\\dbinom": case "\\binom": case "\\tbinom": leftDelim = "("; rightDelim = ")"; break; case "\\\\bracefrac": leftDelim = "\\{"; rightDelim = "\\}"; break; case "\\\\brackfrac": leftDelim = "["; rightDelim = "]"; break; default: throw new Error("Unrecognized genfrac command"); } switch (funcName) { case "\\dfrac": case "\\dbinom": scriptLevel = "display"; break; case "\\tfrac": case "\\tbinom": scriptLevel = "text"; break; } return { type: "genfrac", mode: parser.mode, continued: false, numer, denom, hasBarLine, leftDelim, rightDelim, scriptLevel, barSize: null }; }, mathmlBuilder: mathmlBuilder$5 }); defineFunction({ type: "genfrac", names: ["\\cfrac"], props: { numArgs: 2 }, handler: ({ parser, funcName }, args) => { const numer = args[0]; const denom = args[1]; return { type: "genfrac", mode: parser.mode, continued: true, numer, denom, hasBarLine: true, leftDelim: null, rightDelim: null, scriptLevel: "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({ parser, funcName, token }) { let 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 }; } }); const delimFromValue = function(delimString) { let 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({ parser }, args) { const numer = args[4]; const denom = args[5]; // Look into the parse nodes to get the desired delimiters. const leftNode = normalizeArgument(args[0]); const leftDelim = leftNode.type === "atom" && leftNode.family === "open" ? delimFromValue(leftNode.text) : null; const rightNode = normalizeArgument(args[1]); const rightDelim = rightNode.type === "atom" && rightNode.family === "close" ? delimFromValue(rightNode.text) : null; const barNode = assertNodeType(args[2], "size"); let hasBarLine; let 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. let scriptLevel = "auto"; let styl = args[3]; if (styl.type === "ordgroup") { if (styl.body.length > 0) { const textOrd = assertNodeType(styl.body[0], "textord"); scriptLevel = stylArray[Number(textOrd.text)]; } } else { styl = assertNodeType(styl, "textord"); scriptLevel = stylArray[Number(styl.text)]; } return { type: "genfrac", mode: parser.mode, numer, denom, continued: false, hasBarLine, barSize, leftDelim, rightDelim, scriptLevel }; }, mathmlBuilder: mathmlBuilder$5 }); // \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({ parser, funcName, token }, args) { return { type: "infix", mode: parser.mode, replaceWith: "\\\\abovefrac", barSize: assertNodeType(args[0], "size").value, token }; } }); defineFunction({ type: "genfrac", names: ["\\\\abovefrac"], props: { numArgs: 3, argTypes: ["math", "size", "math"] }, handler: ({ parser, funcName }, args) => { const numer = args[0]; const barSize = assert(assertNodeType(args[1], "infix").barSize); const denom = args[2]; const hasBarLine = barSize.number > 0; return { type: "genfrac", mode: parser.mode, numer, denom, continued: false, hasBarLine, barSize, leftDelim: null, rightDelim: null, scriptLevel: "auto" }; }, mathmlBuilder: mathmlBuilder$5 }); // \hbox is provided for compatibility with LaTeX functions that act on a box. // This function by itself doesn't do anything but set scriptlevel to \textstyle // and prevent a soft line break. defineFunction({ type: "hbox", names: ["\\hbox"], props: { numArgs: 1, argTypes: ["hbox"], allowedInArgument: true, allowedInText: false }, handler({ parser }, args) { return { type: "hbox", mode: parser.mode, body: ordargument(args[0]) }; }, mathmlBuilder(group, style) { const newStyle = style.withLevel(StyleLevel.TEXT); const mrow = buildExpressionRow(group.body, newStyle); return consolidateText(mrow) } }); const mathmlBuilder$4 = (group, style) => { const accentNode = stretchy.mathMLnode(group.label); accentNode.style["math-depth"] = 0; return new mathMLTree.MathNode(group.isOver ? "mover" : "munder", [ buildGroup$1(group.base, style), accentNode ]); }; // Horizontal stretchy braces defineFunction({ type: "horizBrace", names: ["\\overbrace", "\\underbrace"], props: { numArgs: 1 }, handler({ parser, funcName }, args) { return { type: "horizBrace", mode: parser.mode, label: funcName, isOver: /^\\over/.test(funcName), base: args[0] }; }, mathmlBuilder: mathmlBuilder$4 }); defineFunction({ type: "href", names: ["\\href"], props: { numArgs: 2, argTypes: ["url", "original"], allowedInText: true }, handler: ({ parser, token }, args) => { const body = args[1]; const href = assertNodeType(args[0], "url").url; if ( !parser.settings.isTrusted({ command: "\\href", url: href }) ) { throw new ParseError(`Function "\\href" is not trusted`, token) } return { type: "href", mode: parser.mode, href, body: ordargument(body) }; }, mathmlBuilder: (group, style) => { let math = buildExpressionRow(group.body, style); 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: ({ parser, token }, args) => { const href = assertNodeType(args[0], "url").url; if ( !parser.settings.isTrusted({ command: "\\url", url: href }) ) { throw new ParseError(`Function "\\url" is not trusted`, token) } const chars = []; for (let i = 0; i < href.length; i++) { let c = href[i]; if (c === "~") { c = "\\textasciitilde"; } chars.push({ type: "textord", mode: "text", text: c }); } const body = { type: "text", mode: parser.mode, font: "\\texttt", body: chars }; return { type: "href", mode: parser.mode, href, body: ordargument(body) }; } }); defineFunction({ type: "html", names: ["\\class", "\\id", "\\style", "\\data"], props: { numArgs: 2, argTypes: ["raw", "original"], allowedInText: true }, handler: ({ parser, funcName, token }, args) => { const value = assertNodeType(args[0], "raw").string; const body = args[1]; if (parser.settings.strict) { throw new ParseError(`Function "${funcName}" is disabled in strict mode`, token) } let trustContext; const attributes = {}; switch (funcName) { case "\\class": attributes.class = value; trustContext = { command: "\\class", class: value }; break; case "\\id": attributes.id = value; trustContext = { command: "\\id", id: value }; break; case "\\style": attributes.style = value; trustContext = { command: "\\style", style: value }; break; case "\\data": { const data = value.split(","); for (let i = 0; i < data.length; i++) { const keyVal = data[i].split("="); if (keyVal.length !== 2) { throw new ParseError("Error parsing key-value for \\data"); } attributes["data-" + keyVal[0].trim()] = keyVal[1].trim(); } trustContext = { command: "\\data", attributes }; break; } default: throw new Error("Unrecognized html command"); } if (!parser.settings.isTrusted(trustContext)) { throw new ParseError(`Function "${funcName}" is not trusted`, token) } return { type: "html", mode: parser.mode, attributes, body: ordargument(body) }; }, mathmlBuilder: (group, style) => { const element = buildExpressionRow(group.body, style); const classes = []; if (group.attributes.class) { classes.push(...group.attributes.class.trim().split(/\s+/)); } element.classes = classes; for (const attr in group.attributes) { if (attr !== "class" && Object.prototype.hasOwnProperty.call(group.attributes, attr)) { element.setAttribute(attr, group.attributes[attr]); } } return element; } }); const sizeData = function(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 { const match = /([-+]?) *(\d+(?:\.\d*)?|\.\d+) *([a-z]{2})/.exec(str); if (!match) { throw new ParseError("Invalid size: '" + str + "' in \\includegraphics"); } const 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: ({ parser, token }, args, optArgs) => { let width = { number: 0, unit: "em" }; let height = { number: 0.9, unit: "em" }; // sorta character sized. let totalheight = { number: 0, unit: "em" }; let alt = ""; if (optArgs[0]) { const attributeStr = assertNodeType(optArgs[0], "raw").string; // Parser.js does not parse key/value pairs. We get a string. const attributes = attributeStr.split(","); for (let i = 0; i < attributes.length; i++) { const keyVal = attributes[i].split("="); if (keyVal.length === 2) { const 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.") } } } } const 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 }) ) { throw new ParseError(`Function "\\includegraphics" is not trusted`, token) } return { type: "includegraphics", mode: parser.mode, alt: alt, width: width, height: height, totalheight: totalheight, src: src } }, mathmlBuilder: (group, style) => { const height = calculateSize(group.height, style); const depth = { number: 0, unit: "em" }; if (group.totalheight.number > 0) { if (group.totalheight.unit === height.unit && group.totalheight.number > height.number) { depth.number = group.totalheight.number - height.number; depth.unit = height.unit; } } let width = 0; if (group.width.number > 0) { width = calculateSize(group.width, style); } const graphicStyle = { height: height.number + depth.number + "em" }; if (width.number > 0) { graphicStyle.width = width.number + width.unit; } if (depth.number > 0) { graphicStyle.verticalAlign = -depth.number + depth.unit; } const node = new Img(group.src, group.alt, graphicStyle); node.height = height; node.depth = depth; return new mathMLTree.MathNode("mtext", [node]) } }); // Horizontal spacing commands // TODO: \hskip and \mskip should support plus and minus in lengths defineFunction({ type: "kern", names: ["\\kern", "\\mkern", "\\hskip", "\\mskip"], props: { numArgs: 1, argTypes: ["size"], primitive: true, allowedInText: true }, handler({ parser, funcName, token }, args) { const size = assertNodeType(args[0], "size"); if (parser.settings.strict) { const mathFunction = funcName[1] === "m"; // \mkern, \mskip const muUnit = size.value.unit === "mu"; if (mathFunction) { if (!muUnit) { throw new ParseError(`LaTeX's ${funcName} supports only mu units, ` + `not ${size.value.unit} units`, token) } if (parser.mode !== "math") { throw new ParseError(`LaTeX's ${funcName} works only in math mode`, token) } } else { // !mathFunction if (muUnit) { throw new ParseError(`LaTeX's ${funcName} doesn't support mu units`, token) } } } return { type: "kern", mode: parser.mode, dimension: size.value }; }, mathmlBuilder(group, style) { const dimension = calculateSize(group.dimension, style); const ch = dimension.unit === "em" ? spaceCharacter(dimension.number) : ""; if (group.mode === "text" && ch.length > 0) { const character = new mathMLTree.TextNode(ch); return new mathMLTree.MathNode("mtext", [character]); } else { const node = new mathMLTree.MathNode("mspace"); node.setAttribute("width", dimension.number + dimension.unit); if (dimension.number < 0) { node.style.marginLeft = dimension.number + dimension.unit; } return node; } } }); const spaceCharacter = function(width) { if (width >= 0.05555 && width <= 0.05556) { return "\u200a"; //   } else if (width >= 0.1666 && width <= 0.1667) { return "\u2009"; //   } else if (width >= 0.2222 && width <= 0.2223) { return "\u2005"; //   } else if (width >= 0.2777 && width <= 0.2778) { return "\u2005\u200a"; //    } else { return ""; } }; // Limit valid characters to a small set, for safety. const invalidIdRegEx = /[^A-Za-z_0-9-]/g; defineFunction({ type: "label", names: ["\\label"], props: { numArgs: 1, argTypes: ["raw"] }, handler({ parser }, args) { return { type: "label", mode: parser.mode, string: args[0].string.replace(invalidIdRegEx, "") }; }, mathmlBuilder(group, style) { // Return a no-width, no-ink element with an HTML id. const node = new mathMLTree.MathNode("mrow", [], ["tml-label"]); if (group.string.length > 0) { node.setAttribute("id", group.string); } return node } }); // Horizontal overlap functions const textModeLap = ["\\clap", "\\llap", "\\rlap"]; defineFunction({ type: "lap", names: ["\\mathllap", "\\mathrlap", "\\mathclap", "\\clap", "\\llap", "\\rlap"], props: { numArgs: 1, allowedInText: true }, handler: ({ parser, funcName, token }, args) => { if (textModeLap.includes(funcName)) { if (parser.settings.strict && parser.mode !== "text") { throw new ParseError(`{${funcName}} can be used only in text mode. Try \\math${funcName.slice(1)}`, token) } funcName = funcName.slice(1); } else { funcName = funcName.slice(5); } const body = args[0]; return { type: "lap", mode: parser.mode, alignment: funcName, body } }, mathmlBuilder: (group, style) => { // mathllap, mathrlap, mathclap let strut; if (group.alignment === "llap") { // We need an invisible strut with the same depth as the group. // We can't just read the depth, so we use \vphantom methods. const phantomInner = buildExpression(ordargument(group.body), style); const phantom = new mathMLTree.MathNode("mphantom", phantomInner); strut = new mathMLTree.MathNode("mpadded", [phantom]); strut.setAttribute("width", "0px"); } const inner = buildGroup$1(group.body, style); let node; if (group.alignment === "llap") { inner.style.position = "absolute"; inner.style.right = "0"; inner.style.bottom = `0`; // If we could have read the ink depth, it would go here. node = new mathMLTree.MathNode("mpadded", [strut, inner]); } else { node = new mathMLTree.MathNode("mpadded", [inner]); } if (group.alignment === "rlap") { if (group.body.body.length > 0 && group.body.body[0].type === "genfrac") { // In Firefox, a squashes the 3/18em padding of a child \frac. Put it back. node.setAttribute("lspace", "0.16667em"); } } else { const offset = group.alignment === "llap" ? "-1" : "-0.5"; node.setAttribute("lspace", offset + "width"); if (group.alignment === "llap") { node.style.position = "relative"; } else { node.style.display = "flex"; node.style.justifyContent = "center"; } } node.setAttribute("width", "0px"); return node } }); // Switching from text mode back to math mode defineFunction({ type: "ordgroup", names: ["\\(", "$"], props: { numArgs: 0, allowedInText: true, allowedInMath: false }, handler({ funcName, parser }, args) { const outerMode = parser.mode; parser.switchMode("math"); const close = funcName === "\\(" ? "\\)" : "$"; const body = parser.parseExpression(false, close); parser.expect(close); parser.switchMode(outerMode); return { type: "ordgroup", mode: parser.mode, 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, token) { throw new ParseError(`Mismatched ${context.funcName}`, token); } }); const chooseStyle = (group, style) => { switch (style.level) { case StyleLevel.DISPLAY: // 0 return group.display; case StyleLevel.TEXT: // 1 return group.text; case StyleLevel.SCRIPT: // 2 return group.script; case StyleLevel.SCRIPTSCRIPT: // 3 return group.scriptscript; default: return group.text; } }; defineFunction({ type: "mathchoice", names: ["\\mathchoice"], props: { numArgs: 4, primitive: true }, handler: ({ parser }, args) => { return { type: "mathchoice", mode: parser.mode, display: ordargument(args[0]), text: ordargument(args[1]), script: ordargument(args[2]), scriptscript: ordargument(args[3]) }; }, mathmlBuilder: (group, style) => { const body = chooseStyle(group, style); return buildExpressionRow(body, style); } }); const textAtomTypes = ["text", "textord", "mathord", "atom"]; const padding = width => { const node = new mathMLTree.MathNode("mspace"); node.setAttribute("width", width + "em"); return node }; function mathmlBuilder$3(group, style) { let node; const inner = buildExpression(group.body, style); if (group.mclass === "minner") { node = new mathMLTree.MathNode("mpadded", inner); } else if (group.mclass === "mord") { if (group.isCharacterBox || inner[0].type === "mathord") { node = inner[0]; node.type = "mi"; if (node.children.length === 1 && node.children[0].text && node.children[0].text === "∇") { node.setAttribute("mathvariant", "normal"); } } else { node = new mathMLTree.MathNode("mi", inner); } } else { node = new mathMLTree.MathNode("mrow", inner); if (group.mustPromote) { node = inner[0]; node.type = "mo"; if (group.isCharacterBox && group.body[0].text && /[A-Za-z]/.test(group.body[0].text)) { node.setAttribute("mathvariant", "italic"); } } else { node = new mathMLTree.MathNode("mrow", inner); } // Set spacing based on what is the most likely adjacent atom type. // See TeXbook p170. const doSpacing = style.level < 2; // Operator spacing is zero inside a (sub|super)script. if (node.type === "mrow") { if (doSpacing ) { if (group.mclass === "mbin") { // medium space node.children.unshift(padding(0.2222)); node.children.push(padding(0.2222)); } else if (group.mclass === "mrel") { // thickspace node.children.unshift(padding(0.2778)); node.children.push(padding(0.2778)); } else if (group.mclass === "mpunct") { node.children.push(padding(0.1667)); } else if (group.mclass === "minner") { node.children.unshift(padding(0.0556)); // 1 mu is the most likely option node.children.push(padding(0.0556)); } } } else { if (group.mclass === "mbin") { // medium space node.attributes.lspace = (doSpacing ? "0.2222em" : "0"); node.attributes.rspace = (doSpacing ? "0.2222em" : "0"); } else if (group.mclass === "mrel") { // thickspace node.attributes.lspace = (doSpacing ? "0.2778em" : "0"); node.attributes.rspace = (doSpacing ? "0.2778em" : "0"); } else if (group.mclass === "mpunct") { node.attributes.lspace = "0em"; node.attributes.rspace = (doSpacing ? "0.1667em" : "0"); } else if (group.mclass === "mopen" || group.mclass === "mclose") { node.attributes.lspace = "0em"; node.attributes.rspace = "0em"; } else if (group.mclass === "minner" && doSpacing) { node.attributes.lspace = "0.0556em"; // 1 mu is the most likely option node.attributes.width = "+0.1111em"; } } if (!(group.mclass === "mopen" || group.mclass === "mclose")) { delete node.attributes.stretchy; delete node.attributes.form; } } return node; } // Math class commands except \mathop defineFunction({ type: "mclass", names: [ "\\mathord", "\\mathbin", "\\mathrel", "\\mathopen", "\\mathclose", "\\mathpunct", "\\mathinner" ], props: { numArgs: 1, primitive: true }, handler({ parser, funcName }, args) { const body = args[0]; const isCharacterBox = utils.isCharacterBox(body); // We should not wrap a around a or . That would be invalid MathML. // In that case, we instead promote the text contents of the body to the parent. let mustPromote = true; const mord = { type: "mathord", text: "", mode: parser.mode }; const arr = (body.body) ? body.body : [body]; for (const arg of arr) { if (textAtomTypes.includes(arg.type)) { if (symbols[parser.mode][arg.text]) { mord.text += symbols[parser.mode][arg.text].replace; } else if (arg.text) { mord.text += arg.text; } else if (arg.body) { arg.body.map(e => { mord.text += e.text; }); } } else { mustPromote = false; break } } return { type: "mclass", mode: parser.mode, mclass: "m" + funcName.slice(5), body: ordargument(mustPromote ? mord : body), isCharacterBox, mustPromote }; }, mathmlBuilder: mathmlBuilder$3 }); const 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. const 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({ parser }, args) { 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({ parser, funcName }, args) { const baseArg = args[1]; const shiftedArg = args[0]; const baseOp = { type: "op", mode: baseArg.mode, limits: true, alwaysHandleSupSub: true, parentIsSupSub: false, symbol: false, stack: true, suppressBaseShift: funcName !== "\\stackrel", body: ordargument(baseArg) }; return { type: "supsub", mode: shiftedArg.mode, base: baseOp, sup: funcName === "\\underset" ? null : shiftedArg, sub: funcName === "\\underset" ? shiftedArg : null }; }, mathmlBuilder: mathmlBuilder$3 }); // Helper function const buildGroup = (el, style, noneNode) => { if (!el) { return noneNode } const node = buildGroup$1(el, style); if (node.type === "mrow" && node.children.length === 0) { return noneNode } return node }; defineFunction({ type: "multiscript", names: ["\\sideset", "\\pres@cript"], // See macros.js for \prescript props: { numArgs: 3 }, handler({ parser, funcName, token }, args) { if (args[2].body.length === 0) { throw new ParseError(funcName + `cannot parse an empty base.`) } const base = args[2].body[0]; if (parser.settings.strict && funcName === "\\sideset" && !base.symbol) { throw new ParseError(`The base of \\sideset must be a big operator. Try \\prescript.`) } if ((args[0].body.length > 0 && args[0].body[0].type !== "supsub") || (args[1].body.length > 0 && args[1].body[0].type !== "supsub")) { throw new ParseError("\\sideset can parse only subscripts and " + "superscripts in its first two arguments", token) } // The prescripts and postscripts come wrapped in a supsub. const prescripts = args[0].body.length > 0 ? args[0].body[0] : null; const postscripts = args[1].body.length > 0 ? args[1].body[0] : null; if (!prescripts && !postscripts) { return base } else if (!prescripts) { // It's not a multi-script. Get a \textstyle supsub. return { type: "styling", mode: parser.mode, scriptLevel: "text", body: [{ type: "supsub", mode: parser.mode, base, sup: postscripts.sup, sub: postscripts.sub }] } } else { return { type: "multiscript", mode: parser.mode, isSideset: funcName === "\\sideset", prescripts, postscripts, base } } }, mathmlBuilder(group, style) { const base = buildGroup$1(group.base, style); const prescriptsNode = new mathMLTree.MathNode("mprescripts"); const noneNode = new mathMLTree.MathNode("none"); let children = []; const preSub = buildGroup(group.prescripts.sub, style, noneNode); const preSup = buildGroup(group.prescripts.sup, style, noneNode); if (group.isSideset) { // This seems silly, but LaTeX does this. Firefox ignores it, which does not make me sad. preSub.setAttribute("style", "text-align: left;"); preSup.setAttribute("style", "text-align: left;"); } if (group.postscripts) { const postSub = buildGroup(group.postscripts.sub, style, noneNode); const postSup = buildGroup(group.postscripts.sup, style, noneNode); children = [base, postSub, postSup, prescriptsNode, preSub, preSup]; } else { children = [base, prescriptsNode, preSub, preSup]; } return new mathMLTree.MathNode("mmultiscripts", children); } }); defineFunction({ type: "not", names: ["\\not"], props: { numArgs: 1, primitive: true, allowedInText: false }, handler({ parser }, args) { const isCharacterBox = utils.isCharacterBox(args[0]); let body; if (isCharacterBox) { body = ordargument(args[0]); if (body[0].text.charAt(0) === "\\") { body[0].text = symbols.math[body[0].text].replace; } // \u0338 is the Unicode Combining Long Solidus Overlay body[0].text = body[0].text.slice(0, 1) + "\u0338" + body[0].text.slice(1); } else { // When the argument is not a character box, TeX does an awkward, poorly placed overlay. // We'll do the same. const notNode = { type: "textord", mode: "math", text: "\u0338" }; const kernNode = { type: "kern", mode: "math", dimension: { number: -0.6, unit: "em" } }; body = [notNode, kernNode, args[0]]; } return { type: "not", mode: parser.mode, body, isCharacterBox }; }, mathmlBuilder(group, style) { if (group.isCharacterBox) { const inner = buildExpression(group.body, style, true); return inner[0] } else { return buildExpressionRow(group.body, style) } } }); // Limits, symbols // Some helpers const ordAtomTypes = ["textord", "mathord", "atom"]; // Most operators have a large successor symbol, but these don't. const noSuccessor = ["\\smallint"]; // Math operators (e.g. \sin) need a space between these types and themselves: const ordTypes = ["textord", "mathord", "ordgroup", "close", "leftright"]; // NOTE: Unlike most `builders`s, this one handles not only "op", but also // "supsub" since some of them (like \int) can affect super/subscripting. const setSpacing = node => { // The user wrote a \mathop{…} function. Change spacing from default to OP spacing. // The most likely spacing for an OP is a thin space per TeXbook p170. node.attributes.lspace = "0.1667em"; node.attributes.rspace = "0.1667em"; }; const mathmlBuilder$2 = (group, style) => { let node; if (group.symbol) { // This is a symbol. Just add the symbol. node = new MathNode("mo", [makeText(group.name, group.mode)]); if (noSuccessor.includes(group.name)) { node.setAttribute("largeop", "false"); } else { node.setAttribute("movablelimits", "false"); } if (group.fromMathOp) { setSpacing(node); } } else if (group.body) { // This is an operator with children. Add them. node = new MathNode("mo", buildExpression(group.body, style)); if (group.fromMathOp) { setSpacing(node); } } 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))]); if (!group.parentIsSupSub) { // Append an invisible . // ref: https://www.w3.org/TR/REC-MathML/chap3_2.html#sec3.2.4 const operator = new MathNode("mo", [makeText("\u2061", "text")]); const row = [node, operator]; // Set spacing if (group.needsLeadingSpace) { const lead = new MathNode("mspace"); lead.setAttribute("width", "0.1667em"); // thin space. row.unshift(lead); } if (!group.isFollowedByDelimiter) { const trail = new MathNode("mspace"); trail.setAttribute("width", "0.1667em"); // thin space. row.push(trail); } node = new MathNode("mrow", row); } } return node; }; const singleCharBigOps = { "\u220F": "\\prod", "\u2210": "\\coprod", "\u2211": "\\sum", "\u22c0": "\\bigwedge", "\u22c1": "\\bigvee", "\u22c2": "\\bigcap", "\u22c3": "\\bigcup", "\u2a00": "\\bigodot", "\u2a01": "\\bigoplus", "\u2a02": "\\bigotimes", "\u2a04": "\\biguplus", "\u2a05": "\\bigsqcap", "\u2a06": "\\bigsqcup", "\u2a09": "\\bigtimes" }; defineFunction({ type: "op", names: [ "\\coprod", "\\bigvee", "\\bigwedge", "\\biguplus", "\\bigcap", "\\bigcup", "\\intop", "\\prod", "\\sum", "\\bigotimes", "\\bigoplus", "\\bigodot", "\\bigsqcap", "\\bigsqcup", "\\bigtimes", "\\smallint", "\u220F", "\u2210", "\u2211", "\u22c0", "\u22c1", "\u22c2", "\u22c3", "\u2a00", "\u2a01", "\u2a02", "\u2a04", "\u2a06" ], props: { numArgs: 0 }, handler: ({ parser, funcName }, args) => { let fName = funcName; if (fName.length === 1) { fName = singleCharBigOps[fName]; } return { type: "op", mode: parser.mode, limits: true, parentIsSupSub: false, symbol: true, stack: false, // This is true for \stackrel{}, not here. name: fName }; }, mathmlBuilder: mathmlBuilder$2 }); // Note: calling defineFunction with a type that's already been defined only // works because the same mathmlBuilder is being used. defineFunction({ type: "op", names: ["\\mathop"], props: { numArgs: 1, primitive: true }, handler: ({ parser }, args) => { const body = args[0]; // It would be convienient to just wrap a around the argument. // But if the argument is a or , that would be invalid MathML. // In that case, we instead promote the text contents of the body to the parent. const arr = (body.body) ? body.body : [body]; const isSymbol = arr.length === 1 && ordAtomTypes.includes(arr[0].type); return { type: "op", mode: parser.mode, limits: true, parentIsSupSub: false, symbol: isSymbol, fromMathOp: true, stack: false, name: isSymbol ? arr[0].text : null, body: isSymbol ? null : ordargument(body) }; }, mathmlBuilder: mathmlBuilder$2 }); // 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. const singleCharIntegrals = { "\u222b": "\\int", "\u222c": "\\iint", "\u222d": "\\iiint", "\u222e": "\\oint", "\u222f": "\\oiint", "\u2230": "\\oiiint", "\u2231": "\\intclockwise", "\u2232": "\\varointclockwise", "\u2a0c": "\\iiiint", "\u2a0d": "\\intbar", "\u2a0e": "\\intBar", "\u2a0f": "\\fint", "\u2a12": "\\rppolint", "\u2a13": "\\scpolint", "\u2a15": "\\pointint", "\u2a16": "\\sqint", "\u2a17": "\\intlarhk", "\u2a18": "\\intx", "\u2a19": "\\intcap", "\u2a1a": "\\intcup" }; // 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", "\\sgn", "\\tan", "\\tanh", "\\tg", "\\th" ], props: { numArgs: 0 }, handler({ parser, funcName }) { const prevAtomType = parser.prevAtomType; const next = parser.gullet.future().text; return { type: "op", mode: parser.mode, limits: false, parentIsSupSub: false, symbol: false, stack: false, isFollowedByDelimiter: isDelimiter(next), needsLeadingSpace: prevAtomType.length > 0 && ordTypes.includes(prevAtomType), name: funcName }; }, mathmlBuilder: mathmlBuilder$2 }); // Limits, not symbols defineFunction({ type: "op", names: ["\\det", "\\gcd", "\\inf", "\\lim", "\\max", "\\min", "\\Pr", "\\sup"], props: { numArgs: 0 }, handler({ parser, funcName }) { const prevAtomType = parser.prevAtomType; const next = parser.gullet.future().text; return { type: "op", mode: parser.mode, limits: true, parentIsSupSub: false, symbol: false, stack: false, isFollowedByDelimiter: isDelimiter(next), needsLeadingSpace: prevAtomType.length > 0 && ordTypes.includes(prevAtomType), name: funcName }; }, mathmlBuilder: mathmlBuilder$2 }); // No limits, symbols defineFunction({ type: "op", names: [ "\\int", "\\iint", "\\iiint", "\\iiiint", "\\oint", "\\oiint", "\\oiiint", "\\intclockwise", "\\varointclockwise", "\\intbar", "\\intBar", "\\fint", "\\rppolint", "\\scpolint", "\\pointint", "\\sqint", "\\intlarhk", "\\intx", "\\intcap", "\\intcup", "\u222b", "\u222c", "\u222d", "\u222e", "\u222f", "\u2230", "\u2231", "\u2232", "\u2a0c", "\u2a0d", "\u2a0e", "\u2a0f", "\u2a12", "\u2a13", "\u2a15", "\u2a16", "\u2a17", "\u2a18", "\u2a19", "\u2a1a" ], props: { numArgs: 0 }, handler({ parser, funcName }) { let fName = funcName; if (fName.length === 1) { fName = singleCharIntegrals[fName]; } return { type: "op", mode: parser.mode, limits: false, parentIsSupSub: false, symbol: true, stack: false, name: fName }; }, mathmlBuilder: mathmlBuilder$2 }); /** * 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`. */ const _macros = {}; // This function might one day accept an additional argument and do more things. function defineMacro(name, body) { _macros[name] = body; } // NOTE: Unlike most builders, this one handles not only // "operatorname", but also "supsub" since \operatorname* can // affect super/subscripting. const mathmlBuilder$1 = (group, style) => { let expression = buildExpression(group.body, style.withFont("mathrm")); // Is expression a string or has it something like a fraction? let isAllString = true; // default for (let i = 0; i < expression.length; i++) { let node = expression[i]; if (node instanceof mathMLTree.MathNode) { if (node.type === "mrow" && node.children.length === 1 && node.children[0] instanceof mathMLTree.MathNode) { node = node.children[0]; } switch (node.type) { case "mi": case "mn": case "ms": case "mtext": break; // Do nothing yet. case "mspace": { if (node.attributes.width) { const width = node.attributes.width.replace("em", ""); const ch = spaceCharacter(Number(width)); if (ch === "") { isAllString = false; } else { expression[i] = new mathMLTree.MathNode("mtext", [new mathMLTree.TextNode(ch)]); } } } break case "mo": { const 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. const word = expression.map((node) => node.toText()).join(""); expression = [new mathMLTree.TextNode(word)]; } else if ( expression.length === 1 && ["mover", "munder"].includes(expression[0].type) && (expression[0].children[0].type === "mi" || expression[0].children[0].type === "mtext") ) { expression[0].children[0].type = "mi"; if (group.parentIsSupSub) { return new mathMLTree.MathNode("mrow", expression) } else { const operator = new mathMLTree.MathNode("mo", [makeText("\u2061", "text")]); return mathMLTree.newDocumentFragment([expression[0], operator]) } } let wrapper; if (isAllString) { wrapper = new mathMLTree.MathNode("mi", expression); if (expression[0].text.length === 1) { wrapper.setAttribute("mathvariant", "normal"); } } else { wrapper = new mathMLTree.MathNode("mrow", expression); } if (!group.parentIsSupSub) { // Append an . // ref: https://www.w3.org/TR/REC-MathML/chap3_2.html#sec3.2.4 const operator = new mathMLTree.MathNode("mo", [makeText("\u2061", "text")]); const fragment = [wrapper, operator]; if (group.needsLeadingSpace) { // LaTeX gives operator spacing, but a gets ord spacing. // So add a leading space. const space = new mathMLTree.MathNode("mspace"); space.setAttribute("width", "0.1667em"); // thin space. fragment.unshift(space); } if (!group.isFollowedByDelimiter) { const trail = new mathMLTree.MathNode("mspace"); trail.setAttribute("width", "0.1667em"); // thin space. fragment.push(trail); } return mathMLTree.newDocumentFragment(fragment) } return wrapper }; // \operatorname // amsopn.dtx: \mathop{#1\kern\z@\operator@font#3}\newmcodes@ defineFunction({ type: "operatorname", names: ["\\operatorname@", "\\operatornamewithlimits"], props: { numArgs: 1, allowedInArgument: true }, handler: ({ parser, funcName }, args) => { const body = args[0]; const prevAtomType = parser.prevAtomType; const next = parser.gullet.future().text; return { type: "operatorname", mode: parser.mode, body: ordargument(body), alwaysHandleSupSub: (funcName === "\\operatornamewithlimits"), limits: false, parentIsSupSub: false, isFollowedByDelimiter: isDelimiter(next), needsLeadingSpace: prevAtomType.length > 0 && ordTypes.includes(prevAtomType) }; }, mathmlBuilder: mathmlBuilder$1 }); defineMacro("\\operatorname", "\\@ifstar\\operatornamewithlimits\\operatorname@"); defineFunctionBuilders({ type: "ordgroup", mathmlBuilder(group, style) { return buildExpressionRow(group.body, style, group.semisimple); } }); defineFunction({ type: "phantom", names: ["\\phantom"], props: { numArgs: 1, allowedInText: true }, handler: ({ parser }, args) => { const body = args[0]; return { type: "phantom", mode: parser.mode, body: ordargument(body) }; }, mathmlBuilder: (group, style) => { const inner = buildExpression(group.body, style); return new mathMLTree.MathNode("mphantom", inner); } }); defineFunction({ type: "hphantom", names: ["\\hphantom"], props: { numArgs: 1, allowedInText: true }, handler: ({ parser }, args) => { const body = args[0]; return { type: "hphantom", mode: parser.mode, body }; }, mathmlBuilder: (group, style) => { const inner = buildExpression(ordargument(group.body), style); const phantom = new mathMLTree.MathNode("mphantom", inner); const 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: ({ parser }, args) => { const body = args[0]; return { type: "vphantom", mode: parser.mode, body }; }, mathmlBuilder: (group, style) => { const inner = buildExpression(ordargument(group.body), style); const phantom = new mathMLTree.MathNode("mphantom", inner); const node = new mathMLTree.MathNode("mpadded", [phantom]); node.setAttribute("width", "0px"); return node; } }); // In LaTeX, \pmb is a simulation of bold font. // The version of \pmb in ambsy.sty works by typesetting three copies of the argument // with small offsets. We use CSS font-weight:bold. defineFunction({ type: "pmb", names: ["\\pmb"], props: { numArgs: 1, allowedInText: true }, handler({ parser }, args) { return { type: "pmb", mode: parser.mode, body: ordargument(args[0]) } }, mathmlBuilder(group, style) { const inner = buildExpression(group.body, style); // Wrap with an element. const node = wrapWithMstyle(inner); node.setAttribute("style", "font-weight:bold"); return node } }); // \raise, \lower, and \raisebox const mathmlBuilder = (group, style) => { const newStyle = style.withLevel(StyleLevel.TEXT); const node = new mathMLTree.MathNode("mpadded", [buildGroup$1(group.body, newStyle)]); const dy = calculateSize(group.dy, style); node.setAttribute("voffset", dy.number + dy.unit); // Add padding, which acts to increase height in Chromium. // TODO: Figure out some way to change height in Firefox w/o breaking Chromium. if (dy.number > 0) { node.style.padding = dy.number + dy.unit + " 0 0 0"; } else { node.style.padding = "0 0 " + Math.abs(dy.number) + dy.unit + " 0"; } return node }; defineFunction({ type: "raise", names: ["\\raise", "\\lower"], props: { numArgs: 2, argTypes: ["size", "primitive"], primitive: true }, handler({ parser, funcName }, args) { const amount = assertNodeType(args[0], "size").value; if (funcName === "\\lower") { amount.number *= -1; } const body = args[1]; return { type: "raise", mode: parser.mode, dy: amount, body }; }, mathmlBuilder }); defineFunction({ type: "raise", names: ["\\raisebox"], props: { numArgs: 2, argTypes: ["size", "hbox"], allowedInText: true }, handler({ parser, funcName }, args) { const amount = assertNodeType(args[0], "size").value; const body = args[1]; return { type: "raise", mode: parser.mode, dy: amount, body }; }, mathmlBuilder }); defineFunction({ type: "ref", names: ["\\ref", "\\eqref"], props: { numArgs: 1, argTypes: ["raw"] }, handler({ parser, funcName }, args) { return { type: "ref", mode: parser.mode, funcName, string: args[0].string.replace(invalidIdRegEx, "") }; }, mathmlBuilder(group, style) { // Create an empty text node. Set a class and an href. // The post-processor will populate with the target's tag or equation number. const classes = group.funcName === "\\ref" ? ["tml-ref"] : ["tml-ref", "tml-eqref"]; const node = new mathMLTree.MathNode("mtext", [new mathMLTree.TextNode("")], classes); node.setAttribute("href", "#" + group.string); return node } }); defineFunction({ type: "reflect", names: ["\\reflectbox"], props: { numArgs: 1, argTypes: ["hbox"], allowedInText: true }, handler({ parser }, args) { return { type: "reflect", mode: parser.mode, body: args[0] }; }, mathmlBuilder(group, style) { const node = buildGroup$1(group.body, style); node.style.transform = "scaleX(-1)"; return node } }); defineFunction({ type: "internal", names: ["\\relax"], props: { numArgs: 0, allowedInText: true }, handler({ parser }) { return { type: "internal", mode: parser.mode }; } }); defineFunction({ type: "rule", names: ["\\rule"], props: { numArgs: 2, numOptionalArgs: 1, argTypes: ["size", "size", "size"] }, handler({ parser }, args, optArgs) { const shift = optArgs[0]; const width = assertNodeType(args[0], "size"); const height = assertNodeType(args[1], "size"); return { type: "rule", mode: parser.mode, shift: shift && assertNodeType(shift, "size").value, width: width.value, height: height.value }; }, mathmlBuilder(group, style) { const width = calculateSize(group.width, style); const height = calculateSize(group.height, style); const shift = group.shift ? calculateSize(group.shift, style) : { number: 0, unit: "em" }; const color = (style.color && style.getColor()) || "black"; const rule = new mathMLTree.MathNode("mspace"); if (width.number > 0 && height.number > 0) { rule.setAttribute("mathbackground", color); } rule.setAttribute("width", width.number + width.unit); rule.setAttribute("height", height.number + height.unit); if (shift.number === 0) { return rule } const wrapper = new mathMLTree.MathNode("mpadded", [rule]); if (shift.number >= 0) { wrapper.setAttribute("height", "+" + shift.number + shift.unit); } else { wrapper.setAttribute("height", shift.number + shift.unit); wrapper.setAttribute("depth", "+" + -shift.number + shift.unit); } wrapper.setAttribute("voffset", shift.number + shift.unit); return wrapper; } }); // The size mappings are taken from TeX with \normalsize=10pt. // We don't have to track script level. MathML does that. const sizeMap = { "\\tiny": 0.5, "\\sixptsize": 0.6, "\\Tiny": 0.6, "\\scriptsize": 0.7, "\\footnotesize": 0.8, "\\small": 0.9, "\\normalsize": 1.0, "\\large": 1.2, "\\Large": 1.44, "\\LARGE": 1.728, "\\huge": 2.074, "\\Huge": 2.488 }; defineFunction({ type: "sizing", names: [ "\\tiny", "\\sixptsize", "\\Tiny", "\\scriptsize", "\\footnotesize", "\\small", "\\normalsize", "\\large", "\\Large", "\\LARGE", "\\huge", "\\Huge" ], props: { numArgs: 0, allowedInText: true }, handler: ({ breakOnTokenText, funcName, parser }, args) => { if (parser.settings.strict && parser.mode === "math") { // eslint-disable-next-line no-console console.log(`Temml strict-mode warning: Command ${funcName} is invalid in math mode.`); } const body = parser.parseExpression(false, breakOnTokenText, true); return { type: "sizing", mode: parser.mode, funcName, body }; }, mathmlBuilder: (group, style) => { const newStyle = style.withFontSize(sizeMap[group.funcName]); const inner = buildExpression(group.body, newStyle); // Wrap with an element. const node = wrapWithMstyle(inner); const factor = (sizeMap[group.funcName] / style.fontSize).toFixed(4); node.setAttribute("mathsize", factor + "em"); return node; } }); // smash, with optional [tb], as in AMS defineFunction({ type: "smash", names: ["\\smash"], props: { numArgs: 1, numOptionalArgs: 1, allowedInText: true }, handler: ({ parser }, args, optArgs) => { let smashHeight = false; let smashDepth = false; const 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}% let letter = ""; for (let i = 0; i < tbArg.body.length; ++i) { const node = tbArg.body[i]; // TODO: Write an AssertSymbolNode 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; } const body = args[0]; return { type: "smash", mode: parser.mode, body, smashHeight, smashDepth }; }, mathmlBuilder: (group, style) => { const node = new mathMLTree.MathNode("mpadded", [buildGroup$1(group.body, style)]); 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({ parser }, args, optArgs) { const index = optArgs[0]; const body = args[0]; return { type: "sqrt", mode: parser.mode, body, index }; }, mathmlBuilder(group, style) { const { body, index } = group; return index ? new mathMLTree.MathNode("mroot", [ buildGroup$1(body, style), buildGroup$1(index, style.incrementLevel()) ]) : new mathMLTree.MathNode("msqrt", [buildGroup$1(body, style)]); } }); const styleMap = { display: 0, text: 1, script: 2, scriptscript: 3 }; const styleAttributes = { display: ["0", "true"], text: ["0", "false"], script: ["1", "false"], scriptscript: ["2", "false"] }; defineFunction({ type: "styling", names: ["\\displaystyle", "\\textstyle", "\\scriptstyle", "\\scriptscriptstyle"], props: { numArgs: 0, allowedInText: true, primitive: true }, handler({ breakOnTokenText, funcName, parser }, args) { // parse out the implicit body const body = parser.parseExpression(true, breakOnTokenText, true); const scriptLevel = funcName.slice(1, funcName.length - 5); return { type: "styling", mode: parser.mode, // Figure out what scriptLevel to use by pulling out the scriptLevel from // the function name scriptLevel, body }; }, mathmlBuilder(group, style) { // Figure out what scriptLevel we're changing to. const newStyle = style.withLevel(styleMap[group.scriptLevel]); // The style argument in the next line does NOT directly set a MathML script level. // It just tracks the style level, in case we need to know it for supsub or mathchoice. const inner = buildExpression(group.body, newStyle); // Wrap with an element. const node = wrapWithMstyle(inner); const attr = styleAttributes[group.scriptLevel]; // Here is where we set the MathML script level. 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. */ // Helpers const symbolRegEx = /^m(over|under|underover)$/; // Super scripts and subscripts, whose precise placement can depend on other // functions that precede them. defineFunctionBuilders({ type: "supsub", mathmlBuilder(group, style) { // Is the inner group a relevant horizonal brace? let isBrace = false; let isOver; let isSup; let appendApplyFunction = false; let appendSpace = false; let needsLeadingSpace = false; 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.stack && (group.base.type === "op" || group.base.type === "operatorname")) { group.base.parentIsSupSub = true; appendApplyFunction = !group.base.symbol; appendSpace = appendApplyFunction && !group.isFollowedByDelimiter; needsLeadingSpace = group.base.needsLeadingSpace; } const children = group.base && group.base.stack ? [buildGroup$1(group.base.body[0], style)] : [buildGroup$1(group.base, style)]; const childStyle = style.inSubOrSup(); if (group.sub) { children.push(buildGroup$1(group.sub, childStyle)); } if (group.sup) { const sup = buildGroup$1(group.sup, childStyle); const testNode = sup.type === "mrow" ? sup.children[0] : sup; if ((testNode.type === "mo" && testNode.classes.includes("tml-prime")) && group.base && group.base.text && group.base.text === "f") { // Chromium does not address italic correction on prime. Prevent f′ from overlapping. testNode.classes.push("prime-pad"); } children.push(sup); } let nodeType; if (isBrace) { nodeType = isOver ? "mover" : "munder"; } else if (!group.sub) { const base = group.base; if ( base && base.type === "op" && base.limits && (style.level === StyleLevel.DISPLAY || base.alwaysHandleSupSub) ) { nodeType = "mover"; } else if ( base && base.type === "operatorname" && base.alwaysHandleSupSub && (base.limits || style.level === StyleLevel.DISPLAY) ) { nodeType = "mover"; } else { nodeType = "msup"; } } else if (!group.sup) { const base = group.base; if ( base && base.type === "op" && base.limits && (style.level === StyleLevel.DISPLAY || base.alwaysHandleSupSub) ) { nodeType = "munder"; } else if ( base && base.type === "operatorname" && base.alwaysHandleSupSub && (base.limits || style.level === StyleLevel.DISPLAY) ) { nodeType = "munder"; } else { nodeType = "msub"; } } else { const base = group.base; if (base && ((base.type === "op" && base.limits) || base.type === "multiscript") && (style.level === StyleLevel.DISPLAY || base.alwaysHandleSupSub) ) { nodeType = "munderover"; } else if ( base && base.type === "operatorname" && base.alwaysHandleSupSub && (style.level === StyleLevel.DISPLAY || base.limits) ) { nodeType = "munderover"; } else { nodeType = "msubsup"; } } let node = new mathMLTree.MathNode(nodeType, children); if (appendApplyFunction) { // Append an . // ref: https://www.w3.org/TR/REC-MathML/chap3_2.html#sec3.2.4 const operator = new mathMLTree.MathNode("mo", [makeText("\u2061", "text")]); if (needsLeadingSpace) { const space = new mathMLTree.MathNode("mspace"); space.setAttribute("width", "0.1667em"); // thin space. node = mathMLTree.newDocumentFragment([space, node, operator]); } else { node = mathMLTree.newDocumentFragment([node, operator]); } if (appendSpace) { const space = new mathMLTree.MathNode("mspace"); space.setAttribute("width", "0.1667em"); // thin space. node.children.push(space); } } else if (symbolRegEx.test(nodeType)) { // Wrap in a . Otherwise Firefox stretchy parens will not stretch to include limits. node = new mathMLTree.MathNode("mrow", [node]); } return node } }); // Operator ParseNodes created in Parser.js from symbol Groups in src/symbols.js. const short = ["\\shortmid", "\\nshortmid", "\\shortparallel", "\\nshortparallel", "\\smallsetminus"]; const arrows = ["\\Rsh", "\\Lsh", "\\restriction"]; const isArrow = str => { if (str.length === 1) { const codePoint = str.codePointAt(0); return (0x218f < codePoint && codePoint < 0x2200) } return str.indexOf("arrow") > -1 || str.indexOf("harpoon") > -1 || arrows.includes(str) }; defineFunctionBuilders({ type: "atom", mathmlBuilder(group, style) { const node = new mathMLTree.MathNode("mo", [makeText(group.text, group.mode)]); 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. if (group.family === "open") { node.setAttribute("form", "prefix"); // Set an explicit attribute for stretch. Otherwise Firefox may do it wrong. node.setAttribute("stretchy", "false"); } else if (group.family === "close") { node.setAttribute("form", "postfix"); node.setAttribute("stretchy", "false"); } } else if (group.text === "\\mid") { // Firefox messes up this spacing if at the end of an . See it explicitly. node.setAttribute("lspace", "0.22em"); // medium space node.setAttribute("rspace", "0.22em"); node.setAttribute("stretchy", "false"); } else if (group.family === "rel" && isArrow(group.text)) { node.setAttribute("stretchy", "false"); } else if (short.includes(group.text)) { node.setAttribute("mathsize", "70%"); } else if (group.text === ":") { // ":" is not in the MathML operator dictionary. Give it BIN spacing. node.attributes.lspace = "0.2222em"; node.attributes.rspace = "0.2222em"; } return node; } }); /** * Maps TeX font commands to "mathvariant" attribute in buildMathML.js */ const fontMap = { // styles mathbf: "bold", mathrm: "normal", textit: "italic", mathit: "italic", mathnormal: "italic", // families mathbb: "double-struck", mathcal: "script", mathfrak: "fraktur", mathscr: "script", mathsf: "sans-serif", mathtt: "monospace" }; /** * Returns the math variant as a string or null if none is required. */ const getVariant = function(group, style) { // Handle font specifiers as best we can. // Chromium does not support the MathML mathvariant attribute. // So we'll use Unicode replacement characters instead. // But first, determine the math variant. // Deal with the \textit, \textbf, etc., functions. if (style.fontFamily === "texttt") { return "monospace" } else if (style.fontFamily === "textsc") { return "normal"; // handled via character substitution in symbolsOrd.js. } else if (style.fontFamily === "textsf") { if (style.fontShape === "textit" && style.fontWeight === "textbf") { return "sans-serif-bold-italic" } else if (style.fontShape === "textit") { return "sans-serif-italic" } else if (style.fontWeight === "textbf") { return "sans-serif-bold" } else { return "sans-serif" } } else if (style.fontShape === "textit" && style.fontWeight === "textbf") { return "bold-italic" } else if (style.fontShape === "textit") { return "italic" } else if (style.fontWeight === "textbf") { return "bold" } // Deal with the \mathit, mathbf, etc, functions. const font = style.font; if (!font || font === "mathnormal") { return null } const mode = group.mode; switch (font) { case "mathit": return "italic" case "mathrm": { const codePoint = group.text.codePointAt(0); // LaTeX \mathrm returns italic for Greek characters. return (0x03ab < codePoint && codePoint < 0x03cf) ? "italic" : "normal" } case "greekItalic": return "italic" case "up@greek": return "normal" case "boldsymbol": case "mathboldsymbol": return "bold-italic" case "mathbf": return "bold" case "mathbb": return "double-struck" case "mathfrak": return "fraktur" case "mathscr": case "mathcal": return "script" case "mathsf": return "sans-serif" case "mathtt": return "monospace" } let text = group.text; if (symbols[mode][text] && symbols[mode][text].replace) { text = symbols[mode][text].replace; } return Object.prototype.hasOwnProperty.call(fontMap, font) ? fontMap[font] : null }; // Chromium does not support the MathML `mathvariant` attribute. // Instead, we replace ASCII characters with Unicode characters that // are defined in the font as bold, italic, double-struck, etc. // This module identifies those Unicode code points. // First, a few helpers. const script = Object.freeze({ B: 0x20EA, // Offset from ASCII B to Unicode script B E: 0x20EB, F: 0x20EB, H: 0x20C3, I: 0x20C7, L: 0x20C6, M: 0x20E6, R: 0x20C9, e: 0x20CA, g: 0x20A3, o: 0x20C5 }); const frak = Object.freeze({ C: 0x20EA, H: 0x20C4, I: 0x20C8, R: 0x20CA, Z: 0x20CE }); const bbb = Object.freeze({ C: 0x20BF, // blackboard bold H: 0x20C5, N: 0x20C7, P: 0x20C9, Q: 0x20C9, R: 0x20CB, Z: 0x20CA }); const bold = Object.freeze({ "\u03f5": 0x1D2E7, // lunate epsilon "\u03d1": 0x1D30C, // vartheta "\u03f0": 0x1D2EE, // varkappa "\u03c6": 0x1D319, // varphi "\u03f1": 0x1D2EF, // varrho "\u03d6": 0x1D30B // varpi }); const boldItalic = Object.freeze({ "\u03f5": 0x1D35B, // lunate epsilon "\u03d1": 0x1D380, // vartheta "\u03f0": 0x1D362, // varkappa "\u03c6": 0x1D38D, // varphi "\u03f1": 0x1D363, // varrho "\u03d6": 0x1D37F // varpi }); const boldsf = Object.freeze({ "\u03f5": 0x1D395, // lunate epsilon "\u03d1": 0x1D3BA, // vartheta "\u03f0": 0x1D39C, // varkappa "\u03c6": 0x1D3C7, // varphi "\u03f1": 0x1D39D, // varrho "\u03d6": 0x1D3B9 // varpi }); const bisf = Object.freeze({ "\u03f5": 0x1D3CF, // lunate epsilon "\u03d1": 0x1D3F4, // vartheta "\u03f0": 0x1D3D6, // varkappa "\u03c6": 0x1D401, // varphi "\u03f1": 0x1D3D7, // varrho "\u03d6": 0x1D3F3 // varpi }); // Code point offsets below are derived from https://www.unicode.org/charts/PDF/U1D400.pdf const offset = Object.freeze({ upperCaseLatin: { // A-Z "normal": ch => { return 0 }, "bold": ch => { return 0x1D3BF }, "italic": ch => { return 0x1D3F3 }, "bold-italic": ch => { return 0x1D427 }, "script": ch => { return script[ch] || 0x1D45B }, "script-bold": ch => { return 0x1D48F }, "fraktur": ch => { return frak[ch] || 0x1D4C3 }, "fraktur-bold": ch => { return 0x1D52B }, "double-struck": ch => { return bbb[ch] || 0x1D4F7 }, "sans-serif": ch => { return 0x1D55F }, "sans-serif-bold": ch => { return 0x1D593 }, "sans-serif-italic": ch => { return 0x1D5C7 }, "sans-serif-bold-italic": ch => { return 0x1D63C }, "monospace": ch => { return 0x1D62F } }, lowerCaseLatin: { // a-z "normal": ch => { return 0 }, "bold": ch => { return 0x1D3B9 }, "italic": ch => { return ch === "h" ? 0x20A6 : 0x1D3ED }, "bold-italic": ch => { return 0x1D421 }, "script": ch => { return script[ch] || 0x1D455 }, "script-bold": ch => { return 0x1D489 }, "fraktur": ch => { return 0x1D4BD }, "fraktur-bold": ch => { return 0x1D525 }, "double-struck": ch => { return 0x1D4F1 }, "sans-serif": ch => { return 0x1D559 }, "sans-serif-bold": ch => { return 0x1D58D }, "sans-serif-italic": ch => { return 0x1D5C1 }, "sans-serif-bold-italic": ch => { return 0x1D5F5 }, "monospace": ch => { return 0x1D629 } }, upperCaseGreek: { // A-Ω "normal": ch => { return 0 }, "bold": ch => { return 0x1D317 }, "italic": ch => { return 0x1D351 }, // \boldsymbol actually returns upright bold for upperCaseGreek "bold-italic": ch => { return 0x1D317 }, "script": ch => { return 0 }, "script-bold": ch => { return 0 }, "fraktur": ch => { return 0 }, "fraktur-bold": ch => { return 0 }, "double-struck": ch => { return 0 }, // Unicode has no code points for regular-weight san-serif Greek. Use bold. "sans-serif": ch => { return 0x1D3C5 }, "sans-serif-bold": ch => { return 0x1D3C5 }, "sans-serif-italic": ch => { return 0 }, "sans-serif-bold-italic": ch => { return 0x1D3FF }, "monospace": ch => { return 0 } }, lowerCaseGreek: { // α-ω "normal": ch => { return 0 }, "bold": ch => { return 0x1D311 }, "italic": ch => { return 0x1D34B }, "bold-italic": ch => { return ch === "\u03d5" ? 0x1D37E : 0x1D385 }, "script": ch => { return 0 }, "script-bold": ch => { return 0 }, "fraktur": ch => { return 0 }, "fraktur-bold": ch => { return 0 }, "double-struck": ch => { return 0 }, // Unicode has no code points for regular-weight san-serif Greek. Use bold. "sans-serif": ch => { return 0x1D3BF }, "sans-serif-bold": ch => { return 0x1D3BF }, "sans-serif-italic": ch => { return 0 }, "sans-serif-bold-italic": ch => { return 0x1D3F9 }, "monospace": ch => { return 0 } }, varGreek: { // \varGamma, etc "normal": ch => { return 0 }, "bold": ch => { return bold[ch] || -51 }, "italic": ch => { return 0 }, "bold-italic": ch => { return boldItalic[ch] || 0x3A }, "script": ch => { return 0 }, "script-bold": ch => { return 0 }, "fraktur": ch => { return 0 }, "fraktur-bold": ch => { return 0 }, "double-struck": ch => { return 0 }, "sans-serif": ch => { return boldsf[ch] || 0x74 }, "sans-serif-bold": ch => { return boldsf[ch] || 0x74 }, "sans-serif-italic": ch => { return 0 }, "sans-serif-bold-italic": ch => { return bisf[ch] || 0xAE }, "monospace": ch => { return 0 } }, numeral: { // 0-9 "normal": ch => { return 0 }, "bold": ch => { return 0x1D79E }, "italic": ch => { return 0 }, "bold-italic": ch => { return 0 }, "script": ch => { return 0 }, "script-bold": ch => { return 0 }, "fraktur": ch => { return 0 }, "fraktur-bold": ch => { return 0 }, "double-struck": ch => { return 0x1D7A8 }, "sans-serif": ch => { return 0x1D7B2 }, "sans-serif-bold": ch => { return 0x1D7BC }, "sans-serif-italic": ch => { return 0 }, "sans-serif-bold-italic": ch => { return 0 }, "monospace": ch => { return 0x1D7C6 } } }); const variantChar = (ch, variant) => { const codePoint = ch.codePointAt(0); const block = 0x40 < codePoint && codePoint < 0x5b ? "upperCaseLatin" : 0x60 < codePoint && codePoint < 0x7b ? "lowerCaseLatin" : (0x390 < codePoint && codePoint < 0x3AA) ? "upperCaseGreek" : 0x3B0 < codePoint && codePoint < 0x3CA || ch === "\u03d5" ? "lowerCaseGreek" : 0x1D6E1 < codePoint && codePoint < 0x1D6FC || bold[ch] ? "varGreek" : (0x2F < codePoint && codePoint < 0x3A) ? "numeral" : "other"; return block === "other" ? ch : String.fromCodePoint(codePoint + offset[block][variant](ch)) }; const smallCaps = Object.freeze({ a: "ᴀ", b: "ʙ", c: "ᴄ", d: "ᴅ", e: "ᴇ", f: "ꜰ", g: "ɢ", h: "ʜ", i: "ɪ", j: "ᴊ", k: "ᴋ", l: "ʟ", m: "ᴍ", n: "ɴ", o: "ᴏ", p: "ᴘ", q: "ǫ", r: "ʀ", s: "s", t: "ᴛ", u: "ᴜ", v: "ᴠ", w: "ᴡ", x: "x", y: "ʏ", z: "ᴢ" }); // "mathord" and "textord" ParseNodes created in Parser.js from symbol Groups in // src/symbols.js. const numberRegEx = /^\d(?:[\d,.]*\d)?$/; const latinRegEx = /[A-Ba-z]/; const primes = new Set(["\\prime", "\\dprime", "\\trprime", "\\qprime", "\\backprime", "\\backdprime", "\\backtrprime"]); const italicNumber = (text, variant, tag) => { const mn = new mathMLTree.MathNode(tag, [text]); const wrapper = new mathMLTree.MathNode("mstyle", [mn]); wrapper.style["font-style"] = "italic"; wrapper.style["font-family"] = "Cambria, 'Times New Roman', serif"; if (variant === "bold-italic") { wrapper.style["font-weight"] = "bold"; } return wrapper }; defineFunctionBuilders({ type: "mathord", mathmlBuilder(group, style) { const text = makeText(group.text, group.mode, style); const codePoint = text.text.codePointAt(0); // Test for upper-case Greek const defaultVariant = (0x0390 < codePoint && codePoint < 0x03aa) ? "normal" : "italic"; const variant = getVariant(group, style) || defaultVariant; if (variant === "script") { text.text = variantChar(text.text, variant); return new mathMLTree.MathNode("mi", [text], [style.font]) } else if (variant !== "italic") { text.text = variantChar(text.text, variant); } let node = new mathMLTree.MathNode("mi", [text]); // TODO: Handle U+1D49C - U+1D4CF per https://www.unicode.org/charts/PDF/U1D400.pdf if (variant === "normal") { node.setAttribute("mathvariant", "normal"); if (text.text.length === 1) { // A Firefox bug will apply spacing here, but there should be none. Fix it. node = new mathMLTree.MathNode("mrow", [node]); } } return node } }); defineFunctionBuilders({ type: "textord", mathmlBuilder(group, style) { let ch = group.text; const codePoint = ch.codePointAt(0); if (style.fontFamily === "textsc") { // Convert small latin letters to small caps. if (96 < codePoint && codePoint < 123) { ch = smallCaps[ch]; } } const text = makeText(ch, group.mode, style); const variant = getVariant(group, style) || "normal"; let node; if (numberRegEx.test(group.text)) { const tag = group.mode === "text" ? "mtext" : "mn"; if (variant === "italic" || variant === "bold-italic") { return italicNumber(text, variant, tag) } else { if (variant !== "normal") { text.text = text.text.split("").map(c => variantChar(c, variant)).join(""); } node = new mathMLTree.MathNode(tag, [text]); } } else if (group.mode === "text") { if (variant !== "normal") { text.text = variantChar(text.text, variant); } node = new mathMLTree.MathNode("mtext", [text]); } else if (primes.has(group.text)) { node = new mathMLTree.MathNode("mo", [text]); // TODO: If/when Chromium uses ssty variant for prime, remove the next line. node.classes.push("tml-prime"); } else { const origText = text.text; if (variant !== "italic") { text.text = variantChar(text.text, variant); } node = new mathMLTree.MathNode("mi", [text]); if (text.text === origText && latinRegEx.test(origText)) { node.setAttribute("mathvariant", "italic"); } } return node } }); // A map of CSS-based spacing functions to their CSS class. const 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`. const 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", mathmlBuilder(group, style) { let node; if (Object.prototype.hasOwnProperty.call(regularSpace, group.text)) { // Firefox does not render a space in a . So write a no-break space. // TODO: If Firefox fixes that bug, uncomment the next line and write ch into the node. //const ch = (regularSpace[group.text].className === "nobreak") ? "\u00a0" : " " node = new mathMLTree.MathNode("mtext", [new mathMLTree.TextNode("\u00a0")]); } else if (Object.prototype.hasOwnProperty.call(cssSpace, group.text)) { // MathML 3.0 calls for nobreak to occur in an , not an // Ref: https://www.w3.org/Math/draft-spec/mathml.html#chapter3_presm.lbattrs node = new mathMLTree.MathNode("mo"); if (group.text === "\\nobreak") { node.setAttribute("linebreak", "nobreak"); } } else { throw new ParseError(`Unknown type of space "${group.text}"`) } return node } }); defineFunctionBuilders({ type: "tag" }); // For a \tag, the work usually done in a mathmlBuilder is instead done in buildMathML.js. // That way, a \tag can be pulled out of the parse tree and wrapped around the outer node. // Non-mathy text, possibly in a font const textFontFamilies = { "\\text": undefined, "\\textrm": "textrm", "\\textsf": "textsf", "\\texttt": "texttt", "\\textnormal": "textrm", "\\textsc": "textsc" // small caps }; const textFontWeights = { "\\textbf": "textbf", "\\textmd": "textmd" }; const textFontShapes = { "\\textit": "textit", "\\textup": "textup" }; const styleWithFont = (group, style) => { const font = group.font; // Checks if the argument is a font family or a font style. if (!font) { return style; } else if (textFontFamilies[font]) { return style.withTextFontFamily(textFontFamilies[font]); } else if (textFontWeights[font]) { return style.withTextFontWeight(textFontWeights[font]); } else { return style.withTextFontShape(textFontShapes[font]); } }; defineFunction({ type: "text", names: [ // Font families "\\text", "\\textrm", "\\textsf", "\\texttt", "\\textnormal", "\\textsc", // Font weights "\\textbf", "\\textmd", // Font Shapes "\\textit", "\\textup" ], props: { numArgs: 1, argTypes: ["text"], allowedInArgument: true, allowedInText: true }, handler({ parser, funcName }, args) { const body = args[0]; return { type: "text", mode: parser.mode, body: ordargument(body), font: funcName }; }, mathmlBuilder(group, style) { const newStyle = styleWithFont(group, style); const mrow = buildExpressionRow(group.body, newStyle); return consolidateText(mrow) } }); 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"); }, mathmlBuilder(group, style) { const text = new mathMLTree.TextNode(makeVerb(group)); const 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 */ const makeVerb = (group) => group.body.replace(/ /g, group.star ? "\u2423" : "\xA0"); /** Include this to ensure that all functions are defined. */ const functions = _functions; /** * Lexing or parsing positional information for error reporting. * This object is immutable. */ class SourceLocation { constructor(lexer, start, end) { this.lexer = lexer; // Lexer holding the input string. this.start = start; // Start offset, zero-based inclusive. this.end = end; // End offset, zero-based exclusive. } /** * Merges two `SourceLocation`s from location providers, given they are * provided in order of appearance. * - Returns the first one's location if only the first is provided. * - Returns a merged range of the first and the last if both are provided * and their lexers match. * - Otherwise, returns null. */ static range(first, second) { if (!second) { return first && first.loc; } else if (!first || !first.loc || !second.loc || first.loc.lexer !== second.loc.lexer) { return null; } else { return new SourceLocation(first.loc.lexer, first.loc.start, second.loc.end); } } } /** * Interface required to break circular dependency between Token, Lexer, and * ParseError. */ /** * The resulting token returned from `lex`. * * It consists of the token text plus some position information. * The position information is essentially a range in an input string, * but instead of referencing the bare input string, we refer to the lexer. * That way it is possible to attach extra metadata to the input string, * like for example a file name or similar. * * The position information is optional, so it is OK to construct synthetic * tokens if appropriate. Not providing available position information may * lead to degraded error reporting, though. */ class Token { constructor( text, // the text of this token loc ) { this.text = text; this.loc = loc; } /** * Given a pair of tokens (this and endToken), compute a `Token` encompassing * the whole input range enclosed by these two. */ range( endToken, // last token of the range, inclusive text // the text of the newly constructed token ) { return new Token(text, SourceLocation.range(this, endToken)); } } /** * 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 two groups * - 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 * - mathches numerals * - 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. */ const spaceRegexString = "[ \r\n\t]"; const controlWordRegexString = "\\\\[a-zA-Z@]+"; const controlSymbolRegexString = "\\\\[^\uD800-\uDFFF]"; const controlWordWhitespaceRegexString = `(${controlWordRegexString})${spaceRegexString}*`; const controlSpaceRegexString = "\\\\(\n|[ \r\t]+\n?)[ \r\t]*"; const combiningDiacriticalMarkString = "[\u0300-\u036f]"; const combiningDiacriticalMarksEndRegex = new RegExp(`${combiningDiacriticalMarkString}+$`); const 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 { constructor(input, settings) { // Separate accents from characters this.input = input; this.settings = settings; this.tokenRegex = new RegExp(tokenRegexString, 'g'); // Category codes. The lexer only supports comment characters (14) for now. // MacroExpander additionally distinguishes active (13). this.catcodes = { "%": 14, // comment character "~": 13 // active character }; } setCatcode(char, code) { this.catcodes[char] = code; } /** * This function lexes a single token. */ lex() { const input = this.input; const pos = this.tokenRegex.lastIndex; if (pos === input.length) { return new Token("EOF", new SourceLocation(this, pos, pos)); } const 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)) ); } const text = match[6] || match[3] || (match[2] ? "\\ " : " "); if (this.catcodes[text] === 14) { // comment character const nlIndex = input.indexOf("\n", this.tokenRegex.lastIndex); if (nlIndex === -1) { this.tokenRegex.lastIndex = input.length; // EOF if (this.settings.strict) { throw new ParseError("% comment has no terminating newline; LaTeX would " + "fail because of commenting the end of math mode") } } 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 = {}) { 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" ); } const undefs = this.undefStack.pop(); for (const undef in undefs) { if (Object.prototype.hasOwnProperty.call(undefs, undef )) { if (undefs[undef] === undefined) { delete this.current[undef]; } else { this.current[undef] = undefs[undef]; } } } } /** * Detect whether `name` has a definition. Equivalent to * `get(name) != null`. */ has(name) { return Object.prototype.hasOwnProperty.call(this.current, name ) || Object.prototype.hasOwnProperty.call(this.builtins, 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 (Object.prototype.hasOwnProperty.call(this.current, 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. */ set(name, value, 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 (let 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. const top = this.undefStack[this.undefStack.length - 1]; if (top && !Object.prototype.hasOwnProperty.call(top, name )) { top[name] = this.current[name]; } } this.current[name] = value; } } /** * Predefined macros for Temml. * This can be used to define some commands in terms of others. */ const 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. const 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. const 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) { const 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) { const 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) { const args = context.consumeArgs(3); // symbol, if, else context.consumeSpaces(); const 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) { const args = context.consumeArgs(2); if (context.mode === "text") { return { tokens: args[0], numArgs: 0 }; } else { return { tokens: args[1], numArgs: 0 }; } }); const stringFromArg = arg => { // Reverse the order of the arg and return a string. let str = ""; for (let i = arg.length - 1; i > -1; i--) { str += arg[i].text; } return str }; // Lookup table for parsing numbers in base 8 through 16 const 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 }; const nextCharNumber = context => { const numStr = context.future().text; if (numStr === "EOF") { return [null, ""] } return [digitToNumber[numStr.charAt(0)], numStr] }; const appendCharNumbers = (number, numStr, base) => { for (let i = 1; i < numStr.length; i++) { const digit = digitToNumber[numStr.charAt(i)]; number *= base; number += digit; } return number }; // 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) { let token = context.popToken(); let base; let 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`. let numStr = token.text; number = digitToNumber[numStr.charAt(0)]; if (number == null || number >= base) { throw new ParseError(`Invalid base-${base} digit ${token.text}`); } number = appendCharNumbers(number, numStr, base); let digit; [digit, numStr] = nextCharNumber(context); while (digit != null && digit < base) { number *= base; number += digit; number = appendCharNumbers(number, numStr, base); context.popToken(); [digit, numStr] = nextCharNumber(context); } } return `\\@char{${number}}`; }); function recreateArgStr(context) { // Recreate the macro's original argument string from the array of parse tokens. const tokens = context.consumeArgs(1)[0]; let str = ""; let expectedLoc = tokens[tokens.length - 1].loc.start; for (let i = tokens.length - 1; i >= 0; i--) { const actualLoc = tokens[i].loc.start; if (actualLoc > expectedLoc) { // context.consumeArgs has eaten a space. str += " "; expectedLoc = actualLoc; } str += tokens[i].text; expectedLoc += tokens[i].text.length; } return str } // The Latin Modern font renders at the wrong vertical alignment. // This macro provides a better rendering. defineMacro("\\surd", '\\sqrt{\\vphantom{|}}'); // See comment for \oplus in symbols.js. defineMacro("\u2295", "\\oplus"); // Since Temml has no \par, ignore \long. defineMacro("\\long", ""); ////////////////////////////////////////////////////////////////////// // Grouping // \let\bgroup={ \let\egroup=} defineMacro("\\bgroup", "{"); defineMacro("\\egroup", "}"); // Symbols from latex.ltx: // \def~{\nobreakspace{}} // \def\lq{`} // \def\rq{'} // \def \aa {\r a} defineMacro("~", "\\nobreakspace"); defineMacro("\\lq", "`"); defineMacro("\\rq", "'"); defineMacro("\\aa", "\\r a"); defineMacro("\\Bbbk", "\\Bbb{k}"); // \mathstrut from the TeXbook, p 360 defineMacro("\\mathstrut", "\\vphantom{(}"); // \underbar from TeXbook p 353 defineMacro("\\underbar", "\\underline{\\text{#1}}"); ////////////////////////////////////////////////////////////////////// // 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 //\newcommand{\substack}[1]{\subarray{c}#1\endsubarray} defineMacro("\\substack", "\\begin{subarray}{c}#1\\end{subarray}"); // \def\iff{\DOTSB\;\Longleftrightarrow\;} // \def\implies{\DOTSB\;\Longrightarrow\;} // \def\impliedby{\DOTSB\;\Longleftarrow\;} defineMacro("\\iff", "\\DOTSB\\;\\Longleftrightarrow\\;"); defineMacro("\\implies", "\\DOTSB\\;\\Longrightarrow\\;"); defineMacro("\\impliedby", "\\DOTSB\\;\\Longleftarrow\\;"); // AMSMath's automatic \dots, based on \mdots@@ macro. const 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", "\\bigsqcap": "\\dotsb", "\\bigsqcup": "\\dotsb", "\\bigtimes": "\\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 Temml, \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). let thedots = "\\dotso"; const 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 (["bin", "rel"].includes(symbols.math[next].group)) { thedots = "\\dotsb"; } } return thedots; }); const 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) { const next = context.future().text; if (next in spaceAfterDots) { return "\\ldots\\,"; } else { return "\\ldots"; } }); defineMacro("\\dotsc", function(context) { const 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) { const next = context.future().text; if (next in spaceAfterDots) { return "\\@cdots\\,"; } else { return "\\@cdots"; } }); defineMacro("\\dotsb", "\\cdots"); defineMacro("\\dotsm", "\\cdots"); defineMacro("\\dotsi", "\\!\\cdots"); defineMacro("\\idotsint", "\\dotsi"); // 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"); defineMacro("\\AA", "\\TextOrMath{\\Angstrom}{\\mathring{A}}\\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 "\\def\\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", "\\mathbin{\\text{mod}}"); 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. defineMacro("\\TeX", "\\textrm{T}\\kern-.1667em\\raisebox{-.5ex}{E}\\kern-.125em\\textrm{X}"); defineMacro( "\\LaTeX", "\\textrm{L}\\kern-.35em\\raisebox{0.2em}{\\scriptstyle A}\\kern-.15em\\TeX" ); defineMacro( "\\Temml", // eslint-disable-next-line max-len "\\textrm{T}\\kern-0.2em\\lower{0.2em}{\\textrm{E}}\\kern-0.08em{\\textrm{M}\\kern-0.08em\\raise{0.2em}\\textrm{M}\\kern-0.08em\\textrm{L}}" ); // \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"); defineMacro("\\colon", `\\mathpunct{\\char"3a}`); ////////////////////////////////////////////////////////////////////// // mathtools.sty defineMacro("\\prescript", "\\pres@cript{_{#1}^{#2}}{}{#3}"); //\providecommand\ordinarycolon{:} defineMacro("\\ordinarycolon", `\\char"3a`); // Raise to center on the math axis, as closely as possible. defineMacro("\\vcentcolon", "\\mathrel{\\raisebox{0.035em}{\\ordinarycolon}}"); // \providecommand*\coloneq{\vcentcolon\mathrel{\mkern-1.2mu}\mathrel{-}} defineMacro("\\coloneq", '\\mathrel{\\raisebox{0.035em}{\\ordinarycolon}\\char"2212}'); // \providecommand*\Coloneq{\dblcolon\mathrel{\mkern-1.2mu}\mathrel{-}} defineMacro("\\Coloneq", '\\mathrel{\\char"2237\\char"2212}'); // \providecommand*\Eqqcolon{=\mathrel{\mkern-1.2mu}\dblcolon} defineMacro("\\Eqqcolon", '\\mathrel{\\char"3d\\char"2237}'); // \providecommand*\Eqcolon{\mathrel{-}\mathrel{\mkern-1.2mu}\dblcolon} defineMacro("\\Eqcolon", '\\mathrel{\\char"2212\\char"2237}'); // \providecommand*\colonapprox{\vcentcolon\mathrel{\mkern-1.2mu}\approx} defineMacro("\\colonapprox", '\\mathrel{\\raisebox{0.035em}{\\ordinarycolon}\\char"2248}'); // \providecommand*\Colonapprox{\dblcolon\mathrel{\mkern-1.2mu}\approx} defineMacro("\\Colonapprox", '\\mathrel{\\char"2237\\char"2248}'); // \providecommand*\colonsim{\vcentcolon\mathrel{\mkern-1.2mu}\sim} defineMacro("\\colonsim", '\\mathrel{\\raisebox{0.035em}{\\ordinarycolon}\\char"223c}'); // \providecommand*\Colonsim{\dblcolon\mathrel{\mkern-1.2mu}\sim} defineMacro("\\Colonsim", '\\mathrel{\\raisebox{0.035em}{\\ordinarycolon}\\char"223c}'); ////////////////////////////////////////////////////////////////////// // 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"); // Present in newtxmath, pxfonts and txfonts defineMacro("\\notni", "\\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{\\text{lim}}}"); defineMacro("\\varliminf", "\\DOTSB\\operatorname*{\\underline{\\text{lim}}}"); defineMacro("\\varinjlim", "\\DOTSB\\operatorname*{\\underrightarrow{\\text{lim}}}"); defineMacro("\\varprojlim", "\\DOTSB\\operatorname*{\\underleftarrow{\\text{lim}}}"); defineMacro("\\centerdot", "{\\medspace\\rule{0.167em}{0.189em}\\medspace}"); ////////////////////////////////////////////////////////////////////// // 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\\operatorname*{plim}"); ////////////////////////////////////////////////////////////////////// // MnSymbol.sty defineMacro("\\leftmodels", "\\mathop{\\reflectbox{$\\models$}}"); ////////////////////////////////////////////////////////////////////// // 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"); // A helper for \Braket and \Set const replaceVert = (argStr, match) => { const ch = match[0] === "|" ? "\\vert" : "\\Vert"; const replaceStr = `}\\,\\middle${ch}\\,{`; return argStr.slice(0, match.index) + replaceStr + argStr.slice(match.index + match[0].length) }; defineMacro("\\Braket", function(context) { let argStr = recreateArgStr(context); const regEx = /\|\||\||\\\|/g; let match; while ((match = regEx.exec(argStr)) !== null) { argStr = replaceVert(argStr, match); } return "\\left\\langle{" + argStr + "}\\right\\rangle" }); defineMacro("\\Set", function(context) { let argStr = recreateArgStr(context); const match = /\|\||\||\\\|/.exec(argStr); if (match) { argStr = replaceVert(argStr, match); } return "\\left\\{\\:{" + argStr + "}\\:\\right\\}" }); defineMacro("\\set", function(context) { const argStr = recreateArgStr(context); return "\\{{" + argStr.replace(/\|/, "}\\mid{") + "}\\}" }); ////////////////////////////////////////////////////////////////////// // actuarialangle.dtx defineMacro("\\angln", "{\\angl n}"); ////////////////////////////////////////////////////////////////////// // derivative.sty defineMacro("\\odv", "\\@ifstar\\odv@next\\odv@numerator"); defineMacro("\\odv@numerator", "\\frac{\\mathrm{d}#1}{\\mathrm{d}#2}"); defineMacro("\\odv@next", "\\frac{\\mathrm{d}}{\\mathrm{d}#2}#1"); defineMacro("\\pdv", "\\@ifstar\\pdv@next\\pdv@numerator"); const pdvHelper = args => { const numerator = args[0][0].text; const denoms = stringFromArg(args[1]).split(","); const power = String(denoms.length); const numOp = power === "1" ? "\\partial" : `\\partial^${power}`; let denominator = ""; denoms.map(e => { denominator += "\\partial " + e.trim() + "\\,";}); return [numerator, numOp, denominator.replace(/\\,$/, "")] }; defineMacro("\\pdv@numerator", function(context) { const [numerator, numOp, denominator] = pdvHelper(context.consumeArgs(2)); return `\\frac{${numOp} ${numerator}}{${denominator}}` }); defineMacro("\\pdv@next", function(context) { const [numerator, numOp, denominator] = pdvHelper(context.consumeArgs(2)); return `\\frac{${numOp}}{${denominator}} ${numerator}` }); ////////////////////////////////////////////////////////////////////// // upgreek.dtx defineMacro("\\upalpha", "\\up@greek{\\alpha}"); defineMacro("\\upbeta", "\\up@greek{\\beta}"); defineMacro("\\upgamma", "\\up@greek{\\gamma}"); defineMacro("\\updelta", "\\up@greek{\\delta}"); defineMacro("\\upepsilon", "\\up@greek{\\epsilon}"); defineMacro("\\upzeta", "\\up@greek{\\zeta}"); defineMacro("\\upeta", "\\up@greek{\\eta}"); defineMacro("\\uptheta", "\\up@greek{\\theta}"); defineMacro("\\upiota", "\\up@greek{\\iota}"); defineMacro("\\upkappa", "\\up@greek{\\kappa}"); defineMacro("\\uplambda", "\\up@greek{\\lambda}"); defineMacro("\\upmu", "\\up@greek{\\mu}"); defineMacro("\\upnu", "\\up@greek{\\nu}"); defineMacro("\\upxi", "\\up@greek{\\xi}"); defineMacro("\\upomicron", "\\up@greek{\\omicron}"); defineMacro("\\uppi", "\\up@greek{\\pi}"); defineMacro("\\upalpha", "\\up@greek{\\alpha}"); defineMacro("\\uprho", "\\up@greek{\\rho}"); defineMacro("\\upsigma", "\\up@greek{\\sigma}"); defineMacro("\\uptau", "\\up@greek{\\tau}"); defineMacro("\\upupsilon", "\\up@greek{\\upsilon}"); defineMacro("\\upphi", "\\up@greek{\\phi}"); defineMacro("\\upchi", "\\up@greek{\\chi}"); defineMacro("\\uppsi", "\\up@greek{\\psi}"); defineMacro("\\upomega", "\\up@greek{\\omega}"); ////////////////////////////////////////////////////////////////////// // cmll package defineMacro("\\invamp", '\\mathbin{\\char"214b}'); defineMacro("\\parr", '\\mathbin{\\char"214b}'); defineMacro("\\with", '\\mathbin{\\char"26}'); defineMacro("\\multimapinv", '\\mathrel{\\char"27dc}'); defineMacro("\\multimapboth", '\\mathrel{\\char"29df}'); defineMacro("\\scoh", '{\\mkern5mu\\char"2322\\mkern5mu}'); defineMacro("\\sincoh", '{\\mkern5mu\\char"2323\\mkern5mu}'); defineMacro("\\coh", `{\\mkern5mu\\rule{}{0.7em}\\mathrlap{\\smash{\\raise2mu{\\char"2322}}} {\\smash{\\lower4mu{\\char"2323}}}\\mkern5mu}`); defineMacro("\\incoh", `{\\mkern5mu\\rule{}{0.7em}\\mathrlap{\\smash{\\raise2mu{\\char"2323}}} {\\smash{\\lower4mu{\\char"2322}}}\\mkern5mu}`); ////////////////////////////////////////////////////////////////////// // chemstyle package defineMacro("\\standardstate", "\\text{\\tiny\\char`⦵}"); /* eslint-disable */ /* -*- Mode: JavaScript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * Temml mhchem.js * * This file implements a Temml version of mhchem version 3.3.0. * It is adapted from MathJax/extensions/TeX/mhchem.js * It differs from the MathJax version as follows: * 1. The interface is changed so that it can be called from Temml, not MathJax. * 2. \rlap and \llap are replaced with \mathrlap and \mathllap. * 3. The reaction arrow code is simplified. All reaction arrows are rendered * using Temml extensible arrows instead of building non-extensible arrows. * 4. The ~bond forms are composed entirely of \rule elements. * 5. Two dashes in _getBond are wrapped in braces to suppress spacing. i.e., {-} * 6. The electron dot uses \textbullet instead of \bullet. * * This code, as other Temml code, is released under the MIT license. * * /************************************************************* * * MathJax/extensions/TeX/mhchem.js * * Implements the \ce command for handling chemical formulas * from the mhchem LaTeX package. * * --------------------------------------------------------------------- * * Copyright (c) 2011-2015 The MathJax Consortium * Copyright (c) 2015-2018 Martin Hensel * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // // Coding Style // - use '' for identifiers that can by minified/uglified // - use "" for strings that need to stay untouched // version: "3.3.0" for MathJax and Temml // Add \ce, \pu, and \tripleDash to the Temml macros. defineMacro("\\ce", function(context) { return chemParse(context.consumeArgs(1)[0], "ce") }); defineMacro("\\pu", function(context) { return chemParse(context.consumeArgs(1)[0], "pu"); }); // Math fonts do not include glyphs for the ~ form of bonds. So we'll send path geometry // So we'll compose characters built from \rule elements. defineMacro("\\uniDash", `{\\rule{0.672em}{0.06em}}`) defineMacro("\\triDash", `{\\rule{0.15em}{0.06em}\\kern2mu\\rule{0.15em}{0.06em}\\kern2mu\\rule{0.15em}{0.06em}}`) defineMacro("\\tripleDash", `\\kern0.075em\\raise0.25em{\\triDash}\\kern0.075em`) defineMacro("\\tripleDashOverLine", `\\kern0.075em\\mathrlap{\\raise0.125em{\\uniDash}}\\raise0.34em{\\triDash}\\kern0.075em`) defineMacro("\\tripleDashOverDoubleLine", `\\kern0.075em\\mathrlap{\\mathrlap{\\raise0.48em{\\triDash}}\\raise0.27em{\\uniDash}}{\\raise0.05em{\\uniDash}}\\kern0.075em`) defineMacro("\\tripleDashBetweenDoubleLine", `\\kern0.075em\\mathrlap{\\mathrlap{\\raise0.48em{\\uniDash}}\\raise0.27em{\\triDash}}{\\raise0.05em{\\uniDash}}\\kern0.075em`) // // This is the main function for handing the \ce and \pu commands. // It takes the argument to \ce or \pu and returns the corresponding TeX string. // var chemParse = function (tokens, stateMachine) { // Recreate the argument string from Temml's array of tokens. var str = ""; var expectedLoc = tokens.length && tokens[tokens.length - 1].loc.start for (var i = tokens.length - 1; i >= 0; i--) { if(tokens[i].loc.start > expectedLoc) { // context.consumeArgs has eaten a space. str += " "; expectedLoc = tokens[i].loc.start; } str += tokens[i].text; expectedLoc += tokens[i].text.length; } // Call the mhchem core parser. var tex = texify.go(mhchemParser.go(str, stateMachine)); return tex; }; // // Core parser for mhchem syntax (recursive) // /** @type {MhchemParser} */ var mhchemParser = { // // Parses mchem \ce syntax // // Call like // go("H2O"); // go: function (input, stateMachine) { if (!input) { return []; } if (stateMachine === undefined) { stateMachine = 'ce'; } var state = '0'; // // String buffers for parsing: // // buffer.a == amount // buffer.o == element // buffer.b == left-side superscript // buffer.p == left-side subscript // buffer.q == right-side subscript // buffer.d == right-side superscript // // buffer.r == arrow // buffer.rdt == arrow, script above, type // buffer.rd == arrow, script above, content // buffer.rqt == arrow, script below, type // buffer.rq == arrow, script below, content // // buffer.text_ // buffer.rm // etc. // // buffer.parenthesisLevel == int, starting at 0 // buffer.sb == bool, space before // buffer.beginsWithBond == bool // // These letters are also used as state names. // // Other states: // 0 == begin of main part (arrow/operator unlikely) // 1 == next entity // 2 == next entity (arrow/operator unlikely) // 3 == next atom // c == macro // /** @type {Buffer} */ var buffer = {}; buffer['parenthesisLevel'] = 0; input = input.replace(/\n/g, " "); input = input.replace(/[\u2212\u2013\u2014\u2010]/g, "-"); input = input.replace(/[\u2026]/g, "..."); // // Looks through mhchemParser.transitions, to execute a matching action // (recursive) // var lastInput; var watchdog = 10; /** @type {ParserOutput[]} */ var output = []; while (true) { if (lastInput !== input) { watchdog = 10; lastInput = input; } else { watchdog--; } // // Find actions in transition table // var machine = mhchemParser.stateMachines[stateMachine]; var t = machine.transitions[state] || machine.transitions['*']; iterateTransitions: for (var i=0; i 0) { if (!task.revisit) { input = matches.remainder; } if (!task.toContinue) { break iterateTransitions; } } else { return output; } } } // // Prevent infinite loop // if (watchdog <= 0) { throw ["MhchemBugU", "mhchem bug U. Please report."]; // Unexpected character } } }, concatArray: function (a, b) { if (b) { if (Array.isArray(b)) { for (var iB=0; iB': /^[=<>]/, '#': /^[#\u2261]/, '+': /^\+/, '-$': /^-(?=[\s_},;\]/]|$|\([a-z]+\))/, // -space -, -; -] -/ -$ -state-of-aggregation '-9': /^-(?=[0-9])/, '- orbital overlap': /^-(?=(?:[spd]|sp)(?:$|[\s,;\)\]\}]))/, '-': /^-/, 'pm-operator': /^(?:\\pm|\$\\pm\$|\+-|\+\/-)/, 'operator': /^(?:\+|(?:[\-=<>]|<<|>>|\\approx|\$\\approx\$)(?=\s|$|-?[0-9]))/, 'arrowUpDown': /^(?:v|\(v\)|\^|\(\^\))(?=$|[\s,;\)\]\}])/, '\\bond{(...)}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "\\bond{", "", "", "}"); }, '->': /^(?:<->|<-->|->|<-|<=>>|<<=>|<=>|[\u2192\u27F6\u21CC])/, 'CMT': /^[CMT](?=\[)/, '[(...)]': function (input) { return mhchemParser.patterns.findObserveGroups(input, "[", "", "", "]"); }, '1st-level escape': /^(&|\\\\|\\hline)\s*/, '\\,': /^(?:\\[,\ ;:])/, // \\x - but output no space before '\\x{}{}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "", /^\\[a-zA-Z]+\{/, "}", "", "", "{", "}", "", true); }, '\\x{}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "", /^\\[a-zA-Z]+\{/, "}", ""); }, '\\ca': /^\\ca(?:\s+|(?![a-zA-Z]))/, '\\x': /^(?:\\[a-zA-Z]+\s*|\\[_&{}%])/, 'orbital': /^(?:[0-9]{1,2}[spdfgh]|[0-9]{0,2}sp)(?=$|[^a-zA-Z])/, // only those with numbers in front, because the others will be formatted correctly anyway 'others': /^[\/~|]/, '\\frac{(...)}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "\\frac{", "", "", "}", "{", "", "", "}"); }, '\\overset{(...)}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "\\overset{", "", "", "}", "{", "", "", "}"); }, '\\underset{(...)}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "\\underset{", "", "", "}", "{", "", "", "}"); }, '\\underbrace{(...)}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "\\underbrace{", "", "", "}_", "{", "", "", "}"); }, '\\color{(...)}0': function (input) { return mhchemParser.patterns.findObserveGroups(input, "\\color{", "", "", "}"); }, '\\color{(...)}{(...)}1': function (input) { return mhchemParser.patterns.findObserveGroups(input, "\\color{", "", "", "}", "{", "", "", "}"); }, '\\color(...){(...)}2': function (input) { return mhchemParser.patterns.findObserveGroups(input, "\\color", "\\", "", /^(?=\{)/, "{", "", "", "}"); }, '\\ce{(...)}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "\\ce{", "", "", "}"); }, 'oxidation$': /^(?:[+-][IVX]+|\\pm\s*0|\$\\pm\$\s*0)$/, 'd-oxidation$': /^(?:[+-]?\s?[IVX]+|\\pm\s*0|\$\\pm\$\s*0)$/, // 0 could be oxidation or charge 'roman numeral': /^[IVX]+/, '1/2$': /^[+\-]?(?:[0-9]+|\$[a-z]\$|[a-z])\/[0-9]+(?:\$[a-z]\$|[a-z])?$/, 'amount': function (input) { var match; // e.g. 2, 0.5, 1/2, -2, n/2, +; $a$ could be added later in parsing match = input.match(/^(?:(?:(?:\([+\-]?[0-9]+\/[0-9]+\)|[+\-]?(?:[0-9]+|\$[a-z]\$|[a-z])\/[0-9]+|[+\-]?[0-9]+[.,][0-9]+|[+\-]?\.[0-9]+|[+\-]?[0-9]+)(?:[a-z](?=\s*[A-Z]))?)|[+\-]?[a-z](?=\s*[A-Z])|\+(?!\s))/); if (match) { return { match_: match[0], remainder: input.substr(match[0].length) }; } var a = mhchemParser.patterns.findObserveGroups(input, "", "$", "$", ""); if (a) { // e.g. $2n-1$, $-$ match = a.match_.match(/^\$(?:\(?[+\-]?(?:[0-9]*[a-z]?[+\-])?[0-9]*[a-z](?:[+\-][0-9]*[a-z]?)?\)?|\+|-)\$$/); if (match) { return { match_: match[0], remainder: input.substr(match[0].length) }; } } return null; }, 'amount2': function (input) { return this['amount'](input); }, '(KV letters),': /^(?:[A-Z][a-z]{0,2}|i)(?=,)/, 'formula$': function (input) { if (input.match(/^\([a-z]+\)$/)) { return null; } // state of aggregation = no formula var match = input.match(/^(?:[a-z]|(?:[0-9\ \+\-\,\.\(\)]+[a-z])+[0-9\ \+\-\,\.\(\)]*|(?:[a-z][0-9\ \+\-\,\.\(\)]+)+[a-z]?)$/); if (match) { return { match_: match[0], remainder: input.substr(match[0].length) }; } return null; }, 'uprightEntities': /^(?:pH|pOH|pC|pK|iPr|iBu)(?=$|[^a-zA-Z])/, '/': /^\s*(\/)\s*/, '//': /^\s*(\/\/)\s*/, '*': /^\s*[*.]\s*/ }, findObserveGroups: function (input, begExcl, begIncl, endIncl, endExcl, beg2Excl, beg2Incl, end2Incl, end2Excl, combine) { /** @type {{(input: string, pattern: string | RegExp): string | string[] | null;}} */ var _match = function (input, pattern) { if (typeof pattern === "string") { if (input.indexOf(pattern) !== 0) { return null; } return pattern; } else { var match = input.match(pattern); if (!match) { return null; } return match[0]; } }; /** @type {{(input: string, i: number, endChars: string | RegExp): {endMatchBegin: number, endMatchEnd: number} | null;}} */ var _findObserveGroups = function (input, i, endChars) { var braces = 0; while (i < input.length) { var a = input.charAt(i); var match = _match(input.substr(i), endChars); if (match !== null && braces === 0) { return { endMatchBegin: i, endMatchEnd: i + match.length }; } else if (a === "{") { braces++; } else if (a === "}") { if (braces === 0) { throw ["ExtraCloseMissingOpen", "Extra close brace or missing open brace"]; } else { braces--; } } i++; } if (braces > 0) { return null; } return null; }; var match = _match(input, begExcl); if (match === null) { return null; } input = input.substr(match.length); match = _match(input, begIncl); if (match === null) { return null; } var e = _findObserveGroups(input, match.length, endIncl || endExcl); if (e === null) { return null; } var match1 = input.substring(0, (endIncl ? e.endMatchEnd : e.endMatchBegin)); if (!(beg2Excl || beg2Incl)) { return { match_: match1, remainder: input.substr(e.endMatchEnd) }; } else { var group2 = this.findObserveGroups(input.substr(e.endMatchEnd), beg2Excl, beg2Incl, end2Incl, end2Excl); if (group2 === null) { return null; } /** @type {string[]} */ var matchRet = [match1, group2.match_]; return { match_: (combine ? matchRet.join("") : matchRet), remainder: group2.remainder }; } }, // // Matching function // e.g. match("a", input) will look for the regexp called "a" and see if it matches // returns null or {match_:"a", remainder:"bc"} // match_: function (m, input) { var pattern = mhchemParser.patterns.patterns[m]; if (pattern === undefined) { throw ["MhchemBugP", "mhchem bug P. Please report. (" + m + ")"]; // Trying to use non-existing pattern } else if (typeof pattern === "function") { return mhchemParser.patterns.patterns[m](input); // cannot use cached var pattern here, because some pattern functions need this===mhchemParser } else { // RegExp var match = input.match(pattern); if (match) { var mm; if (match[2]) { mm = [ match[1], match[2] ]; } else if (match[1]) { mm = match[1]; } else { mm = match[0]; } return { match_: mm, remainder: input.substr(match[0].length) }; } return null; } } }, // // Generic state machine actions // actions: { 'a=': function (buffer, m) { buffer.a = (buffer.a || "") + m; }, 'b=': function (buffer, m) { buffer.b = (buffer.b || "") + m; }, 'p=': function (buffer, m) { buffer.p = (buffer.p || "") + m; }, 'o=': function (buffer, m) { buffer.o = (buffer.o || "") + m; }, 'q=': function (buffer, m) { buffer.q = (buffer.q || "") + m; }, 'd=': function (buffer, m) { buffer.d = (buffer.d || "") + m; }, 'rm=': function (buffer, m) { buffer.rm = (buffer.rm || "") + m; }, 'text=': function (buffer, m) { buffer.text_ = (buffer.text_ || "") + m; }, 'insert': function (buffer, m, a) { return { type_: a }; }, 'insert+p1': function (buffer, m, a) { return { type_: a, p1: m }; }, 'insert+p1+p2': function (buffer, m, a) { return { type_: a, p1: m[0], p2: m[1] }; }, 'copy': function (buffer, m) { return m; }, 'rm': function (buffer, m) { return { type_: 'rm', p1: m || ""}; }, 'text': function (buffer, m) { return mhchemParser.go(m, 'text'); }, '{text}': function (buffer, m) { var ret = [ "{" ]; mhchemParser.concatArray(ret, mhchemParser.go(m, 'text')); ret.push("}"); return ret; }, 'tex-math': function (buffer, m) { return mhchemParser.go(m, 'tex-math'); }, 'tex-math tight': function (buffer, m) { return mhchemParser.go(m, 'tex-math tight'); }, 'bond': function (buffer, m, k) { return { type_: 'bond', kind_: k || m }; }, 'color0-output': function (buffer, m) { return { type_: 'color0', color: m[0] }; }, 'ce': function (buffer, m) { return mhchemParser.go(m); }, '1/2': function (buffer, m) { /** @type {ParserOutput[]} */ var ret = []; if (m.match(/^[+\-]/)) { ret.push(m.substr(0, 1)); m = m.substr(1); } var n = m.match(/^([0-9]+|\$[a-z]\$|[a-z])\/([0-9]+)(\$[a-z]\$|[a-z])?$/); n[1] = n[1].replace(/\$/g, ""); ret.push({ type_: 'frac', p1: n[1], p2: n[2] }); if (n[3]) { n[3] = n[3].replace(/\$/g, ""); ret.push({ type_: 'tex-math', p1: n[3] }); } return ret; }, '9,9': function (buffer, m) { return mhchemParser.go(m, '9,9'); } }, // // createTransitions // convert { 'letter': { 'state': { action_: 'output' } } } to { 'state' => [ { pattern: 'letter', task: { action_: [{type_: 'output'}] } } ] } // with expansion of 'a|b' to 'a' and 'b' (at 2 places) // createTransitions: function (o) { var pattern, state; /** @type {string[]} */ var stateArray; var i; // // 1. Collect all states // /** @type {Transitions} */ var transitions = {}; for (pattern in o) { for (state in o[pattern]) { stateArray = state.split("|"); o[pattern][state].stateArray = stateArray; for (i=0; i': { '0|1|2|3': { action_: 'r=', nextState: 'r' }, 'a|as': { action_: [ 'output', 'r=' ], nextState: 'r' }, '*': { action_: [ 'output', 'r=' ], nextState: 'r' } }, '+': { 'o': { action_: 'd= kv', nextState: 'd' }, 'd|D': { action_: 'd=', nextState: 'd' }, 'q': { action_: 'd=', nextState: 'qd' }, 'qd|qD': { action_: 'd=', nextState: 'qd' }, 'dq': { action_: [ 'output', 'd=' ], nextState: 'd' }, '3': { action_: [ 'sb=false', 'output', 'operator' ], nextState: '0' } }, 'amount': { '0|2': { action_: 'a=', nextState: 'a' } }, 'pm-operator': { '0|1|2|a|as': { action_: [ 'sb=false', 'output', { type_: 'operator', option: '\\pm' } ], nextState: '0' } }, 'operator': { '0|1|2|a|as': { action_: [ 'sb=false', 'output', 'operator' ], nextState: '0' } }, '-$': { 'o|q': { action_: [ 'charge or bond', 'output' ], nextState: 'qd' }, 'd': { action_: 'd=', nextState: 'd' }, 'D': { action_: [ 'output', { type_: 'bond', option: "-" } ], nextState: '3' }, 'q': { action_: 'd=', nextState: 'qd' }, 'qd': { action_: 'd=', nextState: 'qd' }, 'qD|dq': { action_: [ 'output', { type_: 'bond', option: "-" } ], nextState: '3' } }, '-9': { '3|o': { action_: [ 'output', { type_: 'insert', option: 'hyphen' } ], nextState: '3' } }, '- orbital overlap': { 'o': { action_: [ 'output', { type_: 'insert', option: 'hyphen' } ], nextState: '2' }, 'd': { action_: [ 'output', { type_: 'insert', option: 'hyphen' } ], nextState: '2' } }, '-': { '0|1|2': { action_: [ { type_: 'output', option: 1 }, 'beginsWithBond=true', { type_: 'bond', option: "-" } ], nextState: '3' }, '3': { action_: { type_: 'bond', option: "-" } }, 'a': { action_: [ 'output', { type_: 'insert', option: 'hyphen' } ], nextState: '2' }, 'as': { action_: [ { type_: 'output', option: 2 }, { type_: 'bond', option: "-" } ], nextState: '3' }, 'b': { action_: 'b=' }, 'o': { action_: { type_: '- after o/d', option: false }, nextState: '2' }, 'q': { action_: { type_: '- after o/d', option: false }, nextState: '2' }, 'd|qd|dq': { action_: { type_: '- after o/d', option: true }, nextState: '2' }, 'D|qD|p': { action_: [ 'output', { type_: 'bond', option: "-" } ], nextState: '3' } }, 'amount2': { '1|3': { action_: 'a=', nextState: 'a' } }, 'letters': { '0|1|2|3|a|as|b|p|bp|o': { action_: 'o=', nextState: 'o' }, 'q|dq': { action_: ['output', 'o='], nextState: 'o' }, 'd|D|qd|qD': { action_: 'o after d', nextState: 'o' } }, 'digits': { 'o': { action_: 'q=', nextState: 'q' }, 'd|D': { action_: 'q=', nextState: 'dq' }, 'q': { action_: [ 'output', 'o=' ], nextState: 'o' }, 'a': { action_: 'o=', nextState: 'o' } }, 'space A': { 'b|p|bp': {} }, 'space': { 'a': { nextState: 'as' }, '0': { action_: 'sb=false' }, '1|2': { action_: 'sb=true' }, 'r|rt|rd|rdt|rdq': { action_: 'output', nextState: '0' }, '*': { action_: [ 'output', 'sb=true' ], nextState: '1'} }, '1st-level escape': { '1|2': { action_: [ 'output', { type_: 'insert+p1', option: '1st-level escape' } ] }, '*': { action_: [ 'output', { type_: 'insert+p1', option: '1st-level escape' } ], nextState: '0' } }, '[(...)]': { 'r|rt': { action_: 'rd=', nextState: 'rd' }, 'rd|rdt': { action_: 'rq=', nextState: 'rdq' } }, '...': { 'o|d|D|dq|qd|qD': { action_: [ 'output', { type_: 'bond', option: "..." } ], nextState: '3' }, '*': { action_: [ { type_: 'output', option: 1 }, { type_: 'insert', option: 'ellipsis' } ], nextState: '1' } }, '. |* ': { '*': { action_: [ 'output', { type_: 'insert', option: 'addition compound' } ], nextState: '1' } }, 'state of aggregation $': { '*': { action_: [ 'output', 'state of aggregation' ], nextState: '1' } }, '{[(': { 'a|as|o': { action_: [ 'o=', 'output', 'parenthesisLevel++' ], nextState: '2' }, '0|1|2|3': { action_: [ 'o=', 'output', 'parenthesisLevel++' ], nextState: '2' }, '*': { action_: [ 'output', 'o=', 'output', 'parenthesisLevel++' ], nextState: '2' } }, ')]}': { '0|1|2|3|b|p|bp|o': { action_: [ 'o=', 'parenthesisLevel--' ], nextState: 'o' }, 'a|as|d|D|q|qd|qD|dq': { action_: [ 'output', 'o=', 'parenthesisLevel--' ], nextState: 'o' } }, ', ': { '*': { action_: [ 'output', 'comma' ], nextState: '0' } }, '^_': { // ^ and _ without a sensible argument '*': { } }, '^{(...)}|^($...$)': { '0|1|2|as': { action_: 'b=', nextState: 'b' }, 'p': { action_: 'b=', nextState: 'bp' }, '3|o': { action_: 'd= kv', nextState: 'D' }, 'q': { action_: 'd=', nextState: 'qD' }, 'd|D|qd|qD|dq': { action_: [ 'output', 'd=' ], nextState: 'D' } }, '^a|^\\x{}{}|^\\x{}|^\\x|\'': { '0|1|2|as': { action_: 'b=', nextState: 'b' }, 'p': { action_: 'b=', nextState: 'bp' }, '3|o': { action_: 'd= kv', nextState: 'd' }, 'q': { action_: 'd=', nextState: 'qd' }, 'd|qd|D|qD': { action_: 'd=' }, 'dq': { action_: [ 'output', 'd=' ], nextState: 'd' } }, '_{(state of aggregation)}$': { 'd|D|q|qd|qD|dq': { action_: [ 'output', 'q=' ], nextState: 'q' } }, '_{(...)}|_($...$)|_9|_\\x{}{}|_\\x{}|_\\x': { '0|1|2|as': { action_: 'p=', nextState: 'p' }, 'b': { action_: 'p=', nextState: 'bp' }, '3|o': { action_: 'q=', nextState: 'q' }, 'd|D': { action_: 'q=', nextState: 'dq' }, 'q|qd|qD|dq': { action_: [ 'output', 'q=' ], nextState: 'q' } }, '=<>': { '0|1|2|3|a|as|o|q|d|D|qd|qD|dq': { action_: [ { type_: 'output', option: 2 }, 'bond' ], nextState: '3' } }, '#': { '0|1|2|3|a|as|o': { action_: [ { type_: 'output', option: 2 }, { type_: 'bond', option: "#" } ], nextState: '3' } }, '{}': { '*': { action_: { type_: 'output', option: 1 }, nextState: '1' } }, '{...}': { '0|1|2|3|a|as|b|p|bp': { action_: 'o=', nextState: 'o' }, 'o|d|D|q|qd|qD|dq': { action_: [ 'output', 'o=' ], nextState: 'o' } }, '$...$': { 'a': { action_: 'a=' }, // 2$n$ '0|1|2|3|as|b|p|bp|o': { action_: 'o=', nextState: 'o' }, // not 'amount' 'as|o': { action_: 'o=' }, 'q|d|D|qd|qD|dq': { action_: [ 'output', 'o=' ], nextState: 'o' } }, '\\bond{(...)}': { '*': { action_: [ { type_: 'output', option: 2 }, 'bond' ], nextState: "3" } }, '\\frac{(...)}': { '*': { action_: [ { type_: 'output', option: 1 }, 'frac-output' ], nextState: '3' } }, '\\overset{(...)}': { '*': { action_: [ { type_: 'output', option: 2 }, 'overset-output' ], nextState: '3' } }, '\\underset{(...)}': { '*': { action_: [ { type_: 'output', option: 2 }, 'underset-output' ], nextState: '3' } }, '\\underbrace{(...)}': { '*': { action_: [ { type_: 'output', option: 2 }, 'underbrace-output' ], nextState: '3' } }, '\\color{(...)}{(...)}1|\\color(...){(...)}2': { '*': { action_: [ { type_: 'output', option: 2 }, 'color-output' ], nextState: '3' } }, '\\color{(...)}0': { '*': { action_: [ { type_: 'output', option: 2 }, 'color0-output' ] } }, '\\ce{(...)}': { '*': { action_: [ { type_: 'output', option: 2 }, 'ce' ], nextState: '3' } }, '\\,': { '*': { action_: [ { type_: 'output', option: 1 }, 'copy' ], nextState: '1' } }, '\\x{}{}|\\x{}|\\x': { '0|1|2|3|a|as|b|p|bp|o|c0': { action_: [ 'o=', 'output' ], nextState: '3' }, '*': { action_: ['output', 'o=', 'output' ], nextState: '3' } }, 'others': { '*': { action_: [ { type_: 'output', option: 1 }, 'copy' ], nextState: '3' } }, 'else2': { 'a': { action_: 'a to o', nextState: 'o', revisit: true }, 'as': { action_: [ 'output', 'sb=true' ], nextState: '1', revisit: true }, 'r|rt|rd|rdt|rdq': { action_: [ 'output' ], nextState: '0', revisit: true }, '*': { action_: [ 'output', 'copy' ], nextState: '3' } } }), actions: { 'o after d': function (buffer, m) { var ret; if ((buffer.d || "").match(/^[0-9]+$/)) { var tmp = buffer.d; buffer.d = undefined; ret = this['output'](buffer); buffer.b = tmp; } else { ret = this['output'](buffer); } mhchemParser.actions['o='](buffer, m); return ret; }, 'd= kv': function (buffer, m) { buffer.d = m; buffer.dType = 'kv'; }, 'charge or bond': function (buffer, m) { if (buffer['beginsWithBond']) { /** @type {ParserOutput[]} */ var ret = []; mhchemParser.concatArray(ret, this['output'](buffer)); mhchemParser.concatArray(ret, mhchemParser.actions['bond'](buffer, m, "-")); return ret; } else { buffer.d = m; } }, '- after o/d': function (buffer, m, isAfterD) { var c1 = mhchemParser.patterns.match_('orbital', buffer.o || ""); var c2 = mhchemParser.patterns.match_('one lowercase greek letter $', buffer.o || ""); var c3 = mhchemParser.patterns.match_('one lowercase latin letter $', buffer.o || ""); var c4 = mhchemParser.patterns.match_('$one lowercase latin letter$ $', buffer.o || ""); var hyphenFollows = m==="-" && ( c1 && c1.remainder==="" || c2 || c3 || c4 ); if (hyphenFollows && !buffer.a && !buffer.b && !buffer.p && !buffer.d && !buffer.q && !c1 && c3) { buffer.o = '$' + buffer.o + '$'; } /** @type {ParserOutput[]} */ var ret = []; if (hyphenFollows) { mhchemParser.concatArray(ret, this['output'](buffer)); ret.push({ type_: 'hyphen' }); } else { c1 = mhchemParser.patterns.match_('digits', buffer.d || ""); if (isAfterD && c1 && c1.remainder==='') { mhchemParser.concatArray(ret, mhchemParser.actions['d='](buffer, m)); mhchemParser.concatArray(ret, this['output'](buffer)); } else { mhchemParser.concatArray(ret, this['output'](buffer)); mhchemParser.concatArray(ret, mhchemParser.actions['bond'](buffer, m, "-")); } } return ret; }, 'a to o': function (buffer) { buffer.o = buffer.a; buffer.a = undefined; }, 'sb=true': function (buffer) { buffer.sb = true; }, 'sb=false': function (buffer) { buffer.sb = false; }, 'beginsWithBond=true': function (buffer) { buffer['beginsWithBond'] = true; }, 'beginsWithBond=false': function (buffer) { buffer['beginsWithBond'] = false; }, 'parenthesisLevel++': function (buffer) { buffer['parenthesisLevel']++; }, 'parenthesisLevel--': function (buffer) { buffer['parenthesisLevel']--; }, 'state of aggregation': function (buffer, m) { return { type_: 'state of aggregation', p1: mhchemParser.go(m, 'o') }; }, 'comma': function (buffer, m) { var a = m.replace(/\s*$/, ''); var withSpace = (a !== m); if (withSpace && buffer['parenthesisLevel'] === 0) { return { type_: 'comma enumeration L', p1: a }; } else { return { type_: 'comma enumeration M', p1: a }; } }, 'output': function (buffer, m, entityFollows) { // entityFollows: // undefined = if we have nothing else to output, also ignore the just read space (buffer.sb) // 1 = an entity follows, never omit the space if there was one just read before (can only apply to state 1) // 2 = 1 + the entity can have an amount, so output a\, instead of converting it to o (can only apply to states a|as) /** @type {ParserOutput | ParserOutput[]} */ var ret; if (!buffer.r) { ret = []; if (!buffer.a && !buffer.b && !buffer.p && !buffer.o && !buffer.q && !buffer.d && !entityFollows) { //ret = []; } else { if (buffer.sb) { ret.push({ type_: 'entitySkip' }); } if (!buffer.o && !buffer.q && !buffer.d && !buffer.b && !buffer.p && entityFollows!==2) { buffer.o = buffer.a; buffer.a = undefined; } else if (!buffer.o && !buffer.q && !buffer.d && (buffer.b || buffer.p)) { buffer.o = buffer.a; buffer.d = buffer.b; buffer.q = buffer.p; buffer.a = buffer.b = buffer.p = undefined; } else { if (buffer.o && buffer.dType==='kv' && mhchemParser.patterns.match_('d-oxidation$', buffer.d || "")) { buffer.dType = 'oxidation'; } else if (buffer.o && buffer.dType==='kv' && !buffer.q) { buffer.dType = undefined; } } ret.push({ type_: 'chemfive', a: mhchemParser.go(buffer.a, 'a'), b: mhchemParser.go(buffer.b, 'bd'), p: mhchemParser.go(buffer.p, 'pq'), o: mhchemParser.go(buffer.o, 'o'), q: mhchemParser.go(buffer.q, 'pq'), d: mhchemParser.go(buffer.d, (buffer.dType === 'oxidation' ? 'oxidation' : 'bd')), dType: buffer.dType }); } } else { // r /** @type {ParserOutput[]} */ var rd; if (buffer.rdt === 'M') { rd = mhchemParser.go(buffer.rd, 'tex-math'); } else if (buffer.rdt === 'T') { rd = [ { type_: 'text', p1: buffer.rd || "" } ]; } else { rd = mhchemParser.go(buffer.rd); } /** @type {ParserOutput[]} */ var rq; if (buffer.rqt === 'M') { rq = mhchemParser.go(buffer.rq, 'tex-math'); } else if (buffer.rqt === 'T') { rq = [ { type_: 'text', p1: buffer.rq || ""} ]; } else { rq = mhchemParser.go(buffer.rq); } ret = { type_: 'arrow', r: buffer.r, rd: rd, rq: rq }; } for (var p in buffer) { if (p !== 'parenthesisLevel' && p !== 'beginsWithBond') { delete buffer[p]; } } return ret; }, 'oxidation-output': function (buffer, m) { var ret = [ "{" ]; mhchemParser.concatArray(ret, mhchemParser.go(m, 'oxidation')); ret.push("}"); return ret; }, 'frac-output': function (buffer, m) { return { type_: 'frac-ce', p1: mhchemParser.go(m[0]), p2: mhchemParser.go(m[1]) }; }, 'overset-output': function (buffer, m) { return { type_: 'overset', p1: mhchemParser.go(m[0]), p2: mhchemParser.go(m[1]) }; }, 'underset-output': function (buffer, m) { return { type_: 'underset', p1: mhchemParser.go(m[0]), p2: mhchemParser.go(m[1]) }; }, 'underbrace-output': function (buffer, m) { return { type_: 'underbrace', p1: mhchemParser.go(m[0]), p2: mhchemParser.go(m[1]) }; }, 'color-output': function (buffer, m) { return { type_: 'color', color1: m[0], color2: mhchemParser.go(m[1]) }; }, 'r=': function (buffer, m) { buffer.r = m; }, 'rdt=': function (buffer, m) { buffer.rdt = m; }, 'rd=': function (buffer, m) { buffer.rd = m; }, 'rqt=': function (buffer, m) { buffer.rqt = m; }, 'rq=': function (buffer, m) { buffer.rq = m; }, 'operator': function (buffer, m, p1) { return { type_: 'operator', kind_: (p1 || m) }; } } }, 'a': { transitions: mhchemParser.createTransitions({ 'empty': { '*': {} }, '1/2$': { '0': { action_: '1/2' } }, 'else': { '0': { nextState: '1', revisit: true } }, '$(...)$': { '*': { action_: 'tex-math tight', nextState: '1' } }, ',': { '*': { action_: { type_: 'insert', option: 'commaDecimal' } } }, 'else2': { '*': { action_: 'copy' } } }), actions: {} }, 'o': { transitions: mhchemParser.createTransitions({ 'empty': { '*': {} }, '1/2$': { '0': { action_: '1/2' } }, 'else': { '0': { nextState: '1', revisit: true } }, 'letters': { '*': { action_: 'rm' } }, '\\ca': { '*': { action_: { type_: 'insert', option: 'circa' } } }, '\\x{}{}|\\x{}|\\x': { '*': { action_: 'copy' } }, '${(...)}$|$(...)$': { '*': { action_: 'tex-math' } }, '{(...)}': { '*': { action_: '{text}' } }, 'else2': { '*': { action_: 'copy' } } }), actions: {} }, 'text': { transitions: mhchemParser.createTransitions({ 'empty': { '*': { action_: 'output' } }, '{...}': { '*': { action_: 'text=' } }, '${(...)}$|$(...)$': { '*': { action_: 'tex-math' } }, '\\greek': { '*': { action_: [ 'output', 'rm' ] } }, '\\,|\\x{}{}|\\x{}|\\x': { '*': { action_: [ 'output', 'copy' ] } }, 'else': { '*': { action_: 'text=' } } }), actions: { 'output': function (buffer) { if (buffer.text_) { /** @type {ParserOutput} */ var ret = { type_: 'text', p1: buffer.text_ }; for (var p in buffer) { delete buffer[p]; } return ret; } } } }, 'pq': { transitions: mhchemParser.createTransitions({ 'empty': { '*': {} }, 'state of aggregation $': { '*': { action_: 'state of aggregation' } }, 'i$': { '0': { nextState: '!f', revisit: true } }, '(KV letters),': { '0': { action_: 'rm', nextState: '0' } }, 'formula$': { '0': { nextState: 'f', revisit: true } }, '1/2$': { '0': { action_: '1/2' } }, 'else': { '0': { nextState: '!f', revisit: true } }, '${(...)}$|$(...)$': { '*': { action_: 'tex-math' } }, '{(...)}': { '*': { action_: 'text' } }, 'a-z': { 'f': { action_: 'tex-math' } }, 'letters': { '*': { action_: 'rm' } }, '-9.,9': { '*': { action_: '9,9' } }, ',': { '*': { action_: { type_: 'insert+p1', option: 'comma enumeration S' } } }, '\\color{(...)}{(...)}1|\\color(...){(...)}2': { '*': { action_: 'color-output' } }, '\\color{(...)}0': { '*': { action_: 'color0-output' } }, '\\ce{(...)}': { '*': { action_: 'ce' } }, '\\,|\\x{}{}|\\x{}|\\x': { '*': { action_: 'copy' } }, 'else2': { '*': { action_: 'copy' } } }), actions: { 'state of aggregation': function (buffer, m) { return { type_: 'state of aggregation subscript', p1: mhchemParser.go(m, 'o') }; }, 'color-output': function (buffer, m) { return { type_: 'color', color1: m[0], color2: mhchemParser.go(m[1], 'pq') }; } } }, 'bd': { transitions: mhchemParser.createTransitions({ 'empty': { '*': {} }, 'x$': { '0': { nextState: '!f', revisit: true } }, 'formula$': { '0': { nextState: 'f', revisit: true } }, 'else': { '0': { nextState: '!f', revisit: true } }, '-9.,9 no missing 0': { '*': { action_: '9,9' } }, '.': { '*': { action_: { type_: 'insert', option: 'electron dot' } } }, 'a-z': { 'f': { action_: 'tex-math' } }, 'x': { '*': { action_: { type_: 'insert', option: 'KV x' } } }, 'letters': { '*': { action_: 'rm' } }, '\'': { '*': { action_: { type_: 'insert', option: 'prime' } } }, '${(...)}$|$(...)$': { '*': { action_: 'tex-math' } }, '{(...)}': { '*': { action_: 'text' } }, '\\color{(...)}{(...)}1|\\color(...){(...)}2': { '*': { action_: 'color-output' } }, '\\color{(...)}0': { '*': { action_: 'color0-output' } }, '\\ce{(...)}': { '*': { action_: 'ce' } }, '\\,|\\x{}{}|\\x{}|\\x': { '*': { action_: 'copy' } }, 'else2': { '*': { action_: 'copy' } } }), actions: { 'color-output': function (buffer, m) { return { type_: 'color', color1: m[0], color2: mhchemParser.go(m[1], 'bd') }; } } }, 'oxidation': { transitions: mhchemParser.createTransitions({ 'empty': { '*': {} }, 'roman numeral': { '*': { action_: 'roman-numeral' } }, '${(...)}$|$(...)$': { '*': { action_: 'tex-math' } }, 'else': { '*': { action_: 'copy' } } }), actions: { 'roman-numeral': function (buffer, m) { return { type_: 'roman numeral', p1: m || "" }; } } }, 'tex-math': { transitions: mhchemParser.createTransitions({ 'empty': { '*': { action_: 'output' } }, '\\ce{(...)}': { '*': { action_: [ 'output', 'ce' ] } }, '{...}|\\,|\\x{}{}|\\x{}|\\x': { '*': { action_: 'o=' } }, 'else': { '*': { action_: 'o=' } } }), actions: { 'output': function (buffer) { if (buffer.o) { /** @type {ParserOutput} */ var ret = { type_: 'tex-math', p1: buffer.o }; for (var p in buffer) { delete buffer[p]; } return ret; } } } }, 'tex-math tight': { transitions: mhchemParser.createTransitions({ 'empty': { '*': { action_: 'output' } }, '\\ce{(...)}': { '*': { action_: [ 'output', 'ce' ] } }, '{...}|\\,|\\x{}{}|\\x{}|\\x': { '*': { action_: 'o=' } }, '-|+': { '*': { action_: 'tight operator' } }, 'else': { '*': { action_: 'o=' } } }), actions: { 'tight operator': function (buffer, m) { buffer.o = (buffer.o || "") + "{"+m+"}"; }, 'output': function (buffer) { if (buffer.o) { /** @type {ParserOutput} */ var ret = { type_: 'tex-math', p1: buffer.o }; for (var p in buffer) { delete buffer[p]; } return ret; } } } }, '9,9': { transitions: mhchemParser.createTransitions({ 'empty': { '*': {} }, ',': { '*': { action_: 'comma' } }, 'else': { '*': { action_: 'copy' } } }), actions: { 'comma': function () { return { type_: 'commaDecimal' }; } } }, //#endregion // // \pu state machines // //#region pu 'pu': { transitions: mhchemParser.createTransitions({ 'empty': { '*': { action_: 'output' } }, 'space$': { '*': { action_: [ 'output', 'space' ] } }, '{[(|)]}': { '0|a': { action_: 'copy' } }, '(-)(9)^(-9)': { '0': { action_: 'number^', nextState: 'a' } }, '(-)(9.,9)(e)(99)': { '0': { action_: 'enumber', nextState: 'a' } }, 'space': { '0|a': {} }, 'pm-operator': { '0|a': { action_: { type_: 'operator', option: '\\pm' }, nextState: '0' } }, 'operator': { '0|a': { action_: 'copy', nextState: '0' } }, '//': { 'd': { action_: 'o=', nextState: '/' } }, '/': { 'd': { action_: 'o=', nextState: '/' } }, '{...}|else': { '0|d': { action_: 'd=', nextState: 'd' }, 'a': { action_: [ 'space', 'd=' ], nextState: 'd' }, '/|q': { action_: 'q=', nextState: 'q' } } }), actions: { 'enumber': function (buffer, m) { /** @type {ParserOutput[]} */ var ret = []; if (m[0] === "+-" || m[0] === "+/-") { ret.push("\\pm "); } else if (m[0]) { ret.push(m[0]); } if (m[1]) { mhchemParser.concatArray(ret, mhchemParser.go(m[1], 'pu-9,9')); if (m[2]) { if (m[2].match(/[,.]/)) { mhchemParser.concatArray(ret, mhchemParser.go(m[2], 'pu-9,9')); } else { ret.push(m[2]); } } m[3] = m[4] || m[3]; if (m[3]) { m[3] = m[3].trim(); if (m[3] === "e" || m[3].substr(0, 1) === "*") { ret.push({ type_: 'cdot' }); } else { ret.push({ type_: 'times' }); } } } if (m[3]) { ret.push("10^{"+m[5]+"}"); } return ret; }, 'number^': function (buffer, m) { /** @type {ParserOutput[]} */ var ret = []; if (m[0] === "+-" || m[0] === "+/-") { ret.push("\\pm "); } else if (m[0]) { ret.push(m[0]); } mhchemParser.concatArray(ret, mhchemParser.go(m[1], 'pu-9,9')); ret.push("^{"+m[2]+"}"); return ret; }, 'operator': function (buffer, m, p1) { return { type_: 'operator', kind_: (p1 || m) }; }, 'space': function () { return { type_: 'pu-space-1' }; }, 'output': function (buffer) { /** @type {ParserOutput | ParserOutput[]} */ var ret; var md = mhchemParser.patterns.match_('{(...)}', buffer.d || ""); if (md && md.remainder === '') { buffer.d = md.match_; } var mq = mhchemParser.patterns.match_('{(...)}', buffer.q || ""); if (mq && mq.remainder === '') { buffer.q = mq.match_; } if (buffer.d) { buffer.d = buffer.d.replace(/\u00B0C|\^oC|\^{o}C/g, "{}^{\\circ}C"); buffer.d = buffer.d.replace(/\u00B0F|\^oF|\^{o}F/g, "{}^{\\circ}F"); } if (buffer.q) { // fraction buffer.q = buffer.q.replace(/\u00B0C|\^oC|\^{o}C/g, "{}^{\\circ}C"); buffer.q = buffer.q.replace(/\u00B0F|\^oF|\^{o}F/g, "{}^{\\circ}F"); var b5 = { d: mhchemParser.go(buffer.d, 'pu'), q: mhchemParser.go(buffer.q, 'pu') }; if (buffer.o === '//') { ret = { type_: 'pu-frac', p1: b5.d, p2: b5.q }; } else { ret = b5.d; if (b5.d.length > 1 || b5.q.length > 1) { ret.push({ type_: ' / ' }); } else { ret.push({ type_: '/' }); } mhchemParser.concatArray(ret, b5.q); } } else { // no fraction ret = mhchemParser.go(buffer.d, 'pu-2'); } for (var p in buffer) { delete buffer[p]; } return ret; } } }, 'pu-2': { transitions: mhchemParser.createTransitions({ 'empty': { '*': { action_: 'output' } }, '*': { '*': { action_: [ 'output', 'cdot' ], nextState: '0' } }, '\\x': { '*': { action_: 'rm=' } }, 'space': { '*': { action_: [ 'output', 'space' ], nextState: '0' } }, '^{(...)}|^(-1)': { '1': { action_: '^(-1)' } }, '-9.,9': { '0': { action_: 'rm=', nextState: '0' }, '1': { action_: '^(-1)', nextState: '0' } }, '{...}|else': { '*': { action_: 'rm=', nextState: '1' } } }), actions: { 'cdot': function () { return { type_: 'tight cdot' }; }, '^(-1)': function (buffer, m) { buffer.rm += "^{"+m+"}"; }, 'space': function () { return { type_: 'pu-space-2' }; }, 'output': function (buffer) { /** @type {ParserOutput | ParserOutput[]} */ var ret = []; if (buffer.rm) { var mrm = mhchemParser.patterns.match_('{(...)}', buffer.rm || ""); if (mrm && mrm.remainder === '') { ret = mhchemParser.go(mrm.match_, 'pu'); } else { ret = { type_: 'rm', p1: buffer.rm }; } } for (var p in buffer) { delete buffer[p]; } return ret; } } }, 'pu-9,9': { transitions: mhchemParser.createTransitions({ 'empty': { '0': { action_: 'output-0' }, 'o': { action_: 'output-o' } }, ',': { '0': { action_: [ 'output-0', 'comma' ], nextState: 'o' } }, '.': { '0': { action_: [ 'output-0', 'copy' ], nextState: 'o' } }, 'else': { '*': { action_: 'text=' } } }), actions: { 'comma': function () { return { type_: 'commaDecimal' }; }, 'output-0': function (buffer) { /** @type {ParserOutput[]} */ var ret = []; buffer.text_ = buffer.text_ || ""; if (buffer.text_.length > 4) { var a = buffer.text_.length % 3; if (a === 0) { a = 3; } for (var i=buffer.text_.length-3; i>0; i-=3) { ret.push(buffer.text_.substr(i, 3)); ret.push({ type_: '1000 separator' }); } ret.push(buffer.text_.substr(0, a)); ret.reverse(); } else { ret.push(buffer.text_); } for (var p in buffer) { delete buffer[p]; } return ret; }, 'output-o': function (buffer) { /** @type {ParserOutput[]} */ var ret = []; buffer.text_ = buffer.text_ || ""; if (buffer.text_.length > 4) { var a = buffer.text_.length - 3; for (var i=0; i, so we change \vphantom{X} to {} // TODO: Reinstate \vphantom{X} when the Firefox bug is fixed. // res += "{\\vphantom{X}}"; res += "{{}}"; res += "_{\\smash[t]{"+b5.q+"}}"; } } else { if (b5.q) { // TODO: Reinstate \vphantom{X} when the Firefox bug is fixed. // res += "{\\vphantom{X}}"; res += "{{}}"; res += "_{\\smash[t]{"+b5.q+"}}"; } if (b5.d) { // TODO: Reinstate \vphantom{X} when the Firefox bug is fixed. // res += "{\\vphantom{X}}"; res += "{{}}"; res += "^{"+b5.d+"}"; } } break; case 'rm': res = "\\mathrm{"+buf.p1+"}"; break; case 'text': if (buf.p1.match(/[\^_]/)) { buf.p1 = buf.p1.replace(" ", "~").replace("-", "\\text{-}"); res = "\\mathrm{"+buf.p1+"}"; } else { res = "\\text{"+buf.p1+"}"; } break; case 'roman numeral': res = "\\mathrm{"+buf.p1+"}"; break; case 'state of aggregation': res = "\\mskip2mu "+texify._goInner(buf.p1); break; case 'state of aggregation subscript': res = "\\mskip1mu "+texify._goInner(buf.p1); break; case 'bond': res = texify._getBond(buf.kind_); if (!res) { throw ["MhchemErrorBond", "mhchem Error. Unknown bond type (" + buf.kind_ + ")"]; } break; case 'frac': var c = "\\frac{" + buf.p1 + "}{" + buf.p2 + "}"; res = "\\mathchoice{\\textstyle"+c+"}{"+c+"}{"+c+"}{"+c+"}"; break; case 'pu-frac': var d = "\\frac{" + texify._goInner(buf.p1) + "}{" + texify._goInner(buf.p2) + "}"; res = "\\mathchoice{\\textstyle"+d+"}{"+d+"}{"+d+"}{"+d+"}"; break; case 'tex-math': res = buf.p1 + " "; break; case 'frac-ce': res = "\\frac{" + texify._goInner(buf.p1) + "}{" + texify._goInner(buf.p2) + "}"; break; case 'overset': res = "\\overset{" + texify._goInner(buf.p1) + "}{" + texify._goInner(buf.p2) + "}"; break; case 'underset': res = "\\underset{" + texify._goInner(buf.p1) + "}{" + texify._goInner(buf.p2) + "}"; break; case 'underbrace': res = "\\underbrace{" + texify._goInner(buf.p1) + "}_{" + texify._goInner(buf.p2) + "}"; break; case 'color': res = "{\\color{" + buf.color1 + "}{" + texify._goInner(buf.color2) + "}}"; break; case 'color0': res = "\\color{" + buf.color + "}"; break; case 'arrow': var b6 = { rd: texify._goInner(buf.rd), rq: texify._goInner(buf.rq) }; var arrow = texify._getArrow(buf.r); if (b6.rq) { arrow += "[{\\rm " + b6.rq + "}]"; } if (b6.rd) { arrow += "{\\rm " + b6.rd + "}"; } else { arrow += "{}"; } res = arrow; break; case 'operator': res = texify._getOperator(buf.kind_); break; case '1st-level escape': res = buf.p1+" "; // &, \\\\, \\hlin break; case 'space': res = " "; break; case 'entitySkip': res = "~"; break; case 'pu-space-1': res = "~"; break; case 'pu-space-2': res = "\\mkern3mu "; break; case '1000 separator': res = "\\mkern2mu "; break; case 'commaDecimal': res = "{,}"; break; case 'comma enumeration L': res = "{"+buf.p1+"}\\mkern6mu "; break; case 'comma enumeration M': res = "{"+buf.p1+"}\\mkern3mu "; break; case 'comma enumeration S': res = "{"+buf.p1+"}\\mkern1mu "; break; case 'hyphen': res = "\\text{-}"; break; case 'addition compound': res = "\\,{\\cdot}\\,"; break; case 'electron dot': res = "\\mkern1mu \\text{\\textbullet}\\mkern1mu "; break; case 'KV x': res = "{\\times}"; break; case 'prime': res = "\\prime "; break; case 'cdot': res = "\\cdot "; break; case 'tight cdot': res = "\\mkern1mu{\\cdot}\\mkern1mu "; break; case 'times': res = "\\times "; break; case 'circa': res = "{\\sim}"; break; case '^': res = "uparrow"; break; case 'v': res = "downarrow"; break; case 'ellipsis': res = "\\ldots "; break; case '/': res = "/"; break; case ' / ': res = "\\,/\\,"; break; default: assertNever(buf); throw ["MhchemBugT", "mhchem bug T. Please report."]; // Missing texify rule or unknown MhchemParser output } assertString(res); return res; }, _getArrow: function (a) { switch (a) { case "->": return "\\yields"; case "\u2192": return "\\yields"; case "\u27F6": return "\\yields"; case "<-": return "\\yieldsLeft"; case "<->": return "\\mesomerism"; case "<-->": return "\\yieldsLeftRight"; case "<=>": return "\\equilibrium"; case "\u21CC": return "\\equilibrium"; case "<=>>": return "\\equilibriumRight"; case "<<=>": return "\\equilibriumLeft"; default: assertNever(a); throw ["MhchemBugT", "mhchem bug T. Please report."]; } }, _getBond: function (a) { switch (a) { case "-": return "{-}"; case "1": return "{-}"; case "=": return "{=}"; case "2": return "{=}"; case "#": return "{\\equiv}"; case "3": return "{\\equiv}"; case "~": return "{\\tripleDash}"; case "~-": return "{\\tripleDashOverLine}"; case "~=": return "{\\tripleDashOverDoubleLine}"; case "~--": return "{\\tripleDashOverDoubleLine}"; case "-~-": return "{\\tripleDashBetweenDoubleLine}"; case "...": return "{{\\cdot}{\\cdot}{\\cdot}}"; case "....": return "{{\\cdot}{\\cdot}{\\cdot}{\\cdot}}"; case "->": return "{\\rightarrow}"; case "<-": return "{\\leftarrow}"; case "<": return "{<}"; case ">": return "{>}"; default: assertNever(a); throw ["MhchemBugT", "mhchem bug T. Please report."]; } }, _getOperator: function (a) { switch (a) { case "+": return " {}+{} "; case "-": return " {}-{} "; case "=": return " {}={} "; case "<": return " {}<{} "; case ">": return " {}>{} "; case "<<": return " {}\\ll{} "; case ">>": return " {}\\gg{} "; case "\\pm": return " {}\\pm{} "; case "\\approx": return " {}\\approx{} "; case "$\\approx$": return " {}\\approx{} "; case "v": return " \\downarrow{} "; case "(v)": return " \\downarrow{} "; case "^": return " \\uparrow{} "; case "(^)": return " \\uparrow{} "; default: assertNever(a); throw ["MhchemBugT", "mhchem bug T. Please report."]; } } }; // // Helpers for code analysis // Will show type error at calling position // /** @param {number} a */ function assertNever(a) {} /** @param {string} a */ function assertString(a) {} /* eslint-disable no-undef */ ////////////////////////////////////////////////////////////////////// // 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("\\bull", "\\bullet"); defineMacro("\\clubs", "\\clubsuit"); defineMacro("\\cnums", "\\mathbb{C}"); defineMacro("\\Complex", "\\mathbb{C}"); defineMacro("\\Dagger", "\\ddagger"); defineMacro("\\diamonds", "\\diamondsuit"); defineMacro("\\empty", "\\emptyset"); defineMacro("\\exist", "\\exists"); defineMacro("\\harr", "\\leftrightarrow"); defineMacro("\\hArr", "\\Leftrightarrow"); defineMacro("\\Harr", "\\Leftrightarrow"); defineMacro("\\hearts", "\\heartsuit"); defineMacro("\\image", "\\Im"); defineMacro("\\infin", "\\infty"); defineMacro("\\isin", "\\in"); defineMacro("\\larr", "\\leftarrow"); defineMacro("\\lArr", "\\Leftarrow"); defineMacro("\\Larr", "\\Leftarrow"); defineMacro("\\lrarr", "\\leftrightarrow"); defineMacro("\\lrArr", "\\Leftrightarrow"); defineMacro("\\Lrarr", "\\Leftrightarrow"); defineMacro("\\natnums", "\\mathbb{N}"); defineMacro("\\plusmn", "\\pm"); defineMacro("\\rarr", "\\rightarrow"); defineMacro("\\rArr", "\\Rightarrow"); defineMacro("\\Rarr", "\\Rightarrow"); defineMacro("\\real", "\\Re"); defineMacro("\\reals", "\\mathbb{R}"); defineMacro("\\Reals", "\\mathbb{R}"); defineMacro("\\sdot", "\\cdot"); defineMacro("\\sect", "\\S"); defineMacro("\\spades", "\\spadesuit"); defineMacro("\\sub", "\\subset"); defineMacro("\\sube", "\\subseteq"); defineMacro("\\supe", "\\supseteq"); defineMacro("\\thetasym", "\\vartheta"); defineMacro("\\weierp", "\\wp"); /* eslint-disable no-undef */ /**************************************************** * * physics.js * * Implements the Physics Package for LaTeX input. * * --------------------------------------------------------------------- * * The original version of this file is licensed as follows: * Copyright (c) 2015-2016 Kolen Cheung . * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * --------------------------------------------------------------------- * * This file has been revised from the original in the following ways: * 1. The interface is changed so that it can be called from Temml, not MathJax. * 2. \Re and \Im are not used, to avoid conflict with existing LaTeX letters. * * This revision of the file is released under the MIT license. * https://mit-license.org/ */ defineMacro("\\quantity", "{\\left\\{ #1 \\right\\}}"); defineMacro("\\qty", "{\\left\\{ #1 \\right\\}}"); defineMacro("\\pqty", "{\\left( #1 \\right)}"); defineMacro("\\bqty", "{\\left[ #1 \\right]}"); defineMacro("\\vqty", "{\\left\\vert #1 \\right\\vert}"); defineMacro("\\Bqty", "{\\left\\{ #1 \\right\\}}"); defineMacro("\\absolutevalue", "{\\left\\vert #1 \\right\\vert}"); defineMacro("\\abs", "{\\left\\vert #1 \\right\\vert}"); defineMacro("\\norm", "{\\left\\Vert #1 \\right\\Vert}"); defineMacro("\\evaluated", "{\\left.#1 \\right\\vert}"); defineMacro("\\eval", "{\\left.#1 \\right\\vert}"); defineMacro("\\order", "{\\mathcal{O} \\left( #1 \\right)}"); defineMacro("\\commutator", "{\\left[ #1 , #2 \\right]}"); defineMacro("\\comm", "{\\left[ #1 , #2 \\right]}"); defineMacro("\\anticommutator", "{\\left\\{ #1 , #2 \\right\\}}"); defineMacro("\\acomm", "{\\left\\{ #1 , #2 \\right\\}}"); defineMacro("\\poissonbracket", "{\\left\\{ #1 , #2 \\right\\}}"); defineMacro("\\pb", "{\\left\\{ #1 , #2 \\right\\}}"); defineMacro("\\vectorbold", "{\\boldsymbol{ #1 }}"); defineMacro("\\vb", "{\\boldsymbol{ #1 }}"); defineMacro("\\vectorarrow", "{\\vec{\\boldsymbol{ #1 }}}"); defineMacro("\\va", "{\\vec{\\boldsymbol{ #1 }}}"); defineMacro("\\vectorunit", "{{\\boldsymbol{\\hat{ #1 }}}}"); defineMacro("\\vu", "{{\\boldsymbol{\\hat{ #1 }}}}"); defineMacro("\\dotproduct", "\\mathbin{\\boldsymbol\\cdot}"); defineMacro("\\vdot", "{\\boldsymbol\\cdot}"); defineMacro("\\crossproduct", "\\mathbin{\\boldsymbol\\times}"); defineMacro("\\cross", "\\mathbin{\\boldsymbol\\times}"); defineMacro("\\cp", "\\mathbin{\\boldsymbol\\times}"); defineMacro("\\gradient", "{\\boldsymbol\\nabla}"); defineMacro("\\grad", "{\\boldsymbol\\nabla}"); defineMacro("\\divergence", "{\\grad\\vdot}"); //defineMacro("\\div", "{\\grad\\vdot}"); Not included in Temml. Conflicts w/LaTeX \div defineMacro("\\curl", "{\\grad\\cross}"); defineMacro("\\laplacian", "\\nabla^2"); defineMacro("\\tr", "{\\operatorname{tr}}"); defineMacro("\\Tr", "{\\operatorname{Tr}}"); defineMacro("\\rank", "{\\operatorname{rank}}"); defineMacro("\\erf", "{\\operatorname{erf}}"); defineMacro("\\Res", "{\\operatorname{Res}}"); defineMacro("\\principalvalue", "{\\mathcal{P}}"); defineMacro("\\pv", "{\\mathcal{P}}"); defineMacro("\\PV", "{\\operatorname{P.V.}}"); // Temml does not use the next two lines. They conflict with LaTeX letters. //defineMacro("\\Re", "{\\operatorname{Re} \\left\\{ #1 \\right\\}}"); //defineMacro("\\Im", "{\\operatorname{Im} \\left\\{ #1 \\right\\}}"); defineMacro("\\qqtext", "{\\quad\\text{ #1 }\\quad}"); defineMacro("\\qq", "{\\quad\\text{ #1 }\\quad}"); defineMacro("\\qcomma", "{\\text{,}\\quad}"); defineMacro("\\qc", "{\\text{,}\\quad}"); defineMacro("\\qcc", "{\\quad\\text{c.c.}\\quad}"); defineMacro("\\qif", "{\\quad\\text{if}\\quad}"); defineMacro("\\qthen", "{\\quad\\text{then}\\quad}"); defineMacro("\\qelse", "{\\quad\\text{else}\\quad}"); defineMacro("\\qotherwise", "{\\quad\\text{otherwise}\\quad}"); defineMacro("\\qunless", "{\\quad\\text{unless}\\quad}"); defineMacro("\\qgiven", "{\\quad\\text{given}\\quad}"); defineMacro("\\qusing", "{\\quad\\text{using}\\quad}"); defineMacro("\\qassume", "{\\quad\\text{assume}\\quad}"); defineMacro("\\qsince", "{\\quad\\text{since}\\quad}"); defineMacro("\\qlet", "{\\quad\\text{let}\\quad}"); defineMacro("\\qfor", "{\\quad\\text{for}\\quad}"); defineMacro("\\qall", "{\\quad\\text{all}\\quad}"); defineMacro("\\qeven", "{\\quad\\text{even}\\quad}"); defineMacro("\\qodd", "{\\quad\\text{odd}\\quad}"); defineMacro("\\qinteger", "{\\quad\\text{integer}\\quad}"); defineMacro("\\qand", "{\\quad\\text{and}\\quad}"); defineMacro("\\qor", "{\\quad\\text{or}\\quad}"); defineMacro("\\qas", "{\\quad\\text{as}\\quad}"); defineMacro("\\qin", "{\\quad\\text{in}\\quad}"); defineMacro("\\differential", "{\\text{d}}"); defineMacro("\\dd", "{\\text{d}}"); defineMacro("\\derivative", "{\\frac{\\text{d}{ #1 }}{\\text{d}{ #2 }}}"); defineMacro("\\dv", "{\\frac{\\text{d}{ #1 }}{\\text{d}{ #2 }}}"); defineMacro("\\partialderivative", "{\\frac{\\partial{ #1 }}{\\partial{ #2 }}}"); defineMacro("\\variation", "{\\delta}"); defineMacro("\\var", "{\\delta}"); defineMacro("\\functionalderivative", "{\\frac{\\delta{ #1 }}{\\delta{ #2 }}}"); defineMacro("\\fdv", "{\\frac{\\delta{ #1 }}{\\delta{ #2 }}}"); defineMacro("\\innerproduct", "{\\left\\langle {#1} \\mid { #2} \\right\\rangle}"); defineMacro("\\outerproduct", "{\\left\\vert { #1 } \\right\\rangle\\left\\langle { #2} \\right\\vert}"); defineMacro("\\dyad", "{\\left\\vert { #1 } \\right\\rangle\\left\\langle { #2} \\right\\vert}"); defineMacro("\\ketbra", "{\\left\\vert { #1 } \\right\\rangle\\left\\langle { #2} \\right\\vert}"); defineMacro("\\op", "{\\left\\vert { #1 } \\right\\rangle\\left\\langle { #2} \\right\\vert}"); defineMacro("\\expectationvalue", "{\\left\\langle {#1 } \\right\\rangle}"); defineMacro("\\expval", "{\\left\\langle {#1 } \\right\\rangle}"); defineMacro("\\ev", "{\\left\\langle {#1 } \\right\\rangle}"); defineMacro("\\matrixelement", "{\\left\\langle{ #1 }\\right\\vert{ #2 }\\left\\vert{#3}\\right\\rangle}"); defineMacro("\\matrixel", "{\\left\\langle{ #1 }\\right\\vert{ #2 }\\left\\vert{#3}\\right\\rangle}"); defineMacro("\\mel", "{\\left\\langle{ #1 }\\right\\vert{ #2 }\\left\\vert{#3}\\right\\rangle}"); /** * 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`. const implicitCommands = { "^": true, // Parser.js _: true, // Parser.js "\\limits": true, // Parser.js "\\nolimits": true // Parser.js }; class MacroExpander { constructor(input, settings, mode) { 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(); } /** * 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) { let start; let end; let 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 (;;) { const 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. const tokens = []; const 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(); } const start = this.future(); let tok; let depth = 0; let 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!"); } const delims = delimiters[0]; for (let i = 0; i < delims.length; i++) { const tok = this.popToken(); if (delims[i] !== tok.text) { throw new ParseError("Use of the macro doesn't match its definition", tok); } } } const args = []; for (let i = 0; i < numArgs; i++) { args.push(this.consumeArg(delimiters && delimiters[i + 1]).tokens); } return args; } /** * 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) { const topToken = this.popToken(); const name = topToken.text; const 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.expansionCount++; if (this.expansionCount > this.settings.maxExpand) { throw new ParseError( "Too many expansions: infinite loop or " + "need to increase maxExpand setting" ); } let tokens = expansion.tokens; const 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 (let i = tokens.length - 1; i >= 0; --i) { let 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 const token = this.stack.pop(); // The token after \noexpand is interpreted as if its meaning were ‘\relax’ if (token.treatAsRelax) { token.text = "\\relax"; } return token } } // This pathway is impossible. 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) { const output = []; const oldStackLength = this.stack.length; this.pushTokens(tokens); while (this.stack.length > oldStackLength) { // Expand only expandable tokens if (this.expandOnce(true) === false) { // fully expanded const token = this.stack.pop(); if (token.treatAsRelax) { // the expansion of \noexpand is the token itself token.noexpand = false; token.treatAsRelax = false; } output.push(token); } } 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) { const 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) { const 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) { const catcode = this.lexer.catcodes[name]; if (catcode != null && catcode !== 13) { return } } const expansion = typeof definition === "function" ? definition(this) : definition; if (typeof expansion === "string") { let numArgs = 0; if (expansion.indexOf("#") !== -1) { const stripped = expansion.replace(/##/g, ""); while (stripped.indexOf("#" + (numArgs + 1)) !== -1) { ++numArgs; } } const bodyLexer = new Lexer(expansion, this.settings); const tokens = []; let tok = bodyLexer.lex(); while (tok.text !== "EOF") { tokens.push(tok); tok = bodyLexer.lex(); } tokens.reverse(); // to fit in with stack using push and pop const 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) || Object.prototype.hasOwnProperty.call(functions, name ) || Object.prototype.hasOwnProperty.call(symbols.math, name ) || Object.prototype.hasOwnProperty.call(symbols.text, name ) || Object.prototype.hasOwnProperty.call(implicitCommands, name ) ); } /** * Determine whether a command is expandable. */ isExpandable(name) { const macro = this.macros.get(name); return macro != null ? typeof macro === "string" || typeof macro === "function" || !macro.unexpandable : Object.prototype.hasOwnProperty.call(functions, name ) && !functions[name].primitive; } } // Helpers for Parser.js handling of Unicode (sub|super)script characters. const unicodeSubRegEx = /^[₊₋₌₍₎₀₁₂₃₄₅₆₇₈₉ₐₑₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓᵦᵧᵨᵩᵪ]/; const 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': 'θ' }); // Used for Unicode input of calligraphic and script letters const asciiFromScript = Object.freeze({ "\ud835\udc9c": "A", "\u212c": "B", "\ud835\udc9e": "C", "\ud835\udc9f": "D", "\u2130": "E", "\u2131": "F", "\ud835\udca2": "G", "\u210B": "H", "\u2110": "I", "\ud835\udca5": "J", "\ud835\udca6": "K", "\u2112": "L", "\u2133": "M", "\ud835\udca9": "N", "\ud835\udcaa": "O", "\ud835\udcab": "P", "\ud835\udcac": "Q", "\u211B": "R", "\ud835\udcae": "S", "\ud835\udcaf": "T", "\ud835\udcb0": "U", "\ud835\udcb1": "V", "\ud835\udcb2": "W", "\ud835\udcb3": "X", "\ud835\udcb4": "Y", "\ud835\udcb5": "Z" }); // Mapping of Unicode accent characters to their LaTeX equivalent in text and // math mode (when they exist). var unicodeAccents = { "\u0301": { text: "\\'", math: "\\acute" }, "\u0300": { text: "\\`", math: "\\grave" }, "\u0308": { text: '\\"', math: "\\ddot" }, "\u0303": { text: "\\~", math: "\\tilde" }, "\u0304": { text: "\\=", math: "\\bar" }, "\u0306": { text: "\\u", math: "\\breve" }, "\u030c": { text: "\\v", math: "\\check" }, "\u0302": { text: "\\^", math: "\\hat" }, "\u0307": { text: "\\.", math: "\\dot" }, "\u030a": { text: "\\r", math: "\\mathring" }, "\u030b": { text: "\\H" }, '\u0327': { text: '\\c' } }; var unicodeSymbols = { "á": "á", "à": "à", "ä": "ä", "ǟ": "ǟ", "ã": "ã", "ā": "ā", "ă": "ă", "ắ": "ắ", "ằ": "ằ", "ẵ": "ẵ", "ǎ": "ǎ", "â": "â", "ấ": "ấ", "ầ": "ầ", "ẫ": "ẫ", "ȧ": "ȧ", "ǡ": "ǡ", "å": "å", "ǻ": "ǻ", "ḃ": "ḃ", "ć": "ć", "č": "č", "ĉ": "ĉ", "ċ": "ċ", "ď": "ď", "ḋ": "ḋ", "é": "é", "è": "è", "ë": "ë", "ẽ": "ẽ", "ē": "ē", "ḗ": "ḗ", "ḕ": "ḕ", "ĕ": "ĕ", "ě": "ě", "ê": "ê", "ế": "ế", "ề": "ề", "ễ": "ễ", "ė": "ė", "ḟ": "ḟ", "ǵ": "ǵ", "ḡ": "ḡ", "ğ": "ğ", "ǧ": "ǧ", "ĝ": "ĝ", "ġ": "ġ", "ḧ": "ḧ", "ȟ": "ȟ", "ĥ": "ĥ", "ḣ": "ḣ", "í": "í", "ì": "ì", "ï": "ï", "ḯ": "ḯ", "ĩ": "ĩ", "ī": "ī", "ĭ": "ĭ", "ǐ": "ǐ", "î": "î", "ǰ": "ǰ", "ĵ": "ĵ", "ḱ": "ḱ", "ǩ": "ǩ", "ĺ": "ĺ", "ľ": "ľ", "ḿ": "ḿ", "ṁ": "ṁ", "ń": "ń", "ǹ": "ǹ", "ñ": "ñ", "ň": "ň", "ṅ": "ṅ", "ó": "ó", "ò": "ò", "ö": "ö", "ȫ": "ȫ", "õ": "õ", "ṍ": "ṍ", "ṏ": "ṏ", "ȭ": "ȭ", "ō": "ō", "ṓ": "ṓ", "ṑ": "ṑ", "ŏ": "ŏ", "ǒ": "ǒ", "ô": "ô", "ố": "ố", "ồ": "ồ", "ỗ": "ỗ", "ȯ": "ȯ", "ȱ": "ȱ", "ő": "ő", "ṕ": "ṕ", "ṗ": "ṗ", "ŕ": "ŕ", "ř": "ř", "ṙ": "ṙ", "ś": "ś", "ṥ": "ṥ", "š": "š", "ṧ": "ṧ", "ŝ": "ŝ", "ṡ": "ṡ", "ẗ": "ẗ", "ť": "ť", "ṫ": "ṫ", "ú": "ú", "ù": "ù", "ü": "ü", "ǘ": "ǘ", "ǜ": "ǜ", "ǖ": "ǖ", "ǚ": "ǚ", "ũ": "ũ", "ṹ": "ṹ", "ū": "ū", "ṻ": "ṻ", "ŭ": "ŭ", "ǔ": "ǔ", "û": "û", "ů": "ů", "ű": "ű", "ṽ": "ṽ", "ẃ": "ẃ", "ẁ": "ẁ", "ẅ": "ẅ", "ŵ": "ŵ", "ẇ": "ẇ", "ẘ": "ẘ", "ẍ": "ẍ", "ẋ": "ẋ", "ý": "ý", "ỳ": "ỳ", "ÿ": "ÿ", "ỹ": "ỹ", "ȳ": "ȳ", "ŷ": "ŷ", "ẏ": "ẏ", "ẙ": "ẙ", "ź": "ź", "ž": "ž", "ẑ": "ẑ", "ż": "ż", "Á": "Á", "À": "À", "Ä": "Ä", "Ǟ": "Ǟ", "Ã": "Ã", "Ā": "Ā", "Ă": "Ă", "Ắ": "Ắ", "Ằ": "Ằ", "Ẵ": "Ẵ", "Ǎ": "Ǎ", "Â": "Â", "Ấ": "Ấ", "Ầ": "Ầ", "Ẫ": "Ẫ", "Ȧ": "Ȧ", "Ǡ": "Ǡ", "Å": "Å", "Ǻ": "Ǻ", "Ḃ": "Ḃ", "Ć": "Ć", "Č": "Č", "Ĉ": "Ĉ", "Ċ": "Ċ", "Ď": "Ď", "Ḋ": "Ḋ", "É": "É", "È": "È", "Ë": "Ë", "Ẽ": "Ẽ", "Ē": "Ē", "Ḗ": "Ḗ", "Ḕ": "Ḕ", "Ĕ": "Ĕ", "Ě": "Ě", "Ê": "Ê", "Ế": "Ế", "Ề": "Ề", "Ễ": "Ễ", "Ė": "Ė", "Ḟ": "Ḟ", "Ǵ": "Ǵ", "Ḡ": "Ḡ", "Ğ": "Ğ", "Ǧ": "Ǧ", "Ĝ": "Ĝ", "Ġ": "Ġ", "Ḧ": "Ḧ", "Ȟ": "Ȟ", "Ĥ": "Ĥ", "Ḣ": "Ḣ", "Í": "Í", "Ì": "Ì", "Ï": "Ï", "Ḯ": "Ḯ", "Ĩ": "Ĩ", "Ī": "Ī", "Ĭ": "Ĭ", "Ǐ": "Ǐ", "Î": "Î", "İ": "İ", "Ĵ": "Ĵ", "Ḱ": "Ḱ", "Ǩ": "Ǩ", "Ĺ": "Ĺ", "Ľ": "Ľ", "Ḿ": "Ḿ", "Ṁ": "Ṁ", "Ń": "Ń", "Ǹ": "Ǹ", "Ñ": "Ñ", "Ň": "Ň", "Ṅ": "Ṅ", "Ó": "Ó", "Ò": "Ò", "Ö": "Ö", "Ȫ": "Ȫ", "Õ": "Õ", "Ṍ": "Ṍ", "Ṏ": "Ṏ", "Ȭ": "Ȭ", "Ō": "Ō", "Ṓ": "Ṓ", "Ṑ": "Ṑ", "Ŏ": "Ŏ", "Ǒ": "Ǒ", "Ô": "Ô", "Ố": "Ố", "Ồ": "Ồ", "Ỗ": "Ỗ", "Ȯ": "Ȯ", "Ȱ": "Ȱ", "Ő": "Ő", "Ṕ": "Ṕ", "Ṗ": "Ṗ", "Ŕ": "Ŕ", "Ř": "Ř", "Ṙ": "Ṙ", "Ś": "Ś", "Ṥ": "Ṥ", "Š": "Š", "Ṧ": "Ṧ", "Ŝ": "Ŝ", "Ṡ": "Ṡ", "Ť": "Ť", "Ṫ": "Ṫ", "Ú": "Ú", "Ù": "Ù", "Ü": "Ü", "Ǘ": "Ǘ", "Ǜ": "Ǜ", "Ǖ": "Ǖ", "Ǚ": "Ǚ", "Ũ": "Ũ", "Ṹ": "Ṹ", "Ū": "Ū", "Ṻ": "Ṻ", "Ŭ": "Ŭ", "Ǔ": "Ǔ", "Û": "Û", "Ů": "Ů", "Ű": "Ű", "Ṽ": "Ṽ", "Ẃ": "Ẃ", "Ẁ": "Ẁ", "Ẅ": "Ẅ", "Ŵ": "Ŵ", "Ẇ": "Ẇ", "Ẍ": "Ẍ", "Ẋ": "Ẋ", "Ý": "Ý", "Ỳ": "Ỳ", "Ÿ": "Ÿ", "Ỹ": "Ỹ", "Ȳ": "Ȳ", "Ŷ": "Ŷ", "Ẏ": "Ẏ", "Ź": "Ź", "Ž": "Ž", "Ẑ": "Ẑ", "Ż": "Ż", "ά": "ά", "ὰ": "ὰ", "ᾱ": "ᾱ", "ᾰ": "ᾰ", "έ": "έ", "ὲ": "ὲ", "ή": "ή", "ὴ": "ὴ", "ί": "ί", "ὶ": "ὶ", "ϊ": "ϊ", "ΐ": "ΐ", "ῒ": "ῒ", "ῑ": "ῑ", "ῐ": "ῐ", "ό": "ό", "ὸ": "ὸ", "ύ": "ύ", "ὺ": "ὺ", "ϋ": "ϋ", "ΰ": "ΰ", "ῢ": "ῢ", "ῡ": "ῡ", "ῠ": "ῠ", "ώ": "ώ", "ὼ": "ὼ", "Ύ": "Ύ", "Ὺ": "Ὺ", "Ϋ": "Ϋ", "Ῡ": "Ῡ", "Ῠ": "Ῠ", "Ώ": "Ώ", "Ὼ": "Ὼ" }; /* eslint no-constant-condition:0 */ const binLeftCancellers = ["bin", "op", "open", "punct", "rel"]; /** * 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, isPreamble = false) { // 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; // Are we defining a preamble? this.isPreamble = isPreamble; // Count leftright depth (for \middle errors) this.leftrightDepth = 0; this.prevAtomType = ""; } /** * Checks a result to make sure it has the right type, and throws an * appropriate error otherwise. */ expect(text, 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() { // Create a group namespace for every $...$, $$...$$, \[...\].) // A \def is then valid only within that pair of delimiters. this.gullet.beginGroup(); if (this.settings.colorIsTextColor) { // 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. this.gullet.macros.set("\\color", "\\textcolor"); } // Try to parse the input const parse = this.parseExpression(false); // If we succeeded, make sure there's an EOF at the end this.expect("EOF"); if (this.isPreamble) { const macros = Object.create(null); Object.entries(this.gullet.macros.current).forEach(([key, value]) => { macros[key] = value; }); this.gullet.endGroup(); return macros } // The only local macro that we want to save is from \tag. const tag = this.gullet.macros.get("\\df@tag"); // End the group namespace for the expression this.gullet.endGroup(); if (tag) { this.gullet.macros.current["\\df@tag"] = tag; } return parse; } static get endOfExpression() { return ["}", "\\endgroup", "\\end", "\\right", "\\endtoggle", "&"]; } /** * 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. const oldToken = this.nextToken; this.consume(); // Run the new job, terminating it with an excess '}' this.gullet.pushToken(new Token("}")); this.gullet.pushTokens(tokens); const 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. * * `breakOnMiddle`: \color, \over, and old styling functions work on an implicit group. * These groups end just before the usual tokens, but they also * end just before `\middle`. */ parseExpression(breakOnInfix, breakOnTokenText, breakOnMiddle) { const 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(); } const lex = this.fetch(); if (Parser.endOfExpression.indexOf(lex.text) !== -1) { break; } if (breakOnTokenText && lex.text === breakOnTokenText) { break; } if (breakOnMiddle && lex.text === "\\middle") { break } if (breakOnInfix && functions[lex.text] && functions[lex.text].infix) { break; } const atom = this.parseAtom(breakOnTokenText); if (!atom) { break; } else if (atom.type === "internal") { continue; } body.push(atom); // Keep a record of the atom type, so that op.js can set correct spacing. this.prevAtomType = atom.type === "atom" ? atom.family : atom.type; } 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) { let overIndex = -1; let funcName; for (let 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) { let numerNode; let denomNode; const numerBody = body.slice(0, overIndex); const 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 }; } let 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. ) { const symbolToken = this.fetch(); const symbol = symbolToken.text; this.consume(); this.consumeSpaces(); // ignore spaces before sup/subscript argument const group = this.parseGroup(name); 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) { const textordArray = []; for (let i = 0; i < text.length; i++) { textordArray.push({ type: "textord", mode: "text", text: text[i] }); } const textNode = { type: "text", mode: this.mode, body: textordArray }; const 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. const base = this.parseGroup("atom", breakOnTokenText); // 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. let superscript; let subscript; while (true) { // Guaranteed in math mode, so eat any spaces first. this.consumeSpaces(); // Lex the first token const lex = this.fetch(); if (lex.text === "\\limits" || lex.text === "\\nolimits") { // We got a limit control if (base && base.type === "op") { const 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); } const prime = { type: "textord", mode: this.mode, text: "\\prime" }; // Many primes can be grouped together, so we handle this here const 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. const isSub = unicodeSubRegEx.test(lex.text); const subsupTokens = []; subsupTokens.push(new Token(uSubsAndSups[lex.text])); this.consume(); // Continue fetching tokens to fill out the group. while (true) { const 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. const body = this.subparse(subsupTokens); if (isSub) { subscript = { type: "ordgroup", mode: "math", body }; } else { superscript = { type: "ordgroup", mode: "math", body }; } } else { // If it wasn't ^, _, a Unicode (sub|super)script, or ', stop parsing super/subscripts break; } } if (superscript || subscript) { if (base && base.type === "multiscript" && !base.postscripts) { // base is the result of a \prescript function. // Write the sub- & superscripts into the multiscript element. base.postscripts = { sup: superscript, sub: subscript }; return base } else { // We got either a superscript or subscript, create a supsub const isFollowedByDelimiter = (!base || base.type !== "op" && base.type !== "operatorname") ? undefined : isDelimiter(this.nextToken.text); return { type: "supsub", mode: this.mode, base: base, sup: superscript, sub: subscript, isFollowedByDelimiter } } } 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 ) { const token = this.fetch(); const func = token.text; const 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); } const prevAtomType = this.prevAtomType; const { args, optArgs } = this.parseArguments(func, funcData); this.prevAtomType = prevAtomType; return this.callFunction(func, args, optArgs, token, breakOnTokenText); } /** * Call a function handler with a suitable context and arguments. */ callFunction(name, args, optArgs, token, breakOnTokenText) { const context = { funcName: name, parser: this, token, breakOnTokenText }; const 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 ) { const totalArgs = funcData.numArgs + funcData.numOptionalArgs; if (totalArgs === 0) { return { args: [], optArgs: [] }; } const args = []; const optArgs = []; for (let i = 0; i < totalArgs; i++) { let argType = funcData.argTypes && funcData.argTypes[i]; const 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"; } const 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 "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. const group = this.parseArgumentGroup(optional, "text"); return group != null ? { type: "styling", mode: group.mode, body: [group], scriptLevel: "text" // simulate \textstyle } : null; } case "raw": { const 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"); } const group = this.parseGroup(name); if (group == null) { throw new ParseError("Expected group as " + name, this.fetch()); } return group; } 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 (true) { const ch = this.fetch().text; // \ufe0e is the Unicode variation selector to supress emoji. Ignore it. if (ch === " " || ch === "\u00a0" || ch === "\ufe0e") { this.consume(); } else { break } } } /** * 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 ) { const argToken = this.gullet.scanArgument(optional); if (argToken == null) { return null; } let str = ""; let 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. ) { const firstToken = this.fetch(); let lastToken = firstToken; let str = ""; let 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 size specification, consisting of magnitude and unit. */ parseSizeGroup(optional) { let res; let 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 } const match = /([-+]?) *(\d+(?:\.\d*)?|\.\d+) *([a-z]{2})/.exec(res.text); if (!match) { throw new ParseError("Invalid size: '" + res.text + "'", res); } const 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 const 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. let url = res.text.replace(/\\([#$%&~_^{}])/g, "$1"); url = res.text.replace(/{\u2044}/g, "/"); return { type: "url", mode: this.mode, url }; } /** * Parses an argument with the mode specified. */ parseArgumentGroup(optional, mode) { const argToken = this.gullet.scanArgument(optional); if (argToken == null) { return null; } const outerMode = this.mode; if (mode) { // Switch to specified mode this.switchMode(mode); } this.gullet.beginGroup(); const 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(); const 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 ) { const firstToken = this.fetch(); const text = firstToken.text; let result; // Try to parse an open brace or \begingroup if (text === "{" || text === "\\begingroup" || text === "\\toggle") { this.consume(); const groupEnd = text === "{" ? "}" : text === "\\begingroup" ? "\\endgroup" : "\\endtoggle"; this.gullet.beginGroup(); // If we get a brace, parse an expression const expression = this.parseExpression(false, groupEnd); const lastToken = this.fetch(); this.expect(groupEnd); // Check that we got a matching closing brace this.gullet.endGroup(); result = { type: (lastToken.text === "\\endtoggle" ? "toggle" : "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/ 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] === "\\" && !Object.prototype.hasOwnProperty.call(implicitCommands, text )) { 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) { let n = group.length - 1; for (let i = 0; i < n; ++i) { const a = group[i]; const v = a.text; if (v === "-" && group[i + 1].text === "-") { if (i + 1 < n && group[i + 2].text === "-") { group.splice(i, 3, { type: "textord", mode: "text", loc: SourceLocation.range(a, group[i + 2]), text: "---" }); n -= 2; } else { group.splice(i, 2, { type: "textord", mode: "text", loc: SourceLocation.range(a, group[i + 1]), text: "--" }); n -= 1; } } if ((v === "'" || v === "`") && group[i + 1].text === v) { group.splice(i, 2, { type: "textord", mode: "text", loc: SourceLocation.range(a, group[i + 1]), text: v + v }); n -= 1; } } } /** * Parse a single symbol out of the string. Here, we handle single character * symbols and special functions like \verb. */ parseSymbol() { const nucleus = this.fetch(); let text = nucleus.text; if (/^\\verb[^a-zA-Z]/.test(text)) { this.consume(); let arg = text.slice(5); const star = arg.charAt(0) === "*"; if (star) { arg = arg.slice(1); } // Lexer's tokenRegex is constructed to always have matching // first/last characters. if (arg.length < 2 || arg.charAt(0) !== arg.slice(-1)) { throw new ParseError(`\\verb assertion failed -- please report what input caused this bug`); } arg = arg.slice(1, -1); // remove first and last char return { type: "verb", mode: "text", body: arg, star }; } // At this point, we should have a symbol, possibly with accents. // First expand any accented base symbol according to unicodeSymbols. if (Object.prototype.hasOwnProperty.call(unicodeSymbols, text[0]) && !symbols[this.mode][text[0]]) { // This behavior is not strict (XeTeX-compatible) in math mode. if (this.settings.strict && this.mode === "math") { throw new ParseError(`Accented Unicode text character "${text[0]}" used in ` + `math mode`, nucleus ); } text = unicodeSymbols[text[0]] + text.slice(1); } // Strip off any combining characters const match = combiningDiacriticalMarksEndRegex.exec(text); if (match) { text = text.substring(0, match.index); if (text === "i") { text = "\u0131"; // dotless i, in math and text mode } else if (text === "j") { text = "\u0237"; // dotless j, in math and text mode } } // Recognize base symbol let symbol; if (symbols[this.mode][text]) { let group = symbols[this.mode][text].group; if (group === "bin" && binLeftCancellers.includes(this.prevAtomType)) { // Change from a binary operator to a unary (prefix) operator group = "open"; } const loc = SourceLocation.range(nucleus); let s; if (Object.prototype.hasOwnProperty.call(ATOMS, group )) { const family = group; s = { type: "atom", mode: this.mode, family, loc, text }; } else { if (asciiFromScript[text]) { // Unicode 14 disambiguates chancery from roundhand. // See https://www.unicode.org/charts/PDF/U1D400.pdf this.consume(); const nextCode = this.fetch().text.charCodeAt(0); // mathcal is Temml default. Use mathscript if called for. const font = nextCode === 0xfe01 ? "mathscr" : "mathcal"; if (nextCode === 0xfe00 || nextCode === 0xfe01) { this.consume(); } return { type: "font", mode: "math", font, body: { type: "mathord", mode: "math", loc, text: asciiFromScript[text] } } } // Default ord character. No disambiguation necessary. s = { type: group, mode: this.mode, loc, text }; } symbol = s; } else if (text.charCodeAt(0) >= 0x80) { // no symbol for e.g. ^ if (this.settings.strict && this.mode === "math") { throw new ParseError(`Unicode text character "${text[0]}" used in math mode`, nucleus) } // All nonmathematical Unicode characters are rendered as if they // are in text mode (wrapped in \text) because that's what it // takes to render them in LaTeX. symbol = { type: "textord", mode: "text", loc: SourceLocation.range(nucleus), text }; } else { return null; // EOF, ^, _, {, }, etc. } this.consume(); // Transform combining characters into accents if (match) { for (let i = 0; i < match[0].length; i++) { const accent = match[0][i]; if (!unicodeAccents[accent]) { throw new ParseError(`Unknown accent ' ${accent}'`, nucleus); } const command = unicodeAccents[accent][this.mode] || unicodeAccents[accent].text; if (!command) { throw new ParseError(`Accent ${accent} unsupported in ${this.mode} mode`, nucleus); } symbol = { type: "accent", mode: this.mode, loc: SourceLocation.range(nucleus), label: command, isStretchy: false, base: symbol }; } } return symbol; } } /** * Parses an expression using a Parser, then returns the parsed result. */ const parseTree = function(toParse, settings) { if (!(typeof toParse === "string" || toParse instanceof String)) { throw new TypeError("Temml can only parse string typed expression") } const parser = new Parser(toParse, settings); // Blank out any \df@tag to avoid spurious "Duplicate \tag" errors delete parser.gullet.macros.current["\\df@tag"]; let tree = parser.parse(); // LaTeX ignores a \tag placed outside an AMS environment. if (!(tree.length > 0 && tree[0].type && tree[0].type === "array" && tree[0].addEqnNum)) { // If the input used \tag, it will set the \df@tag macro to the tag. // In this case, we separately parse the tag and wrap the tree. if (parser.gullet.macros.get("\\df@tag")) { if (!settings.displayMode) { throw new ParseError("\\tag works only in display mode") } parser.gullet.feed("\\df@tag"); tree = [ { type: "tag", mode: "text", body: tree, tag: parser.parse() } ]; } } return tree }; /** * This file contains information about the style that the mathmlBuilder carries * around with it. Data is held in an `Style` object, and when * recursing, a new `Style` object can be created with the `.with*` functions. */ const subOrSupLevel = [2, 2, 3, 3]; /** * This is the main Style class. It contains the current style.level, color, and font. * * Style objects should not be modified. To create a new Style with * different properties, call a `.with*` method. */ class Style { constructor(data) { // Style.level can be 0 | 1 | 2 | 3, which correspond to // displaystyle, textstyle, scriptstyle, and scriptscriptstyle. // style.level does not directly set MathML's script level. MathML does that itself. // We use style.level to track, not set, math style so that we can get the // correct scriptlevel when needed in supsub.js, mathchoice.js, or for dimensions in em. this.level = data.level; this.color = data.color; // string | void // 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 this.font = data.font || ""; // string this.fontFamily = data.fontFamily || ""; // string this.fontSize = data.fontSize || 1.0; // number this.fontWeight = data.fontWeight || ""; this.fontShape = data.fontShape || ""; this.maxSize = data.maxSize; // [number, number] } /** * Returns a new style object with the same properties as "this". Properties * from "extension" will be copied to the new style object. */ extend(extension) { const data = { level: this.level, color: this.color, font: this.font, fontFamily: this.fontFamily, fontSize: this.fontSize, fontWeight: this.fontWeight, fontShape: this.fontShape, maxSize: this.maxSize }; for (const key in extension) { if (Object.prototype.hasOwnProperty.call(extension, key)) { data[key] = extension[key]; } } return new Style(data); } withLevel(n) { return this.extend({ level: n }); } incrementLevel() { return this.extend({ level: Math.min(this.level + 1, 3) }); } inSubOrSup() { return this.extend({ level: subOrSupLevel[this.level] }) } /** * Create a new style object with the given color. */ withColor(color) { return this.extend({ color: color }); } /** * Creates a new style object with the given math font or old text font. * @type {[type]} */ withFont(font) { return this.extend({ font }); } /** * Create a new style objects with the given fontFamily. */ withTextFontFamily(fontFamily) { return this.extend({ fontFamily, font: "" }); } /** * Creates a new style object with the given font size */ withFontSize(num) { return this.extend({ fontSize: num }); } /** * Creates a new style object with the given font weight */ withTextFontWeight(fontWeight) { return this.extend({ fontWeight, font: "" }); } /** * Creates a new style object with the given font weight */ withTextFontShape(fontShape) { return this.extend({ fontShape, font: "" }); } /** * Gets the CSS color of the current style object */ getColor() { return this.color; } } /* Temml Post Process * Perform two tasks not done by Temml when it created each individual Temml element. * Given a block, * 1. At each AMS auto-numbered environment, assign an id. * 2. Populate the text contents of each \ref & \eqref * * As with other Temml code, this file is released under terms of the MIT license. * https://mit-license.org/ */ const version = "0.10.22"; function postProcess(block) { const labelMap = {}; let i = 0; // Get a collection of the parents of each \tag & auto-numbered equation const parents = block.getElementsByClassName("tml-tageqn"); for (const parent of parents) { const eqns = parent.getElementsByClassName("tml-eqn"); if (eqns. length > 0 ) { // AMS automatically numbered equation. // Assign an id. i += 1; eqns[0].id = "tml-eqn-" + i; // No need to write a number into the text content of the element. // A CSS counter does that even if this postProcess() function is not used. } // If there is a \label, add it to labelMap const labels = parent.getElementsByClassName("tml-label"); if (labels.length === 0) { continue } if (eqns.length > 0) { labelMap[labels[0].id] = String(i); } else { const tags = parent.getElementsByClassName("tml-tag"); if (tags.length > 0) { labelMap[labels[0].id] = tags[0].textContent; } } } // Populate \ref & \eqref text content const refs = block.getElementsByClassName("tml-ref"); [...refs].forEach(ref => { let str = labelMap[ref.getAttribute("href").slice(1)]; if (ref.className.indexOf("tml-eqref") === -1) { // \ref. Omit parens. str = str.replace(/^\(/, ""); str = str.replace(/\($/, ""); } { // \eqref. Include parens if (str.charAt(0) !== "(") { str = "(" + str; } if (str.slice(-1) !== ")") { str = str + ")"; } } ref.textContent = str; }); } /* eslint no-console:0 */ /** * @type {import('./temml').render} * Parse and build an expression, and place that expression in the DOM node * given. */ let render = function(expression, baseNode, options = {}) { baseNode.textContent = ""; const alreadyInMathElement = baseNode.tagName.toLowerCase() === "math"; if (alreadyInMathElement) { options.wrap = "none"; } const math = renderToMathMLTree(expression, options); if (alreadyInMathElement) { // The element already exists. Populate it. baseNode.textContent = ""; math.children.forEach(e => { baseNode.appendChild(e.toNode()); }); } else if (math.children.length > 1) { baseNode.textContent = ""; math.children.forEach(e => { baseNode.appendChild(e.toNode()); }); } else { baseNode.appendChild(math.toNode()); } }; // Temml's styles don't work properly in quirks mode. Print out an error, and // disable rendering. if (typeof document !== "undefined") { if (document.compatMode !== "CSS1Compat") { typeof console !== "undefined" && console.warn( "Warning: Temml doesn't work in quirks mode. Make sure your " + "website has a suitable doctype." ); render = function() { throw new ParseError("Temml doesn't work in quirks mode."); }; } } /** * @type {import('./temml').renderToString} * Parse and build an expression, and return the markup for that. */ const renderToString = function(expression, options) { const markup = renderToMathMLTree(expression, options).toMarkup(); return markup; }; /** * @type {import('./temml').generateParseTree} * Parse an expression and return the parse tree. */ const generateParseTree = function(expression, options) { const settings = new Settings(options); return parseTree(expression, settings); }; /** * @type {import('./temml').definePreamble} * Take an expression which contains a preamble. * Parse it and return the macros. */ const definePreamble = function(expression, options) { const settings = new Settings(options); settings.macros = {}; if (!(typeof expression === "string" || expression instanceof String)) { throw new TypeError("Temml can only parse string typed expression") } const parser = new Parser(expression, settings, true); // Blank out any \df@tag to avoid spurious "Duplicate \tag" errors delete parser.gullet.macros.current["\\df@tag"]; const macros = parser.parse(); return macros }; /** * If the given error is a Temml ParseError, * renders the invalid LaTeX as a span with hover title giving the Temml * error message. Otherwise, simply throws the error. */ const renderError = function(error, expression, options) { if (options.throwOnError || !(error instanceof ParseError)) { throw error; } const node = new Span(["temml-error"], [new TextNode$1(expression + "\n" + error.toString())]); node.style.color = options.errorColor; node.style.whiteSpace = "pre-line"; return node; }; /** * @type {import('./temml').renderToMathMLTree} * Generates and returns the Temml build tree. This is used for advanced * use cases (like rendering to custom output). */ const renderToMathMLTree = function(expression, options) { const settings = new Settings(options); try { const tree = parseTree(expression, settings); const style = new Style({ level: settings.displayMode ? StyleLevel.DISPLAY : StyleLevel.TEXT, maxSize: settings.maxSize }); return buildMathML(tree, expression, style, settings); } catch (error) { return renderError(error, expression, settings); } }; /** @type {import('./temml').default} */ var temml = { /** * Current Temml version */ version: version, /** * Renders the given LaTeX into MathML, and adds * it as a child to the specified DOM node. */ render, /** * Renders the given LaTeX into MathML string, * for sending to the client. */ renderToString, /** * Post-process an entire HTML block. * Writes AMS auto-numbers and implements \ref{}. * Typcally called once, after a loop has rendered many individual spans. */ postProcess, /** * Temml error, usually during parsing. */ ParseError, /** * Creates a set of macros with document-wide scope. */ definePreamble, /** * Parses the given LaTeX into Temml's internal parse tree structure, * without rendering to HTML or MathML. * * NOTE: This method is not currently recommended for public use. * The internal tree representation is unstable and is very likely * to change. Use at your own risk. */ __parse: generateParseTree, /** * Renders the given LaTeX into a MathML internal DOM tree * representation, without flattening that representation to a string. * * NOTE: This method is not currently recommended for public use. * The internal tree representation is unstable and is very likely * to change. Use at your own risk. */ __renderToMathMLTree: renderToMathMLTree, /** * adds a new symbol to builtin symbols table */ __defineSymbol: defineSymbol, /** * adds a new macro to builtin macro list */ __defineMacro: defineMacro }; export { temml as default };