mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-07 04:08:43 +00:00
477 lines
11 KiB
JavaScript
477 lines
11 KiB
JavaScript
// @flow
|
|
/** Include this to ensure that all functions are defined. */
|
|
import utils from "./utils";
|
|
import ParseError from "./ParseError";
|
|
import ParseNode from "./ParseNode";
|
|
import {
|
|
default as _defineFunction,
|
|
ordargument,
|
|
_functions,
|
|
} from "./defineFunction";
|
|
|
|
import type {FunctionPropSpec, FunctionHandler} from "./defineFunction" ;
|
|
|
|
// WARNING: New functions should be added to src/functions and imported here.
|
|
|
|
const functions = _functions;
|
|
export default functions;
|
|
|
|
// Define a convenience function that mimcs the old semantics of defineFunction
|
|
// to support existing code so that we can migrate it a little bit at a time.
|
|
const defineFunction = function(
|
|
names: string[],
|
|
props: FunctionPropSpec,
|
|
handler: ?FunctionHandler, // null only if handled in parser
|
|
) {
|
|
_defineFunction({names, props, handler});
|
|
};
|
|
|
|
// A normal square root
|
|
defineFunction(["\\sqrt"], {
|
|
numArgs: 1,
|
|
numOptionalArgs: 1,
|
|
}, function(context, args, optArgs) {
|
|
const index = optArgs[0];
|
|
const body = args[0];
|
|
return {
|
|
type: "sqrt",
|
|
body: body,
|
|
index: index,
|
|
};
|
|
});
|
|
|
|
import "./functions/color";
|
|
|
|
import "./functions/text";
|
|
|
|
// \color is handled in Parser.js's parseImplicitGroup
|
|
defineFunction(["\\color"], {
|
|
numArgs: 1,
|
|
allowedInText: true,
|
|
greediness: 3,
|
|
argTypes: ["color"],
|
|
}, null);
|
|
|
|
// colorbox
|
|
defineFunction(["\\colorbox"], {
|
|
numArgs: 2,
|
|
allowedInText: true,
|
|
greediness: 3,
|
|
argTypes: ["color", "text"],
|
|
}, function(context, args) {
|
|
const color = args[0];
|
|
const body = args[1];
|
|
return {
|
|
type: "enclose",
|
|
label: context.funcName,
|
|
backgroundColor: color,
|
|
body: body,
|
|
};
|
|
});
|
|
|
|
// fcolorbox
|
|
defineFunction(["\\fcolorbox"], {
|
|
numArgs: 3,
|
|
allowedInText: true,
|
|
greediness: 3,
|
|
argTypes: ["color", "color", "text"],
|
|
}, function(context, args) {
|
|
const borderColor = args[0];
|
|
const backgroundColor = args[1];
|
|
const body = args[2];
|
|
return {
|
|
type: "enclose",
|
|
label: context.funcName,
|
|
backgroundColor: backgroundColor,
|
|
borderColor: borderColor,
|
|
body: body,
|
|
};
|
|
});
|
|
|
|
import "./functions/overline";
|
|
|
|
import "./functions/underline";
|
|
|
|
import "./functions/rule";
|
|
|
|
import "./functions/kern";
|
|
|
|
import "./functions/phantom";
|
|
|
|
// Math class commands except \mathop
|
|
defineFunction([
|
|
"\\mathord", "\\mathbin", "\\mathrel", "\\mathopen",
|
|
"\\mathclose", "\\mathpunct", "\\mathinner",
|
|
], {
|
|
numArgs: 1,
|
|
}, function(context, args) {
|
|
const body = args[0];
|
|
return {
|
|
type: "mclass",
|
|
mclass: "m" + context.funcName.substr(5),
|
|
value: ordargument(body),
|
|
};
|
|
});
|
|
|
|
// Build a relation by placing one symbol on top of another
|
|
defineFunction(["\\stackrel"], {
|
|
numArgs: 2,
|
|
}, function(context, args) {
|
|
const top = args[0];
|
|
const bottom = args[1];
|
|
|
|
const bottomop = new ParseNode("op", {
|
|
type: "op",
|
|
limits: true,
|
|
alwaysHandleSupSub: true,
|
|
symbol: false,
|
|
value: ordargument(bottom),
|
|
}, bottom.mode);
|
|
|
|
const supsub = new ParseNode("supsub", {
|
|
base: bottomop,
|
|
sup: top,
|
|
sub: null,
|
|
}, top.mode);
|
|
|
|
return {
|
|
type: "mclass",
|
|
mclass: "mrel",
|
|
value: [supsub],
|
|
};
|
|
});
|
|
|
|
import "./functions/mod";
|
|
|
|
const fontAliases = {
|
|
"\\Bbb": "\\mathbb",
|
|
"\\bold": "\\mathbf",
|
|
"\\frak": "\\mathfrak",
|
|
"\\bm": "\\boldsymbol",
|
|
};
|
|
|
|
const singleCharIntegrals: {[string]: string} = {
|
|
"\u222b": "\\int",
|
|
"\u222c": "\\iint",
|
|
"\u222d": "\\iiint",
|
|
"\u222e": "\\oint",
|
|
};
|
|
|
|
// There are 2 flags for operators; whether they produce limits in
|
|
// displaystyle, and whether they are symbols and should grow in
|
|
// displaystyle. These four groups cover the four possible choices.
|
|
|
|
// No limits, not symbols
|
|
defineFunction([
|
|
"\\arcsin", "\\arccos", "\\arctan", "\\arctg", "\\arcctg",
|
|
"\\arg", "\\ch", "\\cos", "\\cosec", "\\cosh", "\\cot", "\\cotg",
|
|
"\\coth", "\\csc", "\\ctg", "\\cth", "\\deg", "\\dim", "\\exp",
|
|
"\\hom", "\\ker", "\\lg", "\\ln", "\\log", "\\sec", "\\sin",
|
|
"\\sinh", "\\sh", "\\tan", "\\tanh", "\\tg", "\\th",
|
|
], {
|
|
numArgs: 0,
|
|
}, function(context) {
|
|
return {
|
|
type: "op",
|
|
limits: false,
|
|
symbol: false,
|
|
body: context.funcName,
|
|
};
|
|
});
|
|
|
|
// Limits, not symbols
|
|
defineFunction([
|
|
"\\det", "\\gcd", "\\inf", "\\lim", "\\max", "\\min", "\\Pr", "\\sup",
|
|
], {
|
|
numArgs: 0,
|
|
}, function(context) {
|
|
return {
|
|
type: "op",
|
|
limits: true,
|
|
symbol: false,
|
|
body: context.funcName,
|
|
};
|
|
});
|
|
|
|
// No limits, symbols
|
|
defineFunction([
|
|
"\\int", "\\iint", "\\iiint", "\\oint", "\u222b", "\u222c",
|
|
"\u222d", "\u222e",
|
|
], {
|
|
numArgs: 0,
|
|
}, function(context) {
|
|
let fName = context.funcName;
|
|
if (fName.length === 1) {
|
|
fName = singleCharIntegrals[fName];
|
|
}
|
|
return {
|
|
type: "op",
|
|
limits: false,
|
|
symbol: true,
|
|
body: fName,
|
|
};
|
|
});
|
|
|
|
import "./functions/op";
|
|
|
|
import "./functions/operatorname";
|
|
|
|
import "./functions/genfrac";
|
|
|
|
import "./functions/lap";
|
|
|
|
import "./functions/smash";
|
|
|
|
import "./functions/delimsizing";
|
|
|
|
// Sizing functions (handled in Parser.js explicitly, hence no handler)
|
|
defineFunction([
|
|
"\\tiny", "\\scriptsize", "\\footnotesize", "\\small",
|
|
"\\normalsize", "\\large", "\\Large", "\\LARGE", "\\huge", "\\Huge",
|
|
], {numArgs: 0}, null);
|
|
|
|
// Style changing functions (handled in Parser.js explicitly, hence no
|
|
// handler)
|
|
defineFunction([
|
|
"\\displaystyle", "\\textstyle", "\\scriptstyle",
|
|
"\\scriptscriptstyle",
|
|
], {numArgs: 0}, null);
|
|
|
|
// Old font changing functions
|
|
defineFunction([
|
|
"\\rm", "\\sf", "\\tt", "\\bf", "\\it", //"\\sl", "\\sc",
|
|
], {numArgs: 0}, null);
|
|
|
|
defineFunction([
|
|
// styles
|
|
"\\mathrm", "\\mathit", "\\mathbf", "\\boldsymbol",
|
|
|
|
// families
|
|
"\\mathbb", "\\mathcal", "\\mathfrak", "\\mathscr", "\\mathsf",
|
|
"\\mathtt",
|
|
|
|
// aliases
|
|
"\\Bbb", "\\bold", "\\frak", "\\bm",
|
|
], {
|
|
numArgs: 1,
|
|
greediness: 2,
|
|
}, function(context, args) {
|
|
const body = args[0];
|
|
let func = context.funcName;
|
|
if (func in fontAliases) {
|
|
func = fontAliases[func];
|
|
}
|
|
return {
|
|
type: "font",
|
|
font: func.slice(1),
|
|
body: body,
|
|
};
|
|
});
|
|
|
|
// Accents
|
|
defineFunction([
|
|
"\\acute", "\\grave", "\\ddot", "\\tilde", "\\bar", "\\breve",
|
|
"\\check", "\\hat", "\\vec", "\\dot",
|
|
"\\widehat", "\\widetilde", "\\overrightarrow", "\\overleftarrow",
|
|
"\\Overrightarrow", "\\overleftrightarrow", "\\overgroup",
|
|
"\\overlinesegment", "\\overleftharpoon", "\\overrightharpoon",
|
|
], {
|
|
numArgs: 1,
|
|
}, function(context, args) {
|
|
const base = args[0];
|
|
|
|
const isStretchy = !utils.contains([
|
|
"\\acute", "\\grave", "\\ddot", "\\tilde", "\\bar", "\\breve",
|
|
"\\check", "\\hat", "\\vec", "\\dot",
|
|
], context.funcName);
|
|
|
|
const isShifty = !isStretchy || utils.contains([
|
|
"\\widehat", "\\widetilde",
|
|
], context.funcName);
|
|
|
|
return {
|
|
type: "accent",
|
|
label: context.funcName,
|
|
isStretchy: isStretchy,
|
|
isShifty: isShifty,
|
|
base: base,
|
|
};
|
|
});
|
|
|
|
// Text-mode accents
|
|
defineFunction([
|
|
"\\'", "\\`", "\\^", "\\~", "\\=", "\\u", "\\.", '\\"',
|
|
"\\r", "\\H", "\\v",
|
|
], {
|
|
numArgs: 1,
|
|
allowedInText: true,
|
|
allowedInMath: false,
|
|
}, function(context, args) {
|
|
const base = args[0];
|
|
|
|
return {
|
|
type: "accent",
|
|
label: context.funcName,
|
|
isStretchy: false,
|
|
isShifty: true,
|
|
base: base,
|
|
};
|
|
});
|
|
|
|
// Horizontal stretchy braces
|
|
defineFunction([
|
|
"\\overbrace", "\\underbrace",
|
|
], {
|
|
numArgs: 1,
|
|
}, function(context, args) {
|
|
const base = args[0];
|
|
return {
|
|
type: "horizBrace",
|
|
label: context.funcName,
|
|
isOver: /^\\over/.test(context.funcName),
|
|
base: base,
|
|
};
|
|
});
|
|
|
|
// Stretchy accents under the body
|
|
defineFunction([
|
|
"\\underleftarrow", "\\underrightarrow", "\\underleftrightarrow",
|
|
"\\undergroup", "\\underlinesegment", "\\utilde",
|
|
], {
|
|
numArgs: 1,
|
|
}, function(context, args) {
|
|
const base = args[0];
|
|
return {
|
|
type: "accentUnder",
|
|
label: context.funcName,
|
|
base: base,
|
|
};
|
|
});
|
|
|
|
// Stretchy arrows with an optional argument
|
|
defineFunction([
|
|
"\\xleftarrow", "\\xrightarrow", "\\xLeftarrow", "\\xRightarrow",
|
|
"\\xleftrightarrow", "\\xLeftrightarrow", "\\xhookleftarrow",
|
|
"\\xhookrightarrow", "\\xmapsto", "\\xrightharpoondown",
|
|
"\\xrightharpoonup", "\\xleftharpoondown", "\\xleftharpoonup",
|
|
"\\xrightleftharpoons", "\\xleftrightharpoons", "\\xlongequal",
|
|
"\\xtwoheadrightarrow", "\\xtwoheadleftarrow", "\\xtofrom",
|
|
], {
|
|
numArgs: 1,
|
|
numOptionalArgs: 1,
|
|
}, function(context, args, optArgs) {
|
|
const below = optArgs[0];
|
|
const body = args[0];
|
|
return {
|
|
type: "xArrow", // x for extensible
|
|
label: context.funcName,
|
|
body: body,
|
|
below: below,
|
|
};
|
|
});
|
|
|
|
// enclose
|
|
defineFunction(["\\cancel", "\\bcancel", "\\xcancel", "\\sout", "\\fbox"], {
|
|
numArgs: 1,
|
|
}, function(context, args) {
|
|
const body = args[0];
|
|
return {
|
|
type: "enclose",
|
|
label: context.funcName,
|
|
body: body,
|
|
};
|
|
});
|
|
|
|
// Infix generalized fractions
|
|
defineFunction(["\\over", "\\choose", "\\atop"], {
|
|
numArgs: 0,
|
|
infix: true,
|
|
}, function(context) {
|
|
let replaceWith;
|
|
switch (context.funcName) {
|
|
case "\\over":
|
|
replaceWith = "\\frac";
|
|
break;
|
|
case "\\choose":
|
|
replaceWith = "\\binom";
|
|
break;
|
|
case "\\atop":
|
|
replaceWith = "\\\\atopfrac";
|
|
break;
|
|
default:
|
|
throw new Error("Unrecognized infix genfrac command");
|
|
}
|
|
return {
|
|
type: "infix",
|
|
replaceWith: replaceWith,
|
|
token: context.token,
|
|
};
|
|
});
|
|
|
|
// Row breaks for aligned data
|
|
defineFunction(["\\\\", "\\cr"], {
|
|
numArgs: 0,
|
|
numOptionalArgs: 1,
|
|
argTypes: ["size"],
|
|
}, function(context, args, optArgs) {
|
|
const size = optArgs[0];
|
|
return {
|
|
type: "cr",
|
|
size: size,
|
|
};
|
|
});
|
|
|
|
// Environment delimiters
|
|
defineFunction(["\\begin", "\\end"], {
|
|
numArgs: 1,
|
|
argTypes: ["text"],
|
|
}, function(context, args) {
|
|
const nameGroup = args[0];
|
|
if (nameGroup.type !== "ordgroup") {
|
|
throw new ParseError("Invalid environment name", nameGroup);
|
|
}
|
|
let name = "";
|
|
for (let i = 0; i < nameGroup.value.length; ++i) {
|
|
name += nameGroup.value[i].value;
|
|
}
|
|
return {
|
|
type: "environment",
|
|
name: name,
|
|
nameGroup: nameGroup,
|
|
};
|
|
});
|
|
|
|
// Box manipulation
|
|
defineFunction(["\\raisebox"], {
|
|
numArgs: 2,
|
|
argTypes: ["size", "text"],
|
|
allowedInText: true,
|
|
}, function(context, args) {
|
|
const amount = args[0];
|
|
const body = args[1];
|
|
return {
|
|
type: "raisebox",
|
|
dy: amount,
|
|
body: body,
|
|
value: ordargument(body),
|
|
};
|
|
});
|
|
|
|
// \verb and \verb* are dealt with directly in Parser.js.
|
|
// If we end up here, it's because of a failure to match the two delimiters
|
|
// in the regex in Lexer.js. LaTeX raises the following error when \verb is
|
|
// terminated by end of line (or file).
|
|
defineFunction(["\\verb"], {
|
|
numArgs: 0,
|
|
allowedInText: true,
|
|
}, function(context) {
|
|
throw new ParseError(
|
|
"\\verb ended by end of line instead of matching delimiter");
|
|
});
|
|
|
|
// Hyperlinks
|
|
import "./functions/href";
|
|
|
|
// MathChoice
|
|
import "./functions/mathchoice";
|