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
This commit is contained in:
Ben Alpert 2013-07-14 16:55:46 -07:00
parent ed82784cba
commit e472b0ba9d
4 changed files with 178 additions and 179 deletions

View File

@ -229,21 +229,27 @@ Parser.prototype.parseNucleus = function(pos) {
} else { } else {
throw "Parse error: Expected group after '" + nucleus.text + "'"; throw "Parse error: Expected group after '" + nucleus.text + "'";
} }
} else if (nucleus.type === "\\dfrac") { } else if (nucleus.type === "\\dfrac" || nucleus.type === "\\frac" ||
// If this is a dfrac, parse its two arguments and return nucleus.type === "\\tfrac") {
// If this is a frac, parse its two arguments and return
var numer = this.parseGroup(nucleus.position); var numer = this.parseGroup(nucleus.position);
if (numer) { if (numer) {
var denom = this.parseGroup(numer.position); var denom = this.parseGroup(numer.position);
if (denom) { if (denom) {
return new ParseResult( return new ParseResult(
new ParseNode("dfrac", new ParseNode("frac", {
{numer: numer.result, denom: denom.result}), numer: numer.result,
denom: denom.result,
size: nucleus.type.slice(1)
}),
denom.position); denom.position);
} else { } else {
throw "Parse error: Expected denominator after '\\dfrac'"; throw "Parse error: Expected denominator after '" +
nucleus.type + "'";
} }
} else { } else {
throw "Parse error: Expected numerator after '\\dfrac'" throw "Parse error: Expected numerator after '" + nucleus.type +
"'";
} }
} else if (funcToType[nucleus.type]) { } else if (funcToType[nucleus.type]) {
// Otherwise if this is a no-argument function, find the type it // Otherwise if this is a no-argument function, find the type it

65
Style.js Normal file
View File

@ -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],
};

View File

