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]); [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). // 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 // Figure out what style this fraction should be in based on the
// function used // function used
var fstyle = options.style; var fstyle = options.style;
if (group.value.size === "dfrac") { if (group.value.size === "display") {
fstyle = Style.DISPLAY; fstyle = Style.DISPLAY;
} else if (group.value.size === "tfrac") { } else if (group.value.size === "text") {
fstyle = Style.TEXT; fstyle = Style.TEXT;
} }
@@ -384,30 +384,55 @@ var groupTypes = {
var denom = buildGroup(group.value.denom, options.withStyle(dstyle)); var denom = buildGroup(group.value.denom, options.withStyle(dstyle));
var denomreset = makeSpan([fstyle.reset(), dstyle.cls()], [denom]); var denomreset = makeSpan([fstyle.reset(), dstyle.cls()], [denom]);
var ruleWidth = fontMetrics.metrics.defaultRuleThickness / var ruleWidth;
if (group.value.hasBarLine) {
ruleWidth = fontMetrics.metrics.defaultRuleThickness /
options.style.sizeMultiplier; options.style.sizeMultiplier;
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;
if (fstyle.size === Style.DISPLAY.size) {
numShift = fontMetrics.metrics.num1;
denomShift = fontMetrics.metrics.denom1;
clearance = 3 * ruleWidth;
} else { } else {
numShift = fontMetrics.metrics.num2; ruleWidth = 0;
denomShift = fontMetrics.metrics.denom2;
clearance = ruleWidth;
} }
// 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;
} else {
if (ruleWidth > 0) {
numShift = fontMetrics.metrics.num2;
clearance = ruleWidth;
} else {
numShift = fontMetrics.metrics.num3;
clearance = 3 * fontMetrics.metrics.defaultRuleThickness;
}
denomShift = fontMetrics.metrics.denom2;
}
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);
}
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; var axisHeight = fontMetrics.metrics.axisHeight;
// Rule 15d
if ((numShift - numer.depth) - (axisHeight + 0.5 * ruleWidth) if ((numShift - numer.depth) - (axisHeight + 0.5 * ruleWidth)
< clearance) { < clearance) {
numShift += numShift +=
@@ -422,22 +447,55 @@ var groupTypes = {
(denom.height - denomShift)); (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); var midShift = -(axisHeight - 0.5 * ruleWidth);
var frac = buildCommon.makeVList([ frac = buildCommon.makeVList([
{type: "elem", elem: denomreset, shift: denomShift}, {type: "elem", elem: denomreset, shift: denomShift},
{type: "elem", elem: mid, shift: midShift}, {type: "elem", elem: mid, shift: midShift},
{type: "elem", elem: numerreset, shift: -numShift} {type: "elem", elem: numerreset, shift: -numShift}
], "individualShift", null, options); ], "individualShift", null, options);
}
// Since we manually change the style sometimes (with \dfrac or \tfrac), // Since we manually change the style sometimes (with \dfrac or \tfrac),
// account for the possible size change here. // account for the possible size change here.
frac.height *= fstyle.sizeMultiplier / options.style.sizeMultiplier; frac.height *= fstyle.sizeMultiplier / options.style.sizeMultiplier;
frac.depth *= 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( return makeSpan(
["minner", "mfrac", options.style.reset(), fstyle.cls()], ["minner", options.style.reset(), fstyle.cls()],
[frac], options.getColor()); innerChildren,
options.getColor());
}, },
spacing: function(group, options, prev) { spacing: function(group, options, prev) {

View File

@@ -1,5 +1,7 @@
/* jshint unused:false */ /* jshint unused:false */
var Style = require("./Style");
/** /**
* This file contains metrics regarding fonts and individual symbols. The sigma * This file contains metrics regarding fonts and individual symbols. The sigma
* and xi variables, as well as the metricMap map contain data extracted from * 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 sigma18 = 0.386;
var sigma19 = 0.050; var sigma19 = 0.050;
var sigma20 = 2.390; var sigma20 = 2.390;
var sigma21 = 0.101; var sigma21 = 1.01;
var sigma21Script = 0.81;
var sigma21ScriptScript = 0.71;
var sigma22 = 0.250; var sigma22 = 0.250;
// These font metrics are extracted from TeX by using // These font metrics are extracted from TeX by using
@@ -81,8 +85,6 @@ var metrics = {
sub2: sigma17, sub2: sigma17,
supDrop: sigma18, supDrop: sigma18,
subDrop: sigma19, subDrop: sigma19,
delim1: sigma20,
delim2: sigma21,
axisHeight: sigma22, axisHeight: sigma22,
defaultRuleThickness: xi8, defaultRuleThickness: xi8,
bigOpSpacing1: xi9, bigOpSpacing1: xi9,
@@ -90,7 +92,21 @@ var metrics = {
bigOpSpacing3: xi11, bigOpSpacing3: xi11,
bigOpSpacing4: xi12, bigOpSpacing4: xi12,
bigOpSpacing5: xi13, 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 // This map contains a mapping from font name and character code to character

View File

@@ -329,16 +329,55 @@ var duplicatedFunctions = [
// Fractions // Fractions
{ {
funcs: ["\\dfrac", "\\frac", "\\tfrac"], funcs: [
"\\dfrac", "\\frac", "\\tfrac",
"\\dbinom", "\\binom", "\\tbinom"
],
data: { data: {
numArgs: 2, numArgs: 2,
greediness: 2, greediness: 2,
handler: function(func, numer, denom) { 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 { return {
type: "frac", type: "genfrac",
numer: numer, numer: numer,
denom: denom, 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}" "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", "name": "NestedFractions",
"screenSize": [1024, 768], "screenSize": [1024, 768],

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

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