diff --git a/src/buildHTML.js b/src/buildHTML.js index c1948441..1d60a509 100644 --- a/src/buildHTML.js +++ b/src/buildHTML.js @@ -305,28 +305,6 @@ groupTypes.ordgroup = function(group, options) { ); }; -groupTypes.text = function(group, options) { - const newOptions = options.withFont(group.value.font); - const inner = buildExpression(group.value.body, newOptions, true); - buildCommon.tryCombineChars(inner); - return makeSpan(["mord", "text"], - inner, newOptions); -}; - -groupTypes.color = function(group, options) { - const elements = buildExpression( - group.value.value, - options.withColor(group.value.color), - false - ); - - // \color isn't supposed to affect the type of the elements it contains. - // To accomplish this, we wrap the results in a fragment, so the inner - // elements will be able to directly interact with their neighbors. For - // example, `\color{red}{2 +} 3` has the same spacing as `2 + 3` - return new buildCommon.makeFragment(elements); -}; - groupTypes.supsub = function(group, options) { // Superscript and subscripts are handled in the TeXbook on page // 445-446, rules 18(a-f). diff --git a/src/buildMathML.js b/src/buildMathML.js index 57a4f330..a71a1433 100644 --- a/src/buildMathML.js +++ b/src/buildMathML.js @@ -158,45 +158,6 @@ groupTypes.ordgroup = function(group, options) { return node; }; -groupTypes.text = function(group, options) { - const body = group.value.body; - - // Convert each element of the body into MathML, and combine consecutive - // outputs into a single tag. In this way, we don't - // nest non-text items (e.g., $nested-math$) within an . - const inner = []; - let currentText = null; - for (let i = 0; i < body.length; i++) { - const group = buildGroup(body[i], options); - if (group.type === 'mtext' && currentText != null) { - Array.prototype.push.apply(currentText.children, group.children); - } else { - inner.push(group); - if (group.type === 'mtext') { - currentText = group; - } - } - } - - // If there is a single tag in the end (presumably ), - // just return it. Otherwise, wrap them in an . - if (inner.length === 1) { - return inner[0]; - } else { - return new mathMLTree.MathNode("mrow", inner); - } -}; - -groupTypes.color = function(group, options) { - const inner = buildExpression(group.value.value, options); - - const node = new mathMLTree.MathNode("mstyle", inner); - - node.setAttribute("mathcolor", group.value.color); - - return node; -}; - groupTypes.supsub = function(group, options) { // Is the inner group a relevant horizonal brace? let isBrace = false; diff --git a/src/functions.js b/src/functions.js index c42a225e..e5dc3756 100644 --- a/src/functions.js +++ b/src/functions.js @@ -40,45 +40,9 @@ defineFunction(["\\sqrt"], { }; }); -// Non-mathy text, possibly in a font -const textFunctionFonts = { - "\\text": undefined, "\\textrm": "mathrm", "\\textsf": "mathsf", - "\\texttt": "mathtt", "\\textnormal": "mathrm", "\\textbf": "mathbf", - "\\textit": "textit", -}; +import "./functions/color"; -defineFunction([ - "\\text", "\\textrm", "\\textsf", "\\texttt", "\\textnormal", - "\\textbf", "\\textit", -], { - numArgs: 1, - argTypes: ["text"], - greediness: 2, - allowedInText: true, -}, function(context, args) { - const body = args[0]; - return { - type: "text", - body: ordargument(body), - font: textFunctionFonts[context.funcName], - }; -}); - -// A two-argument custom color -defineFunction(["\\textcolor"], { - numArgs: 2, - allowedInText: true, - greediness: 3, - argTypes: ["color", "original"], -}, function(context, args) { - const color = args[0]; - const body = args[1]; - return { - type: "color", - color: color.value, - value: ordargument(body), - }; -}); +import "./functions/text"; // \color is handled in Parser.js's parseImplicitGroup defineFunction(["\\color"], { @@ -185,34 +149,6 @@ const fontAliases = { "\\frak": "\\mathfrak", }; -// Single-argument color functions -defineFunction([ - "\\blue", "\\orange", "\\pink", "\\red", - "\\green", "\\gray", "\\purple", - "\\blueA", "\\blueB", "\\blueC", "\\blueD", "\\blueE", - "\\tealA", "\\tealB", "\\tealC", "\\tealD", "\\tealE", - "\\greenA", "\\greenB", "\\greenC", "\\greenD", "\\greenE", - "\\goldA", "\\goldB", "\\goldC", "\\goldD", "\\goldE", - "\\redA", "\\redB", "\\redC", "\\redD", "\\redE", - "\\maroonA", "\\maroonB", "\\maroonC", "\\maroonD", "\\maroonE", - "\\purpleA", "\\purpleB", "\\purpleC", "\\purpleD", "\\purpleE", - "\\mintA", "\\mintB", "\\mintC", - "\\grayA", "\\grayB", "\\grayC", "\\grayD", "\\grayE", - "\\grayF", "\\grayG", "\\grayH", "\\grayI", - "\\kaBlue", "\\kaGreen", -], { - numArgs: 1, - allowedInText: true, - greediness: 3, -}, function(context, args) { - const body = args[0]; - return { - type: "color", - color: "katex-" + context.funcName.slice(1), - value: ordargument(body), - }; -}); - const singleCharIntegrals: {[string]: string} = { "\u222b": "\\int", "\u222c": "\\iint", diff --git a/src/functions/color.js b/src/functions/color.js new file mode 100644 index 00000000..4c04ce50 --- /dev/null +++ b/src/functions/color.js @@ -0,0 +1,88 @@ +// @flow +import defineFunction, {ordargument} from "../defineFunction"; +import buildCommon from "../buildCommon"; +import mathMLTree from "../mathMLTree"; + +import * as html from "../buildHTML"; +import * as mml from "../buildMathML"; + +const htmlBuilder = (group, options) => { + const elements = html.buildExpression( + group.value.value, + options.withColor(group.value.color), + false + ); + + // \color isn't supposed to affect the type of the elements it contains. + // To accomplish this, we wrap the results in a fragment, so the inner + // elements will be able to directly interact with their neighbors. For + // example, `\color{red}{2 +} 3` has the same spacing as `2 + 3` + return new buildCommon.makeFragment(elements); +}; + +const mathmlBuilder = (group, options) => { + const inner = mml.buildExpression(group.value.value, options); + + const node = new mathMLTree.MathNode("mstyle", inner); + + node.setAttribute("mathcolor", group.value.color); + + return node; +}; + +defineFunction({ + type: "color", + names: ["\\textcolor"], + props: { + numArgs: 2, + allowedInText: true, + greediness: 3, + argTypes: ["color", "original"], + }, + handler(context, args) { + const color = args[0]; + const body = args[1]; + return { + type: "color", + color: color.value, + value: ordargument(body), + }; + }, + htmlBuilder, + mathmlBuilder, +}); + +// TODO(kevinb): define these using macros +defineFunction({ + type: "color", + names: [ + "\\blue", "\\orange", "\\pink", "\\red", + "\\green", "\\gray", "\\purple", + "\\blueA", "\\blueB", "\\blueC", "\\blueD", "\\blueE", + "\\tealA", "\\tealB", "\\tealC", "\\tealD", "\\tealE", + "\\greenA", "\\greenB", "\\greenC", "\\greenD", "\\greenE", + "\\goldA", "\\goldB", "\\goldC", "\\goldD", "\\goldE", + "\\redA", "\\redB", "\\redC", "\\redD", "\\redE", + "\\maroonA", "\\maroonB", "\\maroonC", "\\maroonD", "\\maroonE", + "\\purpleA", "\\purpleB", "\\purpleC", "\\purpleD", "\\purpleE", + "\\mintA", "\\mintB", "\\mintC", + "\\grayA", "\\grayB", "\\grayC", "\\grayD", "\\grayE", + "\\grayF", "\\grayG", "\\grayH", "\\grayI", + "\\kaBlue", "\\kaGreen", + ], + props: { + numArgs: 1, + allowedInText: true, + greediness: 3, + }, + handler(context, args) { + const body = args[0]; + return { + type: "color", + color: "katex-" + context.funcName.slice(1), + value: ordargument(body), + }; + }, + htmlBuilder, + mathmlBuilder, +}); diff --git a/src/functions/text.js b/src/functions/text.js new file mode 100644 index 00000000..26b13100 --- /dev/null +++ b/src/functions/text.js @@ -0,0 +1,71 @@ +// @flow +import defineFunction, {ordargument} from "../defineFunction"; +import buildCommon from "../buildCommon"; +import mathMLTree from "../mathMLTree"; + +import * as html from "../buildHTML"; +import * as mml from "../buildMathML"; + +// Non-mathy text, possibly in a font +const textFunctionFonts = { + "\\text": undefined, "\\textrm": "mathrm", "\\textsf": "mathsf", + "\\texttt": "mathtt", "\\textnormal": "mathrm", "\\textbf": "mathbf", + "\\textit": "textit", +}; + +defineFunction({ + type: "text", + names: [ + "\\text", "\\textrm", "\\textsf", "\\texttt", "\\textnormal", + "\\textbf", "\\textit", + ], + props: { + numArgs: 1, + argTypes: ["text"], + greediness: 2, + allowedInText: true, + }, + handler(context, args) { + const body = args[0]; + return { + type: "text", + body: ordargument(body), + font: textFunctionFonts[context.funcName], + }; + }, + htmlBuilder(group, options) { + const newOptions = options.withFont(group.value.font); + const inner = html.buildExpression(group.value.body, newOptions, true); + buildCommon.tryCombineChars(inner); + return buildCommon.makeSpan(["mord", "text"], + inner, newOptions); + }, + mathmlBuilder(group, options) { + const body = group.value.body; + + // Convert each element of the body into MathML, and combine consecutive + // outputs into a single tag. In this way, we don't + // nest non-text items (e.g., $nested-math$) within an . + const inner = []; + let currentText = null; + for (let i = 0; i < body.length; i++) { + const group = mml.buildGroup(body[i], options); + if (group.type === 'mtext' && currentText != null) { + Array.prototype.push.apply(currentText.children, group.children); + } else { + inner.push(group); + if (group.type === 'mtext') { + currentText = group; + } + } + } + + // If there is a single tag in the end (presumably ), + // just return it. Otherwise, wrap them in an . + if (inner.length === 1) { + return inner[0]; + } else { + return new mathMLTree.MathNode("mrow", inner); + } + }, +});