mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-05 19:28:39 +00:00
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:
committed by
Kevin Barabash
parent
0edd3d1bbb
commit
a019f36f8a
3
.babelrc
3
.babelrc
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"presets": ["es2015"],
|
||||
"plugins": [
|
||||
"transform-runtime"
|
||||
"transform-runtime",
|
||||
"transform-class-properties"
|
||||
]
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -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);
|
||||
}
|
||||
|
10
katex.js
10
katex.js
@@ -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
|
||||
|
@@ -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",
|
||||
|
41
server.js
41
server.js
@@ -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);
|
||||
|
97
src/Lexer.js
97
src/Lexer.js
@@ -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;
|
||||
|
@@ -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;
|
||||
|
452
src/Options.js
452
src/Options.js
@@ -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.
|
||||
*/
|
||||
|
@@ -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
|
||||
|
1637
src/Parser.js
1637
src/Parser.js
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
114
src/Style.js
114
src/Style.js
@@ -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;
|
||||
|
@@ -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 = [
|
||||
|
@@ -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";
|
||||
};
|
||||
|
@@ -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
|
||||
|
@@ -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({});
|
||||
|
@@ -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.
|
||||
|
526
src/domTree.js
526
src/domTree.js
@@ -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,
|
||||
|
@@ -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];
|
||||
|
@@ -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
|
||||
|
@@ -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.
|
||||
|
@@ -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,
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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.
|
||||
|
@@ -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({});
|
||||
|
||||
|
@@ -5,6 +5,7 @@
|
||||
"contrib/**/*[sS]pec.js"
|
||||
],
|
||||
"helpers": [
|
||||
"helpers/**/*.js"
|
||||
"helpers/**/*.js",
|
||||
"node_modules/babel-core/register.js"
|
||||
]
|
||||
}
|
||||
|
@@ -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({
|
||||
|
Reference in New Issue
Block a user