Add support for \{,d,t}binom

Test Plan: `\binom xy^{\binom xy^{\binom xy}}` looks like something. `\dbinom` and `\tbinom` also seem to work.

Reviewers: emily

Reviewed By: emily

Subscribers: jessie

Differential Revision: http://phabricator.khanacademy.org/D13315
This commit is contained in:
Ben Alpert 2014-10-14 17:01:19 -07:00
parent 10e9b4ec12
commit 006a0a761c
6 changed files with 171 additions and 47 deletions

View File

@ -364,14 +364,14 @@ var groupTypes = {
[base, supsub]);
},
frac: function(group, options, prev) {
genfrac: function(group, options, prev) {
// 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
var fstyle = options.style;
if (group.value.size === "dfrac") {
if (group.value.size === "display") {
fstyle = Style.DISPLAY;
} else if (group.value.size === "tfrac") {
} else if (group.value.size === "text") {
fstyle = Style.TEXT;
}
@ -384,60 +384,118 @@ var groupTypes = {
var denom = buildGroup(group.value.denom, options.withStyle(dstyle));
var denomreset = makeSpan([fstyle.reset(), dstyle.cls()], [denom]);
var ruleWidth = fontMetrics.metrics.defaultRuleThickness /
options.style.sizeMultiplier;
var ruleWidth;
if (group.value.hasBarLine) {
ruleWidth = fontMetrics.metrics.defaultRuleThickness /
options.style.sizeMultiplier;
} else {
ruleWidth = 0;
}
var mid = makeSpan(
[options.style.reset(), Style.TEXT.cls(), "frac-line"]);
// Manually set the height of the line because its height is created in
// CSS
mid.height = ruleWidth;
// Rule 15b, 15d
var numShift, denomShift, clearance;
// Rule 15b
var numShift;
var clearance;
var denomShift;
if (fstyle.size === Style.DISPLAY.size) {
numShift = fontMetrics.metrics.num1;
if (ruleWidth > 0) {
clearance = 3 * ruleWidth;
} else {
clearance = 7 * fontMetrics.metrics.defaultRuleThickness;
}
denomShift = fontMetrics.metrics.denom1;
clearance = 3 * ruleWidth;
} else {
numShift = fontMetrics.metrics.num2;
if (ruleWidth > 0) {
numShift = fontMetrics.metrics.num2;
clearance = ruleWidth;
} else {
numShift = fontMetrics.metrics.num3;
clearance = 3 * fontMetrics.metrics.defaultRuleThickness;
}
denomShift = fontMetrics.metrics.denom2;
clearance = ruleWidth;
}
var axisHeight = fontMetrics.metrics.axisHeight;
var frac;
if (ruleWidth === 0) {
// Rule 15c
var candiateClearance =
(numShift - numer.depth) - (denom.height - denomShift);
if (candiateClearance < clearance) {
numShift += 0.5 * (clearance - candiateClearance);
denomShift += 0.5 * (clearance - candiateClearance);
}
// Rule 15d
if ((numShift - numer.depth) - (axisHeight + 0.5 * ruleWidth)
< clearance) {
numShift +=
clearance - ((numShift - numer.depth) -
(axisHeight + 0.5 * ruleWidth));
frac = buildCommon.makeVList([
{type: "elem", elem: denomreset, shift: denomShift},
{type: "elem", elem: numerreset, shift: -numShift}
], "individualShift", null, options);
} else {
// Rule 15d
var axisHeight = fontMetrics.metrics.axisHeight;
if ((numShift - numer.depth) - (axisHeight + 0.5 * ruleWidth)
< clearance) {
numShift +=
clearance - ((numShift - numer.depth) -
(axisHeight + 0.5 * ruleWidth));
}
if ((axisHeight - 0.5 * ruleWidth) - (denom.height - denomShift)
< clearance) {
denomShift +=
clearance - ((axisHeight - 0.5 * ruleWidth) -
(denom.height - denomShift));
}
var mid = makeSpan(
[options.style.reset(), Style.TEXT.cls(), "frac-line"]);
// Manually set the height of the line because its height is
// created in CSS
mid.height = ruleWidth;
var midShift = -(axisHeight - 0.5 * ruleWidth);
frac = buildCommon.makeVList([
{type: "elem", elem: denomreset, shift: denomShift},
{type: "elem", elem: mid, shift: midShift},
{type: "elem", elem: numerreset, shift: -numShift}
], "individualShift", null, options);
}
if ((axisHeight - 0.5 * ruleWidth) - (denom.height - denomShift)
< clearance) {
denomShift +=
clearance - ((axisHeight - 0.5 * ruleWidth) -
(denom.height - denomShift));
}
var midShift = -(axisHeight - 0.5 * ruleWidth);
var frac = buildCommon.makeVList([
{type: "elem", elem: denomreset, shift: denomShift},
{type: "elem", elem: mid, shift: midShift},
{type: "elem", elem: numerreset, shift: -numShift}
], "individualShift", null, options);
// Since we manually change the style sometimes (with \dfrac or \tfrac),
// account for the possible size change here.
frac.height *= fstyle.sizeMultiplier / options.style.sizeMultiplier;
frac.depth *= fstyle.sizeMultiplier / options.style.sizeMultiplier;
// Rule 15e
var innerChildren = [makeSpan(["mfrac"], [frac])];
var delimSize;
if (fstyle.size === Style.DISPLAY.size) {
delimSize = fontMetrics.metrics.delim1;
} else {
delimSize = fontMetrics.metrics.getDelim2(fstyle);
}
if (group.value.leftDelim != null) {
innerChildren.unshift(
delimiter.customSizedDelim(
group.value.leftDelim, delimSize, true,
options.withStyle(fstyle), group.mode)
);
}
if (group.value.rightDelim != null) {
innerChildren.push(
delimiter.customSizedDelim(
group.value.rightDelim, delimSize, true,
options.withStyle(fstyle), group.mode)
);
}
return makeSpan(
["minner", "mfrac", options.style.reset(), fstyle.cls()],
[frac], options.getColor());
["minner", options.style.reset(), fstyle.cls()],
innerChildren,
options.getColor());
},
spacing: function(group, options, prev) {

View File

@ -1,5 +1,7 @@
/* jshint unused:false */
var Style = require("./Style");
/**
* This file contains metrics regarding fonts and individual symbols. The sigma
* and xi variables, as well as the metricMap map contain data extracted from
@ -35,7 +37,9 @@ var sigma17 = 0.247;
var sigma18 = 0.386;
var sigma19 = 0.050;
var sigma20 = 2.390;
var sigma21 = 0.101;
var sigma21 = 1.01;
var sigma21Script = 0.81;
var sigma21ScriptScript = 0.71;
var sigma22 = 0.250;
// These font metrics are extracted from TeX by using
@ -81,8 +85,6 @@ var metrics = {
sub2: sigma17,
supDrop: sigma18,
subDrop: sigma19,
delim1: sigma20,
delim2: sigma21,
axisHeight: sigma22,
defaultRuleThickness: xi8,
bigOpSpacing1: xi9,
@ -90,7 +92,21 @@ var metrics = {
bigOpSpacing3: xi11,
bigOpSpacing4: xi12,
bigOpSpacing5: xi13,
ptPerEm: ptPerEm
ptPerEm: ptPerEm,
// TODO(alpert): Missing parallel structure here. We should probably add
// style-specific metrics for all of these.
delim1: sigma20,
getDelim2: function(style) {
if (style.size === Style.TEXT.size) {
return sigma21;
} else if (style.size === Style.SCRIPT.size) {
return sigma21Script;
} else if (style.size === Style.SCRIPTSCRIPT.size) {
return sigma21ScriptScript;
}
throw new Error("Unexpected style size: " + style.size);
}
};
// This map contains a mapping from font name and character code to character

View File

@ -329,16 +329,55 @@ var duplicatedFunctions = [
// Fractions
{
funcs: ["\\dfrac", "\\frac", "\\tfrac"],
funcs: [
"\\dfrac", "\\frac", "\\tfrac",
"\\dbinom", "\\binom", "\\tbinom"
],
data: {
numArgs: 2,
greediness: 2,
handler: function(func, numer, denom) {
var hasBarLine;
var leftDelim = null;
var rightDelim = null;
var size = "auto";
switch (func) {
case "\\dfrac":
case "\\frac":
case "\\tfrac":
hasBarLine = true;
break;
case "\\dbinom":
case "\\binom":
case "\\tbinom":
hasBarLine = false;
leftDelim = "(";
rightDelim = ")";
break;
default:
throw new Error("Unrecognized genfrac command");
}
switch (func) {
case "\\dfrac":
case "\\dbinom":
size = "display";
break;
case "\\tfrac":
case "\\tbinom":
size = "text";
break;
}
return {
type: "frac",
type: "genfrac",
numer: numer,
denom: denom,
size: func.slice(1)
hasBarLine: hasBarLine,
leftDelim: leftDelim,
rightDelim: rightDelim,
size: size
};
}
}

View File

@ -11,6 +11,12 @@
"url": "http://localhost:7936/test/huxley/test.html?m=\\dfrac{a}{b}\\frac{a}{b}\\tfrac{a}{b}"
},
{
"name": "BinomTest",
"screenSize": [1024, 768],
"url": "http://localhost:7936/test/huxley/test.html?m=\\dbinom{a}{b}\\tbinom{a}{b}^{\\binom{a}{b}+17}"
},
{
"name": "NestedFractions",
"screenSize": [1024, 768],

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,5 @@
[
{
"action": "screenshot"
}
]