Add a \color command for custom colors
Summary: Keep track of the color inside the style now, and use that when we are rendering things. Added a custom lexing mode to handle lexing colors correctly. Prefixed the old katex colors (internally) with "katex-" to distinguish them from CSS colors. Test Plan: - Run the normal tests, see they work - Run the huxley tests, see that they didn't change except for the color one which looks right Reviewers: alpert Reviewed By: alpert Differential Revision: http://phabricator.khanacademy.org/D7763
This commit is contained in:
parent
7723d3dcaf
commit
d729ba5281
23
Lexer.js
23
Lexer.js
|
@ -81,12 +81,35 @@ Lexer.prototype._innerLex = function(pos, normals, ignoreWhitespace) {
|
|||
"' at position " + pos);
|
||||
}
|
||||
|
||||
// A regex to match a CSS color (like #ffffff or BlueViolet)
|
||||
var cssColor = /^(#[a-z0-9]+|[a-z]+)/i;
|
||||
|
||||
Lexer.prototype._innerLexColor = function(pos) {
|
||||
var input = this._input.slice(pos);
|
||||
|
||||
// Ignore whitespace
|
||||
var whitespace = input.match(/^\s*/)[0];
|
||||
pos += whitespace.length;
|
||||
input = input.slice(whitespace.length);
|
||||
|
||||
var match;
|
||||
if ((match = input.match(cssColor))) {
|
||||
// If we look like a color, return a color
|
||||
return new LexResult("color", match[0], pos + match[0].length);
|
||||
}
|
||||
|
||||
// We didn't match a color, so throw an error.
|
||||
throw new ParseError("Invalid color at position " + pos);
|
||||
};
|
||||
|
||||
// Lex a single token
|
||||
Lexer.prototype.lex = function(pos, mode) {
|
||||
if (mode === "math") {
|
||||
return this._innerLex(pos, mathNormals, true);
|
||||
} else if (mode === "text") {
|
||||
return this._innerLex(pos, textNormals, false);
|
||||
} else if (mode === "color") {
|
||||
return this._innerLexColor(pos);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
14
Options.js
14
Options.js
|
@ -40,4 +40,18 @@ Options.prototype.reset = function() {
|
|||
this.style, this.size);
|
||||
};
|
||||
|
||||
var colorMap = {
|
||||
"katex-blue": "#6495ed",
|
||||
"katex-orange": "#ffa500",
|
||||
"katex-pink": "#ff00af",
|
||||
"katex-red": "#df0030",
|
||||
"katex-green": "#28ae7b",
|
||||
"katex-gray": "gray",
|
||||
"katex-purple": "#9d38bd"
|
||||
};
|
||||
|
||||
Options.prototype.getColor = function() {
|
||||
return colorMap[this.color] || this.color;
|
||||
};
|
||||
|
||||
module.exports = Options;
|
||||
|
|
49
Parser.js
49
Parser.js
|
@ -193,6 +193,26 @@ Parser.prototype.parseGroup = function(pos, mode) {
|
|||
}
|
||||
};
|
||||
|
||||
// Parses a custom color group, which looks like "{#ffffff}"
|
||||
Parser.prototype.parseColorGroup = function(pos, mode) {
|
||||
var start = this.lexer.lex(pos, mode);
|
||||
// Try to parse an open brace
|
||||
if (start.type === "{") {
|
||||
// Parse the color
|
||||
var color = this.lexer.lex(start.position, "color");
|
||||
// Make sure we get a close brace
|
||||
var closeBrace = this.lexer.lex(color.position, mode);
|
||||
expect(closeBrace, "}");
|
||||
return new ParseResult(
|
||||
new ParseNode("color", color.text),
|
||||
closeBrace.position);
|
||||
} else {
|
||||
// It has to have an open brace, so if it doesn't we throw
|
||||
throw new ParseError(
|
||||
"Parse error: There must be braces around colors");
|
||||
}
|
||||
};
|
||||
|
||||
// A list of 1-argument color functions
|
||||
var colorFuncs = [
|
||||
"\\blue", "\\orange", "\\pink", "\\red", "\\green", "\\gray", "\\purple"
|
||||
|
@ -229,12 +249,39 @@ Parser.prototype.parseNucleus = function(pos, mode) {
|
|||
}
|
||||
return new ParseResult(
|
||||
new ParseNode("color",
|
||||
{color: nucleus.type.slice(1), value: atoms}, mode),
|
||||
{color: "katex-" + nucleus.type.slice(1), value: atoms},
|
||||
mode),
|
||||
group.position);
|
||||
} else {
|
||||
throw new ParseError(
|
||||
"Expected group after '" + nucleus.text + "'");
|
||||
}
|
||||
} else if (nucleus.type === "\\color") {
|
||||
// If this is a custom color function, parse its first argument as a
|
||||
// custom color and its second argument normally
|
||||
var color = this.parseColorGroup(nucleus.position, mode);
|
||||
if (color) {
|
||||
var inner = this.parseGroup(color.position, mode);
|
||||
if (inner) {
|
||||
var atoms;
|
||||
if (inner.result.type === "ordgroup") {
|
||||
atoms = inner.result.value;
|
||||
} else {
|
||||
atoms = [inner.result];
|
||||
}
|
||||
return new ParseResult(
|
||||
new ParseNode("color",
|
||||
{color: color.result.value, value: atoms},
|
||||
mode),
|
||||
inner.position);
|
||||
} else {
|
||||
throw new ParseError(
|
||||
"Expected second group after '" + nucleus.text + "'");
|
||||
}
|
||||
} else {
|
||||
throw new ParseError(
|
||||
"Expected color after '" + nucleus.text + "'");
|
||||
}
|
||||
} else if (mode === "math" && utils.contains(sizeFuncs, nucleus.type)) {
|
||||
// If this is a size function, parse its argument and return
|
||||
var group = this.parseGroup(nucleus.position, mode);
|
||||
|
|
56
buildTree.js
56
buildTree.js
|
@ -18,7 +18,7 @@ var buildExpression = function(expression, options, prev) {
|
|||
return groups;
|
||||
};
|
||||
|
||||
var makeSpan = function(classes, children) {
|
||||
var makeSpan = function(classes, children, color) {
|
||||
var height = 0;
|
||||
var depth = 0;
|
||||
|
||||
|
@ -33,7 +33,13 @@ var makeSpan = function(classes, children) {
|
|||
}
|
||||
}
|
||||
|
||||
return new domTree.span(classes, children, height, depth);
|
||||
var span = new domTree.span(classes, children, height, depth);
|
||||
|
||||
if (color) {
|
||||
span.style.color = color;
|
||||
}
|
||||
|
||||
return span;
|
||||
};
|
||||
|
||||
var groupToType = {
|
||||
|
@ -71,15 +77,17 @@ var getTypeOfGroup = function(group) {
|
|||
var groupTypes = {
|
||||
mathord: function(group, options, prev) {
|
||||
return makeSpan(
|
||||
["mord", options.color],
|
||||
[mathit(group.value, group.mode)]
|
||||
["mord"],
|
||||
[mathit(group.value, group.mode)],
|
||||
options.getColor()
|
||||
);
|
||||
},
|
||||
|
||||
textord: function(group, options, prev) {
|
||||
return makeSpan(
|
||||
["mord", options.color],
|
||||
[mathrm(group.value, group.mode)]
|
||||
["mord"],
|
||||
[mathrm(group.value, group.mode)],
|
||||
options.getColor()
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -96,15 +104,17 @@ var groupTypes = {
|
|||
className = "mord";
|
||||
}
|
||||
return makeSpan(
|
||||
[className, options.color],
|
||||
[mathrm(group.value, group.mode)]
|
||||
[className],
|
||||
[mathrm(group.value, group.mode)],
|
||||
options.getColor()
|
||||
);
|
||||
},
|
||||
|
||||
rel: function(group, options, prev) {
|
||||
return makeSpan(
|
||||
["mrel", options.color],
|
||||
[mathrm(group.value, group.mode)]
|
||||
["mrel"],
|
||||
[mathrm(group.value, group.mode)],
|
||||
options.getColor()
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -201,15 +211,17 @@ var groupTypes = {
|
|||
|
||||
open: function(group, options, prev) {
|
||||
return makeSpan(
|
||||
["mopen", options.color],
|
||||
[mathrm(group.value, group.mode)]
|
||||
["mopen"],
|
||||
[mathrm(group.value, group.mode)],
|
||||
options.getColor()
|
||||
);
|
||||
},
|
||||
|
||||
close: function(group, options, prev) {
|
||||
return makeSpan(
|
||||
["mclose", options.color],
|
||||
[mathrm(group.value, group.mode)]
|
||||
["mclose"],
|
||||
[mathrm(group.value, group.mode)],
|
||||
options.getColor()
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -276,9 +288,9 @@ var groupTypes = {
|
|||
|
||||
var wrap = makeSpan([options.style.reset(), fstyle.cls()], [frac]);
|
||||
|
||||
return makeSpan(["minner", options.color], [
|
||||
return makeSpan(["minner"], [
|
||||
makeSpan(["mfrac"], [wrap])
|
||||
]);
|
||||
], options.getColor());
|
||||
},
|
||||
|
||||
color: function(group, options, prev) {
|
||||
|
@ -339,13 +351,15 @@ var groupTypes = {
|
|||
|
||||
punct: function(group, options, prev) {
|
||||
return makeSpan(
|
||||
["mpunct", options.color],
|
||||
[mathrm(group.value, group.mode)]
|
||||
["mpunct"],
|
||||
[mathrm(group.value, group.mode)],
|
||||
options.getColor()
|
||||
);
|
||||
},
|
||||
|
||||
ordgroup: function(group, options, prev) {
|
||||
return makeSpan(["mord", options.style.cls()],
|
||||
return makeSpan(
|
||||
["mord", options.style.cls()],
|
||||
buildExpression(group.value, options.reset())
|
||||
);
|
||||
},
|
||||
|
@ -356,7 +370,7 @@ var groupTypes = {
|
|||
chars.push(mathrm(group.value[i], group.mode));
|
||||
}
|
||||
|
||||
return makeSpan(["mop", options.color], chars);
|
||||
return makeSpan(["mop"], chars, options.getColor());
|
||||
},
|
||||
|
||||
katex: function(group, options, prev) {
|
||||
|
@ -374,7 +388,7 @@ var groupTypes = {
|
|||
|
||||
var x = makeSpan(["x"], [mathrm("X", group.mode)]);
|
||||
|
||||
return makeSpan(["katex-logo", options.color], [k, a, t, e, x]);
|
||||
return makeSpan(["katex-logo"], [k, a, t, e, x], options.getColor());
|
||||
},
|
||||
|
||||
sizing: function(group, options, prev) {
|
||||
|
|
|
@ -288,14 +288,6 @@ big parens
|
|||
left: 0;
|
||||
}
|
||||
|
||||
.blue { color: #6495ed; }
|
||||
.orange { color: #ffa500; }
|
||||
.pink { color: #ff00af; }
|
||||
.red { color: #df0030; }
|
||||
.green { color: #28ae7b; }
|
||||
.gray { color: gray; }
|
||||
.purple { color: #9d38bd; }
|
||||
|
||||
.katex-logo {
|
||||
.a {
|
||||
font-size: 0.75em;
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.4 KiB |
|
@ -11,7 +11,7 @@ url=http://localhost:7936/test/huxley/test.html?m=\dfrac{\frac{a}{b}}{\frac{c}{d
|
|||
url=http://localhost:7936/test/huxley/test.html?m=a^{a^a_a}_{a^a_a}
|
||||
|
||||
[Colors]
|
||||
url=http://localhost:7936/test/huxley/test.html?m=\blue{a}\green{b}\red{c}
|
||||
url=http://localhost:7936/test/huxley/test.html?m=\blue{a}\color{%%230f0}{b}\color{red}{c}
|
||||
|
||||
[GreekLetters]
|
||||
url=http://localhost:7936/test/huxley/test.html?m=\alpha\beta\gamma\omega
|
||||
|
|
|
@ -496,3 +496,41 @@ describe("A text parser", function() {
|
|||
expect(group[3].type).toMatch("spacing");
|
||||
});
|
||||
});
|
||||
|
||||
describe("A color parser", function() {
|
||||
var colorExpression = "\\blue{x}";
|
||||
var customColorExpression = "\\color{#fA6}{x}";
|
||||
var badCustomColorExpression = "\\color{bad-color}{x}";
|
||||
|
||||
it("should not fail", function() {
|
||||
expect(function() {
|
||||
parseTree(colorExpression);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it("should build a color node", function() {
|
||||
var parse = parseTree(colorExpression)[0];
|
||||
|
||||
expect(parse.type).toMatch("color");
|
||||
expect(parse.value.color).toBeDefined();
|
||||
expect(parse.value.value).toBeDefined();
|
||||
});
|
||||
|
||||
it("should parse a custom color", function() {
|
||||
expect(function() {
|
||||
parseTree(customColorExpression);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it("should correctly extract the custom color", function() {
|
||||
var parse = parseTree(customColorExpression)[0];
|
||||
|
||||
expect(parse.value.color).toMatch("#fA6");
|
||||
});
|
||||
|
||||
it("should not parse a bad custom color", function() {
|
||||
expect(function() {
|
||||
parseTree(badCustomColorExpression);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue
Block a user