Upgrade the source to use ES6 syntax including classes, import and static properties (#679)

* Add babel transform-class-properties to have static class properties

* Upgrade Lexer and Parser files to use ES6 classes

* Update eslint max line length to 90 character (more indent because of using ES6 classes)

* Upgrade eslint and jasmin to support ES stage-2 features

* Use static properties to place constants near their functions

* Migrate all remaining sources to ES6 syntax

* Increase eslint max line length to 84

* Remove non-babelified endpoint in dev server.js

* Clean up server.js functions after removing browserified

* Make screenshotter not to use babel endpoint as we babelify everything now
This commit is contained in:
Hossein Saniei
2017-07-03 16:39:21 +04:30
committed by Kevin Barabash
parent 0edd3d1bbb
commit a019f36f8a
28 changed files with 1766 additions and 1759 deletions

View File

@@ -1,6 +1,7 @@
{
"presets": ["es2015"],
"plugins": [
"transform-runtime"
"transform-runtime",
"transform-class-properties"
]
}

View File

@@ -1,4 +1,5 @@
{
"parser": "babel-eslint",
"rules": {
"arrow-spacing": 2,
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
@@ -14,7 +15,7 @@
"indent": [2, 4, {"SwitchCase": 1}],
"keyword-spacing": 2,
"linebreak-style": [2, "unix"],
"max-len": [2, 80, 4, { "ignoreUrls": true, "ignorePattern": "\\brequire\\([\"']|eslint-disable" }],
"max-len": [2, 84, 4, { "ignoreUrls": true, "ignorePattern": "\\brequire\\([\"']|eslint-disable" }],
"no-alert": 2,
"no-array-constructor": 2,
"no-console": 2,

View File

@@ -291,7 +291,7 @@ function findHostIP() {
}
if (katexIP !== "*any*" || katexURL) {
if (!katexURL) {
katexURL = "http://" + katexIP + ":" + katexPort + "/babel/";
katexURL = "http://" + katexIP + ":" + katexPort + "/";
console.log("KaTeX URL is " + katexURL);
}
process.nextTick(takeScreenshots);
@@ -303,7 +303,7 @@ function findHostIP() {
app.get("/ss-connect.js", function(req, res, next) {
if (!katexURL) {
katexIP = req.query.ip;
katexURL = "http://" + katexIP + ":" + katexPort + "/babel/";
katexURL = "http://" + katexIP + ":" + katexPort + "/";
console.log("KaTeX URL is " + katexURL);
process.nextTick(takeScreenshots);
}

View File

@@ -7,12 +7,12 @@
* errors in the expression, or errors in javascript handling.
*/
const ParseError = require("./src/ParseError");
const Settings = require("./src/Settings");
import ParseError from "./src/ParseError";
import Settings from "./src/Settings";
const buildTree = require("./src/buildTree");
const parseTree = require("./src/parseTree");
const utils = require("./src/utils");
import buildTree from "./src/buildTree";
import parseTree from "./src/parseTree";
import utils from "./src/utils";
/**
* Parse and build an expression, and place that expression in the DOM node

View File

@@ -15,8 +15,11 @@
],
"license": "MIT",
"devDependencies": {
"babel-eslint": "^7.2.0",
"babel-plugin-transform-class-properties": "^6.23.0",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-preset-es2015": "^6.18.0",
"babel-register": "^6.24.0",
"babelify": "^7.3.0",
"browserify": "^13.3.0",
"clean-css": "^3.4.23",

View File

@@ -15,7 +15,7 @@ if (require.main === module) {
":date[iso] :method :url HTTP/:http-version - :status"));
}
function serveBrowserified(file, standaloneName, doBabelify) {
function serveBrowserified(file, standaloneName) {
return function(req, res, next) {
let files;
if (Array.isArray(file)) {
@@ -26,10 +26,9 @@ function serveBrowserified(file, standaloneName, doBabelify) {
files = [path.join(__dirname, file)];
}
const options = {};
if (doBabelify) {
options.transform = [babelify];
}
const options = {
transform: [babelify],
};
if (standaloneName) {
options.standalone = standaloneName;
}
@@ -46,30 +45,24 @@ function serveBrowserified(file, standaloneName, doBabelify) {
};
}
function twoBrowserified(url, file, standaloneName) {
app.get(url, serveBrowserified(file, standaloneName, false));
app.get("/babel" + url, serveBrowserified(file, standaloneName, true));
function browserified(url, file, standaloneName) {
app.get(url, serveBrowserified(file, standaloneName));
}
function twoUse(url, handler) {
app.use(url, handler);
app.use("/babel" + url, handler);
function getStatic(url, file) {
app.use(url, express.static(path.join(__dirname, file)));
}
function twoStatic(url, file) {
twoUse(url, express.static(path.join(__dirname, file)));
}
twoBrowserified("/katex.js", "katex", "katex");
twoUse("/test/jasmine", express.static(path.dirname(
browserified("/katex.js", "katex", "katex");
app.use("/test/jasmine", express.static(path.dirname(
require.resolve("jasmine-core/lib/jasmine-core/jasmine.js"))));
twoBrowserified("/test/katex-spec.js", "test/*[Ss]pec.js");
twoBrowserified(
browserified("/test/katex-spec.js", "test/*[Ss]pec.js");
browserified(
"/contrib/auto-render/auto-render.js",
"contrib/auto-render/auto-render",
"renderMathInElement");
twoUse("/katex.css", function(req, res, next) {
app.use("/katex.css", function(req, res, next) {
const lessfile = path.join(__dirname, "static", "katex.less");
fs.readFile(lessfile, {encoding: "utf8"}, function(err, data) {
if (err) {
@@ -93,10 +86,10 @@ twoUse("/katex.css", function(req, res, next) {
});
});
twoStatic("", "static");
twoStatic("", "build");
twoStatic("/test", "test");
twoStatic("/contrib", "contrib");
getStatic("", "static");
getStatic("", "build");
getStatic("/test", "test");
getStatic("/contrib", "contrib");
app.use(function(err, req, res, next) {
console.error(err.stack);

View File

@@ -11,15 +11,8 @@
* kinds.
*/
const matchAt = require("match-at");
const ParseError = require("./ParseError");
// The main lexer class
function Lexer(input) {
this.input = input;
this.pos = 0;
}
import matchAt from "match-at";
import ParseError from "./ParseError";
/**
* The resulting token returned from `lex`.
@@ -40,26 +33,28 @@ function Lexer(input) {
* @param {number=} end the end offset, zero-based exclusive
* @param {Lexer=} lexer the lexer which in turn holds the input string
*/
function Token(text, start, end, lexer) {
this.text = text;
this.start = start;
this.end = end;
this.lexer = lexer;
}
/**
* Given a pair of tokens (this and endToken), compute a “Token” encompassing
* the whole input range enclosed by these two.
*
* @param {Token} endToken last token of the range, inclusive
* @param {string} text the text of the newly constructed token
*/
Token.prototype.range = function(endToken, text) {
if (endToken.lexer !== this.lexer) {
return new Token(text); // sorry, no position information available
class Token {
constructor(text, start, end, lexer) {
this.text = text;
this.start = start;
this.end = end;
this.lexer = lexer;
}
return new Token(text, this.start, endToken.end, this.lexer);
};
/**
* Given a pair of tokens (this and endToken), compute a “Token” encompassing
* the whole input range enclosed by these two.
*
* @param {Token} endToken last token of the range, inclusive
* @param {string} text the text of the newly constructed token
*/
range(endToken, text) {
if (endToken.lexer !== this.lexer) {
return new Token(text); // sorry, no position information available
}
return new Token(text, this.start, endToken.end, this.lexer);
}
}
/* The following tokenRegex
* - matches typical whitespace (but not NBSP etc.) using its first group
@@ -84,26 +79,36 @@ const tokenRegex = new RegExp(
")"
);
/**
* This function lexes a single token.
/*
* Main Lexer class
*/
Lexer.prototype.lex = function() {
const input = this.input;
const pos = this.pos;
if (pos === input.length) {
return new Token("EOF", pos, pos, this);
class Lexer {
constructor(input) {
this.input = input;
this.pos = 0;
}
const match = matchAt(tokenRegex, input, pos);
if (match === null) {
throw new ParseError(
"Unexpected character: '" + input[pos] + "'",
new Token(input[pos], pos, pos + 1, this));
/**
* This function lexes a single token.
*/
lex() {
const input = this.input;
const pos = this.pos;
if (pos === input.length) {
return new Token("EOF", pos, pos, this);
}
const match = matchAt(tokenRegex, input, pos);
if (match === null) {
throw new ParseError(
"Unexpected character: '" + input[pos] + "'",
new Token(input[pos], pos, pos + 1, this));
}
const text = match[2] || " ";
const start = this.pos;
this.pos += match[0].length;
const end = this.pos;
return new Token(text, start, end, this);
}
const text = match[2] || " ";
const start = this.pos;
this.pos += match[0].length;
const end = this.pos;
return new Token(text, start, end, this);
};
}
module.exports = Lexer;

View File

@@ -3,144 +3,146 @@
* until only non-macro tokens remain.
*/
const Lexer = require("./Lexer");
const builtinMacros = require("./macros");
const ParseError = require("./ParseError");
const objectAssign = require("object-assign");
import Lexer from "./Lexer";
import builtinMacros from "./macros";
import ParseError from "./ParseError";
import objectAssign from "object-assign";
function MacroExpander(input, macros) {
this.lexer = new Lexer(input);
this.macros = objectAssign({}, builtinMacros, macros);
this.stack = []; // contains tokens in REVERSE order
this.discardedWhiteSpace = [];
}
class MacroExpander {
constructor(input, macros) {
this.lexer = new Lexer(input);
this.macros = objectAssign({}, builtinMacros, macros);
this.stack = []; // contains tokens in REVERSE order
this.discardedWhiteSpace = [];
}
/**
* Recursively expand first token, then return first non-expandable token.
*
* At the moment, macro expansion doesn't handle delimited macros,
* i.e. things like those defined by \def\foo#1\end{…}.
* See the TeX book page 202ff. for details on how those should behave.
*/
MacroExpander.prototype.nextToken = function() {
for (;;) {
if (this.stack.length === 0) {
this.stack.push(this.lexer.lex());
}
const topToken = this.stack.pop();
const name = topToken.text;
if (!(name.charAt(0) === "\\" && this.macros.hasOwnProperty(name))) {
return topToken;
}
let tok;
let expansion = this.macros[name];
if (typeof expansion === "string") {
let numArgs = 0;
if (expansion.indexOf("#") !== -1) {
const stripped = expansion.replace(/##/g, "");
while (stripped.indexOf("#" + (numArgs + 1)) !== -1) {
++numArgs;
/**
* Recursively expand first token, then return first non-expandable token.
*
* At the moment, macro expansion doesn't handle delimited macros,
* i.e. things like those defined by \def\foo#1\end{…}.
* See the TeX book page 202ff. for details on how those should behave.
*/
nextToken() {
for (;;) {
if (this.stack.length === 0) {
this.stack.push(this.lexer.lex());
}
const topToken = this.stack.pop();
const name = topToken.text;
if (!(name.charAt(0) === "\\" && this.macros.hasOwnProperty(name))) {
return topToken;
}
let tok;
let expansion = this.macros[name];
if (typeof expansion === "string") {
let numArgs = 0;
if (expansion.indexOf("#") !== -1) {
const stripped = expansion.replace(/##/g, "");
while (stripped.indexOf("#" + (numArgs + 1)) !== -1) {
++numArgs;
}
}
}
const bodyLexer = new Lexer(expansion);
expansion = [];
tok = bodyLexer.lex();
while (tok.text !== "EOF") {
expansion.push(tok);
const bodyLexer = new Lexer(expansion);
expansion = [];
tok = bodyLexer.lex();
while (tok.text !== "EOF") {
expansion.push(tok);
tok = bodyLexer.lex();
}
expansion.reverse(); // to fit in with stack using push and pop
expansion.numArgs = numArgs;
this.macros[name] = expansion;
}
expansion.reverse(); // to fit in with stack using push and pop
expansion.numArgs = numArgs;
this.macros[name] = expansion;
}
if (expansion.numArgs) {
const args = [];
let i;
// obtain arguments, either single token or balanced {…} group
for (i = 0; i < expansion.numArgs; ++i) {
const startOfArg = this.get(true);
if (startOfArg.text === "{") {
const arg = [];
let depth = 1;
while (depth !== 0) {
tok = this.get(false);
arg.push(tok);
if (tok.text === "{") {
++depth;
} else if (tok.text === "}") {
--depth;
} else if (tok.text === "EOF") {
if (expansion.numArgs) {
const args = [];
let i;
// obtain arguments, either single token or balanced {…} group
for (i = 0; i < expansion.numArgs; ++i) {
const startOfArg = this.get(true);
if (startOfArg.text === "{") {
const arg = [];
let depth = 1;
while (depth !== 0) {
tok = this.get(false);
arg.push(tok);
if (tok.text === "{") {
++depth;
} else if (tok.text === "}") {
--depth;
} else if (tok.text === "EOF") {
throw new ParseError(
"End of input in macro argument",
startOfArg);
}
}
arg.pop(); // remove last }
arg.reverse(); // like above, to fit in with stack order
args[i] = arg;
} else if (startOfArg.text === "EOF") {
throw new ParseError(
"End of input expecting macro argument", topToken);
} else {
args[i] = [startOfArg];
}
}
// paste arguments in place of the placeholders
expansion = expansion.slice(); // make a shallow copy
for (i = expansion.length - 1; i >= 0; --i) {
tok = expansion[i];
if (tok.text === "#") {
if (i === 0) {
throw new ParseError(
"End of input in macro argument",
startOfArg);
"Incomplete placeholder at end of macro body",
tok);
}
tok = expansion[--i]; // next token on stack
if (tok.text === "#") { // ## → #
expansion.splice(i + 1, 1); // drop first #
} else if (/^[1-9]$/.test(tok.text)) {
// expansion.splice(i, 2, arg[0], arg[1], …)
// to replace placeholder with the indicated argument.
// TODO: use spread once we move to ES2015
expansion.splice.apply(
expansion,
[i, 2].concat(args[tok.text - 1]));
} else {
throw new ParseError(
"Not a valid argument number",
tok);
}
}
arg.pop(); // remove last }
arg.reverse(); // like above, to fit in with stack order
args[i] = arg;
} else if (startOfArg.text === "EOF") {
throw new ParseError(
"End of input expecting macro argument", topToken);
} else {
args[i] = [startOfArg];
}
}
// paste arguments in place of the placeholders
expansion = expansion.slice(); // make a shallow copy
for (i = expansion.length - 1; i >= 0; --i) {
tok = expansion[i];
if (tok.text === "#") {
if (i === 0) {
throw new ParseError(
"Incomplete placeholder at end of macro body",
tok);
}
tok = expansion[--i]; // next token on stack
if (tok.text === "#") { // ## → #
expansion.splice(i + 1, 1); // drop first #
} else if (/^[1-9]$/.test(tok.text)) {
// expansion.splice(i, 2, arg[0], arg[1], …)
// to replace placeholder with the indicated argument.
// TODO: use spread once we move to ES2015
expansion.splice.apply(
expansion,
[i, 2].concat(args[tok.text - 1]));
} else {
throw new ParseError(
"Not a valid argument number",
tok);
}
}
this.stack = this.stack.concat(expansion);
}
}
get(ignoreSpace) {
this.discardedWhiteSpace = [];
let token = this.nextToken();
if (ignoreSpace) {
while (token.text === " ") {
this.discardedWhiteSpace.push(token);
token = this.nextToken();
}
}
this.stack = this.stack.concat(expansion);
return token;
}
};
MacroExpander.prototype.get = function(ignoreSpace) {
this.discardedWhiteSpace = [];
let token = this.nextToken();
if (ignoreSpace) {
while (token.text === " ") {
this.discardedWhiteSpace.push(token);
token = this.nextToken();
/**
* Undo the effect of the preceding call to the get method.
* A call to this method MUST be immediately preceded and immediately followed
* by a call to get. Only used during mode switching, i.e. after one token
* was got in the old mode but should get got again in a new mode
* with possibly different whitespace handling.
*/
unget(token) {
this.stack.push(token);
while (this.discardedWhiteSpace.length !== 0) {
this.stack.push(this.discardedWhiteSpace.pop());
}
}
return token;
};
/**
* Undo the effect of the preceding call to the get method.
* A call to this method MUST be immediately preceded and immediately followed
* by a call to get. Only used during mode switching, i.e. after one token
* was got in the old mode but should get got again in a new mode
* with possibly different whitespace handling.
*/
MacroExpander.prototype.unget = function(token) {
this.stack.push(token);
while (this.discardedWhiteSpace.length !== 0) {
this.stack.push(this.discardedWhiteSpace.pop());
}
};
}
module.exports = MacroExpander;

View File

@@ -5,7 +5,7 @@
* `.reset` functions.
*/
const fontMetrics = require("./fontMetrics");
import fontMetrics from "./fontMetrics";
const BASESIZE = 6;
@@ -31,6 +31,10 @@ const sizeMultipliers = [
0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.2, 1.44, 1.728, 2.074, 2.488,
];
const sizeAtStyle = function(size, style) {
return style.size < 2 ? size : sizeStyleMap[size - 1][style.size - 1];
};
/**
* This is the main options class. It contains the current style, size, color,
* and font.
@@ -38,238 +42,236 @@ const sizeMultipliers = [
* Options objects should not be modified. To create a new Options with
* different properties, call a `.having*` method.
*/
function Options(data) {
this.style = data.style;
this.color = data.color;
this.size = data.size || BASESIZE;
this.textSize = data.textSize || this.size;
this.phantom = data.phantom;
this.font = data.font;
this.sizeMultiplier = sizeMultipliers[this.size - 1];
this._fontMetrics = null;
}
class Options {
constructor(data) {
this.style = data.style;
this.color = data.color;
this.size = data.size || BASESIZE;
this.textSize = data.textSize || this.size;
this.phantom = data.phantom;
this.font = data.font;
this.sizeMultiplier = sizeMultipliers[this.size - 1];
this._fontMetrics = null;
}
/**
* Returns a new options object with the same properties as "this". Properties
* from "extension" will be copied to the new options object.
*/
Options.prototype.extend = function(extension) {
const data = {
style: this.style,
size: this.size,
textSize: this.textSize,
color: this.color,
phantom: this.phantom,
font: this.font,
};
/**
* Returns a new options object with the same properties as "this". Properties
* from "extension" will be copied to the new options object.
*/
extend(extension) {
const data = {
style: this.style,
size: this.size,
textSize: this.textSize,
color: this.color,
phantom: this.phantom,
font: this.font,
};
for (const key in extension) {
if (extension.hasOwnProperty(key)) {
data[key] = extension[key];
for (const key in extension) {
if (extension.hasOwnProperty(key)) {
data[key] = extension[key];
}
}
return new Options(data);
}
/**
* Return an options object with the given style. If `this.style === style`,
* returns `this`.
*/
havingStyle(style) {
if (this.style === style) {
return this;
} else {
return this.extend({
style: style,
size: sizeAtStyle(this.textSize, style),
});
}
}
return new Options(data);
};
/**
* Return an options object with a cramped version of the current style. If
* the current style is cramped, returns `this`.
*/
havingCrampedStyle() {
return this.havingStyle(this.style.cramp());
}
function sizeAtStyle(size, style) {
return style.size < 2 ? size : sizeStyleMap[size - 1][style.size - 1];
/**
* Return an options object with the given size and in at least `\textstyle`.
* Returns `this` if appropriate.
*/
havingSize(size) {
if (this.size === size && this.textSize === size) {
return this;
} else {
return this.extend({
style: this.style.text(),
size: size,
textSize: size,
});
}
}
/**
* Like `this.havingSize(BASESIZE).havingStyle(style)`. If `style` is omitted,
* changes to at least `\textstyle`.
*/
havingBaseStyle(style) {
style = style || this.style.text();
const wantSize = sizeAtStyle(BASESIZE, style);
if (this.size === wantSize && this.textSize === BASESIZE
&& this.style === style) {
return this;
} else {
return this.extend({
style: style,
size: wantSize,
baseSize: BASESIZE,
});
}
}
/**
* Create a new options object with the given color.
*/
withColor(color) {
return this.extend({
color: color,
});
}
/**
* Create a new options object with "phantom" set to true.
*/
withPhantom() {
return this.extend({
phantom: true,
});
}
/**
* Create a new options objects with the give font.
*/
withFont(font) {
return this.extend({
font: font || this.font,
});
}
/**
* Return the CSS sizing classes required to switch from enclosing options
* `oldOptions` to `this`. Returns an array of classes.
*/
sizingClasses(oldOptions) {
if (oldOptions.size !== this.size) {
return ["sizing", "reset-size" + oldOptions.size, "size" + this.size];
} else {
return [];
}
}
/**
* Return the CSS sizing classes required to switch to the base size. Like
* `this.havingSize(BASESIZE).sizingClasses(this)`.
*/
baseSizingClasses() {
if (this.size !== BASESIZE) {
return ["sizing", "reset-size" + this.size, "size" + BASESIZE];
} else {
return [];
}
}
/**
* Return the font metrics for this size.
*/
fontMetrics() {
if (!this._fontMetrics) {
this._fontMetrics = fontMetrics.getFontMetrics(this.size);
}
return this._fontMetrics;
}
/**
* A map of color names to CSS colors.
* TODO(emily): Remove this when we have real macros
*/
static colorMap = {
"katex-blue": "#6495ed",
"katex-orange": "#ffa500",
"katex-pink": "#ff00af",
"katex-red": "#df0030",
"katex-green": "#28ae7b",
"katex-gray": "gray",
"katex-purple": "#9d38bd",
"katex-blueA": "#ccfaff",
"katex-blueB": "#80f6ff",
"katex-blueC": "#63d9ea",
"katex-blueD": "#11accd",
"katex-blueE": "#0c7f99",
"katex-tealA": "#94fff5",
"katex-tealB": "#26edd5",
"katex-tealC": "#01d1c1",
"katex-tealD": "#01a995",
"katex-tealE": "#208170",
"katex-greenA": "#b6ffb0",
"katex-greenB": "#8af281",
"katex-greenC": "#74cf70",
"katex-greenD": "#1fab54",
"katex-greenE": "#0d923f",
"katex-goldA": "#ffd0a9",
"katex-goldB": "#ffbb71",
"katex-goldC": "#ff9c39",
"katex-goldD": "#e07d10",
"katex-goldE": "#a75a05",
"katex-redA": "#fca9a9",
"katex-redB": "#ff8482",
"katex-redC": "#f9685d",
"katex-redD": "#e84d39",
"katex-redE": "#bc2612",
"katex-maroonA": "#ffbde0",
"katex-maroonB": "#ff92c6",
"katex-maroonC": "#ed5fa6",
"katex-maroonD": "#ca337c",
"katex-maroonE": "#9e034e",
"katex-purpleA": "#ddd7ff",
"katex-purpleB": "#c6b9fc",
"katex-purpleC": "#aa87ff",
"katex-purpleD": "#7854ab",
"katex-purpleE": "#543b78",
"katex-mintA": "#f5f9e8",
"katex-mintB": "#edf2df",
"katex-mintC": "#e0e5cc",
"katex-grayA": "#f6f7f7",
"katex-grayB": "#f0f1f2",
"katex-grayC": "#e3e5e6",
"katex-grayD": "#d6d8da",
"katex-grayE": "#babec2",
"katex-grayF": "#888d93",
"katex-grayG": "#626569",
"katex-grayH": "#3b3e40",
"katex-grayI": "#21242c",
"katex-kaBlue": "#314453",
"katex-kaGreen": "#71B307",
};
/**
* Gets the CSS color of the current options object, accounting for the
* `colorMap`.
*/
getColor() {
if (this.phantom) {
return "transparent";
} else {
return Options.colorMap[this.color] || this.color;
}
}
}
/**
* Return an options object with the given style. If `this.style === style`,
* returns `this`.
*/
Options.prototype.havingStyle = function(style) {
if (this.style === style) {
return this;
} else {
return this.extend({
style: style,
size: sizeAtStyle(this.textSize, style),
});
}
};
/**
* Return an options object with a cramped version of the current style. If
* the current style is cramped, returns `this`.
*/
Options.prototype.havingCrampedStyle = function() {
return this.havingStyle(this.style.cramp());
};
/**
* Return an options object with the given size and in at least `\textstyle`.
* Returns `this` if appropriate.
*/
Options.prototype.havingSize = function(size) {
if (this.size === size && this.textSize === size) {
return this;
} else {
return this.extend({
style: this.style.text(),
size: size,
textSize: size,
});
}
};
/**
* Like `this.havingSize(BASESIZE).havingStyle(style)`. If `style` is omitted,
* changes to at least `\textstyle`.
*/
Options.prototype.havingBaseStyle = function(style) {
style = style || this.style.text();
const wantSize = sizeAtStyle(BASESIZE, style);
if (this.size === wantSize && this.textSize === BASESIZE
&& this.style === style) {
return this;
} else {
return this.extend({
style: style,
size: wantSize,
baseSize: BASESIZE,
});
}
};
/**
* Create a new options object with the given color.
*/
Options.prototype.withColor = function(color) {
return this.extend({
color: color,
});
};
/**
* Create a new options object with "phantom" set to true.
*/
Options.prototype.withPhantom = function() {
return this.extend({
phantom: true,
});
};
/**
* Create a new options objects with the give font.
*/
Options.prototype.withFont = function(font) {
return this.extend({
font: font || this.font,
});
};
/**
* Return the CSS sizing classes required to switch from enclosing options
* `oldOptions` to `this`. Returns an array of classes.
*/
Options.prototype.sizingClasses = function(oldOptions) {
if (oldOptions.size !== this.size) {
return ["sizing", "reset-size" + oldOptions.size, "size" + this.size];
} else {
return [];
}
};
/**
* Return the CSS sizing classes required to switch to the base size. Like
* `this.havingSize(BASESIZE).sizingClasses(this)`.
*/
Options.prototype.baseSizingClasses = function() {
if (this.size !== BASESIZE) {
return ["sizing", "reset-size" + this.size, "size" + BASESIZE];
} else {
return [];
}
};
/**
* Return the font metrics for this size.
*/
Options.prototype.fontMetrics = function() {
if (!this._fontMetrics) {
this._fontMetrics = fontMetrics.getFontMetrics(this.size);
}
return this._fontMetrics;
};
/**
* A map of color names to CSS colors.
* TODO(emily): Remove this when we have real macros
*/
const colorMap = {
"katex-blue": "#6495ed",
"katex-orange": "#ffa500",
"katex-pink": "#ff00af",
"katex-red": "#df0030",
"katex-green": "#28ae7b",
"katex-gray": "gray",
"katex-purple": "#9d38bd",
"katex-blueA": "#ccfaff",
"katex-blueB": "#80f6ff",
"katex-blueC": "#63d9ea",
"katex-blueD": "#11accd",
"katex-blueE": "#0c7f99",
"katex-tealA": "#94fff5",
"katex-tealB": "#26edd5",
"katex-tealC": "#01d1c1",
"katex-tealD": "#01a995",
"katex-tealE": "#208170",
"katex-greenA": "#b6ffb0",
"katex-greenB": "#8af281",
"katex-greenC": "#74cf70",
"katex-greenD": "#1fab54",
"katex-greenE": "#0d923f",
"katex-goldA": "#ffd0a9",
"katex-goldB": "#ffbb71",
"katex-goldC": "#ff9c39",
"katex-goldD": "#e07d10",
"katex-goldE": "#a75a05",
"katex-redA": "#fca9a9",
"katex-redB": "#ff8482",
"katex-redC": "#f9685d",
"katex-redD": "#e84d39",
"katex-redE": "#bc2612",
"katex-maroonA": "#ffbde0",
"katex-maroonB": "#ff92c6",
"katex-maroonC": "#ed5fa6",
"katex-maroonD": "#ca337c",
"katex-maroonE": "#9e034e",
"katex-purpleA": "#ddd7ff",
"katex-purpleB": "#c6b9fc",
"katex-purpleC": "#aa87ff",
"katex-purpleD": "#7854ab",
"katex-purpleE": "#543b78",
"katex-mintA": "#f5f9e8",
"katex-mintB": "#edf2df",
"katex-mintC": "#e0e5cc",
"katex-grayA": "#f6f7f7",
"katex-grayB": "#f0f1f2",
"katex-grayC": "#e3e5e6",
"katex-grayD": "#d6d8da",
"katex-grayE": "#babec2",
"katex-grayF": "#888d93",
"katex-grayG": "#626569",
"katex-grayH": "#3b3e40",
"katex-grayI": "#21242c",
"katex-kaBlue": "#314453",
"katex-kaGreen": "#71B307",
};
/**
* Gets the CSS color of the current options object, accounting for the
* `colorMap`.
*/
Options.prototype.getColor = function() {
if (this.phantom) {
return "transparent";
} else {
return colorMap[this.color] || this.color;
}
};
/**
* The base size index.
*/

View File

@@ -9,53 +9,55 @@
* @param {string} message The error message
* @param {(Token|ParseNode)=} token An object providing position information
*/
function ParseError(message, token) {
let error = "KaTeX parse error: " + message;
let start;
let end;
class ParseError {
constructor(message, token) {
let error = "KaTeX parse error: " + message;
let start;
let end;
if (token && token.lexer && token.start <= token.end) {
// If we have the input and a position, make the error a bit fancier
if (token && token.lexer && token.start <= token.end) {
// If we have the input and a position, make the error a bit fancier
// Get the input
const input = token.lexer.input;
// Get the input
const input = token.lexer.input;
// Prepend some information
start = token.start;
end = token.end;
if (start === input.length) {
error += " at end of input: ";
} else {
error += " at position " + (start + 1) + ": ";
// Prepend some information
start = token.start;
end = token.end;
if (start === input.length) {
error += " at end of input: ";
} else {
error += " at position " + (start + 1) + ": ";
}
// Underline token in question using combining underscores
const underlined = input.slice(start, end).replace(/[^]/g, "$&\u0332");
// Extract some context from the input and add it to the error
let left;
if (start > 15) {
left = "…" + input.slice(start - 15, start);
} else {
left = input.slice(0, start);
}
let right;
if (end + 15 < input.length) {
right = input.slice(end, end + 15) + "…";
} else {
right = input.slice(end);
}
error += left + underlined + right;
}
// Underline token in question using combining underscores
const underlined = input.slice(start, end).replace(/[^]/g, "$&\u0332");
// Some hackery to make ParseError a prototype of Error
// See http://stackoverflow.com/a/8460753
const self = new Error(error);
self.name = "ParseError";
self.__proto__ = ParseError.prototype;
// Extract some context from the input and add it to the error
let left;
if (start > 15) {
left = "…" + input.slice(start - 15, start);
} else {
left = input.slice(0, start);
}
let right;
if (end + 15 < input.length) {
right = input.slice(end, end + 15) + "…";
} else {
right = input.slice(end);
}
error += left + underlined + right;
self.position = start;
return self;
}
// Some hackery to make ParseError a prototype of Error
// See http://stackoverflow.com/a/8460753
const self = new Error(error);
self.name = "ParseError";
self.__proto__ = ParseError.prototype;
self.position = start;
return self;
}
// More hackery

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
* default settings.
*/
const utils = require("./utils");
import utils from "./utils";
/**
* The main Settings object
@@ -15,14 +15,16 @@ const utils = require("./utils");
* math (true), meaning that the math starts in \displaystyle
* and is placed in a block with vertical margin.
*/
function Settings(options) {
// allow null options
options = options || {};
this.displayMode = utils.deflt(options.displayMode, false);
this.throwOnError = utils.deflt(options.throwOnError, true);
this.errorColor = utils.deflt(options.errorColor, "#cc0000");
this.macros = options.macros || {};
this.colorIsTextColor = utils.deflt(options.colorIsTextColor, false);
class Settings {
constructor(options) {
// allow null options
options = options || {};
this.displayMode = utils.deflt(options.displayMode, false);
this.throwOnError = utils.deflt(options.throwOnError, true);
this.errorColor = utils.deflt(options.errorColor, "#cc0000");
this.macros = options.macros || {};
this.colorIsTextColor = utils.deflt(options.colorIsTextColor, false);
}
}
module.exports = Settings;

View File

@@ -10,64 +10,66 @@
* 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), and a cramped flag.
*/
function Style(id, size, cramped) {
this.id = id;
this.size = size;
this.cramped = cramped;
class Style {
constructor(id, size, cramped) {
this.id = id;
this.size = size;
this.cramped = cramped;
}
/**
* Get the style of a superscript given a base in the current style.
*/
sup() {
return styles[sup[this.id]];
}
/**
* Get the style of a subscript given a base in the current style.
*/
sub() {
return styles[sub[this.id]];
}
/**
* Get the style of a fraction numerator given the fraction in the current
* style.
*/
fracNum() {
return styles[fracNum[this.id]];
}
/**
* Get the style of a fraction denominator given the fraction in the current
* style.
*/
fracDen() {
return styles[fracDen[this.id]];
}
/**
* Get the cramped version of a style (in particular, cramping a cramped style
* doesn't change the style).
*/
cramp() {
return styles[cramp[this.id]];
}
/**
* Get a text or display version of this style.
*/
text() {
return styles[text[this.id]];
}
/**
* Return if this style is tightly spaced (scriptstyle/scriptscriptstyle)
*/
isTight() {
return this.size >= 2;
}
}
/**
* Get the style of a superscript given a base in the current style.
*/
Style.prototype.sup = function() {
return styles[sup[this.id]];
};
/**
* Get the style of a subscript given a base in the current style.
*/
Style.prototype.sub = function() {
return styles[sub[this.id]];
};
/**
* Get the style of a fraction numerator given the fraction in the current
* style.
*/
Style.prototype.fracNum = function() {
return styles[fracNum[this.id]];
};
/**
* Get the style of a fraction denominator given the fraction in the current
* style.
*/
Style.prototype.fracDen = function() {
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() {
return styles[cramp[this.id]];
};
/**
* Get a text or display version of this style.
*/
Style.prototype.text = function() {
return styles[text[this.id]];
};
/**
* Return if this style is tightly spaced (scriptstyle/scriptscriptstyle)
*/
Style.prototype.isTight = function() {
return this.size >= 2;
};
// IDs of the different styles
const D = 0;
const Dc = 1;

View File

@@ -4,10 +4,10 @@
* different kinds of domTree nodes in a consistent manner.
*/
const domTree = require("./domTree");
const fontMetrics = require("./fontMetrics");
const symbols = require("./symbols");
const utils = require("./utils");
import domTree from "./domTree";
import fontMetrics from "./fontMetrics";
import symbols from "./symbols";
import utils from "./utils";
// The following have to be loaded from Main-Italic font, using class mainit
const mainitLetters = [

View File

@@ -6,16 +6,14 @@
* called, to produce a final HTML tree.
*/
const ParseError = require("./ParseError");
const Style = require("./Style");
import ParseError from "./ParseError";
import Style from "./Style";
const buildCommon = require("./buildCommon");
const delimiter = require("./delimiter");
const domTree = require("./domTree");
const utils = require("./utils");
const stretchy = require("./stretchy");
const makeSpan = buildCommon.makeSpan;
import buildCommon, { makeSpan } from "./buildCommon";
import delimiter from "./delimiter";
import domTree from "./domTree";
import utils from "./utils";
import stretchy from "./stretchy";
const isSpace = function(node) {
return node instanceof domTree.span && node.classes[0] === "mspace";
@@ -24,7 +22,6 @@ const isSpace = function(node) {
// Binary atoms (first class `mbin`) change into ordinary atoms (`mord`)
// depending on their surroundings. See TeXbook pg. 442-446, Rules 5 and 6,
// and the text before Rule 19.
const isBin = function(node) {
return node && node.classes[0] === "mbin";
};

View File

@@ -4,16 +4,13 @@
* parser.
*/
const buildCommon = require("./buildCommon");
const fontMetrics = require("./fontMetrics");
const mathMLTree = require("./mathMLTree");
const ParseError = require("./ParseError");
const symbols = require("./symbols");
const utils = require("./utils");
const stretchy = require("./stretchy");
const makeSpan = buildCommon.makeSpan;
const fontMap = buildCommon.fontMap;
import buildCommon, { makeSpan, fontMap } from "./buildCommon";
import fontMetrics from "./fontMetrics";
import mathMLTree from "./mathMLTree";
import ParseError from "./ParseError";
import symbols from "./symbols";
import utils from "./utils";
import stretchy from "./stretchy";
/**
* Takes a symbol and converts it into a MathML text node after performing

View File

@@ -1,11 +1,9 @@
const buildHTML = require("./buildHTML");
const buildMathML = require("./buildMathML");
const buildCommon = require("./buildCommon");
const Options = require("./Options");
const Settings = require("./Settings");
const Style = require("./Style");
const makeSpan = buildCommon.makeSpan;
import buildHTML from "./buildHTML";
import buildMathML from "./buildMathML";
import { makeSpan } from "./buildCommon";
import Options from "./Options";
import Settings from "./Settings";
import Style from "./Style";
const buildTree = function(tree, expression, settings) {
settings = settings || new Settings({});

View File

@@ -20,15 +20,13 @@
* used in `\left` and `\right`.
*/
const ParseError = require("./ParseError");
const Style = require("./Style");
import ParseError from "./ParseError";
import Style from "./Style";
const buildCommon = require("./buildCommon");
const fontMetrics = require("./fontMetrics");
const symbols = require("./symbols");
const utils = require("./utils");
const makeSpan = buildCommon.makeSpan;
import buildCommon, { makeSpan } from "./buildCommon";
import fontMetrics from "./fontMetrics";
import symbols from "./symbols";
import utils from "./utils";
/**
* Get the metrics for a given symbol and font, after transformation (i.e.

View File

@@ -7,8 +7,8 @@
*
* Similar functions for working with MathML nodes exist in mathMLTree.js.
*/
const unicodeRegexes = require("./unicodeRegexes");
const utils = require("./utils");
import unicodeRegexes from "./unicodeRegexes";
import utils from "./utils";
/**
* Create an HTML className based on a list of classes. In addition to joining
@@ -30,114 +30,116 @@ const createClass = function(classes) {
* an inline style. It also contains information about its height, depth, and
* maxFontSize.
*/
function span(classes, children, options) {
this.classes = classes || [];
this.children = children || [];
this.height = 0;
this.depth = 0;
this.maxFontSize = 0;
this.style = {};
this.attributes = {};
if (options) {
if (options.style.isTight()) {
this.classes.push("mtight");
}
if (options.getColor()) {
this.style.color = options.getColor();
}
}
}
/**
* Sets an arbitrary attribute on the span. Warning: use this wisely. Not all
* browsers support attributes the same, and having too many custom attributes
* is probably bad.
*/
span.prototype.setAttribute = function(attribute, value) {
this.attributes[attribute] = value;
};
span.prototype.tryCombine = function(sibling) {
return false;
};
/**
* Convert the span into an HTML node
*/
span.prototype.toNode = function() {
const span = document.createElement("span");
// Apply the class
span.className = createClass(this.classes);
// Apply inline styles
for (const style in this.style) {
if (Object.prototype.hasOwnProperty.call(this.style, style)) {
span.style[style] = this.style[style];
class span {
constructor(classes, children, options) {
this.classes = classes || [];
this.children = children || [];
this.height = 0;
this.depth = 0;
this.maxFontSize = 0;
this.style = {};
this.attributes = {};
if (options) {
if (options.style.isTight()) {
this.classes.push("mtight");
}
if (options.getColor()) {
this.style.color = options.getColor();
}
}
}
// Apply attributes
for (const attr in this.attributes) {
if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) {
span.setAttribute(attr, this.attributes[attr]);
/**
* Sets an arbitrary attribute on the span. Warning: use this wisely. Not all
* browsers support attributes the same, and having too many custom attributes
* is probably bad.
*/
setAttribute(attribute, value) {
this.attributes[attribute] = value;
}
tryCombine(sibling) {
return false;
}
/**
* Convert the span into an HTML node
*/
toNode() {
const span = document.createElement("span");
// Apply the class
span.className = createClass(this.classes);
// Apply inline styles
for (const style in this.style) {
if (Object.prototype.hasOwnProperty.call(this.style, style)) {
span.style[style] = this.style[style];
}
}
}
// Append the children, also as HTML nodes
for (let i = 0; i < this.children.length; i++) {
span.appendChild(this.children[i].toNode());
}
return span;
};
/**
* Convert the span into an HTML markup string
*/
span.prototype.toMarkup = function() {
let markup = "<span";
// Add the class
if (this.classes.length) {
markup += " class=\"";
markup += utils.escape(createClass(this.classes));
markup += "\"";
}
let styles = "";
// Add the styles, after hyphenation
for (const style in this.style) {
if (this.style.hasOwnProperty(style)) {
styles += utils.hyphenate(style) + ":" + this.style[style] + ";";
// Apply attributes
for (const attr in this.attributes) {
if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) {
span.setAttribute(attr, this.attributes[attr]);
}
}
// Append the children, also as HTML nodes
for (let i = 0; i < this.children.length; i++) {
span.appendChild(this.children[i].toNode());
}
return span;
}
if (styles) {
markup += " style=\"" + utils.escape(styles) + "\"";
}
/**
* Convert the span into an HTML markup string
*/
toMarkup() {
let markup = "<span";
// Add the attributes
for (const attr in this.attributes) {
if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) {
markup += " " + attr + "=\"";
markup += utils.escape(this.attributes[attr]);
// Add the class
if (this.classes.length) {
markup += " class=\"";
markup += utils.escape(createClass(this.classes));
markup += "\"";
}
let styles = "";
// Add the styles, after hyphenation
for (const style in this.style) {
if (this.style.hasOwnProperty(style)) {
styles += utils.hyphenate(style) + ":" + this.style[style] + ";";
}
}
if (styles) {
markup += " style=\"" + utils.escape(styles) + "\"";
}
// Add the attributes
for (const attr in this.attributes) {
if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) {
markup += " " + attr + "=\"";
markup += utils.escape(this.attributes[attr]);
markup += "\"";
}
}
markup += ">";
// Add the markup of the children, also as markup
for (let i = 0; i < this.children.length; i++) {
markup += this.children[i].toMarkup();
}
markup += "</span>";
return markup;
}
markup += ">";
// Add the markup of the children, also as markup
for (let i = 0; i < this.children.length; i++) {
markup += this.children[i].toMarkup();
}
markup += "</span>";
return markup;
};
}
/**
* This node represents a document fragment, which contains elements, but when
@@ -145,42 +147,44 @@ span.prototype.toMarkup = function() {
* contains children and doesn't have any HTML properties. It also keeps track
* of a height, depth, and maxFontSize.
*/
function documentFragment(children) {
this.children = children || [];
this.height = 0;
this.depth = 0;
this.maxFontSize = 0;
class documentFragment {
constructor(children) {
this.children = children || [];
this.height = 0;
this.depth = 0;
this.maxFontSize = 0;
}
/**
* Convert the fragment into a node
*/
toNode() {
// Create a fragment
const frag = document.createDocumentFragment();
// Append the children
for (let i = 0; i < this.children.length; i++) {
frag.appendChild(this.children[i].toNode());
}
return frag;
}
/**
* Convert the fragment into HTML markup
*/
toMarkup() {
let markup = "";
// Simply concatenate the markup for the children together
for (let i = 0; i < this.children.length; i++) {
markup += this.children[i].toMarkup();
}
return markup;
}
}
/**
* Convert the fragment into a node
*/
documentFragment.prototype.toNode = function() {
// Create a fragment
const frag = document.createDocumentFragment();
// Append the children
for (let i = 0; i < this.children.length; i++) {
frag.appendChild(this.children[i].toNode());
}
return frag;
};
/**
* Convert the fragment into HTML markup
*/
documentFragment.prototype.toMarkup = function() {
let markup = "";
// Simply concatenate the markup for the children together
for (let i = 0; i < this.children.length; i++) {
markup += this.children[i].toMarkup();
}
return markup;
};
const iCombinations = {
'î': '\u0131\u0302',
'ï': '\u0131\u0308',
@@ -194,141 +198,143 @@ const iCombinations = {
* 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) {
this.value = value || "";
this.height = height || 0;
this.depth = depth || 0;
this.italic = italic || 0;
this.skew = skew || 0;
this.classes = classes || [];
this.style = style || {};
this.maxFontSize = 0;
class symbolNode {
constructor(value, height, depth, italic, skew, classes, style) {
this.value = value || "";
this.height = height || 0;
this.depth = depth || 0;
this.italic = italic || 0;
this.skew = skew || 0;
this.classes = classes || [];
this.style = style || {};
this.maxFontSize = 0;
// Mark CJK characters with specific classes so that we can specify which
// fonts to use. This allows us to render these characters with a serif
// font in situations where the browser would either default to a sans serif
// or render a placeholder character.
if (unicodeRegexes.cjkRegex.test(value)) {
// I couldn't find any fonts that contained Hangul as well as all of
// the other characters we wanted to test there for it gets its own
// CSS class.
if (unicodeRegexes.hangulRegex.test(value)) {
this.classes.push('hangul_fallback');
} else {
this.classes.push('cjk_fallback');
// Mark CJK characters with specific classes so that we can specify which
// fonts to use. This allows us to render these characters with a serif
// font in situations where the browser would either default to a sans serif
// or render a placeholder character.
if (unicodeRegexes.cjkRegex.test(value)) {
// I couldn't find any fonts that contained Hangul as well as all of
// the other characters we wanted to test there for it gets its own
// CSS class.
if (unicodeRegexes.hangulRegex.test(value)) {
this.classes.push('hangul_fallback');
} else {
this.classes.push('cjk_fallback');
}
}
if (/[îïíì]/.test(this.value)) { // add ī when we add Extended Latin
this.value = iCombinations[this.value];
}
}
if (/[îïíì]/.test(this.value)) { // add ī when we add Extended Latin
this.value = iCombinations[this.value];
tryCombine(sibling) {
if (!sibling
|| !(sibling instanceof symbolNode)
|| this.italic > 0
|| createClass(this.classes) !== createClass(sibling.classes)
|| this.skew !== sibling.skew
|| this.maxFontSize !== sibling.maxFontSize) {
return false;
}
for (const style in this.style) {
if (this.style.hasOwnProperty(style)
&& this.style[style] !== sibling.style[style]) {
return false;
}
}
for (const style in sibling.style) {
if (sibling.style.hasOwnProperty(style)
&& this.style[style] !== sibling.style[style]) {
return false;
}
}
this.value += sibling.value;
this.height = Math.max(this.height, sibling.height);
this.depth = Math.max(this.depth, sibling.depth);
this.italic = sibling.italic;
return true;
}
/**
* Creates a text node or span from a symbol node. Note that a span is only
* created if it is needed.
*/
toNode() {
const node = document.createTextNode(this.value);
let span = null;
if (this.italic > 0) {
span = document.createElement("span");
span.style.marginRight = this.italic + "em";
}
if (this.classes.length > 0) {
span = span || document.createElement("span");
span.className = createClass(this.classes);
}
for (const style in this.style) {
if (this.style.hasOwnProperty(style)) {
span = span || document.createElement("span");
span.style[style] = this.style[style];
}
}
if (span) {
span.appendChild(node);
return span;
} else {
return node;
}
}
/**
* Creates markup for a symbol node.
*/
toMarkup() {
// TODO(alpert): More duplication than I'd like from
// span.prototype.toMarkup and symbolNode.prototype.toNode...
let needsSpan = false;
let markup = "<span";
if (this.classes.length) {
needsSpan = true;
markup += " class=\"";
markup += utils.escape(createClass(this.classes));
markup += "\"";
}
let styles = "";
if (this.italic > 0) {
styles += "margin-right:" + this.italic + "em;";
}
for (const style in this.style) {
if (this.style.hasOwnProperty(style)) {
styles += utils.hyphenate(style) + ":" + this.style[style] + ";";
}
}
if (styles) {
needsSpan = true;
markup += " style=\"" + utils.escape(styles) + "\"";
}
const escaped = utils.escape(this.value);
if (needsSpan) {
markup += ">";
markup += escaped;
markup += "</span>";
return markup;
} else {
return escaped;
}
}
}
symbolNode.prototype.tryCombine = function(sibling) {
if (!sibling
|| !(sibling instanceof symbolNode)
|| this.italic > 0
|| createClass(this.classes) !== createClass(sibling.classes)
|| this.skew !== sibling.skew
|| this.maxFontSize !== sibling.maxFontSize) {
return false;
}
for (const style in this.style) {
if (this.style.hasOwnProperty(style)
&& this.style[style] !== sibling.style[style]) {
return false;
}
}
for (const style in sibling.style) {
if (sibling.style.hasOwnProperty(style)
&& this.style[style] !== sibling.style[style]) {
return false;
}
}
this.value += sibling.value;
this.height = Math.max(this.height, sibling.height);
this.depth = Math.max(this.depth, sibling.depth);
this.italic = sibling.italic;
return true;
};
/**
* 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() {
const node = document.createTextNode(this.value);
let span = null;
if (this.italic > 0) {
span = document.createElement("span");
span.style.marginRight = this.italic + "em";
}
if (this.classes.length > 0) {
span = span || document.createElement("span");
span.className = createClass(this.classes);
}
for (const style in this.style) {
if (this.style.hasOwnProperty(style)) {
span = span || document.createElement("span");
span.style[style] = this.style[style];
}
}
if (span) {
span.appendChild(node);
return span;
} else {
return node;
}
};
/**
* Creates markup for a symbol node.
*/
symbolNode.prototype.toMarkup = function() {
// TODO(alpert): More duplication than I'd like from
// span.prototype.toMarkup and symbolNode.prototype.toNode...
let needsSpan = false;
let markup = "<span";
if (this.classes.length) {
needsSpan = true;
markup += " class=\"";
markup += utils.escape(createClass(this.classes));
markup += "\"";
}
let styles = "";
if (this.italic > 0) {
styles += "margin-right:" + this.italic + "em;";
}
for (const style in this.style) {
if (this.style.hasOwnProperty(style)) {
styles += utils.hyphenate(style) + ":" + this.style[style] + ";";
}
}
if (styles) {
needsSpan = true;
markup += " style=\"" + utils.escape(styles) + "\"";
}
const escaped = utils.escape(this.value);
if (needsSpan) {
markup += ">";
markup += escaped;
markup += "</span>";
return markup;
} else {
return escaped;
}
};
module.exports = {
span: span,
documentFragment: documentFragment,

View File

@@ -1,8 +1,6 @@
/* eslint no-constant-condition:0 */
const parseData = require("./parseData");
const ParseError = require("./ParseError");
const ParseNode = parseData.ParseNode;
import { ParseNode } from "./parseData";
import ParseError from "./ParseError";
/**
* Parse the body of the environment, with rows delimited by \\ and
@@ -67,7 +65,6 @@ function parseArray(parser, result, style) {
* - positions: the positions associated with these arguments from args.
* The handler must return a ParseResult.
*/
function defineEnvironment(names, props, handler) {
if (typeof names === "string") {
names = [names];

View File

@@ -1,4 +1,4 @@
const cjkRegex = require("./unicodeRegexes").cjkRegex;
import { cjkRegex } from "./unicodeRegexes";
/**
* This file contains metrics regarding fonts and individual symbols. The sigma
@@ -81,7 +81,7 @@ const sigmasAndXis = {
// metrics, including height, depth, italic correction, and skew (kern from the
// character to the corresponding \skewchar)
// This map is generated via `make metrics`. It should not be changed manually.
const metricMap = require("./fontMetricsData");
import metricMap from "./fontMetricsData";
// These are very rough approximations. We default to Times New Roman which
// should have Latin-1 and Cyrillic characters, but may not depending on the

View File

@@ -1,7 +1,6 @@
const utils = require("./utils");
const ParseError = require("./ParseError");
const parseData = require("./parseData");
const ParseNode = parseData.ParseNode;
import utils from "./utils";
import ParseError from "./ParseError";
import { ParseNode } from "./parseData";
/* This file contains a list of functions that we parse, identified by
* the calls to defineFunction.

View File

@@ -8,94 +8,98 @@
* domTree.js, creating namespaced DOM nodes and HTML text markup respectively.
*/
const utils = require("./utils");
import utils from "./utils";
/**
* This node represents a general purpose MathML node of any type. The
* constructor requires the type of node to create (for example, `"mo"` or
* `"mspace"`, corresponding to `<mo>` and `<mspace>` tags).
*/
function MathNode(type, children) {
this.type = type;
this.attributes = {};
this.children = children || [];
class MathNode {
constructor(type, children) {
this.type = type;
this.attributes = {};
this.children = children || [];
}
/**
* Sets an attribute on a MathML node. MathML depends on attributes to convey a
* semantic content, so this is used heavily.
*/
setAttribute(name, value) {
this.attributes[name] = value;
}
/**
* Converts the math node into a MathML-namespaced DOM element.
*/
toNode() {
const node = document.createElementNS(
"http://www.w3.org/1998/Math/MathML", this.type);
for (const attr in this.attributes) {
if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) {
node.setAttribute(attr, this.attributes[attr]);
}
}
for (let i = 0; i < this.children.length; i++) {
node.appendChild(this.children[i].toNode());
}
return node;
}
/**
* Converts the math node into an HTML markup string.
*/
toMarkup() {
let markup = "<" + this.type;
// Add the attributes
for (const attr in this.attributes) {
if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) {
markup += " " + attr + "=\"";
markup += utils.escape(this.attributes[attr]);
markup += "\"";
}
}
markup += ">";
for (let i = 0; i < this.children.length; i++) {
markup += this.children[i].toMarkup();
}
markup += "</" + this.type + ">";
return markup;
}
}
/**
* Sets an attribute on a MathML node. MathML depends on attributes to convey a
* semantic content, so this is used heavily.
*/
MathNode.prototype.setAttribute = function(name, value) {
this.attributes[name] = value;
};
/**
* Converts the math node into a MathML-namespaced DOM element.
*/
MathNode.prototype.toNode = function() {
const node = document.createElementNS(
"http://www.w3.org/1998/Math/MathML", this.type);
for (const attr in this.attributes) {
if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) {
node.setAttribute(attr, this.attributes[attr]);
}
}
for (let i = 0; i < this.children.length; i++) {
node.appendChild(this.children[i].toNode());
}
return node;
};
/**
* Converts the math node into an HTML markup string.
*/
MathNode.prototype.toMarkup = function() {
let markup = "<" + this.type;
// Add the attributes
for (const attr in this.attributes) {
if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) {
markup += " " + attr + "=\"";
markup += utils.escape(this.attributes[attr]);
markup += "\"";
}
}
markup += ">";
for (let i = 0; i < this.children.length; i++) {
markup += this.children[i].toMarkup();
}
markup += "</" + this.type + ">";
return markup;
};
/**
* This node represents a piece of text.
*/
function TextNode(text) {
this.text = text;
class TextNode {
constructor(text) {
this.text = text;
}
/**
* Converts the text node into a DOM text node.
*/
toNode() {
return document.createTextNode(this.text);
}
/**
* Converts the text node into HTML markup (which is just the text itself).
*/
toMarkup() {
return utils.escape(this.text);
}
}
/**
* Converts the text node into a DOM text node.
*/
TextNode.prototype.toNode = function() {
return document.createTextNode(this.text);
};
/**
* Converts the text node into HTML markup (which is just the text itself).
*/
TextNode.prototype.toMarkup = function() {
return utils.escape(this.text);
};
module.exports = {
MathNode: MathNode,
TextNode: TextNode,

View File

@@ -15,14 +15,16 @@
* @param {Token=} lastToken last token of the input for this node,
* will default to firstToken if unset
*/
function ParseNode(type, value, mode, firstToken, lastToken) {
this.type = type;
this.value = value;
this.mode = mode;
if (firstToken && (!lastToken || lastToken.lexer === firstToken.lexer)) {
this.lexer = firstToken.lexer;
this.start = firstToken.start;
this.end = (lastToken || firstToken).end;
class ParseNode {
constructor(type, value, mode, firstToken, lastToken) {
this.type = type;
this.value = value;
this.mode = mode;
if (firstToken && (!lastToken || lastToken.lexer === firstToken.lexer)) {
this.lexer = firstToken.lexer;
this.start = firstToken.start;
this.end = (lastToken || firstToken).end;
}
}
}

View File

@@ -3,7 +3,7 @@
* TODO(emily): Remove this
*/
const Parser = require("./Parser");
import Parser from "./Parser";
/**
* Parses an expression using a Parser, then returns the parsed result.

View File

@@ -4,8 +4,8 @@
/* global it: false */
/* global describe: false */
const parseTree = require("../src/parseTree");
const Settings = require("../src/Settings");
import parseTree from "../src/parseTree";
import Settings from "../src/Settings";
const defaultSettings = new Settings({});

View File

@@ -5,6 +5,7 @@
"contrib/**/*[sS]pec.js"
],
"helpers": [
"helpers/**/*.js"
"helpers/**/*.js",
"node_modules/babel-core/register.js"
]
}

View File

@@ -5,14 +5,14 @@
/* global it: false */
/* global describe: false */
const buildMathML = require("../src/buildMathML");
const buildTree = require("../src/buildTree");
const katex = require("../katex");
const ParseError = require("../src/ParseError");
const parseTree = require("../src/parseTree");
const Options = require("../src/Options");
const Settings = require("../src/Settings");
const Style = require("../src/Style");
import buildMathML from "../src/buildMathML";
import buildTree from "../src/buildTree";
import katex from "../katex";
import ParseError from "../src/ParseError";
import parseTree from "../src/parseTree";
import Options from "../src/Options";
import Settings from "../src/Settings";
import Style from "../src/Style";
const defaultSettings = new Settings({});
const defaultOptions = new Options({