mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-07 04:08:43 +00:00
* Round distances to 4 places * Revert numbers in mathMLTree.js * Update Jest test * fix: round dimension to 4 places via makeEm * test: update screenshots * docs: add link to this PR Co-authored-by: ylemkimon <y@ylem.kim>
335 lines
10 KiB
JavaScript
335 lines
10 KiB
JavaScript
// @flow
|
|
// Limits, symbols
|
|
import defineFunction, {ordargument} from "../defineFunction";
|
|
import buildCommon from "../buildCommon";
|
|
import {SymbolNode} from "../domTree";
|
|
import * as mathMLTree from "../mathMLTree";
|
|
import utils from "../utils";
|
|
import Style from "../Style";
|
|
import {assembleSupSub} from "./utils/assembleSupSub";
|
|
import {assertNodeType} from "../parseNode";
|
|
import {makeEm} from "../units";
|
|
|
|
import * as html from "../buildHTML";
|
|
import * as mml from "../buildMathML";
|
|
|
|
import type {HtmlBuilderSupSub, MathMLBuilder} from "../defineFunction";
|
|
import type {ParseNode} from "../parseNode";
|
|
|
|
// Most operators have a large successor symbol, but these don't.
|
|
const noSuccessor = [
|
|
"\\smallint",
|
|
];
|
|
|
|
// NOTE: Unlike most `htmlBuilder`s, this one handles not only "op", but also
|
|
// "supsub" since some of them (like \int) can affect super/subscripting.
|
|
export const htmlBuilder: HtmlBuilderSupSub<"op"> = (grp, options) => {
|
|
// Operators are handled in the TeXbook pg. 443-444, rule 13(a).
|
|
let supGroup;
|
|
let subGroup;
|
|
let hasLimits = false;
|
|
let group: ParseNode<"op">;
|
|
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, "op");
|
|
hasLimits = true;
|
|
} else {
|
|
group = assertNodeType(grp, "op");
|
|
}
|
|
|
|
const style = options.style;
|
|
|
|
let large = false;
|
|
if (style.size === Style.DISPLAY.size &&
|
|
group.symbol &&
|
|
!utils.contains(noSuccessor, group.name)) {
|
|
|
|
// Most symbol operators get larger in displaystyle (rule 13)
|
|
large = true;
|
|
}
|
|
|
|
let base;
|
|
if (group.symbol) {
|
|
// If this is a symbol, create the symbol.
|
|
const fontName = large ? "Size2-Regular" : "Size1-Regular";
|
|
|
|
let stash = "";
|
|
if (group.name === "\\oiint" || group.name === "\\oiiint") {
|
|
// No font glyphs yet, so use a glyph w/o the oval.
|
|
// TODO: When font glyphs are available, delete this code.
|
|
stash = group.name.substr(1);
|
|
group.name = stash === "oiint" ? "\\iint" : "\\iiint";
|
|
}
|
|
|
|
base = buildCommon.makeSymbol(
|
|
group.name, fontName, "math", options,
|
|
["mop", "op-symbol", large ? "large-op" : "small-op"]);
|
|
|
|
if (stash.length > 0) {
|
|
// We're in \oiint or \oiiint. Overlay the oval.
|
|
// TODO: When font glyphs are available, delete this code.
|
|
const italic = base.italic;
|
|
const oval = buildCommon.staticSvg(stash + "Size"
|
|
+ (large ? "2" : "1"), options);
|
|
base = buildCommon.makeVList({
|
|
positionType: "individualShift",
|
|
children: [
|
|
{type: "elem", elem: base, shift: 0},
|
|
{type: "elem", elem: oval, shift: large ? 0.08 : 0},
|
|
],
|
|
}, options);
|
|
group.name = "\\" + stash;
|
|
base.classes.unshift("mop");
|
|
// $FlowFixMe
|
|
base.italic = italic;
|
|
}
|
|
} else if (group.body) {
|
|
// If this is a list, compose that list.
|
|
const inner = html.buildExpression(group.body, options, true);
|
|
if (inner.length === 1 && inner[0] instanceof SymbolNode) {
|
|
base = inner[0];
|
|
base.classes[0] = "mop"; // replace old mclass
|
|
} else {
|
|
base = buildCommon.makeSpan(["mop"], inner, options);
|
|
}
|
|
} else {
|
|
// Otherwise, this is a text operator. Build the text from the
|
|
// operator's name.
|
|
const output = [];
|
|
for (let i = 1; i < group.name.length; i++) {
|
|
output.push(buildCommon.mathsym(group.name[i], group.mode, options));
|
|
}
|
|
base = buildCommon.makeSpan(["mop"], output, options);
|
|
}
|
|
|
|
// If content of op is a single symbol, shift it vertically.
|
|
let baseShift = 0;
|
|
let slant = 0;
|
|
if ((base instanceof SymbolNode
|
|
|| group.name === "\\oiint" || group.name === "\\oiiint")
|
|
&& !group.suppressBaseShift) {
|
|
// We suppress the shift of the base of \overset and \underset. Otherwise,
|
|
// shift the symbol so its center lies on the axis (rule 13). It
|
|
// appears that our fonts have the centers of the symbols already
|
|
// almost on the axis, so these numbers are very small. Note we
|
|
// don't actually apply this here, but instead it is used either in
|
|
// the vlist creation or separately when there are no limits.
|
|
baseShift = (base.height - base.depth) / 2 -
|
|
options.fontMetrics().axisHeight;
|
|
|
|
// The slant of the symbol is just its italic correction.
|
|
// $FlowFixMe
|
|
slant = base.italic;
|
|
}
|
|
|
|
if (hasLimits) {
|
|
return assembleSupSub(base, supGroup, subGroup, options,
|
|
style, slant, baseShift);
|
|
|
|
} else {
|
|
if (baseShift) {
|
|
base.style.position = "relative";
|
|
base.style.top = makeEm(baseShift);
|
|
}
|
|
|
|
return base;
|
|
}
|
|
};
|
|
|
|
const mathmlBuilder: MathMLBuilder<"op"> = (group, options) => {
|
|
let node;
|
|
|
|
if (group.symbol) {
|
|
// This is a symbol. Just add the symbol.
|
|
node = new mathMLTree.MathNode(
|
|
"mo", [mml.makeText(group.name, group.mode)]);
|
|
if (utils.contains(noSuccessor, group.name)) {
|
|
node.setAttribute("largeop", "false");
|
|
}
|
|
} else if (group.body) {
|
|
// This is an operator with children. Add them.
|
|
node = new mathMLTree.MathNode(
|
|
"mo", mml.buildExpression(group.body, options));
|
|
} else {
|
|
// This is a text operator. Add all of the characters from the
|
|
// operator's name.
|
|
node = new mathMLTree.MathNode(
|
|
"mi", [new mathMLTree.TextNode(group.name.slice(1))]);
|
|
// Append an <mo>⁡</mo>.
|
|
// ref: https://www.w3.org/TR/REC-MathML/chap3_2.html#sec3.2.4
|
|
const operator = new mathMLTree.MathNode("mo",
|
|
[mml.makeText("\u2061", "text")]);
|
|
if (group.parentIsSupSub) {
|
|
node = new mathMLTree.MathNode("mrow", [node, operator]);
|
|
} else {
|
|
node = mathMLTree.newDocumentFragment([node, operator]);
|
|
}
|
|
}
|
|
|
|
return node;
|
|
};
|
|
|
|
const singleCharBigOps: {[string]: string} = {
|
|
"\u220F": "\\prod",
|
|
"\u2210": "\\coprod",
|
|
"\u2211": "\\sum",
|
|
"\u22c0": "\\bigwedge",
|
|
"\u22c1": "\\bigvee",
|
|
"\u22c2": "\\bigcap",
|
|
"\u22c3": "\\bigcup",
|
|
"\u2a00": "\\bigodot",
|
|
"\u2a01": "\\bigoplus",
|
|
"\u2a02": "\\bigotimes",
|
|
"\u2a04": "\\biguplus",
|
|
"\u2a06": "\\bigsqcup",
|
|
};
|
|
|
|
defineFunction({
|
|
type: "op",
|
|
names: [
|
|
"\\coprod", "\\bigvee", "\\bigwedge", "\\biguplus", "\\bigcap",
|
|
"\\bigcup", "\\intop", "\\prod", "\\sum", "\\bigotimes",
|
|
"\\bigoplus", "\\bigodot", "\\bigsqcup", "\\smallint", "\u220F",
|
|
"\u2210", "\u2211", "\u22c0", "\u22c1", "\u22c2", "\u22c3", "\u2a00",
|
|
"\u2a01", "\u2a02", "\u2a04", "\u2a06",
|
|
],
|
|
props: {
|
|
numArgs: 0,
|
|
},
|
|
handler: ({parser, funcName}, args) => {
|
|
let fName = funcName;
|
|
if (fName.length === 1) {
|
|
fName = singleCharBigOps[fName];
|
|
}
|
|
return {
|
|
type: "op",
|
|
mode: parser.mode,
|
|
limits: true,
|
|
parentIsSupSub: false,
|
|
symbol: true,
|
|
name: fName,
|
|
};
|
|
},
|
|
htmlBuilder,
|
|
mathmlBuilder,
|
|
});
|
|
|
|
// Note: calling defineFunction with a type that's already been defined only
|
|
// works because the same htmlBuilder and mathmlBuilder are being used.
|
|
defineFunction({
|
|
type: "op",
|
|
names: ["\\mathop"],
|
|
props: {
|
|
numArgs: 1,
|
|
primitive: true,
|
|
},
|
|
handler: ({parser}, args) => {
|
|
const body = args[0];
|
|
return {
|
|
type: "op",
|
|
mode: parser.mode,
|
|
limits: false,
|
|
parentIsSupSub: false,
|
|
symbol: false,
|
|
body: ordargument(body),
|
|
};
|
|
},
|
|
htmlBuilder,
|
|
mathmlBuilder,
|
|
});
|
|
|
|
// There are 2 flags for operators; whether they produce limits in
|
|
// displaystyle, and whether they are symbols and should grow in
|
|
// displaystyle. These four groups cover the four possible choices.
|
|
|
|
const singleCharIntegrals: {[string]: string} = {
|
|
"\u222b": "\\int",
|
|
"\u222c": "\\iint",
|
|
"\u222d": "\\iiint",
|
|
"\u222e": "\\oint",
|
|
"\u222f": "\\oiint",
|
|
"\u2230": "\\oiiint",
|
|
};
|
|
|
|
// No limits, not symbols
|
|
defineFunction({
|
|
type: "op",
|
|
names: [
|
|
"\\arcsin", "\\arccos", "\\arctan", "\\arctg", "\\arcctg",
|
|
"\\arg", "\\ch", "\\cos", "\\cosec", "\\cosh", "\\cot", "\\cotg",
|
|
"\\coth", "\\csc", "\\ctg", "\\cth", "\\deg", "\\dim", "\\exp",
|
|
"\\hom", "\\ker", "\\lg", "\\ln", "\\log", "\\sec", "\\sin",
|
|
"\\sinh", "\\sh", "\\tan", "\\tanh", "\\tg", "\\th",
|
|
],
|
|
props: {
|
|
numArgs: 0,
|
|
},
|
|
handler({parser, funcName}) {
|
|
return {
|
|
type: "op",
|
|
mode: parser.mode,
|
|
limits: false,
|
|
parentIsSupSub: false,
|
|
symbol: false,
|
|
name: funcName,
|
|
};
|
|
},
|
|
htmlBuilder,
|
|
mathmlBuilder,
|
|
});
|
|
|
|
// Limits, not symbols
|
|
defineFunction({
|
|
type: "op",
|
|
names: [
|
|
"\\det", "\\gcd", "\\inf", "\\lim", "\\max", "\\min", "\\Pr", "\\sup",
|
|
],
|
|
props: {
|
|
numArgs: 0,
|
|
},
|
|
handler({parser, funcName}) {
|
|
return {
|
|
type: "op",
|
|
mode: parser.mode,
|
|
limits: true,
|
|
parentIsSupSub: false,
|
|
symbol: false,
|
|
name: funcName,
|
|
};
|
|
},
|
|
htmlBuilder,
|
|
mathmlBuilder,
|
|
});
|
|
|
|
// No limits, symbols
|
|
defineFunction({
|
|
type: "op",
|
|
names: [
|
|
"\\int", "\\iint", "\\iiint", "\\oint", "\\oiint", "\\oiiint",
|
|
"\u222b", "\u222c", "\u222d", "\u222e", "\u222f", "\u2230",
|
|
],
|
|
props: {
|
|
numArgs: 0,
|
|
},
|
|
handler({parser, funcName}) {
|
|
let fName = funcName;
|
|
if (fName.length === 1) {
|
|
fName = singleCharIntegrals[fName];
|
|
}
|
|
return {
|
|
type: "op",
|
|
mode: parser.mode,
|
|
limits: false,
|
|
parentIsSupSub: false,
|
|
symbol: true,
|
|
name: fName,
|
|
};
|
|
},
|
|
htmlBuilder,
|
|
mathmlBuilder,
|
|
});
|