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"]
|
[/^~/, "spacing"]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
var whitespaceRegex = /^\s*/;
|
||||||
|
var whitespaceConcatRegex = /^( +|\\ +)/;
|
||||||
|
|
||||||
// Build a regex to easily parse the functions
|
// Build a regex to easily parse the functions
|
||||||
var anyFunc = /^\\(?:[a-zA-Z]+|.)/;
|
var anyFunc = /^\\(?:[a-zA-Z]+|.)/;
|
||||||
|
|
||||||
|
@ -44,12 +47,12 @@ Lexer.prototype._innerLex = function(pos, normals, ignoreWhitespace) {
|
||||||
|
|
||||||
// Get rid of whitespace
|
// Get rid of whitespace
|
||||||
if (ignoreWhitespace) {
|
if (ignoreWhitespace) {
|
||||||
var whitespace = input.match(/^\s*/)[0];
|
var whitespace = input.match(whitespaceRegex)[0];
|
||||||
pos += whitespace.length;
|
pos += whitespace.length;
|
||||||
input = input.slice(whitespace.length);
|
input = input.slice(whitespace.length);
|
||||||
} else {
|
} else {
|
||||||
// Do the funky concatenation of whitespace
|
// Do the funky concatenation of whitespace
|
||||||
var whitespace = input.match(/^( +|\\ +)/);
|
var whitespace = input.match(whitespaceConcatRegex);
|
||||||
if (whitespace !== null) {
|
if (whitespace !== null) {
|
||||||
return new LexResult(" ", " ", pos + whitespace[0].length);
|
return new LexResult(" ", " ", pos + whitespace[0].length);
|
||||||
}
|
}
|
||||||
|
@ -90,7 +93,7 @@ Lexer.prototype._innerLexColor = function(pos) {
|
||||||
var input = this._input.slice(pos);
|
var input = this._input.slice(pos);
|
||||||
|
|
||||||
// Ignore whitespace
|
// Ignore whitespace
|
||||||
var whitespace = input.match(/^\s*/)[0];
|
var whitespace = input.match(whitespaceRegex)[0];
|
||||||
pos += whitespace.length;
|
pos += whitespace.length;
|
||||||
input = input.slice(whitespace.length);
|
input = input.slice(whitespace.length);
|
||||||
|
|
||||||
|
@ -104,6 +107,31 @@ Lexer.prototype._innerLexColor = function(pos) {
|
||||||
throw new ParseError("Invalid color", this, 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
|
// Lex a single token
|
||||||
Lexer.prototype.lex = function(pos, mode) {
|
Lexer.prototype.lex = function(pos, mode) {
|
||||||
if (mode === "math") {
|
if (mode === "math") {
|
||||||
|
@ -112,6 +140,8 @@ Lexer.prototype.lex = function(pos, mode) {
|
||||||
return this._innerLex(pos, textNormals, false);
|
return this._innerLex(pos, textNormals, false);
|
||||||
} else if (mode === "color") {
|
} else if (mode === "color") {
|
||||||
return this._innerLexColor(pos);
|
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 = [
|
var delimiters = [
|
||||||
"(", ")", "[", "\\lbrack", "]", "\\rbrack",
|
"(", ")", "[", "\\lbrack", "]", "\\rbrack",
|
||||||
"\\{", "\\lbrace", "\\}", "\\rbrace",
|
"\\{", "\\lbrace", "\\}", "\\rbrace",
|
||||||
|
@ -479,6 +500,31 @@ Parser.prototype.parseNucleus = function(pos, mode) {
|
||||||
this.lexer, nucleus.position
|
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]) {
|
} else if (symbols[mode][nucleus.text]) {
|
||||||
// Otherwise if this is a no-argument function, find the type it
|
// Otherwise if this is a no-argument function, find the type it
|
||||||
// corresponds to in the symbols map
|
// corresponds to in the symbols map
|
||||||
|
|
28
buildTree.js
28
buildTree.js
|
@ -72,7 +72,8 @@ var groupToType = {
|
||||||
ordgroup: "mord",
|
ordgroup: "mord",
|
||||||
namedfn: "mop",
|
namedfn: "mop",
|
||||||
katex: "mord",
|
katex: "mord",
|
||||||
overline: "mord"
|
overline: "mord",
|
||||||
|
rule: "mord"
|
||||||
};
|
};
|
||||||
|
|
||||||
var getTypeOfGroup = function(group) {
|
var getTypeOfGroup = function(group) {
|
||||||
|
@ -696,6 +697,31 @@ var groupTypes = {
|
||||||
} else {
|
} else {
|
||||||
throw new ParseError("Illegal delimiter: '" + original + "'");
|
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 {
|
.overline {
|
||||||
.baseline-align-hack-outer;
|
.baseline-align-hack-outer;
|
||||||
|
|
||||||
|
|
|
@ -123,5 +123,11 @@
|
||||||
"name": "SupSubHorizSpacing",
|
"name": "SupSubHorizSpacing",
|
||||||
"screenSize": [1024, 768],
|
"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|"
|
"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");
|
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