Support \operatorname* (#1899)

* Support \operatorname*

* Fix lint errors

* Fix YAML

* Update screenshots

* Break out a function to avoid code duplication

* Fix lint errors

* Restore wrapper span

* Update docs

* Reinstall color macros lost in merge

* Update screenshots

* Add type annotations, Move to utils file, add \limits to screenshots

* Fix lint errors

* Rearrange screen shot to fit onto page

* Update screenshots

* tweak location of utils.js and assembleSupSup.js
This commit is contained in:
Ron Kok
2019-07-17 18:38:44 -07:00
committed by Kevin Barabash
parent 08df67b7cd
commit 2dc8f8121a
17 changed files with 306 additions and 207 deletions

View File

@@ -760,7 +760,7 @@ use `\ce` instead|
|\omicron|$\omicron$||
|\ominus|$\ominus$||
|\operatorname|$\operatorname{asin} x$|\operatorname{asin} x|
|\operatorname\*|<span style="color:firebrick;">Not supported</span>|[Issue #1242](https://github.com/KaTeX/KaTeX/issues/1242)|
|\operatorname\*|$\operatorname{asin}\limits_y x$|`\operatorname{asin}\limits_y x`|
|\oplus|$\oplus$||
|\or|<span style="color:firebrick;">Not supported</span>||
|\oslash|$\oslash$||

View File

@@ -364,8 +364,8 @@ Direct Input: $+ - / * ⋅ ± × ÷ ∓ ∔ ∧ ≀ ⊎ ⊓ ⊔ ⊕
| $\cos$ `\cos` | $\exp$ `\exp` | $\tanh$ `\tanh`| $\min$ `\min` |
| $\cosec$ `\cosec` | $\hom$ `\hom` | $\tg$ `\tg` | $\Pr$ `\Pr` |
| $\cosh$ `\cosh`| $\ker$ `\ker` | $\th$ `\th` | $\sup$ `\sup` |
| $\cot$ `\cot` | $\lg$ `\lg`| $\operatorname{f}$ `\operatorname{f}` | $\argmax$ `\argmax` |
| $\argmin$ `\argmin`| $\plim$ `\plim` |
| $\cot$ `\cot` | $\lg$ `\lg`| $\argmax$ `\argmax` | $\argmin$ `\argmin` |
| $\plim$ `\plim` | $\operatorname{f}$ `\operatorname{f}`| $\operatorname*{f}$ `\operatorname*{f}`| |
Functions on the right column of this table can take `\limits`.

View File

@@ -51,6 +51,7 @@ const tokenRegexString = `(${spaceRegexString}+)|` + // whitespace
`${combiningDiacriticalMarkString}*` + // ...plus accents
"|\\\\verb\\*([^]).*?\\3" + // \verb*
"|\\\\verb([^*a-zA-Z]).*?\\4" + // \verb unstarred
"|\\\\operatorname\\*" + // \operatorname*
`|${controlWordWhitespaceRegexString}` + // \macroName + spaces
`|${controlSymbolRegexString})`; // \\, \', etc.

View File

@@ -315,15 +315,21 @@ export default class Parser {
if (lex.text === "\\limits" || lex.text === "\\nolimits") {
// We got a limit control
const opNode = checkNodeType(base, "op");
let opNode = checkNodeType(base, "op");
if (opNode) {
const limits = lex.text === "\\limits";
opNode.limits = limits;
opNode.alwaysHandleSupSub = true;
} else {
throw new ParseError(
"Limit controls must follow a math operator",
lex);
opNode = checkNodeType(base, "operatorname");
if (opNode && opNode.alwaysHandleSupSub) {
const limits = lex.text === "\\limits";
opNode.limits = limits;
} else {
throw new ParseError(
"Limit controls must follow a math operator",
lex);
}
}
this.consume();
} else if (lex.text === "^") {

View File

@@ -6,6 +6,7 @@ import {SymbolNode} from "../domTree";
import * as mathMLTree from "../mathMLTree";
import utils from "../utils";
import Style from "../Style";
import {assembleSupSub} from "./utils/assembleSupSub";
import {assertNodeType, checkNodeType} from "../parseNode";
import * as html from "../buildHTML";
@@ -131,99 +132,9 @@ export const htmlBuilder: HtmlBuilderSupSub<"op"> = (grp, options) => {
}
if (hasLimits) {
// IE 8 clips \int if it is in a display: inline-block. We wrap it
// in a new span so it is an inline, and works.
base = buildCommon.makeSpan([], [base]);
return assembleSupSub(base, supGroup, subGroup, options,
style, slant, baseShift);
let sub;
let sup;
// We manually have to handle the superscripts and subscripts. This,
// aside from the kern calculations, is copied from supsub.
if (supGroup) {
const elem = html.buildGroup(
supGroup, options.havingStyle(style.sup()), options);
sup = {
elem,
kern: Math.max(
options.fontMetrics().bigOpSpacing1,
options.fontMetrics().bigOpSpacing3 - elem.depth),
};
}
if (subGroup) {
const elem = html.buildGroup(
subGroup, options.havingStyle(style.sub()), options);
sub = {
elem,
kern: Math.max(
options.fontMetrics().bigOpSpacing2,
options.fontMetrics().bigOpSpacing4 - elem.height),
};
}
// Build the final group as a vlist of the possible subscript, base,
// and possible superscript.
let finalGroup;
if (sup && sub) {
const bottom = options.fontMetrics().bigOpSpacing5 +
sub.elem.height + sub.elem.depth +
sub.kern +
base.depth + baseShift;
finalGroup = buildCommon.makeVList({
positionType: "bottom",
positionData: bottom,
children: [
{type: "kern", size: options.fontMetrics().bigOpSpacing5},
{type: "elem", elem: sub.elem, marginLeft: -slant + "em"},
{type: "kern", size: sub.kern},
{type: "elem", elem: base},
{type: "kern", size: sup.kern},
{type: "elem", elem: sup.elem, marginLeft: slant + "em"},
{type: "kern", size: options.fontMetrics().bigOpSpacing5},
],
}, options);
} else if (sub) {
const top = base.height - baseShift;
// Shift the limits by the slant of the symbol. Note
// that we are supposed to shift the limits by 1/2 of the slant,
// but since we are centering the limits adding a full slant of
// margin will shift by 1/2 that.
finalGroup = buildCommon.makeVList({
positionType: "top",
positionData: top,
children: [
{type: "kern", size: options.fontMetrics().bigOpSpacing5},
{type: "elem", elem: sub.elem, marginLeft: -slant + "em"},
{type: "kern", size: sub.kern},
{type: "elem", elem: base},
],
}, options);
} else if (sup) {
const bottom = base.depth + baseShift;
finalGroup = buildCommon.makeVList({
positionType: "bottom",
positionData: bottom,
children: [
{type: "elem", elem: base},
{type: "kern", size: sup.kern},
{type: "elem", elem: sup.elem, marginLeft: slant + "em"},
{type: "kern", size: options.fontMetrics().bigOpSpacing5},
],
}, options);
} else {
// This case probably shouldn't occur (this would mean the
// supsub was sending us a group with no superscript or
// subscript) but be safe.
return base;
}
return buildCommon.makeSpan(
["mop", "op-limits"], [finalGroup], options);
} else {
if (baseShift) {
base.style.position = "relative";
@@ -251,8 +162,6 @@ const mathmlBuilder: MathMLBuilder<"op"> = (group, options) => {
} else {
// This is a text operator. Add all of the characters from the
// operator's name.
// TODO(emily): Add a space in the middle of some of these
// operators, like \limsup.
node = new mathMLTree.MathNode(
"mi", [new mathMLTree.TextNode(group.name.slice(1))]);
// Append an <mo>&ApplyFunction;</mo>.

View File

@@ -3,115 +3,160 @@ import defineFunction, {ordargument} from "../defineFunction";
import buildCommon from "../buildCommon";
import mathMLTree from "../mathMLTree";
import {SymbolNode} from "../domTree";
import {assembleSupSub} from "./utils/assembleSupSub";
import {assertNodeType, checkNodeType} 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">;
const supSub = checkNodeType(grp, "supsub");
if (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 = supSub.sup;
subGroup = supSub.sub;
group = assertNodeType(supSub.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 &ApplyFunction;
// 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"],
names: ["\\operatorname", "\\operatorname*"],
props: {
numArgs: 1,
},
handler: ({parser}, args) => {
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: (group, options) => {
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/, "*");
}
}
return buildCommon.makeSpan(["mop"], expression, options);
} else {
return buildCommon.makeSpan(["mop"], [], options);
}
},
mathmlBuilder: (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 &ApplyFunction;
// ref: https://www.w3schools.com/charsets/ref_html_entities_a.asp
const operator = new mathMLTree.MathNode("mo",
[mml.makeText("\u2061", "text")]);
return mathMLTree.newDocumentFragment([identifier, operator]);
},
htmlBuilder,
mathmlBuilder,
});

View File

@@ -12,6 +12,7 @@ import * as mml from "../buildMathML";
import * as accent from "./accent";
import * as horizBrace from "./horizBrace";
import * as op from "./op";
import * as operatorname from "./operatorname";
import type Options from "../Options";
import type {ParseNode} from "../parseNode";
@@ -39,6 +40,10 @@ const htmlBuilderDelegate = function(
(options.style.size === Style.DISPLAY.size ||
base.alwaysHandleSupSub);
return delegate ? op.htmlBuilder : null;
} else if (base.type === "operatorname") {
const delegate = base.alwaysHandleSupSub &&
(options.style.size === Style.DISPLAY.size || base.limits);
return delegate ? operatorname.htmlBuilder : null;
} else if (base.type === "accent") {
return utils.isCharacterBox(base.base) ? accent.htmlBuilder : null;
} else if (base.type === "horizBrace") {
@@ -201,7 +206,8 @@ defineFunctionBuilders({
}
}
if (group.base && group.base.type === "op") {
if (group.base &&
(group.base.type === "op" || group.base.type === "operatorname")) {
group.base.parentIsSupSub = true;
}
@@ -223,6 +229,10 @@ defineFunctionBuilders({
if (base && base.type === "op" && base.limits &&
(options.style === Style.DISPLAY || base.alwaysHandleSupSub)) {
nodeType = "mover";
} else if (base && base.type === "operatorname" &&
base.alwaysHandleSupSub &&
(base.limits || options.style === Style.DISPLAY)) {
nodeType = "mover";
} else {
nodeType = "msup";
}
@@ -231,6 +241,10 @@ defineFunctionBuilders({
if (base && base.type === "op" && base.limits &&
(options.style === Style.DISPLAY || base.alwaysHandleSupSub)) {
nodeType = "munder";
} else if (base && base.type === "operatorname" &&
base.alwaysHandleSupSub &&
(base.limits || options.style === Style.DISPLAY)) {
nodeType = "munder";
} else {
nodeType = "msub";
}
@@ -239,6 +253,10 @@ defineFunctionBuilders({
if (base && base.type === "op" && base.limits &&
options.style === Style.DISPLAY) {
nodeType = "munderover";
} else if (base && base.type === "operatorname" &&
base.alwaysHandleSupSub &&
(options.style === Style.DISPLAY || base.limits)) {
nodeType = "munderover";
} else {
nodeType = "msubsup";
}

View File

@@ -0,0 +1,112 @@
// @flow
import buildCommon from "../../buildCommon";
import * as html from "../../buildHTML";
import type {StyleInterface} from "../../Style";
import type Options from "../../Options";
import type {DomSpan, SymbolNode} from "../../domTree";
import type {AnyParseNode} from "../../parseNode";
// For an operator with limits, assemble the base, sup, and sub into a span.
export const assembleSupSub = (
base: DomSpan | SymbolNode,
supGroup: ?AnyParseNode,
subGroup: ?AnyParseNode,
options: Options,
style: StyleInterface,
slant: number,
baseShift: number,
): DomSpan => {
// IE 8 clips \int if it is in a display: inline-block. We wrap it
// in a new span so it is an inline, and works.
base = buildCommon.makeSpan([], [base]);
let sub;
let sup;
// We manually have to handle the superscripts and subscripts. This,
// aside from the kern calculations, is copied from supsub.
if (supGroup) {
const elem = html.buildGroup(
supGroup, options.havingStyle(style.sup()), options);
sup = {
elem,
kern: Math.max(
options.fontMetrics().bigOpSpacing1,
options.fontMetrics().bigOpSpacing3 - elem.depth),
};
}
if (subGroup) {
const elem = html.buildGroup(
subGroup, options.havingStyle(style.sub()), options);
sub = {
elem,
kern: Math.max(
options.fontMetrics().bigOpSpacing2,
options.fontMetrics().bigOpSpacing4 - elem.height),
};
}
// Build the final group as a vlist of the possible subscript, base,
// and possible superscript.
let finalGroup;
if (sup && sub) {
const bottom = options.fontMetrics().bigOpSpacing5 +
sub.elem.height + sub.elem.depth +
sub.kern +
base.depth + baseShift;
finalGroup = buildCommon.makeVList({
positionType: "bottom",
positionData: bottom,
children: [
{type: "kern", size: options.fontMetrics().bigOpSpacing5},
{type: "elem", elem: sub.elem, marginLeft: -slant + "em"},
{type: "kern", size: sub.kern},
{type: "elem", elem: base},
{type: "kern", size: sup.kern},
{type: "elem", elem: sup.elem, marginLeft: slant + "em"},
{type: "kern", size: options.fontMetrics().bigOpSpacing5},
],
}, options);
} else if (sub) {
const top = base.height - baseShift;
// Shift the limits by the slant of the symbol. Note
// that we are supposed to shift the limits by 1/2 of the slant,
// but since we are centering the limits adding a full slant of
// margin will shift by 1/2 that.
finalGroup = buildCommon.makeVList({
positionType: "top",
positionData: top,
children: [
{type: "kern", size: options.fontMetrics().bigOpSpacing5},
{type: "elem", elem: sub.elem, marginLeft: -slant + "em"},
{type: "kern", size: sub.kern},
{type: "elem", elem: base},
],
}, options);
} else if (sup) {
const bottom = base.depth + baseShift;
finalGroup = buildCommon.makeVList({
positionType: "bottom",
positionData: bottom,
children: [
{type: "elem", elem: base},
{type: "kern", size: sup.kern},
{type: "elem", elem: sup.elem, marginLeft: slant + "em"},
{type: "kern", size: options.fontMetrics().bigOpSpacing5},
],
}, options);
} else {
// This case probably shouldn't occur (this would mean the
// supsub was sending us a group with no superscript or
// subscript) but be safe.
return base;
}
return buildCommon.makeSpan(
["mop", "op-limits"], [finalGroup], options);
};

View File

@@ -815,8 +815,8 @@ defineMacro("\\approxcoloncolon",
// Present in newtxmath, pxfonts and txfonts
defineMacro("\\notni", "\\html@mathml{\\not\\ni}{\\mathrel{\\char`\u220C}}");
defineMacro("\\limsup", "\\DOTSB\\mathop{\\operatorname{lim\\,sup}}\\limits");
defineMacro("\\liminf", "\\DOTSB\\mathop{\\operatorname{lim\\,inf}}\\limits");
defineMacro("\\limsup", "\\DOTSB\\operatorname*{lim\\,sup}");
defineMacro("\\liminf", "\\DOTSB\\operatorname*{lim\\,inf}");
//////////////////////////////////////////////////////////////////////
// MathML alternates for KaTeX glyphs in the Unicode private area
@@ -942,8 +942,8 @@ defineMacro("\\Zeta", "\\mathrm{Z}");
// statmath.sty
// https://ctan.math.illinois.edu/macros/latex/contrib/statmath/statmath.pdf
defineMacro("\\argmin", "\\DOTSB\\mathop{\\operatorname{arg\\,min}}\\limits");
defineMacro("\\argmax", "\\DOTSB\\mathop{\\operatorname{arg\\,max}}\\limits");
defineMacro("\\argmin", "\\DOTSB\\operatorname*{arg\\,min}");
defineMacro("\\argmax", "\\DOTSB\\operatorname*{arg\\,max}");
defineMacro("\\plim", "\\DOTSB\\mathop{\\operatorname{plim}}\\limits");
// Custom Khan Academy colors, should be moved to an optional package

View File

@@ -358,6 +358,9 @@ type ParseNodeTypes = {
mode: Mode,
loc?: ?SourceLocation,
body: AnyParseNode[],
alwaysHandleSupSub: boolean,
limits: boolean,
parentIsSupSub: boolean,
|},
"overline": {|
type: "overline",

View File

@@ -327,7 +327,7 @@ exports[`A MathML builder should output \\limsup_{x \\rightarrow \\infty} correc
<math xmlns="http://www.w3.org/1998/Math/MathML">
<semantics>
<mrow>
<munder>
<msub>
<mo>
<mi mathvariant="normal">
limsup
@@ -347,7 +347,7 @@ exports[`A MathML builder should output \\limsup_{x \\rightarrow \\infty} correc
</mi>
</mrow>
</munder>
</msub>
</mrow>
<annotation encoding="application/x-tex">
\\limsup_{x \\rightarrow \\infty}

View File

@@ -2651,6 +2651,9 @@ describe("An aligned environment", function() {
describe("operatorname support", function() {
it("should not fail", function() {
expect("\\operatorname{x*Π∑\\Pi\\sum\\frac a b}").toBuild();
expect("\\operatorname*{x*Π∑\\Pi\\sum\\frac a b}").toBuild();
expect("\\operatorname*{x*Π∑\\Pi\\sum\\frac a b}_y x").toBuild();
expect("\\operatorname*{x*Π∑\\Pi\\sum\\frac a b}\\limits_y x").toBuild();
});
});
@@ -3218,11 +3221,11 @@ describe("A macro expander", function() {
});
it("should expand \\limsup as expected", () => {
expect`\limsup`.toParseLike`\mathop{\operatorname{lim\,sup}}\limits`;
expect`\limsup`.toParseLike`\operatorname*{lim\,sup}`;
});
it("should expand \\liminf as expected", () => {
expect`\liminf`.toParseLike`\mathop{\operatorname{lim\,inf}}\limits`;
expect`\liminf`.toParseLike`\operatorname*{lim\,inf}`;
});
it("should expand \\plim as expected", () => {
@@ -3230,11 +3233,11 @@ describe("A macro expander", function() {
});
it("should expand \\argmin as expected", () => {
expect`\argmin`.toParseLike`\mathop{\operatorname{arg\,min}}\limits`;
expect`\argmin`.toParseLike`\operatorname*{arg\,min}`;
});
it("should expand \\argmax as expected", () => {
expect`\argmax`.toParseLike`\mathop{\operatorname{arg\,max}}\limits`;
expect`\argmax`.toParseLike`\operatorname*{arg\,max}`;
});
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -271,14 +271,16 @@ OldFont: |
OperatorName: |
\begin{matrix}
\operatorname g (z) + 5\operatorname{g}z + \operatorname{Gam-ma}(z) \\
\operatorname{Gam ma}(z) + \operatorname{\Gamma}(z) + \operatorname{}x
\operatorname{Gam ma}(z) + \operatorname{\Gamma}(z) + \operatorname{}x \\
\operatorname*{asin} x + \operatorname*{asin}_y x + \operatorname*{asin}\limits_y x \\[0.3em]
{\displaystyle \operatorname*{asin}_y x}
\end{matrix}
OpLimits: |
\begin{matrix}
{\sin_2^2 \lim_2^2 \int_2^2 \sum_2^2}
{\displaystyle \lim_2^2 \int_2^2 \intop_2^2 \sum_2^2} \\
\limsup_{x \rightarrow \infty} x \stackrel{?}= \liminf_{x \rightarrow \infty} x \\
\displaystyle \sum_{\substack{0<i<m\\0<j<n}}
{\displaystyle \limsup_{x \rightarrow \infty} x\:\: \sum_{\substack{0<i<m\\0<j<n}}}
\end{matrix}
OverUnderline: x\underline{x}\underline{\underline{x}}\underline{x_{x_{x_x}}}\underline{x^{x^{x^x}}}\overline{x}\overline{x}\overline{x^{x^{x^x}}} \blue{\overline{\underline{x}}\underline{\overline{x}}}
OverUnderset: |