/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ %{ var MathMLNameSpace = "http://www.w3.org/1998/Math/MathML", SVGNameSpace = "http://www.w3.org/2000/svg", TeXMimeTypes = ["TeX", "LaTeX", "text/x-tex", "text/x-latex", "application/x-tex", "application/x-latex"]; function escapeText(aString) { /* Escape reserved XML characters for use as text nodes. */ return aString.replace(/&/g, "&").replace(//g, ">"); } function escapeQuote(aString) { /* Escape the double quote characters for use as attribute. */ return aString.replace(/"/g, """); } function namedSpaceToEm(aString) { var index = [ "negativeveryverythinmathspace", "negativeverythinmathspace", "negativemediummathspace", "negativethickmathspace", "negativeverythickmathspace", "negativeveryverythickmathspace", "", "veryverythinmathspace", "verythinmathspace", "thinmathspace", "mediummathspace", "thickmathspace", "verythickmathspace", "veryverythickmathspace" ].indexOf(aString); return (index === -1 ? 0 : index - 6) / 18.0; } function parseLength(aString) { /* See http://www.w3.org/TR/MathML3/appendixa.html#parsing_length */ aString = aString.trim(); var lengthRegexp = /(-?[0-9]*(?:[0-9]\.?|\.[0-9])[0-9]*)(e[mx]|in|cm|mm|p[xtc]|%)?/, result = lengthRegexp.exec(aString); if (result) { result[1] = parseFloat(result[1]); if (!result[2]) { /* Unitless values are treated as a percent */ result[1] *= 100; result[2] = "%"; } return { l: result[1], u: result[2] }; } return { l: namedSpaceToEm(aString), u: "em" }; } function serializeTree(aTree) { var output = "<" + aTree["tag"]; for (var name in aTree["attributes"]) { if (aTree["attributes"][name] !== undefined) output += " " + name + "=\"" + aTree["attributes"][name] + "\""; } if (aTree["content"]) { output += ">"; if (Array.isArray(aTree["content"])) { aTree["content"].forEach(function(child) { output += serializeTree(child); }); } else output += aTree["content"]; output += ""; } else { output += "/>"; } return output; } function newTag(aTag, aChildren, aAttributes) { return { "tag": aTag, "content": aChildren, "attributes": aAttributes }; } function isEmptyMrow(aTree) { return aTree["tag"] === "mrow" && !aTree["content"] && !aTree["attributes"]; } function newMo(aContent, aLeftSpace, aRightSpace) { return newTag("mo", escapeText(aContent), { "lspace": aLeftSpace !== undefined ? aLeftSpace + "em" : undefined, "rspace": aRightSpace !== undefined ? aRightSpace + "em" : undefined }); } function newMi(aContent, aNormal) { return newTag("mi", escapeText(aContent), aNormal ? { "mathvariant": "normal" } : undefined); } function newSpace(aWidth) { return newTag("mspace", null, {"width": aWidth + "em"}); } function applyMathVariantToCharacter(codePoint, aMathVariant) { // FIXME: We should have LaTeX commmands for all these variants. // See https://github.com/fred-wang/TeXZilla/issues/64 var mathvariant = [ "bold", "italic", "bold-italic", "script", "bold-script", "fraktur", "double-struck", "bold-fraktur", "sans-serif", "bold-sans-serif", "sans-serif-italic", "sans-serif-bold-italic", "monospace", "initial", "tailed", "looped", "stretched" ].indexOf(aMathVariant); var Bold = 0; var Italic = 1; var BoldItalic = 2; var Script = 3; var BoldScript = 4; var Fraktur = 5; var DoubleStruck = 6; var BoldFraktur = 7; var SansSerif = 8; var BoldSansSerif = 9; var SansSerifItalic = 10; var SansSerifBoldItalic = 11; var Monospace = 12; var Initial = 13; var Tailed = 14; var Looped = 15; var Stretched = 16; var greekUpperTheta = 0x03F4; var holeGreekUpperTheta = 0x03A2; var nabla = 0x2207; var partialDifferential = 0x2202; var greekUpperAlpha = 0x0391; var greekUpperOmega = 0x03A9; var greekLowerAlpha = 0x03B1; var greekLowerOmega = 0x03C9; var greekLunateEpsilonSymbol = 0x03F5; var greekThetaSymbol = 0x03D1; var greekKappaSymbol = 0x03F0; var greekPhiSymbol = 0x03D5; var greekRhoSymbol = 0x03F1; var greekPiSymbol = 0x03D6; var greekLetterDigamma = 0x03DC; var greekSmallLetterDigamma = 0x03DD; var mathBoldCapitalDigamma = 0x1D7CA; var mathBoldSmallDigamma = 0x1D7CB; var latinSmallLetterDotlessI = 0x0131; var latinSmallLetterDotlessJ = 0x0237; var mathItalicSmallDotlessI = 0x1D6A4; var mathItalicSmallDotlessJ = 0x1D6A5; var digit0 = 0x30; var digit9 = 0x39; var upperA = 0x41; var upperZ = 0x5A; var smallA = 0x61; var smallZ = 0x7A; var mathBoldUpperA = 0x1D400; var mathItalicUpperA = 0x1D434; var mathBoldSmallA = 0x1D41A; var mathBoldUpperAlpha = 0x1D6A8; var mathBoldSmallAlpha = 0x1D6C2; var mathItalicUpperAlpha = 0x1D6E2; var mathBoldDigitZero = 0x1D7CE; var mathDoubleStruckZero = 0x1D7D8; var mathBoldUpperTheta = 0x1D6B9; var mathBoldNabla = 0x1D6C1; var mathBoldPartialDifferential = 0x1D6DB; var mathBoldEpsilonSymbol = 0x1D6DC; var mathBoldThetaSymbol = 0x1D6DD; var mathBoldKappaSymbol = 0x1D6DE; var mathBoldPhiSymbol = 0x1D6DF; var mathBoldRhoSymbol = 0x1D6E0; var mathBoldPiSymbol = 0x1D6E1; /* Exceptional characters with at most one possible transformation. */ if (codePoint == holeGreekUpperTheta) return codePoint; if (codePoint == greekLetterDigamma) { if (mathvariant == Bold) return mathBoldCapitalDigamma; return codePoint; } if (codePoint == greekSmallLetterDigamma) { if (mathvariant == Bold) return mathBoldSmallDigamma; return codePoint; } if (codePoint == latinSmallLetterDotlessI) { if (mathvariant == Italic) return mathItalicSmallDotlessI; return codePoint; } if (codePoint == latinSmallLetterDotlessJ) { if (mathvariant == Italic) return mathItalicSmallDotlessJ; return codePoint; } var baseChar, multiplier, map; /* Latin */ if (upperA <= codePoint && codePoint <= upperZ || smallA <= codePoint && codePoint <= smallZ) { baseChar = codePoint <= upperZ ? codePoint - upperA : mathBoldSmallA - mathBoldUpperA + codePoint - smallA; multiplier = mathvariant; if (mathvariant > Monospace) return codePoint; // Latin doesn't support the Arabic mathvariants var transformedChar = baseChar + mathBoldUpperA + multiplier * (mathItalicUpperA - mathBoldUpperA); map = { 0x1D455: 0x210E, 0x1D49D: 0x212C, 0x1D4A0: 0x2130, 0x1D4A1: 0x2131, 0x1D4A3: 0x210B, 0x1D4A4: 0x2110, 0x1D4A7: 0x2112, 0x1D4A8: 0x2133, 0x1D4AD: 0x211B, 0x1D4BA: 0x212F, 0x1D4BC: 0x210A, 0x1D4C4: 0x2134, 0x1D506: 0x212D, 0x1D50B: 0x210C, 0x1D50C: 0x2111, 0x1D515: 0x211C, 0x1D51D: 0x2128, 0x1D53A: 0x2102, 0x1D53F: 0x210D, 0x1D545: 0x2115, 0x1D547: 0x2119, 0x1D548: 0x211A, 0x1D549: 0x211D, 0x1D551: 0x2124 }; return map[transformedChar] ? map[transformedChar] : transformedChar; } /* Digits */ if (digit0 <= codePoint && codePoint <= digit9) { baseChar = codePoint - digit0; switch (mathvariant) { case Bold: multiplier = 0; break; case DoubleStruck: multiplier = 1; break; case SansSerif: multiplier = 2; break; case BoldSansSerif: multiplier = 3; break; case Monospace: multiplier = 4; break; default: return codePoint; } return baseChar + multiplier * (mathDoubleStruckZero - mathBoldDigitZero) + mathBoldDigitZero; } // Arabic characters are defined within this range if (0x0600 <= codePoint && codePoint <= 0x06FF) { // The Arabic mathematical block is not continuous, nor does it have a // monotonic mapping to the unencoded characters, requiring the use of a // lookup table. switch (mathvariant) { case Initial: map = { 0x628: 0x1EE21, 0x62A: 0x1EE35, 0x62B: 0x1EE36, 0x62C: 0x1EE22, 0x62D: 0x1EE27, 0x62E: 0x1EE37, 0x633: 0x1EE2E, 0x634: 0x1EE34, 0x635: 0x1EE31, 0x636: 0x1EE39, 0x639: 0x1EE2F, 0x63A: 0x1EE3B, 0x641: 0x1EE30, 0x642: 0x1EE32, 0x643: 0x1EE2A, 0x644: 0x1EE2B, 0x645: 0x1EE2C, 0x646: 0x1EE2D, 0x647: 0x1EE24, 0x64A: 0x1EE29 }; break; case Tailed: map = { 0x62C: 0x1EE42, 0x62D: 0x1EE47, 0x62E: 0x1EE57, 0x633: 0x1EE4E, 0x634: 0x1EE54, 0x635: 0x1EE51, 0x636: 0x1EE59, 0x639: 0x1EE4F, 0x63A: 0x1EE5B, 0x642: 0x1EE52, 0x644: 0x1EE4B, 0x646: 0x1EE4D, 0x64A: 0x1EE49, 0x66F: 0x1EE5F, 0x6BA: 0x1EE5D }; break; case Stretched: map = { 0x628: 0x1EE61, 0x62A: 0x1EE75, 0x62B: 0x1EE76, 0x62C: 0x1EE62, 0x62D: 0x1EE67, 0x62E: 0x1EE77, 0x633: 0x1EE6E, 0x634: 0x1EE74, 0x635: 0x1EE71, 0x636: 0x1EE79, 0x637: 0x1EE68, 0x638: 0x1EE7A, 0x639: 0x1EE6F, 0x63A: 0x1EE7B, 0x641: 0x1EE70, 0x642: 0x1EE72, 0x643: 0x1EE6A, 0x645: 0x1EE6C, 0x646: 0x1EE6D, 0x647: 0x1EE64, 0x64A: 0x1EE69, 0x66E: 0x1EE7C, 0x6A1: 0x1EE7E }; break; case Looped: map = { 0x627: 0x1EE80, 0x628: 0x1EE81, 0x62A: 0x1EE95, 0x62B: 0x1EE96, 0x62C: 0x1EE82, 0x62D: 0x1EE87, 0x62E: 0x1EE97, 0x62F: 0x1EE83, 0x630: 0x1EE98, 0x631: 0x1EE93, 0x632: 0x1EE86, 0x633: 0x1EE8E, 0x634: 0x1EE94, 0x635: 0x1EE91, 0x636: 0x1EE99, 0x637: 0x1EE88, 0x638: 0x1EE9A, 0x639: 0x1EE8F, 0x63A: 0x1EE9B, 0x641: 0x1EE90, 0x642: 0x1EE92, 0x644: 0x1EE8B, 0x645: 0x1EE8C, 0x646: 0x1EE8D, 0x647: 0x1EE84, 0x648: 0x1EE85, 0x64A: 0x1EE89 }; break; case DoubleStruck: map = { 0x628: 0x1EEA1, 0x62A: 0x1EEB5, 0x62B: 0x1EEB6, 0x62C: 0x1EEA2, 0x62D: 0x1EEA7, 0x62E: 0x1EEB7, 0x62F: 0x1EEA3, 0x630: 0x1EEB8, 0x631: 0x1EEB3, 0x632: 0x1EEA6, 0x633: 0x1EEAE, 0x634: 0x1EEB4, 0x635: 0x1EEB1, 0x636: 0x1EEB9, 0x637: 0x1EEA8, 0x638: 0x1EEBA, 0x639: 0x1EEAF, 0x63A: 0x1EEBB, 0x641: 0x1EEB0, 0x642: 0x1EEB2, 0x644: 0x1EEAB, 0x645: 0x1EEAC, 0x646: 0x1EEAD, 0x648: 0x1EEA5, 0x64A: 0x1EEA9 }; break; default: return codePoint; } return map[codePoint] ? map[codePoint] : codePoint; } // Greek if (greekUpperAlpha <= codePoint && codePoint <= greekUpperOmega) { baseChar = codePoint - greekUpperAlpha; } else if (greekLowerAlpha <= codePoint && codePoint <= greekLowerOmega) { baseChar = mathBoldSmallAlpha - mathBoldUpperAlpha + codePoint - greekLowerAlpha; } else { switch (codePoint) { case greekUpperTheta: baseChar = mathBoldUpperTheta - mathBoldUpperAlpha; break; case nabla: baseChar = mathBoldNabla - mathBoldUpperAlpha; break; case partialDifferential: baseChar = mathBoldPartialDifferential - mathBoldUpperAlpha; break; case greekLunateEpsilonSymbol: baseChar = mathBoldEpsilonSymbol - mathBoldUpperAlpha; break; case greekThetaSymbol: baseChar = mathBoldThetaSymbol - mathBoldUpperAlpha; break; case greekKappaSymbol: baseChar = mathBoldKappaSymbol - mathBoldUpperAlpha; break; case greekPhiSymbol: baseChar = mathBoldPhiSymbol - mathBoldUpperAlpha; break; case greekRhoSymbol: baseChar = mathBoldRhoSymbol - mathBoldUpperAlpha; break; case greekPiSymbol: baseChar = mathBoldPiSymbol - mathBoldUpperAlpha; break; default: return codePoint; } } switch (mathvariant) { case Bold: multiplier = 0; break; case Italic: multiplier = 1; break; case BoldItalic: multiplier = 2; break; case BoldSansSerif: multiplier = 3; break; case SansSerifBoldItalic: multiplier = 4; break; default: // This mathvariant isn't defined for Greek or is otherwise normal. return codePoint; } return baseChar + mathBoldUpperAlpha + multiplier * (mathItalicUpperAlpha - mathBoldUpperAlpha); } function applyMathVariant(aToken, aMathVariant) { if (aMathVariant === "normal") return; var content = aToken["content"]; var transformedText = ""; for (var i = 0; i < content.length; i++) { var c = content["codePointAt"](i); if (c > 0xFFFF) { transformedText += content[i]; i++; transformedText += content[i]; } else { transformedText += String["fromCodePoint"]( applyMathVariantToCharacter(c, aMathVariant) ); } } aToken["content"] = transformedText; } function isToken(aTree) { return ["mi", "mn", "mo", "mtext", "ms"].indexOf(aTree["tag"]) !== -1; } function isSingleCharMi(aTree) { if (aTree["tag"] !== "mi") return false; var content = aTree["content"]; var c = content["codePointAt"](0); return content.length === 1 && c <= 0xFFFF || content.length === 2 && c > 0xFFFF; } function isTokenAttribute(aAttribute) { return ["mathcolor", "mathbackground", "mathvariant"].indexOf(aAttribute) !== -1; } function applyTokenAttributes(aChildren, aAttributes) { var allAttributesAppliedToAllChildren = true; for (var name in aAttributes) { // Only consider mstyle attributes that apply to token elements. if (!isTokenAttribute(name)) { allAttributesAppliedToAllChildren = false; continue; } // In general, keep mstyle element if there are multiple children. if (name !== "mathvariant" && aChildren.length != 1) { allAttributesAppliedToAllChildren = false; continue; } aChildren.forEach(function(child) { if (!isToken(child)) { allAttributesAppliedToAllChildren = false; return; } if (!child["attributes"]) child["attributes"] = {}; if (child["attributes"][name]) return; if (name === "mathvariant") { // Transform the text instead of using a mathvariant attribute. // Explicit "normal" attribute is only needed on single-char 's. if (aAttributes[name] !== "normal" || !isSingleCharMi(child)) applyMathVariant(child, aAttributes[name]) else child["attributes"][name] = aAttributes[name]; } else { // Apply the token attribute to the child. child["attributes"][name] = aAttributes[name]; } }); } return allAttributesAppliedToAllChildren; } /* FIXME: try to restore the operator grouping when compoundTermList does not contain any fences. https://github.com/fred-wang/TeXZilla/issues/9 */ function newMrow(aChildren, aTag, aAttributes) { aTag = aTag || "mrow"; if (aTag === "mstyle") { // Mstyle with one mrow child that does not have any attribute. if (aChildren.length == 1 && aChildren[0]["tag"] === "mrow" && !aChildren[0]["attributes"]) return newMrow(aChildren[0]["content"], aTag, aAttributes); // Try an apply all the attributes to chidren and replace mstyle with mrow. if (applyTokenAttributes(aChildren, aAttributes)) return newMrow(aChildren); } // Mrow with one child and no attributes: return the child. if (aChildren.length == 1 && aTag === "mrow" && !aAttributes) return aChildren[0]; return newTag(aTag, aChildren, aAttributes); } function newMath(aChildren, aDisplay, aRTL, aTeX) { return newTag("math", [ newTag("semantics", [ newMrow(aChildren), newTag("annotation", escapeText(aTeX), {"encoding": "TeX"}) ]) ], { "xmlns": MathMLNameSpace, "display": aDisplay ? "block" : undefined, "dir": aRTL ? "rtl" : undefined }); } function getTeXSourceInternal(aMathMLElement) { var child; if (!aMathMLElement || aMathMLElement.namespaceURI !== MathMLNameSpace) { return null; } if (aMathMLElement.tagName === "semantics") { // Note: we can't use aMathMLElement.children on WebKit/Blink because of // https://bugs.webkit.org/show_bug.cgi?id=109556. for (child = aMathMLElement.firstElementChild; child; child = child.nextElementSibling) { if (child.namespaceURI === MathMLNameSpace && child.localName === "annotation" && TeXMimeTypes.indexOf(child.getAttribute("encoding")) !== -1) { return child.textContent; } } } else if (aMathMLElement.childElementCount === 1) { return getTeXSourceInternal(aMathMLElement.firstElementChild); } return null; } try { // Try to create a DOM Parser object if it exists (e.g. in a Web page, // in a chrome script running in a window etc) parser.mDOMParser = new DOMParser(); } catch (e) { // Make the DOMParser throw an exception if used. parser.mDOMParser = { parseFromString: function() { throw "DOMParser undefined. Did you call TeXZilla.setDOMParser?"; } }; } parser.setDOMParser = function(aDOMParser) { this.mDOMParser = aDOMParser; } try { // Try to create a XMLSerializer object if it exists (e.g. in a Web page, // in a chrome script running in a window etc) parser.mXMLSerializer = new XMLSerializer(); } catch (e) { // Make the XMLSerializer throw an exception if used. parser.mXMLSerializer = { serializeToString: function() { throw "XMLSerializer undefined. Did you call TeXZilla.setXMLSerializer?"; } }; } parser.setXMLSerializer = function(aXMLSerializer) { this.mXMLSerializer = aXMLSerializer; } parser.parseMathMLDocument = function (aString) { // Parse the string into a MathML document and return the root. return this.mDOMParser. parseFromString(aString, "application/xml").documentElement; } parser.setSafeMode = function(aEnable) { this.yy.mSafeMode = aEnable; } parser.setItexIdentifierMode = function(aEnable) { this.yy.mItexIdentifierMode = aEnable; } parser.getTeXSource = function(aMathMLElement) { if (typeof aMathMLElement === "string") { aMathMLElement = this.parseMathMLDocument(aMathMLElement); } return getTeXSourceInternal(aMathMLElement); } parser.toMathMLString = function(aTeX, aDisplay, aRTL, aThrowExceptionOnError) { var output, mathml; /* Parse the TeX source and get the main MathML node. */ try { output = this.parse("\\(" + aTeX + "\\)"); if (aRTL) { /* Set the RTL mode if specified. */ output = output.replace(/^ element. */ return this.parseMathMLDocument(this.toMathMLString(aTeX, aDisplay, aRTL, aThrowExceptionOnError)); } function escapeHTML(aString) { var rv = "", code1, code2; for (var i = 0; i < aString.length; i++) { var code1 = aString.charCodeAt(i); if (code1 < 0x80) { rv += aString.charAt(i); continue; } if (0xD800 <= code1 && code1 <= 0xDBFF) { i++; code2 = aString.charCodeAt(i); rv += "&#x" + ((code1-0xD800)*0x400 + code2-0xDC00 + 0x10000).toString(16) + ";"; continue; } rv += "&#x" + code1.toString(16) + ";"; } return rv; } parser.toImage = function(aTeX, aRTL, aRoundToPowerOfTwo, aSize, aDocument) { var math, el, box, svgWidth, svgHeight, svg, image; // Set default values. if (aSize === undefined) { aSize = 64; } if (aDocument === undefined) { aDocument = window.document; } // Create the MathML element. math = this.toMathML(aTeX, true, aRTL); math.setAttribute("mathsize", aSize + "px"); // Temporarily insert the MathML element in the document to measure it. el = document.createElement("div"); el.style.visibility = "hidden"; el.style.position = "absolute"; el.appendChild(math); aDocument.body.appendChild(el); box = math.getBoundingClientRect(); aDocument.body.removeChild(el); el.removeChild(math); // Round up the computed sizes. if (aRoundToPowerOfTwo) { // Harmony's Math.log2() is not supported by all rendering engines and is // removed by closure-compiler, so we use Math.log() / Math.LN2 instead. svgWidth = Math.pow(2, Math.ceil(Math.log(box.width) / Math.LN2)); svgHeight = Math.pow(2, Math.ceil(Math.log(box.height) / Math.LN2)); } else { svgWidth = Math.ceil(box.width); svgHeight = Math.ceil(box.height); } // Embed the MathML in an SVG element. svg = document.createElementNS(SVGNameSpace, "svg"); svg.setAttribute("width", svgWidth + "px"); svg.setAttribute("height", svgHeight + "px"); el = document.createElementNS(SVGNameSpace, "g"); el.setAttribute("transform", "translate(" + (svgWidth - box.width) / 2.0 + "," + (svgHeight - box.height) / 2.0 + ")"); svg.appendChild(el); el = document.createElementNS(SVGNameSpace, "foreignObject"); el.setAttribute("width", box.width); el.setAttribute("height", box.height); el.appendChild(math); svg.firstChild.appendChild(el); // Create the image element. image = new Image(); image.src = "data:image/svg+xml;base64," + window.btoa(escapeHTML(this.mXMLSerializer.serializeToString(svg))); image.width = svgWidth; image.height = svgHeight; image.alt = escapeText(aTeX); return image; } parser.filterString = function(aString, aThrowExceptionOnError) { try { return this.parse(aString); } catch (e) { if (aThrowExceptionOnError) { throw e; } return aString; } } parser.filterElement = function(aElement, aThrowExceptionOnError) { var root, child, node; for (var node = aElement.firstChild; node; node = node.nextSibling) { switch(node.nodeType) { case 1: // Node.ELEMENT_NODE this.filterElement(node, aThrowExceptionOnError); break; case 3: // Node.TEXT_NODE this.yy.escapeXML = true; root = this.mDOMParser.parseFromString("" + TeXZilla.filterString(node.data, aThrowExceptionOnError) + "", "application/xml").documentElement; this.yy.escapeXML = false; while (child = root.firstChild) { aElement.insertBefore(root.removeChild(child), node); } child = node.previousSibling; aElement.removeChild(node); node = child; break; default: } } } function parseError(aString, aHash) { // We delete the last line, which contains token names that are obscure // to the users. See issue #16 throw new Error(aString.replace(/\nExpecting [^\n]*$/, "\n")); } %} /* Operator associations and precedence. */ %left TEXOVER TEXATOP TEXCHOOSE %right "^" "_" "OPP" %start document %% /* text option argument */ textOptArg : "[" TEXTOPTARG "]" { /* Unescape \] and \\. */ $$ = $2.replace(/\\[\\\]]/g, function(match) { return match.slice(1); }); /* Escape some XML characters. */ $$ = escapeText($$); } ; /* text argument */ textArg : "{" TEXTARG "}" { /* Unescape \} and \\. */ $$ = $2.replace(/\\[\\\}]/g, function(match) { return match.slice(1); }); /* Escape some XML characters. */ $$ = escapeText($$); } ; /* length optional argument */ lengthOptArg : "[" TEXTOPTARG "]" { $$ = parseLength($2); } ; /* length argument */ lengthArg : "{" TEXTARG "}" { $$ = parseLength($2); } ; /* attribute optional argument */ attrOptArg : textOptArg { $$ = escapeQuote($1); } ; /* attribute argument */ attrArg : textArg { $$ = escapeQuote($1); } ; /* MathML token content */ tokenContent : textArg { /* The MathML specification indicates that trailing/leading whitespaces should be removed and that inner whitespace should be collapsed. Let's replace trailing/leading whitespace by no-break space so that people can write e.g. \text{ if }. We also collapse internal whitespace here. See https://github.com/fred-wang/TeXZilla/issues/25. */ $$ = $1.replace(/\s+/g, " ").replace(/^ | $/g, "\u00A0"); } ; /* array alignment */ arrayAlign : textOptArg { $1 = $1.trim(); if ($1 === "t") { $$ = "axis 1"; } else if ($1 === "c") { $$ = "center"; } else if ($1 === "b") { $$ = "axis -1"; } else { throw "Unknown array alignment"; } } ; /* array column alignment */ columnAlign : textArg { $$ = ""; $1 = $1.replace(/\s+/g, "");; for (var i = 0; i < $1.length; i++) { if ($1[i] === "c") { $$ += " center"; } else if ($1[i] === "l") { $$ += " left"; } else if ($1[i] === "r") { $$ += " right"; } } if ($$.length) { $$ = $$.slice(1); } else { throw "Invalid column alignments"; } } ; /* table attributes */ collayout: COLLAYOUT attrArg { $$ = {"columnalign": $2}; }; colalign: COLALIGN attrArg { $$ = {"columnalign": $2}; }; rowalign: ROWALIGN attrArg { $$ = {"rowalign": $2}; }; rowspan: ROWSPAN attrArg { $$ = {"rowspan": $2}; }; colspan: COLSPAN attrArg { $$ = {"colspan": $2}; }; align: ALIGN attrArg { $$ = {"align": $2}; }; eqrows: EQROWS attrArg { $$ = {"equalrows": $2}; }; eqcols: EQCOLS attrArg { $$ = {"equalcolumns": $2}; }; rowlines: ROWLINES attrArg { $$ = {"rowlines": $2}; }; collines: COLLINES attrArg { $$ = {"columnlines": $2}; }; frame: FRAME attrArg { $$ = {"frame": $2}; }; padding: PADDING attrArg { $$ = {"rowspacing": $2, "columnspacing": $2}; }; /* cell option */ cellopt : colalign { $$ = $1; } | rowalign { $$ = $1; } | rowspan { $$ = $1; } | colspan { $$ = $1; } ; /* list of cell options */ celloptList : cellopt { $$ = $1; } | celloptList cellopt { $$ = Object.assign($1, $2); } ; /* row option */ rowopt : colalign { $$ = $1; } | rowalign { $$ = $1; } ; /* array option */ arrayopt : collayout { $$ = $1; } | colalign { $$ = $1; } | rowalign { $$ = $1; } | align { $$ = $1; } | eqrows { $$ = $1; } | eqcols { $$ = $1; } | rowlines { $$ = $1; } | collines { $$ = $1; } | frame { $$ = $1; } | padding { $$ = $1; } ; /* list of array options */ arrayoptList : arrayopt { $$ = $1; } | arrayoptList arrayopt { $$ = Object.assign($1, $2); } ; /* list of row options */ rowoptList : rowopt { $$ = $1; } | rowoptList rowopt { $$ = Object.assign($1, $2); } ; /* left fence */ left : LEFT OPFS { $$ = newMo($2); } | LEFT "." { $$ = ""; } ; /* right fence */ right : RIGHT OPFS { $$ = newMo($2); } | RIGHT "." { $$ = ""; } ; /* closed terms */ closedTerm : "{" "}" { $$ = newTag("mrow"); } | "{" styledExpression "}" { $$ = newMrow($2); } | BIG OPFS { $$ = newTag("mo", $2, {"maxsize": "1.2em", "minsize": "1.2em"}); } | BBIG OPFS { $$ = newTag("mo", $2, {"maxsize": "1.8em", "minsize": "1.8em"}); } | BIGG OPFS { $$ = newTag("mo", $2, {"maxsize": "2.4em", "minsize": "2.4em"}); } | BBIGG OPFS { $$ = newTag("mo", $2, {"maxsize": "3em", "minsize": "3em"}); } | BIGL OPFS { $$ = newTag("mo", $2, {"maxsize": "1.2em", "minsize": "1.2em"}); } | BBIGL OPFS { $$ = newTag("mo", $2, {"maxsize": "1.8em", "minsize": "1.8em"}); } | BIGGL OPFS { $$ = newTag("mo", $2, {"maxsize": "2.4em", "minsize": "2.4em"}); } | BBIGGL OPFS { $$ = newTag("mo", $2, {"maxsize": "3em", "minsize": "3em"}); } | left styledExpression right { $$ = newTag("mrow", [$1, newMrow($2), $3]); } | "{" styledExpression TEXATOP styledExpression "}" { $$ = newTag("mfrac", [newMrow($2), newMrow($4)], {"linethickness": "0px"}); } | left styledExpression TEXATOP styledExpression right { $$ = newTag("mfrac", [newMrow($2), newMrow($4)], {"linethickness": "0px"}); $$ = newTag("mrow", [$1, $$, $5]); } | "{" styledExpression TEXOVER styledExpression "}" { $$ = newTag("mfrac", [newMrow($2), newMrow($4)]); } | left styledExpression TEXOVER styledExpression right { $$ = newTag("mfrac", [newMrow($2), newMrow($4)]); $$ = newTag("mrow", [$1, $$, $5]); } | "{" styledExpression TEXCHOOSE styledExpression "}" { $$ = newTag("mfrac", [newMrow($2), newMrow($4)], {"linethickness": "0px"}); $$ = newTag("mrow", [newMo("("), $$, newMo(")")]); } | left styledExpression TEXCHOOSE styledExpression right { $$ = newTag("mfrac", [newMrow($2), newMrow($4)], {"linethickness": "0px"}); $$ = newTag("mrow", [$1, $$, $5]); $$ = newTag("mrow", [newMo("("), $$, newMo(")")]); } | NUM { $$ = newTag("mn", $1); } | TEXT { $$ = newTag("mtext", $1); } /* FIXME: It should be possible to decide when these are italic or normal. See https://github.com/fred-wang/TeXZilla/issues/72*/ | A { $$ = newMi($1); } | AILL { $$ = newMi($1); } | AIUL { $$ = newMi($1); } | AILG { $$ = newMi($1); } | AIUG { $$ = newMi($1, true); } | F { $$ = newMo($1, 0, 0); } | MI tokenContent { $$ = newTag("mi", $2); } | MN tokenContent { $$ = newTag("mn", $2); } | MO tokenContent { $$ = newMo($2); } | "." { $$ = newMo($1); } | OP { $$ = newMo($1); } | OPS { $$ = newTag("mo", $1, {"stretchy": "false"}); } | OPAS { $$ = newTag("mo", $1, {"stretchy": "false"}); } | OPFS { $$ = newTag("mo", $1, {"stretchy": "false"}); } | MS tokenContent { $$ = newTag("ms", $2); } | MS attrOptArg attrOptArg tokenContent { $$ = newTag("ms", $4, {"lquote": $2, "rquote": $3}); } | MTEXT tokenContent { $$ = newTag("mtext", $2); } | HIGH_SURROGATE LOW_SURROGATE { $$ = newTag("mtext", $1 + $2); } | BMP_CHARACTER { $$ = newTag("mtext", $1); } | OPERATORNAME textArg { $$ = newMo($2, 0, namedSpaceToEm("thinmathspace")); } | MATHOP textArg { $$ = newMo($2, namedSpaceToEm("thinmathspace"), namedSpaceToEm("thinmathspace")); } | MATHBIN textArg { $$ = newMo($2, namedSpaceToEm("mediummathspace"), namedSpaceToEm("mediummathspace")); } | MATHREL textArg { $$ = newMo($2, namedSpaceToEm("thickmathspace"), namedSpaceToEm("thickmathspace")); } | FRAC closedTerm closedTerm { $$ = newTag("mfrac", [$2, $3]); } | ROOT closedTerm closedTerm { $$ = newTag("mroot", [$3, $2]); } | SQRT closedTerm { $$ = newTag("msqrt", [$2]); } | SQRT "[" styledExpression "]" closedTerm { $$ = newTag("mroot", [$5, newMrow($3)]); } | UNDERSET closedTerm closedTerm { $$ = newTag("munder", [$3, $2]); } | OVERSET closedTerm closedTerm { $$ = newTag("mover", [$3, $2]); } | UNDEROVERSET closedTerm closedTerm closedTerm { $$ = newTag("munderover", [$4, $2, $3]); } } | XARROW "[" styledExpression "]" closedTerm { $$ = (isEmptyMrow($5) ? newTag("munder", [newMo($1), newMrow($3)]) : newTag("munderover", [newMo($1), newMrow($3), $5])); } | XARROW closedTerm { $$ = newTag("mover", [newMo($1), $2]); } | MATHRLAP closedTerm { $$ = newTag("mpadded", [$2], {"width": "0em"}); } | MATHLLAP closedTerm { $$ = newTag("mpadded", [$2], {"width": "0em", "lspace": "-100%width"}); } | MATHCLAP closedTerm { $$ = newTag("mpadded", [$2], {"width": "0em", "lspace": "-50%width"}); } | PHANTOM closedTerm { $$ = newTag("mphantom", [$2]); } | TFRAC closedTerm closedTerm { $$ = newTag("mfrac", [$2, $3]); $$ = newMrow([$$], "mstyle", {"displaystyle": "false"}); } | BINOM closedTerm closedTerm { $$ = newTag("mfrac", [$2, $3], {"linethickness": "0px"}); $$ = newTag("mrow", [newMo("("), $$, newMo(")")]); } | TBINOM closedTerm closedTerm { $$ = newTag("mfrac", [$2, $3], {"linethickness": "0px"}); $$ = newMrow([$$], "mstyle", {"displaystyle": "false"}); $$ = newTag("mrow", [newMo("("), $$, newMo(")")]); } | PMOD closedTerm { $$ = newTag("mrow", [newMo("(", namedSpaceToEm("mediummathspace")), newMo("mod", undefined, namedSpaceToEm("thinmathspace")), $2, newMo(")", undefined, namedSpaceToEm("mediummathspace"))]); } | UNDERBRACE closedTerm { $$ = newTag("munder", [$2, newMo("\u23DF")]); } | UNDERLINE closedTerm { $$ = newTag("munder", [$2, newMo("_")]); } | OVERBRACE closedTerm { $$ = newTag("mover", [$2, newMo("\u23DE")]); } | ACCENT closedTerm { $$ = newTag("mover", [$2, newMo($1)]); } | ACCENTNS closedTerm { $$ = newTag("mover", [$2, newTag("mo", $1, {"stretchy": "false"})]); } | BOXED closedTerm { $$ = newTag("menclose", [$2], {"notation": "box"}); } | SLASH closedTerm { $$ = newTag("menclose", [$2], {"notation": "updiagonalstrike"}); } | QUAD { $$ = newSpace(1); } | QQUAD { $$ = newSpace(2); } | NEGSPACE { $$ = newSpace(namedSpaceToEm("negativethinmathspace")); } | NEGMEDSPACE { $$ = newSpace(namedSpaceToEm("negativemediummathspace")); } | NEGTHICKSPACE { $$ = newSpace(namedSpaceToEm("negativethickmathspace")); } | THINSPACE { $$ = newSpace(namedSpaceToEm("thinmathspace")); } | MEDSPACE { $$ = newSpace(namedSpaceToEm("mediummathspace")); } | THICKSPACE { $$ = newSpace(namedSpaceToEm("thickmathspace")); } | SPACE textArg textArg textArg { $$ = newTag("mspace", null, {"height": "." + $2 + "ex", "depth": "." + $3 + "ex", "width": "." + $4 + "em"}); } | MATHRAISEBOX lengthArg lengthOptArg lengthOptArg closedTerm { $$ = newTag("mpadded", [$5], {"voffset": $2.l + $2.u, "height": $3.l + $3.u, "depth": $4.l + $4.u}); } | MATHRAISEBOX lengthArg lengthOptArg closedTerm { $$ = newTag("mpadded", [$4], {"voffset": $2.l + $2.u, "height": $3.l + $3.u, "depth": ($2.l < 0 ? "+" + (-$2.l) + $2.u : "depth")}); } | MATHRAISEBOX lengthArg closedTerm { var attributes = {"voffset": $2.l + $2.u}; if ($2.l >= 0) attributes.height = "+" + $2.l + $2.u; else { attributes.height = "0pt"; attributes.depth = "+" + (-$2.l) + $2.u; } $$ = newTag("mpadded", [$3], attributes); } | MATHBB closedTerm { $$ = newMrow([$2], "mstyle", {"mathvariant": "double-struck"}); } | MATHBF closedTerm { $$ = newMrow([$2], "mstyle", {"mathvariant": "bold"}); } | MATHBIT closedTerm { $$ = newMrow([$2], "mstyle", {"mathvariant": "bold-italic"}); } | MATHSCR closedTerm { $$ = newMrow([$2], "mstyle", {"mathvariant": "script"}); } | MATHBSCR closedTerm { $$ = newMrow([$2], "mstyle", {"mathvariant": "bold-script"}); } | MATHSF closedTerm { $$ = newMrow([$2], "mstyle", {"mathvariant": "sans-serif"}); } | MATHFRAK closedTerm { $$ = newMrow([$2], "mstyle", {"mathvariant": "fraktur"}); } | MATHIT closedTerm { $$ = newMrow([$2], "mstyle", {"mathvariant": "italic"}); } | MATHTT closedTerm { $$ = newMrow([$2], "mstyle", {"mathvariant": "monospace"}); } | MATHRM closedTerm { $$ = newMrow([$2], "mstyle", {"mathvariant": "normal"}); } | HREF attrArg closedTerm { $$ = newTag("mrow", [$3], yy.mSafeMode ? null : {"href": $2}); } | STATUSLINE textArg closedTerm { $$ = yy.mSafeMode ? $3 : newTag("maction", [$3, newTag("mtext", $2)], {"actiontype": "statusline"}); } | TOOLTIP textArg closedTerm { $$ = yy.mSafeMode ? $3 : newTag("maction", [$3, newTag("mtext", $2)], {"actiontype": "tooltip"}); } | TOGGLE closedTerm closedTerm { /* Backward compatibility with itex2MML */ $$ = yy.mSafeMode ? $3 : newTag("maction", [$2, $3], {"actiontype": "toggle", selection: "2"}); } | BTOGGLE closedTermList ETOGGLE { $$ = yy.mSafeMode ? newTag("mrow", $2) : newTag("maction", $2, {"actiontype": "toggle"}); } | TENSOR closedTerm "{" subsupList "}" { $$ = newTag("mmultiscripts", [$2].concat($4)); } | MULTI "{" subsupList "}" closedTerm "{" subsupList "}" { $$ = newTag("mmultiscripts", [$5].concat($7).concat(newTag("mprescripts")).concat($3)); } | MULTI "{" subsupList "}" closedTerm "{" "}" { $$ = newTag("mmultiscripts", [$5, newTag("mprescripts")].concat($3)); } | MULTI "{" "}" closedTerm "{" subsupList "}" { $$ = newTag("mmultiscripts", [$4].concat($6)); } | BMATRIX tableRowList EMATRIX { $$ = newTag("mtable", $2, {"displaystyle": "false", "rowspacing": "0.5ex"}); } | BGATHERED tableRowList EGATHERED { $$ = newTag("mtable", $2, {"displaystyle": "true", "rowspacing": "1.0ex"}); } | BPMATRIX tableRowList EPMATRIX { $$ = newTag("mtable", $2, {"displaystyle": "false", "rowspacing": "0.5ex"}); $$ = newTag("mrow", [newMo("("), $$, newMo(")")]); } | BBMATRIX tableRowList EBMATRIX { $$ = newTag("mtable", $2, {"displaystyle": "false", "rowspacing": "0.5ex"}); $$ = newTag("mrow", [newMo("["), $$, newMo("]")]); } | BVMATRIX tableRowList EVMATRIX { $$ = newTag("mtable", $2, {"displaystyle": "false", "rowspacing": "0.5ex"}); $$ = newTag("mrow", [newMo("|"), $$, newMo("|")]); } | BBBMATRIX tableRowList EBBMATRIX { $$ = newTag("mtable", $2, {"displaystyle": "false", "rowspacing": "0.5ex"}); $$ = newTag("mrow", [newMo("{"), $$, newMo("}")]); } | BVVMATRIX tableRowList EVVMATRIX { $$ = newTag("mtable", $2, {"displaystyle": "false", "rowspacing": "0.5ex"}); $$ = newTag("mrow", [newMo("\u2016"), $$, newMo("\u2016")]); } | BSMALLMATRIX tableRowList ESMALLMATRIX { $$ = newTag("mtable", $2, {"displaystyle": "false", "rowspacing": "0.5ex"}); $$ = newMrow([$$], "mstyle", {"scriptlevel": "2"}); } | BCASES tableRowList ECASES { $$ = newTag("mtable", $2, {"displaystyle": "false", "columnalign": "left left"}); $$ = newTag("mrow", [newMo("{"), $$]); } | BALIGNED tableRowList EALIGNED { $$ = newTag("mtable", $2, {"displaystyle": "true", "columnalign": "right left right left right left right left right left", "columnspacing": "0em"}); } | BARRAY arrayAlign columnAlign tableRowList EARRAY { $$ = newTag("mtable", $4, {"displaystyle": "false", "rowspacing": "0.5ex", "align": $2, "columnalign": $3}); } | BARRAY columnAlign tableRowList EARRAY { $$ = newTag("mtable", $3, {"displaystyle": "false", "rowspacing": "0.5ex", "columnalign": $2}); } | SUBSTACK "{" tableRowList "}" { $$ = newTag("mtable", $3, {"displaystyle": "false", "columnalign": "center", "rowspacing": "0.5ex"}); } | ARRAY "{" tableRowList "}" { $$ = newTag("mtable", $3, {"displaystyle": "false"}); } | ARRAY "{" ARRAYOPTS "{" arrayoptList "}" tableRowList "}" { $$ = newTag("mtable", $7, Object.assign($5, {"displaystyle": "false"})); } ; /* list of closed terms */ closedTermList : closedTerm { $$ = [$1]; } | closedTermList closedTerm { $$ = $1.concat([$2]); } ; /* compound terms (closed terms with scripts) */ compoundTerm : TENSOR closedTerm subsupList { $$ = newTag("mmultiscripts", [$2].concat($3)); } | closedTerm "_" closedTerm "^" closedTerm { $$ = newTag("msubsup", [$1, $3, $5]); } | closedTerm "_" closedTerm OPP { $$ = newTag("msubsup", [$1, $3, newMo($4)]); } | closedTerm "^" closedTerm "_" closedTerm { $$ = newTag("msubsup", [$1, $5, $3]); } | closedTerm OPP "_" closedTerm { $$ = newTag("msubsup", [$1, $4, newMo($2)]); } | closedTerm "_" closedTerm { $$ = newTag("msub", [$1, $3]); } | closedTerm "^" closedTerm { $$ = newTag("msup", [$1, $3]); } | closedTerm OPP { $$ = newTag("msup", [$1, newMo($2)]); } | closedTerm { $$ = $1; } | opm "_" closedTerm "^" closedTerm { $$ = newTag("munderover", [$1, $3, $5]); } | opm "^" closedTerm "_" closedTerm { $$ = newTag("munderover", [$1, $5, $3]); } | opm "_" closedTerm { $$ = newTag("munder", [$1, $3]); } | opm "^" closedTerm { $$ = newTag("mover", [$1, $3]); } | opm { $$ = $1; } ; opm : OPM { $$ = newMo($1); } | FM { $$ = newMo($1, 0, 0); } ; /* list of compound terms */ compoundTermList : compoundTerm { $$ = [$1]; } | compoundTermList compoundTerm { $$ = $1.concat([$2]); } ; /* subsup term */ subsupTermScript : closedTerm { $$ = $1; } | opm { $$ = $1; } ; /* subsup term as scripts */ subsupTerm : "_" subsupTermScript "^" subsupTermScript { $$ = [$2, $4]; } | "_" subsupTermScript { $$ = [$2, newTag("none")]; } | "^" subsupTermScript { $$ = [newTag("none"), $2]; } | "_" "^" subsupTermScript { $$ = [newTag("none"), $3]; } ; /* list of subsup terms */ subsupList : subsupTerm { $$ = $1; } | subsupList subsupTerm { $$ = $1.concat($2); } ; /* text style */ textstyle : DISPLAYSTYLE { $$ = {"displaystyle": "true"}; } | TEXTSTYLE { $$ = {"displaystyle": "false"}; } | TEXTSIZE { $$ = {"scriptlevel": "0"}; } | SCRIPTSIZE { $$ = {"scriptlevel": "1"}; } | SCRIPTSCRIPTSIZE { $$ = {"scriptlevel": "2"}; } | COLOR attrArg { $$ = {"mathcolor": $2}; } | BGCOLOR attrArg { $$ = {"mathbackground": $2}; } ; /* styled expression (compoundTermList with additional style) */ styledExpression : textstyle styledExpression { $$ = [newMrow($2, "mstyle", $1)]; } | compoundTermList { $$ = $1; } ; /* table cell */ tableCell : { $$ = newTag("mtd", []); } | CELLOPTS "{" celloptList "}" styledExpression { $$ = newMrow($5, "mtd", $3); } | styledExpression { $$ = newMrow($1, "mtd"); } ; /* list of table cells */ tableCellList : tableCell { $$ = [$1]; } | tableCellList COLSEP tableCell { $$ = $1.concat([$3]); } ; /* table row */ tableRow : ROWOPTS "{" rowoptList "}" tableCellList { $$ = $$ = newTag("mtr", $5, $3); } | tableCellList { $$ = newTag("mtr", $1); } ; /* list of table rows */ tableRowList : tableRow { $$ = [$1]; } | tableRowList ROWSEP tableRow { $$ = $1.concat([$3]); } ; /* a document with embedded math */ document : documentItemList EOF { $$ = $1 return $$; } ; documentItemList : documentItem { $$ = $1; } | documentItemList documentItem { $$ = $1 + $2 } ; documentItem : TEXT { $$ = $1; } | mathItem { $$ = serializeTree($1); } ; mathItem : STARTMATH0 ENDMATH0 { // \( \) $$ = newMath([newTag("mrow")], false, false, yy.tex); } | STARTMATH0 styledExpression ENDMATH0 { // \( ... \) $$ = newMath($2, false, false, yy.tex); } | STARTMATH1 ENDMATH1 { // \[ \] $$ = newMath([newTag("mrow")], true, false, yy.tex); } | STARTMATH1 styledExpression ENDMATH1 { // \[ ... \] $$ = newMath($2, true, false, yy.tex); } | STARTMATH2 styledExpression ENDMATH2 { // $ ... $ $$ = newMath($2, false, false, yy.tex); } | STARTMATH3 styledExpression ENDMATH3 { // $$ ... $$ $$ = newMath($2, true, false, yy.tex); } ;