Add looots of comments
Summary: Add comments everywhere! Also fix some small bugs like using Style.id instead of Style.size, and rename some variables to be more descriptive. Fixes #22 Test Plan: - Make sure the huxley screenshots didn't change - Make sure the tests still pass Reviewers: alpert Reviewed By: alpert Differential Revision: http://phabricator.khanacademy.org/D13158
This commit is contained in:
parent
79a5687057
commit
f63af87f17
58
Lexer.js
58
Lexer.js
|
@ -1,3 +1,16 @@
|
||||||
|
/**
|
||||||
|
* The Lexer class handles tokenizing the input in various ways. Since our
|
||||||
|
* parser expects us to be able to backtrack, the lexer allows lexing from any
|
||||||
|
* given starting point.
|
||||||
|
*
|
||||||
|
* Its main exposed function is the `lex` function, which takes a position to
|
||||||
|
* lex from and a type of token to lex. It defers to the appropriate `_innerLex`
|
||||||
|
* function.
|
||||||
|
*
|
||||||
|
* The various `_innerLex` functions perform the actual lexing of different
|
||||||
|
* kinds.
|
||||||
|
*/
|
||||||
|
|
||||||
var ParseError = require("./ParseError");
|
var ParseError = require("./ParseError");
|
||||||
|
|
||||||
// The main lexer class
|
// The main lexer class
|
||||||
|
@ -5,14 +18,15 @@ function Lexer(input) {
|
||||||
this._input = input;
|
this._input = input;
|
||||||
};
|
};
|
||||||
|
|
||||||
// The result of a single lex
|
// The resulting token returned from `lex`.
|
||||||
function LexResult(type, text, position) {
|
function LexResult(type, text, position) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.position = position;
|
this.position = position;
|
||||||
}
|
}
|
||||||
|
|
||||||
// "normal" types of tokens
|
// "normal" types of tokens. These are tokens which can be matched by a simple
|
||||||
|
// regex, and have a type which is listed.
|
||||||
var mathNormals = [
|
var mathNormals = [
|
||||||
[/^[/|@."`0-9]/, "textord"],
|
[/^[/|@."`0-9]/, "textord"],
|
||||||
[/^[a-zA-Z]/, "mathord"],
|
[/^[a-zA-Z]/, "mathord"],
|
||||||
|
@ -29,6 +43,8 @@ var mathNormals = [
|
||||||
[/^~/, "spacing"]
|
[/^~/, "spacing"]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// These are "normal" tokens like above, but should instead be parsed in text
|
||||||
|
// mode.
|
||||||
var textNormals = [
|
var textNormals = [
|
||||||
[/^[a-zA-Z0-9`!@*()-=+\[\]'";:?\/.,]/, "textord"],
|
[/^[a-zA-Z0-9`!@*()-=+\[\]'";:?\/.,]/, "textord"],
|
||||||
[/^{/, "{"],
|
[/^{/, "{"],
|
||||||
|
@ -36,22 +52,29 @@ var textNormals = [
|
||||||
[/^~/, "spacing"]
|
[/^~/, "spacing"]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Regexes for matching whitespace
|
||||||
var whitespaceRegex = /^\s*/;
|
var whitespaceRegex = /^\s*/;
|
||||||
var whitespaceConcatRegex = /^( +|\\ +)/;
|
var whitespaceConcatRegex = /^( +|\\ +)/;
|
||||||
|
|
||||||
// Build a regex to easily parse the functions
|
// This regex matches any other TeX function, which is a backslash followed by a
|
||||||
|
// word or a single symbol
|
||||||
var anyFunc = /^\\(?:[a-zA-Z]+|.)/;
|
var anyFunc = /^\\(?:[a-zA-Z]+|.)/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function lexes a single normal token. It takes a position, a list of
|
||||||
|
* "normal" tokens to try, and whether it should completely ignore whitespace or
|
||||||
|
* not.
|
||||||
|
*/
|
||||||
Lexer.prototype._innerLex = function(pos, normals, ignoreWhitespace) {
|
Lexer.prototype._innerLex = function(pos, normals, ignoreWhitespace) {
|
||||||
var input = this._input.slice(pos);
|
var input = this._input.slice(pos);
|
||||||
|
|
||||||
// Get rid of whitespace
|
|
||||||
if (ignoreWhitespace) {
|
if (ignoreWhitespace) {
|
||||||
|
// Get rid of whitespace.
|
||||||
var whitespace = input.match(whitespaceRegex)[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 that happens in text mode.
|
||||||
var whitespace = input.match(whitespaceConcatRegex);
|
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);
|
||||||
|
@ -65,7 +88,7 @@ Lexer.prototype._innerLex = function(pos, normals, ignoreWhitespace) {
|
||||||
|
|
||||||
var match;
|
var match;
|
||||||
if ((match = input.match(anyFunc))) {
|
if ((match = input.match(anyFunc))) {
|
||||||
// If we match one of the tokens, extract the type
|
// If we match a function token, return it
|
||||||
return new LexResult(match[0], match[0], pos + match[0].length);
|
return new LexResult(match[0], match[0], pos + match[0].length);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, we look through the normal token regexes and see if it's
|
// Otherwise, we look through the normal token regexes and see if it's
|
||||||
|
@ -81,7 +104,6 @@ Lexer.prototype._innerLex = function(pos, normals, ignoreWhitespace) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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] +
|
||||||
"'", this, pos);
|
"'", this, pos);
|
||||||
}
|
}
|
||||||
|
@ -89,6 +111,9 @@ Lexer.prototype._innerLex = function(pos, normals, ignoreWhitespace) {
|
||||||
// A regex to match a CSS color (like #ffffff or BlueViolet)
|
// A regex to match a CSS color (like #ffffff or BlueViolet)
|
||||||
var cssColor = /^(#[a-z0-9]+|[a-z]+)/i;
|
var cssColor = /^(#[a-z0-9]+|[a-z]+)/i;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function lexes a CSS color.
|
||||||
|
*/
|
||||||
Lexer.prototype._innerLexColor = function(pos) {
|
Lexer.prototype._innerLexColor = function(pos) {
|
||||||
var input = this._input.slice(pos);
|
var input = this._input.slice(pos);
|
||||||
|
|
||||||
|
@ -101,14 +126,18 @@ Lexer.prototype._innerLexColor = function(pos) {
|
||||||
if ((match = input.match(cssColor))) {
|
if ((match = input.match(cssColor))) {
|
||||||
// If we look like a color, return a color
|
// If we look like a color, return a color
|
||||||
return new LexResult("color", match[0], pos + match[0].length);
|
return new LexResult("color", match[0], pos + match[0].length);
|
||||||
|
} else {
|
||||||
|
throw new ParseError("Invalid color", this, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We didn't match a color, so throw an error.
|
|
||||||
throw new ParseError("Invalid color", this, pos);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A regex to match a dimension. Dimensions look like
|
||||||
|
// "1.2em" or ".4pt" or "1 ex"
|
||||||
var sizeRegex = /^(\d+(?:\.\d*)?|\.\d+)\s*([a-z]{2})/;
|
var sizeRegex = /^(\d+(?:\.\d*)?|\.\d+)\s*([a-z]{2})/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function lexes a dimension.
|
||||||
|
*/
|
||||||
Lexer.prototype._innerLexSize = function(pos) {
|
Lexer.prototype._innerLexSize = function(pos) {
|
||||||
var input = this._input.slice(pos);
|
var input = this._input.slice(pos);
|
||||||
|
|
||||||
|
@ -120,6 +149,7 @@ Lexer.prototype._innerLexSize = function(pos) {
|
||||||
var match;
|
var match;
|
||||||
if ((match = input.match(sizeRegex))) {
|
if ((match = input.match(sizeRegex))) {
|
||||||
var unit = match[2];
|
var unit = match[2];
|
||||||
|
// We only currently handle "em" and "ex" units
|
||||||
if (unit !== "em" && unit !== "ex") {
|
if (unit !== "em" && unit !== "ex") {
|
||||||
throw new ParseError("Invalid unit: '" + unit + "'", this, pos);
|
throw new ParseError("Invalid unit: '" + unit + "'", this, pos);
|
||||||
}
|
}
|
||||||
|
@ -132,6 +162,9 @@ Lexer.prototype._innerLexSize = function(pos) {
|
||||||
throw new ParseError("Invalid size", this, pos);
|
throw new ParseError("Invalid size", this, pos);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function lexes a string of whitespace.
|
||||||
|
*/
|
||||||
Lexer.prototype._innerLexWhitespace = function(pos) {
|
Lexer.prototype._innerLexWhitespace = function(pos) {
|
||||||
var input = this._input.slice(pos);
|
var input = this._input.slice(pos);
|
||||||
|
|
||||||
|
@ -141,7 +174,10 @@ Lexer.prototype._innerLexWhitespace = function(pos) {
|
||||||
return new LexResult("whitespace", whitespace, pos);
|
return new LexResult("whitespace", whitespace, pos);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Lex a single token
|
/**
|
||||||
|
* This function lexes a single token starting at `pos` and of the given mode.
|
||||||
|
* Based on the mode, we defer to one of the `_innerLex` functions.
|
||||||
|
*/
|
||||||
Lexer.prototype.lex = function(pos, mode) {
|
Lexer.prototype.lex = function(pos, mode) {
|
||||||
if (mode === "math") {
|
if (mode === "math") {
|
||||||
return this._innerLex(pos, mathNormals, true);
|
return this._innerLex(pos, mathNormals, true);
|
||||||
|
|
37
Options.js
37
Options.js
|
@ -1,3 +1,19 @@
|
||||||
|
/**
|
||||||
|
* This file contains information about the options that the Parser carries
|
||||||
|
* around with it while parsing. Data is held in an `Options` object, and when
|
||||||
|
* recursing, a new `Options` object can be created with the `.with*` and
|
||||||
|
* `.reset` functions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the main options class. It contains the style, size, and color of the
|
||||||
|
* current parse level. It also contains the style and size of the parent parse
|
||||||
|
* level, so size changes can be handled efficiently.
|
||||||
|
*
|
||||||
|
* Each of the `.with*` and `.reset` functions passes its current style and size
|
||||||
|
* as the parentStyle and parentSize of the new options class, so parent
|
||||||
|
* handling is taken care of automatically.
|
||||||
|
*/
|
||||||
function Options(style, size, color, parentStyle, parentSize) {
|
function Options(style, size, color, parentStyle, parentSize) {
|
||||||
this.style = style;
|
this.style = style;
|
||||||
this.color = color;
|
this.color = color;
|
||||||
|
@ -14,23 +30,40 @@ function Options(style, size, color, parentStyle, parentSize) {
|
||||||
this.parentSize = parentSize;
|
this.parentSize = parentSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new options object with the given style.
|
||||||
|
*/
|
||||||
Options.prototype.withStyle = function(style) {
|
Options.prototype.withStyle = function(style) {
|
||||||
return new Options(style, this.size, this.color, this.style, this.size);
|
return new Options(style, this.size, this.color, this.style, this.size);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new options object with the given size.
|
||||||
|
*/
|
||||||
Options.prototype.withSize = function(size) {
|
Options.prototype.withSize = function(size) {
|
||||||
return new Options(this.style, size, this.color, this.style, this.size);
|
return new Options(this.style, size, this.color, this.style, this.size);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new options object with the given color.
|
||||||
|
*/
|
||||||
Options.prototype.withColor = function(color) {
|
Options.prototype.withColor = function(color) {
|
||||||
return new Options(this.style, this.size, color, this.style, this.size);
|
return new Options(this.style, this.size, color, this.style, this.size);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new options object with the same style, size, and color. This is
|
||||||
|
* used so that parent style and size changes are handled correctly.
|
||||||
|
*/
|
||||||
Options.prototype.reset = function() {
|
Options.prototype.reset = function() {
|
||||||
return new Options(
|
return new Options(
|
||||||
this.style, this.size, this.color, this.style, this.size);
|
this.style, this.size, this.color, this.style, this.size);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of color names to CSS colors.
|
||||||
|
* TODO(emily): Remove this when we have real macros
|
||||||
|
*/
|
||||||
var colorMap = {
|
var colorMap = {
|
||||||
"katex-blue": "#6495ed",
|
"katex-blue": "#6495ed",
|
||||||
"katex-orange": "#ffa500",
|
"katex-orange": "#ffa500",
|
||||||
|
@ -41,6 +74,10 @@ var colorMap = {
|
||||||
"katex-purple": "#9d38bd"
|
"katex-purple": "#9d38bd"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the CSS color of the current options object, accounting for the
|
||||||
|
* `colorMap`.
|
||||||
|
*/
|
||||||
Options.prototype.getColor = function() {
|
Options.prototype.getColor = function() {
|
||||||
return colorMap[this.color] || this.color;
|
return colorMap[this.color] || this.color;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
|
/**
|
||||||
|
* This is the ParseError class, which is the main error thrown by KaTeX
|
||||||
|
* functions when something has gone wrong. This is used to distinguish internal
|
||||||
|
* errors from errors in the expression that the user provided.
|
||||||
|
*/
|
||||||
function ParseError(message, lexer, position) {
|
function ParseError(message, lexer, position) {
|
||||||
var error = "KaTeX parse error: " + message;
|
var error = "KaTeX parse error: " + message;
|
||||||
|
|
||||||
if (lexer !== undefined && position !== undefined) {
|
if (lexer !== undefined && position !== undefined) {
|
||||||
// If we have the input and a position, make the error a bit fancier
|
// If we have the input and a position, make the error a bit fancier
|
||||||
|
|
||||||
// Prepend some information
|
// Prepend some information
|
||||||
error += " at position " + position + ": ";
|
error += " at position " + position + ": ";
|
||||||
|
|
||||||
|
@ -18,12 +24,15 @@ function ParseError(message, lexer, position) {
|
||||||
error += input.slice(begin, end);
|
error += input.slice(begin, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some hackery to make ParseError a prototype of Error
|
||||||
|
// See http://stackoverflow.com/a/8460753
|
||||||
var self = new Error(error);
|
var self = new Error(error);
|
||||||
self.name = "ParseError";
|
self.name = "ParseError";
|
||||||
self.__proto__ = ParseError.prototype;
|
self.__proto__ = ParseError.prototype;
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// More hackery
|
||||||
ParseError.prototype.__proto__ = Error.prototype;
|
ParseError.prototype.__proto__ = Error.prototype;
|
||||||
|
|
||||||
module.exports = ParseError;
|
module.exports = ParseError;
|
||||||
|
|
183
Parser.js
183
Parser.js
|
@ -5,60 +5,70 @@ var utils = require("./utils");
|
||||||
|
|
||||||
var ParseError = require("./ParseError");
|
var ParseError = require("./ParseError");
|
||||||
|
|
||||||
// This file contains the parser used to parse out a TeX expression from the
|
/**
|
||||||
// input. Since TeX isn't context-free, standard parsers don't work particularly
|
* This file contains the parser used to parse out a TeX expression from the
|
||||||
// well.
|
* input. Since TeX isn't context-free, standard parsers don't work particularly
|
||||||
|
* well.
|
||||||
|
*
|
||||||
|
* The strategy of this parser is as such:
|
||||||
|
*
|
||||||
|
* The main functions (the `.parse...` ones) take a position in the current
|
||||||
|
* parse string to parse tokens from. The lexer (found in Lexer.js, stored at
|
||||||
|
* this.lexer) also supports pulling out tokens at arbitrary places. When
|
||||||
|
* individual tokens are needed at a position, the lexer is called to pull out a
|
||||||
|
* token, which is then used.
|
||||||
|
*
|
||||||
|
* The main functions also take a mode that the parser is currently in
|
||||||
|
* (currently "math" or "text"), which denotes whether the current environment
|
||||||
|
* is a math-y one or a text-y one (e.g. inside \text). Currently, this serves
|
||||||
|
* to limit the functions which can be used in text mode.
|
||||||
|
*
|
||||||
|
* The main functions then return an object which contains the useful data that
|
||||||
|
* was parsed at its given point, and a new position at the end of the parsed
|
||||||
|
* data. The main functions can call each other and continue the parsing by
|
||||||
|
* using the returned position as a new starting point.
|
||||||
|
*
|
||||||
|
* There are also extra `.handle...` functions, which pull out some reused
|
||||||
|
* functionality into self-contained functions.
|
||||||
|
*
|
||||||
|
* The earlier functions return `ParseResult`s, which contain a ParseNode and a
|
||||||
|
* new position.
|
||||||
|
*
|
||||||
|
* The later functions (which are called deeper in the parse) sometimes return
|
||||||
|
* ParseFuncOrArgument, which contain a ParseResult as well as some data about
|
||||||
|
* whether the parsed object is a function which is missing some arguments, or a
|
||||||
|
* standalone object which can be used as an argument to another function.
|
||||||
|
*/
|
||||||
|
|
||||||
// The strategy of this parser is as such:
|
/**
|
||||||
//
|
* Main Parser class
|
||||||
// The main functions (the `.parse...` ones) take a position in the current
|
*/
|
||||||
// parse string to parse tokens from. The lexer (found in Lexer.js, stored at
|
|
||||||
// this.lexer) also supports pulling out tokens at arbitrary places. When
|
|
||||||
// individual tokens are needed at a position, the lexer is called to pull out a
|
|
||||||
// token, which is then used.
|
|
||||||
//
|
|
||||||
// The main functions also take a mode that the parser is currently in
|
|
||||||
// (currently "math" or "text"), which denotes whether the current environment
|
|
||||||
// is a math-y one or a text-y one (e.g. inside \text). Currently, this serves
|
|
||||||
// to limit the functions which can be used in text mode.
|
|
||||||
//
|
|
||||||
// The main functions then return an object which contains the useful data that
|
|
||||||
// was parsed at its given point, and a new position at the end of the parsed
|
|
||||||
// data. The main functions can call each other and continue the parsing by
|
|
||||||
// using the returned position as a new starting point.
|
|
||||||
//
|
|
||||||
// There are also extra `.handle...` functions, which pull out some reused
|
|
||||||
// functionality into self-contained functions.
|
|
||||||
//
|
|
||||||
// The earlier functions return `ParseResult`s, which contain a ParseNode and a
|
|
||||||
// new position.
|
|
||||||
//
|
|
||||||
// The later functions (which are called deeper in the parse) sometimes return
|
|
||||||
// ParseFuncOrArgument, which contain a ParseResult as well as some data about
|
|
||||||
// whether the parsed object is a function which is missing some arguments, or a
|
|
||||||
// standalone object which can be used as an argument to another function.
|
|
||||||
|
|
||||||
// Main Parser class
|
|
||||||
function Parser(input) {
|
function Parser(input) {
|
||||||
// Make a new lexer
|
// Make a new lexer
|
||||||
this.lexer = new Lexer(input);
|
this.lexer = new Lexer(input);
|
||||||
};
|
};
|
||||||
|
|
||||||
// The resulting parse tree nodes of the parse tree.
|
/**
|
||||||
|
* The resulting parse tree nodes of the parse tree.
|
||||||
|
*/
|
||||||
function ParseNode(type, value, mode) {
|
function ParseNode(type, value, mode) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A result and final position returned by the `.parse...` functions.
|
/**
|
||||||
|
* A result and final position returned by the `.parse...` functions.
|
||||||
|
*/
|
||||||
function ParseResult(result, newPosition) {
|
function ParseResult(result, newPosition) {
|
||||||
this.result = result;
|
this.result = result;
|
||||||
this.position = newPosition;
|
this.position = newPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
// An initial function (without its arguments), or an argument to a function.
|
/**
|
||||||
// The `result` argument should be a ParseResult.
|
* An initial function (without its arguments), or an argument to a function.
|
||||||
|
* The `result` argument should be a ParseResult.
|
||||||
|
*/
|
||||||
function ParseFuncOrArgument(result, isFunction, allowedInText, numArgs, argTypes) {
|
function ParseFuncOrArgument(result, isFunction, allowedInText, numArgs, argTypes) {
|
||||||
this.result = result;
|
this.result = result;
|
||||||
// Is this a function (i.e. is it something defined in functions.js)?
|
// Is this a function (i.e. is it something defined in functions.js)?
|
||||||
|
@ -71,8 +81,10 @@ function ParseFuncOrArgument(result, isFunction, allowedInText, numArgs, argType
|
||||||
this.argTypes = argTypes;
|
this.argTypes = argTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks a result to make sure it has the right type, and throws an
|
/**
|
||||||
// appropriate error otherwise.
|
* Checks a result to make sure it has the right type, and throws an
|
||||||
|
* appropriate error otherwise.
|
||||||
|
*/
|
||||||
Parser.prototype.expect = function(result, type) {
|
Parser.prototype.expect = function(result, type) {
|
||||||
if (result.type !== type) {
|
if (result.type !== type) {
|
||||||
throw new ParseError(
|
throw new ParseError(
|
||||||
|
@ -82,15 +94,20 @@ Parser.prototype.expect = function(result, type) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Main parsing function, which parses an entire input. Returns either a list
|
/**
|
||||||
// of parseNodes or null if the parse fails.
|
* Main parsing function, which parses an entire input.
|
||||||
|
*
|
||||||
|
* @return {?Array.<ParseNode>}
|
||||||
|
*/
|
||||||
Parser.prototype.parse = function(input) {
|
Parser.prototype.parse = function(input) {
|
||||||
// Try to parse the input
|
// Try to parse the input
|
||||||
var parse = this.parseInput(0, "math");
|
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, mode) {
|
Parser.prototype.parseInput = function(pos, mode) {
|
||||||
// Parse an expression
|
// Parse an expression
|
||||||
var expression = this.parseExpression(pos, mode);
|
var expression = this.parseExpression(pos, mode);
|
||||||
|
@ -100,7 +117,9 @@ Parser.prototype.parseInput = function(pos, mode) {
|
||||||
return expression;
|
return expression;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles a body of an expression
|
/**
|
||||||
|
* Handles a body of an expression.
|
||||||
|
*/
|
||||||
Parser.prototype.handleExpressionBody = function(pos, mode) {
|
Parser.prototype.handleExpressionBody = function(pos, mode) {
|
||||||
var body = [];
|
var body = [];
|
||||||
var atom;
|
var atom;
|
||||||
|
@ -116,9 +135,11 @@ Parser.prototype.handleExpressionBody = function(pos, mode) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parses an "expression", which is a list of atoms
|
/**
|
||||||
//
|
* Parses an "expression", which is a list of atoms.
|
||||||
// Returns ParseResult
|
*
|
||||||
|
* @return {ParseResult}
|
||||||
|
*/
|
||||||
Parser.prototype.parseExpression = function(pos, mode) {
|
Parser.prototype.parseExpression = function(pos, mode) {
|
||||||
var body = this.handleExpressionBody(pos, mode);
|
var body = this.handleExpressionBody(pos, mode);
|
||||||
return new ParseResult(body.body, body.position);
|
return new ParseResult(body.body, body.position);
|
||||||
|
@ -127,7 +148,9 @@ Parser.prototype.parseExpression = function(pos, mode) {
|
||||||
// The greediness of a superscript or subscript
|
// The greediness of a superscript or subscript
|
||||||
var SUPSUB_GREEDINESS = 1;
|
var SUPSUB_GREEDINESS = 1;
|
||||||
|
|
||||||
// Handle a subscript or superscript with nice errors
|
/**
|
||||||
|
* Handle a subscript or superscript with nice errors.
|
||||||
|
*/
|
||||||
Parser.prototype.handleSupSubscript = function(pos, mode, symbol, name) {
|
Parser.prototype.handleSupSubscript = function(pos, mode, symbol, name) {
|
||||||
var group = this.parseGroup(pos, mode);
|
var group = this.parseGroup(pos, mode);
|
||||||
|
|
||||||
|
@ -151,9 +174,11 @@ Parser.prototype.handleSupSubscript = function(pos, mode, symbol, name) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parses a group with optional super/subscripts
|
/**
|
||||||
//
|
* Parses a group with optional super/subscripts.
|
||||||
// Returns ParseResult or null
|
*
|
||||||
|
* @return {?ParseResult}
|
||||||
|
*/
|
||||||
Parser.prototype.parseAtom = function(pos, mode) {
|
Parser.prototype.parseAtom = function(pos, mode) {
|
||||||
// The body of an atom is an implicit group, so that things like
|
// The body of an atom is an implicit group, so that things like
|
||||||
// \left(x\right)^2 work correctly.
|
// \left(x\right)^2 work correctly.
|
||||||
|
@ -247,15 +272,17 @@ var styleFuncs = [
|
||||||
"\\displaystyle", "\\textstyle", "\\scriptstyle", "\\scriptscriptstyle"
|
"\\displaystyle", "\\textstyle", "\\scriptstyle", "\\scriptscriptstyle"
|
||||||
];
|
];
|
||||||
|
|
||||||
// Parses an implicit group, which is a group that starts at the end of a
|
/**
|
||||||
// specified, and ends right before a higher explicit group ends, or at EOL. It
|
* Parses an implicit group, which is a group that starts at the end of a
|
||||||
// is used for functions that appear to affect the current style, like \Large or
|
* specified, and ends right before a higher explicit group ends, or at EOL. It
|
||||||
// \textrm, where instead of keeping a style we just pretend that there is an
|
* is used for functions that appear to affect the current style, like \Large or
|
||||||
// implicit grouping after it until the end of the group. E.g.
|
* \textrm, where instead of keeping a style we just pretend that there is an
|
||||||
// small text {\Large large text} small text again
|
* implicit grouping after it until the end of the group. E.g.
|
||||||
// It is also used for \left and \right to get the correct grouping.
|
* small text {\Large large text} small text again
|
||||||
//
|
* It is also used for \left and \right to get the correct grouping.
|
||||||
// Returns ParseResult or null
|
*
|
||||||
|
* @return {?ParseResult}
|
||||||
|
*/
|
||||||
Parser.prototype.parseImplicitGroup = function(pos, mode) {
|
Parser.prototype.parseImplicitGroup = function(pos, mode) {
|
||||||
var start = this.parseSymbol(pos, mode);
|
var start = this.parseSymbol(pos, mode);
|
||||||
|
|
||||||
|
@ -320,9 +347,11 @@ Parser.prototype.parseImplicitGroup = function(pos, mode) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parses an entire function, including its base and all of its arguments
|
/**
|
||||||
//
|
* Parses an entire function, including its base and all of its arguments
|
||||||
// Returns ParseResult or null
|
*
|
||||||
|
* @return {?ParseResult}
|
||||||
|
*/
|
||||||
Parser.prototype.parseFunction = function(pos, mode) {
|
Parser.prototype.parseFunction = function(pos, mode) {
|
||||||
var baseGroup = this.parseGroup(pos, mode);
|
var baseGroup = this.parseGroup(pos, mode);
|
||||||
|
|
||||||
|
@ -392,10 +421,12 @@ Parser.prototype.parseFunction = function(pos, mode) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parses a group when the mode is changing. Takes a position, a new mode, and
|
/**
|
||||||
// an outer mode that is used to parse the outside.
|
* Parses a group when the mode is changing. Takes a position, a new mode, and
|
||||||
//
|
* an outer mode that is used to parse the outside.
|
||||||
// Returns a ParseFuncOrArgument or null
|
*
|
||||||
|
* @return {?ParseFuncOrArgument}
|
||||||
|
*/
|
||||||
Parser.prototype.parseSpecialGroup = function(pos, mode, outerMode) {
|
Parser.prototype.parseSpecialGroup = function(pos, mode, outerMode) {
|
||||||
if (mode === "color" || mode === "size") {
|
if (mode === "color" || mode === "size") {
|
||||||
// color and size modes are special because they should have braces and
|
// color and size modes are special because they should have braces and
|
||||||
|
@ -420,10 +451,12 @@ Parser.prototype.parseSpecialGroup = function(pos, mode, outerMode) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parses a group, which is either a single nucleus (like "x") or an expression
|
/**
|
||||||
// in braces (like "{x+y}")
|
* Parses a group, which is either a single nucleus (like "x") or an expression
|
||||||
//
|
* in braces (like "{x+y}")
|
||||||
// Returns a ParseFuncOrArgument or null
|
*
|
||||||
|
* @return {?ParseFuncOrArgument}
|
||||||
|
*/
|
||||||
Parser.prototype.parseGroup = function(pos, mode) {
|
Parser.prototype.parseGroup = function(pos, mode) {
|
||||||
var start = this.lexer.lex(pos, mode);
|
var start = this.lexer.lex(pos, mode);
|
||||||
// Try to parse an open brace
|
// Try to parse an open brace
|
||||||
|
@ -444,10 +477,12 @@ Parser.prototype.parseGroup = function(pos, mode) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse a single symbol out of the string. Here, we handle both the functions
|
/**
|
||||||
// we have defined, as well as the single character symbols
|
* Parse a single symbol out of the string. Here, we handle both the functions
|
||||||
//
|
* we have defined, as well as the single character symbols
|
||||||
// Returns a ParseFuncOrArgument or null
|
*
|
||||||
|
* @return {?ParseFuncOrArgument}
|
||||||
|
*/
|
||||||
Parser.prototype.parseSymbol = function(pos, mode) {
|
Parser.prototype.parseSymbol = function(pos, mode) {
|
||||||
var nucleus = this.lexer.lex(pos, mode);
|
var nucleus = this.lexer.lex(pos, mode);
|
||||||
|
|
||||||
|
|
47
Style.js
47
Style.js
|
@ -1,3 +1,17 @@
|
||||||
|
/**
|
||||||
|
* This file contains information and classes for the various kinds of styles
|
||||||
|
* used in TeX. It provides a generic `Style` class, which holds information
|
||||||
|
* about a specific style. It then provides instances of all the different kinds
|
||||||
|
* of styles possible, and provides functions to move between them and get
|
||||||
|
* information about them.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main style class. Contains a unique id for the style, a size (which is
|
||||||
|
* the same for cramped and uncramped version of a style), a cramped flag, and a
|
||||||
|
* size multiplier, which gives the size difference between a style and
|
||||||
|
* textstyle.
|
||||||
|
*/
|
||||||
function Style(id, size, multiplier, cramped) {
|
function Style(id, size, multiplier, cramped) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.size = size;
|
this.size = size;
|
||||||
|
@ -5,36 +19,59 @@ function Style(id, size, multiplier, cramped) {
|
||||||
this.sizeMultiplier = multiplier;
|
this.sizeMultiplier = multiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the style of a superscript given a base in the current style.
|
||||||
|
*/
|
||||||
Style.prototype.sup = function() {
|
Style.prototype.sup = function() {
|
||||||
return styles[sup[this.id]];
|
return styles[sup[this.id]];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the style of a subscript given a base in the current style.
|
||||||
|
*/
|
||||||
Style.prototype.sub = function() {
|
Style.prototype.sub = function() {
|
||||||
return styles[sub[this.id]];
|
return styles[sub[this.id]];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the style of a fraction numerator given the fraction in the current
|
||||||
|
* style.
|
||||||
|
*/
|
||||||
Style.prototype.fracNum = function() {
|
Style.prototype.fracNum = function() {
|
||||||
return styles[fracNum[this.id]];
|
return styles[fracNum[this.id]];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the style of a fraction denominator given the fraction in the current
|
||||||
|
* style.
|
||||||
|
*/
|
||||||
Style.prototype.fracDen = function() {
|
Style.prototype.fracDen = function() {
|
||||||
return styles[fracDen[this.id]];
|
return styles[fracDen[this.id]];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cramped version of a style (in particular, cramping a cramped style
|
||||||
|
* doesn't change the style).
|
||||||
|
*/
|
||||||
Style.prototype.cramp = function() {
|
Style.prototype.cramp = function() {
|
||||||
return styles[cramp[this.id]];
|
return styles[cramp[this.id]];
|
||||||
};
|
};
|
||||||
|
|
||||||
// HTML class name, like "displaystyle cramped"
|
/**
|
||||||
|
* HTML class name, like "displaystyle cramped"
|
||||||
|
*/
|
||||||
Style.prototype.cls = function() {
|
Style.prototype.cls = function() {
|
||||||
return sizeNames[this.size] + (this.cramped ? " cramped" : " uncramped");
|
return sizeNames[this.size] + (this.cramped ? " cramped" : " uncramped");
|
||||||
};
|
};
|
||||||
|
|
||||||
// HTML Reset class name, like "reset-textstyle"
|
/**
|
||||||
|
* HTML Reset class name, like "reset-textstyle"
|
||||||
|
*/
|
||||||
Style.prototype.reset = function() {
|
Style.prototype.reset = function() {
|
||||||
return resetNames[this.size];
|
return resetNames[this.size];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// IDs of the different styles
|
||||||
var D = 0;
|
var D = 0;
|
||||||
var Dc = 1;
|
var Dc = 1;
|
||||||
var T = 2;
|
var T = 2;
|
||||||
|
@ -44,6 +81,7 @@ var Sc = 5;
|
||||||
var SS = 6;
|
var SS = 6;
|
||||||
var SSc = 7;
|
var SSc = 7;
|
||||||
|
|
||||||
|
// String names for the different sizes
|
||||||
var sizeNames = [
|
var sizeNames = [
|
||||||
"displaystyle textstyle",
|
"displaystyle textstyle",
|
||||||
"textstyle",
|
"textstyle",
|
||||||
|
@ -51,6 +89,7 @@ var sizeNames = [
|
||||||
"scriptscriptstyle"
|
"scriptscriptstyle"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Reset names for the different sizes
|
||||||
var resetNames = [
|
var resetNames = [
|
||||||
"reset-textstyle",
|
"reset-textstyle",
|
||||||
"reset-textstyle",
|
"reset-textstyle",
|
||||||
|
@ -58,6 +97,7 @@ var resetNames = [
|
||||||
"reset-scriptscriptstyle",
|
"reset-scriptscriptstyle",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Instances of the different styles
|
||||||
var styles = [
|
var styles = [
|
||||||
new Style(D, 0, 1.0, false),
|
new Style(D, 0, 1.0, false),
|
||||||
new Style(Dc, 0, 1.0, true),
|
new Style(Dc, 0, 1.0, true),
|
||||||
|
@ -69,12 +109,15 @@ var styles = [
|
||||||
new Style(SSc, 3, 0.5, true)
|
new Style(SSc, 3, 0.5, true)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Lookup tables for switching from one style to another
|
||||||
var sup = [S, Sc, S, Sc, SS, SSc, SS, SSc];
|
var sup = [S, Sc, S, Sc, SS, SSc, SS, SSc];
|
||||||
var sub = [Sc, Sc, Sc, Sc, SSc, SSc, SSc, SSc];
|
var sub = [Sc, Sc, Sc, Sc, SSc, SSc, SSc, SSc];
|
||||||
var fracNum = [T, Tc, S, Sc, SS, SSc, SS, SSc];
|
var fracNum = [T, Tc, S, Sc, SS, SSc, SS, SSc];
|
||||||
var fracDen = [Tc, Tc, Sc, Sc, SSc, SSc, SSc, SSc];
|
var fracDen = [Tc, Tc, Sc, Sc, SSc, SSc, SSc, SSc];
|
||||||
var cramp = [Dc, Dc, Tc, Tc, Sc, Sc, SSc, SSc];
|
var cramp = [Dc, Dc, Tc, Tc, Sc, Sc, SSc, SSc];
|
||||||
|
|
||||||
|
// We only export some of the styles. Also, we don't export the `Style` class so
|
||||||
|
// no more styles can be generated.
|
||||||
module.exports = {
|
module.exports = {
|
||||||
DISPLAY: styles[D],
|
DISPLAY: styles[D],
|
||||||
TEXT: styles[T],
|
TEXT: styles[T],
|
||||||
|
|
|
@ -1,8 +1,19 @@
|
||||||
|
/**
|
||||||
|
* This module contains general functions that can be used for building
|
||||||
|
* different kinds of domTree nodes in a consistent manner.
|
||||||
|
*/
|
||||||
|
|
||||||
var domTree = require("./domTree");
|
var domTree = require("./domTree");
|
||||||
var fontMetrics = require("./fontMetrics");
|
var fontMetrics = require("./fontMetrics");
|
||||||
var symbols = require("./symbols");
|
var symbols = require("./symbols");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a symbolNode after translation via the list of symbols in symbols.js.
|
||||||
|
* Correctly pulls out metrics for the character, and optionally takes a list of
|
||||||
|
* classes to be attached to the node.
|
||||||
|
*/
|
||||||
var makeSymbol = function(value, style, mode, color, classes) {
|
var makeSymbol = function(value, style, mode, color, classes) {
|
||||||
|
// Replace the value with its replaced value from symbol.js
|
||||||
if (symbols[mode][value] && symbols[mode][value].replace) {
|
if (symbols[mode][value] && symbols[mode][value].replace) {
|
||||||
value = symbols[mode][value].replace;
|
value = symbols[mode][value].replace;
|
||||||
}
|
}
|
||||||
|
@ -15,8 +26,10 @@ var makeSymbol = function(value, style, mode, color, classes) {
|
||||||
value, metrics.height, metrics.depth, metrics.italic, metrics.skew,
|
value, metrics.height, metrics.depth, metrics.italic, metrics.skew,
|
||||||
classes);
|
classes);
|
||||||
} else {
|
} else {
|
||||||
console && console.warn("No character metrics for '" + value +
|
// TODO(emily): Figure out a good way to only print this in development
|
||||||
"' in style '" + style + "'");
|
typeof console !== "undefined" && console.warn(
|
||||||
|
"No character metrics for '" + value + "' in style '" +
|
||||||
|
style + "'");
|
||||||
symbolNode = new domTree.symbolNode(value, 0, 0, 0, 0, classes);
|
symbolNode = new domTree.symbolNode(value, 0, 0, 0, 0, classes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,12 +40,20 @@ var makeSymbol = function(value, style, mode, color, classes) {
|
||||||
return symbolNode;
|
return symbolNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a symbol in the italic math font.
|
||||||
|
*/
|
||||||
var mathit = function(value, mode, color, classes) {
|
var mathit = function(value, mode, color, classes) {
|
||||||
return makeSymbol(
|
return makeSymbol(
|
||||||
value, "Math-Italic", mode, color, classes.concat(["mathit"]));
|
value, "Math-Italic", mode, color, classes.concat(["mathit"]));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a symbol in the upright roman font.
|
||||||
|
*/
|
||||||
var mathrm = function(value, mode, color, classes) {
|
var mathrm = function(value, mode, color, classes) {
|
||||||
|
// Decide what font to render the symbol in by its entry in the symbols
|
||||||
|
// table.
|
||||||
if (symbols[mode][value].font === "main") {
|
if (symbols[mode][value].font === "main") {
|
||||||
return makeSymbol(value, "Main-Regular", mode, color, classes);
|
return makeSymbol(value, "Main-Regular", mode, color, classes);
|
||||||
} else {
|
} else {
|
||||||
|
@ -41,6 +62,10 @@ var mathrm = function(value, mode, color, classes) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the height, depth, and maxFontSize of an element based on its
|
||||||
|
* children.
|
||||||
|
*/
|
||||||
var sizeElementFromChildren = function(elem) {
|
var sizeElementFromChildren = function(elem) {
|
||||||
var height = 0;
|
var height = 0;
|
||||||
var depth = 0;
|
var depth = 0;
|
||||||
|
@ -65,6 +90,9 @@ var sizeElementFromChildren = function(elem) {
|
||||||
elem.maxFontSize = maxFontSize;
|
elem.maxFontSize = maxFontSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a span with the given list of classes, list of children, and color.
|
||||||
|
*/
|
||||||
var makeSpan = function(classes, children, color) {
|
var makeSpan = function(classes, children, color) {
|
||||||
var span = new domTree.span(classes, children);
|
var span = new domTree.span(classes, children);
|
||||||
|
|
||||||
|
@ -77,6 +105,9 @@ var makeSpan = function(classes, children, color) {
|
||||||
return span;
|
return span;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a document fragment with the given list of children.
|
||||||
|
*/
|
||||||
var makeFragment = function(children) {
|
var makeFragment = function(children) {
|
||||||
var fragment = new domTree.documentFragment(children);
|
var fragment = new domTree.documentFragment(children);
|
||||||
|
|
||||||
|
@ -85,6 +116,11 @@ var makeFragment = function(children) {
|
||||||
return fragment;
|
return fragment;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes an element placed in each of the vlist elements to ensure that each
|
||||||
|
* element has the same max font size. To do this, we create a zero-width space
|
||||||
|
* with the correct font size.
|
||||||
|
*/
|
||||||
var makeFontSizer = function(options, fontSize) {
|
var makeFontSizer = function(options, fontSize) {
|
||||||
var fontSizeInner = makeSpan([], [new domTree.symbolNode("\u200b")]);
|
var fontSizeInner = makeSpan([], [new domTree.symbolNode("\u200b")]);
|
||||||
fontSizeInner.style.fontSize = (fontSize / options.style.sizeMultiplier) + "em";
|
fontSizeInner.style.fontSize = (fontSize / options.style.sizeMultiplier) + "em";
|
||||||
|
@ -96,7 +132,7 @@ var makeFontSizer = function(options, fontSize) {
|
||||||
return fontSizer;
|
return fontSizer;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Makes a vertical list by stacking elements and kerns on top of each other.
|
* Makes a vertical list by stacking elements and kerns on top of each other.
|
||||||
* Allows for many different ways of specifying the positioning method.
|
* Allows for many different ways of specifying the positioning method.
|
||||||
*
|
*
|
||||||
|
@ -229,6 +265,5 @@ module.exports = {
|
||||||
mathrm: mathrm,
|
mathrm: mathrm,
|
||||||
makeSpan: makeSpan,
|
makeSpan: makeSpan,
|
||||||
makeFragment: makeFragment,
|
makeFragment: makeFragment,
|
||||||
makeFontSizer: makeFontSizer,
|
|
||||||
makeVList: makeVList
|
makeVList: makeVList
|
||||||
};
|
};
|
||||||
|
|
538
buildTree.js
538
buildTree.js
|
@ -1,3 +1,10 @@
|
||||||
|
/**
|
||||||
|
* This file does the main work of building a domTree sturcture from a parse
|
||||||
|
* tree. The entry point is the `buildTree` function, which takes a parse tree.
|
||||||
|
* Then, the buildExpression, buildGroup, and various groupTypes functions are
|
||||||
|
* called, to produce a final tree.
|
||||||
|
*/
|
||||||
|
|
||||||
var Options = require("./Options");
|
var Options = require("./Options");
|
||||||
var ParseError = require("./ParseError");
|
var ParseError = require("./ParseError");
|
||||||
var Style = require("./Style");
|
var Style = require("./Style");
|
||||||
|
@ -12,6 +19,11 @@ var utils = require("./utils");
|
||||||
|
|
||||||
var makeSpan = buildCommon.makeSpan;
|
var makeSpan = buildCommon.makeSpan;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take a list of nodes, build them in order, and return a list of the built
|
||||||
|
* nodes. This function handles the `prev` node correctly, and passes the
|
||||||
|
* previous element from the list as the prev of the next element.
|
||||||
|
*/
|
||||||
var buildExpression = function(expression, options, prev) {
|
var buildExpression = function(expression, options, prev) {
|
||||||
var groups = [];
|
var groups = [];
|
||||||
for (var i = 0; i < expression.length; i++) {
|
for (var i = 0; i < expression.length; i++) {
|
||||||
|
@ -22,6 +34,7 @@ var buildExpression = function(expression, options, prev) {
|
||||||
return groups;
|
return groups;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// List of types used by getTypeOfGroup
|
||||||
var groupToType = {
|
var groupToType = {
|
||||||
mathord: "mord",
|
mathord: "mord",
|
||||||
textord: "mord",
|
textord: "mord",
|
||||||
|
@ -44,6 +57,20 @@ var groupToType = {
|
||||||
accent: "mord"
|
accent: "mord"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the final math type of an expression, given its group type. This type is
|
||||||
|
* used to determine spacing between elements, and affects bin elements by
|
||||||
|
* causing them to change depending on what types are around them. This type
|
||||||
|
* must be attached to the outermost node of an element as a CSS class so that
|
||||||
|
* spacing with its surrounding elements works correctly.
|
||||||
|
*
|
||||||
|
* Some elements can be mapped one-to-one from group type to math type, and
|
||||||
|
* those are listed in the `groupToType` table.
|
||||||
|
*
|
||||||
|
* Others (usually elements that wrap around other elements) often have
|
||||||
|
* recursive definitions, and thus call `getTypeOfGroup` on their inner
|
||||||
|
* elements.
|
||||||
|
*/
|
||||||
var getTypeOfGroup = function(group) {
|
var getTypeOfGroup = function(group) {
|
||||||
if (group == null) {
|
if (group == null) {
|
||||||
// Like when typesetting $^3$
|
// Like when typesetting $^3$
|
||||||
|
@ -65,11 +92,19 @@ var getTypeOfGroup = function(group) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sometimes, groups perform special rules when they have superscripts or
|
||||||
|
* subscripts attached to them. This function lets the `supsub` group know that
|
||||||
|
* its inner element should handle the superscripts and subscripts instead of
|
||||||
|
* handling them itself.
|
||||||
|
*/
|
||||||
var shouldHandleSupSub = function(group, options) {
|
var shouldHandleSupSub = function(group, options) {
|
||||||
if (group == null) {
|
if (group == null) {
|
||||||
return false;
|
return false;
|
||||||
} else if (group.type === "op") {
|
} else if (group.type === "op") {
|
||||||
return group.value.limits && options.style.id === Style.DISPLAY.id;
|
// Operators handle supsubs differently when they have limits
|
||||||
|
// (e.g. `\displaystyle\sum_2^3`)
|
||||||
|
return group.value.limits && options.style.size === Style.DISPLAY.size;
|
||||||
} else if (group.type === "accent") {
|
} else if (group.type === "accent") {
|
||||||
return isCharacterBox(group.value.base);
|
return isCharacterBox(group.value.base);
|
||||||
} else {
|
} else {
|
||||||
|
@ -77,6 +112,11 @@ var shouldHandleSupSub = function(group, options) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sometimes we want to pull out the innermost element of a group. In most
|
||||||
|
* cases, this will just be the group itself, but when ordgroups and colors have
|
||||||
|
* a single element, we want to pull that out.
|
||||||
|
*/
|
||||||
var getBaseElem = function(group) {
|
var getBaseElem = function(group) {
|
||||||
if (group == null) {
|
if (group == null) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -97,18 +137,29 @@ var getBaseElem = function(group) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TeXbook algorithms often reference "character boxes", which are simply groups
|
||||||
|
* with a single character in them. To decide if something is a character box,
|
||||||
|
* we find its innermost group, and see if it is a single character.
|
||||||
|
*/
|
||||||
var isCharacterBox = function(group) {
|
var isCharacterBox = function(group) {
|
||||||
var baseElem = getBaseElem(group);
|
var baseElem = getBaseElem(group);
|
||||||
|
|
||||||
|
// These are all they types of groups which hold single characters
|
||||||
return baseElem.type === "mathord" ||
|
return baseElem.type === "mathord" ||
|
||||||
baseElem.type === "textord" ||
|
baseElem.type === "textord" ||
|
||||||
baseElem.type === "bin" ||
|
baseElem.type === "bin" ||
|
||||||
baseElem.type === "rel" ||
|
baseElem.type === "rel" ||
|
||||||
|
baseElem.type === "inner" ||
|
||||||
baseElem.type === "open" ||
|
baseElem.type === "open" ||
|
||||||
baseElem.type === "close" ||
|
baseElem.type === "close" ||
|
||||||
baseElem.type === "punct";
|
baseElem.type === "punct";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a map of group types to the function used to handle that type.
|
||||||
|
* Simpler types come at the beginning, while complicated types come afterwards.
|
||||||
|
*/
|
||||||
var groupTypes = {
|
var groupTypes = {
|
||||||
mathord: function(group, options, prev) {
|
mathord: function(group, options, prev) {
|
||||||
return buildCommon.mathit(
|
return buildCommon.mathit(
|
||||||
|
@ -122,11 +173,17 @@ var groupTypes = {
|
||||||
|
|
||||||
bin: function(group, options, prev) {
|
bin: function(group, options, prev) {
|
||||||
var className = "mbin";
|
var className = "mbin";
|
||||||
|
// Pull out the most recent element. Do some special handling to find
|
||||||
|
// things at the end of a \color group. Note that we don't use the same
|
||||||
|
// logic for ordgroups (which count as ords).
|
||||||
var prevAtom = prev;
|
var prevAtom = prev;
|
||||||
while (prevAtom && prevAtom.type == "color") {
|
while (prevAtom && prevAtom.type == "color") {
|
||||||
var atoms = prevAtom.value.value;
|
var atoms = prevAtom.value.value;
|
||||||
prevAtom = atoms[atoms.length - 1];
|
prevAtom = atoms[atoms.length - 1];
|
||||||
}
|
}
|
||||||
|
// See TeXbook pg. 442-446, Rules 5 and 6, and the text before Rule 19.
|
||||||
|
// Here, we determine whether the bin should turn into an ord. We
|
||||||
|
// currently only apply Rule 5.
|
||||||
if (!prev || utils.contains(["mbin", "mopen", "mrel", "mop", "mpunct"],
|
if (!prev || utils.contains(["mbin", "mopen", "mrel", "mop", "mpunct"],
|
||||||
getTypeOfGroup(prevAtom))) {
|
getTypeOfGroup(prevAtom))) {
|
||||||
group.type = "textord";
|
group.type = "textord";
|
||||||
|
@ -142,14 +199,59 @@ var groupTypes = {
|
||||||
group.value, group.mode, options.getColor(), ["mrel"]);
|
group.value, group.mode, options.getColor(), ["mrel"]);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
open: function(group, options, prev) {
|
||||||
|
return buildCommon.mathrm(
|
||||||
|
group.value, group.mode, options.getColor(), ["mopen"]);
|
||||||
|
},
|
||||||
|
|
||||||
|
close: function(group, options, prev) {
|
||||||
|
return buildCommon.mathrm(
|
||||||
|
group.value, group.mode, options.getColor(), ["mclose"]);
|
||||||
|
},
|
||||||
|
|
||||||
|
inner: function(group, options, prev) {
|
||||||
|
return buildCommon.mathrm(
|
||||||
|
group.value, group.mode, options.getColor(), ["minner"]);
|
||||||
|
},
|
||||||
|
|
||||||
|
punct: function(group, options, prev) {
|
||||||
|
return buildCommon.mathrm(
|
||||||
|
group.value, group.mode, options.getColor(), ["mpunct"]);
|
||||||
|
},
|
||||||
|
|
||||||
|
ordgroup: function(group, options, prev) {
|
||||||
|
return makeSpan(
|
||||||
|
["mord", options.style.cls()],
|
||||||
|
buildExpression(group.value, options.reset())
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
text: function(group, options, prev) {
|
text: function(group, options, prev) {
|
||||||
return makeSpan(["text", "mord", options.style.cls()],
|
return makeSpan(["text", "mord", options.style.cls()],
|
||||||
buildExpression(group.value.body, options.reset()));
|
buildExpression(group.value.body, options.reset()));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
color: function(group, options, prev) {
|
||||||
|
var elements = buildExpression(
|
||||||
|
group.value.value,
|
||||||
|
options.withColor(group.value.color),
|
||||||
|
prev
|
||||||
|
);
|
||||||
|
|
||||||
|
// \color isn't supposed to affect the type of the elements it contains.
|
||||||
|
// To accomplish this, we wrap the results in a fragment, so the inner
|
||||||
|
// elements will be able to directly interact with their neighbors. For
|
||||||
|
// example, `\color{red}{2 +} 3` has the same spacing as `2 + 3`
|
||||||
|
return new buildCommon.makeFragment(elements);
|
||||||
|
},
|
||||||
|
|
||||||
supsub: function(group, options, prev) {
|
supsub: function(group, options, prev) {
|
||||||
|
// Superscript and subscripts are handled in the TeXbook on page
|
||||||
|
// 445-446, rules 18(a-f).
|
||||||
var baseGroup = group.value.base;
|
var baseGroup = group.value.base;
|
||||||
|
|
||||||
|
// Here is where we defer to the inner group if it should handle
|
||||||
|
// superscripts and subscripts itself.
|
||||||
if (shouldHandleSupSub(group.value.base, options)) {
|
if (shouldHandleSupSub(group.value.base, options)) {
|
||||||
return groupTypes[group.value.base.type](group, options, prev);
|
return groupTypes[group.value.base.type](group, options, prev);
|
||||||
}
|
}
|
||||||
|
@ -170,77 +272,90 @@ var groupTypes = {
|
||||||
[options.style.reset(), options.style.sub().cls()], [sub]);
|
[options.style.reset(), options.style.sub().cls()], [sub]);
|
||||||
}
|
}
|
||||||
|
|
||||||
var u, v;
|
// Rule 18a
|
||||||
|
var supShift, subShift;
|
||||||
if (isCharacterBox(group.value.base)) {
|
if (isCharacterBox(group.value.base)) {
|
||||||
u = 0;
|
supShift = 0;
|
||||||
v = 0;
|
subShift = 0;
|
||||||
} else {
|
} else {
|
||||||
u = base.height - fontMetrics.metrics.supDrop;
|
supShift = base.height - fontMetrics.metrics.supDrop;
|
||||||
v = base.depth + fontMetrics.metrics.subDrop;
|
subShift = base.depth + fontMetrics.metrics.subDrop;
|
||||||
}
|
}
|
||||||
|
|
||||||
var p;
|
// Rule 18c
|
||||||
|
var minSupShift;
|
||||||
if (options.style === Style.DISPLAY) {
|
if (options.style === Style.DISPLAY) {
|
||||||
p = fontMetrics.metrics.sup1;
|
minSupShift = fontMetrics.metrics.sup1;
|
||||||
} else if (options.style.cramped) {
|
} else if (options.style.cramped) {
|
||||||
p = fontMetrics.metrics.sup3;
|
minSupShift = fontMetrics.metrics.sup3;
|
||||||
} else {
|
} else {
|
||||||
p = fontMetrics.metrics.sup2;
|
minSupShift = fontMetrics.metrics.sup2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// scriptspace is a font-size-independent size, so scale it
|
||||||
|
// appropriately
|
||||||
var multiplier = Style.TEXT.sizeMultiplier *
|
var multiplier = Style.TEXT.sizeMultiplier *
|
||||||
options.style.sizeMultiplier;
|
options.style.sizeMultiplier;
|
||||||
var scriptspace =
|
var scriptspace =
|
||||||
(0.5 / fontMetrics.metrics.ptPerEm) / multiplier + "em";
|
(0.5 / fontMetrics.metrics.ptPerEm) / multiplier + "em";
|
||||||
|
|
||||||
var supsub;
|
var supsub;
|
||||||
|
|
||||||
if (!group.value.sup) {
|
if (!group.value.sup) {
|
||||||
v = Math.max(v, fontMetrics.metrics.sub1,
|
// Rule 18b
|
||||||
|
subShift = Math.max(
|
||||||
|
subShift, fontMetrics.metrics.sub1,
|
||||||
sub.height - 0.8 * fontMetrics.metrics.xHeight);
|
sub.height - 0.8 * fontMetrics.metrics.xHeight);
|
||||||
|
|
||||||
supsub = buildCommon.makeVList([
|
supsub = buildCommon.makeVList([
|
||||||
{type: "elem", elem: submid}
|
{type: "elem", elem: submid}
|
||||||
], "shift", v, options);
|
], "shift", subShift, options);
|
||||||
|
|
||||||
supsub.children[0].style.marginRight = scriptspace;
|
supsub.children[0].style.marginRight = scriptspace;
|
||||||
|
|
||||||
|
// Subscripts shouldn't be shifted by the base's italic correction.
|
||||||
|
// Account for that by shifting the subscript back the appropriate
|
||||||
|
// amount. Note we only do this when the base is a single symbol.
|
||||||
if (base instanceof domTree.symbolNode) {
|
if (base instanceof domTree.symbolNode) {
|
||||||
supsub.children[0].style.marginLeft = -base.italic + "em";
|
supsub.children[0].style.marginLeft = -base.italic + "em";
|
||||||
}
|
}
|
||||||
} else if (!group.value.sub) {
|
} else if (!group.value.sub) {
|
||||||
u = Math.max(u, p,
|
// Rule 18c, d
|
||||||
|
supShift = Math.max(supShift, minSupShift,
|
||||||
sup.depth + 0.25 * fontMetrics.metrics.xHeight);
|
sup.depth + 0.25 * fontMetrics.metrics.xHeight);
|
||||||
|
|
||||||
supsub = buildCommon.makeVList([
|
supsub = buildCommon.makeVList([
|
||||||
{type: "elem", elem: supmid}
|
{type: "elem", elem: supmid}
|
||||||
], "shift", -u, options);
|
], "shift", -supShift, options);
|
||||||
|
|
||||||
supsub.children[0].style.marginRight = scriptspace;
|
supsub.children[0].style.marginRight = scriptspace;
|
||||||
} else {
|
} else {
|
||||||
u = Math.max(u, p,
|
supShift = Math.max(
|
||||||
|
supShift, minSupShift,
|
||||||
sup.depth + 0.25 * fontMetrics.metrics.xHeight);
|
sup.depth + 0.25 * fontMetrics.metrics.xHeight);
|
||||||
v = Math.max(v, fontMetrics.metrics.sub2);
|
subShift = Math.max(subShift, fontMetrics.metrics.sub2);
|
||||||
|
|
||||||
var theta = fontMetrics.metrics.defaultRuleThickness;
|
var ruleWidth = fontMetrics.metrics.defaultRuleThickness;
|
||||||
|
|
||||||
if ((u - sup.depth) - (sub.height - v) < 4 * theta) {
|
// Rule 18e
|
||||||
v = 4 * theta - (u - sup.depth) + sub.height;
|
if ((supShift - sup.depth) - (sub.height - subShift) <
|
||||||
var psi = 0.8 * fontMetrics.metrics.xHeight - (u - sup.depth);
|
4 * ruleWidth) {
|
||||||
|
subShift = 4 * ruleWidth - (supShift - sup.depth) + sub.height;
|
||||||
|
var psi = 0.8 * fontMetrics.metrics.xHeight -
|
||||||
|
(supShift - sup.depth);
|
||||||
if (psi > 0) {
|
if (psi > 0) {
|
||||||
u += psi;
|
supShift += psi;
|
||||||
v -= psi;
|
subShift -= psi;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
supsub = buildCommon.makeVList([
|
supsub = buildCommon.makeVList([
|
||||||
{type: "elem", elem: submid, shift: v},
|
{type: "elem", elem: submid, shift: subShift},
|
||||||
{type: "elem", elem: supmid, shift: -u}
|
{type: "elem", elem: supmid, shift: -supShift}
|
||||||
], "individualShift", null, options);
|
], "individualShift", null, options);
|
||||||
|
|
||||||
|
// See comment above about subscripts not being shifted
|
||||||
if (base instanceof domTree.symbolNode) {
|
if (base instanceof domTree.symbolNode) {
|
||||||
supsub.children[1].style.marginLeft = base.italic + "em";
|
supsub.children[0].style.marginLeft = -base.italic + "em";
|
||||||
base.italic = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
supsub.children[0].style.marginRight = scriptspace;
|
supsub.children[0].style.marginRight = scriptspace;
|
||||||
|
@ -251,22 +366,10 @@ var groupTypes = {
|
||||||
[base, supsub]);
|
[base, supsub]);
|
||||||
},
|
},
|
||||||
|
|
||||||
open: function(group, options, prev) {
|
|
||||||
return buildCommon.mathrm(
|
|
||||||
group.value, group.mode, options.getColor(), ["mopen"]);
|
|
||||||
},
|
|
||||||
|
|
||||||
close: function(group, options, prev) {
|
|
||||||
return buildCommon.mathrm(
|
|
||||||
group.value, group.mode, options.getColor(), ["mclose"]);
|
|
||||||
},
|
|
||||||
|
|
||||||
inner: function(group, options, prev) {
|
|
||||||
return buildCommon.mathrm(
|
|
||||||
group.value, group.mode, options.getColor(), ["minner"]);
|
|
||||||
},
|
|
||||||
|
|
||||||
frac: function(group, options, prev) {
|
frac: function(group, options, prev) {
|
||||||
|
// Fractions are handled in the TeXbook on pages 444-445, rules 15(a-e).
|
||||||
|
// Figure out what style this fraction should be in based on the
|
||||||
|
// function used
|
||||||
var fstyle = options.style;
|
var fstyle = options.style;
|
||||||
if (group.value.size === "dfrac") {
|
if (group.value.size === "dfrac") {
|
||||||
fstyle = Style.DISPLAY;
|
fstyle = Style.DISPLAY;
|
||||||
|
@ -283,40 +386,54 @@ var groupTypes = {
|
||||||
var denom = buildGroup(group.value.denom, options.withStyle(dstyle));
|
var denom = buildGroup(group.value.denom, options.withStyle(dstyle));
|
||||||
var denomreset = makeSpan([fstyle.reset(), dstyle.cls()], [denom])
|
var denomreset = makeSpan([fstyle.reset(), dstyle.cls()], [denom])
|
||||||
|
|
||||||
var theta = fontMetrics.metrics.defaultRuleThickness / options.style.sizeMultiplier;
|
var ruleWidth = fontMetrics.metrics.defaultRuleThickness /
|
||||||
|
options.style.sizeMultiplier;
|
||||||
|
|
||||||
var mid = makeSpan([options.style.reset(), Style.TEXT.cls(), "frac-line"]);
|
var mid = makeSpan(
|
||||||
mid.height = theta;
|
[options.style.reset(), Style.TEXT.cls(), "frac-line"]);
|
||||||
|
// Manually set the height of the line because its height is created in
|
||||||
|
// CSS
|
||||||
|
mid.height = ruleWidth;
|
||||||
|
|
||||||
var u, v, phi;
|
// Rule 15b, 15d
|
||||||
|
var numShift, denomShift, clearance;
|
||||||
if (fstyle.size === Style.DISPLAY.size) {
|
if (fstyle.size === Style.DISPLAY.size) {
|
||||||
u = fontMetrics.metrics.num1;
|
numShift = fontMetrics.metrics.num1;
|
||||||
v = fontMetrics.metrics.denom1;
|
denomShift = fontMetrics.metrics.denom1;
|
||||||
phi = 3 * theta;
|
clearance = 3 * ruleWidth;
|
||||||
} else {
|
} else {
|
||||||
u = fontMetrics.metrics.num2;
|
numShift = fontMetrics.metrics.num2;
|
||||||
v = fontMetrics.metrics.denom2;
|
denomShift = fontMetrics.metrics.denom2;
|
||||||
phi = theta;
|
clearance = ruleWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
var a = fontMetrics.metrics.axisHeight;
|
var axisHeight = fontMetrics.metrics.axisHeight;
|
||||||
|
|
||||||
if ((u - numer.depth) - (a + 0.5 * theta) < phi) {
|
// Rule 15d
|
||||||
u += phi - ((u - numer.depth) - (a + 0.5 * theta));
|
if ((numShift - numer.depth) - (axisHeight + 0.5 * ruleWidth)
|
||||||
|
< clearance) {
|
||||||
|
numShift +=
|
||||||
|
clearance - ((numShift - numer.depth) -
|
||||||
|
(axisHeight + 0.5 * ruleWidth));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((a - 0.5 * theta) - (denom.height - v) < phi) {
|
if ((axisHeight - 0.5 * ruleWidth) - (denom.height - denomShift)
|
||||||
v += phi - ((a - 0.5 * theta) - (denom.height - v));
|
< clearance) {
|
||||||
|
denomShift +=
|
||||||
|
clearance - ((axisHeight - 0.5 * ruleWidth) -
|
||||||
|
(denom.height - denomShift));
|
||||||
}
|
}
|
||||||
|
|
||||||
var midShift = -(a - 0.5 * theta);
|
var midShift = -(axisHeight - 0.5 * ruleWidth);
|
||||||
|
|
||||||
var frac = buildCommon.makeVList([
|
var frac = buildCommon.makeVList([
|
||||||
{type: "elem", elem: denomreset, shift: v},
|
{type: "elem", elem: denomreset, shift: denomShift},
|
||||||
{type: "elem", elem: mid, shift: midShift},
|
{type: "elem", elem: mid, shift: midShift},
|
||||||
{type: "elem", elem: numerreset, shift: -u}
|
{type: "elem", elem: numerreset, shift: -numShift}
|
||||||
], "individualShift", null, options);
|
], "individualShift", null, options);
|
||||||
|
|
||||||
|
// Since we manually change the style sometimes (with \dfrac or \tfrac),
|
||||||
|
// account for the possible size change here.
|
||||||
frac.height *= fstyle.sizeMultiplier / options.style.sizeMultiplier;
|
frac.height *= fstyle.sizeMultiplier / options.style.sizeMultiplier;
|
||||||
frac.depth *= fstyle.sizeMultiplier / options.style.sizeMultiplier;
|
frac.depth *= fstyle.sizeMultiplier / options.style.sizeMultiplier;
|
||||||
|
|
||||||
|
@ -325,24 +442,19 @@ var groupTypes = {
|
||||||
[frac], options.getColor());
|
[frac], options.getColor());
|
||||||
},
|
},
|
||||||
|
|
||||||
color: function(group, options, prev) {
|
|
||||||
var elements = buildExpression(
|
|
||||||
group.value.value,
|
|
||||||
options.withColor(group.value.color),
|
|
||||||
prev
|
|
||||||
);
|
|
||||||
|
|
||||||
return new buildCommon.makeFragment(elements);
|
|
||||||
},
|
|
||||||
|
|
||||||
spacing: function(group, options, prev) {
|
spacing: function(group, options, prev) {
|
||||||
if (group.value === "\\ " || group.value === "\\space" ||
|
if (group.value === "\\ " || group.value === "\\space" ||
|
||||||
group.value === " " || group.value === "~") {
|
group.value === " " || group.value === "~") {
|
||||||
|
// Spaces are generated by adding an actual space. Each of these
|
||||||
|
// things has an entry in the symbols table, so these will be turned
|
||||||
|
// into appropriate outputs.
|
||||||
return makeSpan(
|
return makeSpan(
|
||||||
["mord", "mspace"],
|
["mord", "mspace"],
|
||||||
[buildCommon.mathrm(group.value, group.mode)]
|
[buildCommon.mathrm(group.value, group.mode)]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
// Other kinds of spaces are of arbitrary width. We use CSS to
|
||||||
|
// generate these.
|
||||||
var spacingClassMap = {
|
var spacingClassMap = {
|
||||||
"\\qquad": "qquad",
|
"\\qquad": "qquad",
|
||||||
"\\quad": "quad",
|
"\\quad": "quad",
|
||||||
|
@ -374,23 +486,15 @@ var groupTypes = {
|
||||||
["rlap", options.style.cls()], [inner, fix]);
|
["rlap", options.style.cls()], [inner, fix]);
|
||||||
},
|
},
|
||||||
|
|
||||||
punct: function(group, options, prev) {
|
|
||||||
return buildCommon.mathrm(
|
|
||||||
group.value, group.mode, options.getColor(), ["mpunct"]);
|
|
||||||
},
|
|
||||||
|
|
||||||
ordgroup: function(group, options, prev) {
|
|
||||||
return makeSpan(
|
|
||||||
["mord", options.style.cls()],
|
|
||||||
buildExpression(group.value, options.reset())
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
op: function(group, options, prev) {
|
op: function(group, options, prev) {
|
||||||
|
// Operators are handled in the TeXbook pg. 443-444, rule 13(a).
|
||||||
var supGroup;
|
var supGroup;
|
||||||
var subGroup;
|
var subGroup;
|
||||||
var hasLimits = false;
|
var hasLimits = false;
|
||||||
if (group.type === "supsub" ) {
|
if (group.type === "supsub" ) {
|
||||||
|
// If we have limits, supsub will pass us its group to handle. Pull
|
||||||
|
// out the superscript and subscript and set the group to the op in
|
||||||
|
// its base.
|
||||||
supGroup = group.value.sup;
|
supGroup = group.value.sup;
|
||||||
subGroup = group.value.sub;
|
subGroup = group.value.sub;
|
||||||
group = group.value.base;
|
group = group.value.base;
|
||||||
|
@ -403,29 +507,40 @@ var groupTypes = {
|
||||||
];
|
];
|
||||||
|
|
||||||
var large = false;
|
var large = false;
|
||||||
|
if (options.style.size === Style.DISPLAY.size &&
|
||||||
if (options.style.id === Style.DISPLAY.id &&
|
|
||||||
group.value.symbol &&
|
group.value.symbol &&
|
||||||
!utils.contains(noSuccessor, group.value.body)) {
|
!utils.contains(noSuccessor, group.value.body)) {
|
||||||
|
|
||||||
// Make symbols larger in displaystyle, except for smallint
|
// Most symbol operators get larger in displaystyle (rule 13)
|
||||||
large = true;
|
large = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var base;
|
var base;
|
||||||
var baseShift = 0;
|
var baseShift = 0;
|
||||||
var delta = 0;
|
var slant = 0;
|
||||||
if (group.value.symbol) {
|
if (group.value.symbol) {
|
||||||
|
// If this is a symbol, create the symbol.
|
||||||
var style = large ? "Size2-Regular" : "Size1-Regular";
|
var style = large ? "Size2-Regular" : "Size1-Regular";
|
||||||
base = buildCommon.makeSymbol(
|
base = buildCommon.makeSymbol(
|
||||||
group.value.body, style, "math", options.getColor(),
|
group.value.body, style, "math", options.getColor(),
|
||||||
["op-symbol", large ? "large-op" : "small-op", "mop"]);
|
["op-symbol", large ? "large-op" : "small-op", "mop"]);
|
||||||
|
|
||||||
|
// Shift the symbol so its center lies on the axis (rule 13). It
|
||||||
|
// appears that our fonts have the centers of the symbols already
|
||||||
|
// almost on the axis, so these numbers are very small. Note we
|
||||||
|
// don't actually apply this here, but instead it is used either in
|
||||||
|
// the vlist creation or separately when there are no limits.
|
||||||
baseShift = (base.height - base.depth) / 2 -
|
baseShift = (base.height - base.depth) / 2 -
|
||||||
fontMetrics.metrics.axisHeight *
|
fontMetrics.metrics.axisHeight *
|
||||||
options.style.sizeMultiplier;
|
options.style.sizeMultiplier;
|
||||||
delta = base.italic;
|
|
||||||
|
// The slant of the symbol is just its italic correction.
|
||||||
|
slant = base.italic;
|
||||||
} else {
|
} else {
|
||||||
|
// Otherwise, this is a text operator. Build the text from the
|
||||||
|
// operator's name.
|
||||||
|
// TODO(emily): Add a space in the middle of some of these
|
||||||
|
// operators, like \limsup
|
||||||
var output = [];
|
var output = [];
|
||||||
for (var i = 1; i < group.value.body.length; i++) {
|
for (var i = 1; i < group.value.body.length; i++) {
|
||||||
output.push(buildCommon.mathrm(group.value.body[i], group.mode));
|
output.push(buildCommon.mathrm(group.value.body[i], group.mode));
|
||||||
|
@ -438,28 +553,34 @@ var groupTypes = {
|
||||||
// in a new span so it is an inline, and works.
|
// in a new span so it is an inline, and works.
|
||||||
var base = makeSpan([], [base]);
|
var base = makeSpan([], [base]);
|
||||||
|
|
||||||
|
var supmid, supKern, submid, subKern;
|
||||||
|
// We manually have to handle the superscripts and subscripts. This,
|
||||||
|
// aside from the kern calculations, is copied from supsub.
|
||||||
if (supGroup) {
|
if (supGroup) {
|
||||||
var sup = buildGroup(supGroup,
|
var sup = buildGroup(
|
||||||
options.withStyle(options.style.sup()));
|
supGroup, options.withStyle(options.style.sup()));
|
||||||
var supmid = makeSpan(
|
supmid = makeSpan(
|
||||||
[options.style.reset(), options.style.sup().cls()], [sup]);
|
[options.style.reset(), options.style.sup().cls()], [sup]);
|
||||||
|
|
||||||
var supKern = Math.max(
|
supKern = Math.max(
|
||||||
fontMetrics.metrics.bigOpSpacing1,
|
fontMetrics.metrics.bigOpSpacing1,
|
||||||
fontMetrics.metrics.bigOpSpacing3 - sup.depth);
|
fontMetrics.metrics.bigOpSpacing3 - sup.depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subGroup) {
|
if (subGroup) {
|
||||||
var sub = buildGroup(subGroup,
|
var sub = buildGroup(
|
||||||
options.withStyle(options.style.sub()));
|
subGroup, options.withStyle(options.style.sub()));
|
||||||
var submid = makeSpan(
|
submid = makeSpan(
|
||||||
[options.style.reset(), options.style.sub().cls()], [sub]);
|
[options.style.reset(), options.style.sub().cls()],
|
||||||
|
[sub]);
|
||||||
|
|
||||||
var subKern = Math.max(
|
subKern = Math.max(
|
||||||
fontMetrics.metrics.bigOpSpacing2,
|
fontMetrics.metrics.bigOpSpacing2,
|
||||||
fontMetrics.metrics.bigOpSpacing4 - sub.height);
|
fontMetrics.metrics.bigOpSpacing4 - sub.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build the final group as a vlist of the possible subscript, base,
|
||||||
|
// and possible superscript.
|
||||||
var finalGroup;
|
var finalGroup;
|
||||||
if (!supGroup) {
|
if (!supGroup) {
|
||||||
var top = base.height - baseShift;
|
var top = base.height - baseShift;
|
||||||
|
@ -471,7 +592,11 @@ var groupTypes = {
|
||||||
{type: "elem", elem: base}
|
{type: "elem", elem: base}
|
||||||
], "top", top, options);
|
], "top", top, options);
|
||||||
|
|
||||||
finalGroup.children[0].style.marginLeft = -delta + "em";
|
// Here, we shift the limits by the slant of the symbol. Note
|
||||||
|
// that we are supposed to shift the limits by 1/2 of the slant,
|
||||||
|
// but since we are centering the limits adding a full slant of
|
||||||
|
// margin will shift by 1/2 that.
|
||||||
|
finalGroup.children[0].style.marginLeft = -slant + "em";
|
||||||
} else if (!subGroup) {
|
} else if (!subGroup) {
|
||||||
var bottom = base.depth + baseShift;
|
var bottom = base.depth + baseShift;
|
||||||
|
|
||||||
|
@ -482,8 +607,12 @@ var groupTypes = {
|
||||||
{type: "kern", size: fontMetrics.metrics.bigOpSpacing5}
|
{type: "kern", size: fontMetrics.metrics.bigOpSpacing5}
|
||||||
], "bottom", bottom, options);
|
], "bottom", bottom, options);
|
||||||
|
|
||||||
finalGroup.children[1].style.marginLeft = delta + "em";
|
// See comment above about slants
|
||||||
|
finalGroup.children[1].style.marginLeft = slant + "em";
|
||||||
} else if (!supGroup && !subGroup) {
|
} else if (!supGroup && !subGroup) {
|
||||||
|
// This case probably shouldn't occur (this would mean the
|
||||||
|
// supsub was sending us a group with no superscript or
|
||||||
|
// subscript) but be safe.
|
||||||
return base;
|
return base;
|
||||||
} else {
|
} else {
|
||||||
var bottom = fontMetrics.metrics.bigOpSpacing5 +
|
var bottom = fontMetrics.metrics.bigOpSpacing5 +
|
||||||
|
@ -501,8 +630,9 @@ var groupTypes = {
|
||||||
{type: "kern", size: fontMetrics.metrics.bigOpSpacing5}
|
{type: "kern", size: fontMetrics.metrics.bigOpSpacing5}
|
||||||
], "bottom", bottom, options);
|
], "bottom", bottom, options);
|
||||||
|
|
||||||
finalGroup.children[0].style.marginLeft = -delta + "em";
|
// See comment above about slants
|
||||||
finalGroup.children[2].style.marginLeft = delta + "em";
|
finalGroup.children[0].style.marginLeft = -slant + "em";
|
||||||
|
finalGroup.children[2].style.marginLeft = slant + "em";
|
||||||
}
|
}
|
||||||
|
|
||||||
return makeSpan(["mop", "op-limits"], [finalGroup]);
|
return makeSpan(["mop", "op-limits"], [finalGroup]);
|
||||||
|
@ -516,6 +646,9 @@ var groupTypes = {
|
||||||
},
|
},
|
||||||
|
|
||||||
katex: function(group, options, prev) {
|
katex: function(group, options, prev) {
|
||||||
|
// The KaTeX logo. The offsets for the K and a were chosen to look
|
||||||
|
// good, but the offsets for the T, E, and X were taken from the
|
||||||
|
// definition of \TeX in TeX (see TeXbook pg. 356)
|
||||||
var k = makeSpan(
|
var k = makeSpan(
|
||||||
["k"], [buildCommon.mathrm("K", group.mode)]);
|
["k"], [buildCommon.mathrm("K", group.mode)]);
|
||||||
var a = makeSpan(
|
var a = makeSpan(
|
||||||
|
@ -539,42 +672,78 @@ var groupTypes = {
|
||||||
["katex-logo"], [k, a, t, e, x], options.getColor());
|
["katex-logo"], [k, a, t, e, x], options.getColor());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
overline: function(group, options, prev) {
|
||||||
|
// Overlines are handled in the TeXbook pg 443, Rule 9.
|
||||||
|
|
||||||
|
// Build the inner group in the cramped style.
|
||||||
|
var innerGroup = buildGroup(group.value.body,
|
||||||
|
options.withStyle(options.style.cramp()));
|
||||||
|
|
||||||
|
var ruleWidth = fontMetrics.metrics.defaultRuleThickness /
|
||||||
|
options.style.sizeMultiplier;
|
||||||
|
|
||||||
|
// Create the line above the body
|
||||||
|
var line = makeSpan(
|
||||||
|
[options.style.reset(), Style.TEXT.cls(), "overline-line"]);
|
||||||
|
line.height = ruleWidth;
|
||||||
|
line.maxFontSize = 1.0;
|
||||||
|
|
||||||
|
// Generate the vlist, with the appropriate kerns
|
||||||
|
var vlist = buildCommon.makeVList([
|
||||||
|
{type: "elem", elem: innerGroup},
|
||||||
|
{type: "kern", size: 3 * ruleWidth},
|
||||||
|
{type: "elem", elem: line},
|
||||||
|
{type: "kern", size: ruleWidth}
|
||||||
|
], "firstBaseline", null, options);
|
||||||
|
|
||||||
|
return makeSpan(["overline", "mord"], [vlist], options.getColor());
|
||||||
|
},
|
||||||
|
|
||||||
sqrt: function(group, options, prev) {
|
sqrt: function(group, options, prev) {
|
||||||
|
// Square roots are handled in the TeXbook pg. 443, Rule 11.
|
||||||
|
|
||||||
|
// First, we do the same steps as in overline to build the inner group
|
||||||
|
// and line
|
||||||
var inner = buildGroup(group.value.body,
|
var inner = buildGroup(group.value.body,
|
||||||
options.withStyle(options.style.cramp()));
|
options.withStyle(options.style.cramp()));
|
||||||
|
|
||||||
var theta = fontMetrics.metrics.defaultRuleThickness /
|
var ruleWidth = fontMetrics.metrics.defaultRuleThickness /
|
||||||
options.style.sizeMultiplier;
|
options.style.sizeMultiplier;
|
||||||
|
|
||||||
var line = makeSpan(
|
var line = makeSpan(
|
||||||
[options.style.reset(), Style.TEXT.cls(), "sqrt-line"], [],
|
[options.style.reset(), Style.TEXT.cls(), "sqrt-line"], [],
|
||||||
options.getColor());
|
options.getColor());
|
||||||
line.height = theta;
|
line.height = ruleWidth;
|
||||||
line.maxFontSize = 1.0;
|
line.maxFontSize = 1.0;
|
||||||
|
|
||||||
var phi = theta;
|
var phi = ruleWidth;
|
||||||
if (options.style.id < Style.TEXT.id) {
|
if (options.style.id < Style.TEXT.id) {
|
||||||
phi = fontMetrics.metrics.xHeight;
|
phi = fontMetrics.metrics.xHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
var psi = theta + phi / 4;
|
// Calculate the clearance between the body and line
|
||||||
|
var lineClearance = ruleWidth + phi / 4;
|
||||||
|
|
||||||
var innerHeight =
|
var innerHeight =
|
||||||
(inner.height + inner.depth) * options.style.sizeMultiplier;
|
(inner.height + inner.depth) * options.style.sizeMultiplier;
|
||||||
var minDelimiterHeight = innerHeight + psi + theta;
|
var minDelimiterHeight = innerHeight + lineClearance + ruleWidth;
|
||||||
|
|
||||||
|
// Create a \surd delimiter of the required minimum size
|
||||||
var delim = makeSpan(["sqrt-sign"], [
|
var delim = makeSpan(["sqrt-sign"], [
|
||||||
delimiter.customSizedDelim("\\surd", minDelimiterHeight,
|
delimiter.customSizedDelim("\\surd", minDelimiterHeight,
|
||||||
false, options, group.mode)],
|
false, options, group.mode)],
|
||||||
options.getColor());
|
options.getColor());
|
||||||
|
|
||||||
var delimDepth = (delim.height + delim.depth) - theta;
|
var delimDepth = (delim.height + delim.depth) - ruleWidth;
|
||||||
|
|
||||||
if (delimDepth > inner.height + inner.depth + psi) {
|
// Adjust the clearance based on the delimiter size
|
||||||
psi = (psi + delimDepth - inner.height - inner.depth) / 2;
|
if (delimDepth > inner.height + inner.depth + lineClearance) {
|
||||||
|
lineClearance =
|
||||||
|
(lineClearance + delimDepth - inner.height - inner.depth) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
delimShift = -(inner.height + psi + theta) + delim.height;
|
// Shift the delimiter so that its top lines up with the top of the line
|
||||||
|
delimShift = -(inner.height + lineClearance + ruleWidth) + delim.height;
|
||||||
delim.style.top = delimShift + "em";
|
delim.style.top = delimShift + "em";
|
||||||
delim.height -= delimShift;
|
delim.height -= delimShift;
|
||||||
delim.depth += delimShift;
|
delim.depth += delimShift;
|
||||||
|
@ -590,38 +759,19 @@ var groupTypes = {
|
||||||
} else {
|
} else {
|
||||||
body = buildCommon.makeVList([
|
body = buildCommon.makeVList([
|
||||||
{type: "elem", elem: inner},
|
{type: "elem", elem: inner},
|
||||||
{type: "kern", size: psi},
|
{type: "kern", size: lineClearance},
|
||||||
{type: "elem", elem: line},
|
{type: "elem", elem: line},
|
||||||
{type: "kern", size: theta}
|
{type: "kern", size: ruleWidth}
|
||||||
], "firstBaseline", null, options);
|
], "firstBaseline", null, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
return makeSpan(["sqrt", "mord"], [delim, body]);
|
return makeSpan(["sqrt", "mord"], [delim, body]);
|
||||||
},
|
},
|
||||||
|
|
||||||
overline: function(group, options, prev) {
|
|
||||||
var innerGroup = buildGroup(group.value.body,
|
|
||||||
options.withStyle(options.style.cramp()));
|
|
||||||
|
|
||||||
var theta = fontMetrics.metrics.defaultRuleThickness /
|
|
||||||
options.style.sizeMultiplier;
|
|
||||||
|
|
||||||
var line = makeSpan(
|
|
||||||
[options.style.reset(), Style.TEXT.cls(), "overline-line"]);
|
|
||||||
line.height = theta;
|
|
||||||
line.maxFontSize = 1.0;
|
|
||||||
|
|
||||||
var vlist = buildCommon.makeVList([
|
|
||||||
{type: "elem", elem: innerGroup},
|
|
||||||
{type: "kern", size: 3 * theta},
|
|
||||||
{type: "elem", elem: line},
|
|
||||||
{type: "kern", size: theta}
|
|
||||||
], "firstBaseline", null, options);
|
|
||||||
|
|
||||||
return makeSpan(["overline", "mord"], [vlist], options.getColor());
|
|
||||||
},
|
|
||||||
|
|
||||||
sizing: function(group, options, prev) {
|
sizing: function(group, options, prev) {
|
||||||
|
// Handle sizing operators like \Huge. Real TeX doesn't actually allow
|
||||||
|
// these functions inside of math expressions, so we do some special
|
||||||
|
// handling.
|
||||||
var inner = buildExpression(group.value.value,
|
var inner = buildExpression(group.value.value,
|
||||||
options.withSize(group.value.size), prev);
|
options.withSize(group.value.size), prev);
|
||||||
|
|
||||||
|
@ -630,26 +780,17 @@ var groupTypes = {
|
||||||
options.style.cls()],
|
options.style.cls()],
|
||||||
inner)]);
|
inner)]);
|
||||||
|
|
||||||
var sizeToFontSize = {
|
// Calculate the correct maxFontSize manually
|
||||||
"size1": 0.5,
|
var fontSize = sizingMultiplier[group.value.size];
|
||||||
"size2": 0.7,
|
|
||||||
"size3": 0.8,
|
|
||||||
"size4": 0.9,
|
|
||||||
"size5": 1.0,
|
|
||||||
"size6": 1.2,
|
|
||||||
"size7": 1.44,
|
|
||||||
"size8": 1.73,
|
|
||||||
"size9": 2.07,
|
|
||||||
"size10": 2.49
|
|
||||||
};
|
|
||||||
|
|
||||||
var fontSize = sizeToFontSize[group.value.size];
|
|
||||||
span.maxFontSize = fontSize * options.style.sizeMultiplier;
|
span.maxFontSize = fontSize * options.style.sizeMultiplier;
|
||||||
|
|
||||||
return span;
|
return span;
|
||||||
},
|
},
|
||||||
|
|
||||||
styling: function(group, options, prev) {
|
styling: function(group, options, prev) {
|
||||||
|
// Style changes are handled in the TeXbook on pg. 442, Rule 3.
|
||||||
|
|
||||||
|
// Figure out what style we're changing to.
|
||||||
var style = {
|
var style = {
|
||||||
"display": Style.DISPLAY,
|
"display": Style.DISPLAY,
|
||||||
"text": Style.TEXT,
|
"text": Style.TEXT,
|
||||||
|
@ -659,6 +800,7 @@ var groupTypes = {
|
||||||
|
|
||||||
var newStyle = style[group.value.style];
|
var newStyle = style[group.value.style];
|
||||||
|
|
||||||
|
// Build the inner expression in the new style.
|
||||||
var inner = buildExpression(
|
var inner = buildExpression(
|
||||||
group.value.value, options.withStyle(newStyle), prev);
|
group.value.value, options.withStyle(newStyle), prev);
|
||||||
|
|
||||||
|
@ -669,9 +811,12 @@ var groupTypes = {
|
||||||
var delim = group.value.value;
|
var delim = group.value.value;
|
||||||
|
|
||||||
if (delim === ".") {
|
if (delim === ".") {
|
||||||
|
// Empty delimiters still count as elements, even though they don't
|
||||||
|
// show anything.
|
||||||
return makeSpan([groupToType[group.value.delimType]]);
|
return makeSpan([groupToType[group.value.delimType]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use delimiter.sizedDelim to generate the delimiter.
|
||||||
return makeSpan(
|
return makeSpan(
|
||||||
[groupToType[group.value.delimType]],
|
[groupToType[group.value.delimType]],
|
||||||
[delimiter.sizedDelim(
|
[delimiter.sizedDelim(
|
||||||
|
@ -679,30 +824,40 @@ var groupTypes = {
|
||||||
},
|
},
|
||||||
|
|
||||||
leftright: function(group, options, prev) {
|
leftright: function(group, options, prev) {
|
||||||
|
// Build the inner expression
|
||||||
var inner = buildExpression(group.value.body, options.reset());
|
var inner = buildExpression(group.value.body, options.reset());
|
||||||
|
|
||||||
var innerHeight = 0;
|
var innerHeight = 0;
|
||||||
var innerDepth = 0;
|
var innerDepth = 0;
|
||||||
|
|
||||||
|
// Calculate its height and depth
|
||||||
for (var i = 0; i < inner.length; i++) {
|
for (var i = 0; i < inner.length; i++) {
|
||||||
innerHeight = Math.max(inner[i].height, innerHeight);
|
innerHeight = Math.max(inner[i].height, innerHeight);
|
||||||
innerDepth = Math.max(inner[i].depth, innerDepth);
|
innerDepth = Math.max(inner[i].depth, innerDepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The size of delimiters is the same, regardless of what style we are
|
||||||
|
// in. Thus, to correctly calculate the size of delimiter we need around
|
||||||
|
// a group, we scale down the inner size based on the size.
|
||||||
innerHeight *= options.style.sizeMultiplier;
|
innerHeight *= options.style.sizeMultiplier;
|
||||||
innerDepth *= options.style.sizeMultiplier;
|
innerDepth *= options.style.sizeMultiplier;
|
||||||
|
|
||||||
var leftDelim;
|
var leftDelim;
|
||||||
if (group.value.left === ".") {
|
if (group.value.left === ".") {
|
||||||
|
// Empty delimiters in \left and \right make null delimiter spaces.
|
||||||
leftDelim = makeSpan(["nulldelimiter"]);
|
leftDelim = makeSpan(["nulldelimiter"]);
|
||||||
} else {
|
} else {
|
||||||
|
// Otherwise, use leftRightDelim to generate the correct sized
|
||||||
|
// delimiter.
|
||||||
leftDelim = delimiter.leftRightDelim(
|
leftDelim = delimiter.leftRightDelim(
|
||||||
group.value.left, innerHeight, innerDepth, options,
|
group.value.left, innerHeight, innerDepth, options,
|
||||||
group.mode);
|
group.mode);
|
||||||
}
|
}
|
||||||
|
// Add it to the beginning of the expression
|
||||||
inner.unshift(leftDelim);
|
inner.unshift(leftDelim);
|
||||||
|
|
||||||
var rightDelim;
|
var rightDelim;
|
||||||
|
// Same for the right delimiter
|
||||||
if (group.value.right === ".") {
|
if (group.value.right === ".") {
|
||||||
rightDelim = makeSpan(["nulldelimiter"]);
|
rightDelim = makeSpan(["nulldelimiter"]);
|
||||||
} else {
|
} else {
|
||||||
|
@ -710,6 +865,7 @@ var groupTypes = {
|
||||||
group.value.right, innerHeight, innerDepth, options,
|
group.value.right, innerHeight, innerDepth, options,
|
||||||
group.mode);
|
group.mode);
|
||||||
}
|
}
|
||||||
|
// Add it to the end of the expression.
|
||||||
inner.push(rightDelim);
|
inner.push(rightDelim);
|
||||||
|
|
||||||
return makeSpan(
|
return makeSpan(
|
||||||
|
@ -720,6 +876,7 @@ var groupTypes = {
|
||||||
// Make an empty span for the rule
|
// Make an empty span for the rule
|
||||||
var rule = makeSpan(["mord", "rule"], [], options.getColor());
|
var rule = makeSpan(["mord", "rule"], [], options.getColor());
|
||||||
|
|
||||||
|
// Calculate the width and height of the rule, and account for units
|
||||||
var width = group.value.width.number;
|
var width = group.value.width.number;
|
||||||
if (group.value.width.unit === "ex") {
|
if (group.value.width.unit === "ex") {
|
||||||
width *= fontMetrics.metrics.xHeight;
|
width *= fontMetrics.metrics.xHeight;
|
||||||
|
@ -730,6 +887,8 @@ var groupTypes = {
|
||||||
height *= fontMetrics.metrics.xHeight;
|
height *= fontMetrics.metrics.xHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The sizes of rules are absolute, so make it larger if we are in a
|
||||||
|
// smaller style.
|
||||||
width /= options.style.sizeMultiplier;
|
width /= options.style.sizeMultiplier;
|
||||||
height /= options.style.sizeMultiplier;
|
height /= options.style.sizeMultiplier;
|
||||||
|
|
||||||
|
@ -745,36 +904,69 @@ var groupTypes = {
|
||||||
},
|
},
|
||||||
|
|
||||||
accent: function(group, options, prev) {
|
accent: function(group, options, prev) {
|
||||||
|
// Accents are handled in the TeXbook pg. 443, rule 12.
|
||||||
var base = group.value.base;
|
var base = group.value.base;
|
||||||
|
|
||||||
var supsubGroup;
|
var supsubGroup;
|
||||||
if (group.type === "supsub") {
|
if (group.type === "supsub") {
|
||||||
|
// If our base is a character box, and we have superscripts and
|
||||||
|
// subscripts, the supsub will defer to us. In particular, we want
|
||||||
|
// to attach the superscripts and subscripts to the inner body (so
|
||||||
|
// that the position of the superscripts and subscripts won't be
|
||||||
|
// affected by the height of the accent). We accomplish this by
|
||||||
|
// sticking the base of the accent into the base of the supsub, and
|
||||||
|
// rendering that, while keeping track of where the accent is.
|
||||||
|
|
||||||
|
// The supsub group is the group that was passed in
|
||||||
var supsub = group;
|
var supsub = group;
|
||||||
group = group.value.base;
|
// The real accent group is the base of the supsub group
|
||||||
|
group = supsub.value.base;
|
||||||
|
// The character box is the base of the accent group
|
||||||
base = group.value.base;
|
base = group.value.base;
|
||||||
|
// Stick the character box into the base of the supsub group
|
||||||
supsub.value.base = base;
|
supsub.value.base = base;
|
||||||
|
|
||||||
|
// Rerender the supsub group with its new base, and store that
|
||||||
|
// result.
|
||||||
supsubGroup = buildGroup(
|
supsubGroup = buildGroup(
|
||||||
supsub, options.reset());
|
supsub, options.reset(), prev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build the base group
|
||||||
var body = buildGroup(
|
var body = buildGroup(
|
||||||
base, options.withStyle(options.style.cramp()));
|
base, options.withStyle(options.style.cramp()));
|
||||||
|
|
||||||
var s;
|
// Calculate the skew of the accent. This is based on the line "If the
|
||||||
if (isCharacterBox(group.value.base)) {
|
// nucleus is not a single character, let s = 0; otherwise set s to the
|
||||||
var baseChar = getBaseElem(group.value.base);
|
// kern amount for the nucleus followed by the \skewchar of its font."
|
||||||
|
// Note that our skew metrics are just the kern between each character
|
||||||
|
// and the skewchar.
|
||||||
|
var skew;
|
||||||
|
if (isCharacterBox(base)) {
|
||||||
|
// If the base is a character box, then we want the skew of the
|
||||||
|
// innermost character. To do that, we find the innermost character:
|
||||||
|
var baseChar = getBaseElem(base);
|
||||||
|
// Then, we render its group to get the symbol inside it
|
||||||
var baseGroup = buildGroup(
|
var baseGroup = buildGroup(
|
||||||
baseChar, options.withStyle(options.style.cramp()));
|
baseChar, options.withStyle(options.style.cramp()));
|
||||||
s = baseGroup.skew;
|
// Finally, we pull the skew off of the symbol.
|
||||||
|
skew = baseGroup.skew;
|
||||||
|
// Note that we now throw away baseGroup, because the layers we
|
||||||
|
// removed with getBaseElem might contain things like \color which
|
||||||
|
// we can't get rid of.
|
||||||
|
// TODO(emily): Find a better way to get the skew
|
||||||
} else {
|
} else {
|
||||||
s = 0;
|
skew = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var delta = Math.min(body.height, fontMetrics.metrics.xHeight);
|
// calculate the amount of space between the body and the accent
|
||||||
|
var clearance = Math.min(body.height, fontMetrics.metrics.xHeight);
|
||||||
|
|
||||||
|
// Build the accent
|
||||||
var accent = buildCommon.makeSymbol(
|
var accent = buildCommon.makeSymbol(
|
||||||
group.value.accent, "Main-Regular", "math", options.getColor());
|
group.value.accent, "Main-Regular", "math", options.getColor());
|
||||||
|
// Remove the italic correction of the accent, because it only serves to
|
||||||
|
// shift the accent over to a place we don't want.
|
||||||
accent.italic = 0;
|
accent.italic = 0;
|
||||||
|
|
||||||
// The \vec character that the fonts use is a combining character, and
|
// The \vec character that the fonts use is a combining character, and
|
||||||
|
@ -788,11 +980,14 @@ var groupTypes = {
|
||||||
|
|
||||||
var accentBody = buildCommon.makeVList([
|
var accentBody = buildCommon.makeVList([
|
||||||
{type: "elem", elem: body},
|
{type: "elem", elem: body},
|
||||||
{type: "kern", size: -delta},
|
{type: "kern", size: -clearance},
|
||||||
{type: "elem", elem: accentBody}
|
{type: "elem", elem: accentBody}
|
||||||
], "firstBaseline", null, options);
|
], "firstBaseline", null, options);
|
||||||
|
|
||||||
accentBody.children[1].style.marginLeft = 2 * s + "em";
|
// Shift the accent over by the skew. Note we shift by twice the skew
|
||||||
|
// because we are centering the accent, so by adding 2*skew to the left,
|
||||||
|
// we shift it to the right by 1*skew.
|
||||||
|
accentBody.children[1].style.marginLeft = 2 * skew + "em";
|
||||||
|
|
||||||
var accentWrap = makeSpan(["mord", "accent"], [accentBody]);
|
var accentWrap = makeSpan(["mord", "accent"], [accentBody]);
|
||||||
|
|
||||||
|
@ -828,14 +1023,22 @@ var sizingMultiplier = {
|
||||||
size10: 2.49
|
size10: 2.49
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* buildGroup is the function that takes a group and calls the correct groupType
|
||||||
|
* function for it. It also handles the interaction of size and style changes
|
||||||
|
* between parents and children.
|
||||||
|
*/
|
||||||
var buildGroup = function(group, options, prev) {
|
var buildGroup = function(group, options, prev) {
|
||||||
if (!group) {
|
if (!group) {
|
||||||
return makeSpan();
|
return makeSpan();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groupTypes[group.type]) {
|
if (groupTypes[group.type]) {
|
||||||
|
// Call the groupTypes function
|
||||||
var groupNode = groupTypes[group.type](group, options, prev);
|
var groupNode = groupTypes[group.type](group, options, prev);
|
||||||
|
|
||||||
|
// If the style changed between the parent and the current group,
|
||||||
|
// account for the size difference
|
||||||
if (options.style !== options.parentStyle) {
|
if (options.style !== options.parentStyle) {
|
||||||
var multiplier = options.style.sizeMultiplier /
|
var multiplier = options.style.sizeMultiplier /
|
||||||
options.parentStyle.sizeMultiplier;
|
options.parentStyle.sizeMultiplier;
|
||||||
|
@ -844,6 +1047,8 @@ var buildGroup = function(group, options, prev) {
|
||||||
groupNode.depth *= multiplier;
|
groupNode.depth *= multiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the size changed between the parent and the current group, account
|
||||||
|
// for that size difference.
|
||||||
if (options.size !== options.parentSize) {
|
if (options.size !== options.parentSize) {
|
||||||
var multiplier = sizingMultiplier[options.size] /
|
var multiplier = sizingMultiplier[options.size] /
|
||||||
sizingMultiplier[options.parentSize];
|
sizingMultiplier[options.parentSize];
|
||||||
|
@ -859,24 +1064,33 @@ var buildGroup = function(group, options, prev) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take an entire parse tree, and build it into an appropriate set of nodes.
|
||||||
|
*/
|
||||||
var buildTree = function(tree) {
|
var buildTree = function(tree) {
|
||||||
// Setup the default options
|
// Setup the default options
|
||||||
var options = new Options(Style.TEXT, "size5", "");
|
var options = new Options(Style.TEXT, "size5", "");
|
||||||
|
|
||||||
|
// Build the expression contained in the tree
|
||||||
var expression = buildExpression(tree, options);
|
var expression = buildExpression(tree, options);
|
||||||
var span = makeSpan(["base", options.style.cls()], expression);
|
var body = makeSpan(["base", options.style.cls()], expression);
|
||||||
|
|
||||||
|
// Add struts, which ensure that the top of the HTML element falls at the
|
||||||
|
// height of the expression, and the bottom of the HTML element falls at the
|
||||||
|
// depth of the expression.
|
||||||
var topStrut = makeSpan(["strut"]);
|
var topStrut = makeSpan(["strut"]);
|
||||||
var bottomStrut = makeSpan(["strut", "bottom"]);
|
var bottomStrut = makeSpan(["strut", "bottom"]);
|
||||||
|
|
||||||
topStrut.style.height = span.height + "em";
|
topStrut.style.height = body.height + "em";
|
||||||
bottomStrut.style.height = (span.height + span.depth) + "em";
|
bottomStrut.style.height = (body.height + body.depth) + "em";
|
||||||
// We'd like to use `vertical-align: top` but in IE 9 this lowers the
|
// We'd like to use `vertical-align: top` but in IE 9 this lowers the
|
||||||
// baseline of the box to the bottom of this strut (instead staying in the
|
// baseline of the box to the bottom of this strut (instead staying in the
|
||||||
// normal place) so we use an absolute value for vertical-align instead
|
// normal place) so we use an absolute value for vertical-align instead
|
||||||
bottomStrut.style.verticalAlign = -span.depth + "em";
|
bottomStrut.style.verticalAlign = -body.depth + "em";
|
||||||
|
|
||||||
|
// Wrap the struts and body together
|
||||||
var katexNode = makeSpan(["katex"], [
|
var katexNode = makeSpan(["katex"], [
|
||||||
makeSpan(["katex-inner"], [topStrut, bottomStrut, span])
|
makeSpan(["katex-inner"], [topStrut, bottomStrut, body])
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return katexNode;
|
return katexNode;
|
||||||
|
|
167
delimiter.js
167
delimiter.js
|
@ -1,17 +1,42 @@
|
||||||
|
/**
|
||||||
|
* This file deals with creating delimiters of various sizes. The TeXbook
|
||||||
|
* discusses these routines on page 441-442, in the "Another subroutine sets box
|
||||||
|
* x to a specified variable delimiter" paragraph.
|
||||||
|
*
|
||||||
|
* There are three main routines here. `makeSmallDelim` makes a delimiter in the
|
||||||
|
* normal font, but in either text, script, or scriptscript style.
|
||||||
|
* `makeLargeDelim` makes a delimiter in textstyle, but in one of the Size1,
|
||||||
|
* Size2, Size3, or Size4 fonts. `makeStackedDelim` makes a delimiter out of
|
||||||
|
* smaller pieces that are stacked on top of one another.
|
||||||
|
*
|
||||||
|
* The functions take a parameter `center`, which determines if the delimiter
|
||||||
|
* should be centered around the axis.
|
||||||
|
*
|
||||||
|
* Then, there are three exposed functions. `sizedDelim` makes a delimiter in
|
||||||
|
* one of the given sizes. This is used for things like `\bigl`.
|
||||||
|
* `customSizedDelim` makes a delimiter with a given total height+depth. It is
|
||||||
|
* called in places like `\sqrt`. `leftRightDelim` makes an appropriate
|
||||||
|
* delimiter which surrounds an expression of a given height an depth. It is
|
||||||
|
* used in `\left` and `\right`.
|
||||||
|
*/
|
||||||
|
|
||||||
var Options = require("./Options");
|
var Options = require("./Options");
|
||||||
var ParseError = require("./ParseError");
|
var ParseError = require("./ParseError");
|
||||||
var Style = require("./Style");
|
var Style = require("./Style");
|
||||||
|
|
||||||
|
var buildCommon = require("./buildCommon");
|
||||||
var domTree = require("./domTree");
|
var domTree = require("./domTree");
|
||||||
var fontMetrics = require("./fontMetrics");
|
var fontMetrics = require("./fontMetrics");
|
||||||
var parseTree = require("./parseTree");
|
var parseTree = require("./parseTree");
|
||||||
var utils = require("./utils");
|
|
||||||
var symbols = require("./symbols");
|
var symbols = require("./symbols");
|
||||||
var buildCommon = require("./buildCommon");
|
var utils = require("./utils");
|
||||||
var makeSpan = require("./buildCommon").makeSpan;
|
|
||||||
|
|
||||||
// Get the metrics for a given symbol and font, after transformation (i.e.
|
var makeSpan = buildCommon.makeSpan;
|
||||||
// after following replacement from symbols.js)
|
|
||||||
|
/**
|
||||||
|
* Get the metrics for a given symbol and font, after transformation (i.e.
|
||||||
|
* after following replacement from symbols.js)
|
||||||
|
*/
|
||||||
var getMetrics = function(symbol, font) {
|
var getMetrics = function(symbol, font) {
|
||||||
if (symbols["math"][symbol] && symbols["math"][symbol].replace) {
|
if (symbols["math"][symbol] && symbols["math"][symbol].replace) {
|
||||||
return fontMetrics.getCharacterMetrics(
|
return fontMetrics.getCharacterMetrics(
|
||||||
|
@ -22,12 +47,20 @@ var getMetrics = function(symbol, font) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a symbol in the given font size (note size is an integer)
|
||||||
|
*/
|
||||||
var mathrmSize = function(value, size, mode) {
|
var mathrmSize = function(value, size, mode) {
|
||||||
return buildCommon.makeSymbol(value, "Size" + size + "-Regular", mode);
|
return buildCommon.makeSymbol(value, "Size" + size + "-Regular", mode);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts a delimiter span in a given style, and adds appropriate height, depth,
|
||||||
|
* and maxFontSizes.
|
||||||
|
*/
|
||||||
var styleWrap = function(delim, toStyle, options) {
|
var styleWrap = function(delim, toStyle, options) {
|
||||||
var span = makeSpan(["style-wrap", options.style.reset(), toStyle.cls()], [delim]);
|
var span = makeSpan(
|
||||||
|
["style-wrap", options.style.reset(), toStyle.cls()], [delim]);
|
||||||
|
|
||||||
var multiplier = toStyle.sizeMultiplier / options.style.sizeMultiplier;
|
var multiplier = toStyle.sizeMultiplier / options.style.sizeMultiplier;
|
||||||
|
|
||||||
|
@ -38,6 +71,11 @@ var styleWrap = function(delim, toStyle, options) {
|
||||||
return span;
|
return span;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a small delimiter. This is a delimiter that comes in the Main-Regular
|
||||||
|
* font, but is restyled to either be in textstyle, scriptstyle, or
|
||||||
|
* scriptscriptstyle.
|
||||||
|
*/
|
||||||
var makeSmallDelim = function(delim, style, center, options, mode) {
|
var makeSmallDelim = function(delim, style, center, options, mode) {
|
||||||
var text = buildCommon.makeSymbol(delim, "Main-Regular", mode);
|
var text = buildCommon.makeSymbol(delim, "Main-Regular", mode);
|
||||||
|
|
||||||
|
@ -56,6 +94,10 @@ var makeSmallDelim = function(delim, style, center, options, mode) {
|
||||||
return span;
|
return span;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a large delimiter. This is a delimiter that comes in the Size1, Size2,
|
||||||
|
* Size3, or Size4 fonts. It is always rendered in textstyle.
|
||||||
|
*/
|
||||||
var makeLargeDelim = function(delim, size, center, options, mode) {
|
var makeLargeDelim = function(delim, size, center, options, mode) {
|
||||||
var inner = mathrmSize(delim, size, mode);
|
var inner = mathrmSize(delim, size, mode);
|
||||||
|
|
||||||
|
@ -76,9 +118,13 @@ var makeLargeDelim = function(delim, size, center, options, mode) {
|
||||||
return span;
|
return span;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Make an inner span with the given offset and in the given font
|
/**
|
||||||
|
* Make an inner span with the given offset and in the given font. This is used
|
||||||
|
* in `makeStackedDelim` to make the stacking pieces for the delimiter.
|
||||||
|
*/
|
||||||
var makeInner = function(symbol, font, mode) {
|
var makeInner = function(symbol, font, mode) {
|
||||||
var sizeClass;
|
var sizeClass;
|
||||||
|
// Apply the correct CSS class to choose the right font.
|
||||||
if (font === "Size1-Regular") {
|
if (font === "Size1-Regular") {
|
||||||
sizeClass = "delim-size1";
|
sizeClass = "delim-size1";
|
||||||
} else if (font === "Size4-Regular") {
|
} else if (font === "Size4-Regular") {
|
||||||
|
@ -89,14 +135,22 @@ var makeInner = function(symbol, font, mode) {
|
||||||
["delimsizinginner", sizeClass],
|
["delimsizinginner", sizeClass],
|
||||||
[makeSpan([], [buildCommon.makeSymbol(symbol, font, mode)])]);
|
[makeSpan([], [buildCommon.makeSymbol(symbol, font, mode)])]);
|
||||||
|
|
||||||
|
// Since this will be passed into `makeVList` in the end, wrap the element
|
||||||
|
// in the appropriate tag that VList uses.
|
||||||
return {type: "elem", elem: inner};
|
return {type: "elem", elem: inner};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a stacked delimiter out of a given delimiter, with the total height at
|
||||||
|
* least `heightTotal`. This routine is mentioned on page 442 of the TeXbook.
|
||||||
|
*/
|
||||||
var makeStackedDelim = function(delim, heightTotal, center, options, mode) {
|
var makeStackedDelim = function(delim, heightTotal, center, options, mode) {
|
||||||
// There are four parts, the top, a middle, a repeated part, and a bottom.
|
// There are four parts, the top, an optional middle, a repeated part, and a
|
||||||
|
// bottom.
|
||||||
var top, middle, repeat, bottom;
|
var top, middle, repeat, bottom;
|
||||||
top = repeat = bottom = delim;
|
top = repeat = bottom = delim;
|
||||||
middle = null;
|
middle = null;
|
||||||
|
// Also keep track of what font the delimiters are in
|
||||||
var font = "Size1-Regular";
|
var font = "Size1-Regular";
|
||||||
|
|
||||||
// We set the parts and font based on the symbol. Note that we use
|
// We set the parts and font based on the symbol. Note that we use
|
||||||
|
@ -175,7 +229,7 @@ var makeStackedDelim = function(delim, heightTotal, center, options, mode) {
|
||||||
font = "Size4-Regular";
|
font = "Size4-Regular";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the metrics of the three sections
|
// Get the metrics of the four sections
|
||||||
var topMetrics = getMetrics(top, font);
|
var topMetrics = getMetrics(top, font);
|
||||||
var topHeightTotal = topMetrics.height + topMetrics.depth;
|
var topHeightTotal = topMetrics.height + topMetrics.depth;
|
||||||
var repeatMetrics = getMetrics(repeat, font);
|
var repeatMetrics = getMetrics(repeat, font);
|
||||||
|
@ -188,36 +242,49 @@ var makeStackedDelim = function(delim, heightTotal, center, options, mode) {
|
||||||
middleHeightTotal = middleMetrics.height + middleMetrics.depth;
|
middleHeightTotal = middleMetrics.height + middleMetrics.depth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calcuate the real height that the delimiter will have. It is at least the
|
||||||
|
// size of the top, bottom, and optional middle combined.
|
||||||
var realHeightTotal = topHeightTotal + bottomHeightTotal;
|
var realHeightTotal = topHeightTotal + bottomHeightTotal;
|
||||||
if (middle !== null) {
|
if (middle !== null) {
|
||||||
realHeightTotal += middleHeightTotal;
|
realHeightTotal += middleHeightTotal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Then add repeated pieces until we reach the specified height.
|
||||||
while (realHeightTotal < heightTotal) {
|
while (realHeightTotal < heightTotal) {
|
||||||
realHeightTotal += repeatHeightTotal;
|
realHeightTotal += repeatHeightTotal;
|
||||||
if (middle !== null) {
|
if (middle !== null) {
|
||||||
|
// If there is a middle section, we need an equal number of pieces
|
||||||
|
// on the top and bottom.
|
||||||
realHeightTotal += repeatHeightTotal;
|
realHeightTotal += repeatHeightTotal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The center of the delimiter is placed at the center of the axis. Note
|
||||||
|
// that in this context, "center" means that the delimiter should be
|
||||||
|
// centered around the axis in the current style, while normally it is
|
||||||
|
// centered around the axis in textstyle.
|
||||||
var axisHeight = fontMetrics.metrics.axisHeight;
|
var axisHeight = fontMetrics.metrics.axisHeight;
|
||||||
if (center) {
|
if (center) {
|
||||||
axisHeight *= options.style.sizeMultiplier;
|
axisHeight *= options.style.sizeMultiplier;
|
||||||
}
|
}
|
||||||
|
// Calculate the height and depth
|
||||||
var height = realHeightTotal / 2 + axisHeight;
|
var height = realHeightTotal / 2 + axisHeight;
|
||||||
var depth = realHeightTotal / 2 - axisHeight;
|
var depth = realHeightTotal / 2 - axisHeight;
|
||||||
|
|
||||||
// Keep a list of the inner spans
|
// Now, we start building the pieces that will go into the vlist
|
||||||
|
|
||||||
|
// Keep a list of the inner pieces
|
||||||
var inners = [];
|
var inners = [];
|
||||||
|
|
||||||
// Add the bottom symbol
|
// Add the bottom symbol
|
||||||
inners.push(makeInner(bottom, font, mode));
|
inners.push(makeInner(bottom, font, mode));
|
||||||
|
|
||||||
if (middle === null) {
|
if (middle === null) {
|
||||||
|
// Calculate the number of repeated symbols we need
|
||||||
var repeatHeight = realHeightTotal - topHeightTotal - bottomHeightTotal;
|
var repeatHeight = realHeightTotal - topHeightTotal - bottomHeightTotal;
|
||||||
var symbolCount = Math.ceil(repeatHeight / repeatHeightTotal);
|
var symbolCount = Math.ceil(repeatHeight / repeatHeightTotal);
|
||||||
|
|
||||||
// Add repeat symbols until there's only space for the bottom symbol
|
// Add that many symbols
|
||||||
for (var i = 0; i < symbolCount; i++) {
|
for (var i = 0; i < symbolCount; i++) {
|
||||||
inners.push(makeInner(repeat, font, mode));
|
inners.push(makeInner(repeat, font, mode));
|
||||||
}
|
}
|
||||||
|
@ -250,8 +317,10 @@ var makeStackedDelim = function(delim, heightTotal, center, options, mode) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the top symbol
|
||||||
inners.push(makeInner(top, font, mode));
|
inners.push(makeInner(top, font, mode));
|
||||||
|
|
||||||
|
// Finally, build the vlist
|
||||||
var inner = buildCommon.makeVList(inners, "bottom", depth, options);
|
var inner = buildCommon.makeVList(inners, "bottom", depth, options);
|
||||||
|
|
||||||
return styleWrap(
|
return styleWrap(
|
||||||
|
@ -259,29 +328,37 @@ var makeStackedDelim = function(delim, heightTotal, center, options, mode) {
|
||||||
Style.TEXT, options);
|
Style.TEXT, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
var normalDelimiters = [
|
// There are three kinds of delimiters, delimiters that stack when they become
|
||||||
|
// too large
|
||||||
|
var stackLargeDelimiters = [
|
||||||
"(", ")", "[", "\\lbrack", "]", "\\rbrack",
|
"(", ")", "[", "\\lbrack", "]", "\\rbrack",
|
||||||
"\\{", "\\lbrace", "\\}", "\\rbrace",
|
"\\{", "\\lbrace", "\\}", "\\rbrace",
|
||||||
"\\lfloor", "\\rfloor", "\\lceil", "\\rceil",
|
"\\lfloor", "\\rfloor", "\\lceil", "\\rceil",
|
||||||
"<", ">", "\\langle", "\\rangle", "/", "\\backslash",
|
|
||||||
"\\surd"
|
"\\surd"
|
||||||
];
|
];
|
||||||
|
|
||||||
var stackDelimiters = [
|
// delimiters that always stack
|
||||||
|
var stackAlwaysDelimiters = [
|
||||||
"\\uparrow", "\\downarrow", "\\updownarrow",
|
"\\uparrow", "\\downarrow", "\\updownarrow",
|
||||||
"\\Uparrow", "\\Downarrow", "\\Updownarrow",
|
"\\Uparrow", "\\Downarrow", "\\Updownarrow",
|
||||||
"|", "\\|", "\\vert", "\\Vert"
|
"|", "\\|", "\\vert", "\\Vert"
|
||||||
];
|
];
|
||||||
|
|
||||||
var onlyNormalDelimiters = [
|
// and delimiters that never stack
|
||||||
|
var stackNeverDelimiters = [
|
||||||
"<", ">", "\\langle", "\\rangle", "/", "\\backslash"
|
"<", ">", "\\langle", "\\rangle", "/", "\\backslash"
|
||||||
];
|
];
|
||||||
|
|
||||||
// Metrics of the different sizes. Found by looking at TeX's output of
|
// Metrics of the different sizes. Found by looking at TeX's output of
|
||||||
// $\bigl| \Bigl| \biggl| \Biggl| \showlists$
|
// $\bigl| // \Bigl| \biggl| \Biggl| \showlists$
|
||||||
|
// Used to create stacked delimiters of appropriate sizes in makeSizedDelim.
|
||||||
var sizeToMaxHeight = [0, 1.2, 1.8, 2.4, 3.0];
|
var sizeToMaxHeight = [0, 1.2, 1.8, 2.4, 3.0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to create a delimiter of a specific size, where `size` is 1, 2, 3, or 4.
|
||||||
|
*/
|
||||||
var makeSizedDelim = function(delim, size, options, mode) {
|
var makeSizedDelim = function(delim, size, options, mode) {
|
||||||
|
// < and > turn into \langle and \rangle in delimiters
|
||||||
if (delim === "<") {
|
if (delim === "<") {
|
||||||
delim = "\\langle";
|
delim = "\\langle";
|
||||||
} else if (delim === ">") {
|
} else if (delim === ">") {
|
||||||
|
@ -290,9 +367,11 @@ var makeSizedDelim = function(delim, size, options, mode) {
|
||||||
|
|
||||||
var retDelim;
|
var retDelim;
|
||||||
|
|
||||||
if (utils.contains(normalDelimiters, delim)) {
|
// Sized delimiters are never centered.
|
||||||
|
if (utils.contains(stackLargeDelimiters, delim) ||
|
||||||
|
utils.contains(stackNeverDelimiters, delim)) {
|
||||||
return makeLargeDelim(delim, size, false, options, mode);
|
return makeLargeDelim(delim, size, false, options, mode);
|
||||||
} else if (utils.contains(stackDelimiters, delim)) {
|
} else if (utils.contains(stackAlwaysDelimiters, delim)) {
|
||||||
return makeStackedDelim(
|
return makeStackedDelim(
|
||||||
delim, sizeToMaxHeight[size], false, options, mode);
|
delim, sizeToMaxHeight[size], false, options, mode);
|
||||||
} else {
|
} else {
|
||||||
|
@ -300,7 +379,20 @@ var makeSizedDelim = function(delim, size, options, mode) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var normalDelimiterSequence = [
|
/**
|
||||||
|
* There are three different sequences of delimiter sizes that the delimiters
|
||||||
|
* follow depending on the kind of delimiter. This is used when creating custom
|
||||||
|
* sized delimiters to decide whether to create a small, large, or stacked
|
||||||
|
* delimiter.
|
||||||
|
*
|
||||||
|
* In real TeX, these sequences aren't explicitly defined, but are instead
|
||||||
|
* defined inside the font metrics. Since there are only three sequences that
|
||||||
|
* are possible for the delimiters that TeX defines, it is easier to just encode
|
||||||
|
* them explicitly here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Delimiters that never stack try small delimiters and large delimiters only
|
||||||
|
var stackNeverDelimiterSequence = [
|
||||||
{type: "small", style: Style.SCRIPTSCRIPT},
|
{type: "small", style: Style.SCRIPTSCRIPT},
|
||||||
{type: "small", style: Style.SCRIPT},
|
{type: "small", style: Style.SCRIPT},
|
||||||
{type: "small", style: Style.TEXT},
|
{type: "small", style: Style.TEXT},
|
||||||
|
@ -310,6 +402,7 @@ var normalDelimiterSequence = [
|
||||||
{type: "large", size: 4}
|
{type: "large", size: 4}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Delimiters that always stack try the small delimiters first, then stack
|
||||||
var stackAlwaysDelimiterSequence = [
|
var stackAlwaysDelimiterSequence = [
|
||||||
{type: "small", style: Style.SCRIPTSCRIPT},
|
{type: "small", style: Style.SCRIPTSCRIPT},
|
||||||
{type: "small", style: Style.SCRIPT},
|
{type: "small", style: Style.SCRIPT},
|
||||||
|
@ -317,6 +410,8 @@ var stackAlwaysDelimiterSequence = [
|
||||||
{type: "stack"}
|
{type: "stack"}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Delimiters that stack when large try the small and then large delimiters, and
|
||||||
|
// stack afterwards
|
||||||
var stackLargeDelimiterSequence = [
|
var stackLargeDelimiterSequence = [
|
||||||
{type: "small", style: Style.SCRIPTSCRIPT},
|
{type: "small", style: Style.SCRIPTSCRIPT},
|
||||||
{type: "small", style: Style.SCRIPT},
|
{type: "small", style: Style.SCRIPT},
|
||||||
|
@ -328,6 +423,9 @@ var stackLargeDelimiterSequence = [
|
||||||
{type: "stack"}
|
{type: "stack"}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the font used in a delimiter based on what kind of delimiter it is.
|
||||||
|
*/
|
||||||
var delimTypeToFont = function(type) {
|
var delimTypeToFont = function(type) {
|
||||||
if (type.type === "small") {
|
if (type.type === "small") {
|
||||||
return "Main-Regular";
|
return "Main-Regular";
|
||||||
|
@ -338,6 +436,10 @@ var delimTypeToFont = function(type) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traverse a sequence of types of delimiters to decide what kind of delimiter
|
||||||
|
* should be used to create a delimiter of the given height+depth.
|
||||||
|
*/
|
||||||
var traverseSequence = function(delim, height, sequence, options) {
|
var traverseSequence = function(delim, height, sequence, options) {
|
||||||
// Here, we choose the index we should start at in the sequences. In smaller
|
// Here, we choose the index we should start at in the sequences. In smaller
|
||||||
// sizes (which correspond to larger numbers in style.size) we start earlier
|
// sizes (which correspond to larger numbers in style.size) we start earlier
|
||||||
|
@ -351,21 +453,29 @@ var traverseSequence = function(delim, height, sequence, options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var metrics = getMetrics(delim, delimTypeToFont(sequence[i]));
|
var metrics = getMetrics(delim, delimTypeToFont(sequence[i]));
|
||||||
|
|
||||||
var heightDepth = metrics.height + metrics.depth;
|
var heightDepth = metrics.height + metrics.depth;
|
||||||
|
|
||||||
|
// Small delimiters are scaled down versions of the same font, so we
|
||||||
|
// account for the style change size.
|
||||||
|
|
||||||
if (sequence[i].type === "small") {
|
if (sequence[i].type === "small") {
|
||||||
heightDepth *= sequence[i].style.sizeMultiplier;
|
heightDepth *= sequence[i].style.sizeMultiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the delimiter at this size works for the given height.
|
||||||
if (heightDepth > height) {
|
if (heightDepth > height) {
|
||||||
return sequence[i];
|
return sequence[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we reached the end of the sequence, return the last sequence element.
|
||||||
return sequence[sequence.length - 1];
|
return sequence[sequence.length - 1];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a delimiter of a given height+depth, with optional centering. Here, we
|
||||||
|
* traverse the sequences, and create a delimiter that the sequence tells us to.
|
||||||
|
*/
|
||||||
var makeCustomSizedDelim = function(delim, height, center, options, mode) {
|
var makeCustomSizedDelim = function(delim, height, center, options, mode) {
|
||||||
if (delim === "<") {
|
if (delim === "<") {
|
||||||
delim = "\\langle";
|
delim = "\\langle";
|
||||||
|
@ -373,17 +483,21 @@ var makeCustomSizedDelim = function(delim, height, center, options, mode) {
|
||||||
delim = "\\rangle";
|
delim = "\\rangle";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decide what sequence to use
|
||||||
var sequence;
|
var sequence;
|
||||||
if (utils.contains(onlyNormalDelimiters, delim)) {
|
if (utils.contains(stackNeverDelimiters, delim)) {
|
||||||
sequence = normalDelimiterSequence;
|
sequence = stackNeverDelimiterSequence;
|
||||||
} else if (utils.contains(normalDelimiters, delim)) {
|
} else if (utils.contains(stackLargeDelimiters, delim)) {
|
||||||
sequence = stackLargeDelimiterSequence;
|
sequence = stackLargeDelimiterSequence;
|
||||||
} else {
|
} else {
|
||||||
sequence = stackAlwaysDelimiterSequence;
|
sequence = stackAlwaysDelimiterSequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Look through the sequence
|
||||||
var delimType = traverseSequence(delim, height, sequence, options);
|
var delimType = traverseSequence(delim, height, sequence, options);
|
||||||
|
|
||||||
|
// Depending on the sequence element we decided on, call the appropriate
|
||||||
|
// function.
|
||||||
if (delimType.type === "small") {
|
if (delimType.type === "small") {
|
||||||
return makeSmallDelim(delim, delimType.style, center, options, mode);
|
return makeSmallDelim(delim, delimType.style, center, options, mode);
|
||||||
} else if (delimType.type === "large") {
|
} else if (delimType.type === "large") {
|
||||||
|
@ -393,7 +507,12 @@ var makeCustomSizedDelim = function(delim, height, center, options, mode) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a delimiter for use with `\left` and `\right`, given a height and depth
|
||||||
|
* of an expression that the delimiters surround.
|
||||||
|
*/
|
||||||
var makeLeftRightDelim = function(delim, height, depth, options, mode) {
|
var makeLeftRightDelim = function(delim, height, depth, options, mode) {
|
||||||
|
// We always center \left/\right delimiters, so the axis is always shifted
|
||||||
var axisHeight =
|
var axisHeight =
|
||||||
fontMetrics.metrics.axisHeight * options.style.sizeMultiplier;
|
fontMetrics.metrics.axisHeight * options.style.sizeMultiplier;
|
||||||
|
|
||||||
|
@ -417,6 +536,8 @@ var makeLeftRightDelim = function(delim, height, depth, options, mode) {
|
||||||
maxDistFromAxis / 500 * delimiterFactor,
|
maxDistFromAxis / 500 * delimiterFactor,
|
||||||
2 * maxDistFromAxis - delimiterExtend);
|
2 * maxDistFromAxis - delimiterExtend);
|
||||||
|
|
||||||
|
// Finally, we defer to `makeCustomSizedDelim` with our calculated total
|
||||||
|
// height
|
||||||
return makeCustomSizedDelim(delim, totalHeight, true, options, mode);
|
return makeCustomSizedDelim(delim, totalHeight, true, options, mode);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
60
domTree.js
60
domTree.js
|
@ -1,11 +1,17 @@
|
||||||
// These objects store the data about the DOM nodes we create, as well as some
|
/**
|
||||||
// extra data. They can then be transformed into real DOM nodes with the toNode
|
* These objects store the data about the DOM nodes we create, as well as some
|
||||||
// function or HTML markup using toMarkup. They are useful for both storing
|
* extra data. They can then be transformed into real DOM nodes with the toNode
|
||||||
// extra properties on the nodes, as well as providing a way to easily work
|
* function or HTML markup using toMarkup. They are useful for both storing
|
||||||
// with the DOM.
|
* extra properties on the nodes, as well as providing a way to easily work
|
||||||
|
* with the DOM.
|
||||||
|
*/
|
||||||
|
|
||||||
var utils = require("./utils");
|
var utils = require("./utils");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an HTML className based on a list of classes. In addition to joining
|
||||||
|
* with spaces, we also remove null or empty classes.
|
||||||
|
*/
|
||||||
var createClass = function(classes) {
|
var createClass = function(classes) {
|
||||||
classes = classes.slice();
|
classes = classes.slice();
|
||||||
for (var i = classes.length - 1; i >= 0; i--) {
|
for (var i = classes.length - 1; i >= 0; i--) {
|
||||||
|
@ -17,6 +23,11 @@ var createClass = function(classes) {
|
||||||
return classes.join(" ");
|
return classes.join(" ");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This node represents a span node, with a className, a list of children, and
|
||||||
|
* an inline style. It also contains information about its height, depth, and
|
||||||
|
* maxFontSize.
|
||||||
|
*/
|
||||||
function span(classes, children, height, depth, maxFontSize, style) {
|
function span(classes, children, height, depth, maxFontSize, style) {
|
||||||
this.classes = classes || [];
|
this.classes = classes || [];
|
||||||
this.children = children || [];
|
this.children = children || [];
|
||||||
|
@ -26,17 +37,23 @@ function span(classes, children, height, depth, maxFontSize, style) {
|
||||||
this.style = style || {};
|
this.style = style || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the span into an HTML node
|
||||||
|
*/
|
||||||
span.prototype.toNode = function() {
|
span.prototype.toNode = function() {
|
||||||
var span = document.createElement("span");
|
var span = document.createElement("span");
|
||||||
|
|
||||||
|
// Apply the class
|
||||||
span.className = createClass(this.classes);
|
span.className = createClass(this.classes);
|
||||||
|
|
||||||
|
// Apply inline styles
|
||||||
for (var style in this.style) {
|
for (var style in this.style) {
|
||||||
if (this.style.hasOwnProperty(style)) {
|
if (this.style.hasOwnProperty(style)) {
|
||||||
span.style[style] = this.style[style];
|
span.style[style] = this.style[style];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Append the children, also as HTML nodes
|
||||||
for (var i = 0; i < this.children.length; i++) {
|
for (var i = 0; i < this.children.length; i++) {
|
||||||
span.appendChild(this.children[i].toNode());
|
span.appendChild(this.children[i].toNode());
|
||||||
}
|
}
|
||||||
|
@ -44,9 +61,13 @@ span.prototype.toNode = function() {
|
||||||
return span;
|
return span;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the span into an HTML markup string
|
||||||
|
*/
|
||||||
span.prototype.toMarkup = function() {
|
span.prototype.toMarkup = function() {
|
||||||
var markup = "<span";
|
var markup = "<span";
|
||||||
|
|
||||||
|
// Add the class
|
||||||
if (this.classes.length) {
|
if (this.classes.length) {
|
||||||
markup += " class=\"";
|
markup += " class=\"";
|
||||||
markup += utils.escape(createClass(this.classes));
|
markup += utils.escape(createClass(this.classes));
|
||||||
|
@ -55,6 +76,7 @@ span.prototype.toMarkup = function() {
|
||||||
|
|
||||||
var styles = "";
|
var styles = "";
|
||||||
|
|
||||||
|
// Add the styles, after hyphenation
|
||||||
for (var style in this.style) {
|
for (var style in this.style) {
|
||||||
if (this.style.hasOwnProperty(style)) {
|
if (this.style.hasOwnProperty(style)) {
|
||||||
styles += utils.hyphenate(style) + ":" + this.style[style] + ";";
|
styles += utils.hyphenate(style) + ":" + this.style[style] + ";";
|
||||||
|
@ -67,6 +89,7 @@ span.prototype.toMarkup = function() {
|
||||||
|
|
||||||
markup += ">";
|
markup += ">";
|
||||||
|
|
||||||
|
// Add the markup of the children, also as markup
|
||||||
for (var i = 0; i < this.children.length; i++) {
|
for (var i = 0; i < this.children.length; i++) {
|
||||||
markup += this.children[i].toMarkup();
|
markup += this.children[i].toMarkup();
|
||||||
}
|
}
|
||||||
|
@ -76,6 +99,12 @@ span.prototype.toMarkup = function() {
|
||||||
return markup;
|
return markup;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This node represents a document fragment, which contains elements, but when
|
||||||
|
* placed into the DOM doesn't have any representation itself. Thus, it only
|
||||||
|
* contains children and doesn't have any HTML properties. It also keeps track
|
||||||
|
* of a height, depth, and maxFontSize.
|
||||||
|
*/
|
||||||
function documentFragment(children, height, depth, maxFontSize) {
|
function documentFragment(children, height, depth, maxFontSize) {
|
||||||
this.children = children || [];
|
this.children = children || [];
|
||||||
this.height = height || 0;
|
this.height = height || 0;
|
||||||
|
@ -83,9 +112,14 @@ function documentFragment(children, height, depth, maxFontSize) {
|
||||||
this.maxFontSize = maxFontSize || 0;
|
this.maxFontSize = maxFontSize || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the fragment into a node
|
||||||
|
*/
|
||||||
documentFragment.prototype.toNode = function() {
|
documentFragment.prototype.toNode = function() {
|
||||||
|
// Create a fragment
|
||||||
var frag = document.createDocumentFragment();
|
var frag = document.createDocumentFragment();
|
||||||
|
|
||||||
|
// Append the children
|
||||||
for (var i = 0; i < this.children.length; i++) {
|
for (var i = 0; i < this.children.length; i++) {
|
||||||
frag.appendChild(this.children[i].toNode());
|
frag.appendChild(this.children[i].toNode());
|
||||||
}
|
}
|
||||||
|
@ -93,9 +127,13 @@ documentFragment.prototype.toNode = function() {
|
||||||
return frag;
|
return frag;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the fragment into HTML markup
|
||||||
|
*/
|
||||||
documentFragment.prototype.toMarkup = function() {
|
documentFragment.prototype.toMarkup = function() {
|
||||||
var markup = "";
|
var markup = "";
|
||||||
|
|
||||||
|
// Simply concatenate the markup for the children together
|
||||||
for (var i = 0; i < this.children.length; i++) {
|
for (var i = 0; i < this.children.length; i++) {
|
||||||
markup += this.children[i].toMarkup();
|
markup += this.children[i].toMarkup();
|
||||||
}
|
}
|
||||||
|
@ -103,6 +141,11 @@ documentFragment.prototype.toMarkup = function() {
|
||||||
return markup;
|
return markup;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A symbol node contains information about a single symbol. It either renders
|
||||||
|
* to a single text node, or a span with a single text node in it, depending on
|
||||||
|
* whether it has CSS classes, styles, or needs italic correction.
|
||||||
|
*/
|
||||||
function symbolNode(value, height, depth, italic, skew, classes, style) {
|
function symbolNode(value, height, depth, italic, skew, classes, style) {
|
||||||
this.value = value || "";
|
this.value = value || "";
|
||||||
this.height = height || 0;
|
this.height = height || 0;
|
||||||
|
@ -114,6 +157,10 @@ function symbolNode(value, height, depth, italic, skew, classes, style) {
|
||||||
this.maxFontSize = 0;
|
this.maxFontSize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a text node or span from a symbol node. Note that a span is only
|
||||||
|
* created if it is needed.
|
||||||
|
*/
|
||||||
symbolNode.prototype.toNode = function() {
|
symbolNode.prototype.toNode = function() {
|
||||||
var node = document.createTextNode(this.value);
|
var node = document.createTextNode(this.value);
|
||||||
var span = null;
|
var span = null;
|
||||||
|
@ -143,6 +190,9 @@ symbolNode.prototype.toNode = function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates markup for a symbol node.
|
||||||
|
*/
|
||||||
symbolNode.prototype.toMarkup = function() {
|
symbolNode.prototype.toMarkup = function() {
|
||||||
// TODO(alpert): More duplication than I'd like from
|
// TODO(alpert): More duplication than I'd like from
|
||||||
// span.prototype.toMarkup and symbolNode.prototype.toNode...
|
// span.prototype.toMarkup and symbolNode.prototype.toNode...
|
||||||
|
|
File diff suppressed because one or more lines are too long
15
katex.js
15
katex.js
|
@ -1,9 +1,21 @@
|
||||||
|
/**
|
||||||
|
* This is the main entry point for KaTeX. Here, we expose functions for
|
||||||
|
* rendering expressions either to DOM nodes or to markup strings.
|
||||||
|
*
|
||||||
|
* We also expose the ParseError class to check if errors thrown from KaTeX are
|
||||||
|
* errors in the expression, or errors in javascript handling.
|
||||||
|
*/
|
||||||
|
|
||||||
var ParseError = require("./ParseError");
|
var ParseError = require("./ParseError");
|
||||||
|
|
||||||
var buildTree = require("./buildTree");
|
var buildTree = require("./buildTree");
|
||||||
var parseTree = require("./parseTree");
|
var parseTree = require("./parseTree");
|
||||||
var utils = require("./utils");
|
var utils = require("./utils");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse and build an expression, and place that expression in the DOM node
|
||||||
|
* given.
|
||||||
|
*/
|
||||||
var process = function(toParse, baseNode) {
|
var process = function(toParse, baseNode) {
|
||||||
utils.clearNode(baseNode);
|
utils.clearNode(baseNode);
|
||||||
|
|
||||||
|
@ -13,6 +25,9 @@ var process = function(toParse, baseNode) {
|
||||||
baseNode.appendChild(node);
|
baseNode.appendChild(node);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse and build an expression, and return the markup for that.
|
||||||
|
*/
|
||||||
var renderToString = function(toParse) {
|
var renderToString = function(toParse) {
|
||||||
var tree = parseTree(toParse);
|
var tree = parseTree(toParse);
|
||||||
return buildTree(tree).toMarkup();
|
return buildTree(tree).toMarkup();
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
|
/**
|
||||||
|
* Provides a single function for parsing an expression using a Parser
|
||||||
|
* TODO(emily): Remove this
|
||||||
|
*/
|
||||||
|
|
||||||
var Parser = require("./Parser");
|
var Parser = require("./Parser");
|
||||||
|
|
||||||
// Parses the expression using the parser
|
/**
|
||||||
|
* Parses an expression using a Parser, then returns the parsed result.
|
||||||
|
*/
|
||||||
var parseTree = function(toParse) {
|
var parseTree = function(toParse) {
|
||||||
var parser = new Parser(toParse);
|
var parser = new Parser(toParse);
|
||||||
|
|
||||||
|
|
28
symbols.js
28
symbols.js
|
@ -1,15 +1,18 @@
|
||||||
/* This file holds a list of all no-argument functions and single-character
|
/**
|
||||||
* symbols (like 'a' or ';'). For each of the symbols, there are three
|
* This file holds a list of all no-argument functions and single-character
|
||||||
* properties they can have:
|
* symbols (like 'a' or ';').
|
||||||
* - font (required): the font to be used for this * symbol. Either "main" (the
|
*
|
||||||
normal font), or "ams" (the ams fonts)
|
* For each of the symbols, there are three properties they can have:
|
||||||
|
* - font (required): the font to be used for this symbol. Either "main" (the
|
||||||
|
normal font), or "ams" (the ams fonts).
|
||||||
* - group (required): the ParseNode group type the symbol should have (i.e.
|
* - group (required): the ParseNode group type the symbol should have (i.e.
|
||||||
"textord" or "mathord" or
|
"textord", "mathord", etc).
|
||||||
* - replace (optiona): the character that this symbol or function should be
|
* - replace (optional): the character that this symbol or function should be
|
||||||
* replaced with (i.e. "\phi" has a replace value of "\u03d5", the phi
|
* replaced with (i.e. "\phi" has a replace value of "\u03d5", the phi
|
||||||
* character in the main font)
|
* character in the main font).
|
||||||
* There outermost map in the table indicates what mode the symbols should be
|
*
|
||||||
* accepted in (e.g. "math" or "text")
|
* The outermost map in the table indicates what mode the symbols should be
|
||||||
|
* accepted in (e.g. "math" or "text").
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var symbols = {
|
var symbols = {
|
||||||
|
@ -854,6 +857,9 @@ var symbols = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// There are lots of symbols which are the same, so we add them in afterwards.
|
||||||
|
|
||||||
|
// All of these are textords in math mode
|
||||||
var mathTextSymbols = "0123456789/@.\"";
|
var mathTextSymbols = "0123456789/@.\"";
|
||||||
for (var i = 0; i < mathTextSymbols.length; i++) {
|
for (var i = 0; i < mathTextSymbols.length; i++) {
|
||||||
var ch = mathTextSymbols.charAt(i);
|
var ch = mathTextSymbols.charAt(i);
|
||||||
|
@ -863,6 +869,7 @@ for (var i = 0; i < mathTextSymbols.length; i++) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All of these are textords in text mode
|
||||||
var textSymbols = "0123456789`!@*()-=+[]'\";:?/.,";
|
var textSymbols = "0123456789`!@*()-=+[]'\";:?/.,";
|
||||||
for (var i = 0; i < textSymbols.length; i++) {
|
for (var i = 0; i < textSymbols.length; i++) {
|
||||||
var ch = textSymbols.charAt(i);
|
var ch = textSymbols.charAt(i);
|
||||||
|
@ -872,6 +879,7 @@ for (var i = 0; i < textSymbols.length; i++) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All of these are textords in text mode, and mathords in math mode
|
||||||
var letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
var letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
for (var i = 0; i < letters.length; i++) {
|
for (var i = 0; i < letters.length; i++) {
|
||||||
var ch = letters.charAt(i);
|
var ch = letters.charAt(i);
|
||||||
|
|
20
utils.js
20
utils.js
|
@ -1,3 +1,12 @@
|
||||||
|
/**
|
||||||
|
* This file contains a list of utility functions which are useful in other
|
||||||
|
* files.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide an `indexOf` function which works in IE8, but defers to native if
|
||||||
|
* possible.
|
||||||
|
*/
|
||||||
var nativeIndexOf = Array.prototype.indexOf;
|
var nativeIndexOf = Array.prototype.indexOf;
|
||||||
var indexOf = function(list, elem) {
|
var indexOf = function(list, elem) {
|
||||||
if (list == null) {
|
if (list == null) {
|
||||||
|
@ -15,6 +24,9 @@ var indexOf = function(list, elem) {
|
||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether an element is contained in a list
|
||||||
|
*/
|
||||||
var contains = function(list, elem) {
|
var contains = function(list, elem) {
|
||||||
return indexOf(list, elem) !== -1;
|
return indexOf(list, elem) !== -1;
|
||||||
};
|
};
|
||||||
|
@ -50,8 +62,11 @@ function escape(text) {
|
||||||
return ('' + text).replace(ESCAPE_REGEX, escaper);
|
return ('' + text).replace(ESCAPE_REGEX, escaper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function to set the text content of a DOM element in all supported
|
||||||
|
* browsers. Note that we don't define this if there is no document.
|
||||||
|
*/
|
||||||
var setTextContent;
|
var setTextContent;
|
||||||
|
|
||||||
if (typeof document !== "undefined") {
|
if (typeof document !== "undefined") {
|
||||||
var testNode = document.createElement("span");
|
var testNode = document.createElement("span");
|
||||||
if ("textContent" in testNode) {
|
if ("textContent" in testNode) {
|
||||||
|
@ -65,6 +80,9 @@ if (typeof document !== "undefined") {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function to clear a node.
|
||||||
|
*/
|
||||||
function clearNode(node) {
|
function clearNode(node) {
|
||||||
setTextContent(node, "");
|
setTextContent(node, "");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user