mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-12 22:48:41 +00:00
We used functions with $FlowFixMe as Flow couldn't refine ParseNode using its type. It seems the issue has been fixed and complicated function calls can be removed.
162 lines
5.3 KiB
JavaScript
162 lines
5.3 KiB
JavaScript
// @flow
|
|
import defineFunction, {ordargument} from "../defineFunction";
|
|
import buildCommon from "../buildCommon";
|
|
import mathMLTree from "../mathMLTree";
|
|
import {SymbolNode} from "../domTree";
|
|
import {assembleSupSub} from "./utils/assembleSupSub";
|
|
import {assertNodeType} from "../parseNode";
|
|
|
|
import * as html from "../buildHTML";
|
|
import * as mml from "../buildMathML";
|
|
|
|
import type {HtmlBuilderSupSub, MathMLBuilder} from "../defineFunction";
|
|
import type {ParseNode} from "../parseNode";
|
|
|
|
// NOTE: Unlike most `htmlBuilder`s, this one handles not only
|
|
// "operatorname", but also "supsub" since \operatorname* can
|
|
// affect super/subscripting.
|
|
export const htmlBuilder: HtmlBuilderSupSub<"operatorname"> = (grp, options) => {
|
|
// Operators are handled in the TeXbook pg. 443-444, rule 13(a).
|
|
let supGroup;
|
|
let subGroup;
|
|
let hasLimits = false;
|
|
let group: ParseNode<"operatorname">;
|
|
if (grp.type === "supsub") {
|
|
// If we have limits, supsub will pass us its group to handle. Pull
|
|
// out the superscript and subscript and set the group to the op in
|
|
// its base.
|
|
supGroup = grp.sup;
|
|
subGroup = grp.sub;
|
|
group = assertNodeType(grp.base, "operatorname");
|
|
hasLimits = true;
|
|
} else {
|
|
group = assertNodeType(grp, "operatorname");
|
|
}
|
|
|
|
let base;
|
|
if (group.body.length > 0) {
|
|
const body = group.body.map(child => {
|
|
// $FlowFixMe: Check if the node has a string `text` property.
|
|
const childText = child.text;
|
|
if (typeof childText === "string") {
|
|
return {
|
|
type: "textord",
|
|
mode: child.mode,
|
|
text: childText,
|
|
};
|
|
} else {
|
|
return child;
|
|
}
|
|
});
|
|
|
|
// Consolidate function names into symbol characters.
|
|
const expression = html.buildExpression(
|
|
body, options.withFont("mathrm"), true);
|
|
|
|
for (let i = 0; i < expression.length; i++) {
|
|
const child = expression[i];
|
|
if (child instanceof SymbolNode) {
|
|
// Per amsopn package,
|
|
// change minus to hyphen and \ast to asterisk
|
|
child.text = child.text.replace(/\u2212/, "-")
|
|
.replace(/\u2217/, "*");
|
|
}
|
|
}
|
|
base = buildCommon.makeSpan(["mop"], expression, options);
|
|
} else {
|
|
base = buildCommon.makeSpan(["mop"], [], options);
|
|
}
|
|
|
|
if (hasLimits) {
|
|
return assembleSupSub(base, supGroup, subGroup, options,
|
|
options.style, 0, 0);
|
|
|
|
} else {
|
|
return base;
|
|
}
|
|
};
|
|
|
|
const mathmlBuilder: MathMLBuilder<"operatorname"> = (group, options) => {
|
|
// The steps taken here are similar to the html version.
|
|
let expression = mml.buildExpression(
|
|
group.body, options.withFont("mathrm"));
|
|
|
|
// Is expression a string or has it something like a fraction?
|
|
let isAllString = true; // default
|
|
for (let i = 0; i < expression.length; i++) {
|
|
const node = expression[i];
|
|
if (node instanceof mathMLTree.SpaceNode) {
|
|
// Do nothing
|
|
} else if (node instanceof mathMLTree.MathNode) {
|
|
switch (node.type) {
|
|
case "mi":
|
|
case "mn":
|
|
case "ms":
|
|
case "mspace":
|
|
case "mtext":
|
|
break; // Do nothing yet.
|
|
case "mo": {
|
|
const child = node.children[0];
|
|
if (node.children.length === 1 &&
|
|
child instanceof mathMLTree.TextNode) {
|
|
child.text =
|
|
child.text.replace(/\u2212/, "-")
|
|
.replace(/\u2217/, "*");
|
|
} else {
|
|
isAllString = false;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
isAllString = false;
|
|
}
|
|
} else {
|
|
isAllString = false;
|
|
}
|
|
}
|
|
|
|
if (isAllString) {
|
|
// Write a single TextNode instead of multiple nested tags.
|
|
const word = expression.map(node => node.toText()).join("");
|
|
expression = [new mathMLTree.TextNode(word)];
|
|
}
|
|
|
|
const identifier = new mathMLTree.MathNode("mi", expression);
|
|
identifier.setAttribute("mathvariant", "normal");
|
|
|
|
// \u2061 is the same as ⁡
|
|
// ref: https://www.w3schools.com/charsets/ref_html_entities_a.asp
|
|
const operator = new mathMLTree.MathNode("mo",
|
|
[mml.makeText("\u2061", "text")]);
|
|
|
|
if (group.parentIsSupSub) {
|
|
return new mathMLTree.MathNode("mo", [identifier, operator]);
|
|
} else {
|
|
return mathMLTree.newDocumentFragment([identifier, operator]);
|
|
}
|
|
};
|
|
|
|
// \operatorname
|
|
// amsopn.dtx: \mathop{#1\kern\z@\operator@font#3}\newmcodes@
|
|
defineFunction({
|
|
type: "operatorname",
|
|
names: ["\\operatorname", "\\operatorname*"],
|
|
props: {
|
|
numArgs: 1,
|
|
},
|
|
handler: ({parser, funcName}, args) => {
|
|
const body = args[0];
|
|
return {
|
|
type: "operatorname",
|
|
mode: parser.mode,
|
|
body: ordargument(body),
|
|
alwaysHandleSupSub: (funcName === "\\operatorname*"),
|
|
limits: false,
|
|
parentIsSupSub: false,
|
|
};
|
|
},
|
|
htmlBuilder,
|
|
mathmlBuilder,
|
|
});
|
|
|