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
This commit is contained in:
parent
a75bf1afc2
commit
f17bbf1b05
36
Lexer.js
36
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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
46
Parser.js
46
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
|
||||
|
|
28
buildTree.js
28
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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -305,6 +305,11 @@ big parens
|
|||
}
|
||||
}
|
||||
|
||||
.rule {
|
||||
display: inline-block;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.overline {
|
||||
.baseline-align-hack-outer;
|
||||
|
||||
|
|
|
@ -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}"
|
||||
}
|
||||
]
|
||||
|
|
BIN
test/huxley/Rule.hux/firefox-1.png
Normal file
BIN
test/huxley/Rule.hux/firefox-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
5
test/huxley/Rule.hux/record.json
Normal file
5
test/huxley/Rule.hux/record.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
[
|
||||
{
|
||||
"action": "screenshot"
|
||||
}
|
||||
]
|
|
@ -683,3 +683,59 @@ describe("An overline parser", function() {
|
|||
expect(parse.type).toMatch("overline");
|
||||
});
|
||||
});
|
||||
|
||||
describe("A rule parser", function() {
|
||||
var emRule = "\\rule{1em}{2em}";
|
||||
var exRule = "\\rule{1ex}{2em}";
|
||||
var badUnitRule = "\\rule{1px}{2em}";
|
||||
var noNumberRule = "\\rule{1em}{em}";
|
||||
var incompleteRule = "\\rule{1em}";
|
||||
var hardNumberRule = "\\rule{ 01.24ex}{2.450 em }";
|
||||
|
||||
it("should not fail", function() {
|
||||
expect(function() {
|
||||
parseTree(emRule);
|
||||
parseTree(exRule);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it("should not parse invalid units", function() {
|
||||
expect(function() {
|
||||
parseTree(badUnitRule);
|
||||
}).toThrow();
|
||||
|
||||
expect(function() {
|
||||
parseTree(noNumberRule);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it("should not parse incomplete rules", function() {
|
||||
expect(function() {
|
||||
parseTree(incompleteRule);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it("should produce a rule", function() {
|
||||
var parse = parseTree(emRule)[0];
|
||||
|
||||
expect(parse.type).toMatch("rule");
|
||||
});
|
||||
|
||||
it("should list the correct units", function() {
|
||||
var emParse = parseTree(emRule)[0];
|
||||
var exParse = parseTree(exRule)[0];
|
||||
|
||||
expect(emParse.value.width.unit).toMatch("em");
|
||||
expect(emParse.value.height.unit).toMatch("em");
|
||||
|
||||
expect(exParse.value.width.unit).toMatch("ex");
|
||||
expect(exParse.value.height.unit).toMatch("em");
|
||||
});
|
||||
|
||||
it("should parse the number correctly", function() {
|
||||
var hardNumberParse = parseTree(hardNumberRule)[0];
|
||||
|
||||
expect(hardNumberParse.value.width.number).toBeCloseTo(1.24);
|
||||
expect(hardNumberParse.value.height.number).toBeCloseTo(2.45);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue
Block a user