mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-16 08:18:39 +00:00
Refactor genfrac.js (#1455)
This commit is contained in:
@@ -9,6 +9,202 @@ import ParseNode from "../ParseNode";
|
|||||||
import * as html from "../buildHTML";
|
import * as html from "../buildHTML";
|
||||||
import * as mml from "../buildMathML";
|
import * as mml from "../buildMathML";
|
||||||
|
|
||||||
|
const 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.size === Style.DISPLAY.size) {
|
||||||
|
// We're in a \tfrac but incoming style is displaystyle, so:
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (group.value.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.value.denom, newOptions, options);
|
||||||
|
|
||||||
|
let rule;
|
||||||
|
let ruleWidth;
|
||||||
|
let ruleSpacing;
|
||||||
|
if (group.value.hasBarLine) {
|
||||||
|
rule = buildCommon.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 = 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.continued) {
|
||||||
|
rightDelim = buildCommon.makeSpan([]); // zero width for \cfrac
|
||||||
|
} else 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
const 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mml.makeRow(withDelims);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
|
||||||
defineFunction({
|
defineFunction({
|
||||||
type: "genfrac",
|
type: "genfrac",
|
||||||
names: [
|
names: [
|
||||||
@@ -83,200 +279,9 @@ defineFunction({
|
|||||||
size: size,
|
size: size,
|
||||||
}, parser.mode);
|
}, parser.mode);
|
||||||
},
|
},
|
||||||
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.size === Style.DISPLAY.size) {
|
|
||||||
// We're in a \tfrac but incoming style is displaystyle, so:
|
|
||||||
style = Style.TEXT;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nstyle = style.fracNum();
|
htmlBuilder,
|
||||||
const dstyle = style.fracDen();
|
mathmlBuilder,
|
||||||
let newOptions;
|
|
||||||
|
|
||||||
newOptions = options.havingStyle(nstyle);
|
|
||||||
const numerm = html.buildGroup(group.value.numer, newOptions, options);
|
|
||||||
|
|
||||||
if (group.value.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.value.denom, newOptions, options);
|
|
||||||
|
|
||||||
let rule;
|
|
||||||
let ruleWidth;
|
|
||||||
let ruleSpacing;
|
|
||||||
if (group.value.hasBarLine) {
|
|
||||||
rule = buildCommon.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 = 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.continued) {
|
|
||||||
rightDelim = buildCommon.makeSpan([]); // zero width for \cfrac
|
|
||||||
} else 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
return mml.makeRow(withDelims);
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Infix generalized fractions -- these are not rendered directly, but replaced
|
// Infix generalized fractions -- these are not rendered directly, but replaced
|
||||||
@@ -316,4 +321,3 @@ defineFunction({
|
|||||||
}, parser.mode);
|
}, parser.mode);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user