diff --git a/src/Options.js b/src/Options.js
index 72ae999aa..323920312 100644
--- a/src/Options.js
+++ b/src/Options.js
@@ -6,9 +6,9 @@
*/
/**
- * This is the main options class. It contains the style, size, and color of the
- * current parse level. It also contains the style and size of the parent parse
- * level, so size changes can be handled efficiently.
+ * This is the main options class. It contains the style, size, color and font
+ * of the current parse level. It also contains the style and size of the parent
+ * parse level, so size changes can be handled efficiently.
*
* Each of the `.with*` and `.reset` functions passes its current style and size
* as the parentStyle and parentSize of the new options class, so parent
@@ -19,6 +19,7 @@ function Options(data) {
this.color = data.color;
this.size = data.size;
this.phantom = data.phantom;
+ this.font = data.font;
if (data.parentStyle === undefined) {
this.parentStyle = data.style;
@@ -44,7 +45,8 @@ Options.prototype.extend = function(extension) {
color: this.color,
parentStyle: this.style,
parentSize: this.size,
- phantom: this.phantom
+ phantom: this.phantom,
+ font: this.font
};
for (var key in extension) {
@@ -92,6 +94,15 @@ Options.prototype.withPhantom = function() {
});
};
+/**
+ * Create a new options objects with the give font.
+ */
+Options.prototype.withFont = function(font) {
+ return this.extend({
+ font: font
+ });
+};
+
/**
* Create a new options object with the same style, size, and color. This is
* used so that parent style and size changes are handled correctly.
diff --git a/src/buildCommon.js b/src/buildCommon.js
index 62eceb3ae..36fe24a6c 100644
--- a/src/buildCommon.js
+++ b/src/buildCommon.js
@@ -6,6 +6,26 @@
var domTree = require("./domTree");
var fontMetrics = require("./fontMetrics");
var symbols = require("./symbols");
+var utils = require("./utils");
+
+var greekCapitals = [
+ "\\Gamma",
+ "\\Delta",
+ "\\Theta",
+ "\\Lambda",
+ "\\Xi",
+ "\\Pi",
+ "\\Sigma",
+ "\\Upsilon",
+ "\\Phi",
+ "\\Psi",
+ "\\Omega"
+];
+
+var dotlessLetters = [
+ "\u0131", // dotless i, \imath
+ "\u0237" // dotless j, \jmath
+];
/**
* Makes a symbolNode after translation via the list of symbols in symbols.js.
@@ -41,17 +61,10 @@ var makeSymbol = function(value, style, mode, color, classes) {
};
/**
- * Makes a symbol in the italic math font.
+ * Makes a symbol in Main-Regular or AMS-Regular.
+ * Used for rel, bin, open, close, inner, and punct.
*/
-var mathit = function(value, mode, color, classes) {
- return makeSymbol(
- value, "Math-Italic", mode, color, classes.concat(["mathit"]));
-};
-
-/**
- * Makes a symbol in the upright roman font.
- */
-var mathrm = function(value, mode, color, classes) {
+var mathsym = function(value, mode, color, classes) {
// Decide what font to render the symbol in by its entry in the symbols
// table.
// Have a special case for when the value = \ because the \ is used as a
@@ -66,6 +79,67 @@ var mathrm = function(value, mode, color, classes) {
}
};
+/**
+ * Makes a symbol in the default font for mathords and textords.
+ */
+var mathDefault = function(value, mode, color, classes, type) {
+ if (type === "mathord") {
+ return mathit(value, mode, color, classes);
+ } else if (type === "textord") {
+ return makeSymbol(
+ value, "Main-Regular", mode, color, classes.concat(["mathrm"]));
+ } else {
+ throw new Error("unexpected type: " + type + " in mathDefault");
+ }
+};
+
+/**
+ * Makes a symbol in the italic math font.
+ */
+var mathit = function(value, mode, color, classes) {
+ if (/[0-9]/.test(value.charAt(0)) ||
+ // glyphs for \imath and \jmath do not exist in Math-Italic so we
+ // need to use Main-Italic instead
+ utils.contains(dotlessLetters, value) ||
+ utils.contains(greekCapitals, value)) {
+ return makeSymbol(
+ value, "Main-Italic", mode, color, classes.concat(["mainit"]));
+ } else {
+ return makeSymbol(
+ value, "Math-Italic", mode, color, classes.concat(["mathit"]));
+ }
+};
+
+/**
+ * Makes either a mathord or textord in the correct font and color.
+ */
+var makeOrd = function(group, options, type) {
+ var mode = group.mode;
+ var value = group.value;
+ if (symbols[mode][value] && symbols[mode][value].replace) {
+ value = symbols[mode][value].replace;
+ }
+
+ var classes = ["mord"];
+ var color = options.getColor();
+
+ var font = options.font;
+ if (font) {
+ if (font === "mathit" || utils.contains(dotlessLetters, value)) {
+ return mathit(value, mode, color, classes);
+ } else {
+ var fontName = fontMap[font].fontName;
+ if (fontMetrics.getCharacterMetrics(value, fontName)) {
+ return makeSymbol(value, fontName, mode, color, classes.concat([font]));
+ } else {
+ return mathDefault(value, mode, color, classes, type);
+ }
+ }
+ } else {
+ return mathDefault(value, mode, color, classes, type);
+ }
+};
+
/**
* Calculate the height, depth, and maxFontSize of an element based on its
* children.
@@ -312,13 +386,61 @@ var spacingFunctions = {
}
};
+/**
+ * Maps TeX font commands to objects containing:
+ * - variant: string used for "mathvariant" attribute in buildMathML.js
+ * - fontName: the "style" parameter to fontMetrics.getCharacterMetrics
+ */
+// A map between tex font commands an MathML mathvariant attribute values
+var fontMap = {
+ // styles
+ "mathbf": {
+ variant: "bold",
+ fontName: "Main-Bold"
+ },
+ "mathrm": {
+ variant: "normal",
+ fontName: "Main-Regular"
+ },
+
+ // "mathit" is missing because it requires the use of two fonts: Main-Italic
+ // and Math-Italic. This is handled by a special case in makeOrd which ends
+ // up calling mathit.
+
+ // families
+ "mathbb": {
+ variant: "double-struck",
+ fontName: "AMS-Regular"
+ },
+ "mathcal": {
+ variant: "script",
+ fontName: "Caligraphic-Regular"
+ },
+ "mathfrak": {
+ variant: "fraktur",
+ fontName: "Fraktur-Regular"
+ },
+ "mathscr": {
+ variant: "script",
+ fontName: "Script-Regular"
+ },
+ "mathsf": {
+ variant: "sans-serif",
+ fontName: "SansSerif-Regular"
+ },
+ "mathtt": {
+ variant: "monospace",
+ fontName: "Typewriter-Regular"
+ }
+};
+
module.exports = {
makeSymbol: makeSymbol,
- mathit: mathit,
- mathrm: mathrm,
+ mathsym: mathsym,
makeSpan: makeSpan,
makeFragment: makeFragment,
makeVList: makeVList,
+ makeOrd: makeOrd,
sizingMultiplier: sizingMultiplier,
spacingFunctions: spacingFunctions
};
diff --git a/src/buildHTML.js b/src/buildHTML.js
index 7eb8619a0..b1b023243 100644
--- a/src/buildHTML.js
+++ b/src/buildHTML.js
@@ -171,13 +171,11 @@ var makeNullDelimiter = function(options) {
*/
var groupTypes = {
mathord: function(group, options, prev) {
- return buildCommon.mathit(
- group.value, group.mode, options.getColor(), ["mord"]);
+ return buildCommon.makeOrd(group, options, "mathord");
},
textord: function(group, options, prev) {
- return buildCommon.mathrm(
- group.value, group.mode, options.getColor(), ["mord"]);
+ return buildCommon.makeOrd(group, options, "textord");
},
bin: function(group, options, prev) {
@@ -199,32 +197,32 @@ var groupTypes = {
className = "mord";
}
- return buildCommon.mathrm(
+ return buildCommon.mathsym(
group.value, group.mode, options.getColor(), [className]);
},
rel: function(group, options, prev) {
- return buildCommon.mathrm(
+ return buildCommon.mathsym(
group.value, group.mode, options.getColor(), ["mrel"]);
},
open: function(group, options, prev) {
- return buildCommon.mathrm(
+ return buildCommon.mathsym(
group.value, group.mode, options.getColor(), ["mopen"]);
},
close: function(group, options, prev) {
- return buildCommon.mathrm(
+ return buildCommon.mathsym(
group.value, group.mode, options.getColor(), ["mclose"]);
},
inner: function(group, options, prev) {
- return buildCommon.mathrm(
+ return buildCommon.mathsym(
group.value, group.mode, options.getColor(), ["minner"]);
},
punct: function(group, options, prev) {
- return buildCommon.mathrm(
+ return buildCommon.mathsym(
group.value, group.mode, options.getColor(), ["mpunct"]);
},
@@ -628,7 +626,7 @@ var groupTypes = {
// into appropriate outputs.
return makeSpan(
["mord", "mspace"],
- [buildCommon.mathrm(group.value, group.mode)]
+ [buildCommon.mathsym(group.value, group.mode)]
);
} else {
// Other kinds of spaces are of arbitrary width. We use CSS to
@@ -712,7 +710,7 @@ var groupTypes = {
// operators, like \limsup
var output = [];
for (var i = 1; i < group.value.body.length; i++) {
- output.push(buildCommon.mathrm(group.value.body[i], group.mode));
+ output.push(buildCommon.mathsym(group.value.body[i], group.mode));
}
base = makeSpan(["mop"], output, options.getColor());
}
@@ -819,26 +817,26 @@ var groupTypes = {
// good, but the offsets for the T, E, and X were taken from the
// definition of \TeX in TeX (see TeXbook pg. 356)
var k = makeSpan(
- ["k"], [buildCommon.mathrm("K", group.mode)]);
+ ["k"], [buildCommon.mathsym("K", group.mode)]);
var a = makeSpan(
- ["a"], [buildCommon.mathrm("A", group.mode)]);
+ ["a"], [buildCommon.mathsym("A", group.mode)]);
a.height = (a.height + 0.2) * 0.75;
a.depth = (a.height - 0.2) * 0.75;
var t = makeSpan(
- ["t"], [buildCommon.mathrm("T", group.mode)]);
+ ["t"], [buildCommon.mathsym("T", group.mode)]);
var e = makeSpan(
- ["e"], [buildCommon.mathrm("E", group.mode)]);
+ ["e"], [buildCommon.mathsym("E", group.mode)]);
e.height = (e.height - 0.2155);
e.depth = (e.depth + 0.2155);
var x = makeSpan(
- ["x"], [buildCommon.mathrm("X", group.mode)]);
+ ["x"], [buildCommon.mathsym("X", group.mode)]);
return makeSpan(
- ["katex-logo"], [k, a, t, e, x], options.getColor());
+ ["katex-logo", "mord"], [k, a, t, e, x], options.getColor());
},
overline: function(group, options, prev) {
@@ -1006,6 +1004,11 @@ var groupTypes = {
return makeSpan([options.style.reset(), newStyle.cls()], inner);
},
+ font: function(group, options, prev) {
+ var font = group.value.font;
+ return buildGroup(group.value.body, options.withFont(font), prev);
+ },
+
delimsizing: function(group, options, prev) {
var delim = group.value.value;
diff --git a/src/buildMathML.js b/src/buildMathML.js
index 736a61aec..28cd68054 100644
--- a/src/buildMathML.js
+++ b/src/buildMathML.js
@@ -252,6 +252,11 @@ var groupTypes = {
return node;
},
+
+ font: function(group) {
+ // pass through so we can render something without throwing
+ return buildGroup(group.value.body);
+ },
spacing: function(group) {
var node;
diff --git a/src/functions.js b/src/functions.js
index 6f6a1792b..1c2b2b2c0 100644
--- a/src/functions.js
+++ b/src/functions.js
@@ -215,6 +215,12 @@ var delimiters = [
"."
];
+var fontAliases = {
+ "\\Bbb": "\\mathbb",
+ "\\bold": "\\mathbf",
+ "\\frak": "\\mathfrak"
+};
+
/*
* This is a list of functions which each have the same function but have
* different names so that we don't have to duplicate the data a bunch of times.
@@ -476,6 +482,33 @@ var duplicatedFunctions = [
}
},
+ {
+ funcs: [
+ // styles
+ "\\mathrm", "\\mathit", "\\mathbf",
+
+ // families
+ "\\mathbb", "\\mathcal", "\\mathfrak", "\\mathscr", "\\mathsf",
+ "\\mathtt",
+
+ // aliases
+ "\\Bbb", "\\bold", "\\frak"
+ ],
+ data: {
+ numArgs: 1,
+ handler: function (func, body) {
+ if (func in fontAliases) {
+ func = fontAliases[func];
+ }
+ return {
+ type: "font",
+ font: func.slice(1),
+ body: body
+ };
+ }
+ }
+ },
+
// Accents
{
funcs: [
diff --git a/src/symbols.js b/src/symbols.js
index e5b1fd471..01e59dbaa 100644
--- a/src/symbols.js
+++ b/src/symbols.js
@@ -2514,6 +2514,17 @@ var symbols = {
font: "main",
group: "accent",
replace: "\u02d9"
+ },
+
+ "\\imath": {
+ font: "main",
+ group: "mathord",
+ replace: "\u0131"
+ },
+ "\\jmath": {
+ font: "main",
+ group: "mathord",
+ replace: "\u0237"
}
},
"text": {
diff --git a/test/katex-spec.js b/test/katex-spec.js
index 52f34425d..271cf5e8c 100644
--- a/test/katex-spec.js
+++ b/test/katex-spec.js
@@ -1160,6 +1160,115 @@ describe("A style change parser", function() {
});
});
+describe("A font parser", function () {
+ it("should parse \\mathrm, \\mathbb, and \\mathit", function () {
+ expect("\\mathrm x").toParse();
+ expect("\\mathbb x").toParse();
+ expect("\\mathit x").toParse();
+ expect("\\mathrm {x + 1}").toParse();
+ expect("\\mathbb {x + 1}").toParse();
+ expect("\\mathit {x + 1}").toParse();
+ });
+
+ it("should parse \\mathcal and \\mathfrak", function () {
+ expect("\\mathcal{ABC123}").toParse();
+ expect("\\mathfrak{abcABC123}").toParse();
+ });
+
+ it("should produce the correct fonts", function () {
+ var mathbbParse = getParsed("\\mathbb x")[0];
+ expect(mathbbParse.value.font).toMatch("mathbb");
+ expect(mathbbParse.value.type).toMatch("font");
+
+ var mathrmParse = getParsed("\\mathrm x")[0];
+ expect(mathrmParse.value.font).toMatch("mathrm");
+ expect(mathrmParse.value.type).toMatch("font");
+
+ var mathitParse = getParsed("\\mathit x")[0];
+ expect(mathitParse.value.font).toMatch("mathit");
+ expect(mathitParse.value.type).toMatch("font");
+
+ var mathcalParse = getParsed("\\mathcal C")[0];
+ expect(mathcalParse.value.font).toMatch("mathcal");
+ expect(mathcalParse.value.type).toMatch("font");
+
+ var mathfrakParse = getParsed("\\mathfrak C")[0];
+ expect(mathfrakParse.value.font).toMatch("mathfrak");
+ expect(mathfrakParse.value.type).toMatch("font");
+ });
+
+ it("should parse nested font commands", function () {
+ var nestedParse = getParsed("\\mathbb{R \\neq \\mathrm{R}}")[0];
+ expect(nestedParse.value.font).toMatch("mathbb");
+ expect(nestedParse.value.type).toMatch("font");
+
+ expect(nestedParse.value.body.value.length).toMatch(3);
+ var bbBody = nestedParse.value.body.value;
+ expect(bbBody[0].type).toMatch("mathord");
+ expect(bbBody[1].type).toMatch("rel");
+ expect(bbBody[2].type).toMatch("font");
+ expect(bbBody[2].value.font).toMatch("mathrm");
+ expect(bbBody[2].value.type).toMatch("font");
+ });
+
+ it("should work with \\color", function () {
+ var colorMathbbParse = getParsed("\\color{blue}{\\mathbb R}")[0];
+ expect(colorMathbbParse.value.type).toMatch("color");
+ expect(colorMathbbParse.value.color).toMatch("blue");
+ var body = colorMathbbParse.value.value;
+ expect(body.length).toMatch(1);
+ expect(body[0].value.type).toMatch("font");
+ expect(body[0].value.font).toMatch("mathbb");
+ });
+
+ it("should not parse a series of font commands", function () {
+ expect("\\mathbb \\mathrm R").toNotParse();
+ });
+
+ it("should nest fonts correctly", function () {
+ var bf = getParsed("\\mathbf{a\\mathrm{b}c}")[0];
+ expect(bf.value.type).toMatch("font");
+ expect(bf.value.font).toMatch("mathbf");
+ expect(bf.value.body.value.length).toMatch(3);
+ expect(bf.value.body.value[0].value).toMatch("a");
+ expect(bf.value.body.value[1].value.type).toMatch("font");
+ expect(bf.value.body.value[1].value.font).toMatch("mathrm");
+ expect(bf.value.body.value[2].value).toMatch("c");
+ });
+});
+
+describe("An HTML font tree-builder", function () {
+ it("should render \\mathbb{R} with the correct font", function () {
+ var markup = katex.renderToString("\\mathbb{R}");
+ expect(markup).toContain("R");
+ });
+
+ it("should render \\mathrm{R} with the correct font", function () {
+ var markup = katex.renderToString("\\mathrm{R}");
+ expect(markup).toContain("R");
+ });
+
+ it("should render \\mathcal{R} with the correct font", function () {
+ var markup = katex.renderToString("\\mathcal{R}");
+ expect(markup).toContain("R");
+ });
+
+ it("should render \\mathfrak{R} with the correct font", function () {
+ var markup = katex.renderToString("\\mathfrak{R}");
+ expect(markup).toContain("R");
+ });
+
+ it("should render a combination of font and color changes", function () {
+ var markup = katex.renderToString("\\color{blue}{\\mathbb R}");
+ var span = "R";
+ expect(markup).toContain(span);
+
+ markup = katex.renderToString("\\mathbb{\\color{blue}{R}}");
+ span = "R";
+ expect(markup).toContain(span);
+ });
+});
+
describe("A bin builder", function() {
it("should create mbins normally", function() {
var built = getBuilt("x + y");
diff --git a/test/screenshotter/images/MathBb-firefox.png b/test/screenshotter/images/MathBb-firefox.png
new file mode 100644
index 000000000..019562e10
Binary files /dev/null and b/test/screenshotter/images/MathBb-firefox.png differ
diff --git a/test/screenshotter/images/MathBf-firefox.png b/test/screenshotter/images/MathBf-firefox.png
new file mode 100644
index 000000000..57c7e5709
Binary files /dev/null and b/test/screenshotter/images/MathBf-firefox.png differ
diff --git a/test/screenshotter/images/MathCal-firefox.png b/test/screenshotter/images/MathCal-firefox.png
new file mode 100644
index 000000000..d35c399c8
Binary files /dev/null and b/test/screenshotter/images/MathCal-firefox.png differ
diff --git a/test/screenshotter/images/MathDefaultFonts-firefox.png b/test/screenshotter/images/MathDefaultFonts-firefox.png
new file mode 100644
index 000000000..f5267988a
Binary files /dev/null and b/test/screenshotter/images/MathDefaultFonts-firefox.png differ
diff --git a/test/screenshotter/images/MathFrak-firefox.png b/test/screenshotter/images/MathFrak-firefox.png
new file mode 100644
index 000000000..152a7410f
Binary files /dev/null and b/test/screenshotter/images/MathFrak-firefox.png differ
diff --git a/test/screenshotter/images/MathIt-firefox.png b/test/screenshotter/images/MathIt-firefox.png
new file mode 100644
index 000000000..9a1e7da31
Binary files /dev/null and b/test/screenshotter/images/MathIt-firefox.png differ
diff --git a/test/screenshotter/images/MathRm-firefox.png b/test/screenshotter/images/MathRm-firefox.png
new file mode 100644
index 000000000..48a7000a2
Binary files /dev/null and b/test/screenshotter/images/MathRm-firefox.png differ
diff --git a/test/screenshotter/images/MathScr-firefox.png b/test/screenshotter/images/MathScr-firefox.png
new file mode 100644
index 000000000..01b273c35
Binary files /dev/null and b/test/screenshotter/images/MathScr-firefox.png differ
diff --git a/test/screenshotter/images/MathSf-firefox.png b/test/screenshotter/images/MathSf-firefox.png
new file mode 100644
index 000000000..76fbff295
Binary files /dev/null and b/test/screenshotter/images/MathSf-firefox.png differ
diff --git a/test/screenshotter/images/MathTt-firefox.png b/test/screenshotter/images/MathTt-firefox.png
new file mode 100644
index 000000000..bbedf692f
Binary files /dev/null and b/test/screenshotter/images/MathTt-firefox.png differ
diff --git a/test/screenshotter/ss_data.yaml b/test/screenshotter/ss_data.yaml
index 61775bb4c..48a1d6a5a 100644
--- a/test/screenshotter/ss_data.yaml
+++ b/test/screenshotter/ss_data.yaml
@@ -65,6 +65,16 @@ LeftRightStyleSizing: |
LimitControls: |
\displaystyle\int\limits_2^3 3x^2\,dx + \sum\nolimits^n_{i=1}i +
\textstyle\int\limits_x^y z
+MathDefaultFonts: Ax2k\breve{a}\omega\Omega\imath+\KaTeX
+MathBb: \mathbb{Ax2k\breve{a}\omega\Omega\imath+\KaTeX}
+MathBf: \mathbf{Ax2k\breve{a}\omega\Omega\imath+\KaTeX}
+MathCal: \mathcal{Ax2k\breve{a}\omega\Omega\imath+\KaTeX}
+MathFrak: \mathfrak{Ax2k\breve{a}\omega\Omega\imath+\KaTeX}
+MathIt: \mathit{Ax2k\breve{a}\omega\Omega\imath+\KaTeX}
+MathRm: \mathrm{Ax2k\breve{a}\omega\Omega\imath+\KaTeX}
+MathSf: \mathsf{Ax2k\breve{a}\omega\Omega\imath+\KaTeX}
+MathScr: \mathscr{Ax2k\breve{a}\omega\Omega\imath+\KaTeX}
+MathTt: \mathtt{Ax2k\breve{a}\omega\Omega\imath+\KaTeX}
NestedFractions: |
\dfrac{\frac{a}{b}}{\frac{c}{d}}\dfrac{\dfrac{a}{b}}
{\dfrac{c}{d}}\frac{\frac{a}{b}}{\frac{c}{d}}