// @flow import defineFunction, {ordargument} from "../defineFunction"; import defineMacro from "../defineMacro"; 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("mrow", [identifier, operator]); } else { return mathMLTree.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@");