From f17bbf1b0533d93ad282f44ad98b06ea8a38c95a Mon Sep 17 00:00:00 2001 From: Emily Eisenberg Date: Fri, 29 Aug 2014 14:45:27 -0700 Subject: [PATCH] Add the '\rule' command for drawing boxes Summary: Supports the 'ex' and 'em' units for sizes. Doesn't support the optional depth argument. Test Plan: - See that the huxley test looks good, and nothing else changed - See that the tests pass Reviewers: alpert Reviewed By: alpert Differential Revision: http://phabricator.khanacademy.org/D12777 --- Lexer.js | 36 +++++++++++++++++-- Parser.js | 46 ++++++++++++++++++++++++ buildTree.js | 28 ++++++++++++++- static/katex.less | 5 +++ test/huxley/Huxleyfile.json | 6 ++++ test/huxley/Rule.hux/firefox-1.png | Bin 0 -> 4454 bytes test/huxley/Rule.hux/record.json | 5 +++ test/katex-tests.js | 56 +++++++++++++++++++++++++++++ 8 files changed, 178 insertions(+), 4 deletions(-) create mode 100644 test/huxley/Rule.hux/firefox-1.png create mode 100644 test/huxley/Rule.hux/record.json diff --git a/Lexer.js b/Lexer.js index 507c3abca..2e55ee484 100644 --- a/Lexer.js +++ b/Lexer.js @@ -36,6 +36,9 @@ var textNormals = [ [/^~/, "spacing"] ]; +var whitespaceRegex = /^\s*/; +var whitespaceConcatRegex = /^( +|\\ +)/; + // Build a regex to easily parse the functions var anyFunc = /^\\(?:[a-zA-Z]+|.)/; @@ -44,12 +47,12 @@ Lexer.prototype._innerLex = function(pos, normals, ignoreWhitespace) { // Get rid of whitespace if (ignoreWhitespace) { - var whitespace = input.match(/^\s*/)[0]; + var whitespace = input.match(whitespaceRegex)[0]; pos += whitespace.length; input = input.slice(whitespace.length); } else { // Do the funky concatenation of whitespace - var whitespace = input.match(/^( +|\\ +)/); + var whitespace = input.match(whitespaceConcatRegex); if (whitespace !== null) { return new LexResult(" ", " ", pos + whitespace[0].length); } @@ -90,7 +93,7 @@ Lexer.prototype._innerLexColor = function(pos) { var input = this._input.slice(pos); // Ignore whitespace - var whitespace = input.match(/^\s*/)[0]; + var whitespace = input.match(whitespaceRegex)[0]; pos += whitespace.length; input = input.slice(whitespace.length); @@ -104,6 +107,31 @@ Lexer.prototype._innerLexColor = function(pos) { throw new ParseError("Invalid color", this, pos); }; +var sizeRegex = /^(\d+(?:\.\d*)?|\.\d+)\s*([a-z]{2})/; + +Lexer.prototype._innerLexSize = function(pos) { + var input = this._input.slice(pos); + + // Ignore whitespace + var whitespace = input.match(whitespaceRegex)[0]; + pos += whitespace.length; + input = input.slice(whitespace.length); + + var match; + if ((match = input.match(sizeRegex))) { + var unit = match[2]; + if (unit !== "em" && unit !== "ex") { + throw new ParseError("Invalid unit: '" + unit + "'", this, pos); + } + return new LexResult("size", { + number: +match[1], + unit: unit + }, pos + match[0].length); + } + + throw new ParseError("Invalid size", this, pos); +}; + // Lex a single token Lexer.prototype.lex = function(pos, mode) { if (mode === "math") { @@ -112,6 +140,8 @@ Lexer.prototype.lex = function(pos, mode) { return this._innerLex(pos, textNormals, false); } else if (mode === "color") { return this._innerLexColor(pos); + } else if (mode === "size") { + return this._innerLexSize(pos); } }; diff --git a/Parser.js b/Parser.js index 3116e3ebd..028686021 100644 --- a/Parser.js +++ b/Parser.js @@ -259,6 +259,27 @@ Parser.prototype.parseTextGroup = function(pos, mode) { } }; +Parser.prototype.parseSizeGroup = function(pos, mode) { + var start = this.lexer.lex(pos, mode); + // Try to parse an open brace + if (start.type === "{") { + // Parse the size + var size = this.lexer.lex(start.position, "size"); + // Make sure we get a close brace + var closeBrace = this.lexer.lex(size.position, mode); + this.expect(closeBrace, "}"); + return new ParseResult( + new ParseNode("size", size.text), + closeBrace.position); + } else { + // It has to have an open brace, so if it doesn't we throw + throw new ParseError( + "There must be braces around sizes", + this.lexer, pos + ); + } +}; + var delimiters = [ "(", ")", "[", "\\lbrack", "]", "\\rbrack", "\\{", "\\lbrace", "\\}", "\\rbrace", @@ -479,6 +500,31 @@ Parser.prototype.parseNucleus = function(pos, mode) { this.lexer, nucleus.position ); } + } else if (mode === "math" && nucleus.type === "\\rule") { + // Parse the width of the rule + var widthGroup = this.parseSizeGroup(nucleus.position, mode); + if (widthGroup) { + // Parse the height of the rule + var heightGroup = this.parseSizeGroup(widthGroup.position, mode); + if (heightGroup) { + return new ParseResult( + new ParseNode("rule", { + width: widthGroup.result.value, + height: heightGroup.result.value + }, mode), + heightGroup.position); + } else { + throw new ParseError("Expected second size group after '" + + nucleus.type + "'", + this.lexer, nucleus.position + ); + } + } else { + throw new ParseError("Expected size 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/buildTree.js b/buildTree.js index 7cd18a78c..5ab3b6ef8 100644 --- a/buildTree.js +++ b/buildTree.js @@ -72,7 +72,8 @@ var groupToType = { ordgroup: "mord", namedfn: "mop", katex: "mord", - overline: "mord" + overline: "mord", + rule: "mord" }; var getTypeOfGroup = function(group) { @@ -696,6 +697,31 @@ var groupTypes = { } else { throw new ParseError("Illegal delimiter: '" + original + "'"); } + }, + + rule: function(group, options, prev) { + // Make an empty span for the rule + var rule = makeSpan(["mord", "rule"], []); + + var width = group.value.width.number; + if (group.value.width.unit === "ex") { + width *= fontMetrics.metrics.xHeight; + } + + var height = group.value.height.number; + if (group.value.height.unit === "ex") { + height *= fontMetrics.metrics.xHeight; + } + + // Style the rule to the right size + rule.style.borderRightWidth = width + "em"; + rule.style.borderTopWidth = height + "em"; + + // Record the height and width + rule.width = width; + rule.height = height; + + return rule; } }; diff --git a/static/katex.less b/static/katex.less index 822b9c3ca..47b0ceaea 100644 --- a/static/katex.less +++ b/static/katex.less @@ -305,6 +305,11 @@ big parens } } + .rule { + display: inline-block; + border-style: solid; + } + .overline { .baseline-align-hack-outer; diff --git a/test/huxley/Huxleyfile.json b/test/huxley/Huxleyfile.json index 335e6075c..de7d40439 100644 --- a/test/huxley/Huxleyfile.json +++ b/test/huxley/Huxleyfile.json @@ -123,5 +123,11 @@ "name": "SupSubHorizSpacing", "screenSize": [1024, 768], "url": "http://localhost:7936/test/huxley/test.html?m=x^{x^{x}}\\Big|x_{x_{x_{x_{x}}}}\\bigg|x^{x^{x_{x_{x_{x_{x}}}}}}\\bigg|" + }, + + { + "name": "Rule", + "screenSize": [1024, 768], + "url": "http://localhost:7936/test/huxley/test.html?m=\\rule{1em}{0.5em}\\rule{1ex}{2ex}\\rule{1em}{1ex}\\rule{1em}{0.431ex}" } ] diff --git a/test/huxley/Rule.hux/firefox-1.png b/test/huxley/Rule.hux/firefox-1.png new file mode 100644 index 0000000000000000000000000000000000000000..8bc17bb6e6376a7b7fb7fd616874801188fd63a8 GIT binary patch literal 4454 zcmeAS@N?(olHy`uVBq!ia0y~yU;#3j893O0)UI8kyg-V{)5S5Qg7J-1-6aMFL3t4G z?ZHN07DEQ+4JNz(hU?{g;NU#Se*1YH$QXmQj6m|4DUdj@2V}~O>p+6xvlNg>h+_tl zXR?7rL#6+Zz~^)RsD8f(n9Q=AS-%VN6*tsO#SWS;4SpJMHWkHJR>78hb(> zbq2ItqAbshrsvUwG@6jGRQIFV3c2w!8XBXafvaIWnl?t$#%S6grVBZmQ$};j;L0g> a3>pu_*cQFqs}AfpGkCiCxvX