@ -1,14 +1,15 @@
var parseTree = require("./parseTree"); var Style = require("./Style");
var parseTree = require("./parseTree");
var utils = require("./utils"); var utils = require("./utils");
var buildExpression = function(expression) { var buildExpression = function(style, expression) {
var groups = []; var groups = [];
for (var i = 0; i < expression.length; i++) { for (var i = 0; i < expression.length; i++) {
var group = expression[i]; var group = expression[i];
var prev = i > 0 ? expression[i-1] : null; var prev = i > 0 ? expression[i-1] : null;
groups.push(buildGroup(group, prev)); groups.push(buildGroup(style, group, prev));
}; };
return groups; return groups;
}; };
@ -26,7 +27,7 @@ var makeSpan = function(className, children) {
return span; return span;
}; };
var buildGroup = function(group, prev) { var buildGroup = function(style, group, prev) {
if (group.type === "mathord") { if (group.type === "mathord") {
return makeSpan("mord", [mathit(group.value)]); return makeSpan("mord", [mathit(group.value)]);
} else if (group.type === "textord") { } else if (group.type === "textord") {
@ -41,30 +42,56 @@ var buildGroup = function(group, prev) {
} else if (group.type === "rel") { } else if (group.type === "rel") {
return makeSpan("mrel", [textit(group.value)]); return makeSpan("mrel", [textit(group.value)]);
} else if (group.type === "sup") { } else if (group.type === "sup") {
var sup = makeSpan("msup", [buildGroup(group.value.sup)]); var sup = makeSpan("msup " + style.cls(), [
return makeSpan("mord", [buildGroup(group.value.base), sup]); makeSpan(style.sup().cls(), [
buildGroup(style.sup(), group.value.sup)
])
]);
return makeSpan("mord", [buildGroup(style, group.value.base), sup]);
} else if (group.type === "sub") { } else if (group.type === "sub") {
var sub = makeSpan("msub", [buildGroup(group.value.sub)]); var sub = makeSpan("msub " + style.cls(), [
return makeSpan("mord", [buildGroup(group.value.base), sub]); makeSpan(style.sub().cls(), [
buildGroup(style.sub(), group.value.sub)
])
]);
return makeSpan("mord", [buildGroup(style, group.value.base), sub]);
} else if (group.type === "supsub") { } else if (group.type === "supsub") {
var sup = makeSpan("msup", [buildGroup(group.value.sup)]); var sup = makeSpan("msup " + style.sup().cls(), [
var sub = makeSpan("msub", [buildGroup(group.value.sub)]); 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") { } else if (group.type === "open") {
return makeSpan("mopen", [textit(group.value)]); return makeSpan("mopen", [textit(group.value)]);
} else if (group.type === "close") { } else if (group.type === "close") {
return makeSpan("mclose", [textit(group.value)]); return makeSpan("mclose", [textit(group.value)]);
} else if (group.type === "dfrac") { } else if (group.type === "frac") {
var numer = makeSpan("mfracnum", [makeSpan("", [buildGroup(group.value.numer)])]); var fstyle = style;
var mid = makeSpan("mfracmid", [makeSpan()]); if (group.value.size === "dfrac") {
var denom = makeSpan("mfracden", [buildGroup(group.value.denom)]); fstyle = Style.DISPLAY;
} else if (group.value.size === "tfrac") {
fstyle = Style.TEXT;
}
return makeSpan("minner mfrac", [numer, mid, denom]); 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 " + dstyle.cls(), [
makeSpan("", [buildGroup(dstyle, group.value.denom)])
]);
return makeSpan("minner mfrac " + fstyle.cls(), [numer, mid, denom]);
} else if (group.type === "color") { } 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") { } else if (group.type === "spacing") {
if (group.value === "\\ " || group.value === "\\space") { if (group.value === "\\ " || group.value === "\\space") {
return makeSpan("mord mspace", [textit(group.value)]); return makeSpan("mord mspace", [textit(group.value)]);
@ -80,15 +107,15 @@ var buildGroup = function(group, prev) {
return makeSpan("mord mspace " + spacingClassMap[group.value]); return makeSpan("mord mspace " + spacingClassMap[group.value]);
} }
} else if (group.type === "llap") { } else if (group.type === "llap") {
var inner = makeSpan("", buildExpression(group.value)); var inner = makeSpan("", buildExpression(style, group.value));
return makeSpan("llap", [inner]); return makeSpan("llap " + style.cls(), [inner]);
} else if (group.type === "rlap") { } else if (group.type === "rlap") {
var inner = makeSpan("", buildExpression(group.value)); var inner = makeSpan("", buildExpression(style, group.value));
return makeSpan("rlap", [inner]); return makeSpan("rlap " + style.cls(), [inner]);
} else if (group.type === "punct") { } else if (group.type === "punct") {
return makeSpan("mpunct", [textit(group.value)]); return makeSpan("mpunct", [textit(group.value)]);
} else if (group.type === "ordgroup") { } 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") { } else if (group.type === "namedfn") {
return makeSpan("mop", [textit(group.value.slice(1))]); return makeSpan("mop", [textit(group.value.slice(1))]);
} else { } else {
@ -141,11 +168,13 @@ var process = function(toParse, baseElem) {
console.error(e); console.error(e);
return false; return false;
} }
var style = Style.TEXT;
var expression = buildExpression(style, tree);
var span = makeSpan(style.cls(), expression);
clearNode(baseElem); clearNode(baseElem);
var expression = buildExpression(tree); baseElem.appendChild(span);
for (var i = 0; i < expression.length; i++) {
baseElem.appendChild(expression[i]);
}
return true; return true;
}; };

View File

@ -16,7 +16,7 @@ big parens
.mathmathmath { .mathmathmath {
font: normal 1.21em katex_main; font: normal 1.21em katex_main;
line-height: 1.4; line-height: 1.2;
} }
.mathit { .mathit {
@ -24,149 +24,47 @@ big parens
font-style: italic; font-style: italic;
} }
.mord + .mbin { .textstyle > .mbin + .minner { margin-left: 0.22222em; }
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; }
.mbin + .mord { .textstyle > .scriptstyle { font-size: 0.66667em; }
margin-left: 0.22222em; .scriptstyle > .scriptscriptstyle { font-size: 0.75em; }
}
.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;
}
.msub { .msub {
vertical-align: bottom; vertical-align: bottom;
font-size: 70%;
position: relative; position: relative;
top: 0.2em; top: 0.2em;
} }
@ -174,7 +72,6 @@ big parens
.msup { .msup {
position: relative; position: relative;
top: -0.5em; top: -0.5em;
font-size: 70%;
} }
.msupsub { .msupsub {
@ -186,13 +83,15 @@ big parens
.msupsub > .msup, .msupsub > .msub { .msupsub > .msup, .msupsub > .msub {
display: table-row; display: table-row;
vertical-align: baseline; vertical-align: baseline;
line-height: 1em;
} }
.mfrac { .mfrac { display: inline-table; }
display: inline-table;
vertical-align: 0.66em; /* 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 { .mfracnum, .mfracmid, .mfracden {
display: table-row; display: table-row;