mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-06 03:38:39 +00:00
* Fix #1212 by supporting `\(...\)` in addition to `$...$` inline math nested inside `\text`. This turned out to be harder than one would think because of the way `$` was being handled specially, while `\(` needed to be handled as a function, which immediately consumed the `\(` token, which meant that the first following token got consumed in text mode instead of math mode (unlike `$` where we could switch the mode before consumption). * Added new `consumeMode` setting for functions which tells the parser to switch modes just for the consumption of the command token. * Now that we're working with functions, move all the `$` handling code into `functions/math.js`. Somewhat bizarrely, we can define `$` as a function (nearly identical to `\(`) and it all works (just like we can have single-character macros). This move removed a lot of messy stuff from `Parser.js`: `ParsedDollar`, `ParsedFuncOrArgOrDollar`, `newDollar`, `assertFuncOrArg`, and at least three explicit checks for `if (... === "$")`. * Moved the `consume()` for the command token from `parseSymbol` to `parseGivenFunction`. This ended up not being strictly necessary, but seems conceptually cleaner. * Partially address #1027 by setting `consumeMode: "text"` for `\text` commands. As a result, `\text` now instantly switches the mode to `"text"`, so even an unbraced macro argument will properly detect text mode. I added a test for this, which is a slightly weaker form of #1027.
303 lines
7.2 KiB
JavaScript
303 lines
7.2 KiB
JavaScript
// @flow
|
|
/** Include this to ensure that all functions are defined. */
|
|
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});
|
|
};
|
|
|
|
// TODO(kevinb): have functions return an object and call defineFunction with
|
|
// that object in this file instead of relying on side-effects.
|
|
import "./functions/sqrt";
|
|
|
|
import "./functions/color";
|
|
|
|
import "./functions/text";
|
|
|
|
import "./functions/math";
|
|
|
|
import "./functions/enclose";
|
|
|
|
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 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";
|
|
|
|
import "./functions/sizing";
|
|
|
|
import "./functions/styling";
|
|
|
|
import "./functions/font";
|
|
|
|
import "./functions/accent";
|
|
|
|
// 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
|
|
import "./functions/accentunder";
|
|
|
|
// 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",
|
|
// The next 3 functions are here to support the mhchem extension.
|
|
// Direct use of these functions is discouraged and may break someday.
|
|
"\\xrightleftarrows", "\\xrightequilibrium",
|
|
"\\xleftequilibrium",
|
|
], {
|
|
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,
|
|
};
|
|
});
|
|
|
|
// 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),
|
|
};
|
|
});
|
|
|
|
import "./functions/verb";
|
|
|
|
// Hyperlinks
|
|
import "./functions/href";
|
|
|
|
// MathChoice
|
|
import "./functions/mathchoice";
|