commit e472b0ba9d5785fb4ef6f475f1950127fa350577
parent ed82784cba9421769177c5053000d778e6ed7755
Author: Ben Alpert <spicyjalapeno@gmail.com>
Date: Sun, 14 Jul 2013 16:55:46 -0700
Add TeX style support
Test Plan:
`\blue\frac12 + \frac{2(y-z)}{2+\frac1{7+\frac31}} \div \orange{\arctan x^{2+\frac43}_{2}} * 2^{2^{2^2}}` looks reasonable, as does
`\blue\frac12 + \dfrac{2(y-z)}{2+\frac1{7+\frac31}} \div \orange{\arctan x^{2+\frac43}_{2}} * 2^{2^{2^2}}`.
Reviewers: emily
Reviewed By: emily
Differential Revision: http://phabricator.khanacademy.org/D3047
Diffstat:
| M | Parser.js | | | 18 | ++++++++++++------ |
| A | Style.js | | | 65 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | katex.js | | | 81 | +++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------- |
| M | static/katex.css | | | 193 | +++++++++++++++++++------------------------------------------------------------ |
4 files changed, 178 insertions(+), 179 deletions(-)
diff --git a/Parser.js b/Parser.js
@@ -229,21 +229,27 @@ Parser.prototype.parseNucleus = function(pos) {
} else {
throw "Parse error: Expected group after '" + nucleus.text + "'";
}
- } else if (nucleus.type === "\\dfrac") {
- // If this is a dfrac, parse its two arguments and return
+ } else if (nucleus.type === "\\dfrac" || nucleus.type === "\\frac" ||
+ nucleus.type === "\\tfrac") {
+ // If this is a frac, parse its two arguments and return
var numer = this.parseGroup(nucleus.position);
if (numer) {
var denom = this.parseGroup(numer.position);
if (denom) {
return new ParseResult(
- new ParseNode("dfrac",
- {numer: numer.result, denom: denom.result}),
+ new ParseNode("frac", {
+ numer: numer.result,
+ denom: denom.result,
+ size: nucleus.type.slice(1)
+ }),
denom.position);
} else {
- throw "Parse error: Expected denominator after '\\dfrac'";
+ throw "Parse error: Expected denominator after '" +
+ nucleus.type + "'";
}
} else {
- throw "Parse error: Expected numerator after '\\dfrac'"
+ throw "Parse error: Expected numerator after '" + nucleus.type +
+ "'";
}
} else if (funcToType[nucleus.type]) {
// Otherwise if this is a no-argument function, find the type it
diff --git a/Style.js b/Style.js
@@ -0,0 +1,65 @@
+function Style(id, size, cramped) {
+ this.id = id;
+ this.size = size;
+ this.cramped = cramped;
+}
+
+Style.prototype.sup = function() {
+ return styles[sup[this.id]];
+};
+
+Style.prototype.sub = function() {
+ return styles[sub[this.id]];
+};
+
+Style.prototype.fracNum = function() {
+ return styles[fracNum[this.id]];
+};
+
+Style.prototype.fracDen = function() {
+ return styles[fracDen[this.id]];
+};
+
+/**
+ * HTML class name, like "display cramped"
+ */
+Style.prototype.cls = function() {
+ return sizeNames[this.size] + (this.cramped ? " cramped" : " uncramped");
+};
+
+var D = 0;
+var Dc = 1;
+var T = 2;
+var Tc = 3;
+var S = 4;
+var Sc = 5;
+var SS = 6;
+var SSc = 7;
+
+var sizeNames = [
+ "displaystyle textstyle",
+ "textstyle",
+ "scriptstyle",
+ "scriptscriptstyle"
+];
+
+var styles = [
+ new Style(D, 0, false),
+ new Style(Dc, 0, true),
+ new Style(T, 1, false),
+ new Style(Tc, 1, true),
+ new Style(S, 2, false),
+ new Style(Sc, 2, true),
+ new Style(SS, 3, false),
+ new Style(SSc, 3, true)
+];
+
+var sup = [S, Sc, S, Sc, SS, SSc, SS, SSc];
+var sub = [Sc, Sc, Sc, Sc, SSc, SSc, SSc, SSc];
+var fracNum = [T, Tc, S, Sc, SS, SSc, SS, SSc];
+var fracDen = [Tc, Tc, Sc, Sc, SSc, SSc, SSc, SSc];
+
+module.exports = {
+ DISPLAY: styles[D],
+ TEXT: styles[T],
+};
diff --git a/katex.js b/katex.js
@@ -1,14 +1,15 @@
-var parseTree = require("./parseTree");
+var Style = require("./Style");
+var parseTree = require("./parseTree");
var utils = require("./utils");
-var buildExpression = function(expression) {
+var buildExpression = function(style, expression) {
var groups = [];
for (var i = 0; i < expression.length; i++) {
var group = expression[i];
var prev = i > 0 ? expression[i-1] : null;
- groups.push(buildGroup(group, prev));
+ groups.push(buildGroup(style, group, prev));
};
return groups;
};
@@ -26,7 +27,7 @@ var makeSpan = function(className, children) {
return span;
};
-var buildGroup = function(group, prev) {
+var buildGroup = function(style, group, prev) {
if (group.type === "mathord") {
return makeSpan("mord", [mathit(group.value)]);
} else if (group.type === "textord") {
@@ -41,30 +42,56 @@ var buildGroup = function(group, prev) {
} else if (group.type === "rel") {
return makeSpan("mrel", [textit(group.value)]);
} else if (group.type === "sup") {
- var sup = makeSpan("msup", [buildGroup(group.value.sup)]);
- return makeSpan("mord", [buildGroup(group.value.base), sup]);
+ var sup = makeSpan("msup " + style.cls(), [
+ makeSpan(style.sup().cls(), [
+ buildGroup(style.sup(), group.value.sup)
+ ])
+ ]);
+ return makeSpan("mord", [buildGroup(style, group.value.base), sup]);
} else if (group.type === "sub") {
- var sub = makeSpan("msub", [buildGroup(group.value.sub)]);
- return makeSpan("mord", [buildGroup(group.value.base), sub]);
+ var sub = makeSpan("msub " + style.cls(), [
+ makeSpan(style.sub().cls(), [
+ buildGroup(style.sub(), group.value.sub)
+ ])
+ ]);
+ return makeSpan("mord", [buildGroup(style, group.value.base), sub]);
} else if (group.type === "supsub") {
- var sup = makeSpan("msup", [buildGroup(group.value.sup)]);
- var sub = makeSpan("msub", [buildGroup(group.value.sub)]);
+ var sup = makeSpan("msup " + style.sup().cls(), [
+ buildGroup(style.sup(), group.value.sup)
+ ]);
+ var sub = makeSpan("msub " + style.sub().cls(), [
+ buildGroup(style.sub(), group.value.sub)
+ ]);
- var supsub = makeSpan("msupsub", [sup, sub]);
+ var supsub = makeSpan("msupsub " + style.cls(), [sup, sub]);
- return makeSpan("mord", [buildGroup(group.value.base), supsub]);
+ return makeSpan("mord", [buildGroup(style, group.value.base), supsub]);
} else if (group.type === "open") {
return makeSpan("mopen", [textit(group.value)]);
} else if (group.type === "close") {
return makeSpan("mclose", [textit(group.value)]);
- } else if (group.type === "dfrac") {
- var numer = makeSpan("mfracnum", [makeSpan("", [buildGroup(group.value.numer)])]);
+ } else if (group.type === "frac") {
+ var fstyle = style;
+ if (group.value.size === "dfrac") {
+ fstyle = Style.DISPLAY;
+ } else if (group.value.size === "tfrac") {
+ fstyle = Style.TEXT;
+ }
+
+ var nstyle = fstyle.fracNum();
+ var dstyle = fstyle.fracDen();
+
+ var numer = makeSpan("mfracnum " + nstyle.cls(), [
+ makeSpan("", [buildGroup(nstyle, group.value.numer)])
+ ]);
var mid = makeSpan("mfracmid", [makeSpan()]);
- var denom = makeSpan("mfracden", [buildGroup(group.value.denom)]);
+ var denom = makeSpan("mfracden " + dstyle.cls(), [
+ makeSpan("", [buildGroup(dstyle, group.value.denom)])
+ ]);
- return makeSpan("minner mfrac", [numer, mid, denom]);
+ return makeSpan("minner mfrac " + fstyle.cls(), [numer, mid, denom]);
} else if (group.type === "color") {
- return makeSpan("mord " + group.value.color, [buildGroup(group.value.value)]);
+ return makeSpan("mord " + group.value.color, [buildGroup(style, group.value.value)]);
} else if (group.type === "spacing") {
if (group.value === "\\ " || group.value === "\\space") {
return makeSpan("mord mspace", [textit(group.value)]);
@@ -80,15 +107,15 @@ var buildGroup = function(group, prev) {
return makeSpan("mord mspace " + spacingClassMap[group.value]);
}
} else if (group.type === "llap") {
- var inner = makeSpan("", buildExpression(group.value));
- return makeSpan("llap", [inner]);
+ var inner = makeSpan("", buildExpression(style, group.value));
+ return makeSpan("llap " + style.cls(), [inner]);
} else if (group.type === "rlap") {
- var inner = makeSpan("", buildExpression(group.value));
- return makeSpan("rlap", [inner]);
+ var inner = makeSpan("", buildExpression(style, group.value));
+ return makeSpan("rlap " + style.cls(), [inner]);
} else if (group.type === "punct") {
return makeSpan("mpunct", [textit(group.value)]);
} else if (group.type === "ordgroup") {
- return makeSpan("mord", buildExpression(group.value));
+ return makeSpan("mord " + style.cls(), buildExpression(style, group.value));
} else if (group.type === "namedfn") {
return makeSpan("mop", [textit(group.value.slice(1))]);
} else {
@@ -141,11 +168,13 @@ var process = function(toParse, baseElem) {
console.error(e);
return false;
}
+
+ var style = Style.TEXT;
+ var expression = buildExpression(style, tree);
+ var span = makeSpan(style.cls(), expression);
+
clearNode(baseElem);
- var expression = buildExpression(tree);
- for (var i = 0; i < expression.length; i++) {
- baseElem.appendChild(expression[i]);
- }
+ baseElem.appendChild(span);
return true;
};
diff --git a/static/katex.css b/static/katex.css
@@ -16,7 +16,7 @@ big parens
.mathmathmath {
font: normal 1.21em katex_main;
- line-height: 1.4;
+ line-height: 1.2;
}
.mathit {
@@ -24,149 +24,47 @@ big parens
font-style: italic;
}
-.mord + .mbin {
- margin-left: 0.22222em;
-}
-
-.mbin + .mord {
- margin-left: 0.22222em;
-}
-
-.mbin + .mopen {
- margin-left: 0.22222em;
-}
-
-.mclose + .mbin {
- margin-left: 0.22222em;
-}
-
-.mrel + .mord {
- margin-left: 0.27778em;
-}
-
-.mord + .mrel {
- margin-left: 0.27778em;
-}
-
-.mrel + .mopen {
- margin-left: 0.27778em;
-}
-
-.mclose + .mrel {
- margin-left: 0.27778em;
-}
-
-.mpunct + .mord {
- margin-left: 0.16667em;
-}
-
-.mpunct + .mbin {
- margin-left: 0.16667em;
-}
-
-.mpunct + .mrel {
- margin-left: 0.16667em;
-}
-
-.mpunct + .mopen {
- margin-left: 0.16667em;
-}
-
-.mpunct + .mclose {
- margin-left: 0.16667em;
-}
-
-.mpunct + .mpunct {
- margin-left: 0.16667em;
-}
-
-.minner + .mord {
- margin-left: 0.16667em;
-}
-
-.minner + .mbin {
- margin-left: 0.22222em;
-}
-
-.minner + .mrel {
- margin-left: 0.27778em;
-}
-
-.minner + .mopen {
- margin-left: 0.16667em;
-}
-
-.minner + .mpunct {
- margin-left: 0.16667em;
-}
-
-.minner + .minner {
- margin-left: 0.16667em;
-}
-
-.mord + .minner {
- margin-left: 0.16667em;
-}
-
-.mbin + .minner {
- margin-left: 0.22222em;
-}
-
-.mrel + .minner {
- margin-left: 0.27778em;
-}
-
-.mclose + .minner {
- margin-left: 0.16667em;
-}
-
-.mpunct + .minner {
- margin-left: 0.16667em;
-}
-
-.mop + .mord {
- margin-left: 0.16667em;
-}
-
-.mop + .mop {
- margin-left: 0.16667em;
-}
-
-.mop + .mrel {
- margin-left: 0.27778em;
-}
-
-.mop + .minner {
- margin-left: 0.16667em;
-}
-
-.mord + .mop {
- margin-left: 0.16667em;
-}
-
-.mbin + .mop {
- margin-left: 0.22222em;
-}
-
-.mrel + .mop {
- margin-left: 0.27778em;
-}
-
-.mclose + .mop {
- margin-left: 0.16667em;
-}
-
-.mpunct + .mop {
- margin-left: 0.16667em;
-}
-
-.minner + .mop {
- margin-left: 0.16667em;
-}
+.textstyle > .mbin + .minner { margin-left: 0.22222em; }
+.textstyle > .mbin + .mop { margin-left: 0.22222em; }
+.textstyle > .mbin + .mopen { margin-left: 0.22222em; }
+.textstyle > .mbin + .mord { margin-left: 0.22222em; }
+.textstyle > .mclose + .mbin { margin-left: 0.22222em; }
+.textstyle > .mclose + .minner { margin-left: 0.16667em; }
+ .mclose + .mop { margin-left: 0.16667em; }
+.textstyle > .mclose + .mrel { margin-left: 0.27778em; }
+.textstyle > .minner + .mbin { margin-left: 0.22222em; }
+.textstyle > .minner + .minner { margin-left: 0.16667em; }
+ .minner + .mop { margin-left: 0.16667em; }
+.textstyle > .minner + .mopen { margin-left: 0.16667em; }
+.textstyle > .minner + .mord { margin-left: 0.16667em; }
+.textstyle > .minner + .mpunct { margin-left: 0.16667em; }
+.textstyle > .minner + .mrel { margin-left: 0.27778em; }
+.textstyle > .mop + .minner { margin-left: 0.16667em; }
+ .mop + .mop { margin-left: 0.16667em; }
+ .mop + .mord { margin-left: 0.16667em; }
+.textstyle > .mop + .mrel { margin-left: 0.27778em; }
+.textstyle > .mord + .mbin { margin-left: 0.22222em; }
+.textstyle > .mord + .minner { margin-left: 0.16667em; }
+ .mord + .mop { margin-left: 0.16667em; }
+.textstyle > .mord + .mrel { margin-left: 0.27778em; }
+.textstyle > .mpunct + .mbin { margin-left: 0.16667em; }
+.textstyle > .mpunct + .mclose { margin-left: 0.16667em; }
+.textstyle > .mpunct + .minner { margin-left: 0.16667em; }
+.textstyle > .mpunct + .mop { margin-left: 0.16667em; }
+.textstyle > .mpunct + .mopen { margin-left: 0.16667em; }
+.textstyle > .mpunct + .mord { margin-left: 0.16667em; }
+.textstyle > .mpunct + .mpunct { margin-left: 0.16667em; }
+.textstyle > .mpunct + .mrel { margin-left: 0.16667em; }
+.textstyle > .mrel + .minner { margin-left: 0.27778em; }
+.textstyle > .mrel + .mop { margin-left: 0.27778em; }
+.textstyle > .mrel + .mopen { margin-left: 0.27778em; }
+.textstyle > .mrel + .mord { margin-left: 0.27778em; }
+
+.textstyle > .scriptstyle { font-size: 0.66667em; }
+.scriptstyle > .scriptscriptstyle { font-size: 0.75em; }
.msub {
vertical-align: bottom;
- font-size: 70%;
position: relative;
top: 0.2em;
}
@@ -174,7 +72,6 @@ big parens
.msup {
position: relative;
top: -0.5em;
- font-size: 70%;
}
.msupsub {
@@ -186,13 +83,15 @@ big parens
.msupsub > .msup, .msupsub > .msub {
display: table-row;
vertical-align: baseline;
- line-height: 1em;
}
-.mfrac {
- display: inline-table;
- vertical-align: 0.66em;
-}
+.mfrac { display: inline-table; }
+
+/* TODO(alpert): Where do these numbers come from? */
+.mfrac.textstyle.displaystyle { vertical-align: 0.58em; }
+.mfrac.textstyle { vertical-align: 0.50em; }
+.mfrac.scriptstyle { vertical-align: 0.50em; }
+.mfrac.scriptscriptstyle { vertical-align: 0.6em; }
.mfracnum, .mfracmid, .mfracden {
display: table-row;