Migrate genfrac, lap, smash, op, mod, and katex into their own files in src/functions (#871)

Fixes #869
This commit is contained in:
Kevin Barabash
2017-11-11 17:45:44 -05:00
committed by GitHub
parent 25db6095a1
commit 632f5af1c3
10 changed files with 859 additions and 752 deletions

253
src/functions/genfrac.js Normal file
View File

@@ -0,0 +1,253 @@
// @flow
import defineFunction from "../defineFunction";
import buildCommon from "../buildCommon";
import delimiter from "../delimiter";
import mathMLTree from "../mathMLTree";
import Style from "../Style";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
defineFunction({
type: "genfrac",
names: [
"\\dfrac", "\\frac", "\\tfrac",
"\\dbinom", "\\binom", "\\tbinom",
"\\\\atopfrac", // cant be entered directly
],
props: {
numArgs: 2,
greediness: 2,
},
handler: (context, args) => {
const numer = args[0];
const denom = args[1];
let hasBarLine;
let leftDelim = null;
let rightDelim = null;
let size = "auto";
switch (context.funcName) {
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;
default:
throw new Error("Unrecognized genfrac command");
}
switch (context.funcName) {
case "\\dfrac":
case "\\dbinom":
size = "display";
break;
case "\\tfrac":
case "\\tbinom":
size = "text";
break;
}
return {
type: "genfrac",
numer: numer,
denom: denom,
hasBarLine: hasBarLine,
leftDelim: leftDelim,
rightDelim: rightDelim,
size: size,
};
},
htmlBuilder: (group, options) => {
// Fractions are handled in the TeXbook on pages 444-445, rules 15(a-e).
// Figure out what style this fraction should be in based on the
// function used
let style = options.style;
if (group.value.size === "display") {
style = Style.DISPLAY;
} else if (group.value.size === "text") {
style = Style.TEXT;
}
const nstyle = style.fracNum();
const dstyle = style.fracDen();
let newOptions;
newOptions = options.havingStyle(nstyle);
const numerm = html.buildGroup(group.value.numer, newOptions, options);
newOptions = options.havingStyle(dstyle);
const denomm = html.buildGroup(group.value.denom, newOptions, options);
let rule;
let ruleWidth;
let ruleSpacing;
if (group.value.hasBarLine) {
rule = html.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 (ruleWidth === 0) {
// Rule 15c
const candidateClearance =
(numShift - numerm.depth) - (denomm.height - denomShift);
if (candidateClearance < clearance) {
numShift += 0.5 * (clearance - candidateClearance);
denomShift += 0.5 * (clearance - candidateClearance);
}
frac = buildCommon.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 = buildCommon.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 {
delimSize = options.fontMetrics().delim2;
}
let leftDelim;
let rightDelim;
if (group.value.leftDelim == null) {
leftDelim = html.makeNullDelimiter(options, ["mopen"]);
} else {
leftDelim = delimiter.customSizedDelim(
group.value.leftDelim, delimSize, true,
options.havingStyle(style), group.mode, ["mopen"]);
}
if (group.value.rightDelim == null) {
rightDelim = html.makeNullDelimiter(options, ["mclose"]);
} else {
rightDelim = delimiter.customSizedDelim(
group.value.rightDelim, delimSize, true,
options.havingStyle(style), group.mode, ["mclose"]);
}
return buildCommon.makeSpan(
["mord"].concat(newOptions.sizingClasses(options)),
[leftDelim, buildCommon.makeSpan(["mfrac"], [frac]), rightDelim],
options);
},
mathmlBuilder: (group, options) => {
const node = new mathMLTree.MathNode(
"mfrac",
[
mml.buildGroup(group.value.numer, options),
mml.buildGroup(group.value.denom, options),
]);
if (!group.value.hasBarLine) {
node.setAttribute("linethickness", "0px");
}
if (group.value.leftDelim != null || group.value.rightDelim != null) {
const withDelims = [];
if (group.value.leftDelim != null) {
const leftOp = new mathMLTree.MathNode(
"mo", [new mathMLTree.TextNode(group.value.leftDelim)]);
leftOp.setAttribute("fence", "true");
withDelims.push(leftOp);
}
withDelims.push(node);
if (group.value.rightDelim != null) {
const rightOp = new mathMLTree.MathNode(
"mo", [new mathMLTree.TextNode(group.value.rightDelim)]);
rightOp.setAttribute("fence", "true");
withDelims.push(rightOp);
}
const outerNode = new mathMLTree.MathNode("mrow", withDelims);
return outerNode;
}
return node;
},
});