UNPKG

katex

Version:

Fast math typesetting for the web.

222 lines (193 loc) 7.94 kB
import type Parser from "./Parser"; import type {ParseNode, AnyParseNode, NodeType, UnsupportedCmdParseNode} from "./parseNode"; import type Options from "./Options"; import type {ArgType, BreakToken} from "./types"; import type {HtmlDomNode} from "./domTree"; import type {Token} from "./Token"; import type {MathDomNode} from "./mathMLTree"; /** Context provided to function handlers for error messages. */ export type FunctionContext = { funcName: string; parser: Parser; token?: Token; breakOnTokenText?: BreakToken; }; export type FunctionHandler<NODETYPE extends NodeType> = ( context: FunctionContext, args: AnyParseNode[], optArgs: (AnyParseNode | null | undefined)[], ) => UnsupportedCmdParseNode | ParseNode<NODETYPE>; // Note: reverse the order of the return type union will cause a flow error. // See https://github.com/facebook/flow/issues/3663. export type HtmlBuilder<NODETYPE extends NodeType> = (group: ParseNode<NODETYPE>, options: Options) => HtmlDomNode; export type MathMLBuilder<NODETYPE extends NodeType> = (group: ParseNode<NODETYPE>, options: Options) => MathDomNode; // More general version of `HtmlBuilder` for nodes (e.g. \sum, accent types) // whose presence impacts super/subscripting. In this case, ParseNode<"supsub"> // delegates its HTML building to the HtmlBuilder corresponding to these nodes. export type HtmlBuilderSupSub<NODETYPE extends NodeType> = (group: ParseNode<"supsub"> | ParseNode<NODETYPE>, options: Options) => HtmlDomNode; export type FunctionPropSpec = { // The number of arguments the function takes. numArgs: number; // An array corresponding to each argument of the function, giving the // type of argument that should be parsed. Its length should be equal // to `numOptionalArgs + numArgs`, and types for optional arguments // should appear before types for mandatory arguments. argTypes?: ArgType[]; // Whether it expands to a single token or a braced group of tokens. // If it's grouped, it can be used as an argument to primitive commands, // such as \sqrt (without the optional argument) and super/subscript. allowedInArgument?: boolean; // Whether or not the function is allowed inside text mode // (default false) allowedInText?: boolean; // Whether or not the function is allowed inside text mode // (default true) allowedInMath?: boolean; // (optional) The number of optional arguments the function // should parse. If the optional arguments aren't found, // `null` will be passed to the handler in their place. // (default 0) numOptionalArgs?: number; // Must be true if the function is an infix operator. infix?: boolean; // Whether or not the function is a TeX primitive. primitive?: boolean; }; type FunctionDefSpec<NODETYPE extends NodeType> = { // Unique string to differentiate parse nodes. // Also determines the type of the value returned by `handler`. type: NODETYPE; // The first argument to defineFunction is a single name or a list of names. // All functions named in such a list will share a single implementation. names: Array<string>; // Properties that control how the functions are parsed. props: FunctionPropSpec; // The handler is called to handle these functions and their arguments and // returns a `ParseNode`. handler: FunctionHandler<NODETYPE> | null | undefined; // This function returns an object representing the DOM structure to be // created when rendering the defined LaTeX function. // This should not modify the `ParseNode`. htmlBuilder?: HtmlBuilder<NODETYPE>; // This function returns an object representing the MathML structure to be // created when rendering the defined LaTeX function. // This should not modify the `ParseNode`. mathmlBuilder?: MathMLBuilder<NODETYPE>; }; /** * Final function spec for use at parse time. * This is almost identical to `FunctionPropSpec`, except it * 1. includes the function handler, and * 2. requires all arguments except argTypes. * It is generated by `defineFunction()` below. */ export type FunctionSpec<NODETYPE extends NodeType> = { type: NODETYPE; // Need to use the type to avoid error. See NOTES below. numArgs: number; argTypes?: ArgType[]; allowedInArgument: boolean; allowedInText: boolean; allowedInMath: boolean; numOptionalArgs: number; infix: boolean; primitive: boolean; // FLOW TYPE NOTES: Doing either one of the following two // // - removing the NODETYPE type parameter in FunctionSpec above; // - using ?FunctionHandler<NODETYPE> below; // // results in a confusing flow typing error: // "string literal `styling`. This type is incompatible with..." // pointing to the definition of `defineFunction` and finishing with // "some incompatible instantiation of `NODETYPE`" // // Having FunctionSpec<NODETYPE> above and FunctionHandler<*> below seems to // circumvent this error. This is not harmful for catching errors since // _functions is typed FunctionSpec<*> (it stores all TeX function specs). // Must be specified unless it's handled directly in the parser. handler: FunctionHandler<any> | null | undefined; }; /** * All registered functions. * `functions.js` just exports this same dictionary again and makes it public. * `Parser.js` requires this dictionary. */ export const _functions: Record<string, FunctionSpec<any>> = {}; /** * All HTML builders. Should be only used in the `define*` and the `build*ML` * functions. */ export const _htmlGroupBuilders: Record<string, HtmlBuilder<any>> = {}; /** * All MathML builders. Should be only used in the `define*` and the `build*ML` * functions. */ export const _mathmlGroupBuilders: Record<string, MathMLBuilder<any>> = {}; export default function defineFunction<NODETYPE extends NodeType>({ type, names, props, handler, htmlBuilder, mathmlBuilder, }: FunctionDefSpec<NODETYPE>) { // Set default values of functions const data = { type, numArgs: props.numArgs, argTypes: props.argTypes, allowedInArgument: !!props.allowedInArgument, allowedInText: !!props.allowedInText, allowedInMath: (props.allowedInMath === undefined) ? true : props.allowedInMath, numOptionalArgs: props.numOptionalArgs || 0, infix: !!props.infix, primitive: !!props.primitive, handler, }; for (let i = 0; i < names.length; ++i) { _functions[names[i]] = data; } if (type) { if (htmlBuilder) { _htmlGroupBuilders[type] = htmlBuilder; } if (mathmlBuilder) { _mathmlGroupBuilders[type] = mathmlBuilder; } } } /** * Use this to register only the HTML and MathML builders for a function (e.g. * if the function's ParseNode is generated in Parser.js rather than via a * stand-alone handler provided to `defineFunction`). */ export function defineFunctionBuilders<NODETYPE extends NodeType>({ type, htmlBuilder, mathmlBuilder, }: { type: NODETYPE; htmlBuilder?: HtmlBuilder<NODETYPE>; mathmlBuilder: MathMLBuilder<NODETYPE>; }) { defineFunction({ type, names: [], props: {numArgs: 0}, handler() { throw new Error('Should never be called.'); }, htmlBuilder, mathmlBuilder, }); } export const normalizeArgument = function(arg: AnyParseNode): AnyParseNode { return arg.type === "ordgroup" && arg.body.length === 1 ? arg.body[0] : arg; }; // Since the corresponding buildHTML/buildMathML function expects a // list of elements, we normalize for different kinds of arguments export const ordargument = function(arg: AnyParseNode): AnyParseNode[] { return arg.type === "ordgroup" ? arg.body : [arg]; };