Pratyush-01's picture
Upload folder using huggingface_hub
9d1374f verified
import defineFunction, {normalizeArgument} from "../defineFunction";
import {makeLineSpan, makeSpan, makeVList} from "../buildCommon";
import {makeCustomSizedDelim} from "../delimiter";
import {MathNode, TextNode} from "../mathMLTree";
import type {ParseNode} from "../parseNode";
import Style from "../Style";
import {assertNodeType} from "../parseNode";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
import {calculateSize, makeEm} from "../units";
import type {StyleStr} from "../types";
import type {HtmlBuilder, MathMLBuilder} from "../defineFunction";
const htmlBuilder: HtmlBuilder<"genfrac"> = (group, options) => {
// Fractions are handled in the TeXbook on pages 444-445, rules 15(a-e).
const style = options.style;
const nstyle = style.fracNum();
const dstyle = style.fracDen();
let newOptions;
newOptions = options.havingStyle(nstyle);
const numerm = html.buildGroup(group.numer, newOptions, options);
if (group.continued) {
// \cfrac inserts a \strut into the numerator.
// Get \strut dimensions from TeXbook page 353.
const hStrut = 8.5 / options.fontMetrics().ptPerEm;
const dStrut = 3.5 / options.fontMetrics().ptPerEm;
numerm.height = numerm.height < hStrut ? hStrut : numerm.height;
numerm.depth = numerm.depth < dStrut ? dStrut : numerm.depth;
}
newOptions = options.havingStyle(dstyle);
const denomm = html.buildGroup(group.denom, newOptions, options);
let rule;
let ruleWidth;
let ruleSpacing;
if (group.hasBarLine) {
if (group.barSize) {
ruleWidth = calculateSize(group.barSize, options);
rule = makeLineSpan("frac-line", options, ruleWidth);
} else {
rule = makeLineSpan("frac-line", options);
}
ruleWidth = rule.height;
ruleSpacing = rule.height;
} else {
rule = null;
ruleWidth = 0;
ruleSpacing = options.fontMetrics().defaultRuleThickness;
}
// Rule 15b
let numShift;
let clearance;
let denomShift;
if (style.size === Style.DISPLAY.size) {
numShift = options.fontMetrics().num1;
if (ruleWidth > 0) {
clearance = 3 * ruleSpacing;
} else {
clearance = 7 * ruleSpacing;
}
denomShift = options.fontMetrics().denom1;
} else {
if (ruleWidth > 0) {
numShift = options.fontMetrics().num2;
clearance = ruleSpacing;
} else {
numShift = options.fontMetrics().num3;
clearance = 3 * ruleSpacing;
}
denomShift = options.fontMetrics().denom2;
}
let frac;
if (!rule) {
// Rule 15c
const candidateClearance =
(numShift - numerm.depth) - (denomm.height - denomShift);
if (candidateClearance < clearance) {
numShift += 0.5 * (clearance - candidateClearance);
denomShift += 0.5 * (clearance - candidateClearance);
}
frac = makeVList({
positionType: "individualShift",
children: [
{type: "elem", elem: denomm, shift: denomShift},
{type: "elem", elem: numerm, shift: -numShift},
],
}, options);
} else {
// Rule 15d
const axisHeight = options.fontMetrics().axisHeight;
if ((numShift - numerm.depth) - (axisHeight + 0.5 * ruleWidth) <
clearance) {
numShift +=
clearance - ((numShift - numerm.depth) -
(axisHeight + 0.5 * ruleWidth));
}
if ((axisHeight - 0.5 * ruleWidth) - (denomm.height - denomShift) <
clearance) {
denomShift +=
clearance - ((axisHeight - 0.5 * ruleWidth) -
(denomm.height - denomShift));
}
const midShift = -(axisHeight - 0.5 * ruleWidth);
frac = makeVList({
positionType: "individualShift",
children: [
{type: "elem", elem: denomm, shift: denomShift},
{type: "elem", elem: rule, shift: midShift},
{type: "elem", elem: numerm, shift: -numShift},
],
}, options);
}
// Since we manually change the style sometimes (with \dfrac or \tfrac),
// account for the possible size change here.
newOptions = options.havingStyle(style);
frac.height *= newOptions.sizeMultiplier / options.sizeMultiplier;
frac.depth *= newOptions.sizeMultiplier / options.sizeMultiplier;
// Rule 15e
let delimSize;
if (style.size === Style.DISPLAY.size) {
delimSize = options.fontMetrics().delim1;
} else if (style.size === Style.SCRIPTSCRIPT.size) {
delimSize = options.havingStyle(Style.SCRIPT).fontMetrics().delim2;
} else {
delimSize = options.fontMetrics().delim2;
}
let leftDelim;
let rightDelim;
if (group.leftDelim == null) {
leftDelim = html.makeNullDelimiter(options, ["mopen"]);
} else {
leftDelim = makeCustomSizedDelim(
group.leftDelim, delimSize, true,
options.havingStyle(style), group.mode, ["mopen"]);
}
if (group.continued) {
rightDelim = makeSpan([]); // zero width for \cfrac
} else if (group.rightDelim == null) {
rightDelim = html.makeNullDelimiter(options, ["mclose"]);
} else {
rightDelim = makeCustomSizedDelim(
group.rightDelim, delimSize, true,
options.havingStyle(style), group.mode, ["mclose"]);
}
return makeSpan(
["mord"].concat(newOptions.sizingClasses(options)),
[leftDelim, makeSpan(["mfrac"], [frac]), rightDelim],
options);
};
const mathmlBuilder: MathMLBuilder<"genfrac"> = (group, options) => {
const node = new MathNode(
"mfrac",
[
mml.buildGroup(group.numer, options),
mml.buildGroup(group.denom, options),
]);
if (!group.hasBarLine) {
node.setAttribute("linethickness", "0px");
} else if (group.barSize) {
const ruleWidth = calculateSize(group.barSize, options);
node.setAttribute("linethickness", makeEm(ruleWidth));
}
if (group.leftDelim != null || group.rightDelim != null) {
const withDelims = [];
if (group.leftDelim != null) {
const leftOp = new MathNode(
"mo",
[new TextNode(group.leftDelim.replace("\\", ""))]
);
leftOp.setAttribute("fence", "true");
withDelims.push(leftOp);
}
withDelims.push(node);
if (group.rightDelim != null) {
const rightOp = new MathNode(
"mo",
[new TextNode(group.rightDelim.replace("\\", ""))]
);
rightOp.setAttribute("fence", "true");
withDelims.push(rightOp);
}
return mml.makeRow(withDelims);
}
return node;
};
const wrapWithStyle = (
frac: ParseNode<"genfrac">,
style?: StyleStr | null,
): ParseNode<"genfrac"> => {
if (!style) {
return frac;
}
const wrapper: ParseNode<"styling"> = {
type: "styling",
mode: frac.mode,
style,
body: [frac],
};
// @ts-ignore defineFunction handler needs to return ParseNode<"genfrac">
return wrapper;
};
defineFunction({
type: "genfrac",
names: [
"\\cfrac", "\\dfrac", "\\frac", "\\tfrac",
"\\dbinom", "\\binom", "\\tbinom",
"\\\\atopfrac", // can’t be entered directly
"\\\\bracefrac", "\\\\brackfrac", // ditto
],
props: {
numArgs: 2,
allowedInArgument: true,
},
handler: ({parser, funcName}, args) => {
const numer = args[0];
const denom = args[1];
let hasBarLine: boolean;
let leftDelim: string | null = null;
let rightDelim: string | null = null;
switch (funcName) {
case "\\cfrac":
case "\\dfrac":
case "\\frac":
case "\\tfrac":
hasBarLine = true;
break;
case "\\\\atopfrac":
hasBarLine = false;
break;
case "\\dbinom":
case "\\binom":
case "\\tbinom":
hasBarLine = false;
leftDelim = "(";
rightDelim = ")";
break;
case "\\\\bracefrac":
hasBarLine = false;
leftDelim = "\\{";
rightDelim = "\\}";
break;
case "\\\\brackfrac":
hasBarLine = false;
leftDelim = "[";
rightDelim = "]";
break;
default:
throw new Error("Unrecognized genfrac command");
}
const continued = funcName === "\\cfrac";
let style = null;
if (continued || funcName.startsWith("\\d")) {
style = "display" as StyleStr;
} else if (funcName.startsWith("\\t")) {
style = "text" as StyleStr;
}
return wrapWithStyle({
type: "genfrac",
mode: parser.mode,
numer,
denom,
continued,
hasBarLine,
leftDelim,
rightDelim,
barSize: null,
}, style);
},
htmlBuilder,
mathmlBuilder,
});
// Infix generalized fractions -- these are not rendered directly, but replaced
// immediately by one of the variants above.
defineFunction({
type: "infix",
names: ["\\over", "\\choose", "\\atop", "\\brace", "\\brack"],
props: {
numArgs: 0,
infix: true,
},
handler({parser, funcName, token}) {
let replaceWith;
switch (funcName) {
case "\\over":
replaceWith = "\\frac";
break;
case "\\choose":
replaceWith = "\\binom";
break;
case "\\atop":
replaceWith = "\\\\atopfrac";
break;
case "\\brace":
replaceWith = "\\\\bracefrac";
break;
case "\\brack":
replaceWith = "\\\\brackfrac";
break;
default:
throw new Error("Unrecognized infix genfrac command");
}
return {
type: "infix",
mode: parser.mode,
replaceWith,
token,
};
},
});
const stylArray: StyleStr[] = ["display", "text", "script", "scriptscript"];
const delimFromValue = function(delimString: string): string | null {
let delim = null;
if (delimString.length > 0) {
delim = delimString;
delim = delim === "." ? null : delim;
}
return delim;
};
defineFunction({
type: "genfrac",
names: ["\\genfrac"],
props: {
numArgs: 6,
allowedInArgument: true,
argTypes: ["math", "math", "size", "text", "math", "math"],
},
handler({parser}, args) {
const numer = args[4];
const denom = args[5];
// Look into the parse nodes to get the desired delimiters.
const leftNode = normalizeArgument(args[0]);
const leftDelim = leftNode.type === "atom" && leftNode.family === "open"
? delimFromValue(leftNode.text) : null;
const rightNode = normalizeArgument(args[1]);
const rightDelim = rightNode.type === "atom" && rightNode.family === "close"
? delimFromValue(rightNode.text) : null;
const barNode = assertNodeType(args[2], "size");
let hasBarLine: boolean;
let barSize = null;
if (barNode.isBlank) {
// \genfrac acts differently than \above.
// \genfrac treats an empty size group as a signal to use a
// standard bar size. \above would see size = 0 and omit the bar.
hasBarLine = true;
} else {
barSize = barNode.value;
hasBarLine = barSize.number > 0;
}
// Find out if we want displaystyle, textstyle, etc.
let size = null;
let styl = args[3];
if (styl.type === "ordgroup") {
if (styl.body.length > 0) {
const textOrd = assertNodeType(styl.body[0], "textord");
size = stylArray[Number(textOrd.text)] as StyleStr;
}
} else {
styl = assertNodeType(styl, "textord");
size = stylArray[Number(styl.text)] as StyleStr;
}
return wrapWithStyle({
type: "genfrac",
mode: parser.mode,
numer,
denom,
continued: false,
hasBarLine,
barSize,
leftDelim,
rightDelim,
}, size);
},
});
// \above is an infix fraction that also defines a fraction bar size.
defineFunction({
type: "infix",
names: ["\\above"],
props: {
numArgs: 1,
argTypes: ["size"],
infix: true,
},
handler({parser, funcName, token}, args) {
return {
type: "infix",
mode: parser.mode,
replaceWith: "\\\\abovefrac",
size: assertNodeType(args[0], "size").value,
token,
};
},
});
defineFunction({
type: "genfrac",
names: ["\\\\abovefrac"],
props: {
numArgs: 3,
argTypes: ["math", "size", "math"],
},
handler: ({parser, funcName}, args) => {
const numer = args[0];
const barSize = assertNodeType(args[1], "infix").size;
if (!barSize) {
throw new Error(
`\\\\abovefrac expected size, but got ${String(barSize)}`);
}
const denom = args[2];
const hasBarLine = barSize.number > 0;
return {
type: "genfrac",
mode: parser.mode,
numer,
denom,
continued: false,
hasBarLine,
barSize,
leftDelim: null,
rightDelim: null,
};
},
});