From a99c7c9e0f67fbc76f0e3d2c7ace24cb02b9d754 Mon Sep 17 00:00:00 2001 From: Kevin Barabash Date: Mon, 17 Jul 2017 23:45:28 -0700 Subject: [PATCH] extract delimsizing functions into their own file --- src/buildHTML.js | 103 +------------- src/buildMathML.js | 58 +------- src/functions.js | 93 +------------ src/functions/delimsizing.js | 263 +++++++++++++++++++++++++++++++++++ 4 files changed, 267 insertions(+), 250 deletions(-) create mode 100644 src/functions/delimsizing.js diff --git a/src/buildHTML.js b/src/buildHTML.js index 8149efd7..e6a83a59 100644 --- a/src/buildHTML.js +++ b/src/buildHTML.js @@ -52,7 +52,7 @@ const isBinRightCanceller = function(node, isRealGroup) { * the spliced-out array. Returns null if `children[i]` does not exist or is not * a space. */ -const spliceSpaces = function(children, i) { +export const spliceSpaces = function(children, i) { let j = i; while (j < children.length && isSpace(children[j])) { j++; @@ -246,7 +246,7 @@ const isCharacterBox = function(group) { baseElem.type === "punct"; }; -const makeNullDelimiter = function(options, classes) { +export const makeNullDelimiter = function(options, classes) { const moreClasses = ["nulldelimiter"].concat(options.baseSizingClasses()); return makeSpan(classes.concat(moreClasses)); }; @@ -1259,105 +1259,6 @@ groupTypes.font = function(group, options) { return buildGroup(group.value.body, options.withFont(font)); }; -groupTypes.delimsizing = function(group, options) { - const delim = group.value.value; - - if (delim === ".") { - // Empty delimiters still count as elements, even though they don't - // show anything. - return makeSpan([group.value.mclass]); - } - - // Use delimiter.sizedDelim to generate the delimiter. - return delimiter.sizedDelim( - delim, group.value.size, options, group.mode, - [group.value.mclass]); -}; - -groupTypes.leftright = function(group, options) { - // Build the inner expression - const inner = buildExpression(group.value.body, options, true); - - let innerHeight = 0; - let innerDepth = 0; - let hadMiddle = false; - - // Calculate its height and depth - for (let i = 0; i < inner.length; i++) { - if (inner[i].isMiddle) { - hadMiddle = true; - } else { - innerHeight = Math.max(inner[i].height, innerHeight); - innerDepth = Math.max(inner[i].depth, innerDepth); - } - } - - // The size of delimiters is the same, regardless of what style we are - // in. Thus, to correctly calculate the size of delimiter we need around - // a group, we scale down the inner size based on the size. - innerHeight *= options.sizeMultiplier; - innerDepth *= options.sizeMultiplier; - - let leftDelim; - if (group.value.left === ".") { - // Empty delimiters in \left and \right make null delimiter spaces. - 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, ["mopen"]); - } - // Add it to the beginning of the expression - inner.unshift(leftDelim); - - // Handle middle delimiters - if (hadMiddle) { - for (let i = 1; i < inner.length; i++) { - const middleDelim = inner[i]; - if (middleDelim.isMiddle) { - // Apply the options that were active when \middle was called - inner[i] = delimiter.leftRightDelim( - middleDelim.isMiddle.value, innerHeight, innerDepth, - middleDelim.isMiddle.options, group.mode, []); - // Add back spaces shifted into the delimiter - const spaces = spliceSpaces(middleDelim.children, 0); - if (spaces) { - buildCommon.prependChildren(inner[i], spaces); - } - } - } - } - - let rightDelim; - // Same for the right delimiter - if (group.value.right === ".") { - rightDelim = makeNullDelimiter(options, ["mclose"]); - } else { - rightDelim = delimiter.leftRightDelim( - group.value.right, innerHeight, innerDepth, options, - group.mode, ["mclose"]); - } - // Add it to the end of the expression. - inner.push(rightDelim); - - return makeSpan(["minner"], inner, options); -}; - -groupTypes.middle = function(group, options) { - let middleDelim; - if (group.value.value === ".") { - middleDelim = makeNullDelimiter(options, []); - } else { - middleDelim = delimiter.sizedDelim( - group.value.value, 1, options, - group.mode, []); - middleDelim.isMiddle = {value: group.value.value, options: options}; - } - return middleDelim; -}; - groupTypes.rule = function(group, options) { // Make an empty span for the rule const rule = makeSpan(["mord", "rule"], [], options); diff --git a/src/buildMathML.js b/src/buildMathML.js index 0205ad21..588774f7 100644 --- a/src/buildMathML.js +++ b/src/buildMathML.js @@ -17,7 +17,7 @@ import stretchy from "./stretchy"; * Takes a symbol and converts it into a MathML text node after performing * optional replacement from symbols.js. */ -const makeText = function(text, mode) { +export const makeText = function(text, mode) { if (symbols[mode][text] && symbols[mode][text].replace) { text = symbols[mode][text].replace; } @@ -315,39 +315,6 @@ groupTypes.sqrt = function(group, options) { return node; }; -groupTypes.leftright = function(group, options) { - const inner = buildExpression(group.value.body, options); - - if (group.value.left !== ".") { - const leftNode = new mathMLTree.MathNode( - "mo", [makeText(group.value.left, group.mode)]); - - leftNode.setAttribute("fence", "true"); - - inner.unshift(leftNode); - } - - if (group.value.right !== ".") { - const rightNode = new mathMLTree.MathNode( - "mo", [makeText(group.value.right, group.mode)]); - - rightNode.setAttribute("fence", "true"); - - inner.push(rightNode); - } - - const outerNode = new mathMLTree.MathNode("mrow", inner); - - return outerNode; -}; - -groupTypes.middle = function(group, options) { - const middleNode = new mathMLTree.MathNode( - "mo", [makeText(group.value.middle, group.mode)]); - middleNode.setAttribute("fence", "true"); - return middleNode; -}; - groupTypes.accent = function(group, options) { let accentNode; if (group.value.isStretchy) { @@ -445,29 +412,6 @@ groupTypes.font = function(group, options) { return buildGroup(group.value.body, options.withFont(font)); }; -groupTypes.delimsizing = function(group) { - const children = []; - - if (group.value.value !== ".") { - children.push(makeText(group.value.value, group.mode)); - } - - const node = new mathMLTree.MathNode("mo", children); - - if (group.value.mclass === "mopen" || - group.value.mclass === "mclose") { - // Only some of the delimsizing functions act as fences, and they - // return "mopen" or "mclose" mclass. - node.setAttribute("fence", "true"); - } else { - // Explicitly disable fencing if it's not a fence, to override the - // defaults. - node.setAttribute("fence", "false"); - } - - return node; -}; - groupTypes.styling = function(group, options) { // Figure out what style we're changing to. // TODO(kevinb): dedupe this with buildHTML.js diff --git a/src/functions.js b/src/functions.js index ee3b1eaf..2b42a27f 100644 --- a/src/functions.js +++ b/src/functions.js @@ -193,41 +193,6 @@ defineFunction(["\\pod", "\\pmod", "\\mod"], { }; }); -// Extra data needed for the delimiter handler down below -const delimiterSizes = { - "\\bigl" : {mclass: "mopen", size: 1}, - "\\Bigl" : {mclass: "mopen", size: 2}, - "\\biggl": {mclass: "mopen", size: 3}, - "\\Biggl": {mclass: "mopen", size: 4}, - "\\bigr" : {mclass: "mclose", size: 1}, - "\\Bigr" : {mclass: "mclose", size: 2}, - "\\biggr": {mclass: "mclose", size: 3}, - "\\Biggr": {mclass: "mclose", size: 4}, - "\\bigm" : {mclass: "mrel", size: 1}, - "\\Bigm" : {mclass: "mrel", size: 2}, - "\\biggm": {mclass: "mrel", size: 3}, - "\\Biggm": {mclass: "mrel", size: 4}, - "\\big" : {mclass: "mord", size: 1}, - "\\Big" : {mclass: "mord", size: 2}, - "\\bigg" : {mclass: "mord", size: 3}, - "\\Bigg" : {mclass: "mord", size: 4}, -}; - -const delimiters = [ - "(", ")", "[", "\\lbrack", "]", "\\rbrack", - "\\{", "\\lbrace", "\\}", "\\rbrace", - "\\lfloor", "\\rfloor", "\\lceil", "\\rceil", - "<", ">", "\\langle", "\\rangle", "\\lt", "\\gt", - "\\lvert", "\\rvert", "\\lVert", "\\rVert", - "\\lgroup", "\\rgroup", "\\lmoustache", "\\rmoustache", - "/", "\\backslash", - "|", "\\vert", "\\|", "\\Vert", - "\\uparrow", "\\Uparrow", - "\\downarrow", "\\Downarrow", - "\\updownarrow", "\\Updownarrow", - ".", -]; - const fontAliases = { "\\Bbb": "\\mathbb", "\\bold": "\\mathbf", @@ -453,63 +418,7 @@ defineFunction("\\smash", { }; }); -// Delimiter functions -const checkDelimiter = function(delim, context) { - if (utils.contains(delimiters, delim.value)) { - return delim; - } else { - throw new ParseError( - "Invalid delimiter: '" + delim.value + "' after '" + - context.funcName + "'", delim); - } -}; - -defineFunction([ - "\\bigl", "\\Bigl", "\\biggl", "\\Biggl", - "\\bigr", "\\Bigr", "\\biggr", "\\Biggr", - "\\bigm", "\\Bigm", "\\biggm", "\\Biggm", - "\\big", "\\Big", "\\bigg", "\\Bigg", -], { - numArgs: 1, -}, function(context, args) { - const delim = checkDelimiter(args[0], context); - - return { - type: "delimsizing", - size: delimiterSizes[context.funcName].size, - mclass: delimiterSizes[context.funcName].mclass, - value: delim.value, - }; -}); - -defineFunction([ - "\\left", "\\right", -], { - numArgs: 1, -}, function(context, args) { - const delim = checkDelimiter(args[0], context); - - // \left and \right are caught somewhere in Parser.js, which is - // why this data doesn't match what is in buildHTML. - return { - type: "leftright", - value: delim.value, - }; -}); - -defineFunction("\\middle", { - numArgs: 1, -}, function(context, args) { - const delim = checkDelimiter(args[0], context); - if (!context.parser.leftrightDepth) { - throw new ParseError("\\middle without preceding \\left", delim); - } - - return { - type: "middle", - value: delim.value, - }; -}); +import "./functions/delimsizing"; // Sizing functions (handled in Parser.js explicitly, hence no handler) defineFunction([ diff --git a/src/functions/delimsizing.js b/src/functions/delimsizing.js new file mode 100644 index 00000000..4674d8ee --- /dev/null +++ b/src/functions/delimsizing.js @@ -0,0 +1,263 @@ +import buildCommon, {makeSpan} from "../buildCommon"; +import defineFunction from "../defineFunction"; +import delimiter from "../delimiter"; +import mathMLTree from "../mathMLTree"; +import ParseError from "../ParseError"; +import utils from "../utils"; + +import * as html from "../buildHTML"; +import * as mml from "../buildMathML"; + +// Extra data needed for the delimiter handler down below +const delimiterSizes = { + "\\bigl" : {mclass: "mopen", size: 1}, + "\\Bigl" : {mclass: "mopen", size: 2}, + "\\biggl": {mclass: "mopen", size: 3}, + "\\Biggl": {mclass: "mopen", size: 4}, + "\\bigr" : {mclass: "mclose", size: 1}, + "\\Bigr" : {mclass: "mclose", size: 2}, + "\\biggr": {mclass: "mclose", size: 3}, + "\\Biggr": {mclass: "mclose", size: 4}, + "\\bigm" : {mclass: "mrel", size: 1}, + "\\Bigm" : {mclass: "mrel", size: 2}, + "\\biggm": {mclass: "mrel", size: 3}, + "\\Biggm": {mclass: "mrel", size: 4}, + "\\big" : {mclass: "mord", size: 1}, + "\\Big" : {mclass: "mord", size: 2}, + "\\bigg" : {mclass: "mord", size: 3}, + "\\Bigg" : {mclass: "mord", size: 4}, +}; + +const delimiters = [ + "(", ")", "[", "\\lbrack", "]", "\\rbrack", + "\\{", "\\lbrace", "\\}", "\\rbrace", + "\\lfloor", "\\rfloor", "\\lceil", "\\rceil", + "<", ">", "\\langle", "\\rangle", "\\lt", "\\gt", + "\\lvert", "\\rvert", "\\lVert", "\\rVert", + "\\lgroup", "\\rgroup", "\\lmoustache", "\\rmoustache", + "/", "\\backslash", + "|", "\\vert", "\\|", "\\Vert", + "\\uparrow", "\\Uparrow", + "\\downarrow", "\\Downarrow", + "\\updownarrow", "\\Updownarrow", + ".", +]; + +// Delimiter functions +const checkDelimiter = function(delim, context) { + if (utils.contains(delimiters, delim.value)) { + return delim; + } else { + throw new ParseError( + "Invalid delimiter: '" + delim.value + "' after '" + + context.funcName + "'", delim); + } +}; + +defineFunction( + [ + "\\bigl", "\\Bigl", "\\biggl", "\\Biggl", + "\\bigr", "\\Bigr", "\\biggr", "\\Biggr", + "\\bigm", "\\Bigm", "\\biggm", "\\Biggm", + "\\big", "\\Big", "\\bigg", "\\Bigg", + ], + { + numArgs: 1, + }, + function(context, args) { + const delim = checkDelimiter(args[0], context); + + return { + type: "delimsizing", + size: delimiterSizes[context.funcName].size, + mclass: delimiterSizes[context.funcName].mclass, + value: delim.value, + }; + }, + "delimsizing", + function(group, options) { + const delim = group.value.value; + + if (delim === ".") { + // Empty delimiters still count as elements, even though they don't + // show anything. + return makeSpan([group.value.mclass]); + } + + // Use delimiter.sizedDelim to generate the delimiter. + return delimiter.sizedDelim( + delim, group.value.size, options, group.mode, + [group.value.mclass]); + }, + function(group) { + const children = []; + + if (group.value.value !== ".") { + children.push(mml.makeText(group.value.value, group.mode)); + } + + const node = new mathMLTree.MathNode("mo", children); + + if (group.value.mclass === "mopen" || + group.value.mclass === "mclose") { + // Only some of the delimsizing functions act as fences, and they + // return "mopen" or "mclose" mclass. + node.setAttribute("fence", "true"); + } else { + // Explicitly disable fencing if it's not a fence, to override the + // defaults. + node.setAttribute("fence", "false"); + } + + return node; + }, +); + +defineFunction( + [ + "\\left", "\\right", + ], { + numArgs: 1, + }, function(context, args) { + const delim = checkDelimiter(args[0], context); + + // \left and \right are caught somewhere in Parser.js, which is + // why this data doesn't match what is in buildHTML. + return { + type: "leftright", + value: delim.value, + }; + }, + "leftright", + function(group, options) { + // Build the inner expression + const inner = html.buildExpression(group.value.body, options, true); + + let innerHeight = 0; + let innerDepth = 0; + let hadMiddle = false; + + // Calculate its height and depth + for (let i = 0; i < inner.length; i++) { + if (inner[i].isMiddle) { + hadMiddle = true; + } else { + innerHeight = Math.max(inner[i].height, innerHeight); + innerDepth = Math.max(inner[i].depth, innerDepth); + } + } + + // The size of delimiters is the same, regardless of what style we are + // in. Thus, to correctly calculate the size of delimiter we need around + // a group, we scale down the inner size based on the size. + innerHeight *= options.sizeMultiplier; + innerDepth *= options.sizeMultiplier; + + let leftDelim; + if (group.value.left === ".") { + // Empty delimiters in \left and \right make null delimiter spaces. + leftDelim = html.makeNullDelimiter(options, ["mopen"]); + } else { + // Otherwise, use leftRightDelim to generate the correct sized + // delimiter. + leftDelim = delimiter.leftRightDelim( + group.value.left, innerHeight, innerDepth, options, + group.mode, ["mopen"]); + } + // Add it to the beginning of the expression + inner.unshift(leftDelim); + + // Handle middle delimiters + if (hadMiddle) { + for (let i = 1; i < inner.length; i++) { + const middleDelim = inner[i]; + if (middleDelim.isMiddle) { + // Apply the options that were active when \middle was called + inner[i] = delimiter.leftRightDelim( + middleDelim.isMiddle.value, innerHeight, innerDepth, + middleDelim.isMiddle.options, group.mode, []); + // Add back spaces shifted into the delimiter + const spaces = html.spliceSpaces(middleDelim.children, 0); + if (spaces) { + buildCommon.prependChildren(inner[i], spaces); + } + } + } + } + + let rightDelim; + // Same for the right delimiter + if (group.value.right === ".") { + rightDelim = html.makeNullDelimiter(options, ["mclose"]); + } else { + rightDelim = delimiter.leftRightDelim( + group.value.right, innerHeight, innerDepth, options, + group.mode, ["mclose"]); + } + // Add it to the end of the expression. + inner.push(rightDelim); + + return makeSpan(["minner"], inner, options); + }, + function(group, options) { + const inner = mml.buildExpression(group.value.body, options); + + if (group.value.left !== ".") { + const leftNode = new mathMLTree.MathNode( + "mo", [mml.makeText(group.value.left, group.mode)]); + + leftNode.setAttribute("fence", "true"); + + inner.unshift(leftNode); + } + + if (group.value.right !== ".") { + const rightNode = new mathMLTree.MathNode( + "mo", [mml.makeText(group.value.right, group.mode)]); + + rightNode.setAttribute("fence", "true"); + + inner.push(rightNode); + } + + const outerNode = new mathMLTree.MathNode("mrow", inner); + + return outerNode; + }, +); + +defineFunction( + "\\middle", + { + numArgs: 1, + }, function(context, args) { + const delim = checkDelimiter(args[0], context); + if (!context.parser.leftrightDepth) { + throw new ParseError("\\middle without preceding \\left", delim); + } + + return { + type: "middle", + value: delim.value, + }; + }, + "middle", + function(group, options) { + let middleDelim; + if (group.value.value === ".") { + middleDelim = html.makeNullDelimiter(options, []); + } else { + middleDelim = delimiter.sizedDelim( + group.value.value, 1, options, + group.mode, []); + middleDelim.isMiddle = {value: group.value.value, options: options}; + } + return middleDelim; + }, + function(group, options) { + const middleNode = new mathMLTree.MathNode( + "mo", [mml.makeText(group.value.middle, group.mode)]); + middleNode.setAttribute("fence", "true"); + return middleNode; + }, +);