diff --git a/src/buildHTML.js b/src/buildHTML.js index ddcbd19c..8149efd7 100644 --- a/src/buildHTML.js +++ b/src/buildHTML.js @@ -71,7 +71,7 @@ const spliceSpaces = function(children, i) { * is a real group (no atoms will be added on either side), as opposed to * a partial group (e.g. one created by \color). */ -const buildExpression = function(expression, options, isRealGroup) { +export const buildExpression = function(expression, options, isRealGroup) { // Parse expressions into `groups`. const groups = []; for (let i = 0; i < expression.length; i++) { @@ -255,7 +255,7 @@ const makeNullDelimiter = function(options, classes) { * This is a map of group types to the function used to handle that type. * Simpler types come at the beginning, while complicated types come afterwards. */ -const groupTypes = {}; +export const groupTypes = {}; groupTypes.mathord = function(group, options) { return buildCommon.makeOrd(group, options, "mathord"); @@ -1723,46 +1723,6 @@ groupTypes.xArrow = function(group, options) { return makeSpan(["mrel", "x-arrow"], [vlist], options); }; -groupTypes.phantom = function(group, options) { - const elements = buildExpression( - group.value.value, - options.withPhantom(), - false - ); - - // \phantom isn't supposed to affect the elements it contains. - // See "color" for more details. - return new buildCommon.makeFragment(elements); -}; - -groupTypes.hphantom = function(group, options) { - let node = makeSpan( - [], [buildGroup(group.value.body, options.withPhantom())]); - node.height = 0; - node.depth = 0; - if (node.children) { - for (let i = 0; i < node.children.length; i++) { - node.children[i].height = 0; - node.children[i].depth = 0; - } - } - - // See smash for comment re: use of makeVList - node = buildCommon.makeVList([ - {type: "elem", elem: node}, - ], "firstBaseline", null, options); - - return node; -}; - -groupTypes.vphantom = function(group, options) { - const inner = makeSpan( - ["inner"], [buildGroup(group.value.body, options.withPhantom())]); - const fix = makeSpan(["fix"], []); - return makeSpan( - ["mord", "rlap"], [inner, fix], options); -}; - groupTypes.mclass = function(group, options) { const elements = buildExpression(group.value.value, options, true); @@ -1774,7 +1734,7 @@ groupTypes.mclass = function(group, options) { * function for it. It also handles the interaction of size and style changes * between parents and children. */ -const buildGroup = function(group, options, baseOptions) { +export const buildGroup = function(group, options, baseOptions) { if (!group) { return makeSpan(); } @@ -1807,7 +1767,7 @@ const buildGroup = function(group, options, baseOptions) { * Take an entire parse tree, and build it into an appropriate set of HTML * nodes. */ -const buildHTML = function(tree, options) { +export default function buildHTML(tree, options) { // buildExpression is destructive, so we need to make a clone // of the incoming tree so that it isn't accidentally changed tree = JSON.parse(JSON.stringify(tree)); @@ -1835,6 +1795,4 @@ const buildHTML = function(tree, options) { htmlNode.setAttribute("aria-hidden", "true"); return htmlNode; -}; - -module.exports = buildHTML; +} diff --git a/src/buildMathML.js b/src/buildMathML.js index 79e4aa78..0205ad21 100644 --- a/src/buildMathML.js +++ b/src/buildMathML.js @@ -60,7 +60,7 @@ const getVariant = function(group, options) { * Functions for handling the different types of groups found in the parse * tree. Each function should take a parse group and return a MathML node. */ -const groupTypes = {}; +export const groupTypes = {}; const defaultVariant = { "mi": "italic", @@ -650,25 +650,6 @@ groupTypes.smash = function(group, options) { return node; }; -groupTypes.phantom = function(group, options) { - const inner = buildExpression(group.value.value, options); - return new mathMLTree.MathNode("mphantom", inner); -}; - -groupTypes.hphantom = function(group, options) { - const inner = buildExpression(group.value.value, options); - const node = new mathMLTree.MathNode("mphantom", inner); - node.setAttribute("height", "0px"); - return node; -}; - -groupTypes.vphantom = function(group, options) { - const inner = buildExpression(group.value.value, options); - const node = new mathMLTree.MathNode("mphantom", inner); - node.setAttribute("width", "0px"); - return node; -}; - groupTypes.mclass = function(group, options) { const inner = buildExpression(group.value.value, options); return new mathMLTree.MathNode("mstyle", inner); @@ -679,7 +660,7 @@ groupTypes.mclass = function(group, options) { * MathML nodes. A little simpler than the HTML version because we don't do any * previous-node handling. */ -const buildExpression = function(expression, options) { +export const buildExpression = function(expression, options) { const groups = []; for (let i = 0; i < expression.length; i++) { const group = expression[i]; @@ -695,8 +676,9 @@ const buildExpression = function(expression, options) { * Takes a group from the parser and calls the appropriate groupTypes function * on it to produce a MathML node. */ -// TODO(kevinb): determine if removeUnnecessaryRow should always be true -const buildGroup = function(group, options, removeUnnecessaryRow = false) { +export const buildGroup = function( + group, options, removeUnnecessaryRow = false, +) { if (!group) { return new mathMLTree.MathNode("mrow"); } @@ -724,7 +706,7 @@ const buildGroup = function(group, options, removeUnnecessaryRow = false) { * Note that we actually return a domTree element with a `` inside it so * we can do appropriate styling. */ -const buildMathML = function(tree, texExpression, options) { +export default function buildMathML(tree, texExpression, options) { const expression = buildExpression(tree, options); // Wrap up the expression in an mrow so it is presented in the semantics @@ -744,6 +726,4 @@ const buildMathML = function(tree, texExpression, options) { // You can't style nodes, so we wrap the node in a span. return makeSpan(["katex-mathml"], [math]); -}; - -module.exports = buildMathML; +} diff --git a/src/defineFunction.js b/src/defineFunction.js new file mode 100644 index 00000000..1b78bcbe --- /dev/null +++ b/src/defineFunction.js @@ -0,0 +1,123 @@ +import functions from "./functions"; +import {groupTypes as htmlGroupTypes} from "./buildHTML"; +import {groupTypes as mathmlGroupTypes} from "./buildMathML"; + +/* This file contains a list of functions that we parse, identified by + * the calls to defineFunction. + * + * The first argument to defineFunction is a single name or a list of names. + * All functions named in such a list will share a single implementation. + * + * Each declared function can have associated properties, which + * include the following: + * + * - numArgs: The number of arguments the function takes. + * If this is the only property, it can be passed as a number + * instead of an element of a properties object. + * - argTypes: (optional) An array corresponding to each argument of the + * function, giving the type of argument that should be parsed. Its + * length should be equal to `numArgs + numOptionalArgs`. Valid + * types: + * - "size": A size-like thing, such as "1em" or "5ex" + * - "color": An html color, like "#abc" or "blue" + * - "original": The same type as the environment that the + * function being parsed is in (e.g. used for the + * bodies of functions like \textcolor where the + * first argument is special and the second + * argument is parsed normally) + * Other possible types (probably shouldn't be used) + * - "text": Text-like (e.g. \text) + * - "math": Normal math + * If undefined, this will be treated as an appropriate length + * array of "original" strings + * - greediness: (optional) The greediness of the function to use ungrouped + * arguments. + * + * E.g. if you have an expression + * \sqrt \frac 1 2 + * since \frac has greediness=2 vs \sqrt's greediness=1, \frac + * will use the two arguments '1' and '2' as its two arguments, + * then that whole function will be used as the argument to + * \sqrt. On the other hand, the expressions + * \frac \frac 1 2 3 + * and + * \frac \sqrt 1 2 + * will fail because \frac and \frac have equal greediness + * and \sqrt has a lower greediness than \frac respectively. To + * make these parse, we would have to change them to: + * \frac {\frac 1 2} 3 + * and + * \frac {\sqrt 1} 2 + * + * The default value is `1` + * - allowedInText: (optional) Whether or not the function is allowed inside + * text mode (default false) + * - numOptionalArgs: (optional) The number of optional arguments the function + * should parse. If the optional arguments aren't found, + * `null` will be passed to the handler in their place. + * (default 0) + * - infix: (optional) Must be true if the function is an infix operator. + * + * The last argument is that implementation, the handler for the function(s). + * It is called to handle these functions and their arguments. + * It receives two arguments: + * - context contains information and references provided by the parser + * - args is an array of arguments obtained from TeX input + * The context contains the following properties: + * - funcName: the text (i.e. name) of the function, including \ + * - parser: the parser object + * - lexer: the lexer object + * - positions: the positions in the overall string of the function + * and the arguments. + * The latter three should only be used to produce error messages. + * + * The function should return an object with the following keys: + * - type: The type of element that this is. This is then used in + * buildHTML/buildMathML to determine which function + * should be called to build this node into a DOM node + * Any other data can be added to the object, which will be passed + * in to the function in buildHTML/buildMathML as `group.value`. + */ + +export default function defineFunction( + names, props, handler, type, htmlBuilder, mathmlBuilder +) { + if (typeof names === "string") { + names = [names]; + } + if (typeof props === "number") { + props = { numArgs: props }; + } + // Set default values of functions + const data = { + numArgs: props.numArgs, + argTypes: props.argTypes, + greediness: (props.greediness === undefined) ? 1 : props.greediness, + allowedInText: !!props.allowedInText, + allowedInMath: props.allowedInMath, + numOptionalArgs: props.numOptionalArgs || 0, + infix: !!props.infix, + handler: handler, + }; + for (let i = 0; i < names.length; ++i) { + functions[names[i]] = data; + } + if (type) { + if (htmlBuilder) { + htmlGroupTypes[type] = htmlBuilder; + } + if (mathmlBuilder) { + mathmlGroupTypes[type] = mathmlBuilder; + } + } +} + +// Since the corresponding buildHTML/buildMathML function expects a +// list of elements, we normalize for different kinds of arguments +export const ordargument = function(arg) { + if (arg.type === "ordgroup") { + return arg.value; + } else { + return [arg]; + } +}; diff --git a/src/functions.js b/src/functions.js index 5db624c2..ee3b1eaf 100644 --- a/src/functions.js +++ b/src/functions.js @@ -2,115 +2,7 @@ import utils from "./utils"; import ParseError from "./ParseError"; import ParseNode from "./ParseNode"; -/* This file contains a list of functions that we parse, identified by - * the calls to defineFunction. - * - * The first argument to defineFunction is a single name or a list of names. - * All functions named in such a list will share a single implementation. - * - * Each declared function can have associated properties, which - * include the following: - * - * - numArgs: The number of arguments the function takes. - * If this is the only property, it can be passed as a number - * instead of an element of a properties object. - * - argTypes: (optional) An array corresponding to each argument of the - * function, giving the type of argument that should be parsed. Its - * length should be equal to `numArgs + numOptionalArgs`. Valid - * types: - * - "size": A size-like thing, such as "1em" or "5ex" - * - "color": An html color, like "#abc" or "blue" - * - "original": The same type as the environment that the - * function being parsed is in (e.g. used for the - * bodies of functions like \textcolor where the - * first argument is special and the second - * argument is parsed normally) - * Other possible types (probably shouldn't be used) - * - "text": Text-like (e.g. \text) - * - "math": Normal math - * If undefined, this will be treated as an appropriate length - * array of "original" strings - * - greediness: (optional) The greediness of the function to use ungrouped - * arguments. - * - * E.g. if you have an expression - * \sqrt \frac 1 2 - * since \frac has greediness=2 vs \sqrt's greediness=1, \frac - * will use the two arguments '1' and '2' as its two arguments, - * then that whole function will be used as the argument to - * \sqrt. On the other hand, the expressions - * \frac \frac 1 2 3 - * and - * \frac \sqrt 1 2 - * will fail because \frac and \frac have equal greediness - * and \sqrt has a lower greediness than \frac respectively. To - * make these parse, we would have to change them to: - * \frac {\frac 1 2} 3 - * and - * \frac {\sqrt 1} 2 - * - * The default value is `1` - * - allowedInText: (optional) Whether or not the function is allowed inside - * text mode (default false) - * - numOptionalArgs: (optional) The number of optional arguments the function - * should parse. If the optional arguments aren't found, - * `null` will be passed to the handler in their place. - * (default 0) - * - infix: (optional) Must be true if the function is an infix operator. - * - * The last argument is that implementation, the handler for the function(s). - * It is called to handle these functions and their arguments. - * It receives two arguments: - * - context contains information and references provided by the parser - * - args is an array of arguments obtained from TeX input - * The context contains the following properties: - * - funcName: the text (i.e. name) of the function, including \ - * - parser: the parser object - * - lexer: the lexer object - * - positions: the positions in the overall string of the function - * and the arguments. - * The latter three should only be used to produce error messages. - * - * The function should return an object with the following keys: - * - type: The type of element that this is. This is then used in - * buildHTML/buildMathML to determine which function - * should be called to build this node into a DOM node - * Any other data can be added to the object, which will be passed - * in to the function in buildHTML/buildMathML as `group.value`. - */ - -function defineFunction(names, props, handler) { - if (typeof names === "string") { - names = [names]; - } - if (typeof props === "number") { - props = { numArgs: props }; - } - // Set default values of functions - const data = { - numArgs: props.numArgs, - argTypes: props.argTypes, - greediness: (props.greediness === undefined) ? 1 : props.greediness, - allowedInText: !!props.allowedInText, - allowedInMath: props.allowedInMath, - numOptionalArgs: props.numOptionalArgs || 0, - infix: !!props.infix, - handler: handler, - }; - for (let i = 0; i < names.length; ++i) { - module.exports[names[i]] = data; - } -} - -// Since the corresponding buildHTML/buildMathML function expects a -// list of elements, we normalize for different kinds of arguments -const ordargument = function(arg) { - if (arg.type === "ordgroup") { - return arg.value; - } else { - return [arg]; - } -}; +import defineFunction, {ordargument} from "./defineFunction"; // A normal square root defineFunction("\\sqrt", { @@ -234,16 +126,7 @@ defineFunction("\\KaTeX", { }; }); -defineFunction(["\\phantom", "\\hphantom", "\\vphantom"], { - numArgs: 1, -}, function(context, args) { - const body = args[0]; - return { - type: context.funcName.slice(1), - value: ordargument(body), - body: body, - }; -}); +import "./functions/phantom"; // Math class commands except \mathop defineFunction([ diff --git a/src/functions/phantom.js b/src/functions/phantom.js new file mode 100644 index 00000000..7b6623f0 --- /dev/null +++ b/src/functions/phantom.js @@ -0,0 +1,108 @@ +import defineFunction, {ordargument} from "../defineFunction"; +import buildCommon from "../buildCommon"; +import mathMLTree from "../mathMLTree"; + +import * as html from "../buildHTML"; +import * as mml from "../buildMathML"; + +defineFunction( + "\\phantom", + { + numArgs: 1, + }, + (context, args) => { + const body = args[0]; + return { + type: "phantom", + value: ordargument(body), + }; + }, + "phantom", + (group, options) => { + const elements = html.buildExpression( + group.value.value, + options.withPhantom(), + false + ); + + // \phantom isn't supposed to affect the elements it contains. + // See "color" for more details. + return new buildCommon.makeFragment(elements); + }, + (group, options) => { + const inner = mml.buildExpression(group.value.value, options); + return new mathMLTree.MathNode("mphantom", inner); + }, +); + +defineFunction( + "\\hphantom", + { + numArgs: 1, + }, + (context, args) => { + const body = args[0]; + return { + type: "hphantom", + value: ordargument(body), + body: body, + }; + }, + "hphantom", + (group, options) => { + let node = buildCommon.makeSpan( + [], [html.buildGroup(group.value.body, options.withPhantom())]); + node.height = 0; + node.depth = 0; + if (node.children) { + for (let i = 0; i < node.children.length; i++) { + node.children[i].height = 0; + node.children[i].depth = 0; + } + } + + // See smash for comment re: use of makeVList + node = buildCommon.makeVList([ + {type: "elem", elem: node}, + ], "firstBaseline", null, options); + + return node; + }, + (group, options) => { + const inner = mml.buildExpression(group.value.value, options); + const node = new mathMLTree.MathNode("mphantom", inner); + node.setAttribute("height", "0px"); + return node; + }, +); + +defineFunction( + "\\vphantom", + { + numArgs: 1, + }, + (context, args) => { + const body = args[0]; + return { + type: "vphantom", + value: ordargument(body), + body: body, + }; + }, + "vphantom", + (group, options) => { + const inner = buildCommon.makeSpan( + ["inner"], + [html.buildGroup(group.value.body, options.withPhantom())]); + const fix = buildCommon.makeSpan(["fix"], []); + return buildCommon.makeSpan( + ["mord", "rlap"], [inner, fix], options); + }, + (group, options) => { + const inner = mml.buildExpression(group.value.value, options); + const node = new mathMLTree.MathNode("mphantom", inner); + node.setAttribute("width", "0px"); + return node; + }, +); +