diff --git a/src/Options.js b/src/Options.js index e6e0996..e5de594 100644 --- a/src/Options.js +++ b/src/Options.js @@ -99,7 +99,7 @@ Options.prototype.withPhantom = function() { */ Options.prototype.withFont = function(font) { return this.extend({ - font: font, + font: font || this.font, }); }; diff --git a/src/buildCommon.js b/src/buildCommon.js index c4ab8ba..bbd174d 100644 --- a/src/buildCommon.js +++ b/src/buildCommon.js @@ -36,6 +36,8 @@ var mainitLetters = [ * classes to be attached to the node. * * TODO: make argument order closer to makeSpan + * TODO: add a separate argument for math class (e.g. `mop`, `mbin`), which + * should if present come first in `classes`. */ var makeSymbol = function(value, fontFamily, mode, options, classes) { // Replace the value with its replaced value from symbol.js @@ -47,8 +49,12 @@ var makeSymbol = function(value, fontFamily, mode, options, classes) { var symbolNode; if (metrics) { + var italic = metrics.italic; + if (mode === "text") { + italic = 0; + } symbolNode = new domTree.symbolNode( - value, metrics.height, metrics.depth, metrics.italic, metrics.skew, + value, metrics.height, metrics.depth, italic, metrics.skew, classes); } else { // TODO(emily): Figure out a good way to only print this in development @@ -183,6 +189,8 @@ var sizeElementFromChildren = function(elem) { * * TODO: Ensure that `options` is always provided (currently some call sites * don't pass it). + * TODO: add a separate argument for math class (e.g. `mop`, `mbin`), which + * should if present come first in `classes`. */ var makeSpan = function(classes, children, options) { var span = new domTree.span(classes, children, options); @@ -422,6 +430,10 @@ var fontMap = { variant: "normal", fontName: "Main-Regular", }, + "textit": { + variant: "italic", + fontName: "Main-Italic", + }, // "mathit" is missing because it requires the use of two fonts: Main-Italic // and Math-Italic. This is handled by a special case in makeOrd which ends diff --git a/src/buildHTML.js b/src/buildHTML.js index 61ec1b7..dccf161 100644 --- a/src/buildHTML.js +++ b/src/buildHTML.js @@ -21,27 +21,52 @@ var isSpace = function(node) { return node instanceof domTree.span && node.classes[0] === "mspace"; }; +// Binary atoms (first class `mbin`) change into ordinary atoms (`mord`) +// depending on their surroundings. See TeXbook pg. 442-446, Rules 5 and 6, +// and the text before Rule 19. + +var isBin = function(node) { + return node && node.classes[0] === "mbin"; +}; + +var isBinLeftCanceller = function(node, isRealGroup) { + // TODO: This code assumes that a node's math class is the first element + // of its `classes` array. A later cleanup should ensure this, for + // instance by changing the signature of `makeSpan`. + if (node) { + return utils.contains(["mbin", "mopen", "mrel", "mop", "mpunct"], + node.classes[0]); + } else { + return isRealGroup; + } +}; + +var isBinRightCanceller = function(node, isRealGroup) { + if (node) { + return utils.contains(["mrel", "mclose", "mpunct"], node.classes[0]); + } else { + return isRealGroup; + } +}; + /** * Take a list of nodes, build them in order, and return a list of the built - * nodes. This function handles the `prev` node correctly, and passes the - * previous element from the list as the prev of the next element, ignoring - * spaces. documentFragments are flattened into their contents, so the - * returned list contains no fragments. + * nodes. documentFragments are flattened into their contents, so the + * returned list contains no fragments. `isRealGroup` is true if `expression` + * is a real group (no atoms will be added on either side), as opposed to + * a partial group (e.g. one created by \color). */ -var buildExpression = function(expression, options, prev) { +var buildExpression = function(expression, options, isRealGroup) { // Parse expressions into `groups`. var groups = []; for (var i = 0; i < expression.length; i++) { var group = expression[i]; - var output = buildGroup(group, options, prev); + var output = buildGroup(group, options); if (output instanceof domTree.documentFragment) { Array.prototype.push.apply(groups, output.children); } else { groups.push(output); } - if (!isSpace(output)) { - prev = group; - } } // At this point `groups` consists entirely of `symbolNode`s and `span`s. @@ -68,69 +93,32 @@ var buildExpression = function(expression, options, prev) { Array.prototype.push.apply(groups, spaces); } + // Binary operators change to ordinary symbols in some contexts. + for (i = 0; i < groups.length; i++) { + if (isBin(groups[i]) + && (isBinLeftCanceller(groups[i - 1], isRealGroup) + || isBinRightCanceller(groups[i + 1], isRealGroup))) { + groups[i].classes[0] = "mord"; + } + } + return groups; }; -// List of types used by getTypeOfGroup, -// see https://github.com/Khan/KaTeX/wiki/Examining-TeX#group-types -var groupToType = { - mathord: "mord", - textord: "mord", - bin: "mbin", - rel: "mrel", - text: "mord", - open: "mopen", - close: "mclose", - inner: "minner", - genfrac: "mord", - array: "mord", - spacing: "mord", - punct: "mpunct", - ordgroup: "mord", - op: "mop", - katex: "mord", - overline: "mord", - underline: "mord", - rule: "mord", - leftright: "minner", - sqrt: "mord", - accent: "mord", -}; - -/** - * Gets the final math type of an expression, given its group type. This type is - * used to determine spacing between elements, and affects bin elements by - * causing them to change depending on what types are around them. This type - * must be attached to the outermost node of an element as a CSS class so that - * spacing with its surrounding elements works correctly. - * - * Some elements can be mapped one-to-one from group type to math type, and - * those are listed in the `groupToType` table. - * - * Others (usually elements that wrap around other elements) often have - * recursive definitions, and thus call `getTypeOfGroup` on their inner - * elements. - */ -var getTypeOfGroup = function(group) { - if (group == null) { - // Like when typesetting $^3$ - return groupToType.mathord; - } else if (group.type === "supsub") { - return getTypeOfGroup(group.value.base); - } else if (group.type === "llap" || group.type === "rlap") { - return getTypeOfGroup(group.value); - } else if (group.type === "color" || group.type === "sizing" - || group.type === "styling") { - // Return type of rightmost element of group. - var atoms = group.value.value; - return getTypeOfGroup(atoms[atoms.length - 1]); - } else if (group.type === "font") { - return getTypeOfGroup(group.value.body); - } else if (group.type === "delimsizing") { - return groupToType[group.value.delimType]; +// Return math atom class (mclass) of a domTree. +var getTypeOfDomTree = function(node) { + if (node instanceof domTree.documentFragment) { + if (node.children.length) { + return getTypeOfDomTree( + node.children[node.children.length - 1]); + } } else { - return groupToType[group.type]; + if (utils.contains(["mord", "mop", "mbin", "mrel", "mopen", "mclose", + "mpunct", "minner"], node.classes[0])) { + return node.classes[0]; + } } + return null; }; /** @@ -201,12 +189,11 @@ var isCharacterBox = function(group) { baseElem.type === "punct"; }; -var makeNullDelimiter = function(options) { - return makeSpan([ +var makeNullDelimiter = function(options, classes) { + return makeSpan(classes.concat([ "sizing", "reset-" + options.size, "size5", options.style.reset(), Style.TEXT.cls(), - "nulldelimiter", - ]); + "nulldelimiter"])); }; /** @@ -215,81 +202,70 @@ var makeNullDelimiter = function(options) { */ var groupTypes = {}; -groupTypes.mathord = function(group, options, prev) { +groupTypes.mathord = function(group, options) { return buildCommon.makeOrd(group, options, "mathord"); }; -groupTypes.textord = function(group, options, prev) { +groupTypes.textord = function(group, options) { return buildCommon.makeOrd(group, options, "textord"); }; -groupTypes.bin = function(group, options, prev) { - var className = "mbin"; - // Pull out the most recent element. Do some special handling to find - // things at the end of a \color group. Note that we don't use the same - // logic for ordgroups (which count as ords). - var prevAtom = prev; - while (prevAtom && prevAtom.type === "color") { - var atoms = prevAtom.value.value; - prevAtom = atoms[atoms.length - 1]; - } - // See TeXbook pg. 442-446, Rules 5 and 6, and the text before Rule 19. - // Here, we determine whether the bin should turn into an ord. We - // currently only apply Rule 5. - if (!prev || utils.contains(["mbin", "mopen", "mrel", "mop", "mpunct"], - getTypeOfGroup(prevAtom))) { - group.type = "textord"; - className = "mord"; - } - +groupTypes.bin = function(group, options) { return buildCommon.mathsym( - group.value, group.mode, options, [className]); + group.value, group.mode, options, ["mbin"]); }; -groupTypes.rel = function(group, options, prev) { +groupTypes.rel = function(group, options) { return buildCommon.mathsym( group.value, group.mode, options, ["mrel"]); }; -groupTypes.open = function(group, options, prev) { +groupTypes.open = function(group, options) { return buildCommon.mathsym( group.value, group.mode, options, ["mopen"]); }; -groupTypes.close = function(group, options, prev) { +groupTypes.close = function(group, options) { return buildCommon.mathsym( group.value, group.mode, options, ["mclose"]); }; -groupTypes.inner = function(group, options, prev) { +groupTypes.inner = function(group, options) { return buildCommon.mathsym( group.value, group.mode, options, ["minner"]); }; -groupTypes.punct = function(group, options, prev) { +groupTypes.punct = function(group, options) { return buildCommon.mathsym( group.value, group.mode, options, ["mpunct"]); }; -groupTypes.ordgroup = function(group, options, prev) { +groupTypes.ordgroup = function(group, options) { return makeSpan( ["mord", options.style.cls()], - buildExpression(group.value, options.reset()), + buildExpression(group.value, options.reset(), true), options ); }; -groupTypes.text = function(group, options, prev) { - return makeSpan(["mord", "text", options.style.cls()], - buildExpression(group.value.body, options.reset()), - options); +groupTypes.text = function(group, options) { + var newOptions = options.withFont(group.value.style); + var inner = buildExpression(group.value.body, newOptions, true); + for (var i = 0; i < inner.length - 1; i++) { + if (inner[i].tryCombine(inner[i + 1])) { + inner.splice(i + 1, 1); + i--; + } + } + return makeSpan(["mord", "text", newOptions.style.cls()], + inner, newOptions); }; -groupTypes.color = function(group, options, prev) { +groupTypes.color = function(group, options) { var elements = buildExpression( group.value.value, options.withColor(group.value.color), - prev + false ); // \color isn't supposed to affect the type of the elements it contains. @@ -299,14 +275,14 @@ groupTypes.color = function(group, options, prev) { return new buildCommon.makeFragment(elements); }; -groupTypes.supsub = function(group, options, prev) { +groupTypes.supsub = function(group, options) { // Superscript and subscripts are handled in the TeXbook on page // 445-446, rules 18(a-f). // Here is where we defer to the inner group if it should handle // superscripts and subscripts itself. if (shouldHandleSupSub(group.value.base, options)) { - return groupTypes[group.value.base.type](group, options, prev); + return groupTypes[group.value.base.type](group, options); } var base = buildGroup(group.value.base, options.reset()); @@ -422,12 +398,13 @@ groupTypes.supsub = function(group, options, prev) { } // We ensure to wrap the supsub vlist in a span.msupsub to reset text-align - return makeSpan([getTypeOfGroup(group.value.base)], + var mclass = getTypeOfDomTree(base) || "mord"; + return makeSpan([mclass], [base, makeSpan(["msupsub"], [supsub])], options); }; -groupTypes.genfrac = function(group, options, prev) { +groupTypes.genfrac = function(group, options) { // Fractions are handled in the TeXbook on pages 444-445, rules 15(a-e). // Figure out what style this fraction should be in based on the // function used @@ -546,18 +523,18 @@ groupTypes.genfrac = function(group, options, prev) { var leftDelim; var rightDelim; if (group.value.leftDelim == null) { - leftDelim = makeNullDelimiter(options); + leftDelim = makeNullDelimiter(options, ["mopen"]); } else { leftDelim = delimiter.customSizedDelim( group.value.leftDelim, delimSize, true, - options.withStyle(style), group.mode); + options.withStyle(style), group.mode, ["mopen"]); } if (group.value.rightDelim == null) { - rightDelim = makeNullDelimiter(options); + rightDelim = makeNullDelimiter(options, ["mclose"]); } else { rightDelim = delimiter.customSizedDelim( group.value.rightDelim, delimSize, true, - options.withStyle(style), group.mode); + options.withStyle(style), group.mode, ["mclose"]); } return makeSpan( @@ -566,7 +543,7 @@ groupTypes.genfrac = function(group, options, prev) { options); }; -groupTypes.array = function(group, options, prev) { +groupTypes.array = function(group, options) { var r; var c; var nr = group.value.body.length; @@ -730,47 +707,51 @@ groupTypes.array = function(group, options, prev) { return makeSpan(["mord"], [body], options); }; -groupTypes.spacing = function(group, options, prev) { +groupTypes.spacing = function(group, options) { if (group.value === "\\ " || group.value === "\\space" || group.value === " " || group.value === "~") { // Spaces are generated by adding an actual space. Each of these // things has an entry in the symbols table, so these will be turned // into appropriate outputs. - return makeSpan( - ["mspace"], - [buildCommon.mathsym(group.value, group.mode)] - ); + if (group.mode === "text") { + return buildCommon.makeOrd(group, options, "textord"); + } else { + return makeSpan(["mspace"], + [buildCommon.mathsym(group.value, group.mode, options)], + options); + } } else { // Other kinds of spaces are of arbitrary width. We use CSS to // generate these. return makeSpan( ["mspace", - buildCommon.spacingFunctions[group.value].className]); + buildCommon.spacingFunctions[group.value].className], + [], options); } }; -groupTypes.llap = function(group, options, prev) { +groupTypes.llap = function(group, options) { var inner = makeSpan( ["inner"], [buildGroup(group.value.body, options.reset())]); var fix = makeSpan(["fix"], []); return makeSpan( - ["llap", options.style.cls()], [inner, fix], options); + ["mord", "llap", options.style.cls()], [inner, fix], options); }; -groupTypes.rlap = function(group, options, prev) { +groupTypes.rlap = function(group, options) { var inner = makeSpan( ["inner"], [buildGroup(group.value.body, options.reset())]); var fix = makeSpan(["fix"], []); return makeSpan( - ["rlap", options.style.cls()], [inner, fix], options); + ["mord", "rlap", options.style.cls()], [inner, fix], options); }; -groupTypes.op = function(group, options, prev) { +groupTypes.op = function(group, options) { // Operators are handled in the TeXbook pg. 443-444, rule 13(a). var supGroup; var subGroup; var hasLimits = false; - if (group.type === "supsub" ) { + if (group.type === "supsub") { // If we have limits, supsub will pass us its group to handle. Pull // out the superscript and subscript and set the group to the op in // its base. @@ -816,6 +797,11 @@ groupTypes.op = function(group, options, prev) { // The slant of the symbol is just its italic correction. slant = base.italic; + } else if (group.value.value) { + // If this is a list, compose that list. + var inner = buildExpression(group.value.value, options, true); + + base = makeSpan(["mop"], inner, options); } else { // Otherwise, this is a text operator. Build the text from the // operator's name. @@ -930,7 +916,7 @@ groupTypes.op = function(group, options, prev) { } }; -groupTypes.katex = function(group, options, prev) { +groupTypes.katex = function(group, options) { // The KaTeX logo. The offsets for the K and a were chosen to look // good, but the offsets for the T, E, and X were taken from the // definition of \TeX in TeX (see TeXbook pg. 356) @@ -957,7 +943,7 @@ groupTypes.katex = function(group, options, prev) { ["mord", "katex-logo"], [k, a, t, e, x], options); }; -groupTypes.overline = function(group, options, prev) { +groupTypes.overline = function(group, options) { // Overlines are handled in the TeXbook pg 443, Rule 9. var style = options.style; @@ -985,7 +971,7 @@ groupTypes.overline = function(group, options, prev) { return makeSpan(["mord", "overline"], [vlist], options); }; -groupTypes.underline = function(group, options, prev) { +groupTypes.underline = function(group, options) { // Underlines are handled in the TeXbook pg 443, Rule 10. var style = options.style; @@ -1011,7 +997,7 @@ groupTypes.underline = function(group, options, prev) { return makeSpan(["mord", "underline"], [vlist], options); }; -groupTypes.sqrt = function(group, options, prev) { +groupTypes.sqrt = function(group, options) { // Square roots are handled in the TeXbook pg. 443, Rule 11. var style = options.style; @@ -1110,12 +1096,12 @@ groupTypes.sqrt = function(group, options, prev) { } }; -groupTypes.sizing = function(group, options, prev) { +groupTypes.sizing = function(group, options) { // Handle sizing operators like \Huge. Real TeX doesn't actually allow // these functions inside of math expressions, so we do some special // handling. var inner = buildExpression(group.value.value, - options.withSize(group.value.size), prev); + options.withSize(group.value.size), false); // Compute the correct maxFontSize. var style = options.style; @@ -1141,7 +1127,7 @@ groupTypes.sizing = function(group, options, prev) { return buildCommon.makeFragment(inner); }; -groupTypes.styling = function(group, options, prev) { +groupTypes.styling = function(group, options) { // Style changes are handled in the TeXbook on pg. 442, Rule 3. // Figure out what style we're changing to. @@ -1157,7 +1143,7 @@ groupTypes.styling = function(group, options, prev) { // Build the inner expression in the new style. var inner = buildExpression( - group.value.value, newOptions, prev); + group.value.value, newOptions, false); // Add style-resetting classes to the inner list. Handle nested changes. for (var i = 0; i < inner.length; i++) { @@ -1174,31 +1160,29 @@ groupTypes.styling = function(group, options, prev) { return new buildCommon.makeFragment(inner); }; -groupTypes.font = function(group, options, prev) { +groupTypes.font = function(group, options) { var font = group.value.font; - return buildGroup(group.value.body, options.withFont(font), prev); + return buildGroup(group.value.body, options.withFont(font)); }; -groupTypes.delimsizing = function(group, options, prev) { +groupTypes.delimsizing = function(group, options) { var delim = group.value.value; if (delim === ".") { // Empty delimiters still count as elements, even though they don't // show anything. - return makeSpan([groupToType[group.value.delimType]]); + return makeSpan([group.value.mclass]); } // Use delimiter.sizedDelim to generate the delimiter. - return makeSpan( - [groupToType[group.value.delimType]], - [delimiter.sizedDelim( - delim, group.value.size, options, group.mode)], - options); + return delimiter.sizedDelim( + delim, group.value.size, options, group.mode, + [group.value.mclass]); }; -groupTypes.leftright = function(group, options, prev) { +groupTypes.leftright = function(group, options) { // Build the inner expression - var inner = buildExpression(group.value.body, options.reset()); + var inner = buildExpression(group.value.body, options.reset(), true); var innerHeight = 0; var innerDepth = 0; @@ -1220,13 +1204,13 @@ groupTypes.leftright = function(group, options, prev) { var leftDelim; if (group.value.left === ".") { // Empty delimiters in \left and \right make null delimiter spaces. - leftDelim = makeNullDelimiter(options); + leftDelim = makeNullDelimiter(options, ["mopen"]); } else { // Otherwise, use leftRightDelim to generate the correct sized // delimiter. leftDelim = delimiter.leftRightDelim( group.value.left, innerHeight, innerDepth, options, - group.mode); + group.mode, ["mopen"]); } // Add it to the beginning of the expression inner.unshift(leftDelim); @@ -1234,11 +1218,11 @@ groupTypes.leftright = function(group, options, prev) { var rightDelim; // Same for the right delimiter if (group.value.right === ".") { - rightDelim = makeNullDelimiter(options); + rightDelim = makeNullDelimiter(options, ["mclose"]); } else { rightDelim = delimiter.leftRightDelim( group.value.right, innerHeight, innerDepth, options, - group.mode); + group.mode, ["mclose"]); } // Add it to the end of the expression. inner.push(rightDelim); @@ -1247,7 +1231,7 @@ groupTypes.leftright = function(group, options, prev) { ["minner", style.cls()], inner, options); }; -groupTypes.rule = function(group, options, prev) { +groupTypes.rule = function(group, options) { // Make an empty span for the rule var rule = makeSpan(["mord", "rule"], [], options); var style = options.style; @@ -1290,7 +1274,7 @@ groupTypes.rule = function(group, options, prev) { return rule; }; -groupTypes.kern = function(group, options, prev) { +groupTypes.kern = function(group, options) { // Make an empty span for the rule var rule = makeSpan(["mord", "rule"], [], options); var style = options.style; @@ -1310,7 +1294,7 @@ groupTypes.kern = function(group, options, prev) { return rule; }; -groupTypes.accent = function(group, options, prev) { +groupTypes.accent = function(group, options) { // Accents are handled in the TeXbook pg. 443, rule 12. var base = group.value.base; var style = options.style; @@ -1337,7 +1321,7 @@ groupTypes.accent = function(group, options, prev) { // Rerender the supsub group with its new base, and store that // result. supsubGroup = buildGroup( - supsub, options.reset(), prev); + supsub, options.reset()); } // Build the base group @@ -1419,11 +1403,11 @@ groupTypes.accent = function(group, options, prev) { } }; -groupTypes.phantom = function(group, options, prev) { +groupTypes.phantom = function(group, options) { var elements = buildExpression( group.value.value, options.withPhantom(), - prev + false ); // \phantom isn't supposed to affect the elements it contains. @@ -1431,19 +1415,25 @@ groupTypes.phantom = function(group, options, prev) { return new buildCommon.makeFragment(elements); }; +groupTypes.mclass = function(group, options) { + var elements = buildExpression(group.value.value, options, true); + + return makeSpan([group.value.mclass], elements, options); +}; + /** * buildGroup is the function that takes a group and calls the correct groupType * function for it. It also handles the interaction of size and style changes * between parents and children. */ -var buildGroup = function(group, options, prev) { +var buildGroup = function(group, options) { if (!group) { return makeSpan(); } if (groupTypes[group.type]) { // Call the groupTypes function - var groupNode = groupTypes[group.type](group, options, prev); + var groupNode = groupTypes[group.type](group, options); var multiplier; // If the style changed between the parent and the current group, @@ -1483,7 +1473,7 @@ var buildHTML = function(tree, options) { tree = JSON.parse(JSON.stringify(tree)); // Build the expression contained in the tree - var expression = buildExpression(tree, options); + var expression = buildExpression(tree, options, true); var body = makeSpan(["base", options.style.cls()], expression, options); // Add struts, which ensure that the top of the HTML element falls at the diff --git a/src/buildMathML.js b/src/buildMathML.js index 575ba75..11a088f 100644 --- a/src/buildMathML.js +++ b/src/buildMathML.js @@ -316,7 +316,7 @@ groupTypes.spacing = function(group) { return node; }; -groupTypes.op = function(group) { +groupTypes.op = function(group, options) { var node; // TODO(emily): handle big operators using the `largeop` attribute @@ -325,6 +325,10 @@ groupTypes.op = function(group) { // This is a symbol. Just add the symbol. node = new mathMLTree.MathNode( "mo", [makeText(group.value.body, group.mode)]); + } else if (group.value.value) { + // This is an operator with children. Add them. + node = new mathMLTree.MathNode( + "mo", buildExpression(group.value.value, options)); } else { // This is a text operator. Add all of the characters from the // operator's name. @@ -358,10 +362,10 @@ groupTypes.delimsizing = function(group) { var node = new mathMLTree.MathNode("mo", children); - if (group.value.delimType === "open" || - group.value.delimType === "close") { + if (group.value.mclass === "mopen" || + group.value.mclass === "mclose") { // Only some of the delimsizing functions act as fences, and they - // return "open" or "close" delimTypes. + // return "mopen" or "mclose" mclass. node.setAttribute("fence", "true"); } else { // Explicitly disable fencing if it's not a fence, to override the @@ -470,11 +474,16 @@ groupTypes.rlap = function(group, options) { return node; }; -groupTypes.phantom = function(group, options, prev) { +groupTypes.phantom = function(group, options) { var inner = buildExpression(group.value.value, options); return new mathMLTree.MathNode("mphantom", inner); }; +groupTypes.mclass = function(group, options) { + var inner = buildExpression(group.value.value, options); + return new mathMLTree.MathNode("mstyle", inner); +}; + /** * Takes a list of nodes, builds them, and returns a list of the generated * MathML nodes. A little simpler than the HTML version because we don't do any diff --git a/src/delimiter.js b/src/delimiter.js index 3b62621..0dbc8c5 100644 --- a/src/delimiter.js +++ b/src/delimiter.js @@ -56,9 +56,11 @@ var mathrmSize = function(value, size, mode, options) { * Puts a delimiter span in a given style, and adds appropriate height, depth, * and maxFontSizes. */ -var styleWrap = function(delim, toStyle, options) { +var styleWrap = function(delim, toStyle, options, classes) { + classes = classes || []; var span = makeSpan( - ["style-wrap", options.style.reset(), toStyle.cls()], [delim], options); + classes.concat(["style-wrap", options.style.reset(), toStyle.cls()]), + [delim], options); var multiplier = toStyle.sizeMultiplier / options.style.sizeMultiplier; @@ -74,10 +76,10 @@ var styleWrap = function(delim, toStyle, options) { * font, but is restyled to either be in textstyle, scriptstyle, or * scriptscriptstyle. */ -var makeSmallDelim = function(delim, style, center, options, mode) { +var makeSmallDelim = function(delim, style, center, options, mode, classes) { var text = buildCommon.makeSymbol(delim, "Main-Regular", mode, options); - var span = styleWrap(text, style, options); + var span = styleWrap(text, style, options, classes); if (center) { var shift = @@ -96,13 +98,12 @@ var makeSmallDelim = function(delim, style, center, options, mode) { * Makes a large delimiter. This is a delimiter that comes in the Size1, Size2, * Size3, or Size4 fonts. It is always rendered in textstyle. */ -var makeLargeDelim = function(delim, size, center, options, mode) { +var makeLargeDelim = function(delim, size, center, options, mode, classes) { var inner = mathrmSize(delim, size, mode, options); var span = styleWrap( - makeSpan(["delimsizing", "size" + size], - [inner], options), - Style.TEXT, options); + makeSpan(["delimsizing", "size" + size], [inner], options), + Style.TEXT, options, classes); if (center) { var shift = (1 - options.style.sizeMultiplier) * @@ -142,7 +143,8 @@ var makeInner = function(symbol, font, mode) { * Make a stacked delimiter out of a given delimiter, with the total height at * least `heightTotal`. This routine is mentioned on page 442 of the TeXbook. */ -var makeStackedDelim = function(delim, heightTotal, center, options, mode) { +var makeStackedDelim = function(delim, heightTotal, center, options, mode, + classes) { // There are four parts, the top, an optional middle, a repeated part, and a // bottom. var top; @@ -320,7 +322,7 @@ var makeStackedDelim = function(delim, heightTotal, center, options, mode) { return styleWrap( makeSpan(["delimsizing", "mult"], [inner], options), - Style.TEXT, options); + Style.TEXT, options, classes); }; // There are three kinds of delimiters, delimiters that stack when they become @@ -354,7 +356,7 @@ var sizeToMaxHeight = [0, 1.2, 1.8, 2.4, 3.0]; /** * Used to create a delimiter of a specific size, where `size` is 1, 2, 3, or 4. */ -var makeSizedDelim = function(delim, size, options, mode) { +var makeSizedDelim = function(delim, size, options, mode, classes) { // < and > turn into \langle and \rangle in delimiters if (delim === "<" || delim === "\\lt") { delim = "\\langle"; @@ -365,10 +367,10 @@ var makeSizedDelim = function(delim, size, options, mode) { // Sized delimiters are never centered. if (utils.contains(stackLargeDelimiters, delim) || utils.contains(stackNeverDelimiters, delim)) { - return makeLargeDelim(delim, size, false, options, mode); + return makeLargeDelim(delim, size, false, options, mode, classes); } else if (utils.contains(stackAlwaysDelimiters, delim)) { return makeStackedDelim( - delim, sizeToMaxHeight[size], false, options, mode); + delim, sizeToMaxHeight[size], false, options, mode, classes); } else { throw new ParseError("Illegal delimiter: '" + delim + "'"); } @@ -471,7 +473,8 @@ var traverseSequence = function(delim, height, sequence, options) { * Make a delimiter of a given height+depth, with optional centering. Here, we * traverse the sequences, and create a delimiter that the sequence tells us to. */ -var makeCustomSizedDelim = function(delim, height, center, options, mode) { +var makeCustomSizedDelim = function(delim, height, center, options, mode, + classes) { if (delim === "<" || delim === "\\lt") { delim = "\\langle"; } else if (delim === ">" || delim === "\\gt") { @@ -494,11 +497,13 @@ var makeCustomSizedDelim = function(delim, height, center, options, mode) { // Depending on the sequence element we decided on, call the appropriate // function. if (delimType.type === "small") { - return makeSmallDelim(delim, delimType.style, center, options, mode); + return makeSmallDelim(delim, delimType.style, center, options, mode, + classes); } else if (delimType.type === "large") { - return makeLargeDelim(delim, delimType.size, center, options, mode); + return makeLargeDelim(delim, delimType.size, center, options, mode, + classes); } else if (delimType.type === "stack") { - return makeStackedDelim(delim, height, center, options, mode); + return makeStackedDelim(delim, height, center, options, mode, classes); } }; @@ -506,7 +511,8 @@ var makeCustomSizedDelim = function(delim, height, center, options, mode) { * Make a delimiter for use with `\left` and `\right`, given a height and depth * of an expression that the delimiters surround. */ -var makeLeftRightDelim = function(delim, height, depth, options, mode) { +var makeLeftRightDelim = function(delim, height, depth, options, mode, + classes) { // We always center \left/\right delimiters, so the axis is always shifted var axisHeight = options.style.metrics.axisHeight * options.style.sizeMultiplier; @@ -533,7 +539,8 @@ var makeLeftRightDelim = function(delim, height, depth, options, mode) { // Finally, we defer to `makeCustomSizedDelim` with our calculated total // height - return makeCustomSizedDelim(delim, totalHeight, true, options, mode); + return makeCustomSizedDelim(delim, totalHeight, true, options, mode, + classes); }; module.exports = { diff --git a/src/domTree.js b/src/domTree.js index 0517b81..f09ff4e 100644 --- a/src/domTree.js +++ b/src/domTree.js @@ -57,6 +57,10 @@ span.prototype.setAttribute = function(attribute, value) { this.attributes[attribute] = value; }; +span.prototype.tryCombine = function(sibling) { + return false; +}; + /** * Convert the span into an HTML node */ @@ -220,6 +224,34 @@ function symbolNode(value, height, depth, italic, skew, classes, style) { } } +symbolNode.prototype.tryCombine = function(sibling) { + if (!sibling + || !(sibling instanceof symbolNode) + || this.italic > 0 + || createClass(this.classes) !== createClass(sibling.classes) + || this.skew !== sibling.skew + || this.maxFontSize !== sibling.maxFontSize) { + return false; + } + for (var style in this.style) { + if (this.style.hasOwnProperty(style) + && this.style[style] !== sibling.style[style]) { + return false; + } + } + for (style in sibling.style) { + if (sibling.style.hasOwnProperty(style) + && this.style[style] !== sibling.style[style]) { + return false; + } + } + this.value += sibling.value; + this.height = Math.max(this.height, sibling.height); + this.depth = Math.max(this.depth, sibling.depth); + this.italic = sibling.italic; + return true; +}; + /** * Creates a text node or span from a symbol node. Note that a span is only * created if it is needed. diff --git a/src/functions.js b/src/functions.js index 7888242..6966ea7 100644 --- a/src/functions.js +++ b/src/functions.js @@ -1,5 +1,7 @@ var utils = require("./utils"); var ParseError = require("./ParseError"); +var parseData = require("./parseData"); +var ParseNode = parseData.ParseNode; /* This file contains a list of functions that we parse, identified by * the calls to defineFunction. @@ -100,6 +102,16 @@ function defineFunction(names, props, handler) { } } +// Since the corresponding buildHTML/buildMathML function expects a +// list of elements, we normalize for different kinds of arguments +var ordargument = function(arg) { + if (arg.type === "ordgroup") { + return arg.value; + } else { + return [arg]; + } +}; + // A normal square root defineFunction("\\sqrt", { numArgs: 1, @@ -114,26 +126,27 @@ defineFunction("\\sqrt", { }; }); -// Some non-mathy text -defineFunction("\\text", { +// Non-mathy text, possibly in a font +var textFunctionStyles = { + "\\text": undefined, "\\textrm": "mathrm", "\\textsf": "mathsf", + "\\texttt": "mathtt", "\\textnormal": "mathrm", "\\textbf": "mathbf", + "\\textit": "textit", +}; + +defineFunction([ + "\\text", "\\textrm", "\\textsf", "\\texttt", "\\textnormal", + "\\textbf", "\\textit", +], { numArgs: 1, argTypes: ["text"], greediness: 2, + allowedInText: true, }, function(context, args) { var body = args[0]; - // Since the corresponding buildHTML/buildMathML function expects a - // list of elements, we normalize for different kinds of arguments - // TODO(emily): maybe this should be done somewhere else - var inner; - if (body.type === "ordgroup") { - inner = body.value; - } else { - inner = [body]; - } - return { type: "text", - body: inner, + body: ordargument(body), + style: textFunctionStyles[context.funcName], }; }); @@ -146,18 +159,10 @@ defineFunction("\\color", { }, function(context, args) { var color = args[0]; var body = args[1]; - // Normalize the different kinds of bodies (see \text above) - var inner; - if (body.type === "ordgroup") { - inner = body.value; - } else { - inner = [body]; - } - return { type: "color", color: color.value, - value: inner, + value: ordargument(body), }; }); @@ -223,37 +228,73 @@ defineFunction("\\phantom", { numArgs: 1, }, function(context, args) { var body = args[0]; - var inner; - if (body.type === "ordgroup") { - inner = body.value; - } else { - inner = [body]; - } - return { type: "phantom", - value: inner, + value: ordargument(body), + }; +}); + +// Math class commands except \mathop +defineFunction([ + "\\mathord", "\\mathbin", "\\mathrel", "\\mathopen", + "\\mathclose", "\\mathpunct", "\\mathinner", +], { + numArgs: 1, +}, function(context, args) { + var body = args[0]; + return { + type: "mclass", + mclass: "m" + context.funcName.substr(5), + value: ordargument(body), + }; +}); + +// Build a relation by placing one symbol on top of another +defineFunction("\\stackrel", { + numArgs: 2, +}, function(context, args) { + var top = args[0]; + var bottom = args[1]; + + var bottomop = new ParseNode("op", { + type: "op", + limits: true, + alwaysHandleSupSub: true, + symbol: false, + value: ordargument(bottom), + }, bottom.mode); + + var supsub = new ParseNode("supsub", { + base: bottomop, + sup: top, + sub: null, + }, top.mode); + + return { + type: "mclass", + mclass: "mrel", + value: [supsub], }; }); // Extra data needed for the delimiter handler down below var delimiterSizes = { - "\\bigl" : {type: "open", size: 1}, - "\\Bigl" : {type: "open", size: 2}, - "\\biggl": {type: "open", size: 3}, - "\\Biggl": {type: "open", size: 4}, - "\\bigr" : {type: "close", size: 1}, - "\\Bigr" : {type: "close", size: 2}, - "\\biggr": {type: "close", size: 3}, - "\\Biggr": {type: "close", size: 4}, - "\\bigm" : {type: "rel", size: 1}, - "\\Bigm" : {type: "rel", size: 2}, - "\\biggm": {type: "rel", size: 3}, - "\\Biggm": {type: "rel", size: 4}, - "\\big" : {type: "textord", size: 1}, - "\\Big" : {type: "textord", size: 2}, - "\\bigg" : {type: "textord", size: 3}, - "\\Bigg" : {type: "textord", size: 4}, + "\\bigl" : {mclass: "mopen", size: 1}, + "\\Bigl" : {mclass: "mopen", size: 2}, + "\\biggl": {mclass: "mopen", size: 3}, + "\\Biggl": {mclass: "mopen", size: 4}, + "\\bigr" : {mclass: "mclose", size: 1}, + "\\Bigr" : {mclass: "mclose", size: 2}, + "\\biggr": {mclass: "mclose", size: 3}, + "\\Biggr": {mclass: "mclose", size: 4}, + "\\bigm" : {mclass: "mrel", size: 1}, + "\\Bigm" : {mclass: "mrel", size: 2}, + "\\biggm": {mclass: "mrel", size: 3}, + "\\Biggm": {mclass: "mrel", size: 4}, + "\\big" : {mclass: "mord", size: 1}, + "\\Big" : {mclass: "mord", size: 2}, + "\\bigg" : {mclass: "mord", size: 3}, + "\\Bigg" : {mclass: "mord", size: 4}, }; var delimiters = [ @@ -298,17 +339,10 @@ defineFunction([ greediness: 3, }, function(context, args) { var body = args[0]; - var atoms; - if (body.type === "ordgroup") { - atoms = body.value; - } else { - atoms = [body]; - } - return { type: "color", color: "katex-" + context.funcName.slice(1), - value: atoms, + value: ordargument(body), }; }); @@ -378,10 +412,24 @@ defineFunction([ }; }); +// \mathop class command +defineFunction("\\mathop", { + numArgs: 1, +}, function(context, args) { + var body = args[0]; + return { + type: "op", + limits: false, + symbol: false, + value: ordargument(body), + }; +}); + // Fractions defineFunction([ "\\dfrac", "\\frac", "\\tfrac", "\\dbinom", "\\binom", "\\tbinom", + "\\\\atopfrac", // can’t be entered directly ], { numArgs: 2, greediness: 2, @@ -399,6 +447,9 @@ defineFunction([ case "\\tfrac": hasBarLine = true; break; + case "\\\\atopfrac": + hasBarLine = false; + break; case "\\dbinom": case "\\binom": case "\\tbinom": @@ -472,7 +523,7 @@ defineFunction([ return { type: "delimsizing", size: delimiterSizes[context.funcName].size, - delimType: delimiterSizes[context.funcName].type, + mclass: delimiterSizes[context.funcName].mclass, value: delim.value, }; } @@ -535,7 +586,7 @@ defineFunction([ }); // Infix generalized fractions -defineFunction(["\\over", "\\choose"], { +defineFunction(["\\over", "\\choose", "\\atop"], { numArgs: 0, infix: true, }, function(context) { @@ -547,6 +598,9 @@ defineFunction(["\\over", "\\choose"], { case "\\choose": replaceWith = "\\binom"; break; + case "\\atop": + replaceWith = "\\\\atopfrac"; + break; default: throw new Error("Unrecognized infix genfrac command"); } diff --git a/src/symbols.js b/src/symbols.js index 55301a4..92fbef0 100644 --- a/src/symbols.js +++ b/src/symbols.js @@ -87,7 +87,9 @@ defineSymbol(math, main, punct, "\u22c5", "\\cdotp"); // Misc Symbols defineSymbol(math, main, textord, "\u0023", "\\#"); +defineSymbol(text, main, textord, "\u0023", "\\#"); defineSymbol(math, main, textord, "\u0026", "\\&"); +defineSymbol(text, main, textord, "\u0026", "\\&"); defineSymbol(math, main, textord, "\u2135", "\\aleph"); defineSymbol(math, main, textord, "\u2200", "\\forall"); defineSymbol(math, main, textord, "\u210f", "\\hbar"); @@ -394,8 +396,11 @@ defineSymbol(math, ams, rel, "\u21be", "\\restriction"); defineSymbol(math, main, textord, "\u2018", "`"); defineSymbol(math, main, textord, "$", "\\$"); +defineSymbol(text, main, textord, "$", "\\$"); defineSymbol(math, main, textord, "%", "\\%"); +defineSymbol(text, main, textord, "%", "\\%"); defineSymbol(math, main, textord, "_", "\\_"); +defineSymbol(text, main, textord, "_", "\\_"); defineSymbol(math, main, textord, "\u2220", "\\angle"); defineSymbol(math, main, textord, "\u221e", "\\infty"); defineSymbol(math, main, textord, "\u2032", "\\prime"); @@ -534,7 +539,9 @@ defineSymbol(math, main, bin, "\u22c6", "\\star"); defineSymbol(math, main, bin, "\u25c3", "\\triangleleft"); defineSymbol(math, main, bin, "\u25b9", "\\triangleright"); defineSymbol(math, main, open, "{", "\\{"); +defineSymbol(text, main, textord, "{", "\\{"); defineSymbol(math, main, close, "}", "\\}"); +defineSymbol(text, main, textord, "}", "\\}"); defineSymbol(math, main, open, "{", "\\lbrace"); defineSymbol(math, main, close, "}", "\\rbrace"); defineSymbol(math, main, open, "[", "\\lbrack"); @@ -652,3 +659,11 @@ for (i = 0x0410; i <= 0x044F; i++) { ch = String.fromCharCode(i); defineSymbol(text, main, textord, ch, ch); } + +// Unicode versions of existing characters +defineSymbol(text, main, textord, "\u2013", "–"); +defineSymbol(text, main, textord, "\u2014", "—"); +defineSymbol(text, main, textord, "\u2018", "‘"); +defineSymbol(text, main, textord, "\u2019", "’"); +defineSymbol(text, main, textord, "\u201c", "“"); +defineSymbol(text, main, textord, "\u201d", "”"); diff --git a/static/katex.less b/static/katex.less index 7b8c293..764e446 100644 --- a/static/katex.less +++ b/static/katex.less @@ -49,6 +49,14 @@ display: inline-block; } + .mathrm { + font-style: normal; + } + + .textit { + font-style: italic; + } + .mathit { font-family: KaTeX_Math; font-style: italic; diff --git a/test/katex-spec.js b/test/katex-spec.js index 122c8db..2018b91 100644 --- a/test/katex-spec.js +++ b/test/katex-spec.js @@ -606,6 +606,15 @@ describe("A frac parser", function() { expect(tfracParse.value.numer).toBeDefined(); expect(tfracParse.value.denom).toBeDefined(); }); + + it("should parse atop", function() { + var parse = getParsed("x \\atop y")[0]; + + expect(parse.type).toEqual("genfrac"); + expect(parse.value.numer).toBeDefined(); + expect(parse.value.denom).toBeDefined(); + expect(parse.value.hasBarLine).toEqual(false); + }); }); describe("An over parser", function() { @@ -883,8 +892,8 @@ describe("A delimiter sizing parser", function() { var leftParse = getParsed(normalDelim)[0]; var rightParse = getParsed(bigDelim)[0]; - expect(leftParse.value.delimType).toEqual("open"); - expect(rightParse.value.delimType).toEqual("close"); + expect(leftParse.value.mclass).toEqual("mopen"); + expect(rightParse.value.mclass).toEqual("mclose"); }); it("should parse the correct size delimiter", function() { @@ -1373,6 +1382,43 @@ describe("An HTML font tree-builder", function() { expect(markup).toContain("R"); }); + it("should render \\text{R} with the correct font", function() { + var markup = katex.renderToString("\\text{R}"); + expect(markup).toContain("R"); + }); + + it("should render \\textit{R} with the correct font", function() { + var markup = katex.renderToString("\\textit{R}"); + expect(markup).toContain("R"); + }); + + it("should render \\text{\\textit{R}} with the correct font", function() { + var markup = katex.renderToString("\\text{\\textit{R}}"); + expect(markup).toContain("R"); + }); + + it("should render \\text{R\\textit{S}T} with the correct fonts", function() { + var markup = katex.renderToString("\\text{R\\textit{S}T}"); + expect(markup).toContain("R"); + expect(markup).toContain("S"); + expect(markup).toContain("T"); + }); + + it("should render \\textbf{R} with the correct font", function() { + var markup = katex.renderToString("\\textbf{R}"); + expect(markup).toContain("R"); + }); + + it("should render \\textsf{R} with the correct font", function() { + var markup = katex.renderToString("\\textsf{R}"); + expect(markup).toContain("R"); + }); + + it("should render \\texttt{R} with the correct font", function() { + var markup = katex.renderToString("\\texttt{R}"); + expect(markup).toContain("R"); + }); + it("should render a combination of font and color changes", function() { var markup = katex.renderToString("\\color{blue}{\\mathbb R}"); var span = "R"; diff --git a/test/screenshotter/images/BinCancellation-chrome.png b/test/screenshotter/images/BinCancellation-chrome.png new file mode 100644 index 0000000..749e2bd Binary files /dev/null and b/test/screenshotter/images/BinCancellation-chrome.png differ diff --git a/test/screenshotter/images/BinCancellation-firefox.png b/test/screenshotter/images/BinCancellation-firefox.png new file mode 100644 index 0000000..021274e Binary files /dev/null and b/test/screenshotter/images/BinCancellation-firefox.png differ diff --git a/test/screenshotter/images/Cases-chrome.png b/test/screenshotter/images/Cases-chrome.png index 775a3d9..d6b1382 100644 Binary files a/test/screenshotter/images/Cases-chrome.png and b/test/screenshotter/images/Cases-chrome.png differ diff --git a/test/screenshotter/images/Cases-firefox.png b/test/screenshotter/images/Cases-firefox.png index 4f77c85..901f2b6 100644 Binary files a/test/screenshotter/images/Cases-firefox.png and b/test/screenshotter/images/Cases-firefox.png differ diff --git a/test/screenshotter/images/DashesAndQuotes-chrome.png b/test/screenshotter/images/DashesAndQuotes-chrome.png index 14fdba8..ea21f69 100644 Binary files a/test/screenshotter/images/DashesAndQuotes-chrome.png and b/test/screenshotter/images/DashesAndQuotes-chrome.png differ diff --git a/test/screenshotter/images/DashesAndQuotes-firefox.png b/test/screenshotter/images/DashesAndQuotes-firefox.png index bf62c85..ca308ae 100644 Binary files a/test/screenshotter/images/DashesAndQuotes-firefox.png and b/test/screenshotter/images/DashesAndQuotes-firefox.png differ diff --git a/test/screenshotter/images/FractionTest-chrome.png b/test/screenshotter/images/FractionTest-chrome.png index b365f94..a54e5b3 100644 Binary files a/test/screenshotter/images/FractionTest-chrome.png and b/test/screenshotter/images/FractionTest-chrome.png differ diff --git a/test/screenshotter/images/FractionTest-firefox.png b/test/screenshotter/images/FractionTest-firefox.png index 7b4d559..298faed 100644 Binary files a/test/screenshotter/images/FractionTest-firefox.png and b/test/screenshotter/images/FractionTest-firefox.png differ diff --git a/test/screenshotter/images/MathAtom-chrome.png b/test/screenshotter/images/MathAtom-chrome.png new file mode 100644 index 0000000..175c72a Binary files /dev/null and b/test/screenshotter/images/MathAtom-chrome.png differ diff --git a/test/screenshotter/images/MathAtom-firefox.png b/test/screenshotter/images/MathAtom-firefox.png new file mode 100644 index 0000000..d451d16 Binary files /dev/null and b/test/screenshotter/images/MathAtom-firefox.png differ diff --git a/test/screenshotter/images/MathAtom2-chrome.png b/test/screenshotter/images/MathAtom2-chrome.png new file mode 100644 index 0000000..cdfc0c7 Binary files /dev/null and b/test/screenshotter/images/MathAtom2-chrome.png differ diff --git a/test/screenshotter/images/MathAtom2-firefox.png b/test/screenshotter/images/MathAtom2-firefox.png new file mode 100644 index 0000000..cc0b696 Binary files /dev/null and b/test/screenshotter/images/MathAtom2-firefox.png differ diff --git a/test/screenshotter/images/StackRel-chrome.png b/test/screenshotter/images/StackRel-chrome.png new file mode 100644 index 0000000..f848e2f Binary files /dev/null and b/test/screenshotter/images/StackRel-chrome.png differ diff --git a/test/screenshotter/images/StackRel-firefox.png b/test/screenshotter/images/StackRel-firefox.png new file mode 100644 index 0000000..deb5e2c Binary files /dev/null and b/test/screenshotter/images/StackRel-firefox.png differ diff --git a/test/screenshotter/images/Unicode-chrome.png b/test/screenshotter/images/Unicode-chrome.png index 6d7472f..6c4f1ff 100644 Binary files a/test/screenshotter/images/Unicode-chrome.png and b/test/screenshotter/images/Unicode-chrome.png differ diff --git a/test/screenshotter/images/Unicode-firefox.png b/test/screenshotter/images/Unicode-firefox.png index bc0fc1b..18afc71 100644 Binary files a/test/screenshotter/images/Unicode-firefox.png and b/test/screenshotter/images/Unicode-firefox.png differ diff --git a/test/screenshotter/images/UnsupportedCmds-chrome.png b/test/screenshotter/images/UnsupportedCmds-chrome.png index 4d8a115..7eaba5f 100644 Binary files a/test/screenshotter/images/UnsupportedCmds-chrome.png and b/test/screenshotter/images/UnsupportedCmds-chrome.png differ diff --git a/test/screenshotter/images/UnsupportedCmds-firefox.png b/test/screenshotter/images/UnsupportedCmds-firefox.png index d2f19ca..797fbdd 100644 Binary files a/test/screenshotter/images/UnsupportedCmds-firefox.png and b/test/screenshotter/images/UnsupportedCmds-firefox.png differ diff --git a/test/screenshotter/ss_data.yaml b/test/screenshotter/ss_data.yaml index ca06906..a37a692 100644 --- a/test/screenshotter/ss_data.yaml +++ b/test/screenshotter/ss_data.yaml @@ -26,6 +26,11 @@ Arrays: | ArrayType: 1\begin{array}{c}2\\3\end{array}4 Baseline: a+b-c\cdot d/e BasicTest: a +BinCancellation: | + \begin{array}{ccc} + +1 & 1+ & 1+1 & (,) \\ + 1++1 & 3\times) & 1+, & \left(,\right) + \end{array} BinomTest: \dbinom{a}{b}\tbinom{a}{b}^{\binom{a}{b}+17} BoldSpacing: \mathbf{A}^2+\mathbf{B}_3*\mathscr{C}' Cases: | @@ -57,7 +62,7 @@ DisplayStyle: | {\displaystyle\sqrt{x}}{\sqrt{x}} {\displaystyle \frac12}{\frac12}{\displaystyle x^1_2}{x^1_2} Exponents: a^{a^a_a}_{a^a_a} -FractionTest: \dfrac{a}{b}\frac{a}{b}\tfrac{a}{b}\;-\dfrac12\;1\tfrac12 +FractionTest: \dfrac{a}{b}\frac{a}{b}\tfrac{a}{b}\;-\dfrac12\;1\tfrac12\;{1 \atop 2} Functions: \sin\cos\tan\ln\log GreekLetters: \alpha\beta\gamma\omega KaTeX: \KaTeX @@ -74,6 +79,8 @@ LeftRightStyleSizing: | LimitControls: | \displaystyle\int\limits_2^3 3x^2\,dx + \sum\nolimits^n_{i=1}i + \textstyle\int\limits_x^y z +MathAtom: a\mathrel{\mathop{=}\limits^{\blue ?}}b +MathAtom2: \mathop{\overline\mathrm{lim}}\limits_{x\to\infty}f(x) MathDefaultFonts: Ax2k\breve{a}\omega\Omega\imath+\KaTeX MathBb: \mathbb{Ax2k\breve{a}\omega\Omega\imath+\KaTeX} MathBf: \mathbf{Ax2k\breve{a}\omega\Omega\imath+\KaTeX} @@ -109,6 +116,7 @@ Sqrt: | ^{\sqrt{\sqrt{\sqrt{x}}}}} SqrtRoot: | 1+\sqrt[3]{2}+\sqrt[1923^234]{2^{2^{2^{2^{2^{2^{2^{2^{2^{2^{2^2}}}}}}}}}}} +StackRel: a \stackrel{?}{=} b \stackrel{\text{def}}{=} c StyleSwitching: a\cdot b\scriptstyle a\cdot ba\textstyle\cdot ba\scriptstyle\cdot b SupSubCharacterBox: a_2f_2{f}_2{aa}_2{af}_2\mathbf{y}_Ay_A SupSubHorizSpacing: |