diff --git a/src/buildHTML.js b/src/buildHTML.js index 299b70e8..7e697e63 100644 --- a/src/buildHTML.js +++ b/src/buildHTML.js @@ -22,17 +22,13 @@ const makeSpan = buildCommon.makeSpan; // Binary atoms (first class `mbin`) change into ordinary atoms (`mord`) // depending on their surroundings. See TeXbook pg. 442-446, Rules 5 and 6, // and the text before Rule 19. -const isBin = function(node) { - return node && node.classes[0] === "mbin"; -}; - const 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]); + getTypeOfDomTree(node, "right")); } else { return isRealGroup; } @@ -40,7 +36,8 @@ const isBinLeftCanceller = function(node, isRealGroup) { const isBinRightCanceller = function(node, isRealGroup) { if (node) { - return utils.contains(["mrel", "mclose", "mpunct"], node.classes[0]); + return utils.contains(["mrel", "mclose", "mpunct"], + getTypeOfDomTree(node, "left")); } else { return isRealGroup; } @@ -58,9 +55,11 @@ const styleMap = { * 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). + * a partial group (e.g. one created by \color). `surrounding` is an array + * consisting type of nodes that will be added to the left and right. */ -export const buildExpression = function(expression, options, isRealGroup) { +export const buildExpression = function(expression, options, isRealGroup, + surrounding = [null, null]) { // Parse expressions into `groups`. const rawGroups = []; for (let i = 0; i < expression.length; i++) { @@ -75,18 +74,27 @@ export const buildExpression = function(expression, options, isRealGroup) { // At this point `rawGroups` consists entirely of `symbolNode`s and `span`s. // Ignore explicit spaces (e.g., \;, \,) when determining what implicit - // spacing should go between atoms of different classes. - const nonSpaces = - rawGroups.filter(group => group && group.classes[0] !== "mspace"); + // spacing should go between atoms of different classes, and add dummy + // spans for determining spacings between surrounding atoms + const nonSpaces = [ + surrounding[0] && makeSpan([surrounding[0]], [], options), + ...rawGroups.filter(group => group && group.classes[0] !== "mspace"), + surrounding[1] && makeSpan([surrounding[1]], [], options), + ]; // Before determining what spaces to insert, perform bin cancellation. // Binary operators change to ordinary symbols in some contexts. - for (let i = 0; i < nonSpaces.length; i++) { - if (isBin(nonSpaces[i])) { - if (isBinLeftCanceller(nonSpaces[i - 1], isRealGroup) - || isBinRightCanceller(nonSpaces[i + 1], isRealGroup)) { - nonSpaces[i].classes[0] = "mord"; - } + for (let i = 1; i < nonSpaces.length - 1; i++) { + const left = getOutermostNode(nonSpaces[i], "left"); + if (left.classes[0] === "mbin" && + isBinLeftCanceller(nonSpaces[i - 1], isRealGroup)) { + left.classes[0] = "mord"; + } + + const right = getOutermostNode(nonSpaces[i], "right"); + if (right.classes[0] === "mbin" && + isBinRightCanceller(nonSpaces[i + 1], isRealGroup)) { + right.classes[0] = "mord"; } } @@ -99,6 +107,13 @@ export const buildExpression = function(expression, options, isRealGroup) { // lookup what implicit space should be placed between those atoms and // add it to groups. if (rawGroups[i].classes[0] !== "mspace" && j < nonSpaces.length - 1) { + // if current non-space node is left dummy span, add a glue before + // first real non-space node + if (j === 0) { + groups.pop(); + i--; + } + // Get the type of the current non-space node. If it's a document // fragment, get the type of the rightmost node in the fragment. const left = getTypeOfDomTree(nonSpaces[j], "right"); @@ -150,28 +165,37 @@ export const buildExpression = function(expression, options, isRealGroup) { return groups; }; -// Return math atom class (mclass) of a domTree. -export const getTypeOfDomTree = function(node, side = "right") { +// Return the outermost node of a domTree. +const getOutermostNode = function(node, side = "right") { if (node instanceof domTree.documentFragment || node instanceof domTree.anchor) { if (node.children.length) { if (side === "right") { - return getTypeOfDomTree( + return getOutermostNode( node.children[node.children.length - 1]); } else if (side === "left") { - return getTypeOfDomTree( + return getOutermostNode( node.children[0]); } } - } else { - // This makes a lot of assumptions as to where the type of atom - // appears. We should do a better job of enforcing this. - if (utils.contains([ - "mord", "mop", "mbin", "mrel", "mopen", "mclose", - "mpunct", "minner", - ], node.classes[0])) { - return node.classes[0]; - } + } + return node; +}; + +// Return math atom class (mclass) of a domTree. +export const getTypeOfDomTree = function(node, side = "right") { + if (!node) { + return null; + } + + node = getOutermostNode(node, side); + // This makes a lot of assumptions as to where the type of atom + // appears. We should do a better job of enforcing this. + if (utils.contains([ + "mord", "mop", "mbin", "mrel", "mopen", "mclose", + "mpunct", "minner", + ], node.classes[0])) { + return node.classes[0]; } return null; }; @@ -181,14 +205,8 @@ export const getTypeOfDomTree = function(node, side = "right") { // leftmost node in the fragment. // 'mtight' indicates that the node is script or scriptscript style. export const isLeftTight = function(node) { - if (node instanceof domTree.documentFragment) { - if (node.children.length) { - return isLeftTight(node.children[0]); - } - } else { - return utils.contains(node.classes, "mtight"); - } - return false; + node = getOutermostNode(node, "left"); + return utils.contains(node.classes, "mtight"); }; /** diff --git a/src/functions/delimsizing.js b/src/functions/delimsizing.js index 1d2ae561..5920a2e7 100644 --- a/src/functions/delimsizing.js +++ b/src/functions/delimsizing.js @@ -5,8 +5,6 @@ import delimiter from "../delimiter"; import mathMLTree from "../mathMLTree"; import ParseError from "../ParseError"; import utils from "../utils"; -import { calculateSize } from "../units"; -import { spacings, tightSpacings } from "../spacingData"; import * as html from "../buildHTML"; import * as mml from "../buildMathML"; @@ -161,7 +159,8 @@ defineFunction({ }, htmlBuilder: (group, options) => { // Build the inner expression - const inner = html.buildExpression(group.value.body, options, true); + const inner = html.buildExpression(group.value.body, options, true, + [null, "mclose"]); let innerHeight = 0; let innerDepth = 0; @@ -210,18 +209,6 @@ defineFunction({ } } - const lastChildType = html.getTypeOfDomTree(inner[inner.length - 1]); - const activeSpacings = options.style.isTight() ? tightSpacings : spacings; - - if (lastChildType && activeSpacings[lastChildType]["mclose"]) { - const glue = - buildCommon.makeSpan(["mord", "rule"], [], options); - const dimension = - calculateSize(activeSpacings[lastChildType]["mclose"], options); - glue.style.marginRight = `${dimension}em`; - inner.push(glue); - } - let rightDelim; // Same for the right delimiter if (group.value.right === ".") { diff --git a/src/functions/href.js b/src/functions/href.js index bd529760..46b03764 100644 --- a/src/functions/href.js +++ b/src/functions/href.js @@ -31,40 +31,7 @@ defineFunction({ const href = group.value.href; - /** - * Determining class for anchors. - * 1. if it has the only element, use its class; - * 2. if it has more than two elements, and the classes - * of its first and last elements coincide, then use it; - * 3. otherwise, we will inject an empty s at both ends, - * with the same classes of both ends of elements, with the - * first span having the same class as the first element of body, - * and the second one the same as the last. - */ - - let classes = []; // Default behaviour for Case 3. - let first; // mathtype of the first child - let last; // mathtype of the last child - // Invariants: both first and last must be non-null if classes is null. - if (elements.length === 1) { // Case 1 - classes = elements[0].classes; - } else if (elements.length >= 2) { - first = html.getTypeOfDomTree(elements[0]) || 'mord'; - last = html.getTypeOfDomTree(elements[elements.length - 1]) || 'mord'; - if (first === last) { // Case 2 : type of both ends coincides - classes = [first]; - } else { // Case 3: both ends have different types. - // TODO(kevinb): figure out a better way to communicate this - // information to buildHTML.js#buildExpression. - const anc = buildCommon.makeAnchor(href, [], elements, options); - return new buildCommon.makeFragment([ - new buildCommon.makeSpan([first], [], options), - anc, - new buildCommon.makeSpan([last], [], options), - ]); - } - } - return new buildCommon.makeAnchor(href, classes, elements, options); + return new buildCommon.makeAnchor(href, [], elements, options); }, mathmlBuilder: (group, options) => { const inner = mml.buildExpression(group.value.body, options);