From fe6b67817c88c249d01415fd88a3ab51f9cae158 Mon Sep 17 00:00:00 2001 From: Emily Eisenberg Date: Tue, 5 Aug 2014 17:48:10 -0700 Subject: [PATCH] Add support for \overline Summary: Follow the instructions in the TeX book for drawing \overlines. This uses the same code as fractions to produce the bars. Also added the ability to cramp styles (i.e. T -> T' and T' -> T'). Test Plan: - Look at `\overline{x}`, `\overline{\dfrac{x}{y}+z}`, and `\blue{\overline{x}}` to make sure they look good. - Make sure the tests work - Make sure the huxley tests look good (Not here yet :( ) Reviewers: alpert Reviewed By: alpert Differential Revision: http://phabricator.khanacademy.org/D11604 --- Parser.js | 13 +++++++++ Style.js | 5 ++++ buildTree.js | 24 ++++++++++++++- static/katex.less | 39 +++++++++++++++++++++++++ test/huxley/Huxleyfile.json | 6 ++++ test/huxley/Overline.hux/firefox-1.png | Bin 0 -> 14934 bytes test/huxley/Overline.hux/record.json | 5 ++++ test/katex-tests.js | 16 ++++++++++ 8 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 test/huxley/Overline.hux/firefox-1.png create mode 100644 test/huxley/Overline.hux/record.json diff --git a/Parser.js b/Parser.js index 1bc2aede2..3116e3ebd 100644 --- a/Parser.js +++ b/Parser.js @@ -466,6 +466,19 @@ Parser.prototype.parseNucleus = function(pos, mode) { new ParseNode("katex", null, mode), nucleus.position ); + } else if (mode === "math" && nucleus.type === "\\overline") { + // If this is an overline, parse its argument and return + var group = this.parseGroup(nucleus.position, mode); + if (group) { + return new ParseResult( + new ParseNode("overline", group, mode), + group.position); + } else { + throw new ParseError("Expected group after '" + + nucleus.type + "'", + this.lexer, nucleus.position + ); + } } else if (symbols[mode][nucleus.text]) { // Otherwise if this is a no-argument function, find the type it // corresponds to in the symbols map diff --git a/Style.js b/Style.js index 8b1206723..94d1a7e0e 100644 --- a/Style.js +++ b/Style.js @@ -21,6 +21,10 @@ Style.prototype.fracDen = function() { return styles[fracDen[this.id]]; }; +Style.prototype.cramp = function() { + return styles[cramp[this.id]]; +}; + // HTML class name, like "displaystyle cramped" Style.prototype.cls = function() { return sizeNames[this.size] + (this.cramped ? " cramped" : " uncramped"); @@ -69,6 +73,7 @@ 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]; +var cramp = [Dc, Dc, Tc, Tc, Sc, Sc, SSc, SSc]; module.exports = { DISPLAY: styles[D], diff --git a/buildTree.js b/buildTree.js index 43e345c28..bea447364 100644 --- a/buildTree.js +++ b/buildTree.js @@ -55,7 +55,8 @@ var groupToType = { punct: "mpunct", ordgroup: "mord", namedfn: "mop", - katex: "mord" + katex: "mord", + overline: "mord" }; var getTypeOfGroup = function(group) { @@ -419,6 +420,27 @@ var groupTypes = { return makeSpan(["katex-logo"], [k, a, t, e, x], options.getColor()); }, + overline: function(group, options, prev) { + var innerGroup = buildGroup(group.value.result, + options.withStyle(options.style.cramp()).deepen()); + + // The theta variable in the TeXbook + var lineWidth = fontMetrics.metrics.defaultRuleThickness; + + var line = makeSpan(["overline-line"], [makeSpan([])]); + var inner = makeSpan(["overline-inner"], [innerGroup]); + var fixIE = makeSpan(["fix-ie"], []); + + line.style.top = (-inner.height - 3 * lineWidth) + "em"; + // The line is supposed to have 1 extra line width above it in height + // (TeXbook pg. 443, nr. 9) + line.height = inner.height + 5 * lineWidth; + + return makeSpan(["overline", "mord"], [ + line, inner, fixIE + ], options.getColor()); + }, + sizing: function(group, options, prev) { var inner = buildGroup(group.value.value, options.withSize(group.value.size), prev); diff --git a/static/katex.less b/static/katex.less index 73bef0c4b..541f9c6b3 100644 --- a/static/katex.less +++ b/static/katex.less @@ -308,6 +308,45 @@ big parens } } + .overline { + .baseline-align-hack-outer; + + > .overline-line, + > .overline-inner, + > .fix-ie { + .baseline-align-hack-middle; + position: relative; + text-align: center; + + > span { + .baseline-align-hack-inner; + } + } + + > .fix-ie { + display: inline-block; + } + + > .overline-line > span { + width: 100%; + + &:before { + border-bottom-style: solid; + border-bottom-width: 1px; + content: ""; + display: block; + } + + &:after { + border-bottom-style: solid; + border-bottom-width: 0.04em; + content: ""; + display: block; + margin-top: -1px; + } + } + } + .sizing { display: inline-block; diff --git a/test/huxley/Huxleyfile.json b/test/huxley/Huxleyfile.json index 42a80e516..d265f333e 100644 --- a/test/huxley/Huxleyfile.json +++ b/test/huxley/Huxleyfile.json @@ -99,5 +99,11 @@ "name": "DelimiterSizing", "screenSize": [1024, 768], "url": "http://localhost:7936/test/huxley/test.html?m=\\bigl\\uparrow\\Bigl\\downarrow\\biggl\\updownarrow\\Biggl\\Uparrow\\Biggr\\Downarrow\\biggr\\langle\\Bigr\\}\\bigr\\rfloor" + }, + + { + "name": "Overline", + "screenSize": [1024, 768], + "url": "http://localhost:7936/test/huxley/test.html?m=\\overline{x}\\overline{x}\\overline{x^{x^{x^x}}} \\blue\\overline{y}" } ] diff --git a/test/huxley/Overline.hux/firefox-1.png b/test/huxley/Overline.hux/firefox-1.png new file mode 100644 index 0000000000000000000000000000000000000000..d16429077b7cfbcfe61476b38dab2f05717e5741 GIT binary patch literal 14934 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 literal 0 HcmV?d00001 diff --git a/test/huxley/Overline.hux/record.json b/test/huxley/Overline.hux/record.json new file mode 100644 index 000000000..3cae6ac65 --- /dev/null +++ b/test/huxley/Overline.hux/record.json @@ -0,0 +1,5 @@ +[ + { + "action": "screenshot" + } +] diff --git a/test/katex-tests.js b/test/katex-tests.js index d645e0e1f..97a4faded 100644 --- a/test/katex-tests.js +++ b/test/katex-tests.js @@ -667,3 +667,19 @@ describe("A delimiter sizing parser", function() { expect(bigParse.value.size).toEqual(4); }); }); + +describe("An overline parser", function() { + var overline = "\\overline{x}"; + + it("should not fail", function() { + expect(function() { + parseTree(overline); + }).not.toThrow(); + }); + + it("should produce an overline", function() { + var parse = parseTree(overline)[0]; + + expect(parse.type).toMatch("overline"); + }); +});