From e472b0ba9d5785fb4ef6f475f1950127fa350577 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Sun, 14 Jul 2013 16:55:46 -0700 Subject: [PATCH] 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 --- Parser.js | 18 +++-- Style.js | 65 ++++++++++++++++ katex.js | 83 +++++++++++++------- static/katex.css | 191 +++++++++++------------------------------------ 4 files changed, 178 insertions(+), 179 deletions(-) create mode 100644 Style.js diff --git a/Parser.js b/Parser.js index 8e535c1d7..c2926fb2d 100644 --- 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 new file mode 100644 index 000000000..5d85427de --- /dev/null +++ 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 index 878f8158d..66e521739 100644 --- 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)])]); - var mid = makeSpan("mfracmid", [makeSpan()]); - var denom = makeSpan("mfracden", [buildGroup(group.value.denom)]); + } 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; + } - 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") { - 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 index 7d9a6b21e..23665923e 100644 --- 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; -} +.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; } -.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 > .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;