You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
169 lines
5.2 KiB
JavaScript
169 lines
5.2 KiB
JavaScript
// @flow
|
|
import defineFunction, {ordargument} from "../defineFunction";
|
|
import buildCommon from "../buildCommon";
|
|
import mathMLTree from "../mathMLTree";
|
|
import utils from "../utils";
|
|
import type {AnyParseNode} from "../parseNode";
|
|
|
|
import * as html from "../buildHTML";
|
|
import * as mml from "../buildMathML";
|
|
|
|
import type {ParseNode} from "../parseNode";
|
|
|
|
const makeSpan = buildCommon.makeSpan;
|
|
|
|
function htmlBuilder(group: ParseNode<"mclass">, options) {
|
|
const elements = html.buildExpression(group.body, options, true);
|
|
return makeSpan([group.mclass], elements, options);
|
|
}
|
|
|
|
function mathmlBuilder(group: ParseNode<"mclass">, options) {
|
|
let node: mathMLTree.MathNode;
|
|
const inner = mml.buildExpression(group.body, options);
|
|
|
|
if (group.mclass === "minner") {
|
|
node = new mathMLTree.MathNode("mpadded", inner);
|
|
} else if (group.mclass === "mord") {
|
|
if (group.isCharacterBox) {
|
|
node = inner[0];
|
|
node.type = "mi";
|
|
} else {
|
|
node = new mathMLTree.MathNode("mi", inner);
|
|
}
|
|
} else {
|
|
if (group.isCharacterBox) {
|
|
node = inner[0];
|
|
node.type = "mo";
|
|
} else {
|
|
node = new mathMLTree.MathNode("mo", inner);
|
|
}
|
|
|
|
// Set spacing based on what is the most likely adjacent atom type.
|
|
// See TeXbook p170.
|
|
if (group.mclass === "mbin") {
|
|
node.attributes.lspace = "0.22em"; // medium space
|
|
node.attributes.rspace = "0.22em";
|
|
} else if (group.mclass === "mpunct") {
|
|
node.attributes.lspace = "0em";
|
|
node.attributes.rspace = "0.17em"; // thinspace
|
|
} else if (group.mclass === "mopen" || group.mclass === "mclose") {
|
|
node.attributes.lspace = "0em";
|
|
node.attributes.rspace = "0em";
|
|
} else if (group.mclass === "minner") {
|
|
node.attributes.lspace = "0.0556em"; // 1 mu is the most likely option
|
|
node.attributes.width = "+0.1111em";
|
|
}
|
|
// MathML <mo> default space is 5/18 em, so <mrel> needs no action.
|
|
// Ref: https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mo
|
|
}
|
|
return node;
|
|
}
|
|
|
|
// Math class commands except \mathop
|
|
defineFunction({
|
|
type: "mclass",
|
|
names: [
|
|
"\\mathord", "\\mathbin", "\\mathrel", "\\mathopen",
|
|
"\\mathclose", "\\mathpunct", "\\mathinner",
|
|
],
|
|
props: {
|
|
numArgs: 1,
|
|
primitive: true,
|
|
},
|
|
handler({parser, funcName}, args) {
|
|
const body = args[0];
|
|
return {
|
|
type: "mclass",
|
|
mode: parser.mode,
|
|
mclass: "m" + funcName.slice(5), // TODO(kevinb): don't prefix with 'm'
|
|
body: ordargument(body),
|
|
isCharacterBox: utils.isCharacterBox(body),
|
|
};
|
|
},
|
|
htmlBuilder,
|
|
mathmlBuilder,
|
|
});
|
|
|
|
export const binrelClass = (arg: AnyParseNode): string => {
|
|
// \binrel@ spacing varies with (bin|rel|ord) of the atom in the argument.
|
|
// (by rendering separately and with {}s before and after, and measuring
|
|
// the change in spacing). We'll do roughly the same by detecting the
|
|
// atom type directly.
|
|
const atom = (arg.type === "ordgroup" && arg.body.length ? arg.body[0] : arg);
|
|
if (atom.type === "atom" && (atom.family === "bin" || atom.family === "rel")) {
|
|
return "m" + atom.family;
|
|
} else {
|
|
return "mord";
|
|
}
|
|
};
|
|
|
|
// \@binrel{x}{y} renders like y but as mbin/mrel/mord if x is mbin/mrel/mord.
|
|
// This is equivalent to \binrel@{x}\binrel@@{y} in AMSTeX.
|
|
defineFunction({
|
|
type: "mclass",
|
|
names: ["\\@binrel"],
|
|
props: {
|
|
numArgs: 2,
|
|
},
|
|
handler({parser}, args) {
|
|
return {
|
|
type: "mclass",
|
|
mode: parser.mode,
|
|
mclass: binrelClass(args[0]),
|
|
body: ordargument(args[1]),
|
|
isCharacterBox: utils.isCharacterBox(args[1]),
|
|
};
|
|
},
|
|
});
|
|
|
|
// Build a relation or stacked op by placing one symbol on top of another
|
|
defineFunction({
|
|
type: "mclass",
|
|
names: ["\\stackrel", "\\overset", "\\underset"],
|
|
props: {
|
|
numArgs: 2,
|
|
},
|
|
handler({parser, funcName}, args) {
|
|
const baseArg = args[1];
|
|
const shiftedArg = args[0];
|
|
|
|
let mclass;
|
|
if (funcName !== "\\stackrel") {
|
|
// LaTeX applies \binrel spacing to \overset and \underset.
|
|
mclass = binrelClass(baseArg);
|
|
} else {
|
|
mclass = "mrel"; // for \stackrel
|
|
}
|
|
|
|
const baseOp = {
|
|
type: "op",
|
|
mode: baseArg.mode,
|
|
limits: true,
|
|
alwaysHandleSupSub: true,
|
|
parentIsSupSub: false,
|
|
symbol: false,
|
|
suppressBaseShift: funcName !== "\\stackrel",
|
|
body: ordargument(baseArg),
|
|
};
|
|
|
|
const supsub = {
|
|
type: "supsub",
|
|
mode: shiftedArg.mode,
|
|
base: baseOp,
|
|
sup: funcName === "\\underset" ? null : shiftedArg,
|
|
sub: funcName === "\\underset" ? shiftedArg : null,
|
|
};
|
|
|
|
return {
|
|
type: "mclass",
|
|
mode: parser.mode,
|
|
mclass,
|
|
body: [supsub],
|
|
isCharacterBox: utils.isCharacterBox(supsub),
|
|
};
|
|
},
|
|
htmlBuilder,
|
|
mathmlBuilder,
|
|
});
|
|
|