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);
+ }
+ },
+});