diff --git a/src/buildCommon.js b/src/buildCommon.js index 8ee9ac0b84..db47f2c6c2 100644 --- a/src/buildCommon.js +++ b/src/buildCommon.js @@ -34,14 +34,16 @@ var mainitLetters = [ * Makes a symbolNode after translation via the list of symbols in symbols.js. * Correctly pulls out metrics for the character, and optionally takes a list of * classes to be attached to the node. + * + * TODO: make argument order closer to makeSpan */ -var makeSymbol = function(value, style, mode, options, classes) { +var makeSymbol = function(value, fontFamily, mode, options, classes) { // Replace the value with its replaced value from symbol.js if (symbols[mode][value] && symbols[mode][value].replace) { value = symbols[mode][value].replace; } - var metrics = fontMetrics.getCharacterMetrics(value, style); + var metrics = fontMetrics.getCharacterMetrics(value, fontFamily); var symbolNode; if (metrics) { @@ -52,7 +54,7 @@ var makeSymbol = function(value, style, mode, options, classes) { // TODO(emily): Figure out a good way to only print this in development typeof console !== "undefined" && console.warn( "No character metrics for '" + value + "' in style '" + - style + "'"); + fontFamily + "'"); symbolNode = new domTree.symbolNode(value, 0, 0, 0, 0, classes); } @@ -187,6 +189,16 @@ var makeSpan = function(classes, children, options) { return span; }; +/** + * Prepends the given children to the given span, updating height, depth, and + * maxFontSize. + */ +var prependChildren = function(span, children) { + span.children = children.concat(span.children); + + sizeElementFromChildren(span); +}; + /** * Makes a document fragment with the given list of children. */ @@ -447,6 +459,7 @@ module.exports = { makeFragment: makeFragment, makeVList: makeVList, makeOrd: makeOrd, + prependChildren: prependChildren, sizingMultiplier: sizingMultiplier, spacingFunctions: spacingFunctions, }; diff --git a/src/buildHTML.js b/src/buildHTML.js index 63f319cc78..5934cf010b 100644 --- a/src/buildHTML.js +++ b/src/buildHTML.js @@ -17,18 +17,60 @@ var utils = require("./utils"); var makeSpan = buildCommon.makeSpan; +var isSpace = function(node) { + return node instanceof domTree.span && node.classes[0] === "mspace"; +}; + /** * 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. + * 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. */ var buildExpression = function(expression, options, prev) { + // Parse expressions into `groups`. var groups = []; for (var i = 0; i < expression.length; i++) { var group = expression[i]; - groups.push(buildGroup(group, options, prev)); - prev = group; + var output = buildGroup(group, options, prev); + 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. + + // Explicit spaces (e.g., \;, \,) should be ignored with respect to atom + // spacing (e.g., "add thick space between mord and mrel"). Since CSS + // adjacency rules implement atom spacing, spaces should be invisible to + // CSS. So we shift them into the atoms themselves. + for (i = 0; i < groups.length; i++) { + if (isSpace(groups[i])) { + // Find next non-space group + for (var j = i + 1; j < groups.length; j++) { + if (!isSpace(groups[j])) { + break; + } + } + // If non-space group exists, ensure that group is a span and tuck + // the spaces---nodes [i, j)---into the span as its first children + if (j < groups.length) { + group = groups[j]; + if (group instanceof domTree.symbolNode) { + group = groups[j] = makeSpan(group.classes, [group]); + } + buildCommon.prependChildren(group, groups.splice(i, j - i)); + } else { + i = j; + } + } + } + return groups; }; @@ -80,12 +122,13 @@ var getTypeOfGroup = function(group) { return getTypeOfGroup(group.value.base); } else if (group.type === "llap" || group.type === "rlap") { return getTypeOfGroup(group.value); - } else if (group.type === "color") { - return getTypeOfGroup(group.value.value); - } else if (group.type === "sizing") { - return getTypeOfGroup(group.value.value); - } else if (group.type === "styling") { - return getTypeOfGroup(group.value.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]; } else { @@ -697,14 +740,14 @@ groupTypes.spacing = function(group, options, prev) { // things has an entry in the symbols table, so these will be turned // into appropriate outputs. return makeSpan( - ["mord", "mspace"], + ["mspace"], [buildCommon.mathsym(group.value, group.mode)] ); } else { // Other kinds of spaces are of arbitrary width. We use CSS to // generate these. return makeSpan( - ["mord", "mspace", + ["mspace", buildCommon.spacingFunctions[group.value].className]); } }; @@ -1109,7 +1152,19 @@ groupTypes.styling = function(group, options, prev) { var inner = buildExpression( group.value.value, newOptions, prev); - return makeSpan([options.style.reset(), newStyle.cls()], inner, newOptions); + // Add style-resetting classes to the inner list. Handle nested changes. + for (var i = 0; i < inner.length; i++) { + var pos = utils.indexOf(inner[i].classes, newStyle.reset()); + if (pos < 0) { + inner[i].classes.push(options.style.reset(), newStyle.cls()); + } else { + // This is a nested style change, as `\textstyle a\scriptstyle b`. + // Only override the old style (the reset class). + inner[i].classes[pos] = options.style.reset(); + } + } + + return new buildCommon.makeFragment(inner); }; groupTypes.font = function(group, options, prev) { diff --git a/test/katex-spec.js b/test/katex-spec.js index 57aad4e511..122c8dbd0e 100644 --- a/test/katex-spec.js +++ b/test/katex-spec.js @@ -1581,7 +1581,8 @@ describe("A bin builder", function() { it("should correctly interact with color objects", function() { expect(getBuilt("\\blue{x}+y")[1].classes).toContain("mbin"); - expect(getBuilt("\\blue{x+}+y")[1].classes).toContain("mord"); + expect(getBuilt("\\blue{x+}+y")[1].classes).toContain("mbin"); + expect(getBuilt("\\blue{x+}+y")[2].classes).toContain("mord"); }); }); @@ -1695,17 +1696,17 @@ describe("A phantom builder", function() { }); it("should make the children transparent", function() { - var children = getBuilt("\\phantom{x+1}")[0].children; + var children = getBuilt("\\phantom{x+1}"); expect(children[0].style.color).toBe("transparent"); expect(children[1].style.color).toBe("transparent"); expect(children[2].style.color).toBe("transparent"); }); it("should make all descendants transparent", function() { - var children = getBuilt("\\phantom{x+\\blue{1}}")[0].children; + var children = getBuilt("\\phantom{x+\\blue{1}}"); expect(children[0].style.color).toBe("transparent"); expect(children[1].style.color).toBe("transparent"); - expect(children[2].children[0].style.color).toBe("transparent"); + expect(children[2].style.color).toBe("transparent"); }); }); diff --git a/test/screenshotter/images/BoldSpacing-chrome.png b/test/screenshotter/images/BoldSpacing-chrome.png new file mode 100644 index 0000000000..443e149bea Binary files /dev/null and b/test/screenshotter/images/BoldSpacing-chrome.png differ diff --git a/test/screenshotter/images/BoldSpacing-firefox.png b/test/screenshotter/images/BoldSpacing-firefox.png new file mode 100644 index 0000000000..022f4ad4e9 Binary files /dev/null and b/test/screenshotter/images/BoldSpacing-firefox.png differ diff --git a/test/screenshotter/images/ColorSpacing-chrome.png b/test/screenshotter/images/ColorSpacing-chrome.png new file mode 100644 index 0000000000..520054429f Binary files /dev/null and b/test/screenshotter/images/ColorSpacing-chrome.png differ diff --git a/test/screenshotter/images/ColorSpacing-firefox.png b/test/screenshotter/images/ColorSpacing-firefox.png new file mode 100644 index 0000000000..5eb2d46606 Binary files /dev/null and b/test/screenshotter/images/ColorSpacing-firefox.png differ diff --git a/test/screenshotter/images/LimitControls-chrome.png b/test/screenshotter/images/LimitControls-chrome.png index 40f2e63d68..1d9a419658 100644 Binary files a/test/screenshotter/images/LimitControls-chrome.png and b/test/screenshotter/images/LimitControls-chrome.png differ diff --git a/test/screenshotter/images/LimitControls-firefox.png b/test/screenshotter/images/LimitControls-firefox.png index db98c97895..8184ac232b 100644 Binary files a/test/screenshotter/images/LimitControls-firefox.png and b/test/screenshotter/images/LimitControls-firefox.png differ diff --git a/test/screenshotter/images/NegativeSpaceBetweenRel-chrome.png b/test/screenshotter/images/NegativeSpaceBetweenRel-chrome.png new file mode 100644 index 0000000000..5428d096b2 Binary files /dev/null and b/test/screenshotter/images/NegativeSpaceBetweenRel-chrome.png differ diff --git a/test/screenshotter/images/NegativeSpaceBetweenRel-firefox.png b/test/screenshotter/images/NegativeSpaceBetweenRel-firefox.png new file mode 100644 index 0000000000..2617807f5f Binary files /dev/null and b/test/screenshotter/images/NegativeSpaceBetweenRel-firefox.png differ diff --git a/test/screenshotter/images/StyleSwitching-chrome.png b/test/screenshotter/images/StyleSwitching-chrome.png index 31e30d3929..9774771ea8 100644 Binary files a/test/screenshotter/images/StyleSwitching-chrome.png and b/test/screenshotter/images/StyleSwitching-chrome.png differ diff --git a/test/screenshotter/images/StyleSwitching-firefox.png b/test/screenshotter/images/StyleSwitching-firefox.png index eef210d94b..60e45bfe17 100644 Binary files a/test/screenshotter/images/StyleSwitching-firefox.png and b/test/screenshotter/images/StyleSwitching-firefox.png differ diff --git a/test/screenshotter/images/SupSubLeftAlignReset-chrome.png b/test/screenshotter/images/SupSubLeftAlignReset-chrome.png index 95fb8a4cb7..8e3edfe41e 100644 Binary files a/test/screenshotter/images/SupSubLeftAlignReset-chrome.png and b/test/screenshotter/images/SupSubLeftAlignReset-chrome.png differ diff --git a/test/screenshotter/images/SupSubLeftAlignReset-firefox.png b/test/screenshotter/images/SupSubLeftAlignReset-firefox.png index 88e7ae7394..97ccd63e75 100644 Binary files a/test/screenshotter/images/SupSubLeftAlignReset-firefox.png and b/test/screenshotter/images/SupSubLeftAlignReset-firefox.png differ diff --git a/test/screenshotter/ss_data.yaml b/test/screenshotter/ss_data.yaml index 27350c840b..ca06906547 100644 --- a/test/screenshotter/ss_data.yaml +++ b/test/screenshotter/ss_data.yaml @@ -27,6 +27,7 @@ ArrayType: 1\begin{array}{c}2\\3\end{array}4 Baseline: a+b-c\cdot d/e BasicTest: a BinomTest: \dbinom{a}{b}\tbinom{a}{b}^{\binom{a}{b}+17} +BoldSpacing: \mathbf{A}^2+\mathbf{B}_3*\mathscr{C}' Cases: | f(a,b)=\begin{cases} a+1&\text{if }b\text{ is odd} \\ @@ -36,6 +37,7 @@ Cases: | Colors: tex: \blue{a}\color{#0f0}{b}\color{red}{c} nolatex: different syntax and different scope +ColorSpacing: \color{red}{\displaystyle \int x} + 1 DashesAndQuotes: \text{``a'' b---c -- d----`e'-{-}-f}--``x'' DeepFontSizing: tex: | @@ -82,6 +84,7 @@ MathRm: \mathrm{Ax2k\breve{a}\omega\Omega\imath+\KaTeX} MathSf: \mathsf{Ax2k\breve{a}\omega\Omega\imath+\KaTeX} MathScr: \mathscr{Ax2k\breve{a}\omega\Omega\imath+\KaTeX} MathTt: \mathtt{Ax2k\breve{a}\omega\Omega\imath+\KaTeX} +NegativeSpaceBetweenRel: A =\!= B NestedFractions: | \dfrac{\frac{a}{b}}{\frac{c}{d}}\dfrac{\dfrac{a}{b}} {\dfrac{c}{d}}\frac{\frac{a}{b}}{\frac{c}{d}}