forked from KaTeX/KaTeX
-
Notifications
You must be signed in to change notification settings - Fork 0
/
operatorname.js
164 lines (148 loc) · 5.45 KB
/
operatorname.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// @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@");