First attempt at \text
function
Summary: Make all of the parsing functions keep track of whether they are parsing in math mode or text mode. Then, add a separate lexing function to lex text mode, which is different than the normal mode because it does weird things with spacing and allows a different set of characters. Test Plan: - See that the normal tests work - See that the huxley screenshot looks reasonable - See that none of the other huxley screenshots changed Reviewers: alpert Reviewed By: alpert Differential Revision: http://phabricator.khanacademy.org/D7578
This commit is contained in:
parent
2eca338e23
commit
7723d3dcaf
34
Lexer.js
34
Lexer.js
|
@ -13,7 +13,7 @@ function LexResult(type, text, position) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// "normal" types of tokens
|
// "normal" types of tokens
|
||||||
var normals = [
|
var mathNormals = [
|
||||||
[/^[/|@."`0-9]/, "textord"],
|
[/^[/|@."`0-9]/, "textord"],
|
||||||
[/^[a-zA-Z]/, "mathord"],
|
[/^[a-zA-Z]/, "mathord"],
|
||||||
[/^[*+-]/, "bin"],
|
[/^[*+-]/, "bin"],
|
||||||
|
@ -28,17 +28,30 @@ var normals = [
|
||||||
[/^[)\]?!]/, "close"]
|
[/^[)\]?!]/, "close"]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
var textNormals = [
|
||||||
|
[/^[a-zA-Z0-9`!@*()-=+\[\]'";:?\/.,]/, "textord"],
|
||||||
|
[/^{/, "{"],
|
||||||
|
[/^}/, "}"]
|
||||||
|
];
|
||||||
|
|
||||||
// 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]+|.)/;
|
||||||
|
|
||||||
// Lex a single token
|
Lexer.prototype._innerLex = function(pos, normals, ignoreWhitespace) {
|
||||||
Lexer.prototype.lex = function(pos) {
|
|
||||||
var input = this._input.slice(pos);
|
var input = this._input.slice(pos);
|
||||||
|
|
||||||
// Get rid of whitespace
|
// Get rid of whitespace
|
||||||
var whitespace = input.match(/^\s*/)[0];
|
if (ignoreWhitespace) {
|
||||||
pos += whitespace.length;
|
var whitespace = input.match(/^\s*/)[0];
|
||||||
input = input.slice(whitespace.length);
|
pos += whitespace.length;
|
||||||
|
input = input.slice(whitespace.length);
|
||||||
|
} else {
|
||||||
|
// Do the funky concatenation of whitespace
|
||||||
|
var whitespace = input.match(/^( +|\\ +)/);
|
||||||
|
if (whitespace !== null) {
|
||||||
|
return new LexResult(" ", " ", pos + whitespace[0].length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If there's no more input to parse, return an EOF token
|
// If there's no more input to parse, return an EOF token
|
||||||
if (input.length === 0) {
|
if (input.length === 0) {
|
||||||
|
@ -66,6 +79,15 @@ Lexer.prototype.lex = function(pos) {
|
||||||
// We didn't match any of the tokens, so throw an error.
|
// We didn't match any of the tokens, so throw an error.
|
||||||
throw new ParseError("Unexpected character: '" + input[0] +
|
throw new ParseError("Unexpected character: '" + input[0] +
|
||||||
"' at position " + pos);
|
"' 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);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Lexer;
|
module.exports = Lexer;
|
||||||
|
|
117
Parser.js
117
Parser.js
|
@ -16,9 +16,10 @@ function ParseResult(result, newPosition) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The resulting parse tree nodes of the parse tree.
|
// The resulting parse tree nodes of the parse tree.
|
||||||
function ParseNode(type, value) {
|
function ParseNode(type, value, mode) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
this.mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks a result to make sure it has the right type, and throws an
|
// Checks a result to make sure it has the right type, and throws an
|
||||||
|
@ -37,27 +38,27 @@ Parser.prototype.parse = function(input) {
|
||||||
this.lexer = new Lexer(input);
|
this.lexer = new Lexer(input);
|
||||||
|
|
||||||
// Try to parse the input
|
// Try to parse the input
|
||||||
var parse = this.parseInput(0);
|
var parse = this.parseInput(0, "math");
|
||||||
return parse.result;
|
return parse.result;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parses an entire input tree
|
// Parses an entire input tree
|
||||||
Parser.prototype.parseInput = function(pos) {
|
Parser.prototype.parseInput = function(pos, mode) {
|
||||||
// Parse an expression
|
// Parse an expression
|
||||||
var expression = this.parseExpression(pos);
|
var expression = this.parseExpression(pos, mode);
|
||||||
// If we succeeded, make sure there's an EOF at the end
|
// If we succeeded, make sure there's an EOF at the end
|
||||||
var EOF = this.lexer.lex(expression.position);
|
var EOF = this.lexer.lex(expression.position, mode);
|
||||||
expect(EOF, "EOF");
|
expect(EOF, "EOF");
|
||||||
return expression;
|
return expression;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parses an "expression", which is a list of atoms
|
// Parses an "expression", which is a list of atoms
|
||||||
Parser.prototype.parseExpression = function(pos) {
|
Parser.prototype.parseExpression = function(pos, mode) {
|
||||||
// Start with a list of nodes
|
// Start with a list of nodes
|
||||||
var expression = [];
|
var expression = [];
|
||||||
while (true) {
|
while (true) {
|
||||||
// Try to parse atoms
|
// Try to parse atoms
|
||||||
var parse = this.parseAtom(pos);
|
var parse = this.parseAtom(pos, mode);
|
||||||
if (parse) {
|
if (parse) {
|
||||||
// Copy them into the list
|
// Copy them into the list
|
||||||
expression.push(parse.result);
|
expression.push(parse.result);
|
||||||
|
@ -70,12 +71,16 @@ Parser.prototype.parseExpression = function(pos) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parses a superscript expression, like "^3"
|
// Parses a superscript expression, like "^3"
|
||||||
Parser.prototype.parseSuperscript = function(pos) {
|
Parser.prototype.parseSuperscript = function(pos, mode) {
|
||||||
|
if (mode !== "math") {
|
||||||
|
throw new ParseError("Trying to parse superscript in non-math mode");
|
||||||
|
}
|
||||||
|
|
||||||
// Try to parse a "^" character
|
// Try to parse a "^" character
|
||||||
var sup = this.lexer.lex(pos);
|
var sup = this.lexer.lex(pos, mode);
|
||||||
if (sup.type === "^") {
|
if (sup.type === "^") {
|
||||||
// If we got one, parse the corresponding group
|
// If we got one, parse the corresponding group
|
||||||
var group = this.parseGroup(sup.position);
|
var group = this.parseGroup(sup.position, mode);
|
||||||
if (group) {
|
if (group) {
|
||||||
return group;
|
return group;
|
||||||
} else {
|
} else {
|
||||||
|
@ -85,19 +90,23 @@ Parser.prototype.parseSuperscript = function(pos) {
|
||||||
} else if (sup.type === "'") {
|
} else if (sup.type === "'") {
|
||||||
var pos = sup.position;
|
var pos = sup.position;
|
||||||
return new ParseResult(
|
return new ParseResult(
|
||||||
new ParseNode("textord", "\\prime"), sup.position);
|
new ParseNode("textord", "\\prime"), sup.position, mode);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parses a subscript expression, like "_3"
|
// Parses a subscript expression, like "_3"
|
||||||
Parser.prototype.parseSubscript = function(pos) {
|
Parser.prototype.parseSubscript = function(pos, mode) {
|
||||||
|
if (mode !== "math") {
|
||||||
|
throw new ParseError("Trying to parse subscript in non-math mode");
|
||||||
|
}
|
||||||
|
|
||||||
// Try to parse a "_" character
|
// Try to parse a "_" character
|
||||||
var sub = this.lexer.lex(pos);
|
var sub = this.lexer.lex(pos, mode);
|
||||||
if (sub.type === "_") {
|
if (sub.type === "_") {
|
||||||
// If we got one, parse the corresponding group
|
// If we got one, parse the corresponding group
|
||||||
var group = this.parseGroup(sub.position);
|
var group = this.parseGroup(sub.position, mode);
|
||||||
if (group) {
|
if (group) {
|
||||||
return group;
|
return group;
|
||||||
} else {
|
} else {
|
||||||
|
@ -111,12 +120,18 @@ Parser.prototype.parseSubscript = function(pos) {
|
||||||
|
|
||||||
// Parses an atom, which consists of a nucleus, and an optional superscript and
|
// Parses an atom, which consists of a nucleus, and an optional superscript and
|
||||||
// subscript
|
// subscript
|
||||||
Parser.prototype.parseAtom = function(pos) {
|
Parser.prototype.parseAtom = function(pos, mode) {
|
||||||
// Parse the nucleus
|
// Parse the nucleus
|
||||||
var nucleus = this.parseGroup(pos);
|
var nucleus = this.parseGroup(pos, mode);
|
||||||
var nextPos = pos;
|
var nextPos = pos;
|
||||||
var nucleusNode;
|
var nucleusNode;
|
||||||
|
|
||||||
|
// Text mode doesn't have superscripts or subscripts, so we only parse the
|
||||||
|
// nucleus in this case
|
||||||
|
if (mode === "text") {
|
||||||
|
return nucleus;
|
||||||
|
}
|
||||||
|
|
||||||
if (nucleus) {
|
if (nucleus) {
|
||||||
nextPos = nucleus.position;
|
nextPos = nucleus.position;
|
||||||
nucleusNode = nucleus.result;
|
nucleusNode = nucleus.result;
|
||||||
|
@ -129,7 +144,7 @@ Parser.prototype.parseAtom = function(pos) {
|
||||||
// depending on whether those succeed, we return the correct type.
|
// depending on whether those succeed, we return the correct type.
|
||||||
while (true) {
|
while (true) {
|
||||||
var node;
|
var node;
|
||||||
if ((node = this.parseSuperscript(nextPos))) {
|
if ((node = this.parseSuperscript(nextPos, mode))) {
|
||||||
if (sup) {
|
if (sup) {
|
||||||
throw new ParseError("Parse error: Double superscript");
|
throw new ParseError("Parse error: Double superscript");
|
||||||
}
|
}
|
||||||
|
@ -137,7 +152,7 @@ Parser.prototype.parseAtom = function(pos) {
|
||||||
sup = node.result;
|
sup = node.result;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ((node = this.parseSubscript(nextPos))) {
|
if ((node = this.parseSubscript(nextPos, mode))) {
|
||||||
if (sub) {
|
if (sub) {
|
||||||
throw new ParseError("Parse error: Double subscript");
|
throw new ParseError("Parse error: Double subscript");
|
||||||
}
|
}
|
||||||
|
@ -151,7 +166,7 @@ Parser.prototype.parseAtom = function(pos) {
|
||||||
if (sup || sub) {
|
if (sup || sub) {
|
||||||
return new ParseResult(
|
return new ParseResult(
|
||||||
new ParseNode("supsub", {base: nucleusNode, sup: sup,
|
new ParseNode("supsub", {base: nucleusNode, sup: sup,
|
||||||
sub: sub}),
|
sub: sub}, mode),
|
||||||
nextPos);
|
nextPos);
|
||||||
} else {
|
} else {
|
||||||
return nucleus;
|
return nucleus;
|
||||||
|
@ -160,25 +175,24 @@ Parser.prototype.parseAtom = function(pos) {
|
||||||
|
|
||||||
// Parses a group, which is either a single nucleus (like "x") or an expression
|
// Parses a group, which is either a single nucleus (like "x") or an expression
|
||||||
// in braces (like "{x+y}")
|
// in braces (like "{x+y}")
|
||||||
Parser.prototype.parseGroup = function(pos) {
|
Parser.prototype.parseGroup = function(pos, mode) {
|
||||||
var start = this.lexer.lex(pos);
|
var start = this.lexer.lex(pos, mode);
|
||||||
// Try to parse an open brace
|
// Try to parse an open brace
|
||||||
if (start.type === "{") {
|
if (start.type === "{") {
|
||||||
// If we get a brace, parse an expression
|
// If we get a brace, parse an expression
|
||||||
var expression = this.parseExpression(start.position);
|
var expression = this.parseExpression(start.position, mode);
|
||||||
// Make sure we get a close brace
|
// Make sure we get a close brace
|
||||||
var closeBrace = this.lexer.lex(expression.position);
|
var closeBrace = this.lexer.lex(expression.position, mode);
|
||||||
expect(closeBrace, "}");
|
expect(closeBrace, "}");
|
||||||
return new ParseResult(
|
return new ParseResult(
|
||||||
new ParseNode("ordgroup", expression.result),
|
new ParseNode("ordgroup", expression.result, mode),
|
||||||
closeBrace.position);
|
closeBrace.position);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, just return a nucleus
|
// Otherwise, just return a nucleus
|
||||||
return this.parseNucleus(pos);
|
return this.parseNucleus(pos, mode);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// A list of 1-argument color functions
|
// A list of 1-argument color functions
|
||||||
var colorFuncs = [
|
var colorFuncs = [
|
||||||
"\\blue", "\\orange", "\\pink", "\\red", "\\green", "\\gray", "\\purple"
|
"\\blue", "\\orange", "\\pink", "\\red", "\\green", "\\gray", "\\purple"
|
||||||
|
@ -200,12 +214,12 @@ var namedFns = [
|
||||||
|
|
||||||
// Parses a "nucleus", which is either a single token from the tokenizer or a
|
// Parses a "nucleus", which is either a single token from the tokenizer or a
|
||||||
// function and its arguments
|
// function and its arguments
|
||||||
Parser.prototype.parseNucleus = function(pos) {
|
Parser.prototype.parseNucleus = function(pos, mode) {
|
||||||
var nucleus = this.lexer.lex(pos);
|
var nucleus = this.lexer.lex(pos, mode);
|
||||||
|
|
||||||
if (utils.contains(colorFuncs, nucleus.type)) {
|
if (utils.contains(colorFuncs, nucleus.type)) {
|
||||||
// If this is a color function, parse its argument and return
|
// If this is a color function, parse its argument and return
|
||||||
var group = this.parseGroup(nucleus.position);
|
var group = this.parseGroup(nucleus.position, mode);
|
||||||
if (group) {
|
if (group) {
|
||||||
var atoms;
|
var atoms;
|
||||||
if (group.result.type === "ordgroup") {
|
if (group.result.type === "ordgroup") {
|
||||||
|
@ -215,55 +229,66 @@ Parser.prototype.parseNucleus = function(pos) {
|
||||||
}
|
}
|
||||||
return new ParseResult(
|
return new ParseResult(
|
||||||
new ParseNode("color",
|
new ParseNode("color",
|
||||||
{color: nucleus.type.slice(1), value: atoms}),
|
{color: nucleus.type.slice(1), value: atoms}, mode),
|
||||||
group.position);
|
group.position);
|
||||||
} else {
|
} else {
|
||||||
throw new ParseError(
|
throw new ParseError(
|
||||||
"Expected group after '" + nucleus.text + "'");
|
"Expected group after '" + nucleus.text + "'");
|
||||||
}
|
}
|
||||||
} else if (utils.contains(sizeFuncs, nucleus.type)) {
|
} else if (mode === "math" && utils.contains(sizeFuncs, nucleus.type)) {
|
||||||
// If this is a size function, parse its argument and return
|
// If this is a size function, parse its argument and return
|
||||||
var group = this.parseGroup(nucleus.position);
|
var group = this.parseGroup(nucleus.position, mode);
|
||||||
if (group) {
|
if (group) {
|
||||||
return new ParseResult(
|
return new ParseResult(
|
||||||
new ParseNode("sizing", {
|
new ParseNode("sizing", {
|
||||||
size: "size" + (utils.indexOf(sizeFuncs, nucleus.type) + 1),
|
size: "size" + (utils.indexOf(sizeFuncs, nucleus.type) + 1),
|
||||||
value: group.result
|
value: group.result
|
||||||
}),
|
}, mode),
|
||||||
group.position);
|
group.position);
|
||||||
} else {
|
} else {
|
||||||
throw new ParseError(
|
throw new ParseError(
|
||||||
"Expected group after '" + nucleus.text + "'");
|
"Expected group after '" + nucleus.text + "'");
|
||||||
}
|
}
|
||||||
} else if (utils.contains(namedFns, nucleus.type)) {
|
} else if (mode === "math" && utils.contains(namedFns, nucleus.type)) {
|
||||||
// If this is a named function, just return it plain
|
// If this is a named function, just return it plain
|
||||||
return new ParseResult(
|
return new ParseResult(
|
||||||
new ParseNode("namedfn", nucleus.text),
|
new ParseNode("namedfn", nucleus.text, mode),
|
||||||
nucleus.position);
|
nucleus.position);
|
||||||
} else if (nucleus.type === "\\llap" || nucleus.type === "\\rlap") {
|
} else if (nucleus.type === "\\llap" || nucleus.type === "\\rlap") {
|
||||||
// If this is an llap or rlap, parse its argument and return
|
// If this is an llap or rlap, parse its argument and return
|
||||||
var group = this.parseGroup(nucleus.position);
|
var group = this.parseGroup(nucleus.position, mode);
|
||||||
if (group) {
|
if (group) {
|
||||||
return new ParseResult(
|
return new ParseResult(
|
||||||
new ParseNode(nucleus.type.slice(1), group.result),
|
new ParseNode(nucleus.type.slice(1), group.result, mode),
|
||||||
group.position);
|
group.position);
|
||||||
} else {
|
} else {
|
||||||
throw new ParseError(
|
throw new ParseError(
|
||||||
"Expected group after '" + nucleus.text + "'");
|
"Expected group after '" + nucleus.text + "'");
|
||||||
}
|
}
|
||||||
} else if (nucleus.type === "\\dfrac" || nucleus.type === "\\frac" ||
|
} else if (mode === "math" && nucleus.type === "\\text") {
|
||||||
nucleus.type === "\\tfrac") {
|
var group = this.parseGroup(nucleus.position, "text");
|
||||||
|
if (group) {
|
||||||
|
return new ParseResult(
|
||||||
|
new ParseNode(nucleus.type.slice(1), group.result, mode),
|
||||||
|
group.position);
|
||||||
|
} else {
|
||||||
|
throw new ParseError(
|
||||||
|
"Expected group after '" + nucleus.text + "'");
|
||||||
|
}
|
||||||
|
} else if (mode === "math" && (nucleus.type === "\\dfrac" ||
|
||||||
|
nucleus.type === "\\frac" ||
|
||||||
|
nucleus.type === "\\tfrac")) {
|
||||||
// If this is a frac, parse its two arguments and return
|
// If this is a frac, parse its two arguments and return
|
||||||
var numer = this.parseGroup(nucleus.position);
|
var numer = this.parseGroup(nucleus.position, mode);
|
||||||
if (numer) {
|
if (numer) {
|
||||||
var denom = this.parseGroup(numer.position);
|
var denom = this.parseGroup(numer.position, mode);
|
||||||
if (denom) {
|
if (denom) {
|
||||||
return new ParseResult(
|
return new ParseResult(
|
||||||
new ParseNode("frac", {
|
new ParseNode("frac", {
|
||||||
numer: numer.result,
|
numer: numer.result,
|
||||||
denom: denom.result,
|
denom: denom.result,
|
||||||
size: nucleus.type.slice(1)
|
size: nucleus.type.slice(1)
|
||||||
}),
|
}, mode),
|
||||||
denom.position);
|
denom.position);
|
||||||
} else {
|
} else {
|
||||||
throw new ParseError("Expected denominator after '" +
|
throw new ParseError("Expected denominator after '" +
|
||||||
|
@ -273,17 +298,17 @@ Parser.prototype.parseNucleus = function(pos) {
|
||||||
throw new ParseError("Parse error: Expected numerator after '" +
|
throw new ParseError("Parse error: Expected numerator after '" +
|
||||||
nucleus.type + "'");
|
nucleus.type + "'");
|
||||||
}
|
}
|
||||||
} else if (nucleus.type === "\\KaTeX") {
|
} else if (mode === "math" && nucleus.type === "\\KaTeX") {
|
||||||
// If this is a KaTeX node, return the special katex result
|
// If this is a KaTeX node, return the special katex result
|
||||||
return new ParseResult(
|
return new ParseResult(
|
||||||
new ParseNode("katex", null),
|
new ParseNode("katex", null, mode),
|
||||||
nucleus.position
|
nucleus.position
|
||||||
);
|
);
|
||||||
} else if (symbols[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
|
||||||
return new ParseResult(
|
return new ParseResult(
|
||||||
new ParseNode(symbols[nucleus.text].group, nucleus.text),
|
new ParseNode(symbols[mode][nucleus.text].group, nucleus.text, mode),
|
||||||
nucleus.position);
|
nucleus.position);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, we couldn't parse it
|
// Otherwise, we couldn't parse it
|
||||||
|
|
82
buildTree.js
82
buildTree.js
|
@ -50,6 +50,7 @@ var groupToType = {
|
||||||
ordgroup: "mord",
|
ordgroup: "mord",
|
||||||
namedfn: "mop",
|
namedfn: "mop",
|
||||||
katex: "mord",
|
katex: "mord",
|
||||||
|
text: "mord",
|
||||||
};
|
};
|
||||||
|
|
||||||
var getTypeOfGroup = function(group) {
|
var getTypeOfGroup = function(group) {
|
||||||
|
@ -69,11 +70,17 @@ var getTypeOfGroup = function(group) {
|
||||||
|
|
||||||
var groupTypes = {
|
var groupTypes = {
|
||||||
mathord: function(group, options, prev) {
|
mathord: function(group, options, prev) {
|
||||||
return makeSpan(["mord", options.color], [mathit(group.value)]);
|
return makeSpan(
|
||||||
|
["mord", options.color],
|
||||||
|
[mathit(group.value, group.mode)]
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
textord: function(group, options, prev) {
|
textord: function(group, options, prev) {
|
||||||
return makeSpan(["mord", options.color], [mathrm(group.value)]);
|
return makeSpan(
|
||||||
|
["mord", options.color],
|
||||||
|
[mathrm(group.value, group.mode)]
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
bin: function(group, options, prev) {
|
bin: function(group, options, prev) {
|
||||||
|
@ -88,15 +95,23 @@ var groupTypes = {
|
||||||
group.type = "ord";
|
group.type = "ord";
|
||||||
className = "mord";
|
className = "mord";
|
||||||
}
|
}
|
||||||
return makeSpan([className, options.color], [mathrm(group.value)]);
|
return makeSpan(
|
||||||
|
[className, options.color],
|
||||||
|
[mathrm(group.value, group.mode)]
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
rel: function(group, options, prev) {
|
rel: function(group, options, prev) {
|
||||||
return makeSpan(["mrel", options.color], [mathrm(group.value)]);
|
return makeSpan(
|
||||||
|
["mrel", options.color],
|
||||||
|
[mathrm(group.value, group.mode)]
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
amsrel: function(group, options, prev) {
|
text: function(group, options, prev) {
|
||||||
return makeSpan(["mrel", options.color], [amsrm(group.value)]);
|
return makeSpan(["text mord", options.style.cls()],
|
||||||
|
[buildGroup(group.value, options.reset())]
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
supsub: function(group, options, prev) {
|
supsub: function(group, options, prev) {
|
||||||
|
@ -185,11 +200,17 @@ var groupTypes = {
|
||||||
},
|
},
|
||||||
|
|
||||||
open: function(group, options, prev) {
|
open: function(group, options, prev) {
|
||||||
return makeSpan(["mopen", options.color], [mathrm(group.value)]);
|
return makeSpan(
|
||||||
|
["mopen", options.color],
|
||||||
|
[mathrm(group.value, group.mode)]
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
close: function(group, options, prev) {
|
close: function(group, options, prev) {
|
||||||
return makeSpan(["mclose", options.color], [mathrm(group.value)]);
|
return makeSpan(
|
||||||
|
["mclose", options.color],
|
||||||
|
[mathrm(group.value, group.mode)]
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
frac: function(group, options, prev) {
|
frac: function(group, options, prev) {
|
||||||
|
@ -283,8 +304,14 @@ var groupTypes = {
|
||||||
},
|
},
|
||||||
|
|
||||||
spacing: function(group, options, prev) {
|
spacing: function(group, options, prev) {
|
||||||
if (group.value === "\\ " || group.value === "\\space") {
|
if (group.value === "\\ " || group.value === "\\space" ||
|
||||||
return makeSpan(["mord", "mspace"], [mathrm(group.value)]);
|
group.value === " ") {
|
||||||
|
return makeSpan(
|
||||||
|
["mord", "mspace"],
|
||||||
|
[mathrm(group.value, group.mode)]
|
||||||
|
);
|
||||||
|
} else if(group.value === "~") {
|
||||||
|
return makeSpan(["mord", "mspace"], [mathrm(" ", group.mode)]);
|
||||||
} else {
|
} else {
|
||||||
var spacingClassMap = {
|
var spacingClassMap = {
|
||||||
"\\qquad": "qquad",
|
"\\qquad": "qquad",
|
||||||
|
@ -311,7 +338,10 @@ var groupTypes = {
|
||||||
},
|
},
|
||||||
|
|
||||||
punct: function(group, options, prev) {
|
punct: function(group, options, prev) {
|
||||||
return makeSpan(["mpunct", options.color], [mathrm(group.value)]);
|
return makeSpan(
|
||||||
|
["mpunct", options.color],
|
||||||
|
[mathrm(group.value, group.mode)]
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
ordgroup: function(group, options, prev) {
|
ordgroup: function(group, options, prev) {
|
||||||
|
@ -323,26 +353,26 @@ var groupTypes = {
|
||||||
namedfn: function(group, options, prev) {
|
namedfn: function(group, options, prev) {
|
||||||
var chars = [];
|
var chars = [];
|
||||||
for (var i = 1; i < group.value.length; i++) {
|
for (var i = 1; i < group.value.length; i++) {
|
||||||
chars.push(mathrm(group.value[i]));
|
chars.push(mathrm(group.value[i], group.mode));
|
||||||
}
|
}
|
||||||
|
|
||||||
return makeSpan(["mop", options.color], chars);
|
return makeSpan(["mop", options.color], chars);
|
||||||
},
|
},
|
||||||
|
|
||||||
katex: function(group, options, prev) {
|
katex: function(group, options, prev) {
|
||||||
var k = makeSpan(["k"], [mathrm("K")]);
|
var k = makeSpan(["k"], [mathrm("K", group.mode)]);
|
||||||
var a = makeSpan(["a"], [mathrm("A")]);
|
var a = makeSpan(["a"], [mathrm("A", group.mode)]);
|
||||||
|
|
||||||
a.height = (a.height + 0.2) * 0.75;
|
a.height = (a.height + 0.2) * 0.75;
|
||||||
a.depth = (a.height - 0.2) * 0.75;
|
a.depth = (a.height - 0.2) * 0.75;
|
||||||
|
|
||||||
var t = makeSpan(["t"], [mathrm("T")]);
|
var t = makeSpan(["t"], [mathrm("T", group.mode)]);
|
||||||
var e = makeSpan(["e"], [mathrm("E")]);
|
var e = makeSpan(["e"], [mathrm("E", group.mode)]);
|
||||||
|
|
||||||
e.height = (e.height - 0.2155);
|
e.height = (e.height - 0.2155);
|
||||||
e.depth = (e.depth + 0.2155);
|
e.depth = (e.depth + 0.2155);
|
||||||
|
|
||||||
var x = makeSpan(["x"], [mathrm("X")]);
|
var x = makeSpan(["x"], [mathrm("X", group.mode)]);
|
||||||
|
|
||||||
return makeSpan(["katex-logo", options.color], [k, a, t, e, x]);
|
return makeSpan(["katex-logo", options.color], [k, a, t, e, x]);
|
||||||
},
|
},
|
||||||
|
@ -407,9 +437,9 @@ var buildGroup = function(group, options, prev) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var makeText = function(value, style) {
|
var makeText = function(value, style, mode) {
|
||||||
if (symbols[value].replace) {
|
if (symbols[mode][value].replace) {
|
||||||
value = symbols[value].replace;
|
value = symbols[mode][value].replace;
|
||||||
}
|
}
|
||||||
|
|
||||||
var metrics = fontMetrics.getCharacterMetrics(value, style);
|
var metrics = fontMetrics.getCharacterMetrics(value, style);
|
||||||
|
@ -432,15 +462,15 @@ var makeText = function(value, style) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var mathit = function(value) {
|
var mathit = function(value, mode) {
|
||||||
return makeSpan(["mathit"], [makeText(value, "math-italic")]);
|
return makeSpan(["mathit"], [makeText(value, "math-italic", mode)]);
|
||||||
};
|
};
|
||||||
|
|
||||||
var mathrm = function(value) {
|
var mathrm = function(value, mode) {
|
||||||
if (symbols[value].font === "main") {
|
if (symbols[mode][value].font === "main") {
|
||||||
return makeText(value, "main-regular");
|
return makeText(value, "main-regular", mode);
|
||||||
} else {
|
} else {
|
||||||
return makeSpan(["amsrm"], [makeText(value, "ams-regular")]);
|
return makeSpan(["amsrm"], [makeText(value, "ams-regular", mode)]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
1123
symbols.js
1123
symbols.js
File diff suppressed because it is too large
Load Diff
|
@ -34,5 +34,8 @@ url=http://localhost:7936/test/huxley/test.html?m=\Huge{x}\LARGE{y}\normalsize{z
|
||||||
[SizingBaseline]
|
[SizingBaseline]
|
||||||
url=http://localhost:7936/test/huxley/test.html?m=\tiny{a+b}a+b\Huge{a+b}&pre=x&post=M
|
url=http://localhost:7936/test/huxley/test.html?m=\tiny{a+b}a+b\Huge{a+b}&pre=x&post=M
|
||||||
|
|
||||||
|
[Text]
|
||||||
|
url=http://localhost:7936/test/huxley/test.html?m=\frac{a}{b}\text{c {ab} \ e}+fg
|
||||||
|
|
||||||
[KaTeX]
|
[KaTeX]
|
||||||
url=http://localhost:7936/test/huxley/test.html?m=\KaTeX
|
url=http://localhost:7936/test/huxley/test.html?m=\KaTeX
|
1
test/huxley/Text.huxley/record.json
Normal file
1
test/huxley/Text.huxley/record.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"py/object": "huxley.run.Test", "screen_size": {"py/tuple": [1024, 768]}, "steps": [{"py/object": "huxley.steps.ScreenshotTestStep", "index": 0, "offset_time": 0}]}
|
BIN
test/huxley/Text.huxley/screenshot0.png
Normal file
BIN
test/huxley/Text.huxley/screenshot0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -447,3 +447,52 @@ describe("A sizing parser", function() {
|
||||||
}).toThrow();
|
}).toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("A text parser", function() {
|
||||||
|
var textExpression = "\\text{a b}";
|
||||||
|
var badTextExpression = "\\text{a b%}";
|
||||||
|
var nestedTextExpression = "\\text{a {b} \\blue{c}}";
|
||||||
|
var spaceTextExpression = "\\text{ a \\ }";
|
||||||
|
|
||||||
|
it("should not fail", function() {
|
||||||
|
expect(function() {
|
||||||
|
parseTree(textExpression);
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should produce a text", function() {
|
||||||
|
var parse = parseTree(textExpression)[0];
|
||||||
|
|
||||||
|
expect(parse.type).toMatch("text");
|
||||||
|
expect(parse.value).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should produce textords instead of mathords", function() {
|
||||||
|
var parse = parseTree(textExpression)[0];
|
||||||
|
var group = parse.value.value;
|
||||||
|
|
||||||
|
expect(group[0].type).toMatch("textord");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not parse bad text", function() {
|
||||||
|
expect(function() {
|
||||||
|
parseTree(badTextExpression);
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should parse nested expressions", function() {
|
||||||
|
expect(function() {
|
||||||
|
parseTree(nestedTextExpression);
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should contract spaces", function() {
|
||||||
|
var parse = parseTree(spaceTextExpression)[0];
|
||||||
|
var group = parse.value.value;
|
||||||
|
|
||||||
|
expect(group[0].type).toMatch("spacing");
|
||||||
|
expect(group[1].type).toMatch("textord");
|
||||||
|
expect(group[2].type).toMatch("spacing");
|
||||||
|
expect(group[3].type).toMatch("spacing");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user