161 lines
5.4 KiB
TypeScript
161 lines
5.4 KiB
TypeScript
import defineFunction, {ordargument} from "../defineFunction";
|
|
import defineMacro from "../defineMacro";
|
|
import {makeSpan} from "../buildCommon";
|
|
import {MathNode, newDocumentFragment, SpaceNode, TextNode} 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 {AnyParseNode, 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): AnyParseNode => {
|
|
const childText = "text" in child ? child.text : undefined;
|
|
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 = makeSpan(["mop"], expression, options);
|
|
} else {
|
|
base = 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: Array<MathNode | TextNode> = 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 SpaceNode) {
|
|
// Do nothing
|
|
} else if (node instanceof MathNode) {
|
|
switch (node.type) {
|
|
case "mi":
|
|
case "mn":
|
|
case "mspace":
|
|
case "mtext":
|
|
break; // Do nothing yet.
|
|
case "mo": {
|
|
const child = node.children[0];
|
|
if (node.children.length === 1 &&
|
|
child instanceof 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 TextNode(word)];
|
|
}
|
|
|
|
const identifier = new 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 MathNode("mo",
|
|
[mml.makeText("\u2061", "text")]);
|
|
|
|
if (group.parentIsSupSub) {
|
|
return new MathNode("mrow", [identifier, operator]);
|
|
} else {
|
|
return newDocumentFragment([identifier, operator]);
|
|
}
|
|
};
|
|
|
|
// \operatorname
|
|
// amsopn.dtx: \mathop{#1\kern\z@\operator@font#3}\newmcodes@
|
|
defineFunction({
|
|
type: "operatorname",
|
|
names: ["\\operatorname@", "\\operatornamewithlimits"],
|
|
props: {
|
|
numArgs: 1,
|
|
},
|
|
handler: ({parser, funcName}, args) => {
|
|
const body = args[0];
|
|
return {
|
|
type: "operatorname",
|
|
mode: parser.mode,
|
|
body: ordargument(body),
|
|
alwaysHandleSupSub: (funcName === "\\operatornamewithlimits"),
|
|
limits: false,
|
|
parentIsSupSub: false,
|
|
};
|
|
},
|
|
htmlBuilder,
|
|
mathmlBuilder,
|
|
});
|
|
|
|
defineMacro("\\operatorname",
|
|
"\\@ifstar\\operatornamewithlimits\\operatorname@");
